# PPTist JSON — o modelo de documento de um `deck`

Fonte de verdade: `pptist/src/types/slides.ts` (fork Horus do PPTist). O `deck`
YAML do Lumo é o dual-doc de sempre (header flat + `---` + body); o body é
`{nome, slides[], version?, tags?}`. `lumo lint` valida a **forma**: `nome` e
`slides` obrigatórios, e — só para elementos `type: horus-widget` — `appId` e
`widgetGuid` obrigatórios. Todo o resto de um elemento é **freeform**: o
schema não valida seus campos internos, então erros neles só aparecem no
render (`lumo deck export`) ou, para `horus-widget`, em `lumo deck preview`.

## Viewport

O canvas é fixo em **1000 × 562.5** unidades lógicas (16:9;
`562.5 = 1000 × 0.5625`). Um deck novo (`lumo new deck`) sempre nasce nesse
tamanho — **não é um campo do body**, o CLI não o expõe nem deixa você mudá-lo
(`width`/`height`/`theme` do documento PPTist ficam só no servidor). Todo
`left`/`top`/`width`/`height` de elemento é relativo a esse canvas de
1000×562.5, não a pixels de tela real — o render escala o canvas inteiro para
caber no viewport de saída (editor, export, kiosk).

## Slide

```yaml
slides:
  - id: s1                # string, qualquer id único (ex.: nanoid curto)
    elements: []           # lista de elementos (ver abaixo)
```

`id` e `elements` são os únicos campos que o lint do Lumo olha. O PPTist
suporta mais campos opcionais de slide (`background`, `notes`, `remark`,
`animations`, `turningMode`, `sectionTag`, `type: cover|contents|transition|
content|end`) — todos passam despercebidos pelo schema (freeform), então são
seguros de incluir se você já souber a forma exata que quer, mas não têm
validação alguma: um valor errado só se manifesta no render.

## Geometria comum a (quase) todo elemento

```yaml
id: "el1"        # string, único dentro do documento
left: 20          # distância do topo-esquerda do canvas (unidades do canvas)
top: 20
width: 480
height: 150
rotate: 0         # graus
```

Exceção: o elemento `line` **não tem** `width`/`height`/`rotate` — usa
`start: [x, y]` / `end: [x, y]` no lugar.

## Catálogo de tipos de elemento

O PPTist define mais tipos do que os listados abaixo (`latex`, `video`,
`audio`, `frame` também existem e seguem a mesma regra freeform) — os sete a
seguir são os que aparecem com frequência em decks de BI.

| `type` | Para que serve | Campos-chave (freeform, não validados pelo lint) |
|---|---|---|
| `text` | Bloco de texto rico | `content` (HTML), `defaultFontName`, `defaultColor`, `lineHeight`, `textType` |
| `image` | Imagem estática | `src`, `fixedRatio`, `filters`, `clip`, `radius` |
| `shape` | Forma vetorial (retângulo, círculo, etc.) com texto opcional dentro | `path`, `viewBox`, `fill`, `gradient`, `text: {content, align, ...}` |
| `line` | Linha/seta/conector | `start`, `end`, `style`, `color`, `points` |
| `table` | Tabela estática (não é o widget Table do BI — é uma tabela desenhada, célula a célula) | `data` (matriz de células), `colWidths`, `theme` |
| `chart` | Gráfico estático do PPTist (dados fixos, não conectado ao BI) | `chartType`, `data: {labels, legends, series}`, `themeColors` |
| `horus-widget` | **Gráfico BI vivo** — o elemento que faz a ponte com um app/widget real do Horus BI | ver seção dedicada abaixo |

Use `text`/`image`/`shape`/`line`/`table`/`chart` para narrativa, capas,
callouts e decoração — a mesma liberdade de um editor de slides comum, sem
conexão a dado real. Use `horus-widget` sempre que o slide precisar mostrar um
número ou gráfico que vem do BI.

## `horus-widget` — o elemento gráfico BI vivo (detalhe completo)

Único elemento com validação de schema além da geometria comum. Campos, na
ordem em que aparecem em `PPTHorusWidgetElement` (`pptist/src/types/
slides.ts`):

```yaml
- type: horus-widget
  id: "w1"
  left: 20
  top: 20
  width: 480
  height: 150
  rotate: 0

  appId: 18421              # obrigatório (lint) — id do app dono do widget
  widgetGuid: "abc-123-def" # obrigatório (lint) — guid do widget dentro do app

  # Metadados cosméticos — só exibição no painel de estilo do editor, NUNCA
  # usados para resolver o render (o preview/render ignoram esses campos).
  # Ausentes em elementos antigos; opcionais.
  appName: "Comercial"
  widgetName: "Faturamento por mês"
  dashboardName: "Visão Geral"

  title:
    mode: widget             # widget | custom | none
    custom: "Faturamento"    # só usado quando mode: custom

  background:
    mode: card                # card | transparent | color
    color: "#ffffff"          # só usado quando mode: color

  theme: light                # light | dark — tema aplicado dentro do iframe

  filters: null                # array de filtros (mesmo formato hidratável de
                                # filtro de widget usado no resto do app) ou
                                # null para herdar os filtros salvos do próprio
                                # widget. Mais seguro copiar de um elemento
                                # existente do que escrever à mão do zero.

  scale: 1                     # opcional, 0.5–2 (ausente = 1). Zoom visual do
                                # CONTEÚDO renderizado dentro do elemento — CSS
                                # transform puro, não muda width/height/left/top.
                                # Útil pra ampliar texto/números quando o
                                # elemento é pequeno (o widget usa fontes fixas
                                # pensadas pra tela cheia de dashboard).

  frozen: false                 # NÃO defina manualmente na autoria normal —
                                 # ver "campos server-owned" abaixo.
```

### Defaults reais (o que o editor usa ao criar um `horus-widget` do zero)

Quando um humano arrasta um gráfico pro deck pela UI (`useCreateElement.ts`),
o elemento nasce com:

```yaml
title: { mode: widget }
background: { mode: card }
theme: light
filters: null
frozen: false
```

`width`/`height` nascem de `min(800, viewport_width * 0.8)` e o aspect ratio
do widget escolhido (clamp 0.3–5; fallback 16:9), centralizado no canvas.
Use esses valores como ponto de partida sensato ao criar elementos novos por
YAML em vez de inventar geometria arbitrária.

### Campos **server-owned** — não escreva à mão

- **`frozen`** (`boolean`) — quando `true`, o render usa `snapshotUrl` (imagem
  estática congelada) em vez do iframe vivo. É setado pelo fluxo de
  export/print (o host pede um snapshot pontual do widget vivo antes de
  congelar a página pra exportação), não algo que se decide na autoria.
- **`snapshotKey` / `snapshotUrl`** — referência do snapshot congelado (S3),
  preenchida pelo mesmo fluxo acima. Deixe ausente em elementos novos.

Se você está montando um deck do zero via YAML, simplesmente **omita** os três
— o padrão (`frozen: false`, sem snapshot) é o comportamento vivo, que é o que
você quer na autoria.

### `detached` + `config` — o caso do gráfico "personalizado"

```yaml
- type: horus-widget
  appId: 18421
  widgetGuid: "abc-123-def"   # continua obrigatório mesmo aqui — ver nota
  detached: true
  config:
    type: Kpi                 # mesmo enum de tipo de widget (Kpi, Barline, Pie, Table, ...)
    chart: { ... }             # árvore de config do gráfico — específica do type
    nome: "Faturamento (editado)"
    display_nome: true
    guid: "abc-123-def"        # decorativo — origem, não usado pra resolver render
  title: { mode: custom, custom: "Faturamento (editado)" }
  background: { mode: transparent }
  theme: light
  filters: null
  frozen: false
```

Quando `detached: true`, o render usa o `config` embutido no próprio elemento
(um deep-clone editável da config do gráfico) em vez de buscar o widget salvo
por `widgetGuid` — é o caso do gráfico "destacado"/personalizado dentro de um
deck específico, que diverge do widget original sem alterar o app de origem.
`appId` continua sendo usado para o contexto de dados (carregamento do app,
segurança); `widgetGuid` continua **obrigatoriamente presente** no schema
mesmo aqui (mantém a referência de origem), mas vira decorativo — o preview e
o render resolvem via `config`, não via guid, quando `detached: true`.

**Não escreva `detached`/`config` do zero na autoria headless.** É a
representação de "editei este gráfico específico dentro deste deck via o
editor de gráfico visual" — a config interna (`chart`) é grande, tem forma
própria por tipo de widget, e não está documentada aqui porque não é o
caminho recomendado para um agente. Se a tarefa pedir um gráfico diferente do
que já existe salvo, prefira criar/editar um widget de verdade no app (skill
`lumo-cli`/`lumo-design`) e referenciá-lo normalmente por `widgetGuid`, ou
peça para um humano fazer a customização pontual pela UI e depois
`lumo pull deck:<id>` para trazer o resultado de volta.
