@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,345 @@
1
+ import { PREVIEW_LIVE_QUERY_MAX_QUERIES, PREVIEW_NAMESPACE, PREVIEW_PROTOCOL_VERSION, validatePreviewEnvelope, } from '@storystick/transport-allowlist';
2
+ import { estimatePreviewPayloadBytes, recordPreviewPerfMetric, } from './perf-metrics.js';
3
+ import { createPreviewRequestId } from './request-id.js';
4
+ const NOOP_HANDLE = Object.freeze({
5
+ queryInstanceId: null,
6
+ getSnapshot: () => null,
7
+ subscribe: () => () => { },
8
+ updateReceipt: () => { },
9
+ release: () => { },
10
+ });
11
+ export function createPreviewLiveQueryOverlayRuntime(options = {}) {
12
+ const randomHex = options.randomHex ?? createPreviewRequestId;
13
+ const now = options.now ?? Date.now;
14
+ const slotsByIdentity = new Map();
15
+ const slotsByInstance = new Map();
16
+ let configuration = {
17
+ enabled: false,
18
+ projectId: null,
19
+ sessionId: null,
20
+ parentOrigin: null,
21
+ };
22
+ let sourceEpoch = null;
23
+ let observedCapability = null;
24
+ let installedView = null;
25
+ const onMessage = (event) => {
26
+ const validated = validatePreviewEnvelope(event.data);
27
+ if (!validated.ok)
28
+ return;
29
+ acceptStudioEnvelope(validated.message, event.origin, installedView !== null && event.source === installedView.parent);
30
+ };
31
+ function configure(next) {
32
+ const previousIdentity = [
33
+ configuration.projectId,
34
+ configuration.sessionId,
35
+ configuration.parentOrigin,
36
+ ].join('|');
37
+ const nextIdentity = [
38
+ next.projectId,
39
+ next.sessionId,
40
+ next.parentOrigin,
41
+ ].join('|');
42
+ configuration = next;
43
+ const nextView = next.enabled
44
+ ? next.view ?? (typeof window === 'undefined' ? null : window)
45
+ : null;
46
+ if (installedView !== nextView) {
47
+ installedView?.removeEventListener('message', onMessage);
48
+ installedView = nextView;
49
+ installedView?.addEventListener('message', onMessage);
50
+ }
51
+ if (!next.enabled || previousIdentity !== nextIdentity) {
52
+ if (!next.enabled)
53
+ observedCapability = null;
54
+ sourceEpoch = capabilityMatchesConfiguration(observedCapability, next)
55
+ ? observedCapability.sourceEpoch
56
+ : null;
57
+ clearProjections();
58
+ if (sourceEpoch !== null) {
59
+ for (const slot of slotsByIdentity.values())
60
+ sendRegister(slot);
61
+ }
62
+ }
63
+ }
64
+ function retain(queryIdentity) {
65
+ let slot = slotsByIdentity.get(queryIdentity);
66
+ if (!slot) {
67
+ if (slotsByIdentity.size >= PREVIEW_LIVE_QUERY_MAX_QUERIES) {
68
+ return NOOP_HANDLE;
69
+ }
70
+ slot = {
71
+ queryIdentity,
72
+ queryInstanceId: `lq_${randomHex()}`,
73
+ listeners: new Set(),
74
+ retainCount: 0,
75
+ receiptId: null,
76
+ projection: null,
77
+ };
78
+ slotsByIdentity.set(queryIdentity, slot);
79
+ slotsByInstance.set(slot.queryInstanceId, slot);
80
+ }
81
+ slot.retainCount += 1;
82
+ let released = false;
83
+ return {
84
+ queryInstanceId: slot.queryInstanceId,
85
+ getSnapshot: () => released ? null : slot.projection,
86
+ subscribe(listener) {
87
+ if (released)
88
+ return () => { };
89
+ slot.listeners.add(listener);
90
+ return () => slot.listeners.delete(listener);
91
+ },
92
+ updateReceipt(receiptId) {
93
+ if (released || slot.receiptId === receiptId)
94
+ return;
95
+ const hadProjection = slot.projection !== null;
96
+ slot.receiptId = receiptId;
97
+ slot.projection = null;
98
+ if (hadProjection)
99
+ notify(slot);
100
+ if (receiptId !== null)
101
+ sendRegister(slot);
102
+ },
103
+ release() {
104
+ if (released)
105
+ return;
106
+ released = true;
107
+ slot.retainCount -= 1;
108
+ if (slot.retainCount > 0)
109
+ return;
110
+ sendUnregister(slot);
111
+ slotsByIdentity.delete(slot.queryIdentity);
112
+ slotsByInstance.delete(slot.queryInstanceId);
113
+ slot.listeners.clear();
114
+ slot.projection = null;
115
+ },
116
+ };
117
+ }
118
+ function acceptStudioEnvelope(message, eventOrigin, sourceIsParent) {
119
+ if (!configuration.enabled || !sourceIsParent)
120
+ return;
121
+ if (message.type === 'storystick.preview.session-broadcast') {
122
+ if (message.projectId !== configuration.projectId)
123
+ return;
124
+ if (configuration.sessionId !== null &&
125
+ message.sessionId !== configuration.sessionId)
126
+ return;
127
+ if (message.payload.parentOrigin !== eventOrigin)
128
+ return;
129
+ if (configuration.parentOrigin !== null &&
130
+ configuration.parentOrigin !== eventOrigin)
131
+ return;
132
+ const nextEpoch = message.payload.liveQueryOverlay?.sourceEpoch ?? null;
133
+ observedCapability = nextEpoch === null
134
+ ? null
135
+ : {
136
+ projectId: message.projectId,
137
+ sessionId: message.sessionId,
138
+ parentOrigin: eventOrigin,
139
+ sourceEpoch: nextEpoch,
140
+ };
141
+ if (nextEpoch === sourceEpoch)
142
+ return;
143
+ sourceEpoch = nextEpoch;
144
+ clearProjections();
145
+ if (sourceEpoch !== null) {
146
+ for (const slot of slotsByIdentity.values())
147
+ sendRegister(slot);
148
+ }
149
+ return;
150
+ }
151
+ if (message.type !== 'storystick.preview.live-query.projection')
152
+ return;
153
+ if (sourceEpoch === null)
154
+ return;
155
+ if (configuration.sessionId === null ||
156
+ configuration.parentOrigin === null ||
157
+ message.sessionId !== configuration.sessionId ||
158
+ message.projectId !== configuration.projectId ||
159
+ eventOrigin !== configuration.parentOrigin ||
160
+ message.payload.sourceEpoch !== sourceEpoch)
161
+ return;
162
+ const slot = slotsByInstance.get(message.payload.queryInstanceId);
163
+ if (!slot || slot.receiptId === null)
164
+ return;
165
+ if (slot.projection !== null &&
166
+ message.payload.sourceRevision <= slot.projection.sourceRevision)
167
+ return;
168
+ slot.projection = message.payload;
169
+ if (installedView !== null) {
170
+ const editorMutationAt = message.payload.editorMutationAt;
171
+ recordPreviewPerfMetric(installedView, {
172
+ name: 'preview.liveQuery.projectionReceived',
173
+ ...(editorMutationAt === undefined
174
+ ? {}
175
+ : {
176
+ durationMs: Math.max(0, epochNow(installedView) - editorMutationAt),
177
+ }),
178
+ detail: {
179
+ queryInstanceId: message.payload.queryInstanceId,
180
+ sourceRevision: message.payload.sourceRevision,
181
+ payloadBytes: estimatePreviewPayloadBytes(installedView, message.payload),
182
+ },
183
+ });
184
+ }
185
+ notify(slot);
186
+ }
187
+ function clear() {
188
+ sourceEpoch = null;
189
+ clearProjections();
190
+ }
191
+ function clearProjections() {
192
+ for (const slot of slotsByIdentity.values()) {
193
+ if (slot.projection === null)
194
+ continue;
195
+ slot.projection = null;
196
+ notify(slot);
197
+ }
198
+ }
199
+ function notify(slot) {
200
+ for (const listener of slot.listeners)
201
+ listener();
202
+ }
203
+ function sendRegister(slot) {
204
+ if (slot.receiptId === null || sourceEpoch === null)
205
+ return;
206
+ const identity = outboundIdentity();
207
+ if (identity === null)
208
+ return;
209
+ send({
210
+ namespace: PREVIEW_NAMESPACE,
211
+ version: PREVIEW_PROTOCOL_VERSION,
212
+ type: 'storystick.preview.live-query.register',
213
+ ...identity.envelope,
214
+ payload: {
215
+ queryInstanceId: slot.queryInstanceId,
216
+ sourceEpoch,
217
+ receiptId: slot.receiptId,
218
+ },
219
+ }, identity.parentOrigin);
220
+ }
221
+ function sendUnregister(slot) {
222
+ if (sourceEpoch === null)
223
+ return;
224
+ const identity = outboundIdentity();
225
+ if (identity === null)
226
+ return;
227
+ send({
228
+ namespace: PREVIEW_NAMESPACE,
229
+ version: PREVIEW_PROTOCOL_VERSION,
230
+ type: 'storystick.preview.live-query.unregister',
231
+ ...identity.envelope,
232
+ payload: {
233
+ queryInstanceId: slot.queryInstanceId,
234
+ sourceEpoch,
235
+ },
236
+ }, identity.parentOrigin);
237
+ }
238
+ function outboundIdentity() {
239
+ if (!configuration.enabled ||
240
+ configuration.projectId === null ||
241
+ configuration.sessionId === null ||
242
+ configuration.parentOrigin === null)
243
+ return null;
244
+ return {
245
+ parentOrigin: configuration.parentOrigin,
246
+ envelope: {
247
+ sessionId: configuration.sessionId,
248
+ projectId: configuration.projectId,
249
+ requestId: randomHex(),
250
+ nonce: randomHex(),
251
+ sentAt: now(),
252
+ },
253
+ };
254
+ }
255
+ function send(envelope, targetOrigin) {
256
+ if (options.send)
257
+ options.send(envelope, targetOrigin);
258
+ else
259
+ installedView?.parent.postMessage(envelope, targetOrigin);
260
+ }
261
+ return {
262
+ configure,
263
+ retain,
264
+ clear,
265
+ acceptStudioEnvelope,
266
+ activeQueryCount: () => slotsByIdentity.size,
267
+ };
268
+ }
269
+ function capabilityMatchesConfiguration(capability, configuration) {
270
+ return capability !== null &&
271
+ capability.projectId === configuration.projectId &&
272
+ (configuration.sessionId === null ||
273
+ capability.sessionId === configuration.sessionId) &&
274
+ (configuration.parentOrigin === null ||
275
+ capability.parentOrigin === configuration.parentOrigin);
276
+ }
277
+ function epochNow(view) {
278
+ return view.performance.timeOrigin + view.performance.now();
279
+ }
280
+ export const previewLiveQueryOverlay = createPreviewLiveQueryOverlayRuntime();
281
+ /**
282
+ * Derive a render-only QueryResult. The confirmed object is never mutated and
283
+ * neither membership, order, pageInfo nor metadata can change.
284
+ */
285
+ export function applyPreviewLiveQueryProjection(confirmed, projection) {
286
+ if (confirmed === undefined ||
287
+ confirmed.exactness === 'blocked' ||
288
+ projection === null)
289
+ return confirmed;
290
+ const data = confirmed.data;
291
+ if (Array.isArray(data)) {
292
+ let changed = false;
293
+ const visible = data.map((document) => {
294
+ const next = overlayDocument(document, projection);
295
+ if (next !== document)
296
+ changed = true;
297
+ return next;
298
+ });
299
+ if (!changed)
300
+ return confirmed;
301
+ return { ...confirmed, data: visible };
302
+ }
303
+ const visible = overlayDocument(data, projection);
304
+ if (visible === data)
305
+ return confirmed;
306
+ return { ...confirmed, data: visible };
307
+ }
308
+ function overlayDocument(document, projection) {
309
+ if (!isRecord(document) || document.documentId !== projection.documentId) {
310
+ return document;
311
+ }
312
+ if (!isRecord(document.fields))
313
+ return document;
314
+ let fields = document.fields;
315
+ for (const entry of projection.entries) {
316
+ fields = updateProjectedPath(fields, entry.path.split('.'), entry);
317
+ }
318
+ return fields === document.fields ? document : { ...document, fields };
319
+ }
320
+ function updateProjectedPath(root, segments, entry) {
321
+ const [head, ...tail] = segments;
322
+ if (head === undefined)
323
+ return root;
324
+ if (tail.length === 0) {
325
+ const hasCurrent = Object.prototype.hasOwnProperty.call(root, head);
326
+ if (!entry.present) {
327
+ if (!hasCurrent)
328
+ return root;
329
+ const next = { ...root };
330
+ delete next[head];
331
+ return next;
332
+ }
333
+ if (hasCurrent && Object.is(root[head], entry.value))
334
+ return root;
335
+ return { ...root, [head]: entry.value };
336
+ }
337
+ const current = root[head];
338
+ if (!isRecord(current) && !entry.present)
339
+ return root;
340
+ const nextChild = updateProjectedPath(isRecord(current) ? current : {}, tail, entry);
341
+ return nextChild === current ? root : { ...root, [head]: nextChild };
342
+ }
343
+ function isRecord(value) {
344
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
345
+ }
@@ -0,0 +1,16 @@
1
+ export declare const MOMENTARY_NAVIGATE_KEY_SIGNAL_TYPE: "storystick.preview.momentary-navigate-key";
2
+ export interface MomentaryNavigateKeySignal {
3
+ type: typeof MOMENTARY_NAVIGATE_KEY_SIGNAL_TYPE;
4
+ key: 'Alt';
5
+ active: boolean;
6
+ }
7
+ interface MomentaryNavigateKeyRelayOptions {
8
+ getTargetOrigin?: () => string | null;
9
+ }
10
+ export declare function isMomentaryNavigateKeySignal(data: unknown): data is MomentaryNavigateKeySignal;
11
+ export declare function installMomentaryNavigateKeyRelay(win?: Window, options?: MomentaryNavigateKeyRelayOptions): () => void;
12
+ /** Customer links are observed only within explicit editing carriers. */
13
+ export declare function getAnchorFromPreviewClickEvent(event: Event, win?: Window): HTMLAnchorElement | null;
14
+ export declare function isMomentaryNavigateKeyDown(event: KeyboardEvent): boolean;
15
+ export declare function normalizeMomentaryNavigateLinkClick(event: MouseEvent, target: HTMLAnchorElement, win?: Window): boolean;
16
+ export {};
@@ -0,0 +1,106 @@
1
+ export const MOMENTARY_NAVIGATE_KEY_SIGNAL_TYPE = 'storystick.preview.momentary-navigate-key';
2
+ export function isMomentaryNavigateKeySignal(data) {
3
+ if (data === null || typeof data !== 'object')
4
+ return false;
5
+ const signal = data;
6
+ return (signal.type === MOMENTARY_NAVIGATE_KEY_SIGNAL_TYPE &&
7
+ signal.key === 'Alt' &&
8
+ typeof signal.active === 'boolean');
9
+ }
10
+ export function installMomentaryNavigateKeyRelay(win = window, options = {}) {
11
+ if (win.parent === win)
12
+ return () => { };
13
+ const doc = win.document;
14
+ let lastActive = null;
15
+ const emit = (active) => {
16
+ if (lastActive === active)
17
+ return;
18
+ lastActive = active;
19
+ const targetOrigin = options.getTargetOrigin?.() ?? '*';
20
+ win.parent.postMessage({
21
+ type: MOMENTARY_NAVIGATE_KEY_SIGNAL_TYPE,
22
+ key: 'Alt',
23
+ active,
24
+ }, targetOrigin && targetOrigin.length > 0 ? targetOrigin : '*');
25
+ };
26
+ const onKeyDown = (event) => {
27
+ if (!isMomentaryNavigateKeyDown(event))
28
+ return;
29
+ emit(true);
30
+ };
31
+ const onKeyUp = (event) => {
32
+ if (event.key === 'Alt' || !event.altKey)
33
+ emit(false);
34
+ };
35
+ const release = () => emit(false);
36
+ doc.addEventListener('keydown', onKeyDown, true);
37
+ doc.addEventListener('keyup', onKeyUp, true);
38
+ win.addEventListener('pagehide', release);
39
+ doc.addEventListener('visibilitychange', release);
40
+ return () => {
41
+ if (lastActive === true)
42
+ emit(false);
43
+ doc.removeEventListener('keydown', onKeyDown, true);
44
+ doc.removeEventListener('keyup', onKeyUp, true);
45
+ win.removeEventListener('pagehide', release);
46
+ doc.removeEventListener('visibilitychange', release);
47
+ };
48
+ }
49
+ /** Customer links are observed only within explicit editing carriers. */
50
+ export function getAnchorFromPreviewClickEvent(event, win = window) {
51
+ const view = win;
52
+ const nodes = typeof event.composedPath === 'function' ? event.composedPath() : [];
53
+ for (const node of [...nodes, event.target]) {
54
+ if (!(node instanceof view.Element))
55
+ continue;
56
+ const anchor = node.closest('a[href]');
57
+ if (!(anchor instanceof view.HTMLAnchorElement))
58
+ continue;
59
+ if (anchor.matches('[data-ss-e], [data-ss-p]') ||
60
+ anchor.closest('[data-ss-e], [data-ss-p]') !== null ||
61
+ anchor.querySelector('[data-ss-e], [data-ss-p]') !== null) {
62
+ return anchor;
63
+ }
64
+ }
65
+ return null;
66
+ }
67
+ export function isMomentaryNavigateKeyDown(event) {
68
+ if (event.key !== 'Alt')
69
+ return false;
70
+ if (!event.altKey)
71
+ return false;
72
+ if (event.ctrlKey || event.metaKey)
73
+ return false;
74
+ if (event.getModifierState('AltGraph'))
75
+ return false;
76
+ return true;
77
+ }
78
+ export function normalizeMomentaryNavigateLinkClick(event, target, win = window) {
79
+ if (!shouldNormalizeMomentaryNavigateLinkClick(event, target, win)) {
80
+ return false;
81
+ }
82
+ event.preventDefault();
83
+ event.stopImmediatePropagation();
84
+ target.click();
85
+ return true;
86
+ }
87
+ function shouldNormalizeMomentaryNavigateLinkClick(event, target, win) {
88
+ if (event.defaultPrevented)
89
+ return false;
90
+ if (event.button !== 0)
91
+ return false;
92
+ if (!event.altKey)
93
+ return false;
94
+ if (event.ctrlKey || event.metaKey || event.shiftKey)
95
+ return false;
96
+ if (target.hasAttribute('download'))
97
+ return false;
98
+ let url;
99
+ try {
100
+ url = new URL(target.href, win.location.href);
101
+ }
102
+ catch {
103
+ return false;
104
+ }
105
+ return url.origin === win.location.origin;
106
+ }
@@ -0,0 +1,15 @@
1
+ export type PreviewObservationReason = 'resize' | 'mutation' | 'fonts-ready' | 'render-mode' | 'initial' | 'kick' | 'cache-version-changed' | 'route-hash-ready' | 'schema-ready' | 'form-contract';
2
+ export interface PreviewObservationScheduler {
3
+ request(reason: PreviewObservationReason): void;
4
+ flush(): void;
5
+ cancel(): void;
6
+ isPending(): boolean;
7
+ }
8
+ export interface CreatePreviewObservationSchedulerOptions {
9
+ raf: (cb: () => void) => number;
10
+ caf: (handle: number) => void;
11
+ onFlush: (reason: PreviewObservationReason) => void;
12
+ onEnqueue?: (reason: PreviewObservationReason) => void;
13
+ onSkipPending?: (reason: PreviewObservationReason, pendingReason: PreviewObservationReason) => void;
14
+ }
15
+ export declare function createPreviewObservationScheduler(opts: CreatePreviewObservationSchedulerOptions): PreviewObservationScheduler;
@@ -0,0 +1,42 @@
1
+ export function createPreviewObservationScheduler(opts) {
2
+ let pending = null;
3
+ let pendingReason = null;
4
+ const run = () => {
5
+ if (pending === null)
6
+ return;
7
+ const reason = pendingReason ?? 'kick';
8
+ pending = null;
9
+ pendingReason = null;
10
+ opts.onFlush(reason);
11
+ };
12
+ return {
13
+ request(reason) {
14
+ if (pending !== null) {
15
+ opts.onSkipPending?.(reason, pendingReason ?? reason);
16
+ return;
17
+ }
18
+ pendingReason = reason;
19
+ opts.onEnqueue?.(reason);
20
+ pending = opts.raf(run);
21
+ },
22
+ flush() {
23
+ if (pending !== null) {
24
+ opts.caf(pending);
25
+ pending = null;
26
+ }
27
+ const reason = pendingReason ?? 'kick';
28
+ pendingReason = null;
29
+ opts.onFlush(reason);
30
+ },
31
+ cancel() {
32
+ if (pending !== null) {
33
+ opts.caf(pending);
34
+ }
35
+ pending = null;
36
+ pendingReason = null;
37
+ },
38
+ isPending() {
39
+ return pending !== null;
40
+ },
41
+ };
42
+ }
@@ -0,0 +1,28 @@
1
+ export type PreviewPerfMetricName = 'preview.grant.request' | 'preview.grant.response' | 'preview.navigate.received' | 'preview.navigate.paintAck' | 'next.navigate.context' | 'next.navigate.replace' | 'next.navigate.refresh' | 'next.navigate.refreshSettled' | 'next.navigate.locationCommitted' | 'next.contentRefresh.schedule' | 'next.contentRefresh.refresh' | 'next.contentRefresh.skipDuplicate' | 'next.contentRefresh.skipUnrelated' | 'next.contentRefresh.skipNavigateRefresh' | 'next.contentRefresh.skipEcho' | 'next.projectionRefresh.request' | 'next.projectionRefresh.satisfied' | 'next.previewCookieRefresh' | 'next.previewCookieRefreshOutcome' | 'preview.authTokenObserved' | 'snapshot.assemble.async' | 'snapshot.assemble.sync' | 'snapshot.emit' | 'snapshot.publish.drop' | 'snapshot.schedule.deferHostResize' | 'snapshot.schedule.enqueue' | 'snapshot.schedule.skipPending' | 'studio.previewBoot.mark' | 'studio.previewBoot.summary' | 'studio.previewOrchestrator.event' | 'studio.previewSurface.render' | 'studio.previewSurface.snapshotWrite' | 'studio.previewV2.keydownToPatchQueued' | 'studio.previewV2.patchQueued' | 'studio.previewV2.patchSendDropped' | 'studio.previewV2.patchPostMessage' | 'studio.previewV2.patchSent' | 'studio.previewV2.sourceIdentityMissing' | 'studio.previewV2.overlayMapAckTimeout' | 'studio.previewV2.overlayMapRejected' | 'studio.previewV2.overlayMapSent' | 'studio.previewV2.overlayReadinessStale' | 'studio.previewV2.overlayClickIgnored' | 'studio.previewV2.directLocatorRevalidation' | 'studio.previewV2.overlayPlanNull' | 'studio.previewV2.overlayReadyIgnored' | 'studio.previewV2.overlayReadyObserved' | 'studio.previewV2.epochAckMissed' | 'studio.previewV2.revealSkipped' | 'studio.previewV2.revealSent' | 'studio.previewV2.revealResult' | 'studio.previewV2.geometryTransactionStart' | 'studio.previewV2.geometryTransactionCommit' | 'studio.previewV2.geometryTransactionSlow' | 'studio.previewV2.geometryTransactionSuperseded' | 'studio.previewV2.recoveryAction' | 'previewV2.patchReceived' | 'previewV2.patchBatchRejected' | 'previewV2.patchSentToReceived' | 'previewV2.patchReceivedToStoreApplied' | 'previewV2.storeApplied' | 'previewV2.storeAppliedToPaint' | 'previewV2.dataSeamRejected' | 'studio.saveAttempt' | 'studio.saveAccepted' | 'studio.saveRejected' | 'studio.saveUnknown' | 'studio.saveNoop' | 'preview.contentRevisionObserved' | 'preview.overlayMapSent' | 'preview.overlayPaintApplied' | 'preview.overlayBoxesCommitted' | 'preview.overlayGeometryPass' | 'preview.overlayPresentationPass' | 'studio.editToPaint' | 'preview.liveQuery.projectionReceived' | 'preview.liveQuery.firstPaintOpportunity' | 'studio.preview.overlayMapAckRetry' | 'studio.preview.overlayReadinessStaleCount';
2
+ export type PreviewPerfMetricValue = string | number | boolean | null;
3
+ export interface PreviewPerfMetric {
4
+ name: PreviewPerfMetricName;
5
+ ts: number;
6
+ durationMs?: number;
7
+ detail?: Record<string, PreviewPerfMetricValue>;
8
+ }
9
+ export interface PreviewPerfSink {
10
+ enabled?: boolean;
11
+ maxEvents?: number;
12
+ events?: PreviewPerfMetric[];
13
+ onEvent?: (metric: PreviewPerfMetric) => void;
14
+ }
15
+ export declare function isPreviewPerfEnabled(win: Window): boolean;
16
+ export declare function previewPerfNow(win: Window): number;
17
+ export declare function recordPreviewPerfMetric(win: Window, metric: Omit<PreviewPerfMetric, 'ts'> & {
18
+ ts?: number;
19
+ }): void;
20
+ export declare function recordPreviewPerfMeasure(win: Window, name: PreviewPerfMetricName, startedAt: number, detail?: Record<string, PreviewPerfMetricValue>): void;
21
+ export declare function estimatePreviewPayloadBytes(win: Window, payload: unknown): number | null;
22
+ export declare function computeLiveQueryPaintP50P95(events: readonly PreviewPerfMetric[]): {
23
+ p50: number;
24
+ p95: number;
25
+ p99: number;
26
+ max: number;
27
+ count: number;
28
+ };
@@ -0,0 +1,90 @@
1
+ const DEFAULT_MAX_EVENTS = 250;
2
+ const MAX_EVENTS = 1000;
3
+ const MAX_DETAIL_FIELDS = 32;
4
+ export function isPreviewPerfEnabled(win) {
5
+ return readPreviewPerfSink(win)?.enabled === true;
6
+ }
7
+ export function previewPerfNow(win) {
8
+ const perf = win.performance;
9
+ return perf && typeof perf.now === 'function' ? perf.now() : Date.now();
10
+ }
11
+ export function recordPreviewPerfMetric(win, metric) {
12
+ const sink = readPreviewPerfSink(win);
13
+ if (sink?.enabled !== true)
14
+ return;
15
+ try {
16
+ const event = {
17
+ name: metric.name,
18
+ ts: Number.isFinite(metric.ts) ? metric.ts : previewPerfNow(win),
19
+ ...(typeof metric.durationMs === 'number' && Number.isFinite(metric.durationMs) && metric.durationMs >= 0
20
+ ? { durationMs: metric.durationMs } : {}),
21
+ ...(metric.detail ? { detail: scalarMetricDetail(metric.detail) } : {}),
22
+ };
23
+ if (!Array.isArray(sink.events))
24
+ sink.events = [];
25
+ sink.events.push(event);
26
+ const maxEvents = typeof sink.maxEvents === 'number' && Number.isFinite(sink.maxEvents) && sink.maxEvents >= 1
27
+ ? Math.min(MAX_EVENTS, Math.floor(sink.maxEvents))
28
+ : DEFAULT_MAX_EVENTS;
29
+ if (sink.events.length > maxEvents) {
30
+ sink.events.splice(0, sink.events.length - maxEvents);
31
+ }
32
+ sink.onEvent?.(event);
33
+ }
34
+ catch {
35
+ // Perf telemetry must never affect preview runtime behavior.
36
+ }
37
+ }
38
+ function scalarMetricDetail(detail) {
39
+ // Metric names carry the closed phase vocabulary. Keep counts/flags only:
40
+ // route URLs, content, credentials and high-cardinality identifiers do not
41
+ // belong in a metrics buffer or in its external onEvent callback.
42
+ return Object.fromEntries(Object.entries(detail).filter(([key, value]) => /^[A-Za-z][A-Za-z0-9_]{0,63}$/.test(key) && (value === null || typeof value === 'boolean' ||
43
+ (typeof value === 'number' && Number.isFinite(value)))).slice(0, MAX_DETAIL_FIELDS));
44
+ }
45
+ export function recordPreviewPerfMeasure(win, name, startedAt, detail) {
46
+ if (!isPreviewPerfEnabled(win))
47
+ return;
48
+ recordPreviewPerfMetric(win, {
49
+ name,
50
+ durationMs: Math.max(0, previewPerfNow(win) - startedAt),
51
+ detail,
52
+ });
53
+ }
54
+ export function estimatePreviewPayloadBytes(win, payload) {
55
+ if (!isPreviewPerfEnabled(win))
56
+ return null;
57
+ try {
58
+ const json = JSON.stringify(payload);
59
+ const encoder = new TextEncoder();
60
+ return encoder.encode(json).byteLength;
61
+ }
62
+ catch {
63
+ return null;
64
+ }
65
+ }
66
+ function readPreviewPerfSink(win) {
67
+ const raw = win.__STORYSTICK_PREVIEW_PERF__;
68
+ if (raw === null || typeof raw !== 'object')
69
+ return null;
70
+ return raw;
71
+ }
72
+ export function computeLiveQueryPaintP50P95(events) {
73
+ const samples = events.flatMap((event) => event.name === 'preview.liveQuery.firstPaintOpportunity' &&
74
+ typeof event.durationMs === 'number' &&
75
+ Number.isFinite(event.durationMs) && event.durationMs >= 0
76
+ ? [event.durationMs]
77
+ : []).sort((a, b) => a - b);
78
+ if (samples.length === 0)
79
+ return { p50: 0, p95: 0, p99: 0, max: 0, count: 0 };
80
+ return {
81
+ p50: percentile(samples, 0.5),
82
+ p95: percentile(samples, 0.95),
83
+ p99: percentile(samples, 0.99),
84
+ max: samples[samples.length - 1],
85
+ count: samples.length,
86
+ };
87
+ }
88
+ function percentile(values, ratio) {
89
+ return values[Math.ceil(values.length * ratio) - 1];
90
+ }
@@ -0,0 +1 @@
1
+ export declare function installPreviewInteractionPolicy(win: Window): () => void;