Imported from Hand-Lock/guidonica (
AGENTS.md). Install upstream withnpx skills add Hand-Lock/guidonica. Copyright stays with the author.
Agent Guidelines & Repository Hygiene: Guidonica
1. Project Overview & Philosophy
Guidonica is a high-performance, client-only web tool for sight-reading and solfège practice, inspired by Guido d'Arezzo's historic manus guidonica pedagogy. It continuously streams procedurally generated music notation in synchronization with a synthesized metronome: across a fixed playhead on a scrolling tape (the steady and squishy views), or under a cursor along page lines that scroll up (the page view).
Suckless Engineering Philosophy
The core software architecture is strictly governed by an uncompromising "suckless", ultra-lightweight, and zero-bloat engineering philosophy:
- Zero Framework Bloat: No React, Vue, Svelte, or Angular. Written in Vanilla TypeScript driving native DOM APIs, HTML5 Canvas, and Web Audio API directly. Zero virtual DOM reconciliation, zero runtime reactivity overhead, zero state management dependencies. Shipped JS is ~142 kB gzipped: ~49 kB of app code (including the English dictionary) plus ~93 kB of VexFlow's font-free
vexflow/core; each other language is one lazy ~6.9 kB chunk (ADR 0059). The music font is a separate 19.6 kB Bravura subset (ADR 0058); never import the full'vexflow'entry, which inlines ~600 kB of fonts. - Single Source of Truth Hardware Clock: Visual motion and synthesized metronome audio clicks are mathematically linked to the hardware audio clock (
AudioContext.currentTime). Never measure time withsetInterval,setTimeout,Date.now()or visual time accumulators. The metronome's single 25 mssetIntervalis allowed because it only wakes the scheduler to queue clicks 100 ms ahead; every click time and tape offset is computed fromcurrentTime, so timer jitter never reaches what is heard or seen. Likewise the rAF timestamp only interpolates between the audio clock's coarse updates and is continuously pulled back to it (ADR 0089); position is never accumulated independently. Visual-auditory drift is mathematically impossible; noteheads cross the playhead at the exact physical microsecond the speaker clicks. - Hardware-Accelerated Measure Blitting: VexFlow layout and font glyph rasterization execute once onto an offscreen canvas per measure. The 60/120 FPS animation loop (
requestAnimationFrame) exclusively executes GPU-accelerated bit-block transfers (ctx.drawImage()). Zero per-frame layout, zero font parsing, sub-millisecond per-frame CPU time (< 1% CPU utilization). VexFlow rasterizes new measures in idle time between frames (requestIdleCallback, one measure per callback); a frame renders synchronously only if the stage itself would otherwise be uncovered. Beat UI changes only compositor properties (opacity,transform), so a click never triggers layout or paint (ADR 0091). - Bounded Ring-Buffer & Zero-Leak Memory Discipline: On the tape views the buffer holds only the measures covering the viewport plus 6 beats of lookahead (a few measures, more when zoomed out), never an unbounded history. Measures scrolling past the left edge are immediately evicted and their offscreen canvases dereferenced. The page view keeps the rows that fill the stage plus one below the reading line, and evicts the played lines above it beyond the few still shown (ADR 0110). An infinite 3-hour practice session maintains the exact same memory footprint (~30–45 MB process memory) as a 5-second test.
- Synthesized Audio (0-Byte Sample Downloads): Metronome clicks and woodblock timbres are synthesized live on the audio hardware using native Web Audio
OscillatorNode(sine/triangle) and exponentialGainNodeenvelopes. The drone (shruti box, pad) is built the same way from a cachedPeriodicWave,BiquadFilterNodes, LFOs andsetTargetAtTimeenvelopes (ADR 0092). Both meet in oneWaveShaperNodesoft clipper without oversampling, which adds no latency, so click plus drone never passes full scale (ADR 0095). Zero audio files (MP3/WAV/OGG) downloaded over the network. - Pure Mathematical Generation: Rhythms are sampled over a 32nd grid from a grammar derived from the notehead, rest and tuplet placement tables (ADR 0065). Pitches are generated via a symmetric discrete Markov random walk. Zero heavy music theory AI or rule engines.
- The Ergodic Generation Principle (State-Space Completeness): The music generator is strictly ergodic (the "infinite monkey theorem" heuristic). For any user-selected parameter configuration $\Omega = (\text{Clef}, \text{TimeSig}, \text{Subdivisions}, \text{Dotted}, \text{Ties}, \text{Intervals}, \text{Notes}, \text{Accidentals})$, every mathematically and grammatically valid permutation within $\Omega$ must possess a strictly non-zero generation probability ($P(\omega) > 0, \forall \omega \in \Omega$). No valid rhythmic figure (such as
q 8or8 qin 6/8, orq handh qin 3/4) or interval leap may be artificially suppressed, hijacked, or hardcoded out of existence. Generation rules must remain transparent, organized, and complete. - Pure CSS3 Liquid Glass & Zero CSS Frameworks: The entire Frutiger Aero / Aqua / Liquid Glass visual design is constructed via pure, hardware-composited CSS3 (
backdrop-filter, multi-stop linear/radial gradients, beveled shadows). Zero Tailwind runtime, zero CSS-in-JS libraries, zero sprite textures. Total CSS is ~10.2 kB gzipped. Text fonts (Alegreya, Alegreya Sans, Ubuntu Mono) are self-hosted Latin woff2 files insrc/fonts/(ADR 0060); never load fonts, scripts or styles from a third-party origin. - Offline Without Dependencies: A hand-written service worker (
src/sw.ts, ~2.3 kB) precaches the build so the app works offline after one visit (ADR 0063). No Workbox, no PWA plugin; never addskipWaiting()or runtime caching of navigations. - Strict Typing, Zero Silent Errors: TypeScript with
strict: true. Avoidany. Catch duration arithmetic mismatches, null pointers, and VexFlow type incompatibilities at compile time. Instant build in < 800ms.
Authoritative Specification
- Consult
SPEC.mdin the project root for the complete functional specification, metric linearity formulas, and musical generation requirements. - Consult
docs/DESIGN_MANIFESTO.mdfor the authoritative Aero-Guidonica visual styling and ergonomics manual.
2. Directory Structure & Architecture
guidonica/
├── AGENTS.md # Strict developer & agent rules (this file)
├── SPEC.md # Product and pedagogical specification
├── CHANGELOG.md # Keep a Changelog, CalVer; every commit adds its Unreleased entry (ADR 0078)
├── CONTRIBUTING.md # Contributor guide: inbound MIT + DCO sign-off (ADR 0067)
├── TRADEMARKS.md # Guidonica™ name & logo policy, AGPL §7(e) notice (ADR 0067)
├── SECURITY.md # Private vulnerability reporting to security@guidonica.it (ADR 0068)
├── PRIVACY.md # Privacy policy, English + Italian, linked from About and the ⋯ menu (ADR 0088)
├── README.md # Public overview, feature tour, 16:9 trailer, ADR table
├── LICENSE # GNU AGPL-3.0-or-later
├── .nvmrc # Node 22
├── package.json # Minimal dependencies (vite, typescript, vexflow; @vexflow-fonts/bravura as font source; vitest + happy-dom for tests)
├── pnpm-lock.yaml # Locked dependency tree (pnpm install --frozen-lockfile in CI)
├── pnpm-workspace.yaml # pnpm settings
├── tsconfig.json # Strict TypeScript configuration
├── vite.config.ts # Minimal Vite configuration (build.target = support baseline, ADR 0096) + serviceWorker(), channel(), localePages() and resetPages() build plugins (ADRs 0063, 0078, 0086, 0105, 0107)
├── index.html # Minimal semantic HTML shell
├── .claude/
│ └── skills/
│ └── run-guidonica/ # Agent run/screenshot skill + Playwright driver (dev-only)
│ ├── SKILL.md # How agents run, drive and screenshot the app
│ ├── driver.mjs # Headless run/screenshot driver, --browser chromium|firefox|webkit
│ └── chromium.mjs # Playwright + cached browser lookup (Chromium, Firefox, WebKit; ADR 0096), shared with build-banners.mjs
├── .github/
│ ├── FUNDING.yml # Sponsor button: Ko-fi only until GitHub Sponsors is approved (ADR 0074)
│ ├── pull_request_template.md # DCO + MIT checkboxes and hygiene checklist (ADR 0067)
│ └── workflows/
│ ├── deploy.yml # Typecheck, test, build:site, Pages deploy; tags create GitHub Releases; releases announced (ADRs 0018, 0078, 0081)
│ └── dco.yml # Signed-off-by check on every pull request commit (ADR 0067)
├── public/ # Copied verbatim: .nojekyll, favicon.svg/.ico, apple-touch-icon.png, icon-*.png, manifest.webmanifest, og-image.png (ADR 0061), robots.txt, sitemap.xml (ADR 0062), CNAME, .well-known/security.txt (ADR 0068)
├── public-nightly/ # Nightly icons, same names as public/, replace them in nightly builds; generated by build-icons.mjs (never hand-edit; ADR 0107)
├── scripts/
│ ├── build-site.mjs # site/ = latest v20* tag (release) + site/nightly/ = working tree (pnpm build:site; ADR 0078)
│ ├── changelog.mjs # CHANGELOG.md parser, cutRelease, releaseSection (shared by Vite, scripts, tests; ADR 0078)
│ ├── release.mjs # pnpm release: cuts Unreleased into the next CalVer version, bumps package.json (ADR 0078)
│ ├── release-notes.mjs # GitHub Release body for a tag, run by CI (ADR 0078)
│ ├── announce.mjs # Bluesky + Mastodon release thread from CHANGELOG.md, run by CI after deploy (ADR 0081)
│ ├── build-icons.mjs # Guidonian Hand mark generator, release and nightly icon sets (npm run icons; ADRs 0046–0048, 0107)
│ ├── build-banners.mjs # Social profile banners from live captures (pnpm banners, dev-only; ADR 0079)
│ ├── build-music-font.py # Bravura → Guidonica Notation subset (npm run music-font; fontTools, dev-only; ADR 0058)
│ ├── fetch-ui-fonts.mjs # Self-hosted text fonts from Google Fonts' Latin subsets (npm run ui-fonts, dev-only; ADR 0060)
│ └── *.d.mts # Type declarations for the .mjs scripts imported by tests and vite.config.ts
├── tests/ # Vitest suites (happy-dom), one *.test.ts per subsystem; pnpm test
├── docs/
│ ├── DESIGN_MANIFESTO.md # Aero-Guidonica design manifesto and visual rules
│ ├── brand/
│ │ ├── guidonica-mark.svg # README logo, generated by build-icons.mjs (never hand-edit)
│ │ ├── trailer-poster.jpg # Still of the 16:9 README trailer (the video is a GitHub attachment)
│ │ └── banners/ # Mastodon/Bluesky/X headers + YouTube art, generated by build-banners.mjs (ADR 0079)
│ └── adr/ # Architectural Decision Records & implementation notes
│ ├── README.md # ADR index and registration log
│ └── 0001-*.md # Specific architectural & subsystem records
└── src/
├── engine.ts # Engine entry point `guidonica/engine`: re-exports only, no app modules or build constants (ADRs 0104, 0111)
├── main.ts # Application bootstrapper, UI event wiring (bar, dock, inspector, ⋯ menu) & locale bootstrap
├── sw.ts # Offline service worker, compiled to dist/sw.js by vite.config.ts (ADR 0063)
├── state.ts # Typed session state and parameter interfaces
├── storage.ts # Validated localStorage settings, defaults, onboarding flag; per-channel keys (ADR 0078)
├── whatsNew.ts # "What's new": version compare, boot action, notes loaders & dialog rendering (ADR 0078)
├── env.d.ts # Build-time constants (__APP_VERSION__, __APP_CHANNEL__, …) and *.md?notes modules
├── share.ts # Shareable exercise links: #x=1 fragment encode/decode (ADR 0085)
├── summary.ts # Top-bar exercise summary chips: clef, meter, drone (ADR 0097)
├── tips.ts # Rotating tips: feature & community tip tables, pickTip rotation (ADR 0087)
├── presets.ts # Level presets, preview representations & signatures (ADR 0049, 0051, 0052)
├── style.css # All styles: light and dark themes, Olo accent, Liquid Glass materials
├── fonts/ # Self-hosted text fonts + OFL/UFL licences (ADR 0060; never hand-edit)
├── i18n/
│ ├── index.ts # Locale runtime: lazy chunks, t(), applyDom, note names & octave formats (ADR 0059)
│ ├── locales/ # en.ts (reference, defines Messages), it.ts, fr.ts, de.ts, es.ts
│ ├── landing.ts # Build-only head metadata of the /it/ /fr/ /de/ /es/ landing pages (ADR 0086)
│ └── changelog/ # it.md, fr.md, de.md, es.md: released notes translated at release time (ADR 0078)
├── utils/
│ ├── radioGroup.ts # Shared roving-tabindex radiogroup helpers (intro cards, language chips)
│ ├── tabs.ts # Roving-tabindex tablist for the settings inspector (ADR 0097)
│ ├── inAppBrowser.ts # Portrait-locked in-app browser detection for the landscape tip (ADR 0083)
│ ├── frameRate.ts # Steady ~30 fps rAF detector behind the Low Power Mode notice (ADR 0098)
│ ├── keyboard.ts # Physical keyboard guess: pointer media query + first real key press (ADR 0106)
│ ├── serviceWorker.ts # Production-only service worker registration (ADR 0063)
│ └── wakeLock.ts # Screen Wake Lock controller
├── audio/
│ ├── metronome.ts # Web Audio oscillator synthesis & clock scheduler
│ ├── click.ts # Click voice, accents & click track into any BaseAudioContext (ADR 0103)
│ ├── drone.ts # Drone synthesis: shruti box, pad (ADR 0092)
│ └── output.ts # Output stage: zero-latency soft clipper before the destination (ADR 0095)
├── notation/
│ ├── generator.ts # Grammar-driven rhythm sampler & pitch random-walk
│ ├── renderer.ts # VexFlow measure builder: offscreen canvas, or SVG for export (ADR 0103)
│ ├── fonts.ts # Music font registration, VexFlow.setFonts & readiness gate (ADR 0012, 0058)
│ ├── fonts/ # Generated by build-music-font.py (never hand-edit): woff2, codepoints.json, OFL.txt
│ ├── preview.ts # Intro notation thumbnails: level strips & clef icons (ADR 0050, 0052)
│ ├── random.ts # Random type and seeded mulberry32 (ADR 0103)
│ ├── spacing.ts # Steady, squishy & page spacing: engraved √2 note space, Hermite beatX, page density, typical bar width (ADRs 0109, 0110)
│ ├── ties.ts # Tie grammar, notehead & rest placement tables, rest spelling
│ └── types.ts # Musical data types (Note, Measure, Clef, TimeSignature)
└── scroller/
├── scroller.ts # Viewport canvas manager, rAF loop, measure blitting
├── page.ts # Page view: line breaking, justification, scroll curve (ADR 0110)
└── buffer.ts # Active measure ring-buffer & disposal pipeline
3. Strict Coding & Hygiene Rules
-
Type Safety & Compile Checks:
- Always run
npm run typecheck(tsc --noEmit) before committing code. All code must compile with zero errors and zero warnings. - Do not use
anyunless wrapping an un-typed third-party dynamic property. Create explicit interfaces and type unions (e.g.,type Clef = 'treble' | 'bass' | 'alto' | 'tenor';). - Prefer type guards and explicit null checks over non-null assertions (
!).
- Always run
-
Memory & Garbage Collection Discipline:
- In an infinite scroller, memory leaks quickly degrade performance.
- Measures that scroll past the left edge of the viewport must be cleanly evicted from the measure ring-buffer; in page view, so must the played lines above the few kept on screen (
evictRowsBefore, ADR 0110). - Do not retain references to offscreen canvases that are no longer visible.
-
Audio Safety & Lifecycle:
- Modern browsers require user interaction before resuming an
AudioContext. - Never call
audioCtx.resume()during module initialization. Only initialize or resumeAudioContextinside a direct user gesture (e.g., clicking "Start" or pressingSpace).
- Modern browsers require user interaction before resuming an
-
Clean Code & Styling:
- 2 spaces for indentation.
- No extraneous console logging in production modules (
console.logshould be removed before committing). - Strict adherence to the Aero-Guidonica Design Manifesto (
docs/DESIGN_MANIFESTO.md). All visual elements must follow the pure CSS3 Liquid Glass / Frutiger Aero / skeuomorphic tactile physics model without adding external CSS or JS dependencies.
-
Localization (ADR 0059):
- New UI text needs a key in every locale (
src/i18n/locales/*.ts);en.tsis the reference and the compiler rejects an incomplete locale. - Never hardcode user-visible strings in TS. Static HTML text keeps its English copy plus a
data-i18n*attribute;tests/i18n.test.tschecks that it matchesen. - Note names and octave formats come from the locale (
noteLabels,formatRange); the renderer receives a resolved label table. - Release notes are translated at release time only (ADR 0078):
CHANGELOG.mdUnreleased stays English, and each release adds its section tosrc/i18n/changelog/{it,fr,de,es}.mdwith the same versions, dates, section kinds and bullet counts,Internalomitted.tests/changelog.test.tsenforces the mirror.
- New UI text needs a key in every locale (
-
Procedural Ergodicity & Rule Transparency:
- Whenever modifying the procedural generator (
src/notation/generator.tsor related files), you must preserve metric and melodic ergodicity. - Never introduce hardcoded duration substitutions, silent omissions of valid rhythms, or hidden heuristics that reduce the reachable state space.
- All musical capabilities (e.g., dotted notes, ties, rests) must be transparently controllable by the user and mathematically reachable in the generator's rhythm grammar.
- Whenever modifying the procedural generator (
-
Licensing & Provenance (ADR 0067):
- The project is AGPL-3.0-or-later, and the copyright holder also ships it under other terms (paid store builds, Guidonica Studio, commercial engine licenses). Keep every line of code relicensable.
- Runtime dependencies and vendored assets must be permissive (MIT, BSD, ISC, Apache-2.0; OFL or UFL for fonts). Never add copyleft (GPL/AGPL/LGPL) or non-commercial (CC BY-NC) material.
- Never paste third-party code of unknown or copyleft license.
- Merge outside contributions only when every commit is DCO signed off under the
CONTRIBUTING.mdterms, and keep theSigned-off-bytrailers (rebase or merge commit; a squash message must carry them all). - Use of the name and logo follows
TRADEMARKS.md. Write Guidonica™, never ®, until a registration is granted. - Never write the private login mailbox or any plus-address (
name+tag@) of guidonica.it into the repository, its history or a commit message. Publish onlyhello@,legal@andsecurity@; refer to a service login as "a private per-service address (ADR 0068 §3)".tests/shellPrivacyAndShare.test.tsenforces it (ADR 0088). - Never commit paid-only features (store extras, Guidonica Studio such as video or PDF export) to this public repository. Anything pushed here is AGPL for everyone; those features live in a private repository.
4. Architectural Decision Records (ADRs) & Knowledge Continuity
- ADR Repository (
docs/adr/):- Whenever a subsystem is designed, an architectural choice is made, or an implementation method is introduced/modified, the agent must document it in
docs/adr/and register it indocs/adr/README.md. - Each ADR must specify: Status, Date, Context/Problem, Decisions & Implementation Methods (with math/code rationale), and Consequences.
- Whenever a subsystem is designed, an architectural choice is made, or an implementation method is introduced/modified, the agent must document it in
- LLM Knowledge Retention:
- The ADRs serve as the persistent source of truth so future LLM agents know with certainty what has been developed, which algorithms are active, and why specific engineering decisions were made.
5. Git & Version Control Hygiene
- Mandatory Commit & Push Upon Task Completion:
- Every time you finish a task requested by the user, you MUST create a git commit AND push it to remote (
git push origin main). - Every commit records its change in
CHANGELOG.mdunder## [Unreleased], in the same commit (ADR 0078). Write user-facing changes for users, in plain sentences, underAdded,Changed,Fixed,RemovedorSecurity, with an optional trailing(ADR NNNN); they are shown in the app. Docs, CI and refactors go underInternal(GitHub only). Never edit a released section. - Pushing to
origin/maintriggers.github/workflows/deploy.yml, which tests and publishes the commit to guidonica.it/nightly/ only. The root, guidonica.it, keeps serving the latest release tag. - The commit message must be written by the agent following Conventional Commits (
feat:,fix:,refactor:,perf:,docs:,chore:). - Always execute macOS Keychain loading before pushing in background agent subshells:
ssh-add --apple-load-keychain 2>&1 && git push origin main - Never leave uncommitted or unpushed changes at the end of a completed task unless explicitly instructed by the user.
- Every time you finish a task requested by the user, you MUST create a git commit AND push it to remote (
- Release Protocol (ADR 0078): only when the user explicitly says "release"; never on your own initiative.
- Confirm the latest
mainCI run is green:gh run list --branch main -L 1. pnpm release: checks a clean tree level withorigin/main, computes the CalVer version (YYYY.M.MICRO), moves Unreleased under it inCHANGELOG.md, bumpspackage.jsonand prints the section. It does not commit.- Write the release headline (ADR 0081): one
>line right under## [<version>] - <date>, in English, for users, naming the one or two biggest changes. It opens the Bluesky and Mastodon announcement and must fit Bluesky's 300-grapheme first post;pnpm testchecks it. It is not translated. - Translate the new section's user-facing sections into
src/i18n/changelog/{it,fr,de,es}.md, at the top, same structure,Internalomitted. pnpm typecheck && pnpm test && pnpm build:site.- Commit
chore(release): <version>, then:git tag -a v<version> -m "Guidonica <version>" ssh-add --apple-load-keychain 2>&1 && git push --atomic origin main v<version> - CI redeploys (root = the new tag) and the tag run creates the GitHub Release from the changelog section. After the deploy, the main run's
announcejob posts the release thread to @guidonica.it on Bluesky and @guidonica@mastodon.social (ADR 0081). A failedannounceleaves the site and the GitHub Release untouched; re-run it from the Actions UI, it never double-posts.
- Confirm the latest
- Repository Setup:
- Keep the repository self-contained and reproducible.
.gitignoremust ignorenode_modules/,dist/,site/,.DS_Store, and temporary test artifacts.
- Cross-Machine Reproducibility:
- Any developer on another machine must be able to run:
git clone <repo-url> cd guidonica pnpm install # or npm install pnpm dev # or npm run dev - No global binaries or environment variables should be assumed.
- Any developer on another machine must be able to run:
- macOS SSH Keychain Authentication:
- The user's SSH key passphrase is saved in macOS Keychain. When running
git push origin mainin background agent or non-interactive subshells, runssh-add --apple-load-keychainfirst to ensure the identity is loaded without interactive prompts.
- The user's SSH key passphrase is saved in macOS Keychain. When running
- Node.js & pnpm Runtime Requirement:
pnpm@11.8.0requires Node.js >= 22.13 (due to dependency onnode:sqlite). All environments,.nvmrc, and CI runners must target Node 22+.
6. Development & Verification Workflow
- Node.js Version: Node 22+ (
node -v>= 22.13.0) - Install Dependencies:
pnpm install(ornpm install) - Start Dev Server:
pnpm dev(ornpm run dev) - Typecheck:
pnpm typecheck(ornpm run typecheck) - Tests:
pnpm test(Vitest;pnpm test:watchfor watch mode) - Production Build:
pnpm build(ornpm run build) - Preview Build:
pnpm preview(ornpm run preview)
