native macOS codings agent orchestrator prowl.onev.cat
Prowl docs-ai 003-diff-window 001-action.md
4.5 kB

003 — Diff Window: Action Log #

Timeline #

Date Change Ref
2026-03-06 Git operations for diff (name-status, untracked paths, show file at HEAD); diff window with file tree + YiTong DiffView; diff badge click, Cmd+] shortcut, Show Diff menu item; preload all file contents on open; toolbar (sidebar toggle, split/unified picker), Cmd+W close, frame persistence PR #1 (commits 0d03848, 09194c4, 59dc4f6, 5850576, 8985fc2)
2026-03-19 Unicode/Chinese filenames: pass -c core.quotePath=false to git diff --name-status and git ls-files --others; unit tests PR #10 (commit 1b32a26, fork issue #9)
2026-03-23 YiTong pinned to 0.2.0 (upToNextMajorVersion), optimized web bundle: −6.6 MB bundle, −7 MB .app PR #45
2026-06-14 Configurable external diff tools (Built-in / Hunk / FileMerge / Kaleidoscope / Custom Command) behind one launcher PR #449 → 002
2026-07-03 Fix stale cache writes after cancellation; testable DiffWindowState (injected git closures, pure reconciliation statics); render spinner; 150 ms select debounce PR #529 → 003
2026-07-03 Render-failure recovery via renderGeneration view identity; leading-edge debounce (first click immediate) PR #536 → 003
2026-07-03 Shared Debouncer/KeyedDebouncer helpers; render phase folded into RenderState enum PR #537 → 003
2026-07-08 Diff window follows the app's appearance setting instead of the system appearance PR #540 → 004

Outcome & current state (as of 2026-07-12) #

The feature lives in supacode/Features/DiffView/:

  • DiffWindowState.swift — @Observable @MainActor store: changed-file list, per-file DiffDocument cache, RenderState enum (idle/rendering/ failed), renderGeneration retry counter, leading-edge select debounce via an injected Debouncer, and pure static reconciliation helpers (evictedCache, resolvedSelection). Git access is injected as closures (live implementations use GitClient).
  • DiffWindowManager.swift — singleton NSWindow host: setFrameAutosaveName("DiffWindow"), local keyDown monitor for Cmd+W, refresh-on-focus, and NSAppearance.from(_:) applied from the app's appearance setting.
  • DiffWindowContentView.swift — NavigationSplitView (file list sidebar + YiTong DiffView), @AppStorage("diffViewStyle") split/unified picker, sidebar toggle (toolbar button + focusedSceneAction), render spinner/error overlay, .id(state.renderGeneration) on the DiffView, and WindowAppearanceSetter for live appearance updates.
  • DiffChangedFile.swift — name-status parsing model.

Supporting pieces:

  • supacode/Clients/Git/GitClient.swift — diffNameStatus(at:), untrackedFilePaths(at:), showFileAtHEAD(_:in:), all with -c core.quotePath=false.
  • supacode/Domain/ExternalDiffTool.swift and supacode/Clients/ExternalDiff/ (ExternalDiffToolClient.swift, ExternalDiffSnapshotClient.swift) — the tool setting and launcher; the Built-in branch calls DiffWindowManager.shared.show(...).
  • supacode/Support/Debouncer.swift — Debouncer + KeyedDebouncer, shared with WorktreeInfoWatcherManager and PullRequestRefreshCoordinator.
  • YiTong is pinned upToNextMajorVersion: 0.2.0 in supacode.xcodeproj (resolved at 0.2.0).
  • Tests: supacodeTests/DiffWindowStateTests.swift, DebouncerTests.swift, ExternalDiffToolTests.swift, GitClientDiffPathEncodingTests.swift.
  • User-facing behavior is documented in docs/components/diff-view.md.

Deviations from plan #

  • The original Cmd+] shortcut no longer exists. Show Diff is a config-driven keybinding (show_diff in supacode/App/AppShortcuts.swift, default ⌘⇧Y) after the keybinding system landed (012-keybinding-system).
  • "Preload all on open" was refined by the July wave: the cache now survives refresh() (with eviction of disappeared files) instead of being rebuilt from scratch, and per-file documents stream into the cache as they complete.
  • The diff badge / Show Diff entry points no longer open the window directly; they route through ExternalDiffToolClient, which dispatches to the built-in window or an external tool (amendment 002).

Open questions #

None.