@sparkletree/core 0.1.0

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 (67) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +105 -0
  3. package/dist/analytics.d.ts +75 -0
  4. package/dist/analytics.d.ts.map +1 -0
  5. package/dist/analytics.js +170 -0
  6. package/dist/analytics.js.map +1 -0
  7. package/dist/audio.d.ts +85 -0
  8. package/dist/audio.d.ts.map +1 -0
  9. package/dist/audio.js +465 -0
  10. package/dist/audio.js.map +1 -0
  11. package/dist/client.d.ts +79 -0
  12. package/dist/client.d.ts.map +1 -0
  13. package/dist/client.js +251 -0
  14. package/dist/client.js.map +1 -0
  15. package/dist/context.d.ts +104 -0
  16. package/dist/context.d.ts.map +1 -0
  17. package/dist/context.js +277 -0
  18. package/dist/context.js.map +1 -0
  19. package/dist/fragments.d.ts +108 -0
  20. package/dist/fragments.d.ts.map +1 -0
  21. package/dist/fragments.js +223 -0
  22. package/dist/fragments.js.map +1 -0
  23. package/dist/index.d.ts +31 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.js +30 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/protocol.d.ts +70 -0
  28. package/dist/protocol.d.ts.map +1 -0
  29. package/dist/protocol.js +97 -0
  30. package/dist/protocol.js.map +1 -0
  31. package/dist/publishableKey.d.ts +38 -0
  32. package/dist/publishableKey.d.ts.map +1 -0
  33. package/dist/publishableKey.js +69 -0
  34. package/dist/publishableKey.js.map +1 -0
  35. package/dist/safeUrl.d.ts +56 -0
  36. package/dist/safeUrl.d.ts.map +1 -0
  37. package/dist/safeUrl.js +117 -0
  38. package/dist/safeUrl.js.map +1 -0
  39. package/dist/sse.d.ts +51 -0
  40. package/dist/sse.d.ts.map +1 -0
  41. package/dist/sse.js +137 -0
  42. package/dist/sse.js.map +1 -0
  43. package/dist/state.d.ts +321 -0
  44. package/dist/state.d.ts.map +1 -0
  45. package/dist/state.js +594 -0
  46. package/dist/state.js.map +1 -0
  47. package/dist/text.d.ts +10 -0
  48. package/dist/text.d.ts.map +1 -0
  49. package/dist/text.js +29 -0
  50. package/dist/text.js.map +1 -0
  51. package/dist/theme.d.ts +52 -0
  52. package/dist/theme.d.ts.map +1 -0
  53. package/dist/theme.js +88 -0
  54. package/dist/theme.js.map +1 -0
  55. package/dist/trust.d.ts +52 -0
  56. package/dist/trust.d.ts.map +1 -0
  57. package/dist/trust.js +95 -0
  58. package/dist/trust.js.map +1 -0
  59. package/dist/variant.d.ts +117 -0
  60. package/dist/variant.d.ts.map +1 -0
  61. package/dist/variant.js +167 -0
  62. package/dist/variant.js.map +1 -0
  63. package/dist/wire.d.ts +564 -0
  64. package/dist/wire.d.ts.map +1 -0
  65. package/dist/wire.js +133 -0
  66. package/dist/wire.js.map +1 -0
  67. package/package.json +55 -0
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Theme engine.
3
+ *
4
+ * The server resolves brand/campaign/context colours and sends tokens plus,
5
+ * for each token, WHERE IT CAME FROM. The client's job is only to put them
6
+ * somewhere CSS can see them, and to do it as custom properties rather than
7
+ * inline styles so a copied component can be restyled without forking the
8
+ * package.
9
+ *
10
+ * `sources` is carried alongside deliberately: it is what the Ledger renders,
11
+ * and a theme that arrives without provenance is a theme nobody can explain
12
+ * to a brand manager asking why their blue changed.
13
+ */
14
+ export interface ResolvedTheme {
15
+ colors: Record<string, string>;
16
+ sources: Record<string, string>;
17
+ }
18
+ /** CSS custom-property prefix. `--st-` is short enough to type in overrides. */
19
+ export declare const TOKEN_PREFIX = "--st-";
20
+ /** `primaryColor` → `--st-primary-color`. */
21
+ export declare function tokenName(key: string): string;
22
+ /**
23
+ * Theme tokens as a style object, ready for React's `style` prop or
24
+ * `Object.assign(el.style, …)`.
25
+ *
26
+ * Values are passed through as authored. Sanitising here would mean parsing
27
+ * CSS colour syntax in the client, which is both a losing game and the wrong
28
+ * layer — the server owns what a valid resolved colour is.
29
+ */
30
+ export declare function themeStyle(theme: ResolvedTheme | null): Record<string, string>;
31
+ /**
32
+ * Write tokens onto an element.
33
+ *
34
+ * Returns a function restoring the previous values, so a component that
35
+ * unmounts mid-stream does not leave a half-applied theme behind on a
36
+ * long-lived host element.
37
+ */
38
+ export declare function applyTheme(element: HTMLElement, theme: ResolvedTheme | null): () => void;
39
+ /**
40
+ * Where a token came from — for the Ledger, and for anyone debugging why a
41
+ * brand colour did not win.
42
+ */
43
+ export declare function tokenSource(theme: ResolvedTheme | null, key: string): string | null;
44
+ /**
45
+ * The ink that reads on a given hex color — the SDK twin of the shell's
46
+ * ReadableTextColor (mosaic-edge-serve/internal/theme/theme.go) and the TS
47
+ * resolver's readableTextColor (mosaic/services/themeService.ts). Same
48
+ * perceived-luminance rule in all three places; change them together.
49
+ * Non-hex input returns light ink, matching both twins.
50
+ */
51
+ export declare function readableTextColor(color: string): string;
52
+ //# sourceMappingURL=theme.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"theme.d.ts","sourceRoot":"","sources":["../src/theme.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACjC;AAED,gFAAgF;AAChF,eAAO,MAAM,YAAY,UAAU,CAAC;AAEpC,6CAA6C;AAC7C,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAE7C;AAED;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,aAAa,GAAG,IAAI,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAO9E;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,aAAa,GAAG,IAAI,GAAG,MAAM,IAAI,CAexF;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,aAAa,GAAG,IAAI,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAEnF;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAYvD"}
package/dist/theme.js ADDED
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Theme engine.
3
+ *
4
+ * The server resolves brand/campaign/context colours and sends tokens plus,
5
+ * for each token, WHERE IT CAME FROM. The client's job is only to put them
6
+ * somewhere CSS can see them, and to do it as custom properties rather than
7
+ * inline styles so a copied component can be restyled without forking the
8
+ * package.
9
+ *
10
+ * `sources` is carried alongside deliberately: it is what the Ledger renders,
11
+ * and a theme that arrives without provenance is a theme nobody can explain
12
+ * to a brand manager asking why their blue changed.
13
+ */
14
+ /** CSS custom-property prefix. `--st-` is short enough to type in overrides. */
15
+ export const TOKEN_PREFIX = "--st-";
16
+ /** `primaryColor` → `--st-primary-color`. */
17
+ export function tokenName(key) {
18
+ return TOKEN_PREFIX + key.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
19
+ }
20
+ /**
21
+ * Theme tokens as a style object, ready for React's `style` prop or
22
+ * `Object.assign(el.style, …)`.
23
+ *
24
+ * Values are passed through as authored. Sanitising here would mean parsing
25
+ * CSS colour syntax in the client, which is both a losing game and the wrong
26
+ * layer — the server owns what a valid resolved colour is.
27
+ */
28
+ export function themeStyle(theme) {
29
+ if (!theme)
30
+ return {};
31
+ const style = {};
32
+ for (const [key, value] of Object.entries(theme.colors)) {
33
+ if (typeof value === "string" && value)
34
+ style[tokenName(key)] = value;
35
+ }
36
+ return style;
37
+ }
38
+ /**
39
+ * Write tokens onto an element.
40
+ *
41
+ * Returns a function restoring the previous values, so a component that
42
+ * unmounts mid-stream does not leave a half-applied theme behind on a
43
+ * long-lived host element.
44
+ */
45
+ export function applyTheme(element, theme) {
46
+ const style = themeStyle(theme);
47
+ const previous = new Map();
48
+ for (const [property, value] of Object.entries(style)) {
49
+ previous.set(property, element.style.getPropertyValue(property));
50
+ element.style.setProperty(property, value);
51
+ }
52
+ return () => {
53
+ for (const [property, value] of previous) {
54
+ if (value)
55
+ element.style.setProperty(property, value);
56
+ else
57
+ element.style.removeProperty(property);
58
+ }
59
+ };
60
+ }
61
+ /**
62
+ * Where a token came from — for the Ledger, and for anyone debugging why a
63
+ * brand colour did not win.
64
+ */
65
+ export function tokenSource(theme, key) {
66
+ return theme?.sources?.[key] ?? null;
67
+ }
68
+ /**
69
+ * The ink that reads on a given hex color — the SDK twin of the shell's
70
+ * ReadableTextColor (mosaic-edge-serve/internal/theme/theme.go) and the TS
71
+ * resolver's readableTextColor (mosaic/services/themeService.ts). Same
72
+ * perceived-luminance rule in all three places; change them together.
73
+ * Non-hex input returns light ink, matching both twins.
74
+ */
75
+ export function readableTextColor(color) {
76
+ const clean = color.startsWith("#") ? color.slice(1) : color;
77
+ const full = /^[0-9a-fA-F]{3}$/.test(clean)
78
+ ? clean.split("").map((c) => c + c).join("")
79
+ : clean;
80
+ if (!/^[0-9a-fA-F]{6}$/.test(full))
81
+ return "#fff";
82
+ const r = parseInt(full.slice(0, 2), 16);
83
+ const g = parseInt(full.slice(2, 4), 16);
84
+ const b = parseInt(full.slice(4, 6), 16);
85
+ const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
86
+ return luminance > 0.6 ? "#000" : "#fff";
87
+ }
88
+ //# sourceMappingURL=theme.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"theme.js","sourceRoot":"","sources":["../src/theme.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Trust chrome.
3
+ *
4
+ * Plan v1.1 decision 6: the adaptation mark is STRUCTURAL. It renders inside
5
+ * the `StreamText` primitive, in the npm package, when and only when the copy
6
+ * beside it was genuinely generated for this viewer. Removing it means forking
7
+ * `@sparkletree/core` — not deleting a line of JSX from a copied component.
8
+ *
9
+ * That is the whole design. A mark a consumer can delete is a mark that will
10
+ * be deleted, and then "marked by default, contractually required" is a
11
+ * sentence in a contract rather than a property of the product.
12
+ *
13
+ * The honesty runs the other way too, and this is the part W5 exists for: the
14
+ * mark must NOT appear on copy that was merely served. Before content-source
15
+ * signalling, the silent static-fallback path was indistinguishable on the
16
+ * wire from live generation — so a client marking "everything that came down a
17
+ * stream" would have been labelling stored copy as adapted. Marking static
18
+ * copy as adapted is a worse failure than not marking generated copy, because
19
+ * one under-claims and the other lies.
20
+ */
21
+ import type { ContentSource } from "./wire.js";
22
+ /** Where the viewer's "show me the standard version" preference lives. */
23
+ export declare const STANDARD_MODE_KEY = "st:standard";
24
+ export interface ChromeDecision {
25
+ /** Render the adaptation mark? */
26
+ marked: boolean;
27
+ /**
28
+ * Why, in one machine-readable word. Reported on the impression, so the
29
+ * chrome state of real traffic is measurable rather than assumed.
30
+ */
31
+ reason: "generated" | "cached" | "static" | "viewer-opted-out";
32
+ }
33
+ /**
34
+ * Should this copy carry the adaptation mark?
35
+ *
36
+ * `cached` counts as adapted: the copy WAS generated, for an earlier viewer in
37
+ * the same variant × context bucket. It is not fresh, but it is not authored
38
+ * either, and calling it authored would be the same lie in the other
39
+ * direction.
40
+ */
41
+ export declare function decideChrome(contentSource: ContentSource, viewerPrefersStandard: boolean): ChromeDecision;
42
+ /**
43
+ * Read the viewer's preference.
44
+ *
45
+ * Any failure — no storage, disabled storage, SSR, a privacy mode that throws
46
+ * on access — means "no stated preference", which is the marked default. A
47
+ * storage error must never be a route to an unmarked creative.
48
+ */
49
+ export declare function viewerPrefersStandard(storage?: Storage): boolean;
50
+ /** Persist the viewer's preference. Silent on failure, for the same reason. */
51
+ export declare function setViewerPrefersStandard(value: boolean, storage?: Storage): void;
52
+ //# sourceMappingURL=trust.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"trust.d.ts","sourceRoot":"","sources":["../src/trust.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAE/C,0EAA0E;AAC1E,eAAO,MAAM,iBAAiB,gBAAgB,CAAC;AAE/C,MAAM,WAAW,cAAc;IAC7B,kCAAkC;IAClC,MAAM,EAAE,OAAO,CAAC;IAChB;;;OAGG;IACH,MAAM,EAAE,WAAW,GAAG,QAAQ,GAAG,QAAQ,GAAG,kBAAkB,CAAC;CAChE;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAC1B,aAAa,EAAE,aAAa,EAC5B,qBAAqB,EAAE,OAAO,GAC7B,cAAc,CAKhB;AAwBD;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,CAAC,EAAE,OAAO,GAAG,OAAO,CAMhE;AAED,+EAA+E;AAC/E,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,OAAO,GAAG,IAAI,CAShF"}
package/dist/trust.js ADDED
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Trust chrome.
3
+ *
4
+ * Plan v1.1 decision 6: the adaptation mark is STRUCTURAL. It renders inside
5
+ * the `StreamText` primitive, in the npm package, when and only when the copy
6
+ * beside it was genuinely generated for this viewer. Removing it means forking
7
+ * `@sparkletree/core` — not deleting a line of JSX from a copied component.
8
+ *
9
+ * That is the whole design. A mark a consumer can delete is a mark that will
10
+ * be deleted, and then "marked by default, contractually required" is a
11
+ * sentence in a contract rather than a property of the product.
12
+ *
13
+ * The honesty runs the other way too, and this is the part W5 exists for: the
14
+ * mark must NOT appear on copy that was merely served. Before content-source
15
+ * signalling, the silent static-fallback path was indistinguishable on the
16
+ * wire from live generation — so a client marking "everything that came down a
17
+ * stream" would have been labelling stored copy as adapted. Marking static
18
+ * copy as adapted is a worse failure than not marking generated copy, because
19
+ * one under-claims and the other lies.
20
+ */
21
+ /** Where the viewer's "show me the standard version" preference lives. */
22
+ export const STANDARD_MODE_KEY = "st:standard";
23
+ /**
24
+ * Should this copy carry the adaptation mark?
25
+ *
26
+ * `cached` counts as adapted: the copy WAS generated, for an earlier viewer in
27
+ * the same variant × context bucket. It is not fresh, but it is not authored
28
+ * either, and calling it authored would be the same lie in the other
29
+ * direction.
30
+ */
31
+ export function decideChrome(contentSource, viewerPrefersStandard) {
32
+ if (viewerPrefersStandard)
33
+ return { marked: false, reason: "viewer-opted-out" };
34
+ if (contentSource === "generated")
35
+ return { marked: true, reason: "generated" };
36
+ if (contentSource === "cached")
37
+ return { marked: true, reason: "cached" };
38
+ return { marked: false, reason: "static" };
39
+ }
40
+ /**
41
+ * Resolve the store.
42
+ *
43
+ * `window.localStorage` FIRST, not the bare `localStorage` global: Node 18+
44
+ * exposes a global of that name which is undefined unless the runtime was
45
+ * started with `--localstorage-file`, and it shadows the real one in any
46
+ * DOM-emulating test environment. Reading the bare global there does not
47
+ * throw — it silently yields undefined, which would make every preference
48
+ * lookup quietly return the default in exactly the environment where the
49
+ * behaviour is being verified.
50
+ */
51
+ function resolveStorage(explicit) {
52
+ if (explicit)
53
+ return explicit;
54
+ try {
55
+ if (typeof window !== "undefined" && window.localStorage)
56
+ return window.localStorage;
57
+ if (typeof localStorage !== "undefined" && localStorage)
58
+ return localStorage;
59
+ }
60
+ catch {
61
+ // Access itself throws under some privacy modes.
62
+ }
63
+ return null;
64
+ }
65
+ /**
66
+ * Read the viewer's preference.
67
+ *
68
+ * Any failure — no storage, disabled storage, SSR, a privacy mode that throws
69
+ * on access — means "no stated preference", which is the marked default. A
70
+ * storage error must never be a route to an unmarked creative.
71
+ */
72
+ export function viewerPrefersStandard(storage) {
73
+ try {
74
+ return resolveStorage(storage)?.getItem(STANDARD_MODE_KEY) === "1";
75
+ }
76
+ catch {
77
+ return false;
78
+ }
79
+ }
80
+ /** Persist the viewer's preference. Silent on failure, for the same reason. */
81
+ export function setViewerPrefersStandard(value, storage) {
82
+ try {
83
+ const store = resolveStorage(storage);
84
+ if (!store)
85
+ return;
86
+ if (value)
87
+ store.setItem(STANDARD_MODE_KEY, "1");
88
+ else
89
+ store.removeItem(STANDARD_MODE_KEY);
90
+ }
91
+ catch {
92
+ // Preference is a nicety; failing to store it must not break a render.
93
+ }
94
+ }
95
+ //# sourceMappingURL=trust.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"trust.js","sourceRoot":"","sources":["../src/trust.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAIH,0EAA0E;AAC1E,MAAM,CAAC,MAAM,iBAAiB,GAAG,aAAa,CAAC;AAY/C;;;;;;;GAOG;AACH,MAAM,UAAU,YAAY,CAC1B,aAA4B,EAC5B,qBAA8B;IAE9B,IAAI,qBAAqB;QAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,kBAAkB,EAAE,CAAC;IAChF,IAAI,aAAa,KAAK,WAAW;QAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC;IAChF,IAAI,aAAa,KAAK,QAAQ;QAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;IAC1E,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;AAC7C,CAAC;AAED;;;;;;;;;;GAUG;AACH,SAAS,cAAc,CAAC,QAAkB;IACxC,IAAI,QAAQ;QAAE,OAAO,QAAQ,CAAC;IAC9B,IAAI,CAAC;QACH,IAAI,OAAO,MAAM,KAAK,WAAW,IAAI,MAAM,CAAC,YAAY;YAAE,OAAO,MAAM,CAAC,YAAY,CAAC;QACrF,IAAI,OAAO,YAAY,KAAK,WAAW,IAAI,YAAY;YAAE,OAAO,YAAY,CAAC;IAC/E,CAAC;IAAC,MAAM,CAAC;QACP,iDAAiD;IACnD,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,qBAAqB,CAAC,OAAiB;IACrD,IAAI,CAAC;QACH,OAAO,cAAc,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC,iBAAiB,CAAC,KAAK,GAAG,CAAC;IACrE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,+EAA+E;AAC/E,MAAM,UAAU,wBAAwB,CAAC,KAAc,EAAE,OAAiB;IACxE,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,cAAc,CAAC,OAAO,CAAC,CAAC;QACtC,IAAI,CAAC,KAAK;YAAE,OAAO;QACnB,IAAI,KAAK;YAAE,KAAK,CAAC,OAAO,CAAC,iBAAiB,EAAE,GAAG,CAAC,CAAC;;YAC5C,KAAK,CAAC,UAAU,CAAC,iBAAiB,CAAC,CAAC;IAC3C,CAAC;IAAC,MAAM,CAAC;QACP,uEAAuE;IACzE,CAAC;AACH,CAAC"}
@@ -0,0 +1,117 @@
1
+ /**
2
+ * Variant pinning (plan v1.1 §3.3).
3
+ *
4
+ * ε-greedy variant selection runs PER REQUEST on the server. A page with a
5
+ * hero island and a CTA island therefore makes two selections, and without
6
+ * pinning they are independent draws: the hero can come from arm A and the
7
+ * button from arm B. That page is not a mixed experience, it is a broken one —
8
+ * and worse, the impression it generates attributes a blend of two variants to
9
+ * whichever id happened to be reported, which quietly poisons the very signal
10
+ * the bandit is learning from.
11
+ *
12
+ * So: the FIRST resolution in a page view wins, and every subsequent call
13
+ * pins it via `variant=`. One page view, one variant, one honest attribution.
14
+ *
15
+ * The sync `get()`/`pin()` pair alone is not enough, because sibling islands
16
+ * mount in the SAME React commit: both read `get()` before either stream has
17
+ * resolved, both see undefined, and the server draws twice — exactly the
18
+ * failure described above, reintroduced by a race. `reserve()` closes it: the
19
+ * first caller per pin key becomes the LEADER and opens its stream unpinned;
20
+ * every later caller becomes a FOLLOWER and receives a promise that resolves
21
+ * with the leader's variant once its stream reports one. A leader that fails
22
+ * before learning a variant aborts the reservation so someone else can lead,
23
+ * and followers carry a timeout so a hung leader degrades to today's
24
+ * unpinned behaviour instead of blocking the page.
25
+ *
26
+ * Scoped per PIN KEY, not globally: two campaigns on one page are genuinely
27
+ * separate experiments and must not pin each other. The key is the campaign id
28
+ * when the island is campaign-addressed, and the SURFACE id when the island is
29
+ * surface-only — sibling surface islands resolve through the same deployment
30
+ * and would otherwise each draw independently, the exact mixed page pinning
31
+ * exists to prevent. (Two different keys that happen to resolve to the same
32
+ * campaign server-side are treated as separate experiments; addressing is the
33
+ * caller's statement of intent.)
34
+ */
35
+ /**
36
+ * How long a follower waits for the leader's stream before giving up and
37
+ * requesting unpinned.
38
+ *
39
+ * This must cover the SERVER'S stream budget, not a hopeful median: the edge
40
+ * caps a stream at 30 seconds (context.WithTimeout in
41
+ * mosaic-edge-serve/internal/handler/stream.go), and the variant id arrives in
42
+ * the terminal `done` event — so a slow generation can legally take the whole
43
+ * budget before any follower learns the draw. An earlier 3s value expired
44
+ * inside that window, sent followers unpinned mid-generation, and painted the
45
+ * mixed page pinning exists to prevent. Waiting is cheap: followers hold
46
+ * their first-paint fallback copy the entire time, which is designed
47
+ * behaviour, not a blank. 30s server budget + 1s margin for transport.
48
+ */
49
+ export declare const RESERVATION_TIMEOUT_MS = 31000;
50
+ export interface LeaderReservation {
51
+ kind: "leader";
52
+ /**
53
+ * The stream resolved: pin `variantId` and release every follower with it.
54
+ * Idempotent; calls after `resolve` or `abort` are ignored.
55
+ */
56
+ resolve: (variantId: string) => void;
57
+ /**
58
+ * The leader failed (or unmounted) before learning a variant. Followers
59
+ * currently waiting are released unpinned, and the NEXT `reserve()` call
60
+ * for this pin key leads. Idempotent.
61
+ */
62
+ abort: () => void;
63
+ }
64
+ export interface FollowerReservation {
65
+ kind: "follower";
66
+ /**
67
+ * Resolves with the pinned variant, or `undefined` when the leader aborted,
68
+ * the wait timed out, or the leader finished without a variant — in which
69
+ * case the follower should request unpinned, exactly as it would have
70
+ * before reservations existed. Never rejects.
71
+ */
72
+ variantId: Promise<string | undefined>;
73
+ }
74
+ export type VariantReservation = LeaderReservation | FollowerReservation;
75
+ export declare class VariantRegistry {
76
+ private readonly pinned;
77
+ private readonly pending;
78
+ /**
79
+ * The variant to request for this pin key — the campaign id, or the surface
80
+ * id for surface-only islands — if one is already pinned. Undefined means
81
+ * "let the server choose" — which it should, exactly once.
82
+ */
83
+ get(key: string): string | undefined;
84
+ /**
85
+ * Record a resolved variant. First writer wins.
86
+ *
87
+ * Later calls are ignored rather than overwriting, because a second
88
+ * resolution arriving at all means one island raced ahead of the pin — and
89
+ * in that case the right repair is to keep the value already reported on the
90
+ * impression, not to adopt the newer one and disagree with our own
91
+ * analytics.
92
+ */
93
+ pin(key: string, variantId: string): string;
94
+ /**
95
+ * Claim, or wait for, this page view's variant for a pin key (campaign id,
96
+ * or surface id for surface-only islands).
97
+ *
98
+ * The first caller (per key, per page view) gets a LEADER reservation
99
+ * and must eventually call `resolve` or `abort` on it. Everyone else gets a
100
+ * FOLLOWER whose promise resolves with the leader's variant, capped at
101
+ * `timeoutMs`. A key that is already pinned returns a follower whose
102
+ * promise is already resolved — awaiting it costs one microtask.
103
+ */
104
+ reserve(key: string, timeoutMs?: number): VariantReservation;
105
+ /** New page view. Call on client-side navigation. */
106
+ reset(): void;
107
+ }
108
+ /**
109
+ * The registry for the current page view.
110
+ *
111
+ * Module-scoped on purpose: pinning must span every SparkleTree component on
112
+ * the page regardless of which provider or tree rendered it, and a
113
+ * per-provider registry would let two providers each pin a different arm of
114
+ * the same campaign.
115
+ */
116
+ export declare const pageVariants: VariantRegistry;
117
+ //# sourceMappingURL=variant.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"variant.d.ts","sourceRoot":"","sources":["../src/variant.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAEH;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,sBAAsB,QAAS,CAAC;AAE7C,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,QAAQ,CAAC;IACf;;;OAGG;IACH,OAAO,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC;;;;OAIG;IACH,KAAK,EAAE,MAAM,IAAI,CAAC;CACnB;AAED,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,UAAU,CAAC;IACjB;;;;;OAKG;IACH,SAAS,EAAE,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;CACxC;AAED,MAAM,MAAM,kBAAkB,GAAG,iBAAiB,GAAG,mBAAmB,CAAC;AAOzE,qBAAa,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA6B;IACpD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAyC;IAEjE;;;;OAIG;IACH,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAIpC;;;;;;;;OAQG;IACH,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM;IAO3C;;;;;;;;;OASG;IACH,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,GAAE,MAA+B,GAAG,kBAAkB;IA8CpF,qDAAqD;IACrD,KAAK,IAAI,IAAI;CAOd;AAoBD;;;;;;;GAOG;AACH,eAAO,MAAM,YAAY,iBAAwB,CAAC"}
@@ -0,0 +1,167 @@
1
+ /**
2
+ * Variant pinning (plan v1.1 §3.3).
3
+ *
4
+ * ε-greedy variant selection runs PER REQUEST on the server. A page with a
5
+ * hero island and a CTA island therefore makes two selections, and without
6
+ * pinning they are independent draws: the hero can come from arm A and the
7
+ * button from arm B. That page is not a mixed experience, it is a broken one —
8
+ * and worse, the impression it generates attributes a blend of two variants to
9
+ * whichever id happened to be reported, which quietly poisons the very signal
10
+ * the bandit is learning from.
11
+ *
12
+ * So: the FIRST resolution in a page view wins, and every subsequent call
13
+ * pins it via `variant=`. One page view, one variant, one honest attribution.
14
+ *
15
+ * The sync `get()`/`pin()` pair alone is not enough, because sibling islands
16
+ * mount in the SAME React commit: both read `get()` before either stream has
17
+ * resolved, both see undefined, and the server draws twice — exactly the
18
+ * failure described above, reintroduced by a race. `reserve()` closes it: the
19
+ * first caller per pin key becomes the LEADER and opens its stream unpinned;
20
+ * every later caller becomes a FOLLOWER and receives a promise that resolves
21
+ * with the leader's variant once its stream reports one. A leader that fails
22
+ * before learning a variant aborts the reservation so someone else can lead,
23
+ * and followers carry a timeout so a hung leader degrades to today's
24
+ * unpinned behaviour instead of blocking the page.
25
+ *
26
+ * Scoped per PIN KEY, not globally: two campaigns on one page are genuinely
27
+ * separate experiments and must not pin each other. The key is the campaign id
28
+ * when the island is campaign-addressed, and the SURFACE id when the island is
29
+ * surface-only — sibling surface islands resolve through the same deployment
30
+ * and would otherwise each draw independently, the exact mixed page pinning
31
+ * exists to prevent. (Two different keys that happen to resolve to the same
32
+ * campaign server-side are treated as separate experiments; addressing is the
33
+ * caller's statement of intent.)
34
+ */
35
+ /**
36
+ * How long a follower waits for the leader's stream before giving up and
37
+ * requesting unpinned.
38
+ *
39
+ * This must cover the SERVER'S stream budget, not a hopeful median: the edge
40
+ * caps a stream at 30 seconds (context.WithTimeout in
41
+ * mosaic-edge-serve/internal/handler/stream.go), and the variant id arrives in
42
+ * the terminal `done` event — so a slow generation can legally take the whole
43
+ * budget before any follower learns the draw. An earlier 3s value expired
44
+ * inside that window, sent followers unpinned mid-generation, and painted the
45
+ * mixed page pinning exists to prevent. Waiting is cheap: followers hold
46
+ * their first-paint fallback copy the entire time, which is designed
47
+ * behaviour, not a blank. 30s server budget + 1s margin for transport.
48
+ */
49
+ export const RESERVATION_TIMEOUT_MS = 31_000;
50
+ export class VariantRegistry {
51
+ pinned = new Map();
52
+ pending = new Map();
53
+ /**
54
+ * The variant to request for this pin key — the campaign id, or the surface
55
+ * id for surface-only islands — if one is already pinned. Undefined means
56
+ * "let the server choose" — which it should, exactly once.
57
+ */
58
+ get(key) {
59
+ return this.pinned.get(key);
60
+ }
61
+ /**
62
+ * Record a resolved variant. First writer wins.
63
+ *
64
+ * Later calls are ignored rather than overwriting, because a second
65
+ * resolution arriving at all means one island raced ahead of the pin — and
66
+ * in that case the right repair is to keep the value already reported on the
67
+ * impression, not to adopt the newer one and disagree with our own
68
+ * analytics.
69
+ */
70
+ pin(key, variantId) {
71
+ const existing = this.pinned.get(key);
72
+ if (existing)
73
+ return existing;
74
+ this.pinned.set(key, variantId);
75
+ return variantId;
76
+ }
77
+ /**
78
+ * Claim, or wait for, this page view's variant for a pin key (campaign id,
79
+ * or surface id for surface-only islands).
80
+ *
81
+ * The first caller (per key, per page view) gets a LEADER reservation
82
+ * and must eventually call `resolve` or `abort` on it. Everyone else gets a
83
+ * FOLLOWER whose promise resolves with the leader's variant, capped at
84
+ * `timeoutMs`. A key that is already pinned returns a follower whose
85
+ * promise is already resolved — awaiting it costs one microtask.
86
+ */
87
+ reserve(key, timeoutMs = RESERVATION_TIMEOUT_MS) {
88
+ const existing = this.pinned.get(key);
89
+ if (existing)
90
+ return { kind: "follower", variantId: Promise.resolve(existing) };
91
+ const inFlight = this.pending.get(key);
92
+ if (inFlight) {
93
+ return { kind: "follower", variantId: withTimeout(inFlight.promise, timeoutMs) };
94
+ }
95
+ let settled = false;
96
+ let release;
97
+ const promise = new Promise((res) => {
98
+ release = res;
99
+ });
100
+ const entry = {
101
+ promise,
102
+ settle: (variantId) => {
103
+ if (settled)
104
+ return;
105
+ settled = true;
106
+ release(variantId);
107
+ },
108
+ };
109
+ this.pending.set(key, entry);
110
+ const clearIfCurrent = () => {
111
+ if (this.pending.get(key) === entry)
112
+ this.pending.delete(key);
113
+ };
114
+ return {
115
+ kind: "leader",
116
+ resolve: (variantId) => {
117
+ if (settled)
118
+ return;
119
+ // `pin` first-writer-wins still applies: if something pinned in the
120
+ // meantime, followers get the value analytics already reported.
121
+ const winner = this.pin(key, variantId);
122
+ clearIfCurrent();
123
+ entry.settle(winner);
124
+ },
125
+ abort: () => {
126
+ if (settled)
127
+ return;
128
+ clearIfCurrent();
129
+ entry.settle(undefined);
130
+ },
131
+ };
132
+ }
133
+ /** New page view. Call on client-side navigation. */
134
+ reset() {
135
+ this.pinned.clear();
136
+ // Anyone still waiting belongs to the OLD page view; release them
137
+ // unpinned rather than leaving promises to dangle past the navigation.
138
+ for (const entry of this.pending.values())
139
+ entry.settle(undefined);
140
+ this.pending.clear();
141
+ }
142
+ }
143
+ /**
144
+ * Resolve with `promise`'s value, or `undefined` after `timeoutMs` —
145
+ * whichever comes first. Never rejects.
146
+ */
147
+ function withTimeout(promise, timeoutMs) {
148
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0)
149
+ return promise;
150
+ return new Promise((resolve) => {
151
+ const timer = setTimeout(() => resolve(undefined), timeoutMs);
152
+ void promise.then((value) => {
153
+ clearTimeout(timer);
154
+ resolve(value);
155
+ });
156
+ });
157
+ }
158
+ /**
159
+ * The registry for the current page view.
160
+ *
161
+ * Module-scoped on purpose: pinning must span every SparkleTree component on
162
+ * the page regardless of which provider or tree rendered it, and a
163
+ * per-provider registry would let two providers each pin a different arm of
164
+ * the same campaign.
165
+ */
166
+ export const pageVariants = new VariantRegistry();
167
+ //# sourceMappingURL=variant.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"variant.js","sourceRoot":"","sources":["../src/variant.ts"],"names":[],"mappings":"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"}