@atom.io/template-react-realtime-text-editor 0.0.11 → 0.0.12

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/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # @atom.io/template-react-realtime-text-editor
2
2
 
3
+ ## 0.0.12
4
+
5
+ ### Patch Changes
6
+
7
+ - aa30b42: Use Mosaic Domains and the reusable Lexical adapter for collaborative Markdown editing, with optimistic drafts, logical collaborator carets and selections, renewable presence, viewport residency, atomic text/index updates, and actor-selective undo and redo. Demonstrate incremental Markdown parsing and virtualized source and preview rendering, with deterministic headless and two-browser conformance coverage.
8
+
9
+ Demonstrate consumer-owned structural CSS Modules and typed part classes for utility-compatible styling. Add automatic light and dark color schemes using component-owned Lasertag theme tokens, with a dark initial canvas and fallback theme that still follows the system's light-mode preference.
10
+
11
+ Use the standalone `@atom.io/react`, `@atom.io/realtime`, and `@atom.io/eslint-plugin` packages. Include pinned Oxlint, ESLint, TypeScript, and dprint quality checks, with TypeScript 7 for builds and TypeScript 6 for ESLint's compiler API. Pin dprint 0.64.1 to distrust the compromised eMudhra certificates when downloading formatter plugins.
12
+ - Updated dependencies [bb1d8de]
13
+ - Updated dependencies [aa30b42]
14
+ - Updated dependencies [bb1d8de]
15
+ - atom.io@0.54.0
16
+ - @atom.io/realtime@0.1.0
17
+ - @atom.io/react@0.1.0
18
+
3
19
  ## 0.0.11
4
20
 
5
21
  ### Patch Changes
package/INNOVATIONS.md CHANGED
@@ -1,113 +1,242 @@
1
- # Mosaic Reconciliation Notes
2
-
3
- This template began as an application-local experiment. The collaboration
4
- machinery now lives in atom.io's Mosaic module, but Mosaic is not a parallel
5
- state system: the document is a transceiver held by an ordinary mutable atom.
6
- That atom token is its network address, its read-only view feeds ordinary
7
- selectors and renderer hooks, and each Store owns the optimistic replica bound
8
- to it. The server registers the same transceiver class as the authority for that
9
- address. The decisions below are the places where this transceiver deliberately
10
- takes a different path from rigid state proxying and whole-value time travel.
11
-
12
- ## Bounded Sequence Intervals
13
-
14
- A shared string is not a useful conflict unit. Mosaic Text represents Unicode
15
- graphemes as stable nodes and edits as insert/delete operations. Each insertion
16
- records both its retained left boundary and its retained right boundary. The
17
- second boundary matters: a predecessor-only graph can move a middle replacement
18
- after the old suffix merely because one author's operation ID sorts later.
19
- Bounded intervals preserve the user's edit location while deterministic,
20
- code-unit ID ordering resolves genuinely concurrent insertions.
21
-
22
- Deletes are edit-owned visibility marks. Hidden nodes remain as anchor stubs, so
23
- foreign descendants, late operations, and relative selections do not lose their
24
- position when an author undoes the edit that introduced an ancestor.
25
-
26
- The model uses the host's Unicode grapheme segmenter. A deployment with
27
- heterogeneous ICU versions should pin runtimes together or ship a versioned
28
- segmenter before claiming cross-runtime model-version equivalence.
29
-
30
- ## Selective, Per-Identity History — Not a Timeline
31
-
32
- Whole-document time travel is unsafe in a shared editor: restoring yesterday's
33
- string can erase another person's accepted work. Mosaic records edit ownership
34
- and represents undo and redo as durable operations that deactivate or reactivate
35
- only one authenticated actor's current edit group. A foreign insertion anchored
36
- inside hidden local work remains visible.
37
-
38
- Atom.io's native timelines still mean local graph time travel. The Mosaic text
39
- transceiver is marked append-only and cannot be attached to one: rewinding its
40
- object would bypass the accepted operation stream and clobber collaboration.
41
- Its actor-scoped capability is named `historyFor`, and undo/redo append normal
42
- operations that every replica reduces in the same order.
43
-
44
- The server validates the actor's current history cursor through the text
45
- transceiver immediately before the append. A stale tab therefore fails closed
46
- and resnapshots. Accepted revisions
47
- are durable reduction metadata, so concurrent history operations have one
48
- canonical order; provisional client projections explicitly use no revision.
49
- This is exact at operation ownership and intentionally best-effort at recovering
50
- the prose-level intention behind arbitrary concurrent rewrites.
51
-
52
- ## Relative, Ephemeral Presence
53
-
54
- Numeric caret offsets become stale after any preceding edit. Mosaic presence
55
- uses left/right node anchors plus an explicit affinity. The text model resolves
56
- those anchors against each local projection, including hidden anchors. Presence
57
- is schema-checked and model-checked, but never enters the durable operation log
58
- or a user's history. Explicit departure and disconnect both remove the exact
59
- actor/session record.
60
-
61
- ## Durable Stream Before Fan-Out
62
-
63
- The server never acknowledges or broadcasts an operation before persistence.
64
- Its adapter contract atomically compares the expected revision and reserves the
65
- atom-stream/operation-ID receipt. A reused ID with different normalized content is
66
- a collision, not an idempotent retry. Receipts survive checkpoint compaction.
67
-
68
- Horizontal notifications are wake-up hints only. Every server drains a checked,
69
- contiguous tail from the shared linearizable store. Recovery hydrates one
70
- consistent checkpoint and then applies every later revision. Checkpoint
71
- installation, tail pruning, session watermarks, and a retention epoch form the
72
- compaction fence. Text model version 1 retains stable node stubs; a future model
73
- that removes them will need an explicit anchor-translation protocol.
74
-
75
- The template uses the in-memory adapter so it runs without infrastructure. It is
76
- restart-safe only while that adapter instance survives. Production should supply
77
- a transactional database implementation and define an offline-session retention
78
- policy before pruning operation bodies.
79
-
80
- ## Optimistic Reconciliation and Recovery
81
-
82
- Each Atom.io Store creates stable session-scoped operation IDs, applies locally,
83
- and retains one causal outbox for the mutable atom token. Multiple components in
84
- that Store share the same replica; a second Silo gets a genuinely independent
85
- replica. Reconnect snapshots report which pending IDs were already accepted and
86
- the authoritative causal frontier; the client hydrates the transceiver
87
- checkpoint, removes those proposals, and replays the rest from that frontier.
88
- Duplicate delivery is harmless. A revision gap resnapshots.
89
- Structured rejection policies distinguish retryable work from stale history or
90
- invalid dependency chains, which are quarantined rather than left as impossible
91
- optimistic state.
92
-
93
- Local wall-clock time may group typing gestures. It is deliberately absent from
94
- accepted reduction semantics, fingerprints, and convergence decisions. Local
95
- signals created inside an Atom.io transaction are observed only after commit;
96
- aborted transaction clones are discarded before they can enter the network
97
- outbox.
98
-
99
- ## Testing Arbitrary Protocols
100
-
101
- Mosaic is not implemented as push/pull state proxying, yet atom.io's realtime
102
- test harness can exercise it because the harness exposes the transport and
103
- lifecycle rather than assuming a particular protocol. The template's
104
- multi-client scenarios run independent React stores and Socket.IO sessions,
105
- disconnect both editors, accept simultaneous offline work, verify selective
106
- history, and verify presence removal. Core suites add deterministic drop,
107
- duplicate, reorder, restart, multi-node, checkpoint, and session-correlation
108
- schedules.
109
-
110
- The remaining seam is application work tracking: integrations that schedule
111
- work beyond transport callbacks should register that work with the harness when
112
- they need barrier-based quiescence. Mosaic's conformance suite otherwise uses
113
- the same public server, client, and renderer APIs as this application.
1
+ # Incremental Markdown Decisions
2
+
3
+ MOS-18 is the first application that composes the run-text model, bounded text
4
+ index, partial residency, Domain history, and renderer projections as one user
5
+ experience. Most of the implementation is application composition. One missing
6
+ generic seam had to move into realtime core so the result could remain correct
7
+ at every accepted revision.
8
+
9
+ ## One Logical Source, Bounded Resident Views
10
+
11
+ The authoritative service owns one Mosaic run-text checkpoint and the MOS-15
12
+ bounded-fanout index derived from it. The index root, nodes, aliases, and leaves
13
+ are ordinary durable Domain members. A browser never acquires the source member:
14
+ MOS-12 authorizes and hydrates only the root and the leaves resolved for a
15
+ bounded UTF-16 range, and MOS-17 projects those leaves through ordinary
16
+ selectors. Full materialization remains an explicit command.
17
+
18
+ This is not application-authored paragraph or file sharding. Syntax cannot
19
+ change storage shape, so a huge paragraph or fenced block crosses the same
20
+ physical thresholds as ordinary prose. Presence and render keys name logical
21
+ run positions rather than physical leaves.
22
+
23
+ MOS-18 exposed a scalability flaw that small MOS-15 conformance fixtures could
24
+ not: index composition expanded every source grapheme into a separate JavaScript
25
+ object. The core index now streams large fragments into bounded spans, caches
26
+ unchanged source spans on the resulting index bundle, and reuses physical leaf
27
+ ownership. Ordinary edits scan run/span identities and touch changed leaves plus
28
+ their bounded tree paths; they do not manufacture a document-sized unit array.
29
+
30
+ The same exercise exposed that `replace-text` is the wrong input seam for a
31
+ large editor because it materializes and diffs the entire old and new strings.
32
+ Mosaic Text now accepts a logical `replace-selection` intent and exposes pure
33
+ `prepare` and `preview` phases. The authoritative service can derive a bounded
34
+ operation and its next visible run projection before proposing the atomic
35
+ Domain batch, without mutating accepted state or asking the application to
36
+ construct CRDT runs. Unicode chunking is streaming, preserves CRLF and complex
37
+ grapheme boundaries, and remains core-owned rather than application sharding.
38
+
39
+ ## Atomic History Completion
40
+
41
+ MOS-16 previously generated a correct run-text compensation but had no way to
42
+ include the index maintenance describing that compensated text. Appending
43
+ maintenance afterward would expose one revision whose source and range index
44
+ disagreed.
45
+
46
+ The history coordinator now accepts an optional `completeCompensation` callback.
47
+ It runs after member policies create compensation operations and before the
48
+ private history proposal is preflighted. Returned operations join that same
49
+ batch only when their member policies classify them as history-free. The
50
+ coordinator still proves that the original target set was compensated exactly;
51
+ an appended change or second compensation fails closed. This seam is generic to
52
+ derived durable indexes and contains no Markdown branch.
53
+
54
+ ## Local Input and Logical Presence
55
+
56
+ The mounted Lexical plain-text editor contains only one resident source window.
57
+ The renderer-neutral Mosaic text editor hook keeps keystrokes in an optimistic
58
+ draft, derives minimal replacement intent, and retains that draft until a
59
+ complete newer projection cut settles. It also publishes local selections as
60
+ run-relative positions and resolves collaborator selections through the same
61
+ visible text deltas, so renderers do not briefly display an absolute cursor at a
62
+ stale offset. DOM selection, composition, scroll, and caret geometry remain
63
+ renderer-owned.
64
+
65
+ Lexical is deliberately a browser editing and geometry layer rather than a
66
+ second collaboration authority. Its Yjs collaboration integration and history
67
+ plugin are not mounted. Mosaic owns accepted text, convergence, logical
68
+ positions, actor history, partial residency, and renewable presence leases. The
69
+ core editor lifecycle projects the current bounded source, translates local
70
+ selection offsets into Mosaic positions, and resolves foreign positions. The
71
+ Lexical adapter only maps those offsets to DOM ranges for colored caret labels
72
+ and selection overlays. A projection-tagged update cannot echo back as a local
73
+ edit, while native composition remains local until it is ready for the existing
74
+ coalesced Domain gesture.
75
+
76
+ The React range hook now reacquires a failed viewport when residency transitions
77
+ back to live. This closes the cold-start race where Vite could render before the
78
+ realtime server accepted its first Socket.IO connection, leaving an otherwise
79
+ healthy client permanently parked on an internal resnapshot error.
80
+
81
+ An accepted edit may also shorten a document while an existing subscription
82
+ still names the old viewport end. The Markdown range adapter therefore clamps
83
+ that durable subscription to the new authoritative length, and command
84
+ acknowledgements do not clear the optimistic draft until residency has settled
85
+ the accepted revision. This prevents a valid remote replacement from stranding
86
+ the UI on a stale projection or briefly presenting the stale text as saved.
87
+
88
+ The demo deliberately coalesces an offline draft for its current viewport. It
89
+ does not claim semantic intent recovery for arbitrary rewrites made in several
90
+ unloaded regions. A richer product can retain several bounded draft windows and
91
+ still submit them as one Domain gesture.
92
+
93
+ The production patterns discovered while stabilizing that lifecycle no longer
94
+ live as Markdown-specific template machinery. Core now publishes the Mosaic
95
+ text source/index models and schemas, a server text-document coordinator for
96
+ atomic import/edit/index/history work, a validated residency socket bridge, and
97
+ a browser session coordinator for reconnect and accepted-revision settlement.
98
+ The template supplies its member names, limits, authorization policy, simulated
99
+ identities, viewport, and renderer. This keeps the correctness boundary reusable
100
+ without pretending that editor layout or product policy is generic.
101
+
102
+ ## Incremental, Cancelable Semantics
103
+
104
+ Parsing is not a render-time whole-document function. The headless parser caches
105
+ input and output block state, yields after a bounded amount of UTF-16 work, and
106
+ cancels an obsolete generation as soon as a newer projection arrives. Fence
107
+ state propagates through following blocks only until an unchanged cached input
108
+ state establishes a stable boundary. React mounts only semantic blocks in the
109
+ current preview window.
110
+
111
+ The included grammar is intentionally a safe illustrative Markdown subset. A
112
+ production CommonMark/GFM worker can replace it behind the same source-block,
113
+ cancelation, stable-state, and instrumentation contract. Sanitization remains a
114
+ renderer responsibility; this template creates React nodes and never injects
115
+ HTML.
116
+
117
+ ## Mosaic Text v3: Atomic Roots Instead of Giant Operations
118
+
119
+ A 50 MB import cannot be both a bounded operation and a single giant Domain
120
+ payload. Splitting that payload into several accepted source revisions was also
121
+ rejected: a crash would expose a prefix, and retry could collide with the
122
+ already-accepted prefix or duplicate it. Mosaic Text v3 takes a different path.
123
+ It stages immutable content-addressed leaves and bounded-fanout branches while
124
+ they are unreachable, protects the staged graph with the same atomic storage
125
+ write, and publishes one small root operation. Before that root is accepted the
126
+ old document remains authoritative; afterward the complete new graph is
127
+ authoritative. Exact retry is idempotent.
128
+
129
+ This required one model-neutral addition to the MOS-13 checkpoint spine:
130
+ checkpoints may name validated external roots. Staging and its expiring proposal
131
+ lease are atomic; the accepted append atomically promotes that lease to durable
132
+ accepted-root protection; and checkpoint publication adopts and releases the
133
+ protection in the same commit. Restart exposes accepted-but-not-yet-checkpointed
134
+ roots to the coordinator, while history and outbox leases protect older roots.
135
+ Garbage collection traces every edge behind a retention-epoch fence. Missing
136
+ parents, stale parents, forged summaries, hash collisions, corrupt objects, and
137
+ stage/append/checkpoint/GC races all fail closed. Abandoned proposals expire by
138
+ time, revision, and bounded retention epochs instead of becoming perpetual
139
+ leases.
140
+
141
+ The external root stores its incremental proof separately from the root header.
142
+ An initial import necessarily reads the whole input once. A later edit
143
+ path-copies only the touched leaf and branch path, sends removals for superseded
144
+ paths, and verifies its delta against the protected parent summary. Commit can
145
+ therefore reuse authenticated subtree totals instead of rehashing a document
146
+ manifest. Range recovery is paged and visits addressed paths rather than
147
+ hydrating the graph.
148
+
149
+ This is deliberately versioned as Mosaic Text v3 rather than silently changing
150
+ v2's run snapshot. The generic Domain hook knows only content-addressed object
151
+ graphs, bounds, and lifecycle dependencies; rope shape, grapheme-safe chunking,
152
+ reference counts, range reads, and root replacement remain text-owned. That is
153
+ where this work takes its own path beyond the original push/pull and monolithic
154
+ checkpoint facilities.
155
+
156
+ ## History and Checkpoint Amplification
157
+
158
+ Scale auditing also found a non-text-specific amplifier in Domain history.
159
+ Accepted gestures were deep-cloned into every retained checkpoint-race cut, so a
160
+ large operation could be retained many times even when text storage itself was
161
+ segmented. History now clones and freezes accepted JSON once at its trusted
162
+ ingress, shares those immutable gesture payloads across race cuts, replaces a
163
+ gesture immutably when it is extended, and clones only at public or storage
164
+ egress. Caller mutation, malicious compensation callbacks, restart hydration,
165
+ and checkpoint observers remain isolated.
166
+
167
+ Mosaic v2 compaction received a related correctness fix. It removes only retired
168
+ runs that are provably unreachable while preserving logical positions, foreign
169
+ descendants, undo/redo protection, checkpoints, restart, and duplicate replay.
170
+ The scale gate keeps the v2 history/index stabilization loop because those are
171
+ the user-facing selective-history and alias contracts, then drives more than
172
+ 100,000 operations through the real Domain receipt, checkpoint, retention, and
173
+ garbage-collection lifecycle.
174
+
175
+ ## Deterministic Release Gate
176
+
177
+ The MOS-20 manifest is the sole corpus authority. The gate verifies its source
178
+ and derived digests before opening the canonical 5.6 MB source, deterministic
179
+ 50 MB repetition, huge paragraph, fenced block, heading-rich, and adversarial
180
+ Unicode variants. A single worker owns one authoritative graph at a time. Each
181
+ document runs multi-client disjoint and shared-boundary edits, duplicate,
182
+ delayed and reordered delivery, rejection, disconnect/resnapshot, partial
183
+ hydration and eviction, individualized undo/redo, and storage-plus-coordinator
184
+ restart. The seed shuffles the client, delivery, and lifecycle fault order that
185
+ is actually executed. Failure output includes that seed, the realized fault and
186
+ client schedules, resident ranges, source and client revisions, and a bounded
187
+ semantic transcript containing the exact commands needed for replay.
188
+
189
+ The gate never constructs a second full-document oracle. Its independent flat
190
+ model is a UTF-16-addressed piece table over the canonical file, and final
191
+ verification streams source ranges directly into the digest. The same pass
192
+ independently counts graphemes, line breaks, and UTF-16 units and reconstructs
193
+ each client's bounded resident ranges; all are compared with the published root
194
+ summary and client projections. Instrumentation fails the gate if any service
195
+ read materializes a document larger than the configured residency bound;
196
+ clients, parsing, join, and resnapshot remain bounded projections over the one
197
+ authoritative segmented graph.
198
+
199
+ Normative counters cover leaf and branch visits and writes, UTF-16 scanned,
200
+ object reads, validation hashing and serialization, bytes persisted, checkpoint
201
+ and batch payloads, resident and delivered bytes, selector invalidations, and
202
+ parser work. Ordinary edits are bounded to addressed leaves and tree height;
203
+ join and parser work are bounded to resident windows; checkpoints reuse clean
204
+ objects. A local checkpoint may persist at most 512 KiB and a viewport transfer
205
+ or accepted delivery at most 384 KiB. The 100,001-operation stabilization
206
+ assertion bounds history, aliases, receipts, tail, sessions, and live checkpoint
207
+ objects, then forces a real index split, resolves its stale leaf alias through
208
+ the public reader, and verifies the leaves merge after contention settles.
209
+ Elapsed time and RSS are reported observations only because runner load and
210
+ allocator behavior are not deterministic correctness signals.
211
+
212
+ Current architecture limits are explicit: text leaves target 32,768 graphemes
213
+ and at most 65,536 UTF-16 units; import chunks are at most 262,144 UTF-16 units;
214
+ branches contain at most 32 children; one edit inserts at most 256 KiB; a range
215
+ read defaults to 128 objects; an external graph defaults to 64 MiB total while
216
+ one staging call may add at most 16 MiB across 4,096 objects and 256 logical
217
+ updates, with 4 MiB and depth 64 per object; and a coordinator defaults to 64
218
+ roots, 256 recovery reads, and 64 MiB aggregate external bytes. Proposal
219
+ retention defaults to 64 epochs and is limited to 1–1,024. These are safety
220
+ contracts, not claims that arbitrarily large documents are fully resident.
221
+
222
+ External staging is a trusted server composition seam, not a socket protocol.
223
+ Client intents first cross Domain authorization and proposal limits; a product
224
+ that derives external updates from an untrusted request must also apply its own
225
+ per-document quota, rate limit, and deadline before hashing begins. Stage work
226
+ is counted before content hashing, checks an abort signal and absolute deadline
227
+ between bounded units, and becomes visible with its proposal lease only after
228
+ the whole attempt succeeds. The pinned 50 MiB corpus gate raises the incremental
229
+ stage budget because its input is authenticated by the MOS-20 manifest and read
230
+ under a fixed deadline. That exception is not the default production request
231
+ budget: it permits at most 128 MiB of cumulative authenticated staging work and
232
+ 32,768 objects while the ordinary defaults remain 16 MiB and 4,096 objects. The
233
+ reported text-stage bytes count every immutable text or index node serialized
234
+ and hashed at the model boundary; directory and proof staging, then storage
235
+ verification, independently enforce the same work budget before publication.
236
+ These cumulative work budgets are not retained bytes, resident memory, or the
237
+ protected graph's 64 MiB value-size cap.
238
+
239
+ The zero-setup editor server remains in-memory and uses simulated authorization.
240
+ A product adapter must provide the linearizable semantics exercised by the
241
+ in-memory conformance adapter, plus durable ACLs, rate limits, observability, and
242
+ an offline-retention policy.
package/README.md CHANGED
@@ -1,51 +1,87 @@
1
1
  # React Realtime Text Editor
2
2
 
3
- Mosaic is a React + atom.io multiplayer Markdown editor with a small Node and
4
- Socket.IO collaboration server. It ships simulated identities so the complete
5
- shared-editing experience can be explored locally without provisioning auth.
3
+ Mosaic is a React + atom.io multiplayer Markdown editor with simulated
4
+ identities. It demonstrates the scalable long-form text path: bounded Domain
5
+ members, viewport-first residency, logical presence, selective history,
6
+ incremental parsing, and virtualized source and preview rendering.
6
7
 
7
8
  ## Run It
8
9
 
9
- Run `npm run dev`, then open the displayed Vite URL. Open a second tab and use
10
- the avatar menu to switch identities. The Vite app runs on port 5173 and the
11
- collaboration service runs on port 3000. The browser always connects to its
12
- current origin; Vite proxies `/socket.io` and `/health` to the collaboration
13
- service in both development and preview modes.
10
+ Run `npm run dev`, then open the displayed Vite URL in two tabs and choose a
11
+ different identity in each tab. The browser connects to `window.location.origin`.
12
+ Vite proxies `/socket.io` and `/health` to the collaboration service in both
13
+ development and preview, so the deployment contract does not require browser
14
+ CORS exceptions.
14
15
 
15
16
  ## What It Shows
16
17
 
17
- - optimistic shared text that converges after simultaneous and offline edits;
18
- - live identity, connection, caret, and line-level presence;
19
- - selection anchors that survive edits around them;
20
- - per-identity undo and redo that preserve other authors' work;
21
- - durable-stream snapshots, idempotent operation replay, and server validation;
22
- - a safe, dependency-free Markdown preview rendered as React nodes; and
23
- - structural component styling checked by Lasertag.
24
-
25
- Run `npm test` for the multi-client collaboration scenarios. Run `npm run lint`
26
- to enforce ESLint, type-aware Oxlint, React hook call-order correctness, TypeScript, and
27
- the Lasertag render-story/CSS relationship with zero warnings. Run
18
+ - one logical Markdown document indexed by bounded-fanout ordinary Domain atom
19
+ families;
20
+ - viewport hydration before full materialization, with released ranges evicted;
21
+ - local input drafts that remain responsive while offline or while an
22
+ authoritative gesture settles;
23
+ - run-relative selection, viewport, and collaborator presence across physical
24
+ leaf splits and merges;
25
+ - a Lexical plain-text editing surface that renders named remote carets and
26
+ translucent selections without delegating convergence or history to a second
27
+ collaboration model;
28
+ - actor-selective undo and redo whose text compensation and index maintenance
29
+ settle in one Domain revision;
30
+ - cancelable incremental Markdown parsing that yields outside the input turn and
31
+ stops propagating when cached parser state reaches a stable boundary;
32
+ - source and preview virtualization with no architecture switch at the 5.6 MB or
33
+ deterministic 50 MB corpus sizes; and
34
+ - one explicitly authorized reset/import transaction rather than an effect run
35
+ by every joining browser.
36
+
37
+ Run `npm test` for the multi-client contention, offline/reconnect, history,
38
+ presence, import, parser, and virtualization scenarios. Run `npm run lint` to
39
+ enforce ESLint, type-aware Oxlint, React hook call-order correctness, TypeScript,
40
+ and the Lasertag render-story/CSS relationship with zero warnings. Run
28
41
  `npm run fmt:check` in CI and `npm run fmt` to apply the pinned dprint
29
- configuration locally.
42
+ configuration locally. The scheduled repository command
43
+ `pnpm test:large-document` verifies the pinned corpus and exercises the same
44
+ logical editing, history, residency, and viewport/parser contracts over Mosaic
45
+ Text v3's content-addressed storage roots. It runs sequentially with one
46
+ authoritative document graph at a time across the canonical 5.6 MB document,
47
+ the deterministic 50 MB repetition, huge-paragraph and fenced-block shapes, and
48
+ the Unicode adversarial corpus.
30
49
 
31
50
  ## Start Reading
32
51
 
33
- - `src/collaboration/mosaic.ts` is the browser collaboration contract: one
34
- mutable atom, graph-derived selectors, selective history, and model-relative
35
- presence.
36
- - `node/mosaic-atom.ts` registers the mutable atom with Zod runtime schemas,
37
- presence validation, and checkpoint cadence without entering the browser
38
- bundle.
39
- - atom.io's Mosaic client owns optimistic edits, reconnect rebasing, causal
40
- retries, and structured recovery.
41
- - atom.io's Mosaic server authenticates authorship, durably orders revisions,
42
- checkpoints streams, and publishes ephemeral presence.
43
- - `src/MarkdownWorkspace.tsx` is the React editing experience.
44
- - `INNOVATIONS.md` records the reconciliation decisions that became Mosaic and
45
- the remaining production boundaries.
46
-
47
- The template selects Mosaic's in-memory storage adapter for a zero-setup demo.
48
- Production deployments should provide a linearizable storage adapter, document
49
- ACLs, rate limits, and metrics as described in `INNOVATIONS.md`. Keep the
50
- same-origin contract in your deployment by forwarding `/socket.io` to the
51
- collaboration service at the edge.
52
+ - `src/document-domain.ts` names the application's durable source and index
53
+ members plus its ephemeral collaborator shape. Core supplies the text and
54
+ index models, schemas, and history policy.
55
+ - `node/service.ts` chooses persistence, authorization, presence lifetime, and
56
+ the app-specific Socket.IO commands. The core text document coordinator owns
57
+ atomic source/index proposals, range reads, and history compensation, while
58
+ the core residency binder owns its wire protocol.
59
+ - `src/collaboration-client.ts` supplies browser identity, viewport policy, and
60
+ application commands. Core owns residency transport, reconnect
61
+ synchronization, accepted-revision settlement, presence renewal, and
62
+ selective history sequencing.
63
+ - `@atom.io/realtime/client` owns the Store-backed range controller, preserving
64
+ the last complete projection while replacement residency is acquired.
65
+ - `@atom.io/realtime/react` owns optimistic draft settlement and maps local and
66
+ remote selections across complete, revision-tagged projection cuts.
67
+ - `@atom.io/realtime/react/lexical` owns projection replacement, native input
68
+ boundaries, DOM selection restoration, collaborator geometry, and semantic
69
+ structure without visual styling. The local `src/LexicalMarkdownEditor.tsx`
70
+ opts into Lasertag's validated render-story adoption for Atom.io's owned root.
71
+ Its CSS Module styles that root, while its ordinary component stylesheet uses
72
+ the adapter's typed `classNames` parts across Lexical's context-provider
73
+ boundary. Utility-CSS consumers can supply those same parts directly.
74
+ - `src/incremental-markdown.ts` contains the renderer-neutral, cancelable parser.
75
+ - `src/workspace-state.ts` expresses status, presence, viewport, parser output,
76
+ diagnostics, and derived peers as Atom state. `src/MarkdownWorkspace.tsx`
77
+ reads those tokens and owns only application presentation and interaction.
78
+ - `INNOVATIONS.md` records the new core seam and the deliberate production
79
+ boundaries.
80
+
81
+ The template uses in-memory persistence and simulated authorization for a
82
+ zero-setup demo. A production service still needs a linearizable storage
83
+ adapter, document ACLs, rate limits, metrics, and a durable offline-retention
84
+ policy. The corpus gate deliberately uses the same public checkpoint adapter
85
+ contract as production, including restart, proposal expiry, retention fences,
86
+ and garbage collection; wall-clock and RSS readings are diagnostics rather than
87
+ portable release thresholds.
package/eslint.config.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as tsParser from "@typescript-eslint/parser"
2
- import AtomIOPlugin from "atom.io/eslint-plugin"
2
+ import AtomIOPlugin from "@atom.io/eslint-plugin"
3
3
  import type { Linter } from "eslint"
4
4
  import LasertagPlugin from "lasertag/eslint-plugin"
5
5
 
package/index.html CHANGED
@@ -3,6 +3,7 @@
3
3
  <head>
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <meta name="color-scheme" content="dark light" />
6
7
  <meta
7
8
  name="description"
8
9
  content="Mosaic, a realtime collaborative Markdown editor built with atom.io."
@@ -0,0 +1,81 @@
1
+ import { createServer } from "node:http"
2
+
3
+ import { Server } from "socket.io"
4
+
5
+ import { identityById } from "../src/identities.ts"
6
+ import {
7
+ createMarkdownDocumentService,
8
+ type MarkdownDocumentService,
9
+ } from "./service.ts"
10
+
11
+ export type MarkdownCollaborationHttpServer = {
12
+ close(): Promise<void>
13
+ readonly collaboration: MarkdownDocumentService
14
+ }
15
+
16
+ export async function createMarkdownCollaborationHttpServer(options: {
17
+ readonly collaboration?: MarkdownDocumentService
18
+ readonly port: number
19
+ }): Promise<MarkdownCollaborationHttpServer> {
20
+ const collaboration =
21
+ options.collaboration ?? (await createMarkdownDocumentService())
22
+ const httpServer = createServer((request, response) => {
23
+ if (request.url === `/health`) {
24
+ response.writeHead(200, { "content-type": `application/json` })
25
+ response.end(
26
+ JSON.stringify({ ok: true, revision: collaboration.revision }),
27
+ )
28
+ return
29
+ }
30
+ response.writeHead(404)
31
+ response.end()
32
+ })
33
+ const socketServer = new Server(httpServer)
34
+
35
+ socketServer.on(`connection`, (socket) => {
36
+ const requestedActor =
37
+ typeof socket.handshake.auth.actor === `string`
38
+ ? socket.handshake.auth.actor
39
+ : undefined
40
+ const session =
41
+ typeof socket.handshake.auth.session === `string`
42
+ ? socket.handshake.auth.session
43
+ : ``
44
+ const identity = identityById(requestedActor)
45
+ if (
46
+ identity.id !== requestedActor ||
47
+ session.length === 0 ||
48
+ session.includes(`::`)
49
+ ) {
50
+ socket.disconnect(true)
51
+ return
52
+ }
53
+ void collaboration.bindSocket({ actor: identity.id, session, socket })
54
+ })
55
+
56
+ await new Promise<void>((resolve, reject) => {
57
+ const onError = (error: Error): void => {
58
+ reject(error)
59
+ }
60
+ httpServer.once(`error`, onError)
61
+ httpServer.listen(options.port, `127.0.0.1`, () => {
62
+ httpServer.off(`error`, onError)
63
+ resolve()
64
+ })
65
+ })
66
+
67
+ let closed = false
68
+ return {
69
+ async close() {
70
+ if (closed) return
71
+ closed = true
72
+ await new Promise<void>((resolve) =>
73
+ socketServer.close(() => {
74
+ resolve()
75
+ }),
76
+ )
77
+ collaboration[Symbol.dispose]()
78
+ },
79
+ collaboration,
80
+ }
81
+ }