developer-guide/extending/index.md

103 lines
4.7 KiB
Markdown

---
id: extending
title: Extending f451
description: Recipes for the most common kinds of change — an API route, a UI string, a design token, a Markdown construct, an MCP tool, a Git provider.
tags: [howto, architecture]
lang: en
---
# Extending f451
Six recipes for changes that come up repeatedly. Each one names where the
change starts and what it touches on the way.
## Add an API route
1. Add a `register*Routes` function under `apps/api/src/routes/`, following
the existing pattern: it takes `{ db, spaces, providerRegistry, access,
canWrite, getUserProvider }` as needed, not the raw request context.
2. Register it in `apps/api/src/app.ts`, in the block that only runs when
`db && opts.spaces && opts.providerRegistry` are present — read-only
routes need `access`; anything that writes needs `canWrite` and
`getUserProvider` as well, since writes commit under the calling user's
own provider token (see [[principles]]).
3. Decide read or write semantics up front: read routes are filtered by
`access.canRead`, write routes additionally need a linked provider
account and go through the draft/SHA-contract machinery described in
[[api]] — don't bypass it for a "simple" new route.
4. Add a Swagger/JSON schema for the response; the OpenAPI spec at
`/api/openapi.json` is generated from it.
## Add a UI string
Add the key to both `apps/web/lib/i18n/messages/en/` and the matching
`de/` file in the same change — f451's interface is bilingual, and a
string that exists in only one language is a regression, not a partial
feature. See [[web-frontend]].
## Add a design token
1. Add the value to `packages/design-tokens/src/tokens.ts`.
2. Describe it in `catalog.ts`: its level (`theme` / `derived` /
`structure` / `switch`), its settings-page group, its role in plain
language, and whether a user theme is allowed to set it (`settable`,
with a `lockReason` if not).
3. Update the token-count tests for the affected level and group — they
are pinned deliberately, so the update should be a conscious edit, not
a number you copy without reading why it existed. See [[web-frontend]].
## Add a Markdown construct
Full recipe in [[markdown-and-editor]]. In short: parse and render it in
`packages/markdown`, make `stringify.ts` reproduce the same syntax, add the
matching node to `packages/editor` and its two conversion directions, then
add a round-trip test that proves Markdown → editor → Markdown is
lossless.
## Add an MCP tool
MCP tools in `apps/mcp/src/tools/` are thin wrappers: each one calls the
HTTP API (`apps/mcp/src/client.ts`) with the token passed through from the
calling agent, and does not hold state or a secret of its own. The
existing set is a useful map of scope:
- **Read** (`tools/read.ts`): `search_wiki`, `list_spaces`, `get_tree`,
`read_page`, `get_page_source`, `get_graph`, `list_broken_links`.
- **Write** (`tools/write.ts`): `create_page`, `edit_page`,
`update_page_draft`, `discard_page_draft`, `request_review`,
`release_page`, `request_changes` — the review workflow from [[api]],
exposed one step at a time.
- **Attachments** (`tools/attachments.ts`): `save_diagram`, `attach_file`.
A new tool follows the same shape: register it with `server.registerTool`,
give it a clear `title` and input schema, and implement it by calling an
existing API route — never by reaching around the API into the database or
Git directly. If no suitable API route exists yet, add one first (see
above); the MCP service is not the place to grow API-shaped logic of its
own.
## Add a Git provider
`packages/git-provider/src/types.ts` defines one interface,
`GitProvider` — file reads (`readFile`, `readFileBinary`, `listTree`),
writes (`writeFile`, `writeFileBinary`, `deleteFile`, the batched
`commitFiles`), branches, and the pull request lifecycle
(`createPullRequest`, `requestReviewers`, `submitPullRequestReview`,
`mergePullRequest`). `forgejo.ts` and `github.ts` are the two existing
implementations.
A third provider means a new file implementing every method of
`GitProvider`, throwing the same three error types
(`NotFoundError`/`ConflictError`/`ProviderError`) the existing
implementations use — callers in `apps/api` branch on those errors, not on
provider-specific ones. `commitFiles` is worth reading closely before
implementing it: it exists because writing many files one call at a time
was measured to take minutes on a large move, and its batching behavior
(splitting large change sets across multiple underlying commits, returning
the last commit's SHA, treating an empty list as a no-op) is part of the
contract, not an implementation detail.
> [!TIP]
> None of these recipes need a browser or a full test run to make progress
> on. See [[testing-and-contributing]] for what to actually check before
> opening a pull request.