mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-07-12 18:27:34 +00:00
de05790f5b9e58dfd950fba6c19bc7207d0fe16f
121 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
d6c582c399 |
feat(billing): gate consolidated billing behind consolidated_billing_enabled flag (#13359)
## Summary Shields personal-workspace billing code paths behind the new `consolidated_billing_enabled` feature flag so they fall back to the **legacy** billing flow while the flag is `false`. Team workspaces are unaffected and continue to use the workspace-scoped billing flow. ## Changes - Add `consolidatedBillingEnabled` to `useFeatureFlags` (reads the `consolidated_billing_enabled` server flag / remote config, defaults to `false`) and to the `RemoteConfig` type. - New `useBillingRouting` composable — a single source of truth for whether the active workspace uses the workspace vs. legacy billing flow: - team workspaces disabled → legacy - personal workspace + consolidated billing off/missing → legacy - personal workspace + consolidated billing on → workspace - team workspace → workspace - workspace not loaded yet → legacy - Route `useBillingContext` and the affected UI sites (`SubscriptionPanel`, `useSubscriptionDialog`, `UsageLogsTable`, `TopUpCreditsDialogContentLegacy`) through `useBillingRouting` instead of keying on `teamWorkspacesEnabled` directly. - Update the storybook `useFeatureFlags` mock to stay in sync. ## Testing - `pnpm test:unit` for `useBillingRouting`, `useBillingContext`, `useSubscriptionDialog`, and `UsageLogsTable` (new + updated coverage for the routing matrix). Remaining quality gates (`typecheck`, `lint`) are being verified in CI. ## Related Requires the backend PR that adds the `consolidated_billing_enabled` flag to `/api/features`. --------- Co-authored-by: Amp <amp@ampcode.com> |
||
|
|
2ec2a0e091 |
feat: attribute payment intent through paywall, checkout, and top-up telemetry (#13363)
## Summary Answers "why did this user want to pay?" by capturing the triggering product moment at every paywall/upsell entry point and carrying it through checkout and success telemetry. ## Changes - **What**: - Widen `SubscriptionDialogReason` from 4 coarse values to 13 grounded intent sources (`subscribe_to_run`, `upgrade_to_add_credits`, `invite_member_upsell`, `settings_billing_panel`, etc.) - Fire `app:subscription_required_modal_opened` from `useSubscriptionDialog` (the choke point all dialog variants pass through) — the workspace/unified path previously emitted nothing; remove the now-duplicate emitters in `useSubscription` and `usePricingTableUrlLoader` - Add `payment_intent_source` to `BeginCheckoutMetadata`/`SubscriptionSuccessMetadata`, threaded via the existing `reason` prop: dialog → `PricingTable` → `performSubscriptionCheckout` → pending-attempt record, so legacy `app:monthly_subscription_succeeded` carries intent alongside `checkout_attempt_id` - Fire `begin_checkout` on the workspace checkout path (`useSubscriptionCheckout`, personal + team confirm) and the team deep-link util — both previously emitted nothing; `tier` widened to `TierKey | 'team'` - Implement `trackBeginCheckout` in `PostHogTelemetryProvider` (was GTM/host-only, so `begin_checkout` never reached PostHog) - Thread `showSubscriptionDialog(options)` through the billing-context adapters and pass a reason at ~14 call sites; add `source` to `app:add_api_credit_button_clicked` ## Review Focus - `modal_opened` now fires once per dialog actually shown, so a free-tier user clicking Upgrade emits two events (free-tier dialog, then pricing table) where the legacy path emitted one - Intent is threaded explicitly via props/params rather than shared state; `useSubscriptionCheckout` gained an optional second parameter |
||
|
|
4a2393be48 |
chore: drop unnecessary exports on file-local types to satisfy knip (#13204)
Current `main` **fails a fresh `knip` run** with 13 unused exported types (exit 1). They're invisible on main because lint/knip only runs on `pull_request`/`merge_group`, never on push to main — so merge skew (one PR adds an export used by file X; a later PR removes X's usage) accumulates latent failures that ambush backport branches (e.g. #13163, #13162). Each of the 13 is `export`ed but referenced only within its own file (verified 0 importers; ≥2 in-file uses, so not dead code). Fix: drop the redundant `export`. Types cleaned: `VideoSource`, `ObjectInfoResponse`, `PromotedMissingModelWorkflow`, `PixelReadout`, `ResizeDirection`, `ResizeHandle`, `RunButtonTelemetryOptions`, `ResolvedModelNode`, `AccountPreconditionContext`, `SubscriptionDialogOptions`, `MonthlyCreditsUsage`, `MissingMediaReference`, `ResolvedHostWidget`. Reviewer note: `ResolvedHostWidget` and `ResolvedModelNode` sit under `renderer/extensions`/`platform/assets`; no in-repo importers, but if either is intended as published/extension-facing API, prefer a knip `entry`/`ignore` over un-exporting — flag in review and I'll adjust. After fresh `knip`: **0 unused exported types**. Supersedes #13179 (fixed only `AccountPreconditionContext`). Pairs with the push-gate workflow #13203 — merge this first so that gate is green on main. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
cfbc378df3 |
feat(billing): align workspace Plan & Credits panel to DES-186 (FE-768) (#12761)
## Summary Aligns the Settings ▸ Workspaces **Plan & Credits** panel to DES-186: state-driven subtitle, team/personal header variants, design perks, and a shared footer help bar. Stacked on FE-964 (#12734), which owns the CreditsTile content. ## Changes - **What**: - Subtitle per design variants (Figma 3255-21472): Active → "Renews on {date}", Ending → "Ends on {date}". `subscription_status: 'scheduled'` falls back to the Active treatment — the facade exposes no scheduled-plan target/date fields yet, so "Changes to {plan} on {date}" cannot be rendered (template reserved in i18n-ready form; see Linear note). - Team-active header: plan name "Team" + seat-aware workspace total (`seat_summary.total_cost_cents` from `/billing/plans`) as "$X USD / mo"; the Next-month-invoice card reads the same computed so the two can't disagree. Per-member tier price + "USD / mo / member" kept as the plans-unresolved fallback. - CTAs per design + designer annotation: "Manage billing" + "Change plan" on a team plan, "Upgrade plan" on personal. - Team perks (Figma 2993-14789): "Your plan includes everything in **Pro**, plus:" (i18n-t, plan name emphasized) + invite members / concurrent runs / shared credit pool / role-based permissions. - Personal no-subscription variant (Figma 2993-14604): "Free · $0 USD / mo" header + primary Subscribe CTA + "What's included:" with the max-runtime perk → "10 min max runtime" (`subscription.maxDuration.free` set to 10 min per DES 3253-16079). - Footer help bar (Learn more / Partner Nodes pricing table / Message support / Invoice history) extracted into `SubscriptionFooterLinks.vue`, shared by workspace + legacy panels; new surface-specific key for "Partner Nodes pricing table". - **Breaking**: none. ## Review Focus - Seat-aware price source (`currentPlan.seat_summary.total_cost_cents`) vs per-member fallback — fixture locks $320 vs $80. - `'scheduled'` → Active fallback (adapter-level test in `useWorkspaceBilling.test.ts`). - Free-state perk copy: `subscription.maxDuration.free` set to **10 min** per DES 3253-16079 (design confirmed; was 30 min). - Free-state overflow (`⋯`) button intentionally omitted: the only existing menu item (Cancel Subscription) doesn't apply to a free plan. Linear: [FE-768](https://linear.app/comfyorg/issue/FE-768/updates-to-workspaces-tab-of-settings) (Plan & Credits half; Members invite UI ships in #12759) ## Screenshots Captured on `local.comfy.org` dev (cloud-prod backend, authenticated session). Team and free states use client-side API stubs (XHR-level for `/api/billing/*` + `/api/workspace/*`, fetch-level for legacy `/customers/*`) since the test workspaces are unsubscribed; personal-active rows are real account data. | State | Before (FE-964 base) | After (this PR) | |---|---|---| | Team plan — active | <img width="400" alt="before: Pro, $100 USD/mo/member, Renews, Manage Payment/Upgrade Plan" src="https://github.com/user-attachments/assets/622a9a27-1875-4c08-92b7-9e43a8067c59" /> | <img width="400" alt="after: Team, $300 USD/mo, Renews on, Manage billing/Change plan, design perks" src="https://github.com/user-attachments/assets/adb0f767-d508-4455-ad8e-ee2d6ac419dc" /> | | Team plan — ending (cancelled) | <img width="400" alt="before: Expires Jul 10, 2026" src="https://github.com/user-attachments/assets/cb4bb978-4e8b-4372-8ecc-7265f477a828" /> | <img width="400" alt="after: Ends on Jul 10, 2026" src="https://github.com/user-attachments/assets/5466b99a-d016-4eba-a60f-99b87bd2693e" /> | | Personal — active (real data) | <img width="400" alt="before: Renews Mar 10 2027, Manage Payment" src="https://github.com/user-attachments/assets/34018400-930b-4147-bd0d-398fb4d159ee" /> | <img width="400" alt="after: Renews on Mar 10 2027, Manage billing" src="https://github.com/user-attachments/assets/87af7fa7-28cf-4cdf-b9a2-158b2a6eb979" /> | | Personal — no subscription | <img width="400" alt="before: generic not-on-a-subscription prompt" src="https://github.com/user-attachments/assets/7ee5b36e-1e07-4630-a9b7-680f4fad349b" /> | <img width="400" alt="after: Free $0 USD/mo header + Subscribe + What's included" src="https://github.com/user-attachments/assets/1eda1791-e3de-46c3-bb69-df0365545211" /> | --- ## Perk descriptions — designer QA (DES `3253-16079`) CDP-verified the Plan & Credits **perk list** per current plan against Figma. Captured at the full 1280px settings layout (width fix: #12849; the perk text/content is identical at the current 960px). | Plan | "Includes" header | Perks shown | |---|---|---| | Free (no subscription) | What's included: | 10 min max runtime | | Personal — Pro | Your plan includes: | 1 hr max run duration · RTX 6000 Pro (96GB VRAM) · Add more credits whenever · Import your own LoRAs | | Team | Your plan includes everything in **Pro**, plus: | Invite members · Members can run workflows concurrently · Shared credit pool for all members · Role-based permissions | **Free — Figma `3253-16079` (left) vs implementation (right):** | Figma | Implementation | |---|---| | <img width="480" alt="Figma DES 3253-16079 free settings" src="https://github.com/user-attachments/assets/cfe79570-f3ba-4627-a8eb-348b9158f6ac" /> | <img width="480" alt="App free settings — What's included: 10 min max runtime" src="https://github.com/user-attachments/assets/d89898e4-d819-486c-9b4f-c2fd61916783" /> | **Personal — Pro:** <img width="640" alt="App personal Pro — Your plan includes" src="https://github.com/user-attachments/assets/adc2fd9f-d249-469f-b947-1ec8f674cbb0" /> **Team:** <img width="640" alt="App team — Your plan includes everything in Pro, plus" src="https://github.com/user-attachments/assets/e7378067-11a2-411b-b37b-98c8aecb82b1" /> Open items (design): - Free perk now reads **"10 min max runtime"** (`subscription.maxDuration.free` set to 10 min) per Figma `3253-16079` — ✅ applied in this PR. - Personal-plan perk **stacking** (show lower-tier perks under the current tier) is an unresolved Figma thread on this node — not implemented. --------- Co-authored-by: GitHub Action <action@github.com> |
||
|
|
e3049e7c31 |
feat(billing): single billing path — collapse personal/team dispatch to flag-only (B1 / FE-966) (#12953)
## What this does
Collapses the personal-vs-team billing dispatch so it keys ONLY on the
build/flag (`teamWorkspacesEnabled ? 'workspace' : 'legacy'`). Personal
now flows through `useWorkspaceBilling` (`/api/billing/*`), same as team
("personal plan = single-seat workspace"). This converges status /
balance / subscribe / preview / cancel / portal in one change.
Dispatch sites collapsed:
- `useBillingContext.ts` — `type` computed: dropped the
`store.isInPersonalWorkspace` branch → flag-only.
- `useBillingContext.ts` — D3 subscription→store mirror watch: dropped
the `isInPersonalWorkspace` early-return guard so personal also mirrors
into the workspace store.
- `useSubscriptionDialog.ts` — `useWorkspaceVariant` compound predicate
→ flag-only (personal + flag-on now uses the workspace required-dialog
variant).
- `SubscriptionPanel.vue` — already flag-only
(`v-if="teamWorkspacesEnabled"`); no change needed.
## Kept (Risk #6)
- The ~11 raw `workspace.type === 'personal'` checks in
`teamWorkspaceStore.ts` — workspace-TYPE membership logic
(can-delete/leave, fetch-members, switcher), NOT billing dispatch.
Untouched.
- `useLegacyBilling` / `useSubscription` / authStore billing methods
kept intact for the flag-OFF (OSS/Desktop) path.
## Flag-off unchanged
Flag-OFF (OSS/Desktop) still selects `legacy` (`/customers/*`). Verified
by unit test.
## Tests
- `useBillingContext`: flag-ON → personal selects `workspace`; flag-OFF
→ `legacy`; D3 mirror now fires for personal under flag-on.
- `useSubscriptionDialog`: flag-ON → workspace required-dialog variant
for personal; flag-OFF → legacy personal variant.
## Follow-up (deferred, not in this PR)
Post-flip cutover deletion of `useLegacyBilling`-only components:
`PricingTable.vue`, `SubscriptionPanelContentLegacy.vue`,
`TopUpCreditsDialogContentLegacy.vue`, `CurrentUserPopoverLegacy.vue`,
`subscriptionCheckoutUtil.ts`, `useSubscriptionCancellationWatcher.ts`.
- Fixes part of FE-903 (B1)
|
||
|
|
026b2c4795 |
feat(billing): unify credits into a facade-driven CreditsTile (FE-964) (#12734)
## Summary ### AS IS <img width="1340" height="798" alt="Screenshot 2026-06-10 at 12 22 36 AM" src="https://github.com/user-attachments/assets/61636fa3-e80c-427b-855b-499e1eca67da" /> ### TO BE <img width="1301" height="793" alt="Screenshot 2026-06-10 at 12 22 39 AM" src="https://github.com/user-attachments/assets/62d9f5a6-da92-45df-94e7-cd3c244249f9" /> ### Empty states ([added to DES-247 on 2026-06-11](https://www.figma.com/design/CkFTD4c20PyRGpNVAJgpfV/Team-Plan---Workspaces?node-id=3349-29750)) | 0 monthly credits | 0 credits | | --- | --- | | <img alt="credits-empty-monthly" src="https://github.com/user-attachments/assets/b3c55d3b-79b0-47b1-9795-c8bf69d5efe2" /> | <img alt="credits-empty-all" src="https://github.com/user-attachments/assets/919081d6-64e1-483b-9c04-6b085243ebc1" /> | Consolidate the divergent Settings credits surfaces into one facade-driven **CreditsTile**, implementing the DES-247 redesign so personal and team modes always render the same balance from `useBillingContext`. ## Changes - **What**: - New `CreditsTile.vue` — total + `remaining`, a stacked monthly/additional progress bar, colored breakdown rows (`Monthly (refills …)` / `Additional`), refresh, and a permission-gated *Add credits* / *Upgrade to add credits* action. Owns the post-checkout (`focus` / `pending_topup`) balance refresh. - Extracted the duplicated inline credits card out of `SubscriptionPanelContentWorkspace.vue` **and** `SubscriptionPanelContentLegacy.vue` onto the shared tile. - Replaced `LegacyCreditsPanel.vue` (read `authStore.balance` directly) with `CreditsPanel.vue` routed through the tile; repointed `useSettingUI` and deleted the legacy panel. - `creditsProgress.ts` pure helper for the bar math + numeric credit getters on `useSubscriptionCredits`. - i18n keys for the unified tile labels. - DES-247 responsive variants via CSS container queries: below ~350px tile width the `{used} used` label, `remaining` suffix, and breakdown subtitle drop and the additional-credits value stacks under its label; below ~230px the monthly summary compacts (`105K left of 200K`). Additional-credits tooltip copy aligned with the updated design (per design feedback). - Empty states (added to DES-247 via Slack on 2026-06-11, low priority): once the monthly allowance is depleted, an info notice renders under the total (`Monthly credits are used up. Refills {date}` / `You're now spending additional credits.`), the monthly bar section dims to 30% opacity, and an `IN USE` pill marks *Additional credits*; once everything is depleted the notice switches to `You're out of credits. Credits refill {date}` and *Add credits* swaps to the `inverted` (filled-white) Button variant. Gated on a loaded balance so the notice never flashes while fetching. - **Dependencies**: Stacked on **#12622 (FE-904 / B2)** for the facade `tier` / `renewalDate` fields — base this PR against that branch; retarget to `main` once FE-904 merges. ## Review Focus - The tile reads everything from the facade (`balance.*Micros` as cents → credits, `subscription.tier`/`renewalDate`), so legacy and workspace modes share one source. - Monthly allowance still comes from `getTierCredits` (hardcoded tier nominal). With real data the monthly *remaining* can exceed the nominal (rolled-over credits), so the bar clamps to a full segment — same semantics as the prior `{monthly} / {planTotal}` display; the canonical allowance is a BE-1047 follow-up. - `LegacyCreditsPanel` deletion: `CreditsPanel.vue` retains the usage-history table + help links and reads the facade. ## Testing - Unit/component (36 green): `CreditsTile.test.ts` (render, zero-state, free-tier, permission gating, add-credits, mount+manual refresh, plus the empty states: depletion notice copy, `IN USE` badge, `inverted` button when fully out, no-flash-while-loading guard), `creditsProgress.test.ts` (clamping/stacking math), `useSubscriptionCredits.test.ts` (`*_micros`-as-cents), and `SubscriptionPanel.test.ts` updated for the extracted tile. - E2E (`@cloud`): `browser_tests/tests/dialogs/creditsTile.spec.ts` boots the cloud app against mocked Firebase auth + stubbed boot endpoints (no backend) and asserts the tile's total / progress bar / monthly+additional breakdown / add-credits in Settings ▸ Workspace ▸ Plan & Credits, then resizes to a narrow viewport and asserts the responsive variant (labels hidden, compacted `11K left of 21K`). A second test boots with a drained monthly balance (0-monthly notice + `IN USE` badge), then re-mocks a fully drained balance and refreshes the tile in place to assert the out-of-credits state. Both pass locally against a cloud dev server; runs in the `cloud` CI project. Drives a raw page because the shared `comfyPage` fixture expects the OSS devtools backend. - Screenshot-verified the tile at the three DES-247 reference widths (448 / 235 / 204px) against the Figma Responsiveness section — 1:1. - Verified live in the running app (Settings ▸ Workspace ▸ Plan & Credits) against the authenticated backend — renders 1:1 with DES-247. The empty-state screenshots above were captured the same way (authenticated app, real Pro subscription, balance endpoint stubbed to the depleted values via CDP). - `pnpm typecheck` / `typecheck:browser` / `lint` / `knip` green. Implements FE-964 (DES-247). --------- Co-authored-by: GitHub Action <action@github.com> |
||
|
|
d60260ac3c |
feat(billing): team-plan subscribe + checkout/confirm screen redesign (FE-934) (#12975)
## Summary Two related streams of FE-934 checkout/billing work on this branch: https://github.com/user-attachments/assets/af629def-543e-4bcd-894d-b35aa032fe0a 1. **Team-plan subscribe** wired to the BE-1254 credit-stop contract (replaces the "coming soon" toast stub). 2. **Checkout / confirm screen redesign** aligned to the updated DES mockup — yearly pricing display, dialog navigation, and the plan-change confirm. <img width="1078" height="427" alt="team subscribe" src="https://github.com/user-attachments/assets/2b5f7192-3c91-4e2d-b495-832ca5a26657" /> ## Changes ### Team-plan subscribe (credit-stop contract) - Team subscribe sends `{ plan_slug: team_per_credit_monthly | team_per_credit_annual, team_credit_stop_id, billing_cycle }` to `POST /api/billing/subscribe` and handles the response like the personal path (`subscribed` → success, `needs_payment_method` → payment URL, `pending_payment` → poll). Slider stops come from `GET /api/billing/plans → team_credit_stops` via `mapApiTeamCreditStops`, falling back to the hardcoded DES-197 stops so OSS / pre-deploy still render. `preview-subscribe` is unchanged — the team confirm step is display-only. - **Internal API change**: `workspaceApi.subscribe(planSlug, returnUrl?, cancelUrl?)` → `subscribe(planSlug, options?: SubscribeOptions)`; the billing facade (`useBillingContext` / `useWorkspaceBilling` / `useLegacyBilling`) and callers were updated to match. ### Checkout / confirm screen redesign (DES mockup) - **Yearly confirm**: headline is now the ÷12 monthly-equivalent with a `{total} Billed yearly` (yearly) / `Billed monthly` (monthly) subtitle; credits show `Each year credits refill to` (×12) for yearly; `Starting today` → `Starts today`. The team confirm now receives the active billing cycle (the subtitle was missing because it wasn't passed), and the redundant header credits/month line was dropped. - **Navigation**: the pricing table stays mounted (`v-show`, not `v-if`) so the plan / billing-cycle / credit-stop selection survives a round trip to the confirm step and back; **Backspace** mirrors the back arrow (ignored while an input/textarea/contenteditable is focused). - **Plan-change confirm**: rewritten from the 2-column current→new comparison to the **single-plan layout**, branching on `previewData.is_immediate` — immediate upgrades show prorated line items (`new_plan.price_cents − cost_today_cents` = credit) + upfront (yearly) / monthly credit refill + a prorated total ("Confirm upgrade"); scheduled downgrades show `Starts {date}`, $0 due today, and an "After that" block ("Confirm change"). - **Storybook**: `SubscriptionCheckoutSteps` stories for each new-sub / upgrade / downgrade variation (props-driven, no API). ## Review Focus - **Merge gate (team subscribe)** — ✅ **resolved**: BE-1254 is now merged in cloud `main`. The live `GET /api/billing/plans` returns `team_credit_stops` (`TeamCreditStop` struct + validation in `common/billing/catalog/catalog.go`, served for every workspace, asserted by the `billing_credit_stops_contract` smoke test). The gate is lifted. - **Fallback safety**: with the contract live, the hardcoded DES-197 stops are now purely the OSS / pre-deploy fallback — they render only when the API doesn't supply `team_credit_stops`. In that window a real subscribe is still impossible (no stop `id`) and surfaces a `teamPlan.unavailable` toast. Open question retained: hide/disable the team CTA in that window instead of toasting? - **Plan-change scope**: the single-plan confirm redesign covers **personal** changes (the real `previewSubscribe` path). **Team** plan changes route through the display-only team confirm and aren't wired to `previewSubscribe` (BE left `PreviewSubscribeRequest` as `plan_slug` only) — team-change proration is a follow-up. - **Dead locale keys**: the old 2-column transition keys (`everyMonthStarting`, `youllBeCharged`, `proratedRefund`, `proratedCharge`, `creditsRefillTo`, `switchToPlan`, `starting`, `ends`, `confirmPlanChange`) are now unused — can be removed in a follow-up cleanup. - **Out of scope**: the success "Your change is scheduled" variant from the mockup. - Based on `fe-934-unified-pricing-table`; base + `main` merged in to resolve conflicts (PR is now mergeable). ## Verification - Confirm / transition component tests green (`SubscriptionAddPaymentPreviewWorkspace`, `SubscriptionTransitionPreviewWorkspace`); oxlint / oxfmt clean locally; full typecheck runs in CI. - Each variation viewable in Storybook → `Components/SubscriptionCheckoutSteps`. --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: GitHub Action <action@github.com> |
||
|
|
0c89f5a3a7 |
feat: route cloud auth through the single Cloud JWT under unified_cloud_auth (FE-950) - step 3 (#12708)
## Summary Behind `unified_cloud_auth` (default OFF), flip the two token accessors so every cloud request rides the single Cloud JWT minted in PR2. This is the consumer-flip phase of FE-950: PR2 built the dormant `unifiedToken` slot; this PR makes consumers read it — and surfaces the permanent-auth-failure path that the flip turns from graceful degradation into a hard stop. Stacked on #12704 (PR2), now merged; base is `main`. ## Changes - **What**: - `getAuthHeader()` — flag ON returns `{ Authorization: Bearer <unifiedToken> }` (or `null` if unminted), with **no** Firebase/API-key fallback. Flag OFF keeps the exact workspace → Firebase → API-key cascade. - `getAuthToken()` — flag ON returns the unified Cloud JWT (or `undefined`); flag OFF keeps workspace → Firebase. - Both accessors are the single seam every cloud consumer already routes through, so the flip propagates automatically with **no edits** to `fetchApi` (`scripts/api.ts`), `/customers/*` (authStore), `workspaceApi`, the WebSocket (`api.ts:568`), or backend-node auth (`app.ts:1593`). - **Surface permanent auth failures** (answers @pythongosssss's review on PR2). Under the flag there is no Firebase fallback, so a silent `clearUnifiedContext()` wipe would strand every cloud request until manual re-login — unlike the legacy path, which degrades to the Firebase token. `refreshUnified()` and `mintAtLogin()` now emit a user-facing error toast (keyed by error code off the existing `workspaceAuth.errors` i18n) on the permanent codes (`ACCESS_DENIED` / `WORKSPACE_NOT_FOUND` / `INVALID_FIREBASE_TOKEN` / `NOT_AUTHENTICATED`). `mintAtLogin()` now resolves `false` on a permanent failure instead of rejecting an unhandled `void`ed promise. Transient failures stay silent (proactive refresh still retries). Also trims the verbose unified-lifecycle comments flagged in review. - **Breaking**: None. Flag OFF is byte-for-byte the current cascade. ## Review Focus - **Single token, no fallback under the flag.** Tests assert `getAuthHeader`/`getAuthToken` return only the unified token and never call `getIdToken` or the API-key store; they return `null`/`undefined` (not a fallback) when the token is unminted. - **Surfacing, not recovery.** This PR makes the terminal state *visible* (toast); the existing router auth-guard still redirects to login on the next navigation. Active recovery (automatic re-mint on 401) stays in the deferred safety-net PR so the toast is never a dead-end "please re-login" with the fix one PR away. - **Flag-OFF parity.** The full existing cascade suite runs with `unifiedCloudAuthEnabled = false` (the `beforeEach` default) and stays green. ## Deferred (intentional) - **`acceptInvite` is left unchanged — still Firebase-authed.** It is the one cloud call that intentionally keeps the raw Firebase token, because the invite is accepted *before* the user is a member of the target workspace. Promoting it to the unified Cloud JWT first needs a quick check that `POST /invites/:token/accept` accepts a personal-scoped Cloud JWT for a not-yet-member; deferred until that is verified. `getFirebaseAuthHeader` / `getFirebaseAuthHeaderOrThrow` stay defined (their removal belongs to the later cleanup ticket, FE-951). No `workspaceApi.ts` change in this PR. - **The reactive 401 re-mint + retry safety net is a follow-up.** A clean place to intercept a `401` and re-mint once does not exist yet: cloud requests use raw `fetch` (`/customers/*`, `/auth/token`) plus several independent axios clients (`workspaceApi`, `customerEventsService`, registry, manager), with no shared response interceptor. PR2's `remintUnifiedOnce()` primitive is ready, and the proactive buffer-based refresh (`refreshUnified`) already covers the common token-*expiry* case, so this cross-cutting safety net (plus deciding whether the surfacing toast escalates to a guided re-login CTA once remint exists) lands in its own focused PR before any production rollout. Note this is orthogonal to the surfacing above: proactive refresh prevents expiry; it cannot prevent *revocation*, which is exactly what triggers the now-surfaced permanent-error path. ## Tests - Extended `authTokenPriority.test.ts`: flag-ON `getAuthHeader` returns only the unified JWT (Firebase + API-key + workspace untouched) and `null` when unminted; flag-ON `getAuthToken` returns the unified JWT (not Firebase) and `undefined` when unminted. Existing cascade tests prove flag-OFF parity. - Added to `useWorkspaceAuth.test.ts` (red-green + regression lock): a permanent refresh error toasts the **correct i18n key for each of the four permanent codes** (`it.for` over 403/404/401 + a lost-Firebase-token `NOT_AUTHENTICATED` case) and clears the slot; a permanent login-mint error toasts and resolves `false`. Negative guards prove the surfacing is **error-only and flag-scoped**: a transient (5xx) refresh does **not** toast and keeps the slot, a **successful** re-mint does not toast, and the unified lifecycle **never toasts when the flag is OFF** (even against a rejecting backend). ## Red-Green Verification | Commit | CI | Purpose | |--------|-----|---------| | `test: cover permanent unified-auth error surfacing` | 🔴 Red ([run](https://github.com/Comfy-Org/ComfyUI_frontend/actions/runs/27455949404)) | Proves the tests catch the silent-failure gap | | `fix: surface permanent unified-auth errors instead of failing silently` | 🟢 Green ([run](https://github.com/Comfy-Org/ComfyUI_frontend/actions/runs/27456200098)) | Proves the surfacing resolves it | Part of FE-950 (single Cloud-JWT provider at login, Phase 1). |
||
|
|
f19597ce81 |
feat(billing): deep link to open the pricing table (FE-1104) (#13001)
## Summary Adds an in-app deep link that opens the pricing table directly, for driving pilot users straight to subscribe (request from nav/Alex). Resolves [FE-1104](https://linear.app/comfyorg/issue/FE-1104). - `/?pricing=1` — on app load, open the pricing table. - `/?pricing=team` / `/?pricing=personal` — open it on the Team / Personal plan tab (via the existing `UnifiedPricingTable` `initialPlanMode`). - Gated to the **original owner** via `useWorkspaceUI().permissions.canManageSubscriptionLifecycle` (personal user, or a team workspace's original owner). A member or a promoted owner is a **silent no-op**: the app loads normally, the param is stripped, no 404 / error / toast. - Off-cloud (OSS): the loader isn't instantiated, so the param is ignored. - Survives the login redirect via the preserved-query system, same as `?invite` / `?create_workspace`. ## How Mirrors the established URL-loader pattern (`useInviteUrlLoader` / `useCreateWorkspaceUrlLoader`): - `preservedQueryNamespaces.ts` / `router.ts` — register the `pricing` namespace + tracker key. - New `usePricingTableUrlLoader.ts` — hydrate preserved query, read `pricing`, strip the param + `clearPreservedQuery` in a single replace before any await, then `await fetchMembers()` (resolves the original-owner gate; no-ops for personal) and open the table only when the gate allows. - `GraphCanvas.vue` — call the loader in `onMounted` after the create-workspace loader (cloud only; not gated on the team-workspaces flag so it also drives personal/legacy users). - `useSubscriptionDialog.ts` — new `'deep_link'` value on `SubscriptionDialogReason`. ## Telemetry Eligible opens emit the existing `subscription_required_modal_opened` PostHog event with the new `reason: 'deep_link'`. Ineligible-click bounce rate is derivable from the autocaptured pageview URL (`?pricing=…`), so no new event plumbing. ## Stacking / dependencies This feature needs two sibling stacks off `main`: - **FE-934** (`#12666`, base of this PR) — the `UnifiedPricingTable` + `showPricingTable({ planMode })`. - **FE-770** (`#12829`) — the `canManageSubscriptionLifecycle` gate. **Merged into this branch**, so the diff against the FE-934 base includes FE-770's changes until it lands. Review the single `feat(billing): deep link…` commit. Once both land on `main`, rebase onto `main` and the diff collapses to just this feature. Do not merge before FE-770 and FE-934. Post-Billing-V1 follow-up. End-state: swap the FE original-owner heuristic for the BE workspace-level `is_original_owner` flag when it lands (removes the members-fetch). ## Tests - Unit (`usePricingTableUrlLoader.test.ts`, 12 cases): opens for an original owner; `team`/`personal` tab preselect; silent no-op + param-strip for a member/promoted owner; proves the gate is read only after `fetchMembers` resolves; preserved-query restore; empty/non-string/absent/unrecognized param; members-fetch failure strips+clears without opening. - E2E (`browser_tests/tests/dialogs/pricingTableDeepLink.spec.ts`, `@cloud`, 4 cases, verified locally): personal owner opens + URL stripped; `?pricing=team` lands on the active Team tab; team original owner opens (real `is_original_owner` + email gate); team member is a silent no-op + URL stripped. - Typecheck + related unit suites (`useSubscriptionDialog`, `useWorkspaceUI`, `teamWorkspaceStore`) green. --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: GitHub Action <action@github.com> Co-authored-by: jaeone94 <89377375+jaeone94@users.noreply.github.com> Co-authored-by: github-actions <github-actions@github.com> |
||
|
|
52d430d1b6 |
fix(billing): repoint direct-bypass billing consumers to the facade (B3) (FE-933) (#12643)
## What **B3 — Repoint direct-bypass billing consumers to the facade.** Billing data was read from the legacy `useSubscription` store / `authStore` directly (empty or personal-only for team workspaces) instead of the workspace-aware `useBillingContext` facade. FE-933 (parent FE-903). > **Stacked on #12622 (B2 / FE-904)** — depends on the facade `tier` / `renewalDate` fields added there. Base is the B2 branch; retarget to `main` once B2 merges. ## Repointed consumers - **T3 — `SubscribeButton.vue`**: `subscribe_clicked` telemetry `current_tier` ← facade `tier` (was wrong/empty for team users) - **T4 — `PostHogTelemetryProvider.ts`**: PostHog `subscription_tier` person property ← facade `tier` watch (tier-segmented analytics was polluted for team users) - **T5 — `FreeTierDialogContent.vue`**: next-refresh date ← facade raw ISO `renewalDate`, formatted at the display site (the line silently disappeared for team users) - **`useSubscriptionActions.handleRefresh` + `SettingDialog` credits-nav**: balance refresh ← facade `fetchBalance()` (was legacy `/customers`-only `authActions.fetchBalance`) - **`CurrentUserPopoverLegacy.vue`**: tier badge / balance / skeleton / refreshes ← facade (`tier`, `balance`, `isLoading`, `fetchStatus`, `fetchBalance`); tier name via shared `useWorkspaceTierLabel` instead of a duplicated mapping - **`PricingTable.vue`**: `isActiveSubscription` / `isFreeTier` / `tier` / yearly-vs-monthly ← facade; the billing-portal flow (`accessBillingPortal` deep-links + proration) is intentionally unchanged — facade `manageSubscription` is not behavior-identical ## Out of scope (triaged) - `TopUpCreditsDialogContentLegacy` / `SubscriptionPanelContentLegacy` / `useSubscriptionDialog` / cancellation watcher — legacy-mode-only surfaces decommissioned by B1 (FE-966); repointing is churn, and `useSubscriptionDialog` would create a legacy↔facade cycle - `LegacyCreditsPanel` / `UserCredit` — deleted/orphaned by FE-964 (#12734); its successor `CreditsPanel.vue` keeps an `authStore.lastBalanceUpdateTime` watch (no facade equivalent yet) — follow-up after FE-964 lands ## Known semantic deltas (intentional, match shipped facade consumers) - Balance-refresh failures no longer toast: legacy `authActions.fetchBalance` wrapped errors with a toast; facade `fetchBalance` rejections are void-ed, same as `CurrentUserPopoverWorkspace` / `SubscriptionPanelContentWorkspace`. Facade-level error surfacing is a follow-up. - Popover skeleton keys on facade `isLoading` (init-time) rather than per-fetch `isFetchingBalance`, matching the workspace popover. ## Tests - New behavioral coverage: FreeTier renewal-date render/disappear, popover tier badge + balance from facade, current-plan highlight from facade tier+duration, facade-vs-legacy fetchBalance tripwire, PostHog `subscription_tier` from facade tier. - Local gates clean (typecheck / lint / format / dead-code); touched unit files 71/71 pass. ## E2E coverage Browser regression tests live in the stacked #12760 (`billingFacadeConsumers.spec.ts`, `@cloud`): avatar popover tier badge + balance, and the free-tier dialog renewal-date line (T5) rendered from the facade. The team-user telemetry fixes (PostHog person property, telemetry payload) are non-UI observables covered by unit tests that mock only the facade and fail on revert. --------- Co-authored-by: Alexander Brown <drjkl@comfy.org> |
||
|
|
a07854755f |
fix(billing): restore unified pricing dialog width (Reka renderer regression) (#13092)
## Summary Restore the unified "Choose a Plan" pricing dialog width — it was collapsing to the default `md` (576px) frame, so the 1280px table overflowed and rendered off-center with the right card clipped. ## Changes - **What**: `showPricingTable` opens the unified dialog (`SubscriptionRequiredDialogContentUnified`) with PrimeVue-path props for sizing (`style: 'max-width: 95vw'` + `pt`). Since #12593 (FE-578 Phase 6a) made **Reka the default dialog renderer**, those props are ignored — Reka sizes via `size`/`contentClass`, so the dialog fell back to `size: 'md'` (`max-w-xl` = 576px). The content root's `xl:w-[min(1280px,95vw)]` then overflowed the 576px box and shifted off-center. Moved the width onto a Reka `contentClass` (`w-fit max-w-[min(1280px,95vw)]`), matching the sibling subscription dialogs in the same file. ## Review Focus - **Regression origin**: the broken config landed when #12666 (FE-934, UnifiedPricingTable) merged on top of #12593's reka-default flip while still using the PrimeVue config. No merge conflict — the `style` line is valid but dead, so it broke silently. FE-991 (#12792) predates #12593, so it still rendered via PrimeVue and looked correct (matching the report that it was fine there). - **`w-fit` vs fixed width**: `w-fit` preserves the original "dialog hugs its content per step" intent — the content root only sets the 1280px width on the pricing step, so confirm/success steps still shrink instead of floating in a 1280px box. - Out of scope: the legacy-team / flag-off paths share a PrimeVue `style` shell and are likely affected the same way under Reka; left for a follow-up (flag-off is the lower-priority OSS path). ## Verification - Unit test `useSubscriptionDialog.test.ts` — red without the fix (dialog has no `contentClass`), green with it. - Verified live (cloud dev, viewport 1301px): box centered at 1236px (95vw), no overflow, all three personal cards visible. ## Screenshots Personal tab, viewport 1301px: | Before | After | | --- | --- | | <img width="480" alt="before" src="https://github.com/user-attachments/assets/e233fe00-f754-4e34-837f-cf6630ccbfb9" /> | <img width="480" alt="after" src="https://github.com/user-attachments/assets/dedd92b7-8707-4865-b7f3-289919043b48" /> | |
||
|
|
84319bea13 |
refactor: drop redundant isCloud guards around telemetry calls (#13082)
## Summary
Remove redundant `if (isCloud)` guards around `useTelemetry()?.x()`
calls. `useTelemetry()` already returns `null` in OSS builds, so the
optional-chain calls no-op there — the guards only duplicated that
central contract.
## Changes
- **What**: Drop the `isCloud` guard wrapping telemetry calls across 9
files and remove the 5 now-unused `isCloud` imports (pure dedent —
implementations unchanged). Add two-path (cloud + OSS) characterization
tests for the two previously-uncovered composables
(`useTemplateWorkflows`, `useSubscriptionActions`).
## e2e
In local/OSS mode, useTelemetry() returns null, so no telemetry-related
behavior occurs, and the workflow loads as expected. There are no
local/OSS flow regressions for the exact template workflow paths touched
by the branch.
| before | after |
| -- | -- |
| <img width="1280" height="800" alt="before-01-templates-open"
src="https://github.com/user-attachments/assets/1cccc686-4e3a-4cf0-a578-a653a1383e3c"
/> | <img width="1280" height="800" alt="after-01-templates-open"
src="https://github.com/user-attachments/assets/ff834a58-4375-432a-8cc1-6e04ceeece77"
/> |
| <img width="1280" height="800" alt="before-02-template-loaded"
src="https://github.com/user-attachments/assets/1abd301b-d66d-4819-a0f3-9dff1a1e23b5"
/> | <img width="1280" height="800" alt="after-02-template-loaded"
src="https://github.com/user-attachments/assets/9fbb6903-c085-4744-b683-39b01680c654"
/> |
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> **Low Risk**
> Behavior is intended to be unchanged: OSS still no-ops via null
telemetry. Router page-view tracking may run in more build contexts but
remains guarded by optional chaining.
>
> **Overview**
> Removes duplicate **`if (isCloud)`** wrappers around
**`useTelemetry()?.…()`** across onboarding, auth, templates,
subscription UI, and routing. Call sites now rely on
**`useTelemetry()`** returning **`null`** in OSS (optional chaining
stays a no-op there), and several unused **`isCloud`** imports are
dropped.
>
> **`trackPageView`** in the router no longer bails early on cloud-only
or **`window`** checks; it always invokes
**`useTelemetry()?.trackPageView(...)`** on navigation.
>
> Adds characterization tests for **`useTemplateWorkflows`** and
**`useSubscriptionActions`** that assert telemetry fires when the mock
dispatcher is registered and does not when the mock simulates OSS
(**`useTelemetry()` → null**).
>
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
|
||
|
|
49a7b7b558 |
feat(billing): UnifiedPricingTable — one table, personal/team plan toggle (B4 / FE-934) (#12666)
## What **B4 (FE-934): `UnifiedPricingTable`** — one pricing table for the **Jun-5 model** (typeless workspace; personal/team is a **plan**, shown as a Gamma-style **plan toggle** on one workspace), per **DES-197**. A new, flag-gated component that will replace the two legacy tables at cutover (strangler). ### video https://github.com/user-attachments/assets/82b704a4-101e-4609-8ff5-06b7cf7f9cd7 > **Stacked on #12644 (FE-935 `CreditSlider`).** Base is the slider branch — retarget to `main` once #12644 merges. The slider commits show in the diff until then. ## Changes - **`UnifiedPricingTable.vue`** — plan toggle (personal/team, flag-gated on `teamWorkspacesEnabled`); personal tier cards (facade `plans` + `TIER_PRICING` fallback, billing-cycle toggle); team column hosting `<CreditSlider>`; Enterprise card. Reuses `useBillingContext`; emits `subscribe`/`resubscribe` (personal) + `subscribeTeam` (team). - **`SubscriptionRequiredDialogContentUnified.vue`** — host; wires personal checkout through `useSubscriptionCheckout` (full flow incl. preview/transition + **new `'success'` step**); team checkout stubbed. - **Confirm/success screens (DES 3084-15873)** — aligned the reused `SubscriptionAddPaymentPreviewWorkspace` / `SubscriptionTransitionPreviewWorkspace` to DES-197 (drop `/member`, `comfy--credits` icon, plan-specific CTAs **"Subscribe to {plan}" / "Switch to {plan}"**); added **`SubscriptionSuccessWorkspace.vue`** ("You're all set") as the `'success'` checkout step. - **`showPricingTable`** — renders the unified host when `teamWorkspacesEnabled`, legacy `PricingTable.vue` for flag-off. (The old workspace-variant fork is removed; `PricingTableWorkspace.vue` becomes unused on flag-on → deleted at cutover.) - **i18n** — `subscription.planScope` / `teamPlan` / `enterprise` + `subscription.preview.{subscribeToPlan,switchToPlan}` + `subscription.success.*` keys. ## Strategy (new component + cutover — per FE-934) Build new, retire old at cutover (a later single PR deletes `PricingTable.vue` + `PricingTableWorkspace.vue` + the legacy dialog host + the dispatch fork; `TIER_PRICING` kept only as the flag-off/OSS fallback). Avoids half-migrated conditional cruft in the live tables. ## Screenshots Pricing captured live on the authenticated cloud-prod session (`local.comfy.org`, flag on). Confirm/success captured in Storybook (prop-driven) — a **personal** workspace can't reach these live until **B1/FE-966** flips its billing path off the legacy `/customers/*` adapter (whose `plans` is always empty); the screens themselves are unchanged regardless of source. **Pricing table** | Personal | Team | |---|---| | <img width="420" alt="pricing-personal" src="https://github.com/user-attachments/assets/2be3b8bc-ac54-41db-8c21-5c950d3e7338" /> | <img width="420" alt="pricing-team" src="https://github.com/user-attachments/assets/c4078eb4-ee7d-42f6-bcc3-375686ab7f1e" /> | **Confirm your payment / plan change** | New subscription | Plan change (Pro → Creator) | |---|---| | <img width="380" alt="confirm-new-subscription" src="https://github.com/user-attachments/assets/e371c744-dc64-43e8-b977-73f9f99f85bc" /> | <img width="380" alt="confirm-plan-change" src="https://github.com/user-attachments/assets/b1ee5ab3-c572-4c40-9b70-f078d66b78f4" /> | **You're all set (success)** <img width="380" alt="success-all-set" src="https://github.com/user-attachments/assets/8ec9e90f-8ba4-4cb0-81a7-6b4316e0c19e" /> ## ⚠️ BE-blocked (deferred) - **Team checkout**: the slider stop → plan-slug / subscribe-request shape is undefined (doc **Open Q#2** / "Team-slider contract", **BE-1254**). `subscribeTeam` is stubbed (toast "coming soon") until BE provides it. - **Live discount data**: stops come from the hardcoded **DES-197 fallback** (`teamPlanCreditStops.ts`) until `GET /api/billing/plans` carries them. - **Confirm "credits you'll get right away" line** + **async-success routing** (Stripe-tab/`pending_payment` → in-dialog success) deferred — see ticket notes; credits-cents unit is the open BE-1254 question. ## Verification - `vue-tsc --noEmit`: clean (pre-commit). - `oxlint`/`eslint`/`stylelint`/`oxfmt`: pass (pre-commit). - `vitest` `useSubscriptionDialog.test.ts` (11) + `useSubscriptionCheckout.test.ts` (17) + new `SubscriptionSuccessWorkspace.test.ts` (2): pass. - Personal checkout reuses the existing `useSubscriptionCheckout` flow; subscribed → new `'success'` step. ## Not in scope - Pixel-finalizing vs DES-197 (visual reference = Alex's **reference-only** #12042); personal-card detail refinement. - Settings / Misc-UX (FE-768/770); team-checkout wiring (BE contract). Design: **DES-197** / **3084-15873**. Survey: *FE Billing API Divergence* (B4 / P1 / P2 / P4). --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: GitHub Action <action@github.com> |
||
|
|
90c523b4a3 |
feat(dialog): flip remaining callers + default renderer to Reka (Phase 6a cutover, stacked on #12848. 6a-2) (#12593)
## Summary The **renderer cutover** for Phase 6: every remaining dialog caller is flipped to Reka, and `createDialog` now defaults `renderer: 'reka'` so the PrimeVue `Dialog` branch is no longer reached by default (it survives only as an explicit `renderer: 'primevue'` escape hatch, deleted in Phase 6b). > **Stacked on #12848** (mask editor + 3D viewer dialogs + dialog infra). Per @jtydhr88's review, the heavy, screenshot-bearing surface (3D + mask editor) was split into #12848 so it reviews and tests on its own. **Merge #12848 first**, then this PR's base auto-retargets to `main`. Parent: [FE-571](https://linear.app/comfyorg/issue/FE-571/dialog-system-migration-primevue-reka-ui-parent) This phase: [FE-578](https://linear.app/comfyorg/issue/FE-578/phase-6-remove-primevue-dialogconfirmdialog-imports-clean-up-css) ## Changes - **drop dead `ConfirmationService` registration** — zero `useConfirm`/`<ConfirmDialog>` consumers remain in `src/`; desktop-ui keeps its own. - **flip `showConfirmDialog`** — all six confirm callers render Reka chrome; width goes from PrimeVue auto-hug to fixed `size:'md'`, matching `dialogService.confirm()`. - **flip remaining `dialogService` + composable callers** — signin, update-password, top-up, workspace family, cancel-subscription, publish, cloud-notification, edit-keybinding / node-conflict / import-failed, upload-model, queue-clear-history, delete-assets, share / open-shared-workflow, subscription pricing. Self-styled panels get a shared transparent `w-fit` chrome replicating PrimeVue's auto-sized root. - **default `createDialog` to `renderer:'reka'`** — cuts over `showExtensionDialog` (third-party dialogs) and anything unflagged. The single-commit revert point. - **retarget class-based e2e selectors** — `BaseDialog` `.p-dialog` → `getByRole('dialog')`, `BuilderSaveAsHelper` close-X → `getByLabel`, `shareWorkflowDialog` role-based, dead `confirm-dialog` testid removed. - honor `[autofocus]` inside Reka dialogs; size the template browser dialog so the filter bar fits; drop redundant Tailwind width constraints on the remaining callers. ## Review focus 1. **`modal:false` on the pricing dialogs** — same trade-off as Settings/Manager (visual overlay without focus trap) because `PricingTable(.Workspace)` hosts a body-teleported PrimeVue `Popover`. 2. **`w-fit` shrink-wrapped chrome** for self-styled panels — replicates PrimeVue's shrink-to-fit root. 3. **Confirm width change** (auto-hug → fixed 576px `md`) — intentional consistency with `dialogService.confirm()`. ## Public API impact `createDialog` now defaults to Reka. Third-party extension dialogs render through Reka by default — a fixed `size:'md'` frame with a modal focus trap instead of PrimeVue auto-width; `renderer:'primevue'` remains an explicit escape hatch until Phase 6b. Worth a release note for extension authors. ## Out of scope (Phase 6b) PrimeVue branch deletion (`GlobalDialog.vue` legacy branch, `PrimeDialog` import, `.p-dialog` CSS/bridge tokens, `dialogStore` `pt`/`position`/`unstyled` typing) — lands after this soaks one cloud deploy cycle. ## 📸 Screenshots — manual verification Captured via Chrome DevTools (CDP) from this branch running locally in **cloud mode** (proxied to the `cloud.comfy.org` backend, free Personal Workspace). Every dialog below now renders through the **Reka** path — the PrimeVue `Dialog` branch is no longer reached. (Mask editor + 3D viewers live in the stacked base #12848.) **Confirm dialog** (`showConfirmDialog`) — Reka chrome at a fixed `size:'md'`, replacing PrimeVue's auto-hug width — *review focus #3* <img width="880" alt="confirm-dialog" src="https://github.com/user-attachments/assets/5d9953c1-4d0c-4ff9-adc7-88dd370c6a24" /> **Settings** — renders through Reka <img width="880" alt="settings" src="https://github.com/user-attachments/assets/44e3fd3f-8d9b-4322-8fbe-8ce8d94ed15d" /> **Edit Keybinding**, stacked on Settings — small-layout `w-fit` chrome; closing it leaves Settings open (stacked-dismiss holds) <img width="880" alt="edit-keybinding-nested" src="https://github.com/user-attachments/assets/d0875c00-7b9c-439d-b24d-ba6770009d08" /> **Subscription pricing** (`PricingTable`) — opened with `modal:false` because it hosts a body-teleported PrimeVue `Popover` — *review focus #1* <img width="880" alt="subscription-pricing" src="https://github.com/user-attachments/assets/3be20397-8a69-4b00-b803-73eff4e0e313" /> **Share** and **Publish** (open-shared-workflow + publish) — shared transparent shrink-wrapped (`w-fit`) chrome — *review focus #2* <img width="880" alt="share-dialog" src="https://github.com/user-attachments/assets/16f1c1b5-e35e-4664-a957-2f7f61ad96bd" /> <img width="880" alt="publish-dialog" src="https://github.com/user-attachments/assets/935ff453-5247-430f-9c21-2f500d4bc6e2" /> **Workspace** (workspace-family callers) <img width="880" alt="workspace-settings" src="https://github.com/user-attachments/assets/8031a352-f6fc-41e4-9567-e26e0c35ecd9" /> **Template selector** (`showExtensionDialog` / `useWorkflowTemplateSelectorDialog`) <img width="880" alt="templates-dialog" src="https://github.com/user-attachments/assets/9975ebbe-75ae-4ad9-a90a-248db4850e1a" /> **Account / workspace menu** (cloud) <img width="880" alt="account-menu" src="https://github.com/user-attachments/assets/5bc0cade-9bd9-49de-8bb4-779d65e211b0" /> |
||
|
|
bc885f383c |
Decouple run telemetry context from providers (#12925)
## Summary Move run-button context assembly out of telemetry providers so telemetry can initialize without importing app-mode/workspace state. ## Changes - **What**: Providers now accept completed `RunButtonProperties`; run-button call sites use a workspace composable to build that payload. - **Dependencies**: None. |
||
|
|
67b884d0f7 |
fix(billing): route subscription/sign-in/credit preconditions to modal, out of error panel (FE-878) (#12785)
## Summary
Account preconditions (sign-in / subscription / credits) on running a
workflow now open their modal directly and stay out of the error panel +
error count — previously `subscription_required` fell through to a red
"1 ERROR — Subscription required to queue workflows" banner. This covers
**both** paths: the `execution_error` websocket event and the `POST
/prompt` 402 queue paywall (`{ type: "PAYMENT_REQUIRED", message:
"Subscription required to queue workflows" }`), which is the exact
payload reported in #12840.
## Changes
- **What**: `execution_error` is classified by a pure
`accountPreconditionRouting` resolver (precedence sign-in > subscription
> credits) and routed to the existing modal via
`useAccountPreconditionDialog`; `executionStore` returns early for
preconditions so they never populate `lastExecutionError` /
`lastPromptError` / `lastNodeErrors` → fully excluded from the panel and
`totalErrorCount`. Runtime credit error at a node → credits modal (out
of panel; can name the node).
- **Queue paywall**: the `queuePrompt` catch resolves the same
precondition from the `POST /prompt` 402 response and opens the modal,
short-circuiting before `lastPromptError`, so the queue paywall stays
out of the panel too. The runtime matcher learns the `"Subscription
required to queue workflows"` message.
- **Breaking**: none.
## Before / After
Free-tier queue paywall (`POST /prompt` → 402) on a cloud build:
**Before** — raw `Subscription required to queue workflows` surfaced in
the error panel (no actionable upgrade):
<img width="1600" height="873" alt="before-error-panel"
src="https://github.com/user-attachments/assets/1b76b742-16bf-47e3-9245-17e35f8f1e70"
/>
**After** — clean subscription modal opens; nothing in the error panel
or error count:
<img width="1600" height="873" alt="after-subscription-modal"
src="https://github.com/user-attachments/assets/13d238cb-20bf-4795-a530-5abcf9968dc7"
/>
## Review Focus
- **Routing-only — the run button is intentionally untouched.** The
original AC#3 ("no Subscribe-to-Run button") is superseded by the FE-978
run-lock decision (pre-emptive role-aware lock, Figma 3253-18671).
Complements #12786 (FE-978 run-lock); disjoint file sets.
- Tests: `accountPreconditionRouting` / `useAccountPreconditionDialog` /
`executionStore` — each precondition routes to its modal and is excluded
from the panel/count; precedence resolves on co-occurrence. Plus
Playwright `browser_tests/tests/subscriptionPaywallError.spec.ts` — the
queue paywall (402) stays out of the error panel, with a control
asserting ordinary queue errors still surface. typecheck / oxlint /
eslint / stylelint / oxfmt / knip clean.
Fixes FE-878
Fixes #12840
|
||
|
|
543a39a6b0 |
fix(billing): DES review polish on workspace billing UI (#12917)
### before <img width="539" height="475" alt="Screenshot 2026-06-17 at 9 52 50 PM" src="https://github.com/user-attachments/assets/dd562cb4-870c-43de-aca4-81e0118735e9" /> ### after <img width="529" height="592" alt="Screenshot 2026-06-17 at 9 53 40 PM" src="https://github.com/user-attachments/assets/ba8ed01e-ac91-4654-bfaa-d8f73923f378" /> Design-review polish on the team-workspace billing UI (3 of the items from the Figma 'Team Plan - Workspaces' review). All three components are on main. ### 1. Remove dead 'Upgrade' badge in account popover `CurrentUserPopoverWorkspace.vue` — the white badge beside 'Plans & pricing' was gated on `canUpgrade`, which is hardcoded `false` (PRO is the only tier), so it never rendered. Removed the badge markup and the dead computed. (Figma node 2797-724189.) ### 2. Subscribe-to-Run button height `SubscribeToRun.vue` — used `size="sm"`, which didn't match the sibling run/queue button (an `h-8` button group it swaps with in the same slot via `CloudRunButtonWrapper`). Switched to `size="unset"` + `h-8 rounded-lg gap-1.5 px-4` to match. ### 3. Duplicate border/radius on member 'subscription inactive' dialog `useSubscriptionDialog.ts` — the layout dialog already zeroes the *content* border (`border-none shadow-none`), but the *root* pt kept only `bg-transparent`, so the dialog frame's default border+radius doubled with the card's own `rounded-2xl border`. Added `border-none rounded-none shadow-none` to root so only the card's single border/radius shows. (Figma node 3253-19473.) Surfaced during Billing V1 design review (team workspaces). --------- Co-authored-by: GitHub Action <action@github.com> |
||
|
|
5a846db6cf |
feat(billing): role-aware run-lock for cancelled/inactive team plans (FE-978) (#12786)
## Summary Cancelled / inactive team plans keep members but lock runs; the run button and the subscription-required dialog are now role-aware — owners are routed to the pricing/subscribe flow, members (who cannot subscribe) see "contact your workspace owner to resubscribe". ## Changes - **What**: `SubscribeToRun.vue` becomes a role-aware locked run button (owner → "Subscribe to Run"; member → neutral locked "Run" + contact-owner tooltip; both open the subscription dialog). `SubscriptionRequiredDialogContentWorkspace.vue` branches on role (member → read-only contact-owner panel, no pricing/subscribe affordance; owner → existing pricing/preview; member view suppressed for `out_of_credits` so the active-but-low-credits path is unchanged). `subscription.inactive.*` i18n keys. - **Breaking**: none. ## Review Focus - Role source = `useWorkspaceUI().permissions.value.canManageSubscription` (owner / personal = true, member = false) — the same accessor `SubscriptionPanelContentWorkspace.vue` uses. - **No BE work**: the run-gate already exists server-side (`InactiveSubscriptionError`; `is_active` checked before funds). The lock is gated on `is_active`, the same field the orchestrator uses, so FE/BE stay consistent; leftover-credits-while-inactive remains blocked by design. - Complements #12785 (FE-878 precondition→modal routing); disjoint file sets. Design: DES-197, Figma 3253-18670 / 3253-18671 / 3246-13962. - Tests: `SubscribeToRun` (4) / `CloudRunButtonWrapper` (3) / `SubscriptionRequiredDialogContentWorkspace` role cases — member sees contact-owner (no subscribe), owner sees pricing, run locks on `!is_active` and unlocks when active (22 total); full `test:unit` green. Fixes FE-978 |
||
|
|
c36da042d0 |
Redesign error tab cards with summary hero and unified sections (#12828)
## Summary Redesigns the Errors tab cards to match the new Figma error-panel spec (file `Czv0JcCfcUiizeEZURevpq`): every error group is now wrapped in a single bordered card led by an error-count summary hero, with each category rendered as a collapsible section whose count lives in a circular badge rather than a parenthetical title suffix. > Rebased onto `main` after #12793 was merged. This PR now contains only the error-card redesign slice. ## Changes - **What**: - **New `ErrorCardSection.vue`** — the shared section shell used by every error type. Renders a 32px header (circular count badge + neutral title + `actions` slot + collapse chevron) and a `TransitionCollapse` body. Replaces the per-group `PropertiesAccordionItem`, dropping the old octagon-alert icon + red title + `(n)` suffix and the sticky-header behavior. - **`TabErrors.vue`** — wraps all groups in one `rounded-lg` card bordered with `secondary-background`. Adds a summary **hero** (large severity-colored total count, vertical divider, "N Errors detected / Resolve before running the workflow"). Moves the per-group action buttons (Install All / Replace All / missing-model Refresh) into the section's `actions` slot. Adds `getGroupCount()` / `totalErrorCount` and switches content background to `interface-panel-surface`. Most of the line count here is re-indentation from the template restructure, not behavior change. - **`missingErrorResolver.ts`** — drops the `formatCountTitle` helper so display titles are `"Missing Models"` instead of `"Missing Models (4)"`; the badge now carries the count. Toast titles/messages are untouched. - **`ErrorNodeCard.vue`** — restyles the runtime/validation error-log box to the Figma spec: borderless `base-foreground/5` surface, `ERROR LOG` header, 12px non-mono body at 50% opacity, inset footer divider with Get Help / Find on GitHub links. - **Row components** (`MissingModelRow`, `MissingPackGroupRow`, `SwapNodeGroupRow`, `MissingMediaCard`, `MissingNodeCard`, `MissingModelCard`) — align spacing, fonts, badges, and button sizes with Figma: 12px row labels, `size="sm"` (24px) action buttons, 16px count badges (`rounded-sm`, `secondary-background-hover`, 9px), 32px reference-row heights, `px-3` card padding. Model-name wrapping is kept independent of its count badge and link button so they never reflow into the metadata sub-label. - **i18n** — adds `errorsDetected` (pluralized), `resolveBeforeRun`, `expand`, `collapse` to `en/main.json`. - **Breaking**: None. No store, composable, action, or data-flow changes — all handlers and emitted events are preserved. The only user-visible copy change is the removal of the `(n)` count suffix from section titles. ## Review Focus - **Title copy change**: `"Missing Models (4)"` → `"Missing Models"`. Search-filter matching against the old `(n)` string no longer applies, but the count is shown by the badge and the hero total. - **Sticky header removed**: section headers no longer pin to the top on scroll (intentional per the new design). - **Collapse click target**: the old single-button header (which nested action buttons inside a `<button>` — invalid HTML) is split into a separate title button and chevron button. Behavior is unchanged and accessibility improves; the empty space beside an action button no longer toggles collapse. - All semantic colors map to existing design-system tokens (no `dark:` variants, no hardcoded hex). Verified the artifact hex values match the tokens (e.g. `#262729` = `secondary-background`, `#e04e48` = `destructive-background-hover`, `#171718` = `interface-panel-surface`). ## Follow-up This PR intentionally keeps the error-count ownership cleanup out of the current diff so the card redesign remains reviewable. A follow-up PR will centralize error counting around a single source of truth so the Errors tab summary hero, section badges, and any overlay surfaces cannot drift from one another. That follow-up will also address the current count mismatch in the ErrorOverlay and continue the ErrorOverlay redesign there, instead of expanding this PR after review. ## Screenshots (if applicable) After <img width="603" height="703" alt="스크린샷 2026-06-13 오후 1 00 02" src="https://github.com/user-attachments/assets/065d7c19-9748-4e99-9b43-675a31e92949" /> <img width="601" height="197" alt="스크린샷 2026-06-13 오후 1 01 07" src="https://github.com/user-attachments/assets/0fa1fbda-9091-4a45-9eca-e99c43089c0e" /> <img width="617" height="612" alt="스크린샷 2026-06-13 오후 1 02 43" src="https://github.com/user-attachments/assets/3d67a057-bf65-4e51-bcf5-70ecce851826" /> <img width="495" height="723" alt="스크린샷 2026-06-13 오후 1 03 28" src="https://github.com/user-attachments/assets/6dcc4021-0fc3-4955-a68b-c0533c66a3cf" /> --------- Co-authored-by: GitHub Action <action@github.com> |
||
|
|
ef93b4696c |
feat(billing): team-plan CreditSlider component — 5 fixed stops (FE-935) (#12644)
## What https://a46f3266.comfy-storybook.pages.dev/?path=/docs/components-creditslider--docs&globals=theme:dark <img width="560" height="288" alt="cs12644_dark" src="https://github.com/user-attachments/assets/c06b5244-d178-4fa5-8bb9-61fd8595fe9b" /> <img width="560" height="288" alt="cs12644_light" src="https://github.com/user-attachments/assets/16626333-43ba-4541-bd11-faaa8513b1e8" /> Adds **CreditSlider** — the team-plan credit-subscription slider from Figma **DES-197** — as a standalone presentational component. **B4 standalone slice (FE-935)**; parent **FE-934**. ## Why it can ship now (no backend dependency) The slider's 5 stops are **locked in DES-197**, so this component is built and reviewable independently of the (still-TBD) backend slider contract. Wiring it into the pricing table is deferred to FE-934. ## How it works - 5 fixed stops — **200 / 400 / 700 / 1,400 / 2,500 USD** ↔ 42,200 / 84,400 / 147,700 / 295,400 / 527,500 credits; default **$700**. - Snap-to-stop is guaranteed by driving the shared `src/components/ui/slider/Slider.vue` (reka-ui) in **index space** (`:min="0" :max="4" :step="1"`) — the thumb can only land on the 5 stops, with free keyboard-arrow support + ARIA from reka-ui. - `v-model` carries the selected **USD** value; a `change` event also emits `{ index, usd, credits }` for the future pricing-table wiring. - Thresholds live in a typed constant `teamPlanCreditStops.ts` (sibling to `tierPricing.ts`), **hardcoded per DES-197** with a `TODO(FE-934)` to source from `GET /api/billing/plans` once the BE contract lands. The credit figures equal `usdToCredits(usd)` (rate 211); a test guards against rate drift. ## Files - `src/platform/cloud/subscription/components/CreditSlider.vue` (+ `CreditSlider.stories.ts`, `CreditSlider.test.ts`) - `src/platform/cloud/subscription/constants/teamPlanCreditStops.ts` ## Verification - `vue-tsc --noEmit`: clean. - `oxlint --type-aware`: 0 errors / 0 warnings. - `vitest run`: **11/11 pass** — default stop, ArrowRight/ArrowLeft snap to the adjacent stop (never in between), `change` payload, disabled state, BE-sourced stops override, empty `stops` renders nothing, all 5 labels render, and the credit-rate-drift guard. ## Not in scope - Wiring into `PricingTableWorkspace` / the team-plan card (FE-934, blocked on the BE slider contract). - The marketing caption and card layout around the slider (parent component's concern). Design source: Figma **DES-197** (Team Plan / Workspaces). --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
671e0cecdf |
chore: upgrade ESLint to v10.4.0 (#12517)
## Summary
Upgrade ESLint from v9 to v10.4.0, bump compatible plugins/configs, and
fix the 32 violations introduced by the new `eslint:recommended` rules.
## Changes
- **What**:
- Catalog bumps in `pnpm-workspace.yaml`:
- `eslint` 9.39.1 → 10.4.0
- `@eslint/js` 9.39.1 → 10.0.1
- `typescript-eslint` 8.49.0 → 8.60.0
- `eslint-plugin-vue` 10.6.2 → 10.9.1
- `eslint-plugin-import-x` 4.16.1 → 4.16.2
- `eslint-plugin-unused-imports` 4.3.0 → 4.4.1
- `@intlify/eslint-plugin-vue-i18n` 4.1.1 → 4.5.0
- Fixed 4 `preserve-caught-error` violations by passing `{ cause }` to
rethrown errors in `useMaskEditorLoader.ts` and `usePainter.ts`.
- Fixed 28 `no-useless-assignment` violations across litegraph
(`LGraph.ts`, `LGraphCanvas.ts`, `LiteGraphGlobal.ts`, `polyfills.ts`)
and seven other files (`colorUtil.ts`, `linkFixer.ts`, `usePaste.ts`,
`audioService.ts`, `versionUtil.ts`, `subscriptionCheckoutTracker.ts`,
`ShiftClick.test.ts`) by removing dead initializers or redundant writes.
- Removed two now-unnecessary `@ts-expect-error` directives on
`importX.flatConfigs.*` (newer plugin ships correct types).
- **Breaking**: None for runtime; consumers using ESLint locally must
use Node ≥20.19/22.13/24 (already required by repo `engines.node:
">=25"`).
## Review Focus
- Audit of the [v10 migration
guide](https://eslint.org/docs/latest/use/migrate-to-10.0.0) found no
other patterns in this codebase to address (no `eslint-env` comments, no
removed `context.*`/`SourceCode` APIs, no `RuleTester` usage, no
affected rule configs).
- `no-useless-assignment` fixes were chosen conservatively: prefer
keeping the variable with a typed declaration over deleting the
statement, except where the assignment was clearly dead after the last
read.
- Per-file diffs in litegraph (especially `LGraphCanvas.ts`) — worth a
glance to confirm intent.
## Verification
- `pnpm exec eslint --version` → v10.4.0
- `pnpm exec eslint src` → 0 errors
- `pnpm typecheck` → clean
- Unit tests for every touched file pass (319/319 in spot checks:
`colorUtil`, `linkFixer`, `audioService`, `usePaste`, `usePainter`,
`versionUtil`, `LGraph`, `litegraph`, all `LGraphCanvas.*`,
`ShiftClick`).
Co-authored-by: Amp <amp@ampcode.com>
|
||
|
|
25c2d828c0 |
test: enable vitest/consistent-each-for and migrate .each → .for (#12161)
*PR Created by the Glary-Bot Agent*
---
Enables the oxlint rule `vitest/consistent-each-for` (configured to
prefer `.for` for `test`, `it`, `describe`, and `suite`) and migrates
every `.each` parameterized test in the repo to `.for`. Using `.for`
avoids accidentally splatting tuple elements into separate callback
arguments and exposes `TestContext` as the second callback argument.
The first commit covers the 38 lint-detected files (88 callsites):
renames `.each` → `.for` and updates callback signatures to destructure
when the data is an array of tuples (objects/primitives already work
unchanged with `.for`).
The follow-up commit addresses code review feedback: oxlint's rule does
not recognize `test.each` on extended test bases
(`baseTest.extend(...)`) and skips files in `ignorePatterns`
(`src/extensions/core/*`). These were converted manually so the policy
is uniform across the codebase.
## Verification
- `node_modules/.bin/oxlint src` — 0 errors, 0 `consistent-each-for`
violations
- `pnpm typecheck` — passes
- `pnpm test:unit` — all modified test files pass; pre-existing
environmental flakes (`GraphView.test.ts`, `ColorWidget.test.ts`, etc.,
unchanged here and flaky on `main` in this sandbox) are unrelated
- `pnpm lint` / `pnpm knip` — clean
- Manual verification: 362 tests across 6 representative converted
suites re-run in an interactive shell — all passing
Manual UI verification (Playwright/screenshots) is not applicable:
changes are test-file-only refactors with no production runtime or UI
behavior change.
## Notes on `.for` semantics
- Array-of-tuples (`[[a, b], ...]`) passes the tuple as a single arg, so
callbacks were changed from `(a, b) => …` to `([a, b]) => …`.
- Array-of-objects (`[{a}, …]`) already used destructuring — unchanged.
- Array-of-primitives (`['a', …]`) — callback signature unchanged.
- A handful of complex cases use a small `type Case = [...]` alias plus
`it.for<Case>([...])` to preserve tuple inference where TS narrowed
unions otherwise broke parameter types.
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-12161-test-enable-vitest-consistent-each-for-and-migrate-each-for-35e6d73d3650810c9417e07bdd9f27a2)
by [Unito](https://www.unito.io)
---------
Co-authored-by: Glary-Bot <glary-bot@users.noreply.github.com>
|
||
|
|
2b010ac8b3 |
fix: dedupe pending checkout attempt construction (#11622)
## Summary Deduplicates pending subscription checkout attempt construction so storage and fallback paths share the same payload creation. ## Changes - **What**: Build the `PendingSubscriptionCheckoutAttempt` once in `recordPendingSubscriptionCheckoutAttempt()` and reuse it for unavailable-storage, failed-write, and successful-write paths. - **Dependencies**: None. ## Review Focus This is intended as a no-behavior-change cleanup: unavailable storage still returns an attempt, failed `setItem()` still returns that attempt without dispatching, and the pending-checkout event only fires after a successful storage write. Linear: FE-209 ## Screenshots (if applicable) N/A --------- Co-authored-by: GitHub Action <action@github.com> |
||
|
|
ef59f46495 |
refactor: migrate cn imports from @/utils/tailwindUtil shim to @comfyorg/tailwind-utils directly (#11453)
*PR Created by the Glary-Bot Agent* --- ## Summary - Replace all `cn` / `ClassValue` imports from the `@/utils/tailwindUtil` re-export shim with direct imports from `@comfyorg/tailwind-utils` across 198 source files in `src/` and 3 in `apps/desktop-ui/` - Delete both shim files (`src/utils/tailwindUtil.ts` and `apps/desktop-ui/src/utils/tailwindUtil.ts`) - Add explicit `@comfyorg/tailwind-utils` dependency to `apps/desktop-ui/package.json` - Update documentation references in `AGENTS.md`, `docs/guidance/design-standards.md`, and `docs/guidance/vue-components.md` Fixes #11288 ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-11453-refactor-migrate-cn-imports-from-utils-tailwindUtil-shim-to-comfyorg-tailwind-utils--3486d73d365081ec92cce91fbf88e6e4) by [Unito](https://www.unito.io) --------- Co-authored-by: Glary-Bot <glary-bot@users.noreply.github.com> Co-authored-by: GitHub Action <action@github.com> Co-authored-by: Alexander Brown <drjkl@comfy.org> |
||
|
|
799ffcf4b6 |
test: cover useWorkspaceUI and useWorkspaceBilling (#11319)
Closes coverage gaps in \`src/platform/workspace/composables/\` as part of the unit-test backfill. ## Testing focus \`useWorkspaceUI\` is wrapped in \`createSharedComposable\` (shared instance across all callers). \`useWorkspaceBilling\` is a large stateful composable: parallel API calls, exponential-backoff polling, computed mappers, lifecycle cleanup. Both need careful state isolation and real lifecycle behavior — not faked hooks. ### \`useWorkspaceUI\` (8 tests) - **Permission / UI-config matrix.** Three role/type combinations — (personal × any), (team × owner), (team × member) — plus the no-active-workspace default. Assertions target concrete flags that differ per role (the table itself is the contract), not the return shape. - **\`createSharedComposable\` identity invariant.** Multiple calls return the same instance. - **Isolation.** Each test uses \`vi.resetModules()\` to get a fresh shared instance so the memoization doesn't leak between cases. ### \`useWorkspaceBilling\` (23 tests) - **Parallel init.** \`initialize\` runs \`Promise.all([status, balance, plans])\` concurrently, then re-fetches balance when free-tier shows a zero amount (lazy credit grant path). - **Polling with fake timers.** \`cancelSubscription\`'s exponential backoff (\`1000 * 2^attempt\`, max 5000ms) driven by \`vi.useFakeTimers()\` + \`advanceTimersByTimeAsync()\`. Covers success, failure, and the unmount-stops-polling case. - **Real lifecycle.** \`onBeforeUnmount\` only fires inside a component instance — not inside a raw \`effectScope\`. The unmount test mounts a minimal Vue app via \`createApp\` / \`app.unmount\` so the production cleanup path actually runs. - **Computed getter mapping.** \`subscription\`, \`balance\`, \`isActiveSubscription\`, \`isFreeTier\` assert the snake_case API shape is remapped to the camelCase UI shape correctly. - **\`window\` effects.** \`window.open\` stubbed via \`vi.spyOn\`, \`window.location.href\` via \`vi.stubGlobal\`. Restored in \`afterEach\`. ## Principles applied - No mocks of \`vue\` or \`@vueuse/core\` — only our own workspace API, stores, and sibling composables. - Behavioral assertions only. - All 31 tests pass; typecheck/lint/format clean. Test-only; no production code touched. |
||
|
|
ecb7fd4796 |
feat: add frontend subscription success recovery (#11286)
Improving our subscription detection system. Optimal will have to come after BE team brings personal billing to cloud repo off of comfy api. ## Summary - replace the dialog-local focus poller with a frontend checkout tracker stored in `localStorage` - recover pending subscription checkouts from app boot plus global page lifecycle (`pageshow`, `visibilitychange`) with bounded retries only while an attempt is pending - emit `subscription_success` through GTM with frontend-derived metadata once subscription state reaches the expected target tier/cycle ## Why This is the frontend-only 80/20 path. It fixes the brittle "old tab must regain focus" behavior without adding new backend endpoints or backend event storage. The browser records one pending checkout attempt when checkout is opened, and any returning cloud tab can recover it later by comparing current subscription state against the expected target plan. ## Tradeoffs - browser-scoped, not backend-authoritative - no server transaction id - scheduled downgrades through the billing portal are intentionally not inferred as immediate success events - still best-effort compared with the backend outbox/WebSocket approach ## Validation - `pnpm exec vitest run src/platform/cloud/subscription/composables/useSubscription.test.ts src/platform/telemetry/providers/cloud/GtmTelemetryProvider.test.ts` - `pnpm typecheck` ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-11286-feat-add-frontend-subscription-success-recovery-3436d73d3650814d9f74c89e6926aa84) by [Unito](https://www.unito.io) |
||
|
|
7ffaff7e1b |
test: cover useBillingPlans and tierBenefits (#11318)
Closes coverage gaps in `src/platform/cloud/subscription/` as part of the unit-test backfill. ## Testing focus `useBillingPlans` holds **module-scoped refs** (`plans`, `currentPlanSlug`, `isLoading`, `error`). If state leaks between tests, failures get masked as false-green. The suite uses `vi.resetModules()` + dynamic `import()` in every test to get a fresh instance — state isolation is the primary design constraint here. ### `useBillingPlans` (12 tests) - **Concurrent-call dedup.** The \`isLoading\` guard is validated by creating a pending promise, firing a second \`fetchPlans()\` while the first is in-flight, and asserting the mock is called **exactly once**. - **Error branching.** \`Error\` instance → \`.message\` captured. Non-Error rejection → fallback string (\`'Failed to fetch plans'\`). Both paths also verify \`console.error\` logging via a spy. - **Error-reset invariant.** After a failure, a subsequent success must null out \`error.value\` — order-dependent and easy to regress. - **Shared-state invariant.** Two separate \`useBillingPlans()\` calls return refs pointing at the same module-level state. - **Computed filtering.** \`monthlyPlans\` / \`annualPlans\` partition by duration — assertions on distinct output, not input re-assertion. ### \`tierBenefits\` (7 tests) - Table-driven across all \`TierKey\` values for \`maxDuration\`, \`addCredits\`, \`customLoRAs\` branches. - \`monthlyCredits\` free-tier path including the \`remoteConfig.free_tier_credits\` null fallback. - Translator/formatter forwarding verified by spy. ## Principles applied - No mocks of \`vue\`, \`pinia\`, or \`@vueuse/core\` — only our own \`workspaceApi\`. - Behavioral assertions only — no return-shape checks. - All 19 tests pass; typecheck/lint/format clean. Test-only; no production code touched. |
||
|
|
f90d6cf607 |
test: migrate 132 test files from @vue/test-utils to @testing-library/vue (#10965)
## Summary Migrate 132 test files from `@vue/test-utils` (VTU) to `@testing-library/vue` (VTL) with `@testing-library/user-event`, adopting user-centric behavioral testing patterns across the codebase. ## Changes - **What**: Systematic migration of component/unit tests from VTU's `mount`/`wrapper` API to VTL's `render`/`screen`/`userEvent` API across 132 files in `src/` - **Breaking**: None — test-only changes, no production code affected ### Migration breakdown | Batch | Files | Description | |-------|-------|-------------| | 1 | 19 | Simple render/assert tests | | 2A | 16 | Interactive tests with user events | | 2B-1 | 14 | Interactive tests (continued) | | 2B-2 | 32 | Interactive tests (continued) | | 3A–3E | 51 | Complex tests (stores, composables, heavy mocking) | | Lint fix | 7 | `await` on `fireEvent` calls for `no-floating-promises` | | Review fixes | 15 | Address CodeRabbit feedback (3 rounds) | ### Review feedback addressed - Removed class-based assertions (`text-ellipsis`, `pr-3`, `.pi-save`, `.skeleton`, `.bg-black\/15`, Tailwind utilities) in favor of behavioral/accessible queries - Added null guards before `querySelector` casts - Added `expect(roots).toHaveLength(N)` guards before indexed NodeList access - Wrapped fake timer tests in `try/finally` for guaranteed cleanup - Split double-render tests into focused single-render tests - Replaced CSS class selectors with `screen.getByText`/`screen.getByRole` queries - Updated stubs to use semantic `role`/`aria-label` instead of CSS classes - Consolidated redundant edge-case tests - Removed manual `document.body.appendChild` in favor of VTL container management - Used distinct mock return values to verify command wiring ### VTU holdouts (2 files) These files intentionally retain `@vue/test-utils` because their components use `<script setup>` without `defineExpose`, making internal computed properties and methods inaccessible via VTL: 1. **`NodeWidgets.test.ts`** — partial VTU for `vm.processedWidgets` 2. **`WidgetSelectDropdown.test.ts`** — full VTU for heavy `wrapper.vm.*` access ## Follow-up Deferred items (`ComponentProps` typing, camelCase listener props) tracked in #10966. ## Review Focus - Test correctness: all migrated tests preserve original behavioral coverage - VTL idioms: proper use of `screen` queries, `userEvent`, and accessibility-based selectors - The 2 VTU holdout files are intentional, not oversights ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-10965-test-migrate-132-test-files-from-vue-test-utils-to-testing-library-vue-33c6d73d36508199a6a7e513cf5d8296) by [Unito](https://www.unito.io) --------- Co-authored-by: Amp <amp@ampcode.com> Co-authored-by: Christian Byrne <cbyrne@comfy.org> |
||
|
|
8b6c1b3649 |
refactor: consolidate SubscriptionTier type (#10487)
## Summary Consolidate the `SubscriptionTier` type from 3 independent definitions into a single source of truth in `tierPricing.ts`. ## Changes - **What**: Exported `SubscriptionTier` from `tierPricing.ts`. Removed hand-written unions from `workspaceApi.ts` (lines 80-88), `PricingTable.vue`, and `PricingTableWorkspace.vue`. All now import from the canonical location. - **Files**: 4 files changed (type-only, ~5 net lines) ## Review Focus - This is a type-only change — `pnpm typecheck` is the primary validation - If the OpenAPI schema ever adds tiers, there is now one place to update ## Stack PR 5/5: #10483 → #10484 → #10485 → #10486 → **→ This PR** |
||
|
|
5b4ebf4d99 |
test: audit skipped tests — prune stale, re-enable stable, remove dead code (#10312)
## Summary Audit all skipped/fixme tests: delete stale tests whose underlying features were removed, re-enable tests that pass with minimal fixes, and remove orphaned production code that only the deleted tests exercised. Net result: **−2,350 lines** across 50 files. ## Changes - **Pruned stale skipped tests** (entire files deleted): - `LGraph.configure.test.ts`, `LGraph.constructor.test.ts` — tested removed LGraph constructor paths - `LGraphCanvas.ghostAutoPan.test.ts`, `LGraphCanvas.linkDragAutoPan.test.ts`, `useAutoPan.test.ts`, `useSlotLinkInteraction.autoPan.test.ts` — tested removed auto-pan feature - `useNodePointerInteractions.test.ts` — single skipped test for removed callback - `ImageLightbox.test.ts` — component replaced by `MediaLightbox` - `appModeWidgetRename.spec.ts` (E2E) — feature removed; helper `AppModeHelper.ts` also deleted - `domWidget.spec.ts`, `widget.spec.ts` (E2E) — tested removed widget behavior - **Removed orphaned production code** surfaced by test pruning: - `useAutoPan.ts` — composable + 93 lines of auto-pan logic in `LGraphCanvas.ts` - `ImageLightbox.vue` — replaced by `MediaLightbox` - Auto-pan integration in `useSlotLinkInteraction.ts` and `useNodeDrag.ts` - Dead settings (`LinkSnapping.AutoPanSpeed`, `LinkSnapping.AutoPanMargin`) in `coreSettings.ts` and `useLitegraphSettings.ts` - Unused subgraph methods (`SubgraphNode.getExposedInput`, `SubgraphInput.getParentInput`) - Dead i18n key, dead API schema field, dead fixture exports (`dirtyTest`, `basicSerialisableGraph`) - Dead test utility `litegraphTestUtils.ts` - **Re-enabled skipped tests with minimal fixes**: - `useBrowserTabTitle.test.ts` — removed skip, test passes as-is - `eventUtils.test.ts` — replaced MSW dependency with direct `fetch` mock - `SubscriptionPanel.test.ts` — stabilized button selectors, timezone-safe date assertion - `LinkConnector.test.ts` — removed stale describe blocks, kept passing suite - `widgetUtil.test.ts` — removed skipped tests for deleted functionality - `comfyManagerStore.test.ts` — removed skipped `isPackInstalling` / `action buttons` / `loading states` blocks - **Re-enabled then re-skipped 3 flaky E2E tests** (fail in CI for pre-existing reasons): - `browserTabTitle.spec.ts` — canvas click timeout (element not visible) - `groupNode.spec.ts` — screenshot diff (stale golden image) - `nodeSearchBox.spec.ts` — `p-dialog-mask` intercepts pointer events - **Simplified production code** alongside test cleanup: - `useNodeDrag.ts` — removed auto-pan integration, simplified from 170→100 lines - `DropZone.vue` — refactored URL-drop handling, removed unused code path - `ToInputFromIoNodeLink.ts`, `SubgraphInputEventMap.ts` — removed dead subgraph wiring - **Dependencies**: none - **Breaking**: none (all removed code was internal/unused) ## Review Focus - Confirm deleted production code (`useAutoPan`, `ImageLightbox`, subgraph methods) has no remaining callers - Validate that simplified `useNodeDrag.ts` preserves drag behavior without auto-pan - Check that re-skipped E2E tests have clear skip reasons for future triage ## Screenshots (if applicable) N/A --------- Co-authored-by: Amp <amp@ampcode.com> Co-authored-by: github-actions <github-actions@github.com> |
||
|
|
d2f4d41960 |
test: make SubscriptionPanel refill date test timezone-agnostic (#10618)
## Summary Fix timezone-dependent test failure in SubscriptionPanel and add a local CI script. ## Changes - **What**: The `renders refill date with literal slashes` test hardcoded `12/31/24` but the component renders using local timezone `Date` methods. In UTC-negative timezones, `2024-12-31T00:00:00Z` renders as Dec 30. Now computes the expected string the same way the component does. - **What**: Added `pnpm test:ci:local` script (`scripts/test-ci-local.sh`) that builds the frontend, starts a ComfyUI backend with `--multi-user --front-end-root dist`, runs vitest + Playwright, then cleans up. One command for full local CI. ## Review Focus This is a test-only change — no production code modified. The SubscriptionPanel component itself is unchanged; only the test assertion is made timezone-agnostic. ## E2E Regression Test Not applicable — this PR fixes a unit test assertion, not a production bug. No user-facing behavior changed. |
||
|
|
62979e3818 |
refactor: rename firebaseAuthStore to authStore with shared test fixtures (#10483)
## Summary Rename `useFirebaseAuthStore` → `useAuthStore` and `FirebaseAuthStoreError` → `AuthStoreError`. Introduce shared mock factory (`authStoreMock.ts`) to replace 16 independent bespoke mocks. ## Changes - **What**: Mechanical rename of store, composable, class, and store ID (`firebaseAuth` → `auth`). Created `src/stores/__tests__/authStoreMock.ts` — a shared mock factory with reactive controls, used by all consuming test files. Migrated all 16 test files from ad-hoc mocks to the shared factory. - **Files**: 62 files changed (rename propagation + new test infra) ## Review Focus - Mock factory API design in `authStoreMock.ts` — covers all store properties with reactive `controls` for per-test customization - Self-test in `authStoreMock.test.ts` validates computed reactivity Fixes #8219 ## Stack This is PR 1/5 in a stacked refactoring series: 1. **→ This PR**: Rename + shared test fixtures 2. #10484: Extract auth-routing from workspaceApi 3. #10485: Auth token priority tests 4. #10486: Decompose MembersPanelContent 5. #10487: Consolidate SubscriptionTier type --------- Co-authored-by: Alexander Brown <drjkl@comfy.org> |
||
|
|
a1c46d7086 |
fix: replace hardcoded font-size 10px/11px with text-2xs Tailwind token (#10604)
## Summary Replace all hardcoded `text-[10px]`, `text-[11px]`, and `font-size: 10px` with a new `text-2xs` Tailwind theme token (0.625rem / 10px). ## Changes - **What**: Add `--text-2xs` custom theme token to design system CSS and replace 14 hardcoded font-size occurrences across 12 files with `text-2xs`. ## Review Focus Consistent use of design tokens instead of arbitrary values for small font sizes. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-10604-fix-replace-hardcoded-font-size-10px-11px-with-text-2xs-Tailwind-token-3306d73d365081dfa1ebdc278e0a20b7) by [Unito](https://www.unito.io) Co-authored-by: Amp <amp@ampcode.com> |
||
|
|
cd45efa983 |
feat: differentiate personal/team pricing table with two-stage team workspace flow (#9901)
## Summary Differentiates the subscription pricing dialog between personal and team workspaces with distinct visual treatments and a two-stage team workspace upgrade flow. ### Changes - **Personal pricing dialog**: Shows "P" avatar badge, "Plans for Personal Workspace" header, and "Solo use only – Need team workspace?" banner on each tier card - **Team pricing dialog**: Shows workspace avatar, "Plans for Team Workspace" header (emerald), green "Invite up to X members" badge, and emerald border on Creator card - **Two-stage upgrade flow**: "Need team workspace?" → closes pricing → opens CreateWorkspaceDialog → sessionStorage flag → page reload → WorkspaceAuthGate auto-opens team pricing dialog - **Spacing**: Reduced vertical gaps/padding/font sizes so the table fits without scrolling ### Key decisions - sessionStorage key `comfy:resume-team-pricing` bridges the page reload during workspace creation - `onChooseTeam` prop is conditionally passed only to the personal variant - `resumePendingPricingFlow()` is called from WorkspaceAuthGate after workspace initialization ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9901-feat-differentiate-personal-team-pricing-table-with-two-stage-team-workspace-flow-3226d73d365081e7af60dcca86e83673) by [Unito](https://www.unito.io) |
||
|
|
6c14802425 |
feat: fire subscription_success telemetry on subscription activation (#10186)
## Summary
Fire a client-side `subscription_success` event to GTM when a
subscription activates, enabling LinkedIn and Meta conversion tracking
tags.
## Changes
- **What**: Wire `trackMonthlySubscriptionSucceeded()` into both
subscription detection paths (legacy dialog watcher + workspace
billingOperationStore). This method existed but had zero call sites
(dead code). The event name remains `subscription_success` (not
`purchase`) to avoid double-counting with the server-side GA4
Measurement Protocol purchase event and the existing Google Ads Purchase
conversion tag in GTM.
## Review Focus
- billingOperationStore only fires telemetry for `subscription`
operations, not `topup`.
- Legacy flow: telemetry fires in the existing `isActiveSubscription`
watcher, before `emit("close", true)`.
- Workspace flow: telemetry fires in `handleSuccess()` after status
update but before billing context refresh.
- No event name change: `subscription_success` was the original name and
avoids collision with the server-side `purchase` event.
---------
Co-authored-by: Benjamin Lu <benjaminlu1107@gmail.com>
|
||
|
|
513510104c |
fix: prevent subscription UI from rendering on non-cloud distributions (#9958)
## Summary Prevent Plans & Pricing dialog, subscription buttons, and cloud-only menu items from appearing on desktop/localhost distributions. ## Changes - **What**: Add `isCloud` guards to `useSubscriptionDialog.showPricingTable`, `TopbarSubscribeButton`, and `CurrentUserPopoverLegacy` so subscription UI only renders on cloud - **Tests**: 24 tests across 3 test files (1 modified, 2 new) covering cloud/non-cloud behavior ## Review Focus - Guard placement in `CurrentUserPopoverLegacy.vue` — multiple `v-if` conditions updated to include `isCloud` - Early-return in `showPricingTable` as a defense-in-depth measure Fixes COM-16820 ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9958-fix-prevent-subscription-UI-from-rendering-on-non-cloud-distributions-3246d73d365081559a9ee8650409c5b4) by [Unito](https://www.unito.io) Co-authored-by: Benjamin Lu <benjaminlu1107@gmail.com> |
||
|
|
ef4e4a69d5 |
fix: enable enforce-consistent-class-order tailwind lint rule (#9428)
## Summary Enable `better-tailwindcss/enforce-consistent-class-order` lint rule and auto-fix all 1027 violations across 263 files. Stacked on #9427. ## Changes - **What**: Sort Tailwind classes into consistent order via `eslint --fix` - Enable `enforce-consistent-class-order` as `'error'` in eslint config - Purely cosmetic reordering — no behavioral or visual changes ## Review Focus Mechanical auto-fix PR — all changes are class reordering only. This is the largest diff but lowest risk since it changes no class names, only their order. **Stack:** #9417 → #9427 → **this PR** Fixes #9300 (partial — 3 of 3 rules) ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9428-fix-enable-enforce-consistent-class-order-tailwind-lint-rule-31a6d73d3650811c9065f5178ba3e724) by [Unito](https://www.unito.io) |
||
|
|
1221756e05 |
fix: enable enforce-canonical-classes tailwind lint rule (#9427)
## Summary Enable `better-tailwindcss/enforce-canonical-classes` lint rule and auto-fix all 611 violations across 173 files. Stacked on #9417. ## Changes - **What**: Simplify Tailwind classes to canonical forms via `eslint --fix`: - `h-X w-X` → `size-X` - `overflow-x-hidden overflow-y-hidden` → `overflow-hidden` - and other canonical simplifications - Enable `enforce-canonical-classes` as `'error'` in eslint config ## Review Focus Mechanical auto-fix PR — all changes produced by `eslint --fix`. No visual or behavioral changes; canonical forms are functionally identical. **Stack:** #9417 → **this PR** → PR 3 (class order) Fixes #9300 (partial — 2 of 3 rules) ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9427-fix-enable-enforce-canonical-classes-tailwind-lint-rule-31a6d73d365081a49340d7d4640ede45) by [Unito](https://www.unito.io) |
||
|
|
589f58f916 |
feat: add ever-present upgrade button for free-tier users (#9315)
## Summary Add persistent upgrade CTAs for free-tier users: a topbar button and "Upgrade to add credits" replacing "Add Credits" in popovers and settings panels. ## Changes - **What**: - New `TopbarSubscribeButton` component in both GraphCanvas and LinearView topbars, visible only to free-tier users - Profile popover (legacy + workspace): free-tier users see "Upgrade to add credits" instead of "Add Credits", linking directly to the pricing table - Manage Plan settings (legacy + workspace): same replacement — free-tier users see "Upgrade to add credits" instead of "Add Credits" - Paid-tier users retain the original "Add Credits" behavior in all locations - All upgrade buttons go directly to the pricing table (one-step flow) ## Review Focus - The `isFreeTier` conditional gating on the buttons — ensure free-tier users see upgrade CTAs and paid users see normal Add Credits - Layout in Manage Plan panels uses `flex flex-col gap-3` to stack the upgrade button below the usage history link instead of side-by-side ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9315-feat-add-ever-present-upgrade-button-for-free-tier-users-3166d73d365081228cdfe6a67fec6aec) by [Unito](https://www.unito.io) |
||
|
|
ac12a3d9b9 |
fix: preserve refill date slashes in subscription credits label (#9251)
### Motivation
- Subscription credit labels were rendering the refill date with
HTML-escaped separators (`/`) because `vue-i18n` parameter escaping
was applied to the date interpolation.
- The goal is to render date-only parameters like `MM/DD/YY` with
literal slashes so the UI shows a human-readable date string.
### Description
- Disabled `vue-i18n` parameter escaping for the
`subscription.creditsRemainingThisMonth` and
`subscription.creditsRemainingThisYear` lookups in both subscription
panels by passing `{ escapeParameter: false }` to `t()` in
`SubscriptionPanelContentLegacy.vue` and
`SubscriptionPanelContentWorkspace.vue`.
- Adjusted the unit test i18n setup in `SubscriptionPanel.test.ts` to
include `escapeParameter: true` in the test `i18n` instance and updated
the test messages to use `Included (Refills {date})`.
- Added a regression unit test in `SubscriptionPanel.test.ts` asserting
the rendered label contains `Included (Refills 12/31/24)` and does not
contain the escaped entity `/`.
### Testing
- Ran formatting with `pnpm format` which completed successfully.
- Ran lint via `pnpm lint` which passed with pre-existing warnings only
(no new errors).
- Ran type checking with `pnpm typecheck` (via `vue-tsc --noEmit`) which
completed successfully.
- Ran the modified unit tests with `pnpm vitest run
src/platform/cloud/subscription/components/SubscriptionPanel.test.ts`
and the test file passed (10 passed, 5 skipped).
- Attempted a Playwright-based visual capture of the running app but
Chromium crashed in this environment (SIGSEGV) before navigation, so no
screenshot was produced.
------
[Codex
Task](https://chatgpt.com/codex/tasks/task_e_69a0175f58788330b2256329a500e14b)
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9251-fix-preserve-refill-date-slashes-in-subscription-credits-label-3136d73d36508182b770f5719a52d189)
by [Unito](https://www.unito.io)
|
||
|
|
188fafa89a |
fix: address trivial CodeRabbit issues (#9196)
## Summary Address several trivial CodeRabbit-filed issues: type guard extraction, ESLint globals, curve editor optimizations, and type relocation. ## Changes - **What**: Extract `isSingleImage()` type guard in WidgetImageCompare; add `__DISTRIBUTION__`/`__IS_NIGHTLY__` to ESLint globals and remove stale disable comments; remove unnecessary `toFixed(4)` from curve path generation; optimize `histogramToPath` with array join; move `CurvePoint` type to curve domain - Fixes #9175 - Fixes #8281 - Fixes #9116 - Fixes #9145 - Fixes #9147 ## Review Focus All changes are mechanical/trivial. Curve path output changes from fixed-precision to raw floats — SVG handles both fine. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9196-fix-address-trivial-CodeRabbit-issues-3126d73d365081f19a5ce20305403098) by [Unito](https://www.unito.io) |
||
|
|
8c3738fb77 |
feat: add Free subscription tier support (#8864)
## Summary Add frontend support for a Free subscription tier — login/signup page restructuring, telemetry instrumentation, and tier-aware billing gating. ## Changes - **What**: - Restructure login/signup pages: OAuth buttons promoted as primary sign-in method, email login available via progressive disclosure - Add Free tier badge on Google sign-up button with dynamic credit count from remote config - Add `FREE` subscription tier to type system (tier pricing, tier rank, registry types) - Add `isFreeTier` computed to `useSubscription()` - Disable credit top-up for Free tier users (dialogService, purchaseCredits, popover CTA) - Show subscription/upgrade dialog instead of top-up dialog when Free tier user hits out-of-credits - Add funnel telemetry: `trackLoginOpened`, enrich `trackSignupOpened` with `free_tier_badge_shown`, track email toggle clicks ## Review Focus - Tier gating logic: Free tier users should see "Upgrade" instead of "Add Credits" and never reach the top-up flow - Telemetry event design for Mixpanel funnel analysis - Progressive disclosure UX on login/signup pages ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-8864-feat-add-Free-subscription-tier-support-3076d73d36508133b84ec5f0a67ccb03) by [Unito](https://www.unito.io) |
||
|
|
514425b560 |
fix: use getAuthHeader for API key auth in subscription/billing (#9142)
## Summary Fix "User not authenticated" errors when API key users (desktop/portable) trigger subscription status checks or billing operations. ## Changes - **What**: Replace `getFirebaseAuthHeader()` with `getAuthHeader()` in subscription and billing call sites (`fetchSubscriptionStatus`, `initiateSubscriptionCheckout`, `fetchBalance`, `addCredits`, `accessBillingPortal`, `performSubscriptionCheckout`). `getAuthHeader()` supports the full auth fallback chain (workspace token → Firebase token → API key), whereas `getFirebaseAuthHeader()` returns null for API key users since they bypass Firebase entirely. Also add an `isCloud` guard to the subscription status watcher so non-cloud environments skip subscription checks. ## Review Focus - The `isCloud` guard on the watcher ensures local/desktop users never hit the subscription endpoint. This was the originally intended design per code owner confirmation. - `getAuthHeader()` already exists in `firebaseAuthStore` with proper fallback logic — no new auth code was added. Fixes https://www.notion.so/comfy-org/Bug-Subscription-status-check-occurring-in-non-cloud-environments-causing-authentication-errors-3116d73d365081738b21db157e88a9ed ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9142-fix-use-getAuthHeader-for-API-key-auth-in-subscription-billing-3116d73d3650817fa345deaddc8c3fcd) by [Unito](https://www.unito.io) |
||
|
|
631d484901 |
refactor: workspaces DDD (#8921)
## Summary Refactor: workspaces related functionality into DDD structure. Note: this is the 1st PR of 2 more refactoring. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-8921-refactor-DDD-3096d73d3650812bb7f6eb955f042663) by [Unito](https://www.unito.io) |
||
|
|
980f280b3c |
fix: align in-app pricing copy with comfy.org/cloud/pricing (#8725)
## Summary Align stale in-app pricing strings and links with the current comfy.org/cloud/pricing page. ## Changes - **What**: Update video estimate numbers (standard 120→380, creator 211→670, pro 600→1915), fix template URL (`video_wan2_2_14B_fun_camera` → `video_wan2_2_14B_i2v`), fix `templateNote` to reference Wan 2.2 Image-to-Video, align `videoEstimateExplanation` wording order with website, remove stale "$10" from `benefit1` string. ## Review Focus Copy-only changes across 4 files — no logic or UI changes. Source of truth: https://www.comfy.org/cloud/pricing ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-8725-fix-align-in-app-pricing-copy-with-comfy-org-cloud-pricing-3006d73d3650811faf11c248e6bf27c3) by [Unito](https://www.unito.io) |
||
|
|
553ea63357 | [refactor] Migrate SettingDialog to BaseModalLayout design system (#8270) | ||
|
|
85ae0a57c3 |
feat: invite member upsell for single-seat plans (#8801)
## Summary - Show an upsell dialog when single-seat plan users try to invite members, with a banner on the members panel directing them to upgrade. - Misc fixes for member max seat display ## Changes - **What**: `InviteMemberUpsellDialogContent.vue`, `MembersPanelContent.vue`, `WorkspacePanelContent.vue` ## Screenshots <img width="2730" height="1907" alt="image" src="https://github.com/user-attachments/assets/e39a23be-8533-4ebb-a4ae-2797fc382bc2" /> <img width="2730" height="1907" alt="image" src="https://github.com/user-attachments/assets/bec55867-1088-4d3a-b308-5d5cce64c8ae" /> ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-8801-feat-invite-member-upsell-for-single-seat-plans-3046d73d365081349b09fe1d4dc572e8) by [Unito](https://www.unito.io) --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: GitHub Action <action@github.com> |
||
|
|
dd1fefe843 |
fix: credit display and top up and other UI display if personal membe… (#8784)
## Summary Consolidate scattered role checks for credits, top-up, and subscribe buttons into centralized workspace permissions (canTopUp, canManageSubscription), ensuring "Add Credits" requires an active subscription, subscribe buttons only appear when needed, and team members see appropriately restricted billing UI. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-8784-fix-credit-display-and-top-up-and-other-UI-display-if-personal-membe-3036d73d3650810fbc2de084f738943c) by [Unito](https://www.unito.io) |
||
|
|
da56c9e554 |
feat: integrate Impact telemetry with checkout attribution for subscriptions (#8688)
Implement Impact telemetry and checkout attribution through cloud
subscription checkout flows.
This PR adds Impact.com tracking support and carries attribution context
from landing-page visits into subscription checkout requests so
conversion attribution can be validated end-to-end.
- Register a new `ImpactTelemetryProvider` during cloud telemetry
initialization.
- Initialize the Impact queue/runtime (`ire`) and load the Universal
Tracking Tag script once.
- Invoke `ire('identify', ...)` on page views with dynamic `customerId`
and SHA-1 `customerEmail` (or empty strings when unknown).
- Expand checkout attribution capture to include `im_ref`, UTM fields,
and Google click IDs, with local persistence across navigation.
- Attempt `ire('generateClickId')` with a timeout and fall back to
URL/local attribution when unavailable.
- Include attribution payloads in checkout creation requests for both:
- `/customers/cloud-subscription-checkout`
- `/customers/cloud-subscription-checkout/{tier}`
- Extend begin-checkout telemetry metadata typing to include attribution
fields.
- Add focused unit coverage for provider behavior, attribution
persistence/fallback logic, and checkout request payloads.
Tradeoffs / constraints:
- Attribution collection is treated as best-effort in tiered checkout
flow to avoid blocking purchases.
- Backend checkout handlers must accept and process the additional JSON
attribution fields.
## Screenshots
<img width="908" height="208" alt="image"
src="https://github.com/user-attachments/assets/03c16d60-ffda-40c9-9bd6-8914d841be50"/>
<img width="1144" height="460" alt="image"
src="https://github.com/user-attachments/assets/74b97fde-ce0a-43e6-838e-9a4aba484488"/>
<img width="1432" height="320" alt="image"
src="https://github.com/user-attachments/assets/30c22a9f-7bd8-409f-b0ef-e4d02343780a"/>
<img width="341" height="135" alt="image"
src="https://github.com/user-attachments/assets/f6d918ae-5f80-45e0-855a-601abea61dec"/>
|
||
|
|
815be49112 |
fix: keep begin_checkout user_id reactive in subscription flows (#8726)
## Summary Use reactive `userId` reads for `begin_checkout` telemetry so delayed auth state updates are reflected at event time instead of using a stale snapshot. ## Changes - **What**: switched subscription checkout telemetry paths to `storeToRefs(useFirebaseAuthStore())` and read `userId.value` when dispatching `trackBeginCheckout`. - **What**: added regression tests that mutate `userId` after setup / after checkout starts and assert telemetry uses the updated ID. ## Review Focus - Verify `PricingTable` and `performSubscriptionCheckout` still emit exactly one `begin_checkout` event per action, with `checkout_type: change` and `checkout_type: new` in their respective paths. - Verify the new tests would fail with stale store destructuring (manually validated during development). ## Screenshots (if applicable) N/A ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-8726-fix-keep-begin_checkout-user_id-reactive-in-subscription-flows-3006d73d365081888c84c0335ab52e09) by [Unito](https://www.unito.io) |