@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,177 @@
1
+ import { fingerprintRenderedText } from './normalize.js';
2
+ import { normalizePreviewVariantId } from '../identity/preview-identity.js';
3
+ export function previewPaintRenderIdentityEquals(left, right) {
4
+ return previewPaintRenderIdentityMismatch(left, right) === null;
5
+ }
6
+ export function previewPaintRenderIdentityMismatch(left, right) {
7
+ if (left.sessionId !== right.sessionId)
8
+ return 'session-id';
9
+ if (left.generation !== right.generation)
10
+ return 'generation';
11
+ if (left.sourceOfTruth !== right.sourceOfTruth)
12
+ return 'source-of-truth';
13
+ if ((left.commandId ?? null) !== (right.commandId ?? null))
14
+ return 'command-id';
15
+ if (left.pathname !== right.pathname)
16
+ return 'pathname';
17
+ if (left.routeHash !== right.routeHash)
18
+ return 'route-hash';
19
+ if (left.codeVersion !== right.codeVersion)
20
+ return 'code-version';
21
+ if (left.renderId !== right.renderId)
22
+ return 'render-id';
23
+ if (normalizePreviewVariantId(left.variantId) !==
24
+ normalizePreviewVariantId(right.variantId)) {
25
+ return 'variant-id';
26
+ }
27
+ if (!previewReadContextsEqual(left.readContext, right.readContext)) {
28
+ return 'read-context';
29
+ }
30
+ if ((left.routeLocale ?? null) !== (right.routeLocale ?? null)) {
31
+ return 'route-locale';
32
+ }
33
+ return null;
34
+ }
35
+ function previewReadContextsEqual(left, right) {
36
+ if (left === undefined || right === undefined)
37
+ return left === right;
38
+ if (left.contextId !== right.contextId || left.revision !== right.revision) {
39
+ return false;
40
+ }
41
+ if (left.perspective.kind !== right.perspective.kind)
42
+ return false;
43
+ switch (left.perspective.kind) {
44
+ case 'published':
45
+ case 'living':
46
+ return true;
47
+ case 'variant':
48
+ return (right.perspective.kind === 'variant' &&
49
+ left.perspective.variantId === right.perspective.variantId);
50
+ case 'release':
51
+ return (right.perspective.kind === 'release' &&
52
+ left.perspective.releaseId === right.perspective.releaseId);
53
+ }
54
+ }
55
+ export function buildAnchorMap(input) {
56
+ const pending = input.pending ?? false;
57
+ const sidecarVariantId = normalizePreviewVariantId(input.sidecar.variantId);
58
+ const expectedVariantId = input.variantId === undefined
59
+ ? sidecarVariantId
60
+ : normalizePreviewVariantId(input.variantId);
61
+ const sidecarMarkers = new Map(input.sidecar.markers.map((marker) => [marker.markerId, marker]));
62
+ const sidecarMarkerIds = new Set(sidecarMarkers.keys());
63
+ const rawAnchors = input.anchors.filter((anchor) => anchor.markerId.length > 0);
64
+ const counts = new Map();
65
+ for (const anchor of rawAnchors)
66
+ counts.set(anchor.markerId, (counts.get(anchor.markerId) || 0) + 1);
67
+ const observedMarkerIds = new Set(rawAnchors.map((anchor) => anchor.markerId));
68
+ const invalidMarkerIds = rawAnchors
69
+ .filter((anchor) => !sidecarMarkerIds.has(anchor.markerId))
70
+ .map((anchor) => anchor.markerId);
71
+ const unpaintedMarkerIds = [...sidecarMarkerIds].filter((markerId) => !observedMarkerIds.has(markerId));
72
+ const missingMarkerIds = [...sidecarMarkerIds].filter((markerId) => !observedMarkerIds.has(markerId) && isRequiredMarker(markerId, sidecarMarkers));
73
+ const ambiguousMarkerIds = [...counts.entries()]
74
+ .filter(([markerId, count]) => count > 1 && !allowsMultipleAnchors(markerId, sidecarMarkers))
75
+ .map(([markerId]) => markerId);
76
+ const contentMismatchMarkerIds = rawAnchors
77
+ .filter((anchor) => {
78
+ const sidecarMarker = sidecarMarkers.get(anchor.markerId);
79
+ const anchorFingerprint = fingerprintRenderedText(anchor.text);
80
+ return sidecarMarker?.renderedTextFingerprint !== undefined &&
81
+ sidecarMarker.renderedTextFingerprint !== anchorFingerprint;
82
+ })
83
+ .map((anchor) => anchor.markerId);
84
+ const contentMismatchMarkerIdSet = new Set(contentMismatchMarkerIds);
85
+ const fatalContentMismatchMarkerIds = [
86
+ ...contentMismatchMarkerIdSet,
87
+ ].filter((markerId) => isRequiredMarker(markerId, sidecarMarkers));
88
+ const anchors = rawAnchors.filter((anchor) => anchor.connected &&
89
+ sidecarMarkerIds.has(anchor.markerId) &&
90
+ ((counts.get(anchor.markerId) || 0) === 1 || allowsMultipleAnchors(anchor.markerId, sidecarMarkers)) &&
91
+ !contentMismatchMarkerIdSet.has(anchor.markerId));
92
+ const codedTextMarkerIds = new Set(anchors
93
+ .filter((anchor) => anchor.carrier === 'coded-text')
94
+ .map((anchor) => anchor.markerId));
95
+ const domMarkerIds = new Set(anchors.filter(isPaintableAnchor).map((anchor) => anchor.markerId));
96
+ const dependencySet = input.sidecar.dependencySet;
97
+ const dependencySetMatches = dependencySet === undefined ||
98
+ (dependencySet.sessionId === input.sessionId &&
99
+ dependencySet.routeHash === input.sidecar.routeHash &&
100
+ dependencySet.codeVersion === input.sidecar.codeVersion &&
101
+ dependencySet.renderId === input.sidecar.renderId &&
102
+ dependencySet.generation === input.sidecar.generation &&
103
+ normalizePreviewVariantId(dependencySet.variantId) === sidecarVariantId);
104
+ return {
105
+ sessionId: input.sessionId,
106
+ generation: input.generation,
107
+ sourceOfTruth: input.sourceOfTruth,
108
+ ...(input.commandId !== undefined && { commandId: input.commandId }),
109
+ pathname: input.pathname,
110
+ routeHash: input.routeHash,
111
+ codeVersion: input.sidecar.codeVersion,
112
+ variantId: sidecarVariantId,
113
+ ...(input.sidecar.previewScope !== undefined
114
+ ? { previewScope: input.sidecar.previewScope }
115
+ : {}),
116
+ renderId: input.sidecar.renderId,
117
+ ...(input.sidecar.renderNonce !== undefined
118
+ ? { renderNonce: input.sidecar.renderNonce }
119
+ : {}),
120
+ ...(input.sidecar.emissionRevision !== undefined
121
+ ? { emissionRevision: input.sidecar.emissionRevision }
122
+ : {}),
123
+ ...(dependencySet !== undefined && dependencySetMatches
124
+ ? { dependencySet }
125
+ : {}),
126
+ phase: input.phase,
127
+ pending,
128
+ anchors,
129
+ editLocators: input.sidecar.markers.flatMap((marker) => marker.editLocator === undefined
130
+ ? []
131
+ : [{
132
+ markerId: marker.markerId,
133
+ locator: marker.editLocator,
134
+ modality: marker.modality,
135
+ ...(marker.presence !== undefined
136
+ ? { presence: marker.presence }
137
+ : {}),
138
+ }]),
139
+ sidecarMarkerIds: [...sidecarMarkerIds],
140
+ invalidMarkerIds: [...new Set(invalidMarkerIds)],
141
+ unpaintedMarkerIds,
142
+ missingMarkerIds,
143
+ ambiguousMarkerIds,
144
+ contentMismatchMarkerIds: [...contentMismatchMarkerIdSet],
145
+ fatalContentMismatchMarkerIds,
146
+ paintDiagnostics: {
147
+ codedTextOnlyNotPaintable: [...sidecarMarkerIds].filter((markerId) => codedTextMarkerIds.has(markerId) && !domMarkerIds.has(markerId)),
148
+ domTargetPainted: [...sidecarMarkerIds].filter((markerId) => domMarkerIds.has(markerId)),
149
+ },
150
+ valid: !pending &&
151
+ input.sessionId.length > 0 &&
152
+ Number.isFinite(input.generation) &&
153
+ input.generation > 0 &&
154
+ input.routeHash.length > 0 &&
155
+ input.codeVersion.length > 0 &&
156
+ input.sidecar.routeHash.length > 0 &&
157
+ input.sidecar.codeVersion.length > 0 &&
158
+ input.sidecar.renderId.length > 0 &&
159
+ input.sidecar.routeHash === input.routeHash &&
160
+ input.sidecar.codeVersion === input.codeVersion &&
161
+ sidecarVariantId === expectedVariantId &&
162
+ dependencySetMatches &&
163
+ invalidMarkerIds.length === 0 &&
164
+ missingMarkerIds.length === 0 &&
165
+ fatalContentMismatchMarkerIds.length === 0,
166
+ };
167
+ }
168
+ function isPaintableAnchor(anchor) {
169
+ return typeof anchor.paintId === 'string' && anchor.paintId.length > 0;
170
+ }
171
+ function allowsMultipleAnchors(markerId, sidecarMarkers) {
172
+ return sidecarMarkers.get(markerId)?.anchorMultiplicity === 'multi';
173
+ }
174
+ function isRequiredMarker(markerId, sidecarMarkers) {
175
+ const presence = sidecarMarkers.get(markerId)?.presence;
176
+ return presence !== 'optional' && presence !== 'expected';
177
+ }
@@ -0,0 +1,51 @@
1
+ import type { PreviewProvenanceCarrierPlan, ProvenanceAnchorKind, ProvenanceMarkerModality } from './types.js';
2
+ export declare const PREVIEW_PROVENANCE_ATTRIBUTE_CARRIER = "data-ss-p";
3
+ export declare const PREVIEW_PROVENANCE_ELEMENT_ATTRIBUTE_CARRIER = "data-ss-e";
4
+ declare const ATTRIBUTE_NAME_BY_CARRIER: {
5
+ readonly "data-ss-p": "data-ss-p";
6
+ readonly "data-ss-e": "data-ss-e";
7
+ };
8
+ export declare function createPreviewProvenanceAttributeCarrierPlan(input: {
9
+ readonly modality: ProvenanceMarkerModality;
10
+ readonly anchorKind: ProvenanceAnchorKind;
11
+ readonly carrier?: keyof typeof ATTRIBUTE_NAME_BY_CARRIER;
12
+ }): PreviewProvenanceCarrierPlan;
13
+ export declare function createPreviewProvenanceCodedTextCarrierPlan(input: {
14
+ readonly surface: 'direct-editorial-text' | 'rich-text-text-node' | 'fallback-text-carrier';
15
+ }): PreviewProvenanceCarrierPlan;
16
+ export declare function createPreviewProvenanceTextRangeCarrierPlan(): PreviewProvenanceCarrierPlan;
17
+ /**
18
+ * Conserved deliberately: these planned carriers are the annotation-model
19
+ * seams for href, src/media, and composite scope output. They are future
20
+ * product surface, not retired-mechanism residue.
21
+ */
22
+ export declare const previewProvenanceFutureCarrierPlans: readonly [{
23
+ readonly kind: "future-attribute";
24
+ readonly carrier: "attribute-output-carrier";
25
+ readonly modality: "attr:href";
26
+ readonly anchorKind: "attribute";
27
+ readonly status: "planned";
28
+ readonly previewOnly: true;
29
+ readonly markerPayload: "opaque-marker-id";
30
+ readonly reason: "URL and technical attributes need explicit attribute capture, not coded text.";
31
+ }, {
32
+ readonly kind: "future-media";
33
+ readonly carrier: "media-carrier";
34
+ readonly modality: "media";
35
+ readonly anchorKind: "attribute";
36
+ readonly status: "planned";
37
+ readonly previewOnly: true;
38
+ readonly markerPayload: "opaque-marker-id";
39
+ readonly reason: "Media fields need an explicit media carrier for src-like outputs.";
40
+ }, {
41
+ readonly kind: "future-scope";
42
+ readonly carrier: "scope-carrier";
43
+ readonly modality: "scope";
44
+ readonly anchorKind: "scope";
45
+ readonly status: "planned";
46
+ readonly previewOnly: true;
47
+ readonly markerPayload: "opaque-marker-id";
48
+ readonly reason: "Composite or structural outputs need group/scope ownership.";
49
+ }];
50
+ export declare function attrsForPreviewProvenanceCarrierPlans(markerId: string | null, carrierPlans: readonly PreviewProvenanceCarrierPlan[]): Readonly<Record<string, string>>;
51
+ export {};
@@ -0,0 +1,90 @@
1
+ export const PREVIEW_PROVENANCE_ATTRIBUTE_CARRIER = 'data-ss-p';
2
+ export const PREVIEW_PROVENANCE_ELEMENT_ATTRIBUTE_CARRIER = 'data-ss-e';
3
+ const ATTRIBUTE_NAME_BY_CARRIER = {
4
+ [PREVIEW_PROVENANCE_ATTRIBUTE_CARRIER]: 'data-ss-p',
5
+ [PREVIEW_PROVENANCE_ELEMENT_ATTRIBUTE_CARRIER]: 'data-ss-e',
6
+ };
7
+ export function createPreviewProvenanceAttributeCarrierPlan(input) {
8
+ return {
9
+ kind: 'attribute',
10
+ carrier: input.carrier ?? PREVIEW_PROVENANCE_ATTRIBUTE_CARRIER,
11
+ modality: input.modality,
12
+ anchorKind: input.anchorKind,
13
+ status: 'emitted',
14
+ previewOnly: true,
15
+ markerPayload: 'opaque-marker-id',
16
+ };
17
+ }
18
+ export function createPreviewProvenanceCodedTextCarrierPlan(input) {
19
+ return {
20
+ kind: 'text',
21
+ carrier: 'coded-text-unicode-tags',
22
+ modality: 'text',
23
+ anchorKind: 'element',
24
+ status: 'emitted',
25
+ previewOnly: true,
26
+ markerPayload: 'opaque-marker-id',
27
+ surface: input.surface,
28
+ };
29
+ }
30
+ export function createPreviewProvenanceTextRangeCarrierPlan() {
31
+ return {
32
+ kind: 'text-range',
33
+ carrier: 'coded-text-unicode-tags',
34
+ modality: 'text',
35
+ anchorKind: 'text-range',
36
+ status: 'emitted',
37
+ previewOnly: true,
38
+ markerPayload: 'opaque-marker-id',
39
+ surface: 'non-promoted-text-range',
40
+ };
41
+ }
42
+ /**
43
+ * Conserved deliberately: these planned carriers are the annotation-model
44
+ * seams for href, src/media, and composite scope output. They are future
45
+ * product surface, not retired-mechanism residue.
46
+ */
47
+ export const previewProvenanceFutureCarrierPlans = [
48
+ {
49
+ kind: 'future-attribute',
50
+ carrier: 'attribute-output-carrier',
51
+ modality: 'attr:href',
52
+ anchorKind: 'attribute',
53
+ status: 'planned',
54
+ previewOnly: true,
55
+ markerPayload: 'opaque-marker-id',
56
+ reason: 'URL and technical attributes need explicit attribute capture, not coded text.',
57
+ },
58
+ {
59
+ kind: 'future-media',
60
+ carrier: 'media-carrier',
61
+ modality: 'media',
62
+ anchorKind: 'attribute',
63
+ status: 'planned',
64
+ previewOnly: true,
65
+ markerPayload: 'opaque-marker-id',
66
+ reason: 'Media fields need an explicit media carrier for src-like outputs.',
67
+ },
68
+ {
69
+ kind: 'future-scope',
70
+ carrier: 'scope-carrier',
71
+ modality: 'scope',
72
+ anchorKind: 'scope',
73
+ status: 'planned',
74
+ previewOnly: true,
75
+ markerPayload: 'opaque-marker-id',
76
+ reason: 'Composite or structural outputs need group/scope ownership.',
77
+ },
78
+ ];
79
+ export function attrsForPreviewProvenanceCarrierPlans(markerId, carrierPlans) {
80
+ if (markerId === null || markerId.length === 0)
81
+ return {};
82
+ const attrs = {};
83
+ for (const plan of carrierPlans) {
84
+ if (plan.kind !== 'attribute')
85
+ continue;
86
+ const attributeName = ATTRIBUTE_NAME_BY_CARRIER[plan.carrier];
87
+ attrs[attributeName] = markerId;
88
+ }
89
+ return attrs;
90
+ }
@@ -0,0 +1,20 @@
1
+ export { PREVIEW_PROVENANCE_CODED_TEXT_START, PREVIEW_PROVENANCE_CODED_TEXT_SEPARATOR, PREVIEW_PROVENANCE_CODED_TEXT_END, encodePreviewProvenanceText, decodePreviewProvenanceText, stripPreviewProvenanceTextMarkers, type PreviewProvenanceCodedTextSegment, type PreviewProvenanceCodedTextDecodeResult, } from '@storystick/types/internal/preview-provenance';
2
+ export type PreviewProvenanceCodedTextPathPart = string | number;
3
+ export type PreviewProvenanceCodedTextSurface = 'direct-editorial-text' | 'rich-text-text-node' | 'fallback-text-carrier';
4
+ export type PreviewProvenanceCodedTextPolicyResult = {
5
+ readonly allowed: true;
6
+ readonly surface: PreviewProvenanceCodedTextSurface;
7
+ } | {
8
+ readonly allowed: false;
9
+ readonly reason: 'technical-field' | 'url-like-field' | 'date-like-field' | 'private-or-system-field' | 'invalid-path';
10
+ };
11
+ export declare function codedTextPathForCanonicalPath(canonicalPath: string, rootPrefix?: readonly PreviewProvenanceCodedTextPathPart[]): readonly PreviewProvenanceCodedTextPathPart[] | null;
12
+ export declare function classifyPreviewProvenanceCodedTextTarget(input: {
13
+ readonly canonicalPath: string;
14
+ readonly sourceKind?: 'stored' | 'richtext';
15
+ readonly fallback?: boolean;
16
+ }): PreviewProvenanceCodedTextPolicyResult;
17
+ export declare function writePreviewProvenanceCodedTextAtPath(root: unknown, path: readonly PreviewProvenanceCodedTextPathPart[], markerId: string | null | undefined, expectedText: string): boolean;
18
+ export declare function writePreviewProvenanceObjectMarkerAtPath(root: unknown, path: readonly PreviewProvenanceCodedTextPathPart[], markerId: string | null | undefined): boolean;
19
+ export declare function previewProvenanceRichTextTextNodes(content: unknown): readonly string[];
20
+ export declare function writePreviewProvenanceCodedRichTextTextNodeAtPath(root: unknown, path: readonly PreviewProvenanceCodedTextPathPart[], textNodeIndex: number, markerId: string | null | undefined, expectedText: string): boolean;
@@ -0,0 +1,302 @@
1
+ import { encodePreviewProvenanceText, } from '@storystick/types/internal/preview-provenance';
2
+ export { PREVIEW_PROVENANCE_CODED_TEXT_START, PREVIEW_PROVENANCE_CODED_TEXT_SEPARATOR, PREVIEW_PROVENANCE_CODED_TEXT_END, encodePreviewProvenanceText, decodePreviewProvenanceText, stripPreviewProvenanceTextMarkers, } from '@storystick/types/internal/preview-provenance';
3
+ const PREVIEW_OBJECT_MARKER_KEY = '_ssMarker';
4
+ const PREVIEW_OBJECT_MARKER_TAG = 'ss-marker-1';
5
+ function withPreviewObjectMarker(value, markerId) {
6
+ if (typeof markerId !== 'string' ||
7
+ markerId.length === 0 ||
8
+ Array.isArray(value)) {
9
+ return value;
10
+ }
11
+ return {
12
+ ...value,
13
+ [PREVIEW_OBJECT_MARKER_KEY]: {
14
+ v: PREVIEW_OBJECT_MARKER_TAG,
15
+ m: markerId,
16
+ },
17
+ };
18
+ }
19
+ const URL_LIKE_CODED_TEXT_FIELD_NAMES = new Set([
20
+ 'href',
21
+ 'pathname',
22
+ 'slug',
23
+ 'src',
24
+ 'url',
25
+ ]);
26
+ const TECHNICAL_CODED_TEXT_FIELD_NAMES = new Set([
27
+ 'anchor',
28
+ 'asset',
29
+ 'blurHash',
30
+ 'code',
31
+ 'crop',
32
+ 'document',
33
+ 'dominantColor',
34
+ 'extension',
35
+ 'filename',
36
+ 'icon',
37
+ 'id',
38
+ 'language',
39
+ 'linkType',
40
+ 'lqip',
41
+ 'mimeType',
42
+ 'rel',
43
+ 'target',
44
+ 'thumbhash',
45
+ 'variant',
46
+ ]);
47
+ const DATE_LIKE_CODED_TEXT_FIELD_NAMES = new Set([
48
+ 'createdAt',
49
+ 'date',
50
+ 'datetime',
51
+ 'publishedAt',
52
+ 'updatedAt',
53
+ ]);
54
+ export function codedTextPathForCanonicalPath(canonicalPath, rootPrefix = ['fields']) {
55
+ const segments = parseCanonicalPath(canonicalPath);
56
+ if (segments.length === 0)
57
+ return null;
58
+ return [...rootPrefix, ...segments];
59
+ }
60
+ export function classifyPreviewProvenanceCodedTextTarget(input) {
61
+ const parts = parseCanonicalPath(input.canonicalPath);
62
+ if (parts.length === 0)
63
+ return { allowed: false, reason: 'invalid-path' };
64
+ for (const part of parts) {
65
+ if (typeof part !== 'string')
66
+ continue;
67
+ if (part.startsWith('_')) {
68
+ return { allowed: false, reason: 'private-or-system-field' };
69
+ }
70
+ if (URL_LIKE_CODED_TEXT_FIELD_NAMES.has(part)) {
71
+ return { allowed: false, reason: 'url-like-field' };
72
+ }
73
+ if (DATE_LIKE_CODED_TEXT_FIELD_NAMES.has(part)) {
74
+ return { allowed: false, reason: 'date-like-field' };
75
+ }
76
+ if (TECHNICAL_CODED_TEXT_FIELD_NAMES.has(part)) {
77
+ return { allowed: false, reason: 'technical-field' };
78
+ }
79
+ }
80
+ if (input.sourceKind === 'richtext') {
81
+ return { allowed: true, surface: 'rich-text-text-node' };
82
+ }
83
+ if (input.fallback === true) {
84
+ return { allowed: true, surface: 'fallback-text-carrier' };
85
+ }
86
+ return { allowed: true, surface: 'direct-editorial-text' };
87
+ }
88
+ export function writePreviewProvenanceCodedTextAtPath(root, path, markerId, expectedText) {
89
+ if (typeof markerId !== 'string' || markerId.length === 0)
90
+ return false;
91
+ if (root === null || root === undefined)
92
+ return false;
93
+ const direct = mutableValueAtPath(root, path);
94
+ if (direct !== null && direct.value === expectedText) {
95
+ direct.set(encodePreviewProvenanceText(markerId, expectedText));
96
+ return true;
97
+ }
98
+ return writePreviewProvenanceCodedRichTextEmbedDataAtPath(root, path, (value) => value === expectedText
99
+ ? encodePreviewProvenanceText(markerId, expectedText)
100
+ : null);
101
+ }
102
+ export function writePreviewProvenanceObjectMarkerAtPath(root, path, markerId) {
103
+ if (typeof markerId !== 'string' || markerId.length === 0)
104
+ return false;
105
+ if (root === null || root === undefined)
106
+ return false;
107
+ const direct = mutableValueAtPath(root, path);
108
+ if (direct !== null) {
109
+ const directObject = asRecord(direct.value);
110
+ if (directObject !== null) {
111
+ direct.set(withPreviewObjectMarker(directObject, markerId));
112
+ return true;
113
+ }
114
+ }
115
+ return writePreviewProvenanceCodedRichTextEmbedDataAtPath(root, path, (value) => {
116
+ const object = asRecord(value);
117
+ return object === null ? null : withPreviewObjectMarker(object, markerId);
118
+ });
119
+ }
120
+ export function previewProvenanceRichTextTextNodes(content) {
121
+ const doc = parseRichTextDocument(content);
122
+ if (doc === null)
123
+ return [];
124
+ const nodes = [];
125
+ collectRichTextTextNodes(doc, nodes);
126
+ return nodes;
127
+ }
128
+ export function writePreviewProvenanceCodedRichTextTextNodeAtPath(root, path, textNodeIndex, markerId, expectedText) {
129
+ if (typeof markerId !== 'string' || markerId.length === 0)
130
+ return false;
131
+ if (!Number.isSafeInteger(textNodeIndex) || textNodeIndex < 0)
132
+ return false;
133
+ if (root === null || root === undefined)
134
+ return false;
135
+ const direct = mutableValueAtPath(root, path);
136
+ const encoded = direct === null
137
+ ? null
138
+ : writePreviewProvenanceCodedRichTextTextNode(direct.value, textNodeIndex, markerId, expectedText);
139
+ if (encoded !== null) {
140
+ direct?.set(encoded);
141
+ return true;
142
+ }
143
+ return writePreviewProvenanceCodedRichTextEmbedDataAtPath(root, path, (value) => writePreviewProvenanceCodedRichTextTextNode(value, textNodeIndex, markerId, expectedText));
144
+ }
145
+ function parseCanonicalPath(path) {
146
+ const segments = [];
147
+ for (const part of path.split('.')) {
148
+ if (part.length === 0)
149
+ return [];
150
+ const match = /^([A-Za-z_$][\w$]*)(\[\d+\])*$/.exec(part);
151
+ if (!match)
152
+ return [];
153
+ segments.push(match[1]);
154
+ const indexes = part.match(/\[(\d+)\]/g) ?? [];
155
+ for (const rawIndex of indexes) {
156
+ const value = Number(rawIndex.slice(1, -1));
157
+ if (!Number.isSafeInteger(value) || value < 0)
158
+ return [];
159
+ segments.push(value);
160
+ }
161
+ }
162
+ return segments;
163
+ }
164
+ function readPathPart(value, part) {
165
+ if ((typeof part !== 'string' && typeof part !== 'number') ||
166
+ value === null ||
167
+ typeof value !== 'object') {
168
+ return undefined;
169
+ }
170
+ return value[part];
171
+ }
172
+ function mutableValueAtPath(root, path) {
173
+ if (path.length === 0)
174
+ return null;
175
+ let cursor = root;
176
+ for (let index = 0; index < path.length - 1; index++) {
177
+ cursor = readPathPart(cursor, path[index]);
178
+ if (cursor === null || cursor === undefined)
179
+ return null;
180
+ }
181
+ const leaf = path[path.length - 1];
182
+ if ((typeof leaf !== 'string' && typeof leaf !== 'number') ||
183
+ cursor === null ||
184
+ typeof cursor !== 'object') {
185
+ return null;
186
+ }
187
+ const holder = cursor;
188
+ return {
189
+ value: holder[leaf],
190
+ set: (value) => {
191
+ holder[leaf] = value;
192
+ },
193
+ };
194
+ }
195
+ function writePreviewProvenanceCodedRichTextEmbedDataAtPath(root, path, writeValue) {
196
+ for (let prefixLength = path.length - 1; prefixLength >= 1; prefixLength--) {
197
+ const richTextSlot = mutableValueAtPath(root, path.slice(0, prefixLength));
198
+ if (richTextSlot === null)
199
+ continue;
200
+ const encoded = writePreviewProvenanceCodedRichTextEmbedData(richTextSlot.value, path.slice(prefixLength), writeValue);
201
+ if (encoded === null)
202
+ continue;
203
+ richTextSlot.set(encoded);
204
+ return true;
205
+ }
206
+ return false;
207
+ }
208
+ function writePreviewProvenanceCodedRichTextEmbedData(content, path, writeValue) {
209
+ if (path.length < 2 ||
210
+ typeof path[0] !== 'number' ||
211
+ (typeof path[1] !== 'string' && typeof path[1] !== 'number')) {
212
+ return null;
213
+ }
214
+ const doc = parseRichTextDocument(content);
215
+ if (doc === null)
216
+ return null;
217
+ const embeds = [];
218
+ collectRichTextSliceEmbedRecords(doc, embeds);
219
+ const embed = embeds[path[0]];
220
+ if (!embed)
221
+ return null;
222
+ const attrs = asRecord(embed.attrs);
223
+ const data = asRecord(attrs?.data);
224
+ if (data === null)
225
+ return null;
226
+ const slot = mutableValueAtPath(data, [path[1]]);
227
+ if (slot === null)
228
+ return null;
229
+ const written = path.length === 2
230
+ ? writeValue(slot.value)
231
+ : writePreviewProvenanceCodedRichTextEmbedData(slot.value, path.slice(2), writeValue);
232
+ if (written === null)
233
+ return null;
234
+ slot.set(written);
235
+ return JSON.stringify(doc);
236
+ }
237
+ function writePreviewProvenanceCodedRichTextTextNode(content, textNodeIndex, markerId, expectedText) {
238
+ const doc = parseRichTextDocument(content);
239
+ if (doc === null)
240
+ return null;
241
+ const nodes = [];
242
+ collectRichTextTextNodeRecords(doc, nodes);
243
+ const target = nodes[textNodeIndex];
244
+ if (!target || target.text !== expectedText)
245
+ return null;
246
+ target.text = encodePreviewProvenanceText(markerId, expectedText);
247
+ return JSON.stringify(doc);
248
+ }
249
+ function parseRichTextDocument(content) {
250
+ let doc = content;
251
+ if (typeof content === 'string') {
252
+ try {
253
+ doc = JSON.parse(content);
254
+ }
255
+ catch {
256
+ return null;
257
+ }
258
+ }
259
+ if (doc === null || typeof doc !== 'object')
260
+ return null;
261
+ return doc;
262
+ }
263
+ function collectRichTextTextNodes(value, out) {
264
+ if (value === null || typeof value !== 'object')
265
+ return;
266
+ const node = value;
267
+ if (node.type === 'text' && typeof node.text === 'string') {
268
+ out.push(node.text);
269
+ }
270
+ if (!Array.isArray(node.content))
271
+ return;
272
+ for (const child of node.content)
273
+ collectRichTextTextNodes(child, out);
274
+ }
275
+ function collectRichTextTextNodeRecords(value, out) {
276
+ if (value === null || typeof value !== 'object')
277
+ return;
278
+ const node = value;
279
+ if (node.type === 'text' && typeof node.text === 'string') {
280
+ out.push(node);
281
+ }
282
+ if (!Array.isArray(node.content))
283
+ return;
284
+ for (const child of node.content)
285
+ collectRichTextTextNodeRecords(child, out);
286
+ }
287
+ function collectRichTextSliceEmbedRecords(value, out) {
288
+ if (value === null || typeof value !== 'object')
289
+ return;
290
+ const node = value;
291
+ if (node.type === 'sliceEmbed')
292
+ out.push(node);
293
+ if (!Array.isArray(node.content))
294
+ return;
295
+ for (const child of node.content)
296
+ collectRichTextSliceEmbedRecords(child, out);
297
+ }
298
+ function asRecord(value) {
299
+ return value !== null && typeof value === 'object' && !Array.isArray(value)
300
+ ? value
301
+ : null;
302
+ }
@@ -0,0 +1,29 @@
1
+ import { type PreviewReadScopeEcho } from '@storystick/client/internal';
2
+ import type { PathAnchor, ProvenanceId, SourceSet } from './types.js';
3
+ /**
4
+ * Public, render-bound address of the single editorial source behind a marker.
5
+ *
6
+ * The locator deliberately contains no presentation identity. The frame and
7
+ * the concrete anchor own that occurrence, while this value owns only the
8
+ * Studio edit target. Keeping those identities separate is what allows one
9
+ * model field to appear several times, and allows cross-document clicks to
10
+ * retain the iframe that produced them.
11
+ */
12
+ export interface DirectEditLocator {
13
+ readonly version: 1;
14
+ readonly sourceSetId: string;
15
+ readonly documentId: string;
16
+ readonly variantId?: string;
17
+ readonly canonicalPath: string;
18
+ readonly pathAnchors: readonly PathAnchor[];
19
+ readonly navigationPath?: string;
20
+ readonly navigationAnchors?: readonly PathAnchor[];
21
+ readonly textNodeIndex?: number;
22
+ readonly sourceKind: ProvenanceId['sourceKind'];
23
+ /** Scope attested by the concrete owner read, when available. */
24
+ readonly readScope?: PreviewReadScopeEcho;
25
+ }
26
+ export declare function directEditLocatorForSourceSet(sourceSet: SourceSet | null | undefined): DirectEditLocator | null;
27
+ export declare function directEditLocatorForSource(sourceSetId: string, source: ProvenanceId): DirectEditLocator | null;
28
+ export declare function normalizeDirectEditLocator(value: unknown): DirectEditLocator | null;
29
+ export declare function normalizePreviewReadScopeEcho(value: unknown): PreviewReadScopeEcho | null;