@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.
Files changed (245) hide show
  1. package/LICENSE +112 -0
  2. package/PUBLIC-API.md +55 -0
  3. package/README.md +42 -2
  4. package/dist/.source-hash +1 -0
  5. package/dist/authoring/runtime-contract.d.ts +89 -0
  6. package/dist/authoring/runtime-contract.js +28 -0
  7. package/dist/channel-taxonomy.d.ts +17 -0
  8. package/dist/channel-taxonomy.js +46 -0
  9. package/dist/context-command.d.ts +18 -0
  10. package/dist/context-command.js +38 -0
  11. package/dist/dev/suspicious-codeword-detector.d.ts +15 -0
  12. package/dist/dev/suspicious-codeword-detector.js +220 -0
  13. package/dist/dev/suspicious-codeword-detector.noop.d.ts +3 -0
  14. package/dist/dev/suspicious-codeword-detector.noop.js +4 -0
  15. package/dist/framework/browser-context.d.ts +2 -0
  16. package/dist/framework/browser-context.js +2 -0
  17. package/dist/framework/generation-refresh.d.ts +23 -0
  18. package/dist/framework/generation-refresh.js +82 -0
  19. package/dist/framework/generation-signal.d.ts +8 -0
  20. package/dist/framework/generation-signal.js +17 -0
  21. package/dist/framework/projection-generation-watch.d.ts +5 -0
  22. package/dist/framework/projection-generation-watch.js +53 -0
  23. package/dist/framework/render-receipt.d.ts +8 -0
  24. package/dist/framework/render-receipt.js +10 -0
  25. package/dist/framework/runtime-adapter-store.d.ts +30 -0
  26. package/dist/framework/runtime-adapter-store.js +81 -0
  27. package/dist/grant-coordinator.d.ts +53 -0
  28. package/dist/grant-coordinator.js +329 -0
  29. package/dist/grant-lifecycle.d.ts +52 -0
  30. package/dist/grant-lifecycle.js +74 -0
  31. package/dist/identity/__test-adapter.d.ts +34 -0
  32. package/dist/identity/__test-adapter.js +59 -0
  33. package/dist/identity/browser-context-gate.d.ts +4 -0
  34. package/dist/identity/browser-context-gate.js +22 -0
  35. package/dist/identity/browser-context.d.ts +13 -0
  36. package/dist/identity/browser-context.js +22 -0
  37. package/dist/identity/carriers/postmessage.d.ts +84 -0
  38. package/dist/identity/carriers/postmessage.js +177 -0
  39. package/dist/identity/carriers/storage-state.d.ts +41 -0
  40. package/dist/identity/carriers/storage-state.js +99 -0
  41. package/dist/identity/carriers/storage.d.ts +39 -0
  42. package/dist/identity/carriers/storage.js +182 -0
  43. package/dist/identity/carriers/url.d.ts +19 -0
  44. package/dist/identity/carriers/url.js +38 -0
  45. package/dist/identity/identity-state-machine.d.ts +70 -0
  46. package/dist/identity/identity-state-machine.js +244 -0
  47. package/dist/identity/identity-types.d.ts +192 -0
  48. package/dist/identity/identity-types.js +22 -0
  49. package/dist/identity/index.d.ts +18 -0
  50. package/dist/identity/index.js +16 -0
  51. package/dist/identity/preview-cookie.d.ts +9 -0
  52. package/dist/identity/preview-cookie.js +12 -0
  53. package/dist/identity/preview-draft.d.ts +6 -0
  54. package/dist/identity/preview-draft.js +25 -0
  55. package/dist/identity/preview-identity.d.ts +28 -0
  56. package/dist/identity/preview-identity.js +84 -0
  57. package/dist/identity/preview-scope.d.ts +28 -0
  58. package/dist/identity/preview-scope.js +47 -0
  59. package/dist/identity/resolver.d.ts +110 -0
  60. package/dist/identity/resolver.js +334 -0
  61. package/dist/identity/session-query.d.ts +4 -0
  62. package/dist/identity/session-query.js +19 -0
  63. package/dist/index.d.ts +90 -0
  64. package/dist/index.js +788 -0
  65. package/dist/interaction-mode.d.ts +58 -0
  66. package/dist/interaction-mode.js +93 -0
  67. package/dist/nav-handler.d.ts +95 -0
  68. package/dist/nav-handler.js +84 -0
  69. package/dist/overlay-label.d.ts +79 -0
  70. package/dist/overlay-label.js +73 -0
  71. package/dist/provenance/anchor-map.d.ts +95 -0
  72. package/dist/provenance/anchor-map.js +177 -0
  73. package/dist/provenance/carrier-plan.d.ts +51 -0
  74. package/dist/provenance/carrier-plan.js +90 -0
  75. package/dist/provenance/coded-text.d.ts +20 -0
  76. package/dist/provenance/coded-text.js +302 -0
  77. package/dist/provenance/direct-edit-locator.d.ts +29 -0
  78. package/dist/provenance/direct-edit-locator.js +218 -0
  79. package/dist/provenance/editable-leaf.d.ts +134 -0
  80. package/dist/provenance/editable-leaf.js +483 -0
  81. package/dist/provenance/editor-target.d.ts +29 -0
  82. package/dist/provenance/editor-target.js +46 -0
  83. package/dist/provenance/field-leaf-walker.d.ts +21 -0
  84. package/dist/provenance/field-leaf-walker.js +98 -0
  85. package/dist/provenance/frame-dependencies.d.ts +212 -0
  86. package/dist/provenance/frame-dependencies.js +550 -0
  87. package/dist/provenance/index.d.ts +26 -0
  88. package/dist/provenance/index.js +26 -0
  89. package/dist/provenance/local-events.d.ts +1 -0
  90. package/dist/provenance/local-events.js +1 -0
  91. package/dist/provenance/markers.d.ts +21 -0
  92. package/dist/provenance/markers.js +70 -0
  93. package/dist/provenance/multi-frame-anchor-map.d.ts +54 -0
  94. package/dist/provenance/multi-frame-anchor-map.js +109 -0
  95. package/dist/provenance/normalize.d.ts +2 -0
  96. package/dist/provenance/normalize.js +19 -0
  97. package/dist/provenance/overlay-focus-echo.d.ts +17 -0
  98. package/dist/provenance/overlay-focus-echo.js +16 -0
  99. package/dist/provenance/overlay-projection.d.ts +65 -0
  100. package/dist/provenance/overlay-projection.js +201 -0
  101. package/dist/provenance/owner-map.d.ts +55 -0
  102. package/dist/provenance/owner-map.js +239 -0
  103. package/dist/provenance/read-carrier.d.ts +22 -0
  104. package/dist/provenance/read-carrier.js +92 -0
  105. package/dist/provenance/recorder.d.ts +38 -0
  106. package/dist/provenance/recorder.js +82 -0
  107. package/dist/provenance/render-context.d.ts +106 -0
  108. package/dist/provenance/render-context.js +338 -0
  109. package/dist/provenance/reveal-contract.d.ts +44 -0
  110. package/dist/provenance/reveal-contract.js +1 -0
  111. package/dist/provenance/richtext-slice-embed-address.d.ts +20 -0
  112. package/dist/provenance/richtext-slice-embed-address.js +35 -0
  113. package/dist/provenance/runtime-script.d.ts +1 -0
  114. package/dist/provenance/runtime-script.js +2217 -0
  115. package/dist/provenance/sidecar-store.d.ts +63 -0
  116. package/dist/provenance/sidecar-store.js +151 -0
  117. package/dist/provenance/sidecar.d.ts +34 -0
  118. package/dist/provenance/sidecar.js +227 -0
  119. package/dist/provenance/slice-model.d.ts +28 -0
  120. package/dist/provenance/slice-model.js +86 -0
  121. package/dist/provenance/source-identity.d.ts +3 -0
  122. package/dist/provenance/source-identity.js +7 -0
  123. package/dist/provenance/source-map-materializer.d.ts +76 -0
  124. package/dist/provenance/source-map-materializer.js +286 -0
  125. package/dist/provenance/source-registry.d.ts +103 -0
  126. package/dist/provenance/source-registry.js +562 -0
  127. package/dist/provenance/transformed-output-gate.d.ts +161 -0
  128. package/dist/provenance/transformed-output-gate.js +171 -0
  129. package/dist/provenance/types.d.ts +102 -0
  130. package/dist/provenance/types.js +1 -0
  131. package/dist/refresh-policy.d.ts +11 -0
  132. package/dist/refresh-policy.js +14 -0
  133. package/dist/reveal-target.d.ts +8 -0
  134. package/dist/reveal-target.js +47 -0
  135. package/dist/route-paths.d.ts +11 -0
  136. package/dist/route-paths.js +14 -0
  137. package/dist/router-subscriber.d.ts +92 -0
  138. package/dist/router-subscriber.js +44 -0
  139. package/dist/runtime/anchor-types.d.ts +5 -0
  140. package/dist/runtime/anchor-types.js +1 -0
  141. package/dist/runtime/authority-store.d.ts +31 -0
  142. package/dist/runtime/authority-store.js +56 -0
  143. package/dist/runtime/boot.d.ts +52 -0
  144. package/dist/runtime/boot.js +379 -0
  145. package/dist/runtime/content-refresh.d.ts +30 -0
  146. package/dist/runtime/content-refresh.js +68 -0
  147. package/dist/runtime/emit-runtime-error.d.ts +51 -0
  148. package/dist/runtime/emit-runtime-error.js +64 -0
  149. package/dist/runtime/field-proxy.d.ts +112 -0
  150. package/dist/runtime/field-proxy.js +471 -0
  151. package/dist/runtime/form-contract-reporter.d.ts +99 -0
  152. package/dist/runtime/form-contract-reporter.js +391 -0
  153. package/dist/runtime/invalidation-adapter.d.ts +17 -0
  154. package/dist/runtime/invalidation-adapter.js +42 -0
  155. package/dist/runtime/live-query-overlay.d.ts +39 -0
  156. package/dist/runtime/live-query-overlay.js +345 -0
  157. package/dist/runtime/momentary-navigate.d.ts +16 -0
  158. package/dist/runtime/momentary-navigate.js +106 -0
  159. package/dist/runtime/observation-scheduler.d.ts +15 -0
  160. package/dist/runtime/observation-scheduler.js +42 -0
  161. package/dist/runtime/perf-metrics.d.ts +28 -0
  162. package/dist/runtime/perf-metrics.js +90 -0
  163. package/dist/runtime/preview-interaction-policy.d.ts +1 -0
  164. package/dist/runtime/preview-interaction-policy.js +103 -0
  165. package/dist/runtime/preview-overlay-painter.d.ts +27 -0
  166. package/dist/runtime/preview-overlay-painter.js +1732 -0
  167. package/dist/runtime/preview-overlay-sidecar.d.ts +26 -0
  168. package/dist/runtime/preview-overlay-sidecar.js +45 -0
  169. package/dist/runtime/preview-perspective-alignment.d.ts +15 -0
  170. package/dist/runtime/preview-perspective-alignment.js +40 -0
  171. package/dist/runtime/preview-perspective-intent-state.d.ts +23 -0
  172. package/dist/runtime/preview-perspective-intent-state.js +5 -0
  173. package/dist/runtime/preview-transition-state.d.ts +142 -0
  174. package/dist/runtime/preview-transition-state.js +257 -0
  175. package/dist/runtime/render-mode-state.d.ts +51 -0
  176. package/dist/runtime/render-mode-state.js +76 -0
  177. package/dist/runtime/request-id.d.ts +4 -0
  178. package/dist/runtime/request-id.js +8 -0
  179. package/dist/runtime/resolve-schema-field.d.ts +99 -0
  180. package/dist/runtime/resolve-schema-field.js +356 -0
  181. package/dist/runtime/runtime-error-dispatch.d.ts +23 -0
  182. package/dist/runtime/runtime-error-dispatch.js +75 -0
  183. package/dist/runtime/schema-store.d.ts +121 -0
  184. package/dist/runtime/schema-store.js +667 -0
  185. package/dist/runtime/snapshot-assembly.d.ts +104 -0
  186. package/dist/runtime/snapshot-assembly.js +553 -0
  187. package/dist/runtime/snapshot-reporter.d.ts +55 -0
  188. package/dist/runtime/snapshot-reporter.js +375 -0
  189. package/dist/runtime/standalone-overlay-controller.d.ts +33 -0
  190. package/dist/runtime/standalone-overlay-controller.js +281 -0
  191. package/dist/runtime/store.d.ts +307 -0
  192. package/dist/runtime/store.js +1026 -0
  193. package/dist/runtime/windowed-minimap-projection.d.ts +28 -0
  194. package/dist/runtime/windowed-minimap-projection.js +189 -0
  195. package/dist/runtime/workspace-shortcuts.d.ts +30 -0
  196. package/dist/runtime/workspace-shortcuts.js +188 -0
  197. package/dist/sdk-error-copy.d.ts +48 -0
  198. package/dist/sdk-error-copy.js +182 -0
  199. package/dist/select-cursor.d.ts +2 -0
  200. package/dist/select-cursor.js +23 -0
  201. package/dist/server/admission-cookie.d.ts +38 -0
  202. package/dist/server/admission-cookie.js +122 -0
  203. package/dist/server/control-client.d.ts +29 -0
  204. package/dist/server/control-client.js +22 -0
  205. package/dist/server/control-errors.d.ts +22 -0
  206. package/dist/server/control-errors.js +165 -0
  207. package/dist/server/draft-route.d.ts +12 -0
  208. package/dist/server/draft-route.js +45 -0
  209. package/dist/server/extract-token.d.ts +30 -0
  210. package/dist/server/extract-token.js +98 -0
  211. package/dist/server/grant.d.ts +128 -0
  212. package/dist/server/grant.js +395 -0
  213. package/dist/server/ordinary-read-provenance.d.ts +11 -0
  214. package/dist/server/ordinary-read-provenance.js +124 -0
  215. package/dist/server/preview-read-metadata.d.ts +10 -0
  216. package/dist/server/preview-read-metadata.js +27 -0
  217. package/dist/server/provenance-frame-builder.d.ts +53 -0
  218. package/dist/server/provenance-frame-builder.js +102 -0
  219. package/dist/server/provenance-runtime.d.ts +70 -0
  220. package/dist/server/provenance-runtime.js +46 -0
  221. package/dist/server/read-context-command.d.ts +11 -0
  222. package/dist/server/read-context-command.js +53 -0
  223. package/dist/server/read-identity.d.ts +3 -0
  224. package/dist/server/read-identity.js +63 -0
  225. package/dist/server/read-lifecycle.d.ts +17 -0
  226. package/dist/server/read-lifecycle.js +52 -0
  227. package/dist/server/render-frame-provenance.d.ts +133 -0
  228. package/dist/server/render-frame-provenance.js +676 -0
  229. package/dist/server/render-id.d.ts +15 -0
  230. package/dist/server/render-id.js +22 -0
  231. package/dist/server/route-document-provenance.d.ts +77 -0
  232. package/dist/server/route-document-provenance.js +1327 -0
  233. package/dist/server/route-session-cookie.d.ts +33 -0
  234. package/dist/server/route-session-cookie.js +54 -0
  235. package/dist/session-projections.d.ts +33 -0
  236. package/dist/session-projections.js +43 -0
  237. package/dist/session-subscriber.d.ts +124 -0
  238. package/dist/session-subscriber.js +79 -0
  239. package/dist/sha256-fallback.d.ts +30 -0
  240. package/dist/sha256-fallback.js +126 -0
  241. package/dist/types.d.ts +446 -0
  242. package/dist/types.js +85 -0
  243. package/dist/validators.d.ts +61 -0
  244. package/dist/validators.js +110 -0
  245. 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 {};