wu-drafts-cloud-sync
Implement or review WU-DRAFT-001 My Documents for WriteUrdu using the existing METRICS_DB D1 database. Add authenticated writing_documents CRUD, Save to my account, cross-device restore, throttled syn
Files1
View on GitHubSKILL.md.claude/skills/wu-drafts-cloud-sync/SKILL.md
GitHubWriteUrdu My Documents — local-first account persistence
Read first:
specs/WU-DRAFT-001-cross-device-cloud-drafts.mdspecs/WU-AUTH-001-social-authentication-foundation.mdspecs/WU-ACCOUNT-001-account-document-platform-boundary.mddocs/WU-AUTH-DRAFTS-IMPLEMENTATION-PLAN-2026-08-13.md- current
js/editor-tools.jsand writer bootstrap code - current migrations and
METRICS_DBconsumers.
Core rule
Account-backed documents extend local drafts; they never replace them.
The existing browser-local autosave stays on the critical path and remains immediate/offline-safe.
Do not map the local ~650 ms save debounce to D1 writes.
Database decision
Use the existing WriteUrdu D1 database:
env.METRICS_DB
Do not create WRITE_URDU_DB, ACCOUNT_DB or another D1 database.
Expected domain layout:
METRICS_DB
├── telemetry tables
├── share-artifact tables
├── Auth.js tables
└── writing_documents
Document code accesses writing_documents only. It obtains identity through getSession() from the auth wrapper.
Step 1 — verify prerequisite state
Before coding:
- AUTH-A and AUTH-B must be stable;
getSession()returns stablesession.user.id;- Google sign-in/local-writing preservation proof is green;
- inspect current migration sequence;
- capture current D1 table inventory;
- run existing product/telemetry/share baseline.
Do not proceed on an unexplained red baseline.
Step 2 — additive document migration
Create the next numbered migration for:
writing_documents
- id
- user_id
- editor_kind
- title
- content
- plain_text
- format_version
- revision
- created_at
- updated_at
Indexes:
(user_id, updated_at DESC)
(user_id, id)
Initial guards:
- title <= 160 chars;
- content <= 750 KB UTF-8;
- editor kinds: basic/rich/keyboard;
- max 100 account-backed documents/user unless spec changes.
Migration rules:
- additive only;
- never edit applied migrations;
- never drop/rename telemetry/share/Auth.js tables;
- capture before/after table inventory;
- run existing telemetry/share/auth regressions after migration.
Step 3 — authenticated API first
Expected routes:
GET /api/documents
POST /api/documents
GET /api/documents/:id
PATCH /api/documents/:id
DELETE /api/documents/:id
Every handler:
- requires
DOCUMENTS_ENABLED=true; - calls project
getSession(); - uses
session.user.idas owner; - uses
env.METRICS_DB; - uses parameterized SQL;
- returns
Cache-Control: no-store; - validates limits server-side;
- never logs content;
- never trusts client-supplied
user_id.
Do not query Auth.js accounts/sessions tables directly for authorization.
Step 4 — optimistic concurrency
PATCH requires client's last known revision.
Equivalent update:
UPDATE writing_documents
SET title = ?, content = ?, plain_text = ?, revision = revision + 1, updated_at = ?
WHERE id = ? AND user_id = ? AND revision = ?
No matching row because revision moved => 409 Conflict.
No silent last-write-wins and no automatic merge.
Step 5 — basic writer pilot
Integrate through one shared account-persistence client/module, not transliteration code.
First remote action is explicit:
Save to my account
Signing in alone uploads nothing.
Keep local account metadata separate from existing draft payload:
documentId
lastSyncedRevision
lastSyncedSignature
syncState/error
Never store OAuth/session tokens or provider identity in localStorage.
Step 6 — remote sync cadence
For an already account-backed document:
- local save continues normally;
- local signature change marks remote state dirty;
- remote write occurs roughly 20–30 seconds later;
- explicit save may sync immediately;
- safe visibility-change sync is optional;
- D1/API failure never blocks local save.
No per-keystroke network calls.
Step 7 — truthful status
Represent local and account state independently:
Saved on this device
Saved to your account
Saving to your account…
Account save paused — your local draft is safe
This document changed on another device
Never imply remote success when only localStorage succeeded.
Step 8 — My Documents
Build a focused noindex /my-documents route:
- recent list;
- title/preview/editor kind/modified time;
- open in correct editor;
- rename;
- delete with confirmation;
- copy/recovery action;
- empty state linking to writing.
List endpoint should not transfer every full document body.
Step 9 — safe restore
Use existing editor adapter/handoff mechanisms.
Rules:
- no document text in URLs;
- rich content remains rich;
- a different non-empty local draft is not silently replaced;
- preserve local work/history or ask before replacement;
- remote revision metadata is updated correctly after open.
Step 10 — rich + keyboard
Extend the same client/API through existing adapters.
Verify:
- rich HTML formatting survives exact round-trip;
- Urdu/RTL characters survive unchanged;
- keyboard editor round-trip works;
- local history remains independent.
Step 11 — conflict UX
On 409 offer:
- Open account version.
- Keep this device as a new copy.
- Optional explicit Replace account version after fresh revision + confirmation.
Default preserves data.
Shared-D1 safety
The physical database is shared; product domains are not.
Documents module must not:
- update telemetry rollups/events;
- update share artifacts;
- modify Auth.js account/session records;
- join provider account/email fields into document authorization.
Telemetry/share/auth code must not begin reading document content.
Avoid a generic all-table DAO.
Tests
Server:
- unauthenticated CRUD => 401;
- disabled documents => safe unavailable;
- user A cannot list/get/update/delete user B's document;
- invalid kind/oversize/quota rejected;
- list omits full bodies;
- stale revision => 409;
- hard delete removes row;
- no content logging;
- migration preserves telemetry/share/Auth.js tables.
Browser/product:
- signed-out local autosave still works;
- sign-in uploads nothing automatically;
- explicit Save to my account creates one document;
- local save remains immediate during API/D1 failure;
- remote writes are throttled;
- second browser/device restore works;
- rich formatting and Urdu/RTL survive;
- conflict is surfaced;
- remote open does not silently destroy local work.
Run the full existing WriteUrdu suite after focused tests.
Rollback
Set:
DOCUMENTS_ENABLED=false
Local drafts/history continue. Existing remote rows and all shared-database tables remain intact.
Do not drop writing_documents as normal rollback.
Stop conditions
Stop and fix if:
- implementation requires another D1 database;
- document migration touches existing table definitions;
- cloud API becomes required for typing;
- sign-in uploads historical local drafts;
- any document query is not scoped by session user ID;
- remote writes mirror the local debounce;
- rich/Urdu content is corrupted;
- conflict resolution silently overwrites unseen changes.