@beyondwork/docx-react-component 1.0.66 → 1.0.69
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/README.md +75 -931
- package/package.json +26 -27
- package/src/api/anchor-conversion.ts +43 -0
- package/src/api/editor-state-types.ts +2 -1
- package/src/api/public-types.ts +504 -101
- package/src/api/session-state.ts +4 -0
- package/src/api/v3/README.md +91 -0
- package/src/api/v3/_create.ts +146 -0
- package/src/api/v3/_layer-metadata.ts +362 -0
- package/src/api/v3/_mocks.ts +84 -0
- package/src/api/v3/_runtime-handle.ts +162 -0
- package/src/api/v3/_ux-response.ts +73 -0
- package/src/api/v3/ai/_metadata-audit.ts +225 -0
- package/src/api/v3/ai/attach.ts +235 -0
- package/src/api/v3/ai/bundle.ts +132 -0
- package/src/api/v3/ai/explain.ts +144 -0
- package/src/api/v3/ai/export.ts +54 -0
- package/src/api/v3/ai/inspect.ts +118 -0
- package/src/api/v3/ai/policy.ts +77 -0
- package/src/api/v3/ai/replacement.ts +341 -0
- package/src/api/v3/ai/resolve.ts +133 -0
- package/src/api/v3/index.ts +79 -0
- package/src/api/v3/runtime/chart.ts +310 -0
- package/src/api/v3/runtime/clipboard.ts +81 -0
- package/src/api/v3/runtime/collab.ts +331 -0
- package/src/api/v3/runtime/content.ts +236 -0
- package/src/api/v3/runtime/document.ts +282 -0
- package/src/api/v3/runtime/formatting.ts +186 -0
- package/src/api/v3/runtime/geometry.ts +349 -0
- package/src/api/v3/runtime/layout.ts +108 -0
- package/src/api/v3/runtime/review.ts +129 -0
- package/src/api/v3/runtime/search.ts +74 -0
- package/src/api/v3/runtime/table.ts +63 -0
- package/src/api/v3/runtime/workflow.ts +434 -0
- package/src/api/v3/ui/_context.ts +86 -0
- package/src/api/v3/ui/_create.ts +65 -0
- package/src/api/v3/ui/_types.ts +520 -0
- package/src/api/v3/ui/chrome-composition.ts +342 -0
- package/src/{ui-tailwind/chrome → api/v3/ui}/chrome-preset-model.ts +11 -1
- package/src/api/v3/ui/chrome.ts +476 -0
- package/src/api/v3/ui/debug.ts +124 -0
- package/src/api/v3/ui/index.ts +64 -0
- package/src/api/v3/ui/overlays-visibility.ts +170 -0
- package/src/api/v3/ui/overlays.ts +427 -0
- package/src/api/v3/ui/scope.ts +71 -0
- package/src/api/v3/ui/session.ts +100 -0
- package/src/api/v3/ui/surface.ts +170 -0
- package/src/api/v3/ui/viewport.ts +303 -0
- package/src/core/commands/index.ts +28 -6
- package/src/core/commands/list-commands.ts +3 -2
- package/src/core/commands/section-layout-commands.ts +9 -8
- package/src/core/schema/text-schema.ts +16 -0
- package/src/core/selection/mapping.ts +33 -72
- package/src/core/state/editor-state.ts +96 -189
- package/src/index.ts +23 -4
- package/src/io/chart-preview-resolver.ts +1 -1
- package/src/io/docx-session.ts +36 -4795
- package/src/io/export/build-app-properties-xml.ts +1 -1
- package/src/io/export/serialize-comments.ts +1 -1
- package/src/io/export/serialize-headers-footers.ts +6 -1
- package/src/io/export/serialize-main-document.ts +45 -0
- package/src/io/export/serialize-run-formatting.ts +17 -2
- package/src/io/export/twip.ts +1 -1
- package/src/io/normalize/normalize-text.ts +27 -20
- package/src/io/ooxml/chart/parse-series.ts +1 -1
- package/src/io/ooxml/chart/resolve-color.ts +2 -2
- package/src/io/ooxml/chart/types.ts +1 -1
- package/src/io/ooxml/classify-embedding.ts +83 -33
- package/src/io/ooxml/parse-fill.ts +1 -1
- package/src/io/ooxml/parse-main-document.ts +71 -1
- package/src/io/ooxml/parse-object.ts +14 -10
- package/src/io/ooxml/parse-run-formatting.ts +47 -1
- package/src/io/ooxml/property-grab-bag.ts +2 -2
- package/src/io/ooxml/units.ts +11 -0
- package/src/io/ooxml/workflow-payload.ts +282 -7
- package/src/model/anchor.ts +85 -0
- package/src/model/canonical-document.ts +351 -15
- package/src/model/chart-types.ts +1 -1
- package/src/model/layout/index.ts +83 -0
- package/src/model/layout/page-graph-types.ts +181 -0
- package/src/model/layout/page-layout-snapshot.ts +105 -0
- package/src/model/layout/resolved-layout-types.ts +47 -0
- package/src/model/layout/runtime-page-graph-types.ts +102 -0
- package/src/model/paragraph-scope-ids.ts +72 -0
- package/src/model/review/comment-types.ts +112 -0
- package/src/model/review/index.ts +2 -0
- package/src/model/review/revision-types.ts +215 -0
- package/src/model/snapshot.ts +32 -0
- package/src/review/store/comment-store.ts +21 -47
- package/src/review/store/revision-types.ts +40 -198
- package/src/runtime/collab/base-doc-fingerprint.ts +6 -1
- package/src/runtime/collab/runtime-collab-sync.ts +13 -3
- package/src/runtime/collab-session.ts +1 -1
- package/src/runtime/debug/build-debug-inspector-snapshot.ts +686 -0
- package/src/runtime/debug/event-ring-buffer.ts +64 -0
- package/src/runtime/debug/probability-sampler.ts +18 -0
- package/src/runtime/debug/runtime-debug-facet.ts +67 -0
- package/src/runtime/debug/stage-tokens.ts +31 -0
- package/src/runtime/debug/telemetry-bus.ts +271 -0
- package/src/runtime/debug/types.ts +275 -0
- package/src/runtime/debug/wrap-ref-for-telemetry.ts +118 -0
- package/src/runtime/document-layout.ts +8 -6
- package/src/runtime/document-runtime.ts +843 -1141
- package/src/runtime/document-search.ts +1 -1
- package/src/runtime/edit-ops/index.ts +1 -1
- package/src/runtime/external-send-runtime.ts +1 -1
- package/src/runtime/formatting/document-lookup.ts +235 -0
- package/src/runtime/formatting/field/registry.ts +41 -0
- package/src/runtime/{field-resolver.ts → formatting/field/resolver.ts} +27 -2
- package/src/runtime/formatting/font-resolution.ts +83 -0
- package/src/runtime/formatting/formatting-context.ts +903 -0
- package/src/runtime/formatting/formatting-types.ts +157 -0
- package/src/runtime/{hyperlink-color-resolver.ts → formatting/hyperlink-color.ts} +2 -2
- package/src/runtime/formatting/index.ts +125 -0
- package/src/runtime/{resolved-numbering-geometry.ts → formatting/numbering/geometry.ts} +1 -1
- package/src/runtime/{numbering-prefix.ts → formatting/numbering/prefix.ts} +170 -3
- package/src/runtime/formatting/paragraph-style-resolver.ts +92 -0
- package/src/runtime/formatting/projector.ts +75 -0
- package/src/runtime/formatting/resolve-effective.ts +407 -0
- package/src/runtime/formatting/revision-display.ts +105 -0
- package/src/runtime/{paragraph-style-resolver.ts → formatting/style-cascade.ts} +84 -141
- package/src/runtime/{table-style-resolver.ts → formatting/table-style-resolver.ts} +1 -1
- package/src/runtime/formatting/telemetry-bridge.ts +106 -0
- package/src/runtime/{theme-color-resolver.ts → formatting/theme-color.ts} +2 -30
- package/src/runtime/geometry/caret-geometry.ts +164 -0
- package/src/runtime/geometry/geometry-facet.ts +364 -0
- package/src/runtime/geometry/geometry-types.ts +256 -0
- package/src/runtime/geometry/hit-test.ts +125 -0
- package/src/runtime/geometry/index.ts +71 -0
- package/src/runtime/geometry/inert-geometry-facet.ts +43 -0
- package/src/runtime/geometry/invalidation.ts +35 -0
- package/src/runtime/geometry/object-handles.ts +77 -0
- package/src/runtime/geometry/overlay-rects.ts +85 -0
- package/src/runtime/geometry/project-anchors.ts +100 -0
- package/src/runtime/geometry/project-fragments.ts +216 -0
- package/src/runtime/geometry/projector.ts +129 -0
- package/src/runtime/geometry/replacement-envelope.ts +130 -0
- package/src/runtime/geometry/viewport.ts +218 -0
- package/src/runtime/layout/compat-input-ledger.ts +211 -0
- package/src/runtime/layout/index.ts +6 -1
- package/src/runtime/layout/inert-layout-facet.ts +12 -7
- package/src/runtime/layout/layout-engine-instance.ts +189 -11
- package/src/runtime/layout/layout-engine-version.ts +450 -1
- package/src/runtime/layout/layout-facet-types.ts +60 -0
- package/src/runtime/layout/layout-measurement-provider.ts +13 -0
- package/src/runtime/layout/measurement-backend-canvas.ts +14 -2
- package/src/runtime/layout/measurement-backend-empirical.ts +23 -4
- package/src/runtime/layout/page-graph.ts +62 -209
- package/src/runtime/layout/page-story-resolver.ts +7 -12
- package/src/runtime/layout/paginated-layout-engine.ts +186 -11
- package/src/runtime/layout/project-block-fragments.ts +11 -0
- package/src/runtime/layout/projector.ts +90 -0
- package/src/runtime/layout/public-facet.ts +187 -442
- package/src/runtime/layout/resolved-formatting-state.ts +158 -26
- package/src/runtime/layout/table-render-plan.ts +1 -1
- package/src/runtime/prerender/cache-envelope.ts +6 -1
- package/src/runtime/prerender/prerender-document.ts +18 -23
- package/src/runtime/render/decoration-resolver.ts +1 -1
- package/src/runtime/render/render-frame-types.ts +20 -0
- package/src/runtime/render/render-kernel.ts +94 -25
- package/src/runtime/scopes/_formatting-seam.ts +262 -0
- package/src/runtime/scopes/_scope-dependencies.ts +49 -0
- package/src/runtime/scopes/action-validation.ts +356 -0
- package/src/runtime/scopes/attach-explanation.ts +102 -0
- package/src/runtime/scopes/audit-bundle.ts +71 -0
- package/src/runtime/scopes/compile-scope-bundle.ts +163 -0
- package/src/runtime/scopes/compile-scope.ts +262 -0
- package/src/runtime/scopes/compiler-service.ts +431 -0
- package/src/runtime/scopes/create-issue.ts +107 -0
- package/src/runtime/scopes/enumerate-scopes.ts +543 -0
- package/src/runtime/scopes/evidence.ts +233 -0
- package/src/runtime/scopes/index.ts +150 -0
- package/src/runtime/scopes/position-map.ts +214 -0
- package/src/runtime/scopes/preservation-boundary.ts +91 -0
- package/src/runtime/scopes/projector.ts +49 -0
- package/src/runtime/scopes/replaceability.ts +87 -0
- package/src/runtime/scopes/replacement/apply.ts +228 -0
- package/src/runtime/scopes/replacement/compile.ts +59 -0
- package/src/runtime/scopes/replacement/propose.ts +42 -0
- package/src/runtime/scopes/resolve-reference.ts +347 -0
- package/src/runtime/scopes/review-bundle.ts +141 -0
- package/src/runtime/scopes/scope-kinds/_paragraph-text.ts +57 -0
- package/src/runtime/scopes/scope-kinds/_table-text.ts +42 -0
- package/src/runtime/scopes/scope-kinds/comment-thread.ts +59 -0
- package/src/runtime/scopes/scope-kinds/field.ts +65 -0
- package/src/runtime/scopes/scope-kinds/heading.ts +84 -0
- package/src/runtime/scopes/scope-kinds/list-item.ts +77 -0
- package/src/runtime/scopes/scope-kinds/paragraph.ts +182 -0
- package/src/runtime/scopes/scope-kinds/revision.ts +62 -0
- package/src/runtime/scopes/scope-kinds/table-cell.ts +57 -0
- package/src/runtime/scopes/scope-kinds/table-row.ts +61 -0
- package/src/runtime/scopes/scope-kinds/table.ts +55 -0
- package/src/runtime/scopes/scope-range.ts +208 -0
- package/src/runtime/scopes/semantic-scope-types.ts +454 -0
- package/src/runtime/scopes/workflow-overlap.ts +92 -0
- package/src/runtime/selection/index.ts +1 -1
- package/src/runtime/structure-ops/fragment-insert.ts +1 -1
- package/src/runtime/structure-ops/index.ts +1 -1
- package/src/runtime/surface-projection.ts +232 -262
- package/src/runtime/units.ts +4 -2
- package/src/runtime/workflow/coordinator.ts +1348 -0
- package/src/runtime/workflow/derived-scope-resolver.ts +125 -0
- package/src/runtime/workflow/index.ts +25 -0
- package/src/runtime/workflow/markup-mode-policy.ts +98 -0
- package/src/runtime/{workflow-markup.ts → workflow/markup.ts} +6 -6
- package/src/runtime/workflow/metadata-persistence.ts +306 -0
- package/src/runtime/workflow/metadata-writer.ts +123 -0
- package/src/runtime/workflow/overlay-store.ts +690 -0
- package/src/runtime/workflow/projector.ts +127 -0
- package/src/runtime/{query-scopes.ts → workflow/query-scopes.ts} +3 -3
- package/src/runtime/{workflow-rail-segments.ts → workflow/rail/compose.ts} +60 -165
- package/src/runtime/workflow/rail/types.ts +198 -0
- package/src/runtime/workflow/scope-rail-composer.ts +39 -0
- package/src/runtime/{scope-resolver.ts → workflow/scope-resolver.ts} +3 -3
- package/src/runtime/workflow/scope-writer.ts +188 -0
- package/src/runtime/{tamper-gate.ts → workflow/tamper-gate.ts} +1 -1
- package/src/runtime/workflow/visibility-policy.ts +129 -0
- package/src/session/_sync-legacy.ts +66 -0
- package/src/session/export/embedded-reconstitute.ts +104 -0
- package/src/session/export/export-diagnostics.ts +85 -0
- package/src/session/export/export-validation.ts +110 -0
- package/src/session/export/index.ts +34 -0
- package/src/session/export/preservation-reattach.ts +30 -0
- package/src/session/export/serialize-dispatch.ts +165 -0
- package/src/session/export/stateful-export-pipeline.ts +432 -0
- package/src/session/export/stateful-export.ts +684 -0
- package/src/session/import/canonical-assembly.ts +227 -0
- package/src/session/import/diagnostics-session.ts +54 -0
- package/src/session/import/embedded-discovery.ts +225 -0
- package/src/session/import/embedded-offload.ts +337 -0
- package/src/session/import/import-diagnostics.ts +69 -0
- package/src/session/import/loader-types.ts +313 -0
- package/src/session/import/loader.ts +1834 -0
- package/src/session/import/normalize.ts +195 -0
- package/src/session/import/package-parts.ts +217 -0
- package/src/session/import/package-read.ts +195 -0
- package/src/session/import/parse-orchestration.ts +105 -0
- package/src/session/import/part-constants.ts +70 -0
- package/src/session/import/part-discovery.ts +94 -0
- package/src/session/import/preservation-index.ts +46 -0
- package/src/{runtime/read-only-diagnostics-runtime.ts → session/import/read-only-diagnostics.ts} +24 -3
- package/src/session/import/review-import.ts +508 -0
- package/src/session/import/styles-consolidation.ts +281 -0
- package/src/session/import/workflow-scope-import.ts +256 -0
- package/src/session/index.ts +37 -0
- package/src/session/session-state.ts +69 -0
- package/src/session/session.ts +532 -0
- package/src/session/shared/protection.ts +228 -0
- package/src/session/shared/session-utils.ts +82 -0
- package/src/session/types.ts +499 -0
- package/src/shell/chart-snapshots.ts +96 -0
- package/src/shell/media-previews.ts +85 -0
- package/src/shell/overlay-anchor-bridge.ts +53 -0
- package/src/shell/paste-adapter.ts +23 -0
- package/src/shell/ref-commands.ts +1697 -0
- package/src/shell/ref-utilities.ts +48 -0
- package/src/shell/search.ts +51 -0
- package/src/{ui/editor-runtime-boundary.ts → shell/session-bootstrap.ts} +243 -67
- package/src/shell/ui-subscriber-channels.ts +81 -0
- package/src/shell/use-collab-sync.ts +116 -0
- package/src/ui/WordReviewEditor.tsx +496 -2051
- package/src/ui/editor-shell-view.tsx +30 -1
- package/src/ui/editor-surface-controller.tsx +49 -1
- package/src/ui/headless/revision-decoration-model.ts +83 -0
- package/src/{ui-tailwind/chrome → ui/headless}/role-action-sets.ts +1 -1
- package/src/ui/headless/scoped-chrome-policy.ts +2 -2
- package/src/ui/headless/selection-tool-context.ts +1 -1
- package/src/ui/headless/selection-tool-resolver.ts +1 -1
- package/src/ui/runtime-shortcut-dispatch.ts +46 -1
- package/src/ui/ui-controller-factory.ts +221 -0
- package/src/ui-tailwind/chart/ChartSurface.tsx +2 -2
- package/src/ui-tailwind/chart/layout/legend-layout.ts +1 -1
- package/src/ui-tailwind/chart/layout/plot-area.ts +2 -2
- package/src/ui-tailwind/chart/layout/title-layout.ts +1 -1
- package/src/ui-tailwind/chart/render/area.tsx +3 -3
- package/src/ui-tailwind/chart/render/bar-column.tsx +3 -3
- package/src/ui-tailwind/chart/render/bubble.tsx +3 -3
- package/src/ui-tailwind/chart/render/combo.tsx +2 -2
- package/src/ui-tailwind/chart/render/data-labels.tsx +2 -2
- package/src/ui-tailwind/chart/render/font-metrics.ts +2 -2
- package/src/ui-tailwind/chart/render/line.tsx +3 -3
- package/src/ui-tailwind/chart/render/pie.tsx +6 -6
- package/src/ui-tailwind/chart/render/scatter.tsx +3 -3
- package/src/ui-tailwind/chart/render/svg-primitives.ts +3 -3
- package/src/ui-tailwind/chart/render/unsupported.tsx +2 -2
- package/src/ui-tailwind/chrome/build-context-menu-entries.ts +88 -0
- package/src/ui-tailwind/chrome/chrome-preset-toolbar.tsx +1 -1
- package/src/ui-tailwind/chrome/collab-send-to-supplier-button.tsx +1 -1
- package/src/ui-tailwind/chrome/collab-tamper-banner.tsx +1 -1
- package/src/ui-tailwind/chrome/collab-top-nav-container.tsx +1 -1
- package/src/ui-tailwind/chrome/editor-action-registry.ts +553 -0
- package/src/ui-tailwind/chrome/editor-actions-to-palette.ts +182 -0
- package/src/ui-tailwind/chrome/local-surface-arbiter.ts +534 -0
- package/src/ui-tailwind/chrome/resolve-target-kind.ts +226 -0
- package/src/ui-tailwind/chrome/tw-alert-banner.tsx +38 -4
- package/src/ui-tailwind/chrome/tw-context-band.tsx +125 -0
- package/src/ui-tailwind/chrome/tw-context-menu-portal.tsx +248 -0
- package/src/ui-tailwind/chrome/tw-image-context-toolbar.tsx +42 -1
- package/src/ui-tailwind/chrome/tw-selection-anchor-resolver.ts +8 -7
- package/src/ui-tailwind/chrome/tw-selection-tool-blocked.tsx +38 -4
- package/src/ui-tailwind/chrome/tw-selection-tool-comment.tsx +104 -6
- package/src/ui-tailwind/chrome/tw-selection-tool-host.tsx +66 -7
- package/src/ui-tailwind/chrome/tw-selection-tool-workflow.tsx +54 -8
- package/src/ui-tailwind/chrome/tw-shortcut-hint.tsx +7 -1
- package/src/ui-tailwind/chrome/tw-suggestion-card.tsx +33 -0
- package/src/ui-tailwind/chrome/tw-table-context-toolbar.tsx +78 -1
- package/src/ui-tailwind/chrome/tw-table-grip-layer.tsx +16 -8
- package/src/ui-tailwind/chrome/tw-workspace-chrome-host.tsx +276 -0
- package/src/ui-tailwind/chrome/use-context-menu-controller.ts +201 -0
- package/src/ui-tailwind/chrome-overlay/chrome-overlay-projector.ts +1 -1
- package/src/ui-tailwind/chrome-overlay/tw-chrome-overlay.tsx +22 -4
- package/src/ui-tailwind/chrome-overlay/tw-comment-balloon-layer.tsx +1 -1
- package/src/ui-tailwind/chrome-overlay/tw-locked-block-layer.tsx +1 -1
- package/src/ui-tailwind/chrome-overlay/tw-object-selection-overlay.tsx +11 -5
- package/src/ui-tailwind/chrome-overlay/tw-page-stack-overlay-layer.tsx +197 -3
- package/src/ui-tailwind/chrome-overlay/tw-revision-margin-bar-layer.tsx +1 -1
- package/src/ui-tailwind/chrome-overlay/tw-scope-card-layer.tsx +35 -6
- package/src/ui-tailwind/chrome-overlay/tw-scope-rail-layer.tsx +24 -16
- package/src/ui-tailwind/chrome-overlay/tw-table-continuation-header.tsx +1 -1
- package/src/ui-tailwind/debug/README.md +57 -0
- package/src/ui-tailwind/debug/index.ts +3 -0
- package/src/ui-tailwind/debug/tw-debug-overlay.tsx +186 -0
- package/src/ui-tailwind/debug/tw-debug-presentation.tsx +80 -0
- package/src/ui-tailwind/debug/tw-debug-top-bar.tsx +83 -0
- package/src/ui-tailwind/editor-surface/chart-node-view.tsx +2 -2
- package/src/ui-tailwind/editor-surface/float-wrap-resolver.ts +1 -1
- package/src/ui-tailwind/editor-surface/pm-command-bridge.ts +135 -10
- package/src/ui-tailwind/editor-surface/pm-decorations.ts +40 -13
- package/src/ui-tailwind/editor-surface/pm-page-break-decorations.ts +1 -1
- package/src/ui-tailwind/editor-surface/pm-schema.ts +1 -1
- package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +3 -3
- package/src/ui-tailwind/editor-surface/predicted-tag-preflight.ts +1 -1
- package/src/ui-tailwind/editor-surface/remote-cursor-plugin.ts +2 -2
- package/src/ui-tailwind/editor-surface/scroll-anchor.ts +91 -9
- package/src/ui-tailwind/editor-surface/shape-renderer.ts +1 -1
- package/src/ui-tailwind/editor-surface/surface-layer.ts +1 -1
- package/src/ui-tailwind/editor-surface/tw-opaque-block.tsx +1 -1
- package/src/ui-tailwind/editor-surface/tw-page-block-view.helpers.ts +23 -6
- package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +132 -22
- package/src/ui-tailwind/editor-surface/tw-table-node-view.tsx +1 -1
- package/src/ui-tailwind/index.ts +0 -5
- package/src/ui-tailwind/overlay-anchor-bridge-context.tsx +33 -0
- package/src/ui-tailwind/page-stack/floating-image-overlay-model.ts +66 -29
- package/src/ui-tailwind/page-stack/tw-floating-image-layer.tsx +25 -2
- package/src/ui-tailwind/review/comment-markdown-renderer.tsx +15 -0
- package/src/ui-tailwind/review/tw-review-rail.tsx +92 -4
- package/src/ui-tailwind/review/tw-workflow-tab.tsx +1 -1
- package/src/ui-tailwind/review-workspace/page-chrome.ts +210 -0
- package/src/ui-tailwind/review-workspace/page-shell-metrics.ts +101 -0
- package/src/ui-tailwind/review-workspace/paragraph-layout.ts +115 -0
- package/src/ui-tailwind/review-workspace/selection-toolbar-placement.ts +97 -0
- package/src/ui-tailwind/review-workspace/tw-review-workspace-navigator.tsx +130 -0
- package/src/ui-tailwind/review-workspace/tw-review-workspace-page-toolbar.tsx +240 -0
- package/src/ui-tailwind/review-workspace/tw-review-workspace-rail.tsx +59 -0
- package/src/ui-tailwind/review-workspace/types.ts +408 -0
- package/src/ui-tailwind/review-workspace/use-chrome-policy.ts +104 -0
- package/src/ui-tailwind/review-workspace/use-derived-view-state.ts +151 -0
- package/src/ui-tailwind/review-workspace/use-diagnostics-signal.ts +70 -0
- package/src/ui-tailwind/review-workspace/use-grabbed-segment-offsets.ts +40 -0
- package/src/ui-tailwind/review-workspace/use-layout-facet-render-signal.ts +55 -0
- package/src/ui-tailwind/review-workspace/use-page-markers.ts +130 -0
- package/src/ui-tailwind/review-workspace/use-pm-surface-capture.ts +60 -0
- package/src/ui-tailwind/review-workspace/use-review-rail-state.ts +63 -0
- package/src/ui-tailwind/review-workspace/use-scope-card-state.ts +170 -0
- package/src/ui-tailwind/review-workspace/use-scroll-root-capture.ts +28 -0
- package/src/ui-tailwind/review-workspace/use-selection-toolbar-placement.ts +113 -0
- package/src/ui-tailwind/review-workspace/use-shell-selection-anchor-bridge.ts +120 -0
- package/src/ui-tailwind/review-workspace/use-status-bar-page-facts.ts +55 -0
- package/src/ui-tailwind/review-workspace/use-viewport-dimensions.ts +43 -0
- package/src/ui-tailwind/review-workspace/use-workspace-arbiter.ts +25 -0
- package/src/ui-tailwind/review-workspace/use-workspace-composition.ts +86 -0
- package/src/ui-tailwind/review-workspace/use-workspace-side-effects.ts +150 -0
- package/src/ui-tailwind/theme/editor-theme.css +25 -0
- package/src/ui-tailwind/toolbar/tw-role-action-region.tsx +2 -2
- package/src/ui-tailwind/toolbar/tw-toolbar.tsx +61 -98
- package/src/ui-tailwind/tw-review-workspace.tsx +521 -1802
- package/src/ui-tailwind/ui-api-context.tsx +43 -0
- package/src/ui-tailwind/ui-shell-channels-context.tsx +49 -0
- package/src/validation/compatibility-engine.ts +6 -6
- package/src/runtime/styles-cascade.ts +0 -33
- package/src/ui-tailwind/chrome/tw-mode-dock.tsx +0 -85
- /package/src/runtime/{page-number-format.ts → formatting/field/page-number-format.ts} +0 -0
- /package/src/runtime/{ai-action-policy.ts → workflow/ai-action-policy.ts} +0 -0
- /package/src/runtime/{scope-tag-registry.ts → workflow/scope-tag-registry.ts} +0 -0
|
@@ -9,8 +9,21 @@
|
|
|
9
9
|
* - `nextPage` (default) — new page set per section
|
|
10
10
|
* - `evenPage` / `oddPage` — blank filler pages to meet parity
|
|
11
11
|
* - `continuous` — the new section continues on the previous section's last page
|
|
12
|
-
* - `nextColumn` —
|
|
13
|
-
*
|
|
12
|
+
* - `nextColumn` — within a section, a block-level column break
|
|
13
|
+
* (`<w:br w:type="column"/>`) advances the active column; on column
|
|
14
|
+
* overflow the engine either advances to the next column or pushes
|
|
15
|
+
* a new page (which inherits the current section's column layout).
|
|
16
|
+
* At section granularity, a `<w:type w:val="nextColumn"/>` section
|
|
17
|
+
* break degrades to `continuous` — Word's behavior on single-column
|
|
18
|
+
* sections, preserved verbatim for multi-column sections in this
|
|
19
|
+
* slice so cross-section column-state carry is explicit follow-up
|
|
20
|
+
* work rather than implicit semantics.
|
|
21
|
+
*
|
|
22
|
+
* Column placement is tracked per block and surfaced via
|
|
23
|
+
* `PageStackResultWithSplits.columnByBlockIdByPageIndex`;
|
|
24
|
+
* `project-block-fragments.ts` consumes it to stamp
|
|
25
|
+
* `RuntimeBlockFragment.columnIndex`, and `buildRegions` routes fragments
|
|
26
|
+
* into `RuntimePageRegions.columns[i].fragmentIds`.
|
|
14
27
|
*
|
|
15
28
|
* Paragraph-level pagination hints:
|
|
16
29
|
* - `keepNext` — already honored in `paginateSectionBlocks`
|
|
@@ -58,6 +71,7 @@ import {
|
|
|
58
71
|
getUsablePageHeight,
|
|
59
72
|
} from "../page-layout-estimation.ts";
|
|
60
73
|
import {
|
|
74
|
+
buildRunFormattingMap,
|
|
61
75
|
calculateParagraphHeight,
|
|
62
76
|
resolveBlockFormatting,
|
|
63
77
|
resolveCharsPerLine,
|
|
@@ -155,6 +169,14 @@ export interface PageStackResultWithSplits {
|
|
|
155
169
|
* Parallel to `noteAllocationsByPageIndex`.
|
|
156
170
|
*/
|
|
157
171
|
noteFragmentsByPageIndex?: ReadonlyMap<number, Array<Omit<RuntimeBlockFragment, "pageId">>>;
|
|
172
|
+
/**
|
|
173
|
+
* Refactor/04 Slice 4 — per-block column placement keyed by global page
|
|
174
|
+
* index. Inner map: `blockId → columnIndex`. Absent when every page on the
|
|
175
|
+
* graph is single-column; `project-block-fragments.ts` consumes this to
|
|
176
|
+
* stamp `RuntimeBlockFragment.columnIndex` and route fragments into
|
|
177
|
+
* `RuntimePageRegions.columns[i].fragmentIds`.
|
|
178
|
+
*/
|
|
179
|
+
columnByBlockIdByPageIndex?: ReadonlyMap<number, ReadonlyMap<string, number>>;
|
|
158
180
|
}
|
|
159
181
|
|
|
160
182
|
// ---------------------------------------------------------------------------
|
|
@@ -207,6 +229,16 @@ export function buildPageStackWithSplits(
|
|
|
207
229
|
number,
|
|
208
230
|
Array<Omit<RuntimeBlockFragment, "pageId">>
|
|
209
231
|
>();
|
|
232
|
+
// Refactor/04 Slice 4 — per-block column placement aggregated across all
|
|
233
|
+
// sections, keyed by global page index. Populated only for blocks that
|
|
234
|
+
// paginated under a multi-column layout; absent entries mean single-column.
|
|
235
|
+
const globalColumnByBlockIdByPageIndex = new Map<number, Map<string, number>>();
|
|
236
|
+
// Refactor/04 post-Slice-4 — end-column per global page, used by the
|
|
237
|
+
// next section's `nextColumn` handling to decide whether to seed
|
|
238
|
+
// section N at `prevEnd + 1` or start fresh on a new page.
|
|
239
|
+
// "End column" = the highest columnIndex any block on the page
|
|
240
|
+
// occupied. 0 on single-column pages.
|
|
241
|
+
const endColumnByGlobalPageIndex = new Map<number, number>();
|
|
210
242
|
let globalPageIndex = 0;
|
|
211
243
|
// A single cache lives for the whole pagination pass so cross-section
|
|
212
244
|
// re-measurement (rare but possible through keepNext heuristics) still
|
|
@@ -234,7 +266,41 @@ export function buildPageStackWithSplits(
|
|
|
234
266
|
const prevSection = sectionIdx > 0 ? sections[sectionIdx - 1] : undefined;
|
|
235
267
|
const breakType = prevSection?.properties?.sectionType;
|
|
236
268
|
const nextDisplayPage = globalPageIndex + 1; // 1-based
|
|
237
|
-
|
|
269
|
+
|
|
270
|
+
// Refactor/04 post-Slice-4 — cross-section column carry. A
|
|
271
|
+
// section-level `nextColumn` break degrades by case:
|
|
272
|
+
// - new section single-column (columns <= 1): merge like
|
|
273
|
+
// continuous (preserves F01 + CCEP single-column regression).
|
|
274
|
+
// - new + prev sections share a column count AND the prev
|
|
275
|
+
// section's last page still has room: merge like continuous
|
|
276
|
+
// AND seed the new section's starting columnIndex =
|
|
277
|
+
// prevEndColumn + 1.
|
|
278
|
+
// - column counts mismatch, or prev ended in the last column
|
|
279
|
+
// (no room): behave like `nextPage` — no merge, start fresh.
|
|
280
|
+
// `continuous` sections still behave exactly as before.
|
|
281
|
+
let nextColumnSeed = 0;
|
|
282
|
+
let nextColumnDegradesToContinuous = false;
|
|
283
|
+
if (breakType === "nextColumn" && prevSection) {
|
|
284
|
+
const newColumns = layout.columns;
|
|
285
|
+
if (newColumns <= 1) {
|
|
286
|
+
nextColumnDegradesToContinuous = true;
|
|
287
|
+
} else {
|
|
288
|
+
const lastPage = pages.length > 0 ? pages[pages.length - 1] : undefined;
|
|
289
|
+
const prevColumns = lastPage?.layout.columns ?? 1;
|
|
290
|
+
if (prevColumns === newColumns && lastPage) {
|
|
291
|
+
const prevEnd = endColumnByGlobalPageIndex.get(lastPage.pageIndex) ?? 0;
|
|
292
|
+
if (prevEnd + 1 < newColumns) {
|
|
293
|
+
nextColumnDegradesToContinuous = true;
|
|
294
|
+
nextColumnSeed = prevEnd + 1;
|
|
295
|
+
}
|
|
296
|
+
// else: prev ended in last column → fall through (no merge,
|
|
297
|
+
// start fresh on new page in column 0 — matches nextPage).
|
|
298
|
+
}
|
|
299
|
+
// else: mismatched layouts → fall through (nextPage-ish).
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
const isContinuous =
|
|
303
|
+
breakType === "continuous" || nextColumnDegradesToContinuous;
|
|
238
304
|
|
|
239
305
|
if (breakType === "evenPage" && globalPageIndex > 0) {
|
|
240
306
|
if (nextDisplayPage % 2 !== 0) {
|
|
@@ -272,6 +338,7 @@ export function buildPageStackWithSplits(
|
|
|
272
338
|
measurementProvider,
|
|
273
339
|
cache,
|
|
274
340
|
defaultTabInterval,
|
|
341
|
+
nextColumnSeed,
|
|
275
342
|
);
|
|
276
343
|
const paginated = paginatedResult.pages;
|
|
277
344
|
|
|
@@ -346,6 +413,31 @@ export function buildPageStackWithSplits(
|
|
|
346
413
|
const existing = globalNoteFragmentsByPageIndex.get(globalPageIdx) ?? [];
|
|
347
414
|
globalNoteFragmentsByPageIndex.set(globalPageIdx, [...existing, ...sectionFrags]);
|
|
348
415
|
}
|
|
416
|
+
|
|
417
|
+
// Refactor/04 Slice 4 — merge per-section column placements into the
|
|
418
|
+
// global map. Sections that shared a page via `continuous` / `nextColumn`
|
|
419
|
+
// merging each contribute their own block ids to the same global index.
|
|
420
|
+
for (const [pageInSec, sectionCols] of paginatedResult.columnByBlockIdByPageInSection) {
|
|
421
|
+
const globalPageIdx = pageInSectionToGlobal.get(pageInSec);
|
|
422
|
+
if (globalPageIdx === undefined) continue;
|
|
423
|
+
let forPage = globalColumnByBlockIdByPageIndex.get(globalPageIdx);
|
|
424
|
+
if (!forPage) {
|
|
425
|
+
forPage = new Map<string, number>();
|
|
426
|
+
globalColumnByBlockIdByPageIndex.set(globalPageIdx, forPage);
|
|
427
|
+
}
|
|
428
|
+
for (const [blockId, col] of sectionCols) {
|
|
429
|
+
if (!forPage.has(blockId)) forPage.set(blockId, col);
|
|
430
|
+
}
|
|
431
|
+
// Refactor/04 post-Slice-4 — track the end-column per global page
|
|
432
|
+
// so the NEXT section's `nextColumn` break can decide whether to
|
|
433
|
+
// seed into this page's remaining columns or push fresh. The
|
|
434
|
+
// highest columnIndex observed on each page is the "end column".
|
|
435
|
+
let maxCol = endColumnByGlobalPageIndex.get(globalPageIdx) ?? 0;
|
|
436
|
+
for (const col of sectionCols.values()) {
|
|
437
|
+
if (col > maxCol) maxCol = col;
|
|
438
|
+
}
|
|
439
|
+
endColumnByGlobalPageIndex.set(globalPageIdx, maxCol);
|
|
440
|
+
}
|
|
349
441
|
}
|
|
350
442
|
|
|
351
443
|
// Guarantee at least one page
|
|
@@ -375,6 +467,9 @@ export function buildPageStackWithSplits(
|
|
|
375
467
|
noteFragmentsByPageIndex: globalNoteFragmentsByPageIndex.size > 0
|
|
376
468
|
? globalNoteFragmentsByPageIndex
|
|
377
469
|
: undefined,
|
|
470
|
+
columnByBlockIdByPageIndex: globalColumnByBlockIdByPageIndex.size > 0
|
|
471
|
+
? globalColumnByBlockIdByPageIndex
|
|
472
|
+
: undefined,
|
|
378
473
|
};
|
|
379
474
|
}
|
|
380
475
|
|
|
@@ -477,6 +572,15 @@ export function buildPageStackFromWithSplits(
|
|
|
477
572
|
return {
|
|
478
573
|
pages: tailPages,
|
|
479
574
|
splits: { byBlockId: tailSplits, tablesByBlockId: tailTableSplits },
|
|
575
|
+
...(full.columnByBlockIdByPageIndex && full.columnByBlockIdByPageIndex.size > 0
|
|
576
|
+
? {
|
|
577
|
+
columnByBlockIdByPageIndex: new Map(
|
|
578
|
+
Array.from(full.columnByBlockIdByPageIndex.entries()).filter(
|
|
579
|
+
([pi]) => pi >= dirtyPageNumberOffset,
|
|
580
|
+
),
|
|
581
|
+
),
|
|
582
|
+
}
|
|
583
|
+
: {}),
|
|
480
584
|
};
|
|
481
585
|
}
|
|
482
586
|
|
|
@@ -539,6 +643,16 @@ export function buildPageStackFromWithSplits(
|
|
|
539
643
|
]),
|
|
540
644
|
)
|
|
541
645
|
: undefined;
|
|
646
|
+
// Refactor/04 Slice 4 — shift per-block column map in lockstep with pages.
|
|
647
|
+
const shiftedColumnByBlockId =
|
|
648
|
+
tailResult.columnByBlockIdByPageIndex && tailResult.columnByBlockIdByPageIndex.size > 0
|
|
649
|
+
? new Map<number, ReadonlyMap<string, number>>(
|
|
650
|
+
Array.from(tailResult.columnByBlockIdByPageIndex.entries()).map(([pi, cols]) => [
|
|
651
|
+
pi + dirtyPageNumberOffset,
|
|
652
|
+
cols,
|
|
653
|
+
]),
|
|
654
|
+
)
|
|
655
|
+
: undefined;
|
|
542
656
|
|
|
543
657
|
return {
|
|
544
658
|
pages: shiftedPages,
|
|
@@ -549,6 +663,9 @@ export function buildPageStackFromWithSplits(
|
|
|
549
663
|
...(shiftedNoteFragments !== undefined
|
|
550
664
|
? { noteFragmentsByPageIndex: shiftedNoteFragments }
|
|
551
665
|
: {}),
|
|
666
|
+
...(shiftedColumnByBlockId !== undefined
|
|
667
|
+
? { columnByBlockIdByPageIndex: shiftedColumnByBlockId }
|
|
668
|
+
: {}),
|
|
552
669
|
};
|
|
553
670
|
}
|
|
554
671
|
|
|
@@ -816,7 +933,7 @@ function measureBlockHeight(
|
|
|
816
933
|
const measured = measurementProvider.measureLineFragments({
|
|
817
934
|
block,
|
|
818
935
|
formatting,
|
|
819
|
-
runs:
|
|
936
|
+
runs: buildRunFormattingMap(block),
|
|
820
937
|
columnWidth,
|
|
821
938
|
});
|
|
822
939
|
cache?.setLineCount(block, columnWidth, measured.lineCount);
|
|
@@ -1024,12 +1141,13 @@ function measureParagraphLineCount(
|
|
|
1024
1141
|
const measured = measurementProvider.measureLineFragments({
|
|
1025
1142
|
block,
|
|
1026
1143
|
formatting,
|
|
1027
|
-
//
|
|
1028
|
-
//
|
|
1029
|
-
//
|
|
1030
|
-
//
|
|
1031
|
-
//
|
|
1032
|
-
|
|
1144
|
+
// Per-run formatting threaded via `buildRunFormattingMap`
|
|
1145
|
+
// (Task 3, 2026-04-23). The canvas backend reads
|
|
1146
|
+
// `runs.get("${blockId}:${segmentId}")` for per-segment font
|
|
1147
|
+
// metrics; the empirical backend ignores `runs` (paragraph-
|
|
1148
|
+
// level metrics are sufficient) so the empirical numerical
|
|
1149
|
+
// output is unchanged.
|
|
1150
|
+
runs: buildRunFormattingMap(block),
|
|
1033
1151
|
columnWidth,
|
|
1034
1152
|
});
|
|
1035
1153
|
return Math.max(1, measured.lineCount);
|
|
@@ -1158,6 +1276,12 @@ interface SectionPaginationResult {
|
|
|
1158
1276
|
noteAllocationsByPageInSection: Map<number, RuntimeNoteAllocation[]>;
|
|
1159
1277
|
/** P8.1b — per-page note body fragments keyed by pageInSection index. */
|
|
1160
1278
|
noteFragmentsByPageInSection: Map<number, Array<Omit<RuntimeBlockFragment, "pageId">>>;
|
|
1279
|
+
/**
|
|
1280
|
+
* Refactor/04 Slice 4 — per-block column placement within a multi-column
|
|
1281
|
+
* section. Keyed by pageInSection → (blockId → columnIndex). Absent for
|
|
1282
|
+
* single-column sections; absent for note/footnote blocks.
|
|
1283
|
+
*/
|
|
1284
|
+
columnByBlockIdByPageInSection: Map<number, Map<string, number>>;
|
|
1161
1285
|
}
|
|
1162
1286
|
|
|
1163
1287
|
/**
|
|
@@ -1193,6 +1317,16 @@ export function paginateSectionBlocksWithSplits(
|
|
|
1193
1317
|
measurementProvider?: LayoutMeasurementProvider,
|
|
1194
1318
|
cache?: MeasurementCache,
|
|
1195
1319
|
defaultTabInterval = 720,
|
|
1320
|
+
/**
|
|
1321
|
+
* Refactor/04 post-Slice-4 — cross-section column carry seed. When
|
|
1322
|
+
* section N starts via `<w:type w:val="nextColumn"/>` from a
|
|
1323
|
+
* matching-layout previous section that ended in column K,
|
|
1324
|
+
* the outer pass seeds K+1 here so section N's first block lands in
|
|
1325
|
+
* the next available column rather than restarting at 0. Ignored
|
|
1326
|
+
* for single-column layouts (`maxColumns <= 1`) and clamped to
|
|
1327
|
+
* `[0, maxColumns-1]` defensively.
|
|
1328
|
+
*/
|
|
1329
|
+
startColumnIndex = 0,
|
|
1196
1330
|
): SectionPaginationResult {
|
|
1197
1331
|
if (blocks.length === 0) {
|
|
1198
1332
|
return {
|
|
@@ -1208,6 +1342,7 @@ export function paginateSectionBlocksWithSplits(
|
|
|
1208
1342
|
splits: { byBlockId: new Map() }, // section-local; global map includes tablesByBlockId via collectTableRowSlices
|
|
1209
1343
|
noteAllocationsByPageInSection: new Map(),
|
|
1210
1344
|
noteFragmentsByPageInSection: new Map(),
|
|
1345
|
+
columnByBlockIdByPageInSection: new Map(),
|
|
1211
1346
|
};
|
|
1212
1347
|
}
|
|
1213
1348
|
|
|
@@ -1216,12 +1351,39 @@ export function paginateSectionBlocksWithSplits(
|
|
|
1216
1351
|
const usableHeight = getUsablePageHeight(layout);
|
|
1217
1352
|
const columnMetrics = getUsableColumnMetrics(layout);
|
|
1218
1353
|
const maxColumns = Math.max(1, columnMetrics.length);
|
|
1354
|
+
const isMultiColumn = maxColumns > 1;
|
|
1219
1355
|
let pageStart = section.start;
|
|
1220
1356
|
let columnHeight = 0;
|
|
1221
|
-
|
|
1357
|
+
// Refactor/04 post-Slice-4 — seed `columnIndex` from the outer pass
|
|
1358
|
+
// when a matching-layout `nextColumn` section break precedes this
|
|
1359
|
+
// section. Clamp so a malformed seed can't read past `maxColumns-1`.
|
|
1360
|
+
// Single-column layouts ignore the seed (there's no column to
|
|
1361
|
+
// advance into).
|
|
1362
|
+
let columnIndex = isMultiColumn
|
|
1363
|
+
? Math.min(Math.max(0, startColumnIndex), maxColumns - 1)
|
|
1364
|
+
: 0;
|
|
1222
1365
|
let pageInSection = 0;
|
|
1223
1366
|
let reservedNoteHeight = 0;
|
|
1224
1367
|
const reservedNotes = new Set<string>();
|
|
1368
|
+
// Refactor/04 Slice 4 — per-block column placement. Populated only when
|
|
1369
|
+
// `maxColumns > 1`; single-column sections skip the map allocation entirely
|
|
1370
|
+
// so the cold path pays nothing for documents Word treats as single-column
|
|
1371
|
+
// (F01 + 10 representative CCEP regression path).
|
|
1372
|
+
const columnByBlockIdByPageInSection = new Map<number, Map<string, number>>();
|
|
1373
|
+
const recordColumnPlacement = (blockId: string): void => {
|
|
1374
|
+
if (!isMultiColumn) return;
|
|
1375
|
+
let forPage = columnByBlockIdByPageInSection.get(pageInSection);
|
|
1376
|
+
if (!forPage) {
|
|
1377
|
+
forPage = new Map<string, number>();
|
|
1378
|
+
columnByBlockIdByPageInSection.set(pageInSection, forPage);
|
|
1379
|
+
}
|
|
1380
|
+
// Only record the FIRST placement — paragraphs placed atomically
|
|
1381
|
+
// emit one fragment; table blocks may re-enter the loop with
|
|
1382
|
+
// tableProgress bumps but the block still resolves to one column.
|
|
1383
|
+
if (!forPage.has(blockId)) {
|
|
1384
|
+
forPage.set(blockId, columnIndex);
|
|
1385
|
+
}
|
|
1386
|
+
};
|
|
1225
1387
|
// P6.c: per-table progress when a table is being split row-by-row
|
|
1226
1388
|
// across pages. Map<blockId, nextRowIndexToPlace>. Cleared once a
|
|
1227
1389
|
// table is fully placed.
|
|
@@ -1430,6 +1592,7 @@ export function paginateSectionBlocksWithSplits(
|
|
|
1430
1592
|
|
|
1431
1593
|
// Case 1: remainder fits — place and break.
|
|
1432
1594
|
if (remainderHeight <= remainingForTable) {
|
|
1595
|
+
recordColumnPlacement(block.blockId);
|
|
1433
1596
|
columnHeight += startRow > 0 ? remainderHeight : baseHeight;
|
|
1434
1597
|
if (startRow > 0) tableProgress.delete(block.blockId);
|
|
1435
1598
|
if (index === blocks.length - 1) pushPage(section.end);
|
|
@@ -1446,6 +1609,7 @@ export function paginateSectionBlocksWithSplits(
|
|
|
1446
1609
|
startRow,
|
|
1447
1610
|
});
|
|
1448
1611
|
if (decision.rowsOnCurrentPage > 0) {
|
|
1612
|
+
recordColumnPlacement(block.blockId);
|
|
1449
1613
|
tableProgress.set(block.blockId, decision.splitRowIndex);
|
|
1450
1614
|
pushPage(rowOffset(decision.splitRowIndex));
|
|
1451
1615
|
continue;
|
|
@@ -1462,6 +1626,7 @@ export function paginateSectionBlocksWithSplits(
|
|
|
1462
1626
|
// Case 4: degraded atomic placement (table on a fresh page that
|
|
1463
1627
|
// it doesn't fit on, AND the first row alone exceeds page
|
|
1464
1628
|
// height). Preserve pre-P6.c semantics so offsets stay clean.
|
|
1629
|
+
recordColumnPlacement(block.blockId);
|
|
1465
1630
|
columnHeight += baseHeight;
|
|
1466
1631
|
if (index === blocks.length - 1) pushPage(section.end);
|
|
1467
1632
|
break;
|
|
@@ -1577,6 +1742,10 @@ export function paginateSectionBlocksWithSplits(
|
|
|
1577
1742
|
columnWidth,
|
|
1578
1743
|
reservedNotes,
|
|
1579
1744
|
);
|
|
1745
|
+
// Refactor/04 Slice 4 — record placement BEFORE the `hasColumnBreak`
|
|
1746
|
+
// branch below advances `columnIndex`; the block itself lives in the
|
|
1747
|
+
// PRE-advance column.
|
|
1748
|
+
recordColumnPlacement(block.blockId);
|
|
1580
1749
|
columnHeight += baseHeight;
|
|
1581
1750
|
reservedNoteHeight += effectiveNoteHeight;
|
|
1582
1751
|
currentPageNoteIds(block).forEach((noteKey) => {
|
|
@@ -1634,6 +1803,7 @@ export function paginateSectionBlocksWithSplits(
|
|
|
1634
1803
|
splits: { byBlockId: splitsByBlock },
|
|
1635
1804
|
noteAllocationsByPageInSection,
|
|
1636
1805
|
noteFragmentsByPageInSection,
|
|
1806
|
+
columnByBlockIdByPageInSection,
|
|
1637
1807
|
};
|
|
1638
1808
|
}
|
|
1639
1809
|
|
|
@@ -1682,6 +1852,11 @@ function estimateFootnoteReservation(
|
|
|
1682
1852
|
const colonIdx = noteKey.indexOf(":");
|
|
1683
1853
|
if (colonIdx === -1) continue;
|
|
1684
1854
|
const noteKind = noteKey.slice(0, colonIdx) as "footnote" | "endnote";
|
|
1855
|
+
// Endnotes flow to the document-end endnote area, not the per-page
|
|
1856
|
+
// footnote area — they do not reserve page-local height. The
|
|
1857
|
+
// snapshotNoteAllocations loop already skips endnotes on emission;
|
|
1858
|
+
// this skip keeps the reservation math aligned.
|
|
1859
|
+
if (noteKind === "endnote") continue;
|
|
1685
1860
|
const noteId = noteKey.slice(colonIdx + 1);
|
|
1686
1861
|
// Use measureNoteBody so reservation math and emission share the same path.
|
|
1687
1862
|
const { heightTwips } = measureNoteBody(noteKind, noteId, footnotes, columnWidth);
|
|
@@ -37,6 +37,7 @@ export function projectSurfaceBlocksToPageFragments(
|
|
|
37
37
|
surface: EditorSurfaceSnapshot,
|
|
38
38
|
pages: readonly DocumentPageSnapshot[],
|
|
39
39
|
splits?: BlockSplits,
|
|
40
|
+
columnByBlockIdByPageIndex?: ReadonlyMap<number, ReadonlyMap<string, number>>,
|
|
40
41
|
): Map<number, FragmentWithoutPageId[]> {
|
|
41
42
|
const byPage = new Map<number, FragmentWithoutPageId[]>();
|
|
42
43
|
const perPageCounter = new Map<number, number>();
|
|
@@ -59,6 +60,10 @@ export function projectSurfaceBlocksToPageFragments(
|
|
|
59
60
|
return n;
|
|
60
61
|
};
|
|
61
62
|
|
|
63
|
+
// Refactor/04 Slice 4 — per-block column placement lookup.
|
|
64
|
+
const columnIndexFor = (pageIndex: number, blockId: string): number | undefined =>
|
|
65
|
+
columnByBlockIdByPageIndex?.get(pageIndex)?.get(blockId);
|
|
66
|
+
|
|
62
67
|
for (const block of surface.blocks) {
|
|
63
68
|
// R3: table split across pages — emit one fragment per row slice.
|
|
64
69
|
// Consumers read `tableRowRange` and prepend header rows when from > 0.
|
|
@@ -69,9 +74,11 @@ export function projectSurfaceBlocksToPageFragments(
|
|
|
69
74
|
block,
|
|
70
75
|
tableSliceList,
|
|
71
76
|
(pageIndex, fragment) => {
|
|
77
|
+
const columnIndex = columnIndexFor(pageIndex, block.blockId);
|
|
72
78
|
pushFragment(pageIndex, {
|
|
73
79
|
...fragment,
|
|
74
80
|
orderInRegion: nextOrder(pageIndex),
|
|
81
|
+
...(columnIndex !== undefined ? { columnIndex } : {}),
|
|
75
82
|
});
|
|
76
83
|
},
|
|
77
84
|
);
|
|
@@ -87,9 +94,11 @@ export function projectSurfaceBlocksToPageFragments(
|
|
|
87
94
|
block,
|
|
88
95
|
sliceList,
|
|
89
96
|
(pageIndex, fragment) => {
|
|
97
|
+
const columnIndex = columnIndexFor(pageIndex, block.blockId);
|
|
90
98
|
pushFragment(pageIndex, {
|
|
91
99
|
...fragment,
|
|
92
100
|
orderInRegion: nextOrder(pageIndex),
|
|
101
|
+
...(columnIndex !== undefined ? { columnIndex } : {}),
|
|
93
102
|
});
|
|
94
103
|
},
|
|
95
104
|
);
|
|
@@ -101,6 +110,7 @@ export function projectSurfaceBlocksToPageFragments(
|
|
|
101
110
|
const pageIndex = findPageIndexForOffset(pages, block.from);
|
|
102
111
|
if (pageIndex === null) continue;
|
|
103
112
|
|
|
113
|
+
const columnIndex = columnIndexFor(pageIndex, block.blockId);
|
|
104
114
|
const fragment: FragmentWithoutPageId = {
|
|
105
115
|
fragmentId: `fragment-${block.blockId}`,
|
|
106
116
|
blockId: block.blockId,
|
|
@@ -111,6 +121,7 @@ export function projectSurfaceBlocksToPageFragments(
|
|
|
111
121
|
heightTwips: estimateBlockHeightFromSpan(block),
|
|
112
122
|
...deriveStyleMetadata(block),
|
|
113
123
|
kind: "whole",
|
|
124
|
+
...(columnIndex !== undefined ? { columnIndex } : {}),
|
|
114
125
|
};
|
|
115
126
|
|
|
116
127
|
pushFragment(pageIndex, fragment);
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Layer 04 — layout debug projector.
|
|
3
|
+
*
|
|
4
|
+
* Produces a `LayoutDebugEntry` for the debug-inspector snapshot when the
|
|
5
|
+
* `layout` telemetry channel is on. Architecture reference:
|
|
6
|
+
* docs/architecture/04-layout-semantics.md §"Projector and oracle surfaces".
|
|
7
|
+
*
|
|
8
|
+
* Contract with CLAUDE.md performance invariant 4:
|
|
9
|
+
* - Callers gate the call through `bus.isEnabled("layout")` so the
|
|
10
|
+
* cost is zero when the channel is off.
|
|
11
|
+
* - The projector never widens the wholesale-snapshot path. It reads
|
|
12
|
+
* four existing facet accessors (`getPageCount`, `getSections`,
|
|
13
|
+
* `getMeasurementFidelity`, `getDirtyFieldFamilies`) and performs no
|
|
14
|
+
* extra work beyond their return values.
|
|
15
|
+
* - Cache warming caveat: the layout facet is lazy. If the projector
|
|
16
|
+
* is the FIRST consumer to read `getPageCount()` on a runtime that
|
|
17
|
+
* has never paginated, the underlying engine will run `fullRebuild`
|
|
18
|
+
* as part of that call — this is the engine's documented lazy
|
|
19
|
+
* behavior, not something the projector causes. A SECOND projector
|
|
20
|
+
* call on the same facet is idempotent and adds no commit (see
|
|
21
|
+
* `test/runtime/layout/projector-on-demand.test.ts`). In real
|
|
22
|
+
* sessions the debug snapshot is invoked after the shell has already
|
|
23
|
+
* mounted and read the facet, so the cold-path cost does not fall
|
|
24
|
+
* on the debug path.
|
|
25
|
+
*
|
|
26
|
+
* The entry is intentionally small. Slice 3 ships the page-count +
|
|
27
|
+
* section-count + measurement-fidelity + dirty-fields summary plus the
|
|
28
|
+
* `LAYOUT_ENGINE_VERSION` discriminator so traces from different engine
|
|
29
|
+
* builds are distinguishable. Slice 4 (multi-column `nextColumn`) adds
|
|
30
|
+
* per-page column counts.
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
import type { LayoutFacet } from "./layout-facet-types.ts";
|
|
34
|
+
import { LAYOUT_ENGINE_VERSION } from "./layout-engine-version.ts";
|
|
35
|
+
import type { DocumentSettings } from "../../model/canonical-document.ts";
|
|
36
|
+
import {
|
|
37
|
+
resolveLayoutCompatInputs,
|
|
38
|
+
type LayoutCompatInputs,
|
|
39
|
+
} from "./compat-input-ledger.ts";
|
|
40
|
+
|
|
41
|
+
export interface LayoutDebugEntry {
|
|
42
|
+
readonly schemaVersion: 2;
|
|
43
|
+
/**
|
|
44
|
+
* `LAYOUT_ENGINE_VERSION` at projection time. Traces from different
|
|
45
|
+
* engine builds are distinguishable; persisted prerender caches key on
|
|
46
|
+
* this constant so its value at projection time is a reliable
|
|
47
|
+
* discriminator.
|
|
48
|
+
*/
|
|
49
|
+
readonly engineVersion: number;
|
|
50
|
+
readonly pageCount: number;
|
|
51
|
+
readonly sectionCount: number;
|
|
52
|
+
readonly measurementFidelity: string;
|
|
53
|
+
readonly dirtyFieldFamilies: readonly string[];
|
|
54
|
+
/**
|
|
55
|
+
* Task 5 (2026-04-23) — compat-input ledger. Surfaces which
|
|
56
|
+
* pre-2013/legacy compat flags + `<w:compatSetting>` entries are
|
|
57
|
+
* active on the document so operators can tell at a glance which
|
|
58
|
+
* compat-conditional branches would fire. Absent when the document
|
|
59
|
+
* settings are unavailable (debug projection can be taken against a
|
|
60
|
+
* facet without an accompanying `DocumentSettings`).
|
|
61
|
+
*/
|
|
62
|
+
readonly compatInputs?: LayoutCompatInputs;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface BuildLayoutDebugEntryInputs {
|
|
66
|
+
readonly facet: LayoutFacet;
|
|
67
|
+
/**
|
|
68
|
+
* Task 5 — when supplied, the projector resolves and attaches
|
|
69
|
+
* `compatInputs`. Optional so existing callers that never plumbed
|
|
70
|
+
* settings through continue working unchanged.
|
|
71
|
+
*/
|
|
72
|
+
readonly settings?: DocumentSettings;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function buildLayoutDebugEntry(
|
|
76
|
+
inputs: BuildLayoutDebugEntryInputs,
|
|
77
|
+
): LayoutDebugEntry {
|
|
78
|
+
const { facet, settings } = inputs;
|
|
79
|
+
return {
|
|
80
|
+
schemaVersion: 2,
|
|
81
|
+
engineVersion: LAYOUT_ENGINE_VERSION,
|
|
82
|
+
pageCount: facet.getPageCount(),
|
|
83
|
+
sectionCount: facet.getSections().length,
|
|
84
|
+
measurementFidelity: facet.getMeasurementFidelity(),
|
|
85
|
+
dirtyFieldFamilies: facet.getDirtyFieldFamilies(),
|
|
86
|
+
...(settings !== undefined
|
|
87
|
+
? { compatInputs: resolveLayoutCompatInputs(settings) }
|
|
88
|
+
: {}),
|
|
89
|
+
};
|
|
90
|
+
}
|