Skip to content

fix: guard workspace auth refresh races#11726

Open
benceruleanlu wants to merge 3 commits intomainfrom
ben/fe-485-workspace-auth-refresh-race-v2
Open

fix: guard workspace auth refresh races#11726
benceruleanlu wants to merge 3 commits intomainfrom
ben/fe-485-workspace-auth-refresh-race-v2

Conversation

@benceruleanlu
Copy link
Copy Markdown
Member

@benceruleanlu benceruleanlu commented Apr 28, 2026

Summary

Fixes FE-485.

This updates workspace auth refresh handling so stale in-flight refresh responses cannot overwrite a newer workspace context, and exhausted transient token exchange failures preserve the existing workspace context while its token is still valid.

Changes

  • Add commit-time request-id guards before switchWorkspace writes workspace state, workspace token, error, or sessionStorage.
  • Track the current workspace token expiry in memory and use it to distinguish safe transient refresh failures from failures that must clear context.
  • Convert the stale refresh race coverage from expected-failing to a normal passing regression test.
  • Update transient retry coverage to assert valid context and sessionStorage preservation.

Browser / E2E coverage

No Playwright test was added because this bug is in the Pinia store race between mocked token-exchange promises, request IDs, token expiry, and sessionStorage commits. The deterministic unit spec directly controls the ordering that is not practical to force through the browser without real auth/session infrastructure and artificial network timing hooks.

Validation

  • pnpm format -- src/platform/workspace/stores/workspaceAuthStore.ts src/platform/workspace/stores/useWorkspaceAuth.test.ts
  • pnpm exec vitest run src/platform/workspace/stores/useWorkspaceAuth.test.ts
  • pnpm exec eslint src/platform/workspace/stores/workspaceAuthStore.ts src/platform/workspace/stores/useWorkspaceAuth.test.ts
  • pnpm exec oxlint src/platform/workspace/stores/workspaceAuthStore.ts src/platform/workspace/stores/useWorkspaceAuth.test.ts --type-aware
  • pnpm exec vue-tsc --noEmit --pretty false

┆Issue is synchronized with this Notion page by Unito

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 28, 2026

📝 Walkthrough

Walkthrough

Adds token expiry tracking, schedules refresh retries based on remaining lifetime, prevents commits from stale in-flight refreshes, and changes retry exhaustion behavior to preserve valid workspace tokens on transient errors. Tests updated to assert preserved state and improve sessionStorage resilience.

Changes

Cohort / File(s) Summary
Workspace Auth Store Implementation
src/platform/workspace/stores/workspaceAuthStore.ts
Adds workspaceTokenExpiresAt and hasValidWorkspaceToken(). Introduces scheduleTokenRefreshRetry() to defer refreshes based on remaining lifetime. Tracks refreshRequestId to detect stale in-flight requests and early-return to avoid committing stale results. On transient TOKEN_EXCHANGE_FAILED, preserves existing valid workspace token and schedules follow-up retries instead of clearing context. clearWorkspaceContext() now clears expiry state.
Workspace Auth Store Tests
src/platform/workspace/stores/useWorkspaceAuth.test.ts
Updates retry/race tests to assert preservation of currentWorkspace/workspaceToken and sessionStorage after exhausted transient errors. Narrows timer advances to only backoff delays to avoid proactive refresh interference. Converts stale in-flight refresh race from expected-failure to passing test with assertions that newer state isn't overwritten. Replaces spying on sessionStorage.setItem with stubbing globalThis.sessionStorage using a Storage-compatible wrapper that throws, ensuring restoration in a try/finally.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant WorkspaceAuthStore
    participant SessionStorage
    participant Timer
    Client->>WorkspaceAuthStore: trigger refreshToken()
    WorkspaceAuthStore->>Timer: schedule retry/backoff (scheduleTokenRefreshRetry)
    Timer-->>WorkspaceAuthStore: retry delay elapsed
    WorkspaceAuthStore->>WorkspaceAuthStore: check refreshRequestId (stale?) and hasValidWorkspaceToken()
    alt request is current
        WorkspaceAuthStore->>SessionStorage: set CURRENT_WORKSPACE / TOKEN / EXPIRES
        WorkspaceAuthStore-->>Client: commit success
    else stale or transient TOKEN_EXCHANGE_FAILED
        WorkspaceAuthStore-->>Timer: schedule another retry (if token still valid)
        WorkspaceAuthStore-->>Client: preserve current state / do not clear
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Poem

🐰 I nibbled tokens in the night,
Kept expiry safe and retries light,
Stale hops stopped mid-air, not thrown,
sessionStorage guarded, gently sewn,
A little rabbit hums: the workspace stays bright.

🚥 Pre-merge checks | ✅ 6 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (6 passed)
Check name Status Explanation
Title check ✅ Passed The title 'fix: guard workspace auth refresh races' accurately summarizes the main change: adding guards to prevent race conditions in workspace auth refresh logic.
Description check ✅ Passed The description includes Summary, Changes, Browser/E2E coverage rationale, and Validation sections. It is well-structured, covers the key modifications, and provides appropriate context.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
End-To-End Regression Coverage For Fixes ✅ Passed PR provides concrete explanation that deterministic unit specs cover Pinia store race conditions, satisfying the check requirement.
Adr Compliance For Entity/Litegraph Changes ✅ Passed PR modifies only files under src/platform/workspace/stores/, which are unrelated to litegraph, ECS, or graph entities. ADR compliance check does not apply.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ben/fe-485-workspace-auth-refresh-race-v2

Warning

Review ran into problems

🔥 Problems

Git: Failed to clone repository. Please run the @coderabbitai full review command to re-trigger a full review. If the issue persists, set path_filters to include or exclude specific files.


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

❤️ Share

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

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 28, 2026

🎨 Storybook: ✅ Built — View Storybook

Details

⏰ Completed at: 04/28/2026, 03:19:19 PM UTC

Links

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 28, 2026

🎭 Playwright: ✅ 1399 passed, 0 failed

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

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 28, 2026

📦 Bundle: 5.23 MB gzip 🔴 +362 B

Details

Summary

  • Raw size: 24.1 MB baseline 24.1 MB — 🔴 +1.38 kB
  • Gzip: 5.23 MB baseline 5.23 MB — 🔴 +362 B
  • Brotli: 4.05 MB baseline 4.05 MB — 🔴 +438 B
  • Bundles: 257 current • 257 baseline • 118 added / 118 removed

Category Glance
Data & Services 🔴 +1.38 kB (3.04 MB) · Vendor & Third-Party ⚪ 0 B (9.88 MB) · Other ⚪ 0 B (8.83 MB) · Graph Workspace ⚪ 0 B (1.24 MB) · Panels & Settings ⚪ 0 B (484 kB) · Utilities & Hooks ⚪ 0 B (363 kB) · + 5 more

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

Main entry bundles and manifests

File Before After Δ Raw Δ Gzip Δ Brotli
assets/index-BPr0qkpz.js (new) 22.5 kB 🔴 +22.5 kB 🔴 +7.99 kB 🔴 +6.85 kB
assets/index-Ds9S8TeI.js (removed) 22.5 kB 🟢 -22.5 kB 🟢 -7.99 kB 🟢 -6.85 kB

Status: 1 added / 1 removed

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

Graph editor runtime, canvas, workflow orchestration

File Before After Δ Raw Δ Gzip Δ Brotli
assets/GraphView-BR3WDTCB.js (removed) 1.24 MB 🟢 -1.24 MB 🟢 -264 kB 🟢 -199 kB
assets/GraphView-CHECwXC2.js (new) 1.24 MB 🔴 +1.24 MB 🔴 +264 kB 🔴 +199 kB

Status: 1 added / 1 removed

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

Top-level views, pages, and routed surfaces

File Before After Δ Raw Δ Gzip Δ Brotli
assets/CloudSurveyView-BIIYZ7nd.js (removed) 15.7 kB 🟢 -15.7 kB 🟢 -3.41 kB 🟢 -2.92 kB
assets/CloudSurveyView-BWOoS0vl.js (new) 15.7 kB 🔴 +15.7 kB 🔴 +3.41 kB 🔴 +2.92 kB
assets/CloudLoginView-Bj5lFXYq.js (new) 12.2 kB 🔴 +12.2 kB 🔴 +3.43 kB 🔴 +3.03 kB
assets/CloudLoginView-Mo--dWWn.js (removed) 12.2 kB 🟢 -12.2 kB 🟢 -3.43 kB 🟢 -3.04 kB
assets/CloudSignupView-B3l7fMfC.js (removed) 9.94 kB 🟢 -9.94 kB 🟢 -2.93 kB 🟢 -2.57 kB
assets/CloudSignupView-DVheBE2o.js (new) 9.94 kB 🔴 +9.94 kB 🔴 +2.93 kB 🔴 +2.58 kB
assets/UserCheckView-BCHC-0NV.js (removed) 9.07 kB 🟢 -9.07 kB 🟢 -2.33 kB 🟢 -2.05 kB
assets/UserCheckView-C-rTGGTu.js (new) 9.07 kB 🔴 +9.07 kB 🔴 +2.33 kB 🔴 +2.05 kB
assets/CloudLayoutView-CxFX7or1.js (new) 7.7 kB 🔴 +7.7 kB 🔴 +2.43 kB 🔴 +2.13 kB
assets/CloudLayoutView-CZNhsaWc.js (removed) 7.7 kB 🟢 -7.7 kB 🟢 -2.43 kB 🟢 -2.1 kB
assets/CloudForgotPasswordView-D8HOivff.js (new) 6.11 kB 🔴 +6.11 kB 🔴 +2.17 kB 🔴 +1.91 kB
assets/CloudForgotPasswordView-DoIuP4uj.js (removed) 6.11 kB 🟢 -6.11 kB 🟢 -2.17 kB 🟢 -1.92 kB
assets/CloudAuthTimeoutView-CBrO2NTO.js (removed) 5.47 kB 🟢 -5.47 kB 🟢 -2 kB 🟢 -1.76 kB
assets/CloudAuthTimeoutView-DUtVf676.js (new) 5.47 kB 🔴 +5.47 kB 🔴 +2 kB 🔴 +1.77 kB
assets/CloudSubscriptionRedirectView-DMJQCeuP.js (new) 5.25 kB 🔴 +5.25 kB 🔴 +1.99 kB 🔴 +1.76 kB
assets/CloudSubscriptionRedirectView-w4ugEFYk.js (removed) 5.25 kB 🟢 -5.25 kB 🟢 -1.98 kB 🟢 -1.76 kB
assets/UserSelectView-0vUXwkO0.js (removed) 4.73 kB 🟢 -4.73 kB 🟢 -1.75 kB 🟢 -1.55 kB
assets/UserSelectView-CYw3DIdd.js (new) 4.73 kB 🔴 +4.73 kB 🔴 +1.75 kB 🔴 +1.55 kB

Status: 9 added / 9 removed / 2 unchanged

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

Configuration panels, inspectors, and settings screens

File Before After Δ Raw Δ Gzip Δ Brotli
assets/KeybindingPanel-C8IKmJGp.js (new) 46.7 kB 🔴 +46.7 kB 🔴 +9.61 kB 🔴 +8.53 kB
assets/KeybindingPanel-Yf3AKbSI.js (removed) 46.7 kB 🟢 -46.7 kB 🟢 -9.6 kB 🟢 -8.53 kB
assets/SecretsPanel-CUY7X65h.js (removed) 22.9 kB 🟢 -22.9 kB 🟢 -5.54 kB 🟢 -4.88 kB
assets/SecretsPanel-MGKJcsf0.js (new) 22.9 kB 🔴 +22.9 kB 🔴 +5.54 kB 🔴 +4.87 kB
assets/LegacyCreditsPanel-BoqpJnIC.js (new) 21.7 kB 🔴 +21.7 kB 🔴 +5.89 kB 🔴 +5.22 kB
assets/LegacyCreditsPanel-VfEs5qfp.js (removed) 21.7 kB 🟢 -21.7 kB 🟢 -5.89 kB 🟢 -5.19 kB
assets/SubscriptionPanel-BvZUbrDT.js (removed) 19.9 kB 🟢 -19.9 kB 🟢 -5.06 kB 🟢 -4.46 kB
assets/SubscriptionPanel-Coc1cPn2.js (new) 19.9 kB 🔴 +19.9 kB 🔴 +5.06 kB 🔴 +4.45 kB
assets/AboutPanel-CVCG7O7y.js (removed) 12 kB 🟢 -12 kB 🟢 -3.33 kB 🟢 -2.98 kB
assets/AboutPanel-DiX16bSC.js (new) 12 kB 🔴 +12 kB 🔴 +3.33 kB 🔴 +2.98 kB
assets/ExtensionPanel-C2jC5Qv0.js (removed) 9.94 kB 🟢 -9.94 kB 🟢 -2.9 kB 🟢 -2.56 kB
assets/ExtensionPanel-CJ2TC6BL.js (new) 9.94 kB 🔴 +9.94 kB 🔴 +2.9 kB 🔴 +2.58 kB
assets/ServerConfigPanel-CIxP3u_7.js (removed) 7.02 kB 🟢 -7.02 kB 🟢 -2.35 kB 🟢 -2.1 kB
assets/ServerConfigPanel-CZZ6v0HT.js (new) 7.02 kB 🔴 +7.02 kB 🔴 +2.35 kB 🔴 +2.1 kB
assets/UserPanel-C4qNlCJj.js (new) 6.73 kB 🔴 +6.73 kB 🔴 +2.23 kB 🔴 +1.96 kB
assets/UserPanel-CVcitOhG.js (removed) 6.73 kB 🟢 -6.73 kB 🟢 -2.22 kB 🟢 -1.96 kB
assets/cloudRemoteConfig-DxUKr5OI.js (removed) 2.02 kB 🟢 -2.02 kB 🟢 -974 B 🟢 -839 B
assets/cloudRemoteConfig-LyHL0B2E.js (new) 2.02 kB 🔴 +2.02 kB 🔴 +977 B 🔴 +835 B
assets/refreshRemoteConfig-DkSVUWtj.js (removed) 1.45 kB 🟢 -1.45 kB 🟢 -648 B 🟢 -554 B
assets/refreshRemoteConfig-F75RJ1oX.js (new) 1.45 kB 🔴 +1.45 kB 🔴 +650 B 🔴 +557 B

Status: 10 added / 10 removed / 11 unchanged

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

Authentication, profile, and account management bundles

File Before After Δ Raw Δ Gzip Δ Brotli
assets/auth-1GVOvNFG.js (removed) 3.57 kB 🟢 -3.57 kB 🟢 -1.26 kB 🟢 -1.07 kB
assets/auth-Cn4kL8oW.js (new) 3.57 kB 🔴 +3.57 kB 🔴 +1.26 kB 🔴 +1.07 kB
assets/SignUpForm-DBMDInZi.js (removed) 3.16 kB 🟢 -3.16 kB 🟢 -1.29 kB 🟢 -1.17 kB
assets/SignUpForm-nSGZI8hs.js (new) 3.16 kB 🔴 +3.16 kB 🔴 +1.29 kB 🔴 +1.15 kB
assets/UpdatePasswordContent-C_LdLpfD.js (new) 2.87 kB 🔴 +2.87 kB 🔴 +1.29 kB 🔴 +1.14 kB
assets/UpdatePasswordContent-C2vp1KCR.js (removed) 2.87 kB 🟢 -2.87 kB 🟢 -1.29 kB 🟢 -1.14 kB
assets/authStore-Bt-RtD4m.js (removed) 1.16 kB 🟢 -1.16 kB 🟢 -554 B 🟢 -494 B
assets/authStore-D9BQNkPN.js (new) 1.16 kB 🔴 +1.16 kB 🔴 +556 B 🔴 +498 B
assets/auth-BeKev9ac.js (removed) 348 B 🟢 -348 B 🟢 -216 B 🟢 -191 B
assets/auth-BP7_re5W.js (new) 348 B 🔴 +348 B 🔴 +215 B 🔴 +189 B

Status: 5 added / 5 removed / 2 unchanged

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

Modals, dialogs, drawers, and in-app editors

File Before After Δ Raw Δ Gzip Δ Brotli
assets/ComfyHubPublishDialog-FxLiulVN.js (removed) 86.9 kB 🟢 -86.9 kB 🟢 -18.8 kB 🟢 -16 kB
assets/ComfyHubPublishDialog-PndciXbu.js (new) 86.9 kB 🔴 +86.9 kB 🔴 +18.8 kB 🔴 +16.1 kB
assets/useShareDialog-B7gwpmYi.js (removed) 23.7 kB 🟢 -23.7 kB 🟢 -5.77 kB 🟢 -5.11 kB
assets/useShareDialog-DknUYpW8.js (new) 23.7 kB 🔴 +23.7 kB 🔴 +5.77 kB 🔴 +5.11 kB
assets/ComfyHubPublishDialog-DD5X1G_Z.js (new) 1.32 kB 🔴 +1.32 kB 🔴 +613 B 🔴 +549 B
assets/ComfyHubPublishDialog-DsJG2Sbm.js (removed) 1.32 kB 🟢 -1.32 kB 🟢 -614 B 🟢 -547 B
assets/useSubscriptionDialog-BnLCiCuR.js (new) 1.14 kB 🔴 +1.14 kB 🔴 +545 B 🔴 +481 B
assets/useSubscriptionDialog-BRFa8b1d.js (removed) 1.14 kB 🟢 -1.14 kB 🟢 -544 B 🟢 -480 B

Status: 4 added / 4 removed

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

Reusable component library chunks

File Before After Δ Raw Δ Gzip Δ Brotli
assets/ComfyQueueButton-bqc4JZKR.js (new) 13.5 kB 🔴 +13.5 kB 🔴 +3.79 kB 🔴 +3.38 kB
assets/ComfyQueueButton-DVJBjSag.js (removed) 13.5 kB 🟢 -13.5 kB 🟢 -3.79 kB 🟢 -3.38 kB
assets/useTerminalTabs-BHlIyNcQ.js (new) 11 kB 🔴 +11 kB 🔴 +3.71 kB 🔴 +3.27 kB
assets/useTerminalTabs-CUl6O8VC.js (removed) 11 kB 🟢 -11 kB 🟢 -3.71 kB 🟢 -3.27 kB
assets/SubscribeButton-B9gCfjus.js (new) 2.42 kB 🔴 +2.42 kB 🔴 +1.05 kB 🔴 +920 B
assets/SubscribeButton-CtoenG4J.js (removed) 2.42 kB 🟢 -2.42 kB 🟢 -1.05 kB 🟢 -931 B
assets/cloudFeedbackTopbarButton-BgZCeV2c.js (new) 1.8 kB 🔴 +1.8 kB 🔴 +930 B 🔴 +815 B
assets/cloudFeedbackTopbarButton-CoqQVOu7.js (removed) 1.8 kB 🟢 -1.8 kB 🟢 -929 B 🟢 -830 B
assets/ComfyQueueButton-CgTAEa6n.js (new) 1.24 kB 🔴 +1.24 kB 🔴 +583 B 🔴 +515 B
assets/ComfyQueueButton-OdwuOZ2v.js (removed) 1.24 kB 🟢 -1.24 kB 🟢 -580 B 🟢 -516 B

Status: 5 added / 5 removed / 8 unchanged

Data & Services — 3.04 MB (baseline 3.04 MB) • 🔴 +1.38 kB

Stores, services, APIs, and repositories

File Before After Δ Raw Δ Gzip Δ Brotli
assets/dialogService-dhMsRX6S.js (new) 1.97 MB 🔴 +1.97 MB 🔴 +454 kB 🔴 +344 kB
assets/dialogService-CrgdTztu.js (removed) 1.97 MB 🟢 -1.97 MB 🟢 -454 kB 🟢 -344 kB
assets/api-CFLs3WUG.js (removed) 894 kB 🟢 -894 kB 🟢 -214 kB 🟢 -168 kB
assets/api-CH8yYomb.js (new) 894 kB 🔴 +894 kB 🔴 +214 kB 🔴 +168 kB
assets/load3dService-6PU-irxF.js (removed) 114 kB 🟢 -114 kB 🟢 -24.8 kB 🟢 -21 kB
assets/load3dService-BS7egc8D.js (new) 114 kB 🔴 +114 kB 🔴 +24.8 kB 🔴 +21 kB
assets/workflowShareService-Bf-KLwJW.js (removed) 16.6 kB 🟢 -16.6 kB 🟢 -4.89 kB 🟢 -4.33 kB
assets/workflowShareService-eK5obt_9.js (new) 16.6 kB 🔴 +16.6 kB 🔴 +4.89 kB 🔴 +4.33 kB
assets/keybindingService-BlgthnNR.js (removed) 13.8 kB 🟢 -13.8 kB 🟢 -3.67 kB 🟢 -3.22 kB
assets/keybindingService-Dw7C8ub-.js (new) 13.8 kB 🔴 +13.8 kB 🔴 +3.67 kB 🔴 +3.22 kB
assets/releaseStore-C3sNfA0v.js (new) 8.12 kB 🔴 +8.12 kB 🔴 +2.28 kB 🔴 +2 kB
assets/releaseStore-CPh9ZGAY.js (removed) 8.12 kB 🟢 -8.12 kB 🟢 -2.28 kB 🟢 -2 kB
assets/userStore-BFkpvEXE.js (new) 2.24 kB 🔴 +2.24 kB 🔴 +871 B 🔴 +761 B
assets/userStore-DDWW4iS6.js (removed) 2.24 kB 🟢 -2.24 kB 🟢 -871 B 🟢 -762 B
assets/audioService-CHBjtXan.js (removed) 1.8 kB 🟢 -1.8 kB 🟢 -878 B 🟢 -758 B
assets/audioService-Dpz8_UIu.js (new) 1.8 kB 🔴 +1.8 kB 🔴 +878 B 🔴 +759 B
assets/releaseStore-hKjR4fML.js (new) 1.16 kB 🔴 +1.16 kB 🔴 +548 B 🔴 +490 B
assets/releaseStore-VJlnqDYA.js (removed) 1.16 kB 🟢 -1.16 kB 🟢 -547 B 🟢 -488 B
assets/workflowDraftStore-Bnci_Nmj.js (removed) 1.14 kB 🟢 -1.14 kB 🟢 -542 B 🟢 -484 B
assets/workflowDraftStore-DU-Pd5p4.js (new) 1.14 kB 🔴 +1.14 kB 🔴 +544 B 🔴 +483 B
assets/dialogService-Cs9WsbQl.js (removed) 1.13 kB 🟢 -1.13 kB 🟢 -535 B 🟢 -477 B
assets/dialogService-DZD5E-1K.js (new) 1.13 kB 🔴 +1.13 kB 🔴 +537 B 🔴 +478 B
assets/settingStore-Bmgs6Kqe.js (removed) 1.12 kB 🟢 -1.12 kB 🟢 -538 B 🟢 -478 B
assets/settingStore-DapXN5ce.js (new) 1.12 kB 🔴 +1.12 kB 🔴 +540 B 🔴 +479 B
assets/assetsStore-BqjHPgFf.js (new) 1.12 kB 🔴 +1.12 kB 🔴 +540 B 🔴 +481 B
assets/assetsStore-Ddj2qor9.js (removed) 1.12 kB 🟢 -1.12 kB 🟢 -538 B 🟢 -479 B

Status: 13 added / 13 removed / 4 unchanged

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

Helpers, composables, and utility bundles

File Before After Δ Raw Δ Gzip Δ Brotli
assets/useConflictDetection-BUy9HpF_.js (removed) 233 kB 🟢 -233 kB 🟢 -51.8 kB 🟢 -42.2 kB
assets/useConflictDetection-DQ4sGJoz.js (new) 233 kB 🔴 +233 kB 🔴 +51.8 kB 🔴 +42.2 kB
assets/useLoad3d-CoC6nNZE.js (removed) 21.6 kB 🟢 -21.6 kB 🟢 -4.92 kB 🟢 -4.35 kB
assets/useLoad3d-D0zUo9fd.js (new) 21.6 kB 🔴 +21.6 kB 🔴 +4.92 kB 🔴 +4.36 kB
assets/useLoad3dViewer-BBMHXfyw.js (new) 20.3 kB 🔴 +20.3 kB 🔴 +4.75 kB 🔴 +4.14 kB
assets/useLoad3dViewer-UUC8PS5S.js (removed) 20.3 kB 🟢 -20.3 kB 🟢 -4.75 kB 🟢 -4.13 kB
assets/useFeatureFlags-Cwpo9xeJ.js (removed) 5.95 kB 🟢 -5.95 kB 🟢 -1.8 kB 🟢 -1.52 kB
assets/useFeatureFlags-D1dgi3KA.js (new) 5.95 kB 🔴 +5.95 kB 🔴 +1.8 kB 🔴 +1.52 kB
assets/useCopyToClipboard-BzmydvxM.js (removed) 5.29 kB 🟢 -5.29 kB 🟢 -1.86 kB 🟢 -1.57 kB
assets/useCopyToClipboard-DuFZYgz3.js (new) 5.29 kB 🔴 +5.29 kB 🔴 +1.86 kB 🔴 +1.58 kB
assets/useWorkspaceUI-9zsxgCJO.js (new) 3.34 kB 🔴 +3.34 kB 🔴 +982 B 🔴 +807 B
assets/useWorkspaceUI-D8y1iT4I.js (removed) 3.34 kB 🟢 -3.34 kB 🟢 -981 B 🟢 -809 B
assets/subscriptionCheckoutUtil-C0omTjRM.js (removed) 3.31 kB 🟢 -3.31 kB 🟢 -1.36 kB 🟢 -1.18 kB
assets/subscriptionCheckoutUtil-DQ2iFHU2.js (new) 3.31 kB 🔴 +3.31 kB 🔴 +1.36 kB 🔴 +1.18 kB
assets/assetPreviewUtil-CzXG6pZx.js (removed) 2.27 kB 🟢 -2.27 kB 🟢 -958 B 🟢 -834 B
assets/assetPreviewUtil-Df3hEyFl.js (new) 2.27 kB 🔴 +2.27 kB 🔴 +958 B 🔴 +835 B
assets/useUpstreamValue-BjEJxMPb.js (removed) 2.08 kB 🟢 -2.08 kB 🟢 -806 B 🟢 -711 B
assets/useUpstreamValue-DIAEVR-3.js (new) 2.08 kB 🔴 +2.08 kB 🔴 +806 B 🔴 +711 B
assets/useLoad3d-Byqg41Fj.js (new) 1.3 kB 🔴 +1.3 kB 🔴 +606 B 🔴 +558 B
assets/useLoad3d-heOYqjvj.js (removed) 1.3 kB 🟢 -1.3 kB 🟢 -604 B 🟢 -564 B
assets/useLoad3dViewer-B9y9lpCw.js (removed) 1.24 kB 🟢 -1.24 kB 🟢 -570 B 🟢 -514 B
assets/useLoad3dViewer-C5TGKQ8U.js (new) 1.24 kB 🔴 +1.24 kB 🔴 +574 B 🔴 +515 B
assets/useCurrentUser-B1ZCEt9i.js (new) 1.12 kB 🔴 +1.12 kB 🔴 +541 B 🔴 +480 B
assets/useCurrentUser-BjRlP0de.js (removed) 1.12 kB 🟢 -1.12 kB 🟢 -539 B 🟢 -479 B
assets/useWorkspaceSwitch-BU_U-cqw.js (new) 747 B 🔴 +747 B 🔴 +383 B 🔴 +330 B
assets/useWorkspaceSwitch-Cv-nK95o.js (removed) 747 B 🟢 -747 B 🟢 -383 B 🟢 -323 B

Status: 13 added / 13 removed / 18 unchanged

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

External libraries and shared vendor chunks

Status: 16 unchanged

Other — 8.83 MB (baseline 8.83 MB) • ⚪ 0 B

Bundles that do not match a named category

File Before After Δ Raw Δ Gzip Δ Brotli
assets/core-Ca8TuC2M.js (new) 76.6 kB 🔴 +76.6 kB 🔴 +19.8 kB 🔴 +16.9 kB
assets/core-jeMO4H3X.js (removed) 76.6 kB 🟢 -76.6 kB 🟢 -19.8 kB 🟢 -16.9 kB
assets/groupNode-Bl9yY1Tk.js (new) 74.9 kB 🔴 +74.9 kB 🔴 +18.7 kB 🔴 +16.5 kB
assets/groupNode-DmsKs0O0.js (removed) 74.9 kB 🟢 -74.9 kB 🟢 -18.7 kB 🟢 -16.5 kB
assets/WidgetSelect-CANPgs8q.js (removed) 67.2 kB 🟢 -67.2 kB 🟢 -14.6 kB 🟢 -12.6 kB
assets/WidgetSelect-D-jRaQ_M.js (new) 67.2 kB 🔴 +67.2 kB 🔴 +14.6 kB 🔴 +12.6 kB
assets/SubscriptionRequiredDialogContentWorkspace-C8XbIzes.js (removed) 49.3 kB 🟢 -49.3 kB 🟢 -9.41 kB 🟢 -8.03 kB
assets/SubscriptionRequiredDialogContentWorkspace-Dl0hFgD3.js (new) 49.3 kB 🔴 +49.3 kB 🔴 +9.41 kB 🔴 +8.05 kB
assets/Load3DControls-BQkN6xlb.js (removed) 45.9 kB 🟢 -45.9 kB 🟢 -7.47 kB 🟢 -6.52 kB
assets/Load3DControls-UDO_j-iU.js (new) 45.9 kB 🔴 +45.9 kB 🔴 +7.47 kB 🔴 +6.54 kB
assets/WidgetPainter-Cm39XBB-.js (new) 34 kB 🔴 +34 kB 🔴 +8.29 kB 🔴 +7.36 kB
assets/WidgetPainter-eBPNrBlt.js (removed) 34 kB 🟢 -34 kB 🟢 -8.28 kB 🟢 -7.33 kB
assets/WorkspacePanelContent-DrjKfeYM.js (removed) 32.8 kB 🟢 -32.8 kB 🟢 -7 kB 🟢 -6.18 kB
assets/WorkspacePanelContent-Qd24J_2E.js (new) 32.8 kB 🔴 +32.8 kB 🔴 +7 kB 🔴 +6.18 kB
assets/Load3dViewerContent-CF50UKvk.js (removed) 28.3 kB 🟢 -28.3 kB 🟢 -5.89 kB 🟢 -5.12 kB
assets/Load3dViewerContent-HJvmZv9j.js (new) 28.3 kB 🔴 +28.3 kB 🔴 +5.9 kB 🔴 +5.12 kB
assets/SubscriptionRequiredDialogContent-CaVHKnum.js (removed) 27.5 kB 🟢 -27.5 kB 🟢 -6.96 kB 🟢 -6.14 kB
assets/SubscriptionRequiredDialogContent-D4mu9D2T.js (new) 27.5 kB 🔴 +27.5 kB 🔴 +6.96 kB 🔴 +6.15 kB
assets/WidgetImageCrop-DkrAcDN-.js (new) 24.3 kB 🔴 +24.3 kB 🔴 +6.19 kB 🔴 +5.45 kB
assets/WidgetImageCrop-wWlrpgwA.js (removed) 24.3 kB 🟢 -24.3 kB 🟢 -6.19 kB 🟢 -5.44 kB
assets/SubscriptionPanelContentWorkspace-MfNYMZoX.js (new) 22.2 kB 🔴 +22.2 kB 🔴 +5.18 kB 🔴 +4.57 kB
assets/SubscriptionPanelContentWorkspace-SR-zlNfb.js (removed) 22.2 kB 🟢 -22.2 kB 🟢 -5.17 kB 🟢 -4.57 kB
assets/SignInContent-B4dMAQ-1.js (new) 20.6 kB 🔴 +20.6 kB 🔴 +5.36 kB 🔴 +4.68 kB
assets/SignInContent-B66-YeRf.js (removed) 20.6 kB 🟢 -20.6 kB 🟢 -5.35 kB 🟢 -4.68 kB
assets/CurrentUserPopoverWorkspace-BIdYmvCp.js (removed) 20.5 kB 🟢 -20.5 kB 🟢 -4.89 kB 🟢 -4.39 kB
assets/CurrentUserPopoverWorkspace-BS6w9W4C.js (new) 20.5 kB 🔴 +20.5 kB 🔴 +4.9 kB 🔴 +4.38 kB
assets/WidgetInputNumber-DchM5Rz1.js (removed) 19.1 kB 🟢 -19.1 kB 🟢 -4.84 kB 🟢 -4.3 kB
assets/WidgetInputNumber-djLnV8_c.js (new) 19.1 kB 🔴 +19.1 kB 🔴 +4.84 kB 🔴 +4.29 kB
assets/Load3D-DGs2paGd.js (new) 18.2 kB 🔴 +18.2 kB 🔴 +4.34 kB 🔴 +3.79 kB
assets/Load3D-DkYMhHNY.js (removed) 18.2 kB 🟢 -18.2 kB 🟢 -4.34 kB 🟢 -3.79 kB
assets/WidgetRecordAudio-BmVZYquc.js (removed) 17.4 kB 🟢 -17.4 kB 🟢 -5 kB 🟢 -4.48 kB
assets/WidgetRecordAudio-vCo3CTDa.js (new) 17.4 kB 🔴 +17.4 kB 🔴 +5 kB 🔴 +4.48 kB
assets/WidgetRange-BR2UidSW.js (new) 17 kB 🔴 +17 kB 🔴 +4.59 kB 🔴 +4.12 kB
assets/WidgetRange-Ctk-S0Ye.js (removed) 17 kB 🟢 -17 kB 🟢 -4.59 kB 🟢 -4.1 kB
assets/load3d-Dcu2ntnp.js (removed) 15.7 kB 🟢 -15.7 kB 🟢 -4.57 kB 🟢 -3.95 kB
assets/load3d-PXEBs9Pq.js (new) 15.7 kB 🔴 +15.7 kB 🔴 +4.56 kB 🔴 +3.95 kB
assets/WaveAudioPlayer-BXujLP5z.js (new) 13.4 kB 🔴 +13.4 kB 🔴 +3.69 kB 🔴 +3.23 kB
assets/WaveAudioPlayer-CIoW20EU.js (removed) 13.4 kB 🟢 -13.4 kB 🟢 -3.69 kB 🟢 -3.24 kB
assets/WidgetCurve-BmxAocfh.js (new) 12.1 kB 🔴 +12.1 kB 🔴 +3.91 kB 🔴 +3.55 kB
assets/WidgetCurve-SvQDWGak.js (removed) 12.1 kB 🟢 -12.1 kB 🟢 -3.91 kB 🟢 -3.55 kB
assets/TeamWorkspacesDialogContent-CaYpVjo0.js (new) 11.3 kB 🔴 +11.3 kB 🔴 +3.41 kB 🔴 +3.03 kB
assets/TeamWorkspacesDialogContent-Dbx9QiyZ.js (removed) 11.3 kB 🟢 -11.3 kB 🟢 -3.4 kB 🟢 -3.03 kB
assets/nodeTemplates-CFCfxz-H.js (removed) 9.81 kB 🟢 -9.81 kB 🟢 -3.47 kB 🟢 -3.06 kB
assets/nodeTemplates-CwRV0o56.js (new) 9.81 kB 🔴 +9.81 kB 🔴 +3.47 kB 🔴 +3.07 kB
assets/NightlySurveyController-cAKiGIyp.js (new) 8.99 kB 🔴 +8.99 kB 🔴 +3.15 kB 🔴 +2.78 kB
assets/NightlySurveyController-D4EMY1fk.js (removed) 8.99 kB 🟢 -8.99 kB 🟢 -3.14 kB 🟢 -2.78 kB
assets/InviteMemberDialogContent-CB6vEcx3.js (new) 7.91 kB 🔴 +7.91 kB 🔴 +2.51 kB 🔴 +2.21 kB
assets/InviteMemberDialogContent-DfG7CNDE.js (removed) 7.91 kB 🟢 -7.91 kB 🟢 -2.51 kB 🟢 -2.2 kB
assets/Load3DConfiguration-B-YyA4Zu.js (removed) 7.82 kB 🟢 -7.82 kB 🟢 -2.31 kB 🟢 -2.02 kB
assets/Load3DConfiguration-Bxa6b0SJ.js (new) 7.82 kB 🔴 +7.82 kB 🔴 +2.31 kB 🔴 +2.02 kB
assets/onboardingCloudRoutes-CqMCH2BC.js (removed) 6.58 kB 🟢 -6.58 kB 🟢 -2.05 kB 🟢 -1.77 kB
assets/onboardingCloudRoutes-eI615iHb.js (new) 6.58 kB 🔴 +6.58 kB 🔴 +2.05 kB 🔴 +1.77 kB
assets/CreateWorkspaceDialogContent-D1bm93mw.js (removed) 6.12 kB 🟢 -6.12 kB 🟢 -2.22 kB 🟢 -1.94 kB
assets/CreateWorkspaceDialogContent-ZSGEh5fx.js (new) 6.12 kB 🔴 +6.12 kB 🔴 +2.23 kB 🔴 +1.95 kB
assets/WidgetWithControl-Bi5l8aqy.js (new) 6.02 kB 🔴 +6.02 kB 🔴 +2.4 kB 🔴 +2.15 kB
assets/WidgetWithControl-DqHOTiTe.js (removed) 6.02 kB 🟢 -6.02 kB 🟢 -2.4 kB 🟢 -2.15 kB
assets/FreeTierDialogContent-BTMa5bZV.js (removed) 5.99 kB 🟢 -5.99 kB 🟢 -2.12 kB 🟢 -1.88 kB
assets/FreeTierDialogContent-L8RFmt9n.js (new) 5.99 kB 🔴 +5.99 kB 🔴 +2.12 kB 🔴 +1.88 kB
assets/EditWorkspaceDialogContent-Ctm-loNj.js (new) 5.92 kB 🔴 +5.92 kB 🔴 +2.19 kB 🔴 +1.92 kB
assets/EditWorkspaceDialogContent-srAQ-qee.js (removed) 5.92 kB 🟢 -5.92 kB 🟢 -2.18 kB 🟢 -1.94 kB
assets/WidgetTextarea-3sLeu2Xq.js (new) 5.73 kB 🔴 +5.73 kB 🔴 +2.26 kB 🔴 +1.99 kB
assets/WidgetTextarea-B0ylQFDE.js (removed) 5.73 kB 🟢 -5.73 kB 🟢 -2.26 kB 🟢 -2 kB
assets/Preview3d-D1JWN5bf.js (new) 5.56 kB 🔴 +5.56 kB 🔴 +1.88 kB 🔴 +1.64 kB
assets/Preview3d-DL2iK3Rn.js (removed) 5.56 kB 🟢 -5.56 kB 🟢 -1.88 kB 🟢 -1.65 kB
assets/ValueControlPopover-D7x_u4Eb.js (new) 5.5 kB 🔴 +5.5 kB 🔴 +2 kB 🔴 +1.8 kB
assets/ValueControlPopover-DQmE6mo-.js (removed) 5.5 kB 🟢 -5.5 kB 🟢 -2 kB 🟢 -1.8 kB
assets/CancelSubscriptionDialogContent-C1oM8A6r.js (removed) 5.46 kB 🟢 -5.46 kB 🟢 -2.04 kB 🟢 -1.79 kB
assets/CancelSubscriptionDialogContent-D_o1Gol_.js (new) 5.46 kB 🔴 +5.46 kB 🔴 +2.04 kB 🔴 +1.79 kB
assets/DeleteWorkspaceDialogContent-BHLAd0iF.js (removed) 4.82 kB 🟢 -4.82 kB 🟢 -1.86 kB 🟢 -1.62 kB
assets/DeleteWorkspaceDialogContent-DU4lqfHY.js (new) 4.82 kB 🔴 +4.82 kB 🔴 +1.86 kB 🔴 +1.62 kB
assets/LeaveWorkspaceDialogContent--NxEi-z6.js (removed) 4.65 kB 🟢 -4.65 kB 🟢 -1.8 kB 🟢 -1.58 kB
assets/LeaveWorkspaceDialogContent-Cv9zTSfB.js (new) 4.65 kB 🔴 +4.65 kB 🔴 +1.81 kB 🔴 +1.57 kB
assets/RemoveMemberDialogContent-RzQAd3G_.js (removed) 4.63 kB 🟢 -4.63 kB 🟢 -1.76 kB 🟢 -1.54 kB
assets/RemoveMemberDialogContent-WGROWusU.js (new) 4.63 kB 🔴 +4.63 kB 🔴 +1.76 kB 🔴 +1.55 kB
assets/RevokeInviteDialogContent-BiDZh3_R.js (new) 4.54 kB 🔴 +4.54 kB 🔴 +1.77 kB 🔴 +1.55 kB
assets/RevokeInviteDialogContent-DZCvAWQy.js (removed) 4.54 kB 🟢 -4.54 kB 🟢 -1.77 kB 🟢 -1.55 kB
assets/tierBenefits-BgKjhNH8.js (new) 4.45 kB 🔴 +4.45 kB 🔴 +1.57 kB 🔴 +1.36 kB
assets/tierBenefits-CtP9QNgm.js (removed) 4.45 kB 🟢 -4.45 kB 🟢 -1.57 kB 🟢 -1.36 kB
assets/InviteMemberUpsellDialogContent-CAwwvaFE.js (new) 4.44 kB 🔴 +4.44 kB 🔴 +1.64 kB 🔴 +1.44 kB
assets/InviteMemberUpsellDialogContent-DxhbRSmB.js (removed) 4.44 kB 🟢 -4.44 kB 🟢 -1.63 kB 🟢 -1.44 kB
assets/cloudSessionCookie-BjFqodJG.js (removed) 4.28 kB 🟢 -4.28 kB 🟢 -1.56 kB 🟢 -1.36 kB
assets/cloudSessionCookie-DIp6JX-X.js (new) 4.28 kB 🔴 +4.28 kB 🔴 +1.56 kB 🔴 +1.37 kB
assets/Media3DTop-Dx5hfUUg.js (new) 4.01 kB 🔴 +4.01 kB 🔴 +1.7 kB 🔴 +1.51 kB
assets/Media3DTop-DzVg02ed.js (removed) 4.01 kB 🟢 -4.01 kB 🟢 -1.7 kB 🟢 -1.49 kB
assets/saveMesh-C6dNWME3.js (new) 3.98 kB 🔴 +3.98 kB 🔴 +1.73 kB 🔴 +1.53 kB
assets/saveMesh-D7bMc0t7.js (removed) 3.98 kB 🟢 -3.98 kB 🟢 -1.73 kB 🟢 -1.53 kB
assets/GlobalToast-DjmrjXym.js (new) 3.05 kB 🔴 +3.05 kB 🔴 +1.26 kB 🔴 +1.07 kB
assets/GlobalToast-DqB7myOa.js (removed) 3.05 kB 🟢 -3.05 kB 🟢 -1.26 kB 🟢 -1.11 kB
assets/CloudRunButtonWrapper-wSV9sD81.js (removed) 2.2 kB 🟢 -2.2 kB 🟢 -1 kB 🟢 -894 B
assets/CloudRunButtonWrapper-z-fdwNhT.js (new) 2.2 kB 🔴 +2.2 kB 🔴 +1.01 kB 🔴 +895 B
assets/SubscribeToRun-BAasIE9o.js (new) 2.13 kB 🔴 +2.13 kB 🔴 +982 B 🔴 +875 B
assets/SubscribeToRun-DJkL11Zz.js (removed) 2.13 kB 🟢 -2.13 kB 🟢 -982 B 🟢 -874 B
assets/MediaAudioTop-BLIFQ_6l.js (new) 2.08 kB 🔴 +2.08 kB 🔴 +1.01 kB 🔴 +866 B
assets/MediaAudioTop-BZJnEHP1.js (removed) 2.08 kB 🟢 -2.08 kB 🟢 -1.01 kB 🟢 -862 B
assets/cloudBadges-BBP9vcfO.js (new) 1.93 kB 🔴 +1.93 kB 🔴 +964 B 🔴 +836 B
assets/cloudBadges-Bs4A9l7K.js (removed) 1.93 kB 🟢 -1.93 kB 🟢 -962 B 🟢 -835 B
assets/cloudSubscription-BzEmoKiv.js (new) 1.85 kB 🔴 +1.85 kB 🔴 +885 B 🔴 +783 B
assets/cloudSubscription-CoO7dRqn.js (removed) 1.85 kB 🟢 -1.85 kB 🟢 -884 B 🟢 -780 B
assets/graphHasMissingNodes-CzkxLfs7.js (new) 1.84 kB 🔴 +1.84 kB 🔴 +861 B 🔴 +761 B
assets/graphHasMissingNodes-DNRA-68h.js (removed) 1.84 kB 🟢 -1.84 kB 🟢 -862 B 🟢 -778 B
assets/Load3D-CzdjQ60K.js (removed) 1.55 kB 🟢 -1.55 kB 🟢 -697 B 🟢 -621 B
assets/Load3D-DHf5qdCf.js (new) 1.55 kB 🔴 +1.55 kB 🔴 +696 B 🔴 +623 B
assets/previousFullPath-BiP3vpbz.js (new) 1.53 kB 🔴 +1.53 kB 🔴 +694 B 🔴 +617 B
assets/previousFullPath-QvuKf7dH.js (removed) 1.53 kB 🟢 -1.53 kB 🟢 -692 B 🟢 -595 B
assets/nightlyBadges-BaevwM3v.js (new) 1.46 kB 🔴 +1.46 kB 🔴 +731 B 🔴 +670 B
assets/nightlyBadges-hqalCssB.js (removed) 1.46 kB 🟢 -1.46 kB 🟢 -729 B 🟢 -670 B
assets/Load3dViewerContent-NTfm3X6I.js (new) 1.43 kB 🔴 +1.43 kB 🔴 +650 B 🔴 +582 B
assets/Load3dViewerContent-P2S4SWkV.js (removed) 1.43 kB 🟢 -1.43 kB 🟢 -650 B 🟢 -591 B
assets/SubscriptionPanelContentWorkspace-C0iA593u.js (removed) 1.32 kB 🟢 -1.32 kB 🟢 -602 B 🟢 -525 B
assets/SubscriptionPanelContentWorkspace-DcOeIeBC.js (new) 1.32 kB 🔴 +1.32 kB 🔴 +605 B 🔴 +526 B
assets/WidgetLegacy-C4QJq7Hl.js (removed) 1.15 kB 🟢 -1.15 kB 🟢 -550 B 🟢 -487 B
assets/WidgetLegacy-CGiHHXxJ.js (new) 1.15 kB 🔴 +1.15 kB 🔴 +551 B 🔴 +488 B
assets/changeTracker-D0abYQA1.js (removed) 1.12 kB 🟢 -1.12 kB 🟢 -539 B 🟢 -471 B
assets/changeTracker-DzOzw96R.js (new) 1.12 kB 🔴 +1.12 kB 🔴 +540 B 🔴 +470 B

Status: 57 added / 57 removed / 78 unchanged

⚡ Performance Report

canvas-idle: · 60.0 avg FPS · 59.9 P5 FPS ✅ (target: ≥52) · 0ms TBT · 44.3 MB heap
canvas-mouse-sweep: · 60.0 avg FPS · 59.9 P5 FPS ✅ (target: ≥52) · 0ms TBT · 57.1 MB heap
canvas-zoom-sweep: · 60.0 avg FPS · 59.5 P5 FPS ✅ (target: ≥52) · 0ms TBT · 65.2 MB heap
dom-widget-clipping: · 60.0 avg FPS · 59.5 P5 FPS ✅ (target: ≥52) · 0ms TBT · 48.6 MB heap
large-graph-idle: · 60.0 avg FPS · 59.9 P5 FPS ✅ (target: ≥52) · 0ms TBT · 54.2 MB heap
large-graph-pan: · 60.0 avg FPS · 59.5 P5 FPS ✅ (target: ≥52) · 0ms TBT · 69.9 MB heap
large-graph-zoom: · 60.0 avg FPS · 59.9 P5 FPS ✅ (target: ≥52) · 0ms TBT · 55.1 MB heap
minimap-idle: · 60.0 avg FPS · 59.5 P5 FPS ✅ (target: ≥52) · 0ms TBT · 66.8 MB heap
subgraph-dom-widget-clipping: · 60.0 avg FPS · 59.9 P5 FPS ✅ (target: ≥52) · 0ms TBT · 48.9 MB heap
subgraph-idle: · 60.0 avg FPS · 59.5 P5 FPS ✅ (target: ≥52) · 0ms TBT · 61.4 MB heap
subgraph-mouse-sweep: · 60.0 avg FPS · 59.9 P5 FPS ✅ (target: ≥52) · 0ms TBT · 53.7 MB heap
viewport-pan-sweep: · 60.0 avg FPS · 59.9 P5 FPS ✅ (target: ≥52) · 0ms TBT · 66.7 MB heap
vue-large-graph-idle: · 58.1 avg FPS · 59.5 P5 FPS ✅ (target: ≥52) · 0ms TBT · 153.1 MB heap
vue-large-graph-pan: · 56.3 avg FPS · 59.9 P5 FPS ✅ (target: ≥52) · 55ms TBT · 155.0 MB heap
workflow-execution: · 60.0 avg FPS · 59.9 P5 FPS ✅ (target: ≥52) · 0ms TBT · 47.9 MB heap

⚠️ 3 regressions detected

Metric Baseline PR (median) Δ Sig
canvas-mouse-sweep: task duration 954ms 992ms +4% ⚠️ z=2.2
subgraph-mouse-sweep: style recalc duration 49ms 50ms +2% ⚠️ z=2.2
subgraph-mouse-sweep: task duration 927ms 962ms +4% ⚠️ z=2.8
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 7ms 10ms +44% z=-1.0
canvas-idle: layout count 0 0 +0%
canvas-idle: style recalc count 8 10 +25% z=-2.1
canvas-idle: task duration 352ms 416ms +18% z=0.7
canvas-idle: script duration 17ms 24ms +44% z=-0.4
canvas-idle: TBT 0ms 0ms +0%
canvas-idle: heap used 61.4 MB 44.3 MB -28%
canvas-idle: DOM nodes 16 20 +25% z=-2.0
canvas-idle: event listeners 6 6 +0% z=-1.2
canvas-mouse-sweep: avg frame time 17ms 17ms +0% z=-0.4
canvas-mouse-sweep: p95 frame time 17ms 17ms -1%
canvas-mouse-sweep: layout duration 4ms 4ms +0% z=0.0
canvas-mouse-sweep: style recalc duration 44ms 44ms -2% z=0.3
canvas-mouse-sweep: layout count 12 12 +0%
canvas-mouse-sweep: style recalc count 80 84 +5% z=2.3
canvas-mouse-sweep: task duration 954ms 992ms +4% ⚠️ z=2.2
canvas-mouse-sweep: script duration 122ms 124ms +1% z=-1.8
canvas-mouse-sweep: TBT 0ms 0ms +0%
canvas-mouse-sweep: heap used 57.1 MB 57.1 MB +0%
canvas-mouse-sweep: DOM nodes 63 66 +5% z=1.4
canvas-mouse-sweep: event listeners 6 6 +0% 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 -8% z=-1.3
canvas-zoom-sweep: style recalc duration 17ms 19ms +8% z=-0.4
canvas-zoom-sweep: layout count 6 6 +0%
canvas-zoom-sweep: style recalc count 31 32 +3% z=1.6
canvas-zoom-sweep: task duration 298ms 341ms +14% z=0.6
canvas-zoom-sweep: script duration 21ms 29ms +42% z=0.8
canvas-zoom-sweep: TBT 0ms 0ms +0%
canvas-zoom-sweep: heap used 65.6 MB 65.2 MB -1%
canvas-zoom-sweep: DOM nodes 76 80 +5% z=1.0
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 +0%
dom-widget-clipping: layout duration 0ms 0ms +0%
dom-widget-clipping: style recalc duration 8ms 8ms +1% z=-2.3
dom-widget-clipping: layout count 0 0 +0%
dom-widget-clipping: style recalc count 11 11 +0% z=-4.2
dom-widget-clipping: task duration 323ms 332ms +3% z=-2.0
dom-widget-clipping: script duration 56ms 55ms -2% z=-3.9
dom-widget-clipping: TBT 0ms 0ms +0%
dom-widget-clipping: heap used 55.4 MB 48.6 MB -12%
dom-widget-clipping: DOM nodes 18 18 +0% z=-2.9
dom-widget-clipping: event listeners 2 2 +0% variance too high
large-graph-idle: avg frame time 17ms 17ms +0% z=-0.2
large-graph-idle: p95 frame time 17ms 17ms +0%
large-graph-idle: layout duration 0ms 0ms +0%
large-graph-idle: style recalc duration 8ms 8ms +4% z=-3.6
large-graph-idle: layout count 0 0 +0%
large-graph-idle: style recalc count 9 9 +0% z=-8.3
large-graph-idle: task duration 549ms 605ms +10% z=1.2
large-graph-idle: script duration 92ms 108ms +17% z=0.5
large-graph-idle: TBT 0ms 0ms +0%
large-graph-idle: heap used 54.5 MB 54.2 MB -1%
large-graph-idle: DOM nodes -263 -260 -1% z=-315.0
large-graph-idle: event listeners -125 -125 +0% z=-24.8
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 19ms 18ms -9% 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 1094ms 1113ms +2% z=0.7
large-graph-pan: script duration 382ms 390ms +2% z=-0.9
large-graph-pan: TBT 0ms 0ms +0%
large-graph-pan: heap used 62.6 MB 69.9 MB +12%
large-graph-pan: DOM nodes -262 -264 +1% z=-171.5
large-graph-pan: event listeners -125 -125 +0% z=-156.9
large-graph-zoom: avg frame time 17ms 17ms +0%
large-graph-zoom: p95 frame time 17ms 17ms -1%
large-graph-zoom: layout duration 9ms 8ms -14%
large-graph-zoom: style recalc duration 22ms 20ms -9%
large-graph-zoom: layout count 60 60 +0%
large-graph-zoom: style recalc count 66 66 +0%
large-graph-zoom: task duration 1404ms 1367ms -3%
large-graph-zoom: script duration 511ms 493ms -3%
large-graph-zoom: TBT 0ms 0ms +0%
large-graph-zoom: heap used 60.7 MB 55.1 MB -9%
large-graph-zoom: DOM nodes -264 -266 +1%
large-graph-zoom: event listeners -125 -123 -2%
minimap-idle: avg frame time 17ms 17ms +0% z=0.1
minimap-idle: p95 frame time 17ms 17ms +1%
minimap-idle: layout duration 0ms 0ms +0%
minimap-idle: style recalc duration 7ms 9ms +20% z=-1.1
minimap-idle: layout count 0 0 +0%
minimap-idle: style recalc count 8 9 +13% z=-0.8
minimap-idle: task duration 531ms 594ms +12% z=1.4
minimap-idle: script duration 80ms 100ms +26% z=0.2
minimap-idle: TBT 0ms 0ms +0%
minimap-idle: heap used 57.0 MB 66.8 MB +17%
minimap-idle: DOM nodes -263 -262 -0% z=-205.6
minimap-idle: event listeners -125 -125 +0% z=-196.2
subgraph-dom-widget-clipping: avg frame time 17ms 17ms +0% z=0.1
subgraph-dom-widget-clipping: p95 frame time 17ms 17ms +0%
subgraph-dom-widget-clipping: layout duration 0ms 0ms +0%
subgraph-dom-widget-clipping: style recalc duration 12ms 12ms -0% z=-0.8
subgraph-dom-widget-clipping: layout count 0 0 +0%
subgraph-dom-widget-clipping: style recalc count 47 47 +0% z=-1.6
subgraph-dom-widget-clipping: task duration 356ms 375ms +5% z=-0.2
subgraph-dom-widget-clipping: script duration 127ms 125ms -2% z=-0.6
subgraph-dom-widget-clipping: TBT 0ms 0ms +0%
subgraph-dom-widget-clipping: heap used 48.9 MB 48.9 MB +0%
subgraph-dom-widget-clipping: DOM nodes 19 19 +0% z=-2.8
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 +1%
subgraph-idle: layout duration 0ms 0ms +0%
subgraph-idle: style recalc duration 9ms 9ms -0% z=-1.4
subgraph-idle: layout count 0 0 +0%
subgraph-idle: style recalc count 10 10 +0% z=-1.4
subgraph-idle: task duration 361ms 394ms +9% z=0.8
subgraph-idle: script duration 19ms 23ms +22% z=1.2
subgraph-idle: TBT 0ms 0ms +0%
subgraph-idle: heap used 61.1 MB 61.4 MB +0%
subgraph-idle: DOM nodes 20 20 +0% z=-1.2
subgraph-idle: event listeners 6 6 +0% 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 5ms 5ms +3% z=0.5
subgraph-mouse-sweep: style recalc duration 49ms 50ms +2% ⚠️ z=2.2
subgraph-mouse-sweep: layout count 16 16 +0%
subgraph-mouse-sweep: style recalc count 84 84 +0% z=2.0
subgraph-mouse-sweep: task duration 927ms 962ms +4% ⚠️ z=2.8
subgraph-mouse-sweep: script duration 104ms 103ms -1% z=0.4
subgraph-mouse-sweep: TBT 0ms 0ms +0%
subgraph-mouse-sweep: heap used 53.3 MB 53.7 MB +1%
subgraph-mouse-sweep: DOM nodes 72 72 +0% z=2.3
subgraph-mouse-sweep: event listeners 6 6 +0% variance too high
viewport-pan-sweep: avg frame time 17ms 17ms +0%
viewport-pan-sweep: p95 frame time 17ms 17ms +0%
viewport-pan-sweep: layout duration 0ms 0ms +0%
viewport-pan-sweep: style recalc duration 61ms 54ms -10%
viewport-pan-sweep: layout count 0 0 +0%
viewport-pan-sweep: style recalc count 250 250 +0%
viewport-pan-sweep: task duration 3659ms 3891ms +6%
viewport-pan-sweep: script duration 1233ms 1272ms +3%
viewport-pan-sweep: TBT 0ms 0ms +0%
viewport-pan-sweep: heap used 73.2 MB 66.7 MB -9%
viewport-pan-sweep: DOM nodes -258 -259 +0%
viewport-pan-sweep: event listeners -111 -111 +0%
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 13610ms 10900ms -20%
vue-large-graph-idle: script duration 627ms 591ms -6%
vue-large-graph-idle: TBT 0ms 0ms +0%
vue-large-graph-idle: heap used 161.5 MB 153.1 MB -5%
vue-large-graph-idle: DOM nodes -9850 -9850 +0%
vue-large-graph-idle: event listeners -23956 -23965 +0%
vue-large-graph-pan: avg frame time 18ms 18ms -0%
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 21ms 18ms -12%
vue-large-graph-pan: layout count 0 0 +0%
vue-large-graph-pan: style recalc count 82 66 -20%
vue-large-graph-pan: task duration 15995ms 13045ms -18%
vue-large-graph-pan: script duration 860ms 892ms +4%
vue-large-graph-pan: TBT 91ms 55ms -40%
vue-large-graph-pan: heap used 166.0 MB 155.0 MB -7%
vue-large-graph-pan: DOM nodes -9850 -9850 +0%
vue-large-graph-pan: event listeners -23951 -23959 +0%
workflow-execution: avg frame time 17ms 17ms -0% z=0.6
workflow-execution: p95 frame time 17ms 17ms -1%
workflow-execution: layout duration 2ms 2ms +3% z=0.2
workflow-execution: style recalc duration 23ms 23ms -1% z=-0.8
workflow-execution: layout count 4 5 +25% z=0.1
workflow-execution: style recalc count 16 14 -13% z=-1.9
workflow-execution: task duration 114ms 121ms +6% z=-0.2
workflow-execution: script duration 23ms 24ms +4% z=-1.9
workflow-execution: TBT 0ms 0ms +0%
workflow-execution: heap used 48.3 MB 47.9 MB -1%
workflow-execution: DOM nodes 153 152 -1% z=-1.2
workflow-execution: event listeners 71 71 +0% 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-28T15:30:39.214Z",
  "gitSha": "e7612785d9a60148b43dc686a070bc301a44562d",
  "branch": "ben/fe-485-workspace-auth-refresh-race-v2",
  "measurements": [
    {
      "name": "canvas-idle",
      "durationMs": 1993.754999999993,
      "styleRecalcs": 8,
      "styleRecalcDurationMs": 6.613,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 396.84200000000004,
      "heapDeltaBytes": 20536688,
      "heapUsedBytes": 65135232,
      "domNodes": 16,
      "jsHeapTotalBytes": 22282240,
      "scriptDurationMs": 20.741000000000003,
      "eventListeners": 6,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "canvas-idle",
      "durationMs": 2016.1959999999794,
      "styleRecalcs": 11,
      "styleRecalcDurationMs": 9.905000000000001,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 416.485,
      "heapDeltaBytes": -4072232,
      "heapUsedBytes": 46434000,
      "domNodes": 22,
      "jsHeapTotalBytes": 24641536,
      "scriptDurationMs": 24.372000000000003,
      "eventListeners": 6,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.699999999999818
    },
    {
      "name": "canvas-idle",
      "durationMs": 2030.0690000000259,
      "styleRecalcs": 10,
      "styleRecalcDurationMs": 10.634,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 426.50200000000007,
      "heapDeltaBytes": -4000232,
      "heapUsedBytes": 46084320,
      "domNodes": 20,
      "jsHeapTotalBytes": 24117248,
      "scriptDurationMs": 26.83,
      "eventListeners": 6,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "canvas-mouse-sweep",
      "durationMs": 2005.1670000000001,
      "styleRecalcs": 79,
      "styleRecalcDurationMs": 43.126,
      "layouts": 12,
      "layoutDurationMs": 3.058,
      "taskDurationMs": 987.9239999999999,
      "heapDeltaBytes": 15747152,
      "heapUsedBytes": 59863108,
      "domNodes": 63,
      "jsHeapTotalBytes": 23330816,
      "scriptDurationMs": 124.02199999999999,
      "eventListeners": 6,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333335,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "canvas-mouse-sweep",
      "durationMs": 2005.7130000000143,
      "styleRecalcs": 84,
      "styleRecalcDurationMs": 43.706,
      "layouts": 12,
      "layoutDurationMs": 3.615,
      "taskDurationMs": 1004.5880000000001,
      "heapDeltaBytes": 16804676,
      "heapUsedBytes": 60562792,
      "domNodes": 66,
      "jsHeapTotalBytes": 23855104,
      "scriptDurationMs": 131.658,
      "eventListeners": 6,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "canvas-mouse-sweep",
      "durationMs": 2033.2540000000563,
      "styleRecalcs": 84,
      "styleRecalcDurationMs": 46.599000000000004,
      "layouts": 12,
      "layoutDurationMs": 3.679,
      "taskDurationMs": 992.273,
      "heapDeltaBytes": 15741860,
      "heapUsedBytes": 59795972,
      "domNodes": 66,
      "jsHeapTotalBytes": 23330816,
      "scriptDurationMs": 123.77600000000001,
      "eventListeners": 6,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.699999999999818
    },
    {
      "name": "canvas-zoom-sweep",
      "durationMs": 1722.300999999959,
      "styleRecalcs": 32,
      "styleRecalcDurationMs": 19.523,
      "layouts": 6,
      "layoutDurationMs": 0.6479999999999999,
      "taskDurationMs": 340.702,
      "heapDeltaBytes": 120192,
      "heapUsedBytes": 50198284,
      "domNodes": 80,
      "jsHeapTotalBytes": 24641536,
      "scriptDurationMs": 28.918000000000006,
      "eventListeners": 19,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "canvas-zoom-sweep",
      "durationMs": 1723.9930000000072,
      "styleRecalcs": 31,
      "styleRecalcDurationMs": 18.531,
      "layouts": 6,
      "layoutDurationMs": 0.583,
      "taskDurationMs": 345.34,
      "heapDeltaBytes": 25059248,
      "heapUsedBytes": 68404440,
      "domNodes": 78,
      "jsHeapTotalBytes": 21233664,
      "scriptDurationMs": 29.375000000000004,
      "eventListeners": 19,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "canvas-zoom-sweep",
      "durationMs": 1756.7050000000108,
      "styleRecalcs": 32,
      "styleRecalcDurationMs": 18.109,
      "layouts": 6,
      "layoutDurationMs": 0.5789999999999998,
      "taskDurationMs": 340.98499999999996,
      "heapDeltaBytes": 25035484,
      "heapUsedBytes": 68707168,
      "domNodes": 80,
      "jsHeapTotalBytes": 21495808,
      "scriptDurationMs": 29.685999999999996,
      "eventListeners": 19,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.799999999999272
    },
    {
      "name": "dom-widget-clipping",
      "durationMs": 535.8960000000081,
      "styleRecalcs": 11,
      "styleRecalcDurationMs": 8.068,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 332.209,
      "heapDeltaBytes": 6754688,
      "heapUsedBytes": 50941180,
      "domNodes": 18,
      "jsHeapTotalBytes": 12582912,
      "scriptDurationMs": 55.81999999999999,
      "eventListeners": 2,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.799999999999727
    },
    {
      "name": "dom-widget-clipping",
      "durationMs": 540.6289999999672,
      "styleRecalcs": 11,
      "styleRecalcDurationMs": 7.531,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 339.7560000000001,
      "heapDeltaBytes": 6988660,
      "heapUsedBytes": 50677372,
      "domNodes": 18,
      "jsHeapTotalBytes": 12582912,
      "scriptDurationMs": 55.053000000000004,
      "eventListeners": 2,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.663333333333338,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "dom-widget-clipping",
      "durationMs": 521.6739999999618,
      "styleRecalcs": 13,
      "styleRecalcDurationMs": 8.412,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 320.06600000000003,
      "heapDeltaBytes": 7078280,
      "heapUsedBytes": 50949676,
      "domNodes": 22,
      "jsHeapTotalBytes": 13107200,
      "scriptDurationMs": 53.33200000000001,
      "eventListeners": 2,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.799999999999727
    },
    {
      "name": "large-graph-idle",
      "durationMs": 2019.2720000000008,
      "styleRecalcs": 9,
      "styleRecalcDurationMs": 8.466999999999999,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 611.2950000000001,
      "heapDeltaBytes": -530200,
      "heapUsedBytes": 52436624,
      "domNodes": -258,
      "jsHeapTotalBytes": 16158720,
      "scriptDurationMs": 110.22300000000001,
      "eventListeners": -127,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "large-graph-idle",
      "durationMs": 2039.9919999999838,
      "styleRecalcs": 10,
      "styleRecalcDurationMs": 9.774000000000001,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 605.134,
      "heapDeltaBytes": 3019668,
      "heapUsedBytes": 57210936,
      "domNodes": -260,
      "jsHeapTotalBytes": 16158720,
      "scriptDurationMs": 107.516,
      "eventListeners": -125,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "large-graph-idle",
      "durationMs": 2024.630000000002,
      "styleRecalcs": 9,
      "styleRecalcDurationMs": 8.258999999999999,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 561.7570000000001,
      "heapDeltaBytes": 4240700,
      "heapUsedBytes": 56862008,
      "domNodes": -261,
      "jsHeapTotalBytes": 16158720,
      "scriptDurationMs": 105.553,
      "eventListeners": -125,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "large-graph-pan",
      "durationMs": 2103.240000000028,
      "styleRecalcs": 68,
      "styleRecalcDurationMs": 17.722999999999995,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 1113.131,
      "heapDeltaBytes": -16057728,
      "heapUsedBytes": 48393432,
      "domNodes": -264,
      "jsHeapTotalBytes": 14962688,
      "scriptDurationMs": 386.898,
      "eventListeners": -125,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.799999999999272
    },
    {
      "name": "large-graph-pan",
      "durationMs": 2118.7229999999886,
      "styleRecalcs": 68,
      "styleRecalcDurationMs": 17.658000000000005,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 1125.1630000000002,
      "heapDeltaBytes": 9881820,
      "heapUsedBytes": 73253360,
      "domNodes": -261,
      "jsHeapTotalBytes": 15953920,
      "scriptDurationMs": 395.27299999999997,
      "eventListeners": -125,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.799999999999272
    },
    {
      "name": "large-graph-pan",
      "durationMs": 2129.1290000000345,
      "styleRecalcs": 68,
      "styleRecalcDurationMs": 16.778000000000002,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 1112.288,
      "heapDeltaBytes": 18709572,
      "heapUsedBytes": 73818132,
      "domNodes": -265,
      "jsHeapTotalBytes": 18460672,
      "scriptDurationMs": 390.156,
      "eventListeners": -125,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66999999999998,
      "p95FrameDurationMs": 16.699999999999818
    },
    {
      "name": "large-graph-zoom",
      "durationMs": 3182.278999999994,
      "styleRecalcs": 66,
      "styleRecalcDurationMs": 19.862999999999996,
      "layouts": 60,
      "layoutDurationMs": 7.72,
      "taskDurationMs": 1367.2300000000002,
      "heapDeltaBytes": 6022572,
      "heapUsedBytes": 62731940,
      "domNodes": -266,
      "jsHeapTotalBytes": 17731584,
      "scriptDurationMs": 492.93600000000004,
      "eventListeners": -123,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "large-graph-zoom",
      "durationMs": 3195.707999999968,
      "styleRecalcs": 64,
      "styleRecalcDurationMs": 18.284,
      "layouts": 60,
      "layoutDurationMs": 7.898000000000001,
      "taskDurationMs": 1363.857,
      "heapDeltaBytes": -11546724,
      "heapUsedBytes": 54436136,
      "domNodes": -269,
      "jsHeapTotalBytes": 13914112,
      "scriptDurationMs": 481.01899999999995,
      "eventListeners": -125,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "large-graph-zoom",
      "durationMs": 3161.811999999941,
      "styleRecalcs": 66,
      "styleRecalcDurationMs": 21.444000000000003,
      "layouts": 60,
      "layoutDurationMs": 7.804,
      "taskDurationMs": 1387.8999999999999,
      "heapDeltaBytes": 2144604,
      "heapUsedBytes": 57819524,
      "domNodes": -265,
      "jsHeapTotalBytes": 16945152,
      "scriptDurationMs": 494.738,
      "eventListeners": -123,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "minimap-idle",
      "durationMs": 2007.6589999999896,
      "styleRecalcs": 9,
      "styleRecalcDurationMs": 9.457,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 593.537,
      "heapDeltaBytes": 2532364,
      "heapUsedBytes": 58770964,
      "domNodes": -263,
      "jsHeapTotalBytes": 15896576,
      "scriptDurationMs": 100.11200000000001,
      "eventListeners": -125,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.670000000000012,
      "p95FrameDurationMs": 16.799999999999272
    },
    {
      "name": "minimap-idle",
      "durationMs": 2053.9920000000507,
      "styleRecalcs": 9,
      "styleRecalcDurationMs": 8.671999999999999,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 593.8860000000001,
      "heapDeltaBytes": 17189488,
      "heapUsedBytes": 72951656,
      "domNodes": -261,
      "jsHeapTotalBytes": 17207296,
      "scriptDurationMs": 104.661,
      "eventListeners": -123,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.699999999999818
    },
    {
      "name": "minimap-idle",
      "durationMs": 2020.799000000011,
      "styleRecalcs": 8,
      "styleRecalcDurationMs": 7.896,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 563.9970000000001,
      "heapDeltaBytes": 2075416,
      "heapUsedBytes": 70037104,
      "domNodes": -262,
      "jsHeapTotalBytes": 10592256,
      "scriptDurationMs": 96.82499999999999,
      "eventListeners": -127,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "subgraph-dom-widget-clipping",
      "durationMs": 588.6480000000347,
      "styleRecalcs": 48,
      "styleRecalcDurationMs": 12.184999999999999,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 375.319,
      "heapDeltaBytes": 6881052,
      "heapUsedBytes": 50894240,
      "domNodes": 21,
      "jsHeapTotalBytes": 12582912,
      "scriptDurationMs": 124.637,
      "eventListeners": 8,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000273
    },
    {
      "name": "subgraph-dom-widget-clipping",
      "durationMs": 563.5540000000105,
      "styleRecalcs": 47,
      "styleRecalcDurationMs": 11.988,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 376.843,
      "heapDeltaBytes": 8239548,
      "heapUsedBytes": 58627836,
      "domNodes": 19,
      "jsHeapTotalBytes": 14155776,
      "scriptDurationMs": 127.863,
      "eventListeners": 8,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.663333333333338,
      "p95FrameDurationMs": 16.700000000000273
    },
    {
      "name": "subgraph-dom-widget-clipping",
      "durationMs": 520.2900000000454,
      "styleRecalcs": 46,
      "styleRecalcDurationMs": 10.386000000000001,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 346.90600000000006,
      "heapDeltaBytes": 7236136,
      "heapUsedBytes": 51262064,
      "domNodes": 18,
      "jsHeapTotalBytes": 13369344,
      "scriptDurationMs": 114.83099999999999,
      "eventListeners": 8,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "subgraph-idle",
      "durationMs": 1990.9420000000182,
      "styleRecalcs": 8,
      "styleRecalcDurationMs": 7.2970000000000015,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 363.75600000000003,
      "heapDeltaBytes": 20105200,
      "heapUsedBytes": 64380256,
      "domNodes": 16,
      "jsHeapTotalBytes": 23068672,
      "scriptDurationMs": 17.903999999999996,
      "eventListeners": 6,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333335,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "subgraph-idle",
      "durationMs": 1992.5790000000347,
      "styleRecalcs": 10,
      "styleRecalcDurationMs": 9.484000000000002,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 393.99300000000005,
      "heapDeltaBytes": 20889284,
      "heapUsedBytes": 64998828,
      "domNodes": 20,
      "jsHeapTotalBytes": 22806528,
      "scriptDurationMs": 24.605999999999995,
      "eventListeners": 6,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "subgraph-idle",
      "durationMs": 1989.7220000000289,
      "styleRecalcs": 10,
      "styleRecalcDurationMs": 9.32,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 407.309,
      "heapDeltaBytes": 19953908,
      "heapUsedBytes": 64020672,
      "domNodes": 20,
      "jsHeapTotalBytes": 22806528,
      "scriptDurationMs": 23.317999999999998,
      "eventListeners": 6,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "subgraph-mouse-sweep",
      "durationMs": 1972.3189999999988,
      "styleRecalcs": 84,
      "styleRecalcDurationMs": 52.989,
      "layouts": 16,
      "layoutDurationMs": 5.347,
      "taskDurationMs": 1026.7279999999998,
      "heapDeltaBytes": 11869888,
      "heapUsedBytes": 56341064,
      "domNodes": 72,
      "jsHeapTotalBytes": 22806528,
      "scriptDurationMs": 103.65299999999999,
      "eventListeners": 6,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "subgraph-mouse-sweep",
      "durationMs": 1991.8810000000349,
      "styleRecalcs": 84,
      "styleRecalcDurationMs": 46.538999999999994,
      "layouts": 16,
      "layoutDurationMs": 4.757000000000001,
      "taskDurationMs": 941.278,
      "heapDeltaBytes": 12315724,
      "heapUsedBytes": 56240480,
      "domNodes": 72,
      "jsHeapTotalBytes": 22806528,
      "scriptDurationMs": 103.16,
      "eventListeners": 6,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "subgraph-mouse-sweep",
      "durationMs": 1975.274000000013,
      "styleRecalcs": 84,
      "styleRecalcDurationMs": 49.666000000000004,
      "layouts": 16,
      "layoutDurationMs": 4.869,
      "taskDurationMs": 961.8610000000001,
      "heapDeltaBytes": 12309248,
      "heapUsedBytes": 56604012,
      "domNodes": 73,
      "jsHeapTotalBytes": 23855104,
      "scriptDurationMs": 102.21,
      "eventListeners": 6,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "viewport-pan-sweep",
      "durationMs": 8138.081999999997,
      "styleRecalcs": 250,
      "styleRecalcDurationMs": 56.603,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 4008.1629999999996,
      "heapDeltaBytes": -1128652,
      "heapUsedBytes": 57874520,
      "domNodes": -76,
      "jsHeapTotalBytes": 21098496,
      "scriptDurationMs": 1305.8129999999999,
      "eventListeners": -68,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333332,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "viewport-pan-sweep",
      "durationMs": 8129.129999999976,
      "styleRecalcs": 249,
      "styleRecalcDurationMs": 53.19,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 3891.354,
      "heapDeltaBytes": 16829572,
      "heapUsedBytes": 69975856,
      "domNodes": -262,
      "jsHeapTotalBytes": 20295680,
      "scriptDurationMs": 1271.4669999999999,
      "eventListeners": -111,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "viewport-pan-sweep",
      "durationMs": 8188.031000000024,
      "styleRecalcs": 251,
      "styleRecalcDurationMs": 54.337999999999994,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 3838.8100000000004,
      "heapDeltaBytes": 22724168,
      "heapUsedBytes": 75306144,
      "domNodes": -259,
      "jsHeapTotalBytes": 20033536,
      "scriptDurationMs": 1272.316,
      "eventListeners": -111,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.669999999999952,
      "p95FrameDurationMs": 16.799999999999272
    },
    {
      "name": "vue-large-graph-idle",
      "durationMs": 10788.685999999983,
      "styleRecalcs": 0,
      "styleRecalcDurationMs": 0,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 10775.095000000001,
      "heapDeltaBytes": -50304928,
      "heapUsedBytes": 166710780,
      "domNodes": -9850,
      "jsHeapTotalBytes": 21557248,
      "scriptDurationMs": 590.8910000000001,
      "eventListeners": -23962,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 17.220000000000073,
      "p95FrameDurationMs": 16.799999999999272
    },
    {
      "name": "vue-large-graph-idle",
      "durationMs": 11069.491000000027,
      "styleRecalcs": 0,
      "styleRecalcDurationMs": 0,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 11020.983,
      "heapDeltaBytes": -41324768,
      "heapUsedBytes": 156120852,
      "domNodes": -9850,
      "jsHeapTotalBytes": -28774400,
      "scriptDurationMs": 639.161,
      "eventListeners": -23965,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 17.219999999999953,
      "p95FrameDurationMs": 16.80000000000291
    },
    {
      "name": "vue-large-graph-idle",
      "durationMs": 10914.434000000028,
      "styleRecalcs": 0,
      "styleRecalcDurationMs": 0,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 10900.104000000001,
      "heapDeltaBytes": -64070080,
      "heapUsedBytes": 160555368,
      "domNodes": -9852,
      "jsHeapTotalBytes": 15917056,
      "scriptDurationMs": 575.4830000000001,
      "eventListeners": -23965,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 17.219999999999953,
      "p95FrameDurationMs": 16.799999999999272
    },
    {
      "name": "vue-large-graph-pan",
      "durationMs": 13064.82500000004,
      "styleRecalcs": 67,
      "styleRecalcDurationMs": 18.137999999999987,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 13044.618999999997,
      "heapDeltaBytes": -63368692,
      "heapUsedBytes": 162550984,
      "domNodes": -9850,
      "jsHeapTotalBytes": -12259328,
      "scriptDurationMs": 892.328,
      "eventListeners": -23960,
      "totalBlockingTimeMs": 60,
      "frameDurationMs": 17.223333333333358,
      "p95FrameDurationMs": 16.799999999999272
    },
    {
      "name": "vue-large-graph-pan",
      "durationMs": 13103.02999999999,
      "styleRecalcs": 65,
      "styleRecalcDurationMs": 18.154000000000003,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 13056.247999999998,
      "heapDeltaBytes": -51616988,
      "heapUsedBytes": 174358324,
      "domNodes": -9850,
      "jsHeapTotalBytes": -25452544,
      "scriptDurationMs": 892.0920000000001,
      "eventListeners": -23957,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 17.776666666666642,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "vue-large-graph-pan",
      "durationMs": 12890.629999999986,
      "styleRecalcs": 66,
      "styleRecalcDurationMs": 18.04,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 12862.792999999998,
      "heapDeltaBytes": -51065328,
      "heapUsedBytes": 161885800,
      "domNodes": -9850,
      "jsHeapTotalBytes": -12521472,
      "scriptDurationMs": 868.139,
      "eventListeners": -23959,
      "totalBlockingTimeMs": 55,
      "frameDurationMs": 17.77333333333336,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "workflow-execution",
      "durationMs": 138.00699999995913,
      "styleRecalcs": 11,
      "styleRecalcDurationMs": 19.349999999999998,
      "layouts": 5,
      "layoutDurationMs": 1.6700000000000002,
      "taskDurationMs": 110.47200000000001,
      "heapDeltaBytes": 3457116,
      "heapUsedBytes": 55014268,
      "domNodes": 149,
      "jsHeapTotalBytes": 262144,
      "scriptDurationMs": 19.766,
      "eventListeners": 37,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.663333333333338,
      "p95FrameDurationMs": 16.700000000000273
    },
    {
      "name": "workflow-execution",
      "durationMs": 462.87900000004356,
      "styleRecalcs": 16,
      "styleRecalcDurationMs": 22.538000000000004,
      "layouts": 5,
      "layoutDurationMs": 1.451,
      "taskDurationMs": 121.04099999999998,
      "heapDeltaBytes": 4960856,
      "heapUsedBytes": 49979680,
      "domNodes": 154,
      "jsHeapTotalBytes": 0,
      "scriptDurationMs": 23.534000000000002,
      "eventListeners": 71,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000273
    },
    {
      "name": "workflow-execution",
      "durationMs": 462.89600000000064,
      "styleRecalcs": 14,
      "styleRecalcDurationMs": 24.495,
      "layouts": 5,
      "layoutDurationMs": 1.5729999999999997,
      "taskDurationMs": 126.384,
      "heapDeltaBytes": 4965988,
      "heapUsedBytes": 50278148,
      "domNodes": 152,
      "jsHeapTotalBytes": 262144,
      "scriptDurationMs": 25.984,
      "eventListeners": 71,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000273
    }
  ]
}

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

🧹 Nitpick comments (1)
src/platform/workspace/stores/useWorkspaceAuth.test.ts (1)

734-742: Add EXPIRES_AT assertions in the new persistence/race checks

These updated tests validate CURRENT_WORKSPACE and TOKEN, but not EXPIRES_AT. Since stale commits also write expiry, asserting it here would close the regression surface for stale overwrite/preserve behavior.

Suggested assertion additions
       expect(sessionStorage.getItem(WORKSPACE_STORAGE_KEYS.TOKEN)).toBe(
         'workspace-token-abc'
       )
+      expect(
+        sessionStorage.getItem(WORKSPACE_STORAGE_KEYS.EXPIRES_AT)
+      ).toBeTruthy()
       expect(sessionStorage.getItem(WORKSPACE_STORAGE_KEYS.TOKEN)).toBe(
         'new-workspace-token'
       )
+      expect(
+        sessionStorage.getItem(WORKSPACE_STORAGE_KEYS.EXPIRES_AT)
+      ).toBeTruthy()

As per coding guidelines "Write tests for all changes, especially bug fixes to catch future regressions."

Also applies to: 823-828, 841-846

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

In `@src/platform/workspace/stores/useWorkspaceAuth.test.ts` around lines 734 -
742, Add assertions that the expiry value is persisted and preserved: after the
existing checks on currentWorkspace and workspaceToken, assert that
sessionStorage.getItem(WORKSPACE_STORAGE_KEYS.EXPIRES_AT) equals the expected
expiry value used in the test setup (e.g. the mock expiry value or
mockWorkspaceWithRole.expiresAt string), and if the test stores expiry as JSON
ensure you compare the same serialized form; apply the same EXPIRES_AT assertion
in the other two assertion blocks referenced (the similar checks around the
other transient/persistence scenarios).
🤖 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/platform/workspace/stores/workspaceAuthStore.ts`:
- Around line 348-354: When the transient-failure branch (isTransientError &&
hasValidWorkspaceToken()) decides to preserve the existing token, do not return
while the last thrown WorkspaceAuthError remains set and no future refresh is
scheduled; instead clear the stored error state (reset the local/instance
"error" / "workspaceAuthError" variable or call the store's clearError helper)
and enqueue a future refresh attempt by invoking the store's refresh scheduling
helper (e.g., scheduleRefresh, scheduleWorkspaceRefresh, or re-arm the existing
timer to call switchWorkspace again after a backoff). This preserves the valid
token, removes the lingering error, and ensures a proactive retry without
changing the branch's intent.

---

Nitpick comments:
In `@src/platform/workspace/stores/useWorkspaceAuth.test.ts`:
- Around line 734-742: Add assertions that the expiry value is persisted and
preserved: after the existing checks on currentWorkspace and workspaceToken,
assert that sessionStorage.getItem(WORKSPACE_STORAGE_KEYS.EXPIRES_AT) equals the
expected expiry value used in the test setup (e.g. the mock expiry value or
mockWorkspaceWithRole.expiresAt string), and if the test stores expiry as JSON
ensure you compare the same serialized form; apply the same EXPIRES_AT assertion
in the other two assertion blocks referenced (the similar checks around the
other transient/persistence scenarios).
🪄 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: 2092171e-46dd-4ce0-9280-881cffb78c34

📥 Commits

Reviewing files that changed from the base of the PR and between 967f1eb and 5584911.

📒 Files selected for processing (2)
  • src/platform/workspace/stores/useWorkspaceAuth.test.ts
  • src/platform/workspace/stores/workspaceAuthStore.ts

Comment thread src/platform/workspace/stores/workspaceAuthStore.ts
@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 28, 2026

Codecov Report

❌ Patch coverage is 87.09677% with 4 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...rc/platform/workspace/stores/workspaceAuthStore.ts 87.09% 4 Missing ⚠️
@@             Coverage Diff             @@
##             main   #11726       +/-   ##
===========================================
- Coverage   69.55%   51.55%   -18.01%     
===========================================
  Files        1485     1376      -109     
  Lines       83671    70377    -13294     
  Branches    23029    19567     -3462     
===========================================
- Hits        58201    36280    -21921     
- Misses      24525    33498     +8973     
+ Partials      945      599      -346     
Flag Coverage Δ
e2e ?
unit 51.55% <87.09%> (+0.01%) ⬆️

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

Files with missing lines Coverage Δ
...rc/platform/workspace/stores/workspaceAuthStore.ts 89.24% <87.09%> (+25.83%) ⬆️

... and 1003 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.

coderabbitai[bot]
coderabbitai Bot previously approved these changes Apr 28, 2026
Copy link
Copy Markdown
Member Author

@benceruleanlu benceruleanlu left a comment

Choose a reason for hiding this comment

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

mind boggling!

Comment thread src/platform/workspace/stores/workspaceAuthStore.ts
@benceruleanlu benceruleanlu marked this pull request as ready for review April 28, 2026 15:19
@benceruleanlu benceruleanlu requested a review from a team April 28, 2026 15:19
@dosubot dosubot Bot added the size:M This PR changes 30-99 lines, ignoring generated files. label Apr 28, 2026
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c3bcc28fbc

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +282 to +286
if (capturedRequestId !== refreshRequestId) {
console.warn(
'Aborting stale workspace switch: workspace context changed before commit'
)
return
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Don't discard old-workspace refresh when new switch fails

This guard drops any in-flight refresh response as soon as refreshRequestId changes, but refreshRequestId is incremented before a new switchWorkspace request is known to succeed. If refreshToken() for workspace A is in flight, then switchWorkspace('B') fails (e.g. 403), workspace A remains active but the successful refresh response for A is discarded here; refreshToken() then returns without committing a new token/schedule, so A can continue with an aging token until expiration. The stale check should also account for whether currentWorkspace actually changed away from the refreshed workspace before aborting.

Useful? React with 👍 / 👎.

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

🧹 Nitpick comments (1)
src/platform/workspace/stores/useWorkspaceAuth.test.ts (1)

825-833: Strengthen stale-race regression by asserting EXPIRES_AT is not clobbered

The test currently protects CURRENT_WORKSPACE and TOKEN, but not EXPIRES_AT. Using distinct expiries for “new” vs “stale” responses would catch expiry-key overwrites too.

✅ Suggested test tightening
-      mockFetch.mockResolvedValueOnce({
+      const newExpiry = new Date(Date.now() + 7200 * 1000).toISOString()
+      mockFetch.mockResolvedValueOnce({
         ok: true,
         json: () =>
           Promise.resolve({
             ...mockTokenResponse,
             token: 'new-workspace-token',
+            expires_at: newExpiry,
             workspace: newWorkspace
           })
       })
@@
       expect(sessionStorage.getItem(WORKSPACE_STORAGE_KEYS.TOKEN)).toBe(
         'new-workspace-token'
       )
+      const expectedNewExpiryMs = new Date(newExpiry).getTime().toString()
+      expect(sessionStorage.getItem(WORKSPACE_STORAGE_KEYS.EXPIRES_AT)).toBe(
+        expectedNewExpiryMs
+      )
@@
       expect(sessionStorage.getItem(WORKSPACE_STORAGE_KEYS.TOKEN)).toBe(
         'new-workspace-token'
       )
+      expect(sessionStorage.getItem(WORKSPACE_STORAGE_KEYS.EXPIRES_AT)).toBe(
+        expectedNewExpiryMs
+      )

As per coding guidelines, "Write tests for all changes, especially bug fixes to catch future regressions".

Also applies to: 857-862

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

In `@src/platform/workspace/stores/useWorkspaceAuth.test.ts` around lines 825 -
833, The test currently only protects CURRENT_WORKSPACE and TOKEN but not
EXPIRES_AT; update the mocked responses (the mockTokenResponse used in
mockFetch.mockResolvedValueOnce and the subsequent mock) to include distinct
expiry values (e.g., expiresAt: 'stale-ts' vs 'new-ts' or numeric EXPIRES_AT)
for the "stale" and "new" responses and then add assertions that the stored
EXPIRES_AT value (the key your code writes, e.g., EXPIRES_AT) equals the
expected new expiry after the race-resolve path and was not clobbered by the
stale response; modify both places the mock is set (the block using
mockTokenResponse at the shown diff and the later similar mock at the other
occurrence) and add an expect(...) asserting EXPIRES_AT remains the new value.
🤖 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/platform/workspace/stores/workspaceAuthStore.ts`:
- Around line 202-203: The loading state can be cleared by stale overlapping
calls to switchWorkspace; modify switchWorkspace to generate a unique request
id/token at start (set isLoading.value = true; error.value = null), store it on
the store (e.g., currentSwitchId), and capture it in the async call; only clear
isLoading.value and update error.value when the captured id matches the
store.currentSwitchId. Apply the same request-id check to the success and error
branches where isLoading is set to false (the code around switchWorkspace and
the lines that currently set isLoading.value = false / error.value = ...).

---

Nitpick comments:
In `@src/platform/workspace/stores/useWorkspaceAuth.test.ts`:
- Around line 825-833: The test currently only protects CURRENT_WORKSPACE and
TOKEN but not EXPIRES_AT; update the mocked responses (the mockTokenResponse
used in mockFetch.mockResolvedValueOnce and the subsequent mock) to include
distinct expiry values (e.g., expiresAt: 'stale-ts' vs 'new-ts' or numeric
EXPIRES_AT) for the "stale" and "new" responses and then add assertions that the
stored EXPIRES_AT value (the key your code writes, e.g., EXPIRES_AT) equals the
expected new expiry after the race-resolve path and was not clobbered by the
stale response; modify both places the mock is set (the block using
mockTokenResponse at the shown diff and the later similar mock at the other
occurrence) and add an expect(...) asserting EXPIRES_AT remains the new value.
🪄 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: ed5c4973-a24b-48e6-b915-1eb4a76db3c3

📥 Commits

Reviewing files that changed from the base of the PR and between 5584911 and c3bcc28.

📒 Files selected for processing (2)
  • src/platform/workspace/stores/useWorkspaceAuth.test.ts
  • src/platform/workspace/stores/workspaceAuthStore.ts

Comment on lines 202 to 203
isLoading.value = true
error.value = null
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

isLoading can be cleared by a stale request while a newer switch is still pending

Line 306 always sets isLoading to false, so with overlapping switchWorkspace calls, an older stale request can hide loading state for the active request.

💡 Proposed fix (track in-flight switches)
   // Timer state
   let refreshTimerId: ReturnType<typeof setTimeout> | null = null
+  let inFlightSwitchCount = 0
@@
-    isLoading.value = true
+    inFlightSwitchCount += 1
+    isLoading.value = true
     error.value = null
@@
     } finally {
-      isLoading.value = false
+      inFlightSwitchCount = Math.max(0, inFlightSwitchCount - 1)
+      isLoading.value = inFlightSwitchCount > 0
     }
   }

Also applies to: 305-307

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

In `@src/platform/workspace/stores/workspaceAuthStore.ts` around lines 202 - 203,
The loading state can be cleared by stale overlapping calls to switchWorkspace;
modify switchWorkspace to generate a unique request id/token at start (set
isLoading.value = true; error.value = null), store it on the store (e.g.,
currentSwitchId), and capture it in the async call; only clear isLoading.value
and update error.value when the captured id matches the store.currentSwitchId.
Apply the same request-id check to the success and error branches where
isLoading is set to false (the code around switchWorkspace and the lines that
currently set isLoading.value = false / error.value = ...).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:M This PR changes 30-99 lines, ignoring generated files. symphony

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants