Skip to content

feat(billing): deep link to open the pricing table (FE-1104)#13001

Open
dante01yoon wants to merge 14 commits into
jaewon/fe-934-unified-pricing-tablefrom
jaewon/fe-1104-pricing-table-deep-link
Open

feat(billing): deep link to open the pricing table (FE-1104)#13001
dante01yoon wants to merge 14 commits into
jaewon/fe-934-unified-pricing-tablefrom
jaewon/fe-1104-pricing-table-deep-link

Conversation

@dante01yoon

@dante01yoon dante01yoon commented Jun 19, 2026

Copy link
Copy Markdown
Collaborator

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 existing UnifiedPricingTable initialPlanMode).
  • Gated to the original owner via 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.
  • Off-cloud (OSS): the loader isn't instantiated, so the param is ignored.
  • Survives the login redirect via the preserved-query system, same as ?invite / ?create_workspace.

How

Mirrors the established URL-loader pattern (useInviteUrlLoader / useCreateWorkspaceUrlLoader):

  • preservedQueryNamespaces.ts / router.ts — register the pricing namespace + tracker key.
  • New usePricingTableUrlLoader.ts — hydrate preserved query, read pricing, strip the param + clearPreservedQuery unconditionally, then await 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 in onMounted after 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 on SubscriptionDialogReason.

Telemetry

Eligible opens emit the existing subscription_required_modal_opened PostHog event with the new reason: '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:

  • FE-934 (#12666, base of this PR) — the UnifiedPricingTable + showPricingTable({ planMode }).
  • FE-770 (#12829) — the canManageSubscriptionLifecycle gate. Merged into this branch, so the diff against the FE-934 base includes FE-770's changes until it lands. Review the single feat(billing): deep link… commit. Once both land on main, rebase onto main and 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_owner flag when it lands (removes the members-fetch).

Tests

  • Unit (usePricingTableUrlLoader.test.ts, 10 cases): opens for an original owner; team/personal tab preselect; silent no-op + param-strip for a member/promoted owner; proves the gate is read only after ensureMembersLoaded resolves; preserved-query restore; empty/non-string/absent param.
  • E2E (browser_tests/tests/dialogs/pricingTableDeepLink.spec.ts, @cloud, 4 cases, verified locally): personal owner opens + URL stripped; ?pricing=team lands on the active Team tab; team original owner opens (real is_original_owner + email gate); team member is a silent no-op + URL stripped.
  • Typecheck + related unit suites (useSubscriptionDialog, useWorkspaceUI, teamWorkspaceStore) green.

…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
@coderabbitai

coderabbitai Bot commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 6631d172-9ca2-44f4-8357-413ba24949f7

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch jaewon/fe-1104-pricing-table-deep-link

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions

github-actions Bot commented Jun 19, 2026

Copy link
Copy Markdown

🎭 Playwright: ❌ 1665 passed, 1 failed · 3 flaky

❌ Failed Tests

📊 Browser Reports
  • chromium: View Report (✅ 1644 / ❌ 1 / ⚠️ 3 / ⏭️ 5)
  • chromium-2x: View Report (✅ 2 / ❌ 0 / ⚠️ 0 / ⏭️ 0)
  • chromium-0.5x: View Report (✅ 1 / ❌ 0 / ⚠️ 0 / ⏭️ 0)
  • mobile-chrome: View Report (✅ 18 / ❌ 0 / ⚠️ 0 / ⏭️ 0)

🎨 Storybook: ✅ Built — View Storybook

Details

⏰ Completed at: 06/19/2026, 01:17:38 PM UTC

Links

📦 Bundle Size

⏳ Size data collection in progress…

⚡ Performance Report

canvas-idle: · 60.0 avg FPS · 59.5 P5 FPS ✅ (target: ≥52) · 0ms TBT · 53.7 MB heap
canvas-mouse-sweep: · 60.0 avg FPS · 59.7 P5 FPS ✅ (target: ≥52) · 0ms TBT · 55.1 MB heap
canvas-zoom-sweep: · 60.0 avg FPS · 59.7 P5 FPS ✅ (target: ≥52) · 0ms TBT · 57.3 MB heap
dom-widget-clipping: · 60.0 avg FPS · 59.9 P5 FPS ✅ (target: ≥52) · 0ms TBT · 62.7 MB heap
large-graph-idle: · 60.0 avg FPS · 59.9 P5 FPS ✅ (target: ≥52) · 0ms TBT · 58.6 MB heap
large-graph-pan: · 60.0 avg FPS · 59.7 P5 FPS ✅ (target: ≥52) · 0ms TBT · 78.9 MB heap
large-graph-zoom: · 60.0 avg FPS · 59.5 P5 FPS ✅ (target: ≥52) · 0ms TBT · 65.1 MB heap
minimap-idle: · 60.0 avg FPS · 59.9 P5 FPS ✅ (target: ≥52) · 0ms TBT · 60.4 MB heap
subgraph-dom-widget-clipping: · 60.0 avg FPS · 59.9 P5 FPS ✅ (target: ≥52) · 0ms TBT · 63.3 MB heap
subgraph-idle: · 60.0 avg FPS · 59.5 P5 FPS ✅ (target: ≥52) · 0ms TBT · 53.5 MB heap
subgraph-mouse-sweep: · 60.0 avg FPS · 59.7 P5 FPS ✅ (target: ≥52) · 0ms TBT · 53.8 MB heap
subgraph-transition-enter: · 60.0 avg FPS · 59.5 P5 FPS ✅ (target: ≥52) · 140ms TBT · 76.1 MB heap
viewport-pan-sweep: · 60.0 avg FPS · 59.7 P5 FPS ✅ (target: ≥52) · 0ms TBT · 67.2 MB heap
vue-large-graph-idle: · 58.1 avg FPS · 59.5 P5 FPS ✅ (target: ≥52) · 0ms TBT · 161.2 MB heap
vue-large-graph-pan: · 58.1 avg FPS · 59.5 P5 FPS ✅ (target: ≥52) · 6ms TBT · 164.3 MB heap
workflow-execution: · 60.0 avg FPS · 59.7 P5 FPS ✅ (target: ≥52) · 0ms TBT · 53.7 MB heap

ℹ️ No baseline found — significance unavailable.

Absolute values
Metric Value
canvas-idle: avg frame time 17ms
canvas-idle: p95 frame time 17ms
canvas-idle: layout duration 0ms
canvas-idle: style recalc duration 8ms
canvas-idle: layout count 0
canvas-idle: style recalc count 9
canvas-idle: task duration 433ms
canvas-idle: script duration 21ms
canvas-idle: TBT 0ms
canvas-idle: heap used 53.7 MB
canvas-idle: DOM nodes 18
canvas-idle: event listeners 5
canvas-mouse-sweep: avg frame time 17ms
canvas-mouse-sweep: p95 frame time 17ms
canvas-mouse-sweep: layout duration 4ms
canvas-mouse-sweep: style recalc duration 35ms
canvas-mouse-sweep: layout count 12
canvas-mouse-sweep: style recalc count 74
canvas-mouse-sweep: task duration 749ms
canvas-mouse-sweep: script duration 117ms
canvas-mouse-sweep: TBT 0ms
canvas-mouse-sweep: heap used 55.1 MB
canvas-mouse-sweep: DOM nodes 56
canvas-mouse-sweep: event listeners 4
canvas-zoom-sweep: avg frame time 17ms
canvas-zoom-sweep: p95 frame time 17ms
canvas-zoom-sweep: layout duration 1ms
canvas-zoom-sweep: style recalc duration 17ms
canvas-zoom-sweep: layout count 6
canvas-zoom-sweep: style recalc count 31
canvas-zoom-sweep: task duration 336ms
canvas-zoom-sweep: script duration 26ms
canvas-zoom-sweep: TBT 0ms
canvas-zoom-sweep: heap used 57.3 MB
canvas-zoom-sweep: DOM nodes 77
canvas-zoom-sweep: event listeners 19
dom-widget-clipping: avg frame time 17ms
dom-widget-clipping: p95 frame time 17ms
dom-widget-clipping: layout duration 0ms
dom-widget-clipping: style recalc duration 8ms
dom-widget-clipping: layout count 0
dom-widget-clipping: style recalc count 12
dom-widget-clipping: task duration 360ms
dom-widget-clipping: script duration 62ms
dom-widget-clipping: TBT 0ms
dom-widget-clipping: heap used 62.7 MB
dom-widget-clipping: DOM nodes 19
dom-widget-clipping: event listeners 0
large-graph-idle: avg frame time 17ms
large-graph-idle: p95 frame time 17ms
large-graph-idle: layout duration 0ms
large-graph-idle: style recalc duration 10ms
large-graph-idle: layout count 0
large-graph-idle: style recalc count 11
large-graph-idle: task duration 571ms
large-graph-idle: script duration 101ms
large-graph-idle: TBT 0ms
large-graph-idle: heap used 58.6 MB
large-graph-idle: DOM nodes 22
large-graph-idle: event listeners 5
large-graph-pan: avg frame time 17ms
large-graph-pan: p95 frame time 17ms
large-graph-pan: layout duration 0ms
large-graph-pan: style recalc duration 20ms
large-graph-pan: layout count 0
large-graph-pan: style recalc count 69
large-graph-pan: task duration 1052ms
large-graph-pan: script duration 366ms
large-graph-pan: TBT 0ms
large-graph-pan: heap used 78.9 MB
large-graph-pan: DOM nodes 18
large-graph-pan: event listeners 6
large-graph-zoom: avg frame time 17ms
large-graph-zoom: p95 frame time 17ms
large-graph-zoom: layout duration 9ms
large-graph-zoom: style recalc duration 23ms
large-graph-zoom: layout count 60
large-graph-zoom: style recalc count 66
large-graph-zoom: task duration 1333ms
large-graph-zoom: script duration 491ms
large-graph-zoom: TBT 0ms
large-graph-zoom: heap used 65.1 MB
large-graph-zoom: DOM nodes 15
large-graph-zoom: event listeners 8
minimap-idle: avg frame time 17ms
minimap-idle: p95 frame time 17ms
minimap-idle: layout duration 0ms
minimap-idle: style recalc duration 9ms
minimap-idle: layout count 0
minimap-idle: style recalc count 10
minimap-idle: task duration 573ms
minimap-idle: script duration 98ms
minimap-idle: TBT 0ms
minimap-idle: heap used 60.4 MB
minimap-idle: DOM nodes 20
minimap-idle: event listeners 4
subgraph-dom-widget-clipping: avg frame time 17ms
subgraph-dom-widget-clipping: p95 frame time 17ms
subgraph-dom-widget-clipping: layout duration 0ms
subgraph-dom-widget-clipping: style recalc duration 12ms
subgraph-dom-widget-clipping: layout count 0
subgraph-dom-widget-clipping: style recalc count 48
subgraph-dom-widget-clipping: task duration 382ms
subgraph-dom-widget-clipping: script duration 127ms
subgraph-dom-widget-clipping: TBT 0ms
subgraph-dom-widget-clipping: heap used 63.3 MB
subgraph-dom-widget-clipping: DOM nodes 22
subgraph-dom-widget-clipping: event listeners 6
subgraph-idle: avg frame time 17ms
subgraph-idle: p95 frame time 17ms
subgraph-idle: layout duration 0ms
subgraph-idle: style recalc duration 8ms
subgraph-idle: layout count 0
subgraph-idle: style recalc count 10
subgraph-idle: task duration 403ms
subgraph-idle: script duration 21ms
subgraph-idle: TBT 0ms
subgraph-idle: heap used 53.5 MB
subgraph-idle: DOM nodes 19
subgraph-idle: event listeners 4
subgraph-mouse-sweep: avg frame time 17ms
subgraph-mouse-sweep: p95 frame time 17ms
subgraph-mouse-sweep: layout duration 5ms
subgraph-mouse-sweep: style recalc duration 40ms
subgraph-mouse-sweep: layout count 16
subgraph-mouse-sweep: style recalc count 77
subgraph-mouse-sweep: task duration 692ms
subgraph-mouse-sweep: script duration 98ms
subgraph-mouse-sweep: TBT 0ms
subgraph-mouse-sweep: heap used 53.8 MB
subgraph-mouse-sweep: DOM nodes 65
subgraph-mouse-sweep: event listeners 4
subgraph-transition-enter: avg frame time 17ms
subgraph-transition-enter: p95 frame time 17ms
subgraph-transition-enter: layout duration 12ms
subgraph-transition-enter: style recalc duration 27ms
subgraph-transition-enter: layout count 5
subgraph-transition-enter: style recalc count 17
subgraph-transition-enter: task duration 705ms
subgraph-transition-enter: script duration 25ms
subgraph-transition-enter: TBT 140ms
subgraph-transition-enter: heap used 76.1 MB
subgraph-transition-enter: DOM nodes 13833
subgraph-transition-enter: event listeners 2527
viewport-pan-sweep: avg frame time 17ms
viewport-pan-sweep: p95 frame time 17ms
viewport-pan-sweep: layout duration 0ms
viewport-pan-sweep: style recalc duration 61ms
viewport-pan-sweep: layout count 0
viewport-pan-sweep: style recalc count 252
viewport-pan-sweep: task duration 3903ms
viewport-pan-sweep: script duration 1291ms
viewport-pan-sweep: TBT 0ms
viewport-pan-sweep: heap used 67.2 MB
viewport-pan-sweep: DOM nodes 21
viewport-pan-sweep: event listeners 20
vue-large-graph-idle: avg frame time 17ms
vue-large-graph-idle: p95 frame time 17ms
vue-large-graph-idle: layout duration 0ms
vue-large-graph-idle: style recalc duration 0ms
vue-large-graph-idle: layout count 0
vue-large-graph-idle: style recalc count 0
vue-large-graph-idle: task duration 13833ms
vue-large-graph-idle: script duration 647ms
vue-large-graph-idle: TBT 0ms
vue-large-graph-idle: heap used 161.2 MB
vue-large-graph-idle: DOM nodes -3306
vue-large-graph-idle: event listeners -16469
vue-large-graph-pan: avg frame time 17ms
vue-large-graph-pan: p95 frame time 17ms
vue-large-graph-pan: layout duration 0ms
vue-large-graph-pan: style recalc duration 22ms
vue-large-graph-pan: layout count 0
vue-large-graph-pan: style recalc count 90
vue-large-graph-pan: task duration 16166ms
vue-large-graph-pan: script duration 923ms
vue-large-graph-pan: TBT 6ms
vue-large-graph-pan: heap used 164.3 MB
vue-large-graph-pan: DOM nodes -3308
vue-large-graph-pan: event listeners -16469
workflow-execution: avg frame time 17ms
workflow-execution: p95 frame time 17ms
workflow-execution: layout duration 1ms
workflow-execution: style recalc duration 25ms
workflow-execution: layout count 5
workflow-execution: style recalc count 20
workflow-execution: task duration 161ms
workflow-execution: script duration 20ms
workflow-execution: TBT 0ms
workflow-execution: heap used 53.7 MB
workflow-execution: DOM nodes -23
workflow-execution: event listeners -33
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
    }
  ]
}

@dante01yoon dante01yoon marked this pull request as ready for review June 19, 2026 10:57
@dosubot dosubot Bot added the size:L This PR changes 100-499 lines, ignoring generated files. label Jun 19, 2026
@codecov

codecov Bot commented Jun 19, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 77.94118% with 15 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...e/components/SubscriptionPanelContentWorkspace.vue 0.00% 8 Missing and 1 partial ⚠️
src/components/graph/GraphCanvas.vue 0.00% 5 Missing ⚠️
...bscription/composables/usePricingTableUrlLoader.ts 96.15% 1 Missing ⚠️
@@                           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              
Flag Coverage Δ
unit 62.98% <77.94%> (+0.06%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
.../subscription/composables/useSubscriptionDialog.ts 52.94% <ø> (ø)
...rc/platform/navigation/preservedQueryNamespaces.ts 100.00% <ø> (ø)
src/platform/workspace/api/workspaceApi.ts 54.11% <ø> (ø)
...rkspace/components/CurrentUserPopoverWorkspace.vue 68.18% <100.00%> (ø)
.../platform/workspace/composables/useMembersPanel.ts 90.00% <ø> (ø)
...c/platform/workspace/composables/useWorkspaceUI.ts 93.54% <100.00%> (+1.24%) ⬆️
...rc/platform/workspace/stores/teamWorkspaceStore.ts 90.23% <100.00%> (+1.46%) ⬆️
src/router.ts 42.52% <ø> (ø)
...bscription/composables/usePricingTableUrlLoader.ts 96.15% <96.15%> (ø)
src/components/graph/GraphCanvas.vue 75.43% <0.00%> (-0.52%) ⬇️
... and 1 more

... and 8 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@dante01yoon dante01yoon force-pushed the jaewon/fe-1104-pricing-table-deep-link branch from f18c486 to 2040f6a Compare June 19, 2026 11:06
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.
@dante01yoon dante01yoon force-pushed the jaewon/fe-1104-pricing-table-deep-link branch from 2040f6a to 0708c01 Compare June 19, 2026 11:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L This PR changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants