Files
paseo/packages/website/src/components/mockup/diff.tsx
T
Mohamed Boudra 375e9b3e53 feat(website): rebuild homepage product mockups
Replace static captures with live, responsive scenes that demonstrate building, reviewing, shipping, and extending work across desktop and mobile.
2026-08-30 23:48:54 +02:00

779 lines
26 KiB
TypeScript

import {
ChevronDown,
ChevronRight,
Columns2,
Ellipsis,
List,
Minus,
Plus,
RefreshCw,
Rows3,
} from "lucide-react";
import { Code, DiffStat } from "./atoms";
import { TABBAR_H } from "./geometry";
import { ReactFileIcon } from "./icons";
// Row height and the gutter width are the two numbers that make a diff read as a
// diff. Everything else follows them.
const ROW_H = 18.5;
const HUNK_H = 23;
const GUTTER_W = 42;
const ROW_STYLE = { height: ROW_H };
const HUNK_STYLE = { height: HUNK_H };
const GUTTER_STYLE = { width: GUTTER_W };
const SCROLLBAR_ROW_STYLE = { height: 9 };
const SUBHEADER_STYLE = { height: TABBAR_H };
export type DiffRow =
| { kind: "file"; name: string; path?: string; add: string; remove: string }
| { kind: "hunk"; label: string }
| { kind: "line"; n: string; text: string; mark?: DiffMark }
| { kind: "scrollbar" }
| { kind: "gap" };
type DiffMark = "add" | "remove";
// The row fill and the gutter number are the whole visual language of a diff:
// status hue at low alpha behind the row, full status hue on the number.
const ROW_FILL: Record<DiffMark, string> = {
add: "bg-mock-diff-add",
remove: "bg-mock-diff-remove",
};
const NUMBER_COLOR: Record<DiffMark, string> = {
add: "text-mock-success",
remove: "text-mock-danger",
};
function DiffLine({ n, text, mark }: { n: string; text: string; mark?: DiffMark }) {
const fill = mark ? ROW_FILL[mark] : "";
const number = mark ? NUMBER_COLOR[mark] : "text-mock-fg-muted";
return (
<div className={`flex items-center ${fill}`} style={ROW_STYLE}>
<span
className={`flex h-full shrink-0 items-center justify-end border-r border-mock-border pr-[10px] font-mono text-[11px] tabular-nums ${number}`}
style={GUTTER_STYLE}
>
{n}
</span>
<span className="min-w-0 flex-1 overflow-hidden whitespace-pre pl-[10px] font-mono text-[12px] leading-[18.5px]">
<Code line={text} />
</span>
</div>
);
}
function FileRow({
name,
path,
add,
remove,
}: {
name: string;
path?: string;
add: string;
remove: string;
}) {
return (
<div className="flex h-[36px] items-center gap-[8px] border-b border-mock-border bg-mock-surface0 pr-[14px] pl-[16px]">
<ChangesFileIcon name={name} />
<span className="min-w-0 flex-1 truncate text-[12px]">
<span className="text-mock-fg">{name}</span>
{path ? <span className="ml-[6px] text-mock-fg-muted">{path}</span> : null}
</span>
<DiffStat add={add} remove={remove} />
</div>
);
}
export function DiffRows({ rows, flush }: { rows: DiffRow[]; flush?: boolean }) {
return (
<div className={`relative overflow-hidden bg-mock-surface1 ${flush ? "" : "min-h-0 flex-1"}`}>
{rows.map((row, index) => {
const key = `${row.kind}-${index}`;
if (row.kind === "file") return <FileRow key={key} {...row} />;
if (row.kind === "scrollbar")
return (
<div key={key} className="flex items-center pl-[46px]" style={SCROLLBAR_ROW_STYLE}>
<span className="h-[5px] w-[110px] rounded-full bg-mock-surface4" />
</div>
);
if (row.kind === "gap") return <div key={key} style={ROW_STYLE} />;
if (row.kind === "hunk")
return (
<div key={key} className="flex items-center" style={HUNK_STYLE}>
<span className="h-full shrink-0 border-r border-mock-border" style={GUTTER_STYLE} />
<span className="pl-[10px] font-mono text-[11px] text-mock-fg-muted">
{row.label}
</span>
</div>
);
return <DiffLine key={key} {...row} />;
})}
{!flush ? (
<span className="pointer-events-none absolute top-[86px] right-[3px] bottom-[8px] w-[4px] rounded-full bg-mock-surface2">
<span className="mt-[112px] block h-[92px] rounded-full bg-mock-surface4" />
</span>
) : null}
</div>
);
}
/** The `Uncommitted ⌄` scope row with the view-mode controls on the right. */
export function ScopeRow({ label }: { label: string }) {
return (
<div className="flex h-[31px] shrink-0 items-center border-b border-mock-border px-[14px]">
<span className="flex items-center gap-[5px] text-[12px] text-mock-fg">
{label}
<ChevronDown size={12} className="text-mock-fg-muted" />
</span>
<span className="flex-1" />
<span className="flex items-center gap-[13px] text-mock-fg-muted">
<Columns2 size={13} />
<span className="rounded-[5px] bg-mock-surface2 p-[3px] text-mock-fg">
<List size={12} />
</span>
<Rows3 size={13} />
<ChevronDown size={12} />
</span>
</div>
);
}
// --- flat diff tree ---------------------------------------------------------
// The default diff view is a flat file list: name · path · +N -M · expand box.
export interface TreeFile {
name: string;
path?: string;
add: string;
remove: string;
}
/** The expand affordance on a tree row — green + for pure additions, else a dot. */
function ExpandBox({ pure }: { pure: boolean }) {
return (
<span
className={`flex size-[15px] shrink-0 items-center justify-center rounded-[4px] border ${
pure
? "border-mock-success/60 text-mock-success"
: "border-mock-warning/50 text-mock-warning"
}`}
>
{pure ? (
<Plus size={11} strokeWidth={2.4} />
) : (
<i className="block size-[3px] rounded-full bg-current" />
)}
</span>
);
}
/** One row of the flat diff tree. */
export function DiffTreeRow({ file }: { file: TreeFile }) {
const pure = file.remove === "0";
return (
<div className="flex items-center gap-[10px] border-b border-mock-border px-[14px] py-[7px]">
<span className="min-w-0 flex-1 truncate text-[12.5px]">
<span className="text-mock-fg">{file.name}</span>
{file.path ? <span className="text-mock-fg-muted"> {file.path}</span> : null}
</span>
<span className="flex shrink-0 items-center gap-[4px] text-[11px] tabular-nums">
<span className="text-mock-success">+{file.add}</span>
<span className={pure ? "text-mock-fg-xmuted" : "text-mock-danger"}>-{file.remove}</span>
</span>
<ExpandBox pure={pure} />
</div>
);
}
export function DiffTree({ files }: { files: TreeFile[] }) {
return (
<div className="min-h-0 flex-1 overflow-hidden">
{files.map((file) => (
<DiffTreeRow key={`${file.path ?? ""}/${file.name}`} file={file} />
))}
</div>
);
}
export const REVIEW_TREE: TreeFile[] = [
{ name: "providers.md", path: "docs", add: "3", remove: "1" },
{
name: "bridge-plugin.mjs",
path: "packages/server/src/server/agent/providers/opencode",
add: "146",
remove: "0",
},
{
name: "bridge.test.ts",
path: "packages/server/src/server/agent/providers/opencode",
add: "181",
remove: "0",
},
{
name: "bridge.ts",
path: "packages/server/src/server/agent/providers/opencode",
add: "284",
remove: "0",
},
{
name: "server-manager.ts",
path: "packages/server/src/server/agent/providers/opencode",
add: "13",
remove: "1",
},
{
name: "opencode-agent.test.ts",
path: "packages/server/src/server/agent/providers",
add: "40",
remove: "0",
},
{
name: "opencode-agent.ts",
path: "packages/server/src/server/agent/providers",
add: "58",
remove: "8",
},
{
name: "opencode-bridge-adapter.test.ts",
path: "packages/server/src/server/agent/providers",
add: "123",
remove: "0",
},
{
name: "opencode-bridge.local.e2e.test.ts",
path: "packages/server/src/server/agent/providers",
add: "158",
remove: "0",
},
{
name: "provider-registry.ts",
path: "packages/server/src/server/agent",
add: "10",
remove: "2",
},
{
name: "provider-snapshot-manager.ts",
path: "packages/server/src/server/agent",
add: "5",
remove: "0",
},
{ name: "bootstrap.ts", path: "packages/server/src/server", add: "12", remove: "0" },
{ name: "package.json", path: "packages/server", add: "2", remove: "1" },
{ name: "trace-daemon.mjs", path: "scripts", add: "2", remove: "0" },
{ name: "package-lock.json", add: "260", remove: "1" },
];
// --- explorer changes tree -------------------------------------------------
interface ChangesTreeFolder {
kind: "folder";
name: string;
depth: ChangesTreeDepth;
add: string;
remove: string;
}
interface ChangesTreeFile {
kind: "file";
name: string;
depth: ChangesTreeDepth;
add: string;
remove: string;
status: ChangeStatus;
}
type ChangesTreeEntry = ChangesTreeFolder | ChangesTreeFile;
type ChangesTreeDepth = 0 | 1 | 2 | 3 | 4;
type ChangeStatus = "added" | "modified" | "deleted";
const CHANGES_TREE: ChangesTreeEntry[] = [
{ kind: "folder", name: "packages/website/src", depth: 0, add: "1.9k", remove: "684" },
{ kind: "folder", name: "components", depth: 1, add: "1.5k", remove: "512" },
{ kind: "folder", name: "mockup", depth: 2, add: "1.3k", remove: "389" },
{ kind: "folder", name: "mobile", depth: 3, add: "480", remove: "231" },
{ kind: "file", name: "atoms.tsx", depth: 4, add: "56", remove: "18", status: "modified" },
{ kind: "file", name: "chat.tsx", depth: 4, add: "155", remove: "74", status: "modified" },
{ kind: "file", name: "frame.tsx", depth: 4, add: "91", remove: "0", status: "added" },
{ kind: "file", name: "legacy-tabs.tsx", depth: 4, add: "0", remove: "139", status: "deleted" },
{ kind: "folder", name: "desktop", depth: 3, add: "612", remove: "143" },
{ kind: "file", name: "window.tsx", depth: 4, add: "167", remove: "42", status: "modified" },
{ kind: "file", name: "titlebar.tsx", depth: 4, add: "117", remove: "61", status: "modified" },
{ kind: "file", name: "toolbar.tsx", depth: 4, add: "204", remove: "0", status: "added" },
{ kind: "file", name: "old-tabs.tsx", depth: 4, add: "0", remove: "40", status: "deleted" },
{ kind: "folder", name: "shared", depth: 3, add: "194", remove: "15" },
{ kind: "file", name: "icons.tsx", depth: 4, add: "62", remove: "15", status: "modified" },
{ kind: "file", name: "geometry.ts", depth: 4, add: "17", remove: "0", status: "added" },
{ kind: "file", name: "states.ts", depth: 4, add: "115", remove: "0", status: "added" },
{ kind: "file", name: "hero-mockup.tsx", depth: 2, add: "89", remove: "52", status: "modified" },
{ kind: "folder", name: "routes", depth: 1, add: "211", remove: "96" },
{ kind: "file", name: "index.tsx", depth: 2, add: "44", remove: "70", status: "modified" },
{ kind: "file", name: "download.tsx", depth: 2, add: "167", remove: "0", status: "added" },
{ kind: "folder", name: "styles", depth: 1, add: "68", remove: "76" },
{ kind: "file", name: "mockup.css", depth: 2, add: "68", remove: "47", status: "modified" },
{ kind: "file", name: "legacy.css", depth: 2, add: "0", remove: "29", status: "deleted" },
];
/** The open workspace diff matches the selected file in the Changes tree. */
export const REVIEW_DIFF_ROWS: DiffRow[] = [
{
kind: "file",
name: "chat.tsx",
path: "packages/website/src/components/mockup/mobile",
add: "155",
remove: "74",
},
{ kind: "hunk", label: "@@ -22,5 +23,11 @@" },
{ kind: "line", n: "22", text: "const TOOL_ROWS = [", mark: "remove" },
{
kind: "line",
n: "23",
text: 'type ToolKind = "read" | "shell";',
mark: "add",
},
{ kind: "line", n: "24", text: "" },
{
kind: "line",
n: "25",
text: "interface ToolCall {",
mark: "add",
},
{ kind: "line", n: "26", text: " kind: ToolKind;", mark: "add" },
{ kind: "line", n: "27", text: " label: string;", mark: "add" },
{ kind: "line", n: "28", text: " command: string;", mark: "add" },
{ kind: "line", n: "29", text: "}", mark: "add" },
{ kind: "line", n: "30", text: "" },
{
kind: "line",
n: "31",
text: "const TOOL_CALLS: ToolCall[] = [",
mark: "add",
},
{ kind: "line", n: "32", text: " {", mark: "add" },
{ kind: "line", n: "33", text: ' ...readToolCall("docs/design.md"),', mark: "add" },
{ kind: "line", n: "34", text: " },", mark: "add" },
{ kind: "line", n: "35", text: "];", mark: "add" },
{ kind: "line", n: "36", text: "" },
{
kind: "file",
name: "titlebar.tsx",
path: "packages/website/src/components/mockup/desktop",
add: "117",
remove: "61",
},
{ kind: "hunk", label: "@@ -88,6 +92,16 @@" },
{
kind: "line",
n: "88",
text: "const AGENT_TABS: Tab[] = [",
mark: "remove",
},
{
kind: "line",
n: "92",
text: "const WORKSPACE_TABS: Tab[] = [",
mark: "add",
},
{
kind: "line",
n: "95",
text: ' { id: "terminal", label: "npm run dev", icon: <SquareTerminal /> },',
mark: "add",
},
{
kind: "line",
n: "96",
text: ' { id: "browser", label: "https://localhost:3000", icon: <Globe /> },',
mark: "add",
},
{ kind: "line", n: "97", text: "];" },
{ kind: "line", n: "98", text: "" },
{
kind: "file",
name: "window.tsx",
path: "packages/website/src/components/mockup/desktop",
add: "167",
remove: "42",
},
{ kind: "hunk", label: "@@ -118,7 +124,7 @@" },
{ kind: "line", n: "118", text: " <TitleBar state={state} />" },
{ kind: "line", n: "119", text: " <TabStrip />", mark: "remove" },
{ kind: "line", n: "124", text: " <TabStrip state={state} />", mark: "add" },
{ kind: "line", n: "125", text: ' <div className="relative min-h-0 flex-1">' },
{ kind: "line", n: "126", text: " <AnimatePresence initial={false}>" },
{
kind: "file",
name: "mockup.css",
path: "packages/website/src/styles",
add: "68",
remove: "47",
},
{ kind: "hunk", label: "@@ -54,4 +61,4 @@" },
{
kind: "line",
n: "54",
text: "--color-mock-diff-add: rgba(108, 177, 123, 0.15);",
mark: "remove",
},
{
kind: "line",
n: "61",
text: "--color-mock-diff-add: rgba(108, 177, 123, 0.08);",
mark: "add",
},
{
kind: "line",
n: "55",
text: "--color-mock-diff-remove: rgba(216, 132, 123, 0.1);",
mark: "remove",
},
{
kind: "line",
n: "62",
text: "--color-mock-diff-remove: rgba(216, 132, 123, 0.07);",
mark: "add",
},
];
const TREE_DEPTH_PADDING = [
"pl-[14px]",
"pl-[27px]",
"pl-[40px]",
"pl-[53px]",
"pl-[66px]",
] as const;
function ChangesFileIcon({ name }: { name: string }) {
if (name.endsWith(".tsx")) return <ReactFileIcon size={13} />;
const label = name.endsWith(".css") ? "CSS" : "TS";
const color = name.endsWith(".css") ? "bg-purple-400/70" : "bg-sky-400/70";
return (
<span
className={`flex h-[13px] w-[15px] shrink-0 items-center justify-center text-[6px] font-bold text-mock-surface0 ${color}`}
>
{label}
</span>
);
}
function ChangeStatusBox({ status }: { status: ChangeStatus }) {
if (status === "added") return <ExpandBox pure />;
const deleted = status === "deleted";
return (
<span
className={`flex size-[15px] shrink-0 items-center justify-center rounded-[4px] border ${
deleted
? "border-mock-danger/50 text-mock-danger"
: "border-mock-warning/50 text-mock-warning"
}`}
>
{deleted ? (
<Minus size={11} strokeWidth={2.4} />
) : (
<i className="block size-[3px] rounded-full bg-current" />
)}
</span>
);
}
function ChangesTreeRow({ entry, selected }: { entry: ChangesTreeEntry; selected: boolean }) {
const hasAdditions = entry.add !== "0";
const hasRemovals = entry.remove !== "0";
return (
<div
className={`flex h-[29px] items-center gap-[8px] pr-[10px] ${TREE_DEPTH_PADDING[entry.depth]} ${
selected ? "bg-mock-surface2" : ""
}`}
>
{entry.kind === "folder" ? (
<ChevronDown size={13} className="shrink-0 text-mock-fg-muted" />
) : null}
{entry.kind === "file" ? <ChangesFileIcon name={entry.name} /> : null}
<span className="min-w-0 flex-1 truncate text-[12px] text-mock-fg">{entry.name}</span>
<span className="flex shrink-0 items-center gap-[4px] text-[11px] tabular-nums">
<span className={hasAdditions ? "text-mock-success" : "text-mock-fg-xmuted"}>
+{entry.add}
</span>
<span className={hasRemovals ? "text-mock-danger" : "text-mock-fg-xmuted"}>
-{entry.remove}
</span>
</span>
{entry.kind === "file" ? (
<ChangeStatusBox status={entry.status} />
) : (
<span className="size-[15px] shrink-0" />
)}
</div>
);
}
export function ExplorerChangesTree({ selectedFile }: { selectedFile?: string }) {
return (
<div className="min-h-0 flex-1 overflow-hidden">
{CHANGES_TREE.map((entry) => (
<ChangesTreeRow
key={`${entry.depth}:${entry.name}`}
entry={entry}
selected={entry.kind === "file" && entry.name === selectedFile}
/>
))}
</div>
);
}
export function ChangesBranchRow() {
return (
<div
className="flex shrink-0 items-center gap-[6px] border-b border-mock-border px-[14px] text-[12px] text-mock-fg-muted"
style={SUBHEADER_STYLE}
>
main
<ChevronDown size={12} />
</div>
);
}
export function ChangesScopeRow() {
return (
<div className="flex h-[36px] shrink-0 items-center border-b border-mock-border px-[14px]">
<span className="flex items-center gap-[5px] text-[12px] text-mock-fg-muted">
Uncommitted
<ChevronDown size={12} />
</span>
<span className="ml-[9px] flex items-center gap-[5px] text-[11.5px] tabular-nums">
<span className="text-mock-success">+1.9k</span>
<span className="text-mock-danger">-684</span>
</span>
<span className="flex-1" />
<span className="flex items-center gap-[12px] text-mock-fg-muted">
<RefreshCw size={13} />
<Ellipsis size={14} />
</span>
</div>
);
}
export function ChangesCommitsRow() {
return (
<div className="flex h-[36px] shrink-0 items-center gap-[8px] border-t border-mock-border px-[10px] text-[12px] text-mock-fg">
<ChevronRight size={13} className="text-mock-fg-muted" />
Commits
</div>
);
}
/** The hunk shown when the first review file is expanded. */
export const PROVIDERS_HUNK: DiffRow[] = [
{ kind: "hunk", label: "@@ -75,7 +75,9 @@" },
{ kind: "line", n: "75", text: "" },
{
kind: "line",
n: "76",
text: "Pi RPC extension UI dialog requests (`select`, `input`, `editor`, `confirm`) are bridged",
},
{ kind: "line", n: "77", text: "" },
{
kind: "line",
n: "78",
text: "OpenCode MCP injection is dynamic and session-scoped. Call OpenCode's `mcp.add` endpoint",
mark: "remove",
},
];
/** The hunk continues below the inline comment box. */
export const PROVIDERS_HUNK_TAIL: DiffRow[] = [
{
kind: "line",
n: "78",
text: "OpenCode 1 keeps MCP and process environment outside the session boundary. Paseo shares",
mark: "add",
},
{ kind: "line", n: "79", text: "", mark: "add" },
{
kind: "line",
n: "80",
text: "An agent with custom environment variables or user-configured MCP servers gets a server",
mark: "add",
},
{ kind: "line", n: "81", text: "" },
{
kind: "line",
n: "82",
text: "OpenCode owns user message IDs. Do not pass Paseo-generated IDs to OpenCode prompt APIs",
},
{ kind: "line", n: "83", text: "" },
];
// --- content ----------------------------------------------------------------
export const UNCOMMITTED_ROWS: DiffRow[] = [
{ kind: "file", name: "alert.tsx", add: "12", remove: "18" },
{ kind: "line", n: "49", text: " <div" },
{ kind: "line", n: "50", text: ' data-slot="alert-description"' },
{ kind: "line", n: "51", text: " className={cn(" },
{
kind: "line",
n: "52",
text: ' "col-start-2 grid justify-items-start gap-1 text-sm tex',
},
{ kind: "line", n: "59", text: " className", mark: "remove" },
{ kind: "line", n: "53", text: " className,", mark: "add" },
{ kind: "line", n: "54", text: " )}" },
{ kind: "line", n: "55", text: " {...props}" },
{ kind: "line", n: "56", text: " />" },
{ kind: "line", n: "63", text: " )", mark: "remove" },
{ kind: "line", n: "57", text: " );", mark: "add" },
{ kind: "line", n: "58", text: "}" },
{ kind: "line", n: "59", text: "" },
{
kind: "line",
n: "66",
text: "export { Alert, AlertTitle, AlertDescription }",
mark: "remove",
},
{ kind: "line", n: "60", text: "export { Alert, AlertTitle, AlertDescription };", mark: "add" },
{ kind: "scrollbar" },
{ kind: "file", name: "badge.tsx", add: "11", remove: "13" },
{ kind: "file", name: "button.tsx", add: "12", remove: "14" },
{ kind: "file", name: "card.tsx", add: "13", remove: "30" },
{ kind: "hunk", label: "@@ -1,6 +1,6 @@" },
{ kind: "line", n: "1", text: 'import * as React from "react"', mark: "remove" },
{ kind: "line", n: "1", text: 'import * as React from "react";', mark: "add" },
{ kind: "line", n: "2", text: "" },
{ kind: "line", n: "3", text: 'import { cn } from "@/lib/utils"', mark: "remove" },
{ kind: "line", n: "3", text: 'import { cn } from "../../lib/utils.js";', mark: "add" },
{ kind: "line", n: "4", text: "" },
{
kind: "line",
n: "5",
text: 'function Card({ className, ...props }: React.ComponentProps<"di',
},
{ kind: "line", n: "6", text: " return (" },
{ kind: "hunk", label: "@@ -8,11 +8,11 @@" },
{ kind: "line", n: "8", text: ' data-slot="card"' },
{ kind: "line", n: "9", text: " className={cn(" },
{
kind: "line",
n: "10",
text: ' "flex flex-col gap-6 rounded-xl border bg-card py-6 tex',
},
{ kind: "line", n: "11", text: " className", mark: "remove" },
{ kind: "line", n: "11", text: " className,", mark: "add" },
{ kind: "line", n: "12", text: " )}" },
{ kind: "line", n: "13", text: " {...props}" },
{ kind: "line", n: "14", text: " />" },
{ kind: "line", n: "15", text: " )", mark: "remove" },
{ kind: "line", n: "15", text: " );", mark: "add" },
{ kind: "line", n: "16", text: "}" },
{ kind: "line", n: "17", text: "" },
];
/** Same change set, room to breathe — used by the wide review pane. */
export const REVIEW_ROWS: DiffRow[] = [
{ kind: "hunk", label: "@@ -1,6 +1,6 @@" },
{ kind: "line", n: "1", text: 'import * as React from "react"', mark: "remove" },
{ kind: "line", n: "1", text: 'import * as React from "react";', mark: "add" },
{ kind: "line", n: "2", text: "" },
{ kind: "line", n: "3", text: 'import { cn } from "@/lib/utils"', mark: "remove" },
{ kind: "line", n: "3", text: 'import { cn } from "../../lib/utils.js";', mark: "add" },
{ kind: "line", n: "4", text: "" },
{
kind: "line",
n: "5",
text: 'function Card({ className, ...props }: React.ComponentProps<"div">) {',
},
{ kind: "line", n: "6", text: " return (" },
{ kind: "line", n: "7", text: " <div" },
{ kind: "hunk", label: "@@ -8,15 +8,15 @@ function Card({ className, ...props })" },
{ kind: "line", n: "8", text: ' data-slot="card"' },
{ kind: "line", n: "9", text: " className={cn(" },
{
kind: "line",
n: "10",
text: ' "flex flex-col gap-6 rounded-xl border bg-card py-6 text-card-foreground",',
},
{ kind: "line", n: "11", text: " className", mark: "remove" },
{ kind: "line", n: "11", text: " className,", mark: "add" },
{ kind: "line", n: "12", text: " )}" },
{ kind: "line", n: "13", text: " {...props}" },
{ kind: "line", n: "14", text: " />" },
{ kind: "line", n: "15", text: " )", mark: "remove" },
{ kind: "line", n: "15", text: " );", mark: "add" },
{ kind: "line", n: "16", text: "}" },
{ kind: "line", n: "17", text: "" },
{ kind: "hunk", label: "@@ -21,9 +21,9 @@ function CardHeader({ className, ...props })" },
{ kind: "line", n: "21", text: "function CardHeader({ className, ...props }) {" },
{ kind: "line", n: "22", text: " return (" },
{ kind: "line", n: "23", text: " <div" },
{ kind: "line", n: "24", text: ' data-slot="card-header"' },
{
kind: "line",
n: "25",
text: ' className={cn("grid auto-rows-min items-start", className)}',
mark: "remove",
},
{
kind: "line",
n: "25",
text: ' className={cn("grid auto-rows-min items-start gap-1.5", className)}',
mark: "add",
},
{ kind: "line", n: "26", text: " {...props}" },
{ kind: "line", n: "27", text: " />" },
{ kind: "line", n: "28", text: " );" },
{ kind: "line", n: "29", text: "}" },
{ kind: "line", n: "30", text: "" },
{ kind: "hunk", label: "@@ -36,12 +36,12 @@ function CardTitle({ className, ...props })" },
{ kind: "line", n: "36", text: "function CardTitle({ className, ...props }) {" },
{ kind: "line", n: "37", text: " return (" },
{ kind: "line", n: "38", text: " <div" },
{ kind: "line", n: "39", text: ' data-slot="card-title"' },
{
kind: "line",
n: "40",
text: ' className={cn("leading-none", className)}',
mark: "remove",
},
{
kind: "line",
n: "40",
text: ' className={cn("leading-none font-semibold", className)}',
mark: "add",
},
{ kind: "line", n: "41", text: " {...props}" },
{ kind: "line", n: "42", text: " />" },
{ kind: "line", n: "43", text: " )", mark: "remove" },
{ kind: "line", n: "43", text: " );", mark: "add" },
{ kind: "line", n: "44", text: "}" },
{ kind: "line", n: "45", text: "" },
{
kind: "line",
n: "46",
text: "export { Card, CardHeader, CardTitle, CardContent }",
mark: "remove",
},
{
kind: "line",
n: "46",
text: "export { Card, CardHeader, CardTitle, CardContent };",
mark: "add",
},
];
export const REVIEW_FILES: {
name: string;
path: string;
add: string;
remove: string;
active?: boolean;
}[] = [
{ name: "alert.tsx", path: "components/ui", add: "12", remove: "18" },
{ name: "badge.tsx", path: "components/ui", add: "11", remove: "13" },
{ name: "button.tsx", path: "components/ui", add: "12", remove: "14" },
{ name: "card.tsx", path: "components/ui", add: "13", remove: "30", active: true },
{ name: "sidebar.tsx", path: "components/ui", add: "48", remove: "9" },
{ name: "utils.ts", path: "lib", add: "3", remove: "1" },
];