Commit Graph

7846 Commits

Author SHA1 Message Date
jaeone94
7149aefaa9 refactor: replace public input cache with vue query 2026-05-04 20:24:56 +09:00
jaeone94
4afe0baa72 test: add public input missing media regression 2026-05-04 18:14:08 +09:00
jaeone94
f508a060a0 refactor: extract asset hash verification 2026-05-04 17:55:20 +09:00
jaeone94
5f43461623 refactor: align asset pagination schema 2026-05-04 17:42:37 +09:00
jaeone94
04918360eb Use hash lookup for missing asset detection (#11873)
## Summary

Use exact BLAKE3 hash lookups first for missing model/media detection,
and add a separate public-inclusive input asset cache so public input
assets are considered missing-detection candidates without changing the
user-only input assets shown in the UI.

## Changes

- **What**:
- Added `assetService.checkAssetHash()` for `HEAD
/api/assets/hash/{hash}` status-only existence checks.
- Added strict BLAKE3 hash helpers so only `blake3:<64 hex>` media
values and raw 64-hex BLAKE3 model metadata are sent to the hash
endpoint.
- Updated missing media detection to group BLAKE3 candidates by hash,
resolve them through the hash endpoint, and fall back to the legacy
asset list path for invalid/unverifiable/non-hash values.
- Updated missing model detection to use hash lookup for BLAKE3-backed
asset-supported candidates before falling back to the existing node-type
asset matching path.
- Added `assetService.getInputAssetsIncludingPublic()` backed by a
dedicated cache that fetches input assets with `include_public=true` for
missing media fallback checks.
- Kept `assetsStore.inputAssets` user-only for widget/UI display, while
invalidating the public-inclusive missing-detection cache when input
assets may change.
- Added abort handling for paginated asset fetches and shared
public-input cache callers so one aborted caller does not cancel the
shared fetch for other callers.
- Added regression coverage for hash lookup, fallback behavior, abort
paths, public input fallback detection, and cache invalidation.
- **Dependencies**: None.
- **Change size**:
  - Production code: 4 files, 400 insertions, 24 deletions, net +376.
  - Test code: 4 files, 806 insertions, 59 deletions, net +747.
  - Total: 8 files, 1206 insertions, 83 deletions, net +1123.

## Review Focus

- The public-inclusive input asset cache is intentionally separate from
`assetsStore.inputAssets`. The existing store data is user-only and
drives the asset widgets/sidebar, so using it for missing input
detection misses public assets. Making that store public-inclusive would
change UI data semantics; this PR instead keeps the UI dataset unchanged
and adds a missing-detection-specific cache in `assetService`.
- Hash lookup is only used when the workflow exposes a valid BLAKE3
hash. Filename-like values and invalid hash values still use the legacy
fallback path.
- Missing model detection keeps the existing fallback behavior for
non-hash candidates and for hash checks that are invalid or fail
transiently.
- Async model download cache refresh behavior is left unchanged; this PR
avoids coupling model download completion to input asset cache
invalidation.
- No browser/e2e test was added because this changes the missing asset
detection data path, not UI interaction or rendering. The behavioral
coverage is in unit tests for the asset service and the missing
media/model scanners.

## Follow-up Items

- Fix `assetsStore.updateAssetTags()` partial-failure recovery. If
`removeAssetTags()` succeeds and `addAssetTags()` fails, the local model
asset cache can roll back to tags that the backend has already removed;
this should be handled in a focused model asset cache PR.
- Consider extracting shared hash-verification flow used by missing
media and missing model scans after this behavior stabilizes.
- Consider adding a concurrency cap or short-lived request cache for
large workflows with many unique hash lookups.
- Consider splitting `assetService.ts` further, e.g. hash helpers, abort
utilities, and the public-inclusive input asset cache.
- Consider tightening the asset hash service API shape so callers do not
directly depend on HTTP-oriented statuses such as `invalid`.
- Consider adding broader mutation-path coverage for public-inclusive
input cache invalidation once the cache has more consumers.

Linear: FE-534

## Screenshots (if applicable)

Before <false positive / missing image / public asset>


https://github.com/user-attachments/assets/db7ce2a9-b169-4fae-bf9f-98bb93d3ee6d

After 


https://github.com/user-attachments/assets/29af9f9e-b536-4fcd-a426-3add40bcb165



┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-11873-Use-hash-lookup-for-missing-asset-detection-3556d73d36508165babafb16614be0d8)
by [Unito](https://www.unito.io)
2026-05-04 03:59:54 +00:00
Dante
af70d88860 fix: keep finished badge fully opaque in ProgressToastItem (#11542)
## Summary
- fix
**[slack](https://comfy-organization.slack.com/archives/C0A4XMHANP3/p1776801170742579)**
- Move `opacity-50` off the row container onto the asset-name column
only, so the contrast badge (white pill, dark label) is not dimmed to
gray-on-gray when a download completes.
- Matches the Figma intent that the `FINISHED` badge stands out —
designer spec uses `base/foreground` for pill, `base/background` for
text, which is unreadable when the parent is 50% opacity.

<img width="560" height="269" alt="Screenshot 2026-04-23 at 2 46 17 PM"
src="https://github.com/user-attachments/assets/fb84aa57-c348-4a86-9a65-9342c12400e1"
/>
<img width="764" height="332" alt="Screenshot 2026-04-23 at 2 46 41 PM"
src="https://github.com/user-attachments/assets/ecbe6a5f-c2e8-4427-9c1d-f8f123009d2e"
/>


## Before / After

![before /
after](https://raw.githubusercontent.com/Comfy-Org/ComfyUI_frontend/jaewon/fe-237-fix-honeytoast-badge-finished-opacity/.github/pr-images/fe-237-before-after.png)

## Repro
Cloud → trigger a model download → wait for completion → the `FINISHED`
badge is the same tone as the toast surface (see Slack thread
screenshots).

## Test plan
- [ ] Complete a model download in cloud and confirm the `FINISHED`
badge is clearly legible in both themes.
- [ ] File name + subtitle still appear dimmed to signal the row is
completed.
- [ ] Running / failed / pending states unchanged.

- Fixes
[FE-237](https://linear.app/comfyorg/issue/FE-237/fix-honeytoast-badge-text-color-for-finished-job-matches-background)
2026-05-03 08:40:27 +00:00
Christian Byrne
c955309b26 [chore] Update Comfy Registry API types from comfy-api@911406c (#11518)
## Automated API Type Update

This PR updates the Comfy Registry API types from the latest comfy-api
OpenAPI specification.

- API commit: 911406c
- Generated on: 2026-04-17T16:10:40Z

These types are automatically generated using openapi-typescript.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-11518-chore-Update-Comfy-Registry-API-types-from-comfy-api-911406c-3496d73d36508146a1e2e1ee90640fa4)
by [Unito](https://www.unito.io)

Co-authored-by: coderfromthenorth93 <213232275+coderfromthenorth93@users.noreply.github.com>
Co-authored-by: Alexander Brown <drjkl@comfy.org>
2026-05-03 01:01:41 -07:00
Comfy Org PR Bot
7abd9d12c8 chore(website): refresh Ashby roles snapshot (#11851)
Automated refresh of `apps/website/src/data/ashby-roles.snapshot.json`
from the Ashby job board API.

**Flow:**
1. `Release: Website` workflow ran (manual trigger).
2. This PR opens with the regenerated snapshot.
3. `CI: Vercel Website Preview` deploys a preview for review.
4. Merging to `main` triggers the production Vercel deploy.

The snapshot fallback in `apps/website/src/utils/ashby.ts` remains
intact: builds without `WEBSITE_ASHBY_API_KEY` continue to use the
committed snapshot.

Triggered by workflow run `25260868155`.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-11851-chore-website-refresh-Ashby-roles-snapshot-3546d73d365081579f98f13f7b58e611)
by [Unito](https://www.unito.io)

Co-authored-by: christian-byrne <72887196+christian-byrne@users.noreply.github.com>
2026-05-02 14:15:34 -07:00
Yourz
dd9cb42fa1 feat: add Run your first workflow button to home hero (#11848)
## Summary

Add an outline-style `BrandButton` to the right side of the home page
hero section linking to the workflows page.

## Changes

- **What**: 
- Added a `Run your first workflow` outline button below the subtitle in
`apps/website/src/components/home/HeroSection.vue`, linking to
`externalLinks.workflows`. Mirrors the button pattern from
`product/local/HeroSection.vue`.
- Added `hero.runFirstWorkflow` i18n entry (en + zh-CN) in
`apps/website/src/i18n/translations.ts`.

## Review Focus

- Confirmed alignment with design spec.

<img width="1505" height="776" alt="image"
src="https://github.com/user-attachments/assets/215e667d-1827-447b-99b8-eba8cb5ec7e3"
/>
<img width="335" height="700" alt="image"
src="https://github.com/user-attachments/assets/aeac0876-74c3-4e12-a4b3-203f9e541bc2"
/>


┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-11848-feat-add-Run-your-first-workflow-button-to-home-hero-3546d73d365081358d54eddfda71111e)
by [Unito](https://www.unito.io)

Co-authored-by: Amp <amp@ampcode.com>
2026-05-02 20:18:28 +00:00
pythongosssss
ccd19d8695 test: add metadata parser coverage (#11307)
## Summary
Adds tests for metadata parsers

## Changes

- **What**: 
- add test file generation script
- identified & fixed bug in webp exif parsing over-reading
- identified & fix bug in mp3/ogg parser where it would read from a
fixed position instead of relative, causing incorrect reads throwing
RangeError
- added catch in latent + json parsing to resolve errors

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-11307-test-add-metadata-parser-coverage-3446d73d36508108ac36dddcec0a54d4)
by [Unito](https://www.unito.io)

---------

Co-authored-by: GitHub Action <action@github.com>
2026-05-02 13:18:45 -07:00
Yourz
809fba7b36 feat: add Groove Jones customer story (#11849)
## Summary

Add a new customer story for Groove Jones — Crocs x NFL FOOH holiday
campaign for Dick's Sporting Goods, delivered with Comfy.

## Changes

- **What**:
- New entry in
[`apps/website/src/config/customerStories.ts`](https://github.com/Comfy-Org/ComfyUI_frontend/blob/feat/groove-jones-customer-story/apps/website/src/config/customerStories.ts)
registering slug `groove-jones` with cover image hosted on
`media.comfy.org/website/customers/groove-jones/`.
- Added `customers.story.groove-jones.{category,title,body}` and
`customers.detail.groove-jones.topic-1` … `topic-10` translations (en +
zh-CN) in
[`apps/website/src/i18n/translations.ts`](https://github.com/Comfy-Org/ComfyUI_frontend/blob/feat/groove-jones-customer-story/apps/website/src/i18n/translations.ts).
10 sections matching design sidebar: INTRO, THE OUTPUT, THE PROBLEM, HOW
COMFY SOLVED THE PROBLEM, BRAND-TRAINED LORAS, MULTI-MODEL
ORCHESTRATION, THE PIPELINE, VERSION CONTROL, FINISHING IN NUKE, THE
TAKEAWAY.
- Includes 2 pull quotes (Doug Hogan, Dale Carman), 1 final blockquote +
author card, and 3 inline images.
- Routes `/customers/groove-jones` and `/zh-CN/customers/groove-jones`
are auto-generated by `[slug].astro`.

## Review Focus

- Contributors author card uses `TBD` placeholder names/roles — to be
filled in.
- No `readMoreHref` set yet (no public blog post URL).
- All 4 images uploaded to
`gs://comfy-org-videos/website/customers/groove-jones/` and served via
`media.comfy.org`.

<img width="1000" height="535" alt="Kapture 2026-05-02 at 23 17 04"
src="https://github.com/user-attachments/assets/28654d24-0d49-4303-82ac-b6923cd6bc93"
/>


┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-11849-feat-add-Groove-Jones-customer-story-3546d73d36508128a64bd6809ad77447)
by [Unito](https://www.unito.io)

Co-authored-by: Amp <amp@ampcode.com>
2026-05-02 18:31:18 +00:00
Christian Byrne
df2ae6f2d0 fix(load3d): dispose THREE.Points GPU resources in clearModel() (#11836)
Fixes #11345

## Summary

`clearModel()` in `SceneModelManager` only traversed and disposed
`THREE.Mesh` instances, leaving `THREE.Points` objects (created by
`handlePLYModeSwitch()` for point-cloud mode) leaking GPU geometry and
material memory on repeated point-cloud loads/clears.

## Changes

- `SceneModelManager.ts`: extend the dispose traversal in `clearModel()`
to also handle `THREE.Points`, mirroring the pattern already used by
`removeAllMainModelsFromScene()`.
- `SceneModelManager.test.ts`: add regression test verifying
`geometry.dispose()` and `material.dispose()` are called for
`THREE.Points` children on `clearModel()`.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-11836-fix-load3d-dispose-THREE-Points-GPU-resources-in-clearModel-3546d73d365081718338e824bc3e737d)
by [Unito](https://www.unito.io)
2026-05-02 12:39:12 +00:00
Christian Byrne
3c7781190a feat(website): add llms.txt for GEO discovery by AI search agents (#11830)
## Summary

Adds `/llms.txt` at the apex following the [llms.txt
standard](https://llmstxt.org) — a curated, link-based markdown file
that tells LLM-based search agents (ChatGPT search, Perplexity, Claude
search, Google AI Overviews, etc.) what's most important on the site. It
complements `robots.txt` (crawler permissions) and `sitemap-index.xml`
(URL inventory) by giving AI agents a short, prose-friendly index they
can ingest into a context window.

## What's in the file

28 links across 6 sections:

- **Product** (6) — homepage, Local download, Cloud, Cloud pricing, API,
Enterprise
- **Workflows and Gallery** (2) — gallery + community workflows site
- **Customers and Case Studies** (5) — customers index + 4 named studios
(Series Entertainment, Moment Factory, Ubisoft Chord, Open Story
Movement)
- **Developers and Documentation** (4) — docs.comfy.org, ComfyUI repo,
Comfy-Org GitHub org, registry.comfy.org
- **Company** (6) — about, careers, contact, blog, privacy, terms
- **Optional** (5) — `zh-CN` locale variant, long-form enterprise case
studies, blog posts (de-prioritized per spec — agents can skip if
context-limited)

The intro paragraph names the four product surfaces (Local, Cloud, API,
Enterprise), the named customers, and the use-case industries (VFX &
animation, advertising, gaming, eCommerce/fashion) — so an agent that
ingests only the prose still gets the elevator pitch.

## Verification

- All 28 URLs verified live (`HTTP 200`) before commit.
- File is plain markdown — no build step. Astro/Vercel will serve it
from `apps/website/public/llms.txt` exactly as it serves `robots.txt`
(which lives in the same directory and ships at
`https://comfy.org/robots.txt`).
- Will verify on the Vercel preview deploy after this PR opens that
`curl -sI https://<preview>/llms.txt` returns `200` with a sensible
`content-type`. (`robots.txt` currently serves as `text/plain;
charset=utf-8` — `.txt` will likely do the same; that's fine for AI
agents.)

## Decisions

- **No `llms-full.txt` yet.** That variant inlines full prose of key
pages and requires curating substantive content. Deferred to a follow-up
— the marketing-site pages are mostly Vue-rendered hero/feature blocks
rather than long-form prose, so a meaningful `llms-full.txt` would need
either dedicated copy or a build step that flattens i18n strings +
section text. Tracking separately.
- **No comment line in `robots.txt`.** I considered adding a `# AI
agents: see /llms.txt` comment above the `Sitemap:` directive, but
decided against it: (a) the convention is to probe the well-known path
`/llms.txt` directly, not to discover it via robots.txt; (b)
`robots.txt` was just polished in #11823 with a deliberate compact
design and adding a non-standard comment would muddy that; (c) zero
implementations I checked actually parse robots.txt for llms.txt hints.
Easy to add later if needed.

## Context

Third of three follow-ups from the SEO/GEO sweep on 2026-05-02:

1. ~~Comfy-Router: add `X-Content-Type-Options: nosniff` to apex
security headers~~ (separate PR on `Comfy-Org/comfy-router`)
2. ~~Cloudflare: enable "Always Use HTTPS"~~ (dashboard toggle, no PR)
3. **This PR** — add `llms.txt` for GEO discovery

## Testing

- [x] All linked URLs return 200
- [x] File parses as valid markdown
- [ ] Preview deploy serves `/llms.txt` (will verify once preview is up)

┆Issue is synchronized with this [Notion
page](https://app.notion.com/p/PR-11830-feat-website-add-llms-txt-for-GEO-discovery-by-AI-search-agents-3546d73d365081a98c6bfc5301699f64)
by [Unito](https://www.unito.io)
2026-05-02 07:16:27 +00:00
Christian Byrne
167a1e6a0c fix(website): override X-Robots-Tag to index,follow on production deployment (#11831)
## P0 SEO fix — entire marketing site is currently `noindex`

A deep-audit sweep on **2026-05-02** confirmed that every page on
`comfy.org` (home, `/about`, `/pricing`, `/customers`, `/careers`, …) is
being served with:

```
x-robots-tag: noindex
```

This hides the entire marketing site from Google and other search
engines.

### Root cause

`comfy.org` reaches the Astro marketing site via a Cloudflare Worker
reverse proxy
([`comfy-router`](https://github.com/Comfy-Org/comfy-router)) that does:

```js
fetch('https://website-frontend-comfyui.vercel.app/...')
```

Per Vercel's KB article [Avoiding duplicate-content with vercel.app
URLs](https://vercel.com/kb/guide/avoiding-duplicate-content-with-vercel-app-urls):

> By default, Vercel adds an `X-Robots-Tag: noindex` HTTP header to all
deployments hosted on `vercel.app` URLs. […] This header tells search
engines like Google not to index these deployment URLs.

Because `website-frontend-comfyui.vercel.app` is **not** registered as a
Production custom domain on the Vercel project, Vercel applies that
header — and our Worker faithfully forwards it back to `comfy.org`
clients (and Googlebot).

### Fix

Vercel's documented workaround for proxied `*.vercel.app` setups is to
override the header via `vercel.json` ([blog post by Dan
Denney](https://www.dandenney.com/posts/front-end-dev/fixing-x-robots-nofollow-with-vercel/)):

```json
"headers": [
  {
    "source": "/(.*)",
    "has": [
      { "type": "host", "value": "website-frontend-comfyui.vercel.app" }
    ],
    "headers": [
      { "key": "X-Robots-Tag", "value": "index, follow" }
    ]
  }
]
```

### Why the `has` host filter

Critical scoping detail: without the `has` filter, the override would
also apply to PR-preview deployments at
`comfy-website-preview-pr-N.vercel.app`. We want previews to **stay**
`noindex` to avoid duplicate-content competition with production.

The filter pins the override to the production deployment hostname only.
The hostname matches `WEBSITE_ORIGIN` in `comfy-router/src/index.js`:

```js
const WEBSITE_ORIGIN = 'https://website-frontend-comfyui.vercel.app';
```

### Defense in depth

A parallel PR on `comfy-router` (TBD) will also strip `X-Robots-Tag:
noindex` at the Worker layer, so the public `comfy.org` response is
correct even if a future Vercel project change re-introduces the
upstream header.

### Verification (after merge + Vercel production deploy)

```bash
# Production should no longer be noindex
curl -sI https://comfy.org/ | grep -i x-robots-tag
curl -sI https://comfy.org/about | grep -i x-robots-tag
curl -sI https://comfy.org/pricing | grep -i x-robots-tag
# Expect: empty output, OR "x-robots-tag: index, follow"

# Direct Vercel production hostname
curl -sI https://website-frontend-comfyui.vercel.app/ | grep -i x-robots-tag
# Expect: "x-robots-tag: index, follow"

# Preview deploys must stay noindex (proves the host filter works)
curl -sI https://comfy-website-preview-pr-<N>.vercel.app/ | grep -i x-robots-tag
# Expect: "x-robots-tag: noindex"
```

### Pre-merge state (for the record)

```
$ curl -sI https://comfy.org/ | grep -iE 'x-robots-tag|x-served-by'
x-served-by: vercel-website
x-robots-tag: noindex
```

### Scope

Minimum delta — only `apps/website/vercel.json`. No other files touched.

┆Issue is synchronized with this [Notion
page](https://app.notion.com/p/PR-11831-fix-website-override-X-Robots-Tag-to-index-follow-on-production-deployment-3546d73d365081489b62e6633d25dfe5)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Christian Byrne <christian@comfy.org>
Co-authored-by: GitHub Action <action@github.com>
2026-05-01 23:03:25 -07:00
Alexander Brown
e4e1546458 test: add queue notification banners lifecycle browser tests (#11366)
*PR Created by the Glary-Bot Agent*

---

## Summary

- Adds `browser_tests/tests/queueNotificationBanners.spec.ts` covering
`useQueueNotificationBanners` composable E2E behavior
- Adds `data-testid="queue-notification-banner"` to
`QueueNotificationBannerHost.vue` for stable test targeting
- Registers the new test ID in `TestIds.queue.notificationBanner`

### Test coverage added (7 tests)

| Group | Tests | Behavior |
|---|---|---|
| Queuing lifecycle | 4 | `promptQueueing` → banner appears,
`promptQueued` upgrades to queued, batch plural text, requestId mismatch
doesn't upgrade |
| Auto-dismiss | 1 | Banner disappears after 4s timeout |
| FIFO queue | 1 | Second notification shows after first auto-dismisses
|
| Direct queued | 1 | `promptQueued` without prior `promptQueueing`
shows banner directly |

### Approach
Tests dispatch `promptQueueing`/`promptQueued` custom events directly
via `window.app.api.dispatchCustomEvent()` inside `page.evaluate()`,
matching how `app.ts` triggers these events during real queue
operations. This avoids needing a running execution pipeline while
exercising the full composable → component → DOM rendering chain.

### Verification
- TypeScript: zero errors
- ESLint: clean
- oxlint: clean
- oxfmt: formatted
- Playwright execution requires running ComfyUI backend (not available
in sandbox)

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-11366-test-add-queue-notification-banners-lifecycle-browser-tests-3466d73d36508172a7ffd3fe3b4fd925)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Glary-Bot <glary-bot@users.noreply.github.com>
Co-authored-by: Amp <amp@ampcode.com>
2026-05-02 05:40:45 +00:00
Christian Byrne
c1954028d1 feat(ci): add Release:Website workflow to refresh Ashby snapshot (#11829)
## Summary

Adds a unified manual-trigger release flow for the `apps/website`
package
(careers/marketing site at comfy.org), mirroring how main-app releases
work.

**User-facing flow:**

```
workflow_dispatch ──► fresh Ashby pull ──► auto-PR with snapshot bump
                                                      │
                                                      ▼
                              existing CI / Vercel preview deploy
                                                      │
                                                      ▼
                                  human merges ──► auto prod deploy
```

The careers data on comfy.org comes from Ashby and is fetched at build
time by
`apps/website/src/utils/ashby.ts`. Without `WEBSITE_ASHBY_API_KEY`, the
build
falls back to a committed snapshot at
`apps/website/src/data/ashby-roles.snapshot.json`. That snapshot has
been
going stale because no CI workflow was passing the API key. This PR
fixes
both: a manual refresh workflow + day-to-day secrets wiring.

## Files

**Added**
- `.github/actions/ashby-pull/action.yaml` — composite action that runs
`pnpm --filter @comfyorg/website ashby:refresh-snapshot` with the Ashby
  secrets piped in. Uses the existing `setup-frontend` composite for
  pnpm/Node setup.
- `.github/workflows/release-website.yaml` — `workflow_dispatch`-only
  release workflow. Checks out `main`, refreshes the snapshot via the
  composite action, opens a PR labelled `Release:Website` via
  `peter-evans/create-pull-request@c0f553fe…` (the same SHA pin used by
  `release-version-bump.yaml`).

**Modified**
- `.github/workflows/ci-website-build.yaml` — adds
`WEBSITE_ASHBY_API_KEY`
  and `WEBSITE_ASHBY_JOB_BOARD_NAME` env to the `Build website` step.
- `.github/workflows/ci-vercel-website-preview.yaml` — adds the same env
  to both `vercel build` steps (preview + production).

## Snapshot fallback preserved

`apps/website/src/utils/ashby.ts` keeps using the committed snapshot
when
the API key is absent (e.g. fork PRs, secret rotation). Verified
locally:

```
$ pnpm --filter @comfyorg/website ashby:refresh-snapshot
Snapshot refresh aborted. Outcome: stale; reason: missing WEBSITE_ASHBY_API_KEY...
```

The release workflow surfaces this as a job failure, which is the
desired
behavior for a manual release trigger.

## Validation

- `yamllint --config-file .yamllint` on all changed YAML — clean
- `./scripts/cicd/check-yaml.sh` — clean
- `pinact run --check` on new files — clean (matches `.pinact.yaml`
policy)
- `pnpm install --frozen-lockfile` — works with `.nvmrc` Node 24
- Husky pre-commit hooks (eslint + typecheck + lint-staged) passed

## Caveats

- **Cannot fully end-to-end test until merged.** `workflow_dispatch`
  workflows only run from branches that exist on `main`. The first
  manual run can only happen after this PR lands. The pieces that
  *can* be tested pre-merge — yamllint, pinact pin validation, and
  the existing `CI: Website Build` / `CI: Vercel Website Preview`
  workflows now picking up the secret — will run on this PR.
- **`Release:Website` label needs to be created** in the repo before
the auto-PR step will successfully apply it.
`peter-evans/create-pull-request`
  will warn but not fail if the label doesn't exist. Suggested color:
  `#4f6ef5` (matches `cloud/*` family in `release-branch-create.yaml`).
- The release workflow uses `secrets.PR_GH_TOKEN` (matching
  `release-version-bump.yaml`) so the auto-PR can be authored by a
  PAT and trigger downstream CI workflows. Without `PR_GH_TOKEN` it
  will fall back behavior is up to GitHub Actions defaults — confirm
  the secret exists before the first run.

## Context

Came out of work on `comfy-router#22` + `ComfyUI_frontend#11823`
(comfy.org/countdown subpage / website refresh). Discovered the
8+-day-stale snapshot while auditing the website build path.

┆Issue is synchronized with this [Notion
page](https://app.notion.com/p/PR-11829-feat-ci-add-Release-Website-workflow-to-refresh-Ashby-snapshot-3546d73d3650811eb300d8bcb593c652)
by [Unito](https://www.unito.io)
2026-05-02 05:31:00 +00:00
Christian Byrne
5cad2c952b refactor+test: extract useSubscriptionCheckout composable, rewrite tests (#11396)
## Summary

Adds 20 component tests for
`SubscriptionRequiredDialogContentWorkspace.vue` covering:

- **Initial rendering**: pricing table display, close/back button
visibility, out_of_credits reason message
- **Close button**: calls onClose callback
- **Subscribe click flow**: pricing→preview transitions (new
subscription & upgrade), error toasts for disallowed/missing/failed
previews, monthly billing cycle
- **Back button**: returns from preview to pricing step
- **Add credit card**: handles subscribed status (success toast +
close), needs_payment_method (opens Stripe URL), error state
- **Confirm transition**: success path with close emit, error toast on
failure
- **Resubscribe**: success path with toast + close, error toast on
failure

## Testing

```bash
pnpm test:unit -- src/platform/workspace/components/SubscriptionRequiredDialogContentWorkspace.test.ts
```

All 20 tests pass. Quality gates (typecheck, lint, format, knip) pass.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-11396-test-add-component-tests-for-SubscriptionRequiredDialogContentWorkspace-3476d73d36508156a218dcb67a2a334e)
by [Unito](https://www.unito.io)
2026-05-02 04:50:44 +00:00
Daxiong (Lin)
e356addeb6 feat: add model links for default workflow (#11308)
We now support detecting the missing models when loading the workflow.
But the default workflow didn't include an embedded model link,

so users don't know where to download the model or which one to use.

Users will see an error when loading the default workflow every time, so
I updated it to include the model link.

Before
<img width="1920" height="1050" alt="image"
src="https://github.com/user-attachments/assets/08774480-78ae-41b4-85bd-64b431079ec1"
/>

After

<img width="1920" height="1050" alt="image"
src="https://github.com/user-attachments/assets/dcec5a02-94ad-416f-9881-d761f4137fbd"
/>

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-11308-feat-add-model-links-for-default-workflow-3446d73d365081188978e1d313c38ffe)
by [Unito](https://www.unito.io)

---------

Co-authored-by: GitHub Action <action@github.com>
2026-05-02 04:24:08 +00:00
Christian Byrne
e831daae59 feat(website): point robots.txt at /sitemap-index.xml + AI crawler rules (#11823)
## Summary

Once
[comfy-router#22](https://github.com/Comfy-Org/comfy-router/pull/22)
ships, `comfy.org/sitemap-index.xml` will return a unified index
aggregating both the website (38 URLs) and workflow-templates sitemaps.
This PR:

1. Reverts `Sitemap:` back to `/sitemap-index.xml` (was `/sitemap-0.xml`
in #11802 as a workaround for the 404).
2. Adds explicit allow records for 21 search and AI/LLM crawlers
(GPTBot, ChatGPT-User, OAI-SearchBot, Google-Extended, ClaudeBot,
Claude-Web, anthropic-ai, PerplexityBot, Perplexity-User,
Applebot-Extended, Bytespider, Amazonbot, CCBot, Meta-ExternalAgent,
Meta-ExternalFetcher, Diffbot, etc.).
3. Adds `Disallow:` for `/_astro/`, `/_website/`, `/_vercel/` — Vercel
build artifacts that aren't useful to crawl.

## Why granular UAs

Stacked `User-agent:` records (per [RFC 9309
§2.2](https://datatracker.ietf.org/doc/html/rfc9309#section-2.2)) share
one rule block. Listing each bot explicitly:

- Signals intent to AI bots that look for their UA in robots.txt before
crawling more aggressively.
- Surfaces our crawl policy clearly to anyone inspecting the file.
- Lets us add per-bot Disallows in future without restructuring.

## Merge order

⚠️ **Do NOT merge until comfy-router#22 is deployed to production.**
Until then, `/sitemap-index.xml` returns 404 and this PR would re-break
the issue PR #11802 patched. Verification:

```bash
curl -sI https://comfy.org/sitemap-index.xml
# expect: HTTP/2 200, x-served-by: worker-sitemap-index
```

Once that returns 200, this is safe to merge.

## Verification (after merge + deploy)

```bash
# robots.txt is served and points at the unified index
curl -s https://comfy.org/robots.txt | grep '^Sitemap:'
# → Sitemap: https://comfy.org/sitemap-index.xml

# Each AI crawler can fetch it
for ua in 'GPTBot/1.0' 'ClaudeBot/1.0' 'PerplexityBot/1.0' 'Google-Extended' 'Applebot-Extended'; do
  curl -s -o /dev/null -w "$ua → %{http_code}\n" -A "$ua" https://comfy.org/robots.txt
done

# Sitemap is reachable from robots.txt
SITEMAP=$(curl -s https://comfy.org/robots.txt | awk -F': ' '/^Sitemap:/ {print $2}')
curl -s "$SITEMAP" | xmllint --noout - && echo "valid XML"
```

## Linear / closes

- Closes FE-437 (AI crawler rules)
- Updates FE-432 — the robots.txt change in #11802 was a workaround
that's no longer needed once #22 ships

┆Issue is synchronized with this [Notion
page](https://app.notion.com/p/PR-11823-feat-website-point-robots-txt-at-sitemap-index-xml-AI-crawler-rules-3546d73d3650811dbceedd06c00db444)
by [Unito](https://www.unito.io)
2026-05-01 21:04:45 -07:00
Robin Huang
96575fcec9 feat: redesign cloud onboarding survey for ICP and persona signal (#11628)
## Summary

Replaces the 4-step Cloud onboarding survey with a 7-step flow that
captures both ICP attributes and user persona dimensions. The survey
questions are now populated dynamically from remoteConfig.

## Changes

- **What**: New survey questions — Usage, Familiarity, Role, Team size,
Industry, Making, Source. Role / Team size / Industry are gated to
"Work" usage; Education users see a Student / Educator short list for
Role. Most option lists are randomized per visit (familiarity and team
size stay ordered as ordinals). \`SurveyResponses\` extended with
optional \`usage\`, \`role\`, \`teamSize\`, \`source\` fields.
- **Breaking**: None — \`useCase\` and \`workflowRelationship\` remain
optional in the type and existing telemetry normalization keeps working
unchanged.

## Review Focus

- The \`role\` step has a function-form \`options\` so the list can swap
based on \`usage\`. \`steps\` is a computed that filters by
\`showWhen()\` and resolves the option function — verify reactivity when
\`usage\` changes.
- Changing \`usage\` clears the previously-picked \`role\` via a watcher
to prevent a stale value from carrying over between Work / Education
modes.
- Per-visit shuffle is stable: option lists are passed through
\`randomize()\` once at module load, not on every render.

## Screenshots


https://github.com/user-attachments/assets/3602a388-50dc-401e-ada9-ea9016c5052d


┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-11628-feat-redesign-cloud-onboarding-survey-for-ICP-and-persona-signal-34d6d73d365081f4a792cfe76a987ffb)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Dante <bunggl@naver.com>
2026-05-01 23:25:17 +00:00
Kelly Yang
e7e1ae25a6 fix(load3d): suppress error toast on 404 when loading output model file (#11807)
## Summary

- Adds `silentOnNotFound` option to `LoadModelOptions` interface,
threaded through `Load3d.loadModel` → `LoaderManager.loadModel`
- 404 errors (detected via message text or `response.status`) are
silently swallowed when `silentOnNotFound: true`; all other errors still
surface a toast
- Sets `silentOnNotFound: true` for output-folder loads in `load3d.ts`
and `saveMesh.ts` — covers shared workflows opened on a machine that
never ran them

## Test plan

- [x] `LoaderManager.test.ts` — 40 unit tests covering 404 suppression,
non-404 still toasts, stale load handling
- [x] `Load3DConfiguration.test.ts` — 4 unit tests verifying
`silentOnNotFound` propagates correctly through `configureForSaveMesh`
and `configure`
- [x] `load3d.spec.ts` — 2 E2E tests: 404 → no toast, 500 → toast
appears

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Changes error-handling behavior in the 3D model loading pipeline and
extends method signatures/options; risk is mainly missed call sites or
incorrectly classifying non-404 errors as 404 and hiding real failures.
> 
> **Overview**
> Prevents noisy user-facing toasts when an *output* 3D model referenced
by `Preview3D`/`SaveGLB` is missing locally by adding a
`silentOnNotFound` flag and suppressing the "Error loading model" toast
specifically for HTTP 404 failures.
> 
> Threads the new `LoadModelOptions` through `Load3d.loadModel` →
`LoaderManager.loadModel` and updates `Load3DConfiguration`/callers to
opt in for output-folder loads, with new unit + Playwright coverage (404
stays silent, non-404 still toasts).
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
049f75ef60. 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-11807-fix-load3d-suppress-error-toast-on-404-when-loading-output-model-file-3536d73d36508129ac0de1d5b081dcf0)
by [Unito](https://www.unito.io)
2026-05-01 18:49:31 -04:00
Yourz
4ed00cec08 update: robots.txt to point to /sitemap-0.xml (#11802)
## Summary

<!-- One sentence describing what changed and why. -->

robots.txt at [comfy.org](https://comfy.org/) references
/sitemap-index.xml which returns 404. The actual working sitemap is at
/sitemap-0.xml (200, 38 URLs). This blocks search engines from
discovering the sitemap.

## Changes

- **What**: <!-- Core functionality added/modified -->
  - Update robots.txt to point to /sitemap-0.xml, OR

## Review Focus

<!-- Critical design decisions or edge cases that need attention -->

<!-- If this PR fixes an issue, uncomment and update the line below -->
<!-- Fixes #ISSUE_NUMBER -->

## Screenshots (if applicable)

<!-- Add screenshots or video recording to help explain your changes -->

┆Issue is synchronized with this [Notion
page](https://app.notion.com/p/PR-11802-update-robots-txt-to-point-to-sitemap-0-xml-3536d73d365081bb9545eb96dd1e8025)
by [Unito](https://www.unito.io)
2026-05-01 19:36:22 +00:00
Alexander Brown
f566abdd6e ci: extract changes-filter composite action; fix docs-only PR stall (#11785)
## Summary

Extract a `.github/actions/changes-filter` composite action and adopt it
across path-gated CI workflows, fixing the docs-only PR stall and
removing duplicated `paths:` / `paths-ignore:` filtering across 8
workflows.

## Background

Docs-only PRs stalled on required status checks because workflows using
`paths-ignore: ['**/*.md']` never created a check run, while branch
protection still required it. Observed on #11776 (the `test` check from
`ci-tests-unit.yaml` never appeared).

The fix pattern: keep the workflow triggered, gate downstream jobs on a
`changes` job whose outputs are computed from a path filter. Skipped
jobs count as passing under branch protection.

## What the action emits

| Output | Meaning |
|---|---|
| `should-run` | Any file outside `apps/`, `docs/`, `.storybook/`,
`**/*.md` changed. |
| `app-website-changes` | Shared deps or `apps/website/**` changed. |
| `app-desktop-changes` | Shared deps or `apps/desktop-ui/**` changed. |
| `app-frontend-changes` | Shared deps or `src/**` changed. |
| `packages-changes` | Shared deps or `packages/**` changed. |
| `storybook-changes` | Shared deps or `.storybook/**` changed. |
| `docs-changes` | `docs/**` or any `**/*.md` changed (deps NOT folded
in). |
| `dependency-changes` | Root `package.json`, `pnpm-lock.yaml`, or
`pnpm-workspace.yaml` changed. |

Shared deps are folded into every `app-*`, `packages-changes`, and
`storybook-changes` output so a lockfile bump correctly invalidates each
granular gate. Outputs default to `'true'` for non-`pull_request` events
to avoid the silent-skip footgun on push / merge_group.

## Workflows migrated

| Workflow | Gate | Notes |
|---|---|---|
| `ci-tests-unit.yaml` | `should-run` | Required check (`test`). Fixes
the original stall. |
| `ci-tests-e2e.yaml` | `should-run` | Required check (`e2e-status`).
Replaces inline filter. |
| `ci-perf-report.yaml` | `should-run` | Removes `paths-ignore`. |
| `ci-website-build.yaml` | `app-website-changes \|\| packages-changes`
| Refactor — not a required check, but moves to job-level gating. Filter
scope broadens from `packages/{design-system}` to all `packages/**`
(strictly safer). |
| `ci-website-e2e.yaml` | `app-website-changes \|\| packages-changes` |
Same restructure; `post-starting-comment` also gated to avoid spurious
"tests are running" when E2E is skipped. |
| `ci-dist-telemetry-scan.yaml` | `should-run` | New gate; was
previously running on every PR including docs-only. |
| `ci-oss-assets-validation.yaml` | `should-run` | Same. |
| `ci-size-data.yaml` | `should-run` | Preserves existing repository
guard on the new `changes` job. |
| `ci-tests-storybook.yaml` | `storybook-changes \|\|
app-frontend-changes \|\| packages-changes` | Gates 4 of 6 jobs.
`deploy-production` (push to main) left ungated;
`update-comment-with-chromatic` cascades naturally. |

## Branch protection (verified)

Required status checks on `main` and `core/**`/`cloud/**`: `test`,
`lint-and-format`, `e2e-status`. Only `test` and `e2e-status` use the
composite — `lint-and-format` correctly stays unfiltered (must run on
docs/apps too). The other 6 migrations are refactor wins (less wasted CI
on docs/apps-only PRs), not stall fixes.

## Changes

- **What**: New `.github/actions/changes-filter` composite + 8 workflow
migrations to consume it.
- **Breaking**: None.
- **Dependencies**: New pin on `dorny/paths-filter@de90cc6` — already
covered by `ci-validate-action-pins`.

## Review Focus

- The `should-run` filter excludes `.storybook/**` (granular
`storybook-changes` covers it instead). Storybook's gate combines all
three: `storybook-changes || app-frontend-changes || packages-changes`.
- Two `dorny/paths-filter` steps inside the composite —
`predicate-quantifier=every` is required for the negated globs in
`should-run` but breaks the multi-pattern OR filters.
- The website filter scope intentionally broadens from
`packages/{design-system,tailwind-utils}/**` to all `packages/**` for
consistency and safety.

Fixes #11776

┆Issue is synchronized with this [Notion
page](https://app.notion.com/p/PR-11785-ci-extract-changes-filter-composite-action-fix-docs-only-PR-stall-3526d73d36508172a1d7fe8c30fa6453)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Amp <amp@ampcode.com>
2026-05-01 18:06:29 +00:00
pythongosssss
3c5695fd42 test: add Vue node error/validation ring e2e coverage (#11727)
## Summary

Add additional test coverage for vue node errors

## Changes

- **What**: 
- add tests for showing error on missing node, execution error,
validation failure & resolved on fix
- move ErrorsTabHelper to fixtures dir & update refs
- add SLOW_MO env var for headed local tests

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-11727-test-add-Vue-node-error-validation-ring-e2e-coverage-3506d73d365081069ff8f70f7970dd55)
by [Unito](https://www.unito.io)
2026-05-01 09:35:29 +00:00
Dante
4fff0c4b49 fix: report total file count, not job count, in ZIP export toast (#11737)
## Summary

ZIP export toast now reports the total number of files instead of the
number of selected jobs when any selected job has multiple outputs.

## Changes

- **What**: In `downloadMultipleAssetsAsZip`
(`src/platform/assets/composables/useMediaAssetActions.ts`), compute
`fileCount` by summing each asset's `outputCount` metadata (fallback 1)
and pass it to `mediaAsset.selection.exportStarted` instead of
`assets.length`. The existing i18n string already handles `file`/`files`
plural.
- **Tests**: 3 new unit tests in `useMediaAssetActions.test.ts` covering
multi-output, single-output fallback, and mixed selections. The
`useToast` and `useI18n` mocks were lifted to hoisted refs so toast call
args are assertable.

## Review Focus

- Reduce uses `count > 1 ? count : 1`, mirroring the
`hasMultiOutputJobs` gate above so a known `outputCount === 1` is still
counted as 1 file (no double-counting).
- Only `downloadMultipleAssetsAsZip` is touched; OSS individual-download
path and direct-download path are unchanged.

Fixes #11736

┆Issue is synchronized with this [Notion
page](https://app.notion.com/p/PR-11737-fix-report-total-file-count-not-job-count-in-ZIP-export-toast-3516d73d3650811ba78dfdb0a0ae8ea1)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Alexander Brown <drjkl@comfy.org>
2026-05-01 09:28:01 +00:00
pythongosssss
69dca2d600 test: add test coverage for workflow save settings (#11763)
## Summary

Add tests for autosave/delay/node sort on save

## Changes

- **What**: 
- add tests

┆Issue is synchronized with this [Notion
page](https://app.notion.com/p/PR-11763-test-add-test-coverage-for-workflow-save-settings-3516d73d365081d1b57bc1cf4b2e2ece)
by [Unito](https://www.unito.io)
2026-05-01 09:09:31 +00:00
Kelly Yang
004530b23a fix: search bar layout and autocomplete clipping on Desktop at small sizes (#11713)
## Summary

Fixes two visual bugs in the Desktop app at small window sizes: the
search bar getting pushed/clipped in modal headers, and autocomplete
suggestion dropdowns being cut off by `overflow-hidden` ancestors.

## Changes

- **`SearchAutocomplete.vue`**: Wrap `ComboboxContent` in
`ComboboxPortal` so the suggestions dropdown teleports to `<body>`,
escaping `overflow-hidden` ancestors (fixes z-index clipping in Manager
dialog and other modals using `BaseModalLayout`)
- **`BaseModalLayout.vue`**: Replace `shrink-0` with `min-w-0` on the
header content container so the search bar can shrink at narrow window
sizes instead of overflowing and being clipped by the modal root's
`overflow-hidden`
- **`GraphCanvas.vue`**: Fix dead code where the native drag
(`app-drag`) div was nested inside a `v-if="workflowTabsPosition ===
'Topbar'"` block with its own mutually exclusive condition — move it
before the block and add `pointer-events-auto` so Desktop window
dragging works when tabs are in Sidebar position

## Why no E2E tests

- **`SearchAutocomplete` portal**: The fix is structural (teleport to
`<body>`). A meaningful regression test would require opening the
Manager dialog with a real or mocked extension list — that is a
substantial standalone effort tracked in #11714.
- **`BaseModalLayout` header shrink**: A viewport-resize assertion would
test CSS layout behaviour, not application logic; it would be fragile
and low-value.
- **`GraphCanvas` app-drag**: Desktop/Electron-only.
`-webkit-app-region: drag` cannot be exercised in headless Chromium.

Unit tests for `SearchAutocomplete` cover the new code paths (portal
rendering, suggestion display, item selection).

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> Low risk UI-only changes: adjusts layout CSS and combobox rendering
via `ComboboxPortal`, plus adds unit tests; no business logic or data
flow changes.
> 
> **Overview**
> Fixes small-window Desktop UI issues where modal-header search inputs
could be clipped and autocomplete dropdowns could be cut off by
`overflow-hidden` ancestors.
> 
> `SearchAutocomplete` now renders its suggestions list inside a
`ComboboxPortal` (teleporting the popper content outside clipping
containers) and adds a focused unit test suite covering empty/non-empty
suggestions, selection behavior, and `optionLabel` handling.
> 
> `BaseModalLayout` tweaks header flexbox constraints (`min-w-0` on the
header content container) to allow the search bar to shrink instead of
overflowing.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
fd32d960f9. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Co-authored-by: Benjamin Lu <benjaminlu1107@gmail.com>
2026-04-30 23:22:04 -07:00
Alexander Brown
73d4e24ffa revert: roll back #10849 + #11697 (per-instance promoted widget values) (#11790)
## Summary

Reverts #10849 (per-instance promoted widget value storage) and its
companion test-pinning PR #11697. The fix in #10849 caused regressions
in promoted-widget serialization (notably the Z-Image-Turbo template,
see #10146 follow-up). A replacement fix is being developed on
`fix/subgraph-promoted-widget-inline-state` and will land separately.

## Changes

- **Revert #11697** — drops the `it.fails`-marked tests that pin the
#10849 corruption symptom. With #10849 reverted, those markers would
falsely flip to passing.
- **Revert #10849** — removes per-instance `_instanceWidgetValues` map,
`_pendingWidgetsValues` configure-time hydration, the `widgets_values`
write path in `SubgraphNode.serialize()`, the `sourceSerialize` field on
`PromotedWidgetView`, the multi-instance Vitest suite, and the
multi-instance E2E test + asset.
- **Conflict resolution** in
`browser_tests/tests/subgraph/subgraphSerialization.spec.ts`: kept the
restored test coverage from #11579 (which is post-#10849 on main) and
removed only the now-unreachable multi-instance test, its helper, and
its workflow constant. Auto-merge with #11698 (`incrementVersion`) and
#11699 (ID type aliases) was clean.

## Review Focus

- Confirm no other on-main code path has come to depend on
`PromotedWidgetView.sourceSerialize` or
`SubgraphNode._instanceWidgetValues` since #10849 (grep is clean
locally).
- Confirm we want to land this revert before the replacement fix on
`fix/subgraph-promoted-widget-inline-state` is ready — this leaves the
original #10146 (multi-instance widget value collision) unfixed in the
meantime.
- The retained #11579 test coverage now exercises pre-#10849 behavior;
some of those assertions were written expecting the #10849 code path. CI
will surface any that need adjustment.

┆Issue is synchronized with this [Notion
page](https://app.notion.com/p/PR-11790-revert-roll-back-10849-11697-per-instance-promoted-widget-values-3536d73d3650814094abd58b6b712d8d)
by [Unito](https://www.unito.io)
2026-05-01 03:37:20 +00:00
Kelly Yang
09790bd7f3 test: add unit tests for useLayoutMutations (#11313)
## Summary
A follow-up PR of
https://github.com/Comfy-Org/ComfyUI_frontend/issues/11106.
This PR only focus on `layoutMutations.ts`.
`layoutMutations.ts` is the central API for all node layout mutations in
the Vue renderer. It previously had zero test coverage despite
containing non-trivial logic such as guard clauses, ID normalization,
and z-index scanning. This PR addresses issue #11106 to prevent silent
regressions in node positioning and lifecycle.

## What was tested and how
All tests use the real `layoutStore` singleton (no mocks).
`initializeFromLiteGraph` resets node state before each test, and
results are verified via `getNodeLayoutRef().value`.

| Method | Tests | Logic covered |
| :--- | :--- | :--- |
| `moveNode` | 3 | **Guard** (missing node -> no-op); position written
to store; numeric ID coerced to string |
| `resizeNode` | 2 | **Guard**; size written to store |
| `setNodeZIndex` | 2 | **Guard**; zIndex written to store |
| `createNode` | 1 | Node becomes readable with the provided position
and size |
| `deleteNode` | 2 | **Guard**; node removed from store |
| `batchMoveNodes` | 4 | Empty array -> no-op; multiple nodes updated
**atomically**; existing size preserved; missing nodes skipped while
valid ones still update |
| `bringNodeToFront` | 1 | Target node ends up with a **higher zIndex**
than all other nodes |

## What was not tested and why

| Method | Reason skipped |
| :--- | :--- |
| `createLink` / `deleteLink` | `layoutStore` exposes no public API to
query link existence by ID; methods contain no logic beyond a straight
`applyOperation` call. |
| `createReroute` / `deleteReroute` / `moveReroute` | Same reason as
above. |
| `setSource` / `setActor` | Single-line delegation to `layoutStore`; no
logic to test. |
| Default value behavior in `createNode` | Avoids "change-detector"
tests; asserting hardcoded defaults adds no regression value and would
block valid product changes. |

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> Test-only changes; no production logic is modified. Main risk is
potential flakiness due to reliance on singleton store state across
tests.
> 
> **Overview**
> Adds a new `layoutMutations.test.ts` Vitest suite that exercises
`useLayoutMutations` against the real `layoutStore` singleton
initialized from LiteGraph data.
> 
> Tests cover no-op guard clauses for missing nodes, node ID
normalization, position/size/z-index updates, node create/delete
behavior, `batchMoveNodes` semantics (empty input, skipping missing
nodes, preserving size), and `bringNodeToFront` z-index promotion
relative to other nodes.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
bb92de0b5b. 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-11313-test-add-unit-tests-for-useLayoutMutations-3446d73d36508134a289d6147244e710)
by [Unito](https://www.unito.io)
2026-05-01 03:06:29 +00:00
Kelly Yang
dafb944c3b refactor: replace PrimeVue InputText/Textarea with project UI components (#11324)
## Summary
Fix part of the
#https://github.com/Comfy-Org/ComfyUI_frontend/issues/11092
A total of 4 `// @ts-expect-error` directives were removed across 3
files — all caused by PrimeVue's legacy `$el` access pattern (`const
inputElement = inputRef.value.$el`) — by replacing PrimeVue with
Reka-based UI components. 1 corresponding unit test file was added.

## Changes
### `src/components/ui/input/Input.vue`
- **Exposed APIs**: Extended `defineExpose` to include `blur()` and
`setSelectionRange()`. This allows parent components to programmatically
control input behavior without direct DOM manipulation.

### `src/components/ui/textarea/Textarea.vue`
- **Exposed APIs**: Added `focus()` via `defineExpose`.
- **Cleanup**: Removed redundant attribute spreading (`...restAttrs`) to
lean on Vue’s default `$attrs` inheritance, making the component more
predictable.

---

## Refactored Feature Components

### `WidgetMarkdown.vue` (Note/Markdown Widgets)
- **Dependency Swap**: Replaced `primevue/textarea` with local
`Textarea.vue`.
- **Logic Simplification**: Simplified focus logic from
`textareaRef.value?.$el?.focus()` to a typed
`textareaRef.value?.focus()`.
- **Code Style**: Converted arrow functions to function declarations and
removed redundant section comments.

### `PromptDialogContent.vue` (Generic Prompt Dialogs)
- **Component Update**: Replaced PrimeVue `FloatLabel` and `InputText`
with a native `<label>` and local `Input.vue`.
- **Vue 3.5 Adoption**: Implemented **Reactive Destructuring** for
props.
- **Conflict Resolution**: Renamed internal `onConfirm` handler to
`handleConfirm` to prevent collision with destructured props.

### `EditableText.vue` (Node Titles & Sidebar Items)
- **Style Modernization**: Removed `<style scoped>` block in favor of
**Tailwind CSS** utility classes (e.g., `inline`, `w-full`).
- **Clean Implementation**: Replaced PrimeVue PassThrough (`:pt`) logic
with standard `@blur` and `v-bind` attributes.

---

## Testing & Quality Assurance

### Updated Tests
- **Redundancy Removal**: Cleaned up `EditableText.test.ts` and
`WidgetMarkdown.test.ts` by removing unused PrimeVue global
registrations. All 34 existing behavioral tests remain passing.

### New Coverage
- **`PromptDialogContent.test.ts`**: Added 3 new tests to verify:
    1. Correct initialization with `defaultValue`.
    2. Value persistence when clicking the Confirm button.
    3. Form submission via the `Enter` key.

---

## Manual Test Screenshot
All functions have passed testing.
 
<img width="594" height="530" alt="test5"
src="https://github.com/user-attachments/assets/46a6b3b2-1855-414e-ac78-65668052ce50"
/>
<img width="1190" height="1074" alt="test4"
src="https://github.com/user-attachments/assets/89aa61ab-9401-44c2-9eae-9ca8761df675"
/>
<img width="1154" height="1028" alt="test3"
src="https://github.com/user-attachments/assets/3f63cfdf-8fbd-4dd3-9e42-dbebe4d8d421"
/>

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Moderate risk because it swaps underlying input/textarea components
and ref handling (focus/blur/selection) in interactive UI paths
(editable labels, prompt dialogs, markdown editor), which could subtly
change keyboard/blur behavior.
> 
> **Overview**
> Refactors several Vue components to stop using PrimeVue
`InputText`/`Textarea` (and `$el` access) in favor of the project’s
`Input`/`Textarea` components, updating bindings/events and Tailwind
classes accordingly.
> 
> Extends the shared `Input` to expose `blur`, `setSelectionRange`, and
`selectAll`, and updates `Textarea` to expose `focus`, enabling callers
to manage focus/selection without DOM internals.
> 
> Adds a new unit test suite for `PromptDialogContent` and simplifies
existing tests by removing PrimeVue plugin/component setup; the groups
e2e test replaces a screenshot assertion with a functional visibility
check for the new title input.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
9c97314d59. 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-11324-refactor-replace-PrimeVue-InputText-Textarea-with-project-UI-components-3456d73d36508109a18bc97a7d0487a7)
by [Unito](https://www.unito.io)
2026-05-01 03:06:19 +00:00
Christian Byrne
d429d481e8 test: use real vue-i18n plugin in useReconnectingNotification tests (#11386)
## Summary

Replace `vi.mock('vue-i18n')` stub with a real `createI18n` plugin
instance in `useReconnectingNotification` tests.

## Changes

- **What**: Add `setupComposable()` helper that renders a wrapper
component via `@testing-library/vue` with a real `createI18n` plugin.
Assertions now check translated values
(`'Reconnecting'`/`'Reconnected'`) instead of raw i18n keys. Removes the
brittle `vi.mock('vue-i18n')` stub.

## Review Focus

Straightforward test-only change — the composable requires a component
setup context for `useI18n()`, so we render a thin wrapper via
`@testing-library/vue` with the i18n plugin installed.

Fixes #11153

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-11386-test-use-real-vue-i18n-plugin-in-useReconnectingNotification-tests-3476d73d3650814ba70eea6df91c8bbe)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Alexander Brown <drjkl@comfy.org>
2026-05-01 01:55:20 +00:00
Comfy Org PR Bot
a9aae6af4a 1.44.15 (#11787)
Patch version increment to 1.44.15

**Base branch:** `main`

┆Issue is synchronized with this [Notion
page](https://app.notion.com/p/PR-11787-1-44-15-3536d73d3650814da3a6dfe300ff559c)
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.44.15
2026-05-01 01:32:05 +00:00
jaeone94
46ba65e25c fix: hide advanced footer button on collapsed Vue nodes (#11778)
Before 


https://github.com/user-attachments/assets/82e323d7-0c9b-4303-81eb-87cb7b62f1c1


After 


https://github.com/user-attachments/assets/56f1db45-1b5d-47a9-9960-9a73deb68e78

Fixes the Vue node footer so the Advanced button only appears while a
node is expanded.

Root cause: `showAdvancedInputsButton` only checked for advanced widgets
and the global setting, so collapsed nodes with advanced widgets could
still render the Advanced tab, including the combined error + advanced
footer state.

Changes:
- Return `false` for advanced footer visibility when the Vue node is
collapsed.
- Add regression coverage for collapsed advanced nodes and collapsed
error + advanced nodes.

Validation:
- `pnpm test:unit --
src/renderer/extensions/vueNodes/components/LGraphNode.test.ts`
- Commit hook: format, stylelint, oxlint, eslint, `pnpm typecheck`
- Push hook: `pnpm knip`

┆Issue is synchronized with this [Notion
page](https://app.notion.com/p/PR-11778-codex-Hide-advanced-footer-on-collapsed-Vue-nodes-3526d73d365081d79399ddda40f2a7f7)
by [Unito](https://www.unito.io)
2026-05-01 01:00:14 +00:00
jaeone94
11432f7d0e refactor: extract missing model refresh pipeline (#11751)
## Summary

Extracts the missing-model pipeline orchestration out of `ComfyApp` and
into an app-independent platform module, while tightening the
workflow-flattening type boundary that refresh needs when rescanning the
live LiteGraph graph.

This PR is intentionally refactor-heavy. It is the follow-up to the
earlier missing-model refresh work: instead of keeping refresh-specific
candidate recheck logic beside the UI, this change makes the refresh
path reuse the existing missing-model pipeline and removes the direct
dependency on private `ComfyApp` pipeline methods.

Linear: FE-499

Issues covered by this PR:

- Fixes #11678
- Fixes #11680
- Partially addresses #11679 by removing the missing-model refresh
path's unsafe `graph.serialize() as unknown as ComfyWorkflowJSON` cast
and replacing it with the narrower flattenable workflow contract.
Broader workflow serialization/type-boundary cleanup outside this
missing-model refresh path remains deferred.

## Changes

- **What**:
- Added `src/platform/missingModel/missingModelPipeline.ts` as the
orchestration module for missing-model detection/verification.
- `runMissingModelPipeline(...)` now owns the pipeline previously
embedded in `ComfyApp`:
      - candidate scan and enrichment
      - active ancestor filtering for muted/bypassed subgraph containers
      - pending warning cache updates
      - OSS folder path and file-size follow-up work
      - cloud asset verification follow-up work
- surfaced missing-model errors via the existing execution error store
- `refreshMissingModelPipeline(...)` handles the refresh-specific flow:
      - calls the injected `reloadNodeDefs()` first
      - serializes the current live graph
- preserves model metadata by preferring active workflow `models`, then
falling back to current missing-model candidate metadata
      - delegates back into the same pipeline used during workflow load
- Kept `ComfyApp` as the compatibility caller instead of the owner of
the pipeline.
- `loadGraphData(...)` now calls `runMissingModelPipeline(...)` with
`graph`, `graphData`, `missingNodeTypes`, and `silent` options.
- `refreshMissingModels(...)` is now a thin wrapper around
`refreshMissingModelPipeline(...)` and keeps the existing default
`silent: true` refresh behavior.
- The new pipeline module does not import `@/scripts/app`; app-owned
data/actions are passed in as inputs.
- Moved the workflow node-flattening helpers out of `workflowSchema.ts`
and into `src/platform/workflow/core/utils/workflowFlattening.ts`.
- This includes `flattenWorkflowNodes`, `buildSubgraphExecutionPaths`,
and `isSubgraphDefinition`.
- The move is intentional: these helpers are not zod schema definitions
or workflow validation logic. They are core workflow traversal utilities
used to flatten root workflow nodes plus nested subgraph definition
nodes into the execution-shaped node list needed by missing-model
scanning.
- The refresh path receives data from `LGraph.serialize()`, whose return
type is serialized LiteGraph data rather than validated
`ComfyWorkflowJSON`. Previously this forced unsafe typing like
`graph.serialize() as unknown as ComfyWorkflowJSON`.
- The new `FlattenableWorkflowGraph` / `FlattenableWorkflowNode`
structural contract describes only what flattening actually needs:
`nodes`, `definitions.subgraphs`, node `id`, `type`, `mode`,
`widgets_values`, and `properties`.
- This lets both normal workflow-load data (`ComfyWorkflowJSON`) and
refresh-time live graph serialization (`LGraph.serialize()`) flow into
the same scan/enrichment path without pretending serialized LiteGraph
output is a fully validated workflow schema document.
- Updated `missingModelScan.ts` to consume that minimal flattenable
workflow shape via `MissingModelWorkflowData`.
- `MissingModelWorkflowData` extends the flattenable workflow contract
with optional workflow-level `models` metadata.
- Removed now-unnecessary casts around execution IDs, flattened nodes,
and `widgets_values` object access.
- Updated `getSelectedModelsMetadata(...)` to accept readonly widget
value arrays so flattened workflow data can stay read-only.
- Reduced the exported surface of the new pipeline module after `knip`
flagged unused exported internal option/store interfaces.
- Kept `workflowSchema.ts` focused on validation schemas. The flattening
helpers are not re-exported from the schema module because they are
internal workflow core utilities, not public schema API.

- **Breaking**: None intended.
  - Internal imports were updated to the new core utility path.
- This repo is not exposing these flattening helpers as a public package
API, so the old schema-local helper location is treated as an internal
implementation detail.

- **Dependencies**: None.

## Review Focus

- **Pipeline extraction / dependency direction**:
- Please verify that `missingModelPipeline.ts` stays independent from
`@/scripts/app`.
- `ComfyApp` should remain the caller/adapter, not the owner of
missing-model pipeline orchestration.

- **Workflow flattening type boundary**:
- The main type-cleanup goal is removing the refresh-time
`graph.serialize() as unknown as ComfyWorkflowJSON` lie.
- `LGraph.serialize()` and validated workflow JSON are not the same
contract. The new flattenable workflow contract is deliberately smaller
and structural because the missing-model enrichment path only needs
enough data to flatten nodes and read embedded model metadata.
- This is why the flattening helpers moved from `workflowSchema.ts` to
`workflow/core/utils`: the logic is reusable workflow traversal, not
validation schema.

- **Behavior preservation**:
- The PR is intended to preserve existing user-facing missing-model
behavior while moving ownership out of `app.ts`.
- Existing async follow-up behavior remains intentionally
fire-and-forget:
- cloud asset verification still surfaces after verification completes
- OSS folder paths still update asynchronously before surfacing
confirmed missing models
    - file-size metadata fetching remains asynchronous
- More invasive behavior changes, such as adding non-cloud post-fetch
`isMissingCandidateActive(...)` re-verification or redesigning the
fire-and-forget result contract, are intentionally left for follow-up
work because they are not pure extraction.

- **Downloadable model metadata**:
- `missingModels` returned for download metadata now requires both `url`
and `directory`.
- Candidates without a directory still remain in `confirmedCandidates`,
but they are not exposed as downloadable model metadata. This keeps the
returned downloadable list aligned with what the download flow can
actually use.

- **Test ownership**:
- Complex missing-model pipeline behavior tests moved out of
`src/scripts/app.test.ts` and into
`src/platform/missingModel/missingModelPipeline.test.ts`.
- `app.test.ts` now only covers thin delegation for
`app.refreshMissingModels(...)`.
- Workflow flattening tests moved with the helper from schema tests into
`src/platform/workflow/core/utils/workflowFlattening.test.ts`.

- **Deferred follow-ups**:
  - Broader function decomposition for cognitive complexity.
- Wider dependency-injection/port cleanup for stores and services beyond
the app boundary.
- Cloud-specific pipeline unit tests, which need a separate `isCloud`
mocking strategy.
- Additional E2E coverage expansion beyond the existing OSS refresh
path.
- More general workflow serialization/type-boundary cleanup outside the
missing-model refresh path.

## Validation

- `pnpm format`
- `pnpm lint`
- Passed. Existing lint output included a pre-existing
`no-misused-spread` warning and icon-name logs, but the command exited
successfully.
- `pnpm typecheck`
- `pnpm test:unit`
  - `714 passed`, `9514 passed | 8 skipped`
- Pre-push `pnpm knip`
- Passed after reducing the exported surface of the new pipeline module.

## Screenshots (if applicable)

Not applicable. This PR is a pipeline/type-boundary refactor with no UI
changes.

┆Issue is synchronized with this [Notion
page](https://app.notion.com/p/PR-11751-refactor-extract-missing-model-refresh-pipeline-3516d73d3650816d9245d4b1324b71c9)
by [Unito](https://www.unito.io)

---------

Co-authored-by: DrJKL <DrJKL0424@gmail.com>
Co-authored-by: Alexander Brown <drjkl@comfy.org>
2026-05-01 00:50:51 +00:00
pythongosssss
9384beaec6 test: Add tests for bounding box widget (#11343)
## Summary

Adds coverage for the bounding box widget

## Changes

- **What**: 
- Validates user interactions and functionality on the widget

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-11343-test-Add-tests-for-bounding-box-widget-3456d73d365081eb8d03f2220a837816)
by [Unito](https://www.unito.io)

---------

Co-authored-by: bymyself <cbyrne@comfy.org>
Co-authored-by: GitHub Action <action@github.com>
2026-05-01 00:23:22 +00:00
pythongosssss
4a05d89fdb fix: detach DOM widget event listeners on widget removal (#11724)
## Summary

Fixes leaked event listeners

## Changes

- **What**: 
- update all listeners to use AbortController to signal removal on
widget remove

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-11724-fix-detach-DOM-widget-event-listeners-on-widget-removal-3506d73d3650811dae81c034c1098759)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Alexander Brown <drjkl@comfy.org>
2026-05-01 00:17:18 +00:00
Alexander Brown
ef98ba0e8f feat: add plum/ink color primitives and standardize design tokens (#11139)
*PR Created by the Glary-Bot Agent*

---

## Summary

Adds new `plum` and `ink` color scales for Comfy Hub branding and
standardizes existing tokens to align with current Figma design system.

### Changes

**Phase 1 — New primitives** (`_palette.css`)
- Added `plum-300/400/500/600` and `ink-100` through `ink-900`

**Phase 2 — Token cleanup** (`style.css`)
- Removed deprecated `slate-100/200/300` primitives (cool blue-grey,
removed from Figma)
- Removed duplicate `graphite-400` (identical hex to slate-100)
- Dark mode: migrated 6 slate/graphite references to muted-foreground,
smoke-700, smoke-800, charcoal-200
- Light mode: replaced 3 `ash-500` references with `smoke-800` per
designer alignment

### Token migration detail

| Dark mode token | Old value | New value | Rationale |
|---|---|---|---|
| `--node-component-header-icon` | slate-300 (#5b5e7d) |
muted-foreground (smoke-800) | Figma `node/foreground-secondary` |
| `--node-component-slot-text` | slate-200 (#9fa2bd) | smoke-700
(#a0a0a0) | Lighter neutral for text contrast |
| `--node-component-surface-highlight` | slate-100 (#9c9eab) | smoke-800
(#8a8a8a) | Neutral grey highlight |
| `--node-component-tooltip-border` | slate-300 (#5b5e7d) | charcoal-200
(#494a50) | Consistent with dark border tokens |
| `--text-secondary` | slate-100 (#9c9eab) | smoke-700 (#a0a0a0) |
Adequate contrast on dark surfaces |
| `--widget-background-highlighted` | graphite-400 (#9c9eab) | smoke-800
(#8a8a8a) | Removed duplicate, neutral replacement |

### Visual note

These changes shift some dark mode colors from cool blue-grey to neutral
grey. This is intentional per the design team. The
`--node-component-surface-highlight` and
`--node-component-tooltip-border` tokens should be QA'd as the designer
noted.

### Not included (Phase 3)

Hub Dark overlay theme will ship separately once the Hub UI work is
ready to validate against.

## Screenshots

![Dark theme with templates dialog - text contrast
verified](https://pub-1fd11710d4c8405b948c9edc4287a3f2.r2.dev/sessions/6425a0f072864b0f279edd70da09bf96b24eaf7d483eb695e2718e864735bea9/pr-images/1775860135830-05ba14b3-f385-4fc8-9d25-3094d8c09b74.png)

![Dark theme canvas view - sidebar and toolbar rendering
correctly](https://pub-1fd11710d4c8405b948c9edc4287a3f2.r2.dev/sessions/6425a0f072864b0f279edd70da09bf96b24eaf7d483eb695e2718e864735bea9/pr-images/1775860136167-5887ce56-5eae-482e-90e5-06e24cb23d2c.png)

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-11139-feat-add-plum-ink-color-primitives-and-standardize-design-tokens-33e6d73d365081418e13e0efe6161fb5)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Glary-Bot <glary-bot@users.noreply.github.com>
Co-authored-by: github-actions <github-actions@github.com>
Co-authored-by: Amp <amp@ampcode.com>
2026-04-30 16:15:53 -07:00
Christian Byrne
019c1787a5 [chore] Update Comfy Registry API types from comfy-api@e993818 (#11783)
## Automated API Type Update

This PR updates the Comfy Registry API types from the latest comfy-api
OpenAPI specification.

- API commit: e993818
- Generated on: 2026-04-30T17:57:05Z

These types are automatically generated using openapi-typescript.

┆Issue is synchronized with this [Notion
page](https://app.notion.com/p/PR-11783-chore-Update-Comfy-Registry-API-types-from-comfy-api-e993818-3526d73d3650810e9e14f55da44714b2)
by [Unito](https://www.unito.io)

Co-authored-by: coderfromthenorth93 <213232275+coderfromthenorth93@users.noreply.github.com>
2026-04-30 21:37:01 +00:00
Alexander Brown
87fab87d84 docs: add reviewing-unit-tests skill (#11777)
## Summary

Adds `.claude/skills/reviewing-unit-tests/SKILL.md`, a project-scoped
agent skill that gives reviewing agents a concrete rubric for evaluating
Vitest unit-test diffs.

## Changes

- **What**: New skill at `.claude/skills/reviewing-unit-tests/SKILL.md`.
Mirrors the structural template of the peer skill
`.claude/skills/writing-playwright-tests/SKILL.md` (frontmatter shape,
table-driven sections, terse prescriptive prose). Description triggers
on PR review of `*.test.ts` files. Codifies: alias-by-renaming
detection, `vi.mocked()` scope rule, unnecessary-cast detection,
divergence checks against `docs/testing/unit-testing.md` /
`store-testing.md` / `component-testing.md` / `vitest-patterns.md`,
redundant `mockClear()` flag, `vue-i18n`-must-not-be-mocked rule (with
`testI18n` reference), bugfix regression-validity check, leading-close
framing flag, testing-library requirement for new component tests.

## Review Focus

- **Retrospective validation against PR #11737** (head SHA
`4573d62450fd12ac6f06e5e491f8af84ccbd27de`, file
`src/platform/assets/composables/useMediaAssetActions.test.ts`).
Mentally running the skill's checklist against that diff flags:
1.  `getToastAddMock` / `getI18nTMock` module-level helpers — caught by
the "Renaming ≠ Restructuring" section + Mocking Smells row
"Module-level helper functions wrapping mocked composable returns".
2.  `as ReturnType<typeof vi.fn>` casts in those helpers — caught by
the "`vi.mocked()` Scope" section + Mocking Smells row on stray casts.
3.  `vi.mock('vue-i18n', ...)` — caught by Mocking Smells row directing
to mount real `createI18n` per `docs/testing/vitest-patterns.md` and the
shared `testI18n` in
`src/components/searchbox/v2/__test__/testUtils.ts`.
4.  `vi.mock('primevue/usetoast', ...)` — explicitly **not** flagged.
The "Distinguish" paragraph after the Mocking Smells table marks
trivially-shaped third-party hooks as acceptable to mock; "Don't Mock
What You Don't Own" applies to behavior-rich APIs, not single-method
composables.
- **Reference style**: links to `docs/testing/*.md` and
`docs/guidance/typescript.md` instead of restating their content, per
the FE-511 acceptance criterion.
- **Open question**: scope. The repo-local skill is the FE-511
deliverable; equivalent prescriptive rules already live in the user's
global `~/.config/amp/AGENTS.md` "Code Review Rigor" section as a
separate artifact. No duplication intended — the skill is the
project-scoped surface a reviewing agent loads when entering this repo.

Closes FE-511

┆Issue is synchronized with this [Notion
page](https://app.notion.com/p/PR-11777-docs-add-reviewing-unit-tests-skill-3526d73d365081848759e6a8fab942f0)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Amp <amp@ampcode.com>
2026-04-30 14:29:47 -07:00
Alexander Brown
f2a99adaa3 docs: prefer real createI18n over mocking vue-i18n in tests (#11776)
## Summary

Document that `vue-i18n` should not be mocked in tests — mount with a
real `createI18n` plugin instance instead.

## Changes

- **What**: Expanded `docs/testing/vitest-patterns.md` "Don't Mock
`vue-i18n`" section with a concrete code example (covering both
component and composable tests), guidance for asserting on translation
keys with empty messages, and a real-example link to
`src/components/searchbox/v2/__test__/testUtils.ts`. Added a callout at
the top of `docs/testing/unit-testing.md` "Mocking Composables with
Reactive State" cross-linking the new section, since that section
applies to *owned* composables.

## Review Focus

- The previous `vitest-patterns.md` paragraph pointed at a non-existent
`SearchBox.test.ts`; the new link points to the actual shared `testI18n`
helper.
- The "Mocking Composables with Reactive State" pattern should not be
applied to third-party composables like `useI18n` — the callout makes
that explicit.

Surfaced during review of #11737, where the test file mocked `vue-i18n`
and then accumulated structural workarounds (hoisted aliases, helper
functions, type casts) to interact with the mocked `t`. A real
`createI18n` instance avoids the entire ceremony.

┆Issue is synchronized with this [Notion
page](https://app.notion.com/p/PR-11776-docs-prefer-real-createI18n-over-mocking-vue-i18n-in-tests-3526d73d365081d4bc39fbf3c2502e49)
by [Unito](https://www.unito.io)

Co-authored-by: Amp <amp@ampcode.com>
2026-04-30 14:29:11 -07:00
Robin Huang
a934056246 feat(website): add PostHog analytics (#11735)
## Summary

Adds PostHog page analytics to the marketing website (`apps/website/`).

## Changes

- **What**: New `posthog.ts` script with
`initPostHog`/`capturePageview`. Wired into `BaseLayout.astro` behind
`import.meta.env.PROD` (mirroring the GTM gate). Pageviews are captured
on every `astro:page-load` so ClientRouter view-transition navigations
are tracked, not just hard reloads.
- **Dependencies**: `posthog-js` (already in the workspace catalog at
`^1.358.1`; previously used by the workbench telemetry provider).

## Review Focus

- API host is set to `https://t.comfy.org` to match
`src/platform/telemetry/providers/cloud/PostHogTelemetryProvider.ts` —
confirm that proxy is OK to share with the website surface.
- Project token is hard-coded inline. It is a public `phc_…` frontend
token (designed to ship to clients); this matches the pattern used for
`gtmId` in the same file. Happy to switch to a `PUBLIC_POSTHOG_KEY` env
var if preferred.
- `person_profiles: 'identified_only'` to avoid creating profiles for
every anonymous visitor — flag if you want full anonymous tracking
instead.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-11735-feat-website-add-PostHog-analytics-3516d73d3650811189c6d64c3af4ded9)
by [Unito](https://www.unito.io)
2026-04-30 20:08:01 +00:00
pythongosssss
b8dfbfc0bb fix: ensure escape key/graph navigation cancels ghost node placement (#11779)
## Summary

When inside a subgraph, the parent key handler intercepts the escape key
before `processKey` is called, causing a stale ghost node to be added to
the inner subgraph when the graph is changed to the parent.

## Changes

- **What**: 
- move escape key handler to document, prevent propagating the event
- add saftey net cancel in setGraph
- tests

┆Issue is synchronized with this [Notion
page](https://app.notion.com/p/PR-11779-fix-ensure-escape-key-graph-navigation-cancels-ghost-node-placement-3526d73d3650812292e4ca10d384f783)
by [Unito](https://www.unito.io)
2026-04-30 17:30:04 +00:00
Comfy Org PR Bot
036c79259b 1.44.14 (#11769)
Patch version increment to 1.44.14

**Base branch:** `main`

┆Issue is synchronized with this [Notion
page](https://app.notion.com/p/PR-11769-1-44-14-3526d73d3650816ba3a9cd87afeef035)
by [Unito](https://www.unito.io)

---------

Co-authored-by: christian-byrne <72887196+christian-byrne@users.noreply.github.com>
Co-authored-by: Alexander Brown <drjkl@comfy.org>
Co-authored-by: github-actions <github-actions@github.com>
v1.44.14
2026-04-30 06:09:17 +00:00
Benjamin Lu
17c18b0707 fix: embed HubSpot contact form (#11723)
## Summary

Embed the HubSpot-hosted contact sales form on `/contact` and
`/zh-CN/contact` so HubSpot owns submission handling, validation,
anti-spam/security updates, tracking context, and form configuration.

## Changes

- **What**: Replaces the custom local contact form stub with a HubSpot
embed component using the HubSpot-hosted developer/raw HTML script and
`hs-form-html` container.
- **Localization**: Uses the existing English form ID by default and
switches to the zh-CN form ID for `/zh-CN/contact`.
- **Styling**: Applies HubSpot form CSS custom properties to match the
Comfy contact page colors and `PP Formula` font more closely.
- **Docs**: Updates the website README with the developer embed snippet
and the zh-CN form ID.
- **Dependencies**: None.

## Why Embedded Form

- HubSpot docs say forms should be loaded with the HubSpot-hosted
JavaScript file, and that security, anti-spam, accessibility, and
performance improvements will not propagate if the embed runtime is
copied or self-hosted:
https://developers.hubspot.com/docs/cms/start-building/building-blocks/modules/forms
- The direct form submission endpoint is documented under `v3 legacy`:
https://developers.hubspot.com/docs/api-reference/legacy/marketing/forms/v3-legacy/submit-data-unauthenticated
- HubSpot’s legacy API overview says numeric-versioned APIs are legacy
and will be deprecated in a future release:
https://developers.hubspot.com/docs/api-reference/legacy/overview

## Review Focus

- Confirm the portal ID and form IDs are correct:
  - `en`: `94e05eab-1373-47f7-ab5e-d84f9e6aa262`
  - `zh-CN`: `6885750c-02ef-4aa2-ba0d-213be9cccf93`
- Check visual fit on `/contact` and `/zh-CN/contact`, especially font,
background, inputs, radio controls, and submit button.
- Confirm the developer/raw HTML embed remains the preferred integration
over a custom Forms API POST.

## Local Checks

- `pnpm exec oxfmt --check
apps/website/src/components/contact/HubspotFormEmbed.vue
apps/website/README.md`
- `pnpm exec eslint
apps/website/src/components/contact/HubspotFormEmbed.vue`
- `pnpm --filter @comfyorg/website typecheck`
- `pnpm --filter @comfyorg/website test:unit`
- `pnpm --filter @comfyorg/website build`
- Commit hooks: stylelint, oxfmt, oxlint, eslint, `pnpm typecheck`,
`pnpm typecheck:website`
- Push hook: `pnpm knip --cache`

Build completed with existing non-fatal environment warnings: Node
`v25.8.1` vs requested `24.x`, unresolved website font paths deferred to
runtime, `transformWithEsbuild` deprecation, and missing Ashby env
falling back to the committed snapshot.

Incredibly, during the taking of these screenshots, I discovered a bug
in macos, where despite the snapshot/record bar not existing, from me
esc'ing out, some of the tooltips persist. Closing and reopening the lid
did not fix this. I didn't see the process in activity monitor.

<img width="1512" height="862" alt="Screenshot 2026-04-29 at 7 09 55 PM"
src="https://github.com/user-attachments/assets/92518795-6845-4b34-8da3-54048b440eb1"
/>

<img width="1512" height="862" alt="Screenshot 2026-04-29 at 7 13 18 PM"
src="https://github.com/user-attachments/assets/f7609e58-898d-413c-975c-b02b70b84e73"
/>

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-11723-fix-embed-HubSpot-contact-form-3506d73d365081528bfbe4b024c2a21f)
by [Unito](https://www.unito.io)

---------

Co-authored-by: github-actions <github-actions@github.com>
2026-04-29 22:40:55 -07:00
Alexander Brown
ca8407218b chore(husky): skip pre-push knip hook in CI (#11772)
*PR Created by the Glary-Bot Agent*

---

## Summary

Fixes a false-positive knip failure in the `i18n: Update Core` workflow
that's been blocking version-bump PRs (e.g. #11769, run
[25141091787](https://github.com/Comfy-Org/ComfyUI_frontend/actions/runs/25141091787/job/73690747401)).

## Root cause

1. The `update-locales` job uses `setup-comfyui-server`, which copies
`tools/devtools/*` → `ComfyUI/custom_nodes/ComfyUI_devtools/*` at the
workspace root.
2. The job's final `git push` triggers the husky `.husky/pre-push` hook,
which runs `pnpm knip`.
3. Knip's `project` glob (`**/*.{js,ts,vue}` in `knip.config.ts`) sweeps
the runtime `ComfyUI/` directory; the existing `tools/devtools/web/**`
ignore doesn't cover the copied path, so
`ComfyUI/custom_nodes/ComfyUI_devtools/web/legacyWidget.js` (added in
#11574) is reported as unused → exit 1 → push aborted.

## Why fix the hook (not knip config or workflows)

- **`knip.config.ts` ignore** (`'ComfyUI/**'`): tried first;
`treatConfigHintsAsErrors: true` makes the unused-pattern hint fatal in
normal CI runs where `ComfyUI/` doesn't exist, breaking `ci-lint-format`
everywhere.
- **`HUSKY=0` per workflow step**: works, but bots can't push workflow
file changes without `workflows` scope, and it would need to be repeated
on every CI workflow that pushes.
- **Skip the hook in CI**: the canonical `pnpm knip` check runs in
`.github/actions/lint-format-verify/action.yml` on every PR, so the
pre-push duplicate is pure dev-side guardrail. Gating it on `$CI` (set
to `true` by all GitHub Actions runners) cleanly removes it from every
CI push without affecting local developer workflow.

## Verification

Reproduced the failure locally by mirroring the CI sequence (`cp -r
tools/devtools/* ComfyUI/custom_nodes/ComfyUI_devtools/` then `pnpm
knip:no-cache`):

- Without fix: `[log] Unused files (1)
ComfyUI/custom_nodes/ComfyUI_devtools/web/legacyWidget.js` → exit 1
(matches CI log)
- With `CI=true`, the hook short-circuits at `exit 0` before invoking
knip
- `pnpm knip` on a clean checkout (no `ComfyUI/`) still passes

┆Issue is synchronized with this [Notion
page](https://app.notion.com/p/PR-11772-chore-husky-skip-pre-push-knip-hook-in-CI-3526d73d3650819f9a01d549d122b69c)
by [Unito](https://www.unito.io)

Co-authored-by: Glary-Bot <glary-bot@users.noreply.github.com>
2026-04-30 05:16:51 +00:00
Benjamin Lu
26ac1eece1 Allow website screenshot workflow to remove its label (#11725)
## Summary

Allows the website screenshot update workflow to remove its own trigger
label when a label-triggered run completes.

## Changes

- **What**: Grants the screenshot update job `issues: write`, which is
required for `issues.removeLabel`, and keeps the cleanup scoped to
`Update Website Screenshots`.
- **Dependencies**: None.

## Review Focus

Confirm the workflow permission scope is appropriate and that unexpected
label cleanup failures should fail the workflow instead of being
silently swallowed.

## Validation

- `/Users/ben/go/bin/actionlint
.github/workflows/pr-update-website-screenshots.yaml`
- `/Users/ben/Library/Python/3.9/bin/yamllint --config-file .yamllint
.github/workflows/pr-update-website-screenshots.yaml`
- `git diff --check HEAD~1 HEAD`

No browser e2e regression was added because this change only adjusts
GitHub Actions token permissions and label cleanup behavior; it does not
change shipped app/runtime behavior.

Fixes FE-487
2026-04-30 04:57:00 +00:00
Benjamin Lu
810381ab63 Stabilize website GitHub stars in visual snapshots (#11771)
## Summary

Stabilize the website nav GitHub star count in visual-test builds so
snapshot comparisons do not drift as the live GitHub count changes.

## Changes

- **What**: Added `WEBSITE_GITHUB_STARS_OVERRIDE` for build-time
star-count overrides and set it to `111000` in the website E2E and
screenshot-update workflows.
- **Dependencies**: None.

## Review Focus

Confirm the deterministic build-time override is preferable to
screenshot masking, since Playwright masks draw a colored rectangle
whose geometry can also drift when masked content changes size.

## Screenshots (if applicable)

Not included; this keeps visual-test input data stable rather than
changing the UI.
2026-04-29 21:57:30 -07:00
Christian Byrne
cc1a737291 test: add unit tests for useImagePreviewWidget (#11394)
## Summary
Add 23 unit tests for `useImagePreviewWidget` composable, improving
coverage from ~52% to significantly higher.

## Test Coverage
- Widget construction (factory return, name, type, serialization,
options)
- `computeLayoutSize` returns correct dimensions
- `createCopyForNode` creates properly bound copy
- Upload spinner rendering (animation, stroke color)
- Single image rendering (draws image, handles missing node.size)
- Image size text overlay (enabled/disabled via setting)
- Multi-image thumbnail grid (compact vs non-compact mode, cell borders)
- Pointer interaction (pointerDown cleanup, overIndex reset)
- `previewImages` override from DrawWidgetOptions
- `onPointerDown` drag handler setup
- `onClick` no-op behavior

## Testing
```bash
pnpm test:unit -- src/renderer/extensions/vueNodes/widgets/composables/useImagePreviewWidget.test.ts
```
All 23 tests pass. No source changes.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-11394-test-add-unit-tests-for-useImagePreviewWidget-3476d73d365081a69c78e87329e75d9f)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Alexander Brown <drjkl@comfy.org>
Co-authored-by: DrJKL <DrJKL0424@gmail.com>
Co-authored-by: Amp <amp@ampcode.com>
2026-04-30 02:15:48 +00:00
Kelly Yang
c74e08e244 refactor: extract useBrushAdjustment from useBrushDrawing (#11544)
## Summary

Part of the `useBrushDrawing` decomposition plan (PR C). Extracts brush
size/hardness adjustment logic (Alt+drag interaction) into a dedicated
`useBrushAdjustment` composable. No runtime behavior is changed — pure
structural refactor.

## Changes

- **New** `src/composables/maskeditor/useBrushAdjustment.ts` —
encapsulates `startBrushAdjustment` and `handleBrushAdjustment`,
including dead zone filtering, dominant axis suppression, and
size/hardness clamping
- **New** `src/composables/maskeditor/useBrushAdjustment.test.ts` — unit
tests covering: no-op before start, dead zone suppression, size increase
on drag, size/hardness clamping, dominant axis lock
- **Updated** `src/composables/maskeditor/useBrushDrawing.ts` — removes
inlined adjustment state and functions, delegates to
`useBrushAdjustment(initialSettings)`

## Test Functionality
Open ComfyUI and enter the MaskEditor of any image node. On the canvas,
Alt + Right-click Drag:

- Drag Right → Increase brush size - pass
- Drag Left → Decrease brush size - pass
- Drag Up → Increase hardness - pass
- Drag Down → Decrease hardness - pass


https://github.com/user-attachments/assets/273e8383-dab5-4c82-ac7b-0a1534dfd770





<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Touches core pointer-interaction logic for brush tuning and changes
adjustment behavior (removes delta saturation and uses initial values),
which could subtly affect UX even though scope is localized to the mask
editor.
> 
> **Overview**
> Extracts the Alt-drag brush size/hardness adjustment logic out of
`useBrushDrawing` into a new `useBrushAdjustment` composable, and wires
`useBrushDrawing` to delegate to it.
> 
> The extracted logic now bases adjustments off the captured initial
brush size/hardness and removes prior delta capping (no ±100px
saturation), which changes how large/continuous drags affect the final
values. Adds a Vitest suite covering dead-zone behavior, dominant-axis
suppression, clamping, and the no-op-before-start contract.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
b2f0ce22d3. 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-11544-refactor-extract-useBrushAdjustment-from-useBrushDrawing-34a6d73d365081a48897dd77b04ef56b)
by [Unito](https://www.unito.io)

---------

Co-authored-by: GitHub Action <action@github.com>
2026-04-29 20:25:36 -04:00
Robin Huang
8f9f452c86 fix: enable Chrome password autofill on signup form (#11636)
## Summary

Add `name` attributes to the signup form's email, password, and
confirm-password inputs so Chrome's password manager recognizes the form
and offers autofill/save.

## Changes

- **What**: Pass `name` through to the underlying `<input>` on the email
field (via `pt:root:name`) and on both password fields (via
`pt:pc-input-text:root:name`). Without `name`, Chrome can't pair the
email with the password and won't surface the save-password /
suggest-strong-password prompts.

## Review Focus

- The PrimeVue passthrough syntax (`pt:root:*` for `InputText`,
`pt:pc-input-text:root:*` for `Password`) lands the attribute on the
actual `<input>` element — verified in DevTools.
- `confirm-password` is not a standard `autocomplete` token; we keep
`autocomplete="new-password"` on both password fields and only
differentiate via `name`.

## Screenshots (if applicable)



https://github.com/user-attachments/assets/e1e25ab5-8496-4c84-b5f1-630f31956c80

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-11636-fix-enable-Chrome-password-autofill-on-signup-form-34e6d73d36508180882cc9ebafb58417)
by [Unito](https://www.unito.io)
2026-04-30 00:11:20 +00:00