@beyondwork/docx-react-component 1.0.41 → 1.0.43

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (118) hide show
  1. package/package.json +38 -37
  2. package/src/api/awareness-identity-types.ts +35 -0
  3. package/src/api/comment-negotiation-types.ts +130 -0
  4. package/src/api/comment-presentation-types.ts +106 -0
  5. package/src/api/editor-state-types.ts +110 -0
  6. package/src/api/external-custody-types.ts +74 -0
  7. package/src/api/participants-types.ts +18 -0
  8. package/src/api/public-types.ts +541 -5
  9. package/src/api/scope-metadata-resolver-types.ts +88 -0
  10. package/src/core/commands/formatting-commands.ts +1 -1
  11. package/src/core/commands/index.ts +601 -9
  12. package/src/core/search/search-text.ts +15 -2
  13. package/src/index.ts +131 -1
  14. package/src/io/docx-session.ts +672 -2
  15. package/src/io/export/escape-xml-attribute.ts +26 -0
  16. package/src/io/export/external-send.ts +188 -0
  17. package/src/io/export/serialize-comments.ts +13 -16
  18. package/src/io/export/serialize-footnotes.ts +17 -24
  19. package/src/io/export/serialize-headers-footers.ts +17 -24
  20. package/src/io/export/serialize-main-document.ts +59 -62
  21. package/src/io/export/serialize-numbering.ts +20 -27
  22. package/src/io/export/serialize-runtime-revisions.ts +2 -9
  23. package/src/io/export/serialize-tables.ts +8 -15
  24. package/src/io/export/table-properties-xml.ts +25 -32
  25. package/src/io/import/external-reimport.ts +40 -0
  26. package/src/io/load-scheduler.ts +230 -0
  27. package/src/io/normalize/normalize-text.ts +83 -0
  28. package/src/io/ooxml/bw-xml.ts +244 -0
  29. package/src/io/ooxml/canonicalize-payload.ts +301 -0
  30. package/src/io/ooxml/comment-negotiation-payload.ts +288 -0
  31. package/src/io/ooxml/comment-presentation-payload.ts +311 -0
  32. package/src/io/ooxml/external-custody-payload.ts +102 -0
  33. package/src/io/ooxml/participants-payload.ts +97 -0
  34. package/src/io/ooxml/payload-signature.ts +112 -0
  35. package/src/io/ooxml/workflow-payload-validator.ts +367 -0
  36. package/src/io/ooxml/workflow-payload.ts +317 -7
  37. package/src/runtime/awareness-identity.ts +173 -0
  38. package/src/runtime/collab/event-types.ts +27 -0
  39. package/src/runtime/collab-session-bridge.ts +157 -0
  40. package/src/runtime/collab-session-facet.ts +193 -0
  41. package/src/runtime/collab-session.ts +273 -0
  42. package/src/runtime/comment-negotiation-sync.ts +91 -0
  43. package/src/runtime/comment-negotiation.ts +158 -0
  44. package/src/runtime/comment-presentation.ts +223 -0
  45. package/src/runtime/document-runtime.ts +639 -124
  46. package/src/runtime/editor-state-channel.ts +544 -0
  47. package/src/runtime/editor-state-integration.ts +217 -0
  48. package/src/runtime/external-send-runtime.ts +117 -0
  49. package/src/runtime/layout/docx-font-loader.ts +11 -30
  50. package/src/runtime/layout/index.ts +2 -0
  51. package/src/runtime/layout/inert-layout-facet.ts +4 -0
  52. package/src/runtime/layout/layout-engine-instance.ts +139 -14
  53. package/src/runtime/layout/page-graph.ts +79 -7
  54. package/src/runtime/layout/paginated-layout-engine.ts +441 -48
  55. package/src/runtime/layout/public-facet.ts +585 -14
  56. package/src/runtime/layout/table-row-split.ts +316 -0
  57. package/src/runtime/markdown-sanitizer.ts +132 -0
  58. package/src/runtime/participants.ts +134 -0
  59. package/src/runtime/perf-counters.ts +28 -0
  60. package/src/runtime/render/render-frame-types.ts +17 -0
  61. package/src/runtime/render/render-kernel.ts +172 -29
  62. package/src/runtime/resign-payload.ts +120 -0
  63. package/src/runtime/surface-projection.ts +10 -5
  64. package/src/runtime/tamper-gate.ts +157 -0
  65. package/src/runtime/workflow-markup.ts +80 -16
  66. package/src/runtime/workflow-rail-segments.ts +244 -5
  67. package/src/ui/WordReviewEditor.tsx +654 -45
  68. package/src/ui/editor-command-bag.ts +14 -0
  69. package/src/ui/editor-runtime-boundary.ts +111 -11
  70. package/src/ui/editor-shell-view.tsx +21 -0
  71. package/src/ui/editor-surface-controller.tsx +5 -0
  72. package/src/ui/headless/selection-helpers.ts +10 -0
  73. package/src/ui-tailwind/chrome/chrome-preset-model.ts +28 -0
  74. package/src/ui-tailwind/chrome/chrome-preset-toolbar.tsx +62 -2
  75. package/src/ui-tailwind/chrome/collab-audience-chip.tsx +73 -0
  76. package/src/ui-tailwind/chrome/collab-negotiation-action-bar.tsx +244 -0
  77. package/src/ui-tailwind/chrome/collab-presence-strip.tsx +150 -0
  78. package/src/ui-tailwind/chrome/collab-role-chip.tsx +62 -0
  79. package/src/ui-tailwind/chrome/collab-send-to-supplier-button.tsx +68 -0
  80. package/src/ui-tailwind/chrome/collab-send-to-supplier-modal.tsx +149 -0
  81. package/src/ui-tailwind/chrome/collab-tamper-banner.tsx +68 -0
  82. package/src/ui-tailwind/chrome/collab-top-nav-container.tsx +281 -0
  83. package/src/ui-tailwind/chrome/forward-non-drag-click.ts +104 -0
  84. package/src/ui-tailwind/chrome/tw-mode-dock.tsx +1 -0
  85. package/src/ui-tailwind/chrome/tw-selection-tool-host.tsx +7 -1
  86. package/src/ui-tailwind/chrome/tw-table-grip-layer.tsx +1 -38
  87. package/src/ui-tailwind/chrome-overlay/index.ts +6 -0
  88. package/src/ui-tailwind/chrome-overlay/scope-card-role-model.ts +78 -0
  89. package/src/ui-tailwind/chrome-overlay/scope-keyboard-cycle.ts +49 -0
  90. package/src/ui-tailwind/chrome-overlay/tw-chrome-overlay.tsx +106 -0
  91. package/src/ui-tailwind/chrome-overlay/tw-page-stack-overlay-layer.tsx +527 -0
  92. package/src/ui-tailwind/chrome-overlay/tw-scope-card-layer.tsx +120 -22
  93. package/src/ui-tailwind/chrome-overlay/tw-scope-card.tsx +310 -32
  94. package/src/ui-tailwind/chrome-overlay/tw-scope-rail-layer.tsx +93 -14
  95. package/src/ui-tailwind/editor-surface/paste-plain-text.ts +72 -0
  96. package/src/ui-tailwind/editor-surface/pm-command-bridge.ts +118 -8
  97. package/src/ui-tailwind/editor-surface/pm-decorations.ts +35 -1
  98. package/src/ui-tailwind/editor-surface/pm-page-break-decorations.ts +86 -3
  99. package/src/ui-tailwind/editor-surface/pm-schema.ts +167 -17
  100. package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +35 -7
  101. package/src/ui-tailwind/editor-surface/remote-cursor-plugin.ts +20 -3
  102. package/src/ui-tailwind/editor-surface/tw-page-block-view.helpers.ts +265 -0
  103. package/src/ui-tailwind/editor-surface/tw-page-block-view.tsx +10 -256
  104. package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +9 -0
  105. package/src/ui-tailwind/editor-surface/tw-table-node-view.tsx +66 -0
  106. package/src/ui-tailwind/index.ts +37 -1
  107. package/src/ui-tailwind/page-stack/tw-endnote-area.tsx +57 -0
  108. package/src/ui-tailwind/page-stack/tw-footnote-area.tsx +71 -0
  109. package/src/ui-tailwind/page-stack/tw-page-footer-band.tsx +73 -0
  110. package/src/ui-tailwind/page-stack/tw-page-header-band.tsx +74 -0
  111. package/src/ui-tailwind/page-stack/tw-page-stack-chrome-layer.tsx +477 -0
  112. package/src/ui-tailwind/page-stack/tw-region-block-renderer.tsx +374 -0
  113. package/src/ui-tailwind/review/comment-markdown-renderer.tsx +155 -0
  114. package/src/ui-tailwind/review/tw-comment-sidebar.tsx +77 -16
  115. package/src/ui-tailwind/review/tw-review-rail-footer.tsx +29 -3
  116. package/src/ui-tailwind/status/tw-status-bar.tsx +52 -1
  117. package/src/ui-tailwind/theme/editor-theme.css +25 -0
  118. package/src/ui-tailwind/tw-review-workspace.tsx +455 -118
package/package.json CHANGED
@@ -1,9 +1,8 @@
1
1
  {
2
2
  "name": "@beyondwork/docx-react-component",
3
3
  "publisher": "beyondwork",
4
- "version": "1.0.41",
4
+ "version": "1.0.43",
5
5
  "description": "Embeddable React Word (docx) editor with review, comments, tracked changes, and round-trip OOXML fidelity.",
6
- "packageManager": "pnpm@10.30.3",
7
6
  "type": "module",
8
7
  "sideEffects": [
9
8
  "**/*.css"
@@ -93,31 +92,6 @@
93
92
  "./ui-tailwind/theme/editor-theme.css": "./src/ui-tailwind/theme/editor-theme.css"
94
93
  },
95
94
  "types": "./src/index.ts",
96
- "scripts": {
97
- "build": "tsup",
98
- "test": "bash scripts/run-workspace-tests.sh",
99
- "test:repo": "node scripts/run-repo-tests.mjs core",
100
- "test:repo:all": "node scripts/run-repo-tests.mjs all",
101
- "test:repo:optional": "node scripts/run-repo-tests.mjs optional",
102
- "test:repo:browser-ui": "node scripts/run-repo-tests.mjs browser-ui",
103
- "test:wcag-audit": "node scripts/run-repo-tests.mjs wcag-audit",
104
- "test:harness": "pnpm --filter @docx-react-component/react-word-editor-harness test",
105
- "lint": "pnpm run lint:no-authored-js && pnpm run lint:docs-contracts && pnpm run lint:tsgo && pnpm run lint:tsgo:harness",
106
- "lint:docs-contracts": "bash scripts/check-reference-load-contract.sh",
107
- "lint:no-authored-js": "bash scripts/check-no-authored-js.sh",
108
- "lint:tsgo": "tsgo --noEmit -p tsconfig.build.json",
109
- "lint:tsgo:harness": "pnpm --filter @docx-react-component/react-word-editor-harness lint:tsgo",
110
- "context7:api-check": "bash scripts/context7-export-env.sh run bash scripts/context7-api-check.sh",
111
- "wave:doctor": "bash scripts/context7-export-env.sh run pnpm exec wave doctor --json",
112
- "wave:dry-run": "bash scripts/context7-export-env.sh run pnpm exec wave launch --lane main --dry-run --no-dashboard",
113
- "wave:launch": "bash scripts/context7-export-env.sh run pnpm exec wave launch --lane main",
114
- "wave:launch:managed": "bash scripts/wave-launch.sh",
115
- "wave:status": "bash scripts/wave-status.sh",
116
- "wave:watch": "bash scripts/wave-watch.sh --follow",
117
- "wave:dashboard:current": "bash scripts/wave-dashboard-attach.sh current",
118
- "wave:dashboard:global": "bash scripts/wave-dashboard-attach.sh global",
119
- "harness:dev": "pnpm --filter @docx-react-component/react-word-editor-harness dev"
120
- },
121
95
  "keywords": [
122
96
  "docx",
123
97
  "word",
@@ -162,22 +136,29 @@
162
136
  "react-dom": "^19.2.0",
163
137
  "tailwindcss": "^4.2.2",
164
138
  "yjs": "^13.6.0",
139
+ "y-prosemirror": "^1.2.0",
165
140
  "y-protocols": "^1.0.0"
166
141
  },
167
142
  "peerDependenciesMeta": {
168
143
  "yjs": {
169
144
  "optional": true
170
145
  },
146
+ "y-prosemirror": {
147
+ "optional": true
148
+ },
171
149
  "y-protocols": {
172
150
  "optional": true
173
151
  }
174
152
  },
175
153
  "devDependencies": {
176
154
  "@chllming/wave-orchestration": "^0.9.15",
155
+ "@playwright/test": "^1.59.1",
177
156
  "@types/react": "19.2.14",
178
157
  "@types/react-dom": "19.2.3",
179
158
  "@typescript/native-preview": "7.0.0-dev.20260409.1",
180
159
  "jsdom": "^29.0.1",
160
+ "pixelmatch": "^7.1.0",
161
+ "pngjs": "^7.0.0",
181
162
  "prosemirror-commands": "^1.7.1",
182
163
  "prosemirror-keymap": "^1.2.3",
183
164
  "prosemirror-model": "^1.25.4",
@@ -189,17 +170,37 @@
189
170
  "react-dom": "19.2.4",
190
171
  "tsup": "^8.3.0",
191
172
  "tsx": "^4.21.0",
173
+ "y-prosemirror": "^1.3.7",
192
174
  "y-protocols": "^1.0.7",
193
175
  "yjs": "^13.6.30"
194
176
  },
195
- "pnpm": {
196
- "onlyBuiltDependencies": [
197
- "esbuild",
198
- "sharp"
199
- ],
200
- "overrides": {
201
- "react": "19.2.4",
202
- "react-dom": "19.2.4"
203
- }
177
+ "scripts": {
178
+ "build": "tsup",
179
+ "test": "bash scripts/run-workspace-tests.sh",
180
+ "test:repo": "node scripts/run-repo-tests.mjs core",
181
+ "test:repo:all": "node scripts/run-repo-tests.mjs all",
182
+ "test:repo:optional": "node scripts/run-repo-tests.mjs optional",
183
+ "test:repo:browser-ui": "node scripts/run-repo-tests.mjs browser-ui",
184
+ "test:wcag-audit": "node scripts/run-repo-tests.mjs wcag-audit",
185
+ "test:harness": "pnpm --filter @docx-react-component/react-word-editor-harness test",
186
+ "test:visual": "VISUAL_SMOKE_PROFILE=bare pnpm exec playwright test --project=chromium",
187
+ "test:visual:chrome": "VISUAL_SMOKE_PROFILE=chrome-cycle pnpm exec playwright test --project=chromium",
188
+ "visual:list-runs": "node scripts/visual-smoke-list-runs.mjs",
189
+ "mcp:visual-smoke": "node scripts/visual-smoke-mcp.mjs",
190
+ "lint": "pnpm run lint:no-authored-js && pnpm run lint:docs-contracts && pnpm run lint:tsgo && pnpm run lint:tsgo:harness",
191
+ "lint:docs-contracts": "bash scripts/check-reference-load-contract.sh",
192
+ "lint:no-authored-js": "bash scripts/check-no-authored-js.sh",
193
+ "lint:tsgo": "tsgo --noEmit -p tsconfig.build.json",
194
+ "lint:tsgo:harness": "pnpm --filter @docx-react-component/react-word-editor-harness lint:tsgo",
195
+ "context7:api-check": "bash scripts/context7-export-env.sh run bash scripts/context7-api-check.sh",
196
+ "wave:doctor": "bash scripts/context7-export-env.sh run pnpm exec wave doctor --json",
197
+ "wave:dry-run": "bash scripts/context7-export-env.sh run pnpm exec wave launch --lane main --dry-run --no-dashboard",
198
+ "wave:launch": "bash scripts/context7-export-env.sh run pnpm exec wave launch --lane main",
199
+ "wave:launch:managed": "bash scripts/wave-launch.sh",
200
+ "wave:status": "bash scripts/wave-status.sh",
201
+ "wave:watch": "bash scripts/wave-watch.sh --follow",
202
+ "wave:dashboard:current": "bash scripts/wave-dashboard-attach.sh current",
203
+ "wave:dashboard:global": "bash scripts/wave-dashboard-attach.sh global",
204
+ "harness:dev": "pnpm --filter @docx-react-component/react-word-editor-harness dev"
204
205
  }
205
- }
206
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Typed shape carried in the Awareness `identity` field. One per
3
+ * connected client. Written by the local client on attach; read by
4
+ * every client to build `PresenceSnapshot`.
5
+ */
6
+ export interface AwarenessIdentity {
7
+ userId: string;
8
+ displayName: string;
9
+ collabIdentity?: string;
10
+ role: "author" | "reviewer" | "observer";
11
+ authorKind: "human" | "agent" | "system";
12
+ /** Identifier of the story the peer is currently looking at, if any. */
13
+ activeStoryId?: string;
14
+ }
15
+
16
+ export interface AwarenessPeer extends AwarenessIdentity {
17
+ /** `Awareness`-local client id. Advisory — hosts should prefer `userId`. */
18
+ clientId: number;
19
+ }
20
+
21
+ export type TransportStatus = "connected" | "syncing" | "offline";
22
+
23
+ export interface PresenceSnapshot {
24
+ peers: AwarenessPeer[];
25
+ transportStatus: TransportStatus;
26
+ /** Count of mutations queued locally while offline. */
27
+ queuedLocalEvents: number;
28
+ }
29
+
30
+ export interface CollabPosture {
31
+ role: "author" | "reviewer" | "observer";
32
+ transport: "none" | "attached";
33
+ /** Count of other connected peers (self excluded). */
34
+ peers: number;
35
+ }
@@ -0,0 +1,130 @@
1
+ export type CommentNegotiationState =
2
+ | "proposed"
3
+ | "negotiating"
4
+ | "accepted"
5
+ | "rejected"
6
+ | "resolved";
7
+
8
+ export interface NegotiationVote {
9
+ authorId: string;
10
+ verdict: "approve" | "reject" | "abstain";
11
+ castAt: string;
12
+ }
13
+
14
+ export interface NegotiationCounterProposal {
15
+ id: string;
16
+ authorId: string;
17
+ createdAt: string;
18
+ body: string;
19
+ proposedRangeEdit?: {
20
+ kind: "replace" | "insert" | "delete";
21
+ start: number;
22
+ end: number;
23
+ text?: string;
24
+ };
25
+ supersededBy?: string;
26
+ }
27
+
28
+ export type CommentNegotiationActionType =
29
+ | "propose-change"
30
+ | "counter-propose"
31
+ | "vote"
32
+ | "accept"
33
+ | "reject"
34
+ | "lock"
35
+ | "reopen";
36
+
37
+ export interface NegotiationHistoryRow {
38
+ from: CommentNegotiationState;
39
+ to: CommentNegotiationState;
40
+ actorId: string;
41
+ at: string;
42
+ action: CommentNegotiationActionType;
43
+ reasonCode?: string;
44
+ }
45
+
46
+ export interface CommentNegotiationEntry {
47
+ commentId: string;
48
+ state: CommentNegotiationState;
49
+ requiredApprovers: string[];
50
+ votes: NegotiationVote[];
51
+ counterProposals: NegotiationCounterProposal[];
52
+ acceptedProposalId?: string;
53
+ lockedAt?: string;
54
+ lockedBy?: string;
55
+ history: NegotiationHistoryRow[];
56
+ }
57
+
58
+ export interface CommentNegotiationSnapshot {
59
+ schemaVersion: 1;
60
+ entries: CommentNegotiationEntry[];
61
+ }
62
+
63
+ export type CommentNegotiationAction =
64
+ | {
65
+ type: "propose-change";
66
+ commentId: string;
67
+ actorId: string;
68
+ counterProposalId?: string;
69
+ }
70
+ | {
71
+ type: "counter-propose";
72
+ commentId: string;
73
+ authorId: string;
74
+ proposalId: string;
75
+ body: string;
76
+ createdAt: string;
77
+ proposedRangeEdit?: NegotiationCounterProposal["proposedRangeEdit"];
78
+ }
79
+ | {
80
+ type: "vote";
81
+ commentId: string;
82
+ authorId: string;
83
+ verdict: NegotiationVote["verdict"];
84
+ }
85
+ | {
86
+ type: "accept";
87
+ commentId: string;
88
+ actorId: string;
89
+ acceptedProposalId?: string;
90
+ }
91
+ | {
92
+ type: "reject";
93
+ commentId: string;
94
+ actorId: string;
95
+ reasonCode?: string;
96
+ }
97
+ | {
98
+ type: "lock";
99
+ commentId: string;
100
+ actorId: string;
101
+ }
102
+ | {
103
+ type: "reopen";
104
+ commentId: string;
105
+ actorId: string;
106
+ };
107
+
108
+ export type NegotiationRole = "author" | "reviewer" | "observer";
109
+
110
+ export type NegotiationBlockReason =
111
+ | "negotiation_invalid_transition"
112
+ | "negotiation_quorum_not_met"
113
+ | "collab_observer_readonly"
114
+ | "collab_role_restricted";
115
+
116
+ /**
117
+ * Superset of every `ok: false` reason the collab runtime surface can
118
+ * return. Kept in one place so hosts + agents can narrow once and
119
+ * handle every case.
120
+ *
121
+ * - `NegotiationBlockReason` — reducer-level role + state-machine checks.
122
+ * - `"metadata_tampered"` — raised by the tamper gate + external-send
123
+ * pipeline when `metadataIntegrity === "tampered"`.
124
+ * - `"collab_not_attached"` — raised by the session facet / bridge when
125
+ * callers dispatch before a `Y.Doc` is wired.
126
+ */
127
+ export type CollabBlockReason =
128
+ | NegotiationBlockReason
129
+ | "metadata_tampered"
130
+ | "collab_not_attached";
@@ -0,0 +1,106 @@
1
+ export type CommentAudience = "internal" | "external" | "shared";
2
+
3
+ export interface CommentMention {
4
+ userId: string;
5
+ displayName: string;
6
+ offsetInBody: number;
7
+ entryId?: string;
8
+ }
9
+
10
+ export interface CommentAttachment {
11
+ id: string;
12
+ kind: "image" | "file" | "link";
13
+ displayName: string;
14
+ mimeType?: string;
15
+ relationshipId?: string;
16
+ href?: string;
17
+ byteLength?: number;
18
+ width?: number;
19
+ height?: number;
20
+ }
21
+
22
+ export interface CommentReaction {
23
+ emoji: string;
24
+ authorId: string;
25
+ reactedAt: string;
26
+ }
27
+
28
+ export interface CommentLabel {
29
+ key: string;
30
+ text: string;
31
+ color?: string;
32
+ }
33
+
34
+ export interface CommentBody {
35
+ format: "markdown";
36
+ text: string;
37
+ digest: string;
38
+ sanitized?: boolean;
39
+ }
40
+
41
+ export interface CommentPresentationReply {
42
+ entryId: string;
43
+ body: CommentBody;
44
+ }
45
+
46
+ export interface CommentPresentation {
47
+ commentId: string;
48
+ audience: CommentAudience;
49
+ body: CommentBody;
50
+ replies: CommentPresentationReply[];
51
+ mentions: CommentMention[];
52
+ attachments: CommentAttachment[];
53
+ reactions: CommentReaction[];
54
+ labels: CommentLabel[];
55
+ }
56
+
57
+ export interface CommentPresentationSnapshot {
58
+ schemaVersion: 1;
59
+ entries: CommentPresentation[];
60
+ }
61
+
62
+ export type CommentPresentationAction =
63
+ | {
64
+ type: "set-body";
65
+ commentId: string;
66
+ text: string;
67
+ audience?: CommentAudience;
68
+ }
69
+ | {
70
+ type: "set-audience";
71
+ commentId: string;
72
+ audience: CommentAudience;
73
+ }
74
+ | {
75
+ type: "set-reply-body";
76
+ commentId: string;
77
+ entryId: string;
78
+ text: string;
79
+ }
80
+ | {
81
+ type: "add-mention";
82
+ commentId: string;
83
+ mention: CommentMention;
84
+ }
85
+ | {
86
+ type: "add-attachment";
87
+ commentId: string;
88
+ attachment: CommentAttachment;
89
+ }
90
+ | {
91
+ type: "remove-attachment";
92
+ commentId: string;
93
+ attachmentId: string;
94
+ }
95
+ | {
96
+ type: "toggle-reaction";
97
+ commentId: string;
98
+ authorId: string;
99
+ emoji: string;
100
+ now: string;
101
+ }
102
+ | {
103
+ type: "set-labels";
104
+ commentId: string;
105
+ labels: CommentLabel[];
106
+ };
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Editor-State Persistence Channel (schema 1.2).
3
+ *
4
+ * Makes the .docx act as a load/save API for editor-level overlay
5
+ * state (host annotations, workflow overlay, workflow metadata, work
6
+ * items, and future subsystems). Storage policy per subsystem is
7
+ * host-controlled across three locations:
8
+ *
9
+ * - `"in-document"` — full blob serialized into /customXml/item1.xml
10
+ * - `"rowstore"` — doc carries a key reference; host persists the blob
11
+ * - `"key-only"` — doc carries only a correlation key; host rehydrates
12
+ *
13
+ * See docs/plans/editor-state-persistence.md for the full design. 1.2
14
+ * extends schema 1.1 additively (unknown namespaces are preserved
15
+ * opaquely per existing forward-compat rules).
16
+ */
17
+ export type EditorStateNamespace =
18
+ | "hostAnnotations"
19
+ | "workflowOverlay"
20
+ | "workflowMetadata"
21
+ | "workItems";
22
+
23
+ export type EditorStateLocation = "in-document" | "rowstore" | "key-only";
24
+
25
+ export type EditorStateResolveErrorMode = "block" | "empty" | "retain-last-known";
26
+
27
+ /**
28
+ * Host-configured per-namespace persistence policy. Default (when
29
+ * absent) is {location: "in-document", onResolveError: "block",
30
+ * debounceMs: 250}, which preserves current behavior.
31
+ */
32
+ export interface EditorStatePolicyEntry {
33
+ location: EditorStateLocation;
34
+ /** Host-provided correlation key (e.g. review_session_id). Editor generates a UUID if absent. */
35
+ key?: string;
36
+ onResolveError?: EditorStateResolveErrorMode;
37
+ debounceMs?: number;
38
+ }
39
+
40
+ export type EditorStatePolicy = Partial<
41
+ Record<EditorStateNamespace, EditorStatePolicyEntry>
42
+ >;
43
+
44
+ /**
45
+ * Reference stored in the docx when the subsystem is backed by a
46
+ * host-owned store. The editor never interprets entryKey — it round-
47
+ * trips verbatim via resolver + persister callbacks.
48
+ */
49
+ export interface EditorStateStorageRef {
50
+ namespace: EditorStateNamespace;
51
+ location: Exclude<EditorStateLocation, "in-document">;
52
+ entryKey: string;
53
+ schemaVersion: string;
54
+ }
55
+
56
+ /**
57
+ * Typed blob the resolver returns and the persister accepts.
58
+ * `data` is the subsystem-specific payload (e.g. a HostAnnotationOverlay
59
+ * for the "hostAnnotations" namespace). Use `schemaVersion` to pin the
60
+ * blob shape so older hosts don't silently load newer blobs.
61
+ */
62
+ export interface EditorStateBlob {
63
+ namespace: EditorStateNamespace;
64
+ schemaVersion: string;
65
+ data: unknown;
66
+ }
67
+
68
+ /**
69
+ * Host-supplied pull-resolver. Returns `null` when the entry key is
70
+ * unknown or revoked. Matches the ScopeMetadataResolver undefined
71
+ * convention but allows null explicitly per the design doc §5 for
72
+ * symmetry with Promise<T | null> idioms. Either is acceptable as
73
+ * "not found"; internal code normalizes.
74
+ */
75
+ export type EditorStateResolver = (
76
+ namespace: EditorStateNamespace,
77
+ entryKey: string,
78
+ ) => Promise<EditorStateBlob | null>;
79
+
80
+ /**
81
+ * Host-supplied persister. Called after the editor's debounce window
82
+ * when a subsystem's state has changed under `rowstore` or `key-only`
83
+ * policy. Must be idempotent within the debounce window.
84
+ */
85
+ export type EditorStatePersister = (
86
+ namespace: EditorStateNamespace,
87
+ entryKey: string,
88
+ blob: EditorStateBlob,
89
+ ) => Promise<void>;
90
+
91
+ export interface EditorStatePolicyMigration {
92
+ namespace: EditorStateNamespace;
93
+ from: EditorStateLocation;
94
+ to: EditorStateLocation;
95
+ key?: string;
96
+ }
97
+
98
+ export interface EditorStatePartLoadFailure {
99
+ namespace: EditorStateNamespace;
100
+ entryKey?: string;
101
+ error: Error;
102
+ fallback: "empty" | "retain-last-known";
103
+ }
104
+
105
+ export interface EditorStatePartPersistFailure {
106
+ namespace: EditorStateNamespace;
107
+ entryKey: string;
108
+ error: Error;
109
+ attemptCount: number;
110
+ }
@@ -0,0 +1,74 @@
1
+ import type {
2
+ CommentNegotiationEntry,
3
+ } from "./comment-negotiation-types.ts";
4
+ import type {
5
+ CommentPresentation,
6
+ } from "./comment-presentation-types.ts";
7
+ import type { Participant } from "./participants-types.ts";
8
+
9
+ /**
10
+ * Custody receipt attached to a docx when internal comments have been
11
+ * stripped out and sent to an external recipient (supplier). The receipt
12
+ * rides inside `bw:extensions/bw:externalCustody` and points at an
13
+ * opaque host archive that a later re-import uses to restore the
14
+ * stripped content.
15
+ *
16
+ * See docs/reference/bw-collab-schema-additions.md §bw:externalCustody
17
+ * for the normative schema.
18
+ */
19
+ export interface ExternalCustody {
20
+ schemaVersion: 1;
21
+ custodyId: string;
22
+ originDocumentId: string;
23
+ originPayloadId: string;
24
+ /** sha256:{hex} of canonicalized word/document.xml at send time. */
25
+ originContentHash: string;
26
+ sentAt: string;
27
+ sentBy: string;
28
+ /** Opaque recipient identifier (supplier user or orgId). */
29
+ recipient: string;
30
+ /**
31
+ * Opaque host-owned reference the resolver uses to fetch the
32
+ * stripped-out internal content on re-upload. Not interpreted by
33
+ * this library.
34
+ */
35
+ archiveRef: string;
36
+ strippedCommentIds: string[];
37
+ strippedParticipantIds: string[];
38
+ }
39
+
40
+ /**
41
+ * Payload handed to the host resolver when internal content is archived
42
+ * during `sendToExternal`. The host persists this opaque bundle keyed
43
+ * by `custodyId` for later `restore` calls.
44
+ */
45
+ export interface ExternalCustodyArchivePayload {
46
+ custodyId: string;
47
+ strippedPresentation: CommentPresentation[];
48
+ strippedNegotiation: CommentNegotiationEntry[];
49
+ strippedParticipants: Participant[];
50
+ originContentHash: string;
51
+ }
52
+
53
+ /**
54
+ * Content returned by the host resolver on `restore`. Same shape as
55
+ * what was archived, minus the custody ID.
56
+ */
57
+ export interface ExternalCustodyRestoredContent {
58
+ presentation: CommentPresentation[];
59
+ negotiation: CommentNegotiationEntry[];
60
+ participants: Participant[];
61
+ }
62
+
63
+ /**
64
+ * Host-owned contract. The editor hands archive + restore calls through
65
+ * this so the library never directly persists internal content.
66
+ */
67
+ export interface ExternalCustodyResolver {
68
+ archive(payload: ExternalCustodyArchivePayload): Promise<void>;
69
+ restore(args: {
70
+ custodyId: string;
71
+ originContentHash: string;
72
+ }): Promise<ExternalCustodyRestoredContent | undefined>;
73
+ delete?(custodyId: string): Promise<void>;
74
+ }
@@ -0,0 +1,18 @@
1
+ export type ParticipantRole = "author" | "reviewer" | "observer";
2
+ export type AuthorKind = "human" | "agent" | "system";
3
+
4
+ export interface Participant {
5
+ userId: string;
6
+ email: string;
7
+ displayName: string;
8
+ collabIdentity: string;
9
+ authorKind: AuthorKind;
10
+ role?: ParticipantRole;
11
+ organization?: string;
12
+ avatarHref?: string;
13
+ }
14
+
15
+ export interface ParticipantRoster {
16
+ schemaVersion: 1;
17
+ entries: Participant[];
18
+ }