dorfteich/docs/architecture
Claude Fable 5 0003063c39
All checks were successful
CI / Auth e2e pack (push) Successful in 4m37s
CI / Import/export fidelity gate (push) Successful in 43s
CI / Lint, typecheck, test (push) Successful in 2m54s
CI / Build container images (push) Has been skipped
CD / Build and push images (push) Successful in 3m14s
CD / Deploy to Test (push) Successful in 9s
CD / Smoke tests against Test (push) Successful in 1m8s
CD / Promote to Int (push) Successful in 9s
Add pageTool plugins with toc and page-index references (#77)
The read-only widget surface over page/pond data (ADR 0008 extension
point `pageTool`):

- Host: PageToolsPanel lists the pond's active pageTool surfaces behind
  disclosures — each sandbox iframe mounts lazily on first open and tears
  down on close. The same surfaces are insertable as plugin_block embeds
  (#76's insert picker now offers pageTool points too; the sandbox drives
  both through the same render lifecycle).
- New `ui.scrollToHeading(headingId)` capability: outline ids are derived
  from the doc and never stamped into the DOM, so the host resolves the id
  to its heading position via the shared extractOutline and scrolls the
  matching rendered heading.
- `readPond.listPages` now carries label *names* per summary
  (PagesService.pluginPageSummaries) — the page-index filter chips work on
  data the viewer could resolve anyway; per-page permission filtering
  stays in the service as before.
- Reference plugins packages/plugins/toc and packages/plugins/page-index:
  real SDK consumers (createPlugin + windowTransport), bundled with
  esbuild into the package ZIP; i18n de/en is inlined at build time — the
  sandbox CSP forbids runtime fetches, the i18n/ files stay the single
  source. The toc re-fetches its outline on a slow poll, so live heading
  edits appear once the collab server has re-derived the content cache.
- e2e page-tools.spec.ts covers the acceptance criteria: live outline
  updates after the persistence debounce, heading click scrolls, embedded
  page-index navigates via ui.openPage, and a label-restricted reader
  never sees the denied page in the index.
- CI: the auth-e2e job now runs the section-styles (missed in #75),
  plugin-blocks, and page-tools packs, with login-rate-limit resets.
- plugins.e2e.db.test clears the plugin registry up front: a local dev DB
  is shared with the e2e stack, whose installed real `toc` would otherwise
  collide with the fixture of the same id.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EwZ4jR4KFAPvpjWevfUGX1
2026-07-11 13:27:30 +02:00
..
adr Move stages, runner and deploy target to dedicated host ONE 2026-07-11 08:05:36 +02:00
data-model.md Add plugin storage, install API, and directory watcher (#71) 2026-07-10 16:55:26 +02:00
deployment.md Move stages, runner and deploy target to dedicated host ONE 2026-07-11 08:05:36 +02:00
operations.md Scaffold pnpm monorepo with lint, format, and test tooling 2026-07-04 19:06:27 +02:00
permissions.md Scaffold pnpm monorepo with lint, format, and test tooling 2026-07-04 19:06:27 +02:00
plugin-architecture.md Add pageTool plugins with toc and page-index references (#77) 2026-07-11 13:27:30 +02:00
README.md Enforce permissions in API guards and retire interim access (#52) 2026-07-09 16:31:41 +02:00
realtime-collaboration.md Scaffold pnpm monorepo with lint, format, and test tooling 2026-07-04 19:06:27 +02:00
roadmap.md Move stages, runner and deploy target to dedicated host ONE 2026-07-11 08:05:36 +02:00
security.md Scaffold pnpm monorepo with lint, format, and test tooling 2026-07-04 19:06:27 +02:00

Dorfteich — Architecture Overview

This directory is the authoritative architecture documentation. Every implementation story references the documents here; when a story and this documentation disagree, clarify before coding.

System context

Dorfteich is shipped as a set of Docker containers behind a reverse proxy. One deployment = one instance (e.g. dorfteich.online, or a self-hosted installation).

flowchart LR
    subgraph clients [Clients]
        B[Browser SPA<br/>React + TipTap + Yjs]
    end
    subgraph instance [Dorfteich instance - Docker Compose]
        RP[Reverse proxy]
        WEB[web<br/>static SPA assets]
        API[api<br/>NestJS REST]
        COLLAB[collab<br/>Hocuspocus WebSocket]
        PG[(PostgreSQL)]
        PAN[pandoc-server<br/>doc conversion]
        GOT[Gotenberg<br/>HTML to PDF]
        VOL[/uploads + plugins volume/]
    end
    B -- HTTPS --> RP
    RP --> WEB
    RP -- /api --> API
    RP -- /collab WebSocket --> COLLAB
    API --> PG
    COLLAB --> PG
    API --> PAN
    API --> GOT
    API --> VOL
    COLLAB -. permission checks .-> API
  • web serves the single-page application (static assets).
  • api owns all business logic: auth, ponds, pages, labels, permissions, quotas, import/export, plugin management, admin functions.
  • collab synchronizes CRDT documents (page content) and awareness (cursors) over WebSocket and persists document state to PostgreSQL. It authenticates clients with short-lived tokens issued by api.
  • pandoc-server and Gotenberg are internal-only conversion sidecars (Word/OpenOffice import/export, PDF export).
  • All page content lives in PostgreSQL; binary uploads (images, attachments) and installed plugins live on a Docker volume.

Documents

Architecture Decision Records (adr/)

ADR Decision
0001 TypeScript everywhere, pnpm monorepo
0002 PostgreSQL as the only database
0003 Yjs CRDT + Hocuspocus for real-time and offline collaboration
0004 TipTap (ProseMirror) as the WYSIWYG editor
0005 React + Vite single-page application
0006 NestJS + Prisma for the API server
0007 Cookie sessions, Argon2id, OIDC-ready identity model
0008 Sandboxed iframe plugins with a message-based API
0009 Pandoc + Gotenberg sidecars for import/export
0010 PostgreSQL full-text search behind a search interface
0011 Filesystem volume for uploads, DB-tracked quotas
0012 i18next with German and English from the start
0013 Page version history via Yjs snapshots, soft-delete trash
0014 CI/CD with Gitea Actions, staged promotion
0015 Nightly pg_dump + uploads sync, 30-day retention, off-host mirror
0016 Self-hosted Google Fonts, per-pond font configuration

Concept documents

Document Contents
data-model.md Entities and relations: users, ponds, pages, labels, grants, quotas, plugins
permissions.md Role model and the "most specific setting wins" resolution algorithm
realtime-collaboration.md CRDT document lifecycle, cursor sync, offline behavior, versioning hooks
plugin-architecture.md Plugin manifest, packaging, sandbox runtime, extension points, admin flows
deployment.md Compose stacks for Dev/Test/Int/Prod, environments, promotion pipeline
operations.md Monitoring, logging, backup/restore, update strategy for self-hosters
security.md Threat-driven security concept: authn/authz, sandboxing, uploads, secrets
roadmap.md Epics and milestones; the order stories are implemented in

Terminology

German (product vision) English (code, docs, issues)
Teich pond
Seite page
Teich-Admin Pond Admin
Bearbeitende Editor
Lesende Reader
Öffentlichkeit Public

Conventions for implementers

  • Language: English for code, comments, commit messages, and issues.
  • Write clear, human-readable code; document the "why", not the "what".
  • UI strings never appear hard-coded — always through i18n resources (see ADR 0012), with German and English translations added in the same change.
  • Every story lists the ADRs it depends on; read them before starting.
  • Every API route declares its access rule explicitly (a permission decorator, @Public(), or the Site-Admin guard — issue #52); permission checks run only through the shared resolution (permissions.md), never ad hoc. 404/403 policy: a denied read answers 404 so the existence of ponds and pages is not revealed; a denied write on something the user may read answers 403. Trash views require write capability and answer 404 on denial (ADR 0013).