You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
@@ -333,7 +333,7 @@ Sprints **1–9** (pages / groups / membership), **Stripe**, and **[slice 10 —
333
333
|**Routes**|[`/page/:pageId`](apps/web/src/features/pages/PageEditorView.vue) (auth: redirect to login with `?redirect=`); guest [`/register`](apps/web/src/features/auth/RegisterView.vue). |
334
334
|**Registration**|[`buildUserRegisterRequest`](apps/web/src/features/auth/build-user-register.ts) = [`buildSessionDemoRequest`](apps/web/src/features/auth/build-demo-session.ts) + `email` + `loginHash` from [`uint8ToBase64(loginPreimage)`](apps/web/src/features/auth/bytes.ts); test [`build-user-register.test.ts`](apps/web/src/features/auth/build-user-register.test.ts). **201** → redirect to `/login?registered=1` + banner. |
335
335
|**Page list**|[`useGroupPages`](apps/web/src/features/pages/useGroupPages.ts): `GET /api/users/me/groups` then per group `GET /api/groups/{groupId}/pages` (first window, max 20); [HomeView](apps/web/src/features/home/HomeView.vue) shows links. |
**Limits (documented in UI):****Demo** accounts cannot derive client keys (no `passwordSalt`). **`/register`** uses [build-user-register](apps/web/src/features/auth/build-user-register.ts) (real crypto). **Group password** / non-raw content keyrings are rejected with a clear message.
352
352
353
+
### Phase 4 — Tiptap + Yjs (SPA)
354
+
355
+
**Goal:**[RESTART_PLAN §4](../docs/RESTART_PLAN.md)*Phase 4* calls for **Tiptap + Yjs** for the editor. Replace the proof-of-concept `textarea` + `Y.Text` binding with **ProseMirror** state synced through Tiptap’s `@tiptap/extension-collaboration` on a shared `Y.Doc`, using an XmlFragment field distinct from the legacy plain-text `Y.Text("default")` path so stored Yjs can evolve without silent corruption.
356
+
357
+
| Layer | What shipped |
358
+
|-------|----------------|
359
+
|**Dependencies**|[`@tiptap/vue-3`](apps/web/package.json), `@tiptap/starter-kit`, `@tiptap/extension-collaboration` (same `yjs` as collab REST). |
|**Legacy text**| If, after `applyUpdateV2` of server rows, `Y.Text("default")` has length (earlier **textarea** builds), the client copies it into a **single paragraph** prosemirror doc via `setContent`, then clears the old `Y.Text` so the next save only encodes the XmlFragment + any other Y types from updates. |
362
+
|**Route remount**|[App.vue](apps/web/src/App.vue)`RouterView``:key="route.path"` so `/page/:pageId` changes create a **fresh**`Y.Doc` + editor instance (avoids reusing a polluted doc when navigating home → another page). |
363
+
|**Styles**|[globals.css](apps/web/src/styles/globals.css) — minimal `.tiptap-editor .ProseMirror` + list/paragraph spacing (headless Tiptap has no default ProseMirror chrome). |
364
+
365
+
**Intentional:** not feature-complete rich text (lists/headings via StarterKit are available, but the product may narrow extensions later). **Not done:**[live collab WebSocket](#not-started-phase-3--realtime--collab-websocket) for multi-user low-latency sync.
366
+
353
367
### Phase 4 — register crypto parity
354
368
355
369
**Goal:** Replace random `buildSessionDemoRequest`/`buildUserRegisterRequest` blobs with libsodium keyrings + group/page creation matching [legacy `getRegistrationValues`](../apps/client/src/code/areas/auth/register.ts) so new accounts can use the editor E2EE path end-to-end.
@@ -363,7 +377,7 @@ Sprints **1–9** (pages / groups / membership), **Stripe**, and **[slice 10 —
363
377
|**`persistSessionKeyringsFromLogin`**: unwrap `UserPrivateKeyring` / `UserSymmetricKeyring` only when `topLayer !== Raw` after server unwrap (legacy double-wrap vs greenfield raw inner) |**Done**|
364
378
| Optional: demo path that uses a deterministic dev keyring for CI |**Todo**|
**Tests:**[build-user-register.test.ts](apps/web/src/features/auth/build-user-register.test.ts) — raw user keyrings, private group, `UserName` decrypt round-trip.
369
383
@@ -427,7 +441,7 @@ Sprints **1–9** (pages / groups / membership), **Stripe**, and **[slice 10 —
427
441
-[x]**Tooling (bootstrap):** Vitest + **happy-dom** + `@vue/test-utils` in `@deepnotes/web` (minimal `App` test); same Vite 6 pipeline via `vitest/config``defineConfig` (RESTART_PLAN §5.8).
428
442
-[x]**API client (bootstrap):** typed client from the same OpenAPI document as the Worker—see [Phase 4 — OpenAPI typed client](#phase-4--openapi-typed-client-bootstrap). Runtime bundle does **not** import `@deepnotes/api` (only generated `api-types.generated.ts` + `openapi-fetch`); regenerate after OpenAPI changes.
429
443
-[x]**Routing + session UI (slice a):**`vue-router` + [`App.vue` shell](apps/web/src/App.vue) (header, Register / Sign in / Sign out). **`useSession`**: as below; Vite [proxy `/api` → 127.0.0.1:8787](apps/web/vite.config.ts). **CORS:** API must allow this app’s origin; proxy avoids cross-origin in local dev. **Not done:** Playwright E2E.
-[x]**Groups** overview as thin SPA: [`/groups`](apps/web/src/features/groups/GroupsView.vue) + [fetch helper](apps/web/src/features/groups/groups-overview.ts) (list → main page + members count + first page window).
432
446
-[x]**Group members / invites (admin):**[`/groups/:groupId`](apps/web/src/features/groups/GroupDetailView.vue) + [`GET …/members/detail`](#phase-4--group-members--invites-admin-ui) — roles, pending rows, leave/remove/`PATCH`, cancel/reject; **[E2EE invite/join](#phase-4--group-invitejoin-e2ee-spa)** (send invite, accept on `/invite`, request join on `/join`, accept join request with crypto).
433
447
-[x]**Notifications** as thin SPA: [`/notifications`](apps/web/src/features/notifications/NotificationsView.vue) + [notifications-list.ts](apps/web/src/features/notifications/notifications-list.ts) — see [Phase 4 — notifications (thin SPA)](#phase-4--notifications-thin-spa). **Not done:** decrypt notification payloads in the browser.
@@ -520,6 +534,7 @@ Cross-cutting work so the new SPA does not repeat **legacy `apps/client`** patte
520
534
521
535
| Date | Change |
522
536
|------|--------|
537
+
| 2026-04-27 |**Phase 4 — Tiptap + Yjs in page editor:**[`@tiptap/vue-3`](apps/web/package.json) + `StarterKit` (`undoRedo: false`) + `Collaboration` on `Y.Doc` field `prosemirror`; [PageEditorView.vue](apps/web/src/features/pages/PageEditorView.vue) replaces `textarea` + `Y.Text`; migration of legacy `Y.Text("default")` into one paragraph; [App.vue](apps/web/src/App.vue)`RouterView :key="route.path"`; [globals.css](apps/web/src/styles/globals.css) ProseMirror minimal styles. [PLAN_PROGRESS — Tiptap section](#phase-4--tiptap--yjs-spa). **Next (Phase 4):** MSW/Playwright. |
0 commit comments