#Working on it
Working notes for anyone — human or model — changing this repository.
#The rule that matters most
Any change to the Firestore schema, security rules, or the cadence engine (
src/lib/cadence/) requires updatingARCHITECTURE.mdin the same commit. Documentation drift is a bug.
The same rule, one layer out: functions/src/capabilities.ts is what the
assistant tells a person the app can do. Any change to what a person can see
or do — a screen, a control, a note marker, a limit — updates it in the same
commit. It is a claim made to the household in the household's own words, and
a claim that has drifted is a wrong answer delivered with confidence.
ARCHITECTURE.md describes what exists, in the present tense. The product spec
describes what is intended. They are different documents and must not be merged.
#Layering
Four layers, strictly one-directional. A layer may import from lower layers only.
1. src/lib/ pure logic — imports nothing outside src/lib. No React, no Firebase.
2. src/data/ data access — Firestore reads/writes, returns plain objects.
3. src/hooks/ subscriptions — wires layer 2 into React.
4. src/components/, src/screens/ — rendering only.
src/lib/layering.test.ts enforces layer 1 mechanically. Do not weaken it.
Never call onSnapshot inside a component. Subscriptions live in layer 3.
Components stay trivially renderable, which is the whole reason the tests in
layer 1 need no mocks, no test renderer, and no emulator.
#Non-negotiables
These were designed in deliberately. Re-introducing any of them is a regression,
and DECISIONS.md records why each one is the way it is.
- Median and MAD, never mean and standard deviation. One vacation wrecks a mean.
- Device clock for
checkedAt/startedAt/ purchaseat. NeverserverTimestamp()— it resolves at sync time and silently shifts intervals. purchases/documents are the truth;staple.purchaseHistory[]is a rebuildable read cache. Array appends do not merge across offline clients.- Manual override beats inference, permanently.
overriddenBy.{field}. - Lateness is never marked. A date can pass uncompleted — that state exists and stays visible on its day — but it is never styled: no red, no badges, no overdue piles, no guilt.
- Silence is the correct output for a bad prediction. Low confidence never auto-adds.
#Performance and responsiveness
Weigh scale, long-term performance and responsiveness in every coding
decision, not only when something is slow. The app derives everything at
render time from whole-household subscriptions — see BACKLOG, "Performance",
for what that costs and the staged fixes — so the questions to ask before
adding a derivation are: how often does it run (per render? per keystroke?),
over how much (every document? every document times every person?), and
what bounds it. Prefer work that is linear in what is on screen over work
that is linear in the household; memoize by a document's id and updatedAt
rather than recomputing on every render; never do O(documents × people)
per keystroke; keep the main thread free while a person is typing. Storing
derived data stays the last resort, taken deliberately and written up.
#Tests
npm test— layer 1, fixture-driven, no network. Every bug becomes a fixture. It also runs the docs renderer's fixtures (scripts/docs/), one of which renders every document here and fails on anything left wearing its markup: the docs site at docs.planet4.me is these documents, nothing written apart.npm run test:emulator— layer 2 and security rules, against the emulator suite.- Layers 3–4 get smoke tests only. The logic is not there; do not chase coverage.
#Scope
This repository implements the full product spec. Steps 1–9 — the shared list
app and the statistical engine — live in src/ and never need a model. The
Claude intelligence layer (entity resolution, classification, receipts, the
seasonal pass's generative half, the assistant, the review pass) lives in
functions/ as callable Cloud Functions, reached only through the seams in
src/data/intelligence.ts.
The layer is an accelerant, never a dependency. Running it live needs the Blaze
plan, App Check (VITE_RECAPTCHA_SITE_KEY in the web build — verify with
npm run check:appcheck), and the ANTHROPIC_API_KEY secret. Without the site
key — this checkout, CI, the emulator suites — every seam returns null and the
app runs on pure statistics and manual entry, which is a supported state, not a
degraded one. Every field added ahead of its feature now has a writer: region
was the last added ahead of its feature, and the Household section of Settings
sets it — until then every seasonal call sent Region: unknown and the model
was told to hedge. lookBack arrived with its writer and its shelf in one
commit.