| 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 | 29 | "@effect/platform-node": "catalog:", |
| 30 | 30 | "@effect/sql-sqlite-bun": "catalog:", |
| 31 | 31 | "@opencode-ai/sdk": "^1.3.15", |
| 32 | "@pierre/diffs": "^1.1.0-beta.16", | |
| 32 | "@pierre/diffs": "catalog:", | |
| 33 | 33 | "effect": "catalog:", |
| 34 | 34 | "node-pty": "^1.1.0", |
| 35 | 35 | "open": "^10.1.0" |
apps/web/package.json+1-1| ... | ... | @@ -22,7 +22,7 @@ |
| 22 | 22 | "@formkit/auto-animate": "^0.9.0", |
| 23 | 23 | "@legendapp/list": "3.0.0-beta.44", |
| 24 | 24 | "@lexical/react": "^0.41.0", |
| 25 | "@pierre/diffs": "^1.1.0-beta.16", | |
| 25 | "@pierre/diffs": "catalog:", | |
| 26 | 26 | "@t3tools/client-runtime": "workspace:*", |
| 27 | 27 | "@t3tools/contracts": "workspace:*", |
| 28 | 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 | 5 | import { scopeThreadRef } from "@t3tools/client-runtime"; |
| 6 | 6 | import type { TurnId } from "@t3tools/contracts"; |
| 7 | 7 | import { |
| 8 | ChevronDownIcon, | |
| 8 | 9 | ChevronLeftIcon, |
| 9 | 10 | ChevronRightIcon, |
| 10 | 11 | Columns2Icon, |
| ... | ... | @@ -161,6 +162,21 @@ function buildFileDiffRenderKey(fileDiff: FileDiffMetadata): string { |
| 161 | 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 | 180 | interface DiffPanelProps { |
| 165 | 181 | mode?: DiffPanelMode; |
| 166 | 182 | } |
| ... | ... | @@ -174,6 +190,9 @@ export default function DiffPanel({ mode = "inline" }: DiffPanelProps) { |
| 174 | 190 | const [diffRenderMode, setDiffRenderMode] = useState<DiffRenderMode>("stacked"); |
| 175 | 191 | const [diffWordWrap, setDiffWordWrap] = useState(settings.diffWordWrap); |
| 176 | 192 | const [diffIgnoreWhitespace, setDiffIgnoreWhitespace] = useState(settings.diffIgnoreWhitespace); |
| 193 | const [collapsedDiffFileKeys, setCollapsedDiffFileKeys] = useState<ReadonlySet<string>>( | |
| 194 | () => new Set(), | |
| 195 | ); | |
| 177 | 196 | const patchViewportRef = useRef<HTMLDivElement>(null); |
| 178 | 197 | const turnStripRef = useRef<HTMLDivElement>(null); |
| 179 | 198 | const previousDiffOpenRef = useRef(false); |
| ... | ... | @@ -317,6 +336,19 @@ export default function DiffPanel({ mode = "inline" }: DiffPanelProps) { |
| 317 | 336 | ); |
| 318 | 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 | 352 | useEffect(() => { |
| 321 | 353 | if (diffOpen && !previousDiffOpenRef.current) { |
| 322 | 354 | setDiffWordWrap(settings.diffWordWrap); |
| ... | ... | @@ -346,6 +378,17 @@ export default function DiffPanel({ mode = "inline" }: DiffPanelProps) { |
| 346 | 378 | }, |
| 347 | 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 | 393 | const selectTurn = (turnId: TurnId) => { |
| 351 | 394 | if (!activeThread) return; |
| ... | ... | @@ -620,11 +663,12 @@ export default function DiffPanel({ mode = "inline" }: DiffPanelProps) { |
| 620 | 663 | const filePath = resolveFileDiffPath(fileDiff); |
| 621 | 664 | const fileKey = buildFileDiffRenderKey(fileDiff); |
| 622 | 665 | const themedFileKey = `${fileKey}:${resolvedTheme}`; |
| 666 | const collapsed = collapsedDiffFileKeys.has(fileKey); | |
| 623 | 667 | return ( |
| 624 | 668 | <div |
| 625 | 669 | key={themedFileKey} |
| 626 | 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 | 672 | onClickCapture={(event) => { |
| 629 | 673 | const nativeEvent = event.nativeEvent as MouseEvent; |
| 630 | 674 | const composedPath = nativeEvent.composedPath?.() ?? []; |
| ... | ... | @@ -638,7 +682,30 @@ export default function DiffPanel({ mode = "inline" }: DiffPanelProps) { |
| 638 | 682 | > |
| 639 | 683 | <FileDiff |
| 640 | 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 | 707 | options={{ |
| 708 | collapsed, | |
| 642 | 709 | diffStyle: diffRenderMode === "split" ? "split" : "unified", |
| 643 | 710 | lineDiffType: "none", |
| 644 | 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 | 28 | const DIFF_INLINE_SIDEBAR_WIDTH_STORAGE_KEY = "chat_diff_sidebar_width"; |
| 29 | 29 | const DIFF_INLINE_DEFAULT_WIDTH = "clamp(24rem,34vw,36rem)"; |
| 30 | 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 | 32 | const COMPOSER_COMPACT_MIN_LEFT_CONTROLS_WIDTH_PX = 208; |
| 33 | 33 | |
| 34 | 34 | const DiffLoadingFallback = (props: { mode: DiffPanelMode }) => { |
bun.lock+5-4| ... | ... | @@ -59,7 +59,7 @@ |
| 59 | 59 | "@effect/platform-node": "catalog:", |
| 60 | 60 | "@effect/sql-sqlite-bun": "catalog:", |
| 61 | 61 | "@opencode-ai/sdk": "^1.3.15", |
| 62 | "@pierre/diffs": "^1.1.0-beta.16", | |
| 62 | "@pierre/diffs": "catalog:", | |
| 63 | 63 | "effect": "catalog:", |
| 64 | 64 | "node-pty": "^1.1.0", |
| 65 | 65 | "open": "^10.1.0", |
| ... | ... | @@ -93,7 +93,7 @@ |
| 93 | 93 | "@formkit/auto-animate": "^0.9.0", |
| 94 | 94 | "@legendapp/list": "3.0.0-beta.44", |
| 95 | 95 | "@lexical/react": "^0.41.0", |
| 96 | "@pierre/diffs": "^1.1.0-beta.16", | |
| 96 | "@pierre/diffs": "catalog:", | |
| 97 | 97 | "@t3tools/client-runtime": "workspace:*", |
| 98 | 98 | "@t3tools/contracts": "workspace:*", |
| 99 | 99 | "@t3tools/shared": "workspace:*", |
| ... | ... | @@ -281,6 +281,7 @@ |
| 281 | 281 | "@effect/platform-node-shared": "4.0.0-beta.59", |
| 282 | 282 | "@effect/sql-sqlite-bun": "4.0.0-beta.59", |
| 283 | 283 | "@effect/vitest": "4.0.0-beta.59", |
| 284 | "@pierre/diffs": "1.1.20", | |
| 284 | 285 | "@types/bun": "^1.3.11", |
| 285 | 286 | "@types/node": "^24.10.13", |
| 286 | 287 | "effect": "4.0.0-beta.59", |
| ... | ... | @@ -701,9 +702,9 @@ |
| 701 | 702 | |
| 702 | 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 | 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 | 17 | "@effect/sql-sqlite-bun": "4.0.0-beta.59", |
| 18 | 18 | "@effect/vitest": "4.0.0-beta.59", |
| 19 | 19 | "@effect/language-service": "0.84.2", |
| 20 | "@pierre/diffs": "1.1.20", | |
| 20 | 21 | "@types/bun": "^1.3.11", |
| 21 | 22 | "@types/node": "^24.10.13", |
| 22 | 23 | "tsdown": "^0.20.3", |