mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-05-20 20:39:30 +00:00
bb74ec94de38c466c093069a92b3a109022ec44b
7778 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
3e62033f09 |
test: extract TestIdValue as mapped type (#11474)
## Summary Prevent needing to update the union with newly added keys ## Changes - **What**: - Change the `TestIdValue` union to a mapped type, excluding function values ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-11474-test-extract-TestIdValue-as-mapped-type-3486d73d365081d299efd87a0c46d66f) by [Unito](https://www.unito.io) |
||
|
|
78630f5485 |
test: add WidgetRange unit tests (#11471)
## Summary Splits the WidgetRange test coverage out of #11446 so this widget can be reviewed independently. ## Changes - **What**: Adds WidgetRange unit tests covering value pass-through, display propagation, disabled-state handling, upstream overrides, and histogram derivation. ## Review Focus Focused test-only PR extracted from #11446. Validated with `pnpm test:unit -- --run src/components/range/WidgetRange.test.ts`. ## Screenshots (if applicable) N/A ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-11471-test-add-WidgetRange-unit-tests-3486d73d365081d7a684ca3ff02320d6) by [Unito](https://www.unito.io) --------- Co-authored-by: GitHub Action <action@github.com> |
||
|
|
55c5fce522 |
ci: stabilize Vercel website preview URLs per PR (#11478)
## Summary Make the website preview URL stable per PR and make deployments show up correctly in the Vercel dashboard. ## Changes - **What**: - Pass git metadata (`githubCommitRef`, `githubCommitSha`, `githubCommitAuthorLogin`, `githubCommitMessage`, `githubPrId`, `githubRepo`) via `vercel deploy --meta` so deployments group by branch/PR in the dashboard and pick up branch-scoped env vars. - Alias each preview deploy to a stable per-PR hostname: `comfy-website-preview-pr-<N>.vercel.app`. URL no longer changes between pushes on the same PR. - PR comment now shows the stable URL prominently, the per-commit URL as subtext, plus a last-updated timestamp and short SHA so reviewers can tell if the preview is current. - User-controlled PR fields routed through env vars (no shell interpolation of untrusted strings). ## Review Focus - `PREVIEW_ALIAS_PREFIX` is set to `comfy-website-preview` — confirm this subdomain pattern is free within the Vercel team (first deploy will claim it). - Production job is untouched. - `vercel.json` keeps `github.enabled: false` — intentional, we stay CLI-driven. ### Known limitation (out of scope) Vercel Shareable Links are bound to a specific deployment ID. Aliasing the stable hostname to a new deployment does **not** carry over previously-issued share links. If the team needs share links to persist across pushes, follow-up options: Protection Bypass for Automation (project-level token) or Deployment Protection Exceptions (Pro+). ### Follow-ups - Optional `vercel alias rm` on PR close to clean up stale aliases. ## Screenshots (if applicable) N/A — CI config only. Verification will land on this PR's own preview run. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-11478-ci-stabilize-Vercel-website-preview-URLs-per-PR-3486d73d3650815ab24be1f7895cecc5) by [Unito](https://www.unito.io) --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
4b5c15fc7d |
fix: show credits in legacy user popover on non-cloud distributions (#11463)
*PR Created by the Glary-Bot Agent* --- ## Summary Credits no longer showed in the current user popover on local/desktop builds. Root cause: the credits row in `CurrentUserPopoverLegacy.vue` was gated behind `isCloud && isActiveSubscription`, and `isCloud` is a compile-time constant that resolves to `false` on local (`DISTRIBUTION='localhost'`) — so the element never rendered and `fetchBalance()` never fired (no network request, no console logs). This fix decouples the credits balance row from the `isCloud` gate. Subscription-specific UI (subscribe button, partner nodes, plans & pricing, manage plan, upgrade-to-add-credits) remains gated by `isCloud` as intended by PR #9958. ## Changes - `CurrentUserPopoverLegacy.vue`: credits row `v-if` changed from `isCloud && isActiveSubscription` → `isActiveSubscription`. On non-cloud, `isActiveSubscription` resolves to `true` via `isSubscribedOrIsNotCloud` in `useSubscription.ts`, so credits display for logged-in users. - `CurrentUserPopoverLegacy.vue`: `upgrade-to-add-credits` button now requires `isCloud && isFreeTier` (subscription-tier concept only meaningful on cloud). The `add-credits` top-up button remains available everywhere. - `CurrentUserPopoverLegacy.test.ts`: updated non-cloud tests to assert credits balance is visible and add-credits button renders, while upgrade-to-add-credits and other subscription UI stay hidden. Mirrors the behavior of `CurrentUserPopoverWorkspace.vue`, which never had the `isCloud` gate on its credits row. ## Verification - `pnpm vitest run src/components/topbar/CurrentUserPopoverLegacy.test.ts`: **21/21 passing**, including new non-cloud assertions - `pnpm typecheck`: clean - `pnpm lint` / `pnpm format:check`: clean - Live frontend dev server renders on localhost with `__DISTRIBUTION__='localhost'` (the previously-failing scenario). Attached screenshot shows the app running on local distribution; the popover itself only appears for logged-in users, so its contents are exercised by the unit tests. Fixes FE-219 ## Screenshots  ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-11463-fix-show-credits-in-legacy-user-popover-on-non-cloud-distributions-3486d73d365081c587d8ee7eae9a5c3d) by [Unito](https://www.unito.io) --------- Co-authored-by: Glary-Bot <glary-bot@users.noreply.github.com> |
||
|
|
b36242475c |
test: add E2E tests for topbar menu commands (#11208)
## Summary Add 5 Playwright E2E tests covering topbar menu command interactions. ## Changes - **What**: New test file `browser_tests/tests/topbarMenuCommands.spec.ts` with 5 tests: - New command creates a new workflow tab - Edit > Undo undoes the last action - Edit > Redo restores an undone action - File > Save opens save dialog - View > Bottom Panel toggles bottom panel visibility ## Review Focus Tests use `triggerTopbarCommand()` for menu navigation and `expect.poll()` for async assertions. The "New" command is a top-level menu item (path `["New"]`), not nested under File. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-11208-test-add-E2E-tests-for-topbar-menu-commands-3416d73d36508143afe5e67a98910f56) by [Unito](https://www.unito.io) |
||
|
|
2f4116fa81 |
test: add unit tests for numberUtil and dateTimeUtil (#11253)
## Summary Adds unit tests for two untested utility modules to improve coverage: - **`numberUtil.ts`** — `clampPercentInt`, `formatPercent0` (clamping, rounding, locale formatting) - **`dateTimeUtil.ts`** — `dateKey`, `isToday`, `isYesterday`, `formatShortMonthDay`, `formatClockTime` 20 new tests total. This PR also serves as an E2E validation of the coverage Slack notification workflow (#10977) — merging should trigger a Slack notification showing the coverage improvement. ## Test Plan - `pnpm test:unit -- src/utils/numberUtil.test.ts src/utils/dateTimeUtil.test.ts` - All 20 tests pass locally ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-11253-test-add-unit-tests-for-numberUtil-and-dateTimeUtil-3436d73d365081aab388fd1f1fcac7d7) by [Unito](https://www.unito.io) |
||
|
|
d83c84aa85 |
test: extract asset api browser fixture (#11279)
## Summary Move asset API mocking off `ComfyPage` and into a standalone Playwright fixture. ## Changes - add `assetApiFixture` for browser tests that need asset API mocking - remove `assetApi` from `ComfyPage` - migrate `browser_tests/tests/assetHelper.spec.ts` to use the standalone fixture ## Why This is the first slice of the browser-fixture split. It reduces global fixture surface area without changing test behavior. ## Validation - `pnpm typecheck:browser` - `pnpm exec oxlint browser_tests/fixtures/ComfyPage.ts browser_tests/fixtures/assetApiFixture.ts browser_tests/tests/assetHelper.spec.ts --type-aware` - repo hooks during commit/push: `pnpm typecheck`, `pnpm typecheck:browser`, `pnpm knip` ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-11279-test-extract-asset-api-browser-fixture-3436d73d3650818393bcd43dc909c8a2) by [Unito](https://www.unito.io) |
||
|
|
c1c3fba1ac |
refactor: extract shared resolve-pr-from-workflow-run action (#11336)
## Summary Extract duplicated PR-number-resolution logic from `workflow_run`-triggered workflows into a shared composite action at `.github/actions/resolve-pr-from-workflow-run/`. ## Changes - **What**: New composite action that resolves PR number from `workflow_run` context using `pull_requests[0]` with `listPullRequestsAssociatedWithCommit` fallback. Updated 4 consumer workflows; removed dead artifact-stored PR metadata from 2 CI workflows. - **Files touched**: - `.github/actions/resolve-pr-from-workflow-run/action.yaml` (new) - `.github/workflows/pr-vercel-website-preview.yaml` (uses shared action) - `.github/workflows/pr-report.yaml` (uses shared action with `check-staleness: true`) - `.github/workflows/ci-tests-storybook-forks.yaml` (replaced `pulls.list` scan) - `.github/workflows/ci-tests-e2e-forks.yaml` (replaced `pulls.list` scan) - `.github/workflows/ci-size-data.yaml` (removed dead `number.txt`/`base.txt`/`head-sha.txt` writes) - `.github/workflows/ci-perf-report.yaml` (removed dead `perf-meta` artifact) ## Review Focus - The fork workflows previously used `pulls.list` (fetches all open PRs, linear scan by SHA). The shared action uses the more targeted `workflow_run.pull_requests[0]` + `listPullRequestsAssociatedWithCommit` fallback. - `coverage-slack-notify.yaml` was intentionally left unchanged — it parses merged commit messages on `main` pushes, which is a different use case. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-11336-refactor-extract-shared-resolve-pr-from-workflow-run-action-3456d73d365081e5b8f5ea29c020763e) by [Unito](https://www.unito.io) --------- Co-authored-by: bymyself <cbyrne@comfy.org> Co-authored-by: Amp <amp@ampcode.com> |
||
|
|
35bfe509b3 |
test: add/update terminal tests (#11239)
## Summary Adds test coverage for the integrated terminal ## Changes - **What**: - refactor and simplify existing tests - add new tests for xterm integration ┆Issue is synchronized with this [Notion page](https://app.notion.com/p/PR-11239-test-add-update-terminal-tests-3426d73d365081c99445c35d8808afb4) by [Unito](https://www.unito.io) |
||
|
|
5d98e11ba1 |
feat: enable queue panel v2 by default on nightly builds (#11376)
*PR Created by the Glary-Bot Agent* --- ## Summary - Changes the `Comfy.Queue.QPOV2` setting's `defaultValue` from `false` to `isNightly` - On nightly builds, users get the docked job history/queue panel (v2) by default - On stable builds, behavior is unchanged (v1 floating overlay remains default) - Users can still toggle the setting manually regardless of build type ## Pattern Follows the existing pattern used by `Comfy.VueNodes.Enabled` which uses `isCloud || isDesktop` as its version-conditional default. This is a compile-time constant from `@/platform/distribution/types`. ## Context Part of a dual-variant audit to graduate experimental features. QPO v2 has 0 extension ecosystem dependencies (confirmed via GitHub codesearch), making nightly default-on safe for gathering feedback before promoting to all users. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-11376-feat-enable-queue-panel-v2-by-default-on-nightly-builds-3466d73d36508140b814d1d684acacba) by [Unito](https://www.unito.io) Co-authored-by: Glary-Bot <glary-bot@users.noreply.github.com> |
||
|
|
60c7471818 |
feat: enable node replacement by default (#11439)
*PR Created by the Glary-Bot Agent* --- ## Summary Enable node replacement suggestions by default so users see Quick Fix options for deprecated/renamed nodes without toggling an experimental setting. - Change `Comfy.NodeReplacement.Enabled` default from `false` to `true` and remove `experimental` flag - Add `versionModified` metadata for release tracking - No breaking change — users who previously disabled this setting keep their preference ## Safety gates This is an intentional global rollout, gated by two additional server-side checks: 1. Server must provide `node_replacements` feature flag as true (PostHog controlled) 2. `GET /api/node_replacements` must return data (cloud PR Comfy-Org/cloud#2686) Without both, changing this default alone has no effect. The three gates ensure safe rollout. ## Companion PRs - Comfy-Org/cloud#2686 — backend `GET /api/node_replacements` endpoint + server-side validation bypass Replicate of #11246, retargeted to `main` for backport automation. Labels: `needs-backport`, `cloud/1.42`, `cloud/1.43`, `core/1.42`, `core/1.43` ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-11439-feat-enable-node-replacement-by-default-3486d73d36508192b77aea9640986106) by [Unito](https://www.unito.io) Co-authored-by: Glary-Bot <glary-bot@users.noreply.github.com> |
||
|
|
0ac4c3d6c5 |
1.44.6 (#11433)
Patch version increment to 1.44.6 **Base branch:** `main` ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-11433-1-44-6-3486d73d365081778622e094f11b500c) by [Unito](https://www.unito.io) Co-authored-by: christian-byrne <72887196+christian-byrne@users.noreply.github.com> Co-authored-by: Christian Byrne <cbyrne@comfy.org>v1.44.6 |
||
|
|
feafdc0b4a |
fix: chain Load3D node lifecycle callbacks to preserve widget cleanup (#11359)
## Summary Undo on a workflow with an interactive 3D/camera node (e.g. Qwen MultiAngle Camera) broke the interactive UI: it disappeared for Vue Nodes 2.0 and desynced for LiteGraph. Root cause: `initializeLoad3d` in `useLoad3d.ts` assigned `node.onRemoved`, `node.onResize`, and the other node lifecycle handlers by direct assignment, overwriting the cleanup chain that `addWidget()` had already appended during node construction (line `node.onRemoved = useChainCallback(node.onRemoved, () => widget.onRemove?.())` in `domWidget.ts`). When undo cleared the graph, `widget.onRemove` never ran, so the component widget stayed in `domWidgetStore` pointing at a detached element while new nodes registered fresh widgets at the same UUID keys. Fix: wrap all of those assignments with `useChainCallback` so earlier subscribers (widget registration, badge composables, extension nodeCreated hooks) continue to fire. - Fixes FE-214 (<https://linear.app/comfyorg/issue/FE-214/undo-breaks-and-desyncs-qwen-multiangle-camera-ui>) ## Red-Green Verification | Commit | CI Status | Purpose | |--------|-----------|---------| | `test: add failing test for FE-214 undo losing Load3D widget callback chain` | 🔴 Red | Proves the test catches the bug | | `fix: chain Load3D node lifecycle callbacks to preserve widget cleanup` | 🟢 Green | Proves the fix resolves the bug | ## Test Plan - [ ] CI red on test-only commit - [ ] CI green on fix commit - [ ] Manual: load Qwen MultiAngle Camera workflow, mutate camera, press Ctrl+Z, confirm interactive UI stays mounted and value reflects restored state (Vue Nodes 2.0 and LiteGraph) ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-11359-fix-chain-Load3D-node-lifecycle-callbacks-to-preserve-widget-cleanup-3466d73d365081e2b64de65c26ee6abf) by [Unito](https://www.unito.io) |
||
|
|
2fea0aa538 |
fix: trigger Vue reactivity on output slot type changes in matchType (#9935)
## Summary Fix VHS unbatch output slot color not updating when slot types change via matchType resolution in Vue renderer. ## Changes - **What**: After `changeOutputType` mutates `output.type` on objects inside a `shallowReactive` array, spread-copy `this.outputs` to trigger the shallowReactive setter so `SlotConnectionDot` re-evaluates the slot color. ## Review Focus The fix adds `this.outputs = [...this.outputs]` after the matchType resolution loop in `withComfyMatchType`. This forces Vue's shallowReactive proxy to fire, since mutating a property on an object inside the array doesn't trigger the setter. The spread is placed after all outputs are updated to batch the reactivity trigger. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9935-fix-trigger-Vue-reactivity-on-output-slot-type-changes-in-matchType-3246d73d365081c4a293f57931892c61) by [Unito](https://www.unito.io) |
||
|
|
a1ba567dbc |
test: remove --listen 0.0.0.0 from E2E test mock argv (#11021)
## Summary Remove `--listen 0.0.0.0` from mock `argv` in E2E test fixtures to avoid normalizing a flag that exposes the server to all network interfaces. ## Changes - **What**: Removed `--listen` and `0.0.0.0` from `mockSystemStats.system.argv` in `browser_tests/fixtures/data/systemStats.ts` (shared fixture) and the ManagerDialog-specific override in `browser_tests/tests/dialogs/managerDialog.spec.ts`. Neither value is required for any test assertion. Fixes #11008 ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-11021-test-remove-listen-0-0-0-0-from-E2E-test-mock-argv-33e6d73d365081c59d3fe9610afbeb6f) by [Unito](https://www.unito.io) |
||
|
|
d2e30645fe |
[chore] Update Ingest API types from cloud@9b9da80 (#11126)
## Automated Ingest API Type Update This PR updates the Ingest API TypeScript types and Zod schemas from the latest cloud OpenAPI specification. - Cloud commit: 9b9da80 - Generated using @hey-api/openapi-ts with Zod plugin These types cover cloud-only endpoints (workspaces, billing, secrets, assets, tasks, etc.). Overlapping endpoints shared with the local ComfyUI Python backend are excluded. --------- Co-authored-by: MillerMedia <7741082+MillerMedia@users.noreply.github.com> Co-authored-by: GitHub Action <action@github.com> |
||
|
|
fc61b19cb9 |
docs: Weekly Documentation Update (#10739)
# Documentation Accuracy Audit - PR Summary ## Summary Conducted a comprehensive audit of all documentation files against the current codebase. The documentation is **exceptionally well-maintained** with 99%+ accuracy. Only one minor enhancement was needed. - Added missing `pnpm dev:cloud` command to AGENTS.md - Verified all 70+ documentation files for accuracy - Confirmed all API examples, file paths, and configuration references are correct - Validated all script commands match package.json ## Changes Made ### Documentation Updates **File: `AGENTS.md`** - Added `pnpm dev:cloud` to the "Build, Test, and Development Commands" section - This command was documented in CONTRIBUTING.md but missing from AGENTS.md - Command connects dev server to cloud backend (testcloud.comfy.org) ## Audit Scope and Findings ### Areas Audited (All ✅ Verified Accurate) **Core Documentation:** - ✅ `README.md` - All extension API examples verified against source code - ✅ `AGENTS.md` - All scripts, file paths, and patterns verified - ✅ `CLAUDE.md` - References to AGENTS.md confirmed valid - ✅ `CONTRIBUTING.md` - All commands and workflows verified **Configuration Files:** - ✅ `vite.config.mts` - Exists and matches documentation - ✅ `playwright.config.ts` - Exists and matches documentation - ✅ `eslint.config.ts` - Exists and matches documentation - ✅ `.oxfmtrc.json` - Exists and matches documentation - ✅ `.oxlintrc.json` - Exists and matches documentation **Documentation Directories:** - ✅ `docs/guidance/*.md` (6 files) - All code patterns match actual implementations - ✅ `docs/testing/*.md` (5 files) - All testing patterns validated - ✅ `docs/extensions/*.md` (3 files) - Extension APIs verified - ✅ `docs/adr/*.md` (9 files) - All ADRs present and referenced correctly - ✅ `docs/architecture/*.md` (8 files) - Architecture documentation accurate - ✅ `.claude/commands/*.md` (8 files) - All skill documentation verified **README Files:** - ✅ 19 README files throughout repository verified for accuracy **Key Verifications:** 1. **Package.json Scripts** - All documented commands exist: - ✅ `pnpm dev`, `dev:electron`, `build`, `preview` - ✅ `test:unit`, `test:browser:local` - ✅ `lint`, `lint:fix`, `format`, `format:check` - ✅ `typecheck`, `storybook` 2. **File Paths** - All referenced paths verified: - ✅ `src/router.ts`, `src/i18n.ts`, `src/main.ts` - ✅ `src/locales/en/main.json` - ✅ `browser_tests/**/*.spec.ts` - ✅ All component and composable paths 3. **API Examples in README.md** - All validated against source: - ✅ `window['app'].extensionManager.dialog` (v1.6.13 API) - ✅ `app.extensionManager.registerSidebarTab` (v1.2.4 API) - ✅ `bottomPanelTabs` extension field (v1.3.22 API) - ✅ `aboutPageBadges` extension field (v1.3.34 API) - ✅ `getSelectionToolboxCommands` method (v1.10.9 API) - ✅ Settings API migration (v1.3.22) - ✅ Commands and keybindings API (v1.3.7) 4. **Code Patterns** - Documentation matches implementation: - ✅ Vue 3.5+ Composition API patterns - ✅ TypeScript strict mode usage - ✅ Tailwind 4 utility-first approach - ✅ Pinia store patterns - ✅ VueUse composables - ✅ Playwright testing patterns ## Review Notes ### Documentation Quality Assessment The ComfyUI Frontend documentation demonstrates **exceptional quality** across all categories: **Strengths:** 1. **Accuracy** - 99%+ of documented information matches current codebase 2. **Comprehensive Coverage** - All major systems documented 3. **Cross-Referencing** - Documents properly reference each other 4. **Code Examples** - All API examples are working and tested 5. **Maintenance** - Recently updated to reflect latest features 6. **Organization** - Logical structure with guidance by file type **Notable Documentation Excellence:** - `docs/guidance/playwright.md` - Exceptional detail on typed API mocks with source-of-truth table - `docs/extensions/development.md` - Clear explanation of extension shim system - `docs/testing/vitest-patterns.md` - Practical, actionable testing patterns - `README.md` - Comprehensive extension API examples with version tracking - `.agents/checks/adr-compliance.md` - Thorough architectural guardrails ### Minor Observations (Not Issues) 1. **Undocumented Scripts** - These exist but aren't in AGENTS.md (likely intentional): - `pnpm dev:no-vue` - Internal development flag - `pnpm build:desktop`, `pnpm build:cloud` - Distribution-specific builds - `pnpm knip` - Dependency analysis tool - `pnpm stylelint` - CSS linting (mentioned in workflows, not main docs) 2. **Vue Test Utils** - Minor inconsistency: - AGENTS.md says "Vue Test Utils is also accepted" - ESLint rule bans it with message "Use @testing-library/vue instead" - Recommendation: Clarify if VTU is acceptable for existing tests only 3. **Extension Examples** - All working, no changes needed: - PrimeVue icons reference still valid (primevue.org/icons) - Toast API reference accurate (primevue.org/toast) - All extension lifecycle hooks documented correctly ### What Was NOT Changed No changes were made to the following areas as they are all accurate: - README.md extension API examples - Configuration file documentation - Testing documentation patterns - Architecture decision records - Extension development guides - Vue component patterns - TypeScript guidelines - Git conventions - Security guidelines ## Statistics - **Total Files Audited:** 70+ markdown files - **Critical Path Verifications:** 25+ items - **Script Command Verifications:** 15+ commands - **Configuration Files Checked:** 6 files - **API Example Validations:** 10+ examples - **Cross-Reference Validations:** 20+ references - **Files Modified:** 1 (AGENTS.md) - **Lines Added:** 1 - **Issues Found:** 0 critical, 0 high, 0 medium ## Conclusion The documentation is in **excellent condition** and remains highly accurate. This audit confirms that the ComfyUI Frontend team maintains documentation as a first-class citizen alongside code. The single enhancement (adding `pnpm dev:cloud`) improves discoverability of an existing command that was already documented elsewhere. **Recommendation:** This is a model example of documentation quality for other projects to follow. Co-authored-by: christian-byrne <72887196+christian-byrne@users.noreply.github.com> Co-authored-by: Christian Byrne <cbyrne@comfy.org> |
||
|
|
8a5a8f0a6e |
docs: add hyperlinks to all supporting files in ADR 008 (#11256)
*PR Created by the Glary-Bot Agent* --- ## Summary ADR 008 (Entity Component System) referenced only 3 of 10 companion architecture documents, making the rest undiscoverable to readers browsing the design. - Add inline contextual links in Context, Systems, and Migration Strategy sections so readers encounter them while reading - Add a comprehensive Supporting Documents table before Notes as a complete index of all 10 companion docs Previously unlinked files now referenced: - `entity-interactions.md` — current entity relationship map - `entity-problems.md` — structural problem catalog - `proto-ecs-stores.md` — existing stores partially implementing ECS - `ecs-target-architecture.md` — full target architecture - `ecs-migration-plan.md` — phased migration roadmap - `ecs-lifecycle-scenarios.md` — lifecycle operation walkthroughs - `appendix-critical-analysis.md` — document accuracy verification - `change-tracker.md` — current undo/redo system ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-11256-docs-add-hyperlinks-to-all-supporting-files-in-ADR-008-3436d73d365081828cf9ffa77e034f2d) by [Unito](https://www.unito.io) Co-authored-by: Glary-Bot <glary-bot@users.noreply.github.com> |
||
|
|
0638e8e993 |
test: add unit tests for SceneModelManager (#11392)
## Summary Add 44 unit tests for `SceneModelManager` in the 3D viewer (`src/extensions/core/load3d/`). ## Changes - **What**: New test file `SceneModelManager.test.ts` covering constructor, dispose, createSTLMaterial, addModelToScene, setupModel, setOriginalModel, clearModel, reset, setMaterialMode (all 5 modes), setupModelMaterials, setUpDirection (all 7 directions), hasSkeleton, setShowSkeleton, containsSplatMesh, and PLY mode switching (point cloud, wireframe, vertex colors, cleanup). ## Review Focus - Test coverage of PLY mode switching edge cases (vertex colors, old model cleanup) - Mock strategy for WebGLRenderer (happy-dom cannot instantiate it) - SplatMesh mock leverages the existing global mock in `vitest.setup.ts` ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-11392-test-add-unit-tests-for-SceneModelManager-3476d73d3650819097f3f6d73d8fbe02) by [Unito](https://www.unito.io) |
||
|
|
07ce7123c8 |
test: cover useErrorActions and useErrorReport (#11320)
Closes coverage gaps in \`src/components/rightSidePanel/errors/\` as
part of the unit-test backfill.
## Testing focus
\`useErrorActions\` is thin (telemetry + command + \`window.open\`), but
\`useErrorReport\` is a real async watcher with multiple store
dependencies, \`@vueuse/core\`'s \`until(...)\`, and a cancellation
guard. The tricky part is keeping \`until\` reactive without mocking
\`@vueuse/core\`.
### \`useErrorActions\` (8 tests)
- Three functions × telemetry-fired × command/window invocation × the
\`telemetry?.\` null-safe branch.
- \`findOnGitHub\` encoding: verifies \`encodeURIComponent\` runs on the
error message and \` is:issue\` is appended.
- \`window.open\` stubbed via \`vi.spyOn\`, restored in \`afterEach\`.
### \`useErrorReport\` (9 tests)
- **Reactive \`until()\`.** \`@vueuse/core\` is **not** mocked. The
\`useSystemStatsStore\` mock creates real Vue \`ref\`s and exposes them
via getter/setter so \`until(() => isLoading).toBe(false)\` resolves
through actual reactivity.
- **\`__setSystemStats\` / \`__setIsLoading\` helpers** on the mocked
store let tests mutate state from the outside without leaking global
mutable state beyond \`vi.hoisted\`.
- **Cancellation guard.** Manually-resolvable deferred \`getLogs\`
promise — while it's pending, the \`cardSource\` ref is swapped. The
previous run's results must **not** mutate \`enrichedDetails\`.
Regressions here would cause race-dependent UI state when users switch
between error cards quickly.
- **Fallback paths.** Missing \`exceptionType\` →
\`FALLBACK_EXCEPTION_TYPE\` ('Runtime Error'). \`serialize()\` throws →
early return. \`generateErrorReport\` throws → \`displayedDetailsMap\`
falls back to the raw \`error.details\`.
- **Watcher cleanup.** Swapping the card ref clears stale
\`enrichedDetails\` before re-enrichment.
- \`console.warn\` spy suppresses noise; restored in \`afterEach\`.
## Principles applied
- No mocks of \`vue\` or \`@vueuse/core\` — only our own modules
(\`api\`, \`app\`, \`systemStatsStore\`, \`errorReportUtil\`).
- \`@vue/test-utils\` isn't installed; a local \`flushPromises\` helper
is used (matches the existing pattern in
\`useNodeHelpContent.test.ts\`).
- All 17 tests pass; typecheck/lint/format clean. Test-only; no
production code touched.
|
||
|
|
799ffcf4b6 |
test: cover useWorkspaceUI and useWorkspaceBilling (#11319)
Closes coverage gaps in \`src/platform/workspace/composables/\` as part of the unit-test backfill. ## Testing focus \`useWorkspaceUI\` is wrapped in \`createSharedComposable\` (shared instance across all callers). \`useWorkspaceBilling\` is a large stateful composable: parallel API calls, exponential-backoff polling, computed mappers, lifecycle cleanup. Both need careful state isolation and real lifecycle behavior — not faked hooks. ### \`useWorkspaceUI\` (8 tests) - **Permission / UI-config matrix.** Three role/type combinations — (personal × any), (team × owner), (team × member) — plus the no-active-workspace default. Assertions target concrete flags that differ per role (the table itself is the contract), not the return shape. - **\`createSharedComposable\` identity invariant.** Multiple calls return the same instance. - **Isolation.** Each test uses \`vi.resetModules()\` to get a fresh shared instance so the memoization doesn't leak between cases. ### \`useWorkspaceBilling\` (23 tests) - **Parallel init.** \`initialize\` runs \`Promise.all([status, balance, plans])\` concurrently, then re-fetches balance when free-tier shows a zero amount (lazy credit grant path). - **Polling with fake timers.** \`cancelSubscription\`'s exponential backoff (\`1000 * 2^attempt\`, max 5000ms) driven by \`vi.useFakeTimers()\` + \`advanceTimersByTimeAsync()\`. Covers success, failure, and the unmount-stops-polling case. - **Real lifecycle.** \`onBeforeUnmount\` only fires inside a component instance — not inside a raw \`effectScope\`. The unmount test mounts a minimal Vue app via \`createApp\` / \`app.unmount\` so the production cleanup path actually runs. - **Computed getter mapping.** \`subscription\`, \`balance\`, \`isActiveSubscription\`, \`isFreeTier\` assert the snake_case API shape is remapped to the camelCase UI shape correctly. - **\`window\` effects.** \`window.open\` stubbed via \`vi.spyOn\`, \`window.location.href\` via \`vi.stubGlobal\`. Restored in \`afterEach\`. ## Principles applied - No mocks of \`vue\` or \`@vueuse/core\` — only our own workspace API, stores, and sibling composables. - Behavioral assertions only. - All 31 tests pass; typecheck/lint/format clean. Test-only; no production code touched. |
||
|
|
1020e8cf32 |
1.44.5 (#11213)
Patch version increment to 1.44.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> Co-authored-by: Christian Byrne <cbyrne@comfy.org>v1.44.5 |
||
|
|
b157182a20 |
refactor: inline node footer layout to fix selection bounding box (#10741)
## Summary Refactor node footer from absolute overlay to inline flow layout, fixing the selection bounding box not encompassing footer buttons and collapsed node dimensions. ## Background The node footer (Enter Subgraph, Advanced, Error buttons) was rendered as an absolute overlay (`absolute top-full`) outside the node body. This caused: 1. **Selection bounding box** did not include footer height — the dashed multi-select border cut through footer buttons 2. **Footer offset compensation** required 3 hardcoded computed classes (`footerStateOutlineBottomClass`, `footerRootBorderBottomClass`, `footerResizeHandleBottomClass`) with magic pixel values (31px, 35px, etc.) that had to stay in sync with CSS ## Solution: Inline Footer with `isolate -z-1` The footer is moved into normal document flow (no longer `absolute top-full`). The key challenge was keeping the footer visually behind the body's rounded bottom edge (the "tuck under" effect) without adding `z-index` to the body — because adding `z-index` to the body creates a stacking context that traps slot connection dots, making them appear behind overlay borders. The solution uses CSS `isolation: isolate` combined with `-z-1` on the footer wrapper: - **`isolate`** creates an independent stacking context for the footer, so internal z-index (Error button `z-10` above Enter button) does not leak to the parent - **`-z-1`** places the entire footer behind the body (`z-index: auto`), achieving the visual overlap without touching the body's stacking behavior - **Slot dots remain free** — the body has no explicit z-index, so slots participate in the root stacking context and are never trapped behind overlay borders This eliminates all 3 footer offset computed classes and their hardcoded pixel values. ## Selection Box: `min-height` on root + unified size path Moving `min-h-(--node-height)` from the body (`node-inner-wrapper`) to the root element makes the footer height naturally included in `node.size` via ResizeObserver → layoutStore → litegraph sync. This means `boundingRect` is automatically correct for expanded nodes — no callbacks or overrides needed. For collapsed nodes, a pre-existing issue (since v1.40) caused `_collapsed_width` to fall back to `NODE_COLLAPSED_WIDTH = 80px` because Vue nodes lack a canvas context for text measurement. The fix lets collapsed dimensions flow through the **same** `batchUpdateNodeBounds` path as expanded nodes — no parallel data structure, no separate accessor, no cache: 1. ResizeObserver writes the collapsed DOM dimensions to `layoutStore.size` via `batchUpdateNodeBounds` 2. `useLayoutSync` syncs `layoutStore.size` → `liteNode.size` as it does for any other size change 3. The expanded size survives the collapse→expand round trip via CSS custom properties — the `isCollapsed` watcher in `LGraphNode.vue` swaps `--node-width` to `--node-width-x` on collapse and restores it on expand 4. `measure()` reads `this.size` directly for Vue collapsed nodes via a one-line gate: `if (!this.flags?.collapsed || LiteGraph.vueNodesMode)`. Legacy behavior is unchanged. ## Changes - **NodeFooter.vue**: `absolute top-full` overlay → inline flow with `isolate -z-1` wrappers, Error/Enter button layering via `-mr-5` + DOM order, reactive props destructuring, static `RADIUS_CLASS` lookup for Tailwind scanning, Vue 3.3+ `defineEmits` property syntax - **LGraphNode.vue**: Move `min-h-(--node-height)` from body to root; remove `footerStateOutlineBottomClass`, `footerRootBorderBottomClass`, `footerResizeHandleBottomClass`, `hasFooter` computed; replace dynamic `beforeShapeClass` interpolation with static `bypassOverlayClass`/`mutedOverlayClass` computeds for Tailwind scanning - **LGraphNode.ts**: `measure()` collapsed branch gated by `|| LiteGraph.vueNodesMode` — Vue mode defers to `this.size`; legacy path unchanged - **useVueNodeResizeTracking.ts**: Collapsed and expanded nodes both flow through `batchUpdateNodeBounds`; narrowed `useVueElementTracking` parameter from `MaybeRefOrGetter<string>` to `string`; `deferredElements.delete(element)` on unmount to prevent memory retention - **selectionBorder.ts**: Unchanged — `createBounds` just works because `boundingRect` is now correct - **12 parameterized E2E tests**: Vue mode (subgraph/regular × expanded/collapsed × bottom-left/bottom-right) + legacy mode (expanded/collapsed × bottom-left/bottom-right), driven by `keyboard.collapse()` (Alt+C) - **Unit tests**: `measure()` branching (legacy fallback, Vue `this.size` usage, expanded parity) - **Shared test helpers**: `repositionNodes`, `KeyboardHelper.collapse`, `measureSelectionBounds`, `assertSelectionEncompassesNodes` ## Review Focus - `isolate -z-1` CSS layering pattern — is this acceptable long-term? - `measure()` collapsed branch gated on `LiteGraph.vueNodesMode` — one-line gate to avoid the canvas-ctx-less fallback in Vue mode - Footer button overlap design (`-mr-5` with DOM order for painting) ## Screenshots <img width="1392" height="800" alt="image" src="https://github.com/user-attachments/assets/abaebff5-bb8c-4b5b-8734-8d44fdee4cb9" /> <img width="1493" height="872" alt="image" src="https://github.com/user-attachments/assets/6b9c77f9-e3ae-4d4e-81dc-acfa9a24c768" /> <img width="813" height="515" alt="image" src="https://github.com/user-attachments/assets/ce15bafb-e157-408c-971b-a650088f316a" /> <img width="1031" height="669" alt="image" src="https://github.com/user-attachments/assets/20fdc336-4bc2-4d47-ab7e-c0cbcee0d150" /> <img width="753" height="525" alt="image" src="https://github.com/user-attachments/assets/2dccbe31-7d18-49bc-9ed4-158b1659fddf" /> <img width="730" height="370" alt="image" src="https://github.com/user-attachments/assets/ab87edfa-a4b4-46f7-86ae-4965a4509b42" /> <img width="1132" height="465" alt="image" src="https://github.com/user-attachments/assets/54643f5b-4a31-4c3d-9475-c433f87aedb0" /> <img width="1102" height="449" alt="image" src="https://github.com/user-attachments/assets/9c045df3-e1f5-481e-b1cb-ead1db1626f5" /> --------- Co-authored-by: github-actions <github-actions@github.com> |
||
|
|
2bfe3443ab |
[chore] Update Comfy Registry API types from comfy-api@8b5b293 (#11334)
## Automated API Type Update This PR updates the Comfy Registry API types from the latest comfy-api OpenAPI specification. - API commit: 8b5b293 - Generated on: 2026-04-16T22:08:45Z These types are automatically generated using openapi-typescript. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-11334-chore-Update-Comfy-Registry-API-types-from-comfy-api-8b5b293-3456d73d365081e9ae7fc5a98bdfe194) by [Unito](https://www.unito.io) Co-authored-by: coderfromthenorth93 <213232275+coderfromthenorth93@users.noreply.github.com> |
||
|
|
4c35add5bc |
feat: add civitai.red hostname support (#11349)
*PR Created by the Glary-Bot Agent* --- ## Summary Civitai split its domain — NSFW content moved to `civitai.red` while `civitai.com` stays SFW. The frontend only recognized `civitai.com` URLs, causing the import button to silently reject `.red` links. This was the root cause of 8+ support tickets in 3 days. Companion to backend PR: https://github.com/Comfy-Org/cloud/pull/3259 ## Changes ### Import source recognition - **`civitaiImportSource.ts`**: Added `'civitai.red'` to `hostnames` array — this is the primary fix for "button doesn't recognize the links" ### Missing model auto-download - **`missingModelDownload.ts`**: Added `'https://civitai.red/'` to `ALLOWED_SOURCES` ### URL detection utilities - **`formatUtil.ts`**: `isCivitaiModelUrl()` now accepts `civitai.red` URLs with proper hostname validation - **`assetMetadataUtils.ts`**: `getSourceName()` returns "Civitai" for `.red` URLs ### Tests (4 files) - `useUploadModelWizard.test.ts`: Added civitai.red hostnames and URL test case - `missingModelDownload.test.ts`: Added civitai.red cases for `toBrowsableUrl` and `isModelDownloadable` - `assetMetadataUtils.test.ts`: Added civitai.red case for `getSourceName` - `useMissingModelInteractions.test.ts`: Updated mock hostnames - `formatUtil.test.ts`: Added civitai.red cases for `isCivitaiModelUrl` ## Not changed (intentionally) - `getAssetSourceUrl()` ARN fallback (line 88) — ARNs don't carry domain info, `civitai.com` is correct default - `fetchCivitaiMetadata()` API URL (line 109) — REST API works on both domains, keeping `civitai.com` Resolves BE-353 ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-11349-feat-add-civitai-red-hostname-support-3456d73d3650810d9c62ef4ad95ae031) by [Unito](https://www.unito.io) --------- Co-authored-by: Glary-Bot <glary-bot@users.noreply.github.com> Co-authored-by: Christian Byrne <cbyrne@comfy.org> Co-authored-by: Terry Jia <terryjia88@gmail.com> |
||
|
|
a3893a593d |
refactor: move select components from input/ to ui/ component library (#11378)
*PR Created by the Glary-Bot Agent* --- ## Summary Reconciles `src/components/input/` (older select components) into `src/components/ui/` (internal component library), eliminating the separate `input/` directory entirely. ## Changes - **Move MultiSelect** → `src/components/ui/multi-select/MultiSelect.vue` - **Move SingleSelect** → `src/components/ui/single-select/SingleSelect.vue` - **Extract shared resources** → `src/components/ui/select/types.ts` (SelectOption type) and `src/components/ui/select/select.variants.ts` (CVA styling variants) - **Update 7 consuming files** to use new import paths - **Update 1 test file** (AssetFilterBar.test.ts mock paths) - **Move stories and tests** alongside their components - **Delete `src/components/input/`** directory ## Context The `input/` directory contained only MultiSelect and SingleSelect — two well-built components that already used the same stack as `ui/` (Reka UI, CVA, Tailwind 4, Composition API). MultiSelect even imported `ui/button/Button.vue`. Moving them into `ui/` removes the split and consolidates all reusable components in one place. No API changes — all component props, slots, events, and behavior are preserved exactly. ## Verification - `pnpm typecheck` ✅ - `pnpm build` ✅ - `pnpm lint` (stylelint + oxlint + eslint) ✅ - All 15 relevant tests pass (MultiSelect: 5, SingleSelect: 2, AssetFilterBar: 8) ✅ - `pnpm knip` — no dead exports ✅ - No stale `@/components/input/` references remain ✅ - Pre-commit hooks pass ✅ - Git detected all moves as renames (97-100% similarity) ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-11378-refactor-move-select-components-from-input-to-ui-component-library-3476d73d3650810e99b4c3e0842e67f3) by [Unito](https://www.unito.io) Co-authored-by: Glary-Bot <glary-bot@users.noreply.github.com> |
||
|
|
deba72e7a0 |
gizmo controls (#11274)
## Summary Add Gizmo transform controls to load3d - Remove automatic model normalization (scale + center) on load; models now appear at their original transform. The previous auto-normalization conflicted with gizmo controls — applying scale/position on load made it impossible to track and reset the user's intentional transform edits vs. the system's normalization - Add a manual Fit to Viewer button that performs the same normalization on demand, giving users explicit control - Add Gizmo Controls (translate/rotate) for interactive model manipulation with full state persistence across node properties, viewer dialog, and model reloads - Gizmo transform state is excluded from scene capture and recording to keep outputs clean ## Motivation The gizmo system is a prerequisite for these potential features: - Custom cameras — user-placed cameras in the scene need transform gizmos for precise positioning and orientation - Custom lights — scene lighting setup requires the ability to interactively position and aim light sources - Multi-object scene composition — positioning multiple models relative to each other requires per-object transform controls - Pose editor — skeletal pose editing depends on the same transform infrastructure to manipulate individual bones/joints Auto-normalization was removed because it silently mutated model transforms on load, making it impossible to distinguish between the original model pose and user edits. This broke gizmo reset (which needs to know the "clean" state) and would corrupt round-trip transform persistence. ## Screenshots (if applicable) https://github.com/user-attachments/assets/621ea559-d7c8-4c5a-a727-98e6a4130b66 ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-11274-gizmo-controls-3436d73d365081c38357c2d58e49c558) by [Unito](https://www.unito.io) |
||
|
|
3db0eac353 |
perf: textarea widget layer composition (#10804)
## Summary I noticed that nodes using textarea for user input, which contain long user-entered text, require scrolling within a single node. Having 40 such textarea nodes in a test canvas is enough to cause lag (20fps). In contrast, a control group using regular nodes can handle up to 500 nodes without lag (60fps). the numerous scrolling text widgets in test workflows are the main source of performance pressure. Each scrolling text input box imposes independent layout and layering pressure. I initially tried more complex solutions to fix this issue, like virtual scrolling. However, I found that a simple CSS modification was sufficient and effective. Even when I quadrupled the problematic number of nodes on my M5 MacBook Air, it remained smooth. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-10804-perf-textarea-widget-layer-composition-3356d73d3650814da75adec266d7cad9) by [Unito](https://www.unito.io) --------- Co-authored-by: bymyself <cbyrne@comfy.org> |
||
|
|
4c7729ee0b |
fix: remove hover dimming overlay on image nodes (#11296)
## Summary Remove the black opacity/dimming overlay on image node hover and add shadows to action buttons for visibility against light backgrounds. ## Changes - **What**: Remove `opacity-50` dimming on hover in `DisplayCarousel.vue`, remove `transition-opacity hover:opacity-80` from grid thumbnails in `ImagePreview.vue`, add `shadow-md` to action buttons in `ImagePreview.vue`. Applies to Save Image, Load Image, Preview Image, and all nodes using these shared image components. ## Review Focus Button shadows (`shadow-md`) should provide sufficient contrast against light image backgrounds without needing the dimming overlay. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-11296-fix-remove-hover-dimming-overlay-on-image-nodes-3446d73d36508193bb5cc27d431014fd) by [Unito](https://www.unito.io) |
||
|
|
40083d593b |
test: cover Button, Textarea, Slider components (#11325)
Closes coverage gaps in \`src/components/ui/\` as part of the unit-test
backfill. Uses \`@testing-library/vue\` +
\`@testing-library/user-event\` for user-centric, behavioral assertions.
## Testing focus
Three Reka-UI primitives. The challenge is testing the contract — not
the library internals — given happy-dom's gaps and Reka's
\`useMounted()\`-based async initialization.
### \`Button\` (7 tests)
- Slot rendering + click event propagation.
- \`loading=true\`: three invariants hold **simultaneously** — slot
hidden, \`pi-spin\` spinner present, button is \`toBeDisabled()\`.
- \`disabled=true\` alone: button disabled, no spinner.
- \`as="a"\`: polymorphic root tag (Reka \`Primitive\`'s \`as\` prop
switches the rendered element).
- Variant class pass-through: **one** deliberate style assertion because
the variant-system wiring is part of the component's public contract. No
other styling/class checks (AGENTS.md bans class-based tests).
### \`Textarea\` (6 tests)
- \`v-model\` two-way binding: \`user.type()\` updates the bound ref;
initial value populates the textarea.
- \`disabled\` asserted **behaviorally** — typing is blocked when
disabled, not just the attribute presence.
- Pass-through: \`placeholder\`, \`rows\`, \`class\`.
### \`Slider\` (8 tests)
- Thumb count matches \`modelValue.length\` (range support).
- ARIA: \`aria-valuemin\` / \`aria-valuemax\` / \`aria-valuenow\`.
**Caveat:** Reka's \`SliderRoot\` uses \`useMounted()\`, so
\`aria-valuenow\` is absent on the first render tick. The tests use a
two-tick \`flush()\` helper (\`await nextTick()\` twice) to wait it out
— no mocking of Reka required.
- Keyboard drag: \`user.keyboard('{ArrowRight}')\` / \`'{ArrowLeft}'\`
moves the value; with \`step: 10\` starting from 50, ArrowRight produces
exactly \`[60]\`.
- \`disabled\` → no emit on keyboard events.
### Reka integration limit
Pointer-driven \`slide-start\` / \`slide-end\` gestures in happy-dom
would require faking \`getBoundingClientRect\` and \`setPointerCapture\`
— that crosses into mocking Reka internals. Keyboard-drag paths are
covered instead (the user-facing contract); the \`pressed\` CSS state is
exercised implicitly by surviving a full mount + update cycle.
## Principles applied
- No mocks of Vue, Reka, or \`@vueuse/core\`.
- Queries via \`getByRole\` / \`getByLabelText\`; **no** class-name or
Tailwind-token queries (per AGENTS.md).
- All 21 tests pass; typecheck/lint/format clean. Test-only; no
production code touched.
|
||
|
|
7089a7d1a0 |
fix: show asset display names in bulk delete confirmation (#11321)
## Summary Bulk-delete confirmation on Comfy Cloud listed raw SHA-256 filenames, making the modal impossible to use to verify what would be deleted. ## Changes - **What**: `useMediaAssetActions.deleteAssets` now maps each asset through `getAssetDisplayName`, so the confirmation's `itemList` matches the user-assigned names shown in the left media panel (`MediaAssetCard`). - **Tests**: Added two regression tests covering `user_metadata.name` / `display_name` resolution and the `asset.name` fallback. ## Review Focus - Parity with `MediaAssetCard` display: we reuse the same `getAssetDisplayName` helper; extension stripping (via `getFilenameDetails`) is not applied in the modal since file extensions are useful context when confirming deletions. Reported in Slack: https://comfy-organization.slack.com/archives/C0A4XMHANP3/p1776383570015289 ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-11321-fix-show-asset-display-names-in-bulk-delete-confirmation-3456d73d36508108a3d5f2290ca39e18) by [Unito](https://www.unito.io) |
||
|
|
3b4811b00d |
feat: deploy E2E coverage HTML report to GitHub Pages (#11291)
## Summary Browsable E2E coverage report deployed to GitHub Pages on every main merge, replacing the current workflow of downloading LCOV artifacts and using an external viewer. ## Changes - **What**: After merging shard LCOVs, run `genhtml` to produce an HTML report with per-file line coverage. On `main`, deploy to GitHub Pages via `actions/deploy-pages`. For PR runs, the HTML report is still available as the `e2e-coverage-html` artifact. - **Dependencies**: None new — `genhtml` is part of the `lcov` package already installed in the workflow. ## Review Focus - **GitHub Pages must be enabled**: Settings → Pages → Source → "GitHub Actions". Without this the deploy job will fail silently. - The deploy job only runs for `main` branch (`if: github.event.workflow_run.head_branch == 'main'`) so PR coverage doesn't clobber the deployed report. - Added `pages: write` and `id-token: write` permissions to the workflow for the Pages deployment. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-11291-feat-deploy-E2E-coverage-HTML-report-to-GitHub-Pages-3446d73d36508136ba6fd806690c9cfc) by [Unito](https://www.unito.io) --------- Co-authored-by: Alexander Brown <drjkl@comfy.org> |
||
|
|
b756545f59 |
refactor: clean up ChangeTracker logging, guards, and redundant widget wrapper (#11328)
## Summary Follow-ups to PR #10816. Bundles four review items left open after that PR merged — three inside `ChangeTracker` itself and one in the widget composable that wraps it. ### What changed - **Removed all `loglevel` logging from `src/scripts/changeTracker.ts`** — the logger was set to `info`, so every `logger.debug` call was dead code at runtime. `logger.warn` calls were replaced with direct reporting. The only-downstream dead code (`graphDiff` helper) and its sole dependency (`jsondiffpatch`) are also removed. - **Named the `captureCanvasState()` guard conditions** — `isUndoRedoing` and `isInsideChangeTransaction` now carry the intent that the inline `_restoringState` / `changeCount > 0` expressions used to obscure. - **Surfaced lifecycle violations through a single reporting helper** — `reportInactiveTrackerCall()` logs `console.warn` once per method per session and, on Desktop, emits a `Sentry.addBreadcrumb` with the offending workflow path. `deactivate()` and `captureCanvasState()` share this path so the same invariant is reported consistently. - **Inlined `captureWorkflowState` wrapper in `useWidgetSelectActions`** — the private helper forwarded to `changeTracker.captureCanvasState()` with no added logic. Both call sites now invoke the change tracker directly. ### Issues fixed - Fixes #11249 - Fixes #11259 - Fixes #11258 - Fixes #11248 ### Test plan - [x] `pnpm test:unit src/scripts/changeTracker.test.ts` — 16 tests pass - [x] `pnpm test:unit src/renderer/extensions/vueNodes/widgets/composables/useWidgetSelectActions.test.ts` — 6 tests pass - [x] `pnpm typecheck` - [x] `pnpm lint` - [x] `pnpm format` |
||
|
|
da91bdc957 |
fix: persist middle-click reroute node setting across reloads (#11362)
*PR Created by the Glary-Bot Agent* --- ## Summary - Remove hardcoded `LiteGraph.middle_click_slot_add_default_node = true` from `slotDefaults` extension `init()` that unconditionally overrode the user's persisted preference on every page load - Add E2E regression test verifying both the setting store value and the LiteGraph runtime flag persist through page reload ## Root Cause The `Comfy.SlotDefaults` extension's `init()` method (in `slotDefaults.ts`) contained a hardcoded `LiteGraph.middle_click_slot_add_default_node = true` from the original JS→TS conversion (July 2024). When `Comfy.Node.MiddleClickRerouteNode` was later made configurable in v1.3.42, this line was never removed. Since extension `init()` runs **after** `useLitegraphSettings()` syncs the stored value, the hardcoded assignment overwrote the user's preference on every reload. ## Changes | File | Change | |------|--------| | `src/extensions/core/slotDefaults.ts` | Remove line 21 (`LiteGraph.middle_click_slot_add_default_node = true`) | | `browser_tests/tests/dialogs/settingsDialog.spec.ts` | Add reload persistence test asserting both store value and LiteGraph global | The setting default (`true`) is already properly managed by `coreSettings.ts` and reactively synced via `useLitegraphSettings.ts`, so removing the hardcoded line preserves existing default behavior while allowing user overrides to persist. ## Screenshots    ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-11362-fix-persist-middle-click-reroute-node-setting-across-reloads-3466d73d365081ef8692dbd0619c8594) by [Unito](https://www.unito.io) Co-authored-by: Glary-Bot <glary-bot@users.noreply.github.com> |
||
|
|
cf3006f82c |
fix: reduce noise in coverage Slack notifications (#11283)
## Summary Suppress low-signal coverage Slack notifications that show +0.0% or -0.0% deltas. ## Changes - **What**: Add `MIN_DELTA` threshold (0.05%) so only meaningful improvements trigger notifications. Only display rows for metrics that actually improved (no more E2E row showing -0.0% alongside a real unit improvement). Fix `formatDelta` to clamp near-zero values to `+0.0%` instead of showing `-0.0%`. - 4 of the first 6 notifications posted were noise (+0.0% deltas from instrumentation jitter). With this change, only 2 of 6 would have been posted — both showing real improvements. ## Review Focus The `MIN_DELTA` value of 0.05 means any delta that rounds to ±0.0% at 1 decimal place is suppressed. This matches the display precision so users never see +0.0% notifications. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-11283-fix-reduce-noise-in-coverage-Slack-notifications-3436d73d3650819ab3bcfebdb748ac8b) by [Unito](https://www.unito.io) |
||
|
|
be2d757c47 |
test: add regression test for getCanvasCenter null guard (#8399) (#11271)
## Summary Add a regression test for #8399 (null check in `getCanvasCenter` to prevent crash on asset insert). The fix in `src/services/litegraphService.ts` added optional chaining around `app.canvas?.ds?.visible_area` with a `[0, 0]` fallback so inserting an asset before the canvas finishes initializing no longer crashes. There was no existing unit test for `litegraphService`, so this regression could silently return. ## Changes - **What**: New unit test file `src/services/litegraphService.test.ts` covering `useLitegraphService().getCanvasCenter`. - Mocks `@/scripts/app` so `app.canvas` can be swapped per test via `Reflect.set`. - Null-canvas case (regression for #8399): returns `[0, 0]` instead of throwing. - Missing `ds.visible_area` case: also returns `[0, 0]`. - Initialised case: returns the centre of the visible area. - Verified RED→GREEN locally. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-11271-test-add-regression-test-for-getCanvasCenter-null-guard-8399-3436d73d3650815c9925c8fdf9ec4bd3) by [Unito](https://www.unito.io) |
||
|
|
54f3127658 |
test: regenerate screenshot expectations (#11360)
## Summary regenerate screenshot expectations ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-11360-test-regenerate-screenshot-expectations-3466d73d365081878addd53a266a31b7) by [Unito](https://www.unito.io) --------- Co-authored-by: github-actions <github-actions@github.com> |
||
|
|
6dba67da6b |
refactor: remove @ts-expect-error suppressions in sidebar components (#11338)
…(issue #11092 phase 4a)
## Summary
Part of #11092 — Phase 4a: remove 10 @ts-expect-error suppressions from
three sidebar component files.
## Changes
3 files in the sidebar had `@ts-expect-error` suppressions that all
traced back to the same root cause: **optional properties on generic
interfaces that TypeScript cannot narrow through indirect conditions.**
`TreeExplorerNode<T>` declares `data?: T` — optional by design, since
folder nodes may carry no payload. Every `handleClick`, `handleDrop`,
and `handleDelete` method that accessed `this.data` was relying on the
runtime invariant that leaf nodes always have data, but TypeScript has
no way to derive `data !== undefined` from `this.leaf === true`. The fix
was to make the invariant explicit in the condition (`if (this.leaf &&
this.data)`) or add an early-return guard (`if (!nodeDefToAdd) return`).
The same pattern appeared in a closure in `ModelLibrarySidebarTab.vue`:
`model` was `ComfyModelDef | null` from an outer const, and `if
(this.leaf)` inside a method cannot narrow a captured variable. Widening
the condition to `if (this.leaf && model)` resolved it. Two additional
suppressions in that file covered `addNodeOnGraph`'s nullable return and
its optional `widgets` property, both fixed with optional chaining.
The remaining suppression was an unannotated function parameter inferred
as `any`; adding the explicit type from the `filters` ref removed it.
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> **Low Risk**
> Low risk: changes are TypeScript-safety refactors (extra
null/undefined guards) plus new unit tests; runtime behavior should only
differ in edge cases where `data`/`model`/`widgets` are unexpectedly
missing.
>
> **Overview**
> Removes several `@ts-expect-error` suppressions in sidebar library
tabs by making leaf-node invariants explicit (`if (this.leaf &&
this.data/model)`), adding early returns for missing drag-drop payloads,
and using optional chaining for nullable `addNodeOnGraph`/`widgets`
access.
>
> Adds new Vitest coverage for `ModelLibrarySidebarTab`,
`NodeLibrarySidebarTab`, and `NodeBookmarkTreeExplorer` to validate
click-to-add-node behavior, folder expansion toggling, filter add/remove
flow, bookmark drag/drop, and safe no-op paths when required data is
absent.
>
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
|
||
|
|
beaa269a63 |
feat: polish settings dialog layout and keybinding display (#11241)
Polish keybinding display. Based on #11212 with adjustments: left-aligned content (no centering), key uppercase moved to UI layer. - Reduce settings content font size to 14px - Increase spacing between setting sections with cleaner dividers - Consistent min-height for form items (toggle, slider, dropdown) - Capitalize keybinding badges via CSS `uppercase` instead of mutating data model - Remove '+' separator between keybinding badges - Unbold keybinding badges with fixed min-width Supersedes #11212 ┆Issue is synchronized with this [Notion page](https://app.notion.com/p/PR-11241-feat-polish-settings-dialog-layout-and-keybinding-display-3426d73d3650812a97e4d941a76a4fe9) by [Unito](https://www.unito.io) --------- Co-authored-by: Alex <alex@Mac.localdomain> Co-authored-by: github-actions <github-actions@github.com> |
||
|
|
cf98013c18 |
test: expand Image Crop E2E and fix loading overlay deadlock (#11193)
## Summary
Expands Playwright coverage for the **ImageCropV2** widget (Levels 1–3
from the image crop E2E plan), fixes **loading / image mount** behavior
when `imageUrl` changes, adds **stable resize-handle selectors**, and
adds a **small Vitest** for URL→loading transitions.
## Changes
- [x] **Level 1 (E2E)** — Empty state: assert resize handle hidden;
screenshot baseline `image-crop-empty-state.png`; pointer drag on empty
state does not change widget bounds.
- [x] **Level 1 (E2E)** — After run: assert **8 visible** resize handles
with `data-testid` + `filter({ visible: true })`; broken `img.src`
returns to empty state (`crop-empty-state`, no overlay).
- [x] **Level 1 (E2E)** — **Slow `/api/view`** route (delay only
`example.png`) to assert **“Loading…”** then hidden after image loads;
comment clarifies delay is in the route handler, not
`page.waitForTimeout`.
- [x] **Level 2 (E2E)** — Drag clamps to **right/bottom** and
**top-left** image bounds via `setCropBounds` + `expect.poll` on natural
bounds.
- [x] **Level 3 (E2E)** — Free resize: right / left / bottom / top
edges; SE and NW corners; `MIN_CROP_SIZE` (16px); right-edge boundary
clamp; **8 handles** screenshot `image-crop-eight-handles.png`; SE/NW
screenshots (`image-crop-resize-se.png`, `image-crop-resize-nw.png`).
- [x] **E2E helpers** — Shared `getCropValue`, `setCropBounds`,
`dragOnLocator`, `POINTER_OPTS`; drag regression uses **`expect.poll`**
instead of `toPass` where appropriate.
- [x] **`WidgetImageCrop.vue`** — When `imageUrl` is set, **always
render `<img>`**; show loading as an **absolute overlay** (fixes
deadlock where `isLoading` blocked `<img>` so `@load` never ran); add
**`data-testid="crop-resize-{direction}"`** on resize handles.
- [x] **`useImageCrop.ts`** — Watch `imageUrl` and drive `isLoading`;
extract **`imageCropLoadingAfterUrlChange`** (`boolean | null`) for
clear semantics and tests.
- [x] **`useImageCrop.test.ts`** — Vitest coverage for
`imageCropLoadingAfterUrlChange` (null URL, URL change, first URL,
unchanged URL).
## Screenshot / CI notes
- [ ] **Linux screenshot expectations** for new/updated
`toHaveScreenshot(...)` names must be produced on **CI (Linux)** — add
the **`New Browser Test Expectation`** label (or equivalent workflow);
**do not** commit local **Darwin** golden files.
- [x] Existing Linux baselines under `imageCrop.spec.ts-snapshots/` for
prior tests are unchanged where applicable; new baselines are expected
from CI after merge workflow.
## Files
- [x] `browser_tests/tests/vueNodes/widgets/imageCrop.spec.ts`
- [x] `src/components/imagecrop/WidgetImageCrop.vue`
- [x] `src/composables/useImageCrop.ts`
- [x] `src/composables/useImageCrop.test.ts` (new)
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> **Medium Risk**
> Touches interactive crop UI rendering and `isLoading` state
transitions, which can affect user-visible behavior and input handling;
changes are mitigated by extensive new E2E and unit tests.
>
> **Overview**
> Improves the `WidgetImageCrop` loading behavior by always rendering
the preview `<img>` when `imageUrl` is set and showing “Loading…” as an
absolute overlay, preventing a deadlock where `isLoading` could block
the `@load` event. Adds stable `data-testid="crop-resize-{direction}"`
selectors for resize handles and hardens pointer-capture handling in
`useImageCrop`.
>
> Greatly expands automated coverage: the Playwright spec now tests
empty-state rendering/screenshot, drag/resize interactions (edge/corner,
min size, and clamping to image bounds), aspect-ratio lock handle
visibility, slow `/api/view` loading overlay behavior, and broken image
fetch recovery. Adds a new Vitest suite for `useImageCrop` (including
`imageCropLoadingAfterUrlChange`) to unit-test URL→loading transitions
and core drag/resize/aspect-ratio logic.
>
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
|
||
|
|
ecb7fd4796 |
feat: add frontend subscription success recovery (#11286)
Improving our subscription detection system. Optimal will have to come after BE team brings personal billing to cloud repo off of comfy api. ## Summary - replace the dialog-local focus poller with a frontend checkout tracker stored in `localStorage` - recover pending subscription checkouts from app boot plus global page lifecycle (`pageshow`, `visibilitychange`) with bounded retries only while an attempt is pending - emit `subscription_success` through GTM with frontend-derived metadata once subscription state reaches the expected target tier/cycle ## Why This is the frontend-only 80/20 path. It fixes the brittle "old tab must regain focus" behavior without adding new backend endpoints or backend event storage. The browser records one pending checkout attempt when checkout is opened, and any returning cloud tab can recover it later by comparing current subscription state against the expected target plan. ## Tradeoffs - browser-scoped, not backend-authoritative - no server transaction id - scheduled downgrades through the billing portal are intentionally not inferred as immediate success events - still best-effort compared with the backend outbox/WebSocket approach ## Validation - `pnpm exec vitest run src/platform/cloud/subscription/composables/useSubscription.test.ts src/platform/telemetry/providers/cloud/GtmTelemetryProvider.test.ts` - `pnpm typecheck` ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-11286-feat-add-frontend-subscription-success-recovery-3436d73d3650814d9f74c89e6926aa84) by [Unito](https://www.unito.io) |
||
|
|
e28c1e7e43 |
Show multitype slices of shared color (#11250)
A tiny update requested by the backend team. Previously, multitype slot indicators would have inputs that resolve to the same connection color display be combined into a single slice. For example, both `INT` and `FLOAT` have the same color, so an `INT,FLOAT` slot displays as a solid color instead of 2 semi-circles. This was a conscientious decision to improve readability on slots that allow many types, but meant that the more common cases (like `INT,FLOAT`) would have no indicator at all. Since priority is given to types based on order of listing, node authors can still control which types are elided on a slot accepting many types. <img width="430" height="320" alt="image" src="https://github.com/user-attachments/assets/1fc7fb1c-a634-487c-bc03-711637aeef13" /> - I do not believe there are any core nodes affected by this change. - The vue implementation of merging slot colors never functioned properly, but is still removed. - Vue was bugged to incorrectly pass slot types for widgets. This is also fixed. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-11250-Show-multitype-slices-of-shared-color-3436d73d365081b6b484ea74423435a1) by [Unito](https://www.unito.io) |
||
|
|
39dc8d896b |
feat(website): unified preview — cloud page, API & enterprise pages, use case images (#11273)
## Summary Unified preview branch combining three feature PRs for the website product pages. > **Constituent PRs:** #11247, #11270, #11266 ## Changes - **Cloud page** (#11247): Add Cloud product page sections (Hero, Reason, FAQ, AI Models, Audience, Pricing, ProductCards). Extract `FAQSection` to `common/` and `ReasonSection` to `product/shared/` for reuse across product pages. Add cloud-related i18n translations. - **API & Enterprise pages** (#11270): Add API page (Hero, Steps, Automation, Reason) and Enterprise page (Hero, Team, DataOwnership, BYOKey, Orchestration, Reason). Add shared `CardGridSection`, `FeatureShowcaseSection`, `CloudBannerSection`. Add all API/enterprise i18n translations. - **Use case images** (#11266): Replace placeholder divs with real images in `UseCaseSection`. Add SVG blob clip-paths (`objectBoundingBox`) and crossfade transitions on category switch. Use `useId()` for unique clip-path IDs. ## Review Focus - Shared component API design (`ReasonSection` slot/prop surface) - Component placement: `common/` vs `product/shared/` - Clip-path coordinate accuracy and crossfade transition smoothness --------- Co-authored-by: Amp <amp@ampcode.com> Co-authored-by: GitHub Action <action@github.com> Co-authored-by: DrJKL <DrJKL0424@gmail.com> Co-authored-by: Alexander Brown <drjkl@comfy.org> Co-authored-by: pythongosssss <125205205+pythongosssss@users.noreply.github.com> Co-authored-by: AustinMroz <austin@comfy.org> |
||
|
|
f6f267b46d |
test: add unit tests for slotCalculations (#11302)
# PR Description
#11106
**This PR only focus on `slotCalculations.ts`.**
Add unit tests for `slotCalculations.ts` — the centralized utility that
calculates input/output slot positions in graph coordinates. This file
had zero test coverage despite containing non-trivial branching logic
used by both the litegraph adapter and the Vue renderer layout system.
## What's covered
### `calculateInputSlotPosFromSlot`
- [x] **Collapsed nodes**: Returns the node origin shifted up by half
the title height.
- [x] **Hard-coded offsets**: Slots with specific `pos` offsets return
`nodeOrigin + pos` directly.
- [x] **Default vertical layout**: Covers first slot x/y, multi-slot
vertical ordering, `slotStartY` offset, exclusion of widget input slots,
and exclusion of fixed-position slots from index ordering.
### `getSlotPosition` (Legacy fallback path, `vueNodesMode` disabled)
- [x] **Coordinate derivation**: Input and output slot positions derived
correctly from `node.pos`.
- [x] **Collapsed state**: Collapsed input and output nodes use
`title-height` and `NODE_COLLAPSED_WIDTH` offsets.
- [x] **Boundary handling**: Out-of-range slot index falls back to node
origin.
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> **Low Risk**
> Test-only changes that don’t affect runtime behavior; risk is limited
to potential brittleness if slot layout constants or expectations
change.
>
> **Overview**
> Adds a new `slotCalculations.test.ts` suite covering
`calculateInputSlotPosFromSlot` and the legacy (`LiteGraph.vueNodesMode`
disabled) path of `getSlotPosition`.
>
> Tests exercise key branches for collapsed nodes, hard-coded slot `pos`
overrides, default vertical slot ordering (including `slotStartY`), and
filtering of widget/fixed-position inputs, plus boundary behavior for
out-of-range slot indices.
>
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
|
||
|
|
e8d833bc54 |
test: cover useLazyPagination, useRangeEditor, useCurveEditor (#11326)
Closes coverage gaps in \`src/composables/\` as part of the unit-test backfill. ## Testing focus Three composables, each a different kind of test challenge: reactive pagination state, DOM-track drag math, and SVG pointer interaction. No third-party library is mocked. ### \`useLazyPagination\` (10 tests) - Accepts both \`Ref<T[]>\` and plain \`T[]\` inputs. - \`currentPage\` ceiling at \`totalPages\` (clamp behavior). - Source-array replacement resets internal page state. - \`loadedPages\` (Set) accumulates across navigation. - **Observed source issue.** \`loadNextPage\` is declared \`async\` but contains no \`await\` (the artificial delay is commented out). Consequence: \`isLoading\` is never externally observable as \`true\`, and the concurrent-call dedup in the design doesn't actually fire in practice. Tests cover **observable** behavior only; the finding is noted here as a candidate follow-up fix. ### \`useRangeEditor\` (11 tests) - Drags each of \`min\` / \`max\` / \`midpoint\` handles; respects the \`showMidpoint\` toggle (events on the midpoint are ignored when hidden). - Value clamping within \`[valueMin, valueMax]\`. - \`denormalize\` receives the correct normalized position — verifies the 0–1 mapping math, not just that it was called. - \`trackRef.value === null\` → pointer events are no-ops (null-safety). - **Real lifecycle.** Mounts a tiny \`defineComponent\` via \`@testing-library/vue\`'s \`render\` and exercises cleanup through \`unmount()\`. \`onBeforeUnmount\` only fires inside a component instance — \`effectScope.stop()\` alone is insufficient. ### \`useCurveEditor\` (14 tests) - \`curvePath\` empty when fewer than 2 points. - Linear interpolation: \`M\` + \`L\` command sequence, points sorted by x before drawing. - Non-linear uses \`createInterpolator\` (our module → OK to mock and assert call shape). - Drag: dispatching \`pointermove\` updates \`modelValue\`; after \`pointerup\`, a follow-up \`pointermove\` is a no-op. - **happy-dom gaps polyfilled.** \`Element.setPointerCapture\` is stubbed per-element and \`DOMPoint.prototype.matrixTransform\` is added in \`beforeEach\`. Since the SVG has no CTM, \`DOMMatrix.inverse()\` returns identity — so \`svgCoords\` maps \`clientX\`/\`clientY\` directly into curve space, giving deterministic assertions without brittle coordinate math. ## Principles applied - No mocks of \`vue\`, \`@vueuse/core\`, or \`es-toolkit\`. - Behavioral assertions only — no return-shape checks. - All 35 tests pass; typecheck/lint/format clean. Test-only; no production code touched. |
||
|
|
3fd3c565ae |
Fix dropdown chevron color (#11335)
Updates the the color of the chevron on dropdown widgets to only have the disabled color when the widget is disabled. | Before | After | | ------ | ----- | | <img width="360" alt="before" src="https://github.com/user-attachments/assets/25d35e78-9147-4397-be19-df9d6f87ac72" /> | <img width="360" alt="after" src="https://github.com/user-attachments/assets/3bf3640d-fa14-42cb-afb4-7109eb878d1a" />| ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-11335-Fix-dropdown-chevron-color-3456d73d3650819e99c7d15173f11319) by [Unito](https://www.unito.io) --------- Co-authored-by: github-actions <github-actions@github.com> |
||
|
|
ff4c812d08 |
feat: show sign-in button via server feature flag (#11298)
## Summary Show the sign-in button in the frontend when the `show_signin_button` server feature flag is set, without requiring a special desktop distribution build. ## Changes - Add `SHOW_SIGNIN_BUTTON` to `ServerFeatureFlag` enum - Add `showSignInButton` getter in `useFeatureFlags` composable (returns `boolean | undefined`) - Update `WorkflowTabs.vue` to use `flags.showSignInButton ?? isDesktop` - the server flag takes precedence when set, falls back to compile-time `isDesktop` for legacy desktop support ## Related - Comfy-Org/ComfyUI-Desktop-2.0-Beta#415 - Backend: Comfy-Org/ComfyUI `feature/generic-feature-flag-cli` - Launcher: Comfy-Org/ComfyUI-Desktop-2.0-Beta#418 Co-authored-by: Amp <amp@ampcode.com> |
||
|
|
836cab1b38 |
fix: deploy website previews via GitHub Actions instead of Vercel auto-deploy (#11289)
## Summary Vercel's auto-deploy triggers on every PR because files outside workspace packages (e.g. `browser_tests/`, `src/`) are treated as global changes by the monorepo skip logic. ## Changes - **What**: Replace Vercel's GitHub integration with a GitHub Action (`ci-vercel-website-preview.yaml`) that uses `paths:` filtering to only deploy when `apps/website/`, `packages/design-system/`, or `packages/tailwind-utils/` change. Add `vercel.json` with `github.enabled: false` to disable Vercel's automatic GitHub integration. ## Setup required after merge Three GitHub repo secrets are needed. All secrets are scoped per-project using the `VERCEL_WEBSITE_*` prefix. Future Vercel projects would follow the same convention (e.g. `VERCEL_DOCS_*`). ### Step 1: Create a Vercel API Token 1. Go to [vercel.com/account/tokens](https://vercel.com/account/tokens) 2. Click **Create Token** 3. Fill in the form: - **Token Name**: `github-actions-website` - **Scope**: Select the **Comfy-Org** team (not "Full Account" — scope it to the team that owns the project) - **Expiration**: Choose **No Expiration** (or set a long expiration like 1 year — if it expires the workflow will silently fail) 4. Click **Create** 5. **Copy the token immediately** — it is only shown once ### Step 2: Get Vercel Org ID and Project ID 1. Go to [vercel.com/comfyui/website-frontend/settings](https://vercel.com/comfyui/website-frontend/settings) 2. Scroll down to the **Project ID** field — copy this value 3. Go to [vercel.com/teams/comfyui/settings](https://vercel.com/teams/comfyui/settings) (Team Settings → General) 4. Find the **Vercel ID** field (also called Team ID / Org ID) — copy this value ### Step 3: Add secrets to GitHub 1. Go to [github.com/Comfy-Org/ComfyUI_frontend/settings/secrets/actions](https://github.com/Comfy-Org/ComfyUI_frontend/settings/secrets/actions) 2. Click **New repository secret** and add each of the three secrets: | Secret name | Value | |---|---| | `VERCEL_WEBSITE_TOKEN` | The token from Step 1 | | `VERCEL_WEBSITE_ORG_ID` | The team/org ID from Step 2 | | `VERCEL_WEBSITE_PROJECT_ID` | The project ID from Step 2 | > **Note:** The `vercel.json` added by this PR (`github.enabled: false`) automatically disables Vercel's built-in auto-deploy — no dashboard changes needed. ## Review Focus - Verify the `paths:` filter covers all dependencies of `apps/website` - Confirm the PR comment logic is sound (creates once, updates on subsequent pushes) --------- Co-authored-by: Alexander Brown <drjkl@comfy.org> Co-authored-by: DrJKL <DrJKL0424@gmail.com> Co-authored-by: Amp <amp@ampcode.com> |
||
|
|
7ffaff7e1b |
test: cover useBillingPlans and tierBenefits (#11318)
Closes coverage gaps in `src/platform/cloud/subscription/` as part of the unit-test backfill. ## Testing focus `useBillingPlans` holds **module-scoped refs** (`plans`, `currentPlanSlug`, `isLoading`, `error`). If state leaks between tests, failures get masked as false-green. The suite uses `vi.resetModules()` + dynamic `import()` in every test to get a fresh instance — state isolation is the primary design constraint here. ### `useBillingPlans` (12 tests) - **Concurrent-call dedup.** The \`isLoading\` guard is validated by creating a pending promise, firing a second \`fetchPlans()\` while the first is in-flight, and asserting the mock is called **exactly once**. - **Error branching.** \`Error\` instance → \`.message\` captured. Non-Error rejection → fallback string (\`'Failed to fetch plans'\`). Both paths also verify \`console.error\` logging via a spy. - **Error-reset invariant.** After a failure, a subsequent success must null out \`error.value\` — order-dependent and easy to regress. - **Shared-state invariant.** Two separate \`useBillingPlans()\` calls return refs pointing at the same module-level state. - **Computed filtering.** \`monthlyPlans\` / \`annualPlans\` partition by duration — assertions on distinct output, not input re-assertion. ### \`tierBenefits\` (7 tests) - Table-driven across all \`TierKey\` values for \`maxDuration\`, \`addCredits\`, \`customLoRAs\` branches. - \`monthlyCredits\` free-tier path including the \`remoteConfig.free_tier_credits\` null fallback. - Translator/formatter forwarding verified by spy. ## Principles applied - No mocks of \`vue\`, \`pinia\`, or \`@vueuse/core\` — only our own \`workspaceApi\`. - Behavioral assertions only — no return-shape checks. - All 19 tests pass; typecheck/lint/format clean. Test-only; no production code touched. |
||
|
|
5d04df7b2c |
fix: prevent duplicate prepareForSave and conflicting is_new telemetry on self-overwrite Save As (#11329)
## Summary Follow-up to PR #10816. Fixes a telemetry semantic bug in `saveWorkflowAs` that emitted two conflicting events for a single user action. ### What changed - `saveWorkflowAs` self-overwrite branch now calls `workflowStore.saveWorkflow` directly instead of delegating to the `saveWorkflow()` wrapper. The wrapper would run `prepareForSave` a second time and emit `trackWorkflowSaved({ is_new: false })`, which then conflicted with the outer `saveWorkflowAs`'s `trackWorkflowSaved({ is_new: true })` for the same user action. - Added regression tests asserting a single `trackWorkflowSaved` call with `{ is_new: true }` and a single `prepareForSave` invocation on both the self-overwrite and copy paths. ### Issues fixed - Fixes #10819 ### Why no E2E test The bug and fix are entirely about observability (how many telemetry events are emitted and with what payload). There is no user-visible change — the file is saved correctly in both pre- and post-fix cases, and `is_new` values are never rendered in the UI. Playwright tests cannot directly verify `trackWorkflowSaved` call counts/payloads without intercepting outbound analytics traffic, which is not a pattern used elsewhere in `browser_tests/`. Unit tests at the service boundary are the appropriate level for this contract: they mock `useTelemetry` and can assert exact call count and payload deterministically. ### Test plan - [x] `pnpm test:unit src/platform/workflow/core/services/workflowService.test.ts` — 56 tests pass (including 2 new regression tests + 1 expanded assertion) - [x] `pnpm typecheck` - [x] `pnpm lint` - [x] `pnpm format` ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-11329-fix-prevent-duplicate-prepareForSave-and-conflicting-is_new-telemetry-on-self-overwrite-3456d73d36508192875ed5e70ab9c359) by [Unito](https://www.unito.io) |