Compare commits
8 Commits
e1e456e02d
...
4eefd2c244
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4eefd2c244 | ||
|
|
04a323da1c | ||
|
|
90e98d2375 | ||
|
|
2a2c268010 | ||
|
|
05f87efea4 | ||
|
|
baf1407785 | ||
|
|
f44da5892a | ||
|
|
efc694eb9b |
@@ -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)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
54
docs/adr.md
54
docs/adr.md
@@ -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.
|
||||
|
||||
35
docs/qfd.md
35
docs/qfd.md
@@ -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.2–v1.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.2–v1.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
|
||||
1–10 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--) |
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 |
|
||||
|
||||
Reference in New Issue
Block a user