@beyondwork/docx-react-component 1.0.58 → 1.0.59

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.
Files changed (134) hide show
  1. package/README.md +2 -2
  2. package/package.json +2 -1
  3. package/src/api/awareness-identity-types.ts +4 -2
  4. package/src/api/comment-negotiation-types.ts +4 -1
  5. package/src/api/external-custody-types.ts +16 -0
  6. package/src/api/internal/build-ref-projections.ts +108 -0
  7. package/src/api/package-version.ts +1 -1
  8. package/src/api/participants-types.ts +11 -1
  9. package/src/api/public-types.ts +978 -10
  10. package/src/api/scope-metadata-resolver-types.ts +6 -0
  11. package/src/compare/diff-engine.ts +3 -0
  12. package/src/core/commands/formatting-commands.ts +1 -0
  13. package/src/core/commands/index.ts +225 -16
  14. package/src/core/commands/legacy-form-field-commands.ts +181 -0
  15. package/src/core/commands/table-structure-commands.ts +149 -31
  16. package/src/core/selection/mapping.ts +20 -0
  17. package/src/core/state/editor-state.ts +2 -1
  18. package/src/index.ts +28 -0
  19. package/src/io/docx-session.ts +22 -3
  20. package/src/io/export/export-session.ts +11 -7
  21. package/src/io/export/ooxml-namespaces.ts +47 -0
  22. package/src/io/export/reattach-preserved-parts.ts +4 -16
  23. package/src/io/export/serialize-comments.ts +3 -131
  24. package/src/io/export/serialize-ffdata.ts +89 -0
  25. package/src/io/export/serialize-headers-footers.ts +5 -0
  26. package/src/io/export/serialize-main-document.ts +224 -34
  27. package/src/io/export/serialize-numbering.ts +22 -2
  28. package/src/io/export/serialize-revisions.ts +99 -0
  29. package/src/io/export/serialize-tables.ts +9 -0
  30. package/src/io/export/split-review-boundaries.ts +1 -0
  31. package/src/io/export/table-properties-xml.ts +14 -0
  32. package/src/io/load-scheduler.ts +70 -28
  33. package/src/io/normalize/normalize-text.ts +13 -0
  34. package/src/io/ooxml/_mini-xml.ts +198 -0
  35. package/src/io/ooxml/canonicalize-payload.ts +1 -4
  36. package/src/io/ooxml/chart/chart-style-table.ts +4 -3
  37. package/src/io/ooxml/chart/parse-chart-space.ts +2 -4
  38. package/src/io/ooxml/chart/parse-series.ts +2 -1
  39. package/src/io/ooxml/chart/resolve-color.ts +2 -2
  40. package/src/io/ooxml/chart/types.ts +6 -434
  41. package/src/io/ooxml/comment-presentation-payload.ts +6 -5
  42. package/src/io/ooxml/highlight-colors.ts +8 -5
  43. package/src/io/ooxml/parse-anchor.ts +68 -53
  44. package/src/io/ooxml/parse-comments.ts +14 -142
  45. package/src/io/ooxml/parse-complex-content.ts +3 -106
  46. package/src/io/ooxml/parse-drawing.ts +100 -195
  47. package/src/io/ooxml/parse-ffdata.ts +93 -0
  48. package/src/io/ooxml/parse-fields.ts +7 -146
  49. package/src/io/ooxml/parse-fill.ts +88 -8
  50. package/src/io/ooxml/parse-font-table.ts +5 -105
  51. package/src/io/ooxml/parse-footnotes.ts +28 -152
  52. package/src/io/ooxml/parse-headers-footers.ts +106 -212
  53. package/src/io/ooxml/parse-inline-media.ts +3 -200
  54. package/src/io/ooxml/parse-main-document.ts +180 -217
  55. package/src/io/ooxml/parse-numbering.ts +154 -335
  56. package/src/io/ooxml/parse-object.ts +147 -0
  57. package/src/io/ooxml/parse-ole-relationship.ts +82 -0
  58. package/src/io/ooxml/parse-paragraph-formatting.ts +7 -10
  59. package/src/io/ooxml/parse-picture-sdt.ts +85 -0
  60. package/src/io/ooxml/parse-picture.ts +72 -42
  61. package/src/io/ooxml/parse-revisions.ts +285 -51
  62. package/src/io/ooxml/parse-settings.ts +6 -99
  63. package/src/io/ooxml/parse-shapes.ts +25 -140
  64. package/src/io/ooxml/parse-styles.ts +3 -218
  65. package/src/io/ooxml/parse-tables.ts +76 -256
  66. package/src/io/ooxml/parse-theme.ts +1 -4
  67. package/src/io/ooxml/property-grab-bag.ts +5 -47
  68. package/src/io/ooxml/xml-element-serialize.ts +32 -0
  69. package/src/io/ooxml/xml-parser.ts +183 -0
  70. package/src/legal/bookmarks.ts +1 -1
  71. package/src/legal/cross-references.ts +1 -1
  72. package/src/legal/defined-terms.ts +1 -1
  73. package/src/legal/{_document-root.ts → document-root.ts} +8 -0
  74. package/src/legal/signature-blocks.ts +1 -1
  75. package/src/model/canonical-document.ts +159 -6
  76. package/src/model/chart-types.ts +439 -0
  77. package/src/model/snapshot.ts +3 -1
  78. package/src/review/store/comment-remapping.ts +24 -11
  79. package/src/review/store/revision-actions.ts +482 -2
  80. package/src/review/store/revision-store.ts +15 -0
  81. package/src/review/store/revision-types.ts +76 -0
  82. package/src/runtime/collab/remote-cursor-awareness.ts +24 -0
  83. package/src/runtime/collab/runtime-collab-sync.ts +33 -0
  84. package/src/runtime/diagnostics/build-diagnostic.ts +151 -0
  85. package/src/runtime/diagnostics/code-metadata-table.ts +221 -0
  86. package/src/runtime/document-runtime.ts +476 -34
  87. package/src/runtime/document-search.ts +115 -0
  88. package/src/runtime/edit-ops/index.ts +18 -2
  89. package/src/runtime/footnote-resolver.ts +130 -0
  90. package/src/runtime/layout/layout-engine-instance.ts +31 -4
  91. package/src/runtime/layout/layout-engine-version.ts +37 -1
  92. package/src/runtime/layout/page-graph.ts +14 -1
  93. package/src/runtime/layout/resolved-formatting-state.ts +21 -0
  94. package/src/runtime/numbering-prefix.ts +17 -0
  95. package/src/runtime/query-scopes.ts +5 -8
  96. package/src/runtime/resolved-numbering-geometry.ts +37 -6
  97. package/src/runtime/revision-runtime.ts +27 -1
  98. package/src/runtime/selection/post-edit-validator.ts +60 -6
  99. package/src/runtime/structure-ops/index.ts +20 -4
  100. package/src/runtime/surface-projection.ts +290 -21
  101. package/src/runtime/table-schema.ts +6 -0
  102. package/src/runtime/theme-color-resolver.ts +2 -2
  103. package/src/runtime/units.ts +9 -0
  104. package/src/runtime/workflow-rail-segments.ts +4 -0
  105. package/src/ui/WordReviewEditor.tsx +187 -43
  106. package/src/ui/editor-runtime-boundary.ts +10 -0
  107. package/src/ui/editor-shell-view.tsx +4 -1
  108. package/src/ui/headless/chrome-registry.ts +53 -0
  109. package/src/ui/headless/selection-tool-resolver.ts +11 -1
  110. package/src/ui-tailwind/chrome/chrome-preset-model.ts +13 -0
  111. package/src/ui-tailwind/chrome/tw-command-palette-mount.tsx +96 -0
  112. package/src/ui-tailwind/chrome/tw-context-menu.tsx +2 -1
  113. package/src/ui-tailwind/chrome/tw-image-context-toolbar.tsx +5 -4
  114. package/src/ui-tailwind/chrome/tw-mode-dock.tsx +6 -2
  115. package/src/ui-tailwind/chrome/use-container-breakpoint.ts +111 -0
  116. package/src/ui-tailwind/chrome-overlay/tw-chrome-overlay.tsx +0 -9
  117. package/src/ui-tailwind/chrome-overlay/tw-object-selection-overlay.tsx +1 -0
  118. package/src/ui-tailwind/chrome-overlay/tw-page-stack-overlay-layer.tsx +6 -7
  119. package/src/ui-tailwind/editor-surface/pm-schema.ts +87 -25
  120. package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +9 -0
  121. package/src/ui-tailwind/editor-surface/shape-renderer.ts +76 -14
  122. package/src/ui-tailwind/editor-surface/tw-page-block-view.helpers.ts +18 -1
  123. package/src/ui-tailwind/editor-surface/tw-page-block-view.tsx +2 -0
  124. package/src/ui-tailwind/editor-surface/tw-table-node-view.tsx +18 -2
  125. package/src/ui-tailwind/index.ts +9 -0
  126. package/src/ui-tailwind/page-chrome-model.ts +77 -5
  127. package/src/ui-tailwind/page-stack/tw-page-stack-chrome-layer.tsx +56 -1
  128. package/src/ui-tailwind/page-stack/tw-region-block-renderer.tsx +2 -0
  129. package/src/ui-tailwind/review/tw-comment-sidebar.tsx +116 -113
  130. package/src/ui-tailwind/review/tw-review-rail-footer.tsx +2 -2
  131. package/src/ui-tailwind/theme/tokens.ts +14 -0
  132. package/src/ui-tailwind/toolbar/tw-shell-header.tsx +5 -0
  133. package/src/ui-tailwind/tw-review-workspace.tsx +29 -87
  134. package/src/validation/diagnostics.ts +1 -0
@@ -0,0 +1,439 @@
1
+ /**
2
+ * Chart data-model types (Stage 1).
3
+ *
4
+ * Pure type declarations. No runtime code, no imports from runtime modules.
5
+ * `ChartModel` is a discriminated union (by `kind`) covering every chart
6
+ * family we parse or intentionally mark as unsupported. Parsed models retain
7
+ * the source XML in `rawXml` so the export path (which re-emits the
8
+ * original drawing verbatim) stays byte-identical regardless of renderer
9
+ * coverage.
10
+ *
11
+ * Stage 2 will add the theme/colors/style cascade that turns `ColorRef`
12
+ * values into concrete sRGB strings. Until then, colors are declared as
13
+ * references; consumers of Stage 1 models should treat color resolution as
14
+ * "not yet available."
15
+ *
16
+ * See docs/plans/lane-5-charts.md §3 Task 1.1 for the full specification.
17
+ */
18
+
19
+ // ---------------------------------------------------------------------------
20
+ // Top-level union
21
+ // ---------------------------------------------------------------------------
22
+
23
+ export type ChartModel =
24
+ | BarChartModel
25
+ | LineChartModel
26
+ | PieChartModel
27
+ | AreaChartModel
28
+ | ScatterChartModel
29
+ | BubbleChartModel
30
+ | ComboChartModel
31
+ | UnsupportedChartModel;
32
+
33
+ export interface ChartCommon {
34
+ /** Chart title, if present. */
35
+ title?: Title;
36
+ /** Legend configuration; absent if the chart has no legend. */
37
+ legend?: Legend;
38
+ /** Whether to plot only visible cells (c:plotVisOnly). */
39
+ plotVisOnly: boolean;
40
+ /** How to render blank/null values (c:dispBlanksAs). */
41
+ dispBlanksAs: "gap" | "zero" | "span";
42
+ /** Legacy chart style id (c:style val, 1–48), if present. */
43
+ styleId?: number;
44
+ /** Theme color scheme slot names available to this chart (accent1..6 etc.). */
45
+ themeColorScheme?: string[];
46
+ /**
47
+ * The raw chart-space XML that produced this model. Preserved so the
48
+ * export path can re-emit the original drawing without depending on the
49
+ * renderer's coverage of a given chart family.
50
+ */
51
+ rawXml: string;
52
+ }
53
+
54
+ /**
55
+ * Category-like axis binding for bar/line/area charts. Date axes are a
56
+ * legal substitute for a category axis at the XML level (c:dateAx replaces
57
+ * c:catAx) and we preserve the kind here so the renderer can format
58
+ * category labels as dates when the source authored them that way.
59
+ */
60
+ export type CategoryLikeAxis = CategoryAxis | DateAxis;
61
+
62
+ export interface BarChartModel extends ChartCommon {
63
+ kind: "bar";
64
+ /** "bar" = horizontal; "column" = vertical. */
65
+ direction: "bar" | "column";
66
+ grouping: "clustered" | "stacked" | "percentStacked" | "standard";
67
+ /** Gap between categories in percent of bar width (c:gapWidth). */
68
+ gapWidth: number;
69
+ /** Overlap within a category in percent, -100..100 (c:overlap). */
70
+ overlap: number;
71
+ series: Series[];
72
+ categoryAxis: CategoryLikeAxis;
73
+ valueAxis: ValueAxis;
74
+ /** Present when any series/group targeted the secondary axis. */
75
+ secondaryValueAxis?: ValueAxis;
76
+ }
77
+
78
+ export interface LineChartModel extends ChartCommon {
79
+ kind: "line";
80
+ grouping: "standard" | "stacked" | "percentStacked";
81
+ /** Global smooth flag; per-series values can override. */
82
+ smooth: boolean;
83
+ /** Global marker flag; per-series values can override. */
84
+ marker: boolean;
85
+ series: LineSeries[];
86
+ categoryAxis: CategoryLikeAxis;
87
+ valueAxis: ValueAxis;
88
+ secondaryValueAxis?: ValueAxis;
89
+ }
90
+
91
+ export interface PieChartModel extends ChartCommon {
92
+ kind: "pie";
93
+ doughnut: boolean;
94
+ /** Doughnut hole size in percent, typical 10–75 (c:holeSize). */
95
+ holeSizePercent?: number;
96
+ /** Clockwise degrees from 12 o'clock, 0–360 (c:firstSliceAngle / 60000). */
97
+ firstSliceAngle: number;
98
+ varyColors: boolean;
99
+ /** Almost always one series; pie-of-pie is length 2 (deferred). */
100
+ series: PieSeries[];
101
+ /** Category labels (from the first series' c:cat). */
102
+ categoryLabels: string[];
103
+ }
104
+
105
+ export interface AreaChartModel extends ChartCommon {
106
+ kind: "area";
107
+ grouping: "standard" | "stacked" | "percentStacked";
108
+ series: Series[];
109
+ categoryAxis: CategoryLikeAxis;
110
+ valueAxis: ValueAxis;
111
+ secondaryValueAxis?: ValueAxis;
112
+ }
113
+
114
+ export interface ScatterChartModel extends ChartCommon {
115
+ kind: "scatter";
116
+ style: "line" | "lineMarker" | "marker" | "smooth" | "smoothMarker";
117
+ series: ScatterSeries[];
118
+ /** Scatter uses a numeric x-axis, not a category axis. */
119
+ xAxis: ValueAxis;
120
+ yAxis: ValueAxis;
121
+ }
122
+
123
+ export interface BubbleChartModel extends ChartCommon {
124
+ kind: "bubble";
125
+ bubble3D: boolean;
126
+ series: BubbleSeries[];
127
+ xAxis: ValueAxis;
128
+ yAxis: ValueAxis;
129
+ }
130
+
131
+ export interface ComboChartModel extends ChartCommon {
132
+ kind: "combo";
133
+ /**
134
+ * Each type-group (bar/line/area) contributes its own series list. Axes
135
+ * may be shared or split into primary/secondary pairs.
136
+ */
137
+ groups: Array<BarChartModel | LineChartModel | AreaChartModel>;
138
+ /** True if any group targeted a secondary value axis. */
139
+ hasSecondaryAxis: boolean;
140
+ }
141
+
142
+ export interface UnsupportedChartModel extends ChartCommon {
143
+ kind: "unsupported";
144
+ reason: UnsupportedReason;
145
+ /** Human-readable detail (e.g. "Chart family lineChart not yet implemented"). */
146
+ detail: string;
147
+ }
148
+
149
+ /**
150
+ * Discriminator values for `UnsupportedChartModel.reason`.
151
+ *
152
+ * - `not-yet-implemented`: the parser recognises the chart family but a
153
+ * subsequent slice is responsible for implementing it (line/pie/area/
154
+ * scatter/bubble/combo in this slice).
155
+ * - `pivot`/`stock`/`surface`/…: families that are intentionally deferred
156
+ * indefinitely per the plan's non-goals (rare in legal/finance corpora,
157
+ * disproportionate renderer cost).
158
+ * - `no-plot-area`: the chart XML had no c:plotArea child.
159
+ * - `parse-error`: unexpected structure or exception during parse.
160
+ */
161
+ export type UnsupportedReason =
162
+ | "not-yet-implemented"
163
+ | "pivot"
164
+ | "stock"
165
+ | "surface"
166
+ | "treemap"
167
+ | "sunburst"
168
+ | "histogram"
169
+ | "waterfall"
170
+ | "funnel"
171
+ | "map"
172
+ | "no-plot-area"
173
+ | "parse-error";
174
+
175
+ // ---------------------------------------------------------------------------
176
+ // Series
177
+ // ---------------------------------------------------------------------------
178
+
179
+ export interface SeriesBase {
180
+ /** c:idx — render order. */
181
+ idx: number;
182
+ /** c:order — data order. */
183
+ order: number;
184
+ /** Series display name (c:tx/c:strRef/c:strCache or c:v). */
185
+ name?: string;
186
+ /** Explicit fill/stroke overrides from c:spPr. */
187
+ spPr?: ShapeProperties;
188
+ }
189
+
190
+ export interface Series extends SeriesBase {
191
+ /** Category labels; string-form even when c:numRef was used. */
192
+ categories: string[];
193
+ /** Numeric values; `null` for blank/missing points (sparse cache). */
194
+ values: Array<number | null>;
195
+ dataLabels?: DataLabelsSpec;
196
+ /** Per-data-point overrides (c:dPt). */
197
+ dataPoints?: DataPointOverride[];
198
+ }
199
+
200
+ export interface LineSeries extends Series {
201
+ /** Per-series smooth override. */
202
+ smooth?: boolean;
203
+ marker?: MarkerSpec;
204
+ }
205
+
206
+ export interface PieSeries extends SeriesBase {
207
+ values: Array<number | null>;
208
+ /** Per-series default explosion percent. */
209
+ explosion?: number;
210
+ dataPoints?: DataPointOverride[];
211
+ }
212
+
213
+ export interface ScatterSeries extends SeriesBase {
214
+ xValues: Array<number | null>;
215
+ yValues: Array<number | null>;
216
+ smooth?: boolean;
217
+ marker?: MarkerSpec;
218
+ }
219
+
220
+ export interface BubbleSeries extends SeriesBase {
221
+ xValues: Array<number | null>;
222
+ yValues: Array<number | null>;
223
+ sizes: Array<number | null>;
224
+ bubbleScale?: number;
225
+ }
226
+
227
+ // ---------------------------------------------------------------------------
228
+ // Axes
229
+ // ---------------------------------------------------------------------------
230
+
231
+ export type Axis = CategoryAxis | ValueAxis | DateAxis | SeriesAxis;
232
+
233
+ export interface AxisBase {
234
+ /** c:axId — unique within the plot area. */
235
+ id: string;
236
+ /** c:crossAx — the id of the perpendicular axis this one crosses. */
237
+ crossAxisId?: string;
238
+ /** Value at which the perpendicular axis crosses this one (c:crossesAt). */
239
+ crossesAt?: number | "autoZero" | "max" | "min";
240
+ /** c:axPos: b = bottom, t = top, l = left, r = right. */
241
+ position: "b" | "t" | "l" | "r";
242
+ /**
243
+ * Whether the axis is visible. Note: c:delete has inverted semantics —
244
+ * `val="1"` means invisible; we store the normal-sense boolean here.
245
+ */
246
+ visible: boolean;
247
+ title?: Title;
248
+ majorGridlines?: boolean;
249
+ minorGridlines?: boolean;
250
+ majorUnit?: number;
251
+ minorUnit?: number;
252
+ /** c:numFmt/@formatCode. */
253
+ numberFormat?: string;
254
+ }
255
+
256
+ export interface CategoryAxis extends AxisBase {
257
+ kind: "category";
258
+ /** c:auto — whether category width is chosen automatically. */
259
+ auto: boolean;
260
+ labelAlign?: "ctr" | "l" | "r";
261
+ labelOffset?: number;
262
+ tickMark?: "none" | "in" | "out" | "cross";
263
+ tickLabelSkip?: number;
264
+ tickMarkSkip?: number;
265
+ /** Category labels resolved from the first series' c:cat cache. */
266
+ categoryLabels: string[];
267
+ }
268
+
269
+ export interface ValueAxis extends AxisBase {
270
+ kind: "value";
271
+ min?: number;
272
+ max?: number;
273
+ logBase?: number;
274
+ /** True when c:scaling/c:orientation val="maxMin". */
275
+ reverse: boolean;
276
+ crossBetween?: "between" | "midCat";
277
+ }
278
+
279
+ export interface DateAxis extends AxisBase {
280
+ kind: "date";
281
+ baseTimeUnit?: "days" | "months" | "years";
282
+ /** Serial date (days since 1899-12-30). */
283
+ min?: number;
284
+ max?: number;
285
+ majorTimeUnit?: "days" | "months" | "years";
286
+ minorTimeUnit?: "days" | "months" | "years";
287
+ }
288
+
289
+ export interface SeriesAxis extends AxisBase {
290
+ kind: "series";
291
+ }
292
+
293
+ // ---------------------------------------------------------------------------
294
+ // Other chart elements
295
+ // ---------------------------------------------------------------------------
296
+
297
+ export interface Title {
298
+ /**
299
+ * Plain-text rendering of the title. Rich-text formatting is not preserved
300
+ * at Stage 1; Stage 2/3 may extend this with per-run styling.
301
+ */
302
+ text?: string;
303
+ overlay: boolean;
304
+ spPr?: ShapeProperties;
305
+ txPr?: TextProperties;
306
+ }
307
+
308
+ export interface Legend {
309
+ position: "b" | "t" | "l" | "r" | "tr";
310
+ overlay: boolean;
311
+ spPr?: ShapeProperties;
312
+ txPr?: TextProperties;
313
+ }
314
+
315
+ export interface DataLabelsSpec {
316
+ showVal: boolean;
317
+ showCatName: boolean;
318
+ showSerName: boolean;
319
+ showPercent: boolean;
320
+ showBubbleSize: boolean;
321
+ showLegendKey: boolean;
322
+ position?: "b" | "ctr" | "l" | "r" | "t" | "bestFit" | "inBase" | "inEnd" | "outEnd";
323
+ separator?: string;
324
+ numberFormat?: string;
325
+ txPr?: TextProperties;
326
+ }
327
+
328
+ export interface MarkerSpec {
329
+ symbol:
330
+ | "circle"
331
+ | "square"
332
+ | "diamond"
333
+ | "triangle"
334
+ | "x"
335
+ | "star"
336
+ | "dot"
337
+ | "dash"
338
+ | "plus"
339
+ | "picture"
340
+ | "none"
341
+ | "auto";
342
+ /** Point size 2..72. */
343
+ size?: number;
344
+ spPr?: ShapeProperties;
345
+ }
346
+
347
+ export interface DataPointOverride {
348
+ /** Index of the data point within the series. */
349
+ idx: number;
350
+ spPr?: ShapeProperties;
351
+ marker?: MarkerSpec;
352
+ /** Pie only: per-slice explosion percent. */
353
+ explosion?: number;
354
+ /** Bar only: render negative values with inverted fill. */
355
+ invertIfNegative?: boolean;
356
+ /** Bubble only: 3D bubble flag (rendered as 2D regardless). */
357
+ bubble3D?: boolean;
358
+ }
359
+
360
+ // ---------------------------------------------------------------------------
361
+ // Shape / fill / stroke / color primitives
362
+ // ---------------------------------------------------------------------------
363
+
364
+ export interface ShapeProperties {
365
+ fill?: FillSpec;
366
+ stroke?: StrokeSpec;
367
+ }
368
+
369
+ export type FillSpec =
370
+ | { kind: "solid"; color: ColorRef }
371
+ | { kind: "gradient"; stops: Array<{ pos: number; color: ColorRef }>; angle?: number }
372
+ | { kind: "none" }
373
+ | { kind: "auto" };
374
+
375
+ export interface StrokeSpec {
376
+ color?: ColorRef;
377
+ /** Stroke width in EMU (English Metric Units). */
378
+ widthEmu?: number;
379
+ dash?: "solid" | "dash" | "dashDot" | "lgDash" | "lgDashDot" | "sysDash" | "sysDashDot";
380
+ noFill?: boolean;
381
+ }
382
+
383
+ /**
384
+ * An unresolved color reference. Stage 2 adds `resolveColor(ref, theme)`
385
+ * which turns this into a concrete sRGB string via the chart-color cascade
386
+ * (explicit sRGB → scheme color → theme color scheme).
387
+ */
388
+ export type ColorRef =
389
+ | { kind: "srgb"; value: string } // "#RRGGBB"
390
+ | { kind: "scheme"; value: string; mods?: ColorMod[] }
391
+ | { kind: "themeLinked"; value: string; mods?: ColorMod[] };
392
+
393
+ export interface ColorMod {
394
+ kind: "lumMod" | "lumOff" | "shade" | "tint" | "satMod" | "hueMod" | "alpha";
395
+ /** Parts per 100,000 (OOXML convention). */
396
+ value: number;
397
+ }
398
+
399
+ export interface TextProperties {
400
+ fontFamily?: string;
401
+ fontSizePt?: number;
402
+ bold?: boolean;
403
+ italic?: boolean;
404
+ color?: ColorRef;
405
+ }
406
+
407
+ // ---------------------------------------------------------------------------
408
+ // Exhaustiveness canary (compile-time only)
409
+ // ---------------------------------------------------------------------------
410
+
411
+ /**
412
+ * Compile-time assertion that `ChartModel["kind"]` equals the expected
413
+ * literal-union below. If a new variant is added to `ChartModel` without
414
+ * being listed in `_ExpectedKinds`, `_Equals` resolves to `false` and the
415
+ * subsequent `true` assignment fails at compile time. The previous
416
+ * dual-assign-cast pattern did not actually enforce union equality — any
417
+ * new kind silently passed through.
418
+ *
419
+ * Reference: the standard "exact type equality" trick using contravariance
420
+ * of conditional-type generic functions.
421
+ */
422
+ type _Equals<X, Y> =
423
+ (<T>() => T extends X ? 1 : 2) extends (<T>() => T extends Y ? 1 : 2)
424
+ ? true
425
+ : false;
426
+
427
+ type _ExpectedKinds =
428
+ | "bar"
429
+ | "line"
430
+ | "pie"
431
+ | "area"
432
+ | "scatter"
433
+ | "bubble"
434
+ | "combo"
435
+ | "unsupported";
436
+
437
+ type _ChartKindCheck = _Equals<ChartModel["kind"], _ExpectedKinds>;
438
+ const _kindExhaustive: _ChartKindCheck = true;
439
+ void _kindExhaustive;
@@ -30,7 +30,8 @@ export type EditorWarningCode =
30
30
  | "revision_anchor_detached"
31
31
  | "large_document_degraded"
32
32
  | "font_substitution"
33
- | "image_missing";
33
+ | "image_missing"
34
+ | "review_target_not_found";
34
35
 
35
36
  export interface EditorWarning {
36
37
  warningId: string;
@@ -237,6 +238,7 @@ const EDITOR_WARNING_CODES = new Set<EditorWarningCode>([
237
238
  "large_document_degraded",
238
239
  "font_substitution",
239
240
  "image_missing",
241
+ "review_target_not_found",
240
242
  ]);
241
243
  const EDITOR_WARNING_SEVERITIES = new Set<EditorWarning["severity"]>(["info", "warning"]);
242
244
  const EDITOR_WARNING_SOURCES = new Set<EditorWarning["source"]>([
@@ -7,7 +7,10 @@ import {
7
7
  rangeStaysWithinCommentableStory,
8
8
  type ReviewAnchor,
9
9
  } from "../../core/selection/review-anchors.ts";
10
- import type { TransactionMapping } from "../../core/selection/mapping.ts";
10
+ import {
11
+ anchorUnaffectedByMapping,
12
+ type TransactionMapping,
13
+ } from "../../core/selection/mapping.ts";
11
14
 
12
15
  export interface RemapCommentThreadsOptions {
13
16
  comments: Record<string, CommentThreadRecord>;
@@ -25,12 +28,19 @@ export interface RemapCommentThreadsResult {
25
28
  export function remapCommentThreads(
26
29
  options: RemapCommentThreadsOptions,
27
30
  ): RemapCommentThreadsResult {
28
- const comments = Object.fromEntries(
29
- Object.entries(options.comments).map(([commentId, comment]) => [
30
- commentId,
31
- remapCommentThread(comment, options.mapping, options.nextContent),
32
- ]),
33
- );
31
+ // Only rebuild the record when at least one comment anchor actually changed.
32
+ const sourceComments = options.comments;
33
+ let comments = sourceComments;
34
+ let anyChanged = false;
35
+ const remappedEntries = Object.entries(sourceComments).map(([commentId, comment]) => {
36
+ const next = remapCommentThread(comment, options.mapping, options.nextContent);
37
+ if (next !== comment) anyChanged = true;
38
+ return [commentId, next] as const;
39
+ });
40
+ if (anyChanged) {
41
+ comments = Object.fromEntries(remappedEntries);
42
+ }
43
+
34
44
  const detachedCommentIds = Object.values(comments)
35
45
  .filter((comment) => comment.anchor.kind === "detached")
36
46
  .map((comment) => comment.commentId);
@@ -50,14 +60,17 @@ export function remapCommentThread(
50
60
  if (comment.anchor.kind === "detached") {
51
61
  return comment;
52
62
  }
63
+ // Skip the full remap + rangeStaysWithinCommentableStory walk when no step
64
+ // reaches this anchor's range — positions are provably unchanged.
65
+ if (anchorUnaffectedByMapping(comment.anchor, mapping)) {
66
+ return comment;
67
+ }
53
68
 
54
69
  const mappedAnchor = mapReviewAnchor(comment.anchor, mapping);
55
70
  const anchor = normalizeCommentAnchor(comment.anchor, mappedAnchor, mapping, nextContent);
56
71
 
57
- return {
58
- ...comment,
59
- anchor,
60
- };
72
+ if (anchor === comment.anchor) return comment;
73
+ return { ...comment, anchor };
61
74
  }
62
75
 
63
76
  function normalizeCommentAnchor(