Merge pull request #169 from levineuwirth/docs-dired-stage1-landed

docs: record dired Stage 1 (#165) as landed
This commit is contained in:
Levi Neuwirth 2026-07-26 20:23:39 +00:00 committed by GitHub
commit 74301d1670
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 153 additions and 164 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** | Full PTY with scrollback + modeline segment, bound to `C-c t` and configurable through three registered settings (`terminal.default-profile`, `terminal.scrollback-rows`, `terminal.escape-key`) plus named `pmacs.terminal.profiles` (PR #173), and searchable through `M-x terminal.copy-mode` / `C-c C-t`, which materializes the retained scrollback into an ordinary read-only buffer (Stage 2). Named limitations: `C-c t` is unreachable from *inside* a terminal window, where `C-c` is consumed as the escape — `M-x terminal` still works there; and there is still **no close/kill command**, which is the remaining half of this step's discoverability gap. *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** | Full PTY with scrollback + modeline segment, bound to `C-c t` and configurable through three registered settings (`terminal.default-profile`, `terminal.scrollback-rows`, `terminal.escape-key`) plus named `pmacs.terminal.profiles` (PR #173), and searchable through `M-x terminal.copy-mode` / `C-c C-t`, which materializes the retained scrollback into an ordinary read-only buffer (Stage 2). Named limitations: `C-c t` is unreachable from *inside* a terminal window, where `C-c` is consumed as the escape — `M-x terminal` still works there; and there is still **no close/kill command**, which is the remaining half of this step's discoverability gap. *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 |
@ -467,7 +467,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
@ -1248,7 +1248,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
@ -1412,7 +1412,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
@ -1476,7 +1477,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
@ -1546,7 +1547,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,15 @@ 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. (The inline-math lane was
here too until #172 removed it — that is the update those two owe.)
## Repository authority ## Repository authority
- Canonical development URL: - Canonical development URL:
@ -24,6 +33,8 @@ backlog.
Stage 1 #160, and the minimap blank-slab fix #159; protocol v20). The Stage 1 #160, and the minimap blank-slab fix #159; protocol v20). The
previous snapshot named `d152120`; the recovery check below accepts it previous snapshot named `d152120`; the recovery check below accepts it
or anything newer. or anything newer.
**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
@ -57,7 +68,11 @@ git worktree list
git status --short --branch git status --short --branch
``` ```
The `git log` command must expose `d152120` or a newer intentional main. The `git log` command must expose `c93f9ee` — the base named above — or a
newer intentional main. Keep this threshold and the canonical-base line in
step: a recovery check that accepts an older commit than the base it
declares canonical will pass on a tree the rest of this file does not
describe.
If it does not, stop and repair the remote/fetch configuration. If it does not, stop and repair the remote/fetch configuration.
## PTY terminate diagnostic lane — IN REVIEW (PR #176) ## PTY terminate diagnostic lane — IN REVIEW (PR #176)
@ -316,154 +331,6 @@ If it does not, stop and repair the remote/fetch configuration.
--check` clean. --check` clean.
- Stage 4b (the input method) is NOT in this PR and not started. - Stage 4b (the input method) is NOT in this PR and not started.
## 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.
## The CRDT half of the test corpus is dark in CI — NEEDS A LANE ## The CRDT half of the test corpus is dark in CI — NEEDS A LANE
- **No branch, no framing yet.** Found while gating #166, then measured - **No branch, no framing yet.** Found while gating #166, then measured
@ -1016,6 +883,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 terminal input (the double terminal-layout sync) — MERGED as #166** - **GPU terminal input (the double terminal-layout sync) — MERGED as #166**
(`main` @ `b889873`, 2026-07-25, one review round, all twelve checks green (`main` @ `b889873`, 2026-07-25, one review round, all twelve checks green
after a macOS PTY-timing rerun). The dispatcher applied **both** after a macOS PTY-timing rerun). The dispatcher applied **both**

View File

@ -179,11 +179,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.
- Protocol **v20** (`SUPPORTED=[6..=20]`; v16 = `ThemeFacts`, v17 = - Protocol **v20** (`SUPPORTED=[6..=20]`; v16 = `ThemeFacts`, v17 =
`FontFacts`, v18 = `StatuslineSegments`, v19 = terminal frames/events, v20 = `FontFacts`, v18 = `StatuslineSegments`, v19 = terminal frames/events, v20 =
the GPU initial-target semantic bootstrap family). the GPU initial-target semantic bootstrap family).
@ -1014,6 +1081,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