Skip to content

feat(files): an opt-in outline beside rendered Markdown previews - #3065

Merged
devswha merged 9 commits into
mainfrom
feat/files-markdown-outline
Nov 5, 2026
Merged

devswha merged 9 commits into
mainfrom
feat/files-markdown-outline

Conversation

@devswha

@devswha devswha commented Nov 5, 2026 •

Copy link
Copy Markdown
Owner

Fixes #636.

What

A new opt-in setting, Settings > File viewer > Markdown outline (off by default, a device setting in its own storage key like the other recent opt-ins). With it on, a Markdown Preview that has two headings or more lists them:

  • Levels and repeated titles. The outline lists the document's own headings (not ones inside a quote), each level under the shallowest one stepped in. A title written twice is two entries, and each goes to its own heading.
  • Follows the reading. As the document scrolls, the heading being read (the last one past a line 32 px under the body's top) is marked with the accent on a tint, and kept in the middle of the list. Only the list scrolls to do that, so the browser clamps it at the list's start and end, and the document's position and the keyboard focus never move with it. At the very end of the document, the last heading in view is marked (short final sections cannot reach the reading line).
  • Choosing an entry brings its heading to 8 px under the top. A chosen entry stays marked while the document rests where the choice left it, even when the end of the document keeps the heading from reaching the top.
  • Wide viewer (body at least 760 px: the popup and the reader tab on a desktop): a 240 px column after the document. The header's new Outline button folds and unfolds it.
  • Narrow viewer (a phone, the docked panel): nothing changes until the Outline button (header; the toolbar when docked) is pressed. It opens the outline as a drawer over the document's end edge, so the text keeps its whole width. Opening the drawer centers the heading being read and puts the focus on it. A choice, Escape (which closes the drawer only, not the viewer) or a press outside closes it, and the focus returns to the button.

With the setting off (the default), the viewer is unchanged: the body's new wrapper is display: contents, so it lays nothing out. The wrapper is always rendered so the body is never remounted when an outline appears, which keeps its focus and scroll.

Design reference: Zed's outline panel (the current symbol is marked and kept in view while the editor scrolls) for the column, and GitHub's Markdown file outline button for the narrow drawer. There is a DESIGN.md entry.

Compared with #651 (closed as a duplicate before the Markdown preview landed): this builds on the preview that is on main now, adds only the outline, and keeps it opt-in.

Tests

  • scripts/markdown-outline-browser-qa.ts (new, added to scripts/ci-browser.sh): the real viewer built by Vite and served under the app's CSP, light and dark.
    • Off: no outline, no button, and the wrapper computes to display: contents.
    • 1280 popup: a column whose width plus the body's equals the row; 61 entries for 61 headings; depth classes for #/##/###; the first entry marked at the start with the list at scrollTop 0; scrolling to Section 16 marks it within 2 px of the list's center while the body's scrollTop and document.activeElement stay unchanged; at the end the list is at its own end; the third "Notes" entry lands the third Notes heading at 8 px; choosing Section 30 near the end keeps it marked; the button folds and restores the column.
    • 390 phone: no column until the button; the drawer does not change the body's width; opening centers and focuses Section 21; Escape closes only the drawer and returns focus to the button; a choice scrolls to the heading, closes, and returns focus; a tap on the document closes it.
    • Narrow, after review: a dialog in front of the viewer (Settings) keeps its Escape and focus while the drawer stays open; a touch stroke down the drawer at its top does not drag the viewer's sheet.
    • Docked at 1280: the toolbar's Outline button opens a drawer over the panel, and the pane beside it keeps its Escape and focus.
    • Failing first: with src/ at origin/main the script fails at its first step (no wrapper, no outline).
  • src/lib/markdownOutline.test.ts: the entry list (levels, words with formatting dropped, quoted headings left out, repeated titles as separate places, wordless headings skipped) and the current-heading rule.
  • src/lib/settings.test.ts: the default, sanitising and device scope, and the setting's own key surviving an old tab's whole-record save.

Screenshots

Fixture data (no user session), 1280x800 and 390x844, dark and light; checked by eye: the column and the drawer, the marked entry centered, the indent per level, and the Outline button's on state.

Validation

  • bun run check fast: pass on the merged head 57863e2 (workflow syntax, generated types/themes, typecheck, build, 12364 unit tests, 0 failures).
  • bun scripts/markdown-outline-browser-qa.ts: pass, also on the merged head (with files-prompt-regression, whose fix(dialogs): Escape closes the dialog in front when two are open #3053 Escape steps landed meanwhile).
  • Sheet-drag regressions after the review fix (src/lib/sheets.ts), in herdr-ci-runner:local: key-bar-customization-demo-regression, link-menu-demo-regression, phone-worktree-sheet-regression: all PASS.
  • Browser regressions covering FileViewer, run in herdr-ci-runner:local: all pass: markdown-outline-browser-qa, file-viewer-regression (79 PASS), file-viewer-mobile-regression, file-preview-text-browser-qa, virtual-source-browser-qa, files-panel-demo-regression, file-text-size-demo-regression, settings-pages-demo-regression, deep-link-demo-regression, render-crash-demo-regression, web-panel-mobile-regression, repo-diff-browser-qa, relay-media-browser-qa, link-menu-demo-regression, and files-prompt-regression (through bun run check run, 8 PASS).
  • Not run: the full browser suite (CI runs it), and native iOS/Android or Windows checks.

Settings > File viewer > Markdown outline (off by default) lists a Preview's headings, marks the one being read and keeps it centered in the list without moving the document or the focus. A column on a wide viewer, a drawer from the header's Outline button on a narrow one.

Fixes #636.
@coderabbitai

coderabbitai Bot commented Nov 5, 2026 •

Copy link
Copy Markdown

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 22 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used all 4 included reviews currently available.

Learn how review limits work.

Review configuration:

Run configuration

Commits

Reviewing files that changed from the base of the PR and between 9f690eb and f21578c.

Files selected for processing: 20

  • DESIGN.md
  • changelog.d/markdown-outline.md
  • scripts/ci-browser.sh
  • scripts/markdown-outline-browser-qa.ts
  • scripts/markdown-outline-fixture.tsx
  • src/components/FileViewer.css
  • src/components/FileViewer.tsx
  • src/components/MarkdownOutline.tsx
  • src/components/MarkdownOutlineRow.tsx
  • src/components/SettingsDialog.tsx
  • src/lib/i18n.test.ts
  • src/lib/i18n/files.ja.ts
  • src/lib/i18n/files.ko.ts
  • src/lib/i18n/files.zh.ts
  • src/lib/markdownOutline.test.ts
  • src/lib/markdownOutline.ts
  • src/lib/settings.legacy-fixture.ts
  • src/lib/settings.test.ts
  • src/lib/settings.ts
  • src/lib/sheets.ts
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

…utline

# Conflicts:
#	src/lib/settings.legacy-fixture.ts
#	src/lib/settings.ts
… never drags the viewer's sheet

Review round 1 (Codex HIGH 1 and 2, both reproduced in scripts/markdown-outline-browser-qa.ts before the fix): the drawer handles Escape only while the focus is in its own viewer, and its list owns its touch strokes (data-sheet-stroke="own", read by lib/sheets.ts ownsStroke).
@devswha

devswha commented Nov 5, 2026

Copy link
Copy Markdown
Owner Author

Review round 1 (head e4efe4b): findings and how each was resolved

Reviewers: omo (dori review, verdict BLOCK on one reproduced MEDIUM) and Codex Terra 6.1 fast, xhigh (verdict BLOCKING on two HIGHs from code reading). The fix round is commit 2b7187f plus the QA steps in the next commit.

Codex HIGH 1 / omo MEDIUM (reproduced, fixed): the drawer took every Escape. While the drawer was open, its capture-phase listener stopped Escape everywhere. A dialog in front of the viewer (Settings) or the pane beside a docked panel (a terminal) never got the key, and the focus was pulled to the Outline button behind them. Reproduced in scripts/markdown-outline-browser-qa.ts before the fix ("Escape reaches the dialog in front": actual null). Fix: the drawer handles Escape only while the focus is inside its own viewer ([role=dialog], .file-panel or .file-reader containing the nav). New QA steps: a dialog in front keeps its Escape and its focus, and the drawer stays open (narrow, light and dark); the pane beside a docked panel keeps its Escape and its focus.

Codex HIGH 2 (reproduced, fixed): a downward swipe on the drawer dragged the whole viewer. ownsStroke in src/lib/sheets.ts exempted fields, touch-action: none and position: fixed, not a list placed inside the sheet, so a stroke down the outline at its top dragged the sheet (reproduced: .file-viewer at translateY(240px) mid-stroke). Fix: an element marked data-sheet-stroke="own" owns its strokes; the outline's nav carries it. New QA step: a CDP touch stroke down the drawer at its top leaves the sheet untransformed and the viewer open. The sheet-drag regressions (key-bar-customization-demo-regression, link-menu-demo-regression, phone-worktree-sheet-regression) were rerun on the fix: 8, 7 and 2 PASS, 0 failures.

Not reproduced, left as notes (no follow-up issues):

  • MEDIUM (Codex): an entry chosen before ProgressiveMarkdown has drawn its heading does nothing. The window is the few tasks it takes to draw the remaining chunks right after the Preview opens.
  • LOW (Codex): the mark is measured on open and on scroll only, so a layout change without a scroll (an image loading) can leave the previous entry marked until the next scroll.
  • LOW (omo): the Outline button's open/folded choice is kept per viewer, not per file, so a folded column stays folded on the next file opened in the same viewer.

@devswha

devswha commented Nov 5, 2026

Copy link
Copy Markdown
Owner Author

Review round 2 (head 57863e2)

  • omo (dori review): CLEAR at 57863e2.
  • Codex Terra 6.1 fast, xhigh: CLEAR. It confirmed both round-1 fixes and raised one new MEDIUM.

MEDIUM (Codex, reproduced, follow-up #3066): on a narrow viewer with the outline drawer open, Escape in the header's More menu closes the menu and the drawer together (reproduced with a scratch step of scripts/markdown-outline-browser-qa.ts: menu and drawer both gone, focus on More). It only touches keyboard use of the new opt-in drawer; the fix round is spent, so it is tracked in the follow-up rather than pushed here.

CodeRabbit did not review this PR (review limit reached).

…utline

# Conflicts:
#	src/components/FileViewer.tsx
#	src/lib/i18n.test.ts
#	src/lib/i18n/files.ja.ts
#	src/lib/i18n/files.ko.ts
#	src/lib/i18n/files.zh.ts
#	src/lib/settings.legacy-fixture.ts
#	src/lib/settings.ts
…utline

# Conflicts:
#	src/lib/settings.legacy-fixture.ts
#	src/lib/settings.ts
…n, so the settings dictionary part stays under 400 lines
…utline

# Conflicts:
#	src/lib/settings.legacy-fixture.ts
#	src/lib/settings.ts
@devswha

devswha commented Nov 5, 2026

Copy link
Copy Markdown
Owner Author

Local merge gate (owner-approved): origin/main merged (with #3064, #3067, #3072; both features kept; the outline is off while the #3064 editor is open), bun run check fast passed at this head (12416), omo CLEAR at this head, threads resolved; 18 FileViewer/editor/sheet browser regressions pass in the CI runner image. Opt-in, off by default. Follow-up #3066. Fixes #636. Merged without waiting for GitHub CI.

@devswha
devswha merged commit 42f4a9a into main Nov 5, 2026
12 checks passed
@devswha
devswha deleted the feat/files-markdown-outline branch November 5, 2026 22:03
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.

feat(files): add a centered outline to rendered Markdown previews

1 participant