@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,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ADR-129 — in-iframe atomic-snapshot reporter.
|
|
3
|
+
*
|
|
4
|
+
* Runs inside the customer site iframe when embedded by the Studio.
|
|
5
|
+
* Supersedes the retired section/field-bounds publish: it observes
|
|
6
|
+
* layout, re-assembles ONE atomic
|
|
7
|
+
* `MsgPreviewSnapshot` (see `snapshot-assembly`), and posts it to the
|
|
8
|
+
* Studio. The Studio renders the overlay layer + owns hover/click —
|
|
9
|
+
* this runtime only observes geometry and reports.
|
|
10
|
+
*
|
|
11
|
+
* Observer triptyque (ADR-059 / ADR-112 pattern): ResizeObserver +
|
|
12
|
+
* MutationObserver + `document.fonts.ready`, all funnelled into ONE
|
|
13
|
+
* rAF-coalesced `publish()` — never a per-frame storm.
|
|
14
|
+
*/
|
|
15
|
+
import { type MsgPreviewSnapshot } from '../types.js';
|
|
16
|
+
import { type PreviewObservationReason } from './observation-scheduler.js';
|
|
17
|
+
export interface SnapshotReporterOptions {
|
|
18
|
+
/** Document observed. Defaults to `window.document`. Injectable for tests. */
|
|
19
|
+
doc?: Document;
|
|
20
|
+
/** Window for `parent` / listeners. Defaults to `window`. Injectable for tests. */
|
|
21
|
+
win?: Window;
|
|
22
|
+
/** rAF override — tests pass a deterministic scheduler. */
|
|
23
|
+
raf?: (cb: () => void) => number;
|
|
24
|
+
/** cancelAnimationFrame override. */
|
|
25
|
+
caf?: (handle: number) => void;
|
|
26
|
+
/**
|
|
27
|
+
* Route hash emitted with the snapshot so Studio can keep document
|
|
28
|
+
* observations scoped to the current customer route.
|
|
29
|
+
*/
|
|
30
|
+
routeHashRef?: () => string | null;
|
|
31
|
+
/**
|
|
32
|
+
* Live customer URL rendered by the iframe. This is emitted so Studio
|
|
33
|
+
* can scope document observations against the post-redirect route,
|
|
34
|
+
* not against the preview bootstrap endpoint stored in the iframe
|
|
35
|
+
* element's `src` attribute.
|
|
36
|
+
*/
|
|
37
|
+
routeUrlRef?: () => string | null;
|
|
38
|
+
/** Observe the assembled wire snapshot locally (for standalone adapters). */
|
|
39
|
+
onSnapshot?: (snapshot: MsgPreviewSnapshot) => void;
|
|
40
|
+
/** Post snapshots/readiness to `window.parent`. Defaults to true. */
|
|
41
|
+
postToParent?: boolean;
|
|
42
|
+
}
|
|
43
|
+
export interface SnapshotReporterHandle {
|
|
44
|
+
/** Tear down observers + listener. */
|
|
45
|
+
stop(): void;
|
|
46
|
+
/** Force a flush — returns the snapshot posted (or `null` when there
|
|
47
|
+
* is no preview seam on the page). */
|
|
48
|
+
flush(): MsgPreviewSnapshot | null;
|
|
49
|
+
/**
|
|
50
|
+
* Request the reporter to publish on the next rAF tick. Idempotent:
|
|
51
|
+
* if a publish is already pending, this is a no-op.
|
|
52
|
+
*/
|
|
53
|
+
kick(reason?: PreviewObservationReason): void;
|
|
54
|
+
}
|
|
55
|
+
export declare function startSnapshotReporter(options?: SnapshotReporterOptions): SnapshotReporterHandle;
|
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ADR-129 — in-iframe atomic-snapshot reporter.
|
|
3
|
+
*
|
|
4
|
+
* Runs inside the customer site iframe when embedded by the Studio.
|
|
5
|
+
* Supersedes the retired section/field-bounds publish: it observes
|
|
6
|
+
* layout, re-assembles ONE atomic
|
|
7
|
+
* `MsgPreviewSnapshot` (see `snapshot-assembly`), and posts it to the
|
|
8
|
+
* Studio. The Studio renders the overlay layer + owns hover/click —
|
|
9
|
+
* this runtime only observes geometry and reports.
|
|
10
|
+
*
|
|
11
|
+
* Observer triptyque (ADR-059 / ADR-112 pattern): ResizeObserver +
|
|
12
|
+
* MutationObserver + `document.fonts.ready`, all funnelled into ONE
|
|
13
|
+
* rAF-coalesced `publish()` — never a per-frame storm.
|
|
14
|
+
*/
|
|
15
|
+
import { isStudioToIframe } from '../types.js';
|
|
16
|
+
import { setRenderMode } from './render-mode-state.js';
|
|
17
|
+
import { assembleSnapshot } from './snapshot-assembly.js';
|
|
18
|
+
import { clearPreviewOverlayRegistry, commitPreviewOverlayRegistryDraft, createPreviewOverlayRegistryDraft, } from './preview-overlay-sidecar.js';
|
|
19
|
+
import { estimatePreviewPayloadBytes, previewPerfNow, recordPreviewPerfMeasure, recordPreviewPerfMetric, } from './perf-metrics.js';
|
|
20
|
+
import { createPreviewObservationScheduler, } from './observation-scheduler.js';
|
|
21
|
+
const SS_SLICE_SELECTOR = '[data-ss-slice]';
|
|
22
|
+
const SS_PREVIEW_SYNTHETIC_OVERLAY_SELECTOR = '[data-ss-preview-field-synthetic-overlay-root],[data-ss-preview-field-synthetic-overlay-box]';
|
|
23
|
+
export function startSnapshotReporter(options = {}) {
|
|
24
|
+
const win = options.win ?? window;
|
|
25
|
+
const doc = options.doc ?? win.document;
|
|
26
|
+
const raf = options.raf ??
|
|
27
|
+
(typeof win.requestAnimationFrame === 'function'
|
|
28
|
+
? win.requestAnimationFrame.bind(win)
|
|
29
|
+
: (cb) => win.setTimeout(cb, 16));
|
|
30
|
+
const caf = options.caf ??
|
|
31
|
+
(typeof win.cancelAnimationFrame === 'function'
|
|
32
|
+
? win.cancelAnimationFrame.bind(win)
|
|
33
|
+
: (h) => win.clearTimeout(h));
|
|
34
|
+
// The controller generation last sent via `render-mode` (ADR-119 D1).
|
|
35
|
+
// Echoed on every snapshot so the Studio's anti-bleed gate can
|
|
36
|
+
// discard stale cross-doc/mode reports. `0` until the first
|
|
37
|
+
// `render-mode` — boot snapshots are pre-contract.
|
|
38
|
+
let currentEpoch = 0;
|
|
39
|
+
function publish() {
|
|
40
|
+
reconcileResizeObserver();
|
|
41
|
+
const liveRouteHash = options.routeHashRef?.() ?? null;
|
|
42
|
+
const liveRouteUrl = options.routeUrlRef?.() ?? null;
|
|
43
|
+
// Overlay-map paint tuples still carry their own protocol member.
|
|
44
|
+
// Snapshot cache state is retired; the living painter starts at zero.
|
|
45
|
+
const overlayMapCacheVersion = 0;
|
|
46
|
+
const overlayRegistry = createPreviewOverlayRegistryDraft();
|
|
47
|
+
let assembled = null;
|
|
48
|
+
let failed = false;
|
|
49
|
+
const startedAt = previewPerfNow(win);
|
|
50
|
+
try {
|
|
51
|
+
assembled = assembleSnapshot(doc, undefined, win.scrollX, win.scrollY, {
|
|
52
|
+
overlayRegistry,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
// Hostile / mid-mutation DOM — never let the reporter throw.
|
|
57
|
+
failed = true;
|
|
58
|
+
assembled = null;
|
|
59
|
+
}
|
|
60
|
+
recordPreviewPerfMeasure(win, 'snapshot.assemble.sync', startedAt, {
|
|
61
|
+
failed,
|
|
62
|
+
itemCount: assembled?.items.length ?? 0,
|
|
63
|
+
});
|
|
64
|
+
commitPreviewOverlayRegistryDraft(overlayRegistry, {
|
|
65
|
+
renderId: assembled?.renderId ?? '',
|
|
66
|
+
routeHash: liveRouteHash,
|
|
67
|
+
codeVersion: readCodeVersionFromWindow(win),
|
|
68
|
+
cacheVersion: overlayMapCacheVersion,
|
|
69
|
+
});
|
|
70
|
+
return emit(assembled, liveRouteHash, liveRouteUrl);
|
|
71
|
+
}
|
|
72
|
+
function emit(assembled,
|
|
73
|
+
/**
|
|
74
|
+
* The routeHash captured at the start of assembly.
|
|
75
|
+
*/
|
|
76
|
+
liveRouteHash,
|
|
77
|
+
/**
|
|
78
|
+
* The routeUrl sampled with the routeHash. It is not part of the
|
|
79
|
+
* anti-bleed key, but Studio needs the concrete URL to avoid using
|
|
80
|
+
* the iframe bootstrap URL as the route observation key.
|
|
81
|
+
*/
|
|
82
|
+
liveRouteUrl) {
|
|
83
|
+
// - `codeVersion` (upstream build/schema/config signature per
|
|
84
|
+
// §D7.1a; resolved once at boot from `window.__STORYSTICK_CODE_VERSION__`
|
|
85
|
+
// OR `''` when absent — the Studio controller treats `''` as
|
|
86
|
+
// "indeterminate" and skips redeploy-drift detection).
|
|
87
|
+
// - `timestamp` (Date.now() at the producing rAF tick; feeds the
|
|
88
|
+
// §D3.2 adaptive correlation window EMA).
|
|
89
|
+
// No island ⇒ no preview seam on this page. Still post an EMPTY
|
|
90
|
+
// snapshot so the Studio clears any overlays from a prior render
|
|
91
|
+
// (anti-bleed on client-nav to a non-SliceZone page).
|
|
92
|
+
// ADR-131 amendment-003 + ADR-139 §5.5 — share the live routeHash
|
|
93
|
+
// (passed in from the caller, captured at assembly time so the
|
|
94
|
+
// (items, routeHash) pair stays consistent across iframe URL
|
|
95
|
+
// changes during assembly. Optional on the wire; absent while the
|
|
96
|
+
// route hash is unavailable.
|
|
97
|
+
const msg = {
|
|
98
|
+
type: 'storystick.preview.snapshot',
|
|
99
|
+
epoch: currentEpoch,
|
|
100
|
+
renderId: assembled?.renderId ?? '',
|
|
101
|
+
pageDocId: assembled?.pageDocId ?? '',
|
|
102
|
+
capabilities: {
|
|
103
|
+
crossDocumentOverlayLabels: true,
|
|
104
|
+
},
|
|
105
|
+
// ADR-139 §2.1 + §5 wire-level rename.
|
|
106
|
+
iframeDeclaredDocumentIdsDebugHint: assembled?.iframeDeclaredDocumentIdsDebugHint ?? [],
|
|
107
|
+
items: assembled?.items ?? [],
|
|
108
|
+
codeVersion: readCodeVersionFromWindow(win),
|
|
109
|
+
timestamp: Date.now(),
|
|
110
|
+
...(liveRouteHash !== null && liveRouteHash !== ''
|
|
111
|
+
? { routeHash: liveRouteHash }
|
|
112
|
+
: {}),
|
|
113
|
+
...(liveRouteUrl !== null && liveRouteUrl !== ''
|
|
114
|
+
? { routeUrl: liveRouteUrl }
|
|
115
|
+
: {}),
|
|
116
|
+
};
|
|
117
|
+
const payloadBytes = estimatePreviewPayloadBytes(win, msg);
|
|
118
|
+
recordPreviewPerfMetric(win, {
|
|
119
|
+
name: 'snapshot.emit',
|
|
120
|
+
detail: {
|
|
121
|
+
itemCount: msg.items.length,
|
|
122
|
+
payloadBytes,
|
|
123
|
+
empty: assembled === null,
|
|
124
|
+
hasRouteHash: liveRouteHash !== null && liveRouteHash !== '',
|
|
125
|
+
hasRouteUrl: liveRouteUrl !== null && liveRouteUrl !== '',
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
options.onSnapshot?.(msg);
|
|
129
|
+
if (options.postToParent !== false) {
|
|
130
|
+
// Parent is the Studio; '*' is acceptable — UI geometry only, no
|
|
131
|
+
// secrets, and the Studio bridge filters by source + origin.
|
|
132
|
+
win.parent.postMessage(msg, '*');
|
|
133
|
+
}
|
|
134
|
+
return assembled === null ? null : msg;
|
|
135
|
+
}
|
|
136
|
+
function readCodeVersionFromWindow(w) {
|
|
137
|
+
const raw = w
|
|
138
|
+
.__STORYSTICK_CODE_VERSION__;
|
|
139
|
+
return typeof raw === 'string' && raw.length > 0 ? raw : '';
|
|
140
|
+
}
|
|
141
|
+
let stopped = false;
|
|
142
|
+
let hostResizeActive = false;
|
|
143
|
+
let hostResizeDirty = false;
|
|
144
|
+
let visibilityDirty = false;
|
|
145
|
+
function flushScheduledSnapshot() {
|
|
146
|
+
if (doc.visibilityState === 'hidden') {
|
|
147
|
+
visibilityDirty = true;
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
publish();
|
|
151
|
+
}
|
|
152
|
+
const scheduler = createPreviewObservationScheduler({
|
|
153
|
+
raf,
|
|
154
|
+
caf,
|
|
155
|
+
onFlush: flushScheduledSnapshot,
|
|
156
|
+
onEnqueue: () => {
|
|
157
|
+
recordPreviewPerfMetric(win, {
|
|
158
|
+
name: 'snapshot.schedule.enqueue',
|
|
159
|
+
});
|
|
160
|
+
},
|
|
161
|
+
onSkipPending: () => {
|
|
162
|
+
recordPreviewPerfMetric(win, {
|
|
163
|
+
name: 'snapshot.schedule.skipPending',
|
|
164
|
+
});
|
|
165
|
+
},
|
|
166
|
+
});
|
|
167
|
+
function schedule(reason = 'kick') {
|
|
168
|
+
if (stopped)
|
|
169
|
+
return;
|
|
170
|
+
if (doc.visibilityState === 'hidden') {
|
|
171
|
+
visibilityDirty = true;
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
if (hostResizeActive && isHostResizeDeferredReason(reason)) {
|
|
175
|
+
markHostResizeDirty(reason);
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
scheduler.request(reason);
|
|
179
|
+
}
|
|
180
|
+
function isHostResizeDeferredReason(reason) {
|
|
181
|
+
return reason === 'resize' || reason === 'mutation';
|
|
182
|
+
}
|
|
183
|
+
function markHostResizeDirty(reason) {
|
|
184
|
+
hostResizeDirty = true;
|
|
185
|
+
recordPreviewPerfMetric(win, {
|
|
186
|
+
name: 'snapshot.schedule.deferHostResize',
|
|
187
|
+
detail: { reason },
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
// ─── ResizeObserver — per slice root (primary geometry signal) ─
|
|
191
|
+
const ro = typeof ResizeObserver !== 'undefined'
|
|
192
|
+
? new ResizeObserver(() => schedule('resize'))
|
|
193
|
+
: null;
|
|
194
|
+
const observedResizeTargets = new Set();
|
|
195
|
+
let resizeTargetsDirty = true;
|
|
196
|
+
function reconcileResizeObserver() {
|
|
197
|
+
if (!ro || !resizeTargetsDirty)
|
|
198
|
+
return;
|
|
199
|
+
resizeTargetsDirty = false;
|
|
200
|
+
const targets = new Set(doc.querySelectorAll(SS_SLICE_SELECTOR));
|
|
201
|
+
// Also observe documentElement so a whole-page reflow is caught
|
|
202
|
+
// even before any slice element resizes.
|
|
203
|
+
if (doc.documentElement)
|
|
204
|
+
targets.add(doc.documentElement);
|
|
205
|
+
for (const target of observedResizeTargets) {
|
|
206
|
+
if (targets.has(target))
|
|
207
|
+
continue;
|
|
208
|
+
ro.unobserve(target);
|
|
209
|
+
observedResizeTargets.delete(target);
|
|
210
|
+
}
|
|
211
|
+
for (const target of targets) {
|
|
212
|
+
if (observedResizeTargets.has(target))
|
|
213
|
+
continue;
|
|
214
|
+
ro.observe(target);
|
|
215
|
+
observedResizeTargets.add(target);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
function isSyntheticOverlayNode(node) {
|
|
219
|
+
if (node instanceof Element) {
|
|
220
|
+
return node.closest(SS_PREVIEW_SYNTHETIC_OVERLAY_SELECTOR) !== null;
|
|
221
|
+
}
|
|
222
|
+
const parent = node.parentNode;
|
|
223
|
+
return (parent instanceof Element &&
|
|
224
|
+
parent.closest(SS_PREVIEW_SYNTHETIC_OVERLAY_SELECTOR) !== null);
|
|
225
|
+
}
|
|
226
|
+
function isSyntheticOverlayOnlyMutation(record) {
|
|
227
|
+
if (isSyntheticOverlayNode(record.target))
|
|
228
|
+
return true;
|
|
229
|
+
if (record.type !== 'childList')
|
|
230
|
+
return false;
|
|
231
|
+
const changedNodes = [...record.addedNodes, ...record.removedNodes];
|
|
232
|
+
return (changedNodes.length > 0 &&
|
|
233
|
+
changedNodes.every((node) => isSyntheticOverlayNode(node)));
|
|
234
|
+
}
|
|
235
|
+
// ─── MutationObserver — structural change (slices appear/move) ─
|
|
236
|
+
const mo = new MutationObserver((records) => {
|
|
237
|
+
if (records.every(isSyntheticOverlayOnlyMutation))
|
|
238
|
+
return;
|
|
239
|
+
if (records.some((record) => !isSyntheticOverlayOnlyMutation(record) &&
|
|
240
|
+
(record.type === 'childList' || record.attributeName === 'data-ss-slice')))
|
|
241
|
+
resizeTargetsDirty = true;
|
|
242
|
+
if (hostResizeActive) {
|
|
243
|
+
markHostResizeDirty('mutation');
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
schedule('mutation');
|
|
247
|
+
});
|
|
248
|
+
mo.observe(doc.documentElement, {
|
|
249
|
+
childList: true,
|
|
250
|
+
subtree: true,
|
|
251
|
+
attributes: true,
|
|
252
|
+
// `characterData` — a field's text changing without an element
|
|
253
|
+
// add/remove or attribute change (the common case: an RSC re-render
|
|
254
|
+
// edits a text node in place) is a pure characterData mutation.
|
|
255
|
+
// Without this explicit field geometry could stay stale.
|
|
256
|
+
// rAF-coalesced like every other signal — no storm.
|
|
257
|
+
characterData: true,
|
|
258
|
+
// `data-ss-slice` / `data-ss-bind` / `data-ss-model` — a binding or
|
|
259
|
+
// a slice model toggled/changed without a coincident subtree change
|
|
260
|
+
// still re-publishes. (`data-ss-model` changes on every customer
|
|
261
|
+
// re-render — its `renderId` is fresh — so this also guarantees a
|
|
262
|
+
// re-render always republishes.)
|
|
263
|
+
attributeFilter: [
|
|
264
|
+
'style',
|
|
265
|
+
'class',
|
|
266
|
+
'hidden',
|
|
267
|
+
'data-ss-slice',
|
|
268
|
+
'data-ss-bind',
|
|
269
|
+
'data-ss-model',
|
|
270
|
+
],
|
|
271
|
+
});
|
|
272
|
+
// ─── document.fonts.ready — one-shot, font-swap reflow ────────
|
|
273
|
+
const fontsLike = doc.fonts;
|
|
274
|
+
if (fontsLike && typeof fontsLike.ready?.then === 'function') {
|
|
275
|
+
void fontsLike.ready.then(() => schedule('fonts-ready'));
|
|
276
|
+
}
|
|
277
|
+
// ─── Studio → iframe message handler ─────────────────────────
|
|
278
|
+
function onMessage(event) {
|
|
279
|
+
if (event.source !== win.parent)
|
|
280
|
+
return;
|
|
281
|
+
if (!isStudioToIframe(event.data))
|
|
282
|
+
return;
|
|
283
|
+
const msg = event.data;
|
|
284
|
+
switch (msg.type) {
|
|
285
|
+
case 'render-mode': {
|
|
286
|
+
doc.body?.setAttribute('data-render-mode', msg.mode);
|
|
287
|
+
setRenderMode(msg.mode);
|
|
288
|
+
// ADR-119 D1 — capture the controller generation; a real bump
|
|
289
|
+
// means a cross-doc / mode change, so the next snapshot must
|
|
290
|
+
// carry it. Re-publish promptly so the Studio's gate recovers
|
|
291
|
+
// without waiting for an observer.
|
|
292
|
+
currentEpoch = msg.epoch ?? currentEpoch;
|
|
293
|
+
schedule('render-mode');
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
case 'overlay-mode':
|
|
297
|
+
doc.body?.setAttribute('data-overlay-mode', msg.mode);
|
|
298
|
+
return;
|
|
299
|
+
case 'device-width':
|
|
300
|
+
doc.body?.setAttribute('data-device-width', String(msg.width));
|
|
301
|
+
return;
|
|
302
|
+
case 'storystick.preview.host-resize':
|
|
303
|
+
hostResizeActive = msg.active;
|
|
304
|
+
if (msg.active) {
|
|
305
|
+
hostResizeDirty = false;
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
if (hostResizeDirty) {
|
|
309
|
+
hostResizeDirty = false;
|
|
310
|
+
schedule('resize');
|
|
311
|
+
}
|
|
312
|
+
return;
|
|
313
|
+
case 'storystick.preview.windowed-minimap':
|
|
314
|
+
schedule('kick');
|
|
315
|
+
return;
|
|
316
|
+
case 'highlight':
|
|
317
|
+
case 'scroll':
|
|
318
|
+
// ADR-129 — highlight + scroll-into-view are Studio-overlay
|
|
319
|
+
// concerns now (the parent-rendered overlay IS the highlight).
|
|
320
|
+
// The iframe-side handlers are retired; the cases stay so the
|
|
321
|
+
// `StudioToIframe` switch remains exhaustive.
|
|
322
|
+
return;
|
|
323
|
+
default: {
|
|
324
|
+
const _exhaustive = msg;
|
|
325
|
+
void _exhaustive;
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
win.addEventListener('message', onMessage);
|
|
331
|
+
function onVisibilityChange() {
|
|
332
|
+
if (doc.visibilityState === 'hidden') {
|
|
333
|
+
// Preserve one catch-up even if a scheduled frame was cancelled.
|
|
334
|
+
visibilityDirty = true;
|
|
335
|
+
scheduler.cancel();
|
|
336
|
+
ro?.disconnect();
|
|
337
|
+
observedResizeTargets.clear();
|
|
338
|
+
resizeTargetsDirty = true;
|
|
339
|
+
}
|
|
340
|
+
else if (visibilityDirty) {
|
|
341
|
+
visibilityDirty = false;
|
|
342
|
+
schedule('kick');
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
doc.addEventListener('visibilitychange', onVisibilityChange);
|
|
346
|
+
// Initial publish + readiness signal.
|
|
347
|
+
schedule('initial');
|
|
348
|
+
if (options.postToParent !== false) {
|
|
349
|
+
const ready = { type: 'ready' };
|
|
350
|
+
win.parent.postMessage(ready, '*');
|
|
351
|
+
}
|
|
352
|
+
return {
|
|
353
|
+
stop() {
|
|
354
|
+
if (stopped)
|
|
355
|
+
return;
|
|
356
|
+
stopped = true;
|
|
357
|
+
scheduler.cancel();
|
|
358
|
+
mo.disconnect();
|
|
359
|
+
ro?.disconnect();
|
|
360
|
+
observedResizeTargets.clear();
|
|
361
|
+
clearPreviewOverlayRegistry();
|
|
362
|
+
win.removeEventListener('message', onMessage);
|
|
363
|
+
doc.removeEventListener('visibilitychange', onVisibilityChange);
|
|
364
|
+
},
|
|
365
|
+
flush() {
|
|
366
|
+
if (stopped)
|
|
367
|
+
return null;
|
|
368
|
+
scheduler.cancel();
|
|
369
|
+
return publish();
|
|
370
|
+
},
|
|
371
|
+
kick(reason = 'kick') {
|
|
372
|
+
schedule(reason);
|
|
373
|
+
},
|
|
374
|
+
};
|
|
375
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { PreviewV2RevealSourceRequest, PreviewV2RevealSliceRequest } from '../provenance/reveal-contract.js';
|
|
2
|
+
import type { PreviewOverlayClickPayload } from '@storystick/transport-allowlist';
|
|
3
|
+
import { type PreviewOverlayClickTarget, type PreviewProvenanceOverlayProjection, type ProvenanceOwnerRejection } from '../provenance/index.js';
|
|
4
|
+
export type StandaloneOverlayMode = 'browse' | 'edit';
|
|
5
|
+
export interface StandaloneOverlaySnapshot {
|
|
6
|
+
readonly mode: StandaloneOverlayMode;
|
|
7
|
+
readonly available: boolean;
|
|
8
|
+
readonly generation: number | null;
|
|
9
|
+
readonly rejections: readonly ProvenanceOwnerRejection[];
|
|
10
|
+
}
|
|
11
|
+
export declare function readStandaloneOverlaySnapshot(win?: Window): StandaloneOverlaySnapshot;
|
|
12
|
+
export declare function subscribeStandaloneOverlaySnapshot(listener: () => void, win?: Window): () => void;
|
|
13
|
+
export declare function subscribeStandaloneOverlayIntent(listener: (target: PreviewOverlayClickTarget) => void, win?: Window): () => void;
|
|
14
|
+
export declare function setStandaloneOverlayMode(mode: StandaloneOverlayMode, win?: Window): void;
|
|
15
|
+
/** Paint and route the same validated projection used by Studio. The active
|
|
16
|
+
* projection is re-read on every click; a stale generation cannot become an
|
|
17
|
+
* edit intent. */
|
|
18
|
+
export declare function startStandaloneOverlayController(args: {
|
|
19
|
+
readonly sessionId: string;
|
|
20
|
+
readonly win?: Window;
|
|
21
|
+
}): () => void;
|
|
22
|
+
/** The click identity and its owner are resolved from one current projection.
|
|
23
|
+
* A host never receives an intent from stale paint or browse mode. */
|
|
24
|
+
export declare function resolveStandaloneOverlayIntent(args: {
|
|
25
|
+
readonly mode: StandaloneOverlayMode;
|
|
26
|
+
readonly projection: PreviewProvenanceOverlayProjection | null;
|
|
27
|
+
readonly click: PreviewOverlayClickPayload;
|
|
28
|
+
}): PreviewOverlayClickTarget | null;
|
|
29
|
+
/** The same provenance resolver and painter as Studio, transported locally. */
|
|
30
|
+
export declare function revealStandaloneSource(request: PreviewV2RevealSourceRequest, win?: Window): boolean;
|
|
31
|
+
export declare function revealStandaloneSlice(request: PreviewV2RevealSliceRequest, win?: Window): boolean;
|
|
32
|
+
/** Admission/render transitions suspend interaction without changing the user's mode. */
|
|
33
|
+
export declare function setStandaloneOverlaySuspended(suspended: boolean, win?: Window): void;
|