- Add wheelInputMode property to LiteGraphGlobal snapshot
- Migrate canvasSettings e2e tests from deprecated MouseWheelScroll
to the new Comfy.Graph.WheelInputMode setting
- Clean up onDetectedDeviceChange callback when canvas is replaced
- Migrate legacy NavigationMode/MouseWheelScroll on first run
- Drop unreachable !isTrackpad branch in trackpad pan path
- Rename isStandardNavMode to isTrackpadWheelMode for clarity
## Summary
Use resized preview URLs for floating QPO row thumbnails so the expanded
overlay does not load full-size image assets while the canvas is being
navigated.
Linear: FE-538
## Changes
- **What**: Pass `ResultItemImpl.previewUrl` into `AssetsListItem` for
completed image/video job rows.
- **Dependencies**: None.
## Review Focus
Confirm this only changes the row thumbnail source; full asset viewing
still flows through the existing job/task preview output.
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-11946-fix-use-resized-QPO-thumbnails-3576d73d365081b68682d1b7b109af30)
by [Unito](https://www.unito.io)
## Summary
Use ShadCN-style Reka popover primitives for the live queue job list
after the unused legacy queue row implementation is removed in #11621.
This is the first step in migrating popovers toward the ShadCN UI
pattern: local design-system wrappers over Reka UI, rather than ad hoc
direct Reka or PrimeVue popovers at each call site.
## Changes
- **What**: Added the minimal ShadCN-style popover primitives needed by
this fix: `Popover`, `PopoverAnchor`, and `PopoverContent`.
- **What**: Migrated `JobAssetsList` job details from manual fixed
positioning to these popover primitives with viewport collision
handling.
- **What**: Removed the obsolete manual hover-position helper after
`JobAssetsList` stopped using it.
- **Dependencies**: No new dependencies; the primitives wrap the
existing `reka-ui` package.
- Added browser coverage for bottom-row job details clipping in the
queue overlay.
## Review Focus
- This PR is stacked on #11621.
- The live queue surfaces are `JobAssetsList` consumers: expanded queue
progress overlay and job history sidebar.
- The new `src/components/ui/popover` files intentionally seed the
ShadCN-style migration path, but only include the pieces used here to
keep the first PR small.
- Follow-up PRs can add `PopoverTrigger` and migrate existing
PrimeVue/direct-Reka popovers once there is an actual caller.
## Summary
A follow-up PR of
https://github.com/Comfy-Org/ComfyUI_frontend/issues/11107.
## Changes
Add unit test to `editAttention.ts`
- [x] `Extract pure functions to module level`: **Moved**
`incrementWeight`, `findNearestEnclosure`, and `addWeightToParentheses`
out of the `init()` closure and **promoted** them to module-level
functions with `export` to allow for independent testing.
- [x] `Add unit tests for incrementWeight`: **Added** 6 tests covering
edge cases such as normal increment/decrement, NaN input, negative
weights, and floating-point precision.
- [x] `Add unit tests for findNearestEnclosure`: **Added** 7 tests
covering edge cases including simple brackets, no brackets, cursor
outside, nested brackets (inner/outer), empty strings, and missing
closing brackets.
- [x] `Add unit tests for addWeightToParentheses`: **Added** 6 tests
covering scenarios like adding a default 1.0 weight, retaining existing
weights, no changes when brackets are absent, scientific notation
weights, negative weights, and multi-word tokens.
- [x] `Mock app module`: **Used** `vi.mock('@/scripts/app')` to
intercept side effects from `app.registerExtension`, **preventing** the
triggering of ComfyUI extension registration logic during module import.
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> **Medium Risk**
> Adjusts token selection/weight-detection logic used during
Ctrl/Cmd+Arrow editing, which could subtly change how prompts are
rewritten in edge cases (nested parens, scientific notation, time-like
text). Adds tests that should reduce regression risk but behavior
changes still warrant verification in the UI.
>
> **Overview**
> Adds a new `vitest` unit test suite for `editAttention` by mocking
`app.registerExtension` side effects and validating `incrementWeight`,
`findNearestEnclosure`, and `addWeightToParentheses` across common and
edge cases.
>
> Refactors those helpers to exported module-level functions and
tightens parsing/selection behavior: `findNearestEnclosure` now handles
the cursor being on an opening `(`, `addWeightToParentheses` improves
trailing weight detection (supports scientific notation/negatives and
avoids misclassifying time-like `12:30`), and the weight-rewrite regex
now matches exponent forms.
>
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
df20340b49. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-11301-Test-edit-attention-unit-tests-3446d73d365081f29e8dfceefc06f5d3)
by [Unito](https://www.unito.io)
## Summary
Adds 11 tests for \`src/renderer/core/layout/store/layoutStore.ts\`
covering paths previously uncovered by the existing 17-test suite.
Targets the customRef setter machinery, reactive queries, and
link-layout updates that are reachable through the public API.
## Test Coverage
\`getNodeLayoutRef\` setter:
- Setter on a fresh ref triggers \`createNode\`.
- Position-only change triggers \`moveNode\`.
- Size-only change triggers \`resizeNode\`.
- zIndex-only change triggers \`setNodeZIndex\`.
- Setting to \`null\` triggers \`deleteNode\`.
Queries:
- \`getNodesInBounds\` returns reactive node IDs intersecting the
bounds.
- \`queryNodeAtPoint\` returns the top-zIndex node containing the point.
- \`queryNodeAtPoint\` returns \`null\` when no node contains the point.
Link layouts:
- \`updateLinkLayout\` short-circuits when bounds and centerPos
unchanged but still updates the path.
- \`updateLinkLayout\` replaces stored layout when bounds change.
- \`deleteLinkLayout\` removes the link and its segment layouts.
## Testing
\`\`\`bash
pnpm vitest run src/renderer/core/layout/store/layoutStore.test.ts
\`\`\`
┆Issue is synchronized with this [Notion
page](https://app.notion.com/p/PR-11747-test-add-unit-tests-for-layoutStore-setter-and-query-paths-3516d73d365081d9bc1de336ff7258ea)
by [Unito](https://www.unito.io)
… (issue #11092 phase 4b)
## Summary
Part of #11092 — Phase 4b: remove 2 `@ts-expect-error` suppressions from
`CustomizationDialog.vue`.
## Changes
`selectedIcon` ref initialisation and `resetCustomization` assignment
both suppressed a type error on `Array.find()` returning `T |
undefined`.
**Why**
`Array.find()` has no way to statically guarantee a match, so its return
type is always `T | undefined`. Both usages were searching `iconOptions`
— a literal array of 8 entries declared in the same scope — and
TypeScript could not prove that the searched value would always be
found, even though at runtime it always is (the default icon value is
defined from `iconOptions[0]`).
**How**
Added `iconOptions[0]` as a final fallback via `??` in both places.
Because `iconOptions` is a non-empty literal array, `iconOptions[0]` is
provably non-null to TypeScript, which makes the overall expression type
`T` and satisfies the assignment. The explicit generic on `ref<{ name:
string; value: string }>` was also dropped — TypeScript infers the type
correctly from the non-nullable initialiser. In `resetCustomization`,
`||` was replaced with `??` since the values being null-coalesced are
objects (never falsy), making `??` the semantically precise operator for
this case.
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> **Low Risk**
> Low risk: UI-only refactor that adds explicit fallbacks for
`Array.find()` results and introduces a small unit test suite; behavior
should remain the same except for safer handling of unexpected icon
values.
>
> **Overview**
> Removes two `@ts-expect-error` suppressions in
`CustomizationDialog.vue` by making `selectedIcon` initialization and
`resetCustomization` use a guaranteed fallback (`iconOptions[0]`) via
`??`, ensuring the selected icon is never `undefined`.
>
> Adds `CustomizationDialog.test.ts` to verify `confirm` emits the
expected icon/color for default, provided initial values, and an invalid
`initialIcon` fallback.
>
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
f77addf713. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-11339-refactor-remove-ts-expect-error-suppressions-in-CustomizationDialog-3456d73d36508165865ac569e047db2e)
by [Unito](https://www.unito.io)
## Summary
Phase D of the **useBrushDrawing-refactor plan.md**. Extract `WebGPU`
state management from `useBrushDrawing` into a dedicated
`useGPUResources` composable, reducing `useBrushDrawing` from ~1,160
lines to ~230. This is Phase D of the ongoing `useBrushDrawing`
decomposition (Phases A–C landed in previous PRs).
## Changes
- **What**: Split `useBrushDrawing` along a clean boundary — GPU
device/texture lifecycle moves to `useGPUResources`, stroke
orchestration stays in `useBrushDrawing`. Shared reactive state
(`dirtyRect`, `isSavingHistory`, `previewCanvas`) is now owned by
`useGPUResources` and exposed as refs. A pure
`clampDirtyRect` helper is extracted to `gpuUtils.ts`.
- **Dependencies**: No new dependencies
## Tests
Local test - pass
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> **Medium Risk**
> Refactors WebGPU initialization, texture management, and readback
paths used during drawing; regressions could affect stroke rendering,
canvas visibility, and undo/redo GPU sync.
>
> **Overview**
> Extracts WebGPU device/texture/renderer lifecycle, watchers (clear,
undo/redo sync, texture recreation), and readback logic out of
`useBrushDrawing` into a new `useGPUResources` composable, with shared
refs (`dirtyRect`, `isSavingHistory`, `previewCanvas`, `hasRenderer`)
now owned by that module.
>
> Updates `useBrushDrawing` to delegate GPU-specific operations
(prepare/render/draw point/composite/readback/cleanup) to
`useGPUResources` while keeping CPU drawing + stroke orchestration, and
adds new pure helpers in `gpuUtils` (`clampDirtyRect`,
`buildStrokePoints`) to centralize dirty-rect clamping and stroke point
resampling.
>
> Adds Vitest coverage for the new helpers, `useGPUResources`
no-op/error behavior when GPU isn’t available, and `useBrushDrawing`
interactions with the extracted GPU API (composition mode selection,
shift-line, history save, and canvas/preview opacity restoration).
>
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
a9fcd80ab5. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
┆Issue is synchronized with this [Notion
page](https://app.notion.com/p/PR-11784-refactor-extract-GPU-lifecycle-into-useGPUResources-phase-D-3526d73d365081108a97c164a0bfa13e)
by [Unito](https://www.unito.io)
## Summary
Render keybinding badges in sentence case (`Ctrl + Shift + A`) instead
of UPPERCASE (`CTRL + SHIFT + A`) by swapping the `uppercase` Tailwind
class for `capitalize` in `KeyComboDisplay.vue`.
`KeyComboImpl.getKeySequences()` already returns labels in their
canonical form (`Ctrl`, `Alt`, `Shift`, plus the raw key). The badge
styling was forcing them all to UPPER, which is what FE-524 calls out.
`text-transform: capitalize` cleanly handles every case: lower modifier,
upper modifier, and single character keys.
- Fixes FE-524
## Before / After
| Before (`uppercase`) | After (`capitalize`) |
| --- | --- |
| <img
src="https://raw.githubusercontent.com/Comfy-Org/ComfyUI_frontend/c6bb96fce/docs/screenshots/fe-524/before.png"
width="480"> | <img
src="https://raw.githubusercontent.com/Comfy-Org/ComfyUI_frontend/c6bb96fce/docs/screenshots/fe-524/after.png"
width="480"> |
## Test plan
- [ ] Open Settings → Keybinding panel and confirm modifier badges
render as `Ctrl`, `Alt`, `Shift` instead of `CTRL`, `ALT`, `SHIFT`
- [ ] Confirm single-letter keys (e.g. `A`, `S`) still render uppercase
- [ ] Edit a keybinding and verify the live preview badges in the dialog
also render in sentence case
## Summary
Use spread pattern when writing `nodeValue.properties['Model Config']`
so future ModelConfig fields are preserved across viewer dialog
cancel/apply.
## Changes
- **What**: Spread existing `Model Config` before applying known keys in
both `restoreInitialState()` and `applyChanges()` in
[useLoad3dViewer.ts](src/composables/useLoad3dViewer.ts). Removes the
hard-coded `showSkeleton: false` override from `applyChanges()` so it
falls through from the existing config.
## Review Focus
The change is intentionally minimal and matches the suggestion in the
upstream issue. Two regression tests added (one each for restore/apply)
verify that an unknown future field on Model Config survives both code
paths.
Fixes#11346
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-11838-fix-load3d-preserve-unknown-Model-Config-fields-with-spread-3546d73d3650819686efc4e1a9799ad9)
by [Unito](https://www.unito.io)
## Summary
`fitToViewer()` in `SceneModelManager` resets the model rotation to
`(0,0,0)` as part of its normalize-and-scale flow, but does not reapply
`currentUpDirection` afterward. This causes a state/view mismatch when
the user has previously selected a non-default up-axis (e.g. `+x`,
`-z`): the model snaps back to its raw orientation while the Up
Direction control still shows the previously selected value.
## Changes
- In `fitToViewer()`, clear `originalRotation` (to avoid compounding
with the stale pre-fit base) then reapply `currentUpDirection` if it is
not `'original'`
- Add 5 unit tests covering: no-op when no model, reapplication of
direction, no rotation compounding on repeated calls, zero rotation for
`'original'` direction, and stale `originalRotation` guard
## Testing
### Automated
- `src/extensions/core/load3d/SceneModelManager.test.ts` — 5 new tests
in `describe('fitToViewer')`
- All 48 unit tests pass
### E2E Verification Steps
1. Open the Load3D viewer with any 3D model
2. Change **Up Direction** to any non-default value (e.g. `+x` or `-z`)
— observe model rotates correctly
3. Click **Fit to Viewer** — model should remain in the chosen
up-direction orientation, not snap back to raw orientation
4. Click **Fit to Viewer** again — rotation should remain stable (no
compounding)
5. Change Up Direction back to `original` then click **Fit to Viewer** —
model should return to neutral orientation `(0,0,0)`
## Review Focus
The key invariant: `fitToViewer()` resets `rotation.set(0,0,0)`
explicitly, so we must clear `originalRotation = null` before calling
`setUpDirection`. Otherwise `setUpDirection` restores the stale pre-fit
rotation as a base and then adds the direction offset on top,
compounding incorrectly.
Fixes#11347
<!-- Pipeline-Ticket: pick-issue-3414 -->
┆Issue is synchronized with this [Notion
page](https://app.notion.com/p/PR-11826-fix-load3d-reapply-up-direction-after-fitToViewer-transform-reset-3546d73d36508166b9bcecc9949c4952)
by [Unito](https://www.unito.io)
## Summary
Fixes a bug where models reloaded in wireframe/normal/depth modes would
not restore to their original materials correctly, because the material
snapshot was being taken *after* the mode was applied.
## Changes
- Move `setupModelMaterials(model)` to immediately after
`scene.add(model)` and before `setMaterialMode()` / `setUpDirection()`
in `SceneModelManager.setupModel()`
- Save `materialMode` into `pendingMaterialMode` before calling
`setupModelMaterials()`, since the latter internally calls
`setMaterialMode('original')` which resets `this.materialMode` —
preserving the value ensures the subsequent reapplication guard works
correctly
- Update stale test assertion that reflected the old (incorrect) call
order
- Add regression test: verifies that `originalMaterials` captures the
pre-mutation material and that restoring to `'original'` after a
non-original load gives back the true original mesh material
## Testing
### Automated
- `src/extensions/core/load3d/SceneModelManager.test.ts` — 44 tests, all
pass
- Full load3d test suite — 401 tests, all pass
### E2E Verification Steps
1. Open ComfyUI with a Load3D node
2. Load any GLB/OBJ model
3. Switch Material Mode to **Wireframe** (or Normal/Depth)
4. Load a different model (or reload the same one)
5. Switch Material Mode back to **Original**
6. Verify the model renders with its original diffuse/PBR materials (not
wireframe)
## Review Focus
The key invariant: `setupModelMaterials()` must snapshot mesh materials
in their *unmodified* state. It must run before any `setMaterialMode()`
call that mutates them. The `pendingMaterialMode` variable is needed
because `setupModelMaterials()` internally calls
`setMaterialMode('original')`, which updates `this.materialMode`, making
the subsequent guard `if (this.materialMode !== 'original')` silently
skip reapplication without it.
Fixes#11344
<!-- Pipeline-Ticket: pick-issue-3417 -->
┆Issue is synchronized with this [Notion
page](https://app.notion.com/p/PR-11825-fix-load3d-snapshot-original-materials-before-reapplying-materialMode-3546d73d3650818b9c35fa60c15f17a3)
by [Unito](https://www.unito.io)
## Summary
Preview 3D and Animation nodes were stuck at the LOD from initial page
load because CSS `scale3d` transforms don't affect
`clientWidth`/`clientHeight` — `handleResize()` always received
layout-space dimensions regardless of zoom level. This fix passes
`ds.scale` as the renderer pixel ratio so the 3D scene renders at the
correct visual resolution when the graph is zoomed in or out.
## Changes
- **What**: In `Load3d.handleResize()`, call
`renderer.setPixelRatio(ds.scale)` before `setSize` so pixel density
scales with canvas zoom. A `getZoomScale` callback is threaded through
`Load3DOptions` → `Load3d` constructor → `handleResize`. In `useLoad3d`,
a watcher on `canvasStore.appScalePercentage` triggers `handleResize`
whenever the zoom level changes.
- **What**: Fix `SceneManager.captureScene()` to save and restore the
renderer's logical size and pixel ratio around capture, so exact-pixel
output is unaffected by the current zoom state.
## Review Focus
- `handleResize` now calls `setPixelRatio` before `setSize`. Three.js
renders at `logicalWidth × pixelRatio` physical pixels while CSS
displays it at `logicalWidth` CSS pixels — this is the standard pattern
for HiDPI but here used to match the visual zoom level.
- `captureScene` must reset `pixelRatio` to 1 so `setSize(w, h)`
produces exactly `w×h` pixel output. It saves and restores both logical
size and pixel ratio via `renderer.getSize()` /
`renderer.getPixelRatio()`.
- The zoom watcher is guarded with `getActivePinia()` to avoid errors in
unit tests and non-Pinia contexts.
## Test
before
https://github.com/user-attachments/assets/9778ad54-7cb2-4fdc-b200-65a683ee8e4d
after
https://github.com/user-attachments/assets/acfaaf7a-43c7-495f-b352-5dd2cdaa94db
## Analysis Report
https://linear.app/comfyorg/issue/FE-401/bug-preview-3d-and-animation-nodes-lod-stuck-at-initial-page-load
## More
- Add `debounce` and pixel ratio limit
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> **Medium Risk**
> Medium risk because it changes core `Load3d.handleResize()` rendering
behavior (pixel ratio/LOD) and adds a debounced zoom-driven resize
watcher, which could affect performance or visual output across all
Load3D nodes. Capture logic is also refactored to manipulate renderer
size/pixel ratio and camera params, so regressions would show up in
thumbnails/exports.
>
> **Overview**
> Fixes Load3D LOD/render sharpness when the graph canvas is zoomed by
threading a new `getZoomScale` option from `useLoad3d` into `Load3d` and
using it to call `renderer.setPixelRatio()` (clamped) during
`handleResize()`.
>
> Adds a debounced watcher on `canvasStore.appScalePercentage` to
trigger `handleResize()` on zoom changes, and updates
`SceneManager.captureScene()` to temporarily force pixel ratio 1 and
restore renderer size/pixel ratio and camera settings after capture.
Coverage is expanded with new Playwright smoke coverage plus unit tests
for zoom propagation, debouncing, pixel ratio behavior, and capture
state restoration.
>
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
261940d111. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-11734-fix-load3d-update-renderer-pixel-ratio-on-canvas-zoom-to-fix-LOD-resolution-3516d73d365081e6b3d4cdd05f516489)
by [Unito](https://www.unito.io)
## Summary
Fixes several issues in both Simplified Chinese (zh) and Traditional
Chinese (zh-TW) locale files that were identified through systematic
comparison against the English source.
### Changes
**nodeDefs.json (zh + zh-TW):**
- **CLIPLoader.description**: Added missing model recipes (lumina2, wan,
hidream, omnigen2) to match English source
- **ByteDanceSeedreamNode.display_name**: Updated version from "Seedream
4" to "Seedream 4.5 and 5.0" to match English
- **BriaImageEditNode.display_name**: Added missing "FIBO" model name
**nodeDefs.json (zh only):**
- **APG.inputs.eta.name**: Fixed incorrect translation "预计到达时间" (ETA) ->
kept as "eta" (technical parameter name)
**commands.json (zh + zh-TW):**
- **Comfy_ToggleLinear**: Updated label to match English "Toggle App
Mode"
- **Experimental_ToggleVueNodes**: Rebranded "Vue 节点"/"Vue 節點" to "Nodes
2.0" to match English
**settings.json (zh + zh-TW):**
- **Comfy_VueNodes_Enabled / Comfy_VueNodes_AutoScaleLayout**: Rebranded
"Vue 节点"/"Vue 節點" to "Nodes 2.0"
**main.json (zh + zh-TW):**
- **errorDialog.accessRestrictedMessage / accessRestrictedTitle**: Added
missing Chinese translations
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-11930-fix-i18n-correct-zh-and-zh-TW-translations-3566d73d365081ff9b0beb1c1fc7ef1a)
by [Unito](https://www.unito.io)
---------
Co-authored-by: LifetimeVip <lifetimevip@users.noreply.github.com>
## Summary
Add 27 unit tests for MembersPanelContent component covering workspace
views, member management, and billing states.
## Changes
- **What**: New test file for MembersPanelContent with 27 tests across 8
describe blocks (personal workspace, owner view, member view, sorting,
search filtering, pending invites, single seat plan, member count
display)
## Review Focus
- Uses `@testing-library/vue` + `@testing-library/user-event` per
project conventions
- Component stubs (Button, SearchInput, Menu, UserAvatar) for isolation
- Reactive mock refs in `vi.hoisted()` shared across `vi.mock()` calls
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-11402-test-add-MembersPanelContent-unit-tests-3476d73d36508107abcbce95b72b3fb7)
by [Unito](https://www.unito.io)
## Summary
Add Playwright e2e tests for the node replacement feature (swap nodes UI
in the errors tab).
## Changes
- **What**: 6 e2e test cases across two describe blocks covering single
and multi-type node replacement flows. Tests verify swap nodes group
visibility, in-place replacement, widget value preservation, Replace All
across multiple types, output connection preservation, and success toast
display. Includes typed mock data for `/api/node_replacements` and two
workflow fixture files with fake missing node types mapped to real core
nodes.
## Review Focus
- Mock setup pattern in `setupNodeReplacement` — enables feature flag
via `page.evaluate` and routes the API endpoint
- Workflow fixture design — uses fake node types (E2E_OldSampler,
E2E_OldUpscaler) that map to real registered types (KSampler,
ImageScaleBy)
- Assertion coverage for link preservation after replacement
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-11242-test-add-e2e-tests-for-node-replacement-flows-3426d73d3650811e87d7f0d96fd66433)
by [Unito](https://www.unito.io)
---------
Co-authored-by: Connor Byrne <c.byrne@comfy.org>
## Summary
The Vue node model picker mixes output assets into the dropdown with a
trailing ' [output]' suffix on the value. Forwarding that string to
loadModel as a literal filename under the configured input folder caused
a 404 and the model never rendered. Strip the trailing folder annotation
per call and use the matching folder so picking an output asset loads
correctly while plain values keep the configured folder.
Output assets stored under a subfolder (e.g. 3d/) were exposed in the
Vue node dropdown as just '<filename> [output]', so selection produced
an /api/view URL with empty subfolder and a 404. Read the subfolder from
the asset's OutputAssetMetadata and prefix it onto the annotated path so
the downstream load handler can split it back out and target the correct
folder.
## Screenshots (if applicable)
https://github.com/user-attachments/assets/463d1071-efdc-46a4-b101-8e1c012d19c7
┆Issue is synchronized with this [Notion
page](https://app.notion.com/p/PR-11805-fix-load3d-parse-output-input-temp-annotation-when-loading-3536d73d365081a8ac9cf75d14ae29e6)
by [Unito](https://www.unito.io)
## Summary
Fixed two minor documentation inaccuracies found during comprehensive
documentation audit:
- Corrected outdated "Lodash" reference to "Utility Functions" in unit
testing guide
- Updated package manager command from `npx` to `pnpm dlx` in Playwright
skill documentation
## Changes Made
### Documentation Fixes
#### docs/testing/unit-testing.md:150
- **Before**: `## Mocking Lodash Functions`
- **After**: `## Mocking Utility Functions`
- **Reason**: The section describes mocking `es-toolkit/compat`
functions, not Lodash. The project uses es-toolkit as stated in
AGENTS.md line 158 and docs/guidance/typescript.md line 60.
#### .claude/skills/writing-playwright-tests/SKILL.md:117
- **Before**: `npx playwright show-trace trace.zip`
- **After**: `pnpm dlx playwright show-trace trace.zip`
- **Reason**: Project standardizes on pnpm, explicitly avoiding npx per
AGENTS.md line 42: "use `pnpx` or `pnpm dlx` — never `npx`"
## Audit Summary
Comprehensive audit verified accuracy of:
- ✅ Core documentation (CLAUDE.md, AGENTS.md, README.md)
- ✅ All docs/**/*.md files (40+ files including ADRs, testing guides,
architecture docs)
- ✅ All README files throughout repository (21 files)
- ✅ All .claude/commands/*.md files (8 files)
- ✅ Code examples and API references
- ✅ File structure references (verified src/router.ts, src/i18n.ts,
src/main.ts, config files exist)
- ✅ Package dependencies (es-toolkit ^1.39.9 confirmed)
- ✅ Script commands (pnpm test:unit, pnpm test:browser:local, etc.)
- ✅ External resource links
- ✅ ADR index and dates
All other documentation remains accurate and up-to-date as of
2026-05-04.
## Review Notes
This PR contains only two trivial corrections to terminology/commands.
No functional changes, no code changes, no breaking changes. The
documentation audit found the codebase documentation to be in excellent
condition overall.
Co-authored-by: christian-byrne <72887196+christian-byrne@users.noreply.github.com>
## Summary
The existing \`MixpanelTelemetryProvider.test.ts\` was misnamed: it only
tested \`getExecutionContext\` from \`../../utils/getExecutionContext\`,
never the provider class itself — provider coverage sat at **0%**
despite a 239-line test file living next to it.
This PR:
1. **Renames** the existing test file to
\`src/platform/telemetry/utils/getExecutionContext.test.ts\` (co-located
with the source it actually tests). Updates its relative import to
\`./getExecutionContext\`.
2. **Adds** a fresh
\`src/platform/telemetry/providers/cloud/MixpanelTelemetryProvider.test.ts\`
covering the provider class.
Lifts provider coverage from **0% → 81.1%** lines (functions 73.5%,
branches 88.5%).
## Test Coverage (new tests)
Constructor / initialization:
- Without \`mixpanel_token\`, warns and disables itself; subsequent
\`trackXxx\` calls are no-ops.
- With \`mixpanel_token\`, dynamically imports mixpanel-browser, calls
\`init\`, and after \`loaded()\` fires identifies users via
\`onUserResolved\`.
Queueing semantics:
- Events fired before \`loaded()\` are queued and flushed in order once
Mixpanel reports ready.
Filtering:
- Events listed in the default \`disabledEvents\` set (e.g.
\`workflow_opened\`) are suppressed.
Direct dispatchers (parameterized \`it.each\`):
- 16 \`trackXxx\` methods covered: signup/auth/login, subscription
lifecycle, credit topup events, template lifecycle, workflow
imported/saved, default-view, enter-linear, share-flow, execution
success/error.
- \`trackApiCreditTopupButtonPurchaseClicked\` payload includes
\`credit_amount\`.
- \`trackEmailVerification\` dispatches the matching
\`USER_EMAIL_VERIFY_*\` event for each stage.
- \`trackSubscription\` maps \`'modal_opened'\` and
\`'subscribe_clicked'\` to their distinct events.
- \`trackRunButton\` populates \`RunButtonProperties\` from the
execution context.
- \`trackWorkflowExecution\` consumes the latest \`trigger_source\` from
\`trackRunButton\`, then resets it to \`'unknown'\`.
Survey:
- On \`'submitted'\`, normalized properties are written to
\`Mixpanel.people\`.
- On \`'opened'\`, \`Mixpanel.people\` is not touched.
Topup delegation:
- \`startTopupTracking\`, \`clearTopupTracking\`,
\`checkForCompletedTopup\` all forward to the \`topupTracker\` utility.
## Testing
\`\`\`bash
pnpm vitest run
src/platform/telemetry/providers/cloud/MixpanelTelemetryProvider.test.ts
pnpm vitest run src/platform/telemetry/utils/getExecutionContext.test.ts
\`\`\`
┆Issue is synchronized with this [Notion
page](https://app.notion.com/p/PR-11749-test-rename-misnamed-Mixpanel-test-and-cover-the-actual-provider-class-3516d73d365081609c54f34bd2d8b00d)
by [Unito](https://www.unito.io)
---------
Co-authored-by: Christian Byrne <cbyrne@comfy.org>
## Automated Ingest API Type Update
This PR updates the Ingest API TypeScript types and Zod schemas from the
latest cloud OpenAPI specification.
- Cloud commit: 758732f
- Generated using @hey-api/openapi-ts with Zod plugin
These types cover cloud-only endpoints (workspaces, billing, secrets,
assets, tasks, etc.).
Overlapping endpoints shared with the local ComfyUI Python backend are
excluded.
---------
Co-authored-by: skishore23 <178779+skishore23@users.noreply.github.com>
Co-authored-by: GitHub Action <action@github.com>
Nodes in the 'essentials' category do not have type 'core'. The check
has been updated to instead use the dedicated `isCoreNode` prop.
No tests currently. The existing tests for this code section all mock
out the relevant code path and properly writing a test for this would
take far more time than I can allocate right now.
┆Issue is synchronized with this [Notion
page](https://app.notion.com/p/PR-11809-Fix-core-node-detection-for-missing-nodes-3536d73d3650815aabb2deb54c4ecec4)
by [Unito](https://www.unito.io)
## Summary
Extends the new unsafe Error assertion lint rule from #11845 to also
reject angle-bracket assertions.
## Changes
- **What**: Adds a `TSTypeAssertion` selector alongside the existing
`TSAsExpression` selector and broadens the lint message to cover Error
type assertions generally.
## Review Focus
This is stacked on #11845 and only addresses the lint-rule bypass for
`<Error>value` syntax.
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-11909-fix-catch-angle-bracket-Error-assertions-3566d73d365081f58ecfecfa2e948c33)
by [Unito](https://www.unito.io)
---------
Co-authored-by: bymyself <cbyrne@comfy.org>
*PR Created by the Glary-Bot Agent*
---
## Summary
Updates the "Install via GitHub" CTA buttons on the `/download` page to
deep-link to the install instructions section of the ComfyUI README
(`#installing`) instead of the repo root, so users land directly on
setup steps.
## Changes
- `apps/website/src/config/routes.ts`: add `externalLinks.githubInstall
= 'https://github.com/Comfy-Org/ComfyUI#installing'` (separate from
`externalLinks.github`, which is still used by the navbar/footer/star
badge for the generic repo link).
- `apps/website/src/components/product/local/HeroSection.vue`: switch
the secondary CTA next to "Download Local" from `externalLinks.github`
to `externalLinks.githubInstall`.
- `apps/website/src/components/product/local/EcoSystemSection.vue`: same
swap on the ecosystem-section CTA.
The platform-aware `Download Local` button (Windows/macOS installers via
`useDownloadUrl`) and the generic GitHub social/repo links elsewhere on
the site are unchanged.
## Verification
- `pnpm --filter @comfyorg/website typecheck` — 0 errors
- `pnpm --filter @comfyorg/website test:unit` — 23/23 passing
- `pnpm exec eslint` on changed files — clean
- `pnpm exec oxfmt --check` — clean
- Manual via `pnpm dev` + Playwright DOM assertion on `/download`:
- Hero "INSTALL FROM GITHUB" →
`https://github.com/Comfy-Org/ComfyUI#installing` ✓
- EcoSystem "INSTALL FROM GITHUB" →
`https://github.com/Comfy-Org/ComfyUI#installing` ✓
- Other "GitHub" links (nav, footer, star badge) → unchanged at
`https://github.com/Comfy-Org/ComfyUI` ✓
Per request from #website-and-docs: the local download surfaces should
at least link to the ComfyUI install instructions on GitHub. Companion
change to comfy-org/website#227.
## Screenshots

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-11852-fix-website-point-Install-via-GitHub-buttons-to-install-docs-anchor-3546d73d365081fe8370cd675ae8f896)
by [Unito](https://www.unito.io)
---------
Co-authored-by: Glary-Bot <glary-bot@users.noreply.github.com>
*PR Created by the Glary-Bot Agent*
---
Synthesizes lessons from the recent `v1.43.16` backport session (PRs
#11856–#11862) into the `.claude/skills/backport-management/` skill.
**Documentation only — no code, no workflow, no automation changes.**
## What's new
### `SKILL.md`
- **Quick Start** expanded from 7 to 12 steps, surfacing the pre-filter
and target-file-existence check that should run BEFORE per-PR triage
- **New gotcha**: *Cherry-Picked Tests Can Reference Files Added By
Earlier Unbackported PRs* — drop the test, document why
- **New gotcha**: *Backport-Only Compatibility Shims* — when a
refactor-style fix's mechanism relies on changes that aren't on the
older branch, a literal cherry-pick can recreate the bug for extensions
still using the old contract. Real example: #11541 + `LGraphNode.vue`
`handleDrop`
- **New section**: *Path Pre-Filter* under Auto-Skip Categories —
auto-skip PRs touching only `apps/website/`, `browser_tests/`,
`.github/`, `packages/design-system/`, generated types, `.claude/`,
`docs/`, or `*.stories.ts`. Removes 30–50% of candidates without reading
their bodies
### `reference/analysis.md`
- **New subsection**: *Verify Target File Existence* — `git cat-file -e`
before cherry-pick to skip PRs targeting features the branch doesn't
ship, faster than letting cherry-pick fail with modify/delete
- **New section**: *Tiered Triage* — **Tier 1** (core editor
must-haves), **Tier 2** (cloud-distribution only), **Tier 3** (skip)
before per-PR Y/N. Surfaces release-engineering decisions a flat
MUST/SHOULD list obscures
### `reference/discovery.md`
- Reconciliation workflow combining Slack bot list + git gap,
subtracting already-backported PRs (extracted via `Backport of #` grep
on the branch)
- Clarifies that no single source is authoritative
### `reference/execution.md`
- **New Step 0**: *Test-Then-Resolve Pre-Pass* — moved the dry-run loop
to the top of the workflow so you classify clean vs conflict before
triggering automation
- **New inline guard**: *Public-API conflict review* in the manual
cherry-pick loop — consult oracle BEFORE pushing if resolution touches
LiteGraph callbacks, `node.*` methods, or extension-API surfaces
- **Per-PR validation block** (typecheck + targeted unit tests + ESLint
+ oxfmt) before push, in addition to wave verification
- **Three new lessons learned** (19–21) covering the above
- **New PR Body Template** for manual cherry-picks — non-negotiable
conflict-resolution section so reviewers don't have to re-derive
resolution logic from the diff six months later
## Why
1. **Path pre-filtering** caught 35 of 61 candidates as `skip` before
any per-PR analysis was needed during the `v1.43.16` session
(`apps/website/`, CI, tests, design-system). The previous flat
MUST/SHOULD/SKIP rubric meant reading every PR.
2. **Oracle review on PR #11856** (#11541 backport) caught a regression
in `LGraphNode.vue:823` where the upstream PR's removal of the legacy
`handled === true` sync-return path would silently break custom nodes
still using the old `onDragDrop` contract — recreating the very
duplicate-node bug the PR was fixing. The skill had no guidance for this
class of issue.
3. **Two separate backports** (#11180, #11541) hit the same
modify/delete conflict pattern: a test file added on `main` by an
earlier unbackported PR. The skill's *Conflict Triage* table covered
modify/delete generally but didn't surface this specific anti-pattern of
smuggling in test scaffolding without its prerequisites.
4. **Discovery sources** — the skill assumed Slack bot + git gap as
inputs but didn't show how to reconcile them with already-backported
PRs, leading to potential double-cherry-picking.
## Verification
- No code, no workflow, no automation changes — skill documentation only
- `git diff --check` clean
- All four edited files render as valid markdown
- Cross-references between SKILL.md and `reference/*.md` files validated
by line-count check
## Out of scope (intentionally not changed)
- The `pr-backport.yaml` GitHub Action — the skill describes this but
doesn't own it
- The Slack bot — described as *Source 1*, not modified
- The PR title convention `[backport TARGET] ...` — kept as-is
(CodeRabbit's auto-skip filter relies on it)
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-11868-docs-skill-improve-backport-management-with-tiered-triage-path-pre-filter-and-public-3556d73d3650814faec3df795567bee3)
by [Unito](https://www.unito.io)
---------
Co-authored-by: Glary-Bot <glary-bot@users.noreply.github.com>
Co-authored-by: GitHub Action <action@github.com>
*PR Created by the Glary-Bot Agent*
---
## Step 2 of 2 — GitHub squash-commit incident recovery for #11460
This is the companion to #11629. After #11629 (the revert) merges, this
PR re-applies the original PR #11460 contents on top of the post-revert
`main`, restoring the intended state of the codebase.
### ⚠️ Sequencing — must merge after #11629
Until #11629 is merged, this PR's diff against `main` is empty (because
`main` currently still contains the squashed bug-dump-ingest commit that
#11629 will revert). After #11629 is merged into `main`, GitHub will
recompute the diff and this PR will show a clean re-add of the same 5
`.claude/skills/bug-dump-ingest/` files.
### Verification
The branch was constructed by:
1. Branching from `glary/revert-pr-11460` (the revert branch in #11629).
2. `git checkout FETCH_HEAD -- .claude/skills/bug-dump-ingest/` from
`refs/pull/11460/head` to restore the exact files.
3. Committing them as a single squash-style commit.
I verified that all 5 file blobs are byte-for-byte identical to the
original squash commit `559922eaa5c129767c22275c206c6877931ac15c` by
comparing git object SHAs:
| File | Object SHA |
|---|---|
| `.claude/skills/bug-dump-ingest/SKILL.md` |
`413737835fa1c996291019483effdd39e6da33e5` |
| `.claude/skills/bug-dump-ingest/reference/examples.md` |
`4fc54a4f14b1359de63f558acca0de48c1b65c57` |
| `.claude/skills/bug-dump-ingest/reference/linear-api.md` |
`57986740df2ee02c19b81059f0f1e00e54c2a042` |
| `.claude/skills/bug-dump-ingest/reference/schema.md` |
`84db1a5818c04ee53a94167092ec76dd814992d4` |
| `.claude/skills/bug-dump-ingest/reference/verify-commands.md` |
`a2c99a43a030ccc3769692d3c09be74132645bb4` |
### Notes
- One commit on `refs/pull/11460/head` (an automated lint commit
`76ca1598e`) modified
`src/renderer/extensions/vueNodes/widgets/components/WidgetChart.test.ts`
to remove an `eslint-disable` directive. That change was **not** in the
original squash commit on `main` (verified via `git show --stat`), and
the directive has separately been removed from `main` by an unrelated
commit (#11550), so re-applying that change would now be a no-op. This
repair PR therefore intentionally restores **only the 5 bug-dump-ingest
files**, matching the original squash commit exactly.
- Branch name is `glary/repair-pr-11460` (the `glary/` prefix is
required by the tooling; otherwise equivalent to GitHub's suggested
`repair-pr-11460`).
Refs: #11460, #11629
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-11630-Repair-re-add-bug-dump-ingest-skill-11460-GitHub-squash-commit-incident-recovery--34d6d73d365081acbd54c19316561fa9)
by [Unito](https://www.unito.io)
---------
Co-authored-by: Glary-Bot <glary-bot@users.noreply.github.com>
*PR Created by the Glary-Bot Agent*
---
## Step 1 of 2 — GitHub squash-commit incident recovery for #11460
GitHub flagged the squash commit for #11460
(`559922eaa5c129767c22275c206c6877931ac15c`) as affected by the
squash-commit incident that produced non-deterministic merges. This PR
is **step 1 of 2** in the recovery procedure GitHub asked us to follow.
### What this PR does
Reverts `559922eaa5c129767c22275c206c6877931ac15c` (the affected squash
commit for #11460, "feat: add bug-dump-ingest skill") on top of current
`main`.
### Diff
5 files removed, all under `.claude/skills/bug-dump-ingest/`:
- `.claude/skills/bug-dump-ingest/SKILL.md`
- `.claude/skills/bug-dump-ingest/reference/examples.md`
- `.claude/skills/bug-dump-ingest/reference/linear-api.md`
- `.claude/skills/bug-dump-ingest/reference/schema.md`
- `.claude/skills/bug-dump-ingest/reference/verify-commands.md`
`git revert` applied cleanly with no conflicts.
### Recovery procedure
Per GitHub's instructions:
1. **This PR (step 1)** — Revert the affected squash commit. Removes
both the original changes and any unintended changes that the incident
may have introduced.
2. **Next PR (step 2)** — Re-apply the original PR's changes from
`refs/pull/11460/head`, rebased onto post-revert `main`. Will be opened
as a separate "repair" PR.
### Review notes
- This is a pure revert; please confirm the diff is exactly the inverse
of #11460.
- After this PR is merged, the companion repair PR will land the
original changes back on `main` from a clean source ref, restoring the
intended state of the codebase.
- Branch name is `glary/revert-pr-11460` (the `glary/` prefix is
required by the tool that opened this PR — it's otherwise equivalent to
GitHub's suggested `revert-pr-11460`).
- Code review: Oracle reviewed and found 0 issues
(critical/warning/suggestion all 0). Ready to merge.
Refs: #11460
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-11629-Revert-feat-add-bug-dump-ingest-skill-11460-GitHub-squash-commit-incident-recove-34d6d73d3650810f9ed3f6068e4f1511)
by [Unito](https://www.unito.io)
Co-authored-by: Glary-Bot <glary-bot@users.noreply.github.com>
## Summary
Per product feedback, scroll-jacking the page to step through
'Industries that create with ComfyUI' categories feels bad on desktop.
This PR removes the desktop scroll override and switches to hover-driven
imagery on lg+, while preserving the existing pin/scrub interaction on
mobile/touch breakpoints.
## Changes
- **What**:
- Gate `usePinScrub` setup to `(max-width: 1023px)` so the pin/scrub
only runs on touch breakpoints; desktop never engages it.
- Wire `@mouseenter` and `@focus` on each category button to update the
active category on desktop. Click still works on both modes via the
existing `scrollToIndex` (which falls through to a direct ref set when
no ScrollTrigger instance is present).
- Pass the same `(max-width: 1023px)` to `useParallax` via its existing
`mediaQuery` option so parallax doesn't run against the no-longer-pinned
section on desktop.
- Apply the section's `lg:h-[calc(100vh+60px)]` unconditionally on lg+
since pin no longer drives it; mobile height is still managed
dynamically by `usePinScrub`'s `cacheLayout`.
- **Breaking**: None.
- **Dependencies**: None.
## Review Focus
- Mobile path inside `usePinScrub.onMounted` is byte-identical to main —
only the early-return condition gained one extra clause
(`!window.matchMedia('(max-width: 1023px)').matches`), which mobile
evaluates to `false` so it falls through to the existing setup.
- `onCategoryHover` early-returns when `isEnabled` is true, making it a
no-op on mobile (where pin is engaged), so a tap doesn't accidentally
fight the scrub.
- `@focus` is wired alongside `@mouseenter` so keyboard tab navigation
also previews the imagery.
- The previous Lenis-on-macOS workaround from this branch is reverted —
it was only needed because the scroll override existed.
## Screenshots (if applicable)
N/A — interaction change. Test on desktop (≥1024px) by hovering category
labels — imagery should swap with no scroll-jacking. Test on mobile
(<1024px) by scrolling the section; pin/scrub should engage as before.
---------
Co-authored-by: Marwan Ahmed <marwan@Marwans-MacBook-Pro.local>
Co-authored-by: GitHub Action <action@github.com>
Co-authored-by: Alexander Brown <drjkl@comfy.org>
## Summary
Refactor E2E tests added in #11210 that repeated full prior-test bodies
as setup, combining duplicate pairs into single tests with named
`test.step()` blocks.
## Changes
- **What**: In
[`browser_tests/tests/keyboardShortcutActions.spec.ts`](../blob/batch-dispatch/cr-11556/browser_tests/tests/keyboardShortcutActions.spec.ts):
- Merge `Ctrl+Z undoes` + `Ctrl+Shift+Z redoes` → single test with two
`test.step()` blocks.
- Merge `Ctrl+, opens settings dialog` + `Escape closes settings dialog`
→ single test with two `test.step()` blocks.
- **What**: In
[`browser_tests/tests/topbarMenuCommands.spec.ts`](../blob/batch-dispatch/cr-11556/browser_tests/tests/topbarMenuCommands.spec.ts):
- Merge `Edit > Undo` + `Edit > Redo` → single test with two
`test.step()` blocks.
The redo step now reuses the post-undo state from its preceding step
instead of re-creating and re-undoing the node, removing the duplicated
setup the reviewer flagged.
## Review Focus
- Naming of combined tests and `test.step()` labels.
- Note: per @AustinMroz's [comment
thread](https://github.com/Comfy-Org/ComfyUI_frontend/pull/11210#discussion_r3113526265),
location 2 in the issue refers to the `Escape closes settings dialog`
test (which duplicated the `Ctrl+,` test body), not the `Delete` test
(which has unique logic). Treated accordingly.
Fixes#11556
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-11835-test-combine-duplicated-undo-redo-and-settings-dialog-E2E-tests-with-test-step-3546d73d365081689df3c56bfbb6f4e4)
by [Unito](https://www.unito.io)
## Summary
Replaces all 7 production `as Error` type assertions with proper
`instanceof Error` narrowing or a new `toError()` helper, and adds an
ESLint rule to prevent new ones. First slice of #11429 (the `as Error`
category — 9 total occurrences, 7 production + 2 in a test file left
untouched).
## Changes
- **What**:
- New `src/utils/errorUtil.ts` exporting `toError(value: unknown):
Error` and `getErrorMessage(value: unknown): string | undefined`.
`toError` returns the value unchanged if already an `Error`, otherwise
wraps it (handles strings, `undefined`, JSON-serializable objects, and
circular refs via `String()` fallback).
- Refactored 7 production call sites:
- `src/services/gateway/registrySearchGateway.ts` — `toError(error)` for
`lastError` assignment in fallback loop
- `src/platform/cloud/onboarding/auth.ts` (×2) — `toError(error)` for
`captureApiError` Sentry calls
-
`src/renderer/extensions/vueNodes/widgets/composables/audio/useAudioRecorder.ts`
— `toError(err)` before forwarding to `options.onError`
- `src/extensions/core/load3d/LoaderManager.ts` — replaced `error as
Error & { response?: ... }` cast inside `isNotFoundError` with
`'response' in error` + nested narrowing
- `apps/desktop-ui/src/stores/maintenanceTaskStore.ts` — inline `error
instanceof Error ? error.message : String(error)`
- `apps/desktop-ui/src/components/maintenance/TaskListPanel.vue` —
inline `error instanceof Error ? error.message : undefined`
- New ESLint rule (`no-restricted-syntax` block named
`comfy/no-unsafe-error-assertion`) banning `TSAsExpression
TSTypeReference[typeName.name='Error']` in `src/**` and `apps/*/src/**`,
with test files (`*.test.ts`, `*.spec.ts`) excluded.
- 12 unit tests for the new helpers in `src/utils/errorUtil.test.ts`.
- **Breaking**: none
- **Dependencies**: none
## Review Focus
- The lint rule is scoped to non-test source files. Test files retain
freedom to use `as Error` for fixture construction; only 2 occurrences
exist (in `teamWorkspaceStore.test.ts` and `errorDialog.spec.ts`) and
they're intentional.
- `toError` is duplicated as inline `instanceof` narrowing in
`apps/desktop-ui/` rather than imported, since the desktop-ui workspace
doesn't share `@/utils/` with the main app and adding a path mapping for
one helper felt heavier than two inline guards.
- Remaining `as`-on-DOM categories (HTMLElement ×133, HTMLInputElement
×55, HTMLCanvasElement ×36, KeyboardEvent ×7, Element ×3, MouseEvent ×2,
Event ×2) are intentionally left for follow-up PRs to keep this one
reviewable.
Refs #11429
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-11845-refactor-replace-unsafe-as-Error-assertions-with-type-guards-3546d73d36508137a015c4f9e8708f23)
by [Unito](https://www.unito.io)
*PR Created by the Glary-Bot Agent*
---
Removes the "coming soon" badge from the Parallel Job Execution feature
card on the cloud pricing page (`comfy.org/cloud/pricing`).
## Changes
- `apps/website/src/components/pricing/WhatsIncludedSection.vue`: drop
`isComingSoon: true` from feature11 so it renders with the standard
check icon and no badge.
The `isComingSoon` mechanism (clock icon + yellow badge) is preserved in
the component for future use on other features.
## Note
The FAQ copy elsewhere on the site (`cloud.faq.9.a`) still references
"one active job at a time" and "parallel runs soon". That copy will be
updated separately.
## Verification
- `pnpm typecheck` (website): 0 errors
- `pnpm lint`: clean (1 pre-existing warning unrelated to this change)
- `pnpm format:check`: clean
- `pnpm test:unit` (website): 20 passed
- Visual check via Playwright on local dev server (see screenshot)
## Screenshots

┆Issue is synchronized with this [Notion
page](https://app.notion.com/p/PR-11819-fix-remove-coming-soon-badge-from-parallel-job-execution-3546d73d365081d19060f976095d03ac)
by [Unito](https://www.unito.io)
---------
Co-authored-by: Glary-Bot <glary-bot@users.noreply.github.com>
*PR Created by the Glary-Bot Agent*
---
## Summary
Registers a new nightly feature survey for the Queue Progress Overlay
using the existing feature-survey registry (same pattern as the merged
node-search survey, PRs #8175/#8355/#9934).
- New registry entry `queue-progress-overlay` → Typeform `HZ5saxry`,
threshold **16**, 5s display delay.
- `trackFeatureUsed()` wired at the major user-initiated handlers inside
the overlay so the survey triggers regardless of panel location
(floating-right v1 or docked-left v2).
- Run button and other ActionBar items that the overlay pops over from
are deliberately **not** tracked — tracking is scoped to interactions
that originate inside the job panel / queue progress overlay itself.
## Tracked interactions
Both variants share most sub-components, so tracking is instrumented
once at each logical surface:
- **`QueueProgressOverlay.vue`** (v1 container): `viewAllJobs`,
`interruptAll`, `cancelQueuedWorkflows`, `onClearHistoryFromMenu`,
`toggleAssetsSidebar`, `onCancelItem`, `onDeleteItem`, `inspectJobAsset`
- **`QueueOverlayExpanded.vue`**: job tab switches
- **`JobHistorySidebarTab.vue`** (v2 docked): job tab switches,
`clearQueuedWorkflows`, `onClearHistory`, `onCancelItem`,
`onDeleteItem`, `onViewItem`
- **`JobFilterActions.vue`** (shared): workflow filter + sort mode
selections
- **`JobHistoryActionsMenu.vue`** (shared): docked-history toggle +
run-progress-bar toggle
Deliberately **not tracked** to keep the signal clean:
- Hover handlers (ambient preview behaviour)
- Search-box keystrokes (debounced typing)
- Context menu open and menu-item dispatch — menu actions either bubble
through already-tracked terminal handlers (e.g. inspect-asset →
`onViewItem`) or are secondary operations (copy-id, open-workflow,
download). Avoids double-counting per code review feedback.
## How it works (inherits from existing infrastructure)
1. `surveyRegistry.ts` drives `NightlySurveyController` →
`NightlySurveyPopover`, which handles the Typeform embed.
2. Eligibility already gated on `isNightly && !isCloud && !isDesktop`,
once-per-user, 4-day global cooldown across all surveys, and opt-out.
3. Typeform response routing to #C0ALLT6Q3SQ is handled on the Typeform
side.
## Verification
- `pnpm typecheck` ✅
- `pnpm lint` ✅ (no new warnings)
- `pnpm knip` ✅
- `pnpm test:unit` on `src/components/queue`,
`src/components/sidebar/tabs/JobHistorySidebarTab`,
`src/platform/surveys` → **123/123 passing**
- Pre-commit hooks (stylelint, oxfmt, oxlint, eslint, typecheck) all
pass
- Manual: dev server + backend boot cleanly, app loads without new
runtime errors, `localStorage['Comfy.FeatureUsage']` layout verified to
match what `useFeatureUsageTracker` writes
## Notes
- Survey key `queue-progress-overlay` covers both v1 (floating-right)
and v2 (docked-sidebar) per product guidance: _"This should trigger
regardless of the location of the panel (docked from left or floating on
right)."_ Both surfaces are the same product feature — the survey is
intentionally scoped to the whole job-panel experience.
## Screenshots

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-11560-feat-add-queue-progress-overlay-feature-survey-34b6d73d3650819a9a50fd67fd9b5941)
by [Unito](https://www.unito.io)
---------
Co-authored-by: Glary-Bot <glary-bot@users.noreply.github.com>