Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
f8c2341
feat(cms): save conflict detection with per-row base seqs and resolut…
DavidBabinec Jul 2, 2026
7ed68ca
feat(cms): live pull over WebSocket — sibling saves merge into open e…
DavidBabinec Jul 2, 2026
ab4ab93
chore(collab): add yjs + y-protocols + lib0
DavidBabinec Jul 3, 2026
95ffc2a
feat(collab): core doc ids, Y schema, seeding + projection with integ…
DavidBabinec Jul 3, 2026
4f9ea2b
feat(collab): text splice diff + granular merge scenario coverage
DavidBabinec Jul 3, 2026
7a58e75
feat(collab): mutative-patch → Y translation layer
DavidBabinec Jul 3, 2026
b24a08a
feat(collab): collab_documents storage (additive migration, both dial…
DavidBabinec Jul 3, 2026
e13f129
feat(collab): server relay — doc registry, deterministic seeding, dua…
DavidBabinec Jul 3, 2026
8955484
feat(collab): socket speaks multiplexed y-protocols; retire hint-even…
DavidBabinec Jul 3, 2026
1367393
feat(collab): client provider — multiplexed transport with backoff re…
DavidBabinec Jul 3, 2026
6bc6247
feat(editor): CRDT write path replaces save pipeline — per-doc undo, …
DavidBabinec Jul 3, 2026
c7aeddf
feat(collab): live peer cursors, selections, carets
DavidBabinec Jul 3, 2026
fd1f19c
test(canvas): flush extra RAF frames for sibling presence overlay
DavidBabinec Jul 3, 2026
c5b5714
test(collab): end-to-end relay convergence over real sockets
DavidBabinec Jul 3, 2026
8b73cb5
docs(collab): document the real-time co-editing architecture
DavidBabinec Jul 3, 2026
ac06fdf
fix(collab): bind peer-created row docs on demand during site projection
DavidBabinec Jul 3, 2026
8ab7718
fix(collab): live pointer publishing + toolbar presence avatars
DavidBabinec Jul 3, 2026
53fcd9b
fix(collab): presence chrome was invisible; Gravatar avatars in the p…
DavidBabinec Jul 3, 2026
5da6205
feat(collab): smooth interpolated peer cursors at a lower wire rate
DavidBabinec Jul 3, 2026
f4d7c4e
style(collab): pixel cursor glyph, aligned presence badges, square rings
DavidBabinec Jul 3, 2026
d4c0a4d
style(collab): notched peer name tag — seamless with the selection ring
DavidBabinec Jul 3, 2026
e608e09
style(collab): avatar cursors with hover tooltip; avatar in the selec…
DavidBabinec Jul 3, 2026
0a1e2be
style(collab): cursor fade in/out with exit linger; tighter avatar + tag
DavidBabinec Jul 3, 2026
956c224
feat(collab): per-category capability guard on relay writes; read-onl…
DavidBabinec Jul 3, 2026
0af6129
feat(collab): character-level peer text carets and selection highlights
DavidBabinec Jul 3, 2026
482df72
refactor(collab): write-policy module, socket hardening, presence pub…
DavidBabinec Jul 3, 2026
ccbf579
fix(collab): correctness + robustness bugs from the feature review
DavidBabinec Jul 3, 2026
7da3147
fix(publish): make collab-flush intrinsic to every publish path
DavidBabinec Jul 3, 2026
eb70329
perf(collab): skip cross-doc presence re-renders and unchanged roster…
DavidBabinec Jul 3, 2026
4c8faa2
refactor(collab): dedup helpers, canonical doc-ids, delete dead code
DavidBabinec Jul 3, 2026
2722c72
fix(publisher): one malformed style rule no longer blanks the canvas
DavidBabinec Jul 3, 2026
aac542a
fix(collab): validate the projected site shell before it enters the s…
DavidBabinec Jul 3, 2026
3869987
fix(collab): prune canvas selection through the projection path
DavidBabinec Jul 12, 2026
25ccc58
test(content): make the posts-rows mock stateful so create tests stop…
DavidBabinec Jul 12, 2026
e092ec0
fix(collab): stop logging routine presence timeouts as identity spoofing
DavidBabinec Jul 12, 2026
5a9e6e8
Merge branch 'main' into feat/collab-crdt-coediting
DavidBabinec Jul 25, 2026
4a2fc0b
fix(dev): dial the collab socket at the CMS origin instead of the Vit…
DavidBabinec Jul 25, 2026
6d33745
fix(collab): project owned data instead of live references into the Y…
DavidBabinec Jul 25, 2026
fc1d472
fix(canvas): guard non-object style bags in ambient placeholder suppr…
DavidBabinec Jul 25, 2026
d5f5c11
test(e2e): retire saveDraft for the collab relay's continuous persist…
DavidBabinec Jul 25, 2026
cf051ea
fix(collab): make relay eviction, reset, and derived-JSON persistence…
DavidBabinec Jul 25, 2026
8879fb8
feat(collab): stamp a per-doc CRDT generation on every frame
DavidBabinec Jul 25, 2026
0ebd7e0
feat(collab): application-level liveness so "connected" means "reacha…
DavidBabinec Jul 25, 2026
a1afb51
feat(collab): server-initiated syncStep1 so a reconnect recovers unse…
DavidBabinec Jul 25, 2026
933ddd9
fix(collab): reset read-only viewers on a refused write so their scre…
DavidBabinec Jul 27, 2026
3e286f3
feat(collab): refuse edits the transport can't deliver, and tell the …
DavidBabinec Jul 27, 2026
eb55585
fix(collab): co-typing one text node no longer deletes the peer's cha…
DavidBabinec Jul 27, 2026
8f3c3f5
docs(collab): start the cross-run hardening ledger
DavidBabinec Jul 27, 2026
38fc8bf
merge: integrate current main into collab co-editing
DavidBabinec Jul 29, 2026
df290ca
fix(collab): harden persistence and concurrent editing
DavidBabinec Jul 29, 2026
bd35ed1
fix(collab): report persistence failures through toasts
DavidBabinec Jul 29, 2026
a3ee480
Delete docs/collab-hardening-log.md
DavidBabinec Jul 30, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,11 @@ Read [`docs/architecture.md`](docs/architecture.md) for the system overview, [`d

- **Runtime:** Bun (server + tooling). Use Bun, not Node.
- **Language:** TypeScript everywhere.
- **Frontend:** React 19 with the **React Compiler enabled** (Babel preset in `vite.config.ts`) + Vite, Zustand + Mutative for state (via `zustand-mutative`; patch-based undo history uses Mutative `create({ enablePatches })` — `immer` is banned), CodeMirror for code-editing UI, `@dnd-kit/core` for drag-and-drop. The compiler auto-memoizes — do not hand-write `useMemo`/`useCallback`/`memo`. See "React Compiler and memoization". Store mutations use draft-mutation style (`set((s) => { s.x = … })`); a recipe that returns a partial must wrap it in `rawReturn(...)` or Mutative emits a perf warning.
- **Frontend:** React 19 with the **React Compiler enabled** (Babel preset in `vite.config.ts`) + Vite, Zustand + Mutative for state (via `zustand-mutative`; `immer` is banned), CodeMirror for code-editing UI, `@dnd-kit/core` for drag-and-drop. The compiler auto-memoizes — do not hand-write `useMemo`/`useCallback`/`memo`. See "React Compiler and memoization". Store mutations use draft-mutation style (`set((s) => { s.x = … })`); a recipe that returns a partial must wrap it in `rawReturn(...)` or Mutative emits a perf warning.
- **Server:** `Bun.serve` with a hand-written router (`server/router.ts`). CMS modules at `server/{repositories,handlers/cms,auth,plugins,publish}/`. Deep dive: [`docs/server.md`](docs/server.md).
- **Database:** Postgres (`Bun.sql`) OR SQLite (`bun:sqlite`), selected by `DATABASE_URL`. One `DbClient` interface, two adapters, two migration files with identical IDs. Rules: [`docs/reference/database-dialects.md`](docs/reference/database-dialects.md).
- **Content model:** All content lives in `data_tables` + `data_rows`. The four system tables (`posts`, `pages`, `components`, `layouts`) are seeded and locked from rename/delete. There are no separate `pages` or `page_versions` tables.
- **Real-time co-editing:** Yjs CRDT engine. One Y doc per row (`page:<id>`, `component:<id>`, `layout:<id>`) + one site-shell doc, multiplexed over `/admin/api/cms/site-socket`. The editor store stays the render source of truth: local mutations apply directly AND translate to Y ops (`@core/collab`); remote/undo changes project back. The server relay (`server/collab/`) persists continuously (blob + derived row JSON) — there is NO client-side save pipeline, no autosave, no Cmd+S. Undo is per-editor per-doc `Y.UndoManager`. Feature doc: [`docs/features/site-shell.md`](docs/features/site-shell.md) → "Real-time co-editing".
- **Validation:** TypeBox at every untyped boundary. Schemas are source of truth (`type Foo = Static<typeof FooSchema>`, never a parallel `interface`). `zod` is banned repo-wide (the AI drivers pass TypeBox schemas through as JSON Schema, so no typebox→zod adapter is needed). Helpers + patterns: [`docs/reference/typebox-patterns.md`](docs/reference/typebox-patterns.md).
- **Sanitization:** DOMPurify at the publisher boundary (`src/core/sanitize.ts`).
- **Plugins:** Zip packages with a `plugin.json` manifest, lifecycle hooks. Server entrypoints and canvas module packs run inside a **QuickJS-WASM sandbox** — no Node/Bun ambient access, network gated by `network.outbound` permission + `networkAllowedHosts`. The VM bootstrap (SDK factory + `__run*` dispatchers) is authored as typed TS in `server/plugins/quickjs/bootstrap/src/` and bundled to committed string artifacts in `bootstrap/generated/` — after editing the source run `bun run bootstrap:sync` (gated by `plugin-bootstrap-fresh.test.ts`). Permission enforcement everywhere (VM, host, editor) validates against `grantedPermissions`, never the declared `permissions` array. Feature doc: [`docs/features/plugin-system.md`](docs/features/plugin-system.md).
Expand Down
11 changes: 11 additions & 0 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ The repo is organized by responsibility, not by feature. Every file has one reas
| HTTP & routing | `server/router.ts`, `server/http.ts` | Request dispatch, body parsing, error envelopes |
| CMS endpoints | `server/handlers/cms/*.ts` | Per-resource handlers (pages, posts, components, media, plugins, …) |
| Auth & sessions | `server/auth/*` | Session validation, capability checks, login flow |
| Real-time co-editing | `src/core/collab/`, `server/collab/*` | CRDT co-editing (Yjs): one Y doc per row + the site shell, multiplexed over the `/admin/api/cms/site-socket` WebSocket; the server relay persists continuously and resets docs on out-of-relay writes |
| Repositories | `server/repositories/*.ts` | Database access; dialect-naive ANSI SQL only |
| Database adapters | `server/db/postgres.ts`, `sqlite.ts` | Engine-specific `DbClient` implementation |
| Migrations | `server/db/migrations-*.ts` | Schema in both dialects, parity-gated |
Expand Down
Loading
Loading