| author | |
| committer | |
| log | f4c9418d40358f61e4e9236992c0620d06c4bea3 |
| tree | 18b395a6b9514593d4e477d4c7c29bf26fe8675e |
| parent | 02dd47ea3185ae46e11d0b9e584dc5afc84a7458 |
| signature | Signed by PGP key B5690EEEBB952194 |
Co-authored-by: Julius Marminge <julius0216@outlook.com>6 files changed, 77 insertions(+), 8 deletions(-)
apps/server/package.json+1-1| ... | @@ -29,7 +29,7 @@ | ... | @@ -29,7 +29,7 @@ |
| 29 | "@effect/platform-node": "catalog:", | 29 | "@effect/platform-node": "catalog:", |
| 30 | "@effect/sql-sqlite-bun": "catalog:", | 30 | "@effect/sql-sqlite-bun": "catalog:", |
| 31 | "@opencode-ai/sdk": "^1.3.15", | 31 | "@opencode-ai/sdk": "^1.3.15", |
| 32 | "@pierre/diffs": "^1.1.0-beta.16", | 32 | "@pierre/diffs": "catalog:", |
| 33 | "effect": "catalog:", | 33 | "effect": "catalog:", |
| 34 | "node-pty": "^1.1.0", | 34 | "node-pty": "^1.1.0", |
| 35 | "open": "^10.1.0" | 35 | "open": "^10.1.0" |
apps/web/package.json+1-1| ... | @@ -22,7 +22,7 @@ | ... | @@ -22,7 +22,7 @@ |
| 22 | "@formkit/auto-animate": "^0.9.0", | 22 | "@formkit/auto-animate": "^0.9.0", |
| 23 | "@legendapp/list": "3.0.0-beta.44", | 23 | "@legendapp/list": "3.0.0-beta.44", |
| 24 | "@lexical/react": "^0.41.0", | 24 | "@lexical/react": "^0.41.0", |
| 25 | "@pierre/diffs": "^1.1.0-beta.16", | 25 | "@pierre/diffs": "catalog:", |
| 26 | "@t3tools/client-runtime": "workspace:*", | 26 | "@t3tools/client-runtime": "workspace:*", |
| 27 | "@t3tools/contracts": "workspace:*", | 27 | "@t3tools/contracts": "workspace:*", |
| 28 | "@t3tools/shared": "workspace:*", | 28 | "@t3tools/shared": "workspace:*", |
apps/web/src/components/DiffPanel.tsx+68-1| ... | @@ -5,6 +5,7 @@ import { useNavigate, useParams, useSearch } from "@tanstack/react-router"; | ... | @@ -5,6 +5,7 @@ import { useNavigate, useParams, useSearch } from "@tanstack/react-router"; |
| 5 | import { scopeThreadRef } from "@t3tools/client-runtime"; | 5 | import { scopeThreadRef } from "@t3tools/client-runtime"; |
| 6 | import type { TurnId } from "@t3tools/contracts"; | 6 | import type { TurnId } from "@t3tools/contracts"; |
| 7 | import { | 7 | import { |
| 8 | ChevronDownIcon, | ||
| 8 | ChevronLeftIcon, | 9 | ChevronLeftIcon, |
| 9 | ChevronRightIcon, | 10 | ChevronRightIcon, |
| 10 | Columns2Icon, | 11 | Columns2Icon, |
| ... | @@ -161,6 +162,21 @@ function buildFileDiffRenderKey(fileDiff: FileDiffMetadata): string { | ... | @@ -161,6 +162,21 @@ function buildFileDiffRenderKey(fileDiff: FileDiffMetadata): string { |
| 161 | return fileDiff.cacheKey ?? `${fileDiff.prevName ?? "none"}:${fileDiff.name}`; | 162 | return fileDiff.cacheKey ?? `${fileDiff.prevName ?? "none"}:${fileDiff.name}`; |
| 162 | } | 163 | } |
| 163 | 164 | ||
| 165 | function getDiffCollapseIconClassName(fileDiff: FileDiffMetadata): string { | ||
| 166 | switch (fileDiff.type) { | ||
| 167 | case "new": | ||
| 168 | return "text-[var(--diffs-addition-base)]"; | ||
| 169 | case "deleted": | ||
| 170 | return "text-[var(--diffs-deletion-base)]"; | ||
| 171 | case "change": | ||
| 172 | case "rename-pure": | ||
| 173 | case "rename-changed": | ||
| 174 | return "text-[var(--diffs-modified-base)]"; | ||
| 175 | default: | ||
| 176 | return "text-muted-foreground/80"; | ||
| 177 | } | ||
| 178 | } | ||
| 179 | |||
| 164 | interface DiffPanelProps { | 180 | interface DiffPanelProps { |
| 165 | mode?: DiffPanelMode; | 181 | mode?: DiffPanelMode; |
| 166 | } | 182 | } |
| ... | @@ -174,6 +190,9 @@ export default function DiffPanel({ mode = "inline" }: DiffPanelProps) { | ... | @@ -174,6 +190,9 @@ export default function DiffPanel({ mode = "inline" }: DiffPanelProps) { |
| 174 | const [diffRenderMode, setDiffRenderMode] = useState<DiffRenderMode>("stacked"); | 190 | const [diffRenderMode, setDiffRenderMode] = useState<DiffRenderMode>("stacked"); |
| 175 | const [diffWordWrap, setDiffWordWrap] = useState(settings.diffWordWrap); | 191 | const [diffWordWrap, setDiffWordWrap] = useState(settings.diffWordWrap); |
| 176 | const [diffIgnoreWhitespace, setDiffIgnoreWhitespace] = useState(settings.diffIgnoreWhitespace); | 192 | const [diffIgnoreWhitespace, setDiffIgnoreWhitespace] = useState(settings.diffIgnoreWhitespace); |
| 193 | const [collapsedDiffFileKeys, setCollapsedDiffFileKeys] = useState<ReadonlySet<string>>( | ||
| 194 | () => new Set(), | ||
| 195 | ); | ||
| 177 | const patchViewportRef = useRef<HTMLDivElement>(null); | 196 | const patchViewportRef = useRef<HTMLDivElement>(null); |
| 178 | const turnStripRef = useRef<HTMLDivElement>(null); | 197 | const turnStripRef = useRef<HTMLDivElement>(null); |
| 179 | const previousDiffOpenRef = useRef(false); | 198 | const previousDiffOpenRef = useRef(false); |
| ... | @@ -317,6 +336,19 @@ export default function DiffPanel({ mode = "inline" }: DiffPanelProps) { | ... | @@ -317,6 +336,19 @@ export default function DiffPanel({ mode = "inline" }: DiffPanelProps) { |
| 317 | ); | 336 | ); |
| 318 | }, [renderablePatch]); | 337 | }, [renderablePatch]); |
| 319 | 338 | ||
| 339 | useEffect(() => { | ||
| 340 | if (renderableFiles.length === 0) { | ||
| 341 | setCollapsedDiffFileKeys((current) => (current.size === 0 ? current : new Set())); | ||
| 342 | return; | ||
| 343 | } | ||
| 344 | |||
| 345 | const visibleFileKeys = new Set(renderableFiles.map(buildFileDiffRenderKey)); | ||
| 346 | setCollapsedDiffFileKeys((current) => { | ||
| 347 | const next = new Set([...current].filter((fileKey) => visibleFileKeys.has(fileKey))); | ||
| 348 | return next.size === current.size ? current : next; | ||
| 349 | }); | ||
| 350 | }, [renderableFiles]); | ||
| 351 | |||
| 320 | useEffect(() => { | 352 | useEffect(() => { |
| 321 | if (diffOpen && !previousDiffOpenRef.current) { | 353 | if (diffOpen && !previousDiffOpenRef.current) { |
| 322 | setDiffWordWrap(settings.diffWordWrap); | 354 | setDiffWordWrap(settings.diffWordWrap); |
| ... | @@ -346,6 +378,17 @@ export default function DiffPanel({ mode = "inline" }: DiffPanelProps) { | ... | @@ -346,6 +378,17 @@ export default function DiffPanel({ mode = "inline" }: DiffPanelProps) { |
| 346 | }, | 378 | }, |
| 347 | [activeCwd], | 379 | [activeCwd], |
| 348 | ); | 380 | ); |
| 381 | const toggleDiffFileCollapsed = useCallback((fileKey: string) => { | ||
| 382 | setCollapsedDiffFileKeys((current) => { | ||
| 383 | const next = new Set(current); | ||
| 384 | if (next.has(fileKey)) { | ||
| 385 | next.delete(fileKey); | ||
| 386 | } else { | ||
| 387 | next.add(fileKey); | ||
| 388 | } | ||
| 389 | return next; | ||
| 390 | }); | ||
| 391 | }, []); | ||
| 349 | 392 | ||
| 350 | const selectTurn = (turnId: TurnId) => { | 393 | const selectTurn = (turnId: TurnId) => { |
| 351 | if (!activeThread) return; | 394 | if (!activeThread) return; |
| ... | @@ -620,11 +663,12 @@ export default function DiffPanel({ mode = "inline" }: DiffPanelProps) { | ... | @@ -620,11 +663,12 @@ export default function DiffPanel({ mode = "inline" }: DiffPanelProps) { |
| 620 | const filePath = resolveFileDiffPath(fileDiff); | 663 | const filePath = resolveFileDiffPath(fileDiff); |
| 621 | const fileKey = buildFileDiffRenderKey(fileDiff); | 664 | const fileKey = buildFileDiffRenderKey(fileDiff); |
| 622 | const themedFileKey = `${fileKey}:${resolvedTheme}`; | 665 | const themedFileKey = `${fileKey}:${resolvedTheme}`; |
| 666 | const collapsed = collapsedDiffFileKeys.has(fileKey); | ||
| 623 | return ( | 667 | return ( |
| 624 | <div | 668 | <div |
| 625 | key={themedFileKey} | 669 | key={themedFileKey} |
| 626 | data-diff-file-path={filePath} | 670 | data-diff-file-path={filePath} |
| 627 | className="diff-render-file mb-2 rounded-md first:mt-2 last:mb-0" | 671 | className="diff-render-file group/diff-file mb-2 rounded-md first:mt-2 last:mb-0" |
| 628 | onClickCapture={(event) => { | 672 | onClickCapture={(event) => { |
| 629 | const nativeEvent = event.nativeEvent as MouseEvent; | 673 | const nativeEvent = event.nativeEvent as MouseEvent; |
| 630 | const composedPath = nativeEvent.composedPath?.() ?? []; | 674 | const composedPath = nativeEvent.composedPath?.() ?? []; |
| ... | @@ -638,7 +682,30 @@ export default function DiffPanel({ mode = "inline" }: DiffPanelProps) { | ... | @@ -638,7 +682,30 @@ export default function DiffPanel({ mode = "inline" }: DiffPanelProps) { |
| 638 | > | 682 | > |
| 639 | <FileDiff | 683 | <FileDiff |
| 640 | fileDiff={fileDiff} | 684 | fileDiff={fileDiff} |
| 685 | renderHeaderPrefix={() => ( | ||
| 686 | <button | ||
| 687 | type="button" | ||
| 688 | className={cn( | ||
| 689 | "inline-flex size-5 shrink-0 cursor-pointer items-center justify-center rounded-sm border-0 bg-transparent p-0 transition-colors hover:bg-foreground/10 focus-visible:outline-hidden", | ||
| 690 | getDiffCollapseIconClassName(fileDiff), | ||
| 691 | )} | ||
| 692 | aria-label={collapsed ? `Expand ${filePath}` : `Collapse ${filePath}`} | ||
| 693 | aria-expanded={!collapsed} | ||
| 694 | title={collapsed ? "Expand diff" : "Collapse diff"} | ||
| 695 | onClick={(event) => { | ||
| 696 | event.stopPropagation(); | ||
| 697 | toggleDiffFileCollapsed(fileKey); | ||
| 698 | }} | ||
| 699 | > | ||
| 700 | {collapsed ? ( | ||
| 701 | <ChevronRightIcon className="size-4" /> | ||
| 702 | ) : ( | ||
| 703 | <ChevronDownIcon className="size-4" /> | ||
| 704 | )} | ||
| 705 | </button> | ||
| 706 | )} | ||
| 641 | options={{ | 707 | options={{ |
| 708 | collapsed, | ||
| 642 | diffStyle: diffRenderMode === "split" ? "split" : "unified", | 709 | diffStyle: diffRenderMode === "split" ? "split" : "unified", |
| 643 | lineDiffType: "none", | 710 | lineDiffType: "none", |
| 644 | overflow: diffWordWrap ? "wrap" : "scroll", | 711 | overflow: diffWordWrap ? "wrap" : "scroll", |
apps/web/src/routes/_chat.$environmentId.$threadId.tsx+1-1| ... | @@ -28,7 +28,7 @@ const DiffPanel = lazy(() => import("../components/DiffPanel")); | ... | @@ -28,7 +28,7 @@ const DiffPanel = lazy(() => import("../components/DiffPanel")); |
| 28 | const DIFF_INLINE_SIDEBAR_WIDTH_STORAGE_KEY = "chat_diff_sidebar_width"; | 28 | const DIFF_INLINE_SIDEBAR_WIDTH_STORAGE_KEY = "chat_diff_sidebar_width"; |
| 29 | const DIFF_INLINE_DEFAULT_WIDTH = "clamp(24rem,34vw,36rem)"; | 29 | const DIFF_INLINE_DEFAULT_WIDTH = "clamp(24rem,34vw,36rem)"; |
| 30 | const DIFF_INLINE_SIDEBAR_MIN_WIDTH = 22 * 16; | 30 | const DIFF_INLINE_SIDEBAR_MIN_WIDTH = 22 * 16; |
| 31 | const DIFF_INLINE_SIDEBAR_MAX_WIDTH = 36 * 16; | 31 | const DIFF_INLINE_SIDEBAR_MAX_WIDTH = 256 * 16; |
| 32 | const COMPOSER_COMPACT_MIN_LEFT_CONTROLS_WIDTH_PX = 208; | 32 | const COMPOSER_COMPACT_MIN_LEFT_CONTROLS_WIDTH_PX = 208; |
| 33 | 33 | ||
| 34 | const DiffLoadingFallback = (props: { mode: DiffPanelMode }) => { | 34 | const DiffLoadingFallback = (props: { mode: DiffPanelMode }) => { |
bun.lock+5-4| ... | @@ -59,7 +59,7 @@ | ... | @@ -59,7 +59,7 @@ |
| 59 | "@effect/platform-node": "catalog:", | 59 | "@effect/platform-node": "catalog:", |
| 60 | "@effect/sql-sqlite-bun": "catalog:", | 60 | "@effect/sql-sqlite-bun": "catalog:", |
| 61 | "@opencode-ai/sdk": "^1.3.15", | 61 | "@opencode-ai/sdk": "^1.3.15", |
| 62 | "@pierre/diffs": "^1.1.0-beta.16", | 62 | "@pierre/diffs": "catalog:", |
| 63 | "effect": "catalog:", | 63 | "effect": "catalog:", |
| 64 | "node-pty": "^1.1.0", | 64 | "node-pty": "^1.1.0", |
| 65 | "open": "^10.1.0", | 65 | "open": "^10.1.0", |
| ... | @@ -93,7 +93,7 @@ | ... | @@ -93,7 +93,7 @@ |
| 93 | "@formkit/auto-animate": "^0.9.0", | 93 | "@formkit/auto-animate": "^0.9.0", |
| 94 | "@legendapp/list": "3.0.0-beta.44", | 94 | "@legendapp/list": "3.0.0-beta.44", |
| 95 | "@lexical/react": "^0.41.0", | 95 | "@lexical/react": "^0.41.0", |
| 96 | "@pierre/diffs": "^1.1.0-beta.16", | 96 | "@pierre/diffs": "catalog:", |
| 97 | "@t3tools/client-runtime": "workspace:*", | 97 | "@t3tools/client-runtime": "workspace:*", |
| 98 | "@t3tools/contracts": "workspace:*", | 98 | "@t3tools/contracts": "workspace:*", |
| 99 | "@t3tools/shared": "workspace:*", | 99 | "@t3tools/shared": "workspace:*", |
| ... | @@ -281,6 +281,7 @@ | ... | @@ -281,6 +281,7 @@ |
| 281 | "@effect/platform-node-shared": "4.0.0-beta.59", | 281 | "@effect/platform-node-shared": "4.0.0-beta.59", |
| 282 | "@effect/sql-sqlite-bun": "4.0.0-beta.59", | 282 | "@effect/sql-sqlite-bun": "4.0.0-beta.59", |
| 283 | "@effect/vitest": "4.0.0-beta.59", | 283 | "@effect/vitest": "4.0.0-beta.59", |
| 284 | "@pierre/diffs": "1.1.20", | ||
| 284 | "@types/bun": "^1.3.11", | 285 | "@types/bun": "^1.3.11", |
| 285 | "@types/node": "^24.10.13", | 286 | "@types/node": "^24.10.13", |
| 286 | "effect": "4.0.0-beta.59", | 287 | "effect": "4.0.0-beta.59", |
| ... | @@ -701,9 +702,9 @@ | ... | @@ -701,9 +702,9 @@ |
| 701 | 702 | ||
| 702 | "@oxlint/binding-win32-x64-msvc": ["@oxlint/binding-win32-x64-msvc@1.56.0", "", { "os": "win32", "cpu": "x64" }, "sha512-ZHa0clocjLmIDr+1LwoWtxRcoYniAvERotvwKUYKhH41NVfl0Y4LNbyQkwMZzwDvKklKGvGZ5+DAG58/Ik47tQ=="], | 703 | "@oxlint/binding-win32-x64-msvc": ["@oxlint/binding-win32-x64-msvc@1.56.0", "", { "os": "win32", "cpu": "x64" }, "sha512-ZHa0clocjLmIDr+1LwoWtxRcoYniAvERotvwKUYKhH41NVfl0Y4LNbyQkwMZzwDvKklKGvGZ5+DAG58/Ik47tQ=="], |
| 703 | 704 | ||
| 704 | "@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=="], | 705 | "@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=="], |
| 705 | 706 | ||
| 706 | "@pierre/theme": ["@pierre/theme@0.0.22", "", {}, "sha512-ePUIdQRNGjrveELTU7fY89Xa7YGHHEy5Po5jQy/18lm32eRn96+tnYJEtFooGdffrx55KBUtOXfvVy/7LDFFhA=="], | 707 | "@pierre/theme": ["@pierre/theme@0.0.28", "", {}, "sha512-1j/H/fECBuc9dEvntdWI+l435HZapw+RCJTlqCA6BboQ5TjlnE005j/ROWutXIs8aq5OAc82JI2Kwk4A1WWBgw=="], |
| 707 | 708 | ||
| 708 | "@polka/url": ["@polka/url@1.0.0-next.29", "", {}, "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww=="], | 709 | "@polka/url": ["@polka/url@1.0.0-next.29", "", {}, "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww=="], |
| 709 | 710 |
package.json+1| ... | @@ -17,6 +17,7 @@ | ... | @@ -17,6 +17,7 @@ |
| 17 | "@effect/sql-sqlite-bun": "4.0.0-beta.59", | 17 | "@effect/sql-sqlite-bun": "4.0.0-beta.59", |
| 18 | "@effect/vitest": "4.0.0-beta.59", | 18 | "@effect/vitest": "4.0.0-beta.59", |
| 19 | "@effect/language-service": "0.84.2", | 19 | "@effect/language-service": "0.84.2", |
| 20 | "@pierre/diffs": "1.1.20", | ||
| 20 | "@types/bun": "^1.3.11", | 21 | "@types/bun": "^1.3.11", |
| 21 | "@types/node": "^24.10.13", | 22 | "@types/node": "^24.10.13", |
| 22 | "tsdown": "^0.20.3", | 23 | "tsdown": "^0.20.3", |