feat(settings): bring the V1 Plan & Credits tabs to local - #15219
feat(settings): bring the V1 Plan & Credits tabs to local#15219comfydesigner wants to merge 6 commits into
Conversation
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
🎨 Storybook: ✅ Built — View Storybook🎭 Playwright: ❌ 1810 passed, 1 failed❌ Failed Tests📊 Browser Reports
📦 Bundle Size
⚡ Performance Report
Absolute values
Raw data{
"timestamp": "2026-08-14T00:50:46.394Z",
"gitSha": "5f8277894675eb317874a1c95a8ca569c1919654",
"branch": "comfydesigner/local-v1-plan-credits",
"measurements": [
{
"name": "canvas-idle",
"durationMs": 2073.6719999999877,
"styleRecalcs": 10,
"styleRecalcDurationMs": 6.330000000000001,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 428.21600000000007,
"heapDeltaBytes": 5877956,
"heapUsedBytes": 70553232,
"domNodes": 20,
"jsHeapTotalBytes": 24117248,
"scriptDurationMs": 15.482000000000001,
"eventListeners": 6,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "canvas-idle",
"durationMs": 2012.911000000031,
"styleRecalcs": 10,
"styleRecalcDurationMs": 6.1080000000000005,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 363.09200000000004,
"heapDeltaBytes": 5907844,
"heapUsedBytes": 70573736,
"domNodes": 20,
"jsHeapTotalBytes": 24117248,
"scriptDurationMs": 13.124,
"eventListeners": 6,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "canvas-mouse-sweep",
"durationMs": 1695.446000000004,
"styleRecalcs": 73,
"styleRecalcDurationMs": 26.371,
"layouts": 12,
"layoutDurationMs": 2.984,
"taskDurationMs": 685.845,
"heapDeltaBytes": -17337096,
"heapUsedBytes": 47346924,
"domNodes": -280,
"jsHeapTotalBytes": 22515712,
"scriptDurationMs": 88.688,
"eventListeners": -151,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.699999999999818
},
{
"name": "canvas-mouse-sweep",
"durationMs": 1726.72799999998,
"styleRecalcs": 72,
"styleRecalcDurationMs": 27.485000000000003,
"layouts": 12,
"layoutDurationMs": 3.2339999999999995,
"taskDurationMs": 647.333,
"heapDeltaBytes": 419076,
"heapUsedBytes": 65179340,
"domNodes": 54,
"jsHeapTotalBytes": 25165824,
"scriptDurationMs": 90.169,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "canvas-zoom-sweep",
"durationMs": 1685.0239999999985,
"styleRecalcs": 31,
"styleRecalcDurationMs": 12.122000000000002,
"layouts": 6,
"layoutDurationMs": 0.5690000000000001,
"taskDurationMs": 293.421,
"heapDeltaBytes": 8245640,
"heapUsedBytes": 72913948,
"domNodes": 77,
"jsHeapTotalBytes": 24903680,
"scriptDurationMs": 15.069999999999999,
"eventListeners": 21,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "canvas-zoom-sweep",
"durationMs": 1667.6909999999907,
"styleRecalcs": 31,
"styleRecalcDurationMs": 11.862,
"layouts": 6,
"layoutDurationMs": 0.499,
"taskDurationMs": 302.49,
"heapDeltaBytes": 9105724,
"heapUsedBytes": 73894304,
"domNodes": 78,
"jsHeapTotalBytes": 24117248,
"scriptDurationMs": 15.539,
"eventListeners": 19,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "dom-widget-clipping",
"durationMs": 509.0120000000411,
"styleRecalcs": 12,
"styleRecalcDurationMs": 13.254999999999999,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 310.404,
"heapDeltaBytes": -11376860,
"heapUsedBytes": 53285856,
"domNodes": 20,
"jsHeapTotalBytes": 25690112,
"scriptDurationMs": 46.148,
"eventListeners": 2,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000273
},
{
"name": "dom-widget-clipping",
"durationMs": 510.627999999997,
"styleRecalcs": 11,
"styleRecalcDurationMs": 5.688000000000001,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 292.254,
"heapDeltaBytes": -11556072,
"heapUsedBytes": 53158644,
"domNodes": 18,
"jsHeapTotalBytes": 25165824,
"scriptDurationMs": 44.75899999999999,
"eventListeners": 2,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "large-graph-idle",
"durationMs": 2011.3660000000095,
"styleRecalcs": 11,
"styleRecalcDurationMs": 6.916999999999998,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 531.871,
"heapDeltaBytes": 7428080,
"heapUsedBytes": 69744052,
"domNodes": -280,
"jsHeapTotalBytes": 1933312,
"scriptDurationMs": 71.57900000000001,
"eventListeners": -149,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "large-graph-idle",
"durationMs": 1994.066000000089,
"styleRecalcs": 10,
"styleRecalcDurationMs": 6.834,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 536.006,
"heapDeltaBytes": 7173656,
"heapUsedBytes": 69194356,
"domNodes": -280,
"jsHeapTotalBytes": 3244032,
"scriptDurationMs": 73.551,
"eventListeners": -149,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "large-graph-pan",
"durationMs": 2082.456000000036,
"styleRecalcs": 69,
"styleRecalcDurationMs": 13.217999999999998,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 966.7900000000001,
"heapDeltaBytes": 18208020,
"heapUsedBytes": 78941276,
"domNodes": -282,
"jsHeapTotalBytes": 3960832,
"scriptDurationMs": 317.109,
"eventListeners": -179,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "large-graph-pan",
"durationMs": 2090.9359999999424,
"styleRecalcs": 68,
"styleRecalcDurationMs": 12.757000000000001,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 964.478,
"heapDeltaBytes": 15954492,
"heapUsedBytes": 79241340,
"domNodes": -281,
"jsHeapTotalBytes": 3960832,
"scriptDurationMs": 309.08799999999997,
"eventListeners": -179,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "large-graph-zoom",
"durationMs": 3068.187000000023,
"styleRecalcs": 65,
"styleRecalcDurationMs": 12.878,
"layouts": 60,
"layoutDurationMs": 6.603,
"taskDurationMs": 1124.2730000000001,
"heapDeltaBytes": 22639296,
"heapUsedBytes": 85623900,
"domNodes": 14,
"jsHeapTotalBytes": 7340032,
"scriptDurationMs": 405.87,
"eventListeners": 6,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333335,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "large-graph-zoom",
"durationMs": 3121.436000000017,
"styleRecalcs": 67,
"styleRecalcDurationMs": 14.309999999999999,
"layouts": 60,
"layoutDurationMs": 6.810000000000001,
"taskDurationMs": 1169.155,
"heapDeltaBytes": 21504424,
"heapUsedBytes": 83716184,
"domNodes": 16,
"jsHeapTotalBytes": 7602176,
"scriptDurationMs": 415.607,
"eventListeners": 6,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "minimap-idle",
"durationMs": 2006.386999999961,
"styleRecalcs": 9,
"styleRecalcDurationMs": 5.485,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 527.1539999999999,
"heapDeltaBytes": 3511900,
"heapUsedBytes": 65222400,
"domNodes": -283,
"jsHeapTotalBytes": 3506176,
"scriptDurationMs": 72.19300000000001,
"eventListeners": -179,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "minimap-idle",
"durationMs": 2002.4970000000621,
"styleRecalcs": 10,
"styleRecalcDurationMs": 6.590000000000001,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 536.667,
"heapDeltaBytes": 7675112,
"heapUsedBytes": 68746556,
"domNodes": -282,
"jsHeapTotalBytes": 4030464,
"scriptDurationMs": 77.938,
"eventListeners": -149,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "subgraph-dom-widget-clipping",
"durationMs": 519.9259999999981,
"styleRecalcs": 48,
"styleRecalcDurationMs": 9.812999999999999,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 323.147,
"heapDeltaBytes": -10755448,
"heapUsedBytes": 53939368,
"domNodes": 22,
"jsHeapTotalBytes": 25952256,
"scriptDurationMs": 97.61200000000001,
"eventListeners": 8,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666682,
"p95FrameDurationMs": 16.700000000000273
},
{
"name": "subgraph-dom-widget-clipping",
"durationMs": 452.50399999997626,
"styleRecalcs": 47,
"styleRecalcDurationMs": 8.332999999999998,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 303.38599999999997,
"heapDeltaBytes": -10442608,
"heapUsedBytes": 54229048,
"domNodes": 20,
"jsHeapTotalBytes": 26476544,
"scriptDurationMs": 91.36699999999999,
"eventListeners": 8,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.799999999999727
},
{
"name": "subgraph-idle",
"durationMs": 1999.6380000000045,
"styleRecalcs": 11,
"styleRecalcDurationMs": 6.277000000000001,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 362.763,
"heapDeltaBytes": 5686964,
"heapUsedBytes": 70362252,
"domNodes": 22,
"jsHeapTotalBytes": 24903680,
"scriptDurationMs": 10.726999999999999,
"eventListeners": 6,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "subgraph-idle",
"durationMs": 2004.240999999979,
"styleRecalcs": 11,
"styleRecalcDurationMs": 6.783000000000001,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 360.128,
"heapDeltaBytes": 5890816,
"heapUsedBytes": 70842712,
"domNodes": 22,
"jsHeapTotalBytes": 24379392,
"scriptDurationMs": 11.233000000000002,
"eventListeners": 6,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "subgraph-mouse-sweep",
"durationMs": 1671.8060000000037,
"styleRecalcs": 76,
"styleRecalcDurationMs": 26.154000000000003,
"layouts": 16,
"layoutDurationMs": 3.162,
"taskDurationMs": 573.922,
"heapDeltaBytes": -3236968,
"heapUsedBytes": 61522200,
"domNodes": 64,
"jsHeapTotalBytes": 24379392,
"scriptDurationMs": 68.36,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "subgraph-mouse-sweep",
"durationMs": 1668.703999999991,
"styleRecalcs": 75,
"styleRecalcDurationMs": 25.466,
"layouts": 16,
"layoutDurationMs": 3.051,
"taskDurationMs": 568.5600000000001,
"heapDeltaBytes": -3575208,
"heapUsedBytes": 61455320,
"domNodes": 62,
"jsHeapTotalBytes": 24641536,
"scriptDurationMs": 66.399,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "subgraph-transition-enter",
"durationMs": 850.291000000027,
"styleRecalcs": 18,
"styleRecalcDurationMs": 23.073999999999998,
"layouts": 14,
"layoutDurationMs": 8.812,
"taskDurationMs": 690.6600000000001,
"heapDeltaBytes": 8915632,
"heapUsedBytes": 100606276,
"domNodes": 13673,
"jsHeapTotalBytes": 10747904,
"scriptDurationMs": 17.912000000000003,
"eventListeners": 2373,
"totalBlockingTimeMs": 75,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "viewport-pan-sweep",
"durationMs": 8123.5350000000035,
"styleRecalcs": 249,
"styleRecalcDurationMs": 35.752,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 3357.4139999999998,
"heapDeltaBytes": 23633200,
"heapUsedBytes": 85675516,
"domNodes": -280,
"jsHeapTotalBytes": 6057984,
"scriptDurationMs": 1038.6369999999997,
"eventListeners": -163,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "viewport-pan-sweep",
"durationMs": 8107.009999999946,
"styleRecalcs": 249,
"styleRecalcDurationMs": 36.704,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 3421.227,
"heapDeltaBytes": 8269832,
"heapUsedBytes": 70253964,
"domNodes": -280,
"jsHeapTotalBytes": 5271552,
"scriptDurationMs": 1031.52,
"eventListeners": -133,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "vue-large-graph-idle",
"durationMs": 13806.887000000017,
"styleRecalcs": 0,
"styleRecalcDurationMs": 0,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 13788.861,
"heapDeltaBytes": -33862604,
"heapUsedBytes": 166119728,
"domNodes": -8312,
"jsHeapTotalBytes": -13832192,
"scriptDurationMs": 416.154,
"eventListeners": -16389,
"totalBlockingTimeMs": 0,
"frameDurationMs": 17.776666666666642,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "vue-large-graph-idle",
"durationMs": 14181.376999999997,
"styleRecalcs": 0,
"styleRecalcDurationMs": 0,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 14129.159999999998,
"heapDeltaBytes": -31421464,
"heapUsedBytes": 168013220,
"domNodes": -8312,
"jsHeapTotalBytes": -10948608,
"scriptDurationMs": 469.742,
"eventListeners": -16393,
"totalBlockingTimeMs": 0,
"frameDurationMs": 17.223333333333358,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "vue-large-graph-pan",
"durationMs": 16417.700999999965,
"styleRecalcs": 91,
"styleRecalcDurationMs": 12.439000000000005,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 16399.097,
"heapDeltaBytes": -51806904,
"heapUsedBytes": 164792028,
"domNodes": -8312,
"jsHeapTotalBytes": -9900032,
"scriptDurationMs": 678.861,
"eventListeners": -16389,
"totalBlockingTimeMs": 0,
"frameDurationMs": 17.776666666666642,
"p95FrameDurationMs": 16.80000000000291
},
{
"name": "vue-large-graph-pan",
"durationMs": 16697.497999999996,
"styleRecalcs": 108,
"styleRecalcDurationMs": 13.274000000000008,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 16670.37,
"heapDeltaBytes": -33992632,
"heapUsedBytes": 165022860,
"domNodes": -8312,
"jsHeapTotalBytes": -11735040,
"scriptDurationMs": 714.586,
"eventListeners": -16387,
"totalBlockingTimeMs": 0,
"frameDurationMs": 17.219999999999953,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "workflow-execution",
"durationMs": 445.30400000002146,
"styleRecalcs": 18,
"styleRecalcDurationMs": 15.703000000000001,
"layouts": 3,
"layoutDurationMs": 0.945,
"taskDurationMs": 93.833,
"heapDeltaBytes": 5180208,
"heapUsedBytes": 69124916,
"domNodes": 150,
"jsHeapTotalBytes": 4980736,
"scriptDurationMs": 8.005000000000003,
"eventListeners": 97,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "workflow-execution",
"durationMs": 435.771999999929,
"styleRecalcs": 14,
"styleRecalcDurationMs": 16.607999999999997,
"layouts": 2,
"layoutDurationMs": 0.3749999999999999,
"taskDurationMs": 91.84700000000001,
"heapDeltaBytes": 5062032,
"heapUsedBytes": 68957320,
"domNodes": 125,
"jsHeapTotalBytes": 5505024,
"scriptDurationMs": 7.496999999999998,
"eventListeners": 99,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.799999999999727
}
]
} |
…g#15239) ## Why Settings currently builds separate local and cloud navigation trees, and cloud's V1 workspace sections are additionally gated by `billing_control_enabled`. This leaves local and flag-off users on the legacy Workspace/Credits organization and forces billing work to target two layouts. The [latest product direction in Slack](https://comfy-organization.slack.com/archives/C0BNGQG2LCW/p1786668631144379?thread_ts=1786649682.983289&cid=C0BNGQG2LCW) clarifies that Activity should reuse `UsageLogsTable` for both personal and team workspaces until the backend-backed workspace activity feed is available. ## What changed - Use the V1 Workspace / Account / General / Other sidebar organization for logged-in local and cloud users. - Always expose Plan & Credits and Members as workspace sections; keep the owner-only Allowlist policy unchanged. - Keep the legacy local Credits entry reachable so existing profile-menu and top-up flows continue to work. - Render the existing `UsageLogsTable` in Activity for personal and team workspaces, remove the obsolete search field, and report refresh failures. - Preserve `WorkspaceActivityContent` unchanged for FE-1249. ## AS IS / TO BE ### AS IS — current local Credits settings  Source: [current local settings screenshot from the product thread](https://comfy-organization.slack.com/archives/C0BNGQG2LCW/p1786651103082259?thread_ts=1786649682.983289&cid=C0BNGQG2LCW). ### TO BE — local V1 workspace layout  Source: [local unsubscribed V1 frame in Figma](https://www.figma.com/design/CkFTD4c20PyRGpNVAJgpfV/Team-Plan---Workspaces?node-id=5874-71646). ### IMPLEMENTED — local Activity tab  Captured from the local distribution with mocked activity events. | Area | AS IS | TO BE | | --- | --- | --- | | Settings navigation | Local and flag-off users use the legacy grouping and Workspace panel, while cloud users can receive the V1 structure. | All logged-in local and cloud users share the V1 Workspace / Account / General / Other organization. | | Workspace billing | Plan & Credits and Members depend on cloud/billing configuration. | Plan & Credits and Members are consistently surfaced under Workspace; existing owner-only policy rules remain unchanged. | | Activity | Uses the placeholder workspace ledger and search path. | Reuses the paginated `UsageLogsTable` backed by billing events for personal and team workspaces, with the obsolete search removed. | | Existing local flows | Credits navigation is coupled to the legacy local settings structure. | The existing local Credits route remains reachable for profile-menu and top-up flows while the V1 workspace sections are also available. | | Future workspace activity | The backend-dependent activity implementation is blocked. | `WorkspaceActivityContent` stays intact for FE-1249 and can replace the stopgap once backend data is available. | ## Design references - [Unified V1 Tab Layout for Local & Cloud](https://www.figma.com/design/CkFTD4c20PyRGpNVAJgpfV/Team-Plan---Workspaces?node-id=6258-41203) — local unsubscribed, cloud unsubscribed, and subscribed settings variants - [Credits / Plan & Credits tab variants](https://www.figma.com/design/CkFTD4c20PyRGpNVAJgpfV/Team-Plan---Workspaces?node-id=5874-72767) — Overview/Credits and Activity states - [Profile menu updates](https://www.figma.com/design/CkFTD4c20PyRGpNVAJgpfV/Team-Plan---Workspaces?node-id=6258-41204) — aligned billing navigation labels and entry points - [Settings sidebar reference](https://www.figma.com/design/CkFTD4c20PyRGpNVAJgpfV/Team-Plan---Workspaces?node-id=5999-37953) Based on the approach in draft PRs Comfy-Org#15206 and Comfy-Org#15219, rebuilt from `main`. ## Verification - `pnpm test:unit src/platform/settings/composables/useSettingUI.test.ts src/platform/workspace/components/dialogs/settings/PlanCreditsPanelContent.test.ts` (18 passed) - `pnpm test:unit src/platform/workspace` (1,118 passed) - `pnpm typecheck` - Targeted oxlint and ESLint (0 warnings/errors) - `pnpm format:check` for changed files FE-1609 --------- Co-authored-by: Amp <amp@ampcode.com>
Stacked on #15206. Brings the V1 Plan & Credits tab organization to local and converges the settings sidebar grammar, as part of the local/cloud UI consolidation. Follow-ups tracked as FE-1619 (post-switcher: header, Members, workspace overview, state-dependent label) and FE-1618 (credits-tile no-plan state).
1. Local gets the shared tab shell. The sidebar entry "Credits" (coins icon; "Plan & Credits" arrives with real plan state, FE-1619) mounts the shared
PlanCreditsPanelContent:CreditsPanelgains anembeddedprop; the workspace plan card is not used off-cloud — it renders misleading data against the legacy balance shape.UsageLogsTable(useBillingRoutingreturnslegacyoff-cloud →/customers/events), plus a "Full activity ↗" footer button to platform.comfy.org/profile/usage.useAutoPageSize, extended with a reserved-height hook because this table's paginator sits inside the measured container. Approximate with mixed row heights; opening the tab issues two fetches (initial + fitted size), deduped by the existing load token.2. One sidebar grammar. Local's ACCOUNT / APPLICATION SETTINGS / SPECIAL SETTINGS groups are replaced with cloud's Workspace / General / Other; the General and Other groups are now shared computeds consumed by both trees (User under General on both platforms, custom-node settings under Other, desktop Server Config at the end of General). Billing belongs to the workspace on the backend, so the group is named Workspace; it becomes navigable with #15164.
3. Footers unified. Both tabs end in
SubscriptionFooterLinks; the embedded credits body drops its bespoke FAQs row. Note for reviewers: the footer's top divider is removed globally (design call), so the cloud subscription overview loses the line too — intentional, not incidental.Cloud behavior is otherwise unchanged (the Credits tab body forks on
isCloud; flag-off is untouched). Coordination: #14811/#14813 and #15151 touch the same panel/rail seams — merge order decides who rebases.?ff=billing_control_enabled) and local (OSS backend) by @comfydesigner.