Reference solution
A strong review should identify the following issues, in roughly this priority order.
Findings
1. Queue entries are deleted before the server durably acknowledges them
Location: src/autosave-drainer.ts:33-45
drain removes an operation before calling the API. A tab crash, navigation teardown, or process termination after the delete but before acknowledgement leaves no durable record to retry, so an edit can disappear permanently. Re-enqueuing in the catch block cannot repair a crash and does not cover an accepted request whose response is lost.
Keep the durable entry through the send and acknowledgement. Atomically mark it acknowledged and remove it only after the accepted result has been recorded; make abandoned claims retryable.
2. The durable queue namespace crosses account boundaries
Location: src/autosave-queue.ts:6-20
The storage prefix and key include the document ID but not the account ID, and list ignores its accountId argument. If two accounts use the same document ID, either account's drainer enumerates and removes both identities' operations. This violates both data preservation and account isolation.
Include the stable account identity in every queue key, prefix, lookup, claim, and cleanup boundary.
3. Retries send a new idempotency key
Location: src/autosave-drainer.ts:33-44
The operation already has a stable operationId, but every send creates a new UUID. If the server accepts a request and the response is lost, the re-enqueued retry appears to be a new edit and can apply the same operation twice.
Use the persisted operation ID as the API idempotency key for every attempt and retain it until the acknowledgement is durably reconciled.
4. Multiple tabs can drain the same durable operations concurrently
Location: src/autosave-drainer.ts:19-27 and src/autosave-lifecycle.ts:39-51
Every open tab constructs a drainer and responds to the same online and broadcast signals. There is no durable claim, lease, or exclusive lock around list and send, so two tabs can select and transmit the same operation together.
Claim each entry atomically with an expiring owner lease, or use a suitable cross-tab exclusive lock, and verify ownership when acknowledging it.
5. A server acknowledgement replaces text that still has pending edits
Location: src/document-session.ts:77-85
applyAcknowledgement installs the server snapshot directly after removing only the acknowledged operation ID. If the user typed another queued or not-yet-queued edit while the request was in flight, that newer local text is discarded from the editor.
Maintain an acknowledged base and an ordered operation log. Remove the acknowledged operation, adopt the returned base revision, and replay all remaining pending edits to derive visible text.
6. A delayed save reads navigation identity after it has changed
Location: src/document-session.ts:37-64
The timeout captures the old body, but persist reads the session's mutable accountId, documentId, and revision when it eventually runs. Navigating before the delay expires can therefore store one document's body as an operation for another document or account.
Cancel or flush delayed work on navigation and bind each scheduled operation to the identity and revision captured when the edit occurred.
7. Reopening documents accumulates global drain listeners
Location: src/autosave-lifecycle.ts:48-51
Each open call adds new anonymous online and channel listeners, but no close path removes them. Repeated navigation starts multiple drain loops, retains old sessions, and amplifies every reconnect or queue notification.
Own one stable listener set per lifecycle instance, remove it on close or account change, and join or invalidate drain work belonging to the closed session.
8. The editor reports a save before the durable enqueue completes
Location: src/document-session.ts:55-69
The session changes status to saved and notifies the UI before awaiting queue.enqueue. Closing the page in that interval, or an enqueue failure, means the UI claimed durability that never existed.
Show a saving state until the durable transaction commits. Only then report local persistence and notify drainers; surface a durable-storage failure without claiming success.
Reasonable non-findings
- Debouncing edits for 400 milliseconds is allowed when navigation correctly owns or flushes the scheduled operation.
- Re-enqueuing after an ordinary failed request is a useful recovery action, but it cannot compensate for deleting before an ambiguous side effect.
- Broadcasting that work exists is a reasonable wake-up mechanism; it does not replace atomic ownership of queue entries.
- Keeping the server's normalized snapshot is required as the new base. Replacing the visible body without replaying pending operations is the defect.
- Indexed storage is abstracted behind
DurableQueueDriver; reviewing browser-specific transaction APIs is outside this exercise.