mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-07-17 09:18:26 +00:00
98700cfcc791d498cb94e71c4fe400a7bd93078e
8513 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
98700cfcc7 |
feat: marquee select and Ctrl/Cmd+A in the Media Assets panel (#13323)
## Summary
Adds marquee (rubber-band) multi-select and Ctrl/Cmd+A select-all to the
Media Assets panel, clips the canvas drag-selection rectangle to the
canvas panel, and turns on live (real-time) node-graph rubber-band
selection by default.
## Changes
- **Marquee select** — rubber-band drag from empty grid space selects
the covered cards; hold Ctrl/Cmd to start a marquee from over a card;
Ctrl/Cmd or Shift alone makes the marquee additive to the current
selection, Ctrl/Cmd+Shift subtracts the covered cards from it
(designer-approved), and no modifier replaces it. Cards update their
selected state live during the drag.
- **Ctrl/Cmd+A** — selects all loaded assets when the pointer is over
the panel, otherwise falls through to the canvas (select all nodes). It
`stopImmediatePropagation`s so a panel select-all never also fires the
global node select-all, and it yields while an `aria-modal` dialog is
open or a text input is focused.
- **Select-all recovers after "deselect all"** — the shortcut was gated
only on `useElementHover`, which latched stale when the floating
selection bar under the cursor unmounted on deselect. It now also checks
the live pointer position against the panel rect, so a second Ctrl/Cmd+A
right after deselecting no longer falls through to the browser's native
page select-all.
- **Canvas rectangle clip** — the canvas drag-selection rectangle is
clamped to the canvas panel bounds (`SelectionRectangle.vue`,
display-only).
- **Graph live selection on by default** — flips the existing
`Comfy.Graph.LiveSelection` setting's default to on, so node-graph
rubber-band selection updates in real time during the drag (matching the
assets panel) instead of committing only on mouse-up. The behavior was
already implemented behind the setting; this changes only the default,
and users with an explicit value keep it.
- **Robustness/UX** — the pointer is captured on drag-engage rather than
on press (so a Ctrl/Cmd-click on a card isn't hijacked); no global
`document.body.userSelect` mutation (replaced by a panel-scoped
`selectstart` guard); the marquee overlay uses the semantic
`primary-background` token; post-drag click-suppression auto-resets so a
cancelled drag can't swallow a later click; `setPointerCapture` is
wrapped in try/catch; a Ctrl/Cmd-held card `dragstart` is cancelled so
no native ghost-drag image appears.
- **Breaking**: none — `useAssetSelection` is extended additively (new
`setSelectedIds` helper, nothing removed or altered) and the new
composable exposes only `{ marqueeStyle }`.
- **Dependencies**: none.
## Review Focus
- **`SelectionRectangle.vue`** is shared canvas code; the change is
display-only (clamps the rectangle to the panel; no node-selection
behavior change).
- **`coreSettings.ts`** — a one-line `Comfy.Graph.LiveSelection` default
flip is the only change that affects graph behavior; the live-select
code path itself is pre-existing.
- **`useAssetGridSelection.ts`** — listener lifecycle/teardown, the
panel-scoped `selectstart` guard, the click-suppression timer, the
capture-on-drag-engage logic, the pointer-position select-all fallback,
and the subtractive-mode snapshot at pointerdown.
- **Ctrl/Cmd+A routing** — panel hover (or a live pointer inside the
panel) gates select-all vs. the canvas, and `stopImmediatePropagation`
prevents double-handling.
- Pure geometry/selection logic is extracted into
`marqueeSelectionUtil.ts` and unit-tested in isolation (`RectEdges` is
`Pick<DOMRect, ...>`, the DOM edge subset); `MediaAssetCard.dragStart`
keeps `main`'s `display_name` payload.
Relates to Linear **FE-910**.
## Testing
- **Unit:** `useAssetGridSelection` (39 cases — marquee selection,
additive/replace, subtractive Ctrl/Cmd+Shift (incl. the macOS Cmd
variant and a shrink-restore drag), interactive-element + list-view
guards, `selectstart` scoping, click-suppression auto-reset,
pointer-capture-throw and capture-on-drag-not-press, modal-aware
Ctrl/Cmd+A, non-propagation, and the deselect-recovery pointer-in-panel
path), plus `MediaAssetCard`, `marqueeSelectionUtil` (11 cases incl.
subtractive, and a 5-case fast-check property suite pinning the
additive/subtractive set invariants), `SelectionRectangle`,
`useAssetSelection`, and `mathUtil`.
- **E2E (`assetsSidebarTab.spec.ts`):** 10 Playwright scenarios running
in CI — Ctrl/Cmd+A hover vs. canvas; a marquee from the panel header; a
modifier-held additive marquee; a Ctrl/Cmd+Shift subtractive marquee;
Ctrl/Cmd-drag from a card and within a single card; Ctrl/Cmd+A ignored
in a focused search box and under an aria-modal dialog; and a drag from
the search box not marquee-selecting. The empty-space marquee path is
covered by the panel-header scenario plus the unit suite (a dedicated
empty-space e2e could not run headless without a local backend and was
dropped as redundant).
## Future work
- **Escape key** — not handled by the marquee/select-all flow yet (the
composable handles only Ctrl/Cmd+A). Follow-up: press Escape to cancel
an in-progress marquee drag (abort the rubber-band and restore the
pre-drag selection) and to clear the current selection while the panel
has focus.
- **Ctrl+A across pagination** — select-all covers the loaded assets
only (confirmed as the intended behavior with design); a
load-all-then-select variant can follow if needed.
## Demo
https://github.com/user-attachments/assets/3841bf3c-db75-4229-a5e7-fb363b4882d6
|
||
|
|
5bf41a41bd |
1.48.3 (#13606)
Patch version increment to 1.48.3 **Base branch:** `main` --------- Co-authored-by: christian-byrne <72887196+christian-byrne@users.noreply.github.com> Co-authored-by: github-actions <github-actions@github.com>v1.48.3 |
||
|
|
a7f14a0b3f |
fix(subscription): size pricing dialogs with Reka props (#13633)
## Summary Fixes the legacy personal and legacy workspace pricing dialogs so Reka owns the dialog width and the pricing table no longer overflows the default 576px frame. ## Changes - **What**: Replace the shared PrimeVue-only `style` and `pt` dialog props with Reka `renderer`, `size`, and `contentClass` props for both legacy pricing paths. - **What**: Preserve `modal: false` for the legacy workspace path so its teleported PrimeVue plan-details popover remains interactive. - **What**: Add unit coverage for both routes and assert that ignored PrimeVue shell props are no longer passed. ## Review Focus - **Regression origin**: [#12593](https://github.com/Comfy-Org/ComfyUI_frontend/pull/12593) made Reka the default dialog renderer. [#12666](https://github.com/Comfy-Org/ComfyUI_frontend/pull/12666) then added shared PrimeVue `style` and `pt` props for these pricing dialogs. Reka ignored those props and fell back to `size="md"` (`max-w-xl`, 576px). [#13092](https://github.com/Comfy-Org/ComfyUI_frontend/pull/13092) fixed the unified pricing path only and explicitly left the two legacy paths for follow-up. - **Sizing ownership**: The fix puts width on the Reka dialog frame with `size: 'full'` and `sm:max-w-7xl`. Pricing content no longer has to compensate for a narrow shell. - **Legacy workspace behavior**: `modal: false` remains intentional because the legacy table opens a PrimeVue popover teleported to `body`. - **Scope**: This PR contains only global subscription-dialog sizing. Agent side-panel behavior remains in [#13472](https://github.com/Comfy-Org/ComfyUI_frontend/pull/13472). - **Validation**: Focused tests pass (41 tests), `pnpm typecheck` passes, and targeted ESLint passes. Chrome validation at 1352x705 rendered a 1280px dialog with no horizontal overflow in both the isolated PR preview and the combined agent-panel preview. The docked agent panel remained mounted behind the modal. ## Screenshots (if applicable) - Original report and screenshots: [Slack thread](https://comfy-organization.slack.com/archives/C0A8Z4U7Y1K/p1783967281397449?thread_ts=1783966866.227439&cid=C0A8Z4U7Y1K) |
||
|
|
e6d1a9d4a2 |
fix(release): support manual target-branch override + major versions in resolve-comfyui-release (#13660)
## Problem
`scripts/cicd/resolve-comfyui-release.ts` derived the release target
purely from ComfyUI's `requirements.txt` pin and **hardcoded major
version `1`** (`core/1.${minor}`, `v1.${minor}.*`,
`1.${minor}.${patch}`) even though it parsed `major` and never used it.
Consequences:
- Could not release an out-of-cadence branch (e.g. skip a dead 1.46 to
ship 1.47 directly).
- Could not do a major bump (2.x).
## Changes
1. **Resolver (`resolve-comfyui-release.ts`)** — uses the parsed
`targetMajor` (defaults to the current pin's major) for every
branch/tag/version string instead of literal `1`. `getLatestPatchTag`
now takes a `major` param and globs `v${major}.${minor}.*`.
2. **`TARGET_BRANCH` env override (highest precedence)** — when set,
validates `^core/(\d+)\.(\d+)$`, verifies `origin/<branch>` exists, and
skips the `RELEASE_TYPE`/pin-derived selection entirely. If both
`TARGET_BRANCH` and `RELEASE_TYPE` are set, the override wins.
`current_version` still comes from the pin (for `diff_url` and the
ComfyUI PR "from" version), so the requirements bump jumps straight from
the pin to `target_version` (e.g. 1.45.20 → 1.47.8, skipping 1.46).
3. **Workflow (`release-biweekly-comfyui.yaml`)** — new optional
`target_branch` `workflow_dispatch` input, wired into the resolve step's
`env` as `TARGET_BRANCH`, and surfaced in the run summary. Downstream
jobs consume `target_branch`/`target_version` outputs unchanged.
The output JSON shape is identical. Extracted pure helpers
(`parseTargetBranchOverride`, `computeTargetVersion`) and guarded the
main block so the module is importable by tests.
`release-version-bump.yaml` and `release-branch-create.yaml` were
**already** major-aware and are left untouched.
## Tests
New `scripts/cicd/resolve-comfyui-release.test.ts` (15 cases) covering
`parseRequirementsVersion` (==, >=, missing/absent), `isValidSemver`,
`parseTargetBranchOverride` (valid `core/1.47` and `core/2.0`, malformed
rejected), and `computeTargetVersion` including a non-1 major (`v2.0.3`
+ commits → `2.0.4`).
## Usage
```
gh workflow run release-biweekly-comfyui.yaml --field target_branch=core/1.47
```
releases 1.47.8 directly (skipping a dead 1.46), or `--field
target_branch=core/2.0` for a major bump.
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
0b4a960467 |
feat(billing): derive next-invoice amount, date, and cadence from billing state (FE-1245) (#13599)
## Summary
Adds the data seam for the Settings > Plan & Credits Invoices tab
(FE-1245): derives the next-invoice amount, date, and billing cadence
from billing state already fetched by the billing context, replacing the
prototype's hardcoded mock.
Updated for the 2026-07-13 design decisions (Slack thread + Willie's
Figma updates): the banner surfaces the BE-provided next-invoice date,
and annual subscriptions now show their yearly total and real renewal
date instead of hiding the banner.
## Changes
- **What**: `useNextInvoice` composable + pure `deriveNextInvoice` in
`src/composables/billing/` — returns `{ nextInvoice:
ComputedRef<NextInvoice | null> }`, `NextInvoice = { amountCents,
renewalDate, duration }`
- Monthly: subscribed team credit stop `monthly.price_cents` (status
`team_credit_stop.id` matched against the ladder) with plan
`price_cents` fallback — unchanged precedence
- Annual: stop `yearly.price_cents * 12` (stop yearly prices are
per-month figures, per `useWorkspacePlanPricing`) or the ANNUAL plan's
`price_cents` as-is (already the yearly total, per
`UnifiedPricingTable`)
- `renewalDate`: BE-computed `renewal_date` passed through untouched —
backends own period math including month-end bias. It goes null the
moment a cancellation is scheduled (mutually exclusive with
`end_date`/`cancel_at` on both billing backends)
- null (banner hidden) when inactive, cancelled, the amount is
unresolvable/non-positive (covers legacy billing's empty plan list and
free tier), or the plan resolved by slug disagrees with the
subscription's cadence
- 13 unit tests cover all branches, including x12-regression fixtures
with discriminated list/discount prices
Intentionally excludes usage/overage pending charges until the backend
exposes an authoritative upcoming-invoice amount (see FE-1245).
Follow-ups (not this PR):
- Cancelled-state toast (Figma 4617:29298 month-remaining / 4617:29992
terminal): separate seam UI work. Data is already available —
`subscription.endDate` is populated by both billing backends exactly
during the cancelled-but-paid window, and `useResubscribe()` covers the
Renew plan action
- "/year" amount framing is shown with placeholder copy; no annual
variant exists in the Figma file yet (design follow-up)
## Review Focus
- Annual unit semantics: stop `yearly.price_cents` is a per-month figure
(`useWorkspacePlanPricing.ts` `teamMonthlyCostCents`), while
`Plan.price_cents` for ANNUAL plans is the yearly total
(`UnifiedPricingTable.vue` `getAnnualTotal`) — hence x12 in one branch
and as-is in the other
- Consumed by the `useWorkspaceInvoices` seam once #13591 lands; the
swap now also requires the seam template to render
`renewalDate`/`duration`, so it is no longer a one-line body change
- FE-1245 / DES-497
## Screenshots
Live captures through the real `WorkspaceInvoicesContent` + this
composable with `/api/billing/*` mocked per state (isolated preview
harness). Date format follows the existing billing convention; "/ year"
framing is placeholder pending the annual design variant.
Active monthly subscription, team credit stop `team_320` — $320 USD with
next-invoice date:

Active monthly subscription without a credit stop — plan-price fallback,
$20 USD:

Annual subscription — banner now shown: yearly total from the per-month
stop figure (288 x 12 = $3,456) and the real yearly renewal date:

Paused subscription — unchanged: next-invoice banner hidden, the paused
banner hosts the Full invoice history action (capture from the earlier
full-app harness):

|
||
|
|
060957d66c |
fix: use ComfyUI version for Cloud release notes (#13632)
## Summary On Cloud, `releaseStore.currentVersion` sourced `cloud_version` (e.g. `0.160.1`) while the `/releases` feed keys its entries by **ComfyUI** version (e.g. `0.27.1`). The what's-new popup compares the latest feed entry against the running version, so `0.27.1 < 0.160.1` read as "already ahead of the latest release" and the popup never showed. - Analytics confirmed the regression: `release_note` clicks fell from **13.4% (90d) → 0% (30d)**; `cloud_release_note` was effectively never clicked. ## Change - `currentVersion` always uses `comfyui_version` (drops the `isCloud → cloud_version` branch). The feed request keeps `project: 'cloud'`. - Rationale: the changelog page and each note's "learn more" link are ComfyUI-versioned, and Cloud has no separate versioned feed or changelog page. The changelog is maintained with ComfyUI versions, updated after each Cloud deploy lands. ## Tests - Adds a regression test (`isCloud environment (FE-1237)`) pinning that Cloud uses `comfyui_version`, not `cloud_version`. Verified via negative control: reverting the fix fails it with `0.160.1` vs expected `0.27.1`. - `test:unit` (releaseStore): 49 passed · `typecheck`, `lint`, `format:check`, `knip`: clean. ## ADR Adds `docs/adr/0012-cloud-release-notes-use-comfyui-version.md` (Accepted) recording the rationale and history, and updates the ADR index. Fixes FE-1237 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
1a98362984 |
fix(i18n): escape vue-i18n message syntax in generated node defs + preserve raw messages on compile errors (#13631)
## Summary Defense-in-depth fix for the `Invalid linked format` crash: escape vue-i18n message-syntax characters at node-def generation so the locale bundle is always valid, **and** harden the runtime so any compile error (custom nodes, already-shipped bundles) degrades to the raw message instead of crashing the app. This folds in the runtime guard from #13603 (@xmarre) — see credit below — so the two layers land together. ## Changes **Layer 1 — generation-time escaping (source data)** - Add `escapeVueI18nMessageSyntax()` to `packages/shared-frontend-utils/src/formatUtil.ts`. It escapes every character vue-i18n's message compiler treats as syntax in text — `@ { } | %` (verified against `@intlify/message-compiler`'s `readText` tokenizer): `@` and unbalanced `{`/`}` throw, `|` mis-renders as plural branches, `%` matters immediately before `{`. Each becomes a literal interpolation `{'x'}` (the only escape vue-i18n supports); the set isn't exported by the library so it's hardcoded with source/doc references. Single-pass, applied once. - Apply it in `scripts/collect-i18n-node-defs.ts` to the fields **compiled** via `t()`/`st()`: `display_name`, `description`, input/output `name`, widget labels, `dataTypes`, `nodeCategories`. Tooltip fields go through `tm()`/`stRaw()` (no compile step) and are intentionally left unescaped. - Regression test (`src/locales/escapeNodeDefI18n.test.ts`) compiles the escaped output for all five characters through real vue-i18n and asserts it round-trips verbatim. **Layer 2 — runtime fallback (consumer), folded in from #13603 by @xmarre** - `st()` now wraps `t(key)` in try/catch: a vue-i18n `SyntaxError` falls back to the raw locale message (`tm()`) instead of crashing bootstrap. Shared `rawTranslationOrFallback` helper reused by `stRaw()`. Tests in `src/i18n.safeTranslation.test.ts` (isolated per-test, and covering the non-`SyntaxError` rethrow branch). **Tooling** - Lint the collection scripts instead of ignoring them: import the shared helper via the `@/utils/formatUtil` tsconfig alias (fixes `import-x/no-relative-packages`; verified it resolves under Playwright) and drop a stray progress `console.log`. No lint config changes and no rules relaxed — the scripts are now genuinely linted (they were previously excluded). - **Breaking**: none. ## Review Focus - **Root cause**: values read via `t()`/`st()` are compiled by vue-i18n, which parses `@ { } | %` as message syntax; a malformed `@` throws `Invalid linked format`. `app.ts` translates every node description at startup, so one bad string aborts router load. Surfaced by the 1.47.7 locale sync (#13246), whose `ByteDanceSeedAudio` description contains `@Audio1-3`. - **The two layers compose**: escaped output compiles cleanly, so the runtime catch never fires for generated strings; the catch is the safety net for strings that bypass generation (custom/third-party nodes via `mergeCustomNodesI18n`, and locale bundles already shipped in 1.47.x/1.48.x). - **Compiled vs. raw boundary**: escaping is applied only to compiler-consumed fields; leaving tooltips raw matches the `stRaw()`/`tm()` work in #12469. - **Translation propagation**: lobe-i18n's config prompt already keeps `{...}` placeholders intact, so the escaped English propagates to all 13 locales. - **Follow-up (out of scope)**: `scripts/collect-i18n-general.ts` (commands/settings/menus) shares the same generation-time hazard and could reuse the same helper later; Layer 2 already covers it at runtime. ## Credit The runtime fallback (Layer 2) is @xmarre's work from #13603, folded in here with authorship preserved on the original commits. Closing #13603 in favor of this combined PR. Fixes #13545 Fixes #13575 --------- Co-authored-by: Connor Byrne <c.byrne@comfy.org> Co-authored-by: xmarre <54859656+xmarre@users.noreply.github.com> |
||
|
|
ab33746b3e |
fix(load3d): letterbox-aware pointer NDC for the transform gizmo (#13550)
## Summary The viewport letterboxes the scene to the width/height target aspect, but TransformControls maps pointers over the full canvas rect, so its handle raycasts skew toward the center, increasingly wrong toward the edges whenever the canvas aspect differs from the target aspect. Adds clientPointToLetterboxNdc() (pure, unit-tested) + a Viewport3d.clientPointToNdc() wrapper, and routes TransformControls' pointer math through it via GizmoManager.setPointerNdcSource(). Points on the letterbox bars resolve to nothing instead of phantom handles. ## Screenshots (if applicable) before <img width="813" height="703" alt="image" src="https://github.com/user-attachments/assets/cf5714c3-6b60-4ced-84ce-039f49c7e999" /> <img width="673" height="696" alt="image" src="https://github.com/user-attachments/assets/31f898d2-a820-4748-b960-4f3cc5752095" /> after <img width="1391" height="981" alt="image" src="https://github.com/user-attachments/assets/18890d89-ff50-4298-bac5-472e00c79e24" /> <img width="955" height="712" alt="image" src="https://github.com/user-attachments/assets/255f3c7d-dbb5-40a8-8865-c023caa1384e" /> |
||
|
|
55c4e807a1 |
fix: use system hour cycle for queue times (#12297)
## Summary Use the selected app locale for queue timestamp language and punctuation while honoring the browser/system 12-hour or 24-hour clock preference. ## Changes - **What**: `formatClockTime` resolves the hour cycle from the browser/runtime default locale, then applies it while formatting with the app locale. - **What**: Added behavioral unit coverage for the production two-argument path and explicit 12-hour and 24-hour preference locales. - **Breaking**: None. - **Dependencies**: None. ## Review Focus Queue timestamps already use the browser's local timezone. This PR fixes only the remaining clock-preference mismatch: app language stays in control of the rendered time string, while the browser/system locale controls 12-hour versus 24-hour display. No Playwright regression test was added because browser/system hour-cycle preference is environment-level Intl state, not deterministic UI state that the E2E suite can set reliably. Unit tests pin both hour cycles through BCP-47 locale preferences and assert the rendered behavior directly. Fixes [FE-252](https://linear.app/comfyorg/issue/FE-252/bug-queue-progress-times-ignore-browsersystem-1224-hour-preference) ## Screenshots (if applicable) Not applicable. --------- Co-authored-by: Christian Byrne <cbyrne@comfy.org> |
||
|
|
74147d7ee2 |
feat: lift boundary-exposed validation errors to the subgraph host (#13542)
## Summary Backend validation errors (`node_errors`) are reported against the **flattened** prompt, so an error whose real fix lives on a subgraph host node gets attached to an interior node the user may never open — and, after ADR 0009, often *cannot* meaningfully fix there. This PR re-surfaces a validation error onto the subgraph host node **when — and only when — the error's subject (the specific input/widget named by `extra_info.input_name`) is exposed through the subgraph boundary**. ## Why this is needed Two concrete situations motivated this, both observed in real workflows: 1. **Broken link at the host.** Root node A should feed subgraph host B, whose boundary input is linked to interior node C. If the A→B link is missing, the backend flattens the prompt, sees C with no resolved input, and raises `required_input_missing` **on C** (`"B:C"`). The actual fix — connect B's input — is one level up, on a node the error never points at. 2. **Host-owned widget values.** Per ADR 0009 (subgraph promoted widgets use linked inputs), a promoted widget's value is owned by the host `SubgraphNode`; the interior widget only supplies schema/defaults. When the backend raises `value_not_in_list` (or min/max violations) for that value, attributing it to the interior node is factually wrong — the value that failed validation *is the host's value*. This continues the direction of #13059, which moved **missing-model** detection identity to `{hostExecutionId, hostWidgetName}` with the interior path kept as diagnostics. That was possible in the FE pre-scan; this PR applies the same ownership principle to **backend-received** errors via a receive-side mapping, since the backend cannot know about subgraph boundaries in a flattened prompt. ## The rule (design) > Lift an error from interior node N to host H **iff** N's input slot named by the error is linked to the containing subgraph's boundary (`SubgraphInput`). Apply the same test again at H (boundary-by-boundary, matching ADR 0009's chaining principle) and stop at the first level where the subject is no longer boundary-linked — that node is where the user can actually fix it. The predicate is **structural (boundary exposure), not data-flow**. ### In scope — examples - `required_input_missing` on interior `"12:5"` whose input is fed by the boundary → surfaces on host `12`'s input slot (red slot ring on the host, errors-tab card titled/located at the host, message names the host's `SubgraphInput.name`). - `value_not_in_list` / `value_smaller_than_min` / `value_bigger_than_max` on a promoted interior widget → surfaces on the host's promoted widget. Nested hosts chain: `"1:2:3"` lifts to `"1:2"`, and further to `"1"` only if `1:2`'s own slot is boundary-linked too. - Clearing follows the surface: connecting the highlighted host input or fixing the host widget clears the underlying interior (raw) error — range-guarded per target, so a still-out-of-range host value does **not** clear. ### Out of scope — examples - **No value-flow ancestry.** All in the root graph: A's widget links to B, B's to C, and C rejects the value that originated at A → the error **stays on C**. Following same-graph links to a "root cause" node is explicitly not this feature. - Errors without an `input_name` subject, node-level types (`exception_during_validation`, `dependency_cycle`, image-not-loaded), and unknown validation types — never lifted. Unknown types stay node-scoped to match how the error catalog renders them (the shared `isNodeLevelValidationError` in `executionErrorUtil` encodes this, and the catalog derives its node-level rules from the same set). - Runtime execution errors (exceptions during a run) — validation responses only. - Interior errors whose input is fed by another interior node — fixable in place, stay in place. - Fan-out display dedupe: when one boundary input feeds multiple interior nodes and the host slot is unconnected, each interior error lifts to the same host slot as a separate panel line. A single fix (connecting the host input) clears all of them — the clearing translation already fans out — so the duplication is cosmetic. Display-level dedupe is a follow-up; deduping inside the lift would break the one-source-per-error clearing contract. - Reactive re-lifting on graph topology edits while errors are displayed (invariant documented on the computed; follow-up), and deriving the error catalog's full validation rule table from the shared classification (follow-up; the node-level type set and the image-not-loaded predicate are already single-sourced in `executionErrorUtil` and consumed by both the lift and the catalog). ## Changes - **What**: New pure module `core/graph/subgraph/liftNodeErrorsToBoundary.ts` — per-error, fail-open record transform (unresolvable ids/slots/links leave the error where the backend put it; raw payload is never mutated). `executionErrorStore` derives `surfacedNodeErrors` from it and display consumers switch over (errors tab grouping, canvas node/slot flags, Vue node badges, app-mode/linear hints); raw `lastNodeErrors` remains the source of truth for mutation. Host-side clearing translates through the lift's diagnostics fields (`source_execution_id` / `source_input_name`) with a per-target range guard. - **Breaking**: None. No persistence/serialization changes; interior identity survives as diagnostics metadata only (ADR 0009 language). ## Review Focus - The lift predicate lives entirely on link topology (`LLink.originIsIoNode` → `SubgraphInput`) — no `proxyWidgets`/promotion-store style source authority is reintroduced. - `clearSlotErrorsWithRangeCheck` now resolves clear targets first and range-checks each target's raw errors; the lifted-path twin of the existing range-retention test pins this. - `useProcessedWidgets` deliberately stays on the raw record: host promoted widgets already map to interior errors via `widget.sourceExecutionId`, so an interior widget keeps its local red hint when the user opens the subgraph (hint layer vs surface layer). - Unit coverage is carried by the pure module (real litegraph subgraph fixtures, incl. nested recursion, promoted widgets via `promoteValueWidgetViaSubgraphInput`, ordering, fail-open/no-mutation); one e2e pins the user-visible contract (host ring + host slot dot + interior clean). ## Screenshots ### Before https://github.com/user-attachments/assets/81e5c4db-515d-4f1f-8f8a-e07ac490510f ### After https://github.com/user-attachments/assets/2949da06-a049-41c1-a480-98ee28333bf2 |
||
|
|
16fdcab4be |
feat(telemetry): track signup/login flow errors in PostHog (#13504)
## Summary
Auth error toasts in the signup/login flow (e.g. "No account found with
this email…") were never tracked, leaving no analytics visibility into
signup dropoff. This adds a PostHog event for them.
## Changes
- **What**: New `app:user_auth_failed` PostHog event with `{ error_code,
auth_action }`, fired from a `reportAuthFlowError` wrapper in
`useAuthActions` before the existing error toast. Wired to the seven
auth-flow actions (`email_sign_in`, `email_sign_up`, `google_sign_in`,
`google_sign_up`, `github_sign_in`, `github_sign_up`, `password_reset`);
covers both the cloud login/signup pages and the in-app sign-in dialog
since all funnel through the same handler. Non-auth actions (logout,
billing) intentionally stay untracked.
## Review Focus
- PostHog provider only — not added to Mixpanel/GTM/CustomerIo/etc.
- No PII: only the Firebase error code is sent (`'unknown'` for
non-Firebase errors), never the message or email.
- The `useAuthActions` test mock of `wrapWithErrorHandlingAsync`
previously rethrew errors; the real implementation swallows them after
handling. The mock now matches the real contract, and one pre-existing
logout assertion that depended on the rethrow was updated.
Fixes
[MAR-451](https://linear.app/comfyorg/issue/MAR-451/add-tracking-for-signup-flow-errors)
---------
Co-authored-by: Deep Mehta <42841935+deepme987@users.noreply.github.com>
|
||
|
|
96c6dbcb69 |
fix(website): replace em dash meta title separators with hyphens (#13628)
## Summary Switch the meta title separator sitewide from an em dash (—) to a spaced hyphen ( - ), the follow-up to #13480 tracked as FE-1233, based on the Screaming Frog crawl that flagged the em dash titles. ## Changes - **What**: Replaced the em dash separator with a hyphen in every page meta title, in both `en` and `zh-CN`. Covers ~44 inline page titles, the shared title keys in `i18n/translations.ts` (`cloudNodes.meta.title`, `mcp.meta.title`, `affiliate-terms.page.title`, `enterprise-msa.page.title`, `affiliate.page.title`, `launches.page.title`), and the dynamic `customers`, `demos`, and `supported-models` `... - Comfy` suffixes. `BaseLayout` reuses each page title for `og:title`, `twitter:title`, and the JSON-LD `name`, so those inherit the change from a single source per page. - Updated the e2e title assertions to match. `launches` asserts via `t()` so it follows the translation change automatically. ## Review Focus - **Scope is titles only.** The crawl and this ticket are about meta titles. Description / body copy em dashes are left untouched, they are marketing copy, which we keep em-dashed per the comms-only styling decision. A full JSON-LD audit across all 57 routes confirms every `name`/title field is now em-dash-free. - **zh-CN cloud-nodes title keeps its Chinese double dash `——`** (`自定义节点包合集——开箱即用`). That is correct CJK punctuation, a dash break, not a brand/Latin separator, so the Latin-script separator concern does not apply. Its e2e assertion is left as `——` to match. - No visual change: titles are `<head>` metadata, not rendered UI, so there is nothing to screenshot on the page. Rendered `<title>` output was verified on the dev server across inline, `t()`-based, zh-CN, dynamic, and 404 routes, and `og:`/`twitter:` inheritance was confirmed. Ref: FE-1233 |
||
|
|
ffb4ae34c6 |
feat: App mode UI updates (#13170)
## Summary App mode now uses the same sidenav as the graph view instead of its own custom navigation, and switching between modes happens through a new animated graph/app toggle shared by both views. ## Changes - **What**: - LinearView hosts the standard `SideToolbar`, filtered to the Assets and Apps tabs via new `visibleTabIds`/`forceConnected` props. The bottom-panel and shortcut toggles hide in app mode, and GraphCanvas/SubgraphBreadcrumb hide their instances so only one toolbar and toggle render at a time. - The workflow actions dropdown trigger is now a two-segment graph/app toggle: the inactive segment switches modes directly, the active one opens the actions menu as before, with a FLIP animation on switch. - Each view renders its own toggle, so switching unmounts one and mounts the other. A `displayLinearMode` ref in canvasStore trails `linearMode` by two frames so the incoming toggle animates from the old mode instead of popping in already-switched. - AppModeToolbar slims down to the toggle plus a "Build an app" button; the per-icon Assets/Apps/Share buttons are covered by the sidenav. - The Apps tab gains "Create" header and empty-state actions wired to `Comfy.NewBlankWorkflow`. - App-mode feedback moves into `SidebarHelpCenterIcon` as a Typeform popover with a load-error fallback, replacing `TypeformPopoverButton` and `LinearFeedback` and simplifying LinearPreview to a single `OutputHistory`. - Tests for the toggle, sidebar tabs, help-center fallback, preview, and view; screenshots regenerated. ## Review Focus - The two-frame `displayLinearMode` lag — timing-sensitive; rapid toggling cancels the pending frame chain so the wrong mode never flashes. - `SideToolbar` now serves both views: confirm only one instance renders and the new props behave. ## Screenshots (if applicable) https://github.com/user-attachments/assets/9ad1eb4f-9370-4975-b3f1-b271482f2bf7 --------- Co-authored-by: github-actions <github-actions@github.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
59341a4b71 |
feat: data-driven provider input types with JSON file upload for BYOK secrets (#13571)
## ELI-5
Some cloud providers hand you an API key you type in a box; others (like
Google Vertex) hand you a whole JSON file. The provider list already
comes from the server. This teaches each provider to also say *how* its
credential is entered. When a provider says `json_file`, the "Add
Secret" form now shows an **Upload JSON file** button plus a paste box
instead of the single-line password field. Everything else works exactly
as before.
## Summary
Extends the server-driven secret provider schema with optional
`input_type` (`'text' | 'json_file'`) and `label`, and renders a
file-upload + paste textarea for `json_file` providers (e.g. a Vertex
service-account JSON) instead of the password field.
## Changes
- **What**:
- `SecretProvider` ingest schema gains optional `input_type` and `label`
(added to the generated `types.gen.ts` / `zod.gen.ts` — see Review
Focus).
- `listSecretProviders()` now returns the full provider objects (id +
optional `input_type`/`label`) instead of just ids; `availableProviders`
carries them through to the form.
- The credential field switches on the selected provider's `input_type`:
`json_file` → upload button (reads the file into the value) + paste
textarea with light "must be valid JSON" validation; anything else → the
existing password field.
- Provider display label prefers the server `label`, falling back to the
frontend registry label, then the raw id.
- New i18n keys under `secrets.*`.
- **Breaking**: none. Providers that omit `input_type` render exactly as
today (single-line secret). Read/List responses are untouched — the
credential value is still never echoed back.
## Review Focus
- **Judgment call — one provider, credential kind inferred, no toggle.**
Per the design, a single provider is kept and the raw credential value
is sent as-is; the credential kind (AI Studio key vs Vertex SA JSON) is
inferred server-side. The design allowed "inferred from the upload *or*
an explicit toggle"; I chose inference to keep the UI minimal and avoid
introducing a new user-facing gating control. No provider ids are added
or renamed, so the proxy's 1:1 path→provider mapping is unaffected.
- **Generated-file edit (bridge).** The `input_type`/`label` fields were
hand-applied to the generated
`packages/ingest-types/src/{types,zod}.gen.ts`. The upstream
`openapi.yaml` (the generation source) is not in this repo, so this
models the wire contract additively until the backend spec lands the
same optional fields; a future regeneration reproduces them. The fields
are optional, so nothing breaks if the server omits them.
- **No secret ever echoed back.** Only the create request carries the
value; GET/List responses (metadata-only) are unchanged.
Tests: unit coverage added/updated for the provider metadata
passthrough, `selectedInputType`, server label precedence, file loading,
JSON validation, and the form rendering both branches. Targeted vitest
(79 secrets tests), `pnpm typecheck`, eslint, and oxfmt all green.
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
|
||
|
|
97fffb5394 |
[feat] Add NEW badge to top-level header nav items (#13527)
## Summary Adds an accent NEW badge next to top-level header nav items (Products, Community) on both desktop and mobile. ## Changes - **What**: New `badge?: 'new'` field on `NavItem`, rendered as an accent `Badge` in `HeaderMainDesktop.vue` and `HeaderMainMobile.vue`; adds a `xxs` badge size and drops hardcoded padding/size from the `accent` variant so it composes with size. - **Tests**: e2e coverage asserting the badge shows only on Products/Community, not Company/Pricing. ## Review Focus - The `accent` badge variant now inherits padding/font-size from `size` instead of hardcoding them — verify existing `accent` usages still render correctly. --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: github-actions <github-actions@github.com> |
||
|
|
4972e4d42f |
fix(form-dropdown): stop cancelling native scroll on slow trackpad wheels (#12334)
## Summary Follow-up to #12052. The local wheel guard added in FormDropdownMenu was cancelling native scroll on slow macOS trackpad gestures, leaving image rows blank in the asset picker until a fast scroll re-pumped samples through. ## Changes - **What**: `FormDropdownMenu.onWheel` now only `preventDefault`s on pinch-zoom (`ctrl/meta + wheel`). The `|deltaX| > |deltaY|` branch of `isCanvasGestureWheel` is no longer used here. - **Why**: Slow vertical trackpad scrolls emit small-delta frames with stray horizontal jitter (e.g. `deltaY=1.2`, `deltaX=1.5`) that satisfied the horizontal-dominant predicate. Cancelling those frames starved `VirtualGrid`'s throttled `useScroll`, so `Math.floor(scrollY / itemHeight)` never advanced and tiles never mounted. - Horizontal-swipe-to-navigate is still blocked at the page boundary by `overscroll-behavior: none` on `html, body` (the actual fix from #12052), so dropping the local horizontal guard does not reintroduce the back/forward bug. - The global `isCanvasGestureWheel` is unchanged — `useCanvasInteractions` still uses it for the canvas wheel handler, where the semantics are different (forward to canvas pan, not preventDefault native scroll). ## Review Focus - Confirm `overscroll-behavior: none` on `html, body` is sufficient on its own for the horizontal-swipe-to-navigate case inside the teleported dropdown. (It is — overscroll-behavior is decided per scroll container at gesture start, independent of where in the page the gesture originates.) - Pinch-zoom in the dropdown still preventDefaults correctly. Tested on macOS trackpad: slow vertical scroll now loads images progressively; fast scroll unchanged; pinch-zoom in dropdown does not zoom the page; horizontal swipe in dropdown does not navigate. Related: this fixes the slow-scroll repro reported in #bug-dump. The blank-on-reopen class of bug from FE-535 (#11885) is a different issue. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-12334-fix-form-dropdown-stop-cancelling-native-scroll-on-slow-trackpad-wheels-3656d73d3650813a987ffa8d6f0eaa9c) by [Unito](https://www.unito.io) |
||
|
|
8d8ae7dfc0 |
feat: share one WebGL context across all 3D views (#13547)
## Summary Browsers cap live WebGL contexts per page, so each Load3D node owning its own renderer broke once enough 3D nodes were added. All 3D views now share a single offscreen WebGLRenderer: each view renders into the bottom-left region of the shared drawing buffer and blits the frame into its own plain 2D canvas, which has no context limit. - Add sharedWebGLRenderer (refcounted singleton, grow-only buffer) and RendererView (per-view canvas + per-view renderer state + blit) - Per-view renderer state (tone mapping, color space, clear color) is applied before each view renders; HDRIManager and SceneModelManager write to it instead of the renderer - OrbitControls/TransformControls bind to the node container explicitly - ViewHelperManager renders the axes helper itself so it can position and scale independently of renderer DOM layout - RecordingManager records from the view canvas; capture renders at exact size on the shared buffer as before - On last view release the context is force-lost and webglcontextlost is dispatched synchronously so context-loss handlers still run ## Screenshots (if applicable) before <img width="2155" height="1527" alt="image" src="https://github.com/user-attachments/assets/35a7ab8a-28dd-41ba-8349-525bd2e97977" /> after <img width="918" height="430" alt="屏幕截图 2026-07-09 135025" src="https://github.com/user-attachments/assets/67328577-bd82-4494-921a-6d298442d081" /> <img width="1636" height="1509" alt="image" src="https://github.com/user-attachments/assets/a3be9215-7cd0-427c-8264-1fd964d3ace1" /> |
||
|
|
38c034e1d6 |
feat(load3d): prototype top-bar chrome for embedded 3D viewer (#13205)
## Summary Prototype redesign of the embedded 3D viewer controls into a framed top/bottom-bar chrome. Proof-of-concept for design exploration — not intended to merge as-is. ## Changes - **What**: Replaces the floating viewer controls (`Load3DControls`) with a new `Load3DMenuBar` framed chrome: a black top bar with a category dropdown (Scene / 3D Model / Camera / Lighting) and the active category's actions (labels collapse to icons on narrow nodes), plus a black bottom bar with Record (left) and fit + export (right). Export is moved out of the menu into the bottom-right button. Adds a "Clay" material mode that renders meshes with a flat grey material so geometry is visible without textures. - **Breaking**: None — `Load3D.vue` swaps the component and hides its existing right-side button column behind `v-if="false"`; nothing is deleted. ## Review Focus - Design prototype: `Load3D.vue` swaps `Load3DControls` → `Load3DMenuBar` and hides the existing fit/center/expand/record column. The original components are left untouched. - The only changes outside the prototype component are for the **Clay** material mode, which touches shared engine code: `MaterialMode` type (`interfaces.ts`), the runtime material switch (`SceneModelManager.ts`), the capability lists (`MeshModelAdapter.ts`, `ModelAdapter.ts`, `useLoad3d.ts`), and the i18n label. - Known prototype gaps: **Record** is a visual toggle only (pulsing dot, not wired to real recording); **fit** reuses the existing `handleFitToViewer`; **export** reuses the existing format options. ## Screenshots (if applicable) Prototype walkthrough captured during development (top-bar chrome, category dropdown, labeled-vs-icon collapse, Clay material, pulsing Record). Available on request. --------- Co-authored-by: PabloWiedemann <PabloWiedemann@users.noreply.github.com> Co-authored-by: Terry Jia <terryjia88@gmail.com> |
||
|
|
b40fad0e75 |
1.48.2 (#13596)
Patch version increment to 1.48.2 **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.48.2 |
||
|
|
01cbfa6a23 |
feat(templates): replace template search with MiniSearch and usage ranking (#13386)
## Summary The template picker's search now surfaces the right template for how people actually type — abbreviations, typos, multi-word intent, and non-Latin (CJK) titles — and orders results by real popularity instead of a fuzzy-match score that was being thrown away. Search and ranking now behave the same here as they do on the workflow hub. ## Changes **What** - Searching for the way people phrase things now works: `t2v`, `i2v`, `cn` expand to their full modality terms, `img2img`/`v2v` expand to editing (matching how the catalog tags image/video edit templates), `flux upscale` and `sdxl lora` match across title/model/tag fields together, prefixes like `vid` match `video`, and typos like `contorlnet` still find ControlNet. Versioned names tokenize sensibly, so `wan 2.2` and `wan2.2` both hit, while `2.5` never blurs into `3.5`. - CJK titles are searchable. Unspaced Han/Hiragana/Katakana runs are tokenized into character unigrams and bigrams, so a substring a user types (`放大` inside `图像放大`, or the single trailing `大`) lands on a match. Korean and other spaced scripts fall to the normal word tokenizer, unchanged. - Fuzzy matching is tighter: a term now tolerates edits up to 20% of its length (down from a flat threshold), so `contorlnet` still finds ControlNet but `upscale` no longer fuzzy-matches the shorter, unrelated `scale`. Short (≤3-char) and digit-bearing terms stay exact. - Results lead with text relevance. Previously the fuzzy match score was computed and then discarded, and any active sort re-ordered results by usage — so the best textual match rarely landed on top. Now relevance is the authoritative order while a query is active, and when two results match about equally well, the more-used template wins the tie (dampened so one runaway-popular template can't dominate). - The ranking is a stable total order. Scores are bucketed before usage breaks ties, so a cluster of near-equally-relevant results always sorts the same way — a naive per-pair "within X%" comparison is intransitive and makes the order depend on internal input order (it can even shuffle as you type another character). - "Popular" ranks by raw usage, matching what the hub and the search index show. It previously blended in a freshness term that pushed newer, less-used templates above genuinely popular ones. - The sort dropdown works during search again: it defaults to "Relevance" but you can switch to Popular/Newest/etc. to re-order the results, and your browse sort is restored (and never overwritten by a search-time choice) when you clear the query. - Alphabetical sort reads correctly: it sorts by the title shown on the card, trims stray leading whitespace that used to jump templates to the top, and groups number-prefixed titles after the letters instead of ahead of them. - Filter telemetry now reports the sort the user is actually seeing (relevance while searching) rather than the persisted browse sort, so analytics reflect the visible ordering. - Removed the old runtime Fuse-options override path, which is obsolete under the new engine. **Breaking** None. Existing filters (Model / Use Case / Runs On / distribution), pagination, and persisted sort settings are unchanged; the relevance mode is search-only and never persisted. ## Review Focus - The ranking crux is `rankByRelevanceThenUsage` in `templateSearchConfig.ts`: relevance is primary, usage only re-orders results in the same score bucket, and bucketing keeps it a stable total order. That's the one function to review for correctness. - The CJK tokenizer (`cjkGrams` / `tokenize` in `templateSearchConfig.ts`): script-matched so only unspaced scripts are grammed, and a pure-CJK run relies on its grams (no whole-word token). Splitting by code point is safe here (these scripts are BMP-only; emoji are excluded by the run regex). - Deliberately not touched: the "Recommended" sort keeps its curated blend (usage + editorial rank + freshness) so it stays distinct from "Popular"; `vram-low-to-high` remains unimplemented exactly as on main. ## Tradeoffs / notes - Adds `minisearch` (~18 kB gzip). The template selector is where it's used; accepted for the search-quality gain (a later change could lazy-load it if bundle size becomes a concern). - Bucketing means two results just across a bucket boundary don't tie-break on usage even when their scores are close — the accepted cost of a transitive, predictable order (this mirrors how the search index quantizes relevance). - `img2img` expands to editing (not literal "image to image") because the catalog labels those templates "Image Edit" — verified against the real data. - CJK bigrams roughly double the token count for a pure-CJK title; negligible at catalog scale (~550 templates, short titles). ## Testing Behavioral coverage over the real search paths, not the mocks — the ranking and tokenizer run against actual MiniSearch output; only the ranking-store math is mocked. Also verified against the full ~550-template catalog end to end (all query types stable, zero input-order-dependent orderings). ### Behavior matrix (verified on the real catalog) | Input / action | Now | Previously | | --- | --- | --- | | `img2img` | Image-editing templates (Qwen Image Edit, …) | Matched every "image" template — intent lost | | `flux upscale` | Flux upscale templates (matches both terms across fields) | **No results** (single-field fuzzy couldn't span title + tag) | | `sdxl lora` | SDXL templates | **No results** | | `t2v` / `i2v` / `cn` | Expand to text→video / image→video / controlnet | Only partial slug hits, if any | | `vid` (prefix) | Matches `video` templates | Unreliable | | `contorlnet` (typo) | Finds ControlNet | Often dropped by the strict threshold | | `upscale` | Matches upscale titles only | Fuzzy-matched the unrelated substring `scale` | | `放大` / `大` (CJK) | Matches `图像放大` and other titles containing the run | No match — CJK titles were unsearchable by substring | | `wan 2.2` and `wan2.2` | Both match; `2.5` never matches `3.5` | Space vs no-space degraded the match | | Near-tied cluster (e.g. `upscale`) | Stable order every time | Reordered depending on input order (could shuffle as you type) | | Query active, "Popular" selected | Best textual match still leads; usage breaks near-ties | Sort re-ordered by usage, burying the best match | | Change sort while searching | Re-orders the search results; relevance is the default | Sort was locked; dropdown had no effect | | Clear the search | Restores the browse sort you had before | — | | "Popular" sort | Orders by raw usage (matches hub / index) | Freshness blend pushed newer low-usage templates up | | A–Z sort | Letters first (`ACE…`), number-prefixed titles last (`3x3…`, `360…`); leading whitespace ignored | Leading-space titles jumped to the top; numbers sorted before letters | ### Unit tests (81 total, all passing) - `templateSearchConfig.test.ts` (34) — tokenizer identifier/version splits, CJK unigram/bigram gramming (and Korean left as a spaced word), per-term fuzziness (`upscale` ≠ `scale`), abbreviation expansion (incl. `img2img`→edit intent), prefix + typo matching, AND-then-OR, literal-before-expansion ordering, relevance>tag>description ranking, and `rankByRelevanceThenUsage` giving a stable order on an intransitive cluster. - `useTemplateFiltering.test.ts` (35) — the `img2img` / `flux upscale` / `sdxl lora` regressions, relevance-default-on-search, override-sort-while-searching, browse-sort restore on clear, ephemeral mid-search sort, telemetry reporting the visible sort, Runs-On filter, empty-result guard, filters preserving relevance order, and alphabetical trimming + numbers-after-letters. - `templateRankingStore.test.ts` (12) — freshness and default-score (recommended) math. Gate: `pnpm typecheck`, `pnpm lint`, `pnpm knip` clean. ## Screen Recording (if applicable) https://github.com/user-attachments/assets/6748a3f7-e69d-44ac-826c-71990c8dce90 |
||
|
|
945a143626 |
fix(ci): harden pr-backport — independent per-target attempts + comment on conflict failures (#13412)
## Symptom When the auto-backport workflow (`.github/workflows/pr-backport.yaml`) hits a cherry-pick **conflict**, it is supposed to comment on the original PR telling the author to backport manually. On PR #13359 (backport to `cloud/1.45`, cherry-pick of `d6c582c39` conflicting on `useSubscriptionDialog.test.ts`) **no comment was posted** — the author (@huntcsg) got no notification and had to find the failure by digging into Actions logs. See run [28616756256](https://github.com/Comfy-Org/ComfyUI_frontend/actions/runs/28616756256/job/84862400420). ## Root cause The "Comment on failures" step actually ran and reached the `conflicts` branch — the failure reason *was* populated and the `if:` condition *was* met. The real failure is at the last line of the loop, under the step's default `bash -e` shell: ```yaml gh pr comment "${PR_NUMBER}" --body "${COMMENT_BODY}" ``` The job log ends with: ``` GraphQL: Unable to create comment because issue is locked (addComment) ##[error]Process completed with exit code 1. ``` PR #13359 is `locked: true`, so `gh pr comment` returns non-zero. Because the call was unguarded under `set -e`, the step aborted on the spot: the comment was lost and — critically for the general case — any remaining failed targets in the loop would also be skipped. The step is then marked failed with no actionable output on the PR. (Related PR #13167 "attempt each backport target branch independently" is still open/unmerged; this is a residual gap in the failure-comment path.) ## Fix Wrap every `gh pr comment` call in a `post_comment` helper. On failure it emits a `::warning::` naming the target, the reason, and the manual-backport branch (`backport-<pr>-to-<target>`) instead of aborting, so the loop always attempts a comment for each failed target and surfaces a clear log message when GitHub refuses (e.g. locked issue). The conflict comment body now also states the manual backport branch explicitly. - YAML: `python3 -c 'import yaml; yaml.safe_load(...)'` passes. - `actionlint`: no new warnings in the changed step (the 2 pre-existing `SC2016` notes on the intentional single-quoted `envsubst` var lists are unchanged). 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
193bbaba81 |
fix: dont remove unowned callbacks when cleaning hooks on unmount (#12380)
## Summary Minimaps unmounted cleanup blindly restored the callbacks that are originally captured, even if other systems have chained their own callbacks onto this, breaking other parts of the system (e.g. vue node graph manager). Recreation: 1. Ensure minimap open 2. Enter subgraph 3. Exit subgraph 4. Close minimap 5. Try adding a node/unpackign subgraph/etc <--- broken ## Changes - **What**: - only replace callbacks that we own - else function becomes no-op ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-12380-fix-dont-remove-unowned-callbacks-when-cleaning-hooks-on-unmount-3666d73d3650817cbfe0d98ab98528b8) by [Unito](https://www.unito.io) --------- Co-authored-by: Alexander Brown <drjkl@comfy.org> |
||
|
|
1eacb224a1 |
Update commit author login retrieval in CLA workflow (#13544)
## Summary Make CLA more robust by including commit authors in the allowlist even if they have no GitHub account. This to ensure only PR authors are required to sign. ## Changes - **What**: `cla.yaml` |
||
|
|
4ed2fe70f3 |
feat: accept bboxes input and add grid snapping to Create Bounding Boxes (#13376)
## Summary - Seed/override the canvas from an upstream bboxes input - Add dotted grid background and magnetic snap-to-grid controls - Darken the canvas well for contrast BE is https://github.com/Comfy-Org/ComfyUI/pull/14724 ## Screenshots (if applicable) https://github.com/user-attachments/assets/7282f4d5-7cac-46f0-9d73-d0add37f4eb9 |
||
|
|
5da5ee5031 |
feat: wire up Save 3D (Advanced) node family (CORE-329) (#13330)
## Summary Register the save-side advanced nodes in the Load3D viewer infrastructure: Save3DAdvanced reuses the mesh advanced extension, while SaveGaussianSplat and SavePointCloud reuse the splat/point cloud preview extensions. Parameterize both extension factories with a loadFolder so save nodes load the persisted file from the output folder instead of temp, and add the node types to the lazy-load and viewport-state sets. BE change https://github.com/Comfy-Org/ComfyUI/pull/14701 ## Screenshots (if applicable) Save 3D (Advanced) <img width="1328" height="939" alt="image" src="https://github.com/user-attachments/assets/c5f3cbe0-6e57-463c-9128-67490c2fc89e" /> Save Splat <img width="1296" height="1052" alt="image" src="https://github.com/user-attachments/assets/f05bbcf8-9794-4861-9dd7-3015d38b11d9" /> |
||
|
|
ceb5ae1eba |
fix(cloud): keep survey footer visible on small screens (#13568)
## Summary Keep the onboarding survey's Back/Submit footer visible on small screens by scrolling only the question area instead of the whole survey. ## Changes - **What**: The survey scrolled as a single block , so on short viewports the button row slid under the template footer (Terms/Privacy). Now the outer is bounded to its slot and the question wrapper in `DynamicSurveyForm` scrolls internally with a responsive cap , so option lists scroll while the footer buttons stay pinned. The step height animation is unchanged. ## Screen Recording https://github.com/user-attachments/assets/6d7f6d50-59b8-4dc0-b20e-8f4ca08167c6 |
||
|
|
9f880c78cb |
1.48.1 (#13559)
Patch version increment to 1.48.1 **Base branch:** `main` --------- Co-authored-by: christian-byrne <72887196+christian-byrne@users.noreply.github.com> Co-authored-by: github-actions <github-actions@github.com>v1.48.1 |
||
|
|
3b2eb50f3b |
feat(cloud): redeem desktop login codes for web-to-desktop identity stitching (GTM-93) (#13418)
## What Browser half of GTM-93 macOS web→desktop identity stitching: when the desktop app opens the system browser at cloud login with `?desktop_login_code=dlc_…`, the frontend redeems that code against the cloud backend once a Firebase session exists — after **explicit user approval**. Reworked on top of the preserved-query `stripAfterCapture` capability (#13465): - The `DESKTOP_LOGIN` namespace opts into strip-on-capture: the code is stashed and removed from the URL **before any navigation completes**, so it never reaches history, `previousFullPath`, later guards, or telemetry — the hand-rolled URL scrubbing this PR previously carried (raw-string parser + three strip sites) is gone. - `desktopLoginRedemption.ts` is a plain module with a single export, `installDesktopLoginRedemption(router)`, installed once in `router.ts`'s cloud block (replaces six per-view/composable trigger sites). Redemption reads the code only from the stash: per-code state (approval + 2-attempt transient budget, so a second code gets its own approval and budget), approval dialog, `POST /api/auth/desktop-login-codes/redeem` with the raw Firebase ID token (backend route is Firebase-JWT-only), 10s fetch timeout. - Triggers: `router.afterEach` (the cloud auth guard settles Firebase init before navigations complete) plus a lazy watcher on `authStore.currentUser` for sessions that appear without a navigation (OAuth-resume error branch, dialog sign-in). One bounded in-page retry (5s) guarantees an approved sign-in always ends in a success or failure toast. - Terminal rejections (400/403/404/409/410) drop the code with an error toast; transient failures (401/5xx/timeout/network) retry once; budget exhaustion now surfaces a failure toast instead of dying silently. ## Why Windows stitches web→desktop at download time via installer stamping; macOS DMGs can't be stamped, so we stitch at login. The browser is where both halves meet: the existing `posthog.identify(uid)` merges the web anon person into the Firebase uid, and the backend emits `comfy.cloud.identity.login_attributed` (uid ↔ installation_id) at redeem. The desktop app polls the backend and receives a one-time custom token — no auth material posted to a desktop loopback server (the concern that stalled #12983, which this supersedes). ## Security - **Approval dialog before redeem** — redemption mints the desktop a sign-in token for *your* account, so a lured click must not be enough (device-code phishing mitigation). Cancel clears the stash and does nothing. - Only the opaque single-use code ever appears in a URL; the tracker strips it on first sight, pre-navigation, and it is never logged. ## Testing Vitest, driven through a real router (createRouter/createMemoryHistory, no vue-router mocks) and the real preserved-query manager: capture/stash lifecycle, approval gate (no fetch before approve; decline/dismiss clears; per-code approval), Bearer/body shape, terminal-vs-transient statuses, timeout abort, bounded in-page retry + failure toast on budget exhaustion, per-code regressions (second code after success/decline/exhaustion redeems independently), auth-watcher trigger (session appearing without navigation), unauthenticated no-op, trigger coalescing. Typecheck/lint/format clean. Types are hand-written with a `TODO(@comfyorg/ingest-types)` — the generated types land automatically once the cloud PR merges and the type-gen workflow runs. ## Landing order 1. Cloud backend: https://github.com/Comfy-Org/cloud/pull/4736 (until it ships, redemption never triggers — this PR is inert) 2. #13465 preserved-query strip-on-capture (base of this PR) 3. #13466 global-prompt FIFO queue (runtime dependency: the approval confirm must settle even if another prompt is open) 4. **This PR** 5. Desktop (activates the flow): https://github.com/Comfy-Org/Comfy-Desktop/pull/1222 GTM-93 · Supersedes #12983 --------- Co-authored-by: AustinMroz <austin@comfy.org> |
||
|
|
2ef341dcd8 |
test: E2E for BYOK secret add / list / delete flow (#13510)
## ELI-5 The settings screen now has a "Secrets" panel where you can save API keys for model/AI providers. This adds an end-to-end test that plays out the whole story like a real user: open the panel, add a key, watch it show up in the list, then delete it. It also checks the security promise — the key you type is sent to the server but is **never** shown back to you afterward — and that an account without access to the gated providers never even sees them in the dropdown. ## What Adds `browser_tests/tests/cloudSecrets.spec.ts`, a Playwright spec covering the secrets (API keys) surface in the cloud app: - **Entitled account, full CRUD round-trip:** empty state -> add a provider key (pick provider, name, secret value, save) -> the key appears in the list -> delete it via the confirm dialog -> back to empty state. - **Secret value is write-only:** asserts the create request carried the plaintext value, but the value is never echoed back into the DOM (the list-response schema is metadata-only). - **Entitlement gate:** an account whose provider allowlist is empty never sees the gated providers anywhere in the add dialog. Follows the existing cloud E2E conventions: drives a raw `page` and reuses the `mockCloudBoot` / `bootCloud` helpers so the app boots signed-in against fully mocked endpoints. A small stateful in-memory handler backs the secrets endpoints (list / create / delete + the provider allowlist) so the flow is deterministic and never touches a real backend. ## Why Verification capstone for the secrets settings surface — proves the add / list / delete flow works against the documented API behavior (`GET`/`POST`/`DELETE` on the secrets collection, `GET` on the provider allowlist) and locks in the two contracts that matter: the secret value is never returned after creation, and the provider allowlist is the only thing that surfaces gated providers to the user. ## Tests - `browser_tests/tests/cloudSecrets.spec.ts` — new, two cases (tagged `@cloud`). - Static checks pass locally: oxlint (0 warnings/errors) and oxfmt formatting. - The browser run itself needs a served app + the E2E harness (CI), so it was not executed in this environment; the spec is self-contained and mocks all network. --------- Co-authored-by: GitHub Action <action@github.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
1815c7f7a4 |
feat(website): add JSON-LD structured data across the site (#13480)
## Summary This PR adds schema.org **JSON-LD structured data across the whole marketing site**, built from one shared, CMS-ready module and gated by a small CI validator. It replaces the old global block (which had a stale logo, wrong social links, disconnected nodes, and a head slot that rendered three times) with a single connected `@graph` on every page. Structured data only — there is no visual or runtime change for users. Tracks Linear **FE-1170**. The design goal was that structured data should be impossible to get subtly wrong: one place builds it, honesty rules are enforced in code, and a build-time validator fails the build if any page ships a broken `@id` graph or a fabricated price/rating. ## Changes - **One builder, one sink.** `utils/jsonLd.ts` holds pure, node-testable builders; `components/common/JsonLdGraph.astro` is the single escaped `<script type="application/ld+json">` sink (prevents `</script>` breakout XSS). - **The layout owns the page entity.** `BaseLayout` emits a baseline `Organization` + `WebSite` + `WebPage` graph on every page from its own `title`/`description`/canonical props. Enriched pages pass only what is specific to them: `pageType`, `breadcrumbs`, `mainEntityId`, and `extraJsonLd` nodes. This makes it impossible for a page's meta tags and its structured data to drift apart. - **Corrected site-wide entity.** Raster PNG logo (Google does not index SVG logos), real `sameAs` handles sourced from the footer links, `@id`-linked `Organization`/`WebSite`, and the triple-rendered head slot fixed. - **Honesty is enforced, not just intended.** No fabricated `Review`/`AggregateRating`/`Offer`. Pricing offers are parsed only from plain `$N` copy (a future "Contact us" drops the offer instead of shipping a garbage price). Third-party node packs and listed models do **not** claim Comfy Org as author/publisher. `noindex` pages (404, payment) emit no structured data. - **CI validator.** `pnpm --filter @comfyorg/website validate:jsonld` runs over `dist/` in the website build workflow and fails on invalid JSON, an unresolved `@id`, a fake rating, or an empty/non-numeric offer price. - **Breaking:** none. ## Coverage (also a manual QA checklist for the preview) Every public page carries at least `Organization` + `WebSite` + `WebPage`. The pages below add a page-specific primary entity, in **English and zh-CN**: | Page | Path (example) | Adds to the graph | |---|---|---| | Home | `/`, `/zh-CN` | `SoftwareApplication` (ComfyUI, free) + `SoftwareSourceCode` | | Download | `/download` | `SoftwareApplication` (ComfyUI desktop) | | Pricing | `/cloud/pricing` | `Product` + 3 monthly `Offer`s ($20/$35/$100) + Breadcrumb | | Models catalog | `/p/supported-models` | `CollectionPage` + `ItemList` (313, lean) + Breadcrumb | | Model detail | `/p/supported-models/4x-ultrasharp` | `SoftwareApplication` + `FAQPage` + Breadcrumb | | Nodes catalog | `/cloud/supported-nodes` | `CollectionPage` + `ItemList` (58 packs) + Breadcrumb | | Node-pack detail | `/cloud/supported-nodes/ComfyQR` | `SoftwareApplication` (+ free `Offer`) + Breadcrumb | | Demos | `/demos/community-workflows` | `LearningResource` + Breadcrumb | | About / Contact | `/about`, `/contact` | `AboutPage` / `ContactPage` (Org as `mainEntity`) + Breadcrumb | | Careers | `/careers` | `CollectionPage` + `ItemList` of open roles + Breadcrumb | | Affiliates | `/affiliates` | `FAQPage` + Breadcrumb | Pages deliberately left at the baseline `WebPage` (generic landings, legal, coming-soon) and pages with **no** structured data (`noindex`: `/404`, `/payment/*`; redirect URLs) are intentional. ## Review Focus - **Layout-owns-WebPage design.** `BaseLayout` builds the `WebPage`; pages contribute only extra nodes. This is the main structural decision and is what removes meta-vs-schema drift by construction. - **Honesty guardrails.** Worth confirming: pricing offers, third-party author omission on packs/models, and that `noindex` pages emit nothing. - **`@id` and URL consistency.** All cross-page links and `@id`s resolve to the canonical trailing-slash form; zh-CN breadcrumbs are rooted under `/zh-CN`; the singleton `WebSite`/`#software` entities carry one consistent definition across pages and locales. - **The validator.** It is a bespoke ~100-line script scoped to the website build job (not the prod deploy). Happy to make it non-blocking or drop it if the team prefers. - **Coordination with #13468.** Both branches add `components/common/JsonLdGraph.astro`. Customer pages are intentionally excluded here; #13468 can converge onto this shared builder. ## Verification `astro check` 0 errors · 166 unit tests · `knip` 0 · `eslint` 0 · build 497 pages · validator passes across 500 pages · JSON-LD e2e specs 24/24. (The 3 pre-existing demo e2e timeouts are an external Arcade-embed flake on one slug, reproduced identically on `main`.) ## Screenshots Not applicable — head-only structured data, no visual change. Validate on the Vercel preview with the Rich Results Test and the schema.org validator. Note: `@id`/URL values render as `comfy.org` (from `astro.config` `site`) even on the preview host, which is correct. |
||
|
|
287b9eb980 |
feat(website): add HeroBackdrop01 block component (#13549)
## Summary Adds the `HeroBackdrop01` hero block component to the website app. This lands the component on its own so it can be merged to `main` ahead of the EDU page work (on another branch) that consumes it. The component renders a responsive hero with an optional image/video backdrop (in-flow rounded card on mobile, full-bleed background on desktop), an optional product badge, title, subtitle, and footnote. It respects `prefers-reduced-motion` by not autoplaying the looping backdrop video (WCAG 2.2.2). ## Notes - No consumer imports the component yet — it is intentionally added ahead of the page that will use it. The `knip` unused-file check flags this, which is expected for this staging PR. - Depends on existing `useReducedMotion` composable and `ProductHeroBadge` component, both already present on `main`. ## Test plan - [ ] `pnpm typecheck:website` passes (verified locally via pre-commit hook) - [ ] Component renders correctly once wired into a page 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
06b0471257 |
test: pin unknown-provider flow-through in server-driven provider options (#13546)
## ELI-5 The provider picker for BYOK secrets is now driven by the server's `availableProviders` list, not a hardcoded frontend list. The point is that a provider the server offers but the frontend has never heard of should still show up — using its raw id as the label and no logo. This adds the one test that actually pins that behavior, so nobody can later re-add a "only show providers the frontend knows about" filter without a test going red. ## Summary Regression test for the server-driven provider options introduced in #13509: asserts that an unknown provider id passes through `providerOptions` rather than being filtered against the local presentational registry. ## Changes - **What**: Adds one `useSecretForm.test.ts` case asserting that a create-mode `availableProviders` list containing an id absent from the local `SECRET_PROVIDERS` registry (`'brand-new-provider'`) yields a single `providerOptions` entry rendered with the raw id as its label and `logo: undefined`. ## Review Focus The existing suite already covers the registry fallback (`providers.test.ts`) and server-listed *known* ids (`runway`/`gemini`), but every one of those cases uses an id that exists in the local registry — so a future change that re-filtered `availableProviders` against `SECRET_PROVIDERS` could pass all current tests while silently dropping unknown providers. This test closes that gap by using an id that is deliberately absent from the registry, so it fails if pass-through is ever broken. Follow-up to the optional review ask on #13509. Test-only; no production code changes. |
||
|
|
8120142f49 |
feat(cloud): redesign the cloud onboarding survey (#13518)
## Summary New users get a cleaner, faster onboarding survey: one question per screen on tappable cards that advance the moment you pick an answer, with follow-up questions that appear only when they're relevant. The questions themselves were reworked to learn what people actually want to do with ComfyUI. ## Changes **What** - Replaced the radio/checkbox list with a card-based, one-question-at-a-time wizard. Choosing a single-select answer advances automatically — no separate Next click — while multi-select and free-text steps still wait for you to confirm. - Reworked the question set: what you want to make, how well you know ComfyUI, and how you found us. Two questions are now conditional — a "what are you building?" follow-up appears only for workflow/API builders, and a "which platform?" follow-up appears only when you say you found us on social media. - Added an "other" free-text escape hatch to the intent and source questions, required before you can move on so we don't capture an empty "other". - Polished the whole surface to the comfy-canvas theme with animated step-height and cross-fade transitions between questions, and hid the marketing hero on the survey and user-check routes so the form has room. - Errors now surface only after you've interacted with a field, not on first paint. - Extended the telemetry survey-response and remote-config option shapes to carry the new fields (including per-option icons), leaving the older fields in place so historical responses still typecheck. **Breaking** — None. The remote-config survey schema stays backend-overridable, hidden branch answers are zeroed in the submitted payload, and the telemetry field names line up 1:1 with the schema. The backend dynamic config already ships the matching version-3 schema. ## Testing Behavioral coverage over the wizard's real interactions rather than DOM structure, since the risk is in navigation/branching/validation, not markup. `vue-i18n` is mounted for real with the actual locale file so tests assert on rendered copy. - [DynamicSurveyForm.test.ts](src/platform/cloud/onboarding/survey/DynamicSurveyForm.test.ts) — auto-advance on single-select, no-advance on multi/other, Back navigation, branch reveal/hide and its submitted payload, required-"other" gating, post-interaction error surfacing, and survey-prop reset. - [DynamicSurveyField.test.ts](src/platform/cloud/onboarding/survey/DynamicSurveyField.test.ts) — card rendering/selection state, stable option ids, multi-select emit, the conditional "other" input, and label resolution via key / locale map / id fallback. - [surveySchema.test.ts](src/platform/cloud/onboarding/survey/surveySchema.test.ts) — default-schema branching (which steps show), hidden-field zeroing and free-text-over-sentinel in the payload, and the shared `hasNonEmptyValue` truth table. Gates: `vue-tsc` typecheck clean; eslint/oxfmt clean on touched files; survey suite green (68 tests across the three files). Manual: run the cloud onboarding flow, pick a workflow/apps intent to confirm the "building" follow-up, pick social to confirm the platform follow-up, and verify an empty "other" blocks advancing. ## Screen Recording https://github.com/user-attachments/assets/1908ca18-93d1-41a2-a55b-1f04a6df2268 |
||
|
|
3164e6ab61 |
On workflow swap, restore 'Preview as Text' text (#13536)
Also adds proper typing for `onNodeOutputsUpdated` See also: #12877 and #13427, which include near equivalent changes for the bug itself, but different tests. If I had more time and had not already made my own fix, I would have liked to spend more time getting either of them cleaned up. |
||
|
|
731512c655 |
test: remove timeout causing flake (#13543)
## Summary The timeout resolved 1 second after the test completed sometimes throwing: ``` ⎯⎯⎯⎯ Unhandled Rejection ⎯⎯⎯⎯⎯ ReferenceError: window is not defined ❯ resolveMessageFormat node_modules/.pnpm/@intlify+core-base@9.14.5/node_modules/@intlify/core-base/dist/core-base.mjs:1357:13 ❯ translate node_modules/.pnpm/@intlify+core-base@9.14.5/node_modules/@intlify/core-base/dist/core-base.mjs:1216:11 ❯ node_modules/.pnpm/vue-i18n@9.14.5_vue@3.5.34_typescript@5.9.3_/node_modules/vue-i18n/dist/vue-i18n.mjs:581:48 ❯ wrapWithDeps node_modules/.pnpm/vue-i18n@9.14.5_vue@3.5.34_typescript@5.9.3_/node_modules/vue-i18n/dist/vue-i18n.mjs:526:19 ❯ t node_modules/.pnpm/vue-i18n@9.14.5_vue@3.5.34_typescript@5.9.3_/node_modules/vue-i18n/dist/vue-i18n.mjs:581:16 ❯ onNodePackChange src/workbench/extensions/manager/components/manager/PackVersionSelectorPopover.vue:201:10 199| // Add Latest option with actual version number 200| const latestLabel = latestVersionNumber 201| ? `${t('manager.latestVersion')} (${latestVersionNumber})` | ^ 202| : t('manager.latestVersion') 203| This error originated in "src/workbench/extensions/manager/components/manager/PackVersionSelectorPopover.test.ts" test file. It doesn't mean the error was thrown inside the file itself, but while it was running. ``` The timeout was not required for the test to validate the loading text. ## Changes - **What**: Remove timeout |
||
|
|
c0ad1e98c2 |
test(website): add e2e specs for the learning page (#13529)
## Summary Add end-to-end test coverage for the `/learning` page, which previously had none. ## Changes - **What**: New `e2e/learning.spec.ts` covering the EN page smoke behaviour (hero, featured workflow, tutorial grid rendered from the `learningTutorials` data source, per-tutorial Try Workflow links, and the contact-sales CTA), the tutorial video dialog open/close/Escape interactions, and the zh-CN localized page. ## Review Focus Assertions are driven off the `learningTutorials` data source and `t()` i18n keys rather than hardcoded strings to avoid change-detector tests. Media is stubbed by the auto-applied `blockExternalMedia` fixture, so the specs have no network dependency. The tutorial-dialog interaction uses a `toPass` retry to accommodate `client:visible` hydration. |
||
|
|
bd9fab2d2f |
1.48.0 (#13541)
Minor version increment to 1.48.0 **Base branch:** `main` Co-authored-by: christian-byrne <72887196+christian-byrne@users.noreply.github.com>v1.48.0 |
||
|
|
c7fe6a23ec |
1.47.7 (#13246)
Patch version increment to 1.47.7 **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.47.7 |
||
|
|
df9b5bfa0a |
feat(website): rework MCP page setup + hero, hide promo banner on /mcp (#13514)
## Summary Two changes to the comfy.org `/mcp` experience: reframe the Setup section around the agent-driven install and tidy the hero CTAs, and stop the sitewide MCP promo banner from showing on the page it links to. ## Changes - **Setup Step 1** is now **"Ask your agent to install Comfy MCP"** with a multi-line, copyable prompt (`Help me install Comfy MCP. / Follow the setup guide at https://docs.comfy.org/agent-tools/cloud`). `CopyableField` gains a `multiline` variant (wraps the text, top-aligns the copy button); `FeatureGrid01`'s `code` action threads the flag through. - **Step 2** becomes the optional manual-connector path (**"Or add it by hand"**) so the three-step flow stays coherent — no dangling "paste the URL" that Step 1 no longer copies. - **Hero** swaps the **"Run a workflow"** primary CTA for **"Install MCP"**, which anchors to the on-page `#setup` steps; **"View Docs"** stays as the secondary CTA. - **Announcement banner** no longer advertises the page you're already on. `evaluateBannerVisibility` gains a build-time gate: when the current path matches the banner's CTA destination it's suppressed. Locale prefix is stripped (so `/zh-CN/mcp` matches an unprefixed `/mcp` href), trailing slashes are tolerated, and external CTA links never suppress. Result: the MCP banner shows everywhere except `/mcp` and `/zh-CN/mcp`. - New i18n keys (`mcp.setup.step1.command`, `mcp.hero.installMcp`) with en + zh-CN parity; 5 new unit tests cover the banner suppression logic. - **Breaking**: none. ## Review Focus - @deepme987 @bertfy — copy + flow check. The Step 1 prompt is a paraphrase of the `agent-tools/cloud` install guide. The raw `cloud.comfy.org/mcp` URL is no longer surfaced on the page (the manual path now points to the MCP docs instead) — flag if you'd rather keep the raw URL visible in Step 2. - Banner suppression keys off the CTA's `link.href` — general, so any future banner pointing at an internal page auto-hides on that page. It's a **build-time** gate (this is a static site), consistent with the existing `startsAt`/`endsAt` behavior. - Only the **hero** CTA changed. The "How it works" section deliberately keeps its "Run a workflow" CTA. - zh-CN strings are machine-drafted; a native check would be welcome. ## Screenshots Verified locally against a production build (`astro build` + preview): - **Hero** — `INSTALL MCP` + `VIEW DOCS` (the "Run a workflow" button is gone). "Install MCP" scrolls to `#setup`. - **Setup** — Step 1 shows the agent-install prompt in a multi-line copyable field; Step 2 "Or add it by hand"; Step 3 unchanged. - **Banner** — present on `/`, `/download`, `/cloud`; absent on `/mcp` and `/zh-CN/mcp`. A Vercel preview deploy attaches automatically for a live view. --------- Co-authored-by: imick-io <153135517+imick-io@users.noreply.github.com> |
||
|
|
a6b7ce11aa |
feat: add preview support for save text node (CORE:-176) (#12521)
## Summary Add a frontend preview extension for the SaveText node that displays saved text content after execution. ## Changes The extension add a multiline text widget into the SaveText node and populates it upon onExecuted PR on core: https://github.com/Comfy-Org/ComfyUI/pull/14102 ## Review Focus <!-- Critical design decisions or edge cases that need attention --> Extension follows the same pattern as previewAny.ts <!-- If this PR fixes an issue, uncomment and update the line below --> <!-- Fixes #ISSUE_NUMBER --> ## Screenshots <img width="1127" height="421" alt="Screenshot 2026-05-29 194925" src="https://github.com/user-attachments/assets/e7a72807-858b-47c7-be07-595f9e539a49" /> <!-- Add screenshots or video recording to help explain your changes --> --------- Co-authored-by: Terry Jia <terryjia88@gmail.com> |
||
|
|
d3b100be8d |
feat: render BYOK provider surface (labels / logos / help) from server data (#13509)
## ELI-5 The "API Keys & Secrets" settings screen lets you save a key for a provider (HuggingFace, Civitai, and now video/image API providers). The list of which providers you can pick is decided by the server. This PR makes the picker and the saved-keys list show a nice name, logo, and short help text for each provider the server offers — and, crucially, actually render providers the server newly lists instead of silently dropping them. ## What - The provider dropdown in the add-secret dialog is now driven by the providers the server returns from `GET /secrets/providers`. Each id is mapped to its display label, logo, and optional help text through a small presentational registry. - Previously the dropdown took a hardcoded known-provider array and *intersected* it with the server list, so any provider the server listed that wasn't already hardcoded could never appear. That intersection is gone: once the server list loads, it renders verbatim. - Unknown provider ids fall back gracefully to the raw id with no logo, so adding a provider server-side requires no frontend change; giving it a first-class label/logo is an optional enhancement. - The saved-keys list already resolved label/logo through the same registry, so it picks up the new providers automatically. - Added provider-specific help text under the picker (falls back to the generic hint), plus placeholder logo assets under `public/assets/images/` for the two new API providers. ## Why Keeps the provider surface data-driven end to end: the server owns *which* providers are configurable, and the frontend owns *how* each one renders. This removes the last hardcoded gate that stopped server-listed providers from showing up. ## Tests - New `providers.test.ts`: label/logo/help lookups for all known providers, graceful fallback for unknown ids and `undefined`, and that the not-loaded fallback list stays the pre-existing baseline (does not silently include the new providers). - Extended `useSecretForm.test.ts`: server-listed providers render with correct labels + logos; providers the server omits do not appear; provider-specific vs generic help text selection. - Full secrets suite green (66 tests). Changed files typecheck clean. Note: the two new provider logos are simple placeholder SVGs and can be swapped for final brand assets. --------- Co-authored-by: GitHub Action <action@github.com> |
||
|
|
54b0c10148 |
fix(auth): stop workspace auth from oscillating to personal identity (#13511)
## Summary Fixes a "weird stale auth" bug where cloud requests oscillated between workspace-scoped and personal (Firebase) identity. **Root cause:** workspace membership lives in two decoupled places — `teamWorkspaceStore.activeWorkspaceId` (durable intent) and `workspaceAuthStore` (the mintable token). When the token was transiently missing while `activeWorkspaceId` was still set (bootstrap mint in flight, expired token, or a context cleared by a recoverable refresh failure), `getAuthHeader`/`getAuthToken` silently downgraded to the personal Firebase token. Depending on timing, consecutive requests carried different identities, so the backend saw the user flip between workspace and personal scope. ## Changes - **On-demand recovery, fail closed:** when a workspace is active, `getAuthHeader`/`getAuthToken` route through `ensureWorkspaceToken(activeWorkspaceId)`, which re-mints the token on demand and returns `null` rather than downgrading. Recovery also revalidates expiry, so an expired token is reminted instead of sent stale. - **`getAuthToken` parity:** WebSocket/queue auth now recovers the same way (previously only `getAuthHeader` did). - **Coalescing:** a burst of callers collapses onto a single in-flight mint (loop re-checks the in-flight promise), and only a token minted for the requested workspace is accepted. - **Backoff:** a 5s cooldown after any failed/empty recovery prevents hammering `POST /auth/token`; reset on a successful mint and on context teardown. - **Lifecycle hygiene:** `clearWorkspaceContext()` now resets `recoveryCooldownUntil` and `inFlightSwitchPromise` so logout/re-login without a reload isn't wedged. - **Transient vs permanent:** a missing Firebase ID token while the user is still signed in (e.g. `NETWORK_REQUEST_FAILED`, which `getIdToken()` swallows) is treated as transient, not a revoked session. - **Revoked-workspace reconciliation:** on `ACCESS_DENIED`/`WORKSPACE_NOT_FOUND`, `teamWorkspaceStore.forgetRevokedActiveWorkspace()` drops the persisted selection and reloads to fall back to the personal workspace (skipping the personal workspace itself to avoid reload loops). `INVALID_FIREBASE_TOKEN`/`NOT_AUTHENTICATED` do not trigger this. ## Testing - `pnpm test:unit` for the three affected stores: **210 tests pass**. - `pnpm lint` and `pnpm typecheck` pass locally (run with a raised Node heap; the pre-commit/`pnpm typecheck` step OOMs in this environment, so commits used `--no-verify` — CI should re-run the gates). Draft pending green CI. --------- Co-authored-by: GitHub Action <action@github.com> |
||
|
|
2b540a5281 |
fix: resolve cloud output video missing-media false positives (#13507)
## Summary Fix Cloud missing-media false positives for output videos inserted as loader nodes when the widget value includes a subfoldered output path such as `video/<hash>.mp4 [output]`, while Cloud output assets expose the generated media by a flat hash. ## Changes - **What**: resolve Cloud output candidates with subfolders against output asset hashes by falling back from the normalized candidate basename only for Cloud output media. - **What**: keep that fallback hash-only so unrelated flat output asset names do not satisfy subfoldered candidates. - **What**: make Cloud output pagination completion hash-aware so a colliding `asset.name` does not stop loading before the hash match appears. - **What**: add unit regressions plus a small Cloud E2E fixture covering `LoadVideo` with `video/cloud-video-hash.mp4 [output]`. - No breaking changes or dependency changes. ## Review Focus Root cause: Cloud output videos can be inserted as loader nodes with widget values from workflow metadata that include a media subfolder, e.g. `video/<hash>.mp4 [output]`. The Cloud output asset list resolves generated media by a flat hash. The existing missing-media scan compared the subfoldered candidate against flat asset identifiers, so valid generated output media could be flagged as missing. Images often did not reproduce because their metadata commonly lacked the subfolder, so the existing exact/compact matching happened to work. Why the fix is scoped to missing-media scan: the inserted widget value is valid node/workflow state and may carry folder information for other Cloud/runtime paths. Stripping the subfolder at insertion would broaden the behavioral change to asset insertion and loader widgets. Missing-media scan owns the decision of whether a candidate resolves to an existing Cloud asset, so the smallest production change is to recognize the Cloud output hash shape there. Why this is safe for Cloud: the basename fallback is applied only for candidates annotated as output and only in Cloud. It matches against output asset hashes, not flat asset names, which prevents unrelated assets named `<hash>.mp4` from masking a real miss. Input media and non-Cloud exact path behavior continue to use the existing identifier matching. Cloud output pagination early-exit is also hash-aware now, so a flat name collision cannot stop paging before the real hash match is fetched. Red-green and validation: added the unit regression first to reproduce `video/<hash>.mp4 [output]` as missing before the production fix, then verified it green after the scanner/resolver change. Added a compact Cloud E2E for the same `LoadVideo` subfoldered output case. Local validation run: - `pnpm test:unit src/platform/missingMedia/missingMediaScan.test.ts src/platform/missingMedia/missingMediaAssetResolver.test.ts` - 59 passed - `PLAYWRIGHT_LOCAL=1 PLAYWRIGHT_TEST_URL=http://localhost:5175 PLAYWRIGHT_SETUP_API_URL=http://localhost:8188 pnpm exec playwright test browser_tests/tests/propertiesPanel/errorsTabMissingMediaRuntime.spec.ts --project=cloud --grep "subfoldered output video" --repeat-each=5 --reporter=line` - 5 passed - `PLAYWRIGHT_LOCAL=1 PLAYWRIGHT_TEST_URL=http://localhost:5175 PLAYWRIGHT_SETUP_API_URL=http://localhost:8188 pnpm exec playwright test browser_tests/tests/propertiesPanel/errorsTabMissingMediaRuntime.spec.ts --project=cloud --grep "resolves compact annotated output media|resolves subfoldered output video" --reporter=line` - 2 passed - `pnpm typecheck` - `pnpm typecheck:browser` - `pnpm knip` - `pnpm lint` - staged pre-commit hooks: oxfmt, oxlint, eslint, typecheck, typecheck:browser ## Screenshots (if applicable) Before https://github.com/user-attachments/assets/4d980546-a981-4764-9c81-4eaed69e4679 After https://github.com/user-attachments/assets/6a4ddb26-6c16-4cbf-b7bc-b9cd55eece58 |
||
|
|
51156c5503 |
chore: route CodeRabbit test reviews through guidance docs (#13486)
## Summary
Adds CodeRabbit `path_instructions` so changed test files are reviewed
with the repo's existing test guidance docs.
## Changes
- **What**: Routes Vitest `**/*.test.ts` files to
`.agents/checks/test-quality.md`, `docs/testing/README.md`, and
`docs/guidance/vitest.md`.
- **What**: Routes Playwright
`{browser_tests,apps/website/e2e}/**/*.spec.ts` files to
`.agents/checks/test-quality.md`, `docs/testing/README.md`, and
`docs/guidance/playwright.md`.
- **What**: Routes LiteGraph Vitest files to the same Vitest docs plus
`docs/testing/litegraph-testing.md`, which now only documents shared
factory usage and preferring real LiteGraph instances where practical.
## Review Focus
Confirm the path globs cover the intended test files and that
LiteGraph-specific review guidance stays scoped to LiteGraph tests.
## Testing
- Parsed `.coderabbit.yaml` with Ruby YAML
- Ran `git diff --check`
- Commit hooks ran `oxfmt`, `oxlint`, `eslint`, and `pnpm typecheck`
- Push hook ran `knip --cache`
Created by Codex
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> **Low Risk**
> Documentation and CodeRabbit review configuration only; no runtime or
test execution behavior changes.
>
> **Overview**
> Configures **CodeRabbit `path_instructions`** so automated reviews of
changed tests pull in the repo’s written testing standards instead of
generic heuristics.
>
> **Vitest** (`**/*.test.ts`) reviews must treat
`.agents/checks/test-quality.md`, `docs/testing/README.md`, and
`docs/guidance/vitest.md` as required context. **LiteGraph Vitest**
under `src/lib/litegraph/**/*.test.ts` adds
`docs/testing/litegraph-testing.md`. **Playwright** specs under
`browser_tests/` and `apps/website/e2e/` use the test-quality and
testing README docs plus `docs/guidance/playwright.md`.
>
> The testing index now lists a fourth guide, **LiteGraph Testing**, and
the new `litegraph-testing.md` doc steers authors toward
`litegraphTestUtils` factories and real LiteGraph instances over broad
mocks.
>
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
|
||
|
|
e11f98b91f |
feat: render secrets provider dropdown from server data (#13494)
## ELI-5 The "Secrets" settings panel lets you add an API key for a provider (HuggingFace, Civitai). Until now the list of providers in that dropdown was hardcoded in the frontend. This PR makes the dropdown ask the server which providers you're allowed to configure (`GET /api/secrets/providers`) and shows that set instead. For everyone today it looks exactly the same — the endpoint returns the same two base providers — but it means the backend can now control the list (needed for the upcoming bring-your-own-key providers) without a frontend change. ## Summary Render the Secrets provider dropdown from server data (`GET /api/secrets/providers`) instead of the hardcoded provider list, with no visual change for existing users. ## Changes - **What**: - `secretsApi.ts`: add `listSecretProviders()` → `GET /api/secrets/providers`, returning the provider ids. - `useSecrets`: fetch the available providers on panel mount into `availableProviders`; failures are logged and swallowed so no new error surfaces to users. - `useSecretForm`: `providerOptions` is now gated by the server response — when providers are returned, the dropdown shows exactly that set; when the list is empty (endpoint absent/unreachable) it falls back to the base providers, preserving today's UX. - Thread `availableProviders` from the panel through both `SecretFormDialog` instances. - **Breaking**: none — panel visibility is still gated on the `userSecretsEnabled` feature flag; only the *contents* of the provider dropdown are now server-driven. ## Review Focus - **Ships dark / byte-for-byte UX.** The server's base response is exactly the two hardcoded providers, and any failure/empty response falls back to the hardcoded list, so existing users see zero change. The membership of the dropdown is what becomes server-driven. - **Response types come from the generated `@comfyorg/ingest-types`.** `SecretMetadata` aliases the generated `SecretResponse`, and `secretsApi` consumes `SecretListResponse` / `SecretProvidersResponse` for the list + providers envelopes — no hand-typed duplicates. `provider` follows the schema as a free-form string; the known-provider union stays only for the label/logo UI in `providers.ts`. - **Provider metadata (labels/logos) still comes from `providers.ts`.** The endpoint returns identifiers only; label/logo per provider is a follow-up (provider surface labels/logos). So this first cut intentionally shows base providers only — an id the server returns that has no local label/logo config is not rendered yet. ## Test plan - `useSecrets`: `fetchProviders` populates `availableProviders`; API failure leaves it empty and raises no toast. - `useSecretForm`: options restrict to the server-returned providers, fall back to base providers when empty, and react to the list changing. --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
bab2a22428 |
fix(dialog): restore backdrop scrim on non-modal Reka dialogs (#13502)
## Summary Restore the backdrop scrim on Reka dialogs opened with `modal: false` (Settings, Manager, legacy-team subscription) — reka-ui only renders `DialogOverlay` for modal roots, so these dialogs silently lost their backdrop when they moved to the Reka renderer. ## Changes - **What**: `src/components/ui/dialog/DialogOverlay.vue` injects `injectDialogRootContext()` (public reka-ui export) and branches: modal dialogs keep Reka's `DialogOverlay`; non-modal dialogs render a plain backdrop `div` wrapped in Reka's `Presence` (`:present="forceMount || open"`), so it honors `forceMount` and plays the `data-[state=closed]` exit fade in sync with the content. Both branches carry `data-testid="dialog-overlay"`. - **Popover stacking**: the scrim/content carry inline z-indexes from @primeuix's `'modal'` counter, which body-portaled popovers with a static `z-1700` class lost to. Extracted `DropdownMenu.vue`'s existing lift into `useModalLiftedZIndex` and applied it to `ColorPicker.vue` and the shared `ui/Popover.vue`, so they stack above the top-most dialog (verified live: scrim 1701 < content 1702 < picker 1703, panel clickable, Settings stays open). ## Review Focus - **Why not `modal: true`**: Settings/Manager intentionally opt out of Reka's modal mode because its focus trap + body `pointer-events: none` break nested PrimeVue overlays teleported to body (see comments in `useSettingsDialog.ts` / `useManagerDialog.ts`). The fallback restores only the visual scrim; body pointer-events stay `auto`. - **Dismissal semantics unchanged**: the scrim sits outside Reka's `DismissableLayer`, so a pointerdown on it goes through the existing `onRekaPointerDownOutside` bridge — scrim click dismisses the top-most dialog, exactly like the modal overlay path (unit-covered). - **CustomizationDialog also gains its scrim back**: the bookmark-folder Customize dialog renders `:modal="false"` with an explicit `<DialogOverlay />`, so it picks up the backdrop too (its template always intended one); its ColorPicker popover is covered by the z-index lift above. A test pins that a mounted-but-closed non-modal root renders no scrim (CustomizationDialog mounts with `open: false`). Verified live (dev): scrim renders behind Settings; nested Modify-Keybinding dialog opens/focuses over it without dismissing Settings; scrim click closes Settings; `Comfy.Load3D.BackgroundColor` color picker opens above the scrim and is fully interactive; no new console warnings. Unit tests are red without the fix, green with it. Reported in Slack (FE Main), design confirmed scrims are intended. ## Screenshots Images hosted on a fork-only branch (`pr-assets/13502-scrim`), not part of this PR's history. | Before (`modal: false` — no scrim) | After | | --- | --- | | <img width="480" alt="before" src="https://raw.githubusercontent.com/dante01yoon/ComfyUI_frontend/pr-assets/13502-scrim/.github/pr-assets/settings-scrim-before.png" /> | <img width="480" alt="after" src="https://raw.githubusercontent.com/dante01yoon/ComfyUI_frontend/pr-assets/13502-scrim/.github/pr-assets/settings-scrim-after.png" /> | Color picker stacking above the scrim (z-index lift): <img width="640" alt="color picker above scrim" src="https://raw.githubusercontent.com/dante01yoon/ComfyUI_frontend/pr-assets/13502-scrim/.github/pr-assets/settings-colorpicker-above-scrim.png" /> |
||
|
|
1efe8d9da5 |
feat: make errors tab selection an emphasis instead of a filter (#13459)
## Summary
Selecting a node no longer filters the errors tab down to that node —
the tab now always shows every error in the workflow, and selection
instead *emphasizes* the matching entries (auto-expand, row highlight,
and a context label), so the error count never lies about whether the
workflow can run.
## Why
The errors tab has quietly been playing two roles at once. It is a
**status surface** ("is this workflow runnable? what's broken and how
much?") — that's what the hero count, the panel-button badge, and the
Run warning all lean on. But it also behaves like an **inspector**:
select a node and the whole list silently narrows to that node's errors.
Mixing the two is confusing in a very concrete way: with 3 errors in the
workflow, clicking one error node makes the tab read "1 Error detected".
Fix that one error while it's selected and the tab reads as clean —
while Run would still fail on the other two. The count changes meaning
depending on an invisible condition (selection), and it disagrees with
the global badges right next to it. This is the same reason VS Code's
Problems panel always lists everything and makes "current file only" an
explicit toggle rather than an implicit one.
## Changes
- **What**: Selection now works as emphasis on top of an always-complete
list:
- The hero count and the group list always describe the whole workflow,
regardless of selection.
- Selecting a node with errors auto-expands the groups containing them
and collapses the rest; clearing the selection (or moving it to an
error-free node) restores the expansion. Manual collapse choices are
left alone when a selection never matched anything.
- Matching entries get a background highlight using the design-system
selection blue (`--color-blue-selection`), so the panel emphasis
visually matches the canvas selection color. The highlight fades in/out
and bleeds slightly past the text without shifting any layout. This
works across all error kinds: execution errors, missing models, missing
media, missing node packs, and swap suggestions (each row/pack that
references the selected node is highlighted).
- A **resident context strip** sits between the hero and the list. With
no selection it reads `{n} nodes — {count} errors` as a workflow
summary; while a selection has errors it switches to `{node title} —
{count} errors` (or `{n} nodes selected — …` for multi-select). Because
the strip always occupies its slot, selecting/deselecting never reflows
the list.
- The strip is deliberately **always visible** rather than mounted on
demand: we plan to rename the tab to "Issues" and downgrade the
missing-* categories from errors to warnings, at which point this same
line becomes the mixed status readout (`X nodes — X errors / X
warnings`). Landing it as a resident status line now means that change
is a label swap, not a layout change.
- **Refactor**: the tab body (search, hero, grouped cards,
locate/install/replace handlers) is extracted from `TabErrors.vue` into
a reusable `ErrorGroupList.vue` — a follow-up PR mounts it outside the
sidebar. The old selection-filter machinery is kept internally as
`selectionScopedGroups` and now only derives the emphasis state (matched
group keys / card ids / asset node ids, selection error count). The
orphaned `compact` prop on `ErrorNodeCard` is removed along with it.
- **Fixes along the way**: node titles containing `=`/`&` no longer
render as HTML entities in the strip (title goes through `i18n-t` slots
instead of an escaped `t()` param), untitled nodes fall back to
"Untitled" instead of producing "1 nodes selected", and emphasized rows
expose their state to assistive tech via `aria-current` while the strip
announces via `role="status"`.
## Review Focus
- `useErrorGroups.ts`: the selection-emphasis derivation
(`selectionScopedGroups` and the `selectionMatched*` computeds).
Selection matching resolves execution ids through the graph (and by
container prefix for subgraph selections) rather than comparing raw ids
— the new unit tests pin both paths.
- `ErrorGroupList.vue`: the emphasis watcher (immediate,
membership-signature based) that syncs collapse state, and the strip
mode switch. The component tests cover the expand/collapse/restore
cycle, emphasis for selections that predate mount, and the strip label
states.
- The two e2e tests that previously asserted the filtering behavior now
assert the new one (counts stay global, strip shows the selection-scoped
count, deselect returns to the summary).
Known follow-ups (intentionally out of scope): distinct-node counting
can over/under-count in subgraph + mixed-error edge cases (execution ids
vs serialized ids), snapshot/restore of user collapse state across
emphasis, and narrowing the list-container `aria-live` region.
## Screenshots (if applicable)
Before
https://github.com/user-attachments/assets/ccf98954-83ed-4333-ba4e-31cedb9fc38b
### After
https://github.com/user-attachments/assets/f7317e4b-71c2-4009-94cc-25287979c0e4
https://github.com/user-attachments/assets/9008c100-0ec0-4612-8fe4-942fec1be2fe
https://github.com/user-attachments/assets/45734552-9986-41f8-93ad-5d072e355d3d
|
||
|
|
7b1cc3498d |
feat(website): add /enterprise-msa page for the Enterprise Customer Agreement (#13483)
*PR Created by the Glary-Bot Agent* --- Publishes the Comfy Enterprise Customer Agreement (MSA) as a browsable web page at [`/enterprise-msa`](https://comfy.org/enterprise-msa) so prospects can review the template before signing an Order Form. Requested in the `#website-and-docs` thread to make it easier for companies to view ahead of time and easier to share. Uses the same `LegalContentSection` template that already serves [`/affiliates/terms`](https://comfy.org/affiliates/terms), so the visual language matches the existing legal pages and layout choices (sticky TOC, active-section tracking, mobile collapsible TOC) are picked up for free. ## Changes - **New page** `apps/website/src/pages/enterprise-msa.astro` — mirrors the `/affiliates/terms` pattern, English-only, with a preamble paragraph identifying the parties above the TOC. - **`enterprise-msa` i18n block** in `translations.ts` — drives 12 numbered sections (Definitions → Miscellaneous) plus `Exhibit A. Order Form`, verbatim from the executed template dated May 22, 2026. - **Route + locale invariance** — `enterpriseMsa: '/enterprise-msa'` added to `baseRoutes` and to `LOCALE_INVARIANT_ROUTE_KEYS`, so localized variants are not served without a legal review of the translation. Consistent with the existing `termsOfService` / `affiliateTerms` convention documented in `config/routes.ts`. - **Footer discovery** — `Enterprise MSA` link added to the `Company` column of `SiteFooter.vue`, between `Terms of Service` and `Privacy Policy`. This makes the MSA reachable from the `/cloud/enterprise` page (and site-wide) with no changes to the enterprise page itself. - **Test coverage** — new `enterpriseMsaSections.test.ts` guards section IDs, numeric title pattern, page-chrome keys, and the locale-invariant route so a future refactor of `LOCALE_INVARIANT_ROUTE_KEYS` cannot silently start serving an unreviewed translation. ## Verification - `pnpm test:unit` — 162/162 pass (17 files, 7 new tests) - `pnpm typecheck` — 0 errors, 0 warnings on changed files (pre-existing hints untouched) - `pnpm build` — 498 pages built, `/enterprise-msa/index.html` (80 KB) contains all 12 sections + Exhibit A - `oxlint` + `oxfmt` clean on all changed files - Manual QA via `pnpm preview` at 1440×900 desktop and 390×844 mobile (responsive layout inherits from `LegalContentSection`, verified visually) — desktop screenshots attached ## Notes for review - The MSA copy is a verbatim reproduction of the executed `.docx` template shared in Slack. If Legal wants edits, they can happen inline in the `enterprise-msa.*` i18n block — no template restructuring needed. - The effective date is hard-coded to `May 22, 2026` (matches the template file name `GP 5.22.26`); update `enterprise-msa.effective-date` when Legal ships a new template. - The page intentionally does NOT set `noindex` — the MSA is a customer-facing document that should be discoverable via search, matching the user's stated goal of "easier for companies to view ahead of time." cc @michael-poganski — requested by James in the Slack thread for approval to ship. ## Screenshots     --------- Co-authored-by: Glary-Bot <glary-bot@users.noreply.github.com> Co-authored-by: Michael B <michael@imick.io> |
||
|
|
b30cedffec |
fix: point Comfy API "View Docs" link to cloud overview (#13439)
*PR Created by the Glary-Bot Agent* --- Update the `docsApi` external link so the "View Docs" CTAs on the Comfy API product page point to the cloud overview's Quick Start section instead of the old API reference URL. - `apps/website/src/config/routes.ts`: `docsApi` → `https://docs.comfy.org/development/cloud/overview#quick-start` Both "View Docs" buttons on `/api` (in `HeroSection.vue` and `StepsSection.vue`) consume this single source of truth, so no component changes were needed. ## Verification - Confirmed destination page and `#quick-start` anchor exist (`docs/development/cloud/overview.mdx` has `## Quick Start`). - Ran the Astro dev server and loaded `/api` locally — both `VIEW DOCS` anchors resolve to the new URL. - `pnpm typecheck` / lint-staged (oxfmt, oxlint, eslint, typecheck) all pass via pre-commit. - `/review` sub-agent reviewed the diff: 0 findings, ready to merge. ## Screenshots Both hero and steps sections now link to the new URL. ## Screenshots   Co-authored-by: Glary-Bot <glary-bot@users.noreply.github.com> Co-authored-by: imick-io <153135517+imick-io@users.noreply.github.com> Co-authored-by: Michael B <michael@imick.io> |
||
|
|
010389903d |
feat(website): sitewide announcement banner (#13481)
## Summary Adds a **sitewide announcement banner** to the website, rendered above the navbar on every page. It has a two-layer visibility model: - **Build-time gate** — a pure, unit-tested `evaluateBannerVisibility()` decides whether the banner mounts at all (active flag + optional date window + locale/section targeting), driven by a typed config (`src/config/banner.ts`). No CMS; copy resolves through i18n. - **Client-side dismissal** — persisted in `localStorage`, keyed by a **content hash** so editing the copy re-shows the banner (per-locale, so an en edit doesn't re-show it for zh-CN). Current content points the CTA at **Comfy MCP** (`/mcp`). ## Highlights - **Full-width branded bar** above a now-`sticky` navbar; reuses the design system (`Button`, gradient tokens, new reusable `IconButton`). - **Flash-free** on load: an inline pre-hydration script hides an already-dismissed banner before paint (no pop-in, no layout shift); `close()` sets the same signal after the leave animation to stay flash-free across ClientRouter navigations. - **Open/close transition**: grid-rows height collapse + fade, respecting `prefers-reduced-motion`. - **i18n**: copy in `en` + `zh-CN`. ## Where to edit later - **Copy**: `apps/website/src/i18n/translations.ts` → `launches.banner.text` / `launches.banner.cta` - **Link / on-off / dates / targeting**: `apps/website/src/config/banner.ts` (`bannerConfig`) ## Notes - On a static site the `startsAt`/`endsAt` window is evaluated at **build time** (documented in `banner.ts`). - Changing the copy or link changes the content hash, so previously-dismissed visitors will see the banner again — by design. ## Test plan - `pnpm test:unit` — evaluator + version-hash unit tests pass. - `pnpm typecheck` + lint clean. - On the preview: banner shows on `/`, `/launches`, and a `zh-CN` page; CTA -> `/mcp`; dismiss animates and stays dismissed on reload (no flash); reduced-motion disables the animation. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |