Como escrever docs
O guia de autoria deste site - frontmatter, diagramas, tabelas de tipo, gráficos, árvores de arquivo, passos, código e os outros componentes do MDX, cada um em uso.
Toda página é um .mdx em apps/docs/content/docs/<raiz>/. Os componentes desta página funcionam em qualquer .mdx sem import: estão no mapa de src/components/mdx.tsx. Os plugins de remark e rehype ficam em source.config.ts.
Frontmatter
Além de title e description, toda página aceita três campos opcionais. O build valida o frontmatter (src/lib/frontmatter.ts): um status com erro de digitação para o build e aponta o arquivo.
---
title: Minha página
description: Uma frase que diz o que a página resolve.
owner: João
status: review
tags: [sse, pareamento]
---Prop
Type
owner, status e tags aparecem numa linha embaixo da descrição, como no topo desta página.
Onde fica cada coisa
Um bloco de código com a linguagem files vira uma árvore (plugin remarkMdxFiles). Pastas terminam em /:
A mesma árvore em JSX, quando precisa abrir uma pasta ou trocar um ícone:
<Files>
<Folder name="content" defaultOpen>
<Folder name="docs" defaultOpen>
<File name="meta.json" />
<File name="index.mdx" />
</Folder>
</Folder>
<File name="source.config.ts" />
</Files>Uma página nova entra no menu pelo meta.json da pasta (pages). Sem isso, ela vai pro fim da lista.
Diagramas
Escreva um bloco mermaid. O plugin remarkMdxMermaid o transforma em <Mermaid chart="…" />, que desenha no navegador com as cores dos tokens e acompanha o tema claro e escuro. No servidor e sem JavaScript, aparece o código do diagrama.
Fluxo
flowchart LR
A["Mudança de estado"] --> B[("Tabela events")]
B --> C["XADD events:account:id"]
C --> D["SSE pro app"]Sequência
sequenceDiagram
participant App as app
participant API as api
App->>API: POST /accounts/{uid}/events/ticket
API-->>App: 201 { ticket, url }
App->>API: GET url (EventSource, em até 60 s)
API-->>App: event: event
API-->>App: event: ping (a cada 5 s sem dados)Estados
Use stateDiagram-v2. O diagrama completo do pareamento está em Pareamento.
stateDiagram-v2
direction LR
[*] --> open
open --> resolved: resolver
open --> dismissed: não é risco
resolved --> open: reabrir em até 7 dias
dismissed --> open: reabrir em até 7 diasEntidades e relações
Um erDiagram com as tabelas de alerta da migração 0044_risk_alerts:
erDiagram
seats ||--o{ risk_alerts : "tem"
risk_alerts |o--o{ risk_findings : "agrupa (alert_id)"
risk_alerts ||--o{ risk_alert_events : "histórico"
risk_alerts |o--o| risk_alerts : "previous_alert_id"
risk_alerts {
uuid uid
text kind
text severity
text group_key
text status
timestamptz superseded_at
}
risk_alert_events {
text type
bigint member_user_id
jsonb detail
timestamptz at
}Os diagramas reais dos guias: estados em Pareamento, o ciclo do alerta em Alertas e o caminho de um evento em Webhooks e eventos.
Tabelas de tipo
Direto do TypeScript
<AutoTypeTable> lê o tipo exportado do arquivo na hora do build (fumadocs-typescript), com os comentários de documentação. O path é relativo a apps/docs.
<AutoTypeTable
path="../../backend/src/domains/protection/dtos/protection.dto.ts"
name="ProtectionState"
/>Prop
Type
Pra mostrar só parte de um tipo grande (props que estendem um elemento HTML, por exemplo), passe um type no mesmo contexto do arquivo:
<AutoTypeTable
path="../../packages/ui/src/feedback/badge.tsx"
name="BadgeLook"
type="Pick<BadgeProps, 'tone' | 'size' | 'icon' | 'render'>"
/>Prop
Type
Tipos que dependem do kysely
O docs não instala as dependências do backend. Um DTO que usa PairingRow["status"] (um Selectable do kysely) sai como Selectable<object>. Prefira os DTOs que usam os tipos do domínio direto (EventDto, PublicPairingDto, ProtectionState). As páginas de componente do design system já trazem as props geradas.
Escrita à mão
<TypeTable> quando o tipo não existe em TypeScript, como o frontmatter acima ou o corpo de um webhook:
<TypeTable
type={{
status: {
type: '"draft" | "review" | "stable" | "deprecated"',
description: "Vira um selo embaixo da descrição.",
},
}}
/>Gráficos
<Chart> desenha linha (line), barra (bar) ou área (area) do mesmo jeito que os gráficos do backoffice: SVG na largura real, grade discreta, legenda a partir de duas séries, tooltip ao passar o mouse e uma tabela com os dados pra leitores de tela. As cores das séries são --docs-chart-1 a --docs-chart-5 (theme.css).
Nunca invente número
Um gráfico aqui mostra configuração ou dado real, com a fonte na legenda (caption). Sem fonte, sem gráfico.
Dados de um JSON
Importe o arquivo no topo da página e espalhe as props. Os JSONs ficam em content/data/ (fora de content/docs/, onde todo .json é lido como meta.json).
import rateLimits from "../../data/rate-limits.json";
<Chart {...rateLimits} />- Por identificador (e-mail ou link)
- Por IP
| regra | Por identificador (e-mail ou link) | Por IP |
|---|---|---|
| Pedir código | 5 | 20 |
| Evidência | 10 | 30 |
| Recusar ou parar | 10 | 30 |
Padrões de backend/src/config/env.ts: RATE_OTP_REQUEST_PER_EMAIL/_PER_IP, RATE_EVIDENCE_PER_LINK/_PER_IP, RATE_CONSENT_WRITE_PER_LINK/_PER_IP.
- Espera antes da tentativa
- Tempo desde a queda
| tentativa | Espera antes da tentativa | Tempo desde a queda |
|---|---|---|
| 1ª tentativa | 0,5 min | 0,5 min |
| 2ª tentativa | 2 min | 2,5 min |
| 3ª tentativa | 10 min | 12,5 min |
RECONNECT_DELAYS_MS em backend/src/domains/channels/use-cases/apply-provider-state.ts: 30 s, 2 min e 10 min. Se a 3ª também falhar, o pareamento vai pra disconnected.
- Espera antes da tentativa
- Tempo desde a queda
| tentativa | Espera antes da tentativa | Tempo desde a queda |
|---|---|---|
| 1ª tentativa | 0,5 min | 0,5 min |
| 2ª tentativa | 2 min | 2,5 min |
| 3ª tentativa | 10 min | 12,5 min |
RECONNECT_DELAYS_MS em backend/src/domains/channels/use-cases/apply-provider-state.ts: 30 s, 2 min e 10 min. Se a 3ª também falhar, o pareamento vai pra disconnected.
Dados na própria página
<Chart
type="bar"
title="Janela de análise ao vivo: o que fecha a janela"
caption="ANALYSIS_QUIET_SECONDS e ANALYSIS_MAX_WINDOW_SECONDS em backend/src/config/env.ts."
x="regra"
unit=" min"
series={[{ key: "minutos", label: "Minutos" }]}
data={[
{ regra: "Conversa quieta", minutos: 2 },
{ regra: "Idade máxima", minutos: 15 },
]}
/>| regra | Minutos |
|---|---|
| Conversa quieta | 2 min |
| Idade máxima | 15 min |
ANALYSIS_QUIET_SECONDS (120 s) e ANALYSIS_MAX_WINDOW_SECONDS (900 s) em backend/src/config/env.ts. A janela também fecha com 60 mensagens (ANALYSIS_MAX_WINDOW_MESSAGES).
Prop
Type
Código
Blocos de código têm título, abas, destaque de linha e números de linha (Shiki, pelo rehypeCode).
await rateLimit(`minha-rota:link:${hashToken(token)}`, {
limit: 10,
windowSeconds: 600,
});
await rateLimit(`minha-rota:ip:${clientIp(c)}`, {
limit: 30,
limit: 60,
windowSeconds: 600,
});| No bloco | Efeito |
|---|---|
```ts title="arquivo.ts" | título em cima do bloco |
```ts lineNumbers | números de linha (lineNumbers=4 começa no 4) |
// [!code highlight] | destaca a linha |
// [!code ++], // [!code --] | linha adicionada ou removida |
// [!code word:rateLimit] | destaca a palavra no bloco |
// [!code focus] | foca a linha e apaga o resto |
```ts tab="Nome" | blocos seguidos com tab viram abas |
Blocos seguidos com tab viram um grupo de abas:
{
"success": true,
"data": { "ticket": "eyJ…", "url": "/accounts/…/events?ticket=eyJ…" }
}Código inline também ganha cor: termine o trecho com {:ts} (ou outra linguagem) dentro dos acentos graves, como em rateLimit(key, { limit: 10 }).
Passos
Títulos com [step] viram passos (plugin remarkSteps):
Crie o arquivo
content/docs/<raiz>/minha-pagina.mdx, com title e description.
Ponha no menu
Adicione "minha-pagina" em pages no meta.json da pasta.
Rode o CI
bun run ci em apps/docs: prettier, eslint, typecheck e build. O build valida o frontmatter e renderiza cada página.
Ou, sem mexer nos títulos, <Steps> e <Step>:
Escreva a página.
Confira em localhost:3300, nos dois temas.
Outros componentes
Callout
Informação
type="info").Sucesso
type="success".
Atenção
type="warn": um "Em aberto" no fim de cada guia.
Ideia
type="idea".
Banner
Um aviso de largura total dentro da página (<Banner>):
Accordion
SSE reconecta sozinho e roda em HTTP puro (AD-010). Veja Tempo real.
Em backend/src/config/env.ts, no formato limit/windowSeconds.
Cards
Imagem com zoom
Toda imagem em Markdown abre ampliada ao clicar. Caminhos relativos viram import (o build sabe o tamanho):

Índice no corpo
<InlineTOC items={toc} /> mostra os títulos da página, como no começo desta. toc já existe em todo .mdx.
Fontes
apps/docs/source.config.ts,src/lib/source.ts,src/lib/frontmatter.tsapps/docs/src/components/mdx.tsx,src/components/mdx/mermaid.tsx,src/components/mdx/chart.tsx,src/components/page-meta.tsxapps/backoffice/src/components/charts/*(o desenho que o<Chart>segue)- Fumadocs: Mermaid, Markdown, Collections, Macro API, Global options, Auto Type Table
