improvement(workflows): replace Zustand workflow sync with React Query as single source of truth#3859
improvement(workflows): replace Zustand workflow sync with React Query as single source of truth#3859waleedlatif1 wants to merge 42 commits intostagingfrom
Conversation
…keyboard shortcuts, audit logs
…rects to rewrites
…stash, algolia tools; isolated-vm robustness improvements, tables backend (#3271) * feat(tools): advanced fields for youtube, vercel; added cloudflare and dataverse tools (#3257) * refactor(vercel): mark optional fields as advanced mode Move optional/power-user fields behind the advanced toggle: - List Deployments: project filter, target, state - Create Deployment: project ID override, redeploy from, target - List Projects: search - Create/Update Project: framework, build/output/install commands - Env Vars: variable type - Webhooks: project IDs filter - Checks: path, details URL - Team Members: role filter - All operations: team ID scope Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * style(youtube): mark optional params as advanced mode Hide pagination, sort order, and filter fields behind the advanced toggle for a cleaner default UX across all YouTube operations. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * added advanced fields for vercel and youtube, added cloudflare and dataverse block * addded desc for dataverse * add more tools * ack comment * more * ops --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * feat(tables): added tables (#2867) * updates * required * trashy table viewer * updates * updates * filtering ui * updates * updates * updates * one input mode * format * fix lints * improved errors * updates * updates * chages * doc strings * breaking down file * update comments with ai * updates * comments * changes * revert * updates * dedupe * updates * updates * updates * refactoring * renames & refactors * refactoring * updates * undo * update db * wand * updates * fix comments * fixes * simplify comments * u[dates * renames * better comments * validation * updates * updates * updates * fix sorting * fix appearnce * updating prompt to make it user sort * rm * updates * rename * comments * clean comments * simplicifcaiton * updates * updates * refactor * reduced type confusion * undo * rename * undo changes * undo * simplify * updates * updates * revert * updates * db updates * type fix * fix * fix error handling * updates * docs * docs * updates * rename * dedupe * revert * uncook * updates * fix * fix * fix * fix * prepare merge * readd migrations * add back missed code * migrate enrichment logic to general abstraction * address bugbot concerns * adhere to size limits for tables * remove conflicting migration * add back migrations * fix tables auth * fix permissive auth * fix lint * reran migrations * migrate to use tanstack query for all server state * update table-selector * update names * added tables to permission groups, updated subblock types --------- Co-authored-by: Vikhyath Mondreti <vikhyath@simstudio.ai> Co-authored-by: waleed <walif6@gmail.com> * fix(snapshot): changed insert to upsert when concurrent identical child workflows are running (#3259) * fix(snapshot): changed insert to upsert when concurrent identical child workflows are running * fixed ci tests failing * fix(workflows): disallow duplicate workflow names at the same folder level (#3260) * feat(tools): added redis, upstash, algolia, and revenuecat (#3261) * feat(tools): added redis, upstash, algolia, and revenuecat * ack comment * feat(models): add gemini-3.1-pro-preview and update gemini-3-pro thinking levels (#3263) * fix(audit-log): lazily resolve actor name/email when missing (#3262) * fix(blocks): move type coercions from tools.config.tool to tools.config.params (#3264) * fix(blocks): move type coercions from tools.config.tool to tools.config.params Number() coercions in tools.config.tool ran at serialization time before variable resolution, destroying dynamic references like <block.result.count> by converting them to NaN/null. Moved all coercions to tools.config.params which runs at execution time after variables are resolved. Fixed in 15 blocks: exa, arxiv, sentry, incidentio, wikipedia, ahrefs, posthog, elasticsearch, dropbox, hunter, lemlist, spotify, youtube, grafana, parallel. Also added mode: 'advanced' to optional exa fields. Closes #3258 * fix(blocks): address PR review — move remaining param mutations from tool() to params() - Moved field mappings from tool() to params() in grafana, posthog, lemlist, spotify, dropbox (same dynamic reference bug) - Fixed parallel.ts excerpts/full_content boolean logic - Fixed parallel.ts search_queries empty case (must set undefined) - Fixed elasticsearch.ts timeout not included when already ends with 's' - Restored dropbox.ts tool() switch for proper default fallback * fix(blocks): restore field renames to tool() for serialization-time validation Field renames (e.g. personalApiKey→apiKey) must be in tool() because validateRequiredFieldsBeforeExecution calls selectToolId()→tool() then checks renamed field names on params. Only type coercions (Number(), boolean) stay in params() to avoid destroying dynamic variable references. * improvement(resolver): resovled empty sentinel to not pass through unexecuted valid refs to text inputs (#3266) * fix(blocks): add required constraint for serviceDeskId in JSM block (#3268) * fix(blocks): add required constraint for serviceDeskId in JSM block * fix(blocks): rename custom field values to request field values in JSM create request * fix(trigger): add isolated-vm support to trigger.dev container builds (#3269) Scheduled workflow executions running in trigger.dev containers were failing to spawn isolated-vm workers because the native module wasn't available in the container. This caused loop condition evaluation to silently fail and exit after one iteration. - Add isolated-vm to build.external and additionalPackages in trigger config - Include isolated-vm-worker.cjs via additionalFiles for child process spawning - Add fallback path resolution for worker file in trigger.dev environment * fix(tables): hide tables from sidebar and block registry (#3270) * fix(tables): hide tables from sidebar and block registry * fix(trigger): add isolated-vm support to trigger.dev container builds (#3269) Scheduled workflow executions running in trigger.dev containers were failing to spawn isolated-vm workers because the native module wasn't available in the container. This caused loop condition evaluation to silently fail and exit after one iteration. - Add isolated-vm to build.external and additionalPackages in trigger config - Include isolated-vm-worker.cjs via additionalFiles for child process spawning - Add fallback path resolution for worker file in trigger.dev environment * lint * fix(trigger): update node version to align with main app (#3272) * fix(build): fix corrupted sticky disk cache on blacksmith (#3273) --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Lakee Sivaraya <71339072+lakeesiv@users.noreply.github.com> Co-authored-by: Vikhyath Mondreti <vikhyath@simstudio.ai> Co-authored-by: Vikhyath Mondreti <vikhyathvikku@gmail.com>
… fixes, removed retired models, hex integration
…ogle tasks and bigquery integrations, workflow lock
…gespeed insights, pagerduty
…, brandfetch, google meet
… pagination, memory improvements
… selectors for 14 blocks
…ory instrumentation
…aders, webhook trigger configs (#3530)
…anvas navigation updates
… block classifications
v0.6.8: mothership tool loop
…ty hardening, connectors improvements
…y as single source of truth
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
PR SummaryMedium Risk Overview The PR introduces shared It also deletes the unused Written by Cursor Bugbot for commit 0eafb33. Configure here. |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 3 potential issues.
Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
| setExecutor, | ||
| setPendingBlocks, | ||
| setActiveBlocks, | ||
| workflows, |
There was a problem hiding this comment.
Missing routeWorkspaceId in useCallback dependency array
Medium Severity
routeWorkspaceId is used inside handleRunWorkflow (line 386 via getWorkflows(routeWorkspaceId)) but is not listed in the useCallback dependency array. The callback captures a stale closure value. If the workspace changes, getWorkflows will query the old workspace's cache, potentially failing to find the active workflow or finding stale metadata, which could prevent workflow execution or miss the isSandbox flag.
Additional Locations (1)
| workspaceId, | ||
| workflowId, | ||
| metadata: { description: description.trim() || 'New workflow' }, | ||
| }) |
There was a problem hiding this comment.
Fire-and-forget mutate loses error handling in save
Medium Severity
The handleSave callback uses updateWorkflowMutation.mutate() (fire-and-forget) instead of await updateWorkflowMutation.mutateAsync(). Since the modal closes immediately after on line 195, any error from the description update is silently swallowed and never reported to the user via setSaveError. The previous code used updateWorkflow which was awaited within the same try/catch. This is a behavioral regression.
| : useWorkflowRegistry.getState().activeWorkflowId | ||
| if (targetWorkflowId) { | ||
| const meta = useWorkflowRegistry.getState().workflows[targetWorkflowId] | ||
| const meta = getWorkflows().find((w) => w.id === targetWorkflowId) |
There was a problem hiding this comment.
getWorkflows() called without workspaceId in callback
Low Severity
getWorkflows() is called without a workspaceId argument, relying on URL parsing as a fallback. This occurs inside an SSE streaming callback where the URL may not reliably reflect the expected workspace (e.g. during navigation). This contrasts with ensureWorkflowInRegistry at line 307 which correctly passes workspaceId. An explicit workspaceId argument would be more robust.
Greptile SummaryThis PR is a significant architectural refactor that makes React Query the single source of truth for workflow metadata, replacing the Key observations:
Confidence Score: 5/5Safe to merge — all findings are P2 style/lint issues with no runtime impact given React Query's stable mutation references. The architectural direction is correct and the implementation is comprehensive. All 7 comments are P2: missing entries in
Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[Component needs workflow metadata] --> B{React context?}
B -->|Hook in component| C[useWorkflows / useWorkflowMap]
B -->|Non-React: event handler / store action| D[getWorkflows]
C --> E[(React Query Cache\nworkflowKeys.list)]
D --> E
E -->|cache miss / stale| F[fetch /api/workflows?workspaceId=...]
F --> E
G[User action: create/update/delete/duplicate] --> H[Mutation hook\nuseCreateWorkflow etc.]
H -->|onMutate| I[Optimistic update to cache]
H -->|mutationFn| J[API call]
J -->|onSuccess| K[Replace temp entry in cache]
J -->|onError| L[Rollback to snapshot]
J -->|onSettled| M[invalidateQueries]
M --> E
N[Zustand useWorkflowRegistry] -.->|activeWorkflowId\nhydration phase\ndeploymentStatuses\nclipboard| O[Active workflow state only]
E -.->|metadata only| N
|
| router, | ||
| workspaceId, | ||
| ]) | ||
| }, [activeWorkflowId, userPermissions.canEdit, isDuplicating, workflows, router, workspaceId]) |
There was a problem hiding this comment.
duplicateWorkflowMutation missing from useCallback dependency array
duplicateWorkflowMutation.mutateAsync is called inside handleDuplicateWorkflow, but duplicateWorkflowMutation is absent from the dep array. Because React Query guarantees a stable mutateAsync reference this is safe today, but the react-hooks/exhaustive-deps lint rule will flag it and a future change to the mutation object could re-introduce the problem.
| }, [activeWorkflowId, userPermissions.canEdit, isDuplicating, workflows, router, workspaceId]) | |
| }, [activeWorkflowId, userPermissions.canEdit, isDuplicating, workflows, router, workspaceId, duplicateWorkflowMutation]) |
| }, [ | ||
| workflowId, | ||
| workspaceId, | ||
| description, | ||
| workflowMetadata, | ||
| updateWorkflow, | ||
| starterBlockId, | ||
| inputFormat, | ||
| paramDescriptions, | ||
| setValue, | ||
| onOpenChange, | ||
| accessMode, | ||
| updatePublicApiMutation, | ||
| ]) |
There was a problem hiding this comment.
updatePublicApiMutation and updateWorkflowMutation missing from handleSave dependency array
Both updatePublicApiMutation.mutateAsync (line 173) and updateWorkflowMutation.mutate (line 180) are called inside handleSave, yet neither mutation object appears in the dependency array. React Query v5 stabilises mutate/mutateAsync across renders so there is no real stale-closure risk today, but this violates react-hooks/exhaustive-deps and should be kept consistent with the rest of the codebase.
| }, [ | |
| workflowId, | |
| workspaceId, | |
| description, | |
| workflowMetadata, | |
| updateWorkflow, | |
| starterBlockId, | |
| inputFormat, | |
| paramDescriptions, | |
| setValue, | |
| onOpenChange, | |
| accessMode, | |
| updatePublicApiMutation, | |
| ]) | |
| }, [ | |
| workflowId, | |
| workspaceId, | |
| description, | |
| workflowMetadata, | |
| starterBlockId, | |
| inputFormat, | |
| paramDescriptions, | |
| setValue, | |
| onOpenChange, | |
| accessMode, | |
| updatePublicApiMutation, | |
| updateWorkflowMutation, | |
| ]) |
| const { data: registryWorkflowList = [] } = useWorkflows(workspaceId) | ||
| const hydrationPhase = useWorkflowRegistry((state) => state.hydration.phase) | ||
| const isLoadingWorkflows = | ||
| hydrationPhase === 'idle' || | ||
| hydrationPhase === 'metadata-loading' || | ||
| hydrationPhase === 'state-loading' | ||
| const isLoadingWorkflows = hydrationPhase === 'idle' || hydrationPhase === 'state-loading' |
There was a problem hiding this comment.
isLoadingWorkflows still derives from hydration phase after React Query migration
The workflow list now comes from useWorkflows (React Query), but isLoadingWorkflows is still driven by hydrationPhase. hydrationPhase tracks whether the workflow state (blocks/edges) is loaded, not whether the metadata list is ready. When hydrationPhase === 'idle' (no workflow selected yet), isLoadingWorkflows is true even though React Query may already have the workflow list in cache, causing the mention picker to show a spinner with data already available.
Consider deriving the flag from the React Query query state instead:
| const { data: registryWorkflowList = [] } = useWorkflows(workspaceId) | |
| const hydrationPhase = useWorkflowRegistry((state) => state.hydration.phase) | |
| const isLoadingWorkflows = | |
| hydrationPhase === 'idle' || | |
| hydrationPhase === 'metadata-loading' || | |
| hydrationPhase === 'state-loading' | |
| const isLoadingWorkflows = hydrationPhase === 'idle' || hydrationPhase === 'state-loading' | |
| const { data: registryWorkflowList = [], isPending: isWorkflowsPending } = useWorkflows(workspaceId) | |
| const isLoadingWorkflows = isWorkflowsPending |
This keeps the loading flag accurate after the move to React Query as single source of truth.
| const result = await duplicateWorkflowMutation.mutateAsync({ | ||
| workspaceId, | ||
| sourceId: activeWorkflowId, | ||
| name: `${sourceWorkflow.name} (copy)`, |
There was a problem hiding this comment.
Inconsistent duplicate naming convention:
(copy) vs (Copy)
The panel's handleDuplicateWorkflow names the copy "${name} (copy)" (lowercase), while use-duplicate-workflow.ts (used from the sidebar context menu) names it "${name} (Copy)" (uppercase). Both code paths create duplicates but will produce differently capitalised names depending on entry point.
| name: `${sourceWorkflow.name} (copy)`, | |
| name: `${sourceWorkflow.name} (Copy)`, |


Summary
workflowsrecord and sync effects from Zustand registry store — React Query is now the single source of truth for workflow metadatauseWorkflowMaphook withselectfor structural sharing (replaces consumer-sideuseMemo+Object.fromEntries)useUpdateWorkflowMutationanduseDeleteWorkflowMutationwith optimistic updatesworkflowKeysto shared util to avoid circular imports between store and query hooksisPending/isSuccess)optimistic-update.tsutil (zero consumers)Type of Change
Testing
Tested manually
Checklist