All checks were successful
CD / Build and push images (push) Successful in 1m9s
CD / Deploy to Test (push) Successful in 10s
CD / Smoke tests against Test (push) Successful in 1m10s
CD / Promote to Int (push) Successful in 11s
CI / Lint, typecheck, test (push) Successful in 4m7s
CI / Build container images (push) Has been skipped
CI / Auth e2e pack (push) Successful in 5m34s
CI / Import/export fidelity gate (push) Successful in 47s
Seven audience-targeted documents (English first, German translation to follow), linked from the README and a new docs/manual/ index: - docs/features.md — public-facing feature overview: what Dorfteich can do and why that matters - docs/manual/user-guide.md — everyday use: editor, wikilinks, labels, search, comments, watches/digests, import/export, settings - docs/manual/pond-admin-guide.md — pond configuration: members/roles, access rules incl. label scoping and public pages, labels, comment policy, plugins, API/MCP opt-ins, files, export - docs/manual/site-admin-guide.md — instance administration: wizard, settings, quotas, uploads, API/MCP switches, legal pages, plugins, users, and the system panel (jobs/backups/audit/storage) - docs/manual/api-guide.md — example-driven public-API walkthrough (tokens, reading, writing through the collab-safe path, labels, comments, error semantics) - docs/manual/mcp-guide.md — connecting AI assistants: switches, token scopes, Claude Code one-liner, mcp-remote bridge, tool table, audit and safety properties - docs/developer/extending.md — plugin development (sandbox contract, SDK, block plugins, bundled apps/fullscreen, shipping) and core contributions (stack, dev environment, gates, house rules) README: documentation index, repository-layout rows for docs/manual and docs/developer, and the stale "architecture phase" status brought up to reality. All relative links verified. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EwZ4jR4KFAPvpjWevfUGX1
155 lines
6.6 KiB
Markdown
155 lines
6.6 KiB
Markdown
# Developer guide — extending Dorfteich
|
|
|
|
Two ways to make Dorfteich do more: write a **plugin** (no fork, no
|
|
redeploy, safe by construction) or contribute to the **core**. Start
|
|
with a plugin unless you need to change how the product itself works.
|
|
|
|
## Writing a plugin
|
|
|
|
Read [`docs/architecture/plugin-architecture.md`](../architecture/plugin-architecture.md)
|
|
once — it is the contract. The short version:
|
|
|
|
A plugin is a ZIP with a `manifest.json`, a single ES-module bundle
|
|
`plugin.js`, optional `styles.css`, `i18n/*.json`, and `assets/…`. It
|
|
contributes one or more **extension points**:
|
|
|
|
| Type | You build | Example |
|
|
| -------------- | ------------------------------------------------------ | ---------------------- |
|
|
| `sectionStyle` | named CSS styles for content sections (no code at all) | `section-styles-basic` |
|
|
| `pageTool` | a read-only widget in the page-tools panel | `toc`, `page-index` |
|
|
| `block` | a custom editor block with its own data and edit UI | `mermaid`, `drawio` |
|
|
|
|
### The sandbox — what your code can and cannot do
|
|
|
|
Your `plugin.js` runs in an `<iframe sandbox="allow-scripts">` with an
|
|
opaque origin and a strict CSP: **no cookies, no storage, no parent DOM,
|
|
and network/frames only to your own bundled assets** — never to the api
|
|
or any external host. Everything else goes through the typed RPC the
|
|
SDK provides, executed with the _viewing user's_ permissions:
|
|
|
|
```ts
|
|
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: editing UI; persist via host.blockData.setData(...) */
|
|
},
|
|
});
|
|
```
|
|
|
|
Capabilities you may declare in the manifest `permissions` and what they
|
|
unlock: `readCurrentPage` (outline/content/meta), `readPond` (page
|
|
lists + contents), `readBlock` (cross-page block reads), `blockData`
|
|
(your block's `getData`/`setData` — writes become normal document
|
|
changes, replicated and versioned), `ui` (`resize`, `openPage`, `toast`,
|
|
`scrollToHeading`, `enterFullscreen`/`exitFullscreen`). Calls outside
|
|
the declared set are rejected at runtime.
|
|
|
|
### Block plugins in three sentences
|
|
|
|
The host mounts your frame per block and calls `render` (view) or
|
|
`edit` (the user pressed the block's edit button). Persist
|
|
`{ …yourData }` via `host.blockData.setData` — collaborators' frames
|
|
re-render live when the data changes under them. Store a static
|
|
snapshot (e.g. an `svg` string) alongside your source data: exports and
|
|
the public view show it through the manifest `fallback` machinery
|
|
without ever executing plugin code.
|
|
|
|
### Bundled apps and fullscreen
|
|
|
|
A plugin may ship an entire sub-application as assets and run it in a
|
|
child iframe of its own asset path — that is how the `drawio` plugin
|
|
embeds the real draw.io editor. Combine with
|
|
`host.ui.enterFullscreen()` for editors that need the whole screen.
|
|
Size limits: 64 MiB ZIP, 256 MiB unpacked.
|
|
|
|
### Developing and shipping
|
|
|
|
The reference plugins under `packages/plugins/` are the templates —
|
|
copy the closest one. Each has a `build.mjs` that bundles `src/plugin.ts`
|
|
with esbuild and packs the installable ZIP into `dist/`:
|
|
|
|
```sh
|
|
cd packages/plugins/<your-plugin>
|
|
pnpm build # → dist/<id>-<version>.zip
|
|
```
|
|
|
|
Install the ZIP via Admin → Plugins (or the dropzone), open the
|
|
sandboxed preview at `/admin/plugins/<id>/preview`, iterate. The install
|
|
gate validates structure, manifest, size and CSS scoping and rejects
|
|
with a precise error code. Publishing an update = same id, higher
|
|
version.
|
|
|
|
Conventions that will be enforced on review: UI strings via the plugin's
|
|
`i18n/` files in **both** `de` and `en`; the fallback must make sense in
|
|
a printed document.
|
|
|
|
## Working on the core
|
|
|
|
### Stack at a glance
|
|
|
|
TypeScript monorepo (pnpm workspaces): `apps/api` (NestJS + Prisma,
|
|
PostgreSQL), `apps/collab` (Hocuspocus/Yjs realtime server),
|
|
`apps/web` (React + Vite + TipTap), `apps/backup` (backup sidecar),
|
|
`packages/shared` (types, schemas, editor schema, i18n catalogs),
|
|
`packages/plugin-sdk`, `packages/plugins/*`. Architecture decisions live
|
|
in [`docs/architecture/adr/`](../architecture/adr/) — read the relevant
|
|
ADR before touching a subsystem; `docs/architecture/` has the deep dives
|
|
(permissions, data model, realtime collaboration, security, plugins).
|
|
|
|
### Getting a dev environment
|
|
|
|
```sh
|
|
pnpm install
|
|
cd deploy/compose && cp .env.example .env
|
|
# Full containerized dev stack (hot reload; first start installs deps):
|
|
docker compose -f docker-compose.yml -f compose.dev.yml up
|
|
# → web http://localhost:5173, api :3001, db :5434
|
|
```
|
|
|
|
Fastest feedback: run only the database in Docker and web/api natively —
|
|
the exact recipe is documented at the top of
|
|
[`deploy/compose/compose.dev.yml`](../../deploy/compose/compose.dev.yml).
|
|
Seed fixture users/ponds with `pnpm --filter @dorfteich/api db:seed`
|
|
(fixture password: see `apps/api/prisma/seed.ts`).
|
|
|
|
### The gates every change must pass
|
|
|
|
```sh
|
|
pnpm lint # ESLint + Prettier — no pipes that swallow exit codes
|
|
pnpm typecheck
|
|
pnpm test # vitest everywhere; DB-backed suites need TEST_DATABASE_URL
|
|
pnpm i18n:check # every UI string in de AND en
|
|
```
|
|
|
|
DB-backed tests run against the compose dev database:
|
|
`TEST_DATABASE_URL=postgresql://dorfteich:dorfteich@localhost:5434/dorfteich pnpm test`.
|
|
Playwright e2e packs live in `apps/web/e2e/` and run against a seeded
|
|
local stack (see `.gitea/workflows/ci.yml` for the exact recipe).
|
|
|
|
### House rules worth knowing before your first PR
|
|
|
|
- **Permissions**: never answer an access question outside
|
|
`PermissionService`/the route decorators; denied reads are 404, denied
|
|
writes on readable things are 403
|
|
([permissions.md](../architecture/permissions.md)).
|
|
- **i18n**: no hard-coded UI strings; add keys to
|
|
`packages/shared/i18n/{de,en}/…` (ADR 0012).
|
|
- **No third-party requests** from the product, ever — fonts, editors,
|
|
everything ships self-hosted (ADR 0016 sets the precedent).
|
|
- **Migrations**: additive and reversible within one minor release; the
|
|
release pipeline's QA gate replays an upgrade from the previous
|
|
release against real data.
|
|
- **Document contracts**: anything two services share (status files,
|
|
NOTIFY channels, wire types) lives in `packages/shared` with a comment
|
|
saying who reads and who writes.
|