feat: add multi-file buffer foundation (v0.5 slice 1)

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.
This commit is contained in:
Julien Calixte
2026-07-11 22:26:37 +02:00
parent fa0ea56e1a
commit 2215da939d
5 changed files with 821 additions and 168 deletions

View File

@@ -316,20 +316,42 @@ pending-`g` machinery, no vim clash. View mode stays; `v`/`V` are now Visual.
- [x] Ahead of schedule / unscheduled: `:fmt` Markdown formatter
(table alignment, blank-line collapse, trailing-whitespace strip)
## v0.5 — File palette + multi-file — [ ]
## v0.5 — File palette + multi-file — [~]
**Status:** not started (Spikes 11 + 14 retire the panel-mechanism and
buffer-lifecycle risk first).
**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`. 93 editor tests pass; the default
(no-git) firmware binary builds clean. Remaining v0.5 slices: 2 palette + fuzzy +
transient panel, 3 `:enew` + delete, 4 prefs + palette command mode.
- [ ] `Ctrl-P` opens fuzzy file palette over **both** `/sd/repo/` and
`/sd/local/`, with a scope marker (e.g. `[git]` / `[local]`) per result
- [ ] Open, switch, close buffers (keep ≤ 3 in memory)
- [ ] `:e` and palette share the same recent-files list
- [~] 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>` drives it today. Explicit **close** (and the
palette that surfaces switching) still to come.
- [~] `:e` and palette share the same recent-files list — **`:e <path>` landed**
(prefix → scope); the shared recent-files list arrives with the palette.
- [ ] `:enew` creates a new file — prompts for scope (tracked vs local)
- [ ] Delete a file — removes it from the SD card; for a Tracked file the
removal reaches the next `Ctrl-G` Publish's staged set (`git rm` / `add -A`
semantics, not plain `git add .`); a Local file is just unlinked
- [ ] `Ctrl-G` is disabled / hidden when the current buffer is local-scope
- [~] `Ctrl-G` is 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-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