@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,58 @@
1
+ /**
2
+ * Iframe-side `interaction-mode` state (ADR-108 D2 + D7).
3
+ *
4
+ * Module-level state mirror of the Studio's authoritative
5
+ * `interactionMode`. Studio is the writer (via the imperative
6
+ * `bridge.setInteractionMode(mode)` method), the SDK is the reader
7
+ * (the click interceptors inside `@storystick/preview`'s overlay
8
+ * runtime + the customer's own gating if desired).
9
+ *
10
+ * Two modes:
11
+ * - `'edit'` (default): iframe-local overlay click interceptors
12
+ * `preventDefault()` on approved field targets and emit
13
+ * `storystick.preview.overlay-click` so Studio validates the
14
+ * `paintKey` before routing to the editor.
15
+ * - `'browse'`: SDK click interceptors let the click bubble untouched
16
+ * so the customer's `<a>` natively navigates. The reverse `location`
17
+ * channel still flows so Studio mirrors the iframe's URL via
18
+ * reverse doc lookup (D5).
19
+ *
20
+ * Realm-level (no React, no component/session ownership). Multiple SDK
21
+ * entrypoints can be bundled into separate physical module graphs in the same
22
+ * iframe. They must still observe one mode and one listener set, so browser
23
+ * state lives on the Window under a global-symbol key. Click interceptors live
24
+ * inside framework-agnostic overlay runtime; they read the mode through
25
+ * `getInteractionMode()` at click time.
26
+ */
27
+ export type InteractionMode = 'edit' | 'browse';
28
+ /**
29
+ * Read the current interaction mode. Click interceptors call this
30
+ * synchronously inside their handlers; the result is the mode the
31
+ * Studio last sent via `interaction-mode` envelope.
32
+ *
33
+ * Default is `'edit'` until Studio explicitly emits a mode. This
34
+ * matches today's behaviour pre-ADR-108 (everything is edit-mode by
35
+ * default) so existing customers see no behaviour change without an
36
+ * explicit toggle.
37
+ */
38
+ export declare function getInteractionMode(): InteractionMode;
39
+ /**
40
+ * Set the interaction mode. Invoked by `activate()`'s message
41
+ * listener after it validates an incoming `interaction-mode`
42
+ * envelope. Idempotent re-asserts deliberately notify subscribers: the
43
+ * in-memory value can outlive the runtime-owned stylesheet across iframe/head
44
+ * lifecycle changes, so each authoritative replay is also a reconciliation
45
+ * signal.
46
+ *
47
+ * Exposed (rather than module-private) so tests can drive the mode
48
+ * directly without going through the full envelope path; also so a
49
+ * future Studio dev-tool / hostile-actor mitigation can call it
50
+ * defensively.
51
+ */
52
+ export declare function setInteractionMode(mode: InteractionMode): void;
53
+ export declare function subscribeInteractionMode(listener: (mode: InteractionMode) => void): () => void;
54
+ /**
55
+ * Test-only reset. Vitest modules are isolated per file but cases
56
+ * within a single file would otherwise share state.
57
+ */
58
+ export declare function _resetInteractionModeForTests(): void;
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Iframe-side `interaction-mode` state (ADR-108 D2 + D7).
3
+ *
4
+ * Module-level state mirror of the Studio's authoritative
5
+ * `interactionMode`. Studio is the writer (via the imperative
6
+ * `bridge.setInteractionMode(mode)` method), the SDK is the reader
7
+ * (the click interceptors inside `@storystick/preview`'s overlay
8
+ * runtime + the customer's own gating if desired).
9
+ *
10
+ * Two modes:
11
+ * - `'edit'` (default): iframe-local overlay click interceptors
12
+ * `preventDefault()` on approved field targets and emit
13
+ * `storystick.preview.overlay-click` so Studio validates the
14
+ * `paintKey` before routing to the editor.
15
+ * - `'browse'`: SDK click interceptors let the click bubble untouched
16
+ * so the customer's `<a>` natively navigates. The reverse `location`
17
+ * channel still flows so Studio mirrors the iframe's URL via
18
+ * reverse doc lookup (D5).
19
+ *
20
+ * Realm-level (no React, no component/session ownership). Multiple SDK
21
+ * entrypoints can be bundled into separate physical module graphs in the same
22
+ * iframe. They must still observe one mode and one listener set, so browser
23
+ * state lives on the Window under a global-symbol key. Click interceptors live
24
+ * inside framework-agnostic overlay runtime; they read the mode through
25
+ * `getInteractionMode()` at click time.
26
+ */
27
+ const INTERACTION_MODE_STATE_KEY = Symbol.for('storystick.preview.interaction-mode-state.v1');
28
+ const serverState = createInteractionModeState();
29
+ function createInteractionModeState() {
30
+ return {
31
+ currentMode: 'edit',
32
+ listeners: new Set(),
33
+ };
34
+ }
35
+ function getInteractionModeState() {
36
+ if (typeof window === 'undefined')
37
+ return serverState;
38
+ const existing = Reflect.get(window, INTERACTION_MODE_STATE_KEY);
39
+ if (existing !== undefined)
40
+ return existing;
41
+ const state = createInteractionModeState();
42
+ Reflect.set(window, INTERACTION_MODE_STATE_KEY, state);
43
+ return state;
44
+ }
45
+ /**
46
+ * Read the current interaction mode. Click interceptors call this
47
+ * synchronously inside their handlers; the result is the mode the
48
+ * Studio last sent via `interaction-mode` envelope.
49
+ *
50
+ * Default is `'edit'` until Studio explicitly emits a mode. This
51
+ * matches today's behaviour pre-ADR-108 (everything is edit-mode by
52
+ * default) so existing customers see no behaviour change without an
53
+ * explicit toggle.
54
+ */
55
+ export function getInteractionMode() {
56
+ return getInteractionModeState().currentMode;
57
+ }
58
+ /**
59
+ * Set the interaction mode. Invoked by `activate()`'s message
60
+ * listener after it validates an incoming `interaction-mode`
61
+ * envelope. Idempotent re-asserts deliberately notify subscribers: the
62
+ * in-memory value can outlive the runtime-owned stylesheet across iframe/head
63
+ * lifecycle changes, so each authoritative replay is also a reconciliation
64
+ * signal.
65
+ *
66
+ * Exposed (rather than module-private) so tests can drive the mode
67
+ * directly without going through the full envelope path; also so a
68
+ * future Studio dev-tool / hostile-actor mitigation can call it
69
+ * defensively.
70
+ */
71
+ export function setInteractionMode(mode) {
72
+ const state = getInteractionModeState();
73
+ state.currentMode = mode;
74
+ for (const listener of [...state.listeners]) {
75
+ listener(mode);
76
+ }
77
+ }
78
+ export function subscribeInteractionMode(listener) {
79
+ const state = getInteractionModeState();
80
+ state.listeners.add(listener);
81
+ return () => {
82
+ state.listeners.delete(listener);
83
+ };
84
+ }
85
+ /**
86
+ * Test-only reset. Vitest modules are isolated per file but cases
87
+ * within a single file would otherwise share state.
88
+ */
89
+ export function _resetInteractionModeForTests() {
90
+ const state = getInteractionModeState();
91
+ state.currentMode = 'edit';
92
+ state.listeners.clear();
93
+ }
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Cross-frame navigation hook (ADR-087 §3.2).
3
+ *
4
+ * Studio → iframe `storystick.preview.navigate` postMessage drives
5
+ * SPA-internal navigation when the editor switches docs / changes
6
+ * the pathname / picks a variant with a different pathname. The
7
+ * handling is **best-effort CSR** (codex round 3 finding C9, ADR
8
+ * §3.2 honest claim) — Lane D' will provide framework adapters
9
+ * (Next, Astro, Vanilla). Lane D defines the contract.
10
+ *
11
+ * Two pieces :
12
+ *
13
+ * 1. `setNavigateHandler(fn)` — consumer registers a callback. The
14
+ * framework adapters call this in their provider integration
15
+ * with `(pathname) => router.push(pathname)`. The default is a
16
+ * no-op so the iframe doesn't crash if no adapter is loaded.
17
+ *
18
+ * 2. `getNavigateHandler()` — internal getter for the iframe-side
19
+ * `message` listener (in `index.ts` orchestrator). The listener
20
+ * validates the envelope through `validateNavigate` and only
21
+ * invokes the handler with a sanitized pathname.
22
+ *
23
+ * `pathname` validation is enforced upstream (in
24
+ * `validatePreviewEnvelope` from the foundation Lane C — same-origin
25
+ * absolute paths only, no `javascript:` / `//cross-origin` / scheme
26
+ * switches). The handler receives a value already cleared by that
27
+ * validator.
28
+ */
29
+ import type { PreviewScopeSelection, PreviewContextCommandFence } from '@storystick/transport-allowlist';
30
+ /**
31
+ * Optional metadata forwarded to the handler from the envelope payload.
32
+ *
33
+ * ADR-111 — `refresh` signals to framework adapters that the navigate
34
+ * accompanies a change in root-layout preview state (typically
35
+ * `draftDocumentId`). App Router adapters refresh the client Router Cache
36
+ * after the destination commits; they must not start `router.replace()` and
37
+ * `router.refresh()` as concurrent Flight transitions. Pages Router adapters
38
+ * ignore the flag (no cached layout to invalidate).
39
+ *
40
+ * Generic name on purpose — the contract is "layout preview state has
41
+ * changed." Future fields affecting `resolvePreviewDraft()` reuse the
42
+ * same flag without a handler signature bump.
43
+ */
44
+ export interface NavigateHandlerOptions {
45
+ history?: 'push' | 'replace';
46
+ refresh?: boolean;
47
+ scope: PreviewScopeSelection;
48
+ commandId?: string;
49
+ contextCommand?: PreviewContextCommandFence;
50
+ }
51
+ /**
52
+ * Handler signature. The pathname is guaranteed to be an absolute
53
+ * same-origin path starting with `/` (validated by the foundation
54
+ * envelope validator, see Lane C foundation round 3 fix).
55
+ *
56
+ * `options` is optional for backward compatibility — handlers
57
+ * registered before ADR-111 keep their `(pathname) => …` signature and
58
+ * the new field is simply ignored by them. Adapters that want to react
59
+ * to `refresh: true` add it explicitly.
60
+ */
61
+ export type NavigateHandler = (pathname: string, options?: NavigateHandlerOptions) => void;
62
+ export interface ReloadHandlerOptions {
63
+ commandId: string;
64
+ }
65
+ export type ReloadMode = 'soft' | 'hard';
66
+ export type ReloadHandler = (mode: ReloadMode, options: ReloadHandlerOptions) => void;
67
+ interface PreviewNavigationHandlerCell {
68
+ navigate: NavigateHandler;
69
+ reload: ReloadHandler;
70
+ }
71
+ declare global {
72
+ var __storystick_preview_navigation_handlers__: PreviewNavigationHandlerCell | undefined;
73
+ }
74
+ /**
75
+ * Register a navigation handler. Replaces any previously registered
76
+ * handler — the framework adapter that wires up first wins, but a
77
+ * later override (e.g. customer's own router) takes precedence.
78
+ *
79
+ * Pass `null` to reset to the default no-op (useful for tests +
80
+ * Provider unmount cleanup).
81
+ */
82
+ export declare function setNavigateHandler(fn: NavigateHandler | null): void;
83
+ /** Internal accessor — used by the orchestrator's message listener. */
84
+ export declare function getNavigateHandler(): NavigateHandler;
85
+ /** True once a framework adapter has replaced the default no-op handler. */
86
+ export declare function hasNavigateHandler(): boolean;
87
+ export declare function setReloadHandler(fn: ReloadHandler | null): void;
88
+ export declare function getReloadHandler(): ReloadHandler;
89
+ /**
90
+ * Test-only reset. Vitest modules are isolated per file, but a
91
+ * single test file with multiple `setNavigateHandler` calls would
92
+ * otherwise share state across cases.
93
+ */
94
+ export declare function _resetNavigateHandlerForTests(): void;
95
+ export {};
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Cross-frame navigation hook (ADR-087 §3.2).
3
+ *
4
+ * Studio → iframe `storystick.preview.navigate` postMessage drives
5
+ * SPA-internal navigation when the editor switches docs / changes
6
+ * the pathname / picks a variant with a different pathname. The
7
+ * handling is **best-effort CSR** (codex round 3 finding C9, ADR
8
+ * §3.2 honest claim) — Lane D' will provide framework adapters
9
+ * (Next, Astro, Vanilla). Lane D defines the contract.
10
+ *
11
+ * Two pieces :
12
+ *
13
+ * 1. `setNavigateHandler(fn)` — consumer registers a callback. The
14
+ * framework adapters call this in their provider integration
15
+ * with `(pathname) => router.push(pathname)`. The default is a
16
+ * no-op so the iframe doesn't crash if no adapter is loaded.
17
+ *
18
+ * 2. `getNavigateHandler()` — internal getter for the iframe-side
19
+ * `message` listener (in `index.ts` orchestrator). The listener
20
+ * validates the envelope through `validateNavigate` and only
21
+ * invokes the handler with a sanitized pathname.
22
+ *
23
+ * `pathname` validation is enforced upstream (in
24
+ * `validatePreviewEnvelope` from the foundation Lane C — same-origin
25
+ * absolute paths only, no `javascript:` / `//cross-origin` / scheme
26
+ * switches). The handler receives a value already cleared by that
27
+ * validator.
28
+ */
29
+ /**
30
+ * Default no-op. The framework adapters override via
31
+ * `setNavigateHandler`. Without an adapter, the iframe simply does
32
+ * not navigate when Studio asks — the published preview content
33
+ * stays on the current page. The honest claim is "best-effort" so
34
+ * a silent ignore is acceptable when no router is wired.
35
+ */
36
+ const NOOP = () => {
37
+ /* default = no-op */
38
+ };
39
+ const RELOAD_NOOP = () => {
40
+ /* default = no-op */
41
+ };
42
+ function handlerCell() {
43
+ const existing = globalThis.__storystick_preview_navigation_handlers__;
44
+ if (existing)
45
+ return existing;
46
+ const created = { navigate: NOOP, reload: RELOAD_NOOP };
47
+ globalThis.__storystick_preview_navigation_handlers__ = created;
48
+ return created;
49
+ }
50
+ /**
51
+ * Register a navigation handler. Replaces any previously registered
52
+ * handler — the framework adapter that wires up first wins, but a
53
+ * later override (e.g. customer's own router) takes precedence.
54
+ *
55
+ * Pass `null` to reset to the default no-op (useful for tests +
56
+ * Provider unmount cleanup).
57
+ */
58
+ export function setNavigateHandler(fn) {
59
+ handlerCell().navigate = fn ?? NOOP;
60
+ }
61
+ /** Internal accessor — used by the orchestrator's message listener. */
62
+ export function getNavigateHandler() {
63
+ return handlerCell().navigate;
64
+ }
65
+ /** True once a framework adapter has replaced the default no-op handler. */
66
+ export function hasNavigateHandler() {
67
+ return handlerCell().navigate !== NOOP;
68
+ }
69
+ export function setReloadHandler(fn) {
70
+ handlerCell().reload = fn ?? RELOAD_NOOP;
71
+ }
72
+ export function getReloadHandler() {
73
+ return handlerCell().reload;
74
+ }
75
+ /**
76
+ * Test-only reset. Vitest modules are isolated per file, but a
77
+ * single test file with multiple `setNavigateHandler` calls would
78
+ * otherwise share state across cases.
79
+ */
80
+ export function _resetNavigateHandlerForTests() {
81
+ const cell = handlerCell();
82
+ cell.navigate = NOOP;
83
+ cell.reload = RELOAD_NOOP;
84
+ }
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Shared visual primitive for non-interactive preview overlay labels.
3
+ *
4
+ * Studio renders slice labels in its React overlay while the preview runtime
5
+ * renders field labels directly in the customer iframe. They intentionally
6
+ * share these visual tokens so both surfaces remain one interaction system.
7
+ */
8
+ export declare const PREVIEW_OVERLAY_LABEL_GAP_PX = 4;
9
+ export declare const PREVIEW_OVERLAY_LABEL_HEIGHT_PX = 20;
10
+ export type PreviewOverlayTone = 'default' | 'model';
11
+ export declare const PREVIEW_OVERLAY_VISUAL_STYLES: {
12
+ readonly default: {
13
+ readonly color: "oklch(0.614 0.194 257 / 0.9)";
14
+ readonly revealColor: "oklch(0.614 0.194 257 / 0.95)";
15
+ };
16
+ readonly model: {
17
+ readonly color: "oklch(0.635 0.19 322 / 0.9)";
18
+ readonly revealColor: "oklch(0.635 0.19 322 / 0.95)";
19
+ };
20
+ };
21
+ export declare const PREVIEW_OVERLAY_LABEL_VISUAL_STYLES: {
22
+ readonly default: {
23
+ readonly background: "oklch(0.46 0.12 257 / 0.96)";
24
+ readonly borderRadius: "999px";
25
+ readonly padding: "3px 8px";
26
+ readonly font: "500 11px/14px system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif";
27
+ readonly color: "white";
28
+ readonly boxShadow: "0 0 0 1px oklch(1 0 0 / 0.72)";
29
+ };
30
+ readonly model: {
31
+ readonly background: "oklch(0.46 0.11 322 / 0.96)";
32
+ readonly borderRadius: "999px";
33
+ readonly padding: "3px 8px";
34
+ readonly font: "500 11px/14px system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif";
35
+ readonly color: "white";
36
+ readonly boxShadow: "0 0 0 1px oklch(1 0 0 / 0.72)";
37
+ };
38
+ };
39
+ export declare function previewOverlayVisualStyle(tone: PreviewOverlayTone): {
40
+ readonly color: "oklch(0.614 0.194 257 / 0.9)";
41
+ readonly revealColor: "oklch(0.614 0.194 257 / 0.95)";
42
+ } | {
43
+ readonly color: "oklch(0.635 0.19 322 / 0.9)";
44
+ readonly revealColor: "oklch(0.635 0.19 322 / 0.95)";
45
+ };
46
+ export declare function previewOverlayLabelVisualStyle(tone: PreviewOverlayTone): {
47
+ readonly background: "oklch(0.46 0.12 257 / 0.96)";
48
+ readonly borderRadius: "999px";
49
+ readonly padding: "3px 8px";
50
+ readonly font: "500 11px/14px system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif";
51
+ readonly color: "white";
52
+ readonly boxShadow: "0 0 0 1px oklch(1 0 0 / 0.72)";
53
+ } | {
54
+ readonly background: "oklch(0.46 0.11 322 / 0.96)";
55
+ readonly borderRadius: "999px";
56
+ readonly padding: "3px 8px";
57
+ readonly font: "500 11px/14px system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif";
58
+ readonly color: "white";
59
+ readonly boxShadow: "0 0 0 1px oklch(1 0 0 / 0.72)";
60
+ };
61
+ export interface PreviewOverlayLabelHorizontalPositionInput {
62
+ targetLeft: number;
63
+ targetWidth: number;
64
+ labelWidth: number;
65
+ viewportWidth: number;
66
+ gap?: number;
67
+ }
68
+ export interface PreviewOverlayLabelHorizontalPosition {
69
+ left: number;
70
+ anchor: 'left' | 'right';
71
+ }
72
+ /**
73
+ * Prefer the target's right edge while the label fits inside it. A label wider
74
+ * than its target instead grows from the target's left edge, unless that would
75
+ * leave the viewport. The final clamp is the safety net for edge targets and
76
+ * labels wider than the available viewport.
77
+ */
78
+ export declare function resolvePreviewOverlayLabelHorizontalPosition({ targetLeft, targetWidth, labelWidth, viewportWidth, gap, }: PreviewOverlayLabelHorizontalPositionInput): PreviewOverlayLabelHorizontalPosition;
79
+ export declare function applyPreviewOverlayLabelVisualStyle(element: HTMLElement, tone?: PreviewOverlayTone): void;
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Shared visual primitive for non-interactive preview overlay labels.
3
+ *
4
+ * Studio renders slice labels in its React overlay while the preview runtime
5
+ * renders field labels directly in the customer iframe. They intentionally
6
+ * share these visual tokens so both surfaces remain one interaction system.
7
+ */
8
+ export const PREVIEW_OVERLAY_LABEL_GAP_PX = 4;
9
+ export const PREVIEW_OVERLAY_LABEL_HEIGHT_PX = 20;
10
+ export const PREVIEW_OVERLAY_VISUAL_STYLES = {
11
+ default: {
12
+ color: 'oklch(0.614 0.194 257 / 0.9)',
13
+ revealColor: 'oklch(0.614 0.194 257 / 0.95)',
14
+ },
15
+ model: {
16
+ color: 'oklch(0.635 0.19 322 / 0.9)',
17
+ revealColor: 'oklch(0.635 0.19 322 / 0.95)',
18
+ },
19
+ };
20
+ const PREVIEW_OVERLAY_LABEL_BASE_STYLE = {
21
+ borderRadius: '999px',
22
+ padding: '3px 8px',
23
+ font: '500 11px/14px system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
24
+ color: 'white',
25
+ boxShadow: '0 0 0 1px oklch(1 0 0 / 0.72)',
26
+ };
27
+ export const PREVIEW_OVERLAY_LABEL_VISUAL_STYLES = {
28
+ default: {
29
+ ...PREVIEW_OVERLAY_LABEL_BASE_STYLE,
30
+ background: 'oklch(0.46 0.12 257 / 0.96)',
31
+ },
32
+ model: {
33
+ ...PREVIEW_OVERLAY_LABEL_BASE_STYLE,
34
+ background: 'oklch(0.46 0.11 322 / 0.96)',
35
+ },
36
+ };
37
+ export function previewOverlayVisualStyle(tone) {
38
+ return PREVIEW_OVERLAY_VISUAL_STYLES[tone];
39
+ }
40
+ export function previewOverlayLabelVisualStyle(tone) {
41
+ return PREVIEW_OVERLAY_LABEL_VISUAL_STYLES[tone];
42
+ }
43
+ /**
44
+ * Prefer the target's right edge while the label fits inside it. A label wider
45
+ * than its target instead grows from the target's left edge, unless that would
46
+ * leave the viewport. The final clamp is the safety net for edge targets and
47
+ * labels wider than the available viewport.
48
+ */
49
+ export function resolvePreviewOverlayLabelHorizontalPosition({ targetLeft, targetWidth, labelWidth, viewportWidth, gap = PREVIEW_OVERLAY_LABEL_GAP_PX, }) {
50
+ const safeLabelWidth = Math.max(0, labelWidth);
51
+ const viewportRight = Math.max(gap, viewportWidth - gap);
52
+ const leftAnchoredPosition = targetLeft;
53
+ const rightAnchoredPosition = targetLeft + targetWidth - safeLabelWidth;
54
+ const leftAnchorFits = leftAnchoredPosition >= gap &&
55
+ leftAnchoredPosition + safeLabelWidth <= viewportRight;
56
+ const preferLeft = safeLabelWidth > targetWidth && leftAnchorFits;
57
+ const anchor = preferLeft ? 'left' : 'right';
58
+ const idealLeft = preferLeft ? leftAnchoredPosition : rightAnchoredPosition;
59
+ const maxLeft = Math.max(gap, viewportRight - safeLabelWidth);
60
+ return {
61
+ left: Math.min(Math.max(idealLeft, gap), maxLeft),
62
+ anchor,
63
+ };
64
+ }
65
+ export function applyPreviewOverlayLabelVisualStyle(element, tone = 'default') {
66
+ const style = previewOverlayLabelVisualStyle(tone);
67
+ element.style.borderRadius = style.borderRadius;
68
+ element.style.padding = style.padding;
69
+ element.style.font = style.font;
70
+ element.style.color = style.color;
71
+ element.style.background = style.background;
72
+ element.style.boxShadow = style.boxShadow;
73
+ }
@@ -0,0 +1,95 @@
1
+ import { type PreviewReadScopeEcho } from '@storystick/client/internal';
2
+ import type { PreviewReadContext } from '@storystick/types/preview-query-targeting';
3
+ import type { PublicProvenanceSidecar } from './sidecar.js';
4
+ import type { DirectEditLocator } from './direct-edit-locator.js';
5
+ import type { MarkerRecord } from './types.js';
6
+ import type { PreviewFrameDependencySet } from './frame-dependencies.js';
7
+ export interface AnchorInput {
8
+ readonly markerId: string;
9
+ /** Preview-only identity of one concrete paint target, including memory-backed coded text. */
10
+ readonly paintId?: string;
11
+ readonly tagName: string;
12
+ readonly text: string;
13
+ readonly connected: boolean;
14
+ readonly carrier?: 'attribute' | 'coded-text';
15
+ readonly documentRect?: {
16
+ x: number;
17
+ y: number;
18
+ width: number;
19
+ height: number;
20
+ };
21
+ }
22
+ export type PreviewLifecycleSourceOfTruth = 'studio' | 'iframe' | 'system';
23
+ export interface PreviewRenderIdentity {
24
+ readonly generation: number;
25
+ readonly sourceOfTruth: PreviewLifecycleSourceOfTruth;
26
+ readonly commandId?: string;
27
+ readonly sessionId?: string;
28
+ readonly pathname?: string;
29
+ readonly routeHash?: string;
30
+ readonly codeVersion?: string;
31
+ readonly renderId?: string;
32
+ readonly variantId?: string | null;
33
+ readonly readContext?: PreviewReadContext;
34
+ readonly routeLocale?: string | null;
35
+ }
36
+ /** Exact local paint projection of the canonical render identity. */
37
+ export type PreviewPaintRenderIdentity = PreviewRenderIdentity & Required<Pick<PreviewRenderIdentity, 'sessionId' | 'pathname' | 'routeHash' | 'codeVersion' | 'renderId' | 'variantId'>>;
38
+ export type PreviewPaintRenderIdentityMismatch = 'session-id' | 'generation' | 'source-of-truth' | 'command-id' | 'pathname' | 'route-hash' | 'code-version' | 'render-id' | 'variant-id' | 'read-context' | 'route-locale';
39
+ export declare function previewPaintRenderIdentityEquals(left: PreviewPaintRenderIdentity, right: PreviewPaintRenderIdentity): boolean;
40
+ export declare function previewPaintRenderIdentityMismatch(left: PreviewPaintRenderIdentity, right: PreviewPaintRenderIdentity): PreviewPaintRenderIdentityMismatch | null;
41
+ export interface AnchorMap {
42
+ readonly sessionId: string;
43
+ readonly generation: number;
44
+ readonly sourceOfTruth: PreviewLifecycleSourceOfTruth;
45
+ readonly commandId?: string;
46
+ readonly pathname: string;
47
+ readonly routeHash: string;
48
+ readonly codeVersion: string;
49
+ readonly variantId: string | null;
50
+ readonly previewScope?: PreviewReadScopeEcho;
51
+ readonly renderId: string;
52
+ readonly renderNonce?: string;
53
+ readonly emissionRevision?: number;
54
+ readonly dependencySet?: PreviewFrameDependencySet;
55
+ readonly phase: string;
56
+ readonly pending: boolean;
57
+ readonly anchors: readonly AnchorInput[];
58
+ /** Public marker-to-owner addresses delivered with this exact render. */
59
+ readonly editLocators?: readonly DirectEditMarkerLocator[];
60
+ readonly sidecarMarkerIds: readonly string[];
61
+ readonly invalidMarkerIds: readonly string[];
62
+ /** Opaque sidecar marker ids absent from the rendered DOM, non-fatal. */
63
+ readonly unpaintedMarkerIds: readonly string[];
64
+ readonly missingMarkerIds: readonly string[];
65
+ readonly ambiguousMarkerIds: readonly string[];
66
+ readonly contentMismatchMarkerIds: readonly string[];
67
+ readonly fatalContentMismatchMarkerIds?: readonly string[];
68
+ readonly paintDiagnostics?: AnchorPaintDiagnostics;
69
+ readonly valid: boolean;
70
+ }
71
+ export interface DirectEditMarkerLocator {
72
+ readonly markerId: string;
73
+ readonly locator: DirectEditLocator;
74
+ readonly modality: MarkerRecord['modality'];
75
+ readonly presence?: MarkerRecord['presence'];
76
+ }
77
+ export interface AnchorPaintDiagnostics {
78
+ readonly codedTextOnlyNotPaintable: readonly string[];
79
+ readonly domTargetPainted: readonly string[];
80
+ }
81
+ export interface BuildAnchorMapInput {
82
+ readonly sessionId: string;
83
+ readonly generation: number;
84
+ readonly sourceOfTruth: PreviewLifecycleSourceOfTruth;
85
+ readonly commandId?: string;
86
+ readonly pathname: string;
87
+ readonly routeHash: string;
88
+ readonly codeVersion: string;
89
+ readonly variantId?: string | null;
90
+ readonly sidecar: PublicProvenanceSidecar;
91
+ readonly anchors: readonly AnchorInput[];
92
+ readonly phase: string;
93
+ readonly pending?: boolean;
94
+ }
95
+ export declare function buildAnchorMap(input: BuildAnchorMapInput): AnchorMap;