Compare commits

..

8 Commits

Author SHA1 Message Date
Julien Calixte
4eefd2c244 docs(qfd): convert requirement sources to clickable cross-references
The customer-requirements (WHATs) Source column was plain text ("product
§UX", "README v0.8"). Replace with anchored Markdown links into the
product spec, README, and roadmap so each requirement's provenance is
one click away.
2026-05-14 12:50:50 +02:00
Julien Calixte
04a323da1c docs(v0.1): link technical risk register to qfd unresolved-conflicts section 2026-05-14 12:50:34 +02:00
Julien Calixte
90e98d2375 docs(v0.1): cross-link technical doc to ADRs and qfd
Adds a companion-docs header and inline ADR jumps on every load-bearing
choice (ADR-001 runtime, ADR-002 UI, ADR-004 git kill-switch, ADR-006
concurrency, ADR-007 storage, ADR-009 USB-host gate). Render module
also points at qfd §3 to anchor "why these functions rank top."
2026-05-14 12:50:12 +02:00
Julien Calixte
2a2c268010 docs(v0.1): cross-link product spec to companion docs and ADRs
Adds a "Companion docs" header (README, technical, ADR, QFD, roadmap)
and inline pointers from the auth section to ADR-005, the screen
layout to ADR-003, and the acceptance criteria block to qfd §6 — so
each "what must this do" claim has a one-click jump to its rationale
or target.
2026-05-14 12:49:52 +02:00
Julien Calixte
05f87efea4 docs(adr): cross-link ADR-007/008/009 to v0.1 docs and roadmap
Adds inline pointers from ADR-007 (storage) to the v0.1 technical
persistence + file-layout sections, from ADR-008 (power) to the v0.1
out-of-scope and roadmap v0.8, and from ADR-009 (keyboard) to spike 4
in the v0.1 technical bring-up order.
2026-05-14 12:49:47 +02:00
Julien Calixte
baf1407785 docs(readme): link software stack section to qfd.md 2026-05-14 12:49:17 +02:00
Julien Calixte
f44da5892a docs(readme): list pnpm + oxfmt toolchain in repo layout 2026-05-14 12:49:04 +02:00
Julien Calixte
efc694eb9b docs(adr): cross-link ADRs to README, roadmap, qfd, and v0.1 docs
Adds a "Related docs" header at the top of adr.md and inline "See also"
pointers from ADR-001 (binary/build tradeoffs → qfd §7) and ADR-002
(render module → v0.1 technical, top H1/H2 functions → qfd §3). Makes
the design-doc set walkable instead of a flat list.
2026-05-14 12:48:45 +02:00
5 changed files with 115 additions and 35 deletions

View File

@@ -51,7 +51,9 @@ proven.
**Language: Rust on `esp-idf-rs` (std).** Decision is load-bearing — see the
rejected alternatives below, and [`docs/adr.md`](docs/adr.md) for the full
decision log covering language, UI strategy, display, git lib, auth,
concurrency, storage, power, and keyboard transport.
concurrency, storage, power, and keyboard transport. How each decision is
weighted against the user-facing requirements — and the critical performance
budget that falls out — lives in [`docs/qfd.md`](docs/qfd.md).
| Layer | Crate / Component | Notes |
|---|---|---|
@@ -164,6 +166,8 @@ gantt
sdkconfig.defaults
/hardware BOM, schematic, enclosure (later)
/docs ADRs, power measurements
package.json pnpm + oxfmt — formatting toolchain for docs/JSON
(companions: pnpm-lock.yaml, .oxfmtrc.json, .node-version)
```
---

View File

@@ -7,6 +7,14 @@ as a consequence. Status moves from **Proposed** → **Accepted** →
Format inspired by Michael Nygard's ADR template, kept short on purpose.
**Related docs:**
[`../README.md`](../README.md) — project overview, hardware table, macro plan.
[`roadmap.md`](roadmap.md) — per-version scope (v0.1 → v1.x).
[`v0.1-mvp-product.md`](v0.1-mvp-product.md) — what the v0.1 device must do.
[`v0.1-mvp-technical.md`](v0.1-mvp-technical.md) — how v0.1 is built.
[`qfd.md`](qfd.md) — Quality Function Deployment: requirements → functions →
components, with the tradeoffs from this file ranked by user-facing weight.
---
## ADR-001: Language and runtime — Rust on `esp-idf-rs` (std)
@@ -42,7 +50,11 @@ and TLS without writing them, and has Espressif as an actual upstream.
- Cross-compiling toolchain (`espup`) is one more thing to install.
- We will not use `tokio` or async runtimes in v0.1 — see ADR-006.
- Revisit if `esp-idf-rs` upstream stalls or if `gitoxide` doesn't compile
cleanly against it (spike 7 is the kill-switch).
cleanly against it (spike 7 is the kill-switch — see
[v0.1 technical: hardware bring-up order](v0.1-mvp-technical.md#hardware-bring-up-order)).
See also: [qfd.md §7](qfd.md#7-tradeoffs-and-their-why-linked-to-adrs) for
the binary-size / build-time costs traded against ecosystem access.
---
@@ -77,6 +89,10 @@ refresh regions.
- If we later want to render to a terminal for desktop testing, we add a
second backend; the widget API stays.
Implementation: [v0.1 technical → render module](v0.1-mvp-technical.md#module-breakdown).
Owns the two top-ranked functions (H1 latency, H2 region area) in
[qfd.md §3](qfd.md#3-house-of-quality--whats--hows).
---
## ADR-003: Display — GDEY0579T93 + DESPI-c579 breakout
@@ -107,11 +123,14 @@ controller — same SPI driver model as any other e-paper.
### Consequences
- Visible edit area is ~11 lines. UI design must embrace this (no
multi-pane, no large headers).
multi-pane, no large headers). See
[v0.1 product → screen layout](v0.1-mvp-product.md#screen-layout).
- Driver: if `epd-waveshare` doesn't already support this panel's
controller (SSD1683-class), we write ~300 LoC of `embedded-hal` SPI
driver. Validated in spike 2.
driver. Validated in spike 2 — see
[v0.1 technical → hardware bring-up order](v0.1-mvp-technical.md#hardware-bring-up-order).
- 10.3" upgrade path is preserved by keeping the renderer resolution-agnostic.
See [roadmap → v1.x](roadmap.md#v1x--stretch--nice-to-have).
---
@@ -146,7 +165,11 @@ risk table).
- We become an early-ish embedded user of `gitoxide`; bugs reported back
upstream.
- Auth via PAT in an Authorization header — no SSH (see ADR-005).
- Performance on PSRAM during pack operations is a watched metric.
- Performance on PSRAM during pack operations is a watched metric — top-3
priority in [qfd.md §6](qfd.md#6-critical-performance-budget).
Implementation: [v0.1 technical → `git` module](v0.1-mvp-technical.md#module-breakdown)
and [risks table](v0.1-mvp-technical.md#risks-and-how-well-know-they-bit-us).
---
@@ -175,9 +198,11 @@ from the chip's eFuse so a stolen SD card alone is not enough. Captive
portal accepts the PAT during first-run setup.
### Consequences
- The user must generate a PAT with `repo` scope. Documented.
- The user must generate a PAT with `repo` scope. Documented in
[v0.1 product → first-run flow](v0.1-mvp-product.md#first-run-provisioning-flow).
- PAT is never logged. Validated in code review.
- Rotation in v0.1 = wipe NVS and re-run setup. Proper rotation UI is v0.9.
- Rotation in v0.1 = wipe NVS and re-run setup. Proper rotation UI is v0.9
— see [roadmap → v0.9](roadmap.md#v09--robustness--).
- Revisit if we ever want to support multiple remotes per device with
different credentials.
@@ -209,8 +234,8 @@ runtime to tune, no colour-of-functions problem.
### Consequences
- ~76 KB of stack space across the five task stacks (8 + 8 + 16 + 12 + 32
KB — see v0.1 technical design for the breakdown). Comfortable in the
ESP32-S3's 512 KB internal SRAM.
KB — see [v0.1 technical → threads / tasks](v0.1-mvp-technical.md#threads--tasks)
for the breakdown). Comfortable in the ESP32-S3's 512 KB internal SRAM.
- Refresh / git / Wi-Fi each get their own thread, so a slow push doesn't
freeze typing.
- If task count balloons past ~10 (unlikely), revisit.
@@ -246,7 +271,9 @@ derived key.
Discouraged but possible.
- Config survives SD reformatting.
- Power-loss safety on FAT is weaker than LittleFS — we mitigate with
atomic-rename writes (see technical design).
atomic-rename writes (see
[v0.1 technical → `persistence`](v0.1-mvp-technical.md#module-breakdown)
and [file layout](v0.1-mvp-technical.md#file-layout)).
---
@@ -274,9 +301,11 @@ power profile of "boot + type + idle + push" is measured on real hardware.
Sizing a battery before measuring is guessing.
### Consequences
- v0.1 device is tethered. Not the final aesthetic, but the right MVP.
- v0.1 device is tethered. Not the final aesthetic, but the right MVP
scope is in [v0.1 product → out of scope](v0.1-mvp-product.md#out-of-scope-for-v01).
- We can decide cell capacity from real numbers in v0.8, not specs sheets.
- Lid-close detection / deep sleep slips to v0.8 with the battery.
- Lid-close detection / deep sleep slips to v0.8 with the battery — see
[roadmap → v0.8](roadmap.md#v08--power-battery--sleep--).
---
@@ -299,7 +328,8 @@ The ESP32-S3 has USB OTG (host capable) and BLE 5. Either transport works.
### Decision
**USB host (TinyUSB) for v0.1.** BLE-HID is kept as a documented fallback
if TinyUSB host turns out unstable (spike 4 is the gate).
if TinyUSB host turns out unstable
([spike 4](v0.1-mvp-technical.md#hardware-bring-up-order) is the gate).
### Consequences
- Enclosure design must include a USB-A or USB-C port for the keyboard.

View File

@@ -5,8 +5,11 @@ must *do* (engineering functions) and what we must *build* (components).
Surfaces the few targets that dominate the design and the conflicts between
them. Every decision cell points back to [`adr.md`](adr.md).
Scope: v0.1 MVP, with the v0.2v1.0 trajectory ([README](../README.md)) in
mind so we don't paint into a corner.
Scope: v0.1 MVP — see
[`v0.1-mvp-product.md`](v0.1-mvp-product.md) for user-facing scope and
[`v0.1-mvp-technical.md`](v0.1-mvp-technical.md) for implementation —
with the v0.2v1.0 trajectory ([README](../README.md),
[roadmap](roadmap.md)) in mind so we don't paint into a corner.
Format inspired by the classic House of Quality, kept compact. Strength
weights: **9** strong, **3** medium, **1** weak, blank none.
@@ -18,20 +21,20 @@ weights: **9** strong, **3** medium, **1** weak, blank none.
What a user (= me) values about the device, with importance weights on a
110 scale. Source columns point at the doc the requirement comes from.
| ID | Requirement | Weight | Source |
|-----|---------------------------------------------------|:------:|-----------------------|
| W1 | Sub-second visible response to typing | 10 | product §UX, README |
| W2 | `Ctrl-G` reliably lands a commit on GitHub | 9 | product user story 4 |
| W3 | Pulling power never corrupts the file | 10 | product story 5, AC |
| W4 | One-shot first-run setup, never repeated | 7 | product story 1 |
| W5 | Quick boot to a writing cursor | 6 | product AC (≤ 5 s) |
| W6 | Long sessions without crash / lag / drift | 9 | product AC (1 h soak) |
| W7 | Distraction-free, single-purpose surface | 8 | README vision |
| W8 | E-ink-honest UI (no blink, no animation, no flash spam) | 7 | README §UX |
| W9 | Refactorable across nine downstream releases | 8 | README roadmap |
| W10 | Hackable / DIY-shaped BOM and code | 5 | README title |
| W11 | Multi-day battery life (v0.8 onward) | 4 | README v0.8 |
| W12 | Local-only file scope coexists with git scope (v0.5+) | 5 | README scopes |
| ID | Requirement | Weight | Source |
|-----|---------------------------------------------------|:------:|--------|
| W1 | Sub-second visible response to typing | 10 | [product → story 2](v0.1-mvp-product.md#user-stories), [README → UX](../README.md#ux-boundaries-set-by-the-medium) |
| W2 | `Ctrl-G` reliably lands a commit on GitHub | 9 | [product story 4](v0.1-mvp-product.md#user-stories) |
| W3 | Pulling power never corrupts the file | 10 | [product story 5](v0.1-mvp-product.md#user-stories), [acceptance](v0.1-mvp-product.md#acceptance-criteria) |
| W4 | One-shot first-run setup, never repeated | 7 | [product story 1](v0.1-mvp-product.md#user-stories) |
| W5 | Quick boot to a writing cursor | 6 | [product → acceptance](v0.1-mvp-product.md#acceptance-criteria) (≤ 5 s) |
| W6 | Long sessions without crash / lag / drift | 9 | [product → acceptance](v0.1-mvp-product.md#acceptance-criteria) (1 h soak) |
| W7 | Distraction-free, single-purpose surface | 8 | [README vision](../README.md#vision) |
| W8 | E-ink-honest UI (no blink, no animation, no flash spam) | 7 | [README → UX](../README.md#ux-boundaries-set-by-the-medium) |
| W9 | Refactorable across nine downstream releases | 8 | [roadmap](roadmap.md) |
| W10 | Hackable / DIY-shaped BOM and code | 5 | [README → vision](../README.md#vision) |
| W11 | Multi-day battery life (v0.8 onward) | 4 | [roadmap → v0.8](roadmap.md#v08--power-battery--sleep--) |
| W12 | Local-only file scope coexists with git scope (v0.5+) | 5 | [README scopes](../README.md#vision), [roadmap → v0.5](roadmap.md#v05--file-palette--multi-file--) |
---

View File

@@ -2,6 +2,13 @@
> Scope: the minimum hardware-justifying release. If you take any of this
> away, the device stops being a typewriter.
>
> Companion docs:
> [README](../README.md) (overview) ·
> [technical design](v0.1-mvp-technical.md) (how it's built) ·
> [ADR log](adr.md) (load-bearing decisions) ·
> [QFD](qfd.md) (requirements ↔ functions ↔ components) ·
> [roadmap](roadmap.md) (where v0.1 sits in the sequence).
## One-line summary
@@ -69,7 +76,8 @@ That's it. No other stories ship in v0.1.
If the clone fails (bad PAT, no `notes.md` in the repo, etc.), the screen
shows the error and offers retry. The repo is expected to already exist on
GitHub and contain (or be willing to contain) `notes.md` — v0.1 does not
create remote repos.
create remote repos. Auth model rationale:
[ADR-005](adr.md#adr-005-auth--https--github-personal-access-token).
## Steady-state flow
@@ -98,7 +106,7 @@ create remote repos.
GDEY0579T93 — 5.79", **792×272**, 1-bit, partial refresh capable. Wide
strip aspect (~2.9:1) — biases the UX toward "current line and recent
context," not a full page.
context," not a full page. Hardware rationale: [ADR-003](adr.md#adr-003-display--gdey0579t93--despi-c579-breakout).
```
┌─────────────────────────────────────────────────────────────────────────────────────┐
@@ -171,7 +179,10 @@ Explicitly **not** in this release, to keep it shippable:
## Acceptance criteria
v0.1 ships when **all** of these pass on real hardware:
v0.1 ships when **all** of these pass on real hardware. Each criterion maps
to an engineering function with a measured target in
[qfd.md §6](qfd.md#6-critical-performance-budget); that's the
place to check before declaring an item done.
- [ ] First-run setup completes in under 5 minutes including typing in a PAT.
- [ ] After a cold boot with valid config, cursor is ready in ≤ 5 s.

View File

@@ -2,12 +2,20 @@
> Scope: ships the product surface in [`v0.1-mvp-product.md`](v0.1-mvp-product.md).
> No more, no less.
>
> Decisions referenced inline point at [`adr.md`](adr.md). Tradeoff weights
> and the critical-performance budget live in [`qfd.md`](qfd.md). Project
> overview: [`../README.md`](../README.md). Release sequence:
> [`roadmap.md`](roadmap.md).
## Architecture
Single Rust binary on `esp-idf-rs` (std). Two cores, several threads, one
shared editor state behind a mutex. Wi-Fi and git work happens off the input
thread so the typing path is never blocked by I/O.
Single Rust binary on `esp-idf-rs` (std)
[ADR-001](adr.md#adr-001-language-and-runtime--rust-on-esp-idf-rs-std).
Two cores, several threads, one shared editor state behind a mutex
([ADR-006](adr.md#adr-006-concurrency--stdthread--channels-no-async-runtime)).
Wi-Fi and git work happens off the input thread so the typing path is never
blocked by I/O.
```
┌──────────────────────────────────────────┐
@@ -71,6 +79,10 @@ Target boot time: ≤ 5 s to cursor (v0.1). The 3 s target is v1.0.
We do **not** try to build the whole stack and turn it on. Each spike below
is a small program that proves one risk before we commit to the next layer.
Spike 7 is the kill-switch for
[ADR-004 (gitoxide)](adr.md#adr-004-git-implementation--gitoxide-gix);
spike 4 is the gate for
[ADR-009 (USB host)](adr.md#adr-009-keyboard-transport--usb-host-tinyusb).
1. **Spike 1 — Blink.** Confirm toolchain, flash, and basic GPIO.
2. **Spike 2 — EPD.** Drive the GDEY0579T93 (via DESPI-c579 breakout) over
@@ -119,6 +131,11 @@ Anything else is ignored in v0.1.
### `render` — dirty-rect to EPD
Custom widget layer on `embedded-graphics`
[ADR-002](adr.md#adr-002-ui-strategy--custom-widgets-on-embedded-graphics-not-ratatui).
Owns the top-ranked engineering functions (latency, refresh-region area)
in [qfd.md §3](qfd.md#3-house-of-quality--whats--hows).
- A render request is a `RenderOp { Lines(range) | Status(text) | FullRefresh }`.
- The render thread maintains a shadow of the current screen contents per
region (header / edit area lines / status).
@@ -131,6 +148,9 @@ Anything else is ignored in v0.1.
### `persistence` — SD I/O
Storage split rationale:
[ADR-007](adr.md#adr-007-storage-split--fat-on-sd-for-working-copy-littlefs-on-flash-for-config).
- Atomic save: write to `notes.md.tmp`, fsync, rename. We accept FAT's
weakness here; on power loss between rename and dir flush, the user gets
the previous version, which is the documented behavior.
@@ -149,6 +169,12 @@ Anything else is ignored in v0.1.
### `git` — commit + push
`gitoxide` choice and kill-switch:
[ADR-004](adr.md#adr-004-git-implementation--gitoxide-gix).
Auth model: [ADR-005](adr.md#adr-005-auth--https--github-personal-access-token).
PSRAM heap during push is a top-3 watched metric — see
[qfd.md §6](qfd.md#6-critical-performance-budget).
- `gix` with the smart-HTTP transport backed by `esp-idf` mbedtls (via a
custom transport impl, or `gix-transport` with `reqwest`+`rustls-mbedtls`
if that path is cleaner — decided in spike 7).
@@ -187,6 +213,9 @@ for SPI buffers).
## Concurrency model
Rationale and rejected alternatives:
[ADR-006](adr.md#adr-006-concurrency--stdthread--channels-no-async-runtime).
- Editor state behind a single `Mutex`. Holders: `ui_task` (writer),
`render_task` (reader, snapshot then unlock), `git_task` (reader for save).
- No `await` / no async runtime — std threads only. Simpler debugging on
@@ -232,6 +261,9 @@ does not implement key rotation.
## Risks and how we'll know they bit us
Mirrored as live conflicts in
[qfd.md §7 "Conflicts left explicitly unresolved by v0.1"](qfd.md#7-tradeoffs-and-their-why-linked-to-adrs).
| Risk | Symptom we'd see | Fallback |
|---|---|---|
| `gix` smart-HTTP push doesn't work on `esp-idf-rs` mbedtls | spike 7 fails | switch to `libgit2-sys` (C, well-trodden) for v0.1 only |