What a Team pick hands off to: create the workspace, say who is in it, one welcome frame, then the Overview where your agent sets the workspace up with you, sample relays fill the empty state, and the Get-started card leads with the first Code. It is one document in the responsive shell, shown here at desktop and at 390px. Every control carries a tag; the Measure drawer on the desktop frame shows what fires, whether consent let it through, and which server stamp is the record. Nothing on this page invents a tag manager: it follows the one telemetry path the app already has.
Drive the left frame: type a name and watch the handle, the rail chip and the aside fill; claim the domain; reach the Overview; answer the agent; bring context; remove the samples. Each act lands in the drawer with its disposition. Flip the consent switch above to see unknown queue and denied drop. Open the document directly at /concepts/shell-responsive-v1/workspace-first-run.html?measure=1; the plan-pick intake carries the same kit at /concepts/shell-responsive-v1/plan-pick.html?measure=1.
A tag earns its place by answering a question on the funnel read. Client events are best-effort and consent-gated; the server stamps are the record, so any metric that can only be read client-side is a metric the product does not have. Names are stable ids of the form onboarding.<stage>.<action>; props are enums and integers, never free text and never an address, a name or a handle (the kit refuses those keys).
| Event | Fires on | Props | Server record | GA4 (server-side) | Answers |
|---|---|---|---|---|---|
| onboarding.intake.view | each intake screen render | step · index · of · arrival · plane | — client only | funnel_intake_view | drop-off per question; time on step |
| onboarding.intake.answer | an option or chip | step · option | the intake row (persisted answer) drawn | funnel_intake_answer | answer distributions; which goal, which size band, which clients |
| onboarding.intake.back | Back | step | — | — | where people reconsider |
| onboarding.intake.continue · .done | Continue on a multi-select; "Go to Overview" | step · count · plan | — | — | how many chips before people move on; who reaches the end |
| onboarding.plan.skip | Skip on any screen | step | onboarding_plan_picked_at (picked=skip) drawn | funnel_plan_picked | Skip rate, and at which step |
| onboarding.plan.pick | the plan screen's primary or "Free instead" | plan · recommended · accepted · arrival · seats | onboarding_plan_picked_at drawn | funnel_plan_picked | pick rate; recommendation acceptance; paid pick; gated-Team intent |
| onboarding.plan.alternative | an alternative card | from · plan | — | — | which alternatives get considered |
| onboarding.plan.join · .seats | an invitee's Join; the seat stepper | plan · dir | — | — | invitee completion; how often the seat prefill is changed, and which way |
| onboarding.receive.first | the first Code received | arrival | onboarding_step2_at built | funnel_first_claim built | time to first receive — the guard |
| onboarding.workspace.view | each first-run screen render | step · plane | — | funnel_workspace_view | drop-off between create, members, overview |
| onboarding.workspace.field | name, handle, logo, region change | field · valid · reason · option | — | — | handle collisions; region split; logo adoption |
| onboarding.workspace.create | Continue on the create step | plane · region · has_logo · handle_len | workspace_created_at drawn | funnel_workspace_created | workspace-created rate after a Team pick |
| onboarding.members.autojoin | claim / release the domain | action | domain_claimed_at drawn | funnel_domain_claimed | autojoin adoption |
| onboarding.members.invite | an invite added | count | onboarding_step4_at built | funnel_first_invite built | invite rate by method; addresses never leave the page |
| onboarding.members.skip · .continue | the members step's actions | autojoin · invites | — | — | how many create with nobody in it |
| onboarding.workspace.view (step=welcome) · onboarding.welcome.open | the welcome frame; "open now" | step | — | — | whether anyone waits the 1.6 s |
| onboarding.setup.correct | edit / save the pre-filled description | action | — | — | how often the public-data pre-fill is wrong |
| onboarding.setup.answer | the agent's question answered | q · option (free_text for typed) | the intake row drawn | funnel_setup_answer | where teams come from; "starting fresh" share |
| onboarding.data.method | paste · file · describe | method | first send, when it seals built | funnel_first_send built | which import path works |
| onboarding.sample.purge | Remove samples | — | — | — | whether samples help or annoy |
| onboarding.checklist.item | a Get-started item | item · done | the item's own signal built for receive · invite | — | checklist completion by item |
| onboarding.plan.action | Keep · Re-size in the rail | action | — | funnel_plan_action | trial-state actions; re-size demand |
The app has one telemetry path today: a refused surface posts a small PII-free record to a first-party endpoint, the server logs a funnel stage and forwards it to GA4 through the Measurement Protocol, and a consent script decides whether GTM loads at all. This plan is that path with more event names, not a second system.
data-track-* attributes drawnrelay_consent: unknown queues, denied drops, granted sends and flushes drawngate-denied drawn[funnel] event.<name> log line + first-time stamps for the events that are the record builtapp-v35/ui/track.js, app-v35/ui/plan-pick.js.