Skip to content

fix(find): reveal collapsed matches, and keep them out of the PDF - #377

Merged
PathGao merged 1 commit into
sftwrdotdev:masterfrom
PathGao:fix/find-and-print-leftovers
Aug 2, 2026
Merged

fix(find): reveal collapsed matches, and keep them out of the PDF#377
PathGao merged 1 commit into
sftwrdotdev:masterfrom
PathGao:fix/find-and-print-leftovers

Conversation

@PathGao

@PathGao PathGao commented Aug 2, 2026

Copy link
Copy Markdown
Collaborator

Find counted matches nobody could reach

The tree walker skipped only SCRIPT, STYLE, NOSCRIPT and its own marks. Text inside a collapsed heading section (.foldable-content-wrapper.is-collapsed, height: 0) or a collapsed callout (.markdown-alert-content.is-collapsed, grid-template-rows: 0fr) stays in the DOM, so it was counted — but both clip to zero height, so pressing Enter onto one of those matches scrolled nowhere. The count said 7, three of them were unreachable, and nothing said why.

Landing on a match now reveals its folds.

Why reveal rather than skip

Both options restore the invariant "a counted match is reachable", so the reference frame decided it:

  • Chromedisplay:none and hidden content is not findable, but hidden=until-found and closed <details> are findable and auto-revealed. The beforematch event exists precisely so a page can sync its own state before the browser reveals.
  • VS Code — finds inside folded regions and unfolds when navigating to a match (#59776 asks for re-folding afterwards, which confirms it unfolds).
  • Obsidian — "counted but never revealed" is filed as a bug (forum 41975), with the expected behaviour stated as unfolding.

Markpad's behaviour matched none of them. Skipping folded text would be internally consistent, but it makes folding silently remove text from search with no signal — a usability cliff nothing else has.

Why it goes through the fold toggle

collapsedHeaders in the viewer is the source of truth: processMarkdownHtml re-applies is-collapsed from it on every render, and the same Set feeds the table of contents. Stripping the class from the find bar would create a second source of truth that the next render silently reverts, with the ToC disagreeing in the meantime. Dispatching the toggle's own click routes through the existing handler so the DOM and that Set stay in step — and MarkdownViewer.svelte needed no change.

Find highlights were printed into exported PDFs

Nothing cleared them before printing, and .markdown-body sets print-color-adjust: exact, so the orange blocks came through.

They cannot simply join the print block's display: none list — that mark wraps document text, so hiding it would delete the matched words from the printed page. Its background, shadow, colour, radius and padding are neutralised instead, and the text survives. Highlights authored with ==…== are untouched. !important is required because the find bar's :global() rule has equal specificity and the order between a component stylesheet and styles.css is not guaranteed; a test locks the find bar's own rules !important-free so the two cannot start fighting.

Images and diagrams taller than a page were clipped

break-inside: avoid was set with no height cap. A replaced element cannot fragment, so anything taller than the printable area simply lost its bottom rather than scaling or moving to the next page.

Capped at 9in with object-fit: contain@page { margin: 0 } plus the body's 0.75in padding leaves 9.5in printable on Letter and 10.19in on A4, so 9in fits both. Absolute units deliberately, not vh: page-box resolution differs across the three platform webviews. Mermaid containers also need overflow: visible in print, because their on-screen overflow-x: auto computes overflow-y to auto and would re-crop what the cap just fixed.

Not conflicting with #359

Checked before editing: #359's mermaid work is a light-theme re-render in utils/mermaidPrint.ts, carrying deliberately zero mermaid print CSS — so a size cap is purely additive. Only new rules were appended; no existing declaration changed, and the paper layout, table word-breaking and alert/tr break rules are byte-identical. The new tests additionally re-assert #359's doesNotMatch(/\.mermaid-diagram svg[^{]*\{[^}]*fill:/) and add a stroke: counterpart, so a future size rule cannot smuggle paint rules back in.

Validation

Baseline counter-proof: 12 of 16 failed on unmodified source; the 4 that passed are deliberate guards (the find bar is already print-hidden, find-match is not in the display:none list, #359's rules intact). After tightening the print-block extraction to brace-match the real @media print block rather than slicing to EOF, the two print files were re-run on still-unmodified source: 9 of 12 failed. 18/18 after the fixes. Every property lookup uses a (?<![\w-]) lookbehind so box-shadow cannot be satisfied by -webkit-box-shadow.

Adjacent gap, deliberately left

A document containing raw <details> (comrak runs unsafe_ = true) renders a real disclosure whose closed children are display: none but still in the DOM — the same "counted, unreachable" symptom, and Chrome auto-opens <details> on find. Closing it is roughly three lines, but it brings a <summary>-match edge case and was outside this scope, so it is flagged rather than folded in.

**Find counted matches nobody could reach.** The tree walker skipped only
script, style, noscript and its own marks, so text inside a collapsed
heading section or a collapsed callout stayed in the DOM and stayed
countable — but both containers clip to zero height, so pressing Enter onto
one of those matches scrolled nowhere and looked like the search was broken.

Matching a match now reveals its folds. That is what the surrounding
software does: Chrome reveals `hidden=until-found` content and closed
`<details>` when find lands inside them, VS Code unfolds a region when
navigating to a match, and Obsidian tracks "counted but never revealed" as a
bug. Simply skipping folded text would have been the other consistent
answer, but it makes folding silently remove text from search with no
signal, which nothing does.

The reveal goes through the viewer's own fold toggle rather than stripping
the class. `collapsedHeaders` is the source of truth and is re-applied on
every render, so a class removed here would come back on the next one, and
the table of contents would disagree in the meantime. Dispatching the
toggle's own click keeps the DOM and that Set in step.

**Find highlights were printed into exported PDFs.** Nothing cleared them
before printing and `print-color-adjust: exact` made sure they came through.
They cannot go in the print block's `display: none` list — the mark wraps
document text, so hiding it would delete the matched words from the page —
so its background, shadow and colour are neutralised instead and the text
survives. Highlights authored with `==…==` are untouched.

**Images and diagrams taller than a page were clipped rather than scaled.**
`break-inside: avoid` was set with no height cap, and a replaced element
cannot fragment, so anything taller than the printable area simply lost its
bottom. They now cap at 9in with `object-fit: contain`, which fits both
Letter (9.5in printable) and A4 (10.19in). Mermaid containers also need
`overflow: visible` in print, because their on-screen `overflow-x: auto`
computes `overflow-y` to `auto` and would re-crop what the cap just fixed.

The size cap is purely additive to #359, whose mermaid work is a light-theme
re-render carrying deliberately zero mermaid print CSS. No existing
declaration in the print block was changed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@PathGao
PathGao merged commit 1fd2e40 into sftwrdotdev:master Aug 2, 2026
4 checks passed
@PathGao
PathGao deleted the fix/find-and-print-leftovers branch August 2, 2026 19:57
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