-
Notifications
You must be signed in to change notification settings - Fork 461
feat(ui): migrate to React Query for data fetching #499
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
base: main
Are you sure you want to change the base?
Conversation
- Install @tanstack/react-query and @tanstack/react-query-devtools - Add QueryClient with default stale times and retry config - Create query-keys.ts factory for consistent cache key management - Wrap app root with QueryClientProvider and DevTools Co-Authored-By: Claude Opus 4.5 <[email protected]>
- Add useFeatures, useFeature, useAgentOutput for feature data - Add useGitHubIssues, useGitHubPRs, useGitHubValidations, useGitHubIssueComments - Add useClaudeUsage, useCodexUsage with polling intervals - Add useRunningAgents, useRunningAgentsCount - Add useWorktrees, useWorktreeInfo, useWorktreeStatus, useWorktreeDiffs - Add useGlobalSettings, useProjectSettings, useCredentials - Add useAvailableModels, useCodexModels, useOpencodeModels - Add useSessions, useSessionHistory, useSessionQueue - Add useIdeationPrompts, useIdeas - Add CLI status queries (claude, cursor, codex, opencode, github) - Add useCursorPermissionsQuery, useWorkspaceDirectories - Add usePipelineConfig, useSpecFile, useSpecRegenerationStatus Co-Authored-By: Claude Opus 4.5 <[email protected]>
- Add feature mutations (create, update, delete with optimistic updates) - Add auto-mode mutations (start, stop, approve plan) - Add worktree mutations (create, delete, checkout, switch branch) - Add settings mutations (update global/project, validate API keys) - Add GitHub mutations (create PR, validate PR) - Add cursor permissions mutations (apply profile, copy config) - Add spec mutations (generate, update, save) - Add pipeline mutations (toggle, update config) - Add session mutations with cache invalidation Co-Authored-By: Claude Opus 4.5 <[email protected]>
- Add useAutoModeQueryInvalidation for feature/agent events - Add useSpecRegenerationQueryInvalidation for spec updates - Add useGitHubValidationQueryInvalidation for PR validation events - Bridge WebSocket events to cache invalidation for real-time updates Co-Authored-By: Claude Opus 4.5 <[email protected]>
- Add reusable SkeletonPulse component to replace 4 duplicate definitions - Update CLI status components to use shared skeleton - Simplify CLI status components by using React Query hooks Co-Authored-By: Claude Opus 4.5 <[email protected]>
- Replace manual fetching in use-board-features with useFeatures query - Migrate use-board-actions to use mutation hooks - Update kanban-card and agent-info-panel to use query hooks - Migrate agent-output-modal to useAgentOutput query - Migrate create-pr-dialog to useCreatePR mutation - Remove manual loading/error state management - Add proper cache invalidation on mutations Co-Authored-By: Claude Opus 4.5 <[email protected]>
- Migrate use-worktrees to useWorktrees query hook - Migrate use-branches to useWorktreeBranches query hook - Migrate use-available-editors to useAvailableEditors query hook - Migrate use-worktree-actions to use mutation hooks - Update worktree-panel component to use query data - Remove manual state management for loading/errors Co-Authored-By: Claude Opus 4.5 <[email protected]>
- Migrate use-github-issues to useGitHubIssues query - Migrate use-issue-comments to useGitHubIssueComments infinite query - Migrate use-issue-validation to useGitHubValidations with mutations - Migrate github-prs-view to useGitHubPRs query - Support pagination for comments with useInfiniteQuery - Remove manual loading state management Co-Authored-By: Claude Opus 4.5 <[email protected]>
- Migrate use-cursor-permissions to query and mutation hooks - Migrate use-cursor-status to React Query - Migrate use-skills-settings to useUpdateGlobalSettings mutation - Migrate use-subagents-settings to mutation hooks - Migrate use-subagents to useDiscoveredAgents query - Migrate opencode-settings-tab to React Query hooks - Migrate worktrees-section to query hooks - Migrate codex/claude usage sections to query hooks - Remove manual useState for loading/error states Co-Authored-By: Claude Opus 4.5 <[email protected]>
- Migrate claude-usage-popover to useClaudeUsage query with polling - Migrate codex-usage-popover to useCodexUsage query with polling - Migrate usage-popover to React Query hooks - Migrate running-agents-view to useRunningAgents query - Replace manual polling intervals with refetchInterval - Remove manual loading/error state management Co-Authored-By: Claude Opus 4.5 <[email protected]>
- Migrate workspace-picker-modal to useWorkspaceDirectories query - Migrate session-manager to useSessions query - Migrate git-diff-panel to useGitDiffs query - Migrate prompt-list to useIdeationPrompts query - Migrate spec-view hooks to useSpecFile query and spec mutations - Migrate use-board-background-settings to useProjectSettings query - Migrate use-guided-prompts to useIdeationPrompts query - Migrate use-project-settings-loader to React Query - Complete React Query migration across all components Co-Authored-By: Claude Opus 4.5 <[email protected]>
|
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 Note Other AI code review bot(s) detectedCodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review. 📝 WalkthroughWalkthroughThis PR introduces React Query as a centralized data-fetching and state-management solution across the UI. It replaces manual API calls, local state management, and direct Electron API usage with declarative query and mutation hooks, adds comprehensive cache management with configurable stale times, and wires query invalidation for cache coherence. Over 80 files are refactored to consume these hooks. Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 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 introduces a significant refactor of the UI's data fetching mechanism, replacing manual implementations with TanStack React Query. This change aims to improve the application's responsiveness, reduce code duplication, and provide a more maintainable architecture for data management. The PR also includes several infrastructure improvements and component migrations to fully leverage React Query's 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 pull request significantly refactors the UI's data fetching and state management by migrating numerous components from manual useState/useEffect patterns and Zustand store interactions to TanStack React Query. Key changes include upgrading React Query dependencies, introducing new query and mutation hooks (useClaudeUsage, useCodexUsage, useWorkspaceDirectories, useSessions, useWorktreeDiffs, useGitDiffs, usePipelineConfig, useFeature, useAgentOutput, useWorktreeBranches, useVerifyFeature, useResumeFeature, useFeatures, useAvailableEditors, useWorktreeInitScript, useGithubIssues, useGitHubIssueComments, useValidateIssue, useMarkValidationViewed, useGitHubPRs, useGenerateIdeationSuggestions, useRunningAgents, useStopFeature, useSetInitScript, useDeleteInitScript, useCreateSpec, useRegenerateSpec, useGenerateFeatures, useSaveSpec, useCursorPermissionsQuery, useApplyCursorProfile, useCopyCursorConfig, useCursorCliStatus, useUpdateGlobalSettings, useUpdateProjectSettings, useSubagentsSettings, useSkillsSettings, useDiscoveredAgents), and centralizing query invalidation logic based on WebSocket events. Many components now derive loading, error, and data states directly from React Query hooks, simplifying their internal logic. A new SkeletonPulse component was added for loading indicators. Review comments highlight the need to map generic React Query errors back to more specific, user-friendly messages in some usage popovers, to fully migrate components to React Query as the single source of truth for data (rather than dual-updating Zustand), and to consider adding generic toast notifications for query errors in the global error handler.
|
@coderabbitai review this |
|
✅ 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: 12
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/board-view/dialogs/agent-output-modal.tsx (1)
85-250: MissingonClosein useEffect dependency array.The
onClosecallback is invoked at line 234 but is not included in the dependency array at line 250. This can cause a stale closure bug where an outdatedonClosereference is called if the parent re-renders with a new callback.Proposed fix
return () => { unsubscribe(); }; - }, [open, featureId]); + }, [open, featureId, onClose]);apps/ui/src/components/views/github-issues-view/hooks/use-issue-validation.ts (1)
264-287: CallresolveModelString()before passing model to mutation.Per coding guidelines for TypeScript files, model aliases (haiku, sonnet, opus) must be resolved to full model names using
resolveModelString()from@automaker/model-resolver. Currently,modelToUseis passed directly to the mutation without resolution. Apply the same pattern used inmodel-config.ts:const modelToUse = resolveModelString(normalizedEntry.model);Import:
import { resolveModelString } from '@automaker/model-resolver';apps/ui/src/components/views/github-prs-view.tsx (1)
380-391:group-hoverwon't work — parent lacksgroupclass.The button uses
group-hover:opacity-100but the parent<div>inPRRow(line 315) doesn't have thegroupclass. The button will remain invisible on hover.Suggested fix
Add
groupclass to the parent div:<div className={cn( - 'flex items-start gap-3 p-3 cursor-pointer hover:bg-accent/50 transition-colors', + 'group flex items-start gap-3 p-3 cursor-pointer hover:bg-accent/50 transition-colors', isSelected && 'bg-accent' )} onClick={onClick} >
🤖 Fix all issues with AI agents
In `@apps/ui/src/components/session-manager.tsx`:
- Around line 155-160: The useEffect currently lists a boolean expression
([sessions.length > 0]) which is unstable and causes ESLint/misrun issues;
update it to use a stable dependency list and a ref to run only once on initial
load: add a useRef flag (e.g., firstLoadRef) and inside the useEffect that
depends on sessions (or sessions.length) check if firstLoadRef.current is false
and sessions.length > 0 then call checkRunningSessions(sessions) and set
firstLoadRef.current = true; ensure checkRunningSessions and sessions are
included in the dependency array (or memoize checkRunningSessions) so ESLint
warnings are resolved.
In `@apps/ui/src/components/usage-popover.tsx`:
- Around line 137-139: The wrapper functions fetchClaudeUsage and
fetchCodexUsage are defined with no parameters but are invoked with a boolean
(false) elsewhere; update either the call sites to remove the unused argument or
change the wrappers to accept a parameter and forward it to
refetchClaude/refetchCodex (e.g., add a param like force?: boolean and call
refetchClaude(force) / refetchCodex(force)) so the signatures match the usages;
adjust all occurrences (including the calls around lines ~302-311, 307, 414) for
consistency.
In
`@apps/ui/src/components/views/board-view/components/kanban-card/kanban-card.tsx`:
- Line 100: Update the import paths in kanban-card.tsx and agent-info-panel.tsx
to follow the `@automaker/`* package pattern: replace imports from '@/lib/utils'
with '@automaker/utils', replace '@/lib/agent-context-parser' with
'@automaker/agent-context-parser', and replace '@/store/app-store' with the
appropriate `@automaker` package (e.g., '@automaker/store' or
'@automaker/app-store' depending on the published package name used elsewhere in
the repo); adjust the import lines in those two files to use these `@automaker/`*
module specifiers, ensure any exported symbols (e.g., useWorktrees,
currentProject, agent-context parsing helpers) are correctly referenced after
the change, and run type-check/build to confirm the new module paths resolve.
In
`@apps/ui/src/components/views/board-view/worktree-panel/hooks/use-branches.ts`:
- Around line 16-28: The gitRepoStatus defaults currently set isGitRepo: true
and hasCommits: true cause the UI to assume repo capability before
useWorktreeBranches completes; change the construction of gitRepoStatus (where
branchData is read) to default both fields to false (e.g., use
branchData?.isGitRepo ?? false and branchData?.hasCommits ?? false) so the UI
reflects an unknown/unavailable state while isLoadingBranches is true or the
query fails; ensure worktree-actions-dropdown behavior relies on these
conservative values rather than presuming true.
In `@apps/ui/src/components/views/spec-view/hooks/use-spec-loading.ts`:
- Around line 27-47: loadSpec currently calls queryClient.invalidateQueries then
immediately uses queryClient.getQueryData to check status, which can return
stale data; replace that pattern by awaiting the latest status via
queryClient.fetchQuery (or awaiting a refetch) for the queryKey from
queryKeys.specRegeneration.status(currentProject.path) to obtain up-to-date
statusData before checking isRunning, then proceed to invalidate/refetch
queryKeys.spec.file; keep the currentProject?.path guard and existing queryKey
symbols (loadSpec, queryClient.invalidateQueries,
queryKeys.specRegeneration.status, queryKeys.spec.file,
queryClient.getQueryData) but remove the stale getQueryData use in favor of
fetchQuery/awaited refetch.
In `@apps/ui/src/components/views/spec-view/hooks/use-spec-save.ts`:
- Around line 9-10: The mutation is being instantiated with an empty-string
fallback which can produce an invalid path; change the call site so useSaveSpec
receives a valid path or is not created when currentProject is null: either pass
currentProject?.path (allowing undefined) instead of '' or wrap the useSaveSpec
call behind a conditional so saveMutation is only created when currentProject
exists, and/or update useSaveSpec to accept an optional projectPath and guard
inside its saveSpec handler to prevent writes when projectPath is missing;
reference useSaveSpec and saveMutation and ensure any UI checks (e.g., the
existing Line 13 guard) align with this conditional instantiation.
In `@apps/ui/src/hooks/mutations/use-auto-mode-mutations.ts`:
- Around line 103-125: useStopFeature only invalidates runningAgents but not the
features cache, which can leave stale feature status in the UI; update
useStopFeature to also invalidate the features cache after success by calling
queryClient.invalidateQueries for the features key (e.g.,
queryClient.invalidateQueries({ queryKey: queryKeys.features.all() })); if
features are namespaced by projectPath, either add projectPath to the
hook/mutation input and invalidate the specific project features key, or use the
broader queryKey invalidation pattern to clear all features if projectPath isn't
available.
In `@apps/ui/src/hooks/mutations/use-github-mutations.ts`:
- Around line 40-44: The example and mutation code pass a model alias string
directly to the API; call resolveModelString from `@automaker/model-resolver` to
convert the alias to a canonical model identifier before sending the payload.
Import resolveModelString into use-github-mutations.ts and use it to transform
the model field (where model: 'sonnet' or the variable holding model is
assembled) inside the mutation handler or payload builder (e.g., in the function
that constructs the request body in useGithubMutations) so the resolved model
string is sent to the API instead of the alias.
In `@apps/ui/src/hooks/mutations/use-spec-mutations.ts`:
- Around line 155-179: The mutation constructs a file path with projectPath and
will write to "/.automaker/..." if projectPath is empty; update useSaveSpec so
mutationFn validates projectPath before calling api.writeFile (e.g., at the top
of mutationFn check if (!projectPath || projectPath.trim() === '') throw new
Error('Invalid projectPath')); then build the path and call api.writeFile, so
you never attempt to write to the root when projectPath is empty and the onError
handler will surface the error; reference useSaveSpec, its mutationFn,
projectPath and api.writeFile for where to add the guard.
In `@apps/ui/src/hooks/queries/use-settings.ts`:
- Around line 105-122: The query key for useDiscoveredAgents omits the sources
parameter so changes to sources won't invalidate cache; update the queryKey to
include sources (e.g., pass sources or a stable representation like sources ??
[] as an extra segment) when calling queryKeys.settings.agents inside
useDiscoveredAgents, or modify queryKeys.settings.agents to accept a sources
argument and use that in the returned key; keep the rest of the hook
(getElectronAPI(), api.settings.discoverAgents, enabled, staleTime) unchanged.
In `@apps/ui/src/hooks/queries/use-worktrees.ts`:
- Around line 163-206: The query key for useWorktreeBranches is missing the
includeRemote flag so cached results can be wrong; update the query key factory
used by queryKeys.worktrees.branches to accept includeRemote (e.g.,
branches(worktreePath, includeRemote)) and change the call in
useWorktreeBranches to pass includeRemote into queryKey, ensuring the key
uniquely reflects both worktreePath and includeRemote; keep the rest of the
query logic and enabled/staleTime behavior unchanged.
In `@apps/ui/src/hooks/use-query-invalidation.ts`:
- Around line 139-164: In useGitHubValidationQueryInvalidation, the optional
chaining on api.github?.onValidationEvent can leave unsubscribe undefined and
cause the returned cleanup to throw; fix by explicitly checking for api.github
before subscribing (e.g., if (!api.github) return undefined or set unsubscribe
to a noop function) so that unsubscribe is always a callable cleanup; update the
code around getElectronAPI()/api.github and the unsubscribe return so the
cleanup always returns a function (or undefined safely) to avoid calling
undefined.
♻️ Duplicate comments (3)
apps/ui/src/lib/query-client.ts (1)
57-70: Query errors are silently logged without user feedback.Unlike
handleMutationError, this handler only logs errors and doesn't show a toast for general query failures. While background refetch failures may not need toasts, initial query failures could leave users unaware of issues if individual queries don't implement their ownonError.Consider whether this is intentional UX behavior or if a generic toast should be added for consistency with mutation error handling.
apps/ui/src/components/views/board-view.tsx (1)
1571-1577: Dual source of truth remains a concern.The cache invalidation via React Query followed by Zustand update maintains two sources of truth for pipeline configuration. While the comment acknowledges this is for "backward compatibility," this creates potential for state drift between React Query cache and Zustand store.
Consider tracking a follow-up task to fully migrate consumers to React Query, eliminating the need for the Zustand update. Until then, ensure the invalidation completes before the Zustand update to maintain consistency.
Potential improvement for consistency
// Invalidate React Query cache to refetch updated config - queryClient.invalidateQueries({ + await queryClient.invalidateQueries({ queryKey: queryKeys.pipeline.config(currentProject.path), }); // Also update Zustand for backward compatibility setPipelineConfig(currentProject.path, config);Using
awaitensures the cache is invalidated before updating Zustand, though the real fix is to eliminate the dual state.apps/ui/src/components/claude-usage-popover.tsx (1)
185-191: Generic error message loses actionable context.The previous implementation likely had specific error handling (e.g.,
TRUST_PROMPT) that provided tailored user guidance. The current generic message suggestsclaude loginfor all errors, which may not be appropriate for network failures or other non-auth issues.Consider mapping error types to specific user-friendly messages, similar to how other usage popovers in the codebase handle this.
🧹 Nitpick comments (46)
apps/ui/src/hooks/use-project-settings-loader.ts (2)
29-32: Consider resetting the ref when settings change to avoid stale state.The
appliedProjectRefprevents re-application when the same project stays selected. However, if settings are updated elsewhere (e.g., another client or settings panel) and React Query refetches, the new values won't be applied because the ref still matches the current project path.If re-applying updated settings is desired, consider keying the ref on a combination of project path and a settings version/timestamp, or removing the guard and relying on setter idempotency.
55-72: Type safety could be improved in the settingsMap iteration.The cast on line 70 loses type information. While this works at runtime if the API types align, TypeScript won't catch mismatches between setting keys and their expected value types (e.g.,
cardOpacityexpects a number,columnBorderEnabledexpects a boolean).If you prefer the iteration pattern, consider a type-safe approach:
♻️ Suggested improvement
- // Settings map for cleaner iteration - const settingsMap = { - cardOpacity: setCardOpacity, - columnOpacity: setColumnOpacity, - columnBorderEnabled: setColumnBorderEnabled, - cardGlassmorphism: setCardGlassmorphism, - cardBorderEnabled: setCardBorderEnabled, - cardBorderOpacity: setCardBorderOpacity, - hideScrollbar: setHideScrollbar, - } as const; - - // Apply all settings that are defined - for (const [key, setter] of Object.entries(settingsMap)) { - const value = bg?.[key as keyof typeof bg]; - if (value !== undefined) { - (setter as (path: string, val: typeof value) => void)(projectPath, value); - } - } + // Apply all board background settings that are defined + if (bg?.cardOpacity !== undefined) setCardOpacity(projectPath, bg.cardOpacity); + if (bg?.columnOpacity !== undefined) setColumnOpacity(projectPath, bg.columnOpacity); + if (bg?.columnBorderEnabled !== undefined) setColumnBorderEnabled(projectPath, bg.columnBorderEnabled); + if (bg?.cardGlassmorphism !== undefined) setCardGlassmorphism(projectPath, bg.cardGlassmorphism); + if (bg?.cardBorderEnabled !== undefined) setCardBorderEnabled(projectPath, bg.cardBorderEnabled); + if (bg?.cardBorderOpacity !== undefined) setCardBorderOpacity(projectPath, bg.cardBorderOpacity); + if (bg?.hideScrollbar !== undefined) setHideScrollbar(projectPath, bg.hideScrollbar);The explicit approach trades brevity for full type safety—TypeScript will catch any type mismatches at compile time.
apps/ui/src/hooks/use-board-background-settings.ts (2)
16-24:awaitonmutate()has no effect; usemutateAsync()if awaiting is intended.
persistSettingscallsupdateProjectSettings.mutate(), which is fire-and-forget and returnsvoid. Theawaitin callers likesetBoardBackground(line 63) does nothing. This pattern repeats across all wrapper functions.If you need to wait for the mutation to complete (e.g., for error handling or sequencing), use
mutateAsync()instead. Otherwise, remove theasync/awaitkeywords to avoid confusion.Option 1: Use mutateAsync for true async behavior
const persistSettings = useCallback( (projectPath: string, settingsToUpdate: Record<string, unknown>) => { - updateProjectSettings.mutate({ + return updateProjectSettings.mutateAsync({ projectPath, settings: { boardBackground: settingsToUpdate }, }); }, [updateProjectSettings] );Option 2: Remove async/await if fire-and-forget is intentional
const setBoardBackground = useCallback( - async (projectPath: string, imagePath: string | null) => { + (projectPath: string, imagePath: string | null) => { const current = getCurrentSettings(projectPath); const toUpdate = { ...current, imagePath, imageVersion: imagePath ? Date.now() : undefined, }; store.setBoardBackground(projectPath, imagePath); - await persistSettings(projectPath, toUpdate); + persistSettings(projectPath, toUpdate); }, [store, persistSettings, getCurrentSettings] );Also applies to: 47-66
143-160: Consider extracting default settings to avoid duplication.The default values in
clearBoardBackground(lines 147-157) duplicate those ingetCurrentSettings(lines 31-40). Extracting to a shared constant would ensure consistency.Suggested refactor
+const DEFAULT_BACKGROUND_SETTINGS = { + imagePath: null, + imageVersion: undefined, + cardOpacity: 100, + columnOpacity: 100, + columnBorderEnabled: true, + cardGlassmorphism: true, + cardBorderEnabled: true, + cardBorderOpacity: 100, + hideScrollbar: false, +}; + export function useBoardBackgroundSettings() { // ... const getCurrentSettings = useCallback( (projectPath: string) => { const current = store.boardBackgroundByProject[projectPath]; - return ( - current || { - imagePath: null, - cardOpacity: 100, - // ... - } - ); + return current || DEFAULT_BACKGROUND_SETTINGS; }, [store.boardBackgroundByProject] ); // ... const clearBoardBackground = useCallback( async (projectPath: string) => { store.clearBoardBackground(projectPath); - await persistSettings(projectPath, { - imagePath: null, - // ... - }); + await persistSettings(projectPath, DEFAULT_BACKGROUND_SETTINGS); }, [store, persistSettings] );apps/ui/src/lib/query-client.ts (1)
131-138: Consider deduplicating error handling to avoid double-logging.The cache subscription calls
handleQueryErrorfor every query error, but individual queries using this client will also have their errors logged through the same handler if they don't provide their ownonError. This could lead to duplicate logs for some error scenarios.Additionally, queries that provide their own error handling may still trigger this global handler, potentially causing unexpected side effects like premature
handleServerOffline()calls.apps/ui/src/components/views/settings-view/hooks/use-cursor-status.ts (1)
47-49: TheuseCallbackwrapper aroundrefetchis unnecessary.
refetchfrom React Query is already a stable function reference. Wrapping it inuseCallbackadds indirection without benefit.♻️ Suggested simplification
- const loadData = useCallback(() => { - refetch(); - }, [refetch]); + const loadData = refetch;Alternatively, if you prefer keeping the same return semantics (void instead of Promise):
- const loadData = useCallback(() => { - refetch(); - }, [refetch]); + const loadData = useCallback(() => void refetch(), [refetch]);apps/ui/src/components/views/settings-view/providers/claude-settings-tab/hooks/use-subagents.ts (1)
63-68: Redundantrefetch()call afterinvalidateQueries().
invalidateQueries()already marks the query as stale and triggers a background refetch if there are active observers. Callingrefetch()immediately after creates a duplicate fetch.♻️ Suggested simplification
const refreshFilesystemAgents = useCallback(async () => { await queryClient.invalidateQueries({ queryKey: queryKeys.settings.agents(currentProject?.path ?? ''), }); - await refetch(); }, [queryClient, currentProject?.path, refetch]); + }, [queryClient, currentProject?.path]);apps/ui/src/hooks/mutations/use-ideation-mutations.ts (1)
10-10: Remove unusedtoastimport.The
toastimport is not used in this file. The comment on line 80 confirms that toast notifications are handled by the component.🧹 Suggested fix
import { getElectronAPI } from '@/lib/electron'; import { queryKeys } from '@/lib/query-keys'; -import { toast } from 'sonner'; import type { IdeaCategory, IdeaSuggestion } from '@automaker/types';apps/ui/src/hooks/queries/use-ideation.ts (1)
47-62: Consider cache key implications when query is disabled.When
projectPathis undefined, the queryKey uses an empty string (queryKeys.ideation.ideas('')). While the query is disabled, this creates a cache entry with an empty path. If multiple disabled queries exist, they share the same cache key, which could cause unexpected cache behavior when one gets enabled.A common alternative is to only compute the queryKey when enabled, or use a sentinel value like
'__disabled__'to make it explicit.💡 Alternative pattern
export function useIdeas(projectPath: string | undefined) { return useQuery({ - queryKey: queryKeys.ideation.ideas(projectPath ?? ''), + queryKey: queryKeys.ideation.ideas(projectPath!), queryFn: async () => { if (!projectPath) throw new Error('No project path'); // ... }, enabled: !!projectPath, staleTime: STALE_TIMES.FEATURES, }); }With
enabled: false, the queryFn won't run, so the!assertion is safe. However, the current approach also works correctly since the empty string key is effectively isolated by the disabled state.apps/ui/src/hooks/use-guided-prompts.ts (1)
50-53: ThehandleRefetchwrapper may be unnecessary.React Query's
refetch()already returns a Promise. The wrapper converts the return type but doesn't change behavior. If consumers don't rely on the void return type, you could exposerefetchdirectly.However, if the interface contract (
Promise<void>) is important for backward compatibility, this wrapper is appropriate.💡 Simplified alternative (if void return is not required)
- // Convert async refetch to match the expected interface - const handleRefetch = useCallback(async () => { - await refetch(); - }, [refetch]); - return { // ... - refetch: handleRefetch, + refetch, // ... };apps/ui/src/components/views/settings-view/worktrees/worktrees-section.tsx (1)
162-175: Consider migrating checkbox settings to React Query mutations for consistency.These checkbox handlers still use direct API calls with only
console.errorfor failures. While functional, this is inconsistent with the React Query migration pattern used for init scripts. Users won't know if settings fail to persist.This could be addressed in a follow-up to maintain consistency and provide better user feedback via toast notifications.
apps/ui/src/hooks/queries/use-models.ts (1)
60-94: Therefreshparameter doesn't affect cache key, leading to confusing behavior.Both
useCodexModelsanduseOpencodeModelsaccept arefreshparameter that's passed to the API but not included in the query key. This means:
useCodexModels(true)anduseCodexModels(false)share the same cache- The
refreshvalue only matters on the first call or after cache invalidationConsider one of these approaches:
Option 1: Include refresh in query key (if different cache entries are desired)
export function useCodexModels(refresh = false) { return useQuery({ - queryKey: queryKeys.models.codex(), + queryKey: [...queryKeys.models.codex(), { refresh }], queryFn: async (): Promise<CodexModel[]> => {Option 2: Remove parameter, use queryClient.invalidateQueries for refresh (recommended)
-export function useCodexModels(refresh = false) { +export function useCodexModels() { return useQuery({ queryKey: queryKeys.models.codex(), queryFn: async (): Promise<CodexModel[]> => { const api = getElectronAPI(); - const result = await api.codex.getModels(refresh); + const result = await api.codex.getModels(false);Then use
queryClient.invalidateQueries({ queryKey: queryKeys.models.codex() })when a refresh is needed.apps/ui/src/hooks/mutations/use-settings-mutations.ts (1)
69-114: Dual invocation pattern is clever but verifyprojectPathextraction inonSuccess.The flexible call pattern supporting both
useUpdateProjectSettings(projectPath)anduseUpdateProjectSettings().mutate({ projectPath, settings })is well-designed.However, on Line 102,
data.projectPathrelies on the spread from Line 99. If the API result already has aprojectPathproperty, it could shadow your added one. Consider being explicit:🔧 Suggested improvement
- return { ...result, projectPath: path }; + return { result, projectPath: path }; }, onSuccess: (data) => { - const path = data.projectPath || projectPath; + const path = data.projectPath;apps/ui/src/components/views/settings-view/providers/opencode-settings-tab.tsx (2)
65-74: Redundant refetch after invalidation.
invalidateQueriesalready triggers a refetch for active queries. The explicitawait refetchCliStatus()on Line 72 causes a duplicate fetch. Consider removing it:♻️ Suggested simplification
const handleRefreshOpencodeCli = useCallback(async () => { await Promise.all([ queryClient.invalidateQueries({ queryKey: queryKeys.cli.opencode() }), queryClient.invalidateQueries({ queryKey: queryKeys.models.opencodeProviders() }), queryClient.invalidateQueries({ queryKey: queryKeys.models.opencode() }), ]); - await refetchCliStatus(); toast.success('OpenCode CLI refreshed'); -}, [queryClient, refetchCliStatus]); +}, [queryClient]);
53-63: Type assertion onmethodmasks type safety without validating actual values.Line 58 asserts
cliStatusData.auth.method(which the API types as a genericstring) toOpencodeAuthStatus['method'], a union of five specific literals. The fallback|| 'none'only catches falsy values—if the API returns an unexpected string value, the assertion silently passes:method: (cliStatusData.auth.method as OpencodeAuthStatus['method']) || 'none',Consider validating the method against known values (
'api_key' | 'api_key_env' | 'oauth' | 'config_file') before using it, or ensure the upstream API type definition guarantees only valid values are returned.apps/ui/src/components/views/spec-view/hooks/use-spec-save.ts (1)
12-18:asynckeyword is misleading since mutation is fire-and-forget.The
saveSpecfunction is declaredasyncbutmutation.mutate()doesn't return a promise. This makesawait saveSpec()from callers return immediately without waiting for the save to complete.If callers need to await completion, use
mutateAsyncinstead:♻️ Option 1: Use mutateAsync for awaitable behavior
const saveSpec = async () => { if (!currentProject) return; - saveMutation.mutate(appSpec, { - onSuccess: () => setHasChanges(false), - }); + await saveMutation.mutateAsync(appSpec); + setHasChanges(false); };♻️ Option 2: Remove async if fire-and-forget is intended
-const saveSpec = async () => { +const saveSpec = () => { if (!currentProject) return; saveMutation.mutate(appSpec, { onSuccess: () => setHasChanges(false), }); };apps/ui/src/components/views/settings-view/providers/claude-settings-tab/hooks/use-skills-settings.ts (1)
20-48: Consider adding error handling for mutations.Both
updateEnabledandupdateSourcesonly handleonSuccess. If the mutation fails, users won't receive feedback. Consider addingonErrorcallbacks to show error toasts.Proposed improvement
updateSettingsMutation.mutate( { enableSkills: newEnabled }, { onSuccess: () => { useAppStore.setState({ enableSkills: newEnabled }); toast.success(newEnabled ? 'Skills enabled' : 'Skills disabled'); }, + onError: (error) => { + toast.error('Failed to update skills setting', { + description: error instanceof Error ? error.message : 'Unknown error', + }); + }, } );Apply the same pattern to
updateSources.apps/ui/src/hooks/queries/use-pipeline.ts (1)
24-38: Consider using a sentinel or conditional query key to avoid cache collisions.When
projectPathisundefined, the query key becomesqueryKeys.pipeline.config(''). If multiple unrelated components call this hook without a path, they would share the same cache entry (keyed by empty string), potentially leading to stale or incorrect data when a valid path is later provided.Since
enabled: !!projectPathprevents the query from executing whenprojectPathis falsy, thethrowon line 28 is effectively unreachable during normal operation—but the query key is still registered.Suggested improvement
export function usePipelineConfig(projectPath: string | undefined) { return useQuery({ - queryKey: queryKeys.pipeline.config(projectPath ?? ''), + queryKey: queryKeys.pipeline.config(projectPath!), queryFn: async (): Promise<PipelineConfig | null> => { - if (!projectPath) throw new Error('No project path'); const api = getHttpApiClient(); const result = await api.pipeline.getConfig(projectPath); if (!result.success) { throw new Error(result.error || 'Failed to fetch pipeline config'); } return result.config ?? null; }, enabled: !!projectPath, staleTime: STALE_TIMES.SETTINGS, }); }The non-null assertion (
!) is safe here becauseenabled: !!projectPathguaranteesprojectPathis truthy whenqueryFnexecutes. This also removes the redundant throw statement.apps/ui/src/hooks/queries/use-usage.ts (1)
59-77: Consider extracting shared query logic.Both
useClaudeUsageanduseCodexUsageare nearly identical. While the current approach is acceptable for clarity, you could reduce duplication with a factory function if more usage hooks are added in the future.♻️ Optional: Factory pattern for usage hooks
function createUsageHook<T>( queryKeyFn: () => readonly string[], apiFn: () => Promise<T | { error: string; message?: string }> ) { return function useUsage(enabled = true) { return useQuery({ queryKey: queryKeyFn(), queryFn: async (): Promise<T> => { const result = await apiFn(); if ('error' in result) { throw new Error(result.message || result.error); } return result; }, enabled, staleTime: STALE_TIMES.USAGE, refetchInterval: enabled ? USAGE_POLLING_INTERVAL : false, placeholderData: (previousData) => previousData, }); }; }apps/ui/src/hooks/queries/use-git.ts (1)
19-36: Verify empty-string query key behavior.The hook uses an empty string fallback for
queryKeywhenprojectPathis undefined (line 21). While the query is disabled in this case (enabled: !!projectPath && enabled), this pattern could potentially cause cache collisions if multiple disabled queries share the same['git', 'diffs', '']key.Consider returning early or using a more defensive pattern:
♻️ Alternative: Skip query when path is missing
export function useGitDiffs(projectPath: string | undefined, enabled = true) { return useQuery({ - queryKey: queryKeys.git.diffs(projectPath ?? ''), + queryKey: queryKeys.git.diffs(projectPath!), queryFn: async () => { - if (!projectPath) throw new Error('No project path'); const api = getElectronAPI(); const result = await api.git.getDiffs(projectPath); if (!result.success) { throw new Error(result.error || 'Failed to fetch diffs'); } return { files: result.files ?? [], diff: result.diff ?? '', }; }, enabled: !!projectPath && enabled, staleTime: STALE_TIMES.WORKTREES, }); }The non-null assertion is safe here because
queryFnonly runs whenenabledis true, which requiresprojectPathto be truthy.apps/ui/src/components/views/board-view/hooks/use-board-actions.ts (2)
485-491: Consider removingasynckeyword fromhandleVerifyFeature.The function no longer awaits any promises since
mutation.mutate()is fire-and-forget. Theasynckeyword is now unnecessary.♻️ Minor cleanup
const handleVerifyFeature = useCallback( - async (feature: Feature) => { + (feature: Feature) => { if (!currentProject) return; verifyFeatureMutation.mutate(feature.id); }, [currentProject, verifyFeatureMutation] );
493-503: Consider removingasynckeyword fromhandleResumeFeature.Same as above—the function no longer awaits any promises.
♻️ Minor cleanup
const handleResumeFeature = useCallback( - async (feature: Feature) => { + (feature: Feature) => { logger.info('handleResumeFeature called for feature:', feature.id); if (!currentProject) { logger.error('No current project'); return; } resumeFeatureMutation.mutate({ featureId: feature.id, useWorktrees }); }, [currentProject, resumeFeatureMutation, useWorktrees] );apps/ui/src/components/views/spec-view/hooks/use-spec-generation.ts (1)
414-441: Consider addingonSuccesscallback for resilience.The mutation relies entirely on the WebSocket event subscription (lines 216-400) for success handling. If the WebSocket connection is interrupted or the event is missed, the UI state could become stale. Consider adding an
onSuccesscallback as a fallback to ensure the UI reflects the operation result.Suggested addition
createSpecMutation.mutate( { projectOverview: projectOverview.trim(), generateFeatures, analyzeProject: analyzeProjectOnCreate, featureCount: generateFeatures ? featureCountOnCreate : undefined, }, { + onSuccess: () => { + // Events handle the detailed state updates, but this ensures + // we don't get stuck if WebSocket events are missed + logger.debug('[useSpecGeneration] Spec creation request sent successfully'); + }, onError: (error) => { // ... existing error handling }, } );apps/ui/src/hooks/queries/use-workspace.ts (1)
12-15: Consider exportingWorkspaceDirectoryinterface.The interface is duplicated in
workspace-picker-modal.tsx(lines 13-16). Consider exporting it from this file to maintain a single source of truth.Suggested change
-interface WorkspaceDirectory { +export interface WorkspaceDirectory { name: string; path: string; }Then in
workspace-picker-modal.tsx:-interface WorkspaceDirectory { - name: string; - path: string; -} +import { useWorkspaceDirectories, type WorkspaceDirectory } from '@/hooks/queries';apps/ui/src/components/views/board-view/worktree-panel/hooks/use-branches.ts (1)
30-41: Good refetch logic, butrefetch()result is ignored.When calling
refetch()for the same path, the returned promise is discarded. If consumers expectfetchBranchesto be awaitable or want to handle refetch errors, this could be a limitation.Consider whether the interface should remain fire-and-forget or if error propagation is needed.
apps/ui/src/components/codex-usage-popover.tsx (1)
228-236: Consider disabling the refresh button during fetch.The button checks
!isFetchingbefore callingrefetch(), but visually it's not disabled. Users may click multiple times expecting action. Consider addingdisabled={isFetching}for better UX.Suggested improvement
<Button variant="ghost" size="icon" - className={cn('h-6 w-6', isFetching && 'opacity-80')} + className={cn('h-6 w-6', isFetching && 'opacity-80')} + disabled={isFetching} onClick={() => !isFetching && refetch()} >apps/ui/src/components/views/settings-view/hooks/use-cursor-permissions.ts (2)
1-3: Remove unuseduseEffectimport.
useEffectis imported but not used in this file.Suggested fix
-import { useState, useCallback, useEffect } from 'react'; +import { useState, useCallback } from 'react';
29-39: Consider cleanup for the timeout.The
setTimeoutfor resettingcopiedConfigworks, but if the component unmounts before the timeout fires, it could cause a React state update warning. This is a minor edge case.Optional: Add cleanup with useEffect
useEffect(() => { if (!copiedConfig) return; const timer = setTimeout(() => setCopiedConfig(false), 2000); return () => clearTimeout(timer); }, [copiedConfig]); const copyConfig = useCallback( (profileId: 'strict' | 'development') => { copyConfigMutation.mutate(profileId, { onSuccess: () => setCopiedConfig(true), }); }, [copyConfigMutation] );apps/ui/src/components/views/running-agents-view.tsx (1)
24-41: Stop button disables for all agents when any stop is pending.When
stopFeature.mutate()is called,stopFeature.isPendingbecomestrueand disables the Stop button for all running agents (line 172), not just the one being stopped. This may confuse users who want to stop multiple agents quickly.Consider tracking pending state per feature ID:
const [pendingStops, setPendingStops] = useState<Set<string>>(new Set()); const handleStopAgent = useCallback( (featureId: string) => { setPendingStops(prev => new Set(prev).add(featureId)); stopFeature.mutate(featureId, { onSettled: () => { setPendingStops(prev => { const next = new Set(prev); next.delete(featureId); return next; }); }, }); }, [stopFeature] );Then use
disabled={pendingStops.has(agent.featureId)}on the button.apps/ui/src/components/ui/git-diff-panel.tsx (1)
380-387: Consider using a type guard for cleaner error handling.The error handling logic could be simplified:
♻️ Optional refactor
- const error = queryError - ? queryError instanceof Error - ? queryError.message - : 'Failed to load diffs' - : null; + const error = queryError + ? (queryError instanceof Error ? queryError.message : 'Failed to load diffs') + : null;Or extract a utility function if this pattern is repeated elsewhere in the codebase.
apps/ui/src/components/usage-popover.tsx (1)
27-28: Unused constantREFRESH_INTERVAL_SECONDS.This constant is defined but never used in the component. Polling is now handled by React Query's configuration in the hooks. Consider removing it to avoid confusion.
♻️ Proposed fix
-// Fixed refresh interval (45 seconds) -const REFRESH_INTERVAL_SECONDS = 45;apps/ui/src/components/views/board-view/worktree-panel/worktree-panel.tsx (1)
100-113: Consider migrating polling to React Query's refetchInterval.The manual
setIntervalfor worktree polling works, but React Query can handle this natively viarefetchInterval. This would provide automatic cleanup and consistency with the init script hook's approach.♻️ Suggested approach
When migrating
useWorktreesto React Query, configure it with:useQuery({ queryKey: queryKeys.worktrees.list(projectPath), queryFn: fetchWorktreesFromAPI, refetchInterval: 5000, });This eliminates manual interval management and ensures proper cleanup.
apps/ui/src/hooks/queries/use-running-agents.ts (1)
55-61: Consider usingselectfor derived data.The current pattern works due to React Query's deduplication, but using the
selectoption provides a cleaner derivation with proper memoization:♻️ Suggested refactor using select
export function useRunningAgentsCount() { - const query = useRunningAgents(); - return { - ...query, - data: query.data?.count ?? 0, - }; + return useQuery({ + queryKey: queryKeys.runningAgents.all(), + queryFn: async (): Promise<RunningAgentsResult> => { + const api = getElectronAPI(); + const result = await api.runningAgents.getAll(); + if (!result.success) { + throw new Error(result.error || 'Failed to fetch running agents'); + } + return { + agents: result.runningAgents ?? [], + count: result.totalCount ?? 0, + }; + }, + staleTime: STALE_TIMES.RUNNING_AGENTS, + select: (data) => data.count, + }); }Alternatively, extract the queryFn to avoid duplication.
apps/ui/src/components/claude-usage-popover.tsx (1)
167-176: Adddisabledattribute for better accessibility.The button guards clicks with
!isFetchingbut doesn't setdisabled, which can confuse assistive technologies and users.♻️ Suggested fix
<Button variant="ghost" size="icon" className={cn('h-6 w-6', isFetching && 'opacity-80')} onClick={() => !isFetching && refetch()} + disabled={isFetching} > <RefreshCw className={cn('w-3.5 h-3.5', isFetching && 'animate-spin')} /> </Button>apps/ui/src/hooks/queries/use-spec.ts (1)
77-103: Good polling pattern withenabledtoggle.The hook correctly uses
refetchInterval: enabled ? 5000 : falseto enable/disable polling based on context. The graceful fallback for missingapi.specRegenerationis defensive.Minor: Consider extracting the hardcoded
5000to a constant (e.g.,STALE_TIMES.REGENERATION_STATUS) for consistency with other hooks.apps/ui/src/hooks/queries/use-settings.ts (1)
66-76: Inconsistent error handling compared to other hooks.
useSettingsStatusreturns the raw API result without checkingresult.success, while all other hooks in this file throw on failure. This inconsistency may confuse consumers expecting uniform behavior.♻️ Suggested fix for consistency
export function useSettingsStatus() { return useQuery({ queryKey: queryKeys.settings.status(), queryFn: async () => { const api = getElectronAPI(); const result = await api.settings.getStatus(); + if (!result.success) { + throw new Error(result.error || 'Failed to fetch settings status'); + } return result; }, staleTime: STALE_TIMES.SETTINGS, }); }apps/ui/src/components/views/board-view/hooks/use-board-features.ts (1)
27-32: UnusedrefetchfromuseFeatures.The
refetchfunction is destructured and renamed toloadFeatureson line 31, but it's never used. Instead,loadFeaturesis redefined in the return object (lines 164-168) to callinvalidateQueries. While both trigger a refetch, this is confusing.♻️ Simplify by removing unused destructuring
// Use React Query for features const { data: features = [], isLoading, - refetch: loadFeatures, } = useFeatures(currentProject?.path);Or use the
refetchdirectly:return { features, isLoading, persistedCategories, - loadFeatures: () => { - queryClient.invalidateQueries({ - queryKey: queryKeys.features.all(currentProject?.path ?? ''), - }); - }, + loadFeatures: refetch, loadCategories, saveCategory, };Also applies to: 164-168
apps/ui/src/components/views/board-view/worktree-panel/hooks/use-worktree-actions.ts (1)
19-28: Consider removing redundantasynckeyword.The handler functions are declared
asyncbut don't useawait. Sincemutation.mutate()is synchronous (fire-and-forget), theasynckeyword is unnecessary and could mislead readers into thinking these operations are awaited.Suggested change
const handleSwitchBranch = useCallback( - async (worktree: WorktreeInfo, branchName: string) => { + (worktree: WorktreeInfo, branchName: string) => { if (switchBranchMutation.isPending || branchName === worktree.branch) return; switchBranchMutation.mutate({ worktreePath: worktree.path, branchName, }); }, [switchBranchMutation] );Apply the same pattern to
handlePull,handlePush, andhandleOpenInEditor.apps/ui/src/hooks/mutations/use-github-mutations.ts (2)
47-48: Remove unusedqueryClientdeclaration.
queryClientis declared but never used inuseValidateIssue. The comment at lines 91-92 explains cache invalidation happens via WebSocket events, so this declaration is dead code.Suggested fix
export function useValidateIssue(projectPath: string) { - const queryClient = useQueryClient(); - return useMutation({
136-158: Consider refactoringuseGetValidationStatusto a query hook.This hook only fetches data without side effects, making it semantically a query rather than a mutation. Using
useMutationfor read-only operations loses React Query benefits like automatic caching, stale-while-revalidate, and background refetching.Suggested refactor to useQuery
export function useValidationStatus(projectPath: string, enabled = true) { return useQuery({ queryKey: queryKeys.github.validationStatus(projectPath), queryFn: async () => { const api = getElectronAPI(); if (!api.github?.getValidationStatus) { throw new Error('Validation status API not available'); } const result = await api.github.getValidationStatus(projectPath); if (!result.success) { throw new Error(result.error || 'Failed to get validation status'); } return result.runningIssues ?? []; }, enabled, staleTime: STALE_TIMES.RUNNING_AGENTS, // Running validations change frequently }); }apps/ui/src/hooks/mutations/use-worktree-mutations.ts (5)
84-107: Consider acceptingprojectPathfor consistent cache invalidation.The generic
['worktrees']invalidation is overly broad compared to other hooks that usequeryKeys.worktrees.all(projectPath). This will invalidate worktree caches for all projects, not just the affected one.Consider either:
- Accept
projectPathas a hook parameter (likeuseCreateWorktree)- Accept
projectPathin the mutation payload alongsideworktreePathThis would enable scoped invalidation:
queryKeys.worktrees.all(projectPath).♻️ Example refactor
-export function useCommitWorktree() { +export function useCommitWorktree(projectPath: string) { const queryClient = useQueryClient(); return useMutation({ mutationFn: async ({ worktreePath, message }: { worktreePath: string; message: string }) => { const api = getElectronAPI(); const result = await api.worktree.commit(worktreePath, message); if (!result.success) { throw new Error(result.error || 'Failed to commit changes'); } return result.result; }, - onSuccess: (_, { worktreePath }) => { - // Invalidate all worktree queries since we don't know the project path - queryClient.invalidateQueries({ queryKey: ['worktrees'] }); + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: queryKeys.worktrees.all(projectPath) }); toast.success('Changes committed'); },
114-165: Same broad invalidation pattern.Same suggestion applies here — consider accepting
projectPathas a hook parameter forusePushWorktreeandusePullWorktreeto enable scoped cache invalidation.
172-221: Good conditional handling, but could leverageprojectPathfor scoped invalidation.The toast logic handles different PR creation outcomes well. The
options?.projectPathis available in the payload — consider using it (when present) for more targeted invalidation:onSuccess: (result, { options }) => { - queryClient.invalidateQueries({ queryKey: ['worktrees'] }); + if (options?.projectPath) { + queryClient.invalidateQueries({ queryKey: queryKeys.worktrees.all(options.projectPath) }); + } else { + queryClient.invalidateQueries({ queryKey: ['worktrees'] }); + } queryClient.invalidateQueries({ queryKey: ['github', 'prs'] });
275-338: Same broad invalidation pattern as other hooks.Consider the same refactor for
useSwitchBranchanduseCheckoutBranchto acceptprojectPathfor scoped invalidation.
397-420: Consider usingprojectPathfor scoped invalidation.
projectPathis available in the mutation function. Consider using it for more targeted cache invalidation, or alternatively accept it as a hook parameter for consistency with other hooks.apps/ui/src/components/views/board-view/worktree-panel/hooks/use-worktrees.ts (1)
31-36: Potential stale store data when worktrees list becomes empty.The condition
worktrees.length > 0prevents syncing an empty array to the store. While this avoids clearing during initial load (whendatais undefined), it also means if all worktrees are legitimately removed, the store retains stale data.Consider checking
dataexistence instead:Suggested improvement
// Sync worktrees to Zustand store when they change useEffect(() => { - if (worktrees.length > 0) { + if (data) { setWorktreesInStore(projectPath, worktrees); } - }, [worktrees, projectPath, setWorktreesInStore]); + }, [data, worktrees, projectPath, setWorktreesInStore]);
apps/ui/src/components/views/board-view/components/kanban-card/kanban-card.tsx
Show resolved
Hide resolved
apps/ui/src/components/views/board-view/worktree-panel/hooks/use-branches.ts
Show resolved
Hide resolved
- Add cache invalidation to useBoardPersistence after create/update/delete - Add useAutoModeQueryInvalidation to board-view for WebSocket events - Add cache invalidation to github-issues-view after converting issue to task - Add cache invalidation to analysis-view after generating features - Fix UI not updating when features are added, updated, or completed Co-Authored-By: Claude Opus 4.5 <[email protected]>
- Update query keys to include all relevant parameters (branches, agents) - Fix use-branches to pass includeRemote parameter to query key - Fix use-settings to include sources in agents query key - Update running-agents-view to use correct query key structure - Update use-spec-loading to properly use spec query hooks - Add missing queryClient invalidation in auto-mode mutations - Add missing cache invalidation in spec mutations after creation Co-Authored-By: Claude Opus 4.5 <[email protected]>
Summary
Changes Included (11 commits)
Foundation
Query Hooks
Mutation Hooks
Infrastructure
Component Migrations
Test plan
🤖 Generated with Claude Code
Summary by CodeRabbit
Release Notes
New Features
Refactor
✏️ Tip: You can customize this high-level summary in your review settings.