fix(toc): stop the floating outline from sitting on the text it covers - #545
Merged
Conversation
**A. The overlap test was asking about the wrong pane.** The outline is positioned against the layout container, so it lands on whichever pane holds that edge — and the editor is always the first child, so it takes the left edge whenever it is on screen. But `isOverhanging` always measured the VIEWER pane against the preview's centred content width. That is only the right question in reading mode: reading viewer alone → preview on both sides ✓ measured split editor | viewer → editor on the left ✗ measured the preview editing only viewer is `flex: 0` → editor on both sides ✗ viewerWidth is 0 In editing-only mode `viewerWidth > 0` is false, so the expression fell through to "no overlap" while the panel sat on top of the code with neither the shadow nor the border that exist to say so. The test now answers "is the preview underneath me at all" first, and only reaches for arithmetic when it is. Moved to `utils/tocOverlay.ts` with the six cases under test. **B. An outline that covers the text now gets out of the way.** This is what #176 is actually about. Unpinned does not mean auto-hiding, it means "do not reflow the text" — the panel is supposed to fall into the gutter beside the centred preview. With the defaults that gutter is only wide enough at 1360px of viewer width (240 > (W - 880) / 2), which is wider than most windows and twice as wide as split view can offer. So for most readers the floating mode has covered their text since the day it shipped, and there was no way to dismiss it except the toggle it came from. It now collapses when you pick an entry, and when you reach past it to touch what it was covering. Both paths are gated on the same predicate A repairs: pinned it is a sidebar, and one genuinely sitting in the margin harms nothing and keeps its old behaviour. **The highlight had to stop being owned by the outline.** A jump marks the heading in the preview and leaves the mark until the reader moves — but the listeners that clear it belonged to the outline, and a new instance starts with its own `activeTargetEl` and cannot see what an earlier one marked. Hiding the outline right after a jump therefore stranded the mark forever. That was already reachable by hand; B makes it routine. The clearing is handed to listeners that outlive the component, deferred past the jump's own smooth scroll for the reason `clickLock` exists. Refs #176
This was referenced Aug 8, 2026
Closed
PathGao
added a commit
that referenced
this pull request
Aug 8, 2026
…ople download (#558) **The syntax reference.** Nothing in 2.7.3 changed what renders, so the compatibility table is untouched. What changed is behaviour, and five of those are demonstrable on the page itself, so they belong in §17 and §15: the right-click Edit jump (#539), split-view sync by source line (#541), sticky scroll (#555), the unpinned outline getting out of the way (#545), and CJK word-wise navigation with the IME space no longer boxed (#546). The outline line is written from what f29928a actually does — it collapses when you pick an entry or reach past it — rather than from the shorter "steps aside" the commit subject suggests. Checked and left alone: Copy Reference still follows the document's own spelling (`preferredReferenceStyle`), heading completion and task checkboxes were already described correctly. Encoding, line endings, the clipboard and the load-race fixes are not syntax and are not on this page. **Making it findable.** It was one link in the nav and one clause in a Features bullet. It now has its own section between Download and Installation from source, with the raw URL first so the primary action is downloading the file rather than reading it on GitHub — the document is built to be opened in Markpad, and reading it here is the fallback. The section also suggests handing the file to an AI: it is a complete list of what renders, so an assistant can reformat an existing document or write a new one that uses the whole range. "Markdown support" is gone as a heading. In a README that word means the help desk; `## What Markpad renders` says what the section is, and does not collide with `## Features`, which is where "what Markpad can do" already lives. The release body gets the same section, with the links pinned to the tag. **The two platform notices.** The Windows SmartScreen note has been pasted into the release body by hand since it was written — the workflow never generated it. Both notices are generated now, so the macOS one cannot go missing the release someone forgets, and the Windows one must no longer be added by hand or it will appear twice. The macOS notice covers what #209 costs a new user: an unnotarized `.dmg` is refused on first launch with only Cancel and Move to Trash, so Control-click → Open comes first as the one-step way past it and the System Settings route second. It also names the consequence people report as a separate bug — unsigned means file access is granted per prompt, so the dialogs repeat, most visibly with auto-save on and in image-heavy documents — and links to the self-signing workaround in #209, noting it has to be redone after every update. Co-authored-by: PathGao <gaoyanbo@gaoyanbodeMacBook-Air.local> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this is
Closes #176, reported by @StrollStars: with the outline unpinned, showing it covers part of the text instead of sitting beside it. Their question was whether the unpinned outline was meant to float and retract on hover — "otherwise the unpinned mode has no point."
The mode does have a point, but their reading of the symptom was right, and the design it depends on has not been holding for most windows.
Two changes:
Mechanism
A — the overlap test measured a pane the outline was not on.
The outline is positioned against the layout container, so it lands on whichever pane owns that edge, and the editor is always the first child — it takes the left edge whenever it is on screen at all.
isOverhangingnevertheless always measured the viewer pane against the preview's centred content width:In editing-only mode
viewerWidth > 0is false, so the whole expression fell through to "no overlap" while the panel sat on top of the code — with neither the shadow nor the border that exist to say exactly that. Asking "is the preview underneath me at all" has to come first: the preview centres its content and leaves a gutter to fall into, the editor fills its pane edge to edge and has none to lend.B — the gutter the floating mode depends on is usually not there.
Unpinned does not mean auto-hiding; it means "do not reflow the text". The panel is supposed to land in the margin beside the centred preview. With the shipped defaults that margin is wide enough only from 1360px of viewer width:
That is wider than most windows, and split view halves the pane, so it would need a window around 2720px. For most readers the floating outline has been covering their text since it shipped, with no way to dismiss it except the toggle it came from. Hence #176.
It now collapses when you pick an entry — the panel exists to be called up, used once and dismissed — and when you press past it onto what it was covering. Both are gated on the predicate A repairs, so a pinned outline stays (it is a sidebar) and one genuinely sitting in the margin keeps its old behaviour.
The highlight had to stop belonging to the outline.
A jump marks the landing heading and holds the mark until the reader moves. The listeners that clear it belonged to
Toc, and a later instance starts with its ownactiveTargetEland cannot see what an earlier one marked — so hiding the outline right after a jump stranded the mark on the heading permanently. That was already reachable by hand (jump, then hide the outline); B would have made it routine. Clearing is now handed to listeners that outlive the component, deferred past the jump's own smooth scroll for the same reasonclickLockexists.Scope
Deliberately left alone:
pointerdowndismisses. Clicking into the editor is the normal way to start typing and already closes it; addingkeydownwould close the outline while someone is using find-as-you-type in another pane.opacity: .6 → 1) while the panel it opens does not react to the pointer at all. That inconsistency is very likely what led the reporter to expect a hover-driven panel.Tests
New
scripts/tocOverlay.test.tscovers the extracted predicate over all six placements, the 1360px boundary either side, the full-width case and the 50px floor, plus two source assertions that both auto-collapse paths stay gated on the same predicate.Reverting each half with the tests kept:
isTocOverPreviewearly return → red,covering the editor always counts, however wide the window isthe outline collapses itself only when it is in the wayscripts/previewWidth.test.tsasserted the gutter arithmetic by matching the inline expression inMarkdownViewer.svelte; it now follows it intotocOverlay.tsand additionally pins that the viewer still feeds it the same preference.Verification
Also built and ran a debug bundle on macOS (aarch64) and exercised it by hand in a window narrow enough to overhang: collapse-on-jump, collapse-on-click-outside, the pinned outline staying put, and the landing highlight surviving the jump's own scroll and then clearing on the next reader action.
What I did not verify: nothing was built or run on Windows or Linux. The changed code is layout and DOM-event logic with no platform branches, so the risk is low, but "low" is a judgement and not a test run. The stranded-highlight handover is timing-dependent and was checked by hand on one machine at one scroll speed, not automated — it is the part of this PR most worth a second pair of eyes.