# METHOD — how this was built, and the practices behind it

You don't need any of this to host the app. But if your team ever builds or
commissions software, the working method used here is worth borrowing — it's the
reason a confidentiality-critical app could be built quickly *without* the usual
"move fast and hope" risk.

**Context in one line:** this app was built by the company's COO — not a
professional developer — working with an AI coding assistant, under a deliberately
strict process. The interesting part isn't the AI; it's the discipline wrapped
around it. Everything below is that discipline.

## 1. One step at a time, with an explicit stop

Work was broken into small, numbered steps, written down in advance in
`DEPLOY_PROMPTS.md`. Each step does *one* thing and then **stops** so a human can
check the result before the next change begins. The instruction file literally
says: *"Work through the steps in order, one at a time. Do not skip ahead... If any
step reports something unexpected, stop and read it."*

Why it matters: when something breaks, you know exactly which small change caused
it, because nothing else changed at the same time. Compare that to a large batch
of changes where a new bug could be hiding anywhere.

## 2. Decisions are written down, with the reasoning — and never erased

Every non-obvious decision is logged in `DECISIONS.md`, with the *why*, not just
the *what*. Superseded decisions are **not deleted** — they're marked superseded
and kept, so the history of "why is it like this?" is always answerable.

Real examples from this repo:
- **#27** records that a "press and hold to reveal the real name" feature was
  *rejected*, and spells out three reasons (it doesn't stop screenshots, it puts
  the real name back on screen, and it would require storing real names on the
  phone). Months later, anyone wondering "why can't I reveal a name on the phone?"
  has the answer.
- **#47** documents a genuine contradiction that a review caught — the app was
  sending an unrecognized real name to a back-office list while the spec claimed
  "no real name is ever transmitted" — and the decision made to resolve it cleanly.

Why it matters: the reasoning outlives the person who had it. A new team member (or
a future you) doesn't re-litigate settled questions or accidentally undo a
deliberate choice.

## 3. A branch per step; merge only after the automated checks pass

Each step's work happened on its own **branch** (an isolated copy of the code).
It was merged into the shared `main` line **only after** the automated test suite
passed. So `main` is always in a known-good state, and risky work-in-progress is
never mixed into it.

## 4. Automated tests as *enforcement*, not documentation

This is the backbone. There's a single command (`node tests/qa.mjs`) that runs a
battery of checks and **blocks** the change if any of them fail. It re-runs
automatically on every edit to the app or the tests — so a later "small fix" can't
quietly break something that used to work. What the suites actually check:

- **Name-hiding (46 fixed cases + a fuzz test):** feeds thousands of messy,
  hostile, foreign, and typo-ridden inputs through the real conversion and proves
  **no real name survives** into what would be sent. A leaked name is a hard
  failure.
- **No-report-lost:** simulates going offline, filing many reports, closing the
  app mid-send, restarting, and flickering signal — and proves **every report
  arrives exactly once**, never zero, never twice.
- **Confidentiality / end-to-end:** files a report the whole way through — phone →
  server → admin console — and proves the company's stored copy contains **codes
  only**, no real name.
- **Attachment & request handling:** proves text pulled from a photo, spreadsheet,
  or a doctor's typed request is run through the same name-hiding **before**
  anything else touches it.
- **Stress:** fires thousands of random and malformed inputs and confirms nothing
  crashes or drops.

Why it matters: the rules aren't just written in a document that people forget —
they're enforced by a machine on every single change.

## 5. When a rule is deliberately relaxed, the test is *rewritten*, not deleted

Sometimes a safety rule is intentionally loosened for a good reason. The temptation
is to delete the test that now "fails." That was never allowed here — the test was
**rewritten to assert the new, still-safe rule.**

Concrete example (`DECISIONS #59`): the admin roster started with the strictest
possible behavior — it showed **nothing** until you searched, so the browser only
ever received names you explicitly asked for. That was safe but painful to use.
The team relaxed it to **paged browsing** (50 names at a time). Instead of deleting
the confidentiality test, they **updated it** to assert the new guarantees: a page
is capped at 50 on the server (the full table can never come back in one response),
and **every page view is recorded in the audit log**, just like a name reveal. The
protection moved; it didn't disappear — and the test proves the new line.

## 6. The AI is made to stop and ask, instead of guessing

The written instructions include explicit "stop and ask me" gates at points where
a real decision is needed, so the assistant **surfaces** the choice to a human
rather than quietly picking one. Example: when it turned out filed reports weren't
being stored anywhere the admin console could read them, the process stopped and
asked the owner to choose how to store them — it did not invent a storage system on
a guess (that became `DECISIONS #56`).

## 7. Never mix cosmetic changes with logic changes

Appearance changes and behavior changes were kept in separate steps. When the admin
console was restyled to match an earlier design, the change was explicitly
"presentation only — no data exposure widened." That way a visual tweak can never
accidentally smuggle in a behavior change, and a reviewer can trust that a
"restyle" commit is exactly that.

## 8. A short list of invariants that were never allowed to move

Some rules were treated as non-negotiable for the life of the project (they live in
`CLAUDE.md`): masking always happens on the device; the AI and the stored reports
**never** receive a real patient name; the code-to-name roster never ships to a
phone; error messages and notifications speak the game vocabulary too. Every step
was checked against these, and no step was allowed to weaken one for convenience.

## Being honest about what went wrong

The method isn't magic, and it didn't prevent every misstep — it **caught** them.

- **Scope creep.** More than once, a branch grew beyond the single step it was
  supposed to be. The clearest case: the step to add admin write-back also
  accumulated paged roster browsing, a new product-category column, and a
  units-by-group dashboard before it was done. That's more than "one step."
- **What caught it:** the written records. `PROJECT_STATUS.md` openly flags that
  this step *"grew beyond its original scope,"* naming exactly what got added. And
  because every addition still went through the same automated safety checks and
  per-change commits, the creep never became *unsafe* — it was visible, attributed,
  and test-covered, just larger than ideal. The honest record is itself part of the
  method: the goal isn't a perfect history, it's a **truthful** one.

The takeaway for your own work: small steps, decisions written down with reasons,
and automated checks that block bad changes will catch far more than heroics will —
and they let a careful non-expert build something a careless expert couldn't.
