@sparkletree/core 0.12.0 → 0.13.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.
- package/README.md +18 -19
- package/dist/analytics.d.ts +7 -1
- package/dist/analytics.d.ts.map +1 -1
- package/dist/analytics.js +2 -1
- package/dist/analytics.js.map +1 -1
- package/dist/client.d.ts +8 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +6 -6
- package/dist/client.js.map +1 -1
- package/dist/context.d.ts +8 -0
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +21 -0
- package/dist/context.js.map +1 -1
- package/dist/fragments.d.ts +21 -24
- package/dist/fragments.d.ts.map +1 -1
- package/dist/fragments.js +10 -7
- package/dist/fragments.js.map +1 -1
- package/dist/index.d.ts +5 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/protocol.d.ts +12 -1
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js +5 -2
- package/dist/protocol.js.map +1 -1
- package/dist/server.d.ts +144 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +463 -0
- package/dist/server.js.map +1 -0
- package/dist/slots.d.ts +66 -0
- package/dist/slots.d.ts.map +1 -0
- package/dist/slots.js +102 -0
- package/dist/slots.js.map +1 -0
- package/dist/state.d.ts +282 -239
- package/dist/state.d.ts.map +1 -1
- package/dist/state.js +434 -413
- package/dist/state.js.map +1 -1
- package/dist/variant.d.ts +70 -1
- package/dist/variant.d.ts.map +1 -1
- package/dist/variant.js +85 -1
- package/dist/variant.js.map +1 -1
- package/dist/visitorContext.d.ts +99 -0
- package/dist/visitorContext.d.ts.map +1 -0
- package/dist/visitorContext.js +84 -0
- package/dist/visitorContext.js.map +1 -0
- package/package.json +5 -1
package/dist/state.d.ts
CHANGED
|
@@ -1,57 +1,64 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Copy choreography — the
|
|
2
|
+
* Copy choreography — the hold/settle state machine.
|
|
3
3
|
*
|
|
4
4
|
* This is the part of the SDK that is not a fetch wrapper. The principle it
|
|
5
5
|
* serves is a user-experience one, and everything else here follows from it:
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
* replaced, taken away, or pushed around — copy, image, video, layout,
|
|
9
|
-
* all of it. So the space is reserved blank (sized by the copy it keeps
|
|
10
|
-
* room for — never a skeleton, never a spinner) until there is something
|
|
11
|
-
* final to show. If the stream delivers inside the budget, that is what
|
|
12
|
-
* the visitor gets. If it is too slow or fails, the fallback is what they
|
|
13
|
-
* get. Either way, that is the content for this page view. A stream that
|
|
14
|
-
* arrives after the visitor already has something in front of them has
|
|
15
|
-
* lost its slot: it is discarded from the canvas (it may still inform
|
|
16
|
-
* provenance and analytics). The fallback is the failure path; it never
|
|
17
|
-
* shows first and then gets replaced.
|
|
7
|
+
* TEXT A VISITOR CAN SEE IS NEVER REPLACED WHILE IT IS ON SCREEN.
|
|
18
8
|
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
9
|
+
* 1. When a copy slot comes into view and live copy is coming, the slot
|
|
10
|
+
* holds blank with its geometry reserved for a short budget
|
|
11
|
+
* (`COPY_HOLD_MS`), restarting once when the stream proves it is alive.
|
|
12
|
+
* 2. Stream text arriving inside the hold types into the blank slot (a
|
|
13
|
+
* cached replay's single flush lands whole). The fallback is never seen.
|
|
14
|
+
* 3. If the hold runs out with nothing delivered, the fallback lands once
|
|
15
|
+
* and is FINAL for this view: any later stream text for that field is
|
|
16
|
+
* dropped — never painted, never composed over it.
|
|
17
|
+
* 4. Art, background and chrome are never held; only text slots are.
|
|
26
18
|
*
|
|
27
|
-
*
|
|
28
|
-
* a budget for the STORED FALLBACK, not for the stream.
|
|
19
|
+
* Every field is in exactly one of three conditions:
|
|
29
20
|
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
* `*-done
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
21
|
+
* - blank: held (reserved) or simply empty, waiting for the stream;
|
|
22
|
+
* - live: stream text is on the canvas; later chunks EXTEND it (typing),
|
|
23
|
+
* and its `*-done` completes it — never replaces it;
|
|
24
|
+
* - final: copy the visitor can read that the stream does not own — the
|
|
25
|
+
* fallback after the hold, finished creative the platform rendered, or
|
|
26
|
+
* copy that was already on screen when the stream opened. Copy events
|
|
27
|
+
* for a final field are dropped.
|
|
36
28
|
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
29
|
+
* There is one exception to "never replaced", and it is isolated in one
|
|
30
|
+
* function: a sentence the stream was still WRITING when it failed. See
|
|
31
|
+
* `UnfinishedCopyPolicy`.
|
|
39
32
|
*/
|
|
40
33
|
import { type ContentSource, type CopyMode } from "./wire.js";
|
|
41
34
|
export type CopyField = "greeting" | "headline" | "body" | "cta";
|
|
42
35
|
export declare const COPY_FIELDS: readonly CopyField[];
|
|
43
36
|
/**
|
|
44
|
-
* How long
|
|
37
|
+
* How long a copy slot holds blank, waiting for live copy, before the
|
|
38
|
+
* fallback lands.
|
|
45
39
|
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
* budget ONCE when the stream proves it is alive, so a slow link still gets a
|
|
51
|
-
* full budget for delivery — without the blank window being extended per
|
|
52
|
-
* field.
|
|
40
|
+
* The budget starts when the slot is in view (or when the stream opens, for
|
|
41
|
+
* a renderer that does not report visibility), and restarts ONCE when the
|
|
42
|
+
* stream proves it is alive (its first `*-start`). So the longest a slot can
|
|
43
|
+
* hold is `COPY_HOLD_MAX_MS`.
|
|
53
44
|
*/
|
|
54
45
|
export declare const COPY_HOLD_MS = 2500;
|
|
46
|
+
/** The longest a slot can hold: one budget plus the one restart. */
|
|
47
|
+
export declare const COPY_HOLD_MAX_MS: number;
|
|
48
|
+
/**
|
|
49
|
+
* The pure-CSS failsafe for server-rendered reservations: if the page's
|
|
50
|
+
* JavaScript never takes control, reserved fallback copy becomes visible on
|
|
51
|
+
* its own after this long. Equal to the longest hold, so the failsafe can
|
|
52
|
+
* never reveal copy a running hold still owns.
|
|
53
|
+
*/
|
|
54
|
+
export declare const RESERVE_FAILSAFE_MS: number;
|
|
55
|
+
/**
|
|
56
|
+
* The one motion grammar for copy: every fade and cross-fade runs for this
|
|
57
|
+
* long on `COPY_MOTION_EASING`. Typing has its own pace; nothing else moves.
|
|
58
|
+
*/
|
|
59
|
+
export declare const COPY_MOTION_MS = 700;
|
|
60
|
+
/** The easing curve for every copy fade and cross-fade. */
|
|
61
|
+
export declare const COPY_MOTION_EASING = "cubic-bezier(0.16, 1, 0.3, 1)";
|
|
55
62
|
export interface FieldState {
|
|
56
63
|
/** What a consumer should render right now. Never a placeholder. */
|
|
57
64
|
text: string;
|
|
@@ -62,20 +69,28 @@ export interface FieldState {
|
|
|
62
69
|
mode: CopyMode | null;
|
|
63
70
|
/** True while live deltas are landing — consumers may animate a caret. */
|
|
64
71
|
typing: boolean;
|
|
65
|
-
/**
|
|
72
|
+
/** `text` is final for this stream: closed by the server, or settled by
|
|
73
|
+
* the SDK at the end of the stream. */
|
|
66
74
|
settled: boolean;
|
|
75
|
+
/**
|
|
76
|
+
* True when `text` deliberately replaces text that may have been visible:
|
|
77
|
+
* an unfinished sentence withdrawn after a failure (`UnfinishedCopyPolicy`)
|
|
78
|
+
* or new copy after the campaign or context changed. Renderers cross-fade
|
|
79
|
+
* once (`COPY_MOTION_MS`, `COPY_MOTION_EASING`) instead of typing. Absent
|
|
80
|
+
* on every other field — no other change ever replaces visible text.
|
|
81
|
+
*/
|
|
82
|
+
crossfade?: boolean;
|
|
67
83
|
}
|
|
68
84
|
export type Phase =
|
|
69
85
|
/** Nothing started. */
|
|
70
86
|
"idle"
|
|
71
|
-
/**
|
|
87
|
+
/** Copy slots are deliberately blank (reserved), waiting for live copy. */
|
|
72
88
|
| "holding"
|
|
73
|
-
/**
|
|
89
|
+
/** Copy is on the canvas and the stream is still open. */
|
|
74
90
|
| "live"
|
|
75
91
|
/** `done` received. */
|
|
76
92
|
| "settled"
|
|
77
|
-
/** `error`, or the transport failed. Copy
|
|
78
|
-
* stream left unfinished or blank is back on its fallback. */
|
|
93
|
+
/** `error`, or the transport failed. Copy on the canvas stays. */
|
|
79
94
|
| "failed";
|
|
80
95
|
export interface CreativeState {
|
|
81
96
|
phase: Phase;
|
|
@@ -97,86 +112,176 @@ export interface CreativeState {
|
|
|
97
112
|
ctaAction: string | null;
|
|
98
113
|
/** Terminal `done.meta`, verbatim. */
|
|
99
114
|
meta: Record<string, unknown> | null;
|
|
100
|
-
/**
|
|
115
|
+
/**
|
|
116
|
+
* Provenance of the copy on the canvas, stream-wide: the strongest any
|
|
117
|
+
* field declared while live, the server's terminal word once settled, and
|
|
118
|
+
* `static` whenever no adapted copy actually reached the canvas. Decide a
|
|
119
|
+
* disclosure mark per field (`field.source`), not from this.
|
|
120
|
+
*/
|
|
101
121
|
contentSource: ContentSource;
|
|
102
|
-
/**
|
|
122
|
+
/** Set when the server served a deterministic winner instead. */
|
|
103
123
|
degraded: string | null;
|
|
104
124
|
/** Resolved variant, for pinning subsequent calls in this page view. */
|
|
105
125
|
variantId: string | null;
|
|
106
126
|
/** Human-readable failure, if any. Never blanks painted copy. */
|
|
107
127
|
error: string | null;
|
|
108
128
|
/**
|
|
109
|
-
* What the hold
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
* is blank (LAYOUT-DOCTRINE.md layer 1). The choreographer stays
|
|
113
|
-
* DOM-pure: it reports what was held; drawing the reservation is the
|
|
114
|
-
* renderer's job. Null when no hold armed this page view.
|
|
129
|
+
* What the hold reserved, per field: the copy that will land if the hold
|
|
130
|
+
* runs out. A renderer draws it invisibly so a blank field keeps its exact
|
|
131
|
+
* geometry. Null when no hold armed in this view.
|
|
115
132
|
*
|
|
116
|
-
* Optional so a `CreativeState`
|
|
117
|
-
*
|
|
118
|
-
* through `state.held?.`, so absent and null are the same fact.
|
|
133
|
+
* Optional so a hand-built `CreativeState` (a test mock, a story fixture)
|
|
134
|
+
* still compiles: read it as `state.held?.`.
|
|
119
135
|
*/
|
|
120
136
|
held?: Partial<Record<CopyField, string>> | null;
|
|
121
137
|
/**
|
|
122
|
-
*
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
*
|
|
126
|
-
* replacement at all. A 1 reached after the initial copy was VISIBLE is a
|
|
127
|
-
* bait and switch (the known bug above). Redefining this to count visible
|
|
128
|
-
* replacements only is pending. Anything above 1 has always been a
|
|
129
|
-
* violation and is asserted against in the tests.
|
|
138
|
+
* How many times text the visitor could see was replaced. Always `0` from
|
|
139
|
+
* a single stream: the hold, the stream's entrance and the fallback's
|
|
140
|
+
* landing all write into blank slots. `useIsland` adds one per cross-fade
|
|
141
|
+
* after a campaign or context change, the only replacement there is.
|
|
130
142
|
*/
|
|
131
143
|
rewrites: number;
|
|
132
144
|
}
|
|
145
|
+
/** What `UnfinishedCopyPolicy` decides about. */
|
|
146
|
+
export interface UnfinishedCopy {
|
|
147
|
+
field: CopyField;
|
|
148
|
+
/** The text the stream had written when it stopped, possibly mid-word. */
|
|
149
|
+
typed: string;
|
|
150
|
+
/** The fallback that would replace it. Never empty when the policy is asked. */
|
|
151
|
+
fallback: string;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* `"keep"`: the typed text stays and settles where it is.
|
|
155
|
+
* `"fallback"`: the typed text fades out and the fallback fades in, once.
|
|
156
|
+
*/
|
|
157
|
+
export type UnfinishedCopyDecision = "keep" | "fallback";
|
|
158
|
+
/**
|
|
159
|
+
* Decides what happens to a field the stream was still writing when it
|
|
160
|
+
* ended without closing it (an error, a dropped connection, or a `done` that
|
|
161
|
+
* arrived without the field's own close).
|
|
162
|
+
*
|
|
163
|
+
* This is the one place visible text may be replaced, and only when the
|
|
164
|
+
* policy says so: the default (`keepUnlessClearlyError`) keeps what was
|
|
165
|
+
* typed unless it is clearly an error. A replacement is a single cross-fade
|
|
166
|
+
* (`FieldState.crossfade`). A field whose text was finished is never asked
|
|
167
|
+
* about; a field that never painted simply gets its fallback.
|
|
168
|
+
*/
|
|
169
|
+
export type UnfinishedCopyPolicy = (copy: UnfinishedCopy) => UnfinishedCopyDecision;
|
|
170
|
+
/**
|
|
171
|
+
* True when `text` is very clearly not copy: it contains an error message, a
|
|
172
|
+
* stack or status text, raw markup or JSON, or a model refusal. Conservative
|
|
173
|
+
* by design — anything that could be ordinary copy returns false.
|
|
174
|
+
*/
|
|
175
|
+
export declare function isClearlyErrorText(text: string): boolean;
|
|
176
|
+
/**
|
|
177
|
+
* The default `UnfinishedCopyPolicy`: keep whatever the stream already typed,
|
|
178
|
+
* even an unfinished line. Only text that is clearly an error
|
|
179
|
+
* (`isClearlyErrorText`) is replaced, with one fade to the fallback.
|
|
180
|
+
*/
|
|
181
|
+
export declare const keepUnlessClearlyError: UnfinishedCopyPolicy;
|
|
133
182
|
export interface ChoreographerOptions {
|
|
134
183
|
/**
|
|
135
|
-
*
|
|
136
|
-
*
|
|
184
|
+
* The fallback: what lands if live copy does not arrive in time. Required by
|
|
185
|
+
* doctrine — there is no skeleton mode.
|
|
137
186
|
*/
|
|
138
187
|
initial?: Partial<Record<CopyField, string>>;
|
|
139
188
|
/**
|
|
140
|
-
* True when the initial copy
|
|
141
|
-
*
|
|
142
|
-
*
|
|
143
|
-
*
|
|
144
|
-
* could hold it. The second should not happen — the fallback is the
|
|
145
|
-
* failure path and the render path should emit the reservation, not the
|
|
146
|
-
* text — and is a defect to fix at the server render, not a state to
|
|
147
|
-
* design for.
|
|
189
|
+
* True when the initial copy is ALREADY VISIBLE when the stream opens: copy
|
|
190
|
+
* at rest from earlier in this view, or a fallback the page revealed before
|
|
191
|
+
* the SDK could hold it. Every non-empty field is then final for this
|
|
192
|
+
* view: no hold, and the stream's copy events for it are dropped.
|
|
148
193
|
*
|
|
149
|
-
*
|
|
150
|
-
*
|
|
151
|
-
* lands once at `*-done` is one of the known bugs the file header lists.)
|
|
194
|
+
* Leave it unset for copy that has not been seen yet — the normal case.
|
|
195
|
+
* The hold then keeps the slot blank until live copy or the fallback lands.
|
|
152
196
|
*/
|
|
153
197
|
initialIsOnScreen?: boolean;
|
|
154
198
|
/**
|
|
155
|
-
* True when the initial copy is
|
|
156
|
-
*
|
|
157
|
-
*
|
|
158
|
-
*
|
|
159
|
-
*
|
|
160
|
-
* stream may replace: live deltas never paint over it. (That their
|
|
161
|
-
* composed result still lands once at `*-done` is a known bug, file
|
|
162
|
-
* header.)
|
|
199
|
+
* True when the initial copy is FINISHED CAMPAIGN CREATIVE rendered by the
|
|
200
|
+
* platform — not a developer's fallback. It is the copy for this view:
|
|
201
|
+
* nothing holds, and the stream's copy events for those fields are dropped
|
|
202
|
+
* (theme, media and the action still apply). Fields left empty may still
|
|
203
|
+
* be written by the stream.
|
|
163
204
|
*
|
|
164
|
-
*
|
|
165
|
-
*
|
|
166
|
-
* typed entrance reachable. Setting it from the ordinary client commit
|
|
167
|
-
* (commit 3dc544913 treated every non-empty first paint this way) made
|
|
168
|
-
* paced streaming unreachable in every real integration, because
|
|
169
|
-
* `fallback` is required by doctrine and always paints before the stream
|
|
170
|
-
* opens.
|
|
205
|
+
* Do not set it for a `fallback`: a fallback is what lands only when live
|
|
206
|
+
* copy does not.
|
|
171
207
|
*/
|
|
172
208
|
serverRendered?: boolean;
|
|
173
|
-
/**
|
|
174
|
-
*
|
|
175
|
-
*
|
|
209
|
+
/**
|
|
210
|
+
* The hold budget for this view. Defaults to `COPY_HOLD_MS`; restarts once
|
|
211
|
+
* when the stream proves alive. However the restart falls, the hold never
|
|
212
|
+
* lasts longer than twice this in total.
|
|
213
|
+
*/
|
|
176
214
|
holdMs?: number;
|
|
177
|
-
/**
|
|
215
|
+
/**
|
|
216
|
+
* How long the slot has ALREADY been holding before this choreographer took
|
|
217
|
+
* over — for example, since a server-rendered reservation first painted.
|
|
218
|
+
* It is spent from the budget and from the absolute maximum, so the wait
|
|
219
|
+
* before hydration and the wait after it are one hold. Defaults to 0.
|
|
220
|
+
*/
|
|
221
|
+
holdElapsedMs?: number;
|
|
222
|
+
/**
|
|
223
|
+
* Provenance of the `initial` copy when it is finished creative
|
|
224
|
+
* (`serverRendered`), for example copy already generated for this view.
|
|
225
|
+
* Those fields carry it as their `source` and are settled from the start.
|
|
226
|
+
* Defaults to `static`.
|
|
227
|
+
*/
|
|
228
|
+
initialSource?: ContentSource;
|
|
229
|
+
/** The variant the `initial` copy belongs to, if known. Seeds `variantId`. */
|
|
230
|
+
initialVariantId?: string;
|
|
231
|
+
/**
|
|
232
|
+
* With `serverRendered`: the `initial` copy is the WHOLE copy for this
|
|
233
|
+
* view, so the stream writes no field, empty ones included. Use it for
|
|
234
|
+
* copy decided before the slot was shown (for example cached copy a
|
|
235
|
+
* server read for this visitor): a field it left empty stays empty rather
|
|
236
|
+
* than gaining text later.
|
|
237
|
+
*/
|
|
238
|
+
initialIsComplete?: boolean;
|
|
239
|
+
/**
|
|
240
|
+
* When true, the hold budget does not start until `slotInView()` is called.
|
|
241
|
+
* The stream may open (and buffer) earlier; a slot that never comes into
|
|
242
|
+
* view never shows anything changing. When false (the default), the slot
|
|
243
|
+
* is treated as in view from `streamOpened()`.
|
|
244
|
+
*/
|
|
245
|
+
holdStartsInView?: boolean;
|
|
246
|
+
/**
|
|
247
|
+
* When the held canvas is handed to the stream.
|
|
248
|
+
*
|
|
249
|
+
* `"text"` (default): on the first text the stream delivers — live copy
|
|
250
|
+
* types into the blank slot.
|
|
251
|
+
*
|
|
252
|
+
* `"complete"`: only when the stream finishes (`done`), so the whole new
|
|
253
|
+
* copy lands at once. For a slot whose canvas is showing something else in
|
|
254
|
+
* the meantime (a campaign or context change); the hold budget still
|
|
255
|
+
* applies.
|
|
256
|
+
*/
|
|
257
|
+
releaseOn?: "text" | "complete";
|
|
258
|
+
/** What happens to a field the stream was still writing when it ended.
|
|
259
|
+
* Defaults to `keepUnlessClearlyError`. */
|
|
260
|
+
unfinishedCopy?: UnfinishedCopyPolicy;
|
|
261
|
+
/**
|
|
262
|
+
* When true, the fallback decides which fields exist: a field whose
|
|
263
|
+
* `initial` copy is empty is never shown, and the stream's copy for it is
|
|
264
|
+
* dropped. A slot sized by its fallback has no room for a field the
|
|
265
|
+
* fallback does not have. Ignored when `initial` has no copy at all.
|
|
266
|
+
*/
|
|
267
|
+
fallbackDefinesSlots?: boolean;
|
|
268
|
+
/**
|
|
269
|
+
* Per-field provenance of the `initial` copy when it is finished creative
|
|
270
|
+
* (`serverRendered`). Overrides `initialSource` for the fields it names,
|
|
271
|
+
* for copy that mixes provenance, such as live copy for one field and the
|
|
272
|
+
* fallback for another.
|
|
273
|
+
*/
|
|
274
|
+
initialSources?: Partial<Record<CopyField, ContentSource>>;
|
|
275
|
+
/**
|
|
276
|
+
* The authored fallback, when `initial` is other copy (for example copy
|
|
277
|
+
* already produced for this view). It is what `keepFallback` lands.
|
|
278
|
+
* Defaults to `initial`.
|
|
279
|
+
*/
|
|
280
|
+
fallback?: Partial<Record<CopyField, string>>;
|
|
281
|
+
/** Injectable for tests; defaults to the platform timers and clock. */
|
|
178
282
|
setTimeout?: (fn: () => void, ms: number) => unknown;
|
|
179
283
|
clearTimeout?: (handle: unknown) => void;
|
|
284
|
+
now?: () => number;
|
|
180
285
|
}
|
|
181
286
|
type Listener = (state: CreativeState) => void;
|
|
182
287
|
/**
|
|
@@ -184,7 +289,7 @@ type Listener = (state: CreativeState) => void;
|
|
|
184
289
|
*
|
|
185
290
|
* Pure with respect to the DOM: it decides WHAT should be on screen and when,
|
|
186
291
|
* and never touches how. That is what lets the same choreography drive React,
|
|
187
|
-
* the iframe shell, and anything else without
|
|
292
|
+
* the iframe shell, and anything else without several copies of these rules.
|
|
188
293
|
*/
|
|
189
294
|
export declare class CopyChoreographer {
|
|
190
295
|
private readonly options;
|
|
@@ -193,59 +298,21 @@ export declare class CopyChoreographer {
|
|
|
193
298
|
private readonly holdMs;
|
|
194
299
|
private readonly setTimer;
|
|
195
300
|
private readonly clearTimer;
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
* TERMINATES (`done`/`failed`), not merely until the hold releases: any
|
|
199
|
-
* field the stream never delivers is restored from here, because a field
|
|
200
|
-
* the stream never took must end on its fallback — never on the blank the
|
|
201
|
-
* hold itself painted.
|
|
202
|
-
*/
|
|
301
|
+
private readonly releaseOn;
|
|
302
|
+
private readonly unfinishedPolicy;
|
|
203
303
|
private heldCopy;
|
|
204
304
|
private holdTimer;
|
|
205
|
-
/**
|
|
206
|
-
* The budget may restart ONCE per hold episode. Without a cap, a server
|
|
207
|
-
* emitting a `*-start` per field would extend the blank window by a full
|
|
208
|
-
* budget per field — proof of life is proof once, and a blank canvas is a
|
|
209
|
-
* cost the visitor pays, so it is not for sale twice.
|
|
210
|
-
*/
|
|
211
305
|
private holdRestarted;
|
|
212
|
-
|
|
306
|
+
private holdStartedAt;
|
|
307
|
+
private holdDueAt;
|
|
308
|
+
private readonly now;
|
|
309
|
+
private inView;
|
|
213
310
|
private pending;
|
|
214
|
-
|
|
215
|
-
private
|
|
216
|
-
/**
|
|
217
|
-
* Fields whose on-canvas text the visitor is already reading: restored
|
|
218
|
-
* fallback, `serverRendered` creative, or initial copy that was on screen
|
|
219
|
-
* when the stream opened (`initialIsOnScreen`). Live deltas buffer
|
|
220
|
-
* off-canvas for these. That the buffer then lands at `*-done` is a known
|
|
221
|
-
* bug against the file header's principle: shown text should stay.
|
|
222
|
-
*
|
|
223
|
-
* Gated on ON-SCREEN, never on non-empty: an earlier revision seeded it
|
|
224
|
-
* with every non-empty field at first paint, which is every field of
|
|
225
|
-
* every integration (fallback is required by doctrine), so `typing` was
|
|
226
|
-
* never true and paced streaming was dead. A held field's canvas is
|
|
227
|
-
* blank, and typing into blank is the entrance — that path must never
|
|
228
|
-
* seed here.
|
|
229
|
-
*/
|
|
230
|
-
private fallbackOnCanvas;
|
|
231
|
-
/** Fields the stream has opened with a `*-start` (or that received a chunk). */
|
|
311
|
+
private final;
|
|
312
|
+
private live;
|
|
232
313
|
private started;
|
|
233
|
-
/**
|
|
234
|
-
* First-paint copy, kept for the terminal restore: a field the stream
|
|
235
|
-
* opened but never closed must end on its WHOLE fallback, never on the
|
|
236
|
-
* half-typed fragment the dead stream left (`"Meet the web that"`). The
|
|
237
|
-
* hold's `heldCopy` only exists when the hold armed; typing over on-screen
|
|
238
|
-
* fallback needs this snapshot instead.
|
|
239
|
-
*/
|
|
240
|
-
private readonly initialCopy;
|
|
241
|
-
/**
|
|
242
|
-
* Fields the stream has closed with a `*-done`, including closes that
|
|
243
|
-
* arrived while the hold was still on and so never reached the canvas at the
|
|
244
|
-
* time. Consulted when the copy finally paints, so `settled` describes the
|
|
245
|
-
* field rather than the route it took.
|
|
246
|
-
*/
|
|
247
314
|
private closed;
|
|
248
|
-
|
|
315
|
+
private readonly initialCopy;
|
|
249
316
|
private disposed;
|
|
250
317
|
constructor(options?: ChoreographerOptions);
|
|
251
318
|
getState(): CreativeState;
|
|
@@ -254,125 +321,103 @@ export declare class CopyChoreographer {
|
|
|
254
321
|
private patch;
|
|
255
322
|
private patchField;
|
|
256
323
|
private hasInitialCopy;
|
|
324
|
+
private anyPending;
|
|
325
|
+
private isTerminal;
|
|
326
|
+
private fallbackFor;
|
|
257
327
|
/**
|
|
258
|
-
* Call when the stream request
|
|
328
|
+
* Call when live copy is coming: when the stream request opens, or earlier
|
|
329
|
+
* when a renderer already knows it will (a stream waiting on a shared
|
|
330
|
+
* variant). Idempotent.
|
|
259
331
|
*
|
|
260
|
-
* Arms the hold
|
|
261
|
-
* visitor
|
|
262
|
-
*
|
|
263
|
-
* the visitor is already looking at (`initialIsOnScreen`, `serverRendered`)
|
|
264
|
-
* is left alone — taking it away now would be the bait and switch.
|
|
332
|
+
* Arms the hold when there is fallback copy the visitor has not seen. Copy
|
|
333
|
+
* the visitor can already see (`initialIsOnScreen`) or finished creative
|
|
334
|
+
* (`serverRendered`) is final instead: nothing to hold, nothing to replace.
|
|
265
335
|
*/
|
|
266
336
|
streamOpened(): void;
|
|
267
337
|
/**
|
|
268
|
-
*
|
|
269
|
-
*
|
|
270
|
-
*
|
|
271
|
-
* resort, never a first draft.
|
|
272
|
-
*/
|
|
273
|
-
private onHoldExpired;
|
|
274
|
-
/**
|
|
275
|
-
* The stream is over and the canvas is final.
|
|
338
|
+
* Call when the slot is in view (already in view at mount counts). Only
|
|
339
|
+
* meaningful with `holdStartsInView`; otherwise the slot is in view from
|
|
340
|
+
* the start and this is a no-op.
|
|
276
341
|
*
|
|
277
|
-
*
|
|
278
|
-
*
|
|
279
|
-
*
|
|
280
|
-
* `typing: true` forever — a caret animating on a dead stream — and a
|
|
281
|
-
* post-`done` chunk+done replaced settled text WITHOUT incrementing
|
|
282
|
-
* `rewrites`, so the doctrine counter read 1 while the reader saw two
|
|
283
|
-
* changes. Everything after the terminal event is dropped.
|
|
342
|
+
* At that moment copy the stream already delivered takes the canvas —
|
|
343
|
+
* finished fields land whole, unfinished ones type from the buffer — and
|
|
344
|
+
* with nothing delivered the hold budget starts.
|
|
284
345
|
*/
|
|
285
|
-
|
|
346
|
+
slotInView(): void;
|
|
347
|
+
/** Milliseconds left before the hold's absolute maximum (2 × budget). */
|
|
348
|
+
private absoluteRemaining;
|
|
349
|
+
private armHold;
|
|
350
|
+
private startHoldTimer;
|
|
286
351
|
private clearHoldTimer;
|
|
287
352
|
/**
|
|
288
|
-
*
|
|
289
|
-
*
|
|
290
|
-
*
|
|
291
|
-
*
|
|
292
|
-
*
|
|
293
|
-
* blank — typing into a blank canvas is their entrance, and `heldCopy`
|
|
294
|
-
* outlives the hold so `done`/`failed` restore any field the stream never
|
|
295
|
-
* takes. On budget EXPIRY the stream has sputtered: whatever it delivered
|
|
296
|
-
* paints, and every undelivered field gets its fallback back now, because
|
|
297
|
-
* blank is never the resting state the budget is allowed to buy.
|
|
353
|
+
* Apply a hold expiry whose time has already passed but whose timer has
|
|
354
|
+
* not run: a background tab throttles timers, and a page restored from the
|
|
355
|
+
* back/forward cache resumes with them overdue. Copy events call this
|
|
356
|
+
* first, so text arriving after the deadline is never shown in place of
|
|
357
|
+
* the fallback. Renderers call it when the page is shown again. Idempotent.
|
|
298
358
|
*/
|
|
299
|
-
|
|
300
|
-
/** Budget spent with nothing delivered: the fallback comes back, composed. */
|
|
301
|
-
private restoreHeld;
|
|
359
|
+
checkDeadline(): void;
|
|
302
360
|
/**
|
|
303
|
-
*
|
|
304
|
-
*
|
|
305
|
-
*
|
|
306
|
-
*
|
|
361
|
+
* The budget ran out. While still holding: delivered copy takes the canvas
|
|
362
|
+
* and keeps streaming, and every field with nothing delivered gets its
|
|
363
|
+
* fallback — final. After a chunk already took the canvas: fields still
|
|
364
|
+
* blank get their fallback, final; fields the stream is writing continue.
|
|
307
365
|
*/
|
|
308
|
-
private
|
|
366
|
+
private onHoldExpired;
|
|
309
367
|
/**
|
|
310
|
-
*
|
|
368
|
+
* Hand the held canvas to the stream. Delivered fields take their text:
|
|
369
|
+
* finished ones whole, unfinished ones typing on. `reason` decides the
|
|
370
|
+
* rest:
|
|
311
371
|
*
|
|
312
|
-
*
|
|
313
|
-
*
|
|
314
|
-
*
|
|
315
|
-
*
|
|
316
|
-
*
|
|
317
|
-
*
|
|
318
|
-
* impression beacon's `chromeMarked`. Leaving it would claim "adapted" on
|
|
319
|
-
* copy nobody adapted, on all of them at once.
|
|
320
|
-
*
|
|
321
|
-
* Provenance must describe what is on screen, not what the server meant to
|
|
322
|
-
* send. Under-claiming adapted copy costs credit; claiming authored copy is
|
|
323
|
-
* adapted is a false statement, and that is the worse of the two.
|
|
324
|
-
*
|
|
325
|
-
* `fallbackOnCanvas` is NOT the whole set. A chunk-driven release leaves
|
|
326
|
-
* undelivered fields blank — restoring them mid-stream would stage the
|
|
327
|
-
* wipe-and-retype this machine exists to prevent — so they never enter that
|
|
328
|
-
* set, and are instead restored from `heldCopy` by the terminal handlers.
|
|
329
|
-
* That is the likeliest degradation there is: the server opens a field,
|
|
330
|
-
* then times out. Demoting only `fallbackOnCanvas` left exactly that path
|
|
331
|
-
* rendering "adapted for you" over the customer's own sentence.
|
|
332
|
-
*
|
|
333
|
-
* So callers pass the fields they restored, and both sets are demoted.
|
|
372
|
+
* - "chunk": the first text arrived in time. Undelivered fields stay
|
|
373
|
+
* blank and reserved; the budget keeps running for them.
|
|
374
|
+
* - "view": the slot just came into view with copy already buffered.
|
|
375
|
+
* - "expiry": the budget ran out; undelivered fields get their fallback,
|
|
376
|
+
* final.
|
|
377
|
+
* - "terminal": the stream ended; the terminal handler settles the rest.
|
|
334
378
|
*/
|
|
335
|
-
private
|
|
379
|
+
private releaseHold;
|
|
380
|
+
/** Budget spent with nothing delivered: every fallback lands once, final. */
|
|
381
|
+
private restoreHeld;
|
|
336
382
|
/** True once any field on canvas actually carries adapted copy. */
|
|
337
383
|
private hasAdaptedCopyOnCanvas;
|
|
384
|
+
/** Copy events that arrive before the stream was opened open it first. */
|
|
385
|
+
private ensureOpened;
|
|
338
386
|
/**
|
|
339
|
-
* A field opened
|
|
340
|
-
*
|
|
341
|
-
* The canvas changes hands on the first CHUNK, not here: the delivery mode
|
|
342
|
-
* is recorded on the field but no longer consulted for hold release. All a
|
|
343
|
-
* start does while holding is RESTART the fallback budget, because the
|
|
344
|
-
* stream has just proven it is alive and the old budget was measuring a
|
|
345
|
-
* connection that had not connected yet.
|
|
387
|
+
* A field opened: proof of life, and provenance. Restarts the hold budget
|
|
388
|
+
* once if it is running. Dropped for a final field.
|
|
346
389
|
*/
|
|
347
390
|
copyStart(field: CopyField, mode: CopyMode | null, source: ContentSource): void;
|
|
348
391
|
/**
|
|
349
|
-
* A delta landed.
|
|
350
|
-
*
|
|
351
|
-
* called itself. While the field shows fallback copy the visitor can see,
|
|
352
|
-
* deltas buffer off-canvas and never paint delta by delta. (That the
|
|
353
|
-
* buffer lands once at `*-done` is a known bug, file header: shown
|
|
354
|
-
* fallback should be final.)
|
|
392
|
+
* A delta landed. While holding (and in view) the first text takes the
|
|
393
|
+
* canvas; on a live field it extends the text. Dropped for a final field.
|
|
355
394
|
*/
|
|
356
395
|
copyChunk(field: CopyField, text: string): void;
|
|
357
396
|
/**
|
|
358
|
-
* A field closed.
|
|
359
|
-
*
|
|
360
|
-
*
|
|
397
|
+
* A field closed. Its text completes what the field shows: it lands whole
|
|
398
|
+
* on a blank field and finishes a typed one. It never replaces different
|
|
399
|
+
* visible text — if it does not extend what was typed, the typed text
|
|
400
|
+
* stands (and development builds warn). For a final field this only marks
|
|
401
|
+
* it settled.
|
|
361
402
|
*/
|
|
362
403
|
copyDone(field: CopyField, text: string): void;
|
|
404
|
+
/**
|
|
405
|
+
* The renderer will not show this field's copy: it does not fit the slot
|
|
406
|
+
* its fallback reserved. The fallback lands instead and is final for the
|
|
407
|
+
* view; later copy for the field is dropped. Call it before the copy is
|
|
408
|
+
* painted (a renderer measures before the browser paints), never for text
|
|
409
|
+
* the visitor has seen.
|
|
410
|
+
*/
|
|
411
|
+
keepFallback(field: CopyField): void;
|
|
363
412
|
themeReady(colors: Record<string, string>, sources: Record<string, string>): void;
|
|
364
413
|
layoutReady(layout: string, manifest?: unknown): void;
|
|
365
414
|
/**
|
|
366
415
|
* Absence of this event means KEEP THE BAKED THEME WASH. It does not mean
|
|
367
|
-
* clear the art layer — the server omits it when there is no creative image
|
|
368
|
-
* and a client that cleared on absence would blank the hero on exactly the
|
|
369
|
-
* campaigns that never had a picture.
|
|
416
|
+
* clear the art layer — the server omits it when there is no creative image.
|
|
370
417
|
*
|
|
371
|
-
* The patch waits for the browser to DECODE the image first
|
|
372
|
-
*
|
|
373
|
-
*
|
|
374
|
-
* art — the contrast tear. On failure the patch still lands (the consumer's
|
|
375
|
-
* own loading treatment owns that case); outside a browser it is immediate.
|
|
418
|
+
* The patch waits for the browser to DECODE the image first, so a new
|
|
419
|
+
* background never paints before its pixels exist. On failure the patch
|
|
420
|
+
* still lands; outside a browser it is immediate.
|
|
376
421
|
*/
|
|
377
422
|
backgroundReady(bgImage: string): void;
|
|
378
423
|
sequenceReady(frames: string[], heroIndex: number, fps: number): void;
|
|
@@ -380,19 +425,17 @@ export declare class CopyChoreographer {
|
|
|
380
425
|
ctaStyleReady(style: unknown): void;
|
|
381
426
|
ctaReady(text: string, style: unknown, action?: string): void;
|
|
382
427
|
/**
|
|
383
|
-
*
|
|
384
|
-
*
|
|
385
|
-
*
|
|
428
|
+
* Settle every field at the end of the stream. Final and closed fields stay
|
|
429
|
+
* as they are; a field that never painted gets its fallback; a field the
|
|
430
|
+
* stream was still writing goes through the `UnfinishedCopyPolicy`.
|
|
386
431
|
*/
|
|
432
|
+
private settleFields;
|
|
433
|
+
/** Terminal, success. */
|
|
387
434
|
done(meta: Record<string, unknown>): void;
|
|
388
435
|
/**
|
|
389
|
-
*
|
|
390
|
-
*
|
|
391
|
-
*
|
|
392
|
-
* copy; a field the stream opened but never finished is repainted with its
|
|
393
|
-
* WHOLE fallback and demoted to `static`; a still-blank field gets its
|
|
394
|
-
* fallback back. The worst honest outcome is that the creative simply
|
|
395
|
-
* stopped adapting, which looks like a normal page.
|
|
436
|
+
* Terminal, failure. No failure blanks the canvas: copy that closed stays,
|
|
437
|
+
* a blank field gets its fallback, and a field the stream was still writing
|
|
438
|
+
* goes through the `UnfinishedCopyPolicy`.
|
|
396
439
|
*/
|
|
397
440
|
failed(message: string): void;
|
|
398
441
|
/** Release timers. Safe to call twice. */
|