Automerge backed Markdown editor for the Atmosphere
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745import { describe, it, expect, afterEach } from 'vitest'import { Repo } from '@automerge/automerge-repo'import * as Automerge from '@automerge/automerge'import { inspectRichText } from '../../automerge-markdown/rich-text-projection'import { visibleTextFromDoc } from '../../format-preservation'import { DocumentManager } from '../../services/document-manager'import { readText } from '../../testing/document-read-helpers'import type { PushworkFileDocument, PushworkDirectoryDocument } from '../../types/pushwork'import { createBCSyncHarness, waitForContent, waitForFolderEntryCount, type BCSyncHarness,} from './support/bc-sync-harness'
// ---------------------------------------------------------------------------// Harness: Isolated repos for export/import/merge tests (no network)// ---------------------------------------------------------------------------
/** * Track repos created in export/import/merge tests for cleanup. */const trackedRepos: Repo[] = []
function visible(doc: PushworkFileDocument | undefined): string { return doc ? visibleTextFromDoc(doc as never) : ''}
function createTrackedRepo(): Repo { const repo = new Repo() trackedRepos.push(repo) return repo}
async function cleanupTrackedRepos(): Promise<void> { const repos = [...trackedRepos] trackedRepos.length = 0 for (const repo of repos) { try { await repo.shutdown() } catch { /* swallow */ } }}
// ===========================================================================// Suite 1: Cross-tab sync via BroadcastChannel// ===========================================================================
describe('Cross-tab sync via BroadcastChannel', () => { let harness: BCSyncHarness | undefined
afterEach(async () => { await harness?.cleanup() harness = undefined })
it('document created in tab A propagates to tab B', async () => { harness = createBCSyncHarness() const { managerA, managerB } = harness
const fileUrl = managerA.createFile('note.md') await managerA.updateContent(fileUrl, '# Hello from tab A')
// Tab B should eventually see this document via BC sync await waitForContent(managerB, fileUrl, '# Hello from tab A') })
it('content edit in tab A propagates to tab B', async () => { harness = createBCSyncHarness() const { managerA, managerB } = harness
const fileUrl = managerA.createFile('note.md') await managerA.updateContent(fileUrl, 'initial')
await waitForContent(managerB, fileUrl, 'initial')
// Now edit in tab A await managerA.updateContent(fileUrl, 'updated from tab A')
await waitForContent(managerB, fileUrl, 'updated from tab A') })
it('bidirectional sync: edits in B propagate back to A', async () => { harness = createBCSyncHarness() const { managerA, managerB } = harness
const fileUrl = managerA.createFile('note.md') await managerA.updateContent(fileUrl, 'start')
await waitForContent(managerB, fileUrl, 'start')
// Edit in tab B await managerB.updateContent(fileUrl, 'edited in tab B')
await waitForContent(managerA, fileUrl, 'edited in tab B') })
it('multiple documents sync independently', async () => { harness = createBCSyncHarness() const { managerA, managerB } = harness
const file1 = managerA.createFile('one.md') const file2 = managerA.createFile('two.md') await managerA.updateContent(file1, 'content one') await managerA.updateContent(file2, 'content two')
await waitForContent(managerB, file1, 'content one') await waitForContent(managerB, file2, 'content two') })
it('folder operations sync across tabs', async () => { harness = createBCSyncHarness() const { managerA, managerB } = harness
const folderUrl = managerA.createFolder() await managerA.createFileInFolder(folderUrl, 'a.md') await managerA.createFileInFolder(folderUrl, 'b.md')
// Tab B should see the folder with 2 entries await waitForFolderEntryCount(managerB, folderUrl, 2)
const entries = await managerB.listFolder(folderUrl) const names = entries.map(e => e.name).sort() expect(names).toEqual(['a.md', 'b.md']) })})
// ===========================================================================// Suite 2: Multi-device sync simulation (export/import/merge)// ===========================================================================
describe('Multi-device sync simulation', () => { afterEach(async () => { await cleanupTrackedRepos() })
it('content changes sync between repos via export/import/merge', async () => { const repoA = createTrackedRepo() const repoB = createTrackedRepo() const managerA = new DocumentManager(repoA)
// Create a file in repo A const fileUrl = managerA.createFile('note.md') await managerA.updateContent(fileUrl, '# Hello')
// Export from A, import into B const binary = await repoA.export(fileUrl) expect(binary).toBeDefined() const handleB = repoB.import<PushworkFileDocument>(binary!)
// Verify B has the content expect(visible(handleB.doc())).toBe('# Hello')
// Edit in A await managerA.updateContent(fileUrl, '# Hello World')
// Re-export A, import into a temp handle, merge into B const binary2 = await repoA.export(fileUrl) expect(binary2).toBeDefined() const handleAUpdate = repoB.import<PushworkFileDocument>(binary2!) handleB.merge(handleAUpdate)
expect(visible(handleB.doc())).toBe('# Hello World') })
it('concurrent folder edits merge correctly', async () => { const repoA = createTrackedRepo() const repoB = createTrackedRepo() const managerA = new DocumentManager(repoA) const managerB = new DocumentManager(repoB)
// Create a folder in repo A const folderUrl = managerA.createFolder()
// Clone to repo B const binary = await repoA.export(folderUrl) expect(binary).toBeDefined() const folderHandleB = repoB.import<PushworkDirectoryDocument>(binary!) const folderHandleA = await repoA.find<PushworkDirectoryDocument>(folderUrl)
// Both repos add files independently const fileA = managerA.createFile('from-a.md') folderHandleA.change(doc => { doc.docs.push({ name: 'from-a.md', type: 'file', url: fileA }) })
const fileB = managerB.createFile('from-b.md') folderHandleB.change(doc => { doc.docs.push({ name: 'from-b.md', type: 'file', url: fileB }) })
// Merge B into A folderHandleA.merge(folderHandleB)
const merged = folderHandleA.doc()! expect(merged.docs).toHaveLength(2) const names = merged.docs.map(d => d.name).sort() expect(names).toEqual(['from-a.md', 'from-b.md']) })
it('metadata changes sync via merge', async () => { const repoA = createTrackedRepo() const repoB = createTrackedRepo() const managerA = new DocumentManager(repoA) const managerB = new DocumentManager(repoB)
// Create a file in A with content const fileUrl = managerA.createFile('note.md') await managerA.updateContent(fileUrl, 'original content')
// Clone to B const binary = await repoA.export(fileUrl) expect(binary).toBeDefined() const handleB = repoB.import<PushworkFileDocument>(binary!) const handleA = await repoA.find<PushworkFileDocument>(fileUrl)
// A renames the file (changes metadata) handleA.change(doc => { doc.name = 'renamed.md' doc.extension = 'md' doc.mimeType = 'text/markdown' })
// B edits content await managerB.updateContent(handleB.url, 'modified content')
// Merge: both metadata change (from A) and content change (from B) should apply handleA.merge(handleB)
const merged = handleA.doc()! expect(merged.name).toBe('renamed.md') expect(visible(merged)).toBe('modified content') })})
// ===========================================================================// Suite 3: Offline behavior (export/import pattern simulating disconnection)// ===========================================================================
describe('Offline behavior', () => { afterEach(async () => { await cleanupTrackedRepos() })
it('edits made while offline are preserved locally', async () => { const repo = createTrackedRepo() const manager = new DocumentManager(repo)
// Create and edit — no network needed const fileUrl = manager.createFile('offline-note.md') await manager.updateContent(fileUrl, 'written offline')
const content = (await readText(manager, fileUrl)).content expect(content).toBe('written offline') })
it('offline edits sync when connection is restored (merge)', async () => { const repoA = createTrackedRepo() const repoB = createTrackedRepo() const managerA = new DocumentManager(repoA) const managerB = new DocumentManager(repoB)
// Create a file in repo A (the "server" baseline) const fileUrl = managerA.createFile('note.md') await managerA.updateContent(fileUrl, 'baseline content')
// "Device B goes online briefly" — clone the document const binary = await repoA.export(fileUrl) expect(binary).toBeDefined() const handleB = repoB.import<PushworkFileDocument>(binary!)
// "Device B goes offline" — edits happen independently await managerB.updateContent(handleB.url, 'baseline content\noffline edit from B')
// Meanwhile, A also edits await managerA.updateContent(fileUrl, 'baseline content updated by A')
// "Device B reconnects" — merge const handleA = await repoA.find<PushworkFileDocument>(fileUrl) handleA.merge(handleB)
// Both edits inserted text at the same position (end of 'baseline content'), // so Automerge's character-level merge will interleave the two insertions. // We verify that the merge preserved the shared prefix and all inserted characters. const merged = visible(handleA.doc()) expect(merged).toContain('baseline content') // All unique characters from both insertions are present (no data loss) for (const fragment of ['updated by A', 'offline edit from B']) { for (const char of new Set(fragment)) { expect(merged).toContain(char) } } // Total length = baseline + both insertions (both insert at same position, no deletions) expect(merged.length).toBe( 'baseline content'.length + '\noffline edit from B'.length + ' updated by A'.length, ) })
it('multiple offline edits queue and sync correctly on reconnect', async () => { const repoA = createTrackedRepo() const repoB = createTrackedRepo() const managerA = new DocumentManager(repoA) const managerB = new DocumentManager(repoB)
// Baseline const fileUrl = managerA.createFile('note.md') await managerA.updateContent(fileUrl, 'v1')
// Clone to B const binary = await repoA.export(fileUrl) expect(binary).toBeDefined() const handleB = repoB.import<PushworkFileDocument>(binary!)
// Multiple offline edits on B (each updateText replaces the full content) await managerB.updateContent(handleB.url, 'v2') await managerB.updateContent(handleB.url, 'v3') await managerB.updateContent(handleB.url, 'v3 final')
// Reconnect: merge B's accumulated changes into A const handleA = await repoA.find<PushworkFileDocument>(fileUrl) handleA.merge(handleB)
expect(visible(handleA.doc())).toBe('v3 final') })
it('offline folder edits are preserved and sync on reconnect', async () => { const repoA = createTrackedRepo() const repoB = createTrackedRepo() const managerA = new DocumentManager(repoA) const managerB = new DocumentManager(repoB)
// Create a folder in A const folderUrl = managerA.createFolder() await managerA.createFileInFolder(folderUrl, 'existing.md')
// Clone to B const binary = await repoA.export(folderUrl) expect(binary).toBeDefined() const folderHandleB = repoB.import<PushworkDirectoryDocument>(binary!) const folderHandleA = await repoA.find<PushworkDirectoryDocument>(folderUrl)
// B adds a file offline const offlineFile = managerB.createFile('offline.md') folderHandleB.change(doc => { doc.docs.push({ name: 'offline.md', type: 'file', url: offlineFile }) })
// Reconnect folderHandleA.merge(folderHandleB)
const merged = folderHandleA.doc()! expect(merged.docs).toHaveLength(2) const names = merged.docs.map(d => d.name).sort() expect(names).toEqual(['existing.md', 'offline.md']) })})
// ===========================================================================// Suite 4: Conflict resolution// ===========================================================================
describe('Conflict resolution', () => { afterEach(async () => { await cleanupTrackedRepos() })
it('concurrent text edits to different parts merge cleanly', async () => { const repoA = createTrackedRepo() const repoB = createTrackedRepo() const managerA = new DocumentManager(repoA) const managerB = new DocumentManager(repoB)
// Create a file with multi-line content const fileUrl = managerA.createFile('note.md') await managerA.updateContent(fileUrl, 'line1\nline2\nline3')
// Clone to B const binary = await repoA.export(fileUrl) expect(binary).toBeDefined() const handleB = repoB.import<PushworkFileDocument>(binary!) const handleA = await repoA.find<PushworkFileDocument>(fileUrl)
// A edits the beginning await managerA.updateContent(fileUrl, 'MODIFIED\nline2\nline3')
// B edits the end await managerB.updateContent(handleB.url, 'line1\nline2\nMODIFIED')
// Merge handleA.merge(handleB)
const merged = visible(handleA.doc()) // Non-overlapping edits merge deterministically in Automerge. // A replaced "line1" -> "MODIFIED" at the start; B replaced "line3" -> "MODIFIED" at the end. // Both diffs target different character positions, so the result is exact. expect(merged).toBe('MODIFIED\nline2\nMODIFIED') })
it('concurrent edits to the same text region produce a coherent merge', async () => { const repoA = createTrackedRepo() const repoB = createTrackedRepo() const managerA = new DocumentManager(repoA) const managerB = new DocumentManager(repoB)
// Create a file const fileUrl = managerA.createFile('note.md') await managerA.updateContent(fileUrl, 'Hello World')
// Clone to B const binary = await repoA.export(fileUrl) expect(binary).toBeDefined() const handleB = repoB.import<PushworkFileDocument>(binary!) const handleA = await repoA.find<PushworkFileDocument>(fileUrl)
// Both edit the same region concurrently await managerA.updateContent(fileUrl, 'Hello Beautiful World')
await managerB.updateContent(handleB.url, 'Hello Wonderful World')
// Merge handleA.merge(handleB)
const merged = visible(handleA.doc()) // EDGE CASE: Automerge's text CRDT merges at the character level. // Both "Beautiful" and "Wonderful" were inserted at the same position. // The result will contain characters from BOTH insertions interleaved. // This is expected CRDT behavior — not a clean "pick one" merge. // The important thing: no data is lost, the result is a valid string. expect(typeof merged).toBe('string') expect(merged.length).toBeGreaterThan('Hello World'.length) // Both insertions contributed characters expect(merged).toContain('Hello') expect(merged).toContain('World') })
it('concurrent folder entry removal and addition merge independently', async () => { // Tests that removing one entry while another repo adds a different entry // results in a merge where both operations take effect independently. // This is NOT a true conflict — both operations target different list positions. const repoA = createTrackedRepo() const repoB = createTrackedRepo() const managerA = new DocumentManager(repoA)
// Create a folder with a file const folderUrl = managerA.createFolder() const fileUrl = await managerA.createFileInFolder(folderUrl, 'note.md') await managerA.updateContent(fileUrl, 'some content')
// Clone folder to B const folderBinary = await repoA.export(folderUrl) expect(folderBinary).toBeDefined() const folderHandleB = repoB.import<PushworkDirectoryDocument>(folderBinary!) const folderHandleA = await repoA.find<PushworkDirectoryDocument>(folderUrl)
// A removes the file from the folder folderHandleA.change(doc => { const idx = doc.docs.findIndex(d => d.url === fileUrl) if (idx !== -1) doc.docs.splice(idx, 1) })
// B adds another file to the same folder const managerB = new DocumentManager(repoB) const newFile = managerB.createFile('new.md') folderHandleB.change(doc => { doc.docs.push({ name: 'new.md', type: 'file', url: newFile }) })
// Merge folderHandleA.merge(folderHandleB)
const merged = folderHandleA.doc()! // The original file should be removed (A's delete) and the new file should be present (B's add) const names = merged.docs.map(d => d.name) expect(names).not.toContain('note.md') expect(names).toContain('new.md') })
it('concurrent folder entry additions from both sides merge', async () => { const repoA = createTrackedRepo() const repoB = createTrackedRepo() const managerA = new DocumentManager(repoA) const managerB = new DocumentManager(repoB)
// Create an empty folder in A const folderUrl = managerA.createFolder()
// Clone to B const binary = await repoA.export(folderUrl) expect(binary).toBeDefined() const folderHandleB = repoB.import<PushworkDirectoryDocument>(binary!) const folderHandleA = await repoA.find<PushworkDirectoryDocument>(folderUrl)
// Both add multiple files concurrently const fileA1 = managerA.createFile('a1.md') const fileA2 = managerA.createFile('a2.md') folderHandleA.change(doc => { doc.docs.push({ name: 'a1.md', type: 'file', url: fileA1 }) doc.docs.push({ name: 'a2.md', type: 'file', url: fileA2 }) })
const fileB1 = managerB.createFile('b1.md') const fileB2 = managerB.createFile('b2.md') folderHandleB.change(doc => { doc.docs.push({ name: 'b1.md', type: 'file', url: fileB1 }) doc.docs.push({ name: 'b2.md', type: 'file', url: fileB2 }) })
// Merge folderHandleA.merge(folderHandleB)
const merged = folderHandleA.doc()! expect(merged.docs).toHaveLength(4) const names = merged.docs.map(d => d.name).sort() expect(names).toEqual(['a1.md', 'a2.md', 'b1.md', 'b2.md']) })
it('large content concurrent edits merge without data loss', async () => { const repoA = createTrackedRepo() const repoB = createTrackedRepo() const managerA = new DocumentManager(repoA)
// Create a file with substantial content const lines = Array.from({ length: 100 }, (_, i) => `Line ${i + 1}: Lorem ipsum dolor sit amet`) const originalContent = lines.join('\n') // Keep this transport/CRDT test on scalar text. Structured Markdown block // reconciliation has its own concurrency tests and is not what this case // is intended to exercise. const fileUrl = managerA.createFile('large.txt') await managerA.updateContent(fileUrl, originalContent)
// Clone to B const binary = await repoA.export(fileUrl) expect(binary).toBeDefined() const handleB = repoB.import<PushworkFileDocument>(binary!) const handleA = await repoA.find<PushworkFileDocument>(fileUrl)
// Apply non-overlapping CRDT edits directly. This test verifies Automerge's // large concurrent text merge, not Markdown structure reconciliation. handleA.change(doc => { Automerge.splice(doc as never, ['content'], 0, 0, '# Document Title\n\n') }) const appendAt = inspectRichText(handleB.doc() as never, { path: ['content'] }) .toRichPosition(originalContent.length, 'after-block') handleB.change(doc => { Automerge.splice(doc as never, ['content'], appendAt, 0, '\n\n--- End of document ---') })
// Merge handleA.merge(handleB)
const merged = visible(handleA.doc()) // Non-overlapping edits (prepend vs append) merge deterministically. // The merged result should be: header + original + footer. const expectedMerged = '# Document Title\n\n' + originalContent + '\n\n--- End of document ---' expect(merged).toBe(expectedMerged) })})
// ===========================================================================// Suite 5: Edge cases and known issues (documented)// ===========================================================================
describe('Edge cases and known issues', () => { afterEach(async () => { await cleanupTrackedRepos() })
it('KNOWN: same-position text insertions interleave at character level', async () => { // EDGE CASE: When two sides insert text at the exact same position, // Automerge's text CRDT interleaves characters from both insertions. // This is NOT a clean "pick one side" — it's character-level interleaving. // Example: inserting "ABC" and "XYZ" at the same position may produce // "AXBYCZ" or "XAYBZC" depending on actor ordering. // This is expected CRDT behavior and will be addressed by the // conflict resolution UI in Chainlink 8. const repoA = createTrackedRepo() const repoB = createTrackedRepo() const managerA = new DocumentManager(repoA) const managerB = new DocumentManager(repoB)
const fileUrl = managerA.createFile('note.md') await managerA.updateContent(fileUrl, 'Hello')
const binary = await repoA.export(fileUrl) expect(binary).toBeDefined() const handleB = repoB.import<PushworkFileDocument>(binary!) const handleA = await repoA.find<PushworkFileDocument>(fileUrl)
// Both append at the end await managerA.updateContent(fileUrl, 'Hello ABC') await managerB.updateContent(handleB.url, 'Hello XYZ')
handleA.merge(handleB)
const merged = visible(handleA.doc()) // The merged text contains all characters from both insertions expect(merged).toContain('Hello') // All inserted characters are present (no data loss) for (const char of 'ABCXYZ') { expect(merged).toContain(char) } // But the order is interleaved, not "ABC" followed by "XYZ" // (or vice versa). This is the known character-interleaving behavior. // Both sides independently insert 4 chars (" ABC" and " XYZ") at the // same position — both insertions are preserved, including both spaces. // Total: 5 ("Hello") + 4 (" ABC") + 4 (" XYZ") = 13 expect(merged.length).toBe('Hello'.length + ' ABC'.length + ' XYZ'.length) })
it('KNOWN: concurrent array entry ordering is nondeterministic', async () => { // EDGE CASE: When two repos independently push entries to a CRDT list, // the resulting order depends on Automerge's actor ID ordering. // Consumers should sort by name (or another key) rather than relying // on insertion order after a merge. const repoA = createTrackedRepo() const repoB = createTrackedRepo() const managerA = new DocumentManager(repoA) const managerB = new DocumentManager(repoB)
const folderUrl = managerA.createFolder()
const binary = await repoA.export(folderUrl) expect(binary).toBeDefined() const folderHandleB = repoB.import<PushworkDirectoryDocument>(binary!) const folderHandleA = await repoA.find<PushworkDirectoryDocument>(folderUrl)
const fileA = managerA.createFile('z-last.md') folderHandleA.change(doc => { doc.docs.push({ name: 'z-last.md', type: 'file', url: fileA }) })
const fileB = managerB.createFile('a-first.md') folderHandleB.change(doc => { doc.docs.push({ name: 'a-first.md', type: 'file', url: fileB }) })
folderHandleA.merge(folderHandleB)
const merged = folderHandleA.doc()! expect(merged.docs).toHaveLength(2) // Don't assert on order — it's nondeterministic after concurrent insert. // Always sort by name for display purposes. const names = merged.docs.map(d => d.name).sort() expect(names).toEqual(['a-first.md', 'z-last.md']) })
it('KNOWN: empty document syncs correctly', async () => { // Edge case: a file with empty string content should sync without issues const repoA = createTrackedRepo() const repoB = createTrackedRepo() const managerA = new DocumentManager(repoA)
const fileUrl = managerA.createFile('empty.md') // Content starts as '' (default)
const binary = await repoA.export(fileUrl) expect(binary).toBeDefined() const handleB = repoB.import<PushworkFileDocument>(binary!)
expect(visible(handleB.doc())).toBe('') expect(handleB.doc()!.name).toBe('empty.md') })
it('KNOWN: unicode and emoji content survives sync round-trip', async () => { // Edge case: ensure multibyte characters are not corrupted during sync const repoA = createTrackedRepo() const repoB = createTrackedRepo() const managerA = new DocumentManager(repoA)
const fileUrl = managerA.createFile('unicode.md') const unicodeContent = '日本語テスト 🎉 émojis\n\n中文测试 🚀\n\nعربي' await managerA.updateContent(fileUrl, unicodeContent)
const binary = await repoA.export(fileUrl) expect(binary).toBeDefined() const handleB = repoB.import<PushworkFileDocument>(binary!)
expect(visible(handleB.doc())).toBe(unicodeContent) })
it('KNOWN: merge is idempotent — merging twice produces the same result', async () => { // Edge case: merging the same handle twice should not cause duplication const repoA = createTrackedRepo() const repoB = createTrackedRepo() const managerA = new DocumentManager(repoA) const managerB = new DocumentManager(repoB)
const fileUrl = managerA.createFile('note.md') await managerA.updateContent(fileUrl, 'content')
const binary = await repoA.export(fileUrl) expect(binary).toBeDefined() const handleB = repoB.import<PushworkFileDocument>(binary!) const handleA = await repoA.find<PushworkFileDocument>(fileUrl)
await managerB.updateContent(handleB.url, 'content from B')
// Merge twice handleA.merge(handleB) handleA.merge(handleB)
expect(visible(handleA.doc())).toBe('content from B') })
it('KNOWN: very rapid sequential edits are all captured', async () => { // Edge case: ensure that many rapid updateText calls don't lose data const repo = createTrackedRepo() const manager = new DocumentManager(repo)
const fileUrl = manager.createFile('rapid.md')
// 50 rapid sequential edits for (let i = 0; i < 50; i++) { await manager.updateContent(fileUrl, `edit #${i}`) }
const content = (await readText(manager, fileUrl)).content expect(content).toBe('edit #49') })
it('BroadcastChannel cross-tab sync with rapid alternating edits', async () => { // Stress test: both tabs making rapid alternating edits const harness = createBCSyncHarness() try { const { managerA, managerB } = harness
const fileUrl = managerA.createFile('ping-pong.md') await managerA.updateContent(fileUrl, 'start')
// Wait for B to see the document await waitForContent(managerB, fileUrl, 'start')
// A edits, then B edits (sequential, not truly concurrent) await managerA.updateContent(fileUrl, 'from A') await waitForContent(managerB, fileUrl, 'from A')
await managerB.updateContent(fileUrl, 'from B') await waitForContent(managerA, fileUrl, 'from B')
await managerA.updateContent(fileUrl, 'final from A') await waitForContent(managerB, fileUrl, 'final from A') } finally { await harness.cleanup() } })})