docs: record dired Stage 1 (PR #165) and what it falsified

docs/dired-framing.md rev 6: §0 gains the Stage 1 implementation notes
(S1-1..S1-9) -- the normalizer is exposed rather than mirrored (so B2 is
false by one small binding, in the direction Q#DR2 preferred); R2-3's
dedication claim is falsified by the display policy; acceptance 3c
cannot pin the descent routing and now says so; dired is the first
builtin to bind a mode-scoped key, which one pre-existing lib test
assumed impossible; `C-x d` takes no completion source on purpose;
ownership is the handle table alone; the mark column ships blank; a
symlinked directory needs a probe; and interactive origin does not
survive an await.

COHERENCE.md, per its §25 (an audited claim this PR changes updates
here, riding the PR): §1.1's interactive-file-opening fact, §2's journey
step 7, §4's beginner-level `files`, §14's tree bullet (Stage 1 landed a
flat listing and did NOT invent a tree convention), and §15's Priority 1
list. Step 3 stays **Missing at the CLI** with the mechanism spelled
out: `pmacs .` still exits 1, and this arc deliberately does not claim
the CLI path -- it supplies the buffer a directory should resolve to.

docs/active-work.md: the dired lane rewritten for Stage 1, including why
the branch is a fresh cut rather than a rebase of `dired`, the durable
substrate facts, the bite results (one VACUOUS, recorded rather than
relabelled), and the verification. Its canonical-base line was four
merges stale and now names 8c86d34.

docs/agent-handoff.md: one forward pointer only. The handoff describes
merged state, so it absorbs the substance when this merges.
This commit is contained in:
Levi Neuwirth 2026-07-25 15:08:24 -04:00
parent e7fa9e9720
commit 8b685dc127
4 changed files with 223 additions and 52 deletions

View File

@ -119,10 +119,10 @@ detailed in §1.1§1.3: **substrate without surface**, **the silence
asymmetry**, and **per-arc coherence debt**.
Coherence-shaped work already in flight at audit time: find-file /
dired Stage 0 (`C-x C-f`, PR #162, `docs/dired-framing.md`), bottom
panel Stage 1 (merged #155), multi-root LSP affinity (branch
`lsp-multi-root-affinity`), the config registry foundation (merged
#127).
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),
multi-root LSP affinity (branch `lsp-multi-root-affinity`), the config
registry foundation (merged #127).
---
@ -192,9 +192,13 @@ working, unreachable capability:
is Lua-bound; no builtin command opens `*lsp*` (§2, §9).
- **Interactive file opening.** `pmacs.buffer.find_or_open`
(`src/lua_bindings/mod.rs:3103`) had no interactive caller at audit
time; a complete 1,384-line dired exists as a frozen test fixture
(`tests/fixtures/pmacs-dired/init.lua`). Being fixed now: dired Stage
0 (PR #162).
time; a complete 1,384-line dired existed only as a frozen test
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
browsing view as a builtin (`C-x d` / `C-x C-j`, PR #165). The fixture
stays frozen — its `install_local` + `require` routing *is* the M8
package-universality proof (Q#DR1) — and shrinking it is scheduled
after Stage 3.
The strategic consequence: **most coherence gaps in pmacs are doors,
not engines** — deliberately deferred surface, not design error. That is
@ -331,11 +335,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 |
| 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** | `pmacs .` exits 1 (above). No directory handling anywhere |
| 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 |
| 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 |
| 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: missing → in flight (PR #162). Symbol: works but undiscoverable** | No find-file/dired/picker existed at audit; `M-.`/`M-?`/`C-c o` 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 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 |
| 8 | Open terminal | **Works but undiscoverable** | Full PTY with scrollback + modeline segment — reachable only as `M-x terminal`, no keybinding |
| 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 |
@ -428,7 +432,8 @@ level is the one missing. Audited level-by-level:
**Beginner** (should see: files, buffers, search, diagnostics, terminal,
build actions, menus, missing-tool guidance):
- files ✗ (no find-file at audit; PR #162 in flight) · buffers ✓ (`C-x
- files ✓ since #162 / PR #165 (`C-x C-f` opens a path, `C-x d` browses;
neither is advertised anywhere but the keymap) · buffers ✓ (`C-x
b`, `*buffer-list*`) · search ✓ (`C-s`/`C-r`/`C-M-s`; project.search
is M-x-only) · diagnostics ✓ once a server runs · terminal ✓ but
M-x-only · build ✓ but M-x-only with empty prompt · menus △
@ -1139,7 +1144,11 @@ Primitive-by-primitive against the list above:
hierarchy, package dependency graph, worker trees, git status) will
each need it; building it once *before* dired's directory view and
the workers tree harden their own conventions is exactly this
section's point.
section's point. Dired Stage 1 (PR #165) landed **without** inventing
one: its listing is flat (Emacs parity), and the recursive
in-buffer case — `i` insert-subdirectory — is a named deferral in
`docs/dired-framing.md` §13, which is where a shared tree primitive
would land.
- **Structured table / inspector / diff view** ✗ — none. (`describe.*`
tables are the inspector's data model without a view; the
wire-declared `ResourceOffer` family was reserved for diff/blame
@ -1346,8 +1355,10 @@ missing runtime entity — a real arc).
Establish the end-to-end workflow; treat regressions as release
blockers. **State: broken at step 3 (§2). Mostly wiring, and unusually
cheap:** directory-argument handling; a find-file surface (in flight,
PR #162); surfacing the LSP spawn failure with guidance (§1.2); a
cheap:** directory-argument handling (the remaining half of step 3 —
dired Stage 1 landed the buffer it should resolve to); a find-file
surface (**done**: #162 open-by-path, PR #165 browsing); surfacing the
LSP spawn failure with guidance (§1.2); a
compile keybinding + `cargo build`/`test` default from the existing
`ProjectKind::Cargo`; a terminal keybinding; a welcome buffer. The
journey acceptance suite (§19) is the ratchet that keeps it fixed.

View File

@ -14,10 +14,11 @@ backlog.
machine-local: `origin` may name this canonical URL, a release mirror,
or something else, and therefore has no authority by name alone.
- Canonical base at this snapshot:
`githubsucks/main` @ `0dd16a5` (GPU initial-target #148 atop folding Stage 2
landed-doc refresh #150, folding Stage 2 #149, the ledger refresh #147, web
grammars HTML+CSS #146, and the LaTeX Stage 1 #144 / inline-math framing
#145 pair; protocol v20).
`githubsucks/main` @ `8c86d34` (the dired framing #164 atop find-file
#162, COHERENCE.md #163, Lean 4 Stage 1 #160, 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 GPU initial-target doc refresh #152; protocol
v20).
- On the transfer source, `origin/main` named a release mirror at
`d3fa632` and lagged badly. On the current destination, `origin` names
the canonical URL. This difference is why all recovery begins by
@ -51,7 +52,7 @@ git worktree list
git status --short --branch
```
The `git log` command must expose `0dd16a5` or a newer intentional main.
The `git log` command must expose `8c86d34` or a newer intentional main.
If it does not, stop and repair the remote/fetch configuration.
## Lean 4 lane (Arc 8) — Stage 1 IN REVIEW (PR #160)
@ -124,44 +125,108 @@ If it does not, stop and repair the remote/fetch configuration.
or markerless scratch files fragment into one server per directory for
every language.
## Dired lane — framing APPROVED; Stage 0 MERGED, Stage 1 next
## Dired lane — Stage 0 MERGED; Stage 1 IN REVIEW (PR #165)
- Approved framing: `docs/dired-framing.md` (revision 5), landing as its
own docs PR off `githubsucks/main` @ `2af1ab3`, branch
`githubsucks/dired-framing`, worktree `../pmacs-dired-framing`. The
repo's `-framing`-branch convention (`vterm-framing`,
`gpu-initial-target-framing`, `tab-width-parity-framing`).
- 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 (the dired view) is next and unstarted.** Branch `dired`
(worktree `../pmacs-dired-arc`) carries the framing commits only and is
based on the now-superseded `0827dd1`; **rebase it onto the `main`
resulting from the framing PR before implementing**, or cut a fresh
branch — its framing commits become redundant once the docs PR lands.
- Stage 1's scope, from the framing §10: `builtin/runtime/dired.lua`; the
`dired` major mode + mode keymap; buffer-per-directory with lexical
canonicalization and the ownership check; read-only intercept +
`set_round_trip_input`; visit routing through `window.display_file`;
parent/sort/revert/quit; `C-x d` (with the `display` opt) / `C-x C-j`;
cursor preservation by basename; the `dired.kill-when-opening` config
key; **and the tolerant `read_dir` opt** — the only Rust in the stage.
- The one Rust change is load-bearing and is why Stage 1 is not
pure-Lua: `read_dir_blocking` (`src/fs.rs:201`) fails the **entire
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** — the primitive returns one error and no partial vec.
- Coherence (framing §0.5, required since #163): serves `COHERENCE.md`
§20 Priority 1, which names this work explicitly; journey steps 7 and
(partially) 3; **adds no interaction island** — keys are a mode-scoped
keymap, and wdired is 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.
- **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.
- Verification on this branch: `cargo fmt --check` clean; strict workspace
Clippy clean; 1,829 default + 2,006 CRDT library tests; dired acceptance
22 default + 22 CRDT; m8_1 10 / m8_2 15 / m8_3 32 unchanged; M4 121;
required GPU 155; **isolated-`XDG_CONFIG_HOME` workspace sweep 3,186
passed across 92 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. 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.
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.
## Bottom-panel lane (window placement + side windows) — Stage 1 IN REVIEW

View File

@ -70,6 +70,11 @@ commands, read `docs/active-work.md` immediately after this file.
against an open buffer yet fails to load one that is not open —
find-file expands the tilde Lua-side. Loading through the normalized
path is a named deferral.
- **Stage 1 (the directory view) is IN REVIEW as PR #165** — the
builtin `dired.lua`, the per-entry-tolerant `read_dir` opt, and
`pmacs.path.canonicalize`. Its branch state, substrate facts, and
verification live in `docs/active-work.md`; this section absorbs them
when it merges.
- **GPU initial target LANDED — #148**
(`docs/gpu-initial-target-framing.md` rev 3; merge `0dd16a5`; two review
rounds). `pmacs --gpu [--socket NAME|PATH] FILE` transports exact Unix path

View File

@ -1,11 +1,13 @@
# Dired — framing
**Revision 5 — 2026-07-25. Status: APPROVED; Stage 0 MERGED as #162.**
**Revision 6 — 2026-07-25. Status: APPROVED; Stage 0 MERGED as #162;
Stage 1 IN REVIEW as PR #165.**
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
implementation falsified in the approved text (§0); rev 5 adds the
**coherence impact** statement now required of every framing
(`CLAUDE.md`, `COHERENCE.md` §20) — see §0.5. Deliberately
(`CLAUDE.md`, `COHERENCE.md` §20) — see §0.5; rev 6 records what Stage
1's implementation falsified (§0, S1-1…S1-9). Deliberately
unnumbered: the roadmap's Arc 8 is GPU
structural parity but `docs/lean4-mode-framing.md` also claims Arc 8, so
the arc space is already forked in uncommitted work. (Rev 2 also cited
@ -203,6 +205,94 @@ the correction belongs here rather than only in the code.
same normalize-before-lookup family as Q#DR5's `apply_resource_op`
correction.
### Stage 1 implementation notes (rev 5 → rev 6)
Implementing Stage 1 (PR #165) falsified four things the approved text
asserted and settled five it left open. Recorded here rather than only
in the code, per the rev-4 precedent.
- **S1-1. The normalizer is EXPOSED, not mirrored — so B2 is partly
false, in the direction Q#DR2 preferred.** Q#DR2 made the mirror
conditional (`Stage 1 may still mirror if exposure turns out to drag
in EditorCore borrow plumbing it does not otherwise need`).
`normalize_buffer_path` is a **free function** (`editor_core.rs`), so
exposure drags in nothing: it is now `pub` and reachable as
`pmacs.path.canonicalize`. Consequences, all deliberate: B2 ("tolerant
`read_dir` is the only Rust change Stage 1 needs") is false by one
small binding; acceptance 3b degenerates to the round-trip form the
framing described; and the Stage 2 mirror-removal follow-up **is not
owed** — there is no second canonical form to remove. The parity
acceptance is still carried, now as "the Lua binding and the Rust
function agree over one shared edge list", which is exactly the claim
a future re-mirroring would break.
- **S1-2. R2-3's dedication claim is falsified by the substrate.** It
read "a dedicated dired panel stays dedicated across descent and the
new dired buffer inherits it". `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 outright. Dired therefore does
**not** try to unpin the user's panel — which is also what Emacs's
`display-buffer` does with a dedicated window. Acceptance 3c is split:
a non-dedicated panel keeps the descent, and a dedicated one keeps its
buffer *and* its pin while the new directory appears in the document
window.
- **S1-3. Acceptance 3c cannot pin the descent ROUTING, and the test now
says so.** Dired holds the focus in its own panel, so a raw
`switch_buffer` lands in that same window and every 3c assertion holds
either way — the mutation is *vacuous* against it. Dedication is the
only thing that distinguishes `display { side = … }` from the raw
switch, so the dedicated-panel test is the discriminating pin. Found
by running the bite rather than by reading the test; the vacuity is
documented at the assertion instead of being left to be believed.
- **S1-4. Dired is the first builtin to bind a mode-scoped key, and one
pre-existing lib test assumed none existed.**
`describe_key_identifies_every_default_binding` iterated *every*
binding in the stack and asserted `pmacs.describe.key` resolves it
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 a global one, because a mode left over from a previous
iteration legitimately shadows a global chord of the same name
(dired's `RET` shadows `edit.newline-and-indent`, which is the point
of the mode).
- **S1-5. `C-x d` deliberately takes NO completion source.** It is the
direct consequence of S0-1/S0-4: with a `files` source, RET on an
empty field opens whatever sorts first (the minibuffer selects
candidate 0 whenever the list is non-empty, and a selected candidate
shadows typed text), and RET-on-the-directory-you-are-in is exactly
the gesture `C-x d` exists for. The field is **prefilled** with the
current directory instead — Emacs's own shape here — and free text
always reaches `on_accept` because `CompletionSource::None` bypasses
candidate resolution entirely. Directory-name completion is what dired
itself replaces.
- **S1-6. Ownership is the handle table ALONE**, narrower than Q#DR2's
"present in dired's handle table, or `major_mode(buf) == "dired"`". A
foreign buffer that carries the mode *is* the case the check exists to
refuse, and a builtin's handle table cannot be lost the way a
reloadable package's can. Acceptance 4 sets the mode on the foreign
buffer to pin the stronger reading.
- **S1-7. The mark column ships in Stage 1, rendered blank.** Q#DR4 is a
Stage 2 decision, but reserving the two columns now means Stage 2 does
not move every offset and Stage 3's column-classifying intercept can
be written against constants that did not shift under it. The
constants are computed from the widths (the fixture hardcoded
`NAME_START = 39` and paid for it in every wdired test) and exported
as `pmacs.dired._layout` so acceptance cannot drift from them.
- **S1-8. A symlinked directory needs a probe, because kinds are
lstat-based.** Both `read_dir` and `stat` report a link as
`"symlink"`, so nothing in the entry says whether it points at a
directory. `RET` on a symlink therefore *tries* to list the target
(one extra syscall, on symlink lines only) and descends if that
succeeds, else visits it as a file. Q#DR10 specified only the
dir/file arms; this is the third.
- **S1-9. Interactive origin does not survive the await.** Every listing
is worker-dispatched, so the work after the first `:await()` resumes
inside `tick_async`, where `InteractiveCommandOrigin` is empty and
`pmacs.window.*` falls back to the **ambient** active frontend. Single
frontend: correct. Multi-frontend: a dired opened from peer B while A
is ambient would display for A. Not fixable from Lua (the display
surface takes no frontend argument) and named here rather than
discovered later.
## 0.5. Coherence impact (`COHERENCE.md` §20)
Required of every framing since #163. This arc was scouted and approved