Skip to content

Draft: Rh/hub#8567

Closed
robinjhuang wants to merge 22 commits intomainfrom
rh/hub
Closed

Draft: Rh/hub#8567
robinjhuang wants to merge 22 commits intomainfrom
rh/hub

Conversation

@robinjhuang
Copy link
Copy Markdown
Member

@robinjhuang robinjhuang commented Feb 3, 2026

Summary

Changes

  • What:
  • Breaking:
  • Dependencies:

Review Focus

Screenshots (if applicable)

┆Issue is synchronized with this Notion page by Unito

@robinjhuang robinjhuang requested review from a team and pythongosssss as code owners February 3, 2026 03:06
@dosubot dosubot bot added the size:XXL This PR changes 1000+ lines, ignoring generated files. label Feb 3, 2026
@robinjhuang robinjhuang added preview and removed size:XXL This PR changes 1000+ lines, ignoring generated files. labels Feb 3, 2026
@github-actions
Copy link
Copy Markdown

github-actions bot commented Feb 3, 2026

🎨 Storybook Build Status

Build completed successfully!

⏰ Completed at: 02/04/2026, 01:45:29 AM UTC

🔗 Links


🎉 Your Storybook is ready for review!

@github-actions
Copy link
Copy Markdown

github-actions bot commented Feb 3, 2026

🎭 Playwright Tests: ❌ Failed

Results: 458 passed, 23 failed, 2 flaky, 8 skipped (Total: 491)

❌ Failed Tests

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

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Feb 3, 2026

📝 Walkthrough

Walkthrough

This pull request introduces a comprehensive Discover feature enabling users to search, browse, and share workflow templates. It adds new components for template discovery, workflow preview and detail views, author profiles, and sharing/publishing dialogs, along with supporting stores, utilities, routing, and internationalization.

Changes

Cohort / File(s) Summary
Discover Components
src/components/discover/DiscoverView.vue, src/components/discover/AuthorProfileView.vue, src/components/discover/WorkflowDetailView.vue, src/components/discover/WorkflowPreviewCanvas.vue, src/views/AuthorProfileView.vue
Introduces discover UI with faceted search, workflow gallery, author profiles, and workflow detail view with LiteGraph canvas preview.
Share & Publish UI
src/components/actionbar/SharePanel.vue, src/components/actionbar/ShareButton.vue, src/components/actionbar/PublishToHubDialogContent.vue
Adds sharing functionality with invite/public URL tabs and publish-to-hub dialog with thumbnail, metadata, and tag management.
Home & Navigation
src/components/home/HomePanel.vue, src/components/topbar/WorkflowTabs.vue
Implements Home panel with recents and discover tabs and home button toggle in workflow tabs.
Full-Page Overlay Layout
src/components/LiteGraphCanvasSplitterOverlay.vue, src/components/graph/GraphCanvas.vue
Introduces full-page overlay logic and conditional rendering for home/share panels with visibility controls.
State Management
src/stores/workspace/homePanelStore.ts, src/stores/workspace/sharePanelStore.ts, src/stores/workspace/sidebarTabStore.ts
New Pinia stores for managing home and share panel state with localStorage persistence; updates sidebar tab store with full-page tab detection.
Search & Types
src/composables/discover/useWorkflowTemplateSearch.ts, src/types/discoverTypes.ts, src/utils/authorProfileUtil.ts
Algolia-based template search composable, type definitions for workflows and search results, and author profile utilities including slug mapping.
Integration & Configuration
src/components/actionbar/ComfyActionbar.vue, src/components/sidebar/SideToolbar.vue, src/components/sidebar/tabs/WorkflowsSidebarTab.vue, knip.config.ts, src/router.ts, src/locales/en/main.json
Adds ShareButton to actionbar, fixes SideToolbar import path, wires template browser in workflows tab, updates ignore list, adds author profile route, and introduces comprehensive i18n strings for discover feature.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch rh/hub

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

@github-actions
Copy link
Copy Markdown

github-actions bot commented Feb 3, 2026

Bundle Size Report

Summary

  • Raw size: 20.3 MB baseline 20.2 MB — 🔴 +86.3 kB
  • Gzip: 4.34 MB baseline 4.32 MB — 🔴 +18.5 kB
  • Brotli: 3.35 MB baseline 3.34 MB — 🔴 +14.2 kB
  • Bundles: 235 current • 233 baseline • 167 added / 165 removed

Category Glance
Data & Services 🔴 +65.8 kB (2.16 MB) · Views & Navigation 🔴 +37.5 kB (106 kB) · Graph Workspace 🟢 -13.3 kB (824 kB) · Other 🟢 -7.87 kB (7.07 MB) · UI Components 🔴 +3.73 kB (41.5 kB) · App Entry Points 🔴 +394 B (22.9 kB) · + 5 more

Per-category breakdown
App Entry Points — 22.9 kB (baseline 22.5 kB) • 🔴 +394 B

Main entry bundles and manifests

File Before After Δ Raw Δ Gzip Δ Brotli
assets/index-pn2GBAsB.js (new) 22.9 kB 🔴 +22.9 kB 🔴 +7.26 kB 🔴 +6.34 kB
assets/index-CShXzA9S.js (removed) 22.5 kB 🟢 -22.5 kB 🟢 -7.18 kB 🟢 -6.25 kB

Status: 1 added / 1 removed

Graph Workspace — 824 kB (baseline 838 kB) • 🟢 -13.3 kB

Graph editor runtime, canvas, workflow orchestration

File Before After Δ Raw Δ Gzip Δ Brotli
assets/GraphView-8ywluF5A.js (removed) 838 kB 🟢 -838 kB 🟢 -180 kB 🟢 -137 kB
assets/GraphView-CjN4_WLK.js (new) 824 kB 🔴 +824 kB 🔴 +176 kB 🔴 +134 kB

Status: 1 added / 1 removed

Views & Navigation — 106 kB (baseline 69 kB) • 🔴 +37.5 kB

Top-level views, pages, and routed surfaces

File Before After Δ Raw Δ Gzip Δ Brotli
assets/AuthorProfileView-Q7e_9eA4.js (new) 18.9 kB 🔴 +18.9 kB 🔴 +4.66 kB 🔴 +4.1 kB
assets/WorkflowDetailView-DOqrY1JU.js (new) 18.6 kB 🔴 +18.6 kB 🔴 +4.84 kB 🔴 +4.21 kB
assets/CloudSurveyView-DFcsr-Ze.js (new) 15.8 kB 🔴 +15.8 kB 🔴 +3.45 kB 🔴 +2.95 kB
assets/CloudSurveyView-3x7cltYB.js (removed) 15.8 kB 🟢 -15.8 kB 🟢 -3.46 kB 🟢 -2.93 kB
assets/CloudLoginView-STqPgWGl.js (new) 10 kB 🔴 +10 kB 🔴 +2.93 kB 🔴 +2.58 kB
assets/CloudLoginView-C03EVaqd.js (removed) 10 kB 🟢 -10 kB 🟢 -2.93 kB 🟢 -2.57 kB
assets/UserCheckView-B6b884AH.js (new) 8.39 kB 🔴 +8.39 kB 🔴 +2.22 kB 🔴 +1.94 kB
assets/UserCheckView-YVRgi8aX.js (removed) 8.39 kB 🟢 -8.39 kB 🟢 -2.23 kB 🟢 -1.94 kB
assets/CloudSignupView-CYQX3x_J.js (new) 7.38 kB 🔴 +7.38 kB 🔴 +2.31 kB 🔴 +2.02 kB
assets/CloudSignupView-irb1bIb2.js (removed) 7.38 kB 🟢 -7.38 kB 🟢 -2.31 kB 🟢 -2.03 kB
assets/CloudLayoutView-BV8pvW19.js (new) 6.43 kB 🔴 +6.43 kB 🔴 +2.11 kB 🔴 +1.83 kB
assets/CloudLayoutView-JE74sr0r.js (removed) 6.43 kB 🟢 -6.43 kB 🟢 -2.12 kB 🟢 -1.83 kB
assets/CloudForgotPasswordView-mu1fPLKd.js (new) 5.56 kB 🔴 +5.56 kB 🔴 +1.93 kB 🔴 +1.73 kB
assets/CloudForgotPasswordView-DE48jpWN.js (removed) 5.56 kB 🟢 -5.56 kB 🟢 -1.94 kB 🟢 -1.7 kB
assets/CloudAuthTimeoutView-Db43wT-G.js (new) 4.91 kB 🔴 +4.91 kB 🔴 +1.77 kB 🔴 +1.55 kB
assets/CloudAuthTimeoutView-ClXxxBs_.js (removed) 4.91 kB 🟢 -4.91 kB 🟢 -1.77 kB 🟢 -1.55 kB
assets/CloudSubscriptionRedirectView-DJd092tX.js (new) 4.71 kB 🔴 +4.71 kB 🔴 +1.77 kB 🔴 +1.57 kB
assets/CloudSubscriptionRedirectView-BjDyMoHp.js (removed) 4.71 kB 🟢 -4.71 kB 🟢 -1.77 kB 🟢 -1.57 kB
assets/UserSelectView-DvO4DYFh.js (new) 4.49 kB 🔴 +4.49 kB 🔴 +1.63 kB 🔴 +1.47 kB
assets/UserSelectView-6eEjpNTo.js (removed) 4.49 kB 🟢 -4.49 kB 🟢 -1.63 kB 🟢 -1.46 kB
assets/CloudSorryContactSupportView-CoYGAV8P.js (new) 1.02 kB 🔴 +1.02 kB 🔴 +539 B 🔴 +469 B
assets/CloudSorryContactSupportView-BPXBOTez.js (removed) 1.02 kB 🟢 -1.02 kB 🟢 -539 B 🟢 -464 B
assets/layout-CrkklrJS.js (new) 296 B 🔴 +296 B 🔴 +223 B 🔴 +209 B
assets/layout-ncetNFDR.js (removed) 296 B 🟢 -296 B 🟢 -222 B 🟢 -186 B

Status: 13 added / 11 removed

Panels & Settings — 410 kB (baseline 410 kB) • 🔴 +9 B

Configuration panels, inspectors, and settings screens

File Before After Δ Raw Δ Gzip Δ Brotli
assets/WorkspacePanel-DyaUkM4Z.js (new) 26.9 kB 🔴 +26.9 kB 🔴 +5.65 kB 🔴 +4.98 kB
assets/WorkspacePanel-BFNV8dPE.js (removed) 26.9 kB 🟢 -26.9 kB 🟢 -5.66 kB 🟢 -4.96 kB
assets/SecretsPanel-Dty-IuOc.js (new) 21.5 kB 🔴 +21.5 kB 🔴 +5.27 kB 🔴 +4.62 kB
assets/SecretsPanel-ClJUgwj1.js (removed) 21.5 kB 🟢 -21.5 kB 🟢 -5.27 kB 🟢 -4.62 kB
assets/LegacyCreditsPanel-Dcu1bTxV.js (new) 20.8 kB 🔴 +20.8 kB 🔴 +5.61 kB 🔴 +4.93 kB
assets/LegacyCreditsPanel-BppVZakZ.js (removed) 20.8 kB 🟢 -20.8 kB 🟢 -5.62 kB 🟢 -4.93 kB
assets/SubscriptionPanel-U1FYq9_5.js (new) 18.8 kB 🔴 +18.8 kB 🔴 +4.75 kB 🔴 +4.19 kB
assets/SubscriptionPanel-CZGVKjC6.js (removed) 18.8 kB 🟢 -18.8 kB 🟢 -4.76 kB 🟢 -4.21 kB
assets/KeybindingPanel-xv4ISE4B.js (new) 12.6 kB 🔴 +12.6 kB 🔴 +3.62 kB 🔴 +3.21 kB
assets/KeybindingPanel-DEn-LNMa.js (removed) 12.6 kB 🟢 -12.6 kB 🟢 -3.63 kB 🟢 -3.22 kB
assets/ExtensionPanel-CTsSt7vI.js (new) 9.5 kB 🔴 +9.5 kB 🔴 +2.68 kB 🔴 +2.39 kB
assets/ExtensionPanel-DIyd5Jqb.js (removed) 9.49 kB 🟢 -9.49 kB 🟢 -2.69 kB 🟢 -2.38 kB
assets/AboutPanel-CZurquSw.js (new) 8.62 kB 🔴 +8.62 kB 🔴 +2.45 kB 🔴 +2.21 kB
assets/AboutPanel-2at5BZCE.js (removed) 8.62 kB 🟢 -8.62 kB 🟢 -2.45 kB 🟢 -2.21 kB
assets/ServerConfigPanel-C7Sayy0F.js (new) 6.64 kB 🔴 +6.64 kB 🔴 +2.15 kB 🔴 +1.93 kB
assets/ServerConfigPanel-CdhIkBnZ.js (removed) 6.64 kB 🟢 -6.64 kB 🟢 -2.16 kB 🟢 -1.93 kB
assets/UserPanel-BCqqN9gJ.js (new) 6.28 kB 🔴 +6.28 kB 🔴 +2.02 kB 🔴 +1.77 kB
assets/UserPanel-ISolmwIZ.js (removed) 6.28 kB 🟢 -6.28 kB 🟢 -2.02 kB 🟢 -1.78 kB
assets/cloudRemoteConfig-BWajHCwc.js (new) 1.44 kB 🔴 +1.44 kB 🔴 +698 B 🔴 +602 B
assets/cloudRemoteConfig-CecEAIzm.js (removed) 1.44 kB 🟢 -1.44 kB 🟢 -700 B 🟢 -608 B
assets/refreshRemoteConfig-5cNBUDs7.js (removed) 1.13 kB 🟢 -1.13 kB 🟢 -514 B 🟢 -449 B
assets/refreshRemoteConfig-CIgukwrI.js (new) 1.13 kB 🔴 +1.13 kB 🔴 +510 B 🔴 +445 B
assets/remoteConfig-CUBB_j_I.js (removed) 581 B 🟢 -581 B 🟢 -330 B 🟢 -290 B
assets/remoteConfig-UVfgKA26.js (new) 581 B 🔴 +581 B 🔴 +329 B 🔴 +289 B
assets/refreshRemoteConfig-CduUeBYj.js (removed) 345 B 🟢 -345 B 🟢 -208 B 🟢 -201 B
assets/refreshRemoteConfig-CXPHYHxa.js (new) 345 B 🔴 +345 B 🔴 +201 B 🔴 +182 B
assets/config-HAvaVehF.js 1.01 kB 1.01 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-BgcR6REE.js 25 kB 25 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-BmEXiKRV.js 32 kB 32 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-BNs5rnMt.js 24.1 kB 24.1 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-C5jqELHS.js 23.6 kB 23.6 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-CKCscONF.js 28.1 kB 28.1 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-CzcL_U24.js 20.8 kB 20.8 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-D95NIw0v.js 26.8 kB 26.8 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-DmYc-PH9.js 24.2 kB 24.2 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-DQe4OZm2.js 25.7 kB 25.7 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-Dxocpd_-.js 20.3 kB 20.3 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-f2yAwEpW.js 23.3 kB 23.3 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B

Status: 13 added / 13 removed

User & Accounts — 16 kB (baseline 16 kB) • 🔴 +4 B

Authentication, profile, and account management bundles

File Before After Δ Raw Δ Gzip Δ Brotli
assets/PasswordFields-DObGnB17.js (new) 4.51 kB 🔴 +4.51 kB 🔴 +1.36 kB 🔴 +1.2 kB
assets/PasswordFields-BAi3GPM-.js (removed) 4.51 kB 🟢 -4.51 kB 🟢 -1.36 kB 🟢 -1.19 kB
assets/auth-Cn7ucQTY.js (new) 3.4 kB 🔴 +3.4 kB 🔴 +1.18 kB 🔴 +986 B
assets/auth-jRF-s1_l.js (removed) 3.4 kB 🟢 -3.4 kB 🟢 -1.18 kB 🟢 -990 B
assets/SignUpForm-DwOKGfqa.js (new) 3.01 kB 🔴 +3.01 kB 🔴 +1.23 kB 🔴 +1.09 kB
assets/SignUpForm-btQkTEhV.js (removed) 3.01 kB 🟢 -3.01 kB 🟢 -1.23 kB 🟢 -1.09 kB
assets/UpdatePasswordContent-DdP22ERA.js (new) 2.37 kB 🔴 +2.37 kB 🔴 +1.07 kB 🔴 +949 B
assets/UpdatePasswordContent-DvU03ib6.js (removed) 2.37 kB 🟢 -2.37 kB 🟢 -1.07 kB 🟢 -949 B
assets/WorkspaceProfilePic-B0Y84gcu.js (new) 1.57 kB 🔴 +1.57 kB 🔴 +818 B 🔴 +727 B
assets/WorkspaceProfilePic-B0BztYjc.js (removed) 1.57 kB 🟢 -1.57 kB 🟢 -819 B 🟢 -729 B
assets/firebaseAuthStore-Lp-lQ_59.js (new) 790 B 🔴 +790 B 🔴 +390 B 🔴 +346 B
assets/firebaseAuthStore-wZqlmfJB.js (removed) 790 B 🟢 -790 B 🟢 -390 B 🟢 -347 B
assets/auth-B752WpvM.js (new) 347 B 🔴 +347 B 🔴 +220 B 🔴 +189 B
assets/auth-B8MpMyH-.js (removed) 347 B 🟢 -347 B 🟢 -221 B 🟢 -190 B

Status: 7 added / 7 removed

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

Modals, dialogs, drawers, and in-app editors

File Before After Δ Raw Δ Gzip Δ Brotli
assets/useSubscriptionDialog-B1MjwVqx.js (new) 2.68 kB 🔴 +2.68 kB 🔴 +1.32 kB 🔴 +1.17 kB
assets/useSubscriptionDialog-DJiMCzEE.js (removed) 2.68 kB 🟢 -2.68 kB 🟢 -1.32 kB 🟢 -1.17 kB
assets/useSubscriptionDialog-DaIbsyAH.js (new) 783 B 🔴 +783 B 🔴 +388 B 🔴 +345 B
assets/useSubscriptionDialog-pnLxLsAs.js (removed) 783 B 🟢 -783 B 🟢 -389 B 🟢 -344 B

Status: 2 added / 2 removed

UI Components — 41.5 kB (baseline 37.8 kB) • 🔴 +3.73 kB

Reusable component library chunks

File Before After Δ Raw Δ Gzip Δ Brotli
assets/useTerminalTabs-Cw9tq9WG.js (new) 9.9 kB 🔴 +9.9 kB 🔴 +3.4 kB 🔴 +3 kB
assets/useTerminalTabs-BT6sU6kK.js (removed) 9.9 kB 🟢 -9.9 kB 🟢 -3.41 kB 🟢 -3 kB
assets/TopbarBadge-b6-QtyKa.js (new) 7.52 kB 🔴 +7.52 kB 🔴 +1.82 kB 🔴 +1.61 kB
assets/TopbarBadge-BsZTG314.js (removed) 7.52 kB 🟢 -7.52 kB 🟢 -1.82 kB 🟢 -1.6 kB
assets/ComfyQueueButton-DFQ39jA8.js (new) 7.13 kB 🔴 +7.13 kB 🔴 +2.3 kB 🔴 +2.06 kB
assets/ComfyQueueButton-ClvGDygu.js (removed) 7.13 kB 🟢 -7.13 kB 🟢 -2.31 kB 🟢 -2.06 kB
assets/FormSearchInput-1lF0Qlup.js (new) 3.72 kB 🔴 +3.72 kB 🔴 +1.54 kB 🔴 +1.34 kB
assets/SubscribeButton-CmYbyMrm.js (new) 3.68 kB 🔴 +3.68 kB 🔴 +1.38 kB 🔴 +1.23 kB
assets/SubscribeButton-BT9OWusp.js (removed) 3.68 kB 🟢 -3.68 kB 🟢 -1.38 kB 🟢 -1.21 kB
assets/Button-CYhuRut-.js (new) 3 kB 🔴 +3 kB 🔴 +1.21 kB 🔴 +1.05 kB
assets/Button-ChqYCdrL.js (removed) 3 kB 🟢 -3 kB 🟢 -1.21 kB 🟢 -1.08 kB
assets/WidgetButton-DGzunjlS.js (new) 1.84 kB 🔴 +1.84 kB 🔴 +874 B 🔴 +769 B
assets/WidgetButton-DVfWCi_V.js (removed) 1.84 kB 🟢 -1.84 kB 🟢 -877 B 🟢 -776 B
assets/cloudFeedbackTopbarButton-BU8xdWb6.js (removed) 1.6 kB 🟢 -1.6 kB 🟢 -857 B 🟢 -753 B
assets/cloudFeedbackTopbarButton-CjvtmS90.js (new) 1.6 kB 🔴 +1.6 kB 🔴 +858 B 🔴 +767 B
assets/CloudBadge-D4m2PEl3.js (new) 1.2 kB 🔴 +1.2 kB 🔴 +589 B 🔴 +506 B
assets/CloudBadge-DtjkqbcY.js (removed) 1.2 kB 🟢 -1.2 kB 🟢 -592 B 🟢 -509 B
assets/UserAvatar-DJNROG3c.js (new) 1.17 kB 🔴 +1.17 kB 🔴 +620 B 🔴 +522 B
assets/UserAvatar-Dtdiv1Hu.js (removed) 1.17 kB 🟢 -1.17 kB 🟢 -619 B 🟢 -528 B
assets/ComfyQueueButton-BADfengS.js (new) 795 B 🔴 +795 B 🔴 +394 B 🔴 +352 B
assets/ComfyQueueButton-D_4LsbVT.js (removed) 795 B 🟢 -795 B 🟢 -396 B 🟢 -352 B

Status: 11 added / 10 removed

Data & Services — 2.16 MB (baseline 2.1 MB) • 🔴 +65.8 kB

Stores, services, APIs, and repositories

File Before After Δ Raw Δ Gzip Δ Brotli
assets/dialogService-BkMx9zFS.js (new) 1.36 MB 🔴 +1.36 MB 🔴 +312 kB 🔴 +242 kB
assets/dialogService-DcoaL1n_.js (removed) 1.3 MB 🟢 -1.3 MB 🟢 -298 kB 🟢 -231 kB
assets/api-C-TD-jxk.js (new) 640 kB 🔴 +640 kB 🔴 +144 kB 🔴 +115 kB
assets/api-FZTUsZ6b.js (removed) 640 kB 🟢 -640 kB 🟢 -144 kB 🟢 -115 kB
assets/load3dService-aQlDhLLS.js (removed) 91.1 kB 🟢 -91.1 kB 🟢 -19.1 kB 🟢 -16.4 kB
assets/load3dService-DANmATFS.js (new) 91.1 kB 🔴 +91.1 kB 🔴 +19.1 kB 🔴 +16.4 kB
assets/teamWorkspaceStore-D78reazS.js (removed) 27.1 kB 🟢 -27.1 kB 🟢 -6.12 kB 🟢 -5.42 kB
assets/teamWorkspaceStore-dDO8ds4d.js (new) 27.1 kB 🔴 +27.1 kB 🔴 +6.12 kB 🔴 +5.4 kB
assets/systemStatsStore-C530gcfU.js (removed) 12.3 kB 🟢 -12.3 kB 🟢 -4.29 kB 🟢 -3.77 kB
assets/systemStatsStore-CjHi0VLd.js (new) 12.3 kB 🔴 +12.3 kB 🔴 +4.29 kB 🔴 +3.77 kB
assets/releaseStore-5mq6I30q.js (removed) 8.14 kB 🟢 -8.14 kB 🟢 -2.24 kB 🟢 -1.97 kB
assets/releaseStore-DnNTPxOo.js (new) 8.14 kB 🔴 +8.14 kB 🔴 +2.23 kB 🔴 +1.97 kB
assets/keybindingService-BgLBup2G.js (new) 6.58 kB 🔴 +6.58 kB 🔴 +1.71 kB 🔴 +1.48 kB
assets/keybindingService-DLmOtcfz.js (removed) 6.58 kB 🟢 -6.58 kB 🟢 -1.71 kB 🟢 -1.47 kB
assets/dialogStore-CFEg-vC4.js (new) 4.1 kB 🔴 +4.1 kB 🔴 +1.24 kB 🔴 +1.1 kB
assets/dialogStore-D2gUGUn6.js (removed) 4.1 kB 🟢 -4.1 kB 🟢 -1.24 kB 🟢 -1.1 kB
assets/serverConfigStore-BeEa5TU1.js (new) 2.32 kB 🔴 +2.32 kB 🔴 +789 B 🔴 +697 B
assets/serverConfigStore-DB58Z5s2.js (removed) 2.32 kB 🟢 -2.32 kB 🟢 -791 B 🟢 -690 B
assets/bootstrapStore-BkB5wxHT.js (new) 2.13 kB 🔴 +2.13 kB 🔴 +879 B 🔴 +810 B
assets/bootstrapStore-UArSSIUf.js (removed) 2.13 kB 🟢 -2.13 kB 🟢 -882 B 🟢 -811 B
assets/userStore-BrdK2ub-.js (removed) 1.85 kB 🟢 -1.85 kB 🟢 -720 B 🟢 -634 B
assets/userStore-CtG2nU36.js (new) 1.85 kB 🔴 +1.85 kB 🔴 +719 B 🔴 +630 B
assets/audioService-1Rmn2--_.js (removed) 1.73 kB 🟢 -1.73 kB 🟢 -845 B 🟢 -726 B
assets/audioService-DE4UQFNk.js (new) 1.73 kB 🔴 +1.73 kB 🔴 +844 B 🔴 +726 B
assets/releaseStore-GX3fzbPD.js (removed) 762 B 🟢 -762 B 🟢 -387 B 🟢 -336 B
assets/releaseStore-IPbbhtmU.js (new) 762 B 🔴 +762 B 🔴 +385 B 🔴 +337 B

Status: 13 added / 13 removed

Utilities & Hooks — 234 kB (baseline 234 kB) • 🔴 +1 B

Helpers, composables, and utility bundles

File Before After Δ Raw Δ Gzip Δ Brotli
assets/useConflictDetection-06q2g_Es.js (new) 177 kB 🔴 +177 kB 🔴 +38.9 kB 🔴 +32.4 kB
assets/useConflictDetection-CAdEmbEC.js (removed) 177 kB 🟢 -177 kB 🟢 -38.9 kB 🟢 -32.4 kB
assets/useLoad3d-BQ03A_SZ.js (new) 14.4 kB 🔴 +14.4 kB 🔴 +3.56 kB 🔴 +3.13 kB
assets/useLoad3d-Q4mSd74N.js (removed) 14.4 kB 🟢 -14.4 kB 🟢 -3.56 kB 🟢 -3.14 kB
assets/useLoad3dViewer-B0zK6fBu.js (removed) 14.2 kB 🟢 -14.2 kB 🟢 -3.16 kB 🟢 -2.8 kB
assets/useLoad3dViewer-CQKA_Fie.js (new) 14.2 kB 🔴 +14.2 kB 🔴 +3.15 kB 🔴 +2.8 kB
assets/colorUtil-DQv66Fnr.js (new) 7 kB 🔴 +7 kB 🔴 +2.14 kB 🔴 +1.89 kB
assets/colorUtil-FW_Ghng2.js (removed) 7 kB 🟢 -7 kB 🟢 -2.14 kB 🟢 -1.9 kB
assets/useErrorHandling-CAZm5w2b.js (removed) 4.26 kB 🟢 -4.26 kB 🟢 -1.34 kB 🟢 -1.18 kB
assets/useErrorHandling-Dh13Nsvo.js (new) 4.26 kB 🔴 +4.26 kB 🔴 +1.34 kB 🔴 +1.18 kB
assets/useWorkspaceUI-Dw7VMHAj.js (new) 3.16 kB 🔴 +3.16 kB 🔴 +882 B 🔴 +756 B
assets/useWorkspaceUI-x8hNeFaB.js (removed) 3.16 kB 🟢 -3.16 kB 🟢 -883 B 🟢 -764 B
assets/useSubscriptionActions-BfTPUU_q.js (removed) 1.76 kB 🟢 -1.76 kB 🟢 -753 B 🟢 -650 B
assets/useSubscriptionActions-CYch5OFW.js (new) 1.76 kB 🔴 +1.76 kB 🔴 +755 B 🔴 +653 B
assets/subscriptionCheckoutUtil-B5fCv7nM.js (removed) 1.75 kB 🟢 -1.75 kB 🟢 -783 B 🟢 -686 B
assets/subscriptionCheckoutUtil-BQmxWTQC.js (new) 1.75 kB 🔴 +1.75 kB 🔴 +784 B 🔴 +687 B
assets/useExternalLink-Br3OkPS9.js (removed) 1.63 kB 🟢 -1.63 kB 🟢 -756 B 🟢 -669 B
assets/useExternalLink-DSwAuuHj.js (new) 1.63 kB 🔴 +1.63 kB 🔴 +756 B 🔴 +668 B
assets/useCopyToClipboard-DpRy017q.js (new) 1.57 kB 🔴 +1.57 kB 🔴 +669 B 🔴 +564 B
assets/useCopyToClipboard-ihQ2636T.js (removed) 1.57 kB 🟢 -1.57 kB 🟢 -666 B 🟢 -562 B
assets/useSubscriptionCredits-iu_nZwRO.js (removed) 1.07 kB 🟢 -1.07 kB 🟢 -500 B 🟢 -438 B
assets/useSubscriptionCredits-rMuSrllm.js (new) 1.07 kB 🔴 +1.07 kB 🔴 +497 B 🔴 +451 B
assets/useLoad3d-CS5ldyBv.js (removed) 861 B 🟢 -861 B 🟢 -428 B 🟢 -382 B
assets/useLoad3d-DmafApQK.js (new) 861 B 🔴 +861 B 🔴 +426 B 🔴 +383 B
assets/audioUtils-BenGzUcw.js (removed) 858 B 🟢 -858 B 🟢 -497 B 🟢 -421 B
assets/audioUtils-J5OtflA4.js (new) 858 B 🔴 +858 B 🔴 +501 B 🔴 +417 B
assets/useLoad3dViewer-B7FBAVvx.js (removed) 840 B 🟢 -840 B 🟢 -412 B 🟢 -374 B
assets/useLoad3dViewer-B9lJg5fl.js (new) 840 B 🔴 +840 B 🔴 +408 B 🔴 +366 B
assets/useCurrentUser-Bu_xl_KU.js (new) 724 B 🔴 +724 B 🔴 +372 B 🔴 +328 B
assets/useCurrentUser-CKbgW21s.js (removed) 724 B 🟢 -724 B 🟢 -378 B 🟢 -328 B
assets/_plugin-vue_export-helper-CAbbkOlw.js 315 B 315 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/envUtil-Djb4sHrV.js 524 B 524 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/markdownRendererUtil-Cc2LFa5s.js 1.56 kB 1.56 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/networkUtil-Du2a_n7V.js 1.07 kB 1.07 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/SkeletonUtils-DwBAnKr-.js 133 B 133 B ⚪ 0 B ⚪ 0 B ⚪ 0 B

Status: 15 added / 15 removed

Vendor & Third-Party — 9.37 MB (baseline 9.37 MB) • 🔴 +22 B

External libraries and shared vendor chunks

File Before After Δ Raw Δ Gzip Δ Brotli
assets/vendor-other-DPcKX1Hx.js (removed) 2.16 MB 🟢 -2.16 MB 🟢 -456 kB 🟢 -346 kB
assets/vendor-other-mOclX7h7.js (new) 2.16 MB 🔴 +2.16 MB 🔴 +456 kB 🔴 +346 kB
assets/vendor-primevue-_nJE7WI4.js (new) 1.74 MB 🔴 +1.74 MB 🔴 +313 kB 🔴 +190 kB
assets/vendor-primevue-Dw8RivU1.js (removed) 1.74 MB 🟢 -1.74 MB 🟢 -313 kB 🟢 -190 kB
assets/vendor-vue-core-CeYeVk9k.js (new) 312 kB 🔴 +312 kB 🔴 +77.2 kB 🔴 +65.2 kB
assets/vendor-vue-core-BBP9kkTE.js (removed) 312 kB 🟢 -312 kB 🟢 -77.2 kB 🟢 -65.2 kB
assets/vendor-reka-ui-CocZye4X.js (new) 240 kB 🔴 +240 kB 🔴 +49 kB 🔴 +40.8 kB
assets/vendor-reka-ui-CPrt7lw0.js (removed) 240 kB 🟢 -240 kB 🟢 -49 kB 🟢 -40.8 kB
assets/vendor-i18n-C6_f776o.js (removed) 132 kB 🟢 -132 kB 🟢 -27.6 kB 🟢 -23.7 kB
assets/vendor-i18n-CweFo4ym.js (new) 132 kB 🔴 +132 kB 🔴 +27.6 kB 🔴 +23.8 kB
assets/vendor-vueuse-CCwuImy8.js (new) 111 kB 🔴 +111 kB 🔴 +27.4 kB 🔴 +23.9 kB
assets/vendor-vueuse-CS5mlw4Q.js (removed) 111 kB 🟢 -111 kB 🟢 -27.4 kB 🟢 -24 kB
assets/vendor-axios-D0w6WYH0.js 71.6 kB 71.6 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-chart-CIdI9Yl4.js 399 kB 399 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-firebase-CLEC0CcJ.js 842 kB 842 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-markdown-BkVHYrKV.js 102 kB 102 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-sentry-BVA5kbUC.js 183 kB 183 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-three-CJhOuqDg.js 1.8 MB 1.8 MB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-tiptap-CvX7mojg.js 632 kB 632 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-xterm-DOEEpxXk.js 398 kB 398 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-yjs-BtrvIr1x.js 143 kB 143 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-zod-gAWmQIF-.js 110 kB 110 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B

Status: 6 added / 6 removed

Other — 7.07 MB (baseline 7.08 MB) • 🟢 -7.87 kB

Bundles that do not match a named category

File Before After Δ Raw Δ Gzip Δ Brotli
assets/i18n-BDokGaVo.js (new) 482 kB 🔴 +482 kB 🔴 +92.7 kB 🔴 +72.2 kB
assets/i18n-CU6mOd8i.js (removed) 478 kB 🟢 -478 kB 🟢 -91.4 kB 🟢 -71.2 kB
assets/core-Be8baSLo.js (new) 71.4 kB 🔴 +71.4 kB 🔴 +18.4 kB 🔴 +15.8 kB
assets/core-DNew2dbc.js (removed) 71.4 kB 🟢 -71.4 kB 🟢 -18.4 kB 🟢 -15.8 kB
assets/groupNode-CIPXUIlG.js (new) 70.9 kB 🔴 +70.9 kB 🔴 +17.5 kB 🔴 +15.4 kB
assets/groupNode-NUD1aTZv.js (removed) 70.9 kB 🟢 -70.9 kB 🟢 -17.5 kB 🟢 -15.4 kB
assets/WidgetSelect-C9C6tV6Y.js (new) 56.6 kB 🔴 +56.6 kB 🔴 +12 kB 🔴 +10.4 kB
assets/WidgetSelect-IqXOof5n.js (removed) 56.6 kB 🟢 -56.6 kB 🟢 -12 kB 🟢 -10.4 kB
assets/SettingDialogContent-ChKceouK.js (new) 30.8 kB 🔴 +30.8 kB 🔴 +7.99 kB 🔴 +7.05 kB
assets/SettingDialogContent-ByXeaPzW.js (removed) 30.8 kB 🟢 -30.8 kB 🟢 -8 kB 🟢 -7.06 kB
assets/Load3DControls-DyvP6nr5.js (new) 30.8 kB 🔴 +30.8 kB 🔴 +5.33 kB 🔴 +4.63 kB
assets/Load3DControls-DlZVZyY2.js (removed) 30.8 kB 🟢 -30.8 kB 🟢 -5.33 kB 🟢 -4.64 kB
assets/SubscriptionRequiredDialogContent-g9Db41mU.js (new) 25.4 kB 🔴 +25.4 kB 🔴 +6.36 kB 🔴 +5.58 kB
assets/SubscriptionRequiredDialogContent-tt7RdYZH.js (removed) 25.4 kB 🟢 -25.4 kB 🟢 -6.36 kB 🟢 -5.57 kB
assets/Load3dViewerContent-B0wK9rI6.js (new) 23.2 kB 🔴 +23.2 kB 🔴 +5.24 kB 🔴 +4.54 kB
assets/Load3dViewerContent-CSkyfjfF.js (removed) 23.2 kB 🟢 -23.2 kB 🟢 -5.24 kB 🟢 -4.54 kB
assets/WidgetImageCrop-qqesFbNA.js (new) 22.3 kB 🔴 +22.3 kB 🔴 +5.51 kB 🔴 +4.86 kB
assets/WidgetImageCrop-DUCmVSeg.js (removed) 22.3 kB 🟢 -22.3 kB 🟢 -5.51 kB 🟢 -4.85 kB
assets/FormItem-XH0mmouX.js (new) 20.2 kB 🔴 +20.2 kB 🔴 +4.86 kB 🔴 +4.24 kB
assets/FormItem-CN4gOlCV.js (removed) 20.2 kB 🟢 -20.2 kB 🟢 -4.87 kB 🟢 -4.25 kB
assets/CurrentUserPopoverWorkspace-CvNs5gAf.js (new) 19.7 kB 🔴 +19.7 kB 🔴 +4.74 kB 🔴 +4.23 kB
assets/CurrentUserPopoverWorkspace-Bm-xUahU.js (removed) 19.7 kB 🟢 -19.7 kB 🟢 -4.74 kB 🟢 -4.22 kB
assets/SignInContent-CqC6cehX.js (new) 19 kB 🔴 +19 kB 🔴 +4.8 kB 🔴 +4.2 kB
assets/SignInContent-CDWs0HY4.js (removed) 19 kB 🟢 -19 kB 🟢 -4.8 kB 🟢 -4.2 kB
assets/WidgetRecordAudio-T3nh0lA0.js (new) 17.2 kB 🔴 +17.2 kB 🔴 +4.92 kB 🔴 +4.39 kB
assets/WidgetRecordAudio-CMoNwfZa.js (removed) 17.2 kB 🟢 -17.2 kB 🟢 -4.92 kB 🟢 -4.4 kB
assets/TopUpCreditsDialogContent-B816Plcg.js (new) 16.9 kB 🔴 +16.9 kB 🔴 +4.79 kB 🔴 +4.25 kB
assets/TopUpCreditsDialogContent-DvtzOwoj.js (removed) 16.9 kB 🟢 -16.9 kB 🟢 -4.8 kB 🟢 -4.25 kB
assets/Load3D-r9TuJ0mL.js (new) 16.2 kB 🔴 +16.2 kB 🔴 +4.03 kB 🔴 +3.51 kB
assets/Load3D-65U7QTuj.js (removed) 16.2 kB 🟢 -16.2 kB 🟢 -4.04 kB 🟢 -3.51 kB
assets/MissingModelsWarning-BrOmZWcc.js (new) 16.1 kB 🔴 +16.1 kB 🔴 +4.39 kB 🔴 +3.91 kB
assets/MissingModelsWarning-Csx5A4v4.js (removed) 16.1 kB 🟢 -16.1 kB 🟢 -4.4 kB 🟢 -3.92 kB
assets/SubscriptionPanelContentWorkspace-DFp27eOL.js (new) 15.8 kB 🔴 +15.8 kB 🔴 +4.03 kB 🔴 +3.54 kB
assets/SubscriptionPanelContentWorkspace-C4R53Nbb.js (removed) 15.8 kB 🟢 -15.8 kB 🟢 -4.03 kB 🟢 -3.54 kB
assets/WidgetInputNumber-DUJEoivy.js (new) 15.2 kB 🔴 +15.2 kB 🔴 +4.14 kB 🔴 +3.68 kB
assets/WidgetInputNumber-DpP2fSrZ.js (removed) 15.2 kB 🟢 -15.2 kB 🟢 -4.15 kB 🟢 -3.69 kB
assets/load3d-BTID0_9y.js (removed) 14.8 kB 🟢 -14.8 kB 🟢 -4.18 kB 🟢 -3.64 kB
assets/load3d-LY1rzEhM.js (new) 14.8 kB 🔴 +14.8 kB 🔴 +4.18 kB 🔴 +3.63 kB
assets/LazyImage-CVvKb-rW.js (removed) 12.3 kB 🟢 -12.3 kB 🟢 -3.78 kB 🟢 -3.32 kB
assets/NodeConflictDialogContent-DBDtWA3o.js (new) 10.5 kB 🔴 +10.5 kB 🔴 +2.36 kB 🔴 +2.07 kB
assets/NodeConflictDialogContent-8Q9R-ZCu.js (removed) 10.5 kB 🟢 -10.5 kB 🟢 -2.37 kB 🟢 -2.08 kB
assets/AudioPreviewPlayer-DrfbcB6Z.js (new) 10.1 kB 🔴 +10.1 kB 🔴 +2.98 kB 🔴 +2.69 kB
assets/AudioPreviewPlayer-DCmEH3QI.js (removed) 10.1 kB 🟢 -10.1 kB 🟢 -2.99 kB 🟢 -2.68 kB
assets/nodeTemplates-C9xFAVZe.js (removed) 9.33 kB 🟢 -9.33 kB 🟢 -3.27 kB 🟢 -2.88 kB
assets/nodeTemplates-CYY3YWHl.js (new) 9.33 kB 🔴 +9.33 kB 🔴 +3.27 kB 🔴 +2.87 kB
assets/SelectValue-Rq-dkF7f.js (new) 8.81 kB 🔴 +8.81 kB 🔴 +2.22 kB 🔴 +1.96 kB
assets/SelectValue-RIZ7-gt8.js (removed) 8.81 kB 🟢 -8.81 kB 🟢 -2.22 kB 🟢 -1.96 kB
assets/InviteMemberDialogContent-CgI7rK9n.js (new) 7.95 kB 🔴 +7.95 kB 🔴 +2.56 kB 🔴 +2.22 kB
assets/InviteMemberDialogContent-RKGV3Yb2.js (removed) 7.95 kB 🟢 -7.95 kB 🟢 -2.56 kB 🟢 -2.22 kB
assets/WidgetWithControl-DucPiAxP.js (new) 6.97 kB 🔴 +6.97 kB 🔴 +2.59 kB 🔴 +2.32 kB
assets/WidgetWithControl-rS5E9OCx.js (removed) 6.97 kB 🟢 -6.97 kB 🟢 -2.59 kB 🟢 -2.31 kB
assets/Load3DConfiguration-B97N9Tta.js (removed) 6.36 kB 🟢 -6.36 kB 🟢 -1.92 kB 🟢 -1.69 kB
assets/Load3DConfiguration-Y1dfWh9h.js (new) 6.36 kB 🔴 +6.36 kB 🔴 +1.92 kB 🔴 +1.69 kB
assets/MissingNodesContent-DQsUkKis.js (new) 6.14 kB 🔴 +6.14 kB 🔴 +2.08 kB 🔴 +1.85 kB
assets/MissingNodesContent-hjf8jk0P.js (removed) 6.14 kB 🟢 -6.14 kB 🟢 -2.08 kB 🟢 -1.86 kB
assets/CreateWorkspaceDialogContent-QmDvFU9t.js (new) 5.58 kB 🔴 +5.58 kB 🔴 +2 kB 🔴 +1.74 kB
assets/CreateWorkspaceDialogContent-D-xcP_8x.js (removed) 5.58 kB 🟢 -5.58 kB 🟢 -2 kB 🟢 -1.75 kB
assets/EditWorkspaceDialogContent-DWg_XPZt.js (new) 5.38 kB 🔴 +5.38 kB 🔴 +1.95 kB 🔴 +1.7 kB
assets/EditWorkspaceDialogContent-BtiUtY-f.js (removed) 5.38 kB 🟢 -5.38 kB 🟢 -1.96 kB 🟢 -1.7 kB
assets/ValueControlPopover-70yf4Cwd.js (new) 4.92 kB 🔴 +4.92 kB 🔴 +1.77 kB 🔴 +1.57 kB
assets/ValueControlPopover-Bd8J0IyE.js (removed) 4.92 kB 🟢 -4.92 kB 🟢 -1.77 kB 🟢 -1.58 kB
assets/Preview3d-CcCM4DPW.js (new) 4.82 kB 🔴 +4.82 kB 🔴 +1.56 kB 🔴 +1.37 kB
assets/Preview3d-DWp89F1Y.js (removed) 4.82 kB 🟢 -4.82 kB 🟢 -1.57 kB 🟢 -1.36 kB
assets/MissingNodesFooter-CLG6FsH4.js (new) 4.8 kB 🔴 +4.8 kB 🔴 +1.78 kB 🔴 +1.57 kB
assets/MissingNodesFooter-D_qjWCTE.js (removed) 4.8 kB 🟢 -4.8 kB 🟢 -1.78 kB 🟢 -1.57 kB
assets/AnimationControls-CueRJVLu.js (new) 4.61 kB 🔴 +4.61 kB 🔴 +1.6 kB 🔴 +1.41 kB
assets/AnimationControls-bPyINTJI.js (removed) 4.61 kB 🟢 -4.61 kB 🟢 -1.6 kB 🟢 -1.41 kB
assets/DeleteWorkspaceDialogContent-D31M349t.js (new) 4.28 kB 🔴 +4.28 kB 🔴 +1.64 kB 🔴 +1.43 kB
assets/DeleteWorkspaceDialogContent-DndTxfUn.js (removed) 4.28 kB 🟢 -4.28 kB 🟢 -1.64 kB 🟢 -1.43 kB
assets/LeaveWorkspaceDialogContent-DD2nkBRc.js (new) 4.11 kB 🔴 +4.11 kB 🔴 +1.59 kB 🔴 +1.38 kB
assets/LeaveWorkspaceDialogContent-DOrzl6yL.js (removed) 4.11 kB 🟢 -4.11 kB 🟢 -1.59 kB 🟢 -1.38 kB
assets/RemoveMemberDialogContent-DR_PT3ob.js (new) 4.09 kB 🔴 +4.09 kB 🔴 +1.53 kB 🔴 +1.34 kB
assets/RemoveMemberDialogContent-C3NstwKW.js (removed) 4.09 kB 🟢 -4.09 kB 🟢 -1.54 kB 🟢 -1.34 kB
assets/RevokeInviteDialogContent-BZueWskJ.js (new) 4 kB 🔴 +4 kB 🔴 +1.55 kB 🔴 +1.36 kB
assets/RevokeInviteDialogContent-WjdOCFHm.js (removed) 4 kB 🟢 -4 kB 🟢 -1.56 kB 🟢 -1.36 kB
assets/WidgetBoundingBox-RBM9mZ7o.js (new) 3.95 kB 🔴 +3.95 kB 🔴 +996 B 🔴 +873 B
assets/WidgetBoundingBox-2w5BjjHm.js (removed) 3.94 kB 🟢 -3.94 kB 🟢 -996 B 🟢 -862 B
assets/WidgetGalleria-L8dc5rcD.js (new) 3.61 kB 🔴 +3.61 kB 🔴 +1.39 kB 🔴 +1.25 kB
assets/WidgetGalleria-C8ikSQqn.js (removed) 3.61 kB 🟢 -3.61 kB 🟢 -1.39 kB 🟢 -1.25 kB
assets/Slider-CdGynAqi.js (new) 3.52 kB 🔴 +3.52 kB 🔴 +1.36 kB 🔴 +1.19 kB
assets/Slider-Bh_ZdHn9.js (removed) 3.52 kB 🟢 -3.52 kB 🟢 -1.36 kB 🟢 -1.19 kB
assets/saveMesh-BXLKM_t1.js (new) 3.38 kB 🔴 +3.38 kB 🔴 +1.45 kB 🔴 +1.29 kB
assets/saveMesh-CjTLVW97.js (removed) 3.38 kB 🟢 -3.38 kB 🟢 -1.45 kB 🟢 -1.28 kB
assets/WidgetImageCompare-ZuDuo4Bv.js (new) 3.1 kB 🔴 +3.1 kB 🔴 +1.15 kB 🔴 +1.02 kB
assets/WidgetImageCompare-B1RSu0_k.js (removed) 3.1 kB 🟢 -3.1 kB 🟢 -1.15 kB 🟢 -1.02 kB
assets/WidgetTextarea-B-Ct3DWW.js (new) 3.09 kB 🔴 +3.09 kB 🔴 +1.28 kB 🔴 +1.11 kB
assets/WidgetTextarea-COgaptkR.js (removed) 3.09 kB 🟢 -3.09 kB 🟢 -1.28 kB 🟢 -1.12 kB
assets/cloudSessionCookie-Bucnv-Xo.js (new) 3.07 kB 🔴 +3.07 kB 🔴 +1.08 kB 🔴 +969 B
assets/cloudSessionCookie-C-A6WgEt.js (removed) 3.07 kB 🟢 -3.07 kB 🟢 -1.08 kB 🟢 -975 B
assets/WidgetColorPicker-CS8sQH2K.js (new) 2.9 kB 🔴 +2.9 kB 🔴 +1.23 kB 🔴 +1.11 kB
assets/WidgetColorPicker-jDbRm4n6.js (removed) 2.9 kB 🟢 -2.9 kB 🟢 -1.23 kB 🟢 -1.11 kB
assets/WidgetMarkdown-D-UN85jp.js (new) 2.88 kB 🔴 +2.88 kB 🔴 +1.22 kB 🔴 +1.06 kB
assets/WidgetMarkdown-wePHc2fh.js (removed) 2.88 kB 🟢 -2.88 kB 🟢 -1.22 kB 🟢 -1.07 kB
assets/ApiNodesSignInContent-CYrffSU5.js (new) 2.69 kB 🔴 +2.69 kB 🔴 +1.05 kB 🔴 +922 B
assets/ApiNodesSignInContent-BIJKq20L.js (removed) 2.69 kB 🟢 -2.69 kB 🟢 -1.05 kB 🟢 -961 B
assets/WidgetToggleSwitch-C_dz2yxi.js (new) 2.5 kB 🔴 +2.5 kB 🔴 +1.09 kB 🔴 +990 B
assets/WidgetToggleSwitch-CoKD027B.js (removed) 2.5 kB 🟢 -2.5 kB 🟢 -1.09 kB 🟢 -990 B
assets/ImportFailedNodeContent-pn4pucgE.js (new) 2.48 kB 🔴 +2.48 kB 🔴 +968 B 🔴 +823 B
assets/ImportFailedNodeContent-VPjQzi79.js (removed) 2.48 kB 🟢 -2.48 kB 🟢 -974 B 🟢 -828 B
assets/NodeConflictFooter-QsYD5fbg.js (new) 2.37 kB 🔴 +2.37 kB 🔴 +1.03 kB 🔴 +907 B
assets/NodeConflictFooter-C8QCTAMD.js (removed) 2.37 kB 🟢 -2.37 kB 🟢 -1.03 kB 🟢 -908 B
assets/GlobalToast-Dyg0846s.js (new) 2.34 kB 🔴 +2.34 kB 🔴 +963 B 🔴 +802 B
assets/GlobalToast-BJ9v6BQY.js (removed) 2.34 kB 🟢 -2.34 kB 🟢 -965 B 🟢 -808 B
assets/MediaVideoTop-Iywqxgfk.js (new) 2.23 kB 🔴 +2.23 kB 🔴 +935 B 🔴 +800 B
assets/MediaVideoTop-CQw3f2T6.js (removed) 2.23 kB 🟢 -2.23 kB 🟢 -937 B 🟢 -800 B
assets/WidgetChart-9c5WGJcq.js (new) 2.21 kB 🔴 +2.21 kB 🔴 +952 B 🔴 +822 B
assets/WidgetChart-Dsp6bf_j.js (removed) 2.21 kB 🟢 -2.21 kB 🟢 -948 B 🟢 -821 B
assets/SubscribeToRun-BxG0rw7R.js (new) 2.16 kB 🔴 +2.16 kB 🔴 +986 B 🔴 +854 B
assets/SubscribeToRun-BCrH2vTR.js (removed) 2.16 kB 🟢 -2.16 kB 🟢 -988 B 🟢 -855 B
assets/WidgetLayoutField-5UdMcieS.js (new) 1.95 kB 🔴 +1.95 kB 🔴 +876 B 🔴 +760 B
assets/WidgetLayoutField-BcUSnWUK.js (removed) 1.95 kB 🟢 -1.95 kB 🟢 -879 B 🟢 -763 B
assets/ImportFailedNodeFooter-CrYkIgmB.js (new) 1.88 kB 🔴 +1.88 kB 🔴 +865 B 🔴 +761 B
assets/ImportFailedNodeFooter-B2l9_-Av.js (removed) 1.88 kB 🟢 -1.88 kB 🟢 -867 B 🟢 -755 B
assets/WidgetInputText-CWR3JYF_.js (new) 1.86 kB 🔴 +1.86 kB 🔴 +872 B 🔴 +806 B
assets/WidgetInputText-MXN3EUoN.js (removed) 1.85 kB 🟢 -1.85 kB 🟢 -872 B 🟢 -795 B
assets/Media3DTop-DeU3G7BP.js (new) 1.82 kB 🔴 +1.82 kB 🔴 +899 B 🔴 +766 B
assets/Media3DTop-DTYkrVtB.js (removed) 1.82 kB 🟢 -1.82 kB 🟢 -897 B 🟢 -767 B
assets/BaseViewTemplate-B6pTilLR.js (new) 1.75 kB 🔴 +1.75 kB 🔴 +912 B 🔴 +815 B
assets/BaseViewTemplate-BnXUotno.js (removed) 1.75 kB 🟢 -1.75 kB 🟢 -910 B 🟢 -799 B
assets/MediaImageTop-CBKkpfLA.js (new) 1.75 kB 🔴 +1.75 kB 🔴 +879 B 🔴 +750 B
assets/MediaImageTop-ATOoJCQr.js (removed) 1.75 kB 🟢 -1.75 kB 🟢 -879 B 🟢 -750 B
assets/CloudRunButtonWrapper-B3PxAVgd.js (new) 1.67 kB 🔴 +1.67 kB 🔴 +780 B 🔴 +704 B
assets/CloudRunButtonWrapper-D3EQCQDx.js (removed) 1.67 kB 🟢 -1.67 kB 🟢 -782 B 🟢 -707 B
assets/SettingDialogHeader-C0gWOZl2.js (new) 1.62 kB 🔴 +1.62 kB 🔴 +810 B 🔴 +713 B
assets/SettingDialogHeader-BHt-6Ao_.js (removed) 1.62 kB 🟢 -1.62 kB 🟢 -810 B 🟢 -719 B
assets/signInSchema-5NW8sj_6.js (removed) 1.53 kB 🟢 -1.53 kB 🟢 -563 B 🟢 -516 B
assets/signInSchema-RomC7SGm.js (new) 1.53 kB 🔴 +1.53 kB 🔴 +562 B 🔴 +485 B
assets/MediaAudioTop-BKjDJdp9.js (new) 1.43 kB 🔴 +1.43 kB 🔴 +762 B 🔴 +631 B
assets/MediaAudioTop-AFmsiWi3.js (removed) 1.43 kB 🟢 -1.43 kB 🟢 -759 B 🟢 -632 B
assets/cloudBadges-B4xqANiJ.js (new) 1.37 kB 🔴 +1.37 kB 🔴 +699 B 🔴 +611 B
assets/cloudBadges-u6jSsu_P.js (removed) 1.37 kB 🟢 -1.37 kB 🟢 -702 B 🟢 -614 B
assets/cloudSubscription-UR1pDeIh.js (new) 1.32 kB 🔴 +1.32 kB 🔴 +652 B 🔴 +561 B
assets/cloudSubscription-v79R77aG.js (removed) 1.32 kB 🟢 -1.32 kB 🟢 -656 B 🟢 -569 B
assets/PanelTemplate-TuXQ0Ja-.js (new) 1.2 kB 🔴 +1.2 kB 🔴 +614 B 🔴 +534 B
assets/PanelTemplate-C1h0RxBM.js (removed) 1.2 kB 🟢 -1.2 kB 🟢 -612 B 🟢 -539 B
assets/MissingNodesHeader-BgeYMYe0.js (new) 1.09 kB 🔴 +1.09 kB 🔴 +581 B 🔴 +506 B
assets/MissingNodesHeader-BOlbqJ0O.js (removed) 1.09 kB 🟢 -1.09 kB 🟢 -580 B 🟢 -501 B
assets/NodeConflictHeader-BiSDkwht.js (new) 1.09 kB 🔴 +1.09 kB 🔴 +565 B 🔴 +477 B
assets/NodeConflictHeader-DRxQs5TH.js (removed) 1.09 kB 🟢 -1.09 kB 🟢 -565 B 🟢 -478 B
assets/ImportFailedNodeHeader-Cbrtv_r_.js (new) 1.08 kB 🔴 +1.08 kB 🔴 +550 B 🔴 +469 B
assets/ImportFailedNodeHeader-DUns4_x2.js (removed) 1.08 kB 🟢 -1.08 kB 🟢 -550 B 🟢 -471 B
assets/SubscriptionPanelContentWorkspace-D-LRFi8s.js (new) 1.07 kB 🔴 +1.07 kB 🔴 +481 B 🔴 +423 B
assets/SubscriptionPanelContentWorkspace-e6_FGb7t.js (removed) 1.07 kB 🟢 -1.07 kB 🟢 -481 B 🟢 -420 B
assets/Load3D-BLMFCifM.js (removed) 1.04 kB 🟢 -1.04 kB 🟢 -488 B 🟢 -435 B
assets/Load3D-DpRCJ_Fc.js (new) 1.04 kB 🔴 +1.04 kB 🔴 +484 B 🔴 +431 B
assets/nightlyBadges-D6a9CvA9.js (new) 1 kB 🔴 +1 kB 🔴 +537 B 🔴 +472 B
assets/nightlyBadges-DXPIXnF3.js (removed) 1 kB 🟢 -1 kB 🟢 -538 B 🟢 -480 B
assets/Load3dViewerContent-DacwVRR7.js (new) 961 B 🔴 +961 B 🔴 +454 B 🔴 +405 B
assets/Load3dViewerContent-DBSIW3ph.js (removed) 961 B 🟢 -961 B 🟢 -457 B 🟢 -403 B
assets/SettingDialogContent-DLlFgqfs.js (removed) 917 B 🟢 -917 B 🟢 -443 B 🟢 -387 B
assets/SettingDialogContent-DrC_kMEf.js (new) 917 B 🔴 +917 B 🔴 +441 B 🔴 +393 B
assets/ComfyOrgHeader-ADa5UmlK.js (new) 910 B 🔴 +910 B 🔴 +495 B 🔴 +428 B
assets/ComfyOrgHeader-32ZIoIYN.js (removed) 909 B 🟢 -909 B 🟢 -495 B 🟢 -434 B
assets/graphHasMissingNodes-akWwzdhB.js (new) 761 B 🔴 +761 B 🔴 +372 B 🔴 +319 B
assets/graphHasMissingNodes-CuNAMof7.js (removed) 761 B 🟢 -761 B 🟢 -375 B 🟢 -321 B
assets/WidgetLegacy-a51v03sl.js (new) 747 B 🔴 +747 B 🔴 +387 B 🔴 +337 B
assets/WidgetLegacy-YU6m_YBu.js (removed) 747 B 🟢 -747 B 🟢 -389 B 🟢 -336 B
assets/widgetTypes-BCsKJASV.js (removed) 393 B 🟢 -393 B 🟢 -258 B 🟢 -234 B
assets/widgetTypes-DYNy1R_e.js (new) 393 B 🔴 +393 B 🔴 +258 B 🔴 +224 B
assets/WidgetInputNumber-BY60ew1U.js (removed) 392 B 🟢 -392 B 🟢 -232 B 🟢 -195 B
assets/WidgetInputNumber-Dtn_D0Jw.js (new) 392 B 🔴 +392 B 🔴 +229 B 🔴 +211 B
assets/src-CR0jN8_H.js (new) 251 B 🔴 +251 B 🔴 +211 B 🔴 +183 B
assets/src-DAMctjF_.js (removed) 251 B 🟢 -251 B 🟢 -210 B 🟢 -177 B
assets/SettingDialogHeader-D5wathvf.js (removed) 244 B 🟢 -244 B 🟢 -165 B 🟢 -141 B
assets/SettingDialogHeader-DmEdTaDV.js (new) 244 B 🔴 +244 B 🔴 +170 B 🔴 +141 B
assets/i18n-DRG4Tivo.js (new) 199 B 🔴 +199 B 🔴 +161 B 🔴 +140 B
assets/i18n-wGA3C3Hi.js (removed) 199 B 🟢 -199 B 🟢 -160 B 🟢 -139 B
assets/WidgetBoundingBox-BkIBvP1d.js (new) 131 B 🔴 +131 B 🔴 +100 B 🔴 +94 B
assets/WidgetBoundingBox-Djm16aYN.js (removed) 131 B 🟢 -131 B 🟢 -100 B 🟢 -104 B
assets/mixpanel.module-CZTcVQ-d.js (new) 92 B 🔴 +92 B 🔴 +100 B 🔴 +75 B
assets/mixpanel.module-Dq6XmUX_.js (removed) 92 B 🟢 -92 B 🟢 -100 B 🟢 -81 B
assets/auto-DjoqfnTX.js 1.7 kB 1.7 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/comfy-logo-single-DCVL1uOL.js 198 B 198 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-7WXVxhf9.js 14.6 kB 14.6 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-BNcWkRcH.js 15.2 kB 15.2 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-Cchriln7.js 18 kB 18 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-COsT3jlc.js 16 kB 16 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-CpqJ59mV.js 16.8 kB 16.8 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-dco-b4jy.js 16.7 kB 16.7 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-Dj4qNjPR.js 15.4 kB 15.4 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-gwZZf095.js 14.4 kB 14.4 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-jn33Krva.js 15.4 kB 15.4 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-kIN_T4OP.js 16.3 kB 16.3 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-mTEY_dzO.js 15.4 kB 15.4 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-9VWyHo5z.js 136 kB 136 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-B8iCY4y4.js 155 kB 155 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-BFqZCm1H.js 131 kB 131 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-CgmTJMw2.js 113 kB 113 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-D2_fl4nn.js 148 kB 148 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-DbGv8BCU.js 114 kB 114 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-DigTCF_h.js 129 kB 129 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-DuAAxgfq.js 179 kB 179 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-DYZBa_3M.js 161 kB 161 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-wdnI-QZq.js 128 kB 128 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-Zns-zxeS.js 133 kB 133 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-aApbAbNI.js 360 kB 360 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-B2JRfCRl.js 332 kB 332 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-BbVIS5BG.js 357 kB 357 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-BddTsShn.js 374 kB 374 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-CEMSHoZg.js 360 kB 360 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-Cfjv7JEn.js 364 kB 364 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-COMqCnF4.js 353 kB 353 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-oMRTTpzO.js 405 kB 405 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-PGdfR_x_.js 441 kB 441 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-SDKvs0-9.js 404 kB 404 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-wKqgWC3H.js 329 kB 329 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/OBJLoader2WorkerModule-DTMpvldF.js 109 kB 109 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/previousFullPath-DsBk66MD.js 665 B 665 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/rolldown-runtime-Ca2S-reV.js 1.87 kB 1.87 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/types-VDank0gz.js 318 B 318 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/widget-RLUqfB5N.js 445 B 445 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/widgetPropFilter-DyQHILEf.js 1.1 kB 1.1 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B

Status: 85 added / 86 removed

@dosubot dosubot bot added the size:XXL This PR changes 1000+ lines, ignoring generated files. label Feb 3, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 26

🤖 Fix all issues with AI agents
In `@src/components/actionbar/PublishToHubDialogContent.vue`:
- Around line 12-18: Replace the raw icon-only <button> here with the repo's
shared icon button component (src/components/button/IconButton.vue), wiring its
click handler to onCancel and passing a clear accessible name via the component
prop (e.g., label or ariaLabel) instead of an inline aria-label; do the same for
the other occurrences mentioned (lines 96-101, 123-140) to use
IconButton/TextButton/IconTextButton as appropriate, keeping visible text
buttons as plain text (no aria-label), and for any toggle controls ensure the
component exposes the appropriate role and checked/pressed state
(aria-checked/aria-pressed or role="switch") so assistive tech can read the
state.
- Around line 200-206: The thumbnail preview currently creates object URLs
without revoking them; update handleThumbnailChange to call URL.revokeObjectURL
on the existing thumbnailPreview.value (if set) before assigning a new URL, and
ensure you revoke the current URL in a component cleanup hook (onUnmounted or
onBeforeUnmount) to avoid memory leaks; modify references to thumbnailFile and
thumbnailPreview accordingly and add the revoke logic and cleanup using
URL.revokeObjectURL(thumbnailPreview.value).

In `@src/components/actionbar/ShareButton.vue`:
- Around line 2-11: The Button element (the component with props variant, size,
class, :aria-pressed and `@click`="handleClick") has redundant :aria-label="{{
$t('discover.share.share') }}" while also rendering visible text {{
$t('discover.share.share') }}; remove the :aria-label attribute so the visible
text becomes the accessible name, keep :aria-pressed="isOpen" and the click
handler handleClick unchanged, and ensure no other accessibility attributes are
added that duplicate the visible label.

In `@src/components/actionbar/SharePanel.vue`:
- Around line 158-188: The copy buttons call copyToClipboard with
shareUrl/appModeShareUrl even when no workflow is active; prevent copying and
provide feedback by disabling those Button components (the ones invoking
copyToClipboard) when the workflow is absent and show a localized message or
toast/tooltip; update the Button props for the two places using copyToClipboard
(the button that uses shareUrl and the one that uses appModeShareUrl, and the
other occurrences at the ranges mentioned) to bind
:disabled="!hasActiveWorkflow" (or a similarly named computed like
hasActiveWorkflow) and, inside copyToClipboard, early-return with a call to the
existing user-notification mechanism (e.g., showToast/localize
$t('discover.share.noActiveWorkflow')) so the function also guards against
direct calls.
- Around line 158-188: Remove the redundant aria-label attributes from the
Button instances that already contain visible text (the Button wrapping the span
with {{ $t('discover.share.publicUrl.copyLink') }} and the Button wrapping {{
$t('discover.share.publicUrl.copyLinkAppMode') }}); locate the two Button
components that call copyToClipboard(shareUrl) and
copyToClipboard(appModeShareUrl) in SharePanel.vue and delete their :aria-label
bindings so the accessible name comes from the visible span text (leave other
props and the icon logic, e.g., the copied state and cn usage, unchanged).
- Around line 458-463: handleThumbnailChange currently calls URL.createObjectURL
but never revokes previous URLs; update handleThumbnailChange to store and
revoke the previous object URL (call URL.revokeObjectURL on the old
thumbnailPreview.value) before assigning a new URL, and add a component cleanup
hook (onUnmounted or onBeforeUnmount) to revoke thumbnailPreview.value when the
component unmounts; reference the handleThumbnailChange function and the
thumbnailPreview ref to implement these revocations using URL.revokeObjectURL.
- Around line 295-301: Replace the raw <button> in SharePanel.vue with the
repo's shared icon button component (e.g., IconButton from
src/components/button/IconButton.vue) so focus/hover/a11y styles are preserved;
keep the same :aria-label and `@click`="removeTag(tag)" behavior, move the <i
class="icon-[lucide--x] size-3" /> into the IconButton slot or icon prop as used
by your button component, and add the corresponding import/registration (or
ensure script setup has the component available) so SharePanel uses IconButton
instead of a plain button.

In `@src/components/discover/AuthorProfileView.vue`:
- Around line 308-313: The computed values formattedRuns and formattedCopies
treat 0 as falsy and render '--'; update their guards to check for
undefined/null explicitly (e.g., use nullish coalescing or an explicit !==
undefined check against stats?.runs and stats?.copies) so that 0 is formatted
with toLocaleString() while only undefined/null falls back to '--'; change both
computed blocks (formattedRuns and formattedCopies) accordingly.
- Around line 319-325: performSearch currently awaits search(...) without error
handling causing unhandled rejections; wrap the await call in a try/catch inside
the performSearch function, catch the error from search(query:
searchQuery.value, pageSize: 24, pageNumber: currentPage.value, facetFilters:
authorFacetFilters.value), and in the catch either set a component error state
(e.g., isSearchError / searchErrorMessage) to render an error UI or invoke the
app toast helper (e.g., $toast.error or useToast) with a localized message (use
i18n.t('errors.searchFailed') or similar) and log the original error for
diagnostics. Ensure the catch returns/clears any loading flags so the UI stays
consistent.
- Around line 197-202: Replace the dynamic Tailwind class binding on the
LazyImage with the project's cn() utility: import cn from
'@/utils/tailwindUtil', then use cn() to merge the static classes ("size-full
rounded-lg object-cover transition-transform duration-300") with the conditional
class that checks hoveredTemplate === template.objectID so the class composition
is consistent (target the existing LazyImage component and the
hoveredTemplate/template.objectID expression).
- Around line 346-351: Replace the immediate watcher on searchQuery with
VueUse's watchDebounced to debounce user input: change the watch( () =>
searchQuery.value, ... ) that resets currentPage.value and calls performSearch()
to use watchDebounced(searchQuery, handler, { debounce: 300 }) (or project
standard debounce ms) so performSearch and currentPage reset run only after the
debounce interval; reference the searchQuery ref, currentPage ref, and
performSearch() function and import watchDebounced from '@vueuse/core' if not
already imported.

In `@src/components/discover/DiscoverView.vue`:
- Around line 124-132: The no-results block currently uses the combined
condition `!cloudOnly || (results && results.templates.length === 0)`, which
causes the empty-state to render whenever the cloudOnly filter is disabled;
change the condition to only check for an empty result set (e.g., `(results &&
results.templates.length === 0)`), removing the `!cloudOnly` part so the grid
can render when results exist; update the template in DiscoverView.vue where
`cloudOnly` and `results.templates` are referenced to ensure the no-results
state only depends on an empty `results.templates`.
- Around line 153-158: The conditional class binding on the LazyImage component
uses the boolean && pattern; update it to use the project's cn() helper instead
for consistency: in the LazyImage element that references
:src="template.thumbnail_url", :alt="template.title" and the reactive
hoveredTemplate/template.objectID comparison, replace ":class='hoveredTemplate
=== template.objectID && \"scale-105\"'" with a call to cn(...) that always
includes the base classes ("size-full rounded-lg object-cover
transition-transform duration-300") and conditionally adds "scale-105" when
hoveredTemplate === template.objectID.

In `@src/components/discover/WorkflowDetailView.vue`:
- Around line 230-241: Replace the misleading placeholder defaults and
hard-coded suffix formatting: change runCount, viewCount, and copyCount to
default to 0 instead of 1_234/5_678/890, and replace the formatCount function
with a locale-aware compact formatter using Intl.NumberFormat with notation:
'compact' and the app's active locale (or navigator.language) so counts are
formatted per user locale rather than appending hard-coded "K"/"M"; update
references to formatCount, runCount, viewCount, copyCount and ensure the
formatter is reused (e.g., create it once outside the function or inside a
computed using the active locale) to avoid repeated construction.
- Around line 253-288: Several top-level helpers are defined as const arrow
functions (handleAuthorClick, setLinearModeInWorkflowData, loadWorkflowFromUrl)
but should be written as function declarations to match the codebase style;
convert each const arrow to a named function declaration (e.g., function
handleAuthorClick() { ... }, function setLinearModeInWorkflowData(workflowData)
{ ... }, function loadWorkflowFromUrl(options) { ... }), preserving the exact
parameter names, return behavior, and internal logic (including the
WorkflowPayload typing and emits) so callers and types remain unchanged.
- Around line 297-305: Add a new i18n key "discover.detail.fetchWorkflowFailed"
in src/locales/en/main.json (e.g. "Failed to load workflow (HTTP {status})"),
replace the hardcoded throw new Error(`Failed to fetch workflow:
${response.status}`) in the fetch block that calls fetch(workflow.workflow_url)
with an error that carries the status or set a variable to use in the catch, and
update the catch block that currently uses the makeCopyFailed key to instead
show the discover.detail.fetchWorkflowFailed message (injecting the HTTP status
or message via i18n interpolation). Ensure the code paths around
setLinearModeInWorkflowData and where the toast is shown use the new
discover.detail.fetchWorkflowFailed key instead of makeCopyFailed.

In `@src/components/discover/WorkflowPreviewCanvas.vue`:
- Around line 14-21: The template in WorkflowPreviewCanvas.vue is exposing raw
error.message to users; replace the raw error output with a localized,
user-friendly message using vue-i18n (e.g., use
$t('discover.detail.previewErrorDetails' or similar) instead of {{ error.message
}}), and move the technical detail to a non-UI log path (e.g.,
console.error(error) or emit a telemetry/log event from the component like
emit('logError', error)) so only localized strings appear in the template and
full error details are recorded separately.
- Around line 129-165: The loadWorkflow function must guard against stale
fetches and untrusted URLs: validate workflowUrl against your
whitelist/allowed-pattern before starting, then create an AbortController and a
monotonically-incremented requestId (stored outside the function) for each
fetch; store the controller and requestId as currentController/currentRequestId,
call currentController.abort() before a new fetch, pass controller.signal to
fetch, and after receiving the JSON check that the requestId matches
currentRequestId before calling graph.value.configure(data), updateCanvasSize()
and fitGraphToCanvas(); ensure fetch errors, AbortError, and invalid URLs set
error.value appropriately and that isLoading.value is cleared in finally.

In `@src/components/LiteGraphCanvasSplitterOverlay.vue`:
- Line 5: The visibility logic for the workflow-tabs is inconsistent; update the
v-if (or the computed property shouldShowWorkflowTabs) so the tab slot is only
shown when no full-page overlay is active by simplifying it to use
!isFullPageOverlayActive.value; locate the shouldShowWorkflowTabs implementation
(around the existing lines 182–184) or the <slot v-if="shouldShowWorkflowTabs"
name="workflow-tabs" /> usage and replace the condition with
!isFullPageOverlayActive.value (or make shouldShowWorkflowTabs return that) so
the home panel and any other full-page overlays hide the tabs.

In `@src/composables/discover/useWorkflowTemplateSearch.ts`:
- Around line 11-12: Replace the hardcoded ALGOLIA_APP_ID and ALGOLIA_SEARCH_KEY
constants with the Vite-injected globals used elsewhere: reference
__ALGOLIA_APP_ID__ and __ALGOLIA_API_KEY__ instead of literal strings so
credentials come from process.env via vite config; update occurrences of
ALGOLIA_APP_ID and ALGOLIA_SEARCH_KEY in useWorkflowTemplateSearch.ts to read
from those globals (consistent with algoliaSearchProvider.ts) and ensure names
match the Vite defines (__ALGOLIA_APP_ID__, __ALGOLIA_API_KEY__).
- Around line 1-2: The current imports use non-exported CDN/browser build paths
and will break bundling; replace them with the package entrypoints that export
the lite client and types — e.g., import the SearchResponse type from the
package and import liteClient from the library's lite entrypoint (replace the
existing imports referencing algoliasearch/dist/... with imports from
'algoliasearch' and 'algoliasearch/lite' so the symbols SearchResponse and
liteClient (used as algoliasearch in this file) resolve correctly).

In `@src/locales/en/main.json`:
- Around line 31-39: The locale entry author.rankValue is hardcoded to "31";
change it to a placeholder like "{rank}" (e.g., set author.rankValue to
"{rank}") and update the UI rendering code that reads discover.author.rankValue
to pass the actual numeric rank from the profile data (ensure the component or
template that renders author.rankValue injects the rank value into the
placeholder). Make sure the placeholder token matches the interpolation system
used by the app so the real rank displays for each author.

In `@src/stores/workspace/homePanelStore.ts`:
- Around line 7-17: The three store action arrow functions openPanel,
closePanel, and togglePanel should be converted to function declarations to
follow the repo convention for pure functions; replace "const openPanel = () =>
{ isOpen.value = true }", "const closePanel = () => { isOpen.value = false }"
and "const togglePanel = () => { isOpen.value = !isOpen.value }" with "function
openPanel() { ... }", "function closePanel() { ... }" and "function
togglePanel() { ... }" respectively, keeping the same bodies and exports so
callers and reactivity (isOpen.value) remain unchanged.

In `@src/utils/authorProfileUtil.ts`:
- Around line 22-36: Replace the custom helpers slugify, authorNameToSlug and
titleize with the corresponding es-toolkit utilities (confirm exact names in
es-toolkit) so slugs preserve word boundaries and repository conventions;
convert these arrow-assigned constants to pure function declarations (e.g.,
function slugify(...) { ... }) and update authorNameToSlug to call the
es-toolkit slug helper and then lookup KNOWN_AUTHORS[slug].slug fallback,
ensuring titleize uses the es-toolkit title/case helper instead of the local
regex-based implementation.

In `@src/views/AuthorProfileView.vue`:
- Around line 53-59: The two stub handlers handleRunWorkflow and handleMakeCopy
currently do nothing; implement handleRunWorkflow(workflow:
AlgoliaWorkflowTemplate) to call the existing workflow execution flow (e.g.,
invoke the API/store action that runs a workflow, passing workflow.id or the
workflow object), set/loading state, handle success/failure notifications and
refresh any relevant data or UI (close modals or navigate as needed); implement
handleMakeCopy(workflow: AlgoliaWorkflowTemplate) to create a duplicate of the
given workflow (clone fields, adjust name like "Copy of <name>" or append "
(copy)"), call the create-workflow API/store action, update the workflows list
or refetch, and show success/error feedback. Ensure both functions use proper
try/catch, update local reactive state, and reference AlgoliaWorkflowTemplate
fields (id, name, etc.) when performing API/store calls and notifications.
- Around line 40-51: Convert the three function expressions
handleAuthorWorkflowSelect, handleBack, and handleAuthorSelected into named
function declarations; keep their current parameter types and bodies intact
(preserve selectedWorkflow.value assignment, the void router.push calls, and the
authorNameToSlug usage), and ensure they remain exported/visible in the
component scope where they are used so existing references continue to work.

Comment on lines +12 to +18
<button
class="cursor-pointer rounded border-none bg-transparent p-0 text-muted-foreground transition-colors hover:text-base-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-secondary-foreground"
:aria-label="$t('g.close')"
@click="onCancel"
>
<i class="icon-[lucide--x] size-4" />
</button>
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

Replace icon-only <button>s with shared button components and add accessible labels.

Use the repo’s common button components for consistency, and ensure icon-only controls have an accessible name (and the toggle exposes role/checked state).

Based on learnings: In Vue files across the ComfyUI_frontend repo, when a button is needed, prefer the repo's common button components from src/components/button/ (IconButton.vue, TextButton.vue, IconTextButton.vue) over plain HTML elements; In the ComfyUI_frontend repository, do not add aria-label to buttons that have visible text content—use aria-label only for elements without visible labels.

Also applies to: 96-101, 123-140

🤖 Prompt for AI Agents
In `@src/components/actionbar/PublishToHubDialogContent.vue` around lines 12 - 18,
Replace the raw icon-only <button> here with the repo's shared icon button
component (src/components/button/IconButton.vue), wiring its click handler to
onCancel and passing a clear accessible name via the component prop (e.g., label
or ariaLabel) instead of an inline aria-label; do the same for the other
occurrences mentioned (lines 96-101, 123-140) to use
IconButton/TextButton/IconTextButton as appropriate, keeping visible text
buttons as plain text (no aria-label), and for any toggle controls ensure the
component exposes the appropriate role and checked/pressed state
(aria-checked/aria-pressed or role="switch") so assistive tech can read the
state.

Comment on lines +200 to +206
function handleThumbnailChange(event: Event) {
const input = event.target as HTMLInputElement
const file = input.files?.[0]
if (file) {
thumbnailFile.value = file
thumbnailPreview.value = URL.createObjectURL(file)
}
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

Revoke thumbnail object URLs to prevent leaks.

Each new preview URL should revoke the prior one, and cleanup should run on unmount.

🛠️ Suggested fix
-import { computed, ref } from 'vue'
+import { computed, onBeforeUnmount, ref } from 'vue'
@@
 function handleThumbnailChange(event: Event) {
   const input = event.target as HTMLInputElement
   const file = input.files?.[0]
   if (file) {
+    if (thumbnailPreview.value) {
+      URL.revokeObjectURL(thumbnailPreview.value)
+    }
     thumbnailFile.value = file
     thumbnailPreview.value = URL.createObjectURL(file)
   }
 }
+
+onBeforeUnmount(() => {
+  if (thumbnailPreview.value) {
+    URL.revokeObjectURL(thumbnailPreview.value)
+  }
+})
🤖 Prompt for AI Agents
In `@src/components/actionbar/PublishToHubDialogContent.vue` around lines 200 -
206, The thumbnail preview currently creates object URLs without revoking them;
update handleThumbnailChange to call URL.revokeObjectURL on the existing
thumbnailPreview.value (if set) before assigning a new URL, and ensure you
revoke the current URL in a component cleanup hook (onUnmounted or
onBeforeUnmount) to avoid memory leaks; modify references to thumbnailFile and
thumbnailPreview accordingly and add the revoke logic and cleanup using
URL.revokeObjectURL(thumbnailPreview.value).

Comment on lines +2 to +11
<Button
:variant="isOpen ? 'primary' : 'secondary'"
size="md"
class="px-3 text-sm font-semibold"
:aria-label="$t('discover.share.share')"
:aria-pressed="isOpen"
@click="handleClick"
>
{{ $t('discover.share.share') }}
</Button>
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.

🧹 Nitpick | 🔵 Trivial

Remove redundant aria-label from button with visible text.

The button already has visible text content ({{ $t('discover.share.share') }}), which provides the accessible name. Adding aria-label is redundant and could cause screen readers to announce the label twice.

♻️ Proposed fix
   <Button
     :variant="isOpen ? 'primary' : 'secondary'"
     size="md"
     class="px-3 text-sm font-semibold"
-    :aria-label="$t('discover.share.share')"
     :aria-pressed="isOpen"
     `@click`="handleClick"
   >

Based on learnings: "In the ComfyUI_frontend repository, for Vue components, do not add aria-label to buttons that have visible text content."

📝 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
<Button
:variant="isOpen ? 'primary' : 'secondary'"
size="md"
class="px-3 text-sm font-semibold"
:aria-label="$t('discover.share.share')"
:aria-pressed="isOpen"
@click="handleClick"
>
{{ $t('discover.share.share') }}
</Button>
<Button
:variant="isOpen ? 'primary' : 'secondary'"
size="md"
class="px-3 text-sm font-semibold"
:aria-pressed="isOpen"
`@click`="handleClick"
>
{{ $t('discover.share.share') }}
</Button>
🤖 Prompt for AI Agents
In `@src/components/actionbar/ShareButton.vue` around lines 2 - 11, The Button
element (the component with props variant, size, class, :aria-pressed and
`@click`="handleClick") has redundant :aria-label="{{ $t('discover.share.share')
}}" while also rendering visible text {{ $t('discover.share.share') }}; remove
the :aria-label attribute so the visible text becomes the accessible name, keep
:aria-pressed="isOpen" and the click handler handleClick unchanged, and ensure
no other accessibility attributes are added that duplicate the visible label.

Comment on lines +158 to +188
<Button
variant="secondary"
size="md"
class="w-full justify-between rounded-lg border border-border-default bg-secondary-background/90 px-4 py-3 text-sm font-semibold shadow-sm transition-colors hover:bg-secondary-background-hover"
:aria-label="$t('discover.share.publicUrl.copyLink')"
@click="copyToClipboard(shareUrl)"
>
<span class="min-w-0 truncate">
{{ $t('discover.share.publicUrl.copyLink') }}
</span>
<i
:class="
cn(
'size-3.5',
copied ? 'icon-[lucide--check]' : 'icon-[lucide--copy]'
)
"
/>
</Button>
<Button
variant="secondary"
size="md"
class="w-full justify-between rounded-lg border border-border-default bg-secondary-background/90 px-4 py-3 text-sm font-semibold shadow-sm transition-colors hover:bg-secondary-background-hover"
:aria-label="$t('discover.share.publicUrl.copyLinkAppMode')"
@click="copyToClipboard(appModeShareUrl)"
>
<span class="min-w-0 truncate">
{{ $t('discover.share.publicUrl.copyLinkAppMode') }}
</span>
<i class="icon-[lucide--copy] size-3.5" />
</Button>
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

Guard copy actions when no active workflow is available.

Right now shareUrl/appModeShareUrl fall back to the site origin, so users can copy a non-workflow link with no feedback. Consider disabling the buttons and showing a localized message when there’s no active workflow.

✅ Example guard + user feedback
+const hasActiveWorkflow = computed(() => !!workflowStore.activeWorkflow)
@@
-              <Button
+              <Button
                 variant="secondary"
                 size="md"
+                :disabled="!hasActiveWorkflow"
                 class="w-full justify-between rounded-lg border border-border-default bg-secondary-background/90 px-4 py-3 text-sm font-semibold shadow-sm transition-colors hover:bg-secondary-background-hover"
-                :aria-label="$t('discover.share.publicUrl.copyLink')"
                 `@click`="copyToClipboard(shareUrl)"
               >
@@
-              <Button
+              <Button
                 variant="secondary"
                 size="md"
+                :disabled="!hasActiveWorkflow"
                 class="w-full justify-between rounded-lg border border-border-default bg-secondary-background/90 px-4 py-3 text-sm font-semibold shadow-sm transition-colors hover:bg-secondary-background-hover"
-                :aria-label="$t('discover.share.publicUrl.copyLinkAppMode')"
                 `@click`="copyToClipboard(appModeShareUrl)"
               >
@@
 async function copyToClipboard(url: string) {
+  if (!hasActiveWorkflow.value) {
+    toastStore.addAlert(t('discover.share.noActiveWorkflow'))
+    return
+  }
   try {
     await navigator.clipboard.writeText(url)

As per coding guidelines: Provide user-friendly and actionable error messages.

Also applies to: 380-395, 437-451

🤖 Prompt for AI Agents
In `@src/components/actionbar/SharePanel.vue` around lines 158 - 188, The copy
buttons call copyToClipboard with shareUrl/appModeShareUrl even when no workflow
is active; prevent copying and provide feedback by disabling those Button
components (the ones invoking copyToClipboard) when the workflow is absent and
show a localized message or toast/tooltip; update the Button props for the two
places using copyToClipboard (the button that uses shareUrl and the one that
uses appModeShareUrl, and the other occurrences at the ranges mentioned) to bind
:disabled="!hasActiveWorkflow" (or a similarly named computed like
hasActiveWorkflow) and, inside copyToClipboard, early-return with a call to the
existing user-notification mechanism (e.g., showToast/localize
$t('discover.share.noActiveWorkflow')) so the function also guards against
direct calls.

⚠️ Potential issue | 🟡 Minor

Remove aria-label on buttons that already have visible text.

These buttons already include visible labels; the extra aria-label is redundant and can override the accessible name in some ATs.

🧹 Remove redundant aria-labels
-                :aria-label="$t('discover.share.publicUrl.copyLink')"
@@
-                :aria-label="$t('discover.share.publicUrl.copyLinkAppMode')"

Based on learnings: In the ComfyUI_frontend repository, for Vue components, do not add aria-label to buttons that have visible text content (e.g., buttons containing text).

📝 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
<Button
variant="secondary"
size="md"
class="w-full justify-between rounded-lg border border-border-default bg-secondary-background/90 px-4 py-3 text-sm font-semibold shadow-sm transition-colors hover:bg-secondary-background-hover"
:aria-label="$t('discover.share.publicUrl.copyLink')"
@click="copyToClipboard(shareUrl)"
>
<span class="min-w-0 truncate">
{{ $t('discover.share.publicUrl.copyLink') }}
</span>
<i
:class="
cn(
'size-3.5',
copied ? 'icon-[lucide--check]' : 'icon-[lucide--copy]'
)
"
/>
</Button>
<Button
variant="secondary"
size="md"
class="w-full justify-between rounded-lg border border-border-default bg-secondary-background/90 px-4 py-3 text-sm font-semibold shadow-sm transition-colors hover:bg-secondary-background-hover"
:aria-label="$t('discover.share.publicUrl.copyLinkAppMode')"
@click="copyToClipboard(appModeShareUrl)"
>
<span class="min-w-0 truncate">
{{ $t('discover.share.publicUrl.copyLinkAppMode') }}
</span>
<i class="icon-[lucide--copy] size-3.5" />
</Button>
<Button
variant="secondary"
size="md"
class="w-full justify-between rounded-lg border border-border-default bg-secondary-background/90 px-4 py-3 text-sm font-semibold shadow-sm transition-colors hover:bg-secondary-background-hover"
`@click`="copyToClipboard(shareUrl)"
>
<span class="min-w-0 truncate">
{{ $t('discover.share.publicUrl.copyLink') }}
</span>
<i
:class="
cn(
'size-3.5',
copied ? 'icon-[lucide--check]' : 'icon-[lucide--copy]'
)
"
/>
</Button>
<Button
variant="secondary"
size="md"
class="w-full justify-between rounded-lg border border-border-default bg-secondary-background/90 px-4 py-3 text-sm font-semibold shadow-sm transition-colors hover:bg-secondary-background-hover"
`@click`="copyToClipboard(appModeShareUrl)"
>
<span class="min-w-0 truncate">
{{ $t('discover.share.publicUrl.copyLinkAppMode') }}
</span>
<i class="icon-[lucide--copy] size-3.5" />
</Button>
🤖 Prompt for AI Agents
In `@src/components/actionbar/SharePanel.vue` around lines 158 - 188, Remove the
redundant aria-label attributes from the Button instances that already contain
visible text (the Button wrapping the span with {{
$t('discover.share.publicUrl.copyLink') }} and the Button wrapping {{
$t('discover.share.publicUrl.copyLinkAppMode') }}); locate the two Button
components that call copyToClipboard(shareUrl) and
copyToClipboard(appModeShareUrl) in SharePanel.vue and delete their :aria-label
bindings so the accessible name comes from the visible span text (leave other
props and the icon logic, e.g., the copied state and cn usage, unchanged).

Comment on lines +295 to +301
<button
class="cursor-pointer border-none bg-transparent p-0 text-muted-foreground hover:text-base-foreground"
:aria-label="$t('g.removeTag')"
@click="removeTag(tag)"
>
<i class="icon-[lucide--x] size-3" />
</button>
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.

🧹 Nitpick | 🔵 Trivial

Use the shared Button component for tag removal.

A raw <button> here bypasses the project’s standard focus/hover styling and a11y defaults. Reuse the existing Button component for consistency.

🔁 Suggested replacement using existing Button
-              <button
-                class="cursor-pointer border-none bg-transparent p-0 text-muted-foreground hover:text-base-foreground"
-                :aria-label="$t('g.removeTag')"
-                `@click`="removeTag(tag)"
-              >
-                <i class="icon-[lucide--x] size-3" />
-              </button>
+              <Button
+                variant="muted-textonly"
+                size="icon-sm"
+                class="text-muted-foreground hover:text-base-foreground"
+                :aria-label="$t('g.removeTag')"
+                `@click`="removeTag(tag)"
+              >
+                <i class="icon-[lucide--x] size-3" />
+              </Button>

Based on learnings: In Vue files across the ComfyUI_frontend repo, when a button is needed, prefer the repo's common button components from src/components/button/ (IconButton.vue, TextButton.vue, IconTextButton.vue) over plain HTML elements.

🤖 Prompt for AI Agents
In `@src/components/actionbar/SharePanel.vue` around lines 295 - 301, Replace the
raw <button> in SharePanel.vue with the repo's shared icon button component
(e.g., IconButton from src/components/button/IconButton.vue) so focus/hover/a11y
styles are preserved; keep the same :aria-label and `@click`="removeTag(tag)"
behavior, move the <i class="icon-[lucide--x] size-3" /> into the IconButton
slot or icon prop as used by your button component, and add the corresponding
import/registration (or ensure script setup has the component available) so
SharePanel uses IconButton instead of a plain button.

Comment on lines +31 to +39
"author": {
"title": "Creator",
"searchPlaceholder": "Search this creator's workflows",
"workflowsCount": "{count} workflow | {count} workflows",
"runsLabel": "runs",
"copiesLabel": "copies",
"rankLabel": "rank",
"rankValue": "31",
"rankCaption": "Last 30 days",
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

Make author rank value dynamic rather than hardcoded.

discover.author.rankValue is set to a fixed "31", which will display the same rank for every author. Consider using a placeholder (e.g., {rank}) and pass the actual value from the profile data.

💡 Suggested locale change
-      "rankValue": "31",
+      "rankValue": "{rank}",
📝 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
"author": {
"title": "Creator",
"searchPlaceholder": "Search this creator's workflows",
"workflowsCount": "{count} workflow | {count} workflows",
"runsLabel": "runs",
"copiesLabel": "copies",
"rankLabel": "rank",
"rankValue": "31",
"rankCaption": "Last 30 days",
"author": {
"title": "Creator",
"searchPlaceholder": "Search this creator's workflows",
"workflowsCount": "{count} workflow | {count} workflows",
"runsLabel": "runs",
"copiesLabel": "copies",
"rankLabel": "rank",
"rankValue": "{rank}",
"rankCaption": "Last 30 days",
🤖 Prompt for AI Agents
In `@src/locales/en/main.json` around lines 31 - 39, The locale entry
author.rankValue is hardcoded to "31"; change it to a placeholder like "{rank}"
(e.g., set author.rankValue to "{rank}") and update the UI rendering code that
reads discover.author.rankValue to pass the actual numeric rank from the profile
data (ensure the component or template that renders author.rankValue injects the
rank value into the placeholder). Make sure the placeholder token matches the
interpolation system used by the app so the real rank displays for each author.

Comment on lines +7 to +17
const openPanel = () => {
isOpen.value = true
}

const closePanel = () => {
isOpen.value = false
}

const togglePanel = () => {
isOpen.value = !isOpen.value
}
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.

🧹 Nitpick | 🔵 Trivial

Prefer function declarations for store actions.

Switch the action definitions to function declarations to match the repo’s TS convention for pure functions.

♻️ Suggested refactor
-  const openPanel = () => {
-    isOpen.value = true
-  }
+  function openPanel() {
+    isOpen.value = true
+  }

-  const closePanel = () => {
-    isOpen.value = false
-  }
+  function closePanel() {
+    isOpen.value = false
+  }

-  const togglePanel = () => {
-    isOpen.value = !isOpen.value
-  }
+  function togglePanel() {
+    isOpen.value = !isOpen.value
+  }

Based on learnings: Prefer pure function declarations over function expressions (e.g., use function foo() { ... } instead of const foo = () => { ... }) for pure functions in the repository.

📝 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
const openPanel = () => {
isOpen.value = true
}
const closePanel = () => {
isOpen.value = false
}
const togglePanel = () => {
isOpen.value = !isOpen.value
}
function openPanel() {
isOpen.value = true
}
function closePanel() {
isOpen.value = false
}
function togglePanel() {
isOpen.value = !isOpen.value
}
🤖 Prompt for AI Agents
In `@src/stores/workspace/homePanelStore.ts` around lines 7 - 17, The three store
action arrow functions openPanel, closePanel, and togglePanel should be
converted to function declarations to follow the repo convention for pure
functions; replace "const openPanel = () => { isOpen.value = true }", "const
closePanel = () => { isOpen.value = false }" and "const togglePanel = () => {
isOpen.value = !isOpen.value }" with "function openPanel() { ... }", "function
closePanel() { ... }" and "function togglePanel() { ... }" respectively, keeping
the same bodies and exports so callers and reactivity (isOpen.value) remain
unchanged.

Comment on lines +22 to +36
const slugify = (value: string) =>
value
.toLowerCase()
.replaceAll(/[^a-z0-9]+/g, '')
.trim()

export const authorNameToSlug = (name: string) =>
KNOWN_AUTHORS[slugify(name)]?.slug ?? slugify(name)

const titleize = (value: string) =>
value
.replaceAll(/[-_]+/g, ' ')
.replaceAll(/\s+/g, ' ')
.trim()
.replaceAll(/\b\w/g, (char) => char.toUpperCase())
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.

🧹 Nitpick | 🔵 Trivial

Use es-toolkit case helpers instead of custom slug/title logic.

This keeps slugs readable (word boundaries preserved) and aligns with the repo’s utility conventions. Please verify the helper names in es-toolkit and adjust accordingly.

♻️ Suggested refactor
+import { kebabCase, startCase } from 'es-toolkit'

-const slugify = (value: string) =>
-  value
-    .toLowerCase()
-    .replaceAll(/[^a-z0-9]+/g, '')
-    .trim()
+function slugify(value: string) {
+  return kebabCase(value)
+}

-const titleize = (value: string) =>
-  value
-    .replaceAll(/[-_]+/g, ' ')
-    .replaceAll(/\s+/g, ' ')
-    .trim()
-    .replaceAll(/\b\w/g, (char) => char.toUpperCase())
+function titleize(value: string) {
+  return startCase(value)
+}

Based on learnings and coding guidelines: Prefer pure function declarations over function expressions (e.g., use function foo() { ... } instead of const foo = () => { ... }) for pure functions in the repository; Use es-toolkit for utility functions.

🤖 Prompt for AI Agents
In `@src/utils/authorProfileUtil.ts` around lines 22 - 36, Replace the custom
helpers slugify, authorNameToSlug and titleize with the corresponding es-toolkit
utilities (confirm exact names in es-toolkit) so slugs preserve word boundaries
and repository conventions; convert these arrow-assigned constants to pure
function declarations (e.g., function slugify(...) { ... }) and update
authorNameToSlug to call the es-toolkit slug helper and then lookup
KNOWN_AUTHORS[slug].slug fallback, ensuring titleize uses the es-toolkit
title/case helper instead of the local regex-based implementation.

Comment on lines +40 to +51
const handleAuthorWorkflowSelect = (template: AlgoliaWorkflowTemplate) => {
selectedWorkflow.value = template
}

const handleBack = () => {
void router.push({ name: 'GraphView' })
}

const handleAuthorSelected = (author: { name: string; avatarUrl?: string }) => {
const slug = authorNameToSlug(author.name)
void router.push({ name: 'AuthorProfileView', params: { slug } })
}
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.

🧹 Nitpick | 🔵 Trivial

Prefer function declarations over function expressions.

Per coding guidelines, use function declarations instead of function expressions when possible.

♻️ Proposed fix
-const handleAuthorWorkflowSelect = (template: AlgoliaWorkflowTemplate) => {
+function handleAuthorWorkflowSelect(template: AlgoliaWorkflowTemplate) {
   selectedWorkflow.value = template
 }

-const handleBack = () => {
+function handleBack() {
   void router.push({ name: 'GraphView' })
 }

-const handleAuthorSelected = (author: { name: string; avatarUrl?: string }) => {
+function handleAuthorSelected(author: { name: string; avatarUrl?: string }) {
   const slug = authorNameToSlug(author.name)
   void router.push({ name: 'AuthorProfileView', params: { slug } })
 }

As per coding guidelines: "Do not use function expressions if it's possible to use function declarations instead."

📝 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
const handleAuthorWorkflowSelect = (template: AlgoliaWorkflowTemplate) => {
selectedWorkflow.value = template
}
const handleBack = () => {
void router.push({ name: 'GraphView' })
}
const handleAuthorSelected = (author: { name: string; avatarUrl?: string }) => {
const slug = authorNameToSlug(author.name)
void router.push({ name: 'AuthorProfileView', params: { slug } })
}
function handleAuthorWorkflowSelect(template: AlgoliaWorkflowTemplate) {
selectedWorkflow.value = template
}
function handleBack() {
void router.push({ name: 'GraphView' })
}
function handleAuthorSelected(author: { name: string; avatarUrl?: string }) {
const slug = authorNameToSlug(author.name)
void router.push({ name: 'AuthorProfileView', params: { slug } })
}
🤖 Prompt for AI Agents
In `@src/views/AuthorProfileView.vue` around lines 40 - 51, Convert the three
function expressions handleAuthorWorkflowSelect, handleBack, and
handleAuthorSelected into named function declarations; keep their current
parameter types and bodies intact (preserve selectedWorkflow.value assignment,
the void router.push calls, and the authorNameToSlug usage), and ensure they
remain exported/visible in the component scope where they are used so existing
references continue to work.

Comment on lines +53 to +59
const handleRunWorkflow = (_workflow: AlgoliaWorkflowTemplate) => {
// TODO: Implement workflow run
}

const handleMakeCopy = (_workflow: AlgoliaWorkflowTemplate) => {
// TODO: Implement make a copy
}
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.

🧹 Nitpick | 🔵 Trivial

Stub handlers need implementation.

These TODO handlers are placeholders. The underscore-prefixed parameters indicate intentionally unused variables, which is acceptable for stubs.

Would you like me to help implement these workflow actions, or should I open issues to track them?

🤖 Prompt for AI Agents
In `@src/views/AuthorProfileView.vue` around lines 53 - 59, The two stub handlers
handleRunWorkflow and handleMakeCopy currently do nothing; implement
handleRunWorkflow(workflow: AlgoliaWorkflowTemplate) to call the existing
workflow execution flow (e.g., invoke the API/store action that runs a workflow,
passing workflow.id or the workflow object), set/loading state, handle
success/failure notifications and refresh any relevant data or UI (close modals
or navigate as needed); implement handleMakeCopy(workflow:
AlgoliaWorkflowTemplate) to create a duplicate of the given workflow (clone
fields, adjust name like "Copy of <name>" or append " (copy)"), call the
create-workflow API/store action, update the workflows list or refetch, and show
success/error feedback. Ensure both functions use proper try/catch, update local
reactive state, and reference AlgoliaWorkflowTemplate fields (id, name, etc.)
when performing API/store calls and notifications.

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

Caution

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

⚠️ Outside diff range comments (1)
src/stores/workspace/sidebarTabStore.ts (1)

53-67: ⚠️ Potential issue | 🟡 Minor

Add "discover" entry to menubarLabelKeys mapping.

The discover tab is registered but lacks a corresponding entry in menubarLabelKeys, causing the menubar label to display the i18n key string 'sideToolbar.discover' instead of the translated value. Add the missing mapping:

Suggested fix
 const menubarLabelKeys: Record<string, string> = {
   'node-library': 'sideToolbar.nodeLibrary',
   'model-library': 'sideToolbar.modelLibrary',
   workflows: 'sideToolbar.workflows',
-  assets: 'sideToolbar.assets'
+  assets: 'sideToolbar.assets',
+  discover: 'sideToolbar.discover'
 }
🤖 Fix all issues with AI agents
In `@src/components/actionbar/PublishToHubDialogContent.vue`:
- Around line 12-18: Replace raw <button> elements (the close button invoking
onCancel and the tag removal buttons that call the tag removal handler) with the
repo's shared Button component to keep styling and behavior consistent; import
the Button component at the top of PublishToHubDialogContent.vue, use it for the
icon-only close control by passing the aria-label="$t('g.close')" and
`@click`="onCancel" (and place the <i class="icon-[lucide--x] size-4" /> as the
button content), and similarly swap the tag removal <button> elements to Button
while forwarding the removal handler, aria-labels, and icon content so
accessibility and theming are preserved.
- Around line 200-207: The current handleThumbnailChange uses
URL.createObjectURL without revoking previous URLs; update handleThumbnailChange
to revoke any existing thumbnailPreview.value (if set) via URL.revokeObjectURL
before assigning a new URL, and also add a component cleanup (e.g.,
beforeUnmount or onBeforeUnmount) to revoke the last thumbnailPreview.value when
the component is destroyed; refer to handleThumbnailChange, thumbnailPreview and
thumbnailFile when making these changes.

In `@src/components/actionbar/ShareButton.vue`:
- Around line 2-11: The Button in ShareButton.vue is using a redundant
aria-label while also rendering visible text via {{ $t('discover.share.share')
}}; remove the :aria-label="$t('discover.share.share')" attribute from the
<Button> element so the visible content provides the accessible name, keeping
other attributes like :aria-pressed="isOpen", :variant, size, class and the
`@click`="handleClick" intact.

In `@src/components/actionbar/SharePanel.vue`:
- Around line 295-301: Replace the plain <button> used for tag removal in
SharePanel.vue with the project's Button component for consistency; locate the
element currently calling `@click`="removeTag(tag)" and using
:aria-label="$t('g.removeTag')" and swap it to use <Button> (preserving the
click handler removeTag(tag), aria-label, and visual classes/props such as icon
sizing and muted styling) so it follows the repository's Button API and visual
patterns.
- Around line 458-464: The handleThumbnailChange function creates an object URL
and assigns it to thumbnailPreview.value but never revokes previous URLs; update
handleThumbnailChange to revoke any existing object URL stored in
thumbnailPreview.value before assigning a new one, and add a lifecycle cleanup
(onUnmounted) that revokes the current thumbnailPreview.value if present;
reference the handleThumbnailChange function and thumbnailPreview reactive ref
to locate where to revoke URLs.

In `@src/components/discover/AuthorProfileView.vue`:
- Around line 308-313: The computed values formattedRuns and formattedCopies
currently treat 0 as falsy because they use truthy checks (stats?.runs ? ...),
causing zeros to render as "--"; change the checks to nullish checks so
legitimate zero values are preserved (e.g., use stats?.runs ?? null or check
stats?.runs != null) and format with toLocaleString only when the value is not
null/undefined; update both formattedRuns and formattedCopies to use the same
nullish logic referencing stats, formattedRuns, and formattedCopies.
- Around line 197-202: The conditional Tailwind class on the LazyImage should be
merged using the cn() helper instead of a separate :class binding; update the
LazyImage props to combine static and conditional classes via cn('size-full
rounded-lg object-cover transition-transform duration-300', hoveredTemplate ===
template.objectID && 'scale-105') (or the object form) and ensure cn is imported
where used; modify the component around the LazyImage tag (referencing
LazyImage, hoveredTemplate and template.objectID) to replace the existing class
+ :class pair with a single :class using cn().

In `@src/components/discover/DiscoverView.vue`:
- Around line 153-158: Replace the conditional Tailwind class array binding with
the cn() utility: import cn from '@/utils/tailwindUtil' in the DiscoverView
component, then change the LazyImage class binding to use :class="cn('size-full
rounded-lg object-cover transition-transform duration-300', hoveredTemplate ===
template.objectID && 'scale-105')" so the static classes and the conditional
'scale-105' are merged via cn() instead of using :class="[]".
- Around line 177-194: Replace the raw <button> block with the shared Button
component (Button) and set variant="muted-textonly", forwarding the existing
class string to preserve styling; keep the `@click.stop` handler calling
handleAuthorClick(template), keep the img using template.author_avatar_url
(falling back to the existing asset) and :alt bound to template.author_name, and
preserve inner span showing template.author_name—ensure props/events on Button
match the original semantics (click stopPropagation) and that the class prop
carries the original utility classes.
- Around line 124-132: The empty-state condition in DiscoverView.vue
short-circuits because of the `!cloudOnly ||` prefix; remove that prefix so the
v-else-if only checks the actual results (e.g., `results &&
results.templates.length === 0`) instead of always showing the empty state when
`cloudOnly` is false. Then update the search filter builder in
`buildFacetFilters()` to include a cloud-only facet when `cloudOnly` is true
(add the appropriate facet filter entry tied to your Algolia attribute, e.g.,
`"isCloudOnly:true"` or the correct attribute name), and verify the exact
Algolia attribute name for cloud-only templates before committing the filter
change. Ensure toggling `cloudOnly` triggers a new search with the updated
filters so the UI reflects the filter change.
- Around line 236-254: Add accessible names to the icon-only pagination buttons
by setting aria-label attributes on the Button components (the left and right
Button elements that call goToPage(currentPage - 1) and goToPage(currentPage +
1)); use the existing translation key g.next for the right button and add a new
translation key g.previous (value "Previous") in the locales JSON so the left
button can use that key for its aria-label (or alternatively use an existing key
like g.back if preferred). Ensure the aria-label binding uses the same i18n
method used elsewhere in the component so screen readers receive the localized
label.

In `@src/components/discover/WorkflowDetailView.vue`:
- Around line 253-259: Convert the arrow function expressions handleAuthorClick,
setLinearModeInWorkflowData, and loadWorkflowFromUrl into function declarations:
replace "const handleAuthorClick = () => { ... }" with "function
handleAuthorClick() { ... }" (and similarly for setLinearModeInWorkflowData and
loadWorkflowFromUrl), keep the same body and references to workflow, emit, and
any captured variables, and ensure they remain exported/accessible the same way
(remove const bindings and retain existing usage sites and event emits).
- Around line 230-232: The computed properties runCount, viewCount, and
copyCount in WorkflowDetailView.vue currently use misleading hardcoded fallbacks
(1_234, 5_678, 890); change these to safe fallbacks such as 0 (e.g.,
workflow.run_count ?? 0) or return null/undefined so the template can render a
placeholder like "—", and update any consuming template logic to display 0 or
the placeholder accordingly.

In `@src/components/discover/WorkflowPreviewCanvas.vue`:
- Around line 185-187: The LGraph instance isn't being cleared on component
unmount; update the onBeforeUnmount handler to call graph.value?.clear()
(referring to the graph ref/LGraph instance) to release node references before
stopping the canvas rendering, e.g. invoke graph.value?.clear() and then
canvas.value?.stopRendering() (and optionally null out refs) to ensure proper
cleanup.

In `@src/components/home/HomePanel.vue`:
- Around line 11-38: Replace the plain <button> elements in HomePanel.vue with
the repository's shared button components (prefer IconTextButton.vue or
TextButton.vue from src/components/button/) and bind the active state to them
using the existing activeTab reactive value; ensure clicks still set activeTab
('recents' / 'discover') and propagate class/style props to preserve the current
styling logic (cn(...) conditions). Add accessibility attributes on the shared
components: either aria-pressed="activeTab === 'recents'| 'discover'" or convert
the buttons into tab roles with role="tab" and aria-selected="activeTab ===
...", and ensure the icon slots use the same lucide icons (icon-[lucide--clock],
icon-[lucide--compass]). Update any prop names (e.g., :active or :pressed) on
IconTextButton/TextButton as required so the component exposes the active state
for styling and ARIA.

In `@src/components/topbar/WorkflowTabs.vue`:
- Around line 207-214: The toggleHomePanel function currently clears
sidebarTabStore.activeSidebarTabId and canvasStore.linearMode when opening but
never restores them on close; modify toggleHomePanel (or move logic into
homePanelStore) to cache the previous values (e.g., prevSidebarTabId and
prevLinearMode) when calling homePanelStore.openPanel(), and when detecting
homePanelStore.isOpen on toggle (i.e., when calling
homePanelStore.closePanel()), restore sidebarTabStore.activeSidebarTabId =
prevSidebarTabId and canvasStore.linearMode = prevLinearMode (or store/restore
these values inside homePanelStore.openPanel/closePanel methods) so the prior UI
state is preserved.

In `@src/composables/discover/useWorkflowTemplateSearch.ts`:
- Around line 67-68: The code blindly asserts response.results[0] into
searchResponse (typed as SearchResponse<AlgoliaWorkflowTemplate>), which will
throw if results is empty; add a defensive check for response.results and its
length before the assertion (e.g., if response.results == null ||
response.results.length === 0) and handle the empty case (return early, set
searchResponse to undefined/null, or provide a safe default) so downstream logic
that uses searchResponse won't crash.
- Around line 11-13: The code currently hardcodes Algolia credentials
(ALGOLIA_APP_ID, ALGOLIA_SEARCH_KEY, INDEX_NAME); change this to read
ALGOLIA_APP_ID, ALGOLIA_SEARCH_KEY and INDEX_NAME from environment variables
(e.g., process.env) and add a startup check to throw/abort if any are missing,
and update any usages of INDEX_NAME accordingly; additionally, restrict the
Algolia search key in the Algolia dashboard to ACL limited to the
workflow_templates index and apply rate/IP limits, and for production move to
issuing short‑lived secured API keys server‑side (use server endpoint to sign
keys) rather than embedding any key in client bundles.

In `@src/locales/en/main.json`:
- Around line 37-39: Remove the hardcoded numeric value from the translation
entry "rankValue" (it's dynamic data), replace it with a translatable
placeholder (e.g., a token such as "{{rank}}" or just remove the key and keep
"rankLabel"/"rankCaption"), and update the component that displays the author
rank to pass the runtime value into the translation or render it next to
$t('rankLabel'); if this was meant as a temporary placeholder, add a TODO
comment clarifying that "rankValue" is a stub and must be replaced by runtime
data (refer to the keys "rankValue", "rankLabel", and "rankCaption" to locate
the entries to change).

In `@src/stores/workspace/homePanelStore.ts`:
- Around line 7-17: Convert the arrow function actions to function declarations:
replace the const arrow functions openPanel, closePanel, and togglePanel with
declared functions (function openPanel() { ... }, function closePanel() { ... },
function togglePanel() { ... }) so the store follows the project convention used
in sharePanelStore.ts; ensure their bodies and references remain unchanged and
exported behavior is preserved.

In `@src/utils/authorProfileUtil.ts`:
- Around line 22-29: Convert the slugify arrow function into a named function
declaration and remove the redundant .trim() (since the regex strips
non-alphanumerics and whitespace); update usages accordingly. Specifically,
replace the const slugify = (...) => ... with function slugify(value: string) {
return value.toLowerCase().replaceAll(/[^a-z0-9]+/g, ''); } and ensure
authorNameToSlug continues to reference slugify(name) and falls back to
KNOWN_AUTHORS[slugify(name)]?.slug ?? slugify(name).
- Around line 31-45: Convert the remaining arrow functions to function
declarations: replace the const titleize = (...) => ... with a function
titleize(value: string): string { ... } and replace the exported const
authorSlugToProfile = (slug: string): AuthorProfile => { ... } with an exported
function export function authorSlugToProfile(slug: string): AuthorProfile { ...
}; preserve all existing logic (slugify call, KNOWN_AUTHORS lookup, returned
object shape) and keep types/signatures identical so behavior and exports remain
unchanged.

In `@src/views/AuthorProfileView.vue`:
- Around line 53-59: The two no-op handlers handleRunWorkflow and handleMakeCopy
must be implemented or the WorkflowDetailView actions hidden; replace the TODOs
by wiring handleRunWorkflow to the existing run workflow service (e.g., call
runWorkflowService.run(workflow) or equivalent, set local loading state, handle
success/error and show user feedback) and wire handleMakeCopy to the existing
copy service (e.g., workflowCopyService.copy(workflow) or equivalent) with
similar loading/error handling; if those services are not available yet, update
WorkflowDetailView to conditionally hide the Run/Make Copy buttons until
handleRunWorkflow and handleMakeCopy are implemented.
- Around line 40-58: Convert the arrow function handlers to named function
declarations: replace the const arrow definitions for
handleAuthorWorkflowSelect, handleBack, handleAuthorSelected, handleRunWorkflow,
and handleMakeCopy with equivalent function declarations (e.g., function
handleAuthorWorkflowSelect(template: AlgoliaWorkflowTemplate) { ... })
preserving current logic, returned values, and usage of router/selectedWorkflow;
ensure TypeScript signatures remain the same and any references to these symbols
in the component template or other code are unchanged.

Comment on lines +12 to +18
<button
class="cursor-pointer rounded border-none bg-transparent p-0 text-muted-foreground transition-colors hover:text-base-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-secondary-foreground"
:aria-label="$t('g.close')"
@click="onCancel"
>
<i class="icon-[lucide--x] size-4" />
</button>
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.

🧹 Nitpick | 🔵 Trivial

Consider using the shared Button component for icon-only buttons.

The close button (lines 12-18) and tag removal buttons (lines 96-101) use plain <button> elements. Per repository patterns, prefer the common Button components for consistency and theming.

♻️ Example refactor for close button
-      <button
-        class="cursor-pointer rounded border-none bg-transparent p-0 text-muted-foreground transition-colors hover:text-base-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-secondary-foreground"
-        :aria-label="$t('g.close')"
-        `@click`="onCancel"
-      >
-        <i class="icon-[lucide--x] size-4" />
-      </button>
+      <Button
+        variant="muted-textonly"
+        size="icon-sm"
+        :aria-label="$t('g.close')"
+        `@click`="onCancel"
+      >
+        <i class="icon-[lucide--x] size-4" />
+      </Button>

Based on learnings: "In Vue files across the ComfyUI_frontend repo, when a button is needed, prefer the repo's common button components."

Also applies to: 96-101

🤖 Prompt for AI Agents
In `@src/components/actionbar/PublishToHubDialogContent.vue` around lines 12 - 18,
Replace raw <button> elements (the close button invoking onCancel and the tag
removal buttons that call the tag removal handler) with the repo's shared Button
component to keep styling and behavior consistent; import the Button component
at the top of PublishToHubDialogContent.vue, use it for the icon-only close
control by passing the aria-label="$t('g.close')" and `@click`="onCancel" (and
place the <i class="icon-[lucide--x] size-4" /> as the button content), and
similarly swap the tag removal <button> elements to Button while forwarding the
removal handler, aria-labels, and icon content so accessibility and theming are
preserved.

Comment on lines +200 to +207
function handleThumbnailChange(event: Event) {
const input = event.target as HTMLInputElement
const file = input.files?.[0]
if (file) {
thumbnailFile.value = file
thumbnailPreview.value = URL.createObjectURL(file)
}
}
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

Memory leak: Object URL is never revoked.

URL.createObjectURL allocates memory that persists until the document is unloaded or explicitly revoked. When the thumbnail changes or the component unmounts, the previous URL should be revoked.

🛡️ Proposed fix to revoke object URL
+import { onBeforeUnmount, computed, ref, watch } from 'vue'
-import { computed, ref } from 'vue'

 const thumbnailPreview = ref('')

+onBeforeUnmount(() => {
+  if (thumbnailPreview.value) {
+    URL.revokeObjectURL(thumbnailPreview.value)
+  }
+})

 function handleThumbnailChange(event: Event) {
   const input = event.target as HTMLInputElement
   const file = input.files?.[0]
   if (file) {
     thumbnailFile.value = file
+    if (thumbnailPreview.value) {
+      URL.revokeObjectURL(thumbnailPreview.value)
+    }
     thumbnailPreview.value = URL.createObjectURL(file)
   }
 }
🤖 Prompt for AI Agents
In `@src/components/actionbar/PublishToHubDialogContent.vue` around lines 200 -
207, The current handleThumbnailChange uses URL.createObjectURL without revoking
previous URLs; update handleThumbnailChange to revoke any existing
thumbnailPreview.value (if set) via URL.revokeObjectURL before assigning a new
URL, and also add a component cleanup (e.g., beforeUnmount or onBeforeUnmount)
to revoke the last thumbnailPreview.value when the component is destroyed; refer
to handleThumbnailChange, thumbnailPreview and thumbnailFile when making these
changes.

Comment on lines +2 to +11
<Button
:variant="isOpen ? 'primary' : 'secondary'"
size="md"
class="px-3 text-sm font-semibold"
:aria-label="$t('discover.share.share')"
:aria-pressed="isOpen"
@click="handleClick"
>
{{ $t('discover.share.share') }}
</Button>
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.

🧹 Nitpick | 🔵 Trivial

Remove redundant aria-label from button with visible text.

The button already has visible text content ({{ $t('discover.share.share') }}), which provides the accessible name. The aria-label duplicates this and is unnecessary.

♻️ Proposed fix
   <Button
     :variant="isOpen ? 'primary' : 'secondary'"
     size="md"
     class="px-3 text-sm font-semibold"
-    :aria-label="$t('discover.share.share')"
     :aria-pressed="isOpen"
     `@click`="handleClick"
   >

Based on learnings: "Do not add aria-label to buttons that have visible text content. The visible text provides the accessible name."

📝 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
<Button
:variant="isOpen ? 'primary' : 'secondary'"
size="md"
class="px-3 text-sm font-semibold"
:aria-label="$t('discover.share.share')"
:aria-pressed="isOpen"
@click="handleClick"
>
{{ $t('discover.share.share') }}
</Button>
<Button
:variant="isOpen ? 'primary' : 'secondary'"
size="md"
class="px-3 text-sm font-semibold"
:aria-pressed="isOpen"
`@click`="handleClick"
>
{{ $t('discover.share.share') }}
</Button>
🤖 Prompt for AI Agents
In `@src/components/actionbar/ShareButton.vue` around lines 2 - 11, The Button in
ShareButton.vue is using a redundant aria-label while also rendering visible
text via {{ $t('discover.share.share') }}; remove the
:aria-label="$t('discover.share.share')" attribute from the <Button> element so
the visible content provides the accessible name, keeping other attributes like
:aria-pressed="isOpen", :variant, size, class and the `@click`="handleClick"
intact.

Comment on lines +295 to +301
<button
class="cursor-pointer border-none bg-transparent p-0 text-muted-foreground hover:text-base-foreground"
:aria-label="$t('g.removeTag')"
@click="removeTag(tag)"
>
<i class="icon-[lucide--x] size-3" />
</button>
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.

🧹 Nitpick | 🔵 Trivial

Consider using Button component for tag removal.

The tag removal button uses a plain <button> element. For consistency with the rest of the panel and repository patterns, consider using the Button component.

🤖 Prompt for AI Agents
In `@src/components/actionbar/SharePanel.vue` around lines 295 - 301, Replace the
plain <button> used for tag removal in SharePanel.vue with the project's Button
component for consistency; locate the element currently calling
`@click`="removeTag(tag)" and using :aria-label="$t('g.removeTag')" and swap it to
use <Button> (preserving the click handler removeTag(tag), aria-label, and
visual classes/props such as icon sizing and muted styling) so it follows the
repository's Button API and visual patterns.

Comment on lines +458 to +464
function handleThumbnailChange(event: Event) {
const input = event.target as HTMLInputElement
const file = input.files?.[0]
if (file) {
thumbnailPreview.value = URL.createObjectURL(file)
}
}
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

Memory leak: Object URL is never revoked.

Same issue as in PublishToHubDialogContent.vue - the thumbnail preview object URL should be revoked when changed or on unmount.

🛡️ Proposed fix
+import { computed, ref, onBeforeUnmount } from 'vue'
-import { computed, ref } from 'vue'

 const thumbnailPreview = ref('')

+onBeforeUnmount(() => {
+  if (thumbnailPreview.value) {
+    URL.revokeObjectURL(thumbnailPreview.value)
+  }
+})

 function handleThumbnailChange(event: Event) {
   const input = event.target as HTMLInputElement
   const file = input.files?.[0]
   if (file) {
+    if (thumbnailPreview.value) {
+      URL.revokeObjectURL(thumbnailPreview.value)
+    }
     thumbnailPreview.value = URL.createObjectURL(file)
   }
 }
🤖 Prompt for AI Agents
In `@src/components/actionbar/SharePanel.vue` around lines 458 - 464, The
handleThumbnailChange function creates an object URL and assigns it to
thumbnailPreview.value but never revokes previous URLs; update
handleThumbnailChange to revoke any existing object URL stored in
thumbnailPreview.value before assigning a new one, and add a lifecycle cleanup
(onUnmounted) that revokes the current thumbnailPreview.value if present;
reference the handleThumbnailChange function and thumbnailPreview reactive ref
to locate where to revoke URLs.

Comment on lines +7 to +17
const openPanel = () => {
isOpen.value = true
}

const closePanel = () => {
isOpen.value = false
}

const togglePanel = () => {
isOpen.value = !isOpen.value
}
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.

🧹 Nitpick | 🔵 Trivial

Prefer function declarations over function expressions.

The store uses arrow functions for actions, but the codebase convention prefers function declarations for pure functions. This is inconsistent with sharePanelStore.ts which uses function declarations.

♻️ Proposed refactor to use function declarations
-  const openPanel = () => {
-    isOpen.value = true
-  }
+  function openPanel() {
+    isOpen.value = true
+  }

-  const closePanel = () => {
-    isOpen.value = false
-  }
+  function closePanel() {
+    isOpen.value = false
+  }

-  const togglePanel = () => {
-    isOpen.value = !isOpen.value
-  }
+  function togglePanel() {
+    isOpen.value = !isOpen.value
+  }

Based on learnings: "Prefer pure function declarations over function expressions (e.g., use function foo() { ... } instead of const foo = () => { ... }) for pure functions in the repository."

📝 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
const openPanel = () => {
isOpen.value = true
}
const closePanel = () => {
isOpen.value = false
}
const togglePanel = () => {
isOpen.value = !isOpen.value
}
function openPanel() {
isOpen.value = true
}
function closePanel() {
isOpen.value = false
}
function togglePanel() {
isOpen.value = !isOpen.value
}
🤖 Prompt for AI Agents
In `@src/stores/workspace/homePanelStore.ts` around lines 7 - 17, Convert the
arrow function actions to function declarations: replace the const arrow
functions openPanel, closePanel, and togglePanel with declared functions
(function openPanel() { ... }, function closePanel() { ... }, function
togglePanel() { ... }) so the store follows the project convention used in
sharePanelStore.ts; ensure their bodies and references remain unchanged and
exported behavior is preserved.

Comment on lines +22 to +29
const slugify = (value: string) =>
value
.toLowerCase()
.replaceAll(/[^a-z0-9]+/g, '')
.trim()

export const authorNameToSlug = (name: string) =>
KNOWN_AUTHORS[slugify(name)]?.slug ?? slugify(name)
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.

🧹 Nitpick | 🔵 Trivial

Prefer function declarations and simplify slugify.

  1. Arrow functions should be function declarations per codebase convention.
  2. The .trim() in slugify is redundant since the regex already removes all non-alphanumeric characters including whitespace.
♻️ Proposed refactor
-const slugify = (value: string) =>
-  value
-    .toLowerCase()
-    .replaceAll(/[^a-z0-9]+/g, '')
-    .trim()
+function slugify(value: string) {
+  return value.toLowerCase().replaceAll(/[^a-z0-9]+/g, '')
+}

-export const authorNameToSlug = (name: string) =>
-  KNOWN_AUTHORS[slugify(name)]?.slug ?? slugify(name)
+export function authorNameToSlug(name: string) {
+  return KNOWN_AUTHORS[slugify(name)]?.slug ?? slugify(name)
+}

Based on learnings: "Prefer pure function declarations over function expressions."

📝 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
const slugify = (value: string) =>
value
.toLowerCase()
.replaceAll(/[^a-z0-9]+/g, '')
.trim()
export const authorNameToSlug = (name: string) =>
KNOWN_AUTHORS[slugify(name)]?.slug ?? slugify(name)
function slugify(value: string) {
return value.toLowerCase().replaceAll(/[^a-z0-9]+/g, '')
}
export function authorNameToSlug(name: string) {
return KNOWN_AUTHORS[slugify(name)]?.slug ?? slugify(name)
}
🤖 Prompt for AI Agents
In `@src/utils/authorProfileUtil.ts` around lines 22 - 29, Convert the slugify
arrow function into a named function declaration and remove the redundant
.trim() (since the regex strips non-alphanumerics and whitespace); update usages
accordingly. Specifically, replace the const slugify = (...) => ... with
function slugify(value: string) { return
value.toLowerCase().replaceAll(/[^a-z0-9]+/g, ''); } and ensure authorNameToSlug
continues to reference slugify(name) and falls back to
KNOWN_AUTHORS[slugify(name)]?.slug ?? slugify(name).

Comment on lines +31 to +45
const titleize = (value: string) =>
value
.replaceAll(/[-_]+/g, ' ')
.replaceAll(/\s+/g, ' ')
.trim()
.replaceAll(/\b\w/g, (char) => char.toUpperCase())

export const authorSlugToProfile = (slug: string): AuthorProfile => {
const normalized = slugify(slug)
return (
KNOWN_AUTHORS[normalized] ?? {
slug: normalized,
name: titleize(slug)
}
)
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.

🧹 Nitpick | 🔵 Trivial

Refactor remaining functions to use function declarations.

♻️ Proposed refactor
-const titleize = (value: string) =>
-  value
-    .replaceAll(/[-_]+/g, ' ')
-    .replaceAll(/\s+/g, ' ')
-    .trim()
-    .replaceAll(/\b\w/g, (char) => char.toUpperCase())
+function titleize(value: string) {
+  return value
+    .replaceAll(/[-_]+/g, ' ')
+    .replaceAll(/\s+/g, ' ')
+    .trim()
+    .replaceAll(/\b\w/g, (char) => char.toUpperCase())
+}

-export const authorSlugToProfile = (slug: string): AuthorProfile => {
-  const normalized = slugify(slug)
-  return (
-    KNOWN_AUTHORS[normalized] ?? {
-      slug: normalized,
-      name: titleize(slug)
-    }
-  )
-}
+export function authorSlugToProfile(slug: string): AuthorProfile {
+  const normalized = slugify(slug)
+  return (
+    KNOWN_AUTHORS[normalized] ?? {
+      slug: normalized,
+      name: titleize(slug)
+    }
+  )
+}
📝 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
const titleize = (value: string) =>
value
.replaceAll(/[-_]+/g, ' ')
.replaceAll(/\s+/g, ' ')
.trim()
.replaceAll(/\b\w/g, (char) => char.toUpperCase())
export const authorSlugToProfile = (slug: string): AuthorProfile => {
const normalized = slugify(slug)
return (
KNOWN_AUTHORS[normalized] ?? {
slug: normalized,
name: titleize(slug)
}
)
function titleize(value: string) {
return value
.replaceAll(/[-_]+/g, ' ')
.replaceAll(/\s+/g, ' ')
.trim()
.replaceAll(/\b\w/g, (char) => char.toUpperCase())
}
export function authorSlugToProfile(slug: string): AuthorProfile {
const normalized = slugify(slug)
return (
KNOWN_AUTHORS[normalized] ?? {
slug: normalized,
name: titleize(slug)
}
)
}
🤖 Prompt for AI Agents
In `@src/utils/authorProfileUtil.ts` around lines 31 - 45, Convert the remaining
arrow functions to function declarations: replace the const titleize = (...) =>
... with a function titleize(value: string): string { ... } and replace the
exported const authorSlugToProfile = (slug: string): AuthorProfile => { ... }
with an exported function export function authorSlugToProfile(slug: string):
AuthorProfile { ... }; preserve all existing logic (slugify call, KNOWN_AUTHORS
lookup, returned object shape) and keep types/signatures identical so behavior
and exports remain unchanged.

Comment on lines +40 to +58
const handleAuthorWorkflowSelect = (template: AlgoliaWorkflowTemplate) => {
selectedWorkflow.value = template
}

const handleBack = () => {
void router.push({ name: 'GraphView' })
}

const handleAuthorSelected = (author: { name: string; avatarUrl?: string }) => {
const slug = authorNameToSlug(author.name)
void router.push({ name: 'AuthorProfileView', params: { slug } })
}

const handleRunWorkflow = (_workflow: AlgoliaWorkflowTemplate) => {
// TODO: Implement workflow run
}

const handleMakeCopy = (_workflow: AlgoliaWorkflowTemplate) => {
// TODO: Implement make a copy
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.

🧹 Nitpick | 🔵 Trivial

Prefer function declarations for view handlers.

These handlers are const arrow functions in Lines 40-58; repo style prefers function declarations.

♻️ Suggested fix
-const handleAuthorWorkflowSelect = (template: AlgoliaWorkflowTemplate) => {
-  selectedWorkflow.value = template
-}
+function handleAuthorWorkflowSelect(template: AlgoliaWorkflowTemplate) {
+  selectedWorkflow.value = template
+}
@@
-const handleBack = () => {
-  void router.push({ name: 'GraphView' })
-}
+function handleBack() {
+  void router.push({ name: 'GraphView' })
+}
@@
-const handleAuthorSelected = (author: { name: string; avatarUrl?: string }) => {
-  const slug = authorNameToSlug(author.name)
-  void router.push({ name: 'AuthorProfileView', params: { slug } })
-}
+function handleAuthorSelected(author: { name: string; avatarUrl?: string }) {
+  const slug = authorNameToSlug(author.name)
+  void router.push({ name: 'AuthorProfileView', params: { slug } })
+}
@@
-const handleRunWorkflow = (_workflow: AlgoliaWorkflowTemplate) => {
-  // TODO: Implement workflow run
-}
+function handleRunWorkflow(_workflow: AlgoliaWorkflowTemplate) {
+  // TODO: Implement workflow run
+}
@@
-const handleMakeCopy = (_workflow: AlgoliaWorkflowTemplate) => {
-  // TODO: Implement make a copy
-}
+function handleMakeCopy(_workflow: AlgoliaWorkflowTemplate) {
+  // TODO: Implement make a copy
+}
As per coding guidelines: Do not use function expressions if it's possible to use function declarations instead.
🤖 Prompt for AI Agents
In `@src/views/AuthorProfileView.vue` around lines 40 - 58, Convert the arrow
function handlers to named function declarations: replace the const arrow
definitions for handleAuthorWorkflowSelect, handleBack, handleAuthorSelected,
handleRunWorkflow, and handleMakeCopy with equivalent function declarations
(e.g., function handleAuthorWorkflowSelect(template: AlgoliaWorkflowTemplate) {
... }) preserving current logic, returned values, and usage of
router/selectedWorkflow; ensure TypeScript signatures remain the same and any
references to these symbols in the component template or other code are
unchanged.

Comment on lines +53 to +59
const handleRunWorkflow = (_workflow: AlgoliaWorkflowTemplate) => {
// TODO: Implement workflow run
}

const handleMakeCopy = (_workflow: AlgoliaWorkflowTemplate) => {
// TODO: Implement make a copy
}
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

Run/Copy handlers are no-ops.

Line 53-59 handlers are empty, so the WorkflowDetailView actions will do nothing. Wire these to the run/copy flows or hide the actions until implemented.

If you want, I can draft wiring to the existing run/copy services.

🤖 Prompt for AI Agents
In `@src/views/AuthorProfileView.vue` around lines 53 - 59, The two no-op handlers
handleRunWorkflow and handleMakeCopy must be implemented or the
WorkflowDetailView actions hidden; replace the TODOs by wiring handleRunWorkflow
to the existing run workflow service (e.g., call
runWorkflowService.run(workflow) or equivalent, set local loading state, handle
success/error and show user feedback) and wire handleMakeCopy to the existing
copy service (e.g., workflowCopyService.copy(workflow) or equivalent) with
similar loading/error handling; if those services are not available yet, update
WorkflowDetailView to conditionally hide the Run/Make Copy buttons until
handleRunWorkflow and handleMakeCopy are implemented.

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

🤖 Fix all issues with AI agents
In `@src/components/discover/AuthorProfileView.vue`:
- Around line 310-329: Replace the hard-coded hubProfileUrl with a derived URL
built from the canonical hub base and the current authorName (used by
authorFacetFilters), and update openHubProfile to use that derived value;
specifically, remove the fixed string assigned to hubProfileUrl, construct the
profile URL at runtime by combining the app's hub base (from a shared
util/config or a new getHubBaseUrl helper) with the URL-encoded authorName (use
encodeURIComponent(authorName)) and the expected path (/profile/...), and ensure
openHubProfile calls window.location.assign with this constructed URL so users
are routed to the correct author profile.
- Around line 254-276: The pagination Button elements in AuthorProfileView.vue
lack accessible names; update the left and right Button components (the ones
using currentPage and `@click`="goToPage(...)") to include aria-label attributes
bound to translations, e.g. add :aria-label="$t('g.back')" (or
:aria-label="$t('g.previous')" if you add that key) for the left chevron Button
and :aria-label="$t('g.next')" for the right chevron Button so screen readers
can announce their purpose.

In `@src/components/discover/DiscoverView.vue`:
- Around line 367-373: The handlers handleRunWorkflow and handleMakeCopy are
no-ops causing the UI actions to silently do nothing; implement them to either
emit events or call the existing workflow runner/duplication logic (e.g.,
emit("run-workflow", _workflow) in handleRunWorkflow and emit("copy-workflow",
_workflow) in handleMakeCopy) so parent components or a store action can perform
the operations, or if you cannot implement the behavior yet, disable the
corresponding UI controls by wiring the template to a computed/prop (e.g.,
:disabled="!canRun" / :disabled="!canCopy") until proper wiring is added. Ensure
the functions reference the AlgoliaWorkflowTemplate payload passed in and update
any callers/listeners accordingly.
- Around line 350-365: Both handleAuthorClick and handleAuthorSelected open
external profile links with window.open using '_blank' which allows reverse
tabnabbing; update these functions (handleAuthorClick and handleAuthorSelected)
to prevent this by either passing a features string 'noopener,noreferrer' as the
third argument to window.open or by capturing the returned window object and
setting newWindow.opener = null, e.g., const w = window.open(url, '_blank',
'noopener,noreferrer') || window.open(url); if (w) w.opener = null; ensure you
apply the change in both functions.
- Around line 322-334: performSearch currently calls search(...) without
handling rejections; wrap the search invocation in a try-catch inside
performSearch() (keeping the existing initialFacets assignment) and on catch
call toastStore.addAlert(translate('g.unknownError') or a new search-specific
key) to surface a user-friendly message; also update call sites handleSearch()
and goToPage() to await performSearch() (or return the promise) so any
rejections are propagated/handled rather than unhandled.

In `@src/components/discover/WorkflowDetailView.vue`:
- Around line 43-69: Replace the raw <button> block with the repo's shared
button component (preferably IconTextButton.vue or TextButton.vue) so
focus/disabled styling is consistent; keep the existing props and behavior by
wiring hasAuthor to the component's disabled prop, pass handleAuthorClick to its
click handler, render the avatar via the component's icon slot or prop using
authorAvatar/authorName for src/alt, and preserve the label text and translation
({{ $t('discover.detail.officialWorkflow') }}) and authorName display inside the
component's default slot.
- Around line 218-228: Update both external link opens to add
noreferrer/noopener and encode the object ID: in the handleAuthorClick flow
where you open the author profile (currently encodes workflow.author_name), pass
the third argument 'noopener,noreferrer' to window.open; in openHubWorkflow,
wrap workflow.objectID with encodeURIComponent and also pass
'noopener,noreferrer' as the third window.open argument so the call becomes
window.open(`${hubWorkflowBaseUrl}/${encodeURIComponent(workflow.objectID)}`,
'_blank', 'noopener,noreferrer').

Comment on lines +254 to +276
<div
v-if="results && results.totalPages > 1"
class="flex shrink-0 items-center justify-center gap-2 border-t border-interface-stroke px-6 py-3"
>
<Button
variant="secondary"
size="md"
:disabled="currentPage === 0"
@click="goToPage(currentPage - 1)"
>
<i class="icon-[lucide--chevron-left]" />
</Button>
<span class="px-4 text-sm text-muted-foreground">
{{ currentPage + 1 }} / {{ results.totalPages }}
</span>
<Button
variant="secondary"
size="md"
:disabled="currentPage >= results.totalPages - 1"
@click="goToPage(currentPage + 1)"
>
<i class="icon-[lucide--chevron-right]" />
</Button>
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

🧩 Analysis chain

🏁 Script executed:

# Find and examine the AuthorProfileView.vue file
fd -t f AuthorProfileView.vue

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 147


🏁 Script executed:

# Once found, examine the specific lines 254-276
cat -n src/components/discover/AuthorProfileView.vue | sed -n '250,280p'

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 1114


🏁 Script executed:

# Check the Button component implementation to understand how it handles accessibility
fd -t f Button.vue | head -5

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 320


🏁 Script executed:

# Find the Button component used in this file
fd -t f Button.vue src/components/button/

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 106


🏁 Script executed:

# Examine the Button component implementation
cat -n src/components/button/Button.vue | head -100

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 134


🏁 Script executed:

# Check imports in AuthorProfileView.vue to see where Button comes from
head -50 src/components/discover/AuthorProfileView.vue | grep -i button

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 253


🏁 Script executed:

# Search for Button component more broadly
rg -t vue "export.*Button" --max-count=10 | head -20

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 97


🏁 Script executed:

# Also check what files exist in src/components/button/
fd -t f . src/components/button/

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 229


🏁 Script executed:

# Check the full imports section of AuthorProfileView.vue
head -30 src/components/discover/AuthorProfileView.vue

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 1201


🏁 Script executed:

# Search for Button component definition globally
rg "defineComponent.*Button|export.*const Button" --max-count=10

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 52


🏁 Script executed:

# Also check if Button is a common import
rg "from.*Button" src/components/discover/AuthorProfileView.vue

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 124


🏁 Script executed:

# Find the Button component in the correct location
find src -name Button.vue -path "*/ui/button/*"

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 105


🏁 Script executed:

# Examine the Button component
cat -n src/components/ui/button/Button.vue | head -150

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 1205


🏁 Script executed:

# Check if other icon-only buttons use aria-labels in the codebase
rg "aria-label.*\$t\(" src/components --max-count=10

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 52


🏁 Script executed:

# Check if 'g.previous' and 'g.next' exist in i18n
cat src/locales/en/main.json | grep -A2 -B2 '"g"'

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 202


🏁 Script executed:

# Get the full 'g' section from i18n
cat src/locales/en/main.json | python3 << 'EOF'
import json, sys
data = json.load(sys.stdin)
g_section = data.get('g', {})
for key in sorted(g_section.keys()):
    print(f'  "{key}": "{g_section[key]}",')
EOF

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 790


🏁 Script executed:

# Also check if there are common patterns for previous/next navigation
rg "previous|next" src/locales/en/main.json | head -20

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 722


🏁 Script executed:

# Search for previous/prev keys in i18n more carefully
rg '"previous"|"prev"' src/locales/en/main.json

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 52


🏁 Script executed:

# List the full 'g' section line by line
sed -n '/"g":/,/^  }/p' src/locales/en/main.json | head -50

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 1993


🏁 Script executed:

# Check what button labels exist in general
rg '"back"|"next"|"previous"' src/locales/en/main.json | sort -u

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 128


🏁 Script executed:

# Search for all pagination or navigation-related keys in i18n
rg 'previous|prev|pagination' src/locales/en/main.json -i

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 1407


🏁 Script executed:

# Double-check what keys exist in the 'g' section
sed -n '/"g":/,/^  }/p' src/locales/en/main.json | wc -l

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 73


🏁 Script executed:

# Look for similar icon-only buttons with aria-labels in the codebase for pattern
rg ':aria-label' src/components --max-count=5 -A 2

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 38670


🏁 Script executed:

# Search more specifically for "previous" in i18n file
cat src/locales/en/main.json | grep -i "previous\|prev" | head -10

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 848


🏁 Script executed:

# Get all keys in 'g' section of i18n
sed -n '/"g":/,/^  }/p' src/locales/en/main.json | grep -o '"[^"]*":' | sort -u

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 4214


🏁 Script executed:

# Also check if there's a dedicated scrollLeft/scrollRight pattern we can use as reference
rg 'scrollLeft|scrollRight' src/locales/en/main.json

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 137


Add aria-labels to icon-only pagination buttons.
These buttons need accessible names for screen readers. Use :aria-label="$t('g.next')" for the right chevron, and for the left chevron consider using :aria-label="$t('g.back')" or add a new translation key g.previous to maintain pagination semantics.

Suggested fix
       <Button
         variant="secondary"
         size="md"
         :disabled="currentPage === 0"
+        :aria-label="$t('g.back')"
         `@click`="goToPage(currentPage - 1)"
       >
         <i class="icon-[lucide--chevron-left]" />
       </Button>
@@
       <Button
         variant="secondary"
         size="md"
         :disabled="currentPage >= results.totalPages - 1"
+        :aria-label="$t('g.next')"
         `@click`="goToPage(currentPage + 1)"
       >
         <i class="icon-[lucide--chevron-right]" />
       </Button>
🤖 Prompt for AI Agents
In `@src/components/discover/AuthorProfileView.vue` around lines 254 - 276, The
pagination Button elements in AuthorProfileView.vue lack accessible names;
update the left and right Button components (the ones using currentPage and
`@click`="goToPage(...)") to include aria-label attributes bound to translations,
e.g. add :aria-label="$t('g.back')" (or :aria-label="$t('g.previous')" if you
add that key) for the left chevron Button and :aria-label="$t('g.next')" for the
right chevron Button so screen readers can announce their purpose.

Comment on lines +310 to +329
const hubProfileUrl = 'https://pr-2289.testenvs.comfy.org/profile/Comfy%20Org'

const authorAvatar = computed(
() => authorAvatarUrl ?? '/assets/images/comfy-logo-single.svg'
)

const formattedRuns = computed(() =>
stats?.runs ? stats.runs.toLocaleString() : '--'
)
const formattedCopies = computed(() =>
stats?.copies ? stats.copies.toLocaleString() : '--'
)

const totalWorkflows = computed(() => results.value?.totalHits ?? 0)

const authorFacetFilters = computed(() => [[`author_name:${authorName}`]])

function openHubProfile() {
window.location.assign(hubProfileUrl)
}
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

Remove the hard-coded hub profile URL.
The current value points to a test environment and a fixed author, which will misroute users. Derive it from authorName (and use the canonical hub base or a shared util).

Suggested fix
-const hubProfileUrl = 'https://pr-2289.testenvs.comfy.org/profile/Comfy%20Org'
+const hubProfileBaseUrl = 'https://comfy-hub.vercel.app/profile/'
+const hubProfileUrl = computed(
+  () => `${hubProfileBaseUrl}${encodeURIComponent(authorName)}`
+)
@@
 function openHubProfile() {
-  window.location.assign(hubProfileUrl)
+  window.location.assign(hubProfileUrl.value)
 }
🤖 Prompt for AI Agents
In `@src/components/discover/AuthorProfileView.vue` around lines 310 - 329,
Replace the hard-coded hubProfileUrl with a derived URL built from the canonical
hub base and the current authorName (used by authorFacetFilters), and update
openHubProfile to use that derived value; specifically, remove the fixed string
assigned to hubProfileUrl, construct the profile URL at runtime by combining the
app's hub base (from a shared util/config or a new getHubBaseUrl helper) with
the URL-encoded authorName (use encodeURIComponent(authorName)) and the expected
path (/profile/...), and ensure openHubProfile calls window.location.assign with
this constructed URL so users are routed to the correct author profile.

Comment on lines +322 to +334
async function performSearch() {
const result = await search({
query: searchQuery.value,
pageSize: 24,
pageNumber: currentPage.value,
facetFilters: buildFacetFilters()
})

// Store initial facets on first search (no filters applied)
if (!initialFacets.value && result.facets) {
initialFacets.value = result.facets
}
}
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

🧩 Analysis chain

🏁 Script executed:

cat -n src/components/discover/DiscoverView.vue | head -350 | tail -50

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 1547


🏁 Script executed:

fd -t f "DiscoverView.vue" src/

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 110


🏁 Script executed:

wc -l src/components/discover/DiscoverView.vue

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 114


🏁 Script executed:

head -50 src/components/discover/DiscoverView.vue

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 1519


🏁 Script executed:

fd -t f "useWorkflowTemplateSearch" src/

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 123


🏁 Script executed:

fd -t f "toastStore" src/

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 111


🏁 Script executed:

cat src/composables/discover/useWorkflowTemplateSearch.ts

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 2594


🏁 Script executed:

cat src/platform/updates/common/toastStore.ts | head -100

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 1142


🏁 Script executed:

grep -n "^import\|^import {" src/components/discover/DiscoverView.vue | head -20

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 813


🏁 Script executed:

rg "g\.error" src/locales/en/main.json

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 52


🏁 Script executed:

rg -A 5 "toastStore.addAlert\|useToastStore" src/components --type vue -A 3

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 97


🏁 Script executed:

rg "performSearch" src/components/discover/DiscoverView.vue

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 176


🏁 Script executed:

cat src/locales/en/main.json | head -100

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 4110


🏁 Script executed:

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

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 1009


🏁 Script executed:

rg "catch.*error" src/components -A 3 --type ts | head -40

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 460


Add error handling and user feedback for search failures.
The search() function can reject, but performSearch() lacks a try-catch guard. Additionally, performSearch() is called from handleSearch() and goToPage() without await, leaving rejections unhandled. Wrap the search call in try-catch and display a user-friendly error message via toastStore.addAlert(). Use an existing translation key like g.unknownError (or create a new one for search-specific failures).

Suggested fix
-import { computed, onMounted, ref, watch } from 'vue'
+import { computed, onMounted, ref, watch } from 'vue'
+import { useI18n } from 'vue-i18n'

 import CardContainer from '@/components/card/CardContainer.vue'
 import CardTop from '@/components/card/CardTop.vue'
 import LazyImage from '@/components/common/LazyImage.vue'
 import SearchBox from '@/components/common/SearchBox.vue'
 import SquareChip from '@/components/chip/SquareChip.vue'
 import WorkflowDetailView from '@/components/discover/WorkflowDetailView.vue'
 import MultiSelect from '@/components/input/MultiSelect.vue'
 import Button from '@/components/ui/button/Button.vue'
 import { useWorkflowTemplateSearch } from '@/composables/discover/useWorkflowTemplateSearch'
+import { useToastStore } from '@/platform/updates/common/toastStore'
 import type { AlgoliaWorkflowTemplate } from '@/types/discoverTypes'

 const { search, isLoading, results } = useWorkflowTemplateSearch()
+const { t } = useI18n()
+const toastStore = useToastStore()

 async function performSearch() {
-  const result = await search({
-    query: searchQuery.value,
-    pageSize: 24,
-    pageNumber: currentPage.value,
-    facetFilters: buildFacetFilters()
-  })
-
-  // Store initial facets on first search (no filters applied)
-  if (!initialFacets.value && result.facets) {
-    initialFacets.value = result.facets
-  }
+  try {
+    const result = await search({
+      query: searchQuery.value,
+      pageSize: 24,
+      pageNumber: currentPage.value,
+      facetFilters: buildFacetFilters()
+    })
+
+    // Store initial facets on first search (no filters applied)
+    if (!initialFacets.value && result.facets) {
+      initialFacets.value = result.facets
+    }
+  } catch (error) {
+    toastStore.addAlert(t('g.unknownError'))
+  }
 }

As per coding guidelines: Implement proper error handling; Provide user-friendly and actionable error messages.

🤖 Prompt for AI Agents
In `@src/components/discover/DiscoverView.vue` around lines 322 - 334,
performSearch currently calls search(...) without handling rejections; wrap the
search invocation in a try-catch inside performSearch() (keeping the existing
initialFacets assignment) and on catch call
toastStore.addAlert(translate('g.unknownError') or a new search-specific key) to
surface a user-friendly message; also update call sites handleSearch() and
goToPage() to await performSearch() (or return the promise) so any rejections
are propagated/handled rather than unhandled.

Comment on lines +350 to +365
function handleAuthorClick(template: AlgoliaWorkflowTemplate) {
if (!template.author_name) return
window.open(
`https://comfy-hub.vercel.app/profile/${encodeURIComponent(
template.author_name
)}`,
'_blank'
)
}

function handleAuthorSelected(author: { name: string; avatarUrl?: string }) {
window.open(
`https://comfy-hub.vercel.app/profile/${encodeURIComponent(author.name)}`,
'_blank'
)
}
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

Prevent reverse tabnabbing when opening external profiles.
window.open(..., '_blank') should include noopener,noreferrer.

Suggested fix
 function handleAuthorClick(template: AlgoliaWorkflowTemplate) {
   if (!template.author_name) return
-  window.open(
-    `https://comfy-hub.vercel.app/profile/${encodeURIComponent(
-      template.author_name
-    )}`,
-    '_blank'
-  )
+  const url = `https://comfy-hub.vercel.app/profile/${encodeURIComponent(
+    template.author_name
+  )}`
+  const newWindow = window.open(url, '_blank', 'noopener,noreferrer')
+  if (newWindow) newWindow.opener = null
 }
 
 function handleAuthorSelected(author: { name: string; avatarUrl?: string }) {
-  window.open(
-    `https://comfy-hub.vercel.app/profile/${encodeURIComponent(author.name)}`,
-    '_blank'
-  )
+  const url = `https://comfy-hub.vercel.app/profile/${encodeURIComponent(
+    author.name
+  )}`
+  const newWindow = window.open(url, '_blank', 'noopener,noreferrer')
+  if (newWindow) newWindow.opener = null
 }
🤖 Prompt for AI Agents
In `@src/components/discover/DiscoverView.vue` around lines 350 - 365, Both
handleAuthorClick and handleAuthorSelected open external profile links with
window.open using '_blank' which allows reverse tabnabbing; update these
functions (handleAuthorClick and handleAuthorSelected) to prevent this by either
passing a features string 'noopener,noreferrer' as the third argument to
window.open or by capturing the returned window object and setting
newWindow.opener = null, e.g., const w = window.open(url, '_blank',
'noopener,noreferrer') || window.open(url); if (w) w.opener = null; ensure you
apply the change in both functions.

Comment on lines +367 to +373
function handleRunWorkflow(_workflow: AlgoliaWorkflowTemplate) {
// TODO: Implement workflow run
}

function handleMakeCopy(_workflow: AlgoliaWorkflowTemplate) {
// TODO: Implement make a copy
}
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

Run/Copy handlers are currently no-ops.
These actions will silently do nothing; consider disabling the UI or wiring the handlers before release.

🤖 Prompt for AI Agents
In `@src/components/discover/DiscoverView.vue` around lines 367 - 373, The
handlers handleRunWorkflow and handleMakeCopy are no-ops causing the UI actions
to silently do nothing; implement them to either emit events or call the
existing workflow runner/duplication logic (e.g., emit("run-workflow",
_workflow) in handleRunWorkflow and emit("copy-workflow", _workflow) in
handleMakeCopy) so parent components or a store action can perform the
operations, or if you cannot implement the behavior yet, disable the
corresponding UI controls by wiring the template to a computed/prop (e.g.,
:disabled="!canRun" / :disabled="!canCopy") until proper wiring is added. Ensure
the functions reference the AlgoliaWorkflowTemplate payload passed in and update
any callers/listeners accordingly.

Comment on lines +43 to +69
<button
type="button"
:disabled="!hasAuthor"
:class="
cn(
'flex w-full items-center gap-3 rounded-lg border border-transparent p-2 text-left transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border-subtle',
hasAuthor
? 'hover:bg-secondary-background'
: 'cursor-default opacity-80'
)
"
@click="handleAuthorClick"
>
<img
:src="authorAvatar"
:alt="authorName"
class="size-8 rounded-full bg-secondary-background object-cover"
/>
<div class="min-w-0 flex-1">
<div class="truncate text-sm font-medium text-base-foreground">
{{ authorName }}
</div>
<div class="truncate text-xs text-muted-foreground">
{{ $t('discover.detail.officialWorkflow') }}
</div>
</div>
</button>
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.

🧹 Nitpick | 🔵 Trivial

Use shared button component for the author action.

This is a user-interactive button; please use the repo’s standard button components (IconButton/TextButton/IconTextButton) to keep focus/disabled styling consistent with the rest of the UI.

Based on learnings: In Vue files across the ComfyUI_frontend repo, when a button is needed, prefer the repo's common button components from src/components/button/ (IconButton.vue, TextButton.vue, IconTextButton.vue) over plain HTML elements.

🤖 Prompt for AI Agents
In `@src/components/discover/WorkflowDetailView.vue` around lines 43 - 69, Replace
the raw <button> block with the repo's shared button component (preferably
IconTextButton.vue or TextButton.vue) so focus/disabled styling is consistent;
keep the existing props and behavior by wiring hasAuthor to the component's
disabled prop, pass handleAuthorClick to its click handler, render the avatar
via the component's icon slot or prop using authorAvatar/authorName for src/alt,
and preserve the label text and translation ({{
$t('discover.detail.officialWorkflow') }}) and authorName display inside the
component's default slot.

Comment on lines +218 to +228
window.open(
`https://comfy-hub.vercel.app/profile/${encodeURIComponent(
workflow.author_name
)}`,
'_blank'
)
}

const openHubWorkflow = () => {
window.open(`${hubWorkflowBaseUrl}/${workflow.objectID}`, '_blank')
}
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

🧩 Analysis chain

🏁 Script executed:

# First, let's read the relevant section of the WorkflowDetailView.vue file
cat -n src/components/discover/WorkflowDetailView.vue | sed -n '210,235p'

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 892


🏁 Script executed:

# Check for other window.open usages in the codebase to see if there's a pattern
rg "window\.open" --type vue --type ts --type tsx -B 2 -A 2

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 97


🏁 Script executed:

# Check if there's a utility function for safe link opening
fd -e ts -e tsx -e vue | xargs grep -l "noopener\|noreferrer\|safeOpen\|secureOpen" 2>/dev/null | head -20

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 1250


🏁 Script executed:

# Search for window.open without type filter
rg "window\.open" -B 2 -A 2

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 18158


🏁 Script executed:

# Find where hubWorkflowBaseUrl is defined
rg "hubWorkflowBaseUrl" -B 2 -A 2

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 781


🏁 Script executed:

# Check if there's a utility for safe/secure link opening
fd -type f \( -name "*.ts" -o -name "*.tsx" -o -name "*.js" \) | xargs grep -l "open.*link\|safe.*open\|Router\|navigate" src/utils/ 2>/dev/null | head -10

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 240


🏁 Script executed:

# Verify that objectID could contain special characters by checking its usage/type
rg "objectID" src/components/discover/ -B 1 -A 1 | head -30

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 2017


Add noopener,noreferrer when opening external links and encode URL parameters.

Both window.open calls with _blank should include noopener,noreferrer to prevent reverse tabnabbing (pattern already used in HelpCenterMenuContent.vue and cloudFeedbackTopbarButton.ts). Additionally, objectID should be URL-encoded since it's API-supplied data, consistent with how author_name is already encoded in handleAuthorClick.

Example hardening
const handleAuthorClick = () => {
  if (!workflow.author_name) return
  window.open(
    `https://comfy-hub.vercel.app/profile/${encodeURIComponent(
      workflow.author_name
    )}`,
    '_blank',
+   'noopener,noreferrer'
  )
}

const openHubWorkflow = () => {
-  window.open(`${hubWorkflowBaseUrl}/${workflow.objectID}`, '_blank')
+  window.open(
+    `${hubWorkflowBaseUrl}/${encodeURIComponent(workflow.objectID)}`,
+    '_blank',
+    'noopener,noreferrer'
+  )
}
🤖 Prompt for AI Agents
In `@src/components/discover/WorkflowDetailView.vue` around lines 218 - 228,
Update both external link opens to add noreferrer/noopener and encode the object
ID: in the handleAuthorClick flow where you open the author profile (currently
encodes workflow.author_name), pass the third argument 'noopener,noreferrer' to
window.open; in openHubWorkflow, wrap workflow.objectID with encodeURIComponent
and also pass 'noopener,noreferrer' as the third window.open argument so the
call becomes
window.open(`${hubWorkflowBaseUrl}/${encodeURIComponent(workflow.objectID)}`,
'_blank', 'noopener,noreferrer').

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (1)
src/components/sidebar/tabs/WorkflowsSidebarTab.vue (1)

114-121: ⚠️ Potential issue | 🟠 Major

Add missing i18n key sideToolbar.browseTemplates to src/locales/en/main.json.

The translation key used in the button label does not exist in the English locale file. Add "browseTemplates": "<label_text>" to the sideToolbar object in src/locales/en/main.json to prevent the UI from displaying the raw key text.

🤖 Fix all issues with AI agents
In `@src/stores/workspace/sidebarTabStore.ts`:
- Around line 25-32: FULL_PAGE_TAB_IDS is an empty array so isFullPageTabActive
(which checks activeSidebarTabId against FULL_PAGE_TAB_IDS) will never be true;
populate or derive FULL_PAGE_TAB_IDS from the actual tab definitions instead of
leaving it empty — for example, build it from your sidebar tab metadata (e.g.,
map/filter your tabs array for a `fullPage` or similar flag) or import the
canonical tab ID list so FULL_PAGE_TAB_IDS contains the real full-page tab ids
and isFullPageTabActive can evaluate correctly against activeSidebarTabId.

Comment on lines +25 to +32
const FULL_PAGE_TAB_IDS: string[] = []

const isFullPageTabActive = computed(() => {
return (
activeSidebarTabId.value !== null &&
FULL_PAGE_TAB_IDS.includes(activeSidebarTabId.value)
)
})
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

FULL_PAGE_TAB_IDS is empty, so isFullPageTabActive is always false.

Unless this list is populated elsewhere (it isn’t, as written), the computed never becomes true and any “full-page tab” logic will never activate. Please wire this to real tab IDs or derive it from tab metadata so the feature actually works.

🤖 Prompt for AI Agents
In `@src/stores/workspace/sidebarTabStore.ts` around lines 25 - 32,
FULL_PAGE_TAB_IDS is an empty array so isFullPageTabActive (which checks
activeSidebarTabId against FULL_PAGE_TAB_IDS) will never be true; populate or
derive FULL_PAGE_TAB_IDS from the actual tab definitions instead of leaving it
empty — for example, build it from your sidebar tab metadata (e.g., map/filter
your tabs array for a `fullPage` or similar flag) or import the canonical tab ID
list so FULL_PAGE_TAB_IDS contains the real full-page tab ids and
isFullPageTabActive can evaluate correctly against activeSidebarTabId.

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

Labels

preview size:XXL This PR changes 1000+ lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant