docs: redesign v0.1 screen as side panel + writing column

Replace the header/status-bar layout with a full-height writing column
plus an always-visible side panel holding all metadata. Canonicalize the
new region vocabulary in CONTEXT.md (Writing column, Side panel) and retire
header / status line / edit area as terms-to-avoid.

Consequent sweeps: rewrite the Error UX section in side-panel terms and
give keyboard-disconnect a home (⌨ ✗, disconnect-only); drop the "line N"
readout; reconcile the render module ops and region shadow; sweep the
status-line/edit-area wording across the technical, roadmap, qfd, notes,
README, and ADR-003 docs (incl. the ~11→~13 line-count figure).
This commit is contained in:
Julien Calixte
2026-07-05 17:27:25 +02:00
parent d3fd6f6ed1
commit a848c05afa
8 changed files with 100 additions and 61 deletions

View File

@@ -152,7 +152,7 @@ as the price of those properties.
### Consequences
- Visible edit area on this panel is ~11 lines. UI must embrace the
- Visible writing column on this panel is ~13 lines. UI must embrace the
constraint — no multi-pane, no large headers. See
[v0.1 product → screen layout](v0.1-mvp-product.md#screen-layout).
- Framebuffer is ~27 KB; keeps PSRAM free for git pack data — a top-3

View File

@@ -19,6 +19,6 @@ The reframing question: **what is the user actually waiting for?** For `Ctrl-S`,
For `Ctrl-G`, the equivalent moment isn't "push complete." It's "commit landed locally" — which happens at ~0.2 seconds, well before the push even starts. From that moment on, your work is preserved across power loss, SD removal, the apocalypse — everything except remote delivery. The remaining 510 seconds is _transport of an already-safe thing_.
Surface that moment in the status line at ~0.2 seconds (`✓ committed abc1234 · pushing…`) and the perceived latency of `Ctrl-G` collapses from 10 seconds to roughly 200 milliseconds. The gap with `Ctrl-S` disappears.
Surface that moment in the side panel at ~0.2 seconds (`✓ committed abc1234 · pushing…`) and the perceived latency of `Ctrl-G` collapses from 10 seconds to roughly 200 milliseconds. The gap with `Ctrl-S` disappears.
**Durability before delivery.** The moment that matters to the user is the moment durability is achieved, not the moment delivery completes. Once you see that, the slow operations stop feeling slow.

View File

@@ -474,9 +474,13 @@ These are the live tensions we are watching, not deciding harder:
transformation; H4's "to cursor" is implicit in Boot's definition.
Matrix cell strengths held; no Σ recompute.
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.
The earlier variance between README's "~12 lines" and product/[ADR-003]'s
"~11 lines" of "edit area" is now superseded: the side-panel redesign removed
the top header and bottom status bars (metadata moved into the **side panel**),
so the **writing column** spans the full panel height — ~13 lines at the
editor's 20 px font (`FONT_10X20`, `editor.rs` `ROWS = HEIGHT / 20 = 13`).
README, the product/technical docs, and [ADR-003] are all updated to ~13 lines
(writing column).
---

View File

@@ -29,7 +29,7 @@ Out of scope: Vim, palette, multiple files, branches, conflict handling.
## v0.2 — Vim navigation — [ ]
- [ ] Mode state machine (Normal / Insert), mode indicator in status line
- [ ] Mode state machine (Normal / Insert), mode indicator in the side panel
- [ ] Movement: `h j k l`, `w b e`, `0 $`, `gg G`, `Ctrl-d Ctrl-u`
- [ ] `i a o O A` to enter Insert
- [ ] `Esc` returns to Normal
@@ -46,7 +46,6 @@ Out of scope: Vim, palette, multiple files, branches, conflict handling.
- [ ] Visual char (`v`) and line (`V`) modes, `y d c` on selections
- [ ] `:` command line: `:w :q :wq :e <path>`
- [ ] Status line shows file path, dirty flag, mode
## v0.5 — File palette + multi-file — [ ]
@@ -56,7 +55,7 @@ Out of scope: Vim, palette, multiple files, branches, conflict handling.
- [ ] `:e` and palette share the same recent-files list
- [ ] `:enew` creates a new file — prompts for scope (tracked vs local)
- [ ] `Ctrl-G` is disabled / hidden when the current buffer is local-scope
- [ ] Status line briefly shows file count on `Ctrl-G` when the publish bundles
- [ ] The side panel briefly shows file count on `Ctrl-G` when the publish bundles
more than one dirty Tracked file (e.g. `"publishing 3 files: abc1234"`),
so workspace-scoped behaviour stays visible to the user
@@ -78,7 +77,7 @@ Out of scope: Vim, palette, multiple files, branches, conflict handling.
- [ ] 18650 + IP5306 charge board, soft power switch
- [ ] Light sleep on idle > 30 s (keyboard interrupt wakes)
- [ ] Deep sleep on lid close (reed switch); restore cursor + buffer
- [ ] Battery indicator in status line
- [ ] Battery indicator in the side panel
## v0.9 — Robustness — [ ]

View File

@@ -99,49 +99,59 @@ strip aspect (~2.9:1) — biases the UX toward "current line and recent
context," not a full page. Hardware rationale: [ADR-003](adr.md#adr-003-display-medium--e-ink-gdey0579t93-panel).
```
┌─────────────────────────────────────────────────────────────────────────────────────┐
notes.md ● 14:02:33 │ ← header
├─────────────────────────────────────────────────────────────────────────────────────┤
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor
incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud
│ exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
│ Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu
fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in ← edit area
culpa qui officia deserunt mollit anim id est laborum. (~11 lines)
│ The quick brown fox jumps over the lazy▎ │ (cursor block)
│ │
├─────────────────────────────────────────────────────────────────────────────────────┤
│ INSERT • unsaved • Wi-Fi — │ ← status
└─────────────────────────────────────────────────────────────────────────────────────┘
┌────────────────────────────────────────────────────────────────────────────┐
Lorem ipsum dolor sit amet, consectetur adipiscing │ notes.md ● │
│ elit. Sed do eiusmod tempor incididunt ut labore et │ INSERT │
dolore magna aliqua. Ut enim ad minim veniam, quis │
nostrud exercitation ullamco laboris nisi ut aliquip │ 1 240 words
│ ex ea commodo consequat. │ +318 this session
│ 12:07 elapsed
│ Duis aute irure dolor in reprehenderit in voluptate
velit esse cillum dolore eu fugiat nulla pariatur. │
Excepteur sint occaecat cupidatat non proident.
│ 14:02
│ The quick brown fox jumps over the lazy▎ │ Wi-Fi — │
│ ✓ pushed abc1234
└───────────────────────────────────────────────────────┴─────────────────────┘
writing column (~60 cols, full height) side panel (~20 ch)
```
- Header: filename, dirty indicator (●/○), wall clock if SNTP has set time.
- Status: mode (always `INSERT` in v0.1), save state, Wi-Fi state
(`—` = off, `✓` = connected, `✗` = failed; default is `—` because the
radio is on-demand). `Ctrl-S` briefly flashes "saved at HH:MM" and
returns. `Ctrl-G` transitions through `✓ committed abc1234 · pushing…`
at ~0.2 s — the commit-landed moment, when the user's work is durable
(see "Durability before delivery" in [`CONTEXT.md`](../CONTEXT.md)) —
and ends at `✓ pushed abc1234` (or `publish failed: <reason>`, with the
local commit preserved either way).
- Cursor: solid block at the insertion point. **No blink.**
- **Writing column** (left, ~60 cols, full panel height): only the text and a
solid-block cursor at the insertion point. **No blink.** The seam at `x = 396`
runs through it invisibly (the driver's split-and-mirror blit handles it).
This is the only region that repaints as you type.
- **Side panel** (right, ~150 px / ~20 cols, full height): all metadata lives
here, so the writing column keeps the full height. Top — **filename** + dirty
dot (●/○) and **mode** (always `INSERT` in v0.1). Middle — **word count** and
the **session** (words written + elapsed time), both refreshed on a short
typing pause, *not* per keystroke. Bottom — ambient state: **clock** (if SNTP
has set the time), **Wi-Fi** (`—` = off/on-demand, `✓` = connected,
`✗` = failed), a **keyboard-disconnect** flag (`⌨ ✗`, shown *only* while the
keyboard is dropped; blank when healthy — no permanent `⌨ ✓`, per
[`CONTEXT.md`](../CONTEXT.md) "No state the user didn't ask for"), and
**publish state**. `Ctrl-S` briefly flashes "saved HH:MM";
`Ctrl-G` transitions through `✓ committed abc1234 · pushing…` at ~0.2 s — the
commit-landed moment, when the user's work is durable (see "Durability before
delivery" in [`CONTEXT.md`](../CONTEXT.md)) — and ends at `✓ pushed abc1234`
(or `publish failed: <reason>`, with the local commit preserved either way).
- **Deliberately not shown:** any cursor-position readout (*line* or *column*),
character count, or reading-time estimate — each repaints a panel row on cursor
movement or per keystroke, on a row apart from the cursor, doubling the ghosting
rate for little writerly value. v0.1 ships no positional readout at all.
Fonts: one mono font, embedded in firmware. Target sizing: a 14 px mono
glyph at roughly 8 px advance gives ~95 columns × ~11 visible lines of edit
area after header (16 px) and status (16 px) bars. Final size tuned during
spike 2 against real legibility on the panel.
Fonts: one mono font for the writing column, a smaller one for the panel, both
embedded in firmware. Sizing target: ~60 columns of writing (down from the
full-width ~79 — too wide for a comfortable line length) across the full ~13
visible lines, now that metadata has moved off the top/bottom bars into the side
panel. Final size tuned during spike 2 against real legibility on the panel.
**UX consequence of the strip aspect:** the cursor stays near the bottom of
the visible area during normal typing; the edit area scrolls one line at a
the visible area during normal typing; the writing column scrolls one line at a
time. There is no "page" — just a continuously advancing strip. Embrace it,
don't fight it.
**What scroll looks like on e-ink:** every line-scroll is a partial refresh
of the full edit area — every line moves, there is no small dirty rect for
of the full writing column — every line moves, there is no small dirty rect for
scroll — so each scroll costs ~200300 ms. Per-keystroke partial refreshes
accumulate ghosting; the render module clears it by upgrading every 20th
refresh to a full refresh, deferred to the next idle pause ≥ 1 s so it
@@ -156,15 +166,16 @@ a save.
| Failure | What the user sees |
| ------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| No SD card on boot | "no SD card — insert one and reboot" |
| SD card mounted but `/sd/local` missing | "missing /sd/local — re-provision and reboot" (dev re-clones onto SD) |
| Wi-Fi credentials wrong (build-time env vars) | status shows `Wi-Fi ✗`; dev rebuilds with correct env vars and re-flashes |
| Wi-Fi won't connect | status shows `Wi-Fi ✗`; editing still works; `Ctrl-G` shows "no network" |
| `Ctrl-G` publish fails (auth, network, merge conflict) | status shows reason; local commit is preserved; user can retry — non-fast-forward auto-recovers via pull-then-push and is not surfaced as a failure unless the pull itself conflicts |
| File write fails | status flashes "save failed"; buffer stays dirty |
| Keyboard disconnects | header shows `⌨ ✗`; editing pauses; reconnects automatically |
| SD card mounted but `/sd/repo` missing | "missing /sd/repo — re-provision and reboot" (dev re-clones onto SD) |
| Wi-Fi credentials wrong (build-time env vars) | panel Wi-Fi field shows `✗`; dev rebuilds with correct env vars and re-flashes |
| Wi-Fi won't connect | panel Wi-Fi field shows `✗`; editing still works; `Ctrl-G`'s publish-state field shows "no network" |
| `Ctrl-G` publish fails (auth, network, merge conflict) | panel publish-state field shows the reason; local commit is preserved; user can retry — non-fast-forward auto-recovers via pull-then-push and is not surfaced as a failure unless the pull itself conflicts |
| File write fails | panel save field flashes "save failed"; buffer stays dirty |
| Keyboard disconnects | panel shows `⌨ ✗` (blank once reconnected); editing pauses; reconnects automatically |
No modal dialogs. Errors live in the status line. The editor is never blocked
from accepting keystrokes (except during the partial-refresh frame itself).
No modal dialogs. Errors live in the side panel's ambient-state fields. The
editor is never blocked from accepting keystrokes (except during the
partial-refresh frame itself).
## Out of scope for v0.1

View File

@@ -135,9 +135,11 @@ Custom widget layer on `embedded-graphics` —
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 }`.
- A render request is a `RenderOp { Lines(range) | Panel(field, text) | FullRefresh }`.
The **side panel** is multi-field (Spike 8), so the old single `Status(text)`
op becomes a per-field write; the exact field enum is what Spike 8 decides.
- The render thread maintains a shadow of the current screen contents per
region (header / edit area lines / status).
region (**writing-column** lines / **side-panel** fields).
- For `Lines(range)`: clear the region's bounding box, draw glyphs, partial
refresh that rect.
- Glyph cache: rasterised mono font kept in PSRAM; one entry per (codepoint).
@@ -165,7 +167,7 @@ Storage split rationale:
[qfd.md H13](qfd.md#6-critical-performance-budget).
- **Bring-up on `Ctrl-G`.** `wifi_task` transitions `Off → Associating →
Connected`. Association timeout: 10 s. If association fails, the local
commit is preserved and the status line shows ``; no background retry.
commit is preserved and the side panel's Wi-Fi field shows ``; no background retry.
- **Grace window** of ~90 s after a Publish completes. A `Ctrl-G` inside
the window reuses the connection (no second association cost). If no
Publish fires before the window expires, the radio tears down.
@@ -202,8 +204,8 @@ PSRAM heap during push is a top-3 watched metric — see
prefix; the timestamp _is_ the message)
- push HEAD to `origin/<current branch>`
- on push failure: `git pull --no-edit` (merge), then retry the push once.
Only surface failure to the status line if the pull conflicts or the
second push also fails.
Only surface failure to the side panel's publish-state field if the pull
conflicts or the second push also fails.
- The PAT is loaded into the Authorization header per request; never logged.
- The whole sequence is atomic from the user's view — see
[`CONTEXT.md` → Publish](../CONTEXT.md#user-facing-actions).