The bench phase the handoff was written for is closed (splice benched,
fast-seek landed, cache removed, decision made), so the note's live half —
the firmware plumbing plan — moves into sync-commit-staging.md as "The fix —
wiring the O(depth) splice into the firmware", reconciled to the run-5 state.
The duplicated TL;DR/measurement summaries are dropped in favor of the
curve's own trail; inbound references (notes index, git_bench comments)
now point at the curve.
Run 4: memory discipline verified (1833 KB flat, no OOM) but 0 hits
with the small maps demonstrably retained — window-repetition theory
refuted, sub-second bar failed (splice 2.83 s cold / ~2 s steady).
Decision: wire the splice in anyway (~9-10 s cold real-repo :sync vs
611 s/OOM for every alternative). Run 5: cache removal confirmed
free; the ~1.85 MB "resident" was mwindow's live window set, which
makes the mwindow opts in shipping git_sync.rs load-bearing.
The real jcalixte/notes clone (570 MB pack, 1179 files) proved both index
strategies are O(N_tree) and unshippable: index.write re-hashes the tree
(611 s), and the index-free read_tree is 77 s cold and OOMs the mmap cache
(zlib crash). Record the run in the tradeoff curve, revise the verdict to an
O(depth) TreeBuilder walk, and add a handoff note with the design, firmware
call sites, and bench steps for the next session.
Break the stage+commit window into sub-phases (FAT working-tree walk vs
object writes) via `commit split —` log lines, and add two micro-benchmarks
(sd_bench for SD/FAT primitive ops, git_bench for libgit2 object overhead)
with justfile recipes. Documents the walk-vs-writes cost model in
tradeoff-curves/sync-commit-staging.md to decide whether explicit-path
staging over the editor's dirty set is worth replacing add_all(["*"]).
Mark v0.6 delivered (core, host-tested; on-device gate pending): the
snippet engine, the Cmd-P/>/$ palette split retiring :e, and the just
init catalog. Note the →/≠ ISO-8859-15 caveat.
Record the firmware boot-read (serde_json builds for xtensa) and the
just-init catalog as delivered, and replace the proposed French catalog
with the finalised 17-snippet English set (three groups). Note the
→/≠ base-font caveat and the no-clobber seeding guard.
Record the > command registry, one-shot/two-step dispatch, and the :e
retirement as done in core; note the ISO-8859-15 label constraints
(ASCII `...`, `»` hint marker).
Snapshot the snippet name inline Tab would expand into a panel field in
refresh_stats, so it rides the typing-pause throttle rather than
repainting per keystroke. Draw it as `» name` on the row above the mode
line (Latin-9 has no tab glyph), sharing the slot with the NO KBD flag,
which can't co-occur since the hint means you're typing.
Reshape v0.6 snippets from a hard-coded table into a git-synced,
Zed-compatible JSON library (.typoena.snippets.json) with an inline Tab
path and a $ palette launcher. Add the file-format reference and
document the Cmd-P/>/$ verb split and the just init catalog.
Reference doc gains a theme key, an Options column, and the e-paper dark-mode
ghosting caveat. The v0.5 spec gets an amendment note superseding the two
reversed "decide-before-build" calls (auto_sync now a set-ahead palette
command; theme shipped early) without rewriting the verified-on-device
history; macroplan updated to match.
Record the d14d9e7 behaviour the doc didn't cover: format-on-save keeps at
most one trailing blank line, and load-verbatim + guarded-save render a
file's POSIX terminator as a visible trailing empty line.
Move each version's scope checklist and status out of the monolithic
macroplan into a dedicated docs/vX.Y-<slug>.md page; macroplan keeps the
source block, the rollup status, and a one-line summary + link per
version. v0.1 reuses its existing product/technical pages.
Also drop the "Optional column ruler at 80" requirement from v0.6.
All four v0.5 slices are done and device-verified: the prefs round-trip is
confirmed both directions (boot-read + on-device palette edit -> push flipped
line_numbers on origin), and the slice-3 delete fix is recorded as confirmed on
device. Descoped items (buffer close, grey-Publish-in-Local cue, multi-file
publish count) noted, not blocking delivery.
Git-tracked editor preferences read at boot and toggled live on-device:
- Prefs type (line-based TOML parse/serialize, no crate on xtensa) held on
Editor; firmware reads /sd/repo/.typoena.toml before the first render and
falls back to per-key defaults. Keys: save_on_idle, format_on_save,
line_numbers (bool) + auto_sync (string, schema/default only until v0.7).
- line_numbers applied live (gutter_cols -> 0 when off).
- Palette > command mode toggles the three bools; the list stays open so
several flip in one visit, and :settings opens it directly. Each toggle
applies live and queues Effect::SavePrefs (host atomic-writes the file,
which rides the next :sync).
- save_on_idle honoured host-side as a silent, unformatted idle auto-save.
- to_toml is newline-free; save_path now appends exactly one terminator
unconditionally so buffers round-trip byte-for-byte (trailing blanks kept).
- Firmware 0.4.0 -> 0.5.0; new docs/typoena-toml.md reference. Also refreshes
the slice-3 macroplan status (delete fix was confirmed on device).
:enew <name> creates a new file (empty, dirty, added to the palette list);
:delete unlinks the current file via a new Effect::Delete and switches to a
parked buffer or scratch. Scope is read from the path (local/x, repo/x) rather
than a modal prompt, and the /sd prefix is now optional in resolve_path.
On-device testing showed deletions never reached the remote: add_all(["*"])
alone does not stage a removal on this libgit2, so the tree came back unchanged
and the push was a silent no-op. stage_and_commit now runs add_all then
update_all(["*"]) (git add -u) — together git add -A. The :delete snackbar now
confirms the scoped file and flags that a Tracked file is local until :sync.
Cmd-P opens a modal transient palette over the writing column: a bare
fuzzy-search input, the ranked file list, and the selected row in reverse
video. A pure host-testable fuzzy_score (subsequence + word-boundary and
consecutive-run bonuses) ranks results; an in-core MRU floats recently
opened files first and is shared with :e (both route through open_path).
The host feeds the file list once at boot (enumerate_files over /sd/repo
and /sd/local). Ctrl-n/Ctrl-p navigate the list; Enter opens via the same
park/evict path as :e; Esc closes.
Ctrl-n/Ctrl-p also become down/up line motions in Normal and View (vim
CTRL-N/CTRL-P, count-aware), which is why the palette opener is Cmd-P
alone. No `>` prefix on the file input — `>` is reserved for the command
palette (slice 4).
112 editor + 28 keymap tests; the no-git firmware binary builds clean.
Rework the single Effect return into a drained effect queue
(Save{path,scope,contents} / Load / Publish / Pull) so one action can
ask the host for several ordered steps: opening a non-resident file
queues a Save of the outgoing dirty buffer then a Load of the target.
Keep the active buffer's fields inline on Editor and park inactive
buffers in a small LRU (<=3 resident = active + 2); switching back to a
resident buffer restores its caret/scroll/undo without touching the SD.
A dirty parked buffer is saved before eviction, so nothing leaves RAM
unsaved. `:e <path>` opens by prefix (/sd/repo -> Tracked, /sd/local ->
Local); `:sync` is refused in-core for a Local buffer.
Firmware drains the queue to empty each batch (a Load can cascade an
eviction Save) and persistence generalises the atomic save off the
hard-coded notes.md into load_path/save_path.
Also bump the side panel to FONT_9X15 and the `:` command line to
FONT_10X20 for legibility, word-wrapping the snackbar so a long notice
keeps its actionable tail.
Charwise `v` and linewise `V` selection with yank/delete/change on the
span; motions and counts extend it. Read-only View moves off v/V to
`gr` (go-read). Selection renders reverse-video on the 1-bit panel.
Normal motions factored into a shared move_by. Firmware -> 0.4.0.
Root cause, shipped safety net, and the specced eradication (a
persistent internal DMA scratch buffer in Epd so paints never allocate
mid-sync). Tracks hardware re-test and the eradication as follow-ups.
Records v0.3 as delivered in core (host-tested, on-device smoke-test pending)
and notes the known dot-repeat limits. Also reorders the plan so the Status
block follows the macroplan source.
Spike 13 (line-number gutter) verified on the panel closes the last v0.2
gate. Record v0.2 as delivered, and give v0.2.5 a delivered date + learning
note to match v0.1/v0.2 (its source-block entry still read on-track).
The file is the macroplan (plus per-version scope), so rename it to match and
retitle to "Macroplan"; update all inbound links and friendly labels across the
docs. Refresh the plan while here: v0.2 gutter built, :gl pull recorded (v0.7),
command-line editing (v0.4), and the format_on_save pref (v0.5).
Mark the v0.2 gutter built + host-tested (Spike 13 bench check still pending),
add the v0.5 .typoena.toml line_numbers toggle, and update CONTEXT.md screen
regions for the rebalanced 63-col writing region / 25-col side panel.
Decode Ctrl+D/Ctrl+U as HalfPageDown/HalfPageUp intents (matching the existing
DeleteWord/DeleteLine chord pattern, so the editor stays ignorant of Ctrl).
They step display (soft-wrapped) rows, not logical lines, so half a page is
half the visible window regardless of how prose wraps: Normal moves the caret
and the viewport follows, View scrolls the viewport, Insert/Command are no-ops.
Relative numbering renumbers the whole gutter on every j/k, a tall partial
refresh per cursor move that eats the e-ink ghosting budget for no
proportionate gain. Gutter is now absolute-only; Spike 13 downgrades from a
genuine e-ink risk to a layout decision.
Update the House now that v0.1 is delivered and hardware-verified:
- Perception zone: Typoena column moves from v0.1 target to measured.
W6 3->4 (1 h soak attested), W1 4->2 (type latency measured ~630 ms,
a visible lag). Typoena total 52->51, lead over Pomera down to one.
- H1 target: v0.1 relaxed 200->400 ms and v1.0 150->300 ms; ~630 ms
still exceeds 400 ms, recorded as the open latency gap, not a pass.
- Fix pre-existing drift: TikZ W14 disagreed with the table/totals
(Pomera/Smart 2/5 vs 5/1); "thirteen rows" -> "fourteen".
- Hoist the House diagram above section 1 so the picture leads.
- Trim AI writing tells and thin em-dash density in the new prose.
Adds a ‡ footnote on H7 (mirroring the H4 boot-latency †): cold :sync ~16 s /
warm ~10 s measured 2026-07-11 — within the ≤30 s v0.1 target, marginal against
the ≤10 s v1.0 target (cold's one-time Wi-Fi+SNTP push it over). Links the
breakdown in notes/sync-latency.md.
quality-house.md mirrored qfd.md's §1/§2 catalogues in a separate file,
which silently drifted (H3 cadence read 20 there vs 64 in qfd). Fold the
House diagram, perception scores, and regen notes into qfd.md §3 with no
section renumbering, so inbound #3/#6/#7 anchors still resolve. Delete
quality-house.md; re-point quality-house-empty.md and the docs index.
New docs/notes/sync-latency.md breaks down the measured ~16 s cold :sync
(Wi-Fi + SNTP + one TLS push), explains the optimistic-retry handshake saving,
the reconcile/last-writer-wins semantics, and why the rest is near the protocol
floor. Linked from the notes index, the docs index, and the Git row of the root
README.
Land the v0.1 editor integration: the git_sync module (libgit2 on the SD
/sd/repo, dedicated 96KB git thread, lazy Wi-Fi, :sync push with
synced/up-to-date/failed snackbars), the boot splash (Spike 9) plus its bin
and justfile recipes, and a power-on→cursor boot-timing log. Also re-syncs
the roadmap/spikes/v0.1-product status and adds the SD hardware reference
photo.
Ground the off-between-syncs assumption in the code: the shipped firmware
runs the stay-associated strategy the section argues against. Link
run_git_service so the claim is verifiable and mark teardown as v0.8 work.
Make explicit that the per-sync burst assumes a full radio de-init between
pushes, and that staying associated to skip handshakes loses on energy until
~150 syncs/hr — a regime a writing appliance never reaches.
Record the git-tracked .typoena.toml preferences file (save_on_idle,
auto_sync) and the palette `>` command mode that edits it in the v0.5
roadmap. Add a tradeoff-curves note deriving Wi-Fi sync energy as a 1/T
curve, which sets auto_sync's default to 10m (opportunistic, min-clamped)
rather than a 5m wall-clock timer.
Spike 3 (SD) is resolved (genuine 32 GB card mounts, verified on its own
SPI3 host per ADR-012) and the mount/save path is now wired into main.rs.
- README: move the SD/CMD59 open question to the retired-risks line; drop
the stale "blocked on SD (waiting on a compatible card)" from the status
blurb. Remaining v0.1 gate is the boot splash + wiring git publish.
- roadmap: flip the boot-load and save-to-SD v0.1 items to done, re-sync
the status blurb and macroplan note, re-point the config-at-boot TODO
from the (landed) SD wiring to the git-publish wiring.
The side-panel split (writing column + panel) is already built in the
editor crate; correct the stale "not built yet" note and add the
snackbar under v0.1.
Record that persistence is now implemented and hardware-verified, and correct
the crash-recovery rule everywhere it appeared as "just promote the tmp": a
crash during the tmp write leaves a partial tmp, so recovery keeps the
committed target when both files are present and only promotes the tmp when
the target was already unlinked. Also fix the stale README heading that still
said the SD was on shared SPI2.
Re-ran the spike on the dedicated SPI3 wiring (CLK 14 / MOSI 15) with
the same clean mount + atomic round-trip result. Flip the docs from
"pending a re-run" to verified.
Document the shared-bus arbitration decision: rather than rework the
proven EPD SPI layer and add a cross-thread mutex on the save path, the
SD moves to a dedicated SPI3. Update the boot sequence, risk table,
Spike 3 postmortem follow-up, hardware overview, and firmware README.
Mark Spike 3 resolved (genuine 32 GB SDHC mounts, round-trips clean on the
shared SPI2 bus; the 133 GB SDXC's CMD59 rejection was the sole fault).
Capture the FatFS f_rename caveat and the ≤32 GB card-compatibility note in
ADR-007, the resolution in the postmortem, and the verified writeup in the
firmware README.
Drop magic adverbs (silently, arguably), a stakes-inflation flourish,
and the bold-first Snippets bullets; de-duplicate the "usable artifact,
not a checkpoint" line shared by the README and roadmap.
Record init/load/provision in the roadmap (the config-on-SD migration
and the firmware env!()->file read TODO) and in the git-sync note as the
implementation of the pre-seed-from-a-computer decision.