CLAUDE.md
Rama main · réplica de lectura del repositorio original.
# CLAUDE.md — songbird
songbird is a personal, self-hosted app for **annotating Scripture**: read a translation,
highlight a verse, attach a rich Markdown note anchored to it, find it later by tag or
meaning.
**songbird is built on top of Concord.** Concord runs on the network and exposes its REST
endpoints; **songbird is a separate app that consumes those endpoints** over HTTP to read
Scripture, search, and look up geography. Concord is the foundation and the data/API
provider; songbird is the client that sits on top of it. songbird depends on Concord;
Concord knows nothing about songbird.
The design canon is `docs/v1/SPEC.md`. Read it. This file is the always-on rules.
---
## The posture: built to last, built small
songbird is the app intended to grow (soap-journal was the learning exercise; Concord is the
foundation). "Built to last" means **clean bones, named boundaries, spec-first,
smallest-slice-first** — not big on day one. Ambition lives in the roadmap; discipline lives
in the slices. Every slice is a thin vertical cut through the whole app that ships on its own
— never a horizontal layer built in isolation.
## Hard invariants (do not violate)
1. **songbird consumes Concord over HTTP. It does not embed Concord, and it does not bundle
Bible text.** songbird calls Concord's REST endpoints (`/v1/verses`, `/v1/chapters/...`,
`/v1/translations`, `/v1/search`, later `/v1/semantic-search`, `/v1/places...`,
`/v1/cross-references/...`) for all Scripture, search, and geography. songbird never
stores or ships its own copy of the Bible text — it comes from Concord at request time.
2. **Concord's location is configuration, never an assumption.** songbird reads a single
`CONCORD_BASE_URL` and calls whatever is there. Concord may be on the **same host** or
**any other machine on the LAN**, on any port — the location is pure config. A localhost
default is fine (zero-config convenience), but **never hardcode "same server" / localhost
as a requirement**. The requirement is that Concord is *reachable* at that URL.
3. **When Concord is unreachable, error — no fallback.** If a Concord call fails (server
down, network gone, wrong URL), songbird surfaces a clear error. It does **not** attempt
offline operation, a bundled copy of the text, or graceful degradation. Concord is a hard
runtime HTTP dependency; its absence is an error state, not a mode to design around.
4. **The canonical-coordinate bridge.** Annotation anchors are **always** canonical — USFM
book code + chapter + verse — **never** a translation-specific id. An annotation is pinned
to an *address* (e.g. `JHN 3:16`), not to a verse in some translation's rendering. This is
clean over HTTP because Concord's endpoints already return canonical coordinates: songbird
reads a chapter from Concord, overlays its annotations by matching on those same
coordinates, and a note shows correctly in every translation it's scoped to — pinned to
the verse *address*, not the *text*. This is the invariant that keeps songbird from
inheriting soap-journal's "a note made in NKJV won't match a BSB lookup" limitation. It is
tested. Protect it like a load-bearing wall.
5. **songbird owns only its annotation data.** songbird's **own** SQLite database (under a
configurable `DATA_DIR`) holds annotations, tags, and users — songbird's SQLAlchemy
models, Alembic-migrated. No Bible text lives in it. (Caching Concord responses for
performance is a possible later optimization, not a change to this ownership.)
6. **Notes are stored as Markdown** — durable, portable, editor-agnostic. Never store
editor-native (TipTap/ProseMirror) JSON as the canonical form. If the editor is swapped,
the notes must survive as readable text.
7. **Never break a shipped slice.** Each merged slice leaves `main` working and usable.
Additive growth; no regressions.
## Git workflow
- **Branch per slice**, off `main`: `slice/N-short-name`.
- **Scoped conventional commits**, small and atomic: `feat(api)`, `feat(web)`,
`feat(reader)`, `feat(annotations)`, `fix(...)`, `test(...)`, `docs(...)`, `chore(...)`.
- **PR per slice** via `gh`. The PR body states what landed, the open-question answers, and
how it was verified.
- **Claude Code merges its own pull requests:** assign Kris (`kbennett2000`) to the PR, confirm
Kris shows as assignee, then merge once its checks are green, with a merge commit. If assigning
or merging fails, stop and tell Kris. **Never push to `main`. Never `--force`.**
- Plan Mode for every slice: produce a plan, Kris approves, then implement.
## Engineering principles
- **Tests are required**, and the canonical-coordinate bridge (invariant 4) gets explicit
tests. Mark slow/integration tests (including any that need a live Concord) so the fast
suite stays fast.
- **Type-checked and linted**: backend Pyright-strict-clean and Ruff-clean; frontend
TypeScript-strict and lint-clean. Green before a PR.
- **Migrations**: schema changes go through Alembic. No ad-hoc drift.
- **Single deployable unit**: a multi-stage Dockerfile builds the Vite SPA, then one uvicorn
process serves the static SPA + songbird's API.
- **Talking to Concord**: use one configured HTTP client against `CONCORD_BASE_URL`; handle
failures explicitly (invariant 3); don't scatter base URLs or assume co-location.
- **Dependency discipline**: songbird is deliberately lean — new dependencies need a reason.
Note the payoff of the HTTP model: the heavy ML (the embedding model + ONNX runtime) stays
in **Concord**; songbird reaches semantic search with an HTTP call and stays light. No ML
stack in songbird.
- **ADRs** in `docs/adr/` for genuinely new architectural decisions (not routine work).
- **Spec is canon — until reality corrects it.** If implementation reveals the spec is wrong
(it happened repeatedly building Concord), fix the spec in the same PR with a `docs:`
commit and note why. Reality wins; the spec gets updated, not worked around.
## Documentation
### The one rule everything serves
**Write for one real reader — and run every line past them.** Picture a specific person: curious,
non-technical, has never done this before, one frustration from closing the tab. Before anything
ships, hold each sentence and each step up to them and ask three things — *would they know what this
means? would they know how to do it? would they see why they're doing it?* Any "no" → cut it, explain
it, or move it. Every rule below is an instance of this one.
### The voice rules (each paired with the mistake it prevents — the mistake is what makes it stick)
- **Break the wall — a scannable page is itself reassurance.** Short paragraphs, frequent plain
subheads, numbered steps for anything sequential, a code block for anything they'd type, one bold
phrase per section. *Prevents:* a dense wall that signals "this is hard" before they've read a word.
- **No unexplained jargon — especially tooling.** Gloss and link every tool or term on first mention
(the editor, the terminal, the package manager, the language). *Prevents:* the reader stranded on a
word — and the subtler error of naming a scary unknown to "reassure" ("don't worry, no [X]"), which
only teaches them [X] is something to fear.
- **Just-in-time, not just-in-case.** Explain a thing in the step that needs it, never earlier. Put
the roadmap at the *end* as encouragement, not a syllabus at the front. *Prevents:* front-loaded
caveats — and, worst, answering an unasked question, because a reassurance *plants* the fear it
meant to soothe.
- **Motivation is timed, not cut.** The "you did it / this is real / you're becoming a builder" beats
land hardest right after a win, at a section's close — never as opening preamble. *Prevents:*
identity and proof beats falling flat because they arrived before the reader earned the feeling.
- **Show the win before the explanation.** Get the reader to a working, visible result as fast as
possible, *then* explain how it works for the reader who's now curious. *Prevents:* the teach-then-do
wall — making someone read a full explanation before anything happens.
- **Set up once; don't re-gate.** Establish setup a single time; later steps assume it's done and link
back rather than re-explaining. Setup reappears only as symptom-tied troubleshooting. *Prevents:*
re-charging a toll every section, which reads as friction and mild condescension.
### Two traps to name explicitly (we hit both, repeatedly)
- **Break-to-test.** Never ask the reader to do something whose only purpose is to exercise the work —
break something, stop a service, force an error. That's the builder's job, already done for them.
The reader only does what a real user would naturally do, and meets error states through reassurance
("if you ever see this, here's what it means"), never by being told to cause them. *(This one slipped
past every specific rule above — it's the reason the root rule has to exist.)*
- **Completeness as a reflex.** "Everything true" is not "everything useful." If a sentence isn't
helping the reader do the thing in front of them right now, it's costing them attention and
confidence. Cut it.
### Before you call a page done
**Read it back as the reader, not the author.** Walk it top to bottom as that first-timer following it
literally — not as the writer confirming it works. Every noun explained or obvious; every step has a
how and a why; nothing asks them to break, inventory, or pre-learn. If you wouldn't follow it
comfortably with zero background, fix it before you ship.
## Out of scope (no build without an explicit scope decision)
- A native mobile app. (The web UI **is** kept responsive — phone/tablet layouts and mobile
fixes are in scope; a packaged iOS/Android app is not.)
- Offline operation / Bible-text fallback when Concord is unreachable (invariant 3 — its
absence is an error).
- Sub-verse / word-level highlighting (whole-verse is the deliberate v1 granularity).
- Cloud / multi-tenant hosting (self-hosted single-unit is the model).
## Don't
- Don't embed Concord or bundle Bible text (invariant 1) — consume the HTTP API.
- Don't hardcode localhost / same-server as a requirement (invariant 2) — it's config.
- Don't invent offline fallback when Concord is down (invariant 3) — error.
- Don't anchor annotations to anything translation-specific (invariant 4).
- Don't store Bible text in songbird's database (invariant 5).
- Don't store notes as editor-native JSON (invariant 6).
- Don't put an ML stack in songbird — that's Concord's job.
- Don't merge a PR before Kris is its assignee and its checks are green. Don't push to `main` or
`--force`.
- Don't build a horizontal layer in isolation — slice vertically.