fix(web): hide YAML frontmatter in Markdown file previews#23914
Problem
Opening a Markdown file with leading YAML frontmatter from Files renders the metadata as a horizontal rule and a large setext heading. The document should start at its body heading.
Change
Mask complete leading YAML frontmatter in web/desktop file and attachment previews. Preserve every source offset and line break so task checkbox edits still target the original file. Source view and saved contents retain the metadata. Recognize BOM-prefixed files and LF, CRLF, or CR line endings.
Scope and approval
Fixes #23704
Triaged bug via the automated issue flow. Reproduced on current main in the real web app with the reporter's exact fixture. Mobile uses a separate renderer and is outside this reported web/desktop fix.
Verification
vp test run src/components/files/FilePreviewPanel.test.tsfromapps/web: 17 passed. BOM and CR-only cases failed before the follow-up fix.- Targeted lint and formatting passed; web
tsc --noEmit -p apps/webpassed. - Real app: Files preview and draft attachment preview at 1440 and 390 CSS pixels, light and dark. Source/render toggles and task checkbox persistence checked with paired recordings and byte comparisons.
Evidence
Evidence index. Independent proof audit reproduced F1-after pixel-identically and verified all 36 artifact hashes and all eight diff pairs.
Same reporter bytes, route /draft/52c1b0b1-6ff3-4fb8-990a-967b991c033f, layout and theme within each still pair. Files panel maximized, file explorer collapsed. Chrome on Linux; themes follow browser prefers-color-scheme. No device emulation; default font scaling. CSS viewports 1440×900 and 390×900, zoom 100%, DPR 2. Browser tool reduces desktop screenshots to 1280×800; narrow screenshots are 780×1800. These are responsive web captures, not native mobile.
Diff0 = per-channel absolute difference amplified 8×, no threshold. DiffT = magenta where maximum RGB channel difference >16/255, black elsewhere. No alignment, crops, masks, or exclusions. Pixel counts include app chrome. Artifact SHA-256 is shown beneath every link. Before code: 51273020db; after code: 373457aebc (subsequent commits only add fixtures).
| Row / CSS viewport | Before (51273020db) | After (373457aebc) | Diff0 | DiffT | Changed pixels (0 / T) |
|---|---|---|---|---|---|
| F1 1440×900 light | F1-before.png SHA-256 ff07910f24e0cc1f7e6ccc6926247bcfa597ffe7c7a296ac9eaaf5a0c1b42f71 | F1-after.png SHA-256 c992bd4f49ab6f723b8e72f8263f7425665fe2b6634f0133dbb5318cd9ece8dd | F1-diff0.png SHA-256 cda2bf2aa37ae82be22a3b7b10ecbdfa9a804dc0c0624d303cf1fc9239ea3447 | F1-diffT.png SHA-256 c1abfcc2f4fa3bd3b3db8bb399285e034061c182b972e009ceddd7ab83ee96e7 | 18688 / 15900 |
| F2 1440×900 dark | F2-before.png SHA-256 860eb814b6acd7d99f9b7f13a5975d3c0645b65129e1013ea1907f0d6cb79f6d | F2-after.png SHA-256 df35ec8d321ee7933d8968e606a101ec172cfdae5e97368f95107f736109b0ef | F2-diff0.png SHA-256 a79dbe8dcf61d91a86bd0cdb028b1bca899f5bf28a4e7b0b240d2abc3c14d6e4 | F2-diffT.png SHA-256 c31d4d70989fb9ef0414886ab8018a006533998ee6dd1699420da103939f8c62 | 18688 / 16333 |
| F3 390×900 light | F3-before.png SHA-256 b78f71fe2b4b99e7f8ca4a09fde6c757ae229573b93559368e729b5bb7292d59 | F3-after.png SHA-256 18dfab21f272c256577319391df03c49d56c70f95464d1235b7662d35422b093 | F3-diff0.png SHA-256 31540e8cbb89b0c436f765defb3195ad0e860716cc24ba28d0785c0011159ff1 | F3-diffT.png SHA-256 cb0a7aa17e64479371583a05ac4f81110f85849701b790fe010b4d788c9ba00d | 70422 / 67809 |
| F4 390×900 dark | F4-before.png SHA-256 3f87c396438b21af650040b7e756068022aa0400277e5fe1fb321c12c09481ef | F4-after.png SHA-256 c6ab8d254d0a8c22d95c3b020d9a5e98a5772cf49525225537e1ab6d89941a20 | F4-diff0.png SHA-256 0f28f66ebe6abd71b8ded5f108943461004e17be85c1fb110e01120e3ee28f1c | F4-diffT.png SHA-256 fd5067a064caba83dad7431a710a8e59fc03aee8b0ab9f9684cd703738334ad1 | 70428 / 67558 |
| D1 1440×900 light | D1-before.png SHA-256 4aea81677b88525892e48a05a2e541e3b645d8dea052919450abaff621becd95 | D1-after.png SHA-256 cccd2d652e1510f807b34798bd7c63d11aa4cc8ae30c53a9171854ffaa9b0c22 | D1-diff0.png SHA-256 b7d4aca41403010d1a466ef431edbe79a3e90947710a489dbfaff81a08f5b438 | D1-diffT.png SHA-256 9caa16e11466232de9dc879070b8cf38f8b3c09faab317bb5bc64e9737a76817 | 17596 / 16118 |
| D2 1440×900 dark | D2-before.png SHA-256 429e56e2e7ac2c7d8433fc60c5dc333ec515914b09d33f480763c663de227d81 | D2-after.png SHA-256 ca067553db6e20b960e3fa2624305e5f0fe78dc8fd58ebca0b9c45a0b2a15203 | D2-diff0.png SHA-256 a4ebb2def446ec5ddfd2eaf40f8cb4e7bb4b90df3467c194d46ae7ae0511ab2c | D2-diffT.png SHA-256 c8694bb60b3a67bc22bcc8417ec7b6831f2d9facfc341b0b25fc9e2b2239cead | 17545 / 15605 |
| D3 390×900 light | D3-before.png SHA-256 4db0a91e251874cf65da7fe377db1d47297d8c4bff8b2d41afc710814c712ae0 | D3-after.png SHA-256 5b10e7d64a8e311a548a400b6ff2190d480c871219346fcf403283c6f9c4819a | D3-diff0.png SHA-256 90248f64d4a115dedd583b27deee77868810862d5a2cf6883ffa728e0cb8dec1 | D3-diffT.png SHA-256 a0d8d8e557d3f93218addc288f30ae3866dd1880102d0dc23b2d5d40ab9e7f59 | 71442 / 68805 |
| D4 390×900 dark | D4-before.png SHA-256 dd1bdf9eb89d99e2f865659e5f853a8635564ef03a29ceb6e9b41d00af2b50dc | D4-after.png SHA-256 0e1556650674b6e938ef6987ce5f02c31dffd3b6986d4a7fb804fd4e18fb4ad1 | D4-diff0.png SHA-256 a808cd213aea78e331d3d146f27f88d9b18e0feab25b9fb0d33f782d2c237b84 | D4-diffT.png SHA-256 eac32bfe6958d4b5cb43f45660b3be32214fdff233a731cc6ef3f047d9746064 | 71443 / 68446 |
F1–F4: Files rendered preview passes: one body heading, no metadata/rule/setext artifact or masked gap. At 390 the body wraps. D1–D4: supplementary draft Blob attachment pairs show the same corrected body using AttachmentFilePreview; these do not establish sent asset fetching.
I1 task interaction, 1440×900 light. Same companion bytes and ordered actions. Timing and sidebar/composer framing differ between recordings; source syntax highlighting loads asynchronously. Recordings are normal speed. Approximate timestamps:
- Before (14.109 s): 0–7 rendered → source with YAML intact; 7–9 rendered → first task checked → source; 9–11 source → rendered → uncheck; 11–14 close/reopen, explorer expanded.
- After (19.158 s): 0–6 rendered → source with YAML intact; 6–9 rendered → first task checked → source; 9–16 source → rendered → uncheck; 16–19 close/reopen, explorer expanded.
- Both versions: disk-byte assertion after checking equals original reporter bytes plus
- [x] first taskand- [ ] second task; after unchecking, byte comparison equals seeded original. No YAML or second-task mutation.
| Interaction | Before | After |
|---|---|---|
| I1 Files task/source | I1-before.mp4 SHA-256 e30e9b7667748eaa5009da0e1995ef51cdbc5c50b3bd8772e934a41ea469246c | I1-after.mp4 SHA-256 e7b5a718458c186f6574c48708cae47e2add49922432b4bcac634f2d9bf15a03 |
| Draft source/render/close/reopen (supplementary) | draft-before.mp4 SHA-256 37b95ab545fec971fabc92f91cdc6ed2123e35b96fcc2b60086015d9c8087798 | draft-after.mp4 SHA-256 192824ad506ae632f79e6359bd2f94ae2b03d467122a685f51df2cd1ebb76f70 |
Draft interaction, 390×900 dark: before 9.230 s; after 7.727 s. At 0 s switch rendered → source; approximately 1–6 s inspect original YAML, closing fence, body heading; approximately 6–8 s return rendered → close → reopen. Before shows metadata in rendered view; after hides it. No task editing in attachment preview. Source syntax highlighting briefly loads, then displays original metadata.
L1 passes: real composer file intake, draft chip, rendered/source/close, exact reporter fixture. C1 passes in Files and draft: Control heading, Paragraph, body horizontal rule, Body after rule, checkbox retained. C2 visual proof is not claimed: formatting normalized its opener to ***; the focused unit test covers true unterminated frontmatter.
Unverified: A1–A4 and I2 sent attachment. No persisted sent fixture was available in this tab's isolated draft state. Sending via composer would invoke a provider; no provider was invoked and no synthetic sent state was injected. Draft proof is supplementary, not a substitute for these matrix rows. Desktop shares web components but was not separately launched. Native mobile, relay/tunnel, other providers, chat Markdown and other preview formats were not exercised; mobile renderer remains unchanged.
Visible differences: the frontmatter rule and metadata heading disappear, and the document body moves up to the beginning of the preview. Source metadata and task edits remain intact.
Reproduce with node apps/web/test/fixtures/frontmatter-preview/seed.mjs <workspace-root>, then open frontmatter-repro.md from Files or attach it in the composer. The committed seed recreates the exact reporter fixture and task companion.
Models: Claude Opus 5.5 (Claude Code harness) and GPT-6.1-Sol (Codex harness).
Checks
- SuccessfulCodeRabbitGitHub
Review completed
- Successful
Skipped - Not affected
- Skipped
- Skipped
- Skipped
- SkippedRemove preview downloadGitHub
- SkippedBuild preview JS bundleGitHub
- SkippedDeploy web previewGitHub
- SkippedBuild preview JS bundleGitHub
- SkippedEAS PreviewGitHub
- SkippedDeploy web previewGitHub
- SkippedEAS PreviewGitHub
- SkippedBuild preview JS bundleGitHub
- SkippedDeploy web previewGitHub
- SkippedEAS PreviewGitHub
- SuccessfulCheckGitHub
- Skipped
- SkippedRelease SmokeGitHub
- SkippedRustGitHub
- SuccessfulTestGitHub
- SuccessfulBuild and TypecheckGitHub
- SuccessfulTest WebGitHub
- SuccessfulLabel PR 23914GitHub
- Skipped
- SuccessfulLabel PR sizeGitHub
- Skipped
- Skipped
- Successful
- Successful
- SuccessfulCollect PR targetsGitHub
- SkippedEAS PreviewGitHub
- SuccessfulTest Server 7GitHub
- SuccessfulTest Server 4GitHub
- SuccessfulTest Server 3GitHub
- SuccessfulTest Server 8GitHub
- SuccessfulTest Server 1GitHub
- SuccessfulTest Server 5GitHub
- SuccessfulTest Server 6GitHub
- SuccessfulTest Server 2GitHub
- SuccessfulPrepare PR size configGitHub
- SuccessfulChangesGitHub
- SuccessfulLintGitHub
- SuccessfulNative fingerprint diffGitHub
Added 4 commits
Approvability
Verdict: Approved at 9102852
Macroscope's review found this PR approvable — Adds a small, pure frontmatter-masking helper to the two existing Markdown preview paths, preserving source offsets and line endings for task interactions. The accompanying unit tests and fixtures cover the supported cases without introducing broader runtime, schema, or infrastructure changes.
You can add or adjust custom eligibility rules. Learn more.
No actionable comments were generated in the recent review. 🎉
ℹ️ Recent review info
⚙️ Run configuration
- Source mode: Auto
- Repository: pingdotgg/t3code
- Branch: fix/issue-23704
- File: .coderabbit.config.ts
- Commit: 9102852
- Layers: Repository TS config
- Review profile: CHILL
- Plan: Team
- Run ID:
15a77357-6bd2-4e41-b811-e8a68da12fae
📥 Commits
Reviewing files that changed from the base of the PR and between 51273020db3245f4ec29514a419d29b5dcb012dc and 9102852665604e7354f00c871e063e648f7dc007.
📒 Files selected for processing: 9
apps/web/src/components/files/AttachmentFilePreview.tsxapps/web/src/components/files/FileMarkdownPreview.tsxapps/web/src/components/files/FilePreviewPanel.test.tsapps/web/src/components/files/filePreviewMode.tsapps/web/test/fixtures/frontmatter-preview/frontmatter-repro.mdapps/web/test/fixtures/frontmatter-preview/frontmatter-task-proof.mdapps/web/test/fixtures/frontmatter-preview/plain-control.mdapps/web/test/fixtures/frontmatter-preview/seed.mjsapps/web/test/fixtures/frontmatter-preview/unterminated-control.md
Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 8 remain after this review.
📝 Walkthrough
Markdown previews now mask a leading YAML frontmatter block before rendering. The masking function preserves line breaks and character offsets. Tests cover supported fences, BOM and CR-only input, and unchanged input when fences are absent or not leading.
Changes: Markdown preview frontmatter
Layer / File(s) Summary Detect and mask frontmatter
apps/web/src/components/files/filePreviewMode.ts,apps/web/src/components/files/FilePreviewPanel.test.ts,apps/web/test/fixtures/frontmatter-preview/*maskMarkdownFrontmatterrecognizes a leading---block closed by---or.... It replaces non-newline characters with spaces and leaves the input unchanged when the opening or closing fence is missing. Tests check preserved offsets and line counts, BOM and CR-only input, and unchanged cases.Mask content in Markdown previews
apps/web/src/components/files/AttachmentFilePreview.tsx,apps/web/src/components/files/FileMarkdownPreview.tsxBoth preview components pass masked text to ChatMarkdown.Priority: ⬇️ Low
Estimated code review effort: 2 (Simple) | ~10 minutes
Change: Bug fix · Severity of issue fixed: Low
Possibly related issues
Merge Risk: ⚪ Minimal · up to 91028
Leading frontmatter is hidden in web previews without changing saved metadata or task checkbox positions. The change is ready to merge after normal checks.
Pre-merge checks: Passed 4
Check name Status Explanation Title check Passed The title "fix(web): hide YAML frontmatter in Markdown file previews" matches the main change. The diff masks leading YAML frontmatter in Markdown file and attachment previews, and the linked issue #2…Description check Passed The description follows the repository template. It has Problem, Change, Scope and approval, and Verification sections. The problem is stated with expected behavior and a reproduction path. The change… Linked Issues check Passed Issue #23704requires leading YAML frontmatter to be recognized so the rendered Markdown starts at the body heading.FileMarkdownPreviewpasses text throughmaskMarkdownFrontmatterbefore `ChatMar…Out of Scope Changes check Passed The changed source files implement the #23704preview behavior. The attachment preview uses the same masking helper, which is consistent with the stated web/desktop preview scope. The tests verify the…Full details: Title check
Explanation
The title "fix(web): hide YAML frontmatter in Markdown file previews" matches the main change. The diff masks leading YAML frontmatter in Markdown file and attachment previews, and the linked issue
#23704asks for the same behavior. The title is short, specific, and follows the conventional commit style used in the repository.
Full details: Description check
Explanation
The description follows the repository template. It has Problem, Change, Scope and approval, and Verification sections. The problem is stated with expected behavior and a reproduction path. The change explains the masking approach, BOM and line-ending handling, and what stays intact. Scope cites the issue
#23704and explains why mobile is excluded. Verification lists the focused test run, lint, type check, and real-app checks with observed results, and it names what was not verified. The description does not include a conventional commit title line from the template, and the Scope section cites an automated triage flow rather than a maintainer approval comment, which the template asks for. These gaps are minor.
Full details: Linked Issues check
Explanation
Issue
#23704requires leading YAML frontmatter to be recognized so the rendered Markdown starts at the body heading.FileMarkdownPreviewpasses text throughmaskMarkdownFrontmatterbeforeChatMarkdownrenders it. The helper masks complete leading---frontmatter, preserves line breaks and character offsets, supports BOM and LF, CRLF, and CR line endings, and leaves unterminated or non-leading fences unchanged. Tests cover the rendered-content behavior, later offsets, BOM, CR-only input, and unchanged invalid cases. The implementation keeps source text available to the source view and preserves task marker offsets.
Full details: Out of Scope Changes check
Explanation
The changed source files implement the
#23704preview behavior. The attachment preview uses the same masking helper, which is consistent with the stated web/desktop preview scope. The tests verify the helper, and the Markdown fixtures and seed script provide focused reproduction and regression coverage. No unrelated behavior or unrelated product area is demonstrated. Mobile remains unchanged, consistent with the separate-renderer boundary.
✨ Finishing Touches
📝 Generate docstrings
- Commit to this branch
- Create a new PR
🧪 Generate unit tests (beta)
- Commit to this branch
- Create a new PR
- Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts
Comment @coderabbitai help to get the list of available commands.
Thanks for picking this up. One UX suggestion: rather than hiding the frontmatter from the preview, could we render it the way GitHub's Markdown preview does, as a small key/value table above the body?
GitHub parses leading YAML frontmatter and shows it as a compact two-column table (type | Note, title | …, description | …, status | draft), then renders the document from its first real heading. That fixes the original bug (no stray <hr> plus giant setext heading) and still lets you see the metadata while reading.
For notes vaults (Obsidian/OKF-style files with type, status, description, tags), the frontmatter is often the most useful part when you skim files in the preview. With it hidden, you have to switch to Source view just to check a file's status.
Possible shape:
- Keep the offset-preserving mask you already have, so task checkbox edits still target the right bytes.
- Parse the masked range as YAML. If it parses into a flat map, render a small read-only table (or a collapsible "Properties" block) above the body. Show nested values as inline YAML/JSON.
- If parsing fails, fall back to the current behaviour (hidden) or show it as a fenced
yamlcode block.
Happy to help test with real vault files if useful.
At 9102852, rendered previews hide complete frontmatter while Source retains it. A metadata table adds YAML parsing and a new preview UI; that suggestion is left for maintainer consideration as a follow-up. This fix stays scoped to removing the rule/setext rendering artifact.