Phase 3 Plan F: editor + persistence + final demo (Phase 3 complete)#18
Merged
Conversation
8 tasks: edit binary skeleton + raw mode (1), file load + ^S save (2), insert + backspace (3), horizontal arrows + ANSI redraw (4), vertical arrows (5), e2e-editor (6), e2e-persist (7), trace polish + README + deck updates (8). Closes Phase 3.
edit.zig is a stub today: enter raw mode, read bytes, exit on ^X, restore cooked mode. No file load, no save, no rendering yet — but it proves the raw-mode in/out dance works end-to-end against the 3.E console line discipline.
edit /etc/motd now loads the file into content[0..content_len] before entering raw mode, and ^S re-opens the path with O_WRONLY|O_TRUNC| O_CREAT and writes the buffer back. With no insert/delete logic yet, ^S^X is a no-op round-trip that leaves the file byte-identical.
Printable bytes (and \n / \r normalized to \n) insert at the cursor offset, shifting the tail right and bumping cursor. Backspace shifts the tail left, decrementing cursor. Buffer-full inserts drop silently; backspace at offset 0 is a no-op. Cursor still moves only via insert (no arrow keys yet — Tasks 4-5).
A 2-state ESC parser ingests ESC [ A/B/C/D. This task wires C (right) and D (left); A/B are accepted but no-op until Task 5. After every content/cursor mutation, redraw() emits \x1b[2J\x1b[H to clear, prints the full buffer, then \x1b[<row>;<col>H to land the terminal cursor at the byte-offset's row/col — both 1-based per ANSI semantics. Row/col arithmetic walks newlines from the start of the buffer.
ESC [ A and ESC [ B move the cursor to the same column on the previous/next row, clamping to the target line's length. lineStart and lineEnd walk newlines forward/backward from a given offset. The motd demo is one line so this is defensive plumbing, but it makes edit usable on multi-line files.
editor_input.txt is a 43-byte binary fixture that drives the canonical demo session: edit /etc/motd → 2× right-arrow → Y → ^S → ^X → cat /etc/motd → exit. The harness copies shell-fs.img to zig-out/ editor-test.img so the editor's save doesn't mutate the build artifact, spawns ccc with --input + --disk, captures stdout, and asserts the discriminating landmark "$ cat /etc/motd\nheYllo from phase 3\n" appears (proves the editor wrote the file and cat read it back through a fully-restored cooked-mode shell).
Two-pass test: pass 1 writes /etc/motd via "echo replaced > /etc/motd"; pass 2 (a fresh ccc invocation on the SAME --disk image) cats /etc/motd and the harness asserts "replaced\n" appears after the prompt. Proves the kernel's bwrite path actually mutates the host-backed block device file and that pass 2 reads it via a fresh kernel + bufcache instance — the only state surviving between passes is the on-disk image. Uses cooked-mode echo (independent of edit.zig) so persistence regressions can't be masked by editor regressions.
Bumped README to "Phase 3 complete" with a Plan 3.F summary block; added kernel-edit / e2e-editor / e2e-persist rows to the build commands table; added edit.zig + the new e2e fixtures to the layout. Deck: replaced the "Next · plan 3.f" panel with a ✓ 3.F status row matching the 3.A-3.E rows above; flipped "Phase 3 · underway" to "Phase 3 · complete" on the closing chapter; added a Ch 3.F slide. Trace eyeball pass over the editor session showed the existing markers are consistent end-to-end — no formatter changes needed.
Code review caught two issues in the index.html updates: 1. The new ✓ 3.F status row was placed AFTER </div> closing check-list, leaving it as a stray sibling of check-list inside two-col rather than as the 6th row alongside 3.A-3.E. Moved it inside check-list. 2. The new Ch 3.F slide used bare <code> tags in its body and caption instead of <code class="inline"> — inconsistent with every other slide's body text. Added the inline class to all 12 instances.
… column
Final-review nits caught two cosmetic issues:
1. zig fmt --check failed on src/kernel/user/edit.zig — the compact
"0x08, 0x7F => { backspace(); redraw(); }," switch arms got
expanded to multi-line by the formatter. Ran zig fmt to match the
neighbor files' style.
2. The epilogue's <div class="two-col"> kept "grid-template-columns:
1.25fr 1fr" even after the right-column "Next · plan 3.f" panel
was replaced by the in-checklist 3.F row. Collapsed to 1fr so the
reserved right column doesn't leave wasted whitespace.
Phase 3 spec §Definition of Done's "^C in the shell cancels a foreground program (proves kill-flag)" bullet had no automated test through Plan 3.F. The code path was fully wired (console.feedByte catches 0x03 → proc.kill(fg_pid) → killed flag set → console.read returns -1 → syscall dispatch calls proc.exit) but only verified by code inspection. This adds a 10-byte fixture "cat\n\x03exit\n" and a harness that asserts the landmark "cat\n^C\n$ exit" appears in stdout after the ccc run — proving cat actually got killed, the shell returned to its prompt loop, and exit cleaned up normally.
The e2e-cancel test added in commit ad65594 closes Phase 3 §DoD's "^C cancels foreground program" bullet, but the README's Plan 3.F summary block and the deck's ✓ 3.F row + Ch 3.F slide didn't yet mention it. This commit extends those doc surfaces so the kill-flag chain is documented alongside the editor and persistence stories.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes Phase 3 — the multi-process OS phase. After this lands, the spec's full §Definition of Done holds end-to-end.
edituserland binary (src/kernel/user/edit.zig, ~205 LoC) — cursor-moving text editor that finally exercises 3.E's raw-mode console arm. Loads file into a 16 KB buffer; redraw-on-every-keystroke ANSI loop; ESC[A/B/C/Darrows, printable insert, backspace,^Ssave (close +O_TRUNCre-open),^Xexit (cooked-mode restore viadefer).e2e-editor(43-byte fixture: edit /etc/motd → 2× right → Y → ^S^X → cat assertsheYllo from phase 3);e2e-persist(two ccc passes on copied image; pass 2 sees pass 1's writes);e2e-cancel(10-byte fixturecat\n\x03exit\nproves the kill-flag chain end-to-end —console.feedByte(0x03)→proc.kill(fg_pid)→killedflag →console.readreturns -1 → syscall dispatch callsproc.exit).index.html) gets a Ch 3.F slide + ✓ 3.F status row replacing the prior "Next · plan 3.f" panel; closing chapter title flipped from "Phase 3 · underway" to "Phase 3 · complete".The plan doc lives at
docs/superpowers/plans/2026-04-27-phase3-plan-f-editor-persist.md(8 tasks, each landing as its own commit).Test plan
zig build test— all unit tests passzig build riscv-tests— rv32ui/um/ua/mi/si-p-*(67 tests) passe2e,e2e-mul,e2e-trap,e2e-hello-elfe2e-kernele2e-multiproc-stub,e2e-fork,e2e-fs,e2e-shelle2e-editor,e2e-persist,e2e-cancele2e-snake,e2e-plic-blockzig build run -- --disk shell-fs.img kernel-fs.elfboots to\$prompt;edit /etc/motdworks interactively;^Xreturns cleanly to cooked-mode shellPhase 3 §Definition of Done — closed
kernel.elfbuildskernel-fs.elf)mkfs+fs-imgproducefs.imgshell-fs.img)ccc --disk … kernel.elfboots /bin/init → /bin/sh →\$prompte2e-shelle2e-shell,e2e-editor,e2e-persist^Ccancels foreground programe2e-cancelriscv-testspasse2e-cancel)--traceshows PLIC--- interrupt 9 (supervisor external, src N) ---markers🤖 Generated with Claude Code