Commit Graph

3 Commits

Author SHA1 Message Date
Levi Neuwirth fb14dc9ec3 test(isolation): migrate the corpus off the ambient roots
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
2026-07-31 18:48:45 -04:00
Levi Neuwirth 180343e6e3 fix(edit): PR #109 round 1 — shared search invalidation, daemon anchor clear, bounded indent scan
Finding 1: the empty-anchor optimistic residual was never GPU-only
(the TUI mirror tracks no selection state; its gate checks cursor
freshness/EOL only). The fix moves daemon-side: handle_remote_crdt_op
clears a selection whose anchor equals the pre-edit cursor (= empty)
before applying the source cursor update; nonempty selections stand.
Covers both frontends. The TUI gate's missing type-over check
(nonempty selection at EOL) is a named deferral.

Finding 2: Q#AI8 invalidation is one helper
(search_invalidate_for_edit) invoked from all four edit paths --
apply_active_edit, notify_buffer_edit, and now undo/redo, which
received precise Edits but invalidated nothing. rebuild_views_for is
named as a lower-frequency bypass (deferral).

Finding 3: acceptance matrix trued up -- added active-search
fail-closed + retype recovery, delete translation on both paths,
undo/redo staleness + origin tests; modal contexts narrowed to what
this suite pins (query-replace/menu/completion ride their own
suites).

Finding 4: indent extraction is a forward-chunked scan stopping at
the first non-whitespace byte -- Enter at the end of a giant
minified line no longer materializes the line. Functional pin at
64 KiB.

Both medium fixes are bite-verified (tests fail with the fix
disabled).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ATiKMwJ4864d82D39EvsU6
2026-07-10 15:46:36 -04:00
Levi Neuwirth 7b5365cfbf feat(edit): auto-indent on newline (Arc 2)
RET now runs edit.newline-and-indent (builtin/runtime/indent.lua):
one insert/replace of "\n" plus the current line's leading whitespace,
copied verbatim and clipped at the split point (Q#AI3). Region RET
stays a single Replace (CUA type-over, one undo step, one CRDT op);
the selection clears after every successful edit (Q#AI4). Fix-up is
snapshot-guarded against context-switching intercepts and repairs the
cursor by right-gravity translation through the effective edit
(Q#AI5). buffer.newline remains the plain-newline escape hatch.

GPU (Q#AI1/Q#AI6): plain Enter is no longer optimistic-eligible --
its classifier arm's premise (byte-identical to a self-insert) died
with the new binding. Enter round-trips like the TUI, which also
makes global and buffer-local RET rebindings (buffer-list visit)
reachable from the GPU frontend.

Substrate fixes that RET would otherwise ship on top of:

- Q#AI8 search staleness: notify_buffer_edit now marks matches stale
  and right-gravity-translates the live session origin, matching
  apply_active_edit; SearchStore::step and search_match_summary fail
  closed while stale (a live search un-sticks on the next pattern
  keystroke, since set() clears staleness).
- Q#AI9 empty selections: insert_char reports success and the
  no-region arm of insert_char_over_region clears a lingering anchor
  only on Ok -- ordinary typing no longer type-overs its own previous
  keystroke after S-Left at BOF, and a rejected insert mutates no
  state.

Acceptance: tests/auto_indent_acceptance.rs (20 dispatch-driven
cases), tests/auto_indent_crdt_acceptance.rs (pending optimistic
input then round-tripped Enter converges on the source replica),
flipped GPU classifier test, and lib tests for the store, core, and
dispatch seams.

Framing: docs/auto-indent-framing.md (five review rounds).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ATiKMwJ4864d82D39EvsU6
2026-07-10 12:11:05 -04:00