docs(qfd): crosslink ADR mentions with reference-style links

Convert every `ADR-NNN` mention in qfd.md to a `[ADR-NNN]` shortcut
reference and add the link definitions at the bottom of the file, so
each ADR mention navigates to the matching adr.md section.
This commit is contained in:
Julien Calixte
2026-05-14 20:52:41 +02:00
parent 22aeb449e3
commit 47eecdc058

View File

@@ -26,7 +26,7 @@ What a user (= me) values about the device, with importance weights on a
| ID | Requirement | Weight | Source |
| --- | ------------------------------------------------------- | :----: | ------------------------------------------------------------------------------------------------------------------ |
| W1 | Sub-second visible response to typing | 10 | [product → Write](v0.1-mvp-product.md#user-stories), [README → UX](../README.md#ux-boundaries-set-by-the-medium) |
| W2 | `Ctrl-G` reliably **Publishes** to the remote | 9 | [product → Publish](v0.1-mvp-product.md#user-stories), [ADR-010](adr.md#adr-010-publish-ux--atomic-ctrl-g-auto-timestamp-commit-message-no-user-prompt), [CONTEXT → Publish](../CONTEXT.md#user-facing-actions) |
| W2 | `Ctrl-G` reliably **Publishes** to the remote | 9 | [product → Publish](v0.1-mvp-product.md#user-stories), [ADR-010], [CONTEXT → Publish](../CONTEXT.md#user-facing-actions) |
| W3 | Pulling power never corrupts the file | 10 | [product → Recover](v0.1-mvp-product.md#user-stories), [acceptance](v0.1-mvp-product.md#acceptance-criteria) |
| W4 | One-shot provisioning, never repeated mid-session | 7 | [product → Provisioning](v0.1-mvp-product.md#provisioning-build-time-dev-only), [roadmap → v0.9](roadmap.md#v09--robustness--) |
| W5 | Quick boot to a writing cursor | 6 | [product → acceptance](v0.1-mvp-product.md#acceptance-criteria) (≤ 5 s) |
@@ -90,21 +90,21 @@ weighted vote on which functions deserve the most engineering attention.
### Top engineering priorities (from importance)
1. **H2 — partial-refresh region area** (174). Bound how many pixels the
panel has to flip per keypress; ADR-003 is the hardware-side answer.
panel has to flip per keypress; [ADR-003] is the hardware-side answer.
2. **H9 — PSRAM heap during push** (162). gitoxide pack + rope + TLS all
share the same arena; ADR-001 and ADR-004 trade binary size for ecosystem
share the same arena; [ADR-001] and [ADR-004] trade binary size for ecosystem
so this becomes the watched metric.
3. **H8 — save durability** (147). Atomic-rename + fsync; FAT's weakness is
acknowledged in ADR-007 and mitigated, not designed around.
acknowledged in [ADR-007] and mitigated, not designed around.
4. **H1 — keypress latency** (138). The single most user-visible number;
ADR-002 and ADR-003 are co-conspirators.
5. **H6 — push success rate** (132). ADR-004 (gitoxide) and ADR-005 (PAT
[ADR-002] and [ADR-003] are co-conspirators.
5. **H6 — push success rate** (132). [ADR-004] (gitoxide) and [ADR-005] (PAT
over HTTPS) own this jointly; spike 7 is the kill-switch.
6. **H3 — full-refresh cadence** (126). The ghosting/flash tradeoff; lives
in the render layer.
The bottom three (H7 push time, H15 build time, H10 binary size) are real
costs but ones we knowingly took on (ADR-001) and are not in the critical
costs but ones we knowingly took on ([ADR-001]) and are not in the critical
path of user experience.
---
@@ -138,13 +138,13 @@ The roof shows where pushing one function pushes another the wrong way.
- **H1 latency ↔ H3 refresh cadence** (mild). More partial refreshes per
second pile up ghosting faster, demanding earlier full refreshes —
visible flashes that hurt H8 perception and H1 burst behaviour. The
ADR-003 strip aspect is the structural answer: a small framebuffer makes
[ADR-003] strip aspect is the structural answer: a small framebuffer makes
_both_ cheaper, not one at the expense of the other. The runtime answer
is render §H3: schedule full refreshes on idle ≥ 1 s (v0.1 tech doc).
- **H9 heap ↔ H10 binary size** (strong). std + gitoxide + mbedtls inflate
both. We chose to spend on these (ADR-001, ADR-004) because 16 MB flash
both. We chose to spend on these ([ADR-001], [ADR-004]) because 16 MB flash
and 8 MB PSRAM make them affordable; the kill-switch is spike 7. If
heap during push refuses to come under 1 MB free, ADR-004 flips to
heap during push refuses to come under 1 MB free, [ADR-004] flips to
libgit2-sys for v0.1.
- **H9 heap ↔ H5 soak** (strong). A long writing session grows the rope
and the glyph cache; pushing on top can OOM. Mitigation: 256 KB file cap
@@ -153,7 +153,7 @@ The roof shows where pushing one function pushes another the wrong way.
- **H6 push success ↔ H12 Wi-Fi reconnect** (reinforcing). Both come from
the same network stack; investing in reconnect backoff helps both.
- **H10 binary ↔ H15 build time** (strong). std builds are slow. Accepted
in ADR-001 — refactor leverage (H14) is the long-term payoff, not the
in [ADR-001] — refactor leverage (H14) is the long-term payoff, not the
per-build seconds.
- **H4 boot ↔ H10 binary** (mild). Larger binary = slower flash load.
Affordable at our size class but worth watching as features land.
@@ -172,24 +172,24 @@ constrains the choice.
Components (with anchoring ADR):
| ID | Component | ADR |
| --- | ------------------------------------ | ------------------- |
| C1 | ESP32-S3-N16R8 SoC | ADR-001, ADR-008 |
| C2 | `esp-idf-rs` (std) + ESP-IDF | ADR-001 |
| C3 | `std::thread` + `crossbeam-channel` | ADR-006 |
| C4 | PSRAM allocator wrapper | ADR-001 |
| C5 | GDEY0579T93 + DESPI-c579 panel | ADR-003 |
| C6 | `embedded-graphics` + e-paper driver | ADR-002, ADR-003 |
| C7 | Custom widget / dirty-rect layer | ADR-002 |
| C8 | `ropey` rope buffer | ADR-001 (ecosystem) |
| C9 | TinyUSB host (`esp-idf` bindings) | ADR-009 |
| C10 | FAT on microSD | ADR-007 |
| C11 | LittleFS on internal flash | ADR-007 |
| C12 | `gitoxide` (`gix-*`) | ADR-004 |
| C13 | mbedtls TLS (via ESP-IDF) | ADR-005 |
| C14 | HTTPS + GitHub PAT auth | ADR-005 |
| C15 | eFuse-derived encryption key | ADR-005, ADR-007 |
| C16 | USB-C wall PSU | ADR-008 |
| ID | Component | ADR |
| --- | ------------------------------------ | ---------------------- |
| C1 | ESP32-S3-N16R8 SoC | [ADR-001], [ADR-008] |
| C2 | `esp-idf-rs` (std) + ESP-IDF | [ADR-001] |
| C3 | `std::thread` + `crossbeam-channel` | [ADR-006] |
| C4 | PSRAM allocator wrapper | [ADR-001] |
| C5 | GDEY0579T93 + DESPI-c579 panel | [ADR-003] |
| C6 | `embedded-graphics` + e-paper driver | [ADR-002], [ADR-003] |
| C7 | Custom widget / dirty-rect layer | [ADR-002] |
| C8 | `ropey` rope buffer | [ADR-001] (ecosystem) |
| C9 | TinyUSB host (`esp-idf` bindings) | [ADR-009] |
| C10 | FAT on microSD | [ADR-007] |
| C11 | LittleFS on internal flash | [ADR-007] |
| C12 | `gitoxide` (`gix-*`) | [ADR-004] |
| C13 | mbedtls TLS (via ESP-IDF) | [ADR-005] |
| C14 | HTTPS + GitHub PAT auth | [ADR-005] |
| C15 | eFuse-derived encryption key | [ADR-005], [ADR-007] |
| C16 | USB-C wall PSU | [ADR-008] |
Function-to-component matrix (9 strong / 3 medium / 1 weak):
@@ -214,25 +214,24 @@ Function-to-component matrix (9 strong / 3 medium / 1 weak):
### Read across, not down
- **C5/C6/C7** (panel + graphics + widget) are the single most leveraged
cluster — they own H1, H2, H3 (the top of the priority list). ADR-002
and ADR-003 are the ADRs to keep most honest as v0.x progresses.
cluster — they own H1, H2, H3 (the top of the priority list). [ADR-002]
and [ADR-003] are the ADRs to keep most honest as v0.x progresses.
- **C12** (`gitoxide`) is overloaded: H6, H7, H9, H10, H11, H14, H15 all
touch it. That's why ADR-004 includes a kill-switch (fall back to
touch it. That's why [ADR-004] includes a kill-switch (fall back to
`libgit2-sys` if spike 7 fails). It's also why H9 sits in the top three
priorities — `gitoxide`'s memory profile is the unknown.
[ADR-010](adr.md#adr-010-publish-ux--atomic-ctrl-g-auto-timestamp-commit-message-no-user-prompt)
pins the *shape* of the publish sequence (the `gct` flow); C12 is just the
library that implements it. Changing ADR-010 doesn't change C12's column,
but changing C12 (the kill-switch) does not change ADR-010's user
contract.
[ADR-010] pins the *shape* of the publish sequence (the `gct` flow); C12
is just the library that implements it. Changing [ADR-010] doesn't change
C12's column, but changing C12 (the kill-switch) does not change
[ADR-010]'s user contract.
- **C11** (LittleFS) is unused in v0.1 — config is build-time. Its non-zero
cells in the matrix describe the v0.9+ shape per
[ADR-007](adr.md#adr-007-storage-split--fat-on-sd-for-working-copy-littlefs-on-flash-for-config),
not v0.1 reality.
cells in the matrix describe the v0.9+ shape per [ADR-007], not v0.1
reality.
- **C2** (std runtime) sits underneath almost everything, but it's the
_enabler_ (H4 boot, H10 binary, H12 Wi-Fi) rather than the bottleneck.
Reversing ADR-001 would force re-deciding ADR-004, ADR-005, ADR-006,
ADR-007 all at once — they're a single decision in three drawers.
Reversing [ADR-001] would force re-deciding [ADR-004], [ADR-005],
[ADR-006], [ADR-007] all at once — they're a single decision in three
drawers.
---
@@ -243,9 +242,9 @@ the numbers spikes 27 must validate before integration starts.
| Rank | Function | Target | Watched on | If we miss it |
| ---- | -------------- | ------------------------------------- | ----------------- | ----------------------------------------------------------------------- |
| 1 | H2 region area | ≤ 1 line per keypress | spike 2 + spike 5 | Increase font size to shrink per-glyph dirty rect (ADR-003 consequence) |
| 2 | H9 PSRAM heap | ≥ 1 MB free at push peak | spike 7 | ADR-004 kill-switch → `libgit2-sys`; cap rope at 128 KB |
| 3 | H8 durability | 100 % survive power yank after status | bench HIL | Re-evaluate ADR-007 (move config to internal NVS only) |
| 1 | H2 region area | ≤ 1 line per keypress | spike 2 + spike 5 | Increase font size to shrink per-glyph dirty rect ([ADR-003] consequence) |
| 2 | H9 PSRAM heap | ≥ 1 MB free at push peak | spike 7 | [ADR-004] kill-switch → `libgit2-sys`; cap rope at 128 KB |
| 3 | H8 durability | 100 % survive power yank after status | bench HIL | Re-evaluate [ADR-007] (move config to internal NVS only) |
| 4 | H1 latency | ≤ 200 ms keypress→glyph | spike 5 | Larger partial-refresh region; render multi-char bursts |
| 5 | H6 push % | ≥ 95 % on healthy Wi-Fi | spike 6 + spike 7 | TLS cipher trim; reconnect backoff tuning |
| 6 | H3 cadence | full every ~20 partials | spike 2 | Adjust per panel temperature; defer flash to idle ≥ 1 s |
@@ -255,7 +254,7 @@ the numbers spikes 27 must validate before integration starts.
The two not-in-MVP rows but already-shaped-by-design:
| — | H13 current | Measured only in v0.1 | bench multimeter | Cell sizing for v0.8 is data-driven, not spec-sheet |
| — | H11 stacks | Sum ≤ 80 KB | static analysis | Was off-by-2x in ADR-006 pre-fix — corrected in §7 |
| — | H11 stacks | Sum ≤ 80 KB | static analysis | Was off-by-2x in [ADR-006] pre-fix — corrected in §7 |
---
@@ -263,30 +262,30 @@ The two not-in-MVP rows but already-shaped-by-design:
Plain-language summary of what we accepted in exchange for what.
| Tradeoff | Got | Paid | ADR |
| ------------------------------------------------ | ---------------------------------------------------------------------------- | ------------------------------------------------------------------------------- | ------- |
| std (esp-idf-rs) over no_std (esp-hal) | Heap, threads, VFS, mbedtls, gitoxide-compatible | +1 MB binary, +510 min builds | ADR-001 |
| Custom widget layer over Ratatui | Dirty-rects aligned to e-ink regions; 200 KB binary back | 500 LoC we own and maintain | ADR-002 |
| 5.79" strip panel over 7.5" page or 10.3" reader | 27 KB framebuffer, fast partial refresh, "Freewrite" UX | Only ~11 visible lines | ADR-003 |
| `gitoxide` over `libgit2-sys` | Pure Rust, modular, no FFI cross-compile pain | Smart-HTTP path is newer; PSRAM profile unproven (spike 7) | ADR-004 |
| HTTPS + PAT over OAuth device-flow or SSH | Simplest auth that `gitoxide` smart-HTTP already supports | Long-lived secret on device; in v0.1 the PAT is compiled into the binary (dev-only target user makes this acceptable); v0.9 moves it to encrypted NVS | ADR-005 |
| `std::thread` over `embassy` or `tokio` | Boring, debuggable, real stack traces; no exec to tune | ~76 KB total stack across 5 tasks | ADR-006 |
| FAT-on-SD + LittleFS-on-flash split | Desktop can read SD; config survives SD reformat | Two filesystems to manage; FAT's power-loss weakness mitigated by atomic-rename | ADR-007 |
| Wall power for v0.1, battery deferred | Measure real draw before sizing the cell | Tethered MVP; not the final aesthetic | ADR-008 |
| USB host (TinyUSB) over BLE-HID | No radio contention with Wi-Fi during push; keyboard powered from the device | One more USB connector on enclosure | ADR-009 |
| Atomic `Ctrl-G` + auto-timestamp commit message | One key, one outcome; matches the user's existing `gct` workflow; no modal prompt to slow H1 latency | Commit history is timestamp noise; the device may author merge commits the user never sees; reversal would break muscle memory | ADR-010 |
| Tradeoff | Got | Paid | ADR |
| ------------------------------------------------ | ---------------------------------------------------------------------------- | ------------------------------------------------------------------------------- | --------- |
| std (esp-idf-rs) over no_std (esp-hal) | Heap, threads, VFS, mbedtls, gitoxide-compatible | +1 MB binary, +510 min builds | [ADR-001] |
| Custom widget layer over Ratatui | Dirty-rects aligned to e-ink regions; 200 KB binary back | 500 LoC we own and maintain | [ADR-002] |
| 5.79" strip panel over 7.5" page or 10.3" reader | 27 KB framebuffer, fast partial refresh, "Freewrite" UX | Only ~11 visible lines | [ADR-003] |
| `gitoxide` over `libgit2-sys` | Pure Rust, modular, no FFI cross-compile pain | Smart-HTTP path is newer; PSRAM profile unproven (spike 7) | [ADR-004] |
| HTTPS + PAT over OAuth device-flow or SSH | Simplest auth that `gitoxide` smart-HTTP already supports | Long-lived secret on device; in v0.1 the PAT is compiled into the binary (dev-only target user makes this acceptable); v0.9 moves it to encrypted NVS | [ADR-005] |
| `std::thread` over `embassy` or `tokio` | Boring, debuggable, real stack traces; no exec to tune | ~76 KB total stack across 5 tasks | [ADR-006] |
| FAT-on-SD + LittleFS-on-flash split | Desktop can read SD; config survives SD reformat | Two filesystems to manage; FAT's power-loss weakness mitigated by atomic-rename | [ADR-007] |
| Wall power for v0.1, battery deferred | Measure real draw before sizing the cell | Tethered MVP; not the final aesthetic | [ADR-008] |
| USB host (TinyUSB) over BLE-HID | No radio contention with Wi-Fi during push; keyboard powered from the device | One more USB connector on enclosure | [ADR-009] |
| Atomic `Ctrl-G` + auto-timestamp commit message | One key, one outcome; matches the user's existing `gct` workflow; no modal prompt to slow H1 latency | Commit history is timestamp noise; the device may author merge commits the user never sees; reversal would break muscle memory | [ADR-010] |
### Conflicts left explicitly _unresolved_ by v0.1
These are the live tensions we are watching, not deciding harder:
- **ADR-004 vs H9.** If `gitoxide` cannot keep ≥ 1 MB PSRAM free at push
- **[ADR-004] vs H9.** If `gitoxide` cannot keep ≥ 1 MB PSRAM free at push
peak, we are committed to switching transports for v0.1, not absorbing
the OOM risk.
- **ADR-009 vs H6/H13.** If TinyUSB host turns out unstable (spike 4),
- **[ADR-009] vs H6/H13.** If TinyUSB host turns out unstable (spike 4),
BLE-HID is the documented fallback — at the cost of Wi-Fi radio
contention during push (re-checking H6).
- **ADR-007 vs H8.** Power loss between FAT rename and dir flush yields
- **[ADR-007] vs H8.** Power loss between FAT rename and dir flush yields
the previous saved version. We document this as expected behavior; it
becomes a real bug only if soak testing shows it triggering on routine
saves.
@@ -295,36 +294,36 @@ These are the live tensions we are watching, not deciding harder:
## 8. Inconsistencies spotted and fixed
- **ADR-006 stack figure.** ADR-006 previously said "~40 KB of stack space
for task stacks" — but the v0.1 technical design's task table
- **[ADR-006] stack figure.** [ADR-006] previously said "~40 KB of stack
space for task stacks" — but the v0.1 technical design's task table
(`usb 8 + wifi 8 + ui 16 + render 12 + git 32`) sums to **76 KB**.
Updated ADR-006's Consequences section to reflect the actual budget and
cross-reference the tech doc. The 76 KB figure still fits comfortably
in the ESP32-S3's 512 KB internal SRAM, so no design change — just
documentation accuracy.
Updated [ADR-006]'s Consequences section to reflect the actual budget
and cross-reference the tech doc. The 76 KB figure still fits
comfortably in the ESP32-S3's 512 KB internal SRAM, so no design
change — just documentation accuracy.
- **Commit-message format triple-mismatch.** README said `git commit -m
"wip"`, the v0.1 product doc said `"wip <timestamp>"`, and the user's
actual shell alias (`gct` / `git-commit-timestamp`) uses a pure ISO-8601
timestamp with no `wip` prefix. Resolved by aligning all docs on `gct`
and recording the decision as
[ADR-010](adr.md#adr-010-publish-ux--atomic-ctrl-g-auto-timestamp-commit-message-no-user-prompt).
[ADR-010].
Pulled the v0.7 roadmap item "Commit message prompt instead of hard-coded
`wip`" — it's now contradicted by ADR-010 and removed.
`wip`" — it's now contradicted by [ADR-010] and removed.
- **First-run flow vs. target user.** The v0.1 product doc described a
captive-portal first-run, but the same doc names the v0.1 target user as
the dev themselves ("Me. Solo."). Provisioning a solo-dev device through
a captive portal is ceremony without a user. Resolved by switching v0.1
to build-time env-var config (no NVS, no LittleFS, no AP mode); on-device
provisioning is the v0.9 release that introduces non-dev users. Touches
ADR-005, ADR-007, the v0.1 product + technical docs, and the v0.9
[ADR-005], [ADR-007], the v0.1 product + technical docs, and the v0.9
roadmap entry.
- **Vocabulary leak.** Earlier docs used "commit" and "push" as if they
were distinct user actions; the gct/ADR-010 model collapses them into a
were distinct user actions; the gct/[ADR-010] model collapses them into a
single user-facing **Publish**. Resolved by introducing
[`CONTEXT.md`](../CONTEXT.md) as the canonical glossary; user-facing text
now uses **Save** and **Publish** only.
The minor variance between README's "~12 lines" and product/ADR-003's
The minor variance between README's "~12 lines" and product/[ADR-003]'s
"~11 lines" of edit area is within rounding for a 14 px glyph in a 240 px
tall edit region and is not load-bearing.
@@ -339,3 +338,14 @@ tall edit region and is not load-bearing.
reality drifts from estimates.
- The WHATs change rarely; the HOWs change with each release; the
matrices are recomputed when either side changes.
[ADR-001]: adr.md#adr-001-language-and-runtime--rust-on-esp-idf-rs-std
[ADR-002]: adr.md#adr-002-ui-strategy--custom-widgets-on-embedded-graphics-not-ratatui
[ADR-003]: adr.md#adr-003-display--gdey0579t93--despi-c579-breakout
[ADR-004]: adr.md#adr-004-git-implementation--gitoxide-gix
[ADR-005]: adr.md#adr-005-auth--https--github-personal-access-token
[ADR-006]: adr.md#adr-006-concurrency--stdthread--channels-no-async-runtime
[ADR-007]: adr.md#adr-007-storage-split--fat-on-sd-for-working-copy-littlefs-on-flash-for-config
[ADR-008]: adr.md#adr-008-mvp-power--wall-powered-battery-deferred-to-v08
[ADR-009]: adr.md#adr-009-keyboard-transport--usb-host-tinyusb
[ADR-010]: adr.md#adr-010-publish-ux--atomic-ctrl-g-auto-timestamp-commit-message-no-user-prompt