a very good jj gui
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

+322 -156
+1 -1
apps/desktop/package.json
··· 60 60 "tailwindcss": "^4.1.18", 61 61 "typescript": "^5.6.3", 62 62 "vite": "^7.3.1", 63 - "vite-plugin-agentation": "link:vite-plugin-agentation" 63 + "vite-plugin-agentation": "file:../../../agentation/vite-plugin/" 64 64 } 65 65 }
+8 -2
apps/desktop/src-tauri/src/lib.rs
··· 101 101 let matcher = EverythingMatcher; 102 102 let mut diff_iter = parent_tree.diff_stream(&commit_tree, &matcher); 103 103 104 + // Load repo ONCE before the loop to avoid redundant load_at_head calls per file 105 + let repo = jj_repo 106 + .repo_loader() 107 + .load_at_head() 108 + .map_err(|e| format!("Failed to load repo: {}", e))?; 109 + 104 110 let mut unified_diffs = Vec::new(); 105 111 106 112 pollster::block_on(async { ··· 120 126 | (None, Some(TreeValue::File { .. })) 121 127 | (Some(TreeValue::File { .. }), None) => { 122 128 let old_content = jj_repo 123 - .get_parent_file_content(&commit, path_str) 129 + .get_parent_file_content_with_repo(repo.as_ref(), &commit, path_str) 124 130 .unwrap_or_default(); 125 131 126 132 let new_content = jj_repo 127 - .get_file_content(&commit, path_str) 133 + .get_file_content_with_repo(repo.as_ref(), &commit, path_str) 128 134 .unwrap_or_default(); 129 135 130 136 let file_diff = diff::compute_file_diff(&old_content, &new_content, path_str)
+52 -28
apps/desktop/src-tauri/src/repo/jj.rs
··· 117 117 } 118 118 119 119 pub fn get_file_content(&self, commit: &Commit, path: &str) -> Result<Vec<u8>> { 120 + let repo = self.workspace.repo_loader().load_at_head()?; 121 + self.get_file_content_with_repo(repo.as_ref(), commit, path) 122 + } 123 + 124 + pub fn get_parent_file_content(&self, commit: &Commit, path: &str) -> Result<Vec<u8>> { 125 + let repo = self.workspace.repo_loader().load_at_head()?; 126 + self.get_parent_file_content_with_repo(repo.as_ref(), commit, path) 127 + } 128 + 129 + /// Get file content using an already-loaded repo (avoids redundant load_at_head) 130 + pub fn get_file_content_with_repo( 131 + &self, 132 + repo: &dyn Repo, 133 + commit: &Commit, 134 + path: &str, 135 + ) -> Result<Vec<u8>> { 136 + use jj_lib::backend::TreeValue; 137 + 120 138 let repo_path = RepoPath::from_internal_string(path).context("Invalid path")?; 121 139 let tree = commit.tree()?; 122 140 let file_value = tree.path_value(repo_path)?; 123 141 124 142 match file_value.into_resolved() { 125 - Ok(Some(value)) => { 126 - use jj_lib::backend::TreeValue; 127 - match value { 128 - TreeValue::File { id, .. } => { 129 - let repo = self.workspace.repo_loader().load_at_head()?; 130 - let mut reader = pollster::block_on(async { 131 - repo.store().read_file(repo_path, &id).await 132 - })?; 133 - let mut content = Vec::new(); 134 - pollster::block_on(async { reader.read_to_end(&mut content).await })?; 135 - Ok(content) 136 - } 137 - _ => Ok(Vec::new()), 143 + Ok(Some(value)) => match value { 144 + TreeValue::File { id, .. } => { 145 + let mut reader = pollster::block_on(async { 146 + repo.store().read_file(repo_path, &id).await 147 + })?; 148 + let mut content = Vec::new(); 149 + pollster::block_on(async { reader.read_to_end(&mut content).await })?; 150 + Ok(content) 138 151 } 139 - } 140 - _ => Ok(Vec::new()), 152 + _ => Ok(Vec::new()), 153 + }, 154 + Ok(None) => Ok(Vec::new()), 155 + Err(_) => Ok(Vec::new()), 141 156 } 142 157 } 143 158 144 - pub fn get_parent_file_content(&self, commit: &Commit, path: &str) -> Result<Vec<u8>> { 159 + /// Get parent file content using an already-loaded repo (avoids redundant load_at_head) 160 + pub fn get_parent_file_content_with_repo( 161 + &self, 162 + repo: &dyn Repo, 163 + commit: &Commit, 164 + path: &str, 165 + ) -> Result<Vec<u8>> { 166 + use jj_lib::backend::TreeValue; 167 + 145 168 let repo_path = RepoPath::from_internal_string(path).context("Invalid path")?; 146 - let repo = self.workspace.repo_loader().load_at_head()?; 147 - let parents = commit.parents(); 148 - let parent = parents.into_iter().next().context("Commit has no parent")?; 149 - let parent_commit = repo.store().get_commit(parent?.id())?; 150 - let parent_tree = parent_commit.tree()?; 151 - let file_value = parent_tree.path_value(repo_path)?; 169 + let parent = commit.parents().next(); 152 170 153 - match file_value.into_resolved() { 154 - Ok(Some(value)) => { 155 - use jj_lib::backend::TreeValue; 156 - match value { 171 + if let Some(parent_result) = parent { 172 + let parent_commit = parent_result?; 173 + let tree = parent_commit.tree()?; 174 + let file_value = tree.path_value(repo_path)?; 175 + 176 + match file_value.into_resolved() { 177 + Ok(Some(value)) => match value { 157 178 TreeValue::File { id, .. } => { 158 179 let mut reader = pollster::block_on(async { 159 180 repo.store().read_file(repo_path, &id).await ··· 163 184 Ok(content) 164 185 } 165 186 _ => Ok(Vec::new()), 166 - } 187 + }, 188 + Ok(None) => Ok(Vec::new()), 189 + Err(_) => Ok(Vec::new()), 167 190 } 168 - _ => Ok(Vec::new()), 191 + } else { 192 + Ok(Vec::new()) 169 193 } 170 194 } 171 195
+98 -110
apps/desktop/src/components/DiffPanel.tsx
··· 1 1 import { useAtom } from "@effect-atom/atom-react"; 2 2 import { useLiveQuery } from "@tanstack/react-db"; 3 3 import { PatchDiff } from "@pierre/diffs/react"; 4 - import { Columns2Icon, RowsIcon } from "lucide-react"; 4 + import { Columns2Icon, Loader2, RowsIcon } from "lucide-react"; 5 5 import type { FocusEvent, RefObject } from "react"; 6 - import { forwardRef, useEffect, useMemo, useRef, useState } from "react"; 6 + import { 7 + forwardRef, 8 + useCallback, 9 + useDeferredValue, 10 + useEffect, 11 + useMemo, 12 + useRef, 13 + useState, 14 + } from "react"; 7 15 import { type DiffStyle, type DiffViewState, diffStyleAtom, diffViewStateAtom } from "@/atoms"; 8 16 import { FileList, RevisionHeader } from "@/components/diff"; 9 17 import { ImageDiff } from "@/components/diff/ImageDiff"; ··· 17 25 getRevisionDiffCollection, 18 26 } from "@/db"; 19 27 import { useDiffPanelKeyboard } from "@/hooks/useDiffPanelKeyboard"; 28 + import { extractFilePath, parsePatchStats, splitMultiFileDiff } from "@/lib/diff-utils"; 20 29 import type { ChangedFileStatus } from "@/schemas"; 21 30 import type { Revision } from "@/tauri-commands"; 22 31 import { isImageFile } from "@/utils/file-types"; 32 + import { cn } from "@/lib/utils"; 23 33 24 34 interface DiffPanelProps { 25 35 repoPath: string | null; ··· 35 45 revisionsPanelRef: RefObject<HTMLElement | null>; 36 46 } 37 47 38 - /** 39 - * Extract file path from a unified diff patch. 40 - */ 41 - function extractFilePath(patch: string): string { 42 - const match = patch.match(/^\+\+\+ b\/(.+)$/m); 43 - return match ? match[1] : "unknown"; 44 - } 45 - 46 - /** 47 - * Split a multi-file unified diff into individual file diffs. 48 - */ 49 - function splitMultiFileDiff(unifiedDiff: string): string[] { 50 - if (!unifiedDiff.trim()) { 51 - return []; 52 - } 53 - 54 - const fileDiffs: string[] = []; 55 - const lines = unifiedDiff.split("\n"); 56 - let currentDiff: string[] = []; 57 - 58 - for (const line of lines) { 59 - if (line.startsWith("--- a/") && currentDiff.length > 0) { 60 - fileDiffs.push(currentDiff.join("\n")); 61 - currentDiff = [line]; 62 - } else { 63 - currentDiff.push(line); 64 - } 65 - } 66 - 67 - if (currentDiff.length > 0) { 68 - fileDiffs.push(currentDiff.join("\n")); 69 - } 70 - 71 - return fileDiffs; 72 - } 73 - 74 - /** 75 - * Parse additions and deletions from a single patch. 76 - */ 77 - function parsePatchStats(patch: string): { additions: number; deletions: number } { 78 - let additions = 0; 79 - let deletions = 0; 80 - const lines = patch.split("\n"); 81 - 82 - for (const line of lines) { 83 - // Skip header lines 84 - if (line.startsWith("---") || line.startsWith("+++") || line.startsWith("@@")) { 85 - continue; 86 - } 87 - if (line.startsWith("+") && !line.startsWith("++")) { 88 - additions++; 89 - } else if (line.startsWith("-") && !line.startsWith("--")) { 90 - deletions++; 91 - } 92 - } 93 - 94 - return { additions, deletions }; 95 - } 96 - 97 48 export const PrerenderedDiffPanel = forwardRef<HTMLDivElement, PrerenderedDiffPanelProps>( 98 49 function PrerenderedDiffPanel({ repoPath, revisions, selectedChangeId, revisionsPanelRef }, ref) { 99 50 const selectedRevision = selectedChangeId ··· 155 106 } 156 107 157 108 const effectiveStyle = diffViewState.styleOverrides.get(path) ?? globalDiffStyle; 109 + 158 110 return ( 159 111 <div key={path} className="min-h-0"> 160 112 {!patch.trim() ? ( ··· 196 148 { repoPath, changeId, revision, revisionsPanelRef }, 197 149 ref, 198 150 ) { 151 + // Defer changeId updates so revision selection highlight updates immediately 152 + // while diff panel data fetching/rendering happens on the next frame 153 + const deferredChangeId = useDeferredValue(changeId); 154 + 199 155 const containerRef = useRef<HTMLDivElement>(null); 200 156 const scrollContainerRef = useRef<HTMLDivElement>(null); 201 157 const [globalDiffStyle] = useAtom(diffStyleAtom); 202 158 const [diffViewState, setDiffViewState] = useAtom(diffViewStateAtom); 203 159 const [selectedFiles, setSelectedFiles] = useState<Set<string>>(new Set()); 204 - const prevChangeIdRef = useRef<string | null>(null); 205 160 const [hasFocus, setHasFocus] = useState(false); 206 161 207 162 // Handler for blur events - only unfocus if focus moves outside container ··· 229 184 ? (diffViewState.styleOverrides.get(firstSelectedFile) ?? globalDiffStyle) 230 185 : globalDiffStyle; 231 186 232 - function handleSetLocalStyle(style: DiffStyle) { 233 - if (selectedFiles.size === 0) return; 234 - setDiffViewState((prev) => { 235 - const next = new Map(prev.styleOverrides); 236 - // Apply style to all selected files 237 - for (const file of selectedFiles) { 238 - next.set(file, style); 239 - } 240 - return { ...prev, styleOverrides: next }; 241 - }); 242 - } 187 + const handleSetLocalStyle = useCallback( 188 + (style: DiffStyle) => { 189 + if (selectedFiles.size === 0) return; 190 + setDiffViewState((prev) => { 191 + const next = new Map(prev.styleOverrides); 192 + // Apply style to all selected files 193 + for (const file of selectedFiles) { 194 + next.set(file, style); 195 + } 196 + return { ...prev, styleOverrides: next }; 197 + }); 198 + }, 199 + [selectedFiles, setDiffViewState], 200 + ); 243 201 244 202 // Reset selected files when changeId changes 245 - if (prevChangeIdRef.current !== changeId) { 246 - prevChangeIdRef.current = changeId; 247 - if (selectedFiles.size > 0) { 248 - setSelectedFiles(new Set()); 249 - } 250 - } 203 + // biome-ignore lint/correctness/useExhaustiveDependencies: intentionally trigger on changeId change 204 + useEffect(() => { 205 + setSelectedFiles(new Set()); 206 + }, [deferredChangeId]); 251 207 252 208 // Keyboard navigation 253 209 useDiffPanelKeyboard({ scrollContainerRef, revisionsPanelRef, hasFocus }); 254 210 255 211 // Fetch file changes (for the file list with status) 256 212 const changesCollection = 257 - repoPath && changeId 258 - ? getRevisionChangesCollection(repoPath, changeId) 213 + repoPath && deferredChangeId 214 + ? getRevisionChangesCollection(repoPath, deferredChangeId) 259 215 : emptyChangesCollection; 260 216 const { data: changedFiles = [] } = useLiveQuery(changesCollection); 261 217 262 218 // Fetch full diff (for the diff content) 263 219 const diffCollection = 264 - repoPath && changeId ? getRevisionDiffCollection(repoPath, changeId) : emptyDiffCollection; 265 - const { data: diffEntries = [], isLoading } = useLiveQuery(diffCollection); 220 + repoPath && deferredChangeId 221 + ? getRevisionDiffCollection(repoPath, deferredChangeId) 222 + : emptyDiffCollection; 223 + const { data: diffEntries = [] } = useLiveQuery(diffCollection); 266 224 const revisionDiff = diffEntries[0]?.content ?? ""; 267 225 226 + // Timing instrumentation for cache analysis 227 + console.log('[DiffPanel] selection:', changeId?.slice(0,8), 228 + 'deferred:', deferredChangeId?.slice(0,8), 229 + 'changedFiles:', changedFiles.length, 230 + 'hasDiff:', !!revisionDiff, 231 + 'at:', performance.now().toFixed(0)); 232 + 233 + // Track what's currently displayed - shows previous while loading 234 + const [displayedState, setDisplayedState] = useState<{ 235 + changeId: string; 236 + patches: Array<{ path: string; patch: string; status: ChangedFileStatus }>; 237 + } | null>(null); 238 + 239 + // Update displayed state only when new data arrives 240 + useEffect(() => { 241 + if (revisionDiff && deferredChangeId) { 242 + const patches = splitMultiFileDiff(revisionDiff).map((patch) => ({ 243 + path: extractFilePath(patch) ?? "unknown", 244 + patch, 245 + status: (changedFiles.find((f) => f.path === extractFilePath(patch))?.status ?? 246 + "modified") as ChangedFileStatus, 247 + })); 248 + setDisplayedState({ changeId: deferredChangeId, patches }); 249 + } 250 + }, [revisionDiff, deferredChangeId, changedFiles]); 251 + 252 + // Determine if we're showing stale data 253 + const isStale = displayedState !== null && displayedState.changeId !== changeId; 254 + 268 255 // Parse diff into individual file patches 269 256 const fileDiffs = useMemo(() => splitMultiFileDiff(revisionDiff), [revisionDiff]); 270 257 ··· 273 260 const map = new Map<string, string>(); 274 261 for (const patch of fileDiffs) { 275 262 const path = extractFilePath(patch); 276 - map.set(path, patch); 263 + if (path) { 264 + map.set(path, patch); 265 + } 277 266 } 278 267 return map; 279 268 }, [fileDiffs]); ··· 290 279 return { totalAdditions: additions, totalDeletions: deletions }; 291 280 }, [fileDiffs]); 292 281 293 - // Derive the effective state - resets automatically when changeId changes 294 - const effectiveState = getDiffViewState(diffViewState, changeId); 295 - 296 - // Sync atom if state was reset (only writes when needed) 297 - if (effectiveState !== diffViewState) { 298 - setDiffViewState(effectiveState); 299 - } 282 + // Sync diffViewState atom when changeId changes (reset to initial state) 283 + useEffect(() => { 284 + const effectiveState = getDiffViewState(diffViewState, deferredChangeId); 285 + if (effectiveState !== diffViewState) { 286 + setDiffViewState(effectiveState); 287 + } 288 + }, [deferredChangeId, diffViewState, setDiffViewState]); 300 289 301 290 // Auto-select first file when files load and none selected 302 291 useEffect(() => { ··· 333 322 ); 334 323 } 335 324 336 - if (isLoading) { 337 - return ( 338 - // biome-ignore lint/a11y/noStaticElementInteractions: Focus tracking for keyboard navigation 339 - <div 340 - ref={setRefs} 341 - tabIndex={-1} 342 - onFocus={() => setHasFocus(true)} 343 - onBlur={handleBlur} 344 - className="flex items-center justify-center h-full text-muted-foreground text-sm outline-none" 345 - > 346 - Loading diffs... 347 - </div> 348 - ); 349 - } 350 - 351 - if (changedFiles.length === 0) { 325 + // Only show "No changes" if we have no displayed state to show 326 + if (changedFiles.length === 0 && !displayedState) { 352 327 return ( 353 328 // biome-ignore lint/a11y/noStaticElementInteractions: Focus tracking for keyboard navigation 354 329 <div ··· 362 337 </div> 363 338 ); 364 339 } 340 + 341 + // Determine which patches to show - use previous while loading 342 + const patchesToRender = isStale && displayedState ? displayedState.patches : selectedPatches; 365 343 366 344 return ( 367 345 // biome-ignore lint/a11y/noStaticElementInteractions: Focus tracking for keyboard navigation ··· 430 408 431 409 {/* Diff content panel */} 432 410 <ResizablePanel id="diff-content" defaultSize="70%"> 433 - <div className="h-full w-full min-w-0"> 411 + <div 412 + className={cn( 413 + "h-full w-full min-w-0 relative", 414 + isStale && "opacity-60 pointer-events-none", 415 + )} 416 + > 417 + {isStale && ( 418 + <div className="absolute inset-0 flex items-center justify-center bg-background/50 z-10"> 419 + <Loader2 className="h-6 w-6 animate-spin" /> 420 + </div> 421 + )} 434 422 <MultiFileDiff 435 - patches={selectedPatches} 423 + patches={patchesToRender} 436 424 diffViewState={diffViewState} 437 425 globalDiffStyle={globalDiffStyle} 438 426 repoPath={repoPath} 439 - changeId={changeId} 427 + changeId={displayedState?.changeId ?? changeId} 440 428 /> 441 429 </div> 442 430 </ResizablePanel>
+4 -4
apps/desktop/src/components/revision-graph/RevisionRow.tsx
··· 162 162 <div className="shrink-0" style={{ width: nodeAreaWidth + 8 }} /> 163 163 {/* Content area with visual styling - full row height */} 164 164 <div 165 - className={`relative flex-1 mr-2 min-w-0 overflow-hidden text-card-foreground flex flex-col justify-center py-1 border-b transition-colors ${ 165 + className={`relative flex-1 mr-2 min-w-0 overflow-hidden text-card-foreground flex flex-col justify-center py-1 border-b transition-colors rounded-md ${ 166 166 isDragOver 167 - ? "bg-primary/20 border-primary/50 rounded-md" 167 + ? "bg-primary/20 border-primary/50" 168 168 : isChecked || isFocused 169 169 ? hasFocus 170 - ? "bg-accent/40 rounded-md border-transparent" 171 - : "bg-muted rounded-md border-transparent" 170 + ? "bg-accent/40 border-transparent" 171 + : "bg-muted border-transparent" 172 172 : "border-border/30" 173 173 }`} 174 174 >
+17 -6
apps/desktop/src/components/revision-graph/index.tsx
··· 2 2 import { useNavigate, useSearch } from "@tanstack/react-router"; 3 3 import { useVirtualizer } from "@tanstack/react-virtual"; 4 4 import type { RefObject } from "react"; 5 - import { forwardRef, useEffect, useImperativeHandle, useMemo, useRef } from "react"; 5 + import { 6 + forwardRef, 7 + useDeferredValue, 8 + useEffect, 9 + useImperativeHandle, 10 + useMemo, 11 + useRef, 12 + } from "react"; 6 13 import { Route } from "@/routes/project.$projectId"; 7 14 import { 8 15 debugOverlayEnabledAtom, ··· 556 563 // Compute related revisions for dimming logic 557 564 // When a stack is focused, use the stack's top and bottom as the "selected" revisions 558 565 const focusedStack = focusedStackId ? stackById.get(focusedStackId) : null; 566 + 567 + // Defer the selected ID so dimming computation doesn't block selection highlight 568 + const deferredSelectedChangeId = useDeferredValue(selectedRevision?.change_id ?? null); 569 + 559 570 const relatedRevisions = useMemo(() => { 560 571 if (focusedStack) { 561 572 // When stack is focused, highlight the stack endpoints and their ancestors/descendants ··· 564 575 // Union of both sets 565 576 return new Set([...topRelated, ...bottomRelated]); 566 577 } 567 - return getRelatedRevisions(revisions, selectedRevision?.change_id ?? null); 568 - }, [revisions, focusedStack, selectedRevision?.change_id]); 578 + return getRelatedRevisions(revisions, deferredSelectedChangeId); 579 + }, [revisions, focusedStack, deferredSelectedChangeId]); 569 580 570 581 // Build revision key -> displayRow index map for scrolling and edge positioning 571 582 // IMPORTANT: Use displayRows indices (not rows) to match virtualizer positioning ··· 820 831 821 832 // Plain click: focus revision (clear selection, anchor, and stack focus) 822 833 navigate({ 823 - search: { 824 - ...search, 834 + search: (prev) => ({ 835 + ...prev, 825 836 selected: undefined, 826 837 selectionAnchor: undefined, 827 838 stack: undefined, 828 839 rev: revisionKey, 829 - }, 840 + }), 830 841 replace: true, 831 842 }); 832 843 }
+15 -2
apps/desktop/src/db.ts
··· 72 72 73 73 /** Ensure the change ID pool is loaded. Call from router beforeLoad. */ 74 74 export async function ensureChangeIdPool(repoPath: string): Promise<void> { 75 + // Fast path: if already cached, return immediately without async work 76 + const existing = queryClient.getQueryData<ChangeIdPool>(changeIdPoolQueryKey(repoPath)); 77 + if (existing && existing.ids.length > 0) { 78 + return; 79 + } 80 + 75 81 await queryClient.ensureQueryData({ 76 82 queryKey: changeIdPoolQueryKey(repoPath), 77 83 queryFn: () => fetchChangeIdPool(repoPath), ··· 428 434 ...queryCollectionOptions({ 429 435 queryClient, 430 436 queryKey: ["revision-changes", repoPath, changeId], 431 - queryFn: () => getRevisionChanges(repoPath, changeId), 437 + queryFn: async () => { 438 + console.log('[DB] FETCHING changes for', changeId.slice(0,8), 'at:', performance.now().toFixed(0)); 439 + const changes = await getRevisionChanges(repoPath, changeId); 440 + console.log('[DB] FETCHED changes for', changeId.slice(0,8), 'at:', performance.now().toFixed(0)); 441 + return changes; 442 + }, 432 443 getKey: (file: ChangedFile) => file.path, 433 444 }), 434 445 }); ··· 476 487 queryClient, 477 488 queryKey: ["revision-diff", repoPath, changeId], 478 489 queryFn: async () => { 490 + console.log('[DB] FETCHING diff for', changeId.slice(0,8), 'at:', performance.now().toFixed(0)); 479 491 const diff = await getRevisionDiff(repoPath, changeId); 492 + console.log('[DB] FETCHED diff for', changeId.slice(0,8), 'at:', performance.now().toFixed(0)); 480 493 return [{ id: "diff" as const, content: diff }]; 481 494 }, 482 495 getKey: (entry: DiffEntry) => entry.id, ··· 518 531 * TanStack DB handles caching - subsequent calls are no-ops. 519 532 */ 520 533 export function prefetchRevisionDiffs(repoPath: string, changeIds: string[]): void { 534 + // Just trigger the data fetch for all revisions 521 535 for (const changeId of changeIds) { 522 - // Creating the collection triggers the query if not already cached 523 536 getRevisionDiffCollection(repoPath, changeId); 524 537 } 525 538 }
+64
apps/desktop/src/lib/diff-utils.ts
··· 1 + /** 2 + * Utilities for parsing and manipulating unified diffs. 3 + */ 4 + 5 + /** 6 + * Extract file path from a unified diff patch. 7 + * Parses the "+++ b/..." line to get the new file path. 8 + */ 9 + export function extractFilePath(patch: string): string | undefined { 10 + const match = patch.match(/^\+\+\+ b\/(.+)$/m); 11 + return match ? match[1] : undefined; 12 + } 13 + 14 + /** 15 + * Split a multi-file unified diff into individual file diffs. 16 + * Each file diff starts with "--- a/..." line. 17 + */ 18 + export function splitMultiFileDiff(unifiedDiff: string): string[] { 19 + if (!unifiedDiff.trim()) { 20 + return []; 21 + } 22 + 23 + const fileDiffs: string[] = []; 24 + const lines = unifiedDiff.split("\n"); 25 + let currentDiff: string[] = []; 26 + 27 + for (const line of lines) { 28 + if (line.startsWith("--- a/") && currentDiff.length > 0) { 29 + fileDiffs.push(currentDiff.join("\n")); 30 + currentDiff = [line]; 31 + } else { 32 + currentDiff.push(line); 33 + } 34 + } 35 + 36 + if (currentDiff.length > 0) { 37 + fileDiffs.push(currentDiff.join("\n")); 38 + } 39 + 40 + return fileDiffs; 41 + } 42 + 43 + /** 44 + * Parse additions and deletions from a single patch. 45 + */ 46 + export function parsePatchStats(patch: string): { additions: number; deletions: number } { 47 + let additions = 0; 48 + let deletions = 0; 49 + const lines = patch.split("\n"); 50 + 51 + for (const line of lines) { 52 + // Skip header lines 53 + if (line.startsWith("---") || line.startsWith("+++") || line.startsWith("@@")) { 54 + continue; 55 + } 56 + if (line.startsWith("+") && !line.startsWith("++")) { 57 + additions++; 58 + } else if (line.startsWith("-") && !line.startsWith("--")) { 59 + deletions++; 60 + } 61 + } 62 + 63 + return { additions, deletions }; 64 + }
+63 -3
bun.lock
··· 64 64 "tailwindcss": "^4.1.18", 65 65 "typescript": "^5.6.3", 66 66 "vite": "^7.3.1", 67 - "vite-plugin-agentation": "link:vite-plugin-agentation", 67 + "vite-plugin-agentation": "file:../../../agentation/vite-plugin/", 68 68 }, 69 69 }, 70 70 "packages/vite-plugin-annotator": { ··· 1318 1318 1319 1319 "vite": ["vite@6.4.1", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", "tinyglobby": "^0.2.13" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g=="], 1320 1320 1321 - "vite-plugin-agentation": ["vite-plugin-agentation@link:vite-plugin-agentation", {}], 1322 - 1323 1321 "vite-plugin-solid": ["vite-plugin-solid@2.11.10", "", { "dependencies": { "@babel/core": "^7.23.3", "@types/babel__core": "^7.20.4", "babel-preset-solid": "^1.8.4", "merge-anything": "^5.1.7", "solid-refresh": "^0.6.3", "vitefu": "^1.0.4" }, "peerDependencies": { "@testing-library/jest-dom": "^5.16.6 || ^5.17.0 || ^6.*", "solid-js": "^1.7.2", "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" }, "optionalPeers": ["@testing-library/jest-dom"] }, "sha512-Yr1dQybmtDtDAHkii6hXuc1oVH9CPcS/Zb2jN/P36qqcrkNnVPsMTzQ06jyzFPFjj3U1IYKMVt/9ZqcwGCEbjw=="], 1324 1322 1325 1323 "vitefu": ["vitefu@1.1.1", "", { "peerDependencies": { "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0" }, "optionalPeers": ["vite"] }, "sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ=="], ··· 1396 1394 1397 1395 "desktop/vite": ["vite@7.3.1", "", { "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA=="], 1398 1396 1397 + "desktop/vite-plugin-agentation": ["vite-plugin-agentation@file:../agentation/vite-plugin", { "devDependencies": { "@types/node": "^22.0.0", "tsup": "^8.0.0", "typescript": "^5.0.0", "vite": "^6.0.0" }, "peerDependencies": { "agentation": "*", "vite": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }], 1398 + 1399 1399 "express/cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="], 1400 1400 1401 1401 "import-fresh/resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], ··· 1447 1447 "cross-spawn/which/isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], 1448 1448 1449 1449 "desktop/vite/esbuild": ["esbuild@0.27.2", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.2", "@esbuild/android-arm": "0.27.2", "@esbuild/android-arm64": "0.27.2", "@esbuild/android-x64": "0.27.2", "@esbuild/darwin-arm64": "0.27.2", "@esbuild/darwin-x64": "0.27.2", "@esbuild/freebsd-arm64": "0.27.2", "@esbuild/freebsd-x64": "0.27.2", "@esbuild/linux-arm": "0.27.2", "@esbuild/linux-arm64": "0.27.2", "@esbuild/linux-ia32": "0.27.2", "@esbuild/linux-loong64": "0.27.2", "@esbuild/linux-mips64el": "0.27.2", "@esbuild/linux-ppc64": "0.27.2", "@esbuild/linux-riscv64": "0.27.2", "@esbuild/linux-s390x": "0.27.2", "@esbuild/linux-x64": "0.27.2", "@esbuild/netbsd-arm64": "0.27.2", "@esbuild/netbsd-x64": "0.27.2", "@esbuild/openbsd-arm64": "0.27.2", "@esbuild/openbsd-x64": "0.27.2", "@esbuild/openharmony-arm64": "0.27.2", "@esbuild/sunos-x64": "0.27.2", "@esbuild/win32-arm64": "0.27.2", "@esbuild/win32-ia32": "0.27.2", "@esbuild/win32-x64": "0.27.2" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw=="], 1450 + 1451 + "desktop/vite-plugin-agentation/@types/node": ["@types/node@22.19.7", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-MciR4AKGHWl7xwxkBa6xUGxQJ4VBOmPTF7sL+iGzuahOFaO0jHCsuEfS80pan1ef4gWId1oWOweIhrDEYLuaOw=="], 1452 + 1453 + "desktop/vite-plugin-agentation/vite": ["vite@6.4.1", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", "tinyglobby": "^0.2.13" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g=="], 1450 1454 1451 1455 "tsup/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.2", "", { "os": "aix", "cpu": "ppc64" }, "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw=="], 1452 1456 ··· 1554 1558 1555 1559 "yargs/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], 1556 1560 1561 + "desktop/vite-plugin-agentation/@types/node/undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], 1562 + 1563 + "desktop/vite-plugin-agentation/vite/esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="], 1564 + 1557 1565 "desktop/vite/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.2", "", { "os": "aix", "cpu": "ppc64" }, "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw=="], 1558 1566 1559 1567 "desktop/vite/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.27.2", "", { "os": "android", "cpu": "arm" }, "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA=="], ··· 1601 1609 "desktop/vite/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.2", "", { "os": "win32", "cpu": "x64" }, "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ=="], 1602 1610 1603 1611 "yargs/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], 1612 + 1613 + "desktop/vite-plugin-agentation/vite/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.12", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA=="], 1614 + 1615 + "desktop/vite-plugin-agentation/vite/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.25.12", "", { "os": "android", "cpu": "arm" }, "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg=="], 1616 + 1617 + "desktop/vite-plugin-agentation/vite/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.12", "", { "os": "android", "cpu": "arm64" }, "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg=="], 1618 + 1619 + "desktop/vite-plugin-agentation/vite/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.25.12", "", { "os": "android", "cpu": "x64" }, "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg=="], 1620 + 1621 + "desktop/vite-plugin-agentation/vite/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.12", "", { "os": "darwin", "cpu": "arm64" }, "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg=="], 1622 + 1623 + "desktop/vite-plugin-agentation/vite/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.12", "", { "os": "darwin", "cpu": "x64" }, "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA=="], 1624 + 1625 + "desktop/vite-plugin-agentation/vite/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.12", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg=="], 1626 + 1627 + "desktop/vite-plugin-agentation/vite/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.12", "", { "os": "freebsd", "cpu": "x64" }, "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ=="], 1628 + 1629 + "desktop/vite-plugin-agentation/vite/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.12", "", { "os": "linux", "cpu": "arm" }, "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw=="], 1630 + 1631 + "desktop/vite-plugin-agentation/vite/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ=="], 1632 + 1633 + "desktop/vite-plugin-agentation/vite/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.12", "", { "os": "linux", "cpu": "ia32" }, "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA=="], 1634 + 1635 + "desktop/vite-plugin-agentation/vite/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng=="], 1636 + 1637 + "desktop/vite-plugin-agentation/vite/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw=="], 1638 + 1639 + "desktop/vite-plugin-agentation/vite/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.12", "", { "os": "linux", "cpu": "ppc64" }, "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA=="], 1640 + 1641 + "desktop/vite-plugin-agentation/vite/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w=="], 1642 + 1643 + "desktop/vite-plugin-agentation/vite/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.12", "", { "os": "linux", "cpu": "s390x" }, "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg=="], 1644 + 1645 + "desktop/vite-plugin-agentation/vite/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.12", "", { "os": "linux", "cpu": "x64" }, "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw=="], 1646 + 1647 + "desktop/vite-plugin-agentation/vite/esbuild/@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg=="], 1648 + 1649 + "desktop/vite-plugin-agentation/vite/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.12", "", { "os": "none", "cpu": "x64" }, "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ=="], 1650 + 1651 + "desktop/vite-plugin-agentation/vite/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.12", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A=="], 1652 + 1653 + "desktop/vite-plugin-agentation/vite/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.12", "", { "os": "openbsd", "cpu": "x64" }, "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw=="], 1654 + 1655 + "desktop/vite-plugin-agentation/vite/esbuild/@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg=="], 1656 + 1657 + "desktop/vite-plugin-agentation/vite/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.12", "", { "os": "sunos", "cpu": "x64" }, "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w=="], 1658 + 1659 + "desktop/vite-plugin-agentation/vite/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.12", "", { "os": "win32", "cpu": "arm64" }, "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg=="], 1660 + 1661 + "desktop/vite-plugin-agentation/vite/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.12", "", { "os": "win32", "cpu": "ia32" }, "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ=="], 1662 + 1663 + "desktop/vite-plugin-agentation/vite/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.12", "", { "os": "win32", "cpu": "x64" }, "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA=="], 1604 1664 } 1605 1665 }