Skip to content

fix: add jitter, toasts, and cleanup to workspace token refresh#12276

Open
christian-byrne wants to merge 1 commit into
fix/be-186-session-logoutfrom
enhance/retry-utility-with-jitter
Open

fix: add jitter, toasts, and cleanup to workspace token refresh#12276
christian-byrne wants to merge 1 commit into
fix/be-186-session-logoutfrom
enhance/retry-utility-with-jitter

Conversation

@christian-byrne
Copy link
Copy Markdown
Contributor

@christian-byrne christian-byrne commented May 14, 2026

Summary

Enhancements to PR #11109 based on code review feedback:

  • Jitter: Added random jitter to retry delays to prevent thundering herd when backend recovers
  • Toast notifications: User-facing feedback during retries (warn: "Reconnecting...", error: "Session expired")
  • AbortController: Proper cleanup when context is cleared or component unmounts
  • Context preservation: Keep workspace session when token still valid but refresh fails transiently

Changes

  • workspaceAuthStore.ts: Add jitter to delays, toast notifications, AbortController cleanup
  • main.json: i18n keys for toast messages
  • useWorkspaceAuth.test.ts: Update tests for jitter-aware delays and context preservation behavior

Test plan

  • pnpm test:unit - all 35 workspace auth tests pass
  • pnpm typecheck - no errors
  • pnpm lint - no errors
  • pnpm format:check - passes

🤖 Generated with Claude Code

┆Issue is synchronized with this Notion page by Unito

- Add jitter to retry delays to prevent thundering herd on backend recovery
- Add toast notifications for retry states (retrying, degraded, expired)
- Add AbortController for cleanup on destroy() and clearWorkspaceContext()
- Preserve workspace context when token still valid but refresh fails
- Update tests for new behavior (jitter-aware delays, context preservation)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 14, 2026

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 1c9f0693-3f6d-451e-9afa-99c9c419446d

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch enhance/retry-utility-with-jitter

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

@dosubot dosubot Bot added the size:M This PR changes 30-99 lines, ignoring generated files. label May 14, 2026
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 14, 2026

🎨 Storybook: ✅ Built — View Storybook

Details

⏰ Completed at: 05/14/2026, 08:10:31 PM UTC

Links

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 14, 2026

🎭 Playwright: ✅ 1471 passed, 0 failed

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

@codecov
Copy link
Copy Markdown

codecov Bot commented May 14, 2026

Codecov Report

❌ Patch coverage is 95.45455% with 1 line in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...rc/platform/workspace/stores/workspaceAuthStore.ts 95.45% 1 Missing ⚠️
@@                     Coverage Diff                      @@
##             fix/be-186-session-logout   #12276   +/-   ##
============================================================
  Coverage                             ?   56.11%           
============================================================
  Files                                ?     1385           
  Lines                                ?    70933           
  Branches                             ?    19775           
============================================================
  Hits                                 ?    39807           
  Misses                               ?    30596           
  Partials                             ?      530           
Flag Coverage Δ
unit 56.11% <95.45%> (?)

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 90.19% <95.45%> (ø)
🚀 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.

@github-actions
Copy link
Copy Markdown

📦 Bundle Size

⏳ Size data collection in progress…

⚡ Performance Report

canvas-idle: · 60.0 avg FPS · 59.5 P5 FPS ✅ (target: ≥52) · 0ms TBT · 69.4 MB heap
canvas-mouse-sweep: · 60.0 avg FPS · 59.9 P5 FPS ✅ (target: ≥52) · 0ms TBT · 50.7 MB heap
canvas-zoom-sweep: · 60.0 avg FPS · 59.9 P5 FPS ✅ (target: ≥52) · 0ms TBT · 46.3 MB heap
dom-widget-clipping: · 60.0 avg FPS · 59.9 P5 FPS ✅ (target: ≥52) · 0ms TBT · 54.6 MB heap
large-graph-idle: · 60.0 avg FPS · 59.9 P5 FPS ✅ (target: ≥52) · 0ms TBT · 65.6 MB heap
large-graph-pan: · 60.0 avg FPS · 59.5 P5 FPS ✅ (target: ≥52) · 0ms TBT · 63.7 MB heap
large-graph-zoom: · 60.0 avg FPS · 59.9 P5 FPS ✅ (target: ≥52) · 0ms TBT · 73.6 MB heap
minimap-idle: · 60.0 avg FPS · 59.9 P5 FPS ✅ (target: ≥52) · 0ms TBT · 66.9 MB heap
subgraph-dom-widget-clipping: · 60.0 avg FPS · 59.9 P5 FPS ✅ (target: ≥52) · 0ms TBT · 54.9 MB heap
subgraph-idle: · 60.0 avg FPS · 59.9 P5 FPS ✅ (target: ≥52) · 0ms TBT · 67.7 MB heap
subgraph-mouse-sweep: · 60.0 avg FPS · 59.5 P5 FPS ✅ (target: ≥52) · 0ms TBT · 60.0 MB heap
viewport-pan-sweep: · 60.0 avg FPS · 59.9 P5 FPS ✅ (target: ≥52) · 0ms TBT · 80.4 MB heap
vue-large-graph-idle: · 56.3 avg FPS · 59.5 P5 FPS ✅ (target: ≥52) · 0ms TBT · 161.5 MB heap
vue-large-graph-pan: · 56.3 avg FPS · 59.5 P5 FPS ✅ (target: ≥52) · 6ms TBT · 157.4 MB heap
workflow-execution: · 60.0 avg FPS · 59.9 P5 FPS ✅ (target: ≥52) · 0ms TBT · 51.8 MB heap

No baseline found — showing absolute values.

Metric Value
canvas-idle: avg frame time 17ms
canvas-idle: p95 frame time 17ms
canvas-idle: layout duration 0ms
canvas-idle: style recalc duration 9ms
canvas-idle: layout count 0
canvas-idle: style recalc count 8
canvas-idle: task duration 412ms
canvas-idle: script duration 22ms
canvas-idle: TBT 0ms
canvas-idle: heap used 69.4 MB
canvas-idle: DOM nodes 16
canvas-idle: event listeners 4
canvas-mouse-sweep: avg frame time 17ms
canvas-mouse-sweep: p95 frame time 17ms
canvas-mouse-sweep: layout duration 4ms
canvas-mouse-sweep: style recalc duration 45ms
canvas-mouse-sweep: layout count 12
canvas-mouse-sweep: style recalc count 77
canvas-mouse-sweep: task duration 1020ms
canvas-mouse-sweep: script duration 137ms
canvas-mouse-sweep: TBT 0ms
canvas-mouse-sweep: heap used 50.7 MB
canvas-mouse-sweep: DOM nodes -265
canvas-mouse-sweep: event listeners -133
canvas-zoom-sweep: avg frame time 17ms
canvas-zoom-sweep: p95 frame time 17ms
canvas-zoom-sweep: layout duration 1ms
canvas-zoom-sweep: style recalc duration 18ms
canvas-zoom-sweep: layout count 6
canvas-zoom-sweep: style recalc count 31
canvas-zoom-sweep: task duration 318ms
canvas-zoom-sweep: script duration 27ms
canvas-zoom-sweep: TBT 0ms
canvas-zoom-sweep: heap used 46.3 MB
canvas-zoom-sweep: DOM nodes 76
canvas-zoom-sweep: event listeners 19
dom-widget-clipping: avg frame time 17ms
dom-widget-clipping: p95 frame time 17ms
dom-widget-clipping: layout duration 0ms
dom-widget-clipping: style recalc duration 9ms
dom-widget-clipping: layout count 0
dom-widget-clipping: style recalc count 12
dom-widget-clipping: task duration 371ms
dom-widget-clipping: script duration 66ms
dom-widget-clipping: TBT 0ms
dom-widget-clipping: heap used 54.6 MB
dom-widget-clipping: DOM nodes 20
dom-widget-clipping: event listeners 0
large-graph-idle: avg frame time 17ms
large-graph-idle: p95 frame time 17ms
large-graph-idle: layout duration 0ms
large-graph-idle: style recalc duration 8ms
large-graph-idle: layout count 0
large-graph-idle: style recalc count 8
large-graph-idle: task duration 670ms
large-graph-idle: script duration 106ms
large-graph-idle: TBT 0ms
large-graph-idle: heap used 65.6 MB
large-graph-idle: DOM nodes -263
large-graph-idle: event listeners -129
large-graph-pan: avg frame time 17ms
large-graph-pan: p95 frame time 17ms
large-graph-pan: layout duration 0ms
large-graph-pan: style recalc duration 21ms
large-graph-pan: layout count 0
large-graph-pan: style recalc count 68
large-graph-pan: task duration 1219ms
large-graph-pan: script duration 411ms
large-graph-pan: TBT 0ms
large-graph-pan: heap used 63.7 MB
large-graph-pan: DOM nodes -261
large-graph-pan: event listeners -127
large-graph-zoom: avg frame time 17ms
large-graph-zoom: p95 frame time 17ms
large-graph-zoom: layout duration 9ms
large-graph-zoom: style recalc duration 20ms
large-graph-zoom: layout count 60
large-graph-zoom: style recalc count 64
large-graph-zoom: task duration 1409ms
large-graph-zoom: script duration 513ms
large-graph-zoom: TBT 0ms
large-graph-zoom: heap used 73.6 MB
large-graph-zoom: DOM nodes -270
large-graph-zoom: event listeners -127
minimap-idle: avg frame time 17ms
minimap-idle: p95 frame time 17ms
minimap-idle: layout duration 0ms
minimap-idle: style recalc duration 8ms
minimap-idle: layout count 0
minimap-idle: style recalc count 8
minimap-idle: task duration 607ms
minimap-idle: script duration 100ms
minimap-idle: TBT 0ms
minimap-idle: heap used 66.9 MB
minimap-idle: DOM nodes -264
minimap-idle: event listeners -129
subgraph-dom-widget-clipping: avg frame time 17ms
subgraph-dom-widget-clipping: p95 frame time 17ms
subgraph-dom-widget-clipping: layout duration 0ms
subgraph-dom-widget-clipping: style recalc duration 13ms
subgraph-dom-widget-clipping: layout count 0
subgraph-dom-widget-clipping: style recalc count 47
subgraph-dom-widget-clipping: task duration 402ms
subgraph-dom-widget-clipping: script duration 131ms
subgraph-dom-widget-clipping: TBT 0ms
subgraph-dom-widget-clipping: heap used 54.9 MB
subgraph-dom-widget-clipping: DOM nodes 20
subgraph-dom-widget-clipping: event listeners 8
subgraph-idle: avg frame time 17ms
subgraph-idle: p95 frame time 17ms
subgraph-idle: layout duration 0ms
subgraph-idle: style recalc duration 10ms
subgraph-idle: layout count 0
subgraph-idle: style recalc count 10
subgraph-idle: task duration 443ms
subgraph-idle: script duration 22ms
subgraph-idle: TBT 0ms
subgraph-idle: heap used 67.7 MB
subgraph-idle: DOM nodes 20
subgraph-idle: event listeners 4
subgraph-mouse-sweep: avg frame time 17ms
subgraph-mouse-sweep: p95 frame time 17ms
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 76
subgraph-mouse-sweep: task duration 733ms
subgraph-mouse-sweep: script duration 106ms
subgraph-mouse-sweep: TBT 0ms
subgraph-mouse-sweep: heap used 60.0 MB
subgraph-mouse-sweep: DOM nodes 63
subgraph-mouse-sweep: event listeners 4
viewport-pan-sweep: avg frame time 17ms
viewport-pan-sweep: p95 frame time 17ms
viewport-pan-sweep: layout duration 0ms
viewport-pan-sweep: style recalc duration 62ms
viewport-pan-sweep: layout count 0
viewport-pan-sweep: style recalc count 249
viewport-pan-sweep: task duration 4283ms
viewport-pan-sweep: script duration 1391ms
viewport-pan-sweep: TBT 0ms
viewport-pan-sweep: heap used 80.4 MB
viewport-pan-sweep: DOM nodes -261
viewport-pan-sweep: event listeners -113
vue-large-graph-idle: avg frame time 18ms
vue-large-graph-idle: p95 frame time 17ms
vue-large-graph-idle: layout duration 0ms
vue-large-graph-idle: style recalc duration 0ms
vue-large-graph-idle: layout count 0
vue-large-graph-idle: style recalc count 0
vue-large-graph-idle: task duration 13818ms
vue-large-graph-idle: script duration 606ms
vue-large-graph-idle: TBT 0ms
vue-large-graph-idle: heap used 161.5 MB
vue-large-graph-idle: DOM nodes -8331
vue-large-graph-idle: event listeners -16462
vue-large-graph-pan: avg frame time 18ms
vue-large-graph-pan: p95 frame time 17ms
vue-large-graph-pan: layout duration 0ms
vue-large-graph-pan: style recalc duration 22ms
vue-large-graph-pan: layout count 0
vue-large-graph-pan: style recalc count 82
vue-large-graph-pan: task duration 15921ms
vue-large-graph-pan: script duration 911ms
vue-large-graph-pan: TBT 6ms
vue-large-graph-pan: heap used 157.4 MB
vue-large-graph-pan: DOM nodes -8331
vue-large-graph-pan: event listeners -16458
workflow-execution: avg frame time 17ms
workflow-execution: p95 frame time 17ms
workflow-execution: layout duration 2ms
workflow-execution: style recalc duration 23ms
workflow-execution: layout count 5
workflow-execution: style recalc count 14
workflow-execution: task duration 131ms
workflow-execution: script duration 23ms
workflow-execution: TBT 0ms
workflow-execution: heap used 51.8 MB
workflow-execution: DOM nodes 148
workflow-execution: event listeners 37
Raw data
{
  "timestamp": "2026-05-14T20:23:08.155Z",
  "gitSha": "287902b7ec298ca41632911b9deea6536d5970fe",
  "branch": "enhance/retry-utility-with-jitter",
  "measurements": [
    {
      "name": "canvas-idle",
      "durationMs": 2065.1249999999945,
      "styleRecalcs": 8,
      "styleRecalcDurationMs": 8.669999999999996,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 412.2260000000001,
      "heapDeltaBytes": 23448104,
      "heapUsedBytes": 72777860,
      "domNodes": 16,
      "jsHeapTotalBytes": 14680064,
      "scriptDurationMs": 21.531000000000002,
      "eventListeners": 4,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333332,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "canvas-idle",
      "durationMs": 2059.1699999999946,
      "styleRecalcs": 10,
      "styleRecalcDurationMs": 9.274999999999999,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 430.706,
      "heapDeltaBytes": 23793488,
      "heapUsedBytes": 72734064,
      "domNodes": 20,
      "jsHeapTotalBytes": 14942208,
      "scriptDurationMs": 30.154000000000003,
      "eventListeners": 6,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "canvas-idle",
      "durationMs": 2032.8139999999166,
      "styleRecalcs": 8,
      "styleRecalcDurationMs": 7.75,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 402.65099999999995,
      "heapDeltaBytes": 23104576,
      "heapUsedBytes": 71446740,
      "domNodes": 16,
      "jsHeapTotalBytes": 15728640,
      "scriptDurationMs": 22.16100000000001,
      "eventListeners": 4,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "canvas-mouse-sweep",
      "durationMs": 1998.1149999999843,
      "styleRecalcs": 77,
      "styleRecalcDurationMs": 51.892,
      "layouts": 12,
      "layoutDurationMs": 4.97,
      "taskDurationMs": 1019.8069999999999,
      "heapDeltaBytes": 2010464,
      "heapUsedBytes": 50811160,
      "domNodes": -265,
      "jsHeapTotalBytes": 15855616,
      "scriptDurationMs": 141.184,
      "eventListeners": -131,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.699999999999818
    },
    {
      "name": "canvas-mouse-sweep",
      "durationMs": 1902.4299999999812,
      "styleRecalcs": 74,
      "styleRecalcDurationMs": 41.228,
      "layouts": 12,
      "layoutDurationMs": 4.034000000000001,
      "taskDurationMs": 855.972,
      "heapDeltaBytes": 4827788,
      "heapUsedBytes": 53119108,
      "domNodes": -262,
      "jsHeapTotalBytes": 15331328,
      "scriptDurationMs": 127.36,
      "eventListeners": -133,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333335,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "canvas-mouse-sweep",
      "durationMs": 2067.3790000000736,
      "styleRecalcs": 80,
      "styleRecalcDurationMs": 45.152,
      "layouts": 12,
      "layoutDurationMs": 3.8429999999999995,
      "taskDurationMs": 1066.203,
      "heapDeltaBytes": 6361388,
      "heapUsedBytes": 54721092,
      "domNodes": -266,
      "jsHeapTotalBytes": 16379904,
      "scriptDurationMs": 136.974,
      "eventListeners": -133,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "canvas-zoom-sweep",
      "durationMs": 1730.2329999999984,
      "styleRecalcs": 31,
      "styleRecalcDurationMs": 19.654,
      "layouts": 6,
      "layoutDurationMs": 0.8099999999999997,
      "taskDurationMs": 392.4920000000001,
      "heapDeltaBytes": -80480,
      "heapUsedBytes": 47925704,
      "domNodes": 76,
      "jsHeapTotalBytes": 15204352,
      "scriptDurationMs": 29.781000000000002,
      "eventListeners": 19,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "canvas-zoom-sweep",
      "durationMs": 1718.7009999999532,
      "styleRecalcs": 31,
      "styleRecalcDurationMs": 17.673,
      "layouts": 6,
      "layoutDurationMs": 0.642,
      "taskDurationMs": 315.35900000000004,
      "heapDeltaBytes": 132716,
      "heapUsedBytes": 48549216,
      "domNodes": 76,
      "jsHeapTotalBytes": 14942208,
      "scriptDurationMs": 22.776000000000003,
      "eventListeners": 19,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.699999999999818
    },
    {
      "name": "canvas-zoom-sweep",
      "durationMs": 1736.7080000000215,
      "styleRecalcs": 32,
      "styleRecalcDurationMs": 17.491,
      "layouts": 6,
      "layoutDurationMs": 0.6150000000000001,
      "taskDurationMs": 318.009,
      "heapDeltaBytes": 7871904,
      "heapUsedBytes": 75794480,
      "domNodes": 78,
      "jsHeapTotalBytes": 18874368,
      "scriptDurationMs": 27.118999999999996,
      "eventListeners": 21,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66999999999998,
      "p95FrameDurationMs": 16.799999999999272
    },
    {
      "name": "dom-widget-clipping",
      "durationMs": 589.6809999999846,
      "styleRecalcs": 12,
      "styleRecalcDurationMs": 8.908000000000003,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 389.864,
      "heapDeltaBytes": -10589688,
      "heapUsedBytes": 57412924,
      "domNodes": 20,
      "jsHeapTotalBytes": 20185088,
      "scriptDurationMs": 70.049,
      "eventListeners": 0,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000273
    },
    {
      "name": "dom-widget-clipping",
      "durationMs": 570.5600000000004,
      "styleRecalcs": 10,
      "styleRecalcDurationMs": 7.6679999999999975,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 363.55600000000004,
      "heapDeltaBytes": 8498344,
      "heapUsedBytes": 57201876,
      "domNodes": 16,
      "jsHeapTotalBytes": 16252928,
      "scriptDurationMs": 62.132,
      "eventListeners": 0,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000273
    },
    {
      "name": "dom-widget-clipping",
      "durationMs": 589.1050000000178,
      "styleRecalcs": 13,
      "styleRecalcDurationMs": 9.727,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 370.783,
      "heapDeltaBytes": -10463108,
      "heapUsedBytes": 57290184,
      "domNodes": 22,
      "jsHeapTotalBytes": 19660800,
      "scriptDurationMs": 66.173,
      "eventListeners": 0,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.669999999999998,
      "p95FrameDurationMs": 16.700000000000273
    },
    {
      "name": "large-graph-idle",
      "durationMs": 2028.3939999999916,
      "styleRecalcs": 8,
      "styleRecalcDurationMs": 7.872999999999998,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 680.9419999999999,
      "heapDeltaBytes": 9375388,
      "heapUsedBytes": 68751756,
      "domNodes": -263,
      "jsHeapTotalBytes": 3756032,
      "scriptDurationMs": 108.348,
      "eventListeners": -131,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "large-graph-idle",
      "durationMs": 2030.4520000000252,
      "styleRecalcs": 8,
      "styleRecalcDurationMs": 8.439000000000002,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 670.0670000000001,
      "heapDeltaBytes": 3275688,
      "heapUsedBytes": 61178880,
      "domNodes": -263,
      "jsHeapTotalBytes": 5009408,
      "scriptDurationMs": 106.09299999999999,
      "eventListeners": -129,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "large-graph-idle",
      "durationMs": 2073.998999999958,
      "styleRecalcs": 9,
      "styleRecalcDurationMs": 8.888,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 599.795,
      "heapDeltaBytes": 19568776,
      "heapUsedBytes": 77951708,
      "domNodes": -251,
      "jsHeapTotalBytes": -233472,
      "scriptDurationMs": 103.27,
      "eventListeners": -129,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "large-graph-pan",
      "durationMs": 2174.7839999999883,
      "styleRecalcs": 68,
      "styleRecalcDurationMs": 20.531999999999996,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 1227.3239999999998,
      "heapDeltaBytes": 7313416,
      "heapUsedBytes": 66807488,
      "domNodes": -266,
      "jsHeapTotalBytes": 1019904,
      "scriptDurationMs": 410.71799999999996,
      "eventListeners": -127,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.699999999999818
    },
    {
      "name": "large-graph-pan",
      "durationMs": 2194.272000000012,
      "styleRecalcs": 69,
      "styleRecalcDurationMs": 21.131,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 1219.3520000000003,
      "heapDeltaBytes": 10368904,
      "heapUsedBytes": 69927032,
      "domNodes": -261,
      "jsHeapTotalBytes": 233472,
      "scriptDurationMs": 427.27899999999994,
      "eventListeners": -127,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "large-graph-pan",
      "durationMs": 2145.4390000000103,
      "styleRecalcs": 68,
      "styleRecalcDurationMs": 19.743999999999996,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 1080.664,
      "heapDeltaBytes": 4240016,
      "heapUsedBytes": 63465484,
      "domNodes": -261,
      "jsHeapTotalBytes": 757760,
      "scriptDurationMs": 378.603,
      "eventListeners": -127,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "large-graph-zoom",
      "durationMs": 3277.290999999991,
      "styleRecalcs": 64,
      "styleRecalcDurationMs": 19.677,
      "layouts": 60,
      "layoutDurationMs": 8.996,
      "taskDurationMs": 1534.005,
      "heapDeltaBytes": 17249140,
      "heapUsedBytes": 78196580,
      "domNodes": -270,
      "jsHeapTotalBytes": 1077248,
      "scriptDurationMs": 574.129,
      "eventListeners": -127,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333335,
      "p95FrameDurationMs": 16.699999999999818
    },
    {
      "name": "large-graph-zoom",
      "durationMs": 3195.6630000000246,
      "styleRecalcs": 64,
      "styleRecalcDurationMs": 20.237000000000002,
      "layouts": 60,
      "layoutDurationMs": 8.51,
      "taskDurationMs": 1408.625,
      "heapDeltaBytes": 5281132,
      "heapUsedBytes": 65776556,
      "domNodes": -271,
      "jsHeapTotalBytes": 4747264,
      "scriptDurationMs": 513.3299999999999,
      "eventListeners": -157,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "large-graph-zoom",
      "durationMs": 3134.2069999999467,
      "styleRecalcs": 65,
      "styleRecalcDurationMs": 19.646000000000004,
      "layouts": 60,
      "layoutDurationMs": 8.78,
      "taskDurationMs": 1378.565,
      "heapDeltaBytes": 17005916,
      "heapUsedBytes": 77211876,
      "domNodes": -269,
      "jsHeapTotalBytes": 815104,
      "scriptDurationMs": 500.4820000000001,
      "eventListeners": -125,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333335,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "minimap-idle",
      "durationMs": 2014.7190000000137,
      "styleRecalcs": 8,
      "styleRecalcDurationMs": 8.511999999999999,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 714.159,
      "heapDeltaBytes": 11217340,
      "heapUsedBytes": 72673728,
      "domNodes": -264,
      "jsHeapTotalBytes": 4018176,
      "scriptDurationMs": 106.593,
      "eventListeners": -131,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "minimap-idle",
      "durationMs": 2011.4970000000199,
      "styleRecalcs": 8,
      "styleRecalcDurationMs": 7.634000000000002,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 606.7289999999999,
      "heapDeltaBytes": 10490768,
      "heapUsedBytes": 70124140,
      "domNodes": -265,
      "jsHeapTotalBytes": 552960,
      "scriptDurationMs": 99.92000000000002,
      "eventListeners": -129,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333335,
      "p95FrameDurationMs": 16.699999999999818
    },
    {
      "name": "minimap-idle",
      "durationMs": 2038.8749999999618,
      "styleRecalcs": 8,
      "styleRecalcDurationMs": 6.974999999999999,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 581.65,
      "heapDeltaBytes": -10055068,
      "heapUsedBytes": 50919288,
      "domNodes": -262,
      "jsHeapTotalBytes": 4018176,
      "scriptDurationMs": 95.20200000000001,
      "eventListeners": -129,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "subgraph-dom-widget-clipping",
      "durationMs": 626.1339999999791,
      "styleRecalcs": 47,
      "styleRecalcDurationMs": 13.021,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 423.53,
      "heapDeltaBytes": 9616968,
      "heapUsedBytes": 57579364,
      "domNodes": 20,
      "jsHeapTotalBytes": 15204352,
      "scriptDurationMs": 148.146,
      "eventListeners": 6,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000273
    },
    {
      "name": "subgraph-dom-widget-clipping",
      "durationMs": 577.8450000000248,
      "styleRecalcs": 48,
      "styleRecalcDurationMs": 13.110000000000001,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 402.36799999999994,
      "heapDeltaBytes": -9368128,
      "heapUsedBytes": 58732088,
      "domNodes": 22,
      "jsHeapTotalBytes": 19660800,
      "scriptDurationMs": 129.71200000000002,
      "eventListeners": 8,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.670000000000012,
      "p95FrameDurationMs": 16.700000000000273
    },
    {
      "name": "subgraph-dom-widget-clipping",
      "durationMs": 572.8490000000193,
      "styleRecalcs": 47,
      "styleRecalcDurationMs": 11.369,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 377.9509999999999,
      "heapDeltaBytes": -12675820,
      "heapUsedBytes": 53096360,
      "domNodes": 20,
      "jsHeapTotalBytes": 20541440,
      "scriptDurationMs": 130.89600000000002,
      "eventListeners": 8,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.669999999999998,
      "p95FrameDurationMs": 16.799999999999727
    },
    {
      "name": "subgraph-idle",
      "durationMs": 2013.5709999999847,
      "styleRecalcs": 11,
      "styleRecalcDurationMs": 11.011,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 481.785,
      "heapDeltaBytes": 23033768,
      "heapUsedBytes": 71000908,
      "domNodes": 21,
      "jsHeapTotalBytes": 14680064,
      "scriptDurationMs": 29.196999999999996,
      "eventListeners": 4,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "subgraph-idle",
      "durationMs": 1998.658999999975,
      "styleRecalcs": 10,
      "styleRecalcDurationMs": 10.224,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 443.41999999999996,
      "heapDeltaBytes": 2886312,
      "heapUsedBytes": 71524704,
      "domNodes": 20,
      "jsHeapTotalBytes": 18874368,
      "scriptDurationMs": 21.553000000000008,
      "eventListeners": 6,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "subgraph-idle",
      "durationMs": 2004.2869999999766,
      "styleRecalcs": 10,
      "styleRecalcDurationMs": 8.794,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 382.38000000000005,
      "heapDeltaBytes": 22314980,
      "heapUsedBytes": 70675080,
      "domNodes": 19,
      "jsHeapTotalBytes": 15204352,
      "scriptDurationMs": 18.216000000000005,
      "eventListeners": 4,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "subgraph-mouse-sweep",
      "durationMs": 2005.9489999999869,
      "styleRecalcs": 84,
      "styleRecalcDurationMs": 51.546,
      "layouts": 16,
      "layoutDurationMs": 4.997000000000001,
      "taskDurationMs": 1045.241,
      "heapDeltaBytes": 9171868,
      "heapUsedBytes": 58788120,
      "domNodes": -260,
      "jsHeapTotalBytes": 13852672,
      "scriptDurationMs": 107.659,
      "eventListeners": -133,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "subgraph-mouse-sweep",
      "durationMs": 1721.0440000000062,
      "styleRecalcs": 76,
      "styleRecalcDurationMs": 42.659000000000006,
      "layouts": 16,
      "layoutDurationMs": 4.922000000000001,
      "taskDurationMs": 732.6429999999999,
      "heapDeltaBytes": -5390148,
      "heapUsedBytes": 62889384,
      "domNodes": 63,
      "jsHeapTotalBytes": 19660800,
      "scriptDurationMs": 105.608,
      "eventListeners": 4,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333332,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "subgraph-mouse-sweep",
      "durationMs": 1698.8770000000386,
      "styleRecalcs": 76,
      "styleRecalcDurationMs": 40.43,
      "layouts": 16,
      "layoutDurationMs": 4.970000000000001,
      "taskDurationMs": 696.875,
      "heapDeltaBytes": 14867672,
      "heapUsedBytes": 63954036,
      "domNodes": 63,
      "jsHeapTotalBytes": 15204352,
      "scriptDurationMs": 102.15199999999999,
      "eventListeners": 6,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "viewport-pan-sweep",
      "durationMs": 8225.655000000017,
      "styleRecalcs": 249,
      "styleRecalcDurationMs": 65.16900000000001,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 4337.976000000001,
      "heapDeltaBytes": 25873384,
      "heapUsedBytes": 83829112,
      "domNodes": -261,
      "jsHeapTotalBytes": 3641344,
      "scriptDurationMs": 1389.27,
      "eventListeners": -113,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "viewport-pan-sweep",
      "durationMs": 8315.219000000014,
      "styleRecalcs": 250,
      "styleRecalcDurationMs": 62.175999999999995,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 4282.89,
      "heapDeltaBytes": 24670900,
      "heapUsedBytes": 84325812,
      "domNodes": -259,
      "jsHeapTotalBytes": 9699328,
      "scriptDurationMs": 1445.3519999999999,
      "eventListeners": -113,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "viewport-pan-sweep",
      "durationMs": 8178.869000000077,
      "styleRecalcs": 249,
      "styleRecalcDurationMs": 59.68899999999999,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 4015.7859999999996,
      "heapDeltaBytes": 25933608,
      "heapUsedBytes": 85081500,
      "domNodes": -261,
      "jsHeapTotalBytes": 12058624,
      "scriptDurationMs": 1391.1370000000002,
      "eventListeners": -113,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333332,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "vue-large-graph-idle",
      "durationMs": 13855.42399999997,
      "styleRecalcs": 0,
      "styleRecalcDurationMs": 0,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 13817.687,
      "heapDeltaBytes": -41524068,
      "heapUsedBytes": 168476108,
      "domNodes": -8329,
      "jsHeapTotalBytes": 22343680,
      "scriptDurationMs": 605.9369999999999,
      "eventListeners": -16462,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 18.330000000000048,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "vue-large-graph-idle",
      "durationMs": 13856.95899999996,
      "styleRecalcs": 0,
      "styleRecalcDurationMs": 0,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 13833.328,
      "heapDeltaBytes": -26928364,
      "heapUsedBytes": 171959072,
      "domNodes": -8331,
      "jsHeapTotalBytes": 24440832,
      "scriptDurationMs": 673.0500000000001,
      "eventListeners": -16462,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 17.223333333333237,
      "p95FrameDurationMs": 16.799999999999272
    },
    {
      "name": "vue-large-graph-idle",
      "durationMs": 13383.418000000005,
      "styleRecalcs": 0,
      "styleRecalcDurationMs": 0,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 13367.151,
      "heapDeltaBytes": -27755312,
      "heapUsedBytes": 169312316,
      "domNodes": -8331,
      "jsHeapTotalBytes": 21295104,
      "scriptDurationMs": 587.607,
      "eventListeners": -16464,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 17.776666666666642,
      "p95FrameDurationMs": 16.799999999999272
    },
    {
      "name": "vue-large-graph-pan",
      "durationMs": 15967.87599999999,
      "styleRecalcs": 82,
      "styleRecalcDurationMs": 21.857000000000014,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 15920.782000000003,
      "heapDeltaBytes": -51449536,
      "heapUsedBytes": 165055652,
      "domNodes": -8331,
      "jsHeapTotalBytes": -3346432,
      "scriptDurationMs": 911.087,
      "eventListeners": -16458,
      "totalBlockingTimeMs": 2,
      "frameDurationMs": 17.776666666666642,
      "p95FrameDurationMs": 16.799999999999272
    },
    {
      "name": "vue-large-graph-pan",
      "durationMs": 16139.173000000028,
      "styleRecalcs": 87,
      "styleRecalcDurationMs": 21.926000000000002,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 16103.638999999997,
      "heapDeltaBytes": -20016628,
      "heapUsedBytes": 187992056,
      "domNodes": -8329,
      "jsHeapTotalBytes": 20946944,
      "scriptDurationMs": 954.1809999999999,
      "eventListeners": -16458,
      "totalBlockingTimeMs": 6,
      "frameDurationMs": 17.223333333333358,
      "p95FrameDurationMs": 16.80000000000291
    },
    {
      "name": "vue-large-graph-pan",
      "durationMs": 15703.431000000022,
      "styleRecalcs": 80,
      "styleRecalcDurationMs": 19.571999999999978,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 15679.546,
      "heapDeltaBytes": -52961392,
      "heapUsedBytes": 158235400,
      "domNodes": -8331,
      "jsHeapTotalBytes": -6230016,
      "scriptDurationMs": 851.4549999999998,
      "eventListeners": -16490,
      "totalBlockingTimeMs": 42,
      "frameDurationMs": 17.776666666666642,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "workflow-execution",
      "durationMs": 474.2119999999659,
      "styleRecalcs": 21,
      "styleRecalcDurationMs": 27.482,
      "layouts": 5,
      "layoutDurationMs": 1.7589999999999997,
      "taskDurationMs": 131.10999999999999,
      "heapDeltaBytes": 5333640,
      "heapUsedBytes": 55281012,
      "domNodes": 190,
      "jsHeapTotalBytes": 262144,
      "scriptDurationMs": 23.269000000000002,
      "eventListeners": 69,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.663333333333338,
      "p95FrameDurationMs": 16.700000000000273
    },
    {
      "name": "workflow-execution",
      "durationMs": 503.43500000008135,
      "styleRecalcs": 14,
      "styleRecalcDurationMs": 22.551000000000002,
      "layouts": 5,
      "layoutDurationMs": 1.4310000000000003,
      "taskDurationMs": 165.61900000000003,
      "heapDeltaBytes": -2338108,
      "heapUsedBytes": 48936524,
      "domNodes": -115,
      "jsHeapTotalBytes": -1351680,
      "scriptDurationMs": 25.485999999999997,
      "eventListeners": -62,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000273
    },
    {
      "name": "workflow-execution",
      "durationMs": 135.02699999992274,
      "styleRecalcs": 13,
      "styleRecalcDurationMs": 20.322999999999997,
      "layouts": 5,
      "layoutDurationMs": 1.5590000000000002,
      "taskDurationMs": 98.12499999999999,
      "heapDeltaBytes": 3450844,
      "heapUsedBytes": 54333552,
      "domNodes": 148,
      "jsHeapTotalBytes": 0,
      "scriptDurationMs": 20.912999999999993,
      "eventListeners": 37,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.800000000000182
    }
  ]
}

Copy link
Copy Markdown
Collaborator

@dante01yoon dante01yoon left a comment

Choose a reason for hiding this comment

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

Nice UX additions over #11109 (jitter, toasts, degraded-state preservation, cancellable refresh). A few things I'd like to resolve before approving:

  • issue: jitter is applied after the 60s cap in scheduleRefreshRetry, so the retry delay can exceed the cap by up to 5s.
  • question: Math.random() * 5000 jitter magnitude vs. remainingMs: when remainingMs is small (e.g. ~11s), the retry can land within ~500ms of token expiry.
  • question: AbortController.signal is never threaded into the fetch in switchWorkspace, so abort() only short-circuits the next retry-loop iteration — it does not cancel the in-flight network call or prevent a late-arriving success from re-arming scheduleTokenRefresh after destroy().
  • suggestion: toast emission per attempt can pile up during sustained outages, especially because scheduleRefreshRetry re-spawns the whole retry cycle.

Details inline.

remainingMs <= 10_000
? remainingMs
: Math.min(60_000, Math.floor(remainingMs / 2))
: Math.min(60_000, Math.floor(remainingMs / 2)) + jitter
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

issue: jitter is added outside the Math.min(60_000, …) cap, so retryDelay can be up to 60_000 + jitter (~65s) once remainingMs / 2 >= 60_000. The intent of the cap ("don't sleep longer than 60s before re-attempting") is defeated by the additive jitter.

Could we move the jitter inside the min so the cap is honored, e.g.:

const retryDelay =
  remainingMs <= 10_000
    ? remainingMs
    : Math.min(60_000, Math.floor(remainingMs / 2) + jitter)


const remainingMs = workspaceTokenExpiresAt.value - Date.now()
// Add jitter to prevent thundering herd across browser tabs
const jitter = Math.random() * 5000
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

question: 5s of additive jitter is large relative to small remainingMs windows. When remainingMs is just above the 10s short-circuit (say ~11s), the non-short-circuit branch gives floor(11_000/2) + up to 5_000 = up to 10.5s, leaving as little as ~500ms before the workspace token actually expires. Was 5_000ms picked deliberately, or would something proportional to remainingMs (or a smaller fixed jitter) be safer? Same question applies to the retry-loop jitter at line 378 (500ms there feels reasonable, just want to confirm the asymmetry is intentional).

// Create AbortController for this refresh operation
abortCurrentRefresh()
const abortController = new AbortController()
currentRefreshAbort = abortController
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

question: the AbortController is allocated and stored, but abortController.signal is never passed to the fetch inside switchWorkspace. So abort():

  • does not cancel the in-flight /auth/token request, and
  • does not prevent a late-resolving successful switchWorkspace from setting currentWorkspace/workspaceToken and calling scheduleTokenRefresh(expiresAt) after destroy() or clearWorkspaceContext() has run.

The PR description says "Proper cleanup when context is cleared or component unmounts." For clearWorkspaceContext, refreshRequestId++ already covers the stale-write case before mutating state, but destroy() does not increment refreshRequestId, so a successful in-flight refresh after destroy can re-arm the timer and resurrect state. Is the intent to thread abortController.signal into the fetch (and treat AbortError as a no-op in the catch), or are you deliberately keeping abort as a loop-only guard? If the latter, the new abortController.signal.aborted check in the loop overlaps almost entirely with the existing capturedRequestId !== refreshRequestId check called from clearWorkspaceContext, so it might be worth either wiring the signal end-to-end or dropping the AbortController and bumping refreshRequestId inside destroy() instead.

delay: Math.round(delay / 1000)
}),
life: delay + 2000
})
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

suggestion: every failed attempt adds a fresh "Reconnecting…" toast (3 per cycle, plus a final degraded/expired toast). Because scheduleRefreshRetry() will eventually trigger another refreshToken() (which restarts the whole 4-attempt cycle), a backend that's down for a few minutes can produce a long stream of warn toasts.

Would it make sense to either gate the retry toast to attempt === 0 (one "Reconnecting…" per refresh cycle) or reuse a single sticky toast that updates? Right now even a recoverable hiccup pops 3 stacked warn toasts before the success, which is noisier than the "toast notifications during retries" intent in the PR description suggests.

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.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants