mirror of
https://github.com/hanzoai/agency.git
synced 2026-11-02 19:13:28 +00:00
105 lines
5.3 KiB
Plaintext
105 lines
5.3 KiB
Plaintext
/** The emitting surfaces — the closed set of `AnalyticsConfig.product` values.
|
|
* `product` is on every event, so a funnel scopes by product instead of every
|
|
* surface prefixing its event names. */
|
|
export declare const PRODUCTS: readonly ['site', 'app', 'chat', 'console', 'admin', 'cloud', 'id', 'billing', 'pay', 'commerce'];
|
|
export type ProductId = (typeof PRODUCTS)[number];
|
|
export interface FunnelStep {
|
|
/** An EVENTS value (or PAGEVIEW). */
|
|
event: string;
|
|
/** Human label for the step. */
|
|
label: string;
|
|
/** The surface this step is emitted from, when the SAME event name marks two
|
|
* different moments on two surfaces — `checkout_started` on billing is
|
|
* leaving for the checkout, and on pay it is arriving at the card form. The
|
|
* gap between them is a real drop-off, and without this they would collapse
|
|
* into one step and hide it. Unset means any product the funnel names. */
|
|
product?: ProductId;
|
|
/** Property equality that qualifies the step, e.g. first_action{action:'api_call'}. */
|
|
where?: {
|
|
property: string;
|
|
equals: string;
|
|
};
|
|
}
|
|
export interface FunnelDef {
|
|
label: string;
|
|
/** Surface(s) the steps are emitted from — matched against the `product` field. */
|
|
products: ProductId[];
|
|
/**
|
|
* How steps are joined:
|
|
* • 'person' — steps join on distinctId (one browser, or one logged-in
|
|
* person across surfaces). Legitimate when every surface
|
|
* shares the anonymous id, which is a first-party cookie on
|
|
* the REGISTRABLE DOMAIN: www, billing and pay under
|
|
* hanzo.ai are one person; hanzo.id is not, it is a domain of
|
|
* its own and mints an id of its own.
|
|
* • 'aggregate' — steps are emitted on DIFFERENT registrable domains by a
|
|
* LOGGED-OUT visitor, so there is no shared id. Read these as
|
|
* step-over-step COUNTS, never as a per-person conversion.
|
|
* Honest by construction.
|
|
*/
|
|
join: 'person' | 'aggregate';
|
|
steps: FunnelStep[];
|
|
}
|
|
export declare const FUNNELS: {
|
|
/** Land on hanzo.ai → account created. hanzo.id hosts the form and is its own
|
|
* registrable domain, so the middle of this journey is a different identity
|
|
* space from the ends: AGGREGATE, and read step over step. Those two id steps
|
|
* are the part that used to be dark — everything between "we sent them to
|
|
* sign up" and "they came back" happened where nothing was counted. */
|
|
readonly signup: {
|
|
readonly label: 'Signup';
|
|
readonly products: ["site", "id"];
|
|
readonly join: 'aggregate';
|
|
readonly steps: [FunnelStep, FunnelStep, FunnelStep, FunnelStep, FunnelStep, FunnelStep];
|
|
};
|
|
/** The developer activation path: an account is worth nothing until a key has
|
|
* made a call. `first_action{action:'api_call'}` is emitted SERVER-SIDE by
|
|
* Cloud on an org's first successful /v1 request — a browser cannot see it. */
|
|
readonly apiActivation: {
|
|
readonly label: 'API activation';
|
|
readonly products: ["site", "cloud"];
|
|
readonly join: 'person';
|
|
readonly steps: [FunnelStep, FunnelStep, FunnelStep];
|
|
};
|
|
/** Upgrade intent → revenue, and the whole path is under one registrable
|
|
* domain (www, billing and pay are all hanzo.ai), so it joins per PERSON.
|
|
* The two checkout steps are two surfaces: billing hands off to pay, and a
|
|
* buyer lost in that hop is the drop-off nobody could see. */
|
|
readonly upgrade: {
|
|
readonly label: 'Upgrade';
|
|
readonly products: ["site", "app", "console", "billing", "pay"];
|
|
readonly join: 'person';
|
|
readonly steps: [FunnelStep, FunnelStep, FunnelStep, FunnelStep, FunnelStep];
|
|
};
|
|
/** hanzo.app: describe → build → deploy → live URL. The whole product thesis
|
|
* in five steps; `deploy_succeeded` is the moment a live URL exists. */
|
|
readonly appShip: {
|
|
readonly label: 'Describe → ship';
|
|
readonly products: ["app"];
|
|
readonly join: 'person';
|
|
readonly steps: [FunnelStep, FunnelStep, FunnelStep, FunnelStep, FunnelStep];
|
|
};
|
|
/** hanzo.chat: visit → first message → answer. `generation_completed` is what
|
|
* separates "typed something" from "got value". */
|
|
readonly chatEngage: {
|
|
readonly label: 'Chat engagement';
|
|
readonly products: ["chat"];
|
|
readonly join: 'person';
|
|
readonly steps: [FunnelStep, FunnelStep, FunnelStep, FunnelStep];
|
|
};
|
|
/** The cross-surface handoff: the hanzo.ai composer forwards its prompt to
|
|
* hanzo.chat. Two origins, two anonymousIds — so this is an AGGREGATE funnel.
|
|
* The join is the `referrerProduct` property hanzo.chat reads off `?hz_ref=`,
|
|
* which makes the drop-off measurable without any cross-domain identity. */
|
|
readonly siteToChat: {
|
|
readonly label: 'Site → Chat handoff';
|
|
readonly products: ["site", "chat"];
|
|
readonly join: 'aggregate';
|
|
readonly steps: [FunnelStep, FunnelStep, FunnelStep];
|
|
};
|
|
};
|
|
export type FunnelId = keyof typeof FUNNELS;
|
|
/** eventsOf flattens a funnel to its ordered event names — what a goal's `funnel`
|
|
* field carries, so the steps are defined exactly once (here). */
|
|
export declare function eventsOf(id: FunnelId): string[];
|