mirror of
https://github.com/fsolla/teqo.git
synced 2026-11-13 15:13:07 +00:00
refactor(C203): seletor de chave no dono do kit, guard estrito e nomes (simplify)
- readKitAssets({ keys }) lê só o ativo pedido; o entry pede namePositive (não falha por ativo alheio)
- guard do brandLogo com regex estrita de data URI PNG/base64 (vazio/aspas fora)
- helper brandMark (sem colisão com a opção brandLogo); JSDoc @param/@returns no dono
- entry-spec: teste dedicado do ativo com repoRoot tmp; stub base64 válido; helper tipado
- triage pós-simplify no impl plan (S9 defer com gatilho; S10 explicitamente fora)
This commit is contained in:
parent
258fd22379
commit
0dae7d4ac1
7 changed files with 56 additions and 22 deletions
|
|
@ -1 +1 @@
|
|||
**C203 — Gráficos de dados do Instagram na marca oficial do kit 1313 (2026-09-19):** os gráficos da skill `graficos-dados` (C191) saem agora na **marca e na paleta oficiais do kit 1313** (C196/C204), resolvendo o débito `NEEDS ASSET · LOGO OFICIAL` registrado no hi-fi aprovado. (1) **Rodapé oficial:** o lockup tipográfico provisório ("JORGE SOLLA"/"MANDATO DEPUTADO FEDERAL") saiu; o rodapé usa `public/campaign-kit/jorge-solla-positivo.png` embutido como data URI (frame 286×90, `object-fit: contain`, `alt` do hi-fi) e o renderer **falha fechado** sem a marca — nunca recria o lockup. (2) **Paleta oficial:** destaque único/valência boa `#e4102f`, sinal de marca `#184e92` (régua de topo e kicker), neutros mantidos; amarelo `#ffeb00`/verde `#009647` só dentro dos ativos e o laranja `#f89c0e` não é usado — o mapeamento semântico fixado no hi-fi (C203) não mudou. (3) **Dono único dos ativos:** `scripts/lib/campaignKitAssets.mjs` (novo) passa a ler o kit para os dois renderers (`reelRender` reusa; `build-reel` só trocou o import) com default de root resolvido por `import.meta.url` — cwd-independente, o que mantém entry/e2e rodando de tmpdir; `renderChartHtml(spec, { brandLogo })` segue sync/puro e o entry injeta o ativo (falha com mensagem acionável se o kit sumir). (4) **Compartilhado:** o default de `lineChart` em `chartPrimitives.mjs` acompanha o vermelho oficial; `CHART_COLORS`, dossiês/boletins (snapshots byte-identical) e o mapa seguem intactos. (5) **Prosa e pinos:** `SKILL.md`/command descrevem a marca do kit 1313 (guardrail novo do rodapé oficial), os 4 specs atualizados pinam os literais do gate (data URI, ausência do lockup e do `#c51414`) e `SCRIPTS_SPEC_PINNED` ganhou o módulo novo. Formatos 1080×1350/1080×1080/1080×1920, guardrails (100% local, gitignored, base zero, ≤7 pontos, valência redundante, fonte obrigatória) e o slogan só dentro das marcas completas permanecem.
|
||||
**C203 — Gráficos de dados do Instagram na marca oficial do kit 1313 (2026-09-19):** os gráficos da skill `graficos-dados` (C191) saem agora na **marca e na paleta oficiais do kit 1313** (C196/C204), resolvendo o débito `NEEDS ASSET · LOGO OFICIAL` registrado no hi-fi aprovado. (1) **Rodapé oficial:** o lockup tipográfico provisório ("JORGE SOLLA"/"MANDATO DEPUTADO FEDERAL") saiu; o rodapé usa `public/campaign-kit/jorge-solla-positivo.png` embutido como data URI (frame 286×90, `object-fit: contain`, `alt` do hi-fi) e o renderer **falha fechado** sem a marca — nunca recria o lockup. (2) **Paleta oficial:** destaque único/valência boa `#e4102f`, sinal de marca `#184e92` (régua de topo e kicker), neutros mantidos; amarelo `#ffeb00`/verde `#009647` só dentro dos ativos e o laranja `#f89c0e` não é usado — o mapeamento semântico fixado no hi-fi (C203) não mudou. (3) **Dono único dos ativos:** `scripts/lib/campaignKitAssets.mjs` (novo) passa a ler o kit para os dois renderers (`reelRender` reusa; `build-reel` só trocou o import) com default de root resolvido por `import.meta.url` — cwd-independente, o que mantém entry/e2e rodando de tmpdir; `renderChartHtml(spec, { brandLogo })` segue sync/puro e o entry injeta o ativo (falha com mensagem acionável se o kit sumir). (4) **Compartilhado:** o default de `lineChart` em `chartPrimitives.mjs` acompanha o vermelho oficial; `CHART_COLORS`, dossiês/boletins (snapshots byte-identical) e o mapa seguem intactos. (5) **Prosa e pinos:** `SKILL.md`/command descrevem a marca do kit 1313 (guardrail novo do rodapé oficial), os 4 specs atualizados pinam os literais do gate (data URI, ausência do lockup e do `#c51414`) e `SCRIPTS_SPEC_PINNED` ganhou o módulo novo. Formatos 1080×1350/1080×1080/1080×1920, guardrails (100% local, gitignored, base zero, ≤7 pontos, valência redundante, fonte obrigatória) e o slogan só dentro das marcas completas permanecem. Crítica de fechamento do `designer` certificada (`Design tier: openai/gpt-5.6-sol`), sem bloqueantes.
|
||||
|
|
|
|||
|
|
@ -86,11 +86,11 @@ flowchart LR
|
|||
|
||||
### Componentes / mudanças
|
||||
|
||||
1. **`scripts/lib/campaignKitAssets.mjs` (novo — dono da leitura do kit):** `KIT_ASSETS` verbatim de `reelRender.mjs:121-127` (5 arquivos) + `readKitAssets({ root })` devolvendo data URIs; default `root = join(dirname(fileURLToPath(import.meta.url)), '..', '..')` (repo root, cwd-independente); exporta **só** `readKitAssets` (`KIT_ASSETS` privado — `knip.json` rules `exports: error`); JSDoc; sem dep além de `node:fs/promises`/`node:path`/`node:url`.
|
||||
1. **`scripts/lib/campaignKitAssets.mjs` (novo — dono da leitura do kit):** `KIT_ASSETS` verbatim de `reelRender.mjs:121-127` (5 arquivos) + `readKitAssets({ root, keys })` devolvendo data URIs (default `keys` = todos; o gráfico pede só `namePositive`); default `root = join(dirname(fileURLToPath(import.meta.url)), '..', '..')` (repo root, cwd-independente); exporta **só** `readKitAssets` (`KIT_ASSETS` privado — `knip.json` rules `exports: error`); JSDoc `@param`/`@returns`; sem dep além de `node:fs/promises`/`node:path`/`node:url`.
|
||||
2. **`scripts/lib/reelRender.mjs` (editar):** remover `KIT_ASSETS`/`readKitAssets` (`:121-138`); nenhum outro comportamento muda (saída do reel C196 inalterada).
|
||||
3. **`scripts/build-reel.mjs` (editar):** `readKitAssets` passa a vir de `./lib/campaignKitAssets.mjs` (import atual em `:51`); chamada `:145` mantém `{ root: ROOT }`.
|
||||
4. **`scripts/lib/graficosInstagramRender.mjs` (editar):** `SOLLA_PALETTE` oficial; CSS `.top-rule`/`.context`→azul e bloco `.brand*` substituído por `.brand-logo-frame`+`img` (286×90, contain); `footer` recebe o data URI; `renderChartHtml(spec, { brandLogo })` sync/puro e fail-closed (ausente ou fora de `data:image/png;base64,`); `alt="Jorge Solla — Deputado Federal"`; `lineChart` chamado com `highlight: SOLLA_PALETTE.highlight` (já passa).
|
||||
5. **`scripts/build-chart-from-data.mjs` (editar):** importar `readKitAssets`; após `validateSpec` e antes do browser, ler `namePositive` com `readKitAssets()` (**sem** `repoRoot`!) e chamar `renderChartHtml(spec, { brandLogo })` (`:195`); falha na leitura → `die` acionável citando o kit.
|
||||
5. **`scripts/build-chart-from-data.mjs` (editar):** importar `readKitAssets`; após `validateSpec` e antes do browser, ler só `namePositive` com `readKitAssets({ keys: ['namePositive'] })` (**sem** `repoRoot`!) e chamar `renderChartHtml(spec, { brandLogo })` (`:195`); falha na leitura → `die` acionável citando o kit.
|
||||
6. **`scripts/lib/chartPrimitives.mjs` (editar):** default `highlight` do `lineChart` (`:504`); `CHART_COLORS` e os demais emissores intactos.
|
||||
7. **`.agents/skills/graficos-dados/SKILL.md` (editar):** frontmatter (marca/paleta oficiais do kit 1313); `:126` e `:169` `#c51414`→`#e4102f`; guardrail do rodapé com o ativo oficial (nunca recriar lockup); referências ao `public/campaign-kit/README.md` e a `docs/plans/graficos-dados-marca-kit-1313.md`.
|
||||
8. **`.opencode/commands/graficos-dados.md` (editar):** descrição `:2` — "na marca e paleta oficiais do kit 1313".
|
||||
|
|
@ -142,9 +142,14 @@ Total ~1,0 dia (0,80 de código+testes, 0,15 de prova/crítica, 0,05 de fechamen
|
|||
- **Limpar os resíduos de ilustração do hi-fi** (`:1966-1975`, `:2127-2130`) — sem trigger (a); a revisão C203 é a autoridade; se quiserem consistência do arquivo, item de design separado.
|
||||
- **Editor gráfico / configurar cores** — template fixo + dados de entrada.
|
||||
- **Expor amarelo/verde na paleta** — eles só vivem dentro dos ativos; expor é furar o guardrail.
|
||||
- **Unificar os defaults de `chartPrimitives` com `SOLLA_PALETTE` (S10)** — decisão (iv) travada; unificar importaria a paleta do kit para dentro do dono compartilhado e os neutros já existem assim em `main`.
|
||||
- **Migration/schema/DB/Consent/access** — ferramenta local, sem persistência.
|
||||
- **Commitar PNG/dado** — saídas gitignored.
|
||||
|
||||
## Adiado com gatilho (triage pós-simplify)
|
||||
|
||||
- **S9 — root redundante em `scripts/build-reel.mjs:145`:** gatilho: na próxima edição de `build-reel.mjs`, trocar `readKitAssets({ root: ROOT })` por `readKitAssets()` — o default do dono resolve o mesmo repo root e passa a ser a única resolução.
|
||||
|
||||
## Riscos e mitigação
|
||||
|
||||
- **Ativo resolvido por cwd/`repoRoot` quebra testes e e2e (cwd tmp).** Default por `import.meta.url` no novo dono; o entry chama sem root; prova no entry-spec (data URI com `repoRoot: tmpdir`) e no e2e (`cwd: workDir`). Se alguém passar `repoRoot`, o ENOENT aparece no teste, não em produção.
|
||||
|
|
|
|||
|
|
@ -195,7 +195,7 @@ export const main = async ({
|
|||
|
||||
let brandLogo = ''
|
||||
try {
|
||||
const assets = await readKitAssets()
|
||||
const assets = await readKitAssets({ keys: ['namePositive'] })
|
||||
brandLogo = assets.namePositive
|
||||
} catch (error) {
|
||||
die(
|
||||
|
|
|
|||
|
|
@ -19,10 +19,21 @@ const KIT_ASSETS = {
|
|||
|
||||
const defaultRoot = join(dirname(fileURLToPath(import.meta.url)), '..', '..')
|
||||
|
||||
/** Official campaign kit marks as data URIs (committed under `public/campaign-kit`). */
|
||||
export const readKitAssets = async ({ root = defaultRoot } = {}) => {
|
||||
/**
|
||||
* Official campaign kit marks as data URIs (committed under `public/campaign-kit`).
|
||||
*
|
||||
* @param {{ root?: string, keys?: string[] }} [options] repo root (defaults to
|
||||
* this module's repo) and the asset keys to read (defaults to all).
|
||||
* @returns {Promise<Record<string, string>>} data URI per requested key.
|
||||
*/
|
||||
export const readKitAssets = async ({
|
||||
root = defaultRoot,
|
||||
keys = Object.keys(KIT_ASSETS),
|
||||
} = {}) => {
|
||||
const entries = await Promise.all(
|
||||
Object.entries(KIT_ASSETS).map(async ([key, file]) => {
|
||||
keys.map(async (key) => {
|
||||
const file = KIT_ASSETS[key]
|
||||
if (!file) throw new Error(`ativo do kit desconhecido: ${key}`)
|
||||
const bytes = await readFile(resolve(root, 'public/campaign-kit', file))
|
||||
return [key, `data:image/png;base64,${bytes.toString('base64')}`]
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -192,13 +192,13 @@ const plotBody = (spec) => {
|
|||
return rankingBody(spec)
|
||||
}
|
||||
|
||||
const brandLogo = (brandLogoDataUri) => `<div class="brand-logo-frame">
|
||||
<img src="${brandLogoDataUri}" alt="Jorge Solla — Deputado Federal" />
|
||||
const brandMark = (brandMarkDataUri) => `<div class="brand-logo-frame">
|
||||
<img src="${brandMarkDataUri}" alt="Jorge Solla — Deputado Federal" />
|
||||
</div>`
|
||||
|
||||
const footer = (spec, brandLogoDataUri) => `<footer class="footer">
|
||||
const footer = (spec, brandMarkDataUri) => `<footer class="footer">
|
||||
<p class="source"><strong>Fonte:</strong> ${htmlEscape(spec.source)}${spec.note ? `<br />Nota: ${htmlEscape(spec.note)}` : ''}</p>
|
||||
${brandLogo(brandLogoDataUri)}
|
||||
${brandMark(brandMarkDataUri)}
|
||||
</footer>`
|
||||
|
||||
const CSS = `* { box-sizing: border-box; }
|
||||
|
|
@ -364,9 +364,9 @@ const layoutFor = (size) => {
|
|||
* lockup is never recreated.
|
||||
*/
|
||||
export const renderChartHtml = (spec, { brandLogo: brandLogoDataUri } = {}) => {
|
||||
if (!brandLogoDataUri || !brandLogoDataUri.startsWith('data:image/png;base64,')) {
|
||||
if (!/^data:image\/png;base64,[A-Za-z0-9+/=]+$/.test(brandLogoDataUri ?? '')) {
|
||||
throw new Error(
|
||||
'marca oficial do kit ausente: leia jorge-solla-positivo.png com readKitAssets() e passe brandLogo.',
|
||||
'marca oficial ausente: passe brandLogo como data:image/png;base64,… do ativo oficial do kit.',
|
||||
)
|
||||
}
|
||||
const sizeKey = SIZES[spec.size] ? spec.size : 'feed'
|
||||
|
|
|
|||
|
|
@ -170,12 +170,6 @@ describe('main — spec assembly, replay and the per-size log', () => {
|
|||
)
|
||||
expect(close).toHaveBeenCalledOnce()
|
||||
|
||||
// C203: the official kit mark is embedded even though `repoRoot` is a temp
|
||||
// dir — the asset resolves from the module, never from the cwd.
|
||||
const html = screenshot.mock.calls[0][1].html
|
||||
expect(html).toContain('data:image/png;base64,')
|
||||
expect(html).not.toContain('MANDATO DEPUTADO FEDERAL')
|
||||
|
||||
const spec = JSON.parse(
|
||||
await readFile(join(dir, 'data/graficos-instagram/ranking-de-teste.chart-spec.json'), 'utf8'),
|
||||
)
|
||||
|
|
@ -194,6 +188,31 @@ describe('main — spec assembly, replay and the per-size log', () => {
|
|||
expect(log).toContain('tipo=bar')
|
||||
})
|
||||
|
||||
it('embeds the official kit mark even with a temp repoRoot (C203)', async () => {
|
||||
const input = await writeInput(dir, fiveRows)
|
||||
const { launchBrowser } = launchTracker()
|
||||
const screenshot = screenshotStub()
|
||||
|
||||
await main({
|
||||
argv: [
|
||||
`--in=${input}`,
|
||||
'--headline=Ranking de teste',
|
||||
'--source=TSE 2022',
|
||||
'--type=bar',
|
||||
`--out=${join(dir, 'brand.png')}`,
|
||||
],
|
||||
repoRoot: dir,
|
||||
launchBrowser,
|
||||
screenshot,
|
||||
})
|
||||
|
||||
// The asset resolves from the module, never from the cwd: `repoRoot` is a
|
||||
// temp dir here and the official mark still reaches the HTML.
|
||||
const html = screenshot.mock.calls[0][1].html
|
||||
expect(html).toContain('data:image/png;base64,')
|
||||
expect(html).not.toContain('MANDATO DEPUTADO FEDERAL')
|
||||
})
|
||||
|
||||
it('assembles the delta spec from a table with the two periods', async () => {
|
||||
const input = await writeInput(
|
||||
dir,
|
||||
|
|
|
|||
|
|
@ -25,9 +25,8 @@ const spec = (overrides = {}) => ({
|
|||
})
|
||||
|
||||
// C203: the official kit mark travels as a data URI injected by the entry.
|
||||
const brandLogo = 'data:image/png;base64,c203-stub'
|
||||
const render = (chartSpec: Parameters<typeof renderChartHtml>[0]) =>
|
||||
renderChartHtml(chartSpec, { brandLogo })
|
||||
const brandLogo = 'data:image/png;base64,QzIwMw=='
|
||||
const render = (chartSpec: Record<string, unknown>) => renderChartHtml(chartSpec, { brandLogo })
|
||||
|
||||
describe('renderChartHtml — canvas and template', () => {
|
||||
it('authors the feed canvas at exactly 1080×1350', () => {
|
||||
|
|
|
|||
Loading…
Reference in a new issue