- GET /link-preview?url= fetches the page on the server (https only,
SSRF-guarded like webhooks), reads Open Graph/Twitter tags, title,
description and icon from the head, loads picture and icon (no SVG) and
keeps them in link_previews (migration 6) for 7 days; readers' browsers
never contact the page. Off in the demo; 300 fetches per address in
15 minutes.
- Compact card above the bullet (icon, site, title, one line, small
picture); the duplicate link badge is left out.
- ✕ turns the address into [title](url), so hiding syncs with the note;
"Link-Vorschauen anzeigen" under appearance switches all off per device.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Table media (migration 5), files in $DATA_DIR/media/<docId>/.
- POST /documents/{id}/media (write access), POST .../media/fetch copies an
external https image (SSRF-guarded, not in the demo), GET /media/{id}
with range requests for readers of a note that shows the file.
- Format detected from the content (no SVG); own sandbox CSP on answers.
- Blocks point to files through their content (media:<id>), so the note
sync needs no change; compaction removes files no block mentions any
more after a day. Demo reset and demo start clear the folder.
- Limits MEDIA_MAX_IMAGE_MB, MEDIA_MAX_AUDIO_MB, MEDIA_QUOTA_MB,
MEDIA_DEMO_MAX_MB.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Background job at start and every 6 h: log entries older than 30 days
(private notes: 7) are removed, documents.log_floor marks the boundary;
clients behind it get resync and keep their queued changes
- Deleted blocks are purged 30 days after deleted_at via a "purge" log
effect, so open editors drop them without reloading; a tombstone stays
while a deleted child still points to it
- Text for a purged block becomes a new block at the end; inserts under a
purged parent go to the top level (server and browser model)
- doc_op_results kept for 90 days; expired lease/session maps are swept
- Tests: storage, API, engine resync, simulation with compactions
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Phase 5 of the collaboration plan:
- Remove PATCH /documents/{id}/blocks, PatchDocumentBlocks, the
DOC_BLOCKS_PATCHED event and its types, FlushDocumentState and
LoadDocumentState. PUT /documents/{id} rejects blocks (400) and only
changes title, icon, folder and pin; editors may now change title/icon.
- The to-do overview ticks via a single operation on /docs/{id}/sync
(new skipLog flag). Task links set the block tick with a targeted
operation and notify open editors; creating a linked task needs edit
rights.
- Keep the three share tables: DocRole already decides centrally, and
rewriting the visibility queries would only add risk (documented in plan).
- Tests adapted to the new paths; share e2e test now also ticks a to-do in
the overview.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Phase 2 of the collaboration plan:
- frontend/src/lib/docSync: TypeScript port of the block tree, operations,
effects and order keys (kept identical to Go via shared test vectors),
diff from the editor's flat list to operations, IndexedDB persistence,
HTTP transport and DocSyncEngine (one request in flight, idempotent
resends, backoff, 3 s/30 s polling, keepalive flush and leave on exit,
block leases for focus, 20 s grace and new blocks).
- Text changes carry the version the user saw when typing (first change
wins when coalescing; a stale focused field keeps its old base), so a
concurrent edit becomes a conflict copy instead of a silent overwrite.
- DocumentEditor uses the engine instead of the 800 ms whole-document PUT;
editors (not only owners) can edit, remote changes appear live.
- Server: task IDs of linked blocks are delivered again, linked tasks follow
ticked/deleted blocks after sync, note tags are derived, title changes
notify other users; block IDs already used in another note are renamed
(starter content with fixed IDs was empty for every further user).
- Fix: unlinking tasks from blocks wrote to non-existent columns and never
worked (error was swallowed).
- Tests: Go/TS order key vectors, diff round trips, engine races (lost
responses, offline + reload, edits arriving between typing and sending,
stale focused field, duplicate first block), plus a Playwright test with
two browsers against the real image (e2e/run.sh).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Phase 1 of the collaboration plan (server side):
- internal/collab: block tree with parent IDs and fractional order keys,
operations with conflict rules (version check, block leases, conflict
copies instead of lost text, restore on late edits, cycle checks),
in-memory leases/sessions and long-poll notifier.
- Storage: per-note lock + transaction, append-only effect log (doc_ops),
idempotent results per opId (doc_op_results), snapshot, log paging,
central DocRole. Old flat block data is converted to the tree.
- The existing whole-document save (PUT/PATCH) runs as an adapter through
the same engine, so the current editor keeps working and writes to the log.
- API: GET /docs/{id}/snapshot, POST /docs/{id}/sync, GET /docs/{id}/changes
(long poll), POST /docs/{id}/leave; DOC_SEQ poke for WebSocket/SSE rooms.
- Backend accepts editor (write) shares for notes and folders; the forced
read-only migration is gone. The share dialog still offers read only.
- Tests: conflict matrix, order keys, leases, storage adapter round trip,
idempotency, API protocol incl. long poll, and a randomized simulation
(4 clients, lost/duplicated requests and responses, offline phases,
lease expiry) checking convergence, no lost text and exactly-once results.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>