Skip to content

fix: filter WS messages by workflow tab to prevent cross-tab state leaking#10689

Closed
christian-byrne wants to merge 11 commits intomainfrom
prompt-tab-graph-state-sync-fix
Closed

fix: filter WS messages by workflow tab to prevent cross-tab state leaking#10689
christian-byrne wants to merge 11 commits intomainfrom
prompt-tab-graph-state-sync-fix

Conversation

@christian-byrne
Copy link
Copy Markdown
Contributor

@christian-byrne christian-byrne commented Mar 28, 2026

Summary

When multiple workflow tabs are open and executing simultaneously, WebSocket messages (node outputs, previews, progress indicators) from one workflow leak into the other tab, causing incorrect visual state — wrong node highlights, mismatched previews, and corrupted progress bars.

This PR adds an isJobForActiveWorkflow(promptId) helper to executionStore that checks whether a job's prompt_id maps to the currently active workflow tab (via the existing jobIdToSessionWorkflowPath map). Visual-state WS handlers are gated behind this check so they only apply to the workflow the user is looking at.

Filtered handlers (visual state only)

  • handleExecuted — node output display
  • handleExecutionCached — cached node markers
  • handleProgress — per-node step progress
  • handleProgressState — only the nodeProgressStates "current view" map (the per-job nodeProgressStatesByJob map is always updated)
  • handleProgressText — already had filtering
  • executed listener in app.ts — node output store updates
  • b_preview_with_metadata listener in app.ts — preview image display

NOT filtered (lifecycle events — manage global state)

  • execution_start — sets activeJobId, manages queue
  • execution_success / execution_error / execution_interrupted — clear active job, update queue status

Graceful degradation

isJobForActiveWorkflow returns true (permissive) when:

  • promptId is null/undefined (legacy messages without prompt_id)
  • promptId is not in the session map (jobs from before this session or another browser tab)
  • No active workflow is open

This ensures backward compatibility — legacy messages and pre-session jobs are never incorrectly suppressed.

Issues

Closes #3377
Closes #2660
Closes #1293
Closes #3856
Closes #10688

Note: #10688 is a mirror of Comfy-Org/ComfyUI#8959 created on this repo.

Note: #4221 (feature request for better workflow status display) is partially addressed by this change but is not closed — it requires additional UI work beyond filtering.

Testing

  • 54 unit tests pass in executionStore.test.ts (38 existing + 16 new)
    • isJobForActiveWorkflow — null/undefined/unknown/no-active-workflow/match/mismatch
    • Cross-tab filtering for executed, cached, progress, progress_state events
    • Multi-tab isolation: two workflows running simultaneously, tab switching
  • 3 browser tests added in browser_tests/tests/tabProgressIsolation.spec.ts
    • WS message filtering between workflow tabs
  • All 7339 existing unit tests continue to pass

┆Issue is synchronized with this Notion page by Unito

@christian-byrne christian-byrne requested a review from a team March 28, 2026 22:32
@dosubot dosubot Bot added the size:XL This PR changes 500-999 lines, ignoring generated files. label Mar 28, 2026
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Mar 28, 2026

🎭 Playwright: ✅ 1183 passed, 0 failed · 2 flaky

📊 Browser Reports
  • chromium: View Report (✅ 1169 / ❌ 0 / ⚠️ 2 / ⏭️ 1)
  • chromium-2x: View Report (✅ 2 / ❌ 0 / ⚠️ 0 / ⏭️ 0)
  • chromium-0.5x: View Report (✅ 1 / ❌ 0 / ⚠️ 0 / ⏭️ 0)
  • mobile-chrome: View Report (✅ 11 / ❌ 0 / ⚠️ 0 / ⏭️ 0)

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 28, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

WebSocket-driven execution and preview handlers are now scoped to the active workflow via an exported isJobForActiveWorkflow(promptId) check; per-job progress caches remain updated for background jobs, and node progress/preview view state is rehydrated or cleared when switching workflows.

Changes

Cohort / File(s) Summary
Execution store core
src/stores/executionStore.ts
Added exported isJobForActiveWorkflow(promptId); gated WS handlers (handleExecutionCached, handleExecuted, handleProgress) to early-return for non-active workflows; handleProgressState always updates per-job cache but only updates current view for active workflow; replaced inline session path logic with createSessionTabMap backed jobIdToSessionWorkflowPath; added watcher to rehydrate/clear nodeProgressStates on workflow switch.
App socket handlers
src/scripts/app.ts
Socket handlers for executed and b_preview_with_metadata now call useExecutionStore().isJobForActiveWorkflow(...) and return early when false to avoid cross-tab UI side effects (node outputs, callbacks, preview URL creation/revocation).
Session tab map util
src/utils/sessionTabMap.ts
New createSessionTabMap(prefix, maxEntries?) providing a session-scoped reactive Map<string,string> persisted to sessionStorage with LRU eviction, one-time migration from legacy keys, safe JSON/storage handling, and set(key,value) semantics; exports SessionTabMap interface.
Unit tests (new & updated)
src/stores/executionStore.test.ts, src/utils/sessionTabMap.test.ts
Added tests for isJobForActiveWorkflow and multi-tab WS event scenarios (isolation, caching, rehydration, tab switching) and comprehensive tests for createSessionTabMap (persistence, migration, LRU eviction, reactivity).

Sequence Diagram(s)

sequenceDiagram
    participant API as "Server (WS API)"
    participant App as "ComfyApp (socket handlers)"
    participant Exec as "executionStore"
    participant WF as "workflowStore"

    API->>App: send execution event (prompt_id / jobId)
    App->>Exec: isJobForActiveWorkflow(prompt_id)?
    Exec->>WF: read activeWorkflow?.path
    alt job matches active workflow
        Exec-->>App: true
        App->>Exec: update activeJob, node outputs, nodeProgressStates, previews
        Exec->>Exec: update nodeProgressStatesByJob (cache)
    else job not for active workflow
        Exec-->>App: false
        App->>Exec: update nodeProgressStatesByJob (cache only, no UI mutations)
    end
    note right of Exec: switching active workflow triggers\nrehydration: populate nodeProgressStates from most recent cached job or clear
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰
I hop through tabs and gather tracks,
Saving prompts in tiny stacks.
Only the active burrow wakes,
Others rest with cached-up flakes.
Nibble on—your preview bakes. 🥕✨


Caution

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

  • Ignore (reviewers only)

❌ Failed checks (1 error, 1 warning)

Check name Status Explanation Resolution
End-To-End Regression Coverage For Fixes ❌ Error PR uses bug-fix language but files changed list excludes browser_tests/, and PR description provides no concrete explanation for missing end-to-end regression tests. Add or update a Playwright regression test under browser_tests/ to cover multi-tab state isolation, or add concrete explanation in PR description for why end-to-end testing is impractical.
Docstring Coverage ⚠️ Warning Docstring coverage is 6.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main fix: filtering WebSocket messages by workflow tab to prevent cross-tab state leaking, which is the core objective of this PR.
Description check ✅ Passed The description provides a clear summary, explains the problem with cross-tab state leaking, lists filtered and non-filtered handlers, discusses graceful degradation, and references linked issues with testing details.
Linked Issues check ✅ Passed The PR successfully implements all required objectives: adds isJobForActiveWorkflow(promptId) to gate visual-state handlers, prevents cross-tab collisions via jobIdToSessionWorkflowPath mapping, preserves progress across reloads via session tracking, prevents preview loss via proper tab association, and maintains backward compatibility through permissive defaults.
Out of Scope Changes check ✅ Passed All changes are directly scoped to the PR objectives: new sessionTabMap utility for job-to-workflow tracking, executionStore filtering logic, handler gating, test coverage, and app.ts listener updates—no unrelated changes detected.
Adr Compliance For Entity/Litegraph Changes ✅ Passed PR does not modify files under src/lib/litegraph/, src/ecs/, or graph entity structures. Changes are confined to execution state management and utility functions.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch prompt-tab-graph-state-sync-fix

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

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Mar 28, 2026

🎨 Storybook: ✅ Built — View Storybook

Details

⏰ Completed at: 04/19/2026, 02:31:40 AM UTC

Links

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Mar 28, 2026

📦 Bundle: 5.15 MB gzip 🔴 +860 B

Details

Summary

  • Raw size: 23.7 MB baseline 23.7 MB — 🔴 +3.17 kB
  • Gzip: 5.15 MB baseline 5.15 MB — 🔴 +860 B
  • Brotli: 3.99 MB baseline 3.99 MB — 🔴 +714 B
  • Bundles: 251 current • 251 baseline • 115 added / 115 removed

Category Glance
Data & Services 🔴 +3.23 kB (3.02 MB) · Other 🟢 -59 B (8.57 MB) · Vendor & Third-Party ⚪ 0 B (9.83 MB) · Graph Workspace ⚪ 0 B (1.22 MB) · Panels & Settings ⚪ 0 B (482 kB) · Utilities & Hooks ⚪ 0 B (345 kB) · + 5 more

App Entry Points — 22.3 kB (baseline 22.3 kB) • ⚪ 0 B

Main entry bundles and manifests

File Before After Δ Raw Δ Gzip Δ Brotli
assets/index-CaEVU6ci.js (removed) 22.3 kB 🟢 -22.3 kB 🟢 -7.92 kB 🟢 -6.8 kB
assets/index-CTBKVq_U.js (new) 22.3 kB 🔴 +22.3 kB 🔴 +7.93 kB 🔴 +6.79 kB

Status: 1 added / 1 removed

Graph Workspace — 1.22 MB (baseline 1.22 MB) • ⚪ 0 B

Graph editor runtime, canvas, workflow orchestration

File Before After Δ Raw Δ Gzip Δ Brotli
assets/GraphView-BbcL7L5q.js (new) 1.22 MB 🔴 +1.22 MB 🔴 +260 kB 🔴 +196 kB
assets/GraphView-BVfQsLx6.js (removed) 1.22 MB 🟢 -1.22 MB 🟢 -260 kB 🟢 -196 kB

Status: 1 added / 1 removed

Views & Navigation — 76.6 kB (baseline 76.6 kB) • ⚪ 0 B

Top-level views, pages, and routed surfaces

File Before After Δ Raw Δ Gzip Δ Brotli
assets/CloudSurveyView-CYrrHZVO.js (new) 15.7 kB 🔴 +15.7 kB 🔴 +3.4 kB 🔴 +2.9 kB
assets/CloudSurveyView-dX9E39qy.js (removed) 15.7 kB 🟢 -15.7 kB 🟢 -3.4 kB 🟢 -2.91 kB
assets/CloudLoginView-65MYTAuZ.js (new) 12 kB 🔴 +12 kB 🔴 +3.35 kB 🔴 +2.97 kB
assets/CloudLoginView-gi2gHHKW.js (removed) 12 kB 🟢 -12 kB 🟢 -3.35 kB 🟢 -2.96 kB
assets/CloudSignupView-CtIRdVx7.js (new) 9.78 kB 🔴 +9.78 kB 🔴 +2.85 kB 🔴 +2.51 kB
assets/CloudSignupView-Dv1ClcUD.js (removed) 9.78 kB 🟢 -9.78 kB 🟢 -2.85 kB 🟢 -2.51 kB
assets/UserCheckView-DFbXaBH4.js (removed) 9.04 kB 🟢 -9.04 kB 🟢 -2.32 kB 🟢 -2.03 kB
assets/UserCheckView-tarV3Iwr.js (new) 9.04 kB 🔴 +9.04 kB 🔴 +2.32 kB 🔴 +2.04 kB
assets/CloudLayoutView-CFVHUxFJ.js (new) 7.54 kB 🔴 +7.54 kB 🔴 +2.36 kB 🔴 +2.07 kB
assets/CloudLayoutView-DDmgvZ0k.js (removed) 7.54 kB 🟢 -7.54 kB 🟢 -2.36 kB 🟢 -2.06 kB
assets/CloudForgotPasswordView-B_OsoDEY.js (removed) 5.94 kB 🟢 -5.94 kB 🟢 -2.09 kB 🟢 -1.85 kB
assets/CloudForgotPasswordView-BVoMWaPa.js (new) 5.94 kB 🔴 +5.94 kB 🔴 +2.1 kB 🔴 +1.85 kB
assets/CloudAuthTimeoutView-1jnp7Rv7.js (removed) 5.31 kB 🟢 -5.31 kB 🟢 -1.93 kB 🟢 -1.7 kB
assets/CloudAuthTimeoutView-DMcdttMM.js (new) 5.31 kB 🔴 +5.31 kB 🔴 +1.93 kB 🔴 +1.69 kB
assets/CloudSubscriptionRedirectView-Blo1WRQe.js (new) 5.08 kB 🔴 +5.08 kB 🔴 +1.91 kB 🔴 +1.69 kB
assets/CloudSubscriptionRedirectView-QARiF1F2.js (removed) 5.08 kB 🟢 -5.08 kB 🟢 -1.91 kB 🟢 -1.69 kB
assets/UserSelectView-0xSgHIHH.js (new) 4.71 kB 🔴 +4.71 kB 🔴 +1.74 kB 🔴 +1.54 kB
assets/UserSelectView-Bb66QMHN.js (removed) 4.71 kB 🟢 -4.71 kB 🟢 -1.74 kB 🟢 -1.54 kB

Status: 9 added / 9 removed / 2 unchanged

Panels & Settings — 482 kB (baseline 482 kB) • ⚪ 0 B

Configuration panels, inspectors, and settings screens

File Before After Δ Raw Δ Gzip Δ Brotli
assets/KeybindingPanel-Cfup6Lqc.js (removed) 46.5 kB 🟢 -46.5 kB 🟢 -9.54 kB 🟢 -8.47 kB
assets/KeybindingPanel-Z0fr3I3P.js (new) 46.5 kB 🔴 +46.5 kB 🔴 +9.54 kB 🔴 +8.49 kB
assets/SecretsPanel-DA8waEtj.js (removed) 22.4 kB 🟢 -22.4 kB 🟢 -5.42 kB 🟢 -4.76 kB
assets/SecretsPanel-DSlpAPeP.js (new) 22.4 kB 🔴 +22.4 kB 🔴 +5.42 kB 🔴 +4.76 kB
assets/LegacyCreditsPanel-BiP4HFUA.js (removed) 21.5 kB 🟢 -21.5 kB 🟢 -5.81 kB 🟢 -5.12 kB
assets/LegacyCreditsPanel-CnOeQa9o.js (new) 21.5 kB 🔴 +21.5 kB 🔴 +5.82 kB 🔴 +5.14 kB
assets/SubscriptionPanel-BoWQT_Hs.js (removed) 19.7 kB 🟢 -19.7 kB 🟢 -5.01 kB 🟢 -4.42 kB
assets/SubscriptionPanel-Bp4Aoj4G.js (new) 19.7 kB 🔴 +19.7 kB 🔴 +5.01 kB 🔴 +4.41 kB
assets/AboutPanel-3YL3Jh7o.js (new) 12 kB 🔴 +12 kB 🔴 +3.33 kB 🔴 +2.99 kB
assets/AboutPanel-DSaqWqTQ.js (removed) 12 kB 🟢 -12 kB 🟢 -3.33 kB 🟢 -2.98 kB
assets/ExtensionPanel-Bm-8RW-c.js (new) 9.78 kB 🔴 +9.78 kB 🔴 +2.83 kB 🔴 +2.51 kB
assets/ExtensionPanel-D2bHzNEt.js (removed) 9.78 kB 🟢 -9.78 kB 🟢 -2.83 kB 🟢 -2.51 kB
assets/ServerConfigPanel-BmC2KswO.js (new) 6.85 kB 🔴 +6.85 kB 🔴 +2.27 kB 🔴 +2.03 kB
assets/ServerConfigPanel-CaP0Yz-i.js (removed) 6.85 kB 🟢 -6.85 kB 🟢 -2.27 kB 🟢 -2.04 kB
assets/UserPanel-C9Q5Tg3a.js (new) 6.56 kB 🔴 +6.56 kB 🔴 +2.15 kB 🔴 +1.9 kB
assets/UserPanel-DYUYWDnI.js (removed) 6.56 kB 🟢 -6.56 kB 🟢 -2.15 kB 🟢 -1.88 kB
assets/cloudRemoteConfig-D4yUQNag.js (new) 1.85 kB 🔴 +1.85 kB 🔴 +906 B 🔴 +810 B
assets/cloudRemoteConfig-Drs2b43U.js (removed) 1.85 kB 🟢 -1.85 kB 🟢 -904 B 🟢 -776 B
assets/refreshRemoteConfig-D6KkNst8.js (new) 1.45 kB 🔴 +1.45 kB 🔴 +649 B 🔴 +553 B
assets/refreshRemoteConfig-tModM0dy.js (removed) 1.45 kB 🟢 -1.45 kB 🟢 -649 B 🟢 -548 B

Status: 10 added / 10 removed / 11 unchanged

User & Accounts — 17.1 kB (baseline 17.1 kB) • ⚪ 0 B

Authentication, profile, and account management bundles

File Before After Δ Raw Δ Gzip Δ Brotli
assets/auth-BkoWqNnj.js (new) 3.57 kB 🔴 +3.57 kB 🔴 +1.26 kB 🔴 +1.07 kB
assets/auth-ISF2t3j1.js (removed) 3.57 kB 🟢 -3.57 kB 🟢 -1.26 kB 🟢 -1.07 kB
assets/SignUpForm-73QwVIiO.js (removed) 3.16 kB 🟢 -3.16 kB 🟢 -1.29 kB 🟢 -1.14 kB
assets/SignUpForm-CZ33rlwQ.js (new) 3.16 kB 🔴 +3.16 kB 🔴 +1.29 kB 🔴 +1.14 kB
assets/UpdatePasswordContent-CTm2J-x-.js (removed) 2.7 kB 🟢 -2.7 kB 🟢 -1.22 kB 🟢 -1.07 kB
assets/UpdatePasswordContent-DVgjWlNy.js (new) 2.7 kB 🔴 +2.7 kB 🔴 +1.22 kB 🔴 +1.07 kB
assets/authStore-CcL8SQbA.js (new) 989 B 🔴 +989 B 🔴 +484 B 🔴 +438 B
assets/authStore-DlzMpt6o.js (removed) 989 B 🟢 -989 B 🟢 -486 B 🟢 -437 B
assets/auth-BJgpYOvT.js (removed) 348 B 🟢 -348 B 🟢 -218 B 🟢 -189 B
assets/auth-CwySNdwl.js (new) 348 B 🔴 +348 B 🔴 +217 B 🔴 +192 B

Status: 5 added / 5 removed / 2 unchanged

Editors & Dialogs — 110 kB (baseline 110 kB) • ⚪ 0 B

Modals, dialogs, drawers, and in-app editors

File Before After Δ Raw Δ Gzip Δ Brotli
assets/useShareDialog-BPGAakeT.js (new) 109 kB 🔴 +109 kB 🔴 +22.5 kB 🔴 +18.9 kB
assets/useShareDialog-Bsr02G9D.js (removed) 109 kB 🟢 -109 kB 🟢 -22.5 kB 🟢 -18.9 kB
assets/useSubscriptionDialog-Bh2jb_hM.js (new) 969 B 🔴 +969 B 🔴 +477 B 🔴 +420 B
assets/useSubscriptionDialog-jKCwGYfS.js (removed) 969 B 🟢 -969 B 🟢 -476 B 🟢 -419 B

Status: 2 added / 2 removed

UI Components — 60.3 kB (baseline 60.3 kB) • ⚪ 0 B

Reusable component library chunks

File Before After Δ Raw Δ Gzip Δ Brotli
assets/ComfyQueueButton-D_vSsYp1.js (removed) 13.5 kB 🟢 -13.5 kB 🟢 -3.79 kB 🟢 -3.38 kB
assets/ComfyQueueButton-x6rvWYOm.js (new) 13.5 kB 🔴 +13.5 kB 🔴 +3.79 kB 🔴 +3.38 kB
assets/useTerminalTabs-DF-IdI0-.js (removed) 10.7 kB 🟢 -10.7 kB 🟢 -3.6 kB 🟢 -3.16 kB
assets/useTerminalTabs-Vv_jWmGv.js (new) 10.7 kB 🔴 +10.7 kB 🔴 +3.6 kB 🔴 +3.17 kB
assets/SubscribeButton-CIaOzdyH.js (removed) 2.42 kB 🟢 -2.42 kB 🟢 -1.05 kB 🟢 -926 B
assets/SubscribeButton-Xh8GYE6y.js (new) 2.42 kB 🔴 +2.42 kB 🔴 +1.05 kB 🔴 +939 B
assets/cloudFeedbackTopbarButton-8beEIXz6.js (new) 1.64 kB 🔴 +1.64 kB 🔴 +859 B 🔴 +787 B
assets/cloudFeedbackTopbarButton-D4-6ErpP.js (removed) 1.64 kB 🟢 -1.64 kB 🟢 -858 B 🟢 -762 B
assets/ComfyQueueButton-DvXOWOHh.js (new) 1.03 kB 🔴 +1.03 kB 🔴 +491 B 🔴 +440 B
assets/ComfyQueueButton-DwJd5GQy.js (removed) 1.03 kB 🟢 -1.03 kB 🟢 -491 B 🟢 -443 B

Status: 5 added / 5 removed / 8 unchanged

Data & Services — 3.02 MB (baseline 3.01 MB) • 🔴 +3.23 kB

Stores, services, APIs, and repositories

File Before After Δ Raw Δ Gzip Δ Brotli
assets/dialogService-CFAdBsKg.js (new) 1.97 MB 🔴 +1.97 MB 🔴 +453 kB 🔴 +343 kB
assets/dialogService-DB1zI-ik.js (removed) 1.97 MB 🟢 -1.97 MB 🟢 -452 kB 🟢 -343 kB
assets/api-B6h7j2Ac.js (removed) 893 kB 🟢 -893 kB 🟢 -214 kB 🟢 -168 kB
assets/api-DswsGXoB.js (new) 893 kB 🔴 +893 kB 🔴 +214 kB 🔴 +168 kB
assets/load3dService-2utg3okT.js (removed) 96.5 kB 🟢 -96.5 kB 🟢 -20.6 kB 🟢 -17.7 kB
assets/load3dService-BAr9XZ5k.js (new) 96.5 kB 🔴 +96.5 kB 🔴 +20.6 kB 🔴 +17.7 kB
assets/workflowShareService-Bbs69pft.js (removed) 16.6 kB 🟢 -16.6 kB 🟢 -4.89 kB 🟢 -4.33 kB
assets/workflowShareService-D4rk1HcP.js (new) 16.6 kB 🔴 +16.6 kB 🔴 +4.89 kB 🔴 +4.33 kB
assets/keybindingService-BJ-puvHA.js (removed) 13.8 kB 🟢 -13.8 kB 🟢 -3.67 kB 🟢 -3.22 kB
assets/keybindingService-CqhphojO.js (new) 13.8 kB 🔴 +13.8 kB 🔴 +3.67 kB 🔴 +3.21 kB
assets/releaseStore-C1tfo02t.js (removed) 8.12 kB 🟢 -8.12 kB 🟢 -2.28 kB 🟢 -2 kB
assets/releaseStore-DrfzHHqW.js (new) 8.12 kB 🔴 +8.12 kB 🔴 +2.28 kB 🔴 +2 kB
assets/userStore-CPXnzo6k.js (new) 2.24 kB 🔴 +2.24 kB 🔴 +869 B 🔴 +763 B
assets/userStore-DIULP-jP.js (removed) 2.24 kB 🟢 -2.24 kB 🟢 -871 B 🟢 -761 B
assets/audioService-CEaMy2Jm.js (new) 1.8 kB 🔴 +1.8 kB 🔴 +879 B 🔴 +756 B
assets/audioService-DAgtWFdU.js (removed) 1.8 kB 🟢 -1.8 kB 🟢 -878 B 🟢 -761 B
assets/releaseStore-Bby9cmVe.js (new) 993 B 🔴 +993 B 🔴 +483 B 🔴 +428 B
assets/releaseStore-D85VZC8y.js (removed) 993 B 🟢 -993 B 🟢 -481 B 🟢 -427 B
assets/workflowDraftStore-B7IK_wc5.js (removed) 969 B 🟢 -969 B 🟢 -476 B 🟢 -424 B
assets/workflowDraftStore-PxPztrxi.js (new) 969 B 🔴 +969 B 🔴 +476 B 🔴 +426 B
assets/dialogService-CHHNsZvS.js (new) 958 B 🔴 +958 B 🔴 +469 B 🔴 +417 B
assets/dialogService-CkKMuWn9.js (removed) 958 B 🟢 -958 B 🟢 -469 B 🟢 -415 B
assets/settingStore-BV6BqrQx.js (new) 956 B 🔴 +956 B 🔴 +471 B 🔴 +422 B
assets/settingStore-C6tKey0b.js (removed) 956 B 🟢 -956 B 🟢 -472 B 🟢 -419 B
assets/assetsStore-DPgU4nla.js (new) 955 B 🔴 +955 B 🔴 +471 B 🔴 +417 B
assets/assetsStore-DxfH1u4N.js (removed) 955 B 🟢 -955 B 🟢 -472 B 🟢 -416 B

Status: 13 added / 13 removed / 4 unchanged

Utilities & Hooks — 345 kB (baseline 345 kB) • ⚪ 0 B

Helpers, composables, and utility bundles

File Before After Δ Raw Δ Gzip Δ Brotli
assets/useConflictDetection-DDQQicsc.js (removed) 232 kB 🟢 -232 kB 🟢 -51.3 kB 🟢 -41.8 kB
assets/useConflictDetection-Mlo_zPwG.js (new) 232 kB 🔴 +232 kB 🔴 +51.3 kB 🔴 +41.7 kB
assets/useLoad3d-CSIhiSHd.js (removed) 20.2 kB 🟢 -20.2 kB 🟢 -4.7 kB 🟢 -4.16 kB
assets/useLoad3d-Dpuf6Q2F.js (new) 20.2 kB 🔴 +20.2 kB 🔴 +4.7 kB 🔴 +4.17 kB
assets/useLoad3dViewer-CfWPj5qJ.js (new) 18.9 kB 🔴 +18.9 kB 🔴 +4.46 kB 🔴 +3.93 kB
assets/useLoad3dViewer-D334JfcI.js (removed) 18.9 kB 🟢 -18.9 kB 🟢 -4.46 kB 🟢 -3.9 kB
assets/useFeatureFlags-CS2sa_EK.js (new) 5.95 kB 🔴 +5.95 kB 🔴 +1.79 kB 🔴 +1.52 kB
assets/useFeatureFlags-tHVXs3eD.js (removed) 5.95 kB 🟢 -5.95 kB 🟢 -1.8 kB 🟢 -1.53 kB
assets/useCopyToClipboard-B6dUUbiN.js (removed) 5.29 kB 🟢 -5.29 kB 🟢 -1.86 kB 🟢 -1.57 kB
assets/useCopyToClipboard-BQcV52aO.js (new) 5.29 kB 🔴 +5.29 kB 🔴 +1.86 kB 🔴 +1.57 kB
assets/useWorkspaceUI-BwYDM5qP.js (removed) 3.34 kB 🟢 -3.34 kB 🟢 -981 B 🟢 -811 B
assets/useWorkspaceUI-toTui2aN.js (new) 3.34 kB 🔴 +3.34 kB 🔴 +981 B 🔴 +807 B
assets/subscriptionCheckoutUtil-BP5esdVs.js (removed) 3.31 kB 🟢 -3.31 kB 🟢 -1.36 kB 🟢 -1.19 kB
assets/subscriptionCheckoutUtil-IxXhZJoB.js (new) 3.31 kB 🔴 +3.31 kB 🔴 +1.36 kB 🔴 +1.18 kB
assets/assetPreviewUtil-Cbby9_rI.js (new) 2.27 kB 🔴 +2.27 kB 🔴 +957 B 🔴 +837 B
assets/assetPreviewUtil-DwvVU-eg.js (removed) 2.27 kB 🟢 -2.27 kB 🟢 -959 B 🟢 -829 B
assets/useUpstreamValue-C_e-dYCq.js (new) 2.08 kB 🔴 +2.08 kB 🔴 +804 B 🔴 +712 B
assets/useUpstreamValue-CFR6U4SJ.js (removed) 2.08 kB 🟢 -2.08 kB 🟢 -806 B 🟢 -712 B
assets/useLoad3d-C-iy0mKo.js (new) 1.13 kB 🔴 +1.13 kB 🔴 +539 B 🔴 +482 B
assets/useLoad3d-DYh9_X3H.js (removed) 1.13 kB 🟢 -1.13 kB 🟢 -540 B 🟢 -485 B
assets/useLoad3dViewer-5k4oX57O.js (new) 1.07 kB 🔴 +1.07 kB 🔴 +507 B 🔴 +457 B
assets/useLoad3dViewer-CrTJpijz.js (removed) 1.07 kB 🟢 -1.07 kB 🟢 -507 B 🟢 -457 B
assets/useCurrentUser-CbP30Md7.js (new) 955 B 🔴 +955 B 🔴 +471 B 🔴 +420 B
assets/useCurrentUser-DoJ-GcEc.js (removed) 955 B 🟢 -955 B 🟢 -471 B 🟢 -418 B
assets/useWorkspaceSwitch-D0u_vf7C.js (new) 747 B 🔴 +747 B 🔴 +384 B 🔴 +332 B
assets/useWorkspaceSwitch-zSlm3Arq.js (removed) 747 B 🟢 -747 B 🟢 -380 B 🟢 -331 B

Status: 13 added / 13 removed / 14 unchanged

Vendor & Third-Party — 9.83 MB (baseline 9.83 MB) • ⚪ 0 B

External libraries and shared vendor chunks

Status: 16 unchanged

Other — 8.57 MB (baseline 8.57 MB) • 🟢 -59 B

Bundles that do not match a named category

File Before After Δ Raw Δ Gzip Δ Brotli
assets/core-DVcmmMbU.js (removed) 76.9 kB 🟢 -76.9 kB 🟢 -19.9 kB 🟢 -17 kB
assets/core-uX0HhjY3.js (new) 76.9 kB 🔴 +76.9 kB 🔴 +19.9 kB 🔴 +17 kB
assets/groupNode-2RkWYdTN.js (removed) 74 kB 🟢 -74 kB 🟢 -18.5 kB 🟢 -16.3 kB
assets/groupNode-CrK_BI4a.js (new) 74 kB 🔴 +74 kB 🔴 +18.5 kB 🔴 +16.3 kB
assets/WidgetSelect-DQsT0a4q.js (new) 64.5 kB 🔴 +64.5 kB 🔴 +13.9 kB 🔴 +12.1 kB
assets/WidgetSelect-n9W5kLe6.js (removed) 64.5 kB 🟢 -64.5 kB 🟢 -13.9 kB 🟢 -12.1 kB
assets/SubscriptionRequiredDialogContentWorkspace-BYAtUQvF.js (new) 49.1 kB 🔴 +49.1 kB 🔴 +9.36 kB 🔴 +7.99 kB
assets/SubscriptionRequiredDialogContentWorkspace-BzvLC2kl.js (removed) 49.1 kB 🟢 -49.1 kB 🟢 -9.35 kB 🟢 -8.02 kB
assets/Load3DControls-C6poCYaJ.js (removed) 40.2 kB 🟢 -40.2 kB 🟢 -6.88 kB 🟢 -6.03 kB
assets/Load3DControls-Dlwo67Xl.js (new) 40.2 kB 🔴 +40.2 kB 🔴 +6.88 kB 🔴 +6.02 kB
assets/WidgetPainter-CrOxbPE9.js (new) 33.6 kB 🔴 +33.6 kB 🔴 +8.2 kB 🔴 +7.29 kB
assets/WidgetPainter-P4gGJdsK.js (removed) 33.6 kB 🟢 -33.6 kB 🟢 -8.2 kB 🟢 -7.29 kB
assets/WorkspacePanelContent-CJNXfRrI.js (new) 32.6 kB 🔴 +32.6 kB 🔴 +6.93 kB 🔴 +6.13 kB
assets/WorkspacePanelContent-DYUVdruH.js (removed) 32.6 kB 🟢 -32.6 kB 🟢 -6.93 kB 🟢 -6.12 kB
assets/SubscriptionRequiredDialogContent-BwmKdGVn.js (new) 27.3 kB 🔴 +27.3 kB 🔴 +6.89 kB 🔴 +6.08 kB
assets/SubscriptionRequiredDialogContent-DBL0ivXt.js (removed) 27.3 kB 🟢 -27.3 kB 🟢 -6.88 kB 🟢 -6.08 kB
assets/Load3dViewerContent-DAHQoAlK.js (removed) 24.5 kB 🟢 -24.5 kB 🟢 -5.35 kB 🟢 -4.65 kB
assets/Load3dViewerContent-q2mYUlng.js (new) 24.5 kB 🔴 +24.5 kB 🔴 +5.36 kB 🔴 +4.65 kB
assets/WidgetImageCrop-BBBbfTce.js (new) 24.2 kB 🔴 +24.2 kB 🔴 +6.14 kB 🔴 +5.4 kB
assets/WidgetImageCrop-zPPjL-XP.js (removed) 24.2 kB 🟢 -24.2 kB 🟢 -6.14 kB 🟢 -5.4 kB
assets/SubscriptionPanelContentWorkspace-DD9LT4KX.js (new) 22.2 kB 🔴 +22.2 kB 🔴 +5.18 kB 🔴 +4.56 kB
assets/SubscriptionPanelContentWorkspace-w-LLngeb.js (removed) 22.2 kB 🟢 -22.2 kB 🟢 -5.18 kB 🟢 -4.57 kB
assets/SignInContent-31PVLYab.js (new) 20.4 kB 🔴 +20.4 kB 🔴 +5.29 kB 🔴 +4.64 kB
assets/SignInContent-C9WQUIQm.js (removed) 20.4 kB 🟢 -20.4 kB 🟢 -5.29 kB 🟢 -4.63 kB
assets/CurrentUserPopoverWorkspace-D7lihJMq.js (new) 20.4 kB 🔴 +20.4 kB 🔴 +4.84 kB 🔴 +4.34 kB
assets/CurrentUserPopoverWorkspace-v95CBef3.js (removed) 20.4 kB 🟢 -20.4 kB 🟢 -4.84 kB 🟢 -4.34 kB
assets/WidgetInputNumber-DwlwBIFK.js (removed) 19.1 kB 🟢 -19.1 kB 🟢 -4.84 kB 🟢 -4.3 kB
assets/WidgetInputNumber-rNWIP17n.js (new) 19.1 kB 🔴 +19.1 kB 🔴 +4.84 kB 🔴 +4.3 kB
assets/WidgetRecordAudio-DMtZTIBT.js (new) 17.2 kB 🔴 +17.2 kB 🔴 +4.93 kB 🔴 +4.41 kB
assets/WidgetRecordAudio-timDjPir.js (removed) 17.2 kB 🟢 -17.2 kB 🟢 -4.92 kB 🟢 -4.41 kB
assets/Load3D-CnMAxt27.js (new) 17 kB 🔴 +17 kB 🔴 +4.14 kB 🔴 +3.62 kB
assets/Load3D-CX6Adzm_.js (removed) 17 kB 🟢 -17 kB 🟢 -4.13 kB 🟢 -3.62 kB
assets/WidgetRange-BOMqAO8G.js (removed) 16.9 kB 🟢 -16.9 kB 🟢 -4.53 kB 🟢 -4.07 kB
assets/WidgetRange-DDtPk_lX.js (new) 16.9 kB 🔴 +16.9 kB 🔴 +4.53 kB 🔴 +4.06 kB
assets/load3d-39Lu-0ad.js (new) 15 kB 🔴 +15 kB 🔴 +4.32 kB 🔴 +3.74 kB
assets/load3d-DeiFHaqJ.js (removed) 15 kB 🟢 -15 kB 🟢 -4.32 kB 🟢 -3.74 kB
assets/WaveAudioPlayer-DD6rfxdz.js (removed) 13.4 kB 🟢 -13.4 kB 🟢 -3.69 kB 🟢 -3.23 kB
assets/WaveAudioPlayer-DQJ4p812.js (new) 13.4 kB 🔴 +13.4 kB 🔴 +3.69 kB 🔴 +3.23 kB
assets/WidgetCurve-B_SdrmmO.js (new) 12 kB 🔴 +12 kB 🔴 +3.87 kB 🔴 +3.51 kB
assets/WidgetCurve-CkWtcQbp.js (removed) 12 kB 🟢 -12 kB 🟢 -3.87 kB 🟢 -3.52 kB
assets/TeamWorkspacesDialogContent-Bch1E3ju.js (new) 11.1 kB 🔴 +11.1 kB 🔴 +3.34 kB 🔴 +2.96 kB
assets/TeamWorkspacesDialogContent-DT8QKGTf.js (removed) 11.1 kB 🟢 -11.1 kB 🟢 -3.34 kB 🟢 -2.97 kB
assets/nodeTemplates-BxKTUY7S.js (removed) 9.58 kB 🟢 -9.58 kB 🟢 -3.38 kB 🟢 -2.98 kB
assets/nodeTemplates-lAHmPlOr.js (new) 9.58 kB 🔴 +9.58 kB 🔴 +3.37 kB 🔴 +2.97 kB
assets/InviteMemberDialogContent-BrzaYR-E.js (removed) 7.77 kB 🟢 -7.77 kB 🟢 -2.45 kB 🟢 -2.16 kB
assets/InviteMemberDialogContent-Do6bn-x9.js (new) 7.77 kB 🔴 +7.77 kB 🔴 +2.45 kB 🔴 +2.14 kB
assets/Load3DConfiguration-8OfAb5mH.js (new) 7.22 kB 🔴 +7.22 kB 🔴 +2.2 kB 🔴 +1.92 kB
assets/Load3DConfiguration-DypOPjfL.js (removed) 7.22 kB 🟢 -7.22 kB 🟢 -2.2 kB 🟢 -1.93 kB
assets/onboardingCloudRoutes-baMVVq4j.js (removed) 6.53 kB 🟢 -6.53 kB 🟢 -2.03 kB 🟢 -1.76 kB
assets/onboardingCloudRoutes-fapwkNbj.js (new) 6.53 kB 🔴 +6.53 kB 🔴 +2.03 kB 🔴 +1.76 kB
assets/WidgetWithControl--J4C58gj.js (removed) 5.99 kB 🟢 -5.99 kB 🟢 -2.38 kB 🟢 -2.12 kB
assets/WidgetWithControl-Br5IyYc9.js (new) 5.99 kB 🔴 +5.99 kB 🔴 +2.39 kB 🔴 +2.14 kB
assets/CreateWorkspaceDialogContent-C1ViSakB.js (new) 5.95 kB 🔴 +5.95 kB 🔴 +2.15 kB 🔴 +1.87 kB
assets/CreateWorkspaceDialogContent-CuMi47Qg.js (removed) 5.95 kB 🟢 -5.95 kB 🟢 -2.15 kB 🟢 -1.88 kB
assets/FreeTierDialogContent-BJIEl6H-.js (removed) 5.82 kB 🟢 -5.82 kB 🟢 -2.04 kB 🟢 -1.81 kB
assets/FreeTierDialogContent-CeLjryij.js (new) 5.82 kB 🔴 +5.82 kB 🔴 +2.04 kB 🔴 +1.8 kB
assets/EditWorkspaceDialogContent-Q8xvRCk5.js (removed) 5.75 kB 🟢 -5.75 kB 🟢 -2.11 kB 🟢 -1.85 kB
assets/EditWorkspaceDialogContent-r9nRaaWS.js (new) 5.75 kB 🔴 +5.75 kB 🔴 +2.11 kB 🔴 +1.85 kB
assets/WidgetTextarea-Di-SAs-G.js (removed) 5.59 kB 🟢 -5.59 kB 🟢 -2.19 kB 🟢 -1.93 kB
assets/WidgetTextarea-Cg5ucer0.js (new) 5.53 kB 🔴 +5.53 kB 🔴 +2.17 kB 🔴 +1.93 kB
assets/Preview3d-CiJrk2qj.js (new) 5.39 kB 🔴 +5.39 kB 🔴 +1.8 kB 🔴 +1.57 kB
assets/Preview3d-DDjuAWEo.js (removed) 5.39 kB 🟢 -5.39 kB 🟢 -1.8 kB 🟢 -1.57 kB
assets/ValueControlPopover-ChnTd2sU.js (removed) 5.33 kB 🟢 -5.33 kB 🟢 -1.93 kB 🟢 -1.72 kB
assets/ValueControlPopover-v8AKjdzC.js (new) 5.33 kB 🔴 +5.33 kB 🔴 +1.93 kB 🔴 +1.72 kB
assets/CancelSubscriptionDialogContent--ugSbvGV.js (new) 5.22 kB 🔴 +5.22 kB 🔴 +1.95 kB 🔴 +1.71 kB
assets/CancelSubscriptionDialogContent-BF5LjoXq.js (removed) 5.22 kB 🟢 -5.22 kB 🟢 -1.95 kB 🟢 -1.71 kB
assets/DeleteWorkspaceDialogContent-Bzj9Ogb6.js (new) 4.65 kB 🔴 +4.65 kB 🔴 +1.79 kB 🔴 +1.56 kB
assets/DeleteWorkspaceDialogContent-DXRLYd78.js (removed) 4.65 kB 🟢 -4.65 kB 🟢 -1.79 kB 🟢 -1.55 kB
assets/LeaveWorkspaceDialogContent-D8mXpgZc.js (removed) 4.48 kB 🟢 -4.48 kB 🟢 -1.73 kB 🟢 -1.51 kB
assets/LeaveWorkspaceDialogContent-w6bsju1n.js (new) 4.48 kB 🔴 +4.48 kB 🔴 +1.73 kB 🔴 +1.5 kB
assets/RemoveMemberDialogContent-CekgigfZ.js (removed) 4.46 kB 🟢 -4.46 kB 🟢 -1.69 kB 🟢 -1.48 kB
assets/RemoveMemberDialogContent-rAFlTYgp.js (new) 4.46 kB 🔴 +4.46 kB 🔴 +1.69 kB 🔴 +1.48 kB
assets/tierBenefits-A607wcAn.js (removed) 4.45 kB 🟢 -4.45 kB 🟢 -1.57 kB 🟢 -1.36 kB
assets/tierBenefits-ClS6lPPz.js (new) 4.45 kB 🔴 +4.45 kB 🔴 +1.57 kB 🔴 +1.36 kB
assets/RevokeInviteDialogContent-BhIGifJc.js (removed) 4.37 kB 🟢 -4.37 kB 🟢 -1.7 kB 🟢 -1.49 kB
assets/RevokeInviteDialogContent-DtIaywx7.js (new) 4.37 kB 🔴 +4.37 kB 🔴 +1.7 kB 🔴 +1.49 kB
assets/InviteMemberUpsellDialogContent-BlBTLqPO.js (new) 4.27 kB 🔴 +4.27 kB 🔴 +1.56 kB 🔴 +1.38 kB
assets/InviteMemberUpsellDialogContent-BRbmHlK9.js (removed) 4.27 kB 🟢 -4.27 kB 🟢 -1.56 kB 🟢 -1.37 kB
assets/cloudSessionCookie-BHOueC65.js (removed) 4.12 kB 🟢 -4.12 kB 🟢 -1.49 kB 🟢 -1.3 kB
assets/cloudSessionCookie-BtArhxC0.js (new) 4.12 kB 🔴 +4.12 kB 🔴 +1.49 kB 🔴 +1.3 kB
assets/saveMesh-CqNVCWdW.js (new) 3.92 kB 🔴 +3.92 kB 🔴 +1.68 kB 🔴 +1.48 kB
assets/saveMesh-D-SAUZ3c.js (removed) 3.92 kB 🟢 -3.92 kB 🟢 -1.69 kB 🟢 -1.48 kB
assets/Media3DTop-C2ZOyGjd.js (new) 3.85 kB 🔴 +3.85 kB 🔴 +1.63 kB 🔴 +1.43 kB
assets/Media3DTop-y4jIhAz5.js (removed) 3.85 kB 🟢 -3.85 kB 🟢 -1.62 kB 🟢 -1.42 kB
assets/GlobalToast-D9hKIU4S.js (removed) 3.05 kB 🟢 -3.05 kB 🟢 -1.26 kB 🟢 -1.07 kB
assets/GlobalToast-YpiP8uA2.js (new) 3.05 kB 🔴 +3.05 kB 🔴 +1.26 kB 🔴 +1.11 kB
assets/SubscribeToRun-BHGdZRW5.js (removed) 2.13 kB 🟢 -2.13 kB 🟢 -982 B 🟢 -874 B
assets/SubscribeToRun-DGIEDYgK.js (new) 2.13 kB 🔴 +2.13 kB 🔴 +983 B 🔴 +881 B
assets/MediaAudioTop-BJfm4SyZ.js (removed) 2.02 kB 🟢 -2.02 kB 🟢 -980 B 🟢 -885 B
assets/MediaAudioTop-Ds27xOJY.js (new) 2.02 kB 🔴 +2.02 kB 🔴 +979 B 🔴 +874 B
assets/CloudRunButtonWrapper-B7qBL6cP.js (removed) 1.99 kB 🟢 -1.99 kB 🟢 -912 B 🟢 -809 B
assets/CloudRunButtonWrapper-BV756vEs.js (new) 1.99 kB 🔴 +1.99 kB 🔴 +913 B 🔴 +811 B
assets/graphHasMissingNodes-Bcullw1H.js (removed) 1.83 kB 🟢 -1.83 kB 🟢 -862 B 🟢 -758 B
assets/graphHasMissingNodes-BQxJ0jZ_.js (new) 1.83 kB 🔴 +1.83 kB 🔴 +862 B 🔴 +754 B
assets/cloudBadges-Cx_Gz2V3.js (new) 1.77 kB 🔴 +1.77 kB 🔴 +894 B 🔴 +772 B
assets/cloudBadges-V1YqbIau.js (removed) 1.77 kB 🟢 -1.77 kB 🟢 -894 B 🟢 -773 B
assets/cloudSubscription-C37IPjFd.js (new) 1.68 kB 🔴 +1.68 kB 🔴 +816 B 🔴 +719 B
assets/cloudSubscription-CFKDgMgK.js (removed) 1.68 kB 🟢 -1.68 kB 🟢 -817 B 🟢 -713 B
assets/previousFullPath-BarGl7SI.js (new) 1.53 kB 🔴 +1.53 kB 🔴 +693 B 🔴 +604 B
assets/previousFullPath-DsKilyGv.js (removed) 1.53 kB 🟢 -1.53 kB 🟢 -692 B 🟢 -595 B
assets/Load3D-2e3b27RR.js (new) 1.34 kB 🔴 +1.34 kB 🔴 +610 B 🔴 +546 B
assets/Load3D-BiTiWdif.js (removed) 1.34 kB 🟢 -1.34 kB 🟢 -612 B 🟢 -550 B
assets/nightlyBadges-D-19jt1N.js (removed) 1.29 kB 🟢 -1.29 kB 🟢 -659 B 🟢 -581 B
assets/nightlyBadges-Dk-N4CEa.js (new) 1.29 kB 🔴 +1.29 kB 🔴 +661 B 🔴 +581 B
assets/Load3dViewerContent-Ckzzas18.js (new) 1.23 kB 🔴 +1.23 kB 🔴 +565 B 🔴 +502 B
assets/Load3dViewerContent-tkHpxEQt.js (removed) 1.23 kB 🟢 -1.23 kB 🟢 -566 B 🟢 -503 B
assets/SubscriptionPanelContentWorkspace-BpKVNZIN.js (removed) 1.15 kB 🟢 -1.15 kB 🟢 -536 B 🟢 -466 B
assets/SubscriptionPanelContentWorkspace-DYtOI1tE.js (new) 1.15 kB 🔴 +1.15 kB 🔴 +536 B 🔴 +469 B
assets/WidgetLegacy-DqCEVi8Y.js (new) 978 B 🔴 +978 B 🔴 +482 B 🔴 +428 B
assets/WidgetLegacy-Q_g_mbsX.js (removed) 978 B 🟢 -978 B 🟢 -482 B 🟢 -429 B
assets/changeTracker-BwS81Bai.js (new) 953 B 🔴 +953 B 🔴 +471 B 🔴 +412 B
assets/changeTracker-DiYbX4wW.js (removed) 953 B 🟢 -953 B 🟢 -472 B 🟢 -416 B

Status: 56 added / 56 removed / 79 unchanged

⚡ Performance Report

canvas-idle: · 60.0 avg FPS · 59.9 P5 FPS ✅ (target: ≥52) · 0ms TBT · 61.5 MB heap
canvas-mouse-sweep: · 60.0 avg FPS · 59.5 P5 FPS ✅ (target: ≥52) · 0ms TBT · 56.8 MB heap
canvas-zoom-sweep: · 60.0 avg FPS · 59.5 P5 FPS ✅ (target: ≥52) · 0ms TBT · 65.1 MB heap
dom-widget-clipping: · 60.0 avg FPS · 59.5 P5 FPS ✅ (target: ≥52) · 0ms TBT · 48.3 MB heap
large-graph-idle: · 60.0 avg FPS · 59.9 P5 FPS ✅ (target: ≥52) · 0ms TBT · 55.2 MB heap
large-graph-pan: · 60.0 avg FPS · 59.9 P5 FPS ✅ (target: ≥52) · 0ms TBT · 71.6 MB heap
large-graph-zoom: · 60.0 avg FPS · 59.5 P5 FPS ✅ (target: ≥52) · 0ms TBT · 62.0 MB heap
minimap-idle: · 60.0 avg FPS · 59.9 P5 FPS ✅ (target: ≥52) · 0ms TBT · 57.1 MB heap
subgraph-dom-widget-clipping: · 60.0 avg FPS · 59.9 P5 FPS ✅ (target: ≥52) · 0ms TBT · 48.2 MB heap
subgraph-idle: · 60.0 avg FPS · 59.5 P5 FPS ✅ (target: ≥52) · 0ms TBT · 61.1 MB heap
subgraph-mouse-sweep: · 60.0 avg FPS · 59.5 P5 FPS ✅ (target: ≥52) · 0ms TBT · 53.1 MB heap
viewport-pan-sweep: · 60.0 avg FPS · 59.9 P5 FPS ✅ (target: ≥52) · 0ms TBT · 62.1 MB heap
vue-large-graph-idle: · 58.1 avg FPS · 59.5 P5 FPS ✅ (target: ≥52) · 0ms TBT · 162.2 MB heap
vue-large-graph-pan: · 56.3 avg FPS · 59.5 P5 FPS ✅ (target: ≥52) · 0ms TBT · 155.3 MB heap
workflow-execution: · 60.0 avg FPS · 59.5 P5 FPS ✅ (target: ≥52) · 0ms TBT · 47.6 MB heap

⚠️ 3 regressions detected

Metric Baseline PR (median) Δ Sig
subgraph-mouse-sweep: style recalc count 76 87 +14% ⚠️ z=3.6
subgraph-mouse-sweep: task duration 525ms 922ms +76% ⚠️ z=2.3
subgraph-mouse-sweep: DOM nodes 62 73 +18% ⚠️ z=2.7
All metrics
Metric Baseline PR (median) Δ Sig
canvas-idle: avg frame time 17ms 17ms +0% z=-0.1
canvas-idle: p95 frame time 17ms 17ms -1%
canvas-idle: layout duration 0ms 0ms +0%
canvas-idle: style recalc duration 6ms 7ms +15% z=-4.0
canvas-idle: layout count 0 0 +0%
canvas-idle: style recalc count 10 8 -20% z=-5.6
canvas-idle: task duration 275ms 354ms +29% z=-1.3
canvas-idle: script duration 17ms 18ms +5% z=-3.3
canvas-idle: TBT 0ms 0ms +0%
canvas-idle: heap used 60.9 MB 61.5 MB +1%
canvas-idle: DOM nodes 18 16 -11% z=-5.2
canvas-idle: event listeners 4 6 +50% z=-1.2
canvas-mouse-sweep: avg frame time 17ms 17ms +0% z=-0.4
canvas-mouse-sweep: p95 frame time 17ms 17ms -0%
canvas-mouse-sweep: layout duration 3ms 3ms +11% z=-0.7
canvas-mouse-sweep: style recalc duration 29ms 45ms +53% z=0.7
canvas-mouse-sweep: layout count 12 12 +0%
canvas-mouse-sweep: style recalc count 72 82 +14% z=1.4
canvas-mouse-sweep: task duration 567ms 932ms +64% z=1.2
canvas-mouse-sweep: script duration 96ms 144ms +50% z=1.3
canvas-mouse-sweep: TBT 0ms 0ms +0%
canvas-mouse-sweep: heap used 56.5 MB 56.8 MB +0%
canvas-mouse-sweep: DOM nodes 55 63 +15% z=0.3
canvas-mouse-sweep: event listeners 4 6 +50% z=-0.6
canvas-zoom-sweep: avg frame time 17ms 17ms +0% z=0.5
canvas-zoom-sweep: p95 frame time 17ms 17ms -0%
canvas-zoom-sweep: layout duration 1ms 1ms +14% z=1.0
canvas-zoom-sweep: style recalc duration 15ms 19ms +26% z=-0.0
canvas-zoom-sweep: layout count 6 6 +0%
canvas-zoom-sweep: style recalc count 30 30 +0% z=-2.8
canvas-zoom-sweep: task duration 242ms 329ms +36% z=0.1
canvas-zoom-sweep: script duration 17ms 29ms +74% z=0.6
canvas-zoom-sweep: TBT 0ms 0ms +0%
canvas-zoom-sweep: heap used 65.1 MB 65.1 MB +0%
canvas-zoom-sweep: DOM nodes 79 77 -3% z=-2.8
canvas-zoom-sweep: event listeners 19 19 +0% z=-0.9
dom-widget-clipping: avg frame time 17ms 17ms +0% z=0.1
dom-widget-clipping: p95 frame time 17ms 17ms +1%
dom-widget-clipping: layout duration 0ms 0ms +0%
dom-widget-clipping: style recalc duration 7ms 8ms +16% z=-1.9
dom-widget-clipping: layout count 0 0 +0%
dom-widget-clipping: style recalc count 13 11 -15% z=-4.2
dom-widget-clipping: task duration 264ms 323ms +22% z=-2.5
dom-widget-clipping: script duration 48ms 58ms +22% z=-2.9
dom-widget-clipping: TBT 0ms 0ms +0%
dom-widget-clipping: heap used 48.0 MB 48.3 MB +1%
dom-widget-clipping: DOM nodes 19 18 -5% z=-2.9
dom-widget-clipping: event listeners 0 2 variance too high
large-graph-idle: avg frame time 17ms 17ms +0% z=-0.2
large-graph-idle: p95 frame time 17ms 17ms -1%
large-graph-idle: layout duration 0ms 0ms +0%
large-graph-idle: style recalc duration 6ms 10ms +72% z=-1.7
large-graph-idle: layout count 0 0 +0%
large-graph-idle: style recalc count 9 10 +11% z=-5.1
large-graph-idle: task duration 441ms 561ms +27% z=0.3
large-graph-idle: script duration 70ms 106ms +50% z=0.3
large-graph-idle: TBT 0ms 0ms +0%
large-graph-idle: heap used 64.2 MB 55.2 MB -14%
large-graph-idle: DOM nodes -259 -259 +0% z=-313.9
large-graph-idle: event listeners -125 -127 +2% z=-25.1
large-graph-pan: avg frame time 17ms 17ms +0% z=0.3
large-graph-pan: p95 frame time 17ms 17ms -1%
large-graph-pan: layout duration 0ms 0ms +0%
large-graph-pan: style recalc duration 16ms 18ms +8% z=0.5
large-graph-pan: layout count 0 0 +0%
large-graph-pan: style recalc count 68 68 +0% z=-2.4
large-graph-pan: task duration 835ms 1070ms +28% z=-0.3
large-graph-pan: script duration 298ms 385ms +29% z=-1.1
large-graph-pan: TBT 0ms 0ms +0%
large-graph-pan: heap used 67.0 MB 71.6 MB +7%
large-graph-pan: DOM nodes -259 -262 +1% z=-170.3
large-graph-pan: event listeners -123 -127 +3% z=-159.3
large-graph-zoom: avg frame time 17ms 17ms +0%
large-graph-zoom: p95 frame time 17ms 17ms +1%
large-graph-zoom: layout duration 7ms 7ms +4%
large-graph-zoom: style recalc duration 18ms 18ms +1%
large-graph-zoom: layout count 60 60 +0%
large-graph-zoom: style recalc count 66 64 -3%
large-graph-zoom: task duration 1101ms 1299ms +18%
large-graph-zoom: script duration 407ms 485ms +19%
large-graph-zoom: TBT 0ms 0ms +0%
large-graph-zoom: heap used 58.2 MB 62.0 MB +7%
large-graph-zoom: DOM nodes -261 -266 +2%
large-graph-zoom: event listeners -121 -123 +2%
minimap-idle: avg frame time 17ms 17ms -0% z=-0.9
minimap-idle: p95 frame time 17ms 17ms -1%
minimap-idle: layout duration 0ms 0ms +0%
minimap-idle: style recalc duration 7ms 9ms +19% z=-1.1
minimap-idle: layout count 0 0 +0%
minimap-idle: style recalc count 9 8 -11% z=-2.3
minimap-idle: task duration 458ms 547ms +19% z=0.4
minimap-idle: script duration 76ms 94ms +23% z=-0.4
minimap-idle: TBT 0ms 0ms +0%
minimap-idle: heap used 55.8 MB 57.1 MB +2%
minimap-idle: DOM nodes -261 -264 +1% z=-207.0
minimap-idle: event listeners -123 -127 +3% z=-199.3
subgraph-dom-widget-clipping: avg frame time 17ms 17ms +0% z=0.1
subgraph-dom-widget-clipping: p95 frame time 17ms 17ms -1%
subgraph-dom-widget-clipping: layout duration 0ms 0ms +0%
subgraph-dom-widget-clipping: style recalc duration 10ms 11ms +4% z=-2.1
subgraph-dom-widget-clipping: layout count 0 0 +0%
subgraph-dom-widget-clipping: style recalc count 47 46 -2% z=-3.3
subgraph-dom-widget-clipping: task duration 290ms 353ms +21% z=-1.4
subgraph-dom-widget-clipping: script duration 103ms 119ms +16% z=-1.4
subgraph-dom-widget-clipping: TBT 0ms 0ms +0%
subgraph-dom-widget-clipping: heap used 48.3 MB 48.2 MB -0%
subgraph-dom-widget-clipping: DOM nodes 20 18 -10% z=-3.7
subgraph-dom-widget-clipping: event listeners 8 8 +0% z=-1.4
subgraph-idle: avg frame time 17ms 17ms +0% z=0.4
subgraph-idle: p95 frame time 17ms 17ms -0%
subgraph-idle: layout duration 0ms 0ms +0%
subgraph-idle: style recalc duration 7ms 10ms +50% z=0.1
subgraph-idle: layout count 0 0 +0%
subgraph-idle: style recalc count 11 10 -9% z=-1.4
subgraph-idle: task duration 281ms 341ms +21% z=-0.9
subgraph-idle: script duration 15ms 18ms +25% z=-0.7
subgraph-idle: TBT 0ms 0ms +0%
subgraph-idle: heap used 60.8 MB 61.1 MB +0%
subgraph-idle: DOM nodes 21 20 -5% z=-1.2
subgraph-idle: event listeners 4 6 +50% variance too high
subgraph-mouse-sweep: avg frame time 17ms 17ms +0% z=0.4
subgraph-mouse-sweep: p95 frame time 17ms 17ms +1%
subgraph-mouse-sweep: layout duration 4ms 4ms +23% z=-0.9
subgraph-mouse-sweep: style recalc duration 30ms 48ms +56% z=1.5
subgraph-mouse-sweep: layout count 16 16 +0%
subgraph-mouse-sweep: style recalc count 76 87 +14% ⚠️ z=3.6
subgraph-mouse-sweep: task duration 525ms 922ms +76% ⚠️ z=2.3
subgraph-mouse-sweep: script duration 75ms 103ms +38% z=0.3
subgraph-mouse-sweep: TBT 0ms 0ms +0%
subgraph-mouse-sweep: heap used 52.8 MB 53.1 MB +1%
subgraph-mouse-sweep: DOM nodes 62 73 +18% ⚠️ z=2.7
subgraph-mouse-sweep: event listeners 4 6 +50% variance too high
viewport-pan-sweep: avg frame time 17ms 17ms -0%
viewport-pan-sweep: p95 frame time 17ms 17ms -1%
viewport-pan-sweep: layout duration 0ms 0ms +0%
viewport-pan-sweep: style recalc duration 51ms 52ms +2%
viewport-pan-sweep: layout count 0 0 +0%
viewport-pan-sweep: style recalc count 250 250 +0%
viewport-pan-sweep: task duration 2909ms 3715ms +28%
viewport-pan-sweep: script duration 956ms 1263ms +32%
viewport-pan-sweep: TBT 0ms 0ms +0%
viewport-pan-sweep: heap used 71.7 MB 62.1 MB -13%
viewport-pan-sweep: DOM nodes -257 -257 +0%
viewport-pan-sweep: event listeners -111 -113 +2%
vue-large-graph-idle: avg frame time 17ms 17ms +0%
vue-large-graph-idle: p95 frame time 17ms 17ms +1%
vue-large-graph-idle: layout duration 0ms 0ms +0%
vue-large-graph-idle: style recalc duration 0ms 0ms +0%
vue-large-graph-idle: layout count 0 0 +0%
vue-large-graph-idle: style recalc count 0 0 +0%
vue-large-graph-idle: task duration 10716ms 12383ms +16%
vue-large-graph-idle: script duration 476ms 621ms +30%
vue-large-graph-idle: TBT 0ms 0ms +0%
vue-large-graph-idle: heap used 161.8 MB 162.2 MB +0%
vue-large-graph-idle: DOM nodes -9850 -9850 +0%
vue-large-graph-idle: event listeners -23961 -23959 -0%
vue-large-graph-pan: avg frame time 17ms 18ms +3%
vue-large-graph-pan: p95 frame time 17ms 17ms +1%
vue-large-graph-pan: layout duration 0ms 0ms +0%
vue-large-graph-pan: style recalc duration 16ms 17ms +4%
vue-large-graph-pan: layout count 0 0 +0%
vue-large-graph-pan: style recalc count 64 67 +5%
vue-large-graph-pan: task duration 12372ms 14559ms +18%
vue-large-graph-pan: script duration 741ms 890ms +20%
vue-large-graph-pan: TBT 0ms 0ms +0%
vue-large-graph-pan: heap used 160.1 MB 155.3 MB -3%
vue-large-graph-pan: DOM nodes -9850 -9849 -0%
vue-large-graph-pan: event listeners -23961 -23953 -0%
workflow-execution: avg frame time 17ms 17ms +0% z=0.6
workflow-execution: p95 frame time 17ms 17ms +1%
workflow-execution: layout duration 1ms 1ms +16% z=-1.4
workflow-execution: style recalc duration 20ms 23ms +13% z=-0.6
workflow-execution: layout count 5 5 +0% z=0.1
workflow-execution: style recalc count 18 16 -11% z=-0.9
workflow-execution: task duration 94ms 126ms +34% z=0.3
workflow-execution: script duration 19ms 28ms +48% z=-0.3
workflow-execution: TBT 0ms 0ms +0%
workflow-execution: heap used 47.8 MB 47.6 MB -1%
workflow-execution: DOM nodes 156 156 +0% z=-0.7
workflow-execution: event listeners 69 71 +3% z=4.4
Historical variance (last 15 runs)
Metric μ σ CV
canvas-idle: avg frame time 17ms 0ms 0.0%
canvas-idle: layout duration 0ms 0ms 0.0%
canvas-idle: style recalc duration 11ms 1ms 8.2%
canvas-idle: layout count 0 0 0.0%
canvas-idle: style recalc count 11 1 5.0%
canvas-idle: task duration 395ms 31ms 7.9%
canvas-idle: script duration 25ms 2ms 8.8%
canvas-idle: TBT 0ms 0ms 0.0%
canvas-idle: DOM nodes 23 1 5.6%
canvas-idle: event listeners 12 5 40.9%
canvas-mouse-sweep: avg frame time 17ms 0ms 0.0%
canvas-mouse-sweep: layout duration 4ms 0ms 5.4%
canvas-mouse-sweep: style recalc duration 43ms 3ms 7.4%
canvas-mouse-sweep: layout count 12 0 0.0%
canvas-mouse-sweep: style recalc count 79 2 3.0%
canvas-mouse-sweep: task duration 865ms 58ms 6.7%
canvas-mouse-sweep: script duration 136ms 6ms 4.8%
canvas-mouse-sweep: TBT 0ms 0ms 0.0%
canvas-mouse-sweep: DOM nodes 62 3 4.2%
canvas-mouse-sweep: event listeners 8 4 49.4%
canvas-zoom-sweep: avg frame time 17ms 0ms 0.0%
canvas-zoom-sweep: layout duration 1ms 0ms 7.0%
canvas-zoom-sweep: style recalc duration 19ms 2ms 8.0%
canvas-zoom-sweep: layout count 6 0 0.0%
canvas-zoom-sweep: style recalc count 31 0 1.5%
canvas-zoom-sweep: task duration 327ms 23ms 7.1%
canvas-zoom-sweep: script duration 27ms 3ms 11.1%
canvas-zoom-sweep: TBT 0ms 0ms 0.0%
canvas-zoom-sweep: DOM nodes 79 1 1.0%
canvas-zoom-sweep: event listeners 24 5 21.8%
dom-widget-clipping: avg frame time 17ms 0ms 0.0%
dom-widget-clipping: layout duration 0ms 0ms 0.0%
dom-widget-clipping: style recalc duration 10ms 1ms 8.0%
dom-widget-clipping: layout count 0 0 0.0%
dom-widget-clipping: style recalc count 13 0 3.8%
dom-widget-clipping: task duration 365ms 16ms 4.5%
dom-widget-clipping: script duration 68ms 3ms 4.8%
dom-widget-clipping: TBT 0ms 0ms 0.0%
dom-widget-clipping: DOM nodes 22 1 6.4%
dom-widget-clipping: event listeners 8 6 81.2%
large-graph-idle: avg frame time 17ms 0ms 0.0%
large-graph-idle: layout duration 0ms 0ms 0.0%
large-graph-idle: style recalc duration 12ms 1ms 8.6%
large-graph-idle: layout count 0 0 0.0%
large-graph-idle: style recalc count 12 0 2.7%
large-graph-idle: task duration 542ms 54ms 10.0%
large-graph-idle: script duration 102ms 11ms 10.3%
large-graph-idle: TBT 0ms 0ms 0.0%
large-graph-idle: DOM nodes 25 1 3.7%
large-graph-idle: event listeners 26 6 23.2%
large-graph-pan: avg frame time 17ms 0ms 0.0%
large-graph-pan: layout duration 0ms 0ms 0.0%
large-graph-pan: style recalc duration 17ms 1ms 4.6%
large-graph-pan: layout count 0 0 0.0%
large-graph-pan: style recalc count 70 1 0.9%
large-graph-pan: task duration 1082ms 43ms 4.0%
large-graph-pan: script duration 408ms 20ms 4.8%
large-graph-pan: TBT 0ms 0ms 0.0%
large-graph-pan: DOM nodes 19 2 8.7%
large-graph-pan: event listeners 5 1 16.8%
minimap-idle: avg frame time 17ms 0ms 0.0%
minimap-idle: layout duration 0ms 0ms 0.0%
minimap-idle: style recalc duration 10ms 1ms 8.6%
minimap-idle: layout count 0 0 0.0%
minimap-idle: style recalc count 10 1 7.1%
minimap-idle: task duration 527ms 47ms 9.0%
minimap-idle: script duration 98ms 10ms 10.1%
minimap-idle: TBT 0ms 0ms 0.0%
minimap-idle: DOM nodes 19 1 7.1%
minimap-idle: event listeners 5 1 14.4%
subgraph-dom-widget-clipping: avg frame time 17ms 0ms 0.0%
subgraph-dom-widget-clipping: layout duration 0ms 0ms 0.0%
subgraph-dom-widget-clipping: style recalc duration 13ms 1ms 7.4%
subgraph-dom-widget-clipping: layout count 0 0 0.0%
subgraph-dom-widget-clipping: style recalc count 48 1 1.2%
subgraph-dom-widget-clipping: task duration 378ms 18ms 4.9%
subgraph-dom-widget-clipping: script duration 128ms 6ms 4.9%
subgraph-dom-widget-clipping: TBT 0ms 0ms 0.0%
subgraph-dom-widget-clipping: DOM nodes 22 1 5.0%
subgraph-dom-widget-clipping: event listeners 16 6 36.0%
subgraph-idle: avg frame time 17ms 0ms 0.0%
subgraph-idle: layout duration 0ms 0ms 0.0%
subgraph-idle: style recalc duration 10ms 1ms 7.5%
subgraph-idle: layout count 0 0 0.0%
subgraph-idle: style recalc count 11 1 6.0%
subgraph-idle: task duration 370ms 31ms 8.5%
subgraph-idle: script duration 20ms 3ms 13.2%
subgraph-idle: TBT 0ms 0ms 0.0%
subgraph-idle: DOM nodes 22 1 6.9%
subgraph-idle: event listeners 10 7 64.5%
subgraph-mouse-sweep: avg frame time 17ms 0ms 0.0%
subgraph-mouse-sweep: layout duration 5ms 0ms 6.8%
subgraph-mouse-sweep: style recalc duration 42ms 3ms 7.8%
subgraph-mouse-sweep: layout count 16 0 0.0%
subgraph-mouse-sweep: style recalc count 80 2 2.4%
subgraph-mouse-sweep: task duration 766ms 69ms 9.0%
subgraph-mouse-sweep: script duration 101ms 7ms 6.5%
subgraph-mouse-sweep: TBT 0ms 0ms 0.0%
subgraph-mouse-sweep: DOM nodes 67 2 3.3%
subgraph-mouse-sweep: event listeners 8 4 52.6%
workflow-execution: avg frame time 17ms 0ms 0.0%
workflow-execution: layout duration 2ms 0ms 9.4%
workflow-execution: style recalc duration 24ms 2ms 9.1%
workflow-execution: layout count 5 1 11.0%
workflow-execution: style recalc count 18 2 11.5%
workflow-execution: task duration 123ms 11ms 8.8%
workflow-execution: script duration 29ms 3ms 10.2%
workflow-execution: TBT 0ms 0ms 0.0%
workflow-execution: DOM nodes 161 7 4.4%
workflow-execution: event listeners 52 4 8.4%
Trend (last 15 commits on main)
Metric Trend Dir Latest
canvas-idle: avg frame time ▆▃▆▁▆▃▆█▆▆▄▃▃▄▃ ➡️ 17ms
canvas-idle: p95 frame time ➡️ NaNms
canvas-idle: layout duration ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
canvas-idle: style recalc duration ▇▇▆▆▃█▄▃▄▃▇▄▁▆▇ ➡️ 11ms
canvas-idle: layout count ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0
canvas-idle: style recalc count █▃▅▂▅▆▃▁▂▁▂▅▆▅▆ ➡️ 12
canvas-idle: task duration ▃▃▃▆▂▃▃▅▆▂█▃▁▃▃ ➡️ 391ms
canvas-idle: script duration ▄▃▅▇▂▅▃▆▇▅█▄▁▅▆ ➡️ 27ms
canvas-idle: TBT ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
canvas-idle: heap used ➡️ NaN MB
canvas-idle: DOM nodes █▇▆▅▃▇▃▁▂▂▅▆▆▆▇ ➡️ 24
canvas-idle: event listeners ▅█▅▄▁▅▁▁▁▄▅▅▁▅▄ 📉 11
canvas-mouse-sweep: avg frame time ▆█▆▃▁▃▁▆▆▁▃▆▆▃▃ ➡️ 17ms
canvas-mouse-sweep: p95 frame time ➡️ NaNms
canvas-mouse-sweep: layout duration ▁▃▂▄▁▂▁▃▆▂█▇▆▄▃ ➡️ 4ms
canvas-mouse-sweep: style recalc duration ▄▄▂▄▁▂▃▃▅▄█▆▂▄▄ ➡️ 43ms
canvas-mouse-sweep: layout count ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 12
canvas-mouse-sweep: style recalc count █▅▄▃▂▂▁▄▄▅▆▅▂▇▄ ➡️ 79
canvas-mouse-sweep: task duration █▆▄▂▂▃▂▄▄▅█▆▁▆▄ ➡️ 868ms
canvas-mouse-sweep: script duration ▄▅▄▆▄▆▆▆▅▅█▆▁▅▆ ➡️ 139ms
canvas-mouse-sweep: TBT ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
canvas-mouse-sweep: heap used ➡️ NaN MB
canvas-mouse-sweep: DOM nodes █▅▃▃▁▂▂▃▂▄▆▅▃▅▅ ➡️ 64
canvas-mouse-sweep: event listeners █▁▁▁▁▁▇▁▁▁██▇▁█ 📈 13
canvas-zoom-sweep: avg frame time ▅▅█▄▅▁▁▁▅▁▁▅▄▅▁ ➡️ 17ms
canvas-zoom-sweep: p95 frame time ➡️ NaNms
canvas-zoom-sweep: layout duration ▆▅▅▄▁▁█▅▃▅▇▆▁▂▆ ➡️ 1ms
canvas-zoom-sweep: style recalc duration ▆▅▄▆▅▃█▆▇▅▇▄▁▃▅ ➡️ 20ms
canvas-zoom-sweep: layout count ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 6
canvas-zoom-sweep: style recalc count ▁▁▃▄▆▃▆█▄▄▆▁▆▁▆ ➡️ 32
canvas-zoom-sweep: task duration ▄▂▁▇▂▂▄▅▆▃█▄▁▁▅ ➡️ 338ms
canvas-zoom-sweep: script duration ▃▃▂▇▂▂▅▇▆▅█▄▁▂▆ ➡️ 30ms
canvas-zoom-sweep: TBT ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
canvas-zoom-sweep: heap used ➡️ NaN MB
canvas-zoom-sweep: DOM nodes ▄▃▁▅█▁▃▆▄▅▅▃▃▄▃ ➡️ 79
canvas-zoom-sweep: event listeners ▁▁▂▅█▂▁▅▁▅▅▄▁▅▁ ➡️ 19
dom-widget-clipping: avg frame time ▂▄▅▅▂▄█▇▅▇▇▅▅▁▇ ➡️ 17ms
dom-widget-clipping: p95 frame time ➡️ NaNms
dom-widget-clipping: layout duration ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
dom-widget-clipping: style recalc duration ▆▆▂▆▄▃██▄▁▆▇▆▃▅ ➡️ 10ms
dom-widget-clipping: layout count ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0
dom-widget-clipping: style recalc count ▇█▅█▅▄█▇▇▁▇▄▇▂▅ ➡️ 13
dom-widget-clipping: task duration ▃▃▁▅▄▃▅▆▅▂▇█▁▅▅ ➡️ 371ms
dom-widget-clipping: script duration ▅▄▄▆▆▅▇▇▆▃█▇▁▇▇ ➡️ 71ms
dom-widget-clipping: TBT ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
dom-widget-clipping: heap used ➡️ NaN MB
dom-widget-clipping: DOM nodes ▇▇▄▇▅▄█▇▅▁▅▄▇▃▄ ➡️ 21
dom-widget-clipping: event listeners ▅▅▅▅▁▅██▁▁▁▁█▁▁ 📉 2
large-graph-idle: avg frame time ▅▅▅▅▅▂▁▂▄▅▄▂▂▅█ ➡️ 17ms
large-graph-idle: p95 frame time ➡️ NaNms
large-graph-idle: layout duration ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
large-graph-idle: style recalc duration ▅▅▅▆▄▅▃▄▅▅▆█▁▄▆ ➡️ 13ms
large-graph-idle: layout count ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0
large-graph-idle: style recalc count █▆█▃▃▁▃▆▃▆▆▃▆██ ➡️ 12
large-graph-idle: task duration ▂▃▂▆▂▃▃▇▅▃██▁▂▅ ➡️ 569ms
large-graph-idle: script duration ▄▅▄▆▄▅▅▇▆▅█▆▁▃▆ ➡️ 110ms
large-graph-idle: TBT ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
large-graph-idle: heap used ➡️ NaN MB
large-graph-idle: DOM nodes ▆█▅▂▅▃▁▂▃▅▅▆▂▆▅ ➡️ 25
large-graph-idle: event listeners ███▇██▄▁▄▇▇█▂█▇ ➡️ 29
large-graph-pan: avg frame time ▆▃▃▆█▃▁█▆▆▆▆█▁▆ ➡️ 17ms
large-graph-pan: p95 frame time ➡️ NaNms
large-graph-pan: layout duration ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
large-graph-pan: style recalc duration ▃▂▄▄▁▅▂▂▁▄▄█▃▁▂ ➡️ 17ms
large-graph-pan: layout count ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0
large-graph-pan: style recalc count ▆▃█▂▃▂▂▂▁▇▅▃█▆▃ ➡️ 69
large-graph-pan: task duration ▄▃▄▆▄▄▄▆▄▄█▆▁▂▅ ➡️ 1100ms
large-graph-pan: script duration ▅▄▅▆▆▅▄▆▄▅█▄▁▄▅ ➡️ 413ms
large-graph-pan: TBT ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
large-graph-pan: heap used ➡️ NaN MB
large-graph-pan: DOM nodes ▅▃▆▂▄▁▃▁▁▅▁▂█▅▂ ➡️ 18
large-graph-pan: event listeners █▆█▁▁▆▁▁▃▆▁▃██▃ ➡️ 5
minimap-idle: avg frame time ▃▆▆▃█▁█▆▆▃▃▆█▆█ ➡️ 17ms
minimap-idle: p95 frame time ➡️ NaNms
minimap-idle: layout duration ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
minimap-idle: style recalc duration ▄█▁█▅▅█▅▅▃▅▁▁▄▆ ➡️ 10ms
minimap-idle: layout count ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0
minimap-idle: style recalc count ▃▅▂▄█▃▆▁▂▅▂▁▅▆▃ ➡️ 9
minimap-idle: task duration ▃▄▁▅▁▃▄▅▇▃█▅▁▁▅ ➡️ 547ms
minimap-idle: script duration ▄▆▃▇▃▅▆▆▇▅█▅▁▃▆ ➡️ 106ms
minimap-idle: TBT ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
minimap-idle: heap used ➡️ NaN MB
minimap-idle: DOM nodes ▃▅▂▄█▃▆▁▂▅▂▁▅▆▃ ➡️ 19
minimap-idle: event listeners ▃▃▆▁▁▁▃▁▁▆▁▃█▆▁ ➡️ 4
subgraph-dom-widget-clipping: avg frame time ▅▄▄▄▄▄█▄▄▄▃▁▆▃▃ ➡️ 17ms
subgraph-dom-widget-clipping: p95 frame time ➡️ NaNms
subgraph-dom-widget-clipping: layout duration ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
subgraph-dom-widget-clipping: style recalc duration ▂▄▃▅▅▃▂▅▇▃▄█▁▄▆ ➡️ 14ms
subgraph-dom-widget-clipping: layout count ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0
subgraph-dom-widget-clipping: style recalc count ▇█▆▃▆▃▁▆█▇▃▆▇█▅ ➡️ 48
subgraph-dom-widget-clipping: task duration ▂▃▃▆▅▅▂▅█▂▆█▁▂▇ ➡️ 398ms
subgraph-dom-widget-clipping: script duration ▃▃▃▄▅▅▂▄█▂▅▇▁▂▅ ➡️ 131ms
subgraph-dom-widget-clipping: TBT ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
subgraph-dom-widget-clipping: heap used ➡️ NaN MB
subgraph-dom-widget-clipping: DOM nodes ▅▇▅▂▅▂▁▅▅▅▁▇▅█▄ ➡️ 22
subgraph-dom-widget-clipping: event listeners ▅▅▅▂▅▁▅██▁▁█▅█▅ 📈 16
subgraph-idle: avg frame time ▆▆█▁▆▃▆▆▆▃▆▁▃▆█ ➡️ 17ms
subgraph-idle: p95 frame time ➡️ NaNms
subgraph-idle: layout duration ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
subgraph-idle: style recalc duration ▁▇▃▆▂▄▂▃▃▆▆▄▃▇█ ➡️ 12ms
subgraph-idle: layout count ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0
subgraph-idle: style recalc count ▃▆▃▃▂▅▁▂▁▆▃▃██▇ ➡️ 12
subgraph-idle: task duration ▁▃▁▇▁▁▃▆▅▂█▅▁▁▄ ➡️ 378ms
subgraph-idle: script duration ▁▃▂▇▁▂▃▇▆▂█▅▂▁▅ ➡️ 22ms
subgraph-idle: TBT ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
subgraph-idle: heap used ➡️ NaN MB
subgraph-idle: DOM nodes ▃▅▃▂▁▄▁▂▁▅▃▂▇█▇ ➡️ 24
subgraph-idle: event listeners ▁▅▁▁▁▁▁▁▁▅▄▁███ 📈 21
subgraph-mouse-sweep: avg frame time ▅▄▁▃▃▄▆▄▆▃▃█▁▃▃ ➡️ 17ms
subgraph-mouse-sweep: p95 frame time ➡️ NaNms
subgraph-mouse-sweep: layout duration ▁▄▄▄▃▃▅▅▅▂█▇▂▃▆ ➡️ 5ms
subgraph-mouse-sweep: style recalc duration ▃▂▄▅▂▃▄▅█▃█▆▁▂▅ ➡️ 43ms
subgraph-mouse-sweep: layout count ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 16
subgraph-mouse-sweep: style recalc count ▅▂▅▅▁▄▃▅█▅▆▄▂▄▅ ➡️ 81
subgraph-mouse-sweep: task duration ▃▂▄▅▂▄▄▅▇▄█▆▁▃▅ ➡️ 785ms
subgraph-mouse-sweep: script duration ▄▅▄▇▅▅▆▇▆▅██▁▄▆ ➡️ 105ms
subgraph-mouse-sweep: TBT ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
subgraph-mouse-sweep: heap used ➡️ NaN MB
subgraph-mouse-sweep: DOM nodes ▅▁▄▅▁▄▃▃█▅▅▄▂▅▃ ➡️ 66
subgraph-mouse-sweep: event listeners ▇▁▂▇▁▂▂▂█▇▂▂▇▇▂ 📈 5
workflow-execution: avg frame time ▆▆▆▄▆▆▃▄▁▄█▆▅▄▆ ➡️ 17ms
workflow-execution: p95 frame time ➡️ NaNms
workflow-execution: layout duration ▁▆▁▃▂▄▃▂▃▃▅█▄▂▅ ➡️ 2ms
workflow-execution: style recalc duration ▃▇▅▇▁▅▆▇█▁██▂▄▆ ➡️ 25ms
workflow-execution: layout count ▁█▂▃▂▃▃▁▃▃▄▃▂▃▂ ➡️ 5
workflow-execution: style recalc count ▃█▅▇▁▄▅▆▅▅▅▅▄▄▂ ➡️ 15
workflow-execution: task duration ▂▅▄▅▁▄▆▆▆▁▇█▁▃▃ ➡️ 120ms
workflow-execution: script duration ▄▃▄▄▃▅▄▅▆▂▇█▁▃▄ ➡️ 29ms
workflow-execution: TBT ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
workflow-execution: heap used ➡️ NaN MB
workflow-execution: DOM nodes ▂█▃▆▁▄▃▅▃█▃▃▄▃▁ ➡️ 152
workflow-execution: event listeners ▅███▁▅███▁██▅█▅ ➡️ 49
Raw data
{
  "timestamp": "2026-04-19T02:44:00.725Z",
  "gitSha": "7fb2cfdff5b0212afcd6a0f0f68e851e925b219b",
  "branch": "prompt-tab-graph-state-sync-fix",
  "measurements": [
    {
      "name": "canvas-idle",
      "durationMs": 2017.601999999897,
      "styleRecalcs": 6,
      "styleRecalcDurationMs": 5.277999999999998,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 344.526,
      "heapDeltaBytes": 19968820,
      "heapUsedBytes": 64492572,
      "domNodes": 12,
      "jsHeapTotalBytes": 23592960,
      "scriptDurationMs": 17.859000000000005,
      "eventListeners": 6,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "canvas-idle",
      "durationMs": 2021.221999999966,
      "styleRecalcs": 10,
      "styleRecalcDurationMs": 10.112000000000002,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 365.907,
      "heapDeltaBytes": 21011192,
      "heapUsedBytes": 64551888,
      "domNodes": 20,
      "jsHeapTotalBytes": 23068672,
      "scriptDurationMs": 23.315,
      "eventListeners": 6,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.699999999999818
    },
    {
      "name": "canvas-idle",
      "durationMs": 2022.9170000000067,
      "styleRecalcs": 8,
      "styleRecalcDurationMs": 7.265000000000001,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 353.87199999999996,
      "heapDeltaBytes": -5137780,
      "heapUsedBytes": 45428628,
      "domNodes": 16,
      "jsHeapTotalBytes": 24641536,
      "scriptDurationMs": 17.729000000000003,
      "eventListeners": 6,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "canvas-mouse-sweep",
      "durationMs": 1963.9319999999998,
      "styleRecalcs": 78,
      "styleRecalcDurationMs": 53.318000000000005,
      "layouts": 12,
      "layoutDurationMs": 4.195000000000001,
      "taskDurationMs": 867.5189999999999,
      "heapDeltaBytes": 15618344,
      "heapUsedBytes": 59522628,
      "domNodes": 63,
      "jsHeapTotalBytes": 23855104,
      "scriptDurationMs": 143.94,
      "eventListeners": 4,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "canvas-mouse-sweep",
      "durationMs": 1999.9990000000025,
      "styleRecalcs": 82,
      "styleRecalcDurationMs": 41.415,
      "layouts": 12,
      "layoutDurationMs": 3.4799999999999995,
      "taskDurationMs": 932.1780000000001,
      "heapDeltaBytes": 15586140,
      "heapUsedBytes": 59548564,
      "domNodes": 62,
      "jsHeapTotalBytes": 23330816,
      "scriptDurationMs": 128.056,
      "eventListeners": 6,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "canvas-mouse-sweep",
      "durationMs": 2022.5500000000238,
      "styleRecalcs": 84,
      "styleRecalcDurationMs": 44.979000000000006,
      "layouts": 12,
      "layoutDurationMs": 3.401,
      "taskDurationMs": 978.4499999999999,
      "heapDeltaBytes": 15825060,
      "heapUsedBytes": 58915516,
      "domNodes": 67,
      "jsHeapTotalBytes": 23068672,
      "scriptDurationMs": 145.543,
      "eventListeners": 6,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.799999999999272
    },
    {
      "name": "canvas-zoom-sweep",
      "durationMs": 1729.0500000000293,
      "styleRecalcs": 30,
      "styleRecalcDurationMs": 19.125,
      "layouts": 6,
      "layoutDurationMs": 0.6479999999999999,
      "taskDurationMs": 328.96000000000004,
      "heapDeltaBytes": 24929724,
      "heapUsedBytes": 67905536,
      "domNodes": 77,
      "jsHeapTotalBytes": 20971520,
      "scriptDurationMs": 29.002,
      "eventListeners": 19,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333332,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "canvas-zoom-sweep",
      "durationMs": 1757.325000000037,
      "styleRecalcs": 30,
      "styleRecalcDurationMs": 19.097999999999995,
      "layouts": 6,
      "layoutDurationMs": 0.719,
      "taskDurationMs": 351.812,
      "heapDeltaBytes": 24901520,
      "heapUsedBytes": 68590996,
      "domNodes": 77,
      "jsHeapTotalBytes": 22020096,
      "scriptDurationMs": 31.418999999999997,
      "eventListeners": 19,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.799999999999272
    },
    {
      "name": "canvas-zoom-sweep",
      "durationMs": 1723.0079999999361,
      "styleRecalcs": 33,
      "styleRecalcDurationMs": 17.9,
      "layouts": 6,
      "layoutDurationMs": 0.6890000000000001,
      "taskDurationMs": 306.542,
      "heapDeltaBytes": 24657212,
      "heapUsedBytes": 68213868,
      "domNodes": 79,
      "jsHeapTotalBytes": 21233664,
      "scriptDurationMs": 26.492999999999995,
      "eventListeners": 19,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "dom-widget-clipping",
      "durationMs": 589.6910000000162,
      "styleRecalcs": 13,
      "styleRecalcDurationMs": 10.596,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 371.333,
      "heapDeltaBytes": 6977824,
      "heapUsedBytes": 50664244,
      "domNodes": 21,
      "jsHeapTotalBytes": 12058624,
      "scriptDurationMs": 69.54599999999999,
      "eventListeners": 2,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "dom-widget-clipping",
      "durationMs": 536.8409999999812,
      "styleRecalcs": 10,
      "styleRecalcDurationMs": 7.443999999999999,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 323.19999999999993,
      "heapDeltaBytes": 6607184,
      "heapUsedBytes": 50604460,
      "domNodes": 16,
      "jsHeapTotalBytes": 11796480,
      "scriptDurationMs": 58.334,
      "eventListeners": 2,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "dom-widget-clipping",
      "durationMs": 564.4179999999324,
      "styleRecalcs": 11,
      "styleRecalcDurationMs": 8.361999999999998,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 319.608,
      "heapDeltaBytes": 6644796,
      "heapUsedBytes": 50685196,
      "domNodes": 18,
      "jsHeapTotalBytes": 12582912,
      "scriptDurationMs": 56.138,
      "eventListeners": 2,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.663333333333338,
      "p95FrameDurationMs": 16.700000000000273
    },
    {
      "name": "large-graph-idle",
      "durationMs": 2069.1210000001092,
      "styleRecalcs": 10,
      "styleRecalcDurationMs": 12.045,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 560.61,
      "heapDeltaBytes": 5115108,
      "heapUsedBytes": 58876460,
      "domNodes": -259,
      "jsHeapTotalBytes": 15740928,
      "scriptDurationMs": 107.75399999999999,
      "eventListeners": -127,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "large-graph-idle",
      "durationMs": 2062.059999999974,
      "styleRecalcs": 9,
      "styleRecalcDurationMs": 10.016,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 582.754,
      "heapDeltaBytes": 4373020,
      "heapUsedBytes": 57930724,
      "domNodes": -259,
      "jsHeapTotalBytes": 16158720,
      "scriptDurationMs": 105.641,
      "eventListeners": -127,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333332,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "large-graph-idle",
      "durationMs": 2035.6559999999035,
      "styleRecalcs": 10,
      "styleRecalcDurationMs": 10.468000000000002,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 542.463,
      "heapDeltaBytes": 4309088,
      "heapUsedBytes": 57658984,
      "domNodes": -258,
      "jsHeapTotalBytes": 16683008,
      "scriptDurationMs": 101.34500000000001,
      "eventListeners": -127,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "large-graph-pan",
      "durationMs": 2098.8820000000032,
      "styleRecalcs": 68,
      "styleRecalcDurationMs": 17.952999999999996,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 1042.245,
      "heapDeltaBytes": 9768648,
      "heapUsedBytes": 75080864,
      "domNodes": -262,
      "jsHeapTotalBytes": 15167488,
      "scriptDurationMs": 381.19199999999995,
      "eventListeners": -129,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "large-graph-pan",
      "durationMs": 2117.7559999999858,
      "styleRecalcs": 68,
      "styleRecalcDurationMs": 17.676,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 1069.8539999999998,
      "heapDeltaBytes": 20028376,
      "heapUsedBytes": 75797032,
      "domNodes": -262,
      "jsHeapTotalBytes": 17149952,
      "scriptDurationMs": 385.405,
      "eventListeners": -127,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "large-graph-pan",
      "durationMs": 2115.2009999999564,
      "styleRecalcs": 67,
      "styleRecalcDurationMs": 16.677999999999997,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 1075.227,
      "heapDeltaBytes": 16627108,
      "heapUsedBytes": 71751528,
      "domNodes": -263,
      "jsHeapTotalBytes": 17674240,
      "scriptDurationMs": 388.81399999999996,
      "eventListeners": -127,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "large-graph-zoom",
      "durationMs": 3132.8119999999444,
      "styleRecalcs": 64,
      "styleRecalcDurationMs": 17.046000000000003,
      "layouts": 60,
      "layoutDurationMs": 7.35,
      "taskDurationMs": 1299.144,
      "heapDeltaBytes": 7186000,
      "heapUsedBytes": 64426992,
      "domNodes": -266,
      "jsHeapTotalBytes": 16683008,
      "scriptDurationMs": 480.72799999999995,
      "eventListeners": -123,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "large-graph-zoom",
      "durationMs": 3169.777999999951,
      "styleRecalcs": 66,
      "styleRecalcDurationMs": 20.208999999999996,
      "layouts": 60,
      "layoutDurationMs": 7.495,
      "taskDurationMs": 1294.47,
      "heapDeltaBytes": 7182668,
      "heapUsedBytes": 65001076,
      "domNodes": -265,
      "jsHeapTotalBytes": 17731584,
      "scriptDurationMs": 485.477,
      "eventListeners": -123,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "large-graph-zoom",
      "durationMs": 3135.116000000039,
      "styleRecalcs": 64,
      "styleRecalcDurationMs": 17.968,
      "layouts": 60,
      "layoutDurationMs": 7.6979999999999995,
      "taskDurationMs": 1342.102,
      "heapDeltaBytes": 7012920,
      "heapUsedBytes": 66054352,
      "domNodes": -269,
      "jsHeapTotalBytes": 16478208,
      "scriptDurationMs": 486.818,
      "eventListeners": -125,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333332,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "minimap-idle",
      "durationMs": 2046.3799999999992,
      "styleRecalcs": 8,
      "styleRecalcDurationMs": 9.047000000000002,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 546.953,
      "heapDeltaBytes": 3699824,
      "heapUsedBytes": 60227160,
      "domNodes": -264,
      "jsHeapTotalBytes": 16683008,
      "scriptDurationMs": 93.935,
      "eventListeners": -127,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333335,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "minimap-idle",
      "durationMs": 2020.1620000000275,
      "styleRecalcs": 8,
      "styleRecalcDurationMs": 8.625,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 514.294,
      "heapDeltaBytes": 2793812,
      "heapUsedBytes": 59759424,
      "domNodes": -264,
      "jsHeapTotalBytes": 15896576,
      "scriptDurationMs": 90.75599999999999,
      "eventListeners": -127,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "minimap-idle",
      "durationMs": 2038.772999999992,
      "styleRecalcs": 7,
      "styleRecalcDurationMs": 7.681999999999998,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 577.973,
      "heapDeltaBytes": 3059568,
      "heapUsedBytes": 59872316,
      "domNodes": -264,
      "jsHeapTotalBytes": 15896576,
      "scriptDurationMs": 100.47399999999999,
      "eventListeners": -127,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333332,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "subgraph-dom-widget-clipping",
      "durationMs": 573.2480000000351,
      "styleRecalcs": 47,
      "styleRecalcDurationMs": 14.141999999999998,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 358.263,
      "heapDeltaBytes": 6712660,
      "heapUsedBytes": 50591524,
      "domNodes": 20,
      "jsHeapTotalBytes": 13107200,
      "scriptDurationMs": 121.52199999999999,
      "eventListeners": 8,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.663333333333338,
      "p95FrameDurationMs": 16.700000000000273
    },
    {
      "name": "subgraph-dom-widget-clipping",
      "durationMs": 538.2370000000947,
      "styleRecalcs": 46,
      "styleRecalcDurationMs": 10.768,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 352.677,
      "heapDeltaBytes": 6678244,
      "heapUsedBytes": 50615224,
      "domNodes": 18,
      "jsHeapTotalBytes": 13107200,
      "scriptDurationMs": 119.216,
      "eventListeners": 8,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000273
    },
    {
      "name": "subgraph-dom-widget-clipping",
      "durationMs": 537.3549999999341,
      "styleRecalcs": 46,
      "styleRecalcDurationMs": 10.350000000000001,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 345.14500000000004,
      "heapDeltaBytes": 6482328,
      "heapUsedBytes": 49535168,
      "domNodes": 18,
      "jsHeapTotalBytes": 12582912,
      "scriptDurationMs": 117.93399999999998,
      "eventListeners": 8,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.799999999999727
    },
    {
      "name": "subgraph-idle",
      "durationMs": 2022.0919999999296,
      "styleRecalcs": 11,
      "styleRecalcDurationMs": 10.484,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 405.33099999999996,
      "heapDeltaBytes": 20127536,
      "heapUsedBytes": 64017372,
      "domNodes": 21,
      "jsHeapTotalBytes": 22544384,
      "scriptDurationMs": 23.717999999999996,
      "eventListeners": 4,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.799999999999272
    },
    {
      "name": "subgraph-idle",
      "durationMs": 1999.145999999996,
      "styleRecalcs": 7,
      "styleRecalcDurationMs": 5.258000000000003,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 333.919,
      "heapDeltaBytes": 19932484,
      "heapUsedBytes": 63172680,
      "domNodes": 12,
      "jsHeapTotalBytes": 22282240,
      "scriptDurationMs": 14.178999999999997,
      "eventListeners": 6,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333332,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "subgraph-idle",
      "durationMs": 2020.0869999999895,
      "styleRecalcs": 10,
      "styleRecalcDurationMs": 12.024000000000001,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 341.003,
      "heapDeltaBytes": 20142116,
      "heapUsedBytes": 64066540,
      "domNodes": 20,
      "jsHeapTotalBytes": 22544384,
      "scriptDurationMs": 18.445,
      "eventListeners": 6,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "subgraph-mouse-sweep",
      "durationMs": 2007.8629999999293,
      "styleRecalcs": 87,
      "styleRecalcDurationMs": 46.65800000000001,
      "layouts": 16,
      "layoutDurationMs": 4.43,
      "taskDurationMs": 922.1189999999999,
      "heapDeltaBytes": 11794724,
      "heapUsedBytes": 55730616,
      "domNodes": 72,
      "jsHeapTotalBytes": 22544384,
      "scriptDurationMs": 102.95700000000001,
      "eventListeners": 6,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.799999999999272
    },
    {
      "name": "subgraph-mouse-sweep",
      "durationMs": 1967.1660000000202,
      "styleRecalcs": 84,
      "styleRecalcDurationMs": 48.06100000000001,
      "layouts": 16,
      "layoutDurationMs": 4.398999999999999,
      "taskDurationMs": 898.606,
      "heapDeltaBytes": 12207040,
      "heapUsedBytes": 56051552,
      "domNodes": 73,
      "jsHeapTotalBytes": 23068672,
      "scriptDurationMs": 98.04899999999999,
      "eventListeners": 6,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "subgraph-mouse-sweep",
      "durationMs": 1988.4690000000091,
      "styleRecalcs": 88,
      "styleRecalcDurationMs": 47.59,
      "layouts": 16,
      "layoutDurationMs": 4.413,
      "taskDurationMs": 933.4480000000001,
      "heapDeltaBytes": 11429244,
      "heapUsedBytes": 55409532,
      "domNodes": 73,
      "jsHeapTotalBytes": 23330816,
      "scriptDurationMs": 106.846,
      "eventListeners": 6,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "viewport-pan-sweep",
      "durationMs": 8165.397999999982,
      "styleRecalcs": 250,
      "styleRecalcDurationMs": 51.982000000000006,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 3670.3900000000003,
      "heapDeltaBytes": 10094304,
      "heapUsedBytes": 65101864,
      "domNodes": -256,
      "jsHeapTotalBytes": 17440768,
      "scriptDurationMs": 1235.6529999999998,
      "eventListeners": -123,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "viewport-pan-sweep",
      "durationMs": 8159.941000000003,
      "styleRecalcs": 250,
      "styleRecalcDurationMs": 52.232,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 3920.689,
      "heapDeltaBytes": 27413944,
      "heapUsedBytes": 81063304,
      "domNodes": -259,
      "jsHeapTotalBytes": 23703552,
      "scriptDurationMs": 1388.236,
      "eventListeners": -111,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333332,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "viewport-pan-sweep",
      "durationMs": 8172.494000000029,
      "styleRecalcs": 252,
      "styleRecalcDurationMs": 54.565,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 3714.7520000000004,
      "heapDeltaBytes": 1496748,
      "heapUsedBytes": 56618892,
      "domNodes": -257,
      "jsHeapTotalBytes": 17702912,
      "scriptDurationMs": 1262.8999999999999,
      "eventListeners": -113,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333332,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "vue-large-graph-idle",
      "durationMs": 12434.883000000013,
      "styleRecalcs": 0,
      "styleRecalcDurationMs": 0,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 12419.384999999998,
      "heapDeltaBytes": -46439740,
      "heapUsedBytes": 170083448,
      "domNodes": -9848,
      "jsHeapTotalBytes": 26537984,
      "scriptDurationMs": 672.7700000000001,
      "eventListeners": -23961,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 17.219999999999953,
      "p95FrameDurationMs": 16.799999999999272
    },
    {
      "name": "vue-large-graph-idle",
      "durationMs": 12414.038000000004,
      "styleRecalcs": 0,
      "styleRecalcDurationMs": 0,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 12383.36,
      "heapDeltaBytes": -29586052,
      "heapUsedBytes": 188853268,
      "domNodes": -9850,
      "jsHeapTotalBytes": 23392256,
      "scriptDurationMs": 606.862,
      "eventListeners": -23957,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 17.779999999999927,
      "p95FrameDurationMs": 16.799999999999272
    },
    {
      "name": "vue-large-graph-idle",
      "durationMs": 12383.950000000026,
      "styleRecalcs": 0,
      "styleRecalcDurationMs": 0,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 12370.842,
      "heapDeltaBytes": -49186768,
      "heapUsedBytes": 168307020,
      "domNodes": -9850,
      "jsHeapTotalBytes": 26013696,
      "scriptDurationMs": 621.103,
      "eventListeners": -23959,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 17.223333333333358,
      "p95FrameDurationMs": 16.80000000000291
    },
    {
      "name": "vue-large-graph-pan",
      "durationMs": 14530.074000000013,
      "styleRecalcs": 69,
      "styleRecalcDurationMs": 16.60600000000001,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 14481.099,
      "heapDeltaBytes": -53456580,
      "heapUsedBytes": 162881628,
      "domNodes": -9849,
      "jsHeapTotalBytes": -15142912,
      "scriptDurationMs": 906.1719999999999,
      "eventListeners": -23953,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 17.776666666666642,
      "p95FrameDurationMs": 16.799999999999272
    },
    {
      "name": "vue-large-graph-pan",
      "durationMs": 14631.280999999944,
      "styleRecalcs": 67,
      "styleRecalcDurationMs": 16.695999999999987,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 14588.412,
      "heapDeltaBytes": -41255416,
      "heapUsedBytes": 163063312,
      "domNodes": -9846,
      "jsHeapTotalBytes": -10878976,
      "scriptDurationMs": 889.664,
      "eventListeners": -23955,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 17.776666666666642,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "vue-large-graph-pan",
      "durationMs": 14579.788000000008,
      "styleRecalcs": 67,
      "styleRecalcDurationMs": 16.693000000000012,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 14559.348,
      "heapDeltaBytes": -53185512,
      "heapUsedBytes": 162691556,
      "domNodes": -9850,
      "jsHeapTotalBytes": -15228928,
      "scriptDurationMs": 888.038,
      "eventListeners": -23953,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 17.776666666666642,
      "p95FrameDurationMs": 16.80000000000291
    },
    {
      "name": "workflow-execution",
      "durationMs": 468.845999999985,
      "styleRecalcs": 16,
      "styleRecalcDurationMs": 22.814,
      "layouts": 5,
      "layoutDurationMs": 1.3470000000000002,
      "taskDurationMs": 126.07400000000001,
      "heapDeltaBytes": 5316336,
      "heapUsedBytes": 56956308,
      "domNodes": 167,
      "jsHeapTotalBytes": 524288,
      "scriptDurationMs": 28.078000000000007,
      "eventListeners": 71,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "workflow-execution",
      "durationMs": 456.7010000000664,
      "styleRecalcs": 15,
      "styleRecalcDurationMs": 22.028,
      "layouts": 5,
      "layoutDurationMs": 1.509,
      "taskDurationMs": 122.81400000000004,
      "heapDeltaBytes": 4964000,
      "heapUsedBytes": 49913932,
      "domNodes": 154,
      "jsHeapTotalBytes": 0,
      "scriptDurationMs": 24.267000000000003,
      "eventListeners": 71,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.669999999999998,
      "p95FrameDurationMs": 16.799999999999727
    },
    {
      "name": "workflow-execution",
      "durationMs": 462.74399999992966,
      "styleRecalcs": 18,
      "styleRecalcDurationMs": 25.089999999999996,
      "layouts": 5,
      "layoutDurationMs": 1.3050000000000002,
      "taskDurationMs": 131.395,
      "heapDeltaBytes": 5152732,
      "heapUsedBytes": 49810112,
      "domNodes": 156,
      "jsHeapTotalBytes": 262144,
      "scriptDurationMs": 30.319,
      "eventListeners": 71,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.799999999999727
    }
  ]
}

@christian-byrne christian-byrne force-pushed the prompt-tab-graph-state-sync-fix branch from eae437c to 9d30ac8 Compare March 28, 2026 22:39
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (3)
src/stores/executionStore.ts (2)

262-267: ⚠️ Potential issue | 🟠 Major

Keep queued-job completion bookkeeping outside the tab filter.

execution_cached and executed only update queuedJobs[prompt_id].nodes, but the new early returns now drop that bookkeeping whenever another workflow tab is active. If the user switches back mid-run, nodesExecuted and executionProgress stay permanently stale.

Suggested fix
 function handleExecutionCached(e: CustomEvent<ExecutionCachedWsMessage>) {
-  if (!isJobForActiveWorkflow(e.detail.prompt_id)) return
-  if (!activeJob.value) return
+  const job = queuedJobs.value[e.detail.prompt_id]
+  if (!job) return
   for (const n of e.detail.nodes) {
-    activeJob.value.nodes[n] = true
+    job.nodes[n] = true
   }
 }

 function handleExecuted(e: CustomEvent<ExecutedWsMessage>) {
-  if (!isJobForActiveWorkflow(e.detail.prompt_id)) return
-  if (!activeJob.value) return
-  activeJob.value.nodes[e.detail.node] = true
+  const job = queuedJobs.value[e.detail.prompt_id]
+  if (!job) return
+  job.nodes[e.detail.node] = true
 }

Also applies to: 278-282

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/stores/executionStore.ts` around lines 262 - 267, The code in
handleExecutionCached currently early-returns based on
isJobForActiveWorkflow/activeJob and therefore skips updating the global
queuedJobs bookkeeping; update queuedJobs[prompt_id].nodes unconditionally
(before any isJobForActiveWorkflow or activeJob checks) so
queuedJobs[prompt_id].nodes is always mutated for each e.detail.nodes, then keep
the existing tab-filtered behavior only for updating activeJob.value (i.e., call
isJobForActiveWorkflow and update activeJob.value.nodes afterward). Apply the
same change to the corresponding executed handler (the block referenced at lines
278-282) so nodesExecuted/executionProgress are kept current regardless of the
active tab.

337-362: ⚠️ Potential issue | 🟠 Major

Move the active-workflow check before preview revocation.

progress_state still calls revokePreviewsByExecutionId(nodeId) before the new isJobForActiveWorkflow(jobId) guard. That means a job from another workflow can still clear previews in the current tab when execution IDs overlap.

Suggested fix
 function handleProgressState(e: CustomEvent<ProgressStateWsMessage>) {
   const { nodes, prompt_id: jobId } = e.detail
+  const isActiveWorkflowJob = isJobForActiveWorkflow(jobId)

   // Revoke previews for nodes that are starting to execute
-  const previousForJob = nodeProgressStatesByJob.value[jobId] || {}
-  for (const nodeId in nodes) {
-    const nodeState = nodes[nodeId]
-    if (nodeState.state === 'running' && !previousForJob[nodeId]) {
-      // This node just started executing, revoke its previews
-      // Note that we're doing the *actual* node id instead of the display node id
-      // here intentionally. That way, we don't clear the preview every time a new node
-      // within an expanded graph starts executing.
-      const { revokePreviewsByExecutionId } = useNodeOutputStore()
-      revokePreviewsByExecutionId(nodeId)
-    }
-  }
+  if (isActiveWorkflowJob) {
+    const previousForJob = nodeProgressStatesByJob.value[jobId] || {}
+    for (const nodeId in nodes) {
+      const nodeState = nodes[nodeId]
+      if (nodeState.state === 'running' && !previousForJob[nodeId]) {
+        const { revokePreviewsByExecutionId } = useNodeOutputStore()
+        revokePreviewsByExecutionId(nodeId)
+      }
+    }
+  }

   // Update the per-job progress map (always, regardless of active tab)
   nodeProgressStatesByJob.value = {
     ...nodeProgressStatesByJob.value,
     [jobId]: nodes
   }
   evictOldProgressJobs()

   // Only update the "current view" progress if this job belongs to the active workflow tab
-  if (!isJobForActiveWorkflow(jobId)) return
+  if (!isActiveWorkflowJob) return
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/stores/executionStore.ts` around lines 337 - 362, Move the
active-workflow guard so previews are only revoked for the active tab: inside
handleProgressState, keep the update to nodeProgressStatesByJob and the
evictOldProgressJobs call (so per-job progress is always recorded), then check
isJobForActiveWorkflow(jobId) and return early if false; only after that perform
the loop that calls revokePreviewsByExecutionId(nodeId) (from
useNodeOutputStore()) and any "current view" updates. This ensures
revokePreviewsByExecutionId, the preview-revoking loop, and current-view
progress updates run only for jobs that belong to the active workflow while
still preserving nodeProgressStatesByJob and evictOldProgressJobs behavior.
src/scripts/app.ts (1)

724-737: ⚠️ Potential issue | 🟠 Major

Don’t discard inactive-tab outputs and previews outright.

These early returns stop leakage into the current canvas, but they also bypass the only code here that persists executed outputs and preview URLs. If a run finishes while another workflow tab is active, switching back to the originating tab leaves its outputs/previews stale or missing because nothing stored those payloads for that workflow. Please route them into workflow/job-scoped state and only gate the current-canvas side effects (node.onExecuted, current preview rendering, etc.).

Also applies to: 768-786

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/scripts/app.ts` around lines 724 - 737, The handler registered on
api.addEventListener('executed') currently bails out early using
useExecutionStore().isJobForActiveWorkflow(detail.prompt_id) which prevents
persisting outputs/previews for inactive workflow tabs; change the flow so
setNodeOutputsByExecutionId(executionId, detail.output, {...}) (and any preview
URL storage) is always called to persist run results into workflow/job-scoped
state, then only gate UI/canvas side-effects (calls to
getNodeByExecutionId(this.rootGraph, executionId) and
node.onExecuted(detail.output), preview rendering, etc.) behind the
isJobForActiveWorkflow check so inactive tabs still receive stored outputs when
the user returns. Ensure the same change is applied to the similar block around
lines 768-786.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/stores/executionStore.ts`:
- Around line 626-647: The progress_text handler (handleProgressText) currently
relies on activeJobId and thus can apply updates from a job belonging to another
workflow; update handleProgressText to call isJobForActiveWorkflow(prompt_id) at
the start and return/ignore the message if it returns false, ensuring the
handler uses the incoming prompt_id rather than activeJobId to gate updates;
also apply the same isJobForActiveWorkflow(prompt_id) check to the related
progress_text handling site referenced nearby so all progress_text processing is
skipped for jobs not belonging to the active workflow.

---

Outside diff comments:
In `@src/scripts/app.ts`:
- Around line 724-737: The handler registered on
api.addEventListener('executed') currently bails out early using
useExecutionStore().isJobForActiveWorkflow(detail.prompt_id) which prevents
persisting outputs/previews for inactive workflow tabs; change the flow so
setNodeOutputsByExecutionId(executionId, detail.output, {...}) (and any preview
URL storage) is always called to persist run results into workflow/job-scoped
state, then only gate UI/canvas side-effects (calls to
getNodeByExecutionId(this.rootGraph, executionId) and
node.onExecuted(detail.output), preview rendering, etc.) behind the
isJobForActiveWorkflow check so inactive tabs still receive stored outputs when
the user returns. Ensure the same change is applied to the similar block around
lines 768-786.

In `@src/stores/executionStore.ts`:
- Around line 262-267: The code in handleExecutionCached currently early-returns
based on isJobForActiveWorkflow/activeJob and therefore skips updating the
global queuedJobs bookkeeping; update queuedJobs[prompt_id].nodes
unconditionally (before any isJobForActiveWorkflow or activeJob checks) so
queuedJobs[prompt_id].nodes is always mutated for each e.detail.nodes, then keep
the existing tab-filtered behavior only for updating activeJob.value (i.e., call
isJobForActiveWorkflow and update activeJob.value.nodes afterward). Apply the
same change to the corresponding executed handler (the block referenced at lines
278-282) so nodesExecuted/executionProgress are kept current regardless of the
active tab.
- Around line 337-362: Move the active-workflow guard so previews are only
revoked for the active tab: inside handleProgressState, keep the update to
nodeProgressStatesByJob and the evictOldProgressJobs call (so per-job progress
is always recorded), then check isJobForActiveWorkflow(jobId) and return early
if false; only after that perform the loop that calls
revokePreviewsByExecutionId(nodeId) (from useNodeOutputStore()) and any "current
view" updates. This ensures revokePreviewsByExecutionId, the preview-revoking
loop, and current-view progress updates run only for jobs that belong to the
active workflow while still preserving nodeProgressStatesByJob and
evictOldProgressJobs behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 1ead92af-bc9b-4a6b-895c-98f3ef4c6eb2

📥 Commits

Reviewing files that changed from the base of the PR and between 54a00aa and eae437c.

📒 Files selected for processing (4)
  • browser_tests/tests/tabProgressIsolation.spec.ts
  • src/scripts/app.ts
  • src/stores/executionStore.test.ts
  • src/stores/executionStore.ts

Comment thread src/stores/executionStore.ts
…aking

Add isJobForActiveWorkflow() to executionStore that checks whether an
incoming WS message's prompt_id belongs to the currently active workflow
tab. Gate all visual-state handlers (executed, execution_cached,
progress, progress_state, b_preview_with_metadata) so outputs, previews,
and progress indicators only apply to the workflow the user is viewing.

The fix leverages the existing jobIdToSessionWorkflowPath map (populated
at queue time by storeJob()) and workflowStore.activeWorkflow.path to
determine whether a job belongs to the active tab.

Lifecycle events (execution_start, execution_success, execution_error,
execution_interrupted) are NOT filtered — they manage global state
(activeJobId, queue status) that needs to update regardless of which
tab is active.

Graceful degradation: returns true (permissive) when prompt_id is
missing, job is unknown, or no active workflow — so legacy messages and
jobs from before this session continue to work.

Closes #3377
Closes #2660
Closes #1293
Closes #3856
Closes #10688
@christian-byrne christian-byrne force-pushed the prompt-tab-graph-state-sync-fix branch from 9d30ac8 to 4f505dc Compare March 28, 2026 22:51
@dosubot dosubot Bot added size:L This PR changes 100-499 lines, ignoring generated files. and removed size:XL This PR changes 500-999 lines, ignoring generated files. labels Mar 28, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@browser_tests/tests/tabProgressIsolation.spec.ts`:
- Around line 30-41: The test's page.evaluate callback silently returns when the
test bridge is missing (__pinia or __executionStore), causing false positives;
update the code so it fails fast by asserting the bridge and throwing if
missing: inside the evaluate callback (used in comfyPage.page.evaluate) check
for (window as any).__pinia and (window as any).__executionStore and throw a
descriptive error if absent, or better add a helper method on comfyPage (e.g.,
comfyPage.assertBridgeInstalled) that performs this check and call it before
running ensureSessionWorkflowPath; reference the functions/objects __pinia,
__executionStore, ensureSessionWorkflowPath and comfyPage to locate where to add
the assertion/throw.

In `@src/stores/executionStore.ts`:
- Around line 354-363: The current-view progress map nodeProgressStates is only
updated when a matching progress_state arrives, so switching workflow tabs can
display stale progress; add logic to sync or clear nodeProgressStates whenever
the active workflow changes: on workflowStore.activeWorkflow?.path change, look
up the latest entry in nodeProgressStatesByJob (using the same job-to-workflow
matching logic as isJobForActiveWorkflow) and set nodeProgressStates to that
job's nodes, or clear nodeProgressStates if no matching job exists; implement
this by adding a watcher/reactive effect tied to
workflowStore.activeWorkflow?.path that calls the existing
evictOldProgressJobs/isJobForActiveWorkflow helpers to find and hydrate/clear
nodeProgressStates.
- Around line 263-264: handleProgressText and related progress_text handlers
still gate on the global activeJobId/activeJob instead of using the
workflow-scoped guard; update handleProgressText to call
isJobForActiveWorkflow(e.detail.prompt_id) (or include that check alongside the
existing activeJob checks) before applying progress_text so previews from
background jobs can't appear in the wrong workflow when node IDs overlap, and
make the same change in the other progress handlers referenced (the other
handlers that currently check activeJobId/activeJob) to use
isJobForActiveWorkflow for consistent workflow-scoped filtering.
- Around line 638-646: isJobForActiveWorkflow currently returns true when
jobIdToSessionWorkflowPath lacks a mapping, allowing unknown jobs to bypass the
tab filter; change its logic so a missing mapping is treated as not active
(return false) instead of true. Additionally, create a pre-response association
for new prompts by setting jobIdToSessionWorkflowPath.value.set(promptId,
workflowStore.activeWorkflow?.path) before awaiting the API call that creates
the job (the place currently invoking storeJob after await
api.queuePrompt(...)), and ensure you remove or update that temporary mapping on
API error or when the true mapping arrives (handleExecutionStart/storeJob) so
the race no longer updates the wrong active workflow.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 7be627f4-fa18-4e0f-94ea-82c0c5c6ff7f

📥 Commits

Reviewing files that changed from the base of the PR and between 65f18d1 and 9d30ac8.

📒 Files selected for processing (4)
  • browser_tests/tests/tabProgressIsolation.spec.ts
  • src/scripts/app.ts
  • src/stores/executionStore.test.ts
  • src/stores/executionStore.ts

Comment thread browser_tests/tests/tabProgressIsolation.spec.ts Outdated
Comment thread src/stores/executionStore.ts
Comment thread src/stores/executionStore.ts
Comment thread src/stores/executionStore.ts
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (3)
src/stores/executionStore.ts (3)

626-647: ⚠️ Potential issue | 🟠 Major

progress_text still bypasses the new workflow-path guard.

Line 529 still compares prompt_id with activeJobId instead of using isJobForActiveWorkflow(). After a tab switch mid-run, text previews can still render on same-ID nodes in the current workflow even though the other visual-state handlers are filtered.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/stores/executionStore.ts` around lines 626 - 647, The progress_text
handler currently compares prompt_id to activeJobId and bypasses the
workflow-path guard; update the handler to use the existing
isJobForActiveWorkflow(prompt_id) helper instead of comparing to activeJobId so
progress_text messages are filtered by workflow path like other visual-state
handlers (locate the progress_text message handler and replace the prompt_id ===
activeJobId check with a call to isJobForActiveWorkflow(prompt_id)).

354-374: ⚠️ Potential issue | 🟠 Major

Switching tabs still leaves the previous workflow's progress snapshot live.

Lines 363 and 379 stop future writes from the wrong job, but they never clear or rehydrate nodeProgressStates / _executingNodeProgress when workflowStore.activeWorkflow?.path changes. After switching from workflow A to workflow B, the UI can keep showing A's last progress until B emits its own update.

Also applies to: 377-379

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/stores/executionStore.ts` around lines 354 - 374, The current logic
updates nodeProgressStatesByJob and only prevents future writes for non-active
jobs via isJobForActiveWorkflow, but never clears or rehydrates
nodeProgressStates/_executingNodeProgress when the active workflow tab changes;
add logic so that when workflowStore.activeWorkflow?.path (or the active job id)
changes you set nodeProgressStates to the saved snapshot for that new active job
from nodeProgressStatesByJob (or to an empty object if none), and reset
_executingNodeProgress to undefined if there is no executing node for the new
job; implement this by watching the active workflow/job change (or adding a
small rehydrate helper invoked when isJobForActiveWorkflow would flip) and call
the existing evictOldProgressJobs as needed to keep state consistent.

638-646: ⚠️ Potential issue | 🟠 Major

Unmapped jobs still leak through during the WebSocket-before-HTTP window.

Lines 254-259 already note that execution_start can beat storeJob(), but Lines 642-643 return true when jobIdToSessionWorkflowPath has no entry. In that window, the first progress / executed / progress_state messages for a newly queued job can still update whichever workflow is open. Pre-seed the mapping before awaiting queuePrompt(), or stop treating an unmapped prompt_id as active once execution_start has been seen.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/stores/executionStore.ts` around lines 638 - 646, The bug is that
isJobForActiveWorkflow treats an unmapped promptId as active, allowing messages
to leak during the WebSocket-before-HTTP window; fix by pre-seeding
jobIdToSessionWorkflowPath for the promptId before awaiting queuePrompt() (or
any async handoff) so the mapping exists when execution_start/progress events
arrive: in the code that calls queuePrompt()/storeJob(), add
jobIdToSessionWorkflowPath.value.set(promptId,
workflowStore.activeWorkflow?.path) immediately when scheduling the job (and
only remove/update it on finalization), or alternatively change
isJobForActiveWorkflow to return false for unmapped promptIds once an
execution_start marker for that prompt has been observed (use the existing
execution_start tracking state) so unmapped ids are not treated as active.
Ensure you reference jobIdToSessionWorkflowPath, isJobForActiveWorkflow,
queuePrompt()/storeJob(), and workflowStore.activeWorkflow when making the
change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/stores/executionStore.ts`:
- Around line 354-362: The revokePreviewsByExecutionId call is happening before
you check isJobForActiveWorkflow(jobId), causing previews for the current tab to
be revoked by background jobs; move the revokePreviewsByExecutionId(executionId)
call so it runs only after the isJobForActiveWorkflow(jobId) guard (i.e., inside
the same block that updates the "current view" progress) and keep the per-job
state update and evictOldProgressJobs() unchanged (update
nodeProgressStatesByJob, call evictOldProgressJobs(), then if
isJobForActiveWorkflow(jobId) { revokePreviewsByExecutionId(...); /* update
current view state as before */ }).

---

Duplicate comments:
In `@src/stores/executionStore.ts`:
- Around line 626-647: The progress_text handler currently compares prompt_id to
activeJobId and bypasses the workflow-path guard; update the handler to use the
existing isJobForActiveWorkflow(prompt_id) helper instead of comparing to
activeJobId so progress_text messages are filtered by workflow path like other
visual-state handlers (locate the progress_text message handler and replace the
prompt_id === activeJobId check with a call to
isJobForActiveWorkflow(prompt_id)).
- Around line 354-374: The current logic updates nodeProgressStatesByJob and
only prevents future writes for non-active jobs via isJobForActiveWorkflow, but
never clears or rehydrates nodeProgressStates/_executingNodeProgress when the
active workflow tab changes; add logic so that when
workflowStore.activeWorkflow?.path (or the active job id) changes you set
nodeProgressStates to the saved snapshot for that new active job from
nodeProgressStatesByJob (or to an empty object if none), and reset
_executingNodeProgress to undefined if there is no executing node for the new
job; implement this by watching the active workflow/job change (or adding a
small rehydrate helper invoked when isJobForActiveWorkflow would flip) and call
the existing evictOldProgressJobs as needed to keep state consistent.
- Around line 638-646: The bug is that isJobForActiveWorkflow treats an unmapped
promptId as active, allowing messages to leak during the WebSocket-before-HTTP
window; fix by pre-seeding jobIdToSessionWorkflowPath for the promptId before
awaiting queuePrompt() (or any async handoff) so the mapping exists when
execution_start/progress events arrive: in the code that calls
queuePrompt()/storeJob(), add jobIdToSessionWorkflowPath.value.set(promptId,
workflowStore.activeWorkflow?.path) immediately when scheduling the job (and
only remove/update it on finalization), or alternatively change
isJobForActiveWorkflow to return false for unmapped promptIds once an
execution_start marker for that prompt has been observed (use the existing
execution_start tracking state) so unmapped ids are not treated as active.
Ensure you reference jobIdToSessionWorkflowPath, isJobForActiveWorkflow,
queuePrompt()/storeJob(), and workflowStore.activeWorkflow when making the
change.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: ce9605d2-f254-432a-ab12-3daca164f31a

📥 Commits

Reviewing files that changed from the base of the PR and between 9d30ac8 and 4f505dc.

📒 Files selected for processing (3)
  • src/scripts/app.ts
  • src/stores/executionStore.test.ts
  • src/stores/executionStore.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/scripts/app.ts

Comment thread src/stores/executionStore.ts
Moves revokePreviewsByExecutionId behind the tab filter so background
jobs with overlapping node IDs cannot clear previews in the currently
viewed workflow.

Addresses review feedback:
#10689 (comment)
Adds a watcher on workflowStore.activeWorkflow?.path that syncs the
"current view" nodeProgressStates from nodeProgressStatesByJob when
the user switches tabs, preventing stale progress from the previous
workflow from being displayed.

Addresses review feedback:
#10689 (comment)
Adds tests for:
- nodeProgressStates rehydration from nodeProgressStatesByJob on tab switch
- nodeProgressStates clearing when switching to workflow with no jobs
- Makes mockActiveWorkflow a Vue ref so watchers fire in tests

Covers review feedback:
#10689 (comment)
#10689 (comment)
@christian-byrne christian-byrne self-assigned this Mar 29, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/stores/executionStore.test.ts`:
- Around line 1003-1017: The test pins nodeProgressStates to workflow-A after
switching mockActiveWorkflow to workflow B; fix by making the assertion
consistent with the intended behavior: either (A) do not switch the active tab —
keep mockActiveWorkflow.value pointing to '/workflow-a' before firing
fireEvent('progress_state', ...) so
expect(store.nodeProgressStates['node-1']?.value).toBe(5) remains valid, or (B)
if you want to test the switch, await the rehydration/flush (e.g., await
nextTick()/flushPromises) after setting mockActiveWorkflow.value = { path:
'/workflow-b' }, then fireEvent('progress_state', { prompt_id: 'job-a', nodes:
nodesA2 }) and assert that nodeProgressStatesByJob (or
store.nodeProgressStatesByJob['job-a']) updated but store.nodeProgressStates for
'node-1' did not change (or is cleared) — update the expectation to reference
nodeProgressStatesByJob when verifying progress-by-job and nodeProgressStates
when verifying the active-workflow view.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: d5f37985-f05f-4651-a939-23c9b10cf884

📥 Commits

Reviewing files that changed from the base of the PR and between 4f505dc and 4730a53.

📒 Files selected for processing (2)
  • src/stores/executionStore.test.ts
  • src/stores/executionStore.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/stores/executionStore.ts

Comment thread src/stores/executionStore.test.ts
Copy link
Copy Markdown
Contributor Author

@christian-byrne christian-byrne left a comment

Choose a reason for hiding this comment

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

There is issue that if we are maintaining a mapping from job id -> workflow name, then we need a way to persist that in session-specific memory so that it survives across reload / tab suspension / etc. Otherwise the mapping won't exist and we wont know what workflow the message should be applied to. If we default to showing when we don't know, then cross-window/browsertab will still leak. If we try to re-build the mapping from the /jobs response that is shared across all clients, it becomes a bit of a perf bottleneck and also still has error cases when the job is not returned yet in the /jobs response but messages are already being sent.

@christian-byrne christian-byrne marked this pull request as draft March 29, 2026 23:08
christian-byrne and others added 2 commits March 29, 2026 17:40
…inter

Extracts a reusable SessionTabMap utility that persists a Map<string, string>
to sessionStorage scoped per browser tab (keyed by window.name clientId).
Handles clientId rotation on WS reconnect via prefix-scan migration.

Replaces the in-memory-only jobIdToSessionWorkflowPath with a persisted
version so the job→workflow mapping survives page reloads, preventing
cross-tab progress leaking after refresh.

Addresses review feedback:
#10689 (comment)
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/stores/executionStore.ts`:
- Around line 633-650: When switching active workflow in the watcher (the
function watching workflowStore.activeWorkflow?.path) you must also clear or
update _executingNodeProgress to avoid showing stale progress from the previous
job; locate the same watcher that currently sets nodeProgressStates.value using
nodeProgressStatesByJob.value and jobIdToSessionWorkflowPath.value and ensure
_executingNodeProgress is either set to the matching job's executing progress
object or reset to an empty/default state whenever nodeProgressStates.value is
reset (including the early return when !newPath and the fallback that sets
nodeProgressStates.value = {}).

In `@src/utils/sessionTabMap.ts`:
- Around line 10-13: Guard the maxEntries parameter in createSessionTabMap to
prevent negative values causing an infinite eviction loop: validate or normalize
maxEntries (e.g., coerce to an integer and clamp to a minimum of 0 or throw for
negative inputs) before entering the eviction logic, and update the eviction
condition to ensure it only attempts deletions while map.size > maxEntries and
map.size > 0 (so deleting undefined cannot loop forever); reference
createSessionTabMap, the maxEntries parameter, the eviction loop that deletes
the oldest key (using map.keys().next().value), and the SessionTabMap return to
locate and fix the code.
- Around line 61-73: The migrate function currently removes the legacy
sessionStorage key before safely parsing and persisting its contents; change
migrate so it first reads and JSON.parse()s the value into a Map (inside a
try/catch), only call sessionStorage.removeItem(existingKey) after parsing
succeeds and after persist(prefix, migrated) completes, and if parsing throws
just continue (do not remove the legacy key); keep the function signature and
return behavior (return migrated Map on success, continue loop on failure) and
reference the symbols migrate, existingKey, persist, and sessionStorage when
making the change.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: dc186fc4-5303-4446-907b-0205bb5cf130

📥 Commits

Reviewing files that changed from the base of the PR and between 4730a53 and fb96e64.

📒 Files selected for processing (3)
  • src/stores/executionStore.ts
  • src/utils/sessionTabMap.test.ts
  • src/utils/sessionTabMap.ts

Comment thread src/stores/executionStore.ts
Comment thread src/utils/sessionTabMap.ts
Comment thread src/utils/sessionTabMap.ts
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
src/utils/sessionTabMap.test.ts (1)

151-165: Minor: test title is slightly imprecise.

The title says "on each set call" but the no-op test (lines 28-35) demonstrates that identical key/value pairs do not produce a new reference. Consider a more precise title.

✏️ Suggested title refinement
   describe('reactivity', () => {
-    it('produces a new Map reference on each set call', () => {
+    it('produces a new Map reference on each effective set', () => {
       const { map, set } = createSessionTabMap(PREFIX)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/utils/sessionTabMap.test.ts` around lines 151 - 165, Test title is
imprecise; update the description string of the test in the
describe('reactivity') block for createSessionTabMap so it reflects that a new
Map reference is produced only when the set call actually changes data (e.g.,
"produces a new Map reference when set changes data" or "produces a new Map
reference on mutating set calls"), and keep references to map.value and
set('key','value') unchanged so the test still asserts that identical key/value
no-ops do not produce a new reference.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/utils/sessionTabMap.test.ts`:
- Around line 151-165: Test title is imprecise; update the description string of
the test in the describe('reactivity') block for createSessionTabMap so it
reflects that a new Map reference is produced only when the set call actually
changes data (e.g., "produces a new Map reference when set changes data" or
"produces a new Map reference on mutating set calls"), and keep references to
map.value and set('key','value') unchanged so the test still asserts that
identical key/value no-ops do not produce a new reference.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 0d15d993-0138-4009-9146-1817e8e89d51

📥 Commits

Reviewing files that changed from the base of the PR and between fb96e64 and de6eb5a.

📒 Files selected for processing (1)
  • src/utils/sessionTabMap.test.ts

@christian-byrne christian-byrne self-assigned this Apr 6, 2026
@christian-byrne christian-byrne marked this pull request as ready for review April 10, 2026 06:38
@dosubot dosubot Bot added size:XL This PR changes 500-999 lines, ignoring generated files. and removed size:L This PR changes 100-499 lines, ignoring generated files. labels Apr 10, 2026
@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 10, 2026

Codecov Report

❌ Patch coverage is 93.97590% with 5 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/scripts/app.ts 0.00% 2 Missing ⚠️
src/stores/executionStore.ts 94.44% 2 Missing ⚠️
src/utils/sessionTabMap.ts 97.77% 1 Missing ⚠️
@@             Coverage Diff             @@
##             main   #10689       +/-   ##
===========================================
- Coverage   64.80%   45.56%   -19.24%     
===========================================
  Files        1459     1352      -107     
  Lines       84898    69593    -15305     
  Branches    23628    19345     -4283     
===========================================
- Hits        55015    31709    -23306     
- Misses      28902    37286     +8384     
+ Partials      981      598      -383     
Flag Coverage Δ
e2e ?
unit 45.56% <93.97%> (+1.29%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
src/utils/sessionTabMap.ts 97.77% <97.77%> (ø)
src/scripts/app.ts 11.04% <0.00%> (-59.42%) ⬇️
src/stores/executionStore.ts 58.18% <94.44%> (-22.09%) ⬇️

... and 1029 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@dosubot dosubot Bot removed the size:XL This PR changes 500-999 lines, ignoring generated files. label Apr 16, 2026
@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 16, 2026

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

Project Deployment Actions Updated (UTC)
website-frontend Ready Ready Preview, Comment Apr 19, 2026 2:30am

Request Review

@dosubot dosubot Bot added the size:L This PR changes 100-499 lines, ignoring generated files. label Apr 16, 2026
@christian-byrne
Copy link
Copy Markdown
Contributor Author

Superseded by the simpler workflow_id-on-message approach:

Backend

Frontend consumer

Closing in favor of routing by workflow_id directly, which removes the session-mapping machinery introduced here.

christian-byrne added a commit that referenced this pull request May 3, 2026
Closes the remaining cross-tab leak surface that #10689 also targeted:

- handleExecutionCached / handleExecuted (executionStore): write per-job
  via prompt_id instead of via activeJob, so a job from another workflow
  tab can no longer mark canvas nodes on the active job by mistake.
- 'executed' listener (app.ts): always persist outputs into the job-scoped
  node-output store; gate node.onExecuted (canvas-visible side effect)
  behind messageMatchesActiveWorkflow so node IDs from another workflow
  cannot fire onExecuted on the wrong canvas node.
- 'b_preview_with_metadata' listener (app.ts): always store the preview
  URL in per-job state so the originating tab can show it on return;
  gate revokePreviewsByExecutionId / setNodePreviewsByExecutionId behind
  messageMatchesActiveWorkflow so previews don't render on the wrong
  canvas. api.ts: forward optional workflow_id from binary preview
  metadata.
- executionStore: expose messageMatchesActiveWorkflow on the store
  surface for app.ts.
christian-byrne added a commit that referenced this pull request May 3, 2026
Closes the remaining cross-tab leak surface that #10689 also targeted:

- handleExecutionCached / handleExecuted (executionStore): write per-job
  via prompt_id instead of via activeJob, so a job from another workflow
  tab can no longer mark canvas nodes on the active job by mistake.
- 'executed' listener (app.ts): always persist outputs into the job-scoped
  node-output store; gate node.onExecuted (canvas-visible side effect)
  behind messageMatchesActiveWorkflow so node IDs from another workflow
  cannot fire onExecuted on the wrong canvas node.
- 'b_preview_with_metadata' listener (app.ts): always store the preview
  URL in per-job state so the originating tab can show it on return;
  gate revokePreviewsByExecutionId / setNodePreviewsByExecutionId behind
  messageMatchesActiveWorkflow so previews don't render on the wrong
  canvas. api.ts: forward optional workflow_id from binary preview
  metadata.
- executionStore: expose messageMatchesActiveWorkflow on the store
  surface for app.ts.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L This PR changes 100-499 lines, ignoring generated files.

Projects

None yet

4 participants