@beyondwork/docx-react-component 1.0.53 → 1.0.55
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 +1 -1
- package/src/api/public-types.ts +125 -7
- package/src/index.ts +5 -0
- package/src/io/docx-session.ts +27 -3
- package/src/io/normalize/normalize-text.ts +1 -0
- package/src/io/ooxml/parse-field-switches.ts +134 -0
- package/src/io/ooxml/parse-fields.ts +28 -2
- package/src/model/canonical-document.ts +13 -2
- package/src/runtime/chart/chart-model-store.ts +88 -0
- package/src/runtime/chart/chart-snapshot.ts +239 -0
- package/src/runtime/collab/checkpoint-store.ts +1 -1
- package/src/runtime/collab/event-types.ts +4 -0
- package/src/runtime/collab/runtime-collab-sync.ts +1 -2
- package/src/runtime/document-runtime.ts +115 -13
- package/src/runtime/layout/inert-layout-facet.ts +1 -0
- package/src/runtime/layout/layout-engine-version.ts +58 -1
- package/src/runtime/layout/layout-invalidation.ts +150 -30
- package/src/runtime/layout/page-graph.ts +19 -0
- package/src/runtime/layout/paginated-layout-engine.ts +128 -19
- package/src/runtime/layout/project-block-fragments.ts +27 -0
- package/src/runtime/layout/public-facet.ts +27 -0
- package/src/runtime/page-number-format.ts +207 -0
- package/src/runtime/render/render-frame-diff.ts +38 -2
- package/src/runtime/surface-projection.ts +32 -3
- package/src/ui/WordReviewEditor.tsx +57 -3
- package/src/ui/headless/comment-decoration-model.ts +60 -5
- package/src/ui/headless/revision-decoration-model.ts +94 -6
- package/src/ui/shared/revision-filters.ts +16 -6
- package/src/ui-tailwind/chart/ChartSurface.tsx +236 -0
- package/src/ui-tailwind/chart/layout/axis-layout.ts +17 -9
- package/src/ui-tailwind/chart/layout/legend-layout.ts +231 -0
- package/src/ui-tailwind/chart/layout/plot-area.ts +152 -59
- package/src/ui-tailwind/chart/layout/title-layout.ts +184 -0
- package/src/ui-tailwind/chart/render/area.tsx +277 -0
- package/src/ui-tailwind/chart/render/bar-column.tsx +356 -0
- package/src/ui-tailwind/chart/render/bubble.tsx +134 -0
- package/src/ui-tailwind/chart/render/combo.tsx +85 -0
- package/src/ui-tailwind/chart/render/data-labels.tsx +513 -0
- package/src/ui-tailwind/chart/render/font-metrics.ts +298 -0
- package/src/ui-tailwind/chart/render/gridlines.ts +228 -0
- package/src/ui-tailwind/chart/render/line.tsx +363 -0
- package/src/ui-tailwind/chart/render/number-format.ts +120 -16
- package/src/ui-tailwind/chart/render/pie.tsx +275 -0
- package/src/ui-tailwind/chart/render/progressive-render.ts +103 -0
- package/src/ui-tailwind/chart/render/scatter.tsx +228 -0
- package/src/ui-tailwind/chart/render/smooth-curve.ts +101 -0
- package/src/ui-tailwind/chart/render/svg-primitives.ts +378 -0
- package/src/ui-tailwind/chart/render/unsupported.tsx +126 -0
- package/src/ui-tailwind/chrome/collab-audience-chip.tsx +11 -0
- package/src/ui-tailwind/chrome/collab-negotiation-action-bar.tsx +44 -18
- package/src/ui-tailwind/chrome/collab-presence-strip.tsx +68 -7
- package/src/ui-tailwind/chrome/collab-role-chip.tsx +21 -2
- package/src/ui-tailwind/chrome/collab-tamper-banner.tsx +20 -3
- package/src/ui-tailwind/chrome/tw-alert-banner.tsx +102 -37
- package/src/ui-tailwind/chrome/tw-command-palette.tsx +358 -0
- package/src/ui-tailwind/chrome/tw-comment-preview.tsx +108 -0
- package/src/ui-tailwind/chrome/tw-context-menu.tsx +227 -0
- package/src/ui-tailwind/chrome/tw-display-mode-selector.tsx +136 -0
- package/src/ui-tailwind/chrome/tw-empty-state.tsx +76 -0
- package/src/ui-tailwind/chrome/tw-image-context-toolbar.tsx +30 -16
- package/src/ui-tailwind/chrome/tw-object-context-toolbar.tsx +23 -4
- package/src/ui-tailwind/chrome/tw-paste-drop-toast.tsx +113 -0
- package/src/ui-tailwind/chrome/tw-revision-hover-preview.tsx +150 -0
- package/src/ui-tailwind/chrome/tw-selection-tool-formatting.tsx +2 -0
- package/src/ui-tailwind/chrome/tw-selection-tool-host.tsx +38 -2
- package/src/ui-tailwind/chrome/tw-selection-tool-placement.ts +15 -3
- package/src/ui-tailwind/chrome/tw-selection-toolbar.tsx +32 -20
- package/src/ui-tailwind/chrome/tw-shortcut-hint.tsx +68 -0
- package/src/ui-tailwind/chrome/tw-suggestion-card.tsx +10 -10
- package/src/ui-tailwind/chrome/tw-table-context-toolbar.tsx +26 -5
- package/src/ui-tailwind/chrome/tw-table-grip-layer.tsx +29 -22
- package/src/ui-tailwind/chrome/tw-unsaved-modal.tsx +72 -10
- package/src/ui-tailwind/chrome-overlay/tw-scope-card.tsx +33 -18
- package/src/ui-tailwind/chrome-overlay/tw-table-continuation-header.tsx +94 -0
- package/src/ui-tailwind/editor-surface/chart-node-view.tsx +90 -0
- package/src/ui-tailwind/editor-surface/pm-page-break-decorations.ts +20 -7
- package/src/ui-tailwind/editor-surface/pm-schema.ts +4 -0
- package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +14 -0
- package/src/ui-tailwind/editor-surface/scroll-anchor.ts +93 -0
- package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +2 -1
- package/src/ui-tailwind/index.ts +11 -0
- package/src/ui-tailwind/page-stack/tw-page-chrome-entry.tsx +52 -2
- package/src/ui-tailwind/page-stack/tw-page-footer-band.tsx +13 -0
- package/src/ui-tailwind/page-stack/tw-page-header-band.tsx +13 -0
- package/src/ui-tailwind/page-stack/tw-page-stack-chrome-layer.tsx +8 -0
- package/src/ui-tailwind/review/tw-comment-sidebar.tsx +83 -32
- package/src/ui-tailwind/review/tw-health-panel.tsx +174 -109
- package/src/ui-tailwind/review/tw-rail-card.tsx +9 -1
- package/src/ui-tailwind/review/tw-review-rail.tsx +36 -42
- package/src/ui-tailwind/review/tw-revision-sidebar.tsx +189 -101
- package/src/ui-tailwind/review/tw-workflow-tab.tsx +11 -1
- package/src/ui-tailwind/status/tw-status-bar.tsx +114 -46
- package/src/ui-tailwind/theme/editor-theme.css +249 -22
- package/src/ui-tailwind/toolbar/tw-role-action-region.tsx +14 -1
- package/src/ui-tailwind/toolbar/tw-shell-header.tsx +73 -32
- package/src/ui-tailwind/toolbar/tw-toolbar-icon-button.tsx +49 -9
- package/src/ui-tailwind/toolbar/tw-toolbar.tsx +178 -14
- package/src/ui-tailwind/tw-review-workspace.tsx +39 -6
- package/src/ui-tailwind/chrome/review-queue-bar.tsx +0 -85
|
@@ -51,6 +51,12 @@ export function CollabPresenceStrip({
|
|
|
51
51
|
const rootClass = [
|
|
52
52
|
"tw-collab-presence-strip",
|
|
53
53
|
observer ? "tw-collab-presence-observer" : null,
|
|
54
|
+
// Lane 6b §6b.S6 — token-bound default styling. BEM classes above
|
|
55
|
+
// stay as host-CSS override hooks; the inline Tailwind below paints
|
|
56
|
+
// the calm presence strip and dims under observer posture.
|
|
57
|
+
"inline-flex items-center gap-2 px-2 py-1",
|
|
58
|
+
"text-[11px] text-[var(--color-text-tertiary)]",
|
|
59
|
+
observer ? "opacity-60" : null,
|
|
54
60
|
className ?? null,
|
|
55
61
|
]
|
|
56
62
|
.filter((value): value is string => value !== null)
|
|
@@ -64,14 +70,23 @@ export function CollabPresenceStrip({
|
|
|
64
70
|
aria-label="Collaborators"
|
|
65
71
|
data-observer={observer ? "true" : "false"}
|
|
66
72
|
>
|
|
67
|
-
<ul
|
|
73
|
+
<ul
|
|
74
|
+
className="tw-collab-presence-strip__peers flex items-center gap-1 list-none m-0 p-0"
|
|
75
|
+
aria-live="polite"
|
|
76
|
+
>
|
|
68
77
|
{tiles.map((peer) => (
|
|
69
78
|
<CollabPresencePeerTile key={peer.userId} peer={peer} />
|
|
70
79
|
))}
|
|
71
80
|
</ul>
|
|
72
81
|
{overflow > 0 ? (
|
|
73
82
|
<span
|
|
74
|
-
className=
|
|
83
|
+
className={[
|
|
84
|
+
"tw-collab-presence-strip__overflow",
|
|
85
|
+
"inline-flex items-center justify-center",
|
|
86
|
+
"h-5 min-w-5 px-1 rounded-[var(--radius-pill)]",
|
|
87
|
+
"bg-[var(--color-bg-muted)] text-[var(--color-text-secondary)]",
|
|
88
|
+
"text-[10px] font-medium",
|
|
89
|
+
].join(" ")}
|
|
75
90
|
aria-label={`${overflow} additional peers`}
|
|
76
91
|
data-testid="collab-presence-overflow"
|
|
77
92
|
>
|
|
@@ -90,19 +105,38 @@ function CollabPresencePeerTile({ peer }: { peer: AwarenessPeer }) {
|
|
|
90
105
|
const initials = computeInitials(peer.displayName);
|
|
91
106
|
return (
|
|
92
107
|
<li
|
|
93
|
-
className=
|
|
108
|
+
className={[
|
|
109
|
+
"tw-collab-presence-strip__tile",
|
|
110
|
+
"inline-flex items-center gap-1",
|
|
111
|
+
].join(" ")}
|
|
94
112
|
data-testid={`collab-presence-peer-${peer.userId}`}
|
|
95
113
|
data-author-kind={peer.authorKind}
|
|
96
114
|
data-active-story={peer.activeStoryId ?? ""}
|
|
97
115
|
title={peer.displayName}
|
|
98
116
|
>
|
|
99
|
-
<span
|
|
117
|
+
<span
|
|
118
|
+
className={[
|
|
119
|
+
"tw-collab-presence-strip__avatar",
|
|
120
|
+
"inline-flex h-5 w-5 items-center justify-center",
|
|
121
|
+
"rounded-[var(--radius-pill)]",
|
|
122
|
+
"bg-[var(--color-accent-soft)] text-[var(--color-accent-primary)]",
|
|
123
|
+
"text-[10px] font-semibold",
|
|
124
|
+
].join(" ")}
|
|
125
|
+
aria-hidden="true"
|
|
126
|
+
>
|
|
100
127
|
{initials}
|
|
101
128
|
</span>
|
|
102
|
-
<span className="tw-collab-presence-strip__name">
|
|
129
|
+
<span className="tw-collab-presence-strip__name sr-only">
|
|
130
|
+
{peer.displayName}
|
|
131
|
+
</span>
|
|
103
132
|
{peer.authorKind !== "human" ? (
|
|
104
133
|
<span
|
|
105
|
-
className=
|
|
134
|
+
className={[
|
|
135
|
+
"tw-collab-presence-strip__badge",
|
|
136
|
+
"inline-flex items-center px-1 rounded-[var(--radius-pill)]",
|
|
137
|
+
"bg-[var(--color-bg-muted)] text-[var(--color-text-tertiary)]",
|
|
138
|
+
"text-[9px] font-medium uppercase tracking-wide",
|
|
139
|
+
].join(" ")}
|
|
106
140
|
data-testid={`collab-presence-peer-${peer.userId}-badge`}
|
|
107
141
|
>
|
|
108
142
|
{peer.authorKind}
|
|
@@ -112,6 +146,17 @@ function CollabPresencePeerTile({ peer }: { peer: AwarenessPeer }) {
|
|
|
112
146
|
);
|
|
113
147
|
}
|
|
114
148
|
|
|
149
|
+
/**
|
|
150
|
+
* Lane 6b §6b.S6 — transport chip tone binding.
|
|
151
|
+
*
|
|
152
|
+
* connected / attached → calm tertiary (no paint)
|
|
153
|
+
* syncing / detached → semantic-warning-soft + warning glyph
|
|
154
|
+
* offline → semantic-error-soft + error glyph
|
|
155
|
+
* none → muted (disconnected intent)
|
|
156
|
+
*
|
|
157
|
+
* Health is the silent default — only degraded states paint, so operators
|
|
158
|
+
* who glance at the strip only see colour when something is off.
|
|
159
|
+
*/
|
|
115
160
|
function CollabTransportChip({
|
|
116
161
|
status,
|
|
117
162
|
queuedLocalEvents,
|
|
@@ -123,9 +168,25 @@ function CollabTransportChip({
|
|
|
123
168
|
status === "offline" && queuedLocalEvents > 0
|
|
124
169
|
? `offline (${queuedLocalEvents} queued)`
|
|
125
170
|
: status;
|
|
171
|
+
|
|
172
|
+
const toneClass =
|
|
173
|
+
status === "offline"
|
|
174
|
+
? "bg-[var(--color-semantic-error-soft)] text-[var(--color-semantic-error)]"
|
|
175
|
+
: status === "syncing"
|
|
176
|
+
? "bg-[var(--color-semantic-warning-soft)] text-[var(--color-semantic-warning)]"
|
|
177
|
+
: // connected — calm tertiary
|
|
178
|
+
"text-[var(--color-text-tertiary)]";
|
|
179
|
+
|
|
126
180
|
return (
|
|
127
181
|
<span
|
|
128
|
-
className={
|
|
182
|
+
className={[
|
|
183
|
+
"tw-collab-presence-strip__transport",
|
|
184
|
+
`tw-collab-presence-strip__transport--${status}`,
|
|
185
|
+
"inline-flex items-center px-2 py-0.5 rounded-[var(--radius-pill)]",
|
|
186
|
+
"text-[10px] font-medium uppercase tracking-wide",
|
|
187
|
+
"transition-colors duration-[var(--motion-fast)]",
|
|
188
|
+
toneClass,
|
|
189
|
+
].join(" ")}
|
|
129
190
|
data-testid="collab-presence-transport"
|
|
130
191
|
data-status={status}
|
|
131
192
|
data-queued={queuedLocalEvents.toString()}
|
|
@@ -31,6 +31,14 @@ export function CollabRoleChip({
|
|
|
31
31
|
"tw-collab-role-chip",
|
|
32
32
|
`tw-collab-role-chip--${posture.role}`,
|
|
33
33
|
offline ? "tw-collab-role-chip--offline" : null,
|
|
34
|
+
// Lane 6b §6b.S6 — calm token-bound chip. Offline dims via opacity;
|
|
35
|
+
// the BEM classes above stay as hooks for host CSS to tint the role.
|
|
36
|
+
"inline-flex items-center gap-1.5 px-2 py-0.5",
|
|
37
|
+
"rounded-[var(--radius-pill)]",
|
|
38
|
+
"bg-[var(--color-bg-muted)] text-[var(--color-text-secondary)]",
|
|
39
|
+
"text-[11px] font-medium",
|
|
40
|
+
"transition-colors duration-[var(--motion-fast)]",
|
|
41
|
+
offline ? "opacity-60" : null,
|
|
34
42
|
className ?? null,
|
|
35
43
|
]
|
|
36
44
|
.filter((v): v is string => v !== null)
|
|
@@ -53,8 +61,19 @@ export function CollabRoleChip({
|
|
|
53
61
|
aria-label={`Role ${posture.role}, ${peerLabel}`}
|
|
54
62
|
title={`${posture.role} · ${peerLabel}`}
|
|
55
63
|
>
|
|
56
|
-
<span className="tw-collab-role-chip__role">
|
|
57
|
-
|
|
64
|
+
<span className="tw-collab-role-chip__role capitalize">
|
|
65
|
+
{posture.role}
|
|
66
|
+
</span>
|
|
67
|
+
<span
|
|
68
|
+
className={[
|
|
69
|
+
"tw-collab-role-chip__peers",
|
|
70
|
+
"inline-flex items-center justify-center",
|
|
71
|
+
"h-4 min-w-4 px-1 rounded-[var(--radius-pill)]",
|
|
72
|
+
"bg-[var(--color-accent-soft)] text-[var(--color-accent-primary)]",
|
|
73
|
+
"text-[10px] font-semibold leading-none",
|
|
74
|
+
].join(" ")}
|
|
75
|
+
aria-hidden="true"
|
|
76
|
+
>
|
|
58
77
|
{posture.peers}
|
|
59
78
|
</span>
|
|
60
79
|
</span>
|
|
@@ -35,6 +35,12 @@ export function CollabTamperBanner({
|
|
|
35
35
|
|
|
36
36
|
const rootClass = [
|
|
37
37
|
"tw-collab-tamper-banner",
|
|
38
|
+
// Lane 6b §6b.S6 — single-action semantic-error banner. Designsystem
|
|
39
|
+
// §6.21 calls for the tamper surface to be the loudest collab chrome
|
|
40
|
+
// element (assertive) with a single recovery affordance.
|
|
41
|
+
"flex items-center gap-3 px-3 py-2 text-xs",
|
|
42
|
+
"bg-[var(--color-semantic-error-soft)] text-[var(--color-semantic-error)]",
|
|
43
|
+
"transition-colors duration-[var(--motion-fast)]",
|
|
38
44
|
className ?? null,
|
|
39
45
|
]
|
|
40
46
|
.filter((v): v is string => v !== null)
|
|
@@ -48,16 +54,27 @@ export function CollabTamperBanner({
|
|
|
48
54
|
role="alert"
|
|
49
55
|
aria-live="assertive"
|
|
50
56
|
>
|
|
51
|
-
<span
|
|
57
|
+
<span
|
|
58
|
+
className="tw-collab-tamper-banner__icon shrink-0"
|
|
59
|
+
aria-hidden="true"
|
|
60
|
+
>
|
|
52
61
|
⚠
|
|
53
62
|
</span>
|
|
54
|
-
<span className="tw-collab-tamper-banner__message">
|
|
63
|
+
<span className="tw-collab-tamper-banner__message flex-1">
|
|
55
64
|
Metadata integrity check failed. The workflow payload was modified
|
|
56
65
|
outside the editor. Mutations are blocked until you acknowledge.
|
|
57
66
|
</span>
|
|
58
67
|
<button
|
|
59
68
|
type="button"
|
|
60
|
-
className=
|
|
69
|
+
className={[
|
|
70
|
+
"tw-collab-tamper-banner__ack",
|
|
71
|
+
"shrink-0 inline-flex items-center",
|
|
72
|
+
"rounded-[var(--radius-sm)] px-3 py-1 text-xs font-semibold",
|
|
73
|
+
"bg-[var(--color-semantic-error)] text-[var(--color-text-on-accent)]",
|
|
74
|
+
"hover:bg-[var(--color-semantic-error)]/90",
|
|
75
|
+
"transition-colors duration-[var(--motion-fast)]",
|
|
76
|
+
"focus-visible:outline-none focus-visible:shadow-[var(--shadow-focus)]",
|
|
77
|
+
].join(" ")}
|
|
61
78
|
data-testid="collab-tamper-banner-ack"
|
|
62
79
|
onClick={onAcknowledge}
|
|
63
80
|
>
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
import React from "react";
|
|
1
|
+
import React, { type ReactNode } from "react";
|
|
2
2
|
import { AlertTriangle, XCircle } from "lucide-react";
|
|
3
3
|
|
|
4
|
-
import type {
|
|
4
|
+
import type {
|
|
5
|
+
RuntimeRenderSnapshot,
|
|
6
|
+
WorkflowBlockedCommandReason,
|
|
7
|
+
} from "../../api/public-types";
|
|
5
8
|
|
|
6
9
|
export interface TwAlertBannerProps {
|
|
7
10
|
snapshot: RuntimeRenderSnapshot;
|
|
@@ -9,55 +12,117 @@ export interface TwAlertBannerProps {
|
|
|
9
12
|
workflowBlockedReasons?: WorkflowBlockedCommandReason[];
|
|
10
13
|
}
|
|
11
14
|
|
|
12
|
-
|
|
15
|
+
/**
|
|
16
|
+
* TwAlertBanner — single-row chrome strip above the toolbar (designsystem §6.17).
|
|
17
|
+
*
|
|
18
|
+
* Lane 6b §6b.S5 contract:
|
|
19
|
+
* (1) ONE banner at a time, highest severity wins.
|
|
20
|
+
* (2) Precedence: fatalError > blockExport > workflowBlocked > preserveOnly.
|
|
21
|
+
* (3) Severity tones bind Lane 6a `--color-semantic-*` tokens — no legacy
|
|
22
|
+
* `bg-danger-soft` / `bg-warning-soft` / `bg-amber-50` class names.
|
|
23
|
+
* (4) Error (fatal / blockExport) → `--color-semantic-error(-soft)`
|
|
24
|
+
* Warning (workflow / preserve) → `--color-semantic-warning(-soft)`
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
type BannerSeverity = "error" | "warning";
|
|
28
|
+
|
|
29
|
+
interface BannerRender {
|
|
30
|
+
severity: BannerSeverity;
|
|
31
|
+
icon: ReactNode;
|
|
32
|
+
message: ReactNode;
|
|
33
|
+
testid: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function renderBanner(variant: BannerRender): React.ReactElement {
|
|
37
|
+
const { severity, icon, message, testid } = variant;
|
|
38
|
+
const toneClass =
|
|
39
|
+
severity === "error"
|
|
40
|
+
? "bg-[var(--color-semantic-error-soft)] text-[var(--color-semantic-error)]"
|
|
41
|
+
: "bg-[var(--color-semantic-warning-soft)] text-[var(--color-semantic-warning)]";
|
|
42
|
+
return (
|
|
43
|
+
<div
|
|
44
|
+
role="alert"
|
|
45
|
+
aria-live={severity === "error" ? "assertive" : "polite"}
|
|
46
|
+
data-testid={testid}
|
|
47
|
+
data-severity={severity}
|
|
48
|
+
className={[
|
|
49
|
+
"flex items-center gap-2 px-3 py-1.5 text-xs",
|
|
50
|
+
"transition-colors duration-[var(--motion-fast)]",
|
|
51
|
+
toneClass,
|
|
52
|
+
].join(" ")}
|
|
53
|
+
>
|
|
54
|
+
{icon}
|
|
55
|
+
<span>{message}</span>
|
|
56
|
+
</div>
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function TwAlertBanner(
|
|
61
|
+
props: TwAlertBannerProps,
|
|
62
|
+
): React.ReactElement | null {
|
|
13
63
|
const { snapshot, preserveOnlyCount, workflowBlockedReasons = [] } = props;
|
|
14
64
|
|
|
65
|
+
// 1. Fatal runtime error — highest precedence.
|
|
15
66
|
if (snapshot.fatalError) {
|
|
16
|
-
return (
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
);
|
|
67
|
+
return renderBanner({
|
|
68
|
+
severity: "error",
|
|
69
|
+
icon: <XCircle className="h-3.5 w-3.5 shrink-0" aria-hidden="true" />,
|
|
70
|
+
message: snapshot.fatalError.message,
|
|
71
|
+
testid: "tw-alert-banner__fatal",
|
|
72
|
+
});
|
|
22
73
|
}
|
|
23
74
|
|
|
75
|
+
// 2. Export blocked — compatibility engine says we can't round-trip.
|
|
24
76
|
if (snapshot.compatibility.blockExport) {
|
|
25
|
-
return (
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
77
|
+
return renderBanner({
|
|
78
|
+
severity: "error",
|
|
79
|
+
icon: <XCircle className="h-3.5 w-3.5 shrink-0" aria-hidden="true" />,
|
|
80
|
+
message: (
|
|
81
|
+
<>
|
|
29
82
|
Export blocked —{" "}
|
|
30
|
-
{snapshot.compatibility.blockExportReasons[0] ??
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
if (preserveOnlyCount > 0) {
|
|
37
|
-
return (
|
|
38
|
-
<div className="flex items-center gap-2 px-3 py-1.5 bg-warning-soft text-comment text-xs">
|
|
39
|
-
<AlertTriangle className="h-3.5 w-3.5 shrink-0" />
|
|
40
|
-
<span>
|
|
41
|
-
{preserveOnlyCount} preserve-only feature
|
|
42
|
-
{preserveOnlyCount !== 1 ? "s" : ""} detected
|
|
43
|
-
</span>
|
|
44
|
-
</div>
|
|
45
|
-
);
|
|
83
|
+
{snapshot.compatibility.blockExportReasons[0] ??
|
|
84
|
+
"unsupported content"}
|
|
85
|
+
</>
|
|
86
|
+
),
|
|
87
|
+
testid: "tw-alert-banner__block-export",
|
|
88
|
+
});
|
|
46
89
|
}
|
|
47
90
|
|
|
91
|
+
// 3. Workflow blocked — host policy refuses a command, per reasons.
|
|
48
92
|
if (workflowBlockedReasons.length > 0) {
|
|
49
|
-
const firstReason = workflowBlockedReasons[0]
|
|
50
|
-
return (
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
<
|
|
93
|
+
const firstReason = workflowBlockedReasons[0]!;
|
|
94
|
+
return renderBanner({
|
|
95
|
+
severity: "warning",
|
|
96
|
+
icon: (
|
|
97
|
+
<AlertTriangle className="h-3.5 w-3.5 shrink-0" aria-hidden="true" />
|
|
98
|
+
),
|
|
99
|
+
message: (
|
|
100
|
+
<>
|
|
54
101
|
{firstReason.message}
|
|
55
102
|
{workflowBlockedReasons.length > 1
|
|
56
103
|
? ` (+${workflowBlockedReasons.length - 1} more)`
|
|
57
104
|
: ""}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
105
|
+
</>
|
|
106
|
+
),
|
|
107
|
+
testid: "tw-alert-banner__workflow-blocked",
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// 4. Preserve-only features — lowest precedence warning.
|
|
112
|
+
if (preserveOnlyCount > 0) {
|
|
113
|
+
return renderBanner({
|
|
114
|
+
severity: "warning",
|
|
115
|
+
icon: (
|
|
116
|
+
<AlertTriangle className="h-3.5 w-3.5 shrink-0" aria-hidden="true" />
|
|
117
|
+
),
|
|
118
|
+
message: (
|
|
119
|
+
<>
|
|
120
|
+
{preserveOnlyCount} preserve-only feature
|
|
121
|
+
{preserveOnlyCount !== 1 ? "s" : ""} detected
|
|
122
|
+
</>
|
|
123
|
+
),
|
|
124
|
+
testid: "tw-alert-banner__preserve-only",
|
|
125
|
+
});
|
|
61
126
|
}
|
|
62
127
|
|
|
63
128
|
return null;
|