feat(desktop): シェル履歴サジェスト & Branch PR表示対応#24
Conversation
~/.zsh_history から入力中のプレフィックスに一致するコマンドを候補表示。 ゴーストテキスト+ドロップダウンリストで候補を表示し、↑↓で選択、→で確定、 Escで破棄。設定画面からON/OFF切り替え可能(デフォルトON)。 末尾到達時に追加候補を自動読み込み。
getGitHubStatus / getGitHubPRComments が worktree レコード必須だった 制限を解消し、type=branch のワークスペースでは mainRepoPath に フォールバックして gh CLI を実行するように変更
候補が8件を超える場合、リストが固定高さ内でスクロールするように変更。 選択中の項目は自動スクロールで追従。フッターは常に表示。
- ゴーストテキストを廃止し候補1件でもドロップダウンを表示 - シェルのzsh-autosuggestions との視覚的衝突を解消 - .zshrc wrapper でユーザーの zshrc source 後に ZSH_AUTOSUGGEST_STRATEGY を空にして無効化 - README にシェル履歴サジェスト機能を追記
Supersetターミナル内でzsh-autosuggestionsのサジェスト関数をno-op化し、 ビルトインサジェストとの視覚的な衝突を防止。SUPERSET_DISABLE_ZSH_AUTOSUGGEST 環境変数で制御し、ユーザーの.zshrcは変更しない。
関数のno-opではなくプラグイン公式の_zsh_autosuggest_disable()を使用。 これによりZLEウィジェットバインドが正しく復元され、self-insert ウィジェットチェーンの破損による入力重複を防止。
- ゴーストテキストを完全に廃止し、ドロップダウンUIのみに統一 - 選択中コマンドのフルプレビューを上部に表示(補完部分を緑色で強調) - 候補1件でもドロップダウンを表示 - ↑キーのループを廃止(先頭で止まる) - zsh-autosuggestions無効化を削除(共存可能) - README更新
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (3)
✅ Files skipped from review due to trivial changes (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughAdds terminal shell-history suggestions (backend parser, TRPC endpoint, frontend hook, UI, keyboard handling, settings toggle), integrates suggestion lifecycle into terminal input handling, disables zsh-autosuggestions when toggled, and adds a PR-display fallback for branch workspaces using project mainRepoPath. Changes
Sequence DiagramsequenceDiagram
participant User
participant Terminal
participant UseHook as useTerminalSuggestion
participant KB as KeyboardHandler
participant TRPC as TRPC Client
participant Backend as ShellHistoryBackend
participant UI as TerminalSuggestion
User->>Terminal: Type characters
Terminal->>UseHook: Read commandBufferRef (poll)
UseHook->>UseHook: Prefix >= 2? debounce
alt Query needed
UseHook->>TRPC: getSuggestions(prefix, offset)
TRPC->>Backend: query cached history
Backend-->>TRPC: return suggestions
TRPC-->>UseHook: suggestions
UseHook->>UI: update displaySuggestions, selectedIndex
UI-->>User: show dropdown
end
User->>KB: ArrowDown / ArrowUp
KB->>UseHook: selectNext / selectPrev
UseHook->>UI: update highlight
User->>KB: ArrowRight
KB->>UseHook: onAccept
UseHook->>Terminal: write suffix (onAcceptWrite)
UseHook->>UI: clear suggestions
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (6)
apps/desktop/src/lib/trpc/routers/workspaces/procedures/git-status.ts (1)
187-195: Extract duplicatedrepoPathfallback resolution into a helper.The branch fallback logic is duplicated in both queries. A small helper would reduce drift risk and keep behavior aligned.
Also applies to: 230-236
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/desktop/src/lib/trpc/routers/workspaces/procedures/git-status.ts` around lines 187 - 195, Extract the duplicated repoPath fallback logic in git-status into a small helper (e.g., resolveRepoPath(workspace, worktree)) that returns worktree?.path or, if missing and workspace.type === "branch", uses getProject(workspace.projectId)?.mainRepoPath or null; replace both occurrences that currently compute repoPath (the block using worktree?.path and the branch fallback) with calls to this helper to keep behavior consistent and reduce duplication.apps/desktop/src/lib/trpc/routers/terminal/terminal.ts (1)
423-427: Consider moving input validation to the tRPC boundary for consistency, but note existing safeguards.The
getSuggestionsimplementation already validates thatprefixmust be non-empty and at least 2 characters long (line 87 ofshell-history.ts). Offset values (including negative integers) are naturally safe due to the iteration logic. While API-level validation is a good practice for fail-fast and clearer contracts, the actual risks of "heavy scans" or "invalid paging" do not materialize—history is cached, empty prefixes are rejected at implementation, and pagination naturally limits results to 8 entries per page.One minor edge case: a whitespace-only prefix (e.g.,
" ") would pass the length check and reach the implementation, which would also reject it (since it won't match any command). Adding.trim()at the API boundary would catch this earlier.Optional improvement (not a critical fix)
- .input(z.object({ prefix: z.string(), offset: z.number().optional() })) + .input( + z.object({ + prefix: z.string().trim().min(2), + offset: z.number().int().min(0).optional(), + }), + )🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/desktop/src/lib/trpc/routers/terminal/terminal.ts` around lines 423 - 427, The router input schema currently accepts whitespace-only prefixes; update the tRPC input validation for this endpoint by trimming and validating prefix at the boundary: change the .input(z.object({ prefix: z.string(), offset: z.number().optional() })) to trim the prefix (e.g., using z.preprocess or z.string().transform to .trim()) and enforce a minimum length (e.g., .min(2)) so whitespace-only values are rejected before calling getSuggestions; keep offset as optional. Reference the router input schema and the getSuggestions call to locate the change.apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/TerminalSuggestion/TerminalSuggestion.tsx (2)
88-89: Consider using theme colors instead of hardcoded values.The hardcoded background color
rgba(30, 30, 46, 0.92)matches a dark theme but may clash with light terminal themes. Consider deriving colors fromxterm.options.themefor better consistency.🤖 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 88 - 89, The inline hardcoded backgroundColor in the TerminalSuggestion component should be replaced with a value derived from the terminal theme (e.g., from the xterm instance's options.theme) to avoid clashing with light themes; update the TerminalSuggestion component to read the terminal theme (xterm.options.theme or a theme prop passed into TerminalSuggestion), compute a suitable background (fallback to the existing rgba if theme value is missing), and use that computed value for the style.backgroundColor instead of "rgba(30, 30, 46, 0.92)"; ensure the change references the TerminalSuggestion component and the backgroundColor style property so it will adapt to xterm.options.theme values.
15-32: Accessing xterm internals for cell dimensions is fragile.The
_core._renderService.dimensionspath is not part of xterm.js's public API and may break in future versions. This is a known limitation (also used inhelpers.ts:817), but worth adding a comment noting the dependency.📝 Suggested documentation
function getCellDimensions( xterm: XTerm, ): { width: number; height: number } | null { + // Note: xterm.js does not expose a public API for cell dimensions. + // This internal path may break in future versions. const dimensions = ( xterm as unknown as {🤖 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 15 - 32, getCellDimensions currently relies on the private xterm internals path _core._renderService?.dimensions on the XTerm instance which is brittle; update the getCellDimensions function to include a clear comment above it stating this dependency on xterm private API (mentioning _core._renderService?.dimensions and that it mirrors the same approach in helpers.ts usage), and document the fallback/guarding behavior already present (returning null on missing/invalid dimensions) so future maintainers know this is intentional and where to look if xterm internals change.apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/helpers.ts (1)
584-590: ArrowLeft dismisses suggestions even when editing mid-command.When the user presses ArrowLeft to navigate within their typed input (not at the beginning), the suggestions are dismissed. This might be surprising if the user is editing the middle of their command and expects suggestions to persist.
Consider only dismissing on ArrowLeft when the cursor is at position 0, or simply not dismissing on ArrowLeft at all (only Escape).
♻️ Proposed change: Only dismiss on Escape
// Dismiss suggestion on Escape or left arrow - if ( - noModifiers && - (event.key === "ArrowLeft" || event.key === "Escape") - ) { + if (noModifiers && event.key === "Escape") { options.activeSuggestionRef?.current?.onDismiss(); }🤖 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/helpers.ts` around lines 584 - 590, The current key handler dismisses suggestions on ArrowLeft as well as Escape, causing suggestions to disappear when users move the caret mid-input; update the condition around noModifiers && (event.key === "ArrowLeft" || event.key === "Escape") so it only calls options.activeSuggestionRef?.current?.onDismiss() for Escape (i.e., event.key === "Escape"), or if you prefer to keep ArrowLeft behavior only dismiss when the caret is at start, check the input/textarea selection start (e.g., event.target.selectionStart === 0) before calling options.activeSuggestionRef?.current?.onDismiss(); modify the key handling block accordingly.apps/desktop/src/main/lib/shell-history.ts (1)
36-42: Consider memory bounds for very large history files.For users with extremely large history files (e.g.,
HISTSIZE=100000), reading the entire file into memory could cause temporary memory pressure. The current 10,000 entry cap on the deduplicated result is good, but the intermediateentriesarray could grow large.This is likely acceptable for typical usage, but worth noting if issues arise.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/desktop/src/main/lib/shell-history.ts` around lines 36 - 42, The code reads the entire zsh history into memory via readFile(zshPath, "utf-8") before calling parseZshHistory, which can blow up for very large history files; change parseZshHistory or the caller to stream-process the file instead (use createReadStream + readline or a tailing approach) so you accumulate deduplicated entries incrementally (using a Set) and stop reading once you reach the deduplicated cap (e.g., 10_000) to avoid unbounded intermediate arrays; update functions referenced (zshPath, parseZshHistory, any callers of readFile) to support streaming and early exit.
🤖 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/routes/_authenticated/settings/terminal/components/TerminalSettings/components/SuggestionsSetting.tsx`:
- Around line 1-29: Move the SuggestionsSetting component into its own folder
named SuggestionsSetting and create a barrel export; specifically, create
SuggestionsSetting/SuggestionsSetting.tsx containing the current
SuggestionsSetting component (keeping imports like Label, Switch and hooks
useTerminalSuggestionsStore) and add a sibling SuggestionsSetting/index.ts that
re-exports the component (export { SuggestionsSetting } from
"./SuggestionsSetting";). Update any imports elsewhere to import from the new
barrel if necessary.
In `@README.md`:
- Around line 52-53: Update the README table rows for "Branch ワークスペースの PR 表示対応"
and "シェル履歴サジェスト" to replace the PR column placeholder "—" with "PR `#24`"; locate
the two entries by their exact titles and change the third-column cell value to
"PR `#24`" so both rows link to PR `#24` for traceability.
---
Nitpick comments:
In `@apps/desktop/src/lib/trpc/routers/terminal/terminal.ts`:
- Around line 423-427: The router input schema currently accepts whitespace-only
prefixes; update the tRPC input validation for this endpoint by trimming and
validating prefix at the boundary: change the .input(z.object({ prefix:
z.string(), offset: z.number().optional() })) to trim the prefix (e.g., using
z.preprocess or z.string().transform to .trim()) and enforce a minimum length
(e.g., .min(2)) so whitespace-only values are rejected before calling
getSuggestions; keep offset as optional. Reference the router input schema and
the getSuggestions call to locate the change.
In `@apps/desktop/src/lib/trpc/routers/workspaces/procedures/git-status.ts`:
- Around line 187-195: Extract the duplicated repoPath fallback logic in
git-status into a small helper (e.g., resolveRepoPath(workspace, worktree)) that
returns worktree?.path or, if missing and workspace.type === "branch", uses
getProject(workspace.projectId)?.mainRepoPath or null; replace both occurrences
that currently compute repoPath (the block using worktree?.path and the branch
fallback) with calls to this helper to keep behavior consistent and reduce
duplication.
In `@apps/desktop/src/main/lib/shell-history.ts`:
- Around line 36-42: The code reads the entire zsh history into memory via
readFile(zshPath, "utf-8") before calling parseZshHistory, which can blow up for
very large history files; change parseZshHistory or the caller to stream-process
the file instead (use createReadStream + readline or a tailing approach) so you
accumulate deduplicated entries incrementally (using a Set) and stop reading
once you reach the deduplicated cap (e.g., 10_000) to avoid unbounded
intermediate arrays; update functions referenced (zshPath, parseZshHistory, any
callers of readFile) to support streaming and early exit.
In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/helpers.ts`:
- Around line 584-590: The current key handler dismisses suggestions on
ArrowLeft as well as Escape, causing suggestions to disappear when users move
the caret mid-input; update the condition around noModifiers && (event.key ===
"ArrowLeft" || event.key === "Escape") so it only calls
options.activeSuggestionRef?.current?.onDismiss() for Escape (i.e., event.key
=== "Escape"), or if you prefer to keep ArrowLeft behavior only dismiss when the
caret is at start, check the input/textarea selection start (e.g.,
event.target.selectionStart === 0) before calling
options.activeSuggestionRef?.current?.onDismiss(); modify the key handling block
accordingly.
In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/TerminalSuggestion/TerminalSuggestion.tsx`:
- Around line 88-89: The inline hardcoded backgroundColor in the
TerminalSuggestion component should be replaced with a value derived from the
terminal theme (e.g., from the xterm instance's options.theme) to avoid clashing
with light themes; update the TerminalSuggestion component to read the terminal
theme (xterm.options.theme or a theme prop passed into TerminalSuggestion),
compute a suitable background (fallback to the existing rgba if theme value is
missing), and use that computed value for the style.backgroundColor instead of
"rgba(30, 30, 46, 0.92)"; ensure the change references the TerminalSuggestion
component and the backgroundColor style property so it will adapt to
xterm.options.theme values.
- Around line 15-32: getCellDimensions currently relies on the private xterm
internals path _core._renderService?.dimensions on the XTerm instance which is
brittle; update the getCellDimensions function to include a clear comment above
it stating this dependency on xterm private API (mentioning
_core._renderService?.dimensions and that it mirrors the same approach in
helpers.ts usage), and document the fallback/guarding behavior already present
(returning null on missing/invalid dimensions) so future maintainers know this
is intentional and where to look if xterm internals change.
🪄 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: f0511140-3e98-4531-af15-e357b116d59d
📒 Files selected for processing (16)
README.mdapps/desktop/src/lib/trpc/routers/terminal/terminal.tsapps/desktop/src/lib/trpc/routers/workspaces/procedures/git-status.tsapps/desktop/src/main/lib/agent-setup/shell-wrappers.tsapps/desktop/src/main/lib/shell-history.tsapps/desktop/src/renderer/routes/_authenticated/settings/terminal/components/TerminalSettings/TerminalSettings.tsxapps/desktop/src/renderer/routes/_authenticated/settings/terminal/components/TerminalSettings/components/SuggestionsSetting.tsxapps/desktop/src/renderer/routes/_authenticated/settings/utils/settings-search/settings-search.tsapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/TerminalSuggestion/TerminalSuggestion.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/TerminalSuggestion/index.tsapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/helpers.tsapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/hooks/index.tsapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/hooks/useTerminalLifecycle.tsapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/hooks/useTerminalSuggestion.tsapps/desktop/src/renderer/stores/terminal-suggestions.ts
Summary
シェル履歴サジェスト
~/.zsh_historyからプレフィックスマッチでコマンド候補を取得Branch ワークスペース PR 表示
getGitHubStatus/getGitHubPRCommentsが worktree レコード必須だった制限を解消mainRepoPathにフォールバックTest plan
Summary by CodeRabbit
New Features
Documentation