Pular para o conteúdo

Landing Pages — Meta Portal (meta.koder.dev)

landing-pages specs/landing-pages/meta.kmd

Estrutura, IA, pipeline de render, deploy e governança do portal público de documentação da Koder Stack em `meta.koder.dev`. Renderiza o conteúdo de `meta/docs/{stack,ia,cryptography,blockchain}/` como HTML estático navegável, com nav hierárquico por tipo de documento (RFC, spec, policy, registry, module, runbook, compendium), search client-side, theme light/dark, i18n EN/PT. Distinto de landings de produto, área, sector, institucional, catálogo, packages e specs — é um **portal-indexador de documentos**, não uma landing-vendedor.

Quando esta spec se aplica

Triggers primários

Todos os triggers

Corpo da especificação

Landing Pages — Meta Portal (meta.koder.dev)

1. Visão Geral

meta.koder.dev é o portal público de documentação da Koder Stack. Renderiza o conteúdo de meta/docs/ do monorepo como site estático navegável.

Não é uma landing de produto, marca, área ou spec individual — é um indexador de documentos. Tem nav hierárquico por tipo (RFC, spec, policy, registry, module, runbook, compendium), uma página por arquivo fonte, search client-side e cross-links resolvidos.

Distinto de:

  • specs/landing-pages/products.kmd — landings de um produto
  • specs/landing-pages/specs.kmd — landing de uma spec/formato (KVG, KPKG)
  • specs/landing-pages/areas.kmd — landing de Área L2 (foundation, ai, …)
  • specs/landing-pages/institutional.kmd — landings sobre a Koder (www, company)
  • specs/landing-pages/catalog.kmd — Hub catalog (hub.koder.dev)
  • specs/landing-pages/packages.kmd — página individual de pacote no Hub

Histórico canônico (2026-05-24):

  1. ~10 landings de Área (meta/sites/areas/*/), meta/sites/brand/ e meta/sites/_ecosystem_gen.py referenciaram docs.koder.dev desde 2026-Q2 como "Public documentation" (sempre dead link).
  2. Sessão de 2026-05-24 turn 3: owner pediu site pro conteúdo de meta/. Pragmaticamente decidido construir docs.koder.dev reusando o nome já referenciado. Implementação concluída em 5 turns (spec, renderer, vhost, audit, deploy paralelo) — portal shipped com 652 docs.
  3. Mesma sessão turn 12: owner observou que o nome docs. está semanticamente errado pro conteúdo (RFCs/specs/policies/registries = "meta", não "docs do usuário"). Decisão revertida: canonical é meta.koder.dev, mirror direto do L1 domain meta/ do monorepo.
  4. Cutover atomic agendado (Phase B) quando infra/net/jet liberar o lock atual (jet#160 Phase 1) — ver jet#NNN follow-up ticket no umbrella META-DOCS-131. docs.koder.dev → 301 → meta.koder.dev, landings editorialmente atualizadas, per-product docs ficam em <product>.koder.dev/docs/ quando os produtos shipam.

2. Escopo de Conteúdo

2.1 Em escopo (público, vai pro portal)

FonteTipoSub-path no portal
meta/docs/stack/rfcs/RFCs/rfcs/
meta/docs/stack/specs/Specs normativas/specs/
meta/docs/stack/policies/Policies comportamentais/policies/
meta/docs/stack/registries/Registries (fontes de verdade)/registries/
meta/docs/stack/modules/Module catalog/modules/
meta/docs/stack/runbooks/Runbooks operacionais públicos/runbooks/
meta/docs/stack/releases/Release notes consolidadas/releases/
meta/docs/ai/compendium-ai/Compendium de IA/compendiums/ia/
meta/docs/cryptography/compendium/Compendium de criptografia/compendiums/cryptography/
meta/docs/blockchain/compendium/Compendium de blockchain/compendiums/blockchain/

2.2 Fora de escopo (NUNCA publicar)

  • meta/context/ — runbooks internos, hooks de IA, credenciais, notices, recaps. Privado por contrato.
  • meta/brand/ — assets de marca (vivem em brand.koder.dev)
  • meta/sites/ — landings (cada uma tem seu próprio domínio)
  • Qualquer arquivo com frontmatter visibility: internal ou audit: private (ver §6.3)
  • meta/docs/stack/backlog/ — tickets de trabalho interno

2.3 Avaliação per-diretório (decidir caso a caso)

  • meta/docs/platform/ — possivelmente público; auditar conteúdo
  • meta/docs/trust/ — possivelmente público; auditar conteúdo
  • meta/docs/flow-releases/ — público se substituir flow.koder.dev/releases; senão, manter privado
  • meta/docs/company/ — documentação de empresa/negócio pt-BR (marca/INPI, datacenter, cloud, projetos, processos, sessões). Mantida no monorepo (decisão de owner: tudo num repo); renomeada de meta/docs/docs/ + .kd.kmd (stack-docs#157).
  • meta/docs/stack/diagrams/, presentation/, build/ — públicos como assets/anexos, não páginas próprias

3. URL Canônica

Padrão: https://meta.koder.dev/<category>/<path>/

  • <category> ∈ { rfcs, specs, policies, registries, modules, runbooks, releases, compendiums }
  • <path> espelha 1:1 a estrutura de subdir na fonte, com .kmd/.md removidos e nomes preservados
  • Trailing slash sempre presente (URLs com slash final → index.html do diretório virtual)
  • Casos especiais:
    • Homepage: https://meta.koder.dev/ (não /index.html)
    • Search: https://meta.koder.dev/search/?q=<query> (client-side)
    • Sitemap: https://meta.koder.dev/sitemap.xml
    • 404: https://meta.koder.dev/404.html

Exemplos:

FonteURL
meta/docs/stack/rfcs/stack-RFC-001-kdb-as-unified-data-plane.kmd/rfcs/stack-RFC-001-kdb-as-unified-data-plane/
meta/docs/stack/specs/auth/oauth-flow.kmd/specs/auth/oauth-flow/
meta/docs/stack/policies/hyperscale-first.kmd/policies/hyperscale-first/
meta/docs/stack/registries/stack-manifest.kmd/registries/component-names/
meta/docs/ai/compendium-ai/01-models.md/compendiums/ia/01-models/

Regra de imutabilidade: URLs uma vez publicadas não mudam. Renomes em meta/docs/ precisam de redirect 301 em /redirects.toml consumido pelo Jet (#131.5 audit).

4. Estrutura do Arquivo de Site

meta/sites/meta/
├── _assets/
│   ├── meta.css            ← Estilos do portal (tokens, nav, sidebar, TOC, footer)
│   ├── meta-search.js      ← Busca da rota /search/ (padrão KDS, vanilla)
│   ├── graph.css / graph.js / graph3d.js  ← Graph explorer (home, §5.5)
│   ├── kvg.wasm / wasm_exec.js            ← gerados (META-DOCS-150.8, gitignored)
│   ├── fonts/              ← Inter + JetBrains Mono woff2 self-hosted
│   ├── search-index.json   ← Gerado pelo renderer (gitignored)
│   ├── icon.svg
│   └── og-image-default.png
├── _data/stack-graph.json  ← Gerado por koder-stack-graph
├── index.html              ← Gerado: homepage
├── <category>/             ← Gerado per category
│   ├── index.html
│   └── <doc-slug>/
│       └── index.html
├── search/index.html       ← Gerado
├── sitemap.xml             ← Gerado
├── robots.txt              ← Gerado (env-aware, §14)
└── 404.html                ← Gerado

Todos os HTMLs servidos são gerados pelo renderer (#131.2); os templates vivem embedded no renderer (products/dev/koder-tools/internal/docsrender/templates/*.html.tpl), não em _layouts/ no site. O diretório _assets/ (exceto o que for gerado) é commitado no monorepo; o resto (<category>/, search/, sitemap.xml, robots.txt, 404.html, index.html, search-index.json, kvg.wasm, wasm_exec.js) é build artifact — gitignored, regenerado a cada build (per policies/hyperscale-first.kmd).

5. Information Architecture

5.1 Homepage (/) — Graph Explorer

A home é o grafo-explorador da Koder Stack (v0.3): um único painel full-bleed que renderiza, em KVG, um sunburst zoomable de todos os componentes e objetos do monorepo, e deixa o usuário navegar pela hierarquia (RFC-003: L1 Domínio → L2 Área → L3 Sector → folha). O nó-foco é o miolo; seus descendentes irradiam como anéis concêntricos (span angular ∝ nº de folhas). Contrato normativo completo na §5.5. Estrutura visível:

  1. Painel único do sunburst (ocupa a viewport) — anéis concêntricos KVG; sem chrome persistente lateral.
  2. Overlay de busca no topo (alinhada ao componente de busca do Koder Design — ver §5.4): filtra/realça nós e dá fly-to no setor.
  3. Botões voltar/avançar de navegação por nível (history de zoom).
  4. Card flutuante ao clicar num setor-folha (objeto concreto) — nome, descrição e links de referência (Source/Landing/Module/Spec). Some o "right sidebar"; o card é a view transiente do nó. Posicionamento: abertura por clique de ponteiro ancora o card com a borda inferior ~12px acima do ponteiro, centrado nele (clamp na viewport; flip pra baixo quando não há espaço acima); aberturas sem ponteiro (busca/fly-to, teclado, breadcrumb) usam o dock superior-direito, e mobile (≤640px) o bottom-sheet. Hover hint (dois níveis): em ponteiros finos (hover: hover + pointer: fine), pairar sobre um elemento mostra um tooltip leve — nome + tipo + 1 linha de descrição, sem links, delay ~220ms, pointer-events: none, segue o ponteiro, some em drag/clique/saída — complementando o card completo, que permanece exclusivo do clique. Desligado em touch/coarse.

Fallback sem-JS / SEO / a11y (obrigatório). Abaixo do grafo (e como conteúdo único quando JS está desligado ou o leitor é um crawler/leitor de tela), renderizar a home estática clássica — crawlable e navegável por teclado:

  • Hero: eyebrow "The Koder Stack", H1 "Documentation" (palavra em accent gradient), lead de 2 frases, 2 CTAs ("Browse RFCs" / "Browse Specs").
  • Category grid — 8 cards (uma por categoria de §2.1) com ícone + título + contagem; card clicável → /<category>/.
  • Recently updated — 10 últimas modificações (de git log no build).
  • Featured — opcional, curado (RFC-001, headless-first.kmd, …).
  • Footer dark (idêntico a institutional.kmd §2.8).

O grafo é uma camada de progressive enhancement sobre esse conteúdo estático: o portal nunca depende de WASM/JS para ser indexável ou acessível (§13, §14).

5.2 Category listing (/<category>/)

Estrutura:

  1. Nav
  2. Breadcrumb: Home / <Category>
  3. Header: nome da categoria + descrição de 1 linha + total
  4. Filtros:
    • Por sub-area (specs: auth/, i18n/, themes/, …)
    • Por status (RFCs: draft / accepted / superseded; specs: pre-normative / normative / draft)
    • Por data (modified last 7d / 30d / 90d / all)
  5. Lista de docs — tabela ou cards:
    • Nome + sub-path + 1-line summary (do frontmatter summary:)
    • Status badge
    • Last-modified date
    • Audit status se aplicável
  6. Footer

5.3 Document page (/<category>/<slug>/)

Estrutura (layout 3 colunas em desktop, stacked em mobile):

  1. Nav
  2. Left sidebar (sticky, scroll-independente):
    • Navegação da categoria atual (árvore expandida no doc atual)
  3. Main content:
    • Breadcrumb
    • Frontmatter rendered como badge bar (status, version, mandatory, audit date)
    • H1: name: ou title: do frontmatter
    • Render do body markdown/kmd (ver §6)
    • Footer da página: "Edit this page" (link pro Flow se público) + prev/next (mesma categoria) + "Last updated · commit "
  4. Right sidebar (TOC):
    • Lista de headings H2/H3 do doc
    • Scroll-spy: destaca seção atual
  5. Footer global

A busca reusa o componente de busca do Koder Design (KDS — tools/design-gen/assets/js/search.js + index gerado em Go), não uma implementação própria (reuse-first / D10). Vanilla JS, self-hosted, sem dependência externa; index client-side (título + frontmatter + headings, não full-text — mantém <500KB).

Dois pontos de entrada, mesmo index:

  1. Overlay no grafo-explorador (home) — campo no topo do painel; tecla / abre; filtra/realça nós do grafo e dá fly-to na bolha correspondente (além de listar docs).
  2. Rota /search/?q=… — resultados agrupados por categoria, highlight do termo, para deep-link e fallback sem-JS.

Com 2 consumidores (KDS + meta portal), copiar o padrão é aceitável pela regra-dos-3 (stack-principles §1). Nomear extração pra engines/sdk quando surgir o 3º consumidor.

5.5 Graph Explorer — modelo de dados & interação

Decisões aprovadas (2026-06-04): painel único · containment-only · card overlay na folha · history+deep-link. Modelo visual revisado para sunburst (2026-06-06): o force-graph original (sim-física + bolhas) resolveu o PoC mas ficou visualmente ruim (nuvem de pontos ilegível com 291 folhas); trocado por um sunburst zoomable — mesma chrome (busca/card/histórico/fallback), só a camada de layout+render+hit-test mudou. Comparados também treemap/icicle/circle-packing (protótipo em meta/sites/meta/graph-prototypes/); sunburst é o default. Galeria de navegadores (#153, 2026-06-06): o home virou um seletor de 7 modelos de navegação hierárquica, todos sobre o mesmo dado + chrome (busca/card/histórico/deep-link/fallback), escolhidos num <select> no topbar e deep-linkáveis pelo #hash (#<modelo>[:<id>]):

  • Canvas (KVG): sunburst (default) · treemap · icicle · circlepack (circle packing aninhado, enclose de Welzl) · radialtree · graph (force-directed — rede completa: TODOS os nós (root+domínios+ áreas+sectors) + arestas de contenção sempre visíveis, seed de árvore radial
    • sim que assenta e para (idle estático, barato p/ ~333 nós); arrasta qualquer nó (root incl.); clique = o nó vai pro CENTRO (fixado lá) + ampliado (raio ~2× + borda branca) em evidência, e realça linhagem (ancestrais) + subárvore (vívidos) enquanto o resto atenua e afasta — nada é escondido; clique no vazio devolve o root ao centro). Estilos de nó (sub-bar, react-force-graph parity 2D — KVG RFC-004): circle · shape (polígono por domínio) · text (nó = rótulo) · html (card) + toggle de rótulos de aresta. A trilha 3D (text/shape/link-label/focus em 3D + large-graph GPU) é gated no Solid (RFC-002) + kvg#096.
  • DOM: tree (árvore colapsável estilo outline/explorer) · columns (Miller/Finder).

Arquitetura: cada modelo canvas é um trio {layout, draw, pick}; cada modelo DOM é um par {render, reveal}; o registry MODELS em graph.js despacha.

Encoding de contenção + cap justo (2026-06-10, owner): nas views network (2D graph e force3d), tamanho codifica contenção — bolas com filhos ("que explodem") são maiores que folhas, com crescimento log pela contagem de filhos diretos (containBoost, cap ~1.6×). O orçamento F3CAP=80 do force3d é preenchido por BFS round-robin por pai (toda área ganha o 1º filho antes de qualquer área ganhar o 2º — nenhuma área pequena é silenciosamente excluída) e a poda é honesta: nós com filhos fora do orçamento exibem (+N) junto à contagem (princípio "no silent caps"). O full-graph 3D continua gated no GPU (kvg#096).

Edge bundling shipou (#154, 2026-06-07) — 9º modelo bundle. As arestas de dependência são mineradas do código real (imports koder.dev/<dom>/<área>/ <sector> nos .go), NÃO autoradas à mão (D1: nada de grafo que mente). O gerador koder-stack-graph emite edges: [{source,target,weight}] no root do stack-graph.json (só entre sectors que existem; sem self-loop). O modelo bundle faz layout radial + roteia cada aresta pelo caminho source→LCA→target e a β-agrupa (Holten, β=0.85) em spline Catmull-Rom. Escopo atual: imports Go (o grosso da Stack; ~52 arestas). Futuro: estender o extrator a path-deps Dart (pubspec) + deps Rust (Cargo) pra cobrir os componentes não-Go. Matriz de adjacência segue como opção futura. Voronoi treemap descartado (layout iterativo caro, ganho marginal vs. treemap/circlepack).

a) Fonte de dados — o filesystem é a espinha. O grafo é gerado por products/dev/koder-tools/cmd/koder-stack-graph (build-time), que percorre a árvore do monorepo nos níveis RFC-003 (L1 Domínio → L2 Área → L3 Sector → folha) e emite JSON aninhado em meta/sites/meta/_data/stack-graph.json.

  • Containment vem do filesystem — única codificação completa e confiável da hierarquia. Os 276 koder.toml são majoritariamente auto-stubs finos (prefix/compat); o registry stack-manifest.kmd é seed.
  • Enriquecimento é best-effort (registry → display name/slug; module deep-dive summary: → descrição; existência de landing/, specs/<slug>/, modules/<slug>.md → links). Um link é emitido quando o alvo existe no disco — o grafo nunca afirma uma referência que não tem (D1/D6). Descrições crescem conforme registry/módulos amadurecem.
  • Arestas de dependência (consumed_by) são camada futura, gated por completude de dado — desenhar de dado esparso produziria um grafo que mente. Quando os koder.toml tiverem consumed_by populado, entram como toggle opcional sobre o containment, sem retrabalhar a base.

b) Render — layout sunburst em JS, KVG rasteriza one-shot. O layout (spans angulares ∝ nº de folhas, anéis por profundidade relativa ao foco) é calculado em JS; o KVG desenha a cena via kvgRender(source,…) — cada setor anular é um path tesselado em segmentos de reta (fan de L, não flags de arco — previsível independente da convenção de eixo). Render é one-shot por nível de foco (não há loop de física: o sunburst é estático até o próximo drill). Como o JS é dono da geometria, o hit-test é polar (raio→anel, ângulo→setor) no mesmo lugar. KVG renderiza y-up; a única conversão kvg_y = H − y acontece na emissão do path (disciplina herdada do force-graph, 150.4).

c) Interação.

  • Estado inicial: foco = raiz — a Stack inteira num sunburst de 3 anéis (Domínio → Área → Sector), miolo rotulado "Koder Stack · N leaves".
  • Drill-down: clicar num setor não-folha → ele vira o novo foco (zoom): o miolo passa a ser aquele nó e seus descendentes re-irradiam.
  • Zoom-out: clicar no miolo (centre hole) volta ao foco-pai.
  • Folha (objeto concreto): clique → card flutuante com nome, descrição e refs (Source/Landing/Module/Spec) do nó. Sem painel fixo.
  • Hover: realça o caminho no #status (nome · id) + cursor; sem re-rasterizar (custo de re-parse de ~300 setores é alto demais por hover).
  • Histórico: botões voltar/avançar percorrem focos já visitados (pilha de estados; espelhada no #hash da URL para deep-link e back/forward do browser).
  • Rótulos: HTML overlay, só em setores grandes o bastante (arco × banda acima do limiar); folhas finas se leem por hover/busca/drill (norma sunburst). Evita o amontoado ilegível de 291 rótulos simultâneos.

d) Disciplina de performance (D4). Renderizar apenas o nível ativo (≤ ~100 nós em tela), nunca os 291 nós de uma vez — o KVG rasteriza no CPU (software renderer), então o nº de nós por frame é o orçamento. O WASM do KVG é lazy-loaded (IntersectionObserver) e versionado para cache-bust. Se o runtime do KVG só expuser "parse string → raster", abrir um entrypoint fino em engines/lang/kvg (render de cena pré-parseada com overrides de transform por nó) para evitar reparse por frame — é o risco central que o PoC valida.

6. Render Pipeline

Implementação: dev/koder-tools/cmd/koder-docs-render/ (Go) — ticket META-DOCS-131.2. Esta seção é o contrato do renderer.

6.1 Input

  • Walk de meta/docs/{stack,ia,cryptography,blockchain}/ (e diretórios promovidos por §2.3)
  • Aceita .kmd e .md. Frontmatter YAML obrigatório (sem frontmatter → log warning + skip ou render como "untyped doc")
  • Respeita .docsignore (lista de globs) em cada diretório raiz

6.2 Parser

  • Markdown: reutilizar parser canônico da Stack (mesmo de kmd render); fallback goldmark se kmd parser indisponível
  • Frontmatter: yaml.v3
  • Cross-links: resolver [texto](relative-path.kmd) → URL canônica do portal (ver §3); [[wiki-link]] → busca por name: no frontmatter de todos os docs indexados, falha emite warning

6.3 Visibility filter

Skip arquivo se:

  • Frontmatter contém visibility: internal
  • Frontmatter contém audit: private
  • Path bate com qualquer entrada de meta/sites/docs/_excluded.toml (escape hatch curado)

6.4 Output

  • meta/sites/docs/<category>/<slug>/index.html per doc
  • meta/sites/docs/<category>/index.html per categoria
  • meta/sites/docs/index.html homepage
  • meta/sites/docs/search/index.html + _assets/search-index.json
  • meta/sites/docs/sitemap.xml (Google + IndexNow)
  • meta/sites/docs/404.html
  • meta/sites/docs/redirects.toml (Jet-consumível, se houver renames detectados)

6.5 Metadata extraída do frontmatter

Frontmatter keyOnde aparece no HTML
name / title<title>, <h1>, og:title
summary<meta description>, og:description, listing card
statusbadge bar + filter em listing
typecategory routing
mandatory: true"Mandatory" badge no header
triggerssection "When this applies" (collapsible)
referencesrendered como linked list no rodapé do doc
auditbadge "Audit: "
facetstags clicáveis

6.6 Hyperscale gate

Per policies/hyperscale-first.kmd: renderer DEVE escalar a 10× o volume atual sem mudança arquitetural (~5k docs). Decisões:

  • HTML estático puro, sem SSR/CSR runtime
  • Build incremental: hash do arquivo fonte + frontmatter; só re-renderiza o que mudou
  • Search index gerado em pass único, paralelizado por worker
  • Sem JS bundlers — docs.js é vanilla JS direto

7. Head — Meta Tags

Template aplicado a TODA página:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>{{page.title}} — Koder Meta</title>
  <meta name="description" content="{{page.summary}}">
  <meta name="theme-color" content="#0b1120">
  <link rel="canonical" href="https://meta.koder.dev{{page.url}}">

  <!-- Open Graph -->
  <meta property="og:title" content="{{page.title}} — Koder Meta">
  <meta property="og:description" content="{{page.summary}}">
  <meta property="og:type" content="article">
  <meta property="og:url" content="https://meta.koder.dev{{page.url}}">
  <meta property="og:image" content="https://meta.koder.dev{{page.og_image | default('/_assets/og-image-default.png')}}">
  <meta property="og:image:width" content="1200">
  <meta property="og:image:height" content="630">

  <!-- Twitter -->
  <meta name="twitter:card" content="summary_large_image">
  <meta name="twitter:title" content="{{page.title}} — Koder Meta">
  <meta name="twitter:description" content="{{page.summary}}">
  <meta name="twitter:image" content="https://meta.koder.dev{{page.og_image | default('/_assets/og-image-default.png')}}">

  <link rel="icon" type="image/svg+xml" href="/_assets/icon.svg">
  <script>
    (function(){
      const s = localStorage.getItem('theme');
      const dark = s ? s === 'dark' : matchMedia('(prefers-color-scheme:dark)').matches;
      if (dark) document.documentElement.setAttribute('data-theme','dark');
    })();
  </script>
  <link rel="stylesheet" href="/_assets/meta.css">
</head>
  • <title> max 60 chars; trunca {{page.title}} se preciso (renderer warn)
  • og:type = "article" em doc pages, "website" em homepage/category listings
  • og:image per page é opcional; default = og-image-default.png (Koder Meta branded)

8. Nav

Idêntica ao padrão de outras landings (ecosystem.css):

<nav>
  <div class="nav-inner">
    <a class="nav-brand" href="/"><img src="/_assets/icon.svg" alt="">Koder Meta</a>
    <button aria-label="Menu" class="nav-toggle" onclick="...">☰</button>
    <div class="nav-links">
      <a href="/rfcs/">RFCs</a>
      <a href="/specs/">Specs</a>
      <a href="/policies/">Policies</a>
      <a href="/registries/">Registries</a>
      <a href="/compendiums/">Compendiums</a>
      <button id="lang-btn" onclick="toggleLang()">EN</button>
      <button class="theme-toggle" onclick="toggleTheme()">…</button>
    </div>
  </div>
</nav>

Versão sticky com backdrop-filter: blur(16px). Ordem dos links é canônica (RFCs antes de Specs antes de Policies, por densidade de referência).

9. CSS — Tokens

O CSS do portal é meta/sites/meta/_assets/meta.css — standalone, escrito sobre os mesmos tokens visuais Verge das demais landings. (Reuso direto de meta/sites/ecosystem.css foi avaliado e adiado: o chrome de portal-de-docs diverge do de landing-vendedor, e a direção de longo prazo de tokens compartilhados é o KDS — ver §18.6.) Cobre:

  • Sidebar tree (left + right TOC)
  • Code blocks com syntax highlighting (Prism vendored ou highlight.js)
  • Tables responsivas (scroll-x em mobile)
  • Badge bar (status, mandatory, audit)
  • Search results (/search/ + overlay do grafo)
  • Footer dark multi-coluna (padrão institutional.kmd §8)

Theme: light/dark per specs/themes/light-dark.kmd. Toggle JS idêntico ao das outras landings.

Tokens visuais: per specs/themes/verge.kmd §R9 — o portal consome o Verge Web export (kds.koder.dev/tokens/verge-web.css, vendored em _assets/tokens/), mapeando as vars locais em var(--kw-*). (Verge default R4 é o preset de apps; propriedades web usam Verge Web.)

Fontes: per specs/fonts/typography.kmd:

  • Sans: Inter (self-hosted woff2 via /_assets/fonts/inter.woff2)
  • Mono: JetBrains Mono (self-hosted)
  • Proibido Google Fonts / jsDelivr / unpkg

10. JavaScript — Comportamentos

_assets/docs.js (vanilla, ~10KB minified):

  • Theme toggle (idêntico a outras landings)
  • Language toggle EN ↔ PT-BR (i18n; conteúdo é EN por policies/language.kmd, mas chrome traduzível)
  • Mobile nav hamburger
  • TOC scroll-spy
  • Sidebar tree expand/collapse + persistência em localStorage
  • Search input (homepage + página search): query lunr index, renderiza resultados ou redireciona pra /search/?q=…
  • Copy-to-clipboard em code blocks (button hover)
  • Anchor links em H2/H3 (hover icon → copy URL com fragment)

lunr.min.js carregado só na rota /search/ (lazy) — não penaliza páginas de leitura.

A spec mandata que ao primeiro deploy em produção, o renderer gere um relatório audit/dead-links-<date>.md listando todas as refs a meta.koder.dev/* no monorepo (especialmente em meta/sites/areas/*/index.html, meta/sites/brand/index.html, meta/sites/_ecosystem_gen.py) e marque cada uma como:

  • resolves — path canônico existe
  • 🔁 redirects-to: <new-path> — entrada em redirects.toml necessária
  • broken — bloqueia release até #131.5 decidir destino

CI gate: build do portal falha se houver broken sem resolução.

12. i18n

  • Conteúdo (doc body) NÃO é traduzido — é EN por policies/language.kmd
  • Chrome (nav, footer, breadcrumbs, status badges, "Recently updated") É traduzido EN ↔ PT-BR
  • Toggle EN/PT no nav (mesmo padrão das outras landings)
  • Default por geo / browser language; persiste em localStorage
  • T1-T6 da spec specs/i18n/contract.kmd aplicam

13. Acessibilidade

  • WCAG 2.1 AA
  • Skip-to-content link no topo
  • Sidebar tree navegável por teclado (arrow keys)
  • Theme toggle com aria-label + aria-pressed
  • TOC com aria-current="location" no item ativo
  • Code blocks com role="region" e aria-label="Code sample"
  • Tabela de conformance per page em audit periódico (#131 followup)

14. SEO

  • sitemap.xml gerado no build, submetido ao Google Search Console
  • Per-page meta tags (§7)
  • robots.txt na raiz (/robots.txt — crawlers só leem a raiz) — Allow: / + Sitemap: em prd, Disallow: / em stg/dev (per policies/environments.kmd); gerado pelo renderer via --env
  • IndexNow ping no deploy de produção (lista de URLs alteradas)
  • Schema.org TechArticle JSON-LD em doc pages

15. Deploy

Per policies/web-server.kmd (Jet only) + policies/environments.kmd:

AmbienteHostBranch sourcenoindex
dev.meta.koder.devs.forgedevtrue
stg.meta.koder.devs.forgestagingtrue
meta.koder.dev (prd)s.forgemainfalse

DNS A record provisionado via ClouDNS (meta/context/infrastructure/dns.md). TLS via Jet autocert (LE/ACME).

Pipeline de deploy:

  1. CI roda koder-docs-render --env <env> no monorepo
  2. Output em meta/sites/docs/ (sub-paths gitignored)
  3. rsync pro /var/www/meta.koder.dev/ no s.forge
  4. Jet reload (zero-downtime)
  5. Smoke: curl -I https://meta.koder.dev/ → 200 + TTFB <500ms
  6. Lighthouse: a11y ≥ 95, perf ≥ 90, SEO = 100

16. Performance Targets

MétricaTarget
TTFB<200ms (CDN/edge se necessário, futuro)
LCP<1.5s
CLS<0.05
Page weight (homepage)<100KB (sem search index)
Page weight (doc page)<150KB (sem code samples grandes)
Search index<500KB
Build time (full)<60s pra ~500 docs
Build time (incremental)<5s

17. Governança

  • Owner: Koder Stack (Rodrigo)
  • Spec maturity gate: v1.0 normative quando #131.2/.3/.4/.5/.6/.7 do umbrella META-DOCS-131 estiverem done + lighthouse scores atingidos + dead-link audit limpa em 2 builds consecutivos
  • Versioning: a spec do portal segue semver per specs/landing-pages/specs.kmd §6
  • Audit: koder-spec-audit landing-pages --slug docs (a ser implementado pelo audit harness em dev/koder-tools/)

18. Decisões Em Aberto

  1. Search backend long-term: lunr.js (v0) → kdb-search quando rfcs/stack-RFC-001-kdb-as-unified-data-plane.kmd entregar layer de busca. Decisão revisitada no kickoff de #131.2.
  2. Versionamento de docs: always-latest (HEAD) vs por tag (v1.0, v1.1). Default v0: always-latest; versões anteriores acessíveis via Flow se requestado. Revisitar quando portal tiver ≥1 ano de tráfego.
  3. OG image generation per page: usar kicon generate --targets og per doc, ou template estático compartilhado por categoria? Default v0: estático por categoria (8 imagens curadas).
  4. Comments / feedback: deixar fora do v0. Avaliar Giscus (GitHub-backed) vs solução self-hosted em v2.
  5. Versioned dark mode: usar Verge default ou expor preset switcher como no KDS? Default v0: Verge default, sem switcher.
  6. Base CSS compartilhada — RESOLVIDO 2026-06-10 (c3, owner-aprovado via /k-arch→/k-go): a paleta web virou o preset Verge Web (verge.kmd §R9, export kds.koder.dev/tokens/verge-web.css) e o portal consome o export vendored com zero mudança visual (META-DOCS-171 done). Migração das demais landings: follow-up.

19. Referência Cruzada

  • specs/landing-pages/institutional.kmd — padrão de footer dark + nav
  • specs/landing-pages/specs.kmd — padrão de doc-as-page
  • specs/kmd/ — parser canônico
  • policies/hyperscale-first.kmd — gate de build incremental
  • policies/reuse-first.kmd — reuso de ecosystem.css
  • meta/sites/_ecosystem_gen.py — gerador atual de landings de Área (referencia o dead link meta.koder.dev/ecosystem)
  • META-DOCS-131 — umbrella ticket do programa
  • JET-161 — vhost ticket

20. Estado Atual (2026-05-24)

Sub-streamTicketStatus
Landing spec#131.1✅ esta spec (v0.1.0 pre-normative)
Renderer pipeline#131.2pending
Templates + nav#131.3pending
Vhost + TLSJET-161pending (blocked on #131.1+#131.2)
Dead-link audit + fix#131.5pending
OG image + brand assets#131.6pending
i18n chrome#131.7pending

Referências