@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
@@ -2,7 +2,7 @@ import React from "react";
2
2
 
3
3
  import { preserveEditorSelectionMouseDown } from "../../ui/headless/preserve-editor-selection";
4
4
  import type { ActiveImageContext } from "../../ui/headless/selection-tool-types";
5
- import { EMU_PER_INCH } from "../../runtime/units.ts";
5
+ import { EMU_PER_INCH } from "../../api/public-types.ts";
6
6
 
7
7
  export interface TwImageContextToolbarProps {
8
8
  activeImage: ActiveImageContext;
@@ -15,6 +15,16 @@ export interface TwImageContextToolbarProps {
15
15
  mediaId: string,
16
16
  offsets: { horizontalOffsetEmu?: number; verticalOffsetEmu?: number },
17
17
  ) => void;
18
+ /**
19
+ * Phase D.3 — progressive-disclosure compact mode. When `true`,
20
+ * the toolbar reduces to the image badge + a single "More…" button
21
+ * that opens the shared context menu via `onOpenMore`. Size
22
+ * presets + nudge controls move into the registry's image actions
23
+ * (see `editor-action-registry.ts` — image-size-small/medium/large
24
+ * + image-replace + image-alt-text). See DESIGN-EDITOR.md §6.4.
25
+ */
26
+ compact?: boolean;
27
+ onOpenMore?: (coords: { clientX: number; clientY: number }) => void;
18
28
  }
19
29
 
20
30
  const IMAGE_SIZE_PRESETS = [
@@ -28,6 +38,37 @@ const NUDGE_EMU = 228600;
28
38
  export function TwImageContextToolbar(props: TwImageContextToolbarProps) {
29
39
  const { activeImage } = props;
30
40
 
41
+ if (props.compact) {
42
+ return (
43
+ <div
44
+ data-testid="image-context-toolbar"
45
+ data-variant="compact"
46
+ className="inline-flex items-center gap-[6px] rounded-[var(--radius-lg)] border border-[var(--color-border-subtle)] bg-[var(--color-bg-canvas)] px-2 py-1 shadow-[var(--shadow-float)]"
47
+ >
48
+ <span className="text-[9px] font-semibold uppercase tracking-[0.12em] text-tertiary">
49
+ Image
50
+ </span>
51
+ <span className="rounded-full bg-surface px-1.5 py-0.5 text-[9px] font-medium uppercase tracking-[0.1em] text-secondary">
52
+ {activeImage.display}
53
+ </span>
54
+ <button
55
+ type="button"
56
+ data-testid="image-context-toolbar-more"
57
+ aria-label="Image actions menu"
58
+ className="inline-flex h-6 items-center gap-1 rounded-[var(--radius-sm)] px-2 text-xs font-medium text-secondary hover:bg-hover focus-visible:outline-none focus-visible:shadow-[var(--shadow-focus)]"
59
+ disabled={props.disabled}
60
+ onMouseDown={preserveEditorSelectionMouseDown}
61
+ onClick={(ev) => {
62
+ const rect = (ev.currentTarget as HTMLButtonElement).getBoundingClientRect();
63
+ props.onOpenMore?.({ clientX: rect.left, clientY: rect.bottom });
64
+ }}
65
+ >
66
+ More…
67
+ </button>
68
+ </div>
69
+ );
70
+ }
71
+
31
72
  return (
32
73
  <div
33
74
  data-testid="image-context-toolbar"
@@ -19,8 +19,8 @@ import type {
19
19
  RenderFrameRect,
20
20
  SelectionSnapshot,
21
21
  TableStructureContextSnapshot,
22
- WordReviewEditorLayoutFacet,
23
22
  } from "../../api/public-types";
23
+ import type { GeometryFacet } from "../../api/public-types";
24
24
  import type {
25
25
  ActiveImageContext,
26
26
  ActiveObjectContext,
@@ -28,7 +28,11 @@ import type {
28
28
  } from "../../ui/headless/selection-tool-types";
29
29
 
30
30
  export interface ResolveSelectionAnchorInput {
31
- facet: WordReviewEditorLayoutFacet;
31
+ // refactor/05 cross-lane-coord §8.4 migration: read through
32
+ // runtime.geometry (not runtime.layout). Geometry facet carries
33
+ // getRenderFrame as a required method; the prior optional form on
34
+ // WordReviewEditorLayoutFacet is being deleted.
35
+ geometryFacet: GeometryFacet;
32
36
  selection: SelectionSnapshot;
33
37
  tool: ActiveSelectionToolModel | null;
34
38
  }
@@ -52,12 +56,9 @@ export interface ResolveSelectionAnchorInput {
52
56
  export function resolveSelectionAnchor(
53
57
  input: ResolveSelectionAnchorInput,
54
58
  ): RenderFrameRect | null {
55
- const { facet, selection, tool } = input;
59
+ const { geometryFacet, selection, tool } = input;
56
60
  // No kernel, no anchor — caller falls back to DOM rects in that case.
57
- const frame =
58
- typeof facet.getRenderFrame === "function"
59
- ? facet.getRenderFrame() ?? null
60
- : null;
61
+ const frame = geometryFacet.getRenderFrame() ?? null;
61
62
  if (!frame) return null;
62
63
 
63
64
  if (!tool) {
@@ -6,18 +6,52 @@ export interface TwSelectionToolBlockedProps {
6
6
  model: BlockedExplainerSelectionToolModel;
7
7
  }
8
8
 
9
+ /**
10
+ * Blocked-action explanation card. Per designsystem.md §6.8 the spec
11
+ * calls for:
12
+ * - Plain operational copy ("Editing is blocked in this scope.")
13
+ * - Always expose the alternative action as a button when available
14
+ * - Warning-tone severity ring; not error-tone unless destructive
15
+ * - role="status" + aria-live="polite"
16
+ *
17
+ * Chrome Closure Pass · Task 5 — split the previous single-line
18
+ * disabledReason render into an eyebrow ("Unavailable here") + a
19
+ * full-text body, and add a warning-tone severity ring + leading
20
+ * severity dot per §3.8 severity ladder. The model carries no
21
+ * "alternative action" callback today (out-of-scope for this slice
22
+ * — runtime enrichment is a follow-up issue against L06); when one
23
+ * lands, the action button slot below already has its placement
24
+ * guarded by the conditional render so adding a callback is a one-
25
+ * prop change.
26
+ */
9
27
  export function TwSelectionToolBlocked(props: TwSelectionToolBlockedProps) {
28
+ const { disabledReason } = props.model;
10
29
  return (
11
30
  <div
12
31
  data-testid="blocked-selection-tool"
13
- className="max-w-[min(20rem,calc(100vw-1.5rem))] rounded-lg border border-border bg-canvas px-2.5 py-1.5 shadow-md"
32
+ className="max-w-[min(20rem,calc(100vw-1.5rem))] rounded-lg border border-[var(--color-semantic-warning)] bg-canvas px-2.5 py-2 shadow-md ring-1 ring-[var(--color-semantic-warning-soft)]"
14
33
  role="status"
15
34
  aria-live="polite"
16
35
  >
17
- <div className="text-[9px] font-semibold uppercase tracking-[0.12em] text-tertiary">
18
- Unavailable here
36
+ <div className="flex items-center gap-2">
37
+ <span
38
+ data-testid="blocked-selection-tool__severity"
39
+ className="inline-block h-1.5 w-1.5 rounded-full bg-[var(--color-semantic-warning)]"
40
+ aria-hidden="true"
41
+ />
42
+ <span
43
+ data-testid="blocked-selection-tool__eyebrow"
44
+ className="text-[9px] font-semibold uppercase tracking-[0.12em] text-[var(--color-semantic-warning)]"
45
+ >
46
+ Unavailable here
47
+ </span>
48
+ </div>
49
+ <div
50
+ data-testid="blocked-selection-tool__reason"
51
+ className="mt-1 text-[13px] text-primary"
52
+ >
53
+ {disabledReason}
19
54
  </div>
20
- <div className="mt-0.5 text-[13px] text-primary">{props.model.disabledReason}</div>
21
55
  </div>
22
56
  );
23
57
  }
@@ -8,22 +8,120 @@ export interface TwSelectionToolCommentProps {
8
8
  onAddComment?: () => void;
9
9
  }
10
10
 
11
+ /**
12
+ * Format an ISO-8601 timestamp as a relative-time hint suitable for the
13
+ * comment card header. Matches §6.6 anatomy ("avatar/initials + name +
14
+ * relative time, text.xs"). Falls through to absolute date when the
15
+ * delta is more than a week old. Pure helper — no Intl polyfills, no
16
+ * locale assumptions beyond the canonical ISO format the runtime emits.
17
+ */
18
+ function formatRelative(iso: string | undefined, now = Date.now()): string {
19
+ if (!iso) return "";
20
+ const t = Date.parse(iso);
21
+ if (Number.isNaN(t)) return "";
22
+ const deltaSec = Math.max(0, Math.floor((now - t) / 1000));
23
+ if (deltaSec < 60) return "just now";
24
+ const min = Math.floor(deltaSec / 60);
25
+ if (min < 60) return `${min}m ago`;
26
+ const hr = Math.floor(min / 60);
27
+ if (hr < 24) return `${hr}h ago`;
28
+ const day = Math.floor(hr / 24);
29
+ if (day < 7) return `${day}d ago`;
30
+ // Older than a week — show the date portion of the ISO string.
31
+ return iso.slice(0, 10);
32
+ }
33
+
34
+ /**
35
+ * Initials helper for the author chip — first letter of each
36
+ * whitespace-separated word, max 2 chars. Falls back to "?" for empty
37
+ * input so the visual rhythm of the chip stays stable.
38
+ */
39
+ function initialsOf(name: string | undefined): string {
40
+ if (!name) return "?";
41
+ const parts = name.trim().split(/\s+/).filter(Boolean);
42
+ if (parts.length === 0) return "?";
43
+ const head = parts[0]![0]!;
44
+ const tail = parts.length > 1 ? parts[parts.length - 1]![0]! : "";
45
+ return (head + tail).toUpperCase().slice(0, 2);
46
+ }
47
+
48
+ /**
49
+ * Comment quick card. Anchored when the selection intersects a comment
50
+ * anchor. Per designsystem.md §6.6 the spec calls for:
51
+ * - Author row: avatar/initials + name + relative time, text.xs
52
+ * - One-or-two-line excerpt at text.sm
53
+ * - One primary action (Open thread / Reply / Comment)
54
+ * - Inline marker pin in margin remains visible (handled elsewhere)
55
+ *
56
+ * Chrome Closure Pass · Task 5 — surface the author + timestamp +
57
+ * thread-status fields the model already exposes; previously the card
58
+ * showed only `previewText` and a single "Comment" button, leaving the
59
+ * §6.6 anatomy unrealized despite the data being available.
60
+ */
11
61
  export function TwSelectionToolComment(props: TwSelectionToolCommentProps) {
62
+ const { thread, previewText, canAddComment } = props.model;
63
+ const author = thread?.createdBy;
64
+ const created = thread?.createdAt;
65
+ const status = thread?.status; // open | resolved | detached
66
+ const excerpt = thread?.excerpt ?? previewText ?? "Comment thread";
67
+ const isResolved = status === "resolved";
68
+ const isDetached = status === "detached";
69
+
70
+ const statusToneClass = isResolved
71
+ ? "bg-[var(--color-semantic-success-soft)] text-[var(--color-semantic-success)]"
72
+ : isDetached
73
+ ? "bg-[var(--color-semantic-warning-soft)] text-[var(--color-semantic-warning)]"
74
+ : "bg-[var(--color-accent-soft)] text-[var(--color-accent-primary)]";
75
+
12
76
  return (
13
77
  <div
14
78
  data-testid="comment-thread-selection-tool"
15
- className="max-w-[min(20rem,calc(100vw-1.5rem))] rounded-lg border border-border bg-canvas px-2.5 py-1.5 shadow-md"
79
+ className="max-w-[min(20rem,calc(100vw-1.5rem))] rounded-lg border border-border bg-canvas px-2.5 py-2 shadow-md"
16
80
  >
17
- <div className="text-[9px] font-semibold uppercase tracking-[0.12em] text-tertiary">
18
- Comment
81
+ <div className="flex items-center gap-2">
82
+ {author ? (
83
+ <span
84
+ data-testid="comment-thread-selection-tool__avatar"
85
+ className="inline-flex h-5 w-5 items-center justify-center rounded-full bg-[var(--color-bg-muted)] text-[9px] font-semibold uppercase tracking-wide text-[var(--color-text-secondary)]"
86
+ aria-hidden="true"
87
+ >
88
+ {initialsOf(author)}
89
+ </span>
90
+ ) : null}
91
+ <span
92
+ data-testid="comment-thread-selection-tool__author"
93
+ className="truncate text-[11px] font-semibold text-[var(--color-text-primary)]"
94
+ >
95
+ {author ?? "Comment"}
96
+ </span>
97
+ {created ? (
98
+ <span
99
+ data-testid="comment-thread-selection-tool__timestamp"
100
+ className="text-[10px] text-[var(--color-text-tertiary)]"
101
+ title={created}
102
+ >
103
+ {formatRelative(created)}
104
+ </span>
105
+ ) : null}
106
+ {status && status !== "open" ? (
107
+ <span
108
+ data-testid="comment-thread-selection-tool__status"
109
+ className={`ml-auto inline-flex items-center rounded-[var(--radius-pill)] px-1.5 py-px text-[9px] font-semibold uppercase tracking-[0.08em] ${statusToneClass}`}
110
+ >
111
+ {status}
112
+ </span>
113
+ ) : null}
19
114
  </div>
20
- <div className="mt-0.5 truncate text-[13px] text-primary">
21
- {props.model.previewText ?? "Comment thread"}
115
+ <div
116
+ data-testid="comment-thread-selection-tool__excerpt"
117
+ className="mt-1 line-clamp-2 text-[13px] text-primary"
118
+ >
119
+ {excerpt}
22
120
  </div>
23
121
  <button
24
122
  type="button"
25
123
  aria-label="Add comment from thread"
26
- disabled={!props.model.canAddComment || !props.onAddComment}
124
+ disabled={!canAddComment || !props.onAddComment}
27
125
  onMouseDown={preserveEditorSelectionMouseDown}
28
126
  onClick={props.onAddComment}
29
127
  className="mt-1.5 inline-flex h-7 items-center rounded-md border border-border px-2 text-[11px] font-medium text-secondary transition-colors hover:bg-surface disabled:cursor-not-allowed disabled:opacity-40"
@@ -7,6 +7,10 @@ import type {
7
7
  RuntimeContextAnalyticsSnapshot,
8
8
  } from "../../api/public-types";
9
9
  import type { ActiveSelectionToolModel } from "../../ui/headless/selection-tool-types";
10
+ import {
11
+ useLocalSurfaceRequest,
12
+ type LocalSurfaceKind,
13
+ } from "./local-surface-arbiter";
10
14
  import { TwContextAnalyticsSummary } from "./tw-context-analytics-summary";
11
15
  import { TwDetachHandle } from "./tw-detach-handle";
12
16
  import { TwSelectionToolBlocked } from "./tw-selection-tool-blocked";
@@ -109,10 +113,42 @@ export function TwSelectionToolHost(props: TwSelectionToolHostProps) {
109
113
  const density = useDwellDensity(props.tool);
110
114
  const { onChromePinChange } = props;
111
115
 
116
+ // Phase F.2 — gate the selection tool on the shared local-surface
117
+ // arbiter. When a higher-priority peer (scope card, suggestion
118
+ // card, blocked card, workflow card) has the slot, the host yields
119
+ // — no hard-thrash between two floats. The dwell hook above
120
+ // remains the density promotion authority; the arbiter is the
121
+ // visibility authority. A pinned scope card does NOT evict us
122
+ // because pinned surfaces coexist with the single active slot
123
+ // (see `local-surface-arbiter.ts` §pinned slot).
124
+ const arbiterKind = props.tool ? arbiterKindForToolKind(props.tool.kind) : null;
125
+ const isArbiterActive = useLocalSurfaceRequest(
126
+ arbiterKind && props.tool
127
+ ? { kind: arbiterKind, id: `selection-${props.tool.kind}` }
128
+ : null,
129
+ );
130
+
131
+ // All hooks MUST be called unconditionally per the Rules of Hooks —
132
+ // keep `useCallback` above the early returns that depend on runtime
133
+ // state (`props.tool`, arbiter verdict).
134
+ const handlePinChange = useCallback(
135
+ (surface: ChromePinSurface, next: PinState | null) => {
136
+ onChromePinChange?.(surface, next);
137
+ },
138
+ [onChromePinChange],
139
+ );
140
+
112
141
  if (!props.tool) {
113
142
  return null;
114
143
  }
115
144
 
145
+ if (!isArbiterActive) {
146
+ // A higher-priority local surface currently owns the slot.
147
+ // Rendering returns null so the host contributes no DOM and the
148
+ // rival surface is the only float visible this frame.
149
+ return null;
150
+ }
151
+
116
152
  // R2.3: pin state now rides ViewState.chromePins.selectionTier so
117
153
  // pinned tools survive snapshot rebuilds. When the host doesn't pass
118
154
  // chromePins/onChromePinChange, the detach handle is suppressed (same
@@ -123,13 +159,6 @@ export function TwSelectionToolHost(props: TwSelectionToolHostProps) {
123
159
  const supportsDetach =
124
160
  supportsDetachForKind(props.tool.kind) && Boolean(props.onChromePinChange);
125
161
 
126
- const handlePinChange = useCallback(
127
- (surface: ChromePinSurface, next: PinState | null) => {
128
- onChromePinChange?.(surface, next);
129
- },
130
- [onChromePinChange],
131
- );
132
-
133
162
  const overlayTestId = getOverlayTestId(props.tool.kind, Boolean(props.placement));
134
163
  const toolContent = renderTool(props, props.tool, density);
135
164
  const content = toolContent ? (
@@ -302,6 +331,36 @@ function supportsDetachForKind(kind: ActiveSelectionToolModel["kind"]): boolean
302
331
  return kind !== "blocked-explainer";
303
332
  }
304
333
 
334
+ /**
335
+ * Map a selection-tool kind into its local-surface arbiter bucket.
336
+ * Priority ordering lives in `local-surface-arbiter.ts §LOCAL_SURFACE_PRIORITY`.
337
+ * - formatting-inline → selection-format
338
+ * - suggestion-review → suggestion-card
339
+ * - workflow-task → workflow-card
340
+ * - blocked-explainer → blocked-card
341
+ * - comment-thread → comment-preview
342
+ * - structure-context → selection-format (table/image/object
343
+ * selection chrome is the same priority tier as text formatting;
344
+ * the audit does not promote it above selection-format).
345
+ */
346
+ function arbiterKindForToolKind(
347
+ kind: ActiveSelectionToolModel["kind"],
348
+ ): LocalSurfaceKind {
349
+ switch (kind) {
350
+ case "formatting-inline":
351
+ case "structure-context":
352
+ return "selection-format";
353
+ case "suggestion-review":
354
+ return "suggestion-card";
355
+ case "workflow-task":
356
+ return "workflow-card";
357
+ case "blocked-explainer":
358
+ return "blocked-card";
359
+ case "comment-thread":
360
+ return "comment-preview";
361
+ }
362
+ }
363
+
305
364
  function getTopMenuLabel(kind: ActiveSelectionToolModel["kind"]): string {
306
365
  switch (kind) {
307
366
  case "formatting-inline":
@@ -6,21 +6,67 @@ export interface TwSelectionToolWorkflowProps {
6
6
  model: WorkflowTaskSelectionToolModel;
7
7
  }
8
8
 
9
+ /**
10
+ * Workflow local card. Per designsystem.md §6.7 the spec calls for:
11
+ * - Title (scope name, text.sm semibold)
12
+ * - State chip (active / blocked / scheduled, tone per §3.7)
13
+ * - Allowed-or-blocked-action row (text.sm)
14
+ * - Optional assignee chip
15
+ *
16
+ * Chrome Closure Pass · Task 5 — derive a state chip from the model's
17
+ * `disabledReason`: present → "Blocked" with warning tone; absent →
18
+ * "Active" with accent tone. Title typography lifts to semibold per
19
+ * §6.7. Assignee chip omitted (not on the model — runtime enrichment
20
+ * is a follow-up issue against L06).
21
+ */
9
22
  export function TwSelectionToolWorkflow(props: TwSelectionToolWorkflowProps) {
23
+ const { workflowTitle, workflowDetail, disabledReason } = props.model;
24
+ const isBlocked = Boolean(disabledReason);
25
+ const stateLabel = isBlocked ? "Blocked" : "Active";
26
+ const stateToneClass = isBlocked
27
+ ? "bg-[var(--color-semantic-warning-soft)] text-[var(--color-semantic-warning)]"
28
+ : "bg-[var(--color-accent-soft)] text-[var(--color-accent-primary)]";
29
+
10
30
  return (
11
31
  <div
12
32
  data-testid="workflow-task-selection-tool"
13
- className="max-w-[min(20rem,calc(100vw-1.5rem))] rounded-lg border border-border bg-canvas px-2.5 py-1.5 shadow-md"
33
+ className="max-w-[min(20rem,calc(100vw-1.5rem))] rounded-lg border border-border bg-canvas px-2.5 py-2 shadow-md"
14
34
  >
15
- <div className="text-[9px] font-semibold uppercase tracking-[0.12em] text-tertiary">
16
- Workflow task
35
+ <div className="flex items-center gap-2">
36
+ <span
37
+ data-testid="workflow-task-selection-tool__eyebrow"
38
+ className="text-[9px] font-semibold uppercase tracking-[0.12em] text-tertiary"
39
+ >
40
+ Workflow
41
+ </span>
42
+ <span
43
+ data-testid="workflow-task-selection-tool__state"
44
+ className={`ml-auto inline-flex items-center rounded-[var(--radius-pill)] px-1.5 py-px text-[9px] font-semibold uppercase tracking-[0.08em] ${stateToneClass}`}
45
+ >
46
+ {stateLabel}
47
+ </span>
48
+ </div>
49
+ <div
50
+ data-testid="workflow-task-selection-tool__title"
51
+ className="mt-1 text-[13px] font-semibold text-primary"
52
+ >
53
+ {workflowTitle ?? "Scoped task"}
17
54
  </div>
18
- <div className="mt-0.5 text-[13px] text-primary">{props.model.workflowTitle ?? "Scoped task"}</div>
19
- {props.model.workflowDetail ? (
20
- <div className="mt-0.5 text-[11px] text-secondary">{props.model.workflowDetail}</div>
55
+ {workflowDetail ? (
56
+ <div
57
+ data-testid="workflow-task-selection-tool__detail"
58
+ className="mt-0.5 text-[11px] text-secondary"
59
+ >
60
+ {workflowDetail}
61
+ </div>
21
62
  ) : null}
22
- {props.model.disabledReason ? (
23
- <div className="mt-1 text-[11px] text-tertiary">{props.model.disabledReason}</div>
63
+ {disabledReason ? (
64
+ <div
65
+ data-testid="workflow-task-selection-tool__reason"
66
+ className="mt-1 text-[11px] text-[var(--color-semantic-warning)]"
67
+ >
68
+ {disabledReason}
69
+ </div>
24
70
  ) : null}
25
71
  </div>
26
72
  );
@@ -12,6 +12,12 @@ export interface TwShortcutHintProps {
12
12
 
13
13
  const MAC_SYMBOL: Record<string, string> = {
14
14
  Cmd: "⌘",
15
+ // "Mod" is the registry's cross-platform modifier — Cmd on Mac,
16
+ // Ctrl elsewhere. Without this entry rows like `["Mod", "X"]`
17
+ // rendered as the literal "ModX" on Mac + "Mod+X" on Win instead
18
+ // of the expected "⌘X" / "Ctrl+X". Chrome Closure Pass Task 4
19
+ // fix-up.
20
+ Mod: "⌘",
15
21
  Shift: "⇧",
16
22
  Alt: "⌥",
17
23
  Ctrl: "⌃",
@@ -46,7 +52,7 @@ export function TwShortcutHint(props: TwShortcutHintProps): React.JSX.Element {
46
52
  const mapped = keys.map((k) =>
47
53
  platform === "mac"
48
54
  ? (MAC_SYMBOL[k] ?? k)
49
- : (k === "Cmd" ? "Ctrl" : k)
55
+ : (k === "Cmd" || k === "Mod" ? "Ctrl" : k),
50
56
  );
51
57
 
52
58
  const containerClass = [
@@ -14,6 +14,16 @@ export interface TwSuggestionCardProps {
14
14
  onReject?: () => void;
15
15
  onEditSuggestion?: () => void;
16
16
  onAddComment?: () => void;
17
+ /**
18
+ * Phase D.4 — rail-detail handoff. When wired, the card exposes a
19
+ * "See detail →" link that opens the `Changes` rail tab focused on
20
+ * the current suggestion (audit §2.8 "long suggestion rationale to
21
+ * rail" + DESIGN-EDITOR.md §6.5 "clicking the card body opens the
22
+ * rail thread"). Lets the floating card stay compact — summary +
23
+ * accept/reject + next — while long rationale lives in the rail
24
+ * where it belongs.
25
+ */
26
+ onOpenDetailInRail?: () => void;
17
27
  }
18
28
 
19
29
  const focusRingClass =
@@ -96,6 +106,29 @@ export function TwSuggestionCard(props: TwSuggestionCardProps) {
96
106
  </Tooltip.Content>
97
107
  </Tooltip.Portal>
98
108
  </Tooltip.Root>
109
+ {/*
110
+ * Rail-detail handoff (Phase D.4). Only renders when the host
111
+ * wires `onOpenDetailInRail` so integrators without a rail
112
+ * don't get a dead link. Secondary-tone text link, not a
113
+ * button — per designsystem.md §6.5 the card stays compact.
114
+ */}
115
+ {props.onOpenDetailInRail ? (
116
+ <button
117
+ type="button"
118
+ data-testid="suggestion-card-rail-handoff"
119
+ aria-label="See suggestion detail in rail"
120
+ onMouseDown={preserveEditorSelectionMouseDown}
121
+ onClick={props.onOpenDetailInRail}
122
+ // D.5.N6 — align focus ring with the design-system
123
+ // --shadow-focus token (designsystem.md §4.7) rather than
124
+ // the older Tailwind ring utility used elsewhere in this
125
+ // file. The ring token is mint-glow consistent across
126
+ // light/dark; Phase B/D compact toolbars already use it.
127
+ className="ml-auto inline-flex h-7 items-center text-[11px] font-medium text-[var(--color-text-tertiary)] underline decoration-dotted underline-offset-2 hover:text-[var(--color-accent-primary)] focus-visible:outline-none focus-visible:shadow-[var(--shadow-focus)] rounded-[var(--radius-sm)]"
128
+ >
129
+ See detail →
130
+ </button>
131
+ ) : null}
99
132
  </div>
100
133
  </div>
101
134
  );
@@ -28,6 +28,35 @@ export interface TwTableContextToolbarProps {
28
28
  onDistributeColumnsEvenly?: () => void;
29
29
  onSetTableAlignment?: (alignment: "left" | "center" | "right") => void;
30
30
  onSetCellVerticalAlign?: (align: "top" | "center" | "bottom") => void;
31
+ /**
32
+ * Phase D.2 — progressive-disclosure compact mode.
33
+ *
34
+ * When `true`, the toolbar reduces to:
35
+ * - the tier badge + size + selection label (diagnostic context)
36
+ * - a single "More…" button that fires `onOpenMore`
37
+ *
38
+ * All action controls (Add row / Merge / Split / Fill / Style /
39
+ * Delete / Align / V-align / Distribute / Borders) are suppressed;
40
+ * the full set lives in the shared editor-action-registry and is
41
+ * accessed via right-click OR the "More…" button (which opens the
42
+ * same context menu). Per DESIGN-EDITOR.md §6.4 ("right-click
43
+ * cannot be richer than the floating surface; cannot duplicate
44
+ * command trees") the compact variant pins that rule — there IS no
45
+ * richer surface to diverge from.
46
+ *
47
+ * Default `false` preserves the rich in-tree behavior so no
48
+ * existing integrator breaks. Phase D.2 ships the opt-in; Phase E
49
+ * flips the default to `true` once the end-to-end
50
+ * `onContextMenuRequested` wiring is proven.
51
+ */
52
+ compact?: boolean;
53
+ /**
54
+ * Fires when the user clicks the "More…" button in compact mode.
55
+ * Receives the button's clientX / clientY so the integrator can
56
+ * open `TwContextMenu` at the button anchor via
57
+ * `chromeControllerRef.current?.openWithKinds({ kinds: ["table-cell"], clientX, clientY })`.
58
+ */
59
+ onOpenMore?: (coords: { clientX: number; clientY: number }) => void;
31
60
  }
32
61
 
33
62
  /**
@@ -117,6 +146,44 @@ export function TwTableContextToolbar(props: TwTableContextToolbarProps) {
117
146
  : null;
118
147
  const selectionLabel = tableContext ? formatSelectionLabel(tableContext, tier) : null;
119
148
 
149
+ // Phase D.2 — compact variant: minimal inline affordance + one
150
+ // "More…" button that opens the shared context menu. The full
151
+ // action set lives in editor-action-registry so right-click and
152
+ // "More…" always show identical options (DESIGN-EDITOR.md §6.4).
153
+ if (props.compact) {
154
+ return (
155
+ <div
156
+ data-testid="table-context-toolbar"
157
+ data-tier={tier}
158
+ data-variant="compact"
159
+ className="inline-flex items-center gap-[6px] rounded-[var(--radius-lg)] border border-[var(--color-border-subtle)] bg-[var(--color-bg-canvas)] px-2 py-1 shadow-[var(--shadow-float)]"
160
+ >
161
+ <span className="text-[9px] font-semibold uppercase tracking-[0.12em] text-[var(--color-text-tertiary)]">
162
+ {tierLabel(tier)}
163
+ </span>
164
+ {tableSizeLabel ? <ToolbarBadge>{tableSizeLabel}</ToolbarBadge> : null}
165
+ {selectionLabel ? <ToolbarBadge>{selectionLabel}</ToolbarBadge> : null}
166
+ <button
167
+ type="button"
168
+ data-testid="table-context-toolbar-more"
169
+ aria-label="Table actions menu"
170
+ className="inline-flex h-6 items-center gap-1 rounded-[var(--radius-sm)] px-2 text-xs font-medium text-secondary hover:bg-hover focus-visible:outline-none focus-visible:shadow-[var(--shadow-focus)]"
171
+ disabled={props.disabled}
172
+ onMouseDown={preserveEditorSelectionMouseDown}
173
+ onClick={(ev) => {
174
+ const rect = (ev.currentTarget as HTMLButtonElement).getBoundingClientRect();
175
+ props.onOpenMore?.({
176
+ clientX: rect.left,
177
+ clientY: rect.bottom,
178
+ });
179
+ }}
180
+ >
181
+ More…
182
+ </button>
183
+ </div>
184
+ );
185
+ }
186
+
120
187
  // Tier-specific width caps. Progressive: T2 ≤ 18rem, T3 ≤ 22rem, T4
121
188
  // ≤ 24rem, T5 ≤ 28rem. Down from the old flat 30rem always.
122
189
  const widthCap = tierWidthCap(tier);
@@ -147,7 +214,17 @@ export function TwTableContextToolbar(props: TwTableContextToolbarProps) {
147
214
  capability={tableContext?.operations.setTableAlignment}
148
215
  disabled={props.disabled}
149
216
  onClick={() => props.onSetTableAlignment?.(align)}
150
- active={tableContext?.currentCell != null && align === "left"}
217
+ // Chrome Closure Pass · Task 3 — pre-fix bug:
218
+ // `active` was hardcoded to `align === "left"`, lighting
219
+ // up the Left button regardless of the actual table
220
+ // alignment. `TableStructureContextSnapshot` does not
221
+ // currently surface table-level alignment (the field
222
+ // lives on `RuntimeTableRenderPlanSnapshot`); until the
223
+ // runtime exposes it on the structure context, treat
224
+ // alignment buttons as plain action buttons (no toggle).
225
+ // Fix scope: presentation-only correction; runtime
226
+ // enrichment is a follow-up issue against L07.
227
+ active={false}
151
228
  >
152
229
  {align[0]!.toUpperCase()}
153
230
  </ToolbarButton>