The mechanical half, riding on the census in the previous commit.
* 342 in-process construction sites in 65 files now take
`new_with_roots` / `open_with_roots` with `iso::roots()`. The isolated
base is a pure function of `CARGO_TARGET_TMPDIR` — no counter, no
`OnceLock` — so two copies of the module in one binary agree instead of
racing, and the tree lives somewhere `cargo clean` owns rather than
leaking into `/tmp` once per run. It is shared deliberately:
materialization is content-gated and idempotent, so a per-test
directory would repeat it ~330 times per run for a byte-identical
result.
* `journey_acceptance` keeps the ambient `EditorState::open`, because
proving the production entry point has a caller is the whole of what
that ratchet is for. Rev 2's "isolated by the environment its binary is
launched with" was not a mechanism — cargo launches each test binary
with the caller's environment, and a binary cannot re-point its own
roots before its tests run. Each test is now a thin parent that
re-execs this binary for its own name with controlled roots, and the
child runs the body against production's call. Two pins guard it: the
child asserts all four roots resolve inside the controlled base, and
the suite asserts against its own source that it has not quietly taken
the seam. The parent also asserts the child ran `1 passed` — a stale
`--exact` filter would otherwise hollow the whole thing out silently.
* The shared spawners take all five storage variables.
`spawn_daemon_process_with_env` set `HOME` and `XDG_CONFIG_HOME` only;
`HOME` is a FALLBACK, so it isolates a root only while the matching
`XDG_*` is unset — the harness's apparent adequacy was a property of
one developer's environment. The PTY spawner backfills whichever of the
five its caller did not pin. The 10 direct `Command::new` daemon and
attach spawns get the same treatment.
Three suites had `mod common;` behind `#[cfg(feature = "crdt")]`;
`common::iso` is needed in every build, so those are ungated. Files that
already pull in `common` reach `iso` through a `use` rather than a second
`#[path]` declaration — loading one file as two modules is
`clippy::duplicate_mod`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lv428Fth9LRtffwJSsqH7T
Three of the five review findings land here; the other two are recorded
as named deferrals in the framing on the dired branch.
Finding 1: the command comment cited "acc4", a name from a draft scheme
that no test carries. It now names the real test, and the comment splits
the shadowing consequence into the two cases that actually exist -- a new
bare name that matches an entry (shadowed) versus one that matches
nothing (creates normally) -- each pointing at its test.
Finding 2: the everyday new-file flow had no test. Typing a bare name
that is not a subsequence of any entry is the path users hit first, and
the only route combining free text with a relative join; every existing
new-file test used a name containing a separator.
find_file_bare_new_name_creates_in_the_root covers it, asserting the
parent is the prompt's root so the join itself is pinned.
Finding 3: the failure arm was never exercised, and as the review noted,
deleting the pcall would have passed the whole suite. Accepting a
directory candidate reaches display_file, whose load fails because
File::open on a directory succeeds and the read returns EISDIR;
find_file_accepting_a_directory_reports_instead_of_raising pins that this
surfaces as the command's status message, leaves the active buffer alone,
and closes the prompt. Verified by manual revert: with the pcall replaced
by a direct call, that test and only that test fails. scripts/bite could
not isolate it, since the guard and its test have no separating commit.
Finding 4 is documented at the command rather than left implicit:
accepting on empty input opens the first-sorted candidate, because
fuzzy_score returns Some(0) for an empty needle and filter_and_sort
breaks the tie lexicographically, so dotfiles lead and a directory can
lead. M-x and switch-buffer share the mechanism, so it is inherited
rather than introduced, and it is listed in the framing beside the
accept-semantics change that would close it.
Dired arc Stage 0 (docs/dired-framing.md section 10, Q#DR11). Until now
pmacs had no discoverable way to open a file by path: no find-file
command and no C-x C-f binding, so a file entered a session only from
the CLI, an LSP jump, a project-search visit, or C-x C-r, whose prompt
does pass free text through but completes only over the recent list.
The command prompts with completion rooted at the active buffer's
directory, or the process cwd when the buffer has no backing path, and
opens the result through pmacs.window.display_file. A path that does not
exist yet creates a buffer bound to it with the "[new file]" status,
which is Emacs parity and comes from resolve_target_buffer rather than
anything added here. Nothing is written to disk until the user saves.
Two substrate facts shape the design and are documented at the command
rather than left to be rediscovered.
Completion is flat: the files source lists one directory and yields bare
basenames, and a custom function source could not do better, because
sources are called with no arguments and run synchronously outside any
coroutine, so a callback can neither see the input to re-root on nor
await a directory listing. Hierarchical completion is a named Rust
change in the framing.
A selected candidate shadows typed text: recompute_candidates selects
index 0 whenever the candidate list is non-empty, and
resolve_accepted_value returns the candidate over the typed contents. So
typed text reaches the accept handler exactly when the input filters
every candidate away, which for basename candidates under a subsequence
filter means when it contains a separator. That makes the deeper-path
case work verbatim and leaves one hole: a new bare name that is a
subsequence of an existing entry opens the existing file. The acceptance
pins that as a decision rather than an accident; closing it needs a Rust
change to accept semantics that Stage 0 deliberately does not make.
A leading tilde is expanded before the path reaches the core, because
get_or_load_buffer normalizes the path it stores but loads from the raw
one -- so an unexpanded tilde path deduplicates against an already-open
buffer yet fails to load a file that is not open yet.
The prompt field starts empty and names its root in the prompt string
instead: any prefill would contain a separator and silently disable
completion.
Acceptance is dispatch-driven throughout -- a real C-x C-f, real typing,
a real RET -- so a dead binding cannot pass vacuously and the Lua
lifecycle accept(), which bypasses the path interactive input takes, is
not used.