dorfteich/docs/de/developer/extending.md
Claude Fable 5 baebd79cc8
All checks were successful
CD / Build and push images (push) Successful in 1m10s
CD / Deploy to Test (push) Successful in 10s
CD / Smoke tests against Test (push) Successful in 1m14s
CD / Promote to Int (push) Successful in 11s
CI / Lint, typecheck, test (push) Successful in 4m6s
CI / Build container images (push) Has been skipped
CI / Auth e2e pack (push) Successful in 5m35s
CI / Import/export fidelity gate (push) Successful in 48s
German translations of the seven user-facing docs under docs/de/
Mirrors the English tree (docs/de/{features.md,manual/*,developer/
extending.md}) so relative links between translated guides resolve
within the German set; links into untranslated areas (self-hosting,
architecture, deploy) point at the English files and say so. Every
quoted UI label matches the actual German interface strings. Each
pair of files cross-links the other language; English stays
authoritative when the two diverge.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EwZ4jR4KFAPvpjWevfUGX1
2026-07-12 19:28:50 +02:00

7.3 KiB

Entwicklerhandbuch — Dorfteich erweitern

Englisches Original: docs/developer/extending.md

Zwei Wege, Dorfteich mehr beizubringen: ein Plugin schreiben (kein Fork, kein Redeploy, konstruktionsbedingt sicher) oder am Kern mitarbeiten. Beginne mit einem Plugin, außer du musst ändern, wie das Produkt selbst funktioniert.

Ein Plugin schreiben

Lies einmal docs/architecture/plugin-architecture.md (englisch) — das ist der Vertrag. Die Kurzfassung:

Ein Plugin ist ein ZIP mit einer manifest.json, einem einzelnen ES-Modul-Bundle plugin.js, optional styles.css, i18n/*.json und assets/…. Es steuert einen oder mehrere Erweiterungspunkte bei:

Typ Du baust Beispiel
sectionStyle benannte CSS-Stile für Inhaltsabschnitte (ganz ohne Code) section-styles-basic
pageTool ein Nur-Lese-Widget im Seiten-Werkzeuge-Panel toc, page-index
block ein eigener Editor-Block mit eigenen Daten und Bearbeitungs-UI mermaid, drawio

Die Sandbox — was dein Code kann und was nicht

Dein plugin.js läuft in einem <iframe sandbox="allow-scripts"> mit opakem Origin und strikter CSP: keine Cookies, kein Storage, kein Eltern-DOM, Netzwerk/Frames nur zu deinen eigenen gebündelten Assets — nie zur API oder einem externen Host. Alles andere läuft über die typisierte RPC des SDK, ausgeführt mit den Berechtigungen der betrachtenden Person:

import { createPlugin, windowTransport } from '@dorfteich/plugin-sdk';

const { host } = createPlugin({
  transport: windowTransport({
    target: { postMessage: (m) => window.parent.postMessage(m, '*') },
    source: window,
  }),
  onRender: async (ctx) => {
    const outline = await host.readCurrentPage.getOutline();
    document.body.textContent = outline.map((e) => e.text).join('\n');
    void host.ui.resize(document.body.scrollHeight + 16);
  },
  onEdit: async (ctx) => {
    /* Block-Plugins: Bearbeitungs-UI; persistieren via host.blockData.setData(...) */
  },
});

Fähigkeiten, die du im Manifest unter permissions deklarieren kannst, und was sie freischalten: readCurrentPage (Gliederung/Inhalt/ Metadaten), readPond (Seitenlisten + Inhalte), readBlock (Block-Lesezugriffe über Seiten hinweg), blockData (getData/setData deines Blocks — Schreibzugriffe werden normale Dokumentänderungen, repliziert und versioniert), ui (resize, openPage, toast, scrollToHeading, enterFullscreen/exitFullscreen). Aufrufe außerhalb des deklarierten Satzes werden zur Laufzeit abgelehnt.

Block-Plugins in drei Sätzen

Der Host mountet deinen Frame pro Block und ruft render (Ansicht) oder edit (die Person hat den Bearbeiten-Knopf des Blocks gedrückt). Persistiere { …deineDaten } via host.blockData.setData — die Frames der Mitarbeitenden rendern live neu, wenn sich die Daten unter ihnen ändern. Lege neben deinen Quelldaten einen statischen Schnappschuss ab (z. B. einen svg-String): Exporte und die öffentliche Ansicht zeigen ihn über die fallback-Mechanik des Manifests, ohne je Plugin-Code auszuführen.

Gebündelte Apps und Vollbild

Ein Plugin darf eine ganze Unter-Anwendung als Assets mitbringen und in einem Kind-iframe seines eigenen Asset-Pfads ausführen — so bettet das drawio-Plugin den echten draw.io-Editor ein. Kombiniere das mit host.ui.enterFullscreen() für Editoren, die den ganzen Bildschirm brauchen. Größen-Limits: 64 MiB ZIP, 256 MiB entpackt.

Entwickeln und ausliefern

Die Referenz-Plugins unter packages/plugins/ sind die Vorlagen — kopiere das nächstliegende. Jedes hat ein build.mjs, das src/plugin.ts mit esbuild bündelt und das installierbare ZIP nach dist/ packt:

cd packages/plugins/<dein-plugin>
pnpm build          # → dist/<id>-<version>.zip

Installiere das ZIP über Admin → Plugins (oder die Dropzone), öffne die gesandboxte Vorschau unter /admin/plugins/<id>/preview, iteriere. Das Install-Gate validiert Struktur, Manifest, Größe und CSS-Scoping und lehnt mit einem präzisen Fehlercode ab. Ein Update veröffentlichen = gleiche ID, höhere Version.

Konventionen, die im Review durchgesetzt werden: UI-Strings über die i18n/-Dateien des Plugins in beiden Sprachen de und en; der Fallback muss in einem gedruckten Dokument Sinn ergeben.

Am Kern arbeiten

Der Stack auf einen Blick

TypeScript-Monorepo (pnpm-Workspaces): apps/api (NestJS + Prisma, PostgreSQL), apps/collab (Hocuspocus/Yjs-Echtzeit-Server), apps/web (React + Vite + TipTap), apps/backup (Backup-Sidecar), packages/shared (Typen, Schemas, Editor-Schema, i18n-Kataloge), packages/plugin-sdk, packages/plugins/*. Architektur-Entscheidungen liegen in docs/architecture/adr/ — lies das zutreffende ADR, bevor du ein Subsystem anfasst; docs/architecture/ hat die Tiefenbohrungen (Berechtigungen, Datenmodell, Echtzeit-Zusammenarbeit, Sicherheit, Plugins; alles englisch).

Eine Entwicklungsumgebung bekommen

pnpm install
cd deploy/compose && cp .env.example .env
# Voll containerisierter Dev-Stack (Hot Reload; der erste Start installiert die Abhängigkeiten):
docker compose -f docker-compose.yml -f compose.dev.yml up
#   → web http://localhost:5173, api :3001, db :5434

Schnellstes Feedback: nur die Datenbank in Docker, web/api nativ — das genaue Rezept steht am Kopf von deploy/compose/compose.dev.yml. Fixture-Nutzer/-Teiche einspielen mit pnpm --filter @dorfteich/api db:seed (Fixture-Passwort: siehe apps/api/prisma/seed.ts).

Die Gates, die jede Änderung bestehen muss

pnpm lint        # ESLint + Prettier — keine Pipes, die Exit-Codes verschlucken
pnpm typecheck
pnpm test        # überall vitest; DB-gestützte Suiten brauchen TEST_DATABASE_URL
pnpm i18n:check  # jeder UI-String in de UND en

DB-gestützte Tests laufen gegen die Compose-Dev-Datenbank: TEST_DATABASE_URL=postgresql://dorfteich:dorfteich@localhost:5434/dorfteich pnpm test. Playwright-e2e-Packs liegen in apps/web/e2e/ und laufen gegen einen lokal geseedeten Stack (das genaue Rezept: .gitea/workflows/ci.yml).

Hausregeln, die du vor deinem ersten PR kennen solltest

  • Berechtigungen: Beantworte eine Zugriffsfrage nie außerhalb des PermissionService/der Routen-Dekoratoren; verweigertes Lesen ist 404, verweigertes Schreiben auf Lesbarem ist 403 (permissions.md).
  • i18n: keine hart codierten UI-Strings; Keys nach packages/shared/i18n/{de,en}/… (ADR 0012).
  • Keine Anfragen an Dritte aus dem Produkt, niemals — Schriften, Editoren, alles wird selbst gehostet ausgeliefert (ADR 0016 setzt den Präzedenzfall).
  • Migrationen: additiv und innerhalb eines Minor-Releases umkehrbar; das QA-Gate der Release-Pipeline spielt ein Upgrade vom vorherigen Release gegen echte Daten nach.
  • Verträge dokumentieren: Alles, was zwei Services teilen (Status-Dateien, NOTIFY-Kanäle, Wire-Typen), lebt in packages/shared mit einem Kommentar, wer liest und wer schreibt.