16 MB layout adding a `storage` FAT data partition for the on-device git
working copy. Applied only by `just flash-git-push` (espflash
--partition-table); the editor flash keeps its default single-app layout.
Adds the extra_components metadata, git2 as an optional dep behind the
`git` feature (default-features off -> no openssl-sys/libssh2-sys), and a
git_smoke bin gated on it. libgit2-sys/libz-sys run in system mode against
fake pkg-config files (empty Libs) so they emit no link flags -- symbols
come from the esp-idf component. Proven on device: git2 version + a blob
SHA1 through the mbedTLS backend.
Builds vendored libgit2 as an esp-idf component so it inherits the
lwip/mbedtls/pthread/vfs/newlib include+link graph -- the include cascade
that sank CFLAGS injection never appears. Configured for mbedTLS via a
hand-written git2_features.h. Small port surface, libgit2 sources
untouched: poll.h shim, lstat==stat (esp_port.h), p_mmap via malloc+read
(esp_map.c), and identity/symlink stubs (esp_stubs.c). Registers empty
when LIBGIT2_SRC is unset so the editor build is unaffected.
Pins the exact version libgit2-sys 0.18.5 expects, so git2's safe API can
bind it in system mode. Sits under the esp-idf component that builds it;
clone with --recurse-submodules to fetch the source.
CFLAGS include-injection cascades through the whole esp-idf component
graph (arpa/inet.h -> sys/ioctl.h -> ...), so path 1 is out. Decide
path 2: libgit2 as an esp-idf CMake component with USE_HTTPS=mbedTLS,
which fixes the includes and the TLS backend together.
libgit2 C cross-compiles for xtensa; blocker is missing esp-idf lwIP
headers (arpa/inet.h) because libgit2-sys builds standalone. PSRAM
enabled and hardware-verified.
Adds the 8 MB octal PSRAM to the heap allocator (verified on the N16R8:
detected, memory-tested, 8192K pooled). OCT mode is required or init
fails; speed left at 40 MHz for a safe first enable. Prerequisite for
the git working set and the rope buffer.
Postmortem + ADR-004 outcome note + risk-table update: gitoxide has no
HTTP(S) push, so v0.1 falls back to libgit2; the on-device libgit2 <->
mbedtls cross-compile is the next gate.
Proves add -> commit -> push over HTTPS+PAT via libgit2 (git2), the
ADR-004 fallback after gitoxide was found to lack HTTP(S) push. Host
crate on stable, kept out of the xtensa firmware tree.
New docs/postmortems/ folder. The bench card (133 GB SDXC) rejects CMD59
(SPI-mode CRC) as illegal; CMD0/CMD8 succeed, so wiring and firmware are
proven and the fix is a compliant card. Captures the root cause, the
keep-CRC-required decision, and the reusable findings (EPD bus lock, LFN
requirement, R1-byte diagnostic) before the work pauses for hardware.
Standalone bench program (src/bin/sd_fat.rs, `just flash-sd`) that mounts
FAT over the EPD's shared SPI2 bus and proves the persistence module's
atomic save: write .tmp, fsync, rename, read back and compare.
Runs SD-only: the EPD's SpiBusDriver holds an exclusive bus lock for its
lifetime, so an arbitrated SD device can't be live alongside it yet. Keeps
CRC required and maps a card that rejects CMD59 to a clear "use a genuine
card" message rather than running the user's notes over an unchecked bus.
sdkconfig gains CONFIG_FATFS_LFN_HEAP (the atomic-save .tmp name isn't valid
8.3) and, temporarily, CONFIG_LOG_MAXIMUM_LEVEL_DEBUG to read the drivers'
per-command R1 bytes during bring-up.
US-International dead-key accents, slotted after navigation without
renumbering later versions so ADR/qfd/README anchors stay valid. Also
adds a UTF-8-correct buffer groundwork bullet to v0.2.
Swap the mono font from the ascii subset to iso_8859_15 (Latin-9) so
à é ê ç … œ € have glyphs. Cell size is identical, so ASCII rendering is
byte-for-byte unchanged; groundwork for v0.2.5 international input.
Spike 14 relies on file deletion, which the roadmap did not list. Add it
explicitly, flagging that a Tracked-file removal must reach the next
Publish's staged set (git rm / add -A, not plain git add .).
Spikes 8-14 are a cross-release display/UX bench batch, not part of the
v0.1 integration gate — only 8 and 9 feed v0.1. Move them out of the v0.1
technical doc into their own log (with a feeds-table and dependency notes),
leaving a pointer behind; link it from the README layout map.
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).
The product doc opened notes.md from /sd/local, the permanently-private
scope where Publish is unavailable — contradicting the headline Ctrl-G
flow and every other doc. Point it at the Tracked working copy.
Standalone `wifi_tls` binary: station assoc, SNTP, then a validated
HTTPS GET to api.github.com against the esp-idf cert bundle. Gates
Spike 7 (gitoxide push). Creds come from firmware/.env via build.rs.
Generalise the normal-mode command state machine into d/c operators that
compose with motions (dw, d$, …), doubled forms (dd, cc), and text
objects: iw/aw plus nesting-aware bracket pairs (i(, a{, …) and quotes
(i", i'). ciw/daw/di( and friends now work; pending op/object shows in
the status strip.
Per keystroke, diff the new frame against the last shown one to find the
changed row band and partial-refresh only those rows instead of all 272.
E-paper update time scales with the gate rows driven, so a single edited
text line refreshes far faster. Windowed in Y only (full width, both
controllers), so the seam/mirroring logic is unchanged. Also drop the
periodic full refresh to every 64 updates — the panel stays visually
clean, so it's now mainly for longevity.
Spike 5 — first spike where keyboard input and panel output run
together. usb_kbd changes from a blocking run() into start(): it brings
the USB host stack up on background threads and pushes edge-detected,
US-layout-translated key-downs onto a queue drained via next_key().
main.rs owns the panel, maintains a wrapped/scrolling text buffer, and
partial-refreshes per keystroke batch with a periodic full refresh to
clear ghosting. Logs per-refresh latency.
display_frame_partial writes the new image to bank 0x24, runs GxEPD2's
partial waveform (_Update_Part: 0x3C/0x80, 0x21/0x00,0x10, 0x22/0xFF,
0x20), then syncs bank 0x26 so the next partial has a correct baseline.
Like GxEPD2 for this dual-controller panel, the update covers the full
panel with the partial waveform — no windowing, since the waveform time
dominates. Much faster than a full refresh and without the flashing.
Spike 4. Drive the ESP-IDF USB Host Library directly through the raw
esp-idf-sys bindings (the convenience HID class driver is a managed
component not vendored in mainline, and a boot keyboard doesn't need
it). On attach, src/usb_kbd.rs enumerates the device and dumps its
descriptors, claims the boot-keyboard interface, sends SET_PROTOCOL(boot)
and SET_IDLE(0), then polls the interrupt-IN endpoint and decodes each
8-byte report into modifiers + keycodes logged over UART.
main.rs becomes the Spike 4 harness; the epd module is kept compiled
(allow(dead_code)) so it doesn't bit-rot. Verified on hardware with a
19f5:3255 keyboard: letters, digits, modifiers, and rollover all decode.
Add a thin dual-SSD1683 driver (src/epd.rs, ported from GxEPD2's
GxEPD2_579_GDEY0579T93) for the 792x272 e-paper panel: reset/init,
RAM addressing, the split-and-mirror full-frame blit across the
master/slave seam at x=396, and full-refresh waveform. Frame
implements embedded-graphics' DrawTarget.
Replace the Spike 1 blink harness in main.rs with the Spike 2 test:
SPI at 4 MHz over SCK 12 / DIN 11 / CS 7 / DC 6 / RST 5 / BUSY 4,
alternating normal/inverted frames with a seam-straddling circle,
"Typoena", and the build tag. Verified on hardware 2026-07-04.
Emit BUILD_TIME and BUILD_GIT (git describe --always --dirty) as
rustc env vars so the running firmware can identify itself on serial
and on-panel. A rerun-if-changed on a nonexistent file forces the
script every build, keeping the timestamp fresh. Bring-up lesson:
know which build you're diagnosing.
The bench board is a DevKitC-1 v1.0 (RGB LED on GPIO 48), not the v1.1
layout (GPIO 38). Vendor the official v1.0 pinout diagram under docs/ and
reference it from the README so the pinout is pinned offline, and fold in
the 2026-07-04 on-hardware blink verification note.
The DevKitC-1 has no discrete LED on GPIO 2, so the Spike 1 blink was
invisible without external wiring. Drive the on-board addressable LED
(WS2812 on GPIO 48) each cycle for visible confirmation on the bench.
Zed's bundled rust-analyzer calls `cargo metadata --lockfile-path`,
which is still gated behind `-Z unstable-options` on cargo 1.95.0
(both stable and the esp Xtensa fork). Workspaces fail to load with
the bundled binary.
Point lsp.rust-analyzer.binary.path at the rustup-managed
rust-analyzer (version-locked to cargo 1.95 stable, no unstable
flags). Also set cargo.target = xtensa-esp32s3-espidf with
allTargets=false so RA doesn't try to check the crate for the host
target — esp-idf-sys can't build for macOS.
Generated from esp-rs/esp-idf-template for the ESP32-S3 std target.
src/main.rs toggles GPIO 2 every 500 ms and logs `blink N` over USB-
serial — the minimum bring-up surface called out in
docs/v0.1-mvp-technical.md (Spike 1: confirm toolchain, flash, and basic
GPIO). edition=2024 with rust-version=1.85.
No editor/render/git/usb/fs modules yet; those land per the spike
methodology when later spikes need them.
Latency is the dominant signal for W1 (sub-second response to typing)
and on a typing-focused device the reMarkable 2 + Type Folio's e-ink
delay disqualifies a 2. Updates the TikZ perception array, the table
row + rationale, and the totals narrative (reMarkable 45 → 44, ranking
unchanged).
Two stale bits in §4's preamble. (a) "15×15 roof matrix" still
referenced H14's pre-retirement column count — corrected to 14×14.
(b) The symbol legend named classical QFD glyphs (◎ ○ × ⊗) but
quality-house.md's roof actually renders ASCII (++ + − −−). Both
listings now lead with the ASCII glyphs that match the source of
truth, with classical equivalents kept in parens.
GLOSSARY.md was reachable only via docs/qfd.md's §2 preamble.
Surfacing it from README adds two entry points: the intro and
software-stack pointers to docs/qfd.md now follow up with a link
to GLOSSARY.md (the ontology layers used across the docs), and
the repo-layout block lists GLOSSARY.md alongside CONTEXT.md so
the two glossaries' division of labour — device vocab vs
methodology vocab — is visible at a glance.
The H10-H15 mild conflict node still pointed at C-10-15 (H15's
pre-retirement column). H14's removal renumbered H15 to column 14,
so the roof coordinate becomes C-10-14. Missed in the earlier
sweep — comment line already says "H10-H15" so the intent was
unchanged, only the cell coord was stale.
The doc previously conflated functions (transformations the device
performs) with characteristics (measurable attributes). Surfacing
Functions as their own ontology layer makes HOW names — which pack
a function reference plus an attribute — readable without function
definitions staying implicit.
- new GLOSSARY.md at root: 5-layer ontology stack (WHAT / Function /
Characteristic / Metric+Unit / Target), peer to CONTEXT.md
(device-specific vocabulary)
- qfd.md §2 opens with a Functions inventory (Type, Save, Publish,
Recover, Boot, Provision); Render and Reconnect noted as
sub-functions; Provision flagged as build-time-only in v0.1
- H1 "Keypress → glyph latency" → "Type latency (keypress → glyph)"
- H4 "Cold boot → cursor ready" → "Boot latency (cold)"
(the arrow transformation moves to the Functions inventory; the
parenthetical keeps the residual qualifier)
- cascade through §3 priority list, §6 critical-performance-budget
rows for H1/H4, §8 inconsistencies, and quality-house.md TikZ
column labels
Matrix cell strengths held; no Σ recompute.
Module count / public-API surface is a static source-code property,
not a runtime function nor an artifact characteristic like H10 binary
or H15 build time. It was a refactor-leverage proxy for W9 — proxies
of proxies belong in the ADRs and the component matrix, not in the
HoQ. Removed from §2, §5 matrix, §4 conflict list, and the C12
overloaded-list mention; basement Σ total drops 1674 → 1557 so rel%
recomputed in quality-house.md.