@beyondwork/docx-react-component 1.0.106 → 1.0.109
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 +19 -5
- package/src/api/geometry-overlay-rects.ts +5 -0
- package/src/api/package-version.ts +1 -1
- package/src/api/page-anchor-id.ts +5 -0
- package/src/api/public-types.ts +16 -9
- package/src/api/table-node-specs.ts +6 -0
- package/src/api/v3/_create.ts +2 -1
- package/src/api/v3/_page-anchor-id.ts +52 -0
- package/src/api/v3/_runtime-handle.ts +92 -1
- package/src/api/v3/ai/_audit-time.ts +5 -0
- package/src/api/v3/ai/_pe2-evidence.ts +38 -0
- package/src/api/v3/ai/attach.ts +7 -2
- package/src/api/v3/ai/replacement.ts +101 -18
- package/src/api/v3/ai/resolve.ts +2 -2
- package/src/api/v3/ai/review.ts +177 -3
- package/src/api/v3/index.ts +1 -0
- package/src/api/v3/runtime/collab.ts +462 -0
- package/src/api/v3/runtime/document.ts +503 -20
- package/src/api/v3/runtime/geometry.ts +97 -0
- package/src/api/v3/runtime/layout.ts +744 -0
- package/src/api/v3/runtime/perf-probe.ts +14 -0
- package/src/api/v3/runtime/viewport.ts +9 -8
- package/src/api/v3/ui/_types.ts +149 -55
- package/src/api/v3/ui/chrome-preset-model.ts +5 -5
- package/src/api/v3/ui/debug.ts +115 -2
- package/src/api/v3/ui/index.ts +13 -0
- package/src/api/v3/ui/overlays.ts +0 -8
- package/src/api/v3/ui/surface.ts +56 -0
- package/src/api/v3/ui/viewport.ts +22 -9
- package/src/core/commands/image-commands.ts +1 -0
- package/src/core/commands/index.ts +6 -0
- package/src/core/schema/text-schema.ts +43 -5
- package/src/core/selection/mapping.ts +8 -1
- package/src/core/selection/review-anchors.ts +5 -1
- package/src/core/state/text-transaction.ts +8 -2
- package/src/io/export/serialize-revisions.ts +149 -1
- package/src/io/normalize/normalize-text.ts +6 -0
- package/src/io/ooxml/parse-bookmark-references.ts +55 -0
- package/src/io/ooxml/parse-fields.ts +24 -2
- package/src/io/ooxml/parse-headers-footers.ts +38 -5
- package/src/io/ooxml/parse-main-document.ts +153 -9
- package/src/io/ooxml/parse-numbering.ts +20 -0
- package/src/io/ooxml/parse-revisions.ts +19 -8
- package/src/io/opc/package-reader.ts +98 -8
- package/src/model/anchor.ts +4 -3
- package/src/model/canonical-document.ts +220 -2
- package/src/model/canonical-hash.ts +221 -0
- package/src/model/canonical-layout-inputs.ts +245 -6
- package/src/model/layout/index.ts +1 -0
- package/src/model/layout/page-graph-types.ts +118 -1
- package/src/model/review/revision-types.ts +14 -3
- package/src/preservation/store.ts +20 -4
- package/src/review/README.md +1 -1
- package/src/review/store/revision-actions.ts +14 -2
- package/src/runtime/collab/event-types.ts +67 -1
- package/src/runtime/collab/runtime-collab-sync.ts +177 -5
- package/src/runtime/diagnostics/layout-guard-warning.ts +18 -0
- package/src/runtime/document-heading-outline.ts +147 -0
- package/src/runtime/document-navigation.ts +8 -243
- package/src/runtime/document-runtime.ts +240 -97
- package/src/runtime/edit-dispatch/dispatch-text-command.ts +11 -0
- package/src/runtime/formatting/layout-inputs.ts +38 -5
- package/src/runtime/formatting/numbering/geometry.ts +28 -2
- package/src/runtime/geometry/adjacent-geometry-intake.ts +835 -0
- package/src/runtime/geometry/caret-geometry.ts +5 -6
- package/src/runtime/geometry/geometry-facet.ts +60 -10
- package/src/runtime/geometry/geometry-index.ts +591 -20
- package/src/runtime/geometry/geometry-types.ts +59 -0
- package/src/runtime/geometry/hit-test.ts +11 -1
- package/src/runtime/geometry/overlay-rects.ts +5 -3
- package/src/runtime/geometry/project-anchors.ts +1 -1
- package/src/runtime/geometry/word-layout-v2-line-intake.ts +323 -0
- package/src/runtime/layout/index.ts +6 -0
- package/src/runtime/layout/layout-engine-instance.ts +6 -1
- package/src/runtime/layout/layout-engine-version.ts +181 -16
- package/src/runtime/layout/layout-facet-types.ts +6 -0
- package/src/runtime/layout/page-graph.ts +21 -4
- package/src/runtime/layout/paginated-layout-engine.ts +139 -15
- package/src/runtime/layout/project-block-fragments.ts +265 -7
- package/src/runtime/layout/public-facet.ts +78 -24
- package/src/runtime/layout/table-row-continuation-contract.ts +107 -0
- package/src/runtime/layout/table-row-split.ts +92 -35
- package/src/runtime/prerender/cache-envelope.ts +2 -2
- package/src/runtime/prerender/cache-key.ts +5 -4
- package/src/runtime/prerender/customxml-cache.ts +0 -1
- package/src/runtime/render/render-kernel.ts +1 -1
- package/src/runtime/revision-runtime.ts +112 -10
- package/src/runtime/scopes/_scope-dependencies.ts +1 -0
- package/src/runtime/scopes/action-validation.ts +22 -2
- package/src/runtime/scopes/capabilities.ts +316 -0
- package/src/runtime/scopes/compile-scope-bundle.ts +14 -0
- package/src/runtime/scopes/compiler-service.ts +108 -4
- package/src/runtime/scopes/content-control-evidence.ts +79 -0
- package/src/runtime/scopes/create-issue.ts +5 -5
- package/src/runtime/scopes/evidence.ts +91 -0
- package/src/runtime/scopes/formatting/apply.ts +2 -0
- package/src/runtime/scopes/geometry-evidence.ts +130 -0
- package/src/runtime/scopes/index.ts +54 -0
- package/src/runtime/scopes/issue-lifecycle.ts +224 -0
- package/src/runtime/scopes/layout-evidence.ts +374 -0
- package/src/runtime/scopes/multi-paragraph-refusal.ts +37 -0
- package/src/runtime/scopes/preservation-boundary.ts +7 -1
- package/src/runtime/scopes/replacement/apply.ts +97 -34
- package/src/runtime/scopes/scope-kinds/paragraph.ts +108 -12
- package/src/runtime/scopes/semantic-scope-types.ts +242 -3
- package/src/runtime/scopes/visualization.ts +28 -0
- package/src/runtime/surface-projection.ts +44 -5
- package/src/runtime/telemetry/perf-probe.ts +216 -0
- package/src/runtime/virtualized-rendering.ts +36 -1
- package/src/runtime/workflow/ai-issue-lifecycle.ts +253 -0
- package/src/runtime/workflow/coordinator.ts +39 -11
- package/src/runtime/workflow/derived-scope-resolver.ts +63 -9
- package/src/runtime/workflow/index.ts +3 -0
- package/src/runtime/workflow/overlay-lane-types.ts +58 -0
- package/src/runtime/workflow/overlay-lanes.ts +168 -10
- package/src/runtime/workflow/overlay-store.ts +2 -2
- package/src/runtime/workflow/redline-posture-calibration.ts +257 -0
- package/src/runtime/workflow/word-field-matrix-calibration.ts +231 -0
- package/src/session/_sync-legacy.ts +17 -27
- package/src/session/import/loader.ts +6 -4
- package/src/session/import/source-package-evidence.ts +186 -2
- package/src/session/index.ts +5 -6
- package/src/session/session.ts +30 -56
- package/src/session/types.ts +8 -13
- package/src/shell/session-bootstrap.ts +155 -81
- package/src/ui/WordReviewEditor.tsx +520 -12
- package/src/ui/editor-shell-view.tsx +14 -4
- package/src/ui/editor-surface-controller.tsx +5 -3
- package/src/ui/headless/selection-tool-resolver.ts +1 -2
- package/src/ui/presence-overlay-lane.ts +0 -1
- package/src/ui/ui-controller-factory.ts +7 -0
- package/src/ui-tailwind/chrome/build-context-menu-entries.ts +5 -1
- package/src/ui-tailwind/chrome/editor-action-registry.ts +105 -5
- package/src/ui-tailwind/chrome/editor-actions-to-palette.ts +7 -0
- package/src/ui-tailwind/chrome/layer-debug-contracts.ts +208 -0
- package/src/ui-tailwind/chrome/resolve-target-kind.ts +13 -0
- package/src/ui-tailwind/chrome/tw-alert-banner.tsx +11 -3
- package/src/ui-tailwind/chrome/tw-command-palette.tsx +36 -6
- package/src/ui-tailwind/chrome/tw-context-menu.tsx +6 -1
- package/src/ui-tailwind/chrome/tw-display-mode-selector.tsx +42 -109
- package/src/ui-tailwind/chrome/tw-inline-find-bar.tsx +26 -6
- package/src/ui-tailwind/chrome/tw-navigation-command-bar.tsx +328 -0
- package/src/ui-tailwind/chrome/tw-object-context-toolbar.tsx +8 -4
- package/src/ui-tailwind/chrome/tw-runtime-repl-dialog.tsx +129 -1
- package/src/ui-tailwind/chrome/tw-selection-tool-host.tsx +19 -5
- package/src/ui-tailwind/chrome/tw-selection-tool-structure.tsx +5 -1
- package/src/ui-tailwind/chrome/tw-workspace-chrome-host.tsx +28 -12
- package/src/ui-tailwind/chrome-overlay/tw-chrome-overlay.tsx +30 -3
- package/src/ui-tailwind/chrome-overlay/tw-object-selection-overlay.tsx +116 -10
- package/src/ui-tailwind/chrome-overlay/tw-page-stack-overlay-layer.tsx +223 -94
- package/src/ui-tailwind/chrome-overlay/tw-presence-overlay-lane.tsx +157 -0
- package/src/ui-tailwind/chrome-overlay/tw-review-overlay-lane-markers.tsx +259 -0
- package/src/ui-tailwind/chrome-overlay/tw-scope-card-layer.tsx +5 -2
- package/src/ui-tailwind/chrome-overlay/tw-substrate-overlay-lanes.tsx +314 -0
- package/src/ui-tailwind/debug/README.md +4 -1
- package/src/ui-tailwind/debug/layer11-consumer-readiness.ts +272 -0
- package/src/ui-tailwind/debug/layer11-word-field-matrix-evidence.ts +160 -0
- package/src/ui-tailwind/editor-surface/perf-probe.ts +14 -215
- package/src/ui-tailwind/editor-surface/pm-decorations.ts +42 -0
- package/src/ui-tailwind/editor-surface/pm-position-map.ts +38 -2
- package/src/ui-tailwind/editor-surface/pm-schema.ts +14 -4
- package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +34 -5
- package/src/ui-tailwind/editor-surface/runtime-decoration-plugin.ts +9 -19
- package/src/ui-tailwind/editor-surface/surface-build-keys.ts +2 -2
- package/src/ui-tailwind/editor-surface/tw-page-block-view.helpers.ts +145 -0
- package/src/ui-tailwind/editor-surface/tw-page-block-view.tsx +16 -11
- package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +8 -10
- package/src/ui-tailwind/editor-surface/tw-table-node-view.tsx +3 -0
- package/src/ui-tailwind/page-stack/tw-page-chrome-entry.tsx +4 -2
- package/src/ui-tailwind/page-stack/tw-page-stack-chrome-layer.tsx +60 -20
- package/src/ui-tailwind/page-stack/tw-region-block-renderer.tsx +16 -11
- package/src/ui-tailwind/review/tw-health-panel.tsx +36 -17
- package/src/ui-tailwind/review/tw-review-rail.tsx +7 -4
- package/src/ui-tailwind/review-workspace/diagnostics-visibility.ts +44 -0
- package/src/ui-tailwind/review-workspace/page-shell-metrics.ts +11 -0
- package/src/ui-tailwind/review-workspace/tw-review-workspace-rail.tsx +16 -1
- package/src/ui-tailwind/review-workspace/types.ts +26 -12
- package/src/ui-tailwind/review-workspace/use-diagnostics-signal.ts +40 -11
- package/src/ui-tailwind/review-workspace/use-layout-facet-render-signal.ts +2 -1
- package/src/ui-tailwind/review-workspace/use-page-markers.ts +15 -26
- package/src/ui-tailwind/review-workspace/use-scope-card-state.ts +35 -18
- package/src/ui-tailwind/review-workspace/use-selection-toolbar-placement.ts +41 -32
- package/src/ui-tailwind/review-workspace/use-status-bar-page-facts.ts +2 -1
- package/src/ui-tailwind/review-workspace/use-workspace-side-effects.ts +2 -1
- package/src/ui-tailwind/status/tw-status-bar.tsx +6 -5
- package/src/ui-tailwind/toolbar/tw-role-action-region.tsx +52 -80
- package/src/ui-tailwind/toolbar/tw-shell-header.tsx +12 -48
- package/src/ui-tailwind/toolbar/tw-toolbar-icon-button.tsx +9 -4
- package/src/ui-tailwind/toolbar/tw-toolbar.tsx +328 -361
- package/src/ui-tailwind/tw-review-workspace.tsx +152 -286
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { ChevronDown, ChevronUp, Rows3, Search, Target, X } from "lucide-react";
|
|
3
|
+
|
|
4
|
+
import { preserveEditorSelectionMouseDown } from "../../ui/headless/preserve-editor-selection";
|
|
5
|
+
|
|
6
|
+
export type NavigationCommandMode = "find" | "replace" | "go-to";
|
|
7
|
+
export type NavigationSearchScope = "main" | "story" | "scope";
|
|
8
|
+
|
|
9
|
+
export interface NavigationSearchScopeOption {
|
|
10
|
+
id: NavigationSearchScope;
|
|
11
|
+
label: string;
|
|
12
|
+
disabled?: boolean;
|
|
13
|
+
disabledReason?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface NavigationGoToItem {
|
|
17
|
+
id: string;
|
|
18
|
+
label: string;
|
|
19
|
+
detail?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface NavigationGoToGroup {
|
|
23
|
+
id: string;
|
|
24
|
+
label: string;
|
|
25
|
+
emptyLabel: string;
|
|
26
|
+
items: readonly NavigationGoToItem[];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface TwNavigationCommandBarProps {
|
|
30
|
+
mode: NavigationCommandMode;
|
|
31
|
+
query: string;
|
|
32
|
+
replacement: string;
|
|
33
|
+
activeIndex: number;
|
|
34
|
+
resultCount: number;
|
|
35
|
+
searchScope: NavigationSearchScope;
|
|
36
|
+
searchScopes: readonly NavigationSearchScopeOption[];
|
|
37
|
+
goToGroups: readonly NavigationGoToGroup[];
|
|
38
|
+
activeGoToGroupId: string;
|
|
39
|
+
onModeChange: (mode: NavigationCommandMode) => void;
|
|
40
|
+
onQueryChange: (query: string) => void;
|
|
41
|
+
onReplacementChange: (replacement: string) => void;
|
|
42
|
+
onSearchScopeChange: (scope: NavigationSearchScope) => void;
|
|
43
|
+
onPrevious: () => void;
|
|
44
|
+
onNext: () => void;
|
|
45
|
+
onReplaceCurrent: () => void;
|
|
46
|
+
onGoToGroupChange: (groupId: string) => void;
|
|
47
|
+
onGoToTarget: (groupId: string, itemId: string) => void;
|
|
48
|
+
onClose: () => void;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const MODE_ITEMS: ReadonlyArray<{
|
|
52
|
+
id: NavigationCommandMode;
|
|
53
|
+
label: string;
|
|
54
|
+
shortcut: string;
|
|
55
|
+
}> = [
|
|
56
|
+
{ id: "find", label: "Find", shortcut: "Ctrl+F" },
|
|
57
|
+
{ id: "replace", label: "Replace", shortcut: "Ctrl+H" },
|
|
58
|
+
{ id: "go-to", label: "Go to", shortcut: "Ctrl+G" },
|
|
59
|
+
];
|
|
60
|
+
|
|
61
|
+
export function TwNavigationCommandBar(
|
|
62
|
+
props: TwNavigationCommandBarProps,
|
|
63
|
+
): React.JSX.Element {
|
|
64
|
+
const inputRef = React.useRef<HTMLInputElement | null>(null);
|
|
65
|
+
const hasResults = props.resultCount > 0;
|
|
66
|
+
const hasQuery = props.query.trim().length > 0;
|
|
67
|
+
const resultState = hasQuery ? (hasResults ? "matches" : "empty") : "idle";
|
|
68
|
+
const statusLabel = hasQuery
|
|
69
|
+
? hasResults
|
|
70
|
+
? `${props.activeIndex + 1}/${props.resultCount}`
|
|
71
|
+
: "No results"
|
|
72
|
+
: "Find";
|
|
73
|
+
const navDisabledReason = hasQuery
|
|
74
|
+
? "No matches to navigate."
|
|
75
|
+
: "Enter a query to navigate matches.";
|
|
76
|
+
const activeGroup =
|
|
77
|
+
props.goToGroups.find((group) => group.id === props.activeGoToGroupId) ??
|
|
78
|
+
props.goToGroups[0] ??
|
|
79
|
+
null;
|
|
80
|
+
|
|
81
|
+
React.useEffect(() => {
|
|
82
|
+
if (props.mode === "go-to") return;
|
|
83
|
+
inputRef.current?.focus();
|
|
84
|
+
inputRef.current?.select();
|
|
85
|
+
}, [props.mode]);
|
|
86
|
+
|
|
87
|
+
const handleRootKeyDown = React.useCallback(
|
|
88
|
+
(event: React.KeyboardEvent<HTMLDivElement>) => {
|
|
89
|
+
if (event.key !== "Escape") return;
|
|
90
|
+
event.preventDefault();
|
|
91
|
+
props.onClose();
|
|
92
|
+
},
|
|
93
|
+
[props.onClose],
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
return (
|
|
97
|
+
<div
|
|
98
|
+
className="pointer-events-auto flex w-[min(560px,calc(100vw-2rem))] flex-col gap-2 rounded-lg border border-[color:color-mix(in_srgb,var(--color-accent-primary)_26%,var(--color-border-subtle))] bg-[color:color-mix(in_srgb,var(--color-bg-canvas)_96%,white)] p-2 shadow-[var(--shadow-float)]"
|
|
99
|
+
data-testid="navigation-command-bar"
|
|
100
|
+
data-mode={props.mode}
|
|
101
|
+
data-result-state={resultState}
|
|
102
|
+
role="search"
|
|
103
|
+
aria-label="Document navigation"
|
|
104
|
+
onKeyDown={handleRootKeyDown}
|
|
105
|
+
>
|
|
106
|
+
<div className="flex items-center gap-2">
|
|
107
|
+
<div className="inline-flex rounded-md bg-surface p-0.5" role="tablist" aria-label="Navigation mode">
|
|
108
|
+
{MODE_ITEMS.map((item) => {
|
|
109
|
+
const selected = item.id === props.mode;
|
|
110
|
+
return (
|
|
111
|
+
<button
|
|
112
|
+
key={item.id}
|
|
113
|
+
type="button"
|
|
114
|
+
role="tab"
|
|
115
|
+
aria-selected={selected}
|
|
116
|
+
title={`${item.label} (${item.shortcut})`}
|
|
117
|
+
data-testid={`navigation-command-bar-mode-${item.id}`}
|
|
118
|
+
onMouseDown={preserveEditorSelectionMouseDown}
|
|
119
|
+
onClick={() => props.onModeChange(item.id)}
|
|
120
|
+
className={[
|
|
121
|
+
"inline-flex h-7 items-center gap-1 rounded px-2 text-[11px] font-semibold transition-colors focus-visible:outline-none focus-visible:shadow-[var(--shadow-focus)]",
|
|
122
|
+
selected
|
|
123
|
+
? "bg-canvas text-primary shadow-sm ring-1 ring-border"
|
|
124
|
+
: "text-secondary hover:bg-hover hover:text-primary",
|
|
125
|
+
].join(" ")}
|
|
126
|
+
>
|
|
127
|
+
{item.label}
|
|
128
|
+
</button>
|
|
129
|
+
);
|
|
130
|
+
})}
|
|
131
|
+
</div>
|
|
132
|
+
<button
|
|
133
|
+
type="button"
|
|
134
|
+
aria-label="Close navigation"
|
|
135
|
+
className="ml-auto inline-flex h-7 w-7 items-center justify-center rounded-md text-tertiary transition-colors hover:bg-hover hover:text-primary focus-visible:outline-none focus-visible:shadow-[var(--shadow-focus)]"
|
|
136
|
+
onMouseDown={preserveEditorSelectionMouseDown}
|
|
137
|
+
onClick={props.onClose}
|
|
138
|
+
>
|
|
139
|
+
<X className="h-4 w-4" aria-hidden="true" />
|
|
140
|
+
</button>
|
|
141
|
+
</div>
|
|
142
|
+
|
|
143
|
+
{props.mode === "go-to" ? (
|
|
144
|
+
<div className="grid gap-2" data-testid="navigation-command-bar-go-to">
|
|
145
|
+
<div className="flex flex-wrap gap-1">
|
|
146
|
+
{props.goToGroups.map((group) => {
|
|
147
|
+
const selected = group.id === activeGroup?.id;
|
|
148
|
+
return (
|
|
149
|
+
<button
|
|
150
|
+
key={group.id}
|
|
151
|
+
type="button"
|
|
152
|
+
aria-pressed={selected}
|
|
153
|
+
onMouseDown={preserveEditorSelectionMouseDown}
|
|
154
|
+
onClick={() => props.onGoToGroupChange(group.id)}
|
|
155
|
+
className={[
|
|
156
|
+
"inline-flex h-7 items-center gap-1 rounded-md border px-2 text-[11px] font-medium transition-colors focus-visible:outline-none focus-visible:shadow-[var(--shadow-focus)]",
|
|
157
|
+
selected
|
|
158
|
+
? "border-[var(--color-border-accent)] bg-[var(--color-accent-soft)] text-[var(--color-accent-primary)]"
|
|
159
|
+
: "border-border bg-canvas text-secondary hover:bg-hover hover:text-primary",
|
|
160
|
+
].join(" ")}
|
|
161
|
+
>
|
|
162
|
+
<Target className="h-3 w-3" aria-hidden="true" />
|
|
163
|
+
{group.label}
|
|
164
|
+
<span className="text-[10px] text-tertiary">{group.items.length}</span>
|
|
165
|
+
</button>
|
|
166
|
+
);
|
|
167
|
+
})}
|
|
168
|
+
</div>
|
|
169
|
+
<div className="max-h-64 overflow-auto rounded-md border border-border bg-canvas p-1">
|
|
170
|
+
{activeGroup && activeGroup.items.length > 0 ? (
|
|
171
|
+
activeGroup.items.map((item) => (
|
|
172
|
+
<button
|
|
173
|
+
key={item.id}
|
|
174
|
+
type="button"
|
|
175
|
+
className="flex w-full items-start gap-2 rounded-md px-2 py-1.5 text-left text-[12px] transition-colors hover:bg-surface focus-visible:bg-surface focus-visible:outline-none focus-visible:shadow-[var(--shadow-focus)]"
|
|
176
|
+
onMouseDown={preserveEditorSelectionMouseDown}
|
|
177
|
+
onClick={() => props.onGoToTarget(activeGroup.id, item.id)}
|
|
178
|
+
>
|
|
179
|
+
<Rows3 className="mt-0.5 h-3.5 w-3.5 shrink-0 text-secondary" aria-hidden="true" />
|
|
180
|
+
<span className="min-w-0">
|
|
181
|
+
<span className="block truncate font-medium text-primary">{item.label}</span>
|
|
182
|
+
{item.detail ? (
|
|
183
|
+
<span className="block truncate text-[11px] text-secondary">{item.detail}</span>
|
|
184
|
+
) : null}
|
|
185
|
+
</span>
|
|
186
|
+
</button>
|
|
187
|
+
))
|
|
188
|
+
) : (
|
|
189
|
+
<div className="px-2 py-3 text-[12px] text-secondary">
|
|
190
|
+
{activeGroup?.emptyLabel ?? "No navigation targets available."}
|
|
191
|
+
</div>
|
|
192
|
+
)}
|
|
193
|
+
</div>
|
|
194
|
+
</div>
|
|
195
|
+
) : (
|
|
196
|
+
<div className="grid gap-2" data-testid="navigation-command-bar-search">
|
|
197
|
+
<div className="flex items-center gap-2">
|
|
198
|
+
<Search className="h-4 w-4 shrink-0 text-accent" aria-hidden="true" />
|
|
199
|
+
<input
|
|
200
|
+
ref={inputRef}
|
|
201
|
+
aria-label={props.mode === "replace" ? "Find text to replace" : "Find text"}
|
|
202
|
+
aria-describedby="navigation-command-bar-status"
|
|
203
|
+
className="min-w-0 flex-1 bg-transparent px-1 text-[13px] font-medium text-primary outline-none placeholder:text-tertiary"
|
|
204
|
+
placeholder={props.mode === "replace" ? "Find text to replace" : "Find in document"}
|
|
205
|
+
type="search"
|
|
206
|
+
value={props.query}
|
|
207
|
+
onChange={(event) => props.onQueryChange(event.currentTarget.value)}
|
|
208
|
+
onKeyDown={(event) => {
|
|
209
|
+
if (event.key === "Escape") {
|
|
210
|
+
event.preventDefault();
|
|
211
|
+
event.stopPropagation();
|
|
212
|
+
props.onClose();
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
if (event.key === "Enter") {
|
|
216
|
+
event.preventDefault();
|
|
217
|
+
if (event.shiftKey) {
|
|
218
|
+
props.onPrevious();
|
|
219
|
+
} else {
|
|
220
|
+
props.onNext();
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}}
|
|
224
|
+
/>
|
|
225
|
+
<span
|
|
226
|
+
id="navigation-command-bar-status"
|
|
227
|
+
data-testid="navigation-command-bar-status"
|
|
228
|
+
className={`min-w-[72px] rounded-full px-2 py-1 text-center text-[11px] font-semibold tabular-nums ${
|
|
229
|
+
resultState === "empty"
|
|
230
|
+
? "bg-[var(--color-semantic-warning-soft)] text-[var(--color-semantic-warning)]"
|
|
231
|
+
: "bg-[color:color-mix(in_srgb,var(--color-accent-primary)_10%,transparent)] text-accent"
|
|
232
|
+
}`}
|
|
233
|
+
aria-live="polite"
|
|
234
|
+
>
|
|
235
|
+
{statusLabel}
|
|
236
|
+
</span>
|
|
237
|
+
<IconButton
|
|
238
|
+
label="Previous match"
|
|
239
|
+
shortcut="Shift+Enter"
|
|
240
|
+
disabled={!hasResults}
|
|
241
|
+
title={hasResults ? "Previous match (Shift+Enter)" : navDisabledReason}
|
|
242
|
+
onClick={props.onPrevious}
|
|
243
|
+
>
|
|
244
|
+
<ChevronUp className="h-4 w-4" aria-hidden="true" />
|
|
245
|
+
</IconButton>
|
|
246
|
+
<IconButton
|
|
247
|
+
label="Next match"
|
|
248
|
+
shortcut="Enter"
|
|
249
|
+
disabled={!hasResults}
|
|
250
|
+
title={hasResults ? "Next match (Enter)" : navDisabledReason}
|
|
251
|
+
onClick={props.onNext}
|
|
252
|
+
>
|
|
253
|
+
<ChevronDown className="h-4 w-4" aria-hidden="true" />
|
|
254
|
+
</IconButton>
|
|
255
|
+
</div>
|
|
256
|
+
<div className="flex flex-wrap items-center gap-1">
|
|
257
|
+
{props.searchScopes.map((scope) => (
|
|
258
|
+
<button
|
|
259
|
+
key={scope.id}
|
|
260
|
+
type="button"
|
|
261
|
+
aria-pressed={props.searchScope === scope.id}
|
|
262
|
+
disabled={scope.disabled}
|
|
263
|
+
title={scope.disabled ? scope.disabledReason : undefined}
|
|
264
|
+
onMouseDown={preserveEditorSelectionMouseDown}
|
|
265
|
+
onClick={() => props.onSearchScopeChange(scope.id)}
|
|
266
|
+
className={[
|
|
267
|
+
"inline-flex h-6 items-center rounded-md border px-2 text-[10px] font-semibold transition-colors focus-visible:outline-none focus-visible:shadow-[var(--shadow-focus)] disabled:cursor-not-allowed disabled:opacity-40",
|
|
268
|
+
props.searchScope === scope.id
|
|
269
|
+
? "border-[var(--color-border-accent)] bg-[var(--color-accent-soft)] text-[var(--color-accent-primary)]"
|
|
270
|
+
: "border-border bg-canvas text-secondary hover:bg-hover hover:text-primary",
|
|
271
|
+
].join(" ")}
|
|
272
|
+
>
|
|
273
|
+
{scope.label}
|
|
274
|
+
</button>
|
|
275
|
+
))}
|
|
276
|
+
</div>
|
|
277
|
+
{props.mode === "replace" ? (
|
|
278
|
+
<div className="flex items-center gap-2 border-t border-border/70 pt-2">
|
|
279
|
+
<input
|
|
280
|
+
aria-label="Replacement text"
|
|
281
|
+
className="min-w-0 flex-1 rounded-md border border-border bg-canvas px-2 py-1.5 text-[13px] text-primary outline-none placeholder:text-tertiary focus-visible:shadow-[var(--shadow-focus)]"
|
|
282
|
+
placeholder="Replace with"
|
|
283
|
+
value={props.replacement}
|
|
284
|
+
onChange={(event) => props.onReplacementChange(event.currentTarget.value)}
|
|
285
|
+
/>
|
|
286
|
+
<button
|
|
287
|
+
type="button"
|
|
288
|
+
disabled={!hasResults}
|
|
289
|
+
title={hasResults ? "Replace current match" : navDisabledReason}
|
|
290
|
+
onMouseDown={preserveEditorSelectionMouseDown}
|
|
291
|
+
onClick={props.onReplaceCurrent}
|
|
292
|
+
className="inline-flex h-8 items-center rounded-md bg-[var(--color-accent-primary)] px-3 text-[12px] font-semibold text-[var(--color-text-inverse)] transition-colors hover:bg-[var(--color-accent-primary-hover)] focus-visible:outline-none focus-visible:shadow-[var(--shadow-focus)] disabled:cursor-not-allowed disabled:opacity-40"
|
|
293
|
+
>
|
|
294
|
+
Replace
|
|
295
|
+
</button>
|
|
296
|
+
</div>
|
|
297
|
+
) : null}
|
|
298
|
+
</div>
|
|
299
|
+
)}
|
|
300
|
+
</div>
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function IconButton(props: {
|
|
305
|
+
label: string;
|
|
306
|
+
shortcut: string;
|
|
307
|
+
disabled?: boolean;
|
|
308
|
+
title?: string;
|
|
309
|
+
children: React.ReactNode;
|
|
310
|
+
onClick: () => void;
|
|
311
|
+
}): React.JSX.Element {
|
|
312
|
+
return (
|
|
313
|
+
<button
|
|
314
|
+
type="button"
|
|
315
|
+
aria-label={props.label}
|
|
316
|
+
aria-keyshortcuts={props.shortcut}
|
|
317
|
+
disabled={props.disabled}
|
|
318
|
+
title={props.title}
|
|
319
|
+
className="inline-flex h-7 w-7 items-center justify-center rounded-md text-secondary transition-colors hover:bg-hover focus-visible:outline-none focus-visible:shadow-[var(--shadow-focus)] disabled:cursor-not-allowed disabled:opacity-35"
|
|
320
|
+
onMouseDown={preserveEditorSelectionMouseDown}
|
|
321
|
+
onClick={props.onClick}
|
|
322
|
+
>
|
|
323
|
+
{props.children}
|
|
324
|
+
</button>
|
|
325
|
+
);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
export default TwNavigationCommandBar;
|
|
@@ -3,6 +3,8 @@ import type { ActiveObjectContext } from "../../ui/headless/selection-tool-types
|
|
|
3
3
|
|
|
4
4
|
export interface TwObjectContextToolbarProps {
|
|
5
5
|
activeObject: ActiveObjectContext;
|
|
6
|
+
/** Debug/operator-only preserve/export detail. Hidden in product chrome. */
|
|
7
|
+
showPreservationDiagnostics?: boolean;
|
|
6
8
|
}
|
|
7
9
|
|
|
8
10
|
function InfoIcon(props: { className?: string; "aria-hidden"?: boolean }) {
|
|
@@ -40,10 +42,12 @@ export function TwObjectContextToolbar(props: TwObjectContextToolbarProps) {
|
|
|
40
42
|
<span className="rounded-full bg-surface px-1.5 py-0.5 text-[9px] font-medium uppercase tracking-[0.1em] text-secondary">
|
|
41
43
|
{props.activeObject.display}
|
|
42
44
|
</span>
|
|
43
|
-
|
|
44
|
-
<
|
|
45
|
-
|
|
46
|
-
|
|
45
|
+
{props.showPreservationDiagnostics ? (
|
|
46
|
+
<div className="inline-flex items-center gap-1.5">
|
|
47
|
+
<InfoIcon className="h-3.5 w-3.5 text-[var(--color-semantic-info)]" aria-hidden={true} />
|
|
48
|
+
<span className="text-[11px] text-[var(--color-text-secondary)]">Shape preserved for export — opens in Word.</span>
|
|
49
|
+
</div>
|
|
50
|
+
) : null}
|
|
47
51
|
</div>
|
|
48
52
|
);
|
|
49
53
|
}
|
|
@@ -6,6 +6,13 @@ import React, {
|
|
|
6
6
|
} from "react";
|
|
7
7
|
|
|
8
8
|
import type { WordReviewEditorRef } from "../../api/public-types";
|
|
9
|
+
import {
|
|
10
|
+
LAYER_DEBUG_PANES,
|
|
11
|
+
getLayerDebugIdFromShortcut,
|
|
12
|
+
getLayerDebugPane,
|
|
13
|
+
getLayerDebugShortcutLabel,
|
|
14
|
+
type LayerDebugPaneId,
|
|
15
|
+
} from "./layer-debug-contracts";
|
|
9
16
|
|
|
10
17
|
/**
|
|
11
18
|
* Opaque runtime handle for the REPL. The REPL hands the value to
|
|
@@ -55,6 +62,7 @@ export function TwRuntimeReplDialog(props: TwRuntimeReplDialogProps): React.JSX.
|
|
|
55
62
|
const [open, setOpen] = useState(false);
|
|
56
63
|
const [input, setInput] = useState("");
|
|
57
64
|
const [entries, setEntries] = useState<readonly ReplEntry[]>([]);
|
|
65
|
+
const [activeLayerId, setActiveLayerId] = useState<LayerDebugPaneId>("01");
|
|
58
66
|
const [history, setHistory] = useState<readonly string[]>(() =>
|
|
59
67
|
loadPersistedHistory(),
|
|
60
68
|
);
|
|
@@ -73,10 +81,25 @@ export function TwRuntimeReplDialog(props: TwRuntimeReplDialogProps): React.JSX.
|
|
|
73
81
|
});
|
|
74
82
|
}, []);
|
|
75
83
|
|
|
84
|
+
const loadSnippet = useCallback((expression: string) => {
|
|
85
|
+
setInput(expression);
|
|
86
|
+
setHistoryIndex(null);
|
|
87
|
+
requestAnimationFrame(() => textareaRef.current?.focus());
|
|
88
|
+
}, []);
|
|
89
|
+
|
|
76
90
|
useEffect(() => {
|
|
77
91
|
if (disabled) return;
|
|
78
92
|
if (typeof window === "undefined") return;
|
|
79
93
|
const handler = (event: KeyboardEvent): void => {
|
|
94
|
+
if (openRef.current) {
|
|
95
|
+
const layerId = getLayerDebugIdFromShortcut(event);
|
|
96
|
+
if (layerId) {
|
|
97
|
+
event.preventDefault();
|
|
98
|
+
event.stopPropagation();
|
|
99
|
+
setActiveLayerId(layerId);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
80
103
|
if (!isReplToggleShortcut(event)) return;
|
|
81
104
|
event.preventDefault();
|
|
82
105
|
event.stopPropagation();
|
|
@@ -208,6 +231,8 @@ export function TwRuntimeReplDialog(props: TwRuntimeReplDialogProps): React.JSX.
|
|
|
208
231
|
|
|
209
232
|
if (!open) return null;
|
|
210
233
|
|
|
234
|
+
const activeLayer = getLayerDebugPane(activeLayerId);
|
|
235
|
+
|
|
211
236
|
return (
|
|
212
237
|
<div
|
|
213
238
|
className="fixed inset-0 z-50 flex items-start justify-center pt-[10vh]"
|
|
@@ -281,6 +306,105 @@ export function TwRuntimeReplDialog(props: TwRuntimeReplDialogProps): React.JSX.
|
|
|
281
306
|
</div>
|
|
282
307
|
</div>
|
|
283
308
|
|
|
309
|
+
<section
|
|
310
|
+
className="border-b border-[var(--color-border-subtle)] px-4 py-3"
|
|
311
|
+
data-testid="tw-runtime-repl__layer-view"
|
|
312
|
+
>
|
|
313
|
+
<div className="flex flex-wrap items-start justify-between gap-3">
|
|
314
|
+
<div>
|
|
315
|
+
<p className="text-[10px] font-semibold uppercase tracking-[0.14em] text-[var(--color-text-tertiary)]">
|
|
316
|
+
Agent layer view
|
|
317
|
+
</p>
|
|
318
|
+
<p className="mt-1 text-sm font-medium text-[var(--color-text-primary)]">
|
|
319
|
+
{activeLayer.owner} - {activeLayer.label}
|
|
320
|
+
</p>
|
|
321
|
+
<p className="mt-1 text-xs leading-5 text-[var(--color-text-secondary)]">
|
|
322
|
+
{activeLayer.focus}
|
|
323
|
+
</p>
|
|
324
|
+
</div>
|
|
325
|
+
<label className="space-y-1 text-[10px] font-semibold uppercase tracking-[0.12em] text-[var(--color-text-tertiary)]">
|
|
326
|
+
<span className="block">Layer</span>
|
|
327
|
+
<select
|
|
328
|
+
className="min-w-[13rem] rounded-[var(--radius-sm)] bg-[var(--color-bg-muted)] px-2 py-1.5 text-xs font-medium normal-case tracking-normal text-[var(--color-text-primary)] ring-1 ring-[var(--color-border-subtle)]"
|
|
329
|
+
data-testid="tw-runtime-repl__layer-select"
|
|
330
|
+
onChange={(event) =>
|
|
331
|
+
setActiveLayerId(event.currentTarget.value as LayerDebugPaneId)
|
|
332
|
+
}
|
|
333
|
+
value={activeLayerId}
|
|
334
|
+
>
|
|
335
|
+
{LAYER_DEBUG_PANES.map((pane) => (
|
|
336
|
+
<option key={pane.id} value={pane.id}>
|
|
337
|
+
L{pane.id} - {pane.label}
|
|
338
|
+
</option>
|
|
339
|
+
))}
|
|
340
|
+
</select>
|
|
341
|
+
</label>
|
|
342
|
+
</div>
|
|
343
|
+
|
|
344
|
+
<div
|
|
345
|
+
className="mt-3 flex gap-1 overflow-x-auto pb-1"
|
|
346
|
+
data-testid="tw-runtime-repl__layer-nav"
|
|
347
|
+
>
|
|
348
|
+
{LAYER_DEBUG_PANES.map((pane) => {
|
|
349
|
+
const active = pane.id === activeLayerId;
|
|
350
|
+
return (
|
|
351
|
+
<button
|
|
352
|
+
className={[
|
|
353
|
+
"shrink-0 rounded-[var(--radius-sm)] px-2 py-1 text-[11px] font-medium",
|
|
354
|
+
"transition-colors duration-[var(--motion-fast)]",
|
|
355
|
+
active
|
|
356
|
+
? "bg-[var(--color-bg-selected)] text-[var(--color-text-primary)] ring-1 ring-[var(--color-border-strong)]"
|
|
357
|
+
: "bg-[var(--color-bg-muted)] text-[var(--color-text-secondary)] hover:bg-[var(--color-bg-hover)] hover:text-[var(--color-text-primary)]",
|
|
358
|
+
].join(" ")}
|
|
359
|
+
data-testid={`tw-runtime-repl__layer-option-${pane.id}`}
|
|
360
|
+
key={pane.id}
|
|
361
|
+
onClick={() => setActiveLayerId(pane.id)}
|
|
362
|
+
type="button"
|
|
363
|
+
>
|
|
364
|
+
L{pane.id}
|
|
365
|
+
<span className="ml-1 text-[10px] opacity-70">
|
|
366
|
+
{getLayerDebugShortcutLabel(pane.id)}
|
|
367
|
+
</span>
|
|
368
|
+
</button>
|
|
369
|
+
);
|
|
370
|
+
})}
|
|
371
|
+
</div>
|
|
372
|
+
|
|
373
|
+
<div
|
|
374
|
+
className="mt-3 grid gap-2"
|
|
375
|
+
data-testid="tw-runtime-repl__layer-snippets"
|
|
376
|
+
>
|
|
377
|
+
{activeLayer.snippets.map((snippet) => (
|
|
378
|
+
<button
|
|
379
|
+
className="rounded-[var(--radius-sm)] bg-[var(--color-bg-muted)] px-2 py-2 text-left font-mono text-[11px] leading-5 text-[var(--color-text-primary)] ring-1 ring-[var(--color-border-subtle)] transition-colors duration-[var(--motion-fast)] hover:bg-[var(--color-bg-hover)]"
|
|
380
|
+
data-testid={`tw-runtime-repl__layer-snippet-${slugify(snippet.label)}`}
|
|
381
|
+
key={snippet.label}
|
|
382
|
+
onClick={() => loadSnippet(snippet.expression)}
|
|
383
|
+
type="button"
|
|
384
|
+
>
|
|
385
|
+
<span className="block font-sans text-[10px] font-semibold uppercase tracking-[0.12em] text-[var(--color-text-tertiary)]">
|
|
386
|
+
{snippet.label}
|
|
387
|
+
</span>
|
|
388
|
+
{snippet.expression}
|
|
389
|
+
</button>
|
|
390
|
+
))}
|
|
391
|
+
</div>
|
|
392
|
+
|
|
393
|
+
<details
|
|
394
|
+
className="mt-3 rounded-[var(--radius-sm)] bg-[var(--color-bg-muted)] px-2 py-2 text-xs text-[var(--color-text-secondary)] ring-1 ring-[var(--color-border-subtle)]"
|
|
395
|
+
data-testid="tw-runtime-repl__layer-routed"
|
|
396
|
+
>
|
|
397
|
+
<summary className="cursor-pointer text-[11px] font-medium text-[var(--color-text-primary)]">
|
|
398
|
+
Routed data gaps
|
|
399
|
+
</summary>
|
|
400
|
+
<ul className="mt-2 space-y-1 leading-5">
|
|
401
|
+
{activeLayer.routed.map((item) => (
|
|
402
|
+
<li key={item}>{item}</li>
|
|
403
|
+
))}
|
|
404
|
+
</ul>
|
|
405
|
+
</details>
|
|
406
|
+
</section>
|
|
407
|
+
|
|
284
408
|
<div
|
|
285
409
|
ref={outputRef}
|
|
286
410
|
className="max-h-[50vh] min-h-[160px] overflow-y-auto px-4 py-3 font-mono text-xs leading-relaxed text-[var(--color-text-primary)]"
|
|
@@ -365,7 +489,7 @@ export function TwRuntimeReplDialog(props: TwRuntimeReplDialogProps): React.JSX.
|
|
|
365
489
|
].join(" ")}
|
|
366
490
|
/>
|
|
367
491
|
<div className="mt-1.5 flex items-center justify-between text-[10px] text-[var(--color-text-tertiary)]">
|
|
368
|
-
<span>Enter to evaluate · Shift+Enter
|
|
492
|
+
<span>Enter to evaluate · Shift+Enter newline · ↑/↓ history · Alt+1..9/0/- layers · Esc close</span>
|
|
369
493
|
<span>{entries.length > 0 ? `${entries.length} entries` : ""}</span>
|
|
370
494
|
</div>
|
|
371
495
|
</div>
|
|
@@ -374,6 +498,10 @@ export function TwRuntimeReplDialog(props: TwRuntimeReplDialogProps): React.JSX.
|
|
|
374
498
|
);
|
|
375
499
|
}
|
|
376
500
|
|
|
501
|
+
function slugify(value: string): string {
|
|
502
|
+
return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
503
|
+
}
|
|
504
|
+
|
|
377
505
|
export function isReplToggleShortcut(event: KeyboardEvent): boolean {
|
|
378
506
|
const isP =
|
|
379
507
|
event.code === "KeyP" ||
|
|
@@ -27,6 +27,11 @@ export interface SelectionToolPlacement {
|
|
|
27
27
|
|
|
28
28
|
export interface TwSelectionToolHostProps {
|
|
29
29
|
tool: ActiveSelectionToolModel | null;
|
|
30
|
+
/**
|
|
31
|
+
* Stable identity for the current selection. When omitted, the host
|
|
32
|
+
* falls back to the tool preview key for direct component consumers.
|
|
33
|
+
*/
|
|
34
|
+
selectionKey?: string | null;
|
|
30
35
|
contextAnalytics?: RuntimeContextAnalyticsSnapshot | null;
|
|
31
36
|
placement: SelectionToolPlacement | null;
|
|
32
37
|
/**
|
|
@@ -76,6 +81,7 @@ export interface TwSelectionToolHostProps {
|
|
|
76
81
|
onSetTableAlignment?: (alignment: "left" | "center" | "right") => void;
|
|
77
82
|
onSetCellVerticalAlign?: (align: "top" | "center" | "bottom") => void;
|
|
78
83
|
onOpenTableMore?: (coords: { clientX: number; clientY: number }) => void;
|
|
84
|
+
showPreservationDiagnostics?: boolean;
|
|
79
85
|
}
|
|
80
86
|
|
|
81
87
|
/**
|
|
@@ -84,12 +90,17 @@ export interface TwSelectionToolHostProps {
|
|
|
84
90
|
* previewText so that a new selection (which produces a new previewText)
|
|
85
91
|
* resets the timer. Pure re-renders with the same selection do not reset.
|
|
86
92
|
*/
|
|
87
|
-
function useDwellDensity(
|
|
93
|
+
function useDwellDensity(
|
|
94
|
+
tool: ActiveSelectionToolModel | null,
|
|
95
|
+
selectionKeyOverride?: string | null,
|
|
96
|
+
): "micro" | "full" {
|
|
88
97
|
// Derive a stable selection key from the tool. For formatting-inline we
|
|
89
98
|
// use the previewText (reflects selected content). For other kinds we use
|
|
90
|
-
// a fixed sentinel so they never reset mid-render.
|
|
99
|
+
// a fixed sentinel so they never reset mid-render. Mounted callers pass
|
|
100
|
+
// the actual anchor/head identity so directional drag selections reset even
|
|
101
|
+
// when they cover the same preview text.
|
|
91
102
|
const selectionKey = tool
|
|
92
|
-
? `${tool.kind}:${tool.previewText ?? "none"}`
|
|
103
|
+
? `${tool.kind}:${selectionKeyOverride ?? tool.previewText ?? "none"}`
|
|
93
104
|
: null;
|
|
94
105
|
const [density, setDensity] = useState<"micro" | "full">("micro");
|
|
95
106
|
|
|
@@ -111,7 +122,7 @@ function useDwellDensity(tool: ActiveSelectionToolModel | null): "micro" | "full
|
|
|
111
122
|
}
|
|
112
123
|
|
|
113
124
|
export function TwSelectionToolHost(props: TwSelectionToolHostProps) {
|
|
114
|
-
const density = useDwellDensity(props.tool);
|
|
125
|
+
const density = useDwellDensity(props.tool, props.selectionKey);
|
|
115
126
|
const { onChromePinChange } = props;
|
|
116
127
|
|
|
117
128
|
// Phase F.2 — gate the selection tool on the shared local-surface
|
|
@@ -162,6 +173,8 @@ export function TwSelectionToolHost(props: TwSelectionToolHostProps) {
|
|
|
162
173
|
|
|
163
174
|
const overlayTestId = getOverlayTestId(props.tool.kind, Boolean(props.placement));
|
|
164
175
|
const toolContent = renderTool(props, props.tool, density);
|
|
176
|
+
const shouldRenderContextAnalytics =
|
|
177
|
+
props.tool.kind !== "blocked-explainer" && Boolean(props.contextAnalytics);
|
|
165
178
|
const content = toolContent ? (
|
|
166
179
|
<div
|
|
167
180
|
ref={props.rootRef}
|
|
@@ -169,7 +182,7 @@ export function TwSelectionToolHost(props: TwSelectionToolHostProps) {
|
|
|
169
182
|
onBlurCapture={props.onBlurCapture}
|
|
170
183
|
className="flex flex-col gap-1.5"
|
|
171
184
|
>
|
|
172
|
-
{
|
|
185
|
+
{shouldRenderContextAnalytics ? (
|
|
173
186
|
<TwContextAnalyticsSummary
|
|
174
187
|
snapshot={props.contextAnalytics}
|
|
175
188
|
compact
|
|
@@ -301,6 +314,7 @@ function renderTool(
|
|
|
301
314
|
onSetTableAlignment={props.onSetTableAlignment}
|
|
302
315
|
onSetCellVerticalAlign={props.onSetCellVerticalAlign}
|
|
303
316
|
onOpenTableMore={props.onOpenTableMore}
|
|
317
|
+
showPreservationDiagnostics={props.showPreservationDiagnostics}
|
|
304
318
|
/>
|
|
305
319
|
);
|
|
306
320
|
case "comment-thread":
|
|
@@ -36,6 +36,7 @@ export interface TwSelectionToolStructureProps {
|
|
|
36
36
|
onSetTableAlignment?: (alignment: "left" | "center" | "right") => void;
|
|
37
37
|
onSetCellVerticalAlign?: (align: "top" | "center" | "bottom") => void;
|
|
38
38
|
onOpenTableMore?: (coords: { clientX: number; clientY: number }) => void;
|
|
39
|
+
showPreservationDiagnostics?: boolean;
|
|
39
40
|
}
|
|
40
41
|
|
|
41
42
|
export function TwSelectionToolStructure(props: TwSelectionToolStructureProps) {
|
|
@@ -53,7 +54,10 @@ export function TwSelectionToolStructure(props: TwSelectionToolStructureProps) {
|
|
|
53
54
|
// Shapes and text boxes are shipped as informational-only structure tools
|
|
54
55
|
// until there is a real runtime-backed object mutation path.
|
|
55
56
|
return props.model.activeObject ? (
|
|
56
|
-
<TwObjectContextToolbar
|
|
57
|
+
<TwObjectContextToolbar
|
|
58
|
+
activeObject={props.model.activeObject}
|
|
59
|
+
showPreservationDiagnostics={props.showPreservationDiagnostics}
|
|
60
|
+
/>
|
|
57
61
|
) : null;
|
|
58
62
|
case "table":
|
|
59
63
|
return (
|