@storystick/preview 0.0.0-stage → 0.2.0-rc.1

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 (247) hide show
  1. package/LICENSE +112 -0
  2. package/PUBLIC-API.md +56 -0
  3. package/README.md +42 -2
  4. package/dist/.source-hash +1 -0
  5. package/dist/authoring/runtime-contract.d.ts +92 -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/page-presentation.d.ts +24 -0
  72. package/dist/page-presentation.js +32 -0
  73. package/dist/provenance/anchor-map.d.ts +95 -0
  74. package/dist/provenance/anchor-map.js +177 -0
  75. package/dist/provenance/carrier-plan.d.ts +51 -0
  76. package/dist/provenance/carrier-plan.js +90 -0
  77. package/dist/provenance/coded-text.d.ts +20 -0
  78. package/dist/provenance/coded-text.js +302 -0
  79. package/dist/provenance/direct-edit-locator.d.ts +29 -0
  80. package/dist/provenance/direct-edit-locator.js +218 -0
  81. package/dist/provenance/editable-leaf.d.ts +134 -0
  82. package/dist/provenance/editable-leaf.js +483 -0
  83. package/dist/provenance/editor-target.d.ts +29 -0
  84. package/dist/provenance/editor-target.js +46 -0
  85. package/dist/provenance/field-leaf-walker.d.ts +21 -0
  86. package/dist/provenance/field-leaf-walker.js +98 -0
  87. package/dist/provenance/frame-dependencies.d.ts +212 -0
  88. package/dist/provenance/frame-dependencies.js +550 -0
  89. package/dist/provenance/index.d.ts +26 -0
  90. package/dist/provenance/index.js +26 -0
  91. package/dist/provenance/local-events.d.ts +1 -0
  92. package/dist/provenance/local-events.js +1 -0
  93. package/dist/provenance/markers.d.ts +21 -0
  94. package/dist/provenance/markers.js +70 -0
  95. package/dist/provenance/multi-frame-anchor-map.d.ts +54 -0
  96. package/dist/provenance/multi-frame-anchor-map.js +109 -0
  97. package/dist/provenance/normalize.d.ts +2 -0
  98. package/dist/provenance/normalize.js +19 -0
  99. package/dist/provenance/overlay-focus-echo.d.ts +17 -0
  100. package/dist/provenance/overlay-focus-echo.js +16 -0
  101. package/dist/provenance/overlay-projection.d.ts +65 -0
  102. package/dist/provenance/overlay-projection.js +201 -0
  103. package/dist/provenance/owner-map.d.ts +55 -0
  104. package/dist/provenance/owner-map.js +239 -0
  105. package/dist/provenance/read-carrier.d.ts +22 -0
  106. package/dist/provenance/read-carrier.js +92 -0
  107. package/dist/provenance/recorder.d.ts +38 -0
  108. package/dist/provenance/recorder.js +82 -0
  109. package/dist/provenance/render-context.d.ts +106 -0
  110. package/dist/provenance/render-context.js +338 -0
  111. package/dist/provenance/reveal-contract.d.ts +44 -0
  112. package/dist/provenance/reveal-contract.js +1 -0
  113. package/dist/provenance/richtext-slice-embed-address.d.ts +20 -0
  114. package/dist/provenance/richtext-slice-embed-address.js +35 -0
  115. package/dist/provenance/runtime-script.d.ts +1 -0
  116. package/dist/provenance/runtime-script.js +2217 -0
  117. package/dist/provenance/sidecar-store.d.ts +63 -0
  118. package/dist/provenance/sidecar-store.js +151 -0
  119. package/dist/provenance/sidecar.d.ts +34 -0
  120. package/dist/provenance/sidecar.js +227 -0
  121. package/dist/provenance/slice-model.d.ts +28 -0
  122. package/dist/provenance/slice-model.js +86 -0
  123. package/dist/provenance/source-identity.d.ts +3 -0
  124. package/dist/provenance/source-identity.js +7 -0
  125. package/dist/provenance/source-map-materializer.d.ts +76 -0
  126. package/dist/provenance/source-map-materializer.js +286 -0
  127. package/dist/provenance/source-registry.d.ts +103 -0
  128. package/dist/provenance/source-registry.js +562 -0
  129. package/dist/provenance/transformed-output-gate.d.ts +161 -0
  130. package/dist/provenance/transformed-output-gate.js +171 -0
  131. package/dist/provenance/types.d.ts +102 -0
  132. package/dist/provenance/types.js +1 -0
  133. package/dist/refresh-policy.d.ts +11 -0
  134. package/dist/refresh-policy.js +14 -0
  135. package/dist/reveal-target.d.ts +8 -0
  136. package/dist/reveal-target.js +47 -0
  137. package/dist/route-paths.d.ts +11 -0
  138. package/dist/route-paths.js +14 -0
  139. package/dist/router-subscriber.d.ts +92 -0
  140. package/dist/router-subscriber.js +44 -0
  141. package/dist/runtime/anchor-types.d.ts +5 -0
  142. package/dist/runtime/anchor-types.js +1 -0
  143. package/dist/runtime/authority-store.d.ts +31 -0
  144. package/dist/runtime/authority-store.js +56 -0
  145. package/dist/runtime/boot.d.ts +52 -0
  146. package/dist/runtime/boot.js +379 -0
  147. package/dist/runtime/content-refresh.d.ts +30 -0
  148. package/dist/runtime/content-refresh.js +68 -0
  149. package/dist/runtime/emit-runtime-error.d.ts +51 -0
  150. package/dist/runtime/emit-runtime-error.js +64 -0
  151. package/dist/runtime/field-proxy.d.ts +112 -0
  152. package/dist/runtime/field-proxy.js +471 -0
  153. package/dist/runtime/form-contract-reporter.d.ts +99 -0
  154. package/dist/runtime/form-contract-reporter.js +391 -0
  155. package/dist/runtime/invalidation-adapter.d.ts +17 -0
  156. package/dist/runtime/invalidation-adapter.js +42 -0
  157. package/dist/runtime/live-query-overlay.d.ts +39 -0
  158. package/dist/runtime/live-query-overlay.js +345 -0
  159. package/dist/runtime/momentary-navigate.d.ts +16 -0
  160. package/dist/runtime/momentary-navigate.js +106 -0
  161. package/dist/runtime/observation-scheduler.d.ts +15 -0
  162. package/dist/runtime/observation-scheduler.js +42 -0
  163. package/dist/runtime/perf-metrics.d.ts +28 -0
  164. package/dist/runtime/perf-metrics.js +90 -0
  165. package/dist/runtime/preview-interaction-policy.d.ts +1 -0
  166. package/dist/runtime/preview-interaction-policy.js +103 -0
  167. package/dist/runtime/preview-overlay-painter.d.ts +27 -0
  168. package/dist/runtime/preview-overlay-painter.js +1732 -0
  169. package/dist/runtime/preview-overlay-sidecar.d.ts +26 -0
  170. package/dist/runtime/preview-overlay-sidecar.js +45 -0
  171. package/dist/runtime/preview-perspective-alignment.d.ts +15 -0
  172. package/dist/runtime/preview-perspective-alignment.js +40 -0
  173. package/dist/runtime/preview-perspective-intent-state.d.ts +23 -0
  174. package/dist/runtime/preview-perspective-intent-state.js +5 -0
  175. package/dist/runtime/preview-transition-state.d.ts +142 -0
  176. package/dist/runtime/preview-transition-state.js +257 -0
  177. package/dist/runtime/render-mode-state.d.ts +51 -0
  178. package/dist/runtime/render-mode-state.js +76 -0
  179. package/dist/runtime/request-id.d.ts +4 -0
  180. package/dist/runtime/request-id.js +8 -0
  181. package/dist/runtime/resolve-schema-field.d.ts +99 -0
  182. package/dist/runtime/resolve-schema-field.js +356 -0
  183. package/dist/runtime/runtime-error-dispatch.d.ts +23 -0
  184. package/dist/runtime/runtime-error-dispatch.js +75 -0
  185. package/dist/runtime/schema-store.d.ts +121 -0
  186. package/dist/runtime/schema-store.js +667 -0
  187. package/dist/runtime/snapshot-assembly.d.ts +104 -0
  188. package/dist/runtime/snapshot-assembly.js +553 -0
  189. package/dist/runtime/snapshot-reporter.d.ts +55 -0
  190. package/dist/runtime/snapshot-reporter.js +375 -0
  191. package/dist/runtime/standalone-overlay-controller.d.ts +33 -0
  192. package/dist/runtime/standalone-overlay-controller.js +281 -0
  193. package/dist/runtime/store.d.ts +307 -0
  194. package/dist/runtime/store.js +1026 -0
  195. package/dist/runtime/windowed-minimap-projection.d.ts +28 -0
  196. package/dist/runtime/windowed-minimap-projection.js +189 -0
  197. package/dist/runtime/workspace-shortcuts.d.ts +30 -0
  198. package/dist/runtime/workspace-shortcuts.js +188 -0
  199. package/dist/sdk-error-copy.d.ts +48 -0
  200. package/dist/sdk-error-copy.js +182 -0
  201. package/dist/select-cursor.d.ts +2 -0
  202. package/dist/select-cursor.js +23 -0
  203. package/dist/server/admission-cookie.d.ts +38 -0
  204. package/dist/server/admission-cookie.js +122 -0
  205. package/dist/server/control-client.d.ts +29 -0
  206. package/dist/server/control-client.js +22 -0
  207. package/dist/server/control-errors.d.ts +22 -0
  208. package/dist/server/control-errors.js +182 -0
  209. package/dist/server/draft-route.d.ts +12 -0
  210. package/dist/server/draft-route.js +45 -0
  211. package/dist/server/extract-token.d.ts +30 -0
  212. package/dist/server/extract-token.js +98 -0
  213. package/dist/server/grant.d.ts +128 -0
  214. package/dist/server/grant.js +395 -0
  215. package/dist/server/ordinary-read-provenance.d.ts +11 -0
  216. package/dist/server/ordinary-read-provenance.js +124 -0
  217. package/dist/server/preview-read-metadata.d.ts +10 -0
  218. package/dist/server/preview-read-metadata.js +27 -0
  219. package/dist/server/provenance-frame-builder.d.ts +53 -0
  220. package/dist/server/provenance-frame-builder.js +102 -0
  221. package/dist/server/provenance-runtime.d.ts +70 -0
  222. package/dist/server/provenance-runtime.js +46 -0
  223. package/dist/server/read-context-command.d.ts +11 -0
  224. package/dist/server/read-context-command.js +53 -0
  225. package/dist/server/read-identity.d.ts +3 -0
  226. package/dist/server/read-identity.js +63 -0
  227. package/dist/server/read-lifecycle.d.ts +17 -0
  228. package/dist/server/read-lifecycle.js +52 -0
  229. package/dist/server/render-frame-provenance.d.ts +133 -0
  230. package/dist/server/render-frame-provenance.js +676 -0
  231. package/dist/server/render-id.d.ts +15 -0
  232. package/dist/server/render-id.js +22 -0
  233. package/dist/server/route-document-provenance.d.ts +77 -0
  234. package/dist/server/route-document-provenance.js +1327 -0
  235. package/dist/server/route-session-cookie.d.ts +33 -0
  236. package/dist/server/route-session-cookie.js +54 -0
  237. package/dist/session-projections.d.ts +33 -0
  238. package/dist/session-projections.js +43 -0
  239. package/dist/session-subscriber.d.ts +124 -0
  240. package/dist/session-subscriber.js +79 -0
  241. package/dist/sha256-fallback.d.ts +30 -0
  242. package/dist/sha256-fallback.js +126 -0
  243. package/dist/types.d.ts +446 -0
  244. package/dist/types.js +85 -0
  245. package/dist/validators.d.ts +61 -0
  246. package/dist/validators.js +110 -0
  247. package/package.json +210 -3
@@ -0,0 +1,281 @@
1
+ import { createPreviewRequestId } from './request-id.js';
2
+ import { expectPreviewOverlayFocusEcho, consumePreviewOverlayFocusEcho, } from '../provenance/overlay-focus-echo.js';
3
+ import { createPreviewV2SourceRegistry, } from '../provenance/source-registry.js';
4
+ import { previewPaintRenderIdentityFromAnchorMap } from '../provenance/index.js';
5
+ import { setInteractionMode } from '../interaction-mode.js';
6
+ import { PREVIEW_PROVENANCE_ANCHOR_MAP_EVENT, findPreviewOverlayClickTarget, hasCompleteDirectLocatorCoverage, projectPreviewProvenanceOverlays, resolveDirectProvenanceOwnerMap, resolvePreviewOverlayClickItem, } from '../provenance/index.js';
7
+ import { applyPreviewOverlayMap, clearPreviewOverlayPaint, revealPreviewV2SourceInIframe, } from './preview-overlay-painter.js';
8
+ const STATE_KEY = Symbol.for('storystick.preview.standalone-overlays.v1');
9
+ const serverState = createState();
10
+ function createState() {
11
+ return {
12
+ mode: 'edit',
13
+ suspended: false,
14
+ focusEcho: null,
15
+ anchorMap: null,
16
+ ownerMap: null,
17
+ activePaint: null,
18
+ projection: null,
19
+ snapshot: { mode: 'edit', available: false, generation: null, rejections: [] },
20
+ subscribers: new Set(),
21
+ intentListeners: new Set(),
22
+ repaint: null,
23
+ };
24
+ }
25
+ function stateFor(win) {
26
+ const target = win ?? (typeof window === 'undefined' ? null : window);
27
+ if (target === null)
28
+ return serverState;
29
+ const existing = Reflect.get(target, STATE_KEY);
30
+ if (existing !== undefined)
31
+ return existing;
32
+ const state = createState();
33
+ Reflect.set(target, STATE_KEY, state);
34
+ return state;
35
+ }
36
+ export function readStandaloneOverlaySnapshot(win) {
37
+ return stateFor(win).snapshot;
38
+ }
39
+ export function subscribeStandaloneOverlaySnapshot(listener, win) {
40
+ const state = stateFor(win);
41
+ state.subscribers.add(listener);
42
+ return () => state.subscribers.delete(listener);
43
+ }
44
+ export function subscribeStandaloneOverlayIntent(listener, win) {
45
+ const state = stateFor(win);
46
+ state.intentListeners.add(listener);
47
+ return () => state.intentListeners.delete(listener);
48
+ }
49
+ export function setStandaloneOverlayMode(mode, win) {
50
+ const state = stateFor(win);
51
+ state.mode = mode;
52
+ setInteractionMode(mode);
53
+ publishSnapshot(state);
54
+ state.repaint?.();
55
+ }
56
+ /** Paint and route the same validated projection used by Studio. The active
57
+ * projection is re-read on every click; a stale generation cannot become an
58
+ * edit intent. */
59
+ export function startStandaloneOverlayController(args) {
60
+ const win = args.win ?? window;
61
+ const state = stateFor(win);
62
+ const repaint = () => {
63
+ const projection = state.projection;
64
+ if (state.mode !== 'edit' ||
65
+ state.suspended ||
66
+ projection === null ||
67
+ projection.payload.items.length === 0) {
68
+ clearPreviewOverlayPaint();
69
+ return;
70
+ }
71
+ const payload = {
72
+ ...projection.payload,
73
+ display: 'hover',
74
+ };
75
+ applyPreviewOverlayMap(payload, win, {
76
+ onClick: (click) => emitIntent(state, click),
77
+ });
78
+ };
79
+ state.repaint = repaint;
80
+ const apply = (value) => {
81
+ const anchorMap = readAnchorMap(value, args.sessionId);
82
+ state.anchorMap = anchorMap;
83
+ const ownerMap = anchorMap ? resolveDirectProvenanceOwnerMap({ anchorMap }) : null;
84
+ state.ownerMap = ownerMap;
85
+ if (anchorMap === null || !hasCompleteDirectLocatorCoverage(anchorMap)) {
86
+ state.projection = null;
87
+ publishSnapshot(state);
88
+ repaint();
89
+ return;
90
+ }
91
+ state.projection = projectPreviewProvenanceOverlays({
92
+ generation: anchorMap.generation,
93
+ anchorMap,
94
+ ownerMap,
95
+ display: 'hover',
96
+ });
97
+ publishSnapshot(state);
98
+ repaint();
99
+ };
100
+ const onAnchorMap = (event) => {
101
+ apply(event instanceof CustomEvent ? event.detail : null);
102
+ };
103
+ win.addEventListener(PREVIEW_PROVENANCE_ANCHOR_MAP_EVENT, onAnchorMap);
104
+ setInteractionMode(state.mode);
105
+ apply(win.__ssMultiFrameAnchorMaps);
106
+ return () => {
107
+ win.removeEventListener(PREVIEW_PROVENANCE_ANCHOR_MAP_EVENT, onAnchorMap);
108
+ if (state.repaint === repaint)
109
+ state.repaint = null;
110
+ state.projection = null;
111
+ state.anchorMap = null;
112
+ state.ownerMap = null;
113
+ state.activePaint = null;
114
+ state.focusEcho = null;
115
+ // Mode/suspension belong to the host control, not this paint subscription.
116
+ // Restarting provenance must preserve the user's choice.
117
+ setInteractionMode('browse');
118
+ publishSnapshot(state);
119
+ clearPreviewOverlayPaint();
120
+ };
121
+ }
122
+ function emitIntent(state, click) {
123
+ if (state.suspended)
124
+ return;
125
+ const target = resolveStandaloneOverlayIntent({
126
+ mode: state.mode,
127
+ projection: state.projection,
128
+ click,
129
+ });
130
+ if (target === null)
131
+ return;
132
+ const renderIdentity = state.anchorMap && previewPaintRenderIdentityFromAnchorMap(state.anchorMap);
133
+ if (renderIdentity && target.sourceSetId && target.markerId) {
134
+ state.activePaint = {
135
+ documentId: target.documentId,
136
+ sourceSetId: target.sourceSetId,
137
+ markerId: target.markerId,
138
+ paintId: state.projection?.payload.items.find((item) => item.paintKey === click.paintKey)?.renderTargetId ?? '',
139
+ renderIdentity,
140
+ };
141
+ state.focusEcho = expectPreviewOverlayFocusEcho(state.activePaint);
142
+ }
143
+ for (const listener of [...state.intentListeners])
144
+ listener(target);
145
+ }
146
+ /** The click identity and its owner are resolved from one current projection.
147
+ * A host never receives an intent from stale paint or browse mode. */
148
+ export function resolveStandaloneOverlayIntent(args) {
149
+ const { projection, click } = args;
150
+ if (args.mode !== 'edit' ||
151
+ projection === null ||
152
+ click.routeHash !== projection.payload.routeHash ||
153
+ click.codeVersion !== projection.payload.codeVersion ||
154
+ click.renderId !== projection.payload.renderId)
155
+ return null;
156
+ const item = resolvePreviewOverlayClickItem({
157
+ activeGeneration: projection.payload.generation,
158
+ itemsByPaintKey: new Map(projection.payload.items.map((candidate) => [
159
+ candidate.paintKey,
160
+ candidate,
161
+ ])),
162
+ payload: click,
163
+ });
164
+ if (item === null)
165
+ return null;
166
+ const target = findPreviewOverlayClickTarget(projection.clickTargets, item);
167
+ return target;
168
+ }
169
+ function publishSnapshot(state) {
170
+ const next = {
171
+ mode: state.mode,
172
+ available: state.projection !== null && state.projection.payload.items.length > 0,
173
+ generation: state.projection?.payload.generation ?? null,
174
+ rejections: state.ownerMap?.rejections ?? [],
175
+ };
176
+ if (next.mode === state.snapshot.mode &&
177
+ next.available === state.snapshot.available &&
178
+ next.generation === state.snapshot.generation &&
179
+ next.rejections.length === state.snapshot.rejections.length &&
180
+ next.rejections.every((rejection, index) => {
181
+ const previous = state.snapshot.rejections[index];
182
+ return rejection.reason === previous.reason &&
183
+ rejection.frameId === previous.frameId && rejection.markerId === previous.markerId;
184
+ }))
185
+ return;
186
+ state.snapshot = next;
187
+ for (const listener of [...state.subscribers])
188
+ listener();
189
+ }
190
+ function readAnchorMap(value, expectedSessionId) {
191
+ if (typeof value !== 'object' || value === null)
192
+ return null;
193
+ const candidate = value;
194
+ if (candidate.sessionId !== expectedSessionId ||
195
+ !Number.isFinite(candidate.generation) ||
196
+ (candidate.generation ?? 0) <= 0 ||
197
+ candidate.pending !== false ||
198
+ candidate.valid !== true ||
199
+ typeof candidate.routeHash !== 'string' ||
200
+ candidate.routeHash.length === 0 ||
201
+ typeof candidate.codeVersion !== 'string' ||
202
+ candidate.codeVersion.length === 0 ||
203
+ !Array.isArray(candidate.maps)) {
204
+ return null;
205
+ }
206
+ const routeFrames = candidate.maps.filter((frame) => frame?.frameId === 'route');
207
+ if (routeFrames.length !== 1)
208
+ return null;
209
+ return candidate;
210
+ }
211
+ /** The same provenance resolver and painter as Studio, transported locally. */
212
+ export function revealStandaloneSource(request, win = window) {
213
+ const state = stateFor(win);
214
+ if (state.mode !== 'edit' || state.suspended)
215
+ return false;
216
+ const registry = standaloneRegistry(state, win);
217
+ const resolution = registry.resolveRevealPayloadForStudioTarget({
218
+ request,
219
+ requestId: createPreviewRequestId(win.crypto),
220
+ behavior: {
221
+ scroll: request.behavior?.scroll ?? true,
222
+ flash: request.behavior?.flash ?? true,
223
+ durationMs: request.behavior?.durationMs ?? 900,
224
+ },
225
+ });
226
+ if (!resolution.ok)
227
+ return false;
228
+ if (resolution.selection.kind === 'active-paint' && state.activePaint) {
229
+ const echo = consumePreviewOverlayFocusEcho(state.focusEcho, resolution.selection.paintId, state.activePaint.renderIdentity);
230
+ state.focusEcho = echo.pending;
231
+ if (echo.consumed)
232
+ return true;
233
+ }
234
+ const result = revealPreviewV2SourceInIframe(resolution.payload, win);
235
+ return result.status === 'found' || result.status === 'multiple';
236
+ }
237
+ export function revealStandaloneSlice(request, win = window) {
238
+ const state = stateFor(win);
239
+ if (state.mode !== 'edit' || state.suspended)
240
+ return false;
241
+ const resolution = standaloneRegistry(state, win).resolveRevealPayloadForStudioSlice({
242
+ request,
243
+ requestId: createPreviewRequestId(win.crypto),
244
+ behavior: {
245
+ scroll: request.behavior?.scroll ?? true,
246
+ flash: request.behavior?.flash ?? true,
247
+ durationMs: request.behavior?.durationMs ?? 900,
248
+ },
249
+ });
250
+ if (!resolution.ok)
251
+ return false;
252
+ const result = revealPreviewV2SourceInIframe(resolution.payload, win);
253
+ return result.status === 'found' || result.status === 'multiple';
254
+ }
255
+ function standaloneRegistry(state, win) {
256
+ const anchorMap = !state.suspended && state.projection ? state.anchorMap : null;
257
+ return createPreviewV2SourceRegistry({
258
+ sessionId: anchorMap?.sessionId ?? null,
259
+ anchorMap,
260
+ ownerMap: anchorMap ? state.ownerMap : null,
261
+ sourceMapBytes: null,
262
+ currentViewport: () => ({
263
+ x: win.scrollX,
264
+ y: win.scrollY,
265
+ width: win.innerWidth,
266
+ height: win.innerHeight,
267
+ }),
268
+ activePaintFor: (documentId, sourceSetId) => state.activePaint?.documentId === documentId &&
269
+ state.activePaint.sourceSetId === sourceSetId
270
+ ? state.activePaint
271
+ : null,
272
+ });
273
+ }
274
+ /** Admission/render transitions suspend interaction without changing the user's mode. */
275
+ export function setStandaloneOverlaySuspended(suspended, win) {
276
+ const state = stateFor(win);
277
+ if (state.suspended === suspended)
278
+ return;
279
+ state.suspended = suspended;
280
+ state.repaint?.();
281
+ }
@@ -0,0 +1,307 @@
1
+ import { type PreviewPerspectiveCommand } from '../context-command.js';
2
+ import { type PreviewReadContext } from '@storystick/types/preview-query-targeting';
3
+ import type { PreviewLifecyclePayload } from '@storystick/transport-allowlist';
4
+ /**
5
+ * `previewRuntime` — module-level singleton (ADR-099 D11 + ADR-116).
6
+ *
7
+ * Five concerns the Provider used to handle, ported to imperative
8
+ * methods on this singleton (D11.1) :
9
+ *
10
+ * 1. Convergent grant lifecycle with a compatibility projection for
11
+ * framework adapters that still consume the historical state field.
12
+ *
13
+ * 2. `httpClient` (ConvexReactClient) lifecycle. Built on first JWT
14
+ * arrival ; disposed via `dispose()`. HMR resets on module reload.
15
+ *
16
+ * 3. In-place Convex auth fetcher fed only by the same-origin grant route.
17
+ *
18
+ * 4. Session subscription gated on token presence (NOT
19
+ * lifecycle state). Survives the `authenticated → refreshing →
20
+ * authenticated` cycle. (Provider's codex Lane D round 10 P1.)
21
+ *
22
+ * 5. `contentRevision` driver. Listeners observe forward jumps and
23
+ * fire `router.refresh()` (the `<Storystick />` Client Component
24
+ * holds the `useRouter()` reference ; the singleton stays
25
+ * framework-agnostic).
26
+ *
27
+ * ADR-116 (PR4) — the identity resolver replaces `defaultDetectPreview`.
28
+ * Boot path :
29
+ *
30
+ * init(opts)
31
+ * → compare-and-set (D5 invariant 1 — set inFlightInitPromise
32
+ * BEFORE notify so a synchronous-reentrant init() observes the
33
+ * slot populated)
34
+ * → resolver.start() resolves the identity via carrier chain
35
+ * (broadcast > storage > URL — ADR-116 D3)
36
+ * → on first identity (provisional or live), start the same-origin grant
37
+ * coordinator and secret-free control plane for that sessionId
38
+ *
39
+ * Full runtime cell persists on `globalThis.__storystick_preview_runtime__`
40
+ * (D5 invariant 2 — codex R6 #3). Persisting only `state` is INSUFFICIENT :
41
+ * Next Fast Refresh re-evaluates the module body but does NOT remount
42
+ * every `useSyncExternalStore` consumer ; old consumers stay subscribed
43
+ * to the OLD module's `listeners` Set. The full cell (state + listeners +
44
+ * revisionListeners + inFlightInitPromise + cleanups + identity resolver
45
+ * handles) on `globalThis` keeps emissions and subscriptions aligned
46
+ * across HMR.
47
+ *
48
+ * Per CSO-PREVIEW-11 — per-tab in-memory only. Module-level `let` state
49
+ * (and the `globalThis` cell) ; no SharedWorker / ServiceWorker /
50
+ * BroadcastChannel / localStorage. The new sessionStorage carrier (PR2)
51
+ * is tier-2 identity persistence within the iframe origin partition —
52
+ * distinct from cross-tab/process IPC.
53
+ */
54
+ import { ConvexReactClient } from "convex/react";
55
+ import type { Client } from "@storystick/client";
56
+ import { type PreviewGrantCoordinator } from "../grant-coordinator.js";
57
+ import { type LegacyPreviewAuthState, type PreviewGrantLifecycle } from "../grant-lifecycle.js";
58
+ import { type PreviewSessionData } from "../session-subscriber.js";
59
+ import { type IdentityResolver, type PreviewIdentity } from "../identity/index.js";
60
+ import { type PreviewContentRefreshEvent } from "./content-refresh.js";
61
+ export interface PreviewRuntimeState {
62
+ /** Current state machine value (ADR-087 §3.4). */
63
+ state: LegacyPreviewAuthState;
64
+ /** Explicitly ended in this browsing context; old URL carriers cannot revive it. */
65
+ previewEnded?: boolean;
66
+ /** Canonical connectivity state. `state` above is a compatibility projection. */
67
+ lifecycle: PreviewGrantLifecycle | null;
68
+ /**
69
+ * Reactive Convex client. `null` until the token route delivers a
70
+ * JWT (preview mode) OR forever (non-preview mode). The
71
+ * `<StorystickQueryProvider>` consumes this via `reactiveClient()`.
72
+ */
73
+ client: ConvexReactClient | null;
74
+ /**
75
+ * Latest preview JWT. Read by the in-place setAuth fetcher when
76
+ * Convex calls `setAuth({forceRefreshToken: true})`. Updated on each
77
+ * refresh cycle.
78
+ */
79
+ previewToken: string | null;
80
+ /**
81
+ * HttpOnly preview-cookie lifecycle observed by the Next adapter.
82
+ * C3 consumes this plain data; token material never enters the shape.
83
+ */
84
+ cookieLifecycle: PreviewCookieLifecycleState | null;
85
+ /**
86
+ * Set on terminal error (state === 'error'). Surfaces a typed
87
+ * payload to consumers (`<Storystick />` renders the self-contained
88
+ * error UI ; `<StorystickQueryProvider>` propagates to in-flight
89
+ * subscribers per ADR-099 §5.6).
90
+ */
91
+ error: {
92
+ code: string;
93
+ message: string;
94
+ } | null;
95
+ /**
96
+ * Live-preview content revision (ADR-096). Monotonic counter from
97
+ * the row sub. `null` until the first emission.
98
+ */
99
+ contentRevision: number | null;
100
+ /**
101
+ * Session id derived from the identity resolver (broadcast > storage
102
+ * > URL). `null` outside preview. Tracks `identity.sessionId` 1-1 ;
103
+ * Replace transitions update it atomically.
104
+ */
105
+ sessionId: string | null;
106
+ /** Latest session payload (full row from `getPreviewSession`). */
107
+ session: PreviewSessionData | null;
108
+ contextCommand?: PreviewLifecyclePayload['contextCommand'];
109
+ /** Accepted perspective actually rendered by every mounted occurrence. */
110
+ renderedReadContext?: PreviewReadContext | null;
111
+ /**
112
+ * Perspective projected from the authenticated shared read context.
113
+ * View URLs and navigation hints never write this authority.
114
+ */
115
+ variantId: string | null;
116
+ /** Explicit release read scope for this view. */
117
+ releaseId: string | null;
118
+ /**
119
+ * ADR-116 — full resolved identity (sessionId, projectId,
120
+ * parentOrigin, source, epoch). Consumers (PR5 — preview-nav,
121
+ * preview-mode-latch replacements) read identity directly instead of
122
+ * re-deriving from URL. `null` outside preview.
123
+ */
124
+ identity: PreviewIdentity | null;
125
+ /**
126
+ * ADR-131 Amendment 002 §5.1 — Pattern D HTTP client used by iframe
127
+ * runtime modules that need to call the gateway directly (e.g. the
128
+ * schema-store fetches `/api/q/schemas/current` through here).
129
+ *
130
+ * Set by `<StorystickQueryProvider>` in preview mode once the token
131
+ * arrives ; reset to `null` on provider unmount. `null` outside
132
+ * preview AND before the provider has built the configured client
133
+ * (the token may arrive after provider mount).
134
+ *
135
+ * Independent of the state machine — `setHttpClient` is a
136
+ * field-only updater that bypasses the sticky-error gate so cleanup
137
+ * (provider unmount) always runs even in terminal `'error'` state.
138
+ */
139
+ httpClient: Client | null;
140
+ }
141
+ export interface PreviewCookieLifecycleState {
142
+ mintedAt: number;
143
+ expiresAt: number;
144
+ lastRefreshOutcome: {
145
+ status: "success";
146
+ at: number;
147
+ } | {
148
+ status: "failure";
149
+ at: number;
150
+ code: string;
151
+ } | null;
152
+ }
153
+ export type PreviewCookieLifecycleEvent = {
154
+ type: "minted";
155
+ mintedAt: number;
156
+ expiresAt: number;
157
+ } | {
158
+ type: "refresh-succeeded";
159
+ mintedAt: number;
160
+ expiresAt: number;
161
+ } | {
162
+ type: "refresh-failed";
163
+ at: number;
164
+ code: string;
165
+ };
166
+ export interface InitOpts {
167
+ projectId: string;
168
+ /**
169
+ * Sub-gateway WebSocket origin. The Convex SDK derives the matching
170
+ * `ws://` / `wss://` and appends `/api/<sdk-version>/sync`.
171
+ */
172
+ gatewaySyncUrl: string;
173
+ /** Same-origin PreviewGrant access route, including any framework basePath. */
174
+ tokenRoute?: string;
175
+ }
176
+ interface PreviewRuntimeCell {
177
+ state: PreviewRuntimeState;
178
+ listeners: Set<() => void>;
179
+ revisionListeners: Set<(rev: number) => void>;
180
+ contentRefreshListeners: Set<(event: PreviewContentRefreshEvent) => void>;
181
+ identityListeners: Set<(identity: PreviewIdentity | null) => void>;
182
+ inFlightInitPromise: Promise<void> | null;
183
+ cleanupActivate: (() => void) | null;
184
+ grantCoordinator: PreviewGrantCoordinator | null;
185
+ grantGeneration: number;
186
+ sessionSnapshotReady: boolean;
187
+ signalSnapshotReady: boolean;
188
+ cleanupSubscription: (() => void) | null;
189
+ cleanupRenderObservation?: () => void;
190
+ subscriptionSessionId: string | null;
191
+ cleanupResolver: (() => void) | null;
192
+ lastObservedRevision: number | null;
193
+ lastQueryTargetingFingerprint: string | null;
194
+ pendingRevisionNotification: number | null;
195
+ pendingContentRefreshEvent: PreviewContentRefreshEvent | null;
196
+ /**
197
+ * Sticky-error Layer (a) — synchronous flag flipped the moment a
198
+ * literal `'error'` transition is requested. A racing thread that
199
+ * fires `_setState(...)` next already sees terminal=true.
200
+ */
201
+ terminal: boolean;
202
+ resolver: IdentityResolver | null;
203
+ /**
204
+ * Last identity passed through to `activate()`. Used to detect
205
+ * Replace transitions (different sessionId → re-handshake).
206
+ */
207
+ authedSessionId: string | null;
208
+ }
209
+ declare global {
210
+ var __storystick_preview_runtime__: PreviewRuntimeCell | undefined;
211
+ }
212
+ export interface PreviewRuntime {
213
+ /**
214
+ * Idempotent. Safe to call from React StrictMode double-mount.
215
+ * Returns a Promise that resolves when the boot path settles — i.e.
216
+ * the state machine transitions to `'authenticated'` or `'error'`.
217
+ * **Never rejects** (ADR-116 D5 + codex R6 #6) — failures surface as
218
+ * `state === 'error'`.
219
+ */
220
+ init(opts: InitOpts): Promise<void>;
221
+ /**
222
+ * Tear down the runtime. Closes the ConvexReactClient, unsubscribes
223
+ * the session sub, drops listeners. Called from `<Storystick />`
224
+ * cleanup. HMR triggers this implicitly via module reload.
225
+ */
226
+ dispose(): void;
227
+ /** After the adapter has cleared the authorized cookie, preserve native subscribers while stopping Preview. */
228
+ deactivate(): void;
229
+ /** Announce a confirmed shared-context exit before the adapter tears this view down. */
230
+ confirmContextEnded(context: PreviewReadContext): boolean;
231
+ /** Keep an ended view terminal when its local cookie could not be cleared. */
232
+ reportContextExitUnavailable(context: PreviewReadContext): boolean;
233
+ /** Revalidate the current cookie after another view changes the browser context. */
234
+ refreshGrant(): void;
235
+ /** Snapshot read for `useSyncExternalStore.getSnapshot`. */
236
+ getState(): PreviewRuntimeState;
237
+ getLifecycleSnapshot(): PreviewLifecyclePayload | null;
238
+ /** Subscribe for `useSyncExternalStore.subscribe`. */
239
+ subscribe(listener: () => void): () => void;
240
+ /** For `<StorystickQueryProvider>` : read the reactive Convex client. */
241
+ reactiveClient(): ConvexReactClient | null;
242
+ /**
243
+ * Subscribe to `contentRevision` forward jumps. The listener fires
244
+ * exactly once per forward jump (ADR-096) ; equal/older revisions
245
+ * are ignored.
246
+ */
247
+ subscribeContentRevision(cb: (n: number) => void): () => void;
248
+ /**
249
+ * Subscribe to typed content-refresh events. This is the canonical
250
+ * customer-router refresh contract: the signal originates from the
251
+ * preview-session `contentRevision` subscription, not from
252
+ * Studio→iframe postMessage.
253
+ */
254
+ subscribeContentRefresh(cb: (event: PreviewContentRefreshEvent) => void): () => void;
255
+ /**
256
+ * ADR-116 PR5 — subscribe to identity changes. Fires on every
257
+ * Confirm / Promote / Replace transition. Consumers (PR5 —
258
+ * preservePreviewSession, customer adapter URL composer) read
259
+ * identity here instead of re-deriving from window.location.
260
+ */
261
+ subscribeIdentity(cb: (identity: PreviewIdentity | null) => void): () => void;
262
+ /** Read the current resolved identity. `null` outside preview. */
263
+ getIdentity(): PreviewIdentity | null;
264
+ /** Read the effective variant scope. `null` means living document. */
265
+ getVariantId(): string | null;
266
+ /** Execute a user gesture against the context actually addressed by the cookie. */
267
+ changePerspective(command: PreviewPerspectiveCommand, route: string): Promise<PreviewReadContext | null>;
268
+ /**
269
+ * ADR-131 Amendment 002 §5.1.bis — publish the Pattern D HTTP client
270
+ * for iframe-runtime consumers (schema-store, future tier-2/3
271
+ * helpers). Called by `<StorystickQueryProvider>` in preview mode
272
+ * when the configured client is (re-)built after a token arrival.
273
+ * Calling with `null` clears it (provider unmount).
274
+ *
275
+ * Field-only update — bypasses the sticky-error gate that protects
276
+ * the state-machine `state` field, since httpClient lifecycle is
277
+ * provider-managed and should always reflect the provider's current
278
+ * intent (notably, cleanup on unmount must run even in `'error'`).
279
+ * No other state machine invariants are affected: this writes one
280
+ * independent field only.
281
+ */
282
+ setHttpClient(client: Client | null): void;
283
+ /** Record token-free cookie lifecycle observations for C3 convergence. */
284
+ observeCookieLifecycle(event: PreviewCookieLifecycleEvent): void;
285
+ /** Enter the existing sticky auth-error surface after cookie death. */
286
+ reportCookieTerminalFailure(error: {
287
+ code: string;
288
+ message: string;
289
+ }): void;
290
+ /**
291
+ * Test-only namespace.
292
+ */
293
+ __test: {
294
+ /** Reset to INITIAL_STATE ; drop listeners ; clear cleanups. */
295
+ reset(): void;
296
+ /** Inject a partial state. Bypasses sticky-error guards. */
297
+ set(partial: Partial<PreviewRuntimeState>): void;
298
+ /** Inject a custom resolver (for tests). */
299
+ setResolver(resolver: IdentityResolver): void;
300
+ /** Wire the session watcher through the production helper. */
301
+ wireSessionSubscription(sessionId: string, client: ConvexReactClient): void;
302
+ /** Read the session id currently owning the subscription cleanup. */
303
+ getSubscriptionSessionId(): string | null;
304
+ };
305
+ }
306
+ export declare const previewRuntime: PreviewRuntime;
307
+ export {};