Skip to content

App mode - more updates & fixes#9137

Merged
pythongosssss merged 14 commits intomainfrom
pysssss/appmode-updates
Feb 26, 2026
Merged

App mode - more updates & fixes#9137
pythongosssss merged 14 commits intomainfrom
pysssss/appmode-updates

Conversation

@pythongosssss
Copy link
Member

@pythongosssss pythongosssss commented Feb 23, 2026

Summary

  • fix sizing of sidebars in app mode
  • update feedback button to match design
  • update job queue notification
  • clickable queue spinner item to allow clear queue
  • refactor mode out of store to specific workflow instance
  • support different saved vs active mode
  • other styling/layout tweaks

Changes

  • What: Changes the store to a composable and moves the mode state to the workflow.
  • This enables switching between tabs and maintaining the mode they were in

Screenshots (if applicable)

image

┆Issue is synchronized with this Notion page by Unito

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 23, 2026

Note

Reviews paused

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

Use the following commands to manage reviews:

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

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • ✅ Review completed - (🔄 Check again to review again)
📝 Walkthrough

Walkthrough

This PR migrates app-mode state from a centralized store to a composable per-workflow model, adds builder UI components and draggable lists, persists builder linearData on workflows, updates many components to use the new composable, and expands tests and translations for builder/app mode handling.

Changes

Cohort / File(s) Summary
Composables & Types
src/composables/useAppMode.ts
Add new useAppMode() and exported AppMode type that derive/set mode from workflows.
Store refactor
src/stores/appModeStore.ts
Reduce mode responsibilities; delegate mode to composable; add selection state (selectedInputs/outputs), flush/reset APIs and exitBuilder.
Workflow model & service
src/platform/workflow/management/stores/comfyWorkflow.ts, src/platform/workflow/core/services/workflowService.ts, src/platform/workflow/validation/schemas/workflowSchema.ts, src/platform/workflow/management/stores/workflowStore.ts
Add initialMode, activeMode, dirtyLinearData + LinearData type; add syncLinearMode; propagate initialMode through save/open/restore flows; extend schema with linearData.
Builder UI & utilities
src/components/builder/AppBuilder.vue, src/components/builder/IoItem.vue, src/components/common/DraggableList.vue, src/components/builder/useBuilderSave.ts, src/components/builder/BuilderToolbar.vue
New AppBuilder and IoItem components; generic draggable list; new useBuilderSave local saving API; toolbar wired to composables.
Component migrations to composable
src/components/appMode/AppModeToolbar.vue, src/components/graph/GraphCanvas.vue, src/views/GraphView.vue, src/views/LinearView.vue, src/renderer/core/canvas/canvasStore.ts, src/components/LiteGraphCanvasSplitterOverlay.vue, src/components/builder/LinearArrange.vue, src/renderer/extensions/linearMode/*
Replace useAppModeStore with useAppMode(), update mode checks/setters, adjust template bindings across many UI files.
Linear mode UI changes
src/renderer/extensions/linearMode/LinearControls.vue, src/renderer/extensions/linearMode/LinearPreview.vue, src/renderer/extensions/linearMode/LinearWelcome.vue, src/renderer/extensions/linearMode/OutputHistory.vue, src/renderer/extensions/linearMode/OutputHistoryActiveQueueItem.vue, src/components/ui/TypeformPopoverButton.vue, src/components/sidebar/tabs/SidebarTabTemplate.vue
UI adjustments for builder mode, new OutputHistoryActiveQueueItem, mappedSelections computed, styling and copy updates.
Canvas / command adjustments
src/composables/useCoreCommands.ts, src/renderer/core/canvas/canvasStore.ts, src/renderer/extensions/linearMode/linearOutputStore.ts
Remove cross-store side-effects from ToggleLinear; wire canvas linearMode getter/setter to composable refs.
Tests & test utils
src/platform/workflow/core/services/workflowService.test.ts, src/utils/__tests__/litegraphTestUtils.ts, src/renderer/extensions/linearMode/linearOutputStore.test.ts
Add extensive per-workflow mode tests, new helpers/mocks, update mocks to reflect composable return shapes.
Misc UI tweaks & types
src/components/rightSidePanel/subgraph/SubgraphEditor.vue, src/components/rightSidePanel/subgraph/SubgraphNodeWidget.vue, src/components/topbar/WorkflowTab.vue, src/lib/litegraph/src/types/widgets.ts
Replace custom draggable logic with DraggableList; reduce draggable CSS; show workflow icon based on initialMode === 'app'; remove vueTrack from IBaseWidget.
Translations
src/locales/en/main.json
Add builder strings, queue messages, and rename some beta copy.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant UI as Component
    participant Composable as useAppMode
    participant Workflow
    participant Store as appModeStore

    User->>UI: Click "Enter Builder"
    UI->>Composable: setMode('builder:select')
    Composable->>Workflow: set activeWorkflow.activeMode = 'builder:select'
    Workflow-->>Composable: updated activeMode
    Composable-->>UI: isBuilderMode true (reactive)
    UI->>UI: Render AppBuilder component
    UI->>Store: flushSelections() (if invoked)
    Store->>Workflow: persist dirtyLinearData (when saved)
Loading
sequenceDiagram
    actor User
    participant Builder as AppBuilder
    participant Draggable as DraggableList
    participant Workflow
    participant Service as workflowService

    User->>Builder: Drag / rename inputs/outputs
    Builder->>Draggable: reorder items (v-model update)
    Draggable-->>Builder: new order emitted
    Builder->>Workflow: update dirtyLinearData / selectedInputs
    User->>Builder: Click "Save"
    Builder->>Service: saveWorkflowAs({ initialMode })
    Service->>Workflow: save (propagate initialMode)
    Service-->>Builder: save success
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

size:XL

Suggested reviewers

  • AustinMroz

Poem

🐰
I hopped through stores and found a hook,
Per-workflow modes now take a look.
Builders drag, IoItems sing,
Workflows save the little things.
A tiny hop — state flows like brook.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 4.55% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: refactoring app mode management and related UI fixes.
Description check ✅ Passed The description is mostly complete with a clear summary, detailed changes section, and a screenshot. However, it lacks the 'Breaking' and 'Dependencies' subsections that are specified in the template.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch pysssss/appmode-updates

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

❤️ Share

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

@github-actions
Copy link

github-actions bot commented Feb 23, 2026

🎭 Playwright: ✅ 547 passed, 0 failed · 5 flaky

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

@github-actions
Copy link

github-actions bot commented Feb 23, 2026

🎨 Storybook: ✅ Built — View Storybook

Details

⏰ Completed at: 02/26/2026, 02:12:29 PM UTC

Links

@github-actions
Copy link

github-actions bot commented Feb 23, 2026

📦 Bundle: 4.44 MB gzip 🔴 +696 B

Details

Summary

  • Raw size: 20.8 MB baseline 20.8 MB — 🔴 +4.12 kB
  • Gzip: 4.44 MB baseline 4.44 MB — 🔴 +696 B
  • Brotli: 3.43 MB baseline 3.43 MB — 🔴 +909 B
  • Bundles: 228 current • 228 baseline • 113 added / 113 removed

Category Glance
Graph Workspace 🔴 +3.94 kB (1.01 MB) · Data & Services 🔴 +190 B (2.55 MB) · Panels & Settings 🟢 -56 B (435 kB) · Other 🔴 +46 B (7.77 MB) · Vendor & Third-Party ⚪ 0 B (8.84 MB) · Views & Navigation ⚪ 0 B (72.1 kB) · + 5 more

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

Main entry bundles and manifests

File Before After Δ Raw Δ Gzip Δ Brotli
assets/index-Douxy7BR.js (new) 17.9 kB 🔴 +17.9 kB 🔴 +6.34 kB 🔴 +5.5 kB
assets/index-Dpe_j5gD.js (removed) 17.9 kB 🟢 -17.9 kB 🟢 -6.35 kB 🟢 -5.51 kB

Status: 1 added / 1 removed

Graph Workspace — 1.01 MB (baseline 1 MB) • 🔴 +3.94 kB

Graph editor runtime, canvas, workflow orchestration

File Before After Δ Raw Δ Gzip Δ Brotli
assets/GraphView-B9faLMDe.js (new) 1.01 MB 🔴 +1.01 MB 🔴 +216 kB 🔴 +164 kB
assets/GraphView-zOA4gv00.js (removed) 1 MB 🟢 -1 MB 🟢 -216 kB 🟢 -163 kB

Status: 1 added / 1 removed

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

Top-level views, pages, and routed surfaces

File Before After Δ Raw Δ Gzip Δ Brotli
assets/CloudSurveyView-BOWWYIqm.js (new) 15.5 kB 🔴 +15.5 kB 🔴 +3.32 kB 🔴 +2.83 kB
assets/CloudSurveyView-NQxNsp9-.js (removed) 15.5 kB 🟢 -15.5 kB 🟢 -3.32 kB 🟢 -2.82 kB
assets/CloudLoginView-D-7mWSJg.js (new) 11.4 kB 🔴 +11.4 kB 🔴 +3.19 kB 🔴 +2.82 kB
assets/CloudLoginView-gi__H1lM.js (removed) 11.4 kB 🟢 -11.4 kB 🟢 -3.19 kB 🟢 -2.82 kB
assets/CloudSignupView-8KqNoPX3.js (removed) 9.37 kB 🟢 -9.37 kB 🟢 -2.69 kB 🟢 -2.36 kB
assets/CloudSignupView-De3Bql_z.js (new) 9.37 kB 🔴 +9.37 kB 🔴 +2.7 kB 🔴 +2.36 kB
assets/UserCheckView-ClZDj-B4.js (new) 8.41 kB 🔴 +8.41 kB 🔴 +2.23 kB 🔴 +1.94 kB
assets/UserCheckView-L95470ee.js (removed) 8.41 kB 🟢 -8.41 kB 🟢 -2.23 kB 🟢 -1.94 kB
assets/CloudLayoutView-B2wVdSKr.js (new) 6.43 kB 🔴 +6.43 kB 🔴 +2.1 kB 🔴 +1.82 kB
assets/CloudLayoutView-BH3VSgCz.js (removed) 6.43 kB 🟢 -6.43 kB 🟢 -2.1 kB 🟢 -1.82 kB
assets/CloudForgotPasswordView-BeNOyHwr.js (removed) 5.56 kB 🟢 -5.56 kB 🟢 -1.93 kB 🟢 -1.7 kB
assets/CloudForgotPasswordView-Blbvo7U5.js (new) 5.56 kB 🔴 +5.56 kB 🔴 +1.93 kB 🔴 +1.7 kB
assets/CloudAuthTimeoutView-BZcFS-KB.js (removed) 4.91 kB 🟢 -4.91 kB 🟢 -1.77 kB 🟢 -1.54 kB
assets/CloudAuthTimeoutView-w9YYdHZp.js (new) 4.91 kB 🔴 +4.91 kB 🔴 +1.77 kB 🔴 +1.54 kB
assets/CloudSubscriptionRedirectView-7vhTrmGt.js (removed) 4.75 kB 🟢 -4.75 kB 🟢 -1.78 kB 🟢 -1.58 kB
assets/CloudSubscriptionRedirectView-D2pD_A2A.js (new) 4.75 kB 🔴 +4.75 kB 🔴 +1.78 kB 🔴 +1.57 kB
assets/UserSelectView-BGbnH_U2.js (removed) 4.5 kB 🟢 -4.5 kB 🟢 -1.64 kB 🟢 -1.47 kB
assets/UserSelectView-DI3_LXfx.js (new) 4.5 kB 🔴 +4.5 kB 🔴 +1.64 kB 🔴 +1.46 kB
assets/CloudSorryContactSupportView-Bypca0av.js 1.02 kB 1.02 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/layout-CI4MZk1L.js 296 B 296 B ⚪ 0 B ⚪ 0 B ⚪ 0 B

Status: 9 added / 9 removed

Panels & Settings — 435 kB (baseline 435 kB) • 🟢 -56 B

Configuration panels, inspectors, and settings screens

File Before After Δ Raw Δ Gzip Δ Brotli
assets/SecretsPanel-C_Sx2BlS.js (new) 21.5 kB 🔴 +21.5 kB 🔴 +5.3 kB 🔴 +4.66 kB
assets/SecretsPanel-Cf7HvIyA.js (removed) 21.5 kB 🟢 -21.5 kB 🟢 -5.3 kB 🟢 -4.66 kB
assets/LegacyCreditsPanel-ClivCX0Y.js (removed) 20.6 kB 🟢 -20.6 kB 🟢 -5.56 kB 🟢 -4.88 kB
assets/LegacyCreditsPanel-gEf5HKSm.js (new) 20.6 kB 🔴 +20.6 kB 🔴 +5.56 kB 🔴 +4.89 kB
assets/SubscriptionPanel-uSP1oEAY.js (removed) 18.2 kB 🟢 -18.2 kB 🟢 -4.65 kB 🟢 -4.09 kB
assets/SubscriptionPanel-iMuOxz4s.js (new) 18.1 kB 🔴 +18.1 kB 🔴 +4.64 kB 🔴 +4.08 kB
assets/KeybindingPanel-CG2N9N4Q.js (removed) 12.3 kB 🟢 -12.3 kB 🟢 -3.51 kB 🟢 -3.12 kB
assets/KeybindingPanel-kPDreg14.js (new) 12.3 kB 🔴 +12.3 kB 🔴 +3.51 kB 🔴 +3.12 kB
assets/AboutPanel-Bgqb4q_r.js (new) 9.79 kB 🔴 +9.79 kB 🔴 +2.73 kB 🔴 +2.46 kB
assets/AboutPanel-N4kzCiHM.js (removed) 9.79 kB 🟢 -9.79 kB 🟢 -2.73 kB 🟢 -2.46 kB
assets/ExtensionPanel-BGOwsPQH.js (new) 9.38 kB 🔴 +9.38 kB 🔴 +2.65 kB 🔴 +2.36 kB
assets/ExtensionPanel-BJ1i2ewI.js (removed) 9.38 kB 🟢 -9.38 kB 🟢 -2.65 kB 🟢 -2.36 kB
assets/ServerConfigPanel-K3WjiSq8.js (removed) 6.44 kB 🟢 -6.44 kB 🟢 -2.12 kB 🟢 -1.94 kB
assets/ServerConfigPanel-S-rTntym.js (new) 6.44 kB 🔴 +6.44 kB 🔴 +2.12 kB 🔴 +1.92 kB
assets/UserPanel-B1fZo_BE.js (new) 6.16 kB 🔴 +6.16 kB 🔴 +1.99 kB 🔴 +1.74 kB
assets/UserPanel-jrNuB4TV.js (removed) 6.16 kB 🟢 -6.16 kB 🟢 -1.99 kB 🟢 -1.74 kB
assets/cloudRemoteConfig-Cx_S7D91.js (new) 1.44 kB 🔴 +1.44 kB 🔴 +705 B 🔴 +613 B
assets/cloudRemoteConfig-R0LoBqrs.js (removed) 1.44 kB 🟢 -1.44 kB 🟢 -702 B 🟢 -610 B
assets/refreshRemoteConfig-BTgbZMti.js (removed) 1.14 kB 🟢 -1.14 kB 🟢 -520 B 🟢 -449 B
assets/refreshRemoteConfig-Bw__rTvq.js (new) 1.14 kB 🔴 +1.14 kB 🔴 +517 B 🔴 +467 B
assets/config-CGn5JFmU.js 996 B 996 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-B5oF6TeI.js 29.9 kB 29.9 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-BVYOg4dh.js 24.5 kB 24.5 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-CBEvSL1z.js 38.5 kB 38.5 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-CGx1t8IZ.js 27.8 kB 27.8 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-CNcb_4nC.js 30.5 kB 30.5 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-Cx1dZM6H.js 23.9 kB 23.9 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-Dw-QS6Nb.js 27.9 kB 27.9 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-DXxgnCSn.js 32.4 kB 32.4 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-GRFn4guL.js 34.2 kB 34.2 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-mgwKIVQ2.js 28.8 kB 28.8 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-s83B801I.js 28.7 kB 28.7 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B

Status: 10 added / 10 removed

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

Authentication, profile, and account management bundles

File Before After Δ Raw Δ Gzip Δ Brotli
assets/auth-CBk2Vgw0.js (removed) 3.4 kB 🟢 -3.4 kB 🟢 -1.18 kB 🟢 -988 B
assets/auth-CcTnPX3V.js (new) 3.4 kB 🔴 +3.4 kB 🔴 +1.18 kB 🔴 +987 B
assets/SignUpForm-C5i8tZ5S.js (new) 3.01 kB 🔴 +3.01 kB 🔴 +1.23 kB 🔴 +1.09 kB
assets/SignUpForm-Dfx_auTy.js (removed) 3.01 kB 🟢 -3.01 kB 🟢 -1.23 kB 🟢 -1.09 kB
assets/UpdatePasswordContent-D-sfMuTL.js (removed) 2.37 kB 🟢 -2.37 kB 🟢 -1.06 kB 🟢 -935 B
assets/UpdatePasswordContent-DyUQfQxo.js (new) 2.37 kB 🔴 +2.37 kB 🔴 +1.07 kB 🔴 +945 B
assets/firebaseAuthStore-_QFvZeVm.js (new) 788 B 🔴 +788 B 🔴 +383 B 🔴 +343 B
assets/firebaseAuthStore-Bv2pnGke.js (removed) 788 B 🟢 -788 B 🟢 -383 B 🟢 -338 B
assets/auth-CjytDI9Y.js (removed) 357 B 🟢 -357 B 🟢 -224 B 🟢 -190 B
assets/auth-d6n7SSwO.js (new) 357 B 🔴 +357 B 🔴 +223 B 🔴 +212 B
assets/PasswordFields-DLbVLg8O.js 4.51 kB 4.51 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WorkspaceProfilePic-D6ioir1T.js 1.57 kB 1.57 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B

Status: 5 added / 5 removed

Editors & Dialogs — 736 B (baseline 736 B) • ⚪ 0 B

Modals, dialogs, drawers, and in-app editors

File Before After Δ Raw Δ Gzip Δ Brotli
assets/useSubscriptionDialog-1IIZgKb5.js (removed) 736 B 🟢 -736 B 🟢 -377 B 🟢 -351 B
assets/useSubscriptionDialog-Bz10rjSp.js (new) 736 B 🔴 +736 B 🔴 +375 B 🔴 +325 B

Status: 1 added / 1 removed

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

Reusable component library chunks

File Before After Δ Raw Δ Gzip Δ Brotli
assets/useTerminalTabs-8iLbREP-.js (removed) 9.84 kB 🟢 -9.84 kB 🟢 -3.4 kB 🟢 -3 kB
assets/useTerminalTabs-DGUrBtQK.js (new) 9.84 kB 🔴 +9.84 kB 🔴 +3.4 kB 🔴 +3 kB
assets/ComfyQueueButton-D7on0fTB.js (removed) 8.02 kB 🟢 -8.02 kB 🟢 -2.49 kB 🟢 -2.22 kB
assets/ComfyQueueButton-DBQ-a4Jg.js (new) 8.02 kB 🔴 +8.02 kB 🔴 +2.49 kB 🔴 +2.22 kB
assets/SubscribeButton-B8UsRZWV.js (removed) 2.48 kB 🟢 -2.48 kB 🟢 -1.07 kB 🟢 -944 B
assets/SubscribeButton-g5gwGDrI.js (new) 2.48 kB 🔴 +2.48 kB 🔴 +1.07 kB 🔴 +931 B
assets/cloudFeedbackTopbarButton-CfRKrrju.js (new) 1.59 kB 🔴 +1.59 kB 🔴 +850 B 🔴 +759 B
assets/cloudFeedbackTopbarButton-Cx8Y1tx9.js (removed) 1.59 kB 🟢 -1.59 kB 🟢 -851 B 🟢 -756 B
assets/ComfyQueueButton-2uvEBQwo.js (new) 793 B 🔴 +793 B 🔴 +391 B 🔴 +351 B
assets/ComfyQueueButton-CUsrsRSt.js (removed) 793 B 🟢 -793 B 🟢 -390 B 🟢 -346 B
assets/Button-D1z3poyI.js 2.98 kB 2.98 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/CloudBadge-BEnZAylJ.js 1.24 kB 1.24 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/FormSearchInput-Bg4LklDe.js 3.73 kB 3.73 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/ScrubableNumberInput-DecBFGbM.js 5.94 kB 5.94 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/TopbarBadge-CJNpTEnW.js 7.44 kB 7.44 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/UserAvatar-CnQQLXB-.js 1.17 kB 1.17 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetButton-sg8Jj4MY.js 1.84 kB 1.84 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B

Status: 5 added / 5 removed

Data & Services — 2.55 MB (baseline 2.55 MB) • 🔴 +190 B

Stores, services, APIs, and repositories

File Before After Δ Raw Δ Gzip Δ Brotli
assets/dialogService-C2BV3e2J.js (new) 1.75 MB 🔴 +1.75 MB 🔴 +393 kB 🔴 +296 kB
assets/dialogService-CfRuFxtX.js (removed) 1.75 MB 🟢 -1.75 MB 🟢 -393 kB 🟢 -296 kB
assets/api-1kAni9Ba.js (new) 677 kB 🔴 +677 kB 🔴 +153 kB 🔴 +122 kB
assets/api-Do1y81mx.js (removed) 677 kB 🟢 -677 kB 🟢 -153 kB 🟢 -122 kB
assets/load3dService-BHVqAeXO.js (new) 91 kB 🔴 +91 kB 🔴 +19.1 kB 🔴 +16.4 kB
assets/load3dService-C1q66XCK.js (removed) 91 kB 🟢 -91 kB 🟢 -19.1 kB 🟢 -16.4 kB
assets/extensionStore-9rUdhYXU.js (new) 12.1 kB 🔴 +12.1 kB 🔴 +4.21 kB 🔴 +3.69 kB
assets/extensionStore-DsraiizB.js (removed) 12.1 kB 🟢 -12.1 kB 🟢 -4.21 kB 🟢 -3.7 kB
assets/releaseStore-BXoH0HOr.js (removed) 7.96 kB 🟢 -7.96 kB 🟢 -2.22 kB 🟢 -1.95 kB
assets/releaseStore-DvPSLDwA.js (new) 7.96 kB 🔴 +7.96 kB 🔴 +2.22 kB 🔴 +1.94 kB
assets/keybindingService-BeiJkTdC.js (removed) 6.52 kB 🟢 -6.52 kB 🟢 -1.71 kB 🟢 -1.48 kB
assets/keybindingService-DTv7DCyC.js (new) 6.52 kB 🔴 +6.52 kB 🔴 +1.71 kB 🔴 +1.48 kB
assets/bootstrapStore-8k5DIUHA.js (new) 2.08 kB 🔴 +2.08 kB 🔴 +872 B 🔴 +790 B
assets/bootstrapStore-CXsU8fHE.js (removed) 2.08 kB 🟢 -2.08 kB 🟢 -873 B 🟢 -791 B
assets/userStore-BwSdsuGA.js (new) 1.85 kB 🔴 +1.85 kB 🔴 +718 B 🔴 +667 B
assets/userStore-DwbxJeUw.js (removed) 1.85 kB 🟢 -1.85 kB 🟢 -718 B 🟢 -631 B
assets/audioService-9201IJtK.js (removed) 1.73 kB 🟢 -1.73 kB 🟢 -850 B 🟢 -725 B
assets/audioService-XGML4lES.js (new) 1.73 kB 🔴 +1.73 kB 🔴 +849 B 🔴 +725 B
assets/releaseStore-DDOzJAL3.js (new) 760 B 🔴 +760 B 🔴 +381 B 🔴 +337 B
assets/releaseStore-DLgTg7uE.js (removed) 760 B 🟢 -760 B 🟢 -383 B 🟢 -333 B
assets/settingStore-BRfiZ4rf.js (new) 744 B 🔴 +744 B 🔴 +382 B 🔴 +363 B
assets/settingStore-J0tZucOM.js (removed) 744 B 🟢 -744 B 🟢 -385 B 🟢 -336 B
assets/workflowDraftStore-C5gXgjPf.js (removed) 736 B 🟢 -736 B 🟢 -377 B 🟢 -329 B
assets/workflowDraftStore-xCFwZKrq.js (new) 736 B 🔴 +736 B 🔴 +375 B 🔴 +331 B
assets/dialogService-DYuX664U.js (removed) 725 B 🟢 -725 B 🟢 -364 B 🟢 -320 B
assets/dialogService-MOTEmHXW.js (new) 725 B 🔴 +725 B 🔴 +363 B 🔴 +326 B
assets/serverConfigStore-EPk4OtIK.js 2.32 kB 2.32 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B

Status: 13 added / 13 removed

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

Helpers, composables, and utility bundles

File Before After Δ Raw Δ Gzip Δ Brotli
assets/useLoad3d-0MRnYWNm.js (removed) 14.6 kB 🟢 -14.6 kB 🟢 -3.63 kB 🟢 -3.21 kB
assets/useLoad3d-BLgVnRp8.js (new) 14.6 kB 🔴 +14.6 kB 🔴 +3.63 kB 🔴 +3.2 kB
assets/useLoad3dViewer-CWhShXJd.js (removed) 14.1 kB 🟢 -14.1 kB 🟢 -3.15 kB 🟢 -2.8 kB
assets/useLoad3dViewer-CZofYuux.js (new) 14.1 kB 🔴 +14.1 kB 🔴 +3.15 kB 🔴 +2.79 kB
assets/useFeatureFlags-0UMnvwKB.js (removed) 4.14 kB 🟢 -4.14 kB 🟢 -1.24 kB 🟢 -1.06 kB
assets/useFeatureFlags-BvP4nBaK.js (new) 4.14 kB 🔴 +4.14 kB 🔴 +1.23 kB 🔴 +1.06 kB
assets/useWorkspaceUI-BNsK80Ts.js (new) 3 kB 🔴 +3 kB 🔴 +822 B 🔴 +704 B
assets/useWorkspaceUI-ChFPYLop.js (removed) 3 kB 🟢 -3 kB 🟢 -821 B 🟢 -707 B
assets/subscriptionCheckoutUtil-DdjDNXSM.js (removed) 2.53 kB 🟢 -2.53 kB 🟢 -1.06 kB 🟢 -924 B
assets/subscriptionCheckoutUtil-Do4aNm6K.js (new) 2.53 kB 🔴 +2.53 kB 🔴 +1.06 kB 🔴 +948 B
assets/useExternalLink-B-2OMa-c.js (removed) 1.66 kB 🟢 -1.66 kB 🟢 -769 B 🟢 -679 B
assets/useExternalLink-CX9r3naC.js (new) 1.66 kB 🔴 +1.66 kB 🔴 +769 B 🔴 +677 B
assets/useErrorHandling-DDmrkzT4.js (removed) 1.5 kB 🟢 -1.5 kB 🟢 -629 B 🟢 -535 B
assets/useErrorHandling-YGicyD2W.js (new) 1.5 kB 🔴 +1.5 kB 🔴 +628 B 🔴 +534 B
assets/useWorkspaceSwitch-Dx-h-DFn.js (new) 1.25 kB 🔴 +1.25 kB 🔴 +544 B 🔴 +480 B
assets/useWorkspaceSwitch-IzvOxAgQ.js (removed) 1.25 kB 🟢 -1.25 kB 🟢 -545 B 🟢 -488 B
assets/useLoad3d-CfMSxHx_.js (new) 859 B 🔴 +859 B 🔴 +420 B 🔴 +384 B
assets/useLoad3d-lAz_mqgt.js (removed) 859 B 🟢 -859 B 🟢 -422 B 🟢 -373 B
assets/audioUtils-DC5TYod6.js (removed) 858 B 🟢 -858 B 🟢 -500 B 🟢 -403 B
assets/audioUtils-DFFrbw8o.js (new) 858 B 🔴 +858 B 🔴 +501 B 🔴 +401 B
assets/useLoad3dViewer-566W-yAy.js (removed) 838 B 🟢 -838 B 🟢 -408 B 🟢 -361 B
assets/useLoad3dViewer-An2xnWny.js (new) 838 B 🔴 +838 B 🔴 +405 B 🔴 +369 B
assets/useCurrentUser-DGvH5f89.js (new) 722 B 🔴 +722 B 🔴 +368 B 🔴 +324 B
assets/useCurrentUser-ms-HTkGa.js (removed) 722 B 🟢 -722 B 🟢 -371 B 🟢 -320 B
assets/_plugin-vue_export-helper-ralzwvFM.js 315 B 315 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/colorUtil-CY7QMUhQ.js 7 kB 7 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/envUtil-Clzmwvt4.js 466 B 466 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/markdownRendererUtil-Cddas8Zl.js 1.56 kB 1.56 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/SkeletonUtils-BputJAFn.js 133 B 133 B ⚪ 0 B ⚪ 0 B ⚪ 0 B

Status: 12 added / 12 removed

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

External libraries and shared vendor chunks

File Before After Δ Raw Δ Gzip Δ Brotli
assets/vendor-axios-Cp6hch1I.js 70.7 kB 70.7 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-chart-BxkFiWzp.js 399 kB 399 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-firebase-BvMr43CG.js 836 kB 836 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-i18n-DNX73mqE.js 133 kB 133 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-markdown-D5S6AC80.js 103 kB 103 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-other-DrYd4O-6.js 1.52 MB 1.52 MB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-primevue-BnCPTL0g.js 1.73 MB 1.73 MB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-reka-ui-DVmi2O2Z.js 388 kB 388 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-sentry-SQwstEKc.js 182 kB 182 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-three-LBLOE6BD.js 1.8 MB 1.8 MB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-tiptap-BnYkbQDM.js 634 kB 634 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-vue-core-DtiQ1dr9.js 311 kB 311 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-vueuse-D2jVNnmE.js 113 kB 113 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-xterm-MKpa1ZAW.js 374 kB 374 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-yjs-CP_4YO8u.js 143 kB 143 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-zod-DcCUUPIi.js 109 kB 109 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
Other — 7.77 MB (baseline 7.77 MB) • 🔴 +46 B

Bundles that do not match a named category

File Before After Δ Raw Δ Gzip Δ Brotli
assets/i18n-DCat69Wv.js (new) 527 kB 🔴 +527 kB 🔴 +102 kB 🔴 +79.3 kB
assets/i18n-CLNN_CRs.js (removed) 527 kB 🟢 -527 kB 🟢 -102 kB 🟢 -79.4 kB
assets/core-BUjMzu3W.js (new) 73.4 kB 🔴 +73.4 kB 🔴 +18.9 kB 🔴 +16.2 kB
assets/core-rAymHfba.js (removed) 73.4 kB 🟢 -73.4 kB 🟢 -18.9 kB 🟢 -16.2 kB
assets/groupNode-Bb5h9a3K.js (new) 71.8 kB 🔴 +71.8 kB 🔴 +17.7 kB 🔴 +15.5 kB
assets/groupNode-Dhloei3K.js (removed) 71.8 kB 🟢 -71.8 kB 🟢 -17.7 kB 🟢 -15.6 kB
assets/WidgetSelect-B_g3h4lh.js (removed) 58.1 kB 🟢 -58.1 kB 🟢 -12.4 kB 🟢 -10.7 kB
assets/WidgetSelect-C-cStljq.js (new) 58.1 kB 🔴 +58.1 kB 🔴 +12.4 kB 🔴 +10.7 kB
assets/SubscriptionRequiredDialogContentWorkspace-CbFX9FNc.js (removed) 46.3 kB 🟢 -46.3 kB 🟢 -8.65 kB 🟢 -7.5 kB
assets/SubscriptionRequiredDialogContentWorkspace-WxBjUqFb.js (new) 46.3 kB 🔴 +46.3 kB 🔴 +8.65 kB 🔴 +7.51 kB
assets/WidgetPainter-AokSdHNM.js (removed) 32.5 kB 🟢 -32.5 kB 🟢 -7.96 kB 🟢 -7.05 kB
assets/WidgetPainter-C4u-9sZR.js (new) 32.5 kB 🔴 +32.5 kB 🔴 +7.96 kB 🔴 +7.07 kB
assets/Load3DControls-CkBa4anr.js (removed) 30.9 kB 🟢 -30.9 kB 🟢 -5.34 kB 🟢 -4.65 kB
assets/Load3DControls-DUMwpSA4.js (new) 30.9 kB 🔴 +30.9 kB 🔴 +5.34 kB 🔴 +4.65 kB
assets/WorkspacePanelContent-DJm7lntC.js (removed) 29.3 kB 🟢 -29.3 kB 🟢 -6.14 kB 🟢 -5.4 kB
assets/WorkspacePanelContent-mx-WHJTV.js (new) 29.3 kB 🔴 +29.3 kB 🔴 +6.14 kB 🔴 +5.4 kB
assets/SubscriptionRequiredDialogContent-BEOipN9b.js (removed) 25.7 kB 🟢 -25.7 kB 🟢 -6.57 kB 🟢 -5.79 kB
assets/SubscriptionRequiredDialogContent-BPWQiE2P.js (new) 25.7 kB 🔴 +25.7 kB 🔴 +6.57 kB 🔴 +5.78 kB
assets/Load3dViewerContent-CPffZQ8L.js (removed) 23 kB 🟢 -23 kB 🟢 -5.18 kB 🟢 -4.5 kB
assets/Load3dViewerContent-DENk_tqY.js (new) 23 kB 🔴 +23 kB 🔴 +5.18 kB 🔴 +4.5 kB
assets/WidgetImageCrop-BT8tBXwQ.js (removed) 22.1 kB 🟢 -22.1 kB 🟢 -5.5 kB 🟢 -4.85 kB
assets/WidgetImageCrop-okXa_rTv.js (new) 22.1 kB 🔴 +22.1 kB 🔴 +5.5 kB 🔴 +4.85 kB
assets/SubscriptionPanelContentWorkspace-DG_gHszK.js (removed) 21.6 kB 🟢 -21.6 kB 🟢 -5.05 kB 🟢 -4.45 kB
assets/SubscriptionPanelContentWorkspace-DPj3wVDI.js (new) 21.5 kB 🔴 +21.5 kB 🔴 +5.03 kB 🔴 +4.43 kB
assets/CurrentUserPopoverWorkspace-BX4RzL2a.js (removed) 19.9 kB 🟢 -19.9 kB 🟢 -4.88 kB 🟢 -4.35 kB
assets/CurrentUserPopoverWorkspace-DqEuXt6C.js (new) 19.9 kB 🔴 +19.9 kB 🔴 +4.88 kB 🔴 +4.35 kB
assets/SignInContent-DpwH8LUj.js (removed) 18.9 kB 🟢 -18.9 kB 🟢 -4.75 kB 🟢 -4.16 kB
assets/SignInContent-eKW9GbdJ.js (new) 18.9 kB 🔴 +18.9 kB 🔴 +4.76 kB 🔴 +4.17 kB
assets/WidgetInputNumber-CSfCTLg5.js (removed) 18.7 kB 🟢 -18.7 kB 🟢 -4.75 kB 🟢 -4.22 kB
assets/WidgetInputNumber-CzQa2fh-.js (new) 18.7 kB 🔴 +18.7 kB 🔴 +4.75 kB 🔴 +4.22 kB
assets/WidgetRecordAudio-Ar2Qbfw2.js (removed) 17.3 kB 🟢 -17.3 kB 🟢 -4.94 kB 🟢 -4.42 kB
assets/WidgetRecordAudio-CdcI0iq7.js (new) 17.3 kB 🔴 +17.3 kB 🔴 +4.94 kB 🔴 +4.42 kB
assets/Load3D-CMhTUdEI.js (removed) 16.2 kB 🟢 -16.2 kB 🟢 -4.03 kB 🟢 -3.52 kB
assets/Load3D-CWyiRzj1.js (new) 16.2 kB 🔴 +16.2 kB 🔴 +4.03 kB 🔴 +3.52 kB
assets/load3d-CMQPuuoz.js (new) 14.7 kB 🔴 +14.7 kB 🔴 +4.19 kB 🔴 +3.63 kB
assets/load3d-DF5PzR8J.js (removed) 14.7 kB 🟢 -14.7 kB 🟢 -4.19 kB 🟢 -3.62 kB
assets/AudioPreviewPlayer-BA3r2CRg.js (removed) 10.9 kB 🟢 -10.9 kB 🟢 -3.19 kB 🟢 -2.87 kB
assets/AudioPreviewPlayer-CIHZDKOM.js (new) 10.9 kB 🔴 +10.9 kB 🔴 +3.19 kB 🔴 +2.87 kB
assets/changeTracker-5avHny1a.js (removed) 9.38 kB 🟢 -9.38 kB 🟢 -2.89 kB 🟢 -2.54 kB
assets/changeTracker-BWmB_Nz0.js (new) 9.38 kB 🔴 +9.38 kB 🔴 +2.9 kB 🔴 +2.55 kB
assets/nodeTemplates-B4PNrZ2F.js (removed) 9.29 kB 🟢 -9.29 kB 🟢 -3.25 kB 🟢 -2.86 kB
assets/nodeTemplates-CvDvG1bH.js (new) 9.29 kB 🔴 +9.29 kB 🔴 +3.25 kB 🔴 +2.86 kB
assets/InviteMemberDialogContent-BNzXSTmK.js (new) 7.38 kB 🔴 +7.38 kB 🔴 +2.29 kB 🔴 +2 kB
assets/InviteMemberDialogContent-H3gDHJZB.js (removed) 7.38 kB 🟢 -7.38 kB 🟢 -2.29 kB 🟢 -2 kB
assets/Load3DConfiguration-BN2dYiKL.js (new) 6.27 kB 🔴 +6.27 kB 🔴 +1.92 kB 🔴 +1.68 kB
assets/Load3DConfiguration-Byp3kqQz.js (removed) 6.27 kB 🟢 -6.27 kB 🟢 -1.92 kB 🟢 -1.68 kB
assets/CreateWorkspaceDialogContent-BVPZugdO.js (removed) 5.53 kB 🟢 -5.53 kB 🟢 -1.99 kB 🟢 -1.73 kB
assets/CreateWorkspaceDialogContent-Cz_eI0O8.js (new) 5.53 kB 🔴 +5.53 kB 🔴 +1.99 kB 🔴 +1.74 kB
assets/onboardingCloudRoutes-BzQByylC.js (new) 5.41 kB 🔴 +5.41 kB 🔴 +1.83 kB 🔴 +1.6 kB
assets/onboardingCloudRoutes-TcUzZqas.js (removed) 5.41 kB 🟢 -5.41 kB 🟢 -1.83 kB 🟢 -1.6 kB
assets/FreeTierDialogContent-Boeg7Tk8.js (new) 5.39 kB 🔴 +5.39 kB 🔴 +1.9 kB 🔴 +1.68 kB
assets/FreeTierDialogContent-DBlKe_-k.js (removed) 5.39 kB 🟢 -5.39 kB 🟢 -1.89 kB 🟢 -1.66 kB
assets/EditWorkspaceDialogContent-C6KguQbi.js (new) 5.33 kB 🔴 +5.33 kB 🔴 +1.94 kB 🔴 +1.69 kB
assets/EditWorkspaceDialogContent-r0bubZBW.js (removed) 5.33 kB 🟢 -5.33 kB 🟢 -1.94 kB 🟢 -1.69 kB
assets/ValueControlPopover-0EIMdBPC.js (new) 4.92 kB 🔴 +4.92 kB 🔴 +1.76 kB 🔴 +1.57 kB
assets/ValueControlPopover-DShNDAuR.js (removed) 4.92 kB 🟢 -4.92 kB 🟢 -1.76 kB 🟢 -1.57 kB
assets/Preview3d-6aQCMBFw.js (new) 4.81 kB 🔴 +4.81 kB 🔴 +1.56 kB 🔴 +1.37 kB
assets/Preview3d-CR6KeiKT.js (removed) 4.81 kB 🟢 -4.81 kB 🟢 -1.56 kB 🟢 -1.36 kB
assets/CancelSubscriptionDialogContent-BWdNvKr4.js (new) 4.79 kB 🔴 +4.79 kB 🔴 +1.78 kB 🔴 +1.56 kB
assets/CancelSubscriptionDialogContent-CPL2xqWE.js (removed) 4.79 kB 🟢 -4.79 kB 🟢 -1.78 kB 🟢 -1.56 kB
assets/DeleteWorkspaceDialogContent-BeMZxKY-.js (removed) 4.23 kB 🟢 -4.23 kB 🟢 -1.63 kB 🟢 -1.42 kB
assets/DeleteWorkspaceDialogContent-C9tc_2tl.js (new) 4.23 kB 🔴 +4.23 kB 🔴 +1.63 kB 🔴 +1.42 kB
assets/WidgetWithControl-B5E2fQ9I.js (new) 4.1 kB 🔴 +4.1 kB 🔴 +1.77 kB 🔴 +1.59 kB
assets/WidgetWithControl-BQC2Tr6C.js (removed) 4.1 kB 🟢 -4.1 kB 🟢 -1.77 kB 🟢 -1.58 kB
assets/LeaveWorkspaceDialogContent-54og-hwV.js (new) 4.06 kB 🔴 +4.06 kB 🔴 +1.58 kB 🔴 +1.37 kB
assets/LeaveWorkspaceDialogContent-Duz7V9xU.js (removed) 4.06 kB 🟢 -4.06 kB 🟢 -1.58 kB 🟢 -1.37 kB
assets/RemoveMemberDialogContent-BEHGKO_8.js (new) 4.04 kB 🔴 +4.04 kB 🔴 +1.52 kB 🔴 +1.33 kB
assets/RemoveMemberDialogContent-DFel28bp.js (removed) 4.04 kB 🟢 -4.04 kB 🟢 -1.52 kB 🟢 -1.33 kB
assets/RevokeInviteDialogContent-CqUAJv7v.js (new) 3.95 kB 🔴 +3.95 kB 🔴 +1.54 kB 🔴 +1.35 kB
assets/RevokeInviteDialogContent-DBVhpOlg.js (removed) 3.95 kB 🟢 -3.95 kB 🟢 -1.54 kB 🟢 -1.35 kB
assets/InviteMemberUpsellDialogContent-Dm47jriE.js (new) 3.82 kB 🔴 +3.82 kB 🔴 +1.4 kB 🔴 +1.23 kB
assets/InviteMemberUpsellDialogContent-DnI88TWR.js (removed) 3.82 kB 🟢 -3.82 kB 🟢 -1.4 kB 🟢 -1.23 kB
assets/tierBenefits-CxJJwgmo.js (new) 3.66 kB 🔴 +3.66 kB 🔴 +1.3 kB 🔴 +1.17 kB
assets/tierBenefits-TSWYEcdX.js (removed) 3.66 kB 🟢 -3.66 kB 🟢 -1.3 kB 🟢 -1.17 kB
assets/saveMesh-Df-y6yiv.js (new) 3.38 kB 🔴 +3.38 kB 🔴 +1.45 kB 🔴 +1.3 kB
assets/saveMesh-Dfwdefnm.js (removed) 3.38 kB 🟢 -3.38 kB 🟢 -1.45 kB 🟢 -1.28 kB
assets/cloudSessionCookie-Bo4nlIrH.js (removed) 3.1 kB 🟢 -3.1 kB 🟢 -1.08 kB 🟢 -977 B
assets/cloudSessionCookie-Cm9k1Dtg.js (new) 3.1 kB 🔴 +3.1 kB 🔴 +1.08 kB 🔴 +951 B
assets/GlobalToast-CIBjq80D.js (new) 2.91 kB 🔴 +2.91 kB 🔴 +1.21 kB 🔴 +1.06 kB
assets/GlobalToast-oeZKnWMI.js (removed) 2.91 kB 🟢 -2.91 kB 🟢 -1.21 kB 🟢 -1.05 kB
assets/ApiNodesSignInContent-0Uj8AGQQ.js (removed) 2.69 kB 🟢 -2.69 kB 🟢 -1.05 kB 🟢 -922 B
assets/ApiNodesSignInContent-BLLHvWL9.js (new) 2.69 kB 🔴 +2.69 kB 🔴 +1.05 kB 🔴 +919 B
assets/SubscribeToRun-BSS6wWlq.js (removed) 2.2 kB 🟢 -2.2 kB 🟢 -1.01 kB 🟢 -881 B
assets/SubscribeToRun-g1uo111M.js (new) 2.2 kB 🔴 +2.2 kB 🔴 +1.01 kB 🔴 +883 B
assets/CloudRunButtonWrapper-c9vq1TYY.js (removed) 1.68 kB 🟢 -1.68 kB 🟢 -784 B 🟢 -712 B
assets/CloudRunButtonWrapper-sYC2g7Cb.js (new) 1.68 kB 🔴 +1.68 kB 🔴 +783 B 🔴 +710 B
assets/signInSchema-D50I55ga.js (removed) 1.53 kB 🟢 -1.53 kB 🟢 -563 B 🟢 -518 B
assets/signInSchema-QhJbZF-0.js (new) 1.53 kB 🔴 +1.53 kB 🔴 +563 B 🔴 +522 B
assets/previousFullPath-BC0dJ4Xp.js (removed) 1.39 kB 🟢 -1.39 kB 🟢 -650 B 🟢 -580 B
assets/previousFullPath-BIJlYLLL.js (new) 1.39 kB 🔴 +1.39 kB 🔴 +650 B 🔴 +570 B
assets/cloudBadges-CZs8NBTq.js (new) 1.37 kB 🔴 +1.37 kB 🔴 +700 B 🔴 +614 B
assets/cloudBadges-DKADkFgB.js (removed) 1.37 kB 🟢 -1.37 kB 🟢 -697 B 🟢 -610 B
assets/cloudSubscription-CX0T9cz1.js (removed) 1.33 kB 🟢 -1.33 kB 🟢 -654 B 🟢 -565 B
assets/cloudSubscription-DSXPY7o2.js (new) 1.33 kB 🔴 +1.33 kB 🔴 +654 B 🔴 +566 B
assets/Load3D-CYWz7FIQ.js (new) 1.07 kB 🔴 +1.07 kB 🔴 +494 B 🔴 +446 B
assets/Load3D-DyhOQktD.js (removed) 1.07 kB 🟢 -1.07 kB 🟢 -493 B 🟢 -439 B
assets/nightlyBadges-MxDSY_wY.js (new) 1 kB 🔴 +1 kB 🔴 +528 B 🔴 +475 B
assets/nightlyBadges-txsCzy1e.js (removed) 1 kB 🟢 -1 kB 🟢 -527 B 🟢 -469 B
assets/Load3dViewerContent-B_OUuZor.js (removed) 993 B 🟢 -993 B 🟢 -465 B 🟢 -412 B
assets/Load3dViewerContent-BpaiUL51.js (new) 993 B 🔴 +993 B 🔴 +464 B 🔴 +419 B
assets/SubscriptionPanelContentWorkspace-CurHNGcs.js (removed) 920 B 🟢 -920 B 🟢 -438 B 🟢 -376 B
assets/SubscriptionPanelContentWorkspace-KegHxMO0.js (new) 920 B 🔴 +920 B 🔴 +435 B 🔴 +377 B
assets/graphHasMissingNodes-BLAMo-5d.js (new) 761 B 🔴 +761 B 🔴 +372 B 🔴 +319 B
assets/graphHasMissingNodes-D7_TJiHL.js (removed) 761 B 🟢 -761 B 🟢 -373 B 🟢 -318 B
assets/changeTracker-DiNF3ggo.js (removed) 757 B 🟢 -757 B 🟢 -381 B 🟢 -356 B
assets/changeTracker-vSmP0hdx.js (new) 757 B 🔴 +757 B 🔴 +382 B 🔴 +334 B
assets/WidgetLegacy-CvrJ3X1-.js (new) 745 B 🔴 +745 B 🔴 +381 B 🔴 +335 B
assets/WidgetLegacy-Z1qGbMJX.js (removed) 745 B 🟢 -745 B 🟢 -383 B 🟢 -349 B
assets/WidgetInputNumber-Bs4ce-tu.js (removed) 469 B 🟢 -469 B 🟢 -264 B 🟢 -224 B
assets/WidgetInputNumber-CwmvJUCE.js (new) 469 B 🔴 +469 B 🔴 +265 B 🔴 +228 B
assets/i18n-BwUEH4SK.js (removed) 199 B 🟢 -199 B 🟢 -159 B 🟢 -139 B
assets/i18n-Cf-RspTR.js (new) 199 B 🔴 +199 B 🔴 +161 B 🔴 +140 B
assets/AnimationControls-e1OB6oJR.js 4.61 kB 4.61 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/auto-BTnZwrs2.js 1.7 kB 1.7 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/BaseViewTemplate-DQKI7wOs.js 1.78 kB 1.78 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/comfy-logo-single-D9MrYETV.js 198 B 198 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/ComfyOrgHeader-CuEodz4y.js 910 B 910 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-B-AdR9IA.js 17.5 kB 17.5 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-CbkxT8K8.js 16.1 kB 16.1 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-CJGmjcIS.js 15.9 kB 15.9 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-CMaLgTTb.js 16.7 kB 16.7 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-Cw07MMbJ.js 18.8 kB 18.8 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-D7EtdE6o.js 16.9 kB 16.9 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-DknEFpK3.js 15.2 kB 15.2 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-Ds6WuXnw.js 16.1 kB 16.1 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-Dvq-F-mb.js 17.5 kB 17.5 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-pUOay9Eo.js 15.1 kB 15.1 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-u2AZ8xU4.js 16.1 kB 16.1 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/constants-htt0vt7m.js 579 B 579 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-0eBOvZE-.js 147 kB 147 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-B2sg68b4.js 169 kB 169 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-BonGln7m.js 183 kB 183 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-Bwaiyhg6.js 130 kB 130 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-CgPRGKFQ.js 128 kB 128 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-COpUCFH6.js 154 kB 154 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-D5fk3t1K.js 176 kB 176 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-DQn-D-q9.js 151 kB 151 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-DRFXRCEv.js 205 kB 205 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-e5nfEcC2.js 146 kB 146 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-MEdUGbq0.js 149 kB 149 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/Media3DTop-Dqa2c7nZ.js 1.82 kB 1.82 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/MediaAudioTop-DLiWNcHw.js 1.43 kB 1.43 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/MediaImageTop-BLQErkwF.js 1.75 kB 1.75 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/MediaOtherTop-NQGNpa4H.js 1.02 kB 1.02 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/MediaTextTop-0crUoXWV.js 1.01 kB 1.01 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/MediaVideoTop-sJMrm9jB.js 2.77 kB 2.77 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-BmbASBY9.js 409 kB 409 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-BPIx0d5-.js 393 kB 393 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-BWj_hhU9.js 393 kB 393 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-C7at0SVC.js 385 kB 385 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-D00E-J_2.js 390 kB 390 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-DRodGSrf.js 362 kB 362 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-ezyqmmhm.js 443 kB 443 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-GaT9kQvQ.js 483 kB 483 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-hnjb8NXS.js 397 kB 397 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-ITpXEN3V.js 442 kB 442 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-ZK3OUxU7.js 358 kB 358 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/OBJLoader2WorkerModule-DTMpvldF.js 109 kB 109 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/Popover-BIYdg9E5.js 3.65 kB 3.65 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/rolldown-runtime-DLICfi3-.js 1.97 kB 1.97 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/SelectValue-C_7cycpB.js 8.94 kB 8.94 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/Slider-DVkw5nPu.js 3.52 kB 3.52 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/src-CbNGuSYA.js 251 B 251 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/SubscriptionBenefits-DVSfLULk.js 2.01 kB 2.01 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/telemetry-zZf2dHJ2.js 226 B 226 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/types-DT3N7am7.js 204 B 204 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/VideoPlayOverlay-D-ZhKuWc.js 1.35 kB 1.35 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/widget-NeEr3XWN.js 586 B 586 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetBoundingBox-BYbwNME9.js 283 B 283 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetBoundingBox-GzA4D-L-.js 3.19 kB 3.19 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetChart-BtoXUSiF.js 2.21 kB 2.21 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetColorPicker-DCbDfd19.js 2.9 kB 2.9 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetCurve-CIcV8pqy.js 9.36 kB 9.36 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetGalleria-DZSYhGzO.js 3.61 kB 3.61 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetImageCompare-oYMwrOjF.js 7 kB 7 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetInputText-0CncUIzA.js 1.86 kB 1.86 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetLayoutField-TvCt3ARa.js 1.98 kB 1.98 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetMarkdown-Cqdttdn1.js 2.93 kB 2.93 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/widgetPropFilter-DN03zIgB.js 1.1 kB 1.1 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetTextarea-B7IIifV6.js 3.96 kB 3.96 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetToggleSwitch-CTquGMvp.js 6.8 kB 6.8 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/widgetTypes-Br_tbhcL.js 393 B 393 B ⚪ 0 B ⚪ 0 B ⚪ 0 B

Status: 56 added / 56 removed

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

Caution

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

⚠️ Outside diff range comments (1)
src/components/ui/TypeformPopoverButton.vue (1)

22-41: ⚠️ Potential issue | 🟡 Minor

Icon-only buttons are missing accessible labels.

Both the mobile button (Line 22) and the popover trigger (Line 35) render only <i class="icon-[lucide--circle-help]" /> with no visible text and no aria-label. Screen reader users cannot determine the button's purpose. While v-bind="$attrs" allows forwarding from a parent, this relies on every call-site providing the attribute, which is fragile.

♿ Proposed fix — add a fallback `aria-label` on both buttons
  <Button
    v-if="isMobile"
    as="a"
    :href="`https://form.typeform.com/to/${dataTfWidget}`"
    target="_blank"
    variant="inverted"
    class="flex h-10 items-center justify-center gap-2.5 px-3 py-2"
+   aria-label="Feedback"
    v-bind="$attrs"
  >
    <i class="icon-[lucide--circle-help] size-4" />
  </Button>
  <Popover v-else>
    <template `#button`>
      <Button
        variant="inverted"
        class="flex h-10 items-center justify-center gap-2.5 px-3 py-2"
+       aria-label="Feedback"
        v-bind="$attrs"
      >
        <i class="icon-[lucide--circle-help] size-4" />
      </Button>
    </template>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ui/TypeformPopoverButton.vue` around lines 22 - 41, Icon-only
buttons lack accessible labels; add an explicit aria-label fallback on both
Button instances so screen readers always get a meaningful name. In the mobile
branch (the Button rendered when isMobile) and in the Popover trigger (the
Button inside template `#button`), add an aria-label that prefers any aria-label
passed via $attrs but falls back to a clear string like "Open help form" (for
the anchor variant consider "Open help form (opens in new tab)"). Ensure the
attribute is applied directly on those Button components so callers can still
override via v-bind="$attrs".
🧹 Nitpick comments (4)
src/composables/useAppMode.ts (2)

7-8: hasOutputs and enableAppBuilder are module-level mutable refs with no controlled setter.

Both are exposed directly, meaning any consumer can write useAppMode().hasOutputs.value = ... or hold a reference and mutate it freely. This creates invisible mutation pathways — it's unclear who "owns" these values and where they're updated. Consider exposing explicit setter functions (setHasOutputs, setEnableAppBuilder) or using readonly() to make the intent clear, similar to how setMode is the designated mutator for mode.

♻️ Suggested approach
 const hasOutputs = ref(true)
 const enableAppBuilder = ref(false)

 export function useAppMode() {
+  function setHasOutputs(val: boolean) {
+    hasOutputs.value = val
+  }
+  function setEnableAppBuilder(val: boolean) {
+    enableAppBuilder.value = val
+  }
   ...
   return {
     ...
-    hasOutputs,
-    enableAppBuilder,
+    hasOutputs: readonly(hasOutputs),
+    enableAppBuilder: readonly(enableAppBuilder),
+    setHasOutputs,
+    setEnableAppBuilder,
     setMode
   }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/composables/useAppMode.ts` around lines 7 - 8, hasOutputs and
enableAppBuilder are module-level mutable refs that consumers can mutate
directly; change useAppMode to prevent uncontrolled external mutation by making
the refs readonly when returned and adding explicit mutator functions (e.g.,
setHasOutputs and setEnableAppBuilder) alongside existing setMode, or
alternatively return readonly(hasOutputs)/readonly(enableAppBuilder) while
providing exported functions to update them from within the composable; update
the public API of useAppMode to return the readonly refs plus the new setter
functions and replace any internal writes to use the new setters.

10-42: New computed instances are created on every useAppMode() call.

mode, isBuilderMode, isAppMode, and isGraphMode are all freshly created inside useAppMode() — calling this composable in N components creates N independent (but functionally identical) computed watchers all tracking the same underlying store state. Moving the shared computed values to module level (alongside hasOutputs/enableAppBuilder) would allow all callers to share a single reactive dependency graph.

♻️ Suggested approach — hoist shared computeds to module scope
 const hasOutputs = ref(true)
 const enableAppBuilder = ref(false)
+
+const mode = computed(() => {
+  const wf = useWorkflowStore().activeWorkflow
+  return wf?.activeMode ?? wf?.initialMode ?? 'graph'
+})
+const isBuilderMode = computed(
+  () => mode.value === 'builder:select' || mode.value === 'builder:arrange'
+)
+const isAppMode = computed(
+  () => mode.value === 'app' || mode.value === 'builder:arrange'
+)
+const isGraphMode = computed(
+  () => mode.value === 'graph' || mode.value === 'builder:select'
+)

 export function useAppMode() {
-  const mode = computed(() => { ... })
-  const isBuilderMode = computed(...)
-  const isAppMode = computed(...)
-  const isGraphMode = computed(...)
   ...
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/composables/useAppMode.ts` around lines 10 - 42, The computed properties
mode, isBuilderMode, isAppMode, and isGraphMode are being recreated on every
call to useAppMode(); hoist these computed declarations to module scope (next to
existing hasOutputs/enableAppBuilder) so all callers share the same reactive
instances, keep setMode as the local function that mutates
useWorkflowStore().activeWorkflow, and update useAppMode() to simply return the
module-scoped mode, isBuilderMode, isAppMode, isGraphMode, hasOutputs,
enableAppBuilder, and setMode so no duplicate computed watchers are created.
src/platform/workflow/core/services/workflowService.ts (1)

360-369: useAppMode() called transiently for a single flag read.

useAppMode() is invoked inside the async afterLoadNewGraph function solely to access isAppMode.value once for the telemetry gate. This creates four computed instances (mode, isBuilderMode, isAppMode, isGraphMode) that are immediately discarded when the function returns. The same check can be derived directly without the composable:

♻️ Suggested simplification
-const { isAppMode } = useAppMode()
-
-// Determine the initial app mode for fresh loads from serialized state.
-// Tab switches don't need this — the mode is already on the workflow.
-const freshLoadMode: AppMode = workflowData.extra?.linearMode
-  ? 'app'
-  : 'graph'
-
-if (!isAppMode.value && freshLoadMode === 'app')
-  useTelemetry()?.trackEnterLinear({ source: 'workflow' })
+const freshLoadMode: AppMode = workflowData.extra?.linearMode ? 'app' : 'graph'
+
+if (freshLoadMode === 'app') {
+  const wf = workflowStore.activeWorkflow
+  const currentMode = wf?.activeMode ?? wf?.initialMode ?? 'graph'
+  const currentIsAppMode = currentMode === 'app' || currentMode === 'builder:arrange'
+  if (!currentIsAppMode) useTelemetry()?.trackEnterLinear({ source: 'workflow' })
+}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/platform/workflow/core/services/workflowService.ts` around lines 360 -
369, The transient call to useAppMode() inside afterLoadNewGraph should be
removed; instead derive the current app mode directly from the workflow object
and use that for the telemetry gate. Replace the useAppMode() invocation and the
isAppMode.value check with a direct read of the workflow's mode (e.g., determine
currentMode from workflow.mode or workflow.value.mode depending on how workflow
is exposed) and then call useTelemetry()?.trackEnterLinear({ source: 'workflow'
}) only when currentMode is not 'app' and freshLoadMode === 'app'; update the
code around freshLoadMode and the telemetry condition to use this direct check
(references: useAppMode(), isAppMode.value, afterLoadNewGraph,
workflowData.extra?.linearMode, useTelemetry()?.trackEnterLinear).
src/components/builder/useBuilderSave.ts (1)

22-30: Consider calling onBuilderSave() directly from setSaving instead of via a watcher.

saving is mutated exclusively through setSaving and resetSaving, so the watch adds an async tick of indirection without providing any resilience benefit. A direct call keeps the control flow synchronous and easier to trace.

♻️ Suggested simplification
  const saving = ref(false)

- watch(saving, (value) => {
-   if (value) void onBuilderSave()
- })
-
  function setSaving(value: boolean) {
    saving.value = value
+   if (value) void onBuilderSave()
  }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/builder/useBuilderSave.ts` around lines 22 - 30, The watcher
indirection is unnecessary because saving is only changed via
setSaving/resetSaving; remove the watch and instead invoke onBuilderSave()
directly from setSaving when setting true (use the same fire-and-forget pattern,
e.g., void onBuilderSave()), while still updating saving.value; also ensure
resetSaving uses setSaving(false) so all mutations go through that single
function; update references to the removed watch accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/platform/workflow/core/services/workflowService.test.ts`:
- Around line 21-52: Replace the double-cast assertions with TypeScript's
`satisfies` to preserve shape-checking: for the changeTracker assignment in
createModeTestWorkflow, replace the "as Partial<ChangeTracker> as ChangeTracker"
double-cast by using "satisfies ChangeTracker" on the object literal passed to
markRaw (i.e. markRaw({ store: vi.fn(), reset: vi.fn(), restore: vi.fn() }
satisfies ChangeTracker)); and for makeWorkflowData, return the object literal
using "satisfies ComfyWorkflowJSON" instead of "as Partial<ComfyWorkflowJSON> as
ComfyWorkflowJSON" so the returned literal is validated against
ComfyWorkflowJSON's shape.

In `@src/renderer/core/canvas/canvasStore.ts`:
- Around line 46-51: The computed linearMode getter/setter is asymmetric:
isAppMode() treats both 'app' and 'builder:arrange' as true while the linearMode
setter only calls setMode('app' | 'graph'), which can silently demote builder
modes; update the linearMode setter (or add a clear comment above the computed)
to preserve builder context—e.g., detect current mode via isAppMode and current
mode string and, when setting true, only call setMode('app') if not already a
builder mode or otherwise leave builder:* modes unchanged; reference the
computed named linearMode, the getter isAppMode, and the mode mutator setMode
(and the 'builder:arrange' mode) when implementing or documenting this behavior.

In `@src/renderer/extensions/linearMode/OutputHistory.vue`:
- Around line 290-292: The clear button should be disabled when there are no
pending tasks (queueCount ≤ 1); update the button that triggers clearQueue to
bind its disabled state to the expression queueCount <= 1 (match the same
condition used for the badge) so the button is non-interactive when only the
active task remains; locate the element that calls clearQueue in
OutputHistory.vue (near OutputHistoryActiveQueueItem and the queueCount
references) and add the disabled binding to that button.

In `@src/renderer/extensions/linearMode/OutputHistoryActiveQueueItem.vue`:
- Around line 50-54: The badge overlay div in OutputHistoryActiveQueueItem.vue
renders a bare numeric visual ("queueCount") which will be read out of context
by screen readers; modify the element that renders the count (the div with
v-if="queueCount > 1" and v-text="queueCount") to include aria-hidden="true" so
assistive tech ignores it (the parent Button already has the descriptive
aria-label).
- Around line 40-47: The Clear Pending Tasks button should be disabled when
there are no pending tasks (queueCount <= 1); update the Button in
OutputHistoryActiveQueueItem.vue to bind its disabled state to (queueCount <= 1)
so it appears non-actionable when only the running task exists, and ensure the
click handler clearQueue(close) remains unchanged (so clicks are ignored when
disabled); reference the Button element, the queueCount reactive/computed value,
and the clearQueue method (and the Comfy.ClearPendingTasks behavior) when
applying this change.

---

Outside diff comments:
In `@src/components/ui/TypeformPopoverButton.vue`:
- Around line 22-41: Icon-only buttons lack accessible labels; add an explicit
aria-label fallback on both Button instances so screen readers always get a
meaningful name. In the mobile branch (the Button rendered when isMobile) and in
the Popover trigger (the Button inside template `#button`), add an aria-label that
prefers any aria-label passed via $attrs but falls back to a clear string like
"Open help form" (for the anchor variant consider "Open help form (opens in new
tab)"). Ensure the attribute is applied directly on those Button components so
callers can still override via v-bind="$attrs".

---

Nitpick comments:
In `@src/components/builder/useBuilderSave.ts`:
- Around line 22-30: The watcher indirection is unnecessary because saving is
only changed via setSaving/resetSaving; remove the watch and instead invoke
onBuilderSave() directly from setSaving when setting true (use the same
fire-and-forget pattern, e.g., void onBuilderSave()), while still updating
saving.value; also ensure resetSaving uses setSaving(false) so all mutations go
through that single function; update references to the removed watch
accordingly.

In `@src/composables/useAppMode.ts`:
- Around line 7-8: hasOutputs and enableAppBuilder are module-level mutable refs
that consumers can mutate directly; change useAppMode to prevent uncontrolled
external mutation by making the refs readonly when returned and adding explicit
mutator functions (e.g., setHasOutputs and setEnableAppBuilder) alongside
existing setMode, or alternatively return
readonly(hasOutputs)/readonly(enableAppBuilder) while providing exported
functions to update them from within the composable; update the public API of
useAppMode to return the readonly refs plus the new setter functions and replace
any internal writes to use the new setters.
- Around line 10-42: The computed properties mode, isBuilderMode, isAppMode, and
isGraphMode are being recreated on every call to useAppMode(); hoist these
computed declarations to module scope (next to existing
hasOutputs/enableAppBuilder) so all callers share the same reactive instances,
keep setMode as the local function that mutates
useWorkflowStore().activeWorkflow, and update useAppMode() to simply return the
module-scoped mode, isBuilderMode, isAppMode, isGraphMode, hasOutputs,
enableAppBuilder, and setMode so no duplicate computed watchers are created.

In `@src/platform/workflow/core/services/workflowService.ts`:
- Around line 360-369: The transient call to useAppMode() inside
afterLoadNewGraph should be removed; instead derive the current app mode
directly from the workflow object and use that for the telemetry gate. Replace
the useAppMode() invocation and the isAppMode.value check with a direct read of
the workflow's mode (e.g., determine currentMode from workflow.mode or
workflow.value.mode depending on how workflow is exposed) and then call
useTelemetry()?.trackEnterLinear({ source: 'workflow' }) only when currentMode
is not 'app' and freshLoadMode === 'app'; update the code around freshLoadMode
and the telemetry condition to use this direct check (references: useAppMode(),
isAppMode.value, afterLoadNewGraph, workflowData.extra?.linearMode,
useTelemetry()?.trackEnterLinear).

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d7546e6 and f51bc1c.

📒 Files selected for processing (23)
  • src/components/appMode/AppModeToolbar.vue
  • src/components/builder/BuilderToolbar.vue
  • src/components/builder/useBuilderSave.ts
  • src/components/graph/GraphCanvas.vue
  • src/components/sidebar/tabs/SidebarTabTemplate.vue
  • src/components/ui/TypeformPopoverButton.vue
  • src/composables/useAppMode.ts
  • src/locales/en/main.json
  • src/platform/workflow/core/services/workflowService.test.ts
  • src/platform/workflow/core/services/workflowService.ts
  • src/platform/workflow/management/stores/comfyWorkflow.ts
  • src/renderer/core/canvas/canvasStore.ts
  • src/renderer/extensions/linearMode/LinearArrange.vue
  • src/renderer/extensions/linearMode/LinearControls.vue
  • src/renderer/extensions/linearMode/LinearPreview.vue
  • src/renderer/extensions/linearMode/LinearWelcome.vue
  • src/renderer/extensions/linearMode/OutputHistory.vue
  • src/renderer/extensions/linearMode/OutputHistoryActiveQueueItem.vue
  • src/renderer/extensions/linearMode/linearOutputStore.test.ts
  • src/renderer/extensions/linearMode/linearOutputStore.ts
  • src/stores/appModeStore.ts
  • src/views/GraphView.vue
  • src/views/LinearView.vue
💤 Files with no reviewable changes (1)
  • src/stores/appModeStore.ts

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
src/platform/workflow/core/services/workflowService.test.ts (1)

133-147: Prefer satisfies over double assertions in the helper.

The as Partial<ComfyWorkflow>as ComfyWorkflow pattern can hide shape drift. Using satisfies keeps excess/field checking while still returning a ComfyWorkflow.

♻️ Suggested refactor
-  } as Partial<ComfyWorkflow>
+  } satisfies Partial<ComfyWorkflow>
   return wf as ComfyWorkflow
#!/bin/bash
# Verify TS version supports `satisfies` (>=4.9) and check existing usage.
rg -n '"typescript"\s*:' package.json pnpm-lock.yaml 2>/dev/null | head -20
rg -n "satisfies" src --type ts --type tsx | head -20

Based on learnings: In test files matching **/*.test.ts under src, when creating test helper functions that construct mock objects implementing an interface (e.g., AssetItem), prefer using satisfies InterfaceType for shape validation instead of type assertions like as Partial as InterfaceType or as any.

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

In `@src/platform/workflow/core/services/workflowService.test.ts` around lines 133
- 147, The helper createWorkflow currently casts a partial object to
ComfyWorkflow using "as Partial<ComfyWorkflow> ... as ComfyWorkflow", which can
hide shape drift; change the return construction to use TypeScript's "satisfies
ComfyWorkflow" on the object literal so the compiler validates fields (keep the
same properties: pendingWarnings, path, isLoaded, activeState, changeTracker and
the options.loadable branch) and return the object as a ComfyWorkflow-compatible
value without double assertions.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@src/platform/workflow/core/services/workflowService.test.ts`:
- Around line 133-147: The helper createWorkflow currently casts a partial
object to ComfyWorkflow using "as Partial<ComfyWorkflow> ... as ComfyWorkflow",
which can hide shape drift; change the return construction to use TypeScript's
"satisfies ComfyWorkflow" on the object literal so the compiler validates fields
(keep the same properties: pendingWarnings, path, isLoaded, activeState,
changeTracker and the options.loadable branch) and return the object as a
ComfyWorkflow-compatible value without double assertions.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f51bc1c and 0c22046.

📒 Files selected for processing (3)
  • src/composables/canvas/useSelectedLiteGraphItems.test.ts
  • src/platform/workflow/core/services/workflowService.test.ts
  • src/utils/__tests__/litegraphTestUtils.ts

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (1)
src/platform/workflow/core/services/workflowService.test.ts (1)

136-150: Prefer satisfies over double casts for the test helper object.

The current as Partial<ComfyWorkflow> ... as ComfyWorkflow pattern hides missing fields. Using satisfies preserves shape validation while keeping the test helper lightweight.

♻️ Proposed refactor
-  const wf = {
+  const wf = {
     pendingWarnings: warnings,
     ...(options.loadable && {
       path: options.path ?? 'workflows/test.json',
       isLoaded: true,
       activeState: { nodes: [], links: [] },
       changeTracker: { reset: vi.fn(), restore: vi.fn() }
     })
-  } as Partial<ComfyWorkflow>
+  } satisfies Partial<ComfyWorkflow>
   return wf as ComfyWorkflow
 }
#!/bin/bash
# Confirm TypeScript version supports the `satisfies` operator (TS 4.9+).
rg -n '"typescript"' package.json pnpm-lock.yaml

Based on learnings "In test files matching **/*.test.ts under src, when creating test helper functions that construct mock objects implementing an interface (e.g., AssetItem), prefer using satisfies InterfaceType for shape validation instead of type assertions like as Partial as InterfaceType or as any."

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

In `@src/platform/workflow/core/services/workflowService.test.ts` around lines 136
- 150, The test helper createWorkflow uses double type assertions (as
Partial<ComfyWorkflow> ... as ComfyWorkflow) which hides missing fields; replace
the double cast with TypeScript's satisfies operator to enforce shape validation
while keeping the object lightweight—update the created wf object expression to
end with "satisfies Partial<ComfyWorkflow>" (or "satisfies ComfyWorkflow" if you
include all required fields) instead of the two as-casts, keeping the same
conditional loadable properties (path, isLoaded, activeState, changeTracker) and
preserving pendingWarnings so the compiler validates the structure of
createWorkflow against ComfyWorkflow.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/components/builder/useBuilderSave.ts`:
- Around line 39-46: The auto-resave path currently swallows errors in
useBuilderSave.ts (inside the branch handling !workflow.isTemporary &&
workflow.initialMode != null) so users get no feedback; update the catch block
for the await workflowService.saveWorkflow(workflow) call to surface the failure
rather than silently calling resetSaving(): either show a localized error
toast/dialog using the existing showErrorDialog or toast helper (provide i18n
keys, e.g. builder.save.failed and builder.save.failedDetails) including
workflow.filename and workflow.initialMode context, then call resetSaving(); or
rethrow the error to be handled by a higher-level handler if that pattern is
used elsewhere—ensure you reference workflowService.saveWorkflow,
showSuccessDialog, resetSaving and keep behavior consistent with other save
error handling in this module.

In `@src/platform/workflow/core/services/workflowService.ts`:
- Around line 10-31: linearModeToAppMode currently treats any truthy value as
boolean which can mis-map corrupted values like the string "graph"; update
linearModeToAppMode to first verify the input is a boolean (e.g., typeof
linearMode === 'boolean') and only then return 'app' for true or 'graph' for
false, otherwise return null to avoid switching modes on invalid data; modify
the function linearModeToAppMode accordingly.

---

Duplicate comments:
In `@src/platform/workflow/core/services/workflowService.test.ts`:
- Around line 136-150: The test helper createWorkflow uses double type
assertions (as Partial<ComfyWorkflow> ... as ComfyWorkflow) which hides missing
fields; replace the double cast with TypeScript's satisfies operator to enforce
shape validation while keeping the object lightweight—update the created wf
object expression to end with "satisfies Partial<ComfyWorkflow>" (or "satisfies
ComfyWorkflow" if you include all required fields) instead of the two as-casts,
keeping the same conditional loadable properties (path, isLoaded, activeState,
changeTracker) and preserving pendingWarnings so the compiler validates the
structure of createWorkflow against ComfyWorkflow.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0c22046 and e3fa7b6.

📒 Files selected for processing (14)
  • src/components/builder/useBuilderSave.ts
  • src/components/topbar/WorkflowTab.vue
  • src/composables/useAppMode.ts
  • src/composables/useCoreCommands.ts
  • src/locales/en/main.json
  • src/platform/workflow/core/services/workflowService.test.ts
  • src/platform/workflow/core/services/workflowService.ts
  • src/platform/workflow/management/stores/comfyWorkflow.ts
  • src/platform/workflow/management/stores/workflowStore.ts
  • src/renderer/core/canvas/canvasStore.ts
  • src/renderer/extensions/linearMode/LinearControls.vue
  • src/renderer/extensions/linearMode/OutputHistoryActiveQueueItem.vue
  • src/utils/__tests__/litegraphTestUtils.ts
  • src/views/GraphView.vue
💤 Files with no reviewable changes (1)
  • src/composables/useCoreCommands.ts
✅ Files skipped from review due to trivial changes (1)
  • src/platform/workflow/management/stores/workflowStore.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/composables/useAppMode.ts
  • src/renderer/extensions/linearMode/OutputHistoryActiveQueueItem.vue
  • src/renderer/core/canvas/canvasStore.ts

@pythongosssss pythongosssss force-pushed the pysssss/appmode-updates branch from e3fa7b6 to 1b9ed06 Compare February 25, 2026 15:32
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

Caution

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

⚠️ Outside diff range comments (1)
src/renderer/extensions/linearMode/linearOutputStore.ts (1)

246-255: ⚠️ Potential issue | 🟠 Major

Bootstrap active job when app mode becomes active

On Line 248, when mode flips to active, you add the executed listener but do not initialize state for an already-running executionStore.activeJobId. After a tab/mode switch, this can leave trackedJobId unset and drop previews/executed outputs for the current job.

💡 Proposed fix
   watch(
     isAppMode,
     (active, wasActive) => {
       if (active) {
         api.addEventListener('executed', handleExecuted)
+        const jobId = executionStore.activeJobId
+        if (jobId && trackedJobId.value !== jobId) {
+          onJobStart(jobId)
+          const url = jobPreviewStore.previewsByPromptId[jobId]
+          if (url) onLatentPreview(jobId, url)
+        }
       } else if (wasActive) {
         api.removeEventListener('executed', handleExecuted)
         reset()
       }
     },
     { immediate: true }
   )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/extensions/linearMode/linearOutputStore.ts` around lines 246 -
255, When switching into app mode you add the executed listener but never
bootstrap state for an already-running job, so ensure after
api.addEventListener('executed', handleExecuted) you check
executionStore.activeJobId and initialize trackedJobId and any preview/state for
that job; specifically, set trackedJobId = executionStore.activeJobId (or call
the existing initializer used in this module) and invoke the same logic that
would run on an 'executed' event (e.g., call handleExecuted or the routine that
loads previews/outputs) so previews aren’t dropped; keep reset() for the
deactivation branch as-is.
♻️ Duplicate comments (1)
src/platform/workflow/core/services/workflowService.ts (1)

29-31: ⚠️ Potential issue | 🟡 Minor

Validate linearMode type before converting it to AppMode.

Line 30 currently treats any truthy value as 'app'. A malformed value (for example, a string) can flip mode unexpectedly.

🛠️ Proposed fix
 function linearModeToAppMode(linearMode: unknown): AppMode | null {
-  return linearMode == null ? null : linearMode ? 'app' : 'graph'
+  if (typeof linearMode !== 'boolean') return null
+  return linearMode ? 'app' : 'graph'
 }
As per coding guidelines "Validate trusted sources before processing".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/platform/workflow/core/services/workflowService.ts` around lines 29 - 31,
The function linearModeToAppMode incorrectly treats any truthy value as 'app';
update it to validate that linearMode is either a boolean or null before
converting: if linearMode === null return null; if typeof linearMode ===
'boolean' return linearMode ? 'app' : 'graph'; otherwise return null (or handle
as a malformed input per policy). Change the logic inside linearModeToAppMode to
explicitly check typeof linearMode === 'boolean' so only true/false map to
AppMode and other types are rejected.
🧹 Nitpick comments (2)
src/components/builder/IoItem.vue (1)

38-44: Hide the menu trigger when there are no actions.

When both rename and remove are absent, entries is empty but the trigger still renders. Consider gating <Popover> with entries.length > 0.

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

In `@src/components/builder/IoItem.vue` around lines 38 - 44, The Popover trigger
still renders even when there are no actions because <Popover :entries> is
always mounted; update the IoItem.vue template to conditionally render the
Popover only when the entries array has items (e.g., check entries &&
entries.length > 0) so the <template `#button`> and <Button>
(icon-[lucide--ellipsis]) are not shown when both rename and remove are absent.
src/renderer/extensions/linearMode/LinearControls.vue (1)

61-80: Make mappedSelections order-independent.

Current grouping relies on contiguous selectedInputs for the same node. Grouping by nodeId first will make it deterministic and easier to maintain.

♻️ Suggested refactor
-import { partition, remove, takeWhile } from 'es-toolkit'
+import { partition } from 'es-toolkit'
@@
 const mappedSelections = computed(() => {
-  let unprocessedInputs = [...appModeStore.selectedInputs]
-  //FIXME strict typing here
-  const processedInputs: ReturnType<typeof nodeToNodeData>[] = []
-  while (unprocessedInputs.length) {
-    const nodeId = unprocessedInputs[0][0]
-    const inputGroup = takeWhile(
-      unprocessedInputs,
-      ([id]) => id === nodeId
-    ).map(([, widgetName]) => widgetName)
-    unprocessedInputs = unprocessedInputs.slice(inputGroup.length)
-    const node = app.rootGraph.getNodeById(nodeId)
-    if (!node) continue
-
-    const nodeData = nodeToNodeData(node)
-    remove(nodeData.widgets ?? [], (w) => !inputGroup.includes(w.name))
-    processedInputs.push(nodeData)
-  }
-  return processedInputs
+  const inputsByNodeId = new Map<number, Set<string>>()
+  for (const [nodeId, widgetName] of appModeStore.selectedInputs) {
+    let names = inputsByNodeId.get(nodeId)
+    if (!names) {
+      names = new Set<string>()
+      inputsByNodeId.set(nodeId, names)
+    }
+    names.add(widgetName)
+  }
+
+  const processedInputs: ReturnType<typeof nodeToNodeData>[] = []
+  for (const [nodeId, names] of inputsByNodeId) {
+    const node = app.rootGraph.getNodeById(nodeId)
+    if (!node) continue
+    const nodeData = nodeToNodeData(node)
+    nodeData.widgets = (nodeData.widgets ?? []).filter((w) =>
+      names.has(w.name)
+    )
+    processedInputs.push(nodeData)
+  }
+  return processedInputs
 })
As per coding guidelines "Use refactoring techniques to make complex code simpler".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/extensions/linearMode/LinearControls.vue` around lines 61 - 80,
mappedSelections currently assumes appModeStore.selectedInputs for the same node
are contiguous and uses takeWhile to group, which breaks if order changes;
replace that logic by first building a Map (or record) keyed by nodeId that
accumulates widgetNames from appModeStore.selectedInputs, then iterate the map
entries, call app.rootGraph.getNodeById(nodeId) and nodeToNodeData(node), filter
nodeData.widgets to only include names from the accumulated widget list (use
remove or Array.prototype.filter), and push the resulting nodeData into
processedInputs so grouping is deterministic and order-independent; update the
mappedSelections computed to use this Map-based grouping and preserve the
processedInputs type and existing helpers (nodeToNodeData, remove).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/components/builder/AppBuilder.vue`:
- Around line 49-50: Guard access to node.inputs before calling .find(): replace
the unsafe lookup with a safe check such as const input =
Array.isArray(node.inputs) ? node.inputs.find((i) => i.widget?.name ===
widget.name) : undefined (or use node.inputs?.find if optional chaining is
preferred), and then keep const rename = input && (() => renameWidget(widget,
input)); this prevents runtime errors when node.inputs is absent while still
using the existing symbols node.inputs, input, rename, renameWidget, and widget.
- Around line 166-170: The computed renderedOutputs currently uses a non-null
assertion on canvas.graph (canvas.graph!.nodes) which can crash if graph is
transiently null; update renderedOutputs to guard access to canvas.graph (e.g.,
check canvas.graph before using .nodes or use optional chaining and a fallback
empty array) and still map through nodeToDisplayTuple, keeping the
appModeStore.selectedOutputs reference if needed—ensure the computed returns an
empty array when canvas.graph is null/undefined to avoid runtime errors.
- Line 41: The setup currently calls
workflowStore.activeWorkflow?.changeTracker?.reset(), which clears
unsaved-change tracking on entering builder mode; remove this call so entering
builder does not drop modification state. Locate the reset invocation
(workflowStore.activeWorkflow?.changeTracker?.reset()) in AppBuilder.vue and
delete it or restrict it to only run in explicit new-workflow flows (e.g.,
inside a createNewWorkflow or initializeNewWorkflow handler) so existing
workflows retain their changeTracker state.

In `@src/components/builder/IoItem.vue`:
- Around line 40-42: The icon-only Button in IoItem.vue (the Button wrapping <i
class="icon-[lucide--ellipsis]"/>) needs an accessible name: update that Button
instance to include a clear aria-label (e.g. aria-label="More actions" or
aria-label="Open options") or supply visually-hidden text inside the Button
(screen-reader-only span) so assistive tech can announce it; do not add
aria-labels to any sibling text buttons—only add the label to this icon-only
trigger.

In `@src/components/common/DraggableList.vue`:
- Around line 21-45: The reorder logic can call splice with invalid indices when
oldPosition or newPosition is -1; before mutating modelValue in the block using
oldPosition, newPosition, modelValue.value and draggableItem, check and guard:
compute oldPosition via the loop (or fallback using
modelValue.value.indexOf(this.draggableItem)), compute newPosition from
reorderedItems.indexOf(this.draggableItem), and if either is -1 or equal (no-op)
return early; otherwise proceed with const itemList = modelValue.value,
splice(oldPosition, 1) and splice(newPosition, 0, item) to update modelValue.
Ensure you reference getAllItems, reorderedItems, isItemToggled and isItemAbove
only for building reorderedItems and do not perform splices when indices are
invalid.

In `@src/stores/appModeStore.ts`:
- Around line 17-19: The store currently exports selectedInputs and
selectedOutputs as reactive arrays which breaks v-model reassignment from
DraggableList; change both to use ref<...>([]) instead of reactive(...), update
hasOutputs to use hasOutputs = computed(() => !!selectedOutputs.value.length),
and update all internal manipulations (push, splice, spreads, assignments) to
operate on .value (e.g., selectedOutputs.value = [...]) so the v-model bindings
in AppBuilder.vue work correctly and the proxy reference remains intact.

---

Outside diff comments:
In `@src/renderer/extensions/linearMode/linearOutputStore.ts`:
- Around line 246-255: When switching into app mode you add the executed
listener but never bootstrap state for an already-running job, so ensure after
api.addEventListener('executed', handleExecuted) you check
executionStore.activeJobId and initialize trackedJobId and any preview/state for
that job; specifically, set trackedJobId = executionStore.activeJobId (or call
the existing initializer used in this module) and invoke the same logic that
would run on an 'executed' event (e.g., call handleExecuted or the routine that
loads previews/outputs) so previews aren’t dropped; keep reset() for the
deactivation branch as-is.

---

Duplicate comments:
In `@src/platform/workflow/core/services/workflowService.ts`:
- Around line 29-31: The function linearModeToAppMode incorrectly treats any
truthy value as 'app'; update it to validate that linearMode is either a boolean
or null before converting: if linearMode === null return null; if typeof
linearMode === 'boolean' return linearMode ? 'app' : 'graph'; otherwise return
null (or handle as a malformed input per policy). Change the logic inside
linearModeToAppMode to explicitly check typeof linearMode === 'boolean' so only
true/false map to AppMode and other types are rejected.

---

Nitpick comments:
In `@src/components/builder/IoItem.vue`:
- Around line 38-44: The Popover trigger still renders even when there are no
actions because <Popover :entries> is always mounted; update the IoItem.vue
template to conditionally render the Popover only when the entries array has
items (e.g., check entries && entries.length > 0) so the <template `#button`> and
<Button> (icon-[lucide--ellipsis]) are not shown when both rename and remove are
absent.

In `@src/renderer/extensions/linearMode/LinearControls.vue`:
- Around line 61-80: mappedSelections currently assumes
appModeStore.selectedInputs for the same node are contiguous and uses takeWhile
to group, which breaks if order changes; replace that logic by first building a
Map (or record) keyed by nodeId that accumulates widgetNames from
appModeStore.selectedInputs, then iterate the map entries, call
app.rootGraph.getNodeById(nodeId) and nodeToNodeData(node), filter
nodeData.widgets to only include names from the accumulated widget list (use
remove or Array.prototype.filter), and push the resulting nodeData into
processedInputs so grouping is deterministic and order-independent; update the
mappedSelections computed to use this Map-based grouping and preserve the
processedInputs type and existing helpers (nodeToNodeData, remove).

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e3fa7b6 and 1b9ed06.

📒 Files selected for processing (35)
  • src/components/LiteGraphCanvasSplitterOverlay.vue
  • src/components/appMode/AppModeToolbar.vue
  • src/components/builder/AppBuilder.vue
  • src/components/builder/BuilderToolbar.vue
  • src/components/builder/IoItem.vue
  • src/components/builder/useBuilderSave.ts
  • src/components/common/DraggableList.vue
  • src/components/graph/GraphCanvas.vue
  • src/components/rightSidePanel/subgraph/SubgraphEditor.vue
  • src/components/rightSidePanel/subgraph/SubgraphNodeWidget.vue
  • src/components/sidebar/tabs/SidebarTabTemplate.vue
  • src/components/topbar/WorkflowTab.vue
  • src/components/ui/TypeformPopoverButton.vue
  • src/composables/useAppMode.ts
  • src/composables/useCoreCommands.ts
  • src/lib/litegraph/src/types/widgets.ts
  • src/locales/en/main.json
  • src/platform/workflow/core/services/workflowService.test.ts
  • src/platform/workflow/core/services/workflowService.ts
  • src/platform/workflow/management/stores/comfyWorkflow.ts
  • src/platform/workflow/management/stores/workflowStore.ts
  • src/platform/workflow/validation/schemas/workflowSchema.ts
  • src/renderer/core/canvas/canvasStore.ts
  • src/renderer/extensions/linearMode/LinearArrange.vue
  • src/renderer/extensions/linearMode/LinearControls.vue
  • src/renderer/extensions/linearMode/LinearPreview.vue
  • src/renderer/extensions/linearMode/LinearWelcome.vue
  • src/renderer/extensions/linearMode/OutputHistory.vue
  • src/renderer/extensions/linearMode/OutputHistoryActiveQueueItem.vue
  • src/renderer/extensions/linearMode/linearOutputStore.test.ts
  • src/renderer/extensions/linearMode/linearOutputStore.ts
  • src/stores/appModeStore.ts
  • src/utils/__tests__/litegraphTestUtils.ts
  • src/views/GraphView.vue
  • src/views/LinearView.vue
💤 Files with no reviewable changes (2)
  • src/composables/useCoreCommands.ts
  • src/lib/litegraph/src/types/widgets.ts
🚧 Files skipped from review as they are similar to previous changes (11)
  • src/components/topbar/WorkflowTab.vue
  • src/utils/tests/litegraphTestUtils.ts
  • src/renderer/extensions/linearMode/linearOutputStore.test.ts
  • src/renderer/extensions/linearMode/OutputHistory.vue
  • src/renderer/extensions/linearMode/LinearPreview.vue
  • src/components/ui/TypeformPopoverButton.vue
  • src/components/sidebar/tabs/SidebarTabTemplate.vue
  • src/renderer/extensions/linearMode/OutputHistoryActiveQueueItem.vue
  • src/composables/useAppMode.ts
  • src/components/appMode/AppModeToolbar.vue
  • src/components/builder/useBuilderSave.ts

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Caution

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

⚠️ Outside diff range comments (1)
src/components/builder/useBuilderSave.ts (1)

79-83: ⚠️ Potential issue | 🟡 Minor

saving is never reset on the early return when workflow is null.

If workflowStore.activeWorkflow is null when handleSave is invoked, the function returns at line 82 without calling resetSaving() or closeSaveDialog(). saving stays true and the save dialog remains open until the user manually dismisses it (where dialogComponentProps.onClose: resetSaving eventually fires). The same guard should clean up state.

🐛 Proposed fix
  async function handleSave(filename: string, openAsApp: boolean) {
    try {
      const workflow = workflowStore.activeWorkflow
-     if (!workflow) return
+     if (!workflow) {
+       closeSaveDialog()
+       resetSaving()
+       return
+     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/builder/useBuilderSave.ts` around lines 79 - 83, In
handleSave, if workflowStore.activeWorkflow is null the function returns early
but never resets the component state; update handleSave to call resetSaving()
and closeSaveDialog() (or run the existing cleanup) before returning when
workflow is null, or wrap the try body in a try/finally and move
resetSaving()/closeSaveDialog() into finally so saving is always cleared and the
dialog closed; reference the handleSave function, workflowStore.activeWorkflow
guard, and the resetSaving/closeSaveDialog helpers when making the change.
♻️ Duplicate comments (1)
src/renderer/extensions/linearMode/OutputHistoryActiveQueueItem.vue (1)

41-41: ⚠️ Potential issue | 🟡 Minor

Disable “Clear Pending Tasks” when queueCount <= 1.

On Line 41, :disabled="queueCount === 0" still enables the action when only the active task exists (queueCount === 1). This should be non-actionable until there are actual pending tasks.

Suggested fix
-          :disabled="queueCount === 0"
+          :disabled="queueCount <= 1"

Based on learnings: In the ComfyUI frontend queue system (useQueuePendingTaskCountStore().count), count includes the currently executing task, and “Clear Pending Tasks” should only be enabled when count > 1.

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

In `@src/renderer/extensions/linearMode/OutputHistoryActiveQueueItem.vue` at line
41, The "Clear Pending Tasks" button is currently disabled only when queueCount
=== 0 but must be disabled when there are no pending tasks (queueCount <= 1). In
OutputHistoryActiveQueueItem.vue update the :disabled binding for the clear
action (the element using :disabled="queueCount === 0") to check queueCount <= 1
instead so the action remains non-actionable when only the active task exists;
keep the existing reactive variable name queueCount and adjust only the boolean
expression.
🧹 Nitpick comments (3)
src/components/builder/useBuilderSave.ts (1)

26-34: Consider replacing the boolean-trigger watch pattern with a direct function call.

The watch fires onBuilderSave whenever saving flips to true, making the trigger mechanism indirect — callers manipulate a boolean flag to initiate side effects rather than calling a function. Collapsing the trigger into setSaving directly keeps the intent explicit and removes the watcher.

♻️ Proposed refactor
-  const saving = ref(false)
-
-  watch(saving, (value) => {
-    if (value) void onBuilderSave()
-  })
-
-  function setSaving(value: boolean) {
-    saving.value = value
-  }
+  const saving = ref(false)
+
+  async function setSaving(value: boolean) {
+    saving.value = value
+    if (value) await onBuilderSave()
+  }

This eliminates the watcher, makes the async nature of the trigger visible at the call site, and removes one reactive dependency from the composable. Callers that use void setSaving(true) get the same fire-and-forget behaviour.

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

In `@src/components/builder/useBuilderSave.ts` around lines 26 - 34, The current
watch on saving triggers onBuilderSave indirectly; remove the watch and make
setSaving perform the trigger explicitly: keep the saving = ref(false) and the
setSaving(value: boolean) function, but delete the watch(saving...) block and
modify setSaving so that it updates saving.value and, if value is true, calls
onBuilderSave() (use void onBuilderSave() for fire-and-forget or await it if you
need sequential behavior). This keeps the saving state but collapses the trigger
into setSaving and preserves callers that do setSaving(true).
src/platform/workflow/core/services/workflowService.ts (2)

133-144: Redundant syncLinearMode / checkState() in the self-overwrite branch.

When isSelfOverwrite is true, lines 133–134 run syncLinearMode and changeTracker?.checkState(), then immediately delegate to saveWorkflow(workflow) (line 137), which is a non-temporary workflow and therefore runs the same two calls again at lines 157–158. The sync and state-check are effectively applied twice with no intermediate state change.

♻️ Proposed refactor — skip pre-sync in the self-overwrite sub-path

Extract the syncLinearMode + checkState pair into the branches that need them rather than calling unconditionally before the branch:

-   if (options.initialMode) workflow.initialMode = options.initialMode
-
-   syncLinearMode(workflow, [app.rootGraph], { flushLinearData: true })
-   workflow.changeTracker?.checkState()
-
-   if (isSelfOverwrite) {
-     await saveWorkflow(workflow)
+   if (options.initialMode) workflow.initialMode = options.initialMode
+
+   if (isSelfOverwrite) {
+     await saveWorkflow(workflow)  // saveWorkflow syncs + checkState internally
    } else if (workflow.isTemporary) {
+     syncLinearMode(workflow, [app.rootGraph], { flushLinearData: true })
+     workflow.changeTracker?.checkState()
      await renameWorkflow(workflow, newPath)
      await workflowStore.saveWorkflow(workflow)
    } else {
+     syncLinearMode(workflow, [app.rootGraph], { flushLinearData: true })
+     workflow.changeTracker?.checkState()
      const tempWorkflow = workflowStore.saveAs(workflow, newPath)
      await openWorkflow(tempWorkflow)
      await workflowStore.saveWorkflow(tempWorkflow)
    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/platform/workflow/core/services/workflowService.ts` around lines 133 -
144, The pre-branch calls to syncLinearMode(...) and
workflow.changeTracker?.checkState() are being run unconditionally and then run
again inside saveWorkflow when isSelfOverwrite is true; to fix, remove the
unconditional pre-sync and instead call syncLinearMode(workflow,
[app.rootGraph], { flushLinearData: true }) and
workflow.changeTracker?.checkState() only in the branches that actually need
them (i.e., before renameWorkflow + workflowStore.saveWorkflow for
workflow.isTemporary==true and before creating
tempWorkflow/openWorkflow/saveWorkflow for the non-self-overwrite save-as path),
leaving the saveWorkflow(workflow) path for isSelfOverwrite untouched; use the
existing symbols syncLinearMode, changeTracker?.checkState, saveWorkflow,
renameWorkflow, workflowStore.saveAs, openWorkflow, workflow.isTemporary and
isSelfOverwrite to locate and apply the change.

133-144: Redundant syncLinearMode / checkState() in the self-overwrite branch.

When isSelfOverwrite is true, lines 133–134 run syncLinearMode and changeTracker?.checkState(), then delegate to saveWorkflow(workflow) (line 137), which — for a non-temporary workflow — immediately repeats both calls at lines 157–158 with no state change in between.

♻️ Proposed refactor
   if (options.initialMode) workflow.initialMode = options.initialMode

-  syncLinearMode(workflow, [app.rootGraph], { flushLinearData: true })
-  workflow.changeTracker?.checkState()
-
   if (isSelfOverwrite) {
-    await saveWorkflow(workflow)
+    // saveWorkflow calls syncLinearMode + checkState internally
+    await saveWorkflow(workflow)
   } else if (workflow.isTemporary) {
+    syncLinearMode(workflow, [app.rootGraph], { flushLinearData: true })
+    workflow.changeTracker?.checkState()
     await renameWorkflow(workflow, newPath)
     await workflowStore.saveWorkflow(workflow)
   } else {
+    syncLinearMode(workflow, [app.rootGraph], { flushLinearData: true })
+    workflow.changeTracker?.checkState()
     const tempWorkflow = workflowStore.saveAs(workflow, newPath)
     await openWorkflow(tempWorkflow)
     await workflowStore.saveWorkflow(tempWorkflow)
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/platform/workflow/core/services/workflowService.ts` around lines 133 -
144, The calls to syncLinearMode(...) and workflow.changeTracker?.checkState()
are duplicated when isSelfOverwrite is true because saveWorkflow(workflow)
repeats them; remove the redundant pre-save invocation in the isSelfOverwrite
branch (or alternatively ensure syncLinearMode/checkState are only performed
inside saveWorkflow) so that syncLinearMode, changeTracker?.checkState(), and
subsequent persistence happen exactly once for saveWorkflow; touch the code
paths around syncLinearMode, saveWorkflow(workflow), renameWorkflow(workflow,
newPath), workflowStore.saveAs(workflow, newPath), openWorkflow(tempWorkflow),
and workflowStore.saveWorkflow(...) to centralize the sync/check calls.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@src/components/builder/useBuilderSave.ts`:
- Around line 79-83: In handleSave, if workflowStore.activeWorkflow is null the
function returns early but never resets the component state; update handleSave
to call resetSaving() and closeSaveDialog() (or run the existing cleanup) before
returning when workflow is null, or wrap the try body in a try/finally and move
resetSaving()/closeSaveDialog() into finally so saving is always cleared and the
dialog closed; reference the handleSave function, workflowStore.activeWorkflow
guard, and the resetSaving/closeSaveDialog helpers when making the change.

---

Duplicate comments:
In `@src/renderer/extensions/linearMode/OutputHistoryActiveQueueItem.vue`:
- Line 41: The "Clear Pending Tasks" button is currently disabled only when
queueCount === 0 but must be disabled when there are no pending tasks
(queueCount <= 1). In OutputHistoryActiveQueueItem.vue update the :disabled
binding for the clear action (the element using :disabled="queueCount === 0") to
check queueCount <= 1 instead so the action remains non-actionable when only the
active task exists; keep the existing reactive variable name queueCount and
adjust only the boolean expression.

---

Nitpick comments:
In `@src/components/builder/useBuilderSave.ts`:
- Around line 26-34: The current watch on saving triggers onBuilderSave
indirectly; remove the watch and make setSaving perform the trigger explicitly:
keep the saving = ref(false) and the setSaving(value: boolean) function, but
delete the watch(saving...) block and modify setSaving so that it updates
saving.value and, if value is true, calls onBuilderSave() (use void
onBuilderSave() for fire-and-forget or await it if you need sequential
behavior). This keeps the saving state but collapses the trigger into setSaving
and preserves callers that do setSaving(true).

In `@src/platform/workflow/core/services/workflowService.ts`:
- Around line 133-144: The pre-branch calls to syncLinearMode(...) and
workflow.changeTracker?.checkState() are being run unconditionally and then run
again inside saveWorkflow when isSelfOverwrite is true; to fix, remove the
unconditional pre-sync and instead call syncLinearMode(workflow,
[app.rootGraph], { flushLinearData: true }) and
workflow.changeTracker?.checkState() only in the branches that actually need
them (i.e., before renameWorkflow + workflowStore.saveWorkflow for
workflow.isTemporary==true and before creating
tempWorkflow/openWorkflow/saveWorkflow for the non-self-overwrite save-as path),
leaving the saveWorkflow(workflow) path for isSelfOverwrite untouched; use the
existing symbols syncLinearMode, changeTracker?.checkState, saveWorkflow,
renameWorkflow, workflowStore.saveAs, openWorkflow, workflow.isTemporary and
isSelfOverwrite to locate and apply the change.
- Around line 133-144: The calls to syncLinearMode(...) and
workflow.changeTracker?.checkState() are duplicated when isSelfOverwrite is true
because saveWorkflow(workflow) repeats them; remove the redundant pre-save
invocation in the isSelfOverwrite branch (or alternatively ensure
syncLinearMode/checkState are only performed inside saveWorkflow) so that
syncLinearMode, changeTracker?.checkState(), and subsequent persistence happen
exactly once for saveWorkflow; touch the code paths around syncLinearMode,
saveWorkflow(workflow), renameWorkflow(workflow, newPath),
workflowStore.saveAs(workflow, newPath), openWorkflow(tempWorkflow), and
workflowStore.saveWorkflow(...) to centralize the sync/check calls.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1b9ed06 and 2214f48.

📒 Files selected for processing (3)
  • src/components/builder/useBuilderSave.ts
  • src/platform/workflow/core/services/workflowService.ts
  • src/renderer/extensions/linearMode/OutputHistoryActiveQueueItem.vue

- fix sizing of sidebars in app mode
- update feedback button to match design
- update job queue notification
- clickable queue spinner item to allow clear queue
- refactor mode out of store to specific workflow instance
- support different saved vs active mode
- other styling/layout tweaks
improve types
- fix hiding panel on builder:select mode
- fix hasOutputs reactivity
@pythongosssss pythongosssss force-pushed the pysssss/appmode-updates branch from 2214f48 to 46a7108 Compare February 25, 2026 17:11
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Caution

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

⚠️ Outside diff range comments (2)
src/components/ui/TypeformPopoverButton.vue (1)

22-32: ⚠️ Potential issue | 🟠 Major

Add an accessible name to both icon-only feedback buttons.

Both branches render icon-only controls without an accessible name, so screen readers cannot identify the action.

💡 Suggested fix
   <Button
     v-if="isMobile"
     as="a"
     :href="`https://form.typeform.com/to/${dataTfWidget}`"
     target="_blank"
     variant="inverted"
+    :aria-label="$t('linearMode.giveFeedback')"
     class="flex h-10 items-center justify-center gap-2.5 px-3 py-2"
     v-bind="$attrs"
   >
     <i class="icon-[lucide--circle-help] size-4" />
   </Button>
@@
       <Button
         variant="inverted"
+        :aria-label="$t('linearMode.giveFeedback')"
         class="flex h-10 items-center justify-center gap-2.5 px-3 py-2"
         v-bind="$attrs"
       >
         <i class="icon-[lucide--circle-help] size-4" />
       </Button>

Based on learnings: in Vue components, use aria-label for icon-only buttons because they have no visible accessible name.

Also applies to: 35-41

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

In `@src/components/ui/TypeformPopoverButton.vue` around lines 22 - 32, The
icon-only controls in TypeformPopoverButton.vue (the mobile <Button as="a">
branch and the desktop <Popover><PopoverTrigger> branch) lack accessible names;
add descriptive aria-label attributes to both interactive elements (e.g.,
aria-label="Open feedback form" or similar) so screen readers can identify the
action—update the <Button> in the isMobile branch and the <PopoverTrigger> (or
the element it renders) in the non-mobile branch to include the aria-label.
src/platform/workflow/core/services/workflowService.ts (1)

112-146: ⚠️ Potential issue | 🟡 Minor

Self-overwrite triggers the overwrite confirmation dialog

The confirm dialog (Lines 115–123) fires for all cases where existingWorkflow && !existingWorkflow.isTemporary, including when isSelfOverwrite is true. A user re-saving a file under its own name will be prompted to confirm overwriting themselves, which is unexpected UX.

🛠️ Suggested fix
-    if (existingWorkflow && !existingWorkflow.isTemporary) {
+    if (existingWorkflow && !existingWorkflow.isTemporary && !isSelfOverwrite) {
       const res = await dialogService.confirm({
         title: t('sideToolbar.workflowTab.confirmOverwriteTitle'),
         type: 'overwrite',
         message: t('sideToolbar.workflowTab.confirmOverwrite'),
         itemList: [newPath]
       })

       if (res !== true) return false

-      if (!isSelfOverwrite) {
         const deleted = await deleteWorkflow(existingWorkflow, true)
         if (!deleted) return false
-      }
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/platform/workflow/core/services/workflowService.ts` around lines 112 -
146, The confirm dialog is shown even for self-overwrites; change the
conditional so dialogService.confirm is only called when an existing
non-temporary workflow is NOT the same workflow (i.e., require existingWorkflow
&& !existingWorkflow.isTemporary && !isSelfOverwrite). Keep the isSelfOverwrite
calculation as-is, skip the deleteWorkflow block for self-overwrite, and ensure
the existing branches that handle isSelfOverwrite (calling saveWorkflow) remain
unchanged; update the if that wraps dialogService.confirm and the subsequent
deleteWorkflow flow to only run when !isSelfOverwrite so self-saves proceed
directly to saveWorkflow without prompting.
♻️ Duplicate comments (1)
src/stores/appModeStore.ts (1)

17-19: ⚠️ Potential issue | 🔴 Critical

reactive arrays break v-model reassignment from DraggableList

selectedInputs and selectedOutputs are still declared as reactive<...>([]). When DraggableList in AppBuilder.vue emits update:modelValue with a new array, the v-model write (store.selectedInputs = newArray) replaces the exported proxy reference with a plain array, losing reactivity.

Use ref instead and update all access sites to .value:

🔧 Suggested fix
-import { reactive, computed, watch } from 'vue'
+import { ref, computed, watch } from 'vue'

-  const selectedInputs = reactive<[NodeId, string][]>([])
-  const selectedOutputs = reactive<NodeId[]>([])
-  const hasOutputs = computed(() => !!selectedOutputs.length)
+  const selectedInputs = ref<[NodeId, string][]>([])
+  const selectedOutputs = ref<NodeId[]>([])
+  const hasOutputs = computed(() => selectedOutputs.value.length > 0)

   function loadSelections(data: Partial<LinearData> | undefined) {
-    selectedInputs.splice(0, selectedInputs.length, ...(data?.inputs ?? []))
-    selectedOutputs.splice(0, selectedOutputs.length, ...(data?.outputs ?? []))
+    selectedInputs.value.splice(0, selectedInputs.value.length, ...(data?.inputs ?? []))
+    selectedOutputs.value.splice(0, selectedOutputs.value.length, ...(data?.outputs ?? []))
   }

   function flushSelections() {
     const workflow = workflowStore.activeWorkflow
     if (workflow) {
       workflow.dirtyLinearData = {
-        inputs: [...selectedInputs],
-        outputs: [...selectedOutputs]
+        inputs: [...selectedInputs.value],
+        outputs: [...selectedOutputs.value]
       }
     }
   }

   // In the watch:
-      oldWorkflow.dirtyLinearData = {
-        inputs: [...selectedInputs],
-        outputs: [...selectedOutputs]
-      }
+      oldWorkflow.dirtyLinearData = {
+        inputs: [...selectedInputs.value],
+        outputs: [...selectedOutputs.value]
+      }

As per coding guidelines, src/**/*.ts: "Use ref for reactive state". Based on learnings, this pattern (reactive arrays exported via v-model) breaks proxy reference on reassignment.

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

In `@src/stores/appModeStore.ts` around lines 17 - 19, selectedInputs and
selectedOutputs are created with reactive([]) which allows a v-model write from
DraggableList (update:modelValue) to replace the exported proxy with a plain
array and lose reactivity; change both to use ref with proper generics (e.g.
ref<Array<[NodeId,string]>> and ref<NodeId[]>) and update all access sites and
assignments to use .value (including the v-model binding sites in AppBuilder.vue
/ DraggableList and any reads/writes), and update hasOutputs computed to
reference selectedOutputs.value.length (or !!selectedOutputs.value.length);
ensure exported types and usages reflect the .value access so reactivity is
preserved on reassignment.
🧹 Nitpick comments (1)
src/platform/workflow/core/services/workflowService.test.ts (1)

140-150: Prefer satisfies over double cast in createWorkflow.

The as Partial<ComfyWorkflow> as ComfyWorkflow pattern silently bypasses shape-checking. Using satisfies preserves compile-time validation of the provided fields.

♻️ Proposed refactor
 function createWorkflow(
   warnings: PendingWarnings | null = null,
   options: { loadable?: boolean; path?: string } = {}
 ): ComfyWorkflow {
-  const wf = {
+  const wf = ({
     pendingWarnings: warnings,
     ...(options.loadable && {
       path: options.path ?? 'workflows/test.json',
       isLoaded: true,
       activeState: { nodes: [], links: [] },
       changeTracker: { reset: vi.fn(), restore: vi.fn() }
     })
-  } as Partial<ComfyWorkflow>
-  return wf as ComfyWorkflow
+  } satisfies Partial<ComfyWorkflow>) as ComfyWorkflow
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/platform/workflow/core/services/workflowService.test.ts` around lines 140
- 150, Replace the silent double-cast in createWorkflow by using TypeScript's
satisfies operator so the returned object is validated against ComfyWorkflow
without losing the final ComfyWorkflow return type; change the construction that
currently uses "as Partial<ComfyWorkflow> as ComfyWorkflow" to build the object
as a Partial shape and append "satisfies ComfyWorkflow" (or use an intermediate
const typed with satisfies) so the compiler verifies fields while still
returning the object as ComfyWorkflow from createWorkflow.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@src/components/ui/TypeformPopoverButton.vue`:
- Around line 22-32: The icon-only controls in TypeformPopoverButton.vue (the
mobile <Button as="a"> branch and the desktop <Popover><PopoverTrigger> branch)
lack accessible names; add descriptive aria-label attributes to both interactive
elements (e.g., aria-label="Open feedback form" or similar) so screen readers
can identify the action—update the <Button> in the isMobile branch and the
<PopoverTrigger> (or the element it renders) in the non-mobile branch to include
the aria-label.

In `@src/platform/workflow/core/services/workflowService.ts`:
- Around line 112-146: The confirm dialog is shown even for self-overwrites;
change the conditional so dialogService.confirm is only called when an existing
non-temporary workflow is NOT the same workflow (i.e., require existingWorkflow
&& !existingWorkflow.isTemporary && !isSelfOverwrite). Keep the isSelfOverwrite
calculation as-is, skip the deleteWorkflow block for self-overwrite, and ensure
the existing branches that handle isSelfOverwrite (calling saveWorkflow) remain
unchanged; update the if that wraps dialogService.confirm and the subsequent
deleteWorkflow flow to only run when !isSelfOverwrite so self-saves proceed
directly to saveWorkflow without prompting.

---

Duplicate comments:
In `@src/stores/appModeStore.ts`:
- Around line 17-19: selectedInputs and selectedOutputs are created with
reactive([]) which allows a v-model write from DraggableList (update:modelValue)
to replace the exported proxy with a plain array and lose reactivity; change
both to use ref with proper generics (e.g. ref<Array<[NodeId,string]>> and
ref<NodeId[]>) and update all access sites and assignments to use .value
(including the v-model binding sites in AppBuilder.vue / DraggableList and any
reads/writes), and update hasOutputs computed to reference
selectedOutputs.value.length (or !!selectedOutputs.value.length); ensure
exported types and usages reflect the .value access so reactivity is preserved
on reassignment.

---

Nitpick comments:
In `@src/platform/workflow/core/services/workflowService.test.ts`:
- Around line 140-150: Replace the silent double-cast in createWorkflow by using
TypeScript's satisfies operator so the returned object is validated against
ComfyWorkflow without losing the final ComfyWorkflow return type; change the
construction that currently uses "as Partial<ComfyWorkflow> as ComfyWorkflow" to
build the object as a Partial shape and append "satisfies ComfyWorkflow" (or use
an intermediate const typed with satisfies) so the compiler verifies fields
while still returning the object as ComfyWorkflow from createWorkflow.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2214f48 and 46a7108.

📒 Files selected for processing (28)
  • src/components/LiteGraphCanvasSplitterOverlay.vue
  • src/components/appMode/AppModeToolbar.vue
  • src/components/builder/BuilderToolbar.vue
  • src/components/builder/useBuilderSave.ts
  • src/components/graph/GraphCanvas.vue
  • src/components/sidebar/tabs/SidebarTabTemplate.vue
  • src/components/topbar/WorkflowTab.vue
  • src/components/ui/TypeformPopoverButton.vue
  • src/composables/useAppMode.ts
  • src/composables/useCoreCommands.ts
  • src/locales/en/main.json
  • src/platform/workflow/core/services/workflowService.test.ts
  • src/platform/workflow/core/services/workflowService.ts
  • src/platform/workflow/management/stores/comfyWorkflow.ts
  • src/platform/workflow/management/stores/workflowStore.ts
  • src/renderer/core/canvas/canvasStore.ts
  • src/renderer/extensions/linearMode/LinearArrange.vue
  • src/renderer/extensions/linearMode/LinearControls.vue
  • src/renderer/extensions/linearMode/LinearPreview.vue
  • src/renderer/extensions/linearMode/LinearWelcome.vue
  • src/renderer/extensions/linearMode/OutputHistory.vue
  • src/renderer/extensions/linearMode/OutputHistoryActiveQueueItem.vue
  • src/renderer/extensions/linearMode/linearOutputStore.test.ts
  • src/renderer/extensions/linearMode/linearOutputStore.ts
  • src/stores/appModeStore.ts
  • src/utils/__tests__/litegraphTestUtils.ts
  • src/views/GraphView.vue
  • src/views/LinearView.vue
💤 Files with no reviewable changes (1)
  • src/composables/useCoreCommands.ts
🚧 Files skipped from review as they are similar to previous changes (8)
  • src/renderer/extensions/linearMode/OutputHistory.vue
  • src/renderer/extensions/linearMode/linearOutputStore.test.ts
  • src/utils/tests/litegraphTestUtils.ts
  • src/renderer/core/canvas/canvasStore.ts
  • src/platform/workflow/management/stores/workflowStore.ts
  • src/renderer/extensions/linearMode/OutputHistoryActiveQueueItem.vue
  • src/renderer/extensions/linearMode/linearOutputStore.ts
  • src/components/appMode/AppModeToolbar.vue

@pythongosssss pythongosssss added the New Browser Test Expectations New browser test screenshot should be set by github action label Feb 25, 2026
@github-actions
Copy link

Updating Playwright Expectations

@pythongosssss pythongosssss added New Browser Test Expectations New browser test screenshot should be set by github action and removed New Browser Test Expectations New browser test screenshot should be set by github action labels Feb 25, 2026
@github-actions github-actions bot removed the New Browser Test Expectations New browser test screenshot should be set by github action label Feb 25, 2026
@pythongosssss pythongosssss marked this pull request as ready for review February 25, 2026 20:16
@pythongosssss pythongosssss requested review from a team as code owners February 25, 2026 20:16
@dosubot dosubot bot added the size:XL This PR changes 500-999 lines, ignoring generated files. label Feb 25, 2026
AustinMroz
AustinMroz previously approved these changes Feb 25, 2026
Copy link
Collaborator

@AustinMroz AustinMroz left a comment

Choose a reason for hiding this comment

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

The complexity of the workflow serialzation changes is concerning, but I'm not seeing any actual bugs in the code or from testing.

@github-actions
Copy link

⚡ Performance Report

Metric Baseline PR Δ
canvas-idle: style recalcs 126 127 +1% ⚪
canvas-idle: layouts 1 1 +0% ⚪
canvas-idle: task duration 455ms 388ms -15% 🟢
canvas-mouse-sweep: style recalcs 173 179 +3% ⚪
canvas-mouse-sweep: layouts 12 12 +0% ⚪
canvas-mouse-sweep: task duration 771ms 874ms +13% 🟠
dom-widget-clipping: style recalcs 42 44 +5% ⚪
dom-widget-clipping: layouts 0 0 +0% ⚪
dom-widget-clipping: task duration 339ms 371ms +10% ⚪
Raw data
{
  "timestamp": "2026-02-26T14:14:55.438Z",
  "gitSha": "613fe9f8ff1b821e72f987e53cf5a318de2da57a",
  "branch": "pysssss/appmode-updates",
  "measurements": [
    {
      "name": "canvas-idle",
      "durationMs": 2048.7229999999954,
      "styleRecalcs": 127,
      "styleRecalcDurationMs": 22.777,
      "layouts": 1,
      "layoutDurationMs": 0.29200000000000004,
      "taskDurationMs": 388.073,
      "heapDeltaBytes": -1507192
    },
    {
      "name": "canvas-mouse-sweep",
      "durationMs": 1972.7169999999887,
      "styleRecalcs": 179,
      "styleRecalcDurationMs": 50.26100000000001,
      "layouts": 12,
      "layoutDurationMs": 3.8339999999999996,
      "taskDurationMs": 874.412,
      "heapDeltaBytes": -2354692
    },
    {
      "name": "dom-widget-clipping",
      "durationMs": 616.4200000000051,
      "styleRecalcs": 44,
      "styleRecalcDurationMs": 13.608,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 371.454,
      "heapDeltaBytes": 7633868
    }
  ]
}

@github-actions
Copy link

🎭 Playwright: ✅ 546 passed, 0 failed · 5 flaky

📊 Browser Reports
  • chromium: View Report (✅ 534 / ❌ 0 / ⚠️ 5 / ⏭️ 10)
  • chromium-2x: View Report (✅ 2 / ❌ 0 / ⚠️ 0 / ⏭️ 0)
  • chromium-0.5x: ❌ Deployment failed
  • mobile-chrome: View Report (✅ 10 / ❌ 0 / ⚠️ 0 / ⏭️ 0)

@pythongosssss pythongosssss merged commit 9fb93a5 into main Feb 26, 2026
60 of 64 checks passed
@pythongosssss pythongosssss deleted the pysssss/appmode-updates branch February 26, 2026 17:55
christian-byrne pushed a commit that referenced this pull request Feb 27, 2026
## Summary

- fix sizing of sidebars in app mode
- update feedback button to match design
- update job queue notification
- clickable queue spinner item to allow clear queue
- refactor mode out of store to specific workflow instance
- support different saved vs active mode
- other styling/layout tweaks

## Changes

- **What**: Changes the store to a composable and moves the mode state
to the workflow.
- This enables switching between tabs and maintaining the mode they were
in

## Screenshots (if applicable)
<img width="1866" height="1455" alt="image"
src="https://github.com/user-attachments/assets/f9a8cd36-181f-4948-b48c-dd27bd9127cf"
/>

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9137-App-mode-more-updates-fixes-3106d73d365081a18ccff6ffe24fdec7)
by [Unito](https://www.unito.io)

---------

Co-authored-by: github-actions <github-actions@github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants