docs: record dired Stage 1 (#165) as landed

#165's own commits could not update the handoff snapshot to name the
merge that contains them, so the protocol obligation lands here.

- `docs/agent-handoff.md`: absorb the dired lane into §1, replacing the
  placeholder that promised exactly this. The bullet carries Stage 1's
  durable substrate facts — why the tolerant `read_dir` had to be Rust,
  why exposing the core normalizer beat mirroring it in Lua, the
  fixed-width `_layout` contract Stage 3 reads offsets from, the
  ambient-action buffer guard, treating a failure as the answer instead
  of probing, the per-entry error cap, the first mode-scoped keymap and
  the pre-existing test it broke, and the dedication a descent does not
  carry. Refresh the head-of-`main` anchor and the last-updated line.
- `docs/agent-handoff.md` §5: two ops lessons that cost real time. A fix
  must be committed before it is bitten, because `scripts/bite` restores
  by `git checkout --` and reverts to HEAD; a CONFLICTING PR runs no CI
  at all, because `pull_request` workflows build a merge ref GitHub does
  not create while the branch conflicts, and nothing reports the absence.
- `docs/active-work.md`: remove the merged lane per update-protocol rule
  4 and summarize it under "Closed since the last snapshot", keeping the
  two forward items Stage 2 needs (the rename rebind is first-match-only
  over a raw path, and Q#DR5's seam is the main-thread drain). Refresh
  the canonical base. Flag the two lane headers that still call a merged
  PR "IN REVIEW" — #161 and #166 — rather than editing lanes another
  thread owns.
- `COHERENCE.md`: #165 is no longer a PR. Per §25 the audited claims this
  work changed were updated when it landed; this corrects their tense in
  seven places and the two prose lines that still asserted dired was in
  flight.
- `docs/dired-framing.md`: status line to MERGED, and state plainly that
  Stages 2 and 3 each still need their own framing.

Docs only; no code, no gate-relevant change.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0126d2sikA6jZpFin3rtLCSK
This commit is contained in:
Levi Neuwirth 2026-07-25 17:39:27 -04:00
parent c8ec8f3718
commit ea0632412e
4 changed files with 164 additions and 175 deletions

View File

@ -120,7 +120,7 @@ asymmetry**, and **per-arc coherence debt**.
Coherence-shaped work already in flight at audit time: find-file / Coherence-shaped work already in flight at audit time: find-file /
dired Stage 0 (`C-x C-f`, merged #162, `docs/dired-framing.md`) and its dired Stage 0 (`C-x C-f`, merged #162, `docs/dired-framing.md`) and its
Stage 1 directory view (PR #165), bottom panel Stage 1 (merged #155), Stage 1 directory view (merged #165), bottom panel Stage 1 (merged #155),
multi-root LSP affinity (merged #161), the config registry foundation multi-root LSP affinity (merged #161), the config registry foundation
(merged #127). (merged #127).
@ -195,7 +195,7 @@ working, unreachable capability:
time; a complete 1,384-line dired existed only as a frozen test time; a complete 1,384-line dired existed only as a frozen test
fixture (`tests/fixtures/pmacs-dired/init.lua`). **Fixed:** dired fixture (`tests/fixtures/pmacs-dired/init.lua`). **Fixed:** dired
Stage 0 opens a path (`C-x C-f`, merged #162) and Stage 1 ships the Stage 0 opens a path (`C-x C-f`, merged #162) and Stage 1 ships the
browsing view as a builtin (`C-x d` / `C-x C-j`, PR #165). The fixture browsing view as a builtin (`C-x d` / `C-x C-j`, merged #165). The fixture
stays frozen — its `install_local` + `require` routing *is* the M8 stays frozen — its `install_local` + `require` routing *is* the M8
package-universality proof (Q#DR1) — and shrinking it is scheduled package-universality proof (Q#DR1) — and shrinking it is scheduled
after Stage 3. after Stage 3.
@ -363,11 +363,11 @@ Full verdict table:
|---|---|---|---| |---|---|---|---|
| 1 | Install | **Partial** | Source build only: `cargo build --release --workspace --features pmacs/crdt` (`README.md`). No binaries, no packaging. Runtime deps (`/bin/sh`, git, tar, coreutils) documented, never checked at runtime | | 1 | Install | **Partial** | Source build only: `cargo build --release --workspace --features pmacs/crdt` (`README.md`). No binaries, no packaging. Runtime deps (`/bin/sh`, git, tar, coreutils) documented, never checked at runtime |
| 2 | Launch unconfigured | **Works** | `EditorState::new()` → empty `*scratch*`; missing config is not an error (`src/config.rs:7-9`); recentf/saveplace/autosave default-on | | 2 | Launch unconfigured | **Works** | `EditorState::new()` → empty `*scratch*`; missing config is not an error (`src/config.rs:7-9`); recentf/saveplace/autosave default-on |
| 3 | Open real project | **Missing at the CLI** | `pmacs .` still exits 1 (above): `load_file` does `File::open` (which succeeds on a directory) then `read_to_end` → EISDIR, which is not `NotFound`, so `resolve_target_buffer`'s create-a-`[new file]` arm never fires. Dired Stage 1 (PR #165) supplies the buffer a directory should resolve *to*; routing `pmacs .` into it is Journey Stage 1's work, which must not invent a second directory surface | | 3 | Open real project | **Missing at the CLI** | `pmacs .` still exits 1 (above): `load_file` does `File::open` (which succeeds on a directory) then `read_to_end` → EISDIR, which is not `NotFound`, so `resolve_target_buffer`'s create-a-`[new file]` arm never fires. Dired Stage 1 (merged #165) supplies the buffer a directory should resolve *to*; routing `pmacs .` into it is Journey Stage 1's work, which must not invent a second directory surface |
| 4 | Understand interface | **Partial** | Mode line gives name/modified/L:C/scroll + mode/LSP/terminal segments; but no welcome text (`EditorCore::new` sets `status: String::new()`), no cheat sheet, and `C-h` deletes a word (§18) | | 4 | Understand interface | **Partial** | Mode line gives name/modified/L:C/scroll + mode/LSP/terminal segments; but no welcome text (`EditorCore::new` sets `status: String::new()`), no cheat sheet, and `C-h` deletes a word (§18) |
| 5 | Edit | **Works** | Full CUA + Emacs keymap in 161 lines (`builtin/keymaps/default.lua`); isearch, query-replace, kill ring, undo/redo, auto-indent/pair/comment, atomic save. Genuinely excellent zero-config | | 5 | Edit | **Works** | Full CUA + Emacs keymap in 161 lines (`builtin/keymaps/default.lua`); isearch, query-replace, kill ring, undo/redo, auto-indent/pair/comment, atomic save. Genuinely excellent zero-config |
| 6 | Language intelligence | **Partial** | Rust grammar bundled and auto-attaches; rust-analyzer preconfigured (`builtin/runtime/lsp.lua:44-52`) — but a missing binary fails silently (§1.2) and highlighting masks it. No LSP status command exists to diagnose | | 6 | Language intelligence | **Partial** | Rust grammar bundled and auto-attaches; rust-analyzer preconfigured (`builtin/runtime/lsp.lua:44-52`) — but a missing binary fails silently (§1.2) and highlighting masks it. No LSP status command exists to diagnose |
| 7 | Find symbol / file | **File: fixed (open by path merged #162; browsing PR #165). Symbol: works but undiscoverable** | No find-file/dired/picker existed at audit. Now `C-x C-f` opens a known path and `C-x d` / `C-x C-j` browse (flat listing, `dired` mode keymap); `M-.`/`M-?`/`C-c o` still bound but advertised nowhere and server-gated; no workspace-symbol command; `pmacs.index.*` has no UI | | 7 | Find symbol / file | **File: fixed (open by path merged #162; browsing #165). Symbol: works but undiscoverable** | No find-file/dired/picker existed at audit. Now `C-x C-f` opens a known path and `C-x d` / `C-x C-j` browse (flat listing, `dired` mode keymap); `M-.`/`M-?`/`C-c o` still bound but advertised nowhere and server-gated; no workspace-symbol command; `pmacs.index.*` has no UI |
| 8 | Open terminal | **Works but undiscoverable** | Full PTY with scrollback + modeline segment — reachable only as `M-x terminal`, no keybinding. *Was broken outright on the GPU frontend until the double terminal-layout sync was fixed: the child took a `SIGWINCH` storm at tick cadence, so typing into it was impossible while output still flowed.* | | 8 | Open terminal | **Works but undiscoverable** | Full PTY with scrollback + modeline segment — reachable only as `M-x terminal`, no keybinding. *Was broken outright on the GPU frontend until the double terminal-layout sync was fixed: the child took a `SIGWINCH` storm at tick cadence, so typing into it was impossible while output still flowed.* |
| 9 | Build / test | **Partial** | `M-x compile.run` works, defaults cwd to detected project root, parses Rust `-->` errors — but no keybinding, an **empty first prompt** (`initial = last and last.cmdline or ""`, `builtin/runtime/compile.lua:1134-1138`), and no `cargo build`/`cargo test` suggestion despite `ProjectKind::Cargo` existing (`src/project.rs:77`) | | 9 | Build / test | **Partial** | `M-x compile.run` works, defaults cwd to detected project root, parses Rust `-->` errors — but no keybinding, an **empty first prompt** (`initial = last and last.cmdline or ""`, `builtin/runtime/compile.lua:1134-1138`), and no `cargo build`/`cargo test` suggestion despite `ProjectKind::Cargo` existing (`src/project.rs:77`) |
| 10 | Inspect error | **Partial (good once reached)** | `E:n W:n` modeline counts, underlines, `M-g n/p` + ``C-x ` `` walking a unified compile/grep/diag source, message echo, `RET` visits. Gated entirely on step 6 or 9 succeeding first | | 10 | Inspect error | **Partial (good once reached)** | `E:n W:n` modeline counts, underlines, `M-g n/p` + ``C-x ` `` walking a unified compile/grep/diag source, message echo, `RET` visits. Gated entirely on step 6 or 9 succeeding first |
@ -460,7 +460,7 @@ level is the one missing. Audited level-by-level:
**Beginner** (should see: files, buffers, search, diagnostics, terminal, **Beginner** (should see: files, buffers, search, diagnostics, terminal,
build actions, menus, missing-tool guidance): build actions, menus, missing-tool guidance):
- files ✓ since #162 / PR #165 (`C-x C-f` opens a path, `C-x d` browses; - files ✓ since #162 / #165 (`C-x C-f` opens a path, `C-x d` browses;
neither is advertised anywhere but the keymap) · buffers ✓ (`C-x neither is advertised anywhere but the keymap) · buffers ✓ (`C-x
b`, `*buffer-list*`) · search ✓ (`C-s`/`C-r`/`C-M-s`; project.search b`, `*buffer-list*`) · search ✓ (`C-s`/`C-r`/`C-M-s`; project.search
is M-x-only) · diagnostics ✓ once a server runs · terminal ✓ but is M-x-only) · diagnostics ✓ once a server runs · terminal ✓ but
@ -1183,7 +1183,7 @@ Primitive-by-primitive against the list above:
hierarchy, package dependency graph, worker trees, git status) will hierarchy, package dependency graph, worker trees, git status) will
each need it; building it once *before* dired's directory view and each need it; building it once *before* dired's directory view and
the workers tree harden their own conventions is exactly this the workers tree harden their own conventions is exactly this
section's point. Dired Stage 1 (PR #165) landed **without** inventing section's point. Dired Stage 1 (merged #165) landed **without** inventing
one: its listing is flat (Emacs parity), and the recursive one: its listing is flat (Emacs parity), and the recursive
in-buffer case — `i` insert-subdirectory — is a named deferral in in-buffer case — `i` insert-subdirectory — is a named deferral in
`docs/dired-framing.md` §13, which is where a shared tree primitive `docs/dired-framing.md` §13, which is where a shared tree primitive
@ -1340,7 +1340,8 @@ greets a new user says nothing (`EditorCore::new` sets an empty
status). status).
Note the dependency: five of the ten onboarding steps above currently Note the dependency: five of the ten onboarding steps above currently
lead somewhere broken or invisible (find a file — in flight; inspect a lead somewhere broken or invisible (find a file — the mechanism is fixed
since #162/#165 but is advertised nowhere except the keymap; inspect a
diagnostic — silent-failure risk; view workers — undiscoverable; diagnostic — silent-failure risk; view workers — undiscoverable;
setting provenance — unanswerable). Onboarding is correctly sequenced setting provenance — unanswerable). Onboarding is correctly sequenced
*after* the P1/P4 fixes, but the cheap floor — a welcome buffer in *after* the P1/P4 fixes, but the cheap floor — a welcome buffer in
@ -1404,7 +1405,7 @@ Establish the end-to-end workflow; treat regressions as release
blockers. **State: broken at step 3 (§2). Mostly wiring, and unusually blockers. **State: broken at step 3 (§2). Mostly wiring, and unusually
cheap:** directory-argument handling (the remaining half of step 3 — cheap:** directory-argument handling (the remaining half of step 3 —
dired Stage 1 landed the buffer it should resolve to); a find-file dired Stage 1 landed the buffer it should resolve to); a find-file
surface (**done**: #162 open-by-path, PR #165 browsing); surfacing the surface (**done**: #162 open-by-path, #165 browsing); surfacing the
LSP spawn failure with guidance (§1.2); a LSP spawn failure with guidance (§1.2); a
compile keybinding + `cargo build`/`test` default from the existing compile keybinding + `cargo build`/`test` default from the existing
`ProjectKind::Cargo`; a terminal keybinding; a welcome buffer. The `ProjectKind::Cargo`; a terminal keybinding; a welcome buffer. The
@ -1474,7 +1475,9 @@ implementation — this list is direction, not commitment):
1. **Journey Stage 1** (P1): directory open + compile defaults + 1. **Journey Stage 1** (P1): directory open + compile defaults +
LSP-failure surfacing + bindings + welcome buffer + the first LSP-failure surfacing + bindings + welcome buffer + the first
journey acceptance suite. Rides alongside the in-flight dired arc. journey acceptance suite. Dired Stage 1 has landed (#165), so the
buffer a directory resolves *to* already exists; this arc routes
`pmacs .` into it rather than growing a second directory surface.
2. **Discovery surface** (P4): the describe/list/where-is command 2. **Discovery surface** (P4): the describe/list/where-is command
family, M-x rich rows, help unification, help prefix. family, M-x rich rows, help unification, help prefix.
3. **Transient keymap layer** (§6): the overlay scope + lifetime 3. **Transient keymap layer** (§6): the overlay scope + lifetime

View File

@ -5,6 +5,14 @@ landed on `main`. Read it after `docs/agent-handoff.md`. Remove completed
entries when their PR merges; do not let this become a second permanent entries when their PR merges; do not let this become a second permanent
backlog. backlog.
**Two lane headers below are stale on purpose**, pending the docs updates
their own lanes owe: multi-root LSP affinity **#161 has merged** (the
Lean 4 lane still says IN REVIEW; its continuation is PR #167) and GPU
terminal input **#166 has merged** (its lane still says IN REVIEW; PR
#168 records it). Trust the canonical-base line below over a lane header:
if a PR number appears in `git log --first-parent githubsucks/main`, it
has landed regardless of what its lane says.
## Repository authority ## Repository authority
- Canonical development URL: - Canonical development URL:
@ -14,11 +22,14 @@ backlog.
machine-local: `origin` may name this canonical URL, a release mirror, machine-local: `origin` may name this canonical URL, a release mirror,
or something else, and therefore has no authority by name alone. or something else, and therefore has no authority by name alone.
- Canonical base at this snapshot: - Canonical base at this snapshot:
`githubsucks/main` @ `8c86d34` (the dired framing #164 atop find-file `githubsucks/main` @ `c8ec8f3` (dired Stage 1 #165 atop GPU terminal
#162, COHERENCE.md #163, Lean 4 Stage 1 #160, the minimap blank-slab fix input #166, multi-root LSP affinity #161, the dired framing #164,
#159, bottom-panel Stage 1 #155, the inline-math re-scout #154, the vterm find-file #162, COHERENCE.md #163, Lean 4 Stage 1 #160, the minimap
PTY-flake fix #153, and the GPU initial-target doc refresh #152; protocol blank-slab fix #159, bottom-panel Stage 1 #155, the inline-math re-scout
v20). #154, the vterm PTY-flake fix #153, and the GPU initial-target doc
refresh #152; protocol v20). **Lanes below that name an older base have
not been re-based; derive their integration surface from
`git diff <their base>..main`.**
- On the transfer source, `origin/main` named a release mirror at - On the transfer source, `origin/main` named a release mirror at
`d3fa632` and lagged badly. On the current destination, `origin` names `d3fa632` and lagged badly. On the current destination, `origin` names
the canonical URL. This difference is why all recovery begins by the canonical URL. This difference is why all recovery begins by
@ -189,154 +200,6 @@ If it does not, stop and repair the remote/fetch configuration.
suites**; `git diff --check` clean. The sweep needs an isolated suites**; `git diff --check` clean. The sweep needs an isolated
`XDG_CONFIG_HOME` and `-- --skip basedpyright`. `XDG_CONFIG_HOME` and `-- --skip basedpyright`.
## Dired lane — Stage 0 MERGED; Stage 1 IN REVIEW (PR #165)
- Approved framing: `docs/dired-framing.md` **revision 6** — rev 5 is the
approved text (merged as its own docs PR #164), rev 6 adds §0's Stage 1
implementation notes (S1-1…S1-9). Stages 2 (marks and operations) and 3
(wdired) each get their own detailed framing after the prior stage lands.
- **Stage 0 (`C-x C-f` find-file) MERGED as #162** (`main` @ `2af1ab3`,
2026-07-25, one review round, 12/12 CI green). Durable facts moved to
`docs/agent-handoff.md` §1 per rule 3 below.
- **Stage 1 branch: `githubsucks/dired-stage1`**, worktree
`../pmacs-dired-stage1`, based on `githubsucks/main` @ `8c86d34` (the
framing merge #164). **A fresh cut, not a rebase:** the older `dired`
branch (`ffdd642`, worktree `../pmacs-dired-arc`) was based on the
superseded `0827dd1` and carried only the framing content #164 already
put on `main`, so merging it would have reconciled two histories of one
document. It is left untouched and carries nothing unmerged.
- **Stage 1 implemented; no wire change (protocol stays v20).** What
landed on the branch:
- `builtin/runtime/dired.lua`: one buffer per directory named
`*dired:<canonical path>*` with the handle-table ownership check;
read-only intercept + `set_round_trip_input`; the `dired` major mode
and its mode-scoped keymap (`RET`/`f`, `^`, `n`/`p`, `g`, `q`, `s`);
basename cursor re-seating across every wholesale repaint;
`display_file` for file visits and same-window reuse for directory
descent; `C-x d` / `C-x C-j`; the `dired.kill-when-opening` setting.
Loaded after `window.lua`.
- `src/fs.rs`: `ReadDirTolerance`, `FsDirEntryError`, `FsDirListing`,
and one walk that either fails on a per-entry condition or records it
(Q#DR6). `src/async_runtime.rs` carries the listing in
`ReplyKind::ReadDir` / `JobResult::ReadDir`; `src/lua_bindings/mod.rs`
keys the Lua result **shape** on `errors.is_some()`, so the bare array
the frozen M8.2 fixture consumes with `ipairs` is untouched;
`builtin/runtime/fs.lua` validates read-op opts and **rejects unknown
keys** (a typo'd `tolerant` used to degrade silently to fatal).
- `src/editor_core.rs` + `src/lua_bindings/mod.rs`:
`normalize_buffer_path` is `pub` and exposed as
`pmacs.path.canonicalize` — Q#DR2's preferred end state, so no Lua
mirror exists and Stage 2 owes no mirror removal. This makes B2
("tolerant `read_dir` is the only Rust change") false by one small
binding, deliberately.
- `tests/dired_acceptance.rs`: 22 tests over framing items 116,
dispatch-driven; item 17 is the m8_1/m8_2/m8_3 additivity gate.
- **The framing claim the substrate falsified (S1-2):** R2-3 expected a
dedicated dired panel to carry its dedication across a descent.
`display_buffer` never replaces the buffer in a slot dedicated to
another one — it discards every side-specific parameter and falls back
to the document window (Q#BP3 2.iii), and the exact-window arm errors.
Dired does not unpin the user's panel; both arms are pinned.
- **The vacuity the bites found (S1-3):** acceptance 3c cannot pin the
descent *routing*. Dired holds focus in its own panel, so a raw
`switch_buffer` lands in the same window and every 3c assertion holds
either way. Dedication is the only discriminator, so the
dedicated-panel test is the real pin — and the vacuity is documented at
the assertion rather than relabelled.
- **The pre-existing test dired's first mode-scoped binding broke
(S1-4):** `describe_key_identifies_every_default_binding` asserted every
binding in the stack resolves through `describe.key` context-free, which
held only while the modes table was empty. It now sets the effective
context per binding and explicitly *clears* the mode for global ones,
because a leaked mode legitimately shadows a global chord of the same
name (dired's `RET` shadows `edit.newline-and-indent`).
- Durable substrate facts, independent of this arc:
- `pmacs.buffer.kill` (not `remove`) redirects windows off a doomed
buffer before removal, so `kill-when-opening` kills **after** the
replacement is displayed.
- Interactive origin does **not** survive an await: work resumed in
`tick_async` sees no `InteractiveCommandOrigin`, so `pmacs.window.*`
acts for the *ambient* active frontend (S1-9).
- Kinds are lstat-based in both `read_dir` and `stat`, so nothing in an
entry says whether a symlink points at a directory; `RET` probes by
trying to list it (S1-8).
- A path-backed buffer's *name* is its full path, not its basename —
worth knowing before writing any name assertion.
- `C-x d` takes **no** completion source on purpose (S1-5): with one,
RET on an empty field opens whatever sorts first, and
RET-on-where-you-are is the gesture the binding exists for. The field
is prefilled instead.
- **Bite verification:** 15 claims, each mutated in place and required to
fail the test that names it. `dired.lua` is new, so `scripts/bite`'s
file swap does not apply; every mutation was applied and reverted with
`git checkout --`. One came back VACUOUS and is recorded above.
- **Review round 1 addressed** (framing rev 7, S1-10…S1-12). Three
behavioral fixes, each bite-verified: `dired.revert`'s re-seat is
guarded on the active buffer (an ambient `move_to_line` after an await
moved an unrelated buffer's cursor — the buffer-level instance of
S1-9); `fmt_size` keeps the column width past ten digits, because
`_layout` is a contract Stage 3 is planned against; and the symlink
descent dropped its probe, since `open_directory`'s
changed-nothing-on-failure invariant *is* the probe (it was listing the
target directory twice). Plus a consecutive-`readdir`-error cap, because
**nothing cancels a dired listing** — it carries no supersede key, so
cancellation was never the backstop the tolerant loop implicitly relied
on. Naming/comment findings taken as-is.
- Durable process lesson, hit twice now: a mutation-bite helper restores
with `git checkout --`, which reverts to **HEAD** — so a fix must be
committed *before* it is bitten. Round 1's fixes were briefly wiped by
exactly that.
- **Canonical main integrated twice** — at `46a1b8f` (multi-root LSP
affinity #161) and again at `b889873` (GPU terminal input #166), both
merged rather than rebased per the #135/#137 precedent so the review
anchors stay addressable. Each conflict was a single doc hunk resolved
as the union: this lane owns COHERENCE's journey step 7 file half, #161
owns the in-flight list, #166 owns step 8's GPU-terminal addendum.
Three things worth carrying:
- **A conflicting PR silently stops running CI.** GitHub builds
`pull_request` runs against the merge ref, which does not exist while
the PR conflicts, so no run is created and nothing reports a
failure — the checks list simply stays as it was. Three pushes to
this branch produced no CI at all before the cause was found. Watch
`mergeable` on a long-lived lane, not just the check list.
- #161's own COHERENCE finding **falsified a claim in this lane's
module doc**: `pmacs.error` is never defined in production, so an
uncaught raise inside a `pmacs.async` coroutine does not reach
`*errors*` as the comment said. It reaches a bare `error()` inside
`pmacs._async.tick()`, whose result `tick_async` discards with
`let _ =` — i.e. nowhere. That makes dired's per-coroutine `pcall` +
`set_status` load-bearing rather than tidy, and the comment now says
so.
- **A lane in review against a fast-moving `main` needs its gates rerun
per integration, not per push.** Main advanced twice inside this
review round, and the second time landed while the first
integration's sweep was still running. The numbers below describe the
twice-merged tree.
- Verification on the twice-merged tree (`main` @ `b889873`):
`cargo fmt --check` clean; strict workspace Clippy clean; **1,832
default + 2,009 CRDT** library tests; dired acceptance **25 default +
25 CRDT**; m8_1 10 / m8_2 15 / m8_3 32 unchanged; multi-root 13 and
vterm Stage 3 5 (both suites main added, green under this lane's
`mod.rs` and `editor.rs` changes); M4 121; required GPU 155;
**isolated-`XDG_CONFIG_HOME` workspace sweep 3,205 passed across 93
suites, zero failures**; `git diff --check` clean. The sweep needs the
isolated config for the reason recorded in the bottom-panel lane
below.
- Coherence (framing §0.5, required since #163): serves `COHERENCE.md` §20
Priority 1, which names this work explicitly; journey step 7's file half
goes from no surface to a surface; **adds no interaction island** — keys
are a mode-scoped keymap, and wdired will be a mode swap; adopts
`pmacs.config` for `dired.kill-when-opening`; inherits §9's
worker-attribution gap for its `read_dir` jobs without worsening it. The
audited claims this changes are updated in `COHERENCE.md` itself, per its
§25.
- **Boundary with the Journey Stage 1 arc** (`COHERENCE.md` §20 arc-cut
1): CLI directory-argument handling (`pmacs .` exits 1) belongs there,
not here — Stage 1 does **not** fix it. The two meet at
`resolve_target_buffer`; dired supplies the buffer a directory should
resolve *to*, and `pmacs .` should route into it rather than growing a
second directory surface.
## GPU terminal input lane — IN REVIEW ## GPU terminal input lane — IN REVIEW
- Portable branch: `githubsucks/gpu-terminal-input`, worktree - Portable branch: `githubsucks/gpu-terminal-input`, worktree
@ -593,6 +456,38 @@ git worktree add --track \
## Closed since the last snapshot ## Closed since the last snapshot
- **Dired Stage 1 (the directory view) — MERGED as #165** (`main` @
`c8ec8f3`, 2026-07-25, after one review round). pmacs has a directory
surface: `C-x d` / `C-x C-j`, one read-only buffer per directory named
`*dired:<canonical path>*`, a `dired` major mode carrying
`RET`/`f`, `^`, `n`/`p`, `g`, `q`, `s`. No wire change (v20). The Rust is
two things — a per-entry-tolerant `read_dir` (Q#DR6), which had to be
Rust because `read_dir_blocking` fails a whole listing on any of five
per-entry conditions and a tolerant wrapper cannot be written in Lua at
all, and `normalize_buffer_path` going `pub` as
`pmacs.path.canonicalize` (Q#DR2's preferred end state, so no Lua mirror
exists and Stage 2 owes no mirror removal). The frozen m8_1/m8_2/m8_3
counts are unchanged, which is the additivity gate. 15 claims
bite-verified; one came back VACUOUS (acceptance 3c cannot pin descent
routing — dired holds focus in its own panel, so dedication is the only
discriminator) and is documented at the assertion rather than
relabelled. Its branch (`dired-stage1`) and worktree
(`../pmacs-dired-stage1`) are done; the abandoned `dired` branch
(`ffdd642`, `../pmacs-dired-arc`) was superseded by a fresh cut and
carries nothing unmerged. **Stage 2 (marks and operations) and Stage 3
(wdired) each still need their own framing**, and the frozen fixture
shrinks after Stage 3. Durable substrate facts and both new ops lessons
live in `docs/agent-handoff.md` §§1/5; the implementation notes are
`docs/dired-framing.md` §0, S1-1…S1-12. Two named forward items for
Stage 2: `apply_resource_op`'s rename rebind is exact-PathBuf-equality,
first-match-only, looked up with the raw path while stored paths are
normalized — so a directory rename strands every buffer under it, and
`pmacs.fs.rename` has zero production callers, so it can be fixed at
the primitive; and Q#DR5's seam is the main-thread drain
`AsyncRuntime::tick`, not `_take_result`, where rename settles as an
undifferentiated `ReplyKind::FsUnit` and so must be keyed on
`JobKind::FsRename`.
- **GPU initial target — MERGED as #148** (`main` @ `0dd16a5`, 2026-07-24, - **GPU initial target — MERGED as #148** (`main` @ `0dd16a5`, 2026-07-24,
after two review rounds). `pmacs --gpu [--socket …] FILE` opens a target after two review rounds). `pmacs --gpu [--socket …] FILE` opens a target
before the GPU window appears. Protocol bumped 19 → 20: a semantic-session before the GPU window appears. Protocol bumped 19 → 20: a semantic-session

View File

@ -1,7 +1,9 @@
# Agent handoff — cross-machine continuity # Agent handoff — cross-machine continuity
**Last updated: 2026-07-25, after find-file (#162) landed — the dired **Last updated: 2026-07-25, after dired Stage 1 (#165) landed — the
arc's Stage 0 — following COHERENCE.md (#163), Lean 4 Stage 1 (#160), the directory view — following GPU terminal input (#166), multi-root LSP
affinity (#161), the dired framing (#164), find-file (#162) — the dired
arc's Stage 0 — COHERENCE.md (#163), Lean 4 Stage 1 (#160), the
minimap blank-slab fix (#159), bottom-panel Stage 1 (#155), the minimap blank-slab fix (#159), bottom-panel Stage 1 (#155), the
inline-math re-scout (#154), the vterm PTY-flake fix (#153), and the inline-math re-scout (#154), the vterm PTY-flake fix (#153), and the
GPU initial-target doc refresh (#152); and before that GPU GPU initial-target doc refresh (#152); and before that GPU
@ -26,11 +28,13 @@ commands, read `docs/active-work.md` immediately after this file.
## 1. Where the project stands (2026-07-25) ## 1. Where the project stands (2026-07-25)
- `main` @ `2af1ab3` (find-file #162 atop COHERENCE.md #163, Lean 4 Stage 1 - `main` @ `c8ec8f3` (dired Stage 1 #165 atop GPU terminal input #166,
#160, minimap blank-slab #159, bottom-panel Stage 1 #155, inline-math multi-root LSP affinity #161, the dired framing #164, find-file #162,
re-scout #154, vterm PTY-flake #153, and doc refresh #152). Protocol COHERENCE.md #163, Lean 4 Stage 1 #160, minimap blank-slab #159,
unchanged at **v20**. The bullets below describe the arcs in their own bottom-panel Stage 1 #155, inline-math re-scout #154, vterm PTY-flake
terms; this line is the head-of-`main` anchor. #153, and doc refresh #152). Protocol unchanged at **v20**. The bullets
below describe the arcs in their own terms; this line is the
head-of-`main` anchor.
- **`COHERENCE.md` is now required reading and a required framing input - **`COHERENCE.md` is now required reading and a required framing input
#163.** It carries the product-coherence thesis, an audited #163.** It carries the product-coherence thesis, an audited
scorecard, per-concern gaps, and §20's priority order, and it is the scorecard, per-concern gaps, and §20's priority order, and it is the
@ -70,11 +74,78 @@ commands, read `docs/active-work.md` immediately after this file.
against an open buffer yet fails to load one that is not open — against an open buffer yet fails to load one that is not open —
find-file expands the tilde Lua-side. Loading through the normalized find-file expands the tilde Lua-side. Loading through the normalized
path is a named deferral. path is a named deferral.
- **Stage 1 (the directory view) is IN REVIEW as PR #165** — the - **dired Stage 1 — the directory view — LANDED — #165**
builtin `dired.lua`, the per-entry-tolerant `read_dir` opt, and (`docs/dired-framing.md` §0, S1-1…S1-12; merge `c8ec8f3`; one review
`pmacs.path.canonicalize`. Its branch state, substrate facts, and round). pmacs now has a directory surface: `C-x d` / `C-x C-j` open a
verification live in `docs/active-work.md`; this section absorbs them read-only listing, one buffer per directory named
when it merges. `*dired:<canonical path>*`, with a `dired` major mode whose
mode-scoped keymap carries `RET`/`f`, `^`, `n`/`p`, `g`, `q`, `s`.
Protocol unchanged at **v20**. **Stage 2 (marks and operations) and
Stage 3 (wdired) each still need their own framing**; the frozen
fixture shrinks after Stage 3.
- **The Rust is confined to two things**: a per-entry-tolerant
`read_dir` (`ReadDirTolerance {Fatal, PerEntry}` →
`FsDirListing {entries, errors}`), because `read_dir_blocking` fails
a whole listing on any of five per-entry conditions and the tolerant
wrapper its own module doc delegates to package authors **cannot be
written in Lua** (one error value, no partial vec); and
`editor_core::normalize_buffer_path` becoming `pub`, exposed as
`pmacs.path.canonicalize`. Only non-UTF-8 **names** stay fatal —
byte-preserving paths would be needed. The Lua result **shape** keys
on `errors.is_some()`, so the bare array the frozen M8.2 fixture
consumes with `ipairs` is untouched.
- **Exposing a core normalizer beat mirroring it in Lua.** A Lua mirror
would have been a second canonical form — the same class of bug as
the five tab-width constants (#137). Applies to any future Lua-side
path reckoning.
- **A fixed-width column must be fixed-width for every input.** The
exported `pmacs.dired._layout` (MARK 0, KIND 2, PERMS 312, SIZE 13,
MTIME 24, NAME 41) is the contract Stage 3 reads offsets from, and
`%10d` overflows at ≥10 GB, silently shifting every column right of
it. Sizes now fall back to a width-clamped magnitude (K/M/G/T/P/E).
- **An ambient action must be gated on the buffer it assumes.** A
revert's cursor re-seat settles a tick or more later, by which time
the user may have switched buffers; the paint names its buffer and is
safe, but seating is ambient. This is the buffer-level instance of
the rule below that interactive origin does not survive an await.
- **A failure IS an answer — don't probe first.** Kinds are lstat-based
in both `read_dir` and `stat`, so nothing in an entry says whether a
symlink points at a directory. `RET` tries to list it and treats the
failure as the answer; an explicit probe was a second full
`read_dir`, so a descent listed twice.
- **Unbounded per-entry error collection needs a cap when nothing
cancels the work.** A dired listing carries no supersede key, so
cancellation was never the backstop the tolerant loop implicitly
relied on (`READDIR_MAX_CONSECUTIVE_ENTRY_ERRORS = 1024`).
- **This is the first builtin with mode-scoped keys** (#129's first
non-detection consumer), which broke the pre-existing
`describe_key_identifies_every_default_binding`: it asserted every
binding resolves through `describe.key` context-free, which held only
while the modes table was empty. It now sets the effective context
per binding and explicitly **clears** the mode for global ones,
because a leaked mode legitimately shadows a global chord of the same
name (dired's `RET` shadows `edit.newline-and-indent`), plus a floor
assertion that at least one mode-scoped binding exists.
- **A dedicated panel does not carry its dedication across a descent**
— the framing expected it to. `display_buffer` never replaces the
buffer in a slot dedicated to another one; it discards every
side-specific parameter and falls back to the document window (Q#BP3
2.iii), and the exact-window arm errors. Dired does not unpin the
user's panel; both arms are pinned.
- Smaller facts worth knowing before touching this code: a path-backed
buffer's **name is its full path**, not its basename, which matters
for any name assertion; `pmacs.buffer.kill` (not `remove`) redirects
windows off a doomed buffer first, so `dired.kill-when-opening` kills
**after** the replacement is displayed; ownership is checked against
the handle table only, never the buffer name; and `C-x d` takes **no**
completion source on purpose (with one, `RET` on an empty field opens
whatever sorts first, and RET-where-you-are is the gesture the binding
exists for — the field is prefilled instead).
- Verification at merge: 1,832 default + 2,009 CRDT library tests;
dired acceptance 25 + 25 CRDT; the frozen m8_1 10 / m8_2 15 / m8_3 32
unchanged, which is the additivity gate for the `read_dir` change; M4
121; required GPU 155; isolated-`XDG_CONFIG_HOME` workspace sweep
3,205 across 93 suites. 15 claims bite-verified.
- **GPU initial target LANDED — #148** - **GPU initial target LANDED — #148**
(`docs/gpu-initial-target-framing.md` rev 3; merge `0dd16a5`; two review (`docs/gpu-initial-target-framing.md` rev 3; merge `0dd16a5`; two review
rounds). `pmacs --gpu [--socket NAME|PATH] FILE` transports exact Unix path rounds). `pmacs --gpu [--socket NAME|PATH] FILE` transports exact Unix path
@ -760,6 +831,24 @@ final variant — its own round-trip cannot detect a discriminant shift.
trap-guarded one-file swap over read-only `git show`, with an trap-guarded one-file swap over read-only `git show`, with an
inverted verdict (exit 0 iff the tests FAIL against the old inverted verdict (exit 0 iff the tests FAIL against the old
version), making bite-verification machine-checkable. version), making bite-verification machine-checkable.
- **A fix must be COMMITTED before it is bitten.** `scripts/bite`
restores by `git checkout --`, which reverts the file to **HEAD**, not
to the state it found — so any uncommitted work in a bitten file is
destroyed. A whole review round's fixes were wiped this way during
#165. Corollary for a NEW file: the swap-over-`git show` mode does not
apply at all, so its claims must be bitten by hand-editing, which makes
the commit-first rule load-bearing rather than hygienic.
- **A CONFLICTING PR silently runs no CI at all.** GitHub builds
`pull_request` workflow runs against the PR's **merge ref**, which it
does not create while the branch conflicts with its base. So pushes
land, the branch updates, no run is ever queued, and **nothing reports
the absence** — the checks list simply keeps showing the last
successful run, which reads as current. Three pushes to #165 produced
zero CI before the cause was found, and `gh pr checks` returns nothing
usable here. On any lane that lives through a moving `main`, check
`gh pr view <N> --json mergeable,mergeStateStatus,headRefOid` and
confirm a run exists **for the current head sha**, not merely that a
recent run was green.
- **Stacked PRs**: retarget the child to main BEFORE merging the - **Stacked PRs**: retarget the child to main BEFORE merging the
parent — GitHub auto-closes a PR whose base branch is deleted and parent — GitHub auto-closes a PR whose base branch is deleted and
cannot reopen it (#104 → re-opened as #105). cannot reopen it (#104 → re-opened as #105).

View File

@ -1,7 +1,9 @@
# Dired — framing # Dired — framing
**Revision 7 — 2026-07-25. Status: APPROVED; Stage 0 MERGED as #162; **Revision 7 — 2026-07-25. Status: APPROVED; Stage 0 MERGED as #162;
Stage 1 IN REVIEW as PR #165, review round 1 addressed.** Stage 1 MERGED as #165 (`main` @ `c8ec8f3`, one review round). Stage 2
(marks and operations) and Stage 3 (wdired) each still need their own
framing before implementation; the frozen fixture shrinks after Stage 3.**
Rev 1 passed a ground-truth review; rev 2 fixed round 1's seven findings; Rev 1 passed a ground-truth review; rev 2 fixed round 1's seven findings;
rev 3 fixed round 2's six and was approved; rev 4 recorded what Stage 0's rev 3 fixed round 2's six and was approved; rev 4 recorded what Stage 0's
implementation falsified in the approved text (§0); rev 5 adds the implementation falsified in the approved text (§0); rev 5 adds the