sagadeck 1.11.5 → 1.11.6

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.
package/docs/ANDAMENTO.md CHANGED
@@ -8,6 +8,33 @@ começar. Trabalho em curso vai para o GitHub (commit + push) a cada etapa, nunc
8
8
 
9
9
  ---
10
10
 
11
+ ## 1.11.6: rodada de uso real — 23/10/2026 (branch `claude/parada-e-fotos`)
12
+
13
+ Quatro pedidos reais no sagadeck-dev (conta de teste `verificacao`, tópico "Rodada de uso real"), pelo caminho da
14
+ biblioteca (Nova › Com IA, `POST /api/library/decks/ai`), com o modelo do servidor (deepseek-v4.1-flash): aula de
15
+ vigas de concreto (20 min, com exemplo numérico), pitch de automação da conciliação (5 min), guia de Git (consulta) e
16
+ palestra sobre burnout (15 min). Os quatro saíram em 4 a 9 min cada, variados (11, 3, 12 e 9 layouts diferentes). A
17
+ revisão automática deixou avisos; cada um foi conferido no YAML e na foto do slide:
18
+ - **Bug: `stats` ignorava `prefix`/`suffix`** (o pitch mostrava "4" por "4%"). Corrigido em `elements.js`, com
19
+ REFERENCIA e formulário do Studio. Teste no `engine.test.js`.
20
+ - **Bug: código quebrava a linha no hífen** (`--no-` / `edit` no dossier do guia). `noHyphenBreak` (markup.js) põe
21
+ cada hífen num `<span class="hy">`, e o CSS um word joiner por `::after` (não vai na cópia); vale no `code()` e no
22
+ código do dossier. Testes: `engine.test.js` e `test/code-hyphen.test.js` (no Chrome, com o slide do guia; falha sem a
23
+ correção).
24
+ - **Bug: barra com `color: hi` usava o marca-texto claro** (palestra, tema editorial: destaque mais fraco que o fundo).
25
+ `charts.js` usa `--hi-ink`, como o texto. Teste no `engine.test.js`.
26
+ - **Bug: variedade cobrava impacto/respiro/tom de material de consulta**, contra a REFERENCIA. `variety.js` só cobra
27
+ isso fora de `purpose: consulta`. Teste no `engine.test.js`.
28
+ - **Alarmes falsos da revisão (não mexidos)**: "barras de 48% e 43% quase iguais" (estão proporcionais) e "nota em
29
+ cinza escuro no preto" (é cinza claro, legível). Ficam como observação: a revisão por imagem ainda erra em proporção
30
+ e contraste; se voltar a aparecer, olhar a resolução das fotos que vão para ela.
31
+ - Conteúdo, para a IA corrigir (Corrigir tudo com IA): coeficiente 0,68 sem a origem (0,85 × 0,8) e diagrama pequeno
32
+ na aula; quarto risco só nas notas do pitch.
33
+ - **Testes com o modelo de verdade (1.11.6 é múltipla de 3)**: 25 de 27 de primeira em 25 min, nenhum quebrado.
34
+ Instáveis (passaram na repetição): "refinamento: conversa e depois 'pode fazer'" (o modelo trocou o slide por outro
35
+ texto em vez de aplicar o combinado; já tinha aparecido antes) e "gerar deck: sai variado" (estourou os 10 min do
36
+ teste esperando o provedor; não é a regra da variedade, que só mudou para `consulta`).
37
+
11
38
  ## 1.11.5: parada do servidor, biblioteca no OneDrive — 23/10/2026 (branch `claude/parada-e-fotos`)
12
39
 
13
40
  - **Parada**: o Playwright, ao abrir o Chrome, instala um tratador de SIGTERM que só fecha o navegador; com ele, o
@@ -93,7 +93,7 @@ Tabela em markdown no meio de um texto (`problem`, `body`, `text`, passo do `sol
93
93
  | `hub` | `kicker, title, question, options: [{icon, title, text, meta, goto}], cols, build` | mapa de caminhos: cada opção leva (`goto`) à parte da apresentação daquele caminho; ver [Navegação por caminhos](#navegação-por-caminhos-hub-goto-back-next) |
94
94
  | `onepage` | `kicker, title, subtitle, journey: [texto ou {icon, title, text, goto}], journeyTitle, problem: {title, text, numbers: [{value, label}], items}, solution: {…}, dashboard: {numbers: [{value, label, trend, title}], figures: [gráfico ou ufmap, com title]}` | tudo numa página (jornada, problema, solução, painel); ver [One-page](#one-page-onepage) |
95
95
  | `status` | `kicker, title, health: ok\|risco\|atrasado, healthLabel, progress (0–100), highlight, done, doing, blocked, risks, upcoming: [texto ou {text, owner, due}], shots: [{image, caption}]` | status semanal; ver [Status semanal](#status-semanal-status) |
96
- | `stats` | `title, stats: [{value, label, text, icon, trend, trendUp, color}], cols, build` | indicadores (KPIs) em cartões, com tendência (`trend: "+12%"`) |
96
+ | `stats` | `title, stats: [{value, prefix, suffix, label, text, icon, trend, trendUp, color}], cols, build` | indicadores (KPIs) em cartões, com tendência (`trend: "+12%"`) |
97
97
  | `timeline` | `title, events: [{when, title, text, tag, more}], highlight, after, build` | linha do tempo; `more` põe um botão + no marco que abre um balão (ver abaixo) |
98
98
  | `chart` | `title, chart: {…}, side (texto ou elemento), chartHeight` | gráfico + comentário |
99
99
  | `table` | `title, head, rows, style, color, highlight, total, side, caption, source` | tabela de verdade nas cores do tema |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sagadeck",
3
- "version": "1.11.5",
3
+ "version": "1.11.6",
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": {
@@ -1,4 +1,4 @@
1
- import {md,esc} from './markup.js';
1
+ import {md,esc,noHyphenBreak} from './markup.js';
2
2
  import {iconSVG} from './figures/icons.js';
3
3
  export function adaptiveGrid(items=[],variant='mosaic') {
4
4
  const n=items.length;
@@ -14,5 +14,5 @@ export function adaptiveHTML(s,variant) {
14
14
  <div class="adaptive-index f-label">${item.icon?iconSVG(item.icon,{size:38}):String(i+1).padStart(2,'0')}</div>
15
15
  ${item.value!=null?`<div class="adaptive-value t f-display">${md(String(item.value))}</div>`:''}
16
16
  <h3 class="t f-heading">${md(item.title||'')}</h3>${item.text?`<div class="adaptive-text t f-body">${md(item.text)}</div>`:''}
17
- ${item.code?`<pre class="adaptive-code f-mono">${esc(item.code)}</pre>`:''}${item.foot?`<div class="adaptive-foot t f-label">${md(item.foot)}</div>`:''}</article>`).join('')}</div>`;
17
+ ${item.code?`<pre class="adaptive-code f-mono">${noHyphenBreak(esc(item.code))}</pre>`:''}${item.foot?`<div class="adaptive-foot t f-label">${md(item.foot)}</div>`:''}</article>`).join('')}</div>`;
18
18
  }
package/src/ai/variety.js CHANGED
@@ -25,10 +25,16 @@ export function varietyReport(spec) {
25
25
  if (n >= 6) {
26
26
  const want = Math.min(8, Math.ceil(n * 0.5));
27
27
  if (distinct < want) problems.push(`só ${distinct} layouts diferentes em ${n} slides de conteúdo (bom: ${want} ou mais)`);
28
- const listy = body.filter((s) => LISTY.has(s.layout)).length;
29
- if (listy / n > 0.45) problems.push(`${listy} de ${n} slides são listas/cartões — falta respiro (manchete, número grande, imagem, pergunta)`);
30
- if (!body.some((s) => IMPACT.has(s.layout))) problems.push("nenhum slide de impacto (manchete, número grande, frase, página inteira, pergunta)");
31
- if (new Set(slides.map((s) => s.tone)).size === 1) problems.push(`todos os slides no mesmo tom (${slides[0].tone}) — alterne com escuro/destaque nos momentos-chave`);
28
+ // material de consulta (purpose: consulta) é para estudar depois: sem "número de impacto" nem slide só de título
29
+ // (REFERENCIA), e lista é o normal. Cobrar respiro e impacto ali era aviso falso, e a revisão empurrava a IA a pôr
30
+ // slides que o propósito pede para não ter (rodada de uso real, guia de Git, 23/10/2026)
31
+ if (spec?.purpose !== "consulta") {
32
+ const listy = body.filter((s) => LISTY.has(s.layout)).length;
33
+ if (listy / n > 0.45) problems.push(`${listy} de ${n} slides são listas/cartões — falta respiro (manchete, número grande, imagem, pergunta)`);
34
+ if (!body.some((s) => IMPACT.has(s.layout))) problems.push("nenhum slide de impacto (manchete, número grande, frase, página inteira, pergunta)");
35
+ // consulta pode ficar toda clara (imprime e lê bem); a regra do tom é de palco
36
+ if (new Set(slides.map((s) => s.tone)).size === 1) problems.push(`todos os slides no mesmo tom (${slides[0].tone}) — alterne com escuro/destaque nos momentos-chave`);
37
+ }
32
38
  }
33
39
  const counts = {};
34
40
  body.forEach((s) => (counts[s.layout] = (counts[s.layout] || 0) + 1));
package/src/elements.js CHANGED
@@ -9,7 +9,7 @@ import katex from "katex";
9
9
  import { formatNumber, decimalSeparator } from "./locale.js";
10
10
  import fs from "node:fs";
11
11
  import path from "node:path";
12
- import { md, esc, plain } from "./markup.js";
12
+ import { md, esc, plain, noHyphenBreak } from "./markup.js";
13
13
  import { iconSVG } from "./figures/icons.js";
14
14
  import { picto } from "./figures/pictos.js";
15
15
  import { diagram } from "./figures/diagrams.js";
@@ -551,7 +551,7 @@ export function stats(e, ctx) {
551
551
  ${iconHtml}
552
552
  ${trendHtml}
553
553
  </div>
554
- <div class="st-val t f-display" style="${valColor}">${md(String(st.value ?? st.stat ?? ""))}</div>
554
+ <div class="st-val t f-display" style="${valColor}">${md(`${st.prefix ?? ""}${st.value ?? st.stat ?? ""}${st.suffix ?? ""}`)}</div>
555
555
  ${st.label ? `<div class="st-lab t f-heading">${md(st.label)}</div>` : ""}
556
556
  ${st.text ? `<div class="st-sub t f-body">${md(st.text)}</div>` : ""}
557
557
  </div>`;
@@ -619,7 +619,7 @@ export function code(e) {
619
619
  const language = resolveCodeLanguage(e.language, e.filename);
620
620
  const lines = language ? highlightCode(source, language) : source.split("\n").map(esc);
621
621
  const hl = new Set([].concat(e.highlight || []));
622
- const body = lines.map((l, i) => `<div class="cl${hl.has(i + 1) ? " hl" : ""}"><span class="cn">${i + 1}</span><span class="cc">${l || " "}</span></div>`).join("");
622
+ const body = lines.map((l, i) => `<div class="cl${hl.has(i + 1) ? " hl" : ""}"><span class="cn">${i + 1}</span><span class="cc">${noHyphenBreak(l) || " "}</span></div>`).join("");
623
623
  return `<div${attrs(e, "code f-mono", `font-size:${e.size || 34}px;`)}${language ? ` data-language="${esc(language)}"` : ""}>${body}</div>`;
624
624
  }
625
625
 
@@ -21,6 +21,10 @@ const cvar = (c, d) => {
21
21
  if (/^#?[0-9a-f]{6}$/i.test(c)) return `#${String(c).replace("#", "")}`;
22
22
  // paleta do tema é namespaced (--c-c1…): nome pelado ("c3") viraria var indefinida = preto
23
23
  if (/^c[1-9]\d*$/.test(c) || /^(accent|alert|ink|paper)$/.test(c)) return `var(--c-${c})`;
24
+ // "hi" é a cor do marca-texto, clara nos temas de papel: a barra em destaque saía rosa-claro, quase sumindo, e as
25
+ // de fundo (muted) mais fortes que ela. Como no texto (elements.js: textColorVal), a marca usa a versão com
26
+ // contraste (--hi-ink: o hi se ele aparece no fundo, senão a ênfase). Rodada de uso real, 23/10/2026.
27
+ if (c === "hi") return "var(--hi-ink,var(--hi))";
24
28
  return `var(--${c})`;
25
29
  };
26
30
  const fmt = (v, o) => {
package/src/markup.js CHANGED
@@ -16,6 +16,12 @@ export function esc(s) {
16
16
  .replace(/"/g, "&quot;");
17
17
  }
18
18
 
19
+ // Código que quebra linha (pre-wrap): o navegador quebra depois do hífen, e "git commit --amend --no-edit" saía como
20
+ // "--no-" / "edit" (quem copia do slide digita errado). Cada hífen vira <span class="hy">, e o CSS põe um "word joiner"
21
+ // (U+2060) depois dele por ::after: a linha não quebra ali, e o texto copiado continua o mesmo (o ::after não é copiado).
22
+ // Recebe HTML já escapado ou realçado: só troca o hífen fora das tags.
23
+ export const noHyphenBreak = (html) => String(html).replace(/(<[^>]*>)|-/g, (m, tag) => tag || '<span class="hy">-</span>');
24
+
19
25
  // tabela em markdown no meio do texto (uma linha "| a | b |" por linha, com ou sem a linha "|---|---|" depois do
20
26
  // cabeçalho): vira tabela de verdade (src/table.js), não texto com barras
21
27
  const PIPE_TABLE = /(^|\n)((?:[ \t]*\|[^\n]*\|[ \t]*(?:\n|$)){2,})/g;
@@ -333,6 +333,8 @@ html[data-motion="none"] .flip-in,.export .flip-in{transition:none}
333
333
  .cl.hl{background:color-mix(in srgb,var(--c-accent) 26%,transparent);box-shadow:inset 8px 0 0 var(--c-accent)}
334
334
  .cn{width:2.2em;color:#777;flex:none}
335
335
  .cc{white-space:pre}
336
+ /* hífen do código sem quebra de linha depois dele (markup.js: noHyphenBreak); o ::after não vai na cópia */
337
+ .hy::after{content:"\2060"}
336
338
  .code.code-wrap .cl{min-width:0!important}.code.code-wrap .cc{white-space:pre-wrap;overflow-wrap:anywhere;flex:1;min-width:0}.code.code-scroll{overflow:auto;scrollbar-width:thin}
337
339
  .tok-keyword{color:#c792ea}
338
340
  .tok-string{color:#c3e88d}
@@ -70,7 +70,7 @@
70
70
  { addLabel: "Adicionar turno", newItem: () => ({ who: 1, commit: "" }) })];
71
71
  const CARD = [f.icon("icon", "Ícone"), f.text("title", "Título"), f.area("text", "Texto"), f.area("flip", "Verso (o cartão vira no clique)"),
72
72
  f.more([f.text("number", "Número"), f.text("foot", "Rodapé"), f.text("badge", "Selo"), f.bool("hl", "Destacar"), f.num("step", "Aparece no clique"), f.text("goto", "Ao clicar, ir para", { hint: "Id do slide de destino (ou o número dele): navegação por caminhos." })])];
73
- const STAT = [f.text("value", "Valor"), f.text("label", "Rótulo"), f.text("trend", "Tendência"), f.icon("icon", "Ícone"),
73
+ const STAT = [f.text("value", "Valor"), f.text("prefix", "Antes do valor", { hint: "Ex.: R$ " }), f.text("suffix", "Depois do valor", { hint: "Ex.: %, h, mil" }), f.text("label", "Rótulo"), f.text("trend", "Tendência"), f.icon("icon", "Ícone"),
74
74
  f.more([f.bool("trendUp", "Tendência positiva"), f.color("color", "Cor")])];
75
75
  const STEP = [f.text("title", "Título"), f.area("text", "Texto"), f.icon("icon", "Ícone"), f.more([f.text("tag", "Etiqueta"), f.text("goto", "Ao clicar, ir para", { hint: "Id do slide de destino (ou o número dele): navegação por caminhos." })])];
76
76
  // bloco do one-page (problema, solução): texto, números grandes e tópicos