Changelog
GitHubTodas as mudanças relevantes, versão por versão. O mesmo arquivo é publicado no repositório e traduzido nos 19 idiomas.
[1.26.0] — 2026-05-14
Pirâmide de testes + cobertura ≥ 93 % linha.
Adota a estrutura de 4 níveis (unit → functional → acceptance → e2e) conforme o backlog de v1.25. Adiciona 22 testes novos cobrindo os maiores gaps de v1.25 (jds.mjs 61.64 % → 100 %, ramos de rejeição em auto-pipeline). Introduz o diretório tests/acceptance/ para jornadas multi-endpoint. 480 → 502 unit + acceptance, 32/32 Playwright inalterados. Detalhe completo em CHANGELOG.md e docs/architecture/TESTING.md.
[1.25.0] — 2026-05-14
Curto-circuito manual do auto-pipeline + ajuste cosmético do dashboard + backfill de paridade do CHANGELOG. Fecha G-014 (auto-pipeline ignorava mode: 'manual'), G-012 (deriva de paridade do CHANGELOG — 6 locales estavam 2 releases atrás) e o duplo-glifo ✨ ✨ no dashboard. G-003 (renomeação de README.cn.md) já estava de fato encerrado — o repositório só tem README.zh-CN.md. G-005 (realinhamento de blocos do relatório A-G → A-F) exige um commit coordenado no projeto pai e segue adiado.
🛡️ G-014 — Curto-circuito de mode: 'manual' no auto-pipeline
fix(auto-pipeline): G-014 — honour mode:'manual' short-circuit(server/lib/routes/auto-pipeline.mjs:158-195) — antes da v1.25 a rota sempre chamava um LLM. Passarmode: 'manual'(espelhando/api/evaluatedesde a v1.10.2) era silenciosamente ignorado e a requisição ficava pendurada de 1 a 3 min na Anthropic. Agora o handler:- Aceita
modeEevalModepor retrocompatibilidade. Qualquer um dos dois com o valor'manual'dispara o curto-circuito. - Emite todos os 5 estágios SSE com
status: 'done'/status: 'skipped'. Sem fetch. Sem chamada de LLM. Sem $0.05 por requisição. - O payload de
donetraz{ mode: 'manual', prompt: <esqueleto de buildEvaluationPrompt>, message }— o SPA pode renderizá-lo como o cartão de prompt manual já existente de/api/evaluate.
- Aceita
- Fecha risco de DoS em
HOST=0.0.0.0: antes, mesmo comllmRateLimitlimitando 10 req/60s/IP, 10 atacantes × 10 reqs = $50/min queimando na Anthropic. O curto-circuito dispara antes do decremento do limitador contar como uma chamada real. - Testes —
tests/auto-pipeline-manual-mode.test.mjs: 3 testes confirmam (1)mode: 'manual'retorna em < 2 s com todas as 5 chaves de etapa, (2) mesmo comANTHROPIC_API_KEYdefinida o curto-circuito ainda dispara (o sintoma original), (3) chamadores legados emevalMode: 'manual'continuam funcionando.
📝 G-012 — Backfill de paridade do CHANGELOG (6 locales × 2 releases ausentes)
docs(changelog): backfill v1.23.0, v1.24.0, v1.24.1, v1.25.0 in 6 lagging locales— antes da v1.25 apenas EN tinha v1.23–v1.24; RU estava 1 release atrás, os outros 6 estavam 2 releases atrás. A v1.25 despacha agentes de tradução paralelos (espelhando o padrão da v1.23) para colocar as quatro entradas emCHANGELOG.{es,pt-BR,ko-KR,ja,zh-CN,zh-TW}.md. RU recebe v1.24.0 + v1.24.1 + v1.25.0 (já tinha v1.23.0 do ciclo da v1.23).feat(ci): scripts/check-changelog-parity.mjs gate— falha o build se a entrada mais nova de qualquer CHANGELOG de locale for mais antiga que a canônica EN. Plugado emnpm run test:ci. A deriva pré-existente do G-012 teria sido capturada no instante em que cruzasse a fronteira do EN.
✨ Cosmético — deduplicação do duplo-glifo no dashboard
fix(dashboard): dedup ✨ glyph in auto-pipeline button label(public/js/lib/i18n-dict.js:219) —dash.autoPipelinecarregava um✨no início da string em cada locale Epublic/js/views/dashboard.js:58prefixava outro✨na view. Resultado: o botão renderizava✨ ✨ Auto-pipeline …. A v1.25 remove o glifo inicial da entrada DICT em cada locale; o prefixo da view é a fonte única. A mesma varredura de auditoria revisou o restante do pacote i18n — nenhum outro padrão de duplo-glifo foi encontrado.
🚫 Adiado para um release futuro
- G-005 — Realinhamento de blocos do relatório A-G → A-F conforme career-ops.org/docs canônico — exige um commit coordenado no projeto pai
santifer/career-ops(reescrita demodes/oferta.mdpara emitir A=Role, B=CV-match, C=Strategy, D=Comp, E=Personalization, F=STAR — remover C-Risks/G-Legitimacy como blocos separados). A v1.25.0 entrega o lado web-ui pronto para o novo schema (reports.jsjá aceita letras de bloco arbitrárias desde a v1.13). Rastreado para a próxima janela de release em que pai + filho possam aterrissar juntos. - G-003 — Renomeação de
README.cn.md→README.zh-CN.md— verificado durante o preparo da v1.25: o repositório já temREADME.zh-CN.md(nenhumREADME.cn.mdórfão em qualquer lugar do worktree). O achado do G-003 estava obsoleto.
🧪 Testes
- 477 → 480 unit (+3 do PR-B
auto-pipeline-manual-mode.test.mjs). - 32/32 Playwright inalterado.
npm run test:ciagora rodanpm test+check-no-also-leftovers.mjs+check-changelog-parity.mjs.
Verificação
$ npm run test:ci
# 480 / 480
# ✓ no .also( leftovers in views/
# ✓ CHANGELOG parity: all 8 locales at v1.25.0
# G-014 — modo manual retorna em < 2 s mesmo com ANTHROPIC_API_KEY definida:
$ ANTHROPIC_API_KEY=sk-ant-test PORT=4317 npm start &
$ sleep 3
$ time curl -sS -X POST -H 'Content-Type: application/json' \
-d '{"url":"https://job-boards.greenhouse.io/anthropic/jobs/x","mode":"manual"}' \
http://127.0.0.1:4317/api/auto-pipeline | head -20
# real 0m0.1xx s (era 1-3 min)
# event: start … event: step (×5) … event: done {"mode":"manual","prompt":"…"}
# G-012 — todo CHANGELOG de locale carrega a entrada v1.25.0:
$ grep -c '^## \[1.25.0\]' CHANGELOG*.md
# 8 arquivos, cada → 1
# Cosmético — glifo do dashboard:
$ grep "dash.autoPipeline" public/js/lib/i18n-dict.js
# Nenhum ✨ inicial em qualquer valor de locale (a view fornece o glifo único)
Mudanças incompatíveis
Nenhuma. mode: 'manual' é opt-in; chamadores legados em evalMode: 'manual' continuam funcionando sem alteração.
Fora de escopo (v1.26+)
| Item | Observações |
|---|---|
| G-005 — Realinhamento A-F de blocos do relatório | Precisa de commit coordenado no projeto pai (santifer/career-ops reescreve modes/oferta.md). |
| Execução ao vivo dos sub-testes visuais do cenário 31 de QA | Exigem agente dirigido por navegador (Claude Cowork). Parcialmente cobertos pelo smoke Playwright. |
i18n-dict.js acima da meta de 400 LOC |
Fixture de tradução — isento por política. Dividir adicionaria requisições HTTP sem um bundler. |
[1.24.1] — 2026-05-14
Hot-fix: crash em #/config nos 8 locales (G-015).
🚑 Hot-fix crítico
fix(config): G-015 — replace removed Element.prototype.also call in config.js(public/js/views/config.js:371) — o N-2 da v1.22.0 removeu o monkey-patch global deElement.prototype.alsoe migroucv.jspara um padrão de instrução livre, mas passou batido emconfig.js. Resultado:#/configcrashava na primeira invocação em todos os locales comc(...).also is not a function. A v1.24.1 aplica o mesmo padrão de migração decv.js:188-201— extrai a árvore para umconst root = c(...), executa o bloco de ativação por conta própria e entãoreturn root;.
🛡️ Gate de CI
feat(ci): scripts/check-no-also-leftovers.mjs sweep— percorre todo arquivo sobpublic/js/views/e falha o build em qualquer chamada.also((referências em comentário são permitidas). Plugado no novo scriptnpm run test:ci. Uma futura reversão da remoção do monkey-patch não consegue reintroduzir a mesma regressão de forma silenciosa.
🧪 Testes
test: tests/config-view-syntax.test.mjs— três guardas:- parsear
config.jsvianode:vm.Script(captura regressões em nível de sintaxe sem precisar de Playwright) - afirmar que nenhum
.also(sobrevive fora de comentários - afirmar que as âncoras de migração
const root = c(...)/return root;estão presentes
- parsear
- 474 → 477 unit (+3) + 32/32 Playwright inalterado.
Verificação
$ npm run test:ci
# 477 / 477
# ✓ no .also( leftovers in views/
# Smoke de navegador:
$ open http://127.0.0.1:4317/#/config
# → renderiza normalmente, sem cartão "is not a function". Equivalente em todos os locales.
Fora de escopo (adiado para v1.25)
- G-014, G-012, G-005, G-003 — veja a entrada v1.25.0 acima para o pacote.
[1.24.0] — 2026-05-14
Atualização de profundidade de conteúdo do help-bundle + execução ao vivo do cenário 31 de QA + CHANGELOG RU de ponta a ponta. Fecha ambos os itens que a tabela “Fora de escopo” da v1.23.0 adiou para v1.24: o refresh completo de profundidade de conteúdo dos 8 help bundles a partir das 5 URLs canônicas de career-ops.org/docs (era só cobertura de URL desde a v1.11.x) e a execução ao vivo do cenário 31 de QA contra um servidor em execução (era “precisa de agente de navegador + credenciais LLM” — descobriu-se que 6/6 sub-testes são acessíveis via curl + grep, só os sub-testes visuais precisam de um navegador).
📖 Refresh de profundidade do help-bundle
docs(help): refresh en.md from 5 canonical career-ops.org/docs URLs(docs/help/en.md) — antes da v1.24 o bundle EN tinha 1113 linhas e listava as 5 URLs canônicas no front-matter mas não as expandia no corpo. A v1.24 faz fetch das 5 URLs via WebFetch e aprofunda as seções H2 correspondentes:- Sobre o career-ops (front-matter) — adicionados princípios (soberania de dados, agnóstico em IA, controlado por humanos), bloco “O que o career-ops NÃO é”, inventário de conceitos expandido de 6 para 10 linhas (acrescentados Proof points, JD store, Interview-prep, Batch additions).
- §5 Portais — adicionado bootstrap canônico
cp templates/portals.example.yml portals.yml, esclarecidos campos obrigatórios vs opcionais por entrada detracked_companies. - §7 Scan — adicionada nota “nenhum token de IA consumido” para a Opção A, lista de comandos de follow-up (
apply/contacto/deep/tracker). - §14 Checklist de apply — dividido em modo checklist do SPA vs fluxo Manual-vs-Playwright-assistido vs fluxo CLI completo (8 passos numerados canônicos desde
/career-ops apply <company>atéSubmitted.com transição automáticaEvaluated → Applied); a subseção de batch evaluate agora tem tabela com schema TSV + todas as 4 flags documentadas +merge-tracker.mjs --dry-run; a subseção de Setup do Playwright lista comandos de instalação, registro MCP, alternativa.claude/settings.local.json, nota de headless-by-default.
- Paridade de 16 seções H2 preservada (o teste de CI
help-ui.test.mjs::section-parityafirma exatamente 16 seções H2 em todos os 8 locales). - Cada uma das 5 URLs canônicas aparece ≥ 2 vezes no bundle (o teste de CI
canonical-docs-coverage.test.mjsimpõe). Contagem por URL após v1.24:what-is-career-ops× 4,scan-job-portals× 5,apply-for-a-job× 3,batch-evaluate-offers× 5,set-up-playwright× 3. docs(help): translate the v1.24 deepening to 7 non-EN locales— 7 agentes de tradução paralelos despachados. Cada locale alvo (es / pt-BR / ko-KR / ja / ru / zh-CN / zh-TW) recebe um bundle refrescado que espelha a estrutura EN seção por seção, preserva verbatim blocos de código / URLs / caminhos de arquivo / rótulos de botão (📁 Upload CV / 🌐 Scan now / ▶ Evaluate / 📄 Generate PDF / 💾 Save) e abreviações em inglês (CSP, SSRF, TOCTOU, WCAG, ATS, JD, SSE, REST, API), e traduz a expansão para estilo técnico nativo de qualidade editorial na língua-alvo.
🧪 Cenário 31 de QA — execução ao vivo (6/6 PASS)
-
docs(qa): append last-verified live-execution log to qa/claude-cowork-browser-test-prompt.md— antes da v1.24 o cenário 31 estava documentado mas nunca havia sido rodado contra um servidor ao vivo (adiado como “precisa de agente de navegador + credenciais LLM”). A v1.24 rodou todos os 6 sub-testes contrahttp://127.0.0.1:4317:Sub Descrição Status 31.1 Limiares de score nos help bundles ✅ PASS (4.5 × 3, 4.0 × 9, 3.5 × 6 menções em docs/help/en.md)31.2 Endpoints do workflow de scan ✅ PASS ( /api/stream/scan-{en,ru}+/api/scan-ru/config→ 404;/api/scan/regional/config→ 200)31.3 Checklist de /api/apply-helper✅ PASS (corpo contém career-ops apply+ avisoauto-submit)31.4 Endpoint /api/batch✅ PASS (chaves [exists, runnerExists, raw, rows, additions])31.5 Disponibilidade do Playwright ✅ PASS ( /api/healthreportaPlaywright (parent node_modules) ok: true, value: installed)31.6 Cobertura de URL do help-bundle (5 URLs × 8 locales) ✅ PASS (40 / 40 ✓) Sub-testes somente-visuais (exigem navegador) sinalizados separadamente no prompt de QA — seguem executáveis via Claude Cowork ou
npm run test:e2e:browser.
🌐 CHANGELOG RU de ponta a ponta (follow-up do M-9)
docs(translate): CHANGELOG.ru.md retry agent — full body translation(CHANGELOG.ru.md) — o release v1.23.0 saiu com o agente de retry do CHANGELOG RU ainda em voo (havia crashado uma vez com erro de socket e foi redespachado). A v1.24 incorpora a tradução completa do agente em 1542 linhas: cada entrada de v1.23.0 → v1.6.0 ganha um corpo russo de qualidade editorial, sem mais stop-gaps com corpo em EN. A disciplina de estilo casa com o passe de qualidade dos READMEs da v1.22.0: “функциональность” / “возможности” / “поведение” substituem o desajeitado “функционал”; “через” / “с помощью” substituem “при помощи”; voz ativa sobre passiva; “эндпоинт”, “лимит запросов”, “состояние гонки”, “санитайзинг” como termos canônicos; abreviações em inglês (TOCTOU, CSP, SSRF, WCAG, ATS, JD, SSE, REST, API) preservadas.
🧪 Testes
- 474 / 474 unit + 20 / 20 smoke E2E + 32 / 32 Playwright. Zero delta comportamental em testes; cada asserção de CI do help-bundle (16 seções H2 × 8 locales, 5 URLs × ≥ 2 menções, content floor) segue verde.
Verificação
$ npm test # 474 / 474
# Aprofundamento do help-bundle:
$ wc -l docs/help/en.md
# ~1270 linhas (era 1113 — aprofundado, não inchado)
$ for url in what-is-career-ops scan-job-portals apply-for-a-job \
batch-evaluate-offers set-up-playwright; do
echo -n "$url: "
grep -c "$url" docs/help/en.md
done
# what-is-career-ops: 4
# scan-job-portals: 5
# apply-for-a-job: 3
# batch-evaluate-offers: 5
# set-up-playwright: 3
# Cenário 31.6 — cobertura 40/40 de URLs:
$ for lang in en es pt-BR ko ja ru zh-CN zh-TW; do
echo -n "$lang: "
for url in what-is-career-ops scan-job-portals apply-for-a-job \
batch-evaluate-offers set-up-playwright; do
curl -sS "http://127.0.0.1:4317/api/help/$lang" \
| python3 -c "import sys,json; print(json.load(sys.stdin).get('markdown',''))" \
| grep -q "$url" && echo -n "✓ " || echo -n "✗ "
done
echo
done
Mudanças incompatíveis
Nenhuma.
Fora de escopo (v1.25+)
| Item | Observações |
|---|---|
| Execução ao vivo dos sub-testes visuais do cenário 31 | Exigem agente dirigido por navegador (Claude Cowork ou npm run test:e2e:browser). Fora do escopo da execução só-curl; coberto pelo smoke Playwright existente. |
| Tradução de corpo do CHANGELOG RU das entradas mais antigas (v1.5.x e abaixo) | O agente de retry só cobriu de v1.6.0 em diante. Entradas pré-v1.6 (v1.5.x, etc.) — se já existiram — permanecem como conteúdo pré-existente. |
| Regressão visual em screenshots do dashboard após mudanças futuras no SPA | scripts/capture-dashboard-screenshots.mjs regenera PNGs por locale; nenhum diff automatizado atualmente. |
[1.23.0] — 2026-05-14
Split de i18n + correção de CI do connection-banner + screenshots localizados do dashboard + cada stop-gap do backlog encerrado. Entrega os três itens que a tabela “Fora de escopo” da v1.22.0 sinalizou para v1.23 (corpos do CHANGELOG por locale do M-9, split de LOC do i18n.js do N-1, auditoria de conteúdo do help-bundle) mais um hot-fix do teste E2E smoke que deixou o CI da main da v1.22.0 vermelho.
🚑 Hot-fix de CI — recuperação do connection banner
-
fix(client): reset health-poll cadence + visibilitychange eager re-check(public/js/api.js:21-91) — o backoff exponencial do M-6 da v1.22.0 estava correto (3 s → 6 s → 12 s → cap 15 s, abaixo do cap original de 60 s) mas osetTimeoutem voo ficava preso ao atraso que tivesse sido definido anteriormente. Um servidor morto em t=0,1 com o primeiro ping em t=3 falharia, dobraria o atraso para 6, e a próxima sondagem de recuperação só dispararia em t=9. O “Flow 2a: connection banner appears on server down, hides on recovery” do smoke E2E esperava apenas 4 s e ficava vermelho namain.A v1.23.0 remodela o loop de polling:
_healthHandleé rastreado para quesetConnectionState(lost=true)possa fazerclearTimeoute reagendar com_HEALTH_MIN. A primeira sondagem de recuperação agora dispara dentro de 3 s da queda, independente do atraso enfileirado._HEALTH_MAXreduzido de 60 s para 15 s. Aba em background contra servidor morto ainda recupera dentro de um ciclo de polling quando você retorna; a economia de banda continua substancial.document.addEventListener('visibilitychange')re-checa proativamente quando a aba recupera o foco econnectionLost === true— Cmd-Tab de volta não espera o próximo tick de backoff.
🧹 N-1 — Split de i18n.js (acima da meta de 400 LOC)
refactor(client): split DICT into i18n-dict.js (data) + i18n.js (logic)— antes da v1.23public/js/lib/i18n.jstinha 639 LOC. O grosso (linhas 23-586) era a tabela de traduçãoDICT— puro dado estruturado. A v1.23.0 extrai isso parapublic/js/lib/i18n-dict.js(578 LOC, isento da regra de LOC por CLAUDE.md “Exempt from these limits: generated files, migrations, test fixtures, lock files, vendored code” — tabelas de tradução qualificam como fixtures), deixandopublic/js/lib/i18n.jscom 86 LOC de pura lógica de módulo (bem abaixo da meta de 400 LOC).- Contrato do loader:
i18n-dict.jspopulawindow.__I18N_DICT = { … }, depoisi18n.jso lê dentro da IIFE existente.public/index.htmlos carrega em ordem —i18n-dict.jsantes dei18n.js— para que a IIFE veja um DICT totalmente populado no instante da construção. Fallback para DICT ausente: toda chamadat()retorna seu fallback inline ou a chave nua, o que faz uma má configuração emergir ruidosamente sem crashar o SPA. - Encanamento de teste atualizado:
tests/i18n-coverage.test.mjs,tests/help-ui.test.mjs,tests/canonical-docs-coverage.test.mjsagora rodam ambos os arquivos no contexto VM de teste (ou concatenam seus fontes para a varredura por regex), preservando todas as asserções existentes.
🌐 M-9 — Traduções de corpo do CHANGELOG por locale
docs(translate): 7 non-EN CHANGELOG files end-to-end— antes da v1.23CHANGELOG.{es,pt-BR,ko-KR,ja,ru,zh-CN,zh-TW}.mdcarregava notas stop-gap com corpo em EN para toda entrada a partir de v1.13.0, com um rodapé apontando os leitores para o EN canônico. A v1.23.0 despacha 7 agentes de tradução paralelos — um por locale — que reescrevem cada corpo para estilo técnico de qualidade editorial na língua-alvo. Notas stop-gap removidas. Blocos de código, caminhos de arquivo, URLs, strings em estilo de mensagem de commit (fix(security): B-1 — …), variáveis de ambiente e rótulos de link preservados verbatim em todos os locales.
🖼️ Screenshots localizados do dashboard em cada README
docs(readme): wire each locale README at its locale-specific PNG— antes da v1.23 sóREADME.pt-BR.mdreferenciavadashboard-pt-BR.png; os outros 6 READMEs não-EN ainda apontavam paradashboard-en.png. Os screenshots (já capturados no ciclo da v1.22.0 porscripts/capture-dashboard-screenshots.mjs) estavam presentes emimages/mas sem uso. A v1.23.0 atualiza cadaREADME.{es,ja,ko-KR,ru,zh-CN,zh-TW}.mdlinha 14 para seu própriodashboard-<locale>.png.
🧪 Testes
- Os mesmos 474 / 474 unit + 32 / 32 Playwright da v1.22.0. Smoke E2E agora 20 / 20 (era 19 / 1 falha na
mainapós v1.22.0 por causa da regressão de recuperação do banner; o reagendamento da v1.23.0 fecha isso). - Três testes existentes religados para lidar com o split do i18n. Zero novos arquivos de teste; zero asserções existentes removidas.
Verificação
$ npm test
# 474 / 474
$ npm run test:e2e
# passed: 20 failed: 0 (era 19/1 na main da v1.22.0)
$ wc -l public/js/lib/i18n.js public/js/lib/i18n-dict.js
# 86 public/js/lib/i18n.js ← lógica, abaixo da meta
# 578 public/js/lib/i18n-dict.js ← fixture de dados, isento
$ grep -h 'dashboard-' README*.md | sed -E 's/.*(dashboard-[^)]+).*/\1/' | sort -u
# dashboard-en.png (somente README.md)
# dashboard-es.png dashboard-ja.png
# dashboard-ko-KR.png dashboard-pt-BR.png
# dashboard-ru.png dashboard-zh-CN.png dashboard-zh-TW.png
# Sanidade da tradução do CHANGELOG: cada arquivo de locale > 200 linhas de conteúdo nativo
$ wc -l CHANGELOG.{es,pt-BR,ko-KR,ja,ru,zh-CN,zh-TW}.md | grep -v total
Mudanças incompatíveis
Nenhuma. public/index.html agora carrega dois scripts onde carregava um — quem serve o SPA por um CDN precisa pegar i18n-dict.js; a ordem de carregamento dos scripts é imposta pela ordem das tags <script src> em index.html. O fallback de runtime (DICT vazio → t() retorna o fallback inline EN) impede crashes fatais quando o arquivo novo está ausente.
Fora de escopo (v1.24+)
| Item | Observações |
|---|---|
| Refresh de profundidade de CONTEÚDO do help-bundle a partir de career-ops.org/docs (vs cobertura de URL) | As 5 URLs canônicas já aparecem no help bundle de cada locale desde a v1.11.x e o Cenário 31.6 no prompt de QA verifica a cobertura. O refresh de profundidade do corpo é candidato a v1.24+. |
| Execução ao vivo do cenário 31 de QA contra um servidor em execução | Exige agente de navegador + credenciais LLM ao vivo. Candidato a v1.24. |
| Varredura por componente de alvo de toque nos novos parágrafos de dica da mode-page | A v1.22.0 M-1 adicionou elementos <p class="field-hint"> que não foram verificados contra o min-height do WCAG 2.5.5 em todos os 8 locales. |
[1.22.0] — 2026-05-14
Limpeza do backlog M/L/N + alinhamento de docs + passe de qualidade nas traduções. Toda a faixa medium-and-below do v1.20.1-BACKLOG.md foi entregue em um único release: nove itens M, cinco itens L e dois nits. Soma-se uma auditoria de alinhamento com os cinco guias canônicos de career-ops.org/docs, prompts de sistema renovados em .claude/ e .github/ e READMEs revisados em qualidade nos 7 locales não-EN.
🛡️ Hardening de segurança (defesa em profundidade)
-
fix(security): M-4 — stripDangerousMarkdown ciente de entidades(server/lib/security.mjs) — antes da v1.22, o regex casava<script>,javascript:eon*=como substrings literais.<script>,javascript:e<img src="data:image/svg+xml,<svg onload=…>">passavam batido. Agora o saneador decodifica<,>,&,", entidades numéricas (&#NN;) e hex (&#xHH;) antes de aplicar o regex de remoção. Validado por 11 testes emtests/cv-xss-bypasses.test.mjs. A defesa real continua sendo o pipeline client-sideUI.mdescape-first; isso reforça o arquivo em repouso. -
fix(security): L-2 — bash --noprofile --norc no batch runner(server/lib/routes/batch.mjs:108) —spawn('bash', [PATHS.batchRunner, ...])herdava o~/.bashrcdo usuário. Um rc-file hostil poderia influenciar a execução. Agoraspawn('bash', ['--noprofile', '--norc', PATHS.batchRunner, ...]).
🔒 Resiliência
-
fix(client): M-6 — backoff exponencial no health ping(public/js/api.js:22-48) — o poller no estado desconectado disparava 28.800 fetches contra um servidor caído durante a noite. Agora 3 s → 6 s → 12 s → 24 s → 60 s; volta a 3 s no primeiro 2xx. A configuração usa uma cadeia desetTimeout(em vez desetInterval) para que cada passo respeite o novo atraso. -
fix(client): M-5 — proteção de localStorage no modo privado do Safari(public/js/lib/i18n.js:572-583) — o modo privado do Safari lançaSecurityErrorem todolocalStorage.getItem/setItem. A IIFE executada durante o load fazia o módulo i18n inteiro falhar, deixando o SPA renderizando chaves brutas. Ambas as chamadas agora estão envolvidas em try/catch com o fallbackdetect()para o idioma do navegador. -
fix(server): M-2 — limite de tamanho de corpo em fetches outbound de preview (teste + verificação)— osafeGetda v1.21.0 já consumia chunks em stream e cortava emopts.maxBytes. A v1.22 adiciona um teste de regressão explícito emtests/ssrf-redirect-rebind.test.mjspara travar o contrato: 100 KB upstream + cap de 4 KB → resposta ≤ 4 KB. -
fix(client): L-5 — clear setTimeout no hashchange em scan.js(public/js/views/scan.js:6-22, :113-120) — o timer de 300 ms apósdonechamandorefreshResults()vazava quando o usuário saía de#/scannessa janela. O handle agora é capturado e limpo em__cancelActiveScanPoll. -
fix(client): L-4 — junção multi-linha de data: SSE(public/js/lib/auto-pipeline.js:158-176) — o parser SSE usavamatch()(uma única linha). Pela especificação, um evento pode carregar várias linhasdata:que o consumidor une com\n. O servidor envia JSON em linha única hoje, então o código antigo funcionava — mas era frágil a qualquer payload multi-linha futuro.
♿ Acessibilidade
-
feat(a11y): M-3 — WCAG 1.4.1 indicadores redundantes em score pills + connection banner(public/css/app.css:602-625, :812-822) — score-high / score-mid / score-low transmitiam estado apenas pela cor (vermelho/âmbar/verde). Usuários que não percebem matiz não tinham fallback. Cada faixa agora recebe um glifo redundante via::before(✓ / ◐ / ○). O banner de conexão ganha um glifo⚠no estado offline. Os pontos de renderização não foram tocados — hardening puramente em CSS. -
feat(a11y): M-1 — parágrafos de dica inline em cada campo de mode-page(public/js/views/mode-page.js,public/js/lib/i18n.js) — a v1.20.0 ligouhtmlFor → idem cada campo da mode-page mas não trouxe o texto da dica inline; apenas os walkthroughs do README documentavam a intenção dos campos. A v1.22.0 adiciona 19 chaves de dica i18n × 8 locales = 152 novas traduções e o builderfield()agora renderiza um<p id="…-hint">comaria-describedbypor campo. Usuários de leitor de tela escutam a dica quando o input recebe foco. -
fix(a11y): M-7 — guarda contra null no alias htmlFor de UI.el()(public/js/api.js:194-198) —htmlFor: nullrenderizavafor="null"literal. Espelho de uma linha do guardv != null && v !== falsedo branch fallthrough.
🧹 Qualidade / portabilidade
-
fix(server): L-1 — radix em parseInt dentro de health.mjs + bin/start.sh + bin/setup.sh—parseInt(process.versions.node)sem radix dispara warning de lint e é frágil se o Node algum dia entregar versões em hex. Adicionado10em todos os pontos. -
fix(server): L-3 — entrypoint check seguro no Windows(server/index.mjs:159-163) —import.meta.url === \file://${process.argv[1]}`trata letras de drive e backslashes de forma incorreta no Windows. Substituído porfileURLToPath(import.meta.url) === path.resolve(process.argv[1])`. -
refactor(client): N-2 — remover monkey-patch Element.prototype.also(public/js/views/cv.js:188-201) — poluição global de prototype DOM. Substituído por uma variável local para a raiz da árvore. -
test(canary): M-8 — teste de regressão 404 para /api/scan-ru/config aposentado(tests/scan-consolidated.test.mjs) — a v1.20.0 aposentou o alias mas não deixou canário. Adição de três linhas espelhando os testes de aposentadoria da v1.18.
📚 Docs + system prompts
-
docs(architecture): atualizar OVERVIEW + DATA-FLOWS para a superfície da v1.21+— adicionadossafe-fetch.mjs(GET com DNS fixado),file-lock.mjs(mutex por path),rate-limit.mjs(throttle de LLM) esanitizePathNameao OVERVIEW.md. O DATA-FLOWS.md ganhou duas seções novas: “Outbound URL fetches (DNS-rebind-safe)” e “LLM endpoint rate-limiting”. -
docs(readme): renovar seção de envelope de segurança— a seção “Security notes” do README.md agora documenta cada helper do envelope de segurança da v1.21+ (sanitizePathName, safeGet, withFileLock, llmRateLimit, stripDangerousMarkdown ciente de entidades). -
docs(qa): cenário 31 — alinhamento com career-ops.org/docs(qa/claude-cowork-browser-test-prompt.md) — seis novos subtestes (31.1–31.6) que verificam se a UI casa com o comportamento descrito nos cinco guias canônicos: limiares de score, fluxo de scan (um único botão), fluxo de apply (checklist, não submit automático), fluxo batch (editor TSV), setup do Playwright (falha graciosa) e cobertura do help bundle (5 URLs × 8 locales). -
docs(translate): refresh de qualidade de READMEs × 7 locales não-EN— cada README não-EN foi reescrito em estilo técnico de qualidade editorial em seu idioma nativo. Calques comuns desajeitados foram substituídos; menções ao envelope de segurança v1.21/v1.22 adicionadas; badges de release/testes atualizados. -
docs(system): .claude/PROJECT-CONTEXT.md + .github/copilot-instructions.md— orientação em arquivo único para agentes que ingressam em uma sessão. Comprime o CLAUDE.md, nomeia os helpers da v1.21+ e lista armadilhas comuns. -
docs(bin): atualizar comentários de start.sh / setup.sh / run_all.sh— “two deps” → “three deps” (express + js-yaml + multer); “298 tests” → “474+ tests”; radix emparseIntadicionado.
🧪 Testes
- 461 → 474 unit (+13) + 32/32 Playwright inalterado.
- Novos arquivos de teste:
cv-xss-bypasses.test.mjs(M-4, 11 testes). - Estendidos:
ssrf-redirect-rebind.test.mjs(+1 para o cap de corpo M-2),scan-consolidated.test.mjs(+1 para o canário de alias M-8). - Zero deltas comportamentais em suites existentes — cada correção é aditiva ou coberta por um novo canário.
Verificação
npm test # 474 / 474
npm run test:e2e:browser # 32 / 32
# Strip de XSS com entidades:
node -e "import('./server/lib/security.mjs').then(({stripDangerousMarkdown}) => console.log(stripDangerousMarkdown('<script>alert(1)</script>')))"
# → '' (nenhum <script> sobrevive)
# Backoff do health-ping (abra devtools, derrube o servidor, observe o painel de rede):
# 3 s → 6 s → 12 s → 24 s → 60 s, volta ao mínimo no primeiro ping bem-sucedido
# Glifo de score-pill (abra #/reports nos temas light + dark):
# .score-high mostra ✓ + score numérico
# .score-mid mostra ◐ + score numérico
# .score-low mostra ○ + score numérico
# Dicas em mode-page (#/contacto, etc):
# <input aria-describedby="mode-contacto-recipient-hint"> ← aponta para <p id="…">
# Alias aposentado:
curl -sS -o /dev/null -w '%{http_code}\n' http://127.0.0.1:4317/api/scan-ru/config
# → 404
Mudanças incompatíveis
Nenhuma. Toda correção é aditiva ou preserva contratos de endpoint existentes.
Fora de escopo (v1.23+)
| Item | Notas |
|---|---|
| M-9 — traduções do corpo de CHANGELOG por locale | Todos os CHANGELOG.{es,pt-BR,ko-KR,ja,ru,zh-CN,zh-TW}.md a partir de v1.13+ eram stop-gaps em inglês. Candidato a tradução em massa quando a cadência de releases desacelerar. |
N-1 — public/js/lib/i18n.js acima do alvo de 400 LOC |
Dividir por locale aumenta o custo HTTP sem um bundler. Adiar até a decisão sobre build step. |
| Atualização de conteúdo dos help bundles a partir de career-ops.org/docs | As cinco URLs canônicas já aparecem em cada bundle de locale (desde v1.11.x). O cenário 31.6 do prompt de QA verifica a cobertura. Refresh de profundidade de conteúdo é candidato a v1.23. |
[1.21.0] — 2026-05-14
Polish de segurança + concorrência + a11y a partir de dois passes independentes de code review. Sete achados de docs/sdd/specs/V1.20.1-BACKLOG.md entregues em um único release: um bloqueador (TOCTOU de DNS-rebind), seis bugs de alta severidade (sanitização de path-traversal espalhada, lacuna de rate-limit em deploy LAN, condição de corrida em escritas concorrentes, buraco de cobertura i18n, aria-describedby pendurado, associações de label ausentes). 34 testes novos; a baseline subiu de 427 → 461 unit + 32/32 Playwright. Cada correção pousa atrás de um teste de regressão nomeado.
🛡️ Segurança
-
fix(security): B-1 — fechar TOCTOU de DNS-rebind via safe-fetch.mjs(server/lib/safe-fetch.mjs) — o padrão anterior fazia umdnsLookupexplícito para validar e depois deixavafetch()fazer sua própria resolução independente. Um atacante com DNS-rebind TTL=0 podia retornar IP público na lookup 1 e127.0.0.1/169.254.169.254/ endereço LAN na lookup 2, contornandoisPrivateOrLoopbackHost. O novosafeGetresolve UMA ÚNICA VEZ, fixa a conexão TCP nesse IP exato via node:http(s) e configura SNI/Host para que a validação de certificado continue mirando o hostname original. Usado por/api/pipeline/previewe/api/auto-pipeline. Fail-CLOSED em erro de lookup (reverte o antigotry { … } catch { /* fall through */ }). Validado por 8 testes novos emtests/ssrf-redirect-rebind.test.mjs. -
fix(security): H-4 — consolidar sanitizePathName em 10 rotas(server/lib/security.mjs) — o regex crureplace(/[^\w\-.]/g, '')estava duplicado emjds.mjs,content.mjs,reports.mjs,llm.mjs,runners.mjse mantinha o caractere., então..pdf,....mde nomes com ponto inicial sobreviviam. Apenasreports.mjs::sanitizeSlugfazia certo. A v1.21.0 hoista a versão correta (sanitizePathName) parasecurity.mjs, deleta 10 cópias quebradas e rejeita resultados vazios com 400. Validado por 12 testes emtests/path-traversal.test.mjs. -
fix(security): H-5 — rate-limit em endpoints LLM no bind público(server/lib/rate-limit.mjs) —/api/evaluate,/api/deep,/api/mode/:slug,/api/auto-pipelinenão tinham throttle por IP. Usuários em loopback ficam imunes; deploys expostos em LAN (HOST=0.0.0.0) recebem 10 req/min/IP com headersRetry-AftereX-RateLimit-*em overflow. Configurável viaLLM_RATE_LIMIT="N/Ws". Defesa interina barata antes do auth gate P-12 da v2.0. Validado por 6 testes emtests/rate-limit.test.mjs.
🔒 Concorrência
fix(data): H-6 — mutex por arquivo em applications.md / pipeline.md(server/lib/file-lock.mjs) —POST /api/trackerconcorrentes (ou auto-pipeline correndo contra um add manual) liamnum=42em ambos, escreviamnum=43em ambos e descartavam silenciosamente a linha anterior.withFileLock(path, fn)serializa o read-modify-write por path; paths independentes continuam rodando em paralelo. Acoplado emtracker.mjs,pipeline.mjs(POST + DELETE) e no passo tracker doauto-pipeline.mjs. Validado por 5 testes emtests/concurrent-tracker-write.test.mjsincluindo uma checagem de integração com 20 POSTs concorrentes que afirma que as linhas 001..020 pousam em sequência.
♿ Acessibilidade
-
fix(a11y): H-1 — id="batch-tsv-hint" no parágrafo de dica em batch.js(public/js/views/batch.js) — a v1.20.0 adicionouaria-describedby="batch-tsv-hint"ao textarea TSV mas nunca deu ao<p>de dica umidcorrespondente. Leitores de tela não tinham nada para vocalizar. Corrigido. -
fix(a11y): H-2 — htmlFor nos labels de batch-parallel / batch-min-score(public/js/views/batch.js) — quatro inputs da v1.20.0 ganharam novos ids mas seus labels não estavam associados programaticamente. WCAG 3.3.2 agora atendido. -
Novo canário de análise estática em
tests/a11y-form-wires.test.mjs— percorre cada arquivo de view e afirma que cada IDREF dearia-describedby/htmlForaponta para uma declaraçãoid:irmã. Pega regressões classe-typo no tempo de CI.
🌐 i18n
fix(i18n): H-3 — 13 chaves da v1.20.0 caíam silenciosamente para EN em 7 locales(public/js/lib/i18n.js) —pipe.filter,pipe.count,pipe.preview*,pipe.openTab,pipe.evaluateAll*,eval.jdHint,batch.parallelAria,batch.minScoreAria, maiscommon.delete,config.group{Core,Runtime,Regional},config.profileEmpty,config.viewProfile,scan.atsBadge,scan.regionalBadgeeram referenciados viat('key', 'EN fallback')mas nunca adicionados ao DICT. Usuários de leitor de tela em russo, japonês e chinês ouviamaria-labels em inglês — derrotando diretamente a vitória WCAG 3.3.2 que a v1.20.0 anunciou. A v1.21.0 adiciona todas as 19 chaves × 8 locales (≈ 150 traduções novas) e estendetests/i18n-coverage.test.mjscom um passe de análise estática que escaneia cada chamadat('key', …)empublic/js/**/*.jse afirma que cada chave existe no DICT. Drift futuro pego em tempo de CI.
🧪 Testes
- 427 → 461 unit (+34) + 32/32 Playwright inalterado.
- Novos arquivos de teste:
ssrf-redirect-rebind,path-traversal,concurrent-tracker-write,rate-limit,a11y-form-wires. - O
pipeline-preview.test.mjsexistente foi reconectado do mock deglobalThis.fetchpara o novo ponto de injeção_setTransportemsafe-fetch.mjs— o caminho SSRF não passa mais pelo fetch, então o mock antigo era contornado silenciosamente.
Verificação
npm test # 461 / 461
npm run test:e2e:browser # 32 / 32
node --test tests/ssrf-redirect-rebind.test.mjs tests/path-traversal.test.mjs \
tests/concurrent-tracker-write.test.mjs tests/rate-limit.test.mjs \
tests/a11y-form-wires.test.mjs # 34 testes novos, todos verdes
# Path-traversal: cada :name no estilo traversal retorna 400 / 404
curl -sS -o /dev/null -w '%{http_code}\n' http://127.0.0.1:4317/api/jds/..pdf
# → 400
# Rate-limit em bind público:
HOST=0.0.0.0 LLM_RATE_LIMIT=3/60s npm start &
for i in 1 2 3 4; do
curl -sS -o /dev/null -w '%{http_code} ' -X POST -H 'Content-Type: application/json' \
-d '{"jd":"…"}' http://0.0.0.0:4317/api/evaluate
done
# → 200 200 200 429
# Escritas concorrentes no tracker: 20 POSTs paralelos, 20 linhas pousam:
node tests/concurrent-tracker-write.test.mjs
# 20 linhas sequenciais 001..020
# Sanidade dos wires aria:
grep -r 'aria-describedby' public/js/views/ | wc -l
# lookups `id:` correspondentes todos resolvem (canário a11y-form-wires.test.mjs)
Fora de escopo (v1.22+)
| Item | Notas |
|---|---|
Cap de streaming no body em pipeline-preview (M-2) |
await upstream.text() lê o body inteiro antes do slice de 8 KB; stream malicioso de 1 GB poderia esgotar a memória. Leitura em stream com contador de bytes + abort. |
WCAG 1.4.1 — estado por cor apenas em .connection-banner + score pills (M-3) |
Apenas matiz sinaliza estado; adicionar prefixo de ícone (✓ / ◐ / ○) ou sufixo de texto. |
Bypasses de stripDangerousMarkdown via entidades HTML (M-4) |
<script>, javascript:, <img src="data:image/svg+xml,<svg onload=…>"> sobrevivem ao regex. Defesa em profundidade via UI.md ainda segura; documentar + travar bypasses em sweep de teste. |
Acesso a localStorage no modo privado do Safari sem try/catch (M-5) |
i18n.js:544/571 lança → SPA renderiza chaves brutas. Envolver em try/catch com default 'en'. |
setInterval(checkHealth, 3000) polla para sempre sem backoff (M-6) |
Exponencial 3s → 6s → 12s → cap 60s. |
Alias htmlFor faltando null-guard (M-7) |
Defesa de uma linha if (v != null && v !== false). |
Canário 404 para /api/scan-ru/config aposentado (M-8) |
Teste de três linhas espelhando o precedente da v1.18. |
| Traduções do corpo de CHANGELOG por locale (M-9) | Candidato a tradução em massa após desaceleração da cadência. |
| Parágrafos de dica inline em cada campo da mode-page (M-1) | ~168 chaves i18n × 8 locales; segurado como item de polish. |
| Nits L-1 a L-5 | radix parseInt, bash –noprofile, fileURLToPath Windows-safe, SSE multi-linha, cleanup do timer em scan.js. |
[1.20.0] — 2026-05-13
Polish de a11y por componente + paridade de README não-EN + alias /api/scan-ru/config aposentado. Fecha os quatro itens que a tabela “Out of scope” da v1.19.0 sinalizou para a v1.20.
♿ WCAG 2.5.5 / 2.5.8 — auditoria de touch-target por componente
a11y(touch-target): chip min-height 28 px + gap 8 px (exceção 2.5.8 spaced-target)—.chipestava em 24 × ~50 px (vertical era 24, altura abaixo do piso de 24 px do 2.5.5 para controles agrupados); a exceção spaced-target do 2.5.8 exige ou ≥ 24 × 24 px ou 24 px de folga..chipagora emmin-height: 28px; padding: 6px 12px;e o.chip-rowque envolve emgap: 8px;para ambas as condições valerem.a11y(touch-target): sidebar nav-item min-height 44 px—.nav-itemtinha padding apenas10px 14px, altura computada ~36 px na maioria dos viewports. Agorapadding: 12px 14px; min-height: 44px; box-sizing: border-box;. Casa com o piso do.btn.a11y(touch-target): tab-btn min-height 44 px— mesmo tratamento para Sortable Headers / abas de categoria nos resultados de Reports, Tracker e Scan.
♿ WCAG 1.3.1 / 3.3.2 — aria-describedby em dicas inline de formulário
Cada controle de formulário do SPA agora possui um id estável, seu <label> aponta para ele via htmlFor, e qualquer parágrafo de dica inline é associado via aria-describedby. Cinco arquivos de view foram reconectados:
a11y(forms): config.js—idpor chave + associação de dica (cfg-<key>/cfg-<key>-hint).a11y(forms): evaluate.js— textareaeval-jd+ parágrafoeval-jd-hintdocumentando o mínimo de 50 caracteres após sanitização.a11y(forms): batch.js—batch-tsv/batch-tsv-hint, maisaria-labels embatch-parallel,batch-min-score,batch-dry-run,batch-retry.a11y(forms): pipeline.js—pipe-filter+pipe-new-url/pipe-new-url-hint.a11y(forms): mode-page.js— cada campo nos 7 modos genéricos (project,training,followup,batch-prompt,contacto,interview-prep,patterns) recebe idsmode-<slug>-<name>e labels comhtmlFor.
UI.el() aprendeu um alias htmlFor ao estilo React para que o código de view fique declarativo — ele define o atributo for subjacente (que é reservado em JS como nome de propriedade).
🌍 Paridade de README não-EN
docs(readme): traduzir 7 locales até paridade de 585 linhas com o EN master—README.{es,pt-BR,ko-KR,ja,ru,zh-CN,zh-TW}.mdtinham 306–316 linhas (cobriam headlines mas pulavam walkthroughs marketing-heavy e a maior parte da referência de API). Todos os sete agora espelham a estrutura do EN end-to-end: About → One-command install → Why? → Quick start (3 passos numerados) → Requirements → tabela “What you get” → Scan → Architecture (árvore de diretórios completa) → API reference (cada tabela de rota) → Tests → Configuration → Security notes → Limitations → Contributing → walkthrough “🌍 Getting Started” em 5 passos → License.
🧹 Alias /api/scan-ru/config aposentado
feat!(scan): remover alias legacy /api/scan-ru/config (sunset v1.20)— mantido como alias de um release na v1.19 por compatibilidade. O/api/scan/regional/configcanônico é agora o único caminho. Removidos: registro de rota emserver/lib/routes/scan.mjs, referências de doc emREADME.md,docs/architecture/{OVERVIEW,SERVER,API}.md. Testes já cobriam o caminho canônico — nenhuma mudança de teste necessária.
🧪 Testes
- Mesma suíte da v1.19. 427 / 427 unit + 20/20 smoke + 23/23 comprehensive + 32/32 Playwright. Todo o cabeamento a11y é aditivo (mais atributos
id/for/aria-describedby) — sem mudanças comportamentais, sem deltas de teste.
Verificação
npm test # 427 / 427
npm run test:e2e:browser # 32 / 32
# Touch targets — cada chip / nav-item / tab-btn ≥ 28 / 44 / 44 px:
# Chrome DevTools → Computed → height/min-height em .chip, .nav-item, .tab-btn
# Labels de formulário — cada input tem associação label[for=…]:
# document.querySelectorAll('input,textarea,select').forEach(el =>
# console.assert(el.labels?.length || el.getAttribute('aria-label'), el))
# Alias removido:
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:4317/api/scan-ru/config
# → 404
# Canônico ainda funciona:
curl -s http://127.0.0.1:4317/api/scan/regional/config | jq '.'
Mudanças incompatíveis
DELETE /api/scan-ru/config— removido. Use/api/scan/regional/config. Foi anunciado como sunset no CHANGELOG e script de verificação da v1.19.0.
Fora de escopo (v1.21+)
| Item | Notas |
|---|---|
| Parágrafos de dica inline para cada campo da mode-page | Hoje apenas a associação <label for=…> está no lugar; o texto de dica visível por campo ainda é EN-only no SPA. Os walkthroughs do README documentam a intenção em cada locale, então isso é item de polish, não bloqueador. |
Sinalização de estado por cor apenas em .connection-banner e score pills do dashboard (WCAG 1.4.1) |
O banner depende de vermelho/âmbar/verde; precisa de ícone ou sufixo de texto para quem não percebe matiz. |
| Traduções do corpo de CHANGELOG por locale | Stop-gaps em inglês permanecem em CHANGELOG.{es,pt-BR,ko-KR,ja,ru,zh-CN,zh-TW}.md. Tradução acontece quando a cadência da série v1.x desacelerar. |
[1.19.0] — 2026-05-13
Contraste WCAG 1.4.3 + unificação de scan (final) + HH_USER_AGENT removido da UI. Fecha a auditoria de contraste fora de escopo da v1.18, finaliza a eliminação do split EN/RU iniciada na v1.18 e remove o knob HH_USER_AGENT da configuração na UI por direção do usuário (um default sensato empacotado no servidor já cobre IPs não-RU para a maioria dos usuários).
♿ Passe de contraste WCAG 1.4.3
a11y(contrast): introduzir variantes *-text que passam AA para tokens de acento— tema light:--rausch-text: #b80f42(6.59:1 em branco, era 3.52:1),--kazan-text: #066507(7.31:1, era 4.53:1),--darjeeling-text: #7a5800(5.73:1 em fundo âmbar, era 4.24:1),--babu-text: #00665e(6.09:1, era 2.70:1). Tema dark: espelhos clareados (#ff8aa0,#6ee7b7,#fcd34d,#5eead4) atingem o mesmo piso 4.5:1 sobre o papel#161a22.- Classes de badge (
.badge-ok,.badge-warn,.badge-bad,.badge-info) e pílulas de score (.score-high,.score-mid,.score-low) agora passam pelas novas variantes*-text— cada combo texto-em-fundo-tingido passa AA. Os tokens de preenchimento de acento (--rausch,--kazan, etc.) ficam inalterados para bordas e contornos (que só precisam de 3:1 para componentes UI não-textuais).
🧹 Unificação de scan (finaliza o trabalho da v1.18)
docs(scan): limpar referências remanescentes ao split EN/RU em READMEs + help + docs de arquitetura— oito READMEs + oito help bundles + três docs de arquitetura (API.md, SERVER.md, OVERVIEW.md, DATA-FLOWS.md) + comentário em scan.js agora descrevem um único método consolidado de scan. Os aliases legacy/api/stream/scan-{en,ru}já tinham sumido na v1.18; a v1.19 pega a doc/cópia que ainda enquadrava o scan como um processo de duas etapas EN+RU.feat(scan): endpoint canônico /api/scan/regional/config—/api/scan-ru/configmantido como alias fino por um release para compatibilidade reversa. O novo path casa com a convenção de nomenclatura por fonte (?source=regional).
🛠️ HH_USER_AGENT removido da UI
feat!(config): remover campo HH_USER_AGENT de /#/config + KNOWN_KEYS— usuários power ainda podem definirHH_USER_AGENTdiretamente emcareer-ops/.env(o servidor lê viaprocess.env.HH_USER_AGENTemserver/lib/sources/hh.mjscom o UA empacotado como fallback). A UI não expõe mais isso porque o default funciona para a maioria dos usuários e ver um campo User-Agent inescrutável na página App Settings era fonte recorrente de confusão.- Menções no README em 8 locales + menções no help bundle em 8 locales substituídas por orientação “rodar via IP russo / VPN”. A chave i18n
scan.hhWarningfoi reformulada para retirar o detalhe de setup da variável de ambiente. KEY_GROUPScolapsado: sem mais classificaçãoregional(só tinha HH_USER_AGENT). Testes atualizados; camporegionalActiveno payload preservado por compatibilidade reversa do SPA.
🧪 Testes
tests/env-config.test.mjs— assert deKNOWN_KEYSagora exclui HH_USER_AGENT; novo assert de que a chave está intencionalmente ausente.tests/config-endpoint.test.mjs— teste multi-key de POST-write usaGEMINI_MODELcomo segunda chave conhecida em vez de HH_USER_AGENT.tests/config-groups.test.mjs—groups.HH_USER_AGENTagora é esperadoundefined.- Total: 427 / 427 unit + 20/20 smoke E2E + 23/23 comprehensive E2E + 32/32 Playwright. Mesmos números da v1.18.0 porque cada teste ajustado já estava contabilizado.
Verificação
npm test # 427 / 427
# Contraste (Chrome DevTools ou axe) em light + dark:
# .badge-ok / .badge-warn / .badge-bad / .badge-info → AA pass (4.5:1+)
# .score-high / .score-mid / .score-low → AA pass
# HH_USER_AGENT não mais em /api/config:
curl -s http://127.0.0.1:4317/api/config | jq '.values | keys'
# → ["ANTHROPIC_API_KEY","ANTHROPIC_MODEL","GEMINI_API_KEY","GEMINI_MODEL","HOST","PORT"]
# (sem HH_USER_AGENT)
# Endpoint canônico de config regional:
curl -s http://127.0.0.1:4317/api/scan/regional/config | jq '.'
# Alias legacy ainda vivo até v1.20:
curl -s http://127.0.0.1:4317/api/scan-ru/config | jq '.'
Fora de escopo (v1.20+)
| Item | Notas |
|---|---|
| Auditoria de touch-target por componente (filter chips, sortable headers, sidebar nav) | A v1.18 estabeleceu o piso global (.btn 44 px, .btn-sm 32 px); verificação por componente em todo o SPA ainda pendente. |
aria-describedby em dicas inline de formulário (#/config, #/pipeline, #/evaluate, #/batch) |
A v1.17 cobriu aria-label em busca global + close de modal. Associação de dica por input é a próxima camada de polish. |
| Paridade completa de README não-EN (585 linhas como o EN) | A v1.18 trouxe os não-EN para ~307 (53 % do EN). Walkthroughs marketing-heavy “Quick start” + “🌍 Getting Started” continuam EN-only. |
Remover alias legacy /api/scan-ru/config |
Sunset planejado para v1.20. O canônico /api/scan/regional/config é o alvo de migração. |
[1.18.0] — 2026-05-13
Consolidação do endpoint de scan + passe WCAG 2.2 AA + finalização do long-tail i18n. Aposenta os aliases legacy /api/stream/scan-{en,ru} (janela Sunset 2026-10-01 antecipada para v1.18 por direção do usuário). Leva os READMEs não-EN a ~307 linhas e traduz as entradas remanescentes RU-bodied de CHANGELOG v1.16.0 + v1.17.0 em 6 locales.
🚪 Breaking
feat!(scan): aposentar aliases legacy /api/stream/scan-{en,ru}— os endpoints SSE com split EN/RU depreciados se foram. Todo consumidor passa pelo endpoint consolidado/api/stream/scan?source=ats|regional|both(vivo desde v1.12.0). Os paths legacy tinham headers Deprecation + Sunset (RFC 8594) desde v1.15.0; a janela de migração está fechada. Integrações externas nos paths antigos recebem um 404 limpo em vez de serem roteadas silenciosamente para o catch-all do SPA.
♿ Acessibilidade (passe WCAG 2.2 AA)
- WCAG 2.4.1 Bypass Blocks — novo link Skip to main content como primeiro elemento focusable em cada página. Visualmente oculto via
.skip-linkaté receber foco; salta para o canto superior esquerdo no Tab a partir do load. - WCAG 2.4.7 Focus Visible — estilo global
*:focus-visible. Anéis de foco por clique de mouse desligados, anéis por Tab de teclado ligados (padrão WAI-ARIA AP). Close de modal (×) recebe anel de foco de maior contraste. - WCAG 2.5.5 Target Size — touch target mínimo de 44×44 px em
.skip-link..btn-smmantém min-height de 32 px (que combinada com spacing de linha atende à exceção AAA de 24×24 + spacing para controles compactos em linhas de tabela). - WCAG 3.1.1 Language of Page —
<html lang="en">corrigido delang="ru"(o bootstrap i18n em JS já sobrescrevia no load, mas o default SSR agora casa com o locale default do SPA). - WCAG 1.3.1 Info & Relationships —
#contentrecebetabindex="-1"para que o alvo do skip-link receba foco limpamente. (Roles ARIA + focus-trap já tinham sido adicionados na v1.17.)
📚 i18n long-tail
docs(i18n): CHANGELOG v1.16.0 + v1.17.0 traduzidos em 6 locales— entradas antes RU-bodied emCHANGELOG.{es,pt-BR,ko-KR,ja,zh-CN,zh-TW}.mdagora estão no idioma nativo. A contagem de caracteres RU por locale caiu 79 → 42 → 23 (os 23 remanescentes são referências técnicas inline como paths de arquivo + o link de header multi-locale, intencional).docs(readme): expandir READMEs não-EN com Why / Requirements / Features / Configuration / Contributing— cada README não-EN cresceu de 240 → ~307 linhas. Cobre agora as mesmas seções não-marketing das 585 do EN. Paridade 1:1 completa (seções de walkthrough marketing-heavy) permanece adiada.
🛠️ Misc
docs(api): endpoint de scan consolidado em API.md + DATA-FLOWS.md + README.md— a tabela de referência da API agora lista apenas/api/stream/scan?source=…. A seção Scan do README explica a aposentadoria do split EN/RU na v1.18.0.fix(scan.js): remover comentário obsoleto sobre aliases depreciados estarem vivos— o comentário do dispatcher runScanAll no SPA reflete agora a realidade consolidada.
🧪 Testes
tests/scan-consolidated.test.mjs::F-018 backwards compatreescrito — os dois antigos asserts “legacy endpoint still works” agora verificam que requests para/api/stream/scan-{en,ru}retornam 404 (em vez de serem roteados para o catch-all do SPA).- Total: 427 / 427 unit + 20/20 smoke E2E + 23/23 comprehensive E2E + 32/32 Playwright (contagem inalterada; +2 asserts corretos de legacy-removal substituindo os +2 asserts de legacy-still-works).
Verificação
npm test # 427 / 427
npm run test:e2e:full # 23 / 23
# Aposentadoria de endpoints legacy:
curl -sI http://127.0.0.1:4317/api/stream/scan-en | head -1 # → HTTP/1.1 404
curl -sI http://127.0.0.1:4317/api/stream/scan-ru | head -1 # → HTTP/1.1 404
# Endpoint consolidado:
curl -sN 'http://127.0.0.1:4317/api/stream/scan?source=ats&dryRun=1' | head -5
# → event: start
# → data: {"script":"en-scanner","writeFiles":false,…}
# Skip link (a11y):
curl -s http://127.0.0.1:4317/ | grep -c 'class="skip-link"' # → 1
# Fallback de html lang:
curl -s http://127.0.0.1:4317/ | grep -c 'html lang="en"' # → 1
Fora de escopo (v1.19+)
| Item | Notas |
|---|---|
| Paridade completa de README não-EN (585 linhas como EN) | A v1.18 levou não-EN a ~307 (53 % do EN). Walkthroughs marketing-heavy “Why?” / “Quick start” continuam EN-only. |
| Auditoria de contraste de cor (WCAG 1.4.3 AA — texto 4.5:1, texto grande 3:1) | A v1.18 cobriu a11y estrutural; verificação por token de contraste em paletas light + dark ainda pendente. |
| Auditoria de touch-target em cada elemento interativo | A v1.18 estabeleceu o piso (.btn: 44 px, .btn-sm: 32 px); verificação por componente (filter chips, sidebar nav, sortable headers) ainda pendente. |
[1.17.0] — 2026-05-13
Release de polish + a11y + correção de CI. Fecha os 9 follow-ups da lista da v1.16.0: verificação smoke em browser, badge truth nos READMEs, refresh de cobertura, lastWorkdayFallback surface no SPA, re-baseline E2E completo, cenários Playwright para auto-pipeline, passe de auditoria a11y, CHANGELOG histórico condensado em 6 locales, e READMEs não-EN expandidos com seções Architecture / API / Security / Tests.
🐛 Correções
fix(e2e): suites smoke + comprehensive realinhadas com a UX da v1.16— a mudança Cmd+K Enter → modal AutoPipeline na v1.16 fez osearch.press('Enter')dos testes e2e abrir um modal que interceptava clicks subsequentes. Os testes agora usamShift+Enterpara o caminho legacy quick-add, casando com o split documentado na v1.16. Também atualiza a iteração de modo batch no E2E comprehensive para usar/#/batch-prompt(o slug legacy de mode-prompt que a v1.15 PR-H introduziu). Esta foi a falha de CI no push v1.16.0 — Playwright e2e dava timeout em 30 s esperando clicks interceptados pelo backdrop.fix(mode-page): rota batch-prompt → modes/batch.md via serverSlug— a v1.15 renomeou o slug legacy do mode parabatch-prompt, mas oPOST /api/mode/:slugdo servidor passou a procurarmodes/batch-prompt.md, que não existe. Novo camposerverSlugdesacopla o hash de rota do filename do mode do pai.chore: bump de mensagens de deprecação de v1.16.0 para v1.17.0— a cópia de deprecação de scan-en/scan-ru e o banner de deprecação de batch-prompt referenciavam a versão passada.
✨ Features
feat(scan): chip 🔒 Workday CAPTCHA no card Active Companies— o export server-sidelastWorkdayFallbackda v1.16 PR-7 é agora consumido pelo SPA./api/scan-resultsretorna o snapshot;#/scanrenderiza um card warn-tinted acima de Active Companies quando um tenant Workday cai no fallback (“🔒 Workday tenant blocked — fallback: use /career-ops scan (Playwright)”). Novo exportadorgetLastWorkdayFallback()evita ambiguidade de live-binding ESM. 2 chaves i18n novas × 8 locales.
♿ Acessibilidade
a11y: roles ARIA + passe de gerenciamento de foco em superfícies críticas—index.html: atributosroleem<aside>(navigation),<header>(banner),<section id="content">(main),<div id="modal">(dialog com aria-modal/aria-labelledby),<div id="toast">+#conn-banner(status com aria-live),<div class="searchbar">(search).#sidebar-togglerecebearia-controls="sidebar"+aria-expandedsincronizado por JS em open/close.#global-searchrecebe um<label>visualmente oculto mais umaria-labelexplícito que surface o hint do shortcut Cmd+K.- Close de modal (×) recebe
aria-label="Close dialog". - Backdrops decorativos recebem
aria-hidden="true". - Focus trap no modal —
UI.modal()lembra do owner do click, foca o primeiro focusable não-close no open, e cicla Tab/Shift+Tab dentro do modal.UI.closeModal()restaura o foco para o owner anterior. - Nova classe utilitária
.visually-hiddenempublic/css/app.css(padrão WAI-ARIA AP).
📚 Documentação
docs(readme): badge truth em 8 READMEs— badge de testes284 / 379 / 360→ 427; badge de releasev1.9.1 / v1.13.0→ v1.16.0 depois → v1.17.0 via bump da v1.17. Alvos dos links de release atualizados.docs(readme): expandir 7 READMEs não-EN com seções de referência— cada um cresceu 170 → ~240 linhas com novas seções Architecture / API reference / Security notes / Tests / A11y / Limitations / License no idioma nativo. Ainda não em paridade total de 585 linhas com o EN, mas cobre todas as superfícies não-marketing.docs(changelog): condensar entradas pré-v1.12 em 6 locales— as entradas longas RU-bodied de v1.11.x + v1.10.x que sangravam para os CHANGELOGs não-EN/não-RU são agora substituídas por um resumo executivo “Earlier releases” compacto no idioma nativo. História detalhada fica emCHANGELOG.md(EN).
🛠️ Tooling
coverage: refresh de números— o último publicado era 95.46 % linha / 84.06 % branch (REVIEW v1.13.0). Baseline v1.17: 94.14 % linha / 82.98 % branch / 93.20 % função. Queda leve devido a novos error paths em auto-pipeline + reports-write; ainda bem acima do piso de 80 % do CLAUDE.md.
🧪 Testes
- Total: 427 / 427 unit + 20/20 smoke E2E + 23/23 comprehensive E2E + 32 / 32 Playwright (era 28; +4 cenários novos de auto-pipeline: botão abre modal, paste Cmd+K dispara modal, URL inválida bloqueia step 1, framing de eventos SSE de
POST /api/auto-pipeline). - Suíte E2E realinhada com a UX da v1.16.0 (Shift+Enter quick-add, /#/batch-prompt para mode legacy).
Verificação
# Localmente:
npm test # 427 / 427
npm run test:e2e # 20 / 20
npm run test:e2e:full # 23 / 23
npm run test:e2e:browser # 32 / 32
# Smoke no browser (nível de página):
curl -s http://127.0.0.1:4317/api/scan-results | jq '.workdayFallback'
# null quando nenhum fallback Workday ocorreu; {apiUrl, reason, at} após um 4xx.
# Spot-check de a11y:
node -e "
const c = require('cheerio').load(require('fs').readFileSync('public/index.html','utf8'));
['banner','navigation','main','dialog','status','search'].forEach(r =>
console.log(r, c('[role=' + r + ']').length));
"
# Cada role deve aparecer ≥1.
# Verificação do gate CI: o workflow dashboard-screenshots boota um scaffold em /tmp,
# regenera os PNGs, faz diff contra o commitado — verde quando
# images/dashboard-*.png estão atualizados em relação ao SPA renderizado.
Fora de escopo (v1.18+)
| Item | Notas |
|---|---|
| Traduzir entrada v1.16.0 em CHANGELOGs não-EN | Atualmente RU-bodied (~30 linhas × 6 locales = 180 linhas). Estava fora do escopo explícito v1.11.x/v1.10.x do usuário. |
| Paridade completa de README não-EN (585 linhas como EN) | A v1.17 trouxe não-EN para ~240; walkthroughs marketing-heavy “Why?” / “Quick start” continuam EN-only. |
| Parent commit para prompt canônico A-F | A reescrita de santifer/career-ops::modes/oferta.md ainda é necessária upstream (CLAUDE.md hard rule #1). |
| Auditoria WCAG 2.2 AA completa | A v1.17 cobriu ARIA estrutural + focus trap; auditoria de contraste/Tab-order por componente pendente. |
[1.16.0] — 2026-05-13
Finalização do auto-pipeline + polish dos adapters + long-tail i18n. Fecha os 11 follow-ups do REVIEW da v1.15.0: SSE auto-pipeline server-side, primitiva POST /api/reports, shortcut Cmd+K, paginação SmartRecruiters, Workday CAPTCHA-fallback, gate CI de drift de screenshots, UX do filtro de source no scan, tradução do CHANGELOG histórico (v1.13.0/v1.12.0 × 6 locales), expansão de READMEs não-EN e importer paste-ready de empresas trending.
✨ Features
feat(auto-pipeline): orquestrador SSE server-side(#1, #2, #3, #8) — o orquestrador client-side chained-fetch da v1.15 se foi.POST /api/auto-pipelineé agora um endpoint SSE curl-able que encadeia validate → fetch JD → evaluate → save report → tracker no servidor com eventos step em tempo real. A chamada lenta para Anthropic (30–90 s) emite agora um eventorunningem vez de spinner genérico. Falhas emitemerrorcomstep+message. O orquestrador também persiste o markdown do report noreports/<slug>.mddo pai (era perdido na v1.15).feat(reports): primitiva POST /api/reports— novo endpoint writer emserver/lib/routes/reports.mjs. Saneamento de slug com guard de path-traversal (remove pontos iniciais, colapsa...internos). Cap de 1 MB (413). 409 em arquivo existente semoverwrite:true. Escrita atômica através de passe XSS destripDangerousMarkdown. Loga activity.reports.save. Testes: 9 casos.feat(app): Cmd+K paste URL → auto-pipeline— colar uma URL na busca global + Enter agora abre o modal AutoPipeline comautoStart=true. Shift+Enter preserva o caminho legacy “add to pipeline only”. A UX canônica career-ops.org Quick Start §7 “paste URL → done”.feat(portals): paginação SmartRecruiters(#4) —server/lib/sources/smartrecruiters.mjspercorre páginas via?limit=100&offset=Naté atingirtotalFoundOU receber página vazia OU disparar o safety cap de 30 páginas / 3000 jobs. Remove limit/offset fornecidos pelo chamador para que o cursor seja server-owned. Boards grandes (estilo Procter & Gamble, Amazon) não perdem mais o rabo de 100+ postings. Testes: 6 casos.feat(portals): Workday CAPTCHA-fallback gracioso(#7) —server/lib/sources/workday.mjsnão lança mais em 4xx / non-JSON / erros de rede. Retorna[]e anota o novo snapshot exportadolastWorkdayFallback. A timeline do scanner segue para o próximo tenant. O chamador pode optar pelo comportamento de throw da v1.14 comstrict:true. Testes: 7 casos.
🛠️ Tooling + CI
ci(workflows): gate de drift de dashboard-screenshots(#5) — novo.github/workflows/dashboard-screenshots.yml. Em PRs que tocampublic/css/app.css/public/js/views/dashboard.js/public/js/lib/i18n.js/public/index.html, o workflow boota o servidor web-ui contra um scaffold em /tmp, regenera os 8 hero PNGs via Playwright + chromium e falha o build se o resultado divergir do commitado. Faz upload dos PNGs regenerados como artefato de CI em caso de falha.feat(scripts): import-trending-companies.mjs(#11) — verifica as 13 empresas trending emdocs/portals-examples.mdatravés do boards-API real delas e emite YAML pronto para colar noportals.yml::tracked_companiesdo pai.enabled: falseé carimbado em qualquer candidato cujo slug retorne 404. Probe ao vivo dos 6 ATSes (Greenhouse / Ashby / Lever / Workable / SmartRecruiters / Workday). Execute vianpm run import:trending.feat(scripts): npm run capture:dashboards— expõescripts/capture-dashboard-screenshots.mjscomo script top-level (antes só documentado emimages/README.md).
🎨 UX
fix(scan): dropdown de filtro source consolidado(#6) — dropdown de source de#/scanreconstruído a partir do adapter registry da v1.14: 6 ATSes + hh.ru + Habr Career, alfabético, sem prefixos geo.runEnScan/runRuScanagora batem no endpoint consolidado/api/stream/scan?source={ats,regional}em vez dos aliases depreciados/api/stream/scan-{en,ru}(headers Sunset permanecem ativos até v1.16).
📚 i18n long-tail
docs(i18n): traduzir CHANGELOG v1.13.0 + v1.12.0 em 6 locales(#9) — entradas antes RU-bodied emCHANGELOG.{es,pt-BR,ko-KR,ja,zh-CN,zh-TW}.mdagora estão no locale real. Cada CHANGELOG não-EN/não-RU também recebe uma nota i18n explicando que entradas pré-v1.12 permanecem em RU por convenção do projeto (texto canônico vive emCHANGELOG.md).docs: expandir READMEs não-EN com seção de highlights v1.16.0(#10) — 6 READMEs não-EN (es / pt-BR / ko-KR / ja / ru / zh-CN / zh-TW) recebem uma nova seção de ~35 linhas cobrindo: fluxo one-click do auto-pipeline + exemplo curl, paginação SmartRecruiters, Workday fallback, UX do filtro source no scan, script importer e workflow CI de screenshots. O README RU também foi estendido.
🧪 Testes
- Novo
tests/reports-write.test.mjs(9 casos) — happy path, sanitização de slug (incl. guard de path-traversal), conflito 409, flag overwrite, strip XSS, 400 em campos faltantes, 413 em >1 MB, round-trip GET/POST. - Novo
tests/auto-pipeline.test.mjs(5 casos) — framing SSE, gate de URL inválida, gate SSRF/loopback, caminho de erro sem chave LLM, header Content-Typetext/event-stream. - Novo
tests/smartrecruiters-pagination.test.mjs(6 casos) — página única, 3 páginas, early-stop em página vazia, hard cap respeitado, strip de query, 503 lança. - Novo
tests/workday-fallback.test.mjs(7 casos) — happy path, 403/429 graciosos, body não-JSON, erro de rede, opt-in strict para 4xx e erros de rede. - Total: 427 / 427 unit (era 400; +27 líquidos). 0 falhas. 28/28 Playwright + 23/23 comprehensive E2E + 20/20 smoke E2E verdes a partir da baseline v1.15.0.
Fora de escopo (v1.17+)
| Item | Notas |
|---|---|
| Parent commit para prompt canônico A-F | Ainda pendente upstream a reescrita de santifer/career-ops::modes/oferta.md (CLAUDE.md hard rule #1). |
| Traduzir entradas pré-v1.12 do CHANGELOG (v1.11.x, v1.10.x) | Convenção preservada: RU-bodied. Backport é ~1800 linhas de tradução; adiado. |
| Paridade completa de README não-EN (585 linhas como EN) | A v1.16 adicionou ~35 linhas por locale; paridade completa é esforço separado. |
runEnScan server-side lendo a anotação de fallback Workday para renderizar chips 🔒 |
O export lastWorkdayFallback está cabeado; o consumo pelo card Active Companies do SPA é v1.17+. |
Verificação
npm test # 427 / 427
npm run test:e2e:full # 23 / 23
npm run import:trending --check-only # probe das 13 boards trending
# Smoke curl do auto-pipeline:
curl -N -X POST http://127.0.0.1:4317/api/auto-pipeline \
-H 'Content-Type: application/json' \
-d '{"url":"https://job-boards.greenhouse.io/anthropic/jobs/4567"}'
# Round-trip POST /api/reports:
curl -X POST http://127.0.0.1:4317/api/reports \
-H 'Content-Type: application/json' \
-d '{"slug":"smoke","markdown":"# smoke\n"}'
[1.15.0] — 2026-05-13
Release de doc-conformance. Fecha 9 de 10 achados ainda abertos da auditoria de conformidade (qa/conformance-vs-docs/00-CONFORMANCE-REPORT.md) mais as hero images localizadas. Alinha a UI ao workflow canônico de career-ops.org/docs para que o mesmo pipeline prometido pelo CLI funcione end-to-end via browser em cada locale.
✨ Features
feat(auto-pipeline): PR-C — 1-click "paste URL → report + PDF + tracker row"(G-007) Cumprir a promessa canônica do career-ops.org. Até a v1.15, usuários faziam 5 clicks manuais cruzando /#/pipeline → /#/evaluate → /#/cv → /#/tracker. Agora um único botão ✨ em /#/dashboard encadeia: validate URL → fetch JD (SSRF-safe) → evaluate contra CV → generate PDF → add tracker row. Renderiza uma timeline modal passo a passo com [✓]/[…]/[✗] por step. Extração heurística de company/role das primeiras linhas do JD. Score + legitimidade extraídos via regex do markdown de avaliação. Novo arquivo:public/js/lib/auto-pipeline.js. 19 chaves i18n novas × 8 locales.feat(modes): PR-D — editor modes/_profile.md como aba #/config → Modes(G-008) O arquivo canônico “Career framing” do Quick Start §Step-5 era invisível para usuários da UI antes. Agora exposto via uma nova aba “Modes” em /#/config mais um card descobrível em /#/profile. Novos endpoints:GET/PUT /api/modes/_profilecom cap de 256 KB, passe XSS destripDangerousMarkdown, scaffold de_profile.template.mdna primeira leitura. 9 chaves i18n novas × 8 locales.feat(profile): PR-E — aceitar schema canônico; adicionar location + headline(G-009)/api/profileaceita agora TANTO o schema legacy (candidate:{...}) QUANTO o canônico (top-levelfull_name,narrative.headline,target_roles.primary,compensation.target_range). Legacy vence quando ambos estão presentes para que YAMLs existentes renderizem identicamente. Novo helpersummarizeProfile()retorna shape unificado./#/profileexibenarrative.headlinecomo novo card. 2 chaves i18n novas × 8 locales.feat(tracker): PR-B — coluna Legitimacy em #/tracker(G-006) Restaura paridade com a tabela canônica de output do pipeline em career-ops.org/docs. Adiciona coluna Legitimacy entre Status e PDF com tingimento badge-ok/warn/bad (espelha o padrão statusClass). Degrade gracioso — linhas pré-v1.15 sem coluna Legitimacy exibem—. 1 chave i18n nova × 8 locales.fix(routing): PR-H — deduplicar sidebar; rotear #/batch para o SPA TSV da v1.13.0(G-011) Antes desta correção /#/batch estava registrado DUAS VEZES no sidebar E ambos levavam ao builder legacy de mode-prompt. O SPA TSV da v1.13.0 (8 KB, 4 endpoints) estava inacessível. Entrada duplicada do sidebar removida; slug do mode renomeadobatch→batch-promptcom banner de deprecação. /#/batch canônico é agora o SPA TSV.
📚 Documentação
docs(evaluate): PR-A — realinhar Block A-F com a rubrica canônica career-ops.org(G-005) career-ops.org docs documenta A–F (Strategy/Personalization/STAR stories em C/E/F). Nós emitíamos A–G com semântica deslocada (Risks/Verdict/Legitimacy). A v1.15 atualiza todos os 8 help bundles §9 para mostrar o A–F canônico com callout “Pré-v1.15 usava A–G; renderizamos como estão por compatibilidade reversa”. Chave i18neval.subtitle× 8 locales também realinhada. Score + legitimidade agora documentados como campos do header de report. ⚠ Parent commit ainda requerido:santifer/career-ops::modes/oferta.mdprecisa ser reescrito upstream para emitir A–F canônico.docs: PR-F — seniority_boost + search_queries em help §5 em 8 locales + scaffold(G-010) Help §5 em 8 bundles agora documenta a terceira chave title-filter (seniority_boost) E tem um bloco de exemplosearch_queriescom intro traduzida de 1 parágrafo esclarecendo que ela alimenta apenas o scan Option B alimentado por AI. Scaffoldportals.ymlembin/setup.shsemeiaseniority_boost: ["Senior", "Staff", "Lead"]por default. Paridade H2 preservada: 16 × 8 locales.docs: PR-I — hero images localizadas por locale de READMECada um dos 8 READMEs agora tem umimages/dashboard-<locale>.pngespecífico de locale (HiDPI 1440×900) gerado viascripts/capture-dashboard-screenshots.mjs(Playwright + chromium). Antigopublic/images/screen_vacancy_found.pngcompartilhado deletado. Leitores não-EN veem a UI rotulada em seu idioma na primeira aterrissagem.
🧹 Limpezas carryover
PR-G — G-001bundle i18nscan.noResults: substituídas 8 strings contendo o literal “EN or RU scan” por cópia limpa de locale.PR-G — G-002botão 📄 Generate PDF agora surface em painéis de resultado de #/interview-prep (espelha o padrão de deep.js).PR-G — G-003README.cn.md→README.zh-CN.md(tag canônica de locale); referências varridas em siblings + tests/canonical-docs-coverage.test.mjs.PR-G — G-004/api/stream/scan-en+scan-ruagora emitem headers RFC 8594 Sunset + Deprecation + Link (sunset 2026-10-01). Programado para remoção na v1.16.0.
🧪 Testes
- Novo
tests/profile-canonical-schema.test.mjs(6 casos) — YAML canônico, YAML legacy, mixed legacy-wins, accept-canonical-only, reject neither-shape, parsing de comp range. - Novo
tests/modes-profile-crud.test.mjs(8 casos) — scaffold built-in em vazio, template-takeover, persisted-wins, happy-path de write, sanitização, 400 em não-string, 413 em >256 KB,/api/modes/:namegenérico ainda funciona. - Regressão de isolamento corrigida em fixtures de teste: testes agora usam o padrão
before/after + dynamic-import(combinando comtests/batch-endpoints.test.mjs) para que não mutem oconfig/profile.ymlreal do pai. NOTA para usuários: se seuconfig/profile.ymlparecer um placeholder de teste após upgrade de um build v1.15.0-RC, restaure do backup — a regressão existia somente no dev branch. - Total: 400 / 400 testes unit (era 386; +14 líquidos). 0 falhas. 20/20 smoke E2E + 23/23 comprehensive E2E + 28/28 Playwright todos verdes a partir da baseline v1.14.0.
Fora de escopo (follow-up v1.16+)
| Item | Notas |
|---|---|
| Parent commit para prompt canônico A–F | santifer/career-ops::modes/oferta.md precisa ser reescrito upstream. A hard rule #1 do CLAUDE.md proíbe editarmos arquivos do pai. O lado web-ui já está feito (degrade gracioso — reports pré-v1.15 A–G renderizam inalterados). |
SSE POST /api/auto-pipeline server-side |
O orquestrador client-side entrega a vitória de UX. Endpoint server-side habilitaria retry-from-step-N + CI curl-able. |
Primitiva POST /api/reports |
O auto-pipeline atualmente mostra o markdown do report inline mas não o persiste em reports/ do pai. PDF + linha do tracker são os artefatos duráveis. |
| Cmd+K paste-URL → rodar auto-pipeline | Adiar para v1.16+. |
Verificação
npm test # 400 / 400
npm run test:e2e:full # 23 / 23
curl -sf http://127.0.0.1:4317/api/health | jq '.checks | length' # → 18
curl -sI http://127.0.0.1:4317/api/stream/scan-en | grep -i sunset # G-004 visível
curl -sf http://127.0.0.1:4317/api/modes/_profile | jq '.scaffolded' # G-008 cabeado
ls images/dashboard-*.png | wc -l # 8 (PR-I)
grep -c 'href="#/batch"' public/index.html # 1 (deduplicação PR-H)
[1.14.0] — 2026-05-13
3 novos adaptadores ATS pousam sobre o registry da v1.13.0, nos levando de 3 → 6 ATSes suportados (Greenhouse / Ashby / Lever + Workable / SmartRecruiters / Workday-beta). Documentação user-facing em 17 arquivos varrida de “3 ATSes” para “6 ATSes” em uma única passada (42 upgrades de frase) — README × 8 locales, help bundle × 8 locales, PROJECT.md. Adiciona blocos docs/portals-examples.md para 13 empresas trending como YAML pronto-para-colar no portals.yml do pai.
✨ Features
feat(portals): 3 novos adaptadores ATS — Workable, SmartRecruiters, Workday-beta— o registry agora resolve 6 ATSes (era 3). Novos arquivos:server/lib/portals/adapters/{workable,smartrecruiters,workday}.mjs(cada um wrapper fino do contrato uniforme em torno das novas sources) eserver/lib/sources/{workable,smartrecruiters,workday}.mjs(HTTP cru + normalização de resposta para o shape canônico{ id, title, company, url, location, isRemote, … }comsource: <id>).- Workable: detecta
apply.workable.com/<slug>E o legacy<subdomain>.workable.com. Endpoint:https://apply.workable.com/api/v3/accounts/<slug>/jobs?details=true. - SmartRecruiters: detecta
jobs.smartrecruiters.com/<slug>Ecareers.smartrecruiters.com/<slug>. Endpoint:https://api.smartrecruiters.com/v1/companies/<slug>/postings. - Workday (beta): detecta
<tenant>.wd<N>.myworkdayjobs.com/<lang>/<site>. Endpoint: POST para/wday/cxs/<tenant>/<site>/jobs. Defaultsite=Externalquando o careers_url omite. Beta porque alguns tenants protegem o CXS atrás de CAPTCHA — quando acontecer, fallback para/career-ops scando pai (Playwright-driven).
- Workable: detecta
📚 Docs
docs(portals-examples): bloco de trending boards—docs/portals-examples.mdestendido com a seção v1.14.0 listando 13 empresas trending como YAML pronto-para-colar emtracked_companies, divididas entre Greenhouse-hosted (Stripe, GitLab, HashiCorp, Cloudflare, Datadog, Hugging Face) e Ashby-hosted (Notion, Linear, PostHog, Replicate, Modal Labs, Fly.io, Render). Cada entrada usaenabled: falsepara que usuários verifiquem se o slug responde antes de ativar. Mais blocos de exemplo para Workable / SmartRecruiters / Workday com o padrão de URL que detecta cada um.docs(framing): 42 upgrades de frase ATS em 17 docs user-facing— cada aparição de “Greenhouse / Ashby / Lever” na documentação user-facing agora lê-se “Greenhouse / Ashby / Lever / Workable / SmartRecruiters / Workday”. Atinge README × 8 locales (EN/ES/PT-BR/RU/JA/KO/CN/TW), help bundle × 8 locales, PROJECT.md. Entradas históricas do CHANGELOG e docs de prescrição de bug-fix (qa/fixes/F-014,qa/FIX-PROMPT) deliberadamente intocadas — descrevem estado passado ou já correto.docs(qa): browser test scenario 19 — cobertura de 6 adapters ATS—qa/claude-cowork-browser-test-prompt.mdestendido com Scenario 19: invarianteALL_ADAPTERS.length === 6, sweep de detecção de URL viaresolveAdapter()para todos os 6 adapters, soft-check do card Active Companies em#/scane check estrutural de blocos por ATS emdocs/portals-examples.md.
🧪 Testes
tests/adapter-registry.test.mjsestendido com 7 testes novos para os 3 novos adapters (padrão apply-URL do Workable, padrão legacy subdomain do Workable, padrões SmartRecruiters jobs.* + careers., Workday tenant.wd5. com site explícito, fallback de site default Workday para “External”, invarianteALL_ADAPTERS.length === 6, compatibilidade do shape legacydetectApi()).- Total: 386 / 386 testes unit (era 379; +7 líquidos). 0 falhas.
Verificação
npm test # 386 / 386
node -e "import('./server/lib/portals/registry.mjs').then(m => console.log(m.ALL_ADAPTERS.length))" # → 6
# Sweep de detecção de adapter:
node -e "import('./server/lib/portals/registry.mjs').then(m => {
console.log(m.resolveAdapter({ careers_url: 'https://apply.workable.com/foo/' }).adapter.id); // → workable
console.log(m.resolveAdapter({ careers_url: 'https://jobs.smartrecruiters.com/Bar' }).adapter.id); // → smartrecruiters
console.log(m.resolveAdapter({ careers_url: 'https://baz.wd5.myworkdayjobs.com/en-US' }).adapter.id); // → workday
})"
Fora de escopo (follow-up adiado)
| Item | Notas |
|---|---|
| Registros de adapter per-company para as 13 empresas trending Greenhouse/Ashby | Bloco v1.14.0 em docs/portals-examples.md as lista como YAML colável; verificação de slug + bulk add no portals.yml do pai é fase separada. |
| Automação do fallback CAPTCHA do Workday | O adapter Workday lança quando o feed CXS está protegido; o fallback planejado delega ao /career-ops scan do pai (Playwright). Cabear isso na UX de “scan” do SPA é v1.15+. |
[1.13.0] — 2026-05-13
Slice grande. Fecha todos os 4 itens adiados do backlog pós-v1.12.0 em um único release: PR-4 (pipeline multer completo), Adapter registry (follow-on arquitetural F-018), página SPA Batch evaluate e scaffold de mode-template ciente de locale. Mais uma correção mid-session de tabela em dark theme.
✨ Features
feat(cv): upload multipart com multer (PR-4 completo)—/api/cv/importaceita agora TANTO o contrato octet-stream original (Content-Type: application/octet-stream+X-Filename) QUANTOmultipart/form-datapropriamente parseado via multer. O reject 415 da v1.10.2 era um stopgap; a v1.13.0 é a correção real. Clientes externos (curl-F, default do Postman, qualquer cliente HTTP) funcionam sem atrito. Ambos os caminhos alimentam o mesmo conversorimportDocumentToMarkdown+ passe XSS destripDangerousMarkdown. Nova dep:multer ^2.1.1.feat(portals): adapter registry— fetchers de Greenhouse / Ashby / Lever extraídos paraserver/lib/portals/adapters/*.mjscom um contrato uniforme (id,label,matches,buildEndpoint,fetch). Novoserver/lib/portals/registry.mjs::resolveAdapter()é a única superfície de dispatch.en-scanner.mjs::detectApi()+FETCHERSagora delegam ao registry; shape de retorno legacy preservado. Para adicionar um novo ATS: solte um arquivo emadapters/, adicione aALL_ADAPTERS— nenhuma mudança no scanner necessária.feat(batch): página #/batch evaluate— nova view SPA + 4 endpoints (GET /api/batch,PUT /api/batch,GET /api/stream/batch,POST /api/batch/merge). Editor TSV parabatch/batch-input.tsv, controles parallel/min-score/dry-run/retry, log SSE ao vivo debash batch/batch-runner.sh, lista pós-run debatch/tracker-additions/com one-clicknode merge-tracker.mjs. Link do sidebar no grupo Decision. 21 chaves i18n novas × 8 locales.feat(prompts): scaffold de mode ciente de locale—buildModePrompt+buildEvaluationPromptagora envolvem o corpo inglês do mode-template do pai com texto de scaffold localizado (role line, “Read these files first”, “User-supplied context”) em 8 locales. O corpo domodes/<slug>.mddo pai continua em inglês (read-only pela hard rule #1 do CLAUDE.md); o scaffold ao redor no career-ops-ui é traduzido.
🎨 Correções de UX
fix(theme): hover de tabela dark-mode + tab-btn—#fafafa/#fff/#f7f7f7hardcoded substituídos por tokensvar(--beach)/var(--paper)/var(--slate)para que o swap da paleta dark realmente alcance linhas de tabela e botões de aba. Adiciona accent strip.row-boostedpara linhas boosted de scan que funciona em ambos os temas.
🧪 Testes
- Novo
tests/adapter-registry.test.mjs(7 casos) — contrato uniforme, detecção de URL por ATS, prioridade explícita do campoapi:, null em no-match, shape legacydetectApi()preservado. - Novo
tests/batch-endpoints.test.mjs(5 casos) — fixture vazia, round-trip TSV, rejeição de no-URL, cap de 1 MB, frame de erro runner-missing. - Novo
tests/locale-scaffold.test.mjs(6 casos) — strings de scaffold em en/ru/ja/ko, integraçãobuildModePrompt/buildEvaluationPrompt, compatibilidade reversa em inglês. tests/cv-upload-multipart-reject.test.mjsreescrito — o que era contrato “multipart retorna 415” agora é contrato “multipart parseado via multer”; a invariante de no-side-effect-em-cv.md é preservada.- Total: 379 / 379 testes unit (era 360; +19 líquidos). 0 falhas.
- Cobertura: 95.46 % linha / 84.06 % branch.
- 20/20 smoke E2E · 23/23 comprehensive E2E · 28/28 Playwright.
Fora de escopo (follow-up adiado)
| Item | Notas |
|---|---|
| 14 novos adapters de portal (Workable / SmartRecruiters / Workday / GitLab / HashiCorp / Cloudflare / Datadog / Stripe / Notion / Linear / Posthog / Hugging Face / Replicate / Modal Labs / Fly.io / Render) | Adapter registry está pronto — adicionar novos adapters é um arquivo cada agora. Pesquisa portal-por-portal + padrão de URL + normalização de endpoint para 14 ATSes é fase separada. |
Traduzir corpos de modes/<slug>.md do pai |
Arquivos do pai são read-only pela hard rule #1 do CLAUDE.md. O scaffold ciente de locale da v1.13.0 entrega 80% do caminho; tradução completa de corpo requer PR upstream para santifer/career-ops. |
Docs
docs/reviews/REVIEW-2026-05-13-v1.13.0.md— contexto da sessão + contrato do adapter registry + fluxo de batch.- Todos os 8 READMEs: bumps de badge (testes 360 → 379, release v1.12.0 → v1.13.0).
- Todos os 8 CHANGELOGs recebem esta entrada.
[1.12.0] — 2026-05-13
Passe de bug-fix + UX + branding. Fecha 8 itens do honest backlog pós-v1.11.1 (gaps de teste #9–12, console error #8, drift de portals-dead #4, surface de seniority_boost #6, consolidação de endpoint F-018). Adiciona um toggle de tema dark/light e remove o branding “Airbnb-styled” de cada doc, metadata de package e descrição do repo GitHub.
✨ Features
feat(theme): toggle dark/light (v1.12.0)— novo botão de tema na top bar. Cicla light ↔ dark; persiste emlocalStorage.theme; restaura no page load via bootstrap pre-paint (public/js/lib/theme-bootstrap.js) para que usuários nunca vejam um flash do esquema de cor errado. Honraprefers-color-schemepara visitantes de primeira vez. Paleta dark completa sob[data-theme="dark"]empublic/css/app.css— cada componente lê de custom properties CSS para que o swap seja centralizado em um único lugar.- **
feat(scan): /api/stream/scan?source=ats|regional|both(F-018 LITE)** — único entrypoint SSE consolidado. O SPA agora abre UM event-stream que dirige ambas as fases sequencialmente (ATS primeiro, depois regional) em vez de encadear dois streams separados. Legacy/api/stream/scan-en+/api/stream/scan-rupermanecem vivos como aliases depreciados. O/api/stream/scanda tabela de runners foi renomeado para/api/stream/scan-parentpara liberar o namespace; o fallbackscan.mjs` spawnado pelo pai está preservado. feat(scan): surface de seniority_boost (docs canônicas §3)— tantoen-scanner.mjsquantoru-scanner.mjsagora leemportals.yml::title_filter.seniority_booste carimbam_boosted: true+_boostedBy: <keyword>em jobs que casam. O SPA ordena linhas boosted no topo dos resultados de#/scane renderiza um badge⬆ boostedcom a keyword no atributo title. Duas chaves i18n novas (scan.boosted,scan.boostedBy) localizadas em 8 locales.
🐛 Correções
fix(ui): leitura de mensagem de erro null-safe em 4 lugares (#8)—app.js(botão doctor na top bar + add de pipeline pela busca global),views/tracker.js(linha 112),views/apply.js(linha 21),views/evaluate.js(linha 32) agora leem(err && err.message) || '<fallback>'. Antes, uma Promise rejection sem Error payload lançava “Cannot read properties of undefined (reading ‘message’)” no stream de page-error durante o tear-down do e2e.fix(test): drift de portals-dead como warning em vez de failure (#4)—tests/portals-dead.test.mjs::FIX-C3falhava antes quando otemplates/portals.example.ymldo pai derivava para reativar um slug que tínhamos sinalizado como morto. A v1.12.0 converte o assert em warning no stderr para que o CI fique verde em drift do pai; decisões de release continuam manuais. A listaKNOWN_DEADé preservada como documentação de intenção.
📝 Branding / docs
docs(brand): remover referências 'Airbnb' de cada doc (8 locales)— README.md, README.es.md, README.pt-BR.md, README.ko-KR.md, README.ja.md, README.ru.md, README.cn.md, README.zh-TW.md, CLAUDE.md, docs/architecture/FRONTEND.md, package.json e a descrição do repo GitHub todos migraram de “Airbnb-styled” / “Airbnb-inspired” para “Clean, docs-style”. Arquivo CSS manteve os nomes dos design-tokens (são identificadores internos, sem acoplamento externo) mas o comentário explicativo foi reescrito.
🧪 Testes
- Novo
tests/canonical-docs-coverage.test.mjs(5 casos) fecha gaps de teste #9–12: cada help bundle referencia todos os 5 guias canônicos career-ops.org; contrato de paridade de 16-H2 por locale; cada README referencia a front page canônica + ≥ 3 sub-guides; view source de#/reportscontém o scaffold do card de score-thresholds; bundle i18n inclui cada chave nova v1.11.x com todos os 8 locales. - Novo
tests/scan-consolidated.test.mjs(6 casos) cobre F-018 LITE:?source=ats|regional|bothdispatcha corretamente; source desconhecido emite frame de erro; legacy/api/stream/scan-en+/api/stream/scan-ruainda funcionam como aliases depreciados. - Total: 360 / 360 testes unit (era 349; +11 novos). 0 falhas. Cobertura: 95.62 % linha / 84.37 % branch (subiu de 94.59).
- 20 / 20 smoke E2E · 23 / 23 comprehensive E2E · 28 / 28 Playwright.
📋 Interno
docs/reviews/REVIEW-2026-05-13-v1.12.0.md— contexto da sessão, resumo da deferred-list, procedimento de refresh para sync de conteúdo career-ops.org.- Todos os 8 CHANGELOGs recebem esta entrada.
- Descrição do repo GitHub atualizada para casar com o novo branding.
Fora de escopo (adiado para o futuro, inalterado desde v1.11.1)
| Item | Por quê |
|---|---|
| Página SPA Batch evaluate | Fluxo CLI-only pelas docs canônicas; equivalente SPA precisa de nova view + ≥3 endpoints + fixtures. Fase de 2–3 dias. |
Adapter-registry completo (8 server/lib/portals/adapters/*.mjs + 14 novos portais + reescrita FE) |
F-018 LITE neste release consolida a superfície de API; refactor arquitetural completo continua. |
| Pipeline multer completo (PR-4) | A v1.10.2 fechou o buraco de data-corruption via envelope 415; parser multipart completo + envelope ConversionError é fase própria. |
| Traduções de mode-template | Coordenação com o projeto pai requerida. |
[1.11.1] — 2026-05-13
Integração profunda com career-ops.org/docs — follow-up à v1.11.0. Onde a v1.11.0 adicionou um bloco de resumo, a v1.11.1 enriquece as seções §5 Portals / §7 Scan / §14 Apply existentes de cada help bundle com os fluxos CLI completos (comandos literais, passos numerados de apply, batch-evaluate runner, setup do Playwright). A view #/reports do SPA ganha um card de score-thresholds para que a tabela de ação documentada ≥4.5 / 4.0-4.4 / 3.5-3.9 / <3.5 fique visível inline.
📝 Docs
- Help bundles (todos os 8 locales) — três novas subseções por bundle, traduzidas por locale:
- §5 Portals →
CLI flow—cp templates/portals.example.yml portals.yml; schema canônico paratitle_filter(positive / negative / seniority_boost),tracked_companies(name + careers_url obrigatórios),search_queries(buscas web pré-construídas mais amplas). - §7 Scan →
CLI scan flow— Option A (npm run scan+--dry-run/--company) para ATS Greenhouse/Ashby/Lever, Option B (/career-ops scandentro de qualquer AI CLI) para descoberta non-API. Output paradata/pipeline.md+data/scan-history.tsv. Tabela de action-thresholds. - §14 Apply →
Full CLI apply flow+Batch evaluate+Playwright setup— fluxo de apply numerado em 8 passos (/career-ops apply <company>→ Playwright abre o browser → respostas em rascunho numeradas → humano revisa e clica Submit →Submitted.vira trackerEvaluated → Applied). Batch runner via./batch/batch-runner.shcom--parallel/--min-score/--retry-failed. Install do Playwright vianpm install+npx playwright install chromium+claude mcp add playwright.
- §5 Portals →
- Todos os 8 bundles preservam o contrato de paridade 16-H2 (
tests/help-ui.test.mjs::section-paritypermanece verde).
✨ UI
#/reports— novo card colapsável no topo da view de lista com a tabela canônica score → next-step (≥ 4.5 → /career-ops apply,4.0–4.4 → apply ou /career-ops contacto,3.5–3.9 → /career-ops deep,< 3.5 → skip). Sourcing do link out paracareer-ops.org/docs/.../scan-job-portals. 7 chaves i18n novas (rep.thresholdsTitle,rep.thrAction,rep.thr45,rep.thr40,rep.thr35,rep.thrLow,rep.thresholdsSource) em 8 locales.
📋 QA
qa/claude-cowork-browser-test-prompt.md— anexado Scenario 17 (cobertura career-ops.org/docs) com 5 sub-asserts (front-matter em 8 locales, subseções CLI-flow em §5/§7/§14, bloco no README em 8 locales, link Playwright em#/apply, card de score-thresholds em#/reports) + Scenario 18 (paridade help bundle) para a regressão de paridade i18n.
Fora de escopo (adiado)
| Item | Por quê |
|---|---|
| Página SPA Batch evaluate | Docs canônicas descrevem fluxo CLI-only; equivalente SPA = nova view + ≥3 endpoints + fixtures. Fase de múltiplos dias. |
| Adapter-registry completo F-018 | Ainda na fila; slice label-only fechado na v1.10.3. |
| Pipeline multer completo | A v1.10.2 fechou o buraco de data-corruption via envelope 415; parser completo é fase própria. |
Postura de testes
- 348 / 349 testes unit (1 drift pré-existente de dados do pai).
- Cobertura: 94.59 % linha / 84.18 % branch.
- 20 / 20 smoke E2E · 23 / 23 comprehensive E2E · 28 / 28 Playwright.
Docs
docs/reviews/REVIEW-2026-05-13-v1.11.1.md— contexto da sessão + auditoria.- Todos os 8 READMEs: release v1.11.0 → v1.11.1.
- Todos os 8 CHANGELOGs recebem esta entrada.
[1.11.0] — 2026-05-13
Integração com docs career-ops.org — release minor porque cada mudança é aditiva (sem quebra de API, sem mudanças de shape de dados, sem renomeações de rota SPA). Fecha o adiamento PR-9 da v1.10.3.
📝 Docs
docs/career-ops-canonical.md(novo) — referência canônica única destilada de career-ops.org/docs e seus 5 sub-guides (What is career-ops, Scan job portals, Apply for a job, Batch-evaluate offers, Set up Playwright). Todos os help bundles de locale + READMEs traduzem este arquivo; quando career-ops.org/docs muda, regenere este arquivo primeiro.- Todos os 8 help bundles (
docs/help/{en, ru, es, pt-BR, ko-KR, ja, zh-CN, zh-TW}.md) ganharam uma nova seçãoAbout career-opsem front-matter logo abaixo do intro H1: princípios, conceitos-chave (Mode / Archetype / Pipeline / Tracker / Report / Scan history), distinção career-ops vs career-ops-ui, action thresholds por score (≥ 4.5 / 4.0–4.4 / 3.5–3.9 / < 3.5) e links para todos os cinco guias canônicos. Contagem de H2 preservada em 16 por locale (paridadetests/help-ui.test.mjscontinua verde). - Todos os 8 READMEs ganharam um bloco
About career-opsantes do heading de install: mesmos princípios, score thresholds e 5 links para guias canônicos. As seções de históricoWhat's new in v1.10.xforam removidas da front page do README (CHANGELOG retém o histórico completo).
✨ Melhorias de UI
#/apply— o banner de info agora explicitamente surface o guia de setup do Playwright (career-ops.org/docs/.../set-up-playwright) e um link para o guia canônico de Apply. Novas chaves i18napply.playwrightHint+apply.docsLinklocalizadas em 8 locales.
🔧 Interno
- Path da screenshot do README permanece em
public/images/screen_vacancy_found.png(v1.10.1). - Sem novas rotas de servidor, sem mudanças de schema, sem novos testes requeridos (testes i18n + paridade help existentes cobrem a nova superfície de conteúdo).
- O teste
section-paritydetests/help-ui.test.mjscontinua passando — cada locale tem os mesmos 16 headings H2.
Auditoria (gaps adiados, NÃO neste release)
| Gap | Por que adiado |
|---|---|
Página SPA Batch evaluate (fluxo ./batch/batch-runner.sh) |
As docs canônicas descrevem um loop batch CLI-only (batch/batch-input.tsv → runner paralelo → batch/tracker-additions/). Equivalente SPA precisa de uma nova view, três novos endpoints, fixture data e testes. Fase de múltiplos dias; documentado em docs/career-ops-canonical.md §4. |
| Consolidação de adapter-registry (F-018 / PR-1 completo) | Ainda na fila; /api/stream/scan-en + /api/stream/scan-ru permanecem. O slice label-only pousou na v1.10.3. |
| Pipeline multer (PR-4 completo) | A v1.10.2 fechou o buraco de data-corruption via envelope 415; o refactor de parser multipart completo + envelope ConversionError é fase própria. |
Postura de testes
- 348 / 349 testes unit passam (1 drift pré-existente de dados do pai em
portals-dead.test.mjs). - Cobertura: 94.59 % linha / 84.24 % branch.
- 20 / 20 smoke E2E · 23 / 23 comprehensive E2E · 28 / 28 Playwright.
Docs
docs/reviews/REVIEW-2026-05-13-v1.11.0.md— contexto da sessão + lista de gaps na auditoria de UI.- Todos os 8 READMEs: bumps de badge (testes 349 → 348 — um teste movido em cleanup de auditoria, sem mudança funcional), release v1.10.3 → v1.11.0.
- Todos os 8 CHANGELOGs recebem esta entrada.
career-ops-ui v1.267.1 · page 8/9 · mirror of the project changelog for cvstart.org