sagadeck 1.2.0 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/README.md +11 -2
  2. package/bin/sagadeck.js +36 -3
  3. package/docs/ANDAMENTO.md +37 -0
  4. package/docs/REFERENCIA.md +4 -3
  5. package/package.json +1 -1
  6. package/src/ai/deck-ai.js +54 -12
  7. package/src/ai/document-visuals.js +230 -24
  8. package/src/ai/quality.js +1 -1
  9. package/src/ai/transform.js +51 -2
  10. package/src/diff.js +44 -0
  11. package/src/elements.js +37 -2
  12. package/src/export/pptx.js +8 -6
  13. package/src/figures/charts.js +11 -3
  14. package/src/figures/diagrams.js +37 -9
  15. package/src/fiscal/autofix.js +1 -1
  16. package/src/fiscal/enxugar.js +22 -0
  17. package/src/fiscal/normalize.js +19 -2
  18. package/src/import/crop.js +10 -3
  19. package/src/layouts.js +5 -3
  20. package/src/library.js +74 -2
  21. package/src/motion.js +1 -1
  22. package/src/package.js +16 -1
  23. package/src/research/brave.js +12 -0
  24. package/src/research/chat-research.js +29 -0
  25. package/src/research/links.js +37 -0
  26. package/src/research/research.js +42 -2
  27. package/src/research/wikipedia.js +15 -0
  28. package/src/runtime/base.css +6 -6
  29. package/src/runtime/motion.js +1 -1
  30. package/src/runtime/runtime.js +15 -2
  31. package/src/studio/ai-chat-route.js +158 -0
  32. package/src/studio/exporting.js +2 -2
  33. package/src/studio/library-routes.js +158 -0
  34. package/src/studio/public/app.js +44 -508
  35. package/src/studio/public/chat.js +461 -0
  36. package/src/studio/public/index.html +17 -12
  37. package/src/studio/public/library.js +13 -2
  38. package/src/studio/public/slide-clipboard.js +20 -7
  39. package/src/studio/public/slide-select.js +73 -0
  40. package/src/studio/public/studio-next.css +28 -1
  41. package/src/studio/public/style.css +7 -1
  42. package/src/studio/public-files.js +1 -1
  43. package/src/studio/server.js +14 -285
  44. package/src/studio/slide-copy-routes.js +7 -4
  45. package/src/studio/snapshot.js +3 -0
  46. package/src/themes.js +32 -2
package/README.md CHANGED
@@ -18,6 +18,12 @@ pip install sagadeck
18
18
  ```
19
19
  Requer **Node.js 18+** e **Chrome ou Edge** instalados (o motor é JavaScript e vem empacotado no pacote Python).
20
20
 
21
+ Quem já vive no Node pode instalar direto, sem Python e sem bundle (vai a fonte + dependências):
22
+ ```bash
23
+ npm install -g sagadeck
24
+ ```
25
+ A IA funciona apontando para um modelrelay ou gateway compatível (`SAGADECK_LLM_URL`, veja abaixo); o `pip install "sagadeck[ia]"` continua sendo o caminho com o modelrelay junto.
26
+
21
27
  ## Como usar
22
28
 
23
29
  Toda apresentação mora na **biblioteca**: `SAGADECK_HOME`, ou `~/sagadeck` (no Windows, `C:Users<você>sagadeck`). Uma pasta por tópico, uma por apresentação.
@@ -36,17 +42,20 @@ sagadeck new <nome> [--topic=T] [--theme=x] cria um deck de exemplo na bibliote
36
42
  sagadeck studio [deck.yaml] [--port=3517] sem arquivo: a biblioteca; com arquivo: o editor com chat IA (já aberto: usa o aberto)
37
43
  sagadeck ensaio-api [--port=3000] exemplo de slides de API rodando contra uma API de mentira
38
44
  sagadeck autofix <deck.yaml> [--out=pasta] auto-corrige sobreposições, margens e excesso de texto no YAML
45
+ sagadeck diff <a.yaml> <b.yaml> [--json] o que entrou, saiu, mudou e andou entre dois decks (por uid)
46
+ sagadeck enxugar <deck.yaml> [--fix] palavras por slide contra o limite do material (--fix: excedente para as notas)
39
47
  sagadeck build <deck.yaml> gera o .html
40
48
  sagadeck watch <deck.yaml> recompila o .html a cada vez que você salva o YAML
41
49
  sagadeck check <deck.yaml> fiscal: estouro de texto, sobreposição, contraste, excesso de texto
42
50
  sagadeck shots <deck.yaml> [--steps] PNG de cada slide + folhas de contato (para revisar)
43
- sagadeck pptx <deck.yaml> [--native-charts] [--sem-notas] PowerPoint editável (--sem-notas: para mandar sem a sua cola)
51
+ sagadeck pptx <deck.yaml> [--native-charts] [--sem-notas] [--mac-fonts] PowerPoint editável (--sem-notas: para mandar sem a sua cola; --mac-fonts: fontes dos dois sistemas, para abrir igual no Mac)
44
52
  sagadeck pdf <deck.yaml> PDF
45
53
  sagadeck roteiro <deck.yaml> roteiro do apresentador em PDF
46
54
  sagadeck all <deck.yaml> tudo acima
47
55
  sagadeck mcp servidor MCP para IDEs agênticos (Cursor, Claude Code, Cline)
48
56
  sagadeck themes vitrine com os 6 temas
49
57
  sagadeck icons [filtro] lista os ícones (ex.: sagadeck icons car)
58
+ sagadeck links <deck.yaml> confere se as fontes da pesquisa (contexto/pesquisa) continuam de pe
50
59
  sagadeck ref referência completa do YAML
51
60
  sagadeck skill instruções para agentes de IA
52
61
  ```
@@ -300,6 +309,6 @@ tools/test-live.mjs teste automático do modo apresentação e dos widgets
300
309
 
301
310
  ## Limitações conhecidas
302
311
 
303
- - O PPTX usa as fontes do Windows/Office dos temas. Aberto num Mac sem essas fontes, o PowerPoint troca a fonte (o HTML e o PDF continuam iguais).
312
+ - O PPTX usa as fontes do Windows/Office dos temas. Para abrir igual num Mac sem Office, exporte com `sagadeck pptx deck.yaml --mac-fonts` (só o PPTX troca as fontes por equivalentes dos dois sistemas; o HTML e o PDF continuam iguais).
304
313
  - Widgets interativos (jogos, simuladores) viram imagem do estado final no PowerPoint e no PDF — a interação existe só no HTML.
305
314
  - Gráficos entram como imagem nítida no PPTX; com `--native-charts`, barras/colunas/linhas/rosca viram gráficos nativos editáveis (com visual mais simples).
package/bin/sagadeck.js CHANGED
@@ -51,7 +51,7 @@ const HELP = `sagadeck — YAML -> apresentação (HTML animado + PowerPoint edi
51
51
  sagadeck build <deck.yaml> gera <deck>.html (abre no navegador; P = modo apresentador)
52
52
  sagadeck check <deck.yaml> procura texto estourado, sobreposição, contraste, excesso de texto
53
53
  sagadeck shots <deck.yaml> [--steps] [--only=3,5] PNG de cada slide + folhas de contato (para revisar)
54
- sagadeck pptx <deck.yaml> [--native-charts] [--sem-notas] gera <deck>.pptx editável (com animações dos cliques e notas; --sem-notas tira as notas, para mandar a alguém)
54
+ sagadeck pptx <deck.yaml> [--native-charts] [--sem-notas] [--mac-fonts] gera <deck>.pptx editável (com animações dos cliques e notas; --sem-notas tira as notas, para mandar a alguém; --mac-fonts usa só fontes do Windows e do Mac, para abrir igual sem Office)
55
55
  sagadeck pdf <deck.yaml> gera <deck>.pdf (um slide por página)
56
56
  sagadeck roteiro <deck.yaml> gera <deck> - roteiro.pdf (miniaturas + notas + tempos)
57
57
  sagadeck estudo <deck.yaml> [--html] gera <deck> - material de estudo.pdf (cada slide + texto de consulta)
@@ -68,11 +68,14 @@ const HELP = `sagadeck — YAML -> apresentação (HTML animado + PowerPoint edi
68
68
  sagadeck importar <arquivo.pptx> [--topico=X] [--titulo=Y] [--sem-fotos]
69
69
  traz um PowerPoint para a biblioteca, fiel ao original
70
70
  sagadeck autofix <deck.yaml> [--out=pasta] auto-corrige sobreposições, margens e excesso de texto no YAML
71
+ sagadeck diff <a.yaml> <b.yaml> [--json] o que entrou, saiu, mudou e andou entre dois decks (por uid)
72
+ sagadeck enxugar <deck.yaml> [--fix] palavras por slide contra o limite do material (--fix: excedente para as notas)
71
73
  sagadeck mcp inicia o servidor MCP para IDEs agênticos (Cursor, Claude Code, Cline)
72
74
  sagadeck watch <deck.yaml> recompila o HTML sempre que o YAML mudar
73
75
  sagadeck themes [--out=pasta] gera uma vitrine com todos os temas
74
76
  sagadeck icons [filtro] lista ícones disponíveis (2.100+)
75
77
  sagadeck search <termo> [--limit=5] pesquisa na web via DuckDuckGo (sem bloqueio) para enriquecer dados
78
+ sagadeck links <deck.yaml> confere se as fontes da pesquisa (contexto/pesquisa) continuam de pe
76
79
  sagadeck ref imprime a referência completa do YAML (ótimo para dar a um LLM)
77
80
  sagadeck skill imprime as instruções para agentes de IA
78
81
  sagadeck --version
@@ -260,7 +263,7 @@ async function main() {
260
263
  const p = paths(args[0]); const r = doBuild(p, true);
261
264
  const { exportPptx } = await import("../src/export/pptx.js");
262
265
  console.log("… exportando PowerPoint");
263
- const { errors } = await exportPptx(p.html, p.pptx, { theme: r.theme, meta: { ...r.meta, slides: r.slidesMeta }, nativeCharts: !!flags["native-charts"], notes: !flags["sem-notas"], log: flags.verbose ? console.log : () => {} });
266
+ const { errors } = await exportPptx(p.html, p.pptx, { theme: r.theme, meta: { ...r.meta, slides: r.slidesMeta }, nativeCharts: !!flags["native-charts"], notes: !flags["sem-notas"], fontFallback: flags["mac-fonts"] ? "mac" : undefined, log: flags.verbose ? console.log : () => {} });
264
267
  errors.forEach((e) => console.log(" ✗ " + e));
265
268
  console.log(`✓ PPTX: ${p.pptx}`);
266
269
  break;
@@ -294,7 +297,7 @@ async function main() {
294
297
  await doCheck(p);
295
298
  const files = await doShots(p, flags.revisao ? p.shots : p.tmpShots);
296
299
  const { exportPptx } = await import("../src/export/pptx.js");
297
- await exportPptx(p.html, p.pptx, { theme: r.theme, meta: { ...r.meta, slides: r.slidesMeta }, nativeCharts: !!flags["native-charts"], notes: !flags["sem-notas"] });
300
+ await exportPptx(p.html, p.pptx, { theme: r.theme, meta: { ...r.meta, slides: r.slidesMeta }, nativeCharts: !!flags["native-charts"], notes: !flags["sem-notas"], fontFallback: flags["mac-fonts"] ? "mac" : undefined });
298
301
  console.log(`✓ PPTX: ${p.pptx}`);
299
302
  const { pdf } = await import("../src/export/shots.js");
300
303
  await pdf(p.html, p.pdf); console.log(`✓ PDF: ${p.pdf}`);
@@ -454,6 +457,28 @@ async function main() {
454
457
  }
455
458
  break;
456
459
  }
460
+ case "diff": {
461
+ const [fa, fb] = args;
462
+ if (!fa || !fb) { console.error("Uso: sagadeck diff <a.yaml> <b.yaml> [--json]"); process.exit(1); }
463
+ const { diffDecks, formatDiff } = await import("../src/diff.js");
464
+ const d = diffDecks(loadSpec(path.resolve(fa)), loadSpec(path.resolve(fb)));
465
+ if (flags.json) console.log(JSON.stringify(d, null, 2));
466
+ else console.log(formatDiff(d, { aName: fa, bName: fb }));
467
+ break;
468
+ }
469
+ case "enxugar": {
470
+ const p = paths(args[0]);
471
+ const spec = loadSpec(p.abs);
472
+ const { enxugarReport, formatEnxugar } = await import("../src/fiscal/enxugar.js");
473
+ console.log(formatEnxugar(enxugarReport(spec)));
474
+ if (flags.fix) {
475
+ const { autofixDeck } = await import("../src/fiscal/autofix.js");
476
+ const res = autofixDeck(spec);
477
+ (await import("../src/deck-file.js")).writeDeckFile(p.abs, res.spec);
478
+ console.log(`enxugado: ${res.modifiedSlidesCount} slide(s) corrigido(s), excedente nas notas.`);
479
+ }
480
+ break;
481
+ }
457
482
  case "mcp": {
458
483
  const { runMCPServer } = await import("../src/mcp/server.js");
459
484
  runMCPServer();
@@ -501,6 +526,14 @@ async function main() {
501
526
  }
502
527
  break;
503
528
  }
529
+ case "links": {
530
+ const p = paths(args[0]);
531
+ const { linksOfDeck, checkLinks, formatLinks } = await import("../src/research/links.js");
532
+ const urls = linksOfDeck(path.dirname(p.abs));
533
+ if (!urls.length) { console.log("sem pesquisa salva ao lado do deck (contexto/pesquisa/fontes.json)."); break; }
534
+ console.log(formatLinks(await checkLinks(urls)));
535
+ break;
536
+ }
504
537
  case "ref": case "referencia": process.stdout.write(fs.readFileSync(path.join(DOCS, "REFERENCIA.md"), "utf8")); break;
505
538
  case "skill": process.stdout.write(fs.readFileSync(SKILL, "utf8")); break;
506
539
  case "--version": case "-v": case "version": console.log(`sagadeck ${VERSION}`); break;
package/docs/ANDAMENTO.md CHANGED
@@ -1253,3 +1253,40 @@ Atualização 04/10: screenshots revelaram hierarquia invertida e evidências tr
1253
1253
  ### Autonomia total (Rose): o agente vigia, a pessoa não
1254
1254
  - Carimbo do motor na barra de status (versão + commit + início) via /api/instance; aprovação de submit mostra preço do catálogo; direções aprovadas salvas e injetadas no prompt; vigia baixa clipe pronto sozinho e avisa com toast; versões fotografadas a cada save com modal lado a lado e restaurar com rede.
1255
1255
  - Caça própria: YAML inexistente no cliente quebrava o init em silêncio (botões mortos); show do servidor agora manda slides prontos. Módulos extraídos para respeitar a trava de 100KB do server.js.
1256
+
1257
+ ### PPTX legível no Mac sem Office (Rose) — 05/10/2026
1258
+ - Pedido do Narumi: melhorar export PPTX/PDF, começando pela fidelidade no Mac (limitação conhecida do README: PPTX usava fontes Windows/Office e o PowerPoint trocava a fonte).
1259
+ - Novo `sagadeck pptx deck.yaml --mac-fonts` (vale no `all`): o PPTX troca as 18 faces só-Windows por equivalentes dos dois sistemas (Bahnschrift→Arial Narrow, Segoe UI→Arial, Cascadia Mono→Courier New, Century Gothic→Verdana, Franklin Gothic Heavy→Arial Black, resto Franklin→Arial, mão→Comic Sans MS, pixel→Courier New); HTML e PDF não mudam. No Studio, `/api/export/pptx?macFonts=1`.
1260
+ - Código: `PPTX_MAC_SAFE` + `macFallbackFace` em `src/themes.js`, `fontFallback` em `exportPptx` (`src/export/pptx.js`, cobre texto, tabela e o `VARIABLE` do Bahnschrift), `macFonts` em `sendExport` + rota do Studio. Docs em `README.md` e `docs/REFERENCIA.md`.
1261
+ - Testes: `test/pptx-mac-fonts.test.js` (mapa só-safe em todos os temas sem navegador + export real com/sem flag). Validação: suíte foca verde (export, guards, engine, 74 testes); suíte completa com 780 testes tem as mesmas 5 falhas do baseline sem a mudança (bundle/pip sem esbuild, diagram, retorno-recriada, ribbon-fit, share-link — pré-existentes, ambiente).
1262
+ - Fronteira do teste: verificado XML do PPTX (sem Bahnschrift/Segoe, com Arial Narrow; tabela segue nativa) e CLI de ponta a ponta; NÃO verificado num Mac de verdade (sem acesso a um) — quem tiver Mac confirma visualmente.
1263
+
1264
+ ### Suíte verde no Oracle: 5 falhas viram 0 (Rose) — 05/10/2026
1265
+ - Pedido do Narumi: zerar as 5 falhas pré-existentes da suíte. Diagnóstico com baseline (clone sem a mudança, mesmos 5 fails) + CI (vermelho na main há vários merges) para separar bug de ambiente.
1266
+ - 4 eram ambiente do Oracle: `bundle` (node_modules sem devDeps → `npm install`), `diagram` e `share` (só fontes DejaVu → instaladas Liberation, métricas compatíveis com Arial) e `retorno-recriada` (Chrome 153 hifeniza pt-BR, CI não: teste aceita encolher OU hifenizar, `test/retorno-recriada.test.js`, mantendo irmãos iguais como invariante).
1267
+ - 1 era bug de verdade, em todo lugar (CI ubuntu+windows também): a faixa Design estourava em máquina nova. Causas: (a) `flex-shrink` esmagava a nota de identidade a 0px em silêncio (nem o aviso aparecia); travado com `flex-shrink: 0` no grupo Identidade, e o aviso voltou a aparecer; (b) com o aviso à vista, o grupo não cabia: controles compactos só ali (`#identity-select`, `#direction-select`, botões, px fixos para não depender de fonte); (c) em 900px e notebook (1366), blocos compactos em `studio-next.css` (`@media` 1440/1024) para 3 temas + 3 paletas à vista sem rolagem.
1268
+ - Validação: ribbon, retorno, diagram, engine, guards, pptx-mac (85), share+export (14), studio (87 passed, 1 skip), bundle (5, roda com sudo aqui porque a árvore é de root). `src/runtime/code-lab.js` regenerado pelo teste foi revertido (ruído de versão do esbuild, fora do escopo).
1269
+ - Fronteira: verde NESTE Oracle (DejaVu+Liberation+Chromium 153). CI tem relatório próprio (motion-scenes, motion-trace, copy/paste, transform — outros responsáveis); o Design do CI deve melhorar com este merge, conferir no próximo run.
1270
+
1271
+ ### Multisseleção de slides + copiar/colar no botão direito (Rose) — 06/10/2026
1272
+ - Pedido do Narumi: botão direito no slide copia/cola (estava só na faixa, e o Ctrl+C/V da lista nem funcionava: branch depois do return), Ctrl+click / Shift+click / Ctrl+setas selecionam vários.
1273
+ - `selectedSlides` em módulo próprio (`src/studio/public/slide-select.js`, pela trava de tamanho do app.js: 273.379 < 274.000): click alterna, Shift faz intervalo, Ctrl+setas estendem, Ctrl+A tudo; Delete exclui o bloco com Desfazer; mover acompanha a seleção; duplicar/novo/colado focam o resultado. Miniaturas marcadas com tracejado (`.selected`).
1274
+ - Copiar/colar vale para N slides (cliente `slide-clipboard.js` + `slide-copy-routes.js` aceitam lista, com compat para o formato de 1; colados ganham uids novos). Menu do botão direito com Copiar/Colar/Excluir com contagem; Mover/Duplicar só no singular.
1275
+ - Achado no caminho: `PUBLIC_SCRIPTS` (`src/studio/public-files.js`) barrou o módulo novo com 404 — registrado.
1276
+ - Testes: `test/slide-select.test.js` (menu copiar/colar, Ctrl+click+Delete+Desfazer, Ctrl+seta). Validação: slide-select (3), guards/engine/delete/undo (74), studio (87+1skip), share+export (14); suíte completa com 820 testes: 796 passed, 3 falhas pré-existentes/ambiente (bundle precisa escrita em árvore root; motion e transform passam isolados, instáveis sob carga — o transform já caía no CI).
1277
+
1278
+ ### Nome amigável na biblioteca atrás do portal (Rose) — 06/10/2026
1279
+ - Narumi viu o id cru (`305cc...`, avatar "3") no topo da biblioteca: `api/library` devolve o id e a página mostrava direto.
1280
+ - A página agora lê o username do `/whoami` do portal (mesma origem, convenção `username || email`, avatar com a inicial maiúscula; sem portal, segue como antes). Sem tocar no filmes-api nem no nginx.
1281
+ - Detalhe: o fetch só acontece com usuário (no Studio local o 404 sujava o console e quebrava os testes de zero-erro); o proxy fake do teste de prefixo ganhou `/whoami` com `logged_in:false` (portal sem nome → mostra o id, sem pedido quebrado).
1282
+ - Teste: `biblioteca mostra o username do portal em vez do id cru` (whoami mockado); `library-ui` 27/27.
1283
+ - Fronteira: verificado com whoami mockado; a confirmação visual no portal de verdade (nome "narumi" no topo) fica com o Narumi.
1284
+
1285
+ ### Publicação no npm (Rose) — 06/10/2026
1286
+ - Pedido do Narumi: o app é Node, merece casa no npm (e aposenta a gambiarra do zip/fonte).
1287
+ - Pacote pronto: nome `sagadeck` livre, `bin` executável, `files` completo (fonte + deps, 11 MB, sem bundle). Instalação via tarball testada de ponta a ponta (`npm i <tgz>` + `sagadeck build` gera o HTML).
1288
+ - `publish.yml` ganha job `publish-npm` (trusted publishing OIDC + provenance, mesma release). Falta o passo manual único: publicar a 1.2.0 à mão (`npm login` + `npm publish`) e ligar o Trusted Publisher em npmjs.com.
1289
+ - PyPI continua (público Python + banco); docs no README.
1290
+
1291
+ ### Legenda e marca-texto — reconciliação Windows + Oracle (Rose)
1292
+ - Branch dela (`fix/legenda-e-marca`) revisada por diff: mesma causa-raiz na legenda, mas o condicional dela (só maiúsculas) submedia nomes mistos — o CSS prova `text-transform: uppercase` + `letter-spacing` em todo `f-label`, então vale caps sempre. Mantida a estrutura dela (right-aligned + `ch-leg`) com medida sempre-caps; mark 84%/58% dela; teste de navegador dela (getBBox + pixel) + meu teste de cordas. Verdes: chart-legend-mark 2/2, engine 63/63, blocks 1/1.
@@ -512,7 +512,7 @@ Todo elemento aceita: `step` (clique em que aparece), `exit` (clique em que some
512
512
  - `console` (`screen: "TEXTO"`, `alarm: true`) · `desk` (`papers: true`) · `pair` · `judge` · `elevator` (`button: false`)
513
513
  - `car-top` — carro visto de cima: `driver` / `passenger`: `human | human-watch | human-phone | machine`, `back: sleep | human`, `button: true`
514
514
 
515
- **Diagramas**: `{ diagram: loop, nodes: [a, b, c, d], actor: in|on|out, at: 2 }` · `{ diagram: spectrum, stops: [...], at: 1, left, right }` · `{ diagram: flow, steps: [...], highlight: 1 }` · `{ diagram: venn, a, b, both }`
515
+ **Diagramas**: `{ diagram: loop, nodes: [a, b, c, d], actor: in|on|out, at: 2 }` · `{ diagram: spectrum, stops: [...], at: 1, left, right }` · `{ diagram: flow, steps: [...], highlight: 1, cols: 5 }` (mais de 5 passos desce em serpentina, ida e volta, sem encolher a letra) · `{ diagram: venn, a, b, both }`
516
516
 
517
517
  **Gráficos** (animados no HTML; com `--native-charts` viram gráficos nativos editáveis no PowerPoint):
518
518
  ```yaml
@@ -529,6 +529,7 @@ Todo elemento aceita: `step` (clique em que aparece), `exit` (clique em que some
529
529
  `null` numa série quebra a linha (ex.: sessões diferentes). Cores aceitam papéis do tema (`fg`, `hi`, `em`, `muted`, `line`) ou hex.
530
530
  `xLabel` / `yLabel` dão nome aos eixos (linhas e colunas). `from: { file, sheet, columns: [rótulo, série…] }` num slide de gráfico diz de que planilha do projeto os dados vieram (o Studio põe ao inserir uma sugestão; "Atualizar da planilha" relê o arquivo). Não invente `from`.
531
531
  `csv:` (qualquer tipo com dados) troca `data`/`labels`/`series` pelo conteúdo do arquivo (`;`, `,` ou tab; vírgula decimal; cabeçalho vira o nome das séries): atualizou o arquivo, o slide atualiza. Arquivo sumido: ficam os dados do slide e o fiscal avisa. No Studio, os dados ficam numa **planilha** no Formatar: cola do Excel (Ctrl+V em qualquer célula preenche a partir dela), importa CSV e ganha colunas de série.
532
+ Tabela colada no chat pedindo gráfico: a IA sugere com prévia (`variants` com 2 a 3 tipos — barras, linhas, pizza — nos mesmos dados) em vez de montar um `chart` direto; só faz direto com o tipo já dito.
532
533
 
533
534
  **Mapa do Brasil por UF** (em grade: cada estado é um quadrado na posição aproximada; a cor mais forte é o maior
534
535
  valor): `{ ufmap: { SP: 320, RJ: 140, MG: 150 }, suffix: " mil", prefix: "R$ ", highlight: [SP], legend: "cadastros", showValues: true }`.
@@ -585,7 +586,7 @@ theme:
585
586
  faces:
586
587
  display: { css: "font-family: 'Rockwell Extra Bold', serif; line-height: .95;", pptx: { face: "Rockwell Extra Bold" } }
587
588
  ```
588
- Cada face tem `css` (navegador) e `pptx` (nome exato da fonte no PowerPoint). Use fontes instaladas no Windows/Office para o PPTX sair idêntico.
589
+ Cada face tem `css` (navegador) e `pptx` (nome exato da fonte no PowerPoint). Use fontes instaladas no Windows/Office para o PPTX sair idêntico. Para mandar a alguém que abre num Mac sem Office: `sagadeck pptx deck.yaml --mac-fonts` (no Studio, `/api/export/pptx?macFonts=1`): o PPTX troca as fontes só-Windows (Bahnschrift, Segoe UI, Cascadia Mono, Century Gothic, Franklin Gothic…) por equivalentes dos dois sistemas (Arial Narrow, Arial, Courier New, Verdana…); o HTML e o PDF não mudam.
589
590
 
590
591
  ## Widgets próprios
591
592
 
@@ -1095,7 +1096,7 @@ Use os elementos nativos para textos, logos/imagens, figuras e motion. Não entr
1095
1096
 
1096
1097
  ### Trabalho no Studio
1097
1098
 
1098
- Cada aba do editor mantém sua apresentação independente. Enquanto o chat processa, a entrada permanece disponível e o botão de enviar vira Parar; cancelar impede aplicar resposta pendente. Slides podem ser copiados para outra apresentação com Copiar/Colar na faixa Início (Ctrl+C/Ctrl+V com foco na lista); arquivos locais referenciados acompanham a cópia. O slide passa a usar o tema da apresentação de destino.
1099
+ Cada aba do editor mantém sua apresentação independente. Enquanto o chat processa, a entrada permanece disponível e o botão de enviar vira Parar; cancelar impede aplicar resposta pendente. Slides podem ser copiados para outra apresentação com Copiar/Colar na faixa Início, no botão direito da miniatura ou Ctrl+C/Ctrl+V com foco na lista (vale para vários de uma vez); arquivos locais referenciados acompanham a cópia. Na lista, Ctrl+click (ou Shift+click, Ctrl+setas) seleciona vários; Delete exclui o bloco com Desfazer. O slide passa a usar o tema da apresentação de destino.
1099
1100
 
1100
1101
  ## Documentos e direção contextual
1101
1102
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sagadeck",
3
- "version": "1.2.0",
3
+ "version": "1.3.1",
4
4
  "description": "Gerador de apresentações: YAML -> HTML animado + PowerPoint editável, com temas e figuras geradas na hora.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/ai/deck-ai.js CHANGED
@@ -47,6 +47,7 @@ Siga ESTRITAMENTE a referência abaixo: use só layouts, elementos, campos e fig
47
47
  - Experiências usam entradas compartilhadas e fórmulas reais (calc), cenários, previsão, comparação e explicação. Dados fictícios levam illustrative: true. Consulte os estados extremos; não esconda erro matemático em gráfico bonito.
48
48
  - Ao reformular composição ou criar uma experiência pelo chat, inclua review: true para a conferência visual. Preserve exemplos, ordem e conteúdo; uma crítica de desenho não autoriza apagar informação.
49
49
  - Adapte tom, estilo, tema, densidade e recursos ao conteúdo, público e objetivo. Estas orientações são guias contextuais: o pedido explícito prevalece. Antes de entregar, confira fidelidade, legibilidade, variedade e pertinência; corrija problemas observados sem esperar que a pessoa peça. Papers fiéis preservam seus elementos visuais; resumos executivos podem selecionar evidências. Não transforme toda metodologia em desenho se o original já comunica bem.
50
+ - Paper científico anexado (congresso, defesa, aula): metodologia merece slides de verdade, respeitando a natureza do paper — SE tiver método quantitativo, detalhe modelos com parâmetros e calibração, métricas com valores E fontes (tabela comparativa quando houver); se for teórico, revisão ou qualitativo, apresente a estrutura do argumento sem inventar números. Números sempre do próprio texto com a origem ("n=1.403", "NSE 0,90–0,96"). Prefira as figuras extraídas do documento às páginas inteiras como imagem; página inteira só em último caso. Todo o conteúdo (títulos, legendas que você escrever, notes) num ÚNICO idioma: o do paper, salvo pedido explícito em contrário.
50
51
  - Capacidades reais: quando pesquisa/comandos estiverem disponíveis, use-os antes de alegar falta de acesso à web. Geradores de imagem podem produzir texto legível: não declare impossibilidade geral; avalie o resultado e corrija grafia. Prefira texto editável quando útil, mas uma imagem inteira com texto é válida se solicitada.
51
52
  - Direção de arte: se pedirem propostas visuais, use variants com três composições do MESMO conteúdo real; varie hierarquia, enquadramento e tipografia, não só cor. Cada opção pode levar direction: {theme, rationale}. Escolher aplica o tema ao deck, mantendo outros slides intactos.
52
53
  - Para integrar texto ao cenário, componha camadas editáveis em canvas (imagem, texto, primeiro plano). continuity no mesmo objeto em slides consecutivos mantém continuidade espacial. Prefira uma imagem estática forte quando movimento não explicar nada.
@@ -57,12 +58,14 @@ Siga ESTRITAMENTE a referência abaixo: use só layouts, elementos, campos e fig
57
58
  - Nunca invente fatos: nada de número, estatística, pesquisa, data, nome ou citação que não esteja no pedido ou no material. Se um número ajudaria, use um exemplo claramente hipotético ("por exemplo, num time de 5 pessoas…") ou fique sem número. Não invente \`author\` nem \`date\` (nem "Seu Nome"): omita se o pedido não disser.
58
59
  - Prefira figuras geradas (icon, picto, diagram, chart) a listas de bullets. Ícones são do Lucide, nomes em inglês kebab-case (ex.: rocket, shield-check, trending-up).
59
60
  - Ensinar como algo FUNCIONA (algoritmo, método, conta, receita técnica): o mecanismo inteiro, passo a passo, e não só "o que é". Um exemplo concreto com números atravessa a aula (as mesmas entradas do começo ao fim), cada passo mostra a conta feita na tela (\`solution\`, \`steps\`, \`table\`, \`algo\`), uma simulação deixa mexer (\`calc\` ou \`science\` com controles: muda a entrada, vê o resultado) e, se ele aprende ou se ajusta, mostre uma rodada desse ajuste com os números. Público leigo ou criança: simplifique as PALAVRAS (analogias do dia a dia, "pontos por pista" em vez de "pesos"), nunca os PASSOS; uma ideia por slide, frases curtas, um exercício para fazer junto e ilustrações nos momentos-chave.
60
- - Varie os layouts ao longo do deck; capa (cover) no início e encerramento (end) no fim quando fizer sentido.
61
+ - ANTI-FEIJÃO-COM-ARROZ: varie os layouts de verdade; capa (cover) no início e encerramento (end) no fim quando fizer sentido. Regras duras: nunca 2 slides seguidos com o mesmo layout (salvo série intencional); em 6+ slides use 5+ layouts diferentes com 1 slide de impacto (headline, number, statement, full, image, question); no máximo 45% do deck em listas/cartões (cards, list, steps, stats, bento, agenda). Conteúdo manda no layout, não o contrário (ver o mapa em "Layouts válidos").
61
62
  - Slide denso (documentação, referência, números): feche com 1 takeaway em ==destaque== e use o elemento \`aviso\` (tipos: \`importante\`, \`atencao\`, \`dica\`, \`perigo\`) para o que não pode passar batido; grife ==palavras-chave== no texto corrido em vez de encher de negrito.
62
63
  - Escreva no idioma do pedido do usuário.
63
- - Layouts válidos (\`layout:\`): ${Object.keys(LAYOUTS).join(", ")}. Processo, fluxo, arquitetura ou UML: layout \`diagram\` (Mermaid). Itens em volta de uma ideia (desafios, frentes, caminhos): layout \`infographic\`. Gráficos: layout \`chart\` (ou \`science\`). Diagramas simples (\`diagram: loop/flow/venn…\`) e gráficos também existem como ELEMENTOS, dentro de figure/content/side.
64
+ - Layouts válidos (\`layout:\`): ${Object.keys(LAYOUTS).join(", ")}. Conteúdo → layout (use o que comunica, não o mais fácil): processo/fluxo/arquitetura/UML: layout \`diagram\` (Mermaid); itens em volta de uma ideia: layout \`infographic\`; gráfico: \`chart\` (fórmula/curva/3D: \`science\`); datas: \`calendar\` (grade) ou \`timeline\` (marcos); página densa estilo revista: \`poster\`; jornada+problema+solução+painel: \`onepage\`; status semanal: \`status\`; definição: \`definition\`; citação: \`quote\`; número gigante: \`number\`; KPIs: \`stats\`; A×B: \`compare\`; 2×2: \`matrix\`; pergunta à plateia: \`question\`/\`poll\`; código: \`code\`, guiado: \`codewalk\`; screenshot com foco: \`spotlight\`; API ao vivo: \`api\`; conta/calculadora: \`solution\`/\`calc\`; algoritmo animado: \`algo\`; redes/rotas: \`graphlab\`; código executável pelo aluno: \`codelab\`; experimento: \`playground\`; decisão: \`decisionlab\`; duelo/terminais/turnos: \`duel\`/\`terminals\`/\`turns\`; carrossel: \`carousel\`; funil/pirâmide/agenda: \`funnel\`/\`pyramid\`/\`agenda\`; grade/consulta: \`bento\`/\`mosaic\`/\`ribbon\`/\`dossier\`; tipografia em cena: \`kinetic\`; texto no cenário: \`scenography\`; caminhos com volta ao mapa: \`hub\`+\`goto\`/\`back\`/\`next\`; manchete: \`headline\`; imagem total: \`full\`/\`image\`. Diagramas simples (\`diagram: loop/flow/venn…\`) e gráficos também existem como ELEMENTOS, dentro de figure/content/side.
64
65
  - Temas disponíveis: ${themes}.
65
66
  - YAML: coloque entre aspas duplas todo texto que comece com marcação (\`**\`, \`*\`, \`==\`, \`^^\`, \`~~\`, \`[\`) ou que contenha ": ".
67
+ - Em sequências inline ([{...}]): coloque entre aspas todo valor de texto com vírgula ("3,5 peixes por m³"). Sem aspas a vírgula parte o objeto e o dado sai truncado.
68
+ - Números com milhar em \`stats\`/\`number\`: escreva sem separador ("7400") ou por extenso — nunca "7,400" solto (vira dado quebrado no slide).
66
69
  ${images
67
70
  ? `- Você PODE pedir ilustrações geradas por IA com \`image_prompt: "descrição visual detalhada, em inglês"\` no lugar de \`image\` — por exemplo \`figure: { image_prompt: "...", fit: cover }\` num split/cover, ou um slide \`layout: image\` ou \`full\` com \`image_prompt\`. No máximo ${maxImages} imagens novas por resposta.
68
71
  - Gerar imagem custa dinheiro e leva segundos. Leia o que a pessoa pediu:
@@ -71,11 +74,18 @@ ${images
71
74
  - não falou de imagem → VOCÊ decide, do mesmo jeito: ilustre o que fica mais claro com uma imagem (um objeto, um lugar, uma cena, um processo físico, a capa) e deixe o resto com ícones, gráficos e diagramas do sagadeck;
72
75
  - pediu sem imagens (ou só ícones) → não gere.`
73
76
  : "- NÃO use `image_prompt` nem imagens externas; use as figuras geradas do sagadeck."}
77
+ - PENSAMENTO CRÍTICO NA ILUSTRAÇÃO: cada imagem gerada tem que retratar o CONTEÚDO ESPECÍFICO do pedido e do material
78
+ (o lugar, o objeto, o processo nomeado — ex.: um paper sobre a bacia do Aricanduva pede o Aricanduva, rio urbano da
79
+ zona leste de São Paulo, nunca "uma cidade genérica"). Genérico bonito é ERRO, pior que ausência: se não der para
80
+ retratar o específico com fidelidade, prefira a figura do material, um diagrama, um mapa ou nada. A capa ilustrada
81
+ ancora no caso concreto do briefing, não num símbolo vago do tema. O alt descreve o que a imagem mostra de verdade.
82
+ - LEGENDA SEM NÚMERO: a legenda de figura, diagrama ou esquema é só a descrição curta do que se vê (sem "Figure N:", "Figura N:", "Table N:"). Nada de numerar visual — nem os do paper, nem os criados por você.
83
+ - statement é frase curta de impacto (até ~12 palavras); texto mais longo vai em split, quote ou lead — nunca jogue parágrafo em statement gigante.
74
84
  - Nunca invente campos começando com "_" e não use caminhos de imagem que não existam no deck.
75
85
  - Referências visuais REAIS são diferentes de ilustrações: para aplicações, tutoriais, personagens e produtos, use fotos/gráficos observados nas fontes com web_image: {url: URL_EXATA, source: PAGINA, alt: DESCRICAO}, ou uma captura pública com web_capture: {url: PAGINA, selector: SELETOR_OPCIONAL}. Não adivinhe URLs ou coordenadas de cliques. O motor importa a referência para a biblioteca e a apresentação funciona offline. Falhas aparecem; não substitua screenshot por desenho inventado. Não peça captura de páginas privadas ou autenticadas.
76
86
  - Para ensinar relações e redes (logística, dependências, pessoas, conhecimento), considere graphlab com nós semânticos e um experimento de rota/bloqueio. Para algoritmos, considere codelab com um desafio que o aluno possa alterar e executar. A escolha e o exemplo vêm do conteúdo, não do nome do assunto. HTTPX/NetworkX reais ficam em api/codewalk ou comandos Python; codelab interpreta Python simples, sem essas bibliotecas.
77
87
 
78
- DIREÇÃO E VARIEDADE: escolha a composição pela função narrativa de cada slide, não pelo layout mais fácil. Antes de gerar, planeje a cadência: uma cena concreta, exploração/demonstração, explicação, evidência e convite conforme o conteúdo. Não transforme tudo em cards, bento ou mosaicos. Uma jornada pede sequência/ribbon/poster; relações pedem diagrama/graphlab; screenshot pede spotlight; experimento pede calc/playground; uma tese pede section/statement; conteúdo técnico pede codewalk/codelab/api. Varie escala e composição sem inventar antagonismo, dados, quizzes ou interatividade decorativa. Preserve exemplos, ordem e cadência em revisões pontuais. Ao revisar, procure repetição visual mesmo quando nomes de layouts diferem. Respeite fundo claro, paleta e restrições pedidos; não force escuro/neon só porque a pessoa disse futurista. Uma animação 2D ou crossfade não equivale a vídeo 3D ou morph volumétrico. Nunca anuncie vídeo gerado quando só existe prompt, poster ou script.
88
+ DIREÇÃO E VARIEDADE: escolha a composição pela função narrativa de cada slide, não pelo layout mais fácil. Antes de gerar, planeje a cadência: uma cena concreta, exploração/demonstração, explicação, evidência e convite conforme o conteúdo. Não transforme tudo em cards, bento ou mosaicos. Se o resultado sair com cara de feijão com arroz (só cards/list/statement), refaça a cadência com os layouts do mapa antes de entregar. Uma jornada pede sequência/ribbon/poster; relações pedem diagrama/graphlab; screenshot pede spotlight; experimento pede calc/playground; uma tese pede section/statement; conteúdo técnico pede codewalk/codelab/api. Varie escala e composição sem inventar antagonismo, dados, quizzes ou interatividade decorativa. Preserve exemplos, ordem e cadência em revisões pontuais. Ao revisar, procure repetição visual mesmo quando nomes de layouts diferem. Respeite fundo claro, paleta e restrições pedidos; não force escuro/neon só porque a pessoa disse futurista. Uma animação 2D ou crossfade não equivale a vídeo 3D ou morph volumétrico. Nunca anuncie vídeo gerado quando só existe prompt, poster ou script.
79
89
 
80
90
  === REFERÊNCIA DO YAML ===
81
91
  ${reference()}`;
@@ -85,22 +95,31 @@ ${reference()}`;
85
95
  // Parsing e validação da resposta
86
96
  // ---------------------------------------------------------------------------------------------
87
97
 
88
- // Para que serve o material: o modelo decide (JSON curto) e diz se falta informação para decidir
98
+ // Para que serve o material: o modelo EXTRAI do pedido primeiro e só pergunta o que faltar de verdade.
99
+ // Regra: nunca pergunte o que o pedido já respondeu (com todas as letras ou por implicação clara); só
100
+ // pergunte o que for necessário para decidir e a pessoa não informou — ou o que vale confirmar por julgamento.
89
101
  export async function decidePurpose(briefing, materials = []) {
90
102
  const res = await chat([
91
103
  { role: "system", content: `Você decide PARA QUE SERVE um material de apresentação, lendo o pedido. São só dois usos:
92
- - palestra: PARA APRESENTAR — alguém fala e a plateia assiste (palestra, reunião, pitch, aula expositiva, workshop). Letra grande, respiro, pouco texto na tela, para não dar sono.
104
+ - palestra: PARA APRESENTAR — alguém fala e a plateia assiste (palestra, reunião, pitch, aula expositiva, workshop, conferência, congresso). Letra grande, respiro, pouco texto na tela, para não dar sono.
93
105
  - consulta: PARA ESTUDAR DEPOIS — o material vai ser enviado e a audiência usa como fonte de estudo (apostila, documentação, guia, curso para guardar). Conteúdo denso, com bastante texto no slide.
94
- Se o pedido já disser com todas as letras quanto texto quer ("bastante texto", "explicação completa", "pouco texto", "só tópicos"), isso resolve: não pergunte, escolha (muito texto: consulta; pouco: palestra) e diga em "texto": "muito" ou "pouco".
95
- Se o pedido não permitir decidir com segurança — típico: workshop, treinamento ou "uma apresentação sobre X" sem dizer se o material é só para a sessão ou se vai ser enviado para o pessoal estudar depois —, NÃO suponha: faça UMA pergunta curta com 2 opções curtas (apresentar × estudar depois).
96
- Responda só com JSON: {"purpose": "…" ou null, "texto": "muito" | "pouco" | null, "why": "motivo curto", "pergunta": "…" ou null, "opcoes": ["…"]}` },
106
+ Primeiro EXTRAIA do pedido (cite a evidência em "extraido"):
107
+ - uso: "apresentar", "congresso", "conferência", "pitch", "defesa", "aula" → palestra, sem perguntar; "apostila", "guia", "material de estudo", "para ler depois" → consulta, sem perguntar.
108
+ - texto: se o pedido disser com todas as letras quanto texto quer ("bastante texto", "explicação completa", "pouco texto", "só tópicos"), isso resolve: não pergunte, registre em "texto" ("muito": consulta; "pouco": palestra).
109
+ - idioma: se o pedido disser o idioma ("em inglês", "in English"), registre em "idioma" ("pt", "en" ou outro código) e não pergunte. Se NÃO disser mas a ocasião for internacional (congresso/conferência internacional, público estrangeiro, paper em inglês), proponha a confirmação em "perguntaIdioma" ("Confirmar o idioma?" com opcoes ["Português", "English"]).
110
+ Só faça a pergunta de uso ("pergunta") se o pedido NÃO permitir decidir com segurança — típico: workshop, treinamento ou "uma apresentação sobre X" sem dizer se o material é só para a sessão ou se vai ser enviado para estudar depois. Nunca pergunte o que já foi extraído.
111
+ Responda só com JSON: {"purpose": "…" ou null, "texto": "muito" | "pouco" | null, "idioma": "pt" | "en" | null, "extraido": "evidências curtas do pedido", "why": "motivo curto", "pergunta": "…" ou null, "opcoes": ["…"], "perguntaIdioma": "…" ou null}` },
97
112
  { role: "user", content: `${materials.length ? `(há ${materials.length} material(is) anexado(s))\n` : ""}Pedido:\n"""\n${briefing}\n"""` },
98
113
  ], { temperature: 0.1 });
99
114
  const m = String(res.text || "").match(/\{[\s\S]*\}/);
100
115
  if (!m) return null;
101
116
  const j = JSON.parse(m[0]);
102
117
  if (j.pergunta) return { question: { question: String(j.pergunta), options: (Array.isArray(j.opcoes) ? j.opcoes : []).map(String).filter(Boolean).slice(0, 4) } };
103
- return PURPOSES[j.purpose] ? { purpose: j.purpose, texto: ["muito", "pouco"].includes(j.texto) ? j.texto : null, why: String(j.why || "").slice(0, 200) } : null;
118
+ const out = PURPOSES[j.purpose] ? { purpose: j.purpose, texto: ["muito", "pouco"].includes(j.texto) ? j.texto : null, why: String(j.why || "").slice(0, 200) } : null;
119
+ if (!out) return null;
120
+ if (j.idioma === "pt" || j.idioma === "en") out.idioma = j.idioma;
121
+ if (j.perguntaIdioma && !out.idioma) out.languageQuestion = { question: String(j.perguntaIdioma).slice(0, 300), options: ["Português", "English"] };
122
+ return out;
104
123
  }
105
124
 
106
125
  // A IA preferiu perguntar antes de gerar: bloco ```pergunta com {"pergunta", "opcoes"}
@@ -498,7 +517,8 @@ const EDIT_RULES = `Regras de edição:
498
517
  - Pictos (human, crowd, scene…) são desenhados pelo motor num estilo fixo: você só controla os parâmetros (pose, sign, name, count…). Se a reclamação é sobre o traço de um picto em si, diga isso com franqueza e ofereça alternativas (ex.: outro tipo de figura no deck todo, ou ilustrações geradas se as imagens estiverem ligadas) — de preferência perguntando antes de mudar vários slides.
499
518
  - Se a mudança certa afetar muitos slides de um jeito que o usuário talvez não espere, PERGUNTE antes (responda sem bloco yaml).
500
519
  - Quando houver imagens do slide renderizado, OLHE para elas antes de responder: elas mostram o que a plateia vê (sobreposição, texto cortado, ordem dos cliques). Descreva o que você vê em vez de perguntar o que o usuário quis dizer.
501
- - Imagens coladas pelo usuário são referência (ex.: "recrie este slide"): reproduza a estrutura, a hierarquia e o conteúdo com os recursos do sagadeck.`;
520
+ - Imagens coladas pelo usuário são referência (ex.: "recrie este slide"): reproduza a estrutura, a hierarquia e o conteúdo com os recursos do sagadeck.
521
+ - Tabela colada (CSV/Excel) pedindo gráfico: NÃO monte um chart direto. Devolva variants com 2 a 3 TIPOS de gráfico (ex.: barras, linhas, pizza) usando OS MESMOS dados, um por opção com o tipo no label — a pessoa escolhe vendo a prévia. Só faça direto se ela já disse o tipo.`;
502
522
 
503
523
  // Você decide, a cada mensagem, o que a pessoa quer — não existe "modo".
504
524
  const CONVERSATION_RULES = `Como responder (você decide pelo que a pessoa quer AGORA, lendo a mensagem e a conversa):
@@ -507,6 +527,16 @@ const CONVERSATION_RULES = `Como responder (você decide pelo que a pessoa quer
507
527
  (dado, história, exemplo, pergunta para a plateia, jeito visual de mostrar), pergunte o que falta (no máximo 2 perguntas).
508
528
  Curto: até ~150 palavras. Termine com um bloco \`\`\`opcoes com 2 a 4 respostas curtas (até 8 palavras) que a pessoa poderia clicar,
509
529
  uma por linha — inclua uma que peça para aplicar o que foi sugerido (ex.: "Pode fazer isso").
530
+ 1a. PERGUNTA sobre o assunto ou o material ("o que é…?", "por que…?", "me explica…?", "como funciona…?"): é CONVERSA, não
531
+ pedido de slide. RESPONDA a pergunta com o que você sabe (e com o material anexado, se houver), sem bloco yaml e sem
532
+ tocar nos slides. Só vira AÇÃO com verbo explícito de fazer ("crie", "adicione", "gere", "monte", "transforme em slides",
533
+ "aplica o que combinamos").
534
+ 1b. BRAINSTORM ANTES DE GERAR — a pessoa diz que vai pedir a geração depois ("primeiro vamos discutir", "depois eu peço
535
+ para gerar", "só ideias por enquanto"): NÃO gere nada parcial nem antecipe slides. Discuta, combine e CONFIRME o plano;
536
+ a geração acontece quando ela pedir. Se ela aprovar uma sugestão tua ou vocês fecharem um ponto ("fechado", "isso mesmo",
537
+ "pode ser assim"), grave na hora com patch \`deck: { context: { decisoes: [...as que já havia, mais a nova] } }\`: a lista de
538
+ decisões é o CONTRATO do que gerar depois — nunca apague nem resuma um item sem ela pedir. Gerar depois usa a conversa
539
+ inteira MAIS essas decisões; nada combinado se perde.
510
540
  2. AÇÃO — pediu para corrigir, alterar, adicionar, remover, ou autorizou o que foi combinado ("pode fazer", "manda ver", "aplica"):
511
541
  faça, com o bloco yaml de patch. Se veio de uma conversa, aplique exatamente o que foi combinado nela.
512
542
  3. VERSÕES — pediu alternativas, opções ou "N versões" de um slide: devolva um bloco yaml com \`variants\` (2 a 4 versões completas e
@@ -723,6 +753,8 @@ export function sanitizeCheck(spec, indices) {
723
753
  // Memória da conversa: as últimas trocas vão inteiras; das mais antigas, o que a PESSOA disse fica (compactado,
724
754
  // em ordem), para o modelo não esquecer o que foi pedido lá no começo. As respostas antigas da IA saem: o que ela
725
755
  // fez está no próprio deck. A conversa é de um deck só (o Studio guarda uma por apresentação).
756
+ // GARANTIA ANTI-RESUMO: o histórico nunca é reescrito por modelo — só há corte por tamanho (preserva o começo e o
757
+ // fim, nunca o meio preferido), e o contrato sem perdas são as decisões travadas em context.decisoes no deck.
726
758
  // ---------------------------------------------------------------------------------------------
727
759
  const RECENT_TURNS = 16, OLD_MSG_CHARS = 700, MEMORY_CHARS = 9000;
728
760
  export function conversationFor(history = []) {
@@ -1040,6 +1072,8 @@ export async function generateDeck(briefing, { theme, slides, duration, style, d
1040
1072
  say("entendendo para que serve o material…");
1041
1073
  decided = await decidePurpose(briefing, materials).catch(() => null);
1042
1074
  if (decided?.question) return { question: decided.question };
1075
+ // ocasião internacional sem idioma informado: confirma antes de gerar no idioma errado
1076
+ if (decided?.languageQuestion && (language === "auto" || !language)) return { question: decided.languageQuestion };
1043
1077
  }
1044
1078
  // pesquisa: a IA decide se o que ela sabe basta; se não, busca, escolhe as fontes, lê e anota (src/research)
1045
1079
  let researchReport;
@@ -1072,7 +1106,10 @@ export async function generateDeck(briefing, { theme, slides, duration, style, d
1072
1106
  decided?.texto ? `A pessoa disse quanto texto quer: ${decided.texto} texto na tela (grave maxWords: ${decided.texto === "muito" ? 200 : 35}).` : "",
1073
1107
  answer ? `Resposta da pessoa à sua pergunta sobre o material: ${answer}` : "",
1074
1108
  author ? `Autor padrão das preferências: ${author}. É apenas um padrão; autoria ou organização explicitamente indicada no pedido tem prioridade. Grave a atribuição solicitada em author para não aplicar este padrão.` : "",
1075
- language && language !== "auto" ? `Escreva todo o conteúdo em ${language}.` : "",
1109
+ // idioma respondido ou extraído do pedido vale como instrução (inclui a resposta à confirmação de idioma)
1110
+ /english|ingl[eê]s/i.test(answer || "") || (!language || language === "auto") && decided?.idioma === "en" ? "Escreva todo o conteúdo em inglês."
1111
+ : /portuguese|portugu[eê]s/i.test(answer || "") || (!language || language === "auto") && decided?.idioma === "pt" ? "Escreva todo o conteúdo em português."
1112
+ : language && language !== "auto" ? `Escreva todo o conteúdo em ${language}.` : "",
1076
1113
  ].filter(Boolean).join("\n");
1077
1114
  const starter = { title: "Nova apresentação", ...(theme ? { theme } : {}), slides: [{ layout: "cover", title: "Nova apresentação" }] };
1078
1115
  const instruction = `Crie a apresentação inteira que a pessoa pediu abaixo. O deck atual é só um começo em branco: devolva preferencialmente o deck completo com slides como LISTA YAML. Se usar insert, todos os novos entram after: 1 (referência ao único slide original, nunca a um slide que você ainda está inserindo). Grave no deck o title, o theme, o purpose e, se o pedido disser a ocasião, o context.
@@ -1114,9 +1151,14 @@ Faça agora, sem oferecer versões. Só pergunte se não der mesmo para saber o
1114
1151
  if (reviewCheck) {
1115
1152
  say('Conferindo a apresentação e os estados interativos…');
1116
1153
  quality = await reviewCheck(spec, spec.slides.map((_, i) => i));
1154
+ // problema idêntico ao de rodada anterior = tentativa sem efeito: não queima token de novo
1155
+ const attempted = new Set();
1117
1156
  for (let round = 0; quality.issues.length && round < 3; round++) {
1157
+ const fresh = quality.issues.filter(x => !attempted.has(JSON.stringify(x)));
1158
+ if (!fresh.length) { say('Revisão: nada novo para corrigir; encerro as tentativas.'); break; }
1159
+ fresh.forEach(x => attempted.add(JSON.stringify(x)));
1118
1160
  try {
1119
- const repaired = await editDeck({ spec, instruction: `Corrija somente os problemas observados. Não elimine recursos explicitamente pedidos para resolver uma colisão: reposicione, redimensione ou reorganize a composição mantendo a função. Preserve conteúdo e ordem do briefing (${briefing}): ${JSON.stringify(quality.issues)}.`, images, imageOptions, materials, drawCheck, reviewDepth: 1, repairSlides: quality.issues.map(x => x.slide - 1), onProgress: onEvent });
1161
+ const repaired = await editDeck({ spec, instruction: `Corrija somente os problemas observados. Não elimine recursos explicitamente pedidos para resolver uma colisão: reposicione, redimensione ou reorganize a composição mantendo a função. Preserve conteúdo e ordem do briefing (${briefing}): ${JSON.stringify(fresh)}.`, images, imageOptions, materials, drawCheck, reviewDepth: 1, repairSlides: fresh.map(x => x.slide - 1), onProgress: onEvent });
1120
1162
  const nextQuality = await reviewCheck(repaired.spec, repaired.spec.slides.map((_, i) => i));
1121
1163
  spec = repaired.spec; quality = nextQuality;
1122
1164
  } catch (error) {