@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,391 @@
|
|
|
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
|
+
import { isStudioToIframe, } from '../types.js';
|
|
50
|
+
import { createPreviewObservationScheduler, } from './observation-scheduler.js';
|
|
51
|
+
import { createWindowedMinimapProjection } from './windowed-minimap-projection.js';
|
|
52
|
+
export const FORM_PREVIEW_ATTR = 'data-storystick-form-preview';
|
|
53
|
+
/**
|
|
54
|
+
* Conceptual viewport for customer JS that needs the Form-preview
|
|
55
|
+
* height (ADR-119 D5 — explicit helper, NOT a global `window.
|
|
56
|
+
* innerHeight` shim). When the contract is active the computed
|
|
57
|
+
* `--ss-screen-h` resolves to `800px` and that is returned as the
|
|
58
|
+
* height; otherwise the real `innerHeight` (Device / no contract /
|
|
59
|
+
* non-starter site). Width is always the real `innerWidth` (Form does
|
|
60
|
+
* not virtualise width — the iframe is a true 1280 logical box).
|
|
61
|
+
*/
|
|
62
|
+
export function getStorystickViewport(win = typeof window === 'undefined'
|
|
63
|
+
? undefined
|
|
64
|
+
: window) {
|
|
65
|
+
if (!win || typeof win.innerWidth !== 'number') {
|
|
66
|
+
return { width: 0, height: 0 };
|
|
67
|
+
}
|
|
68
|
+
const width = win.innerWidth;
|
|
69
|
+
const root = win.document?.documentElement;
|
|
70
|
+
if (root && typeof win.getComputedStyle === 'function') {
|
|
71
|
+
const raw = win
|
|
72
|
+
.getComputedStyle(root)
|
|
73
|
+
.getPropertyValue('--ss-screen-h')
|
|
74
|
+
.trim();
|
|
75
|
+
if (raw) {
|
|
76
|
+
const n = Number.parseFloat(raw);
|
|
77
|
+
if (Number.isFinite(n) && n > 0)
|
|
78
|
+
return { width, height: n };
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return { width, height: win.innerHeight };
|
|
82
|
+
}
|
|
83
|
+
export function startFormContractReporter(options = {}) {
|
|
84
|
+
const win = options.win ?? window;
|
|
85
|
+
const doc = options.doc ?? win.document;
|
|
86
|
+
const raf = options.raf ??
|
|
87
|
+
(typeof win.requestAnimationFrame === 'function'
|
|
88
|
+
? win.requestAnimationFrame.bind(win)
|
|
89
|
+
: (cb) => win.setTimeout(cb, 16));
|
|
90
|
+
const caf = options.caf ??
|
|
91
|
+
(typeof win.cancelAnimationFrame === 'function'
|
|
92
|
+
? win.cancelAnimationFrame.bind(win)
|
|
93
|
+
: (h) => win.clearTimeout(h));
|
|
94
|
+
let mode = 'device';
|
|
95
|
+
let epoch = 0;
|
|
96
|
+
// ADR-119 §D5-B change #4 — the epoch a pending settle-rAF will
|
|
97
|
+
// publish under. Without this, `epoch` is a mutable module var read
|
|
98
|
+
// at rAF FIRE time: if a render-mode bumps `epoch` (D5-B's
|
|
99
|
+
// `confirmNav` fresh-epoch re-begin) while an OLD-epoch rAF is
|
|
100
|
+
// pending, that stale measurement would publish under the NEW
|
|
101
|
+
// epoch → the Studio D1 generation guard could not discard it →
|
|
102
|
+
// raw-CSS cross-doc ratchet. Stamping the pending measurement with
|
|
103
|
+
// the epoch it was scheduled for (and re-arming on an epoch change)
|
|
104
|
+
// makes a pre-confirm measurement publish under its OWN old epoch
|
|
105
|
+
// → D1-discarded; the new epoch gets its own post-confirm rAF.
|
|
106
|
+
// The shared observation scheduler owns the rAF handle; this reporter
|
|
107
|
+
// still owns the epoch stamp because it is part of the form-contract
|
|
108
|
+
// protocol, not generic DOM observation.
|
|
109
|
+
let pendingEpoch = null;
|
|
110
|
+
let last = null;
|
|
111
|
+
let stopped = false;
|
|
112
|
+
const windowedProjection = createWindowedMinimapProjection(win, doc);
|
|
113
|
+
let windowedProjectionRequested = false;
|
|
114
|
+
let configuredProjectionEpoch = null;
|
|
115
|
+
function setContract(on) {
|
|
116
|
+
const root = doc.documentElement;
|
|
117
|
+
if (!root)
|
|
118
|
+
return;
|
|
119
|
+
if (on)
|
|
120
|
+
root.setAttribute(FORM_PREVIEW_ATTR, '');
|
|
121
|
+
else
|
|
122
|
+
root.removeAttribute(FORM_PREVIEW_ATTR);
|
|
123
|
+
}
|
|
124
|
+
function publish(applied, epochArg = epoch) {
|
|
125
|
+
// Reading `scrollHeight` forces a synchronous reflow that accounts
|
|
126
|
+
// for the just-applied `data-storystick-form-preview` attribute
|
|
127
|
+
// (hence `--ss-screen-h:800px` → the starter's `body min-height`
|
|
128
|
+
// pins to a real 800; tall content overflows the measuring iframe
|
|
129
|
+
// so `scrollHeight` is the true content extent, never the inherited
|
|
130
|
+
// tall-iframe height — the EXP-4 invariant).
|
|
131
|
+
const projectedGeometry = windowedProjection.geometry;
|
|
132
|
+
const pageHeight = applied
|
|
133
|
+
? projectedGeometry?.pageHeight ??
|
|
134
|
+
Math.max(0, doc.documentElement?.scrollHeight ?? 0)
|
|
135
|
+
: 0;
|
|
136
|
+
const viewportHeight = applied
|
|
137
|
+
? Math.max(0, Number.isFinite(win.innerHeight) ? win.innerHeight : 0)
|
|
138
|
+
: 0;
|
|
139
|
+
const report = {
|
|
140
|
+
epoch: epochArg,
|
|
141
|
+
applied,
|
|
142
|
+
pageHeight,
|
|
143
|
+
viewportHeight,
|
|
144
|
+
...(windowedProjectionRequested
|
|
145
|
+
? {
|
|
146
|
+
windowedProjection: windowedProjection.active
|
|
147
|
+
? configuredProjectionEpoch === epochArg
|
|
148
|
+
? 'active'
|
|
149
|
+
: 'pending'
|
|
150
|
+
: 'unsupported',
|
|
151
|
+
...(!windowedProjection.active && windowedProjection.stoppedReason
|
|
152
|
+
? {
|
|
153
|
+
windowedProjectionReason: windowedProjection.stoppedReason,
|
|
154
|
+
}
|
|
155
|
+
: {}),
|
|
156
|
+
...(projectedGeometry
|
|
157
|
+
? {
|
|
158
|
+
windowStart: projectedGeometry.windowStart,
|
|
159
|
+
renderSpan: projectedGeometry.renderSpan,
|
|
160
|
+
compression: projectedGeometry.compression,
|
|
161
|
+
}
|
|
162
|
+
: {}),
|
|
163
|
+
}
|
|
164
|
+
: {}),
|
|
165
|
+
};
|
|
166
|
+
last = report;
|
|
167
|
+
const msg = {
|
|
168
|
+
type: 'storystick.preview.form-contract',
|
|
169
|
+
epoch: epochArg,
|
|
170
|
+
applied,
|
|
171
|
+
pageHeight,
|
|
172
|
+
viewportHeight,
|
|
173
|
+
...(report.windowedProjection
|
|
174
|
+
? {
|
|
175
|
+
windowedProjection: report.windowedProjection,
|
|
176
|
+
...(report.windowedProjectionReason
|
|
177
|
+
? { windowedProjectionReason: report.windowedProjectionReason }
|
|
178
|
+
: {}),
|
|
179
|
+
...(report.windowStart !== undefined
|
|
180
|
+
? { windowStart: report.windowStart }
|
|
181
|
+
: {}),
|
|
182
|
+
...(report.renderSpan !== undefined
|
|
183
|
+
? { renderSpan: report.renderSpan }
|
|
184
|
+
: {}),
|
|
185
|
+
...(report.compression !== undefined
|
|
186
|
+
? { compression: report.compression }
|
|
187
|
+
: {}),
|
|
188
|
+
}
|
|
189
|
+
: {}),
|
|
190
|
+
};
|
|
191
|
+
// `'*'` mirrors section/snapshot reporters: the payload is UI
|
|
192
|
+
// geometry, no secrets; Studio's bridge filters by source + origin.
|
|
193
|
+
win.parent.postMessage(msg, '*');
|
|
194
|
+
return report;
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* rAF-coalesced settle-then-publish. One frame lets the just-set
|
|
198
|
+
* attribute's style/layout flush before we read `scrollHeight`; the
|
|
199
|
+
* observers below catch the long tail (font swap, image decode).
|
|
200
|
+
* Only meaningful while in Form (navigation) mode.
|
|
201
|
+
*/
|
|
202
|
+
const scheduler = createPreviewObservationScheduler({
|
|
203
|
+
raf,
|
|
204
|
+
caf,
|
|
205
|
+
onFlush: () => {
|
|
206
|
+
const forEpoch = pendingEpoch ?? epoch;
|
|
207
|
+
pendingEpoch = null;
|
|
208
|
+
if (mode !== 'navigation')
|
|
209
|
+
return;
|
|
210
|
+
if (windowedProjectionRequested)
|
|
211
|
+
windowedProjection.refresh();
|
|
212
|
+
// Publish under the epoch this measurement was SCHEDULED for —
|
|
213
|
+
// never the (possibly newer) live `epoch`.
|
|
214
|
+
publish(true, forEpoch);
|
|
215
|
+
},
|
|
216
|
+
});
|
|
217
|
+
function schedule(reason = 'form-contract') {
|
|
218
|
+
if (stopped)
|
|
219
|
+
return;
|
|
220
|
+
if (scheduler.isPending()) {
|
|
221
|
+
// A rAF for the SAME epoch is already pending → coalesce.
|
|
222
|
+
if (pendingEpoch === epoch)
|
|
223
|
+
return;
|
|
224
|
+
// A new render-mode bumped `epoch` while an OLD-epoch rAF is
|
|
225
|
+
// still pending (D5-B `confirmNav` re-begin) → drop the stale
|
|
226
|
+
// one and re-arm for the new epoch, so the OLD measurement can
|
|
227
|
+
// never publish under the NEW epoch.
|
|
228
|
+
scheduler.cancel();
|
|
229
|
+
pendingEpoch = null;
|
|
230
|
+
}
|
|
231
|
+
const forEpoch = epoch;
|
|
232
|
+
pendingEpoch = forEpoch;
|
|
233
|
+
scheduler.request(reason);
|
|
234
|
+
}
|
|
235
|
+
// Layout observers — fire while the contract is active so in-doc
|
|
236
|
+
// content changes (autosave re-render, contentRevision) re-report
|
|
237
|
+
// the new page height. The Studio controller owns the debounce +
|
|
238
|
+
// no-reset cadence (ADR-119 D3 cadence note); the SDK just keeps the
|
|
239
|
+
// feed honest. ResizeObserver on documentElement catches the bulk;
|
|
240
|
+
// a filtered MutationObserver catches structural swaps that don't
|
|
241
|
+
// change the root box synchronously.
|
|
242
|
+
const ro = typeof ResizeObserver !== 'undefined'
|
|
243
|
+
? new ResizeObserver(() => {
|
|
244
|
+
if (mode === 'navigation')
|
|
245
|
+
schedule('resize');
|
|
246
|
+
})
|
|
247
|
+
: null;
|
|
248
|
+
const mo = typeof MutationObserver !== 'undefined'
|
|
249
|
+
? new MutationObserver((records) => {
|
|
250
|
+
if (mode !== 'navigation')
|
|
251
|
+
return;
|
|
252
|
+
if (windowedProjectionRequested &&
|
|
253
|
+
records.some((record) => record.target === doc.documentElement &&
|
|
254
|
+
record.type === 'attributes' &&
|
|
255
|
+
record.attributeName === 'style')) {
|
|
256
|
+
// Mutation delivery precedes paint. Waiting for the measurement
|
|
257
|
+
// rAF or Studio's post-paint route ACK exposes an unscaled frame
|
|
258
|
+
// when the router replaces the root layout, including a refresh
|
|
259
|
+
// after the route has already acknowledged its projection.
|
|
260
|
+
windowedProjection.reapplyAfterRootReset();
|
|
261
|
+
}
|
|
262
|
+
if (windowedProjectionRequested &&
|
|
263
|
+
records.every((record) => record.target === doc.documentElement &&
|
|
264
|
+
record.type === 'attributes' &&
|
|
265
|
+
record.attributeName === 'style' &&
|
|
266
|
+
windowedProjection.ownsRootStyle())) {
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
schedule('mutation');
|
|
270
|
+
})
|
|
271
|
+
: null;
|
|
272
|
+
const fontsLike = doc.fonts;
|
|
273
|
+
const onViewportResize = () => {
|
|
274
|
+
if (mode === 'navigation')
|
|
275
|
+
schedule('resize');
|
|
276
|
+
};
|
|
277
|
+
win.addEventListener('resize', onViewportResize);
|
|
278
|
+
if (fontsLike && typeof fontsLike.ready?.then === 'function') {
|
|
279
|
+
void fontsLike.ready.then(() => {
|
|
280
|
+
if (mode === 'navigation')
|
|
281
|
+
schedule('fonts-ready');
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
function attachObservers() {
|
|
285
|
+
if (ro) {
|
|
286
|
+
ro.disconnect();
|
|
287
|
+
if (doc.documentElement)
|
|
288
|
+
ro.observe(doc.documentElement);
|
|
289
|
+
if (doc.body)
|
|
290
|
+
ro.observe(doc.body);
|
|
291
|
+
}
|
|
292
|
+
if (mo && doc.documentElement) {
|
|
293
|
+
mo.observe(doc.documentElement, {
|
|
294
|
+
childList: true,
|
|
295
|
+
subtree: true,
|
|
296
|
+
attributes: true,
|
|
297
|
+
attributeFilter: ['style', 'class', 'hidden'],
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
function detachObservers() {
|
|
302
|
+
ro?.disconnect();
|
|
303
|
+
mo?.disconnect();
|
|
304
|
+
}
|
|
305
|
+
function onMessage(event) {
|
|
306
|
+
if (event.source !== win.parent)
|
|
307
|
+
return;
|
|
308
|
+
if (!isStudioToIframe(event.data))
|
|
309
|
+
return;
|
|
310
|
+
const msg = event.data;
|
|
311
|
+
if (msg.type === 'storystick.preview.windowed-minimap') {
|
|
312
|
+
if (mode === 'navigation' &&
|
|
313
|
+
windowedProjectionRequested &&
|
|
314
|
+
msg.epoch === epoch) {
|
|
315
|
+
const updated = windowedProjection.update(msg.windowStart, msg.renderSpan);
|
|
316
|
+
configuredProjectionEpoch = updated ? epoch : null;
|
|
317
|
+
publish(true);
|
|
318
|
+
}
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
if (msg.type !== 'render-mode')
|
|
322
|
+
return;
|
|
323
|
+
const nextEpoch = msg.epoch ?? 0;
|
|
324
|
+
if (nextEpoch !== epoch)
|
|
325
|
+
configuredProjectionEpoch = null;
|
|
326
|
+
epoch = nextEpoch;
|
|
327
|
+
if (msg.mode === 'navigation') {
|
|
328
|
+
mode = 'navigation';
|
|
329
|
+
windowedProjectionRequested = msg.windowedMinimapProjection === true;
|
|
330
|
+
// Windowed projection is the canonical Navigation renderer requested by
|
|
331
|
+
// the current Studio. Older senders that omit the capability can still
|
|
332
|
+
// ask for the legacy contract explicitly, but a failed windowed
|
|
333
|
+
// projection must never fall back to it implicitly.
|
|
334
|
+
setContract(!windowedProjectionRequested);
|
|
335
|
+
if (windowedProjectionRequested) {
|
|
336
|
+
windowedProjection.enter();
|
|
337
|
+
}
|
|
338
|
+
else {
|
|
339
|
+
configuredProjectionEpoch = null;
|
|
340
|
+
windowedProjection.exit();
|
|
341
|
+
}
|
|
342
|
+
attachObservers();
|
|
343
|
+
schedule('render-mode');
|
|
344
|
+
}
|
|
345
|
+
else {
|
|
346
|
+
mode = 'device';
|
|
347
|
+
detachObservers();
|
|
348
|
+
configuredProjectionEpoch = null;
|
|
349
|
+
windowedProjection.exit();
|
|
350
|
+
windowedProjectionRequested = false;
|
|
351
|
+
setContract(false);
|
|
352
|
+
// Immediate (no settle needed — removing the attribute is the
|
|
353
|
+
// semantic; pageHeight is irrelevant in Device).
|
|
354
|
+
if (scheduler.isPending()) {
|
|
355
|
+
scheduler.cancel();
|
|
356
|
+
pendingEpoch = null;
|
|
357
|
+
}
|
|
358
|
+
publish(false);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
win.addEventListener('message', onMessage);
|
|
362
|
+
return {
|
|
363
|
+
stop() {
|
|
364
|
+
if (stopped)
|
|
365
|
+
return;
|
|
366
|
+
stopped = true;
|
|
367
|
+
if (scheduler.isPending()) {
|
|
368
|
+
scheduler.cancel();
|
|
369
|
+
pendingEpoch = null;
|
|
370
|
+
}
|
|
371
|
+
detachObservers();
|
|
372
|
+
windowedProjection.exit();
|
|
373
|
+
win.removeEventListener('message', onMessage);
|
|
374
|
+
win.removeEventListener('resize', onViewportResize);
|
|
375
|
+
// A torn-down SDK must not leave the customer document
|
|
376
|
+
// virtualised — restore strict truth.
|
|
377
|
+
setContract(false);
|
|
378
|
+
},
|
|
379
|
+
flush() {
|
|
380
|
+
if (stopped)
|
|
381
|
+
return last;
|
|
382
|
+
if (scheduler.isPending()) {
|
|
383
|
+
scheduler.cancel();
|
|
384
|
+
pendingEpoch = null;
|
|
385
|
+
}
|
|
386
|
+
if (mode === 'navigation')
|
|
387
|
+
return publish(true);
|
|
388
|
+
return last;
|
|
389
|
+
},
|
|
390
|
+
};
|
|
391
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { PreviewContentRefreshEvent } from './content-refresh.js';
|
|
2
|
+
import { type PreviewRuntime } from './store.js';
|
|
3
|
+
/**
|
|
4
|
+
* Framework seam for preview content invalidation.
|
|
5
|
+
*
|
|
6
|
+
* The runtime remains the authority for authenticated content clocks,
|
|
7
|
+
* deduplication, and refresh decisions. Adapters only translate an accepted
|
|
8
|
+
* event into the framework's render primitive. Next keeps its richer scheduler;
|
|
9
|
+
* Astro and other integrations can use this progressive-enhancement seam.
|
|
10
|
+
*/
|
|
11
|
+
export interface PreviewInvalidationAdapter {
|
|
12
|
+
refresh(event: PreviewContentRefreshEvent): void | Promise<void>;
|
|
13
|
+
}
|
|
14
|
+
export declare function connectPreviewInvalidationAdapter(adapter: PreviewInvalidationAdapter, runtime?: Pick<PreviewRuntime, 'subscribeContentRefresh'>): () => void;
|
|
15
|
+
/** Astro-compatible fallback: preserve the exact URL and reload the document. */
|
|
16
|
+
export declare function createBrowserReloadPreviewInvalidationAdapter(view?: Pick<Window, 'location'>): PreviewInvalidationAdapter;
|
|
17
|
+
export type { PreviewContentRefreshEvent };
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { previewRuntime } from './store.js';
|
|
2
|
+
export function connectPreviewInvalidationAdapter(adapter, runtime = previewRuntime) {
|
|
3
|
+
let stopped = false;
|
|
4
|
+
let refreshing = false;
|
|
5
|
+
let pending = null;
|
|
6
|
+
const drain = async (event) => {
|
|
7
|
+
if (refreshing) {
|
|
8
|
+
pending = event;
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
refreshing = true;
|
|
12
|
+
try {
|
|
13
|
+
await adapter.refresh(event);
|
|
14
|
+
}
|
|
15
|
+
finally {
|
|
16
|
+
refreshing = false;
|
|
17
|
+
const next = pending;
|
|
18
|
+
pending = null;
|
|
19
|
+
if (!stopped && next !== null)
|
|
20
|
+
void drain(next);
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
const unsubscribe = runtime.subscribeContentRefresh((event) => {
|
|
24
|
+
if (!stopped && event.refresh)
|
|
25
|
+
void drain(event);
|
|
26
|
+
});
|
|
27
|
+
return () => {
|
|
28
|
+
if (stopped)
|
|
29
|
+
return;
|
|
30
|
+
stopped = true;
|
|
31
|
+
pending = null;
|
|
32
|
+
unsubscribe();
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
/** Astro-compatible fallback: preserve the exact URL and reload the document. */
|
|
36
|
+
export function createBrowserReloadPreviewInvalidationAdapter(view = window) {
|
|
37
|
+
return {
|
|
38
|
+
refresh() {
|
|
39
|
+
view.location.reload();
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { type PreviewLiveQueryProjectionPayload, type PreviewLiveQueryRegisterEnvelope, type PreviewLiveQueryUnregisterEnvelope, type PreviewMessage } from '@storystick/transport-allowlist';
|
|
2
|
+
import type { PreviewQueryReceiptId } from '@storystick/types/preview-query-targeting';
|
|
3
|
+
import type { QueryPageInfo, QueryResult } from '@storystick/types/query-contract';
|
|
4
|
+
export interface PreviewLiveQueryOverlayConfiguration {
|
|
5
|
+
readonly enabled: boolean;
|
|
6
|
+
readonly projectId: string | null;
|
|
7
|
+
readonly sessionId: string | null;
|
|
8
|
+
readonly parentOrigin: string | null;
|
|
9
|
+
readonly view?: Window;
|
|
10
|
+
}
|
|
11
|
+
export interface PreviewLiveQueryOverlayHandle {
|
|
12
|
+
readonly queryInstanceId: string | null;
|
|
13
|
+
getSnapshot(): PreviewLiveQueryProjectionPayload | null;
|
|
14
|
+
subscribe(listener: () => void): () => void;
|
|
15
|
+
updateReceipt(receiptId: PreviewQueryReceiptId | null): void;
|
|
16
|
+
release(): void;
|
|
17
|
+
}
|
|
18
|
+
export interface PreviewLiveQueryOverlayRuntime {
|
|
19
|
+
configure(configuration: PreviewLiveQueryOverlayConfiguration): void;
|
|
20
|
+
retain(queryIdentity: string): PreviewLiveQueryOverlayHandle;
|
|
21
|
+
clear(): void;
|
|
22
|
+
/** Internal/test seam after shared envelope validation. */
|
|
23
|
+
acceptStudioEnvelope(message: PreviewMessage, eventOrigin: string, sourceIsParent: boolean): void;
|
|
24
|
+
/** Test-only visibility; never content authority. */
|
|
25
|
+
activeQueryCount(): number;
|
|
26
|
+
}
|
|
27
|
+
interface RuntimeOptions {
|
|
28
|
+
readonly randomHex?: () => string;
|
|
29
|
+
readonly now?: () => number;
|
|
30
|
+
readonly send?: (envelope: PreviewLiveQueryRegisterEnvelope | PreviewLiveQueryUnregisterEnvelope, targetOrigin: string) => void;
|
|
31
|
+
}
|
|
32
|
+
export declare function createPreviewLiveQueryOverlayRuntime(options?: RuntimeOptions): PreviewLiveQueryOverlayRuntime;
|
|
33
|
+
export declare const previewLiveQueryOverlay: PreviewLiveQueryOverlayRuntime;
|
|
34
|
+
/**
|
|
35
|
+
* Derive a render-only QueryResult. The confirmed object is never mutated and
|
|
36
|
+
* neither membership, order, pageInfo nor metadata can change.
|
|
37
|
+
*/
|
|
38
|
+
export declare function applyPreviewLiveQueryProjection<TData, TPageInfo extends QueryPageInfo | null>(confirmed: QueryResult<TData, TPageInfo> | undefined, projection: PreviewLiveQueryProjectionPayload | null): QueryResult<TData, TPageInfo> | undefined;
|
|
39
|
+
export {};
|