feat: add bidirectional notebook sync#117
Conversation
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Notebooks now sync before notes in syncNow() to ensure note-notebook dependencies are satisfied. Adds pullNotebooks/pushNotebooks methods and applyRemoteNotebookChange for bidirectional notebook sync. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move validateNotebookTree from inline test definition to a shared module so it can be reused by the API route and other consumers. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 405559595b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| break; | ||
| } | ||
| } | ||
|
|
||
| this.state.notebookCursor = result.cursor; |
There was a problem hiding this comment.
Preserve notebook cursor on partial apply failures
If applyRemoteNotebookChange throws for any item, the loop breaks, but the code still advances notebookCursor to the server cursor. That acknowledges unapplied changes and prevents them from being retried, which can permanently desync notebooks after a single malformed or out-of-order change; this should track the last successfully applied version like pull() does for notes.
Useful? React with 👍 / 👎.
| notebookId: notebook.id, | ||
| operation: 'update' as const, | ||
| data: JSON.stringify({ |
There was a problem hiding this comment.
Emit notebook delete operations in push payloads
The push payload forces every pending notebook change to operation: 'update', so local notebook removals are never sent as delete events. Because notebook deletes are hard deletes locally, removed rows cannot appear in getPendingChanges, so other devices keep notebooks that were deleted on this device.
Useful? React with 👍 / 👎.
| if (!latestByNotebook.has(entry.notebookId) && entry.operation !== 'delete' && entry.data) { | ||
| const parsed = JSON.parse(entry.data); | ||
| latestByNotebook.set(entry.notebookId, { parentId: parsed.parentId, depth: parsed.depth }); |
There was a problem hiding this comment.
Exclude deleted notebooks from tree validation snapshot
This reconstruction iterates log entries newest-first, but when the latest entry is delete it does not mark that notebook ID as handled. An older non-delete entry for the same notebook is then added back, so tree validation can incorrectly treat previously deleted parents as existing and accept invalid parent references.
Useful? React with 👍 / 👎.
| const changes = await db | ||
| .select() | ||
| .from(notebookSyncLog) | ||
| .where(and(eq(notebookSyncLog.userId, userId), gt(notebookSyncLog.version, cursor))) |
There was a problem hiding this comment.
Add migration for notebook_sync_log before using it
These new routes query notebookSyncLog, but the commit does not add a corresponding SQL migration in packages/api/drizzle to create notebook_sync_log. In environments that provision schema via pnpm db:migrate, /sync/notebooks will fail at runtime with a missing-table error.
Useful? React with 👍 / 👎.
- Fix pullNotebooks() to only advance cursor to last successfully applied change (prevents skipping failed changes on retry) - Fix tree validation snapshot to properly exclude deleted notebooks (prevents ghost parent references in validation) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Codex Review ResponsesP1: Cursor advancement on partial failure — Fixed in 043148c. P1: Notebook delete operations — Valid observation. Notebook deletes are hard deletes in SQLite, so deleted rows cannot appear in P2: Deleted notebooks in tree validation — Fixed in 043148c. Tree validation snapshot now uses a P1: Missing migration — Not applicable. The API uses Drizzle with Turso and schema is managed via |
Summary
needs_sync,local_version)Changes
notebookSyncLogtable +GET/POST /sync/notebooksendpointsSyncServicenow orchestrates notebook → note sync cyclevalidateNotebookTree()extracted to@readied/sync-coreTest plan
🤖 Generated with Claude Code