@beyondwork/docx-react-component 1.0.67 → 1.0.70

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 (384) hide show
  1. package/README.md +75 -932
  2. package/package.json +26 -27
  3. package/src/api/anchor-conversion.ts +43 -0
  4. package/src/api/editor-state-types.ts +2 -1
  5. package/src/api/public-types.ts +504 -101
  6. package/src/api/session-state.ts +4 -0
  7. package/src/api/v3/README.md +91 -0
  8. package/src/api/v3/_create.ts +146 -0
  9. package/src/api/v3/_layer-metadata.ts +362 -0
  10. package/src/api/v3/_mocks.ts +84 -0
  11. package/src/api/v3/_runtime-handle.ts +162 -0
  12. package/src/api/v3/_ux-response.ts +73 -0
  13. package/src/api/v3/ai/_metadata-audit.ts +225 -0
  14. package/src/api/v3/ai/attach.ts +235 -0
  15. package/src/api/v3/ai/bundle.ts +132 -0
  16. package/src/api/v3/ai/explain.ts +144 -0
  17. package/src/api/v3/ai/export.ts +54 -0
  18. package/src/api/v3/ai/inspect.ts +118 -0
  19. package/src/api/v3/ai/policy.ts +77 -0
  20. package/src/api/v3/ai/replacement.ts +341 -0
  21. package/src/api/v3/ai/resolve.ts +133 -0
  22. package/src/api/v3/index.ts +79 -0
  23. package/src/api/v3/runtime/chart.ts +310 -0
  24. package/src/api/v3/runtime/clipboard.ts +81 -0
  25. package/src/api/v3/runtime/collab.ts +331 -0
  26. package/src/api/v3/runtime/content.ts +236 -0
  27. package/src/api/v3/runtime/document.ts +282 -0
  28. package/src/api/v3/runtime/formatting.ts +186 -0
  29. package/src/api/v3/runtime/geometry.ts +349 -0
  30. package/src/api/v3/runtime/layout.ts +108 -0
  31. package/src/api/v3/runtime/review.ts +129 -0
  32. package/src/api/v3/runtime/search.ts +74 -0
  33. package/src/api/v3/runtime/table.ts +63 -0
  34. package/src/api/v3/runtime/workflow.ts +434 -0
  35. package/src/api/v3/ui/_context.ts +86 -0
  36. package/src/api/v3/ui/_create.ts +65 -0
  37. package/src/api/v3/ui/_types.ts +520 -0
  38. package/src/api/v3/ui/chrome-composition.ts +342 -0
  39. package/src/{ui-tailwind/chrome → api/v3/ui}/chrome-preset-model.ts +11 -1
  40. package/src/api/v3/ui/chrome.ts +476 -0
  41. package/src/api/v3/ui/debug.ts +124 -0
  42. package/src/api/v3/ui/index.ts +64 -0
  43. package/src/api/v3/ui/overlays-visibility.ts +170 -0
  44. package/src/api/v3/ui/overlays.ts +427 -0
  45. package/src/api/v3/ui/scope.ts +71 -0
  46. package/src/api/v3/ui/session.ts +100 -0
  47. package/src/api/v3/ui/surface.ts +170 -0
  48. package/src/api/v3/ui/viewport.ts +303 -0
  49. package/src/core/commands/index.ts +28 -6
  50. package/src/core/commands/list-commands.ts +3 -2
  51. package/src/core/commands/section-layout-commands.ts +9 -8
  52. package/src/core/schema/text-schema.ts +16 -0
  53. package/src/core/selection/mapping.ts +33 -72
  54. package/src/core/state/editor-state.ts +96 -189
  55. package/src/index.ts +23 -4
  56. package/src/io/chart-preview-resolver.ts +1 -1
  57. package/src/io/docx-session.ts +36 -4797
  58. package/src/io/export/build-app-properties-xml.ts +1 -1
  59. package/src/io/export/serialize-comments.ts +1 -1
  60. package/src/io/export/serialize-headers-footers.ts +6 -1
  61. package/src/io/export/serialize-main-document.ts +45 -0
  62. package/src/io/export/serialize-run-formatting.ts +17 -2
  63. package/src/io/export/twip.ts +1 -1
  64. package/src/io/normalize/normalize-text.ts +27 -20
  65. package/src/io/ooxml/chart/parse-series.ts +1 -1
  66. package/src/io/ooxml/chart/resolve-color.ts +2 -2
  67. package/src/io/ooxml/chart/types.ts +1 -1
  68. package/src/io/ooxml/classify-embedding.ts +83 -33
  69. package/src/io/ooxml/parse-fill.ts +1 -1
  70. package/src/io/ooxml/parse-main-document.ts +71 -1
  71. package/src/io/ooxml/parse-object.ts +14 -10
  72. package/src/io/ooxml/parse-run-formatting.ts +47 -1
  73. package/src/io/ooxml/property-grab-bag.ts +2 -2
  74. package/src/io/ooxml/units.ts +11 -0
  75. package/src/io/ooxml/workflow-payload.ts +282 -7
  76. package/src/model/anchor.ts +85 -0
  77. package/src/model/canonical-document.ts +351 -15
  78. package/src/model/chart-types.ts +1 -1
  79. package/src/model/layout/index.ts +83 -0
  80. package/src/model/layout/page-graph-types.ts +181 -0
  81. package/src/model/layout/page-layout-snapshot.ts +105 -0
  82. package/src/model/layout/resolved-layout-types.ts +47 -0
  83. package/src/model/layout/runtime-page-graph-types.ts +102 -0
  84. package/src/model/paragraph-scope-ids.ts +72 -0
  85. package/src/model/review/comment-types.ts +112 -0
  86. package/src/model/review/index.ts +2 -0
  87. package/src/model/review/revision-types.ts +215 -0
  88. package/src/model/snapshot.ts +32 -0
  89. package/src/review/store/comment-store.ts +21 -47
  90. package/src/review/store/revision-types.ts +40 -198
  91. package/src/runtime/collab/base-doc-fingerprint.ts +6 -1
  92. package/src/runtime/collab/runtime-collab-sync.ts +13 -3
  93. package/src/runtime/collab-session.ts +1 -1
  94. package/src/runtime/debug/build-debug-inspector-snapshot.ts +686 -0
  95. package/src/runtime/debug/event-ring-buffer.ts +64 -0
  96. package/src/runtime/debug/probability-sampler.ts +18 -0
  97. package/src/runtime/debug/runtime-debug-facet.ts +67 -0
  98. package/src/runtime/debug/stage-tokens.ts +31 -0
  99. package/src/runtime/debug/telemetry-bus.ts +271 -0
  100. package/src/runtime/debug/types.ts +275 -0
  101. package/src/runtime/debug/wrap-ref-for-telemetry.ts +118 -0
  102. package/src/runtime/document-layout.ts +8 -6
  103. package/src/runtime/document-runtime.ts +843 -1141
  104. package/src/runtime/document-search.ts +1 -1
  105. package/src/runtime/edit-ops/index.ts +1 -1
  106. package/src/runtime/external-send-runtime.ts +1 -1
  107. package/src/runtime/formatting/document-lookup.ts +235 -0
  108. package/src/runtime/formatting/field/registry.ts +41 -0
  109. package/src/runtime/{field-resolver.ts → formatting/field/resolver.ts} +27 -2
  110. package/src/runtime/formatting/font-resolution.ts +83 -0
  111. package/src/runtime/formatting/formatting-context.ts +903 -0
  112. package/src/runtime/formatting/formatting-types.ts +157 -0
  113. package/src/runtime/{hyperlink-color-resolver.ts → formatting/hyperlink-color.ts} +2 -2
  114. package/src/runtime/formatting/index.ts +125 -0
  115. package/src/runtime/{resolved-numbering-geometry.ts → formatting/numbering/geometry.ts} +1 -1
  116. package/src/runtime/{numbering-prefix.ts → formatting/numbering/prefix.ts} +170 -3
  117. package/src/runtime/formatting/paragraph-style-resolver.ts +92 -0
  118. package/src/runtime/formatting/projector.ts +75 -0
  119. package/src/runtime/formatting/resolve-effective.ts +407 -0
  120. package/src/runtime/formatting/revision-display.ts +105 -0
  121. package/src/runtime/{paragraph-style-resolver.ts → formatting/style-cascade.ts} +84 -141
  122. package/src/runtime/{table-style-resolver.ts → formatting/table-style-resolver.ts} +1 -1
  123. package/src/runtime/formatting/telemetry-bridge.ts +106 -0
  124. package/src/runtime/{theme-color-resolver.ts → formatting/theme-color.ts} +2 -30
  125. package/src/runtime/geometry/caret-geometry.ts +164 -0
  126. package/src/runtime/geometry/geometry-facet.ts +364 -0
  127. package/src/runtime/geometry/geometry-types.ts +256 -0
  128. package/src/runtime/geometry/hit-test.ts +125 -0
  129. package/src/runtime/geometry/index.ts +71 -0
  130. package/src/runtime/geometry/inert-geometry-facet.ts +43 -0
  131. package/src/runtime/geometry/invalidation.ts +35 -0
  132. package/src/runtime/geometry/object-handles.ts +77 -0
  133. package/src/runtime/geometry/overlay-rects.ts +85 -0
  134. package/src/runtime/geometry/project-anchors.ts +100 -0
  135. package/src/runtime/geometry/project-fragments.ts +216 -0
  136. package/src/runtime/geometry/projector.ts +129 -0
  137. package/src/runtime/geometry/replacement-envelope.ts +130 -0
  138. package/src/runtime/geometry/viewport.ts +218 -0
  139. package/src/runtime/layout/compat-input-ledger.ts +211 -0
  140. package/src/runtime/layout/index.ts +6 -1
  141. package/src/runtime/layout/inert-layout-facet.ts +12 -7
  142. package/src/runtime/layout/layout-engine-instance.ts +189 -11
  143. package/src/runtime/layout/layout-engine-version.ts +450 -1
  144. package/src/runtime/layout/layout-facet-types.ts +60 -0
  145. package/src/runtime/layout/layout-measurement-provider.ts +13 -0
  146. package/src/runtime/layout/measurement-backend-canvas.ts +14 -2
  147. package/src/runtime/layout/measurement-backend-empirical.ts +23 -4
  148. package/src/runtime/layout/page-graph.ts +62 -209
  149. package/src/runtime/layout/page-story-resolver.ts +7 -12
  150. package/src/runtime/layout/paginated-layout-engine.ts +186 -11
  151. package/src/runtime/layout/project-block-fragments.ts +11 -0
  152. package/src/runtime/layout/projector.ts +90 -0
  153. package/src/runtime/layout/public-facet.ts +187 -442
  154. package/src/runtime/layout/resolved-formatting-state.ts +158 -26
  155. package/src/runtime/layout/table-render-plan.ts +1 -1
  156. package/src/runtime/prerender/cache-envelope.ts +6 -1
  157. package/src/runtime/prerender/prerender-document.ts +18 -23
  158. package/src/runtime/render/decoration-resolver.ts +1 -1
  159. package/src/runtime/render/render-frame-types.ts +20 -0
  160. package/src/runtime/render/render-kernel.ts +94 -25
  161. package/src/runtime/scopes/_formatting-seam.ts +262 -0
  162. package/src/runtime/scopes/_scope-dependencies.ts +49 -0
  163. package/src/runtime/scopes/action-validation.ts +356 -0
  164. package/src/runtime/scopes/attach-explanation.ts +102 -0
  165. package/src/runtime/scopes/audit-bundle.ts +71 -0
  166. package/src/runtime/scopes/compile-scope-bundle.ts +163 -0
  167. package/src/runtime/scopes/compile-scope.ts +262 -0
  168. package/src/runtime/scopes/compiler-service.ts +431 -0
  169. package/src/runtime/scopes/create-issue.ts +107 -0
  170. package/src/runtime/scopes/enumerate-scopes.ts +543 -0
  171. package/src/runtime/scopes/evidence.ts +233 -0
  172. package/src/runtime/scopes/index.ts +150 -0
  173. package/src/runtime/scopes/position-map.ts +214 -0
  174. package/src/runtime/scopes/preservation-boundary.ts +91 -0
  175. package/src/runtime/scopes/projector.ts +49 -0
  176. package/src/runtime/scopes/replaceability.ts +87 -0
  177. package/src/runtime/scopes/replacement/apply.ts +228 -0
  178. package/src/runtime/scopes/replacement/compile.ts +59 -0
  179. package/src/runtime/scopes/replacement/propose.ts +42 -0
  180. package/src/runtime/scopes/resolve-reference.ts +347 -0
  181. package/src/runtime/scopes/review-bundle.ts +141 -0
  182. package/src/runtime/scopes/scope-kinds/_paragraph-text.ts +57 -0
  183. package/src/runtime/scopes/scope-kinds/_table-text.ts +42 -0
  184. package/src/runtime/scopes/scope-kinds/comment-thread.ts +59 -0
  185. package/src/runtime/scopes/scope-kinds/field.ts +65 -0
  186. package/src/runtime/scopes/scope-kinds/heading.ts +84 -0
  187. package/src/runtime/scopes/scope-kinds/list-item.ts +77 -0
  188. package/src/runtime/scopes/scope-kinds/paragraph.ts +182 -0
  189. package/src/runtime/scopes/scope-kinds/revision.ts +62 -0
  190. package/src/runtime/scopes/scope-kinds/table-cell.ts +57 -0
  191. package/src/runtime/scopes/scope-kinds/table-row.ts +61 -0
  192. package/src/runtime/scopes/scope-kinds/table.ts +55 -0
  193. package/src/runtime/scopes/scope-range.ts +208 -0
  194. package/src/runtime/scopes/semantic-scope-types.ts +454 -0
  195. package/src/runtime/scopes/workflow-overlap.ts +92 -0
  196. package/src/runtime/selection/index.ts +1 -1
  197. package/src/runtime/structure-ops/fragment-insert.ts +1 -1
  198. package/src/runtime/structure-ops/index.ts +1 -1
  199. package/src/runtime/surface-projection.ts +232 -262
  200. package/src/runtime/units.ts +4 -2
  201. package/src/runtime/workflow/coordinator.ts +1348 -0
  202. package/src/runtime/workflow/derived-scope-resolver.ts +125 -0
  203. package/src/runtime/workflow/index.ts +25 -0
  204. package/src/runtime/workflow/markup-mode-policy.ts +98 -0
  205. package/src/runtime/{workflow-markup.ts → workflow/markup.ts} +6 -6
  206. package/src/runtime/workflow/metadata-persistence.ts +306 -0
  207. package/src/runtime/workflow/metadata-writer.ts +123 -0
  208. package/src/runtime/workflow/overlay-store.ts +690 -0
  209. package/src/runtime/workflow/projector.ts +127 -0
  210. package/src/runtime/{query-scopes.ts → workflow/query-scopes.ts} +3 -3
  211. package/src/runtime/{workflow-rail-segments.ts → workflow/rail/compose.ts} +60 -165
  212. package/src/runtime/workflow/rail/types.ts +198 -0
  213. package/src/runtime/workflow/scope-rail-composer.ts +39 -0
  214. package/src/runtime/{scope-resolver.ts → workflow/scope-resolver.ts} +3 -3
  215. package/src/runtime/workflow/scope-writer.ts +188 -0
  216. package/src/runtime/{tamper-gate.ts → workflow/tamper-gate.ts} +1 -1
  217. package/src/runtime/workflow/visibility-policy.ts +129 -0
  218. package/src/session/_sync-legacy.ts +66 -0
  219. package/src/session/export/embedded-reconstitute.ts +104 -0
  220. package/src/session/export/export-diagnostics.ts +85 -0
  221. package/src/session/export/export-validation.ts +110 -0
  222. package/src/session/export/index.ts +34 -0
  223. package/src/session/export/preservation-reattach.ts +30 -0
  224. package/src/session/export/serialize-dispatch.ts +165 -0
  225. package/src/session/export/stateful-export-pipeline.ts +432 -0
  226. package/src/session/export/stateful-export.ts +684 -0
  227. package/src/session/import/canonical-assembly.ts +227 -0
  228. package/src/session/import/diagnostics-session.ts +54 -0
  229. package/src/session/import/embedded-discovery.ts +225 -0
  230. package/src/session/import/embedded-offload.ts +337 -0
  231. package/src/session/import/import-diagnostics.ts +69 -0
  232. package/src/session/import/loader-types.ts +313 -0
  233. package/src/session/import/loader.ts +1834 -0
  234. package/src/session/import/normalize.ts +195 -0
  235. package/src/session/import/package-parts.ts +217 -0
  236. package/src/session/import/package-read.ts +195 -0
  237. package/src/session/import/parse-orchestration.ts +105 -0
  238. package/src/session/import/part-constants.ts +70 -0
  239. package/src/session/import/part-discovery.ts +94 -0
  240. package/src/session/import/preservation-index.ts +46 -0
  241. package/src/{runtime/read-only-diagnostics-runtime.ts → session/import/read-only-diagnostics.ts} +24 -3
  242. package/src/session/import/review-import.ts +508 -0
  243. package/src/session/import/styles-consolidation.ts +281 -0
  244. package/src/session/import/workflow-scope-import.ts +256 -0
  245. package/src/session/index.ts +37 -0
  246. package/src/session/session-state.ts +69 -0
  247. package/src/session/session.ts +532 -0
  248. package/src/session/shared/protection.ts +228 -0
  249. package/src/session/shared/session-utils.ts +82 -0
  250. package/src/session/types.ts +499 -0
  251. package/src/shell/chart-snapshots.ts +96 -0
  252. package/src/shell/media-previews.ts +85 -0
  253. package/src/shell/overlay-anchor-bridge.ts +53 -0
  254. package/src/shell/paste-adapter.ts +23 -0
  255. package/src/shell/ref-commands.ts +1697 -0
  256. package/src/shell/ref-utilities.ts +48 -0
  257. package/src/shell/search.ts +51 -0
  258. package/src/{ui/editor-runtime-boundary.ts → shell/session-bootstrap.ts} +243 -67
  259. package/src/shell/ui-subscriber-channels.ts +81 -0
  260. package/src/shell/use-collab-sync.ts +116 -0
  261. package/src/ui/WordReviewEditor.tsx +496 -2051
  262. package/src/ui/editor-shell-view.tsx +30 -1
  263. package/src/ui/editor-surface-controller.tsx +49 -1
  264. package/src/ui/headless/revision-decoration-model.ts +83 -0
  265. package/src/{ui-tailwind/chrome → ui/headless}/role-action-sets.ts +1 -1
  266. package/src/ui/headless/scoped-chrome-policy.ts +2 -2
  267. package/src/ui/headless/selection-tool-context.ts +1 -1
  268. package/src/ui/headless/selection-tool-resolver.ts +1 -1
  269. package/src/ui/runtime-shortcut-dispatch.ts +46 -1
  270. package/src/ui/ui-controller-factory.ts +221 -0
  271. package/src/ui-tailwind/chart/ChartSurface.tsx +2 -2
  272. package/src/ui-tailwind/chart/layout/legend-layout.ts +1 -1
  273. package/src/ui-tailwind/chart/layout/plot-area.ts +2 -2
  274. package/src/ui-tailwind/chart/layout/title-layout.ts +1 -1
  275. package/src/ui-tailwind/chart/render/area.tsx +3 -3
  276. package/src/ui-tailwind/chart/render/bar-column.tsx +3 -3
  277. package/src/ui-tailwind/chart/render/bubble.tsx +3 -3
  278. package/src/ui-tailwind/chart/render/combo.tsx +2 -2
  279. package/src/ui-tailwind/chart/render/data-labels.tsx +2 -2
  280. package/src/ui-tailwind/chart/render/font-metrics.ts +2 -2
  281. package/src/ui-tailwind/chart/render/line.tsx +3 -3
  282. package/src/ui-tailwind/chart/render/pie.tsx +6 -6
  283. package/src/ui-tailwind/chart/render/scatter.tsx +3 -3
  284. package/src/ui-tailwind/chart/render/svg-primitives.ts +3 -3
  285. package/src/ui-tailwind/chart/render/unsupported.tsx +2 -2
  286. package/src/ui-tailwind/chrome/build-context-menu-entries.ts +88 -0
  287. package/src/ui-tailwind/chrome/chrome-preset-toolbar.tsx +1 -1
  288. package/src/ui-tailwind/chrome/collab-send-to-supplier-button.tsx +1 -1
  289. package/src/ui-tailwind/chrome/collab-tamper-banner.tsx +1 -1
  290. package/src/ui-tailwind/chrome/collab-top-nav-container.tsx +1 -1
  291. package/src/ui-tailwind/chrome/editor-action-registry.ts +553 -0
  292. package/src/ui-tailwind/chrome/editor-actions-to-palette.ts +182 -0
  293. package/src/ui-tailwind/chrome/local-surface-arbiter.ts +534 -0
  294. package/src/ui-tailwind/chrome/resolve-target-kind.ts +226 -0
  295. package/src/ui-tailwind/chrome/tw-alert-banner.tsx +38 -4
  296. package/src/ui-tailwind/chrome/tw-context-band.tsx +125 -0
  297. package/src/ui-tailwind/chrome/tw-context-menu-portal.tsx +248 -0
  298. package/src/ui-tailwind/chrome/tw-image-context-toolbar.tsx +42 -1
  299. package/src/ui-tailwind/chrome/tw-selection-anchor-resolver.ts +8 -7
  300. package/src/ui-tailwind/chrome/tw-selection-tool-blocked.tsx +38 -4
  301. package/src/ui-tailwind/chrome/tw-selection-tool-comment.tsx +104 -6
  302. package/src/ui-tailwind/chrome/tw-selection-tool-host.tsx +66 -7
  303. package/src/ui-tailwind/chrome/tw-selection-tool-workflow.tsx +54 -8
  304. package/src/ui-tailwind/chrome/tw-shortcut-hint.tsx +7 -1
  305. package/src/ui-tailwind/chrome/tw-suggestion-card.tsx +33 -0
  306. package/src/ui-tailwind/chrome/tw-table-context-toolbar.tsx +78 -1
  307. package/src/ui-tailwind/chrome/tw-table-grip-layer.tsx +16 -8
  308. package/src/ui-tailwind/chrome/tw-workspace-chrome-host.tsx +276 -0
  309. package/src/ui-tailwind/chrome/use-context-menu-controller.ts +201 -0
  310. package/src/ui-tailwind/chrome-overlay/chrome-overlay-projector.ts +1 -1
  311. package/src/ui-tailwind/chrome-overlay/tw-chrome-overlay.tsx +22 -4
  312. package/src/ui-tailwind/chrome-overlay/tw-comment-balloon-layer.tsx +1 -1
  313. package/src/ui-tailwind/chrome-overlay/tw-locked-block-layer.tsx +1 -1
  314. package/src/ui-tailwind/chrome-overlay/tw-object-selection-overlay.tsx +11 -5
  315. package/src/ui-tailwind/chrome-overlay/tw-page-stack-overlay-layer.tsx +197 -3
  316. package/src/ui-tailwind/chrome-overlay/tw-revision-margin-bar-layer.tsx +1 -1
  317. package/src/ui-tailwind/chrome-overlay/tw-scope-card-layer.tsx +35 -6
  318. package/src/ui-tailwind/chrome-overlay/tw-scope-rail-layer.tsx +24 -16
  319. package/src/ui-tailwind/chrome-overlay/tw-table-continuation-header.tsx +1 -1
  320. package/src/ui-tailwind/debug/README.md +57 -0
  321. package/src/ui-tailwind/debug/index.ts +3 -0
  322. package/src/ui-tailwind/debug/tw-debug-overlay.tsx +186 -0
  323. package/src/ui-tailwind/debug/tw-debug-presentation.tsx +80 -0
  324. package/src/ui-tailwind/debug/tw-debug-top-bar.tsx +83 -0
  325. package/src/ui-tailwind/editor-surface/chart-node-view.tsx +2 -2
  326. package/src/ui-tailwind/editor-surface/float-wrap-resolver.ts +1 -1
  327. package/src/ui-tailwind/editor-surface/pm-command-bridge.ts +135 -10
  328. package/src/ui-tailwind/editor-surface/pm-decorations.ts +40 -13
  329. package/src/ui-tailwind/editor-surface/pm-page-break-decorations.ts +1 -1
  330. package/src/ui-tailwind/editor-surface/pm-schema.ts +1 -1
  331. package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +3 -3
  332. package/src/ui-tailwind/editor-surface/predicted-tag-preflight.ts +1 -1
  333. package/src/ui-tailwind/editor-surface/remote-cursor-plugin.ts +2 -2
  334. package/src/ui-tailwind/editor-surface/scroll-anchor.ts +91 -9
  335. package/src/ui-tailwind/editor-surface/shape-renderer.ts +1 -1
  336. package/src/ui-tailwind/editor-surface/surface-layer.ts +1 -1
  337. package/src/ui-tailwind/editor-surface/tw-opaque-block.tsx +1 -1
  338. package/src/ui-tailwind/editor-surface/tw-page-block-view.helpers.ts +23 -6
  339. package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +132 -22
  340. package/src/ui-tailwind/editor-surface/tw-table-node-view.tsx +1 -1
  341. package/src/ui-tailwind/index.ts +0 -5
  342. package/src/ui-tailwind/overlay-anchor-bridge-context.tsx +33 -0
  343. package/src/ui-tailwind/page-stack/floating-image-overlay-model.ts +66 -29
  344. package/src/ui-tailwind/page-stack/tw-floating-image-layer.tsx +25 -2
  345. package/src/ui-tailwind/review/comment-markdown-renderer.tsx +15 -0
  346. package/src/ui-tailwind/review/tw-review-rail.tsx +92 -4
  347. package/src/ui-tailwind/review/tw-workflow-tab.tsx +1 -1
  348. package/src/ui-tailwind/review-workspace/page-chrome.ts +210 -0
  349. package/src/ui-tailwind/review-workspace/page-shell-metrics.ts +101 -0
  350. package/src/ui-tailwind/review-workspace/paragraph-layout.ts +115 -0
  351. package/src/ui-tailwind/review-workspace/selection-toolbar-placement.ts +97 -0
  352. package/src/ui-tailwind/review-workspace/tw-review-workspace-navigator.tsx +130 -0
  353. package/src/ui-tailwind/review-workspace/tw-review-workspace-page-toolbar.tsx +240 -0
  354. package/src/ui-tailwind/review-workspace/tw-review-workspace-rail.tsx +59 -0
  355. package/src/ui-tailwind/review-workspace/types.ts +408 -0
  356. package/src/ui-tailwind/review-workspace/use-chrome-policy.ts +104 -0
  357. package/src/ui-tailwind/review-workspace/use-derived-view-state.ts +151 -0
  358. package/src/ui-tailwind/review-workspace/use-diagnostics-signal.ts +70 -0
  359. package/src/ui-tailwind/review-workspace/use-grabbed-segment-offsets.ts +40 -0
  360. package/src/ui-tailwind/review-workspace/use-layout-facet-render-signal.ts +55 -0
  361. package/src/ui-tailwind/review-workspace/use-page-markers.ts +130 -0
  362. package/src/ui-tailwind/review-workspace/use-pm-surface-capture.ts +60 -0
  363. package/src/ui-tailwind/review-workspace/use-review-rail-state.ts +63 -0
  364. package/src/ui-tailwind/review-workspace/use-scope-card-state.ts +170 -0
  365. package/src/ui-tailwind/review-workspace/use-scroll-root-capture.ts +28 -0
  366. package/src/ui-tailwind/review-workspace/use-selection-toolbar-placement.ts +113 -0
  367. package/src/ui-tailwind/review-workspace/use-shell-selection-anchor-bridge.ts +120 -0
  368. package/src/ui-tailwind/review-workspace/use-status-bar-page-facts.ts +55 -0
  369. package/src/ui-tailwind/review-workspace/use-viewport-dimensions.ts +43 -0
  370. package/src/ui-tailwind/review-workspace/use-workspace-arbiter.ts +25 -0
  371. package/src/ui-tailwind/review-workspace/use-workspace-composition.ts +86 -0
  372. package/src/ui-tailwind/review-workspace/use-workspace-side-effects.ts +150 -0
  373. package/src/ui-tailwind/theme/editor-theme.css +25 -0
  374. package/src/ui-tailwind/toolbar/tw-role-action-region.tsx +2 -2
  375. package/src/ui-tailwind/toolbar/tw-toolbar.tsx +61 -98
  376. package/src/ui-tailwind/tw-review-workspace.tsx +521 -1802
  377. package/src/ui-tailwind/ui-api-context.tsx +43 -0
  378. package/src/ui-tailwind/ui-shell-channels-context.tsx +49 -0
  379. package/src/validation/compatibility-engine.ts +6 -6
  380. package/src/runtime/styles-cascade.ts +0 -33
  381. package/src/ui-tailwind/chrome/tw-mode-dock.tsx +0 -85
  382. /package/src/runtime/{page-number-format.ts → formatting/field/page-number-format.ts} +0 -0
  383. /package/src/runtime/{ai-action-policy.ts → workflow/ai-action-policy.ts} +0 -0
  384. /package/src/runtime/{scope-tag-registry.ts → workflow/scope-tag-registry.ts} +0 -0
@@ -0,0 +1,347 @@
1
+ /**
2
+ * Slice 3 — reference resolution.
3
+ *
4
+ * Takes an agent-supplied `ScopeReference` hint + a live document and
5
+ * returns either a resolved `ScopeHandle`, a list of candidate handles
6
+ * (ambiguous), a detached placeholder (scopeId was once valid), or a
7
+ * `not-found`.
8
+ *
9
+ * Deterministic hint kinds — `scope-id`, `semantic-path`, `offset`,
10
+ * `range` — all resolve live today. Natural-language hints return a
11
+ * `partial` result with `confidence: "low"`; a richer NL matcher lives
12
+ * in a later slice (the AI API will keep the function at status
13
+ * `partial` until then).
14
+ */
15
+
16
+ import type { CanonicalDocument } from "../../model/canonical-document.ts";
17
+ import type { CanonicalDocumentEnvelope } from "../../core/state/editor-state.ts";
18
+ import type { WorkflowOverlay } from "./_scope-dependencies.ts";
19
+
20
+ import type { EnumeratedScope } from "./enumerate-scopes.ts";
21
+ import { enumerateScopes } from "./enumerate-scopes.ts";
22
+ import { buildScopePositionMap, type ScopePositionRange } from "./position-map.ts";
23
+ import { resolveScopeRange, scopeSpecificity } from "./scope-range.ts";
24
+ import type { ScopeHandle } from "./semantic-scope-types.ts";
25
+
26
+ export type ScopeReference =
27
+ | { readonly kind: "scope-id"; readonly value: string }
28
+ | { readonly kind: "semantic-path"; readonly path: readonly string[] }
29
+ | { readonly kind: "offset"; readonly at: number }
30
+ | { readonly kind: "range"; readonly from: number; readonly to: number }
31
+ | { readonly kind: "natural-language"; readonly hint: string };
32
+
33
+ export type ResolveReferenceResult =
34
+ | {
35
+ readonly status: "resolved";
36
+ readonly handle: ScopeHandle;
37
+ readonly confidence: "high" | "medium" | "low";
38
+ }
39
+ | {
40
+ readonly status: "ambiguous";
41
+ readonly candidates: readonly ScopeHandle[];
42
+ }
43
+ | {
44
+ readonly status: "detached";
45
+ readonly scopeId: string;
46
+ readonly lastKnownRange?: ScopePositionRange;
47
+ readonly reason: string;
48
+ }
49
+ | {
50
+ readonly status: "not-found";
51
+ readonly reason: string;
52
+ };
53
+
54
+ export interface ResolveReferenceInputs {
55
+ readonly document: Pick<CanonicalDocument, "content" | "docId" | "review"> | CanonicalDocumentEnvelope;
56
+ readonly overlay?: WorkflowOverlay | null;
57
+ /** Pre-enumerated scopes; computed when absent. */
58
+ readonly scopes?: readonly EnumeratedScope[];
59
+ }
60
+
61
+ function scopesFor(inputs: ResolveReferenceInputs): readonly EnumeratedScope[] {
62
+ if (inputs.scopes) return inputs.scopes;
63
+ return enumerateScopes(inputs.document, { overlay: inputs.overlay ?? null });
64
+ }
65
+
66
+ function rangeForScope(
67
+ handle: ScopeHandle,
68
+ entry: EnumeratedScope,
69
+ positionMap: ReturnType<typeof buildScopePositionMap>,
70
+ ): ScopePositionRange | undefined {
71
+ const range = resolveScopeRange(entry, handle, positionMap);
72
+ return range ?? undefined;
73
+ }
74
+
75
+ function resolveByScopeId(
76
+ value: string,
77
+ scopes: readonly EnumeratedScope[],
78
+ overlay: WorkflowOverlay | null | undefined,
79
+ positionMap: ReturnType<typeof buildScopePositionMap>,
80
+ ): ResolveReferenceResult {
81
+ const match = scopes.find((s) => s.handle.scopeId === value);
82
+ if (match) {
83
+ return { status: "resolved", handle: match.handle, confidence: "high" };
84
+ }
85
+ // Overlay may know a detached scope not present in the canonical walk.
86
+ if (overlay?.scopes?.some((s) => s.scopeId === value)) {
87
+ const lastKnownRange = positionMap.markerScopes.get(value);
88
+ return {
89
+ status: "detached",
90
+ scopeId: value,
91
+ lastKnownRange,
92
+ reason: "scope-id known to overlay but absent from canonical walk",
93
+ };
94
+ }
95
+ return { status: "not-found", reason: `no scope with id "${value}"` };
96
+ }
97
+
98
+ function resolveBySemanticPath(
99
+ path: readonly string[],
100
+ scopes: readonly EnumeratedScope[],
101
+ ): ResolveReferenceResult {
102
+ const wanted = path.join("/");
103
+ const matches = scopes.filter(
104
+ (s) => s.handle.semanticPath.join("/") === wanted,
105
+ );
106
+ if (matches.length === 1) {
107
+ return { status: "resolved", handle: matches[0]!.handle, confidence: "high" };
108
+ }
109
+ if (matches.length > 1) {
110
+ return { status: "ambiguous", candidates: matches.map((m) => m.handle) };
111
+ }
112
+ return {
113
+ status: "not-found",
114
+ reason: `no scope at semantic-path "${wanted}"`,
115
+ };
116
+ }
117
+
118
+ function innermostContaining(
119
+ scopes: readonly EnumeratedScope[],
120
+ positionMap: ReturnType<typeof buildScopePositionMap>,
121
+ predicate: (range: ScopePositionRange) => boolean,
122
+ ): EnumeratedScope | null {
123
+ let best: {
124
+ entry: EnumeratedScope;
125
+ range: ScopePositionRange;
126
+ specificity: number;
127
+ } | null = null;
128
+ for (const entry of scopes) {
129
+ const range = rangeForScope(entry.handle, entry, positionMap);
130
+ if (!range) continue;
131
+ if (!predicate(range)) continue;
132
+ const specificity = scopeSpecificity(entry.kind);
133
+ if (!best) {
134
+ best = { entry, range, specificity };
135
+ continue;
136
+ }
137
+ const bestWidth = best.range.to - best.range.from;
138
+ const width = range.to - range.from;
139
+ // Precedence order:
140
+ // 1. Narrower range wins.
141
+ // 2. On width-tie, the more specific kind wins (field > paragraph,
142
+ // table-cell > table-row > table). This is the adversarial-
143
+ // closure fix — nested kinds sharing a canonical block range
144
+ // must still resolve to the deepest semantic unit.
145
+ // 3. On width + specificity tie, later start wins (deeper in the
146
+ // document walk — preserves the pre-fix tie-break for legacy
147
+ // cases not covered by specificity).
148
+ if (width < bestWidth) {
149
+ best = { entry, range, specificity };
150
+ } else if (width === bestWidth) {
151
+ if (specificity > best.specificity) {
152
+ best = { entry, range, specificity };
153
+ } else if (
154
+ specificity === best.specificity &&
155
+ range.from > best.range.from
156
+ ) {
157
+ best = { entry, range, specificity };
158
+ }
159
+ }
160
+ }
161
+ return best?.entry ?? null;
162
+ }
163
+
164
+ function resolveByOffset(
165
+ at: number,
166
+ scopes: readonly EnumeratedScope[],
167
+ positionMap: ReturnType<typeof buildScopePositionMap>,
168
+ ): ResolveReferenceResult {
169
+ const hit = innermostContaining(
170
+ scopes,
171
+ positionMap,
172
+ (range) => range.from <= at && at <= range.to,
173
+ );
174
+ if (!hit) {
175
+ return { status: "not-found", reason: `no scope contains offset ${at}` };
176
+ }
177
+ return {
178
+ status: "resolved",
179
+ handle: hit.handle,
180
+ confidence: hit.handle.provenance === "marker-backed" ? "high" : "medium",
181
+ };
182
+ }
183
+
184
+ function resolveByRange(
185
+ from: number,
186
+ to: number,
187
+ scopes: readonly EnumeratedScope[],
188
+ positionMap: ReturnType<typeof buildScopePositionMap>,
189
+ ): ResolveReferenceResult {
190
+ const low = Math.min(from, to);
191
+ const high = Math.max(from, to);
192
+ const hit = innermostContaining(
193
+ scopes,
194
+ positionMap,
195
+ (range) => range.from <= low && high <= range.to,
196
+ );
197
+ if (!hit) {
198
+ return {
199
+ status: "not-found",
200
+ reason: `no scope fully contains range [${low}, ${high}]`,
201
+ };
202
+ }
203
+ return {
204
+ status: "resolved",
205
+ handle: hit.handle,
206
+ confidence: hit.handle.provenance === "marker-backed" ? "high" : "medium",
207
+ };
208
+ }
209
+
210
+ /**
211
+ * Extract a short lowercased text snippet from an `EnumeratedScope`
212
+ * suitable for natural-language substring matching. Paragraph-like and
213
+ * table-like kinds expose the inline text directly; review-store kinds
214
+ * expose the comment body / revision summary. Capped at 200 characters so
215
+ * the linear scan stays bounded.
216
+ */
217
+ function extractNLHaystack(entry: EnumeratedScope): string {
218
+ switch (entry.kind) {
219
+ case "paragraph":
220
+ case "heading":
221
+ case "list-item": {
222
+ let out = "";
223
+ for (const child of entry.paragraph.children) {
224
+ if (out.length >= 200) break;
225
+ if (child.type === "text") out += child.text;
226
+ }
227
+ return out.slice(0, 200).toLowerCase();
228
+ }
229
+ case "field": {
230
+ let out = "";
231
+ for (const child of entry.field.children) {
232
+ if (out.length >= 200) break;
233
+ if (child.type === "text") out += child.text;
234
+ }
235
+ return out.slice(0, 200).toLowerCase();
236
+ }
237
+ case "table":
238
+ case "table-row":
239
+ case "table-cell":
240
+ // Tables carry structured content; skip their haystack to keep the
241
+ // linear scan cheap. Semantic-path matching still covers them.
242
+ return "";
243
+ case "comment-thread": {
244
+ const body = entry.thread.body ?? "";
245
+ return body.slice(0, 200).toLowerCase();
246
+ }
247
+ case "revision":
248
+ return `${entry.revision.kind} ${entry.revision.authorId ?? ""}`
249
+ .toLowerCase();
250
+ default: {
251
+ const _never: never = entry;
252
+ void _never;
253
+ return "";
254
+ }
255
+ }
256
+ }
257
+
258
+ /**
259
+ * Natural-language hint resolver (deliberately bounded).
260
+ *
261
+ * Contract (documented at `docs/architecture/09-ai-api.md` §A8): this
262
+ * path is **partial / low-confidence**. It substring-matches on:
263
+ * - `semanticPath.join("/")` — structural hints like
264
+ * "body/paragraph/3" or "review/comment-thread/..."
265
+ * - `scopeId` — direct id substring
266
+ * - `overlay.scopes[].label` — human-given scope labels (when the
267
+ * overlay is threaded)
268
+ * - a capped text excerpt from the scope's content (first 200 chars)
269
+ *
270
+ * Every returned `resolved` status carries `confidence: "low"` unchanged
271
+ * — callers must surface this to humans before committing a mutating
272
+ * action. Richer NL resolution (embedding-backed, domain-aware) is
273
+ * explicitly deferred; this matcher is a deterministic structural + text
274
+ * substring heuristic, not a semantic parser.
275
+ */
276
+ function resolveByNaturalLanguage(
277
+ hint: string,
278
+ scopes: readonly EnumeratedScope[],
279
+ overlay: WorkflowOverlay | null | undefined,
280
+ ): ResolveReferenceResult {
281
+ const needle = hint.trim().toLowerCase();
282
+ if (!needle) {
283
+ return { status: "not-found", reason: "empty natural-language hint" };
284
+ }
285
+ const overlayLabelByScopeId = new Map<string, string>();
286
+ if (overlay?.scopes) {
287
+ for (const s of overlay.scopes) {
288
+ if (s.label) overlayLabelByScopeId.set(s.scopeId, s.label.toLowerCase());
289
+ }
290
+ }
291
+ const matches: EnumeratedScope[] = [];
292
+ for (const entry of scopes) {
293
+ const path = entry.handle.semanticPath.join("/").toLowerCase();
294
+ if (path.includes(needle)) {
295
+ matches.push(entry);
296
+ continue;
297
+ }
298
+ if (entry.handle.scopeId.toLowerCase().includes(needle)) {
299
+ matches.push(entry);
300
+ continue;
301
+ }
302
+ const label = overlayLabelByScopeId.get(entry.handle.scopeId);
303
+ if (label && label.includes(needle)) {
304
+ matches.push(entry);
305
+ continue;
306
+ }
307
+ const haystack = extractNLHaystack(entry);
308
+ if (haystack && haystack.includes(needle)) {
309
+ matches.push(entry);
310
+ }
311
+ }
312
+ if (matches.length === 1) {
313
+ return { status: "resolved", handle: matches[0]!.handle, confidence: "low" };
314
+ }
315
+ if (matches.length > 1) {
316
+ return { status: "ambiguous", candidates: matches.slice(0, 8).map((m) => m.handle) };
317
+ }
318
+ return { status: "not-found", reason: `no scope matches hint "${hint}"` };
319
+ }
320
+
321
+ export function resolveReference(
322
+ reference: ScopeReference,
323
+ inputs: ResolveReferenceInputs,
324
+ ): ResolveReferenceResult {
325
+ const scopes = scopesFor(inputs);
326
+ const positionMap = buildScopePositionMap(inputs.document);
327
+ switch (reference.kind) {
328
+ case "scope-id":
329
+ return resolveByScopeId(reference.value, scopes, inputs.overlay, positionMap);
330
+ case "semantic-path":
331
+ return resolveBySemanticPath(reference.path, scopes);
332
+ case "offset":
333
+ return resolveByOffset(reference.at, scopes, positionMap);
334
+ case "range":
335
+ return resolveByRange(reference.from, reference.to, scopes, positionMap);
336
+ case "natural-language":
337
+ return resolveByNaturalLanguage(
338
+ reference.hint,
339
+ scopes,
340
+ inputs.overlay,
341
+ );
342
+ default: {
343
+ const never: never = reference;
344
+ return { status: "not-found", reason: `unsupported reference kind: ${String((never as { kind?: string }).kind)}` };
345
+ }
346
+ }
347
+ }
@@ -0,0 +1,141 @@
1
+ /**
2
+ * Slice 3 — per-scope review bundle.
3
+ *
4
+ * Enumerates comments + revisions (+ eventually issues + suggestions from
5
+ * the workflow overlay in Slice 6) that overlap one scope. Consumers:
6
+ * the CLM negotiation-review flow, the AI-API `getScopeBundle`, and the
7
+ * chrome-side review sidebar.
8
+ */
9
+
10
+ import type {
11
+ CanonicalDocument,
12
+ CommentThread,
13
+ RevisionRecord,
14
+ } from "../../model/canonical-document.ts";
15
+ import type { CanonicalDocumentEnvelope } from "../../core/state/editor-state.ts";
16
+ import type { WorkflowOverlay } from "./_scope-dependencies.ts";
17
+
18
+ import { buildScopePositionMap, type ScopePositionRange } from "./position-map.ts";
19
+ import type { EnumeratedScope } from "./enumerate-scopes.ts";
20
+ import { enumerateScopes } from "./enumerate-scopes.ts";
21
+ import { rangesOverlap, resolveScopeRange } from "./scope-range.ts";
22
+ import type { SemanticScope } from "./semantic-scope-types.ts";
23
+
24
+ export interface ReviewBundleCommentSummary {
25
+ readonly commentId: string;
26
+ readonly status: CommentThread["status"];
27
+ readonly author?: string;
28
+ readonly body?: string;
29
+ readonly entryCount: number;
30
+ }
31
+
32
+ export interface ReviewBundleRevisionSummary {
33
+ readonly changeId: string;
34
+ readonly kind: RevisionRecord["kind"];
35
+ readonly status: RevisionRecord["status"];
36
+ readonly author?: string;
37
+ readonly semanticKind?: string;
38
+ }
39
+
40
+ export interface ReviewBundle {
41
+ readonly scopeId: string;
42
+ readonly comments: readonly ReviewBundleCommentSummary[];
43
+ readonly revisions: readonly ReviewBundleRevisionSummary[];
44
+ readonly issueIds: readonly string[];
45
+ readonly suggestionIds: readonly string[];
46
+ }
47
+
48
+ export interface ComposeReviewBundleInputs {
49
+ readonly scope: SemanticScope;
50
+ readonly document: Pick<CanonicalDocument, "content" | "review"> | CanonicalDocumentEnvelope;
51
+ readonly overlay?: WorkflowOverlay | null;
52
+ readonly scopes?: readonly EnumeratedScope[];
53
+ }
54
+
55
+ function anchorToRange(
56
+ anchor: CommentThread["anchor"] | RevisionRecord["anchor"],
57
+ ): ScopePositionRange | null {
58
+ switch (anchor.kind) {
59
+ case "range":
60
+ return { from: anchor.range.from, to: anchor.range.to };
61
+ case "node":
62
+ return { from: anchor.at, to: anchor.at };
63
+ case "detached":
64
+ return anchor.lastKnownRange;
65
+ default:
66
+ return null;
67
+ }
68
+ }
69
+
70
+ function findEntryForScope(
71
+ scope: SemanticScope,
72
+ scopes: readonly EnumeratedScope[],
73
+ ): EnumeratedScope | null {
74
+ return scopes.find((entry) => entry.handle.scopeId === scope.handle.scopeId) ?? null;
75
+ }
76
+
77
+ export function composeReviewBundle(
78
+ inputs: ComposeReviewBundleInputs,
79
+ ): ReviewBundle {
80
+ const { scope, document, overlay } = inputs;
81
+ const scopes =
82
+ inputs.scopes ??
83
+ enumerateScopes(
84
+ document as Pick<CanonicalDocument, "content" | "docId" | "review">,
85
+ { overlay: overlay ?? null },
86
+ );
87
+ const positionMap = buildScopePositionMap(document);
88
+ const entry = findEntryForScope(scope, scopes);
89
+ const selfRange = entry
90
+ ? resolveScopeRange(entry, scope.handle, positionMap)
91
+ : null;
92
+
93
+ const comments: ReviewBundleCommentSummary[] = [];
94
+ const revisions: ReviewBundleRevisionSummary[] = [];
95
+
96
+ const review = (document as {
97
+ review?: {
98
+ comments?: Record<string, CommentThread>;
99
+ revisions?: Record<string, RevisionRecord>;
100
+ };
101
+ }).review;
102
+
103
+ if (selfRange && review) {
104
+ for (const thread of Object.values(review.comments ?? {})) {
105
+ const range = anchorToRange(thread.anchor);
106
+ if (!range || !rangesOverlap(selfRange, range)) continue;
107
+ comments.push({
108
+ commentId: thread.commentId,
109
+ status: thread.status,
110
+ author: thread.authorId ?? thread.createdBy,
111
+ body: thread.body,
112
+ entryCount: thread.entries?.length ?? 0,
113
+ });
114
+ }
115
+ for (const rev of Object.values(review.revisions ?? {})) {
116
+ const range = anchorToRange(rev.anchor);
117
+ if (!range || !rangesOverlap(selfRange, range)) continue;
118
+ revisions.push({
119
+ changeId: rev.changeId,
120
+ kind: rev.kind,
121
+ status: rev.status,
122
+ author: rev.authorId,
123
+ semanticKind: rev.metadata?.semanticKind,
124
+ });
125
+ }
126
+ }
127
+
128
+ comments.sort((a, b) => (a.commentId < b.commentId ? -1 : a.commentId > b.commentId ? 1 : 0));
129
+ revisions.sort((a, b) => (a.changeId < b.changeId ? -1 : a.changeId > b.changeId ? 1 : 0));
130
+
131
+ // Issues + suggestions live on the workflow overlay; Slice 6 lands them
132
+ // as first-class kinds. For Slice 3 we leave the arrays empty so the
133
+ // bundle shape stays stable for consumers.
134
+ return {
135
+ scopeId: scope.handle.scopeId,
136
+ comments,
137
+ revisions,
138
+ issueIds: [],
139
+ suggestionIds: [],
140
+ };
141
+ }
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Shared paragraph-text extractor used by paragraph/heading/list-item kind
3
+ * compilers. Excludes markers (bookmarks, scope markers) and non-text
4
+ * content. Deterministic: identical inputs produce identical output.
5
+ */
6
+
7
+ import type {
8
+ InlineNode,
9
+ ParagraphNode,
10
+ } from "../../../model/canonical-document.ts";
11
+
12
+ function inlineText(node: InlineNode): string {
13
+ switch (node.type) {
14
+ case "text":
15
+ return node.text;
16
+ case "hyperlink":
17
+ case "field":
18
+ return node.children
19
+ .map((child) => inlineText(child as InlineNode))
20
+ .join("");
21
+ case "tab":
22
+ return "\t";
23
+ case "hard_break":
24
+ case "column_break":
25
+ return "\n";
26
+ case "symbol":
27
+ return node.char ?? "";
28
+ default:
29
+ return "";
30
+ }
31
+ }
32
+
33
+ export function extractParagraphText(paragraph: ParagraphNode): string {
34
+ return paragraph.children
35
+ .map((child) => inlineText(child as InlineNode))
36
+ .join("");
37
+ }
38
+
39
+ const EXCERPT_MAX = 160;
40
+
41
+ /**
42
+ * Truncate to at most `EXCERPT_MAX` UTF-16 code units with an ellipsis. We
43
+ * iterate code points (spread) and rejoin to guarantee we never split a
44
+ * surrogate pair in half — naive `String.slice` can cut `"😀"` between its
45
+ * high + low surrogates and produce a malformed string.
46
+ */
47
+ export function buildExcerpt(text: string): string | undefined {
48
+ if (!text) return undefined;
49
+ if (text.length <= EXCERPT_MAX) return text;
50
+ const codePoints = [...text];
51
+ let acc = "";
52
+ for (const cp of codePoints) {
53
+ if (acc.length + cp.length > EXCERPT_MAX - 1) break;
54
+ acc += cp;
55
+ }
56
+ return `${acc}…`;
57
+ }
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Shared text extractor for table kinds. Concatenates paragraph text inside
3
+ * a cell (or across cells in a row / rows in a table). Skips non-paragraph
4
+ * block children (opaque blocks, section breaks, nested tables — later
5
+ * slices can recurse for nested table previews).
6
+ */
7
+
8
+ import type {
9
+ BlockNode,
10
+ TableCellNode,
11
+ TableNode,
12
+ TableRowNode,
13
+ } from "../../../model/canonical-document.ts";
14
+
15
+ import { extractParagraphText } from "./_paragraph-text.ts";
16
+
17
+ export function extractCellText(cell: TableCellNode): string {
18
+ const parts: string[] = [];
19
+ for (const child of cell.children as readonly BlockNode[]) {
20
+ if (child.type === "paragraph") {
21
+ const t = extractParagraphText(child);
22
+ if (t) parts.push(t);
23
+ }
24
+ }
25
+ return parts.join(" ");
26
+ }
27
+
28
+ export function extractRowText(row: TableRowNode): string {
29
+ return (row.cells as readonly TableCellNode[])
30
+ .map((cell) => extractCellText(cell))
31
+ .filter((t) => t.length > 0)
32
+ .join(" | ");
33
+ }
34
+
35
+ export function extractTablePreview(table: TableNode, maxRows = 2): string {
36
+ const rows: string[] = [];
37
+ for (let idx = 0; idx < Math.min(table.rows.length, maxRows); idx += 1) {
38
+ const t = extractRowText(table.rows[idx]!);
39
+ if (t) rows.push(t);
40
+ }
41
+ return rows.join(" ; ");
42
+ }
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Comment-thread scope kind — Slice 3.
3
+ *
4
+ * Projects a canonical `CommentThread` record into a scope. Content is
5
+ * the thread body + concatenated entry bodies (the narrative the agent
6
+ * needs). Workflow mode is `comment` (open threads) / `view` (resolved).
7
+ */
8
+
9
+ import type { CommentThreadEnumeratedScope } from "../enumerate-scopes.ts";
10
+ import { deriveReplaceability } from "../replaceability.ts";
11
+ import type {
12
+ SemanticScope,
13
+ SemanticScopeWorkflow,
14
+ } from "../semantic-scope-types.ts";
15
+ import { buildExcerpt } from "./_paragraph-text.ts";
16
+
17
+ function threadText(thread: CommentThreadEnumeratedScope["thread"]): string {
18
+ const parts: string[] = [];
19
+ if (thread.body) parts.push(thread.body);
20
+ for (const entry of thread.entries ?? []) {
21
+ if (entry.body) parts.push(entry.body);
22
+ }
23
+ return parts.join("\n");
24
+ }
25
+
26
+ export function compileCommentThreadScope(
27
+ entry: CommentThreadEnumeratedScope,
28
+ ): SemanticScope {
29
+ const { handle, thread } = entry;
30
+ const text = threadText(thread);
31
+ const workflow: SemanticScopeWorkflow = {
32
+ scopeIds: [thread.commentId],
33
+ effectiveMode: thread.status === "open" ? "comment" : "view",
34
+ ...(thread.status === "detached"
35
+ ? { blockedReasons: ["comment-thread-detached"] }
36
+ : {}),
37
+ };
38
+ return {
39
+ handle,
40
+ kind: "comment-thread",
41
+ classifications: entry.classifications,
42
+ content: {
43
+ text,
44
+ excerpt: buildExcerpt(text),
45
+ },
46
+ formatting: {},
47
+ layout: {},
48
+ geometry: {},
49
+ workflow,
50
+ replaceability: deriveReplaceability(entry.kind, handle.provenance),
51
+ audit: {
52
+ source: thread.metadata?.source === "import" ? "import" : "runtime",
53
+ derivedFrom: ["canonical.review.comments", `status:${thread.status}`],
54
+ confidence: thread.status === "detached" ? "low" : "high",
55
+ },
56
+ // layout / geometry projections unpopulated until refactor/05 Slice 5.
57
+ partial: true,
58
+ };
59
+ }