Skip to content

fix(reseed): clamp stale folds that swallow an inserted sibling heading - #293

Merged
mtskf merged 3 commits into
mainfrom
fix/reseed-stale-fold-sibling-heading
Jul 29, 2026
Merged

fix(reseed): clamp stale folds that swallow an inserted sibling heading#293
mtskf merged 3 commits into
mainfrom
fix/reseed-stale-fold-sibling-heading

Conversation

@mtskf

@mtskf mtskf commented Jul 29, 2026

Copy link
Copy Markdown
Owner

Summary

A follow-up to PR #292's "preserve folds across external reseed" policy. That policy maps overlapping folds through an external change instead of dropping them — but the trade-off is that an external insert into a collapsed section can widen the mapped fold to swallow a newly-inserted sibling heading, hiding it until the user unfolds. This reconciles mapped folds against the real Markdown structure after a reseed so the new heading stays visible.

Changes

  • src/webview/cm/fold/index.ts: new reconcileReseedFolds(state) — walks every folded range and clamps any fold that (a) now extends past its line's current foldable() section end and (b) whose excess span conceals a heading boundary, back to the real foldable range (or unfolds it when the line is no longer foldable). Guarded on syntaxTreeAvailable (an incomplete parse frontier would clamp wrong → skip, preserving today's behaviour).
  • src/webview/editor.ts: applyDocument calls it after a content-changing reseed, dispatched display-only (no changes, no hostDocumentReseed label — it rewrites no bytes), so the round-trip stays byte-identical and edit-sync never fires.
  • The two-condition rule is precise: foldable()'s sectionEnd already excludes legit nested child headings, and requiring a concealed heading (not merely over-wide) leaves benign remaps untouched — which is why the existing fold-preservation tests (r1–r6) still pass verbatim.

Related

Test Plan

  • New non-vacuous test (r7) in test/webview/editor.test.ts: red without the fix, green with it — asserts an external reseed inserting # New into a collapsed # One clamps the fold so # New is not concealed, landing exactly on the current foldable() range.
  • pnpm compile clean
  • pnpm test:unit — all 4142 tests pass
  • pnpm lint clean on changed files

mtskf added 3 commits July 29, 2026 21:00
The minimal-span reseed maps overlapping folds through an external change
rather than dropping them (PR #292). The trade-off is that an external insert
INTO a collapsed section can widen the mapped fold to swallow a newly-inserted
sibling heading, hiding it until the user unfolds.

After a content-changing reseed, applyDocument now reconciles native folds:
reconcileReseedFolds clamps any fold that extends past its line's current
foldable() section end AND whose excess span conceals a heading boundary back
to the real foldable range (or unfolds it). foldable()'s sectionEnd already
excludes legit child headings, and requiring a concealed heading leaves benign
over-wide remaps untouched. Guarded on syntaxTreeAvailable; dispatched
display-only (no changes), so the round-trip stays byte-identical.
… pin child-heading clamp

reconcileReseedFolds only clamped a stale fold back to its canonical
range when the excess span concealed a heading boundary. A fold whose
own heading line lost its marker entirely (fresh foldable() === null)
had no canonical range to clamp to, but fell through the same
concealment gate and could survive indefinitely with nothing left to
show for it.

Release that fold instead, gated on the reseed's edit actually
touching the fold's own line (editedRange, threaded from
computeReseedChange's post-change span) so an unrelated edit elsewhere
in the document cannot spring open a fold that was never canonical to
begin with (e.g. a hand-applied fold over a non-foldable paragraph).

Also documents the reconcileReseedFolds contract accurately: it is
exercised indirectly via editor.ts's applyDocument, not imported
directly by any test.
@mtskf
mtskf merged commit 33cee7f into main Jul 29, 2026
1 check passed
@mtskf
mtskf deleted the fix/reseed-stale-fold-sibling-heading branch July 29, 2026 12:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant