Skip to content

refactor: consolidate mutation hooks and eliminate backend actions#445

Merged
yannickmonney merged 1 commit into
mainfrom
refactor/collection-consolidation
Feb 13, 2026
Merged

refactor: consolidate mutation hooks and eliminate backend actions#445
yannickmonney merged 1 commit into
mainfrom
refactor/collection-consolidation

Conversation

@yannickmonney

@yannickmonney yannickmonney commented Feb 13, 2026

Copy link
Copy Markdown
Contributor

Phase 1: Remove ~40 thin backend action wrapper files, replace with useConvexMutation/useConvexActionMutation hooks that call Convex mutations/actions directly from the frontend.

Phase 2: Consolidate all useConvexMutation hooks into mutations.ts files (previously split between actions.ts and mutations.ts). Now mutations.ts = all mutation hooks, actions.ts = only server actions.

Phase 3: Convert useDeleteIntegrationAction to collection-based useDeleteIntegration and remove dead useAddMember hook.

Clean up dead code: remove unused convex-server.ts (last consumer was deleted improve-message.ts action) and unused useSsoCredentials.

Summary by CodeRabbit

  • Refactor
    • Improved internal state management for asynchronous operations to provide better loading state handling and more consistent UI behavior across features.

…ppers

Phase 1: Remove ~40 thin backend action wrapper files, replace with
useConvexMutation/useConvexActionMutation hooks that call Convex
mutations/actions directly from the frontend.

Phase 2: Consolidate all useConvexMutation hooks into mutations.ts
files (previously split between actions.ts and mutations.ts). Now
mutations.ts = all mutation hooks, actions.ts = only server actions.

Phase 3: Convert useDeleteIntegrationAction to collection-based
useDeleteIntegration and remove dead useAddMember hook.

Clean up dead code: remove unused convex-server.ts (last consumer
was deleted improve-message.ts action) and unused useSsoCredentials.
@yannickmonney yannickmonney force-pushed the refactor/collection-consolidation branch from 01d5fe8 to 0fff361 Compare February 13, 2026 00:45
@coderabbitai

coderabbitai Bot commented Feb 13, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

This pull request migrates the codebase from Convex action-based hook patterns to mutation-based patterns. New React hooks useConvexMutation and useConvexActionMutation are introduced to wrap Convex operations with React Query's useMutation. Hundreds of hooks across approvals, automations, chat, conversations, custom-agents, documents, integrations, organization, settings, teams, tone-of-voice, vendors, and websites features are relocated from actions.ts to mutations.ts files. Components are updated to destructure { mutateAsync, isPending } from hook returns instead of receiving direct functions, and local loading state is replaced with the hook's isPending flag. Correspondingly, many Convex server-side actions in actions.ts files are removed, with some switching to useConvexActionMutation. The lib/convex-server.ts module containing fetchAction is entirely deleted, and documentation guidance on optimistic updates is removed.

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly Related PRs

🚥 Pre-merge checks | ✅ 3 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 6.25% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main objective of the pull request: consolidating mutation hooks and eliminating backend actions through a systematic refactoring effort.
Merge Conflict Detection ✅ Passed ✅ No merge conflicts detected when merging into main

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch refactor/collection-consolidation

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

❤️ Share

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
services/platform/app/features/settings/integrations/components/integration-manage-dialog.tsx (1)

587-608: 🧹 Nitpick | 🔵 Trivial

String(integration._id) is redundant but harmless.

integration._id is already a string-compatible Id<'integrations'>. The String() call is unnecessary since useDeleteIntegration expects { integrationId: string } and Convex Id types are string-branded. Not blocking, but could be simplified.

Suggested simplification
-      await deleteIntegration({ integrationId: String(integration._id) });
+      await deleteIntegration({ integrationId: integration._id });
🤖 Fix all issues with AI agents
In
`@services/platform/app/features/custom-agents/components/custom-agent-active-toggle.test.tsx`:
- Around line 13-17: The mocked hooks return the wrong shape: update the vi.mock
for useActivateCustomAgentVersion and useUnpublishCustomAgent so each returns an
object with { mutateAsync, isPending } rather than a bare function — e.g. have
useActivateCustomAgentVersion: () => ({ mutateAsync: mockActivateVersion,
isPending: false }) and useUnpublishCustomAgent: () => ({ mutateAsync:
mockUnpublish, isPending: false }) so the component destructuring of {
mutateAsync: activateVersion, isPending: isActivating } works and the
mockActivateVersion/mockUnpublish calls are invoked.

In `@services/platform/app/hooks/use-convex-action-mutation.ts`:
- Around line 7-14: Add a stable mutationKey to useConvexActionMutation so each
action mutation is identifiable and filterable in React Query; update the
useMutation call in function useConvexActionMutation (which currently returns
useMutation({ mutationFn: (args: FunctionArgs<Func>) =>
convexClient.action(func, args) })) to include a deterministic key derived from
the function reference (e.g., ['convexAction', func] or another consistent
representation of Func) via the mutationKey property so DevTools and isMutating
filters can target specific action mutations.

In `@services/platform/app/hooks/use-convex-mutation.ts`:
- Around line 7-14: Add a stable mutationKey to the useConvexMutation hook for
consistency with useConvexActionMutation: in the useConvexMutation function
(which calls useConvexClient and returns useMutation), pass mutationKey: [func]
into the useMutation options alongside mutationFn so the hook uses the function
reference as the cache key.

Comment on lines +13 to 17
vi.mock('../hooks/mutations', async (importOriginal) => ({
...(await importOriginal()),
useActivateCustomAgentVersion: () => mockActivateVersion,
useUnpublishCustomAgent: () => mockUnpublish,
}));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Mock shape mismatch causes pipeline failures — hooks now return { mutateAsync, isPending }, not a bare function.

The component destructures { mutateAsync: activateVersion, isPending: isActivating } from the hook, but the mocks return the raw vi.fn(). This is the root cause of the two test failures (mockActivateVersion / mockUnpublish never called).

🐛 Fix mock return shape
 vi.mock('../hooks/mutations', async (importOriginal) => ({
   ...(await importOriginal()),
-  useActivateCustomAgentVersion: () => mockActivateVersion,
-  useUnpublishCustomAgent: () => mockUnpublish,
+  useActivateCustomAgentVersion: () => ({
+    mutateAsync: mockActivateVersion,
+    isPending: false,
+  }),
+  useUnpublishCustomAgent: () => ({
+    mutateAsync: mockUnpublish,
+    isPending: false,
+  }),
 }));
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
vi.mock('../hooks/mutations', async (importOriginal) => ({
...(await importOriginal()),
useActivateCustomAgentVersion: () => mockActivateVersion,
useUnpublishCustomAgent: () => mockUnpublish,
}));
vi.mock('../hooks/mutations', async (importOriginal) => ({
...(await importOriginal()),
useActivateCustomAgentVersion: () => ({
mutateAsync: mockActivateVersion,
isPending: false,
}),
useUnpublishCustomAgent: () => ({
mutateAsync: mockUnpublish,
isPending: false,
}),
}));
🤖 Prompt for AI Agents
In
`@services/platform/app/features/custom-agents/components/custom-agent-active-toggle.test.tsx`
around lines 13 - 17, The mocked hooks return the wrong shape: update the
vi.mock for useActivateCustomAgentVersion and useUnpublishCustomAgent so each
returns an object with { mutateAsync, isPending } rather than a bare function —
e.g. have useActivateCustomAgentVersion: () => ({ mutateAsync:
mockActivateVersion, isPending: false }) and useUnpublishCustomAgent: () => ({
mutateAsync: mockUnpublish, isPending: false }) so the component destructuring
of { mutateAsync: activateVersion, isPending: isActivating } works and the
mockActivateVersion/mockUnpublish calls are invoked.

Comment on lines +7 to +14
export function useConvexActionMutation<
Func extends FunctionReference<'action'>,
>(func: Func) {
const convexClient = useConvexClient();

return useMutation({
mutationFn: (args: FunctionArgs<Func>) => convexClient.action(func, args),
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Consider adding a mutationKey for better React Query cache management.

Without a mutationKey, all mutations created by this hook share an implicit undefined key. This can hinder debugging in React Query DevTools and prevents targeted cache operations (e.g., isMutating({ mutationKey }) filters). A stable key derived from the function reference would improve observability.

💡 Suggested improvement
 export function useConvexActionMutation<
   Func extends FunctionReference<'action'>,
 >(func: Func) {
   const convexClient = useConvexClient();

   return useMutation({
+    mutationKey: [func],
     mutationFn: (args: FunctionArgs<Func>) => convexClient.action(func, args),
   });
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export function useConvexActionMutation<
Func extends FunctionReference<'action'>,
>(func: Func) {
const convexClient = useConvexClient();
return useMutation({
mutationFn: (args: FunctionArgs<Func>) => convexClient.action(func, args),
});
export function useConvexActionMutation<
Func extends FunctionReference<'action'>,
>(func: Func) {
const convexClient = useConvexClient();
return useMutation({
mutationKey: [func],
mutationFn: (args: FunctionArgs<Func>) => convexClient.action(func, args),
});
}
🤖 Prompt for AI Agents
In `@services/platform/app/hooks/use-convex-action-mutation.ts` around lines 7 -
14, Add a stable mutationKey to useConvexActionMutation so each action mutation
is identifiable and filterable in React Query; update the useMutation call in
function useConvexActionMutation (which currently returns useMutation({
mutationFn: (args: FunctionArgs<Func>) => convexClient.action(func, args) })) to
include a deterministic key derived from the function reference (e.g.,
['convexAction', func] or another consistent representation of Func) via the
mutationKey property so DevTools and isMutating filters can target specific
action mutations.

Comment on lines +7 to +14
export function useConvexMutation<Func extends FunctionReference<'mutation'>>(
func: Func,
) {
const convexClient = useConvexClient();

return useMutation({
mutationFn: (args: FunctionArgs<Func>) => convexClient.mutation(func, args),
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Same mutationKey suggestion as the action counterpart.

For consistency, consider adding mutationKey: [func] here as well — same reasoning as the useConvexActionMutation hook.

💡 Suggested improvement
 export function useConvexMutation<Func extends FunctionReference<'mutation'>>(
   func: Func,
 ) {
   const convexClient = useConvexClient();

   return useMutation({
+    mutationKey: [func],
     mutationFn: (args: FunctionArgs<Func>) => convexClient.mutation(func, args),
   });
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export function useConvexMutation<Func extends FunctionReference<'mutation'>>(
func: Func,
) {
const convexClient = useConvexClient();
return useMutation({
mutationFn: (args: FunctionArgs<Func>) => convexClient.mutation(func, args),
});
export function useConvexMutation<Func extends FunctionReference<'mutation'>>(
func: Func,
) {
const convexClient = useConvexClient();
return useMutation({
mutationKey: [func],
mutationFn: (args: FunctionArgs<Func>) => convexClient.mutation(func, args),
});
}
🤖 Prompt for AI Agents
In `@services/platform/app/hooks/use-convex-mutation.ts` around lines 7 - 14, Add
a stable mutationKey to the useConvexMutation hook for consistency with
useConvexActionMutation: in the useConvexMutation function (which calls
useConvexClient and returns useMutation), pass mutationKey: [func] into the
useMutation options alongside mutationFn so the hook uses the function reference
as the cache key.

@yannickmonney yannickmonney merged commit 1e230fe into main Feb 13, 2026
15 checks passed
@yannickmonney yannickmonney deleted the refactor/collection-consolidation branch February 13, 2026 00:54
@coderabbitai coderabbitai Bot mentioned this pull request Mar 15, 2026
3 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant