diff --git a/apps/server/package.json b/apps/server/package.json index 28fd3a2f8bdaa1e08b48233ab69e1a1a482b317c..484f5877f6c95e569ad2018891f8a0e2b6cc6940 100644 --- a/apps/server/package.json +++ b/apps/server/package.json @@ -29,7 +29,7 @@ "@effect/platform-node": "catalog:", "@effect/sql-sqlite-bun": "catalog:", "@opencode-ai/sdk": "^1.3.15", - "@pierre/diffs": "^1.1.0-beta.16", + "@pierre/diffs": "catalog:", "effect": "catalog:", "node-pty": "^1.1.0", "open": "^10.1.0" diff --git a/apps/web/package.json b/apps/web/package.json index 2996104e9fc3e87e5f811f3fdfdf639c8f431ac1..dac78ec77006202c660f0c3ae312908c0886aafc 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -22,7 +22,7 @@ "@formkit/auto-animate": "^0.9.0", "@legendapp/list": "3.0.0-beta.44", "@lexical/react": "^0.41.0", - "@pierre/diffs": "^1.1.0-beta.16", + "@pierre/diffs": "catalog:", "@t3tools/client-runtime": "workspace:*", "@t3tools/contracts": "workspace:*", "@t3tools/shared": "workspace:*", diff --git a/apps/web/src/components/DiffPanel.tsx b/apps/web/src/components/DiffPanel.tsx index 28db53e917ef349344819cf363da05b210bb532d..f178a69fb43a286a5d6ef6ce799a87ea123bf8a7 100644 --- a/apps/web/src/components/DiffPanel.tsx +++ b/apps/web/src/components/DiffPanel.tsx @@ -5,6 +5,7 @@ import { useNavigate, useParams, useSearch } from "@tanstack/react-router"; import { scopeThreadRef } from "@t3tools/client-runtime"; import type { TurnId } from "@t3tools/contracts"; import { + ChevronDownIcon, ChevronLeftIcon, ChevronRightIcon, Columns2Icon, @@ -161,6 +162,21 @@ function buildFileDiffRenderKey(fileDiff: FileDiffMetadata): string { return fileDiff.cacheKey ?? `${fileDiff.prevName ?? "none"}:${fileDiff.name}`; } +function getDiffCollapseIconClassName(fileDiff: FileDiffMetadata): string { + switch (fileDiff.type) { + case "new": + return "text-[var(--diffs-addition-base)]"; + case "deleted": + return "text-[var(--diffs-deletion-base)]"; + case "change": + case "rename-pure": + case "rename-changed": + return "text-[var(--diffs-modified-base)]"; + default: + return "text-muted-foreground/80"; + } +} + interface DiffPanelProps { mode?: DiffPanelMode; } @@ -174,6 +190,9 @@ export default function DiffPanel({ mode = "inline" }: DiffPanelProps) { const [diffRenderMode, setDiffRenderMode] = useState("stacked"); const [diffWordWrap, setDiffWordWrap] = useState(settings.diffWordWrap); const [diffIgnoreWhitespace, setDiffIgnoreWhitespace] = useState(settings.diffIgnoreWhitespace); + const [collapsedDiffFileKeys, setCollapsedDiffFileKeys] = useState>( + () => new Set(), + ); const patchViewportRef = useRef(null); const turnStripRef = useRef(null); const previousDiffOpenRef = useRef(false); @@ -317,6 +336,19 @@ export default function DiffPanel({ mode = "inline" }: DiffPanelProps) { ); }, [renderablePatch]); + useEffect(() => { + if (renderableFiles.length === 0) { + setCollapsedDiffFileKeys((current) => (current.size === 0 ? current : new Set())); + return; + } + + const visibleFileKeys = new Set(renderableFiles.map(buildFileDiffRenderKey)); + setCollapsedDiffFileKeys((current) => { + const next = new Set([...current].filter((fileKey) => visibleFileKeys.has(fileKey))); + return next.size === current.size ? current : next; + }); + }, [renderableFiles]); + useEffect(() => { if (diffOpen && !previousDiffOpenRef.current) { setDiffWordWrap(settings.diffWordWrap); @@ -346,6 +378,17 @@ export default function DiffPanel({ mode = "inline" }: DiffPanelProps) { }, [activeCwd], ); + const toggleDiffFileCollapsed = useCallback((fileKey: string) => { + setCollapsedDiffFileKeys((current) => { + const next = new Set(current); + if (next.has(fileKey)) { + next.delete(fileKey); + } else { + next.add(fileKey); + } + return next; + }); + }, []); const selectTurn = (turnId: TurnId) => { if (!activeThread) return; @@ -620,11 +663,12 @@ export default function DiffPanel({ mode = "inline" }: DiffPanelProps) { const filePath = resolveFileDiffPath(fileDiff); const fileKey = buildFileDiffRenderKey(fileDiff); const themedFileKey = `${fileKey}:${resolvedTheme}`; + const collapsed = collapsedDiffFileKeys.has(fileKey); return (
{ const nativeEvent = event.nativeEvent as MouseEvent; const composedPath = nativeEvent.composedPath?.() ?? []; @@ -638,7 +682,30 @@ export default function DiffPanel({ mode = "inline" }: DiffPanelProps) { > ( + + )} options={{ + collapsed, diffStyle: diffRenderMode === "split" ? "split" : "unified", lineDiffType: "none", overflow: diffWordWrap ? "wrap" : "scroll", diff --git a/apps/web/src/routes/_chat.$environmentId.$threadId.tsx b/apps/web/src/routes/_chat.$environmentId.$threadId.tsx index dc1abed8a0be5bbef526ab555b6cc8be9cc2006a..1877fee6f7d7dbdb80fba86e8b3113b5fe3962ba 100644 --- a/apps/web/src/routes/_chat.$environmentId.$threadId.tsx +++ b/apps/web/src/routes/_chat.$environmentId.$threadId.tsx @@ -28,7 +28,7 @@ const DiffPanel = lazy(() => import("../components/DiffPanel")); const DIFF_INLINE_SIDEBAR_WIDTH_STORAGE_KEY = "chat_diff_sidebar_width"; const DIFF_INLINE_DEFAULT_WIDTH = "clamp(24rem,34vw,36rem)"; const DIFF_INLINE_SIDEBAR_MIN_WIDTH = 22 * 16; -const DIFF_INLINE_SIDEBAR_MAX_WIDTH = 36 * 16; +const DIFF_INLINE_SIDEBAR_MAX_WIDTH = 256 * 16; const COMPOSER_COMPACT_MIN_LEFT_CONTROLS_WIDTH_PX = 208; const DiffLoadingFallback = (props: { mode: DiffPanelMode }) => { diff --git a/bun.lock b/bun.lock index e94e5c6c03ab91ed7fd9f65099f1be5074746e38..a87ac77094b7ebdb99b1bd46dbdb900fad0c3fd8 100644 --- a/bun.lock +++ b/bun.lock @@ -59,7 +59,7 @@ "@effect/platform-node": "catalog:", "@effect/sql-sqlite-bun": "catalog:", "@opencode-ai/sdk": "^1.3.15", - "@pierre/diffs": "^1.1.0-beta.16", + "@pierre/diffs": "catalog:", "effect": "catalog:", "node-pty": "^1.1.0", "open": "^10.1.0", @@ -93,7 +93,7 @@ "@formkit/auto-animate": "^0.9.0", "@legendapp/list": "3.0.0-beta.44", "@lexical/react": "^0.41.0", - "@pierre/diffs": "^1.1.0-beta.16", + "@pierre/diffs": "catalog:", "@t3tools/client-runtime": "workspace:*", "@t3tools/contracts": "workspace:*", "@t3tools/shared": "workspace:*", @@ -281,6 +281,7 @@ "@effect/platform-node-shared": "4.0.0-beta.59", "@effect/sql-sqlite-bun": "4.0.0-beta.59", "@effect/vitest": "4.0.0-beta.59", + "@pierre/diffs": "1.1.20", "@types/bun": "^1.3.11", "@types/node": "^24.10.13", "effect": "4.0.0-beta.59", @@ -701,9 +702,9 @@ "@oxlint/binding-win32-x64-msvc": ["@oxlint/binding-win32-x64-msvc@1.56.0", "", { "os": "win32", "cpu": "x64" }, "sha512-ZHa0clocjLmIDr+1LwoWtxRcoYniAvERotvwKUYKhH41NVfl0Y4LNbyQkwMZzwDvKklKGvGZ5+DAG58/Ik47tQ=="], - "@pierre/diffs": ["@pierre/diffs@1.1.0", "", { "dependencies": { "@pierre/theme": "0.0.22", "@shikijs/transformers": "^3.0.0", "diff": "8.0.3", "hast-util-to-html": "9.0.5", "lru_map": "0.4.1", "shiki": "^3.0.0" }, "peerDependencies": { "react": "^18.3.1 || ^19.0.0", "react-dom": "^18.3.1 || ^19.0.0" } }, "sha512-wbxrzcmanJuHZb81iir09j42uU9AnKxXDtAuEQJbAnti5f2UfYdCQYejawuHZStFrlsMacCZLh/dDHmqvAaQCw=="], + "@pierre/diffs": ["@pierre/diffs@1.1.20", "", { "dependencies": { "@pierre/theme": "0.0.28", "@shikijs/transformers": "^3.0.0", "diff": "8.0.3", "hast-util-to-html": "9.0.5", "lru_map": "0.4.1", "shiki": "^3.0.0" }, "peerDependencies": { "react": "^18.3.1 || ^19.0.0", "react-dom": "^18.3.1 || ^19.0.0" } }, "sha512-lLi+3sLCm3QDd5/aLO9pw+WbF6UzhrkWm2oTZ5WZJTGemOyUNRJ4DDhcEKmVusu4C4bXx9Nssh6fF+wQcapb5w=="], - "@pierre/theme": ["@pierre/theme@0.0.22", "", {}, "sha512-ePUIdQRNGjrveELTU7fY89Xa7YGHHEy5Po5jQy/18lm32eRn96+tnYJEtFooGdffrx55KBUtOXfvVy/7LDFFhA=="], + "@pierre/theme": ["@pierre/theme@0.0.28", "", {}, "sha512-1j/H/fECBuc9dEvntdWI+l435HZapw+RCJTlqCA6BboQ5TjlnE005j/ROWutXIs8aq5OAc82JI2Kwk4A1WWBgw=="], "@polka/url": ["@polka/url@1.0.0-next.29", "", {}, "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww=="], diff --git a/package.json b/package.json index 244ab36e066a9bb198e1a14df8946b5316e3fb9c..82be8bdf927a1cb2039c774838641f5fdf175fdc 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "@effect/sql-sqlite-bun": "4.0.0-beta.59", "@effect/vitest": "4.0.0-beta.59", "@effect/language-service": "0.84.2", + "@pierre/diffs": "1.1.20", "@types/bun": "^1.3.11", "@types/node": "^24.10.13", "tsdown": "^0.20.3",