diff --git a/docs/active-work.md b/docs/active-work.md index 10524a4..9304ee1 100644 --- a/docs/active-work.md +++ b/docs/active-work.md @@ -347,6 +347,59 @@ If it does not, stop and repair the remote/fetch configuration. githubsucks/journey-stage1b1-compile-defaults ``` +## Journey Stage 1b-3 (P1) — FRAMING OPEN, revision 1 + +- **Branch `journey-stage1b3-welcome`**, worktree `../pmacs-journey-1b3`, + based on `githubsucks/main` @ `1f290d5`. **Framing only; no code, no + PR yet.** `docs/journey-stage1b3-welcome-framing.md` revision 1, no + review rounds. The last of the 1b split (1b-1 landed #203, 1b-2 is + PR #204). +- **What it is.** Journey step 4 / `COHERENCE.md` §18: a fresh `pmacs` + greets the user with an empty buffer, an empty status line, and no + indication that `M-x` exists. The stage renders a three-line welcome + into `*scratch*` and adds a minimal `M-x help`. +- **The existing step-2 pin collides with it.** + `journey_step2_launches_unconfigured_into_scratch` asserts + `status.is_empty()` while its own message says "reports no error" — + the same predicate only while nothing writes a *non-error* status at + startup. Acceptance 8 corrects the assertion to its message's claim + and calls it out rather than burying it, since the ratchet's rule is + that stages add rows. +- **`C-h` is NOT free, and the reason is load-bearing.** It is bound to + `buffer.delete-word-backward` because non-kitty terminals cannot + disambiguate Ctrl+Backspace from Ctrl+H — both produce byte 0x08 + (`builtin/keymaps/default.lua:78-86`). Rebinding it to a help prefix + would break Ctrl+Backspace on every legacy terminal, so §2's step-4 + row calling it an oversight is wrong: it is a deliberate trade. The + help-prefix decision is deferred to §20 Priority 4's discovery arc + with the constraint recorded. +- **`*help*` already exists** (`builtin/commands/default.lua:1226`, + `show_help_text`, reused buffer, buffer-local `q`), used by + `editor.describe-command` / `-setting`, over `src/help.rs`'s + renderers and link resolution. Two gaps recorded rather than + inherited silently: it writes with `buf:delete`/`buf:insert` instead + of `set_generated_contents`, and it is **found by name**, so a + foreign `*help*` would be cleared. +- **Deliberately NOT `set_generated_contents` for the welcome** — that + lifts read-only, discards history and marks the buffer generated, all + wrong for a buffer step 5 requires the user to type into immediately. + The one place not adopting that invariant is correct, stated so a + later audit does not "fix" it. +- **Step 4 stays Partial**, so this is the first 1b stage with no §25 + landed-evidence obligation on merge: §2's row names a welcome, a + cheat sheet *and* `C-h`, and this closes the first plus a minimal + second. +- **Integrate late.** #204 is open and touches `COHERENCE.md`, + `docs/agent-handoff.md` and `docs/active-work.md`; this lane will + conflict there. Never open a standalone refresh PR. + + ```sh + git fetch githubsucks + git worktree add ../pmacs-journey-1b3 \ + -b journey-stage1b3-welcome \ + githubsucks/journey-stage1b3-welcome + ``` + ## Generated-buffer immutability lane (Arc: workbench primitives) — STAGE 1 MERGED; STAGE 2 IS NEXT **Framing #188 (revision 7) and Stage 1 #191 are both on `main` @ diff --git a/docs/journey-stage1b3-welcome-framing.md b/docs/journey-stage1b3-welcome-framing.md new file mode 100644 index 0000000..397b65a --- /dev/null +++ b/docs/journey-stage1b3-welcome-framing.md @@ -0,0 +1,293 @@ +# Journey Stage 1b-3 — say something when the editor opens + +**Status: framing, rev 1 — awaiting approval.** +**Serves `COHERENCE.md` §2 (the golden journey, step 4), §18 +(onboarding), §19, §20 Priority 1.** + +## 0. Revision history + +- rev 1 (2026-07-30) — first framing. Scouted against `githubsucks/main` + @ `1f290d5` (Journey Stage 1b-1, #203). + +## 1. What this stage is + +The last of the 1b split. `COHERENCE.md` §20 named three things: +**1b-1** (compile binding + defaults, landed as #203), **1b-2** (LSP +spawn guidance, PR #204), and this — the welcome buffer. + +§18 is unusually specific about the size of it: + +> the cheap floor — a welcome buffer in `*scratch*` naming `M-x`, the +> keybinding cheat sheet as a help buffer, and a help prefix decision — +> has no prerequisites at all. + +**This stage takes the first of those three.** §2's step-4 row lists all +three as what is missing, so §5 is explicit that step 4 does **not** +reach Works here, and says what remains. + +## 2. Ground truth + +Verified in the tree at `1f290d5`. + +### 2.1 A fresh launch says nothing, and the audit's citation is right + +`EditorCore::new` sets `status: String::new()` (`src/editor_core.rs:659`) +and creates `*scratch*` (`:629`). A user who runs `pmacs` with no +arguments and no config gets an empty buffer, an empty status line, and +no indication that `M-x` exists. + +`builtin/keymaps/default.lua`'s own header comment calls `M-x` the +"command palette" — the sole discovery affordance in the product is +knowing to press it. + +### 2.2 The step-2 ratchet pin will collide with any status-line welcome + +This is the finding that most shapes the design. +`tests/journey_acceptance.rs`: + +```rust +fn journey_step2_launches_unconfigured_into_scratch() { + let s = EditorState::new(); + assert_eq!(active_name(&s), "*scratch*"); + assert!( + status(&s).is_empty(), + "a clean launch reports no error; got {:?}", status(&s) + ); +} +``` + +The assertion's *message* says "reports no error"; the assertion itself +says **the status is empty**. Those are the same predicate only while +nothing ever writes a non-error status at startup — which is exactly +what a welcome would do. + +So a status-line welcome does not merely need a new row, it needs that +existing pin's predicate corrected to what its own message already +claims. §4 treats that as a deliberate, named amendment rather than a +silent edit, because the ratchet's rule is *stages add rows, none +removes them* and an assertion change deserves the same scrutiny. + +### 2.3 `C-h` is not free, and the reason is load-bearing + +§18 lists "a help prefix decision" as part of the cheap floor. It is +cheap to *decide* and not cheap to get wrong: + +```lua +-- builtin/keymaps/default.lua:78-86 +-- Why we also bind C-h: most terminals (anything not implementing the +-- kitty keyboard protocol) cannot disambiguate Ctrl+Backspace from +-- Ctrl+H — both legacy paths produce byte 0x08 … +bind("C-h", "buffer.delete-word-backward") +``` + +**Rebinding `C-h` to a help prefix would break Ctrl+Backspace on every +non-kitty terminal**, because those terminals cannot tell the two apart. +§2's step-4 row says "`C-h` deletes a word" as though it were an +oversight; it is a deliberate trade with a stated rationale. + +This stage therefore **does not touch `C-h`** (§5), and the framing +records the constraint so the discovery arc inherits the reason rather +than rediscovering it. + +### 2.4 There is already a `*help*` buffer mechanism + +`builtin/commands/default.lua:1226-1260` has `show_help_text(text)`: a +single reused `*help*` buffer, found by name, with a buffer-local `q` → +`buffer.kill-this`. `editor.describe-command` and +`editor.describe-setting` both render through it. `src/help.rs` supplies +`render_command` / `render_key` / `render_buffer` / `render_mode` / +`render_view` plus link resolution (`link_at`, `follow_link_at`). + +**Two facts about it that matter to any consumer:** + +- It writes with `buf:delete` + `buf:insert`, **not** + `pmacs.buffer.set_generated_contents`. It is one of the writer + mechanisms that has not adopted the generated-buffer write invariant, + so its buffer stays ordinarily editable and keeps its undo history. + Riding it inherits that; fixing it is not this stage's job, but a + stage that renders into it should not claim read-only guarantees it + does not have. +- It is **found by name**, which is the adoption hazard `listview` and + dired both refused. A foreign `*help*` buffer would be cleared. + +### 2.5 A modified `*scratch*` does not block quitting + +`editor.quit` runs the `editor.before-quit` hook and then `ed.quit()` +(`builtin/commands/default.lua:250-256`). There is no unsaved-buffer +confirmation, so welcome text left in `*scratch*` cannot strand a user +at exit. That removes the strongest objection to putting text in the +buffer, and it is stated because it is the kind of thing that would +otherwise be assumed in either direction. + +## 3. Design + +### 3.1 What the welcome says + +Three lines, no more. The floor is "this editor is not inert and here is +the one key that opens everything": + +``` +Welcome to pmacs. M-x run a command C-x C-f open a file + C-c c build C-c t terminal +This buffer is *scratch* — type to edit it, or M-x help for more. +``` + +Every key named must be **bound in the default keymap and verified by +the acceptance suite**, or the welcome becomes documentation drift with +a user attached. §4 pins that as a property over the message, not a +hardcoded list. + +### 3.2 Where it goes — `*scratch*`, not the status line + +The status line is the wrong vehicle even though §2's audit cites it: +it is overwritten by the next status write, and a user who blinks loses +the only pointer to `M-x`. §18 says "a welcome buffer in `*scratch*`", +and that is right for the reason the audit itself gives — *the empty +`*scratch*` buffer is what greets the user*. + +So: **welcome text is rendered into `*scratch*`**, under three +conditions, all necessary: + +1. **No arguments.** `pmacs FILE` and `pmacs DIR` both put something + else on screen; a welcome would be noise, and for the directory case + it would fight Stage 1a's dired listing. +2. **`*scratch*` is empty.** Never overwrite content — including a + restored session's scratch, or anything an `init.lua` wrote. +3. **It leaves the buffer unmodified**, so nothing about the greeting + looks like unsaved work. §2.5 shows quitting is not blocked either + way; this is about not lying in the modeline. + +### 3.3 What it must not do + +- **Not read-only.** Step 5 is "edit immediately"; a read-only greeting + would break the journey one step after it starts. The user types over + it, as in Emacs. +- **Not a new buffer.** A `*welcome*` buffer displayed instead of + `*scratch*` would change what step 2 asserts and give the user a + buffer whose only purpose is to be closed. +- **Not `set_generated_contents`.** That lifts the rope's read-only, + discards history, and marks the buffer generated — all wrong for a + buffer the user is meant to edit immediately. This is the one place + where *not* adopting the generated-buffer invariant is correct, and it + is stated so a later audit does not "fix" it. + +### 3.4 `M-x help` + +The welcome names `M-x help`, so that command has to exist. It renders +the keybinding cheat sheet through the existing `show_help_text` +mechanism (§2.4). + +**This is the smallest possible version of §18's second item**, and it +is included only because the welcome would otherwise point at nothing. +The full cheat sheet, `where-is`, `describe-key` and the help-prefix +question belong to §20 Priority 4's discovery arc (§5). + +## 4. Acceptance + +**N** = new behaviour, must fail on full revert. **P** = preservation, +falsified by a named mutation. + +1. **N — journey step 4, through the real entry point.** + `EditorState::new()` — the same construction `pmacs` with no + arguments performs — leaves `*scratch*` active **and non-empty**, and + its text names `M-x`. This is the ratchet row. +2. **N — every key the welcome names is actually bound.** Parse the key + sequences out of the rendered text and assert each resolves through + `pmacs.keymap.lookup`. A property over the message, so the pin cannot + rot when the wording changes — and so the welcome cannot advertise a + binding that a later stage removes. +3. **N — `M-x help` renders the cheat sheet** into `*help*`, containing + at least the keys the welcome names. Asserts content produced. +4. **P — the buffer is editable and unmodified.** Typing a character + into the greeted `*scratch*` inserts it (step 5 still works from the + first frame), and the buffer reports unmodified *before* that + keystroke. Targeted mutation: rendering through + `set_generated_contents`, which would make the buffer read-only and + fail the insert. +5. **P — a file argument suppresses the welcome.** `EditorState::open` + on a file leaves that file active with no greeting anywhere. + Targeted mutation: greeting unconditionally in `EditorCore::new`. +6. **P — a directory argument suppresses it too**, so Stage 1a's dired + listing is what the user sees. Same mutation; separate pin because + the directory path reaches scratch differently (the bootstrap + replaces the buffer rather than never creating it). +7. **P — a non-empty `*scratch*` is never overwritten.** Write to + scratch, then trigger the greeting path: the content survives. +8. **P (amended pin) — step 2 still reports no error.** + `journey_step2_launches_unconfigured_into_scratch` currently asserts + `status.is_empty()` while its message says "reports no error" + (§2.2). The assertion is corrected to the message's claim — no error + text on the status line — rather than deleted or weakened. **This is + the one existing assertion this stage changes**, it is called out + here rather than buried in the diff, and the row itself is kept. + +## 5. Deferred, and why + +- **The help prefix / `C-h`.** §2.3: rebinding it breaks Ctrl+Backspace + on every non-kitty terminal. The decision belongs with §20 Priority + 4's discovery arc, which can weigh a prefix (`F1`? `C-c ?`?) across + the whole command family instead of one key at a time. +- **The full cheat sheet and the discovery command family** — + `where-is`, `describe-key`, richer M-x rows: §20 Priority 4, called + "the best payoff-per-effort in this document". +- **Making `show_help_text` adopt the generated-buffer write + invariant** (§2.4). A real gap, with its own lane. +- **Onboarding proper** — §18's ten-step teaching sequence. This stage + is the floor beneath it, not a down payment on it. + +**Step 4 therefore stays Partial**, and the PR must say so: §2's row +names a welcome, a cheat sheet *and* `C-h`, and this closes the first +plus a minimal version of the second. + +## 6. Coherence impact + +- **Journey steps touched:** 4 (Partial → still Partial, with the + welcome half closed); 2 indirectly, whose pin is amended (§4.8). No + grade flips on merge, which makes this the first 1b stage with no + §25 landed-evidence obligation. +- **Interaction islands: none added.** `M-x help` renders through the + existing `*help*` mechanism rather than inventing a second help + surface — though §2.4 records that mechanism's own two gaps rather + than pretending it is exemplary. +- **Config registry:** not adopted. A `welcome.enabled` scalar would be + expressible, and is deliberately not added — a greeting the user can + delete by typing does not need a setting, and §11's adoption work + should not gain a token entry from a stage this small. +- **Background-work attribution:** unchanged; nothing here is + asynchronous. +- **Docs riding the PR:** `COHERENCE.md` §2's step-4 row and §18's + ground-truth grade (both stay Partial, with the closed half named); + `docs/keybindings.md` gains `M-x help`; `docs/agent-handoff.md` §1; + the ledger. + +## 7. Questions + +- **Q#W1 — three lines, or one?** One line ("`M-x` runs a command") + is the true floor and never wraps at 80 columns. Three teaches more + but risks looking like chrome the user must clear. Recommended: + three, because the whole complaint in §18 is that the editor teaches + nothing. +- **Q#W2 — should the welcome name `C-c c` and `C-c t`?** They are + real, bound, and journey steps 8 and 9 — but naming them means the + welcome must be updated whenever the default map changes. + Acceptance 2 turns that from a risk into a caught failure, which is + the argument for naming them. +- **Q#W3 — is `M-x help` the right name?** `help` is short and + guessable. The discovery arc may want `help.keys` / + `help.commands` as a family, and renaming later costs a deprecation. + +## 8. Ledger + +Branch `journey-stage1b3-welcome`, worktree `../pmacs-journey-1b3`, +based on `githubsucks/main` @ `1f290d5`. Framing only; no code, no PR. + +**#204 is open and touches `COHERENCE.md`, `docs/agent-handoff.md` and +`docs/active-work.md`.** This lane will conflict there; integrate late +(at PR time), never by opening a refresh PR. + +```sh +git fetch githubsucks +git worktree add ../pmacs-journey-1b3 \ + -b journey-stage1b3-welcome \ + githubsucks/journey-stage1b3-welcome +```