[backport core/1.51] feat: align local settings with V1 billing layout (FE-1609) - #15588
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>
🎨 Storybook: ✅ Built — View Storybook🎭 Playwright: ✅ 1821 passed, 0 failed · 4 flaky📊 Browser Reports
📦 Bundle Size
⚡ Performance Report
Show regressions
All metrics
Historical variance (last 15 runs)
Trend (last 15 commits on main)
Raw data{
"timestamp": "2026-08-21T23:47:13.192Z",
"gitSha": "a80a5e2b30283a4cb1f3f9dd791315c9ae1f1919",
"branch": "backport-15239-to-core-1.51",
"measurements": [
{
"name": "canvas-idle",
"durationMs": 2009.2159999999808,
"styleRecalcs": 9,
"styleRecalcDurationMs": 6.916999999999998,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 436.485,
"heapDeltaBytes": 5146760,
"heapUsedBytes": 70477992,
"domNodes": 18,
"jsHeapTotalBytes": 24379392,
"scriptDurationMs": 5.983999999999998,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.699999999999818
},
{
"name": "canvas-idle",
"durationMs": 2023.36200000002,
"styleRecalcs": 11,
"styleRecalcDurationMs": 9.576,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 443.52900000000005,
"heapDeltaBytes": 4897244,
"heapUsedBytes": 70330236,
"domNodes": 22,
"jsHeapTotalBytes": 23855104,
"scriptDurationMs": 6.307999999999999,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "canvas-mouse-sweep",
"durationMs": 2033.1300000000283,
"styleRecalcs": 76,
"styleRecalcDurationMs": 40.346,
"layouts": 12,
"layoutDurationMs": 3.614,
"taskDurationMs": 984.7429999999999,
"heapDeltaBytes": -16776652,
"heapUsedBytes": 48501276,
"domNodes": -280,
"jsHeapTotalBytes": 22908928,
"scriptDurationMs": 127.37599999999999,
"eventListeners": -151,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "canvas-mouse-sweep",
"durationMs": 1756.07100000002,
"styleRecalcs": 73,
"styleRecalcDurationMs": 32.159000000000006,
"layouts": 12,
"layoutDurationMs": 3.236,
"taskDurationMs": 778.784,
"heapDeltaBytes": 36964,
"heapUsedBytes": 65282716,
"domNodes": 54,
"jsHeapTotalBytes": 24907776,
"scriptDurationMs": 106.71700000000001,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "canvas-zoom-sweep",
"durationMs": 1721.6530000000034,
"styleRecalcs": 31,
"styleRecalcDurationMs": 15.214,
"layouts": 6,
"layoutDurationMs": 0.52,
"taskDurationMs": 366.865,
"heapDeltaBytes": 8151364,
"heapUsedBytes": 73419948,
"domNodes": 77,
"jsHeapTotalBytes": 24379392,
"scriptDurationMs": 9.613,
"eventListeners": 19,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "canvas-zoom-sweep",
"durationMs": 1739.6019999999908,
"styleRecalcs": 32,
"styleRecalcDurationMs": 16.366,
"layouts": 6,
"layoutDurationMs": 0.678,
"taskDurationMs": 358.156,
"heapDeltaBytes": 7702732,
"heapUsedBytes": 73221200,
"domNodes": 79,
"jsHeapTotalBytes": 24379392,
"scriptDurationMs": 8.77,
"eventListeners": 19,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333335,
"p95FrameDurationMs": 16.699999999999818
},
{
"name": "dom-widget-clipping",
"durationMs": 573.7670000000321,
"styleRecalcs": 11,
"styleRecalcDurationMs": 6.9639999999999995,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 353.33400000000006,
"heapDeltaBytes": -11604268,
"heapUsedBytes": 53772252,
"domNodes": 18,
"jsHeapTotalBytes": 25427968,
"scriptDurationMs": 53.555,
"eventListeners": 2,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "dom-widget-clipping",
"durationMs": 579.5679999999948,
"styleRecalcs": 13,
"styleRecalcDurationMs": 8.645999999999999,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 357.308,
"heapDeltaBytes": -11502352,
"heapUsedBytes": 54099660,
"domNodes": 22,
"jsHeapTotalBytes": 24641536,
"scriptDurationMs": 52.443000000000005,
"eventListeners": 2,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.670000000000012,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "large-graph-idle",
"durationMs": 2012.7100000000269,
"styleRecalcs": 8,
"styleRecalcDurationMs": 6.931,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 573.0450000000001,
"heapDeltaBytes": 12364136,
"heapUsedBytes": 72182716,
"domNodes": -282,
"jsHeapTotalBytes": 2195456,
"scriptDurationMs": 12.32,
"eventListeners": -179,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "large-graph-idle",
"durationMs": 2037.386999999967,
"styleRecalcs": 9,
"styleRecalcDurationMs": 7.177,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 572.517,
"heapDeltaBytes": 12666744,
"heapUsedBytes": 72424068,
"domNodes": -280,
"jsHeapTotalBytes": 2981888,
"scriptDurationMs": 13.804999999999998,
"eventListeners": -149,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "large-graph-pan",
"durationMs": 2150.935000000004,
"styleRecalcs": 68,
"styleRecalcDurationMs": 12.132,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 1122.299,
"heapDeltaBytes": 985308,
"heapUsedBytes": 62638068,
"domNodes": -282,
"jsHeapTotalBytes": 3960832,
"scriptDurationMs": 314.35400000000004,
"eventListeners": -147,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "large-graph-pan",
"durationMs": 2152.6269999999386,
"styleRecalcs": 68,
"styleRecalcDurationMs": 11.890999999999998,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 1133.898,
"heapDeltaBytes": 1998328,
"heapUsedBytes": 63155504,
"domNodes": -282,
"jsHeapTotalBytes": 3960832,
"scriptDurationMs": 323.048,
"eventListeners": -179,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "large-graph-zoom",
"durationMs": 3130.8400000000347,
"styleRecalcs": 65,
"styleRecalcDurationMs": 14.276999999999997,
"layouts": 60,
"layoutDurationMs": 7.043,
"taskDurationMs": 1326.374,
"heapDeltaBytes": -7120136,
"heapUsedBytes": 55681748,
"domNodes": -284,
"jsHeapTotalBytes": 2719744,
"scriptDurationMs": 368.224,
"eventListeners": -153,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.699999999999818
},
{
"name": "large-graph-zoom",
"durationMs": 3132.0619999999053,
"styleRecalcs": 65,
"styleRecalcDurationMs": 13.854999999999999,
"layouts": 60,
"layoutDurationMs": 7.083000000000001,
"taskDurationMs": 1330.64,
"heapDeltaBytes": -6514764,
"heapUsedBytes": 55760860,
"domNodes": -286,
"jsHeapTotalBytes": 2719744,
"scriptDurationMs": 373.964,
"eventListeners": -153,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "minimap-idle",
"durationMs": 2021.3090000000307,
"styleRecalcs": 8,
"styleRecalcDurationMs": 7.123000000000001,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 578.057,
"heapDeltaBytes": 8263620,
"heapUsedBytes": 69330168,
"domNodes": -282,
"jsHeapTotalBytes": 3768320,
"scriptDurationMs": 13.556,
"eventListeners": -181,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "minimap-idle",
"durationMs": 2009.4990000000053,
"styleRecalcs": 8,
"styleRecalcDurationMs": 6.483000000000002,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 569.88,
"heapDeltaBytes": 9057440,
"heapUsedBytes": 70108176,
"domNodes": -284,
"jsHeapTotalBytes": 2981888,
"scriptDurationMs": 12.532000000000002,
"eventListeners": -179,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.699999999999818
},
{
"name": "subgraph-dom-widget-clipping",
"durationMs": 597.3719999999503,
"styleRecalcs": 47,
"styleRecalcDurationMs": 10.409,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 394.093,
"heapDeltaBytes": -9949488,
"heapUsedBytes": 55598432,
"domNodes": 20,
"jsHeapTotalBytes": 24379392,
"scriptDurationMs": 117.511,
"eventListeners": 6,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "subgraph-dom-widget-clipping",
"durationMs": 581.5840000000208,
"styleRecalcs": 47,
"styleRecalcDurationMs": 10.557000000000002,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 399.9200000000001,
"heapDeltaBytes": -9906068,
"heapUsedBytes": 55551300,
"domNodes": 20,
"jsHeapTotalBytes": 24641536,
"scriptDurationMs": 120.981,
"eventListeners": 8,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.699999999999818
},
{
"name": "subgraph-idle",
"durationMs": 2004.5450000000073,
"styleRecalcs": 9,
"styleRecalcDurationMs": 7.676,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 442.655,
"heapDeltaBytes": 5385232,
"heapUsedBytes": 70838252,
"domNodes": 18,
"jsHeapTotalBytes": 24379392,
"scriptDurationMs": 5.4739999999999975,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "subgraph-idle",
"durationMs": 2004.4789999999466,
"styleRecalcs": 8,
"styleRecalcDurationMs": 6.686000000000001,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 426.715,
"heapDeltaBytes": 5586300,
"heapUsedBytes": 71186228,
"domNodes": 16,
"jsHeapTotalBytes": 24117248,
"scriptDurationMs": 5.657000000000001,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "subgraph-mouse-sweep",
"durationMs": 1674.8929999999973,
"styleRecalcs": 75,
"styleRecalcDurationMs": 32.2,
"layouts": 16,
"layoutDurationMs": 3.8580000000000005,
"taskDurationMs": 729.375,
"heapDeltaBytes": -3849016,
"heapUsedBytes": 61548016,
"domNodes": 60,
"jsHeapTotalBytes": 24641536,
"scriptDurationMs": 81.69699999999999,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "subgraph-mouse-sweep",
"durationMs": 1715.4580000000124,
"styleRecalcs": 77,
"styleRecalcDurationMs": 34.095,
"layouts": 16,
"layoutDurationMs": 4.016,
"taskDurationMs": 746.905,
"heapDeltaBytes": -3834060,
"heapUsedBytes": 61542544,
"domNodes": 63,
"jsHeapTotalBytes": 24641536,
"scriptDurationMs": 86.007,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "subgraph-transition-enter",
"durationMs": 1406.9049999999947,
"styleRecalcs": 18,
"styleRecalcDurationMs": 29.303999999999995,
"layouts": 13,
"layoutDurationMs": 12.218,
"taskDurationMs": 895.095,
"heapDeltaBytes": 22248080,
"heapUsedBytes": 99467536,
"domNodes": 13673,
"jsHeapTotalBytes": 10485760,
"scriptDurationMs": 15.647999999999996,
"eventListeners": 2375,
"totalBlockingTimeMs": 143,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "viewport-pan-sweep",
"durationMs": 8228.738000000021,
"styleRecalcs": 250,
"styleRecalcDurationMs": 34.754999999999995,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 3964.7769999999996,
"heapDeltaBytes": 5325036,
"heapUsedBytes": 65995680,
"domNodes": -281,
"jsHeapTotalBytes": 4485120,
"scriptDurationMs": 988.8070000000001,
"eventListeners": -163,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.670000000000012,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "viewport-pan-sweep",
"durationMs": 8254.370999999992,
"styleRecalcs": 250,
"styleRecalcDurationMs": 35.016999999999996,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 3994.964,
"heapDeltaBytes": 14980640,
"heapUsedBytes": 74662308,
"domNodes": -283,
"jsHeapTotalBytes": 3698688,
"scriptDurationMs": 1004.5569999999999,
"eventListeners": -133,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "vue-large-graph-idle",
"durationMs": 16721.875999999953,
"styleRecalcs": 0,
"styleRecalcDurationMs": 0,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 15754.347999999998,
"heapDeltaBytes": -25927144,
"heapUsedBytes": 171286216,
"domNodes": -8312,
"jsHeapTotalBytes": -14098432,
"scriptDurationMs": 108.519,
"eventListeners": -16389,
"totalBlockingTimeMs": 0,
"frameDurationMs": 17.780000000000047,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "vue-large-graph-idle",
"durationMs": 16369.872999999985,
"styleRecalcs": 0,
"styleRecalcDurationMs": 0,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 15319.860999999999,
"heapDeltaBytes": -28714740,
"heapUsedBytes": 168405948,
"domNodes": -8312,
"jsHeapTotalBytes": -10952704,
"scriptDurationMs": 100.61099999999999,
"eventListeners": -16387,
"totalBlockingTimeMs": 0,
"frameDurationMs": 17.776666666666642,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "vue-large-graph-pan",
"durationMs": 19952.395999999964,
"styleRecalcs": 171,
"styleRecalcDurationMs": 16.52999999999999,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 19434.896,
"heapDeltaBytes": -14234672,
"heapUsedBytes": 183136996,
"domNodes": -8312,
"jsHeapTotalBytes": -12857344,
"scriptDurationMs": 383.108,
"eventListeners": -16385,
"totalBlockingTimeMs": 96,
"frameDurationMs": 17.779999999999927,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "vue-large-graph-pan",
"durationMs": 19690.623000000072,
"styleRecalcs": 173,
"styleRecalcDurationMs": 15.34100000000002,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 19172.32,
"heapDeltaBytes": -14416276,
"heapUsedBytes": 183052108,
"domNodes": -8312,
"jsHeapTotalBytes": -13119488,
"scriptDurationMs": 383.733,
"eventListeners": -16385,
"totalBlockingTimeMs": 28,
"frameDurationMs": 17.223333333333358,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "workflow-execution",
"durationMs": 449.30299999998624,
"styleRecalcs": 13,
"styleRecalcDurationMs": 16.341,
"layouts": 2,
"layoutDurationMs": 0.41200000000000003,
"taskDurationMs": 99.178,
"heapDeltaBytes": 4947720,
"heapUsedBytes": 69633028,
"domNodes": 126,
"jsHeapTotalBytes": 4718592,
"scriptDurationMs": 6.554999999999998,
"eventListeners": 99,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.699999999999818
},
{
"name": "workflow-execution",
"durationMs": 453.1660000000102,
"styleRecalcs": 14,
"styleRecalcDurationMs": 19.778,
"layouts": 3,
"layoutDurationMs": 1.015,
"taskDurationMs": 106.877,
"heapDeltaBytes": 4926880,
"heapUsedBytes": 69722996,
"domNodes": 123,
"jsHeapTotalBytes": 5242880,
"scriptDurationMs": 6.915999999999999,
"eventListeners": 97,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
}
]
} |
Codecov Report❌ Patch coverage is
@@ Coverage Diff @@
## core/1.51 #15588 +/- ##
=============================================
- Coverage 81.63% 81.62% -0.01%
=============================================
Files 1884 1884
Lines 107114 107109 -5
Branches 32644 33108 +464
=============================================
- Hits 87443 87433 -10
- Misses 19329 19333 +4
- Partials 342 343 +1
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
core/1.51Automatically created by backport workflow.