Commit Graph

7183 Commits

Author SHA1 Message Date
dante01yoon
8033782f4b feat: add Paste Image option to Load Image node context menu
- Fixes #9989

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-16 15:49:39 +09:00
Yourz
06f7e13957 fix: add reve and elevenlabs to icon safelist (#9990)
## Summary

Reve and ElevenLabs provider icons were not displaying in the node
library because they were missing from the Tailwind icon safelist.

## Changes

- **What**: Add `reve` and `elevenlabs` to the `@source inline` safelist
in `style.css` so `icon-[comfy--reve]` and `icon-[comfy--elevenlabs]`
classes are generated. Add corresponding `PROVIDER_COLORS` entries in
`categoryUtil.ts`.

<img width="308" height="106" alt="image"
src="https://github.com/user-attachments/assets/d488898a-fbad-4af0-8921-0e8ee7d4705a"
/>
<img width="308" height="78" alt="image"
src="https://github.com/user-attachments/assets/2b3b7172-095b-415e-a49a-d303977e0abc"
/>


## Review Focus

The SVG files already existed in `packages/design-system/src/icons/` but
Tailwind's tree-shaking dropped the classes since they're only used
dynamically via `getProviderIcon()`.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9990-fix-add-reve-and-elevenlabs-to-icon-safelist-3256d73d36508105994fcdd5d0568027)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Amp <amp@ampcode.com>
2026-03-15 20:24:01 -07:00
Benjamin Lu
e31d98b743 fix: block missing e2e regression coverage in CodeRabbit (#9987)
## Summary

Make the CodeRabbit end-to-end regression coverage check actually block
fix-like PRs until it is resolved or explicitly overridden by a
requested reviewer, and harden the prompt so it evaluates only PR-local
metadata.

## Changes

- **What**: Set the `End-to-end regression coverage for fixes` custom
check mode from `warning` to `error`
- **What**: Enable `reviews.request_changes_workflow` so CodeRabbit can
block on failed `error` pre-merge checks
- **What**: Set
`reviews.pre_merge_checks.override_requested_reviewers_only` to `true`
so only requested reviewers can bypass a failed check
- **What**: Tighten the custom check instructions to use only PR
metadata in review context, avoid shell commands, and avoid reverse-diff
or base-branch file evaluation

## Review Focus

Confirm this is the intended CodeRabbit enforcement model for missing
Playwright regression coverage on fix-like PRs and that the prompt
wording is strict enough to avoid false positives from reversed diffs.
2026-03-15 19:06:10 -07:00
Comfy Org PR Bot
736f0fa416 1.42.6 (#9986)
Patch version increment to 1.42.6

**Base branch:** `main`

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9986-1-42-6-3256d73d365081a28bfad82022ce3440)
by [Unito](https://www.unito.io)

---------

Co-authored-by: christian-byrne <72887196+christian-byrne@users.noreply.github.com>
Co-authored-by: github-actions <github-actions@github.com>
v1.42.6
2026-03-15 17:29:44 -07:00
jaeone94
d8d8aeb99e feat: add Copy URL button to missing model rows for OSS (#9966) 2026-03-16 07:48:48 +09:00
Yourz
f4868894fa fix: tree explorer nodes not filling parent container width (#9964)
## Summary

Fix tree explorer nodes not filling the full width of the sidebar
container, causing text to overflow instead of truncating.

## Changes

- **What**: Add `min-w-0` to `TreeRoot` to allow flex shrinking within
sidebar. Add `w-full` and `min-w-0` to tree node rows so
absolutely-positioned virtualizer items fill the container width and
text truncates correctly.
<img width="365" height="749" alt="image"
src="https://github.com/user-attachments/assets/320910f3-52ad-4634-a935-6bd1a40aea7f"
/>


## Review Focus

The virtualizer renders each item with `position: absolute; left: 0` but
no explicit width, so rows would size to content rather than filling the
container. Adding `w-full` ensures rows stretch to 100% of the
virtualizer container, and `min-w-0` allows proper flex shrinking for
deep indentation levels.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9964-fix-tree-explorer-nodes-not-filling-parent-container-width-3246d73d36508138be38fdcac15ae4ef)
by [Unito](https://www.unito.io)

Co-authored-by: Amp <amp@ampcode.com>
2026-03-15 15:40:06 -07:00
Jukka Seppänen
a96c61d2c2 fix: LGraphGroup paste position (#9962)
## Summary

Fix group paste position: groups now paste at the cursor location
instead of on top of the original.

## Changes

- **What**: Added LGraphGroup offset handling in _deserializeItems()
position adjustment loop, matching existing LGraphNode and Reroute
behavior.

## Screenshots

Before:


https://github.com/user-attachments/assets/e317af10-8009-4092-9d14-de79316cd853

After:


https://github.com/user-attachments/assets/f4ffefd5-519a-4592-812c-c88e3b5940fd

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9962-fix-LGraphGroup-paste-position-3246d73d365081eea5b2e2507da861de)
by [Unito](https://www.unito.io)
2026-03-15 18:47:47 +00:00
Christian Byrne
c420cff4f0 feat: add TBT/frameDuration metrics and new perf test scenarios (#9910)
## Summary

Adds Total Blocking Time (TBT) and frame duration metrics to the
performance testing infrastructure, plus three new test scenarios
covering zoom, pan, and many-nodes-idle.

## Changes

### New Metrics
- **`totalBlockingTimeMs`** — Computed from PerformanceObserver
`longtask` entries: `sum(duration - 50ms)` for tasks >50ms. Measures
main thread blocking.
- **`frameDurationMs`** — Average frame duration via rAF timing (16.67ms
= 60fps target). Measures rendering smoothness.

### New Test Scenarios
| Scenario | Description |
|---|---|
| `canvas-zoom-sweep` | 10 zoom-in + 10 zoom-out cycles on default
workflow |
| `canvas-pan-many-nodes` | 10 pan sweeps over 100-node workflow |
| `canvas-many-nodes-idle` | 2-second idle measurement with 100 nodes
rendered |

### Infrastructure
- `PerformanceHelper.ts`: Installs PerformanceObserver for longtask,
collects TBT, measures frame duration via rAF
- `perf-report.ts`: Reports TBT and frame duration in PR comment tables
- `browser_tests/assets/perf/many_nodes_100.json`: 100-node (10×10 grid)
test fixture

## Review Focus
- TBT collection clears entries at `startMeasuring()` and reads at
`stopMeasuring()` — ensure no race with observer buffering
- Frame duration sampling uses 10 frames — enough for signal without
slowing tests

Depends on: #9886, #9887

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9910-feat-add-TBT-frameDuration-metrics-and-new-perf-test-scenarios-3236d73d365081488ae3c594a8bf7cff)
by [Unito](https://www.unito.io)
2026-03-15 08:54:00 -07:00
Yourz
8ccfe852b4 fix: cloud subscribe redirect hangs waiting for billing init (#9965)
## Summary

Fix /cloud/subscribe route hanging indefinitely because billing context
never initializes during the onboarding flow.

## Changes

- **What**: Replace passive `await until(isInitialized).toBe(true)` with
explicit `await initialize()` in CloudSubscriptionRedirectView. Remove
unused `until` import.


![Kapture 2026-03-15 at 23 16
22](https://github.com/user-attachments/assets/0a12487b-b39a-4f96-9a4c-96a01facfdd8)

## Review Focus

In the onboarding flow, `useTeamWorkspaceStore().activeWorkspace` is not
set, so `useBillingContext`'s internal watch (which triggers
`initialize()` on workspace change) enters the `!newWorkspaceId` branch
— it resets `isInitialized` to `false` and returns without ever calling
`initialize()`. The old code then awaited `isInitialized` becoming
`true` forever.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9965-fix-cloud-subscribe-redirect-hangs-waiting-for-billing-init-3246d73d3650812d93ebd477c544fa0a)
by [Unito](https://www.unito.io)

Co-authored-by: Amp <amp@ampcode.com>
2026-03-15 23:23:02 +08:00
jaeone94
e2ef041170 feat: surface missing models in Error Tab for OSS and remove legacy dialog (#9921)
## Summary
- Surface missing models in the Error Tab for OSS environments,
replacing the legacy modal dialog
- Add Download button per model and Download All button in group header
with file size display
- Move download business logic from `components/dialog/content` to
`platform/missingModel`
- Remove legacy missing models dialog components and composable

## Changes
- **Pipeline**: Remove `isCloud` guard from `scanAllModelCandidates` and
`surfaceMissingModels` so OSS detects missing models
- **Grouping**: Group non-asset-supported models by directory in OSS
instead of lumping into UNSUPPORTED
- **UI**: Add Download button (matching Install Node Pack design) and
Download All header button
- **Store**: Add `folderPaths`/`fileSizes` state with setter methods,
race condition guard
- **Cleanup**: Delete `MissingModelsContent`, `MissingModelsHeader`,
`MissingModelsFooter`, `useMissingModelsDialog`, `missingModelsUtils`
- **Tests**: Add OSS/Cloud grouping tests, migrate Playwright E2E to
Error Tab, improve test isolation
- **Snapshots**: Reset Playwright screenshot expectations since OSS
missing model error detection now causes red highlights on affected
nodes
- **Accessibility**: Add `aria-label` with model name, `aria-expanded`
on toggle, warning icon for unknown category

## Test plan
- [x] Unit tests pass (86 tests)
- [x] TypeScript typecheck passes
- [x] knip passes
- [x] Load workflow with missing models in OSS → Error Tab shows missing
models grouped by directory
- [x] Download button triggers browser download with correct URL
- [x] Download All button downloads all downloadable models
- [x] Cloud environment behavior unchanged
- [x] Playwright E2E: `pnpm test:browser:local -- --grep "Missing models
in Error Tab"`

## Screenshots


https://github.com/user-attachments/assets/12f15e09-215a-4c58-87ed-39bbffd1359c

 


┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9921-feat-surface-missing-models-in-Error-Tab-for-OSS-and-remove-legacy-dialog-3236d73d365081f0a9dfc291978f5ecf)
by [Unito](https://www.unito.io)

---------

Co-authored-by: GitHub Action <action@github.com>
Co-authored-by: github-actions <github-actions@github.com>
2026-03-15 22:46:47 +09:00
Dante
a4952e9c45 feat: add Ingest API codegen with Zod schema generation (#9932)
## Summary

- Add `packages/ingest-types/` package that auto-generates TypeScript
types and Zod schemas from the Ingest service OpenAPI spec
- Uses `@hey-api/openapi-ts` with built-in Zod plugin (Zod v3
compatible)
- Filters out overlapping endpoints shared with the local ComfyUI Python
backend
- Generates **493 TypeScript types** and **256 Zod schemas** covering
cloud-only endpoints
- Configure knip to ignore generated files

## CI automation

The cloud repo pushes generated types to this repo (push model, no
private repo cloning).
See: Comfy-Org/cloud#2858

## How endpoint filtering works

Codegen targets are controlled by the **exclude list** in
`packages/ingest-types/openapi-ts.config.ts`. Everything in the Ingest
`openapi.yaml` is included **except** overlapping endpoints that also
exist in the local ComfyUI Python backend:

**Excluded (overlapping with ComfyUI Python):**
`/prompt`, `/queue`, `/history`, `/object_info`, `/features`,
`/settings`, `/system_stats`, `/interrupt`, `/upload/*`, `/view`,
`/jobs`, `/userdata`, `/webhooks/*`, `/internal/*`

**Included (cloud-only, codegen targets):**
`/workspaces/*`, `/billing/*`, `/secrets/*`, `/assets/*`, `/tasks/*`,
`/auth/*`, `/workflows/*`, `/workspace/*`, `/user`, `/settings/{key}`,
`/tags`, `/feedback`, `/invite_code/*`, `/experiment/models/*`,
`/global_subgraphs/*`

## Follow-up: replace manual types with generated ones

This PR only sets up the codegen infrastructure. A follow-up PR should
replace manually maintained types with imports from
`@comfyorg/ingest-types`:

| File | Lines | Current | Replace with |
|------|-------|---------|-------------|
| `src/platform/workspace/api/workspaceApi.ts` | ~270 | TS interfaces |
`import type { ... } from '@comfyorg/ingest-types'` |
| `src/platform/secrets/types.ts` | ~32 | TS interfaces | `import type {
... } from '@comfyorg/ingest-types'` |
| `src/platform/assets/schemas/assetSchema.ts` | ~125 | Zod schemas |
`import { ... } from '@comfyorg/ingest-types/zod'` |
| `src/platform/assets/schemas/mediaAssetSchema.ts` | ~50 | Zod schemas
| `import { ... } from '@comfyorg/ingest-types/zod'` |
| `src/platform/tasks/services/taskService.ts` | ~70 | Zod schemas |
`import { ... } from '@comfyorg/ingest-types/zod'` |
| `src/platform/workspace/workspaceTypes.ts` | ~6 | TS interface |
`export type { ... } from '@comfyorg/ingest-types'` |

## Test plan

- [x] `pnpm generate` in `packages/ingest-types/` produces
`types.gen.ts` and `zod.gen.ts`
- [x] `pnpm typecheck` passes
- [x] Pre-commit hooks pass (lint, typecheck, format)
- [x] Generated Zod schemas validate correct data and reject invalid
data
- [x] No import conflicts with existing code (generated types are
isolated in separate package)

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: GitHub Action <action@github.com>
2026-03-15 21:53:26 +09:00
Christian Byrne
64c852bf82 test: add large-graph perf test with 245-node workflow (backlog N5) (#9940)
## What

Adds a 245-node workflow asset and two `@perf` tests to establish a
baseline for large-graph performance regressions (Tier 6 in the
performance backlog).

## Why

Backlog item N5: we need CI regression detection for compositor layer
management, GPU texture count, and transform pane cost at 245+ nodes.
This is PR1 of 2 — establishes baseline metrics on main. Future
optimization PRs will show improvement deltas against this baseline.

## Tests Added

- **`large graph idle rendering`** — 120 frames idle with 245 nodes,
measures style recalcs, layouts, task duration, heap delta
- **`large graph pan interaction`** — middle-click pan across 245 nodes,
stresses compositor layer management and transform recalculation

## Workflow Asset

`browser_tests/assets/large-graph-workflow.json` — 245 nodes (49
pipelines of CheckpointLoader → 2× CLIPTextEncode → KSampler +
EmptyLatentImage), 294 links. Minimal structure focused on node count.

## Verification

- [x] `pnpm typecheck:browser` passes
- [x] `pnpm lint` passes (eslint on changed file)
- [x] All link references in JSON validated programmatically

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9940-test-add-large-graph-perf-test-with-245-node-workflow-backlog-N5-3246d73d365081f6b5d8ddb9a85e6ad0)
by [Unito](https://www.unito.io)

---------

Co-authored-by: GitHub Action <action@github.com>
2026-03-15 03:04:39 -07:00
Dante
697087fdca draft: add red-green-fix skill for verified bug fix workflow (#9954)
## Summary

- Add a Claude Code skill (`/red-green-fix`) that enforces the red-green
commit pattern for bug fixes
- Ensures a failing test is committed first (red CI), then the fix is
committed separately (green CI)
- Gives reviewers proof that the test actually catches the bug
- Includes `reference/testing-anti-patterns.md` with common mistakes
contextualized to this codebase

## Structure

```
.claude/skills/red-green-fix/
├── SKILL.md                              # Main skill definition
└── reference/
    └── testing-anti-patterns.md          # Anti-patterns guide
```

## Test Plan

- [ ] Invoke `/red-green-fix <bug description>` in Claude Code and
verify the two-step workflow
- [ ] Confirm PR template includes red-green verification table
- [ ] Review anti-patterns reference for completeness

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9954-draft-add-red-green-fix-skill-for-verified-bug-fix-workflow-3246d73d365081339a83dc09263b0f33)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: GitHub Action <action@github.com>
2026-03-15 03:02:02 -07:00
Christian Byrne
a7218b2922 fix: fix perf CI pipeline — z-score baselines, force-push staleness, baseline storage (#9886)
## Summary

Fixes three critical issues with the CI performance reporting pipeline
that made perf reports useless on PRs (demonstrated by PR #9248 — deep
watcher removal merged without useful perf signal).

## Changes

### 1. Fix z-score baseline variance collection (`0/5 runs`)
**Root cause:** PR #9305 added z-score statistical analysis code to
`perf-report.ts`, but the historical data download step was placed in
the wrong workflow file. The report is generated in
`pr-perf-report.yaml` (a `workflow_run`-triggered job), but the
historical download was in `ci-perf-report.yaml` (the test runner) —
different runners, different filesystems.

**Fix:** Implement `perf-data` orphan branch storage:
- On push to main: save `perf-metrics.json` to `perf-data` branch with
timestamped filename
- On PR report: fetch last 5 baselines from `perf-data` branch into
`temp/perf-history/`
- Rolling window of 20 baselines, oldest pruned automatically
- Same pattern used by `github-action-benchmark` (33.7k repos)

### 2. Fix force-push comment staleness
**Root cause:** `cancel-in-progress: true` kills the perf test run
before it uploads artifacts. The downstream report workflow only
triggers on `conclusion == 'success'` — cancelled runs are ignored, so
the comment from the first successful run goes stale.

**Fix:**
- Change `cancel-in-progress: false` — with GitHub's queue depth of 1,
rapid pushes (A,B,C,D) run A and D, skipping B and C
- Add SHA validation in `pr-perf-report.yaml` — before posting, check if
the workflow_run's head SHA still matches the PR's current head. Skip
posting stale results.

### 3. Add permissions for baseline operations
- `contents: write` on CI job (needed for pushing to perf-data branch)
- `actions: read` on both workflows (needed for artifact/baseline
access)

## One-time setup required
After merging, create the `perf-data` orphan branch:
```bash
git checkout --orphan perf-data
git rm -rf .
echo '# Performance Baselines' > README.md
mkdir -p baselines
git add README.md baselines
git commit -m 'Initialize perf-data branch'
git push origin perf-data
```

The first 2 pushes to main after setup will build up variance data, and
z-scores will start appearing in PR reports (threshold is
`historical.length >= 2`).

## Testing
- YAML validated with `yaml.safe_load()`
- `perf-report.ts` `loadHistoricalReports()` already reads from
`temp/perf-history/<index>/perf-metrics.json` — no code changes needed
- All new steps use `continue-on-error: true` for graceful degradation

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9886-fix-fix-perf-CI-pipeline-z-score-baselines-force-push-staleness-baseline-storage-3226d73d365081538424c7945e71f308)
by [Unito](https://www.unito.io)
2026-03-15 02:46:10 -07:00
Christian Byrne
39a774bc15 feat: make Vue nodes (Nodes 2.0) default for new desktop installs (#9947)
## What

Makes Vue nodes (Nodes 2.0) the default renderer for new desktop app
installs (version ≥1.41.0), matching the behavior already live for cloud
new installs.

## Why

Step 2 of the Nodes 2.0 rollout sequence:
1.  Cloud new installs (≥1.41.0) — DONE
2. 👉 **Desktop app (new installs)** — this PR
3.  Local installs
4.  Remove Beta tag
5.  GTM announcement

No forced migration — only changes the default for new installs.
Existing users keep their setting. Rollback is a settings flip.

## Change

In `coreSettings.ts`, the `defaultsByInstallVersion` for
`Comfy.VueNodes.Enabled` changes from:
```typescript
defaultsByInstallVersion: { '1.41.0': isCloud },
```
to:
```typescript
defaultsByInstallVersion: { '1.41.0': isCloud || isDesktop },
```

## Gated on

- M2 perf target (≥52 FPS on 245-node workflow) — layer merge landed,
likely met
- M-DevRel migration docs (blocks Beta tag removal, not this flip)

Draft PR — ceremonial, to be merged when M2 checkpoint passes.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9947-feat-make-Vue-nodes-Nodes-2-0-default-for-new-desktop-installs-3246d73d365081b280dfff932c7aa016)
by [Unito](https://www.unito.io)
2026-03-15 01:51:25 -07:00
Christian Byrne
74dedc314d fix: prevent live preview dimension flicker between frames (#9937)
## Summary

Fix "Calculating dimensions" text flickering during live sampling
preview in Vue renderer.

## Changes

- **What**: Stop resetting `actualDimensions` to `null` on every
`imageUrl` change. Previous dimensions are retained while the new frame
loads, eliminating the flicker. Error state is still reset correctly.

## Review Focus

The watcher on `props.imageUrl` previously reset both `actualDimensions`
and `imageError`. Now it only resets `imageError`, since
`handleImageLoad` updates dimensions when the new frame actually loads.
This means stale dimensions show briefly between frames, which is
intentionally better than showing "Calculating dimensions" text.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9937-fix-prevent-live-preview-dimension-flicker-between-frames-3246d73d36508154a676e5996112354f)
by [Unito](https://www.unito.io)
2026-03-15 01:39:24 -07:00
Dante
5ae747e7c1 fix: prevent white flash when opening mask editor (#9860)
## Summary

- Remove hardcoded `bg-white` from mask editor canvas background div to
prevent white flash on dialog open
- Add a loading spinner while the mask editor initializes (image
loading, canvas setup, GPU resources)
- Background color is set dynamically by `setCanvasBackground()` after
initialization

Fixes #9852


### AS IS


https://github.com/user-attachments/assets/7da61e32-671b-4056-b5ec-8cb246fc7689



### TO BE 

https://github.com/user-attachments/assets/bfdedc69-f690-42c5-8591-619623c04f55



┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9860-fix-prevent-white-flash-when-opening-mask-editor-3226d73d365081de9b7ad4622438e6ed)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-15 01:15:04 -07:00
Christian Byrne
21069d54e7 feat: expand CDP perf metrics — add DOM nodes, script duration, event listeners (#9887)
## Summary

Expands the performance testing infrastructure to collect 4 additional
CDP metrics that are already returned by `Performance.getMetrics` but
were not being read. This is a zero-cost expansion — no additional CDP
calls, just reading more fields from the existing response.

## New Metrics

| Metric | CDP Source | What It Detects |
|---|---|---|
| `domNodes` | `Nodes` | DOM node count delta — widget DOM leaks during
node create/destroy |
| `jsHeapTotalBytes` | `JSHeapTotalSize` | Total heap delta — combined
with `heapDeltaBytes` shows GC pressure |
| `scriptDurationMs` | `ScriptDuration` | JS execution time vs total
task time — script vs rendering balance |
| `eventListeners` | `JSEventListeners` | Listener count delta — detects
listener accumulation across lifecycle |

## Changes

### `browser_tests/fixtures/helpers/PerformanceHelper.ts`
- Added 4 fields to `PerfSnapshot` interface
- Added 4 fields to `PerfMeasurement` interface
- Wired through `getSnapshot()` and `stopMeasuring()`

### `scripts/perf-report.ts`
- Added 4 fields to `PerfMeasurement` interface
- Expanded `MetricKey` type and `REPORTED_METRICS` array with 3 new
reported metrics (`domNodes`, `scriptDurationMs`, `eventListeners`)
- `jsHeapTotalBytes` is collected but not in `REPORTED_METRICS` — it's
used alongside `heapDeltaBytes` for GC pressure ratio analysis

## Why These 4

From a gap analysis of all ~30 CDP metrics, these were identified as
highest priority for ComfyUI:
- **`Nodes`** (P0): ComfyUI dynamically creates/destroys widget DOM. DOM
bloat from leaked widgets is a key performance risk, especially for Vue
Nodes 2.0.
- **`ScriptDuration`** (P1): Separates JS execution from layout/paint.
Reveals whether perf issues are script-heavy or rendering-heavy.
- **`JSEventListeners`** (P1): Widget lifecycle can leak listeners
across node add/remove cycles.
- **`JSHeapTotalSize`** (P1): With `JSHeapUsedSize`, the ratio shows GC
fragmentation pressure.

## Backward Compatibility

The `PerfMeasurement` interface is extended (not changed). Old baseline
`perf-metrics.json` files without these fields will have `undefined`
values, which the report script handles gracefully (shows `—` for
missing data).

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9887-feat-expand-CDP-perf-metrics-add-DOM-nodes-script-duration-event-listeners-3226d73d3650818abea1d4a441667c38)
by [Unito](https://www.unito.io)

---------

Co-authored-by: GitHub Action <action@github.com>
Co-authored-by: Alexander Brown <drjkl@comfy.org>
2026-03-14 23:24:02 -07:00
Christian Byrne
8a456043e8 test: add browser test for textarea right-click context menu in subgraph (#9891)
## Summary

Add E2E test coverage for the textarea widget right-click context menu
inside subgraphs.

The fix was shipped in #9840 — this PR adds the missing browser test.

## Test

- Loads a subgraph workflow with a CLIPTextEncode (textarea) node
- Navigates into the subgraph
- Right-clicks the textarea DOM element
- Asserts that the ComfyUI "Promote Widget" context menu option appears

## Related

- Fixes the test gap from #9840
- Notion ticket: d7a53160-e1e1-42bb-a5ac-c0c2702c629c

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9891-test-add-browser-test-for-textarea-right-click-context-menu-in-subgraph-3226d73d365081a4be51f89b5d505361)
by [Unito](https://www.unito.io)
2026-03-14 22:36:43 -07:00
Christian Byrne
585e6f87fa fix: skip redundant appScalePercentage updates during zoom/pan (#9403)
## What
Add equality check before updating `appScalePercentage` reactive ref.

## Why
Firefox profiler shows 586 `setElementText` markers from continuous text
interpolation updates during zoom/pan. The rounded percentage value
often doesn't change between events.

## How
Extract `updateAppScalePercentage()` helper with equality guard —
compares new rounded value to current before assigning to the ref.

## Perf Impact
Expected: eliminates ~90% of `setElementText` markers during zoom/pan

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9403-fix-skip-redundant-appScalePercentage-updates-during-zoom-pan-31a6d73d3650812db8f2d68ac73c95b0)
by [Unito](https://www.unito.io)
2026-03-14 21:44:44 -07:00
Comfy Org PR Bot
4781775a78 1.42.5 (#9906)
Patch version increment to 1.42.5

**Base branch:** `main`

---------

Co-authored-by: christian-byrne <72887196+christian-byrne@users.noreply.github.com>
Co-authored-by: github-actions <github-actions@github.com>
v1.42.5
2026-03-14 19:22:21 -07:00
Alexander Brown
74a48ab2aa fix: stabilize subgraph promoted widget identity and rendering (#9896)
## Summary

Fix subgraph promoted widget identity/rendering so on-node widgets stay
correct through configure/hydration churn, duplicate names, and
linked+independent coexistence.

## Changes

- **Subgraph promotion reconciliation**: stabilize linked-entry identity
by subgraph slot id, preserve deterministic linked representative
selection, and prune stale alias/fallback entries without dropping
legitimate independent promotions.
- **Promoted view resolution**: bind slot mapping by promoted view
object identity (`getSlotFromWidget` / `getWidgetFromSlot`) to avoid
same-name collisions.
- **On-node widget rendering**: harden `NodeWidgets` identity and dedup
to avoid visual aliasing, prefer visible duplicates over hidden stale
entries, include type/source execution identity, and avoid collapsing
transient unresolved entries.
- **Mapping correctness**: update `useGraphNodeManager` promoted source
mapping to resolve by input target only when the promoted view is
actually bound to that input.
- **Subgraph input uniqueness**: ensure empty-slot promotion creates
unique input names (`seed`, `seed_1`, etc.) for same-name multi-source
promotions.
- **Safety fix**: guard against undefined canvas in slot-link
interaction.
- **Tests/fixtures**: add focused regressions for fixture path
`subgraph_complex_promotion_1`, linked+independent same-name cases,
duplicate-name identity mapping, dedup behavior, and input-name
uniqueness.

## Review Focus

Validate behavior around transient configure/hydration states (`-1` id
to concrete id), duplicate-name promotions, linked representative
recovery, and that dedup never hides legitimate widgets while still
removing true duplicates.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9896-fix-stabilize-subgraph-promoted-widget-identity-and-rendering-3226d73d365081c8a1e8d0a5a22e826d)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Amp <amp@ampcode.com>
2026-03-14 11:30:31 -07:00
Christian Byrne
0875e2f50f fix: clear stale widget slotMetadata on link disconnect (#9885)
## Summary
Fixes text field becoming non-editable when a previously linked input is
removed from a custom node.

## Problem
When a widget's input was promoted to a slot, connected via a link, and
then the input was removed (e.g., by updating the custom node
definition), the widget retained stale `slotMetadata` with `linked:
true`. This prevented the widget from being editable.

## Solution
In `refreshNodeSlots`, removed the `if (slotInfo)` guard so
`widget.slotMetadata` is always assigned — either to valid metadata or
`undefined`. This ensures stale linked state is cleared when inputs no
longer match widgets.

## Acceptance Criteria
1. Text field remains editable after promote→connect→disconnect cycle
2. Text field returns to editable state when noodle disconnected
3. No mode switching needed to restore editability

## Testing
- Added regression test: "clears stale slotMetadata when input no longer
matches widget"
- All existing tests pass (18/18 in affected file)

---
**Note: This PR currently contains only the RED (failing test) commit
for TDD verification. The GREEN (fix) commit will be pushed after CI
confirms the test failure.**

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9885-fix-clear-stale-widget-slotMetadata-on-link-disconnect-3226d73d365081269319c027b42d9f6b)
by [Unito](https://www.unito.io)
2026-03-14 08:13:34 -07:00
Johnpaul Chiwetelu
63442d2fb0 fix: restore native copy/paste events for image paste support (#9914)
## Summary

- Remove Ctrl+C and Ctrl+V keybindings from the keybinding service
defaults so native browser copy/paste events fire
- This restores image paste into LoadImage nodes, which broke after
#9459

## Problem

PR #9459 moved Ctrl+C/V into the keybinding service, which calls
`event.preventDefault()` on keydown. This prevents the browser `paste`
event from firing, so `usePaste` (which detects images in the clipboard)
never runs. The `PasteFromClipboard` command only reads from
localStorage, completely bypassing image detection.

**Repro:** Copy a node → copy an image externally → try to paste the
image into a LoadImage node → gets old node data from localStorage
instead.

## Fix

Remove Ctrl+C and Ctrl+V from `CORE_KEYBINDINGS` in `defaults.ts`. The
native browser events now fire as before, and `useCopy`/`usePaste`
handle them correctly. Ctrl+Shift+V, Ctrl+A, Delete, and Backspace
keybindings remain in the keybinding service.

Fixes #9459 (regression)

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9914-fix-restore-native-copy-paste-events-for-image-paste-support-3236d73d365081c7ac53f983f316e10f)
by [Unito](https://www.unito.io)
2026-03-14 08:06:05 +00:00
Alexander Brown
bb6f00dc68 fix: hide template selector after shared workflow accept (#9913)
## Summary

Hide the template selector when a first-time cloud user accepts a shared
workflow from a share link, so the shared workflow opens without the
onboarding template dialog lingering.

## Changes

- **What**: Added shared-workflow loader behavior to close the global
template selector on accept actions (`copy-and-open` and `open-only`)
while keeping cancel behavior unchanged.
- **What**: Added targeted unit tests covering hide-on-accept and
no-hide-on-cancel behavior in the shared workflow URL loader.

## Review Focus

Confirm that share-link accept paths now dismiss the template selector
and that cancel still leaves it available.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9913-fix-hide-template-selector-after-shared-workflow-accept-3236d73d365081099c04e350d499fad2)
by [Unito](https://www.unito.io)

Co-authored-by: Amp <amp@ampcode.com>
2026-03-14 01:05:31 -07:00
Christian Byrne
11fc09220c fix: reorder forwardPanEvent conditions to skip expensive hasTextSelection on non-middle-button events (#9892)
## Summary

Reorder condition checks in `forwardPanEvent` so the cheap
`isMiddlePointerInput()` check runs first, avoiding expensive
`hasTextSelection()` → `window.getSelection().toString().trim()` on
every pointermove event.

## Changes

- **What**: `forwardPanEvent` now early-returns on
`!isMiddlePointerInput(e)` before calling `shouldIgnoreCopyPaste`, which
internally calls `hasTextSelection()`. Since most pointermove events are
not middle-button, this skips the expensive `toString()` call entirely.

## Review Focus

Semantic equivalence: the original condition was `(A && B) || C →
return`. Rewritten as two guards: `if (C) return; if (A && B) return;`.
The logic is equivalent — if `C` (`!isMiddlePointerInput`) is true, we
return regardless of `A && B`. If `C` is false (middle button), we check
`A && B` (`shouldIgnoreCopyPaste && activeElement`).

## Evidence

Backlog item #19. rizumu's Chrome DevTools profiling (Mar 2026) of a
245-node workflow showed `toString()` in `hasTextSelection` called on
every pointermove. Source: Slack `#C095BJSFV24` thread
`p1772823346206479`.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9892-fix-reorder-forwardPanEvent-conditions-to-skip-expensive-hasTextSelection-on-non-middle--3226d73d365081d38b89c8bb1dde3693)
by [Unito](https://www.unito.io)
2026-03-13 19:59:09 -07:00
Terry Jia
16f4f3f3ed fix: address PR review feedback for upstream value composable (#9908)
## Summary
follow up https://github.com/Comfy-Org/ComfyUI_frontend/pull/9851
fix https://github.com/Comfy-Org/ComfyUI_frontend/issues/9877 and
https://github.com/Comfy-Org/ComfyUI_frontend/issues/9878

- Make useUpstreamValue generic to eliminate as Bounds/CurvePoint[]
casts
- Change isBoundsObject to type predicate (value is Bounds)
- Reuse WidgetState from widgetValueStore instead of duplicate interface
- Add length >= 2 guard in isCurvePointArray for empty arrays
- Add disabled guard in effectiveBounds setter
- Add unit tests for singleValueExtractor and boundsExtractor

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9908-fix-address-PR-review-feedback-for-upstream-value-composable-3236d73d365081f7a01dcb416732544a)
by [Unito](https://www.unito.io)

---------

Co-authored-by: GitHub Action <action@github.com>
2026-03-13 19:58:30 -07:00
Deep Mehta
4c5a49860c feat: add model-to-node mappings for CogVideo, inpaint, and LayerDiffuse (#9890)
## Summary
- Add `quickRegister()` entries for 3 model directories missing UI
backlinks:
- `CogVideo` → `DownloadAndLoadCogVideoModel` (covers CogVideo,
CogVideo/ControlNet/*, CogVideo/VAE)
  - `inpaint` → `INPAINT_LoadInpaintModel`
  - `layer_model` → `LayeredDiffusionApply`

These mappings ensure the "Use" button in the model browser correctly
creates
the appropriate loader node when users click on models from these node
packs.

## Test plan
- [ ] Verify "Use" button works for CogVideo models in model browser
- [ ] Verify "Use" button works for inpaint models (fooocus, lama, MAT)
- [ ] Verify "Use" button works for LayerDiffuse models

🤖 Generated with [Claude Code](https://claude.com/claude-code)

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9890-feat-add-model-to-node-mappings-for-CogVideo-inpaint-and-LayerDiffuse-3226d73d3650816ea547fbcdf3a20c35)
by [Unito](https://www.unito.io)

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 17:45:24 -07:00
Johnpaul Chiwetelu
db5e8961e0 chore: upgrade vite to 8.0.0 stable (#9903)
## Summary
- Upgrade Vite from `8.0.0-beta.13` to `8.0.0` stable release

## Test plan
- [x] `pnpm typecheck` passes
- [x] `pnpm build` passes
- [x] `pnpm test:unit` passes (489 files, 6436 tests)

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9903-chore-upgrade-vite-to-8-0-0-stable-3226d73d365081feab2af46f0d95d6a4)
by [Unito](https://www.unito.io)
2026-03-13 21:45:41 +00:00
Terry Jia
9f9fa60137 feat: reactive upstream value display for disabled curve and imagecrop widgets (#9851)
Replacement for https://github.com/Comfy-Org/ComfyUI_frontend/pull/9364

## Summary
Add a generic mechanism for widgets to reactively display upstream
linked values when disabled, with concrete implementations for curve and
imagecrop widgets.

## Changes

- What: When a widget input is linked to an upstream node, the widget
enters a disabled state and displays the upstream node's current value
reactively. This is built as a two-layer system:
- Infrastructure layer: Resolve link origin info (originNodeId,
originOutputName) in buildSlotMetadata, pass it through
SimplifiedWidget.linkedUpstream, and provide a generic useUpstreamValue
composable that reads upstream values from widgetValueStore.
- Widget layer: Each widget type provides its own ValueExtractor to
interpret upstream data. singleValueExtractor handles simple
type-matched values (e.g. CurvePoint[]); boundsExtractor composes a
Bounds object from either a single upstream widget or four individual
x/y/width/height number widgets.
- Curve widget: shows upstream curve points in read-only mode
- ImageCrop widget: shows upstream bounding box with disabled crop
handles and number inputs
- CurveEditor and WidgetBoundingBox: gain disabled prop support

## Adapting future widgets
The system is designed so that any widget needing upstream value display
only needs to:
1. Accept widget: SimplifiedWidget as a prop (provides linkedUpstream
automatically)
2. Call useUpstreamValue(() => widget.linkedUpstream, extractor) with a
suitable extractor
3. Use singleValueExtractor(typeGuard) for single-value types, or write
a custom ValueExtractor for composite cases like boundsExtractor
4. Compute an effectiveValue that switches between upstream and local
based on disabled state

No infrastructure changes are needed — linkedUpstream is already
populated for all widget types that have a corresponding input slot.

## Review Focus
- The buildSlotMetadata helper is shared between extractVueNodeData and
refreshNodeSlots — verify the graph ref is reliably available in both
paths
- boundsExtractor composing from 4 individual number widgets
(x/y/width/height) — this handles the BBox→ImageCrop case where upstream
exposes separate widgets rather than a single Bounds object

## Screenshots

https://github.com/user-attachments/assets/dbc57a44-c5df-44f0-acce-d347797ee8fb

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9851-feat-reactive-upstream-value-display-for-disabled-curve-and-imagecrop-widgets-3226d73d36508134b386ddc9b9f1266b)
by [Unito](https://www.unito.io)
2026-03-13 16:59:53 -04:00
Christian Byrne
7131c274f3 fix: clear stale progress bar on SubgraphNode after navigation (#9865)
## Problem

When navigating back from a subgraph to the root graph, the SubgraphNode
can retain a stale progress bar. This happens because the progress
watcher in `GraphCanvas.vue` watches `[nodeLocationProgressStates,
canvasStore.canvas]`, but neither value changes reference during
subgraph navigation:

- `nodeLocationProgressStates` is already `{}` (execution completed
while viewing the subgraph)
- `canvasStore.canvas` is a `shallowRef` set once at startup — only
`canvas.graph` changes (via `setGraph()`)

**Reproduction** (from PR #4382 comment thread by @guill):
1. Create a subgraph with a KSampler
2. Execute the workflow
3. While progress bar is halfway, enter the subgraph
4. Wait for execution to complete
5. Navigate back to root graph
6. Progress bar is stuck at 50%

## Root Cause

`canvasStore.canvas` is a `shallowRef` — subgraph navigation mutates
`canvas.graph` (a nested property) via `LGraphCanvas.setGraph()`, which
doesn't trigger a shallow watch. The watcher never re-fires to clear
stale `node.progress` values.

## Fix

Add `canvasStore.currentGraph` to the watcher's dependency array. This
is already a `shallowRef` in `canvasStore` that's updated on every
`litegraph:set-graph` event. Zero overhead, precise targeting.

## Context

- Original discussion:
https://github.com/Comfy-Org/ComfyUI_frontend/pull/4382/files/BASE..868e047272f6c5d710db7e607b8997d4c243490f#r2202024855
- PR #9248 correctly removed `deep: true` from this watcher but missed
the subgraph edge case
- `deep: true` was the wrong fix — `canvasStore.currentGraph` is the
precise solution

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9865-fix-clear-stale-progress-bar-on-SubgraphNode-after-navigation-3226d73d3650811c8f9de612d81ef98a)
by [Unito](https://www.unito.io)
2026-03-13 13:04:10 -07:00
woctordho
82556f02a9 fix: respect 'always snap to grid' when auto-scale layout from nodes 1.0 to 2.0 (#9332)
## Summary

Previously when I switch from nodes 1.0 to 2.0, positions and sizes of
nodes do not follow 'always snap to grid'. You can guess what a mess it
is for people relying on snap to grid to retain sanity. This PR fixes
it.

## Changes

In `ensureCorrectLayoutScale`, we call `snapPoint` after the position
and the size are updated.

We also need to ensure that the snapped size is larger than the minimal
size required by the content, so I've added 'ceil' mode to `snapPoint`,
and the patch is larger than I thought first.

I'd happily try out nodes 2.0 once this is addressed :)

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9332-fix-respect-always-snap-to-grid-when-auto-scale-layout-from-nodes-1-0-to-2-0-3176d73d365081f5b6bcc035a8ffa648)
by [Unito](https://www.unito.io)
2026-03-13 11:40:51 -07:00
Christian Byrne
e34548724d feat(telemetry): add view_mode and is_app_mode to run_button_click event (#9881)
## Summary

Adds `view_mode` and `is_app_mode` properties to the
`app:run_button_click` telemetry event so analytics can segment run
button clicks by the user's current view context.

## Changes

- **`types.ts`**: Added `view_mode?: string` and `is_app_mode?: boolean`
to `RunButtonProperties`
- **`PostHogTelemetryProvider.ts`**: Computes `view_mode` and
`is_app_mode` from `useAppMode()` in `trackRunButton()`
- **`MixpanelTelemetryProvider.ts`**: Same as PostHog (providers are
mirrors)

## New Properties

| Property | Type | Description | Example Values |
|----------|------|-------------|----------------|
| `view_mode` | `string` | Granular AppMode value | `'graph'`, `'app'`,
`'builder:inputs'`, `'builder:outputs'`, `'builder:arrange'` |
| `is_app_mode` | `boolean` | Simplified flag for app mode vs non-app
mode | `true` when `mode === 'app' \|\| mode === 'builder:arrange'` |

## Design Decisions

- **Both granular and simplified**: `view_mode` gives exact mode for
detailed analysis; `is_app_mode` gives a quick boolean for simple
segmentation
- **Computed in providers**: View mode is read from `useAppMode()` at
tracking time, same pattern as `getExecutionContext()` — no changes
needed at call sites
- **`trigger_source` unchanged**: `keybindingService.ts` already reports
`trigger_source: 'keybinding'` regardless of view mode, satisfying the
requirement that keybinding invocations are correctly identified even in
app mode

## Testing

- Typecheck passes (no new errors)
- Format and lint pass (no new errors)
- Manual verification: all pre-existing errors are in unrelated files
(`draftCacheV2.property.test.ts`, `workflowDraftStoreV2.fsm.test.ts`)

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9881-feat-telemetry-add-view_mode-and-is_app_mode-to-run_button_click-event-3226d73d36508101b3a8c7ff27706f81)
by [Unito](https://www.unito.io)
2026-03-13 10:35:13 -07:00
Johnpaul Chiwetelu
fcdc08fb27 feat: structured preload error logging with Sentry enrichment (#8928)
## Summary

Add structured preload error logging with Sentry context enrichment and
a user-facing toast notification when chunk loading fails (e.g. after a
deploy with new hashed filenames).

## Changes

- **`parsePreloadError` utility** (`src/utils/preloadErrorUtil.ts`):
Extracts structured info from `vite:preloadError` events — URL, file
type (JS/CSS/unknown), chunk name, and whether it looks like a hash
mismatch.
- **Sentry enrichment** (`src/App.vue`): Sets Sentry context and tags on
preload errors so they are searchable/filterable in the Sentry
dashboard.
- **User-facing toast**: Shows an actionable "please refresh" message
when a preload error occurs, across all distributions (cloud, desktop,
localhost).
- **Capture-phase resource error listener** (`src/App.vue`): Catches
CSS/script load failures that bypass `vite:preloadError` and reports
them to Sentry with the same structured context.
- **Unit tests** (`src/utils/preloadErrorUtil.test.ts`): 9 tests
covering URL parsing, chunk name extraction, hash mismatch detection,
and edge cases.

## Files Changed

| File | What |
|------|------|
| `src/App.vue` | Preload error handler + resource error listener |
| `src/locales/en/main.json` | Toast message string |
| `src/utils/preloadErrorUtil.ts` | `parsePreloadError()` utility |
| `src/utils/preloadErrorUtil.test.ts` | Unit tests |

## Review Focus

- Toast fires for all distributions (cloud/desktop/localhost) —
intentional so all users see stale chunk errors
- `parsePreloadError` is defensive — returns `unknown` for any field it
cannot parse
- Capture-phase listener filters to only `<script>` and `<link
rel="stylesheet">` elements

## References

- [Vite preload error
handling](https://vite.dev/guide/build#load-error-handling)

---------

Co-authored-by: bymyself <cbyrne@comfy.org>
2026-03-13 10:29:33 -07:00
pythongosssss
f1626acb61 refactor: Unify app builder & app widget lists (#9829)
## Summary

Currently app builder & app mode use slightly different rendering paths
for the widgets, giving a different preview to what you actually get,
this changes it to use the same path for both.

## Changes

- **What**: 
- Extract LinearControls widget rendering
- Replace app builder arrange step with this
- Add ability to rename/remove widgets during app mode

## Screenshots (if applicable)

<img width="1205" height="853" alt="image"
src="https://github.com/user-attachments/assets/9160e33f-08c7-4863-a62d-c03929ffd3c8"
/>
<img width="1246" height="843" alt="image"
src="https://github.com/user-attachments/assets/82d8bacd-fd32-4ad7-b09d-eaa6670590ef"
/>

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9829-refactor-Unify-app-builder-app-widget-lists-3216d73d36508157a888f08dbe3655ea)
by [Unito](https://www.unito.io)
2026-03-13 10:01:46 -07:00
Arthur R Longbottom
5640eb7d92 fix: subgraph output slot labels not updating in v2 renderer (#9266)
## Summary

Custom names set on subgraph output nodes are ignored in the v2 renderer
— it always shows the data type name (e.g. "texts") instead of the
user-defined label. Works correctly in v1.

## Changes

- **What**: Made `outputs` in `extractVueNodeData` reactive via
`shallowReactive` + `defineProperty` (matching the existing `inputs`
pattern). Added a `node:slot-label:changed` graph trigger that
`SubgraphNode` fires when input/output labels are renamed, so the Vue
layer picks up the change.

## Review Focus

- The `outputs` reactivity mirrors `inputs` exactly — same
`shallowReactive` + setter pattern. The new trigger event forces
`shallowReactive` to detect the deep property change by re-assigning the
array.
- Also handles input label renames for consistency, even though the
current bug report is output-specific.

## Screenshots

**v1 — output correctly shows custom label "output_text":**
<img width="1076" height="628" alt="Screenshot 2026-02-26 at 4 43 00 PM"
src="https://github.com/user-attachments/assets/b4d6ae4c-9970-4d99-a872-4ce1b28522f2"
/>

**v2 before fix — output shows type name "texts" instead of custom
label:**
<img width="808" height="298" alt="Screenshot 2026-02-26 at 4 43 30 PM"
src="https://github.com/user-attachments/assets/cf06aa6c-6d4d-4be9-9bcd-dcc072ed1907"
/>

**v2 after fix — output correctly shows "output_text":**
<img width="1013" height="292" alt="Screenshot 2026-02-26 at 5 14 44 PM"
src="https://github.com/user-attachments/assets/3c43fa9b-0615-4758-bee6-be3481168675"
/>

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9266-fix-subgraph-output-slot-labels-not-updating-in-v2-renderer-3146d73d365081979327fd775a6ef62b)
by [Unito](https://www.unito.io)
2026-03-13 09:58:13 -07:00
Benjamin Lu
9447a1f5d6 test: warn on fix PRs without e2e regression coverage (#9880)
## Summary

Add a CodeRabbit pre-merge warning for fix-like PRs that do not update
`browser_tests/` and do not explain why no end-to-end regression test
was added.

Requested by Christian

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9880-test-warn-on-fix-PRs-without-e2e-regression-coverage-3226d73d3650816eb3e1c1c7d0824edd)
by [Unito](https://www.unito.io)
2026-03-13 09:57:56 -07:00
pythongosssss
1054503b4e Add support for values factory function in widget select combo (#8775)
## Summary

Adds support for values factory functions, e.g.
```
this.addWidget(
    "combo",
    "Dynamic",
    "",
    (e) => { },
    {
        values: () => {
            return getSomeValuesHere() 
        }
    }
)
```

Specifically for fixing KJNodes get/set

## Changes

- **What**:  Check if object is a function, if so, calls it.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8775-Add-support-for-values-factory-function-in-widget-select-combo-3036d73d3650819bb4e4f9181445cb1d)
by [Unito](https://www.unito.io)
2026-03-13 09:47:27 -07:00
jaeone94
9652871aaf [bugfix] Align advanced footer design with subgraph footer layout (#9879)
## Summary
Fix advanced widget footer on Vue nodes to use the same absolute
positioning and design as subgraph/error footers, and add dual-tab
layout when both error and advanced states coexist.

## Changes
- **What**: Changed advanced footer (Case 4) from relative to absolute
positioning matching subgraph footer design. Added new Case 1b for Error
+ Advanced dual-tab layout on regular nodes.
- **i18n**: Added `showAdvancedShort` / `hideAdvancedShort` keys for
compact dual-tab display

## Review Focus
- Visual consistency between advanced footer and subgraph footer across
collapsed/expanded states
- Dual-tab (Error + Advanced) layout mirrors subgraph dual-tab (Error +
Enter Subgraph)

## Screenshots 
**Before**

<img width="451" height="313" alt="image"
src="https://github.com/user-attachments/assets/98998e27-2283-49b4-8e32-3593c5577851"
/>

**After**
<img width="902" height="687" alt="image"
src="https://github.com/user-attachments/assets/83e7cec8-1ee7-44a6-b984-fd4d7282edd1"
/>
<img width="1150" height="1154" alt="image3"
src="https://github.com/user-attachments/assets/32605807-4c43-48e3-bb6e-b645afd1a403"
/>
<img width="1212" height="415" alt="image4"
src="https://github.com/user-attachments/assets/3e8184a6-6be4-4c83-98e3-0a6732ae4e3f"
/>

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9879-bugfix-Align-advanced-footer-design-with-subgraph-footer-layout-3226d73d365081868c3af5df528dc81e)
by [Unito](https://www.unito.io)
2026-03-14 00:44:04 +09:00
Alexander Brown
1280d4110d fix: simplify ensureCorrectLayoutScale and fix link sync during Vue node drag (#9680)
## Summary

Fix node layout drift from repeated `ensureCorrectLayoutScale` scaling,
simplify it to a pure one-time normalizer, and fix links not following
Vue nodes during drag.

## Changes

- **What**:
- `ensureCorrectLayoutScale` simplified to a one-time normalizer:
unprojects legacy Vue-scaled coordinates back to canonical LiteGraph
coordinates, marks the graph as corrected, and does nothing else. No
longer touches the layout store, syncs reroutes, or changes canvas
scale.
- Removed no-op calls from `useVueNodeLifecycle.ts` (a renderer version
string was passed where an `LGraph` was expected).
- `layoutStore.finalizeOperation` now calls `notifyChange` synchronously
instead of via `setTimeout`. This ensures `useLayoutSync`'s `onChange`
callback pushes positions to LiteGraph `node.pos` and calls
`canvas.setDirty()` within the same RAF frame as a drag update, fixing
links not following Vue nodes during drag.
- **Tests**: Added tests for `ensureCorrectLayoutScale` (idempotency,
round-trip, unknown-renderer no-op) and `graphRenderTransform`
(project/unproject round-trips, anchor caching).

## Review Focus

- The `setTimeout(() => this.notifyChange(change), 0)` →
`this.notifyChange(change)` change in `layoutStore.ts` is the key fix
for the drag-link-sync bug. The listener (`useLayoutSync`) only writes
to LiteGraph, not back to the layout store, so synchronous notification
is safe.
- `ensureCorrectLayoutScale` no longer has any side effects beyond
normalizing coordinates and setting `workflowRendererVersion` metadata.

---------

Co-authored-by: Amp <amp@ampcode.com>
Co-authored-by: Christian Byrne <cbyrne@comfy.org>
Co-authored-by: jaeone94 <89377375+jaeone94@users.noreply.github.com>
Co-authored-by: AustinMroz <austin@comfy.org>
Co-authored-by: Hunter <huntcsg@users.noreply.github.com>
Co-authored-by: GitHub Action <action@github.com>
2026-03-13 08:43:18 -07:00
Christian Byrne
871715113c fix: detect and remove duplicate links in subgraph unpacking (#9120)
## Summary

Fix duplicate LLink objects created during subgraph unpacking, where
output.links contains multiple link IDs for the same connection but
input.link only references one, leaving orphaned links.

## Changes

- **What**: Three layers of defense against duplicate links:
1. **Serialization fix** (`slotUtils.ts`): Clone `output.links` array in
`outputAsSerialisable` to prevent shared-reference mutation during
serialization round-trips
2. **Self-healing** (`LGraph.ts`): `_removeDuplicateLinks()` sanitizes
corrupted data during `configure()`, keeping the link referenced by
`input.link` and removing orphaned duplicates from `output.links` and
`_links`
3. **Unpack dedup** (`LGraph.ts`): Subgraph unpacking filters `newLinks`
via a `seenLinks` Set before creating connections

Runtime diagnostic logging via `graph.events` (no Sentry import in
litegraph):
- `_dupLinkIndex` Map for O(1) duplicate detection, only allocated when
enabled
- `_checkDuplicateLink()` called at the 3 link-creation sites
(`connectSlots`, `SubgraphInput.connect`, `SubgraphOutput.connect`)
- App layer listens for `diagnostic:duplicate-link` events and forwards
to Sentry with rate-limiting (1 per key per 60s)

## Review Focus

- The `_removeDuplicateLinks` strategy of keeping the link referenced by
`input.link` and removing others from `output.links` + `_links`
- The diagnostic index lifecycle: built on enable, updated on link
create/remove, cleared on disable
- Sentry integration in `app.ts` using the existing `graph.events`
system to avoid coupling litegraph to Sentry

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9120-fix-detect-and-remove-duplicate-links-in-subgraph-unpacking-3106d73d3650815b995ddf8f41da67ae)
by [Unito](https://www.unito.io)
2026-03-13 08:35:27 -07:00
Christian Byrne
a9f9afd062 fix: avoid forced layout in renderInfo by using canvas.height (#9304)
## What

Replace `canvas.offsetHeight` with `canvas.height / devicePixelRatio` in
`renderInfo` to avoid forced synchronous layout.

## Why

`renderInfo` is called ~2,631 times in a typical session. Each call
reads `this.canvas.offsetHeight`, which forces the browser to flush
pending style/layout changes synchronously. With PrimeVue injecting
styles dynamically and Vue patching the DOM, there are almost always
pending mutations — converting every canvas-only `renderInfo` call into
a forced layout.

## How

`canvas.height` is the DPR-scaled internal resolution (set in
`resizeCanvas` as `cssHeight * devicePixelRatio`). Dividing by
`devicePixelRatio` yields the same CSS pixel value as `offsetHeight`
without triggering layout.

## Verification

- [x] Unit test: verifies `offsetHeight` is not accessed when y is
provided
- [x] Unit test: verifies fallback uses `canvas.height /
devicePixelRatio`
- [x] `pnpm typecheck` passes
- [x] `pnpm lint` passes
- [x] All litegraph tests pass (538 passed)

## Perf Impact

Eliminates ~2,631 forced synchronous layouts per session from the canvas
info panel.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9304-fix-avoid-forced-layout-in-renderInfo-by-using-canvas-height-3156d73d36508171973dda289b30d5ee)
by [Unito](https://www.unito.io)

---------

Co-authored-by: GitHub Action <action@github.com>
2026-03-13 08:35:03 -07:00
Christian Byrne
79d0e6dc69 fix: cache ctx.measureText results to avoid redundant calls in draw loop (#9404)
## What
Add a per-frame text measurement cache for all hot-path
ctx.measureText() calls.

## Why
drawTruncatingText() in BaseWidget calls ctx.measureText() per widget
per frame with zero caching. For a 50-node workflow at 60fps:
~78,000-243,000 measureText calls/sec. Text labels rarely change between
frames.

## How
Global Map<string, number> cache keyed by font+text, cleared once per
frame at the start of drawFrontCanvas(). Replaces direct
ctx.measureText() calls in BaseWidget.drawTruncatingText, draw.ts
truncateTextToWidth/drawTextInArea, LGraphBadge.getWidth,
LGraphButton.getWidth, and textUtils.truncateText.

## Perf Impact
Expected: ~95% reduction in measureText calls (only cache misses on
first frame and value changes). Firefox has slower measureText than
Chrome, so this disproportionately benefits Firefox.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9404-fix-cache-ctx-measureText-results-to-avoid-redundant-calls-in-draw-loop-31a6d73d3650814e9cdac16949c55cb7)
by [Unito](https://www.unito.io)

---------

Co-authored-by: GitHub Action <action@github.com>
2026-03-13 08:16:53 -07:00
Christian Byrne
de866a15d2 Add prompt_id support to progress_text WS messages (#9002)
## Summary

Add frontend support for `prompt_id` in `progress_text` binary WS
messages, enabling parallel workflow execution to route progress text to
the correct active prompt.

Backend PR: https://github.com/Comfy-Org/ComfyUI/pull/12540

## Changes

- Advertise `supports_progress_text_metadata` in client feature flags
- Decode `prompt_id` from new binary format when flag is active
- Add optional `prompt_id` to `zProgressTextWsMessage` schema
- Filter `progress_text` events by `activePromptId` — skip messages for
non-active prompts

## Deployment

Can be deployed independently in any order — feature flag negotiation
ensures graceful degradation.

Part of COM-12671

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9002-Add-prompt_id-support-to-progress_text-WS-messages-30d6d73d365081acabbcdac939e0c751)
by [Unito](https://www.unito.io)


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Progress text updates can include optional metadata so richer context
is available.
* **Bug Fixes / Improvements**
* Progress updates are now filtered to show only the currently active
prompt, reducing cross-talk from concurrent operations and improving
update accuracy.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: GitHub Action <action@github.com>
2026-03-13 07:55:05 -07:00
jaeone94
31a33a0ba2 feat: auto-resolve simple validation errors on widget change and slot connection (#9464)
## Summary

Automatically clears transient validation errors
(`value_bigger_than_max`, `value_smaller_than_min`, `value_not_in_list`,
`required_input_missing`) when the user modifies a widget value or
connects an input slot, so resolved errors don't linger in the error
panel. Also clears missing model state when the user changes a combo
widget value.

## Changes

- **`useNodeErrorAutoResolve` composable**: watches widget changes and
slot connections, clears matching errors via `executionErrorStore`
- **`executionErrorStore`**: adds `clearSimpleNodeErrors` and
`clearSimpleWidgetErrorIfValid` with granular per-slot error removal
- **`executionErrorUtil`**: adds `isValueStillOutOfRange` to prevent
premature clearing when a new value still violates the constraint
- **`graphTraversalUtil`**: adds `getExecutionIdFromNodeData` for
subgraph-aware execution ID resolution
- **`GraphCanvas.vue`**: fixes subgraph error key lookup by using
`getExecutionIdByNode` instead of raw `node.id`
- **`NodeWidgets.vue`**: wires up the new composable to the widget layer
- **`missingModelStore`**: adds `removeMissingModelByWidget` to clear
missing model state on widget value change
- **`useGraphNodeManager`**: registers composable per node
- **Tests**: 126 new unit tests covering error clearing, range
validation, and graph traversal edge cases

## Screenshots



https://github.com/user-attachments/assets/515ea811-ff84-482a-a866-a17e5c779c39



https://github.com/user-attachments/assets/a2b30f02-4929-4537-952c-a0febe20f02e


┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9464-feat-auto-resolve-simple-validation-errors-on-widget-change-and-slot-connection-31b6d73d3650816b8afdc34f4b40295a)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 23:49:44 +09:00
Dante
d73f8e1beb fix: show most recent image first in asset sidebar batch view (#9467)
## Summary
- Use the last previewable output as the batch cover/thumbnail instead
of the first, so the most recently generated image (e.g., `_00010`) is
shown as the representative
- Reverse output order in batch folder view so newest images appear at
the top
- Gitignore `.claude/worktrees` to fix knip scanning untracked worktree
copies

## Linked Issues
- Fixes #9354
- Related to #9080

## Test plan
- [ ] Generate a batch of images (e.g., 10 images) and verify the
sidebar shows the last generated image as the cover
- [ ] Expand the batch folder view and verify images are in
reverse-chronological order (newest first)
- [ ] Verify existing unit tests pass (`pnpm test:unit`)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9467-fix-show-most-recent-image-first-in-asset-sidebar-batch-view-31b6d73d365081cbaf30f81009c7fcfa)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 23:49:42 +09:00
Dante
4e85537b15 test: add property-based FSM tests for workflow persistence (#9370)
## Summary

- Fixes #9319
- Add [fast-check](https://github.com/dubzzz/fast-check) property-based
testing with FSM (Finite State Machine) traversal to automatically
explore state combinations in the workflow persistence system
- Fix a real bug in `saveDraft()` discovered by the FSM test: orphan
cleanup in `loadIndex()` could delete a just-written payload when the
in-memory cache was empty

## Why this is needed

#9317 exposed a class of bug where two independently correct changes
interact to cause workflow loss. Conventional unit tests verify
specific, hand-picked scenarios and cannot catch these cross-PR
interaction bugs.

### AS IS (before)

| Aspect | Status |
|---|---|
| Testing approach | Example-based: developer picks specific inputs and
expected outputs |
| State coverage | Only explicitly written scenarios are tested |
| Cross-interaction bugs | Not detectable — each test runs one isolated
path |
| Bug in `saveDraft` | Undetected — `loadIndex()` orphan cleanup could
delete a just-written payload after `reset()` |

### TO BE (after)

| Aspect | Status |
|---|---|
| Testing approach | Property-based: fast-check generates **200 random
command sequences** per run |
| State coverage | Random exploration of `SaveDraft → GetDraft →
RemoveDraft → MoveDraft → GetMostRecentPath → Reset` combinations |
| Cross-interaction bugs | Detected automatically — fast-check shrinks
failing sequences to minimal reproductions |
| Bug in `saveDraft` | Found and fixed — `loadIndex()` now runs
**before** `writePayload()` to prevent orphan cleanup race |

## What fast-check does

fast-check is a property-based testing library. Instead of testing "does
this specific input produce this specific output?", it tests "does this
**property** hold for **all possible inputs**?"

For FSM testing specifically, fast-check:
1. Takes a set of **commands** (SaveDraft, GetDraft, RemoveDraft,
MoveDraft, GetMostRecentPath, Reset)
2. Generates **random sequences** of these commands
3. Runs each sequence against both a **model** (simplified oracle) and
the **real system** (store + localStorage)
4. Verifies **invariants** after every mutating command (index/payload
consistency, no orphans, LRU correctness, model agreement)
5. When a failure is found, **shrinks** the sequence to the minimal
reproduction

Example: the bug this PR fixes was shrunk to just 4 commands:
```
SaveDraft(d.json) → RemoveDraft(d.json) → Reset() → SaveDraft(a.json) ✗
```

## Changes

| File | Change |
|---|---|
| `package.json` / `pnpm-workspace.yaml` | Add `fast-check`
devDependency |
| `draftCacheV2.property.test.ts` | 7 property tests for pure index
functions |
| `workflowDraftStoreV2.fsm.test.ts` | FSM test: 6 commands, invariant
checking, 200 runs |
| `workflowDraftStoreV2.ts` | Fix: move `loadIndex()` before
`writePayload()` in `saveDraft()` |

## Test plan

- [x] `pnpm test:unit` — all 117 persistence tests pass (including 7
property + 1 FSM)
- [x] `pnpm typecheck` — clean
- [x] `pnpm lint` — clean
- [x] Pre-commit hooks pass (format, lint, typecheck)
- [x] Pre-push hook passes (knip)

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9370-test-add-property-based-FSM-tests-for-workflow-persistence-3196d73d3650813daa98cdd8bef7e975)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: GitHub Action <action@github.com>
2026-03-13 07:28:38 -07:00
Christian Byrne
b5ddc70233 feat: show ComfyUI context menu on textarea widget right-click (#9840)
## Summary

Right-clicking a textarea widget (e.g. text node) shows the browser's
native context menu instead of ComfyUI's context menu, preventing access
to promote/un-promote options in subgraphs.

## Changes

- **What**: Replace `@contextmenu.capture.stop` on
`WidgetTextarea.vue`'s `<Textarea>` with a handler implementing
double-right-click toggling: first right-click shows ComfyUI's context
menu, second right-click (while menu is open) allows browser native
menu. Exposes `isNodeOptionsOpen()` from `useMoreOptionsMenu.ts` to
check menu state.

## Review Focus

The capture-phase handler in `WidgetTextarea.vue` only changes
`contextmenu` handling — pointer event modifiers
(`pointerdown/move/up.capture.stop`) that prevent canvas panning are
untouched. The double-right-click pattern matches Notion/YouTube
behavior for editable text fields.

<!-- Pipeline-Ticket: d7a53160-e1e1-42bb-a5ac-c0c2702c629c -->

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9840-fix-show-ComfyUI-context-menu-on-textarea-widget-right-click-3216d73d36508102b4c9c13a5915bc48)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Alexander Brown <drjkl@comfy.org>
2026-03-13 07:03:20 -07:00
Johnpaul Chiwetelu
48d928fc9e feat: multi-keybinding support in settings panel (#9738)
## Summary

Adds VS Code-style multi-keybinding support to the Keybinding settings
panel. Commands can now have multiple keybindings displayed, expanded,
and individually managed.

- Fixes #1088

## Changes

### Store (`keybindingStore.ts`)
- `removeAllKeybindingsForCommand(commandId)` — unsets all bindings for
a command
- `updateSpecificKeybinding(old, new)` — replaces a single binding
without affecting others
- `resetKeybindingForCommand` — updated to restore **all** default
bindings, not just the first
- `isCommandKeybindingModified` — updated to compare full sorted sets of
bindings

### UI (`KeybindingPanel.vue`)
- **Data model**: `keybinding: KeybindingImpl | null` → `keybindings:
KeybindingImpl[]`
- **Multi-binding display**: shows up to 2 combos inline with `, `
separator, then `+ N more` badge
- **Expand/collapse**: click any row with 2+ bindings to expand
individual binding rows; chevron-right icon rotates on expand
- **Per-binding actions**: edit (pencil), reset, trash on each expanded
sub-row
- **Parent row actions**: `+`/trash for 2+ bindings, pencil/reset/trash
for 1, `+`/disabled for 0
- **Edit modes**: `edit` (replace specific binding via
`updateSpecificKeybinding`) and `add` (append via `addUserKeybinding`)
- **Right-click context menu**: Change keybinding, Add new, Reset to
default, Remove keybinding — with proper disabled states and lucide
icons
- **Remove all dialog**: confirmation via `showSmallLayoutDialog` with
`RemoveAllKeybindingsHeader`/`Content` components
- **Reset all dialog**: confirmation via `showConfirmDialog` before
resetting all keybindings to defaults
- **Double-click**: 0 bindings → add, 1 → edit, 2+ → no-op (single click
toggles expand)
- **Consistent alignment**: commands without chevron get `pl-5` padding
to align with those that have it

### Tests (`keybindingStore.test.ts`)
- 7 new tests covering `removeAllKeybindingsForCommand`,
`updateSpecificKeybinding`, multi-binding `isCommandKeybindingModified`,
and multi-binding `resetKeybindingForCommand`

### i18n (`main.json`)
- 11 new keys: removeAllKeybindingsTitle/Message, removeAll,
changeKeybinding, addNewKeybinding, resetToDefault, removeKeybinding,
nMoreKeybindings, resetAllKeybindingsTitle/Message, allKeybindingsReset

### New components
- `RemoveAllKeybindingsHeader.vue` — dialog header
- `RemoveAllKeybindingsContent.vue` — dialog body with Close/Remove all
buttons

## Test plan

- [x] `pnpm typecheck` passes
- [x] `pnpm lint` passes (no new errors)
- [x] `pnpm vitest run src/platform/keybindings/` — 45 tests pass
- [x] CodeRabbit review — 0 findings
- [ ] Manual: open Settings → Keybindings, verify multi-binding commands
(e.g. Delete Selected Items, Zoom In) show multiple combos
- [ ] Manual: click row to expand, verify per-binding actions work
- [ ] Manual: right-click row, verify context menu actions
- [ ] Manual: click trash on 2+ binding command, verify "Remove all"
confirmation dialog
- [ ] Manual: click "Reset All" button, verify confirmation dialog
appears
- [ ] Manual: add/edit/remove individual bindings, verify persistence

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9738-feat-multi-keybinding-support-in-settings-panel-3206d73d365081e9b08bd3cfe21495f1)
by [Unito](https://www.unito.io)
2026-03-13 13:43:33 +00:00
Jin Yi
10b0350d01 feat: unify sidebar panel header layout with SidebarTopArea component (#9740)
## Summary

Unify the search bar + action buttons layout across all left sidebar
panels (Node Library, Workflows, Model Library, Media Assets) using a
shared `SidebarTopArea` presentation component.

## Changes

- **What**:
- Add `SidebarTopArea.vue` — layout component with `flex-1` default slot
(search) and `#actions` slot (buttons), plus optional `bottomDivider`
prop
- Replace raw `<button>` elements in Node Library with `<Button
variant="secondary" size="icon">`
- Replace reka-ui `TabsTrigger` with shared `Tab/TabList` component in
Node Library
- Move Media Assets tab list from hover-only `#tool-buttons` to
always-visible header below search area
- Unify spacing (`gap-2`, `p-2 2xl:px-4`) and divider styles across all
sidebar panels
- Remove unused `assetType` prop and header from
`AssetsSidebarGridView`/`AssetsSidebarListView`

## Review Focus

- `SidebarTopArea` API simplicity — just slots + one optional prop
- Node Library still requires `TabsRoot` in the body for reka-ui
`TabsContent` in child panels
- Media Assets tabs are now always visible instead of hover-only

[screen-capture
(1).webm](https://github.com/user-attachments/assets/fe1d8f7b-5674-4bb3-9842-569e4c3af6c9)

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9740-feat-unify-sidebar-panel-header-layout-with-SidebarTopArea-component-3206d73d365081ea8ba7fd6ac54e0169)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Amp <amp@ampcode.com>
2026-03-13 06:32:18 -07:00