Commit Graph

3 Commits

Author SHA1 Message Date
f9d23bf8d0 #151: Mention-Benachrichtigungen über die Glocke
Some checks failed
CI / Lint, typecheck, test (pull_request) Successful in 4m38s
CI / Build container images (pull_request) Successful in 12s
CI / Auth e2e pack (pull_request) Failing after 6m12s
CI / Import/export fidelity gate (pull_request) Has been skipped
Neuer Notification-Typ mentioned; abgeleitete Tabelle page_mentions
(Migration), vom Collab-Persist transaktional neu geschrieben — der
Diff gegen den Vorzustand wird als pg_notify
(page_mentions_changed) emittiert, nur NEU Erwähnte lösen aus (kein
Spam bei Folge-Saves). Der api-Listener (erweitert um den zweiten
Kanal) ruft NotificationsService.fanoutMentions: Zustellung nur nach
canAccessPage-Recheck, die Autoren (pending contributors) benachrich-
tigen sich nie selbst; Payload wie gehabt mit Actor-Namen. API-seitig
erzeugte Seiten seeden page_mentions aus deriveContent. Glocken-Text
de+en; DB-Test (Leser ja / Outsider nein / Autor nein); kompletter
Loop live verifiziert (Tippen → Persist → NOTIFY → Notification).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0155v2aT8AG1kZDQEZiCLBWC
2026-07-20 01:25:16 +02:00
14e69b399c Add wikilink index, backlinks API, and phantom resolution (#47)
All checks were successful
CD / Build and push images (push) Successful in 3m2s
CI / Lint, typecheck, test (push) Successful in 2m16s
CI / Auth e2e pack (push) Successful in 2m44s
CI / Build container images (push) Has been skipped
CD / Deploy to Test (push) Successful in 9s
CD / Smoke tests against Test (push) Successful in 1m16s
CD / Promote to Int (push) Successful in 11s
Maintain a server-side `page_links` index on every content change so
backlinks and missing-target ("phantom") links can be queried.

- prisma: `PageLink` (from_page_id, nullable to_page_id, target_slug;
  unique per (from, slug); cascade on source purge, set-null on target
  purge); migration.
- shared: `extractWikilinkSlugs(doc)` (distinct target slugs) and the
  `BacklinkView` / `PhantomLinkView` read shapes.
- collab: the persistence hook (#35) now rewrites the source page's outgoing
  links in the same transaction as the content cache — one row per distinct
  wikilink slug, resolved to a page in the same pond (null = phantom).
- api: `GET /pages/:id/backlinks` (permission-filtered — wikilinks resolve
  within a pond, so seeing the pond is the read right) and
  `GET /ponds/:id/phantom-links` (missing targets grouped with their
  referrers). Creating or renaming a page to a slug that pages already link
  to resolves those phantom rows; because links store the target's id,
  backlinks survive a later rename of the target's slug.
- tests: shared extraction unit test; collab persistence db test (store
  writes resolved + phantom rows and rewrites the index); api LinksService
  db test (backlinks, permission filter, phantom aggregation, create/rename
  resolution, id-based backlinks survive target rename).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PGdhRiwU1WRL4XxJfZYipY
2026-07-09 12:54:10 +02:00
7d8f331870 Add collab document persistence hooks and content-cache refresh (#35)
All checks were successful
CD / Build and push images (push) Successful in 2m51s
CI / Lint, typecheck, test (push) Successful in 1m55s
CI / Auth e2e pack (push) Successful in 2m0s
CI / Build container images (push) Has been skipped
CD / Deploy to Test (push) Successful in 9s
CD / Smoke tests against Test (push) Successful in 1m11s
CD / Promote to Int (push) Successful in 11s
The collaboration server becomes the writer of page state (ADR 0003,
realtime-collaboration.md §lifecycle):

- onLoadDocument reconstructs a page's Y.Doc from PostgreSQL by applying
  `pages.ydoc_state` and then every `page_updates` row in order, so a page
  with a long update log loads correctly.
- onStoreDocument persists debounced (2 s, max 30 s): it appends the delta
  since the last flush to `page_updates`, periodically merges the log back
  into `ydoc_state` (inline threshold; the session-aware compaction of idle
  pages remains the separate job, #40), refreshes `page_content_cache`
  (plain text / Markdown / HTML / outline via the shared derivation, #24),
  bumps `pages.updated_at`, and keeps `Attachment.pageId` pointed at the
  embedding page (#31). Each flush runs in one transaction and its duration
  is logged.
- The document size ceiling (MAX_PAGE_DOCUMENT_BYTES) is enforced on store:
  an oversize document is not persisted and the clients are notified with a
  stateless error so they can revert.

Persistence is an injected port (PagePersistence): the Postgres
implementation is covered by a DB-backed test (store/load round-trip,
content-cache refresh, a 1000-entry update log, size-ceiling rejection,
not-found), and the hook wiring — two-client sync, survival across a server
restart, and the size-ceiling stateless notification — by an integration
test using an in-memory fake. The collab package gains its own vitest setup
that provisions an isolated `_collab` test database.

The REST `PUT /pages/:id/state` write path stays in place for now and is
retired (410) together with switching the editor to live collaboration in
#36, so the deployed editor is never left unable to save between the two
deploys.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PGdhRiwU1WRL4XxJfZYipY
2026-07-08 17:06:41 +02:00