@appfunnel-dev/sdk 2.0.0-canary.4 → 2.0.0-canary.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/README.md +1 -1
  2. package/dist/{checkout-Dz8cGkB_.d.cts → checkout-CMEdxpgo.d.cts} +13 -13
  3. package/dist/{checkout-7Dy6IedP.d.ts → checkout-D1NUPv4p.d.ts} +13 -13
  4. package/dist/{chunk-AKO6XKXP.js → chunk-7YQLPPPG.js} +66 -91
  5. package/dist/chunk-7YQLPPPG.js.map +1 -0
  6. package/dist/{chunk-JSRKA375.cjs → chunk-BPOAWI4G.cjs} +68 -94
  7. package/dist/chunk-BPOAWI4G.cjs.map +1 -0
  8. package/dist/{chunk-M6U3FNRW.js → chunk-CFFMZYPE.js} +3 -3
  9. package/dist/{chunk-M6U3FNRW.js.map → chunk-CFFMZYPE.js.map} +1 -1
  10. package/dist/chunk-EMMSS5I5.cjs +37 -0
  11. package/dist/chunk-EMMSS5I5.cjs.map +1 -0
  12. package/dist/chunk-G3PMV62Z.js +33 -0
  13. package/dist/chunk-G3PMV62Z.js.map +1 -0
  14. package/dist/{chunk-LJYLGLFS.cjs → chunk-QMAZGLGV.cjs} +2 -35
  15. package/dist/chunk-QMAZGLGV.cjs.map +1 -0
  16. package/dist/{chunk-YY375F2B.js → chunk-TNLRQPVY.js} +3 -33
  17. package/dist/chunk-TNLRQPVY.js.map +1 -0
  18. package/dist/{chunk-CY4VBSMX.cjs → chunk-VAOR77NW.cjs} +7 -7
  19. package/dist/{chunk-CY4VBSMX.cjs.map → chunk-VAOR77NW.cjs.map} +1 -1
  20. package/dist/chunk-WYUDL4FI.cjs +8 -0
  21. package/dist/chunk-WYUDL4FI.cjs.map +1 -0
  22. package/dist/chunk-ZZJG4EYL.js +6 -0
  23. package/dist/chunk-ZZJG4EYL.js.map +1 -0
  24. package/dist/driver-paddle.cjs +23 -22
  25. package/dist/driver-paddle.cjs.map +1 -1
  26. package/dist/driver-paddle.d.cts +1 -1
  27. package/dist/driver-paddle.d.ts +1 -1
  28. package/dist/driver-paddle.js +3 -2
  29. package/dist/driver-paddle.js.map +1 -1
  30. package/dist/driver-stripe.cjs +277 -224
  31. package/dist/driver-stripe.cjs.map +1 -1
  32. package/dist/driver-stripe.d.cts +18 -2
  33. package/dist/driver-stripe.d.ts +18 -2
  34. package/dist/driver-stripe.js +244 -192
  35. package/dist/driver-stripe.js.map +1 -1
  36. package/dist/index.cjs +750 -150
  37. package/dist/index.cjs.map +1 -1
  38. package/dist/index.d.cts +151 -90
  39. package/dist/index.d.ts +151 -90
  40. package/dist/index.js +702 -98
  41. package/dist/index.js.map +1 -1
  42. package/dist/{manifest-Cr2y1op6.d.cts → manifest-CMe8yVkr.d.cts} +131 -88
  43. package/dist/{manifest-Cr2y1op6.d.ts → manifest-CMe8yVkr.d.ts} +131 -88
  44. package/dist/manifest-entry.cjs +62 -47
  45. package/dist/manifest-entry.cjs.map +1 -1
  46. package/dist/manifest-entry.d.cts +15 -15
  47. package/dist/manifest-entry.d.ts +15 -15
  48. package/dist/manifest-entry.js +25 -14
  49. package/dist/manifest-entry.js.map +1 -1
  50. package/dist/protocol.cjs +13 -0
  51. package/dist/protocol.cjs.map +1 -0
  52. package/dist/protocol.d.cts +182 -0
  53. package/dist/protocol.d.ts +182 -0
  54. package/dist/protocol.js +4 -0
  55. package/dist/protocol.js.map +1 -0
  56. package/package.json +14 -2
  57. package/dist/chunk-AKO6XKXP.js.map +0 -1
  58. package/dist/chunk-JSRKA375.cjs.map +0 -1
  59. package/dist/chunk-LJYLGLFS.cjs.map +0 -1
  60. package/dist/chunk-YY375F2B.js.map +0 -1
@@ -140,16 +140,20 @@ interface SessionContext {
140
140
  }
141
141
  /**
142
142
  * Durable identity for **stable experiment bucketing** (phase-4 §4.2). The
143
- * preferred seed is `customerId` once known; before identification it's the
144
- * signed anonymous `visitorId` (P1). It is deliberately **not** the session id or
145
- * fingerprint a returning visitor (e.g. via winback) must keep their variant
146
- * across sessions/devices (landmine #4). Both null = unidentifiable traffic, and
147
- * the experiment layer falls back to control rather than bucket on nothing.
143
+ * preferred seed is the signed anonymous `visitorId` (stable from the first
144
+ * pageload, matching the server-side FUNNEL split in resolve.ts); `customerId` is
145
+ * only the fallback for cookieless SDK embeds. visitorId-FIRST is deliberate the
146
+ * anon customerId is minted server-side AFTER the first tracked event, so seeding
147
+ * on it would flip a returning visitor's arm between visit 1 and 2 (see
148
+ * {@link ../flow/experiments}.bucketingSeed). It is deliberately **not** the
149
+ * session id or fingerprint — a returning visitor (e.g. via winback) must keep
150
+ * their variant across sessions (landmine #4). Both null = unidentifiable traffic,
151
+ * and the experiment layer falls back to control rather than bucket on nothing.
148
152
  */
149
153
  interface IdentityContext {
150
- /** Known customer id (post-identification). The preferred bucketing seed. */
154
+ /** Known customer id (post-identification). The fallback bucketing seed (cookieless embeds). */
151
155
  customerId: string | null;
152
- /** Signed anonymous visitor id. The fallback bucketing seed. */
156
+ /** Signed anonymous visitor id. The preferred bucketing seed (stable from visit 1). */
153
157
  visitorId: string | null;
154
158
  }
155
159
  interface SystemContext {
@@ -203,14 +207,14 @@ declare function buildContext(opts?: BuildContextOptions): FunnelContext;
203
207
  /**
204
208
  * Pure catalog math — no React, no I/O. Split out of {@link ./catalog} so the
205
209
  * server/tooling entry (`@appfunnel-dev/sdk/manifest`) can compile/resolve
206
- * products without pulling react-dom or any component code into its graph.
210
+ * offerings without pulling react-dom or any component code into its graph.
207
211
  * {@link ./catalog} re-exports everything here and adds the React wiring.
208
212
  */
209
213
  type Interval = 'day' | 'week' | 'month' | 'year' | 'one_time';
210
- /** What the platform hands the funnel per product (a single provider-resolved price). */
211
- interface ProductInput {
214
+ /** What the platform hands the funnel per offering (a single provider-resolved price). */
215
+ interface OfferingInput {
212
216
  id: string;
213
- /** The catalog product KEY this input charges (the slot's assignment). Defaults to `id` (legacy/identity slots). */
217
+ /** The catalog charge KEY this input charges (the slot's assignment). Defaults to `id` (legacy/identity slots). */
214
218
  catalogKey?: string;
215
219
  name?: string;
216
220
  displayName?: string;
@@ -237,12 +241,12 @@ interface Money {
237
241
  formatted: string;
238
242
  }
239
243
  /**
240
- * The product with its amounts resolved (currency + period math), **before**
241
- * locale formatting — what the catalog stores. `useProduct` formats it.
244
+ * The offering with its amounts resolved (currency + period math), **before**
245
+ * locale formatting — what the catalog stores. `useOffering` formats it.
242
246
  */
243
- interface ResolvedProduct {
247
+ interface ResolvedOffering {
244
248
  id: string;
245
- /** The catalog product KEY charged (the CHARGE identity; `id` is the SLOT/display identity). */
249
+ /** The catalog charge KEY charged (the CHARGE identity; `id` is the SLOT/display identity). */
246
250
  catalogKey: string;
247
251
  name: string;
248
252
  displayName: string;
@@ -259,10 +263,10 @@ interface ResolvedProduct {
259
263
  trialDays: number;
260
264
  trialMinor: number;
261
265
  }
262
- /** Display-ready product the author reads (every amount locale-formatted). */
263
- interface Product {
266
+ /** Display-ready offering the author reads (every amount locale-formatted). */
267
+ interface Offering {
264
268
  id: string;
265
- /** The catalog product KEY charged (the CHARGE identity; `id` is the SLOT/display identity). */
269
+ /** The catalog charge KEY charged (the CHARGE identity; `id` is the SLOT/display identity). */
266
270
  catalogKey: string;
267
271
  name: string;
268
272
  displayName: string;
@@ -282,13 +286,13 @@ interface Product {
282
286
  trialPrice: Money | null;
283
287
  }
284
288
  /**
285
- * Resolve a {@link ProductInput} into amounts + period math (no formatting yet).
289
+ * Resolve an {@link OfferingInput} into amounts + period math (no formatting yet).
286
290
  * Per-period amounts are **integer minor units** (each derived from the exact
287
291
  * daily rate, then rounded — never round-then-multiply, so error doesn't compound).
288
292
  */
289
- declare function resolveProduct(input: ProductInput): ResolvedProduct;
290
- /** Build the id → {@link ResolvedProduct} catalog. Formatting happens at read time. */
291
- declare function buildCatalog(inputs: ProductInput[]): Map<string, ResolvedProduct>;
293
+ declare function resolveOffering(input: OfferingInput): ResolvedOffering;
294
+ /** Build the id → {@link ResolvedOffering} catalog. Formatting happens at read time. */
295
+ declare function buildCatalog(inputs: OfferingInput[]): Map<string, ResolvedOffering>;
292
296
  /**
293
297
  * The minor-unit exponent for a currency (`USD`→2, `JPY`→0, `KWD`/`BHD`→3),
294
298
  * derived from `Intl.NumberFormat(...).resolvedOptions().maximumFractionDigits`
@@ -297,8 +301,8 @@ declare function buildCatalog(inputs: ProductInput[]): Map<string, ResolvedProdu
297
301
  declare function currencyExponent(currency: string): number;
298
302
  /** Format minor units of `currency` in `locale` (exponent-aware: 7900 JPY = ¥7,900). */
299
303
  declare function formatMoney(minor: number, currency: string, locale: string): Money;
300
- /** Format a {@link ResolvedProduct} into a display {@link Product} in `locale`. */
301
- declare function formatProduct(r: ResolvedProduct, locale: string): Product;
304
+ /** Format a {@link ResolvedOffering} into a display {@link Offering} in `locale`. */
305
+ declare function formatOffering(r: ResolvedOffering, locale: string): Offering;
302
306
 
303
307
  /**
304
308
  * The funnel **spine** — the constrained, declarative configuration authored as
@@ -372,6 +376,13 @@ interface PageMeta {
372
376
  /**
373
377
  * Ordered routing edges out of this page (first match wins). Each has a static
374
378
  * `to` key + an optional `when` gate. Omit for the default linear flow.
379
+ *
380
+ * OPTIONAL per-variant routing: a `@variant` page (`paywall@b`) MAY carry its own
381
+ * `next`. When that variant is the one served for its slot, its `next` governs
382
+ * routing for that visitor; absent, the variant inherits the slot's routing (the
383
+ * default — a pure superset, no behavior change for existing funnels). Assignment,
384
+ * exposure, and progress still anchor on the slot; only the next-page lookup
385
+ * consults the served variant's routes. See {@link FunnelPage}.
375
386
  */
376
387
  next?: Route[];
377
388
  /**
@@ -383,6 +394,19 @@ interface PageMeta {
383
394
  * always restorable. Same gate kind as routing (`Condition` or predicate).
384
395
  */
385
396
  guard?: Gate;
397
+ /**
398
+ * BUILD-TIME PRERENDER OPT-OUT (SSR-isolation Option B, FIX 2). Set `dynamic: true` (alias
399
+ * `prerender: false`) on a page whose **structure** (which sections exist — not just their text
400
+ * values) depends on the visitor's `sessionValues`. Such a page can't be prerendered flash-free:
401
+ * the default-state markup would structurally differ from the value-applied render and reflow
402
+ * after hydration. Opting out makes the build SKIP prerendering it, so the renderer serves it via
403
+ * live SSR (the same fallback branch a build with no prerender entry already uses) — correct
404
+ * first paint, no pop-in. Value-only variation (text/price into a fixed structure) does NOT need
405
+ * this: deferred defaults make it flash-free. Omit = prerendered (the default).
406
+ */
407
+ dynamic?: boolean;
408
+ /** @see {@link PageMeta.dynamic} — `prerender: false` is the inverse alias (author preference). */
409
+ prerender?: boolean;
386
410
  }
387
411
  /**
388
412
  * Identity helper for a page's co-located metadata.
@@ -412,6 +436,15 @@ interface FlowPage {
412
436
  * AUTHORED shape (funnel.ts owns the page list, order, and routing); the build generates
413
437
  * the code-split `mount.tsx` from it. Routing (`next`) must stay DECLARATIVE (Route[] with
414
438
  * `{ to, when }` conditions, no predicate functions) so the web builder UI can edit it.
439
+ *
440
+ * OPTIONAL variant routing (default off): a served variant's OWN routing is authored by
441
+ * declaring the variant here with its `@` key and a `next`, e.g.
442
+ * `{ key: 'paywall@b', next: [{ to: 'special-offer' }] }`. A variant declared this way keys
443
+ * on the `@` marker, so it still collapses out of the linear flow (it is NOT a linear step);
444
+ * its `next` is consulted only when that variant is the one served for its slot. Omit the
445
+ * declaration (leave the variant an undeclared `pages/<slot>@<v>.tsx` file) to inherit the
446
+ * slot's routing — the existing behavior, unchanged. The slot's control keeps owning
447
+ * assignment/exposure/progress; only the next-page lookup for a branched variant differs.
415
448
  */
416
449
  interface FunnelPage extends PageMeta {
417
450
  key: string;
@@ -441,6 +474,13 @@ interface FunnelLocales {
441
474
  default: string;
442
475
  supported: string[];
443
476
  fallback?: string;
477
+ /**
478
+ * When `true`, a visitor with no explicit language in the URL (no `/​<locale>/` path prefix and no
479
+ * `?language=`) is served the best match for their **browser** `Accept-Language` among `supported`
480
+ * (base-matched, so `en-GB` → `en`); no match falls back to `default`. When `false`/absent (the
481
+ * default), an unspecified visitor always gets `default` — the URL is the only way to switch.
482
+ */
483
+ autoDetectLanguage?: boolean;
444
484
  }
445
485
  /**
446
486
  * The funnel-level spine. Flow is mostly inferred from page file order + each
@@ -463,19 +503,19 @@ interface FunnelDefinition {
463
503
  /** `data.*` working/scratch variables (default values). */
464
504
  data?: Record<string, VariableConfig>;
465
505
  /**
466
- * The catalog products this funnel sells, as SLOTS: a local, stable key each PAGE
467
- * references (`useProduct('primary')`, `<Checkout product="primary">`) → the catalog product
506
+ * The catalog offerings this funnel sells, as SLOTS: a local, stable key each PAGE
507
+ * references (`useOffering('primary')`, `<Checkout offering="primary">`) → the catalog charge
468
508
  * KEY it's assigned to. Swap the assignment to change the offer WITHOUT touching page code;
469
509
  * `null` = a declared-but-unassigned slot. A bare `string[]` is legacy sugar for IDENTITY
470
510
  * slots (`['pro'] ≡ { pro: 'pro' }`) — every existing funnel keeps working. Prices resolve
471
511
  * from the project catalog per environment (Live/Test) at render.
472
512
  */
473
- products?: string[] | Record<string, string | null>;
513
+ offerings?: string[] | Record<string, string | null>;
474
514
  locales?: FunnelLocales;
475
515
  /**
476
516
  * When `true`, the funnel shows the **geo-specific** currency/price the payment
477
517
  * provider resolves (Stripe `currency_options`, Paddle preview, Whop, SolidGate,
478
- * …). When `false` (default), a single fixed currency is used. AppFunnel does no
518
+ * …). When `false` (default), a single fixed currency is used. Appfunnel does no
479
519
  * FX — this only toggles whether the platform asks the provider for the geo
480
520
  * price; the SDK just displays whatever resolved `{amount, currency}` it's handed.
481
521
  * (See docs/platform-v2 phase-3 §3.6.)
@@ -484,19 +524,19 @@ interface FunnelDefinition {
484
524
  }
485
525
  /** Identity helper for a funnel definition. */
486
526
  declare function defineFunnel(def: FunnelDefinition): FunnelDefinition;
487
- /** A funnel product SLOT: the local key pages reference → the assigned catalog product key (null = unassigned). */
488
- interface ProductSlot {
527
+ /** A funnel offering SLOT: the local key pages reference → the assigned catalog charge key (null = unassigned). */
528
+ interface OfferingSlot {
489
529
  slot: string;
490
530
  catalogKey: string | null;
491
531
  }
492
532
  /**
493
- * Normalize `FunnelDefinition.products` to a slot list. A legacy `string[]` becomes IDENTITY
533
+ * Normalize `FunnelDefinition.offerings` to a slot list. A legacy `string[]` becomes IDENTITY
494
534
  * slots (`['pro'] → [{ slot:'pro', catalogKey:'pro' }]`); a `{ slot: key|null }` map becomes its
495
- * entries (insertion order preserved, so `useProducts()` is deterministic).
535
+ * entries (insertion order preserved, so `useOfferings()` is deterministic).
496
536
  */
497
- declare function normalizeProducts(products: FunnelDefinition['products']): ProductSlot[];
537
+ declare function normalizeOfferings(offerings: FunnelDefinition['offerings']): OfferingSlot[];
498
538
  /** The unique, ASSIGNED catalog keys a funnel charges — for the promote gate + catalog reverse-lookup. */
499
- declare function funnelCatalogKeys(products: FunnelDefinition['products']): string[];
539
+ declare function funnelCatalogKeys(offerings: FunnelDefinition['offerings']): string[];
500
540
 
501
541
  /**
502
542
  * Pure locale resolution — no React. Split out of {@link ./translation} so the
@@ -590,8 +630,8 @@ declare function parseSlotKey(key: string): {
590
630
  };
591
631
  /** True if a page key is an off-flow variant (`welcome@b`), by the source-side `@` marker. */
592
632
  declare function isVariantKey(key: string): boolean;
593
- /** One arm of an experiment: which page renders, at what weight. */
594
- interface ExperimentVariant {
633
+ /** One arm of a running experiment: which page renders, at what weight. */
634
+ interface ExperimentArm {
595
635
  /** The page key this arm renders (the slot's control, or a `@variant` sibling). */
596
636
  page: string;
597
637
  /** Relative weight in the split (need not sum to 100; zero = paused arm). */
@@ -599,19 +639,24 @@ interface ExperimentVariant {
599
639
  }
600
640
  /**
601
641
  * A runtime experiment record — the operational wiring the **platform** owns and
602
- * hands to the SDK (not authored in the funnel source). The variant *pages* are
603
- * code in the bundle; this is the live config over them.
642
+ * hands to the SDK (not authored in the funnel source). Projected from the unified
643
+ * `Experiment`/`ExperimentArm` graph (a RUNNING PAGE experiment + its arms): the
644
+ * experiment's `slot` names the base page, and each arm becomes a `variants` entry
645
+ * `{ page: arm.pageKey, weight: arm.weight }` keyed by `arm.label`. The variant
646
+ * *pages* are code in the LIVE build; this is the live split over them.
647
+ *
648
+ * The serve join only ever hands over RUNNING experiments, and the server's
649
+ * draft-write + serve guards make every active arm's page GUARANTEED present in the
650
+ * LIVE build (an experiment owns its arm pages' lifecycle — you end the experiment to
651
+ * remove a page, you can't drift it away). So there is no lifecycle/winner state and
652
+ * no "arm page missing" case to resolve here.
604
653
  */
605
654
  interface RuntimeExperiment {
606
655
  id: string;
607
- /** The page key holding the flow position (the anchor / control). */
656
+ /** The page key holding the flow position (the anchor / the control arm's base page). */
608
657
  slot: string;
609
- /** label → arm. One arm's `page` is the `slot` (control); others are `@variant` siblings. */
610
- variants: Record<string, ExperimentVariant>;
611
- /** Lifecycle. Absent = running. */
612
- status?: 'running' | 'paused' | 'stopped';
613
- /** When stopped, the variant label everyone sees until promotion bakes it into the slot. */
614
- winner?: string;
658
+ /** label → arm. The control arm's `page` is the `slot`; others are `@variant` siblings. */
659
+ variants: Record<string, ExperimentArm>;
615
660
  /** Primary metric (analytics only; not used for resolution). */
616
661
  metric?: string;
617
662
  /** Platform record version (immutable-while-running; reweight = new version). Rides on
@@ -641,43 +686,20 @@ declare function weightsOf(variants: Record<string, {
641
686
  weight: number;
642
687
  }>): Record<string, number>;
643
688
  /**
644
- * Resolve which page version each experiment slot shows for this visitor, and
645
- * which page keys remain in the linear flow.
689
+ * Resolve which page version each experiment slot shows for this visitor, and which
690
+ * page keys remain in the linear flow.
646
691
  *
647
- * Variant pages are detected structurally by the `@` in their key, so they
648
- * collapse out of the linear flow even for a slot with no active experiment.
649
- * For each *running* experiment the visitor is bucketed (stable on `seed`); a
650
- * *stopped* one serves its `winner`; a *paused* one (or no seed) serves the slot.
692
+ * Variant pages are detected structurally by the `@` in their key, so they collapse
693
+ * out of the linear flow even for a slot with no active experiment. Each served
694
+ * experiment is RUNNING with all its arm pages guaranteed present in the LIVE build
695
+ * (the server draft-write + serve guards enforce the experiment-owned lifecycle
696
+ * invariant), so the visitor is bucketed deterministically on `seed` and the assigned
697
+ * arm's page is swapped in with no presence check. With no `seed` there is nothing
698
+ * stable to bucket on, so every slot renders its own control page.
651
699
  */
652
700
  declare function resolveExperiments(pages: {
653
701
  key: string;
654
702
  }[], experiments: RuntimeExperiment[], seed: string | null): ExperimentResolution;
655
- /** One referential problem with an experiment record. */
656
- interface ExperimentIssue {
657
- /** The offending experiment id (`'*'` for a cross-experiment issue). */
658
- experimentId: string;
659
- code: 'slot_missing' | 'slot_is_variant' | 'slot_conflict' | 'duplicate_id' | 'variant_page_missing' | 'variant_slot_mismatch' | 'no_control' | 'no_traffic' | 'negative_weight' | 'bad_winner' | 'single_arm' | 'orphan_variant';
660
- message: string;
661
- }
662
- interface ExperimentValidation {
663
- ok: boolean;
664
- errors: ExperimentIssue[];
665
- warnings: ExperimentIssue[];
666
- }
667
- /**
668
- * Cross-check experiment records against the funnel's pages — the publish-time /
669
- * experiment-edit gate that catches the dangling references the runtime can't see
670
- * (the experiment wiring lives in the DB; the pages live in the build, so they can
671
- * drift). Pure: hand it the runtime experiments + the funnel's page keys (e.g.
672
- * `manifest.pages`).
673
- *
674
- * Errors block; warnings inform. `orphan_variant` is a warning, not an error — a
675
- * `@variant` file with no running experiment is dead-but-harmless (a paused test, or
676
- * one not wired yet).
677
- */
678
- declare function validateExperiments(experiments: RuntimeExperiment[], pages: {
679
- key: string;
680
- }[]): ExperimentValidation;
681
703
 
682
704
  /**
683
705
  * The **manifest** — the declarative IR a funnel project compiles to (doc 06).
@@ -723,6 +745,20 @@ interface ManifestPage {
723
745
  * not the manifest.
724
746
  */
725
747
  variantOf?: string;
748
+ /**
749
+ * The page's effective ENTRY guard, serialized (SSR-isolation Option B, FIX 1): a `declarative`
750
+ * {@link Condition} the renderer can evaluate SERVER-SIDE against the visitor's session snapshot
751
+ * to pick the right prerendered variant (target vs bounce) without running tenant code, or an
752
+ * opaque `predicate` marker (author code — the renderer must fall back to live SSR for that
753
+ * deep-link). Absent = no entry guard (always restorable). Mirrors {@link entryGuard}.
754
+ */
755
+ entryGuard?: EdgeCondition;
756
+ /**
757
+ * BUILD-TIME PRERENDER OPT-OUT (SSR-isolation Option B, FIX 2). True when the page's `meta.dynamic`
758
+ * (or `prerender: false`) opts it out of prerendering because its STRUCTURE depends on
759
+ * sessionValues. The prerender stage skips it; the renderer serves it via live SSR.
760
+ */
761
+ dynamic?: boolean;
726
762
  }
727
763
  type EdgeCondition = {
728
764
  kind: 'declarative';
@@ -739,6 +775,13 @@ interface ManifestEdge {
739
775
  kind: 'branch' | 'linear';
740
776
  /** Absent = unconditional (a fallback route, or the linear fall-through). */
741
777
  condition?: EdgeCondition;
778
+ /**
779
+ * True on a **variant-only branch**: an edge whose `from` is a `@variant` key,
780
+ * emitted when that served variant carries its OWN `meta.next` (optional divergent
781
+ * routing). Lets consumers (the editor's Flow canvas) tell a variant's private branch
782
+ * apart from a normal slot edge. Optional/absent on every non-variant edge.
783
+ */
784
+ variant?: true;
742
785
  }
743
786
  interface ManifestValidation {
744
787
  /**
@@ -776,9 +819,9 @@ interface ManifestValidation {
776
819
  * though every page can proceed. Hard error.
777
820
  */
778
821
  noReachableFinish: boolean;
779
- /** Slot names a PAGE references (`useProduct`/`<Checkout product>`) that the funnel doesn't declare — hard error. */
780
- unknownProductRefs: string[];
781
- /** Declared slots a page references but which have no catalog product assigned (map value `null`) — hard error. */
822
+ /** Slot names a PAGE references (`useOffering`/`<Checkout offering>`) that the funnel doesn't declare — hard error. */
823
+ unknownOfferingRefs: string[];
824
+ /** Declared slots a page references but which have no catalog charge key assigned (map value `null`) — hard error. */
782
825
  unassignedSlots: string[];
783
826
  }
784
827
  interface FunnelManifest {
@@ -790,11 +833,11 @@ interface FunnelManifest {
790
833
  edges: ManifestEdge[];
791
834
  };
792
835
  /** The unique, ASSIGNED catalog keys the funnel charges (for the promote gate + catalog reverse-lookup). */
793
- products: string[];
794
- /** Product SLOTS: slot → assigned catalog key|null. `products` above is the unique assigned keys. */
795
- productSlots: ProductSlot[];
796
- /** Slot names PAGES reference (from the build's scan); validated against `productSlots`. Absent = no scan ran. */
797
- productRefs?: string[];
836
+ offerings: string[];
837
+ /** Offering SLOTS: slot → assigned catalog key|null. `offerings` above is the unique assigned keys. */
838
+ offeringSlots: OfferingSlot[];
839
+ /** Slot names PAGES reference (from the build's scan); validated against `offeringSlots`. Absent = no scan ran. */
840
+ offeringRefs?: string[];
798
841
  locales?: FunnelLocales;
799
842
  validation: ManifestValidation;
800
843
  }
@@ -805,10 +848,10 @@ interface CompileInput {
805
848
  key: string;
806
849
  meta?: PageMeta;
807
850
  }[];
808
- /** Slot names the funnel's PAGES reference (from `scanProductRefs`) — cross-checked against the declared slots. */
809
- productRefs?: string[];
851
+ /** Slot names the funnel's PAGES reference (from `scanOfferingRefs`) — cross-checked against the declared slots. */
852
+ offeringRefs?: string[];
810
853
  }
811
854
  /** Compile a funnel's spine into its declarative {@link FunnelManifest} IR. */
812
855
  declare function compileManifest(input: CompileInput): FunnelManifest;
813
856
 
814
- export { evaluateCondition as $, type Acquisition as A, type BrowserContext as B, type ClickIdContext as C, type Direction as D, type EdgeCondition as E, type FunnelContext as F, type Gate as G, type Route as H, type IdentityContext as I, type SystemContext as J, assignVariant as K, type LocaleContext as L, type ManifestEdge as M, bucketingSeed as N, type OsContext as O, type Product as P, buildAcquisition as Q, type RuntimeExperiment as R, type SessionContext as S, buildCatalog as T, type UtmContext as U, type VariableValue as V, compileManifest as W, currencyExponent as X, defineFunnel as Y, definePage as Z, entryGuard as _, type VariableConfig as a, evaluateGate as a0, expectedPathLength as a1, fnv1a as a2, formatMoney as a3, formatProduct as a4, hashToUnit as a5, isRtl as a6, isVariantKey as a7, nextPage as a8, outgoingKeys as a9, pageMeta as aa, parseSlotKey as ab, pickByWeight as ac, resolveExperiments as ad, resolveLocale as ae, resolveProduct as af, resolveRoute as ag, validateExperiments as ah, type FunnelPage as ai, type ProductSlot as aj, funnelCatalogKeys as ak, normalizeProducts as al, weightsOf as am, type FunnelSnapshot as b, type FunnelDefinition as c, buildContext as d, type ProductInput as e, type PageMeta as f, type PageContext as g, type BuildContextOptions as h, type CompileInput as i, type Condition as j, type ConditionOp as k, type DeviceContext as l, type ExperimentIssue as m, type ExperimentResolution as n, type ExperimentValidation as o, type ExperimentVariant as p, type FlowPage as q, type FunnelLocales as r, type FunnelManifest as s, type Interval as t, type ManifestPage as u, type ManifestValidation as v, type Money as w, type PageType as x, type Predicate as y, type ResolvedProduct as z };
857
+ export { expectedPathLength as $, type Acquisition as A, type BrowserContext as B, type ClickIdContext as C, type Direction as D, type EdgeCondition as E, type FunnelContext as F, type Gate as G, assignVariant as H, type IdentityContext as I, bucketingSeed as J, buildAcquisition as K, type LocaleContext as L, type ManifestEdge as M, buildCatalog as N, type Offering as O, type PageMeta as P, compileManifest as Q, type RuntimeExperiment as R, type SessionContext as S, currencyExponent as T, type UtmContext as U, type VariableValue as V, defineFunnel as W, definePage as X, entryGuard as Y, evaluateCondition as Z, evaluateGate as _, type VariableConfig as a, fnv1a as a0, formatMoney as a1, formatOffering as a2, hashToUnit as a3, isRtl as a4, isVariantKey as a5, nextPage as a6, outgoingKeys as a7, pageMeta as a8, parseSlotKey as a9, pickByWeight as aa, resolveExperiments as ab, resolveLocale as ac, resolveOffering as ad, resolveRoute as ae, type FunnelPage as af, type OfferingSlot as ag, funnelCatalogKeys as ah, normalizeOfferings as ai, weightsOf as aj, type FunnelSnapshot as b, type FunnelDefinition as c, buildContext as d, type OfferingInput as e, type PageContext as f, type BuildContextOptions as g, type CompileInput as h, type Condition as i, type ConditionOp as j, type DeviceContext as k, type ExperimentArm as l, type ExperimentResolution as m, type FlowPage as n, type FunnelLocales as o, type FunnelManifest as p, type Interval as q, type ManifestPage as r, type ManifestValidation as s, type Money as t, type OsContext as u, type PageType as v, type Predicate as w, type ResolvedOffering as x, type Route as y, type SystemContext as z };