sagadeck 1.11.3 → 1.11.4

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,28 @@ começar. Trabalho em curso vai para o GitHub (commit + push) a cada etapa, nunc
8
8
 
9
9
  ---
10
10
 
11
+ ## 1.11.4: CI mais rápida, implantação por roteiro, diário em dia — 23/10/2026 (branch `claude/melhorias-infra`)
12
+
13
+ - **Antivírus do Windows segurava a abertura do deck** (exportação, fotos, PDF, PPTX e conferência): o Node lia o
14
+ HTML recém-gravado só para ver se tinha mapa, e no Windows essa primeira leitura de um HTML grande fica parada 15 a
15
+ 20 s (o Chrome abre na hora). Agora quem grava usa `writeDeckHTML` (`src/export/recent-html.js`: build, exportação
16
+ do Studio) e a abertura usa o texto da memória; sem ele, quem lê é o Chrome, e o Node só relê se houver mapa (o
17
+ mapa abre por endereço local, servido da memória). `lengths.test.js` caiu de 84 s para 18 s nesta máquina; cada
18
+ exportação no Windows de quem usa deixa de esperar. Teste: `test/open-deck.test.js` (conta as leituras do arquivo
19
+ pelo Node; falha com o código antigo).
20
+ - **CI em 3 partes por sistema**, ao mesmo tempo: `scripts/test-parte.mjs` reparte os arquivos pelo peso (os lentos
21
+ numa tabela lá dentro; dividir pela ordem dos arquivos dava 22 min × 13 min). Os checks exigidos na main continuam
22
+ `test (ubuntu-latest, 24.x)` e `test (windows-latest, 24.x)`: o job `test` de cada sistema confere pela API do run
23
+ que as 3 partes dele passaram. Antes: 26 min no Ubuntu e 37 no Windows por PR. Teste: `test/test-parte.test.js`
24
+ (todo arquivo em exatamente uma parte, partes equilibradas, pesos só de arquivos que existem).
25
+ - **Implantação no Oracle por roteiro**: `node scripts/implantar-oracle.mjs` (dev primeiro, estável só se o dev subiu
26
+ na versão certa; git como root; npm ci só quando o lock muda além do número da versão; confere `/api/instance`).
27
+ Hoje à mão o git sem sudo falhou calado e o npm ci rodou sem precisar. Teste: `test/implantar-oracle.test.js` roda o
28
+ roteiro de verdade com git real e sudo/systemctl/curl/npm de mentira (no Linux; conferido no próprio Oracle).
29
+ - `SAGADECK_ADMINS=305cc6c7416383c8` (a Narumi) em `/etc/sagadeck.env` no Oracle: vale para os dois serviços.
30
+ - Diário: as pendências "Falta: PR…" do Painel Conferência (#204), do Pedido da criação (#203), da Conferência das
31
+ contas (#201, 1.11.0) e da Frente E (Frente F) viraram "Concluído".
32
+
11
33
  ## 1.11.3 — 23/10/2026 (branch `claude/versao-1.11.3`)
12
34
 
13
35
  Junta o envio de cópia para outra pessoa (#217), o Corrigir tudo (#218) e os testes ao vivo a cada 3 versões (#216).
@@ -216,7 +238,7 @@ Testes: `ai.test.js` (itens, ignorar, recordMath, Conferir de novo com e sem IA)
216
238
  usado como a pessoa usaria: selo, Conferir de novo, Ignorar, Corrigir com IA numa conta e num slide da IA,
217
239
  conferindo deck salvo, relatório e o que foi ao modelo; zero erros JS). Ícones Lucide novos: list-checks,
218
240
  calculator, hash, image-off. Suíte completa: `npm test`, 1.116 testes, 1.087 aprovados, 29 condicionais pulados, zero falhas.
219
- Falta: PR empilhado sobre o #201 (`claude/conferir-contas`), CI verde, merge.
241
+ Concluído: entrou no PR #204 (depois do #201) e saiu na 1.11.0.
220
242
 
221
243
  ## Pedido da criação gravado — 22/10/2026 (branch `claude/pedido-da-criacao-0d34bc`, sobre `claude/conferir-contas`)
222
244
 
@@ -234,7 +256,7 @@ pasta inteira; o pacote `.sagadeck` agora leva `.sagadeck/geracao.json`.
234
256
  Testes: `storage.test.js` (biblioteca com anexo; segredos e `anteriores`), `library-ui.test.js` (pedido guiado
235
257
  digitado; só com anexo grava o texto padrão marcado, duplicar e o pacote levam o arquivo, zero erros JS),
236
258
  `studio-ai.test.js` (Deck com IA do editor e criação pelo chat com docx). Conferido que falham sem a gravação.
237
- Falta: PR com CI verde e merge (depois do PR #201, que é a base).
259
+ Concluído: entrou no PR #203 (depois do #201) e saiu na 1.11.0.
238
260
 
239
261
  ## Modelo para conferir — 22/10/2026 (branch `claude/modelo-por-etapa-d74f41`, parte de `claude/conferir-contas`)
240
262
 
@@ -292,9 +314,8 @@ erros que ninguém tinha visto (velocidades da tabela do slide 26). A correção
292
314
  corrigir o material" da pessoa vale sobre a regra da autoria.
293
315
  Suíte completa: `npm test`, 1.108 testes, 1.079 aprovados, 29 condicionais pulados, zero falhas. Studio local na
294
316
  3517 rodando `c65772e` a partir desta worktree, para validação dela.
295
- Falta (depois do ok dela): PR, CI verde, merge, deploy na Oracle (produção estava na 1.9.0) e, no deck da conta da
296
- Mary, pedir à IA do sagadeck pelo chat que confira e corrija as contas (inclusive as do material: a aula é dela e
297
- ela autorizou caprichar), corrija os avisos da criação e melhore a aula. A correção é da IA, não à mão.
317
+ Concluído: entrou no PR #201 e saiu na 1.11.0 (Oracle e npm); a IA do sagadeck corrigiu o deck da conta da Mary pelo
318
+ chat, e a versão revisada foi para a pasta dela como outra apresentação.
298
319
 
299
320
  ## UX para programação e APIs com cURL — 12/10/2026 (branch `codex/ux-dev-slides`, versão 1.10.0)
300
321
 
@@ -2242,8 +2263,8 @@ raciocínio, a mesma conferência sai em 1–2 s e acha o que importa (título r
2242
2263
  "a partir de um arquivo" só com o anexo), `studio-themes.test.js` (prévia sem gravar, clique aplica),
2243
2264
  `studio.test.js` (Baixar tudo: zip com pptx com notas, pdf e roteiro), `storage.test.js` (gerado na biblioteca).
2244
2265
 
2245
- Falta da lista: modo simples por padrão (avançado dobrável) e as fontes do Google embutidas (a rede do banco barra
2246
- `fonts.googleapis.com`: temas perdem a letra, abrir/exportar pode esperar, e cada abertura avisa o Google).
2266
+ Concluído depois, na Frente F (28/09/2026, logo acima): modo simples e fontes embutidas, sem o Google. O modo
2267
+ simples saiu mais tarde (a faixa mostra tudo).
2247
2268
 
2248
2269
  ## Frente D (preguiçoso): trocar o tema muda o arranjo dos slides de conteúdo — 28/09/2026
2249
2270
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sagadeck",
3
- "version": "1.11.3",
3
+ "version": "1.11.4",
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/build.js CHANGED
@@ -19,6 +19,7 @@ import { esc, notesHTML, plain, md, INLINE_MATH } from "./markup.js";
19
19
  import { normalizeSpec } from "./fiscal/normalize.js";
20
20
  import { readRecordings } from "./api-client.js";
21
21
  import { explorationStates } from './exploration.js';
22
+ import { writeDeckHTML } from "./export/recent-html.js";
22
23
  import { calcModel } from './lessons.js';
23
24
  import { loadMapConfig, mapaFile, tilesForDeck } from "./map-config.js";
24
25
 
@@ -257,7 +258,7 @@ export function buildFile(file, outFile) {
257
258
  const spec = loadSpec(file);
258
259
  const r = buildHTML(spec);
259
260
  fs.mkdirSync(path.dirname(outFile), { recursive: true });
260
- fs.writeFileSync(outFile, r.html);
261
+ writeDeckHTML(outFile, r.html); // a conferência e a exportação abrem em seguida sem reler (export/recent-html.js)
261
262
  return { ...r, spec, outFile };
262
263
  }
263
264
 
@@ -4,6 +4,8 @@ import http from "node:http";
4
4
  import path from "node:path";
5
5
  import { pathToFileURL } from "node:url";
6
6
  import { chromium } from "playwright-core";
7
+ import { recentHTML } from "./recent-html.js";
8
+ export { writeDeckHTML } from "./recent-html.js";
7
9
 
8
10
  const CANDIDATES = [
9
11
  process.env.SAGADECK_BROWSER,
@@ -24,10 +26,12 @@ export function findBrowser() {
24
26
 
25
27
  // Deck com mapa abre por um endereço local (http://127.0.0.1), não como arquivo: o servidor de tiles do OpenStreetMap
26
28
  // pede que o navegador diga de onde vem o pedido, e página aberta do disco não diz. Só lê a pasta do deck.
27
- async function serveDir(dir) {
29
+ // main: { "deck.html": texto } serve o deck da memória (sem o Node reler o arquivo; veja writeDeckHTML)
30
+ async function serveDir(dir, main = {}) {
28
31
  const server = http.createServer((req, res) => {
29
32
  const rel = decodeURIComponent(new URL(req.url, "http://x").pathname).replace(/^\/+/, "");
30
33
  const file = path.resolve(dir, rel);
34
+ if (req.method === "GET" && Object.hasOwn(main, rel)) { res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" }); return res.end(main[rel]); }
31
35
  if (req.method !== "GET" || !file.startsWith(path.resolve(dir)) || !fs.existsSync(file) || !fs.statSync(file).isFile()) { res.writeHead(404); return res.end(); }
32
36
  res.writeHead(200, { "Content-Type": file.endsWith(".html") ? "text/html; charset=utf-8" : "application/octet-stream" });
33
37
  fs.createReadStream(file).pipe(res);
@@ -41,15 +45,23 @@ export const settleSlide = (page) => page.evaluate(() => window.SagaMap?.settled
41
45
 
42
46
  export async function openDeck(htmlFile, { scale = 1 } = {}) {
43
47
  const browser = await chromium.launch({ executablePath: findBrowser() });
44
- const hasMap = fs.readFileSync(htmlFile, "utf8").includes('class="map-box');
45
- const server = hasMap ? await serveDir(path.dirname(htmlFile)) : null;
46
- if (server) { const close = browser.close.bind(browser); browser.close = async () => { await close(); server.close(); }; }
47
48
  try {
48
49
  const page = await browser.newPage({ viewport: { width: 1920, height: 1080 }, deviceScaleFactor: scale });
49
50
  const errors = [];
50
51
  page.on("pageerror", (e) => errors.push(e.message));
51
52
  page.on("console", (m) => { if (m.type() === "error") errors.push(m.text()); });
52
- await page.goto((server ? `http://127.0.0.1:${server.address().port}/${encodeURIComponent(path.basename(htmlFile))}` : pathToFileURL(htmlFile).href) + "?export=1");
53
+ const fileURL = pathToFileURL(htmlFile).href + "?export=1";
54
+ // tem mapa? Pelo texto na memória; sem ele, quem lê o arquivo é o Chrome, e o Node só relê se houver mapa
55
+ let html = recentHTML(htmlFile), hasMap;
56
+ if (html != null) hasMap = html.includes('class="map-box');
57
+ else { await page.goto(fileURL); hasMap = await page.evaluate(() => !!document.querySelector(".map-box")); }
58
+ if (hasMap) {
59
+ const name = path.basename(htmlFile);
60
+ const server = await serveDir(path.dirname(htmlFile), { [name]: html ?? fs.readFileSync(htmlFile) });
61
+ const close = browser.close.bind(browser); browser.close = async () => { await close(); server.close(); };
62
+ errors.length = 0;
63
+ await page.goto(`http://127.0.0.1:${server.address().port}/${encodeURIComponent(name)}?export=1`);
64
+ } else if (html != null) await page.goto(fileURL);
53
65
  await page.waitForFunction(() => window.sagadeck && window.sagadeck.cur >= 0 && document.fonts.status === "loaded");
54
66
  // a janela com o tamanho do slide (deck.aspect: 4:3 → 1920 × 1440): foto, PDF e PPTX sem faixa nem corte
55
67
  const size = await page.evaluate(() => window.sagadeck.size || { w: 1920, h: 1080 });
@@ -0,0 +1,24 @@
1
+ // HTML que este processo acabou de gravar (build, exportação, conferência). No Windows, o antivírus segura por 15 a
2
+ // 20 s a primeira leitura pelo Node de um HTML grande recém-gravado (o Chrome abre na hora): a abertura no Chrome
3
+ // (browser.js: openDeck) usa o texto da memória em vez de reler o arquivo. Vale só enquanto o arquivo no disco é o
4
+ // mesmo (tamanho e data); guarda só os últimos (o Studio fica aberto o dia todo). Módulo à parte, sem o Playwright,
5
+ // para o build gravar por aqui sem carregar o navegador.
6
+ import fs from "node:fs";
7
+ import path from "node:path";
8
+
9
+ const RECENT = new Map(); // caminho -> { html, size, mtimeMs }
10
+
11
+ export function writeDeckHTML(file, html) {
12
+ fs.writeFileSync(file, html);
13
+ const key = path.resolve(file), st = fs.statSync(file);
14
+ RECENT.delete(key);
15
+ RECENT.set(key, { html, size: st.size, mtimeMs: st.mtimeMs });
16
+ while (RECENT.size > 4) RECENT.delete(RECENT.keys().next().value);
17
+ }
18
+
19
+ export function recentHTML(file) {
20
+ const r = RECENT.get(path.resolve(file));
21
+ if (!r) return null;
22
+ const st = fs.statSync(file, { throwIfNoEntry: false });
23
+ return st && st.size === r.size && st.mtimeMs === r.mtimeMs ? r.html : null;
24
+ }
@@ -9,6 +9,7 @@ import os from "node:os";
9
9
  import path from "node:path";
10
10
  import JSZip from "jszip";
11
11
  import { buildHTML } from "../build.js";
12
+ import { writeDeckHTML } from "../export/recent-html.js";
12
13
  import { THEMES } from "../themes.js";
13
14
  import { loadPreferences } from "../preferences.js";
14
15
  import { packDeck, EXTENSION, MIME } from "../package.js";
@@ -46,7 +47,7 @@ export async function generateExport(kind, spec, name, { notes = true, audience,
46
47
  if (["pptx","pdf","tudo"].includes(kind)) spec=expandApiCollections(spec);
47
48
  const r = buildHTML(spec);
48
49
  const htmlFile = path.join(tmp, "deck.html");
49
- fs.writeFileSync(htmlFile, r.html);
50
+ writeDeckHTML(htmlFile, r.html);
50
51
  const errors = [];
51
52
  const make = async (what) => {
52
53
  const progress = event => onProgress({ ...event, format: what });
@@ -60,7 +61,7 @@ export async function generateExport(kind, spec, name, { notes = true, audience,
60
61
  // Preferências › Exportação: tema escuro com par claro (manual-noite → manual) sai claro no PDF, bom para imprimir
61
62
  const light = loadPreferences().exportacao.pdfClaro === true ? lightVariant(spec) : null;
62
63
  let file = htmlFile;
63
- if (light) { file = path.join(tmp, "deck-claro.html"); fs.writeFileSync(file, buildHTML(light).html); }
64
+ if (light) { file = path.join(tmp, "deck-claro.html"); writeDeckHTML(file, buildHTML(light).html); }
64
65
  await pdf(file, out, { onProgress: progress });
65
66
  } else if (what === "estudo" || what === "estudo-html") {
66
67
  const { shots } = await import("../export/shots.js");