@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,26 @@
1
+ /**
2
+ * ADR-142 P1 — in-iframe Preview overlay sidecar registry.
3
+ *
4
+ * Snapshot assembly can see the live Element for every produced item,
5
+ * but the Studio only receives serialisable SnapshotItems. The sidecar
6
+ * keeps the Element lookup in iframe memory and exposes opaque
7
+ * `snapshotItemKey` ids that Studio can echo back in an approved
8
+ * overlay-map.
9
+ */
10
+ import type { SnapshotItem } from '../types.js';
11
+ export interface PreviewOverlayRegistryTuple {
12
+ renderId: string;
13
+ routeHash: string | null;
14
+ codeVersion: string;
15
+ cacheVersion: number;
16
+ }
17
+ export interface PreviewOverlayRegistryDraft {
18
+ attach<T extends SnapshotItem>(item: T, element: Element): T;
19
+ entries(): ReadonlyMap<string, Element>;
20
+ }
21
+ export declare function createPreviewOverlayRegistryDraft(): PreviewOverlayRegistryDraft;
22
+ export declare function attachPreviewOverlayRegistryTarget<T extends SnapshotItem>(item: T, element: Element, draft?: PreviewOverlayRegistryDraft): T;
23
+ export declare function commitPreviewOverlayRegistryDraft(draft: PreviewOverlayRegistryDraft, tuple: PreviewOverlayRegistryTuple): void;
24
+ export declare function clearPreviewOverlayRegistry(): void;
25
+ export declare function getPreviewOverlayTarget(snapshotItemKey: string): Element | null;
26
+ export declare function getPreviewOverlayRegistryTuple(): PreviewOverlayRegistryTuple | null;
@@ -0,0 +1,45 @@
1
+ /**
2
+ * ADR-142 P1 — in-iframe Preview overlay sidecar registry.
3
+ *
4
+ * Snapshot assembly can see the live Element for every produced item,
5
+ * but the Studio only receives serialisable SnapshotItems. The sidecar
6
+ * keeps the Element lookup in iframe memory and exposes opaque
7
+ * `snapshotItemKey` ids that Studio can echo back in an approved
8
+ * overlay-map.
9
+ */
10
+ let activeTargets = new Map();
11
+ let activeTuple = null;
12
+ export function createPreviewOverlayRegistryDraft() {
13
+ let ordinal = 0;
14
+ const entries = new Map();
15
+ return {
16
+ attach(item, element) {
17
+ const snapshotItemKey = makeSnapshotItemKey(item, ordinal++);
18
+ entries.set(snapshotItemKey, element);
19
+ return { ...item, snapshotItemKey };
20
+ },
21
+ entries() {
22
+ return entries;
23
+ },
24
+ };
25
+ }
26
+ export function attachPreviewOverlayRegistryTarget(item, element, draft) {
27
+ return draft ? draft.attach(item, element) : item;
28
+ }
29
+ export function commitPreviewOverlayRegistryDraft(draft, tuple) {
30
+ activeTargets = new Map(draft.entries());
31
+ activeTuple = tuple;
32
+ }
33
+ export function clearPreviewOverlayRegistry() {
34
+ activeTargets.clear();
35
+ activeTuple = null;
36
+ }
37
+ export function getPreviewOverlayTarget(snapshotItemKey) {
38
+ return activeTargets.get(snapshotItemKey) ?? null;
39
+ }
40
+ export function getPreviewOverlayRegistryTuple() {
41
+ return activeTuple;
42
+ }
43
+ function makeSnapshotItemKey(_item, ordinal) {
44
+ return `snap1:${ordinal}`;
45
+ }
@@ -0,0 +1,15 @@
1
+ import type { PreviewPerspective, PreviewReadContext } from '@storystick/types/preview-query-targeting';
2
+ export type PreviewPerspectiveAlignmentObservation = {
3
+ readonly kind: 'unknown';
4
+ } | {
5
+ readonly kind: 'aligned';
6
+ readonly context: PreviewReadContext;
7
+ } | {
8
+ readonly kind: 'conflict';
9
+ readonly addressPerspective: PreviewPerspective | null;
10
+ readonly context: PreviewReadContext;
11
+ };
12
+ export declare const UNKNOWN_PREVIEW_PERSPECTIVE_ALIGNMENT: PreviewPerspectiveAlignmentObservation;
13
+ export declare function derivePreviewPerspectiveAlignment(addressPerspective: PreviewPerspective | null, context: PreviewReadContext | null): PreviewPerspectiveAlignmentObservation;
14
+ export declare function previewPerspectiveAlignmentEquals(left: PreviewPerspectiveAlignmentObservation, right: PreviewPerspectiveAlignmentObservation): boolean;
15
+ export declare function previewPerspectivesEqual(left: PreviewPerspective, right: PreviewPerspective): boolean;
@@ -0,0 +1,40 @@
1
+ export const UNKNOWN_PREVIEW_PERSPECTIVE_ALIGNMENT = Object.freeze({ kind: 'unknown' });
2
+ export function derivePreviewPerspectiveAlignment(addressPerspective, context) {
3
+ if (context === null)
4
+ return UNKNOWN_PREVIEW_PERSPECTIVE_ALIGNMENT;
5
+ if (addressPerspective && previewPerspectivesEqual(addressPerspective, context.perspective)) {
6
+ return { kind: 'aligned', context };
7
+ }
8
+ return { kind: 'conflict', addressPerspective, context };
9
+ }
10
+ export function previewPerspectiveAlignmentEquals(left, right) {
11
+ if (left.kind !== right.kind)
12
+ return false;
13
+ if (left.kind === 'unknown' || right.kind === 'unknown')
14
+ return true;
15
+ if (!previewReadContextsEqual(left.context, right.context))
16
+ return false;
17
+ return (left.kind === 'aligned' ||
18
+ right.kind === 'aligned' ||
19
+ (left.addressPerspective === null || right.addressPerspective === null
20
+ ? left.addressPerspective === right.addressPerspective
21
+ : previewPerspectivesEqual(left.addressPerspective, right.addressPerspective)));
22
+ }
23
+ export function previewPerspectivesEqual(left, right) {
24
+ if (left.kind !== right.kind)
25
+ return false;
26
+ switch (left.kind) {
27
+ case 'published':
28
+ case 'living':
29
+ return true;
30
+ case 'variant':
31
+ return right.kind === 'variant' && left.variantId === right.variantId;
32
+ case 'release':
33
+ return right.kind === 'release' && left.releaseId === right.releaseId;
34
+ }
35
+ }
36
+ function previewReadContextsEqual(left, right) {
37
+ return (left.contextId === right.contextId &&
38
+ left.revision === right.revision &&
39
+ previewPerspectivesEqual(left.perspective, right.perspective));
40
+ }
@@ -0,0 +1,23 @@
1
+ import type { PreviewPerspective, PreviewReadContext } from '@storystick/types/preview-query-targeting';
2
+ interface PreviewPerspectiveIntentObservationBase {
3
+ readonly perspective: PreviewPerspective;
4
+ readonly generation: number | null;
5
+ readonly commandId: string | null;
6
+ }
7
+ export type PreviewPerspectiveIntentObservation = {
8
+ readonly kind: 'idle';
9
+ } | (PreviewPerspectiveIntentObservationBase & {
10
+ readonly kind: 'pending';
11
+ }) | (PreviewPerspectiveIntentObservationBase & {
12
+ readonly kind: 'accepted';
13
+ readonly acceptance: 'applied' | 'local-noop';
14
+ readonly context: PreviewReadContext;
15
+ }) | (PreviewPerspectiveIntentObservationBase & {
16
+ readonly kind: 'conflict';
17
+ readonly context: PreviewReadContext;
18
+ }) | (PreviewPerspectiveIntentObservationBase & {
19
+ readonly kind: 'unavailable' | 'superseded';
20
+ });
21
+ export declare const IDLE_PREVIEW_PERSPECTIVE_INTENT: PreviewPerspectiveIntentObservation;
22
+ export declare function previewPerspectiveIntentAppliesToGeneration(observation: PreviewPerspectiveIntentObservation, generation: number): boolean;
23
+ export {};
@@ -0,0 +1,5 @@
1
+ export const IDLE_PREVIEW_PERSPECTIVE_INTENT = Object.freeze({ kind: 'idle' });
2
+ export function previewPerspectiveIntentAppliesToGeneration(observation, generation) {
3
+ return (observation.kind !== 'idle' &&
4
+ (observation.generation === null || observation.generation === generation));
5
+ }
@@ -0,0 +1,142 @@
1
+ import type { PreviewReadContext } from '@storystick/types/preview-query-targeting';
2
+ import type { PreviewPerspective } from '@storystick/types/preview-query-targeting';
3
+ export declare const PREVIEW_TRANSITION_SLOW_MS = 1500;
4
+ export declare const PREVIEW_TRANSITION_UNCONFIRMED_MS = 8000;
5
+ export type PreviewTransitionReason = 'first-render' | 'document-open' | 'preview-intent' | 'resync' | 'host-loading' | 'host-loading-timeout';
6
+ export type PreviewTransitionSourceOfTruth = 'studio' | 'iframe' | 'system';
7
+ export type PreviewHostStatus = 'none' | 'loading' | 'valid' | 'invalid' | 'timed-out';
8
+ export type PreviewTransitionPerspective = PreviewPerspective;
9
+ export interface PreviewTransitionTarget {
10
+ sessionId: string | null;
11
+ editorDocumentId: string | null;
12
+ expectedPreviewDocumentId: string | null;
13
+ /** Locale selected by Studio. Required as an explicit coordinate when a
14
+ * draft has no public pathname from which the runtime could derive it. */
15
+ expectedLocale?: string | null;
16
+ expectedPathname: string | null;
17
+ /**
18
+ * Present when completion must prove the requested content perspective, not
19
+ * merely the route tuple. Route snapshots do not carry this evidence;
20
+ * command-correlated paint ACKs and provenance anchor maps do.
21
+ */
22
+ expectedPerspective?: PreviewTransitionPerspective;
23
+ expectedSearch: string;
24
+ expectedHash: string;
25
+ draftDocumentId?: string | null;
26
+ hostDocumentId?: string | null;
27
+ hostStatus: PreviewHostStatus;
28
+ commandId?: string | null;
29
+ sourceOfTruth?: PreviewTransitionSourceOfTruth;
30
+ }
31
+ export type NormalizedPreviewTransitionTarget = PreviewTransitionTarget & {
32
+ sourceOfTruth: PreviewTransitionSourceOfTruth;
33
+ };
34
+ export interface PreviousPreviewConfirmation {
35
+ /** Session whose rendered frame supplied this confirmation. */
36
+ sessionId: string | null;
37
+ /** Effective locale when known; null means the runtime confirmed no locale. */
38
+ locale?: string | null;
39
+ /** Content perspective proven by the same rendered frame when available. */
40
+ perspective?: PreviewPerspective;
41
+ documentId: string | null;
42
+ pathname: string | null;
43
+ search: string;
44
+ hash: string;
45
+ }
46
+ export type PreviewTransitionState = {
47
+ kind: 'idle';
48
+ } | {
49
+ kind: 'pending';
50
+ generation: number;
51
+ reason: PreviewTransitionReason;
52
+ sourceOfTruth: PreviewTransitionSourceOfTruth;
53
+ target: NormalizedPreviewTransitionTarget;
54
+ previousConfirmed?: PreviousPreviewConfirmation;
55
+ startedAt: number;
56
+ slowAt: number;
57
+ deadlineAt: number;
58
+ slow: boolean;
59
+ } | {
60
+ kind: 'unconfirmed';
61
+ generation: number;
62
+ sourceOfTruth: PreviewTransitionSourceOfTruth;
63
+ target: NormalizedPreviewTransitionTarget;
64
+ previousConfirmed?: PreviousPreviewConfirmation;
65
+ startedAt: number;
66
+ failedAt: number;
67
+ sourceReason: PreviewTransitionReason;
68
+ reason: 'soft-navigation-unconfirmed' | 'host-loading-timeout';
69
+ };
70
+ export type PreviewTransitionConfirmation = {
71
+ kind: 'presentation';
72
+ generation?: number;
73
+ pageDocId: string;
74
+ pathname: string;
75
+ locale: string;
76
+ context: PreviewReadContext;
77
+ acceptedContext: PreviewReadContext;
78
+ } | {
79
+ kind: 'location';
80
+ generation?: number;
81
+ pathname: string;
82
+ search?: string;
83
+ hash?: string;
84
+ commandId?: string | null;
85
+ } | {
86
+ kind: 'snapshot';
87
+ generation?: number;
88
+ pageDocId: string | null;
89
+ routeUrl?: string | null;
90
+ routeHash?: string | null;
91
+ codeVersion?: string | null;
92
+ } | {
93
+ kind: 'route-rendered';
94
+ generation?: number;
95
+ commandId: string;
96
+ codeVersion?: string | null;
97
+ };
98
+ export interface PreviewTransitionAnchorMapCandidate {
99
+ generation: number;
100
+ sessionId: string;
101
+ sourceOfTruth: PreviewTransitionSourceOfTruth;
102
+ commandId?: string | null;
103
+ pathname: string;
104
+ routeHash: string;
105
+ perspective?: PreviewTransitionPerspective;
106
+ codeVersion: string;
107
+ }
108
+ export declare function previewTransitionPerspective(input: {
109
+ published?: boolean;
110
+ variantId: string | null | undefined;
111
+ releaseId: string | null | undefined;
112
+ }): PreviewTransitionPerspective;
113
+ export interface BeginPreviewTransitionInput {
114
+ generation: number;
115
+ reason: PreviewTransitionReason;
116
+ target: PreviewTransitionTarget;
117
+ previousConfirmed?: PreviousPreviewConfirmation;
118
+ now: number;
119
+ slowMs?: number;
120
+ unconfirmedMs?: number;
121
+ }
122
+ export declare function beginPreviewTransition({ generation, reason, target, previousConfirmed, now, slowMs, unconfirmedMs, }: BeginPreviewTransitionInput): PreviewTransitionState;
123
+ export declare function advancePreviewTransitionClock(state: PreviewTransitionState, now: number): PreviewTransitionState;
124
+ export declare function confirmPreviewTransition(state: PreviewTransitionState, confirmation: PreviewTransitionConfirmation): PreviewTransitionState;
125
+ export declare function matchesTransitionTarget(target: PreviewTransitionTarget, confirmation: PreviewTransitionConfirmation): boolean;
126
+ export declare function matchesTransitionRouteUrl(target: PreviewTransitionTarget, routeUrl: string | null | undefined): boolean;
127
+ export declare function matchesAnchorMapCandidateTarget(target: PreviewTransitionTarget, candidate: PreviewTransitionAnchorMapCandidate): boolean;
128
+ export declare function parsePreviewTransitionRoute(routeUrl: string | null | undefined): {
129
+ pathname: string;
130
+ search: string;
131
+ hash: string;
132
+ } | null;
133
+ export declare function targetKey(target: PreviewTransitionTarget): string;
134
+ export declare function previewTransitionPerspectivesEqual(expected: PreviewTransitionPerspective, actual: PreviewTransitionPerspective | undefined): boolean;
135
+ export declare function confirmationFromLocation(input: {
136
+ generation?: number;
137
+ pathname: string;
138
+ search?: string;
139
+ hash?: string;
140
+ commandId?: string | null;
141
+ }): PreviewTransitionConfirmation;
142
+ export declare function normalizeTransitionTarget(target: PreviewTransitionTarget): NormalizedPreviewTransitionTarget;
@@ -0,0 +1,257 @@
1
+ import { normalizePathname } from '@storystick/client/normalize-pathname';
2
+ export const PREVIEW_TRANSITION_SLOW_MS = 1_500;
3
+ export const PREVIEW_TRANSITION_UNCONFIRMED_MS = 8_000;
4
+ export function previewTransitionPerspective(input) {
5
+ if (input.published)
6
+ return { kind: 'published' };
7
+ if (input.releaseId) {
8
+ return { kind: 'release', releaseId: input.releaseId };
9
+ }
10
+ if (input.variantId) {
11
+ return { kind: 'variant', variantId: input.variantId };
12
+ }
13
+ return { kind: 'living' };
14
+ }
15
+ export function beginPreviewTransition({ generation, reason, target, previousConfirmed, now, slowMs = PREVIEW_TRANSITION_SLOW_MS, unconfirmedMs = PREVIEW_TRANSITION_UNCONFIRMED_MS, }) {
16
+ const normalizedTarget = normalizeTransitionTarget(target);
17
+ return {
18
+ kind: 'pending',
19
+ generation,
20
+ reason,
21
+ sourceOfTruth: normalizedTarget.sourceOfTruth,
22
+ target: normalizedTarget,
23
+ previousConfirmed,
24
+ startedAt: now,
25
+ slowAt: now + slowMs,
26
+ deadlineAt: now + unconfirmedMs,
27
+ slow: false,
28
+ };
29
+ }
30
+ export function advancePreviewTransitionClock(state, now) {
31
+ if (state.kind !== 'pending')
32
+ return state;
33
+ if (now >= state.deadlineAt) {
34
+ return {
35
+ kind: 'unconfirmed',
36
+ generation: state.generation,
37
+ sourceOfTruth: state.sourceOfTruth,
38
+ target: state.target,
39
+ previousConfirmed: state.previousConfirmed,
40
+ startedAt: state.startedAt,
41
+ failedAt: now,
42
+ sourceReason: state.reason,
43
+ reason: state.reason === 'host-loading' ||
44
+ state.reason === 'host-loading-timeout'
45
+ ? 'host-loading-timeout'
46
+ : 'soft-navigation-unconfirmed',
47
+ };
48
+ }
49
+ if (!state.slow && now >= state.slowAt) {
50
+ return { ...state, slow: true };
51
+ }
52
+ return state;
53
+ }
54
+ export function confirmPreviewTransition(state, confirmation) {
55
+ if (state.kind === 'idle')
56
+ return state;
57
+ if (confirmation.generation !== undefined &&
58
+ confirmation.generation !== state.generation) {
59
+ return state;
60
+ }
61
+ // PR-1 — a post-paint route-rendered ack confirms the transition iff
62
+ // it echoes the commandId this transition is waiting on. This is the
63
+ // preferred (earliest, render-accurate) confirmation; the snapshot
64
+ // path below remains the fallback floor for transitions with no
65
+ // commandId and for SDKs/frameworks that never emit route-rendered.
66
+ if (confirmation.kind === 'route-rendered') {
67
+ if (state.target.commandId != null &&
68
+ confirmation.commandId === state.target.commandId) {
69
+ return { kind: 'idle' };
70
+ }
71
+ return state;
72
+ }
73
+ // A location envelope only proves that the iframe router reached the
74
+ // target URL. It can arrive before the target page is visibly rendered,
75
+ // so it must not confirm the transition — only a render snapshot or a
76
+ // route-rendered paint ack may.
77
+ if (confirmation.kind !== 'snapshot' && confirmation.kind !== 'presentation')
78
+ return state;
79
+ if (!matchesTransitionTarget(state.target, confirmation))
80
+ return state;
81
+ return { kind: 'idle' };
82
+ }
83
+ export function matchesTransitionTarget(target, confirmation) {
84
+ if (confirmation.kind === 'presentation') {
85
+ return confirmation.pageDocId === target.expectedPreviewDocumentId &&
86
+ confirmation.locale === target.expectedLocale &&
87
+ target.expectedPathname !== null && normalizePathname(confirmation.pathname) === normalizePathname(target.expectedPathname) &&
88
+ confirmation.context.contextId === confirmation.acceptedContext.contextId &&
89
+ confirmation.context.revision === confirmation.acceptedContext.revision &&
90
+ target.expectedPerspective !== undefined && previewTransitionPerspectivesEqual(target.expectedPerspective, confirmation.context.perspective);
91
+ }
92
+ if (confirmation.kind === 'location') {
93
+ if (target.expectedPathname === null)
94
+ return false;
95
+ const expectedPathname = normalizePathname(target.expectedPathname);
96
+ const actualPathname = normalizePathname(confirmation.pathname);
97
+ return (actualPathname === expectedPathname &&
98
+ normalizeTransitionSearch(confirmation.search ?? '') ===
99
+ normalizeTransitionSearch(target.expectedSearch) &&
100
+ (confirmation.hash ?? '') === target.expectedHash);
101
+ }
102
+ // route-rendered acks are correlated by commandId in
103
+ // `confirmPreviewTransition`, never by content identity here.
104
+ if (confirmation.kind !== 'snapshot')
105
+ return false;
106
+ if (target.expectedPreviewDocumentId === null)
107
+ return false;
108
+ if (confirmation.pageDocId !== target.expectedPreviewDocumentId)
109
+ return false;
110
+ if (target.expectedPathname !== null) {
111
+ const route = parsePreviewTransitionRoute(confirmation.routeUrl);
112
+ if (route === null)
113
+ return false;
114
+ if (normalizePathname(route.pathname) !==
115
+ normalizePathname(target.expectedPathname)) {
116
+ return false;
117
+ }
118
+ if (normalizeTransitionSearch(route.search) !==
119
+ normalizeTransitionSearch(target.expectedSearch)) {
120
+ return false;
121
+ }
122
+ if (route.hash !== target.expectedHash)
123
+ return false;
124
+ }
125
+ // A route snapshot can prove document + URL, but not which living, variant,
126
+ // or release content produced that render. Scope-aware transitions stay
127
+ // pending for a command-correlated paint ACK or provenance evidence.
128
+ if (target.expectedPerspective !== undefined)
129
+ return false;
130
+ return true;
131
+ }
132
+ export function matchesTransitionRouteUrl(target, routeUrl) {
133
+ const route = parsePreviewTransitionRoute(routeUrl);
134
+ if (route === null)
135
+ return false;
136
+ return matchesTransitionTarget(target, {
137
+ kind: 'location',
138
+ pathname: route.pathname,
139
+ search: route.search,
140
+ hash: route.hash,
141
+ });
142
+ }
143
+ export function matchesAnchorMapCandidateTarget(target, candidate) {
144
+ const normalizedTarget = normalizeTransitionTarget(target);
145
+ if (candidate.sessionId !== (normalizedTarget.sessionId ?? ''))
146
+ return false;
147
+ if (candidate.sourceOfTruth !== normalizedTarget.sourceOfTruth)
148
+ return false;
149
+ if (normalizedTarget.commandId !== null &&
150
+ normalizedTarget.commandId !== undefined &&
151
+ candidate.commandId !== null &&
152
+ candidate.commandId !== undefined &&
153
+ candidate.commandId !== normalizedTarget.commandId) {
154
+ return false;
155
+ }
156
+ if (normalizedTarget.expectedPathname === null)
157
+ return false;
158
+ if (normalizedTarget.expectedSearch !== '')
159
+ return false;
160
+ if (normalizedTarget.expectedHash !== '')
161
+ return false;
162
+ if (candidate.routeHash.length === 0)
163
+ return false;
164
+ if (candidate.codeVersion.length === 0)
165
+ return false;
166
+ if (normalizedTarget.expectedPerspective !== undefined &&
167
+ !previewTransitionPerspectivesEqual(normalizedTarget.expectedPerspective, candidate.perspective)) {
168
+ return false;
169
+ }
170
+ return (normalizePathname(candidate.pathname) ===
171
+ normalizePathname(normalizedTarget.expectedPathname));
172
+ }
173
+ export function parsePreviewTransitionRoute(routeUrl) {
174
+ if (typeof routeUrl !== 'string' || routeUrl.length === 0)
175
+ return null;
176
+ try {
177
+ const parsed = new URL(routeUrl, 'http://storystick.local');
178
+ return {
179
+ pathname: parsed.pathname,
180
+ search: normalizeTransitionSearch(parsed.search),
181
+ hash: parsed.hash,
182
+ };
183
+ }
184
+ catch {
185
+ return null;
186
+ }
187
+ }
188
+ function normalizeTransitionSearch(search) {
189
+ const raw = search.startsWith('?') ? search.slice(1) : search;
190
+ if (raw === '')
191
+ return '';
192
+ const params = new URLSearchParams(raw);
193
+ for (const key of [...params.keys()]) {
194
+ if (key.startsWith('_ss_'))
195
+ params.delete(key);
196
+ }
197
+ return params.toString();
198
+ }
199
+ export function targetKey(target) {
200
+ const t = normalizeTransitionTarget(target);
201
+ return [
202
+ t.sessionId ?? '',
203
+ t.editorDocumentId ?? '',
204
+ t.expectedPreviewDocumentId ?? '',
205
+ t.expectedLocale ?? '',
206
+ t.expectedPathname ?? '',
207
+ t.expectedPerspective === undefined
208
+ ? '<unscoped>'
209
+ : previewTransitionPerspectiveKey(t.expectedPerspective),
210
+ t.expectedSearch,
211
+ t.expectedHash,
212
+ t.draftDocumentId ?? '',
213
+ t.hostDocumentId ?? '',
214
+ t.hostStatus,
215
+ t.sourceOfTruth,
216
+ ].join('\x00');
217
+ }
218
+ export function previewTransitionPerspectivesEqual(expected, actual) {
219
+ if (actual === undefined || actual.kind !== expected.kind)
220
+ return false;
221
+ if (expected.kind === 'variant') {
222
+ return actual.kind === 'variant' && actual.variantId === expected.variantId;
223
+ }
224
+ if (expected.kind === 'release') {
225
+ return actual.kind === 'release' && actual.releaseId === expected.releaseId;
226
+ }
227
+ return true;
228
+ }
229
+ function previewTransitionPerspectiveKey(perspective) {
230
+ if (perspective.kind === 'variant')
231
+ return `variant:${perspective.variantId}`;
232
+ if (perspective.kind === 'release')
233
+ return `release:${perspective.releaseId}`;
234
+ return perspective.kind;
235
+ }
236
+ export function confirmationFromLocation(input) {
237
+ return {
238
+ kind: 'location',
239
+ generation: input.generation,
240
+ pathname: input.pathname,
241
+ search: input.search ?? '',
242
+ hash: input.hash ?? '',
243
+ commandId: input.commandId ?? null,
244
+ };
245
+ }
246
+ export function normalizeTransitionTarget(target) {
247
+ return {
248
+ ...target,
249
+ expectedPathname: target.expectedPathname === null
250
+ ? null
251
+ : normalizePathname(target.expectedPathname),
252
+ expectedSearch: target.expectedSearch ?? '',
253
+ expectedHash: target.expectedHash ?? '',
254
+ commandId: target.commandId ?? null,
255
+ sourceOfTruth: target.sourceOfTruth ?? 'studio',
256
+ };
257
+ }
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Module-level mutable state for the current render mode inside the
3
+ * developer's iframe. Read by framework SDKs to decide whether to
4
+ * debounce Convex subscriptions (Navigation mode = 100ms debounce
5
+ * per ADR-059 §Technical commitments + spec/preview-system.md
6
+ * §"Convex subscription debounced 100ms in Navigation mode").
7
+ *
8
+ * The snapshot reporter writes here when a `render-mode` postMessage
9
+ * arrives. Framework SDKs read here in their query hook. Subscribers
10
+ * can register a callback to be notified on changes — useful for
11
+ * triggering re-renders without polling.
12
+ *
13
+ * This intentionally lives at module scope (not React Context) for
14
+ * three reasons:
15
+ *
16
+ * 1. The state is iframe-global (one site rendered in one iframe).
17
+ * Two providers in the same iframe with different render modes
18
+ * would be a bug, not a feature.
19
+ *
20
+ * 2. The state is set by the postMessage runtime (vanilla JS,
21
+ * no React tree). A Context API requires a Provider mounted
22
+ * above every consumer; module state is observed regardless.
23
+ *
24
+ * 3. Tests can reset it deterministically via `_resetForTests()`.
25
+ * Cross-test pollution is the only real risk and it's explicit.
26
+ */
27
+ import type { RenderMode } from '../types.js';
28
+ export declare function getRenderMode(): RenderMode;
29
+ export declare function setRenderMode(mode: RenderMode): void;
30
+ /**
31
+ * Subscribe to render-mode changes. Returns an unsubscribe function.
32
+ * Subscribers are called synchronously after `setRenderMode`. They
33
+ * MUST not call `setRenderMode` from within their handler — that
34
+ * would re-enter the dispatch.
35
+ */
36
+ export declare function subscribeRenderMode(callback: (mode: RenderMode) => void): () => void;
37
+ /**
38
+ * Returns the debounce window (ms) appropriate for the current
39
+ * render mode. Navigation mode = 100ms; Device mode = 0 (no debounce).
40
+ *
41
+ * Per ADR-059 §Technical commitments: "the editor reads structure
42
+ * (section order, slice type, slice count), not typography. Typo-
43
+ * graphic updates do not need to propagate to the Navigation
44
+ * miniature within one frame."
45
+ */
46
+ export declare function debounceForRenderMode(mode?: RenderMode): number;
47
+ /**
48
+ * Test-only reset. Not exported from index.ts to keep the public
49
+ * API small.
50
+ */
51
+ export declare function _resetForTests(): void;