{
+ if (text.startsWith("+") && !text.startsWith("+++")) return "added";
+ if (text.startsWith("-") && !text.startsWith("---")) return "removed";
+ if (text.startsWith("@@")) return "hunk";
+ return "context";
+}
+
+function displayDiffLineText(text: string, lineClass: DiffLineClass): string {
+ const displayText = lineClass === "added" || lineClass === "removed" ? text.slice(1) : text;
+ return displayText || " ";
+}
+
+function filePathFromGitDiffHeader(line: string): string | null {
+ const match = /^diff --git a\/(.+) b\/(.+)$/.exec(line);
+ if (!match) return null;
+ return match[2] ?? null;
+}
+
+function DiffLineFrame({ lines, className }: { lines: readonly RenderDiffLine[]; className?: string | undefined }): UiNode {
const preClassName = ["codex-panel-diff", className].filter(Boolean).join(" ");
return (
diff --git a/tests/scripts/grit-policy.test.mjs b/tests/scripts/grit-policy.test.mjs
index 9db274dc..9f399d55 100644
--- a/tests/scripts/grit-policy.test.mjs
+++ b/tests/scripts/grit-policy.test.mjs
@@ -393,6 +393,61 @@ export type Params = ToolRequestUserInputParams;
expect(pluginDiagnostics(report, "src/app-server/protocol/server-requests.ts")).toEqual([]);
});
+ it("keeps TSX files in rendering-owned source folders", async () => {
+ const cwd = await tempBiomeWorkspace(["no-misplaced-tsx.grit"]);
+ await writeFile(
+ path.join(cwd, "src/features/chat/application/state/view.tsx"),
+ `
+export const value = ;
+`.trimStart(),
+ );
+ await writeFile(
+ path.join(cwd, "src/domain/threads/view.tsx"),
+ `
+export const value = ;
+`.trimStart(),
+ );
+ await writeFile(
+ path.join(cwd, "src/features/chat/ui/message.tsx"),
+ `
+export const value = ;
+`.trimStart(),
+ );
+ await writeFile(
+ path.join(cwd, "src/settings/section.tsx"),
+ `
+export const value = ;
+`.trimStart(),
+ );
+ await writeFile(
+ path.join(cwd, "src/shared/ui/diff.tsx"),
+ `
+export const value = ;
+`.trimStart(),
+ );
+
+ const report = biomeLint(
+ [
+ "src/features/chat/application/state/view.tsx",
+ "src/domain/threads/view.tsx",
+ "src/features/chat/ui/message.tsx",
+ "src/settings/section.tsx",
+ "src/shared/ui/diff.tsx",
+ ],
+ cwd,
+ );
+
+ expect(pluginMessages(report, "src/features/chat/application/state/view.tsx")).toEqual([
+ "Keep TSX files in UI, panel, settings, or explicit render bridge modules; non-rendering source should use .ts.",
+ ]);
+ expect(pluginMessages(report, "src/domain/threads/view.tsx")).toEqual([
+ "Keep TSX files in UI, panel, settings, or explicit render bridge modules; non-rendering source should use .ts.",
+ ]);
+ expect(pluginDiagnostics(report, "src/features/chat/ui/message.tsx")).toEqual([]);
+ expect(pluginDiagnostics(report, "src/settings/section.tsx")).toEqual([]);
+ expect(pluginDiagnostics(report, "src/shared/ui/diff.tsx")).toEqual([]);
+ });
+
it("keeps app-server protocol modules behind app-server and chat ingestion boundaries", async () => {
const cwd = await tempBiomeWorkspace(["no-app-server-protocol-boundary-imports.grit"]);
await writeFile(
@@ -792,6 +847,7 @@ async function tempBiomeWorkspace(plugins) {
await mkdir(path.join(cwd, "src/features/chat/panel"), { recursive: true });
await mkdir(path.join(cwd, "src/features/chat/panel/surface"), { recursive: true });
await mkdir(path.join(cwd, "src/features/chat/ui"), { recursive: true });
+ await mkdir(path.join(cwd, "src/settings"), { recursive: true });
await mkdir(path.join(cwd, "src/domain/threads"), { recursive: true });
await mkdir(path.join(cwd, "src/app-server/connection"), { recursive: true });
await mkdir(path.join(cwd, "src/app-server/protocol"), { recursive: true });
diff --git a/tests/shared/diff/unified.test.ts b/tests/shared/ui/diff.test.ts
similarity index 87%
rename from tests/shared/diff/unified.test.ts
rename to tests/shared/ui/diff.test.ts
index 817a3bde..ff2843a8 100644
--- a/tests/shared/diff/unified.test.ts
+++ b/tests/shared/ui/diff.test.ts
@@ -1,11 +1,11 @@
import { describe, expect, it } from "vitest";
-import { displayDiffLines } from "../../../src/shared/diff/unified";
+import { unifiedDiffDisplayLines } from "../../../src/shared/ui/diff";
describe("unified diff display lines", () => {
it("simplifies git diff file headers for turn diff display", () => {
expect(
- displayDiffLines(
+ unifiedDiffDisplayLines(
"diff --git a/days/2026-05-16.md b/days/2026-05-16.md\nindex 111..222\n--- a/days/2026-05-16.md\n+++ b/days/2026-05-16.md\n@@\n-old\n+new",
),
).toEqual([{ text: "days/2026-05-16.md", kind: "file" }, { text: "@@" }, { text: "-old" }, { text: "+new" }]);
@@ -13,7 +13,7 @@ describe("unified diff display lines", () => {
it("keeps added-file diffs readable after simplifying headers", () => {
expect(
- displayDiffLines(
+ unifiedDiffDisplayLines(
"diff --git a/new-note.md b/new-note.md\nnew file mode 100644\nindex 0000000..1111111\n--- /dev/null\n+++ b/new-note.md\n@@\n+hello",
),
).toEqual([{ text: "new-note.md", kind: "file" }, { text: "new file mode 100644" }, { text: "@@" }, { text: "+hello" }]);
@@ -21,7 +21,7 @@ describe("unified diff display lines", () => {
it("keeps body lines that look like file markers after the hunk starts", () => {
expect(
- displayDiffLines(
+ unifiedDiffDisplayLines(
"diff --git a/note.md b/note.md\nindex 111..222\n--- a/note.md\n+++ b/note.md\n@@\n+++ frontmatter\n--- removed marker",
),
).toEqual([{ text: "note.md", kind: "file" }, { text: "@@" }, { text: "+++ frontmatter" }, { text: "--- removed marker" }]);