@beyondwork/docx-react-component 1.0.37 → 1.0.39

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 (116) hide show
  1. package/package.json +41 -31
  2. package/src/api/public-types.ts +496 -1
  3. package/src/core/commands/section-layout-commands.ts +58 -0
  4. package/src/core/commands/table-grid.ts +431 -0
  5. package/src/core/commands/table-structure-commands.ts +845 -56
  6. package/src/core/commands/text-commands.ts +122 -2
  7. package/src/io/docx-session.ts +1 -0
  8. package/src/io/export/serialize-main-document.ts +2 -11
  9. package/src/io/export/serialize-numbering.ts +43 -10
  10. package/src/io/export/serialize-paragraph-formatting.ts +152 -0
  11. package/src/io/export/serialize-run-formatting.ts +90 -0
  12. package/src/io/export/serialize-styles.ts +212 -0
  13. package/src/io/export/serialize-tables.ts +74 -0
  14. package/src/io/export/table-properties-xml.ts +139 -4
  15. package/src/io/normalize/normalize-text.ts +15 -0
  16. package/src/io/ooxml/parse-fields.ts +10 -3
  17. package/src/io/ooxml/parse-footnotes.ts +60 -0
  18. package/src/io/ooxml/parse-headers-footers.ts +60 -0
  19. package/src/io/ooxml/parse-main-document.ts +137 -0
  20. package/src/io/ooxml/parse-numbering.ts +41 -1
  21. package/src/io/ooxml/parse-paragraph-formatting.ts +188 -0
  22. package/src/io/ooxml/parse-run-formatting.ts +129 -0
  23. package/src/io/ooxml/parse-styles.ts +31 -0
  24. package/src/io/ooxml/parse-tables.ts +249 -0
  25. package/src/io/ooxml/xml-attr-helpers.ts +60 -0
  26. package/src/io/ooxml/xml-element.ts +19 -0
  27. package/src/model/canonical-document.ts +117 -3
  28. package/src/runtime/collab/event-types.ts +165 -0
  29. package/src/runtime/collab/index.ts +22 -0
  30. package/src/runtime/collab/remote-cursor-awareness.ts +93 -0
  31. package/src/runtime/collab/runtime-collab-sync.ts +273 -0
  32. package/src/runtime/document-layout.ts +4 -2
  33. package/src/runtime/document-navigation.ts +1 -1
  34. package/src/runtime/document-runtime.ts +248 -18
  35. package/src/runtime/layout/default-page-format.ts +96 -0
  36. package/src/runtime/layout/index.ts +47 -0
  37. package/src/runtime/layout/inert-layout-facet.ts +16 -0
  38. package/src/runtime/layout/layout-engine-instance.ts +100 -23
  39. package/src/runtime/layout/layout-invalidation.ts +14 -5
  40. package/src/runtime/layout/margin-preset-catalog.ts +178 -0
  41. package/src/runtime/layout/page-format-catalog.ts +233 -0
  42. package/src/runtime/layout/page-graph.ts +55 -0
  43. package/src/runtime/layout/paginate-paragraph-lines.ts +128 -0
  44. package/src/runtime/layout/paginated-layout-engine.ts +484 -37
  45. package/src/runtime/layout/project-block-fragments.ts +225 -0
  46. package/src/runtime/layout/public-facet.ts +748 -16
  47. package/src/runtime/layout/resolve-page-fields.ts +70 -0
  48. package/src/runtime/layout/resolve-page-previews.ts +185 -0
  49. package/src/runtime/layout/resolved-formatting-state.ts +30 -26
  50. package/src/runtime/layout/table-render-plan.ts +249 -0
  51. package/src/runtime/numbering-prefix.ts +5 -0
  52. package/src/runtime/paragraph-style-resolver.ts +194 -0
  53. package/src/runtime/render/block-fragment-projection.ts +35 -0
  54. package/src/runtime/render/decoration-resolver.ts +189 -0
  55. package/src/runtime/render/index.ts +57 -0
  56. package/src/runtime/render/pending-op-delta-reader.ts +129 -0
  57. package/src/runtime/render/render-frame-types.ts +317 -0
  58. package/src/runtime/render/render-kernel.ts +759 -0
  59. package/src/runtime/resolved-numbering-geometry.ts +9 -1
  60. package/src/runtime/surface-projection.ts +129 -9
  61. package/src/runtime/table-schema.ts +11 -0
  62. package/src/runtime/view-state.ts +67 -0
  63. package/src/runtime/workflow-markup.ts +1 -5
  64. package/src/runtime/workflow-rail-segments.ts +280 -0
  65. package/src/ui/WordReviewEditor.tsx +368 -19
  66. package/src/ui/editor-command-bag.ts +4 -0
  67. package/src/ui/editor-runtime-boundary.ts +16 -0
  68. package/src/ui/editor-shell-view.tsx +10 -0
  69. package/src/ui/editor-surface-controller.tsx +9 -1
  70. package/src/ui/headless/chrome-registry.ts +310 -15
  71. package/src/ui/headless/scoped-chrome-policy.ts +49 -1
  72. package/src/ui/headless/selection-tool-types.ts +10 -0
  73. package/src/ui-tailwind/chrome/chrome-preset-model.ts +23 -2
  74. package/src/ui-tailwind/chrome/review-queue-bar.tsx +2 -14
  75. package/src/ui-tailwind/chrome/role-action-sets.ts +80 -0
  76. package/src/ui-tailwind/chrome/tw-detach-handle.tsx +147 -0
  77. package/src/ui-tailwind/chrome/tw-selection-anchor-resolver.ts +160 -0
  78. package/src/ui-tailwind/chrome/tw-selection-tool-host.tsx +68 -92
  79. package/src/ui-tailwind/chrome/tw-selection-tool-placement.ts +149 -0
  80. package/src/ui-tailwind/chrome/tw-selection-tool-structure.tsx +11 -0
  81. package/src/ui-tailwind/chrome/tw-selection-toolbar.tsx +15 -4
  82. package/src/ui-tailwind/chrome/tw-table-border-picker.tsx +245 -0
  83. package/src/ui-tailwind/chrome/tw-table-context-toolbar.tsx +356 -140
  84. package/src/ui-tailwind/chrome/tw-table-grip-layer.tsx +284 -0
  85. package/src/ui-tailwind/chrome-overlay/chrome-overlay-projector.ts +94 -0
  86. package/src/ui-tailwind/chrome-overlay/index.ts +16 -0
  87. package/src/ui-tailwind/chrome-overlay/tw-chrome-overlay.tsx +96 -0
  88. package/src/ui-tailwind/chrome-overlay/tw-scope-rail-layer.tsx +178 -0
  89. package/src/ui-tailwind/editor-surface/fast-text-edit-lane.ts +4 -0
  90. package/src/ui-tailwind/editor-surface/local-edit-session-state.ts +11 -0
  91. package/src/ui-tailwind/editor-surface/page-slice-util.ts +15 -0
  92. package/src/ui-tailwind/editor-surface/perf-probe.ts +7 -1
  93. package/src/ui-tailwind/editor-surface/pm-command-bridge.ts +40 -4
  94. package/src/ui-tailwind/editor-surface/pm-decorations.ts +22 -12
  95. package/src/ui-tailwind/editor-surface/pm-page-break-decorations.ts +389 -0
  96. package/src/ui-tailwind/editor-surface/pm-schema.ts +40 -2
  97. package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +144 -62
  98. package/src/ui-tailwind/editor-surface/remote-cursor-plugin.ts +179 -0
  99. package/src/ui-tailwind/editor-surface/tw-page-block-view.tsx +559 -0
  100. package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +224 -75
  101. package/src/ui-tailwind/editor-surface/tw-table-bands.css +61 -0
  102. package/src/ui-tailwind/editor-surface/tw-table-node-view.tsx +19 -0
  103. package/src/ui-tailwind/index.ts +29 -0
  104. package/src/ui-tailwind/review/tw-comment-sidebar.tsx +2 -2
  105. package/src/ui-tailwind/review/tw-rail-card.tsx +150 -0
  106. package/src/ui-tailwind/review/tw-review-rail-footer.tsx +52 -0
  107. package/src/ui-tailwind/review/tw-review-rail.tsx +166 -11
  108. package/src/ui-tailwind/review/tw-workflow-tab.tsx +108 -0
  109. package/src/ui-tailwind/theme/editor-theme.css +498 -163
  110. package/src/ui-tailwind/toolbar/tw-role-action-region.tsx +680 -0
  111. package/src/ui-tailwind/toolbar/tw-scope-posture-menu.tsx +182 -0
  112. package/src/ui-tailwind/toolbar/tw-shell-header.tsx +162 -0
  113. package/src/ui-tailwind/toolbar/tw-toolbar.tsx +104 -2
  114. package/src/ui-tailwind/tw-review-workspace.tsx +234 -21
  115. package/src/runtime/collab-review-sync.ts +0 -254
  116. package/src/ui-tailwind/editor-surface/pm-collab-plugins.ts +0 -40
@@ -1,4 +1,7 @@
1
- import type { WordReviewEditorChromePreset } from "../../api/public-types";
1
+ import type {
2
+ EditorRole,
3
+ WordReviewEditorChromePreset,
4
+ } from "../../api/public-types";
2
5
  import type { SelectionToolKind } from "./selection-tool-types";
3
6
 
4
7
  export type ChromeSurface = "top-toolbar" | "selection-tool";
@@ -6,6 +9,7 @@ export type ChromeDensity = "compact";
6
9
  export type ToolbarChromePlacement = "inline" | "overflow" | "hidden";
7
10
 
8
11
  export type ToolbarChromeItemId =
12
+ // Universal formatting + history
9
13
  | "history"
10
14
  | "text-style-selectors"
11
15
  | "inline-formatting"
@@ -24,7 +28,27 @@ export type ToolbarChromeItemId =
24
28
  | "workspace-mode"
25
29
  | "zoom"
26
30
  | "health"
27
- | "export";
31
+ | "export"
32
+ // R1: role-scoped action region entries
33
+ | "editor-scope-posture-menu"
34
+ | "review-sidebar-tracked-changes"
35
+ | "review-sidebar-comments"
36
+ | "review-queue-prev"
37
+ | "review-queue-next"
38
+ | "review-queue-counts"
39
+ | "review-queue-active-label"
40
+ | "review-accept"
41
+ | "review-reject"
42
+ | "review-accept-all"
43
+ | "review-reject-all"
44
+ | "review-markup-mode"
45
+ | "workflow-prev"
46
+ | "workflow-next"
47
+ | "workflow-mark-complete"
48
+ | "workflow-claim"
49
+ | "workflow-skip"
50
+ | "workflow-mark-blocked"
51
+ | "workflow-jump-to-scope";
28
52
 
29
53
  export interface ChromeRegistryEntryBase {
30
54
  id: string;
@@ -42,12 +66,33 @@ export interface ToolbarChromeRegistryEntry extends ChromeRegistryEntryBase {
42
66
  id: ToolbarChromeItemId;
43
67
  surfaces: ["top-toolbar"];
44
68
  presets: ReadonlyArray<WordReviewEditorChromePreset>;
69
+ /**
70
+ * Roles in which this entry appears. Empty = appears in every role.
71
+ * R1 uses this to filter the inline action set down to the per-role
72
+ * matrix described in the chrome-phase plan.
73
+ */
74
+ roles: ReadonlyArray<EditorRole>;
45
75
  fullPlacement: Exclude<ToolbarChromePlacement, "hidden">;
46
76
  compactPlacement: ToolbarChromePlacement;
47
- runtimeBehavior: "always" | "formatting" | "structure" | "comment";
77
+ runtimeBehavior: "always" | "formatting" | "structure" | "comment" | "sidebar-panel";
48
78
  scopeBehavior?: "default" | "scoped-only" | "hidden-when-scoped";
79
+ /**
80
+ * Optional per-role placement override. When a role overrides the
81
+ * placement (for instance, "text-style-selectors" is `inline` in editor
82
+ * but `overflow` in review/workflow), list the role-specific placement
83
+ * here. Missing roles inherit `fullPlacement` / `compactPlacement`.
84
+ */
85
+ rolePlacement?: Partial<
86
+ Record<EditorRole, Exclude<ToolbarChromePlacement, "hidden">>
87
+ >;
49
88
  }
50
89
 
90
+ const ALL_ROLES: ReadonlyArray<EditorRole> = ["editor", "review", "workflow"];
91
+ const EDITOR_ONLY: ReadonlyArray<EditorRole> = ["editor"];
92
+ const REVIEW_ONLY: ReadonlyArray<EditorRole> = ["review"];
93
+ const WORKFLOW_ONLY: ReadonlyArray<EditorRole> = ["workflow"];
94
+ const EDITOR_AND_REVIEW: ReadonlyArray<EditorRole> = ["editor", "review"];
95
+
51
96
  export const SELECTION_TOOL_REGISTRY: ReadonlyArray<SelectionToolRegistryEntry> = [
52
97
  { id: "suggestion-review", surfaces: ["selection-tool"], group: "review", precedence: 10 },
53
98
  { id: "comment-thread", surfaces: ["selection-tool"], group: "review", precedence: 20 },
@@ -62,7 +107,8 @@ export const TOOLBAR_CHROME_REGISTRY: ReadonlyArray<ToolbarChromeRegistryEntry>
62
107
  id: "history",
63
108
  surfaces: ["top-toolbar"],
64
109
  group: "history",
65
- presets: ["simple", "advanced", "review"],
110
+ presets: ["simple", "advanced", "review", "workflow"],
111
+ roles: ALL_ROLES,
66
112
  fullPlacement: "inline",
67
113
  compactPlacement: "inline",
68
114
  runtimeBehavior: "always",
@@ -71,61 +117,94 @@ export const TOOLBAR_CHROME_REGISTRY: ReadonlyArray<ToolbarChromeRegistryEntry>
71
117
  id: "text-style-selectors",
72
118
  surfaces: ["top-toolbar"],
73
119
  group: "text",
74
- presets: ["advanced"],
120
+ presets: ["advanced", "workflow"],
121
+ roles: ALL_ROLES,
75
122
  fullPlacement: "inline",
76
123
  compactPlacement: "overflow",
77
124
  runtimeBehavior: "formatting",
125
+ rolePlacement: {
126
+ editor: "inline",
127
+ review: "overflow",
128
+ workflow: "overflow",
129
+ },
78
130
  },
79
131
  {
80
132
  id: "inline-formatting",
81
133
  surfaces: ["top-toolbar"],
82
134
  group: "text",
83
- presets: ["simple", "advanced", "review"],
135
+ presets: ["simple", "advanced", "review", "workflow"],
136
+ roles: ALL_ROLES,
84
137
  fullPlacement: "inline",
85
138
  compactPlacement: "inline",
86
139
  runtimeBehavior: "formatting",
140
+ rolePlacement: {
141
+ editor: "inline",
142
+ review: "overflow",
143
+ workflow: "overflow",
144
+ },
87
145
  },
88
146
  {
89
147
  id: "text-colors",
90
148
  surfaces: ["top-toolbar"],
91
149
  group: "text",
92
150
  presets: ["simple", "advanced"],
151
+ roles: EDITOR_AND_REVIEW,
93
152
  fullPlacement: "inline",
94
153
  compactPlacement: "inline",
95
154
  runtimeBehavior: "formatting",
155
+ rolePlacement: {
156
+ editor: "inline",
157
+ review: "overflow",
158
+ },
96
159
  },
97
160
  {
98
161
  id: "paragraph-alignment",
99
162
  surfaces: ["top-toolbar"],
100
163
  group: "paragraph",
101
164
  presets: ["simple", "advanced"],
165
+ roles: EDITOR_AND_REVIEW,
102
166
  fullPlacement: "inline",
103
167
  compactPlacement: "inline",
104
168
  runtimeBehavior: "formatting",
169
+ rolePlacement: {
170
+ editor: "inline",
171
+ review: "overflow",
172
+ },
105
173
  },
106
174
  {
107
175
  id: "list-actions",
108
176
  surfaces: ["top-toolbar"],
109
177
  group: "paragraph",
110
178
  presets: ["simple", "advanced"],
179
+ roles: EDITOR_AND_REVIEW,
111
180
  fullPlacement: "inline",
112
181
  compactPlacement: "overflow",
113
182
  runtimeBehavior: "formatting",
183
+ rolePlacement: {
184
+ editor: "inline",
185
+ review: "overflow",
186
+ },
114
187
  },
115
188
  {
116
189
  id: "indentation",
117
190
  surfaces: ["top-toolbar"],
118
191
  group: "paragraph",
119
192
  presets: ["simple", "advanced"],
193
+ roles: EDITOR_AND_REVIEW,
120
194
  fullPlacement: "inline",
121
195
  compactPlacement: "overflow",
122
196
  runtimeBehavior: "formatting",
197
+ rolePlacement: {
198
+ editor: "inline",
199
+ review: "overflow",
200
+ },
123
201
  },
124
202
  {
125
203
  id: "list-continuation",
126
204
  surfaces: ["top-toolbar"],
127
205
  group: "paragraph",
128
206
  presets: ["simple", "advanced"],
207
+ roles: EDITOR_ONLY,
129
208
  fullPlacement: "inline",
130
209
  compactPlacement: "overflow",
131
210
  runtimeBehavior: "formatting",
@@ -135,6 +214,7 @@ export const TOOLBAR_CHROME_REGISTRY: ReadonlyArray<ToolbarChromeRegistryEntry>
135
214
  surfaces: ["top-toolbar"],
136
215
  group: "document",
137
216
  presets: ["simple", "advanced"],
217
+ roles: EDITOR_ONLY,
138
218
  fullPlacement: "inline",
139
219
  compactPlacement: "overflow",
140
220
  runtimeBehavior: "structure",
@@ -145,6 +225,7 @@ export const TOOLBAR_CHROME_REGISTRY: ReadonlyArray<ToolbarChromeRegistryEntry>
145
225
  surfaces: ["top-toolbar"],
146
226
  group: "document",
147
227
  presets: ["advanced"],
228
+ roles: EDITOR_ONLY,
148
229
  fullPlacement: "inline",
149
230
  compactPlacement: "overflow",
150
231
  runtimeBehavior: "structure",
@@ -154,7 +235,8 @@ export const TOOLBAR_CHROME_REGISTRY: ReadonlyArray<ToolbarChromeRegistryEntry>
154
235
  id: "scope-status",
155
236
  surfaces: ["top-toolbar"],
156
237
  group: "scope",
157
- presets: ["simple", "advanced", "review"],
238
+ presets: ["simple", "advanced", "review", "workflow"],
239
+ roles: ALL_ROLES,
158
240
  fullPlacement: "inline",
159
241
  compactPlacement: "inline",
160
242
  runtimeBehavior: "always",
@@ -164,7 +246,8 @@ export const TOOLBAR_CHROME_REGISTRY: ReadonlyArray<ToolbarChromeRegistryEntry>
164
246
  id: "story-breadcrumb",
165
247
  surfaces: ["top-toolbar"],
166
248
  group: "scope",
167
- presets: ["simple", "advanced", "review"],
249
+ presets: ["simple", "advanced", "review", "workflow"],
250
+ roles: ALL_ROLES,
168
251
  fullPlacement: "inline",
169
252
  compactPlacement: "inline",
170
253
  runtimeBehavior: "always",
@@ -173,7 +256,8 @@ export const TOOLBAR_CHROME_REGISTRY: ReadonlyArray<ToolbarChromeRegistryEntry>
173
256
  id: "sidebar-toggle",
174
257
  surfaces: ["top-toolbar"],
175
258
  group: "review",
176
- presets: ["simple", "advanced", "review"],
259
+ presets: ["simple", "advanced", "review", "workflow"],
260
+ roles: ALL_ROLES,
177
261
  fullPlacement: "inline",
178
262
  compactPlacement: "inline",
179
263
  runtimeBehavior: "always",
@@ -182,7 +266,11 @@ export const TOOLBAR_CHROME_REGISTRY: ReadonlyArray<ToolbarChromeRegistryEntry>
182
266
  id: "comment",
183
267
  surfaces: ["top-toolbar"],
184
268
  group: "review",
185
- presets: ["simple", "advanced", "review"],
269
+ presets: ["simple", "advanced", "review", "workflow"],
270
+ // Visible in every role, but editor/review roles render it inside the
271
+ // role action region (see ROLE_ACTION_SETS) and the right cluster
272
+ // suppresses it via `isChromeItemOwnedByRoleRegion` to avoid duplication.
273
+ roles: ALL_ROLES,
186
274
  fullPlacement: "inline",
187
275
  compactPlacement: "inline",
188
276
  runtimeBehavior: "comment",
@@ -191,7 +279,9 @@ export const TOOLBAR_CHROME_REGISTRY: ReadonlyArray<ToolbarChromeRegistryEntry>
191
279
  id: "tracked-changes-toggle",
192
280
  surfaces: ["top-toolbar"],
193
281
  group: "review",
194
- presets: ["advanced", "review"],
282
+ presets: ["simple", "advanced", "review", "workflow"],
283
+ // Same ownership rule as `comment` — editor/review role regions own it.
284
+ roles: ALL_ROLES,
195
285
  fullPlacement: "inline",
196
286
  compactPlacement: "inline",
197
287
  runtimeBehavior: "always",
@@ -200,7 +290,8 @@ export const TOOLBAR_CHROME_REGISTRY: ReadonlyArray<ToolbarChromeRegistryEntry>
200
290
  id: "workspace-mode",
201
291
  surfaces: ["top-toolbar"],
202
292
  group: "view",
203
- presets: ["simple", "advanced", "review"],
293
+ presets: ["simple", "advanced", "review", "workflow"],
294
+ roles: ALL_ROLES,
204
295
  fullPlacement: "inline",
205
296
  compactPlacement: "inline",
206
297
  runtimeBehavior: "always",
@@ -209,7 +300,8 @@ export const TOOLBAR_CHROME_REGISTRY: ReadonlyArray<ToolbarChromeRegistryEntry>
209
300
  id: "zoom",
210
301
  surfaces: ["top-toolbar"],
211
302
  group: "view",
212
- presets: ["simple", "advanced", "review"],
303
+ presets: ["simple", "advanced", "review", "workflow"],
304
+ roles: ALL_ROLES,
213
305
  fullPlacement: "inline",
214
306
  compactPlacement: "inline",
215
307
  runtimeBehavior: "always",
@@ -218,7 +310,8 @@ export const TOOLBAR_CHROME_REGISTRY: ReadonlyArray<ToolbarChromeRegistryEntry>
218
310
  id: "health",
219
311
  surfaces: ["top-toolbar"],
220
312
  group: "status",
221
- presets: ["simple", "advanced", "review"],
313
+ presets: ["simple", "advanced", "review", "workflow"],
314
+ roles: ALL_ROLES,
222
315
  fullPlacement: "inline",
223
316
  compactPlacement: "inline",
224
317
  runtimeBehavior: "always",
@@ -227,9 +320,211 @@ export const TOOLBAR_CHROME_REGISTRY: ReadonlyArray<ToolbarChromeRegistryEntry>
227
320
  id: "export",
228
321
  surfaces: ["top-toolbar"],
229
322
  group: "status",
230
- presets: ["simple", "advanced", "review"],
323
+ presets: ["simple", "advanced", "review", "workflow"],
324
+ roles: ALL_ROLES,
325
+ fullPlacement: "inline",
326
+ compactPlacement: "inline",
327
+ runtimeBehavior: "always",
328
+ },
329
+
330
+ // ───── R1: Workflow-role scope posture menu ───────────────────────────
331
+ {
332
+ id: "editor-scope-posture-menu",
333
+ surfaces: ["top-toolbar"],
334
+ group: "scope",
335
+ presets: ["advanced", "review", "workflow"],
336
+ // Scoping/tagging is a workflow-primary action; moved from editor role.
337
+ roles: WORKFLOW_ONLY,
338
+ fullPlacement: "inline",
339
+ compactPlacement: "overflow",
340
+ runtimeBehavior: "always",
341
+ },
342
+
343
+ // ───── R1: Review-role sidebar panel toggles (optional, host-provided) ─
344
+ {
345
+ id: "review-sidebar-tracked-changes",
346
+ surfaces: ["top-toolbar"],
347
+ group: "review-sidebar",
348
+ presets: ["review"],
349
+ roles: REVIEW_ONLY,
350
+ fullPlacement: "inline",
351
+ compactPlacement: "inline",
352
+ // Hidden unless the host provides the sidebar panel callback via
353
+ // hasSidebarPanelAccess in ResolveScopedChromePolicyInput.
354
+ runtimeBehavior: "sidebar-panel",
355
+ },
356
+ {
357
+ id: "review-sidebar-comments",
358
+ surfaces: ["top-toolbar"],
359
+ group: "review-sidebar",
360
+ presets: ["review"],
361
+ roles: REVIEW_ONLY,
362
+ fullPlacement: "inline",
363
+ compactPlacement: "inline",
364
+ runtimeBehavior: "sidebar-panel",
365
+ },
366
+
367
+ // ───── R1: Review-role primaries ──────────────────────────────────────
368
+ {
369
+ id: "review-queue-prev",
370
+ surfaces: ["top-toolbar"],
371
+ group: "review-queue",
372
+ presets: ["review", "workflow"],
373
+ roles: REVIEW_ONLY,
374
+ fullPlacement: "inline",
375
+ compactPlacement: "inline",
376
+ runtimeBehavior: "always",
377
+ },
378
+ {
379
+ id: "review-queue-next",
380
+ surfaces: ["top-toolbar"],
381
+ group: "review-queue",
382
+ presets: ["review", "workflow"],
383
+ roles: REVIEW_ONLY,
384
+ fullPlacement: "inline",
385
+ compactPlacement: "inline",
386
+ runtimeBehavior: "always",
387
+ },
388
+ {
389
+ id: "review-queue-counts",
390
+ surfaces: ["top-toolbar"],
391
+ group: "review-queue",
392
+ presets: ["review"],
393
+ roles: REVIEW_ONLY,
394
+ fullPlacement: "inline",
395
+ compactPlacement: "overflow",
396
+ runtimeBehavior: "always",
397
+ },
398
+ {
399
+ id: "review-queue-active-label",
400
+ surfaces: ["top-toolbar"],
401
+ group: "review-queue",
402
+ presets: ["review"],
403
+ roles: REVIEW_ONLY,
404
+ fullPlacement: "inline",
405
+ compactPlacement: "overflow",
406
+ runtimeBehavior: "always",
407
+ },
408
+ {
409
+ id: "review-accept",
410
+ surfaces: ["top-toolbar"],
411
+ group: "review-action",
412
+ presets: ["review"],
413
+ roles: REVIEW_ONLY,
231
414
  fullPlacement: "inline",
232
415
  compactPlacement: "inline",
233
416
  runtimeBehavior: "always",
234
417
  },
418
+ {
419
+ id: "review-reject",
420
+ surfaces: ["top-toolbar"],
421
+ group: "review-action",
422
+ presets: ["review"],
423
+ roles: REVIEW_ONLY,
424
+ fullPlacement: "inline",
425
+ compactPlacement: "inline",
426
+ runtimeBehavior: "always",
427
+ },
428
+ {
429
+ id: "review-accept-all",
430
+ surfaces: ["top-toolbar"],
431
+ group: "review-action",
432
+ presets: ["review"],
433
+ roles: REVIEW_ONLY,
434
+ fullPlacement: "inline",
435
+ compactPlacement: "overflow",
436
+ runtimeBehavior: "always",
437
+ },
438
+ {
439
+ id: "review-reject-all",
440
+ surfaces: ["top-toolbar"],
441
+ group: "review-action",
442
+ presets: ["review"],
443
+ roles: REVIEW_ONLY,
444
+ fullPlacement: "inline",
445
+ compactPlacement: "overflow",
446
+ runtimeBehavior: "always",
447
+ },
448
+ {
449
+ id: "review-markup-mode",
450
+ surfaces: ["top-toolbar"],
451
+ group: "review-action",
452
+ presets: ["review"],
453
+ roles: REVIEW_ONLY,
454
+ fullPlacement: "inline",
455
+ compactPlacement: "inline",
456
+ runtimeBehavior: "always",
457
+ },
458
+
459
+ // ───── R1: Workflow-role primaries ────────────────────────────────────
460
+ {
461
+ id: "workflow-prev",
462
+ surfaces: ["top-toolbar"],
463
+ group: "workflow-queue",
464
+ presets: ["workflow"],
465
+ roles: WORKFLOW_ONLY,
466
+ fullPlacement: "inline",
467
+ compactPlacement: "inline",
468
+ runtimeBehavior: "always",
469
+ },
470
+ {
471
+ id: "workflow-next",
472
+ surfaces: ["top-toolbar"],
473
+ group: "workflow-queue",
474
+ presets: ["workflow"],
475
+ roles: WORKFLOW_ONLY,
476
+ fullPlacement: "inline",
477
+ compactPlacement: "inline",
478
+ runtimeBehavior: "always",
479
+ },
480
+ {
481
+ id: "workflow-mark-complete",
482
+ surfaces: ["top-toolbar"],
483
+ group: "workflow-action",
484
+ presets: ["workflow"],
485
+ roles: WORKFLOW_ONLY,
486
+ fullPlacement: "inline",
487
+ compactPlacement: "inline",
488
+ runtimeBehavior: "always",
489
+ },
490
+ {
491
+ id: "workflow-claim",
492
+ surfaces: ["top-toolbar"],
493
+ group: "workflow-action",
494
+ presets: ["workflow"],
495
+ roles: WORKFLOW_ONLY,
496
+ fullPlacement: "inline",
497
+ compactPlacement: "inline",
498
+ runtimeBehavior: "always",
499
+ },
500
+ {
501
+ id: "workflow-skip",
502
+ surfaces: ["top-toolbar"],
503
+ group: "workflow-action",
504
+ presets: ["workflow"],
505
+ roles: WORKFLOW_ONLY,
506
+ fullPlacement: "inline",
507
+ compactPlacement: "overflow",
508
+ runtimeBehavior: "always",
509
+ },
510
+ {
511
+ id: "workflow-mark-blocked",
512
+ surfaces: ["top-toolbar"],
513
+ group: "workflow-action",
514
+ presets: ["workflow"],
515
+ roles: WORKFLOW_ONLY,
516
+ fullPlacement: "inline",
517
+ compactPlacement: "overflow",
518
+ runtimeBehavior: "always",
519
+ },
520
+ {
521
+ id: "workflow-jump-to-scope",
522
+ surfaces: ["top-toolbar"],
523
+ group: "workflow-action",
524
+ presets: ["workflow"],
525
+ roles: WORKFLOW_ONLY,
526
+ fullPlacement: "inline",
527
+ compactPlacement: "overflow",
528
+ runtimeBehavior: "always",
529
+ },
235
530
  ];
@@ -1,5 +1,6 @@
1
1
  import type {
2
2
  ActiveListContext,
3
+ EditorRole,
3
4
  InteractionGuardSnapshot,
4
5
  WorkflowScopeSnapshot,
5
6
  WordReviewEditorChromePreset,
@@ -10,6 +11,7 @@ import {
10
11
  type ToolbarChromeItemId,
11
12
  type ToolbarChromePlacement,
12
13
  } from "./chrome-registry";
14
+ import { ROLE_ACTION_SETS } from "../../ui-tailwind/chrome/role-action-sets";
13
15
  import type { SelectionToolKind } from "./selection-tool-types";
14
16
 
15
17
  export interface ToolbarChromeItemPolicy {
@@ -36,6 +38,20 @@ export interface ResolveScopedChromePolicyInput {
36
38
  interactionGuardSnapshot?: InteractionGuardSnapshot;
37
39
  workflowScopeSnapshot?: WorkflowScopeSnapshot | null;
38
40
  activeListContext?: ActiveListContext | null;
41
+ /**
42
+ * Active chrome role (spec §6.4). When supplied, the policy filters
43
+ * registry entries whose `roles` array excludes the active role. When
44
+ * omitted, the preset filter alone drives visibility (back-compat for
45
+ * callers that haven't adopted the role model yet).
46
+ */
47
+ role?: EditorRole;
48
+ /**
49
+ * Whether the host has wired sidebar-panel callbacks
50
+ * (`onReviewSidebarTrackedChanges` / `onReviewSidebarComments`).
51
+ * Defaults to `false` — sidebar panel buttons are hidden unless the host
52
+ * explicitly opts in (typically the harness).
53
+ */
54
+ hasSidebarPanelAccess?: boolean;
39
55
  }
40
56
 
41
57
  export function resolveScopedChromePolicy(
@@ -62,6 +78,13 @@ export function resolveScopedChromePolicy(
62
78
  TOOLBAR_CHROME_REGISTRY.map((entry) => {
63
79
  let visible = entry.presets.includes(input.preset);
64
80
 
81
+ // Role filter — apply only when the caller supplied a role so existing
82
+ // host apps that haven't adopted the role model keep the pre-R1
83
+ // behavior (preset + capability only).
84
+ if (visible && input.role !== undefined) {
85
+ visible = entry.roles.includes(input.role);
86
+ }
87
+
65
88
  if (visible) {
66
89
  switch (entry.runtimeBehavior) {
67
90
  case "formatting":
@@ -73,6 +96,9 @@ export function resolveScopedChromePolicy(
73
96
  case "comment":
74
97
  visible = canAddComment;
75
98
  break;
99
+ case "sidebar-panel":
100
+ visible = Boolean(input.hasSidebarPanelAccess);
101
+ break;
76
102
  default:
77
103
  visible = true;
78
104
  break;
@@ -93,8 +119,12 @@ export function resolveScopedChromePolicy(
93
119
  visible = !hasScopedContext;
94
120
  }
95
121
 
122
+ // Resolve placement: when the entry overrides placement for the
123
+ // active role, that wins over the preset-density default.
124
+ const fullPlacement =
125
+ (input.role && entry.rolePlacement?.[input.role]) ?? entry.fullPlacement;
96
126
  const placement = visible
97
- ? (input.compactMode ? entry.compactPlacement : entry.fullPlacement)
127
+ ? (input.compactMode ? entry.compactPlacement : fullPlacement)
98
128
  : "hidden";
99
129
  const enabled =
100
130
  visible &&
@@ -133,6 +163,24 @@ export function isToolbarChromeItemVisible(
133
163
  return policy.toolbar[itemId]?.visible ?? false;
134
164
  }
135
165
 
166
+ /**
167
+ * Returns true when the given chrome item is owned by the active role's
168
+ * inline role-action region (i.e. it appears in `ROLE_ACTION_SETS[role]`).
169
+ * The right-cluster render path uses this to suppress its own copy so we
170
+ * don't render the same button twice.
171
+ *
172
+ * When `role` is undefined (legacy host, no role model adopted) this
173
+ * returns `false` — the right cluster keeps ownership per the pre-R1
174
+ * layout.
175
+ */
176
+ export function isChromeItemOwnedByRoleRegion(
177
+ itemId: ToolbarChromeItemId,
178
+ role: EditorRole | undefined,
179
+ ): boolean {
180
+ if (!role) return false;
181
+ return ROLE_ACTION_SETS[role].includes(itemId);
182
+ }
183
+
136
184
  export function getToolbarChromePlacement(
137
185
  policy: ScopedChromePolicy,
138
186
  itemId: ToolbarChromeItemId,
@@ -59,6 +59,16 @@ export interface FormattingInlineSelectionToolModel extends BaseSelectionToolMod
59
59
  italicActive: boolean;
60
60
  underlineActive: boolean;
61
61
  canAddComment: boolean;
62
+ /**
63
+ * One-click "apply" color for the text-color affordance. R2.5 plumbs
64
+ * the current `formattingState.textColor` (or the user's most recent
65
+ * pick) here so the mini-toolbar reflects the active color instead of
66
+ * a hardcoded blue. Consumers fall back to a fixed default when
67
+ * absent.
68
+ */
69
+ textColorDefault?: string;
70
+ /** Matching one-click highlight color. See `textColorDefault`. */
71
+ highlightColorDefault?: string;
62
72
  }
63
73
 
64
74
  export interface SuggestionReviewSelectionToolModel extends BaseSelectionToolModel {
@@ -7,12 +7,15 @@ import type {
7
7
 
8
8
  export function resolveChromePreset(
9
9
  chromePreset: WordReviewEditorProps["chromePreset"],
10
- _reviewMode: WordReviewEditorProps["reviewMode"] = "review",
10
+ reviewMode: WordReviewEditorProps["reviewMode"] = "review",
11
11
  ): WordReviewEditorChromePreset {
12
12
  if (chromePreset) {
13
13
  return chromePreset;
14
14
  }
15
- return "advanced";
15
+ // When the host does not set an explicit preset, pick one from the
16
+ // review-mode signal so review sessions get the review strip inline
17
+ // with the toolbar (rather than the density default "advanced").
18
+ return reviewMode === "review" ? "review" : "advanced";
16
19
  }
17
20
 
18
21
  export function resolveChromePresetOptions(
@@ -40,6 +43,14 @@ export function resolveChromePresetOptions(
40
43
  showSectionTagAction: true,
41
44
  showReviewRail: true,
42
45
  },
46
+ workflow: {
47
+ // Workflow role consolidates prev/next + mark-section into the top
48
+ // toolbar via the role-action region, so the separate queue bar
49
+ // strip is suppressed.
50
+ showReviewQueueBar: false,
51
+ showSectionTagAction: false,
52
+ showReviewRail: true,
53
+ },
43
54
  };
44
55
 
45
56
  return {
@@ -95,6 +106,16 @@ export function resolveChromeVisibilityForPreset(input: {
95
106
  statusBar: true,
96
107
  reviewRail: options.showReviewRail,
97
108
  },
109
+ workflow: {
110
+ toolbar: true,
111
+ alerts: true,
112
+ selectionOverlay: true,
113
+ contextToolbars: true,
114
+ contextAnalytics: true,
115
+ pageChrome: true,
116
+ statusBar: true,
117
+ reviewRail: options.showReviewRail,
118
+ },
98
119
  };
99
120
 
100
121
  return {
@@ -1,15 +1,15 @@
1
1
  import React from "react";
2
2
 
3
- import { BookmarkPlus, ChevronLeft, ChevronRight, MessageSquareText, Rows3 } from "lucide-react";
3
+ import { ChevronLeft, ChevronRight, MessageSquareText, Rows3 } from "lucide-react";
4
4
 
5
5
  import type { ReviewQueueSnapshot } from "../../api/public-types";
6
6
  import { preserveEditorSelectionMouseDown } from "../../ui/headless/preserve-editor-selection";
7
7
 
8
+ /** @deprecated Use the role action region (TwRoleActionRegion) for queue navigation. */
8
9
  export interface TwReviewQueueBarProps {
9
10
  queue: ReviewQueueSnapshot;
10
11
  onPrevious?: () => void;
11
12
  onNext?: () => void;
12
- onMarkSection?: () => void;
13
13
  }
14
14
 
15
15
  const buttonClass =
@@ -47,18 +47,6 @@ export function TwReviewQueueBar(props: TwReviewQueueBarProps) {
47
47
  Next
48
48
  <ChevronRight className="h-3.5 w-3.5" />
49
49
  </button>
50
- {props.onMarkSection ? (
51
- <button
52
- type="button"
53
- aria-label="Mark section for review"
54
- className={buttonClass}
55
- onMouseDown={preserveEditorSelectionMouseDown}
56
- onClick={props.onMarkSection}
57
- >
58
- <BookmarkPlus className="h-3.5 w-3.5" />
59
- Mark section
60
- </button>
61
- ) : null}
62
50
  <div className="ml-auto flex flex-wrap items-center gap-2 text-xs text-secondary">
63
51
  <span className="inline-flex items-center gap-1 rounded-full bg-canvas px-2.5 py-1 font-medium text-primary">
64
52
  <MessageSquareText className="h-3.5 w-3.5 text-comment" />