@beyondwork/docx-react-component 1.0.95 → 1.0.96
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/runtime/document-runtime.ts +46 -1
- package/src/runtime/workflow/rail/compose.ts +5 -0
- package/src/ui-tailwind/chrome-overlay/tw-chrome-overlay.tsx +1 -16
- package/src/ui-tailwind/chrome-overlay/tw-scope-card-layer.tsx +1 -5
- package/src/ui-tailwind/chrome-overlay/tw-scope-rail-layer.tsx +8 -16
- package/src/ui-tailwind/theme/editor-theme.css +69 -78
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.
|
|
4
|
+
"version": "1.0.96",
|
|
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": [
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
type EditorWarning as InternalEditorWarning,
|
|
14
14
|
} from "../core/state/editor-state.ts";
|
|
15
15
|
import {
|
|
16
|
+
createPlainText,
|
|
16
17
|
logicalPositionToUnitIndex,
|
|
17
18
|
parseTextStory,
|
|
18
19
|
serializeTextStory,
|
|
@@ -3195,6 +3196,13 @@ export function createDocumentRuntime(
|
|
|
3195
3196
|
replaceText(text, target, formatting) {
|
|
3196
3197
|
try {
|
|
3197
3198
|
const timestamp = clock();
|
|
3199
|
+
const selection = target ? createSelectionFromPublicAnchor(target) : state.selection;
|
|
3200
|
+
if (
|
|
3201
|
+
shouldPreserveEquivalentReplacement(formatting) &&
|
|
3202
|
+
replacementTextMatchesCurrentRange(state.document, activeStory, selection, text)
|
|
3203
|
+
) {
|
|
3204
|
+
return;
|
|
3205
|
+
}
|
|
3198
3206
|
applyTextCommandInActiveStory(
|
|
3199
3207
|
{
|
|
3200
3208
|
type: "text.insert",
|
|
@@ -3203,7 +3211,7 @@ export function createDocumentRuntime(
|
|
|
3203
3211
|
origin: createOrigin("api", timestamp),
|
|
3204
3212
|
},
|
|
3205
3213
|
{
|
|
3206
|
-
selection
|
|
3214
|
+
selection,
|
|
3207
3215
|
blockedCommandName: "replaceText",
|
|
3208
3216
|
},
|
|
3209
3217
|
);
|
|
@@ -6448,6 +6456,43 @@ function createSelectionFromPublicAnchor(
|
|
|
6448
6456
|
}
|
|
6449
6457
|
}
|
|
6450
6458
|
|
|
6459
|
+
function shouldPreserveEquivalentReplacement(formatting: TextFormattingDirective | undefined): boolean {
|
|
6460
|
+
return !formatting || formatting.mode === "match-replaced-range";
|
|
6461
|
+
}
|
|
6462
|
+
|
|
6463
|
+
function replacementTextMatchesCurrentRange(
|
|
6464
|
+
document: CanonicalDocumentEnvelope,
|
|
6465
|
+
activeStory: EditorStoryTarget,
|
|
6466
|
+
selection: import("../core/state/editor-state.ts").SelectionSnapshot,
|
|
6467
|
+
replacement: string,
|
|
6468
|
+
): boolean {
|
|
6469
|
+
const from = Math.max(0, Math.min(selection.anchor, selection.head));
|
|
6470
|
+
const to = Math.max(0, Math.max(selection.anchor, selection.head));
|
|
6471
|
+
if (from === to) {
|
|
6472
|
+
return replacement.length === 0;
|
|
6473
|
+
}
|
|
6474
|
+
|
|
6475
|
+
const content = activeStory.kind === "main"
|
|
6476
|
+
? document.content
|
|
6477
|
+
: {
|
|
6478
|
+
type: "doc" as const,
|
|
6479
|
+
children: [...getStoryBlocks(document, activeStory)],
|
|
6480
|
+
};
|
|
6481
|
+
const story = parseTextStory(content);
|
|
6482
|
+
if (from > story.size || to > story.size) {
|
|
6483
|
+
return false;
|
|
6484
|
+
}
|
|
6485
|
+
|
|
6486
|
+
const unitFrom = logicalPositionToUnitIndex(story.units, from, "after");
|
|
6487
|
+
const unitTo = logicalPositionToUnitIndex(story.units, to, "before");
|
|
6488
|
+
const selectedText = createPlainText({
|
|
6489
|
+
firstParagraph: story.firstParagraph,
|
|
6490
|
+
units: story.units.slice(unitFrom, unitTo),
|
|
6491
|
+
size: to - from,
|
|
6492
|
+
});
|
|
6493
|
+
return selectedText === replacement;
|
|
6494
|
+
}
|
|
6495
|
+
|
|
6451
6496
|
/**
|
|
6452
6497
|
* I2 Tier B Slice 4b — extract the selection range from a document as a
|
|
6453
6498
|
* `CanonicalDocumentFragment`. The fragment preserves text + marks +
|
|
@@ -94,6 +94,11 @@ export function collectScopeRailSegments(
|
|
|
94
94
|
const activeIds = new Set(input.activeWorkItemScopeIds ?? []);
|
|
95
95
|
|
|
96
96
|
for (const scope of input.scopes ?? []) {
|
|
97
|
+
// Invisible scopes are runtime/agent context only. They may still
|
|
98
|
+
// participate in guard decisions, but they must not surface as rail,
|
|
99
|
+
// card, or body-tint chrome.
|
|
100
|
+
if (scope.visibility === "invisible") continue;
|
|
101
|
+
|
|
97
102
|
const range = anchorToRuntimeRange(scope.anchor);
|
|
98
103
|
if (!range) continue;
|
|
99
104
|
const storyTarget = scope.storyTarget ?? MAIN_STORY_TARGET;
|
|
@@ -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 {
|
|
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,
|
|
@@ -253,16 +250,6 @@ export const TwChromeOverlay: React.FC<TwChromeOverlayProps> = ({
|
|
|
253
250
|
[],
|
|
254
251
|
[ui, workflowFacet, renderFrameRevision],
|
|
255
252
|
);
|
|
256
|
-
const visibleScopeIds = React.useMemo(() => {
|
|
257
|
-
if (!visibleScopePostures) return undefined;
|
|
258
|
-
const ids = new Set<string>();
|
|
259
|
-
for (const segment of scopeRailSegments) {
|
|
260
|
-
if (visibleScopePostures.has(segment.posture)) {
|
|
261
|
-
ids.add(segment.scopeId);
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
return ids;
|
|
265
|
-
}, [scopeRailSegments, visibleScopePostures]);
|
|
266
253
|
|
|
267
254
|
return (
|
|
268
255
|
<div
|
|
@@ -291,7 +278,6 @@ export const TwChromeOverlay: React.FC<TwChromeOverlayProps> = ({
|
|
|
291
278
|
scopeRailSegments={scopeRailSegments}
|
|
292
279
|
space={space}
|
|
293
280
|
activeScopeId={activeScopeId}
|
|
294
|
-
visibleScopePostures={visibleScopePostures}
|
|
295
281
|
onStripeClick={onScopeStripeClick}
|
|
296
282
|
onSegmentClick={onScopeSegmentClick}
|
|
297
283
|
/>
|
|
@@ -299,7 +285,6 @@ export const TwChromeOverlay: React.FC<TwChromeOverlayProps> = ({
|
|
|
299
285
|
facet={facet}
|
|
300
286
|
workflowFacet={workflowFacet}
|
|
301
287
|
activeScopeId={activeScopeId ?? null}
|
|
302
|
-
visibleScopeIds={visibleScopeIds}
|
|
303
288
|
onClose={onScopeCardClose ?? noop}
|
|
304
289
|
onModeChange={onScopeCardModeChange ?? noopModeChange}
|
|
305
290
|
onIssueAction={onScopeCardIssueAction ?? noopIssueAction}
|
|
@@ -53,8 +53,6 @@ export interface TwScopeCardLayerProps {
|
|
|
53
53
|
*/
|
|
54
54
|
workflowFacet: WorkflowFacet | null;
|
|
55
55
|
activeScopeId: string | null;
|
|
56
|
-
/** Scope ids currently visible under the Workflow rail layer filters. */
|
|
57
|
-
visibleScopeIds?: ReadonlySet<string>;
|
|
58
56
|
onClose: () => void;
|
|
59
57
|
onModeChange: (scopeId: string, mode: WorkflowScopeMode) => void;
|
|
60
58
|
onIssueAction: (
|
|
@@ -94,7 +92,6 @@ export const TwScopeCardLayer: React.FC<TwScopeCardLayerProps> = ({
|
|
|
94
92
|
facet,
|
|
95
93
|
workflowFacet,
|
|
96
94
|
activeScopeId,
|
|
97
|
-
visibleScopeIds,
|
|
98
95
|
onClose,
|
|
99
96
|
onModeChange,
|
|
100
97
|
onIssueAction,
|
|
@@ -131,11 +128,10 @@ export const TwScopeCardLayer: React.FC<TwScopeCardLayerProps> = ({
|
|
|
131
128
|
const getVisibleScopeCardModel = React.useCallback(
|
|
132
129
|
(scopeId: string | null): ScopeCardModel | null => {
|
|
133
130
|
if (!scopeId) return null;
|
|
134
|
-
if (visibleScopeIds && !visibleScopeIds.has(scopeId)) return null;
|
|
135
131
|
if (ui) return ui.scope.card(scopeId);
|
|
136
132
|
return getWorkflowScopeCardModel(scopeId);
|
|
137
133
|
},
|
|
138
|
-
[getWorkflowScopeCardModel, ui
|
|
134
|
+
[getWorkflowScopeCardModel, ui],
|
|
139
135
|
);
|
|
140
136
|
|
|
141
137
|
// The effective scope is the pinned one if it still resolves to a
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Scope rail layer — renders workflow scopes as a thin color stripe in
|
|
3
|
-
* the reserved left-gutter lane plus a
|
|
4
|
-
*
|
|
3
|
+
* the reserved left-gutter lane plus a border-only line outline. The
|
|
4
|
+
* visible scope ownership border lives on the PM inline text decoration.
|
|
5
5
|
*
|
|
6
6
|
* Per runtime-rendering-and-chrome-phase.md §5 and
|
|
7
7
|
* docs/plans/scope-card-overlay.md P0, the rail is a projection over
|
|
@@ -52,8 +52,6 @@ export interface TwScopeRailLayerProps {
|
|
|
52
52
|
railLaneWidthPx?: number;
|
|
53
53
|
/** Scope id that should render with the `active` emphasis. */
|
|
54
54
|
activeScopeId?: string | null;
|
|
55
|
-
/** Posture filters shared with the Workflow rail. Omitted means all scopes render. */
|
|
56
|
-
visibleScopePostures?: ReadonlySet<ScopeRailPosture>;
|
|
57
55
|
/**
|
|
58
56
|
* Fires when the user clicks the rail stripe — opens the scope card.
|
|
59
57
|
* P0 wires this directly; P1 replaces with card-layer-aware routing.
|
|
@@ -95,8 +93,8 @@ const POSTURE_STYLES: Record<ScopeRailPosture, PostureStyle> = {
|
|
|
95
93
|
// ---------------------------------------------------------------------------
|
|
96
94
|
|
|
97
95
|
const DEFAULT_RAIL_LANE_PX = 44;
|
|
98
|
-
const STRIPE_WIDTH_PX =
|
|
99
|
-
const LABEL_WIDTH_PX =
|
|
96
|
+
const STRIPE_WIDTH_PX = 4;
|
|
97
|
+
const LABEL_WIDTH_PX = 28;
|
|
100
98
|
const STACK_OFFSET_PX = 6;
|
|
101
99
|
|
|
102
100
|
export const TwScopeRailLayer: React.FC<TwScopeRailLayerProps> = ({
|
|
@@ -106,21 +104,17 @@ export const TwScopeRailLayer: React.FC<TwScopeRailLayerProps> = ({
|
|
|
106
104
|
space,
|
|
107
105
|
railLaneWidthPx = DEFAULT_RAIL_LANE_PX,
|
|
108
106
|
activeScopeId,
|
|
109
|
-
visibleScopePostures,
|
|
110
107
|
onStripeClick,
|
|
111
108
|
onSegmentClick,
|
|
112
109
|
"data-testid": testId,
|
|
113
110
|
}) => {
|
|
114
111
|
const ui = useUiApi();
|
|
115
112
|
const frame = geometryFacet.getRenderFrame() ?? null;
|
|
116
|
-
const
|
|
113
|
+
const segments =
|
|
117
114
|
scopeRailSegments ??
|
|
118
115
|
ui?.scope.rail().segments ??
|
|
119
116
|
workflowFacet?.getAllRailSegments() ??
|
|
120
117
|
[];
|
|
121
|
-
const segments = railSegments.filter((segment) =>
|
|
122
|
-
visibleScopePostures ? visibleScopePostures.has(segment.posture) : true,
|
|
123
|
-
);
|
|
124
118
|
|
|
125
119
|
if (!frame || segments.length === 0) {
|
|
126
120
|
return null;
|
|
@@ -248,16 +242,14 @@ export const TwScopeRailLayer: React.FC<TwScopeRailLayerProps> = ({
|
|
|
248
242
|
onKeyDown={handleStripeKey}
|
|
249
243
|
style={projectRectToOverlay(stripeRect, projectorSpace)}
|
|
250
244
|
/>
|
|
251
|
-
{/*
|
|
245
|
+
{/* Edit handle — revealed on stripe hover via CSS. */}
|
|
252
246
|
<button
|
|
253
247
|
type="button"
|
|
254
248
|
tabIndex={-1}
|
|
255
|
-
className={`wre-scope-rail-label wre-scope-rail-label-${style.railToken}
|
|
256
|
-
isActive ? "wre-scope-rail-label-active" : ""
|
|
257
|
-
}`}
|
|
249
|
+
className={`wre-scope-rail-label wre-scope-rail-label-${style.railToken}`}
|
|
258
250
|
data-scope-id={segment.scopeId}
|
|
259
251
|
data-posture={segment.posture}
|
|
260
|
-
aria-label={
|
|
252
|
+
aria-label={`Edit scope${segment.label ? `: ${segment.label}` : ""}`}
|
|
261
253
|
onClick={handleActivate}
|
|
262
254
|
style={projectRectToOverlay(labelRect, projectorSpace)}
|
|
263
255
|
>
|
|
@@ -379,17 +379,31 @@
|
|
|
379
379
|
/*
|
|
380
380
|
* ─── Workflow inline text emphasis ───
|
|
381
381
|
*
|
|
382
|
-
*
|
|
383
|
-
*
|
|
384
|
-
*
|
|
385
|
-
*
|
|
386
|
-
* wavy underline, active = thin outline). The rounded in-text background
|
|
387
|
-
* boxes that previously wrapped every run are gone — the overlay's flat
|
|
388
|
-
* tint handles that signal.
|
|
382
|
+
* Scopes should read as text ownership, not block selection. PM inline
|
|
383
|
+
* decorations carry the visible border on the scoped text, while the
|
|
384
|
+
* ChromeOverlay plane supplies the gutter/action rail. Keep this
|
|
385
|
+
* border-only: no filled boxes over document content.
|
|
389
386
|
*/
|
|
390
387
|
.prosemirror-surface .ProseMirror .wre-workflow-inline {
|
|
391
388
|
-webkit-box-decoration-break: clone;
|
|
392
389
|
box-decoration-break: clone;
|
|
390
|
+
border-radius: 2px;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
.prosemirror-surface .ProseMirror .wre-workflow-inline-edit {
|
|
394
|
+
box-shadow: 0 0 0 1px color-mix(in srgb, var(--color-accent) 52%, transparent);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
.prosemirror-surface .ProseMirror .wre-workflow-inline-suggest {
|
|
398
|
+
box-shadow: 0 0 0 1px color-mix(in srgb, var(--color-warning) 56%, transparent);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
.prosemirror-surface .ProseMirror .wre-workflow-inline-comment {
|
|
402
|
+
box-shadow: 0 0 0 1px color-mix(in srgb, var(--color-insert) 48%, transparent);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
.prosemirror-surface .ProseMirror .wre-workflow-inline-view {
|
|
406
|
+
box-shadow: 0 0 0 1px color-mix(in srgb, var(--color-secondary) 46%, transparent);
|
|
393
407
|
}
|
|
394
408
|
|
|
395
409
|
.prosemirror-surface .ProseMirror .wre-workflow-inline-candidate {
|
|
@@ -408,31 +422,29 @@
|
|
|
408
422
|
|
|
409
423
|
/*
|
|
410
424
|
* Locked zone marker for inline runs: a subtle dotted right edge so the
|
|
411
|
-
* reader can tell where the locked range ends when the gutter
|
|
412
|
-
* out of view. The
|
|
425
|
+
* reader can tell where the locked range ends when the gutter handle scrolls
|
|
426
|
+
* out of view. The rail carries the action affordance.
|
|
413
427
|
*/
|
|
414
428
|
.prosemirror-surface .ProseMirror .wre-workflow-inline-locked-zone {
|
|
415
429
|
box-shadow: inset -1px 0 0 color-mix(in srgb, var(--color-danger) 35%, transparent);
|
|
416
430
|
}
|
|
417
431
|
|
|
418
432
|
/*
|
|
419
|
-
*
|
|
420
|
-
*
|
|
421
|
-
*
|
|
422
|
-
* fought with the overlay's flat tint. The class name is kept on the
|
|
423
|
-
* inline decoration as a data hook (no visual), and emphasis for the
|
|
424
|
-
* active scope now lives on the ChromeOverlay rail stripe + scope card.
|
|
433
|
+
* Active scope emphasis is a stronger text border plus the gutter handle.
|
|
434
|
+
* This keeps focus local to scoped text without reintroducing filled green
|
|
435
|
+
* rectangles.
|
|
425
436
|
*/
|
|
426
437
|
.prosemirror-surface .ProseMirror .wre-workflow-inline-active {
|
|
427
|
-
|
|
438
|
+
box-shadow:
|
|
439
|
+
0 0 0 1px color-mix(in srgb, var(--color-accent) 72%, transparent),
|
|
440
|
+
0 0 0 3px color-mix(in srgb, var(--color-accent) 12%, transparent);
|
|
428
441
|
}
|
|
429
442
|
|
|
430
443
|
/*
|
|
431
444
|
* ─── ChromeOverlay: scope rail layer ───
|
|
432
445
|
*
|
|
433
|
-
* The overlay sits above PM and paints
|
|
434
|
-
*
|
|
435
|
-
* kernel's anchor index, not DOM rects.
|
|
446
|
+
* The overlay sits above PM and paints gutter handles plus optional
|
|
447
|
+
* border-only line outlines. It must not fill document content.
|
|
436
448
|
*/
|
|
437
449
|
.wre-scope-rail-layer {
|
|
438
450
|
pointer-events: none;
|
|
@@ -471,58 +483,36 @@
|
|
|
471
483
|
border-radius: 0.2rem;
|
|
472
484
|
pointer-events: none;
|
|
473
485
|
z-index: 0;
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
.wre-scope-rail-tint-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
.wre-scope-rail-tint-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
.wre-scope-rail-tint-
|
|
487
|
-
|
|
488
|
-
}
|
|
489
|
-
.wre-scope-rail-tint-danger {
|
|
490
|
-
background: color-mix(in srgb, var(--color-danger) 14%, transparent);
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
/* §3.7 canonical scope families */
|
|
494
|
-
.wre-scope-rail-tint-blocked {
|
|
495
|
-
background: var(--color-scope-tint-blocked);
|
|
496
|
-
}
|
|
497
|
-
.wre-scope-rail-tint-in-scope {
|
|
498
|
-
background: var(--color-scope-tint-in-scope);
|
|
499
|
-
}
|
|
500
|
-
.wre-scope-rail-tint-suggest {
|
|
501
|
-
background: var(--color-scope-tint-suggest);
|
|
502
|
-
}
|
|
503
|
-
.wre-scope-rail-tint-comment {
|
|
504
|
-
background: var(--color-scope-tint-comment);
|
|
505
|
-
}
|
|
506
|
-
.wre-scope-rail-tint-scheduled {
|
|
507
|
-
background: var(--color-scope-tint-scheduled);
|
|
508
|
-
}
|
|
486
|
+
background: transparent;
|
|
487
|
+
transition: box-shadow 140ms ease-out;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
.wre-scope-rail-tint-accent,
|
|
491
|
+
.wre-scope-rail-tint-warning,
|
|
492
|
+
.wre-scope-rail-tint-insert,
|
|
493
|
+
.wre-scope-rail-tint-secondary,
|
|
494
|
+
.wre-scope-rail-tint-danger,
|
|
495
|
+
.wre-scope-rail-tint-blocked,
|
|
496
|
+
.wre-scope-rail-tint-in-scope,
|
|
497
|
+
.wre-scope-rail-tint-suggest,
|
|
498
|
+
.wre-scope-rail-tint-comment,
|
|
499
|
+
.wre-scope-rail-tint-scheduled,
|
|
509
500
|
.wre-scope-rail-tint-proposed {
|
|
510
|
-
background:
|
|
501
|
+
background: transparent;
|
|
511
502
|
}
|
|
512
503
|
|
|
513
504
|
.wre-scope-rail-tint-active {
|
|
514
|
-
outline: 1px solid color-mix(in srgb, var(--color-accent)
|
|
505
|
+
outline: 1px solid color-mix(in srgb, var(--color-accent) 52%, transparent);
|
|
515
506
|
outline-offset: -1px;
|
|
516
507
|
}
|
|
517
508
|
|
|
518
509
|
/*
|
|
519
510
|
* ─── Agent-pending shimmer (K2 / scope-card-overlay P2) ───
|
|
520
511
|
*
|
|
521
|
-
* Painted on every scope
|
|
522
|
-
* with `source: "ai"`.
|
|
523
|
-
*
|
|
524
|
-
*
|
|
525
|
-
* border so the posture is still readable.
|
|
512
|
+
* Painted on every scope outline that overlaps a WorkflowCandidateRange
|
|
513
|
+
* with `source: "ai"`. A soft 1.8s pulse signals the agent is thinking
|
|
514
|
+
* without competing with the active outline. Reduced-motion disables the
|
|
515
|
+
* animation and holds a static 60% opacity outline.
|
|
526
516
|
*/
|
|
527
517
|
@keyframes wre-agent-pulse {
|
|
528
518
|
0%, 100% { opacity: 0.4; }
|
|
@@ -544,9 +534,9 @@
|
|
|
544
534
|
* ─── Scope rail stripe ───
|
|
545
535
|
*
|
|
546
536
|
* The rail stripe is the rest-state representation of a scope: a 4px
|
|
547
|
-
* color stripe in the gutter lane.
|
|
548
|
-
* accent/warning/insert/secondary/danger tokens.
|
|
549
|
-
*
|
|
537
|
+
* color stripe in the gutter lane. Posture color comes from the
|
|
538
|
+
* accent/warning/insert/secondary/danger tokens. Hover widens the stripe
|
|
539
|
+
* via transform (zero layout cost) and reveals the edit handle.
|
|
550
540
|
*/
|
|
551
541
|
.wre-scope-rail-stripe {
|
|
552
542
|
position: absolute;
|
|
@@ -595,34 +585,31 @@
|
|
|
595
585
|
.wre-scope-rail-stripe.wre-scope-rail-tint-proposed { background: var(--color-scope-tint-proposed); }
|
|
596
586
|
|
|
597
587
|
/*
|
|
598
|
-
* ─── Scope rail
|
|
588
|
+
* ─── Scope rail edit handle ───
|
|
599
589
|
*
|
|
600
|
-
* Shown only on stripe hover (CSS-driven).
|
|
601
|
-
* stripe with
|
|
602
|
-
* of the scope.
|
|
590
|
+
* Shown only on stripe hover (CSS-driven). The handle overlays the
|
|
591
|
+
* stripe with a compact icon anchored to the first line of the scope.
|
|
603
592
|
*/
|
|
604
593
|
.wre-scope-rail-label {
|
|
605
594
|
position: absolute;
|
|
606
595
|
display: flex;
|
|
607
596
|
align-items: center;
|
|
608
597
|
justify-content: center;
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
598
|
+
width: 24px;
|
|
599
|
+
height: 24px;
|
|
600
|
+
padding: 0;
|
|
601
|
+
border-radius: 999px;
|
|
612
602
|
border: 1px solid transparent;
|
|
613
603
|
background: var(--color-canvas, #fff);
|
|
614
604
|
box-shadow: var(--shadow-sm);
|
|
615
|
-
font
|
|
616
|
-
line-height: 1;
|
|
617
|
-
text-transform: uppercase;
|
|
618
|
-
letter-spacing: 0.06em;
|
|
619
|
-
font-weight: 600;
|
|
605
|
+
font: inherit;
|
|
620
606
|
cursor: pointer;
|
|
621
607
|
z-index: 2;
|
|
622
608
|
opacity: 0;
|
|
623
609
|
pointer-events: none;
|
|
624
610
|
transition: opacity 140ms ease-out, transform 140ms ease-out;
|
|
625
611
|
transform: translateX(-4px);
|
|
612
|
+
margin: 0;
|
|
626
613
|
}
|
|
627
614
|
|
|
628
615
|
.wre-scope-rail-stripe:hover + .wre-scope-rail-label,
|
|
@@ -697,8 +684,8 @@
|
|
|
697
684
|
|
|
698
685
|
.wre-scope-rail-icon {
|
|
699
686
|
display: inline-block;
|
|
700
|
-
width:
|
|
701
|
-
height:
|
|
687
|
+
width: 13px;
|
|
688
|
+
height: 13px;
|
|
702
689
|
background-color: currentColor;
|
|
703
690
|
mask-repeat: no-repeat;
|
|
704
691
|
mask-position: center;
|
|
@@ -708,6 +695,10 @@
|
|
|
708
695
|
-webkit-mask-size: contain;
|
|
709
696
|
}
|
|
710
697
|
|
|
698
|
+
.wre-scope-rail-label-text {
|
|
699
|
+
display: none;
|
|
700
|
+
}
|
|
701
|
+
|
|
711
702
|
/* Simple inline-SVG-as-mask icons so consumers don't need an icon font. */
|
|
712
703
|
.wre-scope-rail-icon-lock {
|
|
713
704
|
mask-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="5" y="11" width="14" height="10" rx="2"/><path d="M8 11V7a4 4 0 018 0v4"/></svg>');
|