[backport cloud/1.51] feat: align local settings with V1 billing layout (FE-1609) - #15589
Conversation
## 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 #15206 and #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>
🎭 Playwright: ✅ 1829 passed, 0 failed📊 Browser Reports
🎨 Storybook: ✅ Built — View Storybook📦 Bundle Size
⚡ Performance Report
Absolute values
Raw data{
"timestamp": "2026-08-21T23:45:26.297Z",
"gitSha": "6a55350aa11850e64bfe0d76a34780a564ef4b4b",
"branch": "backport-15239-to-cloud-1.51",
"measurements": [
{
"name": "canvas-idle",
"durationMs": 2021.4490000000183,
"styleRecalcs": 11,
"styleRecalcDurationMs": 5.498000000000001,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 345.17199999999997,
"heapDeltaBytes": 5077420,
"heapUsedBytes": 70489812,
"domNodes": 22,
"jsHeapTotalBytes": 24379392,
"scriptDurationMs": 4.727,
"eventListeners": 6,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.670000000000012,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "canvas-idle",
"durationMs": 1990.1370000000043,
"styleRecalcs": 10,
"styleRecalcDurationMs": 4.877,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 303.783,
"heapDeltaBytes": 4932184,
"heapUsedBytes": 70534304,
"domNodes": 20,
"jsHeapTotalBytes": 23855104,
"scriptDurationMs": 4.1209999999999996,
"eventListeners": 6,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "canvas-mouse-sweep",
"durationMs": 1711.8990000000167,
"styleRecalcs": 72,
"styleRecalcDurationMs": 21.232999999999997,
"layouts": 12,
"layoutDurationMs": 2.261,
"taskDurationMs": 552.984,
"heapDeltaBytes": 44384,
"heapUsedBytes": 65432164,
"domNodes": 53,
"jsHeapTotalBytes": 24645632,
"scriptDurationMs": 63.21300000000001,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "canvas-mouse-sweep",
"durationMs": 1673.6250000000155,
"styleRecalcs": 72,
"styleRecalcDurationMs": 18.137,
"layouts": 12,
"layoutDurationMs": 1.6590000000000003,
"taskDurationMs": 487.364,
"heapDeltaBytes": 54384,
"heapUsedBytes": 65620636,
"domNodes": 56,
"jsHeapTotalBytes": 24907776,
"scriptDurationMs": 54.778999999999996,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "canvas-zoom-sweep",
"durationMs": 1716.8439999999805,
"styleRecalcs": 31,
"styleRecalcDurationMs": 10.34,
"layouts": 6,
"layoutDurationMs": 0.36299999999999993,
"taskDurationMs": 232.323,
"heapDeltaBytes": 7981864,
"heapUsedBytes": 73443012,
"domNodes": 78,
"jsHeapTotalBytes": 24379392,
"scriptDurationMs": 5.844000000000002,
"eventListeners": 19,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "canvas-zoom-sweep",
"durationMs": 1668.4599999999818,
"styleRecalcs": 29,
"styleRecalcDurationMs": 9.772000000000002,
"layouts": 6,
"layoutDurationMs": 0.3979999999999999,
"taskDurationMs": 236.933,
"heapDeltaBytes": 8307364,
"heapUsedBytes": 73818448,
"domNodes": 78,
"jsHeapTotalBytes": 24117248,
"scriptDurationMs": 5.6209999999999996,
"eventListeners": 19,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "dom-widget-clipping",
"durationMs": 390.1910000000157,
"styleRecalcs": 15,
"styleRecalcDurationMs": 5.195999999999999,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 236.23799999999997,
"heapDeltaBytes": -11851568,
"heapUsedBytes": 53540512,
"domNodes": 24,
"jsHeapTotalBytes": 24641536,
"scriptDurationMs": 27.726,
"eventListeners": 2,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "dom-widget-clipping",
"durationMs": 391.9819999999845,
"styleRecalcs": 14,
"styleRecalcDurationMs": 5.073000000000001,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 227.61899999999997,
"heapDeltaBytes": -11808304,
"heapUsedBytes": 53583772,
"domNodes": 24,
"jsHeapTotalBytes": 24903680,
"scriptDurationMs": 29.344,
"eventListeners": 2,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.663333333333338,
"p95FrameDurationMs": 16.799999999999727
},
{
"name": "large-graph-idle",
"durationMs": 2012.2119999999768,
"styleRecalcs": 9,
"styleRecalcDurationMs": 4.175,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 415.566,
"heapDeltaBytes": 12521628,
"heapUsedBytes": 73167516,
"domNodes": -283,
"jsHeapTotalBytes": 3506176,
"scriptDurationMs": 9.395000000000001,
"eventListeners": -147,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "large-graph-idle",
"durationMs": 1998.312999999996,
"styleRecalcs": 10,
"styleRecalcDurationMs": 4.773999999999999,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 412.924,
"heapDeltaBytes": 9025048,
"heapUsedBytes": 69399896,
"domNodes": -280,
"jsHeapTotalBytes": 2981888,
"scriptDurationMs": 7.389999999999997,
"eventListeners": -177,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "large-graph-pan",
"durationMs": 2078.109999999981,
"styleRecalcs": 69,
"styleRecalcDurationMs": 8.622,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 766.7190000000002,
"heapDeltaBytes": 9975780,
"heapUsedBytes": 71557516,
"domNodes": -283,
"jsHeapTotalBytes": 3960832,
"scriptDurationMs": 209.62900000000002,
"eventListeners": -147,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "large-graph-pan",
"durationMs": 2093.9710000000105,
"styleRecalcs": 69,
"styleRecalcDurationMs": 8.204999999999998,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 743.452,
"heapDeltaBytes": -2212036,
"heapUsedBytes": 59390632,
"domNodes": -282,
"jsHeapTotalBytes": 3174400,
"scriptDurationMs": 194.26399999999998,
"eventListeners": -177,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "large-graph-zoom",
"durationMs": 3063.6989999999855,
"styleRecalcs": 67,
"styleRecalcDurationMs": 9.421000000000001,
"layouts": 60,
"layoutDurationMs": 4.417000000000001,
"taskDurationMs": 805.2450000000001,
"heapDeltaBytes": 16517384,
"heapUsedBytes": 79259056,
"domNodes": 18,
"jsHeapTotalBytes": 4980736,
"scriptDurationMs": 203.38400000000001,
"eventListeners": 8,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66999999999998,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "large-graph-zoom",
"durationMs": 3021.8169999999986,
"styleRecalcs": 66,
"styleRecalcDurationMs": 8.75,
"layouts": 60,
"layoutDurationMs": 4.34,
"taskDurationMs": 773.076,
"heapDeltaBytes": 16384064,
"heapUsedBytes": 79441356,
"domNodes": 18,
"jsHeapTotalBytes": 5767168,
"scriptDurationMs": 204.14600000000002,
"eventListeners": 8,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.699999999999818
},
{
"name": "minimap-idle",
"durationMs": 2001.1749999999893,
"styleRecalcs": 10,
"styleRecalcDurationMs": 4.874999999999999,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 403.21399999999994,
"heapDeltaBytes": 12897492,
"heapUsedBytes": 76315860,
"domNodes": -280,
"jsHeapTotalBytes": 3244032,
"scriptDurationMs": 7.181,
"eventListeners": -147,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "minimap-idle",
"durationMs": 2007.8649999999243,
"styleRecalcs": 10,
"styleRecalcDurationMs": 5.309000000000001,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 449.7559999999999,
"heapDeltaBytes": 13249424,
"heapUsedBytes": 74913720,
"domNodes": -282,
"jsHeapTotalBytes": 2981888,
"scriptDurationMs": 8.868,
"eventListeners": -147,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "subgraph-dom-widget-clipping",
"durationMs": 425.1729999999725,
"styleRecalcs": 48,
"styleRecalcDurationMs": 6.7170000000000005,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 246.13100000000003,
"heapDeltaBytes": -10012364,
"heapUsedBytes": 55643964,
"domNodes": 22,
"jsHeapTotalBytes": 24903680,
"scriptDurationMs": 66.05999999999999,
"eventListeners": 8,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000273
},
{
"name": "subgraph-dom-widget-clipping",
"durationMs": 420.7979999999907,
"styleRecalcs": 48,
"styleRecalcDurationMs": 6.851,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 230.235,
"heapDeltaBytes": -9977092,
"heapUsedBytes": 55568804,
"domNodes": 22,
"jsHeapTotalBytes": 24379392,
"scriptDurationMs": 67.093,
"eventListeners": 8,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000273
},
{
"name": "subgraph-idle",
"durationMs": 1991.7310000000157,
"styleRecalcs": 11,
"styleRecalcDurationMs": 5.183999999999999,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 301.87199999999996,
"heapDeltaBytes": 5529324,
"heapUsedBytes": 71129104,
"domNodes": 22,
"jsHeapTotalBytes": 24117248,
"scriptDurationMs": 3.941,
"eventListeners": 6,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "subgraph-idle",
"durationMs": 2003.0780000000163,
"styleRecalcs": 11,
"styleRecalcDurationMs": 5.701,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 319.141,
"heapDeltaBytes": 5474156,
"heapUsedBytes": 71130440,
"domNodes": 22,
"jsHeapTotalBytes": 24117248,
"scriptDurationMs": 4.612,
"eventListeners": 6,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.699999999999818
},
{
"name": "subgraph-mouse-sweep",
"durationMs": 1672.1919999999955,
"styleRecalcs": 77,
"styleRecalcDurationMs": 22.386999999999997,
"layouts": 16,
"layoutDurationMs": 2.6159999999999997,
"taskDurationMs": 472.993,
"heapDeltaBytes": -3650304,
"heapUsedBytes": 61860720,
"domNodes": 63,
"jsHeapTotalBytes": 23592960,
"scriptDurationMs": 44.629000000000005,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "subgraph-mouse-sweep",
"durationMs": 1674.303000000009,
"styleRecalcs": 78,
"styleRecalcDurationMs": 19.264,
"layouts": 17,
"layoutDurationMs": 2.292,
"taskDurationMs": 457.62,
"heapDeltaBytes": -3614660,
"heapUsedBytes": 61862700,
"domNodes": 65,
"jsHeapTotalBytes": 24641536,
"scriptDurationMs": 42.52,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.699999999999818
},
{
"name": "subgraph-transition-enter",
"durationMs": 799.4019999999864,
"styleRecalcs": 20,
"styleRecalcDurationMs": 19.604999999999997,
"layouts": 15,
"layoutDurationMs": 7.140000000000002,
"taskDurationMs": 573.0649999999999,
"heapDeltaBytes": -4021216,
"heapUsedBytes": 80821412,
"domNodes": 13673,
"jsHeapTotalBytes": 10223616,
"scriptDurationMs": 8.998,
"eventListeners": 2373,
"totalBlockingTimeMs": 81,
"frameDurationMs": 16.670000000000012,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "viewport-pan-sweep",
"durationMs": 8087.20500000004,
"styleRecalcs": 248,
"styleRecalcDurationMs": 19.584,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 2553.4410000000003,
"heapDeltaBytes": 15252248,
"heapUsedBytes": 75909200,
"domNodes": -282,
"jsHeapTotalBytes": 3436544,
"scriptDurationMs": 606.749,
"eventListeners": -133,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "viewport-pan-sweep",
"durationMs": 8061.149,
"styleRecalcs": 249,
"styleRecalcDurationMs": 20.041000000000004,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 2586.3619999999996,
"heapDeltaBytes": 10281124,
"heapUsedBytes": 72321960,
"domNodes": -280,
"jsHeapTotalBytes": 3436544,
"scriptDurationMs": 586.2429999999999,
"eventListeners": -133,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "vue-large-graph-idle",
"durationMs": 9033.644000000038,
"styleRecalcs": 0,
"styleRecalcDurationMs": 0,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 8268.479000000001,
"heapDeltaBytes": -36839164,
"heapUsedBytes": 175021776,
"domNodes": -8312,
"jsHeapTotalBytes": -11472896,
"scriptDurationMs": 38.56699999999999,
"eventListeners": -16393,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "vue-large-graph-idle",
"durationMs": 9517.021000000113,
"styleRecalcs": 0,
"styleRecalcDurationMs": 0,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 8573.768999999998,
"heapDeltaBytes": -30775280,
"heapUsedBytes": 166678512,
"domNodes": -8312,
"jsHeapTotalBytes": -6496256,
"scriptDurationMs": 50.43300000000001,
"eventListeners": -16393,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "vue-large-graph-pan",
"durationMs": 11734.601999999995,
"styleRecalcs": 67,
"styleRecalcDurationMs": 10.809999999999986,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 10997.520999999999,
"heapDeltaBytes": -29030932,
"heapUsedBytes": 169433684,
"domNodes": -8316,
"jsHeapTotalBytes": -15179776,
"scriptDurationMs": 321.412,
"eventListeners": -16387,
"totalBlockingTimeMs": 45,
"frameDurationMs": 17.776666666666642,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "vue-large-graph-pan",
"durationMs": 11463.456000000064,
"styleRecalcs": 67,
"styleRecalcDurationMs": 9.992999999999974,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 10550.43,
"heapDeltaBytes": -35608240,
"heapUsedBytes": 177381972,
"domNodes": -8312,
"jsHeapTotalBytes": -6303744,
"scriptDurationMs": 249.209,
"eventListeners": -16389,
"totalBlockingTimeMs": 0,
"frameDurationMs": 17.223333333333358,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "workflow-execution",
"durationMs": 458.48499999999603,
"styleRecalcs": 19,
"styleRecalcDurationMs": 14.320999999999998,
"layouts": 3,
"layoutDurationMs": 0.852,
"taskDurationMs": 95.59399999999998,
"heapDeltaBytes": 5093380,
"heapUsedBytes": 69667760,
"domNodes": 152,
"jsHeapTotalBytes": 5242880,
"scriptDurationMs": 5.451,
"eventListeners": 99,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.799999999999727
},
{
"name": "workflow-execution",
"durationMs": 419.36500000008436,
"styleRecalcs": 13,
"styleRecalcDurationMs": 9.956000000000001,
"layouts": 2,
"layoutDurationMs": 0.2459999999999999,
"taskDurationMs": 64.27000000000001,
"heapDeltaBytes": 4951008,
"heapUsedBytes": 69521760,
"domNodes": 123,
"jsHeapTotalBytes": 5242880,
"scriptDurationMs": 4.906000000000001,
"eventListeners": 99,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.663333333333338,
"p95FrameDurationMs": 16.799999999999727
}
]
} |
Codecov Report❌ Patch coverage is
@@ Coverage Diff @@
## cloud/1.51 #15589 +/- ##
=============================================
Coverage ? 81.52%
=============================================
Files ? 1884
Lines ? 117704
Branches ? 37242
=============================================
Hits ? 95961
Misses ? 21272
Partials ? 471
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
Backport of #15239 to
cloud/1.51Automatically created by backport workflow.