# Files changed · PR \#23914 · pingdotgg/t3code

[View on GitCafe](https://git.cafe/pingdotgg/t3code/pull/23914/diffs)

Repository: [pingdotgg/t3code](https://git.cafe/pingdotgg/t3code)

Visibility: public

[fix\(web\): hide YAML frontmatter in Markdown file previews](https://git.cafe/pingdotgg/t3code/pull/23914.md)

Version: 7

Head commit: 9102852665604e7354f00c871e063e648f7dc007

Base commit: 51273020db3245f4ec29514a419d29b5dcb012dc

Comparison base: 51273020db3245f4ec29514a419d29b5dcb012dc

Showing up to 20 entries from this revision. If the pull request changes, reopen the first page before continuing.

## apps/web/src/components/files/AttachmentFilePreview.tsx

Status: modified; +6; −1

```diff
@@ -29,6 +29,7 @@
   FileSurfaceLoading,
   FileSurfaceNotice,
 } from "./fileSurfaceChrome";
+import { maskMarkdownFrontmatter } from "./filePreviewMode";
 
 const SourcePreview = lazy(() => import("./ReadOnlySourcePreview"));
 
@@ -214,7 +215,11 @@
       <DelimitedTablePreview name={props.name} text={content.text} delimiter={delimiter} />
     ) : kind === "markdown" && rendered ? (
       <ScrollArea className="min-h-0 flex-1">
+        <ChatMarkdown
+          text={maskMarkdownFrontmatter(content.text)}
+          cwd={undefined}
+          className="mx-auto max-w-4xl px-6 py-5"
+        />
-        <ChatMarkdown text={content.text} cwd={undefined} className="mx-auto max-w-4xl px-6 py-5" />
       </ScrollArea>
     ) : (
       <ReadOnlySourcePreview name={props.name} text={content.text} />
```

## apps/web/src/components/files/FileMarkdownPreview.tsx

Status: modified; +3; −1

```diff
@@ -2,6 +2,8 @@
 
 import ChatMarkdown from "~/components/ChatMarkdown";
 import { resolvePathLinkTarget } from "@t3tools/shared/fileLinks";
+
+import { maskMarkdownFrontmatter } from "./filePreviewMode";
 
 export function FileMarkdownPreview(props: {
   readonly cwd: string;
@@ -24,7 +26,7 @@
 
   return (
     <ChatMarkdown
-      text={props.text}
+      text={maskMarkdownFrontmatter(props.text)}
       cwd={props.cwd}
       imageBaseDir={imageBaseDir}
       threadRef={props.threadRef}
```

## apps/web/src/components/files/FilePreviewPanel.test.ts

Status: modified; +30; −0

```diff
@@ -7,6 +7,7 @@
 import {
   filePreviewReadErrorMessage,
   resolveFilePreviewPath,
+  maskMarkdownFrontmatter,
   setMarkdownTaskChecked,
   shouldShowFileExplorer,
   workspaceAssetResource,
@@ -118,3 +119,32 @@
     });
   });
 });
+
+describe("maskMarkdownFrontmatter", () => {
+  it("blanks leading frontmatter without moving later offsets", () => {
+    const markdown = "---\r\ntitle: Task\r\n---\r\n# Task\n- [ ] item\n";
+    const masked = maskMarkdownFrontmatter(markdown);
+
+    expect(masked.length).toBe(markdown.length);
+    expect(masked.trimStart()).toBe("# Task\n- [ ] item\n");
+    expect(masked.split("\n").length).toBe(markdown.split("\n").length);
+    expect(masked.indexOf("[ ]")).toBe(markdown.indexOf("[ ]"));
+  });
+
+  it("blanks frontmatter after a byte order mark", () => {
+    expect(maskMarkdownFrontmatter("\uFEFF---\ntitle: Task\n---\n# Body")).toBe(
+      "\uFEFF   \n           \n   \n# Body",
+    );
+  });
+
+  it("blanks frontmatter with CR-only line endings", () => {
+    expect(maskMarkdownFrontmatter("---\rtitle: Task\r---\r# Body")).toBe(
+      "   \r           \r   \r# Body",
+    );
+  });
+
+  it("leaves unterminated or non-leading fences alone", () => {
+    expect(maskMarkdownFrontmatter("---\ntitle: Task\n")).toBe("---\ntitle: Task\n");
+    expect(maskMarkdownFrontmatter("# A\n---\nb\n---\n")).toBe("# A\n---\nb\n---\n");
+  });
+});
```

## apps/web/src/components/files/filePreviewMode.ts

Status: modified; +30; −0

```diff
@@ -38,6 +38,36 @@
   return input.explorerOpen || input.relativePath === null;
 }
 
+const isFrontmatterFence = (line: string, fence: string): boolean =>
+  line.startsWith(fence) && line.slice(fence.length).trim() === "";
+
+/**
+ * Blanks a leading YAML frontmatter block so it renders as nothing instead of
+ * a rule and a setext heading. Every masked character becomes a space and
+ * newlines stay put, so node offsets (task checkbox toggles) and line numbers
+ * still point into the original source.
+ */
+export function maskMarkdownFrontmatter(markdown: string): string {
+  const start = markdown.startsWith("\uFEFF") ? 1 : 0;
+  const line = /([^\r\n]*)(\r\n|\r|\n|$)/y;
+  line.lastIndex = start;
+  let match = line.exec(markdown);
+  if (!match?.[2] || !isFrontmatterFence(match[1] ?? "", "---")) return markdown;
+
+  while (line.lastIndex < markdown.length && (match = line.exec(markdown))) {
+    const text = match[1] ?? "";
+    if (isFrontmatterFence(text, "---") || isFrontmatterFence(text, "...")) {
+      const blockEnd = match.index + text.length;
+      return (
+        markdown.slice(0, start) +
+        markdown.slice(start, blockEnd).replace(/[^\r\n]/g, " ") +
+        markdown.slice(blockEnd)
+      );
+    }
+  }
+  return markdown;
+}
+
 export function setMarkdownTaskChecked(
   markdown: string,
   markerOffset: number,
```

## apps/web/test/fixtures/frontmatter-preview/frontmatter-repro.md

Status: added; +11; −0

```diff
@@ -1,0 +1,11 @@
+---
+type: Task
+title: Decide the joke source and Draft safety limits
+description: Settle where Jokes come from and what a Draft may never contain.
+status: draft
+task_status: ready
+blocked_by: []
+created: 2026-10-22
+---
+
+# Decide the joke source and Draft safety limits
```

## apps/web/test/fixtures/frontmatter-preview/frontmatter-task-proof.md

Status: added; +14; −0

```diff
@@ -1,0 +1,14 @@
+---
+type: Task
+title: Decide the joke source and Draft safety limits
+description: Settle where Jokes come from and what a Draft may never contain.
+status: draft
+task_status: ready
+blocked_by: []
+created: 2026-10-22
+---
+
+# Decide the joke source and Draft safety limits
+
+- [ ] first task
+- [ ] second task
```

## apps/web/test/fixtures/frontmatter-preview/plain-control.md

Status: added; +9; −0

```diff
@@ -1,0 +1,9 @@
+# Control
+
+Paragraph
+
+---
+
+Body after rule
+
+- [ ] control task
```

## apps/web/test/fixtures/frontmatter-preview/seed.mjs

Status: added; +11; −0

```diff
@@ -1,0 +1,11 @@
+import * as NodeFS from "node:fs";
+import * as NodeURL from "node:url";
+import * as NodePath from "node:path";
+const destination = NodePath.resolve(process.argv[2] ?? ".");
+for (const name of NodeFS.readdirSync(new URL(".", import.meta.url))) {
+  if (name.endsWith(".md"))
+    NodeFS.copyFileSync(
+      NodeURL.fileURLToPath(new URL(name, import.meta.url)),
+      NodePath.resolve(destination, name),
+    );
+}
```

## apps/web/test/fixtures/frontmatter-preview/unterminated-control.md

Status: added; +5; −0

```diff
@@ -1,0 +1,5 @@
+***
+
+title: Visible control
+
+# Unclosed
```
