Skip to content

Conversation

@DrJKL
Copy link
Contributor

@DrJKL DrJKL commented Jan 16, 2026

Summary

Adds an editable Model Info Panel to show and modify asset details in the asset browser.

Changes

  • Add ModelInfoPanel component with editable display name, description, model type, base models, and tags
  • Add updateAssetMetadata action in assetsStore with optimistic cache updates
  • Add shadcn-vue Select components with design system styling
  • Add utility functions in assetMetadataUtils for extracting model metadata
  • Convert BaseModalLayout right panel state to defineModel pattern
  • Add slide-in animation and collapse button for right panel
  • Add class prop to PropertiesAccordionItem for custom styling
  • Fix keyboard handling: Escape in TagsInput/TextArea doesn't close parent modal

Testing

  • Unit tests for ModelInfoPanel component
  • Unit tests for assetMetadataUtils functions

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 16, 2026

📝 Walkthrough

Walkthrough

Adds a Model Info panel with right-side panel wiring to the Asset Browser, many asset metadata utilities and schema types, optimistic cache update APIs in the assets store, asset card/grid focus and show-info events, ownership/filter removal, i18n/test updates, and small prop destructuring tweaks.

Changes

Cohort / File(s) Summary
Schema & Types
src/platform/assets/schemas/assetSchema.ts
Made created_at optional, added metadata, introduced zAssetUserMetadata, exported AssetUserMetadata, and added AssetUpdatePayload.
Metadata Utilities & Tests
src/platform/assets/utils/assetMetadataUtils.ts, src/platform/assets/utils/assetMetadataUtils.test.ts
Added helpers (getAssetBaseModels, getAssetDisplayName, getAssetSourceUrl, getAssetTriggerPhrases, getAssetAdditionalTags, getSourceName, getAssetModelType, getAssetUserDescription) and central getStringProperty; expanded tests for many input shapes.
Store: assetsStore
src/stores/assetsStore.ts, src/stores/assetsStore.test.ts
Added optimistic cache helpers updateAssetInCache, updateAssetMetadata, updateAssetTags, exposed them and hasAssetKey publicly, and guarded created_at handling in logic/tests.
Service API
src/platform/assets/services/assetService.ts
updateAsset(id, newData) now accepts AssetUpdatePayload (payload shape changed).
Model Info Components & Tests (New)
src/platform/assets/components/modelInfo/ModelInfoPanel.vue, src/platform/assets/components/modelInfo/ModelInfoField.vue, src/platform/assets/components/modelInfo/ModelInfoPanel.test.ts
New ModelInfoPanel with three sections (basic info, model tagging, description), inline editing with debounced persistence, immutability guards; ModelInfoField component and tests added.
Asset Browser UI & Grid
src/platform/assets/components/AssetBrowserModal.vue, src/platform/assets/components/AssetGrid.vue
Right-panel v-model/title bindings and #rightPanel slot rendering ModelInfoPanel; focusedAsset/isRightPanelOpen state; props focusedAssetId/emptyTitle/emptyMessage; emits assetFocus/assetShowInfo; eager fetchModelTypes.
Asset Card
src/platform/assets/components/AssetCard.vue
Removed inline rename, added static displayName, showInfo and focus emits, focused prop, Use button and StatusBadge, layout/interaction changes.
Filter Bar & Tests
src/platform/assets/components/AssetFilterBar.vue, src/platform/assets/components/AssetFilterBar.test.ts
Removed ownership UI/state/props/emits and allAssets prop; switched to useI18n(); simplified explicit sort options; tests updated to match removed ownership.
Composables & Tests
src/platform/assets/composables/*, src/renderer/extensions/vueNodes/widgets/composables/*
Replaced single base-model API with getAssetBaseModels, exposed fetchModelTypes() replacing execute, adjusted null-safety and tests/mocks to include hasAssetKey.
Right-side panel prop tweak
src/components/rightSidePanel/layout/PropertiesAccordionItem.vue
Switched to destructured props, aliased classclassName, and updated template/computed bindings.
i18n & Docs
src/locales/en/main.json, docs/testing/vitest-patterns.md
Added g.use and expanded assetBrowser.modelInfo translations; docs advise using a real createI18n in component tests.
Minor
src/renderer/extensions/linearMode/LinearPreview.vue
Made formatTime(time?: string) parameter optional.

Sequence Diagram(s)

sequenceDiagram
    participant User as User
    participant Card as AssetCard
    participant Grid as AssetGrid
    participant Modal as AssetBrowserModal
    participant Panel as ModelInfoPanel
    participant Store as assetsStore
    participant API as API

    User->>Card: click / focus
    Card->>Grid: emit assetFocus
    Grid->>Modal: re-emit assetFocus
    Modal->>Modal: set focusedAsset

    User->>Card: click "Show Info"
    Card->>Grid: emit assetShowInfo
    Grid->>Modal: re-emit assetShowInfo
    Modal->>Modal: set isRightPanelOpen = true
    Modal->>Panel: render with focused asset

    User->>Panel: edit metadata (name/tags/description)
    Panel->>Panel: debounce pendingUpdates
    Panel->>Store: call updateAssetMetadata()
    Store->>Store: optimistically update cache
    Store->>API: send updateAsset(payload)
    API-->>Store: respond success
    Store-->>Panel: confirm persisted
Loading

Possibly related PRs

Suggested reviewers

  • KarryCharon
  • christian-byrne
  • shinshin86
  • Yorha4D
  • AustinMroz

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

@github-actions
Copy link

github-actions bot commented Jan 16, 2026

🎭 Playwright Tests: ⚠️ Passed with flaky tests

Results: 503 passed, 0 failed, 2 flaky, 8 skipped (Total: 513)

❌ Failed Tests

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

@github-actions
Copy link

github-actions bot commented Jan 16, 2026

🎨 Storybook Build Status

Build completed successfully!

⏰ Completed at: 01/22/2026, 03:30:23 AM UTC

🔗 Links


🎉 Your Storybook is ready for review!

@DrJKL DrJKL changed the title refactor: Model Info Panel styling and layout improvements feat(assets): add ModelInfoPanel for asset browser right panel Jan 16, 2026
@github-actions
Copy link

github-actions bot commented Jan 16, 2026

Bundle Size Report

Summary

  • Raw size: 21.5 MB baseline 21.4 MB — 🔴 +127 kB
  • Gzip: 4.46 MB baseline 4.44 MB — 🔴 +22.4 kB
  • Brotli: 3.31 MB baseline 3.29 MB — 🔴 +16.7 kB
  • Bundles: 152 current • 152 baseline • 96 added / 96 removed

Category Glance
Vendor & Third-Party 🔴 +84.6 kB (10.5 MB) · Data & Services 🔴 +50.3 kB (3.11 MB) · Graph Workspace 🟢 -7.48 kB (1.01 MB) · Other 🟢 -91 B (6.28 MB) · Panels & Settings ⚪ 0 B (430 kB) · Views & Navigation ⚪ 0 B (80.7 kB) · + 5 more

Per-category breakdown
App Entry Points — 22.3 kB (baseline 22.3 kB) • ⚪ 0 B

Main entry bundles and manifests

File Before After Δ Raw Δ Gzip Δ Brotli
assets/index-CGKX8nVd.js (new) 22.3 kB 🔴 +22.3 kB 🔴 +6.72 kB 🔴 +5.92 kB
assets/index-p6km4KIz.js (removed) 22.3 kB 🟢 -22.3 kB 🟢 -6.72 kB 🟢 -5.91 kB

Status: 1 added / 1 removed

Graph Workspace — 1.01 MB (baseline 1.02 MB) • 🟢 -7.48 kB

Graph editor runtime, canvas, workflow orchestration

File Before After Δ Raw Δ Gzip Δ Brotli
assets/GraphView-CqAKSg0J.js (removed) 1.02 MB 🟢 -1.02 MB 🟢 -201 kB 🟢 -153 kB
assets/GraphView-Bkcd-nwX.js (new) 1.01 MB 🔴 +1.01 MB 🔴 +200 kB 🔴 +152 kB

Status: 1 added / 1 removed

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

Top-level views, pages, and routed surfaces

File Before After Δ Raw Δ Gzip Δ Brotli
assets/CloudSurveyView-BBG1W_oH.js (removed) 17.1 kB 🟢 -17.1 kB 🟢 -3.6 kB 🟢 -3.06 kB
assets/CloudSurveyView-C37385Mz.js (new) 17.1 kB 🔴 +17.1 kB 🔴 +3.61 kB 🔴 +3.05 kB
assets/CloudLoginView-Ci-a1cYt.js (removed) 11.8 kB 🟢 -11.8 kB 🟢 -3.08 kB 🟢 -2.72 kB
assets/CloudLoginView-i5RGNmax.js (new) 11.8 kB 🔴 +11.8 kB 🔴 +3.09 kB 🔴 +2.72 kB
assets/UserCheckView-Bm0TblWV.js (new) 10.5 kB 🔴 +10.5 kB 🔴 +2.44 kB 🔴 +2.13 kB
assets/UserCheckView-PSVaO7Ue.js (removed) 10.5 kB 🟢 -10.5 kB 🟢 -2.44 kB 🟢 -2.13 kB
assets/CloudLayoutView-BALOzIq1.js (removed) 8.54 kB 🟢 -8.54 kB 🟢 -2.24 kB 🟢 -1.96 kB
assets/CloudLayoutView-CrbMmxR1.js (new) 8.54 kB 🔴 +8.54 kB 🔴 +2.25 kB 🔴 +1.97 kB
assets/CloudSignupView-BiHDGfGm.js (removed) 8.18 kB 🟢 -8.18 kB 🟢 -2.33 kB 🟢 -2.03 kB
assets/CloudSignupView-CrkAeI8e.js (new) 8.18 kB 🔴 +8.18 kB 🔴 +2.33 kB 🔴 +2.03 kB
assets/CloudForgotPasswordView-DnBeX3Gq.js (new) 6.26 kB 🔴 +6.26 kB 🔴 +1.92 kB 🔴 +1.69 kB
assets/CloudForgotPasswordView-Dtxw7J8E.js (removed) 6.26 kB 🟢 -6.26 kB 🟢 -1.92 kB 🟢 -1.69 kB
assets/UserSelectView-4OeAfWhy.js (new) 5.28 kB 🔴 +5.28 kB 🔴 +1.76 kB 🔴 +1.57 kB
assets/UserSelectView-B1c2_ppV.js (removed) 5.28 kB 🟢 -5.28 kB 🟢 -1.76 kB 🟢 -1.57 kB
assets/CloudSubscriptionRedirectView-BoRpGpg2.js (removed) 5.27 kB 🟢 -5.27 kB 🟢 -1.73 kB 🟢 -1.53 kB
assets/CloudSubscriptionRedirectView-C_T3ZVu8.js (new) 5.27 kB 🔴 +5.27 kB 🔴 +1.73 kB 🔴 +1.54 kB
assets/CloudAuthTimeoutView-R6biRwiE.js (removed) 5.24 kB 🟢 -5.24 kB 🟢 -1.7 kB 🟢 -1.49 kB
assets/CloudAuthTimeoutView-V2tkCDqi.js (new) 5.24 kB 🔴 +5.24 kB 🔴 +1.71 kB 🔴 +1.48 kB
assets/CloudSorryContactSupportView-DLFOuIIP.js (removed) 1.97 kB 🟢 -1.97 kB 🟢 -701 B 🟢 -630 B
assets/CloudSorryContactSupportView-Dsh-iHOS.js (new) 1.97 kB 🔴 +1.97 kB 🔴 +700 B 🔴 +623 B
assets/layout-BvtHapJI.js (new) 500 B 🔴 +500 B 🔴 +307 B 🔴 +264 B
assets/layout-DnknD3a1.js (removed) 500 B 🟢 -500 B 🟢 -309 B 🟢 -267 B

Status: 11 added / 11 removed

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

Configuration panels, inspectors, and settings screens

File Before After Δ Raw Δ Gzip Δ Brotli
assets/LegacyCreditsPanel-CzlJ-oxM.js (removed) 23.8 kB 🟢 -23.8 kB 🟢 -5.94 kB 🟢 -5.22 kB
assets/LegacyCreditsPanel-DSma3a-e.js (new) 23.8 kB 🔴 +23.8 kB 🔴 +5.95 kB 🔴 +5.25 kB
assets/SubscriptionPanel-CbNhH9R4.js (removed) 20.6 kB 🟢 -20.6 kB 🟢 -4.99 kB 🟢 -4.38 kB
assets/SubscriptionPanel-XR93wrPK.js (new) 20.6 kB 🔴 +20.6 kB 🔴 +5 kB 🔴 +4.39 kB
assets/KeybindingPanel-BlhVnoWW.js (removed) 14.2 kB 🟢 -14.2 kB 🟢 -3.74 kB 🟢 -3.31 kB
assets/KeybindingPanel-CLEfwqiN.js (new) 14.2 kB 🔴 +14.2 kB 🔴 +3.74 kB 🔴 +3.3 kB
assets/AboutPanel-BkEgxVxK.js (removed) 10.8 kB 🟢 -10.8 kB 🟢 -2.68 kB 🟢 -2.43 kB
assets/AboutPanel-CDelGfNA.js (new) 10.8 kB 🔴 +10.8 kB 🔴 +2.68 kB 🔴 +2.43 kB
assets/ExtensionPanel-BwL1ajtN.js (new) 10.2 kB 🔴 +10.2 kB 🔴 +2.71 kB 🔴 +2.4 kB
assets/ExtensionPanel-JT6TZJ4g.js (removed) 10.2 kB 🟢 -10.2 kB 🟢 -2.71 kB 🟢 -2.4 kB
assets/ServerConfigPanel-B5sttdws.js (new) 7.23 kB 🔴 +7.23 kB 🔴 +2.16 kB 🔴 +1.94 kB
assets/ServerConfigPanel-DvdgrGXc.js (removed) 7.23 kB 🟢 -7.23 kB 🟢 -2.17 kB 🟢 -1.94 kB
assets/UserPanel-Dg7pkYj-.js (removed) 6.58 kB 🟢 -6.58 kB 🟢 -1.9 kB 🟢 -1.67 kB
assets/UserPanel-Du6Jf2En.js (new) 6.58 kB 🔴 +6.58 kB 🔴 +1.91 kB 🔴 +1.67 kB
assets/cloudRemoteConfig-Dj-StW7p.js (new) 1.82 kB 🔴 +1.82 kB 🔴 +767 B 🔴 +665 B
assets/cloudRemoteConfig-DOS-eww7.js (removed) 1.82 kB 🟢 -1.82 kB 🟢 -770 B 🟢 -662 B
assets/remoteConfig-BEkdBLxH.js (removed) 1.07 kB 🟢 -1.07 kB 🟢 -519 B 🟢 -460 B
assets/remoteConfig-KDSIvxu_.js (new) 1.07 kB 🔴 +1.07 kB 🔴 +519 B 🔴 +451 B
assets/remoteConfig-CDQywK2g.js (new) 188 B 🔴 +188 B 🔴 +132 B 🔴 +114 B
assets/remoteConfig-D_gf6SLU.js (removed) 188 B 🟢 -188 B 🟢 -132 B 🟢 -115 B
assets/settings-2SPczzu9.js 38.3 kB 38.3 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-aJKlZz5L.js 32.1 kB 32.1 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-BpTSS9Vb.js 28.6 kB 28.6 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-CAe8lz65.js 30.4 kB 30.4 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-Cc5JlSUV.js 28.9 kB 28.9 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-CeebhifS.js 29.5 kB 29.5 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-CfriwPVt.js 29.6 kB 29.6 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-Cnh-GeSb.js 25.9 kB 25.9 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-CvUS0UPK.js 25.2 kB 25.2 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-D4bS3Z6x.js 34.3 kB 34.3 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-ERr_IA1D.js 31.2 kB 31.2 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B

Status: 10 added / 10 removed

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

Authentication, profile, and account management bundles

File Before After Δ Raw Δ Gzip Δ Brotli
assets/auth-C0d4lD8X.js (new) 3.54 kB 🔴 +3.54 kB 🔴 +1.24 kB 🔴 +1.06 kB
assets/auth-Ci6S9uPV.js (removed) 3.54 kB 🟢 -3.54 kB 🟢 -1.24 kB 🟢 -1.05 kB
assets/firebaseAuthStore-9CEgDzN1.js (removed) 217 B 🟢 -217 B 🟢 -137 B 🟢 -132 B
assets/firebaseAuthStore-ssTHedt0.js (new) 217 B 🔴 +217 B 🔴 +136 B 🔴 +117 B
assets/auth-HrCahzPF.js (removed) 178 B 🟢 -178 B 🟢 -142 B 🟢 -133 B
assets/auth-tw-cImRE.js (new) 178 B 🔴 +178 B 🔴 +142 B 🔴 +142 B

Status: 3 added / 3 removed

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

Modals, dialogs, drawers, and in-app editors

File Before After Δ Raw Δ Gzip Δ Brotli
assets/useSubscriptionDialog--GLdcPOW.js (removed) 2.62 kB 🟢 -2.62 kB 🟢 -1.24 kB 🟢 -1.09 kB
assets/useSubscriptionDialog-BXZbDrjf.js (new) 2.62 kB 🔴 +2.62 kB 🔴 +1.24 kB 🔴 +1.09 kB
assets/useSubscriptionDialog-BIMvKzSN.js (new) 179 B 🔴 +179 B 🔴 +110 B 🔴 +101 B
assets/useSubscriptionDialog-Dkc8lNEB.js (removed) 179 B 🟢 -179 B 🟢 -110 B 🟢 -98 B

Status: 2 added / 2 removed

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

Reusable component library chunks

File Before After Δ Raw Δ Gzip Δ Brotli
assets/SubscribeButton-B8L4EhE1.js (removed) 12.5 kB 🟢 -12.5 kB 🟢 -3.01 kB 🟢 -2.7 kB
assets/SubscribeButton-dAPD2OSS.js (new) 12.5 kB 🔴 +12.5 kB 🔴 +3.01 kB 🔴 +2.7 kB
assets/ComfyQueueButton-CMuIwBOM.js (new) 9.52 kB 🔴 +9.52 kB 🔴 +2.69 kB 🔴 +2.42 kB
assets/ComfyQueueButton-Cy1XYK9h.js (removed) 9.52 kB 🟢 -9.52 kB 🟢 -2.69 kB 🟢 -2.42 kB
assets/Button-BrECiPIE.js (removed) 3.75 kB 🟢 -3.75 kB 🟢 -1.37 kB 🟢 -1.21 kB
assets/Button-CjKNP5rX.js (new) 3.75 kB 🔴 +3.75 kB 🔴 +1.37 kB 🔴 +1.21 kB
assets/WidgetButton-2-KReTPF.js (removed) 2.41 kB 🟢 -2.41 kB 🟢 -980 B 🟢 -891 B
assets/WidgetButton-ZbTzc4JR.js (new) 2.41 kB 🔴 +2.41 kB 🔴 +983 B 🔴 +888 B
assets/CloudBadge-B1Mn_tt0.js (removed) 1.85 kB 🟢 -1.85 kB 🟢 -728 B 🟢 -647 B
assets/CloudBadge-DF9XvSNk.js (new) 1.85 kB 🔴 +1.85 kB 🔴 +731 B 🔴 +646 B
assets/UserAvatar-4JWUZu7Z.js (new) 1.73 kB 🔴 +1.73 kB 🔴 +728 B 🔴 +638 B
assets/UserAvatar-Bj7TTp1S.js (removed) 1.73 kB 🟢 -1.73 kB 🟢 -726 B 🟢 -639 B
assets/cloudFeedbackTopbarButton-CrMht5vw.js (removed) 866 B 🟢 -866 B 🟢 -524 B 🟢 -469 B
assets/cloudFeedbackTopbarButton-D7guWzeF.js (new) 866 B 🔴 +866 B 🔴 +526 B 🔴 +439 B
assets/ComfyQueueButton-CMXMljs0.js (removed) 181 B 🟢 -181 B 🟢 -118 B 🟢 -123 B
assets/ComfyQueueButton-mJnRAiSe.js (new) 181 B 🔴 +181 B 🔴 +118 B 🔴 +111 B

Status: 8 added / 8 removed

Data & Services — 3.11 MB (baseline 3.06 MB) • 🔴 +50.3 kB

Stores, services, APIs, and repositories

File Before After Δ Raw Δ Gzip Δ Brotli
assets/dialogService-BrIrD2DE.js (new) 1.93 MB 🔴 +1.93 MB 🔴 +409 kB 🔴 +312 kB
assets/dialogService-jJ-FPumn.js (removed) 1.89 MB 🟢 -1.89 MB 🟢 -400 kB 🟢 -306 kB
assets/api-C3J_IGjJ.js (new) 1.15 MB 🔴 +1.15 MB 🔴 +241 kB 🔴 +186 kB
assets/api-C7-EZv1R.js (removed) 1.15 MB 🟢 -1.15 MB 🟢 -240 kB 🟢 -186 kB
assets/releaseStore-CFF0DMOV.js (removed) 8.91 kB 🟢 -8.91 kB 🟢 -2.41 kB 🟢 -2.12 kB
assets/releaseStore-DQFtzxHo.js (new) 8.91 kB 🔴 +8.91 kB 🔴 +2.41 kB 🔴 +2.12 kB
assets/keybindingService-BWia1OXi.js (removed) 6.78 kB 🟢 -6.78 kB 🟢 -1.74 kB 🟢 -1.51 kB
assets/keybindingService-C6iRjms_.js (new) 6.78 kB 🔴 +6.78 kB 🔴 +1.74 kB 🔴 +1.51 kB
assets/serverConfigStore-9ynD59UJ.js (new) 2.64 kB 🔴 +2.64 kB 🔴 +878 B 🔴 +777 B
assets/serverConfigStore-BH5YNTKL.js (removed) 2.64 kB 🟢 -2.64 kB 🟢 -879 B 🟢 -782 B
assets/userStore-BhDd7-2f.js (removed) 2.16 kB 🟢 -2.16 kB 🟢 -813 B 🟢 -727 B
assets/userStore-DFe4ppB_.js (new) 2.16 kB 🔴 +2.16 kB 🔴 +812 B 🔴 +727 B
assets/audioService-B6a4CO8S.js (removed) 2.03 kB 🟢 -2.03 kB 🟢 -932 B 🟢 -820 B
assets/audioService-DwQpixO4.js (new) 2.03 kB 🔴 +2.03 kB 🔴 +931 B 🔴 +825 B
assets/releaseStore-BLKgLQ3J.js (removed) 140 B 🟢 -140 B 🟢 -106 B 🟢 -108 B
assets/releaseStore-Nht8uZvd.js (new) 140 B 🔴 +140 B 🔴 +106 B 🔴 +97 B

Status: 8 added / 8 removed

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

Helpers, composables, and utility bundles

File Before After Δ Raw Δ Gzip Δ Brotli
assets/colorUtil-CtMtIsH_.js (new) 7.2 kB 🔴 +7.2 kB 🔴 +2.22 kB 🔴 +1.95 kB
assets/colorUtil-uBzFbZOf.js (removed) 7.2 kB 🟢 -7.2 kB 🟢 -2.22 kB 🟢 -1.96 kB
assets/useErrorHandling-CPqNSmvW.js (new) 5.08 kB 🔴 +5.08 kB 🔴 +1.5 kB 🔴 +1.31 kB
assets/useErrorHandling-CTKetuo1.js (removed) 5.08 kB 🟢 -5.08 kB 🟢 -1.5 kB 🟢 -1.31 kB
assets/subscriptionCheckoutUtil-BRs3xXSX.js (new) 1.98 kB 🔴 +1.98 kB 🔴 +864 B 🔴 +752 B
assets/subscriptionCheckoutUtil-BtVA1lXe.js (removed) 1.98 kB 🟢 -1.98 kB 🟢 -858 B 🟢 -748 B
assets/markdownRendererUtil-omIIlgYg.js (new) 1.78 kB 🔴 +1.78 kB 🔴 +883 B 🔴 +776 B
assets/markdownRendererUtil-QWiFuAoc.js (removed) 1.78 kB 🟢 -1.78 kB 🟢 -883 B 🟢 -772 B
assets/audioUtils-BrdAWu9l.js (new) 970 B 🔴 +970 B 🔴 +549 B 🔴 +491 B
assets/audioUtils-CwLrSgzA.js (removed) 970 B 🟢 -970 B 🟢 -546 B 🟢 -487 B
assets/tailwindUtil-B2ghOW_p.js (new) 487 B 🔴 +487 B 🔴 +299 B 🔴 +267 B
assets/tailwindUtil-DEfdq6LJ.js (removed) 487 B 🟢 -487 B 🟢 -298 B 🟢 -266 B
assets/useCurrentUser-Dn2YGV9x.js (removed) 145 B 🟢 -145 B 🟢 -114 B 🟢 -106 B
assets/useCurrentUser-gsxw1oVK.js (new) 145 B 🔴 +145 B 🔴 +114 B 🔴 +100 B
assets/_plugin-vue_export-helper-xVPqUhAl.js 467 B 467 B ⚪ 0 B ⚪ 0 B ⚪ 0 B

Status: 7 added / 7 removed

Vendor & Third-Party — 10.5 MB (baseline 10.4 MB) • 🔴 +84.6 kB

External libraries and shared vendor chunks

File Before After Δ Raw Δ Gzip Δ Brotli
assets/vendor-other-CGoJGCaP.js (new) 3.92 MB 🔴 +3.92 MB 🔴 +832 kB 🔴 +645 kB
assets/vendor-other-CGljyUaV.js (removed) 3.92 MB 🟢 -3.92 MB 🟢 -832 kB 🟢 -644 kB
assets/vendor-primevue-ZNoQ1mvw.js (new) 3.04 MB 🔴 +3.04 MB 🔴 +560 kB 🔴 +328 kB
assets/vendor-primevue-DvjPM_Lx.js (removed) 3.04 MB 🟢 -3.04 MB 🟢 -560 kB 🟢 -329 kB
assets/vendor-reka-ui-BSEnCSTX.js (new) 256 kB 🔴 +256 kB 🔴 +50.6 kB 🔴 +42.1 kB
assets/vendor-reka-ui-BRjoKiJi.js (removed) 172 kB 🟢 -172 kB 🟢 -35.9 kB 🟢 -30.7 kB
assets/vendor-vue-7XRCqLaG.js (removed) 13.6 kB 🟢 -13.6 kB 🟢 -4.37 kB 🟢 -3.92 kB
assets/vendor-vue-ZDzjc7xN.js (new) 13.6 kB 🔴 +13.6 kB 🔴 +4.38 kB 🔴 +3.92 kB
assets/vendor-chart-Dr8GmMlH.js 408 kB 408 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-three-Dqb1VEds.js 1.83 MB 1.83 MB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-tiptap-BxNhpyUI.js 650 kB 650 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-xterm-CArXWFIl.js 398 kB 398 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B

Status: 4 added / 4 removed

Other — 6.28 MB (baseline 6.28 MB) • 🟢 -91 B

Bundles that do not match a named category

File Before After Δ Raw Δ Gzip Δ Brotli
assets/core-LOWaGDI1.js (removed) 177 kB 🟢 -177 kB 🟢 -42.9 kB 🟢 -35.8 kB
assets/core-CNOJ_Nyn.js (new) 177 kB 🔴 +177 kB 🔴 +42.8 kB 🔴 +35.8 kB
assets/Load3D-B4XRuDCR.js (removed) 55.7 kB 🟢 -55.7 kB 🟢 -9.17 kB 🟢 -7.92 kB
assets/Load3D-C2QEn_wA.js (new) 55.7 kB 🔴 +55.7 kB 🔴 +9.18 kB 🔴 +7.91 kB
assets/WidgetSelect-CWQeoi0X.js (new) 50.5 kB 🔴 +50.5 kB 🔴 +11.1 kB 🔴 +9.7 kB
assets/WidgetSelect-Dn-ahm0f.js (removed) 50.4 kB 🟢 -50.4 kB 🟢 -11.1 kB 🟢 -9.69 kB
assets/SubscriptionRequiredDialogContent-Bf6aLN7P.js (new) 28.7 kB 🔴 +28.7 kB 🔴 +6.78 kB 🔴 +5.93 kB
assets/SubscriptionRequiredDialogContent-DmUsHkBM.js (removed) 28.7 kB 🟢 -28.7 kB 🟢 -6.78 kB 🟢 -5.91 kB
assets/WidgetRecordAudio-BH-jZx8P.js (new) 18.2 kB 🔴 +18.2 kB 🔴 +4.96 kB 🔴 +4.43 kB
assets/WidgetRecordAudio-D5o76obQ.js (removed) 18.2 kB 🟢 -18.2 kB 🟢 -4.96 kB 🟢 -4.42 kB
assets/WidgetInputNumber--Ox4m3vq.js (removed) 18.2 kB 🟢 -18.2 kB 🟢 -4.49 kB 🟢 -4.01 kB
assets/WidgetInputNumber-Cw6BkNme.js (new) 18.2 kB 🔴 +18.2 kB 🔴 +4.49 kB 🔴 +4.01 kB
assets/WidgetImageCrop-DTK-2K2G.js (removed) 17.1 kB 🟢 -17.1 kB 🟢 -4.14 kB 🟢 -3.61 kB
assets/WidgetImageCrop-DYRm_D47.js (new) 17.1 kB 🔴 +17.1 kB 🔴 +4.14 kB 🔴 +3.63 kB
assets/PanelTemplate-BbqXTLO_.js (removed) 16.2 kB 🟢 -16.2 kB 🟢 -5.45 kB 🟢 -4.79 kB
assets/PanelTemplate-BOzXoGM5.js (new) 16.2 kB 🔴 +16.2 kB 🔴 +5.45 kB 🔴 +4.79 kB
assets/LazyImage-CeZNIuco.js (new) 14.1 kB 🔴 +14.1 kB 🔴 +4 kB 🔴 +3.54 kB
assets/LazyImage-CgUWd8TN.js (removed) 14.1 kB 🟢 -14.1 kB 🟢 -4 kB 🟢 -3.54 kB
assets/AudioPreviewPlayer-DiYGT8VS.js (removed) 10.8 kB 🟢 -10.8 kB 🟢 -2.98 kB 🟢 -2.65 kB
assets/AudioPreviewPlayer-xZJtNAyi.js (new) 10.8 kB 🔴 +10.8 kB 🔴 +2.97 kB 🔴 +2.65 kB
assets/WidgetWithControl-BiwhhXUn.js (removed) 8.02 kB 🟢 -8.02 kB 🟢 -2.65 kB 🟢 -2.39 kB
assets/WidgetWithControl-BjiDmTFp.js (new) 8.02 kB 🔴 +8.02 kB 🔴 +2.65 kB 🔴 +2.37 kB
assets/ValueControlPopover-CW9zF_W8.js (new) 4.86 kB 🔴 +4.86 kB 🔴 +1.55 kB 🔴 +1.38 kB
assets/ValueControlPopover-DLGIPLSO.js (removed) 4.86 kB 🟢 -4.86 kB 🟢 -1.55 kB 🟢 -1.38 kB
assets/WidgetBoundingBox-BRX2wHPg.js (removed) 4.71 kB 🟢 -4.71 kB 🟢 -1.13 kB 🟢 -990 B
assets/WidgetBoundingBox-CR9inj7y.js (new) 4.71 kB 🔴 +4.71 kB 🔴 +1.13 kB 🔴 +993 B
assets/WidgetGalleria-BaGgNvPD.js (removed) 4.57 kB 🟢 -4.57 kB 🟢 -1.56 kB 🟢 -1.43 kB
assets/WidgetGalleria-CmyH3M5n.js (new) 4.57 kB 🔴 +4.57 kB 🔴 +1.57 kB 🔴 +1.42 kB
assets/Slider-B3Y0CvB9.js (new) 4.21 kB 🔴 +4.21 kB 🔴 +1.52 kB 🔴 +1.34 kB
assets/Slider-CtzT4XB4.js (removed) 4.21 kB 🟢 -4.21 kB 🟢 -1.51 kB 🟢 -1.34 kB
assets/WidgetImageCompare-BdZ-BvCR.js (removed) 3.79 kB 🟢 -3.79 kB 🟢 -1.28 kB 🟢 -1.12 kB
assets/WidgetImageCompare-CF1qgx1r.js (new) 3.79 kB 🔴 +3.79 kB 🔴 +1.28 kB 🔴 +1.12 kB
assets/WidgetColorPicker-D8gXMtef.js (new) 3.71 kB 🔴 +3.71 kB 🔴 +1.38 kB 🔴 +1.25 kB
assets/WidgetColorPicker-SGgOvLcS.js (removed) 3.71 kB 🟢 -3.71 kB 🟢 -1.38 kB 🟢 -1.25 kB
assets/WidgetTextarea-B57L5K50.js (removed) 3.52 kB 🟢 -3.52 kB 🟢 -1.33 kB 🟢 -1.18 kB
assets/WidgetTextarea-DV6lPbf9.js (new) 3.52 kB 🔴 +3.52 kB 🔴 +1.33 kB 🔴 +1.17 kB
assets/WidgetMarkdown-CA02tD4L.js (removed) 3.22 kB 🟢 -3.22 kB 🟢 -1.28 kB 🟢 -1.14 kB
assets/WidgetMarkdown-FaXtNhvG.js (new) 3.22 kB 🔴 +3.22 kB 🔴 +1.28 kB 🔴 +1.14 kB
assets/WidgetToggleSwitch-CeVpnSql.js (new) 3.08 kB 🔴 +3.08 kB 🔴 +1.19 kB 🔴 +1.09 kB
assets/WidgetToggleSwitch-DZSbAjPX.js (removed) 3.08 kB 🟢 -3.08 kB 🟢 -1.19 kB 🟢 -1.1 kB
assets/GlobalToast-C5C4UJCF.js (new) 3.05 kB 🔴 +3.05 kB 🔴 +1.1 kB 🔴 +944 B
assets/GlobalToast-GczmNq8c.js (removed) 3.05 kB 🟢 -3.05 kB 🟢 -1.1 kB 🟢 -938 B
assets/SubscribeToRun-l3vhTHUp.js (new) 2.96 kB 🔴 +2.96 kB 🔴 +1.15 kB 🔴 +1.04 kB
assets/SubscribeToRun-ZrZqHbii.js (removed) 2.96 kB 🟢 -2.96 kB 🟢 -1.15 kB 🟢 -1.01 kB
assets/cloudSessionCookie-CsmwtphN.js (removed) 2.94 kB 🟢 -2.94 kB 🟢 -931 B 🟢 -807 B
assets/cloudSessionCookie-DGYyPpJq.js (new) 2.94 kB 🔴 +2.94 kB 🔴 +934 B 🔴 +806 B
assets/MediaVideoTop-BU8tqqSp.js (removed) 2.84 kB 🟢 -2.84 kB 🟢 -1.06 kB 🟢 -921 B
assets/MediaVideoTop-VT1eGBOq.js (new) 2.84 kB 🔴 +2.84 kB 🔴 +1.06 kB 🔴 +918 B
assets/WidgetChart-BpivfpYx.js (new) 2.79 kB 🔴 +2.79 kB 🔴 +1.06 kB 🔴 +967 B
assets/WidgetChart-Cm7DsFuW.js (removed) 2.79 kB 🟢 -2.79 kB 🟢 -1.06 kB 🟢 -942 B
assets/WidgetLayoutField-B8u6oxZz.js (removed) 2.61 kB 🟢 -2.61 kB 🟢 -1.01 kB 🟢 -891 B
assets/WidgetLayoutField-C_YvenHW.js (new) 2.61 kB 🔴 +2.61 kB 🔴 +1 kB 🔴 +893 B
assets/WidgetInputText-1n8eWUca.js (new) 2.58 kB 🔴 +2.58 kB 🔴 +1.01 kB 🔴 +914 B
assets/WidgetInputText-BT1Hl8HS.js (removed) 2.58 kB 🟢 -2.58 kB 🟢 -1.01 kB 🟢 -913 B
assets/BaseViewTemplate-CDu1mnir.js (removed) 2.42 kB 🟢 -2.42 kB 🟢 -1.04 kB 🟢 -944 B
assets/BaseViewTemplate-DO6kEXWg.js (new) 2.42 kB 🔴 +2.42 kB 🔴 +1.05 kB 🔴 +954 B
assets/Media3DTop-BWNjOk70.js (removed) 2.38 kB 🟢 -2.38 kB 🟢 -1.02 kB 🟢 -882 B
assets/Media3DTop-BXBGuOuC.js (new) 2.38 kB 🔴 +2.38 kB 🔴 +1.02 kB 🔴 +881 B
assets/MediaImageTop-De5G4aop.js (new) 2.34 kB 🔴 +2.34 kB 🔴 +1 kB 🔴 +874 B
assets/MediaImageTop-W-ky37Tn.js (removed) 2.34 kB 🟢 -2.34 kB 🟢 -1 kB 🟢 -879 B
assets/MediaAudioTop-BDEsaR6I.js (new) 2 kB 🔴 +2 kB 🔴 +883 B 🔴 +762 B
assets/MediaAudioTop-Bh5lwucv.js (removed) 2 kB 🟢 -2 kB 🟢 -880 B 🟢 -763 B
assets/CloudRunButtonWrapper-B1xjvZTV.js (new) 1.79 kB 🔴 +1.79 kB 🔴 +643 B 🔴 +561 B
assets/CloudRunButtonWrapper-CuHD3Hwi.js (removed) 1.79 kB 🟢 -1.79 kB 🟢 -641 B 🟢 -571 B
assets/cloudBadges-B6vDcM1O.js (new) 1.08 kB 🔴 +1.08 kB 🔴 +538 B 🔴 +482 B
assets/cloudBadges-Eor5E916.js (removed) 1.08 kB 🟢 -1.08 kB 🟢 -536 B 🟢 -497 B
assets/graphHasMissingNodes-DZ3GTnMZ.js (removed) 1.06 kB 🟢 -1.06 kB 🟢 -461 B 🟢 -425 B
assets/graphHasMissingNodes-HgjegHCp.js (new) 1.06 kB 🔴 +1.06 kB 🔴 +458 B 🔴 +423 B
assets/cloudSubscription-BfPdPHFn.js (new) 976 B 🔴 +976 B 🔴 +457 B 🔴 +401 B
assets/cloudSubscription-BhKL7InR.js (removed) 976 B 🟢 -976 B 🟢 -457 B 🟢 -399 B
assets/nightlyBadges-ClaRDL9r.js (new) 594 B 🔴 +594 B 🔴 +356 B 🔴 +313 B
assets/nightlyBadges-OfDzsHXt.js (removed) 594 B 🟢 -594 B 🟢 -357 B 🟢 -311 B
assets/WidgetBoundingBox-BttrDPHP.js (removed) 186 B 🟢 -186 B 🟢 -119 B 🟢 -112 B
assets/WidgetBoundingBox-nifCFubT.js (new) 186 B 🔴 +186 B 🔴 +119 B 🔴 +112 B
assets/WidgetInputNumber-CMfNGYiM.js (new) 186 B 🔴 +186 B 🔴 +119 B 🔴 +111 B
assets/WidgetInputNumber-h2b8gjAq.js (removed) 186 B 🟢 -186 B 🟢 -119 B 🟢 -119 B
assets/WidgetLegacy-BHTnOvOs.js (removed) 164 B 🟢 -164 B 🟢 -125 B 🟢 -109 B
assets/WidgetLegacy-CbwymTdP.js (new) 164 B 🔴 +164 B 🔴 +125 B 🔴 +104 B
assets/mixpanel.module-BAotZvHf.js (removed) 143 B 🟢 -143 B 🟢 -125 B 🟢 -108 B
assets/mixpanel.module-BU8p5dVL.js (new) 143 B 🔴 +143 B 🔴 +125 B 🔴 +109 B
assets/Load3D-BDGpwAF-.js (new) 131 B 🔴 +131 B 🔴 +107 B 🔴 +113 B
assets/Load3D-ClLGzQFN.js (removed) 131 B 🟢 -131 B 🟢 -107 B 🟢 -107 B
assets/auto-Bv9cmrEd.js 1.73 kB 1.73 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-Bhnu7Bw7.js 17 kB 17 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-BNUp31dg.js 18 kB 18 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-C3TkAluu.js 17.9 kB 17.9 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-D6LQMdxf.js 18 kB 18 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-DBI65Lek.js 18.8 kB 18.8 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-DFVgUBSf.js 19.3 kB 19.3 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-Dh30X9as.js 17.2 kB 17.2 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-DqWk-Mir.js 17.8 kB 17.8 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-M0rkm8ta.js 19.3 kB 19.3 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-Ofgk4qHD.js 18.5 kB 18.5 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-werCdhrX.js 20.6 kB 20.6 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-B_AN99IE.js 141 kB 141 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-BC1GV92E.js 161 kB 161 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-BqusGe8L.js 118 kB 118 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-C8RKguN8.js 134 kB 134 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-CDQbC8AF.js 105 kB 105 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-CkaEB0RZ.js 121 kB 121 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-Cp1IqAGs.js 124 kB 124 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-D7LGutim.js 105 kB 105 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-DWzvVuca.js 145 kB 145 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-k0E77V4e.js 120 kB 120 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-sSRfQYuB.js 117 kB 117 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-2P19NBSM.js 365 kB 365 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-BdrCgdBR.js 361 kB 361 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-BeCd-PGT.js 334 kB 334 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-BK0y0AHu.js 361 kB 361 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-BSY8OIp6.js 332 kB 332 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-CtGbZggL.js 403 kB 403 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-D5oNs76O.js 355 kB 355 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-DdCHCHTz.js 374 kB 374 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-DE71tjdx.js 437 kB 437 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-DmVgg8Xc.js 358 kB 358 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-naxJj6nk.js 403 kB 403 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/OBJLoader2WorkerModule-DTMpvldF.js 109 kB 109 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/preservedQueryNamespaces-BsMrb3S_.js 3.23 kB 3.23 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/previousFullPath-xzPidGnZ.js 838 B 838 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/rolldown-runtime-CqTjxoQm.js 1.53 kB 1.53 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/widget-Dneex3J5.js 518 B 518 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/widgetPropFilter-DdGUGU83.js 1.31 kB 1.31 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B

Status: 41 added / 41 removed

@DrJKL DrJKL force-pushed the drjkl/others-call-it-a-garage-sale branch 4 times, most recently from 3a09d83 to f93d463 Compare January 17, 2026 00:40
@DrJKL DrJKL added the preview label Jan 17, 2026
@DrJKL DrJKL force-pushed the drjkl/others-call-it-a-garage-sale branch from 17e00e1 to 6600086 Compare January 17, 2026 22:29
@DrJKL DrJKL self-assigned this Jan 17, 2026
@DrJKL
Copy link
Contributor Author

DrJKL commented Jan 17, 2026

Let me know if you'd like me to extract the Select component as a separate PR.
Did it.

@DrJKL DrJKL added the Design Used to request Product feedback on design decisions label Jan 18, 2026
@DrJKL DrJKL force-pushed the drjkl/others-call-it-a-garage-sale branch from bfa147f to feadad5 Compare January 18, 2026 09:26
@DrJKL DrJKL marked this pull request as ready for review January 19, 2026 04:03
@DrJKL DrJKL requested review from a team as code owners January 19, 2026 04:03
@dosubot dosubot bot added the size:XXL This PR changes 1000+ lines, ignoring generated files. label Jan 19, 2026
@DrJKL DrJKL assigned PabloWiedemann and comfydesigner and unassigned DrJKL Jan 19, 2026
Copy link
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: 8

Caution

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

⚠️ Outside diff range comments (1)
src/platform/assets/services/assetService.ts (1)

300-326: Use fromZodError for consistent error handling.

The updateAsset method directly includes the Zod error in the thrown message (line 324), which could expose internal schema details. Other methods like validateAssetResponse (line 66) use fromZodError for user-friendly error formatting.

♻️ Proposed fix
+import { fromZodError } from 'zod-validation-error'
+
 // ... in updateAsset function
     const newAsset = assetItemSchema.safeParse(await res.json())
     if (newAsset.success) {
       return newAsset.data
     }

-    throw new Error(
-      `Unable to update asset ${id}: Invalid response - ${newAsset.error}`
-    )
+    const error = fromZodError(newAsset.error)
+    console.error(`Invalid update response for asset ${id}:`, error)
+    throw new Error(`Unable to update asset ${id}: Invalid response`)

Based on learnings, prefer safeParse() with fromZodError() for logging while throwing user-friendly errors that don't reveal internal schema details.

🤖 Fix all issues with AI agents
In `@src/components/ui/select/SelectValue.vue`:
- Around line 1-12: The file SelectValue.vue uses
defineProps<SelectValueProps>() directly; to follow the project's reactive props
destructuring pattern (as used in SelectLabel.vue), destructure props into a
reactive constant (e.g., const props = defineProps<SelectValueProps>() -> const
{ /* ...props */ } = toRefs(defineProps<SelectValueProps>()) or the project's
established helper) and then bind that reactive object to the <SelectValue>
component so props are consistent across Select wrappers; locate
SelectValue.vue, the defineProps and the <SelectValue v-bind="props"> usage and
convert to the same reactive-destructured pattern used by SelectLabel.vue.

In `@src/components/widget/layout/BaseModalLayout.vue`:
- Around line 22-46: The icon-only Buttons (those invoking toggleLeftPanel,
toggleRightPanel, and closeDialog and conditioned by notMobile, hasRightPanel,
isRightPanelOpen, showLeftPanel) lack accessible names; add descriptive
accessible labels by adding an aria-label (or aria-labelledby) and optional
title to each Button so screen readers can announce their purpose (e.g., left
panel toggle, right panel open/close, close dialog), ensuring the label reflects
the dynamic state when applicable (use showLeftPanel to switch the label text).

In `@src/platform/assets/components/AssetGrid.vue`:
- Around line 75-86: The responsive breakpoint logic in AssetGrid.vue works but
can be simplified: replace multiple reactive refs (is2Xl, isXl, isLg, isMd) with
a single current breakpoint read from useBreakpoints().current() and compute
maxColumns based on that single value; update references to the useBreakpoints
import and the computed maxColumns (function name/symbol: maxColumns, and the
useBreakpoints call) to derive columns from the current breakpoint string
instead of multiple greaterOrEqual refs to reduce verbosity while preserving
behavior.

In `@src/platform/assets/components/modelInfo/ModelInfoPanel.test.ts`:
- Around line 131-155: Add a test in ModelInfoPanel.test.ts that verifies both
description fields render: create an asset via createMockAsset with a value that
getAssetDescription(asset) would return and a user-editable description in
user_metadata (or the prop the component uses), mount it with mountPanel, then
assert the read-only description text (from getAssetDescription) appears and
that the editable textarea/input for the user description exists and contains
the expected user-provided value; reference ModelInfoPanel.vue,
getAssetDescription(asset), mountPanel, and createMockAsset to locate where to
add this spec.

In `@src/platform/assets/components/modelInfo/ModelInfoPanel.vue`:
- Around line 141-157: The two ModelInfoField instances both use the same label
key causing duplicate "Description" headings; update the first (read-only)
ModelInfoField and the second (editable) ModelInfoField to use distinct
translation keys (e.g., change the label prop on the read-only ModelInfoField
and the editable one) so they display different labels (e.g., "Source
Description" vs "Your Notes"); update or add corresponding i18n keys for those
new labels and ensure the editable field still binds to userDescription, uses
ref descriptionTextarea and respects isImmutable.

In `@src/platform/assets/composables/useModelTypes.ts`:
- Around line 78-81: The fetchModelTypes function currently returns undefined on
the early exit but returns execute() (a Promise<ModelTypeOption[]>) otherwise,
causing an inconsistent return type; fix this by making the return contract
explicit: either (A) keep it fire-and-forget and make fetchModelTypes async and
await execute() so it always returns Promise<void> (no value) or (B) keep
returning the result and update the signature to
Promise<ModelTypeOption[]|undefined> and explicitly return undefined on early
exit; locate fetchModelTypes and the execute() call (and checks against
isReady.value / isLoading.value) and apply one of these two approaches
consistently across callers.

In `@src/platform/assets/utils/assetMetadataUtils.test.ts`:
- Around line 71-199: Add unit tests for the three new helpers:
getAssetBaseModels, getAssetModelType, and getAssetUserDescription. For
getAssetBaseModels, add tests that return an array when
user_metadata.base_models is an array of strings, filter out non-string entries,
and return [] when missing or not an array; for getAssetModelType, add tests
that return the expected model type string when user_metadata.model_type is a
string (including mixed-case inputs if normalization exists) and return null or
a default when absent or not a string; for getAssetUserDescription, add tests
that return the description string when user_metadata.user_description is a
string and return null/empty when missing or not a string. Use the same
mockAsset pattern as the existing tests and mirror the style used for
getAssetTriggerPhrases/getAssetAdditionalTags to keep consistency.

In `@src/stores/assetsStore.ts`:
- Around line 349-401: The optimistic cache updates in updateAssetMetadata and
updateAssetTags call updateAssetInCache before the network request and lack
rollback on failure; modify these functions to capture the previous asset
state(s) from modelAssetsByNodeType (using the same key-selection logic as
updateAssetInCache), perform the optimistic update, then wrap the
assetService.updateAsset call in try/catch and on error restore the previous
asset state(s) into modelAssetsByNodeType (or trigger a full re-fetch if
restoration is impractical); ensure the rollback honors the optional cacheKey
behavior and that error is re-thrown or handled/logged after restoring state.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (1)
src/components/widget/layout/BaseModalLayout.vue (1)

198-210: Consider converting scoped styles to Tailwind utilities.

Per coding guidelines, Vue components should avoid <style> blocks and use Tailwind for styling. Most of these styles can be expressed as Tailwind utilities:

<div class="h-[80vh] w-[90vw] max-w-[1280px] aspect-[20/13] 
            min-[1450px]:max-w-[1724px] rounded-2xl overflow-hidden relative">

The custom 1450px breakpoint can use Tailwind's arbitrary breakpoint syntax min-[1450px]:.

♻️ Proposed refactor
 <template>
   <div
-    class="base-widget-layout rounded-2xl overflow-hidden relative"
+    class="h-[80vh] w-[90vw] max-w-[1280px] min-[1450px]:max-w-[1724px] aspect-[20/13] rounded-2xl overflow-hidden relative"
     `@keydown.esc.capture`="handleEscape"
   >

Then remove the <style scoped> block entirely.

🤖 Fix all issues with AI agents
In `@src/components/widget/layout/BaseModalLayout.vue`:
- Around line 184-196: The escape handler in handleEscape currently only ignores
HTMLInputElement and HTMLTextAreaElement; update it to also ignore
contenteditable elements, select elements, and inputs inside Shadow DOM by:
treat event.target as Node/HTMLElement, return early if (target as
HTMLElement).isContentEditable is true or if tagName === 'SELECT', and
additionally inspect event.composedPath() for any
HTMLInputElement/HTMLTextAreaElement/select or an element with isContentEditable
so components using Shadow DOM are covered; keep the existing logic that closes
the right panel by setting isRightPanelOpen.value = false and stopping
propagation when none of these conditions match.

@DrJKL DrJKL marked this pull request as ready for review January 22, 2026 02:26
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Copy link
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: 8

Caution

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

⚠️ Outside diff range comments (1)
src/renderer/extensions/vueNodes/widgets/composables/useAssetWidgetData.ts (1)

50-59: Redundant null guard, but harmless.

The ?? [] at line 51 is unnecessary since assets (line 35-38) already guarantees an array via ?? []. The defensive guard doesn't hurt but adds noise.

♻️ Optional simplification
     const dropdownItems = computed<DropdownItem[]>(() => {
-      return (assets.value ?? []).map((asset) => ({
+      return assets.value.map((asset) => ({
         id: asset.id,
🤖 Fix all issues with AI agents
In `@src/platform/assets/components/AssetBrowserModal.vue`:
- Around line 26-29: The header div currently clears focusedAsset with
`@click`="focusedAsset = null", which bubbles and also fires when interacting with
children (e.g., the SearchBox or Upload button). Change the handler to use
`@click.self` so it only runs when the div itself is clicked (leave focusedAsset
variable name as is); update the div with `@click.self`="focusedAsset = null" to
avoid interfering with child component interactions.
- Around line 56-57: The click handler on the modal root currently assigns
focusedAsset = null and is triggering when interacting with child controls
(e.g., AssetFilterBar), causing unwanted bubbling; change the handler to only
fire for direct clicks by using `@click.self` on the element with the current
`@click` or move the clear logic into the AssetFilterBar component (e.g., emit a
close or clear event and let the parent set focusedAsset = null) so interactions
inside AssetFilterBar no longer clear focusedAsset.
- Around line 157-159: The current eager call "void fetchModelTypes()" silences
promise errors and can swallow failures; replace it with explicit error handling
by invoking fetchModelTypes() inside an async context and catching/logging
failures (e.g., using try/catch around await fetchModelTypes() or adding
.catch(...) to the promise) so errors from useModelTypes().fetchModelTypes() are
surfaced (include a clear log message) to aid debugging and ensure
ModelInfoPanel has visibility into load failures.

In `@src/platform/assets/components/modelInfo/ModelInfoPanel.test.ts`:
- Around line 37-48: The $t mock in mountPanel should be typed to match the real
i18n translation function to catch invalid keys/params; update the mock
signature in the mountPanel function (where ModelInfoPanel is mounted) to use
the actual i18n type (e.g., derive from the i18n instance's t type or
ComponentCustomProperties['$t'] / typeof i18n.global.t) instead of an untyped
(key: string, params?: Record<string,string>) signature so the compiler enforces
the correct key/param shapes during tests.
- Around line 115-123: Update the test mock so it matches the real asset shape:
change the user_metadata key used in ModelInfoPanel.test from the plural array
"base_models" to the singular string "base_model" (e.g., user_metadata: {
base_model: 'SDXL' }) so getAssetBaseModels will read and populate the field;
locate the usage in the test that calls createMockAsset and mountPanel to adjust
the mock accordingly.

In `@src/platform/assets/schemas/assetSchema.ts`:
- Around line 99-107: The zAssetUserMetadata schema is defined but only its
inferred type AssetUserMetadata is exported, so runtime validation isn’t
available; either export the schema (export const zAssetUserMetadata) so callers
can perform runtime checks or replace usages that expect validation with direct
zAssetUserMetadata.parse/ safeParse calls where appropriate (look for references
to AssetUserMetadata, zAssetUserMetadata) and update exports accordingly to
ensure runtime validation is accessible.

In `@src/platform/assets/utils/assetMetadataUtils.ts`:
- Around line 47-56: The getAssetBaseModels function currently returns
base_model strings without excluding empty or whitespace-only values; update the
logic in getAssetBaseModels to trim and filter out empty/whitespace entries:
when baseModel is an array, change the filter predicate to ensure typeof m ===
'string' and m.trim().length > 0, and when baseModel is a single string, return
[baseModel.trim()] only if baseModel.trim().length > 0; keep other behavior
unchanged.
- Around line 125-128: getSourceName currently returns localized display
strings; change it to return stable source keys (e.g., 'civitai' | 'huggingface'
| 'source') instead so translation happens at the call site via vue-i18n. Update
the function getSourceName(url: string) to return 'civitai' when
url.includes('civitai.com'), 'huggingface' when url.includes('huggingface.co'),
and 'source' otherwise; consumers should map those keys to translations (reuse
existing locale keys like providerCivitai and providerHuggingFace in
src/locales/en/main.json or add new entries as needed). Ensure no direct i18n
calls are added inside assetMetadataUtils.ts and adjust any call sites to
translate the returned key via vue-i18n.
♻️ Duplicate comments (4)
src/platform/assets/components/modelInfo/ModelInfoPanel.vue (2)

141-166: Duplicate "Description" labels remain unaddressed.

Both the read-only description (line 143) and the editable user description (line 147) use the same label key assetBrowser.modelInfo.description. As noted in the previous review, distinct labels would improve clarity. The design team input was requested - awaiting resolution.


243-250: Guard asset.user_metadata before spreading to prevent runtime error.

When asset.user_metadata is undefined, the spread { ...asset.user_metadata, ...pendingUpdates.value } will work in JavaScript but the explicit guard makes intent clearer and prevents potential issues.

🐛 Suggested fix
 const debouncedFlushMetadata = useDebounceFn(() => {
   if (isImmutable.value) return
   assetsStore.updateAssetMetadata(
     asset.id,
-    { ...asset.user_metadata, ...pendingUpdates.value },
+    { ...(asset.user_metadata ?? {}), ...pendingUpdates.value },
     cacheKey
   )
 }, 500)
src/stores/assetsStore.ts (1)

429-480: Add rollback/error handling for optimistic cache updates.
If assetService.updateAsset fails, the cache stays out of sync. Capture previous values and restore (or re-fetch) on failure to keep UI consistent. As per coding guidelines, implement proper error handling.

🛠️ Suggested rollback pattern
-      function updateAssetInCache(
+      function updateAssetInCache(
         assetId: string,
         updates: Partial<AssetItem>,
         cacheKey?: string
-      ) {
+      ): Map<string, AssetItem> {
         const keysToCheck = cacheKey
           ? [cacheKey]
           : Array.from(modelStateByKey.value.keys())
+        const previousByKey = new Map<string, AssetItem>()
 
         for (const key of keysToCheck) {
           const state = modelStateByKey.value.get(key)
           if (!state?.assets) continue
 
           const existingAsset = state.assets.get(assetId)
           if (existingAsset) {
+            previousByKey.set(key, existingAsset)
             const updatedAsset = { ...existingAsset, ...updates }
             state.assets.set(assetId, updatedAsset)
             assetsArrayCache.delete(key)
-            if (cacheKey) return
+            if (cacheKey) break
           }
         }
+        return previousByKey
       }
 
       async function updateAssetMetadata(
         assetId: string,
         userMetadata: Record<string, unknown>,
         cacheKey?: string
       ) {
-        updateAssetInCache(assetId, { user_metadata: userMetadata }, cacheKey)
-        await assetService.updateAsset(assetId, { user_metadata: userMetadata })
+        const previous = updateAssetInCache(
+          assetId,
+          { user_metadata: userMetadata },
+          cacheKey
+        )
+        try {
+          await assetService.updateAsset(assetId, { user_metadata: userMetadata })
+        } catch (err) {
+          for (const [key, asset] of previous) {
+            updateAssetInCache(assetId, asset, key)
+          }
+          throw err
+        }
       }
 
       async function updateAssetTags(
         assetId: string,
         tags: string[],
         cacheKey?: string
       ) {
-        updateAssetInCache(assetId, { tags }, cacheKey)
-        await assetService.updateAsset(assetId, { tags })
+        const previous = updateAssetInCache(assetId, { tags }, cacheKey)
+        try {
+          await assetService.updateAsset(assetId, { tags })
+        } catch (err) {
+          for (const [key, asset] of previous) {
+            updateAssetInCache(assetId, asset, key)
+          }
+          throw err
+        }
       }
src/platform/assets/utils/assetMetadataUtils.ts (1)

136-139: Avoid returning empty string from getAssetModelType.
Line 137 can yield '' for tags like '' or models/, which is awkward for consumers.

♻️ Suggested fix
-export function getAssetModelType(asset: AssetItem): string | null {
-  const typeTag = asset.tags?.find((tag) => tag !== 'models')
-  if (!typeTag) return null
-  return typeTag.includes('/') ? (typeTag.split('/').pop() ?? null) : typeTag
-}
+export function getAssetModelType(asset: AssetItem): string | null {
+  const rawTag = asset.tags?.find((tag) => tag && tag !== 'models')
+  const typeTag = rawTag?.trim()
+  if (!typeTag) return null
+  const lastSegment = typeTag.includes('/') ? typeTag.split('/').pop() : typeTag
+  return lastSegment && lastSegment.length > 0 ? lastSegment : null
+}

Comment on lines +157 to +159
// Eagerly fetch model types so they're available when ModelInfoPanel loads
const { fetchModelTypes } = useModelTypes()
void fetchModelTypes()
Copy link
Contributor

@coderabbitai coderabbitai bot Jan 22, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Unhandled promise rejection from fetchModelTypes.

The void prefix silences the floating promise lint error but doesn't handle potential errors. If fetchModelTypes fails, the error will be silently swallowed. Consider adding error handling or at minimum logging failures.

🐛 Proposed fix
 // Eagerly fetch model types so they're available when ModelInfoPanel loads
 const { fetchModelTypes } = useModelTypes()
-void fetchModelTypes()
+fetchModelTypes().catch((err) => {
+  console.warn('Failed to fetch model types:', err)
+})
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Eagerly fetch model types so they're available when ModelInfoPanel loads
const { fetchModelTypes } = useModelTypes()
void fetchModelTypes()
// Eagerly fetch model types so they're available when ModelInfoPanel loads
const { fetchModelTypes } = useModelTypes()
fetchModelTypes().catch((err) => {
console.warn('Failed to fetch model types:', err)
})
🤖 Prompt for AI Agents
In `@src/platform/assets/components/AssetBrowserModal.vue` around lines 157 - 159,
The current eager call "void fetchModelTypes()" silences promise errors and can
swallow failures; replace it with explicit error handling by invoking
fetchModelTypes() inside an async context and catching/logging failures (e.g.,
using try/catch around await fetchModelTypes() or adding .catch(...) to the
promise) so errors from useModelTypes().fetchModelTypes() are surfaced (include
a clear log message) to aid debugging and ensure ModelInfoPanel has visibility
into load failures.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I like to live dangerously.

Copy link
Contributor

Choose a reason for hiding this comment

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

@DrJKL Fair enough! 🚀 I'll leave the eager fetch as-is.


🧠 Learnings used
Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: src/AGENTS.md:0-0
Timestamp: 2026-01-16T21:33:47.062Z
Learning: Applies to src/**/*.{ts,tsx,vue} : Avoid using `ts-expect-error` - fix the underlying type issue instead

Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-24T19:47:02.860Z
Learning: Applies to src/**/*.{vue,ts} : Implement proper error handling

Comment on lines 37 to 48
const mountPanel = (asset: AssetDisplayItem) => {
return mount(ModelInfoPanel, {
props: { asset },
global: {
plugins: [createTestingPinia({ stubActions: false }), i18n],
mocks: {
$t: (key: string, params?: Record<string, string>) =>
params ? `${key}:${JSON.stringify(params)}` : key
}
}
})
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Consider typing the $t mock more strictly.

The $t mock function signature could benefit from stricter typing to catch translation key issues during test development.

♻️ Optional improvement
 mocks: {
-  $t: (key: string, params?: Record<string, string>) =>
+  $t: (key: string, params?: Record<string, unknown>) =>
     params ? `${key}:${JSON.stringify(params)}` : key
 }
🤖 Prompt for AI Agents
In `@src/platform/assets/components/modelInfo/ModelInfoPanel.test.ts` around lines
37 - 48, The $t mock in mountPanel should be typed to match the real i18n
translation function to catch invalid keys/params; update the mock signature in
the mountPanel function (where ModelInfoPanel is mounted) to use the actual i18n
type (e.g., derive from the i18n instance's t type or
ComponentCustomProperties['$t'] / typeof i18n.global.t) instead of an untyped
(key: string, params?: Record<string,string>) signature so the compiler enforces
the correct key/param shapes during tests.

Comment on lines 115 to 123
it('renders base models field', () => {
const asset = createMockAsset({
user_metadata: { base_models: ['SDXL'] }
})
const wrapper = mountPanel(asset)
expect(wrapper.text()).toContain(
'assetBrowser.modelInfo.compatibleBaseModels'
)
})
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

rg -n 'base_model' src/platform/assets/utils/assetMetadataUtils.ts

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 388


Fix test mock data: use base_model instead of base_models.

The test provides user_metadata: { base_models: ['SDXL'] } (plural array), but getAssetBaseModels reads from user_metadata?.base_model (singular string). The test passes because it only checks for the i18n key presence, but the mock data won't actually populate the field, failing to exercise the intended rendering path. Change the mock to user_metadata: { base_model: 'SDXL' }.

🤖 Prompt for AI Agents
In `@src/platform/assets/components/modelInfo/ModelInfoPanel.test.ts` around lines
115 - 123, Update the test mock so it matches the real asset shape: change the
user_metadata key used in ModelInfoPanel.test from the plural array
"base_models" to the singular string "base_model" (e.g., user_metadata: {
base_model: 'SDXL' }) so getAssetBaseModels will read and populate the field;
locate the usage in the test that calls createMockAsset and mountPanel to adjust
the mock accordingly.

Comment on lines +99 to +107
/** User-editable metadata fields for model assets */
const zAssetUserMetadata = z.object({
name: z.string().optional(),
base_model: z.array(z.string()).optional(),
additional_tags: z.array(z.string()).optional(),
user_description: z.string().optional()
})

export type AssetUserMetadata = z.infer<typeof zAssetUserMetadata>
Copy link
Contributor

@coderabbitai coderabbitai bot Jan 22, 2026

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Consider whether zAssetUserMetadata should be exported or used for validation.

The schema zAssetUserMetadata is defined but only its inferred type is exported. If runtime validation of user metadata is needed (e.g., before API calls), consider exporting the schema itself. If only the type is needed, this is fine as-is.

♻️ Optional: Export schema for runtime validation
 /** User-editable metadata fields for model assets */
-const zAssetUserMetadata = z.object({
+export const zAssetUserMetadata = z.object({
   name: z.string().optional(),
   base_model: z.array(z.string()).optional(),
   additional_tags: z.array(z.string()).optional(),
   user_description: z.string().optional()
 })
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/** User-editable metadata fields for model assets */
const zAssetUserMetadata = z.object({
name: z.string().optional(),
base_model: z.array(z.string()).optional(),
additional_tags: z.array(z.string()).optional(),
user_description: z.string().optional()
})
export type AssetUserMetadata = z.infer<typeof zAssetUserMetadata>
/** User-editable metadata fields for model assets */
export const zAssetUserMetadata = z.object({
name: z.string().optional(),
base_model: z.array(z.string()).optional(),
additional_tags: z.array(z.string()).optional(),
user_description: z.string().optional()
})
export type AssetUserMetadata = z.infer<typeof zAssetUserMetadata>
🤖 Prompt for AI Agents
In `@src/platform/assets/schemas/assetSchema.ts` around lines 99 - 107, The
zAssetUserMetadata schema is defined but only its inferred type
AssetUserMetadata is exported, so runtime validation isn’t available; either
export the schema (export const zAssetUserMetadata) so callers can perform
runtime checks or replace usages that expect validation with direct
zAssetUserMetadata.parse/ safeParse calls where appropriate (look for references
to AssetUserMetadata, zAssetUserMetadata) and update exports accordingly to
ensure runtime validation is accessible.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No unused exports.

Copy link
Contributor

Choose a reason for hiding this comment

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

@DrJKL Understood! Keeping the type export only is fine—no need to expose the schema if it won't be used for runtime validation. 👍


🧠 Learnings used
Learnt from: DrJKL
Repo: Comfy-Org/ComfyUI_frontend PR: 7746
File: src/platform/assets/services/assetService.ts:484-491
Timestamp: 2026-01-06T19:20:56.167Z
Learning: In `src/platform/assets/services/assetService.ts`, prefer using `schema.safeParse()` over `schema.parse()` for validating API responses to avoid throwing ZodError with internal schema details; follow the existing pattern: call `safeParse()`, check `result.success`, and use `fromZodError(result.error)` to format error messages for logging while throwing user-friendly errors.

Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-16T21:33:34.380Z
Learning: Applies to src/**/*.{ts,tsx} : Minimize the surface area (exported values) of each module and composable

Comment on lines +125 to +128
export function getSourceName(url: string): string {
if (url.includes('civitai.com')) return 'Civitai'
if (url.includes('huggingface.co')) return 'Hugging Face'
return 'Source'
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

cat -n src/platform/assets/utils/assetMetadataUtils.ts | head -150

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 6129


🏁 Script executed:

rg "getSourceName" -A 3 -B 3

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 3591


🏁 Script executed:

fd -t f "assetMetadataUtils" --exec head -5 {}

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 434


🏁 Script executed:

rg "sourceName" src/platform/assets/components/modelInfo/ModelInfoPanel.vue -A 5 -B 2

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 781


🏁 Script executed:

grep -n "sourceName" src/platform/assets/components/modelInfo/ModelInfoPanel.vue | head -20

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 235


🏁 Script executed:

cat src/platform/assets/components/modelInfo/ModelInfoPanel.vue | sed -n '1,50p'

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 1820


🏁 Script executed:

rg "viewOnSource" src/locales/en/main.json

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 111


🏁 Script executed:

rg "assetBrowser" src/locales/en/main.json | head -20

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 89


🏁 Script executed:

rg "Civitai|Hugging Face" src/locales/en/main.json

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 428


🏁 Script executed:

cat src/platform/assets/components/modelInfo/ModelInfoPanel.vue | sed -n '35,55p'

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 776


Localize source names using i18n.

These strings are user-facing. Return a stable source key (e.g., 'civitai' | 'huggingface' | 'source') and translate at the call site via vue-i18n. Translation keys already exist in src/locales/en/main.json (providerCivitai, providerHuggingFace); reuse or add new entries as needed.

🤖 Prompt for AI Agents
In `@src/platform/assets/utils/assetMetadataUtils.ts` around lines 125 - 128,
getSourceName currently returns localized display strings; change it to return
stable source keys (e.g., 'civitai' | 'huggingface' | 'source') instead so
translation happens at the call site via vue-i18n. Update the function
getSourceName(url: string) to return 'civitai' when url.includes('civitai.com'),
'huggingface' when url.includes('huggingface.co'), and 'source' otherwise;
consumers should map those keys to translations (reuse existing locale keys like
providerCivitai and providerHuggingFace in src/locales/en/main.json or add new
entries as needed). Ensure no direct i18n calls are added inside
assetMetadataUtils.ts and adjust any call sites to translate the returned key
via vue-i18n.

Copy link
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

🤖 Fix all issues with AI agents
In `@src/platform/assets/components/modelInfo/ModelInfoPanel.vue`:
- Around line 236-241: The watcher currently only watches asset.user_metadata so
pendingUpdates can persist across asset switches when user_metadata is
undefined; update the watch to observe both asset.id and asset.user_metadata
(e.g., watch(() => [asset.id, asset.user_metadata], ...)) and keep the reset
logic setting pendingUpdates.value = {} so it runs on any asset change; also
remove the non-existent debouncedFlushMetadata.cancel?.() call (it’s not part of
`@vueuse/core` useDebounceFn) to avoid relying on an unsupported cancellation API.
- Around line 264-271: The removal filter in debouncedSaveModelType incorrectly
compares full tag strings to currentModelType when tags may be namespaced;
update the filter over asset.tags (used before calling
assetsStore.updateAssetTags) to compare the tag's last segment (e.g.,
tag.split('/').pop()) or call getAssetModelType on each tag so you remove tags
whose extracted model type equals currentModelType, then concat newModelType and
call assetsStore.updateAssetTags(asset.id, newTags, cacheKey).
♻️ Duplicate comments (4)
src/platform/assets/utils/assetMetadataUtils.ts (3)

47-56: Filter/trim empty base model values.

This avoids blank badges/options when metadata contains empty strings.

♻️ Proposed tweak
-  if (Array.isArray(baseModel)) {
-    return baseModel.filter((m): m is string => typeof m === 'string')
-  }
-  if (typeof baseModel === 'string' && baseModel) {
-    return [baseModel]
-  }
+  if (Array.isArray(baseModel)) {
+    return baseModel.filter(
+      (m): m is string => typeof m === 'string' && m.trim().length > 0
+    )
+  }
+  if (typeof baseModel === 'string' && baseModel.trim().length > 0) {
+    return [baseModel.trim()]
+  }

125-128: Return a stable source key and localize at the call site.

User-facing strings shouldn’t originate in TS utilities. Return a key (e.g., civitai | huggingface | source) and translate where you render.

As per coding guidelines, localize user-facing strings via vue-i18n.


136-139: Avoid empty/whitespace model types.

Trailing slashes or whitespace can return empty strings; normalize and guard.

♻️ Suggested normalization
-export function getAssetModelType(asset: AssetItem): string | null {
-  const typeTag = asset.tags?.find((tag) => tag && tag !== 'models')
-  if (!typeTag) return null
-  return typeTag.includes('/') ? (typeTag.split('/').pop() ?? null) : typeTag
-}
+export function getAssetModelType(asset: AssetItem): string | null {
+  const typeTag = asset.tags?.find((tag) => tag?.trim() && tag.trim() !== 'models')
+  if (!typeTag) return null
+  const normalized = typeTag.trim()
+  const leaf = normalized.includes('/') ? normalized.split('/').pop() : normalized
+  return leaf?.trim().length ? leaf.trim() : null
+}
src/platform/assets/components/modelInfo/ModelInfoPanel.vue (1)

141-148: Use distinct labels for source vs. user descriptions.

The two fields currently share the same label key and will render identical headings.

As per coding guidelines, add distinct vue-i18n keys in src/locales/en/main.json.

Comment on lines +236 to +241
watch(
() => asset.user_metadata,
() => {
pendingUpdates.value = {}
}
)
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n src/platform/assets/components/modelInfo/ModelInfoPanel.vue | sed -n '230,250p'

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 748


🏁 Script executed:

wc -l src/platform/assets/components/modelInfo/ModelInfoPanel.vue

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 133


🏁 Script executed:

cat -n src/platform/assets/components/modelInfo/ModelInfoPanel.vue | head -100

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 4140


🏁 Script executed:

rg "debouncedFlushMetadata" src/platform/assets/components/modelInfo/ModelInfoPanel.vue -B 2 -A 2

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 349


🏁 Script executed:

rg "pendingUpdates" src/platform/assets/components/modelInfo/ModelInfoPanel.vue -B 1 -A 1

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 1069


🏁 Script executed:

rg "useDebounceFn" src/platform/assets/components/modelInfo/ModelInfoPanel.vue

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 240


🏁 Script executed:

rg "cancel" src/platform/assets/components/modelInfo/ModelInfoPanel.vue -B 2 -A 2

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 251


🏁 Script executed:

rg "useDebounceFn" src/ -A 5 | rg "cancel" -B 5 -A 2

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 52


🌐 Web query:

@vueuse/core useDebounceFn cancel method documentation

💡 Result:

Short answer: The official @vueuse/core docs for useDebounceFn do not document a public .cancel() method — the composable returns a debounced function (a PromisifyFn) and cancellations are internal; to observe cancellation you can pass the option rejectOnCancel: true so the returned promise rejects when a pending call is cancelled. [1]

If you need an explicit cancel/flush API, some community wrappers / alternative implementations (e.g. vue-hooks-plus or other utilities) expose run/cancel/flush methods; those are not part of the official @vueuse/core useDebounceFn API. [2][3]

Sources:

  • Official VueUse useDebounceFn docs (shows returned debounced function and rejectOnCancel option). [1]
  • vue-hooks-plus documentation (example of a debounce API that exposes cancel/flush). [2]
  • W3cub / mirrored docs showing same official behavior. [3]

🏁 Script executed:

rg "@vueuse/core" package.json

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 101


🏁 Script executed:

rg "useDebounceFn" src/ --type typescript -B 3 -A 8 | head -80

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 104


🏁 Script executed:

rg "debouncedFlushMetadata" src/platform/assets/components/modelInfo/ModelInfoPanel.vue -B 5 -A 15

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 963


Watch asset changes to prevent stale pendingUpdates writes across asset switches.

When asset.user_metadata remains undefined during an asset switch, the watcher doesn't trigger, leaving pendingUpdates populated with data from the previous asset. The next debounced flush would then update the wrong asset. Watch asset.id alongside asset.user_metadata to ensure resets occur on any asset change.

Note: The proposed debouncedFlushMetadata.cancel?.() in the diff is not part of the official @vueuse/core useDebounceFn API and will not cancel the debounced call. Remove that line from the fix.

🤖 Prompt for AI Agents
In `@src/platform/assets/components/modelInfo/ModelInfoPanel.vue` around lines 236
- 241, The watcher currently only watches asset.user_metadata so pendingUpdates
can persist across asset switches when user_metadata is undefined; update the
watch to observe both asset.id and asset.user_metadata (e.g., watch(() =>
[asset.id, asset.user_metadata], ...)) and keep the reset logic setting
pendingUpdates.value = {} so it runs on any asset change; also remove the
non-existent debouncedFlushMetadata.cancel?.() call (it’s not part of
`@vueuse/core` useDebounceFn) to avoid relying on an unsupported cancellation API.

Comment on lines +264 to +271
const debouncedSaveModelType = useDebounceFn((newModelType: string) => {
if (isImmutable.value) return
const currentModelType = getAssetModelType(asset)
if (currentModelType === newModelType) return
const newTags = asset.tags
.filter((tag) => tag !== currentModelType)
.concat(newModelType)
assetsStore.updateAssetTags(asset.id, newTags, cacheKey)
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Find the asset type definition to understand the tags property
fd -e ts -e d.ts | xargs grep -l "tags.*:" | grep -E "(types|models|asset)" | head -20

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 1008


🏁 Script executed:

# Look at the ModelInfoPanel.vue file to understand context
cat -n src/platform/assets/components/modelInfo/ModelInfoPanel.vue | sed -n '250,280p'

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 1238


🏁 Script executed:

# Find getAssetModelType function
rg -n "getAssetModelType" -A 5 --type ts --type vue

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 97


🏁 Script executed:

# Find updateAssetTags function
rg -n "updateAssetTags" -A 10 --type ts

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 2573


🏁 Script executed:

# Search for asset type definitions
rg -n "interface.*[Aa]sset|type.*[Aa]sset" --type ts | grep -v node_modules | head -20

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 2089


🏁 Script executed:

# Check the AssetItem definition in assetSchema
cat -n src/platform/assets/schemas/assetSchema.ts | head -100

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 4103


🏁 Script executed:

# Find getAssetModelType function
rg -n "getAssetModelType" -A 10 -B 2

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 7784


🏁 Script executed:

# Check how tags are used elsewhere in the codebase
rg -n "asset\.tags" -B 2 -A 2

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 6400


🏁 Script executed:

# Check the complete getAssetModelType function and verify tag format
cat -n src/platform/assets/utils/assetMetadataUtils.ts | sed -n '130,145p'

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 782


🏁 Script executed:

# Look at test cases for getAssetModelType to understand expected behavior
cat -n src/platform/assets/utils/assetMetadataUtils.test.ts | sed -n '223,245p'

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 905


🏁 Script executed:

# Check if asset is guaranteed to be validated through Zod schema
rg -n "AssetItem|assetItemSchema" -A 3 -B 3 | grep -A 5 -B 5 "ModelInfoPanel"

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 52


🏁 Script executed:

# Check the prop definition for asset in ModelInfoPanel.vue
cat -n src/platform/assets/components/modelInfo/ModelInfoPanel.vue | sed -n '1,230p' | grep -A 10 "defineProps"

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 408


🏁 Script executed:

# Look for other usages of asset.tags without guards to understand the pattern
rg -n "\.tags\." -B 2 -A 2 | head -60

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 4700


🏁 Script executed:

# Check what happens when updateAssetTags is called - does it accept empty arrays or require specific format?
cat -n src/stores/assetsStore.ts | sed -n '473,490p'

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 643


🏁 Script executed:

# Find AssetDisplayItem type definition
rg -n "AssetDisplayItem" -B 2 -A 5 | head -40

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 3403


🏁 Script executed:

# Check if AssetDisplayItem extends AssetItem or has its own definition
rg -n "type AssetDisplayItem|interface AssetDisplayItem" -A 10

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 857


🏁 Script executed:

# Verify the tag removal logic with examples from tests
cat -n src/platform/assets/utils/assetMetadataUtils.test.ts | sed -n '223,245p'

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 905


🏁 Script executed:

# Check if there are any tests for the debouncedSaveModelType function
rg -n "debouncedSaveModelType|handleModelTypeChange" -B 3 -A 10

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 2019


🏁 Script executed:

# Look for any tag normalization utilities
rg -n "normaliz|canonicali" --type ts --type vue

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 97


🏁 Script executed:

# Look at how tags are structured in fixtures/tests to understand the format
cat -n src/platform/assets/fixtures/ui-mock-assets.ts | head -80

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 4012


🏁 Script executed:

# Check if there are any integration tests for the ModelInfoPanel tag update logic
fd -e test.ts -o -e test.tsx | xargs grep -l "ModelInfoPanel" | head -5

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 186


🏁 Script executed:

# Check how the new model type is being constructed when it's added back to tags
cat -n src/platform/assets/components/modelInfo/ModelInfoPanel.vue | sed -n '287,297p'

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 493


🏁 Script executed:

# Look at the test fixtures more carefully to see actual tag values
cat -n src/platform/assets/fixtures/ui-mock-assets.ts | sed -n '75,150p'

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 3120


🏁 Script executed:

# Final verification: check if tags are actually used with path format in real code
rg -n "models/" src/platform/assets --type ts | grep -i tag | head -20

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 315


Guard tag removal logic to handle namespaced tags correctly.

Tags can be namespaced with forward slashes (e.g., models/loras). When removing the old model type tag, the current filter compares the full tag string against the extracted type name, causing old namespaced tags to not be removed. The filter should extract the last segment from each tag before comparison, or better yet, use getAssetModelType to identify which tag to remove.

const debouncedSaveModelType = useDebounceFn((newModelType: string) => {
  if (isImmutable.value) return
  const currentModelType = getAssetModelType(asset)
  if (currentModelType === newModelType) return
-  const newTags = asset.tags
-    .filter((tag) => tag !== currentModelType)
+  const newTags = asset.tags
+    .filter((tag) => tag === 'models' || !tag.includes(currentModelType ?? ''))
     .concat(newModelType)
  assetsStore.updateAssetTags(asset.id, newTags, cacheKey)
}, 500)
🤖 Prompt for AI Agents
In `@src/platform/assets/components/modelInfo/ModelInfoPanel.vue` around lines 264
- 271, The removal filter in debouncedSaveModelType incorrectly compares full
tag strings to currentModelType when tags may be namespaced; update the filter
over asset.tags (used before calling assetsStore.updateAssetTags) to compare the
tag's last segment (e.g., tag.split('/').pop()) or call getAssetModelType on
each tag so you remove tags whose extracted model type equals currentModelType,
then concat newModelType and call assetsStore.updateAssetTags(asset.id, newTags,
cacheKey).

DrJKL and others added 2 commits January 21, 2026 18:50
Add hasAssetKey method to assetsStore to detect initialized keys

Change useAssetWidgetData watch to check loading state and key existence instead of array length

Amp-Thread-ID: https://ampcode.com/threads/T-019be386-8ca3-7206-afbd-dc75c597a50d
Co-authored-by: Amp <[email protected]>
DrJKL and others added 2 commits January 21, 2026 19:23
- Use composition API useI18n instead of options API \$t

- Update test to check Civitai icon instead of text
Copy link
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

🤖 Fix all issues with AI agents
In `@src/platform/assets/components/AssetBrowserModal.vue`:
- Around line 60-72: The click handler on the AssetGrid clears focusedAsset for
any inner click and should only run when the grid backdrop itself is clicked;
update the AssetGrid's click listener to use the `.self` modifier (change
`@click`="focusedAsset = null" to use the self modifier) so clicks that originate
from asset cards or child elements (which emit `@asset-focus/`@asset-select via
handleAssetFocus/handleAssetSelectAndEmit) do not bubble and immediately clear
the focus.

Comment on lines 60 to 72
<template #content>
<AssetGrid
:assets="filteredAssets"
:loading="isLoading"
:focused-asset-id="focusedAsset?.id"
:empty-message
@asset-focus="handleAssetFocus"
@asset-select="handleAssetSelectAndEmit"
@asset-deleted="refreshAssets"
@asset-show-info="handleShowInfo"
@click="focusedAsset = null"
/>
</template>
Copy link
Contributor

@coderabbitai coderabbitai bot Jan 22, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Missing .self modifier on click handler.

The @click="focusedAsset = null" on AssetGrid will trigger for any click inside the grid, including clicks on asset cards. This causes the focused asset to be cleared immediately after being set via @asset-focus. This is the same event bubbling issue that was fixed on lines 28 and 56 with .self.

🐛 Proposed fix
       <AssetGrid
         :assets="filteredAssets"
         :loading="isLoading"
         :focused-asset-id="focusedAsset?.id"
         :empty-message
         `@asset-focus`="handleAssetFocus"
         `@asset-select`="handleAssetSelectAndEmit"
         `@asset-deleted`="refreshAssets"
         `@asset-show-info`="handleShowInfo"
-        `@click`="focusedAsset = null"
+        `@click.self`="focusedAsset = null"
       />
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<template #content>
<AssetGrid
:assets="filteredAssets"
:loading="isLoading"
:focused-asset-id="focusedAsset?.id"
:empty-message
@asset-focus="handleAssetFocus"
@asset-select="handleAssetSelectAndEmit"
@asset-deleted="refreshAssets"
@asset-show-info="handleShowInfo"
@click="focusedAsset = null"
/>
</template>
<template `#content`>
<AssetGrid
:assets="filteredAssets"
:loading="isLoading"
:focused-asset-id="focusedAsset?.id"
:empty-message
`@asset-focus`="handleAssetFocus"
`@asset-select`="handleAssetSelectAndEmit"
`@asset-deleted`="refreshAssets"
`@asset-show-info`="handleShowInfo"
`@click.self`="focusedAsset = null"
/>
</template>
🤖 Prompt for AI Agents
In `@src/platform/assets/components/AssetBrowserModal.vue` around lines 60 - 72,
The click handler on the AssetGrid clears focusedAsset for any inner click and
should only run when the grid backdrop itself is clicked; update the AssetGrid's
click listener to use the `.self` modifier (change `@click`="focusedAsset = null"
to use the self modifier) so clicks that originate from asset cards or child
elements (which emit `@asset-focus/`@asset-select via
handleAssetFocus/handleAssetSelectAndEmit) do not bubble and immediately clear
the focus.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It does not. Clicking on cards works just fine.

Copy link
Contributor

Choose a reason for hiding this comment

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

@DrJKL, thank you for the clarification! You're right—I apologize for the incorrect concern. The Vue custom events (@asset-focus, @asset-select, etc.) emitted from AssetGrid don't bubble like native DOM events would, and the implementation handles click interactions correctly without the .self modifier causing any issues.


🧠 Learnings used
Learnt from: simula-r
Repo: Comfy-Org/ComfyUI_frontend PR: 7252
File: src/renderer/extensions/vueNodes/components/ImagePreview.vue:151-158
Timestamp: 2025-12-11T03:55:57.926Z
Learning: In src/renderer/extensions/vueNodes/components/ImagePreview.vue and LGraphNode.vue, keyboard navigation for image galleries should respond to node-level focus (via keyEvent injection from LGraphNode), not require focus within the image preview wrapper itself. This allows users to navigate the gallery with arrow keys immediately when the node is focused/selected.

Copy link
Member

@viva-jinyi viva-jinyi left a comment

Choose a reason for hiding this comment

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

LGTM

DrJKL added a commit that referenced this pull request Jan 22, 2026
## Summary

Add session download tracking to track which assets were downloaded
during the current session. This enables UI features like:
- Badge count on "Imported" nav showing newly downloaded assets
- Visual indicator on asset cards for recently downloaded items

## Changes

- Add `acknowledged` flag to `AssetDownload` interface
- Add `unacknowledgedDownloads` computed for filtering
- Add `sessionDownloadCount` computed for badge display
- Add `isDownloadedThisSession(identifier)` to check individual assets
- Add `acknowledgeDownload(identifier)` to mark assets as seen

## Testing

- 6 new unit tests covering all session tracking functionality
- Run: `pnpm test:unit -- src/stores/assetDownloadStore.test.ts`

## Related

- Part of Asset Browser improvements (#8090)

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8213-feat-add-session-download-tracking-to-assetDownloadStore-2ef6d73d365081538045e8544d26bafa)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Amp <[email protected]>
@DrJKL DrJKL merged commit 93e7a4f into main Jan 22, 2026
28 checks passed
@DrJKL DrJKL deleted the drjkl/others-call-it-a-garage-sale branch January 22, 2026 03:43
@DrJKL DrJKL added needs-backport Fix/change that needs to be cherry-picked to the current feature freeze branch cloud/1.37 Backport PRs for cloud 1.37 labels Jan 22, 2026
@github-actions
Copy link

⚠️ Backport to cloud/1.37 failed

Reason: Merge conflicts detected during cherry-pick of 93e7a4f

📄 Conflicting files
src/components/rightSidePanel/layout/PropertiesAccordionItem.vue
🤖 Prompt for AI Agents
Backport PR #8090 (https://github.com/Comfy-Org/ComfyUI_frontend/pull/8090) to cloud/1.37.
Cherry-pick merge commit 93e7a4f9f9743308c8839423240316f29f6f20b4 onto new branch
backport-8090-to-cloud-1.37 from origin/cloud/1.37.
Resolve conflicts in: src/components/rightSidePanel/layout/PropertiesAccordionItem.vue .
For test snapshots (browser_tests/**/*-snapshots/), accept PR version if
changed in original PR, else keep target. For package.json versions, keep
target branch. For pnpm-lock.yaml, regenerate with pnpm install.
Ask user for non-obvious conflicts.
Create PR titled "[backport cloud/1.37] <original title>" with label "backport".
See .github/workflows/pr-backport.yaml for workflow details.

cc @DrJKL

1 similar comment
@github-actions
Copy link

⚠️ Backport to cloud/1.37 failed

Reason: Merge conflicts detected during cherry-pick of 93e7a4f

📄 Conflicting files
src/components/rightSidePanel/layout/PropertiesAccordionItem.vue
🤖 Prompt for AI Agents
Backport PR #8090 (https://github.com/Comfy-Org/ComfyUI_frontend/pull/8090) to cloud/1.37.
Cherry-pick merge commit 93e7a4f9f9743308c8839423240316f29f6f20b4 onto new branch
backport-8090-to-cloud-1.37 from origin/cloud/1.37.
Resolve conflicts in: src/components/rightSidePanel/layout/PropertiesAccordionItem.vue .
For test snapshots (browser_tests/**/*-snapshots/), accept PR version if
changed in original PR, else keep target. For package.json versions, keep
target branch. For pnpm-lock.yaml, regenerate with pnpm install.
Ask user for non-obvious conflicts.
Create PR titled "[backport cloud/1.37] <original title>" with label "backport".
See .github/workflows/pr-backport.yaml for workflow details.

cc @DrJKL

DrJKL added a commit that referenced this pull request Jan 22, 2026
Cherry-picked from main commit 93e7a4f

Adapted PropertiesAccordionItem.vue: removed TransitionCollapse dependency (not in cloud/1.37)

Amp-Thread-ID: https://ampcode.com/threads/T-019be3e5-7a8a-7048-bf94-8474134f5467
Co-authored-by: Amp <[email protected]>
DrJKL added a commit that referenced this pull request Jan 22, 2026
…ser right panel (#8241)

## Summary

Backport of #8090 to cloud/1.37 branch.

Cherry-picked from main commit 93e7a4f.

## Conflict Resolutions

- `src/components/rightSidePanel/layout/PropertiesAccordionItem.vue`:
Took PR version but removed `TransitionCollapse` dependency (not present
in cloud/1.37). The transition animation is omitted; collapse/expand
works without animation.

## Original PR Description

Adds an editable Model Info Panel to show and modify asset details in
the asset browser.

### Changes

- Add `ModelInfoPanel` component with editable display name,
description, model type, base models, and tags
- Add `updateAssetMetadata` action in `assetsStore` with optimistic
cache updates
- Add shadcn-vue `Select` components with design system styling
- Add utility functions in `assetMetadataUtils` for extracting model
metadata
- Convert `BaseModalLayout` right panel state to `defineModel` pattern
- Add slide-in animation and collapse button for right panel
- Add `class` prop to `PropertiesAccordionItem` for custom styling
- Fix keyboard handling: Escape in TagsInput/TextArea doesn't close
parent modal

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8241-backport-cloud-1-37-feat-assets-add-ModelInfoPanel-for-asset-browser-right-panel-2f06d73d365081ffb57dca42a82349b6)
by [Unito](https://www.unito.io)

Co-authored-by: Amp <[email protected]>
godwiniheuwa pushed a commit to godwiniheuwa/ComfyUI_frontend that referenced this pull request Jan 22, 2026
…8213)

## Summary

Add session download tracking to track which assets were downloaded
during the current session. This enables UI features like:
- Badge count on "Imported" nav showing newly downloaded assets
- Visual indicator on asset cards for recently downloaded items

## Changes

- Add `acknowledged` flag to `AssetDownload` interface
- Add `unacknowledgedDownloads` computed for filtering
- Add `sessionDownloadCount` computed for badge display
- Add `isDownloadedThisSession(identifier)` to check individual assets
- Add `acknowledgeDownload(identifier)` to mark assets as seen

## Testing

- 6 new unit tests covering all session tracking functionality
- Run: `pnpm test:unit -- src/stores/assetDownloadStore.test.ts`

## Related

- Part of Asset Browser improvements (Comfy-Org#8090)

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8213-feat-add-session-download-tracking-to-assetDownloadStore-2ef6d73d365081538045e8544d26bafa)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Amp <[email protected]>
godwiniheuwa pushed a commit to godwiniheuwa/ComfyUI_frontend that referenced this pull request Jan 22, 2026
…-Org#8090)

## Summary

Adds an editable Model Info Panel to show and modify asset details in
the asset browser.

## Changes

- Add `ModelInfoPanel` component with editable display name,
description, model type, base models, and tags
- Add `updateAssetMetadata` action in `assetsStore` with optimistic
cache updates
- Add shadcn-vue `Select` components with design system styling
- Add utility functions in `assetMetadataUtils` for extracting model
metadata
- Convert `BaseModalLayout` right panel state to `defineModel` pattern
- Add slide-in animation and collapse button for right panel
- Add `class` prop to `PropertiesAccordionItem` for custom styling
- Fix keyboard handling: Escape in TagsInput/TextArea doesn't close
parent modal

## Testing

- Unit tests for `ModelInfoPanel` component
- Unit tests for `assetMetadataUtils` functions

---------

Co-authored-by: Amp <[email protected]>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: GitHub Action <[email protected]>
This was referenced Jan 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cloud/1.37 Backport PRs for cloud 1.37 Design Used to request Product feedback on design decisions needs-backport Fix/change that needs to be cherry-picked to the current feature freeze branch preview size:XXL This PR changes 1000+ lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants