Changelog

GitHub

Todas as mudanças relevantes, versão por versão. O mesmo arquivo é publicado no repositório e traduzido nos 19 idiomas.

[1.10.3] — 2026-05-12

Fecha 7 dos 11 achados de QA da v1.10.0 (F-001, F-010 mínimo, F-011 mínimo, F-013, F-014, F-015, F-019). Os 4 remanescentes (F-018 — consolidação completa de adapter-registry; pipeline multer completo PR-4; follow-ups PR-7; sweep de doc PR-9 em career-ops.org docs) são adiados para v1.11.0.

✨ Features

  • feat(pdf): Generate-PDF em cada superfície long-form (F-015) — três novos endpoints SSE (GET /api/stream/pdf/report?slug=, GET /api/stream/pdf/deep?name=, POST /api/stream/pdf/inline { markdown }) mais um helper compartilhado public/js/lib/pdf-generate.js. O botão 📄 Generate PDF agora aparece em #/reports/:slug, #/deep (manual + live), #/evaluate (manual + live) e #/interview-prep (via o endpoint deep). Cada tipo reutiliza o helper cv-markdown-to-print-HTML da v1.10.2 e pousa o resultado sob output/<slug>-<TS>.pdf para que o fluxo de auto-download existente assuma.
  • feat(config): grupo regional de config (F-013) — /api/config agora expõe groups (core | runtime | regional) e regionalActive (boolean computado de portals.yml::russian_portals.sources). O SPA renderiza os três grupos como seções colapsáveis; Regional sources é auto-colapsado e só presente quando uma source regional está configurada.

🐛 Correções

  • fix(server): error handler global do Express (F-019) — PayloadTooLargeError (ex.: upload de 11 MB para /api/cv/import) e SyntaxError de express.json agora retornam envelopes JSON que o SPA pode localizar (HTTP 413 / 400). Antes o handler default do Express retornava um stack trace HTML que quebrava o try { await res.json() } do SPA.
  • fix(i18n): tokens em inglês não vazam mais para UI não-EN (F-001) — adicionadas localizações para Pipeline, Deep research, Follow-up, Health, Outreach, Doctor, Quick scan (os labels que usuários viam no idioma da UI enquanto o resto da chrome estava traduzido).
  • fix(scan): remover framing EN/RU dos labels (F-010 mínimo) — a linha de summary em #/scan, dois badges scan-done e os labels do source-filter agora leem “ATS adapters” + “Regional portals”. Os dois endpoints SSE (/api/stream/scan-en, /api/stream/scan-ru) ficam como estão; a consolidação completa do registry vive em PR-1 / v1.11.0.
  • fix(scan): contador Active-Companies auto-refresh (F-011 mínimo) — a view dispatcha um evento scan:refresh após cada refreshResults(); o contador re-deriva “empresas com hits no último scan” do payload real de /api/scan-results em vez de ficar congelado no snapshot do view-mount.
  • docs(en-ru-framing): sweep em READMEs + help bundles (F-014) — EN sweep → ATS sweep, RU sweep → regional sweep, EN scanner → ATS scanner, EN: Greenhouse / Ashby / Lever, RU: hh.ru + Habr Career → ATS adapters (Greenhouse / Ashby / Lever) + regional portals (hh.ru / Habr Career). Atinge README.md, README.ru.md, README.ja.md, README.ko-KR.md, docs/help/en.md, docs/help/es.md, docs/help/pt-BR.md.

🧪 Testes

  • Novo tests/global-error-handler.test.mjs (2 casos): JSON malformado → 400 JSON; upload 11 MB → 413 JSON.
  • Novo tests/config-groups.test.mjs (2 casos): /api/config expõe groups; regionalActive vira on quando portals.yml ganha uma source regional.
  • Novo tests/pdf-extra-routes.test.mjs (5 casos): cada um de /report, /deep, /inline invoca generate-pdf.mjs com os três argumentos posicionais documentados; 404 em slug ausente; 400 em markdown inline vazio.
  • Total: 349 / 350 testes unit (1 drift pré-existente de dados do pai em portals-dead.test.mjs).
  • Cobertura: 94.59 % linha / 84.16 % branch.
  • 20 / 20 smoke E2E, 23 / 23 comprehensive E2E, 28 / 28 Playwright.

📝 Docs

  • docs/reviews/REVIEW-2026-05-12-v1.10.3.md — contexto da sessão + lista de scope-out.
  • Todos os 8 READMEs: bumps de badge (testes 340 → 349, release v1.10.2 → v1.10.3), seção “What’s new in v1.10.3” por locale.
  • Todos os 8 CHANGELOGs recebem esta entrada.

Fora de escopo (adiado para v1.11.0)

  • PR-1 — adapter registry locale-agnostic completo (8 arquivos de adapter ATS + novo /api/stream/scan?source= consolidando os dois endpoints existentes + +14 novos portais + reescrita da scan-view). O slice label-only neste release fecha F-010 / F-011 visualmente; o refactor arquitetural é uma fase de múltiplos dias.
  • PR-4 — pipeline de CV import baseado em multer (substitui o envelope 415 da v1.10.2 por parser multipart real + envelope ConversionError + revisão de dependências).
  • PR-9 — integração completa de docs career-ops.org: fetch career-ops.org/docs + os 4 sub-guides (scan-job-portals, apply-for-a-job, batch-evaluate-offers, set-up-playwright), traduzir para 7 locales não-EN, reescrever help bundles + READMEs conforme, auditar telas UI contra o comportamento documentado.

[1.10.2] — 2026-05-12

Patch de regressão funcional. Dois bugs descobertos em hand-testing da v1.10.1 fechados; superfície de documentação expandida.

🐛 Correções

  • fix(cv): /api/cv/import rejeita multipart/form-data com 415 (hardening F-016) — qualquer cliente externo (curl -F, clientes HTTP comuns) defaultando para multipart/form-data tinha antes seu envelope de fio (--boundary…\r\nContent-Disposition: form-data; name="file"; filename="x"…) armazenado como conteúdo de cv.md. O caminho real do SPA (Content-Type: application/octet-stream + X-Filename) não era afetado. A rota agora retorna 415 com uma dica apontando para o contrato documentado. Defesa em profundidade: bodies octet-stream que cheiram a multipart nos primeiros 256 bytes também recebem 415. cv.md nunca é tocado em um 415.
  • fix(pdf): /api/stream/pdf invoca generate-pdf.mjs com argumentos posicionais corretos — chamava o script com []. O script imprimia sua linha Usage: e saía com code 1 — o SPA mostrava o toast verde “PDF generated” mas nenhum arquivo chegava ao disco. A rota agora lê cv.md, renderiza para um arquivo HTML em output/cv-input-<TIMESTAMP>.html via um helper in-route markdown-to-print-HTML, então spawn’a generate-pdf.mjs <input.html> <output.pdf> --format=a4. Query opcional ?format=letter para output US-letter. Quando cv.md está ausente, emite evento error + done { code: 2 } em vez de frame de start falso.

🧪 Testes

  • Novo tests/cv-upload-multipart-reject.test.mjs (5 casos): happy path SPA retorna 200 com markdown limpo; multipart/form-data → 415; body octet-stream que PARECE multipart → 415; body vazio → 400; request rejeitada NÃO modifica cv.md.
  • Novo tests/pdf-stream-args.test.mjs (3 casos): evento start carrega <input.html> <output.pdf> --format=a4 com paths absolutos e o HTML existe em disco; ?format=letter troca a flag; cv.md ausente emite o frame de erro esperado.
  • Total: 340 testes unit (era 318). Uma falha pré-existente em portals-dead.test.mjs permanece drift de dados do lado do pai, não relacionada a web-ui.
  • Cobertura: 94.63 % linha / 84.94 % branch.

📝 Docs

  • Novo docs/test-scenarios/ — 21 arquivos de cenário em inglês (índice + contratos por página):
    • 01 smoke / health · 02 CV upload · 03 CV edit-save · 04 CV → PDF download
    • 05 profile YAML · 06 config env · 07 scan · 08 pipeline
    • 09 evaluate · 10 deep research · 11 modes · 12 apply checklist
    • 13 tracker · 14 reports · 15 activity log · 16 interview prep · 17 JDs
    • 18 i18n · 19 help center · 20 security · 21 full funnel
  • Cada arquivo documenta: objetivo, pré-condições, entradas, saídas esperadas, casos negativos, cobertura de teste (arquivo + range de linhas) e passos Playwright manuais quando aplicável.
  • Novo docs/reviews/REVIEW-2026-05-12-v1.10.2.md — contexto completo da sessão, lista de scope-out, comandos de verificação.
  • Todos os 8 READMEs: bumps de badge (testes 318 → 340, release v1.10.1 → v1.10.2) + seção “What’s new in v1.10.2” por locale.
  • Todos os 8 CHANGELOGs recebem esta entrada.

Fora de escopo (adiado para futuras fases GSD)

PR-1 adapter registry locale-agnostic (ainda na fila), PR-4 CV import baseado em multer com pipeline de conversão completo, PR-7 botões Generate-PDF em reports / evaluate / deep / interview-prep, PR-8 reagrupamento de UI de config, PR-9 sweep de docs, PR-10 auditoria de localização botão-por-botão + gate jsdom de CI, retradução completa para coreano.


[1.10.1] — 2026-05-09

Patch de correções críticas conduzido pelo QA regression run da v1.10.0 (qa/reports/00-FINAL-SUMMARY.md).

🛡️ Segurança

  • fix(security): apertar isValidJobUrl + adicionar defesa DNS-rebind (PR-3 / F-003) — isValidJobUrl agora rejeita RFC1918 (10/8, 172.16/12, 192.168/16), o range loopback 127/8 completo, link-local 169.254/16 (incl. AWS IMDS), 0.0.0.0, CGNAT 100.64/10 e IPv6 ULA / link-local. Novo helper isPrivateOrLoopbackHost() é exportado de server/lib/security.mjs e reutilizado por /api/pipeline/preview, que agora faz dns.lookup no host em cada redirect hop e rejeita quando o endereço resolvido é privado — derrota DNS-rebind. Falha de DNS fail-open (fetch reporta o erro) para que stubs de teste / sandboxes sem DNS continuem funcionando.

🐛 Correções

  • fix(activity): registrar apenas mudanças de estado bem-sucedidas (PR-5 / F-005) — middleware agora faz early-return em res.statusCode >= 400. Requests pipeline / cv / tracker rejeitadas não poluem mais o feed de auditoria.
  • fix(activity): adicionar mapeamentos de evento profile.save / config.save / cv.import (F-008) — chamadas bem-sucedidas PUT /api/profile e POST /api/config agora aparecem em /api/activity.
  • fix(help): aliasar ko → ko-KR.md para servir o body do Help coreano (F-002) — o SPA envia códigos BCP-47 nus (ko); o arquivo no disco é ko-KR.md. O resolver agora percorre 4 candidatos: exato, alias de region-tag, language-only base, depois en.md.
  • fix(llm): /api/evaluate honra mode:'manual' (F-009) — espelha /api/deep. Modo manual pula chamadas Anthropic / Gemini mesmo com chave configurada para que usuários possam copiar o prompt no Claude Code sem queimar créditos.
  • fix(api): DELETE /api/pipeline aceita ?url= E body.url, retorna 404 em miss (PR-6 / F-017) — fazia silenciosamente 200-on-miss com apenas ?url=.

✨ Features

  • feat(llm): propagação de locale em cada prompt (PR-2 / F-012) — novo resolveLocale(req) escolhe um locale de body.lang → body.locale → Accept-Language → 'en'. Novo buildLocaleDirective(lang) emite um header de uma linha “Respond in X”. buildEvaluationPrompt, buildDeepPrompt, buildModePrompt agora aceitam e embed lang. API.call() do SPA auto-anexa Accept-Language e mescla lang em bodies JSON.
  • feat(scripts): post-qa-cleanup.mjs (PR-11) — replays o checklist de cleanup do QA-regression; --apply escreve, default é dry-run, idempotente. Sweep de URLs RFC1918 / nip.io / test-cloud-* de data/pipeline.md e audita o tamanho de cv.md.

🧪 Testes

  • Novo tests/critical-fixes.test.mjs (15 casos) cobrindo: resolução de alias ko F-002, opt-out de modo manual F-009, shape de DELETE PR-6 (body / 404 / 400), testes unit do helper PR-3 para IPv4 + IPv6 + formas bracketed, precedência resolveLocale PR-2 + buildLocaleDirective + integração com prompt-builder.
  • tests/url-validation.test.mjs estendido com 5 testes novos para RFC1918 / link-local / 0.0.0.0 / 127/8 / CGNAT / IPv6 ULA / link-local.
  • tests/activity-log.test.mjs teste 8 atualizado para afirmar o novo contrato “no log on 4xx”.
  • Total: 318 testes unit (era 298; uma falha pré-existente em portals-dead.test.mjs é drift de dados do lado do pai em templates/portals.example.yml, não relacionada a código web-ui).

📝 Docs

  • Novo docs/reviews/REVIEW-2026-05-09-v1.10.1.md — contexto completo da sessão + lista de scope-out + comandos de verificação.
  • Todos os 8 READMEs: bumps de badge (contagem de testes 298 → 318, release v1.10.0 → v1.10.1), path da screenshot movido para public/images/screen_vacancy_found.png, seção “What’s new in v1.10.1” adicionada por locale (Inglês, Espanhol, Português, Coreano, Japonês, Russo, Chinês Simplificado, Chinês Tradicional).
  • Todos os 8 CHANGELOGs atualizados com esta entrada.

Fora de escopo (adiado para futuras fases GSD)

PR-1 (adapter registry locale-agnostic, +14 portais, reescrita FE), PR-4 (CV import baseado em multer + ConversionError + error handler global), PR-7 (botões Generate-PDF em reports / evaluate / deep / interview-prep), PR-8 (reagrupamento de UI de config), PR-9 (sweep completo de README/docs/8 help bundle no framing EN-RU), PR-10 (auditoria de localização botão-por-botão + gate jsdom de CI), retradução completa de help coreano (o arquivo existe; o PR apenas corrigiu a entrega em runtime).


[1.10.0] — 2026-05-08

Revamp de CV import + abas em #/config + rota canônica #/profile.

✨ Features

  • feat(cv): import server-side para .docx / .doc / .odt / .rtf / .pdf / .html / .txt / .md — novo endpoint POST /api/cv/import converte um documento uploadado (qualquer formato comum) para markdown que o editor pode importar. Formatos Office passam por pandoc, PDF via pdftotext do Poppler. Resultado é sanitizado via stripDangerousMarkdown (defesa em profundidade XSS). Cap rígido: 10 MB por upload. O 📁 Upload CV no frontend aceita agora o conjunto completo de formatos; toasts de erro elegantes quando um conversor está ausente no host.
  • feat(cv): auto-download do PDF gerado quando generate-pdf.mjs termina — o fluxo streaming Generate-PDF agora faz snapshot do PDF mais recente no diretório output e, no done, dispara um download no browser para o arquivo novo (no-op se o run não produziu artefato novo). A lista existente na página continua mostrando cada PDF anterior.
  • feat(config): layout de duas abas — API keys & runtime + Profile — #/config agora tem uma faixa de abas. A primeira aba mantém o editor .env existente (API keys, models, knobs de scanner). A nova aba Profile é editor YAML direto para config/profile.yml: PUT /api/profile valida o YAML (deve ser mapping, deve incluir candidate), carimba um header canônico # Career-Ops Profile Configuration se ausente e escreve o arquivo. Save propaga sem restart.
  • feat(routes): rota canônica /#/profile (era /#/settings) — sidebar agora aponta para #/profile. O hash antigo #/settings ainda resolve via a tabela de alias do router, então bookmarks existentes continuam funcionando. Handler interno de rota renomeado; testes atualizados refletindo a nova direção.

🧪 Testes

  • Novo tests/cv-import.test.mjs (7 casos): passthrough .md / .txt, body vazio 400, extensão não suportada 422, oversized 413, sanitização HTML→markdown (pula quando pandoc ausente), round-trip PDF→texto com PDF hand-crafted (pula quando poppler ausente).
  • Novo tests/profile-put.test.mjs (7 casos): round-trip happy-path, carimbo de header, empty / invalid-YAML / non-object / missing-candidate 400, oversized 413.
  • tests/playwright-full-cycle.mjs estendido 14 → 16 subtests — adiciona CV-import via HTML e round-trip PUT /api/profile.
  • Regex ALIAS de tests/router.test.mjs invertido para afirmar a nova direção settings → profile.

📚 Docs

  • docs/help/{en,ru}.md — atualizações completas das seções 2/3/4: novas abas de App-settings, mensagem edit-via-config na página read-only Profile, matriz completa de formatos de upload na seção CV, comportamento de auto-download de PDF.
  • docs/help/{es,pt-BR,ko-KR,ja,zh-CN,zh-TW}.md — espelhos concisos dos novos blocos de conteúdo; contagem de seções inalterada (16) para que o teste de paridade fique verde.

🔧 Interno

  • Novo server/lib/cv-import.mjs — fonte única de verdade para a conversão formato → markdown, com timeout + detecção de conversor ausente que surface dicas acionáveis em vez de 500s.
  • server/lib/routes/content.mjs ganha POST /api/cv/import e PUT /api/profile (binary-safe via express.raw para o upload, JSON para o PUT YAML).

[1.9.1] — 2026-05-08

Passe de production-readiness. Quatro bug fixes alvo (BF-1..BF-4), Playwright smoke expandido de 5 para 12 testes cobrindo round-trips tracker / pipeline / reports / evaluate / config / cv save. Tudo verde no CI.

🐛 Correções

  • fix(tracker): escapar pipes + colapsar newlines em cada célula, não só notes (BF-1) — um nome de empresa como "Acme | Co" antes quebrava o layout da tabela markdown (parser dividia a célula em duas). O saneador de célula agora é aplicado uniformemente em company / role / reportSlug / notes; fix companheiro em parsers.mjs::parseMarkdownTable adiciona suporte a escape \| compatível com GFM para que o round-trip seja lossless.
  • fix(config): envolver updateEnvFile em try/catch (BF-2) — POST /api/config antes deixava uma rejeição não tratada em permission-denied / filesystem read-only. Agora retorna um 500 limpo { error: 'failed to write parent .env', details: [...] }.
  • fix(llm): cap soft no tamanho do prompt montado para chamadas Anthropic SDK (BF-3 + BF-4) — branches Anthropic de /api/evaluate, /api/deep e /api/mode/:slug agora bailam com 413 quando bundleProjectContext + prompt excede 200 KB (≈50K tokens). Economiza segundos de roundtrip + tokens vs deixar a API reclamar de tamanho de contexto. O cap está bem abaixo de qualquer teto de modelo atual (Sonnet 4.6 = 1M de contexto).

🧪 Playwright smoke — cobertura expandida

5 → 12 testes. Casos novos:

  • tracker view renders empty + accepts API-seeded row — exercita BF-1 semeando uma linha com pipe literal no nome da empresa e afirmando que o round-trip preserva.
  • pipeline add-URL form populates the queue + sweep de rejeição de URL inválido (loopback, javascript:, strings nuas).
  • reports view handles empty state — assert de não-crash.
  • evaluate view returns a manual prompt without API key — verifica a cadeia de fallback.
  • config GET returns known keys masked — secrets nunca vazam via /api/config.
  • cv.md PUT round-trips with sanitization — bits XSS (script tags, schemes javascript:) são strippados end-to-end.
  • pipeline preview proxy strips scripts — caminho de rejeição de URL inválido.

📦 Mudanças de comportamento (sem mudanças de contrato de API)

  • Escritas no tracker são agora lossless contra nomes company / role carregados de pipes. Linhas existentes com pipes crus começarão a fazer parse corretamente na próxima leitura.
  • /api/{evaluate,deep,mode/:slug} agora retornará 413 em vez de 502/timeout quando o prompt for absurdamente grande (200 KB+).

🧪 Testes

  • 284 testes unit (sem mudança de contagem; testes existentes continuam todos verdes após o update do parser).
  • 12 testes Playwright browser-smoke (era 5).

[1.9.0] — 2026-05-08

P-6 → P-10 do backlog da v1.8.0 todos entregues em um bundle. Manchete: server/index.mjs é agora um orchestrator de 130 LOC (caiu de 762, total 1230 → 130 = -89%); cada tópico de rota tem seu próprio módulo. Paridade Anthropic em /api/evaluate, shims multi-CLI, teste de paridade i18n expandido e Playwright browser-smoke cabeado no CI.

🏗️ P-6 — split do servidor por concern (fase 2)

Continuação de P-2. Extraiu os 9 tópicos de rota remanescentes de server/index.mjs para módulos server/lib/routes/<topic>.mjs. index.mjs é agora puro orchestrator: middleware (security headers + activity log + static), 12 chamadas register<Topic>Routes(app) e o catch-all do SPA.

  • server/lib/routes/activity.mjs — /api/activity.
  • server/lib/routes/config.mjs — /api/config GET/POST (round-trip do .env do pai).
  • server/lib/routes/health.mjs — /api/health + /api/dashboard.
  • server/lib/routes/help.mjs — /api/help/:lang.
  • server/lib/routes/jds.mjs — CRUD completo para jds/*.txt.
  • server/lib/routes/llm.mjs — cada endpoint LLM-bound (evaluate, deep, mode, apply-helper, interview-prep).
  • server/lib/routes/pipeline.mjs — /api/pipeline* incluindo o proxy de preview SSRF-safe com constantes nomeadas para timeout / max-redirects / max-body.
  • server/lib/routes/reports.mjs — /api/reports*.
  • server/lib/routes/tracker.mjs — GET de /api/tracker + POST dedup-aware.

Comportamento inalterado. 283/283 testes unit ficaram verdes em cada passo. A superfície de import do orchestrator caiu de 47 linhas para 22.

🔌 P-7 — paridade Anthropic para /api/evaluate

/api/evaluate antes era Gemini-ou-manual. A v1.9.0 adiciona um branch Anthropic (preferido quando ambas as chaves estão presentes), espelhando a regra de roteamento já usada por /api/deep e /api/mode/:slug. Roteia via bundleProjectContext({ modeSlugs: ['_shared', 'oferta'] }) para que o modelo tenha cv / profile / templates de mode inlined (REVIEW-A1).

Novo endpoint: POST /api/evaluate/test-anthropic — smoke check para ANTHROPIC_API_KEY, espelha o smoke Gemini existente. Envia um prompt minúsculo (≤256 tokens de output) para que custe essencialmente nada; retorna um sample de 200 caracteres.

Cadeia de fallback agora é: Anthropic → Gemini → manual.

🌐 P-8 — paridade i18n do help center (auditoria + hardening de teste)

Auditado cada docs/help/<lang>.md quanto à paridade estrutural. Todos os 8 locales já cobrem as mesmas 14 seções H2 canônicas. Testes upgraded:

  • tests/help-ui.test.mjs::every help doc covers the same 14 sections checava apenas en + ru. Agora itera todos os 8 locales (en, es, pt-BR, ko-KR, ja, ru, zh-CN, zh-TW) e afirma a contagem de seções para cada.
  • Novo teste: tests/help-ui.test.mjs::every help locale has substantive content — guarda contra stubs de locale afirmando que cada locale não-EN é pelo menos 30% do tamanho em bytes de en.md. Traduções compactas naturalmente atingem 40-50%; um stub ficaria em % de um dígito.

Resultado: paridade estrutural agora é enforced pelo CI.

🤖 P-9 — Playwright browser smoke na matrix de CI

tests/playwright-smoke.mjs (adicionado na v1.8.0 como opt-in) agora é parte do workflow CI. O job e2e existente já instala Playwright + Chromium; um passo novo (npm run test:e2e:browser) roda os 5 testes browser-smoke logo após o E2E node comprehensive.

Ordem no CI: unit (matrix Node 18/20/22) → smoke node E2E → comprehensive node E2E → Playwright browser smoke → upload de artefato de screenshot em falha.

🌍 P-10 — Compatibilidade multi-CLI

O career-ops v1.7.0 pai introduziu suporte ao standard multi-CLI / Open Agent Skill. O sub-projeto UI segue a mesma convenção com shims finos apontando para o CLAUDE.md canônico:

  • web-ui/AGENTS.md — Codex / Aider / entrypoint CLI genérico.
  • web-ui/GEMINI.md — entrypoint Gemini CLI.

Ambos os shims reafirmam as hard rules e a quick reference mas adiam para CLAUDE.md quanto às instruções completas de nível de projeto, para que CLIs não-Claude aterrissem na mesma orientação que sessões do Claude Code. A UI deployada continua CLI-agnostic em runtime.

🧪 Testes

  • 284 testes unit (era 283): +1 novo teste de paridade help-locale.
  • 5 testes Playwright browser-smoke — agora parte do CI, não só opt-in.
  • Cobertura mantida.

🔧 Arquivos tocados

+ server/lib/routes/activity.mjs              + server/lib/routes/config.mjs
+ server/lib/routes/health.mjs                + server/lib/routes/help.mjs
+ server/lib/routes/jds.mjs                   + server/lib/routes/llm.mjs
+ server/lib/routes/pipeline.mjs              + server/lib/routes/reports.mjs
+ server/lib/routes/tracker.mjs
+ AGENTS.md                                   + GEMINI.md

~ server/index.mjs (762 → 130 LOC, -83%)
~ .github/workflows/ci.yml (passo Playwright smoke)
~ tests/help-ui.test.mjs (paridade de seção em 8 locales + content-floor)
~ docs/{ROADMAP,architecture/{OVERVIEW,SERVER}}.md
~ docs/sdd/CONVENTIONS.md
~ CLAUDE.md
~ package.json (1.8.0 → 1.9.0)

📦 Novos endpoints REST

Método Path Propósito
POST /api/evaluate/test-anthropic Smoke check para ANTHROPIC_API_KEY (P-7). Espelha /api/evaluate/test-gemini.

🤖 Novos entrypoints de CLI

Arquivo CLI Notas
AGENTS.md Codex / Aider / genérico Aponta para CLAUDE.md para as instruções completas.
GEMINI.md Gemini CLI Auto-carregado pelo Gemini no início de sessão.

[1.8.0] — 2026-05-08

Hardening, refactor e bootstrap SDD. Três correções de correção/segurança de alta severidade (A1, A2, A3), quatro de severidade média (B1–B4), seis limpezas, auditoria da superfície do pai career-ops v1.7.0, split do servidor por concern (P-2 fase 1), harness Playwright browser smoke e uma fundação SDD completa sob docs/ e .claude/.

🔥 Correções de alta severidade

  • fix(deep): inline cv/profile/mode files para chamadas Anthropic SDK (REVIEW-A1) — /api/deep e /api/mode/:slug antes diziam ao modelo “leia esses arquivos primeiro” mas o Anthropic SDK não tem filesystem. O output era oco. Novo bundleProjectContext({ modeSlugs }) lê cv.md, config/profile.yml, modes/_shared.md e o template de mode, trunca cada um em 16 KB e antepõe um bloco <project_context> ao prompt. Verificado ao vivo: resposta de 26 KB markdown grounded de claude-sonnet-4-6 para uma chamada de deep-research.
  • fix(runner): escalada SIGKILL após período de graça SIGTERM (REVIEW-A2) — runNodeScript e streamNodeScript antes enviavam apenas SIGTERM em timeout / client-disconnect. Um filho preso em syscall (DNS, socket bloqueado) ignorava, travando a conexão SSE até o GC do Node ceifar. Agora cada caminho arma um watchdog de 5 s que escala para SIGKILL. Promises sempre resolvem.
  • fix(runner): cap de max-runtime em endpoints streaming (REVIEW-A3) — cada runner SSE de script (/api/stream/{scan,liveness,pdf}) tem agora um teto rígido de 30 minutos. Na expiração: emite event: error { message: 'maximum runtime exceeded' }, mata o filho via o watchdog A2, encerra a resposta.

🛡️ Correções de severidade média

  • fix(preview): validação per-hop de redirect em /api/pipeline/preview (REVIEW-B1) — trocado de redirect: 'follow' para redirect-walking manual. Cada header Location é re-validado por isValidJobUrl; cap em 3 hops. Boards hostis não podem mais nos rebater para loopback / IPs privados / file://. 4 testes novos cobrem os caminhos de rejeição.
  • refactor(keys): helper hasGeminiKey unifica checks de chave LLM (REVIEW-B2) — leituras diretas process.env.GEMINI_API_KEY em handlers de rota substituídas por hasGeminiKey() de lib/anthropic.mjs. Espelha o shape de hasAnthropicKey() para consistência e mocking mais fácil.
  • feat(scanners): propagar AbortSignal por hh.ru, Habr, Greenhouse, Ashby, Lever (REVIEW-B3) — quando o cliente SSE desconecta mid-scan, fetches HTTP em voo agora são abortados em vez de rodar cada query até completar e descartar os eventos. runRuScan e runEnScan aceitam opts.signal; handlers SSE em /api/stream/scan-{ru,en} criam um AbortController e abortam em res.close.
  • test(anthropic): log-guard test previne vazamentos futuros de API-key via console (REVIEW-B4) — captura cada chamada console.{log,info,warn,error,debug} durante caminhos happy + error de runAnthropic, afirma output zero e que a canary key string nunca aparece. Defesa em profundidade contra uma regressão futura de console.log(opts).

🧹 Polish de baixa severidade

  • fix(parsers): URL gate de defesa em profundidade dentro de addPipelineUrl (REVIEW-C4) — rejeição em nível de parser de valores não-http(s), complementando o isValidJobUrl em nível de rota. opts.validate opcional para chamadores que querem regras mais estritas.
  • docs(readme): badge "tests-88 passed" → "tests-277 passed" (REVIEW-C3) — estava errado por uma ordem de grandeza.
  • test(i18n): diff de chaves ausentes agrupado por locale (REVIEW-C6) — quando tests/i18n-coverage.test.mjs encontra um gap, o output é agora [ru] (3): foo, bar, baz em vez de linhas misturadas.
  • docs(review): C1 fechado como resolvido-na-inspeção — regexes do sanitizer já estavam em forma hex \x00-\x08; entrada de review era artefato de renderização de ferramenta.

🏗️ P-2 fase 1 — split do servidor por concern

server/index.mjs tinha 1230 LOC, bem além do teto de 800 linhas. Dividido em módulos focados sem mudança de comportamento. Todos os 283 testes unit ficaram verdes em cada passo.

  • server/lib/security.mjs — isValidJobUrl, stripDangerousMarkdown, sanitizeJobDescription, isPubliclyExposed. Re-exportados de index.mjs para compatibilidade reversa com consumidores externos.
  • server/lib/prompts.mjs — bundleProjectContext, buildEvaluationPrompt, buildDeepPrompt, buildModePrompt, buildApplyChecklist.
  • server/lib/store.mjs — safeReadApps, safeReadPipeline, safeListReports, checkProfileCustomized, ensureRussianPortalsDefaults.
  • server/lib/routes/scan.mjs — registerScanRoutes(app) para /api/stream/scan-{ru,en}, /api/scan-ru/config, /api/scan-results.
  • server/lib/routes/runners.mjs — registerRunnerRoutes(app) para /api/run/* em buffer, streaming /api/stream/{scan,liveness,pdf}, list/download de PDFs gerados.
  • server/lib/routes/content.mjs — registerContentRoutes(app) para CV / Profile / Portals / Modes.

index.mjs agora tem 762 LOC (-38%, abaixo do cap de 800). Fase 2 vai extrair tracker, pipeline, reports, jds, llm (evaluate/deep/mode) e health para módulos de rota. Alvo <500 LOC para o orchestrator.

🔍 Auditoria do pai career-ops v1.7.0

O usuário atualizou o projeto pai para v1.7.0. Cada superfície consumida foi auditada — UI é totalmente compatível. Achados notáveis documentados em docs/architecture/DATA-FLOWS.md:

  • Catálogo de modes cresceu de 7 para 19 arquivos. O MODE_ALLOWLIST da UI deliberadamente exibe apenas 7 (os outros são Claude-Code-only). Comentário adicionado explicando o escopo estreito intencional.
  • Schema do portals.yml confirmado: tracked_companies (96 entradas, 87 habilitadas, 71 com API). Scanner EN lê corretamente; chave legacy companies ainda suportada.
  • Novas superfícies do pai NÃO consumidas hoje: dashboard/ (programa Go), update-system.mjs, generate-latex.mjs, analyze-patterns.mjs, liveness-core.mjs, followup-cadence.mjs, test-all.mjs, subdirs de modes localizados (de/fr/ja/pt/ru).
  • Live /api/dashboard, /api/health, /api/modes, /api/portals, /api/profile, /api/cv, /api/jds, /api/reports, /api/tracker, /api/pipeline, /api/evaluate, /api/deep, /api/stream/scan-en todos verificados verdes.

🤖 Bootstrap SDD / GSD

career-ops-ui tem agora uma fundação Spec-Driven Development completa alinhada com o pipeline GSD (skills gsd-* de superpowers@claude-plugins-official).

  • CLAUDE.md (raiz) — system prompt de agente em nível de projeto: stack, pipeline GSD, hard rules (contrato com o pai, envelope de segurança, sem --no-verify), convenções, fronteira com projeto pai.
  • .aiignore — lista de exclusão para agentes AI: vendored, binários, dados de usuário do pai, .planning/, .env, duplicatas de locale.
  • .claude/agents/ — três definições de subagent específicas do projeto:
    • web-ui-route-reviewer.md — porteia novas rotas contra SSRF, CSP, sanitizers, contrato de write do pai, convenções, testes.
    • spa-view-reviewer.md — DOM CSP-safe, i18n, registro de router, acessibilidade.
    • test-isolation-reviewer.md — verifica se testes são CI-isolados (sem suposições sobre projeto pai, sem rede ao vivo, sem colisão de porta).
  • .claude/commands/ — stubs de slash-command: /sdd-status, /codebase-tour.
  • Árvore docs/ — toda em inglês:
    • PROJECT.md — what/why/for-whom, scope, constraints, success criteria.
    • ROADMAP.md — marco atual + histórico completo + backlog.
    • sdd/SDD-GUIDE.md — pipeline discuss → spec → plan → execute → verify → review mapeado para skills gsd-*.
    • sdd/CONVENTIONS.md — module system, naming, routes, sanitizers, client patterns, i18n, errors, logging, testing, commits, branches, CSS.
    • architecture/OVERVIEW.md — diagrama top-level, layers, sequência de boot, invariantes, cheat sheet “where to look first when…”.
    • architecture/SERVER.md — mapa por arquivo para server/lib/*.mjs (atualizado para o split P-2).
    • architecture/FRONTEND.md — estrutura SPA, inventário de views, globals, “how to add a view”.
    • architecture/API.md — inventário completo de cada rota /api/*.
    • architecture/DATA-FLOWS.md — cada read/write no projeto pai, com o contrato de explicit-user-action.
    • reviews/REVIEW-2026-05-07.md — review estática que produziu as correções deste changelog.

🔒 Segurança & higiene do repo

  • chore(.gitignore): padrões abrangentes de defesa em profundidade — cobre variantes de env, pastas de IDE, scratch GSD (.planning/), settings de agent por usuário (.claude/settings.local.json, .claude/cache/, .claude/state/, .claude/memory/), artefatos Playwright (playwright-report/, test-results/, .playwright/, trace.zip), profiles de heap/CPU, lockfiles para tooling ainda não shippado, ruído expandido de macOS Finder, padrões genéricos de secret (secrets.json, credentials.json, *.pem, *.key).

🧪 Testes

  • 283 testes unit (era 277): +11 novos (4 para B1 redirect-rejection, 1 para hasGeminiKey, 1 para log-guard de runAnthropic).
  • 5 testes Playwright browser-smoke (novo, opt-in via npm run test:e2e:browser): render do dashboard + footer de versão, navegação dashboard → scan → pipeline → cv, persistência de language-switch, view 404, render da página health. Resolve Playwright via node_modules do pai — sem nova dependência.
  • Cobertura mantida em ~93% linha / ~83% branch.

📝 Scripts novos / atualizados de package.json

Script Propósito
npm run test:e2e:browser Roda harness Playwright smoke contra servidor in-process (5 testes).

🔧 Arquivos tocados

+ CLAUDE.md                                    +  .aiignore
+ docs/PROJECT.md                              +  docs/ROADMAP.md
+ docs/sdd/SDD-GUIDE.md                        +  docs/sdd/CONVENTIONS.md
+ docs/architecture/OVERVIEW.md                +  docs/architecture/SERVER.md
+ docs/architecture/FRONTEND.md                +  docs/architecture/API.md
+ docs/architecture/DATA-FLOWS.md              +  docs/reviews/REVIEW-2026-05-07.md
+ .claude/agents/web-ui-route-reviewer.md      +  .claude/agents/spa-view-reviewer.md
+ .claude/agents/test-isolation-reviewer.md
+ .claude/commands/sdd-status.md               +  .claude/commands/codebase-tour.md
+ server/lib/security.mjs                      +  server/lib/prompts.mjs
+ server/lib/store.mjs
+ server/lib/routes/scan.mjs                   +  server/lib/routes/runners.mjs
+ server/lib/routes/content.mjs
+ tests/playwright-smoke.mjs

~ .gitignore                                   ~  README.md (correção de badge)
~ package.json (1.7.2 → 1.8.0)
~ server/index.mjs (1230 → 762 LOC)
~ server/lib/runner.mjs (escalada SIGKILL, cap de max-runtime)
~ server/lib/anthropic.mjs (hasGeminiKey)
~ server/lib/parsers.mjs (URL gate em addPipelineUrl)
~ server/lib/ru-scanner.mjs                    ~  server/lib/en-scanner.mjs
~ server/lib/sources/{hh,habr,greenhouse,ashby,lever}.mjs (signal threading)
~ tests/anthropic.test.mjs                     ~  tests/i18n-coverage.test.mjs
~ tests/pipeline-preview.test.mjs

[1.7.2] — 2026-05-04

Help center, App settings in-UI, sidebar mobile, botão Scan único e atalho “Show result” em cada prompt-builder.

✨ Novas features

  • feat(help): guia de usuário in-app (/#/help) — documentação Markdown long-form acessível de uma nova entrada de sidebar. Cobre cada página passo a passo: quick start, editor de CV, Profile, filtros de Scan, preview de Pipeline, Evaluate, Deep research, Apply, Tracker, Reports, todos os 7 modes, Activity log, Health, dicas de setup. Auto-build de sumário sticky a partir de headings <h2>, build de DOM síncrono (sem race). Localizado para todos os 8 locales suportados.
  • feat(config): página App settings in-UI (/#/config) — edita ANTHROPIC_API_KEY, ANTHROPIC_MODEL, GEMINI_API_KEY, GEMINI_MODEL, HH_USER_AGENT, PORT, HOST pelo browser. Escreve no .env do projeto pai para que scripts Node do career-ops E o loader dotenv do web-ui peguem da mesma fonte. Chaves secretas mascaradas em leitura (primeiros/últimos 4 chars). Campos de modelo são dropdowns com listas curadas (claude-sonnet-4-6 / claude-opus-4-7 / claude-haiku-4-5 / gemini-2.0-flash / etc.). Valor vazio deleta a chave. Valores aplicados ao process.env do processo em execução imediatamente — sem restart para a maioria das settings.
  • feat(modes): botão "⚡ Show result" ao lado de "Copy prompt" — quando um prompt é gerado em modo manual, usuários não precisam mais retipar suas entradas para obter o resultado do LLM. O novo botão re-submete o mesmo form com run: true, caindo para um toast claro (Set ANTHROPIC_API_KEY or GEMINI_API_KEY in .env first) quando nenhuma chave está configurada. Funciona em /#/deep, /#/project, /#/training, /#/followup, /#/batch, /#/contacto, /#/interview-prep, /#/patterns.

🐛 Correções de UX + UI

  • fix(scan): botão Scan único substitui três (Scan all + EN + RU) — escolha avassaladora, default idêntico em 99% dos casos. O botão unificado 🌐 Scan roda cada source habilitada. Help docs atualizadas em 8 locales.
  • fix(ui): drawer de sidebar mobile — viewport <900px agora recebe um botão hamburger (☰) na top bar; body.sidebar-open toggla um transform CSS que desliza o sidebar para dentro. Backdrop escurece + click-em-qualquer-lugar fecha. Click em âncora + hashchange auto-close para que o usuário pouse na nova página com o drawer escondido. Viewports maiores não são afetados.
  • fix(server): versão do footer reflete o web-ui, não o VERSION do pai — /api/health agora lê o package.json próprio do web-ui. O footer não vaza mais um 1.6.0 velho do arquivo de versão do pai. VERSION do pai ainda é surface separadamente como parentVersion.

📦 Novos endpoints REST

Método Path Propósito
GET /api/help/:lang Retorna o guia de usuário em Markdown para o locale solicitado, caindo para en.md. Path-traversal-safe.
GET /api/config Retorna valores atuais para todas as chaves de env conhecidas; secrets mascarados.
POST /api/config Escreve as chaves dadas no .env do projeto pai, valida cada valor, aplica ao vivo a process.env.

🌐 i18n

  • 30+ chaves novas em nav.help, nav.config, help.*, config.*, deep.showResult, deep.needKey, scan.btnRun. Todos os 8 locales populados.

🧪 Testes

  • tests/help.test.mjs (12 casos) — cada locale suportado retorna markdown substantivo, EN spot-check de cada slug de página, lang desconhecido → fallback EN, path-traversal sanitizado, cada locale referencia cv.md / profile.yml / .env.
  • tests/help-ui.test.mjs (9 casos) — registro de arquivo de view, entrada no sidebar, chaves i18n presentes em cada locale, arquivos de docs existem para cada locale, help EN/RU tem 14 seções canônicas, cada rota #/foo coberta, cabeamento Show-result em deep + mode-page.
  • tests/env-config.test.mjs (18 casos) — testes pure-function para parseEnv, maskSecret, validateConfig, updateEnvFile (bootstrap, rewrite in-place preservando comentários, delete em valor vazio, quote-when-needed).
  • tests/config-endpoint.test.mjs (8 casos) — GET mascara secrets / retorna env path; POST escreve no .env do pai; aplicação live a process.env; valor vazio unset; rejeita chaves desconhecidas + Anthropic keys malformadas com 400.

📊 Estatísticas

  • Testes: 233 → 277 (+44 em 4 novos arquivos de teste).
  • E2E: 20 smoke + 23 comprehensive = 43 passos Playwright, todos verdes.
  • Cobertura: 93.5% linha / 82.6% branch / 93.7% funcs (inalterado — código novo é totalmente testado).

[1.7.1] — 2026-05-04

Patch release empilhando o trabalho pós-v1.7.0: pipeline preview pane, integração Anthropic API, sidebar scrollable, dotenv loader, lista dinâmica de Active-companies, hardening do workflow CI.

✨ Pipeline preview pane

  • Overhaul de /#/pipeline — lista à esquerda + pane de preview à direita. Click em qualquer URL para fetch de um snapshot proxied server-side (GET /api/pipeline/preview strippa scripts/styles/tags, cap em 8 KB, validado via isValidJobUrl). Filtro ao vivo, contador “In queue”, botão de header ⚡ “Evaluate first”. Inline ▶/✕ em cada linha mais Evaluate / Open in tab / Delete completos no pane de preview. Seletores de teste estáveis via classes data-url + .pipeline-row + .pipeline-row-delete. 8 testes novos em tests/pipeline-preview.test.mjs (fetch mockado, sem precisar de binding upstream).

✨ Integração Anthropic API — “Run live” em todo lugar

  • server/lib/anthropic.mjs — cliente zero-dependency para Anthropic Messages API (claude-sonnet-4-6 default, override via ANTHROPIC_MODEL). Quando ANTHROPIC_API_KEY está set, cada página de mode (/#/deep, /#/project, /#/training, /#/batch, /#/contacto, /#/interview-prep, /#/patterns) renderiza um botão “⚡ Run live (Anthropic)” como ação primária — clicar executa o prompt e renderiza Markdown de volta no browser em vez de delegar ao Claude Code. Gemini fica como fallback quando só sua chave está set. Modo manual ainda funciona sem chaves. 8 testes novos em tests/anthropic.test.mjs.

🐛 Correções de CI / pipeline

  • fix(api): apertar validator de URL de pipeline (FIX-M7) — agora também rejeita hostnames loopback, tamanho <10 ou >2000, whitespace dentro de URLs.
  • fix(server): de fato carregar .env para que dicas de HH_USER_AGENT / GEMINI_API_KEY funcionem — adicionado server/lib/dotenv.mjs (loader zero-dep de 35 linhas) cabeado no topo de server/index.mjs. Dicas em runtime no código do scanner finalmente fazem algo. 6 testes novos.
  • fix(ui): sidebar scrollable — 18 itens de nav em 6 grupos transbordavam em viewports menores. .sidebar tem agora overflow-y: auto com scrollbars finas customizadas.
  • fix(ui): tornar banner HH_USER_AGENT dismissable — depois removido totalmente de /scan quando percebemos que era overkill. Check da Health page ainda surface.
  • fix(scan): lista Active companies é agora colapsável + filtrável + agrupada — 87 tags flat era avassalador. Agora um toggle “▸ Active companies 87/71” expande uma lista ordenada (✓ API-backed primeiro, ○ websearch depois) mais filtro de busca.
  • fix(test): isolar api.test.mjs + en-scanner.test.mjs do projeto pai — ambos agora sobem tmp project roots para que o CI funcione sem o pai em checkout ao lado do web-ui.
  • fix(workflow): match de versão em publish-package só em release events — workflow_dispatch da main não falha mais o check de tag/versão.
  • fix(e2e): seletor estável para delete de pipeline row — restaurou o anchor wrapper + adicionou atributo data-url para que a suíte e2e fique selector-stable.

📦 Novo endpoint REST

Método Path Propósito
GET /api/pipeline/preview?url=… Proxy server-side: retorna snapshot visible-text da URL (scripts/styles strippados, cap de 8 KB), porteado por isValidJobUrl.

📊 Estatísticas após este batch

  • Testes: 225 → 233 (8 a mais em cima da v1.7.0).
  • Arquivos de teste: 25 → 26.
  • E2E: 20 + 23 = 43 passos Playwright, todos verdes.

[1.7.0] — 2026-05-03

Passe de hardening + UX + feature-completion de 35 commits dirigido por QA r5. Três camadas de segurança pousaram (sanitização XSS, CSP, validação de input), cada endpoint CRUD faltando foi preenchido, o bootstrap do projeto pai é agora totalmente automatizado, e a UI ganhou 9 novas páginas — Activity, Deep Research redesenhada, mais 7 modes agrupados no sidebar (project / training / followup / batch / outreach / interview-prep / patterns) cobrindo 100% do modes/ do pai. Pipeline ganhou pane de preview server-side. Integração Anthropic API faz “Run live” ser ação one-click em todos os modes. Cobertura de testes foi de 73 para 225, em 25 arquivos de teste, mais 23 passos Playwright e2e comprehensive. GitHub Actions deliveram workflows CI / AI review / Release / Publish-Package.

🔒 Segurança

  • fix(cv): sanitizar markdown de CV para bloquear XSS stored no preview (FIX-C10) — PUT /api/cv agora remove <script>, <iframe>, <object>, <embed>, <style>, <form>, <svg>, handlers de evento on*= e URIs javascript:/vbscript:/data:text/html antes de escrever cv.md. Body cappado em 1 MB (413 em overflow). UI.md() client-side foi reescrito para escapar cada byte antes de qualquer transformação markdown rodar, para que HTML cru nunca alcance innerHTML. Atributos href de link são validados contra uma allowlist de schemes seguros (http/https/mailto/tel/relativo + data:image só). 17 testes novos no helper de strip e round-trips HTTP.
  • fix(server): adicionar CSP e security headers baseline (FIX-L2) — cada resposta carrega agora X-Content-Type-Options: nosniff, X-Frame-Options: DENY, Referrer-Policy: same-origin. Quando o servidor binda além de loopback (HOST ≠ 127.0.0.1/::1/localhost), um Content-Security-Policy estrito é layerizado em cima: default-src 'self', script-src 'self' (sem unsafe-inline), Google Fonts na whitelist, connect-src 'self' bloqueia exfiltração XSS. Handlers onclick inline em index.html e router.js foram movidos para addEventListener para manter o CSP estrito intacto. 8 testes novos validando CSP em 5 valores diferentes de HOST.
  • fix(api): apertar validator de URL de pipeline (FIX-M7) — POST /api/pipeline aceitava antes "not-a-url" e persistia. Agora isValidJobUrl() rejeita strings nuas, inputs <10 ou >2000 chars, URLs com whitespace, schemes não-http(s) e hostnames loopback (localhost/127.0.0.1/::1). Engloba FIX-M3 + FIX-M6 (retorna 400 em inválido, mais uma flag deduped em sucesso).
  • fix(server): de fato carregar .env para que dicas de HH_USER_AGENT / GEMINI_API_KEY funcionem — antes o runtime dizia aos usuários “set HH_USER_AGENT in .env” mas o servidor nunca lia esse arquivo, então seguir a instrução não fazia nada. Adiciona um loader dotenv zero-dependency de 35 linhas (server/lib/dotenv.mjs) cabeado no topo de server/index.mjs. Valores de process-env setados na linha de comando ainda vencem, então overrides de CI existentes não são sombreados. .env.example do pai agora inclui um bloco documentado HH_USER_AGENT com exemplo real de User-Agent do Chrome. 6 testes novos.
  • fix(api): sanitizar JD antes da montagem do prompt (FIX-M5) — POST /api/evaluate strippa escapes ANSI, bytes de controle, tags <script> inline e trima whitespace antes de chamar o Gemini ou ecoar o prompt de volta. Cap de 50 KB. O mínimo de 50 chars roda contra o texto sanitizado, então tentativas de prompt injection que parecem longas mas consistem majoritariamente em escapes fail-fast com 400.
  • fix(health): mascarar versão do Node + project root quando HOST!=loopback (FIX-M1) — /api/health não dá mais fingerprint do host em deploys expostos em LAN. Respostas em loopback mantêm os valores para diagnose local.

✨ Novas features

  • feat: 7 novos modes no sidebar + sidebar agrupado (FIX-C8) — cobre 100% do diretório modes/ do pai sem gaps de UI. Novas rotas: #/project (advisor de projeto de portfólio), #/training (avaliação de curso / cert), #/followup (cadência por application), #/batch (processador paralelo de URLs), #/contacto (drafter de outreach LinkedIn), #/interview-prep (prep específico por stage), #/patterns (analyzer de padrões de rejeição). Todos os sete compartilham um único view factory dirigido por config (public/js/views/mode-page.js) e um único endpoint genérico POST /api/mode/:slug — adicionar novo mode no futuro é uma linha de config + um bloco i18n. Sidebar reorganizado em 6 grupos: Sourcing / Decision / Application / Networking / Analytics / Setup. 18 itens de nav no total. 12 testes novos em tests/modes-endpoints.test.mjs.
  • fix: bootstrap de deps do pai + defaults russian_portals (FIX-C4 + C9 + C12 + H2) — bin/start.sh instala agora o node_modules do pai (js-yaml, playwright, jsdom) E npx playwright install chromium em clones frescos, para que /api/stream/scan, /pdf e /liveness funcionem end-to-end out-of-the-box. createApp() faz probe de portals.yml em cada boot — se o bloco russian_portals: está ausente, anexa um default documentado com comentários. Idempotente: o segundo boot é no-op. 3 testes novos.
  • fix: desabilitar 9 slugs de portal mortos no template + health-check script (FIX-C3) — templates/portals.example.yml agora ship com Ada / Factorial / Tinybird / Weights & Biases / Travelperk / Clarity AI / Forto / Vinted / Runway flaggeados enabled: false (cada entrada tem comentário inline de razão). Instalações novas scaneiam 87 empresas vivas em vez de 96. Novo web-ui/scripts/portals-health-check.mjs HEAD-probes cada careers_url habilitada e reporta entradas DEAD com lista sugerida de patch (output JSON via --json). 3 testes novos.
  • feat(activity): log de user-action + página Activity no sidebar — cada request de API que muda estado é capturada em data/activity.jsonl (timestamp, action verb, target, success flag, detalhe opcional). Nova entrada no sidebar Activity com filtros de chip por prefixo de action (pipeline / cv / jd / evaluate / scan / stream / script), badges ✓/✗ por action e botão de refresh. Auto-rotaciona em 5 MB. 10 testes novos cobrindo middleware, filtros de read, tolerância a linha corrupta e o recursion guard para GET /api/activity.
  • feat(deep): ver Deep Research no browser + arquivo de resultados salvos — a página Deep Research agora (a) roda o prompt via Gemini live quando { run: true } e GEMINI_API_KEY está set, persistindo output para interview-prep/{slug}.md; (b) lista cada arquivo deep-research salvo como cards clicáveis com timestamps relativos; (c) renderiza resultados como Markdown com ações 📋 Copy / ⬇ Download .md / ↗ Open in tab por resultado. Nova superfície REST: GET /api/interview-prep, GET /api/interview-prep/:name, DELETE /api/interview-prep/:name. 7 testes novos.
  • feat(cv): gerar + download PDF no browser, com arquivo de PDFs — novo botão 📄 Generate PDF na página CV streamia /api/stream/pdf em console modal. Em ERR_MODULE_NOT_FOUND / erros de playwright, surface comando bootstrap copiável. Nova seção “Generated PDFs” auto-load após cada run bem-sucedido, listando cada output/*.pdf com botões ↗ Open e ⬇ Download. Nova superfície REST: GET /api/output/pdfs, GET /api/output/pdfs/:name. 6 testes novos.
  • feat(api): POST /api/tracker — append de linhas da UI (FIX-H8) — append de linha canônica a data/applications.md pelo browser. Valida company + role, normaliza status contra templates/states.yml, auto-incrementa # zero-padded, dedupa por company+role (case-insensitive), escapa pipes em notes para que a tabela markdown não fratura. Bootstrapa a tabela quando o arquivo está vazio. 6 testes novos.
  • feat(api): DELETE /api/jds/:name (FIX-H4) — remove JDs salvos sem shell out. Caracteres de path-traversal são strippados antes de qualquer toque no filesystem; o parâmetro deve terminar em .txt. 5 testes novos, incluindo recusa de ../../etc/passwd.
  • feat(api): POST /api/evaluate/test-gemini (FIX-H7) — endpoint de smoke-test que roda um JD dummy de 50 chars através de gemini-eval.mjs para que o usuário possa verificar se a API key funciona sem sentar por uma evaluation real. Retorna { ok, code, sampleLength, sample }.

🐛 Correções

  • fix(router): view catch-all 404 + guard de cobertura i18n (FIX-C7) — rotas de hash desconhecidas antes caíam silenciosamente para o dashboard, mascarando typos e bookmarks quebrados. Agora #/totally-random-xyz renderiza uma página 404 dedicada que cita o path errado de volta e linka para o dashboard. A view 404 é registrada dentro da própria IIFE do router para que não colida com nenhuma rota de usuário. Novo tests/i18n-coverage.test.mjs roda i18n.js dentro de um vm.Context com window stubado, expõe o DICT privado e afirma que cada uma das 173+ chaves × 8 locales é populada e não-vazia. 4 testes de router novos.
  • fix(router): alias #/profile → settings (FIX-C2) — o nome interno de rota é settings (com nav.settings renderizando “Profile”) mas links externos e memória muscular vão para #/profile. Agora ambos os endereços alcançam a mesma view, e o nav-item do sidebar acende em ambos. 2 testes novos.
  • fix(health): unificar Health/Doctor + flag template profiles (FIX-C6 + FIX-H6) — Health e Doctor eram duas fontes diferentes de verdade. Agora /api/health expõe tudo o que Doctor reporta (parent-deps, Playwright, dirs, profile-customized, HH_USER_AGENT). O check Profile customized detecta nomes placeholder (Jane Smith, Alex Doe, John Doe, Your Name, Test User) e erros explícitos de parse YAML. 4 testes novos.
  • fix(scan): warn em colisões query↔negative em config RU (FIX-H3) — quando portals.yml ship com "PHP" em title_filter.negative enquanto as queries miram Senior PHP, cada match fica filtrado e o usuário vê zero resultados. loadConfig() agora computa um array warnings; runRuScan() emite cada warning como linha stderr SSE antes do scan começar. 2 testes novos verificam que os defaults shipped continuam PHP-friendly out-of-the-box.
  • fix(scan): warn quando HH_USER_AGENT está unset (FIX-H1) — a página /scan faz probe em /api/health e mostra um card warn amarelo acima da action row quando HH_USER_AGENT está vazio, para que usuários saibam do 403 do hh.ru antes de clicar RU scan.
  • fix(api): warn quando slug de POST /api/jds teve chars unsafe strippados (FIX-M2) — normalização de slug que strippa chars perigosos retorna agora um campo warning; cleanup puro de case/whitespace continua silencioso. Resultado vazio após sanitização retorna 400.
  • fix(ui): limpar busca global em mudança de rota + spinners de botão (FIX-M4 + FIX-L1) — o input da busca global é limpo no hashchange (com guard para typing ativo). Novo helper UI.withSpinner(button, fn) cabeia estado de loading, ARIA e prevenção de double-click em cada click async de botão. Já adotado em Doctor / Verify / sync-check / Save CV / Normalize / Dedup / Merge.
  • fix(ui): tornar sidebar scrollable para que 18 itens de nav sempre alcancem o footer — o sidebar agrupado de FIX-C8 transbordava em viewports menores; itens de baixo (Activity / Health) ficavam clippados. .sidebar tem agora overflow-y: auto com scrollbars finas customizadas (WebKit + Firefox). Footer fica pinado via o margin-top: auto existente.
  • fix(ui): placeholder de modal-title vazio (FIX-H9) — a string "Title" em inglês hardcoded em index.html se foi, fechando a breve janela de race onde era visível durante a abertura do modal.

🌐 i18n

  • 173+ chaves de tradução × 8 locales suportados (en, es, pt-BR, ko, ja, ru, zh-CN, zh-TW). Chaves novas adicionadas em todos os locales para: página 404, activity log, deep research, fluxo PDF, warnings de segurança, mutação de tracker, rename de apply. Cobertura é agora enforced por tests/i18n-coverage.test.mjs — cada chave deve ter valor não-vazio em cada locale suportado ou o CI falha.

⚙️ DevOps

  • Contagem de testes: 73 → 201 (+128 testes em 23 arquivos de teste). O único teste remanescente falhando (runEnScan: dry-run end-to-end across multiple sources) é flake pré-existente dependente de respostas ao vivo da API Greenhouse/Ashby/Lever.
  • Playwright e2e comprehensive (tests/e2e-comprehensive.mjs, 23 passos): percorre toda a jornada do usuário — CV save → preview → PDF generation → todos os 7 modes novos → filtros do tracker → activity log → 404 → ESC do modal → scroll do sidebar → focus Ctrl-K → search clear → alias de profile → persistência de idioma.
  • GitHub Actions (.github/workflows/):
    • ci.yml — testes unit + integration em matrix Node 18/20/22, mais gate de cobertura i18n (cada chave × 8 locales deve ser não-vazia), mais Playwright e2e completo em cada PR.
    • ai-review.yml — review AI Claude Code em cada PR. Maintainers retêm autoridade de merge; Claude só sugere. Skip via label skip-ai-review.
    • release.yml — auto-publica um GitHub Release quando uma tag v*.*.* é pushada; release notes são fatiadas de CHANGELOG.md para que todas as 8 variantes de idioma fiquem como fonte canônica.
  • UI CSP-friendly: todos os handlers onclick inline removidos de index.html e router.js. A política estrita script-src 'self' é agora enforceable sem quebrar nenhuma feature.

📦 Novos endpoints REST

Método Path Propósito
GET /api/activity Lista eventos de user-action, mais novos primeiro
GET /api/interview-prep Lista arquivos Deep Research salvos
GET /api/interview-prep/:name Lê um único arquivo Deep Research
DELETE /api/interview-prep/:name Remove um arquivo Deep Research
GET /api/output/pdfs Lista PDFs gerados
GET /api/output/pdfs/:name Streamia um PDF como attachment
POST /api/tracker Append de uma linha em applications.md
DELETE /api/jds/:name Remove um JD salvo
POST /api/evaluate/test-gemini Smoke-test da Gemini API key
POST /api/mode/:slug Prompt builder genérico para os 7 modes novos (project / training / followup / batch / contacto / interview-prep / patterns)

[1.6.0] — 2026-05-02

Release público inicial da web UI. Veja README.md para o inventário de features nesta baseline.


career-ops-ui v1.267.1 · page 9/9 · mirror of the project changelog for cvstart.org