@beyondwork/docx-react-component 1.0.90 → 1.0.91

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.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@beyondwork/docx-react-component",
3
3
  "publisher": "beyondwork",
4
- "version": "1.0.90",
4
+ "version": "1.0.91",
5
5
  "description": "Embeddable React Word (docx) editor with review, comments, tracked changes, and round-trip OOXML fidelity.",
6
6
  "type": "module",
7
7
  "sideEffects": [
@@ -14,7 +14,7 @@
14
14
 
15
15
  import * as React from "react";
16
16
  import type { OverlayCoordinateSpace } from "./chrome-overlay-projector";
17
- import type { ScopeRailPosture, ScopeRailSegment } from "../../api/public-types.ts";
17
+ import type { ScopeRailSegment } from "../../api/public-types.ts";
18
18
  import type {
19
19
  EditorRole,
20
20
  EditorStoryTarget,
@@ -49,8 +49,6 @@ export interface TwChromeOverlayProps {
49
49
  space?: OverlayCoordinateSpace;
50
50
  /** Active scope id (for emphasis + rail tab sync). */
51
51
  activeScopeId?: string | null;
52
- /** Posture filters shared with the Workflow rail. Omitted means all scopes render. */
53
- visibleScopePostures?: ReadonlySet<ScopeRailPosture>;
54
52
  /**
55
53
  * Click handler fired when the user clicks a scope rail stripe.
56
54
  * P0 wires this to open the scope card (P1 ships the card layer).
@@ -215,7 +213,6 @@ export const TwChromeOverlay: React.FC<TwChromeOverlayProps> = ({
215
213
  geometryFacet,
216
214
  space,
217
215
  activeScopeId,
218
- visibleScopePostures,
219
216
  onScopeStripeClick,
220
217
  onScopeSegmentClick,
221
218
  onScopeCardClose,
@@ -245,17 +242,6 @@ export const TwChromeOverlay: React.FC<TwChromeOverlayProps> = ({
245
242
  mediaPreviews,
246
243
  activeBandRibbonProps,
247
244
  }) => {
248
- const visibleScopeIds = React.useMemo(() => {
249
- if (!visibleScopePostures) return undefined;
250
- const ids = new Set<string>();
251
- for (const segment of workflowFacet?.getAllRailSegments() ?? []) {
252
- if (visibleScopePostures.has(segment.posture)) {
253
- ids.add(segment.scopeId);
254
- }
255
- }
256
- return ids;
257
- }, [visibleScopePostures, workflowFacet]);
258
-
259
245
  return (
260
246
  <div
261
247
  className="wre-chrome-overlay pointer-events-none absolute inset-0 z-30"
@@ -282,7 +268,6 @@ export const TwChromeOverlay: React.FC<TwChromeOverlayProps> = ({
282
268
  workflowFacet={workflowFacet}
283
269
  space={space}
284
270
  activeScopeId={activeScopeId}
285
- visibleScopePostures={visibleScopePostures}
286
271
  onStripeClick={onScopeStripeClick}
287
272
  onSegmentClick={onScopeSegmentClick}
288
273
  />
@@ -290,7 +275,6 @@ export const TwChromeOverlay: React.FC<TwChromeOverlayProps> = ({
290
275
  facet={facet}
291
276
  workflowFacet={workflowFacet}
292
277
  activeScopeId={activeScopeId ?? null}
293
- visibleScopeIds={visibleScopeIds}
294
278
  onClose={onScopeCardClose ?? noop}
295
279
  onModeChange={onScopeCardModeChange ?? noopModeChange}
296
280
  onIssueAction={onScopeCardIssueAction ?? noopIssueAction}
@@ -52,8 +52,6 @@ export interface TwScopeCardLayerProps {
52
52
  */
53
53
  workflowFacet: WorkflowFacet | null;
54
54
  activeScopeId: string | null;
55
- /** Scope ids currently visible under the Workflow rail layer filters. */
56
- visibleScopeIds?: ReadonlySet<string>;
57
55
  onClose: () => void;
58
56
  onModeChange: (scopeId: string, mode: WorkflowScopeMode) => void;
59
57
  onIssueAction: (
@@ -93,7 +91,6 @@ export const TwScopeCardLayer: React.FC<TwScopeCardLayerProps> = ({
93
91
  facet,
94
92
  workflowFacet,
95
93
  activeScopeId,
96
- visibleScopeIds,
97
94
  onClose,
98
95
  onModeChange,
99
96
  onIssueAction,
@@ -122,9 +119,7 @@ export const TwScopeCardLayer: React.FC<TwScopeCardLayerProps> = ({
122
119
  // The effective scope is the pinned one if it still resolves to a
123
120
  // model, else the active one. When a pinned scope disappears
124
121
  // (e.g. the host cleared the overlay), drop the pin.
125
- const models = (workflowFacet?.getAllScopeCardModels() ?? []).filter((model) =>
126
- visibleScopeIds ? visibleScopeIds.has(model.scopeId) : true,
127
- );
122
+ const models = workflowFacet?.getAllScopeCardModels() ?? [];
128
123
 
129
124
  const pinnedModel = pinnedScopeId
130
125
  ? models.find((m) => m.scopeId === pinnedScopeId) ?? null
@@ -46,8 +46,6 @@ export interface TwScopeRailLayerProps {
46
46
  railLaneWidthPx?: number;
47
47
  /** Scope id that should render with the `active` emphasis. */
48
48
  activeScopeId?: string | null;
49
- /** Posture filters shared with the Workflow rail. Omitted means all scopes render. */
50
- visibleScopePostures?: ReadonlySet<ScopeRailPosture>;
51
49
  /**
52
50
  * Fires when the user clicks the rail stripe — opens the scope card.
53
51
  * P0 wires this directly; P1 replaces with card-layer-aware routing.
@@ -89,8 +87,8 @@ const POSTURE_STYLES: Record<ScopeRailPosture, PostureStyle> = {
89
87
  // ---------------------------------------------------------------------------
90
88
 
91
89
  const DEFAULT_RAIL_LANE_PX = 44;
92
- const STRIPE_WIDTH_PX = 6;
93
- const LABEL_WIDTH_PX = 58;
90
+ const STRIPE_WIDTH_PX = 4;
91
+ const LABEL_WIDTH_PX = 40;
94
92
  const STACK_OFFSET_PX = 6;
95
93
 
96
94
  export const TwScopeRailLayer: React.FC<TwScopeRailLayerProps> = ({
@@ -99,15 +97,12 @@ export const TwScopeRailLayer: React.FC<TwScopeRailLayerProps> = ({
99
97
  space,
100
98
  railLaneWidthPx = DEFAULT_RAIL_LANE_PX,
101
99
  activeScopeId,
102
- visibleScopePostures,
103
100
  onStripeClick,
104
101
  onSegmentClick,
105
102
  "data-testid": testId,
106
103
  }) => {
107
104
  const frame = geometryFacet.getRenderFrame() ?? null;
108
- const segments = (workflowFacet?.getAllRailSegments() ?? []).filter((segment) =>
109
- visibleScopePostures ? visibleScopePostures.has(segment.posture) : true,
110
- );
105
+ const segments = workflowFacet?.getAllRailSegments() ?? [];
111
106
 
112
107
  if (!frame || segments.length === 0) {
113
108
  return null;
@@ -228,21 +223,16 @@ export const TwScopeRailLayer: React.FC<TwScopeRailLayerProps> = ({
228
223
  style={projectRectToOverlay(stripeRect, projectorSpace)}
229
224
  />
230
225
  {/* Label pill — revealed on stripe hover via CSS. */}
231
- <button
232
- type="button"
233
- tabIndex={-1}
234
- className={`wre-scope-rail-label wre-scope-rail-label-${style.railToken} ${
235
- isActive ? "wre-scope-rail-label-active" : ""
236
- }`}
226
+ <div
227
+ className={`wre-scope-rail-label wre-scope-rail-label-${style.railToken}`}
237
228
  data-scope-id={segment.scopeId}
238
229
  data-posture={segment.posture}
239
- aria-label={`${style.labelText}${segment.label ? `: ${segment.label}` : ""}`}
240
- onClick={handleActivate}
230
+ aria-hidden="true"
241
231
  style={projectRectToOverlay(labelRect, projectorSpace)}
242
232
  >
243
233
  <span aria-hidden="true" className={`wre-scope-rail-icon wre-scope-rail-icon-${style.icon}`} />
244
234
  <span className="wre-scope-rail-label-text">{style.labelText}</span>
245
- </button>
235
+ </div>
246
236
  </React.Fragment>
247
237
  );
248
238
  })}
@@ -23,7 +23,6 @@ import {
23
23
  TwReviewRailFooter,
24
24
  type TwReviewRailFooterProps,
25
25
  } from "./tw-review-rail-footer";
26
- import type { WorkflowScopeLayerKey } from "../workflow-scope-layers";
27
26
 
28
27
  /**
29
28
  * Review rail with up to four tabs (Workflow / Comments / Changes / Health).
@@ -67,8 +66,6 @@ export interface TwReviewRailProps {
67
66
  */
68
67
  scopeRailSegments?: readonly ScopeRailSegment[];
69
68
  activeScopeId?: string | null;
70
- workflowLayerFilters?: ReadonlySet<WorkflowScopeLayerKey>;
71
- onWorkflowLayerFiltersChange?: (filters: ReadonlySet<WorkflowScopeLayerKey>) => void;
72
69
  /**
73
70
  * Optional host-provided Workflow-tab override. When supplied this
74
71
  * ReactNode replaces the default TwWorkflowTab content while still using
@@ -265,8 +262,6 @@ export function TwReviewRail(props: TwReviewRailProps) {
265
262
  <TwWorkflowTab
266
263
  segments={workflowSegments}
267
264
  activeScopeId={props.activeScopeId ?? null}
268
- enabledLayerFilters={props.workflowLayerFilters}
269
- onEnabledLayerFiltersChange={props.onWorkflowLayerFiltersChange}
270
265
  onOpenScope={props.onOpenScope}
271
266
  />
272
267
  )}
@@ -10,13 +10,6 @@
10
10
 
11
11
  import React from "react";
12
12
  import type { ScopeRailSegment, ScopeRailPosture } from "../../api/public-types";
13
- import {
14
- WORKFLOW_SCOPE_LAYER_FILTERS,
15
- createDefaultWorkflowScopeLayerKeys,
16
- isWorkflowScopePostureVisible,
17
- toggleWorkflowScopeLayerKey,
18
- type WorkflowScopeLayerKey,
19
- } from "../workflow-scope-layers";
20
13
 
21
14
  export interface TwWorkflowTabProps {
22
15
  segments: readonly ScopeRailSegment[];
@@ -28,8 +21,6 @@ export interface TwWorkflowTabProps {
28
21
  * matching overlay card. If omitted, focus sync is not wired.
29
22
  */
30
23
  onActiveScopeChange?: (scopeId: string) => void;
31
- enabledLayerFilters?: ReadonlySet<WorkflowScopeLayerKey>;
32
- onEnabledLayerFiltersChange?: (filters: ReadonlySet<WorkflowScopeLayerKey>) => void;
33
24
  }
34
25
 
35
26
  const POSTURE_META: Record<
@@ -48,13 +39,26 @@ const POSTURE_META: Record<
48
39
  const focusRingClass =
49
40
  "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-2 focus-visible:ring-offset-canvas";
50
41
 
42
+ type ScopeFilterKey = "edit" | "suggest" | "comment" | "view" | "candidate" | "blocked";
43
+
44
+ const SCOPE_FILTERS: ReadonlyArray<{
45
+ key: ScopeFilterKey;
46
+ label: string;
47
+ postures: readonly ScopeRailPosture[];
48
+ }> = [
49
+ { key: "edit", label: "Edit", postures: ["edit"] },
50
+ { key: "suggest", label: "Suggest", postures: ["suggest"] },
51
+ { key: "comment", label: "Comment", postures: ["comment"] },
52
+ { key: "view", label: "Review", postures: ["view"] },
53
+ { key: "candidate", label: "Scheduled", postures: ["candidate"] },
54
+ { key: "blocked", label: "Blocked", postures: ["preserve-only", "blocked-import"] },
55
+ ];
56
+
51
57
  export const TwWorkflowTab: React.FC<TwWorkflowTabProps> = ({
52
58
  segments,
53
59
  activeScopeId,
54
60
  onOpenScope,
55
61
  onActiveScopeChange,
56
- enabledLayerFilters,
57
- onEnabledLayerFiltersChange,
58
62
  }) => {
59
63
  // Dedupe by scopeId so a scope spanning multiple pages shows once.
60
64
  const uniqueSegments = React.useMemo(() => {
@@ -67,31 +71,20 @@ export const TwWorkflowTab: React.FC<TwWorkflowTabProps> = ({
67
71
  return Array.from(byScopeId.values()).sort(compareWorkflowSegments(activeScopeId ?? null));
68
72
  }, [activeScopeId, segments]);
69
73
  const [query, setQuery] = React.useState("");
70
- const [uncontrolledEnabledFilters, setUncontrolledEnabledFilters] = React.useState<
71
- ReadonlySet<WorkflowScopeLayerKey>
72
- >(
73
- createDefaultWorkflowScopeLayerKeys,
74
- );
75
- const activeEnabledFilters = enabledLayerFilters ?? uncontrolledEnabledFilters;
76
- const setEnabledFilters = React.useCallback(
77
- (next: ReadonlySet<WorkflowScopeLayerKey>) => {
78
- if (!enabledLayerFilters) {
79
- setUncontrolledEnabledFilters(next);
80
- }
81
- onEnabledLayerFiltersChange?.(next);
82
- },
83
- [enabledLayerFilters, onEnabledLayerFiltersChange],
74
+ const [enabledFilters, setEnabledFilters] = React.useState<ReadonlySet<ScopeFilterKey>>(
75
+ () => new Set(SCOPE_FILTERS.map((filter) => filter.key)),
84
76
  );
85
77
  const availableFilters = React.useMemo(() => {
86
78
  const presentPostures = new Set(uniqueSegments.map((segment) => segment.posture));
87
- return WORKFLOW_SCOPE_LAYER_FILTERS.filter((filter) =>
79
+ return SCOPE_FILTERS.filter((filter) =>
88
80
  filter.postures.some((posture) => presentPostures.has(posture)),
89
81
  );
90
82
  }, [uniqueSegments]);
91
83
  const visibleSegments = React.useMemo(() => {
92
84
  const normalizedQuery = normalizeScopeQuery(query);
93
85
  return uniqueSegments.filter((segment) => {
94
- if (!isWorkflowScopePostureVisible(segment.posture, activeEnabledFilters)) {
86
+ const filterKey = filterKeyForPosture(segment.posture);
87
+ if (!enabledFilters.has(filterKey)) {
95
88
  return false;
96
89
  }
97
90
  if (!normalizedQuery) {
@@ -99,7 +92,7 @@ export const TwWorkflowTab: React.FC<TwWorkflowTabProps> = ({
99
92
  }
100
93
  return scopeSearchText(segment).includes(normalizedQuery);
101
94
  });
102
- }, [activeEnabledFilters, query, uniqueSegments]);
95
+ }, [enabledFilters, query, uniqueSegments]);
103
96
 
104
97
  if (uniqueSegments.length === 0) {
105
98
  return (
@@ -153,7 +146,7 @@ export const TwWorkflowTab: React.FC<TwWorkflowTabProps> = ({
153
146
  role="group"
154
147
  >
155
148
  {availableFilters.map((filter) => {
156
- const isEnabled = activeEnabledFilters.has(filter.key);
149
+ const isEnabled = enabledFilters.has(filter.key);
157
150
  return (
158
151
  <button
159
152
  key={filter.key}
@@ -167,9 +160,15 @@ export const TwWorkflowTab: React.FC<TwWorkflowTabProps> = ({
167
160
  ].join(" ")}
168
161
  data-testid={`workflow-scope-filter-${filter.key}`}
169
162
  onClick={() => {
170
- setEnabledFilters(
171
- toggleWorkflowScopeLayerKey(activeEnabledFilters, filter.key),
172
- );
163
+ setEnabledFilters((current) => {
164
+ const next = new Set(current);
165
+ if (next.has(filter.key)) {
166
+ next.delete(filter.key);
167
+ } else {
168
+ next.add(filter.key);
169
+ }
170
+ return next;
171
+ });
173
172
  }}
174
173
  >
175
174
  {filter.label}
@@ -246,6 +245,13 @@ function compareWorkflowSegments(activeScopeId: string | null) {
246
245
  };
247
246
  }
248
247
 
248
+ function filterKeyForPosture(posture: ScopeRailPosture): ScopeFilterKey {
249
+ if (posture === "preserve-only" || posture === "blocked-import") {
250
+ return "blocked";
251
+ }
252
+ return posture;
253
+ }
254
+
249
255
  function normalizeScopeQuery(value: string): string {
250
256
  return value.trim().toLocaleLowerCase();
251
257
  }
@@ -475,19 +475,19 @@
475
475
  }
476
476
 
477
477
  .wre-scope-rail-tint-accent {
478
- background: color-mix(in srgb, var(--color-accent) 20%, transparent);
478
+ background: color-mix(in srgb, var(--color-accent) 12%, transparent);
479
479
  }
480
480
  .wre-scope-rail-tint-warning {
481
- background: color-mix(in srgb, var(--color-warning) 23%, transparent);
481
+ background: color-mix(in srgb, var(--color-warning) 14%, transparent);
482
482
  }
483
483
  .wre-scope-rail-tint-insert {
484
- background: color-mix(in srgb, var(--color-insert) 20%, transparent);
484
+ background: color-mix(in srgb, var(--color-insert) 12%, transparent);
485
485
  }
486
486
  .wre-scope-rail-tint-secondary {
487
- background: color-mix(in srgb, var(--color-secondary) 16%, transparent);
487
+ background: color-mix(in srgb, var(--color-secondary) 9%, transparent);
488
488
  }
489
489
  .wre-scope-rail-tint-danger {
490
- background: color-mix(in srgb, var(--color-danger) 24%, transparent);
490
+ background: color-mix(in srgb, var(--color-danger) 14%, transparent);
491
491
  }
492
492
 
493
493
  /* §3.7 canonical scope families */
@@ -543,22 +543,22 @@
543
543
  /*
544
544
  * ─── Scope rail stripe ───
545
545
  *
546
- * The rail stripe is the rest-state representation of a scope: a 6px
546
+ * The rail stripe is the rest-state representation of a scope: a 4px
547
547
  * color stripe in the gutter lane. Posture color comes from the
548
548
  * accent/warning/insert/secondary/danger tokens. Hover widens the
549
549
  * stripe via transform (zero layout cost) and reveals the label pill.
550
550
  */
551
551
  .wre-scope-rail-stripe {
552
552
  position: absolute;
553
- width: 6px;
554
- border-radius: 999px;
553
+ width: 4px;
554
+ border-radius: 2px;
555
555
  background: currentColor;
556
556
  pointer-events: auto;
557
557
  cursor: pointer;
558
558
  z-index: 1;
559
559
  transform-origin: left center;
560
560
  transition: transform 120ms ease-out, opacity 120ms ease-out;
561
- opacity: 0.9;
561
+ opacity: 0.75;
562
562
  /* Reset button defaults. */
563
563
  border: none;
564
564
  padding: 0;
@@ -568,22 +568,16 @@
568
568
  background-clip: padding-box;
569
569
  }
570
570
 
571
- .wre-scope-rail-stripe::before {
572
- content: "";
573
- position: absolute;
574
- inset: -5px -10px;
575
- }
576
-
577
571
  .wre-scope-rail-stripe:hover,
578
572
  .wre-scope-rail-stripe:focus-visible {
579
- transform: scaleX(1.45);
573
+ transform: scaleX(1.5);
580
574
  opacity: 1;
581
575
  outline: none;
582
576
  }
583
577
 
584
578
  .wre-scope-rail-stripe-active {
585
579
  opacity: 1;
586
- transform: scaleX(1.6);
580
+ transform: scaleX(1.75);
587
581
  }
588
582
 
589
583
  .wre-scope-rail-stripe.wre-scope-rail-label-accent { color: var(--color-accent); }
@@ -629,8 +623,6 @@
629
623
  pointer-events: none;
630
624
  transition: opacity 140ms ease-out, transform 140ms ease-out;
631
625
  transform: translateX(-4px);
632
- margin: 0;
633
- font-family: inherit;
634
626
  }
635
627
 
636
628
  .wre-scope-rail-stripe:hover + .wre-scope-rail-label,
@@ -700,12 +692,7 @@
700
692
  }
701
693
 
702
694
  .wre-scope-rail-label-active {
703
- opacity: 1;
704
- pointer-events: auto;
705
- transform: translateX(0);
706
- box-shadow:
707
- 0 0 0 1px color-mix(in srgb, currentColor 42%, transparent),
708
- 0 8px 22px color-mix(in srgb, currentColor 14%, transparent);
695
+ box-shadow: 0 0 0 1px color-mix(in srgb, currentColor 30%, transparent);
709
696
  }
710
697
 
711
698
  .wre-scope-rail-icon {
@@ -68,11 +68,6 @@ import { useLayoutFacetRenderSignal } from "./review-workspace/use-layout-facet-
68
68
  import { useScrollRootCapture } from "./review-workspace/use-scroll-root-capture.ts";
69
69
  import { usePmSurfaceCapture } from "./review-workspace/use-pm-surface-capture.ts";
70
70
  import { TwReviewWorkspaceNavigator } from "./review-workspace/tw-review-workspace-navigator.tsx";
71
- import {
72
- createDefaultWorkflowScopeLayerKeys,
73
- workflowScopePosturesForLayerKeys,
74
- type WorkflowScopeLayerKey,
75
- } from "./workflow-scope-layers";
76
71
 
77
72
  export {
78
73
  FRAME_PX_PER_TWIP_AT_96DPI,
@@ -179,13 +174,6 @@ export function TwReviewWorkspace(inputProps: TwReviewWorkspaceProps) {
179
174
  useRef<TwWorkspaceChromeHostController | null>(null);
180
175
  const [shellModeOverride, setShellModeOverride] =
181
176
  useState<ShellHeaderMode | null>(null);
182
- const [workflowLayerFilters, setWorkflowLayerFilters] = useState<
183
- ReadonlySet<WorkflowScopeLayerKey>
184
- >(createDefaultWorkflowScopeLayerKeys);
185
- const visibleScopePostures = useMemo(
186
- () => workflowScopePosturesForLayerKeys(workflowLayerFilters),
187
- [workflowLayerFilters],
188
- );
189
177
  // P8.11 — body slot wrapping `{props.document}` (the PM surface) + scroll
190
178
  // root ref. The chrome layer's `TwPageStackChromeLayer` needs both to
191
179
  // measure per-page rects and to reparent PM's DOM node across band
@@ -1178,7 +1166,6 @@ export function TwReviewWorkspace(inputProps: TwReviewWorkspaceProps) {
1178
1166
  onSetColumnWidth={props.onSetColumnWidth}
1179
1167
  onSetRowHeight={props.onSetRowHeight}
1180
1168
  activeScopeId={activeScopeId}
1181
- visibleScopePostures={visibleScopePostures}
1182
1169
  editorRole={viewState.editorRole}
1183
1170
  scopeCardScopeTagEditor={props.scopeCardScopeTagEditor}
1184
1171
  onScopeStripeClick={handleScopeStripeClick}
@@ -1285,8 +1272,6 @@ export function TwReviewWorkspace(inputProps: TwReviewWorkspaceProps) {
1285
1272
  // `getAllScopeRailSegments` (methods removed in v40 / Slice 4C).
1286
1273
  scopeRailSegments: props.workflowFacet?.getAllRailSegments() ?? [],
1287
1274
  activeScopeId,
1288
- workflowLayerFilters,
1289
- onWorkflowLayerFiltersChange: setWorkflowLayerFilters,
1290
1275
  onOpenScope: (segment) => {
1291
1276
  handleScopeStripeClick({ scopeId: segment.scopeId });
1292
1277
  },
@@ -1,70 +0,0 @@
1
- import type { ScopeRailPosture } from "../api/public-types";
2
-
3
- export type WorkflowScopeLayerKey =
4
- | "edit"
5
- | "suggest"
6
- | "comment"
7
- | "view"
8
- | "candidate"
9
- | "blocked";
10
-
11
- export interface WorkflowScopeLayerFilter {
12
- key: WorkflowScopeLayerKey;
13
- label: string;
14
- postures: readonly ScopeRailPosture[];
15
- }
16
-
17
- export const WORKFLOW_SCOPE_LAYER_FILTERS: readonly WorkflowScopeLayerFilter[] = [
18
- { key: "edit", label: "Edit", postures: ["edit"] },
19
- { key: "suggest", label: "Suggest", postures: ["suggest"] },
20
- { key: "comment", label: "Comment", postures: ["comment"] },
21
- { key: "view", label: "Review", postures: ["view"] },
22
- { key: "candidate", label: "Scheduled", postures: ["candidate"] },
23
- { key: "blocked", label: "Blocked", postures: ["preserve-only", "blocked-import"] },
24
- ];
25
-
26
- export function createDefaultWorkflowScopeLayerKeys(): ReadonlySet<WorkflowScopeLayerKey> {
27
- return new Set(WORKFLOW_SCOPE_LAYER_FILTERS.map((filter) => filter.key));
28
- }
29
-
30
- export function workflowScopeLayerKeyForPosture(
31
- posture: ScopeRailPosture,
32
- ): WorkflowScopeLayerKey {
33
- if (posture === "preserve-only" || posture === "blocked-import") {
34
- return "blocked";
35
- }
36
- return posture;
37
- }
38
-
39
- export function isWorkflowScopePostureVisible(
40
- posture: ScopeRailPosture,
41
- enabledLayers: ReadonlySet<WorkflowScopeLayerKey>,
42
- ): boolean {
43
- return enabledLayers.has(workflowScopeLayerKeyForPosture(posture));
44
- }
45
-
46
- export function workflowScopePosturesForLayerKeys(
47
- enabledLayers: ReadonlySet<WorkflowScopeLayerKey>,
48
- ): ReadonlySet<ScopeRailPosture> {
49
- const postures = new Set<ScopeRailPosture>();
50
- for (const filter of WORKFLOW_SCOPE_LAYER_FILTERS) {
51
- if (!enabledLayers.has(filter.key)) continue;
52
- for (const posture of filter.postures) {
53
- postures.add(posture);
54
- }
55
- }
56
- return postures;
57
- }
58
-
59
- export function toggleWorkflowScopeLayerKey(
60
- current: ReadonlySet<WorkflowScopeLayerKey>,
61
- key: WorkflowScopeLayerKey,
62
- ): ReadonlySet<WorkflowScopeLayerKey> {
63
- const next = new Set(current);
64
- if (next.has(key)) {
65
- next.delete(key);
66
- } else {
67
- next.add(key);
68
- }
69
- return next;
70
- }