Skip to content

Commit a2f37cb

Browse files
committed
docs: remove single-note editor parity
1 parent a8ede65 commit a2f37cb

1 file changed

Lines changed: 30 additions & 81 deletions

File tree

docs/RESTART_PLAN.md

Lines changed: 30 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# DeepNotes — Restart (greenfield) plan — v4
22

33
> **Last updated:** 2026-05-30
4-
> **Status:** Phase 0 foundation complete. Phase 1 spatial checklist complete. **Phase 2 backend parity verified.** **Phase 3 collab wire parity complete.** Phase 4 routing decision complete. Phase 5 SPA partially complete.
4+
> **Status:** Phase 0 foundation complete. Phase 1 spatial checklist complete. **Phase 2 backend parity verified.** **Phase 3 collab wire parity complete.** Phase 4 routing decision complete.
55
> **This document replaces all prior restart plan versions.** If a prior statement conflicts with this one, this version wins.
66
> **Analyzed:** 2026-05-30 — additional gaps identified in §0.2–0.4, §3, §4, §6–8. Collab protocol gap and routing/product-model divergence newly documented.
77
@@ -95,7 +95,7 @@ The legacy collab syncs the **entire page state** (note positions, arrow endpoin
9595

9696
**Routing / product-model divergence:** In the new SPA, `/pages/:pageId` renders a **single Tiptap text card** (`PageEditorView.vue`), while `/spatial` is a separate stub showing page pins. In legacy, a "page" IS the spatial canvas — there is no bifurcation. The new architecture implicitly redefines "page" as a text document, which will conflict with spatial parity unless `/pages/:pageId` becomes the canvas and the single-note editor becomes one component inside it (the active note's head/body). **This is a deeper problem than "missing UI" — it is a routing and data-model decision that must be made before Phase 6.**
9797

98-
**Decision required:** Do we commit to full spatial parity, or do we ship a **single-note-per-page** product first and add the canvas later? This plan assumes **full spatial parity is required** because the legacy product is defined by it. If product wants to defer spatial canvas to a v2, rewrite §0.3 and all Phase 6+ references accordingly.
98+
**Decision:** This plan commits to **full spatial parity**. The single-note-per-page stepping stone has been removed; `/pages/:pageId` will be the spatial canvas directly.
9999

100100
---
101101

@@ -122,15 +122,15 @@ These were found during the v3–v4 analysis and must be addressed in the phases
122122

123123
5. **Group password unlock is unscheduled**
124124
- `unlockPageCollabSymmetricKeyring` throws when a group requires a password. The comment says "Unlock is not implemented in the web MVP."
125-
- **Fix:** Add to Phase 8 (group/account polish) or document as v2 scope.
125+
- **Fix:** Add to Phase 7 (group/account polish) or document as v2 scope.
126126

127127
6. **`page_links` / backlink UI is missing**
128128
- The backend has `pageLinks` table and routes (`POST /api/pages/:pageId/backlinks`). No SPA UI exposes backlinks.
129-
- **Fix:** Add backlink display to Phase 5 or Phase 8.
129+
- **Fix:** Add backlink display to Phase 7.
130130

131131
7. **No scheduler / manager CLI replacement**
132132
- Legacy had `apps/scheduler` (cleanup) and `apps/manager` (ops CLI). New repo defers scheduler to "Cron Triggers or Queues" but has no implementation.
133-
- **Fix:** Add deferred scheduler task to Phase 8 or Phase 9.
133+
- **Fix:** Add deferred scheduler task to Phase 7 or Phase 8.
134134

135135
8. **Collab protocol is narrower than legacy; missing bootstrap-over-WS and unacked-buffer retry**
136136
- Legacy client maintains `_unackedUpdates: Map<number, Uint8Array>` and re-sends on reconnect. New client has `collabClientUpdateId` but no `_unackedUpdates` buffer. Legacy collab-server sends `ALL_UPDATES_UNMERGED` on connect. New DO sends nothing — client must `GET /collab-updates` via REST.
@@ -139,11 +139,11 @@ These were found during the v3–v4 analysis and must be addressed in the phases
139139

140140
9. **`@deepnotes/session` package contains 71 files — potential god package**
141141
- It mixes auth, users, groups, pages, billing, collab, crypto, and realtime logic. This violates the feature-based vertical-slice principle.
142-
- **Fix:** Before Phase 8, audit file count per domain. If any subfolder exceeds 20 files, extract to a dedicated package (e.g., `@deepnotes/billing`, `@deepnotes/scheduler`).
142+
- **Fix:** Before Phase 7, audit file count per domain. If any subfolder exceeds 20 files, extract to a dedicated package (e.g., `@deepnotes/billing`, `@deepnotes/scheduler`).
143143

144144
10. **No `@syncedstore/core` or equivalent reactive Yjs wrapper for Vue**
145145
- Legacy uses `@syncedstore/core` so Vue components re-render when CRDT state changes. New repo has `yjs` + `y-protocols` + `@tiptap/y-tiptap` but no SyncedStore.
146-
- **Impact:** Phase 6 spatial canvas cannot use Vue reactivity against Yjs maps directly without a wrapper.
146+
- **Impact:** Phase 5 spatial canvas cannot use Vue reactivity against Yjs maps directly without a wrapper.
147147
- **Fix:** Evaluate SyncedStore with Vite 6 (spike in Phase 3). If bundling fails, document Option C (hybrid reactive proxy) in `docs/SPATIAL_ARCHITECTURE_DECISION.md`.
148148

149149
11. **`page_updates` REST bootstrap lacks pagination / cursor**
@@ -156,8 +156,8 @@ These were found during the v3–v4 analysis and must be addressed in the phases
156156
- **Fix:** Before coding Phase 3, produce a complete schema diff table in `docs/SPATIAL_PARITY_CHECKLIST.md`.
157157

158158
13. **No Playwright E2E infrastructure exists**
159-
- Phase 8/9 success criteria require a Playwright smoke test, but there is no `apps/web/playwright.config.ts`, no `e2e/` folder, and no `@playwright/test` dependency.
160-
- **Fix:** Add Playwright to Phase 4 or Phase 5 as a deliverable.
159+
- Phase 7/8 success criteria require a Playwright smoke test, but there is no `apps/web/playwright.config.ts`, no `e2e/` folder, and no `@playwright/test` dependency.
160+
- **Fix:** Add Playwright to Phase 4 as a deliverable.
161161

162162
14. **`PageCollabRoom` broadcast has no backpressure throttling**
163163
- The DO calls `this.broadcast()` synchronously for every connected socket. Under high load (many clients, rapid edits), DO CPU time could exceed Cloudflare limits.
@@ -196,7 +196,7 @@ These were found during the v3–v4 analysis and must be addressed in the phases
196196
- **Collab:** `PageCollabRoom` Durable Object. Yjs updates persisted to Postgres `page_updates`. **Only ProseMirror content is synced.**
197197
- **Realtime:** `UserRealtimeRoom` Durable Object. Hash HGET/HSET + pub/sub via Upstash.
198198
- **Scheduler:** Cron Triggers or Queues (not yet implemented; deferred).
199-
- **Routing divergence:** `/pages/:pageId` = single-note text editor; `/spatial` = stub canvas. Legacy: `/pages/:pageId` = spatial canvas. This must be reconciled before Phase 6.
199+
- **Routing decision:** `/pages/:pageId` will be the spatial canvas. The Tiptap editor becomes the head/body editing component inside a note. Legacy divergence resolved in Phase 4.
200200

201201
---
202202

@@ -532,9 +532,9 @@ The new `usePageCollabEditor` only syncs a ProseMirror `Y.XmlFragment`. We need
532532
- `pnpm --filter @deepnotes/web test` runs in < 30 seconds.
533533

534534
5. **Route consolidation decision**
535-
- Resolve the §0.3 routing divergence: either (a) make `/pages/:pageId` the spatial canvas and move the single-note Tiptap editor into a note-editing mode, or (b) keep `/pages/:pageId` as text-only and accept that spatial parity is a v2 feature.
535+
- Resolve the §0.3 routing divergence: make `/pages/:pageId` the spatial canvas. The Tiptap editor becomes the head/body editing component inside a note.
536536
- Document the decision in `docs/ROUTING_DECISION.md`.
537-
- If option (a), create a migration plan for existing page bookmarks and shared links.
537+
- Create a migration plan for existing page bookmarks and shared links if URLs change.
538538

539539
**Verification:**
540540
- `app.test.ts` passes (shell renders, auth state reflects cookie).
@@ -549,60 +549,9 @@ The new `usePageCollabEditor` only syncs a ProseMirror `Y.XmlFragment`. We need
549549

550550
---
551551

552-
### Phase 5: Single-note editor parity (2 weeks)
552+
### Phase 5: Spatial canvas MVP — notes + arrows + camera (4 weeks)
553553

554-
**Prerequisites:** Phase 0 and Phase 4 done.
555-
556-
**Goal:** The `PageEditorView` is a fully functional **single-note** editor with all rich-text features from legacy. This is a **stepping stone** to the spatial canvas, not the final state.
557-
558-
**Deliverables:**
559-
560-
1. **Rich-text feature completeness**
561-
- Verify every Tiptap extension from legacy is present:
562-
- StarterKit (bold, italic, bullet, ordered, blockquote, hard break, heading, horizontal rule)
563-
- Link, underline, placeholder
564-
- Table (resizable), image (inline + base64), task list
565-
- Highlight, text align, subscript, superscript
566-
- Code block (lowlight), inline math, math block, YouTube embed
567-
- Styling matches legacy (or deliberate product decision documents differences).
568-
569-
2. **Editor management**
570-
- Snapshots: list, save, load, delete.
571-
- Path breadcrumb with decrypted titles.
572-
- Bump, favorite, recent, starting page.
573-
574-
3. **Collab in single-note mode**
575-
- WS awareness (caret colors, selection) works.
576-
- WS fallback to REST `POST /collab-updates` works.
577-
- Demo mode uses local-only Yjs (no WS, no REST push).
578-
579-
4. **Backlink display**
580-
- The backend exposes `POST /api/pages/:pageId/backlinks` and `DELETE /api/pages/:pageId/backlinks/:targetPageId`.
581-
- Add a backlinks card to `PageEditorView.vue` showing incoming links with decrypted titles.
582-
- Must have a component test verifying decrypted titles render.
583-
584-
5. **Demo mode spatial awareness**
585-
- Ensure demo users can at least view the `/spatial` stub and navigate to `/pages/:pageId` without crashing.
586-
- Demo session should not attempt WS connect (already true) but should show a clear "Demo — changes not saved" banner.
587-
588-
**Verification:**
589-
- `page-editor-tiptap-extensions.test.ts` passes.
590-
- Manual QA: open a page in two tabs, type in both, verify text syncs within 1 second.
591-
- Snapshot save/load integration test passes.
592-
- Backlink component test passes.
593-
594-
**Exit criteria:**
595-
- [ ] All Tiptap extensions listed above are present and tested.
596-
- [ ] Collab syncs text + awareness in real time across tabs.
597-
- [ ] Page management (bump, favorite, snapshots, soft-delete) works end-to-end.
598-
- [ ] Backlinks UI displays incoming links with decrypted titles.
599-
- [ ] Demo mode does not crash on `/spatial` or `/pages/:pageId`.
600-
601-
---
602-
603-
### Phase 6: Spatial canvas MVP — notes + arrows + camera (4 weeks)
604-
605-
**Prerequisites:** Phase 1 checklist signed off, Phase 3 page-level Yjs doc done, Phase 4 route consolidation done, Phase 5 done.
554+
**Prerequisites:** Phase 1 checklist signed off, Phase 3 page-level Yjs doc done, Phase 4 route consolidation done.
606555

607556
**Goal:** A `PageEditorView` that renders an **infinite canvas** with draggable, resizable notes and connectable arrows. This is the core DeepNotes product differentiator.
608557

@@ -682,9 +631,9 @@ The new `usePageCollabEditor` only syncs a ProseMirror `Y.XmlFragment`. We need
682631

683632
---
684633

685-
### Phase 7: Spatial canvas polish (3 weeks)
634+
### Phase 6: Spatial canvas polish (3 weeks)
686635

687-
**Prerequisites:** Phase 6 done.
636+
**Prerequisites:** Phase 5 done.
688637

689638
**Goal:** All remaining spatial interactions from the legacy checklist.
690639

@@ -737,7 +686,7 @@ The new `usePageCollabEditor` only syncs a ProseMirror `Y.XmlFragment`. We need
737686

738687
---
739688

740-
### Phase 8: Account, billing, groups polish (1 week)
689+
### Phase 7: Account, billing, groups polish (1 week)
741690

742691
**Prerequisites:** Phase 4 and Phase 5 done.
743692

@@ -782,9 +731,9 @@ The new `usePageCollabEditor` only syncs a ProseMirror `Y.XmlFragment`. We need
782731

783732
---
784733

785-
### Phase 9: Mobile shells and cutover (2 weeks)
734+
### Phase 8: Mobile shells and cutover (2 weeks)
786735

787-
**Prerequisites:** Phase 7 and Phase 8 done.
736+
**Prerequisites:** Phase 6 and Phase 7 done.
788737

789738
**Goal:** Prepare for production cutover.
790739

@@ -824,22 +773,22 @@ The new `usePageCollabEditor` only syncs a ProseMirror `Y.XmlFragment`. We need
824773
|------|------------|--------|------------|
825774
| **Test infrastructure stays broken** | High if not prioritized | Blocks all other work | **Phase 0 is mandatory and comes first.** No feature work until tests pass. |
826775
| **Spatial canvas underestimated** | Already happened | 6+ weeks slip | Acknowledged in §0.3. Do not allow agents to mark stubs as "done." Use checklist in Phase 1. |
827-
| **SyncedStore / Yjs reactivity issues** | Medium | Blocks Phase 6 | Make architecture decision (§6) before coding. Spike 1 day to test SyncedStore with Vite 6 + Vue 3.5. |
776+
| **SyncedStore / Yjs reactivity issues** | Medium | Blocks Phase 5 | Make architecture decision (§6) before coding. Spike 1 day to test SyncedStore with Vite 6 + Vue 3.5. |
828777
| **Collab protocol mismatch** | Medium | Data corruption | Version the collab protocol (`v1` = ProseMirror-only, `v2` = page-level). Reject unknown message types gracefully. |
829778
| **Performance: many notes on one page** | Medium | Laggy canvas | Set a soft limit (e.g., 200 notes) and benchmark. Use virtual rendering or canvas-based rendering if DOM scales poorly. |
830779
| **Stripe-only after dropping RevenueCat** | Low | User churn | Communicate to IAP users before cutover. Offer migration grace period. |
831-
| **Worker CPU limits under collab load** | Medium | Dropped connections | Load test early (Phase 9 staging). If DO CPU is the bottleneck, shard `PageCollabRoom` by page ID prefix. |
780+
| **Worker CPU limits under collab load** | Medium | Dropped connections | Load test early (Phase 8 staging). If DO CPU is the bottleneck, shard `PageCollabRoom` by page ID prefix. |
832781
| **God-object state returns** | Medium | Unmaintainable code | Cap composable size at 300 lines. If `useSpatialViewport.ts` grows beyond that, split into `useCamera`, `usePanning`, `useZooming`. |
833782
| **`page_updates` format migration** | Medium | Data corruption or unreadable legacy pages | Decide Option A/B in Phase 3 before any spatial collab code. Test decrypt of 100 random legacy pages after migration. |
834783
| **DO hibernation drops WS state** | Medium | Users see collab reconnects | `PageCollabRoom` is stateless relay, so hibernation is safe. Document in `docs/COLLAB_DO_ARCHITECTURE.md`. If stateful DO chosen later, implement reconnect protocol. |
835784
| **i18n / SSR regressions** | Low | Accessibility, SEO, share-ability loss | Document as accepted v2 regressions or schedule recovery. |
836-
| **Group password not implemented** | Low | Users cannot access password-protected groups in new app | Add to Phase 8. If deferred, document v2 scope. |
837-
| **No scheduler = soft-deleted data accumulates** | Medium | DB bloat | Add Cron Trigger or Queue cleanup to Phase 8/9. |
785+
| **Group password not implemented** | Low | Users cannot access password-protected groups in new app | Add to Phase 7. If deferred, document v2 scope. |
786+
| **No scheduler = soft-deleted data accumulates** | Medium | DB bloat | Add Cron Trigger or Queue cleanup to Phase 7/8. |
838787
| **Collab protocol narrower than legacy** | Medium | Slower reconnects, lost ACK edge cases | Document in `docs/COLLAB_PROTOCOL_PARITY.md`. Monitor unacked-update metrics. |
839-
| **`@deepnotes/session` god package** | Medium | Cross-domain coupling, slow test feedback | Audit and split into dedicated packages before Phase 8. |
788+
| **`@deepnotes/session` god package** | Medium | Cross-domain coupling, slow test feedback | Audit and split into dedicated packages before Phase 7. |
840789
| **`page_updates` no pagination** | Medium | OOM on large page bootstrap | Fixed in Phase 0 with `?sinceIndex=`. Monitor max response size in production. |
841790
| **Routing divergence (page vs spatial)** | Medium | User confusion, broken bookmarks | Decide in Phase 4. Communicate clearly if URLs change. |
842-
| **No Playwright = no E2E gate** | Medium | Regressions slip into production | Add skeleton in Phase 0; build smoke test in Phase 8. |
791+
| **No Playwright = no E2E gate** | Medium | Regressions slip into production | Add skeleton in Phase 0; build smoke test in Phase 7. |
843792
| **Legacy schema fields omitted in new model** | Medium | Subtle data-loss or UI bugs | Enforce Phase 1 schema diff table as a hard gate before Phase 3 coding. |
844793

845794
---
@@ -862,8 +811,8 @@ A criterion is **not met** until the verification command or check passes in CI.
862811
- [ ] **Auth + crypto:** 2FA enable/disable flow tested end-to-end. Password change invalidates all sessions.
863812
- [ ] **No banned tech:** No tRPC, no `superjson`, no RevenueCat, no key rotation code paths. Enforced by ESLint `no-restricted-imports`.
864813
- [ ] **Routing decision:** `docs/ROUTING_DECISION.md` exists and is signed off.
865-
- [ ] **Spatial canvas (Phase 6):** User can create, move, resize, delete notes and arrows on an infinite canvas. Changes sync via WS.
866-
- [ ] **Spatial polish (Phase 7):** ≥ 80% of `docs/SPATIAL_PARITY_CHECKLIST.md` rows marked done.
814+
- [ ] **Spatial canvas (Phase 5):** User can create, move, resize, delete notes and arrows on an infinite canvas. Changes sync via WS.
815+
- [ ] **Spatial polish (Phase 6):** ≥ 80% of `docs/SPATIAL_PARITY_CHECKLIST.md` rows marked done.
867816
- [ ] **Schema completeness:** Phase 3 Yjs schema includes every field from the Phase 1 diff table.
868817
- [ ] **Backlinks:** SPA displays incoming page backlinks with decrypted titles.
869818
- [ ] **Playwright:** E2E smoke test covers register → create page → edit → invite → logout in < 60 seconds.
@@ -925,16 +874,16 @@ What was wrong: the original plan **catastrophically underestimated the spatial
925874

926875
What v4 adds beyond v3:
927876
- **Collab protocol gap is wider than described.** The new protocol lacks bootstrap-over-WS, unacked-update retry, and the ACK handler has a logic error. These must be fixed in Phase 0 before spatial work touches Yjs.
928-
- **Routing/product-model divergence.** `/pages/:pageId` is a text editor; `/spatial` is a stub. Legacy has no such split. Phase 4 must resolve this before Phase 6.
877+
- **Routing/product-model divergence.** `/pages/:pageId` will become the spatial canvas; `/spatial` stub is removed. Legacy has no such split. Phase 4 must resolve this before Phase 5.
929878
- **Schema incompleteness risk.** The proposed Phase 3 schema omitted ~10 legacy fields. Phase 1 now requires a complete diff table as a hard gate.
930879
- **Missing infrastructure.** No `vitest.workspace.ts`, no Playwright, no collab pagination, no `ydoc.on('updateV2')` listener. Phase 0 now includes all of these.
931880

932881
What must happen now:
933882
1. **Fix the test foundation (Phase 0).** No agent should add features while tests are broken. Split `usePageCollabEditor`, fix ACK logic, add `updateV2` listener, add pagination, add Playwright.
934883
2. **Inventory spatial features (Phase 1).** Produce a checklist **and a complete schema diff table** that prevents misreporting stubs as done.
935884
3. **Extend collab to page-level Yjs (Phase 3).** The current ProseMirror-only collab cannot support spatial notes. Include SyncedStore spike and incremental bootstrap.
936-
4. **Resolve routing divergence (Phase 4).** Decide whether `/pages/:pageId` becomes the canvas or stays text-only.
937-
5. **Build the spatial canvas incrementally (Phases 6–7).** MVP first (create/move/resize/delete notes + arrows), then polish (selection, containers, clipboard, undo).
885+
4. **Resolve routing divergence (Phase 4).** Make `/pages/:pageId` the spatial canvas and integrate the Tiptap editor as a note component.
886+
5. **Build the spatial canvas incrementally (Phases 5–6).** MVP first (create/move/resize/delete notes + arrows), then polish (selection, containers, clipboard, undo).
938887
6. **Verify everything with automated tests.** Every phase has objective exit criteria.
939888

940889
Retire the legacy repo only when spatial parity, auth smoke, and data checks are proven.

0 commit comments

Comments
 (0)