# Tech Spec — Layout do TUI estilo OpenCode

## Rastreabilidade
- Orquestração: Precisamos que o layout em si do TUI tenha uma experiência similar à do OpenCode
- Slug: `precisamos-que-o-layout-em-si-do-tui-tenha-uma-experiencia-similar-a-do-opencode`
- Documento: Especificação Técnica (techspec)
- Risco: **medium** (`00-risk-classification.md`, fator `external_dependency`)
- Código impactado: `src/tui/ui.rs`, `src/tui/app.rs`, `src/tui/event.rs`, `src/tui/mod.rs` + dois módulos novos (`theme.rs`, `palette.rs`)
- Subfluxo de design: `information-architecture`, `design-tokens`, `artifact-diagrams`
- Etapa anterior: `02-prd.md` (aprovado)
- Estado: draft — **aguardando checkpoint humano**

## Visão técnica
O TUI hoje segue um padrão limpo e testável: `event_loop` (em `mod.rs`) chama `ui::render(frame, app)` (funções puras de desenho) e roteia teclas via `map_key(key, text_mode) -> Action` (`event.rs`), aplicadas pelo reducer `App::update(Action) -> SideEffects` (`app.rs`). O domínio (etapas, checkpoints, persistência, providers) é independente da apresentação.

A estratégia é **preservar esse contrato** e introduzir a estética OpenCode em duas frentes aditivas:
1. **Camada de tema** (`theme.rs`): tokens semânticos + helpers de estilo + detecção de capacidade do terminal. Todas as funções de `ui.rs` passam a desenhar a partir do tema, em vez de cores literais e `Borders::ALL`.
2. **Camada de overlays** (`palette.rs` + estado `Overlay` em `app.rs`): command palette e painel de telemetria como modais flutuantes desenhados **por cima** do conteúdo, sem alterar o reducer de domínio — um comando do palette apenas resolve para uma `Action` já existente.

Princípio-chave (RN-01/RN-02): **overlays e tema são apresentação/entrada; nunca tocam `stage.rs::can_navigate` nem auto-aprovam checkpoints.**

## Arquitetura

### Módulos
```mermaid
flowchart TD
    mod[mod.rs event_loop] -->|map_key| event[event.rs Action]
    mod -->|render| ui[ui.rs draw puro]
    mod -->|update| app[app.rs App + reducer]
    app -->|estado Overlay/CommandPalette| palette[palette.rs novo]
    ui --> theme[theme.rs novo: tokens + helpers]
    ui --> palette
    app --> stage[stage.rs DOMÍNIO intacto]
    app --> runner[runner.rs DOMÍNIO intacto]
    app --> persist[persist.rs DOMÍNIO intacto]
```

### `theme.rs` (novo)
- `struct Theme` com tokens semânticos: `primary` (acento pêssego/laranja), `on_primary`, `text_strong`, `text_muted`, `success`, `warning`, `error`, `overlay_dim`, `section_header`.
- `enum ColorDepth { TrueColor, Ansi256, Ansi16 }` + `fn detect(colorterm: Option<&str>, term: Option<&str>) -> ColorDepth` (pura, testável; lê `COLORTERM`/`TERM`).
- `fn theme(depth: ColorDepth) -> Theme` — mapeia cada token para a melhor cor disponível, com fallback 16-cores (RN-04).
- Helpers de estilo reutilizados por `ui.rs`:
  - `selection_bar(width) -> Style` / linha de seleção full-width (RF-03).
  - `section_header(label) -> Line` (RF-07).
  - `label_value(label, value) -> Line` (par rótulo/valor minimalista).
  - `footer(hint, route) -> Line` (RF-08).
  - `centered_rect(pct_x, pct_y, area) -> Rect` e `overlay_dim(frame, area)` (RF-09).
  - `figlet_banner(text, depth) -> Vec<Line>` com versão reduzida para larguras pequenas (RF-04, R1).
- Sem estado mutável; recebe `area`/`depth` por parâmetro para continuar puro e testável com `TestBackend`.

### `palette.rs` (novo)
- `struct Command { id: &'static str, label: &'static str, shortcut: &'static str, action: Action, available_in: fn(&App) -> bool }`.
- `const COMMANDS: &[Command]` declarativo cobrindo todas as ações primárias (RF-05, RF-10): nova orquestração, abrir orquestração, trocar provider, abrir/executar etapa, aprovar, regenerar, editar, alternar telemetria, ajuda, sair.
- `fn filter(commands, query) -> Vec<usize>`: fuzzy match por subsequência case-insensitive, ordenado por qualidade do match (puro, testável).
- `struct CommandPalette { open: bool, query: String, index: usize }` com reducers puros (`open`, `close`, `type_char`, `backspace`, `move_up/down`, `selected(&App) -> Option<Action>`).

### Mudanças em `app.rs`
- Novo enum de sobreposição: `enum Overlay { None, CommandPalette, Telemetry }` (campo `app.overlay`).
- Campo `app.palette: CommandPalette`.
- `text_mode()` passa a retornar `true` também quando `overlay == CommandPalette` (para o filtro capturar caracteres).
- `update(action)`: **interceptação no topo** — se houver overlay aberto, despacha para `update_overlay(action)` antes da lógica de tela. Um comando selecionado resolve para `Action` e re-entra no `update` normal (reuso total do domínio).
- Telemetria: `Overlay::Telemetry` lê os mesmos dados já existentes (`session_usage`, `provider_selection`, `last_usage`) — nenhum cálculo novo, apenas realocação de exibição (RF-06).

### Mudanças em `event.rs`
- `Action` ganha: `OpenPalette`, `Telemetry`. Reuso de `Char`/`Backspace`/`Confirm`/`Up`/`Down`/`Back` para operar o palette.
- Bindings novos:
  - `Ctrl+K` → `OpenPalette` (global, funciona inclusive em text_mode).
  - `/` → `OpenPalette` quando **não** estiver em text_mode (telas de navegação).
  - `t` → `Telemetry` (toggle) nas telas densas (board/checkpoint/running).
- Nenhum binding existente é removido (RF-10); `map_key` permanece puro e ganha testes.

### Mudanças em `ui.rs`
- `render()` mantém o esqueleto header/body/footer, mas:
  1. todas as `Block::default().borders(Borders::ALL)` de **moldura de conteúdo** são removidas; bordas só em caixa de input e contorno fino do overlay (RF-02).
  2. seleção em listas vira barra full-width via helper do tema (RF-03).
  3. após desenhar a tela, se `app.overlay != None`, chama `overlay_dim` + render do modal centralizado (RF-09).
- Funções novas: `render_home` (figlet + lista de comandos + input + footer, RF-04), `render_command_palette` (RF-05/RF-07), `render_telemetry` (RF-06).
- Home: renderizada como landing quando não há orquestração ativa (banner figlet acima da lista/atalhos); não exige novo `Screen` — reduz churn e risco (R2). Alternativa registrada: `Screen::Home` dedicado caso a revisão prefira separação explícita.

## Contratos
- **Assinaturas de render preservadas**: `fn render_*(frame: &mut Frame, area: Rect, app: &App)`. Aditivo, sem breaking change.
- **`Action`**: aditivo (`OpenPalette`, `Telemetry`); variantes existentes inalteradas.
- **Reducer**: `App::update(Action) -> SideEffects` mantém assinatura; ganha ramo de overlay no topo.
- **Domínio intocado**: `stage.rs` (`can_navigate`, `current_stage`), `persist.rs`, `runner.rs`, `orchestration.rs`, schemas de `docs/<slug>/`.
- **Mapa de comandos ⇒ ações**: cada `Command.action` é uma `Action` já tratada pelo reducer (garante paridade RF-10 e regra RN-01).

## Dados
- **Sem mudanças de schema, persistência ou contrato externo.** Nenhuma alteração em `schemas/artifact-sections.json` nem em `traceability-map.yaml`.
- Estado novo é **efêmero em memória** (`Overlay`, `CommandPalette`), não persistido.
- Detecção de capacidade do terminal deriva de `COLORTERM`/`TERM` + largura do `Rect` no momento do desenho.

## Segurança
- Superfície local, sem rede e sem IA (`mod.rs` doc: "Sem IA, sem rede"). O overhaul não adiciona I/O externo.
- **Risco de bypass de gate**: o palette poderia, em tese, expor uma ação destrutiva. Mitigação: `Command.available_in(&App)` esconde/inabilita comandos inválidos no contexto, e a ação ainda passa pelas mesmas regras de domínio (`can_navigate`, recusa de overwrite). Nenhum comando do palette chama persistência diretamente.
- Sem novos segredos, sem novos sinks de entrada além do teclado já tratado.

## Performance
- Render continua via funções puras; sem custo perceptível. `palette::filter` é O(n·m) com n≈12 comandos — desprezível.
- `overlay_dim` repinta a área inteira uma vez por frame quando aberto — aceitável a 100ms de poll (`event_loop`).
- `figlet_banner` deve ser pré-computado/estático (sem alocação por frame); larguras pequenas usam a versão curta.

## Observabilidade
- Reaproveitar `tui-events.jsonl` (helper `log_event` em `mod.rs`) e o padrão `SideEffects`.
- Novos eventos via `SideEffects`: `palette_opened`, `command_executed` (campo `command_id`), `telemetry_toggled`. Mantém o pipeline de side-effects já existente em `process_side_effects`.

## Testes
- **Unit (puros, sem terminal)**:
  - `theme::detect` para `COLORTERM=truecolor`, `TERM=xterm-256color`, `TERM=dumb`/16-cores (RN-04).
  - `palette::filter`: fuzzy match, ordenação, query vazia, sem match.
  - `palette` reducers: open/close/type/backspace/navegação; `selected(&App)` retorna a `Action` esperada.
  - `Command.available_in`: comando inválido (ex.: "Aprovar" sem artefato) fica indisponível.
  - `event::map_key`: novos bindings (`Ctrl+K`, `/` fora de text_mode, `t`) e não-regressão dos existentes.
- **Render (TestBackend, larguras 40/80/120)**: estender `renders_all_screens_no_panic` para cobrir home, palette aberto e telemetria aberta, sem panic (NF-02, CA "testes passam").
- **Guardrail de estética (M-1)**: teste/checagem garantindo ausência de `Borders::ALL` como moldura padrão de conteúdo (grep em CI ou asserção de render).
- **Comando**: `cargo test` (já é o gate do projeto); `cargo fmt --check` e `cargo clippy` conforme padrão do repo.

## Riscos
- **R1 (terminal / external_dependency)**: truecolor/largura/glifos variam. → `ColorDepth::detect` + fallback 16-cores + figlet reduzido; testes em 40/80/120.
- **R2 (regressão em `ui.rs`, 1484 linhas)**: → tema é aditivo, assinaturas de render preservadas; migrar tela a tela com testes de render a cada fase.
- **R3 (conflito de teclas / complexidade de input)**: `Ctrl+K` e `/` podem colidir em alguns terminais/conthextos. → `/` só fora de text_mode; `Ctrl+K` como caminho garantido; mapa declarativo e testes de `map_key`.
- **R4 (informação escondida)**: telemetria fora do default. → hint fixo no footer + comando fixo "Telemetria" no palette (descoberta garantida).
- **R5 (escopo grande)**: → plano incremental abaixo, cada fase entregável e verde no `cargo test`.

## Plano de implementação
Sequência incremental (cada fase compila, passa `cargo test` e é um checkpoint natural de PR):

1. **Fase 1 — Tema base**: criar `theme.rs` (tokens + `detect` + helpers `selection_bar`/`section_header`/`label_value`/`footer`/`centered_rect`/`overlay_dim`). Migrar header/footer (casco) para o tema. Testes de `detect`.
2. **Fase 2 — Borderless + seleção**: remover `Borders::ALL` de moldura; aplicar barra full-width nas listas (`OrchestrationList`, `StageBoard`, `ProviderSetup`); acento único. Atualizar testes de render.
3. **Fase 3 — Home/launcher**: `render_home` com `figlet_banner` + lista de comandos + input + footer; landing quando sem orquestração ativa.
4. **Fase 4 — Command palette**: `palette.rs` (Command, COMMANDS, filter, CommandPalette), `Overlay` em `app.rs`, `Action::OpenPalette`, bindings `Ctrl+K`/`/`, `render_command_palette`, interceptação no `update`. Testes unitários + render do overlay.
5. **Fase 5 — Telemetria sob demanda**: `Overlay::Telemetry`, `Action::Telemetry` (`t`), `render_telemetry`; remover telemetria densa do default de board/checkpoint/running (RF-06). Hint de descoberta no footer.
6. **Fase 6 — Seletor agrupado + polimento**: reestilizar provider picker como modal agrupado por seção com tags à direita (RF-07); revisão de degradação graciosa; guardrail de estética (M-1); `visual-review` contra as referências do OpenCode.

Decomposição detalhada em tarefas atômicas com critérios Gherkin será feita na Etapa 4 (Tasks). **Checkpoint humano obrigatório após esta Tech Spec.**
