Headless-first — UI fully automatable / testable without a human display

mandatory

Toda UI da Koder Stack (Flutter, GTK/Adwaita, Wayland client, compositor, TUI, web, installer, initramfs) deve nascer com um caminho headless completo: input programático + observação determinística + golden capture, exercitando o MESMO binário que o usuário vê (sem mocks no caminho de render). "Funciona na minha tela" não é estado válido pra promote-to-prod — só "passa headless E na tela". Esta policy NÃO é o RFC de arquitetura — esse é `stack-RFC-005-full-headless-testability` (3 camadas SDK, fidelity contract, `.kdt` DSL, etc.). Aqui ficam as regras comportamentais enforce-ables; o RFC define o como técnico.

Policy — Headless-first

Toda UI Koder é fully automatable + testable em modo headless, reproduzindo as mesmas observações que a sessão de usuário produz.

Escopo de plataformas

Esta policy aplica a *odas*as superfícies de UI Koder em *odos*os sistemas operacionais que a Stack targeta:

Categoria Sistemas alvo Stack técnica Binding Layer 2
*obile* Android, iOS Flutter (Dart) koder_kit/test
*obile native* Android (Kotlin/InputMethodService — ex.: Koder Keyboard) Kotlin + AndroidX koder_kit_android/test
*esktop* Linux, macOS, Windows Flutter (Dart) koder_kit/test
*esktop native (Linux)* Linux GTK4/Adwaita (C) koder_kit_gtk/test
*esktop native (Wayland clients)* Linux wlroots-client (C) koder_kit_wayland/test
*V* Tizen, WebOS JS/React koder_kit_tv/test
*eb* qualquer browser-capable (LinuxmacOSWindowsAndroidiOS/Chrome OS) Flutter web OU templ+HTMX koder_web_test
*UI* LinuxmacOSWindows/BSD Bubble Tea (Go) koder_kit_tui/test
*ompositor* Linux koder-x (C, wlroots) koder_kit_compositor/test
*nitramfs / pre-boot* Linux CbusyboxGRUB koder-test-iso serial + screendump (Layer 3)

Componentes que adicionam novo target de plataforma sem o binding correspondente em engines/sdk/ violam *8 (SDK reuse)*— abrir o binding antes da feature shippar é parte do trabalho, não follow-up.

R1 — Fidelity contract

O teste headless executa * mesmo binário*que o usuário roda. Sem mocks no caminho de render, layout ou input dispatch. Substituições permitidas SOMENTE em bordas de I/O (clock, network, filesystem, RNG) via fakes injetados pelo SDK (koder_test_clock, koder_test_net, koder_test_fs, koder_test_rng).

*hy:*o motivo pra esta policy existir é evitar a regressão clássica em que "tests passam mas produção quebra" — quando o caminho testado divergiu do caminho userfacing. Headlessfirst sem fidelity contract é só ilusão de cobertura.

R2 — Input programmatic

Toda UI expõe uma API de injection de eventos:

Stack Mecanismo
Flutter WidgetTester (já existe no Flutter SDK)
GTK/Adwaita koder_kit_gtk/test (R3 do RFC-020)
Wayland client koder_kit_wayland/test via wlr-virtual-pointer-v1 + wlr-virtual-keyboard-v1
koder-x compositor koder_kit_compositor/test (output simulation + per-surface input feed)
TUI Go PTY + koder_kit_tui/test
Web Chrome headless + CDP via koder_web_test

Componente que NÃO expõe API de injection compatível bloqueia release até R8 (SDK reuse) ser cumprida.

R3 — Observation API

Toda UI expõe captura determinística:

capture(target) -> image     // pixel buffer
query(selector) -> json      // state tree (widget tree, scene tree, DOM)
events(filter) -> stream     // log estruturado de eventos UI

O retorno tem que ser *it-exact reproduzível*entre runs com mesma seedclockinput. Variação inter-run = test flake = blocker.

R4 — Golden corpus

Surfaces que renderizam pixels (todas exceto CLI/TUI pure-text) têm golden files versionados sob <componente>/tests/headless/golden/.

  • Formato: PNG ou WebP, alpha preservado, sem metadata variável.
  • Tolerance: ≤ 0.001 (pixel-diff fração) para layouts; 0.0 (exact) pra

    text rendering em fonts self-hosted (Inter/JetBrains Mono per specs/fonts/typography.kmd).

  • Atualização: flag explícita --update-golden em release engineering,

    nunca implícito. Commit que altera golden tem que justificar no message body.

R5 — Determinismo

Sources de não-determinismo são *roibidas*no caminho de render:

  • clock_gettime(CLOCK_MONOTONIC) direto pra animations / timers
  • random() sem seed em test mode
  • select() / epoll_wait() com timeouts derivados de wall-clock
  • ❌ Background workers sem sync barrier antes da capture

✅ Permitido: koder_test_clock::now() (FakeClock controlado pelo teste), koder_test_rng::next() (seeded), pump_frames(N) antes da capture (sync barrier).

Teste rodado *00 vezes seguidas em loop tem que dar resultado idêntico* Se varia, é flake = blocker.

R6 — CI-friendly por construção

Headless tests rodam em:

  • LXC Debian Trixie (build host, ex.: s.khost1.dev-linux-klinux)
  • VM QEMU headless via policies/test-host-isolation.kmd
  • Docker container CI (futuro: s.forge runner)

NUNCA exigem:

  • Monitor físico
  • X11 forwarding
  • Sessão gráfica logada
  • GPU dedicada (software renderer aceitável; aceleração é optimization

    separada)

Componente que requer qualquer um dos acima na suite headless = blocker.

R7 — Testasspec workflow

Quando a spec normativa muda:

  1. Headless test é atualizado *rimeiro*pra refletir a spec nova (RED).
  2. Código é alterado pra fazer o teste passar (GREEN).
  3. Commit é único, contendo (1) + (2). Mensagem cita a spec ratificada.

Sem TDD inverso (codar primeiro, testar depois) em código UI novo.

R8 — SDK reuse obrigatório (compõe com reuse-first)

Componentes consomem os SDKs de teste canônicos:

  • *amada 1*— engines/sdk/koder_test_compositor, koder_test_screencap, koder_test_input, koder_test_clock, koder_test_rng, koder_test_state
  • *amada 2*— bindings perstack (um por superfície × OStarget):
    • koder_kit/test — Flutter (crossplatform: AndroidiOSLinuxmacOSWindows/TVFlutter-web)
    • koder_kit_android/test — Native Android (Kotlin/InputMethodService — Koder Keyboard etc.)
    • koder_kit_gtk/test — GTK4/Adwaita (Linux desktop nativo)
    • koder_kit_wayland/test — Native Wayland clients (Linux)
    • koder_kit_compositor/test — Testing koder-x (Linux)
    • koder_kit_tv/test — Tizen + WebOS (JS/React)
    • koder_kit_tui/test — Bubble Tea (LinuxmacOSWindows/BSD)
    • koder_web_test — Web (qualquer browser-capable)
  • *amada 3*— orchestration: koder-test-iso, koder-test-stack, koder-test-dialog (.kdt runner)

Reimplementação inhouse de qualquer primitive da Camada 1 OU 2 = lint failure (/k-sdkify reverte) + ticket de remediação obrigatório antes da próxima release do componente.

R9 — Promotetoprod gate

Release engineering bloqueia release de componente UI se:

Critério Limiar
Headless test coverage de user-facing flows ≥ 80%
Golden files atualizados nos últimos 30 dias sim ou tolerância 0 nos últimos 30 dias (sem drift)
Flake rate (último mês de runs) ≤ 0.1% (1 em 1000)
Tempo total da suite headless ≤ 10 min em CI nominal

Registry percomponente em `registries/headlesscoverage.md` rastreia estado. Componente abaixo do limiar em qualquer linha = release-block com PR de remediação prioritário.

Componentes alvo (não-exaustivo)

Aplicação direta:

  • infra/linux/x — koder-x compositor (camada 1 — ele É o display)
  • infra/linux/kolide — paneldocklauncher/overview
  • infra/linux/distro — installer-gui + installer CLI
  • infra/linux/keyboard — Android IME
  • Todos os products/horizontalapp/{mobile,desktop,tv,web,tui}
  • Todos os `products/dev

Source: ../home/koder/dev/koder/meta/docs/stack/policies/headless-first.kmd