Skip to content
Closed
Show file tree
Hide file tree
Changes from 41 commits
Commits
Show all changes
196 commits
Select commit Hold shift + click to select a range
b4b2306
Establish trusted Automerge reference backend
cursoragent Aug 7, 2026
072c26e
Expose the Automerge sync protocol
cursoragent Aug 7, 2026
b2e3945
Add persistent document collaboration
cursoragent Aug 7, 2026
af26887
Connect the document editor to Automerge
cursoragent Aug 7, 2026
3092be6
Regenerate the versioned Automerge module
cursoragent Aug 7, 2026
9e9a822
Materialize live collaboration edits
cursoragent Aug 7, 2026
b8b3208
Reconnect collaborative document sessions
cursoragent Aug 7, 2026
d67e117
Show collaborator cursors in documents
cursoragent Aug 7, 2026
63f1aea
Audit Automerge Rust dependencies
cursoragent Aug 7, 2026
cea43c1
Decode native Automerge binary containers
cursoragent Aug 7, 2026
16594d8
Materialize native Automerge operations
cursoragent Aug 7, 2026
9f22ebc
Encode native Automerge sync messages
cursoragent Aug 7, 2026
5a28b82
Decode Automerge binaries in pure Go
cursoragent Aug 7, 2026
20a7a25
Adopt the validated native history decoder
cursoragent Aug 7, 2026
b2c2ebf
Encode native Automerge changes
cursoragent Aug 7, 2026
83cd5bd
Expose the experimental pure-Go engine
cursoragent Aug 7, 2026
c531de1
Make the pure-Go Automerge engine default
cursoragent Aug 7, 2026
7e34174
Fix Go lint violations
cursoragent Aug 7, 2026
cc2902f
Wait for peer acknowledgements during sync
cursoragent Aug 8, 2026
5efbcc2
Topologically apply merged sync changes
cursoragent Aug 8, 2026
6b83b71
Preserve remote change hashes during sync
cursoragent Aug 8, 2026
93c9033
Restore collaborative block controls
cursoragent Aug 8, 2026
a8710e9
Preserve tables during collaboration
cursoragent Aug 8, 2026
73bff9b
Restore collaborative undo history
cursoragent Aug 8, 2026
b4b47dc
Encode dividers as structural blocks
cursoragent Aug 8, 2026
c1a5f69
Preserve Mermaid language through CRDT
cursoragent Aug 8, 2026
404ebeb
Send incremental Automerge sync changes
cursoragent Aug 8, 2026
5939003
Broadcast edits through in-memory rooms
cursoragent Aug 8, 2026
a9f05f5
Persist append-only collaboration changes
cursoragent Aug 8, 2026
f873804
Compact persisted collaboration changes
cursoragent Aug 8, 2026
9d67f44
Debounce persistence off the sync path
cursoragent Aug 8, 2026
981ca79
Fan out collaboration with PostgreSQL notify
cursoragent Aug 8, 2026
6afc786
Broadcast cursor presence through rooms
cursoragent Aug 8, 2026
0ccfeab
Close collaboration sockets during shutdown
cursoragent Aug 8, 2026
a79dfde
Project legacy dividers without content
cursoragent Aug 8, 2026
3b0055d
Require canonical structural dividers
cursoragent Aug 8, 2026
af91ee6
Store empty Automerge heads as bytes
cursoragent Aug 8, 2026
da804a1
Normalize text after structural dividers
cursoragent Aug 8, 2026
5f2342b
Cover edits during in-flight sync
cursoragent Aug 8, 2026
9cfec6c
Sync text after structural leaf blocks
cursoragent Aug 8, 2026
dc7314e
Add collaboration sync diagnostics
cursoragent Aug 8, 2026
525ba3f
Reconcile remote structural text edits
cursoragent Aug 8, 2026
66219c2
Keep slash commands out of documents
cursoragent Aug 8, 2026
c8a8b1b
Hide placeholder during slash commands
cursoragent Aug 8, 2026
1bd771e
Fix collaboration diagnostic import order
cursoragent Aug 8, 2026
557b83a
Preserve text following horizontal rules
cursoragent Aug 8, 2026
7830893
Extend native and reference Automerge backends
cursoragent Aug 9, 2026
fe8e024
Expand public Automerge API for parity
cursoragent Aug 9, 2026
5cd4caa
Add upstream Automerge parity test suite
cursoragent Aug 9, 2026
e6c1e2c
Add Automerge parity tooling and benchmarks
cursoragent Aug 9, 2026
8863a5a
Document Automerge parity plan
cursoragent Aug 9, 2026
c4e50d5
Record author's prior change in change dependencies
cursoragent Aug 9, 2026
c5ae7be
Expose list element object replacement
cursoragent Aug 9, 2026
e1aa61b
Cover Rust core-model conflict and history parity
cursoragent Aug 9, 2026
421e141
Cover Rust list-indexing, rollback, and delete-actor parity
cursoragent Aug 9, 2026
6c19c09
Cover empty-commit and op-tree boundary save/load parity
cursoragent Aug 9, 2026
6599bb0
Resolve list-index conflicts against all visible values
cursoragent Aug 9, 2026
1c1db76
Cover Rust list counter deletion parity
cursoragent Aug 9, 2026
7b9ca5f
Cover Rust read-only synchronization parity
cursoragent Aug 9, 2026
90135b3
Cover Rust batch-insertion parity
cursoragent Aug 9, 2026
a6072d6
Cover Rust incremental orphan-load rejection
cursoragent Aug 9, 2026
f3fab7f
Fix native sync message generation edge cases
cursoragent Aug 9, 2026
51f9cd1
Cover previously deferred read-only sync parity
cursoragent Aug 9, 2026
dc6cd17
Cover Rust rich-text span parity
cursoragent Aug 9, 2026
66c6ed3
Include block markers in the rich-text index space
cursoragent Aug 9, 2026
812288a
Cover block-adjacent mark and splice parity
cursoragent Aug 9, 2026
c887260
Normalize splice positions inside UTF-16 characters
cursoragent Aug 9, 2026
c425d0c
Cover UTF-16 mid-character splice parity
cursoragent Aug 9, 2026
e78f5ec
Preserve expanding marks across boundary replacements
cursoragent Aug 9, 2026
bdc0b62
Cover mark-boundary splice parity
cursoragent Aug 9, 2026
f158d71
Expose active text marks and historical marks
cursoragent Aug 9, 2026
bdc86ad
Cover active-mark reads and record expansion defect
cursoragent Aug 9, 2026
f6cd639
Cover branchy synchronization convergence
cursoragent Aug 9, 2026
3965069
Cover JavaScript Date interoperability in maps and lists
cursoragent Aug 9, 2026
3fe0b7e
Cover reusing JavaScript dates across documents
cursoragent Aug 9, 2026
5956470
Expose document statistics
cursoragent Aug 9, 2026
5571974
Cover document statistics parity
cursoragent Aug 9, 2026
6a22995
Add current-state materialization patches
cursoragent Aug 9, 2026
d6428af
Cover current-state materialization parity
cursoragent Aug 9, 2026
350d336
Cover remaining current-state materialization cases
cursoragent Aug 9, 2026
6e09542
Add diff patches between two frontiers
cursoragent Aug 9, 2026
5ffdf25
Cover diff reversal parity
cursoragent Aug 9, 2026
2c2a61e
Cover large list diff index parity
cursoragent Aug 9, 2026
4de11d9
Add grapheme-aware update_text with Rust parity
cursoragent Aug 9, 2026
cdf5781
Index text operations by UTF-16 code units
cursoragent Aug 9, 2026
da8e0ac
Diff text sequences by UTF-16 with deletions
cursoragent Aug 9, 2026
25620fa
Emit mark patches from text diffs
cursoragent Aug 9, 2026
ea832ec
Add incremental diff cursor and patch log
cursoragent Aug 9, 2026
56bcbf7
Carry marks on text splice patches
cursoragent Aug 9, 2026
51a4078
Derive mark patches from mark operations
cursoragent Aug 9, 2026
cc2dfe9
Expand marks across same-anchor insertions
cursoragent Aug 9, 2026
f423b30
Cover cross-page mark patch isolation
cursoragent Aug 9, 2026
41a2ed9
Add update_spans for text and marks
cursoragent Aug 9, 2026
c3f4706
Support blocks in update_spans
cursoragent Aug 9, 2026
7804f80
Cover batch-insert patch generation
cursoragent Aug 9, 2026
a5aab3d
Add historical spans and block diff materialization
cursoragent Aug 9, 2026
84d7486
Cover JavaScript text behaviors
cursoragent Aug 9, 2026
efec1da
Cover JavaScript block behaviors
cursoragent Aug 9, 2026
82c789e
Cover transaction rollback parity
cursoragent Aug 9, 2026
056f07c
Cover incremental mark patch loading
cursoragent Aug 9, 2026
23367d9
Cover list-range values and conflicts
cursoragent Aug 9, 2026
b9117d9
Add string-to-text load migration
cursoragent Aug 9, 2026
2e1e75c
Add historical text cursors
cursoragent Aug 9, 2026
745251d
Cover clock ancestry and scope internal doctests
cursoragent Aug 9, 2026
7f529c0
Cover whole-document first sync response
cursoragent Aug 9, 2026
7ef435c
Scope legacy sync out and cover change encoding
cursoragent Aug 9, 2026
bfd08e1
Derive mark patches from operations, not state
cursoragent Aug 9, 2026
a58d144
Scope internal-only upstream cases
cursoragent Aug 9, 2026
d09c6cc
Cover loading current state from a fixture
cursoragent Aug 9, 2026
2de0183
Cover unmarked insertion after a block span
cursoragent Aug 9, 2026
d8dc5ad
Expose bloom, isolation, and save reference FFIs
cursoragent Aug 9, 2026
c319204
Reach complete V2 interoperability parity
cursoragent Aug 9, 2026
41d2c1c
Cache RGA insertion order incrementally
cursoragent Aug 9, 2026
9c558fe
Materialize sequence reads from incremental caches
cursoragent Aug 9, 2026
773d8a8
Index map operations by property
cursoragent Aug 9, 2026
70f1142
Treat an equal list assignment as a no-op
cursoragent Aug 9, 2026
23ff913
Add randomized differential stress tests
cursoragent Aug 9, 2026
7a1657f
Add a differential fuzz target for operations
cursoragent Aug 9, 2026
1e0855d
Specify the mark boundary defect and its fix
cursoragent Aug 9, 2026
6202cc5
Anchor insertions against mark boundaries
cursoragent Aug 9, 2026
2c13687
Clamp over-long splice deletions and mark ranges
cursoragent Aug 9, 2026
bf0a538
Characterize the remaining empty-text mark case
cursoragent Aug 9, 2026
3c2f2ba
Extend an unmatched mark begin over following text
cursoragent Aug 10, 2026
87a7c0a
Exclude parity inventory from secret scanning
cursoragent Aug 10, 2026
9d24510
Apply go fix modernizations and tidy whitespace
cursoragent Aug 10, 2026
c34bfef
Match upstream mark order and tolerate unknowns in renderer
cursoragent Aug 10, 2026
1460709
Add ProseMirror render parity gate against upstream
cursoragent Aug 10, 2026
00b3244
Guard ProseMirror schema mapping against drift
cursoragent Aug 10, 2026
5d64ef1
Render session and persistent tracker durations
cursoragent Aug 10, 2026
2fc688c
Run the frontend test suite in CI
cursoragent Aug 10, 2026
5049dfe
Cover edit-derived spans in render parity
cursoragent Aug 10, 2026
eab05a7
Degrade malformed rich-text structure safely
cursoragent Aug 10, 2026
fa20fd6
Add reverse ProseMirror span parity oracle
cursoragent Aug 10, 2026
d87a544
Add repeatable ProseMirror bridge benchmarks
cursoragent Aug 10, 2026
9276a4a
Keep stray table parts out of rendered documents
cursoragent Aug 10, 2026
e52a19d
Add property-generated ProseMirror differential corpus
cursoragent Aug 10, 2026
e31dfaf
Cover concurrent merges and real mark edits
cursoragent Aug 10, 2026
7809e74
Allow property corpus enough time under CI load
cursoragent Aug 10, 2026
2f181aa
Collapse conflicts when assigning an unchanged value
cursoragent Aug 10, 2026
c332c56
Pin the dangling mark divergence with reproducers
cursoragent Aug 10, 2026
0a8914a
Start leftward dangling marks at their anchor
cursoragent Aug 10, 2026
68225d1
Quiesce sync when a needed change never arrives
cursoragent Aug 10, 2026
da171db
Split the native engine by responsibility
cursoragent Aug 10, 2026
258f962
Add model-based sync and persistence chaos testing
cursoragent Aug 10, 2026
eb3ae1b
Run the real official Automerge benchmark battery
cursoragent Aug 10, 2026
c7c9852
Apply sync test whitespace formatting
cursoragent Aug 10, 2026
626e4d3
Keep collaboration working with an inconsistent frontier
cursoragent Aug 10, 2026
3427ff4
Extract native internals into real Go packages
cursoragent Aug 10, 2026
4d84fee
Replace generic types package with opset domain
cursoragent Aug 10, 2026
1271d93
Rebuild change identity when loading a snapshot
cursoragent Aug 10, 2026
e54dbe3
Add a document-chunk encoder
cursoragent Aug 10, 2026
2b76145
Drop a scratch probe left in the encoder commit
cursoragent Aug 10, 2026
bcf94f0
Compact the history when saving a document
cursoragent Aug 10, 2026
1f4fa86
Record the snapshot encoder in the parity plan
cursoragent Aug 10, 2026
a555147
Degrade incremental reads instead of wedging
cursoragent Aug 11, 2026
4bce2ac
Compact by default when saving
cursoragent Aug 11, 2026
9902087
Anchor split blocks against mark boundaries
cursoragent Aug 11, 2026
3fc9983
Fold the save variants into Save options
cursoragent Aug 11, 2026
0a82280
Fold load variants and drop the PureGo aliases
cursoragent Aug 11, 2026
d150f1d
Accept Change values in ApplyChanges
cursoragent Aug 11, 2026
39c26e2
Update the parity plan for the new save API
cursoragent Aug 11, 2026
bc58b7f
Round off small public API rough edges
cursoragent Aug 11, 2026
0437e19
Cache the compacted save between mutations
cursoragent Aug 11, 2026
d078f43
Make text splicing linear in document size
cursoragent Aug 11, 2026
6542341
Preallocate decode buffers to speed up load
cursoragent Aug 11, 2026
b2aadd7
Inventory the automerge-repo protocol for a Go client
cursoragent Aug 12, 2026
3acfbd8
Add the Go collaboration protocol codec
cursoragent Aug 12, 2026
6e577a9
Add the automerge-repo WebSocket transport layer
cursoragent Aug 12, 2026
9217171
Add the collaboration server session state machine
cursoragent Aug 12, 2026
ffc3a00
Add the server connection driver for the gateway
cursoragent Aug 12, 2026
711cd60
Validate the gateway against a real repo client
cursoragent Aug 12, 2026
beed669
Add the Go client connection driver for agents
cursoragent Aug 12, 2026
464fc9c
Let the gateway adopt the client's document id
cursoragent Aug 12, 2026
416c5a6
Add opaque ephemeral fan-out to the collaboration hub
cursoragent Aug 12, 2026
8eb42f9
Document repo gateway wiring and open contracts
cursoragent Aug 12, 2026
8636574
Carry carets as stable cursors in presence
cursoragent Aug 12, 2026
25801d3
Document cursor-based selection presence
cursoragent Aug 12, 2026
4d37284
Derive automerge-repo document ids in Go
cursoragent Aug 12, 2026
7baa690
Pin the repo gateway integration contract
cursoragent Aug 12, 2026
389ffe1
Mirror repo document-id derivation in the frontend
cursoragent Aug 12, 2026
8d4580b
Mark the frontend document-id mirror as landed
cursoragent Aug 12, 2026
ec8ebae
Serve documents over the automerge-repo protocol
cursoragent Aug 12, 2026
b660ecc
Mark the repo route as wired in the contract
cursoragent Aug 12, 2026
5cf65fd
Convert ProseMirror documents to Automerge spans
cursoragent Aug 12, 2026
51dcd7c
Seed repo documents from stored content server-side
cursoragent Aug 12, 2026
16995cd
Record forward conversion and seeding as done
cursoragent Aug 12, 2026
fb0a1a9
Add a cross-instance ephemeral NOTIFY envelope
cursoragent Aug 12, 2026
e6c5b02
Publish collaboration ephemerals over NOTIFY
cursoragent Aug 12, 2026
f181a38
Relay repo ephemerals across server instances
cursoragent Aug 12, 2026
beedad4
Record cross-instance ephemeral fan-out as done
cursoragent Aug 12, 2026
5df5a4f
Add cursor-based selection helper for repo presence
cursoragent Aug 12, 2026
eb33fd6
Note the frontend selection helper in the contract
cursoragent Aug 12, 2026
0daed1a
Map ProseMirror selections to Automerge cursors
cursoragent Aug 12, 2026
3d20a46
Move the editor to the automerge-repo client
cursoragent Aug 12, 2026
72bcdd3
Remove the legacy sync protocol and DB presence
cursoragent Aug 12, 2026
23ad6ad
Record the frontend migration and legacy removal
cursoragent Aug 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
bin/
pkg/automerge/internal/reference/wasm/target/
node_modules/
.cache/
.turbo
Expand Down
23 changes: 23 additions & 0 deletions GNUmakefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ NPROC ?= $(shell nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || getconf _
MAKEFLAGS := --jobs=$(NPROC)

CAT ?= cat
CARGO ?= cargo
CP ?= cp
DOCKER ?= docker
GO ?= go
Expand All @@ -13,12 +14,14 @@ NPM ?= npm
NPX ?= npx
OPENSSL ?= openssl
SED ?= sed
SHA256SUM ?= sha256sum
SYFT ?= syft
TAIL ?= tail
ECHO ?= echo
GOLINTCMD ?= golangci-lint
SWIFTLINTCMD ?= swiftlint
SWIFTCMD ?= swift
RUST_TOOLCHAIN ?= 1.89.0
SWIFT_ENROLL_UI ?= cmd/probo-agent/installer/macos/enroll-ui
SWIFT_FORMAT_CONFIG ?= .swift-format
SWIFTLINT_CONFIG ?= .swiftlint.yml
Expand Down Expand Up @@ -104,6 +107,9 @@ EMBEDDED= apps/console/dist/index.html \
apps/compliance-portal/dist/index.html \
@probo/emails

AUTOMERGE_REFERENCE_DIR= pkg/automerge/internal/reference
AUTOMERGE_REFERENCE_WASM= $(AUTOMERGE_REFERENCE_DIR)/reference.wasm

PROBOD_BIN_EXTRA_DEPS=
PROBOD_BIN= bin/probod
PROBOD_SRC= cmd/probod/main.go
Expand Down Expand Up @@ -208,6 +214,11 @@ test-verbose: test ## Run tests with verbose output
test-short: TEST_FLAGS+=-short
test-short: test ## Run short tests only

.PHONY: test-automerge-conformance
test-automerge-conformance: ## Test Go binary compatibility with official Automerge JS
AUTOMERGE_JS_ORACLE=$(CURDIR)/packages/automerge-conformance/oracle.mjs \
$(GO_BASE) test -count=1 ./pkg/automerge

.PHONY: coverage-report
coverage-report: test ## Generate HTML coverage report
$(GO) tool cover -html=coverage.out -o coverage.html
Expand Down Expand Up @@ -414,6 +425,18 @@ pkg/server/api/complianceportal/v1/schema.graphql: pkg/server/api/complianceport
.PHONY: generate
generate: $(GENERATED)

.PHONY: generate-automerge-reference
generate-automerge-reference: ## Rebuild the embedded official Automerge WASM backend
cd $(AUTOMERGE_REFERENCE_DIR)/wasm && \
$(CARGO) +$(RUST_TOOLCHAIN) build --locked --release --target wasm32-wasip1
$(CP) $(AUTOMERGE_REFERENCE_DIR)/wasm/target/wasm32-wasip1/release/probo_automerge_reference.wasm $(AUTOMERGE_REFERENCE_WASM)
cd $(AUTOMERGE_REFERENCE_DIR) && $(SHA256SUM) reference.wasm > reference.wasm.sha256

.PHONY: audit-automerge-reference
audit-automerge-reference: ## Audit Automerge Rust advisories, licenses, bans, and sources
cd $(AUTOMERGE_REFERENCE_DIR)/wasm && \
$(CARGO) +$(RUST_TOOLCHAIN) deny check

.PHONY: embed
embed: $(EMBEDDED)

Expand Down
2 changes: 2 additions & 0 deletions apps/console/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
"preview": "vite preview"
},
"dependencies": {
"@automerge/automerge": "^3.4.0",
"@automerge/prosemirror": "^0.2.0",
"@hookform/resolvers": "^5.7.1",
"@phosphor-icons/react": "^2.1.10",
"@probo/coredata": "^1.0.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,13 @@
// SOFTWARE.

import { formatError } from "@probo/helpers";
import { RichEditor, useToast } from "@probo/ui";
import { useCallback, useState } from "react";
import {
createRichEditorAutomergeDocument,
RichEditor,
supportsRichEditorCollaboration,
useToast,
} from "@probo/ui";
import { useCallback, useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { type PreloadedQuery, useMutation, usePreloadedQuery } from "react-relay";
import { useOutletContext } from "react-router";
Expand All @@ -30,8 +35,20 @@ import { useDebounceCallback } from "usehooks-ts";
import type { DocumentDescriptionPage_updateContentMutation } from "#/__generated__/core/DocumentDescriptionPage_updateContentMutation.graphql";
import type { DocumentDescriptionPageQuery } from "#/__generated__/core/DocumentDescriptionPageQuery.graphql";

import {
type AutomergeDocumentHandle,
connectAutomergeDocument,
} from "./_lib/AutomergeDocumentHandle";

const autoSaveIntervalMs = 1000;

type CollaborationState = {
versionID: string;
handle?: AutomergeDocumentHandle;
failed?: boolean;
reconnecting?: boolean;
};

export const documentDescriptionPageQuery = graphql`
query DocumentDescriptionPageQuery($documentId: ID! $versionId: ID! $versionSpecified: Boolean!) {
# We use this on /documents/:documentId/versions/:versionId/description
Expand Down Expand Up @@ -158,6 +175,86 @@ export function DocumentDescriptionPage(props: {
&& document.canUpdate
&& document.status !== "ARCHIVED"
&& document.writeMode !== "GENERATED";
const collaborationSupported = canEdit
&& supportsRichEditorCollaboration(currentVersion.content);
const [collaboration, setCollaboration] = useState<CollaborationState>();

useEffect(() => {
if (!collaborationSupported) return;

let cancelled = false;
let activeHandle: AutomergeDocumentHandle | undefined;

void connectAutomergeDocument(
currentVersion.id,
content => createRichEditorAutomergeDocument(content),
(connected) => {
if (cancelled) return;

setCollaboration({
versionID: currentVersion.id,
handle: activeHandle,
reconnecting: !connected,
});
if (!connected) {
toast({
title: t("documentDescriptionPage.errors.title"),
description: t("documentDescriptionPage.errors.save"),
variant: "error",
});
}
},
).then((handle) => {
if (cancelled) {
handle.close();
return;
}

activeHandle = handle;
setCollaboration({
versionID: currentVersion.id,
handle,
});
}).catch((error) => {
if (cancelled) return;

setCollaboration({
versionID: currentVersion.id,
failed: true,
});
toast({
title: t("documentDescriptionPage.errors.title"),
description: error instanceof Error
? error.message
: t("documentDescriptionPage.errors.save"),
variant: "error",
});
});

return () => {
cancelled = true;
activeHandle?.close();
};
}, [
collaborationSupported,
currentVersion.id,
t,
toast,
]);

const collaborationConnecting = collaborationSupported
&& (
collaboration?.versionID !== currentVersion.id
|| (!collaboration?.handle && !collaboration?.failed)
);
const collaborationHandle = collaborationSupported
&& collaboration?.versionID === currentVersion.id
&& !collaboration.failed
? collaboration.handle
: undefined;
const collaborationReconnecting = collaborationSupported
&& collaboration?.versionID === currentVersion.id
&& collaboration.reconnecting;

// The editor key must change on explicit actions (delete draft, edit
// title/type) but NOT on auto-save side effects (cursor preservation).
Expand Down Expand Up @@ -199,7 +296,8 @@ export function DocumentDescriptionPage(props: {
className="flex-1"
content={currentVersion.content}
data-theme="document"
disabled={!canEdit}
disabled={!canEdit || collaborationConnecting || collaborationReconnecting}
collaborationHandle={collaborationHandle}
onChangeContent={handleUpdate}
/>
);
Expand Down
Loading
Loading