Planet4.Me docs
Open the app

#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 updating ARCHITECTURE.md in 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 / purchase at. Never serverTimestamp() — 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.