Skip to content

Commit e616959

Browse files
fix: hide template selector after shared workflow accept (#9913)
Hide the template selector when a first-time cloud user accepts a shared workflow from a share link, so the shared workflow opens without the onboarding template dialog lingering. - **What**: Added shared-workflow loader behavior to close the global template selector on accept actions (`copy-and-open` and `open-only`) while keeping cancel behavior unchanged. - **What**: Added targeted unit tests covering hide-on-accept and no-hide-on-cancel behavior in the shared workflow URL loader. Confirm that share-link accept paths now dismiss the template selector and that cancel still leaves it available. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9913-fix-hide-template-selector-after-shared-workflow-accept-3236d73d365081099c04e350d499fad2) by [Unito](https://www.unito.io) Co-authored-by: Amp <amp@ampcode.com> fix: restore native copy/paste events for image paste support (#9914) - Remove Ctrl+C and Ctrl+V keybindings from the keybinding service defaults so native browser copy/paste events fire - This restores image paste into LoadImage nodes, which broke after PR #9459 moved Ctrl+C/V into the keybinding service, which calls `event.preventDefault()` on keydown. This prevents the browser `paste` event from firing, so `usePaste` (which detects images in the clipboard) never runs. The `PasteFromClipboard` command only reads from localStorage, completely bypassing image detection. **Repro:** Copy a node → copy an image externally → try to paste the image into a LoadImage node → gets old node data from localStorage instead. Remove Ctrl+C and Ctrl+V from `CORE_KEYBINDINGS` in `defaults.ts`. The native browser events now fire as before, and `useCopy`/`usePaste` handle them correctly. Ctrl+Shift+V, Ctrl+A, Delete, and Backspace keybindings remain in the keybinding service. Fixes #9459 (regression) ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9914-fix-restore-native-copy-paste-events-for-image-paste-support-3236d73d365081c7ac53f983f316e10f) by [Unito](https://www.unito.io) fix: clear stale widget slotMetadata on link disconnect (#9885) Fixes text field becoming non-editable when a previously linked input is removed from a custom node. When a widget's input was promoted to a slot, connected via a link, and then the input was removed (e.g., by updating the custom node definition), the widget retained stale `slotMetadata` with `linked: true`. This prevented the widget from being editable. In `refreshNodeSlots`, removed the `if (slotInfo)` guard so `widget.slotMetadata` is always assigned — either to valid metadata or `undefined`. This ensures stale linked state is cleared when inputs no longer match widgets. 1. Text field remains editable after promote→connect→disconnect cycle 2. Text field returns to editable state when noodle disconnected 3. No mode switching needed to restore editability - Added regression test: "clears stale slotMetadata when input no longer matches widget" - All existing tests pass (18/18 in affected file) --- **Note: This PR currently contains only the RED (failing test) commit for TDD verification. The GREEN (fix) commit will be pushed after CI confirms the test failure.** ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9885-fix-clear-stale-widget-slotMetadata-on-link-disconnect-3226d73d365081269319c027b42d9f6b) by [Unito](https://www.unito.io) fix: stabilize subgraph promoted widget identity and rendering (#9896) Fix subgraph promoted widget identity/rendering so on-node widgets stay correct through configure/hydration churn, duplicate names, and linked+independent coexistence. - **Subgraph promotion reconciliation**: stabilize linked-entry identity by subgraph slot id, preserve deterministic linked representative selection, and prune stale alias/fallback entries without dropping legitimate independent promotions. - **Promoted view resolution**: bind slot mapping by promoted view object identity (`getSlotFromWidget` / `getWidgetFromSlot`) to avoid same-name collisions. - **On-node widget rendering**: harden `NodeWidgets` identity and dedup to avoid visual aliasing, prefer visible duplicates over hidden stale entries, include type/source execution identity, and avoid collapsing transient unresolved entries. - **Mapping correctness**: update `useGraphNodeManager` promoted source mapping to resolve by input target only when the promoted view is actually bound to that input. - **Subgraph input uniqueness**: ensure empty-slot promotion creates unique input names (`seed`, `seed_1`, etc.) for same-name multi-source promotions. - **Safety fix**: guard against undefined canvas in slot-link interaction. - **Tests/fixtures**: add focused regressions for fixture path `subgraph_complex_promotion_1`, linked+independent same-name cases, duplicate-name identity mapping, dedup behavior, and input-name uniqueness. Validate behavior around transient configure/hydration states (`-1` id to concrete id), duplicate-name promotions, linked representative recovery, and that dedup never hides legitimate widgets while still removing true duplicates. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9896-fix-stabilize-subgraph-promoted-widget-identity-and-rendering-3226d73d365081c8a1e8d0a5a22e826d) by [Unito](https://www.unito.io) --------- Co-authored-by: Amp <amp@ampcode.com> 1.42.5 (#9906) Patch version increment to 1.42.5 **Base branch:** `main` --------- Co-authored-by: christian-byrne <72887196+christian-byrne@users.noreply.github.com> Co-authored-by: github-actions <github-actions@github.com> fix: skip redundant appScalePercentage updates during zoom/pan (#9403) Add equality check before updating `appScalePercentage` reactive ref. Firefox profiler shows 586 `setElementText` markers from continuous text interpolation updates during zoom/pan. The rounded percentage value often doesn't change between events. Extract `updateAppScalePercentage()` helper with equality guard — compares new rounded value to current before assigning to the ref. Expected: eliminates ~90% of `setElementText` markers during zoom/pan ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9403-fix-skip-redundant-appScalePercentage-updates-during-zoom-pan-31a6d73d3650812db8f2d68ac73c95b0) by [Unito](https://www.unito.io) test: add browser test for textarea right-click context menu in subgraph (#9891) Add E2E test coverage for the textarea widget right-click context menu inside subgraphs. The fix was shipped in #9840 — this PR adds the missing browser test. - Loads a subgraph workflow with a CLIPTextEncode (textarea) node - Navigates into the subgraph - Right-clicks the textarea DOM element - Asserts that the ComfyUI "Promote Widget" context menu option appears - Fixes the test gap from #9840 - Notion ticket: d7a53160-e1e1-42bb-a5ac-c0c2702c629c ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9891-test-add-browser-test-for-textarea-right-click-context-menu-in-subgraph-3226d73d365081a4be51f89b5d505361) by [Unito](https://www.unito.io) feat: expand CDP perf metrics — add DOM nodes, script duration, event listeners (#9887) Expands the performance testing infrastructure to collect 4 additional CDP metrics that are already returned by `Performance.getMetrics` but were not being read. This is a zero-cost expansion — no additional CDP calls, just reading more fields from the existing response. | Metric | CDP Source | What It Detects | |---|---|---| | `domNodes` | `Nodes` | DOM node count delta — widget DOM leaks during node create/destroy | | `jsHeapTotalBytes` | `JSHeapTotalSize` | Total heap delta — combined with `heapDeltaBytes` shows GC pressure | | `scriptDurationMs` | `ScriptDuration` | JS execution time vs total task time — script vs rendering balance | | `eventListeners` | `JSEventListeners` | Listener count delta — detects listener accumulation across lifecycle | - Added 4 fields to `PerfSnapshot` interface - Added 4 fields to `PerfMeasurement` interface - Wired through `getSnapshot()` and `stopMeasuring()` - Added 4 fields to `PerfMeasurement` interface - Expanded `MetricKey` type and `REPORTED_METRICS` array with 3 new reported metrics (`domNodes`, `scriptDurationMs`, `eventListeners`) - `jsHeapTotalBytes` is collected but not in `REPORTED_METRICS` — it's used alongside `heapDeltaBytes` for GC pressure ratio analysis From a gap analysis of all ~30 CDP metrics, these were identified as highest priority for ComfyUI: - **`Nodes`** (P0): ComfyUI dynamically creates/destroys widget DOM. DOM bloat from leaked widgets is a key performance risk, especially for Vue Nodes 2.0. - **`ScriptDuration`** (P1): Separates JS execution from layout/paint. Reveals whether perf issues are script-heavy or rendering-heavy. - **`JSEventListeners`** (P1): Widget lifecycle can leak listeners across node add/remove cycles. - **`JSHeapTotalSize`** (P1): With `JSHeapUsedSize`, the ratio shows GC fragmentation pressure. The `PerfMeasurement` interface is extended (not changed). Old baseline `perf-metrics.json` files without these fields will have `undefined` values, which the report script handles gracefully (shows `—` for missing data). ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9887-feat-expand-CDP-perf-metrics-add-DOM-nodes-script-duration-event-listeners-3226d73d3650818abea1d4a441667c38) by [Unito](https://www.unito.io) --------- Co-authored-by: GitHub Action <action@github.com> Co-authored-by: Alexander Brown <drjkl@comfy.org> fix: prevent white flash when opening mask editor (#9860) - Remove hardcoded `bg-white` from mask editor canvas background div to prevent white flash on dialog open - Add a loading spinner while the mask editor initializes (image loading, canvas setup, GPU resources) - Background color is set dynamically by `setCanvasBackground()` after initialization Fixes #9852 https://github.com/user-attachments/assets/7da61e32-671b-4056-b5ec-8cb246fc7689 https://github.com/user-attachments/assets/bfdedc69-f690-42c5-8591-619623c04f55 ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9860-fix-prevent-white-flash-when-opening-mask-editor-3226d73d365081de9b7ad4622438e6ed) by [Unito](https://www.unito.io) --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> fix: prevent live preview dimension flicker between frames (#9937) Fix "Calculating dimensions" text flickering during live sampling preview in Vue renderer. - **What**: Stop resetting `actualDimensions` to `null` on every `imageUrl` change. Previous dimensions are retained while the new frame loads, eliminating the flicker. Error state is still reset correctly. The watcher on `props.imageUrl` previously reset both `actualDimensions` and `imageError`. Now it only resets `imageError`, since `handleImageLoad` updates dimensions when the new frame actually loads. This means stale dimensions show briefly between frames, which is intentionally better than showing "Calculating dimensions" text. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9937-fix-prevent-live-preview-dimension-flicker-between-frames-3246d73d36508154a676e5996112354f) by [Unito](https://www.unito.io) feat: make Vue nodes (Nodes 2.0) default for new desktop installs (#9947) Makes Vue nodes (Nodes 2.0) the default renderer for new desktop app installs (version ≥1.41.0), matching the behavior already live for cloud new installs. Step 2 of the Nodes 2.0 rollout sequence: 1. ✅ Cloud new installs (≥1.41.0) — DONE 2. 👉 **Desktop app (new installs)** — this PR 3. ⬜ Local installs 4. ⬜ Remove Beta tag 5. ⬜ GTM announcement No forced migration — only changes the default for new installs. Existing users keep their setting. Rollback is a settings flip. In `coreSettings.ts`, the `defaultsByInstallVersion` for `Comfy.VueNodes.Enabled` changes from: ```typescript defaultsByInstallVersion: { '1.41.0': isCloud }, ``` to: ```typescript defaultsByInstallVersion: { '1.41.0': isCloud || isDesktop }, ``` - M2 perf target (≥52 FPS on 245-node workflow) — layer merge landed, likely met - M-DevRel migration docs (blocks Beta tag removal, not this flip) Draft PR — ceremonial, to be merged when M2 checkpoint passes. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9947-feat-make-Vue-nodes-Nodes-2-0-default-for-new-desktop-installs-3246d73d365081b280dfff932c7aa016) by [Unito](https://www.unito.io) fix: fix perf CI pipeline — z-score baselines, force-push staleness, baseline storage (#9886) Fixes three critical issues with the CI performance reporting pipeline that made perf reports useless on PRs (demonstrated by PR #9248 — deep watcher removal merged without useful perf signal). **Root cause:** PR #9305 added z-score statistical analysis code to `perf-report.ts`, but the historical data download step was placed in the wrong workflow file. The report is generated in `pr-perf-report.yaml` (a `workflow_run`-triggered job), but the historical download was in `ci-perf-report.yaml` (the test runner) — different runners, different filesystems. **Fix:** Implement `perf-data` orphan branch storage: - On push to main: save `perf-metrics.json` to `perf-data` branch with timestamped filename - On PR report: fetch last 5 baselines from `perf-data` branch into `temp/perf-history/` - Rolling window of 20 baselines, oldest pruned automatically - Same pattern used by `github-action-benchmark` (33.7k repos) **Root cause:** `cancel-in-progress: true` kills the perf test run before it uploads artifacts. The downstream report workflow only triggers on `conclusion == 'success'` — cancelled runs are ignored, so the comment from the first successful run goes stale. **Fix:** - Change `cancel-in-progress: false` — with GitHub's queue depth of 1, rapid pushes (A,B,C,D) run A and D, skipping B and C - Add SHA validation in `pr-perf-report.yaml` — before posting, check if the workflow_run's head SHA still matches the PR's current head. Skip posting stale results. - `contents: write` on CI job (needed for pushing to perf-data branch) - `actions: read` on both workflows (needed for artifact/baseline access) After merging, create the `perf-data` orphan branch: ```bash git checkout --orphan perf-data git rm -rf . echo '# Performance Baselines' > README.md mkdir -p baselines git add README.md baselines git commit -m 'Initialize perf-data branch' git push origin perf-data ``` The first 2 pushes to main after setup will build up variance data, and z-scores will start appearing in PR reports (threshold is `historical.length >= 2`). - YAML validated with `yaml.safe_load()` - `perf-report.ts` `loadHistoricalReports()` already reads from `temp/perf-history/<index>/perf-metrics.json` — no code changes needed - All new steps use `continue-on-error: true` for graceful degradation ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9886-fix-fix-perf-CI-pipeline-z-score-baselines-force-push-staleness-baseline-storage-3226d73d365081538424c7945e71f308) by [Unito](https://www.unito.io) draft: add red-green-fix skill for verified bug fix workflow (#9954) - Add a Claude Code skill (`/red-green-fix`) that enforces the red-green commit pattern for bug fixes - Ensures a failing test is committed first (red CI), then the fix is committed separately (green CI) - Gives reviewers proof that the test actually catches the bug - Includes `reference/testing-anti-patterns.md` with common mistakes contextualized to this codebase ``` .claude/skills/red-green-fix/ ├── SKILL.md # Main skill definition └── reference/ └── testing-anti-patterns.md # Anti-patterns guide ``` - [ ] Invoke `/red-green-fix <bug description>` in Claude Code and verify the two-step workflow - [ ] Confirm PR template includes red-green verification table - [ ] Review anti-patterns reference for completeness ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9954-draft-add-red-green-fix-skill-for-verified-bug-fix-workflow-3246d73d365081339a83dc09263b0f33) by [Unito](https://www.unito.io) --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: GitHub Action <action@github.com> test: add large-graph perf test with 245-node workflow (backlog N5) (#9940) Adds a 245-node workflow asset and two `@perf` tests to establish a baseline for large-graph performance regressions (Tier 6 in the performance backlog). Backlog item N5: we need CI regression detection for compositor layer management, GPU texture count, and transform pane cost at 245+ nodes. This is PR1 of 2 — establishes baseline metrics on main. Future optimization PRs will show improvement deltas against this baseline. - **`large graph idle rendering`** — 120 frames idle with 245 nodes, measures style recalcs, layouts, task duration, heap delta - **`large graph pan interaction`** — middle-click pan across 245 nodes, stresses compositor layer management and transform recalculation `browser_tests/assets/large-graph-workflow.json` — 245 nodes (49 pipelines of CheckpointLoader → 2× CLIPTextEncode → KSampler + EmptyLatentImage), 294 links. Minimal structure focused on node count. - [x] `pnpm typecheck:browser` passes - [x] `pnpm lint` passes (eslint on changed file) - [x] All link references in JSON validated programmatically ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9940-test-add-large-graph-perf-test-with-245-node-workflow-backlog-N5-3246d73d365081f6b5d8ddb9a85e6ad0) by [Unito](https://www.unito.io) --------- Co-authored-by: GitHub Action <action@github.com> feat: add Ingest API codegen with Zod schema generation (#9932) - Add `packages/ingest-types/` package that auto-generates TypeScript types and Zod schemas from the Ingest service OpenAPI spec - Uses `@hey-api/openapi-ts` with built-in Zod plugin (Zod v3 compatible) - Filters out overlapping endpoints shared with the local ComfyUI Python backend - Generates **493 TypeScript types** and **256 Zod schemas** covering cloud-only endpoints - Configure knip to ignore generated files The cloud repo pushes generated types to this repo (push model, no private repo cloning). See: Comfy-Org/cloud#2858 Codegen targets are controlled by the **exclude list** in `packages/ingest-types/openapi-ts.config.ts`. Everything in the Ingest `openapi.yaml` is included **except** overlapping endpoints that also exist in the local ComfyUI Python backend: **Excluded (overlapping with ComfyUI Python):** `/prompt`, `/queue`, `/history`, `/object_info`, `/features`, `/settings`, `/system_stats`, `/interrupt`, `/upload/*`, `/view`, `/jobs`, `/userdata`, `/webhooks/*`, `/internal/*` **Included (cloud-only, codegen targets):** `/workspaces/*`, `/billing/*`, `/secrets/*`, `/assets/*`, `/tasks/*`, `/auth/*`, `/workflows/*`, `/workspace/*`, `/user`, `/settings/{key}`, `/tags`, `/feedback`, `/invite_code/*`, `/experiment/models/*`, `/global_subgraphs/*` This PR only sets up the codegen infrastructure. A follow-up PR should replace manually maintained types with imports from `@comfyorg/ingest-types`: | File | Lines | Current | Replace with | |------|-------|---------|-------------| | `src/platform/workspace/api/workspaceApi.ts` | ~270 | TS interfaces | `import type { ... } from '@comfyorg/ingest-types'` | | `src/platform/secrets/types.ts` | ~32 | TS interfaces | `import type { ... } from '@comfyorg/ingest-types'` | | `src/platform/assets/schemas/assetSchema.ts` | ~125 | Zod schemas | `import { ... } from '@comfyorg/ingest-types/zod'` | | `src/platform/assets/schemas/mediaAssetSchema.ts` | ~50 | Zod schemas | `import { ... } from '@comfyorg/ingest-types/zod'` | | `src/platform/tasks/services/taskService.ts` | ~70 | Zod schemas | `import { ... } from '@comfyorg/ingest-types/zod'` | | `src/platform/workspace/workspaceTypes.ts` | ~6 | TS interface | `export type { ... } from '@comfyorg/ingest-types'` | - [x] `pnpm generate` in `packages/ingest-types/` produces `types.gen.ts` and `zod.gen.ts` - [x] `pnpm typecheck` passes - [x] Pre-commit hooks pass (lint, typecheck, format) - [x] Generated Zod schemas validate correct data and reject invalid data - [x] No import conflicts with existing code (generated types are isolated in separate package) --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: GitHub Action <action@github.com> feat: surface missing models in Error Tab for OSS and remove legacy dialog (#9921) - Surface missing models in the Error Tab for OSS environments, replacing the legacy modal dialog - Add Download button per model and Download All button in group header with file size display - Move download business logic from `components/dialog/content` to `platform/missingModel` - Remove legacy missing models dialog components and composable - **Pipeline**: Remove `isCloud` guard from `scanAllModelCandidates` and `surfaceMissingModels` so OSS detects missing models - **Grouping**: Group non-asset-supported models by directory in OSS instead of lumping into UNSUPPORTED - **UI**: Add Download button (matching Install Node Pack design) and Download All header button - **Store**: Add `folderPaths`/`fileSizes` state with setter methods, race condition guard - **Cleanup**: Delete `MissingModelsContent`, `MissingModelsHeader`, `MissingModelsFooter`, `useMissingModelsDialog`, `missingModelsUtils` - **Tests**: Add OSS/Cloud grouping tests, migrate Playwright E2E to Error Tab, improve test isolation - **Snapshots**: Reset Playwright screenshot expectations since OSS missing model error detection now causes red highlights on affected nodes - **Accessibility**: Add `aria-label` with model name, `aria-expanded` on toggle, warning icon for unknown category - [x] Unit tests pass (86 tests) - [x] TypeScript typecheck passes - [x] knip passes - [x] Load workflow with missing models in OSS → Error Tab shows missing models grouped by directory - [x] Download button triggers browser download with correct URL - [x] Download All button downloads all downloadable models - [x] Cloud environment behavior unchanged - [x] Playwright E2E: `pnpm test:browser:local -- --grep "Missing models in Error Tab"` https://github.com/user-attachments/assets/12f15e09-215a-4c58-87ed-39bbffd1359c ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9921-feat-surface-missing-models-in-Error-Tab-for-OSS-and-remove-legacy-dialog-3236d73d365081f0a9dfc291978f5ecf) by [Unito](https://www.unito.io) --------- Co-authored-by: GitHub Action <action@github.com> Co-authored-by: github-actions <github-actions@github.com> fix: cloud subscribe redirect hangs waiting for billing init (#9965) Fix /cloud/subscribe route hanging indefinitely because billing context never initializes during the onboarding flow. - **What**: Replace passive `await until(isInitialized).toBe(true)` with explicit `await initialize()` in CloudSubscriptionRedirectView. Remove unused `until` import. ![Kapture 2026-03-15 at 23 16 22](https://github.com/user-attachments/assets/0a12487b-b39a-4f96-9a4c-96a01facfdd8) In the onboarding flow, `useTeamWorkspaceStore().activeWorkspace` is not set, so `useBillingContext`'s internal watch (which triggers `initialize()` on workspace change) enters the `!newWorkspaceId` branch — it resets `isInitialized` to `false` and returns without ever calling `initialize()`. The old code then awaited `isInitialized` becoming `true` forever. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9965-fix-cloud-subscribe-redirect-hangs-waiting-for-billing-init-3246d73d3650812d93ebd477c544fa0a) by [Unito](https://www.unito.io) Co-authored-by: Amp <amp@ampcode.com> feat: add TBT/frameDuration metrics and new perf test scenarios (#9910) Adds Total Blocking Time (TBT) and frame duration metrics to the performance testing infrastructure, plus three new test scenarios covering zoom, pan, and many-nodes-idle. - **`totalBlockingTimeMs`** — Computed from PerformanceObserver `longtask` entries: `sum(duration - 50ms)` for tasks >50ms. Measures main thread blocking. - **`frameDurationMs`** — Average frame duration via rAF timing (16.67ms = 60fps target). Measures rendering smoothness. | Scenario | Description | |---|---| | `canvas-zoom-sweep` | 10 zoom-in + 10 zoom-out cycles on default workflow | | `canvas-pan-many-nodes` | 10 pan sweeps over 100-node workflow | | `canvas-many-nodes-idle` | 2-second idle measurement with 100 nodes rendered | - `PerformanceHelper.ts`: Installs PerformanceObserver for longtask, collects TBT, measures frame duration via rAF - `perf-report.ts`: Reports TBT and frame duration in PR comment tables - `browser_tests/assets/perf/many_nodes_100.json`: 100-node (10×10 grid) test fixture - TBT collection clears entries at `startMeasuring()` and reads at `stopMeasuring()` — ensure no race with observer buffering - Frame duration sampling uses 10 frames — enough for signal without slowing tests Depends on: #9886, #9887 ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9910-feat-add-TBT-frameDuration-metrics-and-new-perf-test-scenarios-3236d73d365081488ae3c594a8bf7cff) by [Unito](https://www.unito.io) fix: LGraphGroup paste position (#9962) Fix group paste position: groups now paste at the cursor location instead of on top of the original. - **What**: Added LGraphGroup offset handling in _deserializeItems() position adjustment loop, matching existing LGraphNode and Reroute behavior. Before: https://github.com/user-attachments/assets/e317af10-8009-4092-9d14-de79316cd853 After: https://github.com/user-attachments/assets/f4ffefd5-519a-4592-812c-c88e3b5940fd ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9962-fix-LGraphGroup-paste-position-3246d73d365081eea5b2e2507da861de) by [Unito](https://www.unito.io) fix: tree explorer nodes not filling parent container width (#9964) Fix tree explorer nodes not filling the full width of the sidebar container, causing text to overflow instead of truncating. - **What**: Add `min-w-0` to `TreeRoot` to allow flex shrinking within sidebar. Add `w-full` and `min-w-0` to tree node rows so absolutely-positioned virtualizer items fill the container width and text truncates correctly. <img width="365" height="749" alt="image" src="https://github.com/user-attachments/assets/320910f3-52ad-4634-a935-6bd1a40aea7f" /> The virtualizer renders each item with `position: absolute; left: 0` but no explicit width, so rows would size to content rather than filling the container. Adding `w-full` ensures rows stretch to 100% of the virtualizer container, and `min-w-0` allows proper flex shrinking for deep indentation levels. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9964-fix-tree-explorer-nodes-not-filling-parent-container-width-3246d73d36508138be38fdcac15ae4ef) by [Unito](https://www.unito.io) Co-authored-by: Amp <amp@ampcode.com> feat: add Copy URL button to missing model rows for OSS (#9966) 1.42.6 (#9986) Patch version increment to 1.42.6 **Base branch:** `main` ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9986-1-42-6-3256d73d365081a28bfad82022ce3440) by [Unito](https://www.unito.io) --------- Co-authored-by: christian-byrne <72887196+christian-byrne@users.noreply.github.com> Co-authored-by: github-actions <github-actions@github.com> fix: block missing e2e regression coverage in CodeRabbit (#9987) Make the CodeRabbit end-to-end regression coverage check actually block fix-like PRs until it is resolved or explicitly overridden by a requested reviewer, and harden the prompt so it evaluates only PR-local metadata. - **What**: Set the `End-to-end regression coverage for fixes` custom check mode from `warning` to `error` - **What**: Enable `reviews.request_changes_workflow` so CodeRabbit can block on failed `error` pre-merge checks - **What**: Set `reviews.pre_merge_checks.override_requested_reviewers_only` to `true` so only requested reviewers can bypass a failed check - **What**: Tighten the custom check instructions to use only PR metadata in review context, avoid shell commands, and avoid reverse-diff or base-branch file evaluation Confirm this is the intended CodeRabbit enforcement model for missing Playwright regression coverage on fix-like PRs and that the prompt wording is strict enough to avoid false positives from reversed diffs. fix: add reve and elevenlabs to icon safelist (#9990) Reve and ElevenLabs provider icons were not displaying in the node library because they were missing from the Tailwind icon safelist. - **What**: Add `reve` and `elevenlabs` to the `@source inline` safelist in `style.css` so `icon-[comfy--reve]` and `icon-[comfy--elevenlabs]` classes are generated. Add corresponding `PROVIDER_COLORS` entries in `categoryUtil.ts`. <img width="308" height="106" alt="image" src="https://github.com/user-attachments/assets/d488898a-fbad-4af0-8921-0e8ee7d4705a" /> <img width="308" height="78" alt="image" src="https://github.com/user-attachments/assets/2b3b7172-095b-415e-a49a-d303977e0abc" /> The SVG files already existed in `packages/design-system/src/icons/` but Tailwind's tree-shaking dropped the classes since they're only used dynamically via `getProviderIcon()`. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9990-fix-add-reve-and-elevenlabs-to-icon-safelist-3256d73d36508105994fcdd5d0568027) by [Unito](https://www.unito.io) --------- Co-authored-by: Amp <amp@ampcode.com> fix: mask editor save shows blank image in Load Image node (#9984) Mask editor save was showing a blank image in the Load Image node (legacy nodes mode, not Nodes 2.0) because `updateNodeWithServerReferences` called `updateNodeImages`, which silently no-ops when the node has no pre-existing execution outputs. Replaced with `setNodeOutputs` which properly creates output entries regardless of prior state. **Affects:** Legacy nodes mode only. Nodes 2.0 (Vue Nodes) renders images via Vue components and is not affected. - Fixes #9983 - Fixes #9782 - Fixes #9952 | Commit | SHA | CI Status | Run | Purpose | |--------|-----|-----------|-----|---------| | `test: add failing test for mask editor save showing blank image` | `0ab66e8` | 🔴 [Red](https://github.com/Comfy-Org/ComfyUI_frontend/actions/runs/23125427860) | CI: Tests Unit **failure** | Proves the test catches the bug | | `fix: mask editor save shows blank image in Load Image node` | `564cc9c` | 🟢 [Green](https://github.com/Comfy-Org/ComfyUI_frontend/actions/runs/23127289891) | CI: Tests Unit **success** | Proves the fix resolves the bug | https://github.com/user-attachments/assets/8d5c36ce-2c5e-4609-b246-dcf896c4a8e7 https://github.com/user-attachments/assets/c8ae4f0e-3da0-40f2-a543-d1d5a6bce795 - [x] CI red on test-only commit - [x] CI green on fix commit - [ ] E2E regression test not added: mask editor save requires canvas pixel manipulation + server upload round-trip which is covered by the existing unit test mocking the full `save()` flow. The Playwright test infrastructure does not currently support mask editor interactions (draw + save). - [x] Manual verification (legacy nodes mode): Load Image → upload → mask editor → draw → save → verify image refreshes --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> fix: allow URL input for free tier users, gate on import button (#10024) - Remove free-tier restriction from the URL input field in `MissingModelUrlInput.vue` so it is always editable - Move the subscription check (`canImportModels`) to the Import button click handler — free-tier users see the upgrade modal only when they attempt to import - Extract inline ternary to named `handleImportClick` method for clarity - [x] Unit tests added (`MissingModelUrlInput.test.ts`) verifying: - URL input is always editable regardless of subscription tier - Import button calls `handleImport` for paid users - Import button calls `showUploadDialog` (upgrade modal) for free-tier users - [x] Verify URL input is editable for free-tier users on cloud - [x] Verify clicking Import as free-tier opens the subscription modal - [x] Verify paid users can import normally without changes Playwright E2E regression tests are impractical for this change because `MissingModelUrlInput` only renders when `isAssetSupported` is true, which requires `isCloud` — a compile-time constant (`__DISTRIBUTION__`). The OSS test build always sets `isCloud = false`, so the component never renders in the E2E environment. Unit tests with mocked feature flags provide equivalent behavioral coverage. fix: prevent subscription UI from rendering on non-cloud distributions (#9958) Prevent Plans & Pricing dialog, subscription buttons, and cloud-only menu items from appearing on desktop/localhost distributions. - **What**: Add `isCloud` guards to `useSubscriptionDialog.showPricingTable`, `TopbarSubscribeButton`, and `CurrentUserPopoverLegacy` so subscription UI only renders on cloud - **Tests**: 24 tests across 3 test files (1 modified, 2 new) covering cloud/non-cloud behavior - Guard placement in `CurrentUserPopoverLegacy.vue` — multiple `v-if` conditions updated to include `isCloud` - Early-return in `showPricingTable` as a defense-in-depth measure Fixes COM-16820 ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9958-fix-prevent-subscription-UI-from-rendering-on-non-cloud-distributions-3246d73d365081559a9ee8650409c5b4) by [Unito](https://www.unito.io) Co-authored-by: Benjamin Lu <benjaminlu1107@gmail.com> fix: prevent animated preview duplication on Vue↔Litegraph switch (#9938) SaveAnimatedPNG/WEBP nodes show duplicate output previews when switching between Vue and Litegraph renderer modes. The `ANIM_PREVIEW_WIDGET` (`$$comfy_animation_preview`) DOM widget lacked `canvasOnly: true`, so `shouldRenderAsVue()` in the widget registry included it in Vue mode rendering. This caused both: 1. Vue's `ImagePreview.vue` (via `nodeMedia` computed from `nodeOutputStore`) 2. The legacy `ANIM_PREVIEW_WIDGET` DOM widget (rendered as `WidgetDOM`) to display simultaneously — duplicating the output preview. Add `canvasOnly: true` to the `ANIM_PREVIEW_WIDGET` options, matching the pattern used by `IMAGE_PREVIEW` widget in `useImagePreviewWidget.ts`. This ensures the legacy widget is filtered out in Vue mode by `shouldRenderAsVue()`, leaving `ImagePreview.vue` as the single source of truth. - All 539 vueNodes tests pass - All 22 nodeOutputStore tests pass - All 140 composables/node tests pass - Typecheck passes ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9938-fix-prevent-animated-preview-duplication-on-Vue-Litegraph-switch-3246d73d365081019bbfd7e33a9c14fb) by [Unito](https://www.unito.io) 1.43.0 (#10032) Minor version increment to 1.43.0 **Base branch:** `main` ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-10032-1-43-0-3256d73d3650818e8408d25fdf28de48) by [Unito](https://www.unito.io) Co-authored-by: christian-byrne <72887196+christian-byrne@users.noreply.github.com> Feat/3d thumbnail inline rendering (#9471) The previous approach generated thumbnails server-side and uploaded them as `model.glb.png` alongside the model file. This breaks on cloud deployments where output files are renamed to content hashes, severing the filename-based association between a model and its thumbnail. Replace the server-upload approach with client-side Three.js rendering ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9471-Feat-3d-thumbnail-inline-rendering-31b6d73d3650816fbd7dd05b507aa80d) by [Unito](https://www.unito.io) test: add FeatureFlagHelper and QueueHelper for E2E test infrastructure (#9554) Add 2 reusable test helpers for Playwright E2E tests, integrated into the ComfyPage fixture. These provide standardized patterns for mocking feature flags and queue state across all E2E tests. - **`FeatureFlagHelper.ts`** — manage localStorage `ff:` prefixed feature flags (`seedFlags` for init-time, `setFlags` for runtime) and mock `/api/features` route - **`QueueHelper.ts`** — mock `/api/queue` and `/api/history` routes with configurable running/pending counts and success/error job entries - **`ComfyPage.ts`** — integrate both helpers as `comfyPage.featureFlags` and `comfyPage.queue` - Helper API design: are `seedFlags`/`setFlags`/`mockServerFeatures` the right abstractions for feature flag testing? - Queue mock fidelity: does the mock history shape match real ComfyUI API responses closely enough? - These are test-only infrastructure — no production code changes. This is the base PR for the Playwright E2E coverage stack. Waves 1-4 all branch from this and can merge independently once this lands: - **→ This PR**: Test infrastructure helpers - #9555: Toasts, error overlay, selection toolbox, linear mode, selection rectangle - #9556: Node search, bottom panel, focus mode, job history, side panel - #9557: Errors tab, node headers, queue notifications, settings sidebar - #9558: Minimap, widget copy, floating menus, node library essentials --------- Co-authored-by: GitHub Action <action@github.com> feat: scaffold Astro 5 website app + design-system base.css - Create apps/website/ with Astro 5, Vue 3, Tailwind v4 integration - Static output, assetsPrefix /_website/, i18n (en + zh-CN) - Nx targets: dev, serve, build, preview, typecheck - Add base.css to design-system: brand tokens + Inter font-face only - Add catalog entries: astro, @astrojs/vue, @astrojs/check, nanostores, @nanostores/vue scaffold-01, scaffold-02 fix: add .gitignore and env.d.ts for Astro website app feat: add layout shell — SEO head, analytics, nav, footer - BaseLayout: OG/Twitter meta, canonical URL, GA4 GTM-NP9JM6K7, Vercel Analytics, ClientRouter for SPA navigation - SiteNav: Comfy logo, Enterprise/Gallery/About/Careers links, Comfy Cloud + Comfy Hub CTA buttons, mobile hamburger menu - SiteFooter: Product/Resources/Company/Legal columns, social icons (GitHub, Discord, X, Reddit, LinkedIn, Instagram) - Add @vercel/analytics to workspace catalog and website deps fix: address CodeRabbit review — ARIA wiring, absolute OG URLs, Analytics component - SiteNav: add aria-controls, aria-expanded, and id for mobile menu - BaseLayout: use absolute URLs for og:image and twitter:image - BaseLayout: replace inline inject() with official <Analytics /> component style: apply oxfmt formatting fix: remove unused deps from website package.json (knip) fix: clean up unused catalog entries from pnpm-workspace.yaml feat: add Wave 3 homepage sections (hero, social proof, pillars, testimonials, CTAs, manifesto, academy, placeholders)
1 parent afb2a47 commit e616959

File tree

13 files changed

+780
-0
lines changed

13 files changed

+780
-0
lines changed
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<script setup lang="ts">
2+
const features = [
3+
{ icon: '📚', label: 'Guided Tutorials' },
4+
{ icon: '🎥', label: 'Video Courses' },
5+
{ icon: '🛠️', label: 'Hands-on Projects' }
6+
]
7+
</script>
8+
9+
<template>
10+
<section class="bg-charcoal-800 py-24">
11+
<div class="mx-auto max-w-3xl px-6 text-center">
12+
<!-- Badge -->
13+
<span
14+
class="inline-block rounded-full bg-brand-yellow/10 px-4 py-1.5 text-xs uppercase tracking-widest text-brand-yellow"
15+
>
16+
COMFY ACADEMY
17+
</span>
18+
19+
<h2 class="mt-6 text-3xl font-bold text-white">Master AI Workflows</h2>
20+
21+
<p class="mt-4 text-smoke-700">
22+
Learn to build professional AI workflows with guided tutorials, video
23+
courses, and hands-on projects.
24+
</p>
25+
26+
<!-- Feature bullets -->
27+
<div class="mt-8 flex flex-wrap items-center justify-center gap-8">
28+
<div
29+
v-for="feature in features"
30+
:key="feature.label"
31+
class="flex items-center gap-2 text-sm text-white"
32+
>
33+
<span aria-hidden="true">{{ feature.icon }}</span>
34+
<span>{{ feature.label }}</span>
35+
</div>
36+
</div>
37+
38+
<!-- CTA -->
39+
<a
40+
href="/academy"
41+
class="mt-8 inline-block rounded-full bg-brand-yellow px-8 py-3 text-sm font-semibold text-black transition-opacity hover:opacity-90"
42+
>
43+
EXPLORE ACADEMY
44+
</a>
45+
</div>
46+
</section>
47+
</template>
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
<script setup lang="ts">
2+
const cards = [
3+
{
4+
icon: '🖥️',
5+
title: 'Comfy Desktop',
6+
description: 'Full power on your local machine. Free and open source.',
7+
cta: 'DOWNLOAD',
8+
href: '/download',
9+
outlined: false
10+
},
11+
{
12+
icon: '☁️',
13+
title: 'Comfy Cloud',
14+
description: 'Run workflows in the cloud. No GPU required.',
15+
cta: 'TRY CLOUD',
16+
href: 'https://app.comfy.org',
17+
outlined: false
18+
},
19+
{
20+
icon: '',
21+
title: 'Comfy API',
22+
description: 'Integrate AI generation into your applications.',
23+
cta: 'VIEW DOCS',
24+
href: 'https://docs.comfy.org',
25+
outlined: true
26+
}
27+
]
28+
</script>
29+
30+
<template>
31+
<section class="bg-charcoal-800 py-24">
32+
<div class="mx-auto max-w-5xl px-6">
33+
<h2 class="text-center text-3xl font-bold text-white">
34+
Choose Your Way to Comfy
35+
</h2>
36+
37+
<!-- CTA cards -->
38+
<div class="mt-12 grid grid-cols-1 gap-6 md:grid-cols-3">
39+
<a
40+
v-for="card in cards"
41+
:key="card.title"
42+
:href="card.href"
43+
class="flex flex-1 flex-col items-center rounded-xl border border-white/10 bg-charcoal-600 p-8 text-center transition-colors hover:border-brand-yellow"
44+
>
45+
<span class="text-4xl" aria-hidden="true">{{ card.icon }}</span>
46+
<h3 class="mt-4 text-xl font-semibold text-white">
47+
{{ card.title }}
48+
</h3>
49+
<p class="mt-2 text-sm text-smoke-700">
50+
{{ card.description }}
51+
</p>
52+
<span
53+
class="mt-6 inline-block rounded-full px-6 py-2 text-sm font-semibold transition-opacity hover:opacity-90"
54+
:class="
55+
card.outlined
56+
? 'border border-brand-yellow text-brand-yellow'
57+
: 'bg-brand-yellow text-black'
58+
"
59+
>
60+
{{ card.cta }}
61+
</span>
62+
</a>
63+
</div>
64+
</div>
65+
</section>
66+
</template>
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
<!-- PLACEHOLDER: Awaiting real quotes and case study content from Baira -->
2+
<script setup lang="ts">
3+
const studies = [
4+
{
5+
title: 'New Pipelines with Chord Mode',
6+
body: 'For AI-assisted texture and environment generation across studio pipelines.',
7+
size: 'tall' as const
8+
},
9+
{
10+
title: 'AI-Assisted Texture and Environment',
11+
body: 'For AI-assisted texture and environment generation across studio pipelines.',
12+
size: 'wide' as const
13+
},
14+
{
15+
title: 'Open-sourced the Chord Mode',
16+
body: 'For AI-assisted texture and environment generation across studio pipelines.',
17+
size: 'small' as const
18+
},
19+
{
20+
title: 'Environment Generation',
21+
body: 'For AI-assisted texture and environment generation across studio pipelines.',
22+
size: 'highlight' as const
23+
}
24+
]
25+
</script>
26+
27+
<template>
28+
<section class="bg-black px-6 py-24">
29+
<div class="mx-auto max-w-7xl">
30+
<header class="mb-12">
31+
<h2 class="text-3xl font-bold text-white">Customer Stories</h2>
32+
<p class="mt-2 text-smoke-700">
33+
See how leading studios use Comfy in production
34+
</p>
35+
</header>
36+
37+
<!-- Bento grid -->
38+
<div
39+
class="relative grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3"
40+
>
41+
<!-- Decorative node dots at grid intersections -->
42+
<div
43+
class="pointer-events-none absolute left-1/3 top-0 hidden h-1 w-1 rounded-full bg-brand-yellow lg:block"
44+
/>
45+
<div
46+
class="pointer-events-none absolute left-2/3 top-0 hidden h-1 w-1 rounded-full bg-brand-yellow lg:block"
47+
/>
48+
<div
49+
class="pointer-events-none absolute left-1/3 top-1/2 hidden h-1 w-1 rounded-full bg-brand-yellow lg:block"
50+
/>
51+
<div
52+
class="pointer-events-none absolute left-2/3 top-1/2 hidden h-1 w-1 rounded-full bg-brand-yellow lg:block"
53+
/>
54+
55+
<!-- Tall card (left, spans 2 rows) -->
56+
<article
57+
class="flex flex-col justify-end rounded-2xl border border-brand-yellow/30 bg-charcoal-600 p-6 md:row-span-2"
58+
>
59+
<h3 class="font-semibold text-white">{{ studies[0].title }}</h3>
60+
<p class="mt-2 text-sm text-smoke-700">{{ studies[0].body }}</p>
61+
<a href="#" class="mt-4 text-sm text-brand-yellow underline">
62+
READ CASE STUDY
63+
</a>
64+
</article>
65+
66+
<!-- Wide card (right top) -->
67+
<article
68+
class="flex min-h-[300px] flex-col justify-end rounded-2xl border border-brand-yellow/30 bg-charcoal-600 p-6 lg:col-span-2"
69+
>
70+
<h3 class="font-semibold text-white">{{ studies[1].title }}</h3>
71+
<p class="mt-2 text-sm text-smoke-700">{{ studies[1].body }}</p>
72+
<a href="#" class="mt-4 text-sm text-brand-yellow underline">
73+
READ CASE STUDY
74+
</a>
75+
</article>
76+
77+
<!-- Bottom-right: 2 small cards side by side -->
78+
<article
79+
class="flex min-h-[200px] flex-col justify-end rounded-2xl border border-brand-yellow/30 bg-charcoal-600 p-6"
80+
>
81+
<h3 class="font-semibold text-white">{{ studies[2].title }}</h3>
82+
<p class="mt-2 text-sm text-smoke-700">{{ studies[2].body }}</p>
83+
<a href="#" class="mt-4 text-sm text-brand-yellow underline">
84+
READ CASE STUDY
85+
</a>
86+
</article>
87+
88+
<!-- Highlight card (brand-yellow bg) -->
89+
<article
90+
class="flex min-h-[200px] flex-col justify-end rounded-2xl border border-brand-yellow/30 bg-brand-yellow p-6"
91+
>
92+
<h3 class="font-semibold text-black">{{ studies[3].title }}</h3>
93+
<p class="mt-2 text-sm text-black/70">{{ studies[3].body }}</p>
94+
<a href="#" class="mt-4 text-sm text-black underline">
95+
READ CASE STUDY
96+
</a>
97+
</article>
98+
</div>
99+
</div>
100+
</section>
101+
</template>
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
<script setup lang="ts">
2+
const steps = [
3+
{
4+
number: '1',
5+
title: 'Download & Sign Up',
6+
description: 'Get Comfy Desktop for free or create a Cloud account'
7+
},
8+
{
9+
number: '2',
10+
title: 'Load a Workflow',
11+
description:
12+
'Choose from thousands of community workflows or build your own'
13+
},
14+
{
15+
number: '3',
16+
title: 'Generate',
17+
description: 'Hit run and watch your AI workflow come to life'
18+
}
19+
]
20+
</script>
21+
22+
<template>
23+
<section class="border-t border-white/10 bg-black py-24">
24+
<div class="mx-auto max-w-7xl px-6 text-center">
25+
<h2 class="text-3xl font-bold text-white">Get Started in Minutes</h2>
26+
<p class="mt-4 text-smoke-700">
27+
From download to your first AI-generated output in three simple steps
28+
</p>
29+
30+
<!-- Steps -->
31+
<div class="mt-16 grid grid-cols-1 gap-8 md:grid-cols-3">
32+
<div v-for="(step, index) in steps" :key="step.number" class="relative">
33+
<!-- Connecting line between steps (desktop only) -->
34+
<div
35+
v-if="index < steps.length - 1"
36+
class="absolute right-0 top-8 hidden w-full translate-x-1/2 border-t border-brand-yellow/20 md:block"
37+
/>
38+
39+
<div class="relative">
40+
<span class="text-6xl font-bold text-brand-yellow/20">
41+
{{ step.number }}
42+
</span>
43+
<h3 class="mt-2 text-xl font-semibold text-white">
44+
{{ step.title }}
45+
</h3>
46+
<p class="mt-2 text-sm text-smoke-700">
47+
{{ step.description }}
48+
</p>
49+
</div>
50+
</div>
51+
</div>
52+
53+
<!-- CTA -->
54+
<a
55+
href="/download"
56+
class="mt-12 inline-block rounded-full bg-brand-yellow px-8 py-3 text-sm font-semibold text-black transition-opacity hover:opacity-90"
57+
>
58+
DOWNLOAD COMFY
59+
</a>
60+
</div>
61+
</section>
62+
</template>
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
<script setup lang="ts">
2+
const ctaButtons = [
3+
{
4+
label: 'GET STARTED',
5+
href: 'https://app.comfy.org',
6+
variant: 'solid' as const
7+
},
8+
{
9+
label: 'LEARN MORE',
10+
href: '/about',
11+
variant: 'outline' as const
12+
}
13+
]
14+
</script>
15+
16+
<template>
17+
<section
18+
class="relative flex min-h-screen items-center overflow-hidden bg-black pt-16"
19+
>
20+
<div
21+
class="mx-auto flex w-full max-w-7xl flex-col items-center gap-12 px-6 md:flex-row md:gap-0"
22+
>
23+
<!-- Left: C Monogram -->
24+
<div class="flex w-full items-center justify-center md:w-[55%]">
25+
<div class="relative -ml-12 -rotate-15 md:-ml-24" aria-hidden="true">
26+
<div
27+
class="h-64 w-64 rounded-full border-[40px] border-brand-yellow md:h-[28rem] md:w-[28rem] md:border-[64px] lg:h-[36rem] lg:w-[36rem] lg:border-[80px]"
28+
>
29+
<!-- Gap on the right side to form "C" shape -->
30+
<div
31+
class="absolute right-0 top-1/2 h-32 w-24 -translate-y-1/2 translate-x-1/2 bg-black md:h-48 md:w-36 lg:h-64 lg:w-48"
32+
/>
33+
</div>
34+
</div>
35+
</div>
36+
37+
<!-- Right: Text content -->
38+
<div class="flex w-full flex-col items-start md:w-[45%]">
39+
<h1
40+
class="text-5xl font-bold leading-tight tracking-tight text-white md:text-6xl lg:text-7xl"
41+
>
42+
Professional Control of Visual AI
43+
</h1>
44+
45+
<p class="mt-6 max-w-lg text-lg text-smoke-700">
46+
Comfy is the AI creation engine for visual professionals who demand
47+
control over every model, every parameter, and every output.
48+
</p>
49+
50+
<div class="mt-8 flex flex-wrap gap-4">
51+
<a
52+
v-for="btn in ctaButtons"
53+
:key="btn.label"
54+
:href="btn.href"
55+
class="rounded-full px-8 py-3 text-sm font-semibold transition-opacity hover:opacity-90"
56+
:class="
57+
btn.variant === 'solid'
58+
? 'bg-brand-yellow text-black'
59+
: 'border border-brand-yellow text-brand-yellow'
60+
"
61+
>
62+
{{ btn.label }}
63+
</a>
64+
</div>
65+
</div>
66+
</div>
67+
</section>
68+
</template>
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<template>
2+
<section class="bg-black py-24">
3+
<div class="mx-auto max-w-4xl px-6 text-center">
4+
<!-- Decorative quote mark -->
5+
<span class="text-6xl text-brand-yellow opacity-30" aria-hidden="true">
6+
&laquo;
7+
</span>
8+
9+
<h2 class="text-4xl font-bold text-white md:text-5xl">
10+
Method, Not Magic
11+
</h2>
12+
13+
<p class="mx-auto mt-6 max-w-2xl text-lg leading-relaxed text-smoke-700">
14+
We believe in giving creators real control over AI. Not black boxes. Not
15+
magic buttons. But transparent, reproducible, node-by-node control over
16+
every step of the creative process.
17+
</p>
18+
19+
<!-- Separator line -->
20+
<div
21+
class="mx-auto mt-8 h-0.5 w-24 bg-brand-yellow opacity-30"
22+
aria-hidden="true"
23+
/>
24+
</div>
25+
</section>
26+
</template>

0 commit comments

Comments
 (0)