Skip to content

improvement(workflows): replace Zustand workflow sync with React Query as single source of truth#3860

Open
waleedlatif1 wants to merge 8 commits intostagingfrom
waleedlatif1/fix-chat-workflow-stale-name
Open

improvement(workflows): replace Zustand workflow sync with React Query as single source of truth#3860
waleedlatif1 wants to merge 8 commits intostagingfrom
waleedlatif1/fix-chat-workflow-stale-name

Conversation

@waleedlatif1
Copy link
Copy Markdown
Collaborator

Summary

  • Remove workflows record and sync effects from Zustand registry store — React Query is now the single source of truth for workflow metadata
  • Add useWorkflowMap hook with select for structural sharing (replaces consumer-side useMemo + Object.fromEntries)
  • Add useUpdateWorkflowMutation and useDeleteWorkflowMutation with optimistic updates
  • Extract workflowKeys to shared util to avoid circular imports between store and query hooks
  • Simplify hydration phases from 6 to 4 (metadata phases replaced by RQ isPending/isSuccess)
  • Delete dead optimistic-update.ts util (zero consumers)
  • ~50 consumer files migrated from Zustand selectors to RQ hooks/cache reads

Type of Change

  • Improvement (refactor)

Testing

Tested manually

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

@vercel
Copy link
Copy Markdown

vercel bot commented Mar 31, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Skipped Skipped Mar 31, 2026 4:12am

Request Review

@cursor
Copy link
Copy Markdown

cursor bot commented Mar 31, 2026

PR Summary

Medium Risk
Broad refactor across workflow, home, logs, sidebar, and copilot UI to read/update workflow metadata via React Query cache instead of a Zustand registry record, which could cause missing/incorrect metadata or stale UI if query keys/scopes are wrong. Adds new optimistic mutations for create/duplicate/update/delete/reorder that may have edge cases around rollback and cross-workspace invalidation.

Overview
React Query is now the single source of truth for workflow metadata. The Zustand workflow registry no longer stores a workflows record or runs metadata hydration phases; many UI surfaces (home resources, logs, sidebar, copilot mentions/tool display, preview rendering) now use useWorkflows/useWorkflowMap or synchronous getWorkflows() cache reads for names/colors/existence checks.

Adds shared workflowKeys utilities plus new React Query mutations (useUpdateWorkflow, useDeleteWorkflowMutation) and reworks existing create/duplicate/reorder flows to perform optimistic cache updates and invalidations instead of mutating the registry store. Sandbox exercise hydration now injects synthetic workflow metadata directly into the React Query cache, and dead optimistic-update.ts is removed.

Written by Cursor Bugbot for commit c282858. Configure here.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Mar 31, 2026

Greptile Summary

This PR replaces the Zustand workflows record (and the sync-effect bridge that kept it in step with React Query) with React Query as the single source of truth for workflow metadata. ~50 consumer files are migrated from useWorkflowRegistry(s => s.workflows) selectors to useWorkflows / useWorkflowMap / getWorkflows, while useUpdateWorkflow and useDeleteWorkflowMutation bring the remaining mutations into the RQ pattern. The dead optimistic-update.ts utility is also removed.

Key changes:

  • workflows.ts — new useWorkflowMap (structured-sharing map), useUpdateWorkflow, useDeleteWorkflowMutation, getWorkflows synchronous cache reader, and fully inlined optimistic update handlers with rollback and onSettled invalidation.
  • registry/store.ts / registry/types.ts — removes workflows state, beginMetadataLoad/completeMetadataLoad/failMetadataLoad actions, and the workspace-transition guard; switchToWorkspace becomes synchronous; hydration phases pruned from 6 to 4.
  • workflow-keys.ts / get-workspace-id-from-url.ts — new shared utils to break circular imports and provide a URL-based fallback for synchronous cache reads outside React hooks.
  • Previous review finding addresseduse-workflow-execution.ts now falls back to hydration.workspaceId for the academy/sandbox route where useParams returns no workspaceId.

Minor issues found:

  • useCreateWorkflow and useDuplicateWorkflowMutation narrow their onSettled invalidation to the active scope only, while useDeleteWorkflowMutation and useUpdateWorkflow invalidate all lists — an inconsistency that could leave archived/all views stale after a create/duplicate.
  • getSiblingItems and collectMovingItems in use-drag-drop.ts capture workspaceId in stale closures ([] deps); harmless in practice due to the URL-based fallback but technically incorrect.
  • handleDeleteSelection omits the mutation callbacks from its useCallback deps; safe because React Query mutation functions are stable references.

Confidence Score: 5/5

Safe to merge — all remaining findings are P2 style/consistency issues with no functional impact on correctness or data integrity.

The refactoring is architecturally sound: RQ cache becomes the single source of truth, optimistic updates follow the canonical pattern with proper rollback, and the hydration state machine is correctly simplified. The previously flagged sandbox/academy execution bug is fixed. No P0/P1 issues found; the three P2 notes cover a stale closure, an incomplete useCallback dep list (stable references), and a minor cache-invalidation scope inconsistency — none of which affect runtime correctness.

apps/sim/hooks/queries/workflows.ts — onSettled invalidation scope inconsistency across mutations worth aligning before adding more scope-aware consumers.

Important Files Changed

Filename Overview
apps/sim/hooks/queries/workflows.ts Core of the refactor — adds useWorkflowMap, useUpdateWorkflow, useDeleteWorkflowMutation, getWorkflows cache-read helper, and inlines all optimistic update logic; minor inconsistency in onSettled invalidation scope across mutations.
apps/sim/stores/workflows/registry/store.ts Removes workflows record and all metadata CRUD actions; switchToWorkspace becomes synchronous, loadWorkflowState reads from RQ cache instead; hydration phases simplified from 6 to 4. Clean and correct.
apps/sim/stores/workflows/registry/types.ts Removes workflows field from state and metadata-phase action signatures; trims HydrationPhase union to the 4 phases still used. Straightforward type cleanup.
apps/sim/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-workflow-execution.ts Addresses the previous review finding: falls back to hydration.workspaceId when useParams returns no workspaceId (academy/sandbox routes), then uses getWorkflows() to find the active workflow for sandbox detection and workspaceId extraction.
apps/sim/app/workspace/[workspaceId]/w/[workflowId]/workflow.tsx Replaces workflows: state.workflows selector with useWorkflowMap; removes metadata-loading/metadata-ready phase guards while keeping idle guard; correctly gates setActiveWorkflow on currentWorkflowExists derived from RQ data.
apps/sim/app/workspace/[workspaceId]/w/components/sidebar/hooks/use-drag-drop.ts Replaces Zustand registry reads with getWorkflows(workspaceId) cache reads; stale-closure issue in getSiblingItems and collectMovingItems (both have [] deps but capture workspaceId) is benign in practice due to URL fallback.
apps/sim/hooks/queries/utils/workflow-keys.ts New file extracting workflowKeys and WorkflowQueryScope to a standalone util to break circular imports between the store and query hooks.
apps/sim/hooks/queries/utils/get-workspace-id-from-url.ts New utility that extracts workspace ID from the browser URL pathname — used as a fallback for synchronous cache-read helpers that cannot call React hooks.
apps/sim/app/academy/components/sandbox-canvas-provider.tsx Migrates sandbox workflow registration from useWorkflowRegistry.setState({ workflows }) to direct RQ cache manipulation via getQueryClient().setQueryData; cleanup logic updated symmetrically.
apps/sim/lib/core/utils/optimistic-update.ts Deleted dead utility — zero remaining consumers after all optimistic logic was migrated to React Query mutation callbacks.
apps/sim/app/workspace/[workspaceId]/w/hooks/use-delete-workflow.ts Replaces removeWorkflow Zustand action with useDeleteWorkflowMutation; uses useWorkflows list instead of registry map. Correct migration; deleteWorkflowMutation missing from useCallback deps (stable reference, not a real bug).
apps/sim/hooks/queries/folders.test.ts Updated test mocks from useWorkflowRegistry.getState().workflows (object map) to mockGetWorkflows returning an array — aligns with the new RQ-based data shape.

Reviews (4): Last reviewed commit: "fix(workflows): address PR review — awai..." | Re-trigger Greptile

- Throw on state PUT failure in useCreateWorkflow instead of swallowing
- Use Map for O(1) lookups in duplicate/export loops (3 hooks)
- Broaden invalidation scope in update/delete mutations to lists()
- Switch workflow-block to useWorkflowMap for direct ID lookup
- Consolidate use-workflow-operations to single useWorkflowMap hook
- Remove workspace transition guard (sync body, unreachable timeout)
- Make switchToWorkspace synchronous (remove async/try-catch/finally)
@waleedlatif1
Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1
Copy link
Copy Markdown
Collaborator Author

@cursor review

loadWorkflowState used hydration.workspaceId (null on cold start) to
look up the RQ cache, causing "Workflow not found" even when the
workflow exists in the DB. Now falls back to getWorkspaceIdFromUrl()
and skips the cache guard when the cache is empty (letting the API
fetch proceed).

Also removes the redundant isRegistryReady guard in workflow.tsx that
blocked setActiveWorkflow when hydration.workspaceId was null.
Dashboard and EmbeddedWorkflow checked workflow list length before
the RQ query resolved, briefly showing "No workflows" or "Workflow
not found" on initial load. Now gates on isPending first.
@waleedlatif1
Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1
Copy link
Copy Markdown
Collaborator Author

@cursor review

…state PUT throw

- api-info-modal: use mutateAsync for description update so errors
  are caught by the surrounding try/catch instead of silently swallowed
- useCreateWorkflow: revert state PUT to log-only — the workflow is
  already created in the DB, throwing rolls back the optimistic entry
  and makes it appear the creation failed when it actually succeeded
@waleedlatif1
Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1
Copy link
Copy Markdown
Collaborator Author

@cursor review

Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

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