Adopt extra model paths install scoped - #1522
morgroth123-commits wants to merge 185 commits into
Conversation
…omfy-Org#1097) * feat(updater): default startup install + installer UI on Windows Flip installUpdatesOnStartup and showInstallerUI from opt-in (default off) to default-on on Windows, mirroring the autoInstallUpdates opt-out pattern (!== false). Windows now applies staged updates at startup (disabling the crash-prone electron-updater install-on-quit) and shows the NSIS progress window during the install. Set either setting to false in settings.json to opt back out. No-op on macOS/Linux. Amp-Thread-ID: https://ampcode.com/threads/T-019eb80f-cba2-715c-8d1c-b5d98540cbec Co-authored-by: Amp <amp@ampcode.com> * docs(updater): fix comments stale after default-on flip Amp-Thread-ID: https://ampcode.com/threads/T-019eb80f-cba2-715c-8d1c-b5d98540cbec Co-authored-by: Amp <amp@ampcode.com> * refactor(updater): share Windows opt-out gate; drop Option B/C wording Addresses CodeRabbit review on Comfy-Org#1097: - Extract isWindowsOptOutGate() so the startup-install and installer-UI gates share one win32 + setting !== false check (no drift). - Replace plan-reference 'Option B/C' wording in tests/comments with behavior-based descriptions. Amp-Thread-ID: https://ampcode.com/threads/T-019eb80f-cba2-715c-8d1c-b5d98540cbec Co-authored-by: Amp <amp@ampcode.com> --------- Co-authored-by: Amp <amp@ampcode.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
* feat(titlebar): match ComfyUI theme inside instances
- Drive the title-bar header + OS window-controls overlay from ComfyUI's
reported bg while inside an instance, instead of locking to brand purple;
symbol color is luminance-derived so the window controls stay legible on
any theme
- Un-stub `isLight` so the existing `.is-light` chrome activates, and extend
it to the resting/hover/open states it never covered (install pill,
downloads tray, icon buttons) so nothing washes out or lifts to invisible
yellow on a light bar
- Scoped to attached instances only — the dashboard/chooser keeps its purple
* feat(chooser): show install version alongside the update pill
- Render the current version pill independently so an available update no
longer hides it (was a v-if/v-else-if chain)
- Right-align the update/migrate action so it reads as the affordance, apart
from the source + version metadata; version becomes the secondary shrink
target after source
- Carry the target version in the update label ("Update v0.25.0") via the new
`chooser.updatePillVersion` key, sourced from `statusTag.version`
* refactor: dedupe luminance + action-dispatch logic (CodeRabbit)
- Extract the renderer's canvas-normalize + lightness test into a single
`isColorLight` helper (`lib/colorScheme.ts`), reused by `useTitleBarIdentity`
and `TitlePopupApp` instead of two copies; it reuses the shared
`perceivedLuminance` math
- Collapse the guarded `emit('trigger-action', …)` repeated across the update +
migrate pills into one `triggerInstallAction` method in `ChooserInstallTile`
… native prompts with in-app dialogs (Comfy-Org#942) * fix(adopt): fail clearly when Legacy Desktop adoption can't source ComfyUI When in-place adoption of a Legacy Desktop install could not obtain the ComfyUI source (no pre-swap staged copy and the git clone failed), the source-missing dialog offered a 'Switch to managed env' button. Choosing it threw 'source-missing-switch-to-managed', which the migrate dispatcher mapped to { ok: true, navigate: 'new-install' }. But navigate:'new-install' is unhandled in the renderer, so the operation reported success while doing nothing — leaving the user back where they started (detail view on the dashboard-tile path, or the chooser on the first-time-migration path). Replace the fake-success escape with a clear failure: drop the 'Switch to managed env' option, keep Retry, and on any non-retry choice throw a source-missing error that the dispatcher surfaces as { ok: false, message } with a user-friendly message suggesting a new install. This fixes both the dashboard-tile and first-use migration surfaces at once, since both flow through the same backend + ProgressModal error banner. Also fixes a latent bug where an unexpected prompt choice would silently break the source loop, leaving sourceMode null and adoption continuing without a source. Closes Comfy-Org#917 Amp-Thread-ID: https://ampcode.com/threads/T-019e96a8-4f9f-709a-920e-b7659a9140a7 Co-authored-by: Amp <amp@ampcode.com> * test(telemetry): drop stale source-missing-switch-to-managed assertion The switch-to-managed escape hatch was removed when adoption was changed to fail clearly on a missing ComfyUI source, so the synthetic 'source-missing-switch-to-managed' error string is no longer produced anywhere. Drop the dead bucketError assertion and refresh the comment to describe the real failure (no staged copy and no working clone). Amp-Thread-ID: https://ampcode.com/threads/T-019eca71-9a5e-743c-acad-5e419001522f Co-authored-by: Amp <amp@ampcode.com> * fix(adopt): replace native message-box prompts with in-app dialogs The Legacy Desktop adoption flow asked the user mid-operation questions (tcc folder access, broken venv, missing ComfyUI source, confirm-adopt) via Electron's native dialog.showMessageBox — the only native message box left in the app. It popped up as an ugly OS modal on top of the in-app ProgressModal takeover. Route those prompts through the existing in-app dialog system instead. showAdoptPrompt now does a correlated IPC round-trip to the originating renderer: main sends an 'adopt-prompt' request (pre-translated labels + button index mapping kept main-side), the renderer surfaces it with useDialogs() alert/confirm above ProgressModal, ACKs delivery, and replies with the chosen button index. Robustness: prompts are correlated by promptId + webContentsId so a wrong/duplicate response is ignored; a 5s ACK timeout, window-destroyed, and operation-abort all reject and fall back to the prompt's cancel choice so the backend never blocks. The renderer bridge serializes prompts and falls back to cancel on any dialog error. Native OS file pickers (showOpenDialog/showSaveDialog) are intentionally left as-is — those are appropriate OS-native UI, not message boxes. Amp-Thread-ID: https://ampcode.com/threads/T-019eca71-9a5e-743c-acad-5e419001522f Co-authored-by: Amp <amp@ampcode.com> * fix(adopt): harden in-app prompt bridge after review Code-review follow-ups to the adopt-prompt IPC bridge: - ACK delivery from the raw onAdoptPrompt callback instead of inside the serialized handler, so a second prompt is acknowledged immediately even while the first dialog is open (matches the 'ACKs immediately' design and avoids a spurious main-side ACK timeout). - Drop the severity-based button tone: the only error-type prompt is source-missing whose primary action is Retry, which is not destructive — a red Retry button was misleading. Prompt buttons are now always primary. - Wrap the renderer response send so a failed send can't poison the prompt chain and stall later prompts. - Wrap main-side sender.send so a destroyed-sender throw settles/cleans up the pending entry, listeners, and timer immediately instead of leaking until the ACK timeout. - Sanitize the returned button index (NaN / out-of-range / non-integer from a buggy renderer) to fall back to cancel rather than throwing a TypeError. Adds tests for immediate ACK under serialization, chain resilience on send failure, wrong-sender response rejection, and malformed button index. Amp-Thread-ID: https://ampcode.com/threads/T-019eca71-9a5e-743c-acad-5e419001522f Co-authored-by: Amp <amp@ampcode.com> * i18n(zh): sync adopt source-missing keys after main merge Add zh translations for adoptPromptDetail and adoptSourceMissingFailed, drop the stale adoptPromptSwitchToManaged orphan, and update the source-missing message to match the en wording (no switch-to-managed). Amp-Thread-ID: https://ampcode.com/threads/T-019eca71-9a5e-743c-acad-5e419001522f Co-authored-by: Amp <amp@ampcode.com> * fix(adopt): route picker migrate through panel so in-app prompts work The title-popup picker dispatched migrate-to-standalone as an inline background op, which runs in main with a stub sender that has no EventEmitter methods. Adopt prompts (venv-broken, source-missing) are bridged only by the panel, so the prompt never reached a renderer: no takeover appeared, the ACK timed out, and cleanup crashed main with 'sender.removeListener is not a function'. - Route migrate-to-standalone to the panel ProgressModal (same-host) so runAction is invoked from the panel renderer (real WebContents + bridge). - Let the reconstructed picker->panel apiCall self-stop a running install. - Harden requestAdoptPromptButton: reject incapable/destroyed senders before arming the timer, and make listener cleanup never throw. Amp-Thread-ID: https://ampcode.com/threads/T-019eca71-9a5e-743c-acad-5e419001522f Co-authored-by: Amp <amp@ampcode.com> * fix(progress): restore vertical spacing on finished/error takeover PR Comfy-Org#1019 (fb6f46e) removed the column gap from .brand-progress__stack during the log-driven progress rewrite, but the finished-state rows (success actions, error message, error CTAs) still carry margin-top:-4px tuned for that gap. Without it the banner, error detail, and Back/Reboot buttons collapsed flush together on the operation-failed screen. Restore the stack's column gap; in-flight is unaffected (single flex child) and the rows' negative margins fine-tune on top of it. Amp-Thread-ID: https://ampcode.com/threads/T-019eca71-9a5e-743c-acad-5e419001522f Co-authored-by: Amp <amp@ampcode.com> * fix(adopt): say 'source code' in user-facing copy; address CodeRabbit nits Amp-Thread-ID: https://ampcode.com/threads/T-019eca71-9a5e-743c-acad-5e419001522f Co-authored-by: Amp <amp@ampcode.com> --------- Co-authored-by: Amp <amp@ampcode.com>
… login stitch) (Comfy-Org#1114) * fix(telemetry): never identify the anonymous installation_id (restore login stitch) PostHog marks any id passed to identify() as an identified person and refuses to merge one identified id into another. Calling identify() with the anonymous installation_id at boot (and on logout, and on every anonymous person-property write) burned it, so the login-time alias(installation_id -> user_id) was a silent no-op: 0 of 13,528 device ids ever stitched onto an account. Invariant: client.identify() is only ever called with the authenticated Firebase user_id, in bindUserId. All anonymous person-property writes ($set / $set_once) now route through a capture-$set on a dedicated comfy.desktop.person.set event, which updates the person without emitting $identify. Fixes the boot bind, unbindUserId (logout), and both registerPersonProperties paths. * docs(telemetry): tighten identity-model comments; fix stale fn reference Trim the verbose docstrings added with the stitch fix to the load-bearing points, and correct a header reference to a non-existent setAnonymousDistinctId() (the function is identify()).
…tored IP) (Comfy-Org#1115) posthog-node runs on the user's machine, so the request IP is the real user IP and PostHog can derive location. Opt in to country-level cohorts: disableGeoip:false and stop forcing $ip:''. Precision is bounded to country by a PostHog ingestion transformation that drops the raw $ip and all sub-country geo (city/region/coords/postal/timezone), keeping only $geoip_country_code/name. Privacy policy (legalDocs) updated to disclose country-level location with IP discarded on receipt.
…#1111) * feat(chooser): redesign install card for density + recency - Restructure tile into tiers (name → source·version meta line → recency + action); only the action stays a pill so metadata stops fighting for the row - Surface last-booted recency on every card, incl. "Not launched yet" - Drop inline version from the Update pill (bare "Update"); version reads brighter at rest in the meta line - Add tooltips: full name (only when clipped), meta line (git URL+commit when clipped), exact target version on Update, "Migrate to Standalone" on Migrate - Extract useTruncation composable for the clipped-text tooltip gate * refactor(chooser): consolidate update/migrate pill into one renderer - Replace the duplicated update/migrate Tooltip blocks with a single `actionPill` computed (icon/label/tooltip/class/action) and one template block, mirroring the existing `statusPill` pattern - Disabled/keyboard/aria wiring now lives in one place * chore(chooser): drop dead updatePillVersion key and revert test formatting noise Amp-Thread-ID: https://ampcode.com/threads/T-019ecdba-1ac2-72a3-92da-02b59f5edf01 Co-authored-by: Amp <amp@ampcode.com> * fix(picker): re-show auto-action confirm when reopening the same install The dashboard pill (Update/Migrate) opens the instance picker with an autoAction that fires a confirm modal. Reopening the SAME install's pill never re-showed the modal; a different install worked. Root cause: main sends set-config (new snapshot) then will-show. The picker's onWillShow ran dismissPickerModals(). For the same install, sections are already fresh so the autoAction confirm opened synchronously during set-config, then the later will-show dismissed it. A different install reloads sections async, so its confirm opened after will-show and survived. Fix: dismiss stale picker modals in onConfig (before the new snapshot can auto-fire), and skip the instance-picker in onWillShow so the fresh confirm is not killed. Non-picker kinds keep will-show cleanup (they can hit the fast path that skips set-config). Amp-Thread-ID: https://ampcode.com/threads/T-019ecdba-1ac2-72a3-92da-02b59f5edf01 Co-authored-by: Amp <amp@ampcode.com> * docs(picker): condense modal-cleanup comments to intent-only Amp-Thread-ID: https://ampcode.com/threads/T-019ecdba-1ac2-72a3-92da-02b59f5edf01 Co-authored-by: Amp <amp@ampcode.com> --------- Co-authored-by: Jedrzej Kosinski <kosinkadink1@gmail.com> Co-authored-by: Amp <amp@ampcode.com>
…fy-Org#1123) * refactor(title-popup): hoist popup kind tags into a shared constant Add `POPUP_KIND` and `PopupTheme` to types/ipc.ts so the popup config discriminant has one source across main, preload, and the renderer instead of literal strings re-typed in each. * feat(title-popup): add the centred downloads-full popup kind - Open "View All Downloads" as a large centred backdrop popup on the reused title-bar view, fed by the existing tray download broadcast - Generalise global-settings sizing into `computeCenteredCardBounds` + `kindIsCentered`, shared by the open path and the resize refit * fix(title-popup): stop the preload validators dropping new popup kinds The config and will-show validators gated on a hardcoded kind allowlist, silently rejecting any kind they didn't enumerate. Key them off `POPUP_KIND` so the renderer actually receives `downloads-full`. * feat(downloads): render the full view inside the title-bar popup - Move the DownloadsModal design into `DownloadsFullView`, rewired from the panel store / window.api to the popup's prop + `__comfyTitlePopup` bridge, and replace BaseModal chrome with the popup card - Drop the per-row entrance animation: its lingering transform layer blocked pointer hit-testing in the transparent popup view - Wire the `downloads-full` branch + card chrome into TitlePopupApp * refactor(panel): drop the downloads-v2 overlay panel path The full Downloads view no longer mounts in the panel renderer, so remove the `downloads-v2` body mode and its DownloadsModal mount across the panel, registry, and host-window layout. `feedback` stays the lone overlay mode. * test(downloads): cover the full-popup hand-off; drop downloads-v2 fixtures - Add e2e for "View All Downloads" opening the full popup with seeded rows and for its empty state - Point the panel-key test fixtures at `feedback` and remove the dead DownloadsModal mock now that the component is gone * fix(downloads): restore hover/cursor in the full popup Chromium skips mousemove delivery to the transparent popup WebContentsView when nothing subscribes to pointer-move, freezing :hover and the cursor until a click forces a hit-test. Add a no-op @mousemove subscriber, and move the close button in-flow into the header (dropping the panel's position:relative stacking context) to match the working global-settings view. * test(downloads): de-flake the full-popup e2e hand-off Wait for the tray footer link and stable bounds before clicking "View All", so the kind-switch isn't raced on the empty (fast-rendering) path. * refactor(downloads): address review nits in the full popup - Import the download entry / state / action types from the preload bridge instead of re-declaring them locally - Memoise the status badge into a lookup so it isn't recomputed three times per row - Extract the tray -> "View All" e2e flow into a shared helper * refactor(title-popup): key remaining kind checks off POPUP_KIND, dedup download types Amp-Thread-ID: https://ampcode.com/threads/T-019ececa-4fd3-76fe-ac8b-1f37facb91a6 Co-authored-by: Amp <amp@ampcode.com> --------- Co-authored-by: Jedrzej Kosinski <kosinkadink1@gmail.com> Co-authored-by: Amp <amp@ampcode.com>
…rd (Comfy-Org#1068) (Comfy-Org#1116) * fix(standalone): show nightly version for Latest on GitHub install card The install-wizard variant card advertised the ComfyUI version baked into the standalone bundle for the 'Latest on GitHub' channel. That bundle lags master by many releases (e.g. v0.20.1 while master is ~v0.24.1), so the card read '4 minor versions behind' even though picking 'latest' fast-forwards the install to master HEAD post-install. Thread the latest stable tag into the 'latest' channel's release data and advertise it as a nightly (e.g. 'ComfyUI 0.24.1 (nightly)'), falling back to the bundled version only when the tag can't be resolved (offline). Display only; the Manage/Update view and actual install behavior were already correct. Fixes Comfy-Org#1068 Amp-Thread-ID: https://ampcode.com/threads/T-019ece60-fd60-72f9-997f-22e7562d07a3 Co-authored-by: Amp <amp@ampcode.com> * refactor(standalone): dedupe channel data payload, fix stale comment Amp-Thread-ID: https://ampcode.com/threads/T-019ece60-fd60-72f9-997f-22e7562d07a3 Co-authored-by: Amp <amp@ampcode.com> --------- Co-authored-by: Amp <amp@ampcode.com>
…-Org#1099) (Comfy-Org#1107) * fix(terminal): activate the right env for git/portable Console (Comfy-Org#1099) The interactive Console resolved its venv/uv via getActiveVenvDir/ getActiveUvPath, which only branch on `adopted` and otherwise assume the standalone layout: `<installPath>/ComfyUI/.venv` + `<installPath>/ standalone-env/uv.exe`. For a git-manual install (venv lives at `venvPath`, no standalone-env) or a portable build (embedded `python_embeded`, no venv) this activated the wrong/nonexistent venv and aliased `pip` to a nonexistent `standalone-env\uv.exe`. Make terminal env resolution source-aware: - Add `TerminalEnv` + optional `SourcePlugin.getTerminalEnv()`. - git: activate `venvPath` (its own pip); no standalone-env reference. - portable: prepend embedded `python_embeded`(+Scripts) to PATH and route pip through `python_embeded\python.exe -s -m pip`. - terminal.ts builds init commands from a `TerminalEnv` (venv-activate vs PATH-prepend, optional pip routing) and uses an injected resolver wired in the IPC layer, keeping the source-plugin graph (and Electron `app`) out of the eagerly imported terminal module. - standalone/adopted keep the existing default behavior. Amp-Thread-ID: https://ampcode.com/threads/T-019eca72-a4d7-7658-814a-a2c95bab28fd Co-authored-by: Amp <amp@ampcode.com> * fix(terminal): also fix legacy desktop env + harden venv/root detection Code-review follow-ups: - desktop (v1) source has the same bug class: it has hasConsole=true, keeps its venv at `<installPath>/.venv`, and has no standalone-env. Add `getTerminalEnv()` so the Console activates that venv instead of the standalone layout. - git: only activate `venvPath` when `resolveVenvPython()` confirms the interpreter still exists; otherwise open a plain shell. - portable: guard `findPortableRoot` against a missing/unreadable installPath so terminal spawn can't throw on a deleted drive. * feat(terminal): open the Console in the ComfyUI code folder (Comfy-Org#1070) Start the per-install Console on the ComfyUI repo folder (where main.py lives) instead of the encompassing install folder, consistently across install types: - standalone/adopted: `<installPath>/ComfyUI` - portable: `<root>/ComfyUI` - git: the dir containing the resolved `main.py` Adds an optional `cwd` to `TerminalEnv`, set per source; terminal.ts uses it and falls back to `installPath` when unset or missing. Amp-Thread-ID: https://ampcode.com/threads/T-019eca72-a4d7-7658-814a-a2c95bab28fd Co-authored-by: Amp <amp@ampcode.com> --------- Co-authored-by: Amp <amp@ampcode.com>
…y-Org#1120) Opening a portable (or any local) install's detail section from the central pill could land on the Terminal/Console tab instead of the default Update tab. Cause: the visible-tabs list gates every tab on backend `sections` except Console, which shows for any local install regardless of sections. While sections are empty/stale (initial load or retarget), `tabs` collapses to just `['console']`, and the tab-fallback watcher latched `activeTab` onto Console; once the real sections arrived, Console was still valid so it never reverted. Gate the fallback on `sectionsFresh` so it only reconciles against a payload that belongs to the current install, never a transient list. Amp-Thread-ID: https://ampcode.com/threads/T-019eca72-a4d7-7658-814a-a2c95bab28fd Co-authored-by: Amp <amp@ampcode.com>
…Comfy-Org#1130) * fix(titlebar): sync menu zoom reset state * test(titlebar): align dashboard zoom menu e2e Dashboard zoom is not a supported user path; the old dropdown e2e forced the chooser host's dummy comfyView to zoom and expected Reset Zoom to appear. Keep the forced internal state coverage, but assert the dashboard menu continues to hide Reset Zoom. * refactor(titlebar): share zoom percent conversion * test(titlebar): always reset dropdown zoom state * refactor(titlebar): tighten reset-zoom gate + drop redundant e2e case * chore: trim comments --------- Co-authored-by: Nynxz <contact@nynxz.com>
…1134) - Add `displayLaunchUrl` to cloudUrl.ts — returns the URL host, parsed not hardcoded - Use it for the `connecting`/`connectingTime`/`cannotConnect` strings so UTM + desktop_device_id no longer leak into the loader - Keep the full tagged URL for `waitForUrl` and the session record - Cover host strip, port retention, and non-URL passthrough
Comfy-Org#1131) * feat(templates): pick a starter template at install, auto-open on first launch - Add bundledTemplate card field to the standalone source (wizard Advanced) - Resolve a template's required models dynamically from its workflow JSON (site-packages first, GitHub-raw fallback; scans top/node/subgraph models[]) - Persist bundledTemplateId + one-shot pendingTemplateOpen on the install record - Append ?template=<id>&source=default to the comfy URL on first launch (attach.ts), consumed once via clearPendingTemplateOpen; zero frontend changes - Consent checkbox + downloadTemplateModels flag; starter-template i18n (en/zh) * feat(templates): download template models in the background during install - New templateDownloadTask: fire-and-forget at install-begin so bytes overlap env setup; own AbortController; per-file failures non-fatal - New templateDownloadCore (pure, unit-tested): runPool (bounded concurrency 3), summarizeTemplateState (cumulative math), formatTemplateSubStatus - Hot-path chunk callback is O(1) counters only; reader formats off-band - Export getModelsBaseDir so install-time + in-window downloads agree on the dir - Kick off from registerInstallationHandlers; abort on install cancel/window close - Remove the old blocking postInstall download path * feat(templates): show model download in the launch progress, with rich status + logs - Splice a synthetic template-models phase into the launch stepper after the security scan; a 500 ms reader drives its substatus from shared state - Rich substatus: speed, ETA, current file (N of M), cumulative X/Y GB - Stream per-file logs into View logs; seed the launch op's terminal from the durable log ring buffer (new logs-snapshot IPC) so install-leg lines survive - Fix the progress-bar leap: phase weight 0.05 + report indeterminate when the download was already complete, real percent (capped 99) while it runs * docs(templates): starter-templates engineering handoff + Phase 2 plan - What's done (deeplink + background download), decision matrix, files touched - Phase 2 decided scope + edge-case dispositions + living build checklist - Notes the live constants are the Phase-1 test set, not the Phase-2 picks * feat(templates): retry, MAX_PATH guard, and surfaced download errors - Per-file 2× auto-retry around download() via a pure withRetry helper; a user cancel is fatal (no retry), and .dl-meta resume means a retry continues the partial rather than restarting it. - Windows MAX_PATH guard (truncateForMaxPath) before each write; a name too long to fit becomes a per-file skip, not a task failure. - Surface download failures in the substatus: red + bold + X icon, wired end-to-end (ProgressData.error → phaseErrors → ProgressStepVM.isError → BrandProgressView .is-error). - In-task disk pre-check now a surfaced hard error with a disk-specific message (templateModelsNoSpace) instead of a silent skip. - Unit tests for withRetry, truncateForMaxPath, and the disk-error branch. * feat(templates): cross-OS VRAM detection + warn-decision helper - detectGPU() now returns vramBytes on any OS: nvidia-smi memory.total (authoritative) → os.totalmem() for Apple Silicon → systeminformation si.graphics() fallback for AMD/Intel/discrete. Reuses the graphics probe the app already runs for telemetry rather than hand-rolling native probes. Undefined only when no real number is readable, so the picker never false-warns. Exposed via the existing detect-gpu IPC + GPUInfo.vramBytes. - shouldWarnVram(detected, recommended) pure helper in bundledTemplates: warns only when a real detected figure is below the template's recommendation; silent on undefined or no recommendation. - BundledTemplate gains optional recommendedVramBytes (populated in group A). - Unit tests for shouldWarnVram. * feat(templates): hard-block install when disk can't fit template models - checkTemplateDiskOrBlock (installHelpers): free disk vs the selected template's model size × 1.1 headroom, with NO continue-anyway — a block alert instead, so the user frees space or picks a lighter template rather than ending up with a half-downloaded model set + a confusing error row. - Wired into InstallWizardModal.handleSave, after the existing install-bundle disk warn; only fires when a template-with-models is chosen and consented. - Pure templateDiskRequiredBytes extracted for the threshold math + tested. - en/zh copy: diskSpace.templateBlock{Title,Message}. * feat(templates): skip model download → hand off to the downloads tray When the trailing template-models phase is still running, surface a centered "Skip model download" button. Clicking hands the resume-capable background task off to the title-bar downloads tray so the user can enter ComfyUI immediately — no restart. - comfyDownloadManager: a separate mirror registry (setTemplateTrayMirror / clearTemplateTrayMirror) merged into getDownloadsTrayState's active/recent. Kept out of pendingDownloads so the real-download DownloadItem lifecycle (cancel/retry/temp-rename) is untouched; the renderer needs no changes. - templateDownloadTask: mirrorTemplateDownloadToTray polls the shared state every 500ms and reflects it via the pure templateStateToTrayEntries mapper until terminal; stopTemplateTrayMirror tears it down on window close. - skip-template-download IPC + preload + ProgressModal button (gated on active=template-models, not-errored, <100%). - Fix: guard op.phaseErrors access with optional chaining (partial op mocks). - Tests for templateStateToTrayEntries. * feat(templates): real per-modality showcase picks + media-package fix - bundledTemplates.ts: one verified showcase per modality, metadata copied verbatim from the live workflow_templates index (title/description/size/vram): Image flux_schnell, Video text_to_video_wan, Audio audio_stable_audio_example, 3D 3d_hunyuan3d_image_to_model. Each confirmed to embed a downloadable models[]. Dropped image_z_image_turbo (no embedded models → nothing pre-downloads) and the doc's stable-audio-3/triposplat ids (absent from the current index). Added modality + thumbnailUrl (/templates/<id>-1.webp) + recommendedVramBytes, passed through getFieldOptions data. - Fix: the templates package was split into per-modality media sub-packages (…_media_image/_video/_other/…). loadTemplateJson only checked the legacy single package, so local resolution always missed and fell back to the slow, hang-prone remote path. Now probes all media packages first. * feat(templates): dedicated starter-template picker step A full-screen template picker after Configure, before install — a modality grid of showcase cards (thumbnail + size + modality chip) plus a "blank canvas" option, with the image template pre-selected. - TemplatePickerStep.vue renders the bundledTemplate FieldOptions the wizard already loads, so selecting drives selections.bundledTemplate. Warns (never blocks) when detected VRAM < the template's recommended VRAM; silent when VRAM is unknown. Hosts the model-download consent + "Don't show again". - InstallWizardModal: step machine (configure → template); CTAs "Skip & Install" / "Install"; Back returns to Configure. The bundledTemplate Advanced card is hidden while the picker step is active (shown as the fallback when the picker is gated off). - Gating: skipTemplatePickerStep setting; "Don't show again" only when getInstallationsSummary().localCount > 0; opted-out returning users auto-skip the step. Express install installs with no template by construction (picks the recommended "None" option). - Dashboard "Add New Instance" inherits the step (same modal). - en/zh copy + nested standalone.modality.* labels. * feat(templates): warn on app quit while template models are downloading before-quit now checks hasActiveTemplateDownloads() and, when a template-model download is still in flight, shows a synchronous confirm ("Quit Anyway" / "Keep Downloading", default = keep) before tearing down. Quitting drops the download (no resume), so the user gets one chance to back out. Only gates a real user quit, not an in-progress relaunch/update quit. * docs(templates): mark Phase 2 groups A–F done in the handoff checklist All 26 checklist items across groups A–F landed; full suite (2138 tests), all four typechecks, and eslint are green. Only live pnpm dev runtime verification (G) remains — interactive, left for the engineer. * feat(templates): redesign starter-template picker + bundle thumbnails - Redesign the picker as a compact selectable row list (reuses brand-variant-list): thumbnail + title + meta (modality · size · VRAM), description expands inside the selected row, full keyboard nav. Drop the card grid, the "None" tile, and the consent toggle ("Skip & Install" is the blank-canvas / no-download path). - Bundle the 4 modality thumbnails in-repo (public/images/templates/*.webp, downscaled) and point bundledTemplates at them; remote raw.githubusercontent previews were blocked by the renderer CSP (img-src 'self' data:). - Live disk-block in the picker: selecting a too-large template shows a red error and disables Install. Extract the shared pure decision isTemplateDiskBlocked used by the picker, the wizard, and the save-time gate. - Drop dead consent-sync in handleSave (buildInstallation derives downloadTemplateModels from the template id) and the unused defineExpose. * feat(templates): show model download as the last launch step + gate reveal - Move the template-models phase to the end of the launch stepper. Its 500ms reader stays silent until the server is up (serverUp flag) so the main tracker drives the real phases honestly; only then does the download become the active last row (no faked-done / 99% jump). - Gate the ComfyUI reveal: at port-ready, if the download is still running, hold and show "Skip & open ComfyUI" instead of flashing past. Resolve on done / skip / cancel; on error (after 2x retry) show a failure line + 3-2-1 countdown, then open. awaitTemplateDownloadSettled is the single settle primitive; requestSkipTemplateDownload releases the gate + mirrors to tray. - Surface a clearer "requires login or license" line for a gated repo (401/403) via describeDownloadFailure; counts toward retry, non-fatal. * docs(templates): sync handoff to shipped picker + launch gate - Picker UI (compact rows, bundled thumbnails, no None/consent, live disk-block). - Stepper: download-last + serverUp-gated reader + waitForTemplateDownloadGate (hold reveal until settle/skip; error countdown). Drop the reverted reachedLastRealPhase notes. - 13-row human-reviewer test matrix. * test(templates): cover launch gate, picker states, and skip path - gate: awaitTemplateDownloadSettled resolves done/error/cancelled/ skipped/aborted/absent + clears the stale skip flag on settle - picker: TemplatePickerStep disk-block, VRAM warn, recommended tag, select/keyboard nav, thumbnail-fail glyph - build: "Skip & Install" (none) builds no model download; a real pick sets bundledTemplateId + pendingTemplateOpen + downloadTemplateModels - docs: rewrite handoff as a durable reference (mental model, decisions, edge cases, coverage map, 13-row review matrix); verified file links * refactor(templates): address PR review — error visibility, cleanup, tests - surface the silent disk-probe failure in the task log instead of swallowing it - launch gate: emit the failure immediately when the download already errored before server-up (closes the ≤500ms blind window) - extract pure buildTemplateDeeplink; attach.ts uses it + logs a failed clearPendingTemplateOpen instead of dropping the error - prune stale template URLs from the tray-mirror createdAt map (no leak) - reset the wizard to the Configure step when the source changes - strip history-narrating comments; convert kept intent to JSDoc - tests: deeplink round-trip, clearPendingTemplateOpen (incl. legacy no-template-fields record), picker Enter/Space native activation * docs(templates): update handoff * feat(templates): gate starter-template picker on PostHog experiment + add funnel telemetry Wraps the new install-wizard template step in the `desktop-starter-templates-picker` A/B flag (control: legacy single-screen Configure, treatment: picker step), reuses the cache-first `telemetryGetExperimentFlag` plumbing used by FirstUseTakeover, and pins the variant as a person property so any downstream activation event can be sliced by arm. Adds `comfy.desktop.template.picker_shown / .selected / .install_confirmed / .skipped` so the picker funnel is measurable end-to-end against the existing `comfy.desktop.template.download.skipped` event. Defaults to control on flag miss / network failure so the legacy flow ships when the experiment can't be resolved. * refactor(templates): address CodeRabbit review Security: - reject non-HTTPS model URLs and sanitize template-provided model name/directory against path traversal (new templateModels.test.ts) Correctness: - scope the tray mirror per install so concurrent installs don't clobber each other's rows - abort the template-models launch reader on success/failure so its 500ms timer can't leak after a skip - validate the chosen template id against the known set before persisting - generation-guard the wizard's open() async setters against reopen races - ProgressModal: gate skip on op.finished, reset per op, and mark consumed only after the skip IPC succeeds Docs/tests: - correct the stale GPUInfo.vramBytes JSDoc (AMD/Intel now probed) - use NO_TEMPLATE_VALUE, fix the none-equivalent picker test, drop a double cast, add isError-render and logsSnapshot-seeding coverage * fix(i18n): correct migrateBannerAction zh translation Was a corrupted string ('迁移 否w'); now reads '立即迁移'. * fix(templates): clear finished template rows from the downloads tray Mirrored template-model rows live in templateTrayMirrorByInstall, not recentDownloads, so dismissRecentDownload / clearFinishedDownloads left finished ones lingering in the tray forever. - dismissRecentDownload also drops a matching mirror row (pruning empty buckets) - clearFinishedDownloads purges terminal mirror rows, keeping in-flight ones - test: both cleanup paths drop finished mirror rows; a downloading row survives * feat(templates): surface running template downloads in All-Downloads modal + auto-open tray - getAllDownloads() now includes template-mirror rows so the All-Downloads modal lists them, matching the tray popup - setTemplateTrayMirror broadcasts each row via model-download-progress so the modal tracks live progress like any real download - auto-open the downloads tray ~2.5s after ComfyUI reveal when a template download is still running; wired into all three onLaunch reveal paths - tests: getAllDownloads includes mirror rows + per-row broadcast fires * feat(templates): skip picker when disk too small; wiggle blocked Install - skip the template step entirely when free disk can't fit even the cheapest model-bearing template (new minTemplateModelBytes feeds diskTooSmallForAnyTemplate → shouldShowPickerStep); fails open while disk space is still loading - Install button stays clickable but reads disabled when blocked; clicking it shakes the disk-error alert instead of installing, mirroring the first-use consent nudge (nudgeDiskError exposed by the picker) - tests: minTemplateModelBytes + the skip-gate threshold; nudgeDiskError shakes when blocked / no-ops when not - docs: both behaviors in gating, decisions, edge cases, coverage, matrix * docs(templates): disambiguate 'Picker UI' → 'Template Picker UI' - fix the cross-ref label + stale #picker-ui anchor to #template-picker-ui - rename the test-coverage row and matrix scenario labels to match * feat(templates): refresh bundled starter set with animated previews - Swap to Z-Image-Turbo, Wan 2.1, Stable Audio 3, TripoSplat - Add previewKind flag (animated vs static) + paired -still.webp frames - Update bundled thumbnails; drop retired Flux/Hunyuan/old-audio assets * fix(experiments): assign absent flags on fresh boot - Back-fill in-memory cache from a settled fetch for keys absent at boot, so a fresh-boot session sees its real variant instead of control - Never flip a key the process already committed to this boot * feat(templates): bigger row media, alerts above the card - Enlarge row preview to a 96x60 landscape frame; play animated webp with a still fallback under prefers-reduced-motion - Move disk/VRAM alerts above the card as filled chips so they're always visible (never clipped by the list scroll); host owns the blocked shake - Taller card (min/max-height) with the list scrolling inside; sleeker footer buttons - Tighten disk/VRAM alert copy to short, crisp essentials * fix(experiments): await the boot fetch on flag read (match cloudCapacity) - add getFlagAsync that awaits the in-flight init fetch before reading, so a renderer query landing before it settles gets the real variant, not control - telemetry:getExperimentFlag handler now awaits getFlagAsync - cache the init promise (initPromise) like cloudCapacity; drop initStarted - tests: mid-fetch query resolves to the fetched variant; cached flag resolves synchronously * docs(templates): remove stale starter-templates handoff * feat(install): ship starter-template picker unconditionally (drop A/B) Builds on Comfy-Org#1084 (@MaanilVerma) — removes the PostHog experiment gating so the starter-template picker is shown to everyone on the standalone install path, instead of only the treatment arm. The picker is a clear UX win (pre-downloaded models + ready-to-run canvas vs a blank canvas with missing-model errors), so the team chose a full rollout over measuring the lift. Removing the flag also fixes a first-run gap where the variant defaulted to control because the experiment flag is fetched only after telemetry consent (granted on the first-use screen, moments before the install wizard opens) — so first-run users, the picker's target population, mostly never saw it. Changes (InstallWizardModal.vue only): - Drop the experiment key/variant, the on-open flag read, exposure event, and the experiment_* person-property pin. - shouldShowPickerStep now gates on: standalone source + templateOptions present + disk fits + the existing skipTemplatePickerStep user opt-out (pickerEnabled). No variant condition. - Remove the now-unneeded flag-ready await race-guard in handleConfigureContinue. - Keep the picker funnel telemetry (picker_shown / selected / install_confirmed / skipped) for adoption monitoring; drop the dead 'variant' property from each. Shared experiments.ts infra is untouched (still used by the live desktop-first-use-fork-default experiment). PostHog side: experiment ended + archived, flag disabled. typecheck + lint clean; 92 template/experiment unit tests pass. * feat(install): always show starter-template descriptions Drop the expand-on-select animation in the template picker — every row shows its description statically. The 0fr→1fr grid reveal only surfaced the description for the selected row, which hid useful info for the other options the user is choosing between. * fix(build): raise ToDesktop uploadSizeLimit to 30MB for bundled template thumbnails The starter-template picker bundles ~1.5MB of preview thumbnails in-repo (src/renderer/public/images/templates/*.webp) so a fresh first-run user has offline, CSP-safe previews. That tips the packaged app source from just under ToDesktop's 20MB default upload cap to ~22MB, failing 'todesktop build' at the upload step. Raise the cap to 30MB. * fix(build): drop invalid JSON comment key from todesktop.json todesktop.json is schema-validated with additionalProperties:false, so the _comment_uploadSizeLimit key added alongside uploadSizeLimit failed CLI validation ('not expected to be here'). Keep only the valid uploadSizeLimit:30 (raised from the 20MB default because the bundled ~1.5MB of starter-template thumbnails push the packaged app source to ~22MB). * fix(install): address CodeRabbit review — popup auto-open guard, path + matchMedia safety Three correctness fixes from the CodeRabbit review of Comfy-Org#1131 (all in the inherited Comfy-Org#1084 picker code): - titlePopup: the ~2.5s post-launch downloads-tray auto-open only suppressed when a *downloads* popup was already open, so it could forcibly replace an instance-picker / settings popup the user had just opened. Suppress for ANY open/opening popup — an unsolicited auto-open must never interrupt the user. - templateModels.sanitizeModelPath: reject an empty / '.' / './' directory (all normalize to a current-dir ref) so a malformed entry is skipped rather than dropping the model file into the models root. Added a regression test. - TemplatePickerStep: harden the reduced-motion read to matchMedia?.(...)?.matches ?? false so it can't throw if matchMedia is absent. Deliberately NOT changed (CodeRabbit flagged but not warranted): - tray-mirror install-scoping (:173) — already install-scoped in current code. - clearTemplateTrayMirror renderer removal (:190) — the background download is still live after window close, so emitting removal could hide a running download; left as-is. - recommended-badge-from-flag — the 'recommended' flag is on the 'None' option (for field-chain auto-select) and filtered out of the cards; the first-card badge is intentional (Image is deliberately first + pre-selected). typecheck + lint clean; templateModels + TemplatePickerStep suites green. * fix(install): remove template VRAM requirements from the picker The picker showed a "recommends ~N VRAM" spec line and a "may run slowly" warning, but the recommended-VRAM figure was set equal to the template's download size (e.g. 20GB for both) — the index's `vram` was never wired up, so the numbers were wildly overestimated. That scared users away from workflows their hardware can run fine. Remove the VRAM display and warning entirely rather than ship a wrong signal: - drop recommendedVramBytes from the bundled-template data + the per-row meta line and the GPU-vs-template warning alert - remove the now-dead GPU VRAM detection (gpu.detectVramBytes via nvidia-smi/systeminformation) and the picker's detectGPU pre-roll; the Configure-screen GPU label hint still uses detectGPU().label - drop the templatePickerSpecVram / templateVramWarning strings (en + zh) and the shouldWarnVram helper + tests Disk-space hard-block (a real, correct constraint) is unchanged. --------- Co-authored-by: Maanil Verma <vermaMaanil97@gmail.com> Co-authored-by: Jedrzej Kosinski <kosinkadink1@gmail.com>
…fy-Org#1136) The title-menu item opens a fresh install-less chooser (the dashboard), not a duplicate of the current window — "New Window" mislabeled what it does. Relabel to "Open Dashboard" (en + zh + the hardcoded fallback). Label-only change: the `fileMenu.newWindow` key, the `new-window` item id, and the `comfy-window:new-chooser-window` IPC are unchanged.
…lts (Comfy-Org#1016) * Clean up unique browser partitions on delete + drive-aware data defaults Deleting an install never removed its browser partition. Unique-partition installs each own a persist:<id> bucket under userData/Partitions/<id> that nothing else reuses, so every delete leaked one forever. handleDelete now removes the install record first, then best-effort clears the session (timeout-bounded) and deletes the partition dir (force + retries) so it can never hang or lock up the delete. Shared partitions are left untouched. Also make the large data dirs follow the drive the user picks in the Windows installer: when the app is installed to a non-system drive, installDir, the shared models/input/output root, and the download cache default to that drive instead of always landing on the system drive. Non-Windows and same-drive installs keep the existing home/userData defaults. Amp-Thread-ID: https://ampcode.com/threads/T-019eac5b-d0b9-7090-a4dd-491a74ab1fbe Co-authored-by: Amp <amp@ampcode.com> * Group drive-redirected data dirs under a single Comfy-Desktop folder When the app is installed to a non-system drive, installs, shared models/input/output, and the download cache now live under one <drive>\\Comfy-Desktop parent instead of three separate folders at the drive root. Home-dir and non-Windows defaults are unchanged. Amp-Thread-ID: https://ampcode.com/threads/T-019eac5b-d0b9-7090-a4dd-491a74ab1fbe Co-authored-by: Amp <amp@ampcode.com> * feat(storage): default new Windows installs to %LOCALAPPDATA% on the system drive On a system-drive Windows install, the large data dirs (installs, shared models/input/output, download cache) previously defaulted to the user home root and the cache to roaming AppData. New installs now group under %LOCALAPPDATA%\Comfy-Desktop instead, while existing installs keep their home-root layout. The choice is classified from a home-root footprint and pinned with a one-time marker so it never flips between launches. Amp-Thread-ID: https://ampcode.com/threads/T-019eac3f-cda6-74ea-ba76-49da53cadea2 Co-authored-by: Amp <amp@ampcode.com> * fix(delete): clean up per-install partition even after toggling to shared browserPartition is user-editable, so an install created as 'unique' (which already created Partitions/<id>) can later read as 'shared'. Gating cleanup on the current setting stranded that dir forever. Remove Partitions/<id> whenever it exists, guarding persist:shared explicitly. Amp-Thread-ID: https://ampcode.com/threads/T-019eca71-9e54-7730-ba49-adfd9a824b07 Co-authored-by: Amp <amp@ampcode.com> * refactor: address CodeRabbit review (system-drive anchor, DRY) - paths.ts: anchor selectedInstallDrive on the OS SystemDrive, not the user profile, so a redirected profile (home on another drive) no longer misclassifies a system-drive install as a redirected one; add regression test. - settings.ts: use builtinDefaultInstallDir() for the installDir default instead of reimplementing it. - delete.integration.test.ts: extract a shared invokeDelete() helper. Amp-Thread-ID: https://ampcode.com/threads/T-019eca71-9e54-7730-ba49-adfd9a824b07 Co-authored-by: Amp <amp@ampcode.com> * fix(settings): fall back installDir/cacheDir when their volume is gone If a configured installDir/cacheDir lives on a drive that no longer exists (reinstall on a different drive, a removed disk), reset it to the current default so the app never strands installs/cache on a dead path. Checks the path root, not the leaf, so a created-on-demand custom location that doesn't exist yet is preserved (matches the deliberate not-yet-created behavior). No-op on POSIX (root is always /). Amp-Thread-ID: https://ampcode.com/threads/T-019eca71-9e54-7730-ba49-adfd9a824b07 Co-authored-by: Amp <amp@ampcode.com> * fix(delete): reclaim leaked browser partitions via startup sweep Manual Windows testing showed the inline deleteBrowserPartition cleanup does not actually remove Partitions/<id>: session.fromPartition().clearStorageData() materializes a live session that locks and re-writes the dir, so the bounded fs.rm loses the race and the folder survives (the mocked integration test couldn't catch this). Add sweepOrphanPartitions(), run at startup before any install session exists, to reliably reclaim any Partitions/inst-* whose install is gone (covers the Windows lock case and crash leftovers). Never touches 'shared' or non-install dirs. Amp-Thread-ID: https://ampcode.com/threads/T-019eca71-9e54-7730-ba49-adfd9a824b07 Co-authored-by: Amp <amp@ampcode.com> --------- Co-authored-by: Amp <amp@ampcode.com>
…) (Comfy-Org#1110) * fix(operations): surface full substep output on failure (Comfy-Org#1106) When a substep of an update/migrate/restore fails (e.g. uv pip install), the user previously saw only a bare exit code (e.g. "requirements install exited with code 2"), and the streamed output sat behind a collapsed "View logs" toggle. This made failures black-box (see Comfy-Org#1083). - Add bounded output capture to the shared pip helpers (runUvPipDetailed / installFilteredRequirementsDetailed) and fold the output tail into update/migrate failure messages via withOutputTail. - Surface which packages failed in the snapshot-restore error message. - Auto-expand the ProgressModal logs panel when an operation finishes with an error so the full output is visible immediately, for all operations. Amp-Thread-ID: https://ampcode.com/threads/T-019eca72-8475-70ec-89a2-ab0c2460da47 Co-authored-by: Amp <amp@ampcode.com> * fix(operations): address review feedback on substep error surfacing - updateOrchestrator: the dry-run conflict-check path (the primary user "Update" path) now captures pip output via runUvPipDetailed instead of the unbounded spawnCommand, and uses the combined output tail instead of stderr-or-stdout (which dropped stdout-only failures). - actions.ts: cap the restore failure package list at 20 with an "…and N more" suffix so a large restore can't produce a wall-of-text. - ProgressModal: make the error auto-expand robust — fire immediately (so reopening a finished failed op expands) and drop the terminalOutput gate (the accordion is already gated on it), covering the error-before-output race. - pip.ts: clarify the capture cap is characters, not bytes. Amp-Thread-ID: https://ampcode.com/threads/T-019eca72-8475-70ec-89a2-ab0c2460da47 Co-authored-by: Amp <amp@ampcode.com> --------- Co-authored-by: Amp <amp@ampcode.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
…fy-Org#1132) * feat(telemetry): instrument the install -> boot -> canvas funnel Adds the missing desktop-side funnel steps so we can see where users drop between download and first render, instead of only knowing a launch started. - comfy.desktop.install.phase — per-phase install boundaries (start / end / error) for the standalone installer. PostHog carries the funnel timing; the error rows ride the Datadog mirror so a monitor can page when a phase hard-fails for a population after a release. - comfy.desktop.comfyui.boot_phase — launch-progress phase timings, buffered in memory per installation and flushed ONLY when the boot fails or times out (healthy-boot timing is already covered by instance_started, and boot_started alone is ~258k/14d — emitting per-phase on every boot would multiply that for no gain). The phases explain WHERE a failed boot stalled. - comfy.desktop.comfyui.boot_failed — port-wait timeout / early process exit / renderer load failure / render-process-gone. Datadog-mirrored; paired with the flushed boot_phase breakdown. - comfy.desktop.comfyui.canvas_rendered — first dom-ready of a LOCAL install's main frame (the bottom of the install->canvas funnel), with server_ready_to_canvas_ms. Deduped per launch; the failed-load leg is recorded separately. - comfy.desktop.first_use.abandoned — the first-use takeover unmounting without any completion path firing (the chooser-drop signal; pairs with first_use.completed to give onboarding its denominator). - comfy.desktop.cloud.entry_blocked — the cloud capacity gate on every gated entry (picker / first_use), with raw flag + tier + decision, so we can see how many cloud entries the kill-switch shed vs. warned vs. let through. boot_phase / install.phase buffers are bounded and terminally cleared. * test(telemetry): cover boot-phase buffer, canvas-render, and cloud capacity gate - bootPhaseBuffer: failure-only flush, first-write-wins per phase, bounded lifecycle (start resets, success clears, flush emits one boot_phase per buffered phase + returns last phase + clears). - canvasEntry: first-render dedup per installation, failed-load leg bypasses the dedup, server_ready_to_canvas_ms from the session anchor. - useCloudCapacity.confirmEntry: cloud.entry_blocked decisions (no_op / declined / proceeded), normal flag emits nothing, paid-user relaxation past a disabled kill-switch. * docs(telemetry): trim verbose comments in boot-phase + canvas-entry taps
…Org#1145) Follow-ups to the merged install -> boot -> canvas instrumentation (Comfy-Org#1132); all side-channel-hardening, no behavior change to the funnel events: - canvasEntry: clamp server_ready_to_canvas_ms to >= 0 so a backward clock step can't emit a negative, funnel-polluting duration. - installer.withInstallPhase: isolate every onPhase tap in try/catch so a throwing telemetry callback can never abort or mask an install. - install.emitInstallPhase: guard classification/emission so side-channel metrics fail quietly (this is also the installer's onPhase tap). - useCloudCapacity.test: fix the loadComposable return type — it resolved to the composable function, not its call result.
…unify the desktop funnel) (Comfy-Org#1147) * feat(telemetry): stamp installation_id on every event default Set defaultEventProperties.installation_id in identify() once the device id is bound at boot, so renderer events (routed in over IPC) and main events share a single join key. Removes the person_id/installation_id split that breaks the install->boot->run funnel. Does not identify the anon id (only bindUserId calls client.identify); pure event property. * feat(telemetry): emit install.completed at every local-install finish captureInstallCompleted existed but was never called, so the comfy.desktop.install.completed funnel event never fired. Wire it into the three local-install completion sites — fresh express/manual (install-instance handler), Desktop-1 adopt, and snapshot migrate — each firing once when the install is ready to boot (distinct from comfyui.boot_started, which fires every launch). Carries {installation_id, method, express}. Excludes the cloud fork (no local install) and the version-update path. Best-effort via capture(). This isolates the install -> boot transition the funnel was missing, and rides the installation_id super-property so it stitches to the rest of the desktop journey. * chore(ci): untrack node_modules symlink (broke pnpm install --frozen-lockfile)
…lls (Comfy-Org#1143) Amp-Thread-ID: https://ampcode.com/threads/T-019ed7b7-9733-752f-9709-e75510d0f380 Co-authored-by: Amp <amp@ampcode.com>
* Add Comfy Desktop bridge types package * Simplify bridge type generation * Move remote bridge flag into Desktop bridge * Fix bridge type entrypoint for NodeNext * fix: consume published desktop bridge types * fix: keep legacy remote marker for hosted frontend
* Add Comfy Desktop bridge types package * Simplify bridge type generation * Move remote bridge flag into Desktop bridge * Fix bridge type entrypoint for NodeNext * Expose ComfyUI telemetry bridge * Bound telemetry IPC payloads
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
…ess rate (Comfy-Org#1156) boot_started had no positive terminal, so server-boot success could only be inferred per-machine -- and boot_started fires ~5.8x/machine (retries, relaunches), so a per-launch rate was not computable at all. Add a per-launch boot_id (generated once, reused across port/reboot retries since tryLaunch recurses) stamped on boot_started, boot_failed, and a new comfy.desktop.comfyui.boot_completed emitted in main right after the session registers (server confirmed up). boot_completed carries installation_id, boot_id, boot_time_ms, and the retry counters. The boot-success rate becomes a clean per-attempt funnel: count(boot_completed.boot_id) / count(distinct boot_started.boot_id). boot_completed is a success/funnel event (PostHog only), deliberately not on the Datadog mirror list so it never pages on a healthy boot.
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
…fy-Org#1416) * feat(bridge-types): own and publish the frontend bridge contract Fold the frontend copy's additions (openModelAccessPage, doc comments) into src/types/comfyDesktopBridge.ts, the file the preload implements, and regenerate the package from it at 0.2.0. Both preloads now import that source directly instead of the stale npm build of themselves, so comfyPreload's hand-written intersection is gone and `satisfies ComfyDesktop2BridgeImplementation` binds the preload to the contract in the same commit that changes either. A CI check regenerates the package and fails on any diff, and merging a version bump publishes. * fix(bridge-types): gate contract changes on a version bump The publish workflow only fires on changes to the package's package.json, so a PR that edited the contract and regenerated it without bumping would pass bridge-types:check and merge, leaving npm on the old contract. CI now fails that PR. Also fixes the README example, which awaited openModelAccessPage outside a try and so skipped the fallback the doc comment promises on rejection. * fix(bridge-types): close the gaps between the gate and the publish The gate compared against pull_request.base.sha, which is the base tip as of the PR's last sync rather than the fork point, so main's later commits were attributed to the PR. Use the merge base, and widen the watched set to index.d.ts/index.js — both ship in the package's `files`, so either can change the published entrypoint without the old gate noticing. semver excludes prereleases from a `>x.y.z` range, so any prerelease bump failed the gate and the publish workflow's `next` dist-tag path was unreachable. Pass -p. Dropping to npx also removes a full dev-dependency install (with native rebuilds) from every PR. Publishing now fails instead of skipping when the version is already on npm and the run came from a merge, so a green publish job means it published; manual dispatch keeps the idempotent skip. Trigger on push to main rather than pull_request: closed, which passes no secrets for fork-authored PRs and needs a merge_commit_sha guard. Drop github.workflow from the concurrency group — inside a called workflow it resolves to the caller, so dispatch and on-merge runs of one version never serialised. README: isRemote() is required, not optional; add noopener to the fallback example; say that the version bump is what publishes. * fix(bridge-types): widen the sync gate to every published declaration bridge-types:gen prettier-formats every *.d.ts in the package and both are published, but the gate diffed only comfyDesktopBridge.d.ts, so a committed misformat of index.d.ts passed. format:check does not cover packages/, so nothing else caught it either. * fix(bridge-types): make isRemote optional and close the publish gaps isRemote landed 2026-06-15, three months after downloadModel, and replaced the window.__comfyDesktop2Remote global rather than adding new information. Builds from that window therefore expose downloadModel without isRemote -- but the contract declared isRemote required, so the frontend calls it unguarded behind a downloadModel check those very builds satisfy. It throws before every fallback branch, so a missing-model download silently dead-ends. Declaring it optional forces the guard and turns the throw into a fallback. Guardrails, so the same class of drift cannot recur: - The on-merge publish compares the published tarball's declarations instead of treating any version collision as a mismatch, which lets the trigger widen to the whole package without a description typo reddening main. - The version gate diffs the branch tip rather than the merge ref, globs the declarations rather than listing them, and covers the merge queue. Its version comparison is inline, so a registry outage cannot fail a PR. - bridge-types:check stages emitted declarations intent-to-add first, so a declaration that was never committed fails the check instead of passing it and shipping as a dangling reference. - The preload test derives its shape from the contract instead of restating it, and the files allowlist globs *.d.ts since tsc emits one per module. * fix(bridge-types): export the versions the gate's comparator reads BASE_VERSION and HEAD_VERSION were assigned but never exported, so the comparator saw process.env.* as undefined, threw, and the gate reported "version not bumped" while its own error text printed the correct versions it had just read. It could never pass, on this PR or any later contract change. The comparison logic itself is fine -- 11 precedence cases, including alpha.2 < alpha.10 and release-beats-prerelease, all agree. * fix(bridge-types): publish from trusted main workflow
…-Org#1460) * fix(mcp): await the flag fetch before the sidebar gate decides The sidebar injection read the flag synchronously at dom-ready, before the async PostHog fetch had populated the cache — so on a cold start (fresh or wiped install) getFlag returned undefined and the icon never injected, no matter how many launches. This is why it showed in dev but not the ToDesktop build, which loses the boot-fetch race every time. - attach.ts: gate on (the pattern every other flag consumer uses) so injection waits for the fetch to resolve, with an isDestroyed guard across the await. - experiments.ts: the boot fetch is fire-and-forget with getFlagAsync as its only awaiter, so the 1500ms cap wasn't protecting boot — it just discarded slow-but-successful fetches and left the cache unseeded. Raise it to a 10s hang-guard so a slow network still resolves and seeds the cache. * fix(review): retire the MCP sidebar inject when the attach is torn down `getFlagAsync` can resolve up to ~10s after the flag fetch, and detach leaves `comfyContents` alive, so the `isDestroyed()` check alone let a late resolution inject the sidebar into a detached or hot-swapped view. - Track attach lifetime with an `attachActive` flag, cleared in `_installCleanup`, and require it before injecting. - Extract the gate as a pure `shouldInjectMcpSidebar` so the truth table (incl. resolve-after-cleanup) is unit-tested deterministically. * docs(attach): tighten the MCP inject-gate comments Condense the shouldInjectMcpSidebar JSDoc and the attachActive note to their load-bearing lines; drop the redundant restatement in the test.
…#1461) * fix(install): keep progress loader clear of the footer, re-aim beams The centred loader stack (scene + bar + stepper that hangs below it) was sized against the full viewport, so on a fresh install its stepper text landed under the footer's Return-to-Dashboard button. - Reserve the footer band as padding-bottom on the hero so the stack centres in the space above it. - The beams anchor to the scene box, so lifting it moved them; thread a beamLift var (ProgressModal -> BrandTakeoverLayout -> BrandBackground) to nudge the beam tops back onto the scene. Defaults to 0px, so every other surface is untouched. * refactor(install): aim beams at a fixed screen-centre point The beams anchored to the loader's scene box, so lifting the loader to clear the footer dragged the spotlights off with it. Anchor them instead to a zero-size element pinned at the hero's centre — the old behaviour where they pointed at the centred wordmark — so they stay put no matter where the loader stack sits. Drops the beamLift prop threaded through BrandTakeoverLayout and BrandBackground; both are back to their original state. * refactor(install): drop the beam-anchor comments The class name and aria-hidden already say what the anchor is; the CSS is self-documenting. Removes the three comments flagged in review.
…erlay flicker (Comfy-Org#1462) * fix(install): fade each scene clip in once its frame is decoded Clips pinned off frame 0 seek away from the preloaded frame, so revealing synchronously showed the mask ground as a black flash. Reveal a clip only once a decoded frame exists (requestVideoFrameCallback, with a readyState-gated fallback) and fade it up from a neutral ground over the unavoidable decode gap. * feat(mcp): warm the setup film into cache before it's opened The MCP setup modal streams a ~5 MB remote video; opened cold it showed black while it loaded. Extract useAssetPrefetch (the idle/busy/network queue shared with thumbnail warming) and add useMediaPrefetch, which warms via a detached <video> — a cross-origin fetch() is CSP-blocked here, but a media-element load fills the same cache the modal reuses. * fix(mcp): open the overlay panel without a flicker or cold-load delay The panel view was destroyed on attach and rebuilt cold on the first MCP/feedback click, and setActivePanel showed it before the renderer had painted the modal — an opaque frame flashed over the canvas as open/close/ open. Rebuild the panel warm right after attach, and hold it hidden until the renderer acks it has painted (overlay-ready), with a timeout fallback so it can never strand hidden. * fix(review): guard scene re-watch and stale overlay timer; trim comments - useBrandScene: skip clips already being watched so a loop re-activation can't stack a second set of fallback listeners on the same <video>. - panelView: track the overlay reveal-fallback timer on the entry and clear it on the next switch / ack, so a stale one can't reveal a later open early. - Cut comments to one or two lines across the changed files; drop the CSS comment; assert the warm element is muted + preload=auto. * fix(review): address CodeRabbit findings on the loader/MCP fixes - useAssetPrefetch: init `cancel` to a no-op so a loader that settles synchronously can't hit it in its TDZ and wedge the warm queue. - useBrandScene: re-arm the paint gate per activation via a per-clip token, so a loop re-seek waits for its own decoded frame and a late callback from the prior activation can't paint the current seek. Hoisted markPaintedWhenReady to a pure, testable module function. - index: reset a picker-progress chooser host to 'comfy' before the panel prewarm, so computeBodyMode keeps the rebuilt panel hidden instead of stranding a progress surface over the attached canvas. - Tests: synchronous-settle drain, paint-gate re-arm + stale-callback, and the progress→comfy prewarm reset. * refactor(install): extract prewarmAttachedPanel, guard its ordering CodeRabbit: the prewarm test asserted a bare setActivePanel transition, so it stayed green if index.ts dropped or reordered the reset. - Extract the reset→ensure→layout sequence into prewarmAttachedPanel, so the "reset before ensurePanelView" ordering lives in one named place with the rationale on the definition, not the call site. - Point the test at the helper; it now fails if the reset is removed. - Trim the comment block down to the load-bearing lines.
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
…PR (Comfy-Org#1464) After a tag build, resolve the version-bump PR from the tagged commit (commits/{sha}/pulls, with a (#N) subject fallback) and upsert a comment with the ToDesktop build page and per-platform download links. Idempotent via a hidden marker; runs on build success or failure.
…laceholder visual) (Comfy-Org#1467) * feat(announcement): MiniMax license announcement modal (copy + CTAs, placeholder visual) Reuses the WhyTryCloud modal layout for a one-off MiniMax license announcement. Left media panel is a placeholder; copy is draft. Two CTAs (Request License -> comfy.org/minimax/license, Learn More -> blog) and show/cta/dismiss telemetry. Trigger + gating wiring left to a follow-up. * feat(announcement): open MiniMax announcement from a title-bar news bell Replace the auto-on-launch modal with a subtle title-bar bell that carries an unread dot until opened, then opens the announcement over the live app via a new 'announcement' overlay panel mode (mirrors the Send Feedback path). Swap the placeholder visual for the MiniMax license hero video with a mute/unmute toggle. Unread state persists via minimaxAnnouncementSeen and clears through the settings-changed broadcast. * fix(announcement): refer to the model as MiniMax H3 * chore(announcement): remove em dashes from comments * feat(announcement): finalize MiniMax H3 copy with inline local-license link Apply Tiger's launch copy (lead + two bullets), replace the CTA buttons with a commercial-license note whose inline 'here' link opens the local-license reach-out (comfy.org/contact, interim) tagged with a Desktop-origin UTM. * feat(announcement): restore Learn More + Request License CTAs, add free-for-personal note Keep the two-button footer (Learn More, Request License) with Desktop-origin UTMs, and add a 'Free for personal use' note alongside them.
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
…odal (Comfy-Org#1469) Follow-up to the merged announcement modal. Add a third highlight ("Want to use it commercially? Reach out to get a license") and drop the separate personal-use footer note, leaving the Learn More + Request License CTAs.
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
…1471) * feat(updater): correlate update telemetry across launches * fix(updater): make telemetry correlation durable Amp-Thread-ID: https://ampcode.com/threads/T-01a04ac7-64c3-778d-b10f-af2ddc4a1840 Co-authored-by: Amp <amp@ampcode.com> * fix(updater): persist installed outcome before cleanup Amp-Thread-ID: https://ampcode.com/threads/T-01a04ac7-64c3-778d-b10f-af2ddc4a1840 Co-authored-by: Amp <amp@ampcode.com> * fix(updater): trust sidecar attempt identity Amp-Thread-ID: https://ampcode.com/threads/T-01a04ac7-64c3-778d-b10f-af2ddc4a1840 Co-authored-by: Amp <amp@ampcode.com> * fix(updater): unify fallback telemetry field Amp-Thread-ID: https://ampcode.com/threads/T-01a04ac7-64c3-778d-b10f-af2ddc4a1840 Co-authored-by: Amp <amp@ampcode.com> --------- Co-authored-by: Jedrzej Kosinski <kosinkadink1@gmail.com> Co-authored-by: Amp <amp@ampcode.com>
… improvements (Comfy-Org#1441) * feat: add workspace dashboard controls Amp-Thread-ID: https://ampcode.com/threads/T-01a021bb-eeb2-7359-b259-6bb1b757bdd6 Co-authored-by: Amp <amp@ampcode.com> * fix: simplify workspace build card Amp-Thread-ID: https://ampcode.com/threads/T-01a021bb-eeb2-7359-b259-6bb1b757bdd6 Co-authored-by: Amp <amp@ampcode.com> * feat(comfybuilder): the client calls the build API and the copy says Build The comfy-builder API renamed its public vocabulary from distribution to build: /v1/distributions -> /v1/builds, /v1/distributions/{id}/versions -> /v1/builds/{id}/versions, /v1/distribution-versions/{id}(/manifest) -> /v1/build-versions/{id}(/manifest); /v1/build-artifacts/{id}/download is unchanged. The list envelope field distributions is now builds. Moves the hand-written client to the new paths and envelope field, points the URL-asserting unit tests at the new URLs (they fail against the old client), and renames the user-facing copy in en/zh locales plus the three hardcoded strings. i18n keys, IPC channel names, type names, testids and the persisted installations.json fields are deliberately untouched. * fix: use Build API contract Amp-Thread-ID: https://ampcode.com/threads/T-01a021bb-eeb2-7359-b259-6bb1b757bdd6 Co-authored-by: Amp <amp@ampcode.com> * fix: create promoted Builds through Builder Amp-Thread-ID: https://ampcode.com/threads/T-01a021bb-eeb2-7359-b259-6bb1b757bdd6 Co-authored-by: Amp <amp@ampcode.com> * fix: open promoted Build drafts directly Amp-Thread-ID: https://ampcode.com/threads/T-01a021bb-eeb2-7359-b259-6bb1b757bdd6 Co-authored-by: Amp <amp@ampcode.com> * fix: use deployed Builder routes Amp-Thread-ID: https://ampcode.com/threads/T-01a021bb-eeb2-7359-b259-6bb1b757bdd6 Co-authored-by: Amp <amp@ampcode.com> * fix: reorder desktop card actions Amp-Thread-ID: https://ampcode.com/threads/T-01a021bb-eeb2-7359-b259-6bb1b757bdd6 Co-authored-by: Amp <amp@ampcode.com> * fix: shorten workspace creation card text Amp-Thread-ID: https://ampcode.com/threads/T-01a021bb-eeb2-7359-b259-6bb1b757bdd6 Co-authored-by: Amp <amp@ampcode.com> * fix: use deployed Builder web route Amp-Thread-ID: https://ampcode.com/threads/T-01a021bb-eeb2-7359-b259-6bb1b757bdd6 Co-authored-by: Amp <amp@ampcode.com> * feat: show workspace promotion progress Amp-Thread-ID: https://ampcode.com/threads/T-01a021bb-eeb2-7359-b259-6bb1b757bdd6 Co-authored-by: Amp <amp@ampcode.com> * fix: load all workspace builds Amp-Thread-ID: https://ampcode.com/threads/T-01a021bb-eeb2-7359-b259-6bb1b757bdd6 Co-authored-by: Amp <amp@ampcode.com> * fix: punctuate workspace build card Amp-Thread-ID: https://ampcode.com/threads/T-01a021bb-eeb2-7359-b259-6bb1b757bdd6 Co-authored-by: Amp <amp@ampcode.com> * fix: rename snapshot export action Amp-Thread-ID: https://ampcode.com/threads/T-01a021bb-eeb2-7359-b259-6bb1b757bdd6 Co-authored-by: Amp <amp@ampcode.com> * fix: remove Forget from instance context menu Amp-Thread-ID: https://ampcode.com/threads/T-01a021bb-eeb2-7359-b259-6bb1b757bdd6 Co-authored-by: Amp <amp@ampcode.com> * fix: refine workspace dashboard controls Amp-Thread-ID: https://ampcode.com/threads/T-01a021bb-eeb2-7359-b259-6bb1b757bdd6 Co-authored-by: Amp <amp@ampcode.com> * feat: filter workspace builds by compatibility Amp-Thread-ID: https://ampcode.com/threads/T-01a021bb-eeb2-7359-b259-6bb1b757bdd6 Co-authored-by: Amp <amp@ampcode.com> * fix: compact workspace filter text Amp-Thread-ID: https://ampcode.com/threads/T-01a021bb-eeb2-7359-b259-6bb1b757bdd6 Co-authored-by: Amp <amp@ampcode.com> * fix: shorten workspace build action Amp-Thread-ID: https://ampcode.com/threads/T-01a021bb-eeb2-7359-b259-6bb1b757bdd6 Co-authored-by: Amp <amp@ampcode.com> * fix: enable managed instance duplication Amp-Thread-ID: https://ampcode.com/threads/T-01a021bb-eeb2-7359-b259-6bb1b757bdd6 Co-authored-by: Amp <amp@ampcode.com> * fix: configure managed Build terminals Amp-Thread-ID: https://ampcode.com/threads/T-01a021bb-eeb2-7359-b259-6bb1b757bdd6 Co-authored-by: Amp <amp@ampcode.com> * fix: widen workspace compatibility filter Amp-Thread-ID: https://ampcode.com/threads/T-01a021bb-eeb2-7359-b259-6bb1b757bdd6 Co-authored-by: Amp <amp@ampcode.com> * fix: release managed Build model locks Amp-Thread-ID: https://ampcode.com/threads/T-01a021bb-eeb2-7359-b259-6bb1b757bdd6 Co-authored-by: Amp <amp@ampcode.com> * fix: keep managed Builds visible while updating Amp-Thread-ID: https://ampcode.com/threads/T-01a021bb-eeb2-7359-b259-6bb1b757bdd6 Co-authored-by: Amp <amp@ampcode.com> * fix: simplify new instance heading Amp-Thread-ID: https://ampcode.com/threads/T-01a021bb-eeb2-7359-b259-6bb1b757bdd6 Co-authored-by: Amp <amp@ampcode.com> * fix: shorten instance search prompt Amp-Thread-ID: https://ampcode.com/threads/T-01a021bb-eeb2-7359-b259-6bb1b757bdd6 Co-authored-by: Amp <amp@ampcode.com> * fix: preserve managed Build data during updates Amp-Thread-ID: https://ampcode.com/threads/T-01a021bb-eeb2-7359-b259-6bb1b757bdd6 Co-authored-by: Amp <amp@ampcode.com> * fix: make workspace selector opaque Amp-Thread-ID: https://ampcode.com/threads/T-01a021bb-eeb2-7359-b259-6bb1b757bdd6 Co-authored-by: Amp <amp@ampcode.com> * fix: keep workspace selector trigger translucent Amp-Thread-ID: https://ampcode.com/threads/T-01a021bb-eeb2-7359-b259-6bb1b757bdd6 Co-authored-by: Amp <amp@ampcode.com> * feat: scope dashboard installs by workspace Amp-Thread-ID: https://ampcode.com/threads/T-01a021bb-eeb2-7359-b259-6bb1b757bdd6 Co-authored-by: Amp <amp@ampcode.com> * fix: keep progress footer clear of install steps Amp-Thread-ID: https://ampcode.com/threads/T-01a021bb-eeb2-7359-b259-6bb1b757bdd6 Co-authored-by: Amp <amp@ampcode.com> * fix: identify invalid Build model hashes Amp-Thread-ID: https://ampcode.com/threads/T-01a021bb-eeb2-7359-b259-6bb1b757bdd6 Co-authored-by: Amp <amp@ampcode.com> * fix: center search above workspace controls Amp-Thread-ID: https://ampcode.com/threads/T-01a021bb-eeb2-7359-b259-6bb1b757bdd6 Co-authored-by: Amp <amp@ampcode.com> * fix: avoid repeated workspace authentication Amp-Thread-ID: https://ampcode.com/threads/T-01a021bb-eeb2-7359-b259-6bb1b757bdd6 Co-authored-by: Amp <amp@ampcode.com> * fix: support team workspace Build routes Amp-Thread-ID: https://ampcode.com/threads/T-01a021bb-eeb2-7359-b259-6bb1b757bdd6 Co-authored-by: Amp <amp@ampcode.com> * fix: use Builder release endpoints Amp-Thread-ID: https://ampcode.com/threads/T-01a021bb-eeb2-7359-b259-6bb1b757bdd6 Co-authored-by: Amp <amp@ampcode.com> * fix: space new instance heading * fix: use Build draft browser route * fix: use sentence case for No workspace * fix: use neutral No workspace avatar * fix: restore new instance subtitle * fix: tailor new instance subtitles * fix: show workspace type in selector * fix: left align dashboard instances Amp-Thread-ID: https://ampcode.com/threads/T-01a021bb-eeb2-7359-b259-6bb1b757bdd6 Co-authored-by: Amp <amp@ampcode.com> * fix: hide Personal workspace subtitle Amp-Thread-ID: https://ampcode.com/threads/T-01a021bb-eeb2-7359-b259-6bb1b757bdd6 Co-authored-by: Amp <amp@ampcode.com> * fix: clarify local install subtitle Amp-Thread-ID: https://ampcode.com/threads/T-01a021bb-eeb2-7359-b259-6bb1b757bdd6 Co-authored-by: Amp <amp@ampcode.com> * fix: reuse New Instance subtitle Amp-Thread-ID: https://ampcode.com/threads/T-01a021bb-eeb2-7359-b259-6bb1b757bdd6 Co-authored-by: Amp <amp@ampcode.com> * feat: improve workspace Build picker Amp-Thread-ID: https://ampcode.com/threads/T-01a021bb-eeb2-7359-b259-6bb1b757bdd6 Co-authored-by: Amp <amp@ampcode.com> * fix: label Build catalog refreshes Amp-Thread-ID: https://ampcode.com/threads/T-01a021bb-eeb2-7359-b259-6bb1b757bdd6 Co-authored-by: Amp <amp@ampcode.com> * fix: preserve workspace identity during startup Amp-Thread-ID: https://ampcode.com/threads/T-01a021bb-eeb2-7359-b259-6bb1b757bdd6 Co-authored-by: Amp <amp@ampcode.com> * feat: add public install option to workspaces Amp-Thread-ID: https://ampcode.com/threads/T-01a021bb-eeb2-7359-b259-6bb1b757bdd6 Co-authored-by: Amp <amp@ampcode.com> * fix: allow uninstalling failed managed builds Amp-Thread-ID: https://ampcode.com/threads/T-01a021bb-eeb2-7359-b259-6bb1b757bdd6 Co-authored-by: Amp <amp@ampcode.com> * feat: select managed Build release targets Amp-Thread-ID: https://ampcode.com/threads/T-01a021bb-eeb2-7359-b259-6bb1b757bdd6 Co-authored-by: Amp <amp@ampcode.com> * fix: clarify Build browser handoff Amp-Thread-ID: https://ampcode.com/threads/T-01a021bb-eeb2-7359-b259-6bb1b757bdd6 Co-authored-by: Amp <amp@ampcode.com> * fix: move GPU details below install options Amp-Thread-ID: https://ampcode.com/threads/T-01a021bb-eeb2-7359-b259-6bb1b757bdd6 Co-authored-by: Amp <amp@ampcode.com> * fix: show managed Build targets directly Amp-Thread-ID: https://ampcode.com/threads/T-01a021bb-eeb2-7359-b259-6bb1b757bdd6 Co-authored-by: Amp <amp@ampcode.com> Amp-Thread-ID: https://ampcode.com/threads/T-01a021bb-eeb2-7359-b259-6bb1b757bdd6 * fix: simplify managed Build selection Amp-Thread-ID: https://ampcode.com/threads/T-01a021bb-eeb2-7359-b259-6bb1b757bdd6 Co-authored-by: Amp <amp@ampcode.com> Amp-Thread-ID: https://ampcode.com/threads/T-01a021bb-eeb2-7359-b259-6bb1b757bdd6 * fix: allow repeated managed Build installs Amp-Thread-ID: https://ampcode.com/threads/T-01a021bb-eeb2-7359-b259-6bb1b757bdd6 Co-authored-by: Amp <amp@ampcode.com> * fix: reset managed Build size display Amp-Thread-ID: https://ampcode.com/threads/T-01a021bb-eeb2-7359-b259-6bb1b757bdd6 Co-authored-by: Amp <amp@ampcode.com> * fix: sign out without confirmation Amp-Thread-ID: https://ampcode.com/threads/T-01a021bb-eeb2-7359-b259-6bb1b757bdd6 Co-authored-by: Amp <amp@ampcode.com> * fix: allow managed models without hashes Amp-Thread-ID: https://ampcode.com/threads/T-01a021bb-eeb2-7359-b259-6bb1b757bdd6 Co-authored-by: Amp <amp@ampcode.com> * fix: improve OIDC completion page Amp-Thread-ID: https://ampcode.com/threads/T-01a021bb-eeb2-7359-b259-6bb1b757bdd6 Co-authored-by: Amp <amp@ampcode.com> * fix: stabilize local Git version queries Amp-Thread-ID: https://ampcode.com/threads/T-01a021bb-eeb2-7359-b259-6bb1b757bdd6 Co-authored-by: Amp <amp@ampcode.com> * fix: use Comfy Desktop callback branding Amp-Thread-ID: https://ampcode.com/threads/T-01a021bb-eeb2-7359-b259-6bb1b757bdd6 Co-authored-by: Amp <amp@ampcode.com> * fix: clarify completed authorization message Amp-Thread-ID: https://ampcode.com/threads/T-01a021bb-eeb2-7359-b259-6bb1b757bdd6 Co-authored-by: Amp <amp@ampcode.com> * Fix template picker leaking into managed installs and flaky GPU detection at install time The wizard's template picker step was gated only on the standalone source, so visiting the Public tab (which auto-selects standalone and loads template options) and returning to Managed left stale state that surfaced the picker before a managed install. Gate the picker on local-install mode and reset all source-scoped state when the Managed/Public mode toggles. Install-time artifact resolution re-ran GPU detection from scratch, so a transient WMI failure mapped the host to the CPU fallback and produced 'No installable build for this machine' on NVIDIA-only builds. Share one memoized detection result (detectGPUCached) across field options, system info, detect-gpu, and resolveHost; a null result is not cached so a failed probe retries instead of poisoning the session. Amp-Thread-ID: https://ampcode.com/threads/T-01a045a7-fa78-70bb-9e24-1216ae381c53 Co-authored-by: Amp <amp@ampcode.com> * Share install record allocation and add managed disk preflight Extract allocateInstallIdentity (unique name, directory allocation, duplicate-path check) into installIdentity.ts and use it from both the generic add-installation handler and the managed installBuild flow, which previously restated the same logic. The managed flow keeps its workspace-changed re-check between allocation and record creation. Hoist the ComfyBuilder launch defaults (launchArgs/launchMode/ browserPartition) into COMFYBUILDER_INSTALL_DEFAULTS in comfybuilder/constants.ts so the install handler and the source plugin share one definition instead of restating the values. Managed installs now get the same soft disk-space warning as local installs: the wizard estimates the install size from the build catalog's sizeBytes (same 2.25x download-to-installed factor) and shows it in PathDiskInfo. Amp-Thread-ID: https://ampcode.com/threads/T-01a045a7-fa78-70bb-9e24-1216ae381c53 Co-authored-by: Amp <amp@ampcode.com> * Launch managed installs without waiting for model downloads The environment install (archive download + extract) is now the only thing that gates a build install; the build's declared models download in the background through the managed Downloads tray, the same way standalone installs fetch starter-template models. - New modelStagingTask owns the background download: model-root lock, parked-job release, abort, and recording completion as modelsStaged on the install record. - installEnvironment resolves the model manifest inside the transaction (a build whose model list cannot be fetched still fails and rolls back) but returns the models for the caller to stage in the background. - A launch of a build whose staging never finished (crash, cancel, failure, or a record from before the flag) silently re-stages; already-downloaded models are skipped by hash. - Version updates stop a running staging before the filesystem swap and re-stage the new version's models after it. - Install cancel/failure and host-window teardown abort the staging task alongside the existing template-download teardown. Amp-Thread-ID: https://ampcode.com/threads/T-01a045a7-fa78-70bb-9e24-1216ae381c53 Co-authored-by: Amp <amp@ampcode.com> * Keep the main process responsive during boot Startup work froze every window for seconds on machines with many installs or large model roots: - The staged-download scan walked overlapping roots repeatedly and processed large Dirent batches without yielding. It now prunes roots covered by another root, finds both sidecar suffixes in one walk, and yields to the event loop every 500 entries. - The background version pass spawned an unbounded burst of pygit2 subprocesses; on Windows each spawn blocks the main thread in CreateProcess. The pass is now capped to one spawn at a time. - get-system-info re-ran nvidia-smi and several WMI/PowerShell probes on every call; the hardware probe is now memoized single-flight while per-call fields (installs, settings, versions) stay fresh. - getAppVersion ran a synchronous git describe on every call in unpackaged builds; the result is now memoized. Amp-Thread-ID: https://ampcode.com/threads/T-01a045a7-fa78-70bb-9e24-1216ae381c53 Co-authored-by: Amp <amp@ampcode.com> * Improve workspace build and instance flows Amp-Thread-ID: https://ampcode.com/threads/T-01a04619-d056-765e-8e82-80ad2ba8a336 Co-authored-by: Amp <amp@ampcode.com> * Show the Updating state on dashboard tiles for standalone updates A managed update flips its record to status 'installing', so the dashboard tile shows the Updating pill; a standalone update only streamed progress to the window that started it and the tile showed nothing. Main now broadcasts 'operation-changed' (with a 'get-active-operations' hydration snapshot) around every dispatched action, sessionStore mirrors it per install, and the tile keys its Updating state on both signals. The registry is in-memory only, so a crash mid-operation can never leave a stale busy state. 'check-update' is now classified as a generic op so a silent version check cannot paint update UI. Amp-Thread-ID: https://ampcode.com/threads/T-01a045a7-fa78-70bb-9e24-1216ae381c53 Co-authored-by: Amp <amp@ampcode.com> * Split status 'updating' out of 'installing' for in-place updates Fresh installs and in-place managed updates shared status 'installing', disambiguated by the rollback payload: fresh installs are hidden from the renderer while updates stay visible and render as Updating. The update flow now writes status 'updating'; a load-time migration rewrites legacy mid-update records, so visibility and the dashboard tile read status alone and showing tiles during a first install can no longer mislabel them. Amp-Thread-ID: https://ampcode.com/threads/T-01a045a7-fa78-70bb-9e24-1216ae381c53 Co-authored-by: Amp <amp@ampcode.com> * Remove dismiss error instance action Amp-Thread-ID: https://ampcode.com/threads/T-01a049b4-add2-77ff-a064-4b4a0db9f86b Co-authored-by: Amp <amp@ampcode.com> * Raise timeout on updater capability tests that re-import the module graph Amp-Thread-ID: https://ampcode.com/threads/T-01a045a7-fa78-70bb-9e24-1216ae381c53 Co-authored-by: Amp <amp@ampcode.com> * Update lifecycle specs for always-open Advanced body and drawer-based Untrack Amp-Thread-ID: https://ampcode.com/threads/T-01a045a7-fa78-70bb-9e24-1216ae381c53 Co-authored-by: Amp <amp@ampcode.com> * Update lifecycle wizard selector for renamed version label Amp-Thread-ID: https://ampcode.com/threads/T-01a045a7-fa78-70bb-9e24-1216ae381c53 Co-authored-by: Amp <amp@ampcode.com> * Retry cleanup deletes in lifecycle specs to absorb transient Windows file locks Amp-Thread-ID: https://ampcode.com/threads/T-01a045a7-fa78-70bb-9e24-1216ae381c53 Co-authored-by: Amp <amp@ampcode.com> * fix: guard sign-out during instance installation Amp-Thread-ID: https://ampcode.com/threads/T-01a049b4-add2-77ff-a064-4b4a0db9f86b Co-authored-by: Amp <amp@ampcode.com> * fix: never let a wedged shell.openExternal strand the sign-in flow * Address review: guard repeated build cursors, explicit HTTP method, shared popover dismissal --------- Co-authored-by: Amp <amp@ampcode.com> Co-authored-by: James Kwon <hongilkwon316@gmail.com> Co-authored-by: Alexis Rolland <alexisrolland@hotmail.com>
* docs: require PR change breakdowns Amp-Thread-ID: https://ampcode.com/threads/T-019ffdd6-1ee5-7066-adc9-19c03ede90dd Co-authored-by: Amp <amp@ampcode.com> * docs: clarify PR change accounting Amp-Thread-ID: https://ampcode.com/threads/T-019ffdd6-1ee5-7066-adc9-19c03ede90dd Co-authored-by: Amp <amp@ampcode.com> --------- Co-authored-by: Amp <amp@ampcode.com>
… downloading (Comfy-Org#1473) * Fix startup update loop when the staged installer is corrupt or still downloading On Windows, a staged update whose cached installer failed validation put boots into an infinite loop: the readiness check raced the re-download electron-updater kicks off and always timed out; the not_ready skip never armed the loop breaker; and the skip path destroyed the update splash before any other window existed, which fired window-all-closed and quit the app, killing the in-flight re-download at a partial size and re-arming the invalid cache for the next boot. - Resolve the bounded startup wait as soon as the outcome is knowable: a download-progress tick means the cached installer was rejected and a re-download started (bail into the app; the download continues in the background), and a failed/empty check means ready is unreachable this launch. - Bound repeated not_ready skips of the same staged version with a 3-strike counter that abandons the stale marker, without clobbering a marker restaged during the wait. - Open the normal UI before destroying the update splash so the app never self-quits through window-all-closed while a background re-download is in flight. - Show the splash with checking copy first and only swap to the install countdown once the install is committed, holding from that point so the countdown plays out fully and never shows for a skipped install. Refs Comfy-Org#1367 Refs Comfy-Org#1472 Amp-Thread-ID: https://ampcode.com/threads/T-01a05158-0efc-72ab-87a6-5f78ef0be040 Co-authored-by: Amp <amp@ampcode.com> * Keep the update splash up if the startup surface fails to open Destroying the splash while it is the only window fires window-all-closed and quits the app, which would also kill any in-flight background re-download. If opening the normal UI fails before another window exists, keep the splash instead of exiting silently. Amp-Thread-ID: https://ampcode.com/threads/T-01a05158-0efc-72ab-87a6-5f78ef0be040 Co-authored-by: Amp <amp@ampcode.com> --------- Co-authored-by: Amp <amp@ampcode.com>
…#1474) * Fix startup update loop when the staged installer is corrupt or still downloading On Windows, a staged update whose cached installer failed validation put boots into an infinite loop: the readiness check raced the re-download electron-updater kicks off and always timed out; the not_ready skip never armed the loop breaker; and the skip path destroyed the update splash before any other window existed, which fired window-all-closed and quit the app, killing the in-flight re-download at a partial size and re-arming the invalid cache for the next boot. - Resolve the bounded startup wait as soon as the outcome is knowable: a download-progress tick means the cached installer was rejected and a re-download started (bail into the app; the download continues in the background), and a failed/empty check means ready is unreachable this launch. - Bound repeated not_ready skips of the same staged version with a 3-strike counter that abandons the stale marker, without clobbering a marker restaged during the wait. - Open the normal UI before destroying the update splash so the app never self-quits through window-all-closed while a background re-download is in flight. - Show the splash with checking copy first and only swap to the install countdown once the install is committed, holding from that point so the countdown plays out fully and never shows for a skipped install. Refs Comfy-Org#1367 Refs Comfy-Org#1472 Amp-Thread-ID: https://ampcode.com/threads/T-01a05158-0efc-72ab-87a6-5f78ef0be040 Co-authored-by: Amp <amp@ampcode.com> * Keep the update splash up if the startup surface fails to open Destroying the splash while it is the only window fires window-all-closed and quits the app, which would also kill any in-flight background re-download. If opening the normal UI fails before another window exists, keep the splash instead of exiting silently. Amp-Thread-ID: https://ampcode.com/threads/T-01a05158-0efc-72ab-87a6-5f78ef0be040 Co-authored-by: Amp <amp@ampcode.com> * Show download progress for auto-installed update downloads Background downloads started by the auto-install setting kept the cached app-update state null until the download finished, so the title-bar pill stayed hidden and Desktop Settings claimed the app was up to date while hundreds of megabytes were in flight. Pressing check for updates gave no feedback beyond a spinner. The display pipeline already existed end to end (progress IPC, Settings progress bar with percent/bytes/speed, pill downloading label) but was only wired up for user-initiated downloads. The first download-progress tick of any download now flips the state to downloading, using the version recorded by the auto-download trigger when the available state was skipped. A failed auto-on download returns to the silent idle state so background network blips stay quiet; a failed user-initiated download still rolls back to available for a manual retry. Amp-Thread-ID: https://ampcode.com/threads/T-01a05158-0efc-72ab-87a6-5f78ef0be040 Co-authored-by: Amp <amp@ampcode.com> --------- Co-authored-by: Amp <amp@ampcode.com> Co-authored-by: Alexis Rolland <alexisrolland@hotmail.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
…fy-Org#1481) * Add native Windows ARM64 (RTX Spark) support to the install path Phase 2 of the RTX Spark plan (BE-11059): teach Desktop about the machine architecture so a native ARM64 build installs the win-nvidia-arm64 bundle and ships native ARM64 runtime pieces. - variantMatchesHostArch gates both wizard filters: a native ARM64 app sees only -arm64 bundles, an x64 app (also under Prism emulation) never does. - Native ARM64 bundles get no index-served torch stacks (no trusted index publishes win_arm64 wheels); variantAccel makes the accel base explicit. - afterPack downloads vc_redist.arm64.exe for arm64 packs and installer.nsh selects the redist and registry key via IsNativeARM64, embedding only the redist for architectures the installer carries. - Native win-arm64 bootstrap Python: build/fetch scripts, ToDesktop beforeBuild hook, targetOverrides in todesktop.json, ${arch} in electron-builder.yml, bootstrap-v3 default tag, windows-11-arm CI job. - ToDesktop CLI 1.22.0 -> 1.28.1, the first schema with targetOverrides. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * Accept beta- vendor ids so pre-release bundles reach only this build The environments repo publishes the Windows ARM64 bundle as beta-win-nvidia-arm64 (ComfyUI-Standalone-Environments#22): shipped desktops list only win-/mac-/linux- ids, so the beta- prefix keeps the ARM64 card away from x64 users who lack the architecture filter. This build reads through it. - stripPlatform, and the renderer's copies in variants.ts and telemetry.ts, drop an optional beta- prefix ahead of the platform prefix. - variantMatchesHost combines the platform check (beta- allowed only directly in front of the host prefix) with variantMatchesHostArch; both wizard filters use it. - getVariantLabel appends Beta, so the card reads NVIDIA (ARM64) Beta. - The snapshot-import handler uses the shared PLATFORM_PREFIX and stripPlatform instead of its own copy, so a snapshot taken on a beta bundle still maps to its accelerator. --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
…omfy-Org#1479) * feat(templates): let content own the starter-template list Desktop reads the picker's list from R2 at boot, so it can change without an app release. Nobody outside engineering could change it, because the file lived nowhere and there was no safe way to edit it. Adds the file, a script, a skill and a CI gate: - `assets/starter-templates.json` — the list, generated from the built-in one, so publishing it is a no-op today. - `scripts/starter-templates.mjs` — list, set, regenerate, validate. Editors supply template ids only; titles, descriptions, sizes and thumbnails come from the live index, so a description cannot drift from the template it describes. - `.claude/skills/starter-templates/` — the skill, plus setup for someone starting cold: Node 22 and `gh auth login`, no build. - CI validates the file and re-runs generation to prove nothing was hand-edited. The rules the script refuses to break are the ones that fail silently in the app: four cards per tab, one free recommended pick per tab, at most one paid template per tab. A short tab quietly backfills and a paid auto-pick spends the user's credits on first run, so neither surfaces as an error. Publishing to R2 on merge still needs a bucket credential and is not wired here. * feat(templates): add a whole-list replace for seasonal refreshes A one-slot `set` is the common case, but swapping most of the list meant four separate calls. `replace` takes four ids per tab, with `*` for the auto-pick and `$` for the paid card, and derives everything else. Same validation, so a wrong count or an unknown id still fails before writing. * fix(templates): require exactly one paid card per tab, and size every free one QA of the authoring script found two states it would have accepted, both of which are silent in the app rather than visible errors. A tab with zero paid templates passed, because the rule was written as "at most one". Every tab is meant to showcase exactly one API-node template, so the check now requires exactly one. A free template reporting zero bytes also passed, which makes the install-time disk-space check under-count and can leave a user out of space mid-download. Free cards must now report a real size, and paid cards must report zero since they download nothing. Adds `starter-templates.spec.mjs`, 36 cases covering every way an edit can break a tab: wrong counts, missing or duplicated recommended and paid picks, a paid auto-pick, cross-tab duplicate ids, ids that are not upstream or that escape a path, and that a refused command leaves the file untouched. CI runs it. * feat(templates): publish the list to R2 and the mirror on merge Merging to main now uploads `starter-templates.json` to R2 and to the GCS mirror, then reads both back and fails if either is missing or serving something different. Desktop reads the file at boot, so a merged change reaches users on their next launch with no app release. Both hosts, not just R2: regions where R2's edge is throttled read the mirror, and a list that exists on only one of them is invisible to half the users it was written for. This also closes the manual mirror copy that has been outstanding. Gated on the `starter-templates` environment and on validation passing, so a broken list cannot reach users. Needs three secrets before it can run: CLOUDFLARE_ACCOUNT_ID, R2_STARTER_TEMPLATES_TOKEN, GCS_MIRROR_SA_KEY. * fix(templates): address review on the authoring workflow CI was failing: the guardrail suite ran under vitest, which needs a dependency install the job never did. It now runs on `node --test`, so it needs nothing installed and starts in a second. The no-drift check failed the job whenever upstream edited a description, which would redden every open PR and send the author hunting a hand-edit that did not exist. It warns now, and says upstream is the likely cause. Three script fixes, each a state the tool would have written: - an id from another tab was accepted, so an image template could land in the audio tab carrying its own title, and the picker groups by the stored modality - `a,b,,d` passed as four ids, writing a different list than the one asked for - an unmarked `api_` id in `replace` was written as free, which then failed the zero-size rule for the wrong reason Also drops the "can never drift" claim from the skill: fields refresh when the commands run, so upstream can move ahead until `regenerate` is next run. Guardrail suite is 39 cases now. Mutation-checked the modality cross-check and the api_ default; the empty-slot guard is a clearer error rather than a new rejection, since `entryFor` already refuses an empty id, and the test says so. * ci(templates): add a dry run so credentials can be proven before merge Publishing only ran on merge to main, so a wrong secret would surface on the merge that ships to users. `workflow_dispatch` with `dry_run` (default true) authenticates to both hosts and does a real write-then-delete under a throwaway key, proving the R2 token has write access without touching the file the app reads. Unchecking dry_run publishes for real. * fix(ci): drop --remote, which wrangler 3 does not accept Wrangler 3 writes to remote R2 by default; --remote only exists in 4. The credential dry run failed on 'Unknown argument: remote' before it ever authenticated. * ci(templates): diagnose both hosts in the dry run The previous check stopped at the first failure, so an R2 403 hid whether the GCS key could write at all. Each host is now checked independently and reports what it found: whether the token is valid, whether it can see the bucket, and whether read and write are separately permitted. That turns 'it failed' into the specific permission to ask for. * ci(templates): print credential findings to the log They only went to the step summary, which the logs API does not return, so the result was invisible to anyone reading the run output. * ci(templates): make the credential dry run actually report The previous attempt was broken three ways: findings went only to the step summary, which the logs API does not return; a mangled helper recursed into itself and segfaulted the step; and continue-on-error marked both steps green regardless, so the run looked like a pass with no output. Logic moves to two scripts that print to stdout and the summary, run without error masking, and use the storage JSON APIs directly rather than the gcloud CLI. Each reports whether the credential is valid, whether it can see the bucket, and whether read and write are separately permitted, so a refusal names the permission to request. * fix(ci): verify account-owned R2 tokens on the right endpoint The credential check called `/user/tokens/verify`, which only accepts user-owned tokens. An account-owned token is rejected there with code 1000 "Invalid API Token" even when it is valid and correctly scoped, so the check failed before it ever reached the bucket and reported a working token as bad. It now tries `/accounts/{id}/tokens/verify` first and falls back to the user endpoint, and says which kind it found. Verified against the real secrets: the token reads and writes `desktop-assets`, and the mirror service account reads and writes `comfy-desktop-public`. Both are ready to publish. Also refreshes a template description upstream had edited, which was failing a guardrail test, and drops the leftover vitest config from before the suite moved to `node --test`. * fix(templates): close the gaps QA found in the authoring workflow Security, in the workflow: - a manual dispatch with dry_run unticked published whatever branch it ran from straight to the file users read at boot; publishing is now pinned to main - R2 was written before authenticating to GCS, so a bad mirror key left the two hosts serving different lists with no rollback; auth happens first now, since it writes nothing and is the likeliest failure - the credential scripts aborted on an unset secret with a bare "unbound variable" and reported nothing, which is the opposite of their job - fixed-name files in /tmp replaced with mktemp, and token validity is parsed rather than substring-matched The validator accepted five states the app silently drops, each of which would pass CI, publish green, and then shorten a tab: a missing or unusable mediaSubtype, the reserved ids `.` and `..`, an id past 128 chars, text past 4096, and a size past the 2 TiB cap. It now mirrors every cap the app enforces. Also: `--id --recommended` bound the flag as the value; passing --recommended with a paid id reported the resulting document rather than the contradiction typed; the index fetch had no timeout; and the post-publish check now polls, since both hosts are behind a CDN that can serve the previous object. * docs(templates): say exactly one paid card, and re-check on main The skill said "at most 1 paid per tab" while the validator requires exactly one, so the doc misled the person it was written for. Also names the mediaSubtype rule the validator now enforces. The push filter omitted the script, so a change to the validator re-checked the committed list on the PR but never again on main. It now matches the PR filter. * docs(templates): write the skill for the agent, not the reader It read as a human tutorial, but the agent is the primary consumer. Rewritten as directives: what to do, when to stop and confirm, and what to report back. Follows Anthropic's authoring guidance: state what to do rather than narrating why, since the body stays in context for the whole session and every line is a recurring cost. Adds the decision points an agent needs and a troubleshooting section keyed on the script's real error strings, so a failure maps to an action instead of a retry. * fix(templates): address the Cursor and CodeRabbit review `flags.paid ?? id.startsWith('api_')` was a dead fallback, since every caller passes an explicit boolean and `false ?? x` is `false`. So `--paid` on an ordinary downloadable template wrote `sizeBytes: 0`, which defeats the pre-install disk-space check. Now `||`, so an `api_` id is paid either way. Publishing had no concurrency group, so two merges in quick succession raced to overwrite the same objects and the older list could win. Queued, not cancelled: every merge must publish. The drift check is documented as a warning but runs under `bash -e`, so a `die()` from an unreachable index failed the step outright, reddening unrelated PRs. It now warns and exits clean. `npx wrangler@3` resolved a mutable dependency tree with the write-capable R2 token in scope; pinned to 3.114.1. Every outbound call now carries connect and max timeouts, `urlopen` a timeout, and both jobs a `timeout-minutes`, so a stalled endpoint cannot hold a runner to the six-hour limit. The GCS probe only proved it could create an object. Publishing overwrites, and a create-only credential would pass the probe then fail the publish, so it now overwrites and reports whether delete works. * fix(templates): type the flags and catch hand-edited fields `check()` accepted a non-boolean `recommended`. The app reads it with `=== true`, so a string "true" published clean and then left the tab with no auto-pick. Both flags are now type-checked. `validate` only confirmed an id still existed upstream, so a hand-edited title, description, size or mediaSubtype passed CI and shipped, which is exactly what the "never hand-edit the JSON" rule forbids. A shrunken sizeBytes also makes the install-time disk-space check under-count. It now regenerates each entry from the live index and reports any field that differs. Three tests discarded the command's exit status and then asserted a count the committed list already satisfied, so they passed whether or not the feature worked. They assert success first now, and the two new rules have their own cases: reverting either fails a test. * docs(templates): add the H1 markdownlint expects MD041 wants a top-level heading before any prose. * fix(templates): stop the validator crashing on malformed input A malformed entry threw a TypeError instead of being reported. `null` in the array crashed on `.id`, a numeric title crashed on `.trim()`, and a non-array `templates` crashed because validate iterated it after check() had already returned. All fifteen hostile shapes now give a clean refusal. The hand-edit check reported upstream drift as an error, which would have reddened every open PR the moment upstream retitled a template. That is the exact case the workflow deliberately downgrades to a warning, so it warns and exits clean now. The GCS upload was writing to standalone-environments/assets/, because the action preserves the parent directory unless told otherwise. The verify step would have failed on the mirror after R2 was already overwritten. Concurrency moved from workflow level to the publish job. Shared with the PR runs, a queued publish could be displaced by a later PR, which is the opposite of what the group was added for. Also: two entries missing an id both hashed to undefined and read as duplicates; a prototype member like `toString` resolved as a command and exited 0 having validated nothing; the mediaSubtype allowlist rejected valid upstream subtypes an editor cannot change; the verify curl had no timeouts and aborted the retry loop under `bash -e`; and the cryptography import is now checked, so a missing package cannot masquerade as a bad credential. The test harness kept stderr, so a refusal can be asserted on its reason rather than on any non-zero exit.
* content(templates): swap image slots 3 and 4 Put PixelDiT ahead of SDXL Turbo in the image picker. Same four templates, same featured and paid cards; only the order of the two free cards changes. * test(templates): assert the slot rule, not the exact order The round-trip snapshot pinned all four ids per tab, so any content swap reddened validate until the fixture was hand-updated in lockstep. Only slots 1 and 2 are load-bearing (recommended auto-pick, then the paid card); the free cards may reorder freely. Assert that rule against the shipped list instead, so a slot 3/4 swap needs no spec change. * docs(templates): teach the skill about the spec fixture and slot rule The skill said to commit only the JSON, but a content change also touches the spec when slot 1/2 need re-checking. Document that only slots 1 and 2 are pinned (recommended, then paid) and that a free-card reorder needs no fixture edit.
…-Org#1480) * feat(announcement): swap the launch modal to Comfy Cloud nodes Reuses the announcement modal the MiniMax license launch shipped, which was written to be reused by swapping the id and copy. Changes the telemetry id, the CTA destinations and UTM campaign, the hero media and the i18n namespace, and adds announcement.cloudNodes.* in both locales. Uses a NEW cloudNodesAnnouncementSeen setting rather than resetting minimaxAnnouncementSeen, so everyone who dismissed the previous announcement still gets the bell for this one. The old key stays defined because it is already persisted on users' machines. Renames the CTA key to primaryCta: it now reads "Get Started" and a key called requestLicenseCta holding that string is a trap for the next person. Copy is draft and the hero video and poster do not exist yet; both paths 404 until someone captures a Comfy Cloud node running on canvas. * fix(announcement): correct cloud-nodes highlight bullet The third highlight claimed "Nine nodes at launch across image, video and audio." The nodes shipping in ComfyUI#15935 are sixteen, covering image and video only (no standalone audio-generation node). Drop the fragile count rather than swap in a new one likely to go stale again before this merges. * feat(announcement): point the cloud-nodes modal at the launch film Swap the placeholder hero paths for the published launch video and poster on media.comfy.org, and correct the third highlight now that nine nodes ship at launch (image, video and audio, not just image and video). * fix(announcement): correct the cloud-nodes count to eight get_node_list() in comfy_api_nodes/nodes_comfy_cloud.py registers eight of the nine node classes the file defines; the reference-to-video node is commented out pending a server-side fix, so it never reaches the node library. * fix(announcement): say the cloud nodes are beta before the CTA Every node ships a [BETA] suffix in its display name, but the modal never said so, which meant a user could only find out after spending credits. State it in the lead, with what beta actually means here (the node set is still changing) rather than a bare label that reads as unreliable. * fix(announcement): match the website CTAs and fix the cta telemetry The primary CTA pointed at cloud.comfy.org, the same bug the landing page had: it sent people to the hosted product this copy tells them they do not need, and on Desktop they already have a local ComfyUI. The pair now mirrors the website. Get Started -> comfy.org/cloud-nodes Read the Docs -> docs.comfy.org/cloud-nodes/overview Both keep the Desktop UTM so the clicks stay attributable. Separately, the primary CTA still reported itself as 'request_license' in telemetry, left over from the MiniMax announcement this component was adapted from. A Cloud Nodes launch has no licence to request, so every primary click would have landed in analytics under the wrong name. It reports 'get_started', and learn_more becomes docs to match the label. * fix(announcement): 16:9 media, lighter copy, beta as fine print The hero was cropped to a square. The wrap was height:100% inside an auto-fit grid with object-fit:cover, so the cell decided the shape and the 16:9 film lost its sides. It carries aspect-ratio 16/9 now and centres in its column. Copy, from reading it in the running app: - open models -> open weight models, which is what these are - the node count came out; it was detail the modal did not need - Get Started -> Read More, since the button opens the landing page - beta moves out of the lead and sits under the actions as fine print, so the disclosure is still there without being the second thing you read Test ids were still announcement-learn-more and announcement-request-license from the MiniMax modal this was adapted from. Nothing referenced them. * fix(announcement): move the beta note above the actions It read as a footnote under the buttons, after the decision point. Above them it is the last thing you read before choosing, which is where a disclosure belongs. * fix(announcement): stop the stacked layout clipping the CTAs CodeRabbit on Comfy-Org#1480: below 720px of card width .announce-grid drops to one column, and the 16:9 media I added is then taller than the whole 916/445 card on its own, so .announce-content's overflow:hidden ate the copy and both CTAs with no way to scroll to them. Measured in Chromium against the component's own CSS. At 760x700 the primary CTA sat at y=815 inside a card ending at y=513; at 640x560, which is an 800x700 panel at 1.25 zoom, y=711 against 417. Two changes. In one column the card ratio goes away and the media takes whatever the copy and CTAs leave, cropping via the object-fit: cover it already has, so nothing scrolls. And .announce-grid scrolls unconditionally, which covers the case the width query cannot see: a wide but short panel is also one column, because the card width is height-derived there. After: both CTAs visible without scrolling at 1280x800, 800x600, 760x700 and 640x560, and reachable by scrolling at 880x440. Two-column geometry is byte-identical. --------- Co-authored-by: Deep Mehta <deep@comfy.org>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
* test: unmount panel components before DOM teardown * test: keep panel teardown fix minimal
* fix(linux): reject x64 runtimes on arm64 * fix(linux): align ARM64 ToDesktop resource paths
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Advanced Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Comment |
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
No description provided.