feat(billing): workspace Activity tab — per-member usage ledger (FE-1247)#13644
feat(billing): workspace Activity tab — per-member usage ledger (FE-1247)#13644dante01yoon wants to merge 9 commits into
Conversation
…247) Adds Settings ▸ Plan & Credits ▸ Activity: a role-scoped, auto-paginated credit-usage ledger with per-user and partner-node hover-card rollups. Owners/admins see team-wide activity; members see their own usage. - useWorkspaceActivity is the data seam (empty until FE-1249 wires the per-workspace usage API); the ledger renders its empty state today. - Splits the workspace settings sidebar into Plan & Credits + Members; the Plan & Credits panel hosts Credits + Activity tabs. - Adds shared table / pagination / hover-card primitives and useAutoPageSize. Design: DES-479 (Activity), DES-497 (tab shell). Reimplemented from the prototype (#13487/#13593) as our own logic.
🎨 Storybook: ✅ Built — View Storybook📦 Bundle: 7.93 MB gzip 🔴 +14.6 kBDetailsSummary
Category Glance App Entry Points — 47.3 kB (baseline 47.3 kB) • ⚪ 0 BMain entry bundles and manifests
Status: 1 added / 1 removed Graph Workspace — 1.25 MB (baseline 1.25 MB) • 🔴 +8 BGraph editor runtime, canvas, workflow orchestration
Status: 1 added / 1 removed Views & Navigation — 109 kB (baseline 109 kB) • ⚪ 0 BTop-level views, pages, and routed surfaces
Status: 12 added / 12 removed / 2 unchanged Panels & Settings — 550 kB (baseline 550 kB) • ⚪ 0 BConfiguration panels, inspectors, and settings screens
Status: 11 added / 11 removed / 15 unchanged User & Accounts — 29.1 kB (baseline 29.1 kB) • ⚪ 0 BAuthentication, profile, and account management bundles
Status: 7 added / 7 removed / 3 unchanged Editors & Dialogs — 121 kB (baseline 121 kB) • ⚪ 0 BModals, dialogs, drawers, and in-app editors
Status: 6 added / 6 removed UI Components — 56.8 kB (baseline 56.8 kB) • 🔴 +1 BReusable component library chunks
Status: 9 added / 9 removed / 4 unchanged Data & Services — 270 kB (baseline 270 kB) • ⚪ 0 BStores, services, APIs, and repositories
Status: 13 added / 13 removed / 3 unchanged Utilities & Hooks — 3.4 MB (baseline 3.4 MB) • 🔴 +1.77 kBHelpers, composables, and utility bundles
Status: 19 added / 19 removed / 17 unchanged Vendor & Third-Party — 15.7 MB (baseline 15.7 MB) • 🔴 +30.5 kBExternal libraries and shared vendor chunks
Status: 1 added / 1 removed / 15 unchanged Other — 11.9 MB (baseline 11.9 MB) • 🔴 +37.2 kBBundles that do not match a named category
Status: 92 added / 90 removed / 73 unchanged ⚡ Performance Report
Show regressions
All metrics
Historical variance (last 15 runs)
Trend (last 15 commits on main)
Raw data{
"timestamp": "2026-07-17T22:23:43.655Z",
"gitSha": "212382ed3d5a51687a1ffe62112a74b9260ada0a",
"branch": "jaewon/fe-1247-settings-billing-per-member-spend-activity-log-ui-owneradmin",
"measurements": [
{
"name": "canvas-idle",
"durationMs": 2050.713999999999,
"styleRecalcs": 9,
"styleRecalcDurationMs": 9.032,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 467.65200000000004,
"heapDeltaBytes": 3915992,
"heapUsedBytes": 72637944,
"domNodes": 18,
"jsHeapTotalBytes": 19673088,
"scriptDurationMs": 24.721,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.670000000000012,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "canvas-idle",
"durationMs": 2043.6480000000188,
"styleRecalcs": 10,
"styleRecalcDurationMs": 8.797,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 371.96999999999997,
"heapDeltaBytes": 3974652,
"heapUsedBytes": 72720744,
"domNodes": 20,
"jsHeapTotalBytes": 19410944,
"scriptDurationMs": 15.466000000000001,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "canvas-mouse-sweep",
"durationMs": 2036.012999999997,
"styleRecalcs": 79,
"styleRecalcDurationMs": 45.971,
"layouts": 12,
"layoutDurationMs": 4.890999999999999,
"taskDurationMs": 969.16,
"heapDeltaBytes": -13480892,
"heapUsedBytes": 55166980,
"domNodes": -264,
"jsHeapTotalBytes": 20848640,
"scriptDurationMs": 143.336,
"eventListeners": -131,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.670000000000012,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "canvas-mouse-sweep",
"durationMs": 1873.913000000016,
"styleRecalcs": 75,
"styleRecalcDurationMs": 39.425000000000004,
"layouts": 12,
"layoutDurationMs": 3.669,
"taskDurationMs": 806.7540000000001,
"heapDeltaBytes": -15840640,
"heapUsedBytes": 53098964,
"domNodes": -270,
"jsHeapTotalBytes": 20848640,
"scriptDurationMs": 125.09899999999999,
"eventListeners": -133,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "canvas-zoom-sweep",
"durationMs": 1737.5109999999836,
"styleRecalcs": 32,
"styleRecalcDurationMs": 20.373,
"layouts": 6,
"layoutDurationMs": 0.649,
"taskDurationMs": 387.903,
"heapDeltaBytes": 7091244,
"heapUsedBytes": 75835500,
"domNodes": 79,
"jsHeapTotalBytes": 20197376,
"scriptDurationMs": 27.501000000000005,
"eventListeners": 19,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "canvas-zoom-sweep",
"durationMs": 1674.9449999999797,
"styleRecalcs": 31,
"styleRecalcDurationMs": 20.997000000000003,
"layouts": 6,
"layoutDurationMs": 0.784,
"taskDurationMs": 395.747,
"heapDeltaBytes": 7173924,
"heapUsedBytes": 76239736,
"domNodes": 78,
"jsHeapTotalBytes": 20197376,
"scriptDurationMs": 28.226,
"eventListeners": 19,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333335,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "dom-widget-clipping",
"durationMs": 595.8929999999896,
"styleRecalcs": 12,
"styleRecalcDurationMs": 9.934999999999999,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 369.099,
"heapDeltaBytes": -11669008,
"heapUsedBytes": 57120516,
"domNodes": 20,
"jsHeapTotalBytes": 19673088,
"scriptDurationMs": 61.675,
"eventListeners": 2,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.670000000000012,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "dom-widget-clipping",
"durationMs": 562.1060000000284,
"styleRecalcs": 12,
"styleRecalcDurationMs": 9.169000000000002,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 354.348,
"heapDeltaBytes": -11727896,
"heapUsedBytes": 57118116,
"domNodes": 20,
"jsHeapTotalBytes": 19935232,
"scriptDurationMs": 60.591,
"eventListeners": 2,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "large-graph-idle",
"durationMs": 2049.6389999999565,
"styleRecalcs": 9,
"styleRecalcDurationMs": 11.312,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 650.017,
"heapDeltaBytes": 4863340,
"heapUsedBytes": 64150684,
"domNodes": -269,
"jsHeapTotalBytes": 4403200,
"scriptDurationMs": 114.38999999999999,
"eventListeners": -129,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.699999999999818
},
{
"name": "large-graph-idle",
"durationMs": 2032.4970000000349,
"styleRecalcs": 8,
"styleRecalcDurationMs": 9.109000000000002,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 657.883,
"heapDeltaBytes": 5328716,
"heapUsedBytes": 64799916,
"domNodes": -267,
"jsHeapTotalBytes": 4927488,
"scriptDurationMs": 115.66399999999999,
"eventListeners": -127,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "large-graph-pan",
"durationMs": 2173.9939999999933,
"styleRecalcs": 69,
"styleRecalcDurationMs": 18.675,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 1178.8379999999997,
"heapDeltaBytes": -4158768,
"heapUsedBytes": 56394716,
"domNodes": -269,
"jsHeapTotalBytes": 4927488,
"scriptDurationMs": 411.65,
"eventListeners": -127,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "large-graph-pan",
"durationMs": 2137.377000000015,
"styleRecalcs": 69,
"styleRecalcDurationMs": 14.967000000000004,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 1204.148,
"heapDeltaBytes": -4598596,
"heapUsedBytes": 55854092,
"domNodes": -269,
"jsHeapTotalBytes": 5451776,
"scriptDurationMs": 417.17099999999994,
"eventListeners": -129,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "large-graph-zoom",
"durationMs": 3228.829000000019,
"styleRecalcs": 66,
"styleRecalcDurationMs": 16.954,
"layouts": 60,
"layoutDurationMs": 7.734,
"taskDurationMs": 1460.655,
"heapDeltaBytes": -2431288,
"heapUsedBytes": 61579104,
"domNodes": -272,
"jsHeapTotalBytes": 9121792,
"scriptDurationMs": 540.0079999999999,
"eventListeners": -133,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "large-graph-zoom",
"durationMs": 3230.995000000007,
"styleRecalcs": 66,
"styleRecalcDurationMs": 22.086,
"layouts": 60,
"layoutDurationMs": 8.001999999999999,
"taskDurationMs": 1472.203,
"heapDeltaBytes": 673080,
"heapUsedBytes": 62508200,
"domNodes": -272,
"jsHeapTotalBytes": 6762496,
"scriptDurationMs": 526.034,
"eventListeners": -133,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.699999999999818
},
{
"name": "minimap-idle",
"durationMs": 2036.5300000000275,
"styleRecalcs": 8,
"styleRecalcDurationMs": 8.327000000000002,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 582.1690000000001,
"heapDeltaBytes": 5347564,
"heapUsedBytes": 66350560,
"domNodes": -269,
"jsHeapTotalBytes": 4927488,
"scriptDurationMs": 102.033,
"eventListeners": -129,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "minimap-idle",
"durationMs": 2038.3019999999306,
"styleRecalcs": 9,
"styleRecalcDurationMs": 10.733999999999996,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 707.352,
"heapDeltaBytes": 5419080,
"heapUsedBytes": 66256248,
"domNodes": -267,
"jsHeapTotalBytes": 5451776,
"scriptDurationMs": 121.339,
"eventListeners": -129,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.699999999999818
},
{
"name": "subgraph-dom-widget-clipping",
"durationMs": 612.9109999999969,
"styleRecalcs": 47,
"styleRecalcDurationMs": 11.258999999999999,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 382.78100000000006,
"heapDeltaBytes": -11314608,
"heapUsedBytes": 57656572,
"domNodes": 20,
"jsHeapTotalBytes": 20459520,
"scriptDurationMs": 123.257,
"eventListeners": 8,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "subgraph-dom-widget-clipping",
"durationMs": 614.5819999999276,
"styleRecalcs": 47,
"styleRecalcDurationMs": 13.152000000000001,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 399.215,
"heapDeltaBytes": -11077524,
"heapUsedBytes": 57842168,
"domNodes": 20,
"jsHeapTotalBytes": 20197376,
"scriptDurationMs": 127.44900000000001,
"eventListeners": 8,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66999999999998,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "subgraph-idle",
"durationMs": 2055.1829999999995,
"styleRecalcs": 9,
"styleRecalcDurationMs": 14.174999999999999,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 482.021,
"heapDeltaBytes": -17969380,
"heapUsedBytes": 50804300,
"domNodes": -265,
"jsHeapTotalBytes": 20324352,
"scriptDurationMs": 20.352,
"eventListeners": -133,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "subgraph-idle",
"durationMs": 2054.8009999999977,
"styleRecalcs": 11,
"styleRecalcDurationMs": 10.058,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 466.879,
"heapDeltaBytes": -20031956,
"heapUsedBytes": 48862472,
"domNodes": -265,
"jsHeapTotalBytes": 20324352,
"scriptDurationMs": 19.271,
"eventListeners": -133,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "subgraph-mouse-sweep",
"durationMs": 1690.5330000000163,
"styleRecalcs": 76,
"styleRecalcDurationMs": 38.046,
"layouts": 16,
"layoutDurationMs": 3.742,
"taskDurationMs": 701.682,
"heapDeltaBytes": -5092188,
"heapUsedBytes": 63723340,
"domNodes": 64,
"jsHeapTotalBytes": 20459520,
"scriptDurationMs": 96.592,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "subgraph-mouse-sweep",
"durationMs": 1724.9290000000315,
"styleRecalcs": 75,
"styleRecalcDurationMs": 39.795,
"layouts": 16,
"layoutDurationMs": 4.794,
"taskDurationMs": 771.555,
"heapDeltaBytes": -19868696,
"heapUsedBytes": 48973228,
"domNodes": -266,
"jsHeapTotalBytes": 19800064,
"scriptDurationMs": 102.447,
"eventListeners": -133,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "subgraph-transition-enter",
"durationMs": 982.4820000000045,
"styleRecalcs": 18,
"styleRecalcDurationMs": 31.26000000000001,
"layouts": 14,
"layoutDurationMs": 13.853000000000005,
"taskDurationMs": 778.405,
"heapDeltaBytes": 27323280,
"heapUsedBytes": 96001144,
"domNodes": 13673,
"jsHeapTotalBytes": 16515072,
"scriptDurationMs": 29.45700000000001,
"eventListeners": 2369,
"totalBlockingTimeMs": 140,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "viewport-pan-sweep",
"durationMs": 8204.551999999978,
"styleRecalcs": 249,
"styleRecalcDurationMs": 39.553999999999995,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 4011.0599999999995,
"heapDeltaBytes": 15428668,
"heapUsedBytes": 75193032,
"domNodes": -267,
"jsHeapTotalBytes": 7491584,
"scriptDurationMs": 1288.049,
"eventListeners": -113,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.670000000000012,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "viewport-pan-sweep",
"durationMs": 8183.177000000001,
"styleRecalcs": 250,
"styleRecalcDurationMs": 40.695,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 4116.598,
"heapDeltaBytes": 15940848,
"heapUsedBytes": 74973732,
"domNodes": -267,
"jsHeapTotalBytes": 6705152,
"scriptDurationMs": 1328.222,
"eventListeners": -115,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.670000000000012,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "vue-large-graph-idle",
"durationMs": 12688.505000000021,
"styleRecalcs": 0,
"styleRecalcDurationMs": 0,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 12674.106,
"heapDeltaBytes": -32025892,
"heapUsedBytes": 170347644,
"domNodes": -8311,
"jsHeapTotalBytes": -8851456,
"scriptDurationMs": 551.041,
"eventListeners": -16386,
"totalBlockingTimeMs": 0,
"frameDurationMs": 17.223333333333358,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "vue-large-graph-idle",
"durationMs": 13458.703000000014,
"styleRecalcs": 0,
"styleRecalcDurationMs": 0,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 13424.801000000001,
"heapDeltaBytes": -58759308,
"heapUsedBytes": 171132672,
"domNodes": -8311,
"jsHeapTotalBytes": -6754304,
"scriptDurationMs": 640.534,
"eventListeners": -16386,
"totalBlockingTimeMs": 0,
"frameDurationMs": 17.77333333333336,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "vue-large-graph-pan",
"durationMs": 15460.655000000032,
"styleRecalcs": 78,
"styleRecalcDurationMs": 12.363000000000014,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 15437.469000000001,
"heapDeltaBytes": -36242680,
"heapUsedBytes": 164547692,
"domNodes": -8311,
"jsHeapTotalBytes": -15142912,
"scriptDurationMs": 931.0050000000001,
"eventListeners": -16380,
"totalBlockingTimeMs": 56,
"frameDurationMs": 17.220000000000073,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "vue-large-graph-pan",
"durationMs": 16517.098000000035,
"styleRecalcs": 94,
"styleRecalcDurationMs": 15.190000000000037,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 16487.915,
"heapDeltaBytes": -51120084,
"heapUsedBytes": 156580636,
"domNodes": -8311,
"jsHeapTotalBytes": -15314944,
"scriptDurationMs": 935.974,
"eventListeners": -16380,
"totalBlockingTimeMs": 0,
"frameDurationMs": 17.219999999999953,
"p95FrameDurationMs": 16.80000000000291
},
{
"name": "workflow-execution",
"durationMs": 449.72000000001344,
"styleRecalcs": 14,
"styleRecalcDurationMs": 19.156,
"layouts": 2,
"layoutDurationMs": 0.428,
"taskDurationMs": 115.07600000000001,
"heapDeltaBytes": -16337900,
"heapUsedBytes": 51672980,
"domNodes": 130,
"jsHeapTotalBytes": 7090176,
"scriptDurationMs": 9.849,
"eventListeners": 67,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "workflow-execution",
"durationMs": 494.3349999999782,
"styleRecalcs": 12,
"styleRecalcDurationMs": 19.750999999999998,
"layouts": 3,
"layoutDurationMs": 0.6839999999999999,
"taskDurationMs": 139.095,
"heapDeltaBytes": -16348764,
"heapUsedBytes": 51725432,
"domNodes": 117,
"jsHeapTotalBytes": 7090176,
"scriptDurationMs": 12.308,
"eventListeners": 65,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333335,
"p95FrameDurationMs": 16.800000000000182
}
]
}🎭 Playwright: ✅ 1717 passed, 0 failed📊 Browser Reports
|
🎭 Playwright: ⏳ Running... |
📝 WalkthroughWalkthroughAdds workspace activity and member settings panels, billing-controlled navigation, reusable UI primitives, reactive activity utilities, localization, tests, browser coverage, Storybook fixtures, and authentication/workspace mocks. ChangesWorkspace activity and settings experience
Estimated code review effort: 4 (Complex) | ~60 minutes Suggested labels: Suggested reviewers: Sequence Diagram(s)sequenceDiagram
participant User
participant SettingDialog
participant WorkspaceActivityContent
participant useWorkspaceActivity
participant useMembersPanel
User->>SettingDialog: open Plan and Credits
SettingDialog->>WorkspaceActivityContent: render activity tab
WorkspaceActivityContent->>useWorkspaceActivity: provide search, page size, scope, and events
useWorkspaceActivity-->>WorkspaceActivityContent: return paged activity and summaries
User->>WorkspaceActivityContent: choose See members
WorkspaceActivityContent->>useMembersPanel: request credits sorting
WorkspaceActivityContent->>SettingDialog: navigate to workspace-members
SettingDialog->>useMembersPanel: consume pending sort request
sequenceDiagram
participant FeatureFlags
participant useSettingUI
participant SettingDialog
participant WorkspaceSettingsPanelContent
participant PlanCreditsPanelContent
FeatureFlags->>useSettingUI: set billingControlEnabled
useSettingUI->>SettingDialog: update workspace navigation
SettingDialog->>WorkspaceSettingsPanelContent: select planCredits or members section
WorkspaceSettingsPanelContent->>PlanCreditsPanelContent: render selected section
WorkspaceSettingsPanelContent-->>SettingDialog: preserve shared banner instance
Important Pre-merge checks failedPlease resolve all errors before merging. Addressing warnings is optional. ❌ Failed checks (1 inconclusive)
✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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 |
There was a problem hiding this comment.
Actionable comments posted: 6
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
src/components/ui/hover-card/HoverCardTrigger.vue (1)
1-13: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winForward props using
useForwardProps.As per coding guidelines, Reka UI wrapper components must use
useForwardPropsfor prop forwarding. Passingpropsdirectly viav-bindpassesundefinedfor any omitted props, which can unintentionally overwrite Reka UI's internal component defaults.♻️ Proposed fix
<script setup lang="ts"> -import { HoverCardTrigger } from 'reka-ui' +import { HoverCardTrigger, useForwardProps } from 'reka-ui' import type { HoverCardTriggerProps } from 'reka-ui' const props = defineProps<HoverCardTriggerProps>() +const forwarded = useForwardProps(props) </script> <template> - <HoverCardTrigger v-bind="props"> + <HoverCardTrigger v-bind="forwarded"> <slot /> </HoverCardTrigger> </template>🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/components/ui/hover-card/HoverCardTrigger.vue` around lines 1 - 13, Update HoverCardTrigger to use Reka UI’s useForwardProps for forwarding the defined HoverCardTriggerProps, and bind the resulting forwarded props to the inner HoverCardTrigger instead of binding props directly. Preserve the existing slot behavior.Source: Coding guidelines
src/platform/settings/composables/useSettingUI.ts (1)
402-418: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winPrevent rendering empty setting groups in the navigation.
If
teamWorkspacesEnabledis true but the user is logged out, the "Workspace" category evaluates to an empty list of children. The currentnavGroupscomputation maps all groups as-is, which will result in an empty "Workspace" section header rendering in the sidebar. Filtering out groups without items prevents this visual glitch.🐛 Proposed fix
const navGroups = computed<NavGroupData[]>(() => - groupedMenuTreeNodes.value.map((group) => ({ - title: - (group as SettingTreeNode & { translatedLabel?: string }) - .translatedLabel ?? group.label, - items: (group.children ?? []).map((child) => ({ - id: child.key, - label: - (child as SettingTreeNode & { translatedLabel?: string }) - .translatedLabel ?? child.label, - icon: - CATEGORY_ICONS[child.key] ?? - CATEGORY_ICONS[child.label] ?? - 'icon-[lucide--plug]' - })) - })) + groupedMenuTreeNodes.value + .filter((group) => group.children && group.children.length > 0) + .map((group) => ({ + title: + (group as SettingTreeNode & { translatedLabel?: string }) + .translatedLabel ?? group.label, + items: (group.children ?? []).map((child) => ({ + id: child.key, + label: + (child as SettingTreeNode & { translatedLabel?: string }) + .translatedLabel ?? child.label, + icon: + CATEGORY_ICONS[child.key] ?? + CATEGORY_ICONS[child.label] ?? + 'icon-[lucide--plug]' + })) + })) )🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/platform/settings/composables/useSettingUI.ts` around lines 402 - 418, Update the navGroups computed mapping to exclude groups whose resulting items array is empty, including the logged-out Workspace case when teamWorkspacesEnabled is enabled. Preserve the existing group title, item mapping, and icon fallback behavior for groups that contain items.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/components/ui/hover-card/HoverCard.vue`:
- Around line 4-19: Update the open-state handling in HoverCard.vue so the local
isOpen state is initialized from the forwarded open/defaultOpen props instead of
always false, and synchronize it through HoverCardRoot’s update:open event.
Preserve the existing forwarded prop bindings and ensure uncontrolled
defaultOpen behavior and externally controlled open state remain functional.
In `@src/components/ui/pagination/Pagination.vue`:
- Around line 70-71: Inline the static class string from the ellipsisClass
constant directly onto the template’s PaginationEllipsis component, then remove
ellipsisClass from the script section. Leave the surrounding pagination logic
unchanged.
- Around line 58-68: Replace the `page` prop and `'update:page'` emit in the
component setup with a `defineModel` declaration, while keeping `total` and
`itemsPerPage` as props. Update the `<PaginationRoot>` usage to bind `page`
through `v-model:page`, preserving the existing total, items-per-page, and
pagination options.
In
`@src/platform/workspace/components/dialogs/settings/WorkspaceMembersPanelContent.vue`:
- Around line 30-33: Update the onMounted initialization in
WorkspaceMembersPanelContent to explicitly handle the promises returned by
fetchMembers and fetchPendingInvites. Use void for intentionally unawaited calls
or an async initialization helper that awaits both and handles failures,
ensuring no promise rejections remain floating.
In `@src/platform/workspace/composables/useWorkspaceActivity.ts`:
- Around line 75-76: Update the detail-field branch in the workspace activity
sorting logic to compare the leading numeric count from a.detail and b.detail
rather than using localeCompare on the formatted strings. Preserve the existing
dir multiplier and ordering behavior for other sort fields.
In `@src/storybook/mocks/useCurrentUser.ts`:
- Around line 12-16: Change useCurrentUser from an exported arrow-function
expression to an exported function declaration, preserving its returned refs and
computed isLoggedIn value unchanged.
---
Outside diff comments:
In `@src/components/ui/hover-card/HoverCardTrigger.vue`:
- Around line 1-13: Update HoverCardTrigger to use Reka UI’s useForwardProps for
forwarding the defined HoverCardTriggerProps, and bind the resulting forwarded
props to the inner HoverCardTrigger instead of binding props directly. Preserve
the existing slot behavior.
In `@src/platform/settings/composables/useSettingUI.ts`:
- Around line 402-418: Update the navGroups computed mapping to exclude groups
whose resulting items array is empty, including the logged-out Workspace case
when teamWorkspacesEnabled is enabled. Preserve the existing group title, item
mapping, and icon fallback behavior for groups that contain items.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 56641dc2-9912-4b89-9c12-ee65aa02f1d9
⛔ Files ignored due to path filters (1)
.github/fe-1247-activity-tab.pngis excluded by!**/*.png
📒 Files selected for processing (38)
.storybook/main.tssrc/components/ui/hover-card/HoverCard.vuesrc/components/ui/hover-card/HoverCardContent.vuesrc/components/ui/hover-card/HoverCardTrigger.vuesrc/components/ui/hover-card/hoverCardContext.tssrc/components/ui/pagination/Pagination.vuesrc/components/ui/table/Table.vuesrc/components/ui/table/TableBody.vuesrc/components/ui/table/TableCell.vuesrc/components/ui/table/TableHead.vuesrc/components/ui/table/TableHeader.vuesrc/components/ui/table/TableRow.vuesrc/locales/en/main.jsonsrc/platform/settings/components/SettingDialog.vuesrc/platform/settings/composables/useSettingUI.tssrc/platform/settings/composables/useSettingsNavigation.tssrc/platform/workspace/components/dialogs/settings/PlanCreditsPanelContent.test.tssrc/platform/workspace/components/dialogs/settings/PlanCreditsPanelContent.vuesrc/platform/workspace/components/dialogs/settings/WorkspaceActivityContent.stories.tssrc/platform/workspace/components/dialogs/settings/WorkspaceActivityContent.test.tssrc/platform/workspace/components/dialogs/settings/WorkspaceActivityContent.vuesrc/platform/workspace/components/dialogs/settings/WorkspaceMembersPanelContent.test.tssrc/platform/workspace/components/dialogs/settings/WorkspaceMembersPanelContent.vuesrc/platform/workspace/components/dialogs/settings/WorkspacePanelContent.test.tssrc/platform/workspace/components/dialogs/settings/WorkspacePanelContent.vuesrc/platform/workspace/composables/useAutoPageSize.test.tssrc/platform/workspace/composables/useAutoPageSize.tssrc/platform/workspace/composables/useMembersPanel.tssrc/platform/workspace/composables/useWorkspaceActivity.test.tssrc/platform/workspace/composables/useWorkspaceActivity.tssrc/platform/workspace/composables/useWorkspaceMenuItems.tssrc/platform/workspace/fixtures/activityFixtures.tssrc/platform/workspace/utils/badgeColor.test.tssrc/platform/workspace/utils/badgeColor.tssrc/platform/workspace/utils/relativeTime.test.tssrc/platform/workspace/utils/relativeTime.tssrc/storybook/mocks/useCurrentUser.tssrc/storybook/mocks/useWorkspaceUI.ts
💤 Files with no reviewable changes (2)
- src/platform/workspace/components/dialogs/settings/WorkspacePanelContent.test.ts
- src/platform/workspace/components/dialogs/settings/WorkspacePanelContent.vue
| const { | ||
| page = 1, | ||
| total, | ||
| itemsPerPage = 10 | ||
| } = defineProps<{ | ||
| page?: number | ||
| total: number | ||
| itemsPerPage?: number | ||
| }>() | ||
|
|
||
| const emit = defineEmits<{ 'update:page': [page: number] }>() |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
Use defineModel for the page binding.
As per coding guidelines, use defineModel for v-model bindings instead of defining separate props and emits. Since the consumer uses v-model:page="page", you can declare page as a model and avoid the separate update:page emit.
♻️ Proposed refactor
-const {
- page = 1,
- total,
- itemsPerPage = 10
-} = defineProps<{
- page?: number
- total: number
- itemsPerPage?: number
-}>()
-
-const emit = defineEmits<{ 'update:page': [page: number] }>()
+const page = defineModel<number>('page', { default: 1 })
+
+const { total, itemsPerPage = 10 } = defineProps<{
+ total: number
+ itemsPerPage?: number
+}>()Update the <PaginationRoot> in the template to use v-model:page="page" instead of :page and @update:page:
<PaginationRoot
v-model:page="page"
:total="total"
:items-per-page="itemsPerPage"
:sibling-count="1"
show-edges
>🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/components/ui/pagination/Pagination.vue` around lines 58 - 68, Replace
the `page` prop and `'update:page'` emit in the component setup with a
`defineModel` declaration, while keeping `total` and `itemsPerPage` as props.
Update the `<PaginationRoot>` usage to bind `page` through `v-model:page`,
preserving the existing total, items-per-page, and pagination options.
Source: Coding guidelines
| const ellipsisClass = | ||
| 'inline-flex size-8 items-center justify-center text-sm text-muted-foreground' |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value
Inline the static class string.
As per retrieved learnings, prefer keeping static class names inline for simplicity rather than extracting them. You can inline this string directly on the <PaginationEllipsis> component and remove this constant.
♻️ Proposed refactor
-const ellipsisClass =
- 'inline-flex size-8 items-center justify-center text-sm text-muted-foreground'And in the template (line 31):
- <PaginationEllipsis v-else :index="index" :class="ellipsisClass">
+ <PaginationEllipsis v-else :index="index" class="inline-flex size-8 items-center justify-center text-sm text-muted-foreground">📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const ellipsisClass = | |
| 'inline-flex size-8 items-center justify-center text-sm text-muted-foreground' |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/components/ui/pagination/Pagination.vue` around lines 70 - 71, Inline the
static class string from the ellipsisClass constant directly onto the template’s
PaginationEllipsis component, then remove ellipsisClass from the script section.
Leave the surrounding pagination logic unchanged.
Source: Learnings
The screenshot lives in the PR description instead of the repo tree. Claude-Session: https://claude.ai/code/session_01Gc3gu2DCrzRAqd6M7x4utx
…pend-activity-log-ui-owneradmin
The workspace settings sidebar split the old "Workspace" panel into "Plan & Credits" and "Members". The cloud specs still clicked a "Workspace" nav button that no longer exists, hanging 60s each until the cloud shard hit its 15m limit and cancelled. Point creditsTile at "Plan & Credits" and memberRoleChange at the standalone "Members" panel (whose Members sub-tab is gone). Claude-Session: https://claude.ai/code/session_01Gc3gu2DCrzRAqd6M7x4utx
…pend-activity-log-ui-owneradmin Resolve conflicts: keep the retired WorkspacePanelContent deleted, merge the configurable useWorkspaceUI storybook mock while preserving workspaceRole, and keep both the activity and billingStatus workspacePanel i18n blocks. Claude-Session: https://claude.ai/code/session_01Gc3gu2DCrzRAqd6M7x4utx
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
.storybook/main.ts (1)
91-110: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueRemove duplicate alias.
The alias for
@/platform/workspace/composables/useWorkspaceUIis defined twice in the array (here and at lines 106-110). Please remove one of the duplicates.♻️ Proposed fix
{ find: '`@/platform/workspace/composables/useWorkspaceUI`', replacement: process.cwd() + '/src/storybook/mocks/useWorkspaceUI.ts' }, - { - find: '`@/platform/workspace/stores/teamWorkspaceStore`', - replacement: - process.cwd() + '/src/storybook/mocks/teamWorkspaceStore.ts' - }, { find: '`@/composables/auth/useCurrentUser`', replacement: process.cwd() + '/src/storybook/mocks/useCurrentUser.ts' }, - { - find: '`@/platform/workspace/composables/useWorkspaceUI`', - replacement: - process.cwd() + '/src/storybook/mocks/useWorkspaceUI.ts' - }, + { + find: '`@/platform/workspace/stores/teamWorkspaceStore`', + replacement: + process.cwd() + '/src/storybook/mocks/teamWorkspaceStore.ts' + }, { find: '`@/utils/formatUtil`',🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.storybook/main.ts around lines 91 - 110, Remove the duplicate "`@/platform/workspace/composables/useWorkspaceUI`" entry from the alias configuration array, keeping one replacement mapping intact alongside the teamWorkspaceStore and useCurrentUser aliases.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In @.storybook/main.ts:
- Around line 91-110: Remove the duplicate
"`@/platform/workspace/composables/useWorkspaceUI`" entry from the alias
configuration array, keeping one replacement mapping intact alongside the
teamWorkspaceStore and useCurrentUser aliases.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 5a70eb19-fa2c-4498-8fa1-f34bdbc9c9b1
📒 Files selected for processing (3)
.storybook/main.tssrc/locales/en/main.jsonsrc/storybook/mocks/useWorkspaceUI.ts
Required findings were addressed in d4c957b, and CodeRabbit re-review succeeded on the latest head with no new mandatory findings.
Summary
Adds the workspace Activity ledger while preserving the existing and new billing settings experiences behind
billing_control_enabled.Changes
workspacedeep-link key remains stable.BillingStatusBannerinstance while navigating between the new Plan & Credits and Members entries, and falls back to Workspace if the flag is disabled while Members is active.useWorkspaceActivityas an injectable data seam that defaults to an empty result until FE-1249 wires the workspace usage API.mainand addresses the required CodeRabbit findings for hover-card state forwarding, member-fetch promise handling, numeric detail sorting, and function declarations.Review Focus
billing_control_enabled = false: existing Workspace UX only, with no Activity surface.billing_control_enabled = true: new Plan & Credits / Activity and separate Members UX only.Validation
pnpm typecheck,pnpm typecheck:browser,pnpm lint,pnpm format:check, andpnpm knippassed.Linear: FE-1247 · DES-479 (Activity) · DES-497 (settings shell). Backend follow-up: FE-1249.
Screenshots (if applicable)
Activity tab in the flag-enabled experience, populated with fixture data while the workspace usage API is pending: