Skip to content

Commit 02283fd

Browse files
test(block-editing): fail-on-revert hardening for §0/§7 seams + e2e audit
Tighten the §7 expand-on-edit bindings (leave-key no-expand §7.c on editExpandedRef; grow-above-contentHeight + floor §7.d; isolate remount-preserve from keyboard-open §7.h) and the §0 0.c-wiring Range-excludes-sublist correctness, replacing DOM-teardown theater with real bindings. Records the e2e fail-on-revert audit results in the plan. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent e1f0df5 commit 02283fd

3 files changed

Lines changed: 332 additions & 156 deletions

File tree

claude-notes/plans/2026-06-15-nesting-cursor-navigation-and-list-items.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,35 @@ All eight sections (§0–§7) are implemented and green at the jsdom/Rust tier:
129129
assertion contradicting the §2 commit-and-reland design) and **filed as a braid bug** (`-l block-editing`,
130130
needs a product decision). Left RED on purpose; not in scope for this plan.
131131

132+
### Fail-on-revert audit (Mode B, adversarial — `audit/fail-on-revert` worktree, 2026-06-17)
133+
134+
Cold, no-trust re-derivation: revert *only* the production hunk each test should protect, rebuild,
135+
confirm RED, restore, confirm GREEN.
136+
137+
**jsdom/Rust tier** — found **4 theater/weak bindings**, each fixed in its own commit on
138+
`audit/fail-on-revert` (revert-proven): **§7.c** was total theater (asserted `data-expanded` on a
139+
textarea that unmounts in the same React batch → now asserts the synchronously-set `editExpandedRef.current`);
140+
**§7.d** was theater on the grow path (jsdom `scrollHeight=0` → now stubs scrollHeight both directions);
141+
**§7.h** conflated remount-preserve with keyboard-open (→ pointer-open + expand-by-typing); **§0 0.c-wiring**
142+
bound "a Range was used" but not "excludes the sublist" (→ boundary-sensitive `intersectsNode` stub).
143+
§4 p3-3, §2 roving LI/DD, and Rust 6.b were spot-checked and confirmed solidly bound.
144+
145+
**e2e tier** — spot-checked the **two most vacuity-prone** specs (the geometry/tolerance tests; the
146+
discrete-assertion specs `item-edit-size`/`delete-by-emptying` read as bound). Both went RED on revert
147+
**no theater**:
148+
- **`breadcrumb-geometry` 4b** (revert hunk 3a: host→`surface.offsetParent` + drop `#quarto-content{position:relative}`):
149+
RED — chip froze at y=336.70 while the surface scrolled −400px (gap delta −400 ≫ 3px tol). The author's
150+
documented vacuity trap (a no-op `#root` scroll) is confirmed *absent* — the logs show the surface really
151+
moved. Clean, decisive bind.
152+
- **`nesting-size-in`** (revert §1: force `snap=undefined` → pre-§1 stale-box fallback): RED, but at the
153+
nest-**OUT** checkpoint (line 127), *before* the headline nest-**IN** assertion (line 142) — §1's snapshot
154+
map serves both directions, so the fallback breaks nest-out sizing first. Verdict: genuinely bound to §1
155+
(red, not green), but this revert proves *snapshot-consumption is wired*, not that the nest-in assertion
156+
isolates; no clean one-line hunk breaks only nest-in (shared map). Provenance note, not a fix.
157+
158+
All reverts restored; `audit/fail-on-revert` tree clean. Conclusion: real-browser distant-state e2e tests
159+
held up at their two weakest points; only the jsdom tier needed repairs.
160+
132161
### Remaining (deferred e2e — tracked, not blocking)
133162
- **0.e** (text-with-sublist nest-in), **1.d** (unlock arrow into sublist + caret col + doc-end no-op),
134163
**6.h** (bullet delete leaves empty `<li>`), **7.f** (roving-Enter opens expanded) — not yet written.

ts-packages/preview-renderer/src/q2-preview/s0-list-item-surfaces.integration.test.tsx

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -436,9 +436,17 @@ describe('0.c — text-with-sublist item: Range measure excludes sublist height'
436436
vi.spyOn(li!, 'getBoundingClientRect').mockReturnValue(rect(0, 0, 200, 60));
437437
vi.spyOn(innerUl!, 'getBoundingClientRect').mockReturnValue(rect(0, 20, 200, 60));
438438

439-
// Stub Range.prototype.getBoundingClientRect to return only the leading text
440-
// height (20px). Production measureBlockBox should use this for the <li>.
441-
(Range.prototype as any).getBoundingClientRect = () => rect(0, 0, 200, 20);
439+
// Stub Range.prototype.getBoundingClientRect: boundary-sensitive.
440+
// When the Range includes the sublist (setEndAfter — wrong), returns 60px.
441+
// When the Range excludes the sublist (setEndBefore — correct), returns 20px.
442+
// This means setEndBefore→setEndAfter REDs the test; the constant stub would not.
443+
const sublistEl = innerUl!;
444+
(Range.prototype as any).getBoundingClientRect = function(this: Range) {
445+
const includesSublist = typeof this.intersectsNode === 'function'
446+
? this.intersectsNode(sublistEl)
447+
: false;
448+
return includesSublist ? rect(0, 0, 200, 60) : rect(0, 0, 200, 20);
449+
};
442450

443451
try {
444452
// topBlockR0 = pool[2].r[0] = 0 (the outer BulletList).

0 commit comments

Comments
 (0)