Architecture decisions
Short records of the decisions that shape this project, written before the code they govern exists. Each states what was decided, what else was considered, and what the decision costs.
Records are numbered and immutable in intent: when a decision changes, a new record supersedes the old one rather than editing history. Status starts at Proposed and becomes Accepted in the pull request that implements it. Implementation is sometimes staged: when a record’s substance lands across more than one pull request, it stays Proposed until the last of them, and the record itself says what remains unbuilt. This clause was written down after the fact and regularises exactly two records that had already shipped code while Proposed — 0006 and 0011 — plus 0013, which documents its own split.
Options and consequences are labelled (O1, C1, …) so they can be cited precisely
from issues, pull requests, and other records — 0004 · C3 is the third consequence
of record 0004. A consequence added after a record is published gets a suffixed label
(C1a) rather than renumbering the ones after it, since renumbering would invalidate
every citation already written down.
| # | Decision | Status |
|---|---|---|
| 0001 | Voice-first input is a primary design constraint | Proposed |
| 0002 | TypeScript, not JSDoc-typed JavaScript | Accepted |
| 0003 | Multi-page static rendering, no framework, no bundler | Accepted |
| 0004 | Prose stays Markdown; question definitions become TypeScript | Accepted |
| 0005 | Cloudflare Pages, for control over response headers | Accepted |
| 0006 | No in-app speech recognition; scope the privacy claim honestly | Proposed |
| 0007 | The clipboard is the airgap | Proposed |
| 0008 | Installation is what makes storage durable, and the app must say so | Proposed |
| 0009 | Encryption is an opt-in add-on; the export envelope is designed for it now | Accepted |
| 0010 | Printing stays a supported output, not an accident | Proposed |
| 0011 | Question identifiers are frozen, registered, and never derived | Proposed |
| 0012 | Client code becomes TypeScript, stripped at build time | Accepted |
| 0013 | Instance identity for slots the build renders | Accepted |
| 0014 | A DOM for tests, and only for tests | Accepted |
| 0015 | Assistant output is self-describing blocks, one per question group | Proposed |
Still open
0013 raised six questions before repeat
answers could be stored. The binding answered two — write atomicity, settled by a
check-and-set operation on the store, and telling a corrupt instance order from an absent
one — and left four: min changing in either direction after a reader has materialised
instances, whether 0011’s rename-on-read can reach a key with an instance spliced into
the middle, how an orphaned instance is surfaced — and with it a retired group’s stored
order, which is JSON the orphan surface would otherwise present to the reader as their
own prose — and what the registry must record for a stored key to be readable back.
#74 is where Q2 gets answered rather
than noted again: a repeat should hold as many instances as the reader has, up to max, and
three groups currently invite a range they cannot deliver.
0015 answers none of those four and opens a new route into Q2. Until now an over-long instance order could only come from a worksheet edit; an import can produce one too, so 0015 bounds new instances at the rendered slot count and refuses the excess rather than relying on Q2 staying theoretical.
0004 · C4 raised question-identifier versioning as the one decision deliberately left
unmade; 0011 settles it.