@beyondwork/docx-react-component 1.0.36 → 1.0.38

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 (107) hide show
  1. package/README.md +103 -13
  2. package/package.json +1 -1
  3. package/src/api/package-version.ts +13 -0
  4. package/src/api/public-types.ts +402 -1
  5. package/src/core/commands/index.ts +18 -1
  6. package/src/core/commands/section-layout-commands.ts +58 -0
  7. package/src/core/commands/table-grid.ts +431 -0
  8. package/src/core/commands/table-structure-commands.ts +815 -55
  9. package/src/core/selection/mapping.ts +6 -0
  10. package/src/io/docx-session.ts +24 -9
  11. package/src/io/export/build-app-properties-xml.ts +88 -0
  12. package/src/io/export/serialize-comments.ts +6 -1
  13. package/src/io/export/serialize-footnotes.ts +10 -9
  14. package/src/io/export/serialize-headers-footers.ts +11 -10
  15. package/src/io/export/serialize-main-document.ts +328 -50
  16. package/src/io/export/serialize-numbering.ts +114 -24
  17. package/src/io/export/serialize-tables.ts +87 -11
  18. package/src/io/export/table-properties-xml.ts +174 -20
  19. package/src/io/export/twip.ts +66 -0
  20. package/src/io/normalize/normalize-text.ts +20 -0
  21. package/src/io/ooxml/parse-footnotes.ts +62 -1
  22. package/src/io/ooxml/parse-headers-footers.ts +62 -1
  23. package/src/io/ooxml/parse-main-document.ts +158 -1
  24. package/src/io/ooxml/parse-tables.ts +249 -0
  25. package/src/legal/bookmarks.ts +78 -0
  26. package/src/model/canonical-document.ts +45 -0
  27. package/src/review/store/scope-tag-diff.ts +130 -0
  28. package/src/runtime/document-layout.ts +4 -2
  29. package/src/runtime/document-navigation.ts +2 -306
  30. package/src/runtime/document-runtime.ts +287 -11
  31. package/src/runtime/layout/default-page-format.ts +96 -0
  32. package/src/runtime/layout/docx-font-loader.ts +143 -0
  33. package/src/runtime/layout/index.ts +233 -0
  34. package/src/runtime/layout/inert-layout-facet.ts +59 -0
  35. package/src/runtime/layout/layout-engine-instance.ts +628 -0
  36. package/src/runtime/layout/layout-invalidation.ts +257 -0
  37. package/src/runtime/layout/layout-measurement-provider.ts +175 -0
  38. package/src/runtime/layout/margin-preset-catalog.ts +178 -0
  39. package/src/runtime/layout/measurement-backend-canvas.ts +307 -0
  40. package/src/runtime/layout/measurement-backend-empirical.ts +208 -0
  41. package/src/runtime/layout/page-format-catalog.ts +233 -0
  42. package/src/runtime/layout/page-fragment-mapper.ts +179 -0
  43. package/src/runtime/layout/page-graph.ts +452 -0
  44. package/src/runtime/layout/page-layout-snapshot-adapter.ts +70 -0
  45. package/src/runtime/layout/page-story-resolver.ts +195 -0
  46. package/src/runtime/layout/paginated-layout-engine.ts +921 -0
  47. package/src/runtime/layout/project-block-fragments.ts +91 -0
  48. package/src/runtime/layout/public-facet.ts +1398 -0
  49. package/src/runtime/layout/resolved-formatting-document.ts +317 -0
  50. package/src/runtime/layout/resolved-formatting-state.ts +430 -0
  51. package/src/runtime/layout/table-render-plan.ts +229 -0
  52. package/src/runtime/render/block-fragment-projection.ts +35 -0
  53. package/src/runtime/render/decoration-resolver.ts +189 -0
  54. package/src/runtime/render/index.ts +57 -0
  55. package/src/runtime/render/pending-op-delta-reader.ts +129 -0
  56. package/src/runtime/render/render-frame-types.ts +317 -0
  57. package/src/runtime/render/render-kernel.ts +755 -0
  58. package/src/runtime/scope-tag-registry.ts +95 -0
  59. package/src/runtime/surface-projection.ts +1 -0
  60. package/src/runtime/text-ack-range.ts +49 -0
  61. package/src/runtime/view-state.ts +67 -0
  62. package/src/runtime/workflow-markup.ts +1 -5
  63. package/src/runtime/workflow-rail-segments.ts +280 -0
  64. package/src/ui/WordReviewEditor.tsx +99 -15
  65. package/src/ui/editor-runtime-boundary.ts +10 -1
  66. package/src/ui/editor-shell-view.tsx +6 -0
  67. package/src/ui/editor-surface-controller.tsx +3 -0
  68. package/src/ui/headless/chrome-registry.ts +501 -0
  69. package/src/ui/headless/scoped-chrome-policy.ts +183 -0
  70. package/src/ui/headless/selection-tool-context.ts +2 -0
  71. package/src/ui/headless/selection-tool-resolver.ts +36 -17
  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/role-action-sets.ts +74 -0
  75. package/src/ui-tailwind/chrome/tw-detach-handle.tsx +147 -0
  76. package/src/ui-tailwind/chrome/tw-selection-anchor-resolver.ts +163 -0
  77. package/src/ui-tailwind/chrome/tw-selection-tool-host.tsx +57 -92
  78. package/src/ui-tailwind/chrome/tw-selection-tool-placement.ts +149 -0
  79. package/src/ui-tailwind/chrome/tw-selection-toolbar.tsx +15 -4
  80. package/src/ui-tailwind/chrome/tw-table-context-toolbar.tsx +274 -138
  81. package/src/ui-tailwind/chrome-overlay/chrome-overlay-projector.ts +90 -0
  82. package/src/ui-tailwind/chrome-overlay/index.ts +22 -0
  83. package/src/ui-tailwind/chrome-overlay/tw-chrome-overlay.tsx +86 -0
  84. package/src/ui-tailwind/chrome-overlay/tw-scope-rail-layer.tsx +178 -0
  85. package/src/ui-tailwind/chrome-overlay/tw-workspace-view-switcher.tsx +95 -0
  86. package/src/ui-tailwind/editor-surface/fast-text-edit-lane.ts +337 -0
  87. package/src/ui-tailwind/editor-surface/local-edit-session-state.ts +100 -0
  88. package/src/ui-tailwind/editor-surface/perf-probe.ts +27 -1
  89. package/src/ui-tailwind/editor-surface/pm-command-bridge.ts +20 -2
  90. package/src/ui-tailwind/editor-surface/pm-decorations.ts +93 -23
  91. package/src/ui-tailwind/editor-surface/predicted-position-map.ts +78 -0
  92. package/src/ui-tailwind/editor-surface/predicted-tag-preflight.ts +63 -0
  93. package/src/ui-tailwind/editor-surface/predicted-tx-gate.ts +39 -0
  94. package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +176 -6
  95. package/src/ui-tailwind/index.ts +33 -0
  96. package/src/ui-tailwind/review/tw-comment-sidebar.tsx +2 -2
  97. package/src/ui-tailwind/review/tw-rail-card.tsx +150 -0
  98. package/src/ui-tailwind/review/tw-review-rail-footer.tsx +52 -0
  99. package/src/ui-tailwind/review/tw-review-rail.tsx +166 -11
  100. package/src/ui-tailwind/review/tw-workflow-tab.tsx +108 -0
  101. package/src/ui-tailwind/theme/editor-theme.css +505 -144
  102. package/src/ui-tailwind/toolbar/tw-role-action-region.tsx +559 -0
  103. package/src/ui-tailwind/toolbar/tw-scope-posture-menu.tsx +182 -0
  104. package/src/ui-tailwind/toolbar/tw-shell-header.tsx +162 -0
  105. package/src/ui-tailwind/toolbar/tw-toolbar-icon-button.tsx +2 -2
  106. package/src/ui-tailwind/toolbar/tw-toolbar.tsx +304 -166
  107. package/src/ui-tailwind/tw-review-workspace.tsx +163 -2
@@ -76,26 +76,90 @@
76
76
  --color-page-shadow: rgba(16, 24, 40, 0.06);
77
77
  --color-page-border: rgba(0, 0, 0, 0.04);
78
78
  --color-page-bg: #ffffff;
79
+ --color-page-ruler: color-mix(in srgb, var(--color-border) 65%, transparent);
80
+
81
+ /*
82
+ * ─── Radius tokens (balanced 10 / 8 / 4 / 2) ───
83
+ *
84
+ * Keeps the editor warm but drops the puffy rounded-xl aesthetic. DESIGN.md
85
+ * §13 calls for one coherent shape language; these five named tokens define
86
+ * it, and the rounded-* Tailwind scale is swept into them so existing
87
+ * component markup inherits the new language without a file-by-file rewrite:
88
+ * - rounded-xs → 2px (page / canvas)
89
+ * - rounded-sm → 4px (pills, tags, small controls)
90
+ * - rounded-md → 4px (icon buttons, tooltip content, menu items)
91
+ * - rounded-lg → 8px (cards)
92
+ * - rounded-xl → 10px (panels, rails)
93
+ * - rounded-2xl → 12px (dialogs, overlays)
94
+ */
95
+ --radius-panel: 10px;
96
+ --radius-card: 8px;
97
+ --radius-control: 4px;
98
+ --radius-page: 2px;
99
+ --radius-pill: 999px;
100
+ --radius-xs: 2px;
101
+ --radius-sm: 4px;
102
+ --radius-md: 4px;
103
+ --radius-lg: 8px;
104
+ --radius-xl: 10px;
105
+ --radius-2xl: 12px;
106
+
107
+ /*
108
+ * ─── Shadow tokens ───
109
+ *
110
+ * Three tiers used across the product chrome. Dark-mode variants land in
111
+ * the .dark block below so the transform lives in one place.
112
+ */
113
+ --shadow-hairline: 0 0 0 1px var(--color-border);
114
+ --shadow-soft: 0 4px 14px -10px rgba(16, 24, 40, 0.14);
115
+ --shadow-float: 0 12px 28px -20px rgba(16, 24, 40, 0.18);
116
+
117
+ /*
118
+ * Tailwind shadow-sm / shadow-md / shadow-lg / shadow-xl follow the runtime
119
+ * three-tier scale. Keeping them consistent with --shadow-soft / --shadow-
120
+ * float lets existing `shadow-lg` usages (dropdowns, popovers) match the
121
+ * calmer DESIGN.md §13.3 target without per-file edits.
122
+ */
123
+ --shadow-sm: 0 1px 2px rgba(16, 24, 40, 0.05);
124
+ --shadow-md: 0 4px 14px -10px rgba(16, 24, 40, 0.14);
125
+ --shadow-lg: 0 8px 22px -16px rgba(16, 24, 40, 0.18);
126
+ --shadow-xl: 0 16px 32px -24px rgba(16, 24, 40, 0.22);
127
+
128
+ /*
129
+ * ─── Motion scale ───
130
+ *
131
+ * Consumed by harness-fade-in / harness-slide-in-right, the review rail
132
+ * tabs, and the scope rail layer. Stays under DESIGN.md §13.4 ceiling.
133
+ */
134
+ --motion-fast: 120ms;
135
+ --motion-default: 160ms;
136
+ --motion-slow: 220ms;
79
137
  }
80
138
 
81
- /* ─── Dark mode overrides ─── */
139
+ /*
140
+ * ─── Dark mode overrides ───
141
+ *
142
+ * Retuned against DESIGN.md §11.4 so the shell reads as graphite + forest-ink
143
+ * instead of near-black. Accents drop from neon to the documented forest
144
+ * green so they do not glow against the deep slate canvas.
145
+ */
82
146
  .dark {
83
- --color-surface: #1e1e1e;
84
- --color-surface-hover: #2a2a2a;
85
- --color-surface-active: #333333;
86
- --color-subtle: #252525;
87
- --color-canvas: #191919;
88
- --color-shell-bg: #171514;
89
- --color-card: #23211f;
90
- --color-editor-frame: #1d1b19;
91
- --color-sidebar-tint: rgba(29, 27, 25, 0.94);
92
-
93
- --color-primary: #e4e4e4;
94
- --color-secondary: #a0a0a0;
95
- --color-tertiary: #6e6e6e;
96
-
97
- --color-accent: #5edec1;
98
- --color-accent-soft: rgba(94, 222, 193, 0.14);
147
+ --color-surface: #182420;
148
+ --color-surface-hover: #20302a;
149
+ --color-surface-active: #2a3d34;
150
+ --color-subtle: #1a2721;
151
+ --color-canvas: #1B2620;
152
+ --color-shell-bg: #0E1411;
153
+ --color-card: #20302a;
154
+ --color-editor-frame: #131B17;
155
+ --color-sidebar-tint: rgba(22, 33, 27, 0.94);
156
+
157
+ --color-primary: #EFF7F2;
158
+ --color-secondary: #A5B7AC;
159
+ --color-tertiary: #7F9187;
160
+
161
+ --color-accent: #53B487;
162
+ --color-accent-soft: rgba(83, 180, 135, 0.16);
99
163
  --color-workflow: #c7b6ff;
100
164
  --color-workflow-soft: rgba(199, 182, 255, 0.16);
101
165
 
@@ -114,15 +178,19 @@
114
178
  --color-success: #8fd2a9;
115
179
  --color-success-soft: rgba(143, 210, 169, 0.18);
116
180
 
117
- --color-border: rgba(255, 255, 255, 0.08);
118
- --color-border-strong: rgba(255, 255, 255, 0.14);
181
+ --color-border: rgba(255, 255, 255, 0.09);
182
+ --color-border-strong: rgba(255, 255, 255, 0.16);
119
183
 
120
184
  --color-shadow: rgba(0, 0, 0, 0.32);
121
185
  --color-shadow-strong: rgba(0, 0, 0, 0.48);
122
186
 
123
- --color-page-shadow: rgba(0, 0, 0, 0.18);
187
+ --color-page-shadow: rgba(0, 0, 0, 0.22);
124
188
  --color-page-border: rgba(255, 255, 255, 0.06);
125
- --color-page-bg: #1e1e1e;
189
+ --color-page-bg: #1B2620;
190
+ --color-page-ruler: color-mix(in srgb, var(--color-border) 70%, transparent);
191
+
192
+ --shadow-soft: 0 6px 18px -10px rgba(0, 0, 0, 0.55);
193
+ --shadow-float: 0 18px 40px -22px rgba(0, 0, 0, 0.7);
126
194
  }
127
195
 
128
196
  /*
@@ -189,7 +257,7 @@
189
257
  .wre-page-chrome {
190
258
  background: var(--color-page-bg);
191
259
  border: 1px solid var(--color-page-border);
192
- border-radius: 2px;
260
+ border-radius: var(--radius-page);
193
261
  box-shadow: 0 8px 24px -20px var(--color-page-shadow);
194
262
  }
195
263
 
@@ -203,7 +271,7 @@
203
271
  min-height: 100%;
204
272
  background: var(--color-page-bg);
205
273
  border: 1px solid var(--color-page-border);
206
- border-radius: 2px;
274
+ border-radius: var(--radius-page);
207
275
  box-shadow: 0 6px 18px -14px var(--color-page-shadow);
208
276
  -webkit-font-smoothing: antialiased;
209
277
  -moz-osx-font-smoothing: grayscale;
@@ -222,6 +290,38 @@
222
290
  box-sizing: border-box;
223
291
  }
224
292
 
293
+ /*
294
+ * ─── Global selection + scrollbar rules ───
295
+ *
296
+ * Every host that imports this theme inherits the same scrollbar + selection
297
+ * treatment. Keep these rules here so harness and consumer apps do not have
298
+ * to redeclare them.
299
+ */
300
+ ::selection {
301
+ background: color-mix(in srgb, var(--color-accent) 22%, transparent);
302
+ }
303
+
304
+ ::-webkit-scrollbar {
305
+ width: 10px;
306
+ height: 10px;
307
+ }
308
+
309
+ ::-webkit-scrollbar-track {
310
+ background: transparent;
311
+ }
312
+
313
+ ::-webkit-scrollbar-thumb {
314
+ background: color-mix(in srgb, var(--color-secondary) 18%, transparent);
315
+ border: 2px solid transparent;
316
+ border-radius: var(--radius-pill);
317
+ background-clip: padding-box;
318
+ }
319
+
320
+ ::-webkit-scrollbar-thumb:hover {
321
+ background: color-mix(in srgb, var(--color-secondary) 28%, transparent);
322
+ background-clip: padding-box;
323
+ }
324
+
225
325
  /* ─── Reduced motion ─── */
226
326
  [data-reduced-motion="true"] {
227
327
  scroll-behavior: auto;
@@ -248,9 +348,7 @@
248
348
  /* ─── Focus ring utility ─── */
249
349
  .wre-focus-ring:focus-visible {
250
350
  outline: none;
251
- box-shadow:
252
- 0 0 0 2px var(--color-canvas),
253
- 0 0 0 4px var(--color-accent);
351
+ box-shadow: 0 0 0 2px color-mix(in srgb, var(--color-accent) 60%, transparent);
254
352
  }
255
353
 
256
354
  /* ─── ProseMirror surface ─── */
@@ -271,171 +369,198 @@
271
369
  outline: none;
272
370
  }
273
371
 
372
+ /*
373
+ * ─── Workflow inline text emphasis ───
374
+ *
375
+ * Since R3a the workflow scope rail + flat block tint are painted on the
376
+ * ChromeOverlay plane (see src/ui-tailwind/chrome-overlay/). PM
377
+ * decorations retain ONLY inline text emphasis for postures that carry
378
+ * unique per-text signals (candidate = dashed underline, blocked-import =
379
+ * wavy underline, active = thin outline). The rounded in-text background
380
+ * boxes that previously wrapped every run are gone — the overlay's flat
381
+ * tint handles that signal.
382
+ */
274
383
  .prosemirror-surface .ProseMirror .wre-workflow-inline {
275
- border-radius: 0.25rem;
276
- box-shadow: inset 0 0 0 1px transparent;
277
384
  -webkit-box-decoration-break: clone;
278
385
  box-decoration-break: clone;
279
386
  }
280
387
 
281
- .prosemirror-surface .ProseMirror .wre-workflow-inline-edit {
282
- background: color-mix(in srgb, var(--color-accent) 10%, transparent);
283
- box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--color-accent) 18%, transparent);
284
- }
285
-
286
- .prosemirror-surface .ProseMirror .wre-workflow-inline-suggest {
287
- background: color-mix(in srgb, var(--color-warning) 12%, transparent);
288
- box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--color-warning) 20%, transparent);
289
- }
290
-
291
- .prosemirror-surface .ProseMirror .wre-workflow-inline-comment {
292
- background: color-mix(in srgb, var(--color-insert) 10%, transparent);
293
- box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--color-insert) 18%, transparent);
294
- }
295
-
296
- .prosemirror-surface .ProseMirror .wre-workflow-inline-view {
297
- background: color-mix(in srgb, var(--color-secondary) 7%, transparent);
298
- box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--color-secondary) 14%, transparent);
299
- }
300
-
301
388
  .prosemirror-surface .ProseMirror .wre-workflow-inline-candidate {
302
- background: color-mix(in srgb, var(--color-warning) 6%, transparent);
303
- box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--color-warning) 18%, transparent);
304
389
  text-decoration: underline;
305
390
  text-decoration-style: dashed;
306
391
  text-decoration-color: color-mix(in srgb, var(--color-warning) 55%, transparent);
307
392
  text-underline-offset: 0.18em;
308
393
  }
309
394
 
310
- .prosemirror-surface .ProseMirror .wre-workflow-inline-metadata {
311
- background: color-mix(in srgb, var(--wre-workflow-metadata-color, var(--color-accent)) 12%, transparent);
312
- box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--wre-workflow-metadata-color, var(--color-accent)) 22%, transparent);
313
- }
314
-
315
- .prosemirror-surface .ProseMirror .wre-workflow-inline-preserve-only {
316
- background: color-mix(in srgb, var(--color-danger) 7%, transparent);
317
- box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--color-danger) 18%, transparent);
318
- }
319
-
320
395
  .prosemirror-surface .ProseMirror .wre-workflow-inline-blocked-import {
321
- background: color-mix(in srgb, var(--color-danger) 8%, transparent);
322
- box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--color-danger) 24%, transparent);
323
396
  text-decoration: underline;
324
397
  text-decoration-style: wavy;
325
398
  text-decoration-color: color-mix(in srgb, var(--color-danger) 70%, transparent);
326
399
  text-underline-offset: 0.18em;
327
400
  }
328
401
 
402
+ /*
403
+ * Locked zone marker for inline runs: a subtle dotted right edge so the
404
+ * reader can tell where the locked range ends when the gutter label scrolls
405
+ * out of view. The overlay's flat tint carries the primary signal.
406
+ */
329
407
  .prosemirror-surface .ProseMirror .wre-workflow-inline-locked-zone {
330
- border-radius: 0.35rem;
408
+ box-shadow: inset -1px 0 0 color-mix(in srgb, var(--color-danger) 35%, transparent);
331
409
  }
332
410
 
333
- .prosemirror-surface .ProseMirror .wre-workflow-rail {
334
- position: relative;
335
- padding-left: 0.875rem;
336
- border-radius: 0.25rem;
411
+ .prosemirror-surface .ProseMirror .wre-workflow-inline-active {
412
+ box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--color-accent) 30%, transparent);
337
413
  }
338
414
 
339
- .prosemirror-surface .ProseMirror .wre-workflow-rail::before {
340
- content: "";
341
- position: absolute;
342
- left: 0;
343
- top: 0.2rem;
344
- bottom: 0.2rem;
345
- width: 0.3rem;
346
- border-radius: 999px;
347
- background: var(--wre-workflow-rail-color, var(--color-border-strong));
415
+ /*
416
+ * ─── ChromeOverlay: scope rail layer ───
417
+ *
418
+ * The overlay sits above PM and paints the flat block-tint + gutter labels
419
+ * that used to be inline PM decorations. Positions come from the render
420
+ * kernel's anchor index, not DOM rects.
421
+ */
422
+ .wre-scope-rail-layer {
423
+ pointer-events: none;
348
424
  }
349
425
 
350
- .prosemirror-surface .ProseMirror .wre-workflow-rail-active::before {
351
- width: 0.3125rem;
352
- opacity: 1;
353
- box-shadow: 0 0 0 1px color-mix(in oklab, var(--wre-workflow-rail-color, var(--color-border-strong)) 30%, transparent);
426
+ .wre-scope-rail-tint {
427
+ position: absolute;
428
+ border-radius: 0.35rem;
429
+ pointer-events: none;
430
+ z-index: 0;
431
+ transition: background 140ms ease-out;
354
432
  }
355
433
 
356
- .prosemirror-surface .ProseMirror .wre-workflow-inline-active {
357
- box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--wre-workflow-rail-color, var(--color-border-strong)) 28%, transparent);
434
+ .wre-scope-rail-tint-accent {
435
+ background: color-mix(in srgb, var(--color-accent) 12%, transparent);
358
436
  }
359
-
360
- .prosemirror-surface .ProseMirror .wre-workflow-inline-zone {
361
- border-radius: 0.35rem;
362
- box-shadow:
363
- inset 0 0 0 1px color-mix(in srgb, var(--wre-workflow-rail-color, var(--color-accent)) 18%, transparent),
364
- inset 0 0 0 999px color-mix(in srgb, var(--wre-workflow-rail-color, var(--color-accent)) 8%, transparent);
437
+ .wre-scope-rail-tint-warning {
438
+ background: color-mix(in srgb, var(--color-warning) 14%, transparent);
365
439
  }
366
-
367
- .prosemirror-surface .ProseMirror .wre-workflow-rail-edit {
368
- --wre-workflow-rail-color: var(--color-accent);
369
- background: color-mix(in srgb, var(--color-accent) 7%, transparent);
440
+ .wre-scope-rail-tint-insert {
441
+ background: color-mix(in srgb, var(--color-insert) 12%, transparent);
370
442
  }
371
-
372
- .prosemirror-surface .ProseMirror .wre-workflow-rail-suggest {
373
- --wre-workflow-rail-color: var(--color-warning);
374
- background: color-mix(in srgb, var(--color-warning) 8%, transparent);
443
+ .wre-scope-rail-tint-secondary {
444
+ background: color-mix(in srgb, var(--color-secondary) 9%, transparent);
375
445
  }
376
-
377
- .prosemirror-surface .ProseMirror .wre-workflow-rail-comment {
378
- --wre-workflow-rail-color: var(--color-insert);
379
- background: color-mix(in srgb, var(--color-insert) 7%, transparent);
446
+ .wre-scope-rail-tint-danger {
447
+ background: color-mix(in srgb, var(--color-danger) 14%, transparent);
380
448
  }
381
449
 
382
- .prosemirror-surface .ProseMirror .wre-workflow-rail-view {
383
- --wre-workflow-rail-color: var(--color-secondary);
384
- background: color-mix(in srgb, var(--color-secondary) 6%, transparent);
450
+ .wre-scope-rail-tint-active {
451
+ outline: 1px solid color-mix(in srgb, var(--color-accent) 40%, transparent);
452
+ outline-offset: -1px;
385
453
  }
386
454
 
387
- .prosemirror-surface .ProseMirror .wre-workflow-rail-candidate {
388
- --wre-workflow-rail-color: var(--color-warning);
389
- background: color-mix(in srgb, var(--color-warning) 4%, transparent);
455
+ .wre-scope-rail-label {
456
+ position: absolute;
457
+ display: flex;
458
+ flex-direction: column;
459
+ align-items: center;
460
+ justify-content: center;
461
+ gap: 0.15rem;
462
+ padding: 0.25rem 0.5rem;
463
+ border-radius: 0.375rem;
464
+ border: 1px solid transparent;
465
+ background: var(--color-canvas, #fff);
466
+ box-shadow: 0 2px 6px -3px rgba(0, 0, 0, 0.18);
467
+ font-size: 10px;
468
+ line-height: 1;
469
+ text-transform: uppercase;
470
+ letter-spacing: 0.08em;
471
+ font-weight: 600;
472
+ cursor: pointer;
473
+ pointer-events: auto;
474
+ z-index: 1;
475
+ transition: transform 120ms ease-out, box-shadow 120ms ease-out;
476
+ }
477
+
478
+ .wre-scope-rail-label:hover {
479
+ transform: translateY(-1px);
480
+ box-shadow: 0 4px 12px -4px rgba(0, 0, 0, 0.22);
481
+ }
482
+
483
+ .wre-scope-rail-label-accent {
484
+ color: var(--color-accent);
485
+ border-color: color-mix(in srgb, var(--color-accent) 40%, transparent);
486
+ background: color-mix(in srgb, var(--color-accent) 8%, var(--color-canvas, #fff));
487
+ }
488
+ .wre-scope-rail-label-warning {
489
+ color: var(--color-warning);
490
+ border-color: color-mix(in srgb, var(--color-warning) 42%, transparent);
491
+ background: color-mix(in srgb, var(--color-warning) 8%, var(--color-canvas, #fff));
492
+ }
493
+ .wre-scope-rail-label-insert {
494
+ color: var(--color-insert);
495
+ border-color: color-mix(in srgb, var(--color-insert) 40%, transparent);
496
+ background: color-mix(in srgb, var(--color-insert) 8%, var(--color-canvas, #fff));
497
+ }
498
+ .wre-scope-rail-label-secondary {
499
+ color: var(--color-secondary);
500
+ border-color: color-mix(in srgb, var(--color-secondary) 38%, transparent);
501
+ background: color-mix(in srgb, var(--color-secondary) 6%, var(--color-canvas, #fff));
502
+ }
503
+ .wre-scope-rail-label-danger {
504
+ color: var(--color-danger);
505
+ border-color: color-mix(in srgb, var(--color-danger) 45%, transparent);
506
+ background: color-mix(in srgb, var(--color-danger) 8%, var(--color-canvas, #fff));
507
+ }
508
+
509
+ .wre-scope-rail-label-active {
510
+ box-shadow: 0 0 0 1px color-mix(in srgb, currentColor 30%, transparent);
511
+ }
512
+
513
+ .wre-scope-rail-icon {
514
+ display: inline-block;
515
+ width: 14px;
516
+ height: 14px;
517
+ background-color: currentColor;
518
+ mask-repeat: no-repeat;
519
+ mask-position: center;
520
+ mask-size: contain;
521
+ -webkit-mask-repeat: no-repeat;
522
+ -webkit-mask-position: center;
523
+ -webkit-mask-size: contain;
390
524
  }
391
525
 
392
- .prosemirror-surface .ProseMirror .wre-workflow-rail-candidate::before {
393
- background:
394
- repeating-linear-gradient(
395
- to bottom,
396
- var(--wre-workflow-rail-color, var(--color-warning)) 0,
397
- var(--wre-workflow-rail-color, var(--color-warning)) 6px,
398
- transparent 6px,
399
- transparent 10px
400
- );
526
+ /* Simple inline-SVG-as-mask icons so consumers don't need an icon font. */
527
+ .wre-scope-rail-icon-lock {
528
+ mask-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="5" y="11" width="14" height="10" rx="2"/><path d="M8 11V7a4 4 0 018 0v4"/></svg>');
529
+ -webkit-mask-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="5" y="11" width="14" height="10" rx="2"/><path d="M8 11V7a4 4 0 018 0v4"/></svg>');
401
530
  }
402
-
403
- .prosemirror-surface .ProseMirror .wre-workflow-rail-preserve-only {
404
- --wre-workflow-rail-color: var(--color-danger);
405
- background: color-mix(in srgb, var(--color-danger) 5%, transparent);
531
+ .wre-scope-rail-icon-eye {
532
+ mask-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M2 12s4-8 10-8 10 8 10 8-4 8-10 8-10-8-10-8z"/><circle cx="12" cy="12" r="3"/></svg>');
533
+ -webkit-mask-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M2 12s4-8 10-8 10 8 10 8-4 8-10 8-10-8-10-8z"/><circle cx="12" cy="12" r="3"/></svg>');
406
534
  }
407
-
408
- .prosemirror-surface .ProseMirror .wre-workflow-rail-preserve-only::before {
409
- opacity: 0.85;
535
+ .wre-scope-rail-icon-pencil {
536
+ mask-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17 3l4 4L8 20H4v-4z"/></svg>');
537
+ -webkit-mask-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17 3l4 4L8 20H4v-4z"/></svg>');
410
538
  }
411
-
412
- .prosemirror-surface .ProseMirror .wre-workflow-rail-blocked-import {
413
- --wre-workflow-rail-color: var(--color-danger);
414
- background: color-mix(in srgb, var(--color-danger) 8%, transparent);
539
+ .wre-scope-rail-icon-sparkles {
540
+ mask-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3v4M12 17v4M3 12h4M17 12h4M6 6l2 2M16 16l2 2M6 18l2-2M16 8l2-2"/></svg>');
541
+ -webkit-mask-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3v4M12 17v4M3 12h4M17 12h4M6 6l2 2M16 16l2 2M6 18l2-2M16 8l2-2"/></svg>');
415
542
  }
416
-
417
- .prosemirror-surface .ProseMirror .wre-workflow-rail-blocked-import::before {
418
- background:
419
- repeating-linear-gradient(
420
- to bottom,
421
- var(--wre-workflow-rail-color, var(--color-danger)) 0,
422
- var(--wre-workflow-rail-color, var(--color-danger)) 4px,
423
- transparent 4px,
424
- transparent 8px
425
- );
543
+ .wre-scope-rail-icon-message {
544
+ mask-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 5h18v11H7l-4 4z"/></svg>');
545
+ -webkit-mask-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 5h18v11H7l-4 4z"/></svg>');
426
546
  }
427
-
428
- .prosemirror-surface .ProseMirror .wre-workflow-rail-locked-zone {
429
- box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--wre-workflow-rail-color, var(--color-danger)) 10%, transparent);
547
+ .wre-scope-rail-icon-flag {
548
+ mask-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 21V4h12l-2 4 2 4H5"/></svg>');
549
+ -webkit-mask-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 21V4h12l-2 4 2 4H5"/></svg>');
430
550
  }
431
551
 
432
- .prosemirror-surface .ProseMirror .wre-workflow-rail-selection-zone {
433
- background: transparent;
552
+ /*
553
+ * ─── ChromeOverlay: workspace view-switcher dock ───
554
+ */
555
+ .wre-workspace-dock {
556
+ user-select: none;
434
557
  }
435
558
 
436
- .prosemirror-surface .ProseMirror .wre-workflow-rail-selection-zone::before {
437
- width: 0.25rem;
438
- opacity: 0.95;
559
+ .wre-workspace-dock-icon {
560
+ display: inline-block;
561
+ width: 14px;
562
+ height: 14px;
563
+ background-color: currentColor;
439
564
  }
440
565
 
441
566
  .prosemirror-surface:focus-visible {
@@ -495,3 +620,239 @@
495
620
  .wre-page-chrome[style*="scale"] {
496
621
  will-change: transform;
497
622
  }
623
+
624
+ /*
625
+ * ─── Review rail — TwRailCard primitive (harness-pass §4.5.3) ───
626
+ *
627
+ * Shared editorial status-eyebrow card used by the Workflow / Comments /
628
+ * Changes tabs. Tone is driven by `data-tone` and a single custom property
629
+ * on the card root so the primitive stays tone-agnostic. The card lives
630
+ * alongside the shipped `.wre-workflow-card` (used by `tw-workflow-tab.tsx`)
631
+ * — consumers pick whichever matches their payload shape.
632
+ */
633
+ .wre-rail-card {
634
+ position: relative;
635
+ border-radius: var(--radius-card);
636
+ background: var(--color-surface);
637
+ border: 1px solid color-mix(in srgb, var(--color-border) 80%, transparent);
638
+ padding: 14px 16px 16px;
639
+ overflow: hidden;
640
+ transition: background-color var(--motion-fast) ease-out,
641
+ border-color var(--motion-fast) ease-out,
642
+ box-shadow var(--motion-fast) ease-out;
643
+ }
644
+
645
+ .wre-rail-card::before {
646
+ content: "";
647
+ position: absolute;
648
+ left: 0;
649
+ top: 10px;
650
+ bottom: 10px;
651
+ width: 3px;
652
+ border-radius: var(--radius-pill);
653
+ background: var(--wre-rail-card-edge, transparent);
654
+ }
655
+
656
+ .wre-rail-card[data-active="true"] {
657
+ box-shadow: var(--shadow-soft);
658
+ border-color: color-mix(in srgb, var(--wre-rail-card-edge, var(--color-border)) 40%, var(--color-border));
659
+ }
660
+
661
+ .wre-rail-card[data-tone="inReview"] {
662
+ --wre-rail-card-edge: var(--color-accent);
663
+ --wre-rail-card-eyebrow: var(--color-accent);
664
+ --wre-rail-card-progress-fill: var(--color-accent);
665
+ }
666
+
667
+ .wre-rail-card[data-tone="blocked"] {
668
+ --wre-rail-card-edge: var(--color-danger);
669
+ --wre-rail-card-eyebrow: var(--color-danger);
670
+ --wre-rail-card-progress-fill: var(--color-danger);
671
+ background: color-mix(in srgb, var(--color-danger-soft) 75%, var(--color-surface));
672
+ }
673
+
674
+ .wre-rail-card[data-tone="scheduled"] {
675
+ --wre-rail-card-edge: var(--color-tertiary);
676
+ --wre-rail-card-eyebrow: var(--color-tertiary);
677
+ --wre-rail-card-progress-fill: var(--color-tertiary);
678
+ }
679
+
680
+ .wre-rail-card[data-tone="resolved"] {
681
+ --wre-rail-card-edge: var(--color-success);
682
+ --wre-rail-card-eyebrow: var(--color-success);
683
+ --wre-rail-card-progress-fill: var(--color-success);
684
+ }
685
+
686
+ .wre-rail-card[data-tone="neutral"] {
687
+ --wre-rail-card-edge: var(--color-border-strong);
688
+ --wre-rail-card-eyebrow: var(--color-tertiary);
689
+ --wre-rail-card-progress-fill: var(--color-secondary);
690
+ }
691
+
692
+ .wre-rail-card__eyebrow {
693
+ font-size: 10px;
694
+ letter-spacing: 0.14em;
695
+ text-transform: uppercase;
696
+ color: var(--wre-rail-card-eyebrow, var(--color-tertiary));
697
+ font-weight: 600;
698
+ display: inline-flex;
699
+ align-items: center;
700
+ gap: 6px;
701
+ }
702
+
703
+ .wre-rail-card__title {
704
+ margin-top: 4px;
705
+ font-size: 14px;
706
+ font-weight: 500;
707
+ color: var(--color-primary);
708
+ line-height: 1.35;
709
+ display: -webkit-box;
710
+ -webkit-line-clamp: 2;
711
+ -webkit-box-orient: vertical;
712
+ overflow: hidden;
713
+ }
714
+
715
+ .wre-rail-card__detail {
716
+ margin-top: 6px;
717
+ font-size: 12.5px;
718
+ line-height: 18px;
719
+ color: var(--color-secondary);
720
+ display: -webkit-box;
721
+ -webkit-line-clamp: 3;
722
+ -webkit-box-orient: vertical;
723
+ overflow: hidden;
724
+ }
725
+
726
+ .wre-rail-card__avatars {
727
+ margin-top: 10px;
728
+ display: inline-flex;
729
+ align-items: center;
730
+ }
731
+
732
+ .wre-rail-card__avatar,
733
+ .wre-rail-card__avatar-counter {
734
+ width: 20px;
735
+ height: 20px;
736
+ border-radius: var(--radius-pill);
737
+ font-size: 9px;
738
+ font-weight: 600;
739
+ color: var(--color-primary);
740
+ background: var(--color-subtle);
741
+ display: inline-flex;
742
+ align-items: center;
743
+ justify-content: center;
744
+ box-shadow: 0 0 0 1.5px var(--color-sidebar-tint);
745
+ }
746
+
747
+ .wre-rail-card__avatar + .wre-rail-card__avatar,
748
+ .wre-rail-card__avatar + .wre-rail-card__avatar-counter {
749
+ margin-left: -4px;
750
+ }
751
+
752
+ .wre-rail-card__avatar-counter {
753
+ color: var(--color-secondary);
754
+ font-size: 10px;
755
+ }
756
+
757
+ .wre-rail-card__counter {
758
+ position: absolute;
759
+ top: 12px;
760
+ right: 14px;
761
+ width: 20px;
762
+ height: 20px;
763
+ border-radius: var(--radius-pill);
764
+ background: var(--color-subtle);
765
+ color: var(--color-secondary);
766
+ font-size: 10px;
767
+ font-weight: 600;
768
+ display: inline-flex;
769
+ align-items: center;
770
+ justify-content: center;
771
+ }
772
+
773
+ .wre-rail-card__progress {
774
+ position: absolute;
775
+ left: 0;
776
+ right: 0;
777
+ bottom: 0;
778
+ height: 2px;
779
+ background: var(--color-subtle);
780
+ }
781
+
782
+ .wre-rail-card__progress-fill {
783
+ display: block;
784
+ height: 100%;
785
+ background: var(--wre-rail-card-progress-fill, var(--color-accent));
786
+ transition: width var(--motion-default) ease-out;
787
+ }
788
+
789
+ .wre-rail-card__footer {
790
+ margin-top: 12px;
791
+ display: flex;
792
+ align-items: center;
793
+ gap: 8px;
794
+ flex-wrap: wrap;
795
+ }
796
+
797
+ /*
798
+ * ─── Review rail tab chip (harness-pass §4.5.2) ───
799
+ *
800
+ * Underline-active triggers used by TwReviewRail when the host opts into
801
+ * the editorial header. Plays nicely with the shipped Tabs.List pill style
802
+ * — consumers pick which chip they want via a class toggle.
803
+ */
804
+ .wre-rail-tab {
805
+ position: relative;
806
+ padding: 8px 14px 10px;
807
+ font-size: 11px;
808
+ font-weight: 600;
809
+ letter-spacing: 0.14em;
810
+ text-transform: uppercase;
811
+ color: var(--color-tertiary);
812
+ background: transparent;
813
+ border: none;
814
+ cursor: pointer;
815
+ transition: color var(--motion-fast) ease-out;
816
+ display: inline-flex;
817
+ align-items: center;
818
+ gap: 4px;
819
+ }
820
+
821
+ .wre-rail-tab:hover {
822
+ color: var(--color-secondary);
823
+ }
824
+
825
+ .wre-rail-tab[data-state="active"] {
826
+ color: var(--color-primary);
827
+ }
828
+
829
+ .wre-rail-tab[data-state="active"]::after {
830
+ content: "";
831
+ position: absolute;
832
+ left: 14px;
833
+ right: 14px;
834
+ bottom: 0;
835
+ height: 2px;
836
+ background: var(--color-accent);
837
+ border-radius: var(--radius-pill);
838
+ }
839
+
840
+ .wre-rail-tab__count {
841
+ display: inline-flex;
842
+ align-items: center;
843
+ justify-content: center;
844
+ min-width: 16px;
845
+ height: 16px;
846
+ padding: 0 5px;
847
+ font-size: 10px;
848
+ font-weight: 600;
849
+ letter-spacing: 0;
850
+ border-radius: var(--radius-pill);
851
+ background: var(--color-subtle);
852
+ color: var(--color-secondary);
853
+ }
854
+
855
+ .wre-rail-tab[data-state="active"] .wre-rail-tab__count {
856
+ color: var(--color-primary);
857
+ background: color-mix(in srgb, var(--color-accent-soft) 80%, var(--color-subtle));
858
+ }