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.
14 KiB
v0.5 — File palette + multi-file
Part of the Typoena macro plan. Requirements and targets: qfd.md. Load-bearing decisions: adr.md. Prefs reference: typoena-toml.md.
Status: buffer foundation landed in core 2026-07-11 (slice 1 of 4),
host-tested; the palette + transient panel (Spike 11) and delete → git-staging
(Spike 14) remain the on-device gates. The single-file Effect return became a
drained effect queue (Save{path,contents} / Load{path} / Publish /
Pull), so one action can ask the host for several steps in order — opening a
non-resident file queues a Save of the outgoing dirty buffer then a Load of
the target. The multi-buffer state deliberately avoids a rope-per-buffer rewrite:
the active buffer keeps its fields inline on Editor, inactive buffers park in a
small LRU Vec<Buffer> (≤ 3 resident = active + 2), and a switch marshals fields
in/out so the ~3k-line editing engine is untouched. A dirty parked buffer is
saved before it is evicted (nothing leaves RAM unsaved); :e <path> opens by
prefix (/sd/repo → Tracked, /sd/local → Local); :sync is refused in-core in
a Local buffer. Firmware drains the queue to empty each batch (a Load can
cascade an eviction Save), and persistence::{load_path,save_path} generalise
the atomic save off the hard-coded notes.md.
Slice 2 of 4 landed in core 2026-07-11, host-tested: the Cmd-P file
palette — a modal transient panel over the writing column with a bare
fuzzy-search input (no > prefix: > is reserved for the command palette,
slice 4 — VS Code semantics), the ranked list, and the selected row in reverse
video. A pure host-testable fuzzy matcher (fuzzy_score: subsequence match,
boundary + consecutive-run bonuses, no penalties) ranks results; an in-core MRU
floats recently-opened files to the top on an empty query and is shared with
:e (both flow through open_path). The host feeds the file list once at boot
(set_file_list, enumerating /sd/repo + /sd/local, dotfiles skipped);
Ctrl-n/Ctrl-p (fzf-style; Ctrl-d/Ctrl-u too) move the selection — the
60 % board has no arrow keys — Enter opens via the same park/evict path as :e,
Esc (or Cmd-P again) closes. Same slice: Ctrl-n/Ctrl-p also work as
down/up line motions in Normal mode (vim CTRL-N≡j, CTRL-P≡k,
count-aware), which is why the palette opener moved to Cmd-P alone. Scope
shows as the inline repo/… vs local/… label rather than the planned
[git]/[local] badge — it also disambiguates subpaths, not just scope. 111
editor tests + 28 keymap tests pass; the no-git firmware binary builds clean.
The transient-panel refresh (Spike 11) is CONFIRMED ON DEVICE 2026-07-12 —
no ghosting (user flashed it and eyeballed the full-area partial the palette
forces); Cmd-P opens it on-device too. Remaining v0.5 slice: 4 prefs +
palette command mode.
Slice 3 (:enew + delete) COMPLETE + CONFIRMED ON DEVICE 2026-07-12
(committed c9c0716). :enew <name> creates a new file: empty, active, marked dirty
so eviction/:w persists it, and added to the in-core file list so the palette
finds it without a disk re-enumeration — no card IO until it is saved. :delete
unlinks the current file (a new Effect::Delete the host services), then
switches to the most-recently-parked buffer or an empty scratch; the discarded
buffer is never saved even when dirty. Scope for a new file is read from the
path, not a modal prompt — local/x / repo/x (the palette label form) select
the scope, a bare name uses the current buffer's scope. Same change made the
/sd prefix optional everywhere in resolve_path: /sd/repo/x, /repo/x,
and repo/x all name one file and nothing resolves outside /sd (the writer
can't reach anything else). Spike 14 (delete → git-staging) DID need a firmware
fix. The first on-device test found add_all(["*"]) alone does not stage a
deletion on this libgit2 build (the tree came back unchanged, so the second push
was a silent "up to date" no-op — the "delete didn't work" report). Fix:
stage_and_commit now runs add_all then update_all(["*"]) (git add -u),
which removes index entries whose working-tree file is gone — together they are
git add -A. Also, :delete gave no clear feedback, so the snackbar now names
the scoped file and, for a Tracked file, that it is local until :sync
(deleted repo/notes.md - :sync to publish). Deferred to later: greying the
Publish affordance for a Local buffer, and the multi-file publish count. 123
editor tests + 28 keymap tests pass; the no-git firmware binary builds clean. The
update_all fix (behind --features git, unbuildable locally) was verified on
device 2026-07-12 — :enew test.txt → :sync → :delete → :sync removed
test.txt from origin.
Slice 4 (.typoena.toml prefs + palette > command mode) COMPLETE in core
2026-07-12, HOST-TESTED not yet on-device. A Prefs type (host-testable
line-based TOML parse/serialize — flat key = value bools + one string with #
comments, no crate pulled onto xtensa) lives on Editor; the host reads
/sd/repo/.typoena.toml at boot and applies it before the first render, and a
missing/partial file falls back to per-key defaults. Keys: save_on_idle,
format_on_save, line_numbers (all bool, default on) and auto_sync
(string, default "10m", schema + default only — nothing reads it yet).
line_numbers is live: gutter_cols() returns 0 when off, so the text reclaims
the gutter's columns (the gutter - 1 field width made saturating to avoid the
underflow). The palette > command mode (VS Code semantics — a leading > in
the query switches file search to the command list) exposes the three booleans
as live toggles; Enter flips the pref, applies it at once, queues a new
Effect::SavePrefs (the editor serializes; the host does the atomic write to
.typoena.toml, which rides the next :sync to other devices), and confirms
the new state on the snackbar. The list stays open after a toggle so several
prefs flip in one visit (Esc/Cmd-P closes); :settings opens the palette
straight into > mode as a one-command shortcut (both requested by the user
2026-07-12, chosen over a separate settings modal — same surface, no duplicate
machinery). Committed c535864. Three "decide before build" calls: (1) the
idle auto-save is unformatted — :fmt runs only on explicit :w/:sync, so
tables/blank-lines are never reflowed mid-session; (2) the per-device auto_sync
override (card-local typoena.conf) is deferred — auto_sync is inert in
v0.5, so there is nothing yet to override; (3) > auto sync: <dur> as a palette
command is deferred to v0.7 — a control that changes a value nothing reads
would be a dead switch. save_on_idle is honoured host-side: a silent idle
auto-save (no snackbar, no forced e-ink flash — a safety net, not an action)
fires once per typing burst after a 1.5 s pause. 141 editor tests + 28 keymap
tests pass; the no-git firmware binary builds clean. Firmware bumped 0.4.0 →
0.5.0 (the v0.5 feature set is met). Boot-read of the prefs file CONFIRMED ON
DEVICE 2026-07-12 — a .typoena.toml in typoena-test with non-default values
(save_on_idle=false, line_numbers=false, auto_sync="5m") logged back
prefs: Prefs { save_on_idle: false, format_on_save: true, line_numbers: false, auto_sync: "5m" } at boot, a byte-exact parse (comments skipped, bools + quoted
string read). Full gate CLOSED 2026-07-12: the palette > live-toggle
round-trip is confirmed — origin's .typoena.toml went line_numbers false →
true via a device-authored publish (3c79f38), proving toggle → SavePrefs →
atomic write → git add -A → push — and the save_on_idle autosave works on
device too. v0.5 slice 4 fully DONE + on-device confirmed.
Cmd-Popens fuzzy file palette over both/sd/repo/and/sd/local/— landed and CONFIRMED ON DEVICE 2026-07-12 (Spike 11: no ghosting on the transient panel); scope shows as the inlinerepo/…/local/…label instead of a[git]/[local]badge.- [~] Open, switch, close buffers (keep ≤ 3 in memory) — open + switch + the
≤ 3 LRU-resident model with dirty-aware save-before-evict done in core
(host-tested);
:e <path>and the palette drive it today. Explicit close still to come. :eand palette share the same recent-files list — both open viaopen_path, which pushes to the in-core MRU that orders the palette.:enewcreates a new file — done in core (host-tested) 2026-07-12. Scope is read from the path (local/x/repo/xselect it, the palette label form; a bare name uses the current scope) rather than a modal prompt — the resolved scope is echoed in the snackbar. The/sdprefix is optional throughout (/sd/repo/x=/repo/x=repo/x).- Delete a file — core done (host-tested) 2026-07-12;
:deleteunlinks the current file viaEffect::Delete. For a Tracked file the removal reaches the next:syncPublish's staged set. Spike 14 (on-device) found the staging incomplete:add_all(["*"])alone did not stage the deletion, sostage_and_commitnow also runsupdate_all(["*"])(git add -u) — the two together aregit add -A. A Local file is just unlinked. The snackbar now confirms the delete and flags that a Tracked file needs:sync. Verified on device 2026-07-12 — the:enew→:sync→:delete→:synccycle removed test.txt from origin. - [~]
Ctrl-Gis disabled / hidden when the current buffer is local-scope —:sync/ Publish is blocked in-core for a Local buffer (posts "Publish unavailable (Local)"); the side-panel affordance that hides/greys the gesture is the remaining half. - The side panel briefly shows file count on
Ctrl-Gwhen the publish bundles more than one dirty Tracked file (e.g."publishing 3 files: abc1234"), so workspace-scoped behaviour stays visible to the user - Preferences file
/sd/repo/.typoena.toml— a git-tracked, hand-editable TOML file for editor behaviour, deliberately distinct from the/sd/typoena.confcard secrets (Wi-Fi / PAT / remote / author, gitignored, never committed — see v0.1). Read at boot; a missing file or key falls back to the defaults below. Core done 2026-07-12 (aPrefstype onEditor, host-testable parse/serialize, applied viaEditor::set_prefsbefore the first render); full reference:typoena-toml.md. Keys:save_on_idle(bool, defaulttrue) — auto-save the current buffer on the idle typing-pause, so:wbecomes optional rather than required. Honoured host-side as a silent save (no snackbar, no forced e-ink flash — a safety net, not an action), unformatted, once per typing burst after a 1.5 s pause.format_on_save(bool, defaulttrue) — run:fmt(table alignment, blank-line collapse, trailing-whitespace strip) on the buffer before it is persisted, so:syncis fmt → save → commit → push and:wsaves formatted. Implemented in-core 2026-07-11 (Editor), now driven by this key. Open question RESOLVED (2026-07-12): fmt runs only on an explicit:w/:sync; thesave_on_idleauto-save is deliberately unformatted, so tables/blank lines are never reflowed mid-session (the caret would jump under you on every thinking pause).line_numbers(bool, defaulttrue) — show the absolute line-number gutter (built always-on in v0.2). Off reclaims the gutter's columns for text (gutter_cols()→ 0); the palette> line numbers: on/offcommand toggles it live. Done 2026-07-12.auto_sync(duration string, default"10m";"0"/ omitted disables; min clamp ~"2m"so a palette typo can't drain the battery) — a max-staleness cap, not a wall-clock timer: opportunistic, rate-limited Publish. Push when already awake + dirty (coalesced into the idle-pause, ≤ once perauto_sync) and once on the way into sleep if dirty; never wake from deep sleep purely to sync. Wi-Fi energy is a1/Tcurve whose knee sits at 5–10 min, andsave_on_idlealready owns local data safety — so 10 min halves the sync energy of a 5-min default for no real risk. Full derivation:tradeoff-curves/wifi-auto-sync.md. The schema + default ("10m") live here in v0.5 and round-trip throughPrefs; nothing reads the value yet — the periodic side rides the better-git work (v0.7) and must interact with light / deep sleep (v0.8). Marked[~]: parsed and preserved, no behaviour.- Open question RESOLVED (2026-07-12): the per-device sync cadence override
(a card-local
typoena.conflayer over the committed prefs) is deferred —auto_syncis inert in v0.5, so there is nothing yet to override; revisit when v0.7 makes the periodic push real.
- Palette command mode — typing
>at theCmd-Ppalette switches it from file search to a command list (VS Code-style). Done in core 2026-07-12. The v0.5 commands toggle the three boolean.typoena.tomlprefs —> save on idle,> format on save,> line numbers— each label carrying its live state; Enter flips the pref, applies it at once, queuesEffect::SavePrefs(persist to the file), and confirms on the snackbar. The list stays open after a toggle (flip several, Esc/Cmd-Pcloses), and:settingsopens it directly — both added 2026-07-12 as the "change config from the device" surface (chosen over a separate settings modal). This command list is the discoverable surface later actions (:fmt, theme, font) also register into.> auto sync: <dur>deferred to v0.7 — a value control that changes nothing readable would be a dead switch.