Skip to content

feat(widgets): rich combo widget for remote options with previews#11310

Open
bigcat88 wants to merge 3 commits intomainfrom
feat/RemoteComboOptions
Open

feat(widgets): rich combo widget for remote options with previews#11310
bigcat88 wants to merge 3 commits intomainfrom
feat/RemoteComboOptions

Conversation

@bigcat88
Copy link
Copy Markdown
Contributor

@bigcat88 bigcat88 commented Apr 16, 2026

Summary

Adds RichComboWidget — a Vue-native renderer for combo inputs that declare remote_combo= (RemoteComboOptions on the backend), with previews, search, pagination, and persistent caching. Runs in parallel to the existing useRemoteWidget composable, which continues to handle plain remote= combos.

Changes

  • What:
    • New RichComboWidget.vue — fetches items from a remote route (optionally paginated as ?page=N&page_size=M returning {items, has_more}), maps each item via a configurable item_schema, and renders them in FormDropdown with image/video/audio previews, search, auto-select, and a refresh button. Cache-API-backed persistent cache (survives page reloads), retry with exponential backoff, abort on unmount/refresh.
    • Wired through WidgetSelect.vue — picks RichComboWidget when the spec has remote_combo, otherwise falls through to the existing dropdown / control-widget paths.
    • Schema additions in nodeDefSchema.ts: zRemoteComboConfig (route, item_schema, refresh_button, auto_select, refresh, response_key, timeout, max_retries, use_comfy_api, page_size) and zRemoteItemSchema (value/label/description/preview fields, preview_type, search_fields).
    • Two new pure utility modules with 37 unit tests: itemSchemaUtils.ts (dot-path traversal, label template substitution, item mapping, search-text indexing) and fetchRemoteRoute.ts (axios GET with optional comfy-api base URL + auth header injection).
    • Audio preview branch added to FormDropdownMenuItem.vue — used by the new widget when preview_type='audio', and picked up by the legacy LoadAudio dropdown which previously rendered audio URLs as broken <img>.
    • Backwards-compatible additions to FormDropdown.vue / FormDropdownMenu.vue / FormDropdownMenuActions.vue: optional showSort / showLayoutSwitcher props (default true), and an optional description row in FormDropdownItem rendered in list / list-small layouts.
    • New i18n keys under widgets.remoteCombo.*.

Review Focus

  • Stale-id handling on workflow load. If the model holds an id no longer present in the fetched items, the input shows the placeholder, the model retains the id, and the workflow saves it as-is. Intentional — the backend reports the invalid value at execution time. (RichComboWidget.vue watch at the bottom of <script setup>; handleSelection mirrors useWidgetSelectActions.updateSelectedItems so user-initiated deselection clears the model to undefined.)
  • Side-effect on legacy LoadAudio dropdown. The audio preview branch in FormDropdownMenuItem.vue fires whenever assetKind='audio' && previewUrl. useWidgetSelectItems already populates preview_url for audio items and WidgetSelectDropdown already provides assetKind='audio', so the legacy dropdown picks this up automatically — net positive (previously broken <img>), but worth a deliberate look.
  • Cache key strategy. buildCacheKey keys on route + use_comfy_api + response_key + page_size, and additionally on userId only when use_comfy_api=true. Non-comfy-api routes intentionally share cache across users on the same machine; comfy-api routes are partitioned per user.
  • Pagination contract. Endpoint must return {items: [...], has_more: bool}; the frontend issues page=0,1,… until has_more=false or empty items. Partial caches are explicitly not written — terminal failure refetches from page 0 on next mount. Documented at nodeDefSchema.ts and in the backend RemoteComboOptions docstring.
  • Two parallel remote-fetch subsystems. useRemoteWidget (in-memory Map, 4 s timeout, 512 ms backoff cap) handles plain remote=. RichComboWidget (Cache API, 30 s timeout, 16 s backoff cap) handles remote_combo=. Intentional — different reliability profiles for different use cases.

Core PR

Comfy-Org/ComfyUI#13432

Screenshots (if applicable)

Screenshots Screenshot From 2026-04-16 19-12-58 Screenshot From 2026-04-16 19-13-38

┆Issue is synchronized with this Notion page by Unito

@bigcat88 bigcat88 requested a review from a team April 16, 2026 16:13
@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 16, 2026

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

Project Deployment Actions Updated (UTC)
website-frontend Ready Ready Preview, Comment Apr 16, 2026 6:29pm

Request Review

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

coderabbitai Bot commented Apr 16, 2026

Note

Reviews paused

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

Use the following commands to manage reviews:

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

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a remote-backed RichComboWidget and supporting schemas, helpers, and tests: remote fetching with retries/backoff and Cache API partitioning, schema-driven item mapping and search, audio preview controls, and configurable dropdown UI flags and locales.

Changes

Cohort / File(s) Summary
Rich combo component & integration
src/renderer/extensions/vueNodes/widgets/components/RichComboWidget.vue, src/renderer/extensions/vueNodes/widgets/components/WidgetSelect.vue
New RichComboWidget and integration into WidgetSelect. Handles abortable remote fetching, retry/backoff, Cache API persistence keyed by auth scope, auto-select behavior, refresh control, item mapping, search indexing, and i18n placeholders.
Dropdown props & menu UI
src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdown.vue, .../FormDropdownMenu.vue, .../FormDropdownMenuActions.vue
Added showSort and showLayoutSwitcher props (default true) and forwarded them to menu/actions to conditionally render sort and layout switcher controls.
Dropdown item enhancements
src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdownMenuItem.vue, src/renderer/extensions/vueNodes/widgets/components/form/dropdown/types.ts
Added optional description prop/field and audio preview playback UI with i18n aria labels; updated preview_url docs to include audio.
Remote fetch helper
src/renderer/extensions/vueNodes/widgets/utils/fetchRemoteRoute.ts, .../fetchRemoteRoute.test.ts
Introduced fetchRemoteRoute that prepends Comfy API base URL, injects auth headers when available, and forwards params/timeout/signal to axios.get. Tests cover URL resolution, auth header injection, option forwarding, and response payload.
Rich combo helpers
src/renderer/extensions/vueNodes/widgets/utils/richComboHelpers.ts, .../richComboHelpers.test.ts
Added helpers: buildCacheKey, getBackoff, isRetriableError, summarizeError, summarizePayload with tests for cache key partitioning, backoff growth/cap, retry rules, and safe summaries.
Item schema utilities
src/renderer/extensions/vueNodes/widgets/utils/itemSchemaUtils.ts, .../itemSchemaUtils.test.ts
New utilities: getByPath, resolveLabel, mapToDropdownItem, extractItems, buildSearchText plus comprehensive tests for path traversal, templating, extraction, and search text building.
Schemas & validation
src/schemas/nodeDefSchema.ts, src/schemas/nodeDefSchema.validation.test.ts
Added zRemoteItemSchema and zRemoteComboConfig, exported RemoteItemSchema/RemoteComboConfig, extended zComboInputOptions with remote_combo, and added validation tests for relative vs absolute route rules.
Component tests
src/renderer/extensions/vueNodes/widgets/components/RichComboWidget.test.ts
New tests for RichComboWidget.vue covering fetch behavior, retry exhaustion, cache interactions, refresh action, and selection synchronization.
Locale
src/locales/en/main.json
Added widgets.remoteCombo keys: loading, loadFailed, itemsLoaded, playAudioPreview, pauseAudioPreview.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant RichCombo as RichComboWidget
    participant Cache as Browser Cache
    participant FetchUtil as fetchRemoteRoute
    participant Remote as Remote API
    participant Renderer as Dropdown Renderer

    User->>RichCombo: mount / open / refresh
    activate RichCombo
    RichCombo->>Cache: check cached items (route+config+scope)
    alt cache hit
        Cache-->>RichCombo: return cached items
    else cache miss
        RichCombo->>FetchUtil: fetch page (params, timeout, signal)
        FetchUtil->>Remote: HTTP GET (Comfy base + route, auth header)
        Remote-->>FetchUtil: response
        FetchUtil-->>RichCombo: items (maybe wrapped)
        alt paginated
            loop pages
                RichCombo->>FetchUtil: fetch next page (signal)
                FetchUtil->>Remote: HTTP GET
                Remote-->>FetchUtil: page response
                FetchUtil-->>RichCombo: append items
            end
        end
        RichCombo->>Cache: store items (if enabled)
    end
    RichCombo->>RichCombo: mapToDropdownItem / buildSearchText
    RichCombo->>Renderer: render dropdown, controls, audio previews
    deactivate RichCombo

    User->>Renderer: click play audio
    Renderer->>Renderer: toggle audio element (play/pause)
    Renderer-->>User: audio playback state
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 I hopped to fetch the far-off list with cache and retries spry,
Pages stitched and mapped to names beneath a network sky.
A click — a tiny drum — the audio twinkles bright,
Sorts and layouts whisper when the menu takes flight.
Hooray, little combo, you make the picker light!

🚥 Pre-merge checks | ✅ 6 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 65.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 clearly and specifically describes the main change: introducing a rich combo widget component with support for remote options and previews.
Description check ✅ Passed The PR description comprehensively covers the Summary and Changes sections with detailed What/Breaking/Dependencies info, and includes a thorough Review Focus section addressing critical design decisions. It exceeds template requirements.
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 uses 'feat' prefix indicating a feature addition, not a bug fix, so e2e regression test coverage requirement does not apply.
Adr Compliance For Entity/Litegraph Changes ✅ Passed Changes are located in widget rendering layer and schema definitions, not in src/lib/litegraph/ or src/ecs/, so ADR requirements do not apply.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/RemoteComboOptions

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 16, 2026

🎨 Storybook: ✅ Built — View Storybook

Details

⏰ Completed at: 05/04/2026, 04:16:51 PM UTC

Links

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 16, 2026

🎭 Playwright: ✅ 1465 passed, 0 failed · 1 flaky

📊 Browser Reports
  • chromium: View Report (✅ 1446 / ❌ 0 / ⚠️ 1 / ⏭️ 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)

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 16, 2026

📦 Bundle: 5.26 MB gzip 🔴 +5 kB

Details

Summary

  • Raw size: 24.2 MB baseline 24.2 MB — 🔴 +18.5 kB
  • Gzip: 5.26 MB baseline 5.26 MB — 🔴 +5 kB
  • Brotli: 4.07 MB baseline 4.07 MB — 🔴 +4.25 kB
  • Bundles: 257 current • 257 baseline • 127 added / 127 removed

Category Glance
Other 🔴 +17.2 kB (8.86 MB) · Utilities & Hooks 🔴 +5.12 kB (369 kB) · UI Components 🟢 -5.11 kB (57.8 kB) · Data & Services 🔴 +1.3 kB (3.05 MB) · Graph Workspace 🔴 +15 B (1.24 MB) · App Entry Points 🔴 +15 B (22.6 kB) · + 5 more

App Entry Points — 22.6 kB (baseline 22.5 kB) • 🔴 +15 B

Main entry bundles and manifests

File Before After Δ Raw Δ Gzip Δ Brotli
assets/index-CO6WW-Oh.js (new) 22.6 kB 🔴 +22.6 kB 🔴 +7.99 kB 🔴 +6.88 kB
assets/index-DnO3nhVm.js (removed) 22.5 kB 🟢 -22.5 kB 🟢 -7.98 kB 🟢 -6.88 kB

Status: 1 added / 1 removed

Graph Workspace — 1.24 MB (baseline 1.24 MB) • 🔴 +15 B

Graph editor runtime, canvas, workflow orchestration

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

Status: 1 added / 1 removed

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

Top-level views, pages, and routed surfaces

File Before After Δ Raw Δ Gzip Δ Brotli
assets/CloudSurveyView-aaN66aT4.js (removed) 19.6 kB 🟢 -19.6 kB 🟢 -5.14 kB 🟢 -4.58 kB
assets/CloudSurveyView-nt7oCpZh.js (new) 19.6 kB 🔴 +19.6 kB 🔴 +5.14 kB 🔴 +4.57 kB
assets/CloudLoginView-BpnRfWmS.js (new) 12.2 kB 🔴 +12.2 kB 🔴 +3.45 kB 🔴 +3.04 kB
assets/CloudLoginView-UndEf1KS.js (removed) 12.2 kB 🟢 -12.2 kB 🟢 -3.45 kB 🟢 -3.05 kB
assets/CloudSignupView-C3vWbf6L.js (new) 9.97 kB 🔴 +9.97 kB 🔴 +2.95 kB 🔴 +2.59 kB
assets/CloudSignupView-D6sDVDkd.js (removed) 9.97 kB 🟢 -9.97 kB 🟢 -2.95 kB 🟢 -2.6 kB
assets/UserCheckView-A-RhMH6x.js (removed) 9.07 kB 🟢 -9.07 kB 🟢 -2.34 kB 🟢 -2.06 kB
assets/UserCheckView-ApWvya2J.js (new) 9.07 kB 🔴 +9.07 kB 🔴 +2.33 kB 🔴 +2.05 kB
assets/CloudLayoutView-C2xghxvz.js (new) 7.73 kB 🔴 +7.73 kB 🔴 +2.45 kB 🔴 +2.14 kB
assets/CloudLayoutView-jbmzpoV5.js (removed) 7.73 kB 🟢 -7.73 kB 🟢 -2.45 kB 🟢 -2.13 kB
assets/CloudForgotPasswordView-B02pfNtn.js (removed) 6.14 kB 🟢 -6.14 kB 🟢 -2.18 kB 🟢 -1.92 kB
assets/CloudForgotPasswordView-CEJXl4Jb.js (new) 6.14 kB 🔴 +6.14 kB 🔴 +2.19 kB 🔴 +1.92 kB
assets/CloudAuthTimeoutView-DEVzsPoY.js (new) 5.5 kB 🔴 +5.5 kB 🔴 +2.01 kB 🔴 +1.77 kB
assets/CloudAuthTimeoutView-FJlKmPa7.js (removed) 5.5 kB 🟢 -5.5 kB 🟢 -2.02 kB 🟢 -1.77 kB
assets/CloudSubscriptionRedirectView-5iw6fYrg.js (new) 5.28 kB 🔴 +5.28 kB 🔴 +2 kB 🔴 +1.78 kB
assets/CloudSubscriptionRedirectView-CGhYdnmS.js (removed) 5.28 kB 🟢 -5.28 kB 🟢 -2 kB 🟢 -1.78 kB
assets/UserSelectView-BWachbWg.js (new) 4.73 kB 🔴 +4.73 kB 🔴 +1.75 kB 🔴 +1.55 kB
assets/UserSelectView-uveG4Wqe.js (removed) 4.73 kB 🟢 -4.73 kB 🟢 -1.75 kB 🟢 -1.55 kB

Status: 9 added / 9 removed / 2 unchanged

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

Configuration panels, inspectors, and settings screens

File Before After Δ Raw Δ Gzip Δ Brotli
assets/KeybindingPanel-SvRjgi0M.js (new) 46.7 kB 🔴 +46.7 kB 🔴 +9.63 kB 🔴 +8.55 kB
assets/KeybindingPanel-TEbodclv.js (removed) 46.7 kB 🟢 -46.7 kB 🟢 -9.62 kB 🟢 -8.54 kB
assets/SecretsPanel-42B4_4F6.js (removed) 22.9 kB 🟢 -22.9 kB 🟢 -5.55 kB 🟢 -4.88 kB
assets/SecretsPanel-CpMS1dQu.js (new) 22.9 kB 🔴 +22.9 kB 🔴 +5.55 kB 🔴 +4.88 kB
assets/LegacyCreditsPanel-DmMpM0ar.js (removed) 21.7 kB 🟢 -21.7 kB 🟢 -5.9 kB 🟢 -5.21 kB
assets/LegacyCreditsPanel-DOeGlm2D.js (new) 21.7 kB 🔴 +21.7 kB 🔴 +5.9 kB 🔴 +5.23 kB
assets/SubscriptionPanel-BnYumXP3.js (removed) 20 kB 🟢 -20 kB 🟢 -5.1 kB 🟢 -4.49 kB
assets/SubscriptionPanel-BrThBlDb.js (new) 20 kB 🔴 +20 kB 🔴 +5.1 kB 🔴 +4.48 kB
assets/AboutPanel-Bs9AtfHu.js (new) 12 kB 🔴 +12 kB 🔴 +3.33 kB 🔴 +2.99 kB
assets/AboutPanel-CPRgcCGk.js (removed) 12 kB 🟢 -12 kB 🟢 -3.33 kB 🟢 -2.99 kB
assets/ExtensionPanel-BUd4EbMp.js (removed) 9.97 kB 🟢 -9.97 kB 🟢 -2.91 kB 🟢 -2.6 kB
assets/ExtensionPanel-Cwe9yieA.js (new) 9.97 kB 🔴 +9.97 kB 🔴 +2.91 kB 🔴 +2.59 kB
assets/ServerConfigPanel-DcsmLa_I.js (removed) 7.05 kB 🟢 -7.05 kB 🟢 -2.36 kB 🟢 -2.12 kB
assets/ServerConfigPanel-DmYGScoZ.js (new) 7.05 kB 🔴 +7.05 kB 🔴 +2.36 kB 🔴 +2.11 kB
assets/UserPanel--X4Mwluq.js (new) 6.75 kB 🔴 +6.75 kB 🔴 +2.24 kB 🔴 +1.96 kB
assets/UserPanel-CQrQKNPh.js (removed) 6.75 kB 🟢 -6.75 kB 🟢 -2.24 kB 🟢 -1.97 kB
assets/cloudRemoteConfig-_Ig5G6SL.js (new) 2.05 kB 🔴 +2.05 kB 🔴 +988 B 🔴 +847 B
assets/cloudRemoteConfig-7Kw9EQpd.js (removed) 2.05 kB 🟢 -2.05 kB 🟢 -987 B 🟢 -852 B
assets/refreshRemoteConfig-9TC9_HVP.js (new) 1.45 kB 🔴 +1.45 kB 🔴 +649 B 🔴 +553 B
assets/refreshRemoteConfig-I002cLsq.js (removed) 1.45 kB 🟢 -1.45 kB 🟢 -649 B 🟢 -550 B

Status: 10 added / 10 removed / 11 unchanged

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

Authentication, profile, and account management bundles

File Before After Δ Raw Δ Gzip Δ Brotli
assets/auth-CWXDv6fZ.js (removed) 3.57 kB 🟢 -3.57 kB 🟢 -1.26 kB 🟢 -1.07 kB
assets/auth-EyT9FGSf.js (new) 3.57 kB 🔴 +3.57 kB 🔴 +1.26 kB 🔴 +1.07 kB
assets/SignUpForm-CTjVWnzY.js (removed) 3.19 kB 🟢 -3.19 kB 🟢 -1.29 kB 🟢 -1.15 kB
assets/SignUpForm-DCPUnDAq.js (new) 3.19 kB 🔴 +3.19 kB 🔴 +1.29 kB 🔴 +1.15 kB
assets/UpdatePasswordContent-Dd1bBtjH.js (new) 2.9 kB 🔴 +2.9 kB 🔴 +1.3 kB 🔴 +1.15 kB
assets/UpdatePasswordContent-DDsWO0q7.js (removed) 2.9 kB 🟢 -2.9 kB 🟢 -1.3 kB 🟢 -1.15 kB
assets/authStore-BrdpQALs.js (removed) 1.19 kB 🟢 -1.19 kB 🟢 -568 B 🟢 -507 B
assets/authStore-DDnnC8vP.js (new) 1.19 kB 🔴 +1.19 kB 🔴 +564 B 🔴 +499 B
assets/auth-Bz5E4znm.js (new) 348 B 🔴 +348 B 🔴 +219 B 🔴 +192 B
assets/auth-DiMiD_Fz.js (removed) 348 B 🟢 -348 B 🟢 -216 B 🟢 -192 B

Status: 5 added / 5 removed / 2 unchanged

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

Modals, dialogs, drawers, and in-app editors

File Before After Δ Raw Δ Gzip Δ Brotli
assets/ComfyHubPublishDialog-BgDUubdx.js (new) 85.8 kB 🔴 +85.8 kB 🔴 +18.6 kB 🔴 +15.9 kB
assets/ComfyHubPublishDialog-Clt1EANi.js (removed) 85.8 kB 🟢 -85.8 kB 🟢 -18.6 kB 🟢 -15.9 kB
assets/useShareDialog-B9Inajez.js (new) 23.8 kB 🔴 +23.8 kB 🔴 +5.77 kB 🔴 +5.13 kB
assets/useShareDialog-DC2T5y_d.js (removed) 23.8 kB 🟢 -23.8 kB 🟢 -5.77 kB 🟢 -5.12 kB
assets/ComfyHubPublishDialog-B0V1vltv.js (new) 1.35 kB 🔴 +1.35 kB 🔴 +626 B 🔴 +567 B
assets/ComfyHubPublishDialog-CA6Ksqax.js (removed) 1.35 kB 🟢 -1.35 kB 🟢 -625 B 🟢 -555 B
assets/useSubscriptionDialog-BbTQBJ0B.js (removed) 1.17 kB 🟢 -1.17 kB 🟢 -556 B 🟢 -488 B
assets/useSubscriptionDialog-Bx1bxRi5.js (new) 1.17 kB 🔴 +1.17 kB 🔴 +557 B 🔴 +488 B

Status: 4 added / 4 removed

UI Components — 57.8 kB (baseline 62.9 kB) • 🟢 -5.11 kB

Reusable component library chunks

File Before After Δ Raw Δ Gzip Δ Brotli
assets/ComfyQueueButton-CaRZkQ6D.js (removed) 13.5 kB 🟢 -13.5 kB 🟢 -3.79 kB 🟢 -3.38 kB
assets/ComfyQueueButton-CNw_XZLE.js (new) 13.5 kB 🔴 +13.5 kB 🔴 +3.79 kB 🔴 +3.38 kB
assets/useTerminalTabs-BdmKDCPm.js (new) 11 kB 🔴 +11 kB 🔴 +3.73 kB 🔴 +3.27 kB
assets/useTerminalTabs-CMlBrw97.js (removed) 11 kB 🟢 -11 kB 🟢 -3.73 kB 🟢 -3.27 kB
assets/FormSearchInput-mVF62dMp.js (removed) 5.11 kB 🟢 -5.11 kB 🟢 -2.12 kB 🟢 -1.83 kB
assets/SubscribeButton-9iV6NhwZ.js (new) 2.42 kB 🔴 +2.42 kB 🔴 +1.05 kB 🔴 +926 B
assets/SubscribeButton-DLJMSIsh.js (removed) 2.42 kB 🟢 -2.42 kB 🟢 -1.05 kB 🟢 -923 B
assets/cloudFeedbackTopbarButton-BrEXnFNf.js (removed) 1.83 kB 🟢 -1.83 kB 🟢 -942 B 🟢 -829 B
assets/cloudFeedbackTopbarButton-CE1lf0I8.js (new) 1.83 kB 🔴 +1.83 kB 🔴 +942 B 🔴 +828 B
assets/ComfyQueueButton-4fUUNoFU.js (new) 1.27 kB 🔴 +1.27 kB 🔴 +594 B 🔴 +528 B
assets/ComfyQueueButton-DA9CKI6E.js (removed) 1.27 kB 🟢 -1.27 kB 🟢 -594 B 🟢 -531 B

Status: 5 added / 6 removed / 8 unchanged

Data & Services — 3.05 MB (baseline 3.05 MB) • 🔴 +1.3 kB

Stores, services, APIs, and repositories

File Before After Δ Raw Δ Gzip Δ Brotli
assets/dialogService-DhL3Ow-z.js (new) 1.99 MB 🔴 +1.99 MB 🔴 +458 kB 🔴 +347 kB
assets/dialogService-DcFS6bie.js (removed) 1.99 MB 🟢 -1.99 MB 🟢 -458 kB 🟢 -347 kB
assets/api-CLPB_4ht.js (new) 887 kB 🔴 +887 kB 🔴 +212 kB 🔴 +167 kB
assets/api-Dx9f_MgR.js (removed) 887 kB 🟢 -887 kB 🟢 -212 kB 🟢 -167 kB
assets/load3dService-CVc5p8Ak.js (new) 115 kB 🔴 +115 kB 🔴 +25.1 kB 🔴 +21.3 kB
assets/load3dService-QyHNASib.js (removed) 115 kB 🟢 -115 kB 🟢 -25.1 kB 🟢 -21.3 kB
assets/workflowShareService-D40suG7F.js (new) 16.6 kB 🔴 +16.6 kB 🔴 +4.89 kB 🔴 +4.34 kB
assets/workflowShareService-DyidLw-V.js (removed) 16.6 kB 🟢 -16.6 kB 🟢 -4.89 kB 🟢 -4.33 kB
assets/keybindingService-AOgMCBvY.js (new) 13.8 kB 🔴 +13.8 kB 🔴 +3.67 kB 🔴 +3.22 kB
assets/keybindingService-BQfDrbV-.js (removed) 13.8 kB 🟢 -13.8 kB 🟢 -3.66 kB 🟢 -3.21 kB
assets/releaseStore-BxxVni2I.js (removed) 8.12 kB 🟢 -8.12 kB 🟢 -2.28 kB 🟢 -2 kB
assets/releaseStore-Dk-l8ia0.js (new) 8.12 kB 🔴 +8.12 kB 🔴 +2.28 kB 🔴 +2 kB
assets/userStore-BdfNneWg.js (new) 2.24 kB 🔴 +2.24 kB 🔴 +869 B 🔴 +762 B
assets/userStore-C5lu6ddP.js (removed) 2.24 kB 🟢 -2.24 kB 🟢 -870 B 🟢 -761 B
assets/audioService-8GxUpFbN.js (removed) 1.8 kB 🟢 -1.8 kB 🟢 -878 B 🟢 -764 B
assets/audioService-DYsmCLiB.js (new) 1.8 kB 🔴 +1.8 kB 🔴 +877 B 🔴 +761 B
assets/releaseStore-CaXzbqz8.js (removed) 1.19 kB 🟢 -1.19 kB 🟢 -561 B 🟢 -497 B
assets/releaseStore-IR5iACx-.js (new) 1.19 kB 🔴 +1.19 kB 🔴 +561 B 🔴 +496 B
assets/workflowDraftStore-CFqs-DIR.js (new) 1.17 kB 🔴 +1.17 kB 🔴 +555 B 🔴 +493 B
assets/workflowDraftStore-DS4kCbdT.js (removed) 1.17 kB 🟢 -1.17 kB 🟢 -554 B 🟢 -493 B
assets/dialogService-D0My6xok.js (new) 1.16 kB 🔴 +1.16 kB 🔴 +548 B 🔴 +488 B
assets/dialogService-Do6CC6Uj.js (removed) 1.16 kB 🟢 -1.16 kB 🟢 -546 B 🟢 -490 B
assets/settingStore-B1iBzDrz.js (new) 1.15 kB 🔴 +1.15 kB 🔴 +551 B 🔴 +488 B
assets/settingStore-CO971Nj_.js (removed) 1.15 kB 🟢 -1.15 kB 🟢 -550 B 🟢 -486 B
assets/assetsStore-B7X2UYrc.js (removed) 1.15 kB 🟢 -1.15 kB 🟢 -549 B 🟢 -489 B
assets/assetsStore-DHKRUoHe.js (new) 1.15 kB 🔴 +1.15 kB 🔴 +551 B 🔴 +489 B

Status: 13 added / 13 removed / 4 unchanged

Utilities & Hooks — 369 kB (baseline 364 kB) • 🔴 +5.12 kB

Helpers, composables, and utility bundles

File Before After Δ Raw Δ Gzip Δ Brotli
assets/useConflictDetection-CvJhRdeR.js (removed) 233 kB 🟢 -233 kB 🟢 -51.8 kB 🟢 -42.2 kB
assets/useConflictDetection-FV2nZEfB.js (new) 233 kB 🔴 +233 kB 🔴 +51.8 kB 🔴 +42.3 kB
assets/useLoad3d-CCRY0RSM.js (new) 22.3 kB 🔴 +22.3 kB 🔴 +5.09 kB 🔴 +4.5 kB
assets/useLoad3d-Dksbfk8t.js (removed) 22.3 kB 🟢 -22.3 kB 🟢 -5.09 kB 🟢 -4.49 kB
assets/useLoad3dViewer-C0mur4t6.js (new) 20.8 kB 🔴 +20.8 kB 🔴 +4.91 kB 🔴 +4.29 kB
assets/useLoad3dViewer-MrH3_pQj.js (removed) 20.8 kB 🟢 -20.8 kB 🟢 -4.91 kB 🟢 -4.3 kB
assets/useFeatureFlags-BN16Czaa.js (new) 5.95 kB 🔴 +5.95 kB 🔴 +1.79 kB 🔴 +1.52 kB
assets/useFeatureFlags-CPIK2vCg.js (removed) 5.95 kB 🟢 -5.95 kB 🟢 -1.79 kB 🟢 -1.53 kB
assets/useCopyToClipboard-CbN6LPBY.js (removed) 5.29 kB 🟢 -5.29 kB 🟢 -1.86 kB 🟢 -1.57 kB
assets/useCopyToClipboard-syqLahvP.js (new) 5.29 kB 🔴 +5.29 kB 🔴 +1.86 kB 🔴 +1.57 kB
assets/useTransformCompatOverlayProps-kkjNMzeh.js (new) 5.12 kB 🔴 +5.12 kB 🔴 +2.12 kB 🔴 +1.83 kB
assets/downloadUtil-C_A-doPc.js (removed) 4.68 kB 🟢 -4.68 kB 🟢 -1.85 kB 🟢 -1.54 kB
assets/downloadUtil-CMh3ZQc2.js (new) 4.68 kB 🔴 +4.68 kB 🔴 +1.85 kB 🔴 +1.53 kB
assets/useWorkspaceUI-CbGoOWRp.js (new) 3.34 kB 🔴 +3.34 kB 🔴 +982 B 🔴 +808 B
assets/useWorkspaceUI-Rk6JBBOu.js (removed) 3.34 kB 🟢 -3.34 kB 🟢 -981 B 🟢 -809 B
assets/subscriptionCheckoutUtil-k0LGogGe.js (removed) 3.31 kB 🟢 -3.31 kB 🟢 -1.36 kB 🟢 -1.18 kB
assets/subscriptionCheckoutUtil-V1Pe5-g_.js (new) 3.31 kB 🔴 +3.31 kB 🔴 +1.36 kB 🔴 +1.18 kB
assets/useExternalLink-DUQAQRPh.js (new) 3.03 kB 🔴 +3.03 kB 🔴 +1.16 kB 🔴 +1 kB
assets/useExternalLink-icE1CU12.js (removed) 3.03 kB 🟢 -3.03 kB 🟢 -1.16 kB 🟢 -1.03 kB
assets/assetPreviewUtil-C4IUJPOe.js (removed) 2.27 kB 🟢 -2.27 kB 🟢 -956 B 🟢 -835 B
assets/assetPreviewUtil-D0D1jjlw.js (new) 2.27 kB 🔴 +2.27 kB 🔴 +958 B 🔴 +838 B
assets/useUpstreamValue-DG3sMEFI.js (removed) 2.08 kB 🟢 -2.08 kB 🟢 -804 B 🟢 -710 B
assets/useUpstreamValue-DzLVwfSk.js (new) 2.08 kB 🔴 +2.08 kB 🔴 +808 B 🔴 +712 B
assets/useErrorHandling-CcB2txP6.js (removed) 1.54 kB 🟢 -1.54 kB 🟢 -648 B 🟢 -551 B
assets/useErrorHandling-m12MK4n6.js (new) 1.54 kB 🔴 +1.54 kB 🔴 +649 B 🔴 +552 B
assets/useLoad3d-4M1-vnR1.js (new) 1.33 kB 🔴 +1.33 kB 🔴 +619 B 🔴 +566 B
assets/useLoad3d-BFZh7OO5.js (removed) 1.33 kB 🟢 -1.33 kB 🟢 -619 B 🟢 -566 B
assets/useLoad3dViewer-BIBfef3_.js (removed) 1.27 kB 🟢 -1.27 kB 🟢 -587 B 🟢 -527 B
assets/useLoad3dViewer-Co-tQ0sS.js (new) 1.27 kB 🔴 +1.27 kB 🔴 +585 B 🔴 +525 B
assets/useCurrentUser-DpMwCnUa.js (new) 1.15 kB 🔴 +1.15 kB 🔴 +552 B 🔴 +487 B
assets/useCurrentUser-Dwop4clB.js (removed) 1.15 kB 🟢 -1.15 kB 🟢 -551 B 🟢 -488 B
assets/useWorkspaceSwitch-B7b2QVW8.js (removed) 747 B 🟢 -747 B 🟢 -383 B 🟢 -332 B
assets/useWorkspaceSwitch-Crd4SD9B.js (new) 747 B 🔴 +747 B 🔴 +383 B 🔴 +330 B

Status: 17 added / 16 removed / 14 unchanged

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

External libraries and shared vendor chunks

Status: 16 unchanged

Other — 8.86 MB (baseline 8.84 MB) • 🔴 +17.2 kB

Bundles that do not match a named category

File Before After Δ Raw Δ Gzip Δ Brotli
assets/i18n-Y_VqT7l_.js (new) 591 kB 🔴 +591 kB 🔴 +115 kB 🔴 +90.2 kB
assets/i18n-DBI5SrUH.js (removed) 591 kB 🟢 -591 kB 🟢 -115 kB 🟢 -90.2 kB
assets/WidgetSelect-DZ8b-6qL.js (new) 84.2 kB 🔴 +84.2 kB 🔴 +19 kB 🔴 +16.6 kB
assets/core-BukG74F1.js (new) 76.7 kB 🔴 +76.7 kB 🔴 +19.9 kB 🔴 +16.9 kB
assets/core-DO5SsTgg.js (removed) 76.7 kB 🟢 -76.7 kB 🟢 -19.9 kB 🟢 -16.9 kB
assets/groupNode-DVdARvs0.js (removed) 74.9 kB 🟢 -74.9 kB 🟢 -18.7 kB 🟢 -16.5 kB
assets/groupNode-DvxIZJgJ.js (new) 74.9 kB 🔴 +74.9 kB 🔴 +18.7 kB 🔴 +16.5 kB
assets/WidgetSelect-BAyXvpm8.js (removed) 67.2 kB 🟢 -67.2 kB 🟢 -14.6 kB 🟢 -12.7 kB
assets/SubscriptionRequiredDialogContentWorkspace-DgTtOzxe.js (new) 48.8 kB 🔴 +48.8 kB 🔴 +9.52 kB 🔴 +8.2 kB
assets/SubscriptionRequiredDialogContentWorkspace-DsTGDYYf.js (removed) 48.8 kB 🟢 -48.8 kB 🟢 -9.52 kB 🟢 -8.23 kB
assets/Load3DControls-C2HnW2cA.js (new) 46.1 kB 🔴 +46.1 kB 🔴 +7.51 kB 🔴 +6.55 kB
assets/Load3DControls-L4nJ7AlJ.js (removed) 46.1 kB 🟢 -46.1 kB 🟢 -7.51 kB 🟢 -6.55 kB
assets/WidgetPainter-BA6tdHya.js (removed) 34 kB 🟢 -34 kB 🟢 -8.29 kB 🟢 -7.33 kB
assets/WidgetPainter-DVvaXtYO.js (new) 34 kB 🔴 +34 kB 🔴 +8.29 kB 🔴 +7.35 kB
assets/WorkspacePanelContent-BFwIPdmo.js (removed) 32.8 kB 🟢 -32.8 kB 🟢 -7 kB 🟢 -6.19 kB
assets/WorkspacePanelContent-DoaL2QK3.js (new) 32.8 kB 🔴 +32.8 kB 🔴 +7.01 kB 🔴 +6.19 kB
assets/Load3dViewerContent-C0BAH8NY.js (removed) 28 kB 🟢 -28 kB 🟢 -5.85 kB 🟢 -5.07 kB
assets/Load3dViewerContent-G8iouGn-.js (new) 28 kB 🔴 +28 kB 🔴 +5.85 kB 🔴 +5.07 kB
assets/SubscriptionRequiredDialogContent-DFU-SGHd.js (removed) 27.5 kB 🟢 -27.5 kB 🟢 -6.98 kB 🟢 -6.16 kB
assets/SubscriptionRequiredDialogContent-lseNL7sK.js (new) 27.5 kB 🔴 +27.5 kB 🔴 +6.98 kB 🔴 +6.17 kB
assets/WidgetImageCrop-BiKK--Ex.js (removed) 24.3 kB 🟢 -24.3 kB 🟢 -6.2 kB 🟢 -5.45 kB
assets/WidgetImageCrop-Cu58_qEj.js (new) 24.3 kB 🔴 +24.3 kB 🔴 +6.2 kB 🔴 +5.47 kB
assets/SubscriptionPanelContentWorkspace-BAsa-ukP.js (new) 22.2 kB 🔴 +22.2 kB 🔴 +5.18 kB 🔴 +4.56 kB
assets/SubscriptionPanelContentWorkspace-BeflX-vG.js (removed) 22.2 kB 🟢 -22.2 kB 🟢 -5.18 kB 🟢 -4.56 kB
assets/SignInContent-C8NX9Fmt.js (removed) 20.6 kB 🟢 -20.6 kB 🟢 -5.37 kB 🟢 -4.7 kB
assets/SignInContent-Qpe0Fgx1.js (new) 20.6 kB 🔴 +20.6 kB 🔴 +5.37 kB 🔴 +4.69 kB
assets/CurrentUserPopoverWorkspace-CwrcmQv7.js (new) 20.6 kB 🔴 +20.6 kB 🔴 +4.91 kB 🔴 +4.4 kB
assets/CurrentUserPopoverWorkspace-Dq49drou.js (removed) 20.6 kB 🟢 -20.6 kB 🟢 -4.9 kB 🟢 -4.41 kB
assets/WidgetInputNumber-Cdz7r2nn.js (removed) 19.1 kB 🟢 -19.1 kB 🟢 -4.84 kB 🟢 -4.3 kB
assets/WidgetInputNumber-DfykFiPk.js (new) 19.1 kB 🔴 +19.1 kB 🔴 +4.84 kB 🔴 +4.3 kB
assets/Load3D-7JPKINp0.js (new) 18.5 kB 🔴 +18.5 kB 🔴 +4.39 kB 🔴 +3.84 kB
assets/Load3D-C_UhlIgq.js (removed) 18.5 kB 🟢 -18.5 kB 🟢 -4.39 kB 🟢 -3.83 kB
assets/WidgetRecordAudio-NA625EqI.js (new) 17.4 kB 🔴 +17.4 kB 🔴 +5.01 kB 🔴 +4.48 kB
assets/WidgetRecordAudio-v-Dpxzku.js (removed) 17.4 kB 🟢 -17.4 kB 🟢 -5.01 kB 🟢 -4.49 kB
assets/WidgetRange-68qbcky2.js (new) 17.1 kB 🔴 +17.1 kB 🔴 +4.61 kB 🔴 +4.15 kB
assets/WidgetRange-CafKKfYD.js (removed) 17.1 kB 🟢 -17.1 kB 🟢 -4.61 kB 🟢 -4.12 kB
assets/load3d-DCaAyA-c.js (removed) 15.8 kB 🟢 -15.8 kB 🟢 -4.59 kB 🟢 -3.98 kB
assets/load3d-gYvuK8MH.js (new) 15.8 kB 🔴 +15.8 kB 🔴 +4.59 kB 🔴 +3.97 kB
assets/WaveAudioPlayer-C2wS7AGI.js (new) 13.4 kB 🔴 +13.4 kB 🔴 +3.69 kB 🔴 +3.23 kB
assets/WaveAudioPlayer-U7xL4Nv3.js (removed) 13.4 kB 🟢 -13.4 kB 🟢 -3.69 kB 🟢 -3.23 kB
assets/WidgetCurve-CfpT1XkH.js (new) 12.2 kB 🔴 +12.2 kB 🔴 +3.93 kB 🔴 +3.56 kB
assets/WidgetCurve-DgSL1o_h.js (removed) 12.2 kB 🟢 -12.2 kB 🟢 -3.93 kB 🟢 -3.56 kB
assets/TeamWorkspacesDialogContent-C6vTXKJT.js (new) 11.3 kB 🔴 +11.3 kB 🔴 +3.42 kB 🔴 +3.04 kB
assets/TeamWorkspacesDialogContent-uTimB1xS.js (removed) 11.3 kB 🟢 -11.3 kB 🟢 -3.42 kB 🟢 -3.05 kB
assets/AudioPreviewPlayer-BBKfRYPZ.js (new) 10.8 kB 🔴 +10.8 kB 🔴 +3.13 kB 🔴 +2.82 kB
assets/AudioPreviewPlayer-BD8KcLnh.js (removed) 10.8 kB 🟢 -10.8 kB 🟢 -3.14 kB 🟢 -2.81 kB
assets/nodeTemplates-DCBEjfh7.js (removed) 9.84 kB 🟢 -9.84 kB 🟢 -3.48 kB 🟢 -3.07 kB
assets/nodeTemplates-Dq_k-4gS.js (new) 9.84 kB 🔴 +9.84 kB 🔴 +3.48 kB 🔴 +3.08 kB
assets/NightlySurveyController-b2t5Nzap.js (removed) 8.97 kB 🟢 -8.97 kB 🟢 -3.15 kB 🟢 -2.78 kB
assets/NightlySurveyController-Cb6yDxbV.js (new) 8.97 kB 🔴 +8.97 kB 🔴 +3.15 kB 🔴 +2.78 kB
assets/Load3DConfiguration-Bz5fSGMP.js (new) 8.03 kB 🔴 +8.03 kB 🔴 +2.37 kB 🔴 +2.07 kB
assets/Load3DConfiguration-DUoaPhgd.js (removed) 8.03 kB 🟢 -8.03 kB 🟢 -2.36 kB 🟢 -2.07 kB
assets/InviteMemberDialogContent-Bv4C2IEB.js (new) 7.94 kB 🔴 +7.94 kB 🔴 +2.53 kB 🔴 +2.23 kB
assets/InviteMemberDialogContent-DHCecGvR.js (removed) 7.94 kB 🟢 -7.94 kB 🟢 -2.52 kB 🟢 -2.23 kB
assets/onboardingCloudRoutes-CEs5JFjm.js (removed) 6.66 kB 🟢 -6.66 kB 🟢 -2.08 kB 🟢 -1.79 kB
assets/onboardingCloudRoutes-DZDLiAII.js (new) 6.66 kB 🔴 +6.66 kB 🔴 +2.08 kB 🔴 +1.78 kB
assets/CreateWorkspaceDialogContent-Bbz07XgP.js (new) 6.15 kB 🔴 +6.15 kB 🔴 +2.24 kB 🔴 +1.96 kB
assets/CreateWorkspaceDialogContent-xPH59UvJ.js (removed) 6.15 kB 🟢 -6.15 kB 🟢 -2.23 kB 🟢 -1.96 kB
assets/WidgetWithControl-6QQmgwhZ.js (removed) 6.09 kB 🟢 -6.09 kB 🟢 -2.44 kB 🟢 -2.18 kB
assets/WidgetWithControl-qnIECbLX.js (new) 6.09 kB 🔴 +6.09 kB 🔴 +2.44 kB 🔴 +2.16 kB
assets/FreeTierDialogContent-CTTAjpEK.js (new) 6.01 kB 🔴 +6.01 kB 🔴 +2.13 kB 🔴 +1.89 kB
assets/FreeTierDialogContent-DG1-kAwO.js (removed) 6.01 kB 🟢 -6.01 kB 🟢 -2.13 kB 🟢 -1.89 kB
assets/EditWorkspaceDialogContent-CMtWFUwN.js (new) 5.95 kB 🔴 +5.95 kB 🔴 +2.2 kB 🔴 +1.93 kB
assets/EditWorkspaceDialogContent-DLkv0Asl.js (removed) 5.95 kB 🟢 -5.95 kB 🟢 -2.19 kB 🟢 -1.93 kB
assets/WidgetTextarea-BbsgiU4A.js (new) 5.76 kB 🔴 +5.76 kB 🔴 +2.27 kB 🔴 +2 kB
assets/WidgetTextarea-Cp1SFro7.js (removed) 5.76 kB 🟢 -5.76 kB 🟢 -2.27 kB 🟢 -2.01 kB
assets/Preview3d-Bh-0agNv.js (new) 5.73 kB 🔴 +5.73 kB 🔴 +1.92 kB 🔴 +1.68 kB
assets/Preview3d-CnT9ldMx.js (removed) 5.73 kB 🟢 -5.73 kB 🟢 -1.92 kB 🟢 -1.69 kB
assets/ValueControlPopover-CHDtRArM.js (removed) 5.53 kB 🟢 -5.53 kB 🟢 -2.01 kB 🟢 -1.79 kB
assets/ValueControlPopover-CjI3gx3M.js (new) 5.53 kB 🔴 +5.53 kB 🔴 +2.01 kB 🔴 +1.79 kB
assets/CancelSubscriptionDialogContent-BsHQxfkE.js (removed) 5.49 kB 🟢 -5.49 kB 🟢 -2.05 kB 🟢 -1.81 kB
assets/CancelSubscriptionDialogContent-qUuyjFTV.js (new) 5.49 kB 🔴 +5.49 kB 🔴 +2.06 kB 🔴 +1.81 kB
assets/DeleteWorkspaceDialogContent-BgKV34Q1.js (removed) 4.85 kB 🟢 -4.85 kB 🟢 -1.87 kB 🟢 -1.63 kB
assets/DeleteWorkspaceDialogContent-Dttk2O9I.js (new) 4.85 kB 🔴 +4.85 kB 🔴 +1.87 kB 🔴 +1.63 kB
assets/LeaveWorkspaceDialogContent-B5lE2V_u.js (new) 4.68 kB 🔴 +4.68 kB 🔴 +1.82 kB 🔴 +1.59 kB
assets/LeaveWorkspaceDialogContent-G3ds7KIL.js (removed) 4.68 kB 🟢 -4.68 kB 🟢 -1.82 kB 🟢 -1.59 kB
assets/RemoveMemberDialogContent-BgF_IdvQ.js (new) 4.66 kB 🔴 +4.66 kB 🔴 +1.77 kB 🔴 +1.55 kB
assets/RemoveMemberDialogContent-FwuCvwsY.js (removed) 4.66 kB 🟢 -4.66 kB 🟢 -1.77 kB 🟢 -1.56 kB
assets/RevokeInviteDialogContent-Bu-ejoUH.js (removed) 4.57 kB 🟢 -4.57 kB 🟢 -1.78 kB 🟢 -1.56 kB
assets/RevokeInviteDialogContent-Bzt8rGmh.js (new) 4.57 kB 🔴 +4.57 kB 🔴 +1.78 kB 🔴 +1.57 kB
assets/InviteMemberUpsellDialogContent-B2ZccMBl.js (new) 4.47 kB 🔴 +4.47 kB 🔴 +1.65 kB 🔴 +1.45 kB
assets/InviteMemberUpsellDialogContent-DIq_QhvU.js (removed) 4.47 kB 🟢 -4.47 kB 🟢 -1.65 kB 🟢 -1.45 kB
assets/tierBenefits-CFC63Th7.js (new) 4.45 kB 🔴 +4.45 kB 🔴 +1.58 kB 🔴 +1.36 kB
assets/tierBenefits-DKQ_bdeG.js (removed) 4.45 kB 🟢 -4.45 kB 🟢 -1.57 kB 🟢 -1.36 kB
assets/cloudSessionCookie-C1FKJY9X.js (new) 4.31 kB 🔴 +4.31 kB 🔴 +1.57 kB 🔴 +1.38 kB
assets/cloudSessionCookie-CdRGfWtf.js (removed) 4.31 kB 🟢 -4.31 kB 🟢 -1.57 kB 🟢 -1.38 kB
assets/ApiNodesSignInContent-1k0fX4Ks.js (removed) 4.26 kB 🟢 -4.26 kB 🟢 -1.4 kB 🟢 -1.23 kB
assets/ApiNodesSignInContent-DlHqMF4Y.js (new) 4.26 kB 🔴 +4.26 kB 🔴 +1.4 kB 🔴 +1.23 kB
assets/Media3DTop-BmByDcol.js (removed) 4.04 kB 🟢 -4.04 kB 🟢 -1.71 kB 🟢 -1.51 kB
assets/Media3DTop-CfOga8YN.js (new) 4.04 kB 🔴 +4.04 kB 🔴 +1.71 kB 🔴 +1.51 kB
assets/saveMesh-B6D5b4wH.js (removed) 4.03 kB 🟢 -4.03 kB 🟢 -1.76 kB 🟢 -1.56 kB
assets/saveMesh-CYgzV85M.js (new) 4.03 kB 🔴 +4.03 kB 🔴 +1.76 kB 🔴 +1.56 kB
assets/GlobalToast-D8kv8vkg.js (new) 3.05 kB 🔴 +3.05 kB 🔴 +1.26 kB 🔴 +1.08 kB
assets/GlobalToast-fy7NP95L.js (removed) 3.05 kB 🟢 -3.05 kB 🟢 -1.26 kB 🟢 -1.11 kB
assets/CloudRunButtonWrapper-C6SOr6a-.js (removed) 2.23 kB 🟢 -2.23 kB 🟢 -1.01 kB 🟢 -909 B
assets/CloudRunButtonWrapper-DoAVnOBG.js (new) 2.23 kB 🔴 +2.23 kB 🔴 +1.02 kB 🔴 +908 B
assets/SubscribeToRun-DjbEESkV.js (new) 2.13 kB 🔴 +2.13 kB 🔴 +983 B 🔴 +879 B
assets/SubscribeToRun-DkUMktwG.js (removed) 2.13 kB 🟢 -2.13 kB 🟢 -981 B 🟢 -865 B
assets/MediaAudioTop-BaSUUCjp.js (removed) 2.08 kB 🟢 -2.08 kB 🟢 -1.01 kB 🟢 -860 B
assets/MediaAudioTop-CWi1d_n_.js (new) 2.08 kB 🔴 +2.08 kB 🔴 +1 kB 🔴 +862 B
assets/cloudBadges-BN_F7taf.js (new) 1.96 kB 🔴 +1.96 kB 🔴 +975 B 🔴 +844 B
assets/cloudBadges-cVe4phE7.js (removed) 1.96 kB 🟢 -1.96 kB 🟢 -973 B 🟢 -851 B
assets/cloudSubscription-7gmlILUp.js (removed) 1.88 kB 🟢 -1.88 kB 🟢 -897 B 🟢 -777 B
assets/cloudSubscription-CLRkU6n7.js (new) 1.88 kB 🔴 +1.88 kB 🔴 +897 B 🔴 +778 B
assets/graphHasMissingNodes-B60siNtI.js (new) 1.83 kB 🔴 +1.83 kB 🔴 +859 B 🔴 +757 B
assets/graphHasMissingNodes-BdoHTvbp.js (removed) 1.83 kB 🟢 -1.83 kB 🟢 -860 B 🟢 -765 B
assets/signInSchema-CfIGWWIN.js (removed) 1.6 kB 🟢 -1.6 kB 🟢 -588 B 🟢 -551 B
assets/signInSchema-g2_i4271.js (new) 1.6 kB 🔴 +1.6 kB 🔴 +586 B 🔴 +553 B
assets/Load3D-BThCbpXZ.js (removed) 1.58 kB 🟢 -1.58 kB 🟢 -710 B 🟢 -631 B
assets/Load3D-CElV2WQr.js (new) 1.58 kB 🔴 +1.58 kB 🔴 +709 B 🔴 +632 B
assets/previousFullPath-1A5OXRV_.js (new) 1.53 kB 🔴 +1.53 kB 🔴 +696 B 🔴 +621 B
assets/previousFullPath-D6W1NHfT.js (removed) 1.53 kB 🟢 -1.53 kB 🟢 -694 B 🟢 -598 B
assets/nightlyBadges-E2KnsxyH.js (new) 1.49 kB 🔴 +1.49 kB 🔴 +744 B 🔴 +649 B
assets/nightlyBadges-JqevKKHr.js (removed) 1.49 kB 🟢 -1.49 kB 🟢 -742 B 🟢 -663 B
assets/Load3dViewerContent-BTrHk_Kx.js (new) 1.46 kB 🔴 +1.46 kB 🔴 +660 B 🔴 +586 B
assets/Load3dViewerContent-DTxffAmv.js (removed) 1.46 kB 🟢 -1.46 kB 🟢 -662 B 🟢 -587 B
assets/SubscriptionPanelContentWorkspace-BZC-0eSw.js (removed) 1.35 kB 🟢 -1.35 kB 🟢 -615 B 🟢 -535 B
assets/SubscriptionPanelContentWorkspace-D5nXui9S.js (new) 1.35 kB 🔴 +1.35 kB 🔴 +615 B 🔴 +537 B
assets/WidgetLegacy-DcsqDac7.js (new) 1.18 kB 🔴 +1.18 kB 🔴 +563 B 🔴 +496 B
assets/WidgetLegacy-tmoEh273.js (removed) 1.18 kB 🟢 -1.18 kB 🟢 -561 B 🟢 -494 B
assets/changeTracker-Dmnqslh8.js (removed) 1.15 kB 🟢 -1.15 kB 🟢 -551 B 🟢 -483 B
assets/changeTracker-kRDvPXyT.js (new) 1.15 kB 🔴 +1.15 kB 🔴 +552 B 🔴 +484 B
assets/i18n-C2JilxN-.js (removed) 137 B 🟢 -137 B 🟢 -122 B 🟢 -110 B
assets/i18n-CTjWCvvn.js (new) 137 B 🔴 +137 B 🔴 +122 B 🔴 +109 B

Status: 62 added / 62 removed / 73 unchanged

⚡ Performance Report

canvas-idle: · 60.0 avg FPS · 59.9 P5 FPS ✅ (target: ≥52) · 0ms TBT · 68.2 MB heap
canvas-mouse-sweep: · 60.0 avg FPS · 59.9 P5 FPS ✅ (target: ≥52) · 0ms TBT · 49.0 MB heap
canvas-zoom-sweep: · 60.0 avg FPS · 59.5 P5 FPS ✅ (target: ≥52) · 0ms TBT · 46.3 MB heap
dom-widget-clipping: · 60.0 avg FPS · 59.5 P5 FPS ✅ (target: ≥52) · 0ms TBT · 54.6 MB heap
large-graph-idle: · 60.0 avg FPS · 59.5 P5 FPS ✅ (target: ≥52) · 0ms TBT · 63.2 MB heap
large-graph-pan: · 60.0 avg FPS · 59.9 P5 FPS ✅ (target: ≥52) · 0ms TBT · 61.2 MB heap
large-graph-zoom: · 60.0 avg FPS · 59.9 P5 FPS ✅ (target: ≥52) · 0ms TBT · 66.7 MB heap
minimap-idle: · 60.0 avg FPS · 59.5 P5 FPS ✅ (target: ≥52) · 0ms TBT · 62.1 MB heap
subgraph-dom-widget-clipping: · 60.0 avg FPS · 59.5 P5 FPS ✅ (target: ≥52) · 0ms TBT · 55.3 MB heap
subgraph-idle: · 60.0 avg FPS · 59.5 P5 FPS ✅ (target: ≥52) · 0ms TBT · 68.0 MB heap
subgraph-mouse-sweep: · 60.0 avg FPS · 59.9 P5 FPS ✅ (target: ≥52) · 0ms TBT · 60.1 MB heap
viewport-pan-sweep: · 60.0 avg FPS · 59.5 P5 FPS ✅ (target: ≥52) · 0ms TBT · 64.9 MB heap
vue-large-graph-idle: · 58.1 avg FPS · 59.5 P5 FPS ✅ (target: ≥52) · 0ms TBT · 164.1 MB heap
vue-large-graph-pan: · 58.1 avg FPS · 59.5 P5 FPS ✅ (target: ≥52) · 0ms TBT · 168.7 MB heap
workflow-execution: · 60.0 avg FPS · 59.9 P5 FPS ✅ (target: ≥52) · 0ms TBT · 52.7 MB heap

No regressions detected.

All metrics
Metric Baseline PR (median) Δ Sig
canvas-idle: avg frame time 17ms 17ms -0% z=-0.9
canvas-idle: p95 frame time 17ms 17ms +0%
canvas-idle: layout duration 0ms 0ms +0%
canvas-idle: style recalc duration 9ms 9ms +5% z=-1.7
canvas-idle: layout count 0 0 +0%
canvas-idle: style recalc count 10 10 +0% z=-2.1
canvas-idle: task duration 344ms 362ms +5% z=-1.1
canvas-idle: script duration 23ms 23ms +2% z=-0.9
canvas-idle: TBT 0ms 0ms +0%
canvas-idle: heap used 68.9 MB 68.2 MB -1%
canvas-idle: DOM nodes 20 20 +0% z=-2.0
canvas-idle: event listeners 4 4 +0% z=-1.6
canvas-mouse-sweep: avg frame time 17ms 17ms +0% z=-0.4
canvas-mouse-sweep: p95 frame time 17ms 17ms +0%
canvas-mouse-sweep: layout duration 3ms 4ms +11% z=0.2
canvas-mouse-sweep: style recalc duration 33ms 39ms +18% z=-1.3
canvas-mouse-sweep: layout count 12 12 +0%
canvas-mouse-sweep: style recalc count 72 72 +0% z=-2.8
canvas-mouse-sweep: task duration 754ms 803ms +7% z=-1.1
canvas-mouse-sweep: script duration 122ms 131ms +8% z=-0.7
canvas-mouse-sweep: TBT 0ms 0ms +0%
canvas-mouse-sweep: heap used 46.5 MB 49.0 MB +6%
canvas-mouse-sweep: DOM nodes -262 -265 +1% z=-126.5
canvas-mouse-sweep: event listeners -133 -133 +0% z=-33.9
canvas-zoom-sweep: avg frame time 17ms 17ms +0% z=0.5
canvas-zoom-sweep: p95 frame time 17ms 17ms +1%
canvas-zoom-sweep: layout duration 1ms 1ms +5% z=0.0
canvas-zoom-sweep: style recalc duration 18ms 17ms -1% z=-1.1
canvas-zoom-sweep: layout count 6 6 +0%
canvas-zoom-sweep: style recalc count 32 30 -6% z=-2.8
canvas-zoom-sweep: task duration 283ms 292ms +3% z=-1.5
canvas-zoom-sweep: script duration 17ms 20ms +17% z=-2.4
canvas-zoom-sweep: TBT 0ms 0ms +0%
canvas-zoom-sweep: heap used 46.3 MB 46.3 MB -0%
canvas-zoom-sweep: DOM nodes 77 76 -1% z=-4.1
canvas-zoom-sweep: event listeners 19 19 +0% z=-0.9
dom-widget-clipping: avg frame time 17ms 17ms +0% z=0.1
dom-widget-clipping: p95 frame time 17ms 17ms +1%
dom-widget-clipping: layout duration 0ms 0ms +0%
dom-widget-clipping: style recalc duration 7ms 8ms +10% z=-3.0
dom-widget-clipping: layout count 0 0 +0%
dom-widget-clipping: style recalc count 10 11 +10% z=-4.2
dom-widget-clipping: task duration 320ms 347ms +8% z=-1.1
dom-widget-clipping: script duration 57ms 60ms +4% z=-2.5
dom-widget-clipping: TBT 0ms 0ms +0%
dom-widget-clipping: heap used 54.7 MB 54.6 MB -0%
dom-widget-clipping: DOM nodes 16 17 +6% z=-3.6
dom-widget-clipping: event listeners 0 0 +0% variance too high
large-graph-idle: avg frame time 17ms 17ms +0% z=-0.2
large-graph-idle: p95 frame time 17ms 17ms +1%
large-graph-idle: layout duration 0ms 0ms +0%
large-graph-idle: style recalc duration 9ms 8ms -7% z=-3.9
large-graph-idle: layout count 0 0 +0%
large-graph-idle: style recalc count 10 8 -20% z=-11.5
large-graph-idle: task duration 510ms 540ms +6% z=-0.0
large-graph-idle: script duration 89ms 96ms +9% z=-0.6
large-graph-idle: TBT 0ms 0ms +0%
large-graph-idle: heap used 58.5 MB 63.2 MB +8%
large-graph-idle: DOM nodes -261 -262 +0% z=-317.2
large-graph-idle: event listeners -129 -129 +0% z=-25.4
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 17ms 17ms +0% z=0.2
large-graph-pan: layout count 0 0 +0%
large-graph-pan: style recalc count 69 68 -1% z=-2.4
large-graph-pan: task duration 1055ms 1090ms +3% z=0.2
large-graph-pan: script duration 396ms 396ms -0% z=-0.6
large-graph-pan: TBT 0ms 0ms +0%
large-graph-pan: heap used 62.4 MB 61.2 MB -2%
large-graph-pan: DOM nodes -264 -266 +1% z=-172.7
large-graph-pan: event listeners -127 -127 +0% z=-159.3
large-graph-zoom: avg frame time 17ms 17ms -0%
large-graph-zoom: p95 frame time 17ms 17ms -1%
large-graph-zoom: layout duration 7ms 7ms +9%
large-graph-zoom: style recalc duration 17ms 18ms +4%
large-graph-zoom: layout count 60 60 +0%
large-graph-zoom: style recalc count 66 65 -2%
large-graph-zoom: task duration 1261ms 1316ms +4%
large-graph-zoom: script duration 473ms 492ms +4%
large-graph-zoom: TBT 0ms 0ms +0%
large-graph-zoom: heap used 72.0 MB 66.7 MB -7%
large-graph-zoom: DOM nodes -268 -270 +1%
large-graph-zoom: event listeners -125 -127 +2%
minimap-idle: avg frame time 17ms 17ms -0% z=-0.9
minimap-idle: p95 frame time 17ms 17ms -0%
minimap-idle: layout duration 0ms 0ms +0%
minimap-idle: style recalc duration 8ms 10ms +24% z=1.1
minimap-idle: layout count 0 0 +0%
minimap-idle: style recalc count 9 10 +11% z=0.6
minimap-idle: task duration 509ms 546ms +7% z=0.4
minimap-idle: script duration 86ms 97ms +14% z=-0.1
minimap-idle: TBT 0ms 0ms +0%
minimap-idle: heap used 62.1 MB 62.1 MB +0%
minimap-idle: DOM nodes -262 -263 +0% z=-206.3
minimap-idle: event listeners -129 -131 +2% z=-205.3
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 11ms 10ms -7% z=-3.0
subgraph-dom-widget-clipping: layout count 0 0 +0%
subgraph-dom-widget-clipping: style recalc count 46 45 -2% z=-5.0
subgraph-dom-widget-clipping: task duration 353ms 351ms -1% z=-1.5
subgraph-dom-widget-clipping: script duration 121ms 118ms -2% z=-1.5
subgraph-dom-widget-clipping: TBT 0ms 0ms +0%
subgraph-dom-widget-clipping: heap used 55.1 MB 55.3 MB +1%
subgraph-dom-widget-clipping: DOM nodes 18 16 -11% z=-5.5
subgraph-dom-widget-clipping: event listeners 6 6 +0% z=-1.7
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 -1% z=-2.4
subgraph-idle: layout count 0 0 +0%
subgraph-idle: style recalc count 10 9 -10% z=-2.9
subgraph-idle: task duration 324ms 334ms +3% z=-1.1
subgraph-idle: script duration 13ms 15ms +12% z=-2.1
subgraph-idle: TBT 0ms 0ms +0%
subgraph-idle: heap used 67.8 MB 68.0 MB +0%
subgraph-idle: DOM nodes 19 17 -11% z=-3.2
subgraph-idle: event listeners 4 4 +0% variance too high
subgraph-mouse-sweep: avg frame time 17ms 17ms +0% z=0.4
subgraph-mouse-sweep: p95 frame time 17ms 17ms +0%
subgraph-mouse-sweep: layout duration 4ms 5ms +11% z=-0.2
subgraph-mouse-sweep: style recalc duration 34ms 36ms +8% z=-1.8
subgraph-mouse-sweep: layout count 16 16 +0%
subgraph-mouse-sweep: style recalc count 76 75 -1% z=-2.7
subgraph-mouse-sweep: task duration 654ms 664ms +2% z=-1.5
subgraph-mouse-sweep: script duration 86ms 92ms +7% z=-1.3
subgraph-mouse-sweep: TBT 0ms 0ms +0%
subgraph-mouse-sweep: heap used 47.6 MB 60.1 MB +26%
subgraph-mouse-sweep: DOM nodes -260 61 -123% z=-2.6
subgraph-mouse-sweep: event listeners -131 4 -103% variance too high
viewport-pan-sweep: avg frame time 17ms 17ms -0%
viewport-pan-sweep: p95 frame time 17ms 17ms +1%
viewport-pan-sweep: layout duration 0ms 0ms +0%
viewport-pan-sweep: style recalc duration 49ms 51ms +3%
viewport-pan-sweep: layout count 0 0 +0%
viewport-pan-sweep: style recalc count 251 249 -1%
viewport-pan-sweep: task duration 3484ms 3666ms +5%
viewport-pan-sweep: script duration 1226ms 1237ms +1%
viewport-pan-sweep: TBT 0ms 0ms +0%
viewport-pan-sweep: heap used 68.4 MB 64.9 MB -5%
viewport-pan-sweep: DOM nodes -259 -263 +2%
viewport-pan-sweep: event listeners -113 -113 +0%
vue-large-graph-idle: avg frame time 17ms 17ms -0%
vue-large-graph-idle: p95 frame time 17ms 17ms +0%
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 11706ms 12276ms +5%
vue-large-graph-idle: script duration 566ms 616ms +9%
vue-large-graph-idle: TBT 0ms 0ms +0%
vue-large-graph-idle: heap used 163.7 MB 164.1 MB +0%
vue-large-graph-idle: DOM nodes -8331 -8331 +0%
vue-large-graph-idle: event listeners -16464 -16464 +0%
vue-large-graph-pan: avg frame time 18ms 17ms -3%
vue-large-graph-pan: p95 frame time 33ms 17ms -50%
vue-large-graph-pan: layout duration 0ms 0ms +0%
vue-large-graph-pan: style recalc duration 16ms 16ms +3%
vue-large-graph-pan: layout count 0 0 +0%
vue-large-graph-pan: style recalc count 66 65 -2%
vue-large-graph-pan: task duration 14008ms 14135ms +1%
vue-large-graph-pan: script duration 852ms 893ms +5%
vue-large-graph-pan: TBT 0ms 0ms +0%
vue-large-graph-pan: heap used 155.9 MB 168.7 MB +8%
vue-large-graph-pan: DOM nodes -8335 -8331 -0%
vue-large-graph-pan: event listeners -16488 -16462 -0%
workflow-execution: avg frame time 17ms 17ms +0% z=0.6
workflow-execution: p95 frame time 17ms 17ms -1%
workflow-execution: layout duration 1ms 2ms +12% z=-0.2
workflow-execution: style recalc duration 25ms 21ms -14% z=-1.4
workflow-execution: layout count 5 5 +0% z=0.1
workflow-execution: style recalc count 17 17 +0% z=-0.4
workflow-execution: task duration 119ms 113ms -6% z=-0.9
workflow-execution: script duration 23ms 23ms +3% z=-2.0
workflow-execution: TBT 0ms 0ms +0%
workflow-execution: heap used 52.4 MB 52.7 MB +1%
workflow-execution: DOM nodes 157 157 +0% z=-0.5
workflow-execution: event listeners 69 69 +0% z=3.9
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-05-04T16:29:14.177Z",
  "gitSha": "62d1c65cc91fc9fe495dbc9723ff408f48726ca6",
  "branch": "feat/RemoteComboOptions",
  "measurements": [
    {
      "name": "canvas-idle",
      "durationMs": 2031.25399999999,
      "styleRecalcs": 8,
      "styleRecalcDurationMs": 7.248999999999999,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 359.75,
      "heapDeltaBytes": 22756992,
      "heapUsedBytes": 71202928,
      "domNodes": 16,
      "jsHeapTotalBytes": 14680064,
      "scriptDurationMs": 18.970999999999997,
      "eventListeners": 4,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "canvas-idle",
      "durationMs": 2024.1350000000011,
      "styleRecalcs": 10,
      "styleRecalcDurationMs": 9.27,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 361.815,
      "heapDeltaBytes": 23288688,
      "heapUsedBytes": 71465464,
      "domNodes": 20,
      "jsHeapTotalBytes": 14680064,
      "scriptDurationMs": 23.31,
      "eventListeners": 4,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333335,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "canvas-idle",
      "durationMs": 2027.5010000000293,
      "styleRecalcs": 11,
      "styleRecalcDurationMs": 11.346,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 363.556,
      "heapDeltaBytes": 23446404,
      "heapUsedBytes": 72424672,
      "domNodes": 22,
      "jsHeapTotalBytes": 14942208,
      "scriptDurationMs": 23.948,
      "eventListeners": 4,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333332,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "canvas-mouse-sweep",
      "durationMs": 1921.2170000000128,
      "styleRecalcs": 73,
      "styleRecalcDurationMs": 38.62200000000001,
      "layouts": 12,
      "layoutDurationMs": 3.686,
      "taskDurationMs": 895.6080000000001,
      "heapDeltaBytes": -889852,
      "heapUsedBytes": 47688276,
      "domNodes": -263,
      "jsHeapTotalBytes": 15331328,
      "scriptDurationMs": 138.142,
      "eventListeners": -133,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333335,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "canvas-mouse-sweep",
      "durationMs": 1821.066999999971,
      "styleRecalcs": 72,
      "styleRecalcDurationMs": 36.163000000000004,
      "layouts": 12,
      "layoutDurationMs": 3.4869999999999997,
      "taskDurationMs": 799.6959999999999,
      "heapDeltaBytes": 5204984,
      "heapUsedBytes": 53892008,
      "domNodes": -265,
      "jsHeapTotalBytes": 16642048,
      "scriptDurationMs": 129.62300000000002,
      "eventListeners": -131,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66999999999998,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "canvas-mouse-sweep",
      "durationMs": 1834.6050000000105,
      "styleRecalcs": 72,
      "styleRecalcDurationMs": 39.720000000000006,
      "layouts": 12,
      "layoutDurationMs": 3.6420000000000003,
      "taskDurationMs": 803.16,
      "heapDeltaBytes": 3075804,
      "heapUsedBytes": 51401024,
      "domNodes": -265,
      "jsHeapTotalBytes": 15331328,
      "scriptDurationMs": 131.27700000000002,
      "eventListeners": -133,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.699999999999818
    },
    {
      "name": "canvas-zoom-sweep",
      "durationMs": 1718.0630000000292,
      "styleRecalcs": 30,
      "styleRecalcDurationMs": 17.444000000000003,
      "layouts": 6,
      "layoutDurationMs": 0.5890000000000001,
      "taskDurationMs": 291.21799999999996,
      "heapDeltaBytes": 101816,
      "heapUsedBytes": 48504528,
      "domNodes": 76,
      "jsHeapTotalBytes": 14417920,
      "scriptDurationMs": 18.302000000000003,
      "eventListeners": 19,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "canvas-zoom-sweep",
      "durationMs": 1733.981999999969,
      "styleRecalcs": 32,
      "styleRecalcDurationMs": 18.535000000000004,
      "layouts": 6,
      "layoutDurationMs": 0.6449999999999999,
      "taskDurationMs": 299.635,
      "heapDeltaBytes": 210812,
      "heapUsedBytes": 48767140,
      "domNodes": 76,
      "jsHeapTotalBytes": 15466496,
      "scriptDurationMs": 20.304000000000002,
      "eventListeners": 19,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.799999999999272
    },
    {
      "name": "canvas-zoom-sweep",
      "durationMs": 1717.2759999999698,
      "styleRecalcs": 30,
      "styleRecalcDurationMs": 16.928,
      "layouts": 6,
      "layoutDurationMs": 0.7030000000000001,
      "taskDurationMs": 292.189,
      "heapDeltaBytes": 181404,
      "heapUsedBytes": 48565152,
      "domNodes": 75,
      "jsHeapTotalBytes": 14680064,
      "scriptDurationMs": 19.823999999999998,
      "eventListeners": 19,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "dom-widget-clipping",
      "durationMs": 598.1560000000172,
      "styleRecalcs": 10,
      "styleRecalcDurationMs": 7.536000000000001,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 354.3310000000001,
      "heapDeltaBytes": 8425468,
      "heapUsedBytes": 57167764,
      "domNodes": 16,
      "jsHeapTotalBytes": 16252928,
      "scriptDurationMs": 60.295,
      "eventListeners": 2,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "dom-widget-clipping",
      "durationMs": 519.9399999999628,
      "styleRecalcs": 11,
      "styleRecalcDurationMs": 7.3500000000000005,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 320.098,
      "heapDeltaBytes": 9336480,
      "heapUsedBytes": 57724088,
      "domNodes": 17,
      "jsHeapTotalBytes": 15466496,
      "scriptDurationMs": 55.821999999999996,
      "eventListeners": 0,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.799999999999727
    },
    {
      "name": "dom-widget-clipping",
      "durationMs": 571.1459999999988,
      "styleRecalcs": 11,
      "styleRecalcDurationMs": 8.501999999999999,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 346.5590000000001,
      "heapDeltaBytes": 8850064,
      "heapUsedBytes": 57213316,
      "domNodes": 18,
      "jsHeapTotalBytes": 15466496,
      "scriptDurationMs": 59.620000000000005,
      "eventListeners": 0,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.669999999999998,
      "p95FrameDurationMs": 16.700000000000273
    },
    {
      "name": "large-graph-idle",
      "durationMs": 2058.3750000000123,
      "styleRecalcs": 9,
      "styleRecalcDurationMs": 8.911999999999999,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 542.0210000000001,
      "heapDeltaBytes": 7566804,
      "heapUsedBytes": 66230976,
      "domNodes": -262,
      "jsHeapTotalBytes": -233472,
      "scriptDurationMs": 97.29899999999999,
      "eventListeners": -129,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66999999999998,
      "p95FrameDurationMs": 16.799999999999272
    },
    {
      "name": "large-graph-idle",
      "durationMs": 2040.8210000000508,
      "styleRecalcs": 8,
      "styleRecalcDurationMs": 8.132,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 540.1819999999999,
      "heapDeltaBytes": 11801564,
      "heapUsedBytes": 70149436,
      "domNodes": -265,
      "jsHeapTotalBytes": -233472,
      "scriptDurationMs": 96.299,
      "eventListeners": -129,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "large-graph-idle",
      "durationMs": 2020.7810000000563,
      "styleRecalcs": 8,
      "styleRecalcDurationMs": 7.945999999999998,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 512.806,
      "heapDeltaBytes": 2905404,
      "heapUsedBytes": 62386412,
      "domNodes": -262,
      "jsHeapTotalBytes": 4542464,
      "scriptDurationMs": 87.43599999999999,
      "eventListeners": -131,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "large-graph-pan",
      "durationMs": 2135.5649999999855,
      "styleRecalcs": 68,
      "styleRecalcDurationMs": 18.435,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 1116.9220000000003,
      "heapDeltaBytes": -5709112,
      "heapUsedBytes": 53872684,
      "domNodes": -266,
      "jsHeapTotalBytes": 5271552,
      "scriptDurationMs": 409.38599999999997,
      "eventListeners": -127,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "large-graph-pan",
      "durationMs": 2134.1559999999618,
      "styleRecalcs": 67,
      "styleRecalcDurationMs": 16.345,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 1090.4389999999999,
      "heapDeltaBytes": 4636040,
      "heapUsedBytes": 64209844,
      "domNodes": -268,
      "jsHeapTotalBytes": 1806336,
      "scriptDurationMs": 395.59999999999997,
      "eventListeners": -127,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "large-graph-pan",
      "durationMs": 2109.8709999999983,
      "styleRecalcs": 68,
      "styleRecalcDurationMs": 17.432999999999996,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 1090.172,
      "heapDeltaBytes": 22872280,
      "heapUsedBytes": 82500280,
      "domNodes": -266,
      "jsHeapTotalBytes": 6000640,
      "scriptDurationMs": 393.063,
      "eventListeners": -127,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66999999999998,
      "p95FrameDurationMs": 16.699999999999818
    },
    {
      "name": "large-graph-zoom",
      "durationMs": 3164.492999999993,
      "styleRecalcs": 65,
      "styleRecalcDurationMs": 17.965,
      "layouts": 60,
      "layoutDurationMs": 7.414000000000001,
      "taskDurationMs": 1353.7310000000002,
      "heapDeltaBytes": 9039548,
      "heapUsedBytes": 69935012,
      "domNodes": -271,
      "jsHeapTotalBytes": 6057984,
      "scriptDurationMs": 495.569,
      "eventListeners": -127,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333335,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "large-graph-zoom",
      "durationMs": 3131.330000000048,
      "styleRecalcs": 65,
      "styleRecalcDurationMs": 18.439,
      "layouts": 60,
      "layoutDurationMs": 7.199,
      "taskDurationMs": 1313.795,
      "heapDeltaBytes": -7469304,
      "heapUsedBytes": 53424800,
      "domNodes": -270,
      "jsHeapTotalBytes": 5271552,
      "scriptDurationMs": 491.80400000000003,
      "eventListeners": -157,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.699999999999818
    },
    {
      "name": "large-graph-zoom",
      "durationMs": 3131.1549999999215,
      "styleRecalcs": 65,
      "styleRecalcDurationMs": 18.064,
      "layouts": 60,
      "layoutDurationMs": 7.412999999999999,
      "taskDurationMs": 1316.098,
      "heapDeltaBytes": 14756144,
      "heapUsedBytes": 75519616,
      "domNodes": -268,
      "jsHeapTotalBytes": 290816,
      "scriptDurationMs": 484.605,
      "eventListeners": -125,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333335,
      "p95FrameDurationMs": 16.699999999999818
    },
    {
      "name": "minimap-idle",
      "durationMs": 2020.7530000000133,
      "styleRecalcs": 10,
      "styleRecalcDurationMs": 10.651999999999997,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 550.8689999999999,
      "heapDeltaBytes": 8727460,
      "heapUsedBytes": 68730016,
      "domNodes": -259,
      "jsHeapTotalBytes": 3231744,
      "scriptDurationMs": 97.25,
      "eventListeners": -131,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333332,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "minimap-idle",
      "durationMs": 2036.670000000072,
      "styleRecalcs": 9,
      "styleRecalcDurationMs": 8.991,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 545.529,
      "heapDeltaBytes": -9436080,
      "heapUsedBytes": 52120060,
      "domNodes": -263,
      "jsHeapTotalBytes": 4018176,
      "scriptDurationMs": 97.937,
      "eventListeners": -131,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "minimap-idle",
      "durationMs": 2007.3840000000018,
      "styleRecalcs": 10,
      "styleRecalcDurationMs": 10.473000000000003,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 542.2249999999999,
      "heapDeltaBytes": 4605952,
      "heapUsedBytes": 65153016,
      "domNodes": -264,
      "jsHeapTotalBytes": 290816,
      "scriptDurationMs": 95.515,
      "eventListeners": -129,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333335,
      "p95FrameDurationMs": 16.799999999999272
    },
    {
      "name": "subgraph-dom-widget-clipping",
      "durationMs": 574.292000000014,
      "styleRecalcs": 46,
      "styleRecalcDurationMs": 10.481,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 351.35499999999996,
      "heapDeltaBytes": 9444688,
      "heapUsedBytes": 58032300,
      "domNodes": 18,
      "jsHeapTotalBytes": 15466496,
      "scriptDurationMs": 118.47799999999998,
      "eventListeners": 6,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "subgraph-dom-widget-clipping",
      "durationMs": 544.9339999998983,
      "styleRecalcs": 45,
      "styleRecalcDurationMs": 8.802999999999999,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 344.74899999999997,
      "heapDeltaBytes": 9213432,
      "heapUsedBytes": 57510552,
      "domNodes": 15,
      "jsHeapTotalBytes": 15990784,
      "scriptDurationMs": 116.242,
      "eventListeners": 6,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "subgraph-dom-widget-clipping",
      "durationMs": 561.3390000000891,
      "styleRecalcs": 45,
      "styleRecalcDurationMs": 9.910000000000002,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 352.196,
      "heapDeltaBytes": 9510024,
      "heapUsedBytes": 59051836,
      "domNodes": 16,
      "jsHeapTotalBytes": 15728640,
      "scriptDurationMs": 122.09899999999999,
      "eventListeners": 6,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.663333333333338,
      "p95FrameDurationMs": 16.700000000000273
    },
    {
      "name": "subgraph-idle",
      "durationMs": 1999.7020000000134,
      "styleRecalcs": 9,
      "styleRecalcDurationMs": 8.068999999999999,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 338.255,
      "heapDeltaBytes": 22516304,
      "heapUsedBytes": 71293776,
      "domNodes": 17,
      "jsHeapTotalBytes": 14680064,
      "scriptDurationMs": 14.773999999999996,
      "eventListeners": 4,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.799999999999272
    },
    {
      "name": "subgraph-idle",
      "durationMs": 1989.2569999999523,
      "styleRecalcs": 9,
      "styleRecalcDurationMs": 8.569,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 333.789,
      "heapDeltaBytes": 23198700,
      "heapUsedBytes": 71842248,
      "domNodes": 18,
      "jsHeapTotalBytes": 14942208,
      "scriptDurationMs": 14.537999999999995,
      "eventListeners": 4,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "subgraph-idle",
      "durationMs": 2000.3779999999551,
      "styleRecalcs": 9,
      "styleRecalcDurationMs": 8.808,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 326.428,
      "heapDeltaBytes": 22524936,
      "heapUsedBytes": 71338556,
      "domNodes": 17,
      "jsHeapTotalBytes": 14417920,
      "scriptDurationMs": 14.07,
      "eventListeners": 4,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.799999999999272
    },
    {
      "name": "subgraph-mouse-sweep",
      "durationMs": 1692.7510000000439,
      "styleRecalcs": 75,
      "styleRecalcDurationMs": 36.55700000000001,
      "layouts": 16,
      "layoutDurationMs": 4.837,
      "taskDurationMs": 664.183,
      "heapDeltaBytes": 14847240,
      "heapUsedBytes": 64351316,
      "domNodes": 61,
      "jsHeapTotalBytes": 15204352,
      "scriptDurationMs": 92.222,
      "eventListeners": 4,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.699999999999818
    },
    {
      "name": "subgraph-mouse-sweep",
      "durationMs": 1687.8909999999223,
      "styleRecalcs": 75,
      "styleRecalcDurationMs": 36.26,
      "layouts": 16,
      "layoutDurationMs": 4.621,
      "taskDurationMs": 650.3149999999999,
      "heapDeltaBytes": 14383840,
      "heapUsedBytes": 62997800,
      "domNodes": 61,
      "jsHeapTotalBytes": 15466496,
      "scriptDurationMs": 92.399,
      "eventListeners": 4,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.670000000000012,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "subgraph-mouse-sweep",
      "durationMs": 1690.1079999998956,
      "styleRecalcs": 75,
      "styleRecalcDurationMs": 36.426,
      "layouts": 16,
      "layoutDurationMs": 4.555,
      "taskDurationMs": 714.39,
      "heapDeltaBytes": -1702940,
      "heapUsedBytes": 46929688,
      "domNodes": -262,
      "jsHeapTotalBytes": 14807040,
      "scriptDurationMs": 94.80300000000001,
      "eventListeners": -133,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "viewport-pan-sweep",
      "durationMs": 8195.746999999983,
      "styleRecalcs": 250,
      "styleRecalcDurationMs": 51.633,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 3690.786,
      "heapDeltaBytes": 19767980,
      "heapUsedBytes": 79292484,
      "domNodes": -262,
      "jsHeapTotalBytes": 6291456,
      "scriptDurationMs": 1236.068,
      "eventListeners": -113,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.799999999999272
    },
    {
      "name": "viewport-pan-sweep",
      "durationMs": 8127.2840000000315,
      "styleRecalcs": 249,
      "styleRecalcDurationMs": 50.96200000000001,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 3665.6120000000005,
      "heapDeltaBytes": 9213524,
      "heapUsedBytes": 67623760,
      "domNodes": -263,
      "jsHeapTotalBytes": 6787072,
      "scriptDurationMs": 1237.3159999999998,
      "eventListeners": -113,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333332,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "viewport-pan-sweep",
      "durationMs": 8171.502999999916,
      "styleRecalcs": 249,
      "styleRecalcDurationMs": 50.526,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 3632.9439999999995,
      "heapDeltaBytes": 9518932,
      "heapUsedBytes": 68041668,
      "domNodes": -263,
      "jsHeapTotalBytes": 7311360,
      "scriptDurationMs": 1249.7019999999998,
      "eventListeners": -113,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333332,
      "p95FrameDurationMs": 16.80000000000109
    },
    {
      "name": "vue-large-graph-idle",
      "durationMs": 12316.667999999992,
      "styleRecalcs": 0,
      "styleRecalcDurationMs": 0,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 12275.838,
      "heapDeltaBytes": -25283756,
      "heapUsedBytes": 172113828,
      "domNodes": -8331,
      "jsHeapTotalBytes": 25489408,
      "scriptDurationMs": 628.14,
      "eventListeners": -16464,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 17.219999999999953,
      "p95FrameDurationMs": 16.799999999999272
    },
    {
      "name": "vue-large-graph-idle",
      "durationMs": 12027.682000000026,
      "styleRecalcs": 0,
      "styleRecalcDurationMs": 0,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 12016.596,
      "heapDeltaBytes": -25481940,
      "heapUsedBytes": 172487076,
      "domNodes": -8331,
      "jsHeapTotalBytes": 26275840,
      "scriptDurationMs": 586.253,
      "eventListeners": -16464,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 17.780000000000047,
      "p95FrameDurationMs": 16.80000000000291
    },
    {
      "name": "vue-large-graph-idle",
      "durationMs": 12361.976000000028,
      "styleRecalcs": 0,
      "styleRecalcDurationMs": 0,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 12325.838000000002,
      "heapDeltaBytes": -25507556,
      "heapUsedBytes": 171835644,
      "domNodes": -8331,
      "jsHeapTotalBytes": 23654400,
      "scriptDurationMs": 615.917,
      "eventListeners": -16464,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 17.219999999999953,
      "p95FrameDurationMs": 16.80000000000291
    },
    {
      "name": "vue-large-graph-pan",
      "durationMs": 14297.843,
      "styleRecalcs": 65,
      "styleRecalcDurationMs": 16.139999999999986,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 14271.356999999998,
      "heapDeltaBytes": -18784700,
      "heapUsedBytes": 190706268,
      "domNodes": -8331,
      "jsHeapTotalBytes": 23568384,
      "scriptDurationMs": 893.347,
      "eventListeners": -16460,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 17.223333333333237,
      "p95FrameDurationMs": 16.799999999999272
    },
    {
      "name": "vue-large-graph-pan",
      "durationMs": 14159.182999999985,
      "styleRecalcs": 67,
      "styleRecalcDurationMs": 16.345,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 14135.386,
      "heapDeltaBytes": -51344960,
      "heapUsedBytes": 153773392,
      "domNodes": -8331,
      "jsHeapTotalBytes": -3608576,
      "scriptDurationMs": 857.65,
      "eventListeners": -16490,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 17.219999999999953,
      "p95FrameDurationMs": 16.799999999999272
    },
    {
      "name": "vue-large-graph-pan",
      "durationMs": 14141.977999999996,
      "styleRecalcs": 65,
      "styleRecalcDurationMs": 16.315999999999995,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 14108.292000000003,
      "heapDeltaBytes": -25427364,
      "heapUsedBytes": 176913768,
      "domNodes": -8331,
      "jsHeapTotalBytes": 23568384,
      "scriptDurationMs": 898.5340000000001,
      "eventListeners": -16462,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 17.223333333333358,
      "p95FrameDurationMs": 16.80000000000291
    },
    {
      "name": "workflow-execution",
      "durationMs": 467.88500000002387,
      "styleRecalcs": 22,
      "styleRecalcDurationMs": 25.822,
      "layouts": 5,
      "layoutDurationMs": 1.7329999999999999,
      "taskDurationMs": 123.46100000000001,
      "heapDeltaBytes": 5312608,
      "heapUsedBytes": 55219972,
      "domNodes": 192,
      "jsHeapTotalBytes": 0,
      "scriptDurationMs": 20.582000000000004,
      "eventListeners": 69,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.663333333333338,
      "p95FrameDurationMs": 16.700000000000273
    },
    {
      "name": "workflow-execution",
      "durationMs": 138.14500000000862,
      "styleRecalcs": 11,
      "styleRecalcDurationMs": 21.185000000000002,
      "layouts": 4,
      "layoutDurationMs": 1.52,
      "taskDurationMs": 100.67499999999998,
      "heapDeltaBytes": 3488108,
      "heapUsedBytes": 53614464,
      "domNodes": 144,
      "jsHeapTotalBytes": 0,
      "scriptDurationMs": 24.65,
      "eventListeners": 37,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "workflow-execution",
      "durationMs": 448.7410000000409,
      "styleRecalcs": 17,
      "styleRecalcDurationMs": 21.197999999999997,
      "layouts": 5,
      "layoutDurationMs": 1.2009999999999998,
      "taskDurationMs": 112.69100000000002,
      "heapDeltaBytes": 5247128,
      "heapUsedBytes": 62159624,
      "domNodes": 157,
      "jsHeapTotalBytes": 3145728,
      "scriptDurationMs": 23.108000000000004,
      "eventListeners": 69,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000273
    }
  ]
}

@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 16, 2026

Codecov Report

❌ Patch coverage is 80.69498% with 50 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...ns/vueNodes/widgets/components/RichComboWidget.vue 77.01% 37 Missing ⚠️
.../components/form/dropdown/FormDropdownMenuItem.vue 31.57% 13 Missing ⚠️
@@             Coverage Diff             @@
##             main   #11310       +/-   ##
===========================================
- Coverage   71.73%   56.16%   -15.58%     
===========================================
  Files        1492     1387      -105     
  Lines       83893    71082    -12811     
  Branches    22192    19814     -2378     
===========================================
- Hits        60181    39922    -20259     
- Misses      22856    30633     +7777     
+ Partials      856      527      -329     
Flag Coverage Δ
e2e ?
unit 56.16% <80.69%> (+0.07%) ⬆️

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

Files with missing lines Coverage Δ
...sions/vueNodes/widgets/components/WidgetSelect.vue 95.74% <100.00%> (-4.26%) ⬇️
.../widgets/components/form/dropdown/FormDropdown.vue 58.97% <ø> (-11.92%) ⬇️
...ets/components/form/dropdown/FormDropdownInput.vue 100.00% <100.00%> (ø)
...gets/components/form/dropdown/FormDropdownMenu.vue 80.00% <ø> (ø)
...mponents/form/dropdown/FormDropdownMenuActions.vue 100.00% <100.00%> (ø)
...vueNodes/widgets/components/form/dropdown/types.ts 100.00% <ø> (ø)
...ensions/vueNodes/widgets/utils/fetchRemoteRoute.ts 100.00% <100.00%> (ø)
...tensions/vueNodes/widgets/utils/itemSchemaUtils.ts 100.00% <100.00%> (ø)
...ensions/vueNodes/widgets/utils/richComboHelpers.ts 100.00% <100.00%> (ø)
src/schemas/nodeDefSchema.ts 87.69% <100.00%> (-6.52%) ⬇️
... and 2 more

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

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 (2)
src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdownMenuItem.vue (1)

3-10: Prefer $t in template when t is only used there.

Since t is only used in the template (for aria-label translations), you can use the built-in $t in the template instead of importing useI18n and destructuring t in the script.

♻️ Suggested simplification
 <script setup lang="ts">
-import { computed, inject, ref } from 'vue'
-import { useI18n } from 'vue-i18n'
+import { computed, inject, ref } from 'vue'

 import { cn } from '@/utils/tailwindUtil'

 import { AssetKindKey } from './types'
 import type { LayoutMode } from './types'

-const { t } = useI18n()
-

Then in template:

         :aria-label="
           isPlayingAudio
-            ? t('widgets.remoteCombo.pauseAudioPreview')
-            : t('widgets.remoteCombo.playAudioPreview')
+            ? $t('widgets.remoteCombo.pauseAudioPreview')
+            : $t('widgets.remoteCombo.playAudioPreview')
         "

Based on learnings: "In Vue single-file components where the i18n t function is only used within the template, prefer using the built-in $t in the template instead of importing useI18n and destructuring t in the script."

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

In
`@src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdownMenuItem.vue`
around lines 3 - 10, The component imports useI18n and destructures t at module
scope (const { t } = useI18n()) even though t is only used in the template;
remove the import and the const { t } = useI18n() declaration and update the
template to use the built-in $t directly (keep existing aria-label usages but
call $t('...') in the template) so you no longer reference useI18n or t in the
script section.
src/renderer/extensions/vueNodes/widgets/components/RichComboWidget.vue (1)

32-43: Consider documenting the synthetic URL scheme.

The cache key uses a synthetic URL (https://cache.comfy.invalid/) that isn't a real endpoint. While functional, a brief inline note explaining this is a deliberate cache-key-only URL would help future maintainers understand the pattern.

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

In `@src/renderer/extensions/vueNodes/widgets/components/RichComboWidget.vue`
around lines 32 - 43, Add a short inline comment inside the buildCacheKey
function explaining that the returned URL (https://cache.comfy.invalid/) is a
synthetic, non-routable URL used solely to construct a deterministic cache key
rather than to call a real endpoint; place the comment immediately above the
return statement in buildCacheKey so future maintainers understand the
deliberate URL choice and its role in cache key generation.
🤖 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/schemas/nodeDefSchema.ts`:
- Around line 37-47: zRemoteComboConfig can currently accept an absolute route
while use_comfy_api is true, which leads to malformed downstream URLs; add a
cross-field validation on zRemoteComboConfig (using .superRefine or .refine)
that checks if use_comfy_api === true and the route is an absolute URL (e.g.,
starts with "http://" or "https://"), then add an error via ctx.addIssue
explaining the invalid combination; update the validation on the route check
accordingly so the superRefine can reliably detect absolute URLs and reject the
pair (refer to zRemoteComboConfig, route, and use_comfy_api).

---

Nitpick comments:
In
`@src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdownMenuItem.vue`:
- Around line 3-10: The component imports useI18n and destructures t at module
scope (const { t } = useI18n()) even though t is only used in the template;
remove the import and the const { t } = useI18n() declaration and update the
template to use the built-in $t directly (keep existing aria-label usages but
call $t('...') in the template) so you no longer reference useI18n or t in the
script section.

In `@src/renderer/extensions/vueNodes/widgets/components/RichComboWidget.vue`:
- Around line 32-43: Add a short inline comment inside the buildCacheKey
function explaining that the returned URL (https://cache.comfy.invalid/) is a
synthetic, non-routable URL used solely to construct a deterministic cache key
rather than to call a real endpoint; place the comment immediately above the
return statement in buildCacheKey so future maintainers understand the
deliberate URL choice and its role in cache key generation.
🪄 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: b725cc33-3f60-45df-948d-8ffc3f86338e

📥 Commits

Reviewing files that changed from the base of the PR and between a1e6fb3 and 0e30e28.

📒 Files selected for processing (12)
  • src/locales/en/main.json
  • src/renderer/extensions/vueNodes/widgets/components/RichComboWidget.vue
  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelect.vue
  • src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdown.vue
  • src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdownMenu.vue
  • src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdownMenuActions.vue
  • src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdownMenuItem.vue
  • src/renderer/extensions/vueNodes/widgets/components/form/dropdown/types.ts
  • src/renderer/extensions/vueNodes/widgets/utils/fetchRemoteRoute.ts
  • src/renderer/extensions/vueNodes/widgets/utils/itemSchemaUtils.test.ts
  • src/renderer/extensions/vueNodes/widgets/utils/itemSchemaUtils.ts
  • src/schemas/nodeDefSchema.ts

Comment thread src/schemas/nodeDefSchema.ts Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (2)
src/schemas/nodeDefSchema.validation.test.ts (1)

76-83: Tighten helper typing and use a function declaration

Line 76 uses object + unknown, which weakens compile-time checks in a schema validation test. Use a typed function declaration with satisfies ComfyNodeDef for better type safety and consistency with coding guidelines.

Suggested refactor
-    const buildNodeDef = (remoteCombo: object): unknown => ({
-      ...EXAMPLE_NODE_DEF,
-      input: {
-        required: {
-          voice: ['COMBO', { remote_combo: remoteCombo }]
-        }
-      }
-    })
+    function buildNodeDef(
+      remoteCombo: Record<string, unknown>
+    ): ComfyNodeDef {
+      return {
+        ...EXAMPLE_NODE_DEF,
+        input: {
+          required: {
+            voice: ['COMBO', { remote_combo: remoteCombo }]
+          }
+        }
+      } satisfies ComfyNodeDef
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/schemas/nodeDefSchema.validation.test.ts` around lines 76 - 83, Replace
the loose arrow helper buildNodeDef with a typed function declaration that
enforces ComfyNodeDef: change buildNodeDef to a function named
buildNodeDef(remoteCombo: unknown | SpecificType) that returns a value that
satisfies ComfyNodeDef (use the TypeScript "satisfies ComfyNodeDef" pattern on
the returned object) and tighten the parameter type instead of using plain
object and unknown; keep the same shape that spreads EXAMPLE_NODE_DEF and sets
input.required.voice to ['COMBO', { remote_combo: remoteCombo }] so tests remain
unchanged but gain compile-time type safety.
src/renderer/extensions/vueNodes/widgets/utils/fetchRemoteRoute.ts (1)

12-15: The schema validation already prevents absolute URLs with use_comfy_api=true.

The zRemoteComboConfig.superRefine() explicitly rejects use_comfy_api=true paired with absolute routes, requiring relative paths when using the Comfy API. The original concern about malformed URLs is already guarded by the schema validation.

That said, using new URL(route, getComfyApiBaseUrl()).toString() would be a reasonable defensive improvement for robustness and clarity, handling edge cases like base URL trailing slashes uniformly:

Optional hardening
 function resolveRoute(route: string, useComfyApi?: boolean): string {
   if (useComfyApi) {
-    return getComfyApiBaseUrl() + route
+    return new URL(route, getComfyApiBaseUrl()).toString()
   }
   return route
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/extensions/vueNodes/widgets/utils/fetchRemoteRoute.ts` around
lines 12 - 15, The schema already prevents absolute routes when useComfyApi is
true, but to harden resolveRoute implement URL resolution using the base API
URL: in function resolveRoute(route: string, useComfyApi?: boolean) call new
URL(route, getComfyApiBaseUrl()).toString() when useComfyApi is true so trailing
slashes and concatenation edge-cases are handled consistently; otherwise return
the original route unchanged.
🤖 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/renderer/extensions/vueNodes/widgets/components/RichComboWidget.vue`:
- Around line 250-296: TerminalSuccess is only used to decide whether to set
cache and to mark success, but the code currently only sets error.value when
rawItems.value.length === 0 which hides failures that occur after some pages
loaded; update the end-of-fetch logic in the async fetch loop (where
terminalSuccess, rawItems, setCache, loading, loadingMore, and error are
handled) to set an error state whenever terminalSuccess is false (even if
rawItems has items) — e.g., set error.value =
t('widgets.remoteCombo.loadFailedPartial') or reuse the existing message to
indicate a partial load, ensure loading.value and loadingMore.value are still
cleared and that caching only happens when terminalSuccess is true, and keep the
existing abort checks (controller.signal.aborted) intact so partial results are
shown but the UI is visibly in an error state.
- Around line 157-173: Replace raw payload/error logs in RichComboWidget.vue: do
not log res.data or the full err object; instead log only safe metadata (e.g.,
config.route, config.response_key, HTTP status/code and maybe
t('widgets.remoteCombo.loadFailed') context). Update the block that checks
fetchedItems (remove received: res.data from console.error) and the catch block
(replace console.error('RichComboWidget: fetch error', err) with a console.error
that includes only config.route, config.response_key, and the HTTP status or a
sanitized error message). Apply the same change to the other occurrences
mentioned (around where setCache, rawItems, applyAutoSelect are used) so no auth
headers or response payloads are ever logged.
- Around line 32-42: buildCacheKey currently only uses useAuthStore().userId to
scope comfy-api cache entries, which misses other auth contexts (workspace
headers or API keys) so cached options can bleed between auths; update
buildCacheKey to include a non-secret auth-scope identifier from the auth store
(e.g., useAuthStore().authScope or a new getter like
useAuthStore().getAuthScope()) when config.use_comfy_api is true, and append
that value to the URLSearchParams (do not include secrets or full headers);
alternatively, if adding a scope identifier isn't available, add logic to
clear/expire comfy-api cache entries on auth context changes by detecting
changes via useAuthStore().getAuthHeader() and invalidating prior keys.

---

Nitpick comments:
In `@src/renderer/extensions/vueNodes/widgets/utils/fetchRemoteRoute.ts`:
- Around line 12-15: The schema already prevents absolute routes when
useComfyApi is true, but to harden resolveRoute implement URL resolution using
the base API URL: in function resolveRoute(route: string, useComfyApi?: boolean)
call new URL(route, getComfyApiBaseUrl()).toString() when useComfyApi is true so
trailing slashes and concatenation edge-cases are handled consistently;
otherwise return the original route unchanged.

In `@src/schemas/nodeDefSchema.validation.test.ts`:
- Around line 76-83: Replace the loose arrow helper buildNodeDef with a typed
function declaration that enforces ComfyNodeDef: change buildNodeDef to a
function named buildNodeDef(remoteCombo: unknown | SpecificType) that returns a
value that satisfies ComfyNodeDef (use the TypeScript "satisfies ComfyNodeDef"
pattern on the returned object) and tighten the parameter type instead of using
plain object and unknown; keep the same shape that spreads EXAMPLE_NODE_DEF and
sets input.required.voice to ['COMBO', { remote_combo: remoteCombo }] so tests
remain unchanged but gain compile-time type safety.
🪄 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: 03eed29a-9e0c-4c36-9ccc-a0635de13baf

📥 Commits

Reviewing files that changed from the base of the PR and between 0e30e28 and 48e5a6f.

📒 Files selected for processing (13)
  • src/locales/en/main.json
  • src/renderer/extensions/vueNodes/widgets/components/RichComboWidget.vue
  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelect.vue
  • src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdown.vue
  • src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdownMenu.vue
  • src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdownMenuActions.vue
  • src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdownMenuItem.vue
  • src/renderer/extensions/vueNodes/widgets/components/form/dropdown/types.ts
  • src/renderer/extensions/vueNodes/widgets/utils/fetchRemoteRoute.ts
  • src/renderer/extensions/vueNodes/widgets/utils/itemSchemaUtils.test.ts
  • src/renderer/extensions/vueNodes/widgets/utils/itemSchemaUtils.ts
  • src/schemas/nodeDefSchema.ts
  • src/schemas/nodeDefSchema.validation.test.ts
✅ Files skipped from review due to trivial changes (1)
  • src/renderer/extensions/vueNodes/widgets/utils/itemSchemaUtils.test.ts
🚧 Files skipped from review as they are similar to previous changes (6)
  • src/renderer/extensions/vueNodes/widgets/components/form/dropdown/types.ts
  • src/locales/en/main.json
  • src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdownMenu.vue
  • src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdownMenuActions.vue
  • src/schemas/nodeDefSchema.ts
  • src/renderer/extensions/vueNodes/widgets/utils/itemSchemaUtils.ts

Comment thread src/renderer/extensions/vueNodes/widgets/components/RichComboWidget.vue Outdated
Comment thread src/renderer/extensions/vueNodes/widgets/components/RichComboWidget.vue Outdated
Comment on lines +250 to +296
if (!hasMore || pageItems.length === 0) {
terminalSuccess = true
break
}
page++
} catch (err: unknown) {
if (controller.signal.aborted) return

if (!isRetriableError(err)) {
console.error(
`RichComboWidget: non-retriable error on page ${page}`,
err
)
break
}
consecutiveErrors++
if (consecutiveErrors >= maxRetries) {
console.error(
`RichComboWidget: giving up after ${maxRetries} consecutive errors on page ${page}`,
err
)
break
}
// Retry same page after backoff
const delay = getBackoff(consecutiveErrors)
await new Promise((resolve) => setTimeout(resolve, delay))
}
}

if (controller.signal.aborted) return

// Cache the accumulated result before releasing loading state; an abort
// during setCache then skips the state reset instead of flickering it.
// Only cache on terminal success — caching partial results would poison the
// next mount with an incomplete list and never re-fetch the missing pages.
if (terminalSuccess && rawItems.value.length > 0) {
await setCache(config, rawItems.value)
}

if (controller.signal.aborted) return

loading.value = false
loadingMore.value = false

if (!terminalSuccess && rawItems.value.length === 0) {
error.value = t('widgets.remoteCombo.loadFailed')
}
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 | 🟠 Major

Don't silently leave the user on a truncated paginated result.

If a later page fails after earlier pages loaded, terminalSuccess stays false but Line 294 only sets error when rawItems is empty. The dropdown then looks complete even though some pages never arrived. Keep the partial items if you want, but still surface a failure state so users know the list is incomplete and should be refreshed.

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

In `@src/renderer/extensions/vueNodes/widgets/components/RichComboWidget.vue`
around lines 250 - 296, TerminalSuccess is only used to decide whether to set
cache and to mark success, but the code currently only sets error.value when
rawItems.value.length === 0 which hides failures that occur after some pages
loaded; update the end-of-fetch logic in the async fetch loop (where
terminalSuccess, rawItems, setCache, loading, loadingMore, and error are
handled) to set an error state whenever terminalSuccess is false (even if
rawItems has items) — e.g., set error.value =
t('widgets.remoteCombo.loadFailedPartial') or reuse the existing message to
indicate a partial load, ensure loading.value and loadingMore.value are still
cleared and that caching only happens when terminalSuccess is true, and keep the
existing abort checks (controller.signal.aborted) intact so partial results are
shown but the UI is visibly in an error state.

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: 2

♻️ Duplicate comments (1)
src/renderer/extensions/vueNodes/widgets/components/RichComboWidget.vue (1)

297-312: ⚠️ Potential issue | 🟠 Major

Surface paginated failures even when earlier pages loaded.

Lines 310-312 only set error when rawItems is empty. If a later page fails after earlier pages succeeded, the dropdown looks complete even though the result set is truncated.

Suggested fix
-  if (!terminalSuccess && rawItems.value.length === 0) {
+  if (!terminalSuccess) {
     error.value = t('widgets.remoteCombo.loadFailed')
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/extensions/vueNodes/widgets/components/RichComboWidget.vue`
around lines 297 - 312, The code only sets error.value when no items were
loaded, so partial results hide paginated failures; update the post-fetch logic
in the block using terminalSuccess, rawItems, setCache,
controller.signal.aborted, loading, loadingMore and error so that after
resetting loading/loadingMore (and after the controller.signal.aborted check)
you set error.value = t('widgets.remoteCombo.loadFailed') whenever
terminalSuccess is false (regardless of rawItems.length) — this surfaces
later-page failures while still preserving the existing behavior of only calling
setCache(config, rawItems.value) on terminalSuccess.
🤖 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/renderer/extensions/vueNodes/widgets/components/RichComboWidget.vue`:
- Around line 414-419: handleRefresh currently calls clearCache(config) then
immediately starts fetchItems(true), which can race with the cache delete; make
handleRefresh await the cache eviction to ensure clearCache(config) completes
before starting the replacement fetch. Specifically, change handleRefresh to be
async (or return the clearCache promise) and await clearCache(config) (using
await clearCache(config)) before calling fetchItems(true); keep the
abortController?.abort() and error.value = null behavior intact and only await
when config is non-null to avoid changing semantics when there's no config.
- Around line 402-435: The watch block that clears selectedSet then only re-adds
when an item is found discards a stale modelValue id; instead when modelValue
(modelValue.value) exists but items.value.find(...) returns undefined, keep or
re-add that id into selectedSet so the UI still shows the stored id; also update
the placeholder computed to, before falling back to the generic upload
placeholder, check if modelValue.value exists and no matching item is present
and return that stale id (or a localized “stale id” string containing
modelValue.value) so the user can see which remote id is stored; refer to the
watch([...], ...) block, selectedSet, modelValue, items, and placeholder
computed to implement these changes.

---

Duplicate comments:
In `@src/renderer/extensions/vueNodes/widgets/components/RichComboWidget.vue`:
- Around line 297-312: The code only sets error.value when no items were loaded,
so partial results hide paginated failures; update the post-fetch logic in the
block using terminalSuccess, rawItems, setCache, controller.signal.aborted,
loading, loadingMore and error so that after resetting loading/loadingMore (and
after the controller.signal.aborted check) you set error.value =
t('widgets.remoteCombo.loadFailed') whenever terminalSuccess is false
(regardless of rawItems.length) — this surfaces later-page failures while still
preserving the existing behavior of only calling setCache(config,
rawItems.value) on terminalSuccess.
🪄 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: 94903f56-5aa5-4fef-a47e-e0ad12a0c718

📥 Commits

Reviewing files that changed from the base of the PR and between 48e5a6f and fc1739c.

📒 Files selected for processing (4)
  • src/renderer/extensions/vueNodes/widgets/components/RichComboWidget.vue
  • src/renderer/extensions/vueNodes/widgets/utils/fetchRemoteRoute.test.ts
  • src/renderer/extensions/vueNodes/widgets/utils/richComboHelpers.test.ts
  • src/renderer/extensions/vueNodes/widgets/utils/richComboHelpers.ts

Comment on lines +402 to +435
watch(
[modelValue, items],
([val]) => {
selectedSet.value.clear()
if (val) {
const item = items.value.find((i) => i.id === val)
if (item) selectedSet.value.add(item.id)
}
},
{ immediate: true }
)

function handleRefresh() {
abortController?.abort()
error.value = null
const config = remoteConfig.value
if (config) void clearCache(config)
void fetchItems(true)
}

function handleSelection(selected: Set<string>) {
modelValue.value = selected.values().next().value
}

const placeholder = computed(() => {
if (loading.value) return t('widgets.remoteCombo.loading')
if (error.value) return error.value
if (loadingMore.value) {
return t('widgets.remoteCombo.itemsLoaded', {
count: items.value.length
})
}
return t('widgets.uploadSelect.placeholder')
})
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 | 🟠 Major

Preserve the stale remote id in the placeholder.

When modelValue contains an unknown id, Line 405 clears the visible selection, and Lines 434-435 fall back to the generic placeholder. That breaks the PR’s stale-id contract: the saved value survives, but the user can no longer see which stale id is currently stored.

Suggested fix
+const staleSelectionLabel = computed(() => {
+  const value = modelValue.value
+  if (!value) return null
+  return items.value.some((item) => item.id === value) ? null : value
+})
+
 const placeholder = computed(() => {
   if (loading.value) return t('widgets.remoteCombo.loading')
   if (error.value) return error.value
   if (loadingMore.value) {
     return t('widgets.remoteCombo.itemsLoaded', {
       count: items.value.length
     })
   }
+  if (staleSelectionLabel.value) return staleSelectionLabel.value
   return t('widgets.uploadSelect.placeholder')
 })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/extensions/vueNodes/widgets/components/RichComboWidget.vue`
around lines 402 - 435, The watch block that clears selectedSet then only
re-adds when an item is found discards a stale modelValue id; instead when
modelValue (modelValue.value) exists but items.value.find(...) returns
undefined, keep or re-add that id into selectedSet so the UI still shows the
stored id; also update the placeholder computed to, before falling back to the
generic upload placeholder, check if modelValue.value exists and no matching
item is present and return that stale id (or a localized “stale id” string
containing modelValue.value) so the user can see which remote id is stored;
refer to the watch([...], ...) block, selectedSet, modelValue, items, and
placeholder computed to implement these changes.

Comment thread src/renderer/extensions/vueNodes/widgets/components/RichComboWidget.vue Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
src/renderer/extensions/vueNodes/widgets/components/RichComboWidget.test.ts (2)

49-50: Assert translated messages (or t(...)) instead of raw i18n keys.

With empty messages (Line 49), these assertions only prove key echoing, not that translations exist. Prefer loading en/main.json or asserting with i18n.global.t(...).

Proposed fix
+import en from '@/locales/en/main.json'
-
-const i18n = createI18n({ legacy: false, locale: 'en', messages: { en: {} } })
+const i18n = createI18n({ legacy: false, locale: 'en', messages: { en } })

 ...
-      expect(screen.getByTestId('placeholder').textContent).toBe(
-        'widgets.remoteCombo.loadFailed'
-      )
+      expect(screen.getByTestId('placeholder').textContent).toBe(
+        i18n.global.t('widgets.remoteCombo.loadFailed')
+      )

 ...
-    expect(screen.getByTestId('placeholder').textContent).toBe(
-      'widgets.uploadSelect.placeholder'
-    )
+    expect(screen.getByTestId('placeholder').textContent).toBe(
+      i18n.global.t('widgets.uploadSelect.placeholder')
+    )

As per coding guidelines: "Use vue-i18n for ALL user-facing strings, configured in src/locales/en/main.json."

Also applies to: 194-196, 210-212, 286-288

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

In `@src/renderer/extensions/vueNodes/widgets/components/RichComboWidget.test.ts`
around lines 49 - 50, The test is currently initializing createI18n with an
empty messages object, so assertions only echo keys; load the real English
messages and assert translated strings (or use i18n.global.t(...)) instead.
Replace the createI18n call that sets messages: {} by importing the English
bundle (e.g. the exported object from src/locales/en/main.json) and pass it into
createI18n, then update assertions that compare to raw keys to instead call
i18n.global.t('your.key') or compare against the actual translated string from
the imported messages; apply the same change for the other failing assertions
referenced (around the other line groups).

68-84: Test stub is intentionally minimal; consider whether accessible query migration is necessary here.

The suggestion to use getByRole/text-based queries is technically valid—buttons can be queried by name, and span text by getByText. However, this is a minimal test stub designed to verify RichComboWidget's prop passing and event emission, not FormDropdown's rendering. The stub's data-testid attributes keep the focus on widget logic without coupling to accessibility structure. The tests already use accessible queries where they matter (e.g., getByText for item visibility at lines 183–184, getByLabelText at line 248). If deeper FormDropdown interaction testing is added, migrating this stub to semantic queries would be more justified.

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

In `@src/renderer/extensions/vueNodes/widgets/components/RichComboWidget.test.ts`
around lines 68 - 84, The test stub in RichComboWidget.test.ts intentionally
uses data-testid selectors for the FormDropdown stub (data-testid="dropdown",
"placeholder", "items-count", "item-{id}", "deselect") to keep focus on
RichComboWidget's prop-passing and event emission rather than accessibility
rendering; add a brief inline comment in the test next to the stub explaining
this intent and why migration to getByRole/getByText isn't done here (but note
that accessible queries should be used if deeper FormDropdown interaction tests
are added), so reviewers understand the deliberate choice without changing the
test behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/renderer/extensions/vueNodes/widgets/components/RichComboWidget.test.ts`:
- Around line 49-50: The test is currently initializing createI18n with an empty
messages object, so assertions only echo keys; load the real English messages
and assert translated strings (or use i18n.global.t(...)) instead. Replace the
createI18n call that sets messages: {} by importing the English bundle (e.g. the
exported object from src/locales/en/main.json) and pass it into createI18n, then
update assertions that compare to raw keys to instead call
i18n.global.t('your.key') or compare against the actual translated string from
the imported messages; apply the same change for the other failing assertions
referenced (around the other line groups).
- Around line 68-84: The test stub in RichComboWidget.test.ts intentionally uses
data-testid selectors for the FormDropdown stub (data-testid="dropdown",
"placeholder", "items-count", "item-{id}", "deselect") to keep focus on
RichComboWidget's prop-passing and event emission rather than accessibility
rendering; add a brief inline comment in the test next to the stub explaining
this intent and why migration to getByRole/getByText isn't done here (but note
that accessible queries should be used if deeper FormDropdown interaction tests
are added), so reviewers understand the deliberate choice without changing the
test behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 58f90ae3-f24c-4ff4-b6e7-b0cdc20a4457

📥 Commits

Reviewing files that changed from the base of the PR and between fc1739c and 9e3bb8a.

📒 Files selected for processing (2)
  • src/renderer/extensions/vueNodes/widgets/components/RichComboWidget.test.ts
  • src/renderer/extensions/vueNodes/widgets/components/RichComboWidget.vue
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/renderer/extensions/vueNodes/widgets/components/RichComboWidget.vue

@bigcat88 bigcat88 marked this pull request as draft April 29, 2026 14:56
@bigcat88 bigcat88 force-pushed the feat/RemoteComboOptions branch from 9e3bb8a to 99ead1c Compare April 29, 2026 16:28
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (1)
src/renderer/extensions/vueNodes/widgets/utils/itemSchemaUtils.test.ts (1)

96-183: Add a regression test for missing configured optional fields.

mapToDropdownItem currently yields '' (not undefined) when description_field or preview_url_field is configured but missing in data. A focused test would lock this edge behavior and prevent regressions.

Suggested test addition
 describe('mapToDropdownItem', () => {
+  it('uses empty strings when configured optional fields are missing', () => {
+    const item = mapToDropdownItem(
+      { id: 'v1', label: 'Roger' },
+      {
+        value_field: 'id',
+        label_field: 'label',
+        description_field: 'desc',
+        preview_url_field: 'sample',
+        preview_type: 'audio'
+      }
+    )
+
+    expect(item.description).toBe('')
+    expect(item.preview_url).toBe('')
+  })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/extensions/vueNodes/widgets/utils/itemSchemaUtils.test.ts`
around lines 96 - 183, Add a regression test to ensure mapToDropdownItem returns
undefined (not empty string) for optional configured fields when they are
missing in the source data: create a new test in itemSchemaUtils.test.ts that
calls mapToDropdownItem with description_field and preview_url_field set in the
config but omitted from the item object, then assert item.description ===
undefined and item.preview_url === undefined (reference the mapToDropdownItem
function in your test). This locks the edge-case behavior and prevents future
regressions.
🤖 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/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdownMenuItem.vue`:
- Around line 43-49: The empty catch in toggleAudioPreview swallows play errors;
update toggleAudioPreview (which uses audioRef) to handle rejections by logging
the error (e.g., console.error or the component logger) and surfacing a
user-friendly notification or emitting an event (e.g.,
this.$emit('audioPreviewError', error) or calling the existing
toast/notification helper) so failures are visible and actionable instead of
being silently ignored.

In `@src/renderer/extensions/vueNodes/widgets/utils/richComboHelpers.test.ts`:
- Around line 69-82: The test suite for getBackoff is missing an assertion for
attempt 0 which can allow an off-by-one regression; update the
describe('getBackoff') tests (the block containing the it('grows exponentially
from 1s')) to include expect(getBackoff(0)).toBe(1000) and adjust the test
description if desired so the baseline is explicit, ensuring the exponential
checks for getBackoff(1..4) and the cap tests for higher attempts remain
unchanged.
- Around line 27-67: Tests for buildCacheKey currently assert route,
responseKey, and auth-scope but miss asserting partitioning by page_size and
use_comfy_api; add test cases that call buildCacheKey (and parseKey) with
different page_size values and with use_comfy_api true vs false (using
baseConfig as the base), then assert the resulting keys are distinct (Set size
=== expected) and that parseKey(...).get('page_size') and
parseKey(...).get('use_comfy_api') reflect the values used so future changes
don’t merge those cache buckets accidentally.

---

Nitpick comments:
In `@src/renderer/extensions/vueNodes/widgets/utils/itemSchemaUtils.test.ts`:
- Around line 96-183: Add a regression test to ensure mapToDropdownItem returns
undefined (not empty string) for optional configured fields when they are
missing in the source data: create a new test in itemSchemaUtils.test.ts that
calls mapToDropdownItem with description_field and preview_url_field set in the
config but omitted from the item object, then assert item.description ===
undefined and item.preview_url === undefined (reference the mapToDropdownItem
function in your test). This locks the edge-case behavior and prevents future
regressions.
🪄 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: 5520af06-eb4a-4fb1-b4b1-ec1a31687746

📥 Commits

Reviewing files that changed from the base of the PR and between 9e3bb8a and 99ead1c.

📒 Files selected for processing (17)
  • src/locales/en/main.json
  • src/renderer/extensions/vueNodes/widgets/components/RichComboWidget.test.ts
  • src/renderer/extensions/vueNodes/widgets/components/RichComboWidget.vue
  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelect.vue
  • src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdown.vue
  • src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdownMenu.vue
  • src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdownMenuActions.vue
  • src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdownMenuItem.vue
  • src/renderer/extensions/vueNodes/widgets/components/form/dropdown/types.ts
  • src/renderer/extensions/vueNodes/widgets/utils/fetchRemoteRoute.test.ts
  • src/renderer/extensions/vueNodes/widgets/utils/fetchRemoteRoute.ts
  • src/renderer/extensions/vueNodes/widgets/utils/itemSchemaUtils.test.ts
  • src/renderer/extensions/vueNodes/widgets/utils/itemSchemaUtils.ts
  • src/renderer/extensions/vueNodes/widgets/utils/richComboHelpers.test.ts
  • src/renderer/extensions/vueNodes/widgets/utils/richComboHelpers.ts
  • src/schemas/nodeDefSchema.ts
  • src/schemas/nodeDefSchema.validation.test.ts
✅ Files skipped from review due to trivial changes (4)
  • src/renderer/extensions/vueNodes/widgets/components/form/dropdown/types.ts
  • src/locales/en/main.json
  • src/renderer/extensions/vueNodes/widgets/utils/itemSchemaUtils.ts
  • src/renderer/extensions/vueNodes/widgets/utils/richComboHelpers.ts
🚧 Files skipped from review as they are similar to previous changes (7)
  • src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdown.vue
  • src/schemas/nodeDefSchema.validation.test.ts
  • src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdownMenuActions.vue
  • src/schemas/nodeDefSchema.ts
  • src/renderer/extensions/vueNodes/widgets/components/RichComboWidget.test.ts
  • src/renderer/extensions/vueNodes/widgets/utils/fetchRemoteRoute.ts
  • src/renderer/extensions/vueNodes/widgets/components/RichComboWidget.vue

Comment on lines +43 to +49
function toggleAudioPreview(event: Event) {
event.stopPropagation()
const audio = audioRef.value
if (!audio) return
if (audio.paused) {
void audio.play().catch(() => {})
} else {
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

Handle audio play failures instead of swallowing them.

The empty catch in audio.play().catch(() => {}) suppresses failures with no propagation path.

Proposed fix
 const emit = defineEmits<{
   click: [index: number]
   mediaLoad: [event: Event]
+  mediaError: [error: Error]
 }>()
@@
 function toggleAudioPreview(event: Event) {
   event.stopPropagation()
   const audio = audioRef.value
   if (!audio) return
   if (audio.paused) {
-    void audio.play().catch(() => {})
+    void audio.play().catch((error: unknown) => {
+      isPlayingAudio.value = false
+      emit(
+        'mediaError',
+        error instanceof Error
+          ? error
+          : new Error('Audio preview failed to play')
+      )
+    })
   } else {
     audio.pause()
   }
 }
As per coding guidelines, "Implement proper error handling" and "Provide user-friendly and actionable error messages".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdownMenuItem.vue`
around lines 43 - 49, The empty catch in toggleAudioPreview swallows play
errors; update toggleAudioPreview (which uses audioRef) to handle rejections by
logging the error (e.g., console.error or the component logger) and surfacing a
user-friendly notification or emitting an event (e.g.,
this.$emit('audioPreviewError', error) or calling the existing
toast/notification helper) so failures are visible and actionable instead of
being silently ignored.

Comment on lines +27 to +67
describe('buildCacheKey', () => {
it('encodes the route and response_key', () => {
const params = parseKey(
buildCacheKey(
{
...baseConfig,
route: '/voices',
response_key: 'data.items'
},
'fb:user-a'
)
)
expect(params.get('route')).toBe('/voices')
expect(params.get('responseKey')).toBe('data.items')
})

it('partitions by authScope', () => {
const a = buildCacheKey(baseConfig, 'ws:team-a')
const b = buildCacheKey(baseConfig, 'ws:team-b')
expect(a).not.toBe(b)
expect(parseKey(a).get('u')).toBe('ws:team-a')
expect(parseKey(b).get('u')).toBe('ws:team-b')
})

it('treats workspace, firebase, and api-key scopes as distinct buckets', () => {
const ws = buildCacheKey(baseConfig, 'ws:abc')
const fb = buildCacheKey(baseConfig, 'fb:abc')
const apikey = buildCacheKey(baseConfig, 'apikey')
expect(new Set([ws, fb, apikey]).size).toBe(3)
})

it('falls back to "anon" when authScope is missing', () => {
expect(parseKey(buildCacheKey(baseConfig, null)).get('u')).toBe('anon')
expect(parseKey(buildCacheKey(baseConfig, undefined)).get('u')).toBe('anon')
})

it('treats missing optional fields as empty so the key stays stable', () => {
const params = parseKey(buildCacheKey(baseConfig, 'fb:user-a'))
expect(params.get('responseKey')).toBe('')
})
})
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 | 🟠 Major

Add cache-key regression tests for all contract dimensions.

Current tests lock route, responseKey, and auth-scope behavior, but they don’t assert the page_size / use_comfy_api cache partition contract called out in the PR. Please add explicit cases so future changes can’t silently merge distinct cache buckets.

✅ Suggested test additions
 describe('buildCacheKey', () => {
+  it('partitions by page_size to avoid cache collisions', () => {
+    const p50 = buildCacheKey({ ...baseConfig, page_size: 50 }, 'fb:user-a')
+    const p100 = buildCacheKey({ ...baseConfig, page_size: 100 }, 'fb:user-a')
+    expect(p50).not.toBe(p100)
+  })
+
+  it('does not user-scope when use_comfy_api is disabled', () => {
+    const cfg = { ...baseConfig, use_comfy_api: false }
+    const a = buildCacheKey(cfg, 'fb:user-a')
+    const b = buildCacheKey(cfg, 'fb:user-b')
+    expect(a).toBe(b)
+  })
+
   it('encodes the route and response_key', () => {

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

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

In `@src/renderer/extensions/vueNodes/widgets/utils/richComboHelpers.test.ts`
around lines 27 - 67, Tests for buildCacheKey currently assert route,
responseKey, and auth-scope but miss asserting partitioning by page_size and
use_comfy_api; add test cases that call buildCacheKey (and parseKey) with
different page_size values and with use_comfy_api true vs false (using
baseConfig as the base), then assert the resulting keys are distinct (Set size
=== expected) and that parseKey(...).get('page_size') and
parseKey(...).get('use_comfy_api') reflect the values used so future changes
don’t merge those cache buckets accidentally.

@bigcat88 bigcat88 force-pushed the feat/RemoteComboOptions branch from 99ead1c to 8f3ff03 Compare April 29, 2026 17:10
Adds a Vue-native renderer for combo inputs that declare `remote_combo=`
(RemoteComboOptions on the backend). Wired through WidgetSelect; runs in
parallel to the existing useRemoteWidget composable, which continues to
handle plain `remote=` combos.

The widget fetches a single items array from a relative `/proxy/...`
route — the frontend always prepends the comfy-api base URL and injects
auth headers (no opt-out flag while the feature is partner-node-only).
Items are mapped via the per-node `item_schema`, with image/video/audio
previews, search across multiple fields, optional auto-select first/last,
and a refresh button.

Caching: browser Cache API with TTL from `refresh`, partitioned by full
auth scope (workspace / firebase uid / api-key / anon). Refresh button
sequences cache delete before refetch to avoid the fast-response race.
Logging: auth headers and response bodies are redacted from error logs.

Also adds an audio preview branch to FormDropdownMenuItem — used by the
new widget when `preview_type='audio'`.

Tests cover: single-shot fetch, error classification, retry exhaustion,
refresh, deselect, stale-id preservation, cache-key partitioning,
route resolution, item-schema mapping, and Zod relative-route
validation.
@bigcat88 bigcat88 force-pushed the feat/RemoteComboOptions branch from 8f3ff03 to 97c2a0d Compare May 3, 2026 11:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XL This PR changes 500-999 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant