test(fold): address PR #142 review round 2 — pin the round-1 wiring

Round 2 correctly found the Finding-2/3 fixes were unpinned (reverting
them left the suite green). Both are now bite-verified:

- **Kill-path purge (Finding 2).** Replaced the direct
  `forget_buffer(id)` unit test with
  `killing_a_buffer_through_the_real_path_purges_its_fold_store`, which
  drives `pmacs.buffer.remove` — the production route through
  `after_buffer_removed` — and asserts the store is gone via the dead id
  (BufferIds never recycle). Mirrors config_registry's real-kill-path
  test. Bite-verified: reverting the `after_buffer_removed` fold branch
  turns it red.
- **close-all point move (Finding 3).** Added
  `close_all_command_moves_point_to_enclosing_head`, which invokes the
  `fold.close-all` command with the point inside the second of two
  top-level fns and asserts the cursor landed on that fn's head-line
  content end (and both folds exist). Bite-verified: reverting close_all's
  `maybe_move_point` loop turns it red.
- Ledger: `docs/active-work.md` folding lane now records PR #142 OPEN +
  the two landed review rounds (was "opens once the gate suite is green").

Correction to the round-1 gate report: the acceptance suite is **21**
tests (round 1 was 20, not 24 — a tally slip), green under default and
`--features crdt`. Full gate suite otherwise green (fmt, clippy
--workspace --all-targets, git diff --check).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q5BkezMppbpCgGAYk2ftxV
This commit is contained in:
Levi Neuwirth 2026-07-23 14:25:28 -04:00
parent 9691dd4e9f
commit 036a994639
2 changed files with 63 additions and 24 deletions

View File

@ -61,18 +61,21 @@ If it does not, stop and repair the remote/fetch configuration.
- Framing head: revision 5 of `docs/folding-framing.md` (rev 1 → … → rev 4 - Framing head: revision 5 of `docs/folding-framing.md` (rev 1 → … → rev 4
absorbed three review rounds; rev 5 records approval + the Q#FD4 binding absorbed three review rounds; rev 5 records approval + the Q#FD4 binding
decision). decision).
- State: **APPROVED; Stage 1 (fold engine, headless) implementing on this - State: **Stage 1 (fold engine, headless) implemented; PR #142 OPEN**,
branch.** Bindings decided (Q#FD4 → Emacs hideshow `C-c @` set); Bet B1 two review rounds landed on the branch. Bindings decided (Q#FD4 → Emacs
accepted as framed. hideshow `C-c @` set); Bet B1 accepted as framed.
Load-bearing decision (Q#FD1): the bundled grammars ship no fold query and Load-bearing decision (Q#FD1): the bundled grammars ship no fold query and
no `folds.scm`, so the roadmap's "tree-sitter fold ranges" is not free; v1 no `folds.scm`, so the roadmap's "tree-sitter fold ranges" is not free; v1
is structural node folding (block-like node ≥2 source lines, derived head is structural node folding (block-like node ≥2 source lines, derived head
line, closer-aware tail), with indentation fallback and curated queries line, closer-aware tail), with indentation fallback and curated queries
deferred. `FoldState` already exists in the protocol, declared-but-unproduced deferred. `FoldState` already exists in the protocol; Stage 1 starts
(a test pins it is never emitted); no frontend consumes it yet; gutter *producing* it (authoritative-empty), no protocol bump; gutter markers are
markers are frontend-derived like the diagnostic sign bars, so no new wire frontend-derived like the diagnostic sign bars, so no new wire type. Staged
type. Staged like vterm: Stage 1 engine (headless), Stage 2 TUI, Stage 3 GPU. like vterm: Stage 1 engine (headless), Stage 2 TUI, Stage 3 GPU.
- PR: Stage 1 opens as the first folding PR once the gate suite is green. - PR: **#142** (`Arc 6 folding — Stage 1: instance fold engine`), open
against `main`. Round 1 (tail-boundary delete bug + buffer-kill cleanup +
close-all point-move) and round 2 (pin the kill-path + close-all through
the real command surface) are landed as fix commits on the branch.
- Next: land Stage 1; Stages 2/3 are separate branches/PRs, each re-framed - Next: land Stage 1; Stages 2/3 are separate branches/PRs, each re-framed
in detail after the prior stage lands. in detail after the prior stage lands.

View File

@ -493,24 +493,60 @@ fn forget_drops_store_and_detaches_view() {
} }
#[test] #[test]
fn forget_buffer_drops_store_on_kill() { fn killing_a_buffer_through_the_real_path_purges_its_fold_store() {
// The id-keyed reset the pmacs.buffer.kill path uses: the buffer (and // Drive the production `pmacs.buffer.remove` route (not `forget_buffer`
// its attached view) is gone, so only the map entry is cleared. // directly), so the app-data stash + the `after_buffer_removed` branch
// are what get exercised — deleting either would leave this red. The
// assertion reads through the DEAD id on purpose: BufferIds are never
// reused, so a stale id cannot alias a later buffer. Mirrors
// config_registry's `killing_a_buffer_through_the_real_path_...`.
let s = EditorState::new();
exec(
&s,
"b = pmacs.buffer.from_bytes('kill.rs', 'aaa\\nbbb\\nccc\\nddd\\n')",
);
let id = {
let core = s.core.borrow();
let reg = core.registry.borrow();
reg.find_by_name("kill.rs").expect("buffer")
};
// Fold via the data API (no parse tree needed) so the store exists.
let folded: bool = eval(&s, "return pmacs.fold.fold(b, { start = 3, ['end'] = 11 })");
assert!(folded);
assert!(
s.fold_registry.store(id).is_some(),
"store exists before kill"
);
exec(&s, "pmacs.buffer.remove(b)");
assert!(
s.fold_registry.store(id).is_none(),
"the real kill path purges the fold store"
);
}
#[test]
fn close_all_command_moves_point_to_enclosing_head() {
// Q#FD3 through the command surface: `fold.close-all` is interactive,
// so when it collapses a top-level fold around the invoking point, the
// point moves to that fold's head line (Finding 3, round 1).
let s = EditorState::new(); let s = EditorState::new();
let id = active_id(&s); let id = active_id(&s);
insert_into(&s, id, "line0\nline1\nline2\n"); let src = "fn first() {\n a();\n b();\n}\nfn second() {\n c();\n d();\n}\n";
{ insert_into(&s, id, src);
let core = s.core.borrow(); install_rust_parse(&s, id);
let mut reg = core.registry.borrow_mut(); // Point inside the SECOND function's body.
s.fold_registry s.core.borrow_mut().set_cursor_byte(byte_of(src, "c()"));
.store_or_attach(reg.get_mut(id).unwrap())
.lock() exec(&s, "pmacs.command.invoke('fold.close-all')");
.unwrap()
.insert(ByteRange { start: 5, end: 11 }); let n: i64 = eval(&s, "return #pmacs.fold.folds(pmacs.window.buffer())");
} assert_eq!(n, 2, "both top-level functions are folded");
assert!(s.fold_registry.store(id).is_some()); assert_eq!(
s.fold_registry.forget_buffer(id); s.core.borrow().active_window().cursor,
assert!(s.fold_registry.store(id).is_none()); line_content_end_of(src, "fn second() {"),
"the invoking point moved to the enclosing fold's head line"
);
} }
#[test] #[test]