Headless-first — UI fully automatable / testable without a human display
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 user
facing. 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 UIO 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-goldenem 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.forgerunner)
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:
- Headless test é atualizado *rimeiro*pra refletir a spec nova (RED).
- Código é alterado pra fazer o teste passar (GREEN).
- 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 per
stack (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(.kdtrunner)
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/overviewinfra/linux/distro— installer-gui + installer CLIinfra/linux/keyboard— Android IME- Todos os
products/horizontalapp/{mobile,desktop,tv,web,tui} - Todos os `products/dev