@sobree/core 0.1.0

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 (127) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +106 -0
  3. package/dist/__vite-browser-external-DYxpcVy9.js +5 -0
  4. package/dist/__vite-browser-external-DYxpcVy9.js.map +1 -0
  5. package/dist/blob/cache.d.ts +69 -0
  6. package/dist/blob/fetch.d.ts +18 -0
  7. package/dist/blob/hash.d.ts +13 -0
  8. package/dist/blob/index.d.ts +33 -0
  9. package/dist/blob/memory.d.ts +2 -0
  10. package/dist/blob/types.d.ts +80 -0
  11. package/dist/createSobree.d.ts +132 -0
  12. package/dist/doc/api.d.ts +132 -0
  13. package/dist/doc/builders.d.ts +42 -0
  14. package/dist/doc/pageSetupBridge.d.ts +26 -0
  15. package/dist/doc/parts.d.ts +18 -0
  16. package/dist/doc/runs.d.ts +47 -0
  17. package/dist/doc/styles.d.ts +19 -0
  18. package/dist/doc/types.d.ts +800 -0
  19. package/dist/doc/walk.d.ts +30 -0
  20. package/dist/docx/export/contentTypes.d.ts +35 -0
  21. package/dist/docx/export/context.d.ts +59 -0
  22. package/dist/docx/export/document.d.ts +19 -0
  23. package/dist/docx/export/drawings.d.ts +10 -0
  24. package/dist/docx/export/headers.d.ts +19 -0
  25. package/dist/docx/export/index.d.ts +14 -0
  26. package/dist/docx/export/runs.d.ts +8 -0
  27. package/dist/docx/export/styles.d.ts +8 -0
  28. package/dist/docx/export/zip.d.ts +13 -0
  29. package/dist/docx/import/anchoredFrames.d.ts +34 -0
  30. package/dist/docx/import/comments.d.ts +3 -0
  31. package/dist/docx/import/document.d.ts +57 -0
  32. package/dist/docx/import/flowFrames.d.ts +11 -0
  33. package/dist/docx/import/footnotes.d.ts +3 -0
  34. package/dist/docx/import/headers.d.ts +50 -0
  35. package/dist/docx/import/index.d.ts +12 -0
  36. package/dist/docx/import/inlineFrames.d.ts +62 -0
  37. package/dist/docx/import/numbering.d.ts +2 -0
  38. package/dist/docx/import/paragraph.d.ts +24 -0
  39. package/dist/docx/import/paragraphs.d.ts +27 -0
  40. package/dist/docx/import/rels.d.ts +5 -0
  41. package/dist/docx/import/runs.d.ts +64 -0
  42. package/dist/docx/import/settings.d.ts +48 -0
  43. package/dist/docx/import/styles.d.ts +3 -0
  44. package/dist/docx/import/tables.d.ts +12 -0
  45. package/dist/docx/import/unzip.d.ts +13 -0
  46. package/dist/docx/shared/namespaces.d.ts +31 -0
  47. package/dist/docx/shared/pageSize.d.ts +27 -0
  48. package/dist/docx/shared/shading.d.ts +2 -0
  49. package/dist/docx/shared/units.d.ts +35 -0
  50. package/dist/docx/shared/xml.d.ts +29 -0
  51. package/dist/docx/types.d.ts +98 -0
  52. package/dist/editor/index.d.ts +1078 -0
  53. package/dist/editor/internal/blockRegistry.d.ts +91 -0
  54. package/dist/editor/internal/mutations.d.ts +63 -0
  55. package/dist/editor/internal/positionMap.d.ts +35 -0
  56. package/dist/editor/table.d.ts +96 -0
  57. package/dist/editor/view/docRenderer/anchorLayer.d.ts +26 -0
  58. package/dist/editor/view/docRenderer/block.d.ts +13 -0
  59. package/dist/editor/view/docRenderer/fontFallback.d.ts +28 -0
  60. package/dist/editor/view/docRenderer/index.d.ts +18 -0
  61. package/dist/editor/view/docRenderer/inline.d.ts +15 -0
  62. package/dist/editor/view/docRenderer/inlineFrame.d.ts +4 -0
  63. package/dist/editor/view/docRenderer/lists.d.ts +28 -0
  64. package/dist/editor/view/docRenderer/paragraph.d.ts +2 -0
  65. package/dist/editor/view/docRenderer/properties.d.ts +2 -0
  66. package/dist/editor/view/docRenderer/table.d.ts +15 -0
  67. package/dist/editor/view/docRenderer/units.d.ts +48 -0
  68. package/dist/editor/view/docSerialize/block.d.ts +14 -0
  69. package/dist/editor/view/docSerialize/index.d.ts +8 -0
  70. package/dist/editor/view/docSerialize/inline.d.ts +11 -0
  71. package/dist/editor/view/docSerialize/table.d.ts +12 -0
  72. package/dist/editor/view/imageResize.d.ts +16 -0
  73. package/dist/embed/floatingCorner.d.ts +44 -0
  74. package/dist/embed/viewport.d.ts +133 -0
  75. package/dist/fonts/embedAPI.d.ts +33 -0
  76. package/dist/fonts/emit.d.ts +24 -0
  77. package/dist/fonts/fontFaceRegistry.d.ts +20 -0
  78. package/dist/fonts/fsType.d.ts +36 -0
  79. package/dist/fonts/index.d.ts +19 -0
  80. package/dist/fonts/liveness.d.ts +2 -0
  81. package/dist/fonts/odttf.d.ts +33 -0
  82. package/dist/fonts/parse.d.ts +29 -0
  83. package/dist/fonts/types.d.ts +52 -0
  84. package/dist/headless.d.ts +168 -0
  85. package/dist/history/history.d.ts +100 -0
  86. package/dist/history/index.d.ts +4 -0
  87. package/dist/history/types.d.ts +54 -0
  88. package/dist/index.css +1 -0
  89. package/dist/index.d.ts +52 -0
  90. package/dist/index.js +10561 -0
  91. package/dist/index.js.map +1 -0
  92. package/dist/markdown/parse.d.ts +6 -0
  93. package/dist/pagination/cost.d.ts +32 -0
  94. package/dist/pagination/index.d.ts +2 -0
  95. package/dist/pagination/paginate.d.ts +10 -0
  96. package/dist/pagination/postConditions.d.ts +10 -0
  97. package/dist/pagination/types.d.ts +94 -0
  98. package/dist/paperStack/pageSetup.d.ts +42 -0
  99. package/dist/paperStack/paginationAdapter/buildItems.d.ts +19 -0
  100. package/dist/paperStack/paginationAdapter/distribute.d.ts +23 -0
  101. package/dist/paperStack/paginationAdapter/index.d.ts +18 -0
  102. package/dist/paperStack/paginationAdapter/paragraphLines.d.ts +23 -0
  103. package/dist/paperStack/paginationAdapter/splitList.d.ts +19 -0
  104. package/dist/paperStack/paginationAdapter/splitParagraph.d.ts +21 -0
  105. package/dist/paperStack/paginationAdapter/types.d.ts +30 -0
  106. package/dist/paperStack/paper.d.ts +107 -0
  107. package/dist/paperStack/paperStack.d.ts +245 -0
  108. package/dist/plugin.d.ts +24 -0
  109. package/dist/plugins/marks.d.ts +49 -0
  110. package/dist/plugins/sections.d.ts +15 -0
  111. package/dist/presence/attach.d.ts +48 -0
  112. package/dist/presence/awareness.d.ts +28 -0
  113. package/dist/presence/index.d.ts +19 -0
  114. package/dist/presence/overlay.d.ts +28 -0
  115. package/dist/presence/state.d.ts +36 -0
  116. package/dist/sobree.d.ts +211 -0
  117. package/dist/tokens.css +144 -0
  118. package/dist/util/selection.d.ts +13 -0
  119. package/dist/ydoc/apply.d.ts +68 -0
  120. package/dist/ydoc/index.d.ts +18 -0
  121. package/dist/ydoc/project.d.ts +41 -0
  122. package/dist/ydoc/runs.d.ts +51 -0
  123. package/dist/ydoc/schema.d.ts +123 -0
  124. package/dist/ydoc/seed.d.ts +45 -0
  125. package/dist/ydoc/textDiff.d.ts +59 -0
  126. package/dist/zoneEdit/index.d.ts +22 -0
  127. package/package.json +61 -0
@@ -0,0 +1,211 @@
1
+ import { PageSetup } from './paperStack/pageSetup';
2
+ import { Editor, OutlineItem, TrackChangesState } from './editor';
3
+ import { SobreeDocument } from './doc/types';
4
+ export type SobreeMode = "edit" | "read";
5
+ export type SobreeEvent = "change" | "paginate" | "setup" | "mode-change" | "track-changes-change" | "docx:import" | "docx:export";
6
+ export interface SobreeEventPayload {
7
+ change: {
8
+ doc: SobreeDocument;
9
+ /** @deprecated Use `doc`. Alias kept for backwards compatibility. */
10
+ document: SobreeDocument;
11
+ revision: number;
12
+ };
13
+ paginate: {
14
+ pageCount: number;
15
+ };
16
+ setup: {
17
+ setup: PageSetup;
18
+ };
19
+ "mode-change": {
20
+ mode: SobreeMode;
21
+ };
22
+ /**
23
+ * Fires when track-changes mode flips on/off or the author changes.
24
+ * Re-emitted from the underlying editor — `Sobree.setTrackChanges`
25
+ * delegates to `editor.setTrackChanges`, so listeners on either
26
+ * surface see the same events.
27
+ */
28
+ "track-changes-change": TrackChangesState;
29
+ "docx:import": {
30
+ warnings: string[];
31
+ };
32
+ "docx:export": {
33
+ warnings: string[];
34
+ };
35
+ }
36
+ export type SobreeUnsubscribe = () => void;
37
+ export interface SobreeOptions {
38
+ /** Initial document AST. */
39
+ initialDocument?: SobreeDocument;
40
+ /** Page setup. Falls back to `DEFAULT_PAGE_SETUP`. */
41
+ pageSetup?: PageSetup;
42
+ /** Forwarded to the underlying Editor. */
43
+ changeDebounceMs?: number;
44
+ /**
45
+ * Y.Doc backing the document. Forwarded to the Editor — see
46
+ * `EditorOptions.ydoc` for the contract. Use this when you want to
47
+ * attach a provider (`y-websocket`, `y-indexeddb`, `y-webrtc`) for
48
+ * persistence or collaboration. If absent, the editor creates one
49
+ * internally (still observable via `sobree.editor.ydoc`).
50
+ */
51
+ ydoc?: import('yjs').Doc;
52
+ /**
53
+ * Optional content-hashed `BlobStore` for binary parts. Forwarded
54
+ * to the Editor — see `EditorOptions.blobStore`.
55
+ */
56
+ blobStore?: import('./blob').BlobStore;
57
+ /**
58
+ * Initial track-changes mode. Forwarded to the editor — see
59
+ * `TrackChangesState`. When omitted, the editor starts in
60
+ * direct-edit mode and embedders flip it later with
61
+ * `sobree.setTrackChanges(...)`.
62
+ */
63
+ trackChanges?: TrackChangesState;
64
+ }
65
+ /**
66
+ * Top-level embeddable product surface. Composes a framework-free `Editor`
67
+ * with a paginated `PaperStack`, exposing a single wire-ready API for
68
+ * hosting webapps, headless Y peers (HeadlessSobree), and agents.
69
+ *
70
+ * Everything on this class is JSON-clean: plain data in, plain data out.
71
+ * No DOM nodes, Ranges, or function handles cross the public surface.
72
+ */
73
+ export declare class Sobree {
74
+ readonly editor: Editor;
75
+ private readonly stack;
76
+ private setup;
77
+ private mode;
78
+ private readonly listeners;
79
+ private readonly detachPaginate;
80
+ private readonly detachChange;
81
+ private readonly detachTrackChanges;
82
+ /** Detachers for default + user-provided plugins, run in reverse on
83
+ * `destroy` so attach order is mirrored on teardown. */
84
+ private readonly pluginDetachers;
85
+ constructor(container: HTMLElement, options?: SobreeOptions);
86
+ /** Internal stack element — useful for attaching context tools / viewport. */
87
+ get stackRoot(): HTMLElement;
88
+ /** First paper element — useful as a viewport fit target on first layout. */
89
+ get firstPaper(): HTMLElement;
90
+ /**
91
+ * First paper's outer ROW (paper card + per-page comments sidebar).
92
+ * Use as `fitWidthTarget` so the viewport's fit-to-width scales to
93
+ * include the sidebar — fitting just `.paper` would leave the
94
+ * sidebar clipped by the viewport's overflow.
95
+ */
96
+ get firstPaperRow(): HTMLElement;
97
+ /** Pass through for Viewport's renderTier callback. */
98
+ setRenderTier(tier: number): void;
99
+ /** Current page setup for section 0 (JSON-clean). */
100
+ getPageSetup(): PageSetup;
101
+ /** Number of sections in the current document. Always >= 1. */
102
+ getSectionCount(): number;
103
+ /**
104
+ * Read section `index` projected onto the demo's `PageSetup` shape so
105
+ * the same Page Setup modal can edit any section. Section 0 returns
106
+ * the live `setup`; other sections are projected from the AST.
107
+ *
108
+ * Lossy for properties the bridge doesn't carry (e.g. per-section
109
+ * pages-per-column will surface here as default), but enough for the
110
+ * fields the modal exposes today.
111
+ */
112
+ getSectionSetup(index: number): PageSetup;
113
+ /**
114
+ * Write back to section `index`. Section 0 funnels through
115
+ * `setPageSetup` (the canonical path); section 1+ goes through the
116
+ * editor's `setDocument` so it round-trips and triggers the change
117
+ * pipeline (repagination, listeners).
118
+ */
119
+ setSectionSetup(index: number, partial: Partial<PageSetup>): void;
120
+ /**
121
+ * Merge `partial` into the current page setup. Triggers repagination and
122
+ * fires `setup` event. Plain-data argument — safe over the wire.
123
+ */
124
+ setPageSetup(partial: Partial<PageSetup>): void;
125
+ /** Project the current `setup` into `doc.sections[0]` and commit. */
126
+ private writeSetupToSection0;
127
+ /** Current rendered page count. */
128
+ getPageCount(): number;
129
+ /**
130
+ * Force a repagination. Normally unnecessary — repagination runs after
131
+ * every `change` event. Useful for hosts that need to ensure pagination
132
+ * after initial mount (layout must be applied first — call from a
133
+ * `requestAnimationFrame` after the container is in the DOM).
134
+ */
135
+ repaginate(): void;
136
+ /** Delegate to `editor.getOutline()`. */
137
+ getOutline(): OutlineItem[];
138
+ /** Current mode. Default is `"edit"`. */
139
+ getMode(): SobreeMode;
140
+ /**
141
+ * Switch between edit and read mode. Read mode turns off
142
+ * `contenteditable`, tags the stack root with `is-read-mode` (so the
143
+ * embedder / block tools can hide indicators and toolbars via CSS or
144
+ * by listening to `mode-change`), and fires the `mode-change` event.
145
+ *
146
+ * Selection remains functional for copy / outline / accessibility;
147
+ * only editing is suspended.
148
+ */
149
+ setMode(mode: SobreeMode): void;
150
+ /**
151
+ * Current track-changes state. See `TrackChangesState`.
152
+ *
153
+ * Thin proxy to `editor.getTrackChanges()` — exposed on the façade
154
+ * so embedders driving the UI don't need to reach `sobree.editor`.
155
+ */
156
+ getTrackChanges(): TrackChangesState;
157
+ /**
158
+ * Switch authoring mode. Delegates to `editor.setTrackChanges`,
159
+ * which fires `track-changes-change`. Listeners attached to either
160
+ * `editor.on("track-changes-change", …)` or
161
+ * `sobree.on("track-changes-change", …)` receive the new state.
162
+ *
163
+ * The mode survives until flipped — it's not bound to selection or
164
+ * document. To run a one-off tracked edit, save the previous state,
165
+ * flip on, mutate, flip back.
166
+ */
167
+ setTrackChanges(state: TrackChangesState): void;
168
+ /**
169
+ * Read a .docx file and load it into the editor. Fully native: the
170
+ * parsed `SobreeDocument` is handed straight to the editor — no djot
171
+ * intermediate.
172
+ */
173
+ openDocx(src: File | Blob | ArrayBuffer | Uint8Array): Promise<void>;
174
+ /**
175
+ * Export the current document as a .docx Blob. Reads the editor's AST
176
+ * directly, overlays the current page setup's section/header/footer,
177
+ * and serialises to OOXML.
178
+ */
179
+ exportDocx(): Blob;
180
+ on<E extends SobreeEvent>(event: E, cb: (payload: SobreeEventPayload[E]) => void): SobreeUnsubscribe;
181
+ /**
182
+ * Compose the sections array the paper stack needs:
183
+ *
184
+ * sections[0] is built from the live `setup` (the demo's UI model
185
+ * represents section 0; setPageSetup updates flow through here).
186
+ * sections[1..] come straight from the document AST — there's no UI
187
+ * for editing them yet, so they're effectively read-only until a
188
+ * future Page Setup section selector lands.
189
+ *
190
+ * Called whenever either source could have changed: constructor,
191
+ * `change` event, `setPageSetup`.
192
+ */
193
+ private syncStackSections;
194
+ /**
195
+ * Re-derive `setup` from `doc.sections[0]` when they diverge.
196
+ *
197
+ * Why: every page-setup edit goes setup → AST via
198
+ * `writeSetupToSection0`. But the inverse — AST → setup — only ran
199
+ * at construction time. That left the renderer's `setup` stuck on
200
+ * the post-edit state when the AST got reverted by undo / redo /
201
+ * external Y-provider edits, so `Cmd+Z` for a margin change reverted
202
+ * the AST silently while the paper kept the new margins.
203
+ *
204
+ * The `sameSetup` early-out is load-bearing — without it, the
205
+ * `change` event fired by `editor.setDocument` inside
206
+ * `writeSetupToSection0` would loop back into here, see the same
207
+ * setup, and waste a re-paginate.
208
+ */
209
+ private syncSetupFromDocument;
210
+ destroy(): void;
211
+ }
@@ -0,0 +1,144 @@
1
+ /* Carlito — Google's open-source, metric-compatible clone of Calibri.
2
+ * MUST be the first rule in this file: CSS `@import` only fires when
3
+ * it precedes every other declaration. Most Sobree-rendered docs are
4
+ * authored in Word with Calibri as the body font; without Calibri or
5
+ * Carlito on the host, browsers fall back to Helvetica/Arial whose
6
+ * ellipsis (…) glyph is roughly 2× wider than Calibri's. A 60-char
7
+ * dot-leader bullet ("……………") that fits one line in Word then
8
+ * overflows or wraps in Sobree.
9
+ *
10
+ * We pull Carlito from Google Fonts so any embedder that imports
11
+ * `@sobree/core/tokens.css` gets Word-fidelity ellipsis (and accurate
12
+ * line metrics) without bundling the ~200 KB font in the package
13
+ * itself. Google Fonts serves subset WOFF2 files (~50 KB per weight)
14
+ * with permissive caching. `font-display: swap` means text reads
15
+ * immediately while Carlito loads — no flash of invisible text.
16
+ *
17
+ * If the host is offline / the CDN is blocked, the cascade falls
18
+ * through to Helvetica → Arial (wider ellipsis but still readable).
19
+ * For deployments that need fully-bundled fonts, override this @import
20
+ * with a local woff2 in the embedder's own CSS.
21
+ */
22
+ @import url("https://fonts.googleapis.com/css2?family=Carlito:ital,wght@0,400;0,700;1,400;1,700&display=swap");
23
+
24
+ /* @sobree/core — design tokens (the single source of truth for the brand).
25
+ *
26
+ * Consume from CSS:
27
+ *
28
+ * @import "@sobree/core/tokens.css";
29
+ *
30
+ * Or from JS / TS:
31
+ *
32
+ * import "@sobree/core/tokens.css";
33
+ *
34
+ * Add new tokens here, not in component CSS — keeps the brand coherent
35
+ * and makes theming a single edit.
36
+ */
37
+
38
+ :root {
39
+ /* Primary — Sobree Amber. */
40
+ --sobree-50: #fdf6ee;
41
+ --sobree-100: #f9e6d0;
42
+ --sobree-500: #c96f22;
43
+ --sobree-600: #b2591a;
44
+
45
+ /* Neutrals — warm ink, not gray. */
46
+ --ink-0: #ffffff;
47
+ --ink-25: #fbfaf7;
48
+ --ink-50: #f6f4ef;
49
+ --ink-100: #eceae3;
50
+ --ink-200: #dedbd0;
51
+ --ink-300: #c4bfaf;
52
+ --ink-400: #a29c88;
53
+ --ink-500: #7c7764;
54
+ --ink-600: #5a5649;
55
+ --ink-700: #3d3a31;
56
+ --ink-800: #26241f;
57
+ --ink-900: #14130f;
58
+
59
+ /* Semantic tokens — what components reference. */
60
+ --bg: var(--ink-25);
61
+ --bg-elevated: var(--ink-0);
62
+ --bg-subtle: var(--ink-50);
63
+ --bg-hover: var(--ink-100);
64
+ --fg: var(--ink-800);
65
+ --fg-strong: var(--ink-900);
66
+ --fg-muted: var(--ink-600);
67
+ --fg-subtle: var(--ink-500);
68
+ --fg-on-primary: var(--ink-0);
69
+ --border: var(--ink-200);
70
+ --border-strong: var(--ink-300);
71
+ --primary: var(--sobree-500);
72
+ --primary-hover: var(--sobree-600);
73
+ --primary-soft: var(--sobree-50);
74
+
75
+ /* Radii. */
76
+ --radius-xs: 2px;
77
+ --radius-sm: 2px;
78
+ --radius-md: 4px;
79
+ --radius-lg: 6px;
80
+
81
+ /* Shadows — warm-ink tinted, never gray. */
82
+ --shadow-xs: 0 1px 0 rgba(20, 19, 15, 0.04);
83
+ --shadow-sm: 0 1px 2px rgba(20, 19, 15, 0.06), 0 1px 1px rgba(20, 19, 15, 0.04);
84
+ --shadow-md: 0 4px 8px -2px rgba(20, 19, 15, 0.08), 0 2px 4px -2px rgba(20, 19, 15, 0.05);
85
+ --shadow-lg: 0 12px 24px -8px rgba(20, 19, 15, 0.12), 0 4px 8px -4px rgba(20, 19, 15, 0.06);
86
+
87
+ /* Focus ring — amber glow. */
88
+ --ring-focus: 0 0 0 3px rgba(201, 111, 34, 0.25);
89
+
90
+ /* Motion. */
91
+ --ease-standard: cubic-bezier(0.2, 0, 0, 1);
92
+ --dur-fast: 120ms;
93
+ --dur-base: 180ms;
94
+ --dur-slow: 280ms;
95
+
96
+ /* Spacing — 4px base. */
97
+ --space-1: 4px;
98
+ --space-2: 8px;
99
+ --space-3: 12px;
100
+ --space-4: 16px;
101
+ --space-5: 20px;
102
+ --space-6: 24px;
103
+
104
+ /* Back-compat aliases for older component CSS. */
105
+ --sobree-primary: var(--sobree-500);
106
+ --sobree-primary-soft: var(--primary-soft);
107
+ --sobree-border: var(--border);
108
+
109
+ /* === Tracked changes & comments ===
110
+ *
111
+ * The renderer emits semantic `<ins>` / `<del>` / comment markup in
112
+ * core (visibility is document fidelity, not an opt-in feature).
113
+ * These tokens make the *styling* themeable — override them in a
114
+ * consumer stylesheet instead of fighting component CSS specificity.
115
+ */
116
+
117
+ /* Per-author palette — 8 slots. `colorForAuthor()` hashes an author
118
+ * name to a slot index; the renderer references the matching token.
119
+ * Chosen for similar luminance (no author looks "louder") and to
120
+ * avoid pure red (clashes with deletion strikethrough) / pure yellow
121
+ * (clashes with the comment-range highlight). */
122
+ --sobree-author-0: #1f77b4; /* muted blue */
123
+ --sobree-author-1: #2ca02c; /* green */
124
+ --sobree-author-2: #9467bd; /* purple */
125
+ --sobree-author-3: #8c564b; /* brown */
126
+ --sobree-author-4: #e377c2; /* pink */
127
+ --sobree-author-5: #17becf; /* teal */
128
+ --sobree-author-6: #bcbd22; /* olive */
129
+ --sobree-author-7: #ff7f0e; /* orange */
130
+
131
+ /* Revision (tracked-change) styling. The author colour drives the
132
+ * text + decoration; `--sobree-revision-tint` is the background
133
+ * mix-strength applied via `color-mix`. */
134
+ --sobree-revision-ins-fallback: #0a7d2a; /* used when no author colour */
135
+ --sobree-revision-del-fallback: #b3261e;
136
+ --sobree-revision-tint: 8%;
137
+
138
+ /* Comment styling. */
139
+ --sobree-comment-range-bg: rgba(255, 217, 0, 0.25);
140
+ --sobree-comment-range-border: rgba(180, 130, 0, 0.5);
141
+ --sobree-comment-accent: #f59e0b; /* card rule when author colour absent */
142
+ --sobree-comment-resolved: #16a34a; /* resolved-thread green */
143
+ }
144
+
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Save and restore the current document selection as a pair of (node, offset)
3
+ * pairs. Works across block re-parenting because text nodes are referenced
4
+ * directly — only breaks when the saved node is actually removed from the DOM.
5
+ */
6
+ export interface SavedSelection {
7
+ startContainer: Node;
8
+ startOffset: number;
9
+ endContainer: Node;
10
+ endOffset: number;
11
+ }
12
+ export declare function saveSelection(): SavedSelection | null;
13
+ export declare function restoreSelection(saved: SavedSelection | null): void;
@@ -0,0 +1,68 @@
1
+ import { SobreeDocument } from '../doc/types';
2
+ import * as Y from "yjs";
3
+ /**
4
+ * Apply a new SobreeDocument to a Y.Doc, diffing against the current
5
+ * Y state by **block id**. The caller supplies the new id-per-block
6
+ * array (typically derived from the BlockRegistry — see
7
+ * `Editor.applyDocument`).
8
+ *
9
+ * # Granularity
10
+ *
11
+ * - **Body blocks:** matched by id; updated in-place / inserted /
12
+ * removed. Concurrent edits to *different* blocks always merge
13
+ * cleanly via the Y.Array CRDT.
14
+ *
15
+ * - **Paragraph block content:** the Y.Text is updated via
16
+ * `diffApplyText` — minimal `insert` / `delete` / `format`
17
+ * mutations matching what changed. Concurrent edits to *different
18
+ * positions* in the same paragraph merge cleanly. Concurrent
19
+ * edits to the *same* position still resolve via Yjs's standard
20
+ * ordering (later op wins per Yjs's deterministic conflict rules).
21
+ *
22
+ * - **Paragraph properties (alignment, indent, …):** stored as a
23
+ * JSON blob on the Y.Map's `props` field. Concurrent property
24
+ * edits clobber. Phase 1c may split into per-key Y.Map.
25
+ *
26
+ * - **Non-paragraph blocks (section_break, table):** stored as
27
+ * JSON-encoded `_ast`. Concurrent edits clobber.
28
+ *
29
+ * - **Document meta (sections, styles, numbering, fonts,
30
+ * headerFooterBodies):** JSON-encoded on a `meta` Y.Map.
31
+ * Clobber on concurrent edits — these change rarely.
32
+ *
33
+ * - **Binary parts (images, fonts):** stored as a Y.Map<Uint8Array>
34
+ * with adds / removes detected by key. Phase 3 moves these to a
35
+ * content-hashed blob store (out-of-band).
36
+ *
37
+ * The whole apply is wrapped in a single `Y.Doc.transact` with the
38
+ * supplied origin so subscribers (UndoManager, change listeners) see
39
+ * one batch.
40
+ */
41
+ export interface ApplyDocumentOptions {
42
+ /**
43
+ * Part paths to **exclude** from the inline `parts` diff. Used when
44
+ * a `BlobStore` is configured — paths already managed via `partRefs`
45
+ * (or in-flight migration to it) must not also be written inline,
46
+ * or the Y.Doc would carry duplicate bytes.
47
+ *
48
+ * Empty / absent: today's path — every `rawParts` entry mirrors
49
+ * inline (no BlobStore).
50
+ */
51
+ skipPartPaths?: ReadonlySet<string>;
52
+ }
53
+ export declare function applyDocumentToYDoc(ydoc: Y.Doc, newDoc: SobreeDocument, newIds: readonly string[], origin?: unknown, opts?: ApplyDocumentOptions): void;
54
+ /**
55
+ * Atomically write `partRefs` entries to a Y.Doc's `partRefs` Y.Map.
56
+ * Used by the Editor / HeadlessSobree when a `BlobStore` is configured —
57
+ * the editor hashes new bytes, uploads via the store, then calls this
58
+ * to publish the hash so other peers can fetch it.
59
+ *
60
+ * Wraps in `Y.Doc.transact` with the supplied origin so subscribers
61
+ * (UndoManager, observers) see one batch.
62
+ */
63
+ export declare function applyPartRefsToYDoc(ydoc: Y.Doc, partRefs: Record<string, string>, origin?: unknown): void;
64
+ /**
65
+ * Remove `partRefs` entries from a Y.Doc. Used when an embedder
66
+ * prunes unreferenced parts.
67
+ */
68
+ export declare function removePartRefsFromYDoc(ydoc: Y.Doc, paths: readonly string[], origin?: unknown): void;
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Y.Doc backing for Sobree.
3
+ *
4
+ * The document is a Y.Doc; `SobreeDocument` is a *projection* computed
5
+ * by `projectYDoc` and cached by the Editor. All mutations go through
6
+ * `applyDocumentToYDoc` which diffs against the current Y state by
7
+ * block id and (for paragraphs) by Y.Text content.
8
+ *
9
+ * See `./schema.ts` for the Y.Doc layout, `./runs.ts` for the
10
+ * Run↔Delta mapping, and `./textDiff.ts` for the smart Y.Text diff
11
+ * that preserves CRDT semantics across applies.
12
+ */
13
+ export { Y_BLOCK_AST_KEY, Y_BLOCK_ID_KEY, Y_BLOCK_KIND_KEY, Y_BLOCK_PROPS_KEY, Y_BLOCK_TEXT_KEY, Y_BODY_KEY, Y_META_FIELDS, Y_META_KEY, Y_PARTREFS_KEY, Y_PARTS_KEY, } from './schema';
14
+ export { seedYDoc, buildBlockYMap, buildSkeletonBlockYMap, populateBlockContent, populateParagraphContent, populateParagraphYMap, } from './seed';
15
+ export { projectYDoc, projectBlock } from './project';
16
+ export { applyDocumentToYDoc, applyPartRefsToYDoc, removePartRefsFromYDoc, } from './apply';
17
+ export { type DeltaOp, type EmbedContent, type LinkMark, attrsToRunProps, deepEqual, deltaToRuns, runPropsToAttrs, runsToDelta, } from './runs';
18
+ export { diffApplyText } from './textDiff';
@@ -0,0 +1,41 @@
1
+ import { Block, SobreeDocument } from '../doc/types';
2
+ import type * as Y from "yjs";
3
+ /**
4
+ * Read the SobreeDocument projection out of a Y.Doc.
5
+ *
6
+ * Returns:
7
+ *
8
+ * - **`doc`** — the projected SobreeDocument. Its `rawParts` is
9
+ * populated from the Y.Doc's inline `parts` Y.Map only.
10
+ * - **`ids`** — block-id order matching `doc.body`. The Editor
11
+ * uses this to keep its BlockRegistry in sync.
12
+ * - **`partRefs`** — partPath → hash mappings from the Y.Doc's
13
+ * `partRefs` Y.Map (Phase 3.2+). Empty `{}` when no BlobStore-
14
+ * using peer has touched the doc. **The caller (Editor /
15
+ * HeadlessSobree) is responsible for resolving these through a
16
+ * `BlobCache`** and merging the results into `doc.rawParts`.
17
+ * We keep the resolution decoupled so projection has no async
18
+ * work or external dependency.
19
+ *
20
+ * Two block shapes are supported:
21
+ *
22
+ * - Phase 1b.5+: paragraphs as `{ kind: "paragraph", text: Y.Text, props }`.
23
+ * - Phase 1a: everything else as `{ _ast: JSON }`.
24
+ *
25
+ * Backwards compat: a Phase 1a-shaped paragraph (only `_ast`, no `kind`
26
+ * field) projects identically — the JSON is parsed straight to
27
+ * `Paragraph`.
28
+ *
29
+ * This is allocation-heavy; the Editor caches the result and invalidates
30
+ * on Y.Doc updates.
31
+ */
32
+ export declare function projectYDoc(ydoc: Y.Doc): {
33
+ doc: SobreeDocument;
34
+ ids: string[];
35
+ partRefs: Record<string, string>;
36
+ };
37
+ /**
38
+ * Read a single block Y.Map into a Block. Returns `null` if the map
39
+ * is empty / unrecognizable (defensive — projectYDoc skips nulls).
40
+ */
41
+ export declare function projectBlock(map: Y.Map<unknown>): Block | null;
@@ -0,0 +1,51 @@
1
+ import { BreakRun, DrawingRun, InlineRun, RunProperties } from '../doc/types';
2
+ export interface DeltaOp {
3
+ /** Text content (string) or embed (object literal). Y.Text differentiates by typeof. */
4
+ insert: string | EmbedContent;
5
+ /** Per-op marks — bold, italic, color, link, etc. Undefined when
6
+ * no marks apply (Yjs preference: omit rather than set empty). */
7
+ attributes?: Record<string, unknown>;
8
+ }
9
+ /** All non-text run kinds become embed objects in the delta. */
10
+ export type EmbedContent = {
11
+ __sobree: "break";
12
+ type: BreakRun["type"];
13
+ } | {
14
+ __sobree: "tab";
15
+ } | {
16
+ __sobree: "field";
17
+ instruction: string;
18
+ cached?: string;
19
+ } | {
20
+ __sobree: "drawing";
21
+ partPath: string;
22
+ widthEmu: number;
23
+ heightEmu: number;
24
+ placement: DrawingRun["placement"];
25
+ altText?: string;
26
+ };
27
+ /** The `link` mark — stamped on every char inside a HyperlinkRun. */
28
+ export interface LinkMark {
29
+ href: string;
30
+ }
31
+ export declare function runsToDelta(runs: readonly InlineRun[]): DeltaOp[];
32
+ export declare function deltaToRuns(delta: readonly DeltaOp[]): InlineRun[];
33
+ /**
34
+ * Convert RunProperties to a flat attributes object. Empty / undefined
35
+ * fields are omitted entirely (Yjs convention). Returns `undefined`
36
+ * when no marks apply.
37
+ */
38
+ export declare function runPropsToAttrs(props: RunProperties | undefined): Record<string, unknown> | undefined;
39
+ /**
40
+ * Inverse of `runPropsToAttrs`. Strips the `link` key (it's handled
41
+ * separately as a hyperlink wrapper). Strips any unknown attributes
42
+ * defensively (forward-compat: a future plugin may add marks Sobree
43
+ * doesn't model).
44
+ */
45
+ export declare function attrsToRunProps(attrs: Record<string, unknown> | undefined): RunProperties;
46
+ /**
47
+ * Deep equality for delta op contents — strings compared as `===`,
48
+ * embeds and attribute objects compared structurally. Used by the
49
+ * Y.Text diff to detect cells that haven't changed.
50
+ */
51
+ export declare function deepEqual(a: unknown, b: unknown): boolean;
@@ -0,0 +1,123 @@
1
+ /**
2
+ * Y.Doc shape used by Sobree.
3
+ *
4
+ * The document is stored as a Y.Doc — that's the source of truth.
5
+ * `SobreeDocument` is a *projection* of this Y.Doc, computed by the
6
+ * helpers in `./project.ts` and cached by the Editor.
7
+ *
8
+ * # Top-level layout
9
+ *
10
+ * ```
11
+ * ydoc
12
+ * ├── getArray("body") : Y.Array<Y.Map> — block list, one Y.Map per block
13
+ * ├── getMap("meta") : Y.Map — sections, styles, numbering,
14
+ * │ headerFooterBodies, fonts.
15
+ * │ Stored as JSON-encoded values
16
+ * │ (rarely edited concurrently).
17
+ * │ Phase 1c may split fields into
18
+ * │ per-key Y types.
19
+ * ├── getMap("parts") : Y.Map<Uint8Array> — inline binary parts
20
+ * │ (legacy / no-BlobStore path).
21
+ * └── getMap("partRefs") : Y.Map<string> — partPath → SHA-256 hex hash
22
+ * (Phase 3.2+ / BlobStore path).
23
+ * Bytes live in a side-channel
24
+ * `BlobStore`; the editor's
25
+ * `BlobCache` resolves hashes.
26
+ * ```
27
+ *
28
+ * Both `parts` and `partRefs` coexist — projection unions them so a
29
+ * Y.Doc with inline legacy bytes alongside hash-addressed refs reads
30
+ * correctly. The editor writes to whichever path matches its config
31
+ * (BlobStore → partRefs, otherwise parts).
32
+ *
33
+ * # Block Y.Map shape — paragraphs (Phase 1b.5+)
34
+ *
35
+ * Paragraph blocks (kind === `"paragraph"`, including headings) are
36
+ * structured for char-level CRDT:
37
+ *
38
+ * ```
39
+ * paragraphMap
40
+ * ├── get("id") : string — stable block id (matches BlockRegistry)
41
+ * ├── get("kind") : "paragraph" — discriminator
42
+ * ├── get("text") : Y.Text — runs flatten into here, marks for marks,
43
+ * │ embeds for breaks/tabs/fields/drawings,
44
+ * │ `link: { href }` mark for hyperlink chars
45
+ * └── get("props") : string (JSON) — ParagraphProperties (alignment, indent,
46
+ * numbering, …). JSON-encoded for v0.1;
47
+ * concurrent property edits clobber.
48
+ * ```
49
+ *
50
+ * # Block Y.Map shape — non-paragraphs
51
+ *
52
+ * Section breaks and tables stay JSON-encoded — neither has inline
53
+ * text content with concurrent-edit demand. Tables get their own
54
+ * structural CRDT in a future Phase 1c (per-cell Y.Map<body Y.Array>).
55
+ *
56
+ * ```
57
+ * otherBlockMap
58
+ * ├── get("id") : string — stable block id
59
+ * └── get("_ast") : string (JSON) — JSON-encoded Block
60
+ * ```
61
+ *
62
+ * # Y.Text mark conventions
63
+ *
64
+ * The mapping between Sobree's `RunProperties` and Y.Text marks is
65
+ * 1:1 — `bold: true` becomes `{ bold: true }` on each char's
66
+ * attributes. Two special cases:
67
+ *
68
+ * - `link: { href }` — chars inside a `HyperlinkRun` carry this mark.
69
+ * The decoder reconstructs the HyperlinkRun by grouping consecutive
70
+ * same-href chars.
71
+ * - Embeds — non-text runs (break / tab / field / drawing) appear as
72
+ * embed objects (`{ insert: { __sobree: "<kind>", … } }` in delta
73
+ * form), occupying one position in the Y.Text.
74
+ *
75
+ * See `./runs.ts` for the conversion helpers and `./textDiff.ts` for
76
+ * the smart Y.Text diff that preserves CRDT semantics across applies.
77
+ */
78
+ export declare const Y_BODY_KEY = "body";
79
+ export declare const Y_META_KEY = "meta";
80
+ /**
81
+ * Inline binary parts — `Y.Map<string, Uint8Array>` keyed by part
82
+ * path (e.g. `word/media/image1.png`). The legacy path: bytes ride
83
+ * along inside Y updates. Acceptable for small docs; expensive at
84
+ * scale (every peer replicates every byte).
85
+ *
86
+ * Used when no `BlobStore` is configured on the editor.
87
+ */
88
+ export declare const Y_PARTS_KEY = "parts";
89
+ /**
90
+ * Content-hashed part references — `Y.Map<string, string>` keyed by
91
+ * part path, valued by SHA-256 hex hash. The Phase 3.2+ path: bytes
92
+ * live in a side-channel `BlobStore`, the Y.Doc carries only small
93
+ * 64-char hashes. Y updates stay tiny regardless of image size.
94
+ *
95
+ * Used when a `BlobStore` is configured. The projection's `rawParts`
96
+ * resolves hashes against the editor's local `BlobCache` (which
97
+ * fetches from the store on miss).
98
+ *
99
+ * Both `parts` and `partRefs` coexist — a Y.Doc can carry inline
100
+ * legacy bytes alongside hash-addressed refs. Projection unions
101
+ * them; mixed-mode swarms work.
102
+ */
103
+ export declare const Y_PARTREFS_KEY = "partRefs";
104
+ export declare const Y_BLOCK_ID_KEY = "id";
105
+ /** Discriminator: `"paragraph"` blocks have `text` + `props`; everything
106
+ * else has `_ast`. Defaults to "_ast" path for forward-compat with
107
+ * Phase 1a docs that didn't write a kind field. */
108
+ export declare const Y_BLOCK_KIND_KEY = "kind";
109
+ /** Phase 1b.5+: Y.Text on paragraph blocks. */
110
+ export declare const Y_BLOCK_TEXT_KEY = "text";
111
+ /** Phase 1b.5+: JSON-encoded ParagraphProperties on paragraph blocks. */
112
+ export declare const Y_BLOCK_PROPS_KEY = "props";
113
+ /** Phase 1a: JSON-encoded Block on non-paragraph blocks (and on
114
+ * Phase 1a-shaped paragraph blocks for backwards compat). */
115
+ export declare const Y_BLOCK_AST_KEY = "_ast";
116
+ /** Keys stored on the `meta` Y.Map. */
117
+ export declare const Y_META_FIELDS: {
118
+ readonly sections: "sections";
119
+ readonly headerFooterBodies: "headerFooterBodies";
120
+ readonly styles: "styles";
121
+ readonly numbering: "numbering";
122
+ readonly fonts: "fonts";
123
+ };