A web app of focused practice tools for violinists. The first tool is Scales — a whole-neck fingerboard note map that shows where every note of a scale falls across the entire fingerboard at a glance. A second tool, the Tuner, is a live, microphone-based chromatic tuner.
Microphone & privacy. The Tuner listens through your microphone, but the audio is processed entirely on-device, in your browser — nothing is recorded, stored, or sent anywhere. There is no backend, so the audio never leaves your machine.
Status: shipped — v1 is live at strings-solo.com
Violin Tools v1 is live. The Scales note map — the whole-neck fingerboard view, scale-aware note spelling, reference overlays, motion, the ⌘K command palette, and the accessibility + mobile-reflow passes — is built against DESIGN.md (the spec for the whole thing: token system, color and contrast, typography, motion, components, accessibility, and the fingerboard note-map model) and deployed.
Install and run locally:
pnpm install # install workspace deps (the monorepo uses pnpm + Turborepo)
pnpm dev # run apps/web locally (Vite dev server)
pnpm build # build the static bundle to apps/web/dist/The four CI gates are pnpm typecheck / pnpm lint / pnpm test:coverage (the Test gate, which runs the Vitest suites and enforces per-workspace coverage thresholds) / pnpm build; a separate soft Playwright suite (pnpm test:e2e) covers motion, accessibility, mobile reflow, and the v1 acceptance + visual flows. See AGENTS.md → "Working in the tree" for the full command set.
Hosting (lean, ~$0/mo): it's a client-side static web app — no backend, no accounts, no personal data, no analytics. The built bundle is published to a public-read Google Cloud Storage bucket; a free Cloudflare Worker fronts it at the edge (TLS + CDN + the force-HTTPS and www→apex 301s). There is no GCP load balancer or Cloud CDN — those were deferred for cost and can be added in front of the same bucket if traffic or latency metrics ever justify it. Deploys are keyless: a GitHub Actions workflow (.github/workflows/deploy.yml) authenticates to GCP via Workload Identity Federation (no service-account JSON key) and syncs the bundle on every push to main. The hosting IaC lives in infra/ — see infra/README.md.
Some surfaces ship behind a runtime feature flag so a not-yet-public tool can be merged and deployed while it gets polish, without being visible to the public — and flipped on live without a redeploy. Today the only flag is intonation (the Intonation drill).
Mechanism. Flags resolve from three layers, lowest precedence to highest:
- Built-in defaults — compiled into the bundle (
intonationdefaults on in a dev build, off in a prod build). Synchronous, so first paint never waits on the network. - Remote
flags.json— a small JSON object at the bucket root (e.g.{ "intonation": true }), fetched once at boot from the same origin (Cloudflare → the GCS bucket). It merges over the defaults; on a 404 or network failure the defaults win (fail-closed for unreleased features). It is servedno-cache, so a flip is visible on the next load with no deploy. ?ff=URL override — per-device, persisted tolocalStorage:?ff=intonationturns a flag on for this device,?ff=-intonationclears it. Highest precedence — lets the maintainer reach a flag-off surface on prod regardless of the public default.
When a flag is off, its surface is absent (the nav item and command-palette row don't render), not disabled-and-badged.
Flipping a flag live (the bucket object, affects everyone):
scripts/flag.sh intonation status # print the current value
scripts/flag.sh intonation on # reveal it to the public
scripts/flag.sh intonation off # hide it againflag.sh read-modify-writes flags.json in place, preserving every other key, and uploads it with Cache-Control: no-cache and Content-Type: application/json. The bucket comes from $GCS_WEBSITE_BUCKET (or the GCS_WEBSITE_BUCKET repo Actions variable via gh); it needs an authenticated gcloud.
Why flags.json isn't in infra/ or the build. It's a runtime-managed object: Terraform manages the bucket, not its mutable contents, and the deploy (deploy.yml) explicitly excludes flags.json from its prune so a deploy never deletes a live flag. The flag value is operational state, flipped out-of-band — not source or build output.
DESIGN.md— the design source of truth. It wins on any design conflict, and it also defines the note-map's pitch model. Read it before any UI work.AGENTS.md— how the project is built: conventions, the PR/review process, agent guardrails, and the rules for keeping docs current. Source of truth for project and process;DESIGN.mdoutranks it on design.SECURITY.md— how to report a security problem, privately, and what to honestly expect back.
Most of the code here is written by AI coding agents, under human review, and squash-merged through pull requests. Every PR gets a real review before it lands — never a rubber-stamp. That development model is part of why the repo is public: the commit, PR, and review history is meant to be readable as a worked example of building software with agents.
The maintainer would otherwise keep this private; it's public to capture four specific benefits — (a) the blog at detached-node.dev links to real, live code; (b) the commit/PR/review trail demonstrates how the maintainer builds with AI agents; (c) public repos get free or better CI, static-hosting, and dependency/secret/code scanning; (d) it showcases the design and engineering craft. Public is not the same as audit-grade: there's no compliance, regulatory, or external-auditability requirement, and none is implied by the source being visible. See SECURITY.md for the full framing.
MIT — see LICENSE.