Changelog
GitHubTodas 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 compartilhadopublic/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 soboutput/<slug>-<TS>.pdfpara que o fluxo de auto-download existente assuma.feat(config): grupo regional de config (F-013)—/api/configagora expõegroups(core | runtime | regional) eregionalActive(boolean computado deportals.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) eSyntaxErrordeexpress.jsonagora retornam envelopes JSON que o SPA pode localizar (HTTP 413 / 400). Antes o handler default do Express retornava um stack trace HTML que quebrava otry { 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 paraPipeline,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 eventoscan:refreshapós cadarefreshResults(); o contador re-deriva “empresas com hits no último scan” do payload real de/api/scan-resultsem 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). AtingeREADME.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/configexpõegroups;regionalActivevira on quando portals.yml ganha uma source regional. - Novo
tests/pdf-extra-routes.test.mjs(5 casos): cada um de/report,/deep,/inlineinvocagenerate-pdf.mjscom 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 paramultipart/form-datatinha antes seu envelope de fio (--boundary…\r\nContent-Disposition: form-data; name="file"; filename="x"…) armazenado como conteúdo decv.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.mdnunca é 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 linhaUsage: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 emoutput/cv-input-<TIMESTAMP>.htmlvia um helper in-route markdown-to-print-HTML, então spawn’agenerate-pdf.mjs <input.html> <output.pdf> --format=a4. Query opcional?format=letterpara output US-letter. Quandocv.mdestá ausente, emite eventoerror+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 modificacv.md. - Novo
tests/pdf-stream-args.test.mjs(3 casos): eventostartcarrega<input.html> <output.pdf> --format=a4com paths absolutos e o HTML existe em disco;?format=lettertroca a flag;cv.mdausente emite o frame de erro esperado. - Total: 340 testes unit (era 318). Uma falha pré-existente em
portals-dead.test.mjspermanece 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)—isValidJobUrlagora rejeita RFC1918 (10/8,172.16/12,192.168/16), o range loopback 127/8 completo, link-local169.254/16(incl. AWS IMDS),0.0.0.0, CGNAT100.64/10e IPv6 ULA / link-local. Novo helperisPrivateOrLoopbackHost()é exportado deserver/lib/security.mjse reutilizado por/api/pipeline/preview, que agora fazdns.lookupno 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 emres.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-sucedidasPUT /api/profileePOST /api/configagora 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, depoisen.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)— novoresolveLocale(req)escolhe um locale debody.lang→body.locale→Accept-Language→'en'. NovobuildLocaleDirective(lang)emite um header de uma linha “Respond in X”.buildEvaluationPrompt,buildDeepPrompt,buildModePromptagora aceitam e embedlang.API.call()do SPA auto-anexaAccept-Languagee mesclalangem bodies JSON.feat(scripts): post-qa-cleanup.mjs (PR-11)— replays o checklist de cleanup do QA-regression;--applyescreve, default é dry-run, idempotente. Sweep de URLs RFC1918 /nip.io/test-cloud-*dedata/pipeline.mde audita o tamanho decv.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ênciaresolveLocalePR-2 +buildLocaleDirective+ integração com prompt-builder. tests/url-validation.test.mjsestendido com 5 testes novos para RFC1918 / link-local / 0.0.0.0 / 127/8 / CGNAT / IPv6 ULA / link-local.tests/activity-log.test.mjsteste 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 emtemplates/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 endpointPOST /api/cv/importconverte 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 viastripDangerousMarkdown(defesa em profundidade XSS). Cap rígido: 10 MB por upload. O📁 Upload CVno 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, nodone, 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—#/configagora tem uma faixa de abas. A primeira aba mantém o editor.envexistente (API keys, models, knobs de scanner). A nova aba Profile é editor YAML direto paraconfig/profile.yml:PUT /api/profilevalida o YAML (deve ser mapping, deve incluircandidate), carimba um header canônico# Career-Ops Profile Configurationse 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#/settingsainda 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.mjsestendido 14 → 16 subtests — adiciona CV-import via HTML e round-tripPUT /api/profile.- Regex ALIAS de
tests/router.test.mjsinvertido para afirmar a nova direçãosettings → 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.mjsganhaPOST /api/cv/importePUT /api/profile(binary-safe viaexpress.rawpara 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 emparsers.mjs::parseMarkdownTableadiciona 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/configantes 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/deepe/api/mode/:slugagora bailam com 413 quandobundleProjectContext + promptexcede 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, schemesjavascript:) 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/configGET/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 parajds/*.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 sectionschecava 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 deen.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/deepe/api/mode/:slugantes diziam ao modelo “leia esses arquivos primeiro” mas o Anthropic SDK não tem filesystem. O output era oco. NovobundleProjectContext({ modeSlugs })lêcv.md,config/profile.yml,modes/_shared.mde 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 declaude-sonnet-4-6para uma chamada de deep-research.fix(runner): escalada SIGKILL após período de graça SIGTERM (REVIEW-A2)—runNodeScriptestreamNodeScriptantes enviavam apenasSIGTERMem 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 paraSIGKILL. 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: emiteevent: 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 deredirect: 'follow'para redirect-walking manual. Cada headerLocationé re-validado porisValidJobUrl; 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 diretasprocess.env.GEMINI_API_KEYem handlers de rota substituídas porhasGeminiKey()delib/anthropic.mjs. Espelha o shape dehasAnthropicKey()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.runRuScanerunEnScanaceitamopts.signal; handlers SSE em/api/stream/scan-{ru,en}criam umAbortControllere abortam emres.close.test(anthropic): log-guard test previne vazamentos futuros de API-key via console (REVIEW-B4)— captura cada chamadaconsole.{log,info,warn,error,debug}durante caminhos happy + error derunAnthropic, afirma output zero e que a canary key string nunca aparece. Defesa em profundidade contra uma regressão futura deconsole.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 oisValidJobUrlem nível de rota.opts.validateopcional 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)— quandotests/i18n-coverage.test.mjsencontra um gap, o output é agora[ru] (3): foo, bar, bazem 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 deindex.mjspara 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_ALLOWLISTda UI deliberadamente exibe apenas 7 (os outros são Claude-Code-only). Comentário adicionado explicando o escopo estreito intencional. - Schema do
portals.ymlconfirmado:tracked_companies(96 entradas, 87 habilitadas, 71 com API). Scanner EN lê corretamente; chave legacycompaniesainda 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-entodos 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 skillsgsd-*.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 paraserver/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 derunAnthropic). - 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 vianode_modulesdo 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) — editaANTHROPIC_API_KEY,ANTHROPIC_MODEL,GEMINI_API_KEY,GEMINI_MODEL,HH_USER_AGENT,PORT,HOSTpelo browser. Escreve no.envdo 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 comrun: 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🌐 Scanroda 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-opentoggla 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/healthagora lê opackage.jsonpróprio do web-ui. O footer não vaza mais um1.6.0velho do arquivo de versão do pai. VERSION do pai ainda é surface separadamente comoparentVersion.
📦 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 referenciacv.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 paraparseEnv,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.envdo pai; aplicação live aprocess.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/previewstrippa scripts/styles/tags, cap em 8 KB, validado viaisValidJobUrl). 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 classesdata-url+.pipeline-row+.pipeline-row-delete. 8 testes novos emtests/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 viaANTHROPIC_MODEL). QuandoANTHROPIC_API_KEYestá 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 emtests/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— adicionadoserver/lib/dotenv.mjs(loader zero-dep de 35 linhas) cabeado no topo deserver/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..sidebartem agoraoverflow-y: autocom scrollbars finas customizadas.fix(ui): tornar banner HH_USER_AGENT dismissable— depois removido totalmente de/scanquando 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_dispatchda 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 atributodata-urlpara 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/cvagora remove<script>,<iframe>,<object>,<embed>,<style>,<form>,<svg>, handlers de eventoon*=e URIsjavascript:/vbscript:/data:text/htmlantes de escrevercv.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 alcanceinnerHTML. Atributoshrefde link são validados contra uma allowlist de schemes seguros (http/https/mailto/tel/relativo +data:imagesó). 17 testes novos no helper de strip e round-trips HTTP.fix(server): adicionar CSP e security headers baseline(FIX-L2) — cada resposta carrega agoraX-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), umContent-Security-Policyestrito é layerizado em cima:default-src 'self',script-src 'self'(semunsafe-inline), Google Fonts na whitelist,connect-src 'self'bloqueia exfiltração XSS. Handlersonclickinline emindex.htmlerouter.jsforam movidos paraaddEventListenerpara manter o CSP estrito intacto. 8 testes novos validando CSP em 5 valores diferentes deHOST.fix(api): apertar validator de URL de pipeline(FIX-M7) —POST /api/pipelineaceitava antes"not-a-url"e persistia. AgoraisValidJobUrl()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 flagdedupedem 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 deserver/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.exampledo pai agora inclui um bloco documentadoHH_USER_AGENTcom exemplo real de User-Agent do Chrome. 6 testes novos.fix(api): sanitizar JD antes da montagem do prompt(FIX-M5) —POST /api/evaluatestrippa 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/healthnã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óriomodes/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éricoPOST /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 emtests/modes-endpoints.test.mjs.fix: bootstrap de deps do pai + defaults russian_portals(FIX-C4 + C9 + C12 + H2) —bin/start.shinstala agora onode_modulesdo pai (js-yaml, playwright, jsdom) Enpx playwright install chromiumem clones frescos, para que/api/stream/scan,/pdfe/livenessfuncionem end-to-end out-of-the-box.createApp()faz probe deportals.ymlem cada boot — se o blocorussian_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.ymlagora ship com Ada / Factorial / Tinybird / Weights & Biases / Travelperk / Clarity AI / Forto / Vinted / Runway flaggeadosenabled: false(cada entrada tem comentário inline de razão). Instalações novas scaneiam 87 empresas vivas em vez de 96. Novoweb-ui/scripts/portals-health-check.mjsHEAD-probes cadacareers_urlhabilitada 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 emdata/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 paraGET /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 }eGEMINI_API_KEYestá set, persistindo output parainterview-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/pdfem console modal. EmERR_MODULE_NOT_FOUND/ erros deplaywright, surface comando bootstrap copiável. Nova seção “Generated PDFs” auto-load após cada run bem-sucedido, listando cadaoutput/*.pdfcom 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 adata/applications.mdpelo browser. Valida company + role, normaliza status contratemplates/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 degemini-eval.mjspara 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-xyzrenderiza 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. Novotests/i18n-coverage.test.mjsrodai18n.jsdentro de umvm.Contextcomwindowstubado, expõe oDICTprivado 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(comnav.settingsrenderizando “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/healthexpõe tudo o que Doctor reporta (parent-deps, Playwright, dirs, profile-customized,HH_USER_AGENT). O checkProfile customizeddetecta 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) — quandoportals.ymlship com"PHP"emtitle_filter.negativeenquanto as queries miram Senior PHP, cada match fica filtrado e o usuário vê zero resultados.loadConfig()agora computa um arraywarnings;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/scanfaz probe em/api/healthe mostra um card warn amarelo acima da action row quandoHH_USER_AGENTestá 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 campowarning; 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 nohashchange(com guard para typing ativo). Novo helperUI.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..sidebartem agoraoverflow-y: autocom scrollbars finas customizadas (WebKit + Firefox). Footer fica pinado via omargin-top: autoexistente.fix(ui): placeholder de modal-title vazio(FIX-H9) — a string"Title"em inglês hardcoded emindex.htmlse 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 portests/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 labelskip-ai-review.release.yml— auto-publica um GitHub Release quando uma tagv*.*.*é pushada; release notes são fatiadas deCHANGELOG.mdpara que todas as 8 variantes de idioma fiquem como fonte canônica.
- UI CSP-friendly: todos os handlers
onclickinline removidos deindex.htmlerouter.js. A política estritascript-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