Context
Scribe is the ecosystem's collaborative-documents product — Notion/Confluence-shaped: rich-text docs, live multi-user editing, team workspaces. Drive is the file product — Dropbox/Google Drive-shaped: a vault that appears as a native folder in Finder or Explorer. Both need offline-capable sync, and both teams designed their sync layers independently.
Scribe published its offline-sync design (client merge of y-indexeddb + PowerSync), and a proposal followed: Drive should reuse Scribe's stack. This page compares the two architectures honestly — what genuinely converged, what genuinely differs, and what that means for reuse. The transport-specific verdict lives in PowerSync as Sync Transport; this page is the architectural comparison behind it.
The convergent core
Neither team copied the other, yet both arrived at the same skeleton: an append-only operation log with a monotonic sequence number, a client-owned cursor, catch-up by replay, a live channel layered on top for connected clients, and a compaction story to bound log growth. This is the "dumb server, smart cursor" pattern — the server is a log plus an auth check; all sync-progress state lives at the edge (see Device Identity & Cardinality).
| Concept | Scribe | Drive |
|---|---|---|
| Append-only log | scribe_doc_updates (seq BIGSERIAL) |
change_log (vault_id, seq) |
| Client-owned cursor | lastSeqApplied in y-indexeddb |
after=N cursor in engine SQLite |
| Catch-up | Phase-2 replay: WHERE seq > lastSeqApplied |
GET /log?after=N replay |
| Live channel | Hocuspocus WebSocket — carries the ops themselves | WebSocket wake hints — deliberately carry nothing |
| Log growth control | Compaction job (collapse old rows past 20 per doc) | Snapshot + min_retained_seq retention |
| Single writer to the log | scribe-backend — clients never insert | Sync server — clients never insert |
One instructive divergence inside the convergence: Scribe's live channel carries data and doubles as the upstream write path, while Drive's wake hints are read-only and content-free — "a hint says something happened, never what" — with upstream writes on a separate mutation POST. Same role in the architecture, different trust placed in the channel.
Where PowerSync actually sits in Scribe
The reuse proposal rests on "Scribe uses PowerSync." So look at what PowerSync does — and does not do — in Scribe's own design:
| Concern | Handled by | PowerSync's role |
|---|---|---|
| Merge / conflict semantics | Yjs CRDT | none |
| Upstream writes | Hocuspocus only — their own invariant: editing clients "never INSERT to PowerSync's upload queue" | none |
| Live propagation | Hocuspocus WebSocket | none |
| Turning rows into something the user sees | Phase-2 replay into the Y.Doc → editor | none — rows sit inert in SQLite until the doc opens |
| Background fan-out of the op log to team SQLite | PowerSync | this, and only this |
In the flagship PowerSync deployment, PowerSync is a downstream-only delivery pipe for an append-only log — the actual sync engine lives elsewhere. Transposed to Drive, the faithful equivalent is the downstream-only hybrid evaluated in the PowerSync record: PowerSync ferries metadata rows to client SQLite while the mutation path, conflict logic, blob plane, and OS adapters remain untouched. Scribe's design is evidence for that scoped swap — and evidence against "reuse Scribe's implementation," because the parts Scribe delegates to PowerSync are the parts Drive already has, and the parts Scribe keeps custom (merge, upstream, presentation) are exactly Drive's hard parts.
Where the products genuinely differ
Merge semantics: text merges, files don't
A Scribe doc is a Yjs CRDT — concurrent edits merge operation-by-operation, and two people typing in the same paragraph both keep their words. That works because rich text has a merge function. A file does not: it is opaque bytes, and there is no principled way to merge two concurrent saves of report.pdf. Drive's honest answer is optimistic concurrency — version-checked mutations, with the losing write preserved as a conflict copy (see Truth & Conflict). Neither approach transfers to the other product: CRDT merge is meaningless for opaque blobs, and conflict copies would be a regression for collaborative text.
Content transport: in-band rows vs a blob plane
A Scribe doc's entire content is the sum of its Yjs diffs — small base64 strings that ride inside the replicated rows. Once PowerSync has delivered the rows, the full doc is reconstructable offline; content and metadata share one channel, and full offline availability of every doc is a free side effect of the transport. Drive's content is unbounded binary. It cannot ride rows, so it lives in a content-addressed blob plane with its own ordering invariant (blob-before-mutation) and its own availability policy — placeholders by default, hydration on open, pinning for offline (see A File's Journey). Any row-sync transport covers all of Scribe's data problem but only the metadata slice of Drive's.
Presentation surface: your own UI vs the operating system
When tree rows land in Scribe's SQLite, "applying" them is a declarative re-query — the app re-renders its own list view, which row-sync tooling supports natively with watched queries. When tree rows land in Drive's SQLite, "applying" them means making Finder agree: imperatively creating and removing placeholders and signalling enumerators through File Provider and CFAPI, continuously, as rows arrive. There is no "re-render the OS from a query." This is the layer that makes Drive hard, and no part of Scribe's stack addresses it (see Meeting the OS).
Identity and access: where the device token went
Scribe has no device tokens in its sync layer — and at first glance that looks like a simplification Drive should copy. It isn't, but seeing why takes the comparison apart into authentication and authorization.
Scribe's model
Authentication: identity is the user, established by the ordinary web login session. PowerSync requires a JWT, so Scribe's backend mints short-lived JWTs from that session whenever the client needs one. No device registration, no device rows, no long-lived sync credential.
Authorization: sync rules assign rows to buckets, and which buckets a client receives is computed from the JWT's user identity joined against membership data. When a user loses access, the membership row changes, PowerSync recomputes her buckets, her clients receive REMOVE operations, and the frontend clears the local cache. Her JWT never changes; nobody rotates a credential. Access is a property of data, and access change is more data flowing down the same pipe.
Why Scribe can skip device identity — and Drive can't
The device identity record gives four reasons device-level identity is necessary. Scribe escapes each one, for reasons that don't transfer:
| Need | How Scribe escapes it | Transfers to Drive? |
|---|---|---|
| Idempotency of writes | Clients never write to the synced table (one-way writes; upstream is Hocuspocus), and Yjs ops are idempotent by construction — applying an op twice is a no-op | No — Drive clients issue mutations that must dedupe server-side |
| Echo suppression | Built into the CRDT: every Y.Doc carries a clientId, and the (clientId, clock) pair makes re-applying your own ops harmless |
No — file operations are not self-deduplicating |
| Revocation granularity | The browser session is the per-device credential; "sign out that laptop" is handled by the web auth stack, below the sync layer | No — Drive's daemon has no browser session layer; it authenticates headless at boot |
| Attribution | created_by at user level suffices for docs |
Mostly — file sync wants device-level attribution ("which machine uploaded the corrupt file") |
Note the detail hiding in the second row: Yjs has device identity inside it. The clientId in every state vector — {A: 6, B: 6} in Scribe's own worked example — is a per-device-session identifier. Scribe didn't eliminate device identity; the CRDT machinery absorbed it, unauthenticated, which is safe only because CRDT merge makes spoofed or duplicated ops harmless to correctness. Drive has no such absorbing layer.
The comparison with Drive's group model
On the authorization side the two models are the same shape. Drive's vault access is a membership edge (device → group → vault) resolved at request time: adding or removing access touches zero tokens. That is the same access-as-data property Scribe gets from sync rules — with one difference in Drive's favor: per-request resolution is immediate, where bucket recomputation is pipeline-mediated.
The only real difference is the authentication subject, and it is forced by platform, not design taste. Scribe's client lives in a browser, which provides per-device sessions and can re-mint short-lived JWTs whenever online — the sync layer gets device-scoped credentials for free and never has to name them. Drive's client is a headless daemon that must authenticate at boot, offline-tolerant, with no interactive login. Something must be the long-lived credential, and that something is the device token. The device token isn't extra machinery on top of a session; it is the session, for a client that has no browser.
Does Scribe's model simplify fanout?
Credential fanout: no. Scribe holds n sessions for n browsers (managed by the auth stack); Drive's target model holds n tokens for n devices. Both linear. The thing that was actually quadratic — Drive's legacy per-vault credential rows — is fixed by the group model, and Scribe's approach would not improve on n + m + membership edges.
Data fanout: Scribe's is heavier, not lighter. Their design states it plainly: every team member's SQLite receives every scribe_doc_updates row, whether or not they ever open the doc. Full content, pushed to everyone, always — affordable when content is tiny text diffs, and exactly the move Drive cannot make with blobs. Drive's hint-plus-pull model exists precisely to avoid pushing everything to everyone.
What transfers — and what doesn't
Transfers:
- PowerSync as a downstream log fan-out. Field-tested next door, on exactly the append-only-log shape Drive has. This is the scoped proposal evaluated in the PowerSync record.
- Access-loss via data, not credentials. Scribe propagates revoked access as
REMOVErows through sync rules, with client-side cache clearing. The same pattern would carry Drive's group membership changes. - The case-matrix discipline. Scribe enumerated fifteen connectivity × cache × dirtiness states and classified every one. Drive's offline behavior deserves the same treatment.
Does not transfer:
- The merge engine. Yjs CRDT has no file analog; Drive's conflict model is not a missing feature but the correct answer for opaque bytes.
- The upstream path. Hocuspocus is a collaborative-session protocol, not a mutation contract with version preconditions and idempotency keys.
- The presentation layer. An editor fed by a Y.Doc and an OS file-system view fed by adapter callbacks share nothing.
- The implementation itself. Even where the pattern transfers, the code does not: different schemas, different buckets, different consumers. What moves across is the pattern and the operational confidence, not the stack.