@beyondwork/docx-react-component 1.0.96 → 1.0.97
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 +33 -19
- package/src/api/v3/ui/_types.ts +11 -21
- package/src/api/v3/ui/chrome.ts +8 -9
- package/src/api/v3/ui/debug.ts +15 -77
- package/src/api/v3/ui/overlays-visibility.ts +9 -10
- package/src/api/v3/ui/overlays.ts +8 -75
- package/src/io/ooxml/parse-main-document.ts +30 -0
- package/src/io/ooxml/parse-picture.ts +14 -0
- package/src/io/ooxml/parse-shapes.ts +41 -1
- package/src/model/canonical-document.ts +17 -0
- package/src/runtime/layout/layout-engine-version.ts +8 -1
- package/src/runtime/layout/page-story-resolver.ts +1 -0
- package/src/runtime/layout/paginated-layout-engine.ts +26 -10
- package/src/runtime/surface-projection.ts +114 -12
- package/src/ui/WordReviewEditor.tsx +6 -10
- package/src/ui/editor-command-bag.ts +2 -0
- package/src/ui/ui-controller-factory.ts +2 -2
- package/src/ui-tailwind/chrome-overlay/tw-chrome-overlay.tsx +11 -25
- package/src/ui-tailwind/chrome-overlay/tw-scope-card-layer.tsx +2 -2
- package/src/ui-tailwind/chrome-overlay/tw-scope-card.tsx +1 -1
- package/src/ui-tailwind/chrome-overlay/tw-scope-rail-layer.tsx +22 -220
- package/src/ui-tailwind/debug/README.md +12 -50
- package/src/ui-tailwind/debug/tw-debug-overlay.tsx +6 -6
- package/src/ui-tailwind/debug/tw-debug-presentation.tsx +9 -20
- package/src/ui-tailwind/debug/tw-debug-top-bar.tsx +5 -6
- package/src/ui-tailwind/editor-surface/chart-node-view.tsx +1 -4
- package/src/ui-tailwind/editor-surface/picture-effects.ts +96 -0
- package/src/ui-tailwind/editor-surface/pm-schema.ts +89 -62
- package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +205 -0
- package/src/ui-tailwind/editor-surface/runtime-decoration-plugin.ts +190 -0
- package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +53 -53
- package/src/ui-tailwind/page-stack/floating-image-overlay-model.ts +83 -20
- package/src/ui-tailwind/page-stack/tw-floating-image-layer.tsx +114 -4
- package/src/ui-tailwind/page-stack/tw-page-chrome-entry.tsx +5 -0
- package/src/ui-tailwind/page-stack/tw-page-footer-band.tsx +3 -0
- package/src/ui-tailwind/page-stack/tw-page-header-band.tsx +3 -0
- package/src/ui-tailwind/page-stack/tw-page-stack-chrome-layer.tsx +8 -0
- package/src/ui-tailwind/page-stack/tw-region-block-renderer.tsx +26 -0
- package/src/ui-tailwind/theme/editor-theme.css +18 -11
- package/src/ui-tailwind/tw-review-workspace.tsx +15 -0
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.97",
|
|
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": [
|
package/src/api/public-types.ts
CHANGED
|
@@ -81,6 +81,15 @@ export type { CanonicalDocumentEnvelope };
|
|
|
81
81
|
export type * from "../model/chart-types.ts";
|
|
82
82
|
export type { ResolvedTheme } from "../model/canonical-document.ts";
|
|
83
83
|
|
|
84
|
+
/**
|
|
85
|
+
* Type-only re-export of the canonical `BlockNode` union. Exposes the
|
|
86
|
+
* shape to presentation-layer consumers that need it for identity caching
|
|
87
|
+
* (refactor/11b Slice A) without forcing them to reach into
|
|
88
|
+
* `src/model/**` directly — which is flagged by the layer-11 boundary
|
|
89
|
+
* guard. This is a TYPE-ONLY re-export: no runtime dependency is added.
|
|
90
|
+
*/
|
|
91
|
+
export type { BlockNode } from "../model/canonical-document.ts";
|
|
92
|
+
|
|
84
93
|
/**
|
|
85
94
|
* Workflow-rail types re-exported for the Layer-11 scope-rail + scope-
|
|
86
95
|
* card overlays (refactor/11 Slice 6 retirement). `WorkflowFacet` +
|
|
@@ -1198,6 +1207,8 @@ export interface SurfaceDrawingAnchor {
|
|
|
1198
1207
|
* degree (OOXML `a:xfrm a:rot`).
|
|
1199
1208
|
*/
|
|
1200
1209
|
export interface SurfacePictureEffects {
|
|
1210
|
+
/** DrawingML a:lum brightness/contrast adjustments. Values are OOXML fixed percentages. */
|
|
1211
|
+
lum?: { bright?: number; contrast?: number };
|
|
1201
1212
|
srcRect?: { top: number; bottom: number; left: number; right: number };
|
|
1202
1213
|
rotation?: number;
|
|
1203
1214
|
flipH?: boolean;
|
|
@@ -1390,10 +1401,32 @@ export type SurfaceInlineSegment =
|
|
|
1390
1401
|
};
|
|
1391
1402
|
line?: { color?: string; widthEmu?: number; noLine?: boolean };
|
|
1392
1403
|
isTextBox?: boolean;
|
|
1404
|
+
/** Text box body layout from `wps:bodyPr` / `a:bodyPr`. */
|
|
1405
|
+
textBoxBody?: SurfaceTextBoxBodyProperties;
|
|
1393
1406
|
/** First-paragraph plain-text preview when `isTextBox` is true. */
|
|
1394
1407
|
txbxText?: string;
|
|
1408
|
+
/** Run-level marks from the first text-box text run, when available. */
|
|
1409
|
+
txbxMarks?: SurfaceTextMark[];
|
|
1410
|
+
/** Run-level visual attrs from the first text-box text run, when available. */
|
|
1411
|
+
txbxMarkAttrs?: {
|
|
1412
|
+
backgroundColor?: string;
|
|
1413
|
+
charSpacing?: number;
|
|
1414
|
+
kerning?: number;
|
|
1415
|
+
textFill?: string;
|
|
1416
|
+
fontFamily?: string;
|
|
1417
|
+
fontSize?: number;
|
|
1418
|
+
textColor?: string;
|
|
1419
|
+
};
|
|
1395
1420
|
};
|
|
1396
1421
|
|
|
1422
|
+
export interface SurfaceTextBoxBodyProperties {
|
|
1423
|
+
anchor?: "t" | "ctr" | "b";
|
|
1424
|
+
insetLeftEmu?: number;
|
|
1425
|
+
insetTopEmu?: number;
|
|
1426
|
+
insetRightEmu?: number;
|
|
1427
|
+
insetBottomEmu?: number;
|
|
1428
|
+
}
|
|
1429
|
+
|
|
1397
1430
|
export interface SurfaceTableCellSnapshot {
|
|
1398
1431
|
gridSpan: number;
|
|
1399
1432
|
verticalMerge: "restart" | "continue" | null;
|
|
@@ -5758,27 +5791,8 @@ export interface WordReviewEditorProps {
|
|
|
5758
5791
|
* unless this prop suppresses it).
|
|
5759
5792
|
*/
|
|
5760
5793
|
commandPaletteDisabled?: boolean;
|
|
5761
|
-
/**
|
|
5762
|
-
* Phase Q runtime-embedded debug UX (refactor/11 Slice 7 + refactor/10
|
|
5763
|
-
* Slice 5). Default is `"off"` — the editor mounts no debug chrome
|
|
5764
|
-
* and silently ignores the `ui.debug.attach()` hook. Flip to
|
|
5765
|
-
* `"top-bar"` to mount a slim status bar showing the active session
|
|
5766
|
-
* + scope telemetry, or to `"full"` to mount the tabbed overlay with
|
|
5767
|
-
* inspector / events / REPL panes.
|
|
5768
|
-
*
|
|
5769
|
-
* **CLAUDE.md Protected Invariant** — the default MUST be `"off"`.
|
|
5770
|
-
* Predecessor debug-preview props (`showUnsupportedObjectPreviews`,
|
|
5771
|
-
* `unsupportedPreviewsPolicy`) have regressed their safe defaults
|
|
5772
|
-
* multiple times; keep this one strict.
|
|
5773
|
-
*/
|
|
5774
|
-
debugMode?: DebugMode;
|
|
5775
5794
|
}
|
|
5776
5795
|
|
|
5777
|
-
/**
|
|
5778
|
-
* Phase Q debug-UX visibility mode. Consumed by
|
|
5779
|
-
* `WordReviewEditorProps.debugMode` + `src/ui-tailwind/debug/**`.
|
|
5780
|
-
*/
|
|
5781
|
-
export type DebugMode = "off" | "top-bar" | "full";
|
|
5782
5796
|
|
|
5783
5797
|
/**
|
|
5784
5798
|
* Selection context handed to host-delegated shortcut callbacks
|
package/src/api/v3/ui/_types.ts
CHANGED
|
@@ -18,7 +18,8 @@
|
|
|
18
18
|
* U3 — selection + viewport are the only bidirectional channels.
|
|
19
19
|
* U4 — overlays derive anchors from geometry, never DOM.
|
|
20
20
|
* U5 — chrome posture composed once per read.
|
|
21
|
-
* U6 — debug attachment
|
|
21
|
+
* U6 — runtime debug attachment is production-disabled; local diagnostics
|
|
22
|
+
* use the mounted keyboard shortcut.
|
|
22
23
|
* U7 — subscribers fire on runtime state changes + coalesced rAF.
|
|
23
24
|
* U8 — no access to internal controllers.
|
|
24
25
|
*/
|
|
@@ -147,14 +148,13 @@ export interface UiController {
|
|
|
147
148
|
readonly getOverlayRects?: (query: OverlayAnchorQuery) => readonly GeometryRect[];
|
|
148
149
|
/**
|
|
149
150
|
* Host-provided chrome posture slice — the fields that live outside the
|
|
150
|
-
* runtime (host props like reviewMode / markupDisplay /
|
|
151
|
-
*
|
|
151
|
+
* runtime (host props like reviewMode / markupDisplay / chromePreset).
|
|
152
|
+
* `ui.chrome.getPosture` composes this with the runtime's
|
|
152
153
|
* guard snapshot + session/render state into the final ChromePosture.
|
|
153
154
|
*
|
|
154
155
|
* Default values when the hook is missing or returns undefined:
|
|
155
156
|
* reviewMode — "observer"
|
|
156
157
|
* markupDisplay — "final"
|
|
157
|
-
* debugMode — "off" (gated per U6; default MUST remain "off")
|
|
158
158
|
* chromePreset — undefined
|
|
159
159
|
*/
|
|
160
160
|
readonly getHostPosture?: () => ChromeHostPosture | undefined;
|
|
@@ -172,14 +172,6 @@ export interface UiController {
|
|
|
172
172
|
readonly subscribeChrome?: (
|
|
173
173
|
listener: UiListener<ChromePosture>,
|
|
174
174
|
) => UiUnsubscribe;
|
|
175
|
-
/**
|
|
176
|
-
* Debug attachment. The bind-side wires the runtime's telemetry bus and
|
|
177
|
-
* projector to the mounted debug surface (Phase Q — see
|
|
178
|
-
* `src/ui-tailwind/debug/`). Returns a cleanup function. Omitting the
|
|
179
|
-
* hook causes `ui.debug.attach` to throw rather than silently ignoring
|
|
180
|
-
* the request.
|
|
181
|
-
*/
|
|
182
|
-
readonly attachDebug?: (session: DebugSession) => () => void;
|
|
183
175
|
}
|
|
184
176
|
|
|
185
177
|
/**
|
|
@@ -317,6 +309,7 @@ export interface ChromePosture {
|
|
|
317
309
|
readonly readOnly: boolean;
|
|
318
310
|
readonly reviewMode: ChromeReviewMode;
|
|
319
311
|
readonly markupDisplay: ChromeMarkupDisplay;
|
|
312
|
+
/** Always "off" in production; runtime debug chrome is not API-enabled. */
|
|
320
313
|
readonly debugMode: ChromeDebugMode;
|
|
321
314
|
readonly chromePreset?: string;
|
|
322
315
|
readonly blockedReasons: readonly string[];
|
|
@@ -346,8 +339,6 @@ export interface ChromeSurface {
|
|
|
346
339
|
export interface ChromeHostPosture {
|
|
347
340
|
readonly reviewMode?: ChromeReviewMode;
|
|
348
341
|
readonly markupDisplay?: ChromeMarkupDisplay;
|
|
349
|
-
/** MUST default to "off" at the caller level (U6); see CLAUDE.md Protected Invariants. */
|
|
350
|
-
readonly debugMode?: ChromeDebugMode;
|
|
351
342
|
readonly chromePreset?: string;
|
|
352
343
|
}
|
|
353
344
|
|
|
@@ -466,9 +457,8 @@ export interface ApiV3UiOverlays {
|
|
|
466
457
|
/**
|
|
467
458
|
* U9 · Composed overlay visibility. Merges the class-A policy from
|
|
468
459
|
* `handle.getVisibilityPolicy(kind)` with the class-C local preference
|
|
469
|
-
* stored per UI API instance. `"debug-panel"`
|
|
470
|
-
* local preference
|
|
471
|
-
* (CLAUDE.md Protected Invariant).
|
|
460
|
+
* stored per UI API instance. `"debug-panel"` is production-forced off;
|
|
461
|
+
* local preference and legacy host posture values cannot unlock it.
|
|
472
462
|
*/
|
|
473
463
|
getVisibility(kind: OverlayKind): OverlayVisibility;
|
|
474
464
|
/** Set the per-session local preference (class C). */
|
|
@@ -478,9 +468,9 @@ export interface ApiV3UiOverlays {
|
|
|
478
468
|
/**
|
|
479
469
|
* Subscribe to visibility changes for a specific `OverlayKind`. Fires
|
|
480
470
|
* when the local preference for that kind changes (via
|
|
481
|
-
* `setLocalPreference` / `resetLocalPreference`). Policy changes
|
|
482
|
-
*
|
|
483
|
-
*
|
|
471
|
+
* `setLocalPreference` / `resetLocalPreference`). Policy changes are
|
|
472
|
+
* also fanned out through the handle when the runtime exposes the policy
|
|
473
|
+
* subscription hook. Returns an
|
|
484
474
|
* unsubscribe function.
|
|
485
475
|
*/
|
|
486
476
|
subscribeVisibility(
|
|
@@ -493,7 +483,7 @@ export interface ApiV3UiChrome {
|
|
|
493
483
|
/**
|
|
494
484
|
* Runtime-sourced chrome posture (U5.a). Narrow slice — effectiveMode,
|
|
495
485
|
* documentMode, readOnly, blocked reasons, plus the host-provided
|
|
496
|
-
* reviewMode / markupDisplay / debugMode
|
|
486
|
+
* reviewMode / markupDisplay / chromePreset. debugMode is always "off".
|
|
497
487
|
*/
|
|
498
488
|
getPosture(): ChromePosture;
|
|
499
489
|
/**
|
package/src/api/v3/ui/chrome.ts
CHANGED
|
@@ -7,10 +7,10 @@
|
|
|
7
7
|
* `handle.getInteractionGuardSnapshot()` and the
|
|
8
8
|
* mode/readOnly from `handle.getRenderSnapshot()`;
|
|
9
9
|
* overlays the host-provided slice (reviewMode,
|
|
10
|
-
* markupDisplay,
|
|
10
|
+
* markupDisplay, chromePreset) via
|
|
11
11
|
* `controller.getHostPosture()`. Host defaults
|
|
12
|
-
* applied at this layer
|
|
13
|
-
* "off"
|
|
12
|
+
* applied at this layer; debugMode is forced
|
|
13
|
+
* "off" in production.
|
|
14
14
|
* - getPinnedSurfaces — live-with-adapter. Delegates to
|
|
15
15
|
* `controller.getPinnedSurfaces()`; returns [] when
|
|
16
16
|
* unbound or the hook is absent. The existing
|
|
@@ -59,7 +59,7 @@ export const getPostureMetadata: ApiV3FnMetadata = {
|
|
|
59
59
|
stateClass: "C-local",
|
|
60
60
|
persistsTo: "none",
|
|
61
61
|
rwdReference:
|
|
62
|
-
"§UI API § ui.chrome.getPosture. Composes handle.getInteractionGuardSnapshot() + handle.getRenderSnapshot() with controller.getHostPosture() at a single site. debugMode
|
|
62
|
+
"§UI API § ui.chrome.getPosture. Composes handle.getInteractionGuardSnapshot() + handle.getRenderSnapshot() with controller.getHostPosture() at a single site. debugMode is production-forced 'off'.",
|
|
63
63
|
};
|
|
64
64
|
|
|
65
65
|
export const getCompositionMetadata: ApiV3FnMetadata = {
|
|
@@ -103,7 +103,7 @@ export const subscribeMetadata: ApiV3FnMetadata = {
|
|
|
103
103
|
commit: "refactor-07-slice-2",
|
|
104
104
|
},
|
|
105
105
|
// Stream-form bidirectional channel (U5 + U7). Posture changes
|
|
106
|
-
// (guard-snapshot updates, host posture flips like
|
|
106
|
+
// (guard-snapshot updates, host posture flips like reviewMode)
|
|
107
107
|
// fan out through this listener. Coalescing is microtask — posture
|
|
108
108
|
// composes at most once per render tick, no rAF batching needed.
|
|
109
109
|
uxIntent: {
|
|
@@ -328,13 +328,12 @@ export function createChromeFamily(ctx: UiApiContext) {
|
|
|
328
328
|
: "edit";
|
|
329
329
|
const readOnly = render?.readOnly === true;
|
|
330
330
|
|
|
331
|
-
// Host-provided slice — reviewMode / markupDisplay /
|
|
332
|
-
//
|
|
333
|
-
// to "off" (CLAUDE.md Protected Invariants).
|
|
331
|
+
// Host-provided slice — reviewMode / markupDisplay / chromePreset.
|
|
332
|
+
// Runtime debug chrome is not host-controllable in production.
|
|
334
333
|
const host = ctx.binding?.controller.getHostPosture?.() ?? undefined;
|
|
335
334
|
const reviewMode = host?.reviewMode ?? "observer";
|
|
336
335
|
const markupDisplay = host?.markupDisplay ?? "final";
|
|
337
|
-
const debugMode =
|
|
336
|
+
const debugMode = "off";
|
|
338
337
|
const chromePreset = host?.chromePreset;
|
|
339
338
|
|
|
340
339
|
return {
|
package/src/api/v3/ui/debug.ts
CHANGED
|
@@ -1,28 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @endStateApi v3 — `ui.debug` family (layer 10).
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* - attach —
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* `detach` invokes the controller-provided cleanup and
|
|
10
|
-
* clears the UI API's attachment record.
|
|
11
|
-
* - detach — live-with-adapter. Ends the current attachment (if any)
|
|
12
|
-
* by invoking its controller-provided cleanup. Idempotent
|
|
13
|
-
* — calling detach without an attachment is a no-op.
|
|
4
|
+
* Production hardening:
|
|
5
|
+
* - attach — intentionally disabled. Runtime debug chrome is no longer
|
|
6
|
+
* activatable through the public UI API.
|
|
7
|
+
* - detach — retained as an idempotent no-op for callers that defensively
|
|
8
|
+
* clean up a previously attempted attachment.
|
|
14
9
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* #3 and §9 finishing-development-branch discipline, Slice 5 ships the
|
|
18
|
-
* substrate; the Phase Q React components + `debugMode` public prop +
|
|
19
|
-
* `test/ui/debug-mode-visibility-invariant.test.ts` land in a follow-up.
|
|
20
|
-
* `src/ui-tailwind/debug/README.md` documents the reserved scope.
|
|
21
|
-
*
|
|
22
|
-
* Contract U6 — debug attachment is opt-in and scoped. Default state:
|
|
23
|
-
* no debug UX. The `debugMode` prop gate (to-be-added) defaults to
|
|
24
|
-
* `"off"`. CLAUDE.md Protected Invariants flag this has regressed
|
|
25
|
-
* multiple times.
|
|
10
|
+
* The only supported local diagnostic surface in the mounted editor is the
|
|
11
|
+
* runtime REPL keyboard shortcut.
|
|
26
12
|
*/
|
|
27
13
|
|
|
28
14
|
import type { ApiV3FnMetadata } from "../_layer-metadata.ts";
|
|
@@ -42,7 +28,7 @@ export const attachMetadata: ApiV3FnMetadata = {
|
|
|
42
28
|
stateClass: "C-local",
|
|
43
29
|
persistsTo: "none",
|
|
44
30
|
rwdReference:
|
|
45
|
-
"§UI API § ui.debug.attach.
|
|
31
|
+
"§UI API § ui.debug.attach. Production-disabled hard stop: always throws and never routes to a controller hook. Use the mounted runtime REPL keyboard shortcut for local diagnostics.",
|
|
46
32
|
};
|
|
47
33
|
|
|
48
34
|
export const detachMetadata: ApiV3FnMetadata = {
|
|
@@ -58,67 +44,19 @@ export const detachMetadata: ApiV3FnMetadata = {
|
|
|
58
44
|
stateClass: "C-local",
|
|
59
45
|
persistsTo: "none",
|
|
60
46
|
rwdReference:
|
|
61
|
-
"§UI API § ui.debug.detach.
|
|
47
|
+
"§UI API § ui.debug.detach. Production-disabled companion no-op retained for defensive cleanup compatibility.",
|
|
62
48
|
};
|
|
63
49
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
cleanup: () => void;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
export function createDebugFamily(ctx: UiApiContext) {
|
|
70
|
-
// Per-instance attachment state. One UI API holds at most one active
|
|
71
|
-
// debug attachment; re-attaching tears down the prior attachment first.
|
|
72
|
-
let current: DebugAttachmentState | null = null;
|
|
73
|
-
|
|
74
|
-
function detachInternal(): void {
|
|
75
|
-
if (current) {
|
|
76
|
-
try {
|
|
77
|
-
current.cleanup();
|
|
78
|
-
} finally {
|
|
79
|
-
current = null;
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
}
|
|
50
|
+
const DEBUG_ATTACH_DISABLED_MESSAGE =
|
|
51
|
+
"ui.debug.attach is disabled in production; use the runtime REPL keyboard shortcut instead.";
|
|
83
52
|
|
|
53
|
+
export function createDebugFamily(_ctx: UiApiContext) {
|
|
84
54
|
return {
|
|
85
|
-
attach(
|
|
86
|
-
|
|
87
|
-
if (!controller) {
|
|
88
|
-
throw new Error(
|
|
89
|
-
"ui.debug.attach: no controller bound — call ui.session.bind(controller) first",
|
|
90
|
-
);
|
|
91
|
-
}
|
|
92
|
-
if (!controller.attachDebug) {
|
|
93
|
-
throw new Error(
|
|
94
|
-
`ui.debug.attach: controller of kind "${controller.kind}" did not provide an attachDebug hook`,
|
|
95
|
-
);
|
|
96
|
-
}
|
|
97
|
-
// Tear down any prior attachment so re-attaching does not leak
|
|
98
|
-
// subscriptions or telemetry-bus listeners.
|
|
99
|
-
detachInternal();
|
|
100
|
-
const cleanup = controller.attachDebug(session);
|
|
101
|
-
current = { session, cleanup };
|
|
102
|
-
// NOTE: `DebugAttachment.detach` is aliased to `detachInternal`,
|
|
103
|
-
// which detaches **whatever is currently attached** — not
|
|
104
|
-
// specifically the session this attachment was created with. At
|
|
105
|
-
// most one attachment is live per UI API instance, so this is
|
|
106
|
-
// semantically correct under the "single attachment at a time"
|
|
107
|
-
// model. Callers that hold onto an older `attachment` ref and
|
|
108
|
-
// call its `.detach()` after a re-attach will tear down the
|
|
109
|
-
// NEWER attachment. Typical hosts create + detach in lockstep
|
|
110
|
-
// (attach on mount, detach on unmount), so this is fine; the
|
|
111
|
-
// debug service's RPC driver re-attaches on every session, which
|
|
112
|
-
// matches this model.
|
|
113
|
-
return {
|
|
114
|
-
session,
|
|
115
|
-
detach: detachInternal,
|
|
116
|
-
};
|
|
55
|
+
attach(_session: DebugSession): DebugAttachment {
|
|
56
|
+
throw new Error(DEBUG_ATTACH_DISABLED_MESSAGE);
|
|
117
57
|
},
|
|
118
58
|
detach(): void {
|
|
119
|
-
// Idempotent
|
|
120
|
-
// state is "no debug UX", so repeated detach calls must stay safe.
|
|
121
|
-
detachInternal();
|
|
59
|
+
// Idempotent compatibility no-op.
|
|
122
60
|
},
|
|
123
61
|
};
|
|
124
62
|
}
|
|
@@ -20,8 +20,8 @@
|
|
|
20
20
|
* policy = handle.getVisibilityPolicy(kind) // may be null
|
|
21
21
|
* pref = localPrefs.get(kind) // may be undefined
|
|
22
22
|
*
|
|
23
|
-
* # debug-panel special case:
|
|
24
|
-
* if kind === "debug-panel"
|
|
23
|
+
* # debug-panel special case: production debug gate wins over everything.
|
|
24
|
+
* if kind === "debug-panel":
|
|
25
25
|
* → { state: "forced-off", reason: "policy-enforcement" }
|
|
26
26
|
*
|
|
27
27
|
* # Policy enforcement is a hard override.
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
* → defaultOn ? visible : hidden, reason: "policy-default"
|
|
40
40
|
*
|
|
41
41
|
* Subscribe fan-out is local-preference-only in this slice. Policy
|
|
42
|
-
* changes
|
|
42
|
+
* changes do not wake listeners yet — consumers
|
|
43
43
|
* that depend on either re-read visibility on their own cadence.
|
|
44
44
|
* Follow-up slice will subscribe to the L06 policy telemetry bus and
|
|
45
45
|
* fan out on `workflow.visibility_policy_changed`.
|
|
@@ -85,13 +85,12 @@ export function createOverlaysVisibilityState(): OverlaysVisibilityState {
|
|
|
85
85
|
}
|
|
86
86
|
|
|
87
87
|
/**
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
* — unbound / no hook / no debugMode field all resolve to `"off"` per
|
|
91
|
-
* U6 + CLAUDE.md Protected Invariants.
|
|
88
|
+
* Runtime debug chrome is production-disabled. Legacy host posture values
|
|
89
|
+
* must not unlock the debug panel.
|
|
92
90
|
*/
|
|
93
91
|
function readDebugMode(ctx: UiApiContext): "off" | "top-bar" | "full" {
|
|
94
|
-
|
|
92
|
+
void ctx;
|
|
93
|
+
return "off";
|
|
95
94
|
}
|
|
96
95
|
|
|
97
96
|
function readPolicy(
|
|
@@ -115,8 +114,8 @@ export function composeOverlayVisibility(
|
|
|
115
114
|
state: OverlaysVisibilityState,
|
|
116
115
|
kind: OverlayKind,
|
|
117
116
|
): OverlayVisibility {
|
|
118
|
-
// debug-panel:
|
|
119
|
-
// regardless of policy or
|
|
117
|
+
// debug-panel: runtime debug chrome is forced off in production
|
|
118
|
+
// regardless of policy, preference, or legacy host posture.
|
|
120
119
|
if (kind === "debug-panel" && readDebugMode(ctx) === "off") {
|
|
121
120
|
return { state: "forced-off", reason: "policy-enforcement" };
|
|
122
121
|
}
|
|
@@ -39,7 +39,6 @@
|
|
|
39
39
|
|
|
40
40
|
import type { ApiV3FnMetadata } from "../_layer-metadata.ts";
|
|
41
41
|
import type {
|
|
42
|
-
ChromePosture,
|
|
43
42
|
GeometryRect,
|
|
44
43
|
OverlayAnchorQuery,
|
|
45
44
|
OverlayVisibility,
|
|
@@ -210,7 +209,7 @@ export const getVisibilityMetadata: ApiV3FnMetadata = {
|
|
|
210
209
|
agentMetadata: { readOrMutate: "read", boundedScope: "session", auditCategory: "ui-overlays-read" },
|
|
211
210
|
stateClass: "C-local",
|
|
212
211
|
persistsTo: "none",
|
|
213
|
-
rwdReference: "§UI API § ui.overlays.getVisibility (U9). Single composition site merging class-A policy (handle.getVisibilityPolicy) with class-C local preference. debug-panel is
|
|
212
|
+
rwdReference: "§UI API § ui.overlays.getVisibility (U9). Single composition site merging class-A policy (handle.getVisibilityPolicy) with class-C local preference. debug-panel is production-forced off.",
|
|
214
213
|
};
|
|
215
214
|
|
|
216
215
|
export const setLocalPreferenceMetadata: ApiV3FnMetadata = {
|
|
@@ -273,7 +272,7 @@ export const subscribeVisibilityMetadata: ApiV3FnMetadata = {
|
|
|
273
272
|
payloadType: "OverlayVisibility",
|
|
274
273
|
coalescing: "microtask",
|
|
275
274
|
},
|
|
276
|
-
rwdReference: "§UI API § ui.overlays.subscribeVisibility (U9). Fires on
|
|
275
|
+
rwdReference: "§UI API § ui.overlays.subscribeVisibility (U9). Fires on local-preference mutation and class-A policy-change (chained to L06's `handle.subscribeVisibilityPolicy`, shipped 2026-04-22). debug-panel remains production-forced off and does not subscribe to chrome debugMode changes.",
|
|
277
276
|
};
|
|
278
277
|
|
|
279
278
|
export function createOverlaysFamily(ctx: UiApiContext) {
|
|
@@ -313,59 +312,13 @@ export function createOverlaysFamily(ctx: UiApiContext) {
|
|
|
313
312
|
}
|
|
314
313
|
});
|
|
315
314
|
|
|
316
|
-
// KI-007 close — chrome-change fan-out for `debug-panel` visibility.
|
|
317
|
-
//
|
|
318
|
-
// The composed `debug-panel` visibility depends on three inputs:
|
|
319
|
-
// (a) class-A policy, (b) class-C local preference, and (c)
|
|
320
|
-
// ChromePosture.debugMode. Fan-out for (a) + (b) was already wired;
|
|
321
|
-
// (c) was the missing channel. Now registered lazily on the first
|
|
322
|
-
// `subscribeVisibility("debug-panel", …)` call and torn down when the
|
|
323
|
-
// last such subscriber unsubscribes, so the chrome-listener cost only
|
|
324
|
-
// applies when someone is actually listening.
|
|
325
|
-
//
|
|
326
|
-
// Comparison-based firing: we compare `ChromePosture.debugMode` to
|
|
327
|
-
// the previous value on each chrome tick and only re-notify when it
|
|
328
|
-
// changes. This avoids firing debug-panel visibility subscribers on
|
|
329
|
-
// unrelated chrome changes (reviewMode flip, markup-display change,
|
|
330
|
-
// etc.) — cost-efficient + semantically correct.
|
|
331
|
-
//
|
|
332
|
-
// Note on re-bind: if the host calls `ui.session.release()` then
|
|
333
|
-
// `bind(newController)`, the chrome-listener's underlying hook is
|
|
334
|
-
// torn down with the old controller. Consumers already subscribed to
|
|
335
|
-
// debug-panel visibility continue holding their listeners but stop
|
|
336
|
-
// receiving chrome-tick fan-out until they unsubscribe and re-
|
|
337
|
-
// subscribe. Release+rebind is a lifecycle event hosts handle
|
|
338
|
-
// explicitly; documented here rather than auto-re-registering
|
|
339
|
-
// because a full bind-cycle integration would also require reading
|
|
340
|
-
// the initial posture to seed `lastKnownDebugMode`, which is out of
|
|
341
|
-
// scope for this targeted fix.
|
|
342
|
-
let chromeUnsubscribe: (() => void) | null = null;
|
|
343
|
-
let lastKnownDebugMode: ChromePosture["debugMode"] | undefined;
|
|
344
|
-
|
|
345
315
|
function ensureChromeFanout(): void {
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
if (!controller?.subscribeChrome) return;
|
|
349
|
-
// Seed last-known from the current posture so the first real change
|
|
350
|
-
// doesn't false-fire against undefined.
|
|
351
|
-
const posture = readPostureForDebugModeSeed(ctx);
|
|
352
|
-
lastKnownDebugMode = posture?.debugMode;
|
|
353
|
-
chromeUnsubscribe = controller.subscribeChrome((next) => {
|
|
354
|
-
const nextMode = next?.debugMode;
|
|
355
|
-
if (nextMode === lastKnownDebugMode) return;
|
|
356
|
-
lastKnownDebugMode = nextMode;
|
|
357
|
-
notifyVisibilitySubscribers(ctx, visibilityState, "debug-panel");
|
|
358
|
-
});
|
|
316
|
+
// Runtime debug chrome is production-disabled, so debug-panel
|
|
317
|
+
// visibility is constant forced-off and never depends on chrome ticks.
|
|
359
318
|
}
|
|
360
319
|
|
|
361
320
|
function releaseChromeFanoutIfEmpty(): void {
|
|
362
|
-
|
|
363
|
-
if (debugPanelSubs && debugPanelSubs.size > 0) return;
|
|
364
|
-
if (chromeUnsubscribe) {
|
|
365
|
-
chromeUnsubscribe();
|
|
366
|
-
chromeUnsubscribe = null;
|
|
367
|
-
lastKnownDebugMode = undefined;
|
|
368
|
-
}
|
|
321
|
+
// Paired no-op for the disabled debug-panel chrome fan-out.
|
|
369
322
|
}
|
|
370
323
|
|
|
371
324
|
// KI-006 close — per-query coalesced subscribe channel.
|
|
@@ -653,9 +606,8 @@ export function createOverlaysFamily(ctx: UiApiContext) {
|
|
|
653
606
|
visibilityState.subscribers.set(kind, listeners);
|
|
654
607
|
}
|
|
655
608
|
listeners.add(listener);
|
|
656
|
-
//
|
|
657
|
-
// debug-panel
|
|
658
|
-
// when a chrome listener is already active.
|
|
609
|
+
// Runtime debug chrome is production-disabled; this remains a no-op
|
|
610
|
+
// for debug-panel subscribers and other kinds.
|
|
659
611
|
if (kind === "debug-panel") {
|
|
660
612
|
ensureChromeFanout();
|
|
661
613
|
}
|
|
@@ -672,8 +624,7 @@ export function createOverlaysFamily(ctx: UiApiContext) {
|
|
|
672
624
|
if (!current) return;
|
|
673
625
|
current.delete(listener);
|
|
674
626
|
if (current.size === 0) visibilityState.subscribers.delete(kind);
|
|
675
|
-
//
|
|
676
|
-
// debug-panel subscriber unsubscribes.
|
|
627
|
+
// Paired no-op for the disabled debug-panel chrome fan-out.
|
|
677
628
|
if (kind === "debug-panel") {
|
|
678
629
|
releaseChromeFanoutIfEmpty();
|
|
679
630
|
}
|
|
@@ -681,21 +632,3 @@ export function createOverlaysFamily(ctx: UiApiContext) {
|
|
|
681
632
|
},
|
|
682
633
|
};
|
|
683
634
|
}
|
|
684
|
-
|
|
685
|
-
/**
|
|
686
|
-
* KI-007 helper — read the current `ChromePosture.debugMode` value so
|
|
687
|
-
* the chrome-change fan-out can seed its `lastKnownDebugMode` tracker
|
|
688
|
-
* without false-firing on the first tick. Mirrors the logic in
|
|
689
|
-
* `chrome.ts::getPosture` but only extracts `debugMode` + returns
|
|
690
|
-
* `undefined` on any missing piece (rather than composing a full
|
|
691
|
-
* posture). Keeps the debug-panel fan-out path cheap.
|
|
692
|
-
*/
|
|
693
|
-
function readPostureForDebugModeSeed(
|
|
694
|
-
ctx: UiApiContext,
|
|
695
|
-
): { debugMode: ChromePosture["debugMode"] } | null {
|
|
696
|
-
const hook = ctx.binding?.controller.getHostPosture;
|
|
697
|
-
if (!hook) return null;
|
|
698
|
-
const host = hook();
|
|
699
|
-
if (!host) return null;
|
|
700
|
-
return { debugMode: host.debugMode ?? "off" };
|
|
701
|
-
}
|
|
@@ -2749,6 +2749,36 @@ function parseRun(
|
|
|
2749
2749
|
);
|
|
2750
2750
|
break;
|
|
2751
2751
|
}
|
|
2752
|
+
case "AlternateContent": {
|
|
2753
|
+
const altXml = sourceXml.slice(child.start, child.end);
|
|
2754
|
+
try {
|
|
2755
|
+
const frame = parseDrawingFrame(altXml, {
|
|
2756
|
+
relationships,
|
|
2757
|
+
mediaParts,
|
|
2758
|
+
sourcePartPath,
|
|
2759
|
+
chartPartLookup: activeChartPartLookup,
|
|
2760
|
+
blockParser: (xml) =>
|
|
2761
|
+
parseBlockStreamFromXml(xml, {
|
|
2762
|
+
relationships,
|
|
2763
|
+
mediaParts,
|
|
2764
|
+
sourcePartPath,
|
|
2765
|
+
depth: activeTxbxBlockStreamDepth + 1,
|
|
2766
|
+
}) as unknown as ReadonlyArray<{ type: string; [key: string]: unknown }>,
|
|
2767
|
+
});
|
|
2768
|
+
if (frame) {
|
|
2769
|
+
result.push(frame);
|
|
2770
|
+
break;
|
|
2771
|
+
}
|
|
2772
|
+
} catch {
|
|
2773
|
+
// Fall through to opaque preservation below.
|
|
2774
|
+
}
|
|
2775
|
+
encounteredUnsupportedChild = true;
|
|
2776
|
+
result.push({
|
|
2777
|
+
type: "opaque_inline",
|
|
2778
|
+
rawXml: altXml,
|
|
2779
|
+
});
|
|
2780
|
+
break;
|
|
2781
|
+
}
|
|
2752
2782
|
case "pict": {
|
|
2753
2783
|
const pictXml = sourceXml.slice(child.start, child.end);
|
|
2754
2784
|
try {
|
|
@@ -49,6 +49,13 @@ export function parsePicture(graphicDataEl: XmlElementNode): PictureContent | nu
|
|
|
49
49
|
const blipRef = blipEmbed ?? blipLink ?? "";
|
|
50
50
|
if (!blipRef) return null;
|
|
51
51
|
const isLinked = !blipEmbed && !!blipLink;
|
|
52
|
+
const lumEl = findFirstChild(blip, "lum");
|
|
53
|
+
const lum = lumEl
|
|
54
|
+
? {
|
|
55
|
+
bright: readIntAttr(lumEl, "bright"),
|
|
56
|
+
contrast: readIntAttr(lumEl, "contrast"),
|
|
57
|
+
}
|
|
58
|
+
: undefined;
|
|
52
59
|
|
|
53
60
|
// srcRect (percentage crop: 0..100000 = 0..100%)
|
|
54
61
|
const srcRectEl = findFirstChild(blipFill, "srcRect");
|
|
@@ -95,6 +102,9 @@ export function parsePicture(graphicDataEl: XmlElementNode): PictureContent | nu
|
|
|
95
102
|
|
|
96
103
|
const result: PictureContent = { type: "picture", blipRef };
|
|
97
104
|
if (isLinked) result.isLinked = true;
|
|
105
|
+
if (lum && (lum.bright !== undefined || lum.contrast !== undefined)) {
|
|
106
|
+
result.lum = lum;
|
|
107
|
+
}
|
|
98
108
|
if (srcRect) result.srcRect = srcRect;
|
|
99
109
|
if (stretch !== undefined) result.stretch = stretch;
|
|
100
110
|
if (tile !== undefined) result.tile = tile;
|
|
@@ -109,6 +119,10 @@ export function parsePicture(graphicDataEl: XmlElementNode): PictureContent | nu
|
|
|
109
119
|
}
|
|
110
120
|
|
|
111
121
|
function readEmuAttr(el: XmlElementNode, name: string): number | undefined {
|
|
122
|
+
return readIntAttr(el, name);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function readIntAttr(el: XmlElementNode, name: string): number | undefined {
|
|
112
126
|
const v = el.attributes[name];
|
|
113
127
|
if (v === undefined) return undefined;
|
|
114
128
|
const n = parseInt(v, 10);
|