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
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/sharedmit einem Kommentar, wer liest und wer schreibt.