@storystick/preview 0.0.0-stage → 0.2.0-rc.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/LICENSE +112 -0
- package/PUBLIC-API.md +55 -0
- package/README.md +42 -2
- package/dist/.source-hash +1 -0
- package/dist/authoring/runtime-contract.d.ts +89 -0
- package/dist/authoring/runtime-contract.js +28 -0
- package/dist/channel-taxonomy.d.ts +17 -0
- package/dist/channel-taxonomy.js +46 -0
- package/dist/context-command.d.ts +18 -0
- package/dist/context-command.js +38 -0
- package/dist/dev/suspicious-codeword-detector.d.ts +15 -0
- package/dist/dev/suspicious-codeword-detector.js +220 -0
- package/dist/dev/suspicious-codeword-detector.noop.d.ts +3 -0
- package/dist/dev/suspicious-codeword-detector.noop.js +4 -0
- package/dist/framework/browser-context.d.ts +2 -0
- package/dist/framework/browser-context.js +2 -0
- package/dist/framework/generation-refresh.d.ts +23 -0
- package/dist/framework/generation-refresh.js +82 -0
- package/dist/framework/generation-signal.d.ts +8 -0
- package/dist/framework/generation-signal.js +17 -0
- package/dist/framework/projection-generation-watch.d.ts +5 -0
- package/dist/framework/projection-generation-watch.js +53 -0
- package/dist/framework/render-receipt.d.ts +8 -0
- package/dist/framework/render-receipt.js +10 -0
- package/dist/framework/runtime-adapter-store.d.ts +30 -0
- package/dist/framework/runtime-adapter-store.js +81 -0
- package/dist/grant-coordinator.d.ts +53 -0
- package/dist/grant-coordinator.js +329 -0
- package/dist/grant-lifecycle.d.ts +52 -0
- package/dist/grant-lifecycle.js +74 -0
- package/dist/identity/__test-adapter.d.ts +34 -0
- package/dist/identity/__test-adapter.js +59 -0
- package/dist/identity/browser-context-gate.d.ts +4 -0
- package/dist/identity/browser-context-gate.js +22 -0
- package/dist/identity/browser-context.d.ts +13 -0
- package/dist/identity/browser-context.js +22 -0
- package/dist/identity/carriers/postmessage.d.ts +84 -0
- package/dist/identity/carriers/postmessage.js +177 -0
- package/dist/identity/carriers/storage-state.d.ts +41 -0
- package/dist/identity/carriers/storage-state.js +99 -0
- package/dist/identity/carriers/storage.d.ts +39 -0
- package/dist/identity/carriers/storage.js +182 -0
- package/dist/identity/carriers/url.d.ts +19 -0
- package/dist/identity/carriers/url.js +38 -0
- package/dist/identity/identity-state-machine.d.ts +70 -0
- package/dist/identity/identity-state-machine.js +244 -0
- package/dist/identity/identity-types.d.ts +192 -0
- package/dist/identity/identity-types.js +22 -0
- package/dist/identity/index.d.ts +18 -0
- package/dist/identity/index.js +16 -0
- package/dist/identity/preview-cookie.d.ts +9 -0
- package/dist/identity/preview-cookie.js +12 -0
- package/dist/identity/preview-draft.d.ts +6 -0
- package/dist/identity/preview-draft.js +25 -0
- package/dist/identity/preview-identity.d.ts +28 -0
- package/dist/identity/preview-identity.js +84 -0
- package/dist/identity/preview-scope.d.ts +28 -0
- package/dist/identity/preview-scope.js +47 -0
- package/dist/identity/resolver.d.ts +110 -0
- package/dist/identity/resolver.js +334 -0
- package/dist/identity/session-query.d.ts +4 -0
- package/dist/identity/session-query.js +19 -0
- package/dist/index.d.ts +90 -0
- package/dist/index.js +788 -0
- package/dist/interaction-mode.d.ts +58 -0
- package/dist/interaction-mode.js +93 -0
- package/dist/nav-handler.d.ts +95 -0
- package/dist/nav-handler.js +84 -0
- package/dist/overlay-label.d.ts +79 -0
- package/dist/overlay-label.js +73 -0
- package/dist/provenance/anchor-map.d.ts +95 -0
- package/dist/provenance/anchor-map.js +177 -0
- package/dist/provenance/carrier-plan.d.ts +51 -0
- package/dist/provenance/carrier-plan.js +90 -0
- package/dist/provenance/coded-text.d.ts +20 -0
- package/dist/provenance/coded-text.js +302 -0
- package/dist/provenance/direct-edit-locator.d.ts +29 -0
- package/dist/provenance/direct-edit-locator.js +218 -0
- package/dist/provenance/editable-leaf.d.ts +134 -0
- package/dist/provenance/editable-leaf.js +483 -0
- package/dist/provenance/editor-target.d.ts +29 -0
- package/dist/provenance/editor-target.js +46 -0
- package/dist/provenance/field-leaf-walker.d.ts +21 -0
- package/dist/provenance/field-leaf-walker.js +98 -0
- package/dist/provenance/frame-dependencies.d.ts +212 -0
- package/dist/provenance/frame-dependencies.js +550 -0
- package/dist/provenance/index.d.ts +26 -0
- package/dist/provenance/index.js +26 -0
- package/dist/provenance/local-events.d.ts +1 -0
- package/dist/provenance/local-events.js +1 -0
- package/dist/provenance/markers.d.ts +21 -0
- package/dist/provenance/markers.js +70 -0
- package/dist/provenance/multi-frame-anchor-map.d.ts +54 -0
- package/dist/provenance/multi-frame-anchor-map.js +109 -0
- package/dist/provenance/normalize.d.ts +2 -0
- package/dist/provenance/normalize.js +19 -0
- package/dist/provenance/overlay-focus-echo.d.ts +17 -0
- package/dist/provenance/overlay-focus-echo.js +16 -0
- package/dist/provenance/overlay-projection.d.ts +65 -0
- package/dist/provenance/overlay-projection.js +201 -0
- package/dist/provenance/owner-map.d.ts +55 -0
- package/dist/provenance/owner-map.js +239 -0
- package/dist/provenance/read-carrier.d.ts +22 -0
- package/dist/provenance/read-carrier.js +92 -0
- package/dist/provenance/recorder.d.ts +38 -0
- package/dist/provenance/recorder.js +82 -0
- package/dist/provenance/render-context.d.ts +106 -0
- package/dist/provenance/render-context.js +338 -0
- package/dist/provenance/reveal-contract.d.ts +44 -0
- package/dist/provenance/reveal-contract.js +1 -0
- package/dist/provenance/richtext-slice-embed-address.d.ts +20 -0
- package/dist/provenance/richtext-slice-embed-address.js +35 -0
- package/dist/provenance/runtime-script.d.ts +1 -0
- package/dist/provenance/runtime-script.js +2217 -0
- package/dist/provenance/sidecar-store.d.ts +63 -0
- package/dist/provenance/sidecar-store.js +151 -0
- package/dist/provenance/sidecar.d.ts +34 -0
- package/dist/provenance/sidecar.js +227 -0
- package/dist/provenance/slice-model.d.ts +28 -0
- package/dist/provenance/slice-model.js +86 -0
- package/dist/provenance/source-identity.d.ts +3 -0
- package/dist/provenance/source-identity.js +7 -0
- package/dist/provenance/source-map-materializer.d.ts +76 -0
- package/dist/provenance/source-map-materializer.js +286 -0
- package/dist/provenance/source-registry.d.ts +103 -0
- package/dist/provenance/source-registry.js +562 -0
- package/dist/provenance/transformed-output-gate.d.ts +161 -0
- package/dist/provenance/transformed-output-gate.js +171 -0
- package/dist/provenance/types.d.ts +102 -0
- package/dist/provenance/types.js +1 -0
- package/dist/refresh-policy.d.ts +11 -0
- package/dist/refresh-policy.js +14 -0
- package/dist/reveal-target.d.ts +8 -0
- package/dist/reveal-target.js +47 -0
- package/dist/route-paths.d.ts +11 -0
- package/dist/route-paths.js +14 -0
- package/dist/router-subscriber.d.ts +92 -0
- package/dist/router-subscriber.js +44 -0
- package/dist/runtime/anchor-types.d.ts +5 -0
- package/dist/runtime/anchor-types.js +1 -0
- package/dist/runtime/authority-store.d.ts +31 -0
- package/dist/runtime/authority-store.js +56 -0
- package/dist/runtime/boot.d.ts +52 -0
- package/dist/runtime/boot.js +379 -0
- package/dist/runtime/content-refresh.d.ts +30 -0
- package/dist/runtime/content-refresh.js +68 -0
- package/dist/runtime/emit-runtime-error.d.ts +51 -0
- package/dist/runtime/emit-runtime-error.js +64 -0
- package/dist/runtime/field-proxy.d.ts +112 -0
- package/dist/runtime/field-proxy.js +471 -0
- package/dist/runtime/form-contract-reporter.d.ts +99 -0
- package/dist/runtime/form-contract-reporter.js +391 -0
- package/dist/runtime/invalidation-adapter.d.ts +17 -0
- package/dist/runtime/invalidation-adapter.js +42 -0
- package/dist/runtime/live-query-overlay.d.ts +39 -0
- package/dist/runtime/live-query-overlay.js +345 -0
- package/dist/runtime/momentary-navigate.d.ts +16 -0
- package/dist/runtime/momentary-navigate.js +106 -0
- package/dist/runtime/observation-scheduler.d.ts +15 -0
- package/dist/runtime/observation-scheduler.js +42 -0
- package/dist/runtime/perf-metrics.d.ts +28 -0
- package/dist/runtime/perf-metrics.js +90 -0
- package/dist/runtime/preview-interaction-policy.d.ts +1 -0
- package/dist/runtime/preview-interaction-policy.js +103 -0
- package/dist/runtime/preview-overlay-painter.d.ts +27 -0
- package/dist/runtime/preview-overlay-painter.js +1732 -0
- package/dist/runtime/preview-overlay-sidecar.d.ts +26 -0
- package/dist/runtime/preview-overlay-sidecar.js +45 -0
- package/dist/runtime/preview-perspective-alignment.d.ts +15 -0
- package/dist/runtime/preview-perspective-alignment.js +40 -0
- package/dist/runtime/preview-perspective-intent-state.d.ts +23 -0
- package/dist/runtime/preview-perspective-intent-state.js +5 -0
- package/dist/runtime/preview-transition-state.d.ts +142 -0
- package/dist/runtime/preview-transition-state.js +257 -0
- package/dist/runtime/render-mode-state.d.ts +51 -0
- package/dist/runtime/render-mode-state.js +76 -0
- package/dist/runtime/request-id.d.ts +4 -0
- package/dist/runtime/request-id.js +8 -0
- package/dist/runtime/resolve-schema-field.d.ts +99 -0
- package/dist/runtime/resolve-schema-field.js +356 -0
- package/dist/runtime/runtime-error-dispatch.d.ts +23 -0
- package/dist/runtime/runtime-error-dispatch.js +75 -0
- package/dist/runtime/schema-store.d.ts +121 -0
- package/dist/runtime/schema-store.js +667 -0
- package/dist/runtime/snapshot-assembly.d.ts +104 -0
- package/dist/runtime/snapshot-assembly.js +553 -0
- package/dist/runtime/snapshot-reporter.d.ts +55 -0
- package/dist/runtime/snapshot-reporter.js +375 -0
- package/dist/runtime/standalone-overlay-controller.d.ts +33 -0
- package/dist/runtime/standalone-overlay-controller.js +281 -0
- package/dist/runtime/store.d.ts +307 -0
- package/dist/runtime/store.js +1026 -0
- package/dist/runtime/windowed-minimap-projection.d.ts +28 -0
- package/dist/runtime/windowed-minimap-projection.js +189 -0
- package/dist/runtime/workspace-shortcuts.d.ts +30 -0
- package/dist/runtime/workspace-shortcuts.js +188 -0
- package/dist/sdk-error-copy.d.ts +48 -0
- package/dist/sdk-error-copy.js +182 -0
- package/dist/select-cursor.d.ts +2 -0
- package/dist/select-cursor.js +23 -0
- package/dist/server/admission-cookie.d.ts +38 -0
- package/dist/server/admission-cookie.js +122 -0
- package/dist/server/control-client.d.ts +29 -0
- package/dist/server/control-client.js +22 -0
- package/dist/server/control-errors.d.ts +22 -0
- package/dist/server/control-errors.js +165 -0
- package/dist/server/draft-route.d.ts +12 -0
- package/dist/server/draft-route.js +45 -0
- package/dist/server/extract-token.d.ts +30 -0
- package/dist/server/extract-token.js +98 -0
- package/dist/server/grant.d.ts +128 -0
- package/dist/server/grant.js +395 -0
- package/dist/server/ordinary-read-provenance.d.ts +11 -0
- package/dist/server/ordinary-read-provenance.js +124 -0
- package/dist/server/preview-read-metadata.d.ts +10 -0
- package/dist/server/preview-read-metadata.js +27 -0
- package/dist/server/provenance-frame-builder.d.ts +53 -0
- package/dist/server/provenance-frame-builder.js +102 -0
- package/dist/server/provenance-runtime.d.ts +70 -0
- package/dist/server/provenance-runtime.js +46 -0
- package/dist/server/read-context-command.d.ts +11 -0
- package/dist/server/read-context-command.js +53 -0
- package/dist/server/read-identity.d.ts +3 -0
- package/dist/server/read-identity.js +63 -0
- package/dist/server/read-lifecycle.d.ts +17 -0
- package/dist/server/read-lifecycle.js +52 -0
- package/dist/server/render-frame-provenance.d.ts +133 -0
- package/dist/server/render-frame-provenance.js +676 -0
- package/dist/server/render-id.d.ts +15 -0
- package/dist/server/render-id.js +22 -0
- package/dist/server/route-document-provenance.d.ts +77 -0
- package/dist/server/route-document-provenance.js +1327 -0
- package/dist/server/route-session-cookie.d.ts +33 -0
- package/dist/server/route-session-cookie.js +54 -0
- package/dist/session-projections.d.ts +33 -0
- package/dist/session-projections.js +43 -0
- package/dist/session-subscriber.d.ts +124 -0
- package/dist/session-subscriber.js +79 -0
- package/dist/sha256-fallback.d.ts +30 -0
- package/dist/sha256-fallback.js +126 -0
- package/dist/types.d.ts +446 -0
- package/dist/types.js +85 -0
- package/dist/validators.d.ts +61 -0
- package/dist/validators.js +110 -0
- package/package.json +206 -3
|
@@ -0,0 +1,471 @@
|
|
|
1
|
+
import { isPreviewBrowserContext } from '../identity/browser-context.js';
|
|
2
|
+
// ─────────────────────────────────────────────
|
|
3
|
+
// Activation gate (production zero-overhead — ADR-125 D3, Lane G)
|
|
4
|
+
// ─────────────────────────────────────────────
|
|
5
|
+
/**
|
|
6
|
+
* Field tracking is active inside the preview iframe or on a top-level page
|
|
7
|
+
* carrying the explicit scrubbed `_ss_session` selector. In a
|
|
8
|
+
* production customer app the package isn't even loaded; this is the
|
|
9
|
+
* belt-and-braces second line so `wrapWithProxy` is a referential
|
|
10
|
+
* identity no-op everywhere else. Overridable for tests.
|
|
11
|
+
*/
|
|
12
|
+
let trackingForced = null;
|
|
13
|
+
export function __setFieldTrackingForcedForTests(v) {
|
|
14
|
+
trackingForced = v;
|
|
15
|
+
}
|
|
16
|
+
export function isFieldTrackingActive() {
|
|
17
|
+
if (trackingForced !== null)
|
|
18
|
+
return trackingForced;
|
|
19
|
+
return isPreviewBrowserContext();
|
|
20
|
+
}
|
|
21
|
+
// ─────────────────────────────────────────────
|
|
22
|
+
// Module model — accessed leaves + registered roots
|
|
23
|
+
// ─────────────────────────────────────────────
|
|
24
|
+
/** Deduped accessed-leaf set (lazy path). Keyed `${docId}\x00${path}` so
|
|
25
|
+
* the same path under two docs (cross-doc) is kept distinct and a
|
|
26
|
+
* re-access overwrites rather than unbounded-grows. */
|
|
27
|
+
const accessed = new Map();
|
|
28
|
+
/**
|
|
29
|
+
* Roots registered by `wrapWithProxy`, keyed by `(docId, basePath)` —
|
|
30
|
+
* the LATEST `data` for a given scope REPLACES the prior one. This is
|
|
31
|
+
* the load-bearing leak fix (cross-model /review P1, Claude + Codex):
|
|
32
|
+
* `useStorystickQuery` produces a fresh `QueryResult` object on every
|
|
33
|
+
* Convex content revision, so an identity-keyed append-only list grows
|
|
34
|
+
* unbounded for the iframe's lifetime — memory leak AND, once the
|
|
35
|
+
* `COLLECT_MAX_LEAVES` cap is hit by stale earlier roots, the CURRENT
|
|
36
|
+
* doc's leaves stop being emitted → field overlays freeze/vanish
|
|
37
|
+
* mid-edit-session (the exact "feels broken" the ADR exists to
|
|
38
|
+
* prevent). Keying by scope bounds `roots` to the small, stable set of
|
|
39
|
+
* distinct (docId, basePath) scopes on the page and always reflects
|
|
40
|
+
* latest content. `MAX_ROOTS` is a hard belt-and-braces cap (a
|
|
41
|
+
* pathological page with thousands of distinct cross-doc scopes evicts
|
|
42
|
+
* oldest-first rather than growing without bound). `clearTrackedFields`
|
|
43
|
+
* is the per-generation / test reset, NOT a per-publish call (the
|
|
44
|
+
* matcher does NOT clear each pass — scope-keying makes that
|
|
45
|
+
* unnecessary and a per-publish clear would race the customer render
|
|
46
|
+
* that registers roots). */
|
|
47
|
+
const MAX_ROOTS = 256;
|
|
48
|
+
const roots = [];
|
|
49
|
+
/**
|
|
50
|
+
* ADR-126 — roots sourced from the RSC render-path field-model island
|
|
51
|
+
* (`<script data-ss-model>` emitted server-side by `@storystick/react`'s
|
|
52
|
+
* `<SliceZone>`). Kept in a SEPARATE list from the Proxy `roots`, NOT
|
|
53
|
+
* merged into it, because both sources legitimately register the same
|
|
54
|
+
* `(docId, '')` scope on a hybrid page (RSC shell + client islands): a
|
|
55
|
+
* single scope-keyed list would let the lazy Proxy subset REPLACE the
|
|
56
|
+
* full server-resolved island root (or vice versa) — codex outside-voice
|
|
57
|
+
* #7, "poorer must not replace richer". `collectFieldModel()` walks the
|
|
58
|
+
* UNION (`roots` ∪ `islandRoots`); the per-leaf `out` Map dedupes
|
|
59
|
+
* identical leaves, so a leaf present in EITHER source survives and no
|
|
60
|
+
* source can erase another's coverage. Same `MAX_ROOTS` discipline. */
|
|
61
|
+
const islandRoots = [];
|
|
62
|
+
/** Proxy identity cache — same target ⇒ same Proxy, so wrapping a
|
|
63
|
+
* reference-stable object (slice-zone hands the same `hydrated` ref
|
|
64
|
+
* when nothing to hydrate) does not break `React.memo`. */
|
|
65
|
+
const proxyCache = new WeakMap();
|
|
66
|
+
function leafKey(docId, path) {
|
|
67
|
+
return `${docId ?? ''}\x00${path}`;
|
|
68
|
+
}
|
|
69
|
+
function recordLeaf(path, value, docId) {
|
|
70
|
+
// Only primitives are leaves; `path === ''` would be a root primitive
|
|
71
|
+
// (not a field) — never record it.
|
|
72
|
+
if (path === '')
|
|
73
|
+
return;
|
|
74
|
+
accessed.set(leafKey(docId, path), { path, value, docId });
|
|
75
|
+
}
|
|
76
|
+
// ─────────────────────────────────────────────
|
|
77
|
+
// Shape predicates
|
|
78
|
+
// ─────────────────────────────────────────────
|
|
79
|
+
function isPlainPrimitive(v) {
|
|
80
|
+
return (v === null ||
|
|
81
|
+
v === undefined ||
|
|
82
|
+
typeof v === 'string' ||
|
|
83
|
+
typeof v === 'number' ||
|
|
84
|
+
typeof v === 'boolean');
|
|
85
|
+
}
|
|
86
|
+
/** A `QueryResult` document scope: `{ documentId, fields, ... }`. The
|
|
87
|
+
* presence of a string `documentId` is the discriminator (mirrors
|
|
88
|
+
* `@storystick/client` `QueryResult`). */
|
|
89
|
+
function isQueryResult(v) {
|
|
90
|
+
return (v !== null &&
|
|
91
|
+
typeof v === 'object' &&
|
|
92
|
+
typeof v.documentId === 'string' &&
|
|
93
|
+
v.documentId.length > 0);
|
|
94
|
+
}
|
|
95
|
+
/** Non-content `QueryResult` metadata keys — returned raw, never a
|
|
96
|
+
* recordable content path, never recursed as content. `fields` and
|
|
97
|
+
* `resolved` are handled specially (not here). */
|
|
98
|
+
const QUERY_RESULT_META = new Set([
|
|
99
|
+
'documentId',
|
|
100
|
+
'storyType',
|
|
101
|
+
'snapshotId',
|
|
102
|
+
'publishedAt',
|
|
103
|
+
'resolvedPathname',
|
|
104
|
+
'slug',
|
|
105
|
+
'isRoutable',
|
|
106
|
+
'_zoneMappings',
|
|
107
|
+
]);
|
|
108
|
+
// ─────────────────────────────────────────────
|
|
109
|
+
// Path grammar (ADR-068 walker-canonical)
|
|
110
|
+
// ─────────────────────────────────────────────
|
|
111
|
+
function joinKey(base, key) {
|
|
112
|
+
return base === '' ? key : `${base}.${key}`;
|
|
113
|
+
}
|
|
114
|
+
function joinIndex(base, index) {
|
|
115
|
+
return `${base}[${index}]`;
|
|
116
|
+
}
|
|
117
|
+
// ─────────────────────────────────────────────
|
|
118
|
+
// Recursive wrap
|
|
119
|
+
// ─────────────────────────────────────────────
|
|
120
|
+
const MAX_DEPTH = 16;
|
|
121
|
+
/**
|
|
122
|
+
* Wrap a query-builder result. Returns `data` unchanged (by reference,
|
|
123
|
+
* zero allocation) when tracking is inactive or `data` is a primitive.
|
|
124
|
+
* Registers the root for the eager `collectFieldModel()` walk and
|
|
125
|
+
* returns a recursive get-trap Proxy for the lazy accessed-leaf path.
|
|
126
|
+
*
|
|
127
|
+
* `docId` scopes recorded paths; pass the document's `documentId` (or
|
|
128
|
+
* omit — `collectFieldModel` re-derives it when the root is itself a
|
|
129
|
+
* `QueryResult`). `basePath` prefixes recorded paths (a sliceZone item
|
|
130
|
+
* wrap passes e.g. `sections[2]`); defaults to the content root `''`.
|
|
131
|
+
*/
|
|
132
|
+
export function wrapWithProxy(data, docId = null, basePath = '') {
|
|
133
|
+
if (!isFieldTrackingActive())
|
|
134
|
+
return data;
|
|
135
|
+
if (data === null || typeof data !== 'object')
|
|
136
|
+
return data;
|
|
137
|
+
// Register the root once per (identity, docId, basePath) so the eager
|
|
138
|
+
// walk has it even when no get-trap ever fires (RSC).
|
|
139
|
+
registerRoot(data, docId, basePath);
|
|
140
|
+
return wrapNode(data, docId, basePath, 0);
|
|
141
|
+
}
|
|
142
|
+
function registerRoot(data, docId, basePath) {
|
|
143
|
+
for (const r of roots) {
|
|
144
|
+
if (r.docId === docId && r.basePath === basePath) {
|
|
145
|
+
// Same scope re-registered (new content revision → new object):
|
|
146
|
+
// REPLACE the data in place. No append → no unbounded growth,
|
|
147
|
+
// model always reflects latest content. Identity-equal is a
|
|
148
|
+
// no-op (the common stable-render case).
|
|
149
|
+
r.data = data;
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
roots.push({ data, docId, basePath });
|
|
154
|
+
// Hard cap (pathological many-distinct-scope page) — evict oldest.
|
|
155
|
+
if (roots.length > MAX_ROOTS)
|
|
156
|
+
roots.shift();
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Wrap one node. Objects/arrays return a recursive Proxy; primitives are
|
|
160
|
+
* returned raw (the parent's get-trap recorded them). A `QueryResult`
|
|
161
|
+
* boundary re-scopes: `.fields` stays in-doc with the content path,
|
|
162
|
+
* `.resolved[field]` opens the child doc scope (docId = child
|
|
163
|
+
* `documentId`, path reset), metadata keys pass through raw.
|
|
164
|
+
*/
|
|
165
|
+
function wrapNode(node, docId, path, depth) {
|
|
166
|
+
if (node === null || typeof node !== 'object')
|
|
167
|
+
return node;
|
|
168
|
+
if (depth >= MAX_DEPTH)
|
|
169
|
+
return node;
|
|
170
|
+
// Identity cache — same target+scope ⇒ same Proxy (memo-stable).
|
|
171
|
+
const cacheKey = `${docId ?? ''}\x00${path}\x00${depth >= MAX_DEPTH ? 'L' : ''}`;
|
|
172
|
+
let perTarget = proxyCache.get(node);
|
|
173
|
+
if (perTarget) {
|
|
174
|
+
const hit = perTarget.get(cacheKey);
|
|
175
|
+
if (hit !== undefined)
|
|
176
|
+
return hit;
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
perTarget = new Map();
|
|
180
|
+
proxyCache.set(node, perTarget);
|
|
181
|
+
}
|
|
182
|
+
const isQR = isQueryResult(node);
|
|
183
|
+
const isArr = Array.isArray(node);
|
|
184
|
+
const proxy = new Proxy(node, {
|
|
185
|
+
get(target, prop, receiver) {
|
|
186
|
+
const raw = Reflect.get(target, prop, receiver);
|
|
187
|
+
if (typeof prop === 'symbol')
|
|
188
|
+
return raw;
|
|
189
|
+
if (typeof raw === 'function')
|
|
190
|
+
return raw;
|
|
191
|
+
// ── QueryResult boundary ──────────────────────────────
|
|
192
|
+
if (isQR) {
|
|
193
|
+
const childDocId = target.documentId;
|
|
194
|
+
if (prop === 'fields') {
|
|
195
|
+
// Content root of THIS doc — path stays, docId becomes this
|
|
196
|
+
// doc's id (the root may have been registered with a null
|
|
197
|
+
// docId; the QueryResult is authoritative).
|
|
198
|
+
return wrapNode(raw, childDocId, path, depth + 1);
|
|
199
|
+
}
|
|
200
|
+
if (prop === 'resolved') {
|
|
201
|
+
// The resolved map itself is a traversal node; each key is a
|
|
202
|
+
// resolved-ref boundary handled one level down.
|
|
203
|
+
return wrapResolvedMap(raw, depth + 1);
|
|
204
|
+
}
|
|
205
|
+
if (QUERY_RESULT_META.has(prop))
|
|
206
|
+
return raw;
|
|
207
|
+
// Any other key on a QueryResult: treat as content under the
|
|
208
|
+
// doc scope (defensive — schemas evolve).
|
|
209
|
+
return descend(raw, childDocId, joinKey(path, prop), depth);
|
|
210
|
+
}
|
|
211
|
+
// ── Array hop ─────────────────────────────────────────
|
|
212
|
+
if (isArr) {
|
|
213
|
+
const idx = typeof prop === 'string' ? Number(prop) : Number.NaN;
|
|
214
|
+
if (Number.isInteger(idx) && idx >= 0) {
|
|
215
|
+
return descend(raw, docId, joinIndex(path, idx), depth);
|
|
216
|
+
}
|
|
217
|
+
return raw; // length, iterator helpers, etc.
|
|
218
|
+
}
|
|
219
|
+
// ── Plain content object ──────────────────────────────
|
|
220
|
+
return descend(raw, docId, joinKey(path, prop), depth);
|
|
221
|
+
},
|
|
222
|
+
});
|
|
223
|
+
perTarget.set(cacheKey, proxy);
|
|
224
|
+
return proxy;
|
|
225
|
+
}
|
|
226
|
+
/** Resolve one property value: record if primitive, recurse if not. */
|
|
227
|
+
function descend(value, docId, childPath, depth) {
|
|
228
|
+
if (isPlainPrimitive(value)) {
|
|
229
|
+
recordLeaf(childPath, value, docId);
|
|
230
|
+
return value;
|
|
231
|
+
}
|
|
232
|
+
return wrapNode(value, docId, childPath, depth + 1);
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* The `.resolved` map. Each key is a resolved-ref boundary: the value is
|
|
236
|
+
* a child `QueryResult` (scalar ref / `.resolve()`) or an array of them
|
|
237
|
+
* (`.resolveSlices()` / `reference[]`). Crossing it opens the child doc
|
|
238
|
+
* scope with the path RESET — the child's fields are canonical relative
|
|
239
|
+
* to ITS own `data-ss-doc` (ADR-125 D2).
|
|
240
|
+
*/
|
|
241
|
+
function wrapResolvedMap(map, depth) {
|
|
242
|
+
if (map === null || typeof map !== 'object')
|
|
243
|
+
return map;
|
|
244
|
+
if (depth >= MAX_DEPTH)
|
|
245
|
+
return map;
|
|
246
|
+
return new Proxy(map, {
|
|
247
|
+
get(target, prop, receiver) {
|
|
248
|
+
const raw = Reflect.get(target, prop, receiver);
|
|
249
|
+
if (typeof prop === 'symbol' || typeof raw === 'function')
|
|
250
|
+
return raw;
|
|
251
|
+
if (raw === null || typeof raw !== 'object')
|
|
252
|
+
return raw;
|
|
253
|
+
if (Array.isArray(raw)) {
|
|
254
|
+
// Array of resolved children — wrap the array; each element is
|
|
255
|
+
// its own doc scope (handled by the QueryResult branch below).
|
|
256
|
+
return new Proxy(raw, {
|
|
257
|
+
get(arrT, arrP, arrR) {
|
|
258
|
+
const item = Reflect.get(arrT, arrP, arrR);
|
|
259
|
+
if (isQueryResult(item)) {
|
|
260
|
+
registerRoot(item, item.documentId, '');
|
|
261
|
+
return wrapNode(item, item.documentId, '', depth + 1);
|
|
262
|
+
}
|
|
263
|
+
return item;
|
|
264
|
+
},
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
if (isQueryResult(raw)) {
|
|
268
|
+
registerRoot(raw, raw.documentId, '');
|
|
269
|
+
return wrapNode(raw, raw.documentId, '', depth + 1);
|
|
270
|
+
}
|
|
271
|
+
return raw;
|
|
272
|
+
},
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
// ─────────────────────────────────────────────
|
|
276
|
+
// Eager structural enumeration (RSC-safe; matcher consumes this)
|
|
277
|
+
// ─────────────────────────────────────────────
|
|
278
|
+
/** Defensive caps — a pathological doc (deep nesting / huge arrays)
|
|
279
|
+
* must never let the per-rAF matcher walk blow the frame budget. */
|
|
280
|
+
const COLLECT_MAX_LEAVES = 8000;
|
|
281
|
+
/**
|
|
282
|
+
* Walk every registered root and return all canonical leaves
|
|
283
|
+
* `{ path, value, docId }`, deduped + bounded. RSC-safe: does not depend
|
|
284
|
+
* on any get-trap having fired. Resolved-ref boundaries re-scope exactly
|
|
285
|
+
* like the lazy path (docId = child `documentId`, path reset).
|
|
286
|
+
*/
|
|
287
|
+
export function collectFieldModel() {
|
|
288
|
+
// ADR-126 — walk the UNION of the RSC-island roots and the Proxy
|
|
289
|
+
// roots. `collectFieldModelFromRoots` is the pure, singleton-free,
|
|
290
|
+
// window-free core (RSC-safe): the SAME `walk()` both the client
|
|
291
|
+
// Proxy path and the server island path converge on, which is what
|
|
292
|
+
// makes the parity invariant hold by construction (one walker, two
|
|
293
|
+
// model sources — never a second path grammar).
|
|
294
|
+
//
|
|
295
|
+
// Two INDEPENDENT walks merged with explicit precedence (cross-model
|
|
296
|
+
// /review R2, codex #1): a single concatenated walk let whichever
|
|
297
|
+
// source came first fill the shared `COLLECT_MAX_LEAVES` budget and
|
|
298
|
+
// starve the other (R1 island-first only moved the starvation onto
|
|
299
|
+
// the Proxy on a huge hybrid page). Walking each source independently
|
|
300
|
+
// removes the order-vs-cap coupling entirely:
|
|
301
|
+
//
|
|
302
|
+
// • island = the RSC / cross-doc BASELINE (the pure-RSC content +
|
|
303
|
+
// the independently-queried `getPageContext()` docs the client
|
|
304
|
+
// Proxy never wrapped),
|
|
305
|
+
// • Proxy `roots` = the LIVE client render — AUTHORITATIVE on a
|
|
306
|
+
// same-`(docId,path)` conflict (the island only refreshes on an
|
|
307
|
+
// RSC re-render; the Proxy reflects the current client value).
|
|
308
|
+
//
|
|
309
|
+
// Build the island model first, then overlay the Proxy model so a
|
|
310
|
+
// conflicting key takes the Proxy (live) value. Union ⇒ a leaf in
|
|
311
|
+
// EITHER source survives; neither can erase the other (codex #7 /
|
|
312
|
+
// D-RECONCILE) and neither can starve the other at the cap. On the
|
|
313
|
+
// dominant pure-RSC path `roots` is empty so this is exactly the
|
|
314
|
+
// island model. Fast path: no island ⇒ the Proxy walk alone.
|
|
315
|
+
if (islandRoots.length === 0)
|
|
316
|
+
return collectFieldModelFromRoots(roots);
|
|
317
|
+
const merged = new Map();
|
|
318
|
+
for (const f of collectFieldModelFromRoots(islandRoots)) {
|
|
319
|
+
merged.set(`${f.docId ?? ''}\x00${f.path}`, f);
|
|
320
|
+
}
|
|
321
|
+
for (const f of collectFieldModelFromRoots(roots)) {
|
|
322
|
+
merged.set(`${f.docId ?? ''}\x00${f.path}`, f); // Proxy overrides
|
|
323
|
+
}
|
|
324
|
+
return [...merged.values()];
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* ADR-126 — pure extraction of the eager walk. No module singleton, no
|
|
328
|
+
* `window`: callable with any explicit `RegisteredRoot[]`. Behaviour for
|
|
329
|
+
* the module `roots` is byte-identical to the prior `collectFieldModel`
|
|
330
|
+
* (a strict no-behaviour-change extraction — pinned by a parity test).
|
|
331
|
+
*/
|
|
332
|
+
export function collectFieldModelFromRoots(rootsList) {
|
|
333
|
+
const out = new Map();
|
|
334
|
+
for (const r of rootsList) {
|
|
335
|
+
if (out.size >= COLLECT_MAX_LEAVES)
|
|
336
|
+
break;
|
|
337
|
+
walk(r.data, r.docId, r.basePath, 0, out, new Set());
|
|
338
|
+
}
|
|
339
|
+
return [...out.values()];
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* ADR-126 — register the documents the RSC render-path island reported.
|
|
343
|
+
* Each `entry` is a projected `QueryResult` (`{ documentId, fields,
|
|
344
|
+
* resolved? }`) the SDK's `<SliceZone>` deep-scanned out of its rendered
|
|
345
|
+
* `context` and serialized server-side. `isQueryResult` is satisfied by
|
|
346
|
+
* the string `documentId`, so the existing `walk()` re-scopes docId and
|
|
347
|
+
* resets path at every `QueryResult`/`.resolved` boundary EXACTLY like
|
|
348
|
+
* the Proxy path — zero downstream change. Scope-keyed `(documentId,'')`
|
|
349
|
+
* replace. Defensive: a non-object / no-`documentId` entry is skipped,
|
|
350
|
+
* never thrown on (the island is parsed from possibly-hostile page HTML).
|
|
351
|
+
*
|
|
352
|
+
* Cap is `MAX_ISLAND_ROOTS` (NOT the Proxy `MAX_ROOTS=256`): callers
|
|
353
|
+
* replace the per-render snapshot through `clearIslandRoots()` followed
|
|
354
|
+
* by `registerIslandRoots()`, so it cannot leak the way the
|
|
355
|
+
* identity-keyed Proxy `roots` could
|
|
356
|
+
* (the original 256 cap was that leak's belt-and-braces). Aligning it to
|
|
357
|
+
* the producer/reader `MAX_ISLAND_DOCS` (4096) stops a large context
|
|
358
|
+
* (>256 docs, e.g. a big blogIndex) from evicting the FIRST-registered
|
|
359
|
+
* root — which is the previewed page itself, scanned first — and
|
|
360
|
+
* silently dropping the main page's own field overlays (codex #6).
|
|
361
|
+
*/
|
|
362
|
+
const MAX_ISLAND_ROOTS = 4096;
|
|
363
|
+
/** ADR-126 — drop the whole island snapshot. It reflects ONE render;
|
|
364
|
+
* callers replace it wholesale so a doc removed from the rendered
|
|
365
|
+
* context on an RSC refresh cannot linger and
|
|
366
|
+
* re-introduce the non-rendered-doc false-positive class γ′ exists to
|
|
367
|
+
* kill (cross-model /review R1, codex #2). Also called by
|
|
368
|
+
* `clearTrackedFields()` on the render-mode epoch bump. */
|
|
369
|
+
export function clearIslandRoots() {
|
|
370
|
+
islandRoots.length = 0;
|
|
371
|
+
}
|
|
372
|
+
export function registerIslandRoots(entries) {
|
|
373
|
+
for (const entry of entries) {
|
|
374
|
+
if (!isQueryResult(entry))
|
|
375
|
+
continue;
|
|
376
|
+
const docId = entry.documentId;
|
|
377
|
+
let replaced = false;
|
|
378
|
+
for (const r of islandRoots) {
|
|
379
|
+
if (r.docId === docId && r.basePath === '') {
|
|
380
|
+
r.data = entry;
|
|
381
|
+
replaced = true;
|
|
382
|
+
break;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
if (replaced)
|
|
386
|
+
continue;
|
|
387
|
+
islandRoots.push({ data: entry, docId, basePath: '' });
|
|
388
|
+
if (islandRoots.length > MAX_ISLAND_ROOTS)
|
|
389
|
+
islandRoots.shift();
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
function walk(node, docId, path, depth, out, seen) {
|
|
393
|
+
if (out.size >= COLLECT_MAX_LEAVES)
|
|
394
|
+
return;
|
|
395
|
+
if (isPlainPrimitive(node)) {
|
|
396
|
+
if (path !== '')
|
|
397
|
+
out.set(leafKey(docId, path), { path, value: node, docId });
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
if (typeof node !== 'object' || node === null)
|
|
401
|
+
return;
|
|
402
|
+
if (depth >= MAX_DEPTH)
|
|
403
|
+
return;
|
|
404
|
+
if (seen.has(node))
|
|
405
|
+
return; // cycle guard
|
|
406
|
+
seen.add(node);
|
|
407
|
+
if (isQueryResult(node)) {
|
|
408
|
+
const childDocId = node.documentId;
|
|
409
|
+
const fields = node.fields;
|
|
410
|
+
if (fields && typeof fields === 'object') {
|
|
411
|
+
walk(fields, childDocId, path, depth + 1, out, seen);
|
|
412
|
+
}
|
|
413
|
+
const resolved = node.resolved;
|
|
414
|
+
if (resolved && typeof resolved === 'object') {
|
|
415
|
+
for (const child of Object.values(resolved)) {
|
|
416
|
+
if (Array.isArray(child)) {
|
|
417
|
+
for (const c of child) {
|
|
418
|
+
if (isQueryResult(c)) {
|
|
419
|
+
walk(c, c.documentId, '', depth + 1, out, seen);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
else if (isQueryResult(child)) {
|
|
424
|
+
walk(child, child.documentId, '', depth + 1, out, seen);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
if (Array.isArray(node)) {
|
|
431
|
+
for (let i = 0; i < node.length; i++) {
|
|
432
|
+
walk(node[i], docId, joinIndex(path, i), depth + 1, out, seen);
|
|
433
|
+
}
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
for (const [k, v] of Object.entries(node)) {
|
|
437
|
+
walk(v, docId, joinKey(path, k), depth + 1, out, seen);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
// ─────────────────────────────────────────────
|
|
441
|
+
// Public accessors (preserve the index.ts surface contract)
|
|
442
|
+
// ─────────────────────────────────────────────
|
|
443
|
+
/** Accessed leaves (lazy path). The matcher prefers `collectFieldModel`
|
|
444
|
+
* (RSC-safe) but this stays for client-render precision + tests. */
|
|
445
|
+
export function getTrackedFields() {
|
|
446
|
+
return [...accessed.values()];
|
|
447
|
+
}
|
|
448
|
+
/**
|
|
449
|
+
* Hard reset — clears the accessed set AND every registered root.
|
|
450
|
+
* NOT called per-publish (scope-keyed `registerRoot` already bounds
|
|
451
|
+
* `roots` and keeps it current; a per-publish clear would race the
|
|
452
|
+
* customer render that registers roots). It is the test reset and the
|
|
453
|
+
* teardown hook a future generation-scoped reset would call if the
|
|
454
|
+
* model ever needs hard isolation across a doc swap (today scope-keying
|
|
455
|
+
* makes the swap self-correct: the new doc replaces its scope entry,
|
|
456
|
+
* cross-model /review P1 fix).
|
|
457
|
+
*/
|
|
458
|
+
export function clearTrackedFields() {
|
|
459
|
+
accessed.clear();
|
|
460
|
+
roots.length = 0;
|
|
461
|
+
// ADR-126 — the island roots are generation-scoped too: a render-mode
|
|
462
|
+
// epoch BUMP (cross-doc / mode / Form-entry) must drop the prior doc's
|
|
463
|
+
// island so a stale cross-doc-switch field never overlays. The fresh
|
|
464
|
+
// RSC render emits a new `<script data-ss-model>`; the next publish's
|
|
465
|
+
// island read repopulates from the current DOM (the island reader's
|
|
466
|
+
// parse cache is keyed by script text, so a content change re-parses;
|
|
467
|
+
// `registerIslandRoots` is called every publish and is idempotent, so
|
|
468
|
+
// it re-fills `islandRoots` after this clear even if the script text
|
|
469
|
+
// is unchanged — overlays never get stuck empty post-epoch).
|
|
470
|
+
clearIslandRoots();
|
|
471
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-iframe SDK runtime — Form-preview viewport-contract reporter
|
|
3
|
+
* (ADR-119 D2/D3/D6).
|
|
4
|
+
*
|
|
5
|
+
* Form preview (render-mode `'navigation'`) is a live, SDK-cooperative
|
|
6
|
+
* editorial mini-map. This runtime is the SDK half of the ADR-119
|
|
7
|
+
* contract; the Studio half is the disciplined controller in
|
|
8
|
+
* `apps/studio/.../preview-surface.tsx`.
|
|
9
|
+
*
|
|
10
|
+
* 1. On `render-mode: 'navigation'` it sets
|
|
11
|
+
* `data-storystick-form-preview` on the customer document's
|
|
12
|
+
* `<html>`. The migrated starter routes viewport-relative
|
|
13
|
+
* primitives (`body { min-height: var(--ss-screen-h, 100dvh) }`,
|
|
14
|
+
* `min-h-dvh`/`h-screen` utilities, full-screen sections) through
|
|
15
|
+
* `--ss-screen-h`, which `html[data-storystick-form-preview]`
|
|
16
|
+
* pins to 800px. This neutralises the EXP-3 cross-doc viewport
|
|
17
|
+
* ratchet **at the source**: the customer's own primitives
|
|
18
|
+
* resolve to a real 800, never the tall measuring iframe.
|
|
19
|
+
*
|
|
20
|
+
* 2. It measures `documentElement.scrollHeight` **under the applied
|
|
21
|
+
* contract** and reports it to the Studio parent as
|
|
22
|
+
* `{type:'storystick.preview.form-contract', epoch, applied:true,
|
|
23
|
+
* pageHeight, viewportHeight}`. The FIRST report after an `epoch`
|
|
24
|
+
* change is the
|
|
25
|
+
* controller's hard ACK gate (ADR-119 D3 — "NEVER take the first
|
|
26
|
+
* measurement without a confirmed ack"). Subsequent same-`epoch`
|
|
27
|
+
* reports (layout-observer driven) feed the controller's
|
|
28
|
+
* debounced in-doc resize cadence.
|
|
29
|
+
*
|
|
30
|
+
* 3. On `render-mode: 'device'` it removes the attribute and reports
|
|
31
|
+
* `{applied:false, pageHeight:0}`. Device is strict browser truth
|
|
32
|
+
* (1280×800), never virtualised.
|
|
33
|
+
*
|
|
34
|
+
* `epoch` is the Studio controller's instance-scoped generation
|
|
35
|
+
* counter (ADR-119 D1), carried on `MsgRenderMode` and echoed back
|
|
36
|
+
* verbatim on every report so the controller can discard stale
|
|
37
|
+
* cross-doc async measurements soundly (the ratchet-class guard).
|
|
38
|
+
*
|
|
39
|
+
* Principle (ADR-119 D6 / no-customer-DOM-interference, relaxed for
|
|
40
|
+
* Form only, contractualised): the ONLY customer-DOM write is the
|
|
41
|
+
* single `data-storystick-form-preview` attribute on `<html>`, set by
|
|
42
|
+
* the SDK the customer already embeds — NOT Studio reaching across
|
|
43
|
+
* origins, NOT CSS injection (the retracted ADR-102/112 §D4 path).
|
|
44
|
+
* Reads are limited to `documentElement.scrollHeight` and the iframe's own
|
|
45
|
+
* `window.innerHeight`. No content mutation, no DOM-tree inspection. A site not using the starter
|
|
46
|
+
* primitives simply gets an honest, slightly-less-faithful overview;
|
|
47
|
+
* Device stays exact.
|
|
48
|
+
*/
|
|
49
|
+
export declare const FORM_PREVIEW_ATTR = "data-storystick-form-preview";
|
|
50
|
+
export interface FormContractReporterOptions {
|
|
51
|
+
/** Document the contract attribute is written on. Defaults to `window.document`. */
|
|
52
|
+
doc?: Document;
|
|
53
|
+
/** Window to read `parent` / attach the listener on. Defaults to `window`. */
|
|
54
|
+
win?: Window;
|
|
55
|
+
/** Override `requestAnimationFrame` for deterministic tests. */
|
|
56
|
+
raf?: (cb: () => void) => number;
|
|
57
|
+
/** Pair with `raf` to cancel pending settle/observer ticks on stop. */
|
|
58
|
+
caf?: (handle: number) => void;
|
|
59
|
+
}
|
|
60
|
+
export interface FormContractReport {
|
|
61
|
+
epoch: number;
|
|
62
|
+
applied: boolean;
|
|
63
|
+
pageHeight: number;
|
|
64
|
+
viewportHeight: number;
|
|
65
|
+
windowedProjection?: 'pending' | 'active' | 'unsupported';
|
|
66
|
+
windowedProjectionReason?: string;
|
|
67
|
+
windowStart?: number;
|
|
68
|
+
renderSpan?: number;
|
|
69
|
+
compression?: number;
|
|
70
|
+
}
|
|
71
|
+
export interface FormContractReporterHandle {
|
|
72
|
+
/**
|
|
73
|
+
* Tear down observers + listener AND remove the contract attribute
|
|
74
|
+
* (a torn-down SDK must not leave the customer document virtualised).
|
|
75
|
+
* Idempotent.
|
|
76
|
+
*/
|
|
77
|
+
stop(): void;
|
|
78
|
+
/**
|
|
79
|
+
* Force a synchronous measure+publish for the current mode — used by
|
|
80
|
+
* tests to assert post-rAF state without real animation frames.
|
|
81
|
+
* Returns the report that was posted (or `null` if nothing has been
|
|
82
|
+
* driven yet).
|
|
83
|
+
*/
|
|
84
|
+
flush(): FormContractReport | null;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Conceptual viewport for customer JS that needs the Form-preview
|
|
88
|
+
* height (ADR-119 D5 — explicit helper, NOT a global `window.
|
|
89
|
+
* innerHeight` shim). When the contract is active the computed
|
|
90
|
+
* `--ss-screen-h` resolves to `800px` and that is returned as the
|
|
91
|
+
* height; otherwise the real `innerHeight` (Device / no contract /
|
|
92
|
+
* non-starter site). Width is always the real `innerWidth` (Form does
|
|
93
|
+
* not virtualise width — the iframe is a true 1280 logical box).
|
|
94
|
+
*/
|
|
95
|
+
export declare function getStorystickViewport(win?: Window): {
|
|
96
|
+
width: number;
|
|
97
|
+
height: number;
|
|
98
|
+
};
|
|
99
|
+
export declare function startFormContractReporter(options?: FormContractReporterOptions): FormContractReporterHandle;
|