docs: fix ARCHITECTURE.md mermaid diagrams + document the autosave last-mile guard
CI / frontend (push) Failing after 1m6s
CI / storybook-a11y (push) Successful in 5m3s
CI / backend (push) Successful in 1m20s
CI / codeql (csharp) (push) Has been cancelled
CI / codeql (javascript-typescript) (push) Has been cancelled
CI / api-client-drift (push) Has been cancelled
CI / e2e (push) Has been cancelled
CI / frontend (push) Failing after 1m6s
CI / storybook-a11y (push) Successful in 5m3s
CI / backend (push) Successful in 1m20s
CI / codeql (csharp) (push) Has been cancelled
CI / codeql (javascript-typescript) (push) Has been cancelled
CI / api-client-drift (push) Has been cancelled
CI / e2e (push) Has been cancelled
Rewrite all five mermaid blocks to portable syntax so they render (verified via a mermaid-native render): split two `classDef`/`class` statements that shared a line (the "why not just signals" graph); drop the `reduce() — PURE` participant alias and the `;` inside a Note (§2c sequence); remove `<br/>` and parenthetical transition labels from the two stateDiagram-v2 blocks (RemoteData, intake), moving the aside into a note; normalize `<br/>`→`<br>` and drop `<b>` in the atomic-hierarchy flowchart. Extend §2g to document the now-closed gap: a CanDeactivate guard flushes the pending debounce before in-app navigation, and beforeunload warns + best-effort flushes on hard close (noting the HttpClient/keepalive limitation). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -43,11 +43,11 @@ ever uses the level(s) below it — so anything you build is reusable by everyth
|
|||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
graph TD
|
graph TD
|
||||||
P["<b>Pages</b><br/>dashboard.page · login.page · intake.page"]
|
P["Pages<br>dashboard.page · login.page · intake.page"]
|
||||||
T["<b>Templates</b><br/>page-shell · shell"]
|
T["Templates<br>page-shell · shell"]
|
||||||
O["<b>Organisms</b><br/>login-form · registration-table · intake-wizard"]
|
O["Organisms<br>login-form · registration-table · intake-wizard"]
|
||||||
M["<b>Molecules</b><br/>form-field · data-row · async"]
|
M["Molecules<br>form-field · data-row · async"]
|
||||||
A["<b>Atoms</b><br/>button · text-input · radio-group · alert · heading"]
|
A["Atoms<br>button · text-input · radio-group · alert · heading"]
|
||||||
P --> T --> O --> M --> A
|
P --> T --> O --> M --> A
|
||||||
classDef l fill:#e5f1fb,stroke:#007bc7,color:#00567d;
|
classDef l fill:#e5f1fb,stroke:#007bc7,color:#00567d;
|
||||||
class P,T,O,M,A l;
|
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
|
graph LR
|
||||||
subgraph bad["3 booleans = 8 states (most illegal)"]
|
subgraph bad["3 booleans = 8 states (most illegal)"]
|
||||||
direction TB
|
direction TB
|
||||||
b1["loading ✓ · error ✗ · data ✗ ✅"]
|
b1["loading ✓ · error ✗ · data ✗ — legal"]
|
||||||
b2["loading ✗ · error ✓ · data ✗ ✅"]
|
b2["loading ✗ · error ✓ · data ✗ — legal"]
|
||||||
b3["loading ✗ · error ✗ · data ✓ ✅"]
|
b3["loading ✗ · error ✗ · data ✓ — legal"]
|
||||||
b4["loading ✓ · error ✓ · data ✓ ❌ nonsense"]
|
b4["loading ✓ · error ✓ · data ✓ — nonsense"]
|
||||||
b5["loading ✓ · error ✗ · data ✓ ❌ nonsense"]
|
b5["loading ✓ · error ✗ · data ✓ — nonsense"]
|
||||||
b6["… 3 more illegal combos ❌"]
|
b6["… 3 more illegal combos"]
|
||||||
end
|
end
|
||||||
subgraph good["1 union = 4 legal states"]
|
subgraph good["1 union = 4 legal states"]
|
||||||
direction TB
|
direction TB
|
||||||
@@ -125,8 +125,10 @@ graph LR
|
|||||||
g4["Success (carries value)"]
|
g4["Success (carries value)"]
|
||||||
end
|
end
|
||||||
bad -->|"choose a better type"| good
|
bad -->|"choose a better type"| good
|
||||||
classDef ok fill:#e8f5e9,stroke:#39870c; classDef no fill:#fdecea,stroke:#d52b1e;
|
classDef ok fill:#e8f5e9,stroke:#39870c;
|
||||||
class b1,b2,b3,g1,g2,g3,g4 ok; class b4,b5,b6 no;
|
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_
|
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 --> Success: data arrived
|
||||||
Loading --> Empty: arrived, but no rows
|
Loading --> Empty: arrived, but no rows
|
||||||
Loading --> Failure: request failed
|
Loading --> Failure: request failed
|
||||||
Failure --> Loading: reload()
|
Failure --> Loading: reload
|
||||||
note right of Success
|
note right of Success
|
||||||
value lives ONLY here
|
value lives ONLY here
|
||||||
end note
|
end note
|
||||||
@@ -230,15 +232,15 @@ _one_ function. No state is mutated anywhere else.
|
|||||||
```mermaid
|
```mermaid
|
||||||
sequenceDiagram
|
sequenceDiagram
|
||||||
actor User
|
actor User
|
||||||
participant View as View (template)
|
participant View
|
||||||
participant Store as createStore (signal)
|
participant Store
|
||||||
participant Reduce as reduce() — PURE
|
participant Reduce
|
||||||
User->>View: clicks / types
|
User->>View: clicks / types
|
||||||
View->>Store: dispatch(msg)
|
View->>Store: dispatch(msg)
|
||||||
Store->>Reduce: reduce(model, msg)
|
Store->>Reduce: reduce(model, msg) — PURE
|
||||||
Reduce-->>Store: next model
|
Reduce-->>Store: next model
|
||||||
Store-->>View: signal updates → re-render
|
Store-->>View: signal updates, re-render
|
||||||
Note over Reduce: the ONLY place state changes;<br/>no HTTP, no timers, no mutation
|
Note over Reduce: the ONLY place state changes<br>no HTTP, no timers, no mutation
|
||||||
```
|
```
|
||||||
|
|
||||||
Side effects (HTTP) sit _outside_ this loop: a command does the I/O, then `dispatch`es a
|
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
|
field, and the debounce fires ~600 ms later. The trigger is **"stopped changing," not
|
||||||
"lost focus."** Submit is a separate, explicit action (§2d).
|
"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
|
## 3. "Parse, don't validate" — value objects
|
||||||
@@ -443,11 +461,14 @@ where the user left off.
|
|||||||
```mermaid
|
```mermaid
|
||||||
stateDiagram-v2
|
stateDiagram-v2
|
||||||
[*] --> Answering
|
[*] --> Answering
|
||||||
Answering --> Answering: SetAnswer / Next / Back<br/>(steps re-derived each time)
|
Answering --> Answering: SetAnswer / Next / Back
|
||||||
Answering --> Submitting: Submit (all answers valid)
|
Answering --> Submitting: Submit when all answers valid
|
||||||
Submitting --> Submitted: ok
|
Submitting --> Submitted: ok
|
||||||
Submitting --> Failed: error
|
Submitting --> Failed: error
|
||||||
Failed --> Submitting: Retry
|
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
|
See it live on `/concepts` (section 5) — the step list and the "stap N van M" counter
|
||||||
|
|||||||
Reference in New Issue
Block a user