Files
agency/.pnpm-store/v3/files/92/8a1ac9ce04304d136eaca0ba111b66e0df9cd73142378f96d00bfb48dc72202812d3ee0b176f15db6866cf428cfb4c2f6985e33b37817d388153677903238e
hanzo-agent 5af6960b87 run project
Run: sess_f37640604ae254cb4d11dc6217f7b721
2026-09-30 00:04:56 +00:00

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[];