dorfteich/docs/architecture
Claude Fable 5 97f94f247b
All checks were successful
CD / Build and push images (push) Successful in 3m54s
CD / Deploy to Test (push) Successful in 10s
CI / Lint, typecheck, test (push) Successful in 4m9s
CI / Build container images (push) Has been skipped
CD / Smoke tests against Test (push) Successful in 1m13s
CD / Promote to Int (push) Successful in 13s
CI / Auth e2e pack (push) Successful in 5m36s
CI / Import/export fidelity gate (push) Successful in 47s
draw.io reference plugin: fullscreen editing, inline SVG rendering
A new block plugin bundling the OFFICIAL draw.io editor — nothing ever
loads from diagrams.net; the sandbox CSP pins every request to the
plugin's own version-pinned asset path (zero-external-network verified
live via a request-capture run).

Plugin (packages/plugins/drawio):
- block data { xml, svg }: xml is the draw.io source (document of
  record), svg the rendered snapshot as raw markup — render mode,
  office/PDF exports (the existing fallback renderer already inlines
  data.svg) and the public view all show the diagram without running
  diagram code
- edit mode: snapshot + "edit in fullscreen" (an empty block opens the
  editor immediately); the bundled editor runs in a child iframe of the
  plugin's own assets and speaks draw.io's JSON embed protocol —
  Save & Exit exports xmlsvg, persists { xml, svg } via blockData, and
  drops back to the inline size
- build.mjs fetches the pinned release (v30.3.6) into a gitignored
  vendor/ cache (fonts-build pattern; skipped in CI — plugin.js still
  bundles, the installable ZIP needs a dev machine) and packs a trimmed
  webapp subset: no dev sources, no embed.diagrams.net integrations
  bundle, no standalone viewers, no MathJax/templates/PWA — 27 MiB ZIP,
  85 MiB unpacked, de+en editor languages

Host/SDK extensions (generic, not drawio-specific):
- new ui.enterFullscreen()/exitFullscreen(): the surface's frame becomes
  a viewport-covering overlay — same sandboxed iframe, only geometry
  changes; destroy removes the frame, so a vanished plugin can never
  leave the app covered
- sandbox CSP: connect-src/frame-src now allow the plugin's OWN asset
  path (was 'none') — bundled apps lazy-load their resources and run in
  a child frame, but the api and external hosts stay unreachable; HTML
  assets are served with the same CSP so a packaged page cannot widen
  the rules, and child frames inherit the sandbox attribute
- plugin size limits raised (ZIP 5→64 MiB, unpacked 20→256 MiB) for
  bundled-app plugins; content types for xml/txt/ico assets

Verified end to end against a local stack (9/9): install via dropzone
(85 MiB validation), block insert, fullscreen entry, bundled editor
boots inside the double sandbox (German UI), shape drawn, Save & Exit
persists, snapshot renders inline, survives reload, zero off-origin
requests throughout.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EwZ4jR4KFAPvpjWevfUGX1
2026-07-12 14:15: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 Run operations QA on every release candidate before the prod gate (#90) 2026-07-12 00:29:53 +02:00
operations.md Backup mirror to BASEL: rsync of the sets after every successful run (#84) 2026-07-12 12:20:32 +02:00
permissions.md Scaffold pnpm monorepo with lint, format, and test tooling 2026-07-04 19:06:27 +02:00
plugin-architecture.md draw.io reference plugin: fullscreen editing, inline SVG rendering 2026-07-12 14:15: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).