Skip to content

improvement(forking): fork time ux#5348

Open
icecrasher321 wants to merge 2 commits into
stagingfrom
feat/improve-forking-ux
Open

improvement(forking): fork time ux#5348
icecrasher321 wants to merge 2 commits into
stagingfrom
feat/improve-forking-ux

Conversation

@icecrasher321

Copy link
Copy Markdown
Collaborator

Summary

Fork time UX improvements.

  • Support non workflow used resource copy during sync
  • Fix user id auth issue with deployed route
  • Scope workflow in workflow to workspace correctly
  • Should not carry over empty folders

Type of Change

  • Other: UX Improvement

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

vercel Bot commented Jul 2, 2026

Copy link
Copy Markdown

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

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Skipped Skipped Jul 2, 2026 2:55am

Request Review

@cursor

cursor Bot commented Jul 2, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Changes touch auth on deployed-state reads, execute authorization for nested workflows, and large fork/promote remapping/sync-gate logic; mistakes could block legitimate syncs or allow wrong-workspace execution if checks are bypassed.

Overview
Fork/sync and execution paths get stricter authorization and containment, plus a clearer promote/sync experience around what must be fixed before a sync can run.

Execution & APIs: GET /api/workflows/[id]/deployed now treats valid internal JWTs like the main workflow route—requires an acting userId, uses workspace read permission, and rejects tokens without a user (fail closed). Child workflow execution accepts optional parentWorkspaceId on the execute API; when set, the route and WorkflowBlockHandler reject runs where the child workflow’s workspace does not match the parent (403), with tests on both paths.

Workspace fork/sync: Promote is gated so sync cannot proceed while references would clear in the target. The diff annotates sourceDeleted on cleared refs; the server returns authoritative blockers when preview and sync diverge. Copy candidates gain a referenced flag—only referenced resources default to “copy”; unreferenced source resources are listed separately for opt-in copy. Mapping entries from synced workflows are all treated as required for the gate. Workflow reference remapping during fork/promote now remaps/clears selector fields only; manual manualWorkflowId(s) and legacy short-input workflowIds stay verbatim. Folder mirroring only creates folders on the ancestor chain of folders that receive copied workflows; orphan folders map to existing targets when safe. KB copies include tag definitions; table workflow-group remaps can use promote’s block-id resolver.

UI: The promote modal splits Blocking sync vs informational Will be cleared, disables Sync until blockers are resolved, handles server blockers toasts, shows archived workflow names in the confirm step, and simplifies the resource copy picker (no per-kind search).

Reviewed by Cursor Bugbot for commit 1aec450. Configure here.

@greptile-apps

greptile-apps Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR improves the fork/sync UX across four axes: (1) copying non-referenced source resources during sync so users can bring along tables, KBs, files, and tools that exist in the source but are not wired into any workflow; (2) a zero-cleared-refs gate that blocks sync when any reference would clear in a target workflow, paired with an authoritative in-transaction re-check (TOCTOU guard); (3) a fix for the /deployed internal-call auth path that now validates the acting user from the JWT instead of bypassing all auth; and (4) pruning empty folders from fork/sync copies, copying KB tag definitions, and scoping workflow-in-workflow execution to the parent workspace.

  • New zero-cleared-refs gate: collectForkSyncBlockers runs before any write, blocking sync on reference/workflow causes; the modal splits cleared-ref entries into a "Blocking sync" section (Sync stays disabled until resolved) and an informational "Will be cleared" section for dependent-cause entries.
  • Unreferenced copyable resources: listForkCopyableSourceResources + collectForkUnreferencedCopyables surface source resources unused by any synced workflow as default-unselected copy candidates, grouped under "Not used by any workflow" in the modal.
  • Auth and cross-workspace safety: /deployed now requires a userId in the internal JWT (fails closed without it); both the execute route and WorkflowBlockHandler independently block cross-workspace child-workflow execution via parentWorkspaceId.

Confidence Score: 4/5

Safe to merge; the auth fix, workspace-scoping guard, and zero-cleared-refs gate are additive correctness improvements with no destructive side effects on the happy path.

Three areas need a second look: the clearDependentsOnRemap BFS per-key visited set makes the MCP tool-selector exemption fragile for future block structures; required: true in the mapping service is only correct if forkRequiredPending treats copy selection as equivalent to mapping (not verifiable from the diff); and search removal from the copy picker is a concrete usability regression for large workspaces.

apps/sim/lib/workspaces/fork/remap/remap-references.ts (BFS visited-set scope), apps/sim/lib/workspaces/fork/mapping/mapping-service.ts (required: true behavioural change), apps/sim/app/workspace/.../fork-resource-picker.tsx (search removal)

Important Files Changed

Filename Overview
apps/sim/app/api/workflows/[id]/deployed/route.ts Fixes internal-call auth: previously bypassed all auth; now extracts userId from the internal JWT and validates workspace-read permission. Fails closed (403) when no userId is present.
apps/sim/app/api/workflows/[id]/execute/route.ts Adds cross-workspace guard via parentWorkspaceId: blocks execution when the target workflow lives in a different workspace.
apps/sim/lib/workspaces/fork/promote/promote.ts Major rework: adds zero-cleared-refs gate (in-tx TOCTOU check), moves blockMap/resolveBlockId loading earlier, and adds MCP server metadata rewrite for remapped tool-input entries.
apps/sim/lib/workspaces/fork/promote/promote-plan.ts Adds unreferenced-copyable collection: surfaces every source copyable resource without a target mapping, annotated with referenced: false.
apps/sim/lib/workspaces/fork/promote/cleared-refs.ts Adds annotateForkClearedRefSourceLiveness and collectForkSyncBlockers with a zero-cost happy-path shortcut via hasForkSyncBlockerCandidates.
apps/sim/lib/workspaces/fork/promote/sync-blockers.ts New file: pure sync-blocker taxonomy classifying cleared-ref entries into unmapped-copyable, unmapped-mcp-server, source-deleted, or workflow-missing reasons.
apps/sim/lib/workspaces/fork/remap/remap-references.ts Adds MCP server metadata rewrite for remapped tool-input entries and replaces getWorkflowSearchDependentClears with a manual BFS to allow the MCP tool-selector exemption. Per-key visited set is a subtle invariant.
apps/sim/lib/workspaces/fork/mapping/mapping-service.ts Hardcodes required: true for all mapping view entries. Aligns with zero-cleared-refs gate semantics but requires forkRequiredPending to treat copy selection as equivalent to mapping.
apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workspace-header/components/promote-workspace-modal/promote-workspace-modal.tsx Major UI rework: splits cleared refs into blockers/informational, adds Blocking sync section, separates referenced/unreferenced copy sections, and shows archived-workflow names in the confirm modal.
apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workspace-header/components/fork-resource-picker/fork-resource-picker.tsx Removes search functionality from ResourceKindRow, simplifying to a plain scrollable list. UX regression for workspaces with large resource counts per kind.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant UI as Promote Modal
    participant Diff as GET /fork/diff
    participant Promote as POST /fork/promote
    participant Gate as collectForkSyncBlockers
    participant Copy as copyPromoteUnmappedResources
    participant Write as Workflow Write Loop

    UI->>Diff: fetch diff (direction, otherWorkspaceId)
    Diff-->>UI: plan + copyableUnmapped + clearedRefs annotated
    UI->>UI: split clearedRefs into blockers + informational
    Note over UI: Sync disabled while blockers.length > 0
    UI->>Promote: POST copyResources + mappings
    Promote->>Promote: buildPromoteCopySelection
    Promote->>Gate: collectForkSyncBlockers with gateResolver
    alt blockers found
        Gate-->>Promote: ForkSyncBlocker[]
        Promote-->>UI: blocked cleared-refs
        UI->>UI: toast + await diff refetch
    else zero blockers
        Gate-->>Promote: empty
        Promote->>Copy: copy selected resources
        Copy-->>Promote: augmented resolver
        Promote->>Write: remap + write workflows
        Write-->>Promote: snapshots + blockPairs
        Promote-->>UI: promoteRunId + stats
    end
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant UI as Promote Modal
    participant Diff as GET /fork/diff
    participant Promote as POST /fork/promote
    participant Gate as collectForkSyncBlockers
    participant Copy as copyPromoteUnmappedResources
    participant Write as Workflow Write Loop

    UI->>Diff: fetch diff (direction, otherWorkspaceId)
    Diff-->>UI: plan + copyableUnmapped + clearedRefs annotated
    UI->>UI: split clearedRefs into blockers + informational
    Note over UI: Sync disabled while blockers.length > 0
    UI->>Promote: POST copyResources + mappings
    Promote->>Promote: buildPromoteCopySelection
    Promote->>Gate: collectForkSyncBlockers with gateResolver
    alt blockers found
        Gate-->>Promote: ForkSyncBlocker[]
        Promote-->>UI: blocked cleared-refs
        UI->>UI: toast + await diff refetch
    else zero blockers
        Gate-->>Promote: empty
        Promote->>Copy: copy selected resources
        Copy-->>Promote: augmented resolver
        Promote->>Write: remap + write workflows
        Write-->>Promote: snapshots + blockPairs
        Promote-->>UI: promoteRunId + stats
    end
Loading

Reviews (1): Last reviewed commit: "improvement(forking): fork time ux" | Re-trigger Greptile

Comment thread apps/sim/lib/workspaces/fork/remap/remap-references.ts
Comment thread apps/sim/lib/workspaces/fork/mapping/mapping-service.ts
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