@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
@@ -1,4 +1,4 @@
1
- import React from "react";
1
+ import React, { type ReactNode } from "react";
2
2
 
3
3
  import * as Tabs from "@radix-ui/react-tabs";
4
4
  import * as ScrollArea from "@radix-ui/react-scroll-area";
@@ -12,11 +12,35 @@ import type {
12
12
  TrackedChangesSnapshot,
13
13
  TrackedChangeEntrySnapshot,
14
14
  } from "../../api/public-types";
15
+ import type { ScopeRailSegment } from "../../runtime/layout";
15
16
  import type { MarkupDisplay } from "../../ui/headless/comment-decoration-model";
16
17
  import { TwCommentSidebar } from "./tw-comment-sidebar";
17
18
  import { TwRevisionSidebar } from "./tw-revision-sidebar";
19
+ import { TwWorkflowTab } from "./tw-workflow-tab";
20
+ import {
21
+ TwReviewRailFooter,
22
+ type TwReviewRailFooterProps,
23
+ } from "./tw-review-rail-footer";
18
24
 
19
- export type ReviewRailTab = "comments" | "changes";
25
+ /**
26
+ * Review rail with three tabs (Comments / Changes / Workflow) that matches
27
+ * the editorial reference mock while preserving the shipped R3a surface.
28
+ *
29
+ * The Workflow tab reads `scopeRailSegments` from the runtime facet — that
30
+ * stays the default path. For hosts that need to override the Workflow card
31
+ * content (CLM, BW, agent integrations that derive their own model), pass
32
+ * `workflowTab` as a ReactNode and it will replace the built-in projection.
33
+ * Either way policy stays host-side; the runtime rail remains projection-only.
34
+ *
35
+ * Optional editorial surface (harness-pass §4.5):
36
+ * - `intelligenceHeader={true}` turns on the DOCUMENT INTELLIGENCE header +
37
+ * underline-active chip style. Off by default so existing pill-tab
38
+ * consumers see no change.
39
+ * - `workflowScopesTitle?: string` overrides the header title.
40
+ * - `railFooter?: TwReviewRailFooterProps` mounts the SEARCH / HELP footer.
41
+ */
42
+
43
+ export type ReviewRailTab = "comments" | "changes" | "workflow";
20
44
 
21
45
  export interface TwReviewRailProps {
22
46
  activeTab: ReviewRailTab;
@@ -30,6 +54,34 @@ export interface TwReviewRailProps {
30
54
  contextAnalytics?: RuntimeContextAnalyticsSnapshot | null;
31
55
  activeCommentId?: string;
32
56
  activeRevisionId?: string;
57
+ /**
58
+ * Scope rail segments used by the Workflow tab. Consumers typically
59
+ * pass `ref.layout.getAllScopeRailSegments()` here. When omitted the
60
+ * Workflow tab renders an empty state (unless `workflowTab` is set).
61
+ */
62
+ scopeRailSegments?: readonly ScopeRailSegment[];
63
+ activeScopeId?: string | null;
64
+ /**
65
+ * Optional host-provided Workflow-tab override. When supplied this
66
+ * ReactNode replaces the default TwWorkflowTab content while still using
67
+ * the same tab chrome. Hosts retain ownership of workflow/CLM policy.
68
+ */
69
+ workflowTab?: ReactNode;
70
+ /** Count badge shown next to the Workflow trigger. Defaults to segment count. */
71
+ workflowCount?: number;
72
+ /** Editorial header title — defaults to "Review" or tab-aware copy. */
73
+ workflowScopesTitle?: string;
74
+ /** Editorial header eyebrow — defaults to "DOCUMENT INTELLIGENCE". */
75
+ intelligenceEyebrow?: string;
76
+ /**
77
+ * Turn on the editorial DOCUMENT INTELLIGENCE header + underline-active
78
+ * tab chip. Defaults to `false` so existing pill-tab callers see no
79
+ * change.
80
+ */
81
+ intelligenceHeader?: boolean;
82
+ /** Utility footer with SEARCH / HELP links. Hides when unset. */
83
+ railFooter?: TwReviewRailFooterProps;
84
+
33
85
  onActiveTabChange: (tab: ReviewRailTab) => void;
34
86
  onOpenComment?: (thread: CommentSidebarThreadSnapshot) => void;
35
87
  onResolveComment?: (commentId: string) => void;
@@ -41,22 +93,38 @@ export interface TwReviewRailProps {
41
93
  onRejectRevision?: (revisionId: string) => void;
42
94
  onAcceptAllChanges?: () => void;
43
95
  onRejectAllChanges?: () => void;
96
+ onOpenScope?: (segment: ScopeRailSegment) => void;
44
97
  }
45
98
 
46
99
  const focusRingClass =
47
100
  "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-2 focus-visible:ring-offset-canvas";
48
101
 
102
+ const PILL_TRIGGER_CLASS = `flex-1 rounded-lg px-3 py-1.5 text-xs font-medium text-tertiary transition-colors data-[state=active]:bg-surface data-[state=active]:text-primary data-[state=active]:shadow-[0_1px_0_var(--color-shadow)] outline-none ${focusRingClass}`;
103
+
49
104
  export function TwReviewRail(props: TwReviewRailProps) {
50
105
  const variant = props.variant ?? "docked";
106
+ const editorial = props.intelligenceHeader ?? false;
107
+ const intelligenceEyebrow =
108
+ props.intelligenceEyebrow ?? "DOCUMENT INTELLIGENCE";
109
+ const headerTitle = resolveHeaderTitle(props);
110
+ const workflowSegments = props.scopeRailSegments ?? [];
111
+ const workflowCount =
112
+ props.workflowCount ?? (props.workflowTab ? undefined : workflowSegments.length);
113
+ const widthClass = editorial ? "w-[360px]" : "w-[336px]";
114
+ const drawerWidthClass = editorial
115
+ ? "w-[min(360px,calc(100vw-1rem))]"
116
+ : "w-[min(336px,calc(100vw-1rem))]";
117
+
51
118
  return (
52
119
  <aside
53
120
  aria-label="Review rail"
54
121
  data-wre-drawer={variant === "drawer" ? "true" : "false"}
122
+ data-editorial-header={editorial ? "true" : "false"}
55
123
  className={[
56
124
  "flex flex-col border-l border-border/60 bg-[var(--color-sidebar-tint)]",
57
125
  variant === "drawer"
58
- ? "h-full w-[min(336px,calc(100vw-1rem))] max-w-full shrink-0 shadow-[0_18px_40px_-22px_var(--color-shadow-strong)]"
59
- : "w-[336px] shrink-0",
126
+ ? `h-full ${drawerWidthClass} max-w-full shrink-0 shadow-[var(--shadow-float)]`
127
+ : `${widthClass} shrink-0`,
60
128
  ].join(" ")}
61
129
  >
62
130
  <Tabs.Root
@@ -64,25 +132,84 @@ export function TwReviewRail(props: TwReviewRailProps) {
64
132
  onValueChange={(v: string) => props.onActiveTabChange(v as ReviewRailTab)}
65
133
  className="flex flex-1 flex-col min-h-0"
66
134
  >
67
- <Tabs.List className="flex shrink-0 border-b border-border/60 px-3 py-2">
135
+ {editorial ? (
136
+ <header className="shrink-0 px-4 pt-[18px] pb-3">
137
+ <p className="text-[10px] font-semibold uppercase tracking-[0.18em] text-tertiary">
138
+ {intelligenceEyebrow}
139
+ </p>
140
+ <p className="mt-1 text-[16px] font-semibold text-primary">
141
+ {headerTitle}
142
+ </p>
143
+ </header>
144
+ ) : null}
145
+
146
+ <Tabs.List
147
+ className={
148
+ editorial
149
+ ? "flex shrink-0 items-center gap-2 border-b border-border/60 px-2"
150
+ : "flex shrink-0 border-b border-border/60 px-3 py-2"
151
+ }
152
+ aria-label="Review rail sections"
153
+ >
154
+ <Tabs.Trigger
155
+ value="workflow"
156
+ className={
157
+ editorial
158
+ ? `wre-rail-tab outline-none ${focusRingClass}`
159
+ : PILL_TRIGGER_CLASS
160
+ }
161
+ >
162
+ {editorial ? "Workflow" : "Workflow "}
163
+ {workflowCount !== undefined && workflowCount > 0 ? (
164
+ <span
165
+ className={
166
+ editorial
167
+ ? "wre-rail-tab__count"
168
+ : "ml-1 inline-flex min-w-[14px] items-center justify-center rounded-full bg-subtle px-1.5 py-px text-[10px] font-medium text-tertiary"
169
+ }
170
+ >
171
+ {workflowCount}
172
+ </span>
173
+ ) : null}
174
+ </Tabs.Trigger>
68
175
  <Tabs.Trigger
69
176
  value="comments"
70
- className={`flex-1 rounded-lg px-3 py-1.5 text-xs font-medium text-tertiary transition-colors data-[state=active]:bg-surface data-[state=active]:text-primary data-[state=active]:shadow-[0_1px_0_var(--color-shadow)] outline-none ${focusRingClass}`}
177
+ className={
178
+ editorial
179
+ ? `wre-rail-tab outline-none ${focusRingClass}`
180
+ : PILL_TRIGGER_CLASS
181
+ }
71
182
  >
72
- Comments{" "}
183
+ {editorial ? "Comments" : "Comments "}
73
184
  {props.comments.totalCount > 0 ? (
74
- <span className="ml-1 inline-flex min-w-[14px] items-center justify-center rounded-full bg-subtle px-1.5 py-px text-[10px] font-medium text-tertiary">
185
+ <span
186
+ className={
187
+ editorial
188
+ ? "wre-rail-tab__count"
189
+ : "ml-1 inline-flex min-w-[14px] items-center justify-center rounded-full bg-subtle px-1.5 py-px text-[10px] font-medium text-tertiary"
190
+ }
191
+ >
75
192
  {props.comments.totalCount}
76
193
  </span>
77
194
  ) : null}
78
195
  </Tabs.Trigger>
79
196
  <Tabs.Trigger
80
197
  value="changes"
81
- className={`flex-1 rounded-lg px-3 py-1.5 text-xs font-medium text-tertiary transition-colors data-[state=active]:bg-surface data-[state=active]:text-primary data-[state=active]:shadow-[0_1px_0_var(--color-shadow)] outline-none ${focusRingClass}`}
198
+ className={
199
+ editorial
200
+ ? `wre-rail-tab outline-none ${focusRingClass}`
201
+ : PILL_TRIGGER_CLASS
202
+ }
82
203
  >
83
- Changes{" "}
204
+ {editorial ? "Changes" : "Changes "}
84
205
  {props.trackedChanges.totalCount > 0 ? (
85
- <span className="ml-1 inline-flex min-w-[14px] items-center justify-center rounded-full bg-subtle px-1.5 py-px text-[10px] font-medium text-tertiary">
206
+ <span
207
+ className={
208
+ editorial
209
+ ? "wre-rail-tab__count"
210
+ : "ml-1 inline-flex min-w-[14px] items-center justify-center rounded-full bg-subtle px-1.5 py-px text-[10px] font-medium text-tertiary"
211
+ }
212
+ >
86
213
  {props.trackedChanges.totalCount}
87
214
  </span>
88
215
  ) : null}
@@ -91,6 +218,16 @@ export function TwReviewRail(props: TwReviewRailProps) {
91
218
 
92
219
  <ScrollArea.Root className="flex-1 min-h-0">
93
220
  <ScrollArea.Viewport className="h-full w-full">
221
+ <Tabs.Content value="workflow" className="p-3 outline-none">
222
+ {props.workflowTab ?? (
223
+ <TwWorkflowTab
224
+ segments={workflowSegments}
225
+ activeScopeId={props.activeScopeId ?? null}
226
+ onOpenScope={props.onOpenScope}
227
+ />
228
+ )}
229
+ </Tabs.Content>
230
+
94
231
  <Tabs.Content value="comments" className="p-3 outline-none">
95
232
  <TwCommentSidebar
96
233
  currentUserId={props.currentUserId}
@@ -125,6 +262,24 @@ export function TwReviewRail(props: TwReviewRailProps) {
125
262
  </ScrollArea.Scrollbar>
126
263
  </ScrollArea.Root>
127
264
  </Tabs.Root>
265
+
266
+ {props.railFooter ? <TwReviewRailFooter {...props.railFooter} /> : null}
128
267
  </aside>
129
268
  );
130
269
  }
270
+
271
+ function resolveHeaderTitle(props: TwReviewRailProps): string {
272
+ if (props.workflowScopesTitle) {
273
+ return props.workflowScopesTitle;
274
+ }
275
+ if (props.activeTab === "workflow") {
276
+ return "Workflow Scopes";
277
+ }
278
+ if (props.activeTab === "comments") {
279
+ return "Comments";
280
+ }
281
+ if (props.activeTab === "changes") {
282
+ return "Tracked Changes";
283
+ }
284
+ return "Review";
285
+ }
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Workflow tab content for the review rail.
3
+ *
4
+ * Renders scope cards sourced from `ref.layout.getAllScopeRailSegments()`.
5
+ * Cards mirror the right-sidebar "Document Intelligence / Workflow Scopes"
6
+ * panel in image copy.png with state chips + labels. Clicking a card
7
+ * forwards to `onOpenScope(segment)` so consumers can scroll to the scope
8
+ * and focus the rail label.
9
+ */
10
+
11
+ import React from "react";
12
+ import type { ScopeRailSegment, ScopeRailPosture } from "../../runtime/layout";
13
+
14
+ export interface TwWorkflowTabProps {
15
+ segments: readonly ScopeRailSegment[];
16
+ activeScopeId?: string | null;
17
+ onOpenScope?: (segment: ScopeRailSegment) => void;
18
+ }
19
+
20
+ const POSTURE_META: Record<
21
+ ScopeRailPosture,
22
+ { chip: string; kind: "accent" | "warning" | "insert" | "secondary" | "danger" }
23
+ > = {
24
+ edit: { chip: "EDIT", kind: "accent" },
25
+ suggest: { chip: "SUGGEST", kind: "warning" },
26
+ comment: { chip: "COMMENT", kind: "insert" },
27
+ view: { chip: "IN REVIEW", kind: "secondary" },
28
+ candidate: { chip: "SCHEDULED", kind: "warning" },
29
+ "preserve-only": { chip: "BLOCKED REGION", kind: "danger" },
30
+ "blocked-import": { chip: "BLOCKED REGION", kind: "danger" },
31
+ };
32
+
33
+ export const TwWorkflowTab: React.FC<TwWorkflowTabProps> = ({
34
+ segments,
35
+ activeScopeId,
36
+ onOpenScope,
37
+ }) => {
38
+ // Dedupe by scopeId so a scope spanning multiple pages shows once.
39
+ const byScopeId = new Map<string, ScopeRailSegment>();
40
+ for (const segment of segments) {
41
+ if (!byScopeId.has(segment.scopeId)) {
42
+ byScopeId.set(segment.scopeId, segment);
43
+ }
44
+ }
45
+ const uniqueSegments = Array.from(byScopeId.values());
46
+
47
+ if (uniqueSegments.length === 0) {
48
+ return (
49
+ <div
50
+ className="wre-workflow-tab-empty rounded-md border border-dashed border-border/60 bg-canvas/50 p-4 text-[11px] text-tertiary"
51
+ data-testid="workflow-tab-empty"
52
+ >
53
+ <div className="font-semibold uppercase tracking-[0.1em] text-secondary">
54
+ Document Intelligence
55
+ </div>
56
+ <div className="mt-1 text-[11px] text-tertiary">
57
+ No workflow scopes on this document yet.
58
+ </div>
59
+ </div>
60
+ );
61
+ }
62
+
63
+ return (
64
+ <div className="wre-workflow-tab flex flex-col gap-2" data-testid="workflow-tab">
65
+ <div className="mb-1">
66
+ <div className="text-[10px] font-semibold uppercase tracking-[0.14em] text-accent">
67
+ Document Intelligence
68
+ </div>
69
+ <div className="text-[15px] font-semibold text-primary">Workflow Scopes</div>
70
+ </div>
71
+ {uniqueSegments.map((segment) => {
72
+ const meta = POSTURE_META[segment.posture];
73
+ const isActive = activeScopeId === segment.scopeId || segment.isActiveWorkItem;
74
+ return (
75
+ <button
76
+ key={segment.scopeId}
77
+ type="button"
78
+ className={`wre-workflow-card flex flex-col gap-1 rounded-md border border-border/50 bg-canvas p-3 text-left transition-shadow hover:shadow-md ${
79
+ isActive ? "ring-1 ring-accent/60" : ""
80
+ }`}
81
+ onClick={onOpenScope ? () => onOpenScope(segment) : undefined}
82
+ data-scope-id={segment.scopeId}
83
+ data-posture={segment.posture}
84
+ >
85
+ <div className="flex items-center gap-2 text-[10px] font-semibold uppercase tracking-[0.12em]">
86
+ <span
87
+ className={`wre-workflow-card-chip wre-workflow-card-chip-${meta.kind} inline-flex items-center gap-1 rounded-full px-2 py-0.5`}
88
+ data-kind={meta.kind}
89
+ >
90
+ {meta.chip}
91
+ </span>
92
+ <span className="text-tertiary">Page {segment.pageIndex + 1}</span>
93
+ </div>
94
+ <div className="text-[13px] font-semibold text-primary">
95
+ {segment.label || segment.scopeId}
96
+ </div>
97
+ <div className="text-[11px] text-tertiary">
98
+ Section {segment.sectionIndex + 1}
99
+ {segment.isActiveWorkItem ? " • Active work item" : ""}
100
+ </div>
101
+ </button>
102
+ );
103
+ })}
104
+ </div>
105
+ );
106
+ };
107
+
108
+ export default TwWorkflowTab;