feat(billing): deep link to open the pricing table (FE-1104)#13001
feat(billing): deep link to open the pricing table (FE-1104)#13001dante01yoon wants to merge 14 commits into
Conversation
…on (FE-770) Adds a creator-only subscription-lifecycle permission so cancel / reactivate / downgrade can be gated to the workspace's original owner (any owner keeps canManageSubscription for manage-payment / top-up / change-commit), per the billing-permission matrix in the FE SSOT. ASSUMES /api/workspaces exposes a current-user-relative is_creator flag — BE spec is NOT finalized (field shape + original-owner determination are open, FE-770 Q3 / BE-1337). Fails closed: when is_creator is absent the lifecycle permission is false, so no behavior changes until the BE signal lands. Code comments mark every assumption point for revisit. Consumers (FE-978 cancel/ reactivate, FE-977 downgrade) wire to this once it is available.
… (FE-770) CodeRabbit (#12829): is_creator was declared on api/workspaceApi WorkspaceWithRole but the duplicate type in workspaceTypes.ts and the Zod schema in workspaceAuthStore stripped it, so the flag could be dropped on the auth/session parse path. Align both so the original-owner flag survives. Still ASSUMED / BE spec not finalized (FE-770 Q3 / BE-1337); optional, fails closed.
BE confirmed the current-user-relative is_creator boolean and that the creator is tracked explicitly (not by creation date). Field stays optional and gating fails closed until it actually ships on /api/workspaces.
…ps (FE-770) WorkspaceWithRole + WorkspaceWithRoleSchema are hand-rolled only because the cloud ingest OpenAPI doesn't expose is_creator yet. @comfyorg/ingest-types already generates the type + zWorkspaceWithRole; swap to them in this PR once the spec ships the field.
…(FE-770) Drop the confusing "this PR" reference per review; the comment outlives the PR, so describe the swap condition (OpenAPI exposes is_creator) as a plain TODO.
…12830) Stacked on the permission infra #12829. Gates owner **Cancel** (plan menu) + **Resubscribe** (panel button + workspace popover re-activate path) on `canManageSubscriptionLifecycle` (original owner only), per the billing-permission matrix (FE SSOT). Promoted owners keep manage-payment / upgrade / top-up; members get none. **Original-owner signal (repointed).** Rebased onto the updated #12829 (`a51183ae`), so the gate now resolves from the member-list `is_original_owner` field that the cloud cutover (Comfy-Org/cloud #4359) ships: the store getter `isCurrentUserOriginalOwner` matches the current user's member self-row by email, and `useWorkspaceUI` eagerly `fetchMembers()` so billing surfaces have the roster loaded. This replaces the earlier `is_creator`-on-`/api/workspaces` assumption — BE standardized on a per-member `is_original_owner` instead. No `is_creator` remains. **Merge-gated** on cutover cloud#4359 reaching cloud main: until `GET /api/workspace/members` returns `is_original_owner` in prod, the gate fails closed (lifecycle actions hidden for every owner). Fail-closed = no regression pre-deploy, but do not merge until the field is live. **Follow-up (non-blocking).** Exposing `is_original_owner` (current-user-relative, sibling of `role`) on `/api/workspaces` — Hunter pre-approved 2026-06-17 — would let us read it directly and drop the eager member fetch. Tracked on #12829 / FE-770. Part of FE-978 (member run-lock modal shipped separately in #12786).
Address review: own the team members-preload coordination in the store instead of the composable. ensureMembersLoaded no-ops for personal / already-loaded workspaces, dedupes in-flight calls, and logs failures so a later call retries. Inline the redundant isCurrentUserOriginalOwner computed.
The watch now gates ensureMembersLoaded on activeWorkspace.type === 'team' so personal/undefined workspaces never invoke the store loader. This keeps consumer composables that mock the store minimally (useSubscriptionDialog) working, while the store method stays the idempotent safe loader.
…permission' into jaewon/fe-1104-pricing-table-deep-link
|
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: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ 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 |
🎭 Playwright: ❌ 1665 passed, 1 failed · 3 flaky❌ Failed Tests📊 Browser Reports
🎨 Storybook: ✅ Built — View Storybook📦 Bundle Size
⚡ Performance Report
Absolute values
Raw data{
"timestamp": "2026-06-19T13:27:06.739Z",
"gitSha": "3d6f70feb0e61f3bc1571a0f25abe073b53d0e7a",
"branch": "jaewon/fe-1104-pricing-table-deep-link",
"measurements": [
{
"name": "canvas-idle",
"durationMs": 2026.5500000000145,
"styleRecalcs": 9,
"styleRecalcDurationMs": 8.793,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 500.38800000000003,
"heapDeltaBytes": -2428436,
"heapUsedBytes": 56296360,
"domNodes": 18,
"jsHeapTotalBytes": 25165824,
"scriptDurationMs": 25.500999999999998,
"eventListeners": 6,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.670000000000012,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "canvas-idle",
"durationMs": 2013.496000000032,
"styleRecalcs": 9,
"styleRecalcDurationMs": 7.231,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 364.697,
"heapDeltaBytes": -2480360,
"heapUsedBytes": 56249920,
"domNodes": 18,
"jsHeapTotalBytes": 26214400,
"scriptDurationMs": 16.192999999999998,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.670000000000012,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "canvas-mouse-sweep",
"durationMs": 1814.709999999991,
"styleRecalcs": 74,
"styleRecalcDurationMs": 37.30700000000001,
"layouts": 12,
"layoutDurationMs": 3.8140000000000005,
"taskDurationMs": 776.6569999999999,
"heapDeltaBytes": 14599568,
"heapUsedBytes": 64058912,
"domNodes": 57,
"jsHeapTotalBytes": 21495808,
"scriptDurationMs": 126.23100000000001,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.699999999999818
},
{
"name": "canvas-mouse-sweep",
"durationMs": 1765.8860000000232,
"styleRecalcs": 73,
"styleRecalcDurationMs": 33.355,
"layouts": 12,
"layoutDurationMs": 3.664,
"taskDurationMs": 721.562,
"heapDeltaBytes": -7426500,
"heapUsedBytes": 51400892,
"domNodes": 55,
"jsHeapTotalBytes": 26476544,
"scriptDurationMs": 107.141,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "canvas-zoom-sweep",
"durationMs": 1728.796999999986,
"styleRecalcs": 31,
"styleRecalcDurationMs": 16.954,
"layouts": 6,
"layoutDurationMs": 0.588,
"taskDurationMs": 357.20099999999996,
"heapDeltaBytes": 1462468,
"heapUsedBytes": 60159016,
"domNodes": 76,
"jsHeapTotalBytes": 26214400,
"scriptDurationMs": 27.233,
"eventListeners": 19,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "canvas-zoom-sweep",
"durationMs": 1723.7910000000056,
"styleRecalcs": 30,
"styleRecalcDurationMs": 16.496,
"layouts": 6,
"layoutDurationMs": 0.5479999999999998,
"taskDurationMs": 314.163,
"heapDeltaBytes": 1725772,
"heapUsedBytes": 60080492,
"domNodes": 77,
"jsHeapTotalBytes": 25165824,
"scriptDurationMs": 24.443,
"eventListeners": 19,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333335,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "dom-widget-clipping",
"durationMs": 608.6940000000141,
"styleRecalcs": 12,
"styleRecalcDurationMs": 8.723999999999998,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 390.655,
"heapDeltaBytes": 7271008,
"heapUsedBytes": 65597012,
"domNodes": 20,
"jsHeapTotalBytes": 18874368,
"scriptDurationMs": 67.584,
"eventListeners": 0,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.669999999999998,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "dom-widget-clipping",
"durationMs": 563.7199999999893,
"styleRecalcs": 11,
"styleRecalcDurationMs": 6.817999999999998,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 328.701,
"heapDeltaBytes": 7209696,
"heapUsedBytes": 65892260,
"domNodes": 18,
"jsHeapTotalBytes": 18874368,
"scriptDurationMs": 55.529999999999994,
"eventListeners": 0,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.669999999999998,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "large-graph-idle",
"durationMs": 2027.5420000000395,
"styleRecalcs": 11,
"styleRecalcDurationMs": 10.776000000000002,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 634.344,
"heapDeltaBytes": -8941540,
"heapUsedBytes": 61744164,
"domNodes": 22,
"jsHeapTotalBytes": 11153408,
"scriptDurationMs": 112.76200000000001,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66999999999998,
"p95FrameDurationMs": 16.699999999999818
},
{
"name": "large-graph-idle",
"durationMs": 2022.0199999999977,
"styleRecalcs": 11,
"styleRecalcDurationMs": 9.653000000000002,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 508.19199999999995,
"heapDeltaBytes": -9168696,
"heapUsedBytes": 61172744,
"domNodes": 22,
"jsHeapTotalBytes": 11415552,
"scriptDurationMs": 89.922,
"eventListeners": 6,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "large-graph-pan",
"durationMs": 2069.0210000000206,
"styleRecalcs": 69,
"styleRecalcDurationMs": 20.313000000000006,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 1067.7759999999998,
"heapDeltaBytes": 9370984,
"heapUsedBytes": 82379180,
"domNodes": 18,
"jsHeapTotalBytes": 12640256,
"scriptDurationMs": 362.909,
"eventListeners": 6,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "large-graph-pan",
"durationMs": 2112.4700000000303,
"styleRecalcs": 69,
"styleRecalcDurationMs": 19.638,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 1036.6979999999999,
"heapDeltaBytes": 10062120,
"heapUsedBytes": 83043136,
"domNodes": 18,
"jsHeapTotalBytes": 9756672,
"scriptDurationMs": 369.627,
"eventListeners": 6,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "large-graph-zoom",
"durationMs": 3188.5540000000105,
"styleRecalcs": 67,
"styleRecalcDurationMs": 25.801,
"layouts": 60,
"layoutDurationMs": 8.884,
"taskDurationMs": 1398.87,
"heapDeltaBytes": 13901120,
"heapUsedBytes": 68770224,
"domNodes": 16,
"jsHeapTotalBytes": 6553600,
"scriptDurationMs": 512.173,
"eventListeners": 8,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "large-graph-zoom",
"durationMs": 3088.2700000000227,
"styleRecalcs": 65,
"styleRecalcDurationMs": 20.540000000000003,
"layouts": 60,
"layoutDurationMs": 8.491999999999999,
"taskDurationMs": 1267.944,
"heapDeltaBytes": 13070604,
"heapUsedBytes": 67843108,
"domNodes": 14,
"jsHeapTotalBytes": 7602176,
"scriptDurationMs": 470.302,
"eventListeners": 8,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.670000000000012,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "minimap-idle",
"durationMs": 2020.3479999999558,
"styleRecalcs": 10,
"styleRecalcDurationMs": 9.31,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 619.251,
"heapDeltaBytes": -8429816,
"heapUsedBytes": 63603428,
"domNodes": 20,
"jsHeapTotalBytes": 9056256,
"scriptDurationMs": 106.66900000000001,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.699999999999818
},
{
"name": "minimap-idle",
"durationMs": 2001.8460000000005,
"styleRecalcs": 10,
"styleRecalcDurationMs": 8.272000000000002,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 526.2339999999999,
"heapDeltaBytes": -10345076,
"heapUsedBytes": 63029228,
"domNodes": 20,
"jsHeapTotalBytes": 8269824,
"scriptDurationMs": 89.434,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "subgraph-dom-widget-clipping",
"durationMs": 584.6970000000056,
"styleRecalcs": 48,
"styleRecalcDurationMs": 11.912,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 379.089,
"heapDeltaBytes": 7640408,
"heapUsedBytes": 66349700,
"domNodes": 22,
"jsHeapTotalBytes": 19136512,
"scriptDurationMs": 125.23400000000001,
"eventListeners": 6,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66666666666665,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "subgraph-dom-widget-clipping",
"durationMs": 601.2439999999515,
"styleRecalcs": 48,
"styleRecalcDurationMs": 12.530000000000001,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 384.32300000000004,
"heapDeltaBytes": 7748524,
"heapUsedBytes": 66379996,
"domNodes": 22,
"jsHeapTotalBytes": 19398656,
"scriptDurationMs": 128.562,
"eventListeners": 6,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333335,
"p95FrameDurationMs": 16.699999999999818
},
{
"name": "subgraph-idle",
"durationMs": 2005.3419999999846,
"styleRecalcs": 10,
"styleRecalcDurationMs": 9.262,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 447.90799999999996,
"heapDeltaBytes": -2480712,
"heapUsedBytes": 56245788,
"domNodes": 20,
"jsHeapTotalBytes": 25952256,
"scriptDurationMs": 27.309,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "subgraph-idle",
"durationMs": 2000.360999999998,
"styleRecalcs": 9,
"styleRecalcDurationMs": 7.242999999999999,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 357.62300000000005,
"heapDeltaBytes": -2941676,
"heapUsedBytes": 55947112,
"domNodes": 18,
"jsHeapTotalBytes": 25690112,
"scriptDurationMs": 14.563,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "subgraph-mouse-sweep",
"durationMs": 1727.3169999999993,
"styleRecalcs": 76,
"styleRecalcDurationMs": 39.131,
"layouts": 16,
"layoutDurationMs": 4.458,
"taskDurationMs": 692.358,
"heapDeltaBytes": 11231656,
"heapUsedBytes": 64797980,
"domNodes": 64,
"jsHeapTotalBytes": 25690112,
"scriptDurationMs": 100.737,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.670000000000012,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "subgraph-mouse-sweep",
"durationMs": 1726.4710000000036,
"styleRecalcs": 78,
"styleRecalcDurationMs": 40.913000000000004,
"layouts": 16,
"layoutDurationMs": 5.103,
"taskDurationMs": 691.095,
"heapDeltaBytes": -10904900,
"heapUsedBytes": 47946724,
"domNodes": 65,
"jsHeapTotalBytes": 26476544,
"scriptDurationMs": 95.238,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333335,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "subgraph-transition-enter",
"durationMs": 915.4189999999858,
"styleRecalcs": 17,
"styleRecalcDurationMs": 27.322,
"layouts": 5,
"layoutDurationMs": 12.203,
"taskDurationMs": 705.004,
"heapDeltaBytes": 4431504,
"heapUsedBytes": 79769708,
"domNodes": 13833,
"jsHeapTotalBytes": 17563648,
"scriptDurationMs": 24.812999999999988,
"eventListeners": 2527,
"totalBlockingTimeMs": 140,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "viewport-pan-sweep",
"durationMs": 8201.181000000019,
"styleRecalcs": 252,
"styleRecalcDurationMs": 60.757,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 3922.1959999999995,
"heapDeltaBytes": 2212080,
"heapUsedBytes": 73039064,
"domNodes": 22,
"jsHeapTotalBytes": 23388160,
"scriptDurationMs": 1336.6719999999998,
"eventListeners": 20,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "viewport-pan-sweep",
"durationMs": 8258.652999999924,
"styleRecalcs": 251,
"styleRecalcDurationMs": 62.174,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 3884.703,
"heapDeltaBytes": -2214868,
"heapUsedBytes": 67815188,
"domNodes": 20,
"jsHeapTotalBytes": 18669568,
"scriptDurationMs": 1246.2169999999999,
"eventListeners": 20,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "vue-large-graph-idle",
"durationMs": 13818.349000000013,
"styleRecalcs": 0,
"styleRecalcDurationMs": 0,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 13801.595000000003,
"heapDeltaBytes": -35020992,
"heapUsedBytes": 163716704,
"domNodes": -3304,
"jsHeapTotalBytes": 12845056,
"scriptDurationMs": 679.729,
"eventListeners": -16468,
"totalBlockingTimeMs": 0,
"frameDurationMs": 17.220000000000073,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "vue-large-graph-idle",
"durationMs": 13877.79899999998,
"styleRecalcs": 0,
"styleRecalcDurationMs": 0,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 13863.585999999998,
"heapDeltaBytes": -24026432,
"heapUsedBytes": 174291364,
"domNodes": -3308,
"jsHeapTotalBytes": 20942848,
"scriptDurationMs": 613.413,
"eventListeners": -16470,
"totalBlockingTimeMs": 0,
"frameDurationMs": 17.223333333333358,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "vue-large-graph-pan",
"durationMs": 16271.499000000005,
"styleRecalcs": 95,
"styleRecalcDurationMs": 22.82500000000004,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 16251.025000000001,
"heapDeltaBytes": -36005524,
"heapUsedBytes": 160252344,
"domNodes": -3308,
"jsHeapTotalBytes": 20680704,
"scriptDurationMs": 940.6239999999999,
"eventListeners": -16468,
"totalBlockingTimeMs": 0,
"frameDurationMs": 17.219999999999953,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "vue-large-graph-pan",
"durationMs": 16104.966999999988,
"styleRecalcs": 85,
"styleRecalcDurationMs": 21.488000000000007,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 16081.881000000003,
"heapDeltaBytes": -24452884,
"heapUsedBytes": 184306980,
"domNodes": -3307,
"jsHeapTotalBytes": 23478272,
"scriptDurationMs": 904.9469999999999,
"eventListeners": -16470,
"totalBlockingTimeMs": 12,
"frameDurationMs": 17.219999999999953,
"p95FrameDurationMs": 16.80000000000291
},
{
"name": "workflow-execution",
"durationMs": 467.62699999999313,
"styleRecalcs": 20,
"styleRecalcDurationMs": 25.902,
"layouts": 5,
"layoutDurationMs": 1.5779999999999998,
"taskDurationMs": 203.139,
"heapDeltaBytes": -21967224,
"heapUsedBytes": 47641748,
"domNodes": -204,
"jsHeapTotalBytes": 5419008,
"scriptDurationMs": 18.391000000000002,
"eventListeners": -134,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66666666666665,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "workflow-execution",
"durationMs": 452.63199999999415,
"styleRecalcs": 19,
"styleRecalcDurationMs": 24.241999999999997,
"layouts": 5,
"layoutDurationMs": 1.4089999999999998,
"taskDurationMs": 119.8,
"heapDeltaBytes": 5236724,
"heapUsedBytes": 64895664,
"domNodes": 159,
"jsHeapTotalBytes": 2883584,
"scriptDurationMs": 21.845,
"eventListeners": 69,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.799999999999727
}
]
} |
Codecov Report❌ Patch coverage is @@ Coverage Diff @@
## jaewon/fe-934-unified-pricing-table #13001 +/- ##
=======================================================================
+ Coverage 76.37% 76.42% +0.05%
=======================================================================
Files 1579 1578 -1
Lines 104186 104122 -64
Branches 32515 32514 -1
=======================================================================
+ Hits 79568 79579 +11
+ Misses 23783 23708 -75
Partials 835 835
Flags with carried forward coverage won't be shown. Click here to find out more.
... and 8 files with indirect coverage changes 🚀 New features to boost your workflow:
|
f18c486 to
2040f6a
Compare
Add a /?pricing=1 (and /?pricing=team / =personal) deep link that opens the pricing table on app load, gated to the original owner via canManageSubscriptionLifecycle. Members and promoted owners are a silent no-op (param stripped, app loads normally). Mirrors the existing preserved- query URL-loader pattern; survives the login redirect. Eligible opens emit the subscription modal_opened telemetry with a new deep_link reason.
2040f6a to
0708c01
Compare
Summary
Adds an in-app deep link that opens the pricing table directly, for driving pilot users straight to subscribe (request from nav/Alex). Resolves FE-1104.
/?pricing=1— on app load, open the pricing table./?pricing=team//?pricing=personal— open it on the Team / Personal plan tab (via the existingUnifiedPricingTableinitialPlanMode).useWorkspaceUI().permissions.canManageSubscriptionLifecycle(personal user, or a team workspace's original owner). A member or a promoted owner is a silent no-op: the app loads normally, the param is stripped, no 404 / error / toast.?invite/?create_workspace.How
Mirrors the established URL-loader pattern (
useInviteUrlLoader/useCreateWorkspaceUrlLoader):preservedQueryNamespaces.ts/router.ts— register thepricingnamespace + tracker key.usePricingTableUrlLoader.ts— hydrate preserved query, readpricing, strip the param +clearPreservedQueryunconditionally, thenawait ensureMembersLoaded()(resolves the original-owner gate; dedupes, no-ops for personal) and open the table only when the gate allows.GraphCanvas.vue— call the loader inonMountedafter the create-workspace loader (cloud only; not gated on the team-workspaces flag so it also drives personal/legacy users).useSubscriptionDialog.ts— new'deep_link'value onSubscriptionDialogReason.Telemetry
Eligible opens emit the existing
subscription_required_modal_openedPostHog event with the newreason: 'deep_link'. Ineligible-click bounce rate is derivable from the autocaptured pageview URL (?pricing=…), so no new event plumbing.Stacking / dependencies
This feature needs two sibling stacks off
main:#12666, base of this PR) — theUnifiedPricingTable+showPricingTable({ planMode }).#12829) — thecanManageSubscriptionLifecyclegate. Merged into this branch, so the diff against the FE-934 base includes FE-770's changes until it lands. Review the singlefeat(billing): deep link…commit. Once both land onmain, rebase ontomainand the diff collapses to just this feature.Do not merge before FE-770 and FE-934. Post-Billing-V1 follow-up. End-state: swap the FE original-owner heuristic for the BE workspace-level
is_original_ownerflag when it lands (removes the members-fetch).Tests
usePricingTableUrlLoader.test.ts, 10 cases): opens for an original owner;team/personaltab preselect; silent no-op + param-strip for a member/promoted owner; proves the gate is read only afterensureMembersLoadedresolves; preserved-query restore; empty/non-string/absent param.browser_tests/tests/dialogs/pricingTableDeepLink.spec.ts,@cloud, 4 cases, verified locally): personal owner opens + URL stripped;?pricing=teamlands on the active Team tab; team original owner opens (realis_original_owner+ email gate); team member is a silent no-op + URL stripped.useSubscriptionDialog,useWorkspaceUI,teamWorkspaceStore) green.