-
Notifications
You must be signed in to change notification settings - Fork 489
feat: Mobile responsiveness improvements from community contributor #449
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
- Sidebar now uses overlay pattern on mobile (fixed position when open) - Added backdrop overlay that dismisses sidebar on tap - Made collapse toggle button visible on all screen sizes - Made project options menu visible on all screen sizes Previously the sidebar was forced to collapsed width (w-16) on mobile even when sidebarOpen was true, and the toggle/options buttons were hidden with `hidden lg:flex`.
Integrate Claude Haiku to automatically generate commit messages when committing worktree changes. Shows a sparkle animation while generating and auto-populates the commit message field. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…provider auth initialization
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the You can disable this status message by setting the 📝 WalkthroughWalkthroughAdds AI-assisted commit message generation endpoint and UI integration; expands list view and mobile responsiveness across board, settings, and worktree panels; introduces prompt/defaults/types for commit messages; enhances Claude usage service and provider auth initialization. Changes
Sequence DiagramsequenceDiagram
actor User
participant UI as UI (Commit Dialog)
participant Electron as Electron API
participant Server as Server (/api/worktree)
participant Git as Local Git
participant Provider as AI Provider
User->>UI: Open commit dialog (or request generate)
UI->>Electron: worktree.generateCommitMessage(worktreePath)
Electron->>Server: POST /api/worktree/generate-commit-message { worktreePath }
Server->>Git: git diff --cached
alt no staged changes
Server->>Git: git diff
end
Git-->>Server: diff text
Server->>Server: truncate diff, resolve model & system prompt
Server->>Provider: stream request (Claude/Cursor)
Provider-->>Server: streamed generated text
Server->>Server: validate & assemble message
Server-->>Electron/UI: 200 { message } or error
UI->>User: populate commit message field / show error
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Possibly related PRs
Suggested labels
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. 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 |
Summary of ChangesHello @Shironex, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request integrates a series of community-contributed enhancements primarily focused on improving the application's user experience on mobile devices. It introduces responsive layouts for key UI elements, adds a powerful AI-driven feature for generating commit messages, and provides a new way to visualize and interact with tasks through a sortable list view. These changes aim to make the application more accessible and efficient across different screen sizes while also expanding its core capabilities. Highlights
🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console. Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code Review
This is an impressive and substantial contribution that significantly improves the mobile experience across the entire application. The changes are well-thought-out, implementing standard responsive design patterns effectively. The new features, such as the AI-generated commit messages and the list view, are excellent additions. The code quality is high, with only a few minor points for improvement regarding TypeScript best practices. Great work!
apps/ui/src/components/views/board-view/components/list-view/list-row.tsx
Show resolved
Hide resolved
apps/ui/src/components/views/board-view/components/list-view/list-view.tsx
Outdated
Show resolved
Hide resolved
apps/ui/src/components/views/board-view/components/list-view/row-actions.tsx
Outdated
Show resolved
Hide resolved
- Implemented a new endpoint to generate commit messages based on git diffs. - Updated worktree routes to include the AI commit message generation functionality. - Enhanced the UI to support automatic generation of commit messages when the commit dialog opens, based on user settings. - Added settings for enabling/disabling AI-generated commit messages and configuring the model used for generation.
- Added a new section in the UI for customizing commit message prompts. - Integrated a system prompt for AI-generated commit messages, allowing users to define their own instructions. - Updated the backend to merge custom prompts with default settings for commit message generation. - Enhanced the commit message generation logic to utilize the effective system prompt based on user settings.
…alSettings interface - Eliminated unused properties related to notification commands and ntfy.sh integration from the GlobalSettings interface. - Updated default global settings to reflect the removal of these properties.
|
@coderabbitai review this pr |
|
@Shironex: I'll review the changes in this PR for you. I'll examine the mobile responsiveness improvements, the 13 patches that were applied, and check for any potential issues with security, code quality, and best practices. ✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 16
🤖 Fix all issues with AI agents
In @apps/server/src/services/claude-usage-service.ts:
- Around line 187-188: In the catch block that catches spawnError inside
apps/server/src/services/claude-usage-service.ts (the catch (spawnError)
handler), remove the leftover code-generation artifact comment "// ... (error
handling omitted for brevity in replace block, keep existing)" so the catch
block only contains the intended error handling logic; ensure you do not remove
any real error-handling lines or messages around the spawnError handler (locate
the catch by the unique token spawnError and delete only that stray comment).
In @apps/ui/src/components/usage-popover.tsx:
- Around line 74-76: The variable name is now misleading because the logic for
claudeAuthStatus was relaxed to any authenticated state but the variable is
still called isClaudeCliVerified; rename the variable to isClaudeAuthenticated
and update every reference inside the component (replace isClaudeCliVerified →
isClaudeAuthenticated) so the name matches the semantics, or revert the
condition to specifically check method === 'cli_authenticated' if the CLI-only
intent was required; ensure you update usages in render logic and any hooks that
depend on the variable (claudeAuthStatus, codexAuthStatus remain unchanged).
In @apps/ui/src/components/views/board-view/board-header.tsx:
- Around line 265-285: The Plan button block currently renders based only on
!isMobile which can cause SSR/client hydration mismatches; update the
conditional to also require isMounted (from useIsMobile/useIsMounted) so the
block renders only on the client (e.g., change the render condition to
{!isMobile && isMounted} for the div containing the Plan and Plan Settings
buttons referenced by onOpenPlanDialog and setShowPlanSettings), matching the
pattern used for the other desktop controls.
In @apps/ui/src/components/views/board-view/components/list-view/index.ts:
- Around line 1-14: Prettier flagged formatting issues in the list-view barrel
exports (exports like ListHeader, LIST_COLUMNS, ListRow, ListView, RowActions,
StatusBadge and their corresponding type exports); run your project's Prettier
formatter (e.g., prettier --write
apps/ui/src/components/views/board-view/components/list-view/index.ts) or use
your editor's formatting command to reformat this file, ensure the exports and
type exports follow project Prettier rules (including final newline), then stage
and commit the updated file so CI passes.
In @apps/ui/src/components/views/board-view/components/list-view/list-row.tsx:
- Line 1: Remove the top-level "@ts-nocheck" from list-row.tsx and replace it by
properly typing the ListRow component and its related values: declare an
explicit ListRowProps interface (include item: ItemType, index: number, onMove:
(from:number,to:number)=>void, onDelete: (id:string)=>void, and any optional
callbacks), annotate the ListRow React component with React.FC<ListRowProps>,
and add concrete types for event handlers (e.g., React.DragEvent<HTMLDivElement>
or React.MouseEvent) and any local state variables; ensure any referenced types
like ItemType are imported or defined, and fix any resulting TypeScript errors
rather than suppressing them so the file compiles without @ts-nocheck.
In @apps/ui/src/components/views/board-view/components/list-view/list-view.tsx:
- Line 1: Remove the top-line "@ts-nocheck" from list-view.tsx and fix the
underlying TypeScript/formatting errors it was hiding: run the TypeScript
compiler and formatter to surface errors, then update the component props/types
and any usages in ListView (e.g., the ListView component, its props
interface/type, and functions like renderItem or onDrag handlers) to have
correct types consistent with board-view.tsx patterns; ensure imports, JSX
element types and any any/unknown usages are tightened or properly typed, and
re-run lint/format pipeline until the formatting/type errors are resolved so the
file can compile without @ts-nocheck.
- Line 13: The import brings in an unused symbol COLUMNS from '../../constants';
remove COLUMNS from the import list in list-view.tsx so the line only imports
getColumnsWithPipeline (i.e., change "import { COLUMNS, getColumnsWithPipeline }
from '../../constants';" to import only getColumnsWithPipeline) to eliminate the
unused import warning and keep imports clean.
In
@apps/ui/src/components/views/board-view/components/list-view/row-actions.tsx:
- Line 1: Remove the top-level "@ts-nocheck" and resolve the type mismatch at
the assignment for setOpen by ensuring the fallback has a consistent signature:
narrow onOpenChange to a function type or wrap setInternalOpen in a small
adapter so both branches are typed as (open: boolean) => void; update the
assignment of const setOpen = ... to use that unified function type (either via
a typed wrapper or an explicit function cast) so onOpenChange, setInternalOpen
and setOpen all share the same (open: boolean) => void signature.
In
@apps/ui/src/components/views/board-view/components/list-view/status-badge.tsx:
- Line 1: Prettier formatting in this file is failing CI; run the formatter
(e.g., `prettier --write`) on
apps/ui/src/components/views/board-view/components/list-view/status-badge.tsx or
otherwise reformat the import and surrounding code to match project Prettier
rules (ensure the import line "import { memo, useMemo } from 'react';" and the
rest of the file conform to configured style), then re-stage the updated file so
CI passes.
In @apps/ui/src/components/views/board-view/dialogs/commit-worktree-dialog.tsx:
- Around line 88-130: The effect that calls generateMessage can update state
after the dialog closes; wrap the async work in an abort pattern: create an
AbortController (or a local `aborted` flag) inside the useEffect, pass the
signal to generateCommitMessage if supported (or check the flag after awaiting),
and in the cleanup return call controller.abort() (or set the flag) so that
before calling setMessage, setIsGenerating, or setError inside generateMessage
you first check !signal.aborted (or !aborted); update the useEffect to use this
check to skip state updates when the dialog has closed to prevent
stale/unmounted updates (references: useEffect, generateMessage, getElectronAPI,
setMessage, setIsGenerating).
In @apps/ui/src/components/views/board-view/hooks/use-list-view-state.ts:
- Around line 1-3: The file use-list-view-state.ts has Prettier formatting
violations; run Prettier (e.g., `prettier --write`) on this file or apply your
project's formatter to fix spacing and import formatting around the top-level
imports (the lines importing useState/useCallback/useEffect/useMemo,
getJSON/setJSON, and ViewMode) so the file matches the repository's prettier
rules.
In @apps/ui/src/components/views/board-view/kanban-board.tsx:
- Around line 62-65: The component declares and destructures an unused prop
`viewMode`—remove `viewMode?: ViewMode` from the `KanbanBoardProps` interface
and delete `viewMode` from the destructuring in the `KanbanBoard` component so
the prop is no longer accepted or referenced; if parent-level logic needs the
prop keep it only where it's used (outside this component) rather than in
`KanbanBoardProps`.
In @apps/ui/src/components/views/board-view/mobile-usage-bar.tsx:
- Around line 166-170: The UsageItem is passed isLoading={false} so the refresh
spinner never animates; add local loading state booleans (e.g., isClaudeLoading
and isCodexLoading) in the component, toggle the appropriate flag to true before
starting the async work in fetchClaudeUsage and fetchCodexUsage and set it false
in a finally block after the fetch completes or errors, and then pass those
flags to the corresponding UsageItem instances as isLoading={isClaudeLoading}
and isLoading={isCodexLoading}.
In @apps/ui/src/components/views/board-view/worktree-panel/worktree-panel.tsx:
- Around line 213-239: WorktreeActionsDropdown is missing the required props
onRunInitScript and hasInitScript; pass the appropriate values for the
selectedWorktree (e.g., hasInitScript={hasInitScript(selectedWorktree)}) and the
existing handler (e.g., onRunInitScript={handleRunInitScript} or
onRunInitScript={onRunInitScript} depending on the handler name in scope) so the
mobile view provides the init-script state and the callback to re-run it.
In @IMPLEMENTATION_PLAN.md:
- Line 1: The IMPLEMENTATION_PLAN.md file has a Prettier formatting violation;
run the formatter (prettier --write IMPLEMENTATION_PLAN.md), review the changes,
ensure they adhere to the repository's Prettier config (e.g., .prettierrc),
stage and commit the reformatted file, and push the updated commit so the
pipeline passes.
- Around line 30-53: The documentation and plan mention a hook
useMobile(breakpoint?: number) but the codebase actually exposes/use useIsMobile
from use-media-query.ts without an optional breakpoint; fix by making docs and
IMPLEMENTATION_PLAN.md reflect the real hook name and signature (useIsMobile or
useMediaQuery with its actual params) or update the implementation to provide
the documented API: add/export a useMobile(breakpoint?: number) wrapper in
use-mobile.ts that delegates to the existing useIsMobile/useMediaQuery and
accepts an optional breakpoint, ensuring the function names (useMobile,
useIsMobile) and parameter (breakpoint) in the docs match the code.
🧹 Nitpick comments (30)
apps/ui/src/components/ui/command.tsx (1)
81-88: Good mobile scrolling improvements; simplify the arbitrary property syntax.The touch scrolling enhancements (
touch-pan-y,overscroll-contain) are appropriate for improving mobile UX. However, the[&]:prefix on line 86 is unnecessary—arbitrary CSS properties can be applied directly to the element without a self-referencing variant.Also note that
-webkit-overflow-scrolling: touchis deprecated since iOS 13 (2019), as momentum scrolling is now enabled by default. It's harmless as a fallback for very old devices, but worth being aware of.♻️ Suggested simplification
className={cn( 'max-h-[300px] scroll-py-1 overflow-x-hidden overflow-y-auto', // Mobile touch scrolling support 'touch-pan-y overscroll-contain', - // iOS Safari momentum scrolling - '[&]:[-webkit-overflow-scrolling:touch]', + // iOS Safari momentum scrolling (legacy fallback) + '[-webkit-overflow-scrolling:touch]', className )}apps/ui/src/components/views/graph-view/graph-canvas.tsx (1)
248-301: Consider cleaning up pending timeouts on unmount.The orientation handling logic is sound, but the
setTimeoutcalls at lines 263-265 and 283-285 aren't tracked or cleared on cleanup. If the component unmounts while a timeout is pending,fitViewwill be called on an unmounted component.♻️ Suggested improvement to clear pending timeouts
useEffect(() => { if (typeof window === 'undefined') return; // Track the previous orientation to detect changes let previousWidth = window.innerWidth; let previousHeight = window.innerHeight; + let timeoutId: ReturnType<typeof setTimeout> | null = null; const handleOrientationChange = () => { // Small delay to allow the browser to complete the orientation change - setTimeout(() => { + if (timeoutId) clearTimeout(timeoutId); + timeoutId = setTimeout(() => { fitView({ padding: 0.2, duration: 300 }); }, 100); }; const handleResize = () => { const currentWidth = window.innerWidth; const currentHeight = window.innerHeight; // Detect orientation change by checking if width and height swapped significantly // This happens when device rotates between portrait and landscape const widthDiff = Math.abs(currentWidth - previousHeight); const heightDiff = Math.abs(currentHeight - previousWidth); // If the dimensions are close to being swapped (within 100px tolerance) // it's likely an orientation change const isOrientationChange = widthDiff < 100 && heightDiff < 100; if (isOrientationChange) { // Delay fitView to allow browser to complete the layout - setTimeout(() => { + if (timeoutId) clearTimeout(timeoutId); + timeoutId = setTimeout(() => { fitView({ padding: 0.2, duration: 300 }); }, 150); } previousWidth = currentWidth; previousHeight = currentHeight; }; // Listen for orientation change event (mobile specific) window.addEventListener('orientationchange', handleOrientationChange); // Also listen for resize as a fallback (some browsers don't fire orientationchange) window.addEventListener('resize', handleResize); return () => { + if (timeoutId) clearTimeout(timeoutId); window.removeEventListener('orientationchange', handleOrientationChange); window.removeEventListener('resize', handleResize); }; }, [fitView]);apps/ui/src/components/views/board-view/dialogs/agent-output-modal.tsx (1)
319-368: Consider extracting repeated button styling into a reusable component.The four view mode buttons share identical className logic, differing only in the
viewModevalue and icon/label. This repetition could be reduced with a small helper component or mapping over an array of button configs.♻️ Optional: Extract view mode button component
// Define button config above the return statement const viewModeButtons: Array<{ mode: ViewMode; icon: React.ElementType; label: string; show?: boolean; }> = [ { mode: 'summary', icon: ClipboardList, label: 'Summary', show: !!summary }, { mode: 'parsed', icon: List, label: 'Logs' }, { mode: 'changes', icon: GitBranch, label: 'Changes' }, { mode: 'raw', icon: FileText, label: 'Raw' }, ]; // Then in JSX: {viewModeButtons .filter((btn) => btn.show !== false) .map(({ mode, icon: Icon, label }) => ( <button key={mode} onClick={() => setViewMode(mode)} className={`flex items-center gap-1.5 px-3 py-1.5 rounded-md text-xs font-medium transition-all whitespace-nowrap ${ effectiveViewMode === mode ? 'bg-primary/20 text-primary shadow-sm' : 'text-muted-foreground hover:text-foreground hover:bg-accent' }`} data-testid={`view-mode-${mode}`} > <Icon className="w-3.5 h-3.5" /> {label} </button> ))}apps/ui/src/components/views/agent-view/input-area/input-controls.tsx (3)
6-6: Consider importing from the barrel export for consistency.The
shared/index.tsbarrel export already re-exportsAgentModelSelector. Using the barrel export keeps imports consistent and easier to maintain.Suggested change
-import { AgentModelSelector } from '../shared/agent-model-selector'; +import { AgentModelSelector } from '../shared';
171-180: Consider adding aria-label for accessibility.The Send/Queue button changes its icon and title dynamically based on
isProcessing. Screen reader users would benefit from an explicitaria-labelthat conveys the current action, since the visual icon change isn't perceivable.Suggested change
<Button onClick={onSend} disabled={!canSend} className="h-11 px-4 rounded-xl shrink-0" variant={isProcessing ? 'outline' : 'default'} data-testid="send-message" title={isProcessing ? 'Add to queue' : 'Send message'} + aria-label={isProcessing ? 'Add to queue' : 'Send message'} > {isProcessing ? <ListOrdered className="w-4 h-4" /> : <Send className="w-4 h-4" />} </Button>
156-168: Consider adding aria-label for the icon-only Stop button.Icon-only buttons benefit from
aria-labelto ensure screen reader users understand the button's purpose without relying on thetitleattribute (which may not be announced).Suggested change
<Button onClick={onStop} disabled={!isConnected} className="h-11 px-4 rounded-xl shrink-0" variant="destructive" data-testid="stop-agent" title="Stop generation" + aria-label="Stop generation" > <Square className="w-4 h-4 fill-current" /> </Button>apps/ui/src/components/layout/sidebar/components/project-actions.tsx (1)
62-64: Consider hiding keyboard shortcut badge on mobile.Keyboard shortcuts aren't actionable on touch devices. Displaying the shortcut badge on mobile takes up button space without providing utility to the user. Consider hiding it on smaller screens.
💡 Suggested change
- <span className="flex items-center justify-center min-w-5 h-5 px-1.5 text-[10px] font-mono rounded-md bg-muted/80 text-muted-foreground ml-2"> + <span className="hidden sm:flex items-center justify-center min-w-5 h-5 px-1.5 text-[10px] font-mono rounded-md bg-muted/80 text-muted-foreground ml-2"> {formatShortcut(shortcuts.openProject, true)} </span>apps/ui/src/components/views/agent-view.tsx (2)
23-30: Initial state doesn't respond to window resizes.The
showSessionManagerstate is set once on mount based onwindow.innerWidth, but won't update if the user resizes the window (e.g., rotating a tablet or resizing a browser). This can lead to a mismatch between the UI state and the actual viewport.Consider using the
useMobilehook mentioned in the AI summary (which is introduced in this PR atapps/ui/src/hooks/use-mobile.ts) to handle responsive behavior consistently and reactively.Suggested approach
+import { useMobile } from '@/hooks/use-mobile'; export function AgentView() { const { currentProject } = useAppStore(); + const isMobile = useMobile(1024); const [input, setInput] = useState(''); const [currentTool, setCurrentTool] = useState<string | null>(null); - // Initialize session manager state based on screen size (hidden on mobile) - const [showSessionManager, setShowSessionManager] = useState(() => { - // Check if we're on a mobile screen (< lg breakpoint = 1024px) - if (typeof window !== 'undefined') { - return window.innerWidth >= 1024; - } - return true; // Default to showing on SSR - }); + const [showSessionManager, setShowSessionManager] = useState(!isMobile);You may also need a
useEffectto sync state whenisMobilechanges if you want the sidebar to auto-hide/show on resize.
129-134: Duplicate window width check — consider reusing the mobile hook.This effect duplicates the mobile detection logic from the initialization. If
useMobileis adopted as suggested above, this effect can simply referenceisMobile:useEffect(() => { - if (currentSessionId && typeof window !== 'undefined' && window.innerWidth < 1024) { + if (currentSessionId && isMobile) { setShowSessionManager(false); } -}, [currentSessionId]); +}, [currentSessionId, isMobile]);This also ensures proper SSR safety and consistency with the rest of the mobile-responsive logic in this PR.
apps/server/src/services/claude-usage-service.ts (3)
226-228: Use the existingstripAnsiCodeshelper to avoid duplication.The ANSI stripping regex here duplicates the
stripAnsiCodes()method defined at line 349. Reuse the helper for consistency and maintainability.Note: The static analysis warning about control characters is a false positive—
\x1Bis the correct escape sequence for stripping ANSI codes.Suggested fix
- // Strip ANSI codes for easier matching - // eslint-disable-next-line no-control-regex - const cleanOutput = output.replace(/\x1B\[[0-9;]*[A-Za-z]/g, ''); + // Strip ANSI codes for easier matching + const cleanOutput = this.stripAnsiCodes(output);
201-218: Timeout value inconsistency with class field.The PTY method uses a hardcoded 45-second timeout, but the class field
this.timeoutis set to 30000ms (line 22). The macOS implementation usesthis.timeout. Consider using a consistent approach, either updating the class field or using a separate constant for the PTY-specific timeout with a comment explaining why it differs.Suggested approach
private claudeBinary = 'claude'; - private timeout = 30000; // 30 second timeout + private timeout = 30000; // 30 second timeout for macOS + private ptyTimeout = 45000; // 45 second timeout for PTY (multi-step interactive flow) private isWindows = os.platform() === 'win32';Then use
this.ptyTimeouton line 218.
326-333: Minor inconsistency in authentication error messages.The auth error message here differs from the one at lines 242-245. The earlier message suggests running
'claude logout'then'claude login', while this one only mentions'claude login'. Consider using a consistent message, preferably the more detailed one.Suggested fix for consistency
if ( output.includes('token_expired') || output.includes('authentication_error') || output.includes('permission_error') ) { - reject(new Error("Authentication required - please run 'claude login'")); + reject( + new Error( + "Claude CLI authentication issue. Please run 'claude logout' and then 'claude login' in your terminal to refresh permissions." + ) + ); return; }apps/ui/src/hooks/use-media-query.ts (1)
22-23: Redundant state update on effect mount.The
setMatches(mediaQuery.matches)on line 23 is unnecessary since theuseStateinitializer (lines 9-12) already sets the initial value fromwindow.matchMedia(query).matches. This causes an extra render on mount.🔧 Suggested fix
useEffect(() => { if (typeof window === 'undefined') return; const mediaQuery = window.matchMedia(query); const handleChange = (e: MediaQueryListEvent) => { setMatches(e.matches); }; - // Set initial value - setMatches(mediaQuery.matches); - // Listen for changes mediaQuery.addEventListener('change', handleChange);apps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsx (1)
593-690: Consider extracting shared mobile expansion logic.The mobile inline expansion patterns for Codex, Claude, and grouped models share significant structural similarity. While the current approach is readable and works correctly, a future refactor could extract a reusable
InlineExpansionListcomponent to reduce duplication.Also applies to: 937-1034, 1171-1252
apps/ui/src/components/views/board-view/components/list-view/row-actions.tsx (1)
527-533: Placeholder handler for disabled action.The "View Branch" menu item has an empty
onClick={() => {}}handler and is always disabled. Consider either implementing this functionality or adding a TODO comment explaining when it will be implemented.Do you want me to open an issue to track implementing the "View Branch" action?
apps/ui/src/components/views/board-view/dialogs/commit-worktree-dialog.tsx (1)
115-122: Consider using the project's logger utility instead ofconsole.warn.Other files in the codebase use
createLoggerfrom@automaker/utils/loggerfor consistent logging.apps/ui/src/components/views/board-view/mobile-usage-bar.tsx (3)
139-143: Consider using the existinggetCodexWindowLabelutility.A
getCodexWindowLabelfunction already exists in@/lib/codex-usage-format.ts(per the relevant code snippets). The local implementation here has a different output format (Xm Windowvs structured label object), but consolidating or aligning these would reduce duplication and ensure consistency across the codebase.💡 Option: Import and adapt existing utility
import { getCodexWindowLabel as getCodexWindowLabelObj } from '@/lib/codex-usage-format'; // Then adapt the output format if needed, or update callers to use the structured label const getCodexWindowLabel = (durationMins: number) => { const label = getCodexWindowLabelObj(durationMins); return label.subtitle || label.title; };
146-156: Consider removing staleness from effect dependencies.Including
isClaudeStale/isCodexStalein the dependency arrays causes the effects to re-evaluate frequently since these are computed fromDate.now()on every render. While this won't cause infinite loops (the condition guards the fetch), it's slightly inefficient.💡 Optimization: Check staleness inside the effect
useEffect(() => { - if (showClaudeUsage && isClaudeStale) { + if (showClaudeUsage) { + const isStale = !claudeUsageLastUpdated || Date.now() - claudeUsageLastUpdated > 2 * 60 * 1000; + if (isStale) { fetchClaudeUsage(); + } } -}, [showClaudeUsage, isClaudeStale, fetchClaudeUsage]); +}, [showClaudeUsage, claudeUsageLastUpdated, fetchClaudeUsage]);
104-107: Minor: Combine store selectors into a single call.Two separate
useAppStore()calls can be merged for slightly cleaner code:-const { claudeUsage, claudeUsageLastUpdated, setClaudeUsage } = useAppStore(); -const { codexUsage, codexUsageLastUpdated, setCodexUsage } = useAppStore(); +const { claudeUsage, claudeUsageLastUpdated, setClaudeUsage, codexUsage, codexUsageLastUpdated, setCodexUsage } = useAppStore();apps/ui/src/components/views/board-view/header-mobile-menu.tsx (1)
100-110: Consider addingaria-labelfor better screen reader support.The settings buttons have
titleattributes for tooltip text, but addingaria-labelwould improve accessibility for screen reader users.<button onClick={(e) => { e.stopPropagation(); onOpenAutoModeSettings(); }} className="p-1 rounded hover:bg-accent/50 transition-colors" title="Auto Mode Settings" + aria-label="Auto Mode Settings" data-testid="mobile-auto-mode-settings-button" >apps/ui/src/hooks/use-provider-auth-init.ts (1)
41-43: Fallback logic silently masks unknown auth methods.When the server returns a method not in
validMethods, the code falls back to'api_key'or'none'. This could hide unexpected server responses. Consider logging when an unknown method is encountered to aid debugging.const method = validMethods.includes(auth.method as ClaudeAuthMethod) ? (auth.method as ClaudeAuthMethod) - : ((auth.authenticated ? 'api_key' : 'none') as ClaudeAuthMethod); + : (() => { + if (auth.method) { + logger.warn('Unknown Claude auth method from server:', auth.method); + } + return (auth.authenticated ? 'api_key' : 'none') as ClaudeAuthMethod; + })();apps/ui/src/components/views/board-view/board-header.tsx (1)
18-21: PotentialViewModetype name collision.The
ViewModetype exported here (from./components) is'kanban' | 'list', whileapps/ui/src/store/app-store.tsexports a differentViewModetype with values like'welcome' | 'setup' | 'board'etc. (per the relevant code snippets). Re-exporting under the same name could cause confusion for consumers who import from both modules.Consider renaming to
BoardViewModeor similar to distinguish from the app-level view mode.-import { ViewToggle, type ViewMode } from './components'; +import { ViewToggle, type ViewMode as BoardViewMode } from './components'; -export type { ViewMode }; +export type { BoardViewMode };apps/ui/src/components/views/settings-view/api-keys/claude-usage-section.tsx (2)
83-88: Potential repeated fetches due toisStalein dependency array.The
isStalevalue is derived fromclaudeUsageLastUpdated, which changes afterfetchUsagecompletes. Including it in the dependency array could cause the effect to re-trigger immediately after data becomes fresh, or cause unnecessary re-evaluations. Consider removingisStalefrom the dependencies and checking it inside the effect body instead.Suggested fix
useEffect(() => { - // Initial fetch if authenticated and stale - if (canFetchUsage && isStale) { + // Initial fetch if authenticated and data is stale + const stale = !claudeUsageLastUpdated || Date.now() - claudeUsageLastUpdated > STALE_THRESHOLD_MS; + if (canFetchUsage && stale) { void fetchUsage(); } - }, [fetchUsage, canFetchUsage, isStale]); + }, [fetchUsage, canFetchUsage, claudeUsageLastUpdated]);
110-146: Inner componentUsageCardrecreated on every render.Defining
UsageCardinsideClaudeUsageSectioncauses React to treat it as a new component type on each render, which can break reconciliation and cause unnecessary DOM updates. Extract it outside the parent component or memoize it.Suggested fix - extract outside
+const UsageCard = ({ + title, + subtitle, + percentage, + resetText, + getUsageColor, +}: { + title: string; + subtitle: string; + percentage: number; + resetText?: string; + getUsageColor: (percentage: number) => string; +}) => { + const safePercentage = Math.min(Math.max(percentage, 0), MAX_PERCENTAGE); + + return ( + <div className="rounded-xl border border-border/60 bg-card/50 p-4"> + <div className="flex items-center justify-between"> + <div> + <p className="text-sm font-semibold text-foreground">{title}</p> + <p className="text-xs text-muted-foreground">{subtitle}</p> + </div> + <span className="text-sm font-semibold text-foreground"> + {Math.round(safePercentage)}% + </span> + </div> + <div className="mt-3 h-2 w-full rounded-full bg-secondary/60"> + <div + className={cn( + 'h-full rounded-full transition-all duration-300', + getUsageColor(safePercentage) + )} + style={{ width: `${safePercentage}%` }} + /> + </div> + {resetText && <p className="mt-2 text-xs text-muted-foreground">{resetText}</p>} + </div> + ); +}; + export function ClaudeUsageSection() { // ... existing code ... - - const UsageCard = ({ ... }) => { ... };apps/ui/src/components/views/board-view/components/list-view/status-badge.tsx (1)
207-220: Pipeline statuses all share the same sort order (1.5).All pipeline statuses return
1.5, meaning they cannot be sorted relative to each other. If the pipeline config includes step order information, consider using it for more granular sorting.Optional enhancement if pipeline order matters
-export function getStatusOrder(status: FeatureStatusWithPipeline): number { +export function getStatusOrder( + status: FeatureStatusWithPipeline, + pipelineConfig: PipelineConfig | null = null +): number { const baseOrder: Record<string, number> = { backlog: 0, in_progress: 1, waiting_approval: 2, verified: 3, }; if (isPipelineStatus(status)) { - // Pipeline statuses come after in_progress but before waiting_approval - return 1.5; + const stepId = status.replace('pipeline_', ''); + const step = pipelineConfig?.steps?.find((s) => s.id === stepId); + // Use step order if available, otherwise default to 1.5 + return step ? 1 + (step.order + 1) / (pipelineConfig!.steps.length + 1) : 1.5; } return baseOrder[status] ?? 0; }apps/ui/src/components/views/settings-view/components/settings-navigation.tsx (1)
185-191: Consider locking body scroll when mobile overlay is open.When the backdrop is visible on mobile, users can still scroll the underlying page content. For a better mobile experience, consider locking body scroll when the navigation overlay is open.
Optional enhancement
+import { useEffect } from 'react'; + export function SettingsNavigation({ activeSection, currentProject, onNavigate, isOpen = true, onClose, }: SettingsNavigationProps) { + // Lock body scroll on mobile when navigation is open + useEffect(() => { + if (isOpen && typeof window !== 'undefined' && window.innerWidth < 1024) { + document.body.style.overflow = 'hidden'; + return () => { + document.body.style.overflow = ''; + }; + } + }, [isOpen]); + // On mobile, only show when isOpen is true // ...apps/server/src/routes/worktree/routes/generate-commit-message.ts (1)
94-119: Add timeout to git diff commands to prevent hanging requests.The
execAsynccalls don't specify a timeout. If the git repository is in an unusual state or the diff is exceptionally large, the command could hang indefinitely, blocking the request.Suggested fix
+const GIT_TIMEOUT_MS = 30_000; // 30 seconds + // First try to get staged changes const { stdout: stagedDiff } = await execAsync('git diff --cached', { cwd: worktreePath, maxBuffer: 1024 * 1024 * 5, // 5MB buffer + timeout: GIT_TIMEOUT_MS, }); // If no staged changes, get unstaged changes if (!stagedDiff.trim()) { const { stdout: unstagedDiff } = await execAsync('git diff', { cwd: worktreePath, maxBuffer: 1024 * 1024 * 5, // 5MB buffer + timeout: GIT_TIMEOUT_MS, }); diff = unstagedDiff; } else { diff = stagedDiff; }apps/ui/src/components/views/board-view/hooks/use-list-view-state.ts (1)
139-142: Consider optimizing initial state loading.The
loadPersistedState()function is called twice during initialization—once for eachuseState. This parses localStorage twice. Consider loading once:💡 Suggested optimization
export function useListViewState(): UseListViewStateReturn { - // Initialize state from localStorage - const [viewMode, setViewModeState] = useState<ViewMode>(() => loadPersistedState().viewMode); - const [sortConfig, setSortConfigState] = useState<SortConfig>(() => loadPersistedState().sortConfig); + // Initialize state from localStorage (load once) + const [{ viewMode, sortConfig: initialSortConfig }] = useState(loadPersistedState); + const [viewModeState, setViewModeState] = useState<ViewMode>(viewMode); + const [sortConfig, setSortConfigState] = useState<SortConfig>(initialSortConfig);Or use a single combined state object if preferred.
apps/ui/src/components/views/board-view/components/list-view/list-view.tsx (2)
252-329: RepeatedallFeatures.find()lookups are unnecessary.Since
createHandlersis called with thefeatureobject directly, the repeated lookups viaallFeatures.find((f) => f.id === id)are redundant. The feature is already available in the closure.💡 Suggested simplification
const createHandlers = useCallback( (feature: Feature): RowActionHandlers => { return createRowActionHandlers(feature.id, { - editFeature: (id) => { - const f = allFeatures.find((f) => f.id === id); - if (f) actionHandlers.onEdit(f); - }, + editFeature: () => actionHandlers.onEdit(feature), deleteFeature: (id) => actionHandlers.onDelete(id), - viewOutput: actionHandlers.onViewOutput - ? (id) => { - const f = allFeatures.find((f) => f.id === id); - if (f) actionHandlers.onViewOutput?.(f); - } - : undefined, + viewOutput: actionHandlers.onViewOutput + ? () => actionHandlers.onViewOutput?.(feature) + : undefined, // ... same pattern for other handlers }); }, - [actionHandlers, allFeatures] + [actionHandlers] );This eliminates O(n) lookups for each action and removes
allFeaturesfrom the dependency array.
351-374: Consider bulk selection API for better performance.The current implementation calls
onToggleFeatureSelectiononce per feature, which could cause multiple re-renders for large feature lists. Consider addingselectAll(ids: string[])anddeselectAll()methods to the selection hook for batch updates.
apps/ui/src/components/views/board-view/components/list-view/index.ts
Outdated
Show resolved
Hide resolved
apps/ui/src/components/views/board-view/components/list-view/list-row.tsx
Show resolved
Hide resolved
apps/ui/src/components/views/board-view/worktree-panel/worktree-panel.tsx
Show resolved
Hide resolved
|
@coderabbitai rereview |
|
@Shironex: I'll perform a full re-review of all the changes in this PR. I'll examine the entire set of mobile responsiveness improvements, including all 13 patches, and provide fresh feedback on code quality, security, best practices, and implementation. ✅ Actions performedFull review triggered. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 14
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/ui/src/components/views/board-view/dialogs/commit-worktree-dialog.tsx (1)
1-17: Guard against message reset whenenableAiCommitMessageschanges while the dialog is open.
As written, changing that setting can re-run the effect and clear a user-typed message. Consider triggering generation only on the “open transition” (and/orworktree.pathchange).Also applies to: 41-43, 88-143
🤖 Fix all issues with AI agents
In @apps/server/src/routes/worktree/routes/generate-commit-message.ts:
- Around line 81-88: Validate the incoming worktreePath before using it as cwd
for execAsync: ensure worktreePath exists and is a directory (use fs.stat or
fs.promises.stat and isDirectory()), verify it contains a .git folder or is
inside a git repo (check for path.join(worktreePath, '.git') or run a safe git
rev-parse --show-toplevel), and optionally confirm it is under allowed project
bounds (compare against an allowedRoot via path.resolve and startsWith); on
failure return a 400 JSON error similar to the existing
GenerateCommitMessageErrorResponse. Apply this validation where worktreePath is
checked and before any execAsync calls that use it as cwd (references:
worktreePath, execAsync, res, GenerateCommitMessageErrorResponse).
In @apps/ui/src/components/views/agent-view.tsx:
- Around line 23-30: The server/client mismatch comes from initializing
showSessionManager based on window during render; change the useState
initializer to a consistent SSR-safe default (e.g., false or true) and then
detect screen size inside a useEffect on mount to call
setShowSessionManager(window.innerWidth >= 1024) (use the existing
showSessionManager and setShowSessionManager symbols); this ensures the initial
SSR value matches across environments and updates the state after mount to
reflect the actual client viewport.
- Line 31: The initial state for modelSelection uses the alias 'sonnet'
directly; update the PhaseModelEntry initialization in the modelSelection
useState to resolve the alias by calling resolveModelString('sonnet') (i.e., set
model: resolveModelString('sonnet') in the object passed to useState for
modelSelection), and add an import for resolveModelString from
@automaker/model-resolver at the top of the file so the resolver is available
for that initialization.
In @apps/ui/src/components/views/board-view/components/list-view/list-row.tsx:
- Around line 16-47: The function formatRelativeDate is an unused helper—either
remove it or wire it up where needed; to fix, delete the formatRelativeDate
function declaration from list-row.tsx if no UI requires relative date
formatting, or locate the component that should display relative dates (e.g.,
the list row rendering logic in the same file) and replace its current date
rendering with a call to formatRelativeDate(dateString) (ensure the prop/field
passed is the correct timestamp string), then run tests/grep to confirm no other
references remain; reference the function name formatRelativeDate to find and
remove or integrate it.
In @apps/ui/src/components/views/board-view/kanban-board.tsx:
- Around line 114-121: The Tailwind class "duration-250" is invalid; update the
class list in the cn(...) call inside the KanbanBoard component (the div using
backgroundImageStyle and className) to use a valid duration utility such as
"duration-200" or "duration-300" or the arbitrary syntax "duration-[250ms]";
apply the same change to the other occurrences noted in board-view.tsx so all
transition-duration classes are valid Tailwind v4 utilities.
In @apps/ui/src/components/views/board-view/mobile-usage-bar.tsx:
- Around line 1-8: The file uses React namespace types but never imports React,
so TypeScript fails; add an import to bring React types into scope—either add a
top-level import React from 'react' or, preferably, import the specific types
used (e.g., import type { MouseEvent, Dispatch, SetStateAction, ReactNode } from
'react')—apply this change in
apps/ui/src/components/views/board-view/mobile-usage-bar.tsx (and the same fix
for the other occurrences around lines 65–103) so references like
React.Dispatch/React.SetStateAction/React.MouseEvent resolve correctly.
- Around line 86-97: The refresh button is icon-only and needs an accessible
name: add an aria-label (e.g. aria-label="Refresh usage") to the <button>
element that wraps the RefreshCw icon so screen readers can announce its
purpose; keep the existing title and onClick handler (onRefresh) and do not
change the RefreshCw, isLoading, cn, or className usage—just add the aria-label
attribute to the button.
- Around line 104-109: MobileUsageBar currently calls useAppStore() twice
without selectors, causing the component to subscribe to the entire Zustand
store and rerender on any change; replace both calls with a single useAppStore
selector that returns only the needed pieces (claudeUsage,
claudeUsageLastUpdated, setClaudeUsage, codexUsage, codexUsageLastUpdated,
setCodexUsage) and pass shallow for comparison so MobileUsageBar only rerenders
when those specific values change.
In @apps/ui/src/components/views/graph-view/graph-canvas.tsx:
- Around line 254-301: The effect sets two setTimeouts in
handleOrientationChange and handleResize but never clears them, risking calls to
fitView after unmount and duplicate invocations; fix by using refs (e.g.,
timeoutRef or orientationTimeoutRef) to store the timeout IDs in the useEffect,
clear any existing timeout before creating a new one, and call clearTimeout for
all stored IDs in the effect cleanup; optionally also use an isMounted/ref flag
to avoid calling fitView after unmount and ensure handleOrientationChange and
handleResize deduplicate by reusing the same timeoutRef so fitView only runs
once per orientation event.
In
@apps/ui/src/components/views/settings-view/api-keys/claude-usage-section.tsx:
- Around line 83-88: The useEffect that triggers fetchUsage includes isStale in
its dependency array, but isStale is recomputed from Date.now() each render
which can cause repeated effects; remove isStale from the dependencies and
instead compute staleness inside the effect (or read a ref like lastFetchedRef
and compare Date.now() there) so that the effect depends only on stable values
(fetchUsage and canFetchUsage) and only calls fetchUsage when the locally
computed staleness check passes.
In
@apps/ui/src/components/views/settings-view/components/settings-navigation.tsx:
- Around line 176-182: The visibility logic in settings-navigation.tsx is wrong:
replace the single boolean shouldShow = isOpen with responsive logic so desktop
(lg+) always shows; update the shouldShow calculation (or remove the early
return) in the SettingsNavigation component to return true when the viewport is
at the desktop breakpoint (e.g., using a useMediaQuery/resize hook or a
CSS-responsive class approach) and only gate visibility by isOpen on smaller
screens; ensure the unique symbols affected are shouldShow, isOpen, and the
component rendering early return so the nav is not hidden on lg+.
In
@apps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsx:
- Around line 4-5: The mobile inline expansion path in PhaseModelSelector
doesn't attach the trigger refs used by the IntersectionObserver (they're only
attached in the desktop Popover branches), so expanded items on mobile never
auto-collapse on scroll; update the mobile render branch that renders
inline-expanded items to attach the same trigger refs (the ref(s) currently
passed to the Popover trigger) used by the IntersectionObserver, or
alternatively wrap the IntersectionObserver setup (the effect that observes
triggerRef/current) with a conditional that returns early when useIsMobile() is
true so you explicitly disable the observer on mobile—ensure you reference the
same ref variable(s) used in the desktop Popover code and the same
IntersectionObserver effect so behavior is consistent.
In @apps/ui/src/store/app-store.ts:
- Around line 540-543: The default for enableAiCommitMessages currently enables
sending diffs to external AI providers; change the default to false or implement
an explicit first-run consent flow: update the store's initial state handling of
enableAiCommitMessages to false (symbol: enableAiCommitMessages) and/or add a
one-time consent UI that sets enableAiCommitMessages=true only after the user
accepts a disclosure about sending diffs to external providers, ensuring the
consent flag is persisted and checked before any AI commit-generation code paths
run.
🧹 Nitpick comments (25)
apps/ui/src/components/ui/command.tsx (1)
81-88: Good mobile scrolling improvements.The addition of
touch-pan-yandoverscroll-containare solid improvements for mobile touch scrolling behavior.Minor note:
-webkit-overflow-scrolling: touch(line 86) is deprecated and automatically handled in iOS 13+. The property is harmless but could be removed if you want to reduce legacy code. Also, the[&]:prefix is redundant—[-webkit-overflow-scrolling:touch]would suffice.🧹 Optional cleanup
function CommandList({ className, ...props }: React.ComponentProps<typeof CommandPrimitive.List>) { return ( <CommandPrimitive.List data-slot="command-list" className={cn( 'max-h-[300px] scroll-py-1 overflow-x-hidden overflow-y-auto', // Mobile touch scrolling support 'touch-pan-y overscroll-contain', - // iOS Safari momentum scrolling - '[&]:[-webkit-overflow-scrolling:touch]', className )} {...props} /> ); }apps/ui/src/components/views/agent-view/input-area/input-controls.tsx (2)
115-119: Consider providing mobile feedback for attached files.The "files attached" badge is hidden on mobile (
hidden sm:block), leaving users without clear visual feedback that files are attached. While the subtle border color change (border-primary/30) is applied, it may not be sufficiently noticeable.Consider adding a small indicator icon or badge that's visible on mobile, or show a count in the attachment button itself when files are attached.
138-151: Consider addingaria-labelfor icon-only buttons.These buttons use icons without visible text. While
titleprovides a tooltip on hover, it's not reliably announced by screen readers. Addingaria-labelensures better accessibility:<Button variant="outline" size="icon" onClick={onToggleImageDropZone} disabled={!isConnected} className={cn(...)} title="Attach files (images, .txt, .md)" + aria-label="Attach files" >Apply similarly to the Stop and Send/Queue buttons.
Also applies to: 158-167, 171-180
apps/ui/src/components/usage-popover.tsx (2)
74-76: Inconsistent boolean coercion between Claude and Codex authentication checks.
isClaudeAuthenticateduses!!for explicit boolean coercion, butisCodexAuthenticateddoes not. This inconsistency could cause subtle type differences (e.g.,undefinedvsfalse) in downstream conditionals.Suggested fix for consistency
// Check authentication status const isClaudeAuthenticated = !!claudeAuthStatus?.authenticated; - const isCodexAuthenticated = codexAuthStatus?.authenticated; + const isCodexAuthenticated = !!codexAuthStatus?.authenticated;
78-85: Default tab selection may override user's manual tab choice.This effect runs whenever authentication state changes, which could reset the user's manually selected tab back to the default. Consider guarding against overriding an explicit user selection or running this logic only once on mount.
Suggested approach using a ref to track user interaction
+ const userHasSelectedTab = useRef(false); + // Determine which tab to show by default useEffect(() => { + if (userHasSelectedTab.current) return; if (isClaudeAuthenticated) { setActiveTab('claude'); } else if (isCodexAuthenticated) { setActiveTab('codex'); } }, [isClaudeAuthenticated, isCodexAuthenticated]);Then update the tab change handler:
onValueChange={(v) => { userHasSelectedTab.current = true; setActiveTab(v as 'claude' | 'codex'); }}apps/ui/src/hooks/use-provider-auth-init.ts (1)
26-29: Update the HTTP API client type definition to include the auth fields returned by the server.The
getClaudeStatus()method inhttp-api-client.tsis missingoauthTokenValidandapiKeyValidfrom itsauthresponse type definition. The server returns these fields (confirmed inapps/server/src/routes/setup/get-claude-status.ts), and the setup store already defines them inClaudeAuthStatus. Update the type definition at lines 1116-1127 inhttp-api-client.tsto include:oauthTokenValid?: boolean; apiKeyValid?: boolean;This will eliminate the need for the inline type cast in this file.
apps/server/src/services/claude-usage-service.ts (2)
225-228: Static analysis false positive — control character is intentional for ANSI stripping.The regex
/\x1B\[[0-9;]*[A-Za-z]/gcorrectly strips ANSI escape sequences. The Biome warning about control characters can be safely ignored here since this is the intended behavior.However, this same regex is duplicated in the
stripAnsiCodeshelper method at line 350. Consider reusing the helper method instead of inlining the regex.♻️ Suggested refactor to reuse stripAnsiCodes helper
ptyProcess.onData((data: string) => { output += data; - // Strip ANSI codes for easier matching - // eslint-disable-next-line no-control-regex - const cleanOutput = output.replace(/\x1B\[[0-9;]*[A-Za-z]/g, ''); + // Strip ANSI codes for easier matching + const cleanOutput = this.stripAnsiCodes(output);Note: You'll need to bind
thiscontext or extract the helper outside the class, sincethisisn't directly accessible inside the Promise callback. Alternatively, define a local const before the Promise:private executeClaudeUsageCommandPty(): Promise<string> { + const stripAnsi = this.stripAnsiCodes.bind(this); return new Promise((resolve, reject) => { // ... later in onData: - const cleanOutput = output.replace(/\x1B\[[0-9;]*[A-Za-z]/g, ''); + const cleanOutput = stripAnsi(output);
284-304: Timing-based REPL interaction is inherently fragile but acceptable.The multi-stage setTimeout approach for REPL detection and command execution is a pragmatic solution for interacting with an interactive CLI. The delays (1500ms + 1200ms) combined with the 45-second timeout should handle most scenarios.
Consider adding a comment documenting why these specific delays were chosen, to help future maintainers understand the tradeoffs:
// Detect REPL prompt and send /usage command + // Note: Delays are tuned to allow CLI to fully initialize (1500ms) + // and handle potential autocomplete menus (1200ms). May need adjustment + // if Claude CLI behavior changes. if ( !hasSentCommand &&apps/ui/src/components/views/board-view/worktree-panel/worktree-panel.tsx (1)
178-274: Mobile view implementation looks clean and well-structured.The mobile-specific render path correctly composes the new mobile dropdown components with the existing data and handlers. Good use of early return pattern to separate mobile/desktop rendering.
One minor observation: Line 200 passes a
branchesprop toBranchSwitchDropdown, but based on the component's interface in the relevant code snippets, it only acceptsfilteredBranches, notbranches. This extra prop will be ignored but adds unnecessary noise.🧹 Optional: Remove unused prop
<BranchSwitchDropdown worktree={selectedWorktree} isSelected={true} standalone={true} - branches={branches} filteredBranches={filteredBranches} branchFilter={branchFilter}apps/ui/src/hooks/use-media-query.ts (1)
14-31: Redundant state update causes unnecessary re-render on mount.The
useStateinitializer (lines 9-12) already sets the initial value fromwindow.matchMedia(query).matches. ThesetMatches(mediaQuery.matches)call on line 23 will trigger an unnecessary re-render with the same value on every mount.♻️ Remove redundant setMatches call
useEffect(() => { if (typeof window === 'undefined') return; const mediaQuery = window.matchMedia(query); const handleChange = (e: MediaQueryListEvent) => { setMatches(e.matches); }; - // Set initial value - setMatches(mediaQuery.matches); - // Listen for changes mediaQuery.addEventListener('change', handleChange); return () => { mediaQuery.removeEventListener('change', handleChange); }; }, [query]);apps/ui/src/components/views/settings-view/feature-defaults/feature-defaults-section.tsx (1)
13-14: Prefer using sharedPlanningModetype instead of duplicating it locally.
This avoids drift if@automaker/typeschanges and aligns with the “shared packages” guideline.Proposed change
+import type { PlanningMode } from '@automaker/types'; ... -type PlanningMode = 'skip' | 'lite' | 'spec' | 'full';Also applies to: 26-39, 41-54, 260-286
apps/ui/src/components/views/board-view/mobile-usage-bar.tsx (2)
147-151: Consider reusinggetCodexWindowLabelfromapps/ui/src/lib/codex-usage-format.ts.
Keeps labels consistent across the app and avoids duplicating logic.
56-59: Clamp progress bar width to[0, 100]to avoid negative widths on unexpected data.
Minor hardening.Proposed change
- style={{ width: `${Math.min(percentage, 100)}%` }} + style={{ width: `${Math.max(0, Math.min(percentage, 100))}%` }}apps/ui/src/components/views/board-view/worktree-panel/components/worktree-mobile-dropdown.tsx (1)
23-112: PreferDropdownMenuItem’sonSelectoveronClickfor Radix menus.
Helps ensure keyboard selection and menu-close semantics behave consistently.Proposed change
<DropdownMenuItem key={worktree.path} - onClick={() => onSelectWorktree(worktree)} + onSelect={() => onSelectWorktree(worktree)} className={cn('flex items-center gap-2 cursor-pointer', isSelected && 'bg-accent')} >apps/server/src/routes/worktree/routes/generate-commit-message.ts (2)
8-18: Consider adding event emitter for frontend streaming.As per coding guidelines, server operations should use
createEventEmitter()fromlib/events.tsto emit events that stream to the frontend via WebSocket. While this endpoint uses a simple request/response pattern, emitting progress events (e.g., "generating commit message", "model response received") would provide better UX feedback, especially on slower connections.
150-192: Consider reusingextractTextFromStreamfor Cursor provider path.The Cursor provider path (lines 160-175) duplicates the text extraction logic that exists in
extractTextFromStream(lines 47-72). Both iterate over stream messages looking for assistant content blocks.Suggested refactor
if (isCursorModel(model)) { // Use Cursor provider for Cursor models logger.info(`Using Cursor provider for model: ${model}`); const provider = ProviderFactory.getProviderForModel(model); const bareModel = stripProviderPrefix(model); const cursorPrompt = `${systemPrompt}\n\n${userPrompt}`; - let responseText = ''; - for await (const msg of provider.executeQuery({ + const stream = provider.executeQuery({ prompt: cursorPrompt, model: bareModel, cwd: worktreePath, maxTurns: 1, allowedTools: [], readOnly: true, - })) { - if (msg.type === 'assistant' && msg.message?.content) { - for (const block of msg.message.content) { - if (block.type === 'text' && block.text) { - responseText += block.text; - } - } - } - } + }); - message = responseText.trim(); + message = await extractTextFromStream(stream); } else {apps/ui/src/components/views/board-view/components/list-view/status-badge.tsx (1)
204-218: Pipeline status sorting may not distinguish between different pipeline steps.All pipeline statuses return
1.5, which means multiple pipeline steps will have the same sort order. If the list view needs to order features by their pipeline progression (e.g.,pipeline_reviewbeforepipeline_qa), this won't differentiate them.If pipeline step ordering is needed, consider extracting the step order from
pipelineConfig.steps:♻️ Suggested enhancement
-export function getStatusOrder(status: FeatureStatusWithPipeline): number { +export function getStatusOrder( + status: FeatureStatusWithPipeline, + pipelineConfig?: PipelineConfig | null +): number { const baseOrder: Record<string, number> = { backlog: 0, in_progress: 1, waiting_approval: 2, verified: 3, }; if (isPipelineStatus(status)) { - // Pipeline statuses come after in_progress but before waiting_approval - return 1.5; + const stepId = status.replace('pipeline_', ''); + const stepIndex = pipelineConfig?.steps?.findIndex((s) => s.id === stepId) ?? 0; + // Pipeline statuses come after in_progress (1) but before waiting_approval (2) + // Space them evenly between 1 and 2 + return 1 + (stepIndex + 1) / ((pipelineConfig?.steps?.length ?? 1) + 1); } return baseOrder[status] ?? 0; }apps/ui/src/components/views/settings-view/api-keys/claude-usage-section.tsx (1)
110-146: MoveUsageCardoutside the component to avoid re-creation on every render.Defining
UsageCardinsideClaudeUsageSectioncauses it to be recreated on every render, which can lead to unnecessary re-renders of child components and potential performance issues. Since it only depends ongetUsageColor, consider either moving it outside or memoizing it.♻️ Suggested refactor
// Move outside ClaudeUsageSection, above the component definition const UsageCard = memo(function UsageCard({ title, subtitle, percentage, resetText, getUsageColor, }: { title: string; subtitle: string; percentage: number; resetText?: string; getUsageColor: (percentage: number) => string; }) { const safePercentage = Math.min(Math.max(percentage, 0), MAX_PERCENTAGE); // ... rest of implementation });apps/ui/src/components/views/board-view/components/list-view/list-view.tsx (1)
239-316: Reduce code duplication in handler creation.Each optional handler follows the same pattern: look up feature by ID, then call the action if found. Consider extracting a helper to reduce repetition:
♻️ Suggested refactor
+ // Helper to wrap handlers that need feature lookup + const wrapHandler = useCallback( + (handler: ((f: Feature) => void) | undefined) => { + if (!handler) return undefined; + return (id: string) => { + const f = allFeatures.find((feat) => feat.id === id); + if (f) handler(f); + }; + }, + [allFeatures] + ); const createHandlers = useCallback( (feature: Feature): RowActionHandlers => { return createRowActionHandlers(feature.id, { - editFeature: (id) => { - const f = allFeatures.find((f) => f.id === id); - if (f) actionHandlers.onEdit(f); - }, + editFeature: wrapHandler(actionHandlers.onEdit), deleteFeature: (id) => actionHandlers.onDelete(id), - viewOutput: actionHandlers.onViewOutput - ? (id) => { - const f = allFeatures.find((f) => f.id === id); - if (f) actionHandlers.onViewOutput?.(f); - } - : undefined, + viewOutput: wrapHandler(actionHandlers.onViewOutput), // ... apply same pattern to other handlers }); }, - [actionHandlers, allFeatures] + [actionHandlers, wrapHandler] );apps/ui/src/components/views/board-view/hooks/use-list-view-state.ts (1)
146-160:loadPersistedState()called twice during initialization.Each
useStateinitializer callsloadPersistedState(), which parses localStorage twice on mount. Consider loading once:♻️ Proposed fix
export function useListViewState(): UseListViewStateReturn { - // Initialize state from localStorage - const [viewMode, setViewModeState] = useState<ViewMode>(() => loadPersistedState().viewMode); - const [sortConfig, setSortConfigState] = useState<SortConfig>( - () => loadPersistedState().sortConfig - ); + // Initialize state from localStorage (load once) + const [{ viewMode, sortConfig: initialSortConfig }] = useState(loadPersistedState); + const [viewModeState, setViewModeState] = useState<ViewMode>(viewMode); + const [sortConfig, setSortConfigState] = useState<SortConfig>(initialSortConfig);Or use a single state object:
+ const [state, setState] = useState<ListViewPersistedState>(loadPersistedState); + const { viewMode, sortConfig } = state;apps/ui/src/components/views/board-view.tsx (1)
1-1: Consider removing@ts-nocheckto enable type safety.The entire file has TypeScript checking disabled. While this may be a legacy issue, it undermines type safety for this complex component. Consider gradually addressing type issues and removing this directive.
apps/ui/src/components/views/board-view/components/list-view/row-actions.tsx (1)
263-280: Stale closure risk withsetOpeninuseCallbackdependencies.
setOpenis redefined on every render since it's not memoized. This meanshandleOpenChangeandwithClosewill be recreated on every render despite usinguseCallback, and may capture stale references.♻️ Suggested fix using useCallback for setOpen
// Use controlled or uncontrolled state const open = isOpen ?? internalOpen; - const setOpen = (value: boolean) => { - if (onOpenChange) { - onOpenChange(value); - } else { - setInternalOpen(value); - } - }; + const setOpen = useCallback( + (value: boolean) => { + if (onOpenChange) { + onOpenChange(value); + } else { + setInternalOpen(value); + } + }, + [onOpenChange] + ); const handleOpenChange = useCallback( (newOpen: boolean) => { setOpen(newOpen); }, - [setOpen] + [setOpen] );apps/ui/src/components/views/board-view/components/list-view/list-row.tsx (3)
16-47: Consider i18n for date formatting.The
'en-US'locale is hardcoded. If the application supports internationalization, consider using a configurable locale or the user's browser locale.
52-59: Falsy check may unintentionally handle0priority.The
!prioritycheck returnsnullfor0as well asundefined. If0could ever be a valid priority value, this would silently fail.♻️ More explicit check
- if (!priority) return null; + if (priority == null) return null;
299-310: Remove unusedpipelineConfigprop from the component.The
pipelineConfigprop is destructured but never referenced anywhere in the component body. This unused parameter should be removed from the destructuring.
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (61)
apps/server/src/index.tsapps/server/src/routes/worktree/index.tsapps/server/src/routes/worktree/routes/generate-commit-message.tsapps/server/src/services/claude-usage-service.tsapps/ui/src/app.tsxapps/ui/src/components/layout/sidebar.tsxapps/ui/src/components/layout/sidebar/components/collapse-toggle-button.tsxapps/ui/src/components/layout/sidebar/components/project-actions.tsxapps/ui/src/components/layout/sidebar/components/sidebar-footer.tsxapps/ui/src/components/layout/sidebar/components/sidebar-navigation.tsxapps/ui/src/components/ui/autocomplete.tsxapps/ui/src/components/ui/command.tsxapps/ui/src/components/usage-popover.tsxapps/ui/src/components/views/agent-view.tsxapps/ui/src/components/views/agent-view/input-area/input-controls.tsxapps/ui/src/components/views/board-view.tsxapps/ui/src/components/views/board-view/board-controls.tsxapps/ui/src/components/views/board-view/board-header.tsxapps/ui/src/components/views/board-view/components/index.tsapps/ui/src/components/views/board-view/components/list-view/index.tsapps/ui/src/components/views/board-view/components/list-view/list-header.tsxapps/ui/src/components/views/board-view/components/list-view/list-row.tsxapps/ui/src/components/views/board-view/components/list-view/list-view.tsxapps/ui/src/components/views/board-view/components/list-view/row-actions.tsxapps/ui/src/components/views/board-view/components/list-view/status-badge.tsxapps/ui/src/components/views/board-view/components/view-toggle.tsxapps/ui/src/components/views/board-view/dialogs/agent-output-modal.tsxapps/ui/src/components/views/board-view/dialogs/commit-worktree-dialog.tsxapps/ui/src/components/views/board-view/header-mobile-menu.tsxapps/ui/src/components/views/board-view/hooks/index.tsapps/ui/src/components/views/board-view/hooks/use-list-view-state.tsapps/ui/src/components/views/board-view/kanban-board.tsxapps/ui/src/components/views/board-view/mobile-usage-bar.tsxapps/ui/src/components/views/board-view/shared/priority-selector.tsxapps/ui/src/components/views/board-view/worktree-panel/components/branch-switch-dropdown.tsxapps/ui/src/components/views/board-view/worktree-panel/components/index.tsapps/ui/src/components/views/board-view/worktree-panel/components/worktree-actions-dropdown.tsxapps/ui/src/components/views/board-view/worktree-panel/components/worktree-mobile-dropdown.tsxapps/ui/src/components/views/board-view/worktree-panel/worktree-panel.tsxapps/ui/src/components/views/graph-view/graph-canvas.tsxapps/ui/src/components/views/settings-view.tsxapps/ui/src/components/views/settings-view/api-keys/claude-usage-section.tsxapps/ui/src/components/views/settings-view/components/settings-header.tsxapps/ui/src/components/views/settings-view/components/settings-navigation.tsxapps/ui/src/components/views/settings-view/feature-defaults/feature-defaults-section.tsxapps/ui/src/components/views/settings-view/model-defaults/model-defaults-section.tsxapps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsxapps/ui/src/components/views/settings-view/prompts/prompt-customization-section.tsxapps/ui/src/hooks/use-media-query.tsapps/ui/src/hooks/use-provider-auth-init.tsapps/ui/src/lib/electron.tsapps/ui/src/lib/http-api-client.tsapps/ui/src/store/app-store.tsapps/ui/src/types/electron.d.tslibs/git-utils/src/diff.tslibs/prompts/src/defaults.tslibs/prompts/src/index.tslibs/prompts/src/merge.tslibs/types/src/index.tslibs/types/src/prompts.tslibs/types/src/settings.ts
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Always import from shared packages (@automaker/*), never from old relative paths
Files:
apps/ui/src/components/ui/command.tsxapps/ui/src/components/layout/sidebar/components/collapse-toggle-button.tsxapps/ui/src/app.tsxlibs/types/src/prompts.tsapps/ui/src/lib/electron.tsapps/ui/src/components/views/board-view/board-controls.tsxapps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsxapps/ui/src/components/views/board-view/worktree-panel/components/index.tslibs/prompts/src/defaults.tsapps/ui/src/components/usage-popover.tsxapps/ui/src/components/views/board-view/components/list-view/status-badge.tsxapps/ui/src/components/ui/autocomplete.tsxapps/ui/src/components/views/agent-view.tsxapps/ui/src/components/layout/sidebar.tsxapps/ui/src/components/layout/sidebar/components/project-actions.tsxapps/ui/src/components/views/board-view/board-header.tsxapps/ui/src/components/views/board-view/worktree-panel/components/branch-switch-dropdown.tsxapps/ui/src/components/views/board-view/components/list-view/list-view.tsxlibs/types/src/settings.tslibs/types/src/index.tsapps/ui/src/hooks/use-media-query.tsapps/ui/src/components/views/board-view/mobile-usage-bar.tsxapps/server/src/routes/worktree/routes/generate-commit-message.tslibs/prompts/src/index.tsapps/server/src/index.tsapps/ui/src/components/views/graph-view/graph-canvas.tsxapps/ui/src/components/views/agent-view/input-area/input-controls.tsxapps/ui/src/lib/http-api-client.tsapps/ui/src/components/views/settings-view/components/settings-header.tsxapps/ui/src/components/views/board-view/dialogs/commit-worktree-dialog.tsxapps/ui/src/components/views/board-view/worktree-panel/components/worktree-actions-dropdown.tsxapps/ui/src/components/views/board-view/worktree-panel/worktree-panel.tsxapps/ui/src/components/views/board-view/kanban-board.tsxapps/ui/src/components/views/board-view/components/view-toggle.tsxapps/ui/src/store/app-store.tsapps/ui/src/components/views/board-view/header-mobile-menu.tsxapps/ui/src/components/views/board-view/hooks/index.tsapps/ui/src/components/views/board-view/dialogs/agent-output-modal.tsxapps/ui/src/components/views/settings-view/prompts/prompt-customization-section.tsxlibs/git-utils/src/diff.tsapps/ui/src/components/views/board-view/components/list-view/list-header.tsxapps/ui/src/components/views/settings-view/feature-defaults/feature-defaults-section.tsxapps/ui/src/types/electron.d.tsapps/ui/src/components/views/board-view/components/index.tsapps/server/src/routes/worktree/index.tsapps/ui/src/components/views/settings-view/api-keys/claude-usage-section.tsxapps/ui/src/components/views/board-view/components/list-view/row-actions.tsxapps/server/src/services/claude-usage-service.tsapps/ui/src/hooks/use-provider-auth-init.tsapps/ui/src/components/views/board-view/components/list-view/index.tsapps/ui/src/components/views/settings-view/model-defaults/model-defaults-section.tsxapps/ui/src/components/views/board-view/shared/priority-selector.tsxapps/ui/src/components/views/settings-view.tsxapps/ui/src/components/views/board-view/hooks/use-list-view-state.tsapps/ui/src/components/views/settings-view/components/settings-navigation.tsxlibs/prompts/src/merge.tsapps/ui/src/components/views/board-view/components/list-view/list-row.tsxapps/ui/src/components/views/board-view.tsxapps/ui/src/components/layout/sidebar/components/sidebar-navigation.tsxapps/ui/src/components/layout/sidebar/components/sidebar-footer.tsxapps/ui/src/components/views/board-view/worktree-panel/components/worktree-mobile-dropdown.tsx
**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Use
resolveModelString()from @automaker/model-resolver to convert model aliases (haiku, sonnet, opus) to full model names
Files:
apps/ui/src/components/ui/command.tsxapps/ui/src/components/layout/sidebar/components/collapse-toggle-button.tsxapps/ui/src/app.tsxlibs/types/src/prompts.tsapps/ui/src/lib/electron.tsapps/ui/src/components/views/board-view/board-controls.tsxapps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsxapps/ui/src/components/views/board-view/worktree-panel/components/index.tslibs/prompts/src/defaults.tsapps/ui/src/components/usage-popover.tsxapps/ui/src/components/views/board-view/components/list-view/status-badge.tsxapps/ui/src/components/ui/autocomplete.tsxapps/ui/src/components/views/agent-view.tsxapps/ui/src/components/layout/sidebar.tsxapps/ui/src/components/layout/sidebar/components/project-actions.tsxapps/ui/src/components/views/board-view/board-header.tsxapps/ui/src/components/views/board-view/worktree-panel/components/branch-switch-dropdown.tsxapps/ui/src/components/views/board-view/components/list-view/list-view.tsxlibs/types/src/settings.tslibs/types/src/index.tsapps/ui/src/hooks/use-media-query.tsapps/ui/src/components/views/board-view/mobile-usage-bar.tsxapps/server/src/routes/worktree/routes/generate-commit-message.tslibs/prompts/src/index.tsapps/server/src/index.tsapps/ui/src/components/views/graph-view/graph-canvas.tsxapps/ui/src/components/views/agent-view/input-area/input-controls.tsxapps/ui/src/lib/http-api-client.tsapps/ui/src/components/views/settings-view/components/settings-header.tsxapps/ui/src/components/views/board-view/dialogs/commit-worktree-dialog.tsxapps/ui/src/components/views/board-view/worktree-panel/components/worktree-actions-dropdown.tsxapps/ui/src/components/views/board-view/worktree-panel/worktree-panel.tsxapps/ui/src/components/views/board-view/kanban-board.tsxapps/ui/src/components/views/board-view/components/view-toggle.tsxapps/ui/src/store/app-store.tsapps/ui/src/components/views/board-view/header-mobile-menu.tsxapps/ui/src/components/views/board-view/hooks/index.tsapps/ui/src/components/views/board-view/dialogs/agent-output-modal.tsxapps/ui/src/components/views/settings-view/prompts/prompt-customization-section.tsxlibs/git-utils/src/diff.tsapps/ui/src/components/views/board-view/components/list-view/list-header.tsxapps/ui/src/components/views/settings-view/feature-defaults/feature-defaults-section.tsxapps/ui/src/types/electron.d.tsapps/ui/src/components/views/board-view/components/index.tsapps/server/src/routes/worktree/index.tsapps/ui/src/components/views/settings-view/api-keys/claude-usage-section.tsxapps/ui/src/components/views/board-view/components/list-view/row-actions.tsxapps/server/src/services/claude-usage-service.tsapps/ui/src/hooks/use-provider-auth-init.tsapps/ui/src/components/views/board-view/components/list-view/index.tsapps/ui/src/components/views/settings-view/model-defaults/model-defaults-section.tsxapps/ui/src/components/views/board-view/shared/priority-selector.tsxapps/ui/src/components/views/settings-view.tsxapps/ui/src/components/views/board-view/hooks/use-list-view-state.tsapps/ui/src/components/views/settings-view/components/settings-navigation.tsxlibs/prompts/src/merge.tsapps/ui/src/components/views/board-view/components/list-view/list-row.tsxapps/ui/src/components/views/board-view.tsxapps/ui/src/components/layout/sidebar/components/sidebar-navigation.tsxapps/ui/src/components/layout/sidebar/components/sidebar-footer.tsxapps/ui/src/components/views/board-view/worktree-panel/components/worktree-mobile-dropdown.tsx
apps/server/src/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Use
createEventEmitter()fromlib/events.tsfor all server operations to emit events that stream to frontend via WebSocket
Files:
apps/server/src/routes/worktree/routes/generate-commit-message.tsapps/server/src/index.tsapps/server/src/routes/worktree/index.tsapps/server/src/services/claude-usage-service.ts
🧠 Learnings (3)
📚 Learning: 2025-12-30T01:02:07.114Z
Learnt from: illia1f
Repo: AutoMaker-Org/automaker PR: 324
File: apps/ui/src/components/views/board-view/components/kanban-card/kanban-card.tsx:122-131
Timestamp: 2025-12-30T01:02:07.114Z
Learning: Tailwind CSS v4 uses postfix syntax for the important modifier: append ! at the end of the utility class (e.g., backdrop-blur-[0px]! or hover:bg-red-500!). The older v3 style used a prefix (!) at the start (e.g., !backdrop-blur-[0px]); prefer the postfix form for consistency across TSX files.
Applied to files:
apps/ui/src/components/ui/command.tsxapps/ui/src/components/layout/sidebar/components/collapse-toggle-button.tsxapps/ui/src/app.tsxapps/ui/src/components/views/board-view/board-controls.tsxapps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsxapps/ui/src/components/usage-popover.tsxapps/ui/src/components/views/board-view/components/list-view/status-badge.tsxapps/ui/src/components/ui/autocomplete.tsxapps/ui/src/components/views/agent-view.tsxapps/ui/src/components/layout/sidebar.tsxapps/ui/src/components/layout/sidebar/components/project-actions.tsxapps/ui/src/components/views/board-view/board-header.tsxapps/ui/src/components/views/board-view/worktree-panel/components/branch-switch-dropdown.tsxapps/ui/src/components/views/board-view/components/list-view/list-view.tsxapps/ui/src/components/views/board-view/mobile-usage-bar.tsxapps/ui/src/components/views/graph-view/graph-canvas.tsxapps/ui/src/components/views/agent-view/input-area/input-controls.tsxapps/ui/src/components/views/settings-view/components/settings-header.tsxapps/ui/src/components/views/board-view/dialogs/commit-worktree-dialog.tsxapps/ui/src/components/views/board-view/worktree-panel/components/worktree-actions-dropdown.tsxapps/ui/src/components/views/board-view/worktree-panel/worktree-panel.tsxapps/ui/src/components/views/board-view/kanban-board.tsxapps/ui/src/components/views/board-view/components/view-toggle.tsxapps/ui/src/components/views/board-view/header-mobile-menu.tsxapps/ui/src/components/views/board-view/dialogs/agent-output-modal.tsxapps/ui/src/components/views/settings-view/prompts/prompt-customization-section.tsxapps/ui/src/components/views/board-view/components/list-view/list-header.tsxapps/ui/src/components/views/settings-view/feature-defaults/feature-defaults-section.tsxapps/ui/src/components/views/settings-view/api-keys/claude-usage-section.tsxapps/ui/src/components/views/board-view/components/list-view/row-actions.tsxapps/ui/src/components/views/settings-view/model-defaults/model-defaults-section.tsxapps/ui/src/components/views/board-view/shared/priority-selector.tsxapps/ui/src/components/views/settings-view.tsxapps/ui/src/components/views/settings-view/components/settings-navigation.tsxapps/ui/src/components/views/board-view/components/list-view/list-row.tsxapps/ui/src/components/views/board-view.tsxapps/ui/src/components/layout/sidebar/components/sidebar-navigation.tsxapps/ui/src/components/layout/sidebar/components/sidebar-footer.tsxapps/ui/src/components/views/board-view/worktree-panel/components/worktree-mobile-dropdown.tsx
📚 Learning: 2025-12-28T05:07:48.147Z
Learnt from: CR
Repo: AutoMaker-Org/automaker PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-28T05:07:48.147Z
Learning: Applies to apps/server/src/**/*.{ts,tsx} : Use `createEventEmitter()` from `lib/events.ts` for all server operations to emit events that stream to frontend via WebSocket
Applied to files:
apps/server/src/index.ts
📚 Learning: 2025-12-28T05:07:48.147Z
Learnt from: CR
Repo: AutoMaker-Org/automaker PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-28T05:07:48.147Z
Learning: Store project-specific rules in `.automaker/context/` and load them into agent prompts via `loadContextFiles()` from automaker/utils
Applied to files:
libs/prompts/src/merge.ts
🧬 Code graph analysis (30)
apps/ui/src/components/ui/command.tsx (1)
apps/ui/src/lib/utils.ts (1)
cn(6-8)
apps/ui/src/app.tsx (1)
apps/ui/src/hooks/use-provider-auth-init.ts (1)
useProviderAuthInit(13-102)
libs/types/src/prompts.ts (2)
libs/types/src/index.ts (3)
CommitMessagePrompts(101-101)CustomPrompt(96-96)ResolvedCommitMessagePrompts(107-107)libs/prompts/src/index.ts (1)
ResolvedCommitMessagePrompts(65-65)
apps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsx (4)
apps/ui/src/hooks/use-media-query.ts (1)
useIsMobile(40-42)libs/types/src/index.ts (6)
CodexModelId(80-80)REASONING_EFFORT_LABELS(152-152)REASONING_EFFORT_LEVELS(151-151)ModelAlias(79-79)THINKING_LEVEL_LABELS(150-150)THINKING_LEVELS(149-149)libs/types/src/model.ts (2)
CodexModelId(70-70)ModelAlias(69-69)apps/ui/src/components/views/board-view/shared/model-constants.ts (4)
REASONING_EFFORT_LABELS(149-156)REASONING_EFFORT_LEVELS(140-147)THINKING_LEVEL_LABELS(128-134)THINKING_LEVELS(126-126)
libs/prompts/src/defaults.ts (3)
libs/prompts/src/index.ts (3)
DEFAULT_COMMIT_MESSAGE_SYSTEM_PROMPT(44-44)DEFAULT_COMMIT_MESSAGE_PROMPTS(45-45)ResolvedCommitMessagePrompts(65-65)libs/types/src/index.ts (1)
ResolvedCommitMessagePrompts(107-107)libs/types/src/prompts.ts (1)
ResolvedCommitMessagePrompts(173-175)
apps/ui/src/components/views/board-view/components/list-view/status-badge.tsx (4)
apps/ui/src/components/views/board-view/constants.ts (1)
COLUMNS(102-102)apps/ui/src/components/views/board-view/components/list-view/index.ts (3)
StatusBadgeProps(20-20)StatusBadge(19-19)getStatusLabel(19-19)apps/ui/src/components/views/board-view/components/index.ts (3)
StatusBadgeProps(33-33)StatusBadge(22-22)getStatusLabel(23-23)apps/ui/src/lib/utils.ts (1)
cn(6-8)
apps/ui/src/components/layout/sidebar.tsx (1)
apps/ui/src/lib/utils.ts (1)
cn(6-8)
apps/ui/src/components/views/board-view/board-header.tsx (5)
apps/ui/src/store/app-store.ts (1)
ViewMode(50-62)apps/ui/src/components/views/board-view/components/index.ts (2)
ViewMode(5-5)ViewToggle(5-5)apps/ui/src/components/views/board-view/components/view-toggle.tsx (2)
ViewMode(4-4)ViewToggle(16-62)apps/ui/src/hooks/use-media-query.ts (1)
useIsMobile(40-42)apps/ui/src/components/views/board-view/header-mobile-menu.tsx (1)
HeaderMobileMenu(35-174)
apps/ui/src/components/views/board-view/components/list-view/list-view.tsx (4)
apps/ui/src/components/views/board-view/hooks/use-list-view-state.ts (2)
SortConfig(15-18)SortColumn(9-9)apps/ui/src/components/views/board-view/constants.ts (1)
getColumnsWithPipeline(107-129)apps/ui/src/components/views/board-view/components/list-view/status-badge.tsx (1)
getStatusOrder(204-218)apps/ui/src/components/views/board-view/components/list-view/list-header.tsx (1)
ListHeader(189-259)
libs/types/src/settings.ts (2)
libs/types/src/index.ts (1)
PhaseModelEntry(117-117)apps/server/src/types/settings.ts (1)
PhaseModelEntry(25-25)
apps/ui/src/components/views/board-view/mobile-usage-bar.tsx (4)
apps/ui/src/lib/utils.ts (1)
cn(6-8)apps/ui/src/lib/electron.ts (1)
getElectronAPI(909-918)apps/ui/src/lib/codex-usage-format.ts (1)
getCodexWindowLabel(52-64)apps/ui/src/components/ui/provider-icon.tsx (2)
AnthropicIcon(153-155)OpenAIIcon(157-159)
apps/server/src/routes/worktree/routes/generate-commit-message.ts (2)
apps/server/src/services/settings-service.ts (1)
SettingsService(106-772)libs/types/src/settings.ts (1)
DEFAULT_PHASE_MODELS(644-665)
apps/server/src/index.ts (1)
apps/server/src/routes/worktree/index.ts (1)
createWorktreeRoutes(45-123)
apps/ui/src/components/views/agent-view/input-area/input-controls.tsx (4)
apps/ui/src/components/views/agent-view/shared/agent-model-selector.tsx (1)
AgentModelSelector(21-25)apps/ui/src/components/views/agent-view/shared/index.ts (1)
AgentModelSelector(1-1)apps/ui/src/components/ui/button.tsx (1)
Button(108-108)apps/ui/src/lib/utils.ts (1)
cn(6-8)
apps/ui/src/components/views/board-view/dialogs/commit-worktree-dialog.tsx (1)
apps/ui/src/lib/electron.ts (1)
getElectronAPI(909-918)
apps/ui/src/components/views/board-view/worktree-panel/components/worktree-actions-dropdown.tsx (1)
apps/ui/src/lib/utils.ts (1)
cn(6-8)
apps/ui/src/components/views/board-view/worktree-panel/worktree-panel.tsx (5)
apps/ui/src/hooks/use-media-query.ts (1)
useIsMobile(40-42)apps/ui/src/components/views/board-view/worktree-panel/components/worktree-mobile-dropdown.tsx (1)
WorktreeMobileDropdown(23-112)apps/ui/src/components/views/board-view/worktree-panel/components/branch-switch-dropdown.tsx (1)
BranchSwitchDropdown(31-117)apps/ui/src/components/views/board-view/worktree-panel/components/worktree-actions-dropdown.tsx (1)
WorktreeActionsDropdown(65-376)apps/ui/src/lib/utils.ts (1)
cn(6-8)
apps/ui/src/components/views/board-view/kanban-board.tsx (1)
apps/ui/src/lib/utils.ts (1)
cn(6-8)
apps/ui/src/components/views/board-view/components/view-toggle.tsx (2)
apps/ui/src/components/views/board-view/components/index.ts (2)
ViewMode(5-5)ViewToggle(5-5)apps/ui/src/lib/utils.ts (1)
cn(6-8)
apps/ui/src/store/app-store.ts (1)
apps/ui/src/hooks/use-settings-migration.ts (1)
syncSettingsToServer(653-663)
apps/ui/src/components/views/board-view/header-mobile-menu.tsx (2)
apps/ui/src/components/views/board-view/mobile-usage-bar.tsx (1)
MobileUsageBar(104-230)apps/ui/src/lib/utils.ts (1)
cn(6-8)
apps/ui/src/components/views/board-view/dialogs/agent-output-modal.tsx (1)
apps/ui/src/components/ui/markdown.tsx (1)
Markdown(16-51)
apps/ui/src/components/views/board-view/components/list-view/list-header.tsx (2)
apps/ui/src/components/views/board-view/hooks/use-list-view-state.ts (3)
SortColumn(9-9)SortConfig(15-18)SortDirection(12-12)apps/ui/src/lib/utils.ts (1)
cn(6-8)
apps/ui/src/components/views/settings-view/feature-defaults/feature-defaults-section.tsx (1)
libs/types/src/settings.ts (1)
PlanningMode(69-69)
apps/ui/src/components/views/board-view/components/list-view/row-actions.tsx (2)
apps/ui/src/store/app-store.ts (1)
Feature(341-358)apps/ui/src/lib/utils.ts (1)
cn(6-8)
apps/ui/src/hooks/use-provider-auth-init.ts (2)
apps/ui/src/store/setup-store.ts (3)
useSetupStore(222-293)ClaudeAuthMethod(84-91)CodexAuthMethod(67-71)apps/ui/src/lib/http-api-client.ts (1)
getHttpApiClient(2474-2479)
apps/ui/src/components/views/board-view/hooks/use-list-view-state.ts (2)
apps/ui/src/components/views/board-view/components/view-toggle.tsx (1)
ViewMode(4-4)apps/ui/src/lib/storage.ts (2)
getJSON(66-74)setJSON(82-88)
apps/ui/src/components/views/settings-view/components/settings-navigation.tsx (2)
apps/ui/src/lib/utils.ts (1)
cn(6-8)apps/ui/src/components/views/settings-view/config/navigation.ts (2)
GLOBAL_NAV_GROUPS(36-75)PROJECT_NAV_ITEMS(81-83)
libs/prompts/src/merge.ts (3)
libs/prompts/src/index.ts (3)
mergeCommitMessagePrompts(55-55)ResolvedCommitMessagePrompts(65-65)DEFAULT_COMMIT_MESSAGE_PROMPTS(45-45)libs/types/src/prompts.ts (2)
CommitMessagePrompts(102-105)ResolvedCommitMessagePrompts(173-175)libs/prompts/src/defaults.ts (1)
DEFAULT_COMMIT_MESSAGE_PROMPTS(463-465)
apps/ui/src/components/views/board-view/components/list-view/list-row.tsx (4)
apps/ui/src/components/views/board-view/components/list-view/index.ts (7)
RowActionHandlers(17-17)ListRow(10-10)getColumnWidth(5-5)getColumnAlign(6-6)RowActions(16-16)getFeatureSortValue(10-10)sortFeatures(10-10)apps/ui/src/store/app-store.ts (1)
Feature(341-358)apps/ui/src/components/views/board-view/components/list-view/row-actions.tsx (2)
RowActionHandlers(32-46)RowActions(243-597)apps/ui/src/components/views/board-view/components/list-view/list-header.tsx (2)
getColumnWidth(271-274)getColumnAlign(279-284)
🪛 Biome (2.1.2)
apps/server/src/services/claude-usage-service.ts
[error] 227-227: Unexpected control character in a regular expression.
Control characters are unusual and potentially incorrect inputs, so they are disallowed.
(lint/suspicious/noControlCharactersInRegex)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: e2e
apps/ui/src/components/views/board-view/components/list-view/list-row.tsx
Outdated
Show resolved
Hide resolved
apps/ui/src/components/views/settings-view/api-keys/claude-usage-section.tsx
Outdated
Show resolved
Hide resolved
apps/ui/src/components/views/settings-view/components/settings-navigation.tsx
Outdated
Show resolved
Hide resolved
apps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsx
Show resolved
Hide resolved
- Added validation to check if the specified worktree path exists before generating commit messages. - Implemented a check to ensure the worktree path is a valid git repository by verifying the presence of the .git directory. - Improved error handling by returning appropriate responses for invalid paths and non-git repositories.
- Eliminated the getPriorityDisplay function and the PriorityBadge component from the ListRow implementation. - Removed the pipelineConfig prop from ListRowProps interface. - Cleaned up the code to streamline the ListRow component, focusing on essential features.
|
@coderabbitai rereview |
|
@Shironex: I'll perform another complete re-review of all the changes in this PR, examining the mobile responsiveness improvements across all 13 patches and providing comprehensive feedback on implementation quality, security, and best practices. ✅ Actions performedFull review triggered. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 9
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
apps/ui/src/components/views/settings-view/feature-defaults/feature-defaults-section.tsx (1)
24-24: ImportPlanningModefrom shared types instead of defining locally.
PlanningModeis defined in@automaker/types(exported fromlibs/types/src/settings.ts). Per coding guidelines, import from shared packages instead of duplicating local type definitions.Suggested fix
+import type { PlanningMode } from '@automaker/types'; -type PlanningMode = 'skip' | 'lite' | 'spec' | 'full';apps/ui/src/components/views/board-view/dialogs/commit-worktree-dialog.tsx (1)
82-86: Keyboard shortcut doesn't account forisGeneratingstate.The
handleKeyDownchecksisLoadingbut notisGenerating, which could allow committing via Cmd+Enter while the AI is still generating the message.🐛 Proposed fix
const handleKeyDown = (e: React.KeyboardEvent) => { - if (e.key === 'Enter' && e.metaKey && !isLoading && message.trim()) { + if (e.key === 'Enter' && e.metaKey && !isLoading && !isGenerating && message.trim()) { handleCommit(); } };apps/ui/src/components/views/board-view.tsx (1)
1-1:@ts-nocheckdisables all TypeScript checking.This directive disables TypeScript type checking for the entire file, which masks type errors and reduces code safety. The file contains type mismatches (e.g., line 1053:
column.id as anypassed togetColumnFeatures) that should be fixed properly rather than suppressed. Remove the directive and resolve the underlying type issues.
🤖 Fix all issues with AI agents
In @apps/server/src/routes/worktree/index.ts:
- Around line 72-77: The route for '/generate-commit-message' doesn't pass the
events emitter to the handler; update createGenerateCommitMessageHandler to
accept an events parameter and modify its callsite in createWorktreeRoutes
(where router.post is registered) to pass the events instance provided to
createWorktreeRoutes; inside the handler implementation
(createGenerateCommitMessageHandler) use the provided events emitter (created
via createEventEmitter()) to emit progress events as the AI model streams
responses so frontend WebSocket clients receive streaming updates.
In @apps/server/src/routes/worktree/routes/generate-commit-message.ts:
- Around line 183-198: The Cursor provider loop using provider.executeQuery({
prompt: cursorPrompt, model: bareModel, cwd: worktreePath, maxTurns: 1,
allowedTools: [], readOnly: true }) can run indefinitely; add timeout protection
by creating an AbortController or a timeout wrapper and pass a cancel signal or
timeout option to executeQuery (or break the for-await loop when a deadline is
reached), ensure the loop checks the timeout and stops iterating, and surface a
clear timeout/error path while still appending any partial content to
responseText.
In @apps/ui/src/components/views/agent-view.tsx:
- Line 33: The initial modelSelection state uses the alias 'sonnet' directly;
update the initialization of modelSelection (and its useState call) to resolve
the alias via resolveModelString so the Electron API receives the full model
string — e.g., import resolveModelString and set useState<PhaseModelEntry>({
model: resolveModelString('sonnet') }) (or use the re-exported resolver from
@/config/model-config), leaving setModelSelection unchanged.
- Around line 27-32: Extract the hardcoded 1024 breakpoint into a shared
constant (e.g., LG_BREAKPOINT) and replace both occurrences where screen width
is checked (the useEffect that sets showSessionManager and the check at the
other location) to use that constant; inside the useEffect that currently
references window.innerWidth, guard with typeof window !== 'undefined', add a
resize event listener that recalculates isDesktop and calls
setShowSessionManager on resize, and ensure you remove the listener in the
cleanup function; finally, import and use resolveModelString from
'@automaker/model-resolver' when initializing the modelSelection state (replace
the literal 'sonnet' with model: resolveModelString('sonnet')).
In @apps/ui/src/components/views/board-view/components/list-view/list-row.tsx:
- Around line 1-3: This file-wide "// @ts-nocheck" in list-row.tsx hides all
type errors; instead remove the file-level directive and either add targeted "//
@ts-expect-error" only where BaseFeature's `[key: string]: unknown` causes an
error, or create a typed adapter/wrapper for the problematic Feature/BaseFeature
shape (e.g., a NarrowedFeature or FeatureAdapter type) and use that in the
list-row component to avoid the index-signature conflict; also open a tracking
issue to fix BaseFeature's index signature so the targeted workarounds can be
removed later.
In @apps/ui/src/components/views/board-view/kanban-board.tsx:
- Around line 114-121: Replace the non-standard Tailwind class `duration-250`
with a supported duration (e.g., `duration-200` or `duration-300`) or use the
arbitrary syntax `duration-[250ms]` where the class appears; update the JSX in
the KanbanBoard component (the div using cn(..., 'transition-opacity
duration-250', ...)) and the two other occurrences in BoardView (replace
`duration-250` in their className strings) so Tailwind will generate the correct
CSS.
In @apps/ui/src/components/views/board-view/mobile-usage-bar.tsx:
- Line 6: The import useSetupStore is unused in this module; remove the unused
import statement ("import { useSetupStore } from '@/store/setup-store';") from
mobile-usage-bar.tsx so there are no dead imports and linting/import checks
pass.
🧹 Nitpick comments (26)
apps/ui/src/components/views/agent-view.tsx (1)
131-137: Duplicate breakpoint logic.This effect uses the same hardcoded
1024breakpoint as the mount effect. If you adopt theuseIsMobilehook as suggested above, this check can be simplified to reference the shared mobile state.apps/ui/src/components/views/agent-view/input-area/input-controls.tsx (1)
138-180: Consider addingaria-labelfor icon-only buttons.The icon-only buttons have
titleattributes for tooltips, but screen readers don't reliably announcetitle. Addingaria-labelwould improve accessibility for assistive technology users.♿ Suggested accessibility improvement
<Button variant="outline" size="icon" onClick={onToggleImageDropZone} disabled={!isConnected} className={cn( 'h-11 w-11 rounded-xl border-border shrink-0', showImageDropZone && 'bg-primary/10 text-primary border-primary/30', hasFiles && 'border-primary/30 text-primary' )} title="Attach files (images, .txt, .md)" + aria-label="Attach files" > <Paperclip className="w-4 h-4" /> </Button> ... {isProcessing && ( <Button onClick={onStop} disabled={!isConnected} className="h-11 px-4 rounded-xl shrink-0" variant="destructive" data-testid="stop-agent" title="Stop generation" + aria-label="Stop generation" > <Square className="w-4 h-4 fill-current" /> </Button> )} <Button onClick={onSend} disabled={!canSend} className="h-11 px-4 rounded-xl shrink-0" variant={isProcessing ? 'outline' : 'default'} data-testid="send-message" title={isProcessing ? 'Add to queue' : 'Send message'} + aria-label={isProcessing ? 'Add to queue' : 'Send message'} > {isProcessing ? <ListOrdered className="w-4 h-4" /> : <Send className="w-4 h-4" />} </Button>apps/ui/src/components/views/board-view/worktree-panel/components/branch-switch-dropdown.tsx (2)
50-56: Minor sizing conflict in className.When
standaloneis true, bothh-7 w-7(line 51) andh-8 w-8(line 53) are applied. While Tailwind's later classes typically win, this could be clearer.Suggested improvement
className={cn( - 'h-7 w-7 p-0', - !standalone && 'rounded-none border-r-0', - standalone && 'h-8 w-8 shrink-0', + 'p-0', + standalone ? 'h-8 w-8 shrink-0' : 'h-7 w-7 rounded-none border-r-0', !standalone && isSelected && 'bg-primary text-primary-foreground', !standalone && !isSelected && 'bg-secondary/50 hover:bg-secondary' )}
72-74:onKeyPressis deprecated in React.The
onKeyPressevent is deprecated. While it still works, consider consolidating keyboard event handling intoonKeyDownfor consistency and future-proofing.Suggested fix
onKeyDown={(e) => e.stopPropagation()} onKeyUp={(e) => e.stopPropagation()} - onKeyPress={(e) => e.stopPropagation()}apps/server/src/services/claude-usage-service.ts (1)
225-228: Static analysis false positive: control character is intentional for ANSI stripping.The Biome warning about the control character (
\x1B) in the regex is a false positive. This is the standard escape sequence for ANSI codes and is required for proper terminal output cleaning.However, there's code duplication here — this same regex is defined in the
stripAnsiCodesprivate method at lines 348-351. Consider reusing the method instead:♻️ Suggested refactor to eliminate duplication
- // Strip ANSI codes for easier matching - // eslint-disable-next-line no-control-regex - const cleanOutput = output.replace(/\x1B\[[0-9;]*[A-Za-z]/g, ''); + // Strip ANSI codes for easier matching + const cleanOutput = this.stripAnsiCodes(output);apps/ui/src/lib/http-api-client.ts (1)
1684-1685: Consider adding explicit return type for better type safety.The method uses
Promise<any>as its return type. While this is consistent with some other methods likecreatePR, adding an explicit return type would improve type safety and IDE support.💡 Suggested improvement
- generateCommitMessage: (worktreePath: string) => - this.post('/api/worktree/generate-commit-message', { worktreePath }), + generateCommitMessage: (worktreePath: string): Promise<{ + success: boolean; + message?: string; + error?: string; + }> => + this.post('/api/worktree/generate-commit-message', { worktreePath }),apps/ui/src/components/views/board-view/header-mobile-menu.tsx (1)
100-111: Consider using Button component for consistency.The settings button uses a raw
<button>element while the rest of the component uses theButtoncomponent from the UI library. For consistency and to inherit proper styling/accessibility features, consider using theButtoncomponent.💡 Suggested improvement
- <button + <Button + variant="ghost" + size="icon" onClick={(e) => { e.stopPropagation(); onOpenAutoModeSettings(); }} - className="p-1 rounded hover:bg-accent/50 transition-colors" + className="h-6 w-6" title="Auto Mode Settings" data-testid="mobile-auto-mode-settings-button" > <Settings2 className="w-4 h-4 text-muted-foreground" /> - </button> + </Button>apps/ui/src/components/views/board-view/dialogs/commit-worktree-dialog.tsx (1)
88-142: Consider usingworktree?.pathas dependency instead ofworktreeobject.The effect depends on
worktreeobject reference, but only usesworktree.path. If the worktree object is recreated with the same path, this could trigger unnecessary API calls.🔧 Suggested improvement
// Generate AI commit message when dialog opens (if enabled) useEffect(() => { - if (open && worktree) { + if (!open || !worktree) return; + // Reset state setMessage(''); setError(null); // Only generate AI commit message if enabled if (!enableAiCommitMessages) { return; } setIsGenerating(true); let cancelled = false; const generateMessage = async () => { try { const api = getElectronAPI(); if (!api?.worktree?.generateCommitMessage) { if (!cancelled) { setIsGenerating(false); } return; } const result = await api.worktree.generateCommitMessage(worktree.path); // ... rest of the logic } finally { if (!cancelled) { setIsGenerating(false); } } }; generateMessage(); return () => { cancelled = true; }; - } - }, [open, worktree, enableAiCommitMessages]); + }, [open, worktree?.path, enableAiCommitMessages]);apps/ui/src/components/views/board-view/components/view-toggle.tsx (1)
26-42: Addtype="button"to prevent unintended form submissions.Native
<button>elements default totype="submit". If this component is ever used inside a form, clicking the toggle could trigger form submission.🔧 Proposed fix
<button + type="button" role="tab" aria-selected={viewMode === 'kanban'} aria-label="Kanban view" onClick={() => onViewModeChange('kanban')} // ... rest >Apply the same fix to the list view button.
Also applies to: 43-59
apps/ui/src/components/views/board-view/components/list-view/status-badge.tsx (1)
165-169: Consider movingsizeClassesoutside the component.The
sizeClassesobject is recreated on every render. Since it's static, it can be defined outside the component for a minor performance improvement.🔧 Suggested optimization
+const SIZE_CLASSES = { + sm: 'px-1.5 py-0.5 text-[10px]', + default: 'px-2 py-0.5 text-xs', + lg: 'px-2.5 py-1 text-sm', +} as const; + export const StatusBadge = memo(function StatusBadge({ status, pipelineConfig = null, size = 'default', className, }: StatusBadgeProps) { const display = useMemo(() => getStatusDisplay(status, pipelineConfig), [status, pipelineConfig]); - const sizeClasses = { - sm: 'px-1.5 py-0.5 text-[10px]', - default: 'px-2 py-0.5 text-xs', - lg: 'px-2.5 py-1 text-sm', - }; return ( <span className={cn( // ... - sizeClasses[size], + SIZE_CLASSES[size], // ... )} >apps/ui/src/components/views/board-view/mobile-usage-bar.tsx (2)
105-106: Combine multipleuseAppStorecalls into one selector.Two separate
useAppStorecalls trigger two separate subscriptions. Consider combining them.🔧 Suggested improvement
- const { claudeUsage, claudeUsageLastUpdated, setClaudeUsage } = useAppStore(); - const { codexUsage, codexUsageLastUpdated, setCodexUsage } = useAppStore(); + const { + claudeUsage, + claudeUsageLastUpdated, + setClaudeUsage, + codexUsage, + codexUsageLastUpdated, + setCodexUsage, + } = useAppStore();
154-164: Consider stabilizing theisStalechecks to prevent potential unnecessary effect triggers.The
isClaudeStaleandisCodexStalevalues are recalculated on every render usingDate.now(). While the current implementation works (since fetching updateslastUpdatedmakingisStalefalse), including these in the dependency array could cause issues if the component re-renders frequently for other reasons.Consider fetching only once on mount when the show flags are true:
🔧 Suggested improvement
// Auto-fetch on mount if data is stale useEffect(() => { - if (showClaudeUsage && isClaudeStale) { + if (showClaudeUsage && (!claudeUsageLastUpdated || Date.now() - claudeUsageLastUpdated > 2 * 60 * 1000)) { fetchClaudeUsage(); } - }, [showClaudeUsage, isClaudeStale, fetchClaudeUsage]); + // Only check staleness on mount or when showClaudeUsage changes + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [showClaudeUsage]); useEffect(() => { - if (showCodexUsage && isCodexStale) { + if (showCodexUsage && (!codexUsageLastUpdated || Date.now() - codexUsageLastUpdated > 2 * 60 * 1000)) { fetchCodexUsage(); } - }, [showCodexUsage, isCodexStale, fetchCodexUsage]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [showCodexUsage]);apps/ui/src/hooks/use-provider-auth-init.ts (1)
93-101: Potential race condition in initialization guard.The guard condition at line 95 may allow
refreshStatusesto be skipped even when only one of the auth statuses is already set. IfclaudeAuthStatusis non-null butcodexAuthStatusis null (or vice versa), the hook will not initialize the missing one.Consider whether you want to initialize both statuses independently:
useEffect(() => { - // Only initialize once per session if not already set - if (initialized.current || (claudeAuthStatus !== null && codexAuthStatus !== null)) { + // Only initialize once per session + if (initialized.current) { return; } initialized.current = true; void refreshStatuses(); - }, [refreshStatuses, claudeAuthStatus, codexAuthStatus]); + }, [refreshStatuses]);Alternatively, if the intent is to skip initialization when both are already set, the current logic is correct but could benefit from a clarifying comment.
apps/ui/src/components/views/settings-view/api-keys/claude-usage-section.tsx (2)
113-149: MoveUsageCardoutside the component or memoize it.
UsageCardis defined insideClaudeUsageSection, causing it to be recreated on every render. This will reset the progress bar animation state and cause unnecessary DOM reconciliation.♻️ Suggested refactor
Move
UsageCardoutside the component:+const UsageCard = memo(function UsageCard({ + title, + subtitle, + percentage, + resetText, + getUsageColor, +}: { + title: string; + subtitle: string; + percentage: number; + resetText?: string; + getUsageColor: (percentage: number) => string; +}) { + const safePercentage = Math.min(Math.max(percentage, 0), MAX_PERCENTAGE); + + return ( + <div className="rounded-xl border border-border/60 bg-card/50 p-4"> + {/* ... same JSX ... */} + </div> + ); +}); + export function ClaudeUsageSection() { - const UsageCard = ({ ... }) => { ... }; + // Use UsageCard from outer scopeAlternatively, wrap it with
useMemoif it must stay inside for closure access.
50-81: Potential state update on unmounted component.The
fetchUsagecallback doesn't track whether the component is still mounted. If the component unmounts while the async operation is in progress, callingsetError,setClaudeUsage, orsetIsLoadingwill cause a React warning.♻️ Suggested fix with cleanup ref
export function ClaudeUsageSection() { + const isMountedRef = useRef(true); + + useEffect(() => { + return () => { + isMountedRef.current = false; + }; + }, []); + const fetchUsage = useCallback(async () => { setIsLoading(true); setError(null); try { // ... fetch logic ... + if (!isMountedRef.current) return; setClaudeUsage(result); } catch (fetchError) { + if (!isMountedRef.current) return; const message = fetchError instanceof Error ? fetchError.message : CLAUDE_FETCH_ERROR; setError(message); } finally { + if (!isMountedRef.current) return; setIsLoading(false); } }, [setClaudeUsage]);apps/ui/src/components/views/board-view/components/list-view/list-view.tsx (3)
186-191: DefaultselectedFeatureIdscreates new Set on each render.The default value
selectedFeatureIds = new Set()creates a newSetinstance on every render when the prop is not provided. This can cause unnecessary re-renders in memoized child components that depend on referential equality.♻️ Suggested fix
+const EMPTY_SET = new Set<string>(); + export const ListView = memo(function ListView({ // ... isSelectionMode = false, - selectedFeatureIds = new Set(), + selectedFeatureIds = EMPTY_SET, // ... }: ListViewProps) {
239-316: Repeated O(n) lookups in handler creation.Each handler created by
createHandlersperformsallFeatures.find()which is O(n). For large feature lists with many action invocations, this could become a performance bottleneck. Consider creating a lookup Map once.♻️ Suggested optimization
+// Create feature lookup map once +const featureMap = useMemo(() => { + const map = new Map<string, Feature>(); + for (const f of allFeatures) { + map.set(f.id, f); + } + return map; +}, [allFeatures]); + const createHandlers = useCallback( (feature: Feature): RowActionHandlers => { return createRowActionHandlers(feature.id, { editFeature: (id) => { - const f = allFeatures.find((f) => f.id === id); + const f = featureMap.get(id); if (f) actionHandlers.onEdit(f); }, // ... apply same pattern to other handlers }); }, - [actionHandlers, allFeatures] + [actionHandlers, featureMap] );
339-356:handleSelectAlliteration during potential state mutation.When deselecting all, the code iterates over
selectedFeatureIdswhile callingonToggleFeatureSelection, which may mutate the set being iterated. While this works becauseforEachsnapshots the iteration order, it's a fragile pattern.♻️ Safer approach
const handleSelectAll = useCallback(() => { if (!onToggleFeatureSelection) return; if (selectionState.allSelected) { - selectedFeatureIds.forEach((id) => onToggleFeatureSelection(id)); + // Snapshot to array before iterating + Array.from(selectedFeatureIds).forEach((id) => onToggleFeatureSelection(id)); } else { // ... } }, [onToggleFeatureSelection, selectionState.allSelected, selectedFeatureIds, statusGroups]);apps/ui/src/components/views/board-view/components/list-view/row-actions.tsx (2)
263-268: Missing dependency inuseCallback.The
handleOpenChangecallback referencessetOpenbut doesn't include it in the dependency array. WhilesetOpenis technically stable (either from props or local state setter), ESLint's exhaustive-deps rule would flag this.♻️ Suggested fix
const handleOpenChange = useCallback( (newOpen: boolean) => { setOpen(newOpen); }, - [setOpen] + [] // setOpen is stable, or use: + // [onOpenChange] if you want to be explicit );Actually, the current code has
[setOpen]which is correct sincesetOpenis defined above. The issue is thatsetOpenis reassigned on each render whenonOpenChangechanges. Consider usinguseCallbackforsetOpenitself or restructuring.
532-538: Placeholder "View Branch" action is disabled with no-op handler.This action is intentionally disabled (
disabled) with an empty click handler. Consider adding a TODO comment or removing it if the feature isn't planned.apps/ui/src/components/views/board-view.tsx (1)
1049-1056: Type assertionas anybypasses type safety.The cast
column.id as anysuggests a type mismatch between the column ID type and whatgetColumnFeaturesexpects. This should be properly typed rather than usingany.♻️ Suggested approach
const columnFeaturesMap = useMemo(() => { const columns = getColumnsWithPipeline(pipelineConfig); - const map: Record<string, typeof hookFeatures> = {}; + const map: Record<string, Feature[]> = {}; for (const column of columns) { - map[column.id] = getColumnFeatures(column.id as any); + map[column.id] = getColumnFeatures(column.id); } return map; }, [pipelineConfig, getColumnFeatures]);If
getColumnFeaturesexpects a specific union type, consider updating the column configuration types or the function signature to acceptstring.apps/ui/src/components/views/board-view/hooks/use-list-view-state.ts (3)
1-6: ViewMode re-export may shadow app-store's ViewMode type.There's a
ViewModetype in@/store/app-store.tswith a broader union ('welcome' | 'setup' | 'board' | ...). Re-exporting a differentViewMode('kanban' | 'list') from this file could cause confusion or type errors if consumers import from the wrong location. Consider renaming this toBoardViewModeorListViewModefor clarity.
147-151: Double call toloadPersistedState()is inefficient.Each
useStateinitializer independently callsloadPersistedState(), which parses localStorage JSON twice during component mount. Extract this to a single call:♻️ Suggested fix
export function useListViewState(): UseListViewStateReturn { // Initialize state from localStorage - const [viewMode, setViewModeState] = useState<ViewMode>(() => loadPersistedState().viewMode); - const [sortConfig, setSortConfigState] = useState<SortConfig>( - () => loadPersistedState().sortConfig - ); + const [initialState] = useState(loadPersistedState); + const [viewMode, setViewModeState] = useState<ViewMode>(initialState.viewMode); + const [sortConfig, setSortConfigState] = useState<SortConfig>(initialState.sortConfig);
199-222: useMemo dependencies include derived values unnecessarily.
isListViewandisKanbanVieware derived fromviewModeand don't need to be in the dependency array—they'll always change whenviewModechanges. The callbacks (setViewMode,toggleViewMode, etc.) are stable due touseCallbackwith empty/stable deps, so they also don't strictly need to be listed. However, this is a minor overhead and doesn't cause bugs.apps/ui/src/components/views/board-view/components/list-view/list-row.tsx (2)
316-336: UseSortColumntype instead of inline union for consistency.The
columnparameter uses an inline type union that duplicatesSortColumn. Import and use the type directly:♻️ Suggested fix
+import type { SortColumn, SortDirection } from '../../hooks/use-list-view-state'; + export function getFeatureSortValue( feature: Feature, - column: 'title' | 'status' | 'category' | 'priority' | 'createdAt' | 'updatedAt' + column: SortColumn ): string | number | Date {Apply the same change to
sortFeaturesat line 343.
327-328: Extract magic number 999 to a named constant.The fallback priority value
999should be a documented constant for clarity:♻️ Suggested fix
+/** Default priority value for features without priority (sorts last) */ +const NO_PRIORITY_VALUE = 999; + export function getFeatureSortValue( // ... case 'priority': - return feature.priority || 999; // No priority sorts last + return feature.priority ?? NO_PRIORITY_VALUE;
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (61)
apps/server/src/index.tsapps/server/src/routes/worktree/index.tsapps/server/src/routes/worktree/routes/generate-commit-message.tsapps/server/src/services/claude-usage-service.tsapps/ui/src/app.tsxapps/ui/src/components/layout/sidebar.tsxapps/ui/src/components/layout/sidebar/components/collapse-toggle-button.tsxapps/ui/src/components/layout/sidebar/components/project-actions.tsxapps/ui/src/components/layout/sidebar/components/sidebar-footer.tsxapps/ui/src/components/layout/sidebar/components/sidebar-navigation.tsxapps/ui/src/components/ui/autocomplete.tsxapps/ui/src/components/ui/command.tsxapps/ui/src/components/usage-popover.tsxapps/ui/src/components/views/agent-view.tsxapps/ui/src/components/views/agent-view/input-area/input-controls.tsxapps/ui/src/components/views/board-view.tsxapps/ui/src/components/views/board-view/board-controls.tsxapps/ui/src/components/views/board-view/board-header.tsxapps/ui/src/components/views/board-view/components/index.tsapps/ui/src/components/views/board-view/components/list-view/index.tsapps/ui/src/components/views/board-view/components/list-view/list-header.tsxapps/ui/src/components/views/board-view/components/list-view/list-row.tsxapps/ui/src/components/views/board-view/components/list-view/list-view.tsxapps/ui/src/components/views/board-view/components/list-view/row-actions.tsxapps/ui/src/components/views/board-view/components/list-view/status-badge.tsxapps/ui/src/components/views/board-view/components/view-toggle.tsxapps/ui/src/components/views/board-view/dialogs/agent-output-modal.tsxapps/ui/src/components/views/board-view/dialogs/commit-worktree-dialog.tsxapps/ui/src/components/views/board-view/header-mobile-menu.tsxapps/ui/src/components/views/board-view/hooks/index.tsapps/ui/src/components/views/board-view/hooks/use-list-view-state.tsapps/ui/src/components/views/board-view/kanban-board.tsxapps/ui/src/components/views/board-view/mobile-usage-bar.tsxapps/ui/src/components/views/board-view/shared/priority-selector.tsxapps/ui/src/components/views/board-view/worktree-panel/components/branch-switch-dropdown.tsxapps/ui/src/components/views/board-view/worktree-panel/components/index.tsapps/ui/src/components/views/board-view/worktree-panel/components/worktree-actions-dropdown.tsxapps/ui/src/components/views/board-view/worktree-panel/components/worktree-mobile-dropdown.tsxapps/ui/src/components/views/board-view/worktree-panel/worktree-panel.tsxapps/ui/src/components/views/graph-view/graph-canvas.tsxapps/ui/src/components/views/settings-view.tsxapps/ui/src/components/views/settings-view/api-keys/claude-usage-section.tsxapps/ui/src/components/views/settings-view/components/settings-header.tsxapps/ui/src/components/views/settings-view/components/settings-navigation.tsxapps/ui/src/components/views/settings-view/feature-defaults/feature-defaults-section.tsxapps/ui/src/components/views/settings-view/model-defaults/model-defaults-section.tsxapps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsxapps/ui/src/components/views/settings-view/prompts/prompt-customization-section.tsxapps/ui/src/hooks/use-media-query.tsapps/ui/src/hooks/use-provider-auth-init.tsapps/ui/src/lib/electron.tsapps/ui/src/lib/http-api-client.tsapps/ui/src/store/app-store.tsapps/ui/src/types/electron.d.tslibs/git-utils/src/diff.tslibs/prompts/src/defaults.tslibs/prompts/src/index.tslibs/prompts/src/merge.tslibs/types/src/index.tslibs/types/src/prompts.tslibs/types/src/settings.ts
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Always import from shared packages (@automaker/*), never from old relative paths
Files:
apps/ui/src/components/views/board-view/worktree-panel/components/index.tsapps/ui/src/components/views/board-view/board-controls.tsxapps/ui/src/components/views/settings-view/model-defaults/model-defaults-section.tsxapps/ui/src/components/ui/autocomplete.tsxapps/ui/src/components/views/board-view/hooks/index.tsapps/server/src/routes/worktree/index.tsapps/ui/src/lib/http-api-client.tsapps/ui/src/components/usage-popover.tsxapps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsxapps/ui/src/components/views/board-view/components/view-toggle.tsxapps/ui/src/components/views/board-view/components/list-view/status-badge.tsxlibs/types/src/settings.tsapps/ui/src/components/views/settings-view/feature-defaults/feature-defaults-section.tsxapps/ui/src/components/views/settings-view/components/settings-header.tsxapps/ui/src/store/app-store.tsapps/ui/src/types/electron.d.tsapps/ui/src/components/views/board-view/mobile-usage-bar.tsxapps/ui/src/components/views/board-view/worktree-panel/worktree-panel.tsxlibs/prompts/src/merge.tsapps/ui/src/hooks/use-media-query.tsapps/ui/src/components/layout/sidebar/components/collapse-toggle-button.tsxapps/ui/src/components/ui/command.tsxlibs/prompts/src/index.tsapps/ui/src/hooks/use-provider-auth-init.tsapps/ui/src/components/views/board-view/hooks/use-list-view-state.tsapps/ui/src/components/views/settings-view/api-keys/claude-usage-section.tsxapps/server/src/routes/worktree/routes/generate-commit-message.tslibs/types/src/index.tsapps/ui/src/app.tsxapps/ui/src/components/views/board-view/header-mobile-menu.tsxapps/ui/src/components/views/agent-view.tsxapps/ui/src/components/views/board-view/dialogs/commit-worktree-dialog.tsxapps/ui/src/components/views/settings-view/prompts/prompt-customization-section.tsxapps/ui/src/components/views/board-view/components/list-view/list-view.tsxapps/ui/src/components/views/board-view/components/list-view/row-actions.tsxapps/ui/src/components/views/board-view/worktree-panel/components/worktree-actions-dropdown.tsxapps/ui/src/components/views/board-view/components/list-view/index.tsapps/ui/src/components/views/board-view/dialogs/agent-output-modal.tsxlibs/prompts/src/defaults.tsapps/ui/src/components/views/board-view/worktree-panel/components/branch-switch-dropdown.tsxapps/ui/src/components/layout/sidebar/components/sidebar-navigation.tsxlibs/types/src/prompts.tsapps/ui/src/components/layout/sidebar/components/project-actions.tsxapps/ui/src/components/views/board-view/board-header.tsxapps/ui/src/components/views/board-view/components/list-view/list-header.tsxapps/ui/src/components/views/board-view/kanban-board.tsxapps/ui/src/components/views/settings-view.tsxapps/ui/src/components/views/board-view/components/index.tsapps/ui/src/lib/electron.tslibs/git-utils/src/diff.tsapps/ui/src/components/views/board-view/worktree-panel/components/worktree-mobile-dropdown.tsxapps/server/src/services/claude-usage-service.tsapps/ui/src/components/views/settings-view/components/settings-navigation.tsxapps/ui/src/components/views/board-view.tsxapps/ui/src/components/views/graph-view/graph-canvas.tsxapps/ui/src/components/views/board-view/shared/priority-selector.tsxapps/ui/src/components/layout/sidebar/components/sidebar-footer.tsxapps/ui/src/components/views/agent-view/input-area/input-controls.tsxapps/ui/src/components/layout/sidebar.tsxapps/ui/src/components/views/board-view/components/list-view/list-row.tsxapps/server/src/index.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Use
resolveModelString()from @automaker/model-resolver to convert model aliases (haiku, sonnet, opus) to full model names
Files:
apps/ui/src/components/views/board-view/worktree-panel/components/index.tsapps/ui/src/components/views/board-view/board-controls.tsxapps/ui/src/components/views/settings-view/model-defaults/model-defaults-section.tsxapps/ui/src/components/ui/autocomplete.tsxapps/ui/src/components/views/board-view/hooks/index.tsapps/server/src/routes/worktree/index.tsapps/ui/src/lib/http-api-client.tsapps/ui/src/components/usage-popover.tsxapps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsxapps/ui/src/components/views/board-view/components/view-toggle.tsxapps/ui/src/components/views/board-view/components/list-view/status-badge.tsxlibs/types/src/settings.tsapps/ui/src/components/views/settings-view/feature-defaults/feature-defaults-section.tsxapps/ui/src/components/views/settings-view/components/settings-header.tsxapps/ui/src/store/app-store.tsapps/ui/src/types/electron.d.tsapps/ui/src/components/views/board-view/mobile-usage-bar.tsxapps/ui/src/components/views/board-view/worktree-panel/worktree-panel.tsxlibs/prompts/src/merge.tsapps/ui/src/hooks/use-media-query.tsapps/ui/src/components/layout/sidebar/components/collapse-toggle-button.tsxapps/ui/src/components/ui/command.tsxlibs/prompts/src/index.tsapps/ui/src/hooks/use-provider-auth-init.tsapps/ui/src/components/views/board-view/hooks/use-list-view-state.tsapps/ui/src/components/views/settings-view/api-keys/claude-usage-section.tsxapps/server/src/routes/worktree/routes/generate-commit-message.tslibs/types/src/index.tsapps/ui/src/app.tsxapps/ui/src/components/views/board-view/header-mobile-menu.tsxapps/ui/src/components/views/agent-view.tsxapps/ui/src/components/views/board-view/dialogs/commit-worktree-dialog.tsxapps/ui/src/components/views/settings-view/prompts/prompt-customization-section.tsxapps/ui/src/components/views/board-view/components/list-view/list-view.tsxapps/ui/src/components/views/board-view/components/list-view/row-actions.tsxapps/ui/src/components/views/board-view/worktree-panel/components/worktree-actions-dropdown.tsxapps/ui/src/components/views/board-view/components/list-view/index.tsapps/ui/src/components/views/board-view/dialogs/agent-output-modal.tsxlibs/prompts/src/defaults.tsapps/ui/src/components/views/board-view/worktree-panel/components/branch-switch-dropdown.tsxapps/ui/src/components/layout/sidebar/components/sidebar-navigation.tsxlibs/types/src/prompts.tsapps/ui/src/components/layout/sidebar/components/project-actions.tsxapps/ui/src/components/views/board-view/board-header.tsxapps/ui/src/components/views/board-view/components/list-view/list-header.tsxapps/ui/src/components/views/board-view/kanban-board.tsxapps/ui/src/components/views/settings-view.tsxapps/ui/src/components/views/board-view/components/index.tsapps/ui/src/lib/electron.tslibs/git-utils/src/diff.tsapps/ui/src/components/views/board-view/worktree-panel/components/worktree-mobile-dropdown.tsxapps/server/src/services/claude-usage-service.tsapps/ui/src/components/views/settings-view/components/settings-navigation.tsxapps/ui/src/components/views/board-view.tsxapps/ui/src/components/views/graph-view/graph-canvas.tsxapps/ui/src/components/views/board-view/shared/priority-selector.tsxapps/ui/src/components/layout/sidebar/components/sidebar-footer.tsxapps/ui/src/components/views/agent-view/input-area/input-controls.tsxapps/ui/src/components/layout/sidebar.tsxapps/ui/src/components/views/board-view/components/list-view/list-row.tsxapps/server/src/index.ts
apps/server/src/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Use
createEventEmitter()fromlib/events.tsfor all server operations to emit events that stream to frontend via WebSocket
Files:
apps/server/src/routes/worktree/index.tsapps/server/src/routes/worktree/routes/generate-commit-message.tsapps/server/src/services/claude-usage-service.tsapps/server/src/index.ts
🧠 Learnings (4)
📚 Learning: 2025-12-30T01:02:07.114Z
Learnt from: illia1f
Repo: AutoMaker-Org/automaker PR: 324
File: apps/ui/src/components/views/board-view/components/kanban-card/kanban-card.tsx:122-131
Timestamp: 2025-12-30T01:02:07.114Z
Learning: Tailwind CSS v4 uses postfix syntax for the important modifier: append ! at the end of the utility class (e.g., backdrop-blur-[0px]! or hover:bg-red-500!). The older v3 style used a prefix (!) at the start (e.g., !backdrop-blur-[0px]); prefer the postfix form for consistency across TSX files.
Applied to files:
apps/ui/src/components/views/board-view/board-controls.tsxapps/ui/src/components/views/settings-view/model-defaults/model-defaults-section.tsxapps/ui/src/components/ui/autocomplete.tsxapps/ui/src/components/usage-popover.tsxapps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsxapps/ui/src/components/views/board-view/components/view-toggle.tsxapps/ui/src/components/views/board-view/components/list-view/status-badge.tsxapps/ui/src/components/views/settings-view/feature-defaults/feature-defaults-section.tsxapps/ui/src/components/views/settings-view/components/settings-header.tsxapps/ui/src/components/views/board-view/mobile-usage-bar.tsxapps/ui/src/components/views/board-view/worktree-panel/worktree-panel.tsxapps/ui/src/components/layout/sidebar/components/collapse-toggle-button.tsxapps/ui/src/components/ui/command.tsxapps/ui/src/components/views/settings-view/api-keys/claude-usage-section.tsxapps/ui/src/app.tsxapps/ui/src/components/views/board-view/header-mobile-menu.tsxapps/ui/src/components/views/agent-view.tsxapps/ui/src/components/views/board-view/dialogs/commit-worktree-dialog.tsxapps/ui/src/components/views/settings-view/prompts/prompt-customization-section.tsxapps/ui/src/components/views/board-view/components/list-view/list-view.tsxapps/ui/src/components/views/board-view/components/list-view/row-actions.tsxapps/ui/src/components/views/board-view/worktree-panel/components/worktree-actions-dropdown.tsxapps/ui/src/components/views/board-view/dialogs/agent-output-modal.tsxapps/ui/src/components/views/board-view/worktree-panel/components/branch-switch-dropdown.tsxapps/ui/src/components/layout/sidebar/components/sidebar-navigation.tsxapps/ui/src/components/layout/sidebar/components/project-actions.tsxapps/ui/src/components/views/board-view/board-header.tsxapps/ui/src/components/views/board-view/components/list-view/list-header.tsxapps/ui/src/components/views/board-view/kanban-board.tsxapps/ui/src/components/views/settings-view.tsxapps/ui/src/components/views/board-view/worktree-panel/components/worktree-mobile-dropdown.tsxapps/ui/src/components/views/settings-view/components/settings-navigation.tsxapps/ui/src/components/views/board-view.tsxapps/ui/src/components/views/graph-view/graph-canvas.tsxapps/ui/src/components/views/board-view/shared/priority-selector.tsxapps/ui/src/components/layout/sidebar/components/sidebar-footer.tsxapps/ui/src/components/views/agent-view/input-area/input-controls.tsxapps/ui/src/components/layout/sidebar.tsxapps/ui/src/components/views/board-view/components/list-view/list-row.tsx
📚 Learning: 2025-12-28T05:07:48.147Z
Learnt from: CR
Repo: AutoMaker-Org/automaker PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-28T05:07:48.147Z
Learning: Store project-specific rules in `.automaker/context/` and load them into agent prompts via `loadContextFiles()` from automaker/utils
Applied to files:
libs/prompts/src/merge.ts
📚 Learning: 2025-12-28T05:07:48.147Z
Learnt from: CR
Repo: AutoMaker-Org/automaker PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-28T05:07:48.147Z
Learning: Applies to **/*.{ts,tsx} : Use `resolveModelString()` from automaker/model-resolver to convert model aliases (haiku, sonnet, opus) to full model names
Applied to files:
apps/ui/src/components/views/agent-view.tsx
📚 Learning: 2025-12-28T05:07:48.147Z
Learnt from: CR
Repo: AutoMaker-Org/automaker PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-28T05:07:48.147Z
Learning: Applies to apps/server/src/**/*.{ts,tsx} : Use `createEventEmitter()` from `lib/events.ts` for all server operations to emit events that stream to frontend via WebSocket
Applied to files:
apps/server/src/index.ts
🧬 Code graph analysis (29)
apps/server/src/routes/worktree/index.ts (3)
apps/server/src/lib/events.ts (1)
EventEmitter(13-16)apps/server/src/services/settings-service.ts (1)
SettingsService(106-772)apps/server/src/routes/worktree/routes/generate-commit-message.ts (1)
createGenerateCommitMessageHandler(76-243)
apps/ui/src/components/views/board-view/components/view-toggle.tsx (1)
apps/ui/src/lib/utils.ts (1)
cn(6-8)
apps/ui/src/components/views/board-view/components/list-view/status-badge.tsx (2)
apps/ui/src/components/views/board-view/constants.ts (1)
COLUMNS(102-102)apps/ui/src/lib/utils.ts (1)
cn(6-8)
apps/ui/src/components/views/settings-view/feature-defaults/feature-defaults-section.tsx (1)
libs/types/src/settings.ts (1)
PlanningMode(69-69)
apps/ui/src/components/views/settings-view/components/settings-header.tsx (2)
apps/ui/src/components/ui/button.tsx (1)
Button(108-108)apps/ui/src/lib/utils.ts (1)
cn(6-8)
apps/ui/src/store/app-store.ts (2)
apps/ui/src/lib/http-api-client.ts (1)
get(791-819)apps/ui/src/hooks/use-settings-migration.ts (1)
syncSettingsToServer(653-663)
libs/prompts/src/merge.ts (3)
libs/prompts/src/index.ts (3)
mergeCommitMessagePrompts(55-55)ResolvedCommitMessagePrompts(65-65)DEFAULT_COMMIT_MESSAGE_PROMPTS(45-45)libs/types/src/prompts.ts (2)
CommitMessagePrompts(102-105)ResolvedCommitMessagePrompts(173-175)libs/prompts/src/defaults.ts (1)
DEFAULT_COMMIT_MESSAGE_PROMPTS(463-465)
apps/ui/src/components/ui/command.tsx (1)
apps/ui/src/lib/utils.ts (1)
cn(6-8)
apps/ui/src/hooks/use-provider-auth-init.ts (2)
apps/ui/src/store/setup-store.ts (3)
useSetupStore(222-293)ClaudeAuthMethod(84-91)CodexAuthMethod(67-71)apps/ui/src/lib/http-api-client.ts (1)
getHttpApiClient(2474-2479)
apps/ui/src/components/views/board-view/hooks/use-list-view-state.ts (3)
apps/ui/src/store/app-store.ts (1)
ViewMode(50-62)apps/ui/src/components/views/board-view/components/view-toggle.tsx (1)
ViewMode(4-4)apps/ui/src/lib/storage.ts (1)
getJSON(66-74)
apps/ui/src/components/views/settings-view/api-keys/claude-usage-section.tsx (3)
apps/ui/src/store/setup-store.ts (1)
useSetupStore(222-293)apps/ui/src/lib/electron.ts (1)
getElectronAPI(909-918)apps/ui/src/lib/utils.ts (1)
cn(6-8)
apps/ui/src/app.tsx (1)
apps/ui/src/hooks/use-provider-auth-init.ts (1)
useProviderAuthInit(13-102)
apps/ui/src/components/views/board-view/header-mobile-menu.tsx (3)
apps/ui/src/components/ui/button.tsx (1)
Button(108-108)apps/ui/src/components/views/board-view/mobile-usage-bar.tsx (1)
MobileUsageBar(104-230)apps/ui/src/lib/utils.ts (1)
cn(6-8)
apps/ui/src/components/views/settings-view/prompts/prompt-customization-section.tsx (2)
libs/prompts/src/defaults.ts (1)
DEFAULT_COMMIT_MESSAGE_PROMPTS(463-465)libs/prompts/src/index.ts (1)
DEFAULT_COMMIT_MESSAGE_PROMPTS(45-45)
apps/ui/src/components/views/board-view/components/list-view/list-view.tsx (4)
apps/ui/src/components/views/board-view/constants.ts (1)
getColumnsWithPipeline(107-129)apps/ui/src/components/views/board-view/components/list-view/list-row.tsx (1)
ListRow(185-311)apps/ui/src/components/views/board-view/components/list-view/row-actions.tsx (2)
RowActionHandlers(32-46)createRowActionHandlers(602-635)apps/ui/src/components/views/board-view/components/list-view/list-header.tsx (1)
ListHeader(189-259)
apps/ui/src/components/views/board-view/components/list-view/row-actions.tsx (2)
apps/ui/src/store/app-store.ts (1)
Feature(341-358)apps/ui/src/lib/utils.ts (1)
cn(6-8)
apps/ui/src/components/views/board-view/worktree-panel/components/worktree-actions-dropdown.tsx (1)
apps/ui/src/lib/utils.ts (1)
cn(6-8)
libs/types/src/prompts.ts (2)
libs/types/src/index.ts (3)
CommitMessagePrompts(101-101)CustomPrompt(96-96)ResolvedCommitMessagePrompts(107-107)libs/prompts/src/index.ts (1)
ResolvedCommitMessagePrompts(65-65)
apps/ui/src/components/views/board-view/board-header.tsx (3)
apps/ui/src/store/app-store.ts (1)
ViewMode(50-62)apps/ui/src/components/views/board-view/components/view-toggle.tsx (2)
ViewMode(4-4)ViewToggle(16-62)apps/ui/src/hooks/use-media-query.ts (1)
useIsMobile(40-42)
apps/ui/src/components/views/board-view/components/list-view/list-header.tsx (2)
apps/ui/src/components/views/board-view/hooks/use-list-view-state.ts (3)
SortColumn(9-9)SortConfig(15-18)SortDirection(12-12)apps/ui/src/lib/utils.ts (1)
cn(6-8)
apps/ui/src/components/views/board-view/kanban-board.tsx (1)
apps/ui/src/lib/utils.ts (1)
cn(6-8)
apps/ui/src/components/views/settings-view.tsx (2)
apps/ui/src/components/views/settings-view/components/settings-header.tsx (1)
SettingsHeader(12-64)apps/ui/src/components/views/settings-view/config/navigation.ts (1)
NAV_ITEMS(86-86)
apps/ui/src/components/views/board-view/worktree-panel/components/worktree-mobile-dropdown.tsx (3)
libs/types/src/settings.ts (1)
WorktreeInfo(570-581)apps/server/src/services/dev-server-service.ts (1)
isRunning(414-416)apps/ui/src/lib/utils.ts (1)
cn(6-8)
apps/ui/src/components/views/settings-view/components/settings-navigation.tsx (2)
apps/ui/src/lib/utils.ts (1)
cn(6-8)apps/ui/src/components/views/settings-view/config/navigation.ts (2)
GLOBAL_NAV_GROUPS(36-75)PROJECT_NAV_ITEMS(81-83)
apps/ui/src/components/views/board-view.tsx (4)
apps/ui/src/components/views/board-view/hooks/use-list-view-state.ts (1)
useListViewState(146-223)apps/ui/src/components/views/board-view/constants.ts (1)
getColumnsWithPipeline(107-129)apps/ui/src/components/views/board-view/components/list-view/list-view.tsx (1)
ListView(177-443)apps/ui/src/components/views/board-view/kanban-board.tsx (1)
KanbanBoard(64-334)
apps/ui/src/components/views/agent-view/input-area/input-controls.tsx (3)
apps/ui/src/components/views/agent-view/shared/agent-model-selector.tsx (1)
AgentModelSelector(21-25)apps/ui/src/components/ui/button.tsx (1)
Button(108-108)apps/ui/src/lib/utils.ts (1)
cn(6-8)
apps/ui/src/components/layout/sidebar.tsx (1)
apps/ui/src/lib/utils.ts (1)
cn(6-8)
apps/ui/src/components/views/board-view/components/list-view/list-row.tsx (4)
apps/ui/src/store/app-store.ts (1)
Feature(341-358)apps/ui/src/components/views/board-view/components/list-view/row-actions.tsx (2)
RowActionHandlers(32-46)RowActions(243-597)apps/ui/src/lib/utils.ts (1)
cn(6-8)apps/ui/src/components/views/board-view/components/list-view/list-header.tsx (2)
getColumnWidth(271-274)getColumnAlign(279-284)
apps/server/src/index.ts (1)
apps/server/src/routes/worktree/index.ts (1)
createWorktreeRoutes(45-123)
🪛 Biome (2.1.2)
apps/server/src/services/claude-usage-service.ts
[error] 227-227: Unexpected control character in a regular expression.
Control characters are unusual and potentially incorrect inputs, so they are disallowed.
(lint/suspicious/noControlCharactersInRegex)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: e2e
apps/server/src/routes/worktree/routes/generate-commit-message.ts
Outdated
Show resolved
Hide resolved
apps/server/src/routes/worktree/routes/generate-commit-message.ts
Outdated
Show resolved
Hide resolved
apps/ui/src/components/views/board-view/components/list-view/list-row.tsx
Show resolved
Hide resolved
apps/ui/src/components/views/board-view/components/list-view/list-row.tsx
Outdated
Show resolved
Hide resolved
Resolves merge conflicts by keeping both features: - enableAiCommitMessages (from our branch) - defaultFeatureModel (from v0.11.0rc) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…nd timeout management - Modified command arguments in tests to include '--add-dir' for better context. - Updated error messages for authentication and timeout scenarios to provide clearer guidance. - Adjusted timer values in tests to align with implementation delays, ensuring accurate simulation of usage data retrieval.
Summary
This PR applies 13 patches from a Discord contributor that improve mobile responsiveness across the application:
Test plan
Notes
🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
UI/UX Improvements
✏️ Tip: You can customize this high-level summary in your review settings.