Skip to content

Improve desktop branch picker, blame tooltip, and terminal suggestion UX#40

Merged
MocA-Love merged 9 commits intomainfrom
feat/desktop-current-branch-quick-pick
Apr 1, 2026
Merged

Improve desktop branch picker, blame tooltip, and terminal suggestion UX#40
MocA-Love merged 9 commits intomainfrom
feat/desktop-current-branch-quick-pick

Conversation

@MocA-Love
Copy link
Copy Markdown
Owner

@MocA-Love MocA-Love commented Apr 1, 2026

概要

  • desktop の branch picker フローを改善し、changes UI に表示する branch metadata を強化
  • blame tooltip に GitHub avatar 表示を追加し、時刻フォーマットを調整
  • terminal の shell history suggestions を Warp に近い挙動へ調整

Terminal の変更点

  • shell history suggestions が入力中に自動表示されないようにし、prompt 上で または を押した時だけ明示的に開くように変更
  • suggestion panel を即座に開けるよう、短い prefix や空 prefix でも history lookup できるように変更
  • suggestion panel で選択中の候補は Enter で実行、 は入力欄への補完のみ、EscCtrl+C では panel を閉じるように変更
  • 候補実行前に visual only の typing preview を追加し、shell へは最後にまとめて送信しつつ、terminal 上では候補が入力されているように見えるように変更
  • terminal settings の説明文と suggestion footer のヒントを新しい挙動に合わせて更新

テスト

  • 未実施。ローカル確認はユーザー実施想定。

Summary by CodeRabbit

  • New Features
    • Branch search, create, and improved branch selector UI
    • GitHub commit author lookup with avatars in blame tooltips
    • Terminal typing-preview animation and suggestion execution flow
    • Shell history suggestions now include timestamps and "last run" info
    • New compact file-list view (including a virtualized compact mode)

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 1, 2026

Warning

Rate limit exceeded

@MocA-Love has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 6 minutes and 26 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 6 minutes and 26 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6c4cb183-f1ae-4e1f-b5f9-85f2f0ed8bfa

📥 Commits

Reviewing files that changed from the base of the PR and between e47df34 and 51e8fea.

📒 Files selected for processing (1)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/hooks/useTerminalSuggestion.ts
📝 Walkthrough

Walkthrough

Adds branch search/create TRPC endpoints and safer git branch commands; enriches blame tooltips with cached GitHub commit-author avatars; reworks terminal history suggestions with typing-preview and keyboard changes; introduces a compact changes-list UI and threads worktreePath through blame/editor components.

Changes

Cohort / File(s) Summary
Branch management & git commands
apps/desktop/src/lib/trpc/routers/changes/branches.ts, apps/desktop/src/lib/trpc/routers/changes/security/git-commands.ts
Added searchRefs query and createBranch mutation; refactored persistence helpers for branch switching; introduced gitCreateBranch and improved gitSwitchBranch (input validation, local/remote checks, modern/legacy command fallbacks).
GitHub commit author caching & TRPC
apps/desktop/src/lib/trpc/routers/changes/git-blame.ts, apps/desktop/src/lib/trpc/routers/workspaces/utils/github/cache.ts
New getGitHubCommitAuthor TRPC procedure with tolerant parsing and validation; added GitHubCommitAuthor cache resource, cache-key helper, and read wrapper (TTL + max entries).
Blame plugin & editor wiring
apps/desktop/src/renderer/.../createBlamePlugin.ts, .../CodeEditor/CodeEditor.tsx, .../CodeMirrorDiffViewer.tsx, .../FileViewerContent.tsx
Threaded optional worktreePath through blame/editor components; BlameEntry may include authorAvatarUrl; blame plugin now can async-enrich avatars via TRPC with per-worktree caching and in-flight dedupe, and updates tooltip UI.
Terminal history, suggestion flow & preview
apps/desktop/src/main/lib/shell-history.ts, apps/desktop/src/renderer/.../Terminal/Terminal.tsx, .../TerminalSuggestion/TerminalSuggestion.tsx, .../TerminalTypingPreview/*, .../helpers.ts, .../hooks/useTerminalLifecycle.ts, .../hooks/useTerminalSuggestion.ts
Shell history now returns structured ShellHistoryEntry[] with timestamps; suggestion hook switched to structured TerminalHistorySuggestion[], removed xterm ghost-text coupling, added explicit open/dismiss, onExecuteCommand, openHistorySuggestions; added typing-preview component and wiring, and keyboard handler changes to support execute vs fill flows.
Changes header / branch selector UI
apps/desktop/src/renderer/.../ChangesHeader/ChangesHeader.tsx
Replaced BaseBranchSelector with CurrentBranchSelector supporting switch/create/create-from-ref/compare-base modes; integrates searchRefs, createBranch, updateBaseBranch, and expanded query invalidation/error handling.
Compact changes list view & related components
apps/desktop/src/renderer/.../FileList/*, .../compact-view.ts, .../FileItem.tsx, .../ViewModeToggle.tsx, apps/desktop/src/renderer/stores/changes/store.ts, .../index.ts
Added compact view mode: FileListCompact, FileListCompactVirtualized, sorting util, directoryLabel prop on FileItem, ViewModeToggle cycling through groupedcompacttree, and store/type updates to accept "compact".
Git client / binary file read
apps/desktop/src/lib/trpc/routers/workspaces/utils/git-client.ts, apps/desktop/src/lib/trpc/routers/changes/file-contents.ts
Replaced promisify-based exec with a typed execGitWithShellPathWithEncoding helper; added execGitWithShellPathBuffer and switched binary file read to use it (returns Buffer, base64-encoded).
UI props / small component changes
apps/desktop/src/renderer/.../SuggestionsSetting/SuggestionsSetting.tsx, apps/desktop/src/renderer/.../CodeEditor/CodeEditor.tsx, .../FileItem.tsx
Updated suggestions description text; added optional worktreePath prop to editor/diff viewer and forwarded it; FileItem accepts optional directoryLabel.
Types & helpers
various files (hooks/components)
Introduced/updated types: GitHubCommitAuthor, ShellHistoryEntry, TerminalHistorySuggestion, BlamePluginOptions, and modified keyboard/active-suggestion interfaces to support onExecute/open flows.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Blame as Blame Plugin
    participant TRPC as TRPC Endpoint
    participant GitHub as GitHub API
    participant Cache as CommitAuthor Cache

    User->>Blame: Open blame tooltip (missing avatar)
    Blame->>TRPC: getGitHubCommitAuthor(worktreePath, commitHash)
    TRPC->>Cache: lookup(worktreePath#commitHash)
    alt cache hit
        Cache-->>TRPC: cached author/null
    else
        TRPC->>GitHub: gh api repos/<owner>/<name>/commits/<sha>
        GitHub-->>TRPC: response (author/login, avatar_url)
        TRPC->>Cache: store result (TTL)
    end
    TRPC-->>Blame: GitHubCommitAuthor | null
    Blame->>Blame: update tooltip avatar (image or initials)
Loading
sequenceDiagram
    participant User
    participant TerminalComp as Terminal Component
    participant Suggestion as useTerminalSuggestion Hook
    participant TRPC as TRPC getSuggestions
    participant Preview as TerminalTypingPreview
    participant Xterm as Xterm Instance

    User->>TerminalComp: Press ↑
    TerminalComp->>Suggestion: openHistorySuggestions()
    Suggestion->>TRPC: getSuggestions(prefix)
    TRPC-->>Suggestion: suggestions[]
    Suggestion-->>TerminalComp: display suggestions
    User->>Suggestion: select + Enter
    Suggestion->>TerminalComp: onExecuteCommand(command, currentInput)
    TerminalComp->>Preview: animate typing suffix (~200ms)
    Preview->>Xterm: render preview at cursor
    TerminalComp->>Xterm: write command + \r
    TerminalComp->>Suggestion: clear/dismiss
Loading
sequenceDiagram
    participant User
    participant UI as Branch Create UI
    participant TRPC as TRPC Endpoint
    participant GitCmd as gitCreateBranch
    participant Git as Git Process
    participant DB as Worktree DB

    User->>UI: submit new branch (name, startPoint)
    UI->>TRPC: createBranch(worktreePath, branch, startPoint)
    TRPC->>Git: check branch existence (local/remote)
    alt branch exists
        Git-->>TRPC: exists
        TRPC-->>UI: { success: false }
    else
        TRPC->>GitCmd: gitCreateBranch(...)
        GitCmd->>Git: git switch -c / fallback checkout -b
        Git-->>GitCmd: switched/created
        GitCmd-->>TRPC: verified current branch
        TRPC->>DB: persist current branch (if registered)
        TRPC->>TRPC: clear status cache, invalidate queries
        TRPC-->>UI: { success: true, branch }
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

🐇 I hopped through branches, fetched an avatar bright,

Typed previews shimmered in the terminal light.
Cached commits, compact lists, and a cleaner git switch—
A rabbit’s small cheer for each helpful new stitch. 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 5.48% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the three main UX improvements in the changeset: branch picker, blame tooltip, and terminal suggestion behavior.
Description check ✅ Passed The description covers all major changes across terminal, branch picker, and blame tooltip features with clear explanations in both Japanese and structured format.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/desktop-current-branch-quick-pick

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@MocA-Love MocA-Love changed the title Align terminal history suggestions with Warp Improve desktop branch picker, blame tooltip, and terminal suggestion UX Apr 1, 2026
@MocA-Love MocA-Love self-assigned this Apr 1, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (4)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/TerminalTypingPreview/TerminalTypingPreview.tsx (1)

10-27: Consider extracting getCellDimensions to a shared utility.

This helper function is duplicated from TerminalSuggestion.tsx. Extracting it to a shared location (e.g., a utils.ts file in the Terminal folder) would reduce duplication and centralize any future maintenance of xterm internal API access.

♻️ Suggested extraction

Create a shared utility in the Terminal folder:

// utils.ts or xterm-utils.ts
import type { Terminal as XTerm } from "@xterm/xterm";

export function getCellDimensions(
  xterm: XTerm,
): { width: number; height: number } | null {
  const dimensions = (
    xterm as unknown as {
      _core?: {
        _renderService?: {
          dimensions?: { css: { cell: { width: number; height: number } } };
        };
      };
    }
  )._core?._renderService?.dimensions;

  if (!dimensions?.css?.cell) return null;
  const { width, height } = dimensions.css.cell;
  if (width <= 0 || height <= 0) return null;
  return { width, height };
}

Then import it in both TerminalSuggestion.tsx and TerminalTypingPreview.tsx.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/TerminalTypingPreview/TerminalTypingPreview.tsx`
around lines 10 - 27, The getCellDimensions function is duplicated between
TerminalTypingPreview.tsx and TerminalSuggestion.tsx; extract it into a shared
utility (e.g., Terminal/utils.ts or Terminal/xterm-utils.ts) as an exported
function getCellDimensions(xterm: XTerm): { width: number; height: number } |
null, move the implementation there, and update both TerminalTypingPreview.tsx
and TerminalSuggestion.tsx to import and use that utility instead of their local
copies; ensure the exported function keeps the same signature and behavior so
callers (getCellDimensions) continue to work unchanged.
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx (1)

341-413: Animation logic is correct; consider requestAnimationFrame for smoother visuals.

The typing preview animation correctly calculates progress based on elapsed time and incrementally reveals characters. Using setTimeout(..., 0) works but may produce inconsistent frame timing.

♻️ Using requestAnimationFrame for smoother animation
-			const tick = () => {
-				const elapsed = performance.now() - startTime;
-				const progress = Math.min(1, elapsed / durationMs);
-				const visibleLength = Math.max(
-					1,
-					Math.min(totalSteps, Math.ceil(progress * totalSteps)),
-				);
-				setTypingPreviewText(suffix.slice(0, visibleLength));
-
-				if (progress >= 1) {
-					finish();
-					return;
-				}
-
-				typingPreviewTimeoutRef.current = setTimeout(tick, 0);
-			};
-
-			setTypingPreviewText(suffix.slice(0, 1));
-			typingPreviewTimeoutRef.current = setTimeout(tick, 0);
+			let rafId: number | null = null;
+
+			const tick = () => {
+				const elapsed = performance.now() - startTime;
+				const progress = Math.min(1, elapsed / durationMs);
+				const visibleLength = Math.max(
+					1,
+					Math.min(totalSteps, Math.ceil(progress * totalSteps)),
+				);
+				setTypingPreviewText(suffix.slice(0, visibleLength));
+
+				if (progress >= 1) {
+					finish();
+					return;
+				}
+
+				rafId = requestAnimationFrame(tick);
+			};
+
+			setTypingPreviewText(suffix.slice(0, 1));
+			rafId = requestAnimationFrame(tick);

Note: You'd need to adjust the ref and cleanup logic to handle cancelAnimationFrame instead of clearTimeout.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx`
around lines 341 - 413, Replace the setTimeout-based animation in
handleSuggestionExecute with requestAnimationFrame for smoother frames: change
typingPreviewTimeoutRef to store an animation frame id (number), clear any
existing frame via cancelAnimationFrame at the start, and use
requestAnimationFrame inside tick instead of setTimeout; update finish to null
the ref and ensure cancelAnimationFrame is used in any cleanup, and keep using
startTime, duration (TYPING_PREVIEW_MAX_DURATION_MS), totalSteps,
setTypingPreviewText, finish, and tick semantics otherwise so the reveal logic
stays identical.
apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/ChangesHeader/ChangesHeader.tsx (1)

759-765: Consider simplifying nested ternary.

The four-level nested ternary is harder to scan. A lookup object or switch would improve readability.

💡 Alternative with object lookup
const modeRenderers = {
  default: renderDefaultList,
  create: renderCreateList,
  "create-from-ref": renderCreateFromRefList,
  "compare-base": renderCompareBaseList,
} as const;

// In JSX:
{modeRenderers[mode]()}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/ChangesHeader/ChangesHeader.tsx`
around lines 759 - 765, Replace the nested ternary expression that switches on
mode with a clearer mapping or switch: create a lookup object (e.g.,
modeRenderers) that maps the mode values to the corresponding renderer functions
renderDefaultList, renderCreateList, renderCreateFromRefList, and
renderCompareBaseList, then invoke the selected renderer in JSX via
modeRenderers[mode]() (or use a switch that returns the appropriate render
function). Ensure the mapping keys cover "default", "create", "create-from-ref",
and the compare case and handle unknown modes safely (fallback to
renderDefaultList) to avoid runtime errors.
apps/desktop/src/lib/trpc/routers/changes/branches.ts (1)

394-477: Consider logging suppressed errors for debuggability.

The empty catch {} blocks (lines 415, 452, 476) silently discard errors. While graceful degradation is appropriate here, consider adding debug-level logging to aid troubleshooting when ref enumeration unexpectedly returns partial results.

💡 Example for debug logging
 	try {
 		for (const localBranch of await getRefEntries(git, {
 			refPath: "refs/heads/",
 			dateField: "committerdate",
 			authorField: "authorname",
 		})) {
 			// ...
 		}
-	} catch {}
+	} catch (error) {
+		// Local branches unavailable, continue with partial results
+		console.debug("Failed to enumerate local branches:", error);
+	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/src/lib/trpc/routers/changes/branches.ts` around lines 394 -
477, The three empty catch blocks that swallow errors during ref enumeration
(around the loops that call getRefEntries for refs/heads, refs/remotes/origin,
and refs/tags) should capture the error and emit a debug-level log so partial
failures are visible; replace each catch {} with catch (err) { /* use existing
logger (e.g. processLogger.debug or console.debug) */ logger?.debug("enumerating
refs failed in <loop context>:", err); } referencing getRefEntries,
matchesSearch, and the refs push logic so you can see which enumeration (local,
remote, tag) failed.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/createBlamePlugin.ts`:
- Around line 83-87: commitAuthorCache currently stores null on lookup failures
which permanently negative-caches transient errors; change caching so only
successful GitHubCommitAuthor results are stored (i.e., make commitAuthorCache a
Map<string, GitHubCommitAuthor> and never set null), and keep
commitAuthorInFlight as-is to coalesce requests; when a fetch fails, ensure you
reject/clear the in-flight entry and do not insert a null into commitAuthorCache
(or alternatively attach a short TTL to negative entries), updating the code
paths that set cache entries (the places that currently write null into
commitAuthorCache and where in-flight promises are resolved/rejected) to only
cache on success.

In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/ChangesHeader/ChangesHeader.tsx`:
- Around line 373-392: The sort comparator inside compareBaseBranches contains
dead checks against branchData?.defaultBranch even though that branch was
already removed by the filter; remove the two comparisons that check a ===
branchData?.defaultBranch and b === branchData?.defaultBranch from the
comparator in compareBaseBranches (or alternatively stop filtering out
branchData?.defaultBranch earlier if you intended to keep it) so the sort logic
only compares against effectiveBaseBranch and falls back to localeCompare.

---

Nitpick comments:
In `@apps/desktop/src/lib/trpc/routers/changes/branches.ts`:
- Around line 394-477: The three empty catch blocks that swallow errors during
ref enumeration (around the loops that call getRefEntries for refs/heads,
refs/remotes/origin, and refs/tags) should capture the error and emit a
debug-level log so partial failures are visible; replace each catch {} with
catch (err) { /* use existing logger (e.g. processLogger.debug or console.debug)
*/ logger?.debug("enumerating refs failed in <loop context>:", err); }
referencing getRefEntries, matchesSearch, and the refs push logic so you can see
which enumeration (local, remote, tag) failed.

In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx`:
- Around line 341-413: Replace the setTimeout-based animation in
handleSuggestionExecute with requestAnimationFrame for smoother frames: change
typingPreviewTimeoutRef to store an animation frame id (number), clear any
existing frame via cancelAnimationFrame at the start, and use
requestAnimationFrame inside tick instead of setTimeout; update finish to null
the ref and ensure cancelAnimationFrame is used in any cleanup, and keep using
startTime, duration (TYPING_PREVIEW_MAX_DURATION_MS), totalSteps,
setTypingPreviewText, finish, and tick semantics otherwise so the reveal logic
stays identical.

In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/TerminalTypingPreview/TerminalTypingPreview.tsx`:
- Around line 10-27: The getCellDimensions function is duplicated between
TerminalTypingPreview.tsx and TerminalSuggestion.tsx; extract it into a shared
utility (e.g., Terminal/utils.ts or Terminal/xterm-utils.ts) as an exported
function getCellDimensions(xterm: XTerm): { width: number; height: number } |
null, move the implementation there, and update both TerminalTypingPreview.tsx
and TerminalSuggestion.tsx to import and use that utility instead of their local
copies; ensure the exported function keeps the same signature and behavior so
callers (getCellDimensions) continue to work unchanged.

In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/ChangesHeader/ChangesHeader.tsx`:
- Around line 759-765: Replace the nested ternary expression that switches on
mode with a clearer mapping or switch: create a lookup object (e.g.,
modeRenderers) that maps the mode values to the corresponding renderer functions
renderDefaultList, renderCreateList, renderCreateFromRefList, and
renderCompareBaseList, then invoke the selected renderer in JSX via
modeRenderers[mode]() (or use a switch that returns the appropriate render
function). Ensure the mapping keys cover "default", "create", "create-from-ref",
and the compare case and handle unknown modes safely (fallback to
renderDefaultList) to avoid runtime errors.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ba6db6dc-e412-4f1e-8410-8d455a4fdf00

📥 Commits

Reviewing files that changed from the base of the PR and between 8fa7010 and d84c725.

📒 Files selected for processing (18)
  • apps/desktop/src/lib/trpc/routers/changes/branches.ts
  • apps/desktop/src/lib/trpc/routers/changes/git-blame.ts
  • apps/desktop/src/lib/trpc/routers/changes/security/git-commands.ts
  • apps/desktop/src/lib/trpc/routers/workspaces/utils/github/cache.ts
  • apps/desktop/src/main/lib/shell-history.ts
  • apps/desktop/src/renderer/routes/_authenticated/settings/terminal/components/TerminalSettings/components/SuggestionsSetting/SuggestionsSetting.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/CodeMirrorDiffViewer/CodeMirrorDiffViewer.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/FileViewerContent.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/TerminalSuggestion/TerminalSuggestion.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/TerminalTypingPreview/TerminalTypingPreview.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/TerminalTypingPreview/index.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/helpers.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/hooks/useTerminalLifecycle.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/hooks/useTerminalSuggestion.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/ChangesHeader/ChangesHeader.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/CodeEditor.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/createBlamePlugin.ts
💤 Files with no reviewable changes (1)
  • apps/desktop/src/main/lib/shell-history.ts

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (4)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/TerminalSuggestion/TerminalSuggestion.tsx (3)

82-83: Potential stale refs when suggestions array shrinks.

itemTextRefs.current is an array that grows as refs are assigned, but it's never trimmed when suggestions shrinks. This could leave stale references in the array, though it shouldn't cause issues since you only access itemTextRefs.current[selectedIndex] which is bounded by suggestions.length.

♻️ Consider trimming refs array on render
+	// Trim refs array to match suggestions length
+	itemTextRefs.current.length = suggestions.length;
+
 	useEffect(() => {
 		const selectedText = itemTextRefs.current[selectedIndex];
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/TerminalSuggestion/TerminalSuggestion.tsx`
around lines 82 - 83, The itemTextRefs ref array can accumulate stale entries
when suggestions shrinks; add logic to trim itemTextRefs.current to match
suggestions.length (e.g. in an effect watching suggestions or before mapping) so
that itemTextRefs.current = itemTextRefs.current.slice(0, suggestions.length)
and ensure accesses like itemTextRefs.current[selectedIndex] remain bounded;
update the ref-trimming logic around the existing itemTextRefs (useRef) and
where suggestions and selectedIndex are used.

197-234: Consider guarding against prefix mismatch in suggestion display.

If suggestion.command doesn't start with prefix (which shouldn't happen normally, but could occur during rapid state updates), slice(prefix.length) would display incorrect text.

🛡️ Proposed defensive rendering
-						<span style={{ color: "#89b4fa" }}>{prefix}</span>
-						{suggestion.command.slice(prefix.length)}
+						{suggestion.command.startsWith(prefix) ? (
+							<>
+								<span style={{ color: "#89b4fa" }}>{prefix}</span>
+								{suggestion.command.slice(prefix.length)}
+							</>
+						) : (
+							suggestion.command
+						)}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/TerminalSuggestion/TerminalSuggestion.tsx`
around lines 197 - 234, In TerminalSuggestion (the suggestions.map rendering
inside the TerminalSuggestion component), guard the substring operation on
suggestion.command.slice(prefix.length) by first checking that
suggestion.command actually starts with prefix (e.g.,
suggestion.command?.startsWith(prefix)); if it does not, fall back to rendering
the full suggestion.command (or a safe truncated version) to avoid showing
incorrect text during rapid state updates—update the span that currently renders
prefix + suggestion.command.slice(prefix.length) to use this guarded logic and
keep itemTextRefs/selectedIndex handling unchanged.

36-72: Consider handling edge case for future timestamps.

If lastRunAt is in the future (e.g., due to clock skew or corrupted history), diffMs becomes negative, causing unexpected output like -1s ago. Consider adding a guard.

🛡️ Proposed defensive check
 function formatLastRunAgo(lastRunAt: number | null): string {
 	if (!lastRunAt) return "";
 
 	const diffMs = Date.now() - lastRunAt;
+	if (diffMs < 0) return "";
+
 	const minuteMs = 60_000;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/TerminalSuggestion/TerminalSuggestion.tsx`
around lines 36 - 72, The function formatLastRunAgo currently computes diffMs =
Date.now() - lastRunAt and can produce negative values when lastRunAt is in the
future; update formatLastRunAgo to guard against future timestamps by clamping
diffMs to 0 (or treating negatives as "just now") before the unit checks so you
never return negative durations; change logic inside formatLastRunAgo to set
diffMs = Math.max(0, Date.now() - lastRunAt) and then proceed with the existing
minute/hour/day/week/month/year branches.
apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/FileList/FileListCompact.tsx (1)

41-41: Consider memoizing sortedFiles for consistency with the virtualized variant.

FileListCompactVirtualized uses useMemo for the sorted files, but this component re-sorts on every render. While the non-virtualized path is used for smaller lists (<200 files), memoization would maintain consistency and avoid unnecessary re-computation.

♻️ Suggested change
+import { useMemo } from "react";
 import type { ExternalApp } from "@superset/local-db";
 import type { ChangeCategory, ChangedFile } from "shared/changes-types";
 import { FileItem } from "../FileItem";
 import { getDirectoryLabel, sortFilesForCompactView } from "./compact-view";
 }: FileListCompactProps) {
-	const sortedFiles = sortFilesForCompactView(files);
+	const sortedFiles = useMemo(() => sortFilesForCompactView(files), [files]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/FileList/FileListCompact.tsx`
at line 41, FileListCompact re-sorts on every render; wrap the call to
sortFilesForCompactView(files) in a useMemo to match FileListCompactVirtualized
and avoid unnecessary work. Replace the direct assignment to sortedFiles with a
memoized value using React.useMemo(() => sortFilesForCompactView(files),
[files]) (or import/useMemo) inside the FileListCompact component so sorting
only runs when files changes.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/TerminalSuggestion/TerminalSuggestion.tsx`:
- Around line 94-104: The useEffect in TerminalSuggestion is over-specified:
remove unneeded dependencies `prefix` and `suggestions` from the dependency
array so it only depends on `selectedIndex` (and any refs you need); update the
effect that reads `itemTextRefs.current[selectedIndex]` and calls
`setIsSelectedTruncated(...)` to run only when `selectedIndex` changes (or, if
truncation should update when the selected item content changes, replace
`suggestions` with the specific value `suggestions[selectedIndex]?.command` in
the dependency array); modify the dependency array for the useEffect in
TerminalSuggestion.tsx accordingly.

In `@apps/desktop/src/renderer/stores/changes/store.ts`:
- Line 17: The store defines a broader FileListViewMode while sortFiles expects
a narrower one, causing unsafe passing of fileListViewMode into sortFiles; fix
by consolidating the type: export a single FileListViewMode from one module
(e.g., create or use the type in sortFiles.ts) and import that type into
store.ts so fileListViewMode uses the exact same union, or alternatively change
the store's usage to map/guard the "compact" value to a supported mode before
calling sortFiles (reference symbols: FileListViewMode, fileListViewMode,
sortFiles, sortFiles.ts).

---

Nitpick comments:
In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/TerminalSuggestion/TerminalSuggestion.tsx`:
- Around line 82-83: The itemTextRefs ref array can accumulate stale entries
when suggestions shrinks; add logic to trim itemTextRefs.current to match
suggestions.length (e.g. in an effect watching suggestions or before mapping) so
that itemTextRefs.current = itemTextRefs.current.slice(0, suggestions.length)
and ensure accesses like itemTextRefs.current[selectedIndex] remain bounded;
update the ref-trimming logic around the existing itemTextRefs (useRef) and
where suggestions and selectedIndex are used.
- Around line 197-234: In TerminalSuggestion (the suggestions.map rendering
inside the TerminalSuggestion component), guard the substring operation on
suggestion.command.slice(prefix.length) by first checking that
suggestion.command actually starts with prefix (e.g.,
suggestion.command?.startsWith(prefix)); if it does not, fall back to rendering
the full suggestion.command (or a safe truncated version) to avoid showing
incorrect text during rapid state updates—update the span that currently renders
prefix + suggestion.command.slice(prefix.length) to use this guarded logic and
keep itemTextRefs/selectedIndex handling unchanged.
- Around line 36-72: The function formatLastRunAgo currently computes diffMs =
Date.now() - lastRunAt and can produce negative values when lastRunAt is in the
future; update formatLastRunAgo to guard against future timestamps by clamping
diffMs to 0 (or treating negatives as "just now") before the unit checks so you
never return negative durations; change logic inside formatLastRunAgo to set
diffMs = Math.max(0, Date.now() - lastRunAt) and then proceed with the existing
minute/hour/day/week/month/year branches.

In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/FileList/FileListCompact.tsx`:
- Line 41: FileListCompact re-sorts on every render; wrap the call to
sortFilesForCompactView(files) in a useMemo to match FileListCompactVirtualized
and avoid unnecessary work. Replace the direct assignment to sortedFiles with a
memoized value using React.useMemo(() => sortFilesForCompactView(files),
[files]) (or import/useMemo) inside the FileListCompact component so sorting
only runs when files changes.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1ca19d3c-aa68-468e-bbdf-01e1f8091fe9

📥 Commits

Reviewing files that changed from the base of the PR and between d84c725 and 6d22da6.

📒 Files selected for processing (12)
  • apps/desktop/src/main/lib/shell-history.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/TerminalSuggestion/TerminalSuggestion.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/hooks/useTerminalSuggestion.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/FileItem/FileItem.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/FileList/FileList.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/FileList/FileListCompact.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/FileList/FileListCompactVirtualized.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/FileList/compact-view.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/FileList/index.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/ViewModeToggle/ViewModeToggle.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/types.ts
  • apps/desktop/src/renderer/stores/changes/store.ts
✅ Files skipped from review due to trivial changes (1)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/types.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/hooks/useTerminalSuggestion.ts

Comment thread apps/desktop/src/renderer/stores/changes/store.ts
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/TerminalSuggestion/TerminalSuggestion.tsx (1)

97-107: ⚠️ Potential issue | 🟡 Minor

Recompute truncation when the selected command changes.

This effect only depends on selectedIndex. When a refresh swaps in a different suggestions[selectedIndex] while the index stays the same, isSelectedTruncated is reused for the wrong command and the overflow tooltip can get stuck in the previous state.

🔧 Suggested fix
-	}, [selectedIndex]);
+	}, [selectedIndex, selectedCommand]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/TerminalSuggestion/TerminalSuggestion.tsx`
around lines 97 - 107, The effect that computes truncation for the selected item
(using itemTextRefs.current[selectedIndex] and calling setIsSelectedTruncated)
only lists selectedIndex as a dependency, so when the suggestion object at that
index changes the truncation state is not recomputed; update the dependency
array to include the suggestion value (e.g., suggestions or
suggestions[selectedIndex]) so the useEffect reruns when the selected command
changes, ensuring itemTextRefs, selectedIndex, suggestions, and
setIsSelectedTruncated are considered when recalculating truncation.
🧹 Nitpick comments (2)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ChangesContent/utils/sortFiles.ts (1)

105-113: Handle "compact" explicitly in sortFiles instead of relying on fallback.

Now that FileListViewMode includes "compact", the current tree vs else branch makes compact behavior implicit. Please add an explicit "compact" branch (even if it currently aliases grouped sorting) so behavior stays intentional and future-safe.

♻️ Suggested refactor
 export function sortFiles(
 	files: ChangedFile[],
 	viewMode: FileListViewMode,
 ): ChangedFile[] {
-	return viewMode === "tree"
-		? sortFilesTreeOrder(files)
-		: sortFilesGroupedOrder(files);
+	switch (viewMode) {
+		case "tree":
+			return sortFilesTreeOrder(files);
+		case "grouped":
+			return sortFilesGroupedOrder(files);
+		case "compact":
+			// Keep explicit even if behavior currently matches grouped.
+			return sortFilesGroupedOrder(files);
+		default: {
+			const exhaustiveCheck: never = viewMode;
+			return exhaustiveCheck;
+		}
+	}
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/ChangesContent/utils/sortFiles.ts`
around lines 105 - 113, The sortFiles function currently uses a binary branch
for viewMode which treats "compact" implicitly; update sortFiles to explicitly
handle "compact" by adding a case for viewMode === "compact" (even if it simply
returns sortFilesGroupedOrder(files)) so behavior is intentional and
future-safe; modify the conditional in sortFiles to check viewMode === "tree",
viewMode === "compact", and otherwise call sortFilesGroupedOrder(files),
referencing the existing sortFilesTreeOrder and sortFilesGroupedOrder functions.
apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/createBlamePlugin.ts (1)

30-36: Consider extracting locale to a shared constant or i18n configuration.

The date formatter uses hardcoded "ja-JP-u-hc-h24" locale. While consistent with the Japanese strings in formatTimeAgo, centralizing locale configuration would ease future i18n changes.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/createBlamePlugin.ts`
around lines 30 - 36, Extract the hardcoded locale string into a shared constant
(e.g., BLAME_DATE_LOCALE or a value in the app i18n config) and use that
constant when creating blameDateFormatter so locale changes are centralized;
update the instantiation in createBlamePlugin (the blameDateFormatter
declaration) to reference the shared locale constant and, if applicable, replace
any other direct uses of "ja-JP-u-hc-h24" such as in formatTimeAgo to the same
constant to ensure consistency across the codebase.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/hooks/useTerminalSuggestion.ts`:
- Around line 67-77: The async fetchSuggestions race can repopulate
historySuggestions after dismiss/execute because those only enqueue state
updates; update dismiss and execute to immediately flip the open-guard ref
(isOpenRef.current = false) and/or increment a local request token before
triggering state updates so any outstanding fetchSuggestions checks (which
currently read isOpenRef.current) will bail out; ensure fetchSuggestions
verifies that token or isOpenRef.current right before calling
setHistorySuggestions and that dismiss clears fetchTimerRef and resets the token
to prevent late responses from resurrecting the panel (references: dismiss,
execute, fetchSuggestions, isOpenRef, fetchTimerRef, setHistorySuggestions,
lastPrefixRef).
- Around line 193-208: The accept() handler uses a stale prefix from
lastPrefixRef.current and skips on empty prompts; change it to read the live
input buffer (commandBufferRef.current) instead of lastPrefixRef.current, remove
the truthy check so acceptance works when the prompt is empty, compute suffix =
item.command.slice(currentInput.length) against that live prefix, call
onAcceptWriteRef.current(suffix) and update commandBufferRef.current and
lastPrefixRef.current to item.command as before, then clear suggestions with
setHistorySuggestions(EMPTY) and reset setSelectedIndex(0); keep references to
selectedIndexRef.current and historySuggestionsRef.current to locate the code.

---

Duplicate comments:
In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/TerminalSuggestion/TerminalSuggestion.tsx`:
- Around line 97-107: The effect that computes truncation for the selected item
(using itemTextRefs.current[selectedIndex] and calling setIsSelectedTruncated)
only lists selectedIndex as a dependency, so when the suggestion object at that
index changes the truncation state is not recomputed; update the dependency
array to include the suggestion value (e.g., suggestions or
suggestions[selectedIndex]) so the useEffect reruns when the selected command
changes, ensuring itemTextRefs, selectedIndex, suggestions, and
setIsSelectedTruncated are considered when recalculating truncation.

---

Nitpick comments:
In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/ChangesContent/utils/sortFiles.ts`:
- Around line 105-113: The sortFiles function currently uses a binary branch for
viewMode which treats "compact" implicitly; update sortFiles to explicitly
handle "compact" by adding a case for viewMode === "compact" (even if it simply
returns sortFilesGroupedOrder(files)) so behavior is intentional and
future-safe; modify the conditional in sortFiles to check viewMode === "tree",
viewMode === "compact", and otherwise call sortFilesGroupedOrder(files),
referencing the existing sortFilesTreeOrder and sortFilesGroupedOrder functions.

In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/createBlamePlugin.ts`:
- Around line 30-36: Extract the hardcoded locale string into a shared constant
(e.g., BLAME_DATE_LOCALE or a value in the app i18n config) and use that
constant when creating blameDateFormatter so locale changes are centralized;
update the instantiation in createBlamePlugin (the blameDateFormatter
declaration) to reference the shared locale constant and, if applicable, replace
any other direct uses of "ja-JP-u-hc-h24" such as in formatTimeAgo to the same
constant to ensure consistency across the codebase.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0a7ab418-8f5e-47e3-a3c9-14fc8c6ce789

📥 Commits

Reviewing files that changed from the base of the PR and between 6d22da6 and e47df34.

📒 Files selected for processing (13)
  • apps/desktop/src/lib/trpc/routers/changes/file-contents.ts
  • apps/desktop/src/lib/trpc/routers/changes/git-blame.ts
  • apps/desktop/src/lib/trpc/routers/workspaces/utils/git-client.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ChangesContent/utils/sortFiles.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/CodeMirrorDiffViewer/CodeMirrorDiffViewer.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/FileViewerContent.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/TerminalSuggestion/TerminalSuggestion.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/hooks/useTerminalSuggestion.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/FileList/index.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/ViewModeToggle/ViewModeToggle.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/CodeEditor.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/createBlamePlugin.ts
✅ Files skipped from review due to trivial changes (2)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/FileViewerContent.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/FileList/index.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/CodeMirrorDiffViewer/CodeMirrorDiffViewer.tsx
  • apps/desktop/src/lib/trpc/routers/changes/git-blame.ts

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant