diff --git a/docs/reference/architecture/ARCHITECTURE.md b/docs/reference/architecture/ARCHITECTURE.md
index cd6844e..4962a3f 100644
--- a/docs/reference/architecture/ARCHITECTURE.md
+++ b/docs/reference/architecture/ARCHITECTURE.md
@@ -43,11 +43,11 @@ ever uses the level(s) below it — so anything you build is reusable by everyth
```mermaid
graph TD
- P["Pages
dashboard.page · login.page · intake.page"]
- T["Templates
page-shell · shell"]
- O["Organisms
login-form · registration-table · intake-wizard"]
- M["Molecules
form-field · data-row · async"]
- A["Atoms
button · text-input · radio-group · alert · heading"]
+ P["Pages
dashboard.page · login.page · intake.page"]
+ T["Templates
page-shell · shell"]
+ O["Organisms
login-form · registration-table · intake-wizard"]
+ M["Molecules
form-field · data-row · async"]
+ A["Atoms
button · text-input · radio-group · alert · heading"]
P --> T --> O --> M --> A
classDef l fill:#e5f1fb,stroke:#007bc7,color:#00567d;
class P,T,O,M,A l;
@@ -110,12 +110,12 @@ the 4 states that are real** — the illegal ones can't be expressed at all.
graph LR
subgraph bad["3 booleans = 8 states (most illegal)"]
direction TB
- b1["loading ✓ · error ✗ · data ✗ ✅"]
- b2["loading ✗ · error ✓ · data ✗ ✅"]
- b3["loading ✗ · error ✗ · data ✓ ✅"]
- b4["loading ✓ · error ✓ · data ✓ ❌ nonsense"]
- b5["loading ✓ · error ✗ · data ✓ ❌ nonsense"]
- b6["… 3 more illegal combos ❌"]
+ b1["loading ✓ · error ✗ · data ✗ — legal"]
+ b2["loading ✗ · error ✓ · data ✗ — legal"]
+ b3["loading ✗ · error ✗ · data ✓ — legal"]
+ b4["loading ✓ · error ✓ · data ✓ — nonsense"]
+ b5["loading ✓ · error ✗ · data ✓ — nonsense"]
+ b6["… 3 more illegal combos"]
end
subgraph good["1 union = 4 legal states"]
direction TB
@@ -125,8 +125,10 @@ graph LR
g4["Success (carries value)"]
end
bad -->|"choose a better type"| good
- classDef ok fill:#e8f5e9,stroke:#39870c; classDef no fill:#fdecea,stroke:#d52b1e;
- class b1,b2,b3,g1,g2,g3,g4 ok; class b4,b5,b6 no;
+ classDef ok fill:#e8f5e9,stroke:#39870c;
+ classDef no fill:#fdecea,stroke:#d52b1e;
+ class b1,b2,b3,g1,g2,g3,g4 ok;
+ class b4,b5,b6 no;
```
The same argument applies to forms (a `submitting` boolean that can be true _with_
@@ -175,7 +177,7 @@ stateDiagram-v2
Loading --> Success: data arrived
Loading --> Empty: arrived, but no rows
Loading --> Failure: request failed
- Failure --> Loading: reload()
+ Failure --> Loading: reload
note right of Success
value lives ONLY here
end note
@@ -230,15 +232,15 @@ _one_ function. No state is mutated anywhere else.
```mermaid
sequenceDiagram
actor User
- participant View as View (template)
- participant Store as createStore (signal)
- participant Reduce as reduce() — PURE
+ participant View
+ participant Store
+ participant Reduce
User->>View: clicks / types
View->>Store: dispatch(msg)
- Store->>Reduce: reduce(model, msg)
+ Store->>Reduce: reduce(model, msg) — PURE
Reduce-->>Store: next model
- Store-->>View: signal updates → re-render
- Note over Reduce: the ONLY place state changes;
no HTTP, no timers, no mutation
+ Store-->>View: signal updates, re-render
+ Note over Reduce: the ONLY place state changes
no HTTP, no timers, no mutation
```
Side effects (HTTP) sit _outside_ this loop: a command does the I/O, then `dispatch`es a
@@ -366,6 +368,22 @@ So it _feels_ like save-on-blur only because you usually stop typing when you le
field, and the debounce fires ~600 ms later. The trigger is **"stopped changing," not
"lost focus."** Submit is a separate, explicit action (§2d).
+**The last-mile guard (leaving mid-debounce).** A debounce means an edit made in the final
+<600 ms before you leave hasn't been written yet. Two seams close that window
+([`pending-saves.ts`](../../../src/app/shared/application/pending-saves.ts)): every autosave
+owner (the brief/org-template root stores and each wizard's `draft-sync`) registers in a
+`PendingSaves` registry, and
+
+- **in-app navigation** — a `CanDeactivate` guard (`flushPendingGuard`, on the autosave
+ routes) flushes the pending write and _awaits_ it before the route changes, so the page
+ can't tear down with an unsaved keystroke;
+- **hard close / reload** — a `beforeunload` handler fires the flush best-effort and triggers
+ the browser's native "unsaved changes" prompt. It is deliberately _not_ a guaranteed sync
+ save: the HTTP seam is Angular `HttpClient` (no `keepalive`/`sendBeacon`), so an async write
+ can't be promised to finish as the page unloads — the prompt lets the debounce land if the
+ user stays. The authoritative _submit_ path already force-flushes first, so only unsent
+ draft keystrokes are ever at risk.
+
---
## 3. "Parse, don't validate" — value objects
@@ -443,11 +461,14 @@ where the user left off.
```mermaid
stateDiagram-v2
[*] --> Answering
- Answering --> Answering: SetAnswer / Next / Back
(steps re-derived each time)
- Answering --> Submitting: Submit (all answers valid)
+ Answering --> Answering: SetAnswer / Next / Back
+ Answering --> Submitting: Submit when all answers valid
Submitting --> Submitted: ok
Submitting --> Failed: error
Failed --> Submitting: Retry
+ note right of Answering
+ steps re-derived each time
+ end note
```
See it live on `/concepts` (section 5) — the step list and the "stap N van M" counter