Skip to content

App Mode - Progress/outputs - 5#9028

Merged
pythongosssss merged 6 commits intomainfrom
pysssss/appmode/linear-run.3.5
Feb 23, 2026
Merged

App Mode - Progress/outputs - 5#9028
pythongosssss merged 6 commits intomainfrom
pysssss/appmode/linear-run.3.5

Conversation

@pythongosssss
Copy link
Member

@pythongosssss pythongosssss commented Feb 20, 2026

Summary

Adds new store for tracking outputs lin linear mode and reworks outputs to display the following: skeleton -> latent preview -> node output -> job result.

Changes

  • What:
  • New store for wrangling various events into a usable list of live outputs
  • Replace manual list with reka-ui list box
  • Extract various composables

Review Focus

Getting all the events and stores aligned to happen consistently and in the correct order was a challenge, unifying the various sources. so suggestions there would be good

Screenshots (if applicable)

app.mode.progress.mp4

┆Issue is synchronized with this Notion page by Unito

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 20, 2026

📝 Walkthrough

Walkthrough

This PR significantly refactors and extends the linear mode UI by introducing new components, a centralized Pinia store for output state management, and utility functions for handling job execution, latent previews, and output history. The changes consolidate output preview logic, add progress tracking, and restructure component interactions around a unified selection model.

Changes

Cohort / File(s) Summary
CSS & Localization
packages/design-system/src/css/style.css, src/locales/en/main.json
Added new CSS variable for job progress border styling in light/dark themes and introduced skeleton shimmer animation. Updated localization string from "Run count:" to "Number of runs".
New Linear Mode Components
src/renderer/extensions/linearMode/LatentPreview.vue, src/renderer/extensions/linearMode/LinearProgressBar.vue, src/renderer/extensions/linearMode/OutputHistoryItem.vue, src/renderer/extensions/linearMode/OutputPreviewItem.vue
Introduced four new presentational Vue components: LatentPreview (centered square skeleton), LinearProgressBar (two-layer progress visualization with configurable opacity), OutputHistoryItem (image or icon based on media type), and OutputPreviewItem (image with overlay progress bar).
Linear Mode State Management
src/renderer/extensions/linearMode/linearOutputStore.ts, src/renderer/extensions/linearMode/linearModeTypes.ts, src/renderer/extensions/linearMode/useOutputHistory.ts
Added new Pinia store (linearOutputStore) managing in-progress items, selection state, and job lifecycle events; introduced type definitions (InProgressItem, OutputSelection, SelectionValue); created composition function (useOutputHistory) for lazy-loading and caching output history with job detail resolution.
Linear Mode Utilities & Tests
src/renderer/extensions/linearMode/flattenNodeOutput.ts, src/renderer/extensions/linearMode/flattenNodeOutput.test.ts, src/renderer/extensions/linearMode/linearOutputStore.test.ts, src/renderer/extensions/linearMode/mediaTypes.ts
Added utility function flattenNodeOutput to convert node execution outputs to ResultItemImpl array; added comprehensive test suites for both the utility and the new store; removed export from StatItem type in mediaTypes module.
Component Refactoring
src/renderer/extensions/linearMode/LinearPreview.vue, src/renderer/extensions/linearMode/LinearControls.vue, src/renderer/extensions/linearMode/OutputHistory.vue
Major refactoring of LinearPreview (replaced nodeOutputStore with manual selection handler); simplified LinearControls (consolidated run button, added rootClass prop support); extensively refactored OutputHistory to use Listbox-driven UI with keyboard navigation, infinite scroll, unified selection model, and new OutputSelection data structure.
Widget System Extensions
src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberInput.vue, src/renderer/extensions/vueNodes/widgets/components/layout/WidgetLayoutField.vue
Added optional rootClass prop to both components for flexible styling without combined class strings.
View & Utilities Updates
src/views/LinearView.vue, src/utils/dateTimeUtil.ts
Integrated LinearProgressBar into LinearView (mobile and center panel), removed legacy preview state management, and removed unused formatDuration utility function.

Sequence Diagram

sequenceDiagram
    participant Execution as Execution Engine
    participant Store as linearOutputStore
    participant JobPreview as JobPreviewStore
    participant OutputHistory as OutputHistory Component
    participant UI as UI Components

    Execution->>Store: onJobStart(jobId)
    Store->>Store: Create skeleton item, auto-select
    Store->>UI: Update inProgressItems

    Execution->>JobPreview: Update latent preview
    JobPreview->>Store: onLatentPreview(jobId, url)
    Store->>Store: Create latent item if tracking job
    Store->>UI: Emit selection update

    Execution->>Store: onNodeExecuted(jobId, outputs)
    Store->>Store: Flatten outputs, convert skeleton to images
    Store->>UI: Update inProgressItems with image items

    Execution->>Store: onJobComplete(jobId)
    Store->>Store: Remove non-image items, mark resolved

    UI->>OutputHistory: User selects output item
    OutputHistory->>Store: handleSelection(selection)
    Store->>Store: Update selectedId, canShowPreview
    Store->>UI: Emit new selection with metadata
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

  • Linear mode arrangement tweaks #8853: Modifies the same linear-mode UI files (LinearPreview.vue, LinearControls.vue, OutputHistory.vue, LinearView.vue) indicating overlapping scope on progress visualization and output handling refactoring.
  • App mode - Unify menus - 2 #9023: Changes LinearControls.vue with appModeStore visibility logic, directly intersecting with button layout and control flow modifications in this PR.
  • linear v2: Simple Mode #7734: Overlapping code-level changes across multiple linearMode files (LinearControls, LinearPreview, OutputHistory, LinearProgressBar, flattenNodeOutput, mediaTypes) indicating concurrent work on the same feature area.

Suggested labels

size:XXL

Suggested reviewers

  • AustinMroz
  • Yorha4D
  • shinshin86

Poem

🐰✨ A rabbit hops through output streams,
With progress bars and latent dreams,
Selection flows like carrots in a row,
History reordered, let the previews show! 🌟

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 5.56% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'App Mode - Progress/outputs - 5' is vague and does not clearly summarize the main changes; it uses a generic, non-descriptive format similar to a tracking label. Consider using a more descriptive title that captures the essence of the changes, such as 'Refactor linear mode output display with new linearOutputStore' or 'Add linearOutputStore and rework output lifecycle for linear mode'.
✅ Passed checks (1 passed)
Check name Status Explanation
Description check ✅ Passed The description covers the main objective and includes required sections (Summary, Changes, Review Focus), though the 'What' section lacks detail and the Changes section formatting could be clearer.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch pysssss/appmode/linear-run.3.5

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

🎨 Storybook: ✅ Built — View Storybook

Details

⏰ Completed at: 02/23/2026, 06:24:40 PM UTC

Links

@github-actions
Copy link

github-actions bot commented Feb 20, 2026

🎭 Playwright: ✅ 531 passed, 0 failed · 2 flaky

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

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

🧹 Nitpick comments (3)
packages/design-system/src/css/style.css (1)

1884-1900: Consider theme-aware colors for skeleton shimmer.

The skeleton shimmer uses hardcoded colors (rgb(188 188 188 / 0.5) and rgb(0 0 0 / 0.5)). This works but won't adapt to the dark theme. If you want consistent theming, consider using CSS variables or adding a .dark-theme .skeleton-shimmer variant.

This is optional since the current implementation provides adequate visual feedback in both themes.

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

In `@packages/design-system/src/css/style.css` around lines 1884 - 1900, Update
the .skeleton-shimmer and `@keyframes` skeleton-shimmer to use theme-aware CSS
variables instead of hardcoded RGB values (e.g., --skeleton-base and
--skeleton-highlight) or provide an override rule for .dark-theme
.skeleton-shimmer that sets alternative variables; replace references to rgb(188
188 188 / 0.5) and rgb(0 0 0 / 0.5) with the variables so the shimmer adapts to
light/dark themes while keeping the existing animation/keyframe names intact.
src/renderer/extensions/linearMode/linearOutputStore.test.ts (1)

9-15: Avoid module-level mutable refs in mocks.

activeJobIdRef, previewsRef, and isAppModeRef are global mutable state. Prefer a vi.hoisted() container to keep mock state contained and predictable across tests.

🧪 Suggested refactor
-const activeJobIdRef = ref<string | null>(null)
-const previewsRef = ref<Record<string, string>>({})
-const isAppModeRef = ref(true)
-
-const { apiTarget } = vi.hoisted(() => ({
-  apiTarget: new EventTarget()
-}))
+const { apiTarget, activeJobIdRef, previewsRef, isAppModeRef } = vi.hoisted(
+  () => ({
+    apiTarget: new EventTarget(),
+    activeJobIdRef: ref<string | null>(null),
+    previewsRef: ref<Record<string, string>>({}),
+    isAppModeRef: ref(true)
+  })
+)
Based on learnings: Keep module mocks contained; do not use global mutable state within test files; use vi.hoisted() if necessary for per-test Arrange phase manipulation.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/extensions/linearMode/linearOutputStore.test.ts` around lines 9
- 15, activeJobIdRef, previewsRef and isAppModeRef are defined as module-level
mutable refs causing shared state between tests; move their creation into a
vi.hoisted() container so each test can control/reset them predictably. Replace
the top-level refs with something like a hoisted object (using vi.hoisted(() =>
({ activeJobIdRef: ref(...), previewsRef: ref(...), isAppModeRef: ref(...) })))
and update tests/mocks to read/write those properties (referencing the hoisted
container and its activeJobIdRef, previewsRef, isAppModeRef) so state is
isolated per test.
src/renderer/extensions/linearMode/OutputHistory.vue (1)

206-269: Extract inline event handlers into named functions.
This aligns with the repo’s preference for function declarations and keeps handlers reusable/testable.

♻️ Suggested refactor
 const pointer = new CanvasPointer(document.body)
 let scrollOffset = 0
-useEventListener(
-  document.body,
-  'wheel',
-  function (e: WheelEvent) {
-    if (!e.ctrlKey && !e.metaKey) return
-    e.preventDefault()
-    e.stopPropagation()
-
-    if (!pointer.isTrackpadGesture(e)) {
-      if (e.deltaY > 0) navigateToAdjacent(1)
-      else navigateToAdjacent(-1)
-      return
-    }
-    scrollOffset += e.deltaY
-    while (scrollOffset >= 60) {
-      scrollOffset -= 60
-      navigateToAdjacent(1)
-    }
-    while (scrollOffset <= -60) {
-      scrollOffset += 60
-      navigateToAdjacent(-1)
-    }
-  },
-  { capture: true, passive: false }
-)
-
-useEventListener(
-  outputsRef,
-  'wheel',
-  function (e: WheelEvent) {
-    if (e.ctrlKey || e.metaKey || e.deltaY === 0) return
-    e.preventDefault()
-    if (outputsRef.value) outputsRef.value.scrollLeft += e.deltaY
-  },
-  { passive: false }
-)
-
-useEventListener(document.body, 'keydown', (e: KeyboardEvent) => {
-  if (
-    (e.key !== 'ArrowDown' && e.key !== 'ArrowUp') ||
-    e.target instanceof HTMLTextAreaElement ||
-    e.target instanceof HTMLInputElement
-  )
-    return
-
-  e.preventDefault()
-  e.stopPropagation()
-  if (e.key === 'ArrowDown') navigateToAdjacent(1)
-  else navigateToAdjacent(-1)
-})
+function onGlobalWheel(e: WheelEvent) {
+  if (!e.ctrlKey && !e.metaKey) return
+  e.preventDefault()
+  e.stopPropagation()
+
+  if (!pointer.isTrackpadGesture(e)) {
+    if (e.deltaY > 0) navigateToAdjacent(1)
+    else navigateToAdjacent(-1)
+    return
+  }
+  scrollOffset += e.deltaY
+  while (scrollOffset >= 60) {
+    scrollOffset -= 60
+    navigateToAdjacent(1)
+  }
+  while (scrollOffset <= -60) {
+    scrollOffset += 60
+    navigateToAdjacent(-1)
+  }
+}
+
+function onOutputsWheel(e: WheelEvent) {
+  if (e.ctrlKey || e.metaKey || e.deltaY === 0) return
+  e.preventDefault()
+  if (outputsRef.value) outputsRef.value.scrollLeft += e.deltaY
+}
+
+function onGlobalKeydown(e: KeyboardEvent) {
+  if (
+    (e.key !== 'ArrowDown' && e.key !== 'ArrowUp') ||
+    e.target instanceof HTMLTextAreaElement ||
+    e.target instanceof HTMLInputElement
+  )
+    return
+
+  e.preventDefault()
+  e.stopPropagation()
+  if (e.key === 'ArrowDown') navigateToAdjacent(1)
+  else navigateToAdjacent(-1)
+}
+
+useEventListener(document.body, 'wheel', onGlobalWheel, {
+  capture: true,
+  passive: false
+})
+useEventListener(outputsRef, 'wheel', onOutputsWheel, { passive: false })
+useEventListener(document.body, 'keydown', onGlobalKeydown)

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

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

In `@src/renderer/extensions/linearMode/OutputHistory.vue` around lines 206 - 269,
The inline anonymous event handler functions passed to useEventListener (the
wheel handler on document.body that uses pointer.isTrackpadGesture and
scrollOffset, the wheel handler on outputsRef that updates
outputsRef.value.scrollLeft, and the keydown handler on document.body that calls
navigateToAdjacent) should be extracted into named function declarations to
match the repo style; create e.g. handleDocumentWheel(e: WheelEvent),
handleOutputsWheel(e: WheelEvent), and handleDocumentKeydown(e: KeyboardEvent)
that encapsulate the existing logic (use pointer.isTrackpadGesture, scrollOffset
mutation, navigateToAdjacent, and checks for ctrl/meta and input/textarea
targets), then pass those named functions to useEventListener instead of inline
functions and keep references to listboxRef, outputsRef, pointer, and
navigateToAdjacent as used by the handlers.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/renderer/extensions/linearMode/flattenNodeOutput.test.ts`:
- Line 12: The test suite currently uses a string title in describe; change it
to use the function reference to satisfy the
vitest/prefer-describe-function-title rule by replacing
describe('flattenNodeOutput', ...) with describe(flattenNodeOutput, ...) so the
suite title is the actual function reference (flattenNodeOutput) used in the
file, ensuring the test import or symbol is in scope for the describe call.

In `@src/renderer/extensions/linearMode/LinearPreview.vue`:
- Around line 101-118: Add accessible names to the two icon-only action buttons
by supplying aria-labels via vue-i18n keys: for the download button (the Button
rendered when selectedOutput and invoking downloadFile(selectedOutput.url)) add
an aria-label using a new i18n key like "actions.download" and for the interrupt
button (the Button rendered when !executionStore.isIdle && !selectedItem and
invoking commandStore.execute('Comfy.Interrupt')) add an aria-label using a key
like "actions.interrupt"; implement the labels using the Composition API i18n
(e.g., useI18n/t function inside the component) and add the corresponding
entries to src/locales/en/main.json.

In `@src/renderer/extensions/linearMode/OutputHistoryItem.vue`:
- Around line 14-25: The img preview rendered when getMediaType(output) ===
'images' is missing an alt attribute; update the <img> element in
OutputHistoryItem.vue to include an alt (e.g., alt="" for decorative images or
alt="Preview" / derived text if available) so accessibility and HTML validity
are satisfied; locate the img that binds :src="output.url" (and is controlled by
getMediaType(output)) and add the alt attribute there.

In `@src/renderer/extensions/linearMode/OutputPreviewItem.vue`:
- Around line 10-14: The <img> in the OutputPreviewItem.vue component (rendered
when latentPreview is truthy) lacks an alt attribute; update the img element to
include an appropriate alt: use alt="" if the image is purely decorative or bind
a descriptive string via i18n (e.g., :alt="$t('...')" or a prop) when it conveys
meaning, ensuring the change references the latentPreview-rendering <img>
element so screen readers receive correct semantics.

---

Nitpick comments:
In `@packages/design-system/src/css/style.css`:
- Around line 1884-1900: Update the .skeleton-shimmer and `@keyframes`
skeleton-shimmer to use theme-aware CSS variables instead of hardcoded RGB
values (e.g., --skeleton-base and --skeleton-highlight) or provide an override
rule for .dark-theme .skeleton-shimmer that sets alternative variables; replace
references to rgb(188 188 188 / 0.5) and rgb(0 0 0 / 0.5) with the variables so
the shimmer adapts to light/dark themes while keeping the existing
animation/keyframe names intact.

In `@src/renderer/extensions/linearMode/linearOutputStore.test.ts`:
- Around line 9-15: activeJobIdRef, previewsRef and isAppModeRef are defined as
module-level mutable refs causing shared state between tests; move their
creation into a vi.hoisted() container so each test can control/reset them
predictably. Replace the top-level refs with something like a hoisted object
(using vi.hoisted(() => ({ activeJobIdRef: ref(...), previewsRef: ref(...),
isAppModeRef: ref(...) }))) and update tests/mocks to read/write those
properties (referencing the hoisted container and its activeJobIdRef,
previewsRef, isAppModeRef) so state is isolated per test.

In `@src/renderer/extensions/linearMode/OutputHistory.vue`:
- Around line 206-269: The inline anonymous event handler functions passed to
useEventListener (the wheel handler on document.body that uses
pointer.isTrackpadGesture and scrollOffset, the wheel handler on outputsRef that
updates outputsRef.value.scrollLeft, and the keydown handler on document.body
that calls navigateToAdjacent) should be extracted into named function
declarations to match the repo style; create e.g. handleDocumentWheel(e:
WheelEvent), handleOutputsWheel(e: WheelEvent), and handleDocumentKeydown(e:
KeyboardEvent) that encapsulate the existing logic (use
pointer.isTrackpadGesture, scrollOffset mutation, navigateToAdjacent, and checks
for ctrl/meta and input/textarea targets), then pass those named functions to
useEventListener instead of inline functions and keep references to listboxRef,
outputsRef, pointer, and navigateToAdjacent as used by the handlers.

@pythongosssss pythongosssss force-pushed the pysssss/appmode/welcome-page.3 branch from 6be06e7 to e0f2de5 Compare February 20, 2026 22:31
@pythongosssss pythongosssss force-pushed the pysssss/appmode/linear-run.3.5 branch from b37e835 to 6ec4e0f Compare February 20, 2026 22: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: 3

🧹 Nitpick comments (6)
src/renderer/extensions/vueNodes/widgets/components/layout/WidgetLayoutField.vue (1)

6-24: Avoid destructuring defineProps to preserve reactivity. Use the props object (or rely on template prop exposure) instead.

♻️ Proposed refactor
-const { rootClass } = defineProps<{
+defineProps<{
   widget: Pick<
     SimplifiedWidget<string | number | undefined>,
     'name' | 'label' | 'borderStyle'
   >
   rootClass?: string
 }>()
Based on learnings: In Vue 3 script setup, props defined with defineProps are automatically available by name in the template without destructuring. Destructuring the result of defineProps inside script can break reactivity.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/renderer/extensions/vueNodes/widgets/components/layout/WidgetLayoutField.vue`
around lines 6 - 24, The code destructures defineProps (extracting rootClass)
which can break Vue reactivity; change to capture the whole props object via
defineProps (e.g., const props = defineProps<...>() or rely on the
template-exposed prop names) and update usages of rootClass in this file to
reference props.rootClass (or use the template-provided rootClass directly) so
reactivity for rootClass and other props is preserved; ensure
useHideLayoutField() remains unchanged and any references to rootClass in the
template or script are updated to the non-destructured form.
src/views/LinearView.vue (1)

6-22: Prefer $t in the template when i18n isn’t used in script.

♻️ Suggested refactor
-import { useI18n } from 'vue-i18n'
@@
-const { t } = useI18n()
@@
-        <div v-text="t('linearMode.beta')" />
+        <div v-text="$t('linearMode.beta')" />
Based on learnings: In Vue single-file components where the i18n t function is only used within the template, prefer using the built-in $t in the template instead of importing useI18n and destructuring t in the script.

Also applies to: 71-71

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

In `@src/views/LinearView.vue` around lines 6 - 22, The file imports useI18n and
destructures t via "const { t } = useI18n()" even though t is only used in the
template; remove the useI18n import and the "const { t } = useI18n()" statement
and update the template to call $t(...) instead of t(...). Search for other
occurrences of destructured t or useI18n in this component (including the other
occurrence referenced) and apply the same change so all template translations
use $t and no unused i18n import or variable remains.
src/renderer/extensions/linearMode/LinearPreview.vue (1)

101-134: Avoid inline function expressions in template handlers.

♻️ Suggested refactor
+function downloadSelected() {
+  if (selectedOutput.value?.url) downloadFile(selectedOutput.value.url)
+}
+
+function interruptExecution() {
+  commandStore.execute('Comfy.Interrupt')
+}
+
+function downloadAllSelected() {
+  downloadAsset(selectedItem.value)
+}
+
+function deleteSelectedAsset() {
+  mediaActions.deleteAssets(selectedItem.value!)
+}
@@
     <Button
       v-if="selectedOutput"
       size="icon"
-      `@click`="
-        () => {
-          if (selectedOutput?.url) downloadFile(selectedOutput.url)
-        }
-      "
+      `@click`="downloadSelected"
     >
@@
     <Button
       v-if="!executionStore.isIdle && !selectedItem"
       variant="destructive"
       size="icon"
-      `@click`="commandStore.execute('Comfy.Interrupt')"
+      `@click`="interruptExecution"
     >
@@
-          command: () => downloadAsset(selectedItem!)
+          command: downloadAllSelected
         },
@@
-          command: () => mediaActions.deleteAssets(selectedItem!)
+          command: deleteSelectedAsset
         }
As per coding guidelines: Do not use function expressions if it's possible to use function declarations instead.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/extensions/linearMode/LinearPreview.vue` around lines 101 - 134,
The template currently uses inline arrow functions for click handlers (e.g., the
download button uses "() => { if (selectedOutput?.url)
downloadFile(selectedOutput.url) }", the download-all and delete entries use
"command: () => downloadAsset(selectedItem!)" and "command: () =>
mediaActions.deleteAssets(selectedItem!)"), and the interrupt button directly
calls commandStore.execute; move these inline expressions into named component
methods (for example create methods like onDownloadSelected(), onInterrupt(),
onDownloadItem(), onDeleteItem()) inside the component's methods block,
reference selectedOutput, selectedItem, downloadFile, downloadAsset,
mediaActions.deleteAssets and commandStore.execute from those methods, and
update the template/Popover entries to call the method names instead of arrow
functions so handlers are declared rather than inlined.
src/renderer/extensions/linearMode/OutputHistory.vue (2)

218-244: CanvasPointer instantiation at module level assumes DOM availability.

new CanvasPointer(document.body) runs during component setup. This is fine for client-side only usage but would fail in SSR. If this component is ever used in an SSR context, consider wrapping in onMounted or checking for typeof document !== 'undefined'.

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

In `@src/renderer/extensions/linearMode/OutputHistory.vue` around lines 218 - 244,
The CanvasPointer is instantiated at module load (new
CanvasPointer(document.body)), which breaks SSR; change to declare let pointer:
CanvasPointer | null = null and defer initialization to onMounted (or guard with
typeof document !== 'undefined') so the instance is created only in the browser;
update the wheel handler (used with useEventListener) to check pointer is
non-null before calling pointer.isTrackpadGesture and ensure any listener
registration happens after mounting (or short-circuit if document is undefined)
and clean up/reset pointer on unmount.

85-88: Consider typing the callback parameter.

The as SelectionValue | undefined assertion could be avoided if reka-ui's ListboxRoot exposes a generic or typed @update:model-value callback. If the library doesn't support this, the assertion is acceptable.

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

In `@src/renderer/extensions/linearMode/OutputHistory.vue` around lines 85 - 88,
The callback parameter is currently untyped (val: unknown) in onSelectionChange
and uses a type assertion; change the signature to accept the correct type
(e.g., function onSelectionChange(val: SelectionValue | undefined)) so you can
remove the "as SelectionValue | undefined" assertion and call
store.select(val?.id ?? null) directly; if reka-ui's ListboxRoot exposes a
generic typed `@update`:model-value, prefer using that generic in the component's
prop/event typing to propagate SelectionValue into the handler, otherwise keep
the explicit typed parameter as shown.
src/renderer/extensions/linearMode/linearOutputStore.ts (1)

258-271: Consider whether lifecycle actions should be exposed.

Exposing onJobStart, onLatentPreview, onNodeExecuted, and onJobComplete allows any consumer to call them out of order, potentially corrupting state. If these are only meant to be triggered by internal watchers and event listeners, consider keeping them private. If external calls are intentional (e.g., for testing), this is fine.

Based on learnings: "Only expose state and actions that are used externally; keep internal state private."

🤖 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 258 -
271, The lifecycle handlers onJobStart, onLatentPreview, onNodeExecuted, and
onJobComplete are being exported from the linearOutputStore even though they
appear to be internal-only and can be called out of order; stop exposing them
publicly by removing them from the returned object and keep the functions as
internal helpers inside linearOutputStore (leave externally-used API like
inProgressItems, selectedId, trackedJobId, pendingResolve, select,
selectAsLatest, resolveIfReady untouched). If you need external access for
tests, provide a clearly-named test-only accessor (e.g.,
getInternalHooksForTest) or export a thin, controlled wrapper that enforces
ordering/validation rather than returning the raw handlers. Ensure references to
onJobStart/onLatentPreview/onNodeExecuted/onJobComplete within the module still
call the internal functions.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/renderer/extensions/linearMode/LinearControls.vue`:
- Around line 157-162: The Tailwind `!` important modifier on the
WidgetInputNumberInput instance should be removed—edit the class string
`class="*:[.min-w-0]:w-24 grid-cols-[auto_96px]!"` to drop the trailing `!` and
instead adjust the base/root classes (e.g. `root-class="text-base-foreground"`)
or component styles to achieve the intended width; also locate the other
WidgetInputNumberInput usage with the same `!` modifier later in the file (the
block around the second occurrence) and remove the `!`, ensuring layout is
preserved by reordering classes or using a stronger selector in the component
CSS rather than using `!important`.

In `@src/renderer/extensions/linearMode/OutputHistory.vue`:
- Around line 336-344: The ListboxItem currently uses a non-null assertion on
selectionMap.get(`history:${asset.id}:${key}`)! which can throw if the map is
out of sync; update the rendering to defensively handle undefined by resolving
the selection into a local const (using selectionMap.get(...)) and either skip
rendering the ListboxItem when that value is undefined (v-if) or provide a safe
fallback selection value before passing it to ListboxItem; adjust the loop over
toValue(allOutputs(asset)) accordingly so OutputHistoryItem is only rendered
paired with a valid selection to avoid runtime exceptions.
- Around line 313-324: The ListboxItem and OutputHistoryItem usage assumes
selectionMap.get(`slot:${item.id}`)! and item.output! are always present which
can throw on race conditions; change to defensive access by checking
selectionMap.has(`slot:${item.id}`) before using its value (or pass
undefined/null when missing) and render OutputHistoryItem only when item.output
is defined (or pass a safe fallback), i.e., update the v-for rendering logic
around ListboxItem, selectionMap and OutputHistoryItem to guard against missing
entries instead of using non-null assertions so the template won't access
undefined values during async updates.

---

Duplicate comments:
In `@src/renderer/extensions/linearMode/flattenNodeOutput.test.ts`:
- Line 12: The test suite currently uses a string title; change
describe('flattenNodeOutput', ...) to use the function reference
describe(flattenNodeOutput, ...) to satisfy
vitest/prefer-describe-function-title; ensure the flattenNodeOutput symbol is
imported or referenced in the test file (from the module supplying
flattenNodeOutput) so the identifier is available to describe.

In `@src/renderer/extensions/linearMode/LinearPreview.vue`:
- Around line 101-118: The two icon-only Button instances (the download button
tied to selectedOutput and downloadFile, and the interrupt button that calls
commandStore.execute('Comfy.Interrupt') when !executionStore.isIdle &&
!selectedItem) need accessible labels: add an aria-label prop bound to vue-i18n
(e.g. :aria-label="$t('linearPreview.download')" and
:aria-label="$t('linearPreview.interrupt')") on those Button components, and add
corresponding entries in src/locales/en/main.json under a new linearPreview
namespace (e.g. "download" and "interrupt"); do not add aria-labels to buttons
that already have visible text.

In `@src/renderer/extensions/linearMode/OutputHistoryItem.vue`:
- Around line 14-21: The image tag in OutputHistoryItem.vue lacks an alt
attribute which harms accessibility; update the <img> used when
getMediaType(output) === 'images' to include a meaningful alt (e.g., use
output.alt, output.description, or fallback like `Preview image`), ensuring the
template binds :alt to the chosen field (and add a safe fallback if the output
object may not have that property).

In `@src/renderer/extensions/linearMode/OutputPreviewItem.vue`:
- Around line 10-14: The <img> used for the latent preview in the
OutputPreviewItem.vue component is missing an alt attribute which harms
accessibility; update the <img v-if="latentPreview" ... :src="latentPreview" />
element to include an appropriate alt attribute (use alt="" if the image is
purely decorative or a short descriptive string if it conveys information) so
the latentPreview image is accessible to assistive technologies.

---

Nitpick comments:
In `@src/renderer/extensions/linearMode/linearOutputStore.ts`:
- Around line 258-271: The lifecycle handlers onJobStart, onLatentPreview,
onNodeExecuted, and onJobComplete are being exported from the linearOutputStore
even though they appear to be internal-only and can be called out of order; stop
exposing them publicly by removing them from the returned object and keep the
functions as internal helpers inside linearOutputStore (leave externally-used
API like inProgressItems, selectedId, trackedJobId, pendingResolve, select,
selectAsLatest, resolveIfReady untouched). If you need external access for
tests, provide a clearly-named test-only accessor (e.g.,
getInternalHooksForTest) or export a thin, controlled wrapper that enforces
ordering/validation rather than returning the raw handlers. Ensure references to
onJobStart/onLatentPreview/onNodeExecuted/onJobComplete within the module still
call the internal functions.

In `@src/renderer/extensions/linearMode/LinearPreview.vue`:
- Around line 101-134: The template currently uses inline arrow functions for
click handlers (e.g., the download button uses "() => { if (selectedOutput?.url)
downloadFile(selectedOutput.url) }", the download-all and delete entries use
"command: () => downloadAsset(selectedItem!)" and "command: () =>
mediaActions.deleteAssets(selectedItem!)"), and the interrupt button directly
calls commandStore.execute; move these inline expressions into named component
methods (for example create methods like onDownloadSelected(), onInterrupt(),
onDownloadItem(), onDeleteItem()) inside the component's methods block,
reference selectedOutput, selectedItem, downloadFile, downloadAsset,
mediaActions.deleteAssets and commandStore.execute from those methods, and
update the template/Popover entries to call the method names instead of arrow
functions so handlers are declared rather than inlined.

In `@src/renderer/extensions/linearMode/OutputHistory.vue`:
- Around line 218-244: The CanvasPointer is instantiated at module load (new
CanvasPointer(document.body)), which breaks SSR; change to declare let pointer:
CanvasPointer | null = null and defer initialization to onMounted (or guard with
typeof document !== 'undefined') so the instance is created only in the browser;
update the wheel handler (used with useEventListener) to check pointer is
non-null before calling pointer.isTrackpadGesture and ensure any listener
registration happens after mounting (or short-circuit if document is undefined)
and clean up/reset pointer on unmount.
- Around line 85-88: The callback parameter is currently untyped (val: unknown)
in onSelectionChange and uses a type assertion; change the signature to accept
the correct type (e.g., function onSelectionChange(val: SelectionValue |
undefined)) so you can remove the "as SelectionValue | undefined" assertion and
call store.select(val?.id ?? null) directly; if reka-ui's ListboxRoot exposes a
generic typed `@update`:model-value, prefer using that generic in the component's
prop/event typing to propagate SelectionValue into the handler, otherwise keep
the explicit typed parameter as shown.

In
`@src/renderer/extensions/vueNodes/widgets/components/layout/WidgetLayoutField.vue`:
- Around line 6-24: The code destructures defineProps (extracting rootClass)
which can break Vue reactivity; change to capture the whole props object via
defineProps (e.g., const props = defineProps<...>() or rely on the
template-exposed prop names) and update usages of rootClass in this file to
reference props.rootClass (or use the template-provided rootClass directly) so
reactivity for rootClass and other props is preserved; ensure
useHideLayoutField() remains unchanged and any references to rootClass in the
template or script are updated to the non-destructured form.

In `@src/views/LinearView.vue`:
- Around line 6-22: The file imports useI18n and destructures t via "const { t }
= useI18n()" even though t is only used in the template; remove the useI18n
import and the "const { t } = useI18n()" statement and update the template to
call $t(...) instead of t(...). Search for other occurrences of destructured t
or useI18n in this component (including the other occurrence referenced) and
apply the same change so all template translations use $t and no unused i18n
import or variable remains.

@pythongosssss pythongosssss force-pushed the pysssss/appmode/welcome-page.3 branch from e0f2de5 to b7071e5 Compare February 20, 2026 23:45
@pythongosssss pythongosssss force-pushed the pysssss/appmode/linear-run.3.5 branch from c0a9be1 to 3234ab0 Compare February 20, 2026 23:45
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.

A dedicated skeleton component was recently added in #8979 . I'm undecided on which animation is better, but they should probably be consolidated at some point.

PR has my tentative approval, but it's complex enough that I want to sleep on it.

return user_metadata?.outputCount ?? 0
}
const selectionMap = computed(
() => new Map(selectableItems.value.map((v) => [v.id, v]))
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm not a fan of the amount of recomputing done here when a selectableItem is created, but it's probably fine.

// the global keybinding handler on window. Intercept in capture phase
// and re-dispatch from above the Listbox.
function onModifierEnter(e: KeyboardEvent) {
if (e.key !== 'Enter' || !(e.ctrlKey || e.metaKey || e.shiftKey)) return
Copy link
Collaborator

Choose a reason for hiding this comment

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

I hate this, but I'm not seeing a cleaner fix.

@pythongosssss pythongosssss force-pushed the pysssss/appmode/welcome-page.3 branch 2 times, most recently from 8307211 to 7062141 Compare February 21, 2026 15:40
@pythongosssss pythongosssss force-pushed the pysssss/appmode/linear-run.3.5 branch from 3234ab0 to 6a8cee2 Compare February 21, 2026 16:13
AustinMroz
AustinMroz previously approved these changes Feb 21, 2026
@pythongosssss pythongosssss marked this pull request as ready for review February 21, 2026 21:02
@pythongosssss pythongosssss requested review from a team as code owners February 21, 2026 21:02
@dosubot dosubot bot added the size:XXL This PR changes 1000+ lines, ignoring generated files. label Feb 21, 2026
@dosubot
Copy link

dosubot bot commented Feb 21, 2026

Related Documentation

Checked 0 published document(s) in 1 knowledge base(s). No updates required.

How did I do? Any feedback?  Join Discord

@pythongosssss pythongosssss force-pushed the pysssss/appmode/welcome-page.3 branch from 7062141 to 052f890 Compare February 23, 2026 18:09
Base automatically changed from pysssss/appmode/welcome-page.3 to main February 23, 2026 18:16
@pythongosssss pythongosssss dismissed AustinMroz’s stale review February 23, 2026 18:16

The base branch was changed.

@dosubot dosubot bot added size:XL This PR changes 500-999 lines, ignoring generated files. and removed size:XXL This PR changes 1000+ lines, ignoring generated files. labels Feb 23, 2026
pythongosssss and others added 6 commits February 23, 2026 10:22
… → image) with

  follow/browse selection modes
  - Extract flattenNodeOutput, useOutputHistory, and shared types into dedicated modules
  - Decompose OutputHistory.vue into smaller components (OutputHistoryItem,
  OutputPreviewItem, LatentPreview, LinearProgressBar)
  - Simplify LinearView/LinearPreview by moving execution and selection state into the
  store
- fix event types
- fix tests
@pythongosssss pythongosssss force-pushed the pysssss/appmode/linear-run.3.5 branch from 6a8cee2 to 2f911ff Compare February 23, 2026 18:23
@dosubot dosubot bot added size:XXL This PR changes 1000+ lines, ignoring generated files. and removed size:XL This PR changes 500-999 lines, ignoring generated files. labels Feb 23, 2026
@github-actions
Copy link

📦 Bundle: 4.37 MB gzip 🔴 +3.22 kB

Details

Summary

  • Raw size: 20.6 MB baseline 20.5 MB — 🔴 +15.2 kB
  • Gzip: 4.37 MB baseline 4.37 MB — 🔴 +3.22 kB
  • Brotli: 3.38 MB baseline 3.38 MB — 🔴 +1.69 kB
  • Bundles: 224 current • 224 baseline • 155 added / 155 removed

Category Glance
Graph Workspace 🔴 +10.2 kB (943 kB) · Vendor & Third-Party 🔴 +5.35 kB (8.84 MB) · Data & Services 🟢 -443 B (2.52 MB) · Other 🔴 +117 B (7.62 MB) · Panels & Settings ⚪ 0 B (436 kB) · Views & Navigation ⚪ 0 B (68.8 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-B6S2Fztp.js (removed) 17.9 kB 🟢 -17.9 kB 🟢 -6.36 kB 🟢 -5.49 kB
assets/index-CgILPFuz.js (new) 17.9 kB 🔴 +17.9 kB 🔴 +6.36 kB 🔴 +5.5 kB

Status: 1 added / 1 removed

Graph Workspace — 943 kB (baseline 932 kB) • 🔴 +10.2 kB

Graph editor runtime, canvas, workflow orchestration

File Before After Δ Raw Δ Gzip Δ Brotli
assets/GraphView-VqKSrCwg.js (new) 943 kB 🔴 +943 kB 🔴 +203 kB 🔴 +154 kB
assets/GraphView-vvwwoSU6.js (removed) 932 kB 🟢 -932 kB 🟢 -201 kB 🟢 -152 kB

Status: 1 added / 1 removed

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

Top-level views, pages, and routed surfaces

File Before After Δ Raw Δ Gzip Δ Brotli
assets/CloudSurveyView-3EWi4ofU.js (removed) 15.5 kB 🟢 -15.5 kB 🟢 -3.32 kB 🟢 -2.83 kB
assets/CloudSurveyView-BIoUuFPb.js (new) 15.5 kB 🔴 +15.5 kB 🔴 +3.32 kB 🔴 +2.83 kB
assets/CloudLoginView--4NBAwZ7.js (removed) 10 kB 🟢 -10 kB 🟢 -2.93 kB 🟢 -2.57 kB
assets/CloudLoginView-C-v6GN74.js (new) 10 kB 🔴 +10 kB 🔴 +2.93 kB 🔴 +2.58 kB
assets/UserCheckView-C8sK7D8S.js (removed) 8.41 kB 🟢 -8.41 kB 🟢 -2.23 kB 🟢 -1.94 kB
assets/UserCheckView-Dt1N5tF7.js (new) 8.41 kB 🔴 +8.41 kB 🔴 +2.23 kB 🔴 +1.94 kB
assets/CloudSignupView-BkE1iKvJ.js (new) 7.41 kB 🔴 +7.41 kB 🔴 +2.32 kB 🔴 +2.03 kB
assets/CloudSignupView-IrBtd8X7.js (removed) 7.41 kB 🟢 -7.41 kB 🟢 -2.32 kB 🟢 -2.03 kB
assets/CloudLayoutView-B2PQK3hD.js (removed) 6.43 kB 🟢 -6.43 kB 🟢 -2.1 kB 🟢 -1.84 kB
assets/CloudLayoutView-BY5ZNFTT.js (new) 6.43 kB 🔴 +6.43 kB 🔴 +2.1 kB 🔴 +1.83 kB
assets/CloudForgotPasswordView-CDjtcRlP.js (new) 5.56 kB 🔴 +5.56 kB 🔴 +1.93 kB 🔴 +1.72 kB
assets/CloudForgotPasswordView-CoJoncmf.js (removed) 5.56 kB 🟢 -5.56 kB 🟢 -1.94 kB 🟢 -1.72 kB
assets/CloudAuthTimeoutView-_0dGq3Yz.js (removed) 4.91 kB 🟢 -4.91 kB 🟢 -1.77 kB 🟢 -1.56 kB
assets/CloudAuthTimeoutView-CNWYFDVf.js (new) 4.91 kB 🔴 +4.91 kB 🔴 +1.76 kB 🔴 +1.55 kB
assets/CloudSubscriptionRedirectView-9d7rB67Y.js (removed) 4.71 kB 🟢 -4.71 kB 🟢 -1.78 kB 🟢 -1.58 kB
assets/CloudSubscriptionRedirectView-CWHXxwnO.js (new) 4.71 kB 🔴 +4.71 kB 🔴 +1.78 kB 🔴 +1.57 kB
assets/UserSelectView-BI9_fjA5.js (removed) 4.5 kB 🟢 -4.5 kB 🟢 -1.64 kB 🟢 -1.47 kB
assets/UserSelectView-CQG-SPhY.js (new) 4.5 kB 🔴 +4.5 kB 🔴 +1.64 kB 🔴 +1.46 kB
assets/CloudSorryContactSupportView-Bi8IsTTh.js (removed) 1.02 kB 🟢 -1.02 kB 🟢 -539 B 🟢 -477 B
assets/CloudSorryContactSupportView-Dd0hIqYZ.js (new) 1.02 kB 🔴 +1.02 kB 🔴 +540 B 🔴 +463 B
assets/layout-CIz0E8h3.js (new) 296 B 🔴 +296 B 🔴 +225 B 🔴 +194 B
assets/layout-CmHcUwxS.js (removed) 296 B 🟢 -296 B 🟢 -223 B 🟢 -192 B

Status: 11 added / 11 removed

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

Configuration panels, inspectors, and settings screens

File Before After Δ Raw Δ Gzip Δ Brotli
assets/SecretsPanel-D6nW_PDF.js (new) 21.5 kB 🔴 +21.5 kB 🔴 +5.3 kB 🔴 +4.67 kB
assets/SecretsPanel-DhmIwTX3.js (removed) 21.5 kB 🟢 -21.5 kB 🟢 -5.31 kB 🟢 -4.66 kB
assets/LegacyCreditsPanel-1uzvkoS5.js (new) 20.6 kB 🔴 +20.6 kB 🔴 +5.56 kB 🔴 +4.9 kB
assets/LegacyCreditsPanel-C8mRnS0s.js (removed) 20.6 kB 🟢 -20.6 kB 🟢 -5.57 kB 🟢 -4.9 kB
assets/SubscriptionPanel-Cq-YewPi.js (removed) 18.6 kB 🟢 -18.6 kB 🟢 -4.74 kB 🟢 -4.18 kB
assets/SubscriptionPanel-OG59s1zB.js (new) 18.6 kB 🔴 +18.6 kB 🔴 +4.73 kB 🔴 +4.17 kB
assets/KeybindingPanel-B9zxBcCr.js (new) 12.3 kB 🔴 +12.3 kB 🔴 +3.57 kB 🔴 +3.17 kB
assets/KeybindingPanel-DSerxK6U.js (removed) 12.3 kB 🟢 -12.3 kB 🟢 -3.57 kB 🟢 -3.17 kB
assets/AboutPanel-B9_jm176.js (removed) 9.79 kB 🟢 -9.79 kB 🟢 -2.73 kB 🟢 -2.46 kB
assets/AboutPanel-DRhgKOST.js (new) 9.79 kB 🔴 +9.79 kB 🔴 +2.73 kB 🔴 +2.46 kB
assets/ExtensionPanel-B4pBJh_a.js (removed) 9.38 kB 🟢 -9.38 kB 🟢 -2.65 kB 🟢 -2.36 kB
assets/ExtensionPanel-BCNaxEn2.js (new) 9.38 kB 🔴 +9.38 kB 🔴 +2.65 kB 🔴 +2.36 kB
assets/ServerConfigPanel-Cd76fV6t.js (new) 6.44 kB 🔴 +6.44 kB 🔴 +2.13 kB 🔴 +1.92 kB
assets/ServerConfigPanel-FJirauzy.js (removed) 6.44 kB 🟢 -6.44 kB 🟢 -2.12 kB 🟢 -1.9 kB
assets/UserPanel-CpS3-nor.js (removed) 6.16 kB 🟢 -6.16 kB 🟢 -2 kB 🟢 -1.76 kB
assets/UserPanel-D5uN_A_A.js (new) 6.16 kB 🔴 +6.16 kB 🔴 +1.99 kB 🔴 +1.75 kB
assets/cloudRemoteConfig-Dg6x5x8L.js (removed) 1.44 kB 🟢 -1.44 kB 🟢 -714 B 🟢 -616 B
assets/cloudRemoteConfig-DgRTvmlz.js (new) 1.44 kB 🔴 +1.44 kB 🔴 +710 B 🔴 +614 B
assets/refreshRemoteConfig-MSV3zEkD.js (removed) 1.14 kB 🟢 -1.14 kB 🟢 -521 B 🟢 -458 B
assets/refreshRemoteConfig-pZly0gxN.js (new) 1.14 kB 🔴 +1.14 kB 🔴 +521 B 🔴 +458 B
assets/config-QxkqTZy6.js 996 B 996 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-3cK4vYSX.js 27.9 kB 27.9 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-AqJa7Oe1.js 28.7 kB 28.7 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-BOcWl0Qp.js 34.2 kB 34.2 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-BodhSOuG.js 30.5 kB 30.5 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-CCD8qxmc.js 27.8 kB 27.8 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-CVDNuOXV.js 23.9 kB 23.9 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-DLodCRRz.js 24.5 kB 24.5 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-DLqeCT09.js 38.5 kB 38.5 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-DP-OgTXN.js 29.9 kB 29.9 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-DRIXyZ_Z.js 28.8 kB 28.8 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-IRk9rDbu.js 32.4 kB 32.4 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/PasswordFields-CwRSeTaU.js (new) 4.51 kB 🔴 +4.51 kB 🔴 +1.36 kB 🔴 +1.2 kB
assets/PasswordFields-koUYSkkX.js (removed) 4.51 kB 🟢 -4.51 kB 🟢 -1.35 kB 🟢 -1.2 kB
assets/auth-DR9Hk6IW.js (new) 3.4 kB 🔴 +3.4 kB 🔴 +1.18 kB 🔴 +991 B
assets/auth-DxYCbDNe.js (removed) 3.4 kB 🟢 -3.4 kB 🟢 -1.18 kB 🟢 -992 B
assets/SignUpForm-BzzINouN.js (removed) 3.01 kB 🟢 -3.01 kB 🟢 -1.23 kB 🟢 -1.09 kB
assets/SignUpForm-CCcgSTPq.js (new) 3.01 kB 🔴 +3.01 kB 🔴 +1.23 kB 🔴 +1.09 kB
assets/UpdatePasswordContent-BJj4Y95u.js (new) 2.37 kB 🔴 +2.37 kB 🔴 +1.07 kB 🔴 +948 B
assets/UpdatePasswordContent-I3WzvNdv.js (removed) 2.37 kB 🟢 -2.37 kB 🟢 -1.07 kB 🟢 -948 B
assets/WorkspaceProfilePic-ClXtwRB6.js (removed) 1.57 kB 🟢 -1.57 kB 🟢 -822 B 🟢 -742 B
assets/WorkspaceProfilePic-Cv4HUNOU.js (new) 1.57 kB 🔴 +1.57 kB 🔴 +821 B 🔴 +707 B
assets/firebaseAuthStore-BJfOcDa4.js (new) 790 B 🔴 +790 B 🔴 +386 B 🔴 +350 B
assets/firebaseAuthStore-BjvTTnMe.js (removed) 790 B 🟢 -790 B 🟢 -390 B 🟢 -347 B
assets/auth-BGUmL_dW.js (removed) 357 B 🟢 -357 B 🟢 -225 B 🟢 -221 B
assets/auth-Dq2lbe4P.js (new) 357 B 🔴 +357 B 🔴 +223 B 🔴 +187 B

Status: 7 added / 7 removed

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

Modals, dialogs, drawers, and in-app editors

File Before After Δ Raw Δ Gzip Δ Brotli
assets/useSubscriptionDialog-BYGO1D88.js (removed) 738 B 🟢 -738 B 🟢 -379 B 🟢 -331 B
assets/useSubscriptionDialog-DtU9Czxt.js (new) 738 B 🔴 +738 B 🔴 +379 B 🔴 +328 B

Status: 1 added / 1 removed

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

Reusable component library chunks

File Before After Δ Raw Δ Gzip Δ Brotli
assets/useTerminalTabs-BbdWWdYV.js (removed) 9.85 kB 🟢 -9.85 kB 🟢 -3.4 kB 🟢 -2.99 kB
assets/useTerminalTabs-DECB1WeP.js (new) 9.85 kB 🔴 +9.85 kB 🔴 +3.4 kB 🔴 +3 kB
assets/ComfyQueueButton-BeILtef5.js (removed) 8.02 kB 🟢 -8.02 kB 🟢 -2.49 kB 🟢 -2.22 kB
assets/ComfyQueueButton-C4-wlTrm.js (new) 8.02 kB 🔴 +8.02 kB 🔴 +2.49 kB 🔴 +2.23 kB
assets/TopbarBadge-BG-5K9Vv.js (new) 7.45 kB 🔴 +7.45 kB 🔴 +1.81 kB 🔴 +1.6 kB
assets/TopbarBadge-YGzOQBJD.js (removed) 7.45 kB 🟢 -7.45 kB 🟢 -1.82 kB 🟢 -1.6 kB
assets/ScrubableNumberInput-CGdfiJAj.js (removed) 5.94 kB 🟢 -5.94 kB 🟢 -2.06 kB 🟢 -1.83 kB
assets/ScrubableNumberInput-CsIG82iD.js (new) 5.94 kB 🔴 +5.94 kB 🔴 +2.06 kB 🔴 +1.83 kB
assets/FormSearchInput-D31qG9G5.js (removed) 3.73 kB 🟢 -3.73 kB 🟢 -1.55 kB 🟢 -1.36 kB
assets/FormSearchInput-DF1KXWVW.js (new) 3.73 kB 🔴 +3.73 kB 🔴 +1.55 kB 🔴 +1.36 kB
assets/Button-CS7CXHnl.js (removed) 2.98 kB 🟢 -2.98 kB 🟢 -1.21 kB 🟢 -1.05 kB
assets/Button-DyEsVPYP.js (new) 2.98 kB 🔴 +2.98 kB 🔴 +1.2 kB 🔴 +1.05 kB
assets/SubscribeButton-BK5pHPS9.js (new) 2.35 kB 🔴 +2.35 kB 🔴 +1.02 kB 🔴 +887 B
assets/SubscribeButton-ByL9lK3g.js (removed) 2.35 kB 🟢 -2.35 kB 🟢 -1.02 kB 🟢 -890 B
assets/WidgetButton-Dej8hAUj.js (removed) 1.84 kB 🟢 -1.84 kB 🟢 -876 B 🟢 -779 B
assets/WidgetButton-DQnJbDgC.js (new) 1.84 kB 🔴 +1.84 kB 🔴 +875 B 🔴 +773 B
assets/cloudFeedbackTopbarButton-D32sJP9y.js (removed) 1.6 kB 🟢 -1.6 kB 🟢 -858 B 🟢 -731 B
assets/cloudFeedbackTopbarButton-ICZ_RzlT.js (new) 1.6 kB 🔴 +1.6 kB 🔴 +856 B 🔴 +766 B
assets/CloudBadge-CKN9xgaL.js (removed) 1.24 kB 🟢 -1.24 kB 🟢 -608 B 🟢 -526 B
assets/CloudBadge-Vch9A557.js (new) 1.24 kB 🔴 +1.24 kB 🔴 +609 B 🔴 +525 B
assets/UserAvatar-DcdcWs-e.js (new) 1.17 kB 🔴 +1.17 kB 🔴 +615 B 🔴 +525 B
assets/UserAvatar-DkTACzmk.js (removed) 1.17 kB 🟢 -1.17 kB 🟢 -618 B 🟢 -529 B
assets/ComfyQueueButton-DvGRhtNK.js (new) 795 B 🔴 +795 B 🔴 +394 B 🔴 +350 B
assets/ComfyQueueButton-VMqKGSvw.js (removed) 795 B 🟢 -795 B 🟢 -396 B 🟢 -351 B

Status: 12 added / 12 removed

Data & Services — 2.52 MB (baseline 2.52 MB) • 🟢 -443 B

Stores, services, APIs, and repositories

File Before After Δ Raw Δ Gzip Δ Brotli
assets/dialogService-yCa0YwOO.js (removed) 1.73 MB 🟢 -1.73 MB 🟢 -388 kB 🟢 -294 kB
assets/dialogService-69eiGfAp.js (new) 1.73 MB 🔴 +1.73 MB 🔴 +387 kB 🔴 +294 kB
assets/api-BFzAz93j.js (removed) 653 kB 🟢 -653 kB 🟢 -147 kB 🟢 -118 kB
assets/api-C4MTWvgI.js (new) 653 kB 🔴 +653 kB 🔴 +147 kB 🔴 +118 kB
assets/load3dService-BnuJHBWU.js (new) 91 kB 🔴 +91 kB 🔴 +19.1 kB 🔴 +16.4 kB
assets/load3dService-zfhWdUqX.js (removed) 91 kB 🟢 -91 kB 🟢 -19.1 kB 🟢 -16.4 kB
assets/systemStatsStore-Dv21XEwa.js (new) 12.7 kB 🔴 +12.7 kB 🔴 +4.42 kB 🔴 +3.88 kB
assets/systemStatsStore-My1DlP_a.js (removed) 12.7 kB 🟢 -12.7 kB 🟢 -4.42 kB 🟢 -3.89 kB
assets/releaseStore-BmBfi5Hb.js (new) 7.96 kB 🔴 +7.96 kB 🔴 +2.21 kB 🔴 +1.95 kB
assets/releaseStore-BYdlqJfR.js (removed) 7.96 kB 🟢 -7.96 kB 🟢 -2.22 kB 🟢 -1.95 kB
assets/keybindingService-BOSk8xqI.js (removed) 6.52 kB 🟢 -6.52 kB 🟢 -1.71 kB 🟢 -1.47 kB
assets/keybindingService-GyROTb1V.js (new) 6.52 kB 🔴 +6.52 kB 🔴 +1.71 kB 🔴 +1.47 kB
assets/serverConfigStore-Bv99woE0.js (removed) 2.32 kB 🟢 -2.32 kB 🟢 -789 B 🟢 -692 B
assets/serverConfigStore-DEs1_NHN.js (new) 2.32 kB 🔴 +2.32 kB 🔴 +791 B 🔴 +689 B
assets/bootstrapStore-BstGVCFA.js (removed) 2.08 kB 🟢 -2.08 kB 🟢 -871 B 🟢 -794 B
assets/bootstrapStore-DvGXmsdw.js (new) 2.08 kB 🔴 +2.08 kB 🔴 +874 B 🔴 +794 B
assets/userStore-B0TS6D84.js (new) 1.85 kB 🔴 +1.85 kB 🔴 +722 B 🔴 +637 B
assets/userStore-BUbFv-fQ.js (removed) 1.85 kB 🟢 -1.85 kB 🟢 -719 B 🟢 -673 B
assets/audioService-C0Ds0G9s.js (new) 1.73 kB 🔴 +1.73 kB 🔴 +848 B 🔴 +727 B
assets/audioService-D4MfchFk.js (removed) 1.73 kB 🟢 -1.73 kB 🟢 -850 B 🟢 -728 B
assets/releaseStore-Bp-fOiUb.js (removed) 762 B 🟢 -762 B 🟢 -389 B 🟢 -339 B
assets/releaseStore-QDxyHiQH.js (new) 762 B 🔴 +762 B 🔴 +386 B 🔴 +341 B
assets/settingStore-BeDUpZcW.js (new) 746 B 🔴 +746 B 🔴 +387 B 🔴 +342 B
assets/settingStore-Dj0rI6yS.js (removed) 746 B 🟢 -746 B 🟢 -390 B 🟢 -345 B
assets/workflowDraftStore-BgPMEGy_.js (new) 738 B 🔴 +738 B 🔴 +379 B 🔴 +334 B
assets/workflowDraftStore-C_GZEZJz.js (removed) 738 B 🟢 -738 B 🟢 -383 B 🟢 -336 B
assets/dialogService-CRi8o_u7.js (removed) 727 B 🟢 -727 B 🟢 -370 B 🟢 -330 B
assets/dialogService-DQLbIABQ.js (new) 727 B 🔴 +727 B 🔴 +368 B 🔴 +329 B

Status: 14 added / 14 removed

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

Helpers, composables, and utility bundles

File Before After Δ Raw Δ Gzip Δ Brotli
assets/useLoad3d-COvgnNiz.js (new) 14.6 kB 🔴 +14.6 kB 🔴 +3.63 kB 🔴 +3.21 kB
assets/useLoad3d-DPMmfY3L.js (removed) 14.6 kB 🟢 -14.6 kB 🟢 -3.63 kB 🟢 -3.22 kB
assets/useLoad3dViewer-bHBw54zs.js (removed) 14.1 kB 🟢 -14.1 kB 🟢 -3.15 kB 🟢 -2.8 kB
assets/useLoad3dViewer-Df2fr_sq.js (new) 14.1 kB 🔴 +14.1 kB 🔴 +3.15 kB 🔴 +2.8 kB
assets/colorUtil-DGTUhmp-.js (new) 7 kB 🔴 +7 kB 🔴 +2.14 kB 🔴 +1.9 kB
assets/colorUtil-m-0Os8lq.js (removed) 7 kB 🟢 -7 kB 🟢 -2.14 kB 🟢 -1.89 kB
assets/useFeatureFlags--Pc059wS.js (removed) 4.14 kB 🟢 -4.14 kB 🟢 -1.24 kB 🟢 -1.05 kB
assets/useFeatureFlags-BS7mYBk0.js (new) 4.14 kB 🔴 +4.14 kB 🔴 +1.24 kB 🔴 +1.06 kB
assets/useWorkspaceUI-BMUGVjWc.js (removed) 3 kB 🟢 -3 kB 🟢 -824 B 🟢 -705 B
assets/useWorkspaceUI-DooEW1B5.js (new) 3 kB 🔴 +3 kB 🔴 +822 B 🔴 +699 B
assets/useSubscriptionCredits-B2WD8Vvo.js (removed) 2.75 kB 🟢 -2.75 kB 🟢 -1.04 kB 🟢 -905 B
assets/useSubscriptionCredits-l9fPa-oh.js (new) 2.75 kB 🔴 +2.75 kB 🔴 +1.04 kB 🔴 +905 B
assets/subscriptionCheckoutUtil-BE_cE2zH.js (removed) 2.53 kB 🟢 -2.53 kB 🟢 -1.06 kB 🟢 -954 B
assets/subscriptionCheckoutUtil-CdfLhkhU.js (new) 2.53 kB 🔴 +2.53 kB 🔴 +1.06 kB 🔴 +923 B
assets/useExternalLink-DBbi0ejd.js (new) 1.66 kB 🔴 +1.66 kB 🔴 +770 B 🔴 +682 B
assets/useExternalLink-e5dEKrUg.js (removed) 1.66 kB 🟢 -1.66 kB 🟢 -772 B 🟢 -677 B
assets/useErrorHandling-COvfOrGq.js (removed) 1.5 kB 🟢 -1.5 kB 🟢 -631 B 🟢 -534 B
assets/useErrorHandling-DDEfVFv9.js (new) 1.5 kB 🔴 +1.5 kB 🔴 +630 B 🔴 +534 B
assets/useWorkspaceSwitch-CL6K-wQh.js (removed) 1.25 kB 🟢 -1.25 kB 🟢 -544 B 🟢 -483 B
assets/useWorkspaceSwitch-CSSpMNNC.js (new) 1.25 kB 🔴 +1.25 kB 🔴 +545 B 🔴 +480 B
assets/useLoad3d-C9-bnKFm.js (removed) 861 B 🟢 -861 B 🟢 -427 B 🟢 -383 B
assets/useLoad3d-D8gFmV8-.js (new) 861 B 🔴 +861 B 🔴 +424 B 🔴 +380 B
assets/audioUtils-BQd-rjVp.js (new) 858 B 🔴 +858 B 🔴 +499 B 🔴 +408 B
assets/audioUtils-Bwkeq7n3.js (removed) 858 B 🟢 -858 B 🟢 -501 B 🟢 -402 B
assets/useLoad3dViewer-9zaptSRi.js (new) 840 B 🔴 +840 B 🔴 +408 B 🔴 +371 B
assets/useLoad3dViewer-CQdtwXGX.js (removed) 840 B 🟢 -840 B 🟢 -412 B 🟢 -368 B
assets/useCurrentUser-25GJMQgH.js (removed) 724 B 🟢 -724 B 🟢 -374 B 🟢 -329 B
assets/useCurrentUser-DqKHhpQ_.js (new) 724 B 🔴 +724 B 🔴 +371 B 🔴 +328 B
assets/_plugin-vue_export-helper-CY4XIWDa.js 315 B 315 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/envUtil-BQSmRN2Q.js 466 B 466 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/markdownRendererUtil-DOdPeMQc.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: 14 added / 14 removed

Vendor & Third-Party — 8.84 MB (baseline 8.83 MB) • 🔴 +5.35 kB

External libraries and shared vendor chunks

File Before After Δ Raw Δ Gzip Δ Brotli
assets/vendor-primevue-Ca9moc73.js (removed) 1.73 MB 🟢 -1.73 MB 🟢 -311 kB 🟢 -190 kB
assets/vendor-primevue-D4sztoo6.js (new) 1.73 MB 🔴 +1.73 MB 🔴 +311 kB 🔴 +190 kB
assets/vendor-other-BzNZEmD_.js (removed) 1.52 MB 🟢 -1.52 MB 🟢 -319 kB 🟢 -254 kB
assets/vendor-other-LUzoxniz.js (new) 1.52 MB 🔴 +1.52 MB 🔴 +319 kB 🔴 +254 kB
assets/vendor-reka-ui-N1PzX--D.js (new) 387 kB 🔴 +387 kB 🔴 +74.2 kB 🔴 +59 kB
assets/vendor-reka-ui-E4SV0IAT.js (removed) 381 kB 🟢 -381 kB 🟢 -73.2 kB 🟢 -58.4 kB
assets/vendor-vue-core-DtiQ1dr9.js (new) 311 kB 🔴 +311 kB 🔴 +77 kB 🔴 +65 kB
assets/vendor-vue-core-CmHHRvL9.js (removed) 311 kB 🟢 -311 kB 🟢 -77 kB 🟢 -65 kB
assets/vendor-i18n-DccD0mxo.js (removed) 133 kB 🟢 -133 kB 🟢 -27.8 kB 🟢 -24 kB
assets/vendor-i18n-DNX73mqE.js (new) 133 kB 🔴 +133 kB 🔴 +27.8 kB 🔴 +23.9 kB
assets/vendor-vueuse-B4hGe0IQ.js (removed) 113 kB 🟢 -113 kB 🟢 -27.7 kB 🟢 -24.2 kB
assets/vendor-vueuse-D2jVNnmE.js (new) 113 kB 🔴 +113 kB 🔴 +27.7 kB 🔴 +24.2 kB
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-markdown-D5S6AC80.js 103 kB 103 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-CHaNo_rA.js 634 kB 634 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-xterm-vkxZGffR.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

Status: 6 added / 6 removed

Other — 7.62 MB (baseline 7.62 MB) • 🔴 +117 B

Bundles that do not match a named category

File Before After Δ Raw Δ Gzip Δ Brotli
assets/i18n-AaULPATR.js (new) 520 kB 🔴 +520 kB 🔴 +99.9 kB 🔴 +77.8 kB
assets/i18n-AQG5Bknd.js (removed) 520 kB 🟢 -520 kB 🟢 -99.9 kB 🟢 -77.8 kB
assets/core-88ChTIQU.js (removed) 72.8 kB 🟢 -72.8 kB 🟢 -18.8 kB 🟢 -16.1 kB
assets/core-Buj2TWYg.js (new) 72.8 kB 🔴 +72.8 kB 🔴 +18.8 kB 🔴 +16.1 kB
assets/groupNode-97hnezBu.js (removed) 71.8 kB 🟢 -71.8 kB 🟢 -17.7 kB 🟢 -15.5 kB
assets/groupNode-DQSmOYpc.js (new) 71.8 kB 🔴 +71.8 kB 🔴 +17.7 kB 🔴 +15.6 kB
assets/WidgetSelect-ClZeRotH.js (removed) 58.1 kB 🟢 -58.1 kB 🟢 -12.4 kB 🟢 -10.7 kB
assets/WidgetSelect-DM0j-Eu1.js (new) 58.1 kB 🔴 +58.1 kB 🔴 +12.4 kB 🔴 +10.7 kB
assets/SubscriptionRequiredDialogContentWorkspace-CaLidiv0.js (removed) 45.8 kB 🟢 -45.8 kB 🟢 -8.56 kB 🟢 -7.42 kB
assets/SubscriptionRequiredDialogContentWorkspace-qv_KCj3c.js (new) 45.8 kB 🔴 +45.8 kB 🔴 +8.56 kB 🔴 +7.42 kB
assets/Load3DControls-B2c-4jo0.js (removed) 30.9 kB 🟢 -30.9 kB 🟢 -5.34 kB 🟢 -4.64 kB
assets/Load3DControls-DEfWLaww.js (new) 30.9 kB 🔴 +30.9 kB 🔴 +5.34 kB 🔴 +4.64 kB
assets/WorkspacePanelContent-hOng7STg.js (new) 29.3 kB 🔴 +29.3 kB 🔴 +6.14 kB 🔴 +5.41 kB
assets/WorkspacePanelContent-VJmulcxY.js (removed) 29.3 kB 🟢 -29.3 kB 🟢 -6.14 kB 🟢 -5.39 kB
assets/SubscriptionRequiredDialogContent-CiscNzLe.js (removed) 26.2 kB 🟢 -26.2 kB 🟢 -6.57 kB 🟢 -5.78 kB
assets/SubscriptionRequiredDialogContent-DwMRjuxC.js (new) 26.2 kB 🔴 +26.2 kB 🔴 +6.56 kB 🔴 +5.78 kB
assets/Load3dViewerContent-CjzKDt2j.js (new) 23 kB 🔴 +23 kB 🔴 +5.18 kB 🔴 +4.49 kB
assets/Load3dViewerContent-DDLB1pI6.js (removed) 23 kB 🟢 -23 kB 🟢 -5.18 kB 🟢 -4.5 kB
assets/WidgetImageCrop-BHo3-i8r.js (removed) 22.1 kB 🟢 -22.1 kB 🟢 -5.51 kB 🟢 -4.86 kB
assets/WidgetImageCrop-ej7lQdgq.js (new) 22.1 kB 🔴 +22.1 kB 🔴 +5.51 kB 🔴 +4.86 kB
assets/SubscriptionPanelContentWorkspace-B3Ie6Mex.js (new) 21.6 kB 🔴 +21.6 kB 🔴 +5.02 kB 🔴 +4.43 kB
assets/SubscriptionPanelContentWorkspace-CuAQ9hal.js (removed) 21.6 kB 🟢 -21.6 kB 🟢 -5.02 kB 🟢 -4.43 kB
assets/CurrentUserPopoverWorkspace--P6sGLoD.js (removed) 19.8 kB 🟢 -19.8 kB 🟢 -4.86 kB 🟢 -4.34 kB
assets/CurrentUserPopoverWorkspace-D23N_wJq.js (new) 19.8 kB 🔴 +19.8 kB 🔴 +4.86 kB 🔴 +4.33 kB
assets/SignInContent-BKR06jOm.js (new) 18.9 kB 🔴 +18.9 kB 🔴 +4.79 kB 🔴 +4.19 kB
assets/SignInContent-Dfl8_yEa.js (removed) 18.9 kB 🟢 -18.9 kB 🟢 -4.79 kB 🟢 -4.2 kB
assets/WidgetInputNumber-CFUnqQ6_.js (new) 18.7 kB 🔴 +18.7 kB 🔴 +4.75 kB 🔴 +4.22 kB
assets/WidgetInputNumber-DInB8YmE.js (removed) 18.6 kB 🟢 -18.6 kB 🟢 -4.71 kB 🟢 -4.18 kB
assets/WidgetRecordAudio-DDs6SCpb.js (new) 17.3 kB 🔴 +17.3 kB 🔴 +4.94 kB 🔴 +4.43 kB
assets/WidgetRecordAudio-QT_UX8ul.js (removed) 17.3 kB 🟢 -17.3 kB 🟢 -4.94 kB 🟢 -4.43 kB
assets/Load3D-BbNOsszc.js (new) 16.2 kB 🔴 +16.2 kB 🔴 +4.03 kB 🔴 +3.51 kB
assets/Load3D-DKuhTLzc.js (removed) 16.2 kB 🟢 -16.2 kB 🟢 -4.03 kB 🟢 -3.51 kB
assets/load3d-CAvqP0GK.js (removed) 14.7 kB 🟢 -14.7 kB 🟢 -4.2 kB 🟢 -3.63 kB
assets/load3d-CvjYbVxr.js (new) 14.7 kB 🔴 +14.7 kB 🔴 +4.19 kB 🔴 +3.63 kB
assets/AudioPreviewPlayer-taY_7WZT.js (new) 10.9 kB 🔴 +10.9 kB 🔴 +3.19 kB 🔴 +2.85 kB
assets/AudioPreviewPlayer-Z_d6P1jI.js (removed) 10.9 kB 🟢 -10.9 kB 🟢 -3.2 kB 🟢 -2.86 kB
assets/changeTracker-BYQ2BBqy.js (removed) 9.38 kB 🟢 -9.38 kB 🟢 -2.9 kB 🟢 -2.55 kB
assets/changeTracker-iVOcG5Ef.js (new) 9.38 kB 🔴 +9.38 kB 🔴 +2.89 kB 🔴 +2.54 kB
assets/nodeTemplates-COSO-9c0.js (removed) 9.3 kB 🟢 -9.3 kB 🟢 -3.26 kB 🟢 -2.87 kB
assets/nodeTemplates-DOFh20v9.js (new) 9.3 kB 🔴 +9.3 kB 🔴 +3.26 kB 🔴 +2.86 kB
assets/SelectValue-BVOyzWx4.js (new) 8.94 kB 🔴 +8.94 kB 🔴 +2.27 kB 🔴 +2 kB
assets/SelectValue-CVlctRkk.js (removed) 8.94 kB 🟢 -8.94 kB 🟢 -2.27 kB 🟢 -2 kB
assets/InviteMemberDialogContent-71WEltVU.js (removed) 7.38 kB 🟢 -7.38 kB 🟢 -2.3 kB 🟢 -2.01 kB
assets/InviteMemberDialogContent-C1pfS2uy.js (new) 7.38 kB 🔴 +7.38 kB 🔴 +2.29 kB 🔴 +2 kB
assets/WidgetToggleSwitch-BWxlrQSQ.js (new) 6.8 kB 🔴 +6.8 kB 🔴 +2.19 kB 🔴 +1.94 kB
assets/WidgetToggleSwitch-CYtH_4Pb.js (removed) 6.8 kB 🟢 -6.8 kB 🟢 -2.19 kB 🟢 -1.94 kB
assets/Load3DConfiguration-C_F-UxPS.js (new) 6.27 kB 🔴 +6.27 kB 🔴 +1.91 kB 🔴 +1.68 kB
assets/Load3DConfiguration-DW8DFZdh.js (removed) 6.27 kB 🟢 -6.27 kB 🟢 -1.92 kB 🟢 -1.68 kB
assets/CreateWorkspaceDialogContent-66FxXS65.js (new) 5.53 kB 🔴 +5.53 kB 🔴 +1.99 kB 🔴 +1.75 kB
assets/CreateWorkspaceDialogContent-uwsb4mzf.js (removed) 5.53 kB 🟢 -5.53 kB 🟢 -1.99 kB 🟢 -1.74 kB
assets/onboardingCloudRoutes-jBbboNbc.js (removed) 5.41 kB 🟢 -5.41 kB 🟢 -1.84 kB 🟢 -1.6 kB
assets/onboardingCloudRoutes-JFyWG_8Y.js (new) 5.41 kB 🔴 +5.41 kB 🔴 +1.84 kB 🔴 +1.61 kB
assets/EditWorkspaceDialogContent-C-rBpdf9.js (new) 5.33 kB 🔴 +5.33 kB 🔴 +1.95 kB 🔴 +1.7 kB
assets/EditWorkspaceDialogContent-DZ0pF4DV.js (removed) 5.33 kB 🟢 -5.33 kB 🟢 -1.95 kB 🟢 -1.7 kB
assets/ValueControlPopover-BdR_svv_.js (removed) 4.92 kB 🟢 -4.92 kB 🟢 -1.77 kB 🟢 -1.59 kB
assets/ValueControlPopover-DDr6RWCc.js (new) 4.92 kB 🔴 +4.92 kB 🔴 +1.76 kB 🔴 +1.58 kB
assets/Preview3d-COccrcQu.js (removed) 4.82 kB 🟢 -4.82 kB 🟢 -1.57 kB 🟢 -1.37 kB
assets/Preview3d-CT9cjiGf.js (new) 4.82 kB 🔴 +4.82 kB 🔴 +1.56 kB 🔴 +1.37 kB
assets/CancelSubscriptionDialogContent-BalLpJNV.js (new) 4.79 kB 🔴 +4.79 kB 🔴 +1.78 kB 🔴 +1.56 kB
assets/CancelSubscriptionDialogContent-CgF5om_Q.js (removed) 4.79 kB 🟢 -4.79 kB 🟢 -1.78 kB 🟢 -1.57 kB
assets/AnimationControls-CyAHRa3f.js (new) 4.61 kB 🔴 +4.61 kB 🔴 +1.6 kB 🔴 +1.41 kB
assets/AnimationControls-DJ6xi24m.js (removed) 4.61 kB 🟢 -4.61 kB 🟢 -1.6 kB 🟢 -1.41 kB
assets/DeleteWorkspaceDialogContent-BT5CeqRU.js (removed) 4.23 kB 🟢 -4.23 kB 🟢 -1.64 kB 🟢 -1.42 kB
assets/DeleteWorkspaceDialogContent-CVU33W15.js (new) 4.23 kB 🔴 +4.23 kB 🔴 +1.63 kB 🔴 +1.42 kB
assets/WidgetWithControl-C6QHwVnJ.js (new) 4.11 kB 🔴 +4.11 kB 🔴 +1.78 kB 🔴 +1.59 kB
assets/WidgetWithControl-Dm45ZYLr.js (removed) 4.11 kB 🟢 -4.11 kB 🟢 -1.78 kB 🟢 -1.63 kB
assets/LeaveWorkspaceDialogContent-BQMF9Wfl.js (new) 4.06 kB 🔴 +4.06 kB 🔴 +1.58 kB 🔴 +1.37 kB
assets/LeaveWorkspaceDialogContent-BSPFj4p-.js (removed) 4.06 kB 🟢 -4.06 kB 🟢 -1.58 kB 🟢 -1.38 kB
assets/RemoveMemberDialogContent-Dkhx2ifU.js (new) 4.04 kB 🔴 +4.04 kB 🔴 +1.52 kB 🔴 +1.33 kB
assets/RemoveMemberDialogContent-Z3XfdU61.js (removed) 4.04 kB 🟢 -4.04 kB 🟢 -1.53 kB 🟢 -1.34 kB
assets/WidgetTextarea-BekkBcLd.js (new) 3.96 kB 🔴 +3.96 kB 🔴 +1.49 kB 🔴 +1.3 kB
assets/WidgetTextarea-BF39Y5Iz.js (removed) 3.96 kB 🟢 -3.96 kB 🟢 -1.49 kB 🟢 -1.3 kB
assets/RevokeInviteDialogContent-BVvnpm4d.js (new) 3.96 kB 🔴 +3.96 kB 🔴 +1.54 kB 🔴 +1.36 kB
assets/RevokeInviteDialogContent-BxdeTgvo.js (removed) 3.96 kB 🟢 -3.96 kB 🟢 -1.54 kB 🟢 -1.36 kB
assets/InviteMemberUpsellDialogContent-B5b_KGl4.js (new) 3.83 kB 🔴 +3.83 kB 🔴 +1.4 kB 🔴 +1.23 kB
assets/InviteMemberUpsellDialogContent-CINPm1qb.js (removed) 3.83 kB 🟢 -3.83 kB 🟢 -1.41 kB 🟢 -1.24 kB
assets/Popover-BJnU6qt6.js (new) 3.65 kB 🔴 +3.65 kB 🔴 +1.44 kB 🔴 +1.27 kB
assets/Popover-DWddnzPZ.js (removed) 3.65 kB 🟢 -3.65 kB 🟢 -1.44 kB 🟢 -1.27 kB
assets/WidgetGalleria-Bb1DZ9I_.js (removed) 3.61 kB 🟢 -3.61 kB 🟢 -1.39 kB 🟢 -1.25 kB
assets/WidgetGalleria-DFOYH-wE.js (new) 3.61 kB 🔴 +3.61 kB 🔴 +1.4 kB 🔴 +1.25 kB
assets/Slider-BbsmghaM.js (new) 3.52 kB 🔴 +3.52 kB 🔴 +1.36 kB 🔴 +1.18 kB
assets/Slider-DcXYEdHo.js (removed) 3.52 kB 🟢 -3.52 kB 🟢 -1.37 kB 🟢 -1.19 kB
assets/saveMesh-3fNIWgOT.js (new) 3.38 kB 🔴 +3.38 kB 🔴 +1.46 kB 🔴 +1.29 kB
assets/saveMesh-Ch_JEplX.js (removed) 3.38 kB 🟢 -3.38 kB 🟢 -1.46 kB 🟢 -1.29 kB
assets/WidgetBoundingBox-BETzz-Ao.js (removed) 3.19 kB 🟢 -3.19 kB 🟢 -896 B 🟢 -778 B
assets/WidgetBoundingBox-DHXaBAGy.js (new) 3.19 kB 🔴 +3.19 kB 🔴 +894 B 🔴 +778 B
assets/cloudSessionCookie-CYFKxvgc.js (removed) 3.1 kB 🟢 -3.1 kB 🟢 -1.09 kB 🟢 -968 B
assets/cloudSessionCookie-wrRp1qF8.js (new) 3.1 kB 🔴 +3.1 kB 🔴 +1.09 kB 🔴 +960 B
assets/WidgetImageCompare-DpWt7SLo.js (removed) 3.1 kB 🟢 -3.1 kB 🟢 -1.15 kB 🟢 -994 B
assets/WidgetImageCompare-DqrezrkI.js (new) 3.1 kB 🔴 +3.1 kB 🔴 +1.15 kB 🔴 +1.02 kB
assets/WidgetMarkdown-Cqk0nyRJ.js (new) 2.93 kB 🔴 +2.93 kB 🔴 +1.23 kB 🔴 +1.07 kB
assets/WidgetMarkdown-Vq4skE3X.js (removed) 2.93 kB 🟢 -2.93 kB 🟢 -1.23 kB 🟢 -1.08 kB
assets/GlobalToast-B24UO3xH.js (new) 2.91 kB 🔴 +2.91 kB 🔴 +1.21 kB 🔴 +1.03 kB
assets/GlobalToast-BKoVZucj.js (removed) 2.91 kB 🟢 -2.91 kB 🟢 -1.21 kB 🟢 -1.03 kB
assets/WidgetColorPicker-B4mml5Z1.js (removed) 2.9 kB 🟢 -2.9 kB 🟢 -1.23 kB 🟢 -1.1 kB
assets/WidgetColorPicker-BT0--RBS.js (new) 2.9 kB 🔴 +2.9 kB 🔴 +1.23 kB 🔴 +1.1 kB
assets/MediaVideoTop-3eH_a2Z8.js (new) 2.77 kB 🔴 +2.77 kB 🔴 +1.13 kB 🔴 +992 B
assets/MediaVideoTop-Dxvkupp8.js (removed) 2.77 kB 🟢 -2.77 kB 🟢 -1.13 kB 🟢 -1 kB
assets/ApiNodesSignInContent-BZ_QRkh1.js (new) 2.69 kB 🔴 +2.69 kB 🔴 +1.05 kB 🔴 +918 B
assets/ApiNodesSignInContent-eJLV77p5.js (removed) 2.69 kB 🟢 -2.69 kB 🟢 -1.05 kB 🟢 -924 B
assets/WidgetChart-BPLD0Sx8.js (removed) 2.21 kB 🟢 -2.21 kB 🟢 -953 B 🟢 -825 B
assets/WidgetChart-crz6xOcB.js (new) 2.21 kB 🔴 +2.21 kB 🔴 +951 B 🔴 +820 B
assets/SubscribeToRun-BnZldrtV.js (new) 2.2 kB 🔴 +2.2 kB 🔴 +1.01 kB 🔴 +882 B
assets/SubscribeToRun-Dd0sSCGj.js (removed) 2.2 kB 🟢 -2.2 kB 🟢 -1.01 kB 🟢 -869 B
assets/WidgetLayoutField-BGDGIZQQ.js (new) 1.98 kB 🔴 +1.98 kB 🔴 +894 B 🔴 +773 B
assets/WidgetLayoutField-hmcBQt7E.js (removed) 1.95 kB 🟢 -1.95 kB 🟢 -877 B 🟢 -763 B
assets/WidgetInputText-Bkj-AL52.js (removed) 1.86 kB 🟢 -1.86 kB 🟢 -873 B 🟢 -790 B
assets/WidgetInputText-Br3WkRHc.js (new) 1.86 kB 🔴 +1.86 kB 🔴 +872 B 🔴 +789 B
assets/Media3DTop-CpdNXNf1.js (new) 1.82 kB 🔴 +1.82 kB 🔴 +896 B 🔴 +764 B
assets/Media3DTop-D3QRJ6eZ.js (removed) 1.82 kB 🟢 -1.82 kB 🟢 -902 B 🟢 -770 B
assets/BaseViewTemplate-Bn_mihBn.js (new) 1.78 kB 🔴 +1.78 kB 🔴 +923 B 🔴 +833 B
assets/BaseViewTemplate-sbUO3_hD.js (removed) 1.78 kB 🟢 -1.78 kB 🟢 -924 B 🟢 -805 B
assets/MediaImageTop-vhWMCCeS.js (new) 1.75 kB 🔴 +1.75 kB 🔴 +878 B 🔴 +751 B
assets/MediaImageTop-XsW6Reb5.js (removed) 1.75 kB 🟢 -1.75 kB 🟢 -880 B 🟢 -753 B
assets/CloudRunButtonWrapper-12-ReXey.js (new) 1.68 kB 🔴 +1.68 kB 🔴 +782 B 🔴 +705 B
assets/CloudRunButtonWrapper-B06hv6yr.js (removed) 1.68 kB 🟢 -1.68 kB 🟢 -789 B 🟢 -721 B
assets/signInSchema-D7Vz7LWf.js (new) 1.53 kB 🔴 +1.53 kB 🔴 +563 B 🔴 +492 B
assets/signInSchema-DuRxuuFe.js (removed) 1.53 kB 🟢 -1.53 kB 🟢 -563 B 🟢 -521 B
assets/MediaAudioTop-qYtfOhNn.js (new) 1.43 kB 🔴 +1.43 kB 🔴 +763 B 🔴 +632 B
assets/MediaAudioTop-vXuump5i.js (removed) 1.43 kB 🟢 -1.43 kB 🟢 -763 B 🟢 -638 B
assets/cloudBadges-BVF8o_kD.js (new) 1.37 kB 🔴 +1.37 kB 🔴 +703 B 🔴 +612 B
assets/cloudBadges-DhL0gti8.js (removed) 1.37 kB 🟢 -1.37 kB 🟢 -708 B 🟢 -614 B
assets/VideoPlayOverlay-DtqFG8Nu.js (new) 1.35 kB 🔴 +1.35 kB 🔴 +703 B 🔴 +620 B
assets/VideoPlayOverlay-vVfixIxQ.js (removed) 1.35 kB 🟢 -1.35 kB 🟢 -703 B 🟢 -622 B
assets/cloudSubscription-C0pBVD8a.js (new) 1.33 kB 🔴 +1.33 kB 🔴 +656 B 🔴 +568 B
assets/cloudSubscription-DDAtzVdB.js (removed) 1.33 kB 🟢 -1.33 kB 🟢 -661 B 🟢 -572 B
assets/Load3D-_wKMhw2u.js (removed) 1.07 kB 🟢 -1.07 kB 🟢 -501 B 🟢 -445 B
assets/Load3D-Cn5YptVn.js (new) 1.07 kB 🔴 +1.07 kB 🔴 +494 B 🔴 +441 B
assets/MediaOtherTop-B7AUGEAD.js (new) 1.02 kB 🔴 +1.02 kB 🔴 +570 B 🔴 +475 B
assets/MediaOtherTop-Crhpst_D.js (removed) 1.02 kB 🟢 -1.02 kB 🟢 -568 B 🟢 -472 B
assets/MediaTextTop-BWwMUA0Y.js (new) 1.01 kB 🔴 +1.01 kB 🔴 +565 B 🔴 +476 B
assets/MediaTextTop-DLkC3BlW.js (removed) 1.01 kB 🟢 -1.01 kB 🟢 -566 B 🟢 -479 B
assets/nightlyBadges-C-5tFFiz.js (new) 1 kB 🔴 +1 kB 🔴 +533 B 🔴 +472 B
assets/nightlyBadges-Csb8HGn0.js (removed) 1 kB 🟢 -1 kB 🟢 -536 B 🟢 -472 B
assets/Load3dViewerContent-BxPSlmmz.js (removed) 995 B 🟢 -995 B 🟢 -471 B 🟢 -421 B
assets/Load3dViewerContent-PDRDrIn1.js (new) 995 B 🔴 +995 B 🔴 +468 B 🔴 +415 B
assets/SubscriptionPanelContentWorkspace-0e83cfSr.js (new) 932 B 🔴 +932 B 🔴 +436 B 🔴 +381 B
assets/SubscriptionPanelContentWorkspace-CvYxhUkO.js (removed) 932 B 🟢 -932 B 🟢 -440 B 🟢 -383 B
assets/ComfyOrgHeader-Ce4UTXTl.js (new) 910 B 🔴 +910 B 🔴 +496 B 🔴 +467 B
assets/ComfyOrgHeader-CKD9vwNi.js (removed) 910 B 🟢 -910 B 🟢 -495 B 🟢 -423 B
assets/graphHasMissingNodes-C8JoCCO8.js (new) 761 B 🔴 +761 B 🔴 +375 B 🔴 +332 B
assets/graphHasMissingNodes-DWVBt-ds.js (removed) 761 B 🟢 -761 B 🟢 -373 B 🟢 -352 B
assets/changeTracker-BfU98JuG.js (removed) 759 B 🟢 -759 B 🟢 -389 B 🟢 -341 B
assets/changeTracker-CK-miK7M.js (new) 759 B 🔴 +759 B 🔴 +386 B 🔴 +340 B
assets/WidgetLegacy-Bc7SkxoJ.js (removed) 747 B 🟢 -747 B 🟢 -388 B 🟢 -340 B
assets/WidgetLegacy-gK4o9XtO.js (new) 747 B 🔴 +747 B 🔴 +386 B 🔴 +338 B
assets/WidgetInputNumber-BY2IFvom.js (removed) 469 B 🟢 -469 B 🟢 -265 B 🟢 -227 B
assets/WidgetInputNumber-DcNn8FJb.js (new) 469 B 🔴 +469 B 🔴 +262 B 🔴 +226 B
assets/widgetTypes-B7_LakDW.js (new) 393 B 🔴 +393 B 🔴 +259 B 🔴 +244 B
assets/widgetTypes-BtuQMzwn.js (removed) 393 B 🟢 -393 B 🟢 -257 B 🟢 -213 B
assets/WidgetBoundingBox-BKtZa_-z.js (removed) 283 B 🟢 -283 B 🟢 -185 B 🟢 -166 B
assets/WidgetBoundingBox-egY4lDkC.js (new) 283 B 🔴 +283 B 🔴 +184 B 🔴 +162 B
assets/src-1EXhnvTZ.js (removed) 251 B 🟢 -251 B 🟢 -212 B 🟢 -195 B
assets/src-D4w3iVDe.js (new) 251 B 🔴 +251 B 🔴 +209 B 🔴 +180 B
assets/i18n-BV3QICzA.js (removed) 199 B 🟢 -199 B 🟢 -161 B 🟢 -138 B
assets/i18n-DoU8BO7X.js (new) 199 B 🔴 +199 B 🔴 +161 B 🔴 +140 B
assets/auto-BTnZwrs2.js 1.7 kB 1.7 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/comfy-logo-single-DhnNuB-i.js 198 B 198 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-BIWmUVEc.js 16.9 kB 16.9 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-BOt38VCw.js 16.1 kB 16.1 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-C0NcxRH3.js 18.8 kB 18.8 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-C2xuXGb5.js 17.5 kB 17.5 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-CmiKIQwc.js 16.1 kB 16.1 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-DMwP8S5e.js 16.7 kB 16.7 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-DwBt3HhT.js 15.9 kB 15.9 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-JX559A2n.js 15.1 kB 15.1 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-n_2KJWcD.js 15.2 kB 15.2 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-OO-8nFm9.js 16.1 kB 16.1 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-wq2Y-YKn.js 17.5 kB 17.5 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/constants-BIWjk1ar.js 579 B 579 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-BgWDQhKn.js 145 kB 145 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-BH99zfen.js 148 kB 148 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-Cl0TDHs-.js 143 kB 143 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-CTgQOf6T.js 196 kB 196 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-CTIe-PdP.js 140 kB 140 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-CxmFjxCA.js 176 kB 176 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-D_siPGTl.js 162 kB 162 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-D-U7Lzb5.js 170 kB 170 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-d6c2x81N.js 123 kB 123 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-DN0hguJt.js 141 kB 141 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-DzJxAN6l.js 124 kB 124 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-2U4KtUml.js 360 kB 360 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-5rcOJVex.js 391 kB 391 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-BTBSP0IQ.js 480 kB 480 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-BZEpP7w6.js 383 kB 383 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-C-Tw-7I_.js 407 kB 407 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-C76rbmLh.js 441 kB 441 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-CxxD1ymF.js 395 kB 395 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-DciNsuYe.js 392 kB 392 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-G7wcMpUD.js 356 kB 356 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-nGdg-hIO.js 388 kB 388 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-xiPSrG6S.js 440 kB 440 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/OBJLoader2WorkerModule-DTMpvldF.js 109 kB 109 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/previousFullPath-DeIFnh1k.js 665 B 665 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/rolldown-runtime-DLICfi3-.js 1.97 kB 1.97 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/widget-DTUjK0ZE.js 445 B 445 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/widgetPropFilter-Cd0O5LPR.js 1.1 kB 1.1 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B

Status: 78 added / 78 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: 2

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/OutputHistory.vue (1)

220-244: ⚠️ Potential issue | 🟠 Major

Global Ctrl+wheel capture on document.body prevents browser zoom site-wide.

The handler at line 224-226 calls preventDefault() + stopPropagation() on every Ctrl+wheel event, which disables the browser's native pinch/zoom across the entire page — not just over the output history area. Users who rely on Ctrl+scroll to zoom the page will be unable to do so while this component is mounted.

Consider scoping the capture to the outputsRef element instead of document.body, or adding a check that the event target is within the outputs area.

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

In `@src/renderer/extensions/linearMode/OutputHistory.vue` around lines 220 - 244,
The wheel handler currently registered with useEventListener(document.body, ...)
calls preventDefault()/stopPropagation() for every Ctrl+wheel and thus blocks
browser zoom globally; update the listener to only act when the event originates
inside the output area (e.g., scope the listener to outputsRef instead of
document.body or add an early guard like if
(!outputsRef?.value?.contains(e.target as Node)) return), and only call
e.preventDefault() / e.stopPropagation() and run pointer.isTrackpadGesture /
navigateToAdjacent / scrollOffset logic when the event is within that outputsRef
element; keep the same options ({ capture: true, passive: false }) when
attaching to the scoped element.
♻️ Duplicate comments (3)
src/renderer/extensions/linearMode/LinearControls.vue (1)

158-163: !-important modifier removed — styling split looks correct.

root-class now carries the grid overrides while class targets the inner input wrapper; tailwind-merge inside cn() correctly lets grid-cols-[auto_96px] supersede grid-cols-subgrid, and w-24 (96 px) is consistent with the 96 px grid column. Resolves the prior review flag.

Also applies to: 266-271

🤖 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 158 -
163, The styling change removed the `!`-important modifier and split layout
rules correctly between props: ensure the WidgetInputNumberInput usage sets the
grid override on the root with `root-class="text-base-foreground
grid-cols-[auto_96px]"` and places the input wrapper width on
`class="*:[.min-w-0]:w-24"` so tailwind-merge via cn() lets
`grid-cols-[auto_96px]` supersede `grid-cols-subgrid` and `w-24` equals 96px;
apply the identical adjustment to the other occurrence of WidgetInputNumberInput
(the one around lines 266-271) so both instances use `root-class` for grid
overrides and `class` for the inner input wrapper.
src/renderer/extensions/linearMode/OutputPreviewItem.vue (1)

10-14: ⚠️ Potential issue | 🟡 Minor

Add alt attribute to the <img> element.

The image is missing an alt attribute. For a purely decorative latent preview, use alt="" to suppress screen-reader noise. If the image carries meaningful content, provide a descriptive i18n string.

🔧 Proposed fix
     <img
       v-if="latentPreview"
       class="block size-10 rounded-sm object-cover"
       :src="latentPreview"
+      alt=""
     />
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/extensions/linearMode/OutputPreviewItem.vue` around lines 10 -
14, The <img> in OutputPreviewItem.vue that uses v-if="latentPreview" and
:src="latentPreview" is missing an alt attribute; add an alt attribute to that
<img> — if the preview is decorative set alt="" to hide it from screen readers,
otherwise bind a localized descriptive string (e.g. :alt="$t('…')" or a
computed/prop like :alt="latentPreviewAlt") so the image always has an
accessible alt value.
src/renderer/extensions/linearMode/OutputHistoryItem.vue (1)

14-21: ⚠️ Potential issue | 🟡 Minor

Add an alt attribute to the <img> element.

The image is missing an alt attribute, which is needed for accessibility and HTML validity. Use alt="" if purely decorative.

🛠️ Suggested fix
   <img
     v-if="getMediaType(output) === 'images'"
     class="block size-10 rounded-sm object-cover bg-secondary-background"
     loading="lazy"
     width="40"
     height="40"
+    alt=""
     :src="output.url"
   />
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/extensions/linearMode/OutputHistoryItem.vue` around lines 14 -
21, The <img> in OutputHistoryItem.vue rendered when getMediaType(output) ===
'images' is missing an alt attribute; update the <img> element used in that v-if
branch to include an appropriate alt (use alt="" if the image is decorative or
bind a meaningful value like :alt="output.alt || ''") so the markup is
accessible and valid, keeping the existing attributes (class, loading, width,
height, :src) unchanged.
🧹 Nitpick comments (9)
src/renderer/extensions/linearMode/flattenNodeOutput.test.ts (1)

36-60: Missing isolated test for the video media type.

Every other handled media type (images, audio, gifs, 3d) has its own dedicated test exercising mediaType and nodeId. The video path is only incidentally covered in the "multiple media types" test, which doesn't assert individual item attributes.

✅ Suggested additional test
it('flattens video outputs', () => {
  const output = makeOutput({
    video: [{ filename: 'clip.mp4', subfolder: '', type: 'output' }]
  })

  const result = flattenNodeOutput(['99', output])

  expect(result).toHaveLength(1)
  expect(result[0].mediaType).toBe('video')
  expect(result[0].nodeId).toBe('99')
  expect(result[0].filename).toBe('clip.mp4')
})
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/extensions/linearMode/flattenNodeOutput.test.ts` around lines 36
- 60, Add an isolated test that exercises the video path of flattenNodeOutput:
create an output via makeOutput with a single video entry (e.g., filename
'clip.mp4'), call flattenNodeOutput with a nodeId like '99', and assert the
returned array has length 1 and that result[0].mediaType === 'video',
result[0].nodeId === '99', and result[0].filename === 'clip.mp4'; place this
alongside the other media-type tests to mirror the existing audio/images/gifs/3d
tests.
src/renderer/extensions/linearMode/linearModeTypes.ts (1)

12-17: Consider making OutputSelection a discriminated union for stronger type safety.

With both asset and output optional and no discriminant, the type allows both fields to be simultaneously set or simultaneously absent — neither of which appears intentional. TypeScript won't help callers narrow to the correct branch.

♻️ Optional refactor to discriminated union
-export interface OutputSelection {
-  asset?: AssetItem
-  output?: ResultItemImpl
-  canShowPreview: boolean
-  latentPreviewUrl?: string
-}
+export type OutputSelection =
+  | { kind: 'asset'; asset: AssetItem; output?: never; canShowPreview: boolean; latentPreviewUrl?: string }
+  | { kind: 'output'; output: ResultItemImpl; asset?: never; canShowPreview: boolean; latentPreviewUrl?: string }
+  | { kind: 'latent'; asset?: never; output?: never; canShowPreview: boolean; latentPreviewUrl: string }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/extensions/linearMode/linearModeTypes.ts` around lines 12 - 17,
OutputSelection is currently a loose interface allowing asset and output to be
both present or both absent; change it to a discriminated union to enforce
exactly-one semantics. Replace OutputSelection with two interfaces discriminated
by a literal field (e.g. kind: 'asset' | 'output') — one variant containing
asset: AssetItem (and any asset-specific fields like latentPreviewUrl) and the
other containing output: ResultItemImpl; include shared fields such as
canShowPreview on both variants if needed. Update any code that constructs or
narrows OutputSelection to switch/if on the discriminant (kind) instead of
checking optional properties.
src/renderer/extensions/linearMode/linearOutputStore.test.ts (2)

64-76: Consider using satisfies instead of as for the mock factory return.

Per repository convention, test helpers constructing mock objects should prefer satisfies for shape validation over type assertions.

However, if ExecutedWsMessage has required fields not present here, the as cast is pragmatic. Verify whether the full type is satisfied.

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

In `@src/renderer/extensions/linearMode/linearOutputStore.test.ts` around lines 64
- 76, The mock factory makeExecutedDetail currently returns the object using a
type assertion ("as ExecutedWsMessage"); replace this with TypeScript's
"satisfies ExecutedWsMessage" to validate the shape at compile time (i.e.,
change the return expression to use satisfies instead of as) and, if the
compiler reports missing required fields on ExecutedWsMessage, add those minimal
required properties to the returned object so the satisfies check passes while
preserving the test intent.

78-89: afterEach doesn't reset isAppModeRef, creating an inconsistency with beforeEach.

beforeEach sets isAppModeRef.value = true, but afterEach only resets activeJobIdRef and previewsRef. While this is currently harmless (the next beforeEach re-sets it), it's inconsistent and could mask leaks if a test modifies isAppModeRef without cleanup.

🛠️ Suggested fix
  afterEach(() => {
    activeJobIdRef.value = null
    previewsRef.value = {}
+   isAppModeRef.value = true
  })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/extensions/linearMode/linearOutputStore.test.ts` around lines 78
- 89, The afterEach cleanup is missing reset for isAppModeRef, causing
inconsistency with beforeEach; update the afterEach block (alongside
activeJobIdRef and previewsRef) to also reset isAppModeRef.value (e.g., set
isAppModeRef.value = true) so tests that modify isAppModeRef are properly
cleaned up; adjust the afterEach in the linearOutputStore.test to include this
change.
src/renderer/extensions/linearMode/linearOutputStore.ts (1)

233-243: { deep: true } on previewsByPromptId may be expensive if the record grows large.

The deep watcher traverses the entire previewsByPromptId record on every change. If this record accumulates many job entries, consider a more targeted approach (e.g., watching a computed that extracts only the active job's preview).

🤖 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 233 -
243, The current deep watcher on jobPreviewStore.previewsByPromptId is
expensive; instead, watch a computed that selects only the active job's preview
so you avoid full-record traversal: replace the watch call that references
jobPreviewStore.previewsByPromptId with a watcher on a computed which reads
executionStore.activeJobId and returns
jobPreviewStore.previewsByPromptId[activeJobId], then in the watcher body keep
the same guard using appModeStore.isAppMode and call onLatentPreview(jobId, url)
when url exists; update references to watch, jobPreviewStore.previewsByPromptId,
executionStore.activeJobId, appModeStore.isAppMode, and onLatentPreview
accordingly.
src/renderer/extensions/linearMode/useOutputHistory.ts (1)

21-61: outputsCache is never invalidated — stale entries persist after job resolution.

Once an asset's outputs are cached on line 24's early return, the entry is never evicted or refreshed. After a job resolves via the pendingResolve path (line 36), the cached in-progress-derived outputs remain even though the job has completed and history data may differ. In practice the ResultItemImpl objects should be identical, but the cache also grows without bound across sessions.

Consider clearing entries for a jobId when it resolves, or using a Map with a bounded size / LRU eviction.

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

In `@src/renderer/extensions/linearMode/useOutputHistory.ts` around lines 21 - 61,
The outputsCache in allOutputs keeps stale entries after jobs move from
linearStore.pendingResolve to completed, causing incorrect/ever-growing cache;
update the implementation to invalidate or refresh cache entries for the
corresponding jobId when a job resolves (e.g., listen for the resolution event
that removes jobId from linearStore.pendingResolve or hooks that update
linearStore.inProgressItems and then delete outputsCache[itemId] for items whose
user_metadata.jobId matches), and/or replace outputsCache with a Map
implementing bounded size or LRU eviction to avoid unbounded growth; refer to
outputsCache, allOutputs, user_metadata.jobId, linearStore.pendingResolve and
linearStore.inProgressItems to locate where to add eviction logic.
src/views/LinearView.vue (1)

102-104: Verify the +16px offset covers all gutter widths.

The w-[calc(100%+16px)] presumably compensates for the Splitter gutter (w-4 -mx-1). If gutter sizing changes, this value will silently drift. Consider leaving a brief note (or deriving from a shared constant) if this coupling is intentional.

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

In `@src/views/LinearView.vue` around lines 102 - 104, The hardcoded
w-[calc(100%+16px)] on the LinearProgressBar is compensating for the Splitter
gutter (w-4 -mx-1) and will drift if gutter sizing changes; update the
LinearProgressBar usage to either derive the offset from a shared CSS
variable/constant (e.g., --splitter-gutter or a shared spacing token) and use
w-[calc(100%+var(--splitter-gutter))] or add an inline comment next to the
LinearProgressBar class explaining the coupling to the Splitter gutter (and
reference the Splitter's class names) so future changes to Splitter gutter size
are noticed and kept in sync.
src/renderer/extensions/linearMode/LinearPreview.vue (1)

73-82: The 500ms timeout is fragile for seed updates.

The FIXME is already acknowledged, but await new Promise((r) => setTimeout(r, 500)) with the note that seeds still fail to update suggests this workaround is unreliable. Consider tracking this as a follow-up issue.

Do you want me to open an issue to track replacing this timeout with a proper synchronization mechanism?

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

In `@src/renderer/extensions/linearMode/LinearPreview.vue` around lines 73 - 82,
The 500ms sleep in rerun is a fragile workaround; replace it with a
deterministic synchronization: have loadWorkflow (or the module that applies
seeds) return or emit a promise/event that resolves when seeds/state are fully
applied, then await that promise in rerun before calling executeWidgetsCallback
and runButtonClick; specifically update loadWorkflow(selectedItem.value) to
expose a completion signal (or add a new function like waitForSeedsApplied) and
use that in rerun instead of setTimeout, and if you cannot implement this
immediately, open a follow-up issue and replace the timeout line with a clear
TODO referencing that issue while keeping behavior unchanged.
src/renderer/extensions/linearMode/OutputHistory.vue (1)

49-51: visibleHistory calls allOutputs() for every asset on each recomputation.

Each call to allOutputs(a) may trigger useAsyncState (for uncached items), potentially creating a waterfall of network requests when many assets are loaded. The cache in useOutputHistory mitigates repeated calls, but the initial load of a large history could be heavy.

This is likely acceptable for typical workloads but worth monitoring.

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

In `@src/renderer/extensions/linearMode/OutputHistory.vue` around lines 49 - 51,
visibleHistory currently calls allOutputs(a) on every recompute which can
trigger useAsyncState per asset and cause many initial network requests; to fix,
stop invoking allOutputs inside the filter and instead precompute/cache
allOutputs results once for the current outputs.media (e.g. build a computed/map
of asset -> allOutputs result using the useOutputHistory cache or Promise.all
for initial load) and then let visibleHistory filter based on those
cached/precomputed values; reference visibleHistory, allOutputs, useAsyncState
and useOutputHistory to locate and change the logic so repeated recomputations
reuse the cached allOutputs results rather than re-calling allOutputs for each
asset.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/renderer/extensions/linearMode/flattenNodeOutput.ts`:
- Around line 8-13: The flattenNodeOutput logic currently assigns mediaType
'gifs' for GIF outputs which has no matching key in mediaTypes, causing missing
icons; update the branch in flattenNodeOutput that handles nodeOutput.gifs so
that GIF items are mapped to mediaType 'images' (i.e., when building
knownOutputs and the ResultItem for each gif, set mediaType='images' instead of
'gifs'), keeping the existing casts (as ResultItem[]) and preserving other
fields on the output; alternatively (less preferred) add a 'gifs' entry to
mediaTypes, but implement the preferred remap in flattenNodeOutput (refer to the
knownOutputs variable and the nodeOutput.gifs handling).

In `@src/renderer/extensions/linearMode/linearOutputStore.ts`:
- Around line 90-93: The expression that computes nodeId in onNodeExecuted uses
the || operator which wrongly treats falsy-but-valid values (like 0 or '') as
absent; change the fallback from using || to the nullish coalescing operator ??
so nodeId is computed as String(detail.display_node ?? detail.node) to preserve
valid falsy values while still falling back when display_node is null or
undefined.

---

Outside diff comments:
In `@src/renderer/extensions/linearMode/OutputHistory.vue`:
- Around line 220-244: The wheel handler currently registered with
useEventListener(document.body, ...) calls preventDefault()/stopPropagation()
for every Ctrl+wheel and thus blocks browser zoom globally; update the listener
to only act when the event originates inside the output area (e.g., scope the
listener to outputsRef instead of document.body or add an early guard like if
(!outputsRef?.value?.contains(e.target as Node)) return), and only call
e.preventDefault() / e.stopPropagation() and run pointer.isTrackpadGesture /
navigateToAdjacent / scrollOffset logic when the event is within that outputsRef
element; keep the same options ({ capture: true, passive: false }) when
attaching to the scoped element.

---

Duplicate comments:
In `@src/renderer/extensions/linearMode/LinearControls.vue`:
- Around line 158-163: The styling change removed the `!`-important modifier and
split layout rules correctly between props: ensure the WidgetInputNumberInput
usage sets the grid override on the root with `root-class="text-base-foreground
grid-cols-[auto_96px]"` and places the input wrapper width on
`class="*:[.min-w-0]:w-24"` so tailwind-merge via cn() lets
`grid-cols-[auto_96px]` supersede `grid-cols-subgrid` and `w-24` equals 96px;
apply the identical adjustment to the other occurrence of WidgetInputNumberInput
(the one around lines 266-271) so both instances use `root-class` for grid
overrides and `class` for the inner input wrapper.

In `@src/renderer/extensions/linearMode/OutputHistoryItem.vue`:
- Around line 14-21: The <img> in OutputHistoryItem.vue rendered when
getMediaType(output) === 'images' is missing an alt attribute; update the <img>
element used in that v-if branch to include an appropriate alt (use alt="" if
the image is decorative or bind a meaningful value like :alt="output.alt || ''")
so the markup is accessible and valid, keeping the existing attributes (class,
loading, width, height, :src) unchanged.

In `@src/renderer/extensions/linearMode/OutputPreviewItem.vue`:
- Around line 10-14: The <img> in OutputPreviewItem.vue that uses
v-if="latentPreview" and :src="latentPreview" is missing an alt attribute; add
an alt attribute to that <img> — if the preview is decorative set alt="" to hide
it from screen readers, otherwise bind a localized descriptive string (e.g.
:alt="$t('…')" or a computed/prop like :alt="latentPreviewAlt") so the image
always has an accessible alt value.

---

Nitpick comments:
In `@src/renderer/extensions/linearMode/flattenNodeOutput.test.ts`:
- Around line 36-60: Add an isolated test that exercises the video path of
flattenNodeOutput: create an output via makeOutput with a single video entry
(e.g., filename 'clip.mp4'), call flattenNodeOutput with a nodeId like '99', and
assert the returned array has length 1 and that result[0].mediaType === 'video',
result[0].nodeId === '99', and result[0].filename === 'clip.mp4'; place this
alongside the other media-type tests to mirror the existing audio/images/gifs/3d
tests.

In `@src/renderer/extensions/linearMode/linearModeTypes.ts`:
- Around line 12-17: OutputSelection is currently a loose interface allowing
asset and output to be both present or both absent; change it to a discriminated
union to enforce exactly-one semantics. Replace OutputSelection with two
interfaces discriminated by a literal field (e.g. kind: 'asset' | 'output') —
one variant containing asset: AssetItem (and any asset-specific fields like
latentPreviewUrl) and the other containing output: ResultItemImpl; include
shared fields such as canShowPreview on both variants if needed. Update any code
that constructs or narrows OutputSelection to switch/if on the discriminant
(kind) instead of checking optional properties.

In `@src/renderer/extensions/linearMode/linearOutputStore.test.ts`:
- Around line 64-76: The mock factory makeExecutedDetail currently returns the
object using a type assertion ("as ExecutedWsMessage"); replace this with
TypeScript's "satisfies ExecutedWsMessage" to validate the shape at compile time
(i.e., change the return expression to use satisfies instead of as) and, if the
compiler reports missing required fields on ExecutedWsMessage, add those minimal
required properties to the returned object so the satisfies check passes while
preserving the test intent.
- Around line 78-89: The afterEach cleanup is missing reset for isAppModeRef,
causing inconsistency with beforeEach; update the afterEach block (alongside
activeJobIdRef and previewsRef) to also reset isAppModeRef.value (e.g., set
isAppModeRef.value = true) so tests that modify isAppModeRef are properly
cleaned up; adjust the afterEach in the linearOutputStore.test to include this
change.

In `@src/renderer/extensions/linearMode/linearOutputStore.ts`:
- Around line 233-243: The current deep watcher on
jobPreviewStore.previewsByPromptId is expensive; instead, watch a computed that
selects only the active job's preview so you avoid full-record traversal:
replace the watch call that references jobPreviewStore.previewsByPromptId with a
watcher on a computed which reads executionStore.activeJobId and returns
jobPreviewStore.previewsByPromptId[activeJobId], then in the watcher body keep
the same guard using appModeStore.isAppMode and call onLatentPreview(jobId, url)
when url exists; update references to watch, jobPreviewStore.previewsByPromptId,
executionStore.activeJobId, appModeStore.isAppMode, and onLatentPreview
accordingly.

In `@src/renderer/extensions/linearMode/LinearPreview.vue`:
- Around line 73-82: The 500ms sleep in rerun is a fragile workaround; replace
it with a deterministic synchronization: have loadWorkflow (or the module that
applies seeds) return or emit a promise/event that resolves when seeds/state are
fully applied, then await that promise in rerun before calling
executeWidgetsCallback and runButtonClick; specifically update
loadWorkflow(selectedItem.value) to expose a completion signal (or add a new
function like waitForSeedsApplied) and use that in rerun instead of setTimeout,
and if you cannot implement this immediately, open a follow-up issue and replace
the timeout line with a clear TODO referencing that issue while keeping behavior
unchanged.

In `@src/renderer/extensions/linearMode/OutputHistory.vue`:
- Around line 49-51: visibleHistory currently calls allOutputs(a) on every
recompute which can trigger useAsyncState per asset and cause many initial
network requests; to fix, stop invoking allOutputs inside the filter and instead
precompute/cache allOutputs results once for the current outputs.media (e.g.
build a computed/map of asset -> allOutputs result using the useOutputHistory
cache or Promise.all for initial load) and then let visibleHistory filter based
on those cached/precomputed values; reference visibleHistory, allOutputs,
useAsyncState and useOutputHistory to locate and change the logic so repeated
recomputations reuse the cached allOutputs results rather than re-calling
allOutputs for each asset.

In `@src/renderer/extensions/linearMode/useOutputHistory.ts`:
- Around line 21-61: The outputsCache in allOutputs keeps stale entries after
jobs move from linearStore.pendingResolve to completed, causing
incorrect/ever-growing cache; update the implementation to invalidate or refresh
cache entries for the corresponding jobId when a job resolves (e.g., listen for
the resolution event that removes jobId from linearStore.pendingResolve or hooks
that update linearStore.inProgressItems and then delete outputsCache[itemId] for
items whose user_metadata.jobId matches), and/or replace outputsCache with a Map
implementing bounded size or LRU eviction to avoid unbounded growth; refer to
outputsCache, allOutputs, user_metadata.jobId, linearStore.pendingResolve and
linearStore.inProgressItems to locate where to add eviction logic.

In `@src/views/LinearView.vue`:
- Around line 102-104: The hardcoded w-[calc(100%+16px)] on the
LinearProgressBar is compensating for the Splitter gutter (w-4 -mx-1) and will
drift if gutter sizing changes; update the LinearProgressBar usage to either
derive the offset from a shared CSS variable/constant (e.g., --splitter-gutter
or a shared spacing token) and use w-[calc(100%+var(--splitter-gutter))] or add
an inline comment next to the LinearProgressBar class explaining the coupling to
the Splitter gutter (and reference the Splitter's class names) so future changes
to Splitter gutter size are noticed and kept in sync.

ℹ️ 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 b29dbec and 2f911ff.

📒 Files selected for processing (20)
  • packages/design-system/src/css/style.css
  • src/locales/en/main.json
  • src/renderer/extensions/linearMode/LatentPreview.vue
  • src/renderer/extensions/linearMode/LinearControls.vue
  • src/renderer/extensions/linearMode/LinearPreview.vue
  • src/renderer/extensions/linearMode/LinearProgressBar.vue
  • src/renderer/extensions/linearMode/OutputHistory.vue
  • src/renderer/extensions/linearMode/OutputHistoryItem.vue
  • src/renderer/extensions/linearMode/OutputPreviewItem.vue
  • src/renderer/extensions/linearMode/flattenNodeOutput.test.ts
  • src/renderer/extensions/linearMode/flattenNodeOutput.ts
  • src/renderer/extensions/linearMode/linearModeTypes.ts
  • src/renderer/extensions/linearMode/linearOutputStore.test.ts
  • src/renderer/extensions/linearMode/linearOutputStore.ts
  • src/renderer/extensions/linearMode/mediaTypes.ts
  • src/renderer/extensions/linearMode/useOutputHistory.ts
  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberInput.vue
  • src/renderer/extensions/vueNodes/widgets/components/layout/WidgetLayoutField.vue
  • src/utils/dateTimeUtil.ts
  • src/views/LinearView.vue
💤 Files with no reviewable changes (1)
  • src/utils/dateTimeUtil.ts

Comment on lines +8 to +13
const knownOutputs: Record<string, ResultItem[]> = {}
if (nodeOutput.audio) knownOutputs.audio = nodeOutput.audio
if (nodeOutput.images) knownOutputs.images = nodeOutput.images
if (nodeOutput.video) knownOutputs.video = nodeOutput.video
if (nodeOutput.gifs) knownOutputs.gifs = nodeOutput.gifs as ResultItem[]
if (nodeOutput['3d']) knownOutputs['3d'] = nodeOutput['3d'] as ResultItem[]
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

'gifs' mediaType will silently produce missing icons in UI consumers.

flattenNodeOutput writes mediaType = 'gifs' for gif outputs (line 12), but mediaTypes in mediaTypes.ts has no 'gifs' key. Any component that does mediaTypes[getMediaType(output)]?.iconClass for a GIF item receives undefined, so no icon is displayed. Since getMediaType delegates to output.mediaType for non-video items, GIFs are unresolvable.

Fix options:

  • A (preferred): map GIF outputs to mediaType = 'images' here (GIFs are images), relying on isImageBySuffix for rendering.
  • B: add a 'gifs' entry to mediaTypes.ts with the image icon.
🔧 Option A — remap gifs to 'images' media type
-  if (nodeOutput.gifs) knownOutputs.gifs = nodeOutput.gifs as ResultItem[]
+  if (nodeOutput.gifs) knownOutputs.images = [
+    ...(knownOutputs.images ?? []),
+    ...(nodeOutput.gifs as ResultItem[])
+  ]

The as ResultItem[] casts on lines 12–13 are needed because gifs/3d schema types differ from ResultItem[]. This is not as any, so it's within type-safety guidelines — but note that any schema-specific fields on those items (e.g. format, frame_rate) are still passed through at runtime via the spread { ...output, ... }.

📝 Committable suggestion

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

Suggested change
const knownOutputs: Record<string, ResultItem[]> = {}
if (nodeOutput.audio) knownOutputs.audio = nodeOutput.audio
if (nodeOutput.images) knownOutputs.images = nodeOutput.images
if (nodeOutput.video) knownOutputs.video = nodeOutput.video
if (nodeOutput.gifs) knownOutputs.gifs = nodeOutput.gifs as ResultItem[]
if (nodeOutput['3d']) knownOutputs['3d'] = nodeOutput['3d'] as ResultItem[]
const knownOutputs: Record<string, ResultItem[]> = {}
if (nodeOutput.audio) knownOutputs.audio = nodeOutput.audio
if (nodeOutput.images) knownOutputs.images = nodeOutput.images
if (nodeOutput.video) knownOutputs.video = nodeOutput.video
if (nodeOutput.gifs) knownOutputs.images = [
...(knownOutputs.images ?? []),
...(nodeOutput.gifs as ResultItem[])
]
if (nodeOutput['3d']) knownOutputs['3d'] = nodeOutput['3d'] as ResultItem[]
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/extensions/linearMode/flattenNodeOutput.ts` around lines 8 - 13,
The flattenNodeOutput logic currently assigns mediaType 'gifs' for GIF outputs
which has no matching key in mediaTypes, causing missing icons; update the
branch in flattenNodeOutput that handles nodeOutput.gifs so that GIF items are
mapped to mediaType 'images' (i.e., when building knownOutputs and the
ResultItem for each gif, set mediaType='images' instead of 'gifs'), keeping the
existing casts (as ResultItem[]) and preserving other fields on the output;
alternatively (less preferred) add a 'gifs' entry to mediaTypes, but implement
the preferred remap in flattenNodeOutput (refer to the knownOutputs variable and
the nodeOutput.gifs handling).

Comment on lines +90 to +93
function onNodeExecuted(jobId: string, detail: ExecutedWsMessage) {
const nodeId = String(detail.display_node || detail.node)
const newOutputs = flattenNodeOutput([nodeId, detail.output])
if (newOutputs.length === 0) return
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

|| may discard falsy-but-valid display_node values — consider ??.

String(detail.display_node || detail.node) falls through to detail.node when display_node is 0, '', or null. If node IDs can be 0 (numeric), the || operator would skip a valid value. Using ?? preserves 0 and '' while only falling back on null/undefined.

🛠️ Suggested fix
-    const nodeId = String(detail.display_node || detail.node)
+    const nodeId = String(detail.display_node ?? detail.node)
🤖 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 90 -
93, The expression that computes nodeId in onNodeExecuted uses the || operator
which wrongly treats falsy-but-valid values (like 0 or '') as absent; change the
fallback from using || to the nullish coalescing operator ?? so nodeId is
computed as String(detail.display_node ?? detail.node) to preserve valid falsy
values while still falling back when display_node is null or undefined.

@pythongosssss pythongosssss merged commit f811b17 into main Feb 23, 2026
36 checks passed
@pythongosssss pythongosssss deleted the pysssss/appmode/linear-run.3.5 branch February 23, 2026 18:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants