@beyondwork/docx-react-component 1.0.0 → 1.0.2
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 +44 -104
- package/package.json +66 -15
- package/src/api/public-types.ts +1 -1
- package/src/compare/diff-engine.ts +530 -0
- package/src/compare/export-redlines.ts +162 -0
- package/src/compare/snapshot.ts +37 -0
- package/src/core/commands/index.ts +1 -1
- package/src/core/state/editor-state.ts +2 -2
- package/src/index.ts +45 -0
- package/src/legal/bookmarks.ts +196 -0
- package/src/legal/cross-references.ts +356 -0
- package/src/legal/defined-terms.ts +203 -0
- package/src/runtime/document-runtime.ts +3 -5
- package/src/runtime/table-commands.ts +4 -1
- package/src/runtime/table-schema.ts +17 -2
- package/src/runtime/virtualized-rendering.ts +258 -0
- package/src/ui/WordReviewEditor.tsx +256 -35
- package/src/ui-tailwind/editor-surface/tw-editor-surface.tsx +2 -2
- package/src/ui-tailwind/editor-surface/tw-table-node-view.tsx +16 -2
- package/.codex/config.toml +0 -5
- package/.corepack/v1/pnpm/10.30.3/.corepack +0 -1
- package/.corepack/v1/pnpm/10.30.3/LICENSE +0 -22
- package/.corepack/v1/pnpm/10.30.3/README.md +0 -240
- package/.corepack/v1/pnpm/10.30.3/dist/node-gyp-bin/node-gyp +0 -6
- package/.corepack/v1/pnpm/10.30.3/dist/node-gyp-bin/node-gyp.cmd +0 -5
- package/.corepack/v1/pnpm/10.30.3/dist/pnpm.cjs +0 -195400
- package/.corepack/v1/pnpm/10.30.3/dist/pnpmrc +0 -2
- package/.corepack/v1/pnpm/10.30.3/dist/reflink.darwin-arm64-2HJ4WGO6.node +0 -0
- package/.corepack/v1/pnpm/10.30.3/dist/reflink.darwin-x64-3G3H6IW4.node +0 -0
- package/.corepack/v1/pnpm/10.30.3/dist/reflink.win32-arm64-msvc-Q6BARPPB.node +0 -0
- package/.corepack/v1/pnpm/10.30.3/dist/reflink.win32-x64-msvc-J2TZHRQI.node +0 -0
- package/.corepack/v1/pnpm/10.30.3/dist/templates/completion.bash +0 -31
- package/.corepack/v1/pnpm/10.30.3/dist/templates/completion.fish +0 -22
- package/.corepack/v1/pnpm/10.30.3/dist/templates/completion.ps1 +0 -193
- package/.corepack/v1/pnpm/10.30.3/dist/templates/completion.zsh +0 -27
- package/.corepack/v1/pnpm/10.30.3/dist/vendor/fastlist-0.3.0-x64.exe +0 -0
- package/.corepack/v1/pnpm/10.30.3/dist/vendor/fastlist-0.3.0-x86.exe +0 -0
- package/.corepack/v1/pnpm/10.30.3/dist/worker.js +0 -10119
- package/.corepack/v1/pnpm/10.30.3/package.json +0 -192
- package/.cursor/mcp.json +0 -7
- package/.github/workflows/ci.yml +0 -35
- package/.mcp.json +0 -7
- package/.openclaw/workspace-state.json +0 -4
- package/.pnpmrc.json +0 -1
- package/.wave-launch.sh +0 -7
- package/.workspace-marker +0 -1
- package/AGENTS.md +0 -78
- package/CHANGELOG.md +0 -177
- package/DESIGN.md +0 -929
- package/HEARTBEAT.md +0 -7
- package/IDENTITY.md +0 -23
- package/SOUL.md +0 -36
- package/TOOLS.md +0 -40
- package/USER.md +0 -17
- package/docs/README.md +0 -107
- package/docs/agents/wave-cont-eval-role.md +0 -36
- package/docs/agents/wave-cont-qa-role.md +0 -52
- package/docs/agents/wave-deploy-verifier-role.md +0 -34
- package/docs/agents/wave-design-role.md +0 -47
- package/docs/agents/wave-documentation-role.md +0 -34
- package/docs/agents/wave-infra-role.md +0 -34
- package/docs/agents/wave-integration-role.md +0 -37
- package/docs/agents/wave-launcher-role.md +0 -41
- package/docs/agents/wave-orchestrator-role.md +0 -52
- package/docs/agents/wave-planner-role.md +0 -39
- package/docs/agents/wave-security-role.md +0 -40
- package/docs/architecture/docx/README.md +0 -10
- package/docs/architecture/future/README.md +0 -8
- package/docs/architecture/ooxml-upgrade-analysis.md +0 -134
- package/docs/architecture/platform/shared-openxml-editor-platform.md +0 -153
- package/docs/architecture/xlsx/canonical-workbook-model-and-commands.md +0 -187
- package/docs/architecture/xlsx/spreadsheet-editor-frontend-architecture.md +0 -150
- package/docs/comment-redline-overview.md +0 -350
- package/docs/concepts/context7-vs-skills.md +0 -118
- package/docs/concepts/operating-modes.md +0 -91
- package/docs/concepts/runtime-agnostic-orchestration.md +0 -111
- package/docs/concepts/what-is-a-wave.md +0 -217
- package/docs/context7/bundles.json +0 -222
- package/docs/context7/planner-agent/README.md +0 -28
- package/docs/context7/planner-agent/manifest.json +0 -83
- package/docs/context7/planner-agent/papers/cooperbench-why-coding-agents-cannot-be-your-teammates-yet.md +0 -3283
- package/docs/context7/planner-agent/papers/dova-deliberation-first-multi-agent-orchestration-for-autonomous-research-automation.md +0 -1699
- package/docs/context7/planner-agent/papers/dpbench-large-language-models-struggle-with-simultaneous-coordination.md +0 -2251
- package/docs/context7/planner-agent/papers/incremental-planning-to-control-a-blackboard-based-problem-solver.md +0 -1729
- package/docs/context7/planner-agent/papers/silo-bench-a-scalable-environment-for-evaluating-distributed-coordination-in-multi-agent-llm-systems.md +0 -3747
- package/docs/context7/planner-agent/papers/todoevolve-learning-to-architect-agent-planning-systems.md +0 -1675
- package/docs/context7/planner-agent/papers/verified-multi-agent-orchestration-a-plan-execute-verify-replan-framework-for-complex-query-resolution.md +0 -1173
- package/docs/context7/planner-agent/papers/why-do-multi-agent-llm-systems-fail.md +0 -5211
- package/docs/context7/planner-agent/topics/planning-and-orchestration.md +0 -24
- package/docs/evals/arm-templates/README.md +0 -13
- package/docs/evals/arm-templates/full-wave.json +0 -15
- package/docs/evals/arm-templates/single-agent.json +0 -15
- package/docs/evals/benchmark-catalog.json +0 -670
- package/docs/evals/cases/README.md +0 -47
- package/docs/evals/cases/wave-blackboard-inbox-targeting.json +0 -73
- package/docs/evals/cases/wave-contradiction-conflict.json +0 -104
- package/docs/evals/cases/wave-expert-routing-preservation.json +0 -69
- package/docs/evals/cases/wave-hidden-profile-private-evidence.json +0 -81
- package/docs/evals/cases/wave-premature-closure-guard.json +0 -71
- package/docs/evals/cases/wave-silo-cross-agent-state.json +0 -77
- package/docs/evals/cases/wave-simultaneous-lockstep.json +0 -92
- package/docs/evals/external-benchmarks.json +0 -85
- package/docs/evals/external-command-config.sample.json +0 -9
- package/docs/evals/external-command-config.swe-bench-pro.json +0 -8
- package/docs/evals/pilots/README.md +0 -47
- package/docs/evals/pilots/swe-bench-pro-public-full-wave-review-10.json +0 -64
- package/docs/evals/pilots/swe-bench-pro-public-pilot.json +0 -111
- package/docs/evals/wave-benchmark-program.md +0 -302
- package/docs/guides/planner.md +0 -220
- package/docs/guides/recommendations-0.8.9.md +0 -133
- package/docs/guides/signal-wrappers.md +0 -165
- package/docs/guides/terminal-surfaces.md +0 -96
- package/docs/image copy.png +0 -0
- package/docs/image.png +0 -0
- package/docs/images/image.png +0 -0
- package/docs/legal-feedback-architecture.md +0 -498
- package/docs/plans/component-cutover-matrix.json +0 -1072
- package/docs/plans/component-cutover-matrix.md +0 -307
- package/docs/plans/context7-wave-orchestrator.md +0 -155
- package/docs/plans/current-state.md +0 -198
- package/docs/plans/docx/README.md +0 -9
- package/docs/plans/examples/wave-benchmark-improvement.md +0 -108
- package/docs/plans/examples/wave-example-live-proof.md +0 -435
- package/docs/plans/master-plan.md +0 -224
- package/docs/plans/migration.md +0 -538
- package/docs/plans/operations/README.md +0 -7
- package/docs/plans/operations/wave-10-word-certification.md +0 -87
- package/docs/plans/operations/wave-8-railway-staging.md +0 -153
- package/docs/plans/operations/wave-9-manual-certification.md +0 -73
- package/docs/plans/platform/README.md +0 -9
- package/docs/plans/reference/legal-checklist-coverage.md +0 -258
- package/docs/plans/wave-orchestrator.md +0 -423
- package/docs/plans/waves/README.md +0 -75
- package/docs/plans/waves/completed/wave-0.md +0 -195
- package/docs/plans/waves/completed/wave-1.md +0 -379
- package/docs/plans/waves/completed/wave-10.md +0 -670
- package/docs/plans/waves/completed/wave-11.md +0 -335
- package/docs/plans/waves/completed/wave-12.md +0 -417
- package/docs/plans/waves/completed/wave-13.md +0 -316
- package/docs/plans/waves/completed/wave-14.md +0 -319
- package/docs/plans/waves/completed/wave-15.md +0 -321
- package/docs/plans/waves/completed/wave-16.md +0 -316
- package/docs/plans/waves/completed/wave-17.md +0 -331
- package/docs/plans/waves/completed/wave-18.md +0 -328
- package/docs/plans/waves/completed/wave-2.md +0 -438
- package/docs/plans/waves/completed/wave-3.md +0 -435
- package/docs/plans/waves/completed/wave-4.md +0 -430
- package/docs/plans/waves/completed/wave-5.md +0 -430
- package/docs/plans/waves/completed/wave-6.md +0 -430
- package/docs/plans/waves/completed/wave-7.md +0 -526
- package/docs/plans/waves/completed/wave-8.md +0 -596
- package/docs/plans/waves/completed/wave-9.md +0 -552
- package/docs/plans/waves/deferred/README.md +0 -14
- package/docs/plans/waves/deferred/encrypted-intake-contracts.md +0 -282
- package/docs/plans/waves/deferred/legal-feedback-wave-expansion.md +0 -308
- package/docs/plans/waves/deferred/wave-encrypted-intake.md +0 -451
- package/docs/plans/waves/design/README.md +0 -5
- package/docs/plans/waves/design/wave-1-a1.md +0 -309
- package/docs/plans/waves/reviews/README.md +0 -5
- package/docs/plans/waves/reviews/wave-0-cont-qa.md +0 -151
- package/docs/plans/waves/reviews/wave-1-cont-qa.md +0 -46
- package/docs/plans/waves/reviews/wave-10-accessibility-and-design.md +0 -51
- package/docs/plans/waves/reviews/wave-10-cont-qa.md +0 -24
- package/docs/plans/waves/reviews/wave-10-dashboard-proof.md +0 -46
- package/docs/plans/waves/reviews/wave-10-performance-signoff.md +0 -55
- package/docs/plans/waves/reviews/wave-10-regression-proof.md +0 -23
- package/docs/plans/waves/reviews/wave-10-release-audit.md +0 -31
- package/docs/plans/waves/reviews/wave-10-service-proof.md +0 -83
- package/docs/plans/waves/reviews/wave-10-word-certification.md +0 -31
- package/docs/plans/waves/reviews/wave-18-ai-contract-closure.md +0 -277
- package/docs/plans/waves/reviews/wave-18-cont-qa.md +0 -255
- package/docs/plans/waves/reviews/wave-18-parity-proof.md +0 -271
- package/docs/plans/waves/reviews/wave-19-cont-qa.md +0 -59
- package/docs/plans/waves/reviews/wave-2-cont-qa.md +0 -72
- package/docs/plans/waves/reviews/wave-20-cont-qa.md +0 -60
- package/docs/plans/waves/reviews/wave-25-cont-qa.md +0 -48
- package/docs/plans/waves/reviews/wave-28-cont-qa.md +0 -46
- package/docs/plans/waves/reviews/wave-29-cont-qa.md +0 -53
- package/docs/plans/waves/reviews/wave-3-cont-qa.md +0 -53
- package/docs/plans/waves/reviews/wave-3-core-proof.md +0 -77
- package/docs/plans/waves/reviews/wave-3-validator-proof.md +0 -73
- package/docs/plans/waves/reviews/wave-32-cont-qa.md +0 -43
- package/docs/plans/waves/reviews/wave-33-cont-qa.md +0 -526
- package/docs/plans/waves/reviews/wave-34-cont-qa.md +0 -100
- package/docs/plans/waves/reviews/wave-35-cont-qa.md +0 -145
- package/docs/plans/waves/reviews/wave-4-cont-qa.md +0 -47
- package/docs/plans/waves/reviews/wave-4-structure-proof.md +0 -69
- package/docs/plans/waves/reviews/wave-5-comment-proof.md +0 -158
- package/docs/plans/waves/reviews/wave-5-cont-qa.md +0 -68
- package/docs/plans/waves/reviews/wave-6-cont-qa.md +0 -416
- package/docs/plans/waves/reviews/wave-6-redline-proof.md +0 -130
- package/docs/plans/waves/reviews/wave-7-cont-qa.md +0 -82
- package/docs/plans/waves/reviews/wave-7-ooxml-compliance.md +0 -85
- package/docs/plans/waves/reviews/wave-7-preservation-proof.md +0 -119
- package/docs/plans/waves/reviews/wave-7-trust-ux.md +0 -87
- package/docs/plans/waves/reviews/wave-8-accessibility-and-design.md +0 -128
- package/docs/plans/waves/reviews/wave-8-cont-qa.md +0 -92
- package/docs/plans/waves/reviews/wave-8-live-proof.md +0 -140
- package/docs/plans/waves/reviews/wave-8-security.md +0 -47
- package/docs/plans/waves/reviews/wave-9-editor-embedding.md +0 -39
- package/docs/plans/waves/reviews/wave-9-fixture-runner.md +0 -56
- package/docs/plans/waves/reviews/wave-9-live-proof.md +0 -105
- package/docs/plans/waves/reviews/wave-9-usability-and-performance.md +0 -152
- package/docs/plans/waves/specs/README.md +0 -5
- package/docs/plans/waves/specs/wave-1-component-boundaries.md +0 -322
- package/docs/plans/waves/specs/wave-1-ooxml-contracts.md +0 -323
- package/docs/plans/waves/specs/wave-1-review-and-ui-contracts.md +0 -339
- package/docs/plans/waves/specs/wave-1-runtime-contracts.md +0 -509
- package/docs/plans/waves/wave-19.md +0 -341
- package/docs/plans/waves/wave-20.md +0 -308
- package/docs/plans/waves/wave-21.md +0 -289
- package/docs/plans/waves/wave-22.md +0 -221
- package/docs/plans/waves/wave-23.md +0 -295
- package/docs/plans/waves/wave-24.md +0 -286
- package/docs/plans/waves/wave-25.md +0 -313
- package/docs/plans/waves/wave-26.md +0 -300
- package/docs/plans/waves/wave-27.md +0 -299
- package/docs/plans/waves/wave-28.md +0 -368
- package/docs/plans/waves/wave-29.md +0 -303
- package/docs/plans/waves/wave-30.md +0 -307
- package/docs/plans/waves/wave-31.md +0 -231
- package/docs/plans/waves/wave-32.md +0 -152
- package/docs/plans/waves/wave-33.md +0 -147
- package/docs/plans/waves/wave-34.md +0 -148
- package/docs/plans/waves/wave-35.md +0 -141
- package/docs/plans/waves/wave-36.md +0 -146
- package/docs/plans/xlsx/README.md +0 -14
- package/docs/plans/xlsx/xlsx-fixture-corpus-and-certification-plan.md +0 -126
- package/docs/reference/cli-reference.md +0 -600
- package/docs/reference/coordination-and-closure.md +0 -487
- package/docs/reference/deep-research-report (15).md +0 -25
- package/docs/reference/docx/README.md +0 -10
- package/docs/reference/legal-checklist.md +0 -445
- package/docs/reference/live-proof-waves.md +0 -199
- package/docs/reference/ooxml-compliance.md +0 -129
- package/docs/reference/ooxml-feature-parity-matrix.md +0 -172
- package/docs/reference/platform/shared-ooxml-platform-guidance.md +0 -77
- package/docs/reference/prototype-agent-prompt-legal-fidelity.md +0 -155
- package/docs/reference/public-api.md +0 -456
- package/docs/reference/repository-guidance.md +0 -58
- package/docs/reference/runtime-config/README.md +0 -182
- package/docs/reference/runtime-config/claude.md +0 -110
- package/docs/reference/runtime-config/codex.md +0 -82
- package/docs/reference/runtime-config/opencode.md +0 -93
- package/docs/reference/sample-waves.md +0 -105
- package/docs/reference/skills.md +0 -237
- package/docs/reference/templates/AGENTS.md +0 -78
- package/docs/reference/templates/HEARTBEAT.md +0 -7
- package/docs/reference/templates/IDENTITY.md +0 -23
- package/docs/reference/templates/SOUL.md +0 -36
- package/docs/reference/templates/TOOLS.md +0 -40
- package/docs/reference/templates/USER.md +0 -17
- package/docs/reference/wave-control.md +0 -184
- package/docs/reference/wave-planning-lessons.md +0 -167
- package/docs/reference/word-review-editor-frontend-architecture.md +0 -479
- package/docs/reference/word-review-editor-ux-guide.md +0 -253
- package/docs/reference/xlsx/xlsx-ooxml-compliance.md +0 -137
- package/docs/research/agent-context-sources.md +0 -178
- package/docs/research/coordination-failure-review.md +0 -290
- package/docs/research/docx-react-component/Canonical Document Schema Specification for a React-based Word-compatible Editor.md +0 -2317
- package/docs/research/docx-react-component/Feature Compatibility Matrix for a React Word Compatible Legal Editor v1.md +0 -219
- package/docs/research/docx-react-component/React Component Architecture and Front-End Structure Specification for a Word-Compatible Legal Review Editor.md +0 -1112
- package/docs/research/docx-react-component/document_compatibility_and_testing_spec.md +0 -751
- package/docs/research/xlsx/raw/README.md +0 -13
- package/docs/roadmap.md +0 -174
- package/docs/superpowers/plans/2026-03-28-harness-control-bar.md +0 -677
- package/docs/superpowers/specs/2026-03-28-harness-control-bar-design.md +0 -274
- package/docs/xlsx-react/README.md +0 -38
- package/docs/xlsx-react/agent-llm-interaction-layer-docx-xlsx.md +0 -621
- package/docs/xlsx-react/canonical-workbook-model-and-commands.md +0 -948
- package/docs/xlsx-react/shared-openxml-editor-platform-docx-xlsx.md +0 -228
- package/docs/xlsx-react/spreadsheet-editor-component-architecture.md +0 -809
- package/docs/xlsx-react/spreadsheet-editor-frontend-architecture.md +0 -537
- package/docs/xlsx-react/spreadsheet-editor-ux-guide.md +0 -520
- package/docs/xlsx-react/xlsx-editor-research-pack.md +0 -871
- package/docs/xlsx-react/xlsx-fixture-corpus-and-certification-plan.md +0 -436
- package/docs/xlsx-react/xlsx-ooxml-compliance.md +0 -320
- package/examples/README.md +0 -16
- package/memory/MEMORY.md +0 -24
- package/pnpm-workspace.yaml +0 -4
- package/scripts/check-no-authored-js.sh +0 -13
- package/scripts/context7-api-check.sh +0 -65
- package/scripts/context7-export-env.sh +0 -42
- package/scripts/run-context7-mcp.sh +0 -8
- package/scripts/run-workspace-tests.sh +0 -15
- package/scripts/start-wave-10-local.sh +0 -189
- package/scripts/wave-agent-attach.sh +0 -47
- package/scripts/wave-auto-answer.sh +0 -118
- package/scripts/wave-dashboard-attach.sh +0 -13
- package/scripts/wave-launch.sh +0 -273
- package/scripts/wave-overnight-supervisor.sh +0 -145
- package/scripts/wave-status.sh +0 -379
- package/scripts/wave-watch.sh +0 -231
- package/services/README.md +0 -17
- package/services/openxml-validator/Dockerfile +0 -29
- package/services/openxml-validator/OpenXmlValidator.Api.csproj +0 -12
- package/services/openxml-validator/Program.cs +0 -436
- package/services/openxml-validator/README.md +0 -152
- package/services/openxml-validator/railway.json +0 -16
- package/services/react-word-editor/.tmp-a4/src/api/public-types.ts +0 -318
- package/services/react-word-editor/.tmp-a4/src/ui/WordReviewEditor.tsx +0 -1302
- package/services/react-word-editor/.tmp-a4/src/ui/editor-surface/editor-surface.tsx +0 -546
- package/services/react-word-editor/.tmp-a4/test/ui/word-review-editor.test.tsx +0 -146
- package/services/react-word-editor/.tmp-a4-build/src/api/public-types.js +0 -2
- package/services/react-word-editor/.tmp-a4-build/src/ui/WordReviewEditor.js +0 -818
- package/services/react-word-editor/.tmp-a4-build/src/ui/editor-surface/editor-surface.js +0 -229
- package/services/react-word-editor/.tmp-a4-build/test/ui/word-review-editor.test.js +0 -121
- package/services/react-word-editor/.tmp-wave-4-a3-tsconfig.json +0 -21
- package/services/react-word-editor/.tmp-wave-4-a3-tsconfig.tsbuildinfo +0 -1
- package/services/react-word-editor/Dockerfile +0 -26
- package/services/react-word-editor/README.md +0 -254
- package/services/react-word-editor/app/api/certification/route.ts +0 -79
- package/services/react-word-editor/app/api/demo-sessions/route.ts +0 -109
- package/services/react-word-editor/app/api/deploy-health/route.ts +0 -23
- package/services/react-word-editor/app/api/exports/[exportId]/route.ts +0 -34
- package/services/react-word-editor/app/api/exports/route.ts +0 -81
- package/services/react-word-editor/app/api/fixtures/[fixtureId]/run/route.ts +0 -100
- package/services/react-word-editor/app/api/health/route.ts +0 -70
- package/services/react-word-editor/app/api/runs/[runId]/route.ts +0 -36
- package/services/react-word-editor/app/api/scenarios/[scenarioId]/run/route.ts +0 -85
- package/services/react-word-editor/app/api/sessions/[sessionId]/route.ts +0 -199
- package/services/react-word-editor/app/api/sessions/[sessionId]/source/route.ts +0 -45
- package/services/react-word-editor/app/api/uploads/route.ts +0 -70
- package/services/react-word-editor/app/api/validate/route.ts +0 -310
- package/services/react-word-editor/app/certification/[runId]/page.tsx +0 -14
- package/services/react-word-editor/app/certification/page.tsx +0 -32
- package/services/react-word-editor/app/dashboard/page.tsx +0 -7
- package/services/react-word-editor/app/demo/page.tsx +0 -30
- package/services/react-word-editor/app/demo/prototype-client.tsx +0 -1080
- package/services/react-word-editor/app/editor/[sessionId]/page.tsx +0 -33
- package/services/react-word-editor/app/fixtures/page.tsx +0 -7
- package/services/react-word-editor/app/globals.css +0 -121
- package/services/react-word-editor/app/layout.tsx +0 -32
- package/services/react-word-editor/app/page.tsx +0 -30
- package/services/react-word-editor/app/runs/[runId]/page.tsx +0 -34
- package/services/react-word-editor/app/wave-10-word-review/page.tsx +0 -7
- package/services/react-word-editor/components/harness-control-bar.tsx +0 -289
- package/services/react-word-editor/components/harness-editor-session-client.tsx +0 -1214
- package/services/react-word-editor/components/harness-workspace-page.tsx +0 -715
- package/services/react-word-editor/components/reduced-motion-toggle.tsx +0 -79
- package/services/react-word-editor/components/workspace-certification-panel.tsx +0 -307
- package/services/react-word-editor/lib/certification-bundle.ts +0 -796
- package/services/react-word-editor/lib/certification-store.ts +0 -661
- package/services/react-word-editor/lib/demo-fixtures.test.mjs +0 -195
- package/services/react-word-editor/lib/demo-fixtures.ts +0 -1519
- package/services/react-word-editor/lib/editor-session-summary.test.mjs +0 -68
- package/services/react-word-editor/lib/editor-session-summary.ts +0 -14
- package/services/react-word-editor/lib/editor-session.ts +0 -228
- package/services/react-word-editor/lib/exports-route.test.mjs +0 -32
- package/services/react-word-editor/lib/harness-client.ts +0 -347
- package/services/react-word-editor/lib/harness-config.json +0 -30
- package/services/react-word-editor/lib/harness-config.test.mjs +0 -31
- package/services/react-word-editor/lib/harness-config.ts +0 -21
- package/services/react-word-editor/lib/harness-editor-datastore.test.mjs +0 -220
- package/services/react-word-editor/lib/harness-editor-datastore.ts +0 -161
- package/services/react-word-editor/lib/private-mode.test.mjs +0 -42
- package/services/react-word-editor/lib/private-mode.ts +0 -61
- package/services/react-word-editor/lib/regression-report.test.mjs +0 -352
- package/services/react-word-editor/lib/regression-report.ts +0 -896
- package/services/react-word-editor/lib/run-artifacts.ts +0 -934
- package/services/react-word-editor/lib/run-history.ts +0 -755
- package/services/react-word-editor/lib/scenario-artifacts.test.mjs +0 -41
- package/services/react-word-editor/lib/scenario-artifacts.ts +0 -44
- package/services/react-word-editor/lib/storage.ts +0 -953
- package/services/react-word-editor/lib/validator-client.test.mjs +0 -54
- package/services/react-word-editor/lib/validator-client.ts +0 -95
- package/services/react-word-editor/lib/workspace-navigation.ts +0 -79
- package/services/react-word-editor/middleware.ts +0 -35
- package/services/react-word-editor/next-env.d.ts +0 -6
- package/services/react-word-editor/next.config.mjs +0 -15
- package/services/react-word-editor/package.json +0 -38
- package/services/react-word-editor/postcss.config.mjs +0 -8
- package/services/react-word-editor/railway.json +0 -21
- package/services/react-word-editor/scripts/wave-10-certification.mjs +0 -101
- package/services/react-word-editor/scripts/wave-9-live-usability-pilot.mjs +0 -911
- package/services/react-word-editor/tsconfig.json +0 -39
- package/services/react-word-editor/tsconfig.tsbuildinfo +0 -1
- package/skills/README.md +0 -48
- package/skills/domain-docx-compatibility/SKILL.md +0 -44
- package/skills/domain-docx-compatibility/skill.json +0 -19
- package/skills/domain-editor-architecture/SKILL.md +0 -49
- package/skills/domain-editor-architecture/skill.json +0 -19
- package/skills/domain-legal-review/SKILL.md +0 -39
- package/skills/domain-legal-review/skill.json +0 -19
- package/skills/provider-aws/SKILL.md +0 -117
- package/skills/provider-aws/adapters/claude.md +0 -1
- package/skills/provider-aws/adapters/codex.md +0 -1
- package/skills/provider-aws/references/service-verification.md +0 -39
- package/skills/provider-aws/skill.json +0 -54
- package/skills/provider-custom-deploy/SKILL.md +0 -64
- package/skills/provider-custom-deploy/skill.json +0 -50
- package/skills/provider-docker-compose/SKILL.md +0 -96
- package/skills/provider-docker-compose/adapters/local.md +0 -1
- package/skills/provider-docker-compose/skill.json +0 -53
- package/skills/provider-github-release/SKILL.md +0 -121
- package/skills/provider-github-release/adapters/claude.md +0 -1
- package/skills/provider-github-release/adapters/codex.md +0 -1
- package/skills/provider-github-release/skill.json +0 -55
- package/skills/provider-kubernetes/SKILL.md +0 -143
- package/skills/provider-kubernetes/adapters/claude.md +0 -1
- package/skills/provider-kubernetes/adapters/codex.md +0 -1
- package/skills/provider-kubernetes/references/kubectl-patterns.md +0 -58
- package/skills/provider-kubernetes/skill.json +0 -52
- package/skills/provider-railway/SKILL.md +0 -123
- package/skills/provider-railway/adapters/claude.md +0 -1
- package/skills/provider-railway/adapters/codex.md +0 -1
- package/skills/provider-railway/adapters/local.md +0 -1
- package/skills/provider-railway/adapters/opencode.md +0 -1
- package/skills/provider-railway/references/verification-commands.md +0 -39
- package/skills/provider-railway/skill.json +0 -71
- package/skills/provider-ssh-manual/SKILL.md +0 -97
- package/skills/provider-ssh-manual/skill.json +0 -54
- package/skills/repo-coding-rules/SKILL.md +0 -55
- package/skills/repo-coding-rules/skill.json +0 -34
- package/skills/role-cont-eval/SKILL.md +0 -91
- package/skills/role-cont-eval/adapters/codex.md +0 -1
- package/skills/role-cont-eval/skill.json +0 -36
- package/skills/role-cont-qa/SKILL.md +0 -100
- package/skills/role-cont-qa/adapters/claude.md +0 -1
- package/skills/role-cont-qa/skill.json +0 -36
- package/skills/role-deploy/SKILL.md +0 -97
- package/skills/role-deploy/skill.json +0 -36
- package/skills/role-design/SKILL.md +0 -50
- package/skills/role-design/skill.json +0 -36
- package/skills/role-documentation/SKILL.md +0 -76
- package/skills/role-documentation/skill.json +0 -36
- package/skills/role-implementation/SKILL.md +0 -45
- package/skills/role-implementation/skill.json +0 -36
- package/skills/role-infra/SKILL.md +0 -81
- package/skills/role-infra/skill.json +0 -36
- package/skills/role-integration/SKILL.md +0 -91
- package/skills/role-integration/skill.json +0 -36
- package/skills/role-planner/SKILL.md +0 -39
- package/skills/role-planner/skill.json +0 -21
- package/skills/role-research/SKILL.md +0 -65
- package/skills/role-research/skill.json +0 -36
- package/skills/role-security/SKILL.md +0 -60
- package/skills/role-security/skill.json +0 -36
- package/skills/runtime-claude/SKILL.md +0 -66
- package/skills/runtime-claude/skill.json +0 -36
- package/skills/runtime-codex/SKILL.md +0 -58
- package/skills/runtime-codex/skill.json +0 -36
- package/skills/runtime-local/SKILL.md +0 -46
- package/skills/runtime-local/skill.json +0 -36
- package/skills/runtime-opencode/SKILL.md +0 -58
- package/skills/runtime-opencode/skill.json +0 -36
- package/skills/signal-hygiene/SKILL.md +0 -51
- package/skills/signal-hygiene/skill.json +0 -20
- package/skills/tui-design/SKILL.md +0 -77
- package/skills/tui-design/references/tui-design.md +0 -259
- package/skills/tui-design/skill.json +0 -36
- package/skills/wave-core/SKILL.md +0 -141
- package/skills/wave-core/references/marker-syntax.md +0 -70
- package/skills/wave-core/skill.json +0 -35
- package/test/README.md +0 -16
- package/test/core/formatting-commands.test.ts +0 -285
- package/test/core/image-commands.test.ts +0 -298
- package/test/core/mapping.test.ts +0 -186
- package/test/core/text-commands.test.ts +0 -176
- package/test/fixtures/docx/F01-basic-contract.docx +0 -0
- package/test/fixtures/docx/F01-basic-contract.md +0 -33
- package/test/fixtures/docx/F02-headings-styles.docx +0 -0
- package/test/fixtures/docx/F02-headings-styles.md +0 -33
- package/test/fixtures/docx/F03-legal-outline-numbering.docx +0 -0
- package/test/fixtures/docx/F03-legal-outline-numbering.md +0 -34
- package/test/fixtures/docx/F04-restart-numbering-schedules.docx +0 -0
- package/test/fixtures/docx/F04-restart-numbering-schedules.md +0 -33
- package/test/fixtures/docx/F05-table-heavy-agreement.docx +0 -0
- package/test/fixtures/docx/F05-table-heavy-agreement.md +0 -34
- package/test/fixtures/docx/F06-merged-cells-signature-table.docx +0 -0
- package/test/fixtures/docx/F06-merged-cells-signature-table.md +0 -34
- package/test/fixtures/docx/F07-inline-images-exhibit.docx +0 -0
- package/test/fixtures/docx/F07-inline-images-exhibit.md +0 -34
- package/test/fixtures/docx/F08-hyperlinks.docx +0 -0
- package/test/fixtures/docx/F08-hyperlinks.md +0 -33
- package/test/fixtures/docx/F09-comments-single-paragraph.docx +0 -0
- package/test/fixtures/docx/F09-comments-single-paragraph.md +0 -33
- package/test/fixtures/docx/F10-threaded-comments-resolve.docx +0 -0
- package/test/fixtures/docx/F10-threaded-comments-resolve.md +0 -33
- package/test/fixtures/docx/F11-redlines-basic.docx +0 -0
- package/test/fixtures/docx/F11-redlines-basic.md +0 -33
- package/test/fixtures/docx/F12-redlines-paragraph-joins-splits.docx +0 -0
- package/test/fixtures/docx/F12-redlines-paragraph-joins-splits.md +0 -33
- package/test/fixtures/docx/F13-comments-on-deleted-text.docx +0 -0
- package/test/fixtures/docx/F13-comments-on-deleted-text.md +0 -33
- package/test/fixtures/docx/F14-revisions-in-tables-and-lists.docx +0 -0
- package/test/fixtures/docx/F14-revisions-in-tables-and-lists.md +0 -33
- package/test/fixtures/docx/F15-sections-headers-footers.docx +0 -0
- package/test/fixtures/docx/F15-sections-headers-footers.md +0 -33
- package/test/fixtures/docx/F16-footnotes-endnotes.docx +0 -0
- package/test/fixtures/docx/F16-footnotes-endnotes.md +0 -33
- package/test/fixtures/docx/F17-fields-and-toc.docx +0 -0
- package/test/fixtures/docx/F17-fields-and-toc.md +0 -33
- package/test/fixtures/docx/F18-content-controls-template.docx +0 -0
- package/test/fixtures/docx/F18-content-controls-template.md +0 -33
- package/test/fixtures/docx/F19-custom-xml-doc-assembly.docx +0 -0
- package/test/fixtures/docx/F19-custom-xml-doc-assembly.md +0 -35
- package/test/fixtures/docx/F20-unknown-ooxml-and-alternatecontent.docx +0 -0
- package/test/fixtures/docx/F20-unknown-ooxml-and-alternatecontent.md +0 -33
- package/test/fixtures/docx/F21-malformed-broken-docx.docx +0 -0
- package/test/fixtures/docx/F21-malformed-broken-docx.md +0 -33
- package/test/fixtures/docx/README.md +0 -74
- package/test/fixtures/docx/certification-manifest.json +0 -104
- package/test/fixtures/docx/fixtures.manifest.json +0 -196
- package/test/fixtures/encrypted-docx/README.md +0 -27
- package/test/fixtures/encrypted-docx/certification-manifest.json +0 -9
- package/test/fixtures/encrypted-docx/fixtures.manifest.json +0 -47
- package/test/fixtures/scenarios/docx/README.md +0 -25
- package/test/fixtures/scenarios/docx/S01-sow-template.docx +0 -0
- package/test/fixtures/scenarios/docx/S01-sow-template.md +0 -30
- package/test/fixtures/scenarios/docx/S02-bw-partner-user-licence-agreement-redlines.docx +0 -0
- package/test/fixtures/scenarios/docx/S02-bw-partner-user-licence-agreement-redlines.md +0 -32
- package/test/fixtures/scenarios/docx/scenario-manifest.json +0 -53
- package/test/formats/xlsx/io/xlsx-import.test.ts +0 -766
- package/test/formats/xlsx/model/workbook.test.ts +0 -669
- package/test/helpers/dom-setup.ts +0 -124
- package/test/io/comment-roundtrip.test.ts +0 -272
- package/test/io/complex-content-roundtrip.test.ts +0 -632
- package/test/io/docx-compatibility-regression.test.ts +0 -199
- package/test/io/docx-session.test.ts +0 -1495
- package/test/io/footnotes-roundtrip.test.ts +0 -318
- package/test/io/headers-footers-roundtrip.test.ts +0 -547
- package/test/io/numbering-roundtrip.test.ts +0 -234
- package/test/io/package-reader.test.ts +0 -199
- package/test/io/paragraph-properties-roundtrip.test.ts +0 -129
- package/test/io/preserved-package-roundtrip.test.ts +0 -365
- package/test/io/property-completeness.test.ts +0 -292
- package/test/io/revision-roundtrip.test.ts +0 -347
- package/test/io/structural-blocks.test.ts +0 -202
- package/test/io/table-media-roundtrip.test.ts +0 -448
- package/test/io/table-properties-roundtrip.test.ts +0 -569
- package/test/io/table-roundtrip.test.ts +0 -302
- package/test/io/text-roundtrip.test.ts +0 -344
- package/test/model/canonical-document.test.ts +0 -285
- package/test/preservation/opaque-fragment-store.test.ts +0 -121
- package/test/preservation/package-preservation.test.ts +0 -395
- package/test/preservation/store.test.ts +0 -84
- package/test/review/comment-remapping.test.ts +0 -220
- package/test/review/comment-store.test.ts +0 -180
- package/test/review/move-revisions.test.ts +0 -143
- package/test/review/property-change-revisions.test.ts +0 -225
- package/test/review/revision-actions.test.ts +0 -330
- package/test/review/revision-store.test.ts +0 -193
- package/test/runtime/session-capabilities.test.ts +0 -260
- package/test/runtime/table-commands.test.ts +0 -356
- package/test/runtime/table-schema.test.ts +0 -221
- package/test/runtime/tracked-changes-toggle.test.ts +0 -107
- package/test/ui/comment-review-surface.test.tsx +0 -114
- package/test/ui/reduced-motion-toggle.test.tsx +0 -137
- package/test/ui/word-review-editor.imported-scenarios.test.tsx +0 -169
- package/test/ui/word-review-editor.interaction.test.tsx +0 -1198
- package/test/ui/word-review-editor.test.js +0 -188
- package/test/ui/word-review-editor.test.tsx +0 -280
- package/test/ui-tailwind/search-plugin.test.ts +0 -286
- package/test/validation/compatibility-engine.test.ts +0 -336
- package/test/validation/compatibility-report.test.ts +0 -189
- package/test/validation/low-priority-word-surfaces.test.ts +0 -282
- package/test/validation/malformed-doc.test.ts +0 -113
- package/test-results/.last-run.json +0 -4
- package/wave.config.json +0 -406
|
@@ -1,2317 +0,0 @@
|
|
|
1
|
-
# Canonical Document Schema Specification for a React-based Word-compatible Editor
|
|
2
|
-
|
|
3
|
-
## Schema principles and compatibility objectives
|
|
4
|
-
|
|
5
|
-
### Compatibility contract
|
|
6
|
-
|
|
7
|
-
This specification defines a **Canonical Document Schema (CDS)** that is the **single source of truth** for:
|
|
8
|
-
|
|
9
|
-
- structured editing in a React-based editor,
|
|
10
|
-
- comments and anchored annotations,
|
|
11
|
-
- tracked changes (revisions),
|
|
12
|
-
- `.docx` (OOXML WordprocessingML) import/export,
|
|
13
|
-
- validation and repair,
|
|
14
|
-
- loss-minimized preservation of unsupported OOXML.
|
|
15
|
-
|
|
16
|
-
The `.docx` container is an **Open Packaging Conventions (OPC)** ZIP package composed of **parts** (XML and binary) connected by **relationships**. citeturn0search2 The CDS must therefore preserve both (a) **document semantics** and (b) sufficient **package-level information** to reconstruct a Word-openable `.docx`.
|
|
17
|
-
|
|
18
|
-
### Separation of content vs. annotations
|
|
19
|
-
|
|
20
|
-
**Rule P1 (Content purity):** The CDS **content tree** contains only structural content nodes and explicit “opaque/preserved content placeholders.” It **does not** embed comments or revision wrappers as structural nodes.
|
|
21
|
-
|
|
22
|
-
- **Comments** and **revisions** are stored in dedicated top-level stores referencing **ranges/anchors** in the content coordinate space.
|
|
23
|
-
- Export materializes WordprocessingML comment/revision markup from these stores (e.g., comment anchors/range markers, revision containers), rather than storing them inline in the canonical content.
|
|
24
|
-
|
|
25
|
-
This aligns with how WordprocessingML treats paragraph content as including properties, run content, and **annotations (comments/revisions/bookmarks)** as separate markup within the paragraph stream. citeturn3search0turn11search19
|
|
26
|
-
|
|
27
|
-
### Immutability vs. mutability model
|
|
28
|
-
|
|
29
|
-
**Rule P2 (Immutable state, transactional edits):**
|
|
30
|
-
|
|
31
|
-
- The editor operates on an **immutable** `CanonicalDocument` value.
|
|
32
|
-
- Updates occur via **Transactions** consisting of **Steps**.
|
|
33
|
-
- Applying a transaction yields a new document with structural sharing permitted at implementation level.
|
|
34
|
-
|
|
35
|
-
This mirrors well-established editor-engine practice: applying atomic steps yields a new document, and each step provides a position mapping. citeturn6search0turn9search4
|
|
36
|
-
|
|
37
|
-
### Deterministic structure (canonicalization)
|
|
38
|
-
|
|
39
|
-
**Rule P3 (Canonical JSON):** For any in-memory CDS instance, serialization must be deterministic:
|
|
40
|
-
|
|
41
|
-
- Object keys are emitted in a stable order (defined below).
|
|
42
|
-
- Arrays with semantic ordering (children, marks, steps) preserve order.
|
|
43
|
-
- Marks are canonical-sorted (defined below).
|
|
44
|
-
- Normalization eliminates representational ambiguity (no empty text nodes, merged adjacent text nodes where allowed, etc.).
|
|
45
|
-
|
|
46
|
-
### Compatibility-first design
|
|
47
|
-
|
|
48
|
-
**Rule P4 (Word correctness > editor convenience):** When a choice conflicts between “nice internal modeling” and “Word-openable export,” the schema and invariants prioritize generating OOXML that:
|
|
49
|
-
|
|
50
|
-
- opens in recent Word without repair warnings,
|
|
51
|
-
- preserves required stories/parts and relationships,
|
|
52
|
-
- respects OOXML constraints for comments/revisions/sections/tables.
|
|
53
|
-
|
|
54
|
-
### Lossless round-trip requirements
|
|
55
|
-
|
|
56
|
-
**Rule P5 (Round-trip tiers):** The system distinguishes three fidelity tiers:
|
|
57
|
-
|
|
58
|
-
- **Tier A (Lossless):** Supported CDS features round-trip `.docx → CDS → .docx → Word` with no semantic loss.
|
|
59
|
-
- **Tier B (Preserved opaque):** Unsupported OOXML is preserved either as untouched parts or as opaque fragments re-injected at export, provided the user did not structurally edit inside those fragments.
|
|
60
|
-
- **Tier C (Degraded with diagnostics):** If edits invalidate preservation reattachment, content may be dropped or flattened, but the system must emit **deterministic diagnostics** describing the loss.
|
|
61
|
-
|
|
62
|
-
Markup Compatibility constructs like `mc:AlternateContent` are explicitly designed for forward/feature compatibility; they must be preserved in Tier B when unsupported. citeturn2search3turn2search7
|
|
63
|
-
|
|
64
|
-
### Mapping stability under edits
|
|
65
|
-
|
|
66
|
-
**Rule P6 (Stable coordinate space):** All anchors (comments, revisions, preservation bindings) use the **same global position space** (defined below). Every transaction produces a **Mapping** allowing deterministic remapping of:
|
|
67
|
-
|
|
68
|
-
- selections,
|
|
69
|
-
- comment anchors,
|
|
70
|
-
- revision target ranges,
|
|
71
|
-
- preservation bindings.
|
|
72
|
-
|
|
73
|
-
This is required because `.docx` comment anchors and revision markup are location-based in the content stream (e.g., comment range start/end markers link to a comment id). citeturn0search8turn0search4
|
|
74
|
-
|
|
75
|
-
## Canonical document envelope and shared types
|
|
76
|
-
|
|
77
|
-
### Fundamental scalar types
|
|
78
|
-
|
|
79
|
-
All types are expressed in TypeScript-style interfaces.
|
|
80
|
-
|
|
81
|
-
```ts
|
|
82
|
-
// Scalars
|
|
83
|
-
export type CDS_SchemaVersion = "cds/1.0.0";
|
|
84
|
-
|
|
85
|
-
// Identifiers
|
|
86
|
-
export type Id = string; // non-empty, UTF-8 JSON string
|
|
87
|
-
export type UUID = string; // RFC 4122 textual form (implementation must validate) citeturn12search0
|
|
88
|
-
export type PartName = string; // OPC part name, e.g. "/word/document.xml"
|
|
89
|
-
export type RelationshipId = string; // e.g. "rId10"
|
|
90
|
-
export type ISO8601DateTime = string; // e.g. "2026-03-25T10:15:30.000Z"
|
|
91
|
-
export type HexColorRGB = string; // "RRGGBB" uppercase
|
|
92
|
-
export type Base64 = string;
|
|
93
|
-
|
|
94
|
-
// Numeric units aligned with WordprocessingML conventions
|
|
95
|
-
export type Twips = number; // integer, 1/20 pt
|
|
96
|
-
export type HalfPoints = number; // integer, 1/2 pt (e.g., w:sz) citeturn8search2turn8search14
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
### Top-level document model
|
|
100
|
-
|
|
101
|
-
#### CanonicalDocument interface
|
|
102
|
-
|
|
103
|
-
```ts
|
|
104
|
-
export interface CanonicalDocument {
|
|
105
|
-
schemaVersion: CDS_SchemaVersion;
|
|
106
|
-
|
|
107
|
-
// Identity
|
|
108
|
-
docId: UUID;
|
|
109
|
-
|
|
110
|
-
// Lifecycle metadata (system-managed)
|
|
111
|
-
createdAt: ISO8601DateTime;
|
|
112
|
-
updatedAt: ISO8601DateTime;
|
|
113
|
-
|
|
114
|
-
// Document-wide metadata (user/authored + imported)
|
|
115
|
-
metadata: DocumentMetadata;
|
|
116
|
-
|
|
117
|
-
// Main editable story (Word main document body)
|
|
118
|
-
content: DocNode;
|
|
119
|
-
|
|
120
|
-
// Style and numbering catalogs (editable subset + preserved extras)
|
|
121
|
-
styles: StylesCatalog;
|
|
122
|
-
numbering: NumberingCatalog;
|
|
123
|
-
|
|
124
|
-
// Media registry
|
|
125
|
-
media: MediaCatalog;
|
|
126
|
-
|
|
127
|
-
// Annotations
|
|
128
|
-
comments: CommentStore;
|
|
129
|
-
revisions: RevisionStore;
|
|
130
|
-
|
|
131
|
-
// Preservation and package fidelity
|
|
132
|
-
preservation: PreservationStore;
|
|
133
|
-
|
|
134
|
-
// Validation/repair output and non-fatal issues
|
|
135
|
-
diagnostics: DiagnosticStore;
|
|
136
|
-
}
|
|
137
|
-
```
|
|
138
|
-
|
|
139
|
-
#### Required invariants
|
|
140
|
-
|
|
141
|
-
**Invariant D1:** `schemaVersion` MUST be exactly `"cds/1.0.0"` for this spec.
|
|
142
|
-
|
|
143
|
-
**Invariant D2:** `content.type` MUST be `"doc"`.
|
|
144
|
-
|
|
145
|
-
**Invariant D3:** All IDs in each namespace MUST be unique:
|
|
146
|
-
|
|
147
|
-
- node ids unique across the content tree,
|
|
148
|
-
- `media.items[*].mediaId` unique,
|
|
149
|
-
- `comments.threads[*].threadId` unique,
|
|
150
|
-
- `revisions.items[*].revisionId` unique,
|
|
151
|
-
- `preservation.fragments[*].fragmentId` unique,
|
|
152
|
-
- `preservation.opc.parts[*].partName` unique.
|
|
153
|
-
|
|
154
|
-
**Invariant D4:** `updatedAt` MUST be ≥ `createdAt`.
|
|
155
|
-
|
|
156
|
-
### Document metadata
|
|
157
|
-
|
|
158
|
-
```ts
|
|
159
|
-
export interface DocumentMetadata {
|
|
160
|
-
// Human-facing name
|
|
161
|
-
title?: string;
|
|
162
|
-
|
|
163
|
-
// OOXML core/app/custom properties pass-through
|
|
164
|
-
coreProperties?: Record<string, string>;
|
|
165
|
-
appProperties?: Record<string, string>;
|
|
166
|
-
customProperties?: Record<string, string | number | boolean | ISO8601DateTime>;
|
|
167
|
-
|
|
168
|
-
// Actors used by comments/revisions
|
|
169
|
-
actors: Record<Id, Actor>;
|
|
170
|
-
|
|
171
|
-
// Import provenance (stable across exports unless reimported)
|
|
172
|
-
provenance?: ImportProvenance;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
export interface Actor {
|
|
176
|
-
actorId: Id; // key == actorId
|
|
177
|
-
displayName: string; // non-empty
|
|
178
|
-
email?: string;
|
|
179
|
-
externalId?: string; // optional mapping to identity provider
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
export interface ImportProvenance {
|
|
183
|
-
importedFrom: "docx";
|
|
184
|
-
importedAt: ISO8601DateTime;
|
|
185
|
-
sourceFingerprint: string; // implementation-defined stable hash of original package bytes
|
|
186
|
-
}
|
|
187
|
-
```
|
|
188
|
-
|
|
189
|
-
### Styles catalog
|
|
190
|
-
|
|
191
|
-
WordprocessingML styles are defined in the styles part (`styles.xml`) and applied via `w:pStyle` (paragraph) and `w:rStyle` (character). citeturn4search0turn4search5turn4search8
|
|
192
|
-
|
|
193
|
-
```ts
|
|
194
|
-
export interface StylesCatalog {
|
|
195
|
-
// Defaults extracted from docDefaults (if present)
|
|
196
|
-
defaults: {
|
|
197
|
-
paragraph?: ParagraphStyleProps;
|
|
198
|
-
run?: RunStyleProps;
|
|
199
|
-
};
|
|
200
|
-
|
|
201
|
-
paragraphStyles: Record<string, ParagraphStyleDef>; // key = styleId
|
|
202
|
-
characterStyles: Record<string, CharacterStyleDef>; // key = styleId
|
|
203
|
-
tableStyles: Record<string, TableStyleDef>; // key = styleId
|
|
204
|
-
|
|
205
|
-
// Unmodeled style XML preserved losslessly
|
|
206
|
-
ooxmlExtras?: {
|
|
207
|
-
rawStylesXmlBase64?: Base64; // original /word/styles.xml (optional)
|
|
208
|
-
};
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
export interface StyleDefBase {
|
|
212
|
-
styleId: string; // Word styleId
|
|
213
|
-
name?: string;
|
|
214
|
-
basedOn?: string; // styleId
|
|
215
|
-
next?: string; // styleId
|
|
216
|
-
linked?: string; // styleId (linked paragraph/character style)
|
|
217
|
-
isDefault?: boolean;
|
|
218
|
-
isCustom?: boolean;
|
|
219
|
-
|
|
220
|
-
// Preservation hook for unknown style attributes/elements
|
|
221
|
-
ooxmlUnknown?: PreservedXmlRef;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
export interface ParagraphStyleDef extends StyleDefBase {
|
|
225
|
-
type: "paragraph";
|
|
226
|
-
pPr?: ParagraphStyleProps;
|
|
227
|
-
rPr?: RunStyleProps; // run defaults for this paragraph style
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
export interface CharacterStyleDef extends StyleDefBase {
|
|
231
|
-
type: "character";
|
|
232
|
-
rPr?: RunStyleProps;
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
export interface TableStyleDef extends StyleDefBase {
|
|
236
|
-
type: "table";
|
|
237
|
-
tblPr?: TableProps;
|
|
238
|
-
trPr?: TableRowProps;
|
|
239
|
-
tcPr?: TableCellProps;
|
|
240
|
-
}
|
|
241
|
-
```
|
|
242
|
-
|
|
243
|
-
**Style precedence model (required):** When computing effective formatting, apply in this order (lowest to highest), consistent with OOXML style hierarchy: document defaults → table styles → numbering styles → paragraph styles (`w:pStyle`) → character styles (`w:rStyle`) → direct formatting. citeturn4search0turn4search5turn11search20
|
|
244
|
-
|
|
245
|
-
### Numbering catalog
|
|
246
|
-
|
|
247
|
-
Numbering in WordprocessingML is referenced from paragraphs via `w:numPr`, including `w:numId` and `w:ilvl`. citeturn4search6turn4search17turn4search2
|
|
248
|
-
|
|
249
|
-
```ts
|
|
250
|
-
export interface NumberingCatalog {
|
|
251
|
-
abstractNums: Record<string, AbstractNumDef>; // key = abstractNumId
|
|
252
|
-
nums: Record<string, NumInstanceDef>; // key = numId
|
|
253
|
-
|
|
254
|
-
ooxmlExtras?: {
|
|
255
|
-
rawNumberingXmlBase64?: Base64; // original /word/numbering.xml (optional)
|
|
256
|
-
};
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
export interface AbstractNumDef {
|
|
260
|
-
abstractNumId: string;
|
|
261
|
-
levels: Record<number, NumberingLevelDef>; // level 0..8
|
|
262
|
-
ooxmlUnknown?: PreservedXmlRef;
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
export interface NumInstanceDef {
|
|
266
|
-
numId: string;
|
|
267
|
-
abstractNumId: string;
|
|
268
|
-
levelOverrides?: Record<number, NumberingLevelDef>;
|
|
269
|
-
ooxmlUnknown?: PreservedXmlRef;
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
export interface NumberingLevelDef {
|
|
273
|
-
level: number; // 0..8
|
|
274
|
-
numFmt: "bullet" | "decimal" | "lowerLetter" | "upperLetter" | "lowerRoman" | "upperRoman" | "other";
|
|
275
|
-
lvlText?: string; // e.g. "%1."
|
|
276
|
-
start?: number; // default start value
|
|
277
|
-
pPr?: ParagraphStyleProps;
|
|
278
|
-
rPr?: RunStyleProps;
|
|
279
|
-
ooxmlUnknown?: PreservedXmlRef;
|
|
280
|
-
}
|
|
281
|
-
```
|
|
282
|
-
|
|
283
|
-
### Media catalog
|
|
284
|
-
|
|
285
|
-
Images in WordprocessingML are represented as DrawingML objects inside a run (`w:r/w:drawing`), commonly using `wp:inline` (in-line with text). citeturn2search10
|
|
286
|
-
|
|
287
|
-
```ts
|
|
288
|
-
export interface MediaCatalog {
|
|
289
|
-
items: Record<Id, MediaItem>; // key = mediaId
|
|
290
|
-
|
|
291
|
-
// Relationship and part naming policy must be deterministic on export.
|
|
292
|
-
exportPolicy: {
|
|
293
|
-
mediaFolder: "/word/media";
|
|
294
|
-
filenameStrategy: "stableByMediaId";
|
|
295
|
-
relationshipIdStrategy: "stableByOrder";
|
|
296
|
-
};
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
export interface MediaItem {
|
|
300
|
-
mediaId: Id;
|
|
301
|
-
kind: "image";
|
|
302
|
-
mimeType: "image/png" | "image/jpeg" | "image/gif" | "image/webp" | "image/svg+xml";
|
|
303
|
-
sha256: string; // hex lowercase
|
|
304
|
-
bytesBase64?: Base64; // present if the CDS is self-contained; optional if stored externally
|
|
305
|
-
externalUrl?: string; // optional pointer if bytes are not embedded
|
|
306
|
-
|
|
307
|
-
// Sizing metadata (used for initial layout hints)
|
|
308
|
-
pixelWidth?: number;
|
|
309
|
-
pixelHeight?: number;
|
|
310
|
-
dpiX?: number;
|
|
311
|
-
dpiY?: number;
|
|
312
|
-
|
|
313
|
-
// OOXML preservation for DrawingML details not modeled
|
|
314
|
-
ooxmlUnknown?: PreservedXmlRef;
|
|
315
|
-
}
|
|
316
|
-
```
|
|
317
|
-
|
|
318
|
-
### Diagnostics and warnings
|
|
319
|
-
|
|
320
|
-
```ts
|
|
321
|
-
export interface DiagnosticStore {
|
|
322
|
-
items: Diagnostic[];
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
export type DiagnosticSeverity = "info" | "warning" | "error" | "fatal";
|
|
326
|
-
|
|
327
|
-
export interface Diagnostic {
|
|
328
|
-
diagnosticId: UUID;
|
|
329
|
-
severity: DiagnosticSeverity;
|
|
330
|
-
code: string; // stable machine-readable code
|
|
331
|
-
message: string; // human-readable
|
|
332
|
-
createdAt: ISO8601DateTime;
|
|
333
|
-
|
|
334
|
-
// Optional localization of where it happened
|
|
335
|
-
location?: {
|
|
336
|
-
kind: "docRange" | "nodeId" | "partName";
|
|
337
|
-
fromPos?: DocPos;
|
|
338
|
-
toPos?: DocPos;
|
|
339
|
-
nodeId?: Id;
|
|
340
|
-
partName?: PartName;
|
|
341
|
-
};
|
|
342
|
-
|
|
343
|
-
// Repair action (if any)
|
|
344
|
-
repair?: {
|
|
345
|
-
applied: boolean;
|
|
346
|
-
description: string;
|
|
347
|
-
};
|
|
348
|
-
}
|
|
349
|
-
```
|
|
350
|
-
|
|
351
|
-
## Content model: nodes, marks, and attribute systems
|
|
352
|
-
|
|
353
|
-
### Global node model
|
|
354
|
-
|
|
355
|
-
All content is represented as a tree of nodes. Nodes are either **block** or **inline**. Text is inline.
|
|
356
|
-
|
|
357
|
-
```ts
|
|
358
|
-
export type Node =
|
|
359
|
-
| DocNode
|
|
360
|
-
| ParagraphNode
|
|
361
|
-
| HeadingNode
|
|
362
|
-
| BlockquoteNode
|
|
363
|
-
| OrderedListNode
|
|
364
|
-
| BulletListNode
|
|
365
|
-
| ListItemNode
|
|
366
|
-
| TableNode
|
|
367
|
-
| TableRowNode
|
|
368
|
-
| TableCellNode
|
|
369
|
-
| ImageBlockNode
|
|
370
|
-
| HorizontalRuleNode
|
|
371
|
-
| SectionBreakNode
|
|
372
|
-
| OoxmlBlockNode
|
|
373
|
-
| TextNode
|
|
374
|
-
| HardBreakNode
|
|
375
|
-
| HyperlinkNode
|
|
376
|
-
| InlineImageNode
|
|
377
|
-
| AnchorPlaceholderNode
|
|
378
|
-
| OoxmlInlineNode;
|
|
379
|
-
|
|
380
|
-
export interface NodeBase {
|
|
381
|
-
id: Id; // unique within document
|
|
382
|
-
type: string; // discriminator
|
|
383
|
-
}
|
|
384
|
-
```
|
|
385
|
-
|
|
386
|
-
### Block node specifications
|
|
387
|
-
|
|
388
|
-
WordprocessingML block-level content in the main body is primarily paragraphs (`w:p`) and tables (`w:tbl`). citeturn3search0turn2search5
|
|
389
|
-
|
|
390
|
-
Each block node below includes: attributes, allowed children, constraints, WordprocessingML mapping, and editing invariants.
|
|
391
|
-
|
|
392
|
-
#### Document root
|
|
393
|
-
|
|
394
|
-
```ts
|
|
395
|
-
export interface DocNode extends NodeBase {
|
|
396
|
-
type: "doc";
|
|
397
|
-
children: BlockNode[];
|
|
398
|
-
|
|
399
|
-
attrs: {
|
|
400
|
-
// Document-wide section defaults (last section properties in Word)
|
|
401
|
-
defaultSection?: SectionPropsRef;
|
|
402
|
-
|
|
403
|
-
// Track changes flag (mirrors settings.xml w:trackRevisions)
|
|
404
|
-
trackRevisionsDefault?: boolean;
|
|
405
|
-
};
|
|
406
|
-
}
|
|
407
|
-
export type BlockNode =
|
|
408
|
-
| ParagraphNode
|
|
409
|
-
| HeadingNode
|
|
410
|
-
| BlockquoteNode
|
|
411
|
-
| OrderedListNode
|
|
412
|
-
| BulletListNode
|
|
413
|
-
| TableNode
|
|
414
|
-
| ImageBlockNode
|
|
415
|
-
| HorizontalRuleNode
|
|
416
|
-
| SectionBreakNode
|
|
417
|
-
| OoxmlBlockNode;
|
|
418
|
-
```
|
|
419
|
-
|
|
420
|
-
**Allowed children:** `BlockNode[]` (at least one block is allowed but not required).
|
|
421
|
-
|
|
422
|
-
**Constraints:** `doc.children` MAY be empty.
|
|
423
|
-
|
|
424
|
-
**WordprocessingML mapping:** `doc` maps to main document part `w:document/w:body` story. WordprocessingML is composed of “stories,” with the main document being required. citeturn3search4
|
|
425
|
-
|
|
426
|
-
**Editing invariants:** The editor MUST maintain a valid block sequence; normalization rules below repair invalid blocks.
|
|
427
|
-
|
|
428
|
-
#### Paragraph
|
|
429
|
-
|
|
430
|
-
```ts
|
|
431
|
-
export interface ParagraphNode extends NodeBase {
|
|
432
|
-
type: "paragraph";
|
|
433
|
-
children: InlineNode[];
|
|
434
|
-
|
|
435
|
-
attrs: ParagraphAttrs;
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
export interface ParagraphAttrs {
|
|
439
|
-
styleId?: string; // maps to w:pPr/w:pStyle citeturn4search0
|
|
440
|
-
alignment?: ParagraphAlignment; // maps to w:pPr/w:jc citeturn5search0
|
|
441
|
-
indent?: ParagraphIndent; // maps to w:pPr/w:ind citeturn5search1
|
|
442
|
-
spacing?: ParagraphSpacing; // maps to w:pPr/w:spacing citeturn5search2
|
|
443
|
-
numbering?: ParagraphNumbering; // maps to w:pPr/w:numPr citeturn4search6
|
|
444
|
-
|
|
445
|
-
// Paragraph mark formatting (Word stores paragraph mark run props)
|
|
446
|
-
paragraphMarkRunStyle?: RunStyleProps; // corresponds to w:pPr/w:rPr (paragraph mark) citeturn4search1turn7search12
|
|
447
|
-
|
|
448
|
-
// Preservation of unmodeled pPr sub-elements/attrs
|
|
449
|
-
ooxmlUnknownPPr?: PreservedXmlRef;
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
export type ParagraphAlignment = "left" | "center" | "right" | "both" | "start" | "end";
|
|
453
|
-
export interface ParagraphIndent {
|
|
454
|
-
leftTwips?: Twips;
|
|
455
|
-
rightTwips?: Twips;
|
|
456
|
-
firstLineTwips?: Twips;
|
|
457
|
-
hangingTwips?: Twips;
|
|
458
|
-
}
|
|
459
|
-
export interface ParagraphSpacing {
|
|
460
|
-
beforeTwips?: Twips;
|
|
461
|
-
afterTwips?: Twips;
|
|
462
|
-
line?: { rule: "auto" | "atLeast" | "exact"; valueTwips?: Twips; value240thLines?: number };
|
|
463
|
-
beforeAutoSpacing?: boolean;
|
|
464
|
-
afterAutoSpacing?: boolean;
|
|
465
|
-
}
|
|
466
|
-
export interface ParagraphNumbering {
|
|
467
|
-
numId: string; // numbering instance id (w:numId) citeturn4search17
|
|
468
|
-
ilvl: number; // 0..8 (w:ilvl) citeturn4search2
|
|
469
|
-
}
|
|
470
|
-
```
|
|
471
|
-
|
|
472
|
-
**Allowed children:** `InlineNode[]`.
|
|
473
|
-
|
|
474
|
-
**Constraints:**
|
|
475
|
-
- Paragraph MUST contain at least one inline node after normalization. If empty, insert a single `AnchorPlaceholderNode` (see inline nodes) to keep selection addressable.
|
|
476
|
-
- `attrs.numbering.ilvl` MUST be integer 0..8.
|
|
477
|
-
- If `attrs.numbering` is present, export MUST emit `w:pPr/w:numPr` with both `w:numId` and `w:ilvl`. citeturn4search6turn4search17turn4search2
|
|
478
|
-
|
|
479
|
-
**WordprocessingML mapping:** `paragraph` maps to `w:p` with:
|
|
480
|
-
- paragraph properties in `w:pPr` (alignment `w:jc`, indent `w:ind`, spacing `w:spacing`, numbering `w:numPr`, paragraph style `w:pStyle`). citeturn3search0turn5search0turn5search1turn5search2turn4search0turn4search6
|
|
481
|
-
- run-level content (`w:r`, `w:hyperlink`, etc.) as children. citeturn3search0turn3search1turn1search2
|
|
482
|
-
|
|
483
|
-
**Editing invariants:** Splitting a paragraph MUST produce two valid paragraphs, each normalized to contain at least one inline.
|
|
484
|
-
|
|
485
|
-
#### Heading
|
|
486
|
-
|
|
487
|
-
Headings are represented as a dedicated node to provide stable editor semantics. On export/import, headings map to paragraphs with heading semantics via styleId and/or outline-level conventions.
|
|
488
|
-
|
|
489
|
-
```ts
|
|
490
|
-
export interface HeadingNode extends NodeBase {
|
|
491
|
-
type: "heading";
|
|
492
|
-
children: InlineNode[];
|
|
493
|
-
|
|
494
|
-
attrs: ParagraphAttrs & {
|
|
495
|
-
level: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9;
|
|
496
|
-
};
|
|
497
|
-
}
|
|
498
|
-
```
|
|
499
|
-
|
|
500
|
-
**Allowed children:** `InlineNode[]`.
|
|
501
|
-
|
|
502
|
-
**Constraints:**
|
|
503
|
-
- `level` MUST be 1..9.
|
|
504
|
-
- If `attrs.styleId` is absent at export, exporter MUST map `level` to `Heading1..Heading9` styleId by default (unless the styles catalog defines a different configured mapping).
|
|
505
|
-
- If `attrs.styleId` is present, it MUST be preserved exactly; `level` is an editor semantic and MUST still export a `w:p` (not a special element) because Word stores headings as paragraph formatting (style/properties). citeturn3search0turn4search0
|
|
506
|
-
|
|
507
|
-
**WordprocessingML mapping:** `heading` exports as `w:p` with `w:pPr/w:pStyle` set to the chosen heading style (or preserved). citeturn4search0
|
|
508
|
-
|
|
509
|
-
**Editing invariants:** Converting between `paragraph` and `heading` MUST be a pure attribute/node-type change (content unchanged).
|
|
510
|
-
|
|
511
|
-
#### Blockquote
|
|
512
|
-
|
|
513
|
-
```ts
|
|
514
|
-
export interface BlockquoteNode extends NodeBase {
|
|
515
|
-
type: "blockquote";
|
|
516
|
-
children: BlockNode[];
|
|
517
|
-
|
|
518
|
-
attrs: {
|
|
519
|
-
// export strategy: apply quoteStyleId to contained paragraphs if present
|
|
520
|
-
quoteStyleId?: string;
|
|
521
|
-
// optional indentation applied when quoteStyleId absent
|
|
522
|
-
indentTwips?: Twips;
|
|
523
|
-
|
|
524
|
-
ooxmlUnknown?: PreservedXmlRef;
|
|
525
|
-
};
|
|
526
|
-
}
|
|
527
|
-
```
|
|
528
|
-
|
|
529
|
-
**Allowed children:** `BlockNode[]` (MUST contain at least one block).
|
|
530
|
-
|
|
531
|
-
**Constraints:**
|
|
532
|
-
- Children MUST NOT include `SectionBreakNode` directly. If encountered, normalization hoists `SectionBreakNode` out of the blockquote boundary (see repair rules).
|
|
533
|
-
- If `quoteStyleId` is set, exporter MUST apply it as `w:pStyle` to each contained paragraph/heading unless that child already has an explicit `styleId`. citeturn4search0
|
|
534
|
-
|
|
535
|
-
**WordprocessingML mapping:** WordprocessingML has no native blockquote element; exporter MUST emit a sequence of `w:p`/`w:tbl` content in the body. Blockquote semantics are encoded via paragraph properties (style and/or indentation). citeturn3search0turn5search1turn4search0
|
|
536
|
-
|
|
537
|
-
**Editing invariants:** Blockquote boundaries MUST be stable under paragraph edits inside; splitting/merging at the boundary MUST either expand or shrink blockquote deterministically (defined in normalization).
|
|
538
|
-
|
|
539
|
-
#### Ordered list and bullet list
|
|
540
|
-
|
|
541
|
-
Word uses numbering properties on paragraphs (`w:numPr`) referencing numbering definitions (`numId`, `ilvl`). citeturn4search6turn4search17turn4search2
|
|
542
|
-
|
|
543
|
-
```ts
|
|
544
|
-
export interface OrderedListNode extends NodeBase {
|
|
545
|
-
type: "orderedList";
|
|
546
|
-
children: ListItemNode[];
|
|
547
|
-
|
|
548
|
-
attrs: ListAttrs & {
|
|
549
|
-
kind: "ordered";
|
|
550
|
-
};
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
export interface BulletListNode extends NodeBase {
|
|
554
|
-
type: "bulletList";
|
|
555
|
-
children: ListItemNode[];
|
|
556
|
-
|
|
557
|
-
attrs: ListAttrs & {
|
|
558
|
-
kind: "bullet";
|
|
559
|
-
};
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
export interface ListAttrs {
|
|
563
|
-
// Word numbering instance id. Required for stable round-trip.
|
|
564
|
-
numId: string;
|
|
565
|
-
|
|
566
|
-
// Base level for this list (0..8). Nested lists add to this.
|
|
567
|
-
baseIlvl: number;
|
|
568
|
-
|
|
569
|
-
// Stable list restart semantics
|
|
570
|
-
restart?: {
|
|
571
|
-
atIndex: number; // list item index where restart starts (0-based)
|
|
572
|
-
startValue: number;
|
|
573
|
-
};
|
|
574
|
-
|
|
575
|
-
// Preservation for advanced numbering not modeled
|
|
576
|
-
ooxmlUnknown?: PreservedXmlRef;
|
|
577
|
-
}
|
|
578
|
-
```
|
|
579
|
-
|
|
580
|
-
**Allowed children:** `ListItemNode[]` (MUST contain ≥ 1 item).
|
|
581
|
-
|
|
582
|
-
**Constraints:**
|
|
583
|
-
- `attrs.baseIlvl` MUST be 0..8.
|
|
584
|
-
- Each list item’s first block MUST be a `paragraph` or `heading`.
|
|
585
|
-
- Export MUST produce `w:p` for each list paragraph with `w:numPr/w:numId` = `attrs.numId` and `w:ilvl` = `attrs.baseIlvl + nestingDepth`. citeturn4search6turn4search2turn4search17
|
|
586
|
-
|
|
587
|
-
**WordprocessingML mapping:** A list is serialized as ordinary paragraphs bearing numbering properties. citeturn4search6turn4search17turn4search2
|
|
588
|
-
|
|
589
|
-
**Editing invariants:**
|
|
590
|
-
- Adjacent lists with identical `(kind, numId, baseIlvl)` MUST be merged by normalization.
|
|
591
|
-
- Lifting a list item out MUST adjust numbering levels deterministically (see structural repair).
|
|
592
|
-
|
|
593
|
-
#### List item
|
|
594
|
-
|
|
595
|
-
```ts
|
|
596
|
-
export interface ListItemNode extends NodeBase {
|
|
597
|
-
type: "listItem";
|
|
598
|
-
children: BlockNode[];
|
|
599
|
-
|
|
600
|
-
attrs: {
|
|
601
|
-
// Optional override of ilvl for this item (rare; used for malformed imports)
|
|
602
|
-
ilvlOverride?: number; // 0..8
|
|
603
|
-
// Optional stable identity for Word paragraph rsid retention (preserved, not interpreted)
|
|
604
|
-
ooxmlUnknown?: PreservedXmlRef;
|
|
605
|
-
};
|
|
606
|
-
}
|
|
607
|
-
```
|
|
608
|
-
|
|
609
|
-
**Allowed children:** `BlockNode[]` (MUST contain ≥ 1).
|
|
610
|
-
|
|
611
|
-
**Constraints:**
|
|
612
|
-
- First child MUST be `paragraph` or `heading`.
|
|
613
|
-
- A `listItem` MUST NOT contain a `SectionBreakNode`; normalization hoists it out.
|
|
614
|
-
|
|
615
|
-
**WordprocessingML mapping:** Each `listItem` exports as one or more paragraphs/tables; the first paragraph carries the list numbering properties (`w:numPr`). citeturn4search6turn3search0
|
|
616
|
-
|
|
617
|
-
**Editing invariants:** Adding/removing blocks inside a list item MUST not change list identity; only numbering props on affected paragraphs may change.
|
|
618
|
-
|
|
619
|
-
#### Table
|
|
620
|
-
|
|
621
|
-
Tables in WordprocessingML are represented by `<w:tbl>` with optional grid definitions (`<w:tblGrid>`). citeturn2search5turn4search7turn4search3
|
|
622
|
-
|
|
623
|
-
```ts
|
|
624
|
-
export interface TableNode extends NodeBase {
|
|
625
|
-
type: "table";
|
|
626
|
-
children: TableRowNode[];
|
|
627
|
-
|
|
628
|
-
attrs: TableProps;
|
|
629
|
-
}
|
|
630
|
-
|
|
631
|
-
export interface TableProps {
|
|
632
|
-
styleId?: string; // table styleId (w:tblStyle)
|
|
633
|
-
alignment?: "left" | "center" | "right"; // table alignment conceptually maps to w:jc at tblPr citeturn5search8
|
|
634
|
-
widthTwips?: Twips;
|
|
635
|
-
|
|
636
|
-
// Grid model for stable cell width semantics
|
|
637
|
-
grid?: {
|
|
638
|
-
colWidthsTwips: Twips[]; // length >= 1
|
|
639
|
-
};
|
|
640
|
-
|
|
641
|
-
// Preservation for tblPr/tblLook/tblBorders etc not modeled
|
|
642
|
-
ooxmlUnknownTblPr?: PreservedXmlRef;
|
|
643
|
-
}
|
|
644
|
-
```
|
|
645
|
-
|
|
646
|
-
**Allowed children:** `TableRowNode[]` (MUST contain ≥ 1 row).
|
|
647
|
-
|
|
648
|
-
**Constraints:**
|
|
649
|
-
- If `attrs.grid` present, length MUST equal `max(columns referenced by cells after applying gridSpan)`.
|
|
650
|
-
- Export MUST emit `<w:tbl>` and, if `grid` present, a `<w:tblGrid>` with `<w:gridCol>` entries matching widths. citeturn4search7turn4search3turn4search21
|
|
651
|
-
|
|
652
|
-
**WordprocessingML mapping:** `<w:tbl>` container, `<w:tr>` rows, `<w:tc>` cells. citeturn2search5turn2search9
|
|
653
|
-
|
|
654
|
-
**Editing invariants:** Table cell content edits MUST not alter the table grid unless explicitly performing a column/row operation.
|
|
655
|
-
|
|
656
|
-
#### Table row
|
|
657
|
-
|
|
658
|
-
```ts
|
|
659
|
-
export interface TableRowNode extends NodeBase {
|
|
660
|
-
type: "tableRow";
|
|
661
|
-
children: TableCellNode[];
|
|
662
|
-
|
|
663
|
-
attrs: TableRowProps;
|
|
664
|
-
}
|
|
665
|
-
|
|
666
|
-
export interface TableRowProps {
|
|
667
|
-
isHeader?: boolean;
|
|
668
|
-
heightTwips?: Twips;
|
|
669
|
-
// Tracked row insertion/deletion in Word may be represented separately; preserved here.
|
|
670
|
-
ooxmlUnknownTrPr?: PreservedXmlRef;
|
|
671
|
-
}
|
|
672
|
-
```
|
|
673
|
-
|
|
674
|
-
**Allowed children:** `TableCellNode[]` (MUST contain ≥ 1 cell).
|
|
675
|
-
|
|
676
|
-
**Constraints:** All rows in the same table MUST normalize to consistent column counts after considering `gridSpan`/`vMerge`.
|
|
677
|
-
|
|
678
|
-
**WordprocessingML mapping:** `<w:tr>` with `<w:trPr>`; cells `<w:tc>`. citeturn2search9turn2search5
|
|
679
|
-
|
|
680
|
-
**Editing invariants:** Row operations are structural steps; plain text edits inside cells do not affect row identity.
|
|
681
|
-
|
|
682
|
-
#### Table cell
|
|
683
|
-
|
|
684
|
-
WordprocessingML cell properties are stored in `<w:tcPr>` and override higher-level table/row properties on conflict. citeturn5search7
|
|
685
|
-
|
|
686
|
-
```ts
|
|
687
|
-
export interface TableCellNode extends NodeBase {
|
|
688
|
-
type: "tableCell";
|
|
689
|
-
children: BlockNode[];
|
|
690
|
-
|
|
691
|
-
attrs: TableCellProps;
|
|
692
|
-
}
|
|
693
|
-
|
|
694
|
-
export interface TableCellProps {
|
|
695
|
-
gridSpan?: number; // >= 1, maps to w:gridSpan (colspan concept) citeturn5search15
|
|
696
|
-
vMerge?: "restart" | "continue"; // maps to w:vMerge semantics citeturn5search11
|
|
697
|
-
|
|
698
|
-
widthTwips?: Twips;
|
|
699
|
-
shading?: { fill?: HexColorRGB };
|
|
700
|
-
|
|
701
|
-
// Cell-level padding/borders not modeled must be preserved
|
|
702
|
-
ooxmlUnknownTcPr?: PreservedXmlRef;
|
|
703
|
-
}
|
|
704
|
-
```
|
|
705
|
-
|
|
706
|
-
**Allowed children:** `BlockNode[]` (MUST contain ≥ 1).
|
|
707
|
-
|
|
708
|
-
**Constraints:**
|
|
709
|
-
- First child MUST be a `paragraph` or `heading` after normalization (Word requires block content in cells and commonly at least one paragraph).
|
|
710
|
-
- If `vMerge="continue"`, the cell MUST be vertically merged with the cell above at same column position after normalization.
|
|
711
|
-
|
|
712
|
-
**WordprocessingML mapping:** `<w:tc>` with `<w:tcPr>` including `w:gridSpan`, `w:vMerge`, widths, etc. citeturn5search7turn5search11
|
|
713
|
-
|
|
714
|
-
**Editing invariants:** Deleting the last paragraph in a cell inserts an empty paragraph placeholder.
|
|
715
|
-
|
|
716
|
-
#### Image block
|
|
717
|
-
|
|
718
|
-
```ts
|
|
719
|
-
export interface ImageBlockNode extends NodeBase {
|
|
720
|
-
type: "imageBlock";
|
|
721
|
-
children: []; // leaf block (caption is separate paragraph/node)
|
|
722
|
-
|
|
723
|
-
attrs: {
|
|
724
|
-
mediaId: Id;
|
|
725
|
-
altText?: string;
|
|
726
|
-
|
|
727
|
-
// Layout hints
|
|
728
|
-
widthTwips?: Twips;
|
|
729
|
-
heightTwips?: Twips;
|
|
730
|
-
|
|
731
|
-
// Default export is inline drawing inside its own paragraph
|
|
732
|
-
exportAs: "ownParagraphInlineDrawing";
|
|
733
|
-
|
|
734
|
-
ooxmlUnknown?: PreservedXmlRef;
|
|
735
|
-
};
|
|
736
|
-
}
|
|
737
|
-
```
|
|
738
|
-
|
|
739
|
-
**Allowed children:** none.
|
|
740
|
-
|
|
741
|
-
**Constraints:** `mediaId` MUST exist in `media.items`.
|
|
742
|
-
|
|
743
|
-
**WordprocessingML mapping:** Exports as a paragraph containing a run with a DrawingML `w:drawing` and `wp:inline` referencing the image relationship. `wp:inline` indicates the object is positioned inline with text. citeturn2search10
|
|
744
|
-
|
|
745
|
-
**Editing invariants:** Treated as an atomic block for selection and deletion.
|
|
746
|
-
|
|
747
|
-
#### Horizontal rule
|
|
748
|
-
|
|
749
|
-
```ts
|
|
750
|
-
export interface HorizontalRuleNode extends NodeBase {
|
|
751
|
-
type: "horizontalRule";
|
|
752
|
-
children: [];
|
|
753
|
-
|
|
754
|
-
attrs: {
|
|
755
|
-
// Export strategy uses paragraph borders (not shapes)
|
|
756
|
-
thicknessTwips?: Twips;
|
|
757
|
-
color?: HexColorRGB;
|
|
758
|
-
spacingBeforeTwips?: Twips;
|
|
759
|
-
spacingAfterTwips?: Twips;
|
|
760
|
-
|
|
761
|
-
ooxmlUnknown?: PreservedXmlRef;
|
|
762
|
-
};
|
|
763
|
-
}
|
|
764
|
-
```
|
|
765
|
-
|
|
766
|
-
**Allowed children:** none.
|
|
767
|
-
|
|
768
|
-
**Constraints:** None.
|
|
769
|
-
|
|
770
|
-
**WordprocessingML mapping:** Exports as an empty paragraph with `pPr` specifying border(s) (typically bottom border). Because OOXML does not define a “horizontal rule” element, this uses paragraph properties. citeturn3search0turn4search4
|
|
771
|
-
|
|
772
|
-
**Editing invariants:** Atomic block.
|
|
773
|
-
|
|
774
|
-
#### Section break placeholder
|
|
775
|
-
|
|
776
|
-
Sections in WordprocessingML store section properties in a `<w:sectPr>` element. For all sections except the last, the `sectPr` is stored as a child of the last paragraph of the section. citeturn0search15turn0search3
|
|
777
|
-
|
|
778
|
-
```ts
|
|
779
|
-
export interface SectionBreakNode extends NodeBase {
|
|
780
|
-
type: "sectionBreak";
|
|
781
|
-
children: [];
|
|
782
|
-
|
|
783
|
-
attrs: {
|
|
784
|
-
// Reference to preserved or generated sectPr XML
|
|
785
|
-
sectPr: SectionPropsRef;
|
|
786
|
-
|
|
787
|
-
// Break type (nextPage, continuous, etc.)
|
|
788
|
-
kind: "nextPage" | "continuous" | "column";
|
|
789
|
-
|
|
790
|
-
ooxmlUnknown?: PreservedXmlRef;
|
|
791
|
-
};
|
|
792
|
-
}
|
|
793
|
-
|
|
794
|
-
export interface SectionPropsRef {
|
|
795
|
-
mode: "preservedXml" | "generated";
|
|
796
|
-
preservedFragmentId?: Id; // references preservation.fragments
|
|
797
|
-
generated?: {
|
|
798
|
-
// Minimal modeled section props (optional; everything else preserved)
|
|
799
|
-
pageSize?: { widthTwips: Twips; heightTwips: Twips };
|
|
800
|
-
margins?: { topTwips: Twips; rightTwips: Twips; bottomTwips: Twips; leftTwips: Twips };
|
|
801
|
-
};
|
|
802
|
-
}
|
|
803
|
-
```
|
|
804
|
-
|
|
805
|
-
**Allowed children:** none.
|
|
806
|
-
|
|
807
|
-
**Constraints:**
|
|
808
|
-
- `sectPr.mode="preservedXml"` requires `preservedFragmentId`.
|
|
809
|
-
- Export MUST place section property markup in the correct OOXML location: associated with a paragraph boundary (the paragraph ending the section). citeturn0search15turn0search3
|
|
810
|
-
- If the CDS contains `SectionBreakNode` between blocks, exporter MUST ensure there is a paragraph to carry the `<w:sectPr>` if required by the chosen break kind.
|
|
811
|
-
|
|
812
|
-
**Editing invariants:** Treated as atomic. Insert/remove must update section association deterministically (defined in export algorithm notes under preservation).
|
|
813
|
-
|
|
814
|
-
#### Opaque preserved block node
|
|
815
|
-
|
|
816
|
-
```ts
|
|
817
|
-
export interface OoxmlBlockNode extends NodeBase {
|
|
818
|
-
type: "ooxmlBlock";
|
|
819
|
-
children: [];
|
|
820
|
-
|
|
821
|
-
attrs: {
|
|
822
|
-
fragmentId: Id; // references preservation.fragments
|
|
823
|
-
editability: "locked"; // always locked in this schema version
|
|
824
|
-
description?: string;
|
|
825
|
-
};
|
|
826
|
-
}
|
|
827
|
-
```
|
|
828
|
-
|
|
829
|
-
**Purpose:** Represents unsupported/unmodeled block-level OOXML content in the main body (e.g., complex content controls, altChunk, etc.) preserved for round-trip. Markup Compatibility constructs must be preserved when not supported. citeturn2search3turn2search7
|
|
830
|
-
|
|
831
|
-
**Constraints:** `fragmentId` MUST exist; exporter MUST re-inject without modification.
|
|
832
|
-
|
|
833
|
-
### Inline node specifications
|
|
834
|
-
|
|
835
|
-
Inline nodes occur inside paragraphs/headings.
|
|
836
|
-
|
|
837
|
-
```ts
|
|
838
|
-
export type InlineNode =
|
|
839
|
-
| TextNode
|
|
840
|
-
| HardBreakNode
|
|
841
|
-
| HyperlinkNode
|
|
842
|
-
| InlineImageNode
|
|
843
|
-
| AnchorPlaceholderNode
|
|
844
|
-
| OoxmlInlineNode;
|
|
845
|
-
```
|
|
846
|
-
|
|
847
|
-
#### Text
|
|
848
|
-
|
|
849
|
-
```ts
|
|
850
|
-
export interface TextNode extends NodeBase {
|
|
851
|
-
type: "text";
|
|
852
|
-
text: string; // Unicode string (may include spaces)
|
|
853
|
-
marks: Mark[]; // canonical sorted
|
|
854
|
-
attrs?: {
|
|
855
|
-
// If true, exporter MUST preserve whitespace equivalently to xml:space="preserve" where needed
|
|
856
|
-
preserveWhiteSpace?: boolean;
|
|
857
|
-
};
|
|
858
|
-
}
|
|
859
|
-
```
|
|
860
|
-
|
|
861
|
-
**Constraints:**
|
|
862
|
-
- `text` MUST be non-empty after normalization (empty text nodes forbidden).
|
|
863
|
-
- Adjacent text nodes with identical `marks` and identical `attrs.preserveWhiteSpace` MUST be merged.
|
|
864
|
-
- Export MUST handle whitespace correctly within `w:t`, using XML whitespace preservation rules (`xml:space="preserve"`) when needed. WordprocessingML text handling depends on XML space preservation semantics. citeturn3search9turn12search11
|
|
865
|
-
|
|
866
|
-
**WordprocessingML mapping:** `text` maps to `<w:r><w:t>...</w:t></w:r>` with `w:rPr` derived from marks. `w:r` is the container for run content. citeturn3search1turn7search11
|
|
867
|
-
|
|
868
|
-
#### Hard break
|
|
869
|
-
|
|
870
|
-
```ts
|
|
871
|
-
export interface HardBreakNode extends NodeBase {
|
|
872
|
-
type: "hardBreak";
|
|
873
|
-
attrs: {
|
|
874
|
-
// Word supports variants; default is line break
|
|
875
|
-
break: "line";
|
|
876
|
-
};
|
|
877
|
-
}
|
|
878
|
-
```
|
|
879
|
-
|
|
880
|
-
**WordprocessingML mapping:** `<w:br/>` in run content. A break overrides normal line breaking. citeturn3search3turn3search17
|
|
881
|
-
|
|
882
|
-
#### Hyperlink
|
|
883
|
-
|
|
884
|
-
Word hyperlinks are represented by `<w:hyperlink>` with either a relationship id (`r:id`) for external targets or an `anchor` for internal targets; if `id` exists it supersedes `anchor`. citeturn1search2
|
|
885
|
-
|
|
886
|
-
```ts
|
|
887
|
-
export interface HyperlinkNode extends NodeBase {
|
|
888
|
-
type: "hyperlink";
|
|
889
|
-
children: InlineNode[]; // typically text + marks
|
|
890
|
-
|
|
891
|
-
attrs: {
|
|
892
|
-
href?: string; // external target
|
|
893
|
-
relationshipId?: RelationshipId; // optional; if absent, exporter generates a relationship
|
|
894
|
-
anchor?: string; // internal target bookmark name
|
|
895
|
-
history?: boolean;
|
|
896
|
-
tooltip?: string;
|
|
897
|
-
targetFrame?: string;
|
|
898
|
-
|
|
899
|
-
// Optional explicit character style for hyperlink runs
|
|
900
|
-
characterStyleId?: string; // maps to run style w:rStyle in contained runs citeturn4search5
|
|
901
|
-
|
|
902
|
-
ooxmlUnknown?: PreservedXmlRef;
|
|
903
|
-
};
|
|
904
|
-
}
|
|
905
|
-
```
|
|
906
|
-
|
|
907
|
-
**Constraints:**
|
|
908
|
-
- Must contain at least one inline child after normalization (if empty, insert a single `TextNode` with `" "`).
|
|
909
|
-
- If `relationshipId` present, exporter MUST create/update a relationship with that id in the main document relationships part for the hyperlink target.
|
|
910
|
-
- If both `href` and `anchor` set, exporter MUST prefer external relationship semantics (consistent with OOXML “id supersedes anchor”). citeturn1search2
|
|
911
|
-
|
|
912
|
-
**WordprocessingML mapping:** `<w:hyperlink r:id="..."> ... </w:hyperlink>`. citeturn1search2
|
|
913
|
-
|
|
914
|
-
#### Inline image
|
|
915
|
-
|
|
916
|
-
```ts
|
|
917
|
-
export interface InlineImageNode extends NodeBase {
|
|
918
|
-
type: "inlineImage";
|
|
919
|
-
children: []; // leaf inline atom
|
|
920
|
-
|
|
921
|
-
attrs: {
|
|
922
|
-
mediaId: Id;
|
|
923
|
-
altText?: string;
|
|
924
|
-
|
|
925
|
-
widthTwips?: Twips;
|
|
926
|
-
heightTwips?: Twips;
|
|
927
|
-
|
|
928
|
-
// Export uses DrawingML inline
|
|
929
|
-
drawing: "wp:inline";
|
|
930
|
-
|
|
931
|
-
ooxmlUnknown?: PreservedXmlRef;
|
|
932
|
-
};
|
|
933
|
-
}
|
|
934
|
-
```
|
|
935
|
-
|
|
936
|
-
**Constraints:** `mediaId` MUST exist.
|
|
937
|
-
|
|
938
|
-
**WordprocessingML mapping:** Run with `w:drawing/wp:inline` referencing an embedded relationship to the image part. `wp:inline` indicates in-line positioning. citeturn2search10turn2search14
|
|
939
|
-
|
|
940
|
-
#### Anchor placeholder
|
|
941
|
-
|
|
942
|
-
This is a **zero-width addressable inline atom** used to ensure valid selections and to provide stable “point anchors” in cases where empty paragraphs/cells must remain selectable.
|
|
943
|
-
|
|
944
|
-
```ts
|
|
945
|
-
export interface AnchorPlaceholderNode extends NodeBase {
|
|
946
|
-
type: "anchor";
|
|
947
|
-
children: [];
|
|
948
|
-
|
|
949
|
-
attrs: {
|
|
950
|
-
role: "selection" | "emptyParagraph" | "emptyCell";
|
|
951
|
-
};
|
|
952
|
-
}
|
|
953
|
-
```
|
|
954
|
-
|
|
955
|
-
**Export behavior:** Exporter MUST NOT emit any visible glyph. It may emit an empty run (`<w:r/>`) if necessary to satisfy Word’s XML constraints for empty paragraphs.
|
|
956
|
-
|
|
957
|
-
#### Opaque preserved inline node
|
|
958
|
-
|
|
959
|
-
```ts
|
|
960
|
-
export interface OoxmlInlineNode extends NodeBase {
|
|
961
|
-
type: "ooxmlInline";
|
|
962
|
-
children: [];
|
|
963
|
-
|
|
964
|
-
attrs: {
|
|
965
|
-
fragmentId: Id;
|
|
966
|
-
editability: "locked";
|
|
967
|
-
description?: string;
|
|
968
|
-
};
|
|
969
|
-
}
|
|
970
|
-
```
|
|
971
|
-
|
|
972
|
-
Used for unsupported inline OOXML (complex fields, content controls inline, markup-compat alternate branches, etc.). Markup compatibility preservation applies. citeturn2search3turn2search7
|
|
973
|
-
|
|
974
|
-
### Marks (inline formatting)
|
|
975
|
-
|
|
976
|
-
Run properties in Word are encoded using `<w:rPr>` elements inside runs. citeturn7search11turn8search3turn8search1turn8search2
|
|
977
|
-
|
|
978
|
-
```ts
|
|
979
|
-
export type Mark =
|
|
980
|
-
| BoldMark
|
|
981
|
-
| ItalicMark
|
|
982
|
-
| UnderlineMark
|
|
983
|
-
| StrikeMark
|
|
984
|
-
| CodeMark
|
|
985
|
-
| SubscriptMark
|
|
986
|
-
| SuperscriptMark
|
|
987
|
-
| TextStyleMark;
|
|
988
|
-
|
|
989
|
-
export interface MarkBase {
|
|
990
|
-
type: string;
|
|
991
|
-
attrs?: Record<string, unknown>;
|
|
992
|
-
}
|
|
993
|
-
```
|
|
994
|
-
|
|
995
|
-
#### Bold
|
|
996
|
-
|
|
997
|
-
```ts
|
|
998
|
-
export interface BoldMark extends MarkBase {
|
|
999
|
-
type: "bold";
|
|
1000
|
-
}
|
|
1001
|
-
```
|
|
1002
|
-
|
|
1003
|
-
**WordprocessingML mapping:** `<w:b/>` toggle. citeturn7search0turn7search7
|
|
1004
|
-
|
|
1005
|
-
#### Italic
|
|
1006
|
-
|
|
1007
|
-
```ts
|
|
1008
|
-
export interface ItalicMark extends MarkBase {
|
|
1009
|
-
type: "italic";
|
|
1010
|
-
}
|
|
1011
|
-
```
|
|
1012
|
-
|
|
1013
|
-
**WordprocessingML mapping:** `<w:i/>`. citeturn7search8turn7search11
|
|
1014
|
-
|
|
1015
|
-
#### Underline
|
|
1016
|
-
|
|
1017
|
-
```ts
|
|
1018
|
-
export interface UnderlineMark extends MarkBase {
|
|
1019
|
-
type: "underline";
|
|
1020
|
-
attrs: {
|
|
1021
|
-
style: "single" | "double" | "dotted" | "dash" | "wave" | "none";
|
|
1022
|
-
};
|
|
1023
|
-
}
|
|
1024
|
-
```
|
|
1025
|
-
|
|
1026
|
-
**WordprocessingML mapping:** `<w:u w:val="..."/>`, where `val` is an underline style enumeration and `none` is equivalent to no underline. citeturn7search2turn7search20
|
|
1027
|
-
|
|
1028
|
-
#### Strike
|
|
1029
|
-
|
|
1030
|
-
```ts
|
|
1031
|
-
export interface StrikeMark extends MarkBase {
|
|
1032
|
-
type: "strike";
|
|
1033
|
-
}
|
|
1034
|
-
```
|
|
1035
|
-
|
|
1036
|
-
**WordprocessingML mapping:** `<w:strike/>` toggle. citeturn8search0turn8search4
|
|
1037
|
-
|
|
1038
|
-
#### Code
|
|
1039
|
-
|
|
1040
|
-
```ts
|
|
1041
|
-
export interface CodeMark extends MarkBase {
|
|
1042
|
-
type: "code";
|
|
1043
|
-
attrs?: {
|
|
1044
|
-
// If set, reference a character style intended for code formatting.
|
|
1045
|
-
characterStyleId?: string;
|
|
1046
|
-
};
|
|
1047
|
-
}
|
|
1048
|
-
```
|
|
1049
|
-
|
|
1050
|
-
**WordprocessingML mapping (deterministic):**
|
|
1051
|
-
- If `characterStyleId` provided, exporter MUST emit `w:rPr/w:rStyle` for runs in the code span. citeturn4search5turn2search4
|
|
1052
|
-
- Otherwise exporter MUST emit a monospace font via `w:rPr/w:rFonts` for ASCII + hAnsi at minimum, preserving any existing font theme attributes if present. `w:rFonts` controls fonts for Unicode subsets. citeturn8search3turn8search7turn8search15
|
|
1053
|
-
|
|
1054
|
-
#### Subscript and superscript
|
|
1055
|
-
|
|
1056
|
-
```ts
|
|
1057
|
-
export interface SubscriptMark extends MarkBase {
|
|
1058
|
-
type: "subscript";
|
|
1059
|
-
}
|
|
1060
|
-
export interface SuperscriptMark extends MarkBase {
|
|
1061
|
-
type: "superscript";
|
|
1062
|
-
}
|
|
1063
|
-
```
|
|
1064
|
-
|
|
1065
|
-
**WordprocessingML mapping:** `<w:vertAlign w:val="subscript|superscript"/>`. citeturn7search3turn7search10
|
|
1066
|
-
|
|
1067
|
-
#### Text style (font, color, size, highlight)
|
|
1068
|
-
|
|
1069
|
-
```ts
|
|
1070
|
-
export interface TextStyleMark extends MarkBase {
|
|
1071
|
-
type: "textStyle";
|
|
1072
|
-
attrs: {
|
|
1073
|
-
// Font: either explicit family or theme-based; exporter must preserve theme attributes if present.
|
|
1074
|
-
font?: {
|
|
1075
|
-
ascii?: string;
|
|
1076
|
-
hAnsi?: string;
|
|
1077
|
-
eastAsia?: string;
|
|
1078
|
-
cs?: string;
|
|
1079
|
-
asciiTheme?: string;
|
|
1080
|
-
hAnsiTheme?: string;
|
|
1081
|
-
eastAsiaTheme?: string;
|
|
1082
|
-
csTheme?: string;
|
|
1083
|
-
};
|
|
1084
|
-
|
|
1085
|
-
// Color: explicit RGB or "auto"
|
|
1086
|
-
color?: { val: HexColorRGB | "auto" };
|
|
1087
|
-
|
|
1088
|
-
// Font size in half-points, per OOXML sz semantics
|
|
1089
|
-
size?: { halfPoints: HalfPoints };
|
|
1090
|
-
|
|
1091
|
-
// Highlight
|
|
1092
|
-
highlight?: { val: string };
|
|
1093
|
-
|
|
1094
|
-
// Preservation
|
|
1095
|
-
ooxmlUnknown?: PreservedXmlRef;
|
|
1096
|
-
};
|
|
1097
|
-
}
|
|
1098
|
-
```
|
|
1099
|
-
|
|
1100
|
-
**WordprocessingML mapping:**
|
|
1101
|
-
- Font via `w:rFonts` (font selection per Unicode range). citeturn8search3turn8search7turn8search15
|
|
1102
|
-
- Color via `w:color`. citeturn8search1turn8search13
|
|
1103
|
-
- Size via `w:sz` half-point values. citeturn8search2turn8search14
|
|
1104
|
-
- Highlight via `w:highlight`. citeturn8search5
|
|
1105
|
-
|
|
1106
|
-
### Mark stacking, exclusivity, and normalization
|
|
1107
|
-
|
|
1108
|
-
**Rule M1 (Canonical mark order):** Marks on a `TextNode` MUST be sorted by this stable order:
|
|
1109
|
-
`bold`, `italic`, `underline`, `strike`, `code`, `subscript`, `superscript`, `textStyle`.
|
|
1110
|
-
|
|
1111
|
-
**Rule M2 (Exclusivity):**
|
|
1112
|
-
- `subscript` and `superscript` are mutually exclusive. If both appear, normalization MUST keep the later-applied mark and drop the other, emitting a warning diagnostic.
|
|
1113
|
-
|
|
1114
|
-
**Rule M3 (Merge adjacent text):**
|
|
1115
|
-
Two adjacent `TextNode`s MUST be merged if:
|
|
1116
|
-
- their mark arrays are deeply equal,
|
|
1117
|
-
- their `attrs.preserveWhiteSpace` values are equal.
|
|
1118
|
-
|
|
1119
|
-
**Rule M4 (Empty text forbidden):**
|
|
1120
|
-
Any `TextNode` with `text.length === 0` MUST be removed. If that would leave a paragraph empty, insert an `AnchorPlaceholderNode`.
|
|
1121
|
-
|
|
1122
|
-
## Structural rules and normalization
|
|
1123
|
-
|
|
1124
|
-
### Allowed nesting graph (complete)
|
|
1125
|
-
|
|
1126
|
-
The allowed parent → child relationships are:
|
|
1127
|
-
|
|
1128
|
-
- `doc` → any `BlockNode`.
|
|
1129
|
-
- `paragraph` / `heading` → any `InlineNode`.
|
|
1130
|
-
- `blockquote` → any `BlockNode` except `SectionBreakNode` (normalized by hoisting).
|
|
1131
|
-
- `orderedList` / `bulletList` → `listItem`.
|
|
1132
|
-
- `listItem` → any `BlockNode` except `SectionBreakNode` (hoisted).
|
|
1133
|
-
- `table` → `tableRow`.
|
|
1134
|
-
- `tableRow` → `tableCell`.
|
|
1135
|
-
- `tableCell` → any `BlockNode` (but normalized to contain at least one paragraph/heading).
|
|
1136
|
-
- `ooxmlBlock` → no children.
|
|
1137
|
-
- `hyperlink` → `InlineNode` (but not `hyperlink` nested inside `hyperlink`; normalization unwraps inner link).
|
|
1138
|
-
- `inlineImage`, `hardBreak`, `anchor`, `ooxmlInline` → no children.
|
|
1139
|
-
|
|
1140
|
-
### Illegal structures (and deterministic repairs)
|
|
1141
|
-
|
|
1142
|
-
The engine MUST run normalization after:
|
|
1143
|
-
- import,
|
|
1144
|
-
- each transaction,
|
|
1145
|
-
- before export.
|
|
1146
|
-
|
|
1147
|
-
Normalization MUST be deterministic and idempotent.
|
|
1148
|
-
|
|
1149
|
-
#### Repair rules
|
|
1150
|
-
|
|
1151
|
-
**Repair R1 (Doc root type):** If `content.type !== "doc"`, reject as fatal validation error.
|
|
1152
|
-
|
|
1153
|
-
**Repair R2 (Paragraph empty):** If `paragraph.children.length === 0`, set to `[ {type:"anchor", role:"emptyParagraph"} ]`.
|
|
1154
|
-
|
|
1155
|
-
**Repair R3 (Table cell empty):** If `tableCell.children.length === 0`, insert a single empty paragraph containing an anchor placeholder.
|
|
1156
|
-
|
|
1157
|
-
**Repair R4 (List item first block):** If first child of `listItem` is not `paragraph|heading`, insert an empty paragraph at index 0.
|
|
1158
|
-
|
|
1159
|
-
**Repair R5 (Nested hyperlink):** If a `hyperlink` contains a descendant `hyperlink`, normalization MUST unwrap the inner hyperlink by replacing it with its children.
|
|
1160
|
-
|
|
1161
|
-
**Repair R6 (Section break inside containers):** If `SectionBreakNode` appears within `blockquote` or `listItem`, hoist it to the nearest ancestor that is a direct child of `doc`. Hoisting preserves relative order by inserting immediately after the container block that contained it.
|
|
1162
|
-
|
|
1163
|
-
**Repair R7 (Adjacent lists):** Adjacent lists of same `kind|numId|baseIlvl` MUST be merged.
|
|
1164
|
-
|
|
1165
|
-
**Repair R8 (Marks):** Enforce M1–M4; emit diagnostics on conflicts.
|
|
1166
|
-
|
|
1167
|
-
### Explicit examples of valid vs invalid structures
|
|
1168
|
-
|
|
1169
|
-
#### Valid
|
|
1170
|
-
|
|
1171
|
-
```json
|
|
1172
|
-
{
|
|
1173
|
-
"type": "paragraph",
|
|
1174
|
-
"children": [
|
|
1175
|
-
{ "type": "text", "text": "Hello", "marks": [], "id": "t1" },
|
|
1176
|
-
{ "type": "hardBreak", "attrs": { "break": "line" }, "id": "br1" },
|
|
1177
|
-
{ "type": "text", "text": "World", "marks": [{ "type": "bold" }], "id": "t2" }
|
|
1178
|
-
],
|
|
1179
|
-
"attrs": {},
|
|
1180
|
-
"id": "p1"
|
|
1181
|
-
}
|
|
1182
|
-
```
|
|
1183
|
-
|
|
1184
|
-
#### Invalid → corrected (empty cell)
|
|
1185
|
-
|
|
1186
|
-
Invalid:
|
|
1187
|
-
|
|
1188
|
-
```json
|
|
1189
|
-
{ "type": "tableCell", "children": [], "attrs": {}, "id": "c1" }
|
|
1190
|
-
```
|
|
1191
|
-
|
|
1192
|
-
Corrected by R3:
|
|
1193
|
-
|
|
1194
|
-
```json
|
|
1195
|
-
{
|
|
1196
|
-
"type": "tableCell",
|
|
1197
|
-
"children": [
|
|
1198
|
-
{
|
|
1199
|
-
"type": "paragraph",
|
|
1200
|
-
"children": [{ "type": "anchor", "children": [], "attrs": { "role": "emptyCell" }, "id": "a1" }],
|
|
1201
|
-
"attrs": {},
|
|
1202
|
-
"id": "p1"
|
|
1203
|
-
}
|
|
1204
|
-
],
|
|
1205
|
-
"attrs": {},
|
|
1206
|
-
"id": "c1"
|
|
1207
|
-
}
|
|
1208
|
-
```
|
|
1209
|
-
|
|
1210
|
-
## Positions, ranges, selections, and transactions
|
|
1211
|
-
|
|
1212
|
-
### Global position indexing model
|
|
1213
|
-
|
|
1214
|
-
CDS uses a **single integer-based coordinate space** over the content tree, based on tokenized traversal rules (ProseMirror-style linear positions). This avoids path fragility and supports deterministic mapping across edits. Positions are similar to the documented concept where entering/leaving non-leaf nodes counts as tokens and each character counts as one token. citeturn9search4
|
|
1215
|
-
|
|
1216
|
-
#### Node size rules
|
|
1217
|
-
|
|
1218
|
-
Let `size(node)` be defined:
|
|
1219
|
-
|
|
1220
|
-
- For `TextNode`: `size = numberOfUnicodeCodePoints(text)`.
|
|
1221
|
-
- For inline leaf atom nodes (`hardBreak`, `inlineImage`, `anchor`, `ooxmlInline`): `size = 1`.
|
|
1222
|
-
- For block leaf nodes (`imageBlock`, `horizontalRule`, `sectionBreak`, `ooxmlBlock`): `size = 1`.
|
|
1223
|
-
- For container nodes (all others):
|
|
1224
|
-
`size = 2 + sum(size(child) for child in children)`
|
|
1225
|
-
where the `2` accounts for the container’s **start** and **end** tokens.
|
|
1226
|
-
|
|
1227
|
-
This is consistent with the notion that entering/leaving a node counts as tokens and leaf nodes count as a single token. citeturn9search4turn9search19
|
|
1228
|
-
|
|
1229
|
-
#### Position domain
|
|
1230
|
-
|
|
1231
|
-
```ts
|
|
1232
|
-
export type DocPos = number; // integer
|
|
1233
|
-
|
|
1234
|
-
export interface DocRange {
|
|
1235
|
-
from: DocPos; // inclusive
|
|
1236
|
-
to: DocPos; // exclusive, must satisfy from <= to
|
|
1237
|
-
}
|
|
1238
|
-
```
|
|
1239
|
-
|
|
1240
|
-
**Invariant PZ1:** `0 <= from <= to <= size(doc)`.
|
|
1241
|
-
|
|
1242
|
-
### Selections
|
|
1243
|
-
|
|
1244
|
-
```ts
|
|
1245
|
-
export type Selection =
|
|
1246
|
-
| TextSelection
|
|
1247
|
-
| NodeSelection
|
|
1248
|
-
| BlockSelection;
|
|
1249
|
-
|
|
1250
|
-
export interface TextSelection {
|
|
1251
|
-
kind: "text";
|
|
1252
|
-
range: DocRange;
|
|
1253
|
-
// assoc controls stickiness when mapping through insertions at boundaries
|
|
1254
|
-
assoc: { start: -1 | 1; end: -1 | 1 };
|
|
1255
|
-
}
|
|
1256
|
-
|
|
1257
|
-
export interface NodeSelection {
|
|
1258
|
-
kind: "node";
|
|
1259
|
-
// selects exactly one atomic node token (leaf: size 1)
|
|
1260
|
-
at: DocPos;
|
|
1261
|
-
}
|
|
1262
|
-
|
|
1263
|
-
export interface BlockSelection {
|
|
1264
|
-
kind: "block";
|
|
1265
|
-
// selects whole blocks by spanning their token ranges
|
|
1266
|
-
range: DocRange;
|
|
1267
|
-
}
|
|
1268
|
-
```
|
|
1269
|
-
|
|
1270
|
-
**Collapsed vs expanded:**
|
|
1271
|
-
- Collapsed selection: `range.from === range.to`.
|
|
1272
|
-
- Expanded selection: `range.from < range.to`.
|
|
1273
|
-
|
|
1274
|
-
### Transactions and steps
|
|
1275
|
-
|
|
1276
|
-
Transactions are the only permitted mutation mechanism.
|
|
1277
|
-
|
|
1278
|
-
```ts
|
|
1279
|
-
export interface Transaction {
|
|
1280
|
-
txId: UUID;
|
|
1281
|
-
createdAt: ISO8601DateTime;
|
|
1282
|
-
actorId: Id;
|
|
1283
|
-
|
|
1284
|
-
// Steps applied in order
|
|
1285
|
-
steps: Step[];
|
|
1286
|
-
|
|
1287
|
-
// Selection before/after (optional but recommended)
|
|
1288
|
-
selectionBefore?: Selection;
|
|
1289
|
-
selectionAfter?: Selection;
|
|
1290
|
-
|
|
1291
|
-
// Arbitrary metadata
|
|
1292
|
-
meta?: Record<string, unknown>;
|
|
1293
|
-
}
|
|
1294
|
-
```
|
|
1295
|
-
|
|
1296
|
-
#### Steps
|
|
1297
|
-
|
|
1298
|
-
```ts
|
|
1299
|
-
export type Step =
|
|
1300
|
-
| ReplaceStep
|
|
1301
|
-
| AddMarkStep
|
|
1302
|
-
| RemoveMarkStep
|
|
1303
|
-
| SetNodeAttrsStep;
|
|
1304
|
-
|
|
1305
|
-
export interface ReplaceStep {
|
|
1306
|
-
type: "replace";
|
|
1307
|
-
from: DocPos;
|
|
1308
|
-
to: DocPos;
|
|
1309
|
-
slice: Slice; // content fragment to insert
|
|
1310
|
-
}
|
|
1311
|
-
|
|
1312
|
-
export interface AddMarkStep {
|
|
1313
|
-
type: "addMark";
|
|
1314
|
-
from: DocPos;
|
|
1315
|
-
to: DocPos;
|
|
1316
|
-
mark: Mark;
|
|
1317
|
-
}
|
|
1318
|
-
|
|
1319
|
-
export interface RemoveMarkStep {
|
|
1320
|
-
type: "removeMark";
|
|
1321
|
-
from: DocPos;
|
|
1322
|
-
to: DocPos;
|
|
1323
|
-
markType: Mark["type"];
|
|
1324
|
-
}
|
|
1325
|
-
|
|
1326
|
-
export interface SetNodeAttrsStep {
|
|
1327
|
-
type: "setNodeAttrs";
|
|
1328
|
-
// position that resolves to a node boundary (start token of node)
|
|
1329
|
-
at: DocPos;
|
|
1330
|
-
// shallow partial patch (implementation must validate types)
|
|
1331
|
-
patch: Record<string, unknown>;
|
|
1332
|
-
}
|
|
1333
|
-
```
|
|
1334
|
-
|
|
1335
|
-
**Slice definition:**
|
|
1336
|
-
|
|
1337
|
-
```ts
|
|
1338
|
-
export interface Slice {
|
|
1339
|
-
// A slice is a fragment of block or inline content with open depth metadata.
|
|
1340
|
-
// For this CDS version, slices are always "closed" (openStart=openEnd=0).
|
|
1341
|
-
openStart: 0;
|
|
1342
|
-
openEnd: 0;
|
|
1343
|
-
|
|
1344
|
-
// Content to insert
|
|
1345
|
-
content: Node[]; // must be valid at insertion point post-normalization
|
|
1346
|
-
}
|
|
1347
|
-
```
|
|
1348
|
-
|
|
1349
|
-
### Mapping model
|
|
1350
|
-
|
|
1351
|
-
Every step produces a `StepMap` that maps old positions to new positions.
|
|
1352
|
-
|
|
1353
|
-
```ts
|
|
1354
|
-
export interface StepMap {
|
|
1355
|
-
// A list of [start, oldSize, newSize] triples describing replacements
|
|
1356
|
-
ranges: Array<[number, number, number]>;
|
|
1357
|
-
}
|
|
1358
|
-
|
|
1359
|
-
export interface Mapping {
|
|
1360
|
-
maps: StepMap[];
|
|
1361
|
-
}
|
|
1362
|
-
```
|
|
1363
|
-
|
|
1364
|
-
**Mapping behavior:** Applying a step shifts positions after the replaced range by `(newSize - oldSize)`. This aligns with the well-established “step provides a change map mapping positions” pattern. citeturn6search0turn9search4
|
|
1365
|
-
|
|
1366
|
-
**Mapping function (normative):**
|
|
1367
|
-
|
|
1368
|
-
```ts
|
|
1369
|
-
export function mapPos(pos: DocPos, map: StepMap, assoc: -1|1): DocPos;
|
|
1370
|
-
export function mapRange(range: DocRange, mapping: Mapping, assoc: {start:-1|1, end:-1|1}): DocRange;
|
|
1371
|
-
```
|
|
1372
|
-
|
|
1373
|
-
Normative rules:
|
|
1374
|
-
|
|
1375
|
-
- For each `[start, oldSize, newSize]`:
|
|
1376
|
-
- Let `end = start + oldSize`.
|
|
1377
|
-
- If `pos < start`: unchanged.
|
|
1378
|
-
- If `pos > end`: `pos += (newSize - oldSize)`.
|
|
1379
|
-
- If `start <= pos <= end` (inside replaced region):
|
|
1380
|
-
- If `assoc === -1`, map to `start`.
|
|
1381
|
-
- If `assoc === 1`, map to `start + newSize`.
|
|
1382
|
-
|
|
1383
|
-
After applying all ranges in order, return final `pos`.
|
|
1384
|
-
|
|
1385
|
-
**Transaction guarantees:**
|
|
1386
|
-
- After applying all steps and normalization, the engine MUST remap:
|
|
1387
|
-
- `selectionAfter` if omitted (derive by mapping `selectionBefore`),
|
|
1388
|
-
- all comment anchors and revision targets (rules below),
|
|
1389
|
-
- preservation bindings (rules below),
|
|
1390
|
-
- and MUST ensure invariants hold or emit diagnostics.
|
|
1391
|
-
|
|
1392
|
-
## Annotations: comments and tracked changes
|
|
1393
|
-
|
|
1394
|
-
### Comment anchor model
|
|
1395
|
-
|
|
1396
|
-
Word comments consist of:
|
|
1397
|
-
- a comment body stored in the comments part, and
|
|
1398
|
-
- anchor/range markup in the main document linking by id. citeturn0search4turn0search8
|
|
1399
|
-
|
|
1400
|
-
A comment not referenced by content via a matching id may be ignored by consumers, so linkage integrity is mandatory on export. citeturn0search4
|
|
1401
|
-
|
|
1402
|
-
#### Data structures
|
|
1403
|
-
|
|
1404
|
-
```ts
|
|
1405
|
-
export interface CommentStore {
|
|
1406
|
-
threads: Record<Id, CommentThread>; // key = threadId
|
|
1407
|
-
comments: Record<Id, Comment>; // key = commentId
|
|
1408
|
-
}
|
|
1409
|
-
|
|
1410
|
-
export interface CommentThread {
|
|
1411
|
-
threadId: Id;
|
|
1412
|
-
|
|
1413
|
-
// Anchor for the thread in the main document
|
|
1414
|
-
anchor: CommentAnchor;
|
|
1415
|
-
|
|
1416
|
-
// Ordered list of commentIds (first is root comment)
|
|
1417
|
-
commentIds: Id[];
|
|
1418
|
-
|
|
1419
|
-
// Status
|
|
1420
|
-
resolved?: boolean;
|
|
1421
|
-
resolvedAt?: ISO8601DateTime;
|
|
1422
|
-
resolvedBy?: Id;
|
|
1423
|
-
|
|
1424
|
-
// Import/export fidelity
|
|
1425
|
-
ooxmlCommentId?: number; // Word w:id (integer)
|
|
1426
|
-
}
|
|
1427
|
-
|
|
1428
|
-
export interface Comment {
|
|
1429
|
-
commentId: Id;
|
|
1430
|
-
threadId: Id;
|
|
1431
|
-
authorId: Id;
|
|
1432
|
-
createdAt: ISO8601DateTime;
|
|
1433
|
-
|
|
1434
|
-
// Rich content stored as a mini-doc fragment
|
|
1435
|
-
body: CommentBody;
|
|
1436
|
-
|
|
1437
|
-
// Threading
|
|
1438
|
-
parentCommentId?: Id;
|
|
1439
|
-
|
|
1440
|
-
// Optional edits
|
|
1441
|
-
editedAt?: ISO8601DateTime;
|
|
1442
|
-
|
|
1443
|
-
// OOXML preservation
|
|
1444
|
-
ooxmlUnknown?: PreservedXmlRef;
|
|
1445
|
-
}
|
|
1446
|
-
|
|
1447
|
-
export interface CommentBody {
|
|
1448
|
-
blocks: BlockNode[]; // same block model; must normalize to valid mini-doc
|
|
1449
|
-
}
|
|
1450
|
-
```
|
|
1451
|
-
|
|
1452
|
-
#### Anchor representation
|
|
1453
|
-
|
|
1454
|
-
```ts
|
|
1455
|
-
export type CommentAnchor =
|
|
1456
|
-
| RangeAnchor
|
|
1457
|
-
| NodeAnchor
|
|
1458
|
-
| OrphanAnchor;
|
|
1459
|
-
|
|
1460
|
-
export interface RangeAnchor {
|
|
1461
|
-
kind: "range";
|
|
1462
|
-
range: DocRange;
|
|
1463
|
-
assoc: { start: -1 | 1; end: -1 | 1 };
|
|
1464
|
-
|
|
1465
|
-
// Context used for degraded reattachment
|
|
1466
|
-
quote?: AnchorQuote;
|
|
1467
|
-
}
|
|
1468
|
-
|
|
1469
|
-
export interface NodeAnchor {
|
|
1470
|
-
kind: "node";
|
|
1471
|
-
// Selects an atomic node token, such as imageBlock/table/ooxmlBlock
|
|
1472
|
-
at: DocPos;
|
|
1473
|
-
assoc: -1 | 1;
|
|
1474
|
-
quote?: AnchorQuote;
|
|
1475
|
-
}
|
|
1476
|
-
|
|
1477
|
-
export interface OrphanAnchor {
|
|
1478
|
-
kind: "orphan";
|
|
1479
|
-
lastKnownRange: DocRange;
|
|
1480
|
-
orphanedAt: ISO8601DateTime;
|
|
1481
|
-
reason: "deleted" | "invalidatedByStructureChange" | "importAmbiguity";
|
|
1482
|
-
quote?: AnchorQuote;
|
|
1483
|
-
}
|
|
1484
|
-
|
|
1485
|
-
export interface AnchorQuote {
|
|
1486
|
-
selectedText?: string; // up to 64 Unicode code points
|
|
1487
|
-
prefix?: string; // up to 32 code points before
|
|
1488
|
-
suffix?: string; // up to 32 code points after
|
|
1489
|
-
}
|
|
1490
|
-
```
|
|
1491
|
-
|
|
1492
|
-
#### Anchor survival rules under edits
|
|
1493
|
-
|
|
1494
|
-
**Rule C1 (Primary remap):** On every transaction, if anchor is `range` or `node`, remap positions using the transaction’s `Mapping`.
|
|
1495
|
-
|
|
1496
|
-
**Rule C2 (Deletion collapse):** If a `RangeAnchor` maps to `range.from === range.to` and the pre-map range length was > 0, mark it as **degraded**:
|
|
1497
|
-
- keep as `RangeAnchor` but set `quote.selectedText` unchanged,
|
|
1498
|
-
- and add a warning diagnostic, unless content-based reattachment succeeds.
|
|
1499
|
-
|
|
1500
|
-
**Rule C3 (Orphaning):** If mapping produces out-of-bounds, or if the anchor points into a region that no longer corresponds to valid selectable content (e.g., removed node), convert to `OrphanAnchor`.
|
|
1501
|
-
|
|
1502
|
-
**Rule C4 (Content-based reattachment):** If anchor becomes degraded/orphaned and has a `quote.selectedText`, attempt deterministic reattachment:
|
|
1503
|
-
- search for exact occurrences of `selectedText` within ±1000 positions of `lastKnownRange.from` in the document’s extracted plain text,
|
|
1504
|
-
- filter by matching `prefix` and `suffix` when present,
|
|
1505
|
-
- choose the match with smallest distance; tie-break by lowest position,
|
|
1506
|
-
- if exactly one winner, re-anchor to that range; else remain orphaned.
|
|
1507
|
-
|
|
1508
|
-
**Rule C5 (Block targets):** To anchor to blocks (tables/images), use `NodeAnchor` selecting the atomic leaf token that represents the block (size 1).
|
|
1509
|
-
|
|
1510
|
-
#### WordprocessingML export expectations for comments
|
|
1511
|
-
|
|
1512
|
-
Exporter MUST generate:
|
|
1513
|
-
|
|
1514
|
-
- `commentRangeStart` at the mapped `from`,
|
|
1515
|
-
- `commentRangeEnd` at the mapped `to`,
|
|
1516
|
-
- a `commentReference` run linked by the same id, as required to create a link to a comment. citeturn0search0turn0search8
|
|
1517
|
-
- a corresponding `<w:comment w:id="...">...</w:comment>` in the comments part. citeturn0search4
|
|
1518
|
-
|
|
1519
|
-
### Revision (tracked changes) model
|
|
1520
|
-
|
|
1521
|
-
Word’s track revisions setting indicates that revisions are recorded so they can be viewed/accepted/reverted; OOXML defines specific revision markup syntax. citeturn10search3turn10search5
|
|
1522
|
-
|
|
1523
|
-
#### Core structures
|
|
1524
|
-
|
|
1525
|
-
```ts
|
|
1526
|
-
export interface RevisionStore {
|
|
1527
|
-
trackRevisions: boolean; // maps to settings.xml w:trackRevisions citeturn10search3turn10search13
|
|
1528
|
-
|
|
1529
|
-
items: Record<Id, Revision>; // key = revisionId
|
|
1530
|
-
}
|
|
1531
|
-
|
|
1532
|
-
export type Revision =
|
|
1533
|
-
| InsertionRevision
|
|
1534
|
-
| DeletionRevision
|
|
1535
|
-
| FormatRevision
|
|
1536
|
-
| MoveRevision;
|
|
1537
|
-
|
|
1538
|
-
export interface RevisionBase {
|
|
1539
|
-
revisionId: Id;
|
|
1540
|
-
authorId: Id;
|
|
1541
|
-
createdAt: ISO8601DateTime;
|
|
1542
|
-
|
|
1543
|
-
state: "active" | "accepted" | "rejected";
|
|
1544
|
-
|
|
1545
|
-
// Word revision identifier (w:id). Required for stable docx export.
|
|
1546
|
-
ooxmlRevisionId?: number;
|
|
1547
|
-
|
|
1548
|
-
// Optional preservation
|
|
1549
|
-
ooxmlUnknown?: PreservedXmlRef;
|
|
1550
|
-
}
|
|
1551
|
-
```
|
|
1552
|
-
|
|
1553
|
-
#### Insertion
|
|
1554
|
-
|
|
1555
|
-
Inserted run content is represented using `w:ins` containers in WordprocessingML. citeturn1search0turn11search19
|
|
1556
|
-
|
|
1557
|
-
```ts
|
|
1558
|
-
export interface InsertionRevision extends RevisionBase {
|
|
1559
|
-
kind: "insertion";
|
|
1560
|
-
range: DocRange;
|
|
1561
|
-
assoc: { start: -1 | 1; end: -1 | 1 };
|
|
1562
|
-
}
|
|
1563
|
-
```
|
|
1564
|
-
|
|
1565
|
-
**Invariant R-INS1:** `range.from < range.to` when `state="active"`.
|
|
1566
|
-
|
|
1567
|
-
#### Deletion
|
|
1568
|
-
|
|
1569
|
-
Deleted run content is represented by `w:del` containing the deleted content (often using `w:delText`). citeturn1search1turn1search5turn10search6
|
|
1570
|
-
|
|
1571
|
-
```ts
|
|
1572
|
-
export interface DeletionRevision extends RevisionBase {
|
|
1573
|
-
kind: "deletion";
|
|
1574
|
-
|
|
1575
|
-
// Point where deletion occurred in the *current* content
|
|
1576
|
-
at: DocPos;
|
|
1577
|
-
assoc: -1 | 1;
|
|
1578
|
-
|
|
1579
|
-
// Payload is the deleted content captured at deletion time
|
|
1580
|
-
deletedSlice: Slice;
|
|
1581
|
-
|
|
1582
|
-
// Optional original range (for UI); may be used for reattachment
|
|
1583
|
-
originalRange?: DocRange;
|
|
1584
|
-
quote?: AnchorQuote;
|
|
1585
|
-
}
|
|
1586
|
-
```
|
|
1587
|
-
|
|
1588
|
-
**Invariant R-DEL1:** `deletedSlice.content.length > 0` when `state="active"`.
|
|
1589
|
-
|
|
1590
|
-
#### Formatting revisions
|
|
1591
|
-
|
|
1592
|
-
Word tracks some formatting changes via `pPrChange` and `rPrChange`, which store the previous property set and revision metadata. citeturn11search1turn11search0
|
|
1593
|
-
|
|
1594
|
-
```ts
|
|
1595
|
-
export interface FormatRevision extends RevisionBase {
|
|
1596
|
-
kind: "format";
|
|
1597
|
-
|
|
1598
|
-
scope: "run" | "paragraph";
|
|
1599
|
-
range: DocRange;
|
|
1600
|
-
|
|
1601
|
-
// Canonical before/after representation of formatting
|
|
1602
|
-
before: { marks?: Mark[]; paragraphAttrs?: Partial<ParagraphAttrs> };
|
|
1603
|
-
after: { marks?: Mark[]; paragraphAttrs?: Partial<ParagraphAttrs> };
|
|
1604
|
-
}
|
|
1605
|
-
```
|
|
1606
|
-
|
|
1607
|
-
#### Move operations (model-aware, optional)
|
|
1608
|
-
|
|
1609
|
-
Word supports move tracking with move-range markers and move containers. citeturn11search6turn11search3turn11search11turn11search7
|
|
1610
|
-
|
|
1611
|
-
```ts
|
|
1612
|
-
export interface MoveRevision extends RevisionBase {
|
|
1613
|
-
kind: "move";
|
|
1614
|
-
|
|
1615
|
-
fromRange: DocRange;
|
|
1616
|
-
toRange: DocRange;
|
|
1617
|
-
|
|
1618
|
-
// Optional payload snapshot for robustness
|
|
1619
|
-
movedSlice?: Slice;
|
|
1620
|
-
}
|
|
1621
|
-
```
|
|
1622
|
-
|
|
1623
|
-
### Revision representation without corrupting structure
|
|
1624
|
-
|
|
1625
|
-
**Rule TR1 (No structural wrappers):** Revisions MUST NOT wrap content nodes in the CDS tree. They are separate records referencing positions/ranges.
|
|
1626
|
-
|
|
1627
|
-
**Rule TR2 (Export materialization):**
|
|
1628
|
-
- Active insertions MUST export as `w:ins` containers around run-level content. citeturn1search0
|
|
1629
|
-
- Active deletions MUST export as `w:del` containers with deleted run content using `w:delText` where appropriate. citeturn1search1turn1search5
|
|
1630
|
-
- Paragraph mark deletions MUST export using the correct WordprocessingML form for deleted paragraphs/paragraph marks. WordprocessingML defines deleted paragraph semantics. citeturn10search0
|
|
1631
|
-
|
|
1632
|
-
### Overlaps, nesting, and invariants
|
|
1633
|
-
|
|
1634
|
-
**Invariant TR-O1 (No conflicting active insertion overlap):** Two active insertion revisions MUST NOT overlap in position space. If a new insertion overlaps, normalization MUST split/merge deterministically:
|
|
1635
|
-
- If same author and createdAt within 5 minutes and insertion is adjacent/overlapping due to mapping, merge into one range.
|
|
1636
|
-
- Otherwise split such that ranges are disjoint and preserve order.
|
|
1637
|
-
|
|
1638
|
-
**Invariant TR-O2 (Deletion anchored at a point):** Deletions are point-anchored and can coexist with insertions, but exporter must order emitted OOXML deterministically: deletions first at a position, then insertions.
|
|
1639
|
-
|
|
1640
|
-
**Invariant TR-O3 (Format revisions):** Format revisions may overlap insertions/deletions, but export must emit `rPrChange/pPrChange` only for scope-specific changes, preserving OOXML constraints. citeturn11search0turn11search1
|
|
1641
|
-
|
|
1642
|
-
### Accept/reject rules (exact)
|
|
1643
|
-
|
|
1644
|
-
Let `rev` be a revision.
|
|
1645
|
-
|
|
1646
|
-
#### Accept insertion
|
|
1647
|
-
|
|
1648
|
-
- Preconditions: `rev.kind="insertion"`.
|
|
1649
|
-
- Effect:
|
|
1650
|
-
1. Remove `rev` from `revisions.items`.
|
|
1651
|
-
2. Content remains unchanged (inserted text becomes “normal”).
|
|
1652
|
-
3. Emit transaction steps:
|
|
1653
|
-
- none on content,
|
|
1654
|
-
- a revision-store removal event (implementation-defined but must be atomic with document update).
|
|
1655
|
-
|
|
1656
|
-
#### Reject insertion
|
|
1657
|
-
|
|
1658
|
-
- Preconditions: `rev.kind="insertion"`.
|
|
1659
|
-
- Effect:
|
|
1660
|
-
1. Delete the content in `rev.range` from the document using a `ReplaceStep(from, to, emptySlice)`.
|
|
1661
|
-
2. Remove `rev` from revisions.
|
|
1662
|
-
3. Remap all other anchors through the produced mapping.
|
|
1663
|
-
|
|
1664
|
-
#### Accept deletion
|
|
1665
|
-
|
|
1666
|
-
- Preconditions: `rev.kind="deletion"`.
|
|
1667
|
-
- Effect:
|
|
1668
|
-
1. Remove `rev` from revisions.
|
|
1669
|
-
2. Content remains unchanged (deleted content stays deleted).
|
|
1670
|
-
|
|
1671
|
-
#### Reject deletion
|
|
1672
|
-
|
|
1673
|
-
- Preconditions: `rev.kind="deletion"`.
|
|
1674
|
-
- Effect:
|
|
1675
|
-
1. Insert `rev.deletedSlice` at `rev.at` using `ReplaceStep(at, at, deletedSlice)`.
|
|
1676
|
-
2. Remove `rev`.
|
|
1677
|
-
3. Remap all anchors.
|
|
1678
|
-
|
|
1679
|
-
#### Merge revisions
|
|
1680
|
-
|
|
1681
|
-
Two active revisions `A` and `B` of same kind MAY merge only if:
|
|
1682
|
-
- same authorId,
|
|
1683
|
-
- same kind,
|
|
1684
|
-
- contiguous after mapping (`A.to === B.from` for insertions; `A.at === B.at` and slice concatenation for deletions),
|
|
1685
|
-
- createdAt within merge window (default 5 minutes).
|
|
1686
|
-
|
|
1687
|
-
Merging is deterministic: choose smallest revisionId lexicographically as survivor.
|
|
1688
|
-
|
|
1689
|
-
## Round-trip preservation, validation, migration, and examples
|
|
1690
|
-
|
|
1691
|
-
### Preservation model
|
|
1692
|
-
|
|
1693
|
-
#### Goals
|
|
1694
|
-
|
|
1695
|
-
Preserve unsupported OOXML in two forms:
|
|
1696
|
-
|
|
1697
|
-
1. **Untouched parts**: parts not edited by the CDS editor (headers, footers, footnotes, endnotes, themes, settings, etc.) are preserved as raw bytes and copied back during export.
|
|
1698
|
-
2. **Opaque fragments** inside edited parts: unsupported markup embedded in the main document story is preserved as **fragments**, referenced by `ooxmlBlock/ooxmlInline` nodes or by `PreservedXmlRef` fields.
|
|
1699
|
-
|
|
1700
|
-
Because `.docx` is an OPC package defined in terms of parts and relationships, preserving unknown parts/relationships is fundamental for compatibility. citeturn0search2turn3search4
|
|
1701
|
-
|
|
1702
|
-
Markup Compatibility (e.g., `mc:AlternateContent`) must be preserved when not explicitly supported. citeturn2search3turn2search7
|
|
1703
|
-
|
|
1704
|
-
#### Data structures
|
|
1705
|
-
|
|
1706
|
-
```ts
|
|
1707
|
-
export interface PreservationStore {
|
|
1708
|
-
// XML fragments that can be re-injected into edited parts
|
|
1709
|
-
fragments: Record<Id, PreservedXmlFragment>;
|
|
1710
|
-
|
|
1711
|
-
// OPC package-level preservation
|
|
1712
|
-
opc: OpcPreservation;
|
|
1713
|
-
}
|
|
1714
|
-
|
|
1715
|
-
export interface PreservedXmlRef {
|
|
1716
|
-
fragmentId: Id;
|
|
1717
|
-
}
|
|
1718
|
-
|
|
1719
|
-
export interface PreservedXmlFragment {
|
|
1720
|
-
fragmentId: Id;
|
|
1721
|
-
kind: "xmlElement" | "xmlFragment";
|
|
1722
|
-
xmlns: Record<string, string>; // prefix->namespace URI
|
|
1723
|
-
xml: string; // UTF-8 XML text; must be well-formed if wrapped in <cds:frag> by implementation
|
|
1724
|
-
|
|
1725
|
-
// Merge policy
|
|
1726
|
-
policy: "readOnly" | "mergeable";
|
|
1727
|
-
|
|
1728
|
-
// Original location hint for diagnostics
|
|
1729
|
-
source?: { partName: PartName; xpath?: string };
|
|
1730
|
-
}
|
|
1731
|
-
|
|
1732
|
-
export interface OpcPreservation {
|
|
1733
|
-
// Original [Content_Types].xml bytes
|
|
1734
|
-
contentTypesXmlBase64: Base64;
|
|
1735
|
-
|
|
1736
|
-
// All original parts and relationships
|
|
1737
|
-
parts: Record<PartName, PreservedPart>;
|
|
1738
|
-
relationships: Record<string, Relationship[]>; // key = "package" or partName
|
|
1739
|
-
|
|
1740
|
-
// Parts that CDS regenerates (must be listed explicitly)
|
|
1741
|
-
regeneratedParts: {
|
|
1742
|
-
mainDocument: "/word/document.xml";
|
|
1743
|
-
styles?: "/word/styles.xml";
|
|
1744
|
-
numbering?: "/word/numbering.xml";
|
|
1745
|
-
comments?: "/word/comments.xml";
|
|
1746
|
-
relsMainDocument: "/word/_rels/document.xml.rels";
|
|
1747
|
-
};
|
|
1748
|
-
}
|
|
1749
|
-
|
|
1750
|
-
export interface PreservedPart {
|
|
1751
|
-
partName: PartName;
|
|
1752
|
-
contentType: string;
|
|
1753
|
-
bytesBase64: Base64;
|
|
1754
|
-
editable: false; // parts are not edited unless explicitly regenerated
|
|
1755
|
-
}
|
|
1756
|
-
|
|
1757
|
-
export interface Relationship {
|
|
1758
|
-
id: RelationshipId;
|
|
1759
|
-
type: string; // relationship type URI
|
|
1760
|
-
target: string; // part URI or external URI
|
|
1761
|
-
targetMode?: "External";
|
|
1762
|
-
}
|
|
1763
|
-
```
|
|
1764
|
-
|
|
1765
|
-
### Preservation linking and edit constraints
|
|
1766
|
-
|
|
1767
|
-
**Rule PR1 (Fragment integrity):** If a content node references `fragmentId`, that fragment MUST exist and MUST be re-emitted verbatim on export.
|
|
1768
|
-
|
|
1769
|
-
**Rule PR2 (Locked regions):** `ooxmlBlock` and `ooxmlInline` nodes are non-editable; attempts to edit inside MUST be rejected and surfaced as a recoverable error diagnostic (editor can still delete the whole atomic node if allowed).
|
|
1770
|
-
|
|
1771
|
-
**Rule PR3 (Reattachment):** Opaque fragments represented as nodes reattach automatically because they occupy a position in the content tree. Fragments referenced from `ooxmlUnknown...` fields reattach to their owning node’s corresponding OOXML property container:
|
|
1772
|
-
- paragraph `ooxmlUnknownPPr` re-injects into `w:pPr`,
|
|
1773
|
-
- run-level unknown re-injects into `w:rPr` for the specific run created from that text span,
|
|
1774
|
-
- numbering/style unknown re-injects into the corresponding style/numbering element.
|
|
1775
|
-
|
|
1776
|
-
### Validation rules
|
|
1777
|
-
|
|
1778
|
-
Validation must classify issues as fatal vs recoverable.
|
|
1779
|
-
|
|
1780
|
-
#### Structural validation (fatal unless repairable)
|
|
1781
|
-
|
|
1782
|
-
- **V-S1:** Node types and required fields must match unions.
|
|
1783
|
-
- **V-S2:** Nesting constraints must hold (graph above).
|
|
1784
|
-
- **V-S3:** All IDs must be unique per D3.
|
|
1785
|
-
|
|
1786
|
-
If a repair rule exists (R1–R8), the engine MUST repair and emit a warning diagnostic; otherwise fatal.
|
|
1787
|
-
|
|
1788
|
-
#### Attribute validation
|
|
1789
|
-
|
|
1790
|
-
- **V-A1:** `ParagraphNumbering.ilvl` 0..8.
|
|
1791
|
-
- **V-A2:** Heading level 1..9.
|
|
1792
|
-
- **V-A3:** Color values must be 6 hex digits or `"auto"`.
|
|
1793
|
-
- **V-A4:** Font sizes must be positive half-points. citeturn8search14
|
|
1794
|
-
|
|
1795
|
-
#### Anchor validity
|
|
1796
|
-
|
|
1797
|
-
- **V-C1:** All anchors positions within 0..size(doc).
|
|
1798
|
-
- **V-C2:** Range anchors must satisfy `from<=to`.
|
|
1799
|
-
- **V-C3:** Node anchors must resolve to an atomic node token (leaf size=1).
|
|
1800
|
-
|
|
1801
|
-
Invalid anchors become `OrphanAnchor` with a warning diagnostic.
|
|
1802
|
-
|
|
1803
|
-
#### Revision validity
|
|
1804
|
-
|
|
1805
|
-
- **V-R1:** Insertions must have non-empty range.
|
|
1806
|
-
- **V-R2:** Deletions must have non-empty deletedSlice.
|
|
1807
|
-
- **V-R3:** No overlapping active insertion ranges.
|
|
1808
|
-
|
|
1809
|
-
Violations repaired by splitting/merging per deterministic rules, with diagnostics.
|
|
1810
|
-
|
|
1811
|
-
#### Preservation integrity
|
|
1812
|
-
|
|
1813
|
-
- **V-P1:** All referenced fragment ids exist.
|
|
1814
|
-
- **V-P2:** OPC parts referenced in relationships exist unless external.
|
|
1815
|
-
- **V-P3:** Regenerated parts must not be simultaneously marked preserved-only.
|
|
1816
|
-
|
|
1817
|
-
### Versioning and migration
|
|
1818
|
-
|
|
1819
|
-
#### Schema version field
|
|
1820
|
-
|
|
1821
|
-
`CanonicalDocument.schemaVersion` is the migration discriminator.
|
|
1822
|
-
|
|
1823
|
-
#### Backward compatibility rules
|
|
1824
|
-
|
|
1825
|
-
- A loader MUST support `"cds/1.0.0"` documents.
|
|
1826
|
-
- The loader MAY accept older versions if a migration function exists; otherwise reject.
|
|
1827
|
-
|
|
1828
|
-
#### Forward compatibility handling
|
|
1829
|
-
|
|
1830
|
-
If `schemaVersion` is unknown:
|
|
1831
|
-
- Reject as fatal, **unless** `preservation.opc` contains the original `.docx` package; in that case the system MAY still allow re-export of the preserved `.docx` without editing (read-only compatibility mode), emitting a fatal diagnostic for editing.
|
|
1832
|
-
|
|
1833
|
-
#### Migration strategy (normative)
|
|
1834
|
-
|
|
1835
|
-
A migration is defined as:
|
|
1836
|
-
|
|
1837
|
-
```ts
|
|
1838
|
-
export interface Migration {
|
|
1839
|
-
from: CDS_SchemaVersion;
|
|
1840
|
-
to: CDS_SchemaVersion;
|
|
1841
|
-
migrate(doc: CanonicalDocument): CanonicalDocument;
|
|
1842
|
-
}
|
|
1843
|
-
```
|
|
1844
|
-
|
|
1845
|
-
Rules:
|
|
1846
|
-
- Migrations MUST be pure (no external state), deterministic, and idempotent.
|
|
1847
|
-
- Each migration MUST run full normalization and validation at the end, emitting diagnostics.
|
|
1848
|
-
- Stored documents MUST be migrated incrementally version-by-version (no skipping).
|
|
1849
|
-
|
|
1850
|
-
### Examples
|
|
1851
|
-
|
|
1852
|
-
All examples below are fully specified CDS JSON documents. IDs are illustrative.
|
|
1853
|
-
|
|
1854
|
-
#### Simple document example
|
|
1855
|
-
|
|
1856
|
-
```json
|
|
1857
|
-
{
|
|
1858
|
-
"schemaVersion": "cds/1.0.0",
|
|
1859
|
-
"docId": "2d6e1c2e-7d15-45a4-8b7a-5f422f18bb3b",
|
|
1860
|
-
"createdAt": "2026-03-25T10:00:00.000Z",
|
|
1861
|
-
"updatedAt": "2026-03-25T10:00:00.000Z",
|
|
1862
|
-
"metadata": {
|
|
1863
|
-
"title": "Simple Doc",
|
|
1864
|
-
"actors": {
|
|
1865
|
-
"u1": { "actorId": "u1", "displayName": "Alex Editor" }
|
|
1866
|
-
}
|
|
1867
|
-
},
|
|
1868
|
-
"content": {
|
|
1869
|
-
"id": "n_doc",
|
|
1870
|
-
"type": "doc",
|
|
1871
|
-
"attrs": { "trackRevisionsDefault": false },
|
|
1872
|
-
"children": [
|
|
1873
|
-
{
|
|
1874
|
-
"id": "p1",
|
|
1875
|
-
"type": "paragraph",
|
|
1876
|
-
"attrs": { "alignment": "left" },
|
|
1877
|
-
"children": [
|
|
1878
|
-
{ "id": "t1", "type": "text", "text": "Hello, world!", "marks": [] }
|
|
1879
|
-
]
|
|
1880
|
-
}
|
|
1881
|
-
]
|
|
1882
|
-
},
|
|
1883
|
-
"styles": {
|
|
1884
|
-
"defaults": {},
|
|
1885
|
-
"paragraphStyles": {},
|
|
1886
|
-
"characterStyles": {},
|
|
1887
|
-
"tableStyles": {}
|
|
1888
|
-
},
|
|
1889
|
-
"numbering": {
|
|
1890
|
-
"abstractNums": {},
|
|
1891
|
-
"nums": {}
|
|
1892
|
-
},
|
|
1893
|
-
"media": {
|
|
1894
|
-
"items": {},
|
|
1895
|
-
"exportPolicy": {
|
|
1896
|
-
"mediaFolder": "/word/media",
|
|
1897
|
-
"filenameStrategy": "stableByMediaId",
|
|
1898
|
-
"relationshipIdStrategy": "stableByOrder"
|
|
1899
|
-
}
|
|
1900
|
-
},
|
|
1901
|
-
"comments": { "threads": {}, "comments": {} },
|
|
1902
|
-
"revisions": { "trackRevisions": false, "items": {} },
|
|
1903
|
-
"preservation": {
|
|
1904
|
-
"fragments": {},
|
|
1905
|
-
"opc": {
|
|
1906
|
-
"contentTypesXmlBase64": "",
|
|
1907
|
-
"parts": {},
|
|
1908
|
-
"relationships": {},
|
|
1909
|
-
"regeneratedParts": {
|
|
1910
|
-
"mainDocument": "/word/document.xml",
|
|
1911
|
-
"relsMainDocument": "/word/_rels/document.xml.rels"
|
|
1912
|
-
}
|
|
1913
|
-
}
|
|
1914
|
-
},
|
|
1915
|
-
"diagnostics": { "items": [] }
|
|
1916
|
-
}
|
|
1917
|
-
```
|
|
1918
|
-
|
|
1919
|
-
#### Document with lists and tables
|
|
1920
|
-
|
|
1921
|
-
```json
|
|
1922
|
-
{
|
|
1923
|
-
"schemaVersion": "cds/1.0.0",
|
|
1924
|
-
"docId": "9c2e7d12-0c8b-4bb5-9f0a-5f2d0d0f7d2e",
|
|
1925
|
-
"createdAt": "2026-03-25T10:05:00.000Z",
|
|
1926
|
-
"updatedAt": "2026-03-25T10:05:00.000Z",
|
|
1927
|
-
"metadata": {
|
|
1928
|
-
"title": "Lists and Tables",
|
|
1929
|
-
"actors": {
|
|
1930
|
-
"u1": { "actorId": "u1", "displayName": "Alex Editor" }
|
|
1931
|
-
}
|
|
1932
|
-
},
|
|
1933
|
-
"content": {
|
|
1934
|
-
"id": "doc",
|
|
1935
|
-
"type": "doc",
|
|
1936
|
-
"attrs": { "trackRevisionsDefault": false },
|
|
1937
|
-
"children": [
|
|
1938
|
-
{
|
|
1939
|
-
"id": "h1",
|
|
1940
|
-
"type": "heading",
|
|
1941
|
-
"attrs": { "level": 1, "styleId": "Heading1" },
|
|
1942
|
-
"children": [{ "id": "ht1", "type": "text", "text": "Project Status", "marks": [] }]
|
|
1943
|
-
},
|
|
1944
|
-
{
|
|
1945
|
-
"id": "ol1",
|
|
1946
|
-
"type": "orderedList",
|
|
1947
|
-
"attrs": { "kind": "ordered", "numId": "num1", "baseIlvl": 0 },
|
|
1948
|
-
"children": [
|
|
1949
|
-
{
|
|
1950
|
-
"id": "li1",
|
|
1951
|
-
"type": "listItem",
|
|
1952
|
-
"attrs": {},
|
|
1953
|
-
"children": [
|
|
1954
|
-
{
|
|
1955
|
-
"id": "p_li1",
|
|
1956
|
-
"type": "paragraph",
|
|
1957
|
-
"attrs": { "numbering": { "numId": "num1", "ilvl": 0 } },
|
|
1958
|
-
"children": [{ "id": "t_li1", "type": "text", "text": "Define scope", "marks": [] }]
|
|
1959
|
-
}
|
|
1960
|
-
]
|
|
1961
|
-
},
|
|
1962
|
-
{
|
|
1963
|
-
"id": "li2",
|
|
1964
|
-
"type": "listItem",
|
|
1965
|
-
"attrs": {},
|
|
1966
|
-
"children": [
|
|
1967
|
-
{
|
|
1968
|
-
"id": "p_li2",
|
|
1969
|
-
"type": "paragraph",
|
|
1970
|
-
"attrs": { "numbering": { "numId": "num1", "ilvl": 0 } },
|
|
1971
|
-
"children": [{ "id": "t_li2", "type": "text", "text": "Build prototype", "marks": [] }]
|
|
1972
|
-
}
|
|
1973
|
-
]
|
|
1974
|
-
}
|
|
1975
|
-
]
|
|
1976
|
-
},
|
|
1977
|
-
{
|
|
1978
|
-
"id": "tbl1",
|
|
1979
|
-
"type": "table",
|
|
1980
|
-
"attrs": { "grid": { "colWidthsTwips": [3000, 5000] } },
|
|
1981
|
-
"children": [
|
|
1982
|
-
{
|
|
1983
|
-
"id": "tr1",
|
|
1984
|
-
"type": "tableRow",
|
|
1985
|
-
"attrs": {},
|
|
1986
|
-
"children": [
|
|
1987
|
-
{
|
|
1988
|
-
"id": "tc1",
|
|
1989
|
-
"type": "tableCell",
|
|
1990
|
-
"attrs": {},
|
|
1991
|
-
"children": [
|
|
1992
|
-
{
|
|
1993
|
-
"id": "p_tc1",
|
|
1994
|
-
"type": "paragraph",
|
|
1995
|
-
"attrs": {},
|
|
1996
|
-
"children": [{ "id": "t_tc1", "type": "text", "text": "Owner", "marks": [{ "type": "bold" }] }]
|
|
1997
|
-
}
|
|
1998
|
-
]
|
|
1999
|
-
},
|
|
2000
|
-
{
|
|
2001
|
-
"id": "tc2",
|
|
2002
|
-
"type": "tableCell",
|
|
2003
|
-
"attrs": {},
|
|
2004
|
-
"children": [
|
|
2005
|
-
{
|
|
2006
|
-
"id": "p_tc2",
|
|
2007
|
-
"type": "paragraph",
|
|
2008
|
-
"attrs": {},
|
|
2009
|
-
"children": [{ "id": "t_tc2", "type": "text", "text": "Alex", "marks": [] }]
|
|
2010
|
-
}
|
|
2011
|
-
]
|
|
2012
|
-
}
|
|
2013
|
-
]
|
|
2014
|
-
}
|
|
2015
|
-
]
|
|
2016
|
-
}
|
|
2017
|
-
]
|
|
2018
|
-
},
|
|
2019
|
-
"styles": { "defaults": {}, "paragraphStyles": {}, "characterStyles": {}, "tableStyles": {} },
|
|
2020
|
-
"numbering": {
|
|
2021
|
-
"abstractNums": {
|
|
2022
|
-
"abs1": { "abstractNumId": "abs1", "levels": { "0": { "level": 0, "numFmt": "decimal", "lvlText": "%1." } } }
|
|
2023
|
-
},
|
|
2024
|
-
"nums": { "num1": { "numId": "num1", "abstractNumId": "abs1" } }
|
|
2025
|
-
},
|
|
2026
|
-
"media": {
|
|
2027
|
-
"items": {},
|
|
2028
|
-
"exportPolicy": { "mediaFolder": "/word/media", "filenameStrategy": "stableByMediaId", "relationshipIdStrategy": "stableByOrder" }
|
|
2029
|
-
},
|
|
2030
|
-
"comments": { "threads": {}, "comments": {} },
|
|
2031
|
-
"revisions": { "trackRevisions": false, "items": {} },
|
|
2032
|
-
"preservation": {
|
|
2033
|
-
"fragments": {},
|
|
2034
|
-
"opc": {
|
|
2035
|
-
"contentTypesXmlBase64": "",
|
|
2036
|
-
"parts": {},
|
|
2037
|
-
"relationships": {},
|
|
2038
|
-
"regeneratedParts": { "mainDocument": "/word/document.xml", "relsMainDocument": "/word/_rels/document.xml.rels" }
|
|
2039
|
-
}
|
|
2040
|
-
},
|
|
2041
|
-
"diagnostics": { "items": [] }
|
|
2042
|
-
}
|
|
2043
|
-
```
|
|
2044
|
-
|
|
2045
|
-
#### Document with comments
|
|
2046
|
-
|
|
2047
|
-
```json
|
|
2048
|
-
{
|
|
2049
|
-
"schemaVersion": "cds/1.0.0",
|
|
2050
|
-
"docId": "f88c2c0a-5f7a-4a1a-8a9c-8f0a2a4b3c11",
|
|
2051
|
-
"createdAt": "2026-03-25T10:10:00.000Z",
|
|
2052
|
-
"updatedAt": "2026-03-25T10:10:00.000Z",
|
|
2053
|
-
"metadata": {
|
|
2054
|
-
"title": "Comments Example",
|
|
2055
|
-
"actors": {
|
|
2056
|
-
"u1": { "actorId": "u1", "displayName": "Alex Editor" },
|
|
2057
|
-
"u2": { "actorId": "u2", "displayName": "Casey Reviewer" }
|
|
2058
|
-
}
|
|
2059
|
-
},
|
|
2060
|
-
"content": {
|
|
2061
|
-
"id": "doc",
|
|
2062
|
-
"type": "doc",
|
|
2063
|
-
"attrs": { "trackRevisionsDefault": false },
|
|
2064
|
-
"children": [
|
|
2065
|
-
{
|
|
2066
|
-
"id": "p1",
|
|
2067
|
-
"type": "paragraph",
|
|
2068
|
-
"attrs": {},
|
|
2069
|
-
"children": [
|
|
2070
|
-
{ "id": "t1", "type": "text", "text": "Please review this sentence.", "marks": [] }
|
|
2071
|
-
]
|
|
2072
|
-
}
|
|
2073
|
-
]
|
|
2074
|
-
},
|
|
2075
|
-
"styles": { "defaults": {}, "paragraphStyles": {}, "characterStyles": {}, "tableStyles": {} },
|
|
2076
|
-
"numbering": { "abstractNums": {}, "nums": {} },
|
|
2077
|
-
"media": {
|
|
2078
|
-
"items": {},
|
|
2079
|
-
"exportPolicy": { "mediaFolder": "/word/media", "filenameStrategy": "stableByMediaId", "relationshipIdStrategy": "stableByOrder" }
|
|
2080
|
-
},
|
|
2081
|
-
"comments": {
|
|
2082
|
-
"threads": {
|
|
2083
|
-
"th1": {
|
|
2084
|
-
"threadId": "th1",
|
|
2085
|
-
"anchor": {
|
|
2086
|
-
"kind": "range",
|
|
2087
|
-
"range": { "from": 2, "to": 28 },
|
|
2088
|
-
"assoc": { "start": -1, "end": 1 },
|
|
2089
|
-
"quote": { "selectedText": "review this sentence" }
|
|
2090
|
-
},
|
|
2091
|
-
"commentIds": ["c1"],
|
|
2092
|
-
"resolved": false,
|
|
2093
|
-
"ooxmlCommentId": 0
|
|
2094
|
-
}
|
|
2095
|
-
},
|
|
2096
|
-
"comments": {
|
|
2097
|
-
"c1": {
|
|
2098
|
-
"commentId": "c1",
|
|
2099
|
-
"threadId": "th1",
|
|
2100
|
-
"authorId": "u2",
|
|
2101
|
-
"createdAt": "2026-03-25T10:10:00.000Z",
|
|
2102
|
-
"body": {
|
|
2103
|
-
"blocks": [
|
|
2104
|
-
{
|
|
2105
|
-
"id": "cp1",
|
|
2106
|
-
"type": "paragraph",
|
|
2107
|
-
"attrs": {},
|
|
2108
|
-
"children": [{ "id": "ct1", "type": "text", "text": "Looks good, but consider tightening wording.", "marks": [] }]
|
|
2109
|
-
}
|
|
2110
|
-
]
|
|
2111
|
-
}
|
|
2112
|
-
}
|
|
2113
|
-
}
|
|
2114
|
-
},
|
|
2115
|
-
"revisions": { "trackRevisions": false, "items": {} },
|
|
2116
|
-
"preservation": {
|
|
2117
|
-
"fragments": {},
|
|
2118
|
-
"opc": {
|
|
2119
|
-
"contentTypesXmlBase64": "",
|
|
2120
|
-
"parts": {},
|
|
2121
|
-
"relationships": {},
|
|
2122
|
-
"regeneratedParts": { "mainDocument": "/word/document.xml", "comments": "/word/comments.xml", "relsMainDocument": "/word/_rels/document.xml.rels" }
|
|
2123
|
-
}
|
|
2124
|
-
},
|
|
2125
|
-
"diagnostics": { "items": [] }
|
|
2126
|
-
}
|
|
2127
|
-
```
|
|
2128
|
-
|
|
2129
|
-
#### Document with tracked changes
|
|
2130
|
-
|
|
2131
|
-
```json
|
|
2132
|
-
{
|
|
2133
|
-
"schemaVersion": "cds/1.0.0",
|
|
2134
|
-
"docId": "c1a76d6b-2f15-4c5f-9a4b-b3c9a7d1a0e2",
|
|
2135
|
-
"createdAt": "2026-03-25T10:15:00.000Z",
|
|
2136
|
-
"updatedAt": "2026-03-25T10:15:00.000Z",
|
|
2137
|
-
"metadata": {
|
|
2138
|
-
"title": "Tracked Changes Example",
|
|
2139
|
-
"actors": {
|
|
2140
|
-
"u1": { "actorId": "u1", "displayName": "Alex Editor" }
|
|
2141
|
-
}
|
|
2142
|
-
},
|
|
2143
|
-
"content": {
|
|
2144
|
-
"id": "doc",
|
|
2145
|
-
"type": "doc",
|
|
2146
|
-
"attrs": { "trackRevisionsDefault": true },
|
|
2147
|
-
"children": [
|
|
2148
|
-
{
|
|
2149
|
-
"id": "p1",
|
|
2150
|
-
"type": "paragraph",
|
|
2151
|
-
"attrs": {},
|
|
2152
|
-
"children": [
|
|
2153
|
-
{ "id": "t1", "type": "text", "text": "The quick brown fox.", "marks": [] }
|
|
2154
|
-
]
|
|
2155
|
-
}
|
|
2156
|
-
]
|
|
2157
|
-
},
|
|
2158
|
-
"styles": { "defaults": {}, "paragraphStyles": {}, "characterStyles": {}, "tableStyles": {} },
|
|
2159
|
-
"numbering": { "abstractNums": {}, "nums": {} },
|
|
2160
|
-
"media": {
|
|
2161
|
-
"items": {},
|
|
2162
|
-
"exportPolicy": { "mediaFolder": "/word/media", "filenameStrategy": "stableByMediaId", "relationshipIdStrategy": "stableByOrder" }
|
|
2163
|
-
},
|
|
2164
|
-
"comments": { "threads": {}, "comments": {} },
|
|
2165
|
-
"revisions": {
|
|
2166
|
-
"trackRevisions": true,
|
|
2167
|
-
"items": {
|
|
2168
|
-
"r_ins_1": {
|
|
2169
|
-
"revisionId": "r_ins_1",
|
|
2170
|
-
"kind": "insertion",
|
|
2171
|
-
"authorId": "u1",
|
|
2172
|
-
"createdAt": "2026-03-25T10:15:00.000Z",
|
|
2173
|
-
"state": "active",
|
|
2174
|
-
"ooxmlRevisionId": 1,
|
|
2175
|
-
"range": { "from": 6, "to": 11 },
|
|
2176
|
-
"assoc": { "start": -1, "end": 1 }
|
|
2177
|
-
},
|
|
2178
|
-
"r_del_1": {
|
|
2179
|
-
"revisionId": "r_del_1",
|
|
2180
|
-
"kind": "deletion",
|
|
2181
|
-
"authorId": "u1",
|
|
2182
|
-
"createdAt": "2026-03-25T10:15:00.000Z",
|
|
2183
|
-
"state": "active",
|
|
2184
|
-
"ooxmlRevisionId": 2,
|
|
2185
|
-
"at": 16,
|
|
2186
|
-
"assoc": -1,
|
|
2187
|
-
"deletedSlice": {
|
|
2188
|
-
"openStart": 0,
|
|
2189
|
-
"openEnd": 0,
|
|
2190
|
-
"content": [
|
|
2191
|
-
{ "id": "dt1", "type": "text", "text": "brown ", "marks": [] }
|
|
2192
|
-
]
|
|
2193
|
-
},
|
|
2194
|
-
"quote": { "selectedText": "brown " }
|
|
2195
|
-
}
|
|
2196
|
-
}
|
|
2197
|
-
},
|
|
2198
|
-
"preservation": {
|
|
2199
|
-
"fragments": {},
|
|
2200
|
-
"opc": {
|
|
2201
|
-
"contentTypesXmlBase64": "",
|
|
2202
|
-
"parts": {},
|
|
2203
|
-
"relationships": {},
|
|
2204
|
-
"regeneratedParts": { "mainDocument": "/word/document.xml", "relsMainDocument": "/word/_rels/document.xml.rels" }
|
|
2205
|
-
}
|
|
2206
|
-
},
|
|
2207
|
-
"diagnostics": { "items": [] }
|
|
2208
|
-
}
|
|
2209
|
-
```
|
|
2210
|
-
|
|
2211
|
-
#### Document with preserved unsupported content
|
|
2212
|
-
|
|
2213
|
-
```json
|
|
2214
|
-
{
|
|
2215
|
-
"schemaVersion": "cds/1.0.0",
|
|
2216
|
-
"docId": "1f5ef4e7-9e2c-4f77-9a4e-1a0d4f6c9c31",
|
|
2217
|
-
"createdAt": "2026-03-25T10:20:00.000Z",
|
|
2218
|
-
"updatedAt": "2026-03-25T10:20:00.000Z",
|
|
2219
|
-
"metadata": {
|
|
2220
|
-
"title": "Preservation Example",
|
|
2221
|
-
"actors": {
|
|
2222
|
-
"u1": { "actorId": "u1", "displayName": "Alex Editor" }
|
|
2223
|
-
}
|
|
2224
|
-
},
|
|
2225
|
-
"content": {
|
|
2226
|
-
"id": "doc",
|
|
2227
|
-
"type": "doc",
|
|
2228
|
-
"attrs": {},
|
|
2229
|
-
"children": [
|
|
2230
|
-
{
|
|
2231
|
-
"id": "p1",
|
|
2232
|
-
"type": "paragraph",
|
|
2233
|
-
"attrs": {},
|
|
2234
|
-
"children": [{ "id": "t1", "type": "text", "text": "Before preserved block.", "marks": [] }]
|
|
2235
|
-
},
|
|
2236
|
-
{
|
|
2237
|
-
"id": "x1",
|
|
2238
|
-
"type": "ooxmlBlock",
|
|
2239
|
-
"attrs": {
|
|
2240
|
-
"fragmentId": "frag_altcontent_1",
|
|
2241
|
-
"editability": "locked",
|
|
2242
|
-
"description": "Unsupported mc:AlternateContent payload"
|
|
2243
|
-
},
|
|
2244
|
-
"children": []
|
|
2245
|
-
},
|
|
2246
|
-
{
|
|
2247
|
-
"id": "p2",
|
|
2248
|
-
"type": "paragraph",
|
|
2249
|
-
"attrs": {},
|
|
2250
|
-
"children": [{ "id": "t2", "type": "text", "text": "After preserved block.", "marks": [] }]
|
|
2251
|
-
}
|
|
2252
|
-
]
|
|
2253
|
-
},
|
|
2254
|
-
"styles": { "defaults": {}, "paragraphStyles": {}, "characterStyles": {}, "tableStyles": {} },
|
|
2255
|
-
"numbering": { "abstractNums": {}, "nums": {} },
|
|
2256
|
-
"media": {
|
|
2257
|
-
"items": {},
|
|
2258
|
-
"exportPolicy": { "mediaFolder": "/word/media", "filenameStrategy": "stableByMediaId", "relationshipIdStrategy": "stableByOrder" }
|
|
2259
|
-
},
|
|
2260
|
-
"comments": { "threads": {}, "comments": {} },
|
|
2261
|
-
"revisions": { "trackRevisions": false, "items": {} },
|
|
2262
|
-
"preservation": {
|
|
2263
|
-
"fragments": {
|
|
2264
|
-
"frag_altcontent_1": {
|
|
2265
|
-
"fragmentId": "frag_altcontent_1",
|
|
2266
|
-
"kind": "xmlElement",
|
|
2267
|
-
"xmlns": { "mc": "http://schemas.openxmlformats.org/markup-compatibility/2006" },
|
|
2268
|
-
"xml": "<mc:AlternateContent mc:Ignorable=\"w14\"><mc:Choice Requires=\"w14\"><w14:someFutureFeature xmlns:w14=\"http://schemas.microsoft.com/office/word/2010/wordml\">...</w14:someFutureFeature></mc:Choice><mc:Fallback><w:r xmlns:w=\"http://schemas.openxmlformats.org/wordprocessingml/2006/main\"><w:t>Fallback</w:t></w:r></mc:Fallback></mc:AlternateContent>",
|
|
2269
|
-
"policy": "readOnly",
|
|
2270
|
-
"source": { "partName": "/word/document.xml", "xpath": "/w:document/w:body/*[2]" }
|
|
2271
|
-
}
|
|
2272
|
-
},
|
|
2273
|
-
"opc": {
|
|
2274
|
-
"contentTypesXmlBase64": "",
|
|
2275
|
-
"parts": {},
|
|
2276
|
-
"relationships": {},
|
|
2277
|
-
"regeneratedParts": { "mainDocument": "/word/document.xml", "relsMainDocument": "/word/_rels/document.xml.rels" }
|
|
2278
|
-
}
|
|
2279
|
-
},
|
|
2280
|
-
"diagnostics": {
|
|
2281
|
-
"items": [
|
|
2282
|
-
{
|
|
2283
|
-
"diagnosticId": "6d0b0b6a-8d5b-4d9b-9a39-8b1b1e1f2d2a",
|
|
2284
|
-
"severity": "warning",
|
|
2285
|
-
"code": "PRESERVE_LOCKED_BLOCK",
|
|
2286
|
-
"message": "Document contains preserved unsupported OOXML block fragment; it is locked and will be round-tripped verbatim.",
|
|
2287
|
-
"createdAt": "2026-03-25T10:20:00.000Z",
|
|
2288
|
-
"location": { "kind": "nodeId", "nodeId": "x1" }
|
|
2289
|
-
}
|
|
2290
|
-
]
|
|
2291
|
-
}
|
|
2292
|
-
}
|
|
2293
|
-
```
|
|
2294
|
-
|
|
2295
|
-
### Non-goals
|
|
2296
|
-
|
|
2297
|
-
The CDS explicitly does **not** attempt to represent:
|
|
2298
|
-
|
|
2299
|
-
- exact Word pagination, line breaking, or layout fidelity (these depend on Word’s layout engine and fonts),
|
|
2300
|
-
- advanced Word stories as editable content (headers, footers, footnotes, endnotes, text boxes); these are preserved as OPC parts unless explicitly modeled,
|
|
2301
|
-
- full fidelity for all OOXML constructs (content controls, complex fields, advanced DrawingML anchoring); unsupported constructs are preserved as opaque fragments or untouched parts,
|
|
2302
|
-
- collaborative CRDT semantics (this spec is transactional and mapping-based, single-thread main-thread execution).
|
|
2303
|
-
|
|
2304
|
-
### Output quality bar (normative acceptance criteria)
|
|
2305
|
-
|
|
2306
|
-
An implementation is conformant to this spec iff:
|
|
2307
|
-
|
|
2308
|
-
1. **Schema validity:** It can parse, validate, normalize, and serialize CDS documents matching these interfaces and invariants.
|
|
2309
|
-
2. **Deterministic normalization:** Running normalization twice yields identical JSON.
|
|
2310
|
-
3. **Anchor stability:** Comment and revision anchors remap through any transaction using the mapping rules, with orphaning only under defined conditions.
|
|
2311
|
-
4. **Word-openable export:** Exported `.docx` opens in Word without structural corruption for all modeled features, including:
|
|
2312
|
-
- paragraphs/runs/text/breaks, citeturn3search0turn3search1turn3search3
|
|
2313
|
-
- hyperlinks via `w:hyperlink`, citeturn1search2
|
|
2314
|
-
- comments via range markers and comment part linkage, citeturn0search8turn0search4turn0search0
|
|
2315
|
-
- tracked insertions/deletions via revision markup, citeturn1search0turn1search1turn10search3turn11search19
|
|
2316
|
-
- tables via `w:tbl` and grid/cell props where modeled. citeturn2search5turn4search7turn5search7
|
|
2317
|
-
5. **Preservation correctness:** Unsupported OOXML is preserved via locked fragments or untouched parts, including Markup Compatibility constructs. citeturn2search3turn2search7turn0search2
|