Adds the single billing-status banner slot for team plans, rendered in priority order — **paused > payment declined > out of credits > ending** — with role-aware CTAs. At most one state shows at a time. Placement matches the prototype (`comfydesigner/team-workspaces-v1`) and Figma: the banner lives in the **workspace settings panel**, in one slot between the tab list and the tab body, so it shows on every tab. Our container has two tabs, so one mount covers both rather than duplicating the banner into each panel as the prototype does. Derivation lives in a pure `deriveBillingBanner()`; the component only renders. State comes from `billing-status` fields, never from error strings (per Luke's user story). ## Gated on the plan, not the workspace type `useBillingContext` gains `isTeamPlan`: a credit stop marks the per-credit Team plan, a `team-` slug the retired seat-based ones. Workspace type is the wrong question in both directions: - A **team workspace can sit on a retired seat-based plan** (tier STANDARD/CREATOR/PRO) — the type gate admits it. - Once consolidated billing lands (cloud#5010), a **personal workspace can hold a team plan**. The backend blocks that today pending BE-1526, so the type gate is only accidentally right, and only for now. Per [Hunter](https://comfy-organization.slack.com/archives/C0BEE5503RQ/p1784151520761519?thread_ts=1784051425.176829): *"We should deprecate the workspace type and derive it from the subscription."* Two deliberate non-choices: - **Not gated on `isActiveSubscription`**, unlike the existing `isLegacyTeamPlan`. The spend gate folds `billing_status` into `is_active`, so a paused or payment-failed team plan reports `is_active=false` — it must still read as a team plan exactly when the banner is needed. - **Not gated on `subscription_tier === 'TEAM'`**, which would silently drop every legacy team subscriber. The FE cannot express `'TEAM'` anyway: `tierPricing.ts` resolves `SubscriptionTier` from the **registry** spec for what is an **ingest** field. That's a real bug, but orthogonal — filing separately. ## Fixes payment_failed, which was dead code `payment_failed` denies spend, so it always arrives with `is_active=false` — and the check sat **below** the `is_active` gate, two lines under a comment documenting that exact trap for `paused`. **Every team in Stripe dunning saw no banner.** Now hoisted alongside `paused`. Its tests passed only because they spread the `funded` fixture (`isActiveSubscription: true`) onto `payment_failed` — a pairing the backend never emits. They now pin `is_active=false`. Both this and the `isTeamPlan` decoupling are mutation-tested: reverting either kills tests. Members no longer fall through from `payment_failed` to out-of-credits — with the real pairing that path was unreachable, and DES-380 says members never see the payment banner. ## Renders The six role/state variants render from the Storybook stories at `Platform/Workspace/BillingStatusBanner`. Each story drives the real `deriveBillingBanner` through a stubbed billing context, so a story can only show a state the backend can actually produce — not a hand-set banner kind. Run them with `DISTRIBUTION=cloud pnpm storybook` — the banner is cloud-only and `isCloud` is compile-time, so a plain `pnpm storybook` renders every story empty. Icon language follows the prototype's severity rule: amber triangle-alert for every action-needed state (paused, payment declined, out of credits), muted circle-alert reserved for the informational "plan ends" notice. ## Feature flag Personal-workspace routing now keys off **`billing_control_enabled`**, replacing `consolidated_billing_enabled` (which had exactly one consumer — this routing check — so it is replaced rather than left alongside). Cloud registers the new flag in [Comfy-Org/cloud#5091](https://github.com/Comfy-Org/cloud/pull/5091). This coupling is load-bearing for the banner. `isTeamPlan` requires the workspace billing rail, so a personal workspace holding a team plan only surfaces billing state once its routing flag is on. Had routing stayed on `consolidated_billing_enabled` while the subscribe path unblocks under `billing_control_enabled`, the two could diverge: a personal workspace could buy a team plan and then be routed to legacy, which carries no `billing_status` at all, blanking the banner. **Merge order:** cloud#5091 must merge and `billing_control_enabled` must exist in PostHog before this ships, otherwise the flag resolves false for everyone and every personal workspace routes to legacy. Both flags default false, so the floor is today's behaviour — but any users already rolled out on `consolidated_billing_enabled` would revert to legacy billing until `billing_control_enabled` is rolled out to them. Worth confirming the current rollout state before merge. ## Backend status `billing_status: 'paused'` merged in cloud [#5075](https://github.com/Comfy-Org/cloud/pull/5075) but is **not emitted yet**: the lifecycle handler that writes it is unreachable in prod (ingest's forward allowlist omits `customer.subscription.*`; billing-api is ClusterIP with no ingress). That's BE-1530's unfinished half, tracked backend-side. The paused banner stays inert until then. The other three states render from fields the API already returns: `billing_status=payment_failed` + `renewal_date`, `has_funds`, and `cancel_at`. ## How has this been tested? - `useBillingContext.test.ts` — `isTeamPlan` across per-credit, legacy, paused, payment-failed, and team-workspace-on-personal-plan - `deriveBillingBanner.test.ts` — priority order, role gating, and the realistic `is_active=false` pairing for both paused and payment_failed - `BillingStatusBanner.test.ts` — per-variant copy and actions, date interpolation + no-date fallback, cross-mount dismiss, ending read-only for a non-original owner - `useBillingBanner.test.ts` — dismiss resets after a top-up so a later exhaustion re-shows - `WorkspacePanelContent.test.ts` — the banner takes one slot above the tab body - 696 tests pass across the touched areas; typecheck / lint / knip clean ## Known gaps - **Blocked-run surface:** settings-scoped per the prototype, so a user who exhausts credits mid-canvas won't see it until they open settings. Queue-time blocked-submit is carried by the existing insufficient-credits dialog. An app-shell surface would be a design call, not this PR. - **Personal workspaces on consolidated billing** get no banner — `isTeamPlan` excludes them by design, matching FE-1246's team scope and the `!isInPersonalWorkspace` precedent in `useSubscriptionDialog`. Their copy would need personal variants (`outOfCredits.body` says "Your team…") and a free-tier decision. Belongs with the consolidated-billing rollout. - **Prototype slot:** the prototype's banner exposes an `actions` slot used only by its Invoices view. We have no such view, so it's omitted until needed. ## screenshots <img width="1150" height="574" alt="01-paused-owner" src="https://github.com/user-attachments/assets/c1ff0abd-0286-4453-85f2-6f89cff1e655" /> <img width="1150" height="574" alt="02-paused-member" src="https://github.com/user-attachments/assets/ad5ce843-cbc6-49d1-95fc-28e908bc010a" /> <img width="1150" height="574" alt="03-payment-declined" src="https://github.com/user-attachments/assets/717c1191-8f12-4835-90c6-5fe345b20780" /> <img width="1150" height="574" alt="04-payment-declined-no-date" src="https://github.com/user-attachments/assets/c7e483e4-e0cb-4810-b090-a9c432546dbd" /> <img width="1150" height="574" alt="05-out-of-credits-owner" src="https://github.com/user-attachments/assets/9261fd39-ecdb-4501-a401-47d9cbee04dc" /> <img width="1150" height="574" alt="06-out-of-credits-member" src="https://github.com/user-attachments/assets/4a91af88-4405-43e1-8975-b64fdf180a13" /> <img width="1150" height="574" alt="07-ending-owner" src="https://github.com/user-attachments/assets/80e5011e-ef11-482f-bc49-1ddfbe991221" /> <img width="1150" height="574" alt="08-ending-non-original-owner" src="https://github.com/user-attachments/assets/129b79aa-1427-4286-829c-eed501a6d44d" />
Release Schedule
The project follows a structured release process for each minor version, consisting of three distinct phases:
-
Development Phase - 2 weeks
- Active development of new features
- Code changes merged to the development branch
-
Feature Freeze - 2 weeks
- No new features accepted
- Only bug fixes are cherry-picked to the release branch
- Testing and stabilization of the codebase
-
Publication
- Release is published at the end of the freeze period
- Version is finalized and made available to all users
Nightly Releases
Nightly releases are published daily at https://github.com/Comfy-Org/ComfyUI_frontend/releases.
To use the latest nightly release, add the following command line argument to your ComfyUI launch script:
--front-end-version Comfy-Org/ComfyUI_frontend@latest
Overlapping Release Cycles
The development of successive minor versions overlaps. For example, while version 1.1 is in feature freeze, development for version 1.2 begins simultaneously. Each feature has approximately 4 weeks from merge to ComfyUI stable release (2 weeks on main, 2 weeks frozen on RC).
Example Release Cycle
| Week | Date Range | Version 1.1 | Version 1.2 | Version 1.3 | Patch Releases |
|---|---|---|---|---|---|
| 1-2 | Mar 1-14 | Development | - | - | - |
| 3-4 | Mar 15-28 | Feature Freeze | Development | - | 1.1.0 through 1.1.13 (daily) |
| 5-6 | Mar 29-Apr 11 | Released | Feature Freeze | Development | 1.1.14+ (daily) 1.2.0 through 1.2.13 (daily) |
| 7-8 | Apr 12-25 | - | Released | Feature Freeze | 1.2.14+ (daily) 1.3.0 through 1.3.13 (daily) |
Release Summary
Major features
v1.5: Native translation (i18n)
ComfyUI now includes built-in translation support, replacing the need for third-party translation extensions. Select your language
in Comfy > Locale > Language to translate the interface into English, Chinese (Simplified), Russian, Japanese, Korean, or Arabic. This native
implementation offers better performance, reliability, and maintainability compared to previous solutions.
More details available here: https://blog.comfy.org/p/native-localization-support-i18n
v1.4: New mask editor
https://github.com/Comfy-Org/ComfyUI_frontend/pull/1284 implements a new mask editor.
v1.3.22: Integrated server terminal
Press Ctrl + ` to toggle integrated terminal.
https://github.com/user-attachments/assets/eddedc6a-07a3-4a83-9475-63b3977f6d94
v1.2.4: Node library sidebar tab
Drag & Drop
https://github.com/user-attachments/assets/853e20b7-bc0e-49c9-bbce-a2ba7566f92f
Filter
https://github.com/user-attachments/assets/4bbca3ee-318f-4cf0-be32-a5a5541066cf
v1.2.0: Queue/History sidebar tab
https://github.com/user-attachments/assets/86e264fe-4d26-4f07-aa9a-83bdd2d02b8f
v1.1.0: Node search box
Fuzzy search & Node preview
Release link with shift
https://github.com/user-attachments/assets/a1b2b5c3-10d1-4256-b620-345de6858f25
QoL changes
v1.3.32: **Litegraph** Nested group
https://github.com/user-attachments/assets/f51adeb1-028e-40af-81e4-0ac13075198a
v1.3.24: **Litegraph** Group selection
https://github.com/user-attachments/assets/e6230a94-411e-4fba-90cb-6c694200adaa
v1.3.6: **Litegraph** Toggle link visibility
v1.3.4: **Litegraph** Auto widget to input conversion
Dropping a link of correct type on node widget will automatically convert the widget to input.
v1.3.4: **Litegraph** Canvas pan mode
The canvas becomes readonly in pan mode. Pan mode is activated by clicking the pan mode button on the canvas menu or by holding the space key.
v1.3.1: **Litegraph** Shift drag link to create a new link
v1.2.44: **Litegraph** Double click group title to edit
https://github.com/user-attachments/assets/5bf0e2b6-8b3a-40a7-b44f-f0879e9ad26f
v1.2.39: **Litegraph** Group selected nodes with Ctrl + G
https://github.com/user-attachments/assets/7805dc54-0854-4a28-8bcd-4b007fa01151
v1.2.38: **Litegraph** Double click node title to edit
https://github.com/user-attachments/assets/d61d5d0e-f200-4153-b293-3e3f6a212b30
v1.2.7: **Litegraph** drags multiple links with shift pressed
https://github.com/user-attachments/assets/68826715-bb55-4b2a-be6e-675cfc424afe
https://github.com/user-attachments/assets/c142c43f-2fe9-4030-8196-b3bfd4c6977d
v1.2.2: **Litegraph** auto connects to correct slot
Before
https://github.com/user-attachments/assets/c253f778-82d5-4e6f-aec0-ea2ccf421651
After
https://github.com/user-attachments/assets/b6360ac0-f0d2-447c-9daa-8a2e20c0dc1d
v1.1.8: **Litegraph** hides text overflow on widget value
https://github.com/user-attachments/assets/5696a89d-4a47-4fcc-9e8c-71e1264943f2
Developer APIs
v1.6.13: prompt/confirm/alert replacements for ComfyUI desktop
Several browser-only APIs are not available in ComfyUI desktop's electron environment.
window.promptwindow.confirmwindow.alert
Please use the following APIs as replacements.
// window.prompt
window['app'].extensionManager.dialog
.prompt({
title: 'Test Prompt',
message: 'Test Prompt Message'
})
.then((value: string) => {
// Do something with the value user entered
})
// window.confirm
window['app'].extensionManager.dialog
.confirm({
title: 'Test Confirm',
message: 'Test Confirm Message'
})
.then((value: boolean) => {
// Do something with the value user entered
})
// window.alert
window['app'].extensionManager.toast.addAlert('Test Alert')
v1.3.34: Register about panel badges
app.registerExtension({
name: 'TestExtension1',
aboutPageBadges: [
{
label: 'Test Badge',
url: 'https://example.com',
icon: 'pi pi-box'
}
]
})
v1.3.22: Register bottom panel tabs
app.registerExtension({
name: 'TestExtension',
bottomPanelTabs: [
{
id: 'TestTab',
title: 'Test Tab',
type: 'custom',
render: (el) => {
el.innerHTML = '<div>Custom tab</div>'
}
}
]
})
v1.3.22: New settings API
Legacy settings API.
// Register a new setting
app.ui.settings.addSetting({
id: 'TestSetting',
name: 'Test Setting',
type: 'text',
defaultValue: 'Hello, world!'
})
// Get the value of a setting
const value = app.ui.settings.getSettingValue('TestSetting')
// Set the value of a setting
app.ui.settings.setSettingValue('TestSetting', 'Hello, universe!')
New settings API.
// Register a new setting
app.registerExtension({
name: 'TestExtension1',
settings: [
{
id: 'TestSetting',
name: 'Test Setting',
type: 'text',
defaultValue: 'Hello, world!'
}
]
})
// Get the value of a setting
const value = app.extensionManager.setting.get('TestSetting')
// Set the value of a setting
app.extensionManager.setting.set('TestSetting', 'Hello, universe!')
v1.3.7: Register commands and keybindings
Extensions can call the following API to register commands and keybindings. Do note that keybindings defined in core cannot be overwritten, and some keybindings are reserved by the browser.
app.registerExtension({
name: 'TestExtension1',
commands: [
{
id: 'TestCommand',
function: () => {
alert('TestCommand')
}
}
],
keybindings: [
{
combo: { key: 'k' },
commandId: 'TestCommand'
}
]
})
v1.3.1: Extension API to register custom topbar menu items
Extensions can call the following API to register custom topbar menu items.
app.registerExtension({
name: 'TestExtension1',
commands: [
{
id: 'foo-id',
label: 'foo',
function: () => {
alert(1)
}
}
],
menuCommands: [
{
path: ['ext', 'ext2'],
commands: ['foo-id']
}
]
})
v1.2.27: Extension API to add toast message
iExtensions can call the following API to add toast messages.
app.extensionManager.toast.add({
severity: 'info',
summary: 'Loaded!',
detail: 'Extension loaded!',
life: 3000
})
Documentation of all supported options can be found here: https://primevue.org/toast/#api.toast.interfaces.ToastMessageOptions
v1.2.4: Extension API to register custom sidebar tab
Extensions now can call the following API to register a sidebar tab.
app.extensionManager.registerSidebarTab({
id: 'search',
icon: 'pi pi-search',
title: 'search',
tooltip: 'search',
type: 'custom',
render: (el) => {
el.innerHTML = '<div>Custom search tab</div>'
}
})
The list of supported icons can be found here: https://primevue.org/icons/#list
We will support custom icons later.
v1.10.9: Selection Toolbox API
Extensions can register commands that appear in the selection toolbox when specific items are selected on the canvas.
app.registerExtension({
name: 'TestExtension1',
commands: [
{
id: 'test.selection.command',
label: 'Test Command',
icon: 'pi pi-star',
function: () => {
// Command logic here
}
}
],
// Return an array of command IDs to show in the selection toolbox
// when an item is selected
getSelectionToolboxCommands: (selectedItem) => ['test.selection.command']
})
The selection toolbox will display the command button when items are selected:
Contributing
We welcome contributions to ComfyUI Frontend! Please see our Contributing Guide for:
- Ways to contribute (code, documentation, testing, community support)
- Development setup and workflow
- Code style guidelines
- Testing requirements
- How to submit pull requests
- Backporting fixes to release branches
Development
For detailed development setup, testing procedures, and technical information, please refer to CONTRIBUTING.md.
i18n
See locales/README.md for details.
Storybook
See .storybook/README.md for component development and visual testing documentation.
Troubleshooting
For comprehensive troubleshooting and technical support, please refer to our official documentation:
- General Troubleshooting Guide - Common issues, performance optimization, and reporting bugs
- Custom Node Issues - Debugging custom node problems and conflicts
- Desktop Installation Guide - Desktop-specific installation and troubleshooting