ADR 0068 — Diagrama C4 do Arquiteto
- Status: aceito
- Data: 2026-08-12
- Contexto: pedido do usuário — o Arquiteto ganha um entregável novo, renderizado na Visão Geral do projeto
- Estende: o mesmo padrão de artefato-sem-tabela do
ADR 0065
(
artifact.project_image)
Contexto
Os entregáveis do Arquiteto hoje são texto e dado estruturado — module_map
(grid de cards), ADRs (lista com link de PR), insights. Nenhum é uma imagem
da arquitetura. O pedido é um diagrama C4 (modelo de Simon Brown — pelo menos
Context e Container) renderizado de verdade na tela, não um link para fora.
Duas perguntas de arquitetura, antes de codar:
Quem escreve o Container level — o modelo ou o repositório? O
module_map já existe, validado sem ciclo (create_module_map). Deixar o
modelo REDIGITAR os módulos e dependências no tool call do diagrama arrisca
divergência silenciosa: o diagrama diria uma coisa, o module_map vigente
diria outra, e não haveria como saber qual mentiu. A alternativa — derivar o
Container level do module_map vigente, no caso de uso, e nunca do que o
modelo escreve de novo — fecha essa divergência por construção. O Context
level não tem essa fonte: quem são os atores externos (o usuário, um
provedor de Git) é julgamento do Arquiteto, sem um module_map de atores
para derivar. Só o Context, então, vem do tool call.
Com o que renderizar? apps/web não tinha nenhuma lib de diagrama
(zero ocorrência de "mermaid" no package.json antes desta mudança); o site
de docs usa Mermaid, mas em BUILD-TIME (Docusaurus). Mermaid é o motor
padrão de facto para diagramas-como-texto, incluindo suporte nativo a
C4Context/C4Container — não escrevemos um layout engine. Decisão do
usuário, confirmada antes desta mudança: mermaid entra como dependência de
RUNTIME nova do app React, a primeira do tipo.
Decisão
O diagrama C4 é artefato versionado no event log, sem tabela — mesmo
desenho do artifact.project_image (ADR 0065) — com o Container level
DERIVADO do module_map vigente, nunca redigitado pelo modelo.
artifact.c4_diagram:CreateC4DiagramUseCasebusca omodule_mapvigente (ModuleMapRepository.findCurrent); sem ele, recusa com 400 — não há Container level sem módulos. Gera as duas sintaxes Mermaid (gerarDiagramaContexto/gerarDiagramaContainer, puras, emdomain/architecture/c4-diagram.ts) e grava o evento comversion=GetC4DiagramUseCase.execute(projectId).version + 1— igual à leitura "o vigente é o de maiorversion" deObterContainerDoProjetoUseCase. Reemitir é gerar de novo; o histórico não é reescrito.- Sem regex na geração da sintaxe: nome de módulo/ator vem do modelo (ou do
que o modelo já escreveu em
create_module_map), e umjs/polynomial-redosaqui seria a mesma HIGH queproject-container.tsjá evita. Escape de label e geração de id Mermaid são caractere-a-caractere, mesmo estilo dereferenciaDeImagemValida. - Ferramenta
create_c4_diagramno Arquiteto:system_name(obrigatório),system_descriptioneactors(nome +person/external_system+ descrição). Fina — só normaliza e repassa; quem valida e deriva é a api, mesmo padrão decreate_module_map/choose_project_image. mermaidisolado atrás deapps/web/src/lib/mermaid-render.ts(renderMermaid(id, sintaxe)), comimport()DINÂMICO — quem nunca abre a Visão Geral com diagrama gerado não paga o bundle.vite buildconfirma: sóindex-*.js(o entrypoint) carrega eager; os chunks de Mermaid (mermaid.core,c4Diagram-*, e as dependências pesadas dele —cytoscape.esm435 KB,katex258 KB — usadas só por OUTROS tipos de diagrama que este produto não gera) ficam sob demanda.C4DiagramView.tsx(apps/web) — três estados por diagrama (RN-088):rendering(Skeleton),erro(Alert com a mensagem do Mermaid + a sintaxe crua colapsada em<details>, NUNCA a tela quebrando) epronto(o SVG, viadangerouslySetInnerHTML— conteúdo que NÓS geramos commermaid.render, não HTML de terceiro repassado, comsecurityLevel: 'strict'no Mermaid). O quarto estado, "sem diagrama nenhum", é da seção que chama o componente (ArchitectureSection), igual ao que já existe paramoduleMap/ADRs.- Tema do Mermaid lido dos tokens do design system em runtime
(
getComputedStylesobre--surface-*/--text-*/--border*), nunca cor fixa — não porque o app tenha toggle de tema hoje (não tem: dark é primário), mas para não hardcodear paleta que já existe emtokens.css.
CSP — confirmado, sem mudança
O nginx da imagem web tem CSP fechado desde o ADR 0058
(script-src 'self', sem unsafe-eval/unsafe-inline;
style-src 'self' 'unsafe-inline'). Checado nos assets de produção
(grep -rl "new Function(\|eval(" dist/assets/*.js): NENHUM chunk do
Mermaid usa eval/new Function. style-src já tinha unsafe-inline
antes desta mudança (por outro motivo — CSS inline do próprio app), e é o
que cobre o <style> que o SVG do Mermaid pode embutir. Nenhuma linha do
nginx.conf mudou.
Consequências
- Bundle maior, mas sob demanda.
pnpm buildconfirmou code-splitting correto: o entrypoint (index-*.js) não cresceu com o peso do Mermoid inteiro — só quem efetivamente renderiza um diagrama baixa os chunks dele. O custo real (não medido em bytes de rede, só confirmado como lazy) fica para a primeira vez que a Visão Geral mostra um diagrama gerado. - Só Context e Container. Component e Code (níveis 3 e 4 do C4) ficam de
fora — o
module_mapnão tem granularidade de componente/código, e inventar essa granularidade não foi pedido. - Diagrama denso não tem tratamento especial. Um
module_mapcom dezenas de módulos produz um Container diagram grande; não há virtualização nem colapso — mesmo corte que a FASE 26 já fez para a aba Code ("a mais custosa do programa"), por ora aceito. - Divergência entre diagrama e module_map é só temporal, não de dado.
Como o Container é DERIVADO na hora da geração, um diagrama antigo pode
ficar desatualizado se o
module_mapmudar depois — mas nunca vai MENTIR sobre o que existia no momento em que foi gerado, porque não foi o modelo quem descreveu os módulos.