# Layout — Grade, Ritmo e Composição de Dashboards

## Conteúdo

- 1. A Grade de 12 Colunas
- 2. KPI-Row-Then-Detail — O Padrão Canônico
- 3. Ritmo de Espaço — Agrupamento e Respiração
- 4. Cross-Filter Ancorado na Linha de KPIs
- 5. Consistência de Alturas por Tipo de Linha
- Checklist antes de empurrar

A grade é a espinha do dashboard. Toda decisão de layout — onde o KPI fica, quanto
espaço o gráfico ocupa, quando um header Svelte separa dois blocos — parte da grade
e do ritmo que você estabelece na primeira linha.

**Referências cruzadas:**
- Paleta e tokens visuais: `design-tokens.md`
- Qual widget nativo escolher por tarefa: `chart-selection.md`
- Como configurar cada widget com qualidade visual: `widget-recipes.md`
- Headers de seção e controls Svelte: `svelte-widgets.md`

---

## 1. A Grade de 12 Colunas

O app usa uma grade de 12 colunas. **`widget_columns` fica no corpo do arquivo
de dashboard**, não no do app, e não existe wrapper `app_data`:

```yaml
# dashboards/<slug>--<id>.yaml
kind: app-dashboard
appId: 123
---
nome: "Visão Executiva"
widget_columns: 12        # 12 (default), 24, 36 ou 60
widgets: [...]
```

Outros valores aceitos (`24`, `36`, `60`) existem, mas 12 é o padrão correto para
dashboards de BI executivo e operacional. Grade mais fina (24+) faz sentido apenas
quando você tem dezenas de KPIs muito estreitos — e nesse caso provavelmente você
deveria dividir em múltiplas abas.

### Largura sempre em colunas (`w`), nunca em pixels

Cada widget recebe `w` (largura em colunas) e `x` (posição horizontal inicial):

| Propósito | `w` típico |
|---|---|
| KPI small | `w: 2` |
| KPI medium | `w: 3` |
| Gráfico meia-tela | `w: 6` |
| Gráfico 2/3 de tela | `w: 8` |
| Componente complementar | `w: 4` |
| Full-width | `w: 12` |

Nunca use larguras em pixels no YAML do dashboard. A grade existe exatamente para
que o layout seja responsivo dentro das proporções definidas.

### Altura por conteúdo (`h`)

A altura `h` é em unidades de linha da grade (não pixels). A regra:

| Tipo de widget | `h` típico |
|---|---|
| KPI card | `2–3` |
| Gráfico de linha / área | `4–5` |
| Donut / pizza | `4–5` |
| Barras horizontais (ranking) | `4–6` |
| Tabela / pivot | `6–10` |
| Section header (Svelte) | `1` |

Linhas de KPIs devem ser **curtas** — o número grande comunica com rapidez, não com
altura. Tabelas precisam de espaço vertical para mostrar linhas sem scroll excessivo.
Gráficos ficam no meio. Essa hierarquia de altura é parte da linguagem visual.

---

## 2. KPI-Row-Then-Detail — O Padrão Canônico

Todo dashboard executivo ou operacional segue a mesma ordem de leitura:

1. **Topo (`y:0`):** KPIs + controle de cross-filter (ListBox).
2. **Meio (`y:1`, `y:2`):** gráficos de tendência, distribuição e ranking.
3. **Fundo (`y:3`+):** tabelas de detalhe e drill-down.

O olho entra pelo topo — o número que responde "como estamos?" — e desce conforme
precisa de contexto. Inverter essa ordem (tabela no topo, KPI no fundo) quebra o
fluxo de leitura e força o usuário a procurar o que é mais importante.

### ASCII — Layout de referência (grade 12 colunas)

```
col:  0    2    4    6    8    10   12
      |    |    |    |    |    |    |
y:0   [KPI w2][KPI w2][Lista w2][KPI w2][KPI w2][KPI w2]
y:1   [  Tendência (linha/área) w8    ][ Donut w4 ]
y:2   [  Ranking (barras horizontais) w12         ]
y:3   [  Tabela detalhe               w12         ]
```

Traduzido em YAML (estrutura de posicionamento):

```yaml
widgets:
  # --- y:0  KPI row + cross-filter ---
  - type: Kpi
    x: 0  w: 2  y: 0  h: 3
  - type: Kpi
    x: 2  w: 2  y: 0  h: 3
  - type: ListBox          # cross-filter — veja seção 4
    x: 4  w: 2  y: 0  h: 3
  - type: Kpi
    x: 6  w: 2  y: 0  h: 3
  - type: Kpi
    x: 8  w: 2  y: 0  h: 3
  - type: Kpi
    x: 10 w: 2  y: 0  h: 3

  # --- y:1  Tendência + distribuição ---
  - type: Barline          # globalType: line (ou area)
    x: 0  w: 8  y: 1  h: 5
  - type: Pie              # chartType: donut
    x: 8  w: 4  y: 1  h: 5

  # --- y:2  Ranking ---
  - type: Barline          # globalType: bar, orientation: horizontal
    x: 0  w: 12 y: 2  h: 5

  # --- y:3  Detalhe ---
  - type: Matrix
    x: 0  w: 12 y: 3  h: 8
```

> **O campo é `type:`, e os valores são os do registry.** `Kpi`, `Barline`,
> `Pie`, `Table`, `Matrix`, `Map`, `Gauge`, `Punchcard`, `Forecast`, `Calendar`,
> `ListBox`, `Text`, `Diagram`, `Svelte`, `ParametricSvelte`, `App`. Não existe
> `kind:` em widget (`kind` é do documento, no header), nem tipo `Line`, `Donut`,
> `Bar` ou `Pivot`: linha e área são `Barline` com `globalType`, donut é `Pie`
> com `chartType`, e pivot é `Matrix`. Confira com
> `lumo scaffold widget.<Tipo>`, que é a fonte de verdade da forma de campo.

Este não é o único layout válido — é o ponto de partida. A variação acontece dentro
da estrutura (um gráfico de área em vez de linha, três colunas no meio em vez de
duas), não contra ela.

### Variante: Quadrante Executivo (showcase / vitrine comercial)

Quando o público é executivo e o pedido é "rápido de leitura, menos número, mais
gráfico" (o padrão dos overviews de Power BI que clientes mandam como referência),
troque a linha única de 6 KPIs por um **bloco 2×2 de 4 cards** à esquerda, com um
ranking ocupando a mesma altura à direita. Validado em cliente real (AR4K, 2026-07):

```
col:  0    3    6    9    12
y:0   [ Header Svelte (h1, no_border)                 ]
y:1   [KPI w3 h2][KPI w3 h2][  Ranking barras         ]
y:3   [KPI w3 h2][KPI w3 h2][  horizontais w6 h4      ]
y:5   [ Colunas mês w5 h4 ][Donut w3 h4][ Área w4 h4  ]
y:9   [ Risco/aging w6 h3    ][ Risco/aging w6 h3     ]
```

As regras do quadrante:

- **Só 4 KPIs** — os que respondem "como estamos?". Todo o resto vira gráfico
  (custos → ranking por categoria; composição → donut; tendência → colunas/área).
  Menos texto na tela = leitura mais rápida; a densidade vem dos gráficos, não
  de mais números.
- Cada card usa KPI v2 `icon-left` + pílula de ícone + `delta.badge` + card com
  `shadow: soft` e `border.radius: lg` (recipes 1b/1d/1e/1g em `widget-recipes.md`) —
  é o que dá o look "card de produto" do print de referência.
- Séries temporais **sem data labels** (`showDataLabels: false` + `showDataScale:
  true`) — label em cada ponto é a reclamação nº 1 de poluição visual de clientes.
- O header Svelte (h1) é o elemento de assinatura da tela; todos os gráficos ficam
  `transparent` para os 4 cards serem a única moldura visível.

---

## 3. Ritmo de Espaço — Agrupamento e Respiração

### Agrupe widgets relacionados na mesma linha (`y`)

Widgets que respondem à mesma pergunta ficam na mesma linha. Um gráfico de tendência
e o donut de composição do período vivem juntos em `y:1` porque ambos explicam o
número do KPI acima. Separá-los em linhas diferentes quebra a narrativa visual.

### Section headers (Svelte) entre blocos temáticos

Quando o dashboard tem dois assuntos distintos — digamos, "Ocupação" e "Receita" —
use um widget Svelte de cabeçalho de seção para demarcar a fronteira:

```yaml
- type: Svelte
  x: 0  w: 12  y: 4  h: 1
  chart:
    svelte: |
      <div class="flex items-center gap-3 px-4 h-full border-b border-gray-200 dark:border-navy-600">
        <span class="text-xs font-semibold uppercase tracking-widest text-gray-400 dark:text-gray-500">
          Receita
        </span>
        <div class="flex-1 h-px bg-gray-200 dark:bg-navy-600"></div>
      </div>
```

O cabeçalho Svelte ocupa `h:1` e 12 colunas — ele é uma linha divisória, não um
bloco de conteúdo. Use-o entre grupos, não entre widgets individuais.

> O widget Text nativo foi descontinuado. Use Svelte para todos os elementos de
> layout textual (headers, banners, callouts).

### Não amontoe — ≤ 9 widgets por "tela"

Uma tela com mais de 9 widgets (excluindo headers de seção) está tentando responder
perguntas demais ao mesmo tempo. O usuário para de ler. A regra:

- 3–6 KPIs na linha do topo.
- 2–3 gráficos no meio.
- 1 tabela no fundo.
- Total: 6–10 widgets por dashboard completo.

Se você precisar de mais, crie uma segunda aba ou um segundo dashboard. Divida por
público (executivo × operacional) ou por domínio (ocupação × receita × ADR).

---

## 4. Cross-Filter Ancorado na Linha de KPIs

O ListBox é o controle de cross-filter mais comum: o usuário seleciona um valor
(unidade, mês, segmento) e todos os outros widgets da tela filtram. Ele pertence
ao `y:0` — na mesma linha dos KPIs, nunca solto no meio da tela.

Por que no topo? Porque o filtro define o contexto de leitura. O usuário escolhe
"Unidade: SP-01" e depois lê os KPIs com essa lente. Se o filtro fica no meio,
ele é descoberto depois — tarde demais.

Posicionamento recomendado: **terceira posição da linha** (depois do segundo KPI),
ocupando `w:2`. Isso cria um ritmo visual de "número, número, filtro, número,
número, número" — o controle fica onde o olho passa, mas não interrompe o bloco
de métricas.

```yaml
- type: ListBox
  x: 4  w: 2  y: 0  h: 3
  chart:
    primaryData:
      columns:
        - id: <coluna_de_dimensao>
    crossFilter: true
```

Para filtros que precisam de mais espaço (múltiplos seletores, datas), use um
widget Svelte com controles compostos e o mesmo `y:0`.

---

## 5. Consistência de Alturas por Tipo de Linha

A altura de cada linha precisa ser consistente dentro do mesmo tipo. Se dois
KPIs estão em `y:0`, ambos têm `h:3` — nunca um com `h:2` e outro com `h:4` na
mesma linha. A grade do Horus empilha os widgets pela coordenada `y`; alturas
inconsistentes na mesma linha criam buracos visuais e quebram o alinhamento.

### Referência rápida de alturas por linha

| Linha (`y`) | Tipo | `h` recomendado |
|---|---|---|
| `y:0` | KPIs + ListBox | `h: 3` |
| `y:1` | Gráficos principais | `h: 5` |
| `y:2` | Ranking / secondary charts | `h: 5` |
| `y:3` | Tabela / pivot | `h: 8` |
| Qualquer | Section header (Svelte) | `h: 1` |

Esses valores são pontos de partida. Ajuste quando o conteúdo pedir — uma tabela
com muitas linhas merece `h:10`, um ranking curto pode ser `h:4`. O que não muda:
todos os widgets da mesma linha têm o mesmo `h`.

---

## Checklist antes de empurrar

- [ ] `widget_columns` declarado no corpo do arquivo de dashboard (não em `app_data`, que não existe).
- [ ] KPIs estão em `y:0`, tabelas no `y` mais alto.
- [ ] Nenhum widget usa largura em pixels — apenas `w` em colunas.
- [ ] Todos os widgets da mesma linha têm o mesmo `h`.
- [ ] Cross-filter (ListBox ou Svelte) está ancorado em `y:0`.
- [ ] Section headers usam `type: Svelte`, não widget Text.
- [ ] Total de widgets por dashboard ≤ 9 (excluindo section headers).
- [ ] `lumo app export app:<id> --image-format png` — olhe a imagem antes de entregar.
