Changelog

GitHub

Ogni cambiamento rilevante, release dopo release. Lo stesso file è distribuito nel repository ed è tradotto in tutte le 17 lingue.

Changelog (Italiano)

Questo changelog inizia dalla v1.85.0 — la versione in cui è stata aggiunta la localizzazione italiana. Per le versioni precedenti vedi 🇬🇧 CHANGELOG.md.

[1.236.0] — 2026-09-18

Parità con il padre — career-ops main @ 6a9c84c (VERSION 1.33.0, 128 commit). Due nuove sorgenti, e tre correzioni replicate — due delle quali qui perdevano silenziosamente le offerte.

Aggiunto

Due sorgenti per lo scanner, 92 → 94 (89 EN + 5 RU). Python.org Jobs (pythonorg) legge il feed pubblico della bacheca; l’attribuzione del datore di lavoro è obbligatoria, quindi una riga il cui titolo non nomina alcun datore di lavoro viene scartata invece di essere completata con il nome della voce del portale. Generalist World (generalist-world) esegue lo scraping delle schede della bacheca e fallisce rumorosamente su un markup che non riesce a interpretare — una scheda la cui forma è cambiata solleva un’eccezione invece di leggersi come una bacheca vuota, che è la modalità di fallimento che rende inaffidabile una sorgente ottenuta tramite scraping. Entrambe compaiono nel filtro Sorgente di #/scan, entrambe hanno l’host fissato (regex ancorata, solo HTTPS), ed entrambe sono guidate end-to-end da suite a trasporto fittizio (43 e 36 casi).

Corretto

Un ruolo Workday aperto in più città veniva scartato da un filtro sede. L’endpoint delle liste di Workday risponde a un’offerta multi-sede con un conteggio dove ogni altra offerta porta una sede: "53 Locations". Non è una sede, e buildLocationFilter confronta per sottostringa senza distinzione tra maiuscole e minuscole — quindi un ruolo aperto ad Austin e in altre 52 città non corrispondeva a nessuna voce allow: [austin] e veniva scartato, mentre lo stesso ruolo elencato singolarmente passava. Misurato a monte su tre tenant: 53 su 291 offerte erano segnaposto, quindi questo è il caso ordinario, non uno marginale. Le sedi reali dell’offerta esistono solo nel documento CXS di dettaglio, quindi ogni segnaposto ora costa una GET aggiuntiva verso di esso, con un tetto di 200 per annuncio — oltre il tetto il segnaposto viene lasciato visibile invece che falsificato, perché un’offerta non risolta non deve mai leggersi come risolta. Un dettaglio fallito o senza sede lascia l’offerta esattamente come restituita dalla lista: una "53 Locations" visibile è onesta su ciò che non si è potuto risolvere, mentre una sede svuotata passerebbe silenziosamente ogni filtro. La stessa GET porta anche startDate, quindi un’offerta arricchita viene datata esattamente invece che con la prosa relativa dell’endpoint delle liste ("Posted 30+ Days Ago"undefined); tutto ciò che non è un semplice YYYY-MM-DD viene lasciato invariato invece di essere passato a Date.parse, il cui fallback non-ISO dipende dall’implementazione. Un livello dichiarato non fa più collassare due requisizioni in un unico repost. "Insurance Specialist II" è la dichiarazione stessa del datore di lavoro che questa requisizione non è sorella dell’altra — una fascia retributiva, un ambito e un numero di requisizione diversi sotto un unico titolo di base. Il tokenizzatore non riusciva a vederlo: w.length > 3 scarta ogni numero romano fino a VIII e ogni cifra singola, quindi "Insurance Specialist" e "Insurance Specialist II" venivano tokenizzati in modo identico, ottenevano un rapporto di Jaccard perfetto, e il rilevatore di repost segnalava il secondo come una ripubblicazione del primo. role-matcher.mjs di web-ui non aveva alcuna gestione dei livelli, quindi questo porta l’intero meccanismo del padre: i livelli vengono estratti ripiegando romano e arabo su un unico numero ("Nurse II" e "Nurse 2" sono la stessa dichiarazione — e quella coppia prima differiva erroneamente), due livelli dichiarati devono concordare, e un livello presente solo su un lato è trattato come una riscrittura libera a meno che ciascun titolo non porti anche una parola che manca all’altro, il che costituisce due disaccordi e non una decorazione. Una bacheca Avature fissata a un filtro percorreva comunque l’intera bacheca globale. L’interfaccia di ricerca di Avature genera parametri di faccetta opachi (?42386=[812132] è un filtro paese), e un operatore che fissa una bacheca ristretta in portals.yml si aspetta che la scansione percorra quella. resolveSearch ricostruiva l’URL come ${origin}${path} e scartava del tutto la query string — quindi ogni richiesta, prima inclusa, percorreva l’intera bacheca del tenant: più pagine, più richieste, e offerte che l’operatore aveva deliberatamente filtrato via. Le faccette ora accompagnano ogni richiesta paginata, con il nostro jobOffset applicato per ultimo, così che una faccetta omonima vagante non possa mai dirottare la paginazione.

Sicurezza

devalue 5.8.1 → 5.9.2 (in modo transitivo tramite astro), che chiude l’unico avviso Dependabot aperto — una denial of service su input malformato. Dipendenza solo del sito; nessun codice a runtime modificato.

Note

Non portato, ciascuno con la propria motivazione. Il padre ha dato a lever un timeout di 30s e a manfred un timeout di 25s contro il suo default condiviso di 10s — un no-port: il trasporto di scansione di web-ui consente già 60s (DEFAULT_SCAN_TIMEOUT_MS), quindi entrambe le bacheche hanno qui più margine di quanto la correzione conceda là. Anche il prefisso careers- raddoppiato di iCIMS non può verificarsi qui: web-ui prende l’origin del tenant direttamente da api/careers_url e non costruisce mai un host a partire da un nome di tenant; il suo nuovo percorso di fallback-host è una funzionalità per la configurazione più permissiva del padre, non la correzione di un difetto. deutschebahn e manfred hanno ottenuto il retry-on-transient-abort, che il tetto di 60s di web-ui rende molto meno urgente — annotato invece che portato, così un futuro timeout su una scansione lunga ha già una prima cosa nota da provare. Tutto il resto del delta di 128 commit è CLI, verificatore, dashboard o web/ (il frontend proprio del padre): liveness-browser, check-jd-archive, verify-ats/verify-portals/verify-cv-facts, update-system, set-status, audit-portals, discover-ats, scaffolder, test-all, manifest dei plugin. Divergenze del fork riverificate dopo il pull: la correzione cirillica \p{L} in providers/telegram-channel.mjs, il providers/telegram.mjs proprio del fork, hermes in web/src/lib/clis.ts. L’upstream career-ops-hq/main è completamente unito (0 commit mancanti). 3066 → 3164 test; la guida ora dichiara 94 adattatori (89 EN + 5 RU) in tutte e 17 le lingue, e la struttura dei titoli resta invariata a 32 H2 / 122 H3.

[1.235.0] — 2026-09-16

Parità con il padre — career-ops main @ 68e6b94 (VERSION 1.33.0, 42 commit). Due correzioni replicate; una delle quali qui perdeva silenziosamente le offerte MokaHR.

Corretto

Una seconda offerta dalla stessa bacheca MokaHR spariva invece di essere aggiunta. url-key.mjs riduce l’URL di un annuncio a una chiave di confronto, e scartava il frammento in modo incondizionato — ragionevole, dato che quasi ogni frammento è solo presentazionale (#apply, #section-2). MokaHR è l’eccezione nel nostro stesso registro: i suoi annunci sono …/social-recruitment/{tenant}/{id}#/job/{n}, dove ogni offerta di un tenant condivide lo stesso percorso e l’id dell’annuncio esiste solo nel frammento. Quindi tutte si normalizzavano sulla stessa chiave. Misurato prima della correzione: incollando due offerte MokaHR diverse nella pipeline si otteneva una sola riga — nessun errore, nessun avviso, nulla da notare; il filtro di freschezza dello scanner (en-scanner.mjs) legge la stessa chiave, quindi una volta che un’offerta di quel tenant era nella cronologia delle scansioni, ogni altra veniva scartata come «già vista». normalizeUrl ora promuove una rotta riconosciuta #/job/{id} o #/jobs/{id} a una chiave di query solo di confronto prima che il frammento venga scartato — MokaHR mantiene il suo nome consolidato mokahr_job_id così le chiavi già scritte restano confrontabili, qualsiasi altro host riceve un _career_ops_fragment_job_id interno. L’id promosso viene aggiunto, non impostato: un URL che porta già quel parametro contiene l’id di un’altra offerta, e sovrascriverlo fonderebbe esattamente le due cose che la chiave esiste per tenere separate. Ogni frammento non riconosciuto continua a collassare, che è la metà che era già corretta. accept-encoding è ora fissato su ogni richiesta verso le sorgenti. Se non impostato, Node negozia qualunque cosa offra la sua build di undici; da Node ≥ 23 questo include zstd, e la risposta zstd di amazon.jobs torna troncata a esattamente 1024 byte con stato 200 — quindi il fallimento si legge come Unterminated string in JSON at position 1024, come un’API malformata piuttosto che un bug di trasporto. Ogni richiesta ora invia gzip, deflate, br. Sui nostri runtime questo è preventivo, non una riparazione: Node 18/20/22 — la matrice CI e il server (22.22.1) — inviano gzip, deflate, verificato direttamente e non presunto, quindi qui nulla era troncato; ciò che cambia oggi è che viene offerto br, che quelle versioni non richiedono di default ma decodificano correttamente. Il default viene applicato senza distinzione tra maiuscole e minuscole, che è la parte che vale la pena rivedere: i nomi degli header non distinguono maiuscole/minuscole e fetch unisce due voci con lo stesso nome in un unico valore separato da virgole, quindi un chiamante che passava Accept-Encoding: identity altrimenti si sarebbe visto inviare gzip, deflate, br, identity.

Note

Gli header restano un oggetto semplice al confine di fetchImpl, dove il padre passa a fetch un’istanza Headers. È una divergenza deliberata, non una svista: gli helper fetch di web-ui accettano un fetchImpl iniettabile, e 45 suite delle sorgenti leggono opts.headers['User-Agent'] da ciò che catturano — un’istanza Headers non ha accesso tramite parentesi quadre, quindi il port fedele è quello del comportamento (override senza distinzione tra maiuscole/minuscole, vince il chiamante) e non dell’implementazione. L’oggetto del chiamante viene copiato, mai mutato, poiché diverse sorgenti condividono un’unica costante di header. La correzione gemella del padre — applicare uno user-agent predefinito senza distinzione tra maiuscole e minuscole — è un non-port: web-ui non inietta alcuno UA predefinito, ogni sorgente imposta quello richiesto dalla propria bacheca, quindi non esiste un default con cui lo User-Agent di un chiamante possa entrare in collisione. Due nuove suite e tre nuovi casi fissano la coppia: tests/http-accept-encoding.test.mjs (il valore fissato esatto, ciascuno dei quattro helper fetch, e un override del chiamante in tre capitalizzazioni — verificando che sopravviva una singola chiave accept-encoding, che è ciò di cui il bug di merge ne produceva due) e tests/parsers-pipeline-hash-route.test.mjs, che lavora sulla giuntura visibile all’utente piuttosto che sulla chiave: due offerte MokaHR devono entrambe raggiungere la pipeline, un reincollaggio della stessa offerta in quattro grafie diverse deve comunque collassare in una riga, e un frammento cosmetico non deve diventare un’identità. Tutti sono stati confermati falliti per primi. Il pin vive nel trasporto, non solo in quegli helper: 25 delle 92 sorgenti chiamano direttamente il fetchImpl iniettato e non ci passano mai, quindi lo applica makeTimeoutFetch — il wrapper che entrambi gli scanner iniettano davvero — ed è l’unico punto di passaggio comune a tutte le sorgenti. (Il progetto padre non ha bisogno di un equivalente: providers/_http.mjs è il suo unico trasporto.) Una guardia strutturale ora fa fallire la CI se una nuova sorgente chiama fetch direttamente invece di usare il trasporto che le è stato consegnato, e un caso comportamentale porta greenhouse — una delle 25 — attraverso il wrapper per dimostrare che l’header arriva davvero sul filo. Non portati, con la motivazione: liveness-browser.mjs (guardia di riutilizzo di Playwright headed — web-ui non guida un browser headed), check-jd-archive.mjs (nessun relay in web-ui), test-all.mjs (l’harness del padre; la divergenza del fork su vpFixtureEnv resta intatta), web/ (il frontend proprio del padre, non rispecchiato), i manifest dei plugin e SIGNATURES.md (solo di repository). batch/batch-runner.sh (le e commerciali vengono sottoposte a escape prima dell’interpolazione sed) e jd-skill-gap.mjs (risolve cv.md rispetto alla radice dei dati) sono inoltrati in sola lettura — web-ui esegue in shell le copie del padre, quindi entrambi arrivano senza alcuna modifica al codice qui, e quello di jd-skill-gap rende più corretto il relay di /api/jds/:id/skill-gap: cv.md ora si risolve dalla radice dei dati invece che dalla directory da cui il processo era stato avviato. Divergenze del fork riverificate dopo il pull: la correzione cirillica \p{L} in providers/telegram-channel.mjs, il providers/telegram.mjs proprio del fork, hermes in web/src/lib/clis.ts. Il conteggio delle sorgenti resta invariato a 92 (87 EN + 5 RU), quindi la guida non è toccata. 3042 → 3066 test.

[1.234.0] — 2026-09-15

Parità con il padre — career-ops main @ 56cce8f (VERSION ancora 1.32.0, 58 commit oltre la 1.32.0). Una correzione replicata su 15 sorgenti, più una guardia perché non possa più tornare.

Corretto

Un singolo id di offerta malformato non svuota più l’intera pagina. encodeURIComponent solleva un URIError su un singolo surrogato UTF-16 isolato, e un payload JSON può portarne uno — JSON.parse('"\\uD800"') lo conserva. Quindici sorgenti costruivano l’URL di ogni annuncio a partire da un id o slug controllato dall’host dentro il ciclo di parsing, quindi un singolo annuncio malformato interrompeva il ciclo e perdeva tutte le offerte di quella pagina; nulla veniva registrato come fallimento della sorgente perché l’eccezione risaliva lo stack prima che il catch per-azienda avesse qualcosa da segnalare. Il padre l’ha corretto in providers/_safe-url.mjs (safeEncodeURIComponentnull su URIError, rilancia qualsiasi altra eccezione) instradandovi l’id di ogni ciclo, scartando solo l’annuncio in questione quando torna null. Quell’helper ora è server/lib/sources/_safe-url.mjs, e la stessa modifica è applicata alle tredici sorgenti replicate — alibaba, arbeitsagentur, bamboohr, feishu-jobs, garena, jibeapply, manfred, meituan, mokahr, phenom, thehub, tkms, vdab — e a due sorgenti solo di web-ui che la nuova guardia ha fatto emergere alla prima esecuzione, jobstreet e trudvsem, che avevano la stessa forma di ciclo e lo stesso throw latente. L’ambito è deliberato: solo gli id di provenienza API passano per l’helper. I valori derivati dalla configurazione mantengono la codifica rigorosa e falliscono rumorosamente — l’office di garena (un segmento di portals.yml) resta sull’urlSegment che solleva eccezioni, mentre il suo id da API riceve un idUrlSegment che restituisce null; il corpName di csod e lo slug di 4dayweek validato da SLUG_RE sono in allowlist per nome, con la relativa motivazione. Il fallback del titolo di rheinmetall poteva sollevare un’eccezione su un href malformato. Quando una scheda non ha un titolo, questo viene ricostruito dallo slug dell’URL tramite decodeURIComponent, che solleva un’eccezione su una sequenza percentuale non valida nell’href estratto — la stessa forma “una riga malformata uccide la pagina”, questa volta sul lato della decodifica. Ora decodifica in modo difensivo e ripiega sullo slug grezzo. Il registro delle sorgenti salta i file con prefisso _. L’auto-discovery importava ogni .mjs in server/lib/sources/ tranne se stesso e avvisava per qualsiasi file privo di export const meta — quindi l’helper produceva un avviso all’avvio. Viene adottata la convenzione del padre: un underscore iniziale significa non è una sorgente; questi file non vengono né importati né segnalati. Un file non dichiarato e privo di meta continua a generare un avviso, e il test di discovery ora verifica entrambe le metà (confermato rosso contro il vecchio filtro). Lo script di build del sito sync-assets.mjs conta i file degli adapter su disco con la stessa regola, quindi l’helper non fa più scattare la sua guardia contro le perdite silenziose.

Note

tests/sources-url-encoding-surrogate.test.mjs rispecchia la suite del padre in tre parti: il contratto dell’helper (un surrogato in coppia — una normale emoji — viene comunque codificato; un’eccezione sollevata dal toString stesso del valore è un bug del chiamante e si propaga), il comportamento per-sorgente per tutte e quindici (un id malformato + un id valido → nessuna eccezione, il valido mantenuto, il malformato scartato, ciascuno guidato attraverso il parser puro esportato dalla sorgente o, per thehub, il suo fetcher con un trasporto fittizio), e una guardia a livello di sorgente: ogni file convertito deve importare l’helper e invocarlo, nessun file può referenziarlo senza importarlo, e nessun file non revisionato può costruire un URL di offerta con un encodeURIComponent nudo — una nuova sorgente con la vecchia forma fallisce la CI indicando il numero di riga incriminato. Tutti e quindici i casi per-sorgente sono stati confermati falliti prima del port. Tutto il resto del delta del padre non è portato, con la motivazione: batch/batch-runner.sh --cli è un flag solo-CLI (web-ui non invoca il batch runner via shell); invite-match.mjs ora risolve set-status.mjs dalla radice del codice — web-ui non lo inoltra; company-funded.mjs ha ricevuto internamente la stessa modifica safe-url e la forma del suo output --json, che /api/company-funded analizza, è invariata; test-all.mjs ha solo spostato un budget di timeout (la divergenza del fork su vpFixtureEnv resta intatta); web/ è il frontend proprio del padre e non è rispecchiato; AGENTS.md/SIGNATURES.md sono solo di repository. Divergenze del fork riverificate dopo il pull: la correzione cirillica \p{L} in providers/telegram-channel.mjs, il providers/telegram.mjs proprio del fork, hermes in web/src/lib/clis.ts. Il conteggio delle sorgenti resta invariato a 92 (87 EN + 5 RU), quindi la guida non è toccata. 3022 → 3042 test.

[1.233.2] — 2026-09-10

Corretto — due avvisi CodeQL js/remote-property-injection ad alta severità.

Corretto

Il ciclo che copia i valori salvati in process.env scorreva Object.entries(safe) — un oggetto costruito dal corpo della richiesta — con una guardia esplicita contro le chiavi di prototipo. Il codice era sicuro, perché safe viene riempito solo da KNOWN_KEYS, ma l’analizzatore non può seguire quel vincolo attraverso due cicli. Ora scorre l’array costante, quindi il nome della proprietà è dimostrabilmente un letterale di modulo. Il comportamento è identico.

Note

Il test che fissava la guardia era un grep del testo sorgente. È stato sostituito da uno strutturale e uno comportamentale che invia __proto__ come JSON grezzo — un letterale di oggetto avrebbe impostato il prototipo invece di creare la proprietà. 3021 → 3022 test.

[1.233.1] — 2026-09-10

Corretto — la guida non descriveva il controllo aggiunto dalla v1.233.0, e la stessa sezione portava ancora istruzioni non più vere da 200 release.

Corretto

DOC-1 — la voce «Usa il valore predefinito (…)» esprime lo stato «la chiave non è impostata»; la differenza dallo scegliere lo stesso valore dall’elenco è invisibile e rilevante. Tre punti nel ### Behavior esistente ×17. DOC-2 — una riga della tabella non nominava alcuna chiave: la cella conteneva (server uses default UA), che descriveva HH_USER_AGENT, rimosso nella v1.19.0, e invitava a registrarsi su dev.hh.ru — inutile dalla v1.65.0. Le tre menzioni obsolete spariscono; la riga di risoluzione problemi è riscritta, non cancellata: da luglio 2026 hh.ru restituisce HTTP 451 agli IP non russi. La tabella delle chiavi ne elenca 6 su 45 mentre il titolo promette un registro. Una frase ×17 la dichiara una selezione e rimanda a #/config.

Note

Il nuovo help-recognized-keys.test.mjs confronta la prima colonna delle tabelle di chiavi con il KNOWN_KEYS vivo. 3018 → 3021 test; la guida resta a 32 H2 / 122 H3.

[1.233.0] — 2026-09-10

Corretto — tre riscontri nel percorso di salvataggio di #/config; CONFIG-3 è stato causato dalla correzione stessa della v1.232.1.

Corretto

CONFIG-3 — una scelta deliberata che coincideva con il valore seminato veniva scartata come «non toccata». initial confonde due origini: seminato da .env e seminato da defaultValue, e nel secondo caso l’uguaglianza è ambigua. Corretto registrando seededFromFile accanto a initial; dove il seme viene dal file nulla cambia, dove viene dal default dirty diventa un secondo segnale valido. Rimuovere una chiave riportava «· 0» perché il server restituiva solo ciò che aveva scritto. Ora risponde anche con removed, e il messaggio conta entrambi. Un menù a tendina poteva essere impostato ma mai svuotato: nessuno dei 18 aveva un’opzione vuota. Ora ognuno inizia con «Usa il valore predefinito (…)». Una nuova chiave i18n ×17.

Note

Base unitaria invariata (3018); browser 111 → 116.

[1.232.1] — 2026-09-10

Corretto — Salva su #/config scriveva campi che l’utente non aveva mai toccato.

Corretto

CONFIG-2. La v1.57.1 semina un controllo non impostato con il suo defaultValue perché il campo mostri ciò che il server usa davvero, mentre save() consultava dirty solo per i segreti e inviava sempre tutti gli altri campi. Il valore messo lì per la VISUALIZZAZIONE partiva quindi come scelta dell’utente: cambiare un menù a tendina inviava 28 chiavi e ne fissava 18 mai aperte. Ciò che cambia è lo stato di ogni impostazione: da «non impostata, segui il default del progetto» a «fissata nel .env».

Note

Confrontare con cfg.values non basta: un select non impostato mostra il suo defaultValue mentre il valore salvato è ''. Ora si confronta con ciò con cui ogni controllo è stato seminato. Il numero di chiavi scritte torna nel messaggio. 105 → 111 test browser.

[1.232.0] — 2026-09-09

Corretto — due difetti da una passata di QA nel browser; nessuno dei due viene dalla v1.231.5.

Corretto

PROFILE-1 — #/profile faceva scorrere lateralmente l’intera pagina. Il valore era un <div> anonimo con soli stili di carattere inline, quindi il suo overflow-wrap restava normal. Un URL LinkedIn non offre al CSS alcun punto di interruzione: la larghezza min-content è l’intera stringa, e min-width: auto sugli elementi di griglia rifiuta di restringersi oltre — la scheda ha fatto scoppiare la traccia, poi la riga e il documento (23 px di scorrimento reale a 1280 px). Corretto con la classe condivisa .card-value e overflow-wrap: anywhere, più .card-row > * { min-width: 0 }. anywhere, non break-word: solo anywhere conta nel calcolo del min-content. CONFIG-1 — POST /api/config accettava qualunque valore per un campo select. {"LLM_PROVIDER":"not-a-provider"} restituiva 200 e finiva nel .env; poi nulla falliva, perché il resolver ripiegava in silenzio. LLM_PROVIDERS era già esportato da env-config.mjs — il validatore non lo consultava.

Note

Solo LLM_PROVIDER è validato: il dominio reale dei modelli appartiene al fornitore e cambia tra le nostre release. Un valore già salvato viene tollerato, perché il form rispedisce ogni campo non segreto a ogni salvataggio. Aggiunto anche il descrittore di OLLAMA_API_KEY. 3013 → 3018 test.

[1.231.5] — 2026-09-09

Sicurezza — sette avvisi chiusi, uno dei quali un’esecuzione di codice remoto critica. Nessun file sorgente è cambiato.

Sicurezza

Unito da Dependabot: multer 2.2.0 → 2.3.0 (alta — denial of service per perdita di descrittori di file all’interruzione) e svgo 4.0.2 → 4.1.0 nel sito (alta e media — removeScripts lasciava passare link eseguibili). Poi altri cinque, emersi dalla riscansione innescata da quelle unioni. Ogni intervallo interessato era già coperto dallo ^ esistente, quindi serviva un aggiornamento, non una modifica al manifesto: astro 7.1.0 → 7.3.2critica, esecuzione di codice remoto tramite l’ottimizzazione delle immagini AVIF (corretta in 7.2.8), e un bypass di autorizzazione (7.2.4); sharp 0.35.3 → 0.35.4alta, vulnerabilità di libheif; e js-yaml 4.3.1 → 4.3.2 in entrambi i manifesti — alta.

Note

“Solo dipendenze” qui non equivale a “senza rischio”. js-yaml analizza ogni configurazione che l’app legge, multer accetta ogni caricamento, e Astro è avanzato di una versione minore. Verificato: test:ci 3013/3013, suite browser 4/4, e una ricostruzione completa del sito con le stesse 86 pagine e i 17 mirror. npm audit: 0 vulnerabilità.

[1.231.4] — 2026-09-07

Corretto — la v1.231.3 ha sistemato le conseguenze del clic su Doctor, non il momento. Segnalato di nuovo, da mobile e da desktop.

Corretto

withSpinner riscriveva ancora il pulsante mentre la richiesta era in volo. La v1.231.3 gli ha fatto catturare i nodi figli e ripristinarli nel finally, e tornavano davvero — ma il segnale di occupato eseguiva comunque replaceChildren con il testo '⏳ ' + original. Per tutta la durata di doctor.mjs il quadrato da 36 px conteneva quindi ⏳ 🩺Doctor e diventava una pillola larga che rompeva la riga; su desktop appariva come un ⏳🩺Doctor ammassato. Un pulsante con figli elemento non viene più riscritto: il suo segnale è la classe .is-loading, che ora scambia l’icona con una clessidra in CSS, così la geometria non può cambiare: 36 px a riposo, 36 px in volo, 36 px dopo.

Note

La lezione riguarda l’asserzione, non il codice. Il gate della v1.231.3 attendeva che aria-busy sparisse e solo allora misurava: per costruzione vedeva solo lo stato già riparato dal finally. Il nuovo controllo tiene aperta la promessa da sé e campiona a metà volo. 3012 → 3013 test.

[1.231.3] — 2026-09-07

Corretto — cliccare Doctor rompeva la barra superiore su mobile. Segnalato da un telefono il giorno stesso in cui è uscita la v1.231.2.

Corretto

UI.withSpinner appiattiva i figli del pulsante che decorava. Il suo segnale di occupato passava per textContent: salvare button.textContent, scrivere '⏳ ' + original, riscriverlo. Assegnare textContent sostituisce tutti i figli con un unico nodo di testo — e la v1.231.2 aveva appena diviso ogni azione della barra in .btn-ico + .btn-label, con l’etichetta nascosta sotto max-width: 900px. Così il primo tocco su Doctor distruggeva entrambi gli <span> per sempre: le regole mobile non avevano più nulla da agganciare, l’etichetta tornava come testo nudo e il quadrato da 36 px cresceva in una pillola 🩺Doctor che si sovrapponeva all’interruttore del tema. Non guariva da solo — solo un ricaricamento ripristinava il markup. La correzione cattura i nodi figli e li ripristina con replaceChildren; i pulsanti di solo testo, cioè la maggior parte dei 24 punti di chiamata, si comportano esattamente come prima.

  • FIND-3 — il campo di ricerca si era ridotto a 8 px su un telefono da 320 px. Portando la barra su una riga sola, il searchbar è rimasto l’unico elemento flessibile, e il changelog precedente lo diceva: «può ridursi a nulla». E si è ridotto a nulla: 8 px di campo a 320 px, 28 px a 340 px, un carattere di un segnaposto da 21. Cliccabile e digitabile, ma illeggibile come campo. Nessun min-width lo risolve: a 320 px la barra tiene 32 px di padding, un menu da 40 px e 162 px di azioni, senza 120 px da cedere. Perciò sotto i 420 px — la larghezza in cui il campo supera finalmente i 100 px da solo — si nasconde dietro una lente e si espande su tutta la barra al tocco: 182 px a 320 px, 222 px a 360 px, 252 px a 390 px, senza overflow ad alcuna larghezza. Il pulsante conserva un unico nome accessibile e comunica lo stato con aria-expanded, e lo scambio 🔍/✕ è CSS puro. Sopra i 420 px non cambia nulla. Trovato da una passata di regressione in browser sulla build live.

Note

È la stessa classe di difetto della trappola applyI18n() annotata nella v1.231.2 — codice che assegna textContent a un elemento che ora ha figli elemento — raggiunta da un chiamante diverso. La lezione si generalizza: dividere il contenuto di un elemento in <span> figli rende ogni scrittore di textContent già esistente su quell’elemento un bug latente. Bloccato due volte: node:vm sul vero withSpinner e un clic in un browser reale a 320 px. Entrambi confermati fallire sul codice precedente. 3009 → 3012 test.

[1.231.2] — 2026-09-06

Corretto — la barra superiore mobile andava a capo su due righe, il server eseguiva un padre più vecchio e il redirect di cvstart.ru dava 404 su qualsiasi percorso che già nominasse una lingua.

Corretto

  • La barra superiore mobile torna su una sola riga, azioni a sole icone. I pulsanti venivano forzati su una seconda riga a tutta larghezza: un telefono mostrava [☰ · ricerca] sopra [🔔 🌙 Doctor Apri Scan] — due pillole larghe su una riga propria. Ora sono quadrati da 36 px (🩺 / ⚡) accanto alla campanella e all’interruttore del tema, e tutto sta su una riga a 320 px. Misurato, non stimato a occhio: la larghezza del documento eguaglia il viewport a 320, 360, 390 e 430 px e su tutta la fascia 560–760 px, dove prima i pulsanti si sovrapponevano alla barra di ricerca. La lingua non incide più sul layout mobile, perché l’etichetta è nascosta e il pulsante è un quadrato fisso; sopra i 900 px le etichette tornano invariate.
  • Il server eseguiva il padre 1.31.0 benché la versione fosse costruita su 1.32.0. /api/health su resumecraft.ru riportava parentVersion: 1.31.0. Sincronizzati 95 file di runtime, due rimossi. Rilevato da QA da browser sulla superficie in produzione.
  • cvstart.ru anteponeva /ru/ a qualsiasi percorso, senza guardare cosa ci fosse già. /ru/help diventava /ru/ru/help e /en/help diventava /ru/en/help; entrambi 404. Il primo è ciò che si ottiene copiando un URL dal sito principale; il secondo escludeva del tutto il visitatore anglofono dalla versione inglese. Ora un percorso che nomina già uno dei 16 prefissi passa così com’è, en viene rimosso (l’inglese sta nella radice) e il resto riceve ancora /ru/.

Modificato

  • 98 prompt QA superati spostati in qa/archive/superseded-prompts/. qa/README.md documenta dalla v1.137.0 che al livello superiore resta solo il prompt della versione corrente — la regola non è stata applicata per 95 versioni.
  • Altri cinque prompt hanno lasciato il livello superiore: si dichiaravano attuali senza esserlo. REGRESSION-FINAL.md si definiva «l’unico prompt di regressione autorevole e indipendente dalla versione», ma i suoi §§11–15 sono cinque registri di cicli chiusi (v1.55.x → v1.59.7) e la baseline dell’aiuto dichiarata era 28 H2 / 103 H3 su 16 lingue contro 32 / 122 e 17 oggi: un agente QA che lo seguisse sarebbe caduto al §9 al primo passo. QA-REGRESSION-PROMPT.md aveva un nome senza versione e contenuti di v1.226.0. QA-REGRESSION-PROMPT-v1.76.0-FULL.md era fissato 155 release indietro, REGRESSION-PROMPT-FINAL.md chiude il ciclo v1.58.52 → v1.59.10 e QA-FULL-REGRESSION.md li duplicava tutti su base v1.131.1. Con loro è andato PARENT-SYNC-WORKLIST-v1.26.0.md (il progetto padre è ormai a 1.32.0) e il datato UX-AUDIT-2026-07-06.md è passato in reports/. Restano quattro driver, con le proprie baseline aggiornate ai valori reali di v1.231.2. qa/README.md è riscritto per descrivere la cartella che esiste.

Note

  • Due trappole di specificità ora commentate in app.css: le regole desktop sono dichiarate più in basso del blocco max-width: 900px, quindi a parità di specificità vincono.
  • applyI18n() assegna el.textContent, perciò la chiave i18n è passata a uno <span> interno; sul pulsante avrebbe cancellato l’icona a ogni cambio lingua.
  • Il primo rsync del padre si è interrotto a metà su un file non più presente in locale, lasciando un deploy parziale che riportava successo. I deploy del padre si verificano per checksum, mai per codice di uscita.

[1.231.1] — 2026-09-06

Corretto — la scansione dei contatori della v1.231.0 ha riscritto il nome dell’account in tutti i link.

Corretto

  • Ogni link Fighter90 è diventato Fighter92. La scansione del contatore di sorgenti in v1.231.0 sostituiva 90 con 92 sotto una guardia di confine numerico, che scarta correttamente 290 e 901 ma corrisponde al 90 dentro Fighter90. 562 link nei 17 README, nei 17 pacchetti di aiuto e nei mirror di aiuto del sito, più 138 nel wiki, puntavano a un account inesistente. Segnalato da un utente, per l’immagine mancante.
  • Cinque riferimenti storici a versioni sono slittati. La scansione 85 → 87 ha trasformato v1.85.0 in v1.87.0, inventando una versione mai rilasciata.

Note

  • Un confine numerico non basta: protegge un numero dalle cifre vicine, non dall’essere parte di un identificatore o di una stringa di versione. I contatori sono corretti: 92 sorgenti, 87 adattatori EN, 3009 test.
  • Il repository padre non è stato toccato.

[1.231.0] — 2026-09-05

Aggiunto — due sorgenti di scansione dal career-ops padre v1.32.0 (Collage, Telegram rigoroso), la modalità REST di Gem e un difetto che il port ha portato alla luce nel codice del padre stesso.

Aggiunto

  • Due nuove sorgenti, 90 → 92 (87 EN + 5 RU), ALL_ADAPTERS 85 → 87. Entrambe senza token.
    • Collage (provider: collage) — l’API pubblica dei siti annunci di Collage HR. Il segmento di percorso è l’indirizzo del sito che il tenant ha scelto, non uno slug del nome azienda: viene quindi letto da api: o da un URL secure.collage.co/jobs/<indirizzo> e mai indovinato — un indirizzo indovinato scansionerebbe la bacheca di qualcun altro.
    • Telegram (rigoroso) (provider: telegram-channel) — legge le stesse anteprime t.me/s/<canale> della sorgente telegram esistente, con lo scambio opposto: un post diventa una riga solo se nomina un datore di lavoro E rimanda a una pagina di annuncio. Digest, post con datore nascosto e post di solo contatto vengono scartati. Bassa copertura per scelta — a monte è stato misurato il 32-77% dei post che passano — ma ogni riga porta un datore reale e un link reale. Entrambe vengono distribuite, come nel padre.
  • Gem ha guadagnato una modalità REST opzionale accanto al percorso GraphQL (api: https://api.gem.com/job_board/v0/<board>/job_posts). Non viene mai derivata da un id di bacheca, e il percorso GraphQL resta il predefinito. Portato dal padre #3783.

Corretto

  • Il filtro di luogo di telegram-channel non vedeva il cirillico e nominava città come datori di lavoro. Il LOCATIONISH_RE del padre usava \b, un confine solo ASCII che non scatta mai accanto al cirillico: tutte le sue alternative russe erano irraggiungibili e Senior Engineer | Москва restituiva «Москва» come datore di lavoro. È esattamente la sostituzione di campo che l’intera politica di attribuzione esiste per impedire, e colpiva il pubblico principale del provider. Il port di web-ui usa controlli Unicode, gli stessi che il padre già applica una costante più sotto in ROLE_WORD_RE. Fissato da un test che fallisce sulla forma ASCII. Non ancora corretto a monte.

Note

  • Il recupero per faccette di Workday nel padre (#3851, #3874) non richiedeva port — web-ui non pagina Workday affatto, un solo POST a offset: 0.
  • providers/_types.js è cambiato solo nella documentazione.
  • Test: 2962 → 3009 (+47).

[1.230.0] — 2026-09-05

Modificato — tre correzioni ai provider dal career-ops padre v1.32.0, e un difetto che il port stesso ha portato alla luce.

Aggiunto

  • Welcome to the Jungle accetta un’espressione filters di Algolia applicata lato server (wttj: { filters: "offices.country_code:FR AND contract_type:full_time" }). Una parola chiave da sola non restringe una bacheca globale: a decidere quali max_hits risultati arrivano è il ranking di pertinenza di Algolia, non i filtri dello scanner — i nostri agiscono dopo, su ciò che è già arrivato, quindi tutto oltre il tetto è invisibile per quanto bene corrisponda. Un filtro riduce l’insieme invece di riordinarlo, perciò il tetto per query sale da 200 a 1000. Con un filtro, queries diventa facoltativo: la query vuota significa «tutto ciò che passa il filtro». Portato dal padre #3757.

Corretto

  • Radancy non avvisa più «truncated» quando un tenant serve semplicemente meno annunci di quanti ne dichiari il suo stesso banner. Il padre ha confrontato data-total-results con nove tenant reali: cinque coincidevano, ma quattro — uno piccolo e uno enorme — hanno consegnato dal 10% al 56% di annunci unici in meno del dichiarato, senza una sola riga duplicata. Il banner sovrastima ciò che l’indice di ricerca del tenant serve davvero, quindi confrontarci il conteggio accumulato dava falsi positivi sulla maggioranza delle bacheche. L’avviso ora compare solo quando il percorso è stato fermato dal nostro max_jobs/max_pages, l’unico caso su cui il chiamante possa agire; totalResults resta nel messaggio come contesto e non decide mai. Portato dal padre #3839.
  • Radancy aggira la cache a ogni richiesta di frammento JSON. Uno strato di cache davanti a quella rotta ripete risposte obsolete su alcuni tenant: lo stesso annuncio ricompare pagine dopo mentre un altro non viene mai servito — riprodotto nove volte su nove a monte. Le intestazioni Cache-Control/Pragma non hanno cambiato nulla; un parametro casuale per richiesta sì. Deve essere unico per chiamata, mai derivato dal numero di pagina: un parametro deterministico darebbe comunque alla cache una chiave stabile, e con essa la stessa corrispondenza stabile e sbagliata. Portato dal padre #3839.
  • Gli elenchi di uffici di Greenhouse hanno ordine deterministico. Greenhouse non promette l’ordine di offices[] tra una risposta e l’altra, e l’insieme ricostruito dall’albero /offices segue il suo ordine di visita. Entrambi finiscono nel campo location dell’annuncio, quindi una bacheca che riordinasse i propri uffici riscriveva quella stringa — e un annuncio immutato veniva letto come nuovo. Il padre ha corretto l’insieme (#3839); web-ui aveva una seconda istanza dello stesso difetto, assente nel padre — il ripiego offices[0], usato quando l’annuncio non porta una localizzazione propria — e ora sono ordinati entrambi.

Note

  • La correzione iCIMS del padre (#3728) non richiede port. Legge tutte le voci jobLocation dal JSON-LD della pagina di dettaglio, perché alcuni tenant emettono una voce interamente UNAVAILABLE prima dell’indirizzo reale. L’iCIMS di web-ui prende la localizzazione dall’HTML della pagina di elenco e non apre mai una pagina di dettaglio.
  • La modifica a _http.mjs non richiede port. Estrae isRefusedRedirectError per un secondo consumatore che web-ui non ha; il comportamento di isRetryableError è invariato.
  • La deduplicazione per localizzazione (#3751) non è rispecchiata — vive nella pipeline scan.mjs della CLI del padre.
  • Registro invariato: 90 fonti (85 EN + 5 RU). Test: 2956 → 2962 (+6).

[1.229.0] — 2026-09-03

Aggiunto — quattro fonti di scansione dal career-ops padre v1.31.0: Built In, Feishu Jobs, Garena e MokaHR.

Aggiunto

  • Quattro nuove fonti, 86 → 90 (85 EN + 5 RU), ALL_ADAPTERS 81 → 85, tutte senza token. Built In copre i nove mercati della bacheca tech statunitense e non porta query predefinite: una voce senza queries:categories: non scansiona nulla e lo dice, perché una fonte condivisa non deve portare i termini di ricerca di qualcuno; un mercato fuori elenco viene rifiutato, mai sostituito in silenzio dalla bacheca nazionale. Feishu Jobs accetta due sole forme di host, che usano percorsi diversi per la pagina dell’offerta. Garena è mono-azienda: office modella il link, non l’elenco. MokaHR restituisce una risposta cifrata (busta AES-128-CBC con la chiave accanto) — offuscamento, non sicurezza, ma senza decifrare non si legge nulla.

Corretto

  • htmlToText poteva lasciar passare nel testo un apri-tag incompleto. safe &lt;img src=x onerror=1 usciva come safe <img src=x onerror=1. Ora ogni decodifica è seguita da una ripulitura e l’apri-tag finale perde la parentesi angolare. Portato dal padre (#3491).

Note

  • Dal fix Workday del padre non è stato portato nulla. Recupera i tenant il cui backend blocca la paginazione all’offset 2000 — web-ui non pagina Workday (un POST a offset: 0, 100 righe).
  • I fix a verify-cv-facts e merge-tracker non hanno richiesto port: web-ui esegue quegli script dal checkout del padre.
  • Test: 2909 → 2956 (+47).

[1.228.5] — 2026-08-30

Corretto — la versione riportata descriveva il file su disco, non il codice in esecuzione.

Corretto

  • /api/health rileggeva package.json a ogni richiesta, quindi un processo obsoleto riportava una versione che non stava eseguendo. Il QA ha trovato un’istanza locale che rispondeva version: 1.228.4 mentre restituiva 404 per /api/ping, una rotta aggiunta in 1.228.3: avviata prima del deploy, serviva il codice vecchio e riportava la versione del file nuovo. È esattamente così che un deploy che copia i file senza mai riavviare sembra riuscito: l’unica stringa che un operatore controlla è quella che non può vedere il problema. Stessa classe del symlink di v1.228.1, dove rsync si interrompeva a metà trasferimento e riportava comunque successo.

    La versione ora viene catturata al caricamento del modulo, quindi descrive il codice in esecuzione. Un processo obsoleto riporta la versione vecchia — la verità, visibile subito. parentVersion resta per richiesta: descrive il checkout del progetto padre, che questo processo non carica e che può legittimamente cambiare sotto un server in esecuzione.

[1.228.4] — 2026-08-30

Corretto — la sonda di liveness non autenticata faceva lavoro su disco a ogni richiesta.

Corretto

  • GET /api/ping leggeva e analizzava package.json a ogni chiamata. È l’unico endpoint raggiungibile senza credenziali, quindi una lettura da disco più un parsing JSON per richiesta è una leva di denial of service consegnata a chiunque — CodeQL l’ha segnalata come mancanza di limitazione di frequenza. La versione ora si legge una sola volta alla registrazione della rotta; non può cambiare senza riavvio, quindi il gestore non fa alcun I/O. Limitare la frequenza avrebbe contenuto il danno; togliere il lavoro toglie la leva.
  • Il mascheramento del nome reale del titolare del profilo fuori dal loopback è ora fissato da un test. /api/health lo sostituiva già con hidden, ma solo perché quella riga condivide la guardia hidden ?? value con la radice del progetto. Nulla lo verificava: una modifica che le desse un valore proprio avrebbe fatto trapelare il nome di una persona reale sulla LAN senza che nulla se ne accorgesse.

[1.228.3] — 2026-08-30

Fixed — the docs assistant could not answer in English, Telegram company names arrived truncated, and a monitor had no way to see the service was up.

Fixed

  • The docs assistant answered “no matching help sections were found” in English while the same question worked in Russian. Retrieval was fine; the context builder was not. buildAskPrompt did if (ctx.length + sec.body.length > MAX_CONTEXT) break — a break, so one oversized section ended the loop and left the context empty, and the assistant truthfully reported it had nothing. The English §5 had grown to 16 081 bytes against a 14 336-byte budget (partly because v1.228.0 added telegram_channels to it); the Russian §5 was 13 009 and fit. Same ranking in both languages — §5 was first — only the size differed.

    Two changes: a section past 6 KB is now split at its ### boundaries, so telegram_channels is its own retrievable chunk (1 340 bytes) that ranks first in both languages; and an oversized section is skipped or truncated rather than ending the loop. The guide went from 32 chunks to 75.

  • A Telegram company label yielded the sentence after it, not the name. Компания: FinCore Technology (продуктовая разработка, высоконагруженные системы) became FinCore Technology (продуктовая разработка, высоконагруженны — capped at 60 characters, cut mid-word. Three of 141 rows. The name now ends at the first separator that introduces a description — a spaced dash (so Coca-Cola survives), a comma, semicolon, pipe or opening parenthesis — and any remaining cap falls on a word boundary. This source attributes a company as fact; a truncated sentence is as wrong as a guessed name, only less obviously so.

Added

  • GET /api/ping — a liveness probe that leaks nothing. { ok, version } and no more. /api/health reports absolute paths, the profile owner’s real name and which API keys are set: legitimate for an authenticated operator, never for the public internet. A monitor behind a Basic-auth gate previously had no way to see the service was up at all.

[1.228.2] — 2026-08-30

Fixed — a two-word search in /api/scan-results matched the phrase, not the words, and under-reported badly.

Fixed

  • ?q= now matches every term independently instead of the literal phrase. The query was a single includes(q) over title + company + location, so продакт менеджер found only rows carrying those two words in that exact adjacency — 32 of the 162 that carry both. Менеджер продукта and Менеджер по развитию продукта were invisible to it: the same words, reordered or with filler between them.

    This is how a narrow match becomes a wrong answer. The Telegram assistant asked for all Продуктовый менеджер reported 9 — faithfully, because 9 was what the endpoint said. The count it quoted was honest; the endpoint’s was not. Word order and the words between are not something a person typing two words intended to pin.

    Matching is AND across terms, so widening precision costs nothing: a row must still carry every term. менеджер разработчик returns zero rather than the union.

[1.228.1] — 2026-08-29

Fixed — a dangling symlink in the repo that silently truncated deploys.

Fixed

  • .claude/skills/refero-design was a tracked symlink to a path that has never existed here. It pointed at ../../.agents/skills/refero-design, which resolves to .agents/ at the repo root; there is no such directory, and the link arrived in the v1.118.0 parity pack from a machine where the surrounding folders nested differently. Anyone cloning the repo has been getting a broken link ever since.

    It was not inert. rsync aborts on it mid-transfer — a deploy today reported success while the server kept running the previous version, and the mismatch only surfaced because the version was checked afterwards. The parent project’s node test-all.mjs also crashes outright when a web-ui checkout sits inside its tree, because its fixture copy walks the working directory and cannot stat the link. Nothing referenced it: the only refero matches in the repo are the Polish word preferowany.

[1.228.0] — 2026-08-29

Aggiunto — i canali Telegram come fonte di scansione e una modalità di query paginata su /api/scan-results.

Aggiunto

  • I canali Telegram sono ora una fonte scansionabile. Un blocco telegram_channels: in portals.yml li elenca; ognuno viene letto dall’anteprima pubblica https://t.me/s/<canale>. Registro: 85 → 86 fonti (81 EN + 5 RU), ALL_ADAPTERS 80 → 81.

    Telegram non pubblica RSS e la Bot API non può leggere un canale che il bot non amministra: nessuna delle due vie ovvie funziona. L’anteprima /s/ è semplice HTML reso dal server: senza autenticazione, senza JS, senza cookie. Verificato su 16 canali: 15 hanno restituito 20 post ciascuno con una GET nuda.

    channel: accetta qualsiasi forma di scrittura. Il parser si ancora alle tre cose che un redesign di Telegram sposterebbe per ultime: data-post, il contenitore del testo e la data ISO <time datetime>.

  • GET /api/scan-results guadagna una modalità di query paginata. ?q=<testo>&limit=50[&region=ru|en][&set=filtered|fresh][&offset=N] restituisce { total, returned, offset, limit, set, rows }, dove total è il conteggio PRIMA della paginazione. Senza parametri la rotta restituisce ancora lo snapshot intero: la tabella #/scan dipende da quella forma.

Corretto

  • L’assistente Telegram rispondeva da una frazione dei dati. Il suo skill puntava già a /api/scan-results, ma la rotta sapeva restituire solo lo snapshot intero — circa 2 MB — che non entra nel contesto di un agente. Alla richiesta «trova tutti i Product Manager» ha riportato 9 corrispondenze dove lo snapshot ne aveva centinaia, dicendo di «lavorare con un indice locale limitato». Era vero: vedeva l’endpoint ma non poteva consumarlo. Con la modalità paginata la stessa domanda costa 2 KB e restituisce un total onesto. Anche lo skill è stato riscritto: chiedeva di riassumere i post nuovi, cioè puntava a fresh invece che a filtered.

Note

  • Un post di canale è prosa, non una scheda di lavoro. Non ci sono campi strutturati: il titolo viene dalla prima riga sostanziale, azienda/luogo/retribuzione da etichette esplicite. Separare le offerte vere da pubblicità e digest resta al title_filter esistente, lo stesso già tarato per le altre fonti. Sono spunti, non un elenco.
  • L’azienda non viene mai indovinata. Senza etichetta la riga è attribuita al canale (@rabotaphp): un datore di lavoro sbagliato entra nel tracker come un fatto.
  • Un parse vuoto è un errore, non «nessuna offerta». t.me risponde con un redirect per un canale privato o inesistente; restituire zero righe si leggerebbe come una giornata tranquilla e nasconderebbe per sempre un refuso nella configurazione. jobGeeks fa esattamente questo (HTTP 302) e deliberatamente non è configurato.
  • Il rilevamento del remoto usa una frontiera di parola Unicode anziché \b, che è solo ASCII — la stessa trappola del filtro sui titoli in v1.227.3, qui colta da un test prima del rilascio.
  • 15 canali configurati e verificati dal vivo: 299 post, 15/15 canali. 30 nuovi test. Test: 2865 → 2895.
  • The README test counts jumped by hundreds, and no, this release did not add them. The localized READMEs carried unit-test totals frozen at 1856 / 1945 / 1955 / 549 / 419 / 284 / 2527 depending on the file — each stale since a different release, and several contradicting the badge at the top of the same page. They now all read 2895, verified by running the suite rather than by copying a badge. Playwright (70 / 90 / 12 → 101), e2e smoke (20 → 21), route modules (12 / 32 → 37), smoke flows (12 → 22) and the test-file count (218 → 329) were stale in the same way. The Total row also repeated the unit count, so it under-summed the three suites below it; it now reads the real sum (3040).

[1.227.5] — 2026-08-29

Corretto — la scansione regionale esauriva la memoria del server e una suite Playwright falliva sul proprio smontaggio.

Corretto

  • La scansione regionale uccideva il server per OOM. ru-scanner accumulava ogni risultato grezzo di ogni query e deduplicava e filtrava solo alla fine. L’elenco di query è volutamente pieno di quasi sinonimi (Golang, Go разработчик, Golang разработчик, Senior Go…), quindi la stessa offerta torna una volta per query e l’array tratteneva un multiplo del conteggio univoco — descrizioni comprese, che sono il grosso dell’oggetto e che i filtri scartano quasi del tutto. Misurato: 742 MB di heap su un’esecuzione reale da 21 query. Il server limita l’heap di Node a 490 MB (V8 lo dimensiona dai 956 MB di RAM della macchina), perciò la scansione abbatteva il servizio con FATAL ERROR: Reached heap limitquattro volte in un giorno. Ora deduplicazione e filtraggio avvengono per query, portando la stessa esecuzione a 177 MB, il 76% in meno. I conteggi riportati non cambiano: il totale univoco è portato da un Set di stringhe URL.
  • net::ERR_SOCKET_NOT_CONNECTED faceva fallire la CI a caso. Tre suite Playwright chiamano window.stop() per fermare le richieste in volo prima di affermare che non ci sono stati errori di console. Chromium riporta quell’unico atto con tre nomi a seconda dello stato del socket: ERR_ABORTED all’annullamento, ERR_EMPTY_RESPONSE in volo, ERR_SOCKET_NOT_CONNECTED quando il socket era già sparito. Il filtro condiviso ne elencava solo due. È emerso nella scansione dei locale su tr, mentre playwright-forms e playwright-smoke erano a un’esecuzione dallo stesso guasto.

Note

  • Perché solo ora. L’elenco è passato da 14 a 21 query con l’aggiunta delle ricerche product manager. La memoria della vecchia implementazione scalava col totale dei risultati: +50% di query ha superato il tetto della macchina.
  • La modifica alla deduplicazione preserva il comportamento, ed è testato. tests/ru-scanner-dedup-order.test.mjs fissa la direzione scomoda — un duplicato successivo che NON passa i filtri deve sfrattare uno precedente che era passato — ed esegue 200 verifiche randomizzate di equivalenza contro la vecchia implementazione.
  • Il filtro della console resta stretto. Solo la famiglia delle richieste annullate è benigna; connessione rifiutata, errore DNS, reset, 5xx ed eccezioni JS continuano a far fallire l’asserzione.
  • Due ipotesi sono state testate e scartate anziché spedite: un timeout di Caddy (configurazione pulita, flush_interval -1 corretto per SSE) e il requestTimeout predefinito di 5 minuti di Node (una riproduzione in scala ha mostrato che non interrompe una lunga risposta in streaming). La prova vera è stata Reached heap limit nel journal. Test: 2860 → 2865.

[1.227.4] — 2026-08-29

Corretto — word: / stem: erano ancora ignorati da content_filter; la v1.227.3 aveva sistemato solo il filtro sui titoli.

Corretto

  • content_filter non rispettava i prefissi. La v1.227.3 ha insegnato a title_filter i prefissi word: / stem: del progetto padre, ma ha lasciato content_filter al suo lower.includes(k) grezzo: lì una voce con prefisso veniva ancora confrontata con il testo letterale "word:java" — lo stesso no-op silenzioso, un filtro più in là. content_filter legge la DESCRIZIONE dell’offerta e il suo default è sempre stata una semplice sottostringa: per questo un negativo nudo java scarta tutto ciò che si limita a menzionare «JavaScript»; il prefisso è il modo di sottrarre una singola voce a quella regola. Portato il compileContentKeyword del padre, che condivide la macchina dei prefissi ma deliberatamente non ancora le parole corte: il filtro sui titoli ancora le sigle di 2–3 lettere perché «COO» dentro «Coordinator» è sempre sbagliato, mentre una sequenza breve dentro un paragrafo di prosa è spesso voluta (aws, gcp, sql, go).

Note

  • Individuato nella revisione IA della PR v1.227.3, che ha notato come il codice non coprisse ciò che il suo stesso commento affermava: diceva «title_filter / content_filter» mentre era collegato solo il percorso dei titoli. Ora il commento distingue esplicitamente: entrambi i filtri rispettano i prefissi, solo quello sui titoli ancora le sigle.
  • Verificato anch’esso da quella revisione e invariato: passare la regola delle sigle di 2–3 lettere da \b alla frontiera Unicode è identico per l’ASCII e più severo solo dove serve — COO_lead e coo1 restano mantenuti (trattino basso e cifra sono caratteri di parola in entrambi), Coordinator mantenuto, «Директор COO» e «VP «Продукт»» correttamente scartati. Verificati 14 casi limite.
  • Le voci di content_filter senza prefisso mantengono la corrispondenza precedente byte per byte, una descrizione vuota o assente passa ancora, e un word: / stem: vuoto non corrisponde a nulla anziché a tutto. 6 nuovi casi in tests/content-filter.test.mjs. Test: 2852 → 2858.

[1.227.3] — 2026-08-29

Corretto — i prefissi word: / stem: del filtro sui titoli venivano ignorati in silenzio, rendendo quelle righe di filtro inefficaci.

Corretto

  • web-ui non implementava i prefissi word: / stem: del progetto padre. title_filter (e content_filter) confrontano per impostazione predefinita sottostringhe senza distinzione di maiuscole: per questo un negativo nudo intern scarta anche «International Product Manager» e «Internal Tools Engineer». Anziché ribaltare quel default — una rottura per ogni installazione già configurata — il padre ha reso la precisione opzionale per singola voce: word:intern corrisponde solo alla parola intera e stem:agent deve iniziare una parola potendo proseguire (separando «Agentforce» da «Reagents»). web-ui non aveva né l’uno né l’altro, quindi confrontava la voce con il testo letterale "word:intern", che non compare in nessun titolo di offerta. La riga diventava un no-op silenzioso: lo stesso portals.yml filtrava correttamente dalla CLI e per niente qui, così ogni annuncio di stage escluso raggiungeva comunque la tabella #/scan. Portato dal title-keywords.mjs del padre, inclusa la frontiera di parola compatibile con Unicode (\p{L}\p{M}\p{N}_) al posto di \b, che è solo ASCII e corrispondeva a metà parola nei titoli accentati o cirillici.

Note

  • Trovato controllando un portals.yml reale che aveva appena adottato word:intern proprio perché un intern nudo smettesse di scartare «International Product Manager». Il prefisso funzionava nella CLI; web-ui teneva silenziosamente tutti gli stage. Ora il comportamento è identico.
  • I default non cambiano. Una parola chiave senza prefisso mantiene il confronto precedente: sottostringa per le frasi e per tutto ciò che contiene caratteri non alfabetici (.NET, SAP , L&D), ancoraggio ai confini di parola per le sigle di 2–3 lettere. Un word: / stem: vuoto è considerato un refuso e non corrisponde a nulla: come negativo, uno schema vuoto corrisponderebbe a ogni titolo e annullerebbe un’intera scansione per via di due punti sfuggiti.
  • La regola delle sigle di 2–3 lettere usa ora la stessa frontiera Unicode: prima usava \b, quindi coo e word:coo divergevano sui titoli non ASCII — due grafie della stessa regola.
  • 6 nuovi casi in tests/title-filter.test.mjs. Test: 2846 → 2852.

[1.227.2] — 2026-08-29

Corretto — #/config scorreva lateralmente sui telefoni stretti.

Corretto

  • MOBILE-1: #/config andava in overflow orizzontale su viewport ≤ 352 px. Ogni campo di tipo select portava un min-width: 300px inline. Uno stile inline batte il foglio di stile, quindi il controllo non poteva restringersi insieme al contenitore: con un viewport da 320 px il bordo destro del select finiva a 353 px, la pagina otteneva una barra di scorrimento orizzontale e i controlli uscivano a destra. Interessati tutti e 18 i select di provider/modello (cfg-llm-provider e ogni cfg-*-model). Ora min-width: min(300px, 100%) + max-width: 100%, così i comodi 300 px valgono solo quando c’è spazio. Dispositivi alla soglia o sotto: iPhone SE (1ª gen., 320 px), Android datati, schermo esterno del Galaxy Fold.

Note

  • Perché tutte le passate mobili precedenti l’hanno mancato. 360 px e 375 px hanno giusto il margine per nascondere l’overflow, e le suite Playwright girano in una finestra da 1280 px. Nulla in CI aveva mai misurato un viewport più stretto di 375 px. Il nuovo tests/playwright-narrow-viewport.mjs percorre le rotte con form a 320 px e fallisce su qualsiasi overflow orizzontale, nominando nel messaggio l’elemento più largo; un secondo caso fissa che nessun select di #/config supera il viewport. È stato prima verificato che riproducesse il bug (scrollWidth 353 > clientWidth 320, 18 select con bordo destro 353). Playwright: 99 → 101.
  • Noto e volutamente non ancora sotto gate: 280 px (schermo esterno del Galaxy Fold) va ancora in overflow di ~5 px, per una causa diversa — contenuto lungo dentro i blocchi <details> della card di configurazione e la sidebar da 256 px contro un viewport da 280 px, non il min-width inline che questa release corregge. Rifluire a una larghezza che nessun telefono diffuso dichiara è lavoro a parte; la lista WIDTHS della suite è già pronta.
  • Corretto anche il prompt QA della v1.227.1, che chiamava la rotta di follow-up #/modes/followup. Le mode-page registrano lo slug nudo (Router.register(cfg.slug)), quindi la rotta è #/followup e #/modes/followup restituisce correttamente 404. Solo documentazione.
  • Suite unitaria invariata a 2846 (la nuova copertura è a livello browser).

[1.227.1] — 2026-08-29

Corretto — la ripartizione delle fonti nella guida non tornava con sé stessa e il banner GitHub veniva stampato come markup grezzo nell’app.

Corretto

  • §17 dava un totale e una ripartizione che non sommavano. Tutti i bundle della guida dicevano «il registro server/lib/sources/ include 85 adapter — 78 inglesi + 5 russi». 78 + 5 = 83, non 85; in ja/ko la deriva arrivava a 77 (= 82). Ogni nuova fonte alzava il totale lasciando intatto l’addendo, e i due numeri divergevano da diverse release. Ora la ripartizione è 80 EN + 5 RU in tutte e 17 le lingue. Conta più dell’estetica: docs/help/<lang>.md è l’UNICO corpus su cui si fonda «Ask the docs», quindi chi chiedeva quante fonti fossero supportate riceveva due numeri contraddittori dalla stessa frase.
  • La stessa frase fissava un’ancora di versione obsoleta. «A partire da v1.213.0 il registro …» è sopravvissuta in 16 lingue mentre il contatore si muoveva due volte (83 in v1.219.0, 85 in v1.226.0). L’ancora era già stata riparata a mano in v1.210.1 ed è semplicemente derivata di nuovo. Viene rimossa a favore di una formulazione senza versione: il contatore è verificato contro il registro vivo, che non può invecchiare.
  • Il banner dei provider veniva stampato come markup escapato in cima a #/help. v1.225.0/v1.225.1 lo davano per «rimosso nell’app da UI.md() (nessun supporto immagini)». Questo confonde due cose: UI.md() fa escape per primo — esegue l’escape di ogni byte della sorgente prima di qualsiasi trasformazione markdown, che è il confine XSS corretto ma l’opposto della rimozione. Nessuno rimuoveva il banner perché nessuno ci provava, e tutte e 17 le lingue aprivano #/help con 268 caratteri di <p align="center"><img src="https://… letterale. Un nuovo ingresso condiviso (server/lib/help-markdown.mjs::stripGithubOnlyBlocks) scarta in lettura i blocchi HTML immagine isolati, sia per GET /api/help/:lang sia per il corpus del docs-assistant. I file mantengono il banner, quindi GitHub continua a mostrare la vetrina.
  • La colonna «#» della bacheca della cadenza era vuota dalla v1.117.0. Leggeva entry.appNum, mentre followup-cadence.mjs emette numappNum non è mai esistito nel payload. Ora vengono letti entrambi. La riga «Next up» della v1.227.0 aveva ereditato lo stesso errore ed è corretta insieme.

Note

  • Tutti e quattro sono passati inosservati perché nulla li presidiava. I gate della guida (canonical-docs-coverage, help-ui, help-ru-config-section) contano le INTESTAZIONI — 32 H2 / 121 H3 — e nessun test aveva mai letto un numero dentro un paragrafo né guardato cosa serve davvero l’ingresso della guida. Due nuove suite chiudono la falla: tests/help-source-counts.test.mjs (il totale dichiarato è pari a SOURCES.length; gli addendi corrispondono alla divisione EN/RU viva e sommano al totale; il paragrafo non fissa versioni) e tests/help-banner-strip.test.mjs (il banner resta su disco per GitHub, non sopravvive mai allo strip, la prima riga renderizzata di ogni bundle è la sua intestazione, lo strip resta stretto ed entrambi i consumatori vi passano). Entrambe verificate in rosso contro esattamente questi bug prima del commit. Suite di test: 2837 → 2846.
  • Nessuna modifica a registro, scanner o contratti di route; le fonti restano 85 (80 EN + 5 RU) e ALL_ADAPTERS 80.

[1.227.0] — 2026-08-28

Aggiunto — SEEK Hong Kong (JobsDB), un filtro solo-in-scadenza sui follow-up e i datori di lavoro riservati mostrano l’agenzia che li ha presentati.

Aggiunto

  • JobsDB Hong Kong tramite la sorgente Jobstreet/SEEK esistente. Hong Kong opera con il marchio JobsDB ma poggia sulla stessa piattaforma SEEK dietro lo stesso endpoint v5, quindi non serve una sorgente separata — bastano hk.jobsdb.com nella allowlist degli host e siteKey: HK-Main. Il registro resta invariato: 85 sorgenti (80 EN + 5 RU) e ALL_ADAPTERS 80 — è un nuovo MERCATO su un adapter esistente, non un adapter nuovo.
  • Filtro solo-in-scadenza sulla bacheca della cadenza (#/modes/followup). Una nuova casella restringe la bacheca a ciò su cui agire adesso. La scadenza si legge dall’urgency già calcolata dal followup-cadence.mjs del progetto padre (urgent / overdue), mai dallo status del tracker, che vale applied/responded/interview e non combacerebbe con nulla. Quando non c’è nulla in scadenza, la bacheca indica il follow-up programmato più vicino invece di mostrare un vuoto. La logica vive nel modulo puro public/js/lib/followup-view.js, specchio del followup-view.mjs del padre.
  • I datori di lavoro riservati vengono attribuiti all’agenzia che li ha presentati. Un annuncio con datore di lavoro celato viene registrato come ? nel tracker, con il recruiter nella colonna Via. #/tracker mostrava un ? nudo, così più righe riservate erano indistinguibili e il risolutore dei loghi non aveva nulla su cui agganciarsi. Ora una riga simile si legge «Riservato · tramite <agenzia>», risolve il logo dal nome dell’agenzia ed è rintracciabile con quel nome nella ricerca. Solo presentazione — il campo canonico company non viene mai sovrascritto con il recruiter. Nuovo modulo puro public/js/lib/company-presentation.js, specchio del company-presentation.mjs del padre.

Corretto

  • Jobstreet/SEEK chiamava un endpoint deprecato. La sorgente puntava ancora a chalice-search v4 (/api/chalice-search/v4/search), che SEEK ha ritirato; il padre è migrato a /api/jobsearch/v5/search e web-ui non ha mai seguito. Ora ogni voce Jobstreet/SEEK interroga v5. La forma dell’item v5 è letta nativamente (URL costruita da id, locations[0].label, advertiser.description, salaryLabel), il parametro di proiezione solrFields proprio della v4 sparisce, e una voce il cui portals.yml fissi ancora il percorso v4 morto conserva l’hostname del suo mercato mentre il percorso viene ricostruito su v5 — senza modifiche da parte dell’utente.

Note

  • Parità con career-ops 1.30.0 (HEAD del padre 8d64f656cc46a4; il suo VERSION segna ancora 1.30.0 — release-please è indietro rispetto a main). Non portato, con motivazioni: riempimento della località da JSON-LD in iCIMS — la sorgente iCIMS di web-ui non ha alcun hook enrichDate() (solo pagine di elenco), quindi non c’è alcun percorso da correggere; stderr CLI non fatale (#1974) — il runner di web-ui decide il fallimento solo dal codice di uscita e non ha mai avuto quell’euristica; tracciamento dei percorsi Turbopack — web-ui non ha bundler; correzioni cv-sync-check — rilanciate in sola lettura e fail-soft; sequenzializzazione di Playwright in scan.mjs, risposte Block H, guardie updater/doctor/update-system, il controllo di sintassi ricorsivo, la dashboard in Go e le auto-verifiche aggiunte a jd-skill-gap/verify-cv-facts — superfici solo-CLI che web-ui non invoca, o auto-test senza cambi di comportamento. Suite di test: 2818 → 2837.

[1.226.0] — 2026-08-27

Aggiunto — due sorgenti di lavoro vietnamite: ITviec e CareerViet.

Aggiunto

  • ITviec + CareerViet (Vietnam). Due board vietnamite con host fissato si uniscono allo scanner tramite voci provider: itviec / provider: careerviet: ITviec (itviec.com, tech) e CareerViet (careerviet.vn, ampia copertura). Entrambi sono scraper HTML senza token, con host-pin anti-SSRF, solo-HTTPS, fail-soft per azienda e limiti di pagina. Il registro ora conta 85 sorgenti (80 EN + 5 RU), ALL_ADAPTERS 80 — entrambi compaiono nel filtro Sorgente di #/scan.

Note

  • Parità con career-ops 1.30.0 (parentVersion 1.29.0 → 1.30.0). Non portato (solo CLI/community o già coperto): la Hired Wall, /calibrate, clean-markers, pacchetti di template, doctor, pdf/LaTeX e le CLI eval/tracker/scan che web-ui non invoca; il fix _html-to-text e roleFuzzyMatch c’erano già; il prefisso title-filter stem: è rimandato. Suite di test: 2818.

[1.225.1] — 2026-08-27

Corretto — il banner dei loghi dei provider ora compare nel README e nella guida.

Corretto

  • Il banner della v1.225.0 non compariva su GitHub: il file images/providers.svg autonomo aveva un font-family non valido (virgolette doppie annidate) che GitHub rifiuta, l’<img> usava un src relativo (l’HTML <img> non risolve percorsi relativi su GitHub) e al blocco <p> mancava una riga vuota finale. Ora punta a un images/providers.png renderizzato tramite URL assoluto, con separazione a righe vuote; anche l’SVG è corretto in XML valido.

Note

  • Solo docs; nessun cambiamento di codice o test. La vetrina di cvstart.org e i riquadri dell’app erano già corretti (SVG inline, non questo file). 2784

[1.225.0] — 2026-08-27

Aggiunto — un banner dei loghi dei provider nel README e nella guida.

Aggiunto

  • Banner dei loghi dei provider. Un unico images/providers.svg autonomo — i 18 provider LLM, con il logo reale del brand (simple-icons, CC0) per gli 11 che ne pubblicano uno e un monogramma con il colore del brand per i 7 restanti — apre ora il README (×17) e la guida in-app. (L’app disegna da sé i riquadri con il logo reale; il banner è reso su GitHub e rimosso nell’app dal renderer sicuro per l’XSS.)

Note

  • Solo docs — nessun cambiamento di codice o test; l’SVG è inline (nessuna risorsa remota). Sorgenti di scansione invariate: 83. Suite di test: 2784.

[1.224.0] — 2026-08-26

Modificato — i riquadri dei provider LLM ora mostrano i loghi reali dei brand.

Modificato

  • Loghi reali dei provider. I riquadri del provider — campi chiave API nelle Impostazioni, le righe di Utilizzo, il chip della dashboard, il riepilogo “Attivo” delle Impostazioni, il risultato della ⚡ eval e il banner di benvenuto — ora mostrano il logo reale del brand per gli 11 provider che pubblicano un’icona open source (Anthropic · Gemini · OpenAI · Qwen · OpenRouter · GitHub · DeepSeek · Kimi · MiniMax · Mistral · Ollama; SVG a percorso singolo da simple-icons, CC0). I 7 senza icona pubblicata (Hermes · GLM/Z.ai · Grok · Together · Fireworks · BytePlus Ark · Volcengine Ark) mantengono il monogramma con il colore del brand. Sempre sicuro per la CSP per costruzione: il percorso del logo è una costante statica incorporata — nessuna risorsa remota, nessun innerHTML.

Note

  • Nessun cambiamento di rotta o comportamento; la stessa API ProviderLogo.el(slug). provider-logo.js è cresciuto (i percorsi dei loghi incorporati). Sorgenti di scansione invariate: 83. Suite di test: 2784.

[1.223.0] — 2026-08-26

Corretto — una valutazione ⚡ live riuscita veniva colorata come errore; inoltre installa il coworker OpenWorker direttamente dal suo URL GitHub o .zip.

Corretto

  • Tinta del badge di risultato della ⚡ eval. Una valutazione live riuscita non è più colorata come fallimento. I provider in-process (OpenRouter, DeepSeek, …) non restituiscono un code di sottoprocesso, quindi lo stretto code === 0 era falso e ogni successo appariva rosso (pur mostrando “exit 0”). La tinta ora segue l’esito — solo un sottoprocesso che esce con codice diverso da 0 lo colora di rosso; un risultato live senza codice è badge-ok, e il suffisso “· exit N” appare solo con un vero codice di uscita.

Modificato

  • Installa il coworker OpenWorker dal suo URL GitHub / .zip. La documentazione del coworker ora descrive l’installer a un comando (curl … | bash, idempotente — riusa un career-ops / web-ui esistente) e i tre percorsi di installazione in OpenWorker (URL GitHub · .zip · import di un singolo file) in docs/integrations/openworker.md, la guida in-app §32 (×17) e il README.

Note

  • Chiusa una vecchia lacuna di copertura: la sorgente Recruitee ora ha un test end-to-end del fix dell’angolo tra virgolette di v1.214.2 (un > dentro un attributo di tag non deve trapelare nella descrizione). Sorgenti di scansione invariate: 83. Suite di test: 2783.

[1.222.0] — 2026-08-26

Modificato — i suggerimenti dei campi dei provider estesi (DeepSeek … Volcengine Ark) sono ora localizzati in tutte le 17 lingue.

Modificato

  • Suggerimenti dei campi dei provider localizzati. Ogni config.<slug>Hint degli 11 provider compatibili con OpenAI aggiunti in v1.216.0–v1.217.0 (DeepSeek · GLM/Z.ai · Kimi · MiniMax · Mistral · Grok · Together · Fireworks · Ollama · BytePlus Ark · Volcengine Ark) — i suggerimenti di chiave API, modello e URL base in Impostazioni → Chiavi API — è ora tradotto in tutte le 17 lingue anziché ripiegare sull’inglese. URL di registrazione, id dei modelli e il marcatore ⚡ della valutazione live sono conservati alla lettera.

Note

  • Nessun cambiamento di comportamento — il hintFallback inglese in field-specs.js protegge ancora una chiave mancante, e un nuovo test di contratto verifica che ogni hintKey dei descrittori di campo si risolva in tutte le 17 lingue. Fonti di scansione invariate: 83. Suite di test: 2779.

[1.221.0] — 2026-08-26

Sicurezza — difesa in profondità contro il DNS-rebinding sul percorso di fetch dello scanner.

Sicurezza

  • Il core HTTP dello scanner (fetchJson / fetchText in server/lib/http-json.mjs) ora risolve l’host di ogni sorgente sul percorso di rete reale e rifiuta di connettersi se si risolve a un indirizzo privato, loopback, link-local, CGNAT o di metadati cloud — chiudendo un vettore di DNS-rebinding. Gli host dello scanner sono già ancorati a domini pubblici, quindi è difesa in profondità; il percorso degli URL utente aveva già un ancoraggio di connessione più forte (safe-fetch.mjs). La guardia gira solo sul trasporto fetch reale — un fetchImpl di test iniettato non viene mai risolto.

Note

  • Nessun cambio di comportamento per una scansione sana. Fonti di scansione invariate: 83. Test: 2775.

[1.220.0] — 2026-08-26

Aggiunto — scansiona più categorie di Get on Board da una sola voce.

Aggiunto

  • Una voce getonbrd può ora impostare categories: [programming, operations-management, machine-learning-ai] (o un singolo category:) invece del solo feed programming; gli annunci sono deduplicati per URL tra le categorie, con un limite di 12. Le voci esistenti restano identiche (il default resta programming).

Note

  • Solo server; l’host-pin SSRF (www.getonbrd.com) e redirect:'error' sono invariati, e uno slug di categoria non valido viene rifiutato prima di qualsiasi richiesta. Fonti di scansione invariate: 83. Test: 2771.

[1.219.0] — 2026-08-26

Aggiunto — Torre entra nello scanner e ora ogni etichetta di provider è corretta.

Aggiunto

  • Torre (provider: torre) — il marketplace di talenti pan-latinoamericano di torre.ai, una ricerca pubblica senza autenticazione con ruoli remoti che non arrivano a Greenhouse/Lever/Ashby; una richiesta limitata per voce. Il registro conta ora 83 sorgenti (78 inglesi + 5 russe). Inoltre, monogramma del provider attivo nel banner di benvenuto.

Corretto

  • La scansione a16z Speedrun si fermava troppo presto su una board che ruota (una pagina corta a metà passata veniva presa per l’ultima, troncando a 149 di ~300); ora si fida del total_pages del feed. E il banner di benvenuto risolveva il provider da una mappa di 4 voci (mostrando lo slug grezzo per gli altri 14) — ora usa etichetta+monogramma dei 18; la nota provider nelle Impostazioni passa da 7 a 18.

Note

  • La nuova opzione multi-categoria per voce di Get on Board non è ancora collegata qui (richiede un refactor a livello di sorgente). Test: 2768.

[1.218.0] — 2026-08-26

Aggiunto — badge dei provider (e nomi corretti) ovunque venga mostrato un provider.

Aggiunto

  • Monogrammi dei provider sul chip della dashboard, nel riepilogo “Attivo” delle Impostazioni e nell’intestazione del risultato della ⚡ valutazione.

Corretto

  • Etichette obsolete: tre schermate risolvevano il nome da una mappa di 5 voci (il risultato della valutazione etichettava come “Gemini” ogni provider non-Anthropic); ora usano ProviderStatus.label (18). Il conteggio delle chiavi mostrava “/ 7” → 18. L’elenco di slug del suggerimento LLM_PROVIDER ha aggiunto ark / arkcn in tutte le 17 lingue.

Note

  • Solo client, sicuro per la CSP. Nessuna modifica al server. Fonti di scansione: 82. Test: 2758.

[1.217.1] — 2026-08-26

Rafforzamento dei test — copertura degli endpoint per l’intero catalogo di provider.

Aggiunto

  • Un test run<Provider> parametrizzato che verifica endpoint, modello predefinito e autenticazione Bearer per Kimi, MiniMax, Mistral e Fireworks.

[1.217.0] — 2026-08-26

Aggiunto — Ark si aggiunge all’elenco LLM (18 provider). BytePlus Ark e Volcengine Ark — i modelli Doubao di ByteDance — sono ora provider ⚡ dal vivo, con lo stesso core compatibile con OpenAI e un URL di base per regione.

Aggiunto

  • BytePlus Ark (ARK_API_KEY, internazionale) e Volcengine Ark (ARK_CN_API_KEY, Cina) — Chat Completions compatibile con OpenAI via runOpenAICompatible(). Imposta ARK_MODEL / ARK_CN_MODEL (un nome di modello Doubao o il tuo id endpoint ep-…); ARK_BASE_URL / ARK_CN_BASE_URL cambiano regione. Entrambi si uniscono all’ordine auto (per ultimi), con il monogramma nelle Impostazioni e in Utilizzo. L’interfaccia web copre ora 18 provider dal vivo.

Note

  • Stesso percorso di configurazione attendibile + controllo schema http(s) del resto (mai il validatore SSRF degli URL di lavoro). Fonti di scansione invariate: 82. Test: 2755.

[1.216.0] — 2026-08-26

Aggiunto — altri nove provider LLM, ciascuno con la propria targhetta di marca. Le tue valutazioni ⚡ dal vivo possono ora usare DeepSeek, GLM (Z.ai), Kimi (Moonshot), MiniMax, Mistral, Grok (xAI), Together, Fireworks o un Ollama completamente locale — a una chiave di distanza, ciascuno segnato da un monogramma accanto al suo campo nelle Impostazioni.

Aggiunto

  • Nove provider compatibili con OpenAI. DeepSeek, GLM (Z.ai), Kimi (Moonshot), MiniMax, Mistral, Grok (xAI), Together AI, Fireworks AI e Ollama (completamente locale, senza chiave). Imposta una chiave — o OLLAMA_BASE_URL per Ollama — nelle Impostazioni e si unisce all’ordine auto dopo Hermes; fissane uno con LLM_PROVIDER. Together ospita anche Inkling di Thinking Machines (thinkingmachines/Inkling). L’interfaccia web copre ora 16 provider dal vivo.
  • Monogrammi dei provider. Una piastrella con l’iniziale nel colore del marchio, sicura per la CSP, segna ogni provider accanto al suo campo chiave nelle Impostazioni e nella sua riga della pagina Utilizzo — SVG inline, nessun logo esterno, nulla lascia il tuo dispositivo.

Note

  • Gli URL di base dei provider sono configurazione attendibile che passa per runOpenAICompatible() con un controllo dello schema http(s) (il loopback di Ollama è consentito), mai il validatore SSRF degli URL di lavoro. Fonti di scansione invariate: 82. Test: 2752.

[1.215.0] — 2026-08-26

Aggiunto — esegui tutta la tua ricerca di lavoro da OpenWorker. Un nuovo coworker guida questa pipeline dall’app di coworker IA open source di Andrew Ng, e la guida in-app ora lo copre così l’assistente Chiedi ai docs può accompagnarti.

Aggiunto

  • Coworker OpenWorker. Un coworker senza codice di OpenWorkerFighter90/career-ops-coworker — esegue tutta la ricerca di lavoro (scansione, punteggio, adattamento, tracciamento, follow-up) e può avviare questa dashboard, tutto dal tuo desktop. È una persona in un solo file distribuita in 17 lingue, verificata installabile con il loader di OpenWorker stesso.
  • Guida §32 “Run it from OpenWorker” (tutte le 17 lingue) lo documenta, così l’assistente in-app Chiedi ai docs e il pulsante di aiuto fluttuante possono rispondere. Collegato anche dal footer della landing, dal README, dalla wiki e da docs/integrations/openworker.md.

Note

  • Solo docs + sito — nessuna modifica al codice dell’app. Fonti di scansione invariate a 82. Suite di test: 2742.

[1.214.2] — 2026-08-25

Corretto — la pipeline HTML→testo condivisa (descrizioni Greenhouse / Recruitee) non fa più trapelare attributi di tag nella descrizione quando un valore di attributo contiene un carattere >.

Corretto

  • Le descrizioni HTML con > dentro un attributo vengono ripulite correttamente. Il rimuovi-tag dietro le descrizioni Greenhouse e Recruitee usava un match ingenuo che si fermava al primo >, così un tag come <a title="salary > 100k"> lasciava il testo di attributo finale (100k">) nella descrizione. Ora rispetta i valori di attributo tra virgolette (singole o doppie), rimuove il markup prima di decodificare le entità (così una virgoletta codificata dentro un attributo non diventa un falso delimitatore), e preserva ancora un <> letterale in testo semplice.

Note

  • Suite di test: 2742. Fonti di scansione invariate a 82. Nient’altro di nuovo dal padre stavolta: le altre modifiche sono ritrasmesse in sola lettura (i fix follow-up / weekly-digest / rejection-latency / salary-gap non richiedono modifiche a web-ui) o non sono superfici web-ui (un plugin esterno X/Xquik BYO-key, fix CLI/verify). Le voci opzionali — descrizioni di dettaglio SmartRecruiters, salto aggregatori in detect-reposts, prefissi di titolo stem: / word:, irrobustimento anti-DNS-rebinding — restano in coda.

[1.214.1] — 2026-08-25

Corretto — le descrizioni Ashby sono ora limitate alla stessa lunghezza di Greenhouse e Recruitee, così un filtro di contenuto si comporta identico sulle tre board e una board non può gonfiare la cache di scansione.

Corretto

  • Le descrizioni Ashby ora rispettano lo stesso limite di lunghezza delle altre board. La v1.214.0 alimentava il corpo dell’annuncio Ashby al filtro di contenuto, ma — a differenza di Greenhouse e Recruitee, che limitano a 4000 caratteri — quello di Ashby era mappato diretto senza limite (alcuni 4× più lunghi). Così la stessa parola di content_filter poteva corrispondere su Ashby ma non su Greenhouse solo in base a dove cadeva nel testo, e una board poteva aggiungere centinaia di KB alla cache di scansione caricata da #/scan. Ashby è ora limitato agli stessi 4000 caratteri (il suo testo è già in chiaro, quindi viene troncato, mai ripulito dai tag).

Note

  • Suite di test: 2738. Fonti di scansione invariate a 82.

[1.214.0] — 2026-08-25

Corretto — le descrizioni di Greenhouse, Ashby e Recruitee ora alimentano il filtro di contenuto, così i filtri parole / paese / visto corrispondono davvero a quelle board; e “Associate ” (inclusi i ranghi accademici) non è più classificato erroneamente come entry-level.

Corretto

  • I filtri di contenuto ora funzionano su Greenhouse, Ashby e Recruitee. Queste board portano ora il testo completo di ogni annuncio come sua descrizione, così un content_filter (o un filtro parole paese / visto) corrisponde al corpo dell’offerta invece di lasciar passare la board alla cieca. Ashby e Recruitee forniscono il corpo nel payload di lista gratis; Greenhouse lo faceva già. Tutte e tre passano per un’unica pipeline HTML→testo condivisa.
  • “Associate Director” / “Associate Professor” è senior, non entry-level. Il classificatore di seniority (usato da skip_tiers:) trattava la parola “associate” come marcatore entry-level anche quando prefissa un titolo senior. Ora legge “Associate ” — inclusi i ranghi accademici (Associate Professor, Associate Dean) — come senior, mantenendo le vere varianti junior (Associate Attorney, Associate Editor) come entry-level, e classifica ancora un “Intern, Associate Dean” iniziale come tirocinio.

Note

  • Suite di test: 2736. Fonti di scansione invariate a 82. In coda per dopo: arricchimento opzionale delle descrizioni SmartRecruiters (una richiesta di dettaglio per annuncio), il salto delle board aggregator: true di detect-reposts e i prefissi di titolo a parola ancorata stem: / word: — ciascuno una configurazione opzionale, non un cambio di comportamento predefinito. L’irrobustimento anti-DNS-rebinding resta in coda per la sua release di sicurezza dedicata.

[1.213.0] — 2026-08-22

Aggiunto — MyCareersFuture, la banca del lavoro nazionale di Singapore, come fonte di scansione. Corretto — le offerte Greenhouse ora portano il testo completo perché i filtri di contenuto funzionino, e le offerte Ashby da remoto non restano più nascoste dietro una sede solo-città.

Aggiunto

  • MyCareersFuture (Singapore) (mycareersfuture.gov.sg) — una nuova fonte di scansione senza token per la banca del lavoro pubblica nazionale di Singapore, gestita da Workforce Singapore. Selezionala nel filtro Fonte in #/scan, o aggiungi un’azienda con provider: mycareersfuture e una lista keywords facoltativa (altrimenti usa i ruoli target del tuo profilo, come Job Bank). Legge l’API di ricerca pubblica, con host fissato, senza chiave.

Corretto

  • Le offerte Greenhouse ora sono filtrabili per contenuto. Le board Greenhouse vengono recuperate con il corpo completo dell’annuncio, decodificato in testo semplice come descrizione — così un content_filter (o un filtro per parole paese/visto) che legge la descrizione ora corrisponde davvero alle offerte Greenhouse invece di lasciarle passare alla cieca.
  • Le offerte Ashby da remoto non vengono più scartate da un filtro città. Ashby tiene il modello di lavoro (Remote/Hybrid/Onsite) separato dalla città dell’ufficio, quindi un ruolo totalmente da remoto si leggeva ancora come es. “San Francisco” — e un filtro sede che blocca quella città nascondeva un ruolo accettabile. Ora “Remote” viene aggiunto alla sede quando il ruolo è da remoto, e workplaceType vince su un isRemote obsoleto per non etichettare male un ruolo ibrido ancorato all’ufficio.

Note

  • Fonti di scansione: 82 (77 inglesi + 5 russe). Suite di test: 2724. Un irrobustimento anti-DNS-rebinding (validare l’indirizzo risolto di un host prima di connettersi) è in coda per una release dedicata — richiede un design proprio di web-ui, non un port diretto.

[1.212.1] — 2026-08-21

Corretto — la landing di cvstart.org contava per difetto le fonti di lavoro dello scanner (mostrava 80 e ometteva Job Bank (Canada)); ora torna a combaciare con le 81 dell’app, e la build del sito fallisce rumorosamente se le due divergono.

Corretto

  • Il conteggio “Fonti di lavoro” della landing è di nuovo sincronizzato con l’app. Dopo la v1.212.0, cvstart.org mostrava 80 board e mancava il nuovo chip Job Bank (Canada), mentre app, menu a tendina di scansione e guida elencavano tutti 81. La landing costruisce l’elenco caricando il registro live dello scanner, e una fonte non si è caricata in quella build per come importava una dipendenza YAML — quindi è stata scartata in silenzio. Ora Job Bank carica quella dipendenza in modo pigro, come fa il resto dell’app al momento della scansione, così compare sempre.
  • La build del sito ora rifiuta di pubblicare un conteggio di fonti incoerente. Se il registro enumera meno fonti di quante esistano su disco (la firma di una fonte non caricata), la build fallisce con un messaggio chiaro invece di pubblicare in silenzio il numero sbagliato.

Note

  • Il comportamento dell’app è invariato — lo scanner ha sempre avuto tutte le 81 fonti; solo la landing era interessata. Fonti di scansione: 81 (76 inglesi + 5 russe) — invariato. Suite di test: 2687.

[1.212.0] — 2026-08-21

Aggiunto — Job Bank (Canada), la job board nazionale federale. Rimosso — EchoJobs (il suo feed è ora bloccato da anti-bot). Corretto — le board Consider restituiscono di nuovo risultati e le offerte Lever multi-sede non nascondono più metà delle loro sedi.

Aggiunto

  • Job Bank (Canada) (jobbank.gc.ca) — una nuova fonte di scansione senza token per il servizio nazionale federale per l’impiego del Canada, una board ad alto volume che nessun aggregatore copre bene. Selezionala nel filtro Fonte in #/scan, o aggiungi un’azienda con provider: jobbankca e una lista keywords facoltativa (altrimenti usa i ruoli target del tuo profilo). Legge il feed ATOM pubblico, con host fissato, senza chiave.

Rimosso

  • EchoJobs — ritirata. Il suo feed pubblico è ora dietro protezione anti-bot e non restituisce nulla, quindi tenerla sprecava solo uno slot di scansione.

Corretto

  • Le board Consider restituiscono di nuovo risultati. Consider ora richiede un handshake anonimo (una GET che semina un cookie di sessione + token CSRF) prima di accettare la ricerca; senza, la richiesta veniva rifiutata in silenzio e la board sembrava vuota.
  • Le offerte Lever multi-sede non nascondono più metà delle loro sedi. Lever mette una città primaria in location e il resto in allLocations; leggere solo la primaria faceva sembrare un’offerta aperta a Barcellona E Montevideo come solo-Barcellona (e la faceva scartare per errore da un filtro di sede). Ora entrambe vengono unite.

Note

  • Ritmo tra pagine più delicato (250 ms, da 150) sulle board paginate, per cortesia verso i siti carriera single-host. Fonti di scansione: 81 (76 inglesi + 5 russe) — invariato (Job Bank entra, EchoJobs esce). Suite di test: 2685.

[1.211.0] — 2026-08-19

Aggiunto — Yourator, job board tech taiwanese. Corretto — le entità accentate di titolo/azienda ora si decodificano ovunque, e un’azienda con un accento nel nome non viene più segnalata per errore.

Aggiunto

  • Yourator (yourator.co) — una nuova fonte di scansione senza token per il mercato del lavoro tech e digitale di Taiwan. Selezionala nel filtro Fonte in #/scan, o aggiungi un’azienda con provider: yourator. Legge l’API JSON pubblica (nessuna chiave, nessun browser), percorre ogni pagina della board ed emette il vero link del datore di lavoro di ogni annuncio (il suo ATS) con i parametri di tracciamento rimossi.

Corretto

  • Le entità con nome accentato ora si decodificano ovunque. Il decoder HTML condiviso ha acquisito le lettere Latin-1 (&eacute; → é, &ccedil; → ç, …), quindi una board europea che scrive D&eacute;veloppeur o Fran&ccedil;ais non lascia più quel letterale in un titolo, nel tracker o in un documento generato. (Le maiuscole restano maiuscole — &Eacute; è É, non é — e una ricerca come &constructor; ora si risolve in se stessa.)
  • Un’azienda con un accento nel nome non viene più segnalata per errore per essere sul proprio dominio. “Işık” ora si ripiega in “isik” e corrisponde a isik.com.tr; “Société Générale” corrisponde a societegenerale.com. Il vecchio controllo cancellava le lettere accentate invece di ripiegarle sulla loro base ASCII.

Note

  • Fonti di scansione: 81 (76 inglesi + 5 russe). Suite di test: 2667.

[1.210.1] — 2026-08-19

Corretto — i titoli degli annunci e i nomi azienda di Habr Career con “&” o virgolette non arrivano più corrotti.

Corretto

  • La fonte Habr Career ora decodifica le entità HTML nel titolo e nel nome azienda prima che proseguano. Le card renderizzate lato server arrivano con entità (“Changellenge >>”, “Demand Forecasting & Inventory Optimization”, “ООО "М-ТЕХ"”), quindi una “&” non decodificata falliva in silenzio il tuo stesso filtro del titolo con “&” — lo stesso sintomo che la release precedente ha chiuso su altre cinque board — e i nomi azienda arrivavano corrotti al tracker e ai report. La decodifica delle entità è ora completa su tutte e sei le fonti interessate.

Note

  • Suite di test: 2644.

[1.210.0] — 2026-08-19

Aggiunto — Senjob, la prima job board africana dello scanner (Senegal); corrispondenza dei titoli più precisa su altre cinque board.

Aggiunto

  • Senjob (senjob.com) — una nuova fonte di scansione senza token per il Senegal, la prima board africana dello scanner. Selezionala nel filtro Fonte in #/scan, o aggiungi un’azienda con provider: senjob. Legge l’elenco pubblico via HTTP semplice (nessuna chiave, nessun browser), fissa ogni richiesta a senjob.com e — analizzando HTML — tratta un elenco che di colpo non restituisce nulla come una board rotta (un errore visibile) anziché un paese senza offerte.

Corretto

  • I titoli con “&” non fanno più cadere offerte su cinque board — su beesite, Cornerstone (csod), Hacker News “Who is hiring”, Phenom e TKMS i titoli arrivano con entità HTML, quindi una “&” con escape in un ruolo come “R&D Engineer” falliva la tua stessa parola chiave “r&d” e l’offerta spariva in silenzio (nemmeno un veto “sales & marketing” scattava). Ora i titoli — e le località di Phenom — vengono decodificati prima del filtro.

Note

  • Fonti di scansione: 80 (75 inglesi + 5 russe). Suite di test: 2643.

[1.209.0] — 2026-08-17

Aggiunto — la guida nell’app ora copre come registrare l’esito di una candidatura, e «Chiedi alla guida» può portarti lì.

Aggiunto

  • La guida del Tracker (§11) ha guadagnato una sezione «Registrare un esito» in tutte le 17 lingue, che illustra il pulsante Esito: scegli cos’è successo (rifiutato / offerta / assunto / rifiutata / ghostato / passato al colloquio), visualizza l’anteprima di cosa farà, poi registra — il che annota il risultato, archivia il CV e la lettera che hai inviato e sincronizza lo Stato della riga per te. L’assistente fluttuante «Chiedi alla guida» legge la guida, quindi ora ti indirizza a quel pulsante invece di suggerirti solo di modificare lo Stato a mano.

Note

  • Ogni bundle della guida ora è 31 H2 / 119 H3 (era 118); le guardie di parità sono state alzate. Solo documentazione — nessuna modifica al codice o al comportamento. Suite: 2625.

[1.208.2] — 2026-08-16

Corretto — su un telefono i pulsanti di notifiche e tema non stanno più sopra il campo di ricerca.

Corretto

  • La v1.208.1 ha impedito ai pulsanti della barra superiore di sovrapporsi al titolo della pagina, ma su un telefono stretto — pur non essendo il più stretto — e soprattutto nelle lingue con etichette più lunghe, l’intera barra si stipava ancora in una sola riga, così i pulsanti 🔔 e 🌙 potevano finire sopra il campo di ricerca. Ora i pulsanti d’azione (notifiche, tema, Diagnostica, Apri Scan) scendono sempre su una loro seconda riga a tutta larghezza sul telefono, così il campo di ricerca resta pienamente leggibile e niente si sovrappone.

Note

  • Sul telefono i pulsanti d’azione della barra passano a una seconda riga a tutta larghezza, eliminando la fragile fascia di “riga quasi piena” in cui il layout distribuiva lo spazio negativo residuo come sovrapposizione. Un guard di Playwright riproduce ora il trigger esatto — una lingua con etichette lunghe nella fascia 565–640px — e verifica che i controlli della barra non condividano mai pixel. Suite: 2621.

[1.208.1] — 2026-08-16

Corretto — su un telefono i pulsanti della barra superiore non si sovrappongono più alla pagina.

Corretto

  • La v1.208.0 mandava i pulsanti della barra superiore (Diagnostica, Apri Scan, notifiche, tema) su una seconda riga sugli schermi stretti, ma la barra manteneva un’altezza fissa, così la riga a capo debordava e si posava sul titolo della pagina. Ora la barra cresce per accogliere le sue righe e il contenuto scorre sotto.

Note

  • L’height fissa della barra è diventata una min-height, così cresce con il contenuto a qualsiasi larghezza (il desktop è invariato). Un guard di Playwright verifica ora anche che la barra non debordi sulla pagina. Suite: 2621.

[1.208.0] — 2026-08-16

Corretto — l’app ora sta nello schermo di un telefono: niente più scorrimento laterale.

Corretto

  • Su uno schermo stretto l’intera app scivolava di lato — la barra superiore, le tabelle, gli articoli di aiuto e le schede delle impostazioni sforavano il bordo destro. Ora ogni pagina sta in qualsiasi larghezza: i pulsanti della barra superiore vanno a capo su una seconda riga, tabelle e blocchi di codice larghi scorrono nel proprio riquadro, l’aiuto impila l’indice sopra l’articolo, le righe di pulsanti/schede vanno a capo, e percorsi o URL lunghi si spezzano invece di allungare la pagina.

Note

  • La causa era la classica trappola min-width: auto di flex/grid più un paio di elementi larghi non incapsulati; risolto con min-width: 0 sugli elementi della griglia, overflow-wrap su markdown/titoli, una tabella markdown scorrevole e l’impilamento della griglia di aiuto al breakpoint mobile. Un guard Playwright verifica 0 overflow orizzontale a 375 px sulle rotte principali. tests/playwright-smoke.mjs. Suite: 2621.

[1.207.2] — 2026-08-16

Corretto — i piani IA e i profili di orientamento non vengono più mostrati come un dump di codice grezzo.

Corretto

  • Alcuni modelli avvolgono l’intera risposta in un recinto di codice markdown … . Quando accadeva, il piano di sviluppo e il profilo di orientamento apparivano come un blocco di codice a spaziatura fissa invece di un documento con titoli ed elenchi. Ora il recinto avvolgente viene rimosso — solo quando avvolge l’intera risposta e il linguaggio è esplicitamente markdown/md, così una vera risposta in python/js/``` senza linguaggio resta intatta.

Note

  • Gestito una volta sola nel passo condiviso di pulizia LLM (cleanLlmMarkdown), così ne beneficiano tutte le route IA e i blocchi di codice interni alla risposta avvolta sopravvivono. tests/llm-output.test.mjs (+3). Suite: 2621.

[1.207.1] — 2026-08-16

Corretto — la landing page non deborda più di lato sui telefoni piccoli.

Corretto

  • Su un telefono stretto l’hero — il titolo, la riga introduttiva e il terminale di installazione — poteva essere tagliato sul bordo destro perché un comando di installazione lungo e le colonne del layout non si restringevano allo schermo. Ora stanno in qualsiasi larghezza; il comando di installazione scorre dentro il proprio terminale.

Note

  • È stato inoltre irrobustito un controllo E2E instabile che poteva fallire per un 404 transitorio di una risorsa: ora ignora il rumore di rete benigno (favicon / connessione / risorsa fallita) come i controlli vicini, pur continuando a rilevare veri errori di script. Nessun cambiamento nel comportamento dell’app. Suite: 2618.

[1.207.0] — 2026-08-15

Aggiunto — registra l’esito di una candidatura direttamente dal tracker.

Aggiunto

  • Ogni riga del tracker ottiene un’azione Esito: scegli cosa è successo (rifiutato, offerta ricevuta, assunto, offerta rifiutata, nessuna risposta, passato al colloquio), aggiungi una nota facoltativa, visualizza in anteprima lo stato risultante e registralo. La registrazione archivia gli artefatti del CV e della lettera inviati e sincronizza il tracker allo stato canonico — un’azione deterministica invece della modifica manuale.

Note

  • Nuova POST /api/outcome che inoltra la CLI degli esiti: dryRun:true è un’anteprima in sola lettura (individua la riga, riporta lo stato risultante, non scrive nulla); una chiamata reale lo registra. Sicurezza in scrittura: il tipo di esito è limitato all’insieme noto e ogni campo di testo viene rifiutato se contiene caratteri di controllo prima della chiamata (argomenti come array, spawn — nessuna shell). tests/outcome-route.test.mjs. Suite: 2618.

[1.206.0] — 2026-08-15

Documentazione — la guida in-app ora copre le cinque funzionalità più recenti, in tutte e 17 le lingue.

Aggiunto

  • La guida in-app — e l’assistente «Chiedi alla guida» che risponde basandosi su di essa — ora documenta cinque funzionalità aggiunte di recente: Doctor di configurazione (Impostazioni — controlla CV e profilo per lacune e dati di esempio rimasti), Scopri board ATS (Portali — trova automaticamente il portale carriere di un’azienda), il controllo «ancora aperta?» (Tracciamento — se un annuncio è ancora aperto), il suggerimento «riusare un CV precedente?» (CV Studio — segnala quando un CV già adattato va bene per un nuovo annuncio) e il Registro competenze (Analisi — registrare punteggi di autovalutazione). Cinque nuove sottosezioni, tradotte in tutte e 17 le lingue.

Note

  • La struttura della guida cresce a 31 H2 / 118 H3, con parità garantita in ogni lingua. Documentazione di riferimento aggiornata: docs/architecture/API.md documenta le cinque route di queste funzionalità, e i contatori di route/versione in CLAUDE.md e docs/sdd/CONVENTIONS.md sono aggiornati (36 moduli di route). Suite: 2610.

[1.205.0] — 2026-08-15

Aggiunto — un Registro competenze per annotare i risultati di test/valutazioni.

Aggiunto

  • Un nuovo Registro competenze (Analisi → Registro competenze) permette di annotare un’autovalutazione — azienda, piattaforma, competenza, punteggio % e una nota facoltativa — aggiunta a data/assessments.tsv, con un elenco delle voci precedenti (più recenti prima). Zero token, deterministico; il formato del file è gestito dalla CLI del progetto padre.

Note

  • Nuova GET /api/assessments (inoltra la lista JSON predefinita di assessment-log.mjs; fallimento morbido {available:false}) + POST /api/assessments (scrittura esplicita: campi passati come argomenti array a assessment-log.mjs add). Sicurezza in scrittura: ogni campo testo con un carattere di controllo viene rifiutato (un TAB romperebbe una colonna, un a-capo inietterebbe una riga) → 400 prima di scrivere; punteggio/soglia limitati a 0–100, lunghezze limitate. tests/assessments-route.test.mjs. Suite: 2610.

[1.204.0] — 2026-08-15

Aggiunto — un pannello “Dottore di configurazione” nelle Impostazioni che segnala un CV/profilo incompleto o con dati di esempio.

Aggiunto

  • Impostazioni → Dottore di configurazione ora esegue un controllo a zero token del tuo cv.md e config/profile.yml ed elenca i problemi bloccanti (file/campi mancanti) e gli avvisi (dati di esempio residui, metriche fisse) — così cogli una configurazione incompleta prima che indebolisca scansioni e adattamenti. Sola lettura; ri-esegui con un clic.

Note

  • Nuova rotta di sola lettura GET /api/cv-sync-check che inoltra cv-sync-check.mjs del progetto padre, che stampa testo + un codice di uscita (nessun --json); la rotta effettua un parsing leggero delle sue righe stabili ERROR: / WARN: in {ok, errors[], warnings[]} — a decidere il successo è il banner, non il codice di uscita. Fallimento morbido {available:false} sulle installazioni autonome. tests/cv-sync-check-route.test.mjs. Suite: 2602.

[1.203.0] — 2026-08-15

Aggiunto — un suggerimento “riutilizzare un CV precedente?” in CV Studio.

Aggiunto

  • Quando apri un annuncio salvato in CV Studio, l’app ora lo confronta con i tuoi altri annunci salvati (sovrapposizione di parole deterministica, zero token) e ti dice se il più simile basta per riutilizzare quel CV su misura, riutilizzarlo con modifiche o prepararne uno nuovo — così non riparti da zero per un ruolo già mirato.

Note

  • Nuova rotta di sola lettura GET /api/jds/:name/reuse che inoltra jd-similarity.mjs del progetto padre (sovrapposizione Jaccard + guardia di seniority; JSON {decision, score, reason}) una volta per annuncio precedente (fan-out limitato a 25, vince il migliore); fallimento morbido {available:false} se mancano lo script o gli annunci precedenti. tests/jd-similarity-reuse-route.test.mjs. Suite: 2594.

[1.202.0] — 2026-08-15

Aggiunto — scopri la bacheca ATS di un’azienda da #/portals e inizia a seguirla.

Aggiunto

  • Su #/portals, digita il nome di un’azienda e l’app sonda Greenhouse, Ashby e Lever per la sua bacheca pubblica — zero LLM, nessun browser — e mostra le bacheche che esistono e listano ≥1 offerta. Un clic aggiunge la bacheca scelta alle aziende monitorate dallo scanner. Il sondaggio è di sola lettura; la scrittura su portals.yml avviene solo al clic su Aggiungi.

Note

  • Nuovo server/lib/discover-ats.mjs (sonda di slug con host fisso e charset validato tramite safeGet con DNS fissato, ≤12 sonde/richiesta) + POST /api/portals/discover (sola lettura) e POST /api/portals/track (scrittura esplicita: withFileLock + innesto testuale + guardia di ri-parsing + rinomina atomica; solo host ATS noti, idempotente). Riusa il registro di adattatori dello scanner. i18n ×17. tests/discover-ats-resolver.test.mjs + tests/discover-ats-route.test.mjs. Suite: 2588.

[1.201.0] — 2026-08-15

Corretto — un tracker con intestazioni di colonna localizzate o varianti non appare più vuoto.

Corretto

  • Se il tuo data/applications.md usa intestazioni non inglesi o varianti — spagnolo empresa / puesto / estado / fecha / enlace, o position / stage / link — il tracker le leggeva con le chiavi sbagliate e mostrava colonne Azienda / Ruolo / Stato / Data / Link vuote. Ora queste intestazioni vengono ripiegate sui nomi di campo canonici e il tracker si visualizza correttamente. Un tracker tutto in inglese viene elaborato come prima.

Note

  • Nuova mappa HEADER_ALIASES + una piegatura di normalizzazione in parseApplications (server/lib/parsers.mjs); intestazioni sconosciute o già canoniche passano invariate. tests/tracker-header-aliases.test.mjs. Suite: 2563.

[1.200.0] — 2026-08-15

Aggiunto — un controllo “ancora attiva?” con un clic per gli annunci su ATS nel tuo tracker.

Aggiunto

  • Su #/tracker, una candidatura il cui URL è un annuncio Greenhouse / Lever / Ashby / Workday / SmartRecruiters mostra ora un pulsante “Ancora attiva?”. Un clic interroga il JSON pubblico dell’ATS stesso — zero token, nessun browser — e mostra Attiva / Scaduta / Sconosciuta, per scovare annunci morti senza aprirli uno a uno. Conservativo per scelta: solo un 404/410 netto vale Scaduta; ogni ambiguità resta Sconosciuta (mai una falsa Scaduta).

Note

  • Nuovi server/lib/liveness-core.mjs + liveness-api.mjs e una rotta di sola lettura GET /api/liveness?url= (nessuna scrittura, nessun LLM). Sicuro rispetto a SSRF: l’URL passa per isValidJobUrl, poi l’API dell’ATS è raggiunta solo via safeGet (DNS fissato) con host fisso e segmenti validati. tests/liveness-core.test.mjs + tests/liveness-route.test.mjs. Suite: 2557.

[1.199.0] — 2026-08-15

Corretto — le tabelle larghe ora scorrono lateralmente invece di essere tagliate.

Corretto

  • Nella pagina Scan (e in tutte le altre tabelle — Tracker, Statistiche, Utilizzo, Dashboard) una tabella più larga della finestra veniva tagliata senza barra di scorrimento, lasciando irraggiungibili le ultime colonne. Ora le tabelle larghe mostrano una barra di scorrimento orizzontale quando serve, così ogni colonna resta raggiungibile a qualsiasi larghezza.

Note

  • .table-wrap in public/css/components.css è passato da overflow: hidden a overflow-x: auto (come il contenitore .reports-scroll esistente); il bordo arrotondato è preservato. tests/table-wrap-scroll.test.mjs. Suite: 2540.

[1.198.0] — 2026-08-15

Aggiunto — i ritentativi di scansione ora usano backoff esponenziale, jitter e rispettano il Retry-After di un limitatore di frequenza.

Aggiunto

  • Quando una bacheca di annunci limita la frequenza o fallisce brevemente (HTTP 429 / 5xx) durante una scansione, il ritentativo ora attende con backoff esponenziale + jitter invece di un ritardo breve fisso — una bacheca occupata non viene martellata alla stessa cadenza e i ritentativi concorrenti non si ri-scontrano all’unisono. Un Retry-After della bacheca viene rispettato (ma limitato, così un Retry-After: 86400 ostile non può bloccare l’intera scansione). Gli errori permanenti (404, redirect rifiutati) falliscono ancora subito — invariato.

Note

  • Nuove parseRetryAfterMs() e la pura computeRetryDelayMs() in server/lib/http-json.mjs; fetchJson ora cattura .retryAfter su una risposta non-ok e fetchJsonWithRetry accetta un maxDelayMs opzionale (predefinito 8000). tests/http-json.test.mjs (+9). Suite: 2536.

[1.197.0] — 2026-08-14

Aggiunto — segui una bacheca di annunci di un fondo Getro con la sola careers_url; l’id della collezione si risolve da solo.

Aggiunto

  • Una bacheca Getro seguita (b2venture, Earlybird, Point Nine, …) non ha più bisogno di un getro_collection numerico cercato a mano. Fornisci la careers_url della bacheca stessa e l’id si risolve automaticamente da quella pagina alla prima scansione — un’unica GET sicura contro l’SSRF legge il network.id numerico direttamente dai dati incorporati nella pagina. Un getro_collection esplicito ha ancora la precedenza e salta del tutto il recupero.

Note

  • Nuove httpsCareersUrl(), extractCollectionId() e la resolveCollectionId() asincrona in server/lib/sources/getro.mjs; la pagina della bacheca viene recuperata tramite safeGet (DNS fissato, dimensione limitata), e l’id risolto resta ancorato all’host api.getro.com da assertGetroUrl. L’adattatore ora corrisponde a una voce provider: getro che porta una careers_url https anche senza id. tests/sources-getro.test.mjs (+8). Suite: 2527.

[1.196.0] — 2026-08-14

Corretto (sicurezza) — l’adapter Workday valida un endpoint api in base al suo hostname, non a una sottostringa.

Corretto

  • Un valore api: Workday in portals.yml ora è accettato solo quando il suo hostname è myworkdayjobs.com (o un sottodominio .myworkdayjobs.com). Il vecchio controllo era una corrispondenza di sottostringa, quindi qualsiasi URL che contenesse soltanto la stringa — es. https://example.com/?x=myworkdayjobs.com — passava e sarebbe stato usato come endpoint. Gli endpoint Workday reali non sono interessati. (Segnalato da CodeQL, #443.)

Note

  • Il nuovo isWorkdayApi() analizza l’URL e verifica l’host (server/lib/portals/adapters/workday.mjs). tests/workday-adapter-endpoint.test.mjs (+1). Suite: 2522.

[1.195.0] — 2026-08-14

Prestazioni (scanner) — il rilevamento dei repost resta veloce su cronologie di scansione grandi.

Prestazioni

  • Il rilevamento di annunci duplicati non degrada più a O(N²) su un grande scan-history.tsv. Il raggruppamento dei titoli per azienda era un ciclo annidato che pagava un roleFuzzyMatch completo per ogni coppia; ora è un indice invertito — raggruppa le righe per titolo esatto in una passata, poi fa il match fuzzy solo tra bucket DISTINTI che condividono un token discriminante (non di base). L’output è identico — gli stessi cluster di repost — dimostrato da un test differenziale contro il vecchio algoritmo su 200+ cronologie casuali.

Note

  • groupRowsByTitle in server/lib/detect-reposts.mjs (esportato per il test differenziale). tests/detect-reposts-grouping.test.mjs (+2). Suite: 2521.

[1.194.0] — 2026-08-14

Corretto (scanner) — le pagine carriere Workday con URL a segmento singolo ora vengono scansionate correttamente.

Corretto

  • L’adapter Workday ora analizza gli URL carriere il cui percorso è un singolo segmento — es. https://parsons.wd5.myworkdayjobs.com/Search, .../KBR_Careers, .../Careers. Prima il sito ripiegava su External, l’adapter colpiva l’endpoint CXS sbagliato e una sonda poteva sembrare sana senza restituire nulla. Ora prende il primo segmento non vuoto del percorso come sito (scartando un prefisso di lingua come en-US); il caso documentato /en-US/External è invariato. (Segnalato in #255.)

Note

  • Analisi strutturale del percorso in server/lib/portals/adapters/workday.mjs. tests/workday-adapter-endpoint.test.mjs (+7). Suite: 2519.

[1.193.0] — 2026-08-14

Aggiunto (statistiche) — una scheda “Silenzio dopo il colloquio” che fa emergere i colloqui da sollecitare.

Aggiunto

  • Una scheda Silenzio dopo il colloquio in #/stats: colloqui rimasti in silenzio oltre una finestra di cortesia (30 giorni di default), unendo i tuoi colloqui attivi e il tracker — con da quanto ciascuno è in silenzio, la data dell’ultimo colloquio e il motivo. Un elenco gentile di sollecito/chiusura; solo suggerimenti, mai un’affermazione di rifiuto. Senza token.

Note

  • Nuova rotta GET /api/stats/rejection-latency (fail-soft {available:false}). tests/stats-rejection-latency-route.test.mjs (+2). +10 chiavi i18n ×17; help-hint di #/stats da 7→8 schede. Suite: 2510.

[1.192.0] — 2026-08-14

Aggiunto (cv-studio) — un controllo “Verifica i fatti del tuo CV” che scova numeri che non hai mai avuto.

Aggiunto

  • Una scheda Verifica i fatti del tuo CV in #/cv-studio: incolla un CV o una lettera su misura e verifica ogni metrica e fatto rispetto al tuo CV, profilo e two-pager reali. Ottieni un verdetto pass / warn / block più le metriche inventate, i fatti non supportati e le frasi vietate / di avviso esatte. Senza LLM; non viene scritto nulla.

Note

  • Nuova rotta POST /api/cv-studio/verify-facts: scrive il testo in un file temporaneo usa-e-getta ed esegue verify-cv-facts.mjs, fidandosi del verdetto JSON anche se lo script esce con 1 su un block. tests/cv-studio-verify-facts-route.test.mjs (+4). +15 chiavi i18n ×17. Suite: 2508.

[1.191.0] — 2026-08-14

Aggiunto (statistiche) — una scheda “Cosa imparare dopo” che ordina le competenze da imparare prima.

Aggiunto

  • Una scheda Cosa imparare dopo in #/stats: un riepilogo su tutto il tracker — le competenze mancanti che più spesso hanno affondato una bassa compatibilità, pesate (per 5−punteggio di compatibilità su ogni report) e in livelli Critical / High / Medium — più quelle già coperte dal tuo CV/profilo. Sola lettura, solo suggerimenti, senza token.

Note

  • Nuova rotta GET /api/stats/upskill (campo { error } quando i dati sono pochi; fail-soft {available:false}). tests/stats-upskill-route.test.mjs (+3). +15 chiavi i18n ×17. Suite: 2504.

[1.190.0] — 2026-08-14

Aggiunto (tracker) — un pannello “Cronologia azienda” che ti dice quali aziende ti rispondono davvero.

Aggiunto

  • Una scheda Cronologia azienda su #/tracker: scegli un’azienda e ottieni prove in sola lettura — quanto è stata reattiva con te (in silenzio con te / misto / ha già risposto) e se lo stesso ruolo viene ripubblicato — unendo tracker, follow-up e cronologia di scansione. Nessun token; lo scanner non viene mai chiamato.

Note

  • Nuova rotta GET /api/stats/company-history[?company=] (fail-soft {available:false}). tests/stats-company-history-route.test.mjs (+3). +18 chiavi i18n ×17. Suite: 2501.

[1.189.0] — 2026-08-14

Corretto (scanner) — i livelli di anzianità scritti in numeri romani ora contano anche nei titoli non latini.

Corretto

  • Il classificatore di livello dietro skip_tiers ora legge un suffisso di livello in numeri romani (I / II / III / IV / V) dopo la parola del ruolo in qualsiasi scrittura — “Инженер III”, “エンジニア I”, “Ingénieur IV” — non solo dopo parole ASCII. Prima, un numero di livello dopo una parola non latina veniva ignorato e l’offerta cadeva su mid, quindi skip_tiers: [senior] o [entry] le mancava.

Note

  • Lookbehind indipendente dalla scrittura in server/lib/classify-tier.mjs; rimosso un matcher Sr. duplicato morto. tests/classify-tier.test.mjs (+1). Suite: 2498.

[1.188.0] — 2026-08-14

Corretto (UI) — i pulsanti d’azione principali non sono più attaccati al sottotitolo della pagina.

Corretto

  • La riga di azione / controllo principale in Riepilogo settimanale dei colloqui, Aziende finanziate, Portali, Piano di carriera e Orientamento professionale ora ha un margine superiore adeguato, così il pulsante respira sotto il sottotitolo invece di appoggiarvisi.

Note

  • Guardia di regressione tests/lead-row-top-margin.test.mjs (+5). Suite: 2497.

[1.187.0] — 2026-08-14

Corretto (scanner) — l’impostazione skip_tiers funziona di nuovo: le offerte che hai chiesto di saltare per seniority vengono scartate.

Corretto

  • Un elenco skip_tiers: in portals.yml (es. skip_tiers: [intern, entry]) è ora rispettato dalla scansione. Il titolo di ogni offerta è classificato in un livello (intern / entry / mid / senior) e scartato se il livello è nel tuo elenco. Prima la scansione applicava i filtri titolo / località / contenuto / affidabilità ma senza filtro di livello, quindi skip_tiers veniva ignorato in silenzio. I titoli senza parola di livello ricadono su mid (quindi skip_tiers: [mid] scarta anche la maggior parte delle offerte comuni), e il classificatore legge la parola di livello PIÙ A SINISTRA.

Note

  • Nuovo modulo puro server/lib/classify-tier.mjs (classifyTier + buildTierFilter), collegato alle catene di filtri degli scanner EN e RU. tests/classify-tier.test.mjs (+7). Suite: 2492.

[1.186.0] — 2026-08-14

Aggiunto (CV Studio) — un pannello “Divario di competenze”: quali competenze richieste da un’offerta il tuo CV nomina, implica o gli mancano.

Aggiunto

  • Un nuovo pannello Divario di competenze in CV Studio. Scegli una descrizione di lavoro salvata e classifica ogni competenza richiesta in nominata nel tuo CV, implicita nel tuo CV o mancante — confronto di parole senza LLM, nulla viene scritto. Una nota di bassa affidabilità appare quando l’offerta non aveva una sezione requisiti chiara.

Note

  • Nuovo GET /api/jds/:name/skill-gap (il nome dell’offerta è sanificato e confermato sotto jds/ prima di diventare un argomento; fallback morbido a {available:false} senza lo script). +13 chiavi i18n ×17. Test: tests/jds-skill-gap-route.test.mjs (+4, incl. rifiuto del path-traversal). Suite: 2485.

[1.185.0] — 2026-08-14

Aggiunto (statistiche) — una scheda “Imbuto e velocità”: come il tuo imbuto si confronta col mercato e quanto velocemente avanzi tra le fasi.

Aggiunto

  • Una nuova scheda Imbuto e velocità in Statistiche mostra i tuoi tassi di risposta e colloquio accanto agli intervalli di riferimento di mercato (con le avvertenze su campione piccolo e bias di selezione), una lista d’attesa delle candidature in corso oltre la finestra tipica di prima risposta, e i giorni mediani per fase (Candidato → Risposto → Colloquio → Offerta) — le righe lente sono censurate a destra per non distorcere le mediane. Sola lettura e zero token; legge solo il tuo tracker.

Note

  • Nuovo GET /api/stats/funnel (fallback morbido a {available:false} senza lo script). +18 chiavi i18n ×17. Test: tests/stats-funnel-route.test.mjs (+2). Suite: 2481.

[1.184.0] — 2026-08-14

Corretto (UI) — le tessere di azione rapida della Dashboard ora si allineano in una griglia uniforme.

Corretto

  • Nella Dashboard (Centro di comando) un gruppo di 3 tessere veniva reso più largo di uno da 4, lasciando le sezioni con un bordo destro irregolare. Ogni gruppo usa ora colonne di uguale larghezza (4 su schermo ampio, fino a 3 / 2 / 1 al restringersi della finestra), così tutte le tessere hanno la stessa dimensione e i loro bordi destri si allineano.

Note

  • Solo CSS (.qa-grid: repeat(N, minmax(0,1fr)) fisso invece di auto-fill). Protetto da tests/dashboard-grid-align.test.mjs (+2). Suite: 2479.

[1.183.0] — 2026-08-14

Aggiunto (scanner) — rilevamento duplicati più intelligente: la stessa offerta ripubblicata con un link di tracciamento non compare più due volte.

Aggiunto

  • Lo scanner ora riconosce un’offerta tramite una chiave URL canonica, così la stessa offerta ripubblicata con un parametro di tracciamento (?utm_…, gclid, …), su http vs https, o con una barra finale / #frammento è trattata come l’unica offerta che è — nessuna riga duplicata nei risultati o nel pipeline e nessuna valutazione sprecata su un’offerta già vista. Offerte davvero diverse (un id funzionale mantenuto come gh_jid) contano ancora separatamente.

Note

  • Nuovo server/lib/url-key.mjs, collegato al dedup di entrambi gli scanner e allo scrittore del pipeline. Normalizza volutamente poco — non fonde mai due offerte distinte. Test: tests/url-key.test.mjs (+5), tests/parsers.test.mjs (+1). Suite: 2477 (+6).

[1.182.0] — 2026-08-14

Corretto (scanner) — le fasce salariali ora si leggono uguali in ogni lingua.

Corretto

  • Gli importi salariali nelle righe di scan e tracker usano i simboli neutri e (es. ≥ 120000 EUR, ≤ 90000) invece delle parole inglesi “from” / “up to”, che trapelavano non tradotte nelle interfacce non inglesi. Vale per ogni bacheca con fascia a un solo lato (Getro, Remotli, Manfred, Agentic Jobs, JustJoin, Jobicy); le fasce a due lati (100000–150000 USD) erano già neutre.

Note

  • Solo visualizzazione — il filtro salari del client analizza i numeri a prescindere dal prefisso, quindi il filtraggio non cambia. Suite: 2471.

[1.181.0] — 2026-08-14

Aggiunto (scanner) — le bacheche Getro ora mostrano stipendio, tutte le località e posizioni da remoto.

Aggiunto

  • Lo scanner Getro (bacheche delle reti di talenti dei fondi) ora mostra uno stipendio su ogni posizione (fascia annuale + valuta), elenca tutte le località invece della sola prima e contrassegna le posizioni da remoto. Una posizione Getro nel tuo scan e tracker porta ora lo stesso dettaglio di stipendio + località delle altre bacheche.

Note

  • Solo scanner; nessuna nuova dipendenza, nessun cambiamento a route / CSP / SSRF. Test: tests/sources-getro.test.mjs (+5). Suite: 2470 (+5).

[1.180.0] — 2026-08-14

Corretto (MEDIO, report) — l’elenco #/reports è ora una tabella e viene recuperato un punteggio reale che un segnaposto di Machine Summary nascondeva.

Corretto

  • L’elenco #/reports è una tabella (Report · Data · Legittimità · Punteggio), non una griglia di 4 schede. Un lungo chip “Punteggio non rilevato” comprimeva la colonna del titolo quasi a zero, e l’overflow-wrap: anywhere del titolo della scheda spezzava il nome del report un carattere alla volta. Ora ogni campo ha la propria colonna, la cella del nome va a capo per parole e la tabella scorre orizzontalmente su viewport stretti (nuovo contenitore .reports-scroll). Nuova chiave i18n rep.colReport ×17.
  • Un punteggio reale nel corpo (**Итоговый балл:** 1.8 / 5) non è più nascosto da un segnaposto di Machine Summary (score: —). Quando il blocco ## Machine Summary portava un punteggio non numerico o fuori intervallo, occupava lo slot del punteggio analizzato e bloccava il fallback forma-valore in grassetto, così il report mostrava “Punteggio non rilevato” nonostante un X / 5 reale nel corpo. parseReportHeader ora recupera la forma-valore dal corpo quando non sopravvive alcun numero utilizzabile (passo 4.5).

Note

  • Solo client + parser; nessun cambiamento a route / CSP / SSRF / scrittura sul padre. Test: tests/reports-table.test.mjs (+5), tests/report-header-locale.test.mjs (+2). Suite: 2465 (+7).

[1.179.0] — 2026-08-13

Modificato (LOW, scanner) — 20 decoder di entità HTML duplicati consolidati sul modulo condiviso (follow-up di parità, chiude la worklist).

Modificato

  • 20 sorgenti di scansione avevano ciascuna il proprio decodeEntities/decodeXmlEntities (+ un helper fromCodePoint) — copie che erano derivate (tre potevano sollevare un RangeError, corretto in v1.172.0; altre ammettevano NUL/C0 o interpretavano male &#1a2;). Ora passano tutte per l’unico server/lib/html-entities.mjs (decoder sicuro secondo Char di XML 1.0), rimuovendo ~237 righe di duplicazione. Le 8 sorgenti in stile RSS hanno guadagnato la decodifica di &nbsp; (prima gestivano solo 5 entità); la doppia decodifica voluta di cryptocurrencyjobs è preservata tramite un alias. hh mantiene il proprio decoder (gestisce &mdash;/&ndash;, fuori dalle 6 condivise). Un nuovo test di guardia fallisce se una sorgente ricrea un decoder locale.

Note

  • Refactoring che preserva il comportamento; nessun cambiamento a route / CSP / SSRF / scrittura sul padre. Test: tests/decoder-consolidation.test.mjs (+2). Suite: 2458 (+2).

[1.178.0] — 2026-08-13

Corretto (LOW, parità col padre) — due costanti obsolete allineate al padre (PARENT-SYNC GAP #4 + #5).

Corretto

  • User-Agent del browser (GAP #4)BROWSER_LIKE_USER_AGENT (inviato da workable/workday/oraclecloud/a16z/eightfold per superare i gate WAF/bot) portato da Chrome 131 a 151, allineando il user-agent.mjs del padre; una versione vecchia è più spesso bloccata. Protetto da un test Chrome major ≥ 151.
  • FALLBACK degli stati del tracker (GAP #5) — il FALLBACK di ultima istanza in states.mjs (usato solo quando il templates/states.yml live è illeggibile — clone nuovo / root isolata in CI) ha guadagnato gli alias di stato turchi del padre (#2615): değerlendirildi, başvuruldu, yanıt verildi, mülakat, teklif, reddedildi, iptal edildi, uygun değil, kabul edildi/işe alındı. In produzione il file live li forniva già.

Note

  • Solo due costanti; nessun cambiamento a route / CSP / SSRF / scrittura sul padre. Test: tests/http-json.test.mjs (+1) + tests/states.test.mjs (+1). Suite: 2456 (+2).

[1.177.0] — 2026-08-13

Corretto (MEDIUM, scanner) — csod (Cornerstone) restituiva 0 offerte sui tenant che proteggono l’API di ricerca con cookie di sessione (parent #2769, PARENT-SYNC GAP #1).

Corretto

  • Alcuni tenant Cornerstone impostano cookie di sessione sulla home del sito carriere e rispondono 401 CSOD Unauthorized all’API di ricerca se quei cookie non tornano insieme al token bearer anonimo. sources/csod.mjs ora legge il bootstrap tramite un nuovo helper fetchResponse, costruisce un header Cookie dai suoi valori Set-Cookie (cookieHeaderFrom — solo nome=valore, semantica jar) e lo ripete sul POST di ricerca. Solo stessa origine (host fissato + redirect:'error'), quindi i cookie di sessione non raggiungono mai terze parti; un tenant senza cookie si comporta come prima.

Note

  • Nuovo server/lib/http-json.mjs::fetchResponse (additivo; sorgenti esistenti intatte). Nessun cambiamento a route / CSP / SSRF / scrittura sul padre. Test: tests/sources-parity-v1118a.test.mjs (+1). Suite: 2454 (+1).

[1.176.0] — 2026-08-13

Corretto (MEDIUM, report) — un punteggio sotto un’etichetta in grassetto che la tabella RU non elenca mostrava ancora “Score not detected” (FIND-5).

Corretto

  • Due report RU scrivevano il punteggio come **Итоговый балл:** 1.8 / 5 / **Скор:** 1.8 / 5 — etichette in grassetto che REPORT_LABELS.ru non enumera (conosce solo “Оценка”/“Балл”), quindi il punteggio restava non analizzato. Invece di ampliare l’elenco di sinonimi, parseReportHeader ora ripiega sulla forma del valore: una frazione sulla rubrica /5 sotto QUALSIASI etichetta in grassetto. È indipendente dalla lingua, immune a un’intestazione (niente **, niente valore /5) e rifiuta una data come 5/5/2026 (lookahead negativo sul denominatore).

Note

  • Solo parser del server; nessun cambiamento a route / CSP / SSRF / scrittura sul padre. Test: tests/report-header-locale.test.mjs (+2). Suite: 2453 (+2).

[1.175.0] — 2026-08-13

Corretto (LOW, irrobustimento) — una guardia di regressione per la descrizione SEO di FIND-3 + uno strip di legittimità sicuro ai valori nulli (follow-up dell’AI-review).

Corretto

  • Guardia di parità della descrizione SEO — la correzione di v1.174.0 che ha sostituito un “~55” fisso nel meta.desc di ogni lingua con un placeholder {adapters} derivato dal registro non aveva test, quindi poteva regredire in silenzio alla prossima modifica di una locale. Il nuovo tests/site-meta-desc-parity.test.mjs (isolato in CI) fallisce se uno dei 17 site/src/i18n/*.json perde il placeholder o rifissa un conteggio, o se Landing.astro smette di interpolarlo nelle tre metadescrizioni.
  • Strip di legittimità sicuro ai nullistripEmphasis restituisce '' per un input nullish invece della stringa “undefined” (i campi sono inizializzati a stringa, quindi è difesa in profondità).

Note

  • Test + una guardia di una riga nel parser; nessun cambiamento a route / CSP / SSRF / scrittura sul padre. Test: tests/site-meta-desc-parity.test.mjs (+3). Suite: 2451 (+3).

[1.174.0] — 2026-08-13

Corretto (HIGH, report) — i report localizzati mostravano “Score not detected”; la descrizione SEO era obsoleta.

Corretto

  • Parsing del punteggio (FIND-1) — un report non inglese il cui H1 contiene la parola dell’etichetta del punteggio (# Оценка вакансии: <titolo>) non scambia più quel titolo per il punteggio. parseReportHeader ora si ancora all’etichetta in grassetto localizzata (**Оценка:** 1.5 / 5), salta le righe di intestazione e richiede l’etichetta adiacente ai due punti — così i report RU che mostravano “Score not detected” espongono il punteggio reale.
  • Chip di legittimità (FIND-2) — l’enfasi Markdown viene rimossa dal valore, così il chip mostra “High Confidence” invece di “** High Confidence”.
  • Overflow del punteggio — una riga di punteggio con testo di stato in coda (“1.8, Status: Evaluated, …”) viene compattata al solo punteggio; .score-pill ottiene un limite no-wrap/overflow e la colonna del titolo può restringersi, così un chip colorato non esce mai dal bordo della card.
  • Descrizione SEO (FIND-3) — le descrizioni meta / OG / Twitter di cvstart.org (tutte le 17 lingue) fissavano “Scan ~55 job boards” mentre il corpo contava il registro reale (“~75”). Ora la descrizione interpola il conteggio derivato dal registro, così non deriva più.

Note

  • Parser del server + render/CSS del client + i18n del sito; nessun cambiamento a route / CSP / SSRF / scrittura sul padre. Test: tests/report-header-locale.test.mjs (+4). Suite: 2448 (+4).

[1.173.0] — 2026-08-13

Aggiunto (LOW, configurazione) — Hermes entra nell’elenco delle CLI di IA rilevate (parità con career-ops).

Aggiunto

  • La scheda #/config → “Strumenti CLI di IA” ora rileva Hermes (Nous Research), il runtime di agente appena supportato dal progetto padre (binario hermes). L’elenco fisso in server/lib/routes/cli-detect.mjs cresce da 10 a 11 strumenti; il rilevamento resta una scansione di PATH in sola lettura (nessun binario viene mai eseguito).

Note

  • Nessun cambiamento a i18n / route / CSP / SSRF / scrittura sul padre; l’elenco è fisso, mai un input. Suite: 2444 (il canary di cli-detect aggiornato da 10 a 11).

[1.172.0] — 2026-08-13

Corretto (MEDIUM, scanner) — un’entità HTML malformata poteva mandare in crash una sorgente di scansione (parità con career-ops #2150).

Corretto

  • Le sorgenti oraclecloud, gem e dassault decodificavano le entità HTML numeriche con un debole controllo Number.isFinite prima di String.fromCodePoint — un riferimento superiore a 0x10FFFF (es. &#99999999; da un feed malformato o ostile) sollevava un RangeError non catturato e interrompeva l’intero parsing di quella sorgente. Un modulo condiviso server/lib/html-entities.mjs (che rispecchia il _html-entities.mjs del progetto padre) ora limita i riferimenti numerici all’insieme Char di XML 1.0 §2.2, così String.fromCodePoint non può mai sollevare eccezioni, e distingue esadecimale e decimale separatamente affinché &#1a2; non venga più interpretato male. Le tre sorgenti lo importano.

Note

  • Nessun cambiamento per i feed validi; nessun cambiamento a JS / i18n / route / CSP / SSRF / scrittura sul padre. Il consolidamento delle ~20 copie del decoder rimaste nelle sorgenti è tracciato in qa/PARENT-SYNC-WORKLIST-v1.26.0.md.
  • Test: tests/html-entities.test.mjs (+7). Suite: 2444 (+7).

[1.171.0] — 2026-08-13

Modificato (BASSA, design-system) — token di scala tipografica + livelli z-index (D-4, primo passo). Dimensioni e impilamento erano letterali per componente.

Modificato

  • Livelli z-index — introdotti token --z-* (--z-topbar--z-skiplink) e migrato ogni letterale z-index. Valori preservati, impilamento identico; un nuovo canarino vieta nuovi numeri magici.
  • Scala tipografica — rampa --font-size-* (xs 112xl 28, base = Inter 15px); migrate le dimensioni centrali (nessun cambiamento visivo). I valori fuori rampa migrano in modo incrementale (docs/UX-ROADMAP.md).

Note

  • Solo token CSS; nessuna modifica a comportamento/JS/i18n/route/CSP/SSRF/scrittura. Nessun cambiamento di pixel. tests/design-tokens-scale.test.mjs (+3). Suite: 2437 (+3).

[1.170.0] — 2026-08-13

Aggiunto (BASSA) — suggerimenti ETA onesti sulle lunghe generazioni IA (P4-ETA). Le generazioni pesanti (piano di carriera ~40 s, orientamento / mercato / networking ~30 s, two-pager ~20 s) mostravano solo “Generazione…” senza percezione della durata.

Aggiunto

  • Accanto a ogni pulsante di generazione lunga compare ora un suggerimento attenuato ⏱ ~Ns (come l’ETA di #/auto). Stile .eta-hint condiviso + due chiavi generiche (common.eta ~{n}s, common.etaTitle).

Note

  • Solo client; nessuna modifica a route/CSP/SSRF/scrittura. +2 chiavi i18n ×17 (snapshot 1219 → 1221). tests/generation-eta-hint.test.mjs (+2). Suite: 2434 (+2).

[1.169.0] — 2026-08-13

Aggiunto (BASSA) — anteprima PDF inline (D-5). GET /api/output/pdfs/:name forzava Content-Disposition: attachment, quindi anche il pulsante “Apri” su #/cv scaricava invece di mostrare.

Aggiunto

  • ?inline=1 serve lo STESSO file sanificato con Content-Disposition: inline, per un’👁 Anteprima in una nuova scheda; il default resta il download. Nessuna nuova route; stesse protezioni sul nome.
  • Il primo pulsante della lista PDF su #/cv è ora 👁 Anteprima accanto a ⬇ Scarica. cv.openPdf “Apri” → “Anteprima” ×17.

Note

  • Nessuna modifica CSP/SSRF — stesso sanitizePathName. Una chiave i18n riformulata ×17 (snapshot 1219). tests/output-pdfs.test.mjs (+3). Suite: 2432 (+3).

[1.168.0] — 2026-08-13

Corretto (BASSA, a11y) — le righe delle checkbox rispettano ora il minimo 24×24 di WCAG 2.5.8 (D-2). Le etichette di checkbox/radio su #/scan, #/config, #/evaluate e #/cv-studio stavano in una banda di ~22 px.

Corretto

  • Una regola circoscritta label:has(> input[type="checkbox"/"radio"]) { min-height: 24px } garantisce una banda ≥24 px. Solo min-height — le etichette sono già flex, nulla si sposta; .apply-checklist (32 px) era già conforme.

Note

  • Solo CSS; nessuna modifica a comportamento/JS/i18n/route/CSP/SSRF/scrittura. tests/checkbox-target-size.test.mjs (+1). Suite: 2429 (+1).

[1.167.0] — 2026-08-13

Corretto (BASSA, design-system) — le superfici rialzate ora si distinguono dai filetti (D-3). I token --panel-2 / --surface-elev1 si risolvevano in --slate, lo stesso valore dei filetti --line / --border, senza separazione visiva.

Corretto

  • Un token dedicato e consapevole del tema --elev (#eef1f6 chiaro / #1e232e scuro, distinto da --slate in entrambi i temi) sostiene ora le superfici rialzate; i filetti restano su --slate. Gli altri rilievi (D-2, D-4, D-5, P4-ETA) sono backlog in docs/UX-ROADMAP.md.

Note

  • Solo token CSS; nessuna modifica a comportamento/JS/i18n/route/CSP/SSRF/scrittura. tests/elevation-token.test.mjs (+2). Suite: 2428 (+2).

[1.166.0] — 2026-08-13

Corretto (BASSA) — la terminologia della rubrica ora rispecchia i docs canonici. career-ops.org/docs descrive “cinque dimensioni più un punteggio globale olistico”, ma il web-ui, cvstart.org e il wiki dicevano “rubrica a sei dimensioni” (5 + 1 = 6, ma il lessico divergeva).

Corretto

  • Adottata la formulazione dei docs — “cinque dimensioni più un punteggio globale olistico” — in modo coerente in README ×17, nel sito cvstart.org ×17, nella guida ×17, in docs/career-ops-canonical.md e nel wiki (Home ×17 + Features).

Note

  • Solo docs/marketing; nessuna modifica a codice/chiave i18n/route/CSP/SSRF/scrittura. tests/rubric-terminology.test.mjs (+2). Suite: 2426 (+2).

[1.165.0] — 2026-08-13

Corretto (BASSA) — il termine “Two-pager” è ora coerente all’interno di ogni lingua. In arabo la barra laterale mostrava il latino “Two-pager” mentre l’<h1> era completamente localizzato — l’unica stringa latina in una navigazione RTL altrimenti speculare.

Corretto

  • Decisione applicata: per lingua, nav.twoPager e twoPager.title concordano sul termine (entrambi in latino o entrambi localizzati). Solo l’arabo era diviso; la sua etichetta di navigazione è ora localizzata (“الصفحتان”). Un nuovo canarino fallisce se una lingua li separa di nuovo.

Note

  • Solo testo; nessuna modifica a route/CSP/SSRF/scrittura. Un valore i18n modificato (ar); nessuna nuova chiave (snapshot 1219). tests/two-pager-term-consistency.test.mjs (+2). Suite: 2424 (+2).

[1.164.0] — 2026-08-13

Corretto (BASSA) — il placeholder della ricerca non trabocca più in nessuna lingua. “Find a company, role or URL…” veniva troncato (nowrap) quando la barra si restringeva; la metà “…or URL” non era mai visibile.

Corretto

  • top.search (×17) è ora il breve “Cerca o incolla una URL” (≤24 caratteri in ogni lingua), sta anche in una barra stretta e mantiene il riferimento all’URL. Il fallback in index.html corrisponde; l’aria-label conserva il dettaglio completo.

Note

  • Solo testo; nessuna modifica a route/CSP/SSRF/scrittura. Una chiave i18n riformulata ×17 (nessuna nuova; snapshot 1219). tests/search-placeholder-fit.test.mjs (+2). Suite: 2422 (+2).

[1.163.0] — 2026-08-13

Corretto (BASSA) — l’assistente in-app “Chiedi ai docs” ora copre l’esportazione di un report in PDF. Rispondeva che la guida non lo copriva, benché #/reports/:slug abbia un pulsante 📄 Generate PDF funzionante.

Corretto

  • Aggiunto un H3 “Esportare un report in PDF” sotto §10 Report in tutti i 17 bundle di aiuto (dov’è il pulsante, il file finisce in output/*.pdf, richiede Playwright, rivedere prima di inviare). Il recupero dell’assistente ora mostra la sezione Report.

Note

  • Solo docs/aiuto; nessuna modifica a codice/route/CSP/SSRF/scrittura. Soglia aiuto 112 → 113 H3 (31 H2 invariato). tests/help-reports-pdf-section.test.mjs (+2). Suite: 2420 (+2).

[1.162.0] — 2026-08-13

Corretto (MEDIA) — il “?” di aiuto è ora un bersaglio di ≥24×24 (WCAG 2.5.8). .help-hint misurava 18×18 px con padding:0, sotto il minimo, su ogni intestazione.

Corretto

  • Il box .help-hint è ora 24×24 (il bersaglio misurabile) mentre l’anello visibile resta a 18px — disegnato da un ::before centrato, quindi il glifo e la linea di base dell’<h1> sono invariati. Gli stati hover/attivo/focus seguono l’anello; margine 6→3px per mantenere lo spazio.

Note

  • Solo CSS; nessuna modifica a JS/i18n/route/CSP/SSRF/scrittura. tests/help-hint-target-size.test.mjs (+2). Suite: 2418 (+2).

[1.161.0] — 2026-08-13

Corretto (MEDIA) — #/reports mostra un chip “Punteggio non rilevato” invece di spazio vuoto. Dopo il parser multilingua di v1.159.0, un report senza punteggio analizzabile mostrava un’area vuota, indistinguibile da un errore.

Corretto

  • La cella del punteggio ora si ramifica: con punteggio → pillola di tono; senza → chip .score-muted attenuato (“Punteggio non rilevato”, ×17) con tooltip “Apri il report…”. La card resta un role="link" operabile da tastiera e la data si mostra.
  • Riusa il token neutro esistente; nessun colore nuovo.

Note

  • Solo client; nessuna modifica a route/CSP/SSRF/scrittura. +2 chiavi i18n ×17 (snapshot 1217 → 1219). Suite: 2416 (+3).

[1.160.0] — 2026-08-13

Corretto (ALTA) — il testo sui provider non contraddice più la promessa dei 7 provider. #/config diceva che la valutazione live “usa la tua chiave Anthropic o Gemini” e che quella OpenAI “non è usata dal web UI”; la dashboard diceva “Score Anthropic-first” — falso dalla cascata a 7 provider (v1.157.0).

Corretto

  • config.providerModelNote (×17): ora dice che la ⚡ valutazione live gira headless con una qualsiasi delle tue sette chiavi (Anthropic · Gemini · OpenAI · Qwen · OpenRouter · GitHub Models · Hermes), ordinate automaticamente con fallback. Rimossa la frase falsa su OpenAI.
  • dash.quick.evaluateSub (×17): neutro (“Punteggio di idoneità 0–5”). Keys: N / 5N / 7.

Note

  • Solo testo; nessuna modifica a route/CSP/SSRF/scrittura. Nessuna nuova chiave i18n (snapshot 1217). Suite: 2413 (+3).

[1.159.0] — 2026-08-13

Corretto (ALTA) — i metadati del report non dipendono più dalla lingua. I report generati in una lingua diversa dall’inglese mostravano una striscia di metadati vuota su #/reports, perché parseReportHeader riconosceva solo le etichette in grassetto in inglese.

Corretto

  • parseReportHeader ora analizza il blocco YAML invariante ## Machine Summary (score: / legitimacy: / date: — la stessa fonte che auto-pipeline già legge): etichette inglesi → blocco Machine Summary → etichette localizzate (REPORT_LABELS, 17 lingue). I report in inglese restano identici byte per byte.
  • Analisi numerica tollerante (1.5/5, 1,5/5, 1.5 из 5, 4.5 out of 5); la data ripiega sul mtime del file quando manca.

Note

  • Solo lettura/analisi; nessuna modifica a route, CSP, SSRF o scrittura sul padre. Nessuna nuova chiave i18n. Suite: 2410 (+8).

[1.158.0] — 2026-08-12

Corretto — due bug cosmetici di visualizzazione (un «?» che trapelava nel titolo della scheda e un conteggio dei provider errato nella landing). Solo visualizzazione; nessuna modifica a comportamento, sicurezza o flusso dei dati.

Corretto

  • Il «?» di HelpHint non trapela più in document.title. Il router derivava il titolo della scheda dal h1.textContent grezzo, mostrando «Vacancy search?» invece di «Vacancy search». router.js::focusNewView ora clona l’intestazione, rimuove .help-hint e poi legge il testo; il «?» visibile resta intatto.
  • cvstart.org mostrava «17 AI providers» invece di «7». L’helper sub() in Features.astro riscriveva ogni {n} con il numero di lingue (17) prima della sostituzione per scheda; ora {n} è risolto per scheda (provider → 7, lingue → 17).

Note

  • Nessuna modifica a server, route, CSP, SSRF o chiavi i18n; forma di facts.json invariata. Suite: 2402 test (+1).

[1.157.0] — 2026-08-12

Corretto — le valutazioni live ora girano con QUALSIASI provider configurato, non solo Anthropic/Gemini. Un utente con solo OPENROUTER_API_KEY veniva forzato erroneamente in modalità manuale.

Corretto

  • Causa radice: un pin LLM_PROVIDER senza chiave (es. LLM_PROVIDER=claude da init) finiva in un vicolo cieco; ora ripiega sull’ordine auto tra i provider configurati (in selectActiveProvider + entrambe le cascate di dispatch).
  • Il gating lato client (#/deep + viste mode-page) usa ora window.ProviderStatus (/api/status/providers, tutti e 7) invece del probe obsoleto Anthropic/Gemini; testi riscritti (deep/eval × 17) + badge «Valutazioni live» della dashboard + config.llmProviderHint.

Note

  • Nessun cambiamento di sicurezza. Suite: 2401 test (+5).

[1.156.0] — 2026-08-12

Refactor — dividere scan.js sotto il limite di dimensione (P-16) + un fix CodeQL. scan.js era di 906 righe; estratte due factory che preservano il comportamento → 648. Completa la coppia di divisioni delle view P-15/P-16.

Modificato

  • Nuovi scan/runner.js (motore di esecuzione dello scan) e scan/filters.js (macchina a stati dei filtri) tramite bag ctx/refs; scan.js collega entrambi.

Corretto

  • CodeQL js/useless-assignment-to-local (#428) in config/tab-controller.js: let n = i;let n;.

Note

  • Refactor puro, nessun cambio di comportamento; 4 test che leggono il sorgente ripuntati. Entrambe le view grandi ora sotto 800 (P-15/P-16 fatto). Suite: 2396 test.

[1.155.0] — 2026-08-12

Refactor — dividere config.js sotto il limite di dimensione (P-15). config.js era di 1030 righe (oltre il limite di 800); estratti due moduli che preservano il comportamento, portandolo a 783.

Modificato

  • Nuovi config/field-specs.js (dati dei campi + liste di modelli) e config/tab-controller.js (factory della barra delle schede); config.js li referenzia, la logica di render è invariata.

Note

  • Refactor puro, nessun cambio di comportamento; 6 test che leggono il sorgente sono stati ripuntati. scan.js (906) resta com’è (già parzialmente diviso; nucleo troppo accoppiato per una divisione meccanica pulita). Suite: 2396 test.

[1.154.0] — 2026-08-12

Nuova guida — “Esegui l’intero stack nel cloud.” career-ops non ha una propria storia cloud/server, quindi ne aggiungiamo una: una ricetta passo passo per mettere la pipeline padre career-ops, questo visualizzatore career-ops-ui e il motore IA (un abbonamento Claude via Claude Code, un Hermes locale, o chiavi API) su un piccolo server sempre attivo. Arriva come Aiuto §31 in 17 lingue, una sezione del README e una pagina wiki.

Aggiunto

  • Aiuto §31 “Esegui l’intero stack nel cloud” (× 17) — le tre parti, provisioning + installazione, scelta del motore, esposizione sicura (reverse proxy HTTPS + auth + le invarianti CSP/SSRF/XSS/nessun-segreto). Il bundle di aiuto cresce a 31 H2 / 112 H3.
  • README — una sezione “Esegui l’intero stack nel cloud” (× 17) + una pagina Cloud-Deployment nel wiki.

Note

  • Solo docs — nessuna rotta, server o modifica client; nessuna nuova chiave i18n. I 4 test di aiuto passano al contratto 31 H2 / 112 H3. Suite: 2396 test (invariata).

[1.153.0] — 2026-08-12

Scanner Jobvite migrato al feed XML pubblico (sync con il parent). Il parent ha ritirato l’API JSON di Jobvite (ora restituisce zero offerte); il source di web-ui usava lo stesso endpoint morto, quindi ogni azienda Jobvite tracciata scansionava vuota in silenzio. Porta il fix del parent (#2623): il source ora legge il feed XML pubblico per-tenant, con chiave companyEId.

Corretto

  • Il source chiamava l’API JSON ritirata e restituiva zero offerte; ora chiama https://app.jobvite.com/CompanyJobs/Xml.aspx?c={companyEId} e fa il parse dell’XML <result><job>… (CDATA + entità, detail-url prima di apply-url).

Modificato

  • Risoluzione di companyEId: (1) company_eid: sul portale, (2) il c= di un api: esplicito, (3) discovery dalla pagina del board. fetchText (http-json.mjs) allega .location/.retryAfter all’errore non-ok (sola lettura, retrocompatibile).

Note

  • Sicurezza — due host (jobs.jobvite.com, app.jobvite.com) fissati da assertJobviteUrl: solo https, allowlist stretta, nessun redirect seguito. Il companyEId è solo un valore ?c=; conteggio dei source invariato.
  • Suite: 2396 test (+4).

[1.152.0] — 2026-08-12

Provider Hermes — cablaggio completato + allineamento docs. Una revisione del codice dell’integrazione Hermes di v1.151.0 ha trovato due lacune reali e quattro punti di completezza; tutti corretti qui, e il roster dei provider LLM dell’intera app è portato ai sette completi su tutte le superfici di docs e le 17 lingue.

Corretto

  • #/config non poteva forzare Hermes — il menu LLM_PROVIDER elencava solo sei provider, quindi si poteva impostare HERMES_API_KEY ma non forzare Hermes dalla UI. Ora hermes è l’8ª opzione, e un nuovo test di parità impedisce al menu di divergere di nuovo da LLM_PROVIDERS.
  • Le chiavi locali corte venivano rifiutate in silenzio — la soglia di 20 caratteri di isUsableKey era tarata per chiavi cloud; hasHermesKey usa ora una soglia allentata di 8 (l’esempio dei docs Hermes è di 19 caratteri).

Modificato

  • Il roster dei provider è stato normalizzato ai sette completi in README (× 17), guida in-app (× 17), dict config.llmProviderHint (× 17) e docs/sdd; hermesChatUrl completa un host senza percorso; il testo di fallback manuale cita Hermes.

Note

  • Sicurezza invariata — nessuna nuova rotta né modifica SSRF/CSP; health/doctor guadagna una riga HERMES_API_KEY.
  • Suite: 2392 test (+2).

[1.151.0] — 2026-08-12

Hermes è ora un provider LLM collegato (Phase 5) — lo spike di analisi della Phase 5 ha confermato che il Hermes di Nous Research include un API Server compatibile con OpenAI (hermes gatewayPOST /v1/chat/completions), quindi career-ops-ui ora esegue valutazioni live tramite un Hermes locale esattamente come OpenAI/Qwen. Imposta HERMES_API_KEY in Impostazioni app ed entra nell’ordine auto (ultimo). Chiude l’ultimo punto aperto della roadmap — Phase 5, Shape A.

Aggiunto

  • Provider LLM Hermes (Shape A)runHermes sul client condiviso runOpenAICompatible (server/lib/openai.mjs), in entrambe le cascate (llm-dispatch.mjs + routes/llm.mjs), in coda all’ordine auto + il pin LLM_PROVIDER=hermes, /api/status/providers e llm-pricing.mjs. Raggiunge una base URL locale configurabile (default http://127.0.0.1:8642/v1) con auth Bearer — è un endpoint di provider CONFIGURATO (come OpenRouter/Qwen), non un URL di offerta dell’utente, quindi non tocca il guard SSRF.
  • Campi #/configHERMES_API_KEY (segreto) + HERMES_BASE_URL + HERMES_MODEL (default hermes-agent), con 6 nuove chiavi i18n × 17 lingue (snapshot 1208 → 1214).

Modificato

  • Lo spike di analisi è risolto: docs/integrations/HERMES.md, l’aiuto in-app §30 (× 17), il teaser del README (× 14), la skill hermes-bridge e la roadmap passano da “pianificato / non ancora collegato” a collegato (Shape A). Shape B (un relay su misura del runtime di agente) non è stato necessario.

Note

  • Sicurezza: il fetch del provider è un endpoint configurato, della stessa categoria degli altri provider compatibili con OpenAI — nessuna nuova superficie SSRF, nessuna modifica a CSP/sanitizer. HERMES_API_KEY è una SECRET_KEY (mai mostrata).
  • Test (isolati in CI, trasporto simulato): tests/hermes-provider.test.mjs (+5); il canarino “nessun ramo Hermes” di v1.146.0 è invertito per affermare che C’È; i test di superficie dei provider aggiornati all’ordine a 7 provider.
  • Suite: 2390 test (+5).

[1.150.0] — 2026-08-12

Stati vuoti coerenti (rifinitura Phase 4) — ogni pannello “ancora niente qui” ora si renderizza tramite l’unico stile condiviso .empty, invece che alcune viste ridichiarino l’aspetto inline con un 40px magico. Piccola correzione di coerenza visiva; gli stati vuoti di #/activity, #/cv-studio, #/stats e #/usage ora combaciano con tutti gli altri (padding di 48px tokenizzato + bordo tratteggiato).

Modificato

  • #/activity, #/cv-studio, #/stats, #/usage hanno rimosso il loro style: { padding: '40px', textAlign: 'center', color: 'var(--foggy)' } inline sui pannelli vuoti — tutte e tre le proprietà sono già fornite dalla classe condivisa .empty (--space-7 = 48px, centrato, attenuato, bordo tratteggiato). Così questi quattro si renderizzano identici agli altri ~25 pannelli .empty.
  • Gli override legittimi per vista (#/dashboard width:100%, #/pipeline border:none) non sono toccati — rimosse solo le ridichiarazioni puramente ridondanti.

Note

  • Solo pulizia dell’uso di CSS lato client — nessuna modifica a rotta, server, chiave i18n o regole CSS (la classe .empty è invariata); snapshot del dizionario 1208. Verificato nel browser (il pannello vuoto di #/usage calcola 48px di padding + bordo tratteggiato, 0 errori di console).
  • Il nuovo canarino tests/empty-state-consistency.test.mjs mantiene .empty come unica fonte di verità. La Phase 5 (provider Hermes) resta bloccata.
  • Suite: 2385 test (+2: tests/empty-state-consistency.test.mjs).

[1.149.0] — 2026-08-12

Portali spostati nelle Impostazioni (Phase 4)#/portals ora si trova nel gruppo di navigazione Setup, accanto a Impostazioni app, invece che sotto Sourcing. Dalla v1.144.0 è una superficie di configurazione (abilita/disabilita le aziende monitorate + una sonda di salute dell’ATS), non un’azione di sourcing — quindi è lì che deve stare. Solo modifica di navigazione; la pagina e la sua rotta non cambiano.

Modificato

  • Elemento di navigazione #/portals → gruppo Setup (in public/index.html), collocato subito dopo Impostazioni app. Rimosso dal gruppo Sourcing (che conserva Scan / Pipeline / Auto-pipeline / Aziende finanziate). La rotta #/portals, la vista e l’etichetta nav.portals non cambiano — si è spostata solo la posizione nella barra laterale.

Note

  • Solo markup di navigazione — nessuna modifica a rotta, vista, chiave i18n o server. Verificato nel browser (0 errori di console); protetto da tests/portals-nav-placement.test.mjs.
  • Suite: 2383 test (+2: tests/portals-nav-placement.test.mjs).

[1.148.0] — 2026-08-12

Filtri di ricerca più chiari (Phase 4) — il pannello dei filtri è ora una griglia ordinata — il pannello dei filtri di #/scan è passato da un flex-wrap irregolare di riquadri rigidi di larghezza variabile a una griglia responsiva, e le azioni Applica / Reimposta ora occupano una loro riga separata e allineata a destra. Stessi filtri, stesso comportamento — solo più leggibili. Una rifinitura di design (senza parent-sync).

Modificato

  • Pannello dei filtri di #/scan → griglia responsiva.scan-filters è ora display: grid con colonne repeat(auto-fill, minmax(180px, 1fr)) e spaziature uniformi, così gli 11 filtri etichettati si allineano in colonne ordinate a qualsiasi larghezza invece di andare a capo in una riga irregolare.
  • Azioni Applica / Reimposta occupano l’intera griglia su una loro riga, separate da una sottile linea e allineate a destra. Rimosso il vecchio trucco dell’etichetta nascosta + il wrapper flex interno in scan.js.

Note

  • Solo CSS + una piccola pulizia del DOM — ogni id di filtro (#scan-filter-*, #scan-apply) e il cablaggio di SR.render() sono invariati, quindi il flusso Playwright non è toccato. Nessuna nuova chiave i18n.
  • Verificato nel browser (0 errori di console); protetto da tests/scan-filters-grid.test.mjs.
  • Suite: 2381 test (+3: tests/scan-filters-grid.test.mjs).

[1.147.0] — 2026-08-12

Hermes & Telegram — la sezione di aiuto in-app + la superficie su cvstart.org (Phase 5b, parte 2) — la seconda e ultima parte del lavoro di documentazione di Hermes: la guida pratica ora vive dentro la guida di aiuto dell’app stessa, in tutte le 17 lingue, e l’assistente di documentazione integrato risponde alle domande su Hermes a partire da essa. Resta solo documentazione — il percorso del provider LLM Hermes rimane pianificato / non ancora collegato (Phase 5).

Aggiunto

  • Aiuto in-app §30 “Hermes & Telegram” × 17 lingue — una nuova sezione della guida (cos’è Hermes + le due forme di integrazione; esecuzione su un server cloud; Telegram tramite Hermes + la regola “cosa NON esporre”), raggiungibile da #/help. Il grounding di docs-assistant / DocsFab la recupera automaticamente, dato che entrambi leggono docs/help/<lang>.md.
  • cvstart.org — un link alla guida di Hermes che punta al documento su GitHub.

Modificato

  • Soglia del bundle di aiuto alzata 29 → 30 H2 / 105 → 108 H3 (canonical-docs-coverage, help-ui, help-ru-config-section); §30 aggiunge 3 H3.

Note

  • Ancora nulla chiama Hermes. Il nuovo canarino tests/help-hermes-section.test.mjs verifica che ogni lingua contenga la §30 con le sue àncore indipendenti dalla lingua (docs/integrations/HERMES.md, hermes-bridge, #/help, 127.0.0.1, Telegram). Il provider resta bloccato in attesa del contratto API della Phase 5.
  • Questo chiude il deliverable documentazione + skill della Phase 5b; l’integrazione del provider (Phase 5) resta un elemento separato e bloccato.
  • Suite: 2378 test (+2: tests/help-hermes-section.test.mjs).

[1.146.0] — 2026-08-12

Agente Hermes + Telegram — la guida di integrazione + una skill (Phase 5b, parte 1) — puoi eseguire career-ops-ui su un server cloud e collegare i suoi eventi (una scansione completata, un nuovo report, un follow-up urgente) a Telegram tramite un agente Hermes di Nous Research. Questa versione porta la documentazione di design + deployment e una skill hermes-bridge; il percorso del provider LLM Hermes resta pianificato / non ancora collegato (bloccato dallo spike sul contratto API della Phase 5). Documentazione intenzionalmente davanti al codice.

Aggiunto

  • docs/integrations/HERMES.md — l’analisi approfondita: le due forme di integrazione (endpoint compatibile con OpenAI vs. runtime dell’agente), il deployment su server cloud (reverse proxy + HTTPS + systemd, il contratto di sola lettura con il parent su una macchina headless), Telegram tramite Hermes, e un elenco da modello di minaccia «cosa NON esporre» (niente CV / stipendio / corpo dei report / chiavi verso il canale).
  • Il teaser ## Hermes agent + Telegram nel README — un breve rimando + link, nel README in inglese e replicato nei README tradotti di ogni lingua.
  • Una skill hermes-bridge (.claude/skills/hermes-bridge/) che rende operativa la guida — controlli di prerequisiti e di ambito (Node ≥ 18, chiavi presenti, raggiungibilità dell’endpoint tramite il percorso sicuro da SSRF), non scrive mai segreti su disco/log, e si rifiuta di inventare un endpoint Hermes o di affermare che il provider sia collegato.
  • Una sezione Integrations in docs/architecture/OVERVIEW.md collega alla guida.

Note

  • Per ora nulla chiama Hermes. Un test canarino (tests/hermes-docs.test.mjs) verifica i marcatori di onestà «pianificato / non ancora collegato» e che llm-dispatch.mjs non abbia alcun ramo Hermes/Nous — quindi collegare il provider in futuro dovrà aggiornare documentazione + roadmap nella stessa modifica.
  • Rinviato a v1.147.0 (Phase 5b, parte 2): la sezione di aiuto in-app «Hermes & Telegram» H2 × 17 lingue e la superficie di marketing di cvstart.org.
  • Suite: 2376 test (+4: tests/hermes-docs.test.mjs).

[1.145.0] — 2026-08-12

Statistiche perspicaci (segue): un grafico ricostruibile — la scheda “Tendenza ruoli target” su #/stats ha ora un widget Crea un grafico: scegli una metrica × dimensione e si ridisegna in tempo reale. Richiesta UX dell’utente (senza parent-sync).

Aggiunto

  • Grafico ricostruibile metrica × dimensione — scegli una metrica (Offerte / Stipendio mediano / Stipendio medio) e una dimensione (Per paese / Per ruolo), e il grafico a barre si ridisegna all’istante. Le metriche salariali rispettano la valuta + l’interruttore all’anno ⇄ al mese; le offerte sono un semplice conteggio.
  • 8 nuove chiavi i18n × 17 lingue; snapshot 1200 → 1208.

Note

  • Verificato nel browser (0 errori console). Suite: 2372 test (+2).

[1.144.0] — 2026-08-12

Impostazioni e filtri (Fase 4, parte 1): abilita/disabilita i portali seguiti — ora puoi accendere o spegnere un’azienda monitorata da #/portals, e lo scanner lo rispetta. Richiesta UX dell’utente (senza parent-sync).

Aggiunto

  • Interruttore Abilita/Disabilita per azienda su #/portals — un clic spegne un portale (lo scanner EN salta già le aziende con enabled: false, quindi un portale disabilitato esce da tutte le scansioni future) o lo riaccende, con un avviso ottimistico.
  • POST /api/portals/toggle — una scrittura esplicita dell’utente che commuta chirurgicamente e con validazione di parsing il flag enabled di un’azienda in portals.yml (commenti, ordine e altri campi preservati). 5 nuove chiavi i18n × 17 lingue; snapshot 1195 → 1200.

Note

  • La modifica allo scanner è stata zeroen-scanner.mjs filtra già enabled !== false. Suite: 2370 test (+3).

[1.143.0] — 2026-08-12

Comprensibile (segue): suggerimenti ? sulle viste di lavoro principali — il ? di aiuto copre ora le nove pagine d’azione principali, in tutte le lingue. Aggiustamento UX segnalato dall’utente (senza parent-sync).

Aggiunto

  • Suggerimento ? su altri 9 titoli di viste#/scan, #/evaluate, #/cv-studio, #/tracker, #/config, #/deep, #/batch, #/auto, #/apply ricevono un ? inline (via HelpHint.title) che apre un popover localizzato «cosa fa / come usarlo / cosa aspettarsi» — lo stesso componente sicuro per la CSP di v1.139.0.
  • 9 nuove chiavi i18n × 17 lingue (help.hint.scan/…/apply); snapshot 1186 → 1195.

Note

  • Verificato nel browser (0 errori console). Suite: 2365 test (+1).

[1.142.0] — 2026-08-12

Correzione: basta con l’archetipo di carriera “Unknown”#/orientation ora classifica sempre tra gli otto vettori di carriera nominati invece di rispondere talvolta “Unknown” e consigliarti di “puntarci di più”. Correzione segnalata dall’utente (senza parent-sync).

Corretto

  • #/orientation — il prompt IA vieta ora un archetipo fuori insieme. Il modello DEVE classificare i primi tre tra esattamente gli otto vettori nominati e non mai rispondere “Unknown”/“N/A”/“dati insufficienti” né inventare un’etichetta. Con un CV scarno nomina comunque i tre più vicini con minore fiducia e dice quali prove mancano.

Note

  • Solo modifica del prompt server (buildOrientationPrompt); nessuna modifica i18n/schema. Suite: 2364 test (+1).

[1.141.0] — 2026-08-12

Statistiche perspicaci (segue): arricchimento delle aziende finanziate#/funded è ora più visivo: loghi aziendali, un grafico per importo di finanziamento e schede con round / importo / punteggio di scoperta / azione suggerita. Aggiustamento UX segnalato dall’utente (senza parent-sync).

Modificato

  • #/funded — tabella piatta → griglia di schede. Ogni azienda finanziata di recente è ora una scheda con logo (derivato dal nome via CompanyLogo, avatar a lettera come ripiego), chip di round + importo, il punteggio di scoperta e l’azione suggerita del progetto principale, oltre a link e data della notizia.
  • Visualizzazione dell’importo di finanziamento — un grafico a barre delle maggiori aziende per importo divulgato; gli importi in testo libero (“$120M”/“€1.5B”) vengono convertiti in grandezza da un nuovo parseAmount. 3 nuove chiavi i18n × 17 lingue.

Note

  • Ancora sola lettura su GET /api/company-funded; descrizione e fascia salariale non sono nella fonte di finanziamento. Suite: 2363 test (+2).

[1.140.0] — 2026-08-12

Statistiche perspicaci: cifre salariali più ricche — la ripartizione salariale di “La mia pipeline” su #/stats mostra ora la media (non solo la mediana), un interruttore all’anno ⇄ al mese e una tabella min · media · mediana · max per paese. Prima parte della Fase 3. Aggiustamento UX segnalato dall’utente (senza parent-sync).

Aggiunto

  • Salario medioRoleStats.salaryStats ora restituisce avgUsd accanto a minUsd/medianUsd/maxUsd. La mediana resiste agli outlier, la media rivela l’asimmetria — insieme si leggono come una distribuzione.
  • Interruttore all’anno ⇄ al mese e una tabella min · media · mediana · max per paese nella sezione salari, guidati dai selettori di valuta e periodo. 8 nuove chiavi i18n × 17 lingue.

Note

  • Le cifre derivano ancora solo da annunci con salario leggibile e sono normalizzate in USD (indicative). Suite: 2361 test (+1).

[1.139.0] — 2026-08-12

Comprensibile: suggerimenti ? — un pulsante ? riutilizzabile e sicuro per la CSP che, al clic, spiega «cosa fa / come funziona / cosa aspettarsi» nella tua lingua. Aggiustamento UX segnalato dall’utente (senza parent-sync).

Aggiunto

  • Popover di suggerimento ? (window.HelpHint) — un ? rotondo accanto a un titolo apre un popover leggero, adattato al tema e speculare in RTL, che mostra una spiegazione localizzata tramite UI.md(); accessibile (role="tooltip", aria-expanded, chiusura con Esc/clic esterno, focus ripristinato) e sicuro per la CSP.
  • ? sulle 5 schede di #/stats e su 8 titoli di viste IA/analitiche (career-plan, orientamento, two-pager, networking, colloquio simulato, memoria, funded, riepilogo settimanale) — 14 nuove chiavi i18n × 17 lingue.

Note

  • Tutte le viste avevano già un sottotitolo di una riga; il ? aggiunge la spiegazione più approfondita su richiesta e rende gli stati vuoti autoesplicativi. Suite: 2360 test (+4).

[1.138.0] — 2026-08-12

Generazione nella lingua della tua interfaccia — ogni generazione IA ora risponde nella lingua scelta nell’UI, più rafforzamenti dei test emersi dalla revisione. Un aggiustamento UX segnalato dall’utente (senza parent-sync).

Modificato

  • Le generazioni IA ora rispettano la lingua dell’interfaccia. Con l’interfaccia in russo, spagnolo, giapponese, … il testo generato torna in quella lingua invece che sempre in inglese. La direttiva di lingua di output attraversa tutti gli endpoint di generazione — piano di carriera, orientamento, report di mercato, colloquio simulato, piano di networking, «chiedi alla documentazione», il suggerimento della nota di memoria e la bozza del two-pager. Codice e identificatori restano in inglese (es. le chiavi YAML del two-pager); solo prosa, titoli e punti vengono localizzati.

Corretto

  • Guardia dei ruoli di colore CSS (tests/css-role-tokens.test.mjs) — un canarino statico che i token alias della modalità scura di v1.137.0 non invertano mai il ruolo: i token di testo (--fg/--danger/--ok/…) mai come background, e quelli di superficie (--card/--panel/--line/…) mai come color di testo, in tutto il CSS e negli stili inline della SPA.
  • Auto-sonda del loader XSS di UI.md() — il test che carica md() da api.js ora sonda md('<script>…') subito dopo l’estrazione e solleva un errore se manca l’escape, così un futuro taglio errato fallisce rumorosamente invece di lasciare verde la suite di sicurezza su una funzione troncata.
  • Guardia di scroll su #/career-plan — lo scrollIntoView dopo la generazione viene eseguito solo se l’anteprima è ancora connessa al documento.

Note

  • docs/UX-ROADMAP.md aggiornato: i suggerimenti ? + descrizioni di pagina + stati vuoti passano a v1.139.0; un provider Nous Research / Hermes — con una guida al deploy su server cloud + Telegram e uno skill Hermes — è tracciato come Fase 5 / 5b.
  • Suite: 2356 test (+5).

[1.137.0] — 2026-08-11

Correzioni di leggibilità e rendering — contrasto in modalità scura, etichette dei grafici e il piano di carriera. Un intervento UX segnalato da un utente (nessun parent-sync).

Corretto

  • Bianco su bianco / nero su nero in modalità scura su molte schermate — quindici proprietà CSS personalizzate referenziate da diverse viste (--fg, --panel, --panel-2, --ok, --danger, --card, …) non erano mai state dichiarate, quindi ricadevano su valori chiaro/nero hardcoded: corretto in modalità chiara, illeggibile in modalità scura (i chip di panoramica di #/pipeline, la tab attiva di #/stats, “Attivo / Chiavi” + “✓ impostato” di #/config, le sezioni di #/two-pager, il fumetto della domanda di #/mock-interview, il testo di errore). Ora sono aliasate ai veri token theme-aware, quindi seguono automaticamente il tema — 0 violazioni di contrasto WCAG-AA su tutte le 29 viste, verificato da un revisore automatico; la tab attiva di #/config è passata a uno stile con tinta più leggibile. Una guardia di regressione (tests/dark-theme-tokens.test.mjs) mantiene l’aliasing.
  • Le etichette dei grafici di #/stats venivano troncate a metà parola (“Senior Backend Engineer” → “…Enginee”) — ora vengono troncate con ellissi, mantenendo l’etichetta completa come tooltip al passaggio del mouse.
  • #/career-plan mostrava il piano generato come Markdown non elaborato — ora viene renderizzato automaticamente come testo formattato e leggibile (il Markdown modificabile resta nella textarea; il pulsante Anteprima lo attiva/disattiva).

Note

  • #/career-plan, #/two-pager, #/stats e il digest settimanale dei colloqui non sono rotti — mostrano stati vuoti finché non generi un piano o non hai dati. Indicazioni più chiare in pagina e suggerimenti di aiuto ? sono previsti come prossimo passo (docs/UX-ROADMAP.md).

[1.136.0] — 2026-08-11

Parità con il career-ops padre v1.26.x (mainline post-v1.26.0) — una nuova sorgente a zero autenticazione più un’ondata di port di qualità e robustezza ai mirror di web-ui. Il registro ora conta 79 sorgenti = 74 EN + 5 RU (ALL_ADAPTERS 74).

Aggiunto

  • eightfold (Eightfold AI, #2684) — bacheche di talent-acquisition tramite l’API a zero autenticazione https://<tenant>.eightfold.ai/api/apply/v2/jobs, fissata sull’host *.eightfold.ai (il CNAME brandizzato careers.<company>.com è deliberatamente rifiutato); paginata con un tetto di sicurezza, errore su bacheca morta, deduplica degli URL. Sorgente + adattatore + suite CI-isolata; compare nel filtro Sorgente di #/scan e sulla landing.

Corretto

  • Deduplica e chiavi di ruolo sensibili a Unicode (#2569 / #2587 / #2667) — una nuova normalizeTextKey condivisa (NFKC, mantiene lettere/segni/cifre di qualsiasi script) sostituisce le chiavi solo-ASCII: detect-reposts ora raggruppa le varianti di larghezza/punteggiatura dell’azienda (“Acme, Inc.” ≡ “Acme Inc”) e non collassa mai datori di lavoro distinti non latini, mentre role-matcher unifica i titoli a larghezza piena e mantiene i token di ruolo non latini invece di eliminarli.
  • fetchJsonWithRetry non ripete più un reindirizzamento rifiutato (#2657) — una guardia redirect:'error' che incontra un 3xx è deterministica, quindi ora non è ripetibile e fallisce rapidamente invece di consumare il budget di retry.
  • Gruppi AND con + in title_filter.positive (#2552) — un + delimitato da spazi all’interno di una voce positiva ora richiede che ogni termine appaia nel titolo, in qualsiasi ordine.
  • oraclecloud accetta gli apici tenant numerati oraclecloud1.com … oraclecloud99.com (#2683) — una famiglia limitata (nessuno zero iniziale, ≤ 2 cifre), mai un apice wildcard.
  • workable irrobustita (#2675) — retry, header simil-browser e serializzazione delle richieste contro l’host dietro Cloudflare.
  • personio ricorre a uno scraping HTML quando il feed XML è disabilitato, invece di non restituire nulla.
  • Alias FALLBACK di states risincronizzati con il padre (#2615).

Note

  • Non portato (non specchiato da web-ui, o solo CLI): reply-matcher (#2672), jd-similarity (#2661), jd-skill-gap (#2686), il parsing di scan env-path (#2568) / --flag=value (#2589), e le modifiche a cover-letter / template CV / doctor / ollama / generate-pdf. Gli avvisi HIGH web js-yaml/nanoid erano già stati corretti in web-ui v1.135.0.

[1.135.0] — 2026-08-11

Parità con career-ops padre v1.26.0 — cinque nuove sorgenti di scansione a zero autenticazione più correzioni di correttezza a quattro bacheche già presenti in web-ui. Il registro ora conta 78 sorgenti = 73 EN + 5 RU (ALL_ADAPTERS 73).

Aggiunto

  • Cinque nuove sorgenti di scansione (ciascuna con una sorgente + adattatore + suite CI-isolata; compaiono nel filtro Sorgente di #/scan e sulla landing di cvstart.org):
    • join (JOIN) — la bacheca JOIN di un’azienda ricavata dal __NEXT_DATA__ di Next.js in join.com/companies/<slug> (host fissato, pagine limitate).
    • getro (Getro) — bacheche di portfolio “talent-network” dei VC tramite l’API pubblica POST api.getro.com, paginata dal più recente; ogni lavoro è attribuito all’azienda del portfolio, non al fondo.
    • consider (Consider) — bacheche di portfolio VC di getconsider.com tramite una POST same-origin; l’host configurabile è fissato da una guardia SSRF strutturale (solo host HTTPS pubblico).
    • joinup (JOINUP) — la bacheca svizzera joinup.ch, che legge la pagina più recente renderizzata lato server; si blocca (fail-closed) in caso di rottura dello scraper.
    • remotli (Remotli) — remotli.ch, ruoli da remoto presso aziende svizzere (stipendi in CHF); emette l’URL di candidatura ATS proprio del datore di lavoro, così le inserzioni incrociate vengono deduplicate.

Corretto

  • a16z Speedrun non interrompe più l’intera bacheca per un intoppo transitorio — i fetch delle pagine ora passano attraverso un fetchJsonWithRetry condiviso (tentativi limitati solo su 429/5xx/timeout transitori, mai su un 4xx permanente), e il budget di pagina è stato ridimensionato per la pagina da 50 lavori.
  • arbeitsagentur è passata alla API Jobsuche v6 (/pc/v6/jobs) — il vecchio endpoint v4 restituisce 404; la forma della risposta è stata rinominata e il filtraggio remoto ora si restringe lato server.
  • thehub è passata alla API v2 jobsandfeatured — le righe non portano una data di pubblicazione e sono esenti dal filtro per età.
  • hackernews trova ora in modo affidabile il thread mensile “Who is hiring?” filtrando la ricerca Algolia sul tag account author_whoishiring invece che su una query in testo libero.

Note

  • Non portato (web-ui è già sicuro, assorbito dall’inoltro, o solo CLI): le chiavi di deduplicazione ruoli / corrispondenza aziende Unicode (il raggruppamento dei ripescaggi di web-ui fa già chiave sull’azienda in minuscolo semplice, quindi datori di lavoro distinti non latini non collassano mai); il segnale di latenza di rifiuto del follow-up + le rifiniture di company-funded (inoltrati in sola lettura, fail-soft); i percorsi di scansione sovrascrivibili da variabili d’ambiente e il parsing --flag=value (web-ui esegue gli scanner in-process); il refactoring di consolidamento dello User-Agent (web-ui lo centralizza già); e le voci solo CLI (elenco contenuti non attendibili, oferta/offer-prep, doctor, modifiche ai template CV/lettera di presentazione).

[1.134.1] — 2026-08-05

Irrobustimento della validazione — correzioni emerse da un audit a livello di intero progetto.

Corretto

  • successfactors non scarta più i lavori raccolti in caso di fallimento a metà scansione (regressione nel port v1.134.0 dell’errore su bacheca morta) — il suo ciclo di paginazione non aveva alcun try/catch, quindi un fallimento a pagina 2 o successiva (dopo il successo della pagina 1) generava un errore e scartava tutto ciò che era già stato raccolto; e se quel fallimento era un 404 (uno startrow fuori intervallo), en-scanner metteva in quarantena per giorni un tenant in realtà attivo. Ora rispecchia phenom/radancy: un fallimento a pagina 0 genera ancora un errore (bacheca morta), ma un fallimento su una pagina successiva mantiene i risultati parziali.
  • Le chip dei filtri di #/scan sono ora azionabili da tastiera (WCAG 2.1.1) — le chip delle facet (e la chip “pulisci”) erano span con un gestore di click ma senza tabindex/ruolo, quindi gli utenti che usano la tastiera o uno screen reader non potevano raggiungerle né attivarle. Ora portano role="button", tabindex="0", aria-pressed e l’attivazione con Invio/Spazio.
  • Tre stringhe inglesi hardcoded sono ora localizzate — il tooltip del badge di affidabilità di #/scan, l’intestazione della colonna di trasferimento di #/scan e l’intestazione del punteggio di #/dashboard erano letterali nudi che il gate di parità i18n non poteva rilevare (non erano mai state chiavi), quindi restavano in inglese in ogni localizzazione non inglese. Ora scan.trustTip + scan.col.reloc (2 nuove chiavi) più un riuso di track.col.score, con una guardia source-static.

[1.134.0] — 2026-08-05

Parità con career-ops padre v1.25.0.

Aggiunto

  • Nuova sorgente di scansione: getManfred (manfred) — un feed a livello di board di ruoli tech spagnoli/UE con stipendi pubblicati, da www.getmanfred.com/api/v2/public/offers (zero autenticazione, host fissato + solo HTTPS, catalogo completo in una singola richiesta). Sorgente + adattatore + una suite CI-isolata (tests/sources-manfred.test.mjs); il registro ora conta 73 sorgenti = 68 inglesi + 5 russe (ALL_ADAPTERS 68). Compare nel filtro Sorgente di #/scan e sulla landing di cvstart.org.

Corretto

  • Il feed a16z Speedrun si troncava silenziosamente a 50 lavori (#2404) — il feed limita una pagina a 50, ma la sorgente richiedeva PER_PAGE = 100, quindi la paginazione si fermava dopo la pagina 1. Corretto a 50.
  • Le bacheche morte ora generano un errore invece di leggersi come “attive ma vuote” (#2379) — cryptocurrencyjobs, phenom, radancy, successfactors: un fallimento di fetch in cui nessuna richiesta si risolve mai ora genera un errore (così #/portals health e la scansione registrano un vero fallimento), invece di ridurlo silenziosamente a un elenco vuoto; un fallimento a metà scansione dopo almeno un successo mantiene i risultati parziali.
  • workable ora usa l’API pubblica del widget (#5ab8425) — passato a apply.workable.com/api/v1/widget/accounts/<slug>, che restituisce in un’unica richiesta l’elenco completo degli annunci di un account grande, così gli account grandi non vengono più troncati.

Note

  • Non portato (solo CLI o non replicato da web-ui): la riscrittura delle prestazioni con bucket per titolo di detect-reposts #2389; le correzioni per chiavi aziendali Unicode (la deduplicazione del tracker di web-ui è già sicura per caratteri non latini); scan --since; cv-facts; il passaggio di audit su template CV / PDF; doctor; la direttiva sui contenuti non attendibili delle modalità.

[1.133.1] — 2026-08-02

Corretto

  • #/funded (Aziende finanziate) ora mostra i risultati — due bug facevano sì che la tabella mostrasse sempre “nessuna azienda finanziata” anche quando company-funded.mjs del padre restituiva un elenco completo. (1) La vista leggeva i risultati sotto res.candidates, ma il padre li emette sotto companies (ciascuna { company, amount, round, funding: { sources: [{ source, url, observed_date }] } }); il client ora legge la chiave corretta e mappa la forma reale delle prove. (2) La tabella dei risultati passava le proprie celle a UI.el('tr', {}, …) come argomenti variadici, ma UI.el(tag, attrs, children) si aspetta children come un singolo nodo o un array, quindi veniva renderizzata solo la prima colonna (Azienda) — ora le celle vengono passate come array. Verificato in un browser reale: 11 aziende dai quattro feed vengono renderizzate con le colonne Azienda / Segnale di finanziamento / Fonte / Data e link alle prove funzionanti, zero errori console. Un passaggio vuoto ora mostra anche la diagnostica per fonte, così un giorno di notizie tranquillo si distingue da un feed bloccato.
  • Guardie di regressione in tests/parity-routes-v1133.test.mjs: lo script fittizio del padre ora emette la forma reale di output companies (il fixture originale rispecchiava erroneamente la forma candidates — proprio il motivo per cui il bug è stato pubblicato con la suite verde), oltre a canarini source-statici che verificano che funded.js legga res.companies (mai res.candidates) e costruisca le righe della tabella con figli in forma di array (+1 → 2144).

[1.133.0] — 2026-08-01

Aggiunto

  • Scoperta aziende finanziate (#/funded, parità col padre #2117) — una nuova vista di sola lettura che inoltra lo script company-funded.mjs del career-ops padre tramite GET /api/company-funded: un elenco a revisione preventiva di aziende recentemente finanziate, scoperte da feed pubblici di finanziamento con host fissato (TechCrunch, PR Newswire, The Guardian, Hacker News). L’inoltro esegue lo script con --json --dry-run (JSON su stdout, nessuna scrittura su file), non passa mai l’input dell’utente in --sources, applica un limite di frequenza ed è attivato dall’utente (un pulsante Scopri, mai al montaggio). Nuovo modulo di rotta server/lib/routes/funded.mjs + public/js/views/funded.js, sotto Sourcing.
  • Digest settimanale dei colloqui (#/interview-digest, parità col padre #2129/#2130) — una nuova vista di sola lettura che inoltra lo script zero-LLM weekly-digest.mjs del padre tramite GET /api/interview/weekly-digest: un riepilogo meccanico delle note di sessione dei colloqui — con quali aziende e in quali fasi hai fatto colloqui questa settimana, competenze ricorrenti e lacune aperte stimate al meglio. L’intervallo opzionale ?from=&to= viene passato solo quando ENTRAMBI sono YYYY-MM-DD validi; un intervallo vuoto è un digest available:true valido. Aggiunto a server/lib/routes/interview.mjs + public/js/views/interview-digest.js, sotto Analytics.
  • Entrambi gli inoltri seguono il contratto consolidato fail-soft available:false quando lo script del padre è assente (CI, installazioni standalone). 26 nuove chiavi i18n ×17 locale; suite CI-isolata tests/parity-routes-v1133.test.mjs (+5 → 2143).

Note

  • Il career-ops padre è avanzato oltre la v1.24.0 con la pagina Follow-up Tracker dell’app web/ Next.js (#1422) e il rendering PDF lato backend (#2182) — non portati: web-ui ha già un proprio inoltro follow-up e i propri runner PDF, e l’irrobustimento sottostante di followup-cadence.mjs arriva gratis tramite l’inoltro con shell-out. Le modifiche a set-status.mjs / tracker-utils.mjs sono interne alla CLI e non vengono replicate.

[1.132.0] — 2026-07-31

Modificato

  • Il sottosistema di rendering dei risultati di #/scan è stato estratto in public/js/lib/scan-results.js (debito da contratto sulla dimensione dei file — public/js/views/scan.js era cresciuto a ~1254 LOC). Il sottosistema — renderResults, buildChipRow, getRows, i builder di righe/faccette, i pittori delle opzioni e lo specchio del registro FALLBACK_SOURCES — si è spostato in una factory window.ScanResults.create(ctx) che chiude su un oggetto di contesto fornito dalla vista. Nessuna modifica di comportamento — le funzioni sono state spostate testualmente, le variabili di chiusura ricollegate meccanicamente a ctx.*; scan.js è ora ~906 LOC (è pianificato un secondo passaggio di estrazione verso l’obiettivo di 800 LOC).
  • I test source-static leggono entrambi i file tramite tests/helpers/scan-src.mjs::loadScanSrc(); tests/scan-fallback-sources.test.mjs ora legge lo specchio del registro da scan-results.js.
  • Nuovo gate di regressione in-browsertests/playwright-scan-filters.mjs semina un data/last-scan.json predefinito e guida ogni filtro di #/scan, verificando i conteggi esatti delle righe (npm run test:e2e:browser); sono stati aggiunti id stabili per i controlli dei filtri (#scan-filter-*, #scan-apply).
  • Il banner “Ultima release” del README è stato snellito a un riepilogo di una riga + un link al changelog completo (il lungo muro narrativo multi-versione è stato ritirato). Applicato in tutte le 17 localizzazioni.

[1.131.2] — 2026-07-31

Modificato

  • app.css diviso in tre fogli di stile ordinati (debito da contratto sulla dimensione dei file — il singolo file era cresciuto a ~1990 LOC, ben oltre il limite rigido di 800 LOC). Ora è app.css (~672 — accessibilità, token di design/tema, sidebar, main, pulsanti, content-shell), components.css (~595 — card, griglie, paginatore, badge, tabelle, form, log/console, markdown, selettore lingua, filtro a chip, banner di connessione) e overlays.css (~737 — toast, cassetto notifiche, modale, varie/responsive, lo specchio [dir="rtl"], docs-fab, usage-hud), ciascuno entro il limite rigido.
    • Il taglio è contiguo e in ordine, quindi la cascata è identica byte per byte al file pre-divisione; index.html carica i tre come <link> ordinati. Nessuna modifica di comportamento, markup o i18n.
    • I test che verificano il CSS ora leggono la concatenazione tramite un helper condiviso tests/helpers/css.mjs::loadAppCss(). Il nuovo tests/css-modularization.test.mjs blocca la divisione (i file esistono · ciascuno ≤ 800 LOC · ordine dei link in index.html) → suite 2138. Verificato nel browser: tutti e tre i fogli di stile vengono analizzati e le loro regole vengono applicate.

[1.131.1] — 2026-07-31

Corretto

  • Coerenza del pinning dell’host degli adattatori sulle due sorgenti v1.130.0 (follow-up di code review, difesa in profondità; nessuna modifica di comportamento per input validi):
    • L’adattatore a16z-speedrun-talent ora rivalida l’override api: / a16z-speedrun-talent: in buildEndpoint (HTTPS + host esatto speedrun-talent-network.com) e torna al feed canonico quando la validazione fallisce — parità con l’adattatore cryptocurrencyjobs, così un valore fuori host non raggiunge mai lo slot di fetch (in precedenza si basava solo sulla guardia a tempo di fetch assertSpeedrunUrl). Il controllo dell’host esatto è ora una singola SPEEDRUN_TALENT_HOST_RE esportata, condivisa dalla guardia e dall’adattatore.
    • Il parser cryptocurrencyjobscleanUrl ora usa la stessa guardia dell’host a corrispondenza esatta di assertCryptocurrencyJobsUrl e dell’override dell’adattatore (era endsWith, che accettava sottodomini). Il parser non è mai più permissivo della guardia SSRF: un link di elemento sub.cryptocurrencyjobs.co viene scartato.
    • +2 test → suite 2135.

[1.131.0] — 2026-07-31

Aggiunto

  • Bacheca a schede di stadio (CRM) per #/tracker (portata dalla vista /pipeline dell’app web del padre). La barra a chip del funnel + il menu a tendina di stato del tracker sono sostituiti da una striscia di schede di stadio: una scheda Tutti più una scheda per ogni stato canonico — Evaluated · Applied · Responded · Interview · Offer · Rejected · Discarded · SKIP · Hired — ciascuna con un conteggio live sull’intera cronologia, incluse le fasi a conteggio zero così l’intero funnel è sempre visibile (l’aspetto CRM). La scheda attiva guida il filtro; un secondo clic la deseleziona tornando a Tutti. Le righe mantengono la tonalità del punteggio, la legittimità e le funzioni PDF e report, e la cella dell’azienda ora mostra un logo del brand quando i loghi sono abilitati (disattivati di default → zero richieste extra).
    • Nuova rotta di sola lettura GET /api/tracker/stages restituisce il funnel canonico (etichette in ordine) + una mappa di fusione degli alias, presa da server/lib/states.mjs (templates/states.yml, con il fallback integrato) — così il client non codifica mai staticamente la whitelist di stato. La risposta legacy senza parametri di GET /api/tracker è invariata (solo { rows }).
    • Nuova libreria client pura e testata a livello di unità public/js/lib/tracker-stages.js raggruppa le righe secondo gli stadi del server, tollerando grassetto markdown residuo e alias localizzati (es. aplicadoApplied). Le schede sono accessibili (role tablist/tab, aria-selected, area di tocco ≥44 px, conteggi nel nome accessibile di ciascuna scheda). Nessuna nuova chiave i18n. Suite 2133.

[1.130.0] — 2026-07-31

Aggiunto

  • Due nuove sorgenti di scansione portate dal career-ops del padre v1.24.0 (in-process, senza nuove dipendenze; entrambe compaiono nel filtro Sorgente di #/scan e sulla landing di cvstart.org):
    • a16z Speedrun (a16z-speedrun-talent, #2231) — il feed JSON dell’intera bacheca della talent-network a16z Speedrun. Fissato all’host speedrun-talent-network.com, solo HTTPS, paginazione a indice 0 con un tetto di pagine, threading di q/config per azienda, fail-soft.
    • Cryptocurrency Jobs (cryptocurrencyjobs) — il job board Web3 cryptocurrencyjobs.co, acquisito tramite il suo feed pubblico RSS 2.0 (zero autenticazione). Decodifica delle entità XML in due passaggi, annunci solo remoti, datore di lavoro estratto dalla coda del titolo "… at <Azienda>".
    • Il totale del registro è ora 72 sorgenti = 67 inglesi + 5 russe (ALL_ADAPTERS = 67 adattatori di portali inglesi).

Corretto

  • echojobs — i ruoli ibridi restano distinguibili dal remoto (rispecchia il #2258 del padre). Un marcatore hybrid case-insensitive ora produce "<Città> · Ibrido" (o un semplice Ibrido in assenza di città) e workplaceType: 'Hybrid', invece di essere collassato in Remoto.
  • radancy — markup legacy TalentBrew + trasporto del frammento di risultati JSON (rispecchia il commit del padre a3e6df9), condizionato da un opts.fetchJson iniettabile.

Note

  • Non portato — funzionalità del padre solo CLI. L’ampia superficie CLI/modalità di career-ops v1.24.0 resta fuori da web-ui, che è un visualizzatore + scrittura passante sottile, non un host di modalità: le tabelle di conformità/giurisdizione, la rubrica contatti + vCard, il debrief da trascrizione colloquio / rilevamento piattaforma di chiamata, l’impostazione stato del ledger, la registrazione degli esiti, il triage a due passaggi, la similarità delle JD, lo schema versionato degli artefatti CV di candidatura, il rilevamento doctor di Playwright-MCP e portals/fix-slugs.mjs. Le modifiche di orchestrazione della scansione che vivono nello scan.mjs del padre — lo scanner Playwright per Interamt.de, lo sweep completo iCIMS reverse-ATS, il filtro di idoneità paese per il remoto, il pacing delle ricerche DNS, la deduplica rltr di StepStone e la colonna azienda normalizzata nella cronologia scansioni — non si applicano: web-ui esegue gli scanner EN/RU in-process e non passa per scan.mjs.
  • Già coperto. La correzione di accent-folding di role-matcher (#2209) è stata portata in v1.127.0, quindi qui è un no-op.

[1.129.1] — 2026-07-29

Corretto

  • Follow-up della revisione IA sui port web di v1.128/v1.129 (tutti consultivi, corretti all’origine): precedenza di livello in job-facets.js (un modificatore esplicito ora batte una parola di gestione — Senior Engineering Managersenior, prima lead); il fallback in states.mjs non è più bloccato (una lettura riuscita è memoizzata, il fallback è restituito senza cache — un padre momentaneamente non disponibile al boot viene riletto alla chiamata successiva) + console.warn su un file presente ma malformato; score-tone.js — una riga senza punteggio è neutra (muted), non rossa; domainFromName() salta gli slug non-ASCII prima di /api/logo; +una guardia di isolamento in tests/states.test.mjs. +4 test → 2073.

[1.129.0] — 2026-07-29

Aggiunto

  • Faccetta Livello + colonna Età su #/scan — la libreria job-facets.js rilasciata in v1.128.0 è ora collegata alla UI di scansione (prima solo logica). Un nuovo menu Livello classifica il titolo di ogni offerta in lead/staff/senior/intermedio/junior/stagista (JobFacets.seniorityFromTitle) e si autopopola da ciò che c’è nei risultati (come la faccetta Paese); i titoli senza parola di livello passano sempre. Mantenuta in ricerche salvate, Reimposta e Applica. La tabella guadagna una colonna Livello (badge) e una colonna Età senza token (oggi / Ng, da JobFacets.daysSince). 12 chiavi i18n ×17, +3 test → 2069.

[1.128.0] — 2026-07-29

Aggiunto

  • Quattro soluzioni portate dalla web app propria del padre (../web/, Next.js), reimplementate in JS vanilla/ESM, senza nuove dipendenze: (1) server/lib/states.mjs legge templates/states.yml dal vivo come vocabolario canonico degli stati del tracker (fallback CI) — elimina il re-sync manuale della whitelist a ogni release; POST ripiega gli alias (spagnolo/legacy) sull’etichetta canonica, il funnel di GET raggruppa per stato canonico; (2) loghi aziendali sulle righe con host ATS via domainFromName() (~90 brand→dominio); (3) score-tone.js — tono di punteggio a 4 livelli (≥4.2/3.8/3.0 + fallback a lettera); (4) job-facets.js — facet seniority/source/days. +21 test.

Note

  • Non portato (solo concetto): il livello di azioni agentiche del padre (actions/registry.ts + api/assistant/route.ts) — progetto per quando docs-fab diventerà un copilota. Nessuna nuova sorgente (registro 70), nessuna modifica i18n/help.

[1.127.0] — 2026-07-29

Aggiunto

  • Tre nuove sorgenti di scansione (parità career-ops v1.23.0) — il registro conta ora 70 adattatori (65 EN + 5 RU): Flowxtra (aggregatore globale senza auth), VDAB (API per parola chiave del servizio pubblico per l’impiego fiammingo) e iCIMS (portali careers-<tenant>.icims.com, distinto da jibeapply). Inoltre Cursor torna nel roster delle CLI (parent #2115): cli-detect ora rileva cursor (10 strumenti), roster ripristinato in help/README/config ×17.

Corretto

  • agenticjobs è passato dallo scraping HTML all’API REST (#2167); Greenhouse recupera la città da /offices quando location.name è solo un modello di lavoro (#2104); parità role-matcher (#1933/#2164/#2009: prefisso MTS, baseline product, ripiegamento accenti, disaccordo sub-baseline).

Note

  • Non portato. Gran parte di v1.23.0 è superficie CLI/dashboard che web-ui non usa (batch-tailor, discover-ats, modi NL/PT, temi PDF, dashboard Go, updater/doctor); gli script relayati non cambiano. VERSION del padre → 1.23.0.

[1.126.1] — 2026-07-25

Corretto

  • Due punti di deriva del roster CLI che il resync di v1.126.0 ha mancato — (1) l’intro della scheda API keys di #/config (config.providerModelNote, i18n ×17) elencava solo 7 CLI — ora Antigravity e Grok Build sono inseriti dopo OpenCode; (2) una seconda riga della tabella comparativa nella guida di aiuto (×17) e l’help del sito (costruito in CI) riportavano ancora Inside Claude Code / Codex / Cursor / Gemini CLI — il set obsoleto con Cursor — ora il roster completo. Entrambi usavano separatori slash/punto medio che i pattern dello sweep v1.126.0 non coprivano. Snapshot i18n rigenerato; la suite resta a 1969.

[1.126.0] — 2026-07-25

Aggiunto

  • La scheda strumenti AI CLI ora rileva tutte le 8 CLI di prima classe di career-ops — il roster di #/config è stato sincronizzato con il docs/SUPPORTED_CLIS.md del padre: server/lib/routes/cli-detect.mjs guadagna Grok Build CLI (grok) e Kimi CLI (kimi), e Antigravity ora sonda per primo il suo binario canonico agy. La scansione PATH in sola lettura ora riporta 9 strumenti; continua a non eseguire mai un binario trovato.

Modificato

  • Risincronizzazione della documentazione con career-ops.org/docs — ogni superficie di docs è stata riconciliata con le pagine live del padre (lette tutte e 31). Il roster canonico di assistenti IA (help ×17 + README ×17) elenca ora le 8 CLI di prima classe — Claude Code, Codex, OpenCode, Antigravity CLI, Grok Build CLI, Qwen Code, Kimi, GitHub Copilot CLI — più Gemini CLI (wrapper legacy). I bundle di aiuto mantengono la struttura 29 H2 / 105 H3.

[1.125.4] — 2026-07-23

Modificato

  • dipendenze di site (dependabot #151–#153) — sharp 0.34.5→0.35.3, svgo 4.0.1→4.0.2, fast-uri (dev) 3.1.3→3.1.4 in site/; build Astro verde, nessun impatto su SPA/server.

Note

  • Sweep di parità col progetto padre (career-ops 37d17ec..254764a, post-v1.22.0) — niente da portare: il guard della riga sbagliata di set-status (#2108) è solo CLI (in web-ui le righe del tracker si selezionano esplicitamente nella UI e nessuna route invoca set-status.mjs), il Risk Summary dei modi localizzati (#2109) tocca file modes/<lang>/ che web-ui non legge mai (solo modes/*.md di primo livello), la verifica del manifest di update-system (#2111) riguarda solo l’updater, e il resto è documentazione del padre (README turco, SIGNATURES ×4, SCRIPTS.md, accenti es). Il VERSION del padre resta 1.22.0parentVersion invariato.

[1.125.3] — 2026-07-23

Corretto

  • I prompt LLM in danese e hindi rispondevano in inglese (segnalato da un utente) — LOCALE_NAMES e i cinque blocchi SCAFFOLD_STRINGS in server/lib/prompts.mjs non erano mai stati estesi a da e hi, quindi resolveLocale() ricadeva su en e ogni prompt AI — deep research (live e manuale), modalità, valutazione, colloquio, networking, CV Studio — perdeva la direttiva # Output language in quelle due lingue. Entrambe sono ora di prima classe: direttiva di lingua + impalcatura localizzata. Il gate di regressione in tests/locale-scaffold.test.mjs ora scorre l’elenco canonico di 17 locale invece di 12 hardcoded, e un nuovo gate strutturale fa fallire qualsiasi chiave dell’impalcatura che ricade sull’inglese — una futura locale che dimentichi prompts.mjs non può più essere pubblicata (+12 test, la suite è ora 1969).

[1.125.2] — 2026-07-22

Corretto

  • Deep research via Gemini: HTTP 502 (MALFORMED_FUNCTION_CALL) (#145, contributo di @Alien10140) — il prompt live di /api/deep chiedeva al modello di «Use WebFetch / WebSearch» e di salvare il brief su file, ma i provider API headless non hanno un canale strumenti; Gemini rispondeva con una chiamata di funzione invece che con testo, manifestandosi come un 502 vuoto. buildDeepPrompt e bundleProjectContext accettano ora un flag headless: le esecuzioni live (Anthropic/Gemini/cascata di fallback) ricevono un prompt senza strumenti che scrive il brief dal contesto inlineato, mentre il prompt copia-incolla per Claude Code mantiene le istruzioni sugli strumenti. +1 test in tests/critical-fixes.test.mjs.

Modificato

  • Modelli Gemini predefiniti aggiornati oltre il deprecato gemini-2.0-flash (#144, contributo di @Alien10140) — il menu a tendina della Configurazione, il fallback server in gemini.mjs (che divergeva in silenzio dal suggerimento), la catena di fallback OpenRouter, config.geminiModelHint ×17 e la guida ×17 ora indicano tutti gemini-3.6-flash. Il nuovo gate anti-deriva tests/gemini-default-model.test.mjs (+5 test) fissa tutte le superfici sullo stesso letterale — la suite arriva a 1957 test.

[1.125.1] — 2026-07-21

Corretto

  • SuccessFactors: i tenant RMK multi-brand mantengono il proprio percorso di brand (parent #2099, post-v1.22.0) — le holding che gestiscono più brand acquisiti da un’unica istanza RMK condivisa li distinguono tramite un segmento di percorso (careers.nemetschek.com/Bluebeam/ contro …/Vectorworks/); l’adattatore in precedenza collassava l’URL configurato alla sua origine, scansionando silenziosamente gli annunci del brand principale. L’endpoint ora preserva il prefisso di brand, rimuovendo solo un segmento finale /search/ o /tile-search-results/ così che nulla si duplichi mai su se stesso; i tenant a dominio singolo restano invariati byte per byte. Nuovo helper esportato resolveTenantBase + 1 blocco di test portato in tests/sources-successfactors.test.mjs.

[1.125.0] — 2026-07-21

Aggiunto

  • cvstart.org: sezione “Fonti di lavoro” nella landing — una nuova sezione tra gli screenshot e il confronto elenca tutte le 67 sorgenti di scansione come chip cliccabili (62 board/ATS in inglese + le 5 board russe sotto un proprio sottotitolo), ciascuna con link al sito pubblico della sorgente. L’elenco è sincronizzato con il registro degli adattatori dal vivo in fase di build (sync-assets.mjsfacts.sources), così non può mai disallinearsi dall’app; una mappa di link curata in Sources.astro è protetta dalla nuova tests/site-sources.test.mjs. La navigazione dell’header ha guadagnato un’ancora Fonti; 4 nuove chiavi i18n del sito ×17. È stato corretto anche l’elenco inLanguage del JSON-LD della landing, a cui mancava ancora hi.

[1.124.0] — 2026-07-21

Aggiunto

  • Cinque sorgenti di scansione (parità con il parent v1.22.0, #1808/#1572/#2024/#2055) — Welcome to the Jungle (API JSON per l’intero board), Agentic Engineering Jobs (board di ingegneria agentica/IA), Jobvite (ATS per-tenant a zero autenticazione), Gem (ATS per-tenant) e Alibaba Group (API JSON delle pagine carriere, pattern Meituan/Tencent). Ognuna è una coppia sorgente + adattatore ancorata all’host e isolata per la CI; il registro ora conta 67 adattatori (62 inglesi + 5 russi); il fallback del menu a tendina Source di #/scan e il relativo gate di deriva sono aggiornati; cinque nuove suite tests/sources-{wttj,agenticjobs,jobvite,gem,alibaba}.test.mjs.

Corretto

  • Arbeitsagentur: full-remote a livello nazionale solo quando homeofficetyp è VOLLSTAENDIG (parent #1981) — la query homeoffice=nv_true restituisce anche ruoli ibridi, quindi il passaggio remoto ora conferma ogni risultato rispetto all’endpoint di dettaglio dell’offerta in piccoli batch e fallisce in modo sicuro (un errore di lookup mantiene la città reale dell’offerta, così i filtri di località continuano ad applicarsi).
  • SmartRecruiters: gli URL pubblici delle offerte venivano costruiti senza /postings/ (parent #2047) — i link ora atterrano sulla pagina pubblica dell’offerta invece di un 404 per i tenant il cui sito pubblico omette il segmento.

Note

  • La v1.22.0 del parent ha anche introdotto modifiche lato CLI che la web UI non richiama via shell o che già copre: il template CV zh-CN + la tipografia del PDF, la modalità /expand, le ottimizzazioni della cache dei prompt dei provider (Gemini/OpenAI/Ollama), la ripartizione dei token per singolo passo (la web UI ha il proprio contatore di utilizzo), la serializzazione con writer-lock del tracker (la web UI instrada le scritture tramite withFileLock dalla v1.21), i flag CLI visa_filter e data di pubblicazione assoluta per lo scan (la web UI ha il proprio filtro “Pubblicato entro”), e il seeding di deduplica delle sorgenti già viste (lo scanner della web UI mantiene la propria deduplica dello storico delle scansioni).

[1.123.0] — 2026-07-17

Aggiunto

  • Sorgente di scansione Oracle Recruiting Cloud (parità con il parent v1.21.0, #1929) — l’API REST a zero autenticazione recruitingCEJobRequisitions dei siti carriere Oracle Fusion/ORC (JPMorgan Chase, Oracle, BNY Mellon, American Express, Honeywell, …): host ancorato a *.fa[.<region>][.ocs].oraclecloud.com, il numero di sito risolto dal careers_url di ciascuna azienda tracciata, paginazione a offset con un limite massimo di pagine, e header simil-browser consapevoli del WAF. Il registro ora conta 62 adattatori (57 inglesi + 5 russi); il fallback del menu a tendina Source di #/scan e il relativo gate di deriva sono aggiornati; nuova suite isolata per CI tests/sources-oraclecloud.test.mjs.

Corretto

  • Rilevatore di repost: i titoli base restano distinti dai fratelli con suffisso specializzato (parent #1922) — “Senior Analytics Engineer” non si raggruppa più con “Senior Analytics Engineer, People Analytics”: quando i token di un titolo sono un sottoinsieme stretto di quelli dell’altro e il token in più è una specializzazione reale (non una parola di base), le due offerte sono trattate come annunci pubblicabili separatamente. Le annotazioni di repost (“(Repost)”, “relisted”) sono ora trattate come stopword di rumore meta. +2 asserzioni in tests/detect-reposts.test.mjs.

Note

  • La v1.21.0 del parent ha anche introdotto modifiche lato CLI che la web UI non richiama via shell o che già copre: l’avviso di ricandidatura per azienda ripetuta (la web UI ha il cooldown di ricandidatura dalla v1.84.0), i flag --format/--report della lettera di presentazione, le modalità del prompt e-mail per red-flag del colloquio / intelligence sul panel / mancata presentazione, la persistenza dei segnali di fiducia della scansione e della salute dei portali (la web UI esegue il proprio scanner in-process con trust-validator e la pagina di salute dei Portali), e le estensioni di statistiche/salary-gap (inoltrate in sola lettura e fail-soft).

[1.122.0] — 2026-07-16

Aggiunto

  • Hindi (हिन्दी) — la 17ª lingua — dizionario completo dell’interfaccia (~1.110 chiavi), la guida in-app completa (parità 29 H2 / 105 H3), README.hi.md, un nuovo CHANGELOG.hi.md (a partire dalla v1.122.0, seguendo il precedente di de/it/tr), le pagine landing di cvstart.org + Metodologia/Licenza/Changelog/Guida, lo switcher lingua (🇮🇳), il rilevamento automatico della lingua del browser e uno screenshot della dashboard localizzato. Ogni gate di parità ×16 ora gira ×17: parità del dizionario i18n + snapshot, i gate H2/H3 della guida, la parità del changelog, check-i18n della site e lo spazzolamento delle lingue con Playwright.

[1.121.0] — 2026-07-16

Aggiunto

  • cvstart.org: pagine Metodologia, Licenza e Changelog — la landing ha guadagnato tre nuove sezioni in tutte le 16 lingue, accanto al blocco Confronto già esistente: /methodology/ (la griglia di punteggio a sei dimensioni da 0.0–5.0, la soglia di candidatura a 4.0 e le regole del “mai fare” — un riassunto localizzato di career-ops.org/methodology), /license/ (il testo canonico MIT con il rimando a NOTICE.md) e /changelog/ (questo file, reso per singola lingua a partire dai 16 CHANGELOG tradotti del repository). Nuova voce Metodologia nell’header e link Risorse nel footer; sync-assets.mjs ora sincronizza anche il CHANGELOG ×16 e la LICENSE nella site in fase di build, così le pagine non possono mai disallinearsi dal repository.
  • Link alla metodologia in tutta la documentazione — il README (tutte le 16 lingue), l’elenco canonico del §1 della guida in-app (tutte le 16 lingue) e il wiki ora rimandano a career-ops.org/methodology (oltre a FAQ e glossario) accanto alle guide già esistenti su career-ops.org/docs.

Modificato

  • Banner e badge di release del README aggiornati (1850 test, release v1.121.0) — il banner annunciava ancora la v1.119.5.

[1.120.0] — 2026-07-16

Aggiunto

  • Il Manifesto CareerOps (parità con il padre v1.20.0) — il progetto padre ha pubblicato il Manifesto CareerOps (MANIFESTO.md · career-ops.org/manifesto) e ora lo mette in evidenza dal proprio README, dall’updater e dalla dashboard Go. La web-ui fa lo stesso: un nuovo link nel footer della barra laterale apre la pagina del manifesto (nuova chiave i18n footer.manifesto in tutte le 16 lingue), la guida in-app ha guadagnato il §29 «Il Manifesto CareerOps» in tutte le 16 lingue, il README spiega cos’è il manifesto e come firmarlo, e anche il footer della landing cvstart.org vi rimanda.

Note

  • Il padre v1.20.0 ha anche corretto la soppressione delle competenze già note nella modalità mirata upskill, ha reso silenzioso dotenv così che lo stdout di scan --json resti analizzabile, e ha corretto il template HTML del CV in modo che l’intestazione di un ruolo resti insieme ai suoi punti elenco — superfici lato CLI in cui la web-ui non entra tramite shell; non è stata necessaria alcuna modifica al codice della web-ui.

[1.119.5] — 2026-07-13

Corretto

  • Il pulsante lingua della landing non va più a capo — con le bandiere della v1.119.2 l’etichetta dello switcher nell’header (es. «🇷🇺 Русский») poteva spezzarsi fino a tre righe alle larghezze desktop strette; l’etichetta dello switcher e tutte le opzioni del menu ora usano whitespace-nowrap — bandiera + endonimo sempre su una riga. L’elenco delle lingue nel footer è passato da una griglia rigida a due colonne a una riga avvolgente di voci su una riga — anche «🇧🇷 Português (Brasil)» non si spezza più a metà nome.

[1.119.4] — 2026-07-13

Modificato

  • LICENSE nomina l’autore — la riga di copyright ora recita: Sergei Emelianov (Fighter90) https://sergey-cv.com and career-ops-ui contributors (testo MIT canonico intatto). Un nuovo NOTICE.md dettaglia il licenziamento: chi detiene il copyright, cosa copre esattamente la concessione MIT (codice, doc, traduzioni, la landing, la wiki), cosa NON copre (i tuoi dati a runtime, il progetto padre, i contenuti delle job board, i marchi), la tabella dei componenti di terze parti (express/js-yaml — MIT; Astro/Tailwind — MIT; i font Figtree e JetBrains Mono — SIL OFL 1.1; sharp — Apache-2.0) e una riga di attribuzione facoltativa.

[1.119.3] — 2026-07-13

Aggiunto

  • SECURITY.md — la security policy a cui puntava CONTRIBUTING ora esiste: versioni supportate, flusso di segnalazione privata (il private vulnerability reporting di GitHub è ora abilitato sul repo — scheda Security → «Report a vulnerability»), il modello di minaccia per un’app mono-utente su localhost (in scope: XSS via annunci ostili / SSRF / path traversal / fuga di segreti / indebolimento della CSP; fuori scope: DoS del proprio localhost e problemi del progetto padre) e la baseline di hardening per i reviewer.

[1.119.2] — 2026-07-13

Aggiunto

  • CONTRIBUTING.md — la guida del contributore a cui landing e README puntavano da sempre ora esiste: installazione, mappa del progetto, le regole dure sicurezza/no-build, i livelli di test, il walkthrough dei «due registri» per aggiungere una sorgente di scansione, il contratto i18n ×16, le convenzioni di commit/PR e il processo di release.
  • Bandiere delle lingue sulla landing — il selettore di lingue di cvstart.org, la griglia delle lingue nel footer e il banner «leggi nella tua lingua» ora mostrano la bandiera di ogni locale accanto al suo endonimo (lo stesso set di indicatori regionali del <select> di lingua dell’app; degrada a lettere di regione dove mancano i glifi delle bandiere).
  • Correzioni del footer della landing — il link morto a Discussions (funzione non abilitata sul repo) ora punta alla wiki del progetto, e il footer accredita l’autore: Sergei Emelianov (sergey-cv.com · LinkedIn).

[1.119.1] — 2026-07-13

Corretto

  • Il filtro sorgenti di #/scan ha raggiunto il registro — la lista statica FALLBACK_SOURCES dietro il menu a tendina Source (usata solo quando GET /api/scan/sources è irraggiungibile) era silenziosamente indietro dalla v1.87.0: mancavano 20 provider nel fallback offline (Amazon, Avature, SAP SuccessFactors, Get on Board, Dassault Systèmes, beesite, HigherEdJobs, JibeApply (iCIMS), softgarden, Cornerstone, Phenom, Radancy, Deutsche Bahn, EchoJobs, TKMS, Heckler & Koch, Rheinmetall, LaraJobs e i nuovi Meituan / Tencent). Sincronizzata con tutti i 61 e ora protetta da un test di deriva che fa fallire la CI quando la lista client diverge dal registro server (valori E etichette). +1 test (1845).

[1.119.0] — 2026-07-13

Parità con il career-ops padre v1.19.0 + aggiornamento della landing cvstart.org.

Aggiunto

  • 2 nuovi provider di scansione — Meituan (zhaopin.meituan.com) e Tencent (careers.tencent.com): le API JSON pubbliche senza autenticazione delle board tech cinesi, rilevate dall’host o selezionate con un provider: esplicito, con ricerca server-side per parola chiave, paginazione e deduplicazione per URL — ora 61 adapter (56 EN + 5 RU). +20 test (1844).
  • Blocco dei contributori sulla landing — cvstart.org mostra gli avatar di tutti coloro che hanno contribuito con codice (API GitHub /contributors in fase di build, bot filtrati), localizzato nelle 16 lingue, con link al grafo completo dei contributori.
  • Contatore di stelle GitHub live sulla landing — il badge nell’header ora si aggiorna lato client dall’API GitHub a ogni visita (lo snapshot di build resta come fallback), e una ricostruzione settimanale pianificata di Pages mantiene freschi snapshot e lista dei contributori; le chiamate API in CI sono autenticate con token.

Corretto

  • Le richieste Workday CXS portano header da browser (padre #1813) — i tenant dietro Cloudflare (visto dal vivo: geico) rispondono 500 alle richieste prive dei normali UA/accept-language/origin/referer; il fetcher ora deriva origin e slug del site dall’URL CXS stesso. Le richieste Glints hanno guadagnato lo stesso UA da browser + origin/referer, entrambi dalla costante condivisa BROWSER_LIKE_USER_AGENT di http-json.mjs.

[1.118.4] — 2026-07-10

Corretto

  • Le scansioni di hh.ru restituivano 0 risultati da un IP russo (link a sottodominio regionale) — da un IP residenziale russo, hh.ru reindirizza la ricerca (302) verso un sottodominio regionale (sochi.hh.ru, spb.hh.ru, …) e restituisce i link delle offerte su quel sottodominio. Il parser cercava il link del titolo sull’host fisso https://hh.ru/vacancy/ e non ne trovava nessuno tra quelli regionali, quindi una scansione perfettamente funzionante registrava 0 in silenzio. Ora accetta qualsiasi host *.hh.ru (gli annunci su adsrv.hh.ru/click?… restano esclusi — non hanno il percorso /vacancy/<id>) e canonicalizza ogni URL di risultato in https://hh.ru/vacancy/<id>. Verificato dal vivo: 17 offerte reali vengono analizzate da una pagina sochi.hh.ru che prima dava 0. +1 test (1824).

[1.118.3] — 2026-07-10

Corretto

  • hh.ru restituiva silenziosamente 0 risultati (interstitial di verifica VPN) — hh.ru ora reindirizza con 302 le reti che considera VPN/proxy (IP di datacenter) verso un interstitial /vpncheeck (“VPN мешает работе сайта”) che risponde HTTP 200 senza alcuna scheda di vacancy, quindi la scansione riportava 0 senza alcun errore. Lo scanner ora rileva il redirect tramite l’URL finale della risposta, disabilita hh.ru per il resto dell’esecuzione e stampa un suggerimento onesto: il traffico deve davvero uscire da un IP residenziale — un VPN/proxy di sistema può restare attivo anche con l’interruttore del browser spento. +1 test (1823).

[1.118.2] — 2026-07-10

Manutenzione

  • Rifinitura della landing (#118)site/README.md riconciliato con Astro 7 (l’upgrade di sicurezza di #116), import inutilizzato rimosso e +4 guardie eseguibili per gli script di build della landing: il gate di parità i18n fallisce dimostrabilmente con un dizionario rotto e sync-assets non scrive mai fuori da site/ — suite 1822. Risolti due avvisi CodeQL (uno corretto alla fonte, uno respinto come comportamento di build voluto).

[1.118.1] — 2026-07-10

Corretto

  • Scansione di hh.ru fuori dalla Russia — hh.ru ora restituisce HTTP 451 (blocco legale regionale) agli IP non russi sulle pagine pubbliche di ricerca. Lo scanner tratta il 451 come il 403: dopo il primo blocco hh.ru viene disattivato per il resto dell’esecuzione con una riga onesta nel log che indica un IP russo / uscita VPN, senza sprecare le query rimanenti né le altre fonti RU. Guida §7 aggiornata in tutte le 16 lingue. +1 test (1818).

[1.118.0] — 2026-07-09

Pacchetto di parità con il career-ops padre v1.18.0.

Aggiunto

  • 9 nuovi provider di scansione — Cornerstone OnDemand (csod), Phenom (phenom), Radancy (radancy), Deutsche Bahn (deutschebahn), EchoJobs (echojobs), TKMS (tkms), Heckler & Koch (hecklerkoch), Rheinmetall (rheinmetall), LaraJobs (larajobs) — ora 54 adapter. L’adapter di Lever rileva inoltre le board del tenant EU (jobs.eu.lever.co).
  • Stato Hired nel tracker (parità con lo states.yml del padre): le offerte accettate hanno uno stato canonico proprio, un badge celebrativo e un banner «lavoro ottenuto» su #/tracker; funnel e conversioni lo contano come avanzato attraverso tutte le fasi.
  • Scheda Totale in #/stats — relay in sola lettura dello stats.mjs del padre (riepilogo complessivo del tracker, tassi cumulati del funnel, totali dello scanner, copertura dei portali) più le osservazioni sulla retribuzione da salary-gap.mjs (desiderata vs pubblicizzata vs effettiva, per candidatura). Nuove rotte GET /api/stats/lifetime e GET /api/stats/salary-gap — shell-out a zero token, degradazione sicura {available:false} senza il progetto padre.
  • 28 nuove chiavi i18n in tutte le 16 lingue; guida in-app §14/§26 aggiornata in ogni lingua.

Test

  • +38 test unitari (tre suite di parità provider + rotte relay/stato) — 1817 in totale.

[1.117.2] — 2026-07-06

Correzione tracker vuoto per gli shell-out di parità. Gli script del padre escono con codice 1 e un JSON {error} strutturato quando il tracker non ha ancora candidature; la bacheca di follow-up e la scheda pattern lo mostravano come «script-error». Entrambe le rotte ora lo inoltrano come uno stato vuoto sano (available:true, empty:true) e la UI mostra il suo onesto messaggio «ancora niente». Verificato dal vivo contro un padre reale.

Nuovo: nessuno.

[1.117.1] — 2026-07-06

Indurimento di v1.117.0 (triage CodeQL). I tre endpoint shell-out (GET /api/followup, POST /api/followup/seed, GET /api/stats/patterns) portano ora il limitatore per-IP condiviso (creano un processo figlio per richiesta; no-op su loopback). L’estrazione del testo da URL di Aggiungi al CV rimuove i tag fino al punto fisso e poi cancella ogni </> residuo — una sanificazione dimostrabilmente completa per testo di prompt LLM. Nessun cambiamento per input validi.

Nuovo: nessuno.

[1.117.0] — 2026-07-06

Pacchetto di parità col padre — sei capacità del career-ops padre portate nella UI. (1) Bacheca di cadenza su #/followup: urgenza per candidatura (🔴/🟠/🟡/🔵) da followup-cadence.mjs, più il pulsante Semina date (followup-seed.mjs --backfill). (2) Pattern di rifiuto: una quarta scheda Statistiche esegue analyze-patterns.mjs (sola lettura) — distribuzione degli esiti, raccomandazioni, tasso di avanzamento per fornitore ATS. (3) Aggiungi al CV: una scheda di CV Studio trasforma un URL o testo incollato in punti ATS basati SOLO su quella fonte (solo suggerimenti, nessuna scrittura; il fetch dell’URL è protetto anti-SSRF). (4) 4 nuovi provider di scansione — beesite, HigherEdJobs (RSS), JibeApply (iCIMS), softgarden — il registro conta ora 50 adattatori (45 EN + 5 RU), tutti nel menu del Scan. (5) Passo di pre-scansione dei disqualificatori nella checklist Apply. (6) Runner reconcile (/api/run/reconcile). Le rotte shell-out degradano onestamente senza gli script del padre.

  • Nuovo modulo server/lib/routes/followup.mjs (31º) + nuove rotte + 8 file source/adapter. Test: 6 + 7 nuovi; suite 1737 → 1750. 41 chiavi i18n ×16. Aiuto §13/§17/§24/§26 esteso ×16.

Nuovo: GET /api/followup, POST /api/followup/seed, GET /api/stats/patterns, POST /api/cv-studio/add-entry, POST /api/run/reconcile.

[1.116.0] — 2026-07-06

Contatore di utilizzo rifatto + primo test end-to-end dei widget. Il contatore di utilizzo IA (v1.114.0) è corretto e ancorato correttamente: ora è fissato in fondo alla barra laterale sinistra (larghezza piena, stessa superficie) e riserva in basso uno spazio pari alla propria altezza così che il menu non venga mai coperto — la navigazione e il piè di versione scorrono sempre liberi sopra di esso. Si aggiorna dal vivo (ogni 15 s, al focus della scheda e al cambio di rotta), e ogni riga di finestra mostra ora i <token> · <costo stimato> reali (le barre si scalano rispetto alla finestra di 30 giorni) invece di una “quota” sempre al 100%. Inoltre: una barriera typeof durevole nell’importatore del CV chiude alla fonte il falso positivo ricorrente di type-confusion di CodeQL, e un nuovo test end-to-end Playwright guida entrambi i widget persistenti in un browser reale.

  • public/js/lib/usage-hud.js + app.css, server/lib/cv-import.mjs. Test: tests/playwright-widgets.mjs (2 E2E) + tests/usage-hud.test.mjs (10). Aiuto §6 esteso ×16.

Nuovo: nessuno.

[1.115.0] — 2026-07-06

Rifinitura del design (conservativa, brand corallo mantenuto). Un passaggio leggero di rifinitura sul sistema di design condiviso — nessuna ristrutturazione, nessun cambio di palette. Le schede metriche della dashboard ora si sollevano e prendono un bordo corallo al passaggio del mouse (come i riquadri di azione rapida); le schede di contenuto si sollevano di poco; i pulsanti primary / dark / danger guadagnano un’ombra a riposo e un lieve sollevamento all’hover per profondità; i numeri grandi si allineano con tabular-nums; e i controlli interattivi ricevono un alone corallo morbido dietro il nitido anello da tastiera di 2px. Tutto il movimento rispetta prefers-reduced-motion, e l’alone è limitato ai controlli — mai un *:focus-visible globale.

  • Solo CSS (public/css/app.css); nessuna modifica a markup, i18n, rotte o CSP. Test: tests/design-polish-v1115.test.mjs (5). Verificato dal vivo con Playwright.

Nuovo: nessuno.

[1.114.0] — 2026-07-06

Contatore di utilizzo e costo dell’IA nella barra laterale (in basso a sinistra). Una sezione UTILIZZO compatta ora si trova in fondo alla barra laterale (una scheda fissa in basso a sinistra se non c’è barra laterale; in basso a destra in RTL) su ogni pagina. Mostra l’uso di token LLM su finestre 24h / 7g / 30g — ciascuna come <token> · <quota%> con una barra verde (quota sul totale) — più un piè di pagina con il costo stimato delle 24h. I dati sono il riepilogo di sola lettura GET /api/usage di data/llm-usage.jsonl (solo locale), la stessa fonte della pagina #/usage; il costo è una stima e le esecuzioni in modalità manuale sono gratuite e non conteggiate. Comprimibile — l’intestazione commuta e lo stato persiste.

  • Nuovo widget client public/js/lib/usage-hud.js caricato da index.html, montato nella barra laterale sopra il piè di versione (fallback ad angolo fisso). Sicuro per la CSP; adattivo al tema e speculare RTL. Nessuna nuova rotta server. Test: tests/usage-hud.test.mjs (8). 3 nuove chiavi i18n ×16.

Nuovo: nessuno.

[1.113.0] — 2026-07-06

Assistente fluttuante “Chiedi alla guida” su ogni pagina. Un pulsante di chat con un robot in gradiente ora fluttua nell’angolo in basso a destra (in basso a sinistra in RTL) di ogni pagina. Toccalo per aprire una chat compatta che risponde a domande d’uso basandosi SOLO sulla guida di aiuto integrata nella tua lingua — lo stesso endpoint della pagina #/docs-assistant (POST /api/docs-assistant/ask), quindi non legge mai il tuo CV, profilo o tracker. Dal vivo con una chiave LLM; senza chiave → un prompt pronto all’uso. L’intestazione mostra un avatar robot + stato online; i chip avviano domande comuni; Esc o clic esterno chiude; si nasconde sulla pagina #/docs-assistant.

  • Nuovo widget client public/js/lib/docs-fab.js montato globalmente da index.html; sicuro per la CSP; stili adattivi al tema e speculari RTL in app.css. Nessuna nuova rotta server. Test: tests/docs-fab.test.mjs (8). 6 nuove chiavi i18n ×16. Aiuto §1 esteso sul posto.

Nuovo: nessuno.

[1.112.0] — 2026-07-06

Consolidamento docs & QA. Nessuna modifica di codice visibile. Il documento di convenzioni SDD (docs/sdd/CONVENTIONS.md) è aggiornato agli attuali 30 moduli di rotta (erano 24) e alla baseline di test attuale; il prompt QA definitivo dell’intero progetto (qa/QA-REGRESSION-PROMPT.md) è consolidato — meccanica di release ripulita (v1.111, parentVersion 1.17.0, pubblicazione attivata dall’evento di release), la tabella delle aggiunte §14 corretta (Escludi di Scan rietichettato v1.109.0) ed estesa con la chiusura CodeQL di v1.111 — così da bastare da solo come unico prompt di regressione per tutte le funzionalità. Aggiunge un test di copertura per il ramo di caricamento sovradimensionato.

Nuovo: nessuno.

[1.111.0] — 2026-07-06

Sicurezza — chiusura del backlog CodeQL. Tre rafforzamenti difesa-in-profondità che chiudono i restanti rilievi dell’analisi statica alla fonte invece di scartarli. stripDangerousMarkdown ora fa l’escape del < di qualsiasi apertura di tag pericoloso troncata (un payload che termina con <script/<iframe/…), così che il suo output non contenga in modo dimostrabile alcun tag pericoloso vivo. L’import del CV legge la dimensione del buffer caricato tramite una coercizione esplicita Number() — una barriera contro la confusione di tipi. Le righe di ruolo delle modalità ora sono stringhe template interpolate con String.replace invece di funzioni memorizzate, rimuovendo del tutto la chiamata a dispatch dinamico. Nessun cambiamento di comportamento visibile all’utente.

  • server/lib/security.mjs, server/lib/cv-import.mjs, server/lib/prompts.mjs. Test: tests/security-hardening-v1111.test.mjs (7) + test di guardia v1108 aggiornato. Nessun cambiamento i18n/aiuto/rotte.

Nuovo: nessuno.

[1.110.0] — 2026-07-06

Aggiornamento docs & QA (tutte le lingue). Nessuna modifica al codice. Il prompt QA dell’intero progetto è aggiornato a v1.109.0 con un nuovo §14 (v1.98→v1.109), e i prompt perenni UX-audit e design-export hanno la superficie di pagine attuale. Ogni paragrafo di aiuto aggiunto in v1.100–v1.109 è ora tradotto in tutte le 16 lingue.

Nuovo: nessuno.

[1.109.0] — 2026-07-06

Filtro Escludi in Scan + panoramica pipeline (parità layout web). Su #/scan, la casella Cerca ora tratta le virgole come OR (“ruoli da trovare”) e un nuovo campo Escludi nasconde ogni riga la cui azienda/ruolo/luogo contiene una delle parole separate da virgole (es. senior, staff); entrambi sono ricordati dalle ricerche salvate. Su #/pipeline, una striscia di panoramica compatta mostra la pipeline a colpo d’occhio — N in arrivo, N tracciati e i conteggi Applied / Responded / Interview / Offer dal tracker, ogni chip collega a #/tracker.

  • Solo client (nessuna nuova rotta/scrittura). public/js/views/scan.js + public/js/views/pipeline.js. Test: tests/scan-pipeline-ui-v1109.test.mjs (2). 4 nuove chiavi i18n ×16. Aiuto §7 + §8 estesi sul posto.

Nuovo: nessuno.

[1.108.0] — 2026-07-06

Rafforzamento della sicurezza (triage CodeQL, round 2). Corrette altre tre vulnerabilità di bassa gravità: il costruttore di prompt risolve la riga di ruolo della locale per chiave propria + typeof === function così che una locale manomessa non possa invocare un metodo del prototipo (unvalidated-dynamic-method-call); lo slug del nome file PDF è limitato a 200 caratteri prima del regex così che un input di soli trattini non torni indietro (ReDoS polinomiale); e l’importazione documenti forza un filename array (header ripetuto) a stringa (type-confusion). Nessun cambiamento di comportamento per input valido.

  • server/lib/prompts.mjs, server/lib/routes/runners.mjs, server/lib/cv-import.mjs + tests/security-hardening-v1108.test.mjs (3). Su v1.106–v1.108 l’arretrato dell’analisi statica è passato da 167 a ~14, con ogni risultato realmente rilevante per la sicurezza corretto e il resto (falsi positivi protetti/sanificati + lint di livello nota) respinto con motivazione.

Nuovo: nessuno.

[1.107.0] — 2026-07-06

Rafforzamento del sanitizzatore (difesa in profondità XSS a riposo). stripDangerousMarkdown — che neutralizza l’HTML pericoloso nel markdown di CV/annuncio memorizzato affinché qualsiasi consumatore che aggiri il client con escape-al-rendering resti sicuro — ora esegue la pulizia dei tag fino a un punto fisso (ripeti fino a stabilizzarsi) così che una rimozione che riforma un payload (es. <scr<script></script>ipt>) venga intercettata, corrisponde ai tag di chiusura script/style ecc. con residui finali (</script foo>) e rimuove un apertore eseguibile non chiuso (<script …>). Il comportamento per markdown valido è invariato — rimuove solo di più.

  • server/lib/security.mjs: ciclo a punto fisso (limitato a 8 passaggi) + pattern di chiusura [^>]*> + rimozione dell’apertore non chiuso. +3 casi di regressione in tests/cv-xss-bypasses.test.mjs. Il confine XSS autorevole resta l’escape in output (UI.md); questo rafforza la garanzia a riposo e chiude i risultati CodeQL corrispondenti.

Nuovo: nessuno.

[1.106.0] — 2026-07-06

Rafforzamento della sicurezza (triage CodeQL). Corrette tre vulnerabilità reali (seppur di bassa gravità) dopo una passata sull’arretrato dell’analisi statica: il percorso di errore del rendering ora effettua l’escape del messaggio di errore prima che raggiunga il DOM (un errore del server può riflettere input dell’utente, quindi trattato come non attendibile — confine XSS), e le scritture di proprietà di profilo/config rifiutano le chiavi __proto__ / constructor / prototype (protezioni anti prototype-pollution per sicurezza — le chiavi provengono da specifiche di campo fisse, non da input grezzo). La maggior parte degli avvisi rimanenti sono falsi positivi sulle letture/scritture legittime dello scanner in data/* e su rotte che già portano il limiter interno; respinti con motivazione.

  • public/js/router.js effettua l’escape di err.message via UI.escapeHtml prima di innerHTML; server/lib/routes/content.mjs e server/lib/routes/config.mjs proteggono le chiavi di prototipo. Nessun cambiamento di comportamento per input valido. Test: tests/security-hardening-v1106.test.mjs (3). Nessuna nuova chiave i18n.

Nuovo: nessuno.

[1.105.0] — 2026-07-06

Pagina uso e costo IA. Una nuova pagina Uso IA (barra laterale, accanto a Salute) mostra quanti token hai speso in generazioni IA live — valutazioni, report, chat — suddivisi per provider nelle ultime 24 ore, 7 giorni, 30 giorni e sempre, con un costo stimato in USD. Ogni chiamata live aggiunge un piccolo record {provider, in, out} a data/llm-usage.jsonl (nulla viene inviato da nessuna parte); le esecuzioni senza chiave (modalità manuale) non costano nulla e non vengono registrate.

  • Nuovo modulo di rotta (il 30°) server/lib/routes/usage.mjsGET /api/usage (aggregati in sola lettura) + server/lib/llm-usage.mjs (recordUsage normalizza le forme d’uso di Anthropic/OpenAI/Gemini e aggiunge in best-effort; readUsage/aggregate aggregano per finestra 24h/7g/30g/tutto × provider) + server/lib/llm-pricing.mjs (una tabella prezzi modificabile per provider $/1M token — i token sono esatti, i dollari sono prezzi di listino approssimativi che puoi correggere; mai fatturati). La registrazione è agganciata ai punti di dispatch (runActiveProvider + routes/llm.mjs).
  • Nuova vista public/js/views/usage.js (#/usage, schede finestra). Test: tests/usage-routes.test.mjs. 17 nuove chiavi i18n ×16 (usage.* + nav.usage). Aiuto §6 esteso sul posto.

Nuovo: server/lib/routes/usage.mjs; server/lib/llm-usage.mjs; server/lib/llm-pricing.mjs; public/js/views/usage.js.

[1.104.0] — 2026-07-06

Logo aziendali nella tabella di scansione (rispettosi della privacy). Un nuovo interruttore Aspetto nelle Impostazioni appMostra i logo delle aziende nella tabella di scansione (disattivato per impostazione predefinita) — disegna il logo di ogni azienda accanto al nome su #/scan. Il logo è la favicon dell’azienda recuperata dal suo dominio e messa in proxy lato server (GET /api/logo), così nessun servizio di logo di terze parti scopre quali datori di lavoro stai guardando. Gli annunci su un portale di lavoro condiviso (Greenhouse, Lever, Ashby, …) mostrano un badge con una lettera colorato invece dell’icona del portale, e qualsiasi logo che non si carica ricade sullo stesso badge.

  • Nuovo modulo di rotta (il 29°) server/lib/routes/logos.mjsGET /api/logo?domain=. Valida il dominio (senza schema/percorso/loopback), recupera /favicon.ico tramite il safeGet sicuro contro l’SSRF (una nuova modalità binary restituisce i byte grezzi + content-type; DNS-pinning, validazione dei redirect e limite di dimensione invariati), esegue uno sniffing della firma dell’immagine per non servire mai una pagina HTML di errore come immagine, mette in cache successi e fallimenti in un LRU in memoria e non scrive nulla su disco.
  • Nuova lib client public/js/lib/company-logo.js (window.CompanyLogo): disattivata per impostazione predefinita tramite flag in localStorage; salta gli host ATS condivisi a favore di un avatar-lettera deterministico; ripiego img.onerror sicuro per la CSP. Test: tests/logo-routes.test.mjs. 5 nuove chiavi i18n ×16 (appear.*). Aiuto §2 esteso sul posto.

Nuovo: server/lib/routes/logos.mjs; public/js/lib/company-logo.js.

[1.103.0] — 2026-07-06

Impostazioni: “Strumenti CLI IA” — quali sono installati. career-ops è basato su Claude Code ma funziona con qualsiasi CLI di agente conforme allo standard aperto di skills. Una nuova scheda Strumenti CLI IA nelle Impostazioni app (#/config) mostra quali — Claude Code, Codex, Gemini CLI, OpenCode, GitHub Copilot CLI, Qwen, Antigravity — sono installati sulla macchina che esegue il server, e i loro percorsi. È una scansione del PATH in sola lettura: verifica solo se ogni binario esiste e non lo esegue mai (nessun --version, nessuna esecuzione), non scrive nulla e non tocca dati utente.

  • Nuovo modulo di rotta (il 28°) server/lib/routes/cli-detect.mjsGET /api/cli-detect. Il rilevamento risolve il percorso di un binario da una allowlist fissa di 7 voci tramite process.env.PATH (shim .cmd/.exe/.bat su Windows; bit di esecuzione su POSIX); un file ostile sul PATH non può mai essere eseguito da questa rotta.
  • Nuova scheda “Strumenti CLI IA” in public/js/views/config.js (caricamento lazy, deep-link via #/config?tab=cli). Test: tests/cli-detect-routes.test.mjs. 8 nuove chiavi i18n ×16 (cli.* + config.tabCli). Aiuto §2 esteso sul posto.

Nuovo: server/lib/routes/cli-detect.mjs.

[1.102.0] — 2026-07-05

“Chiedi alla guida” — una chat fondata sulla guida di aiuto integrata. Una nuova pagina Chiedi alla guida 💬 (barra laterale, sotto Aiuto): scrivi una domanda come “Come faccio a scansionare i portali di lavoro?” e ottieni una risposta tratta solo dalla guida di aiuto dell’app nella tua lingua — mostra quali sezioni ha usato e non legge mai il tuo CV, profilo o la tua ricerca di lavoro. Riguarda come usare l’app, non te. Con una chiave LLM risponde in tempo reale; senza chiave ti consegna un prompt pronto, già riempito con le sezioni di aiuto pertinenti.

  • Nuovo modulo di rotta (il 27°) server/lib/routes/docs-assistant.mjsPOST /api/docs-assistant/ask. Recupero senza dipendenze: la guida nella tua lingua è divisa nelle sue sezioni ## e valutata per sovrapposizione di parole chiave con la domanda; le migliori vengono incluse e il modello deve rispondere da esse o dire che la guida non lo copre (nessuna funzione/rotta inventata). Cascata di provider condivisa, ripiego manuale, con limite di frequenza, senza scritture, non legge dati utente.
  • Nuova vista public/js/views/docs-assistant.js. Test: tests/docs-assistant-routes.test.mjs. 14 nuove chiavi i18n ×16 (docs.* + nav.docsAssistant). Aiuto §1 esteso sul posto.

Nuovo: server/lib/routes/docs-assistant.mjs; public/js/views/docs-assistant.js.

[1.101.0] — 2026-07-05

CV Studio: adatta il tuo CV + scrivi una lettera di presentazione per un lavoro specifico, con un controllo in stile recruiter. Nuova scheda Adatta a un lavoro su #/cv-studio: incolla una descrizione di lavoro (e, facoltativamente, un ruolo/titolo target) e CV Studio produce un CV adattato a quell’annuncio più una lettera di presentazione coerente, poi li passa attraverso un controllo prima di consegnarli — gli error bloccano (corretti prima che tu veda il risultato), i warn consigliano. La meccanica è distillata dalla pratica del career coaching in regole generiche — un recruiter legge in secondi, quindi l’esperienza rilevante va in alto, il titolo corrisponde al ruolo dell’annuncio, i risultati portano numeri specifici e la lettera resta un teaser breve con un unico ponte “requisito ↔ il tuo fatto corrispondente”. Si basa solo sul tuo CV, profilo e two-pager e non inventa mai — nessuna azienda, ruolo o storia hardcoded.

  • Nuovo endpoint POST /api/cv-studio/tailor (estende il modulo cv-studio esistente — nessun 27° modulo): buildTailorPrompt + un controllo generico TAILOR_INSTRUCTIONS, basato su bundleProjectContext, cascata di provider condivisa, ripiego manuale senza chiave, con limite di frequenza, senza scritture. Il risultato si esporta in Markdown / PDF / DOCX tramite la barra condivisa report-export.js.
  • Test: +3 in tests/cv-studio-routes.test.mjs. 10 nuove chiavi i18n ×16 (cvs.tailor*). Riferimento generico docs/prompts/resume-cover.md. Aiuto §24 esteso sul posto.

Nuovo: docs/prompts/resume-cover.md.

[1.100.0] — 2026-07-05

Two-pager: compilazione automatica con IA dal tuo CV + Anteprima + esportazione in PDF/DOCX/Markdown. Il two-pager (#/two-pager) raccoglie ciò che vuoi davvero dal prossimo ruolo, ma finora ogni campo andava scritto a mano o copiando un prompt in un altro strumento. Ora l’✨ assistente di compilazione IA viene eseguito in tempo reale con il provider configurato — legge solo il tuo CV + profilo (tramite bundleProjectContext, senza inventare nulla), redige tutti i campi (chi sono / cosa amo / irrinunciabili / cosa detesto / deal-breaker / non negoziabili / ambiente target) e compila il modulo perché tu lo riveda, modifichi e salvi. Senza chiave API torna alla finestra copia-il-prompt come prima. Un nuovo pulsante 👁 Anteprima ed esporta rende il two-pager come documento formattato con una barra Scarica .md / Salva come PDF / Salva come DOCX / Copia.

  • Esportazione .docx senza dipendenze. Nuovo server/lib/docx.mjs che produce un .docx Office Open XML minimo ma valido (uno ZIP DEFLATE delle quattro parti OOXML, con CRC-32 per voce) — senza nuova dipendenza runtime (le deps restano express + js-yaml). Nuova rotta POST /api/export/docx (server/lib/routes/export.mjs, il 26° modulo di rotte; stateless, limitato a 200 KB, senza scritture / senza LLM / senza fetch di URL). Integrato nel condiviso public/js/lib/report-export.js, quindi il report di mercato, il piano di carriera e l’orientamento professionale ottengono anch’essi l’esportazione DOCX.
  • La compilazione automatica in tempo reale usa la cascata di provider condivisa (runActiveProvider / providerAvailable); lo YAML restituito viene analizzato e ricondotto alla forma limitata del two-pager (parseYamlFields + normalizeTwoPager) — chiavi sconosciute scartate, array/stringhe limitati. Modalità manuale preservata.
  • Test: tests/export-routes.test.mjs. 4 nuove chiavi i18n ×16 (export.saveDocx, twoPager.preview, twoPager.aiFilling, twoPager.aiFilled).

Nuovo: server/lib/docx.mjs; server/lib/routes/export.mjs.

[1.99.0] — 2026-07-05

Pagina salute dei portali (#/portals). Lo scanner sorveglia un insieme di aziende in portals.yml; uno slug ATS può rompersi silenziosamente e quel datore di lavoro sparisce da ogni scansione futura. La nuova pagina Portals elenca ogni azienda sorvegliata e, con Check portal health, sonda ogni careers_url tramite il safeGet con DNS ancorato (anti-SSRF) e segnala quelle morte (un 404 = scartata in silenzio) — sola lettura. Rafforza anche il segnalatore di bug della v1.98.0 dopo la revisione: il buffer degli errori ora cattura i fallimenti di rete del fetch e lo scrubber oscura le chiavi provider senza etichetta.

Nuovo: server/lib/routes/portals.mjs; public/js/views/portals.js.

[1.98.0] — 2026-07-05

Segnalatore di bug integrato (parità con il web web-v0.2.0 del progetto padre). Un pulsante 🐞 Report a bug nel cassetto delle notifiche raccoglie un’istantanea diagnostica con soglia di privacy — versioni, il tuo schermo, browser, un riepilogo dei controlli di /api/health e gli ultimi 20 errori da un nuovo buffer circolare lato client — più un’impronta di deduplicazione deterministica (co-web-<base36>), ti fa rivedere il Markdown esatto e poi apre una issue GitHub precompilata. Nulla viene inviato automaticamente; non trasporta mai il tuo CV, profilo, risposte, URL di lavoro o chiavi. Nuove lib logbuf.js + bug-report.js; 11 chiavi i18n ×16; tests/bug-report.test.mjs.

Nuovo: public/js/lib/logbuf.js; public/js/lib/bug-report.js.

[1.97.1] — 2026-07-05

Corretto

  • Consolidamento guidato dalla revisione e parità della documentazione (seguito della v1.97.0). Un passaggio sui log di revisione dell’IA ha fatto emergere correzioni reali:
  • fit-score.js (badge di fit della scansione). salaryFloor() non promuove più una tariffa infra-annuale a un falso minimo annuale — «at least 500 EUR/day», «$80/hr», «6000 monthly» ora restituiscono null invece di un fattore eliminatorio da 500k/80k. La corrispondenza dei paesi è ora a parola intera (\b…\b), così «Germany» non corrisponde più all’aggettivo «German» (né «Nigeria» dentro «Nigerian») e non scatena una falsa violazione di indispensabile-altrove. +3 test in tests/fit-score.test.mjs.
  • Parità della documentazione. Ogni README localizzato ora pubblicizza 16 lingue in modo coerente — il conteggio/l’elenco della riga Aiuto (×13) e la prosa della sezione Localizzazione più la nota «aggiungi la chiave a tutti gli N file» (×8) erano ancora sui conteggi precedenti alla v1.85 (8/9). Il conteggio degli adattatori dell’aiuto integrato §17 è corretto a 46 adattatori — 41 in inglese + 5 in russo in tutti i 16 pacchetti.
  • Nessun cambiamento di comportamento oltre all’euristica del badge di fit; nessuna nuova route, chiave o aggiunta i18n.

[1.97.0] — 2026-07-05

Aggiunto

  • Sorgente di scansione Dassault Systèmes + una revisione della qualità su tre fronti.
  • Nuova sorgente di scansione — Dassault Systèmes (parità con il career-ops principale, #1498). server/lib/sources/dassault.mjs + server/lib/portals/adapters/dassault.mjs rispecchiano il provider Exalead di «ricerca a schede» a costo zero in token del progetto principale (il feed pubblico dietro 3ds.com/careers/jobs). È un unico endpoint globale, quindi è selezionato per provider (provider: dassault) oppure rilevato automaticamente da un host 3ds.com, con l’host ancorato contro l’SSRF a www.3ds.com tramite redirect:'error'. L’XML viene analizzato senza DOM (mappe <Meta> per ogni <Hit>), città/paese vengono estratti dalla stringa di categoria localizzata, e le offerte vengono mantenute solo quando il loro URL pubblico è su *.3ds.com. Il registro ora include 46 adattatori (41 EN + 5 RU); il conteggio di ALL_ADAPTERS, le asserzioni di id ordinato e dell’insieme EN di /api/scan/sources passano da 40 → 41. Suite tests/sources-dassault.test.mjs (10 casi).
  • Correzioni di robustezza portate dal progetto principale. Il parser di Avature ora tollera due varianti di markup dei tenant in produzione (article--result con un suffisso di indice di posizione + un’ancora di titolo JobDetail senza classe, #1541); Get on Board si protegge da un published_at 0/negativo (niente più date fasulle del 1970); SuccessFactors limita l’ultima pagina in modo che non possa superare MAX_JOBS (#1528).
  • Correzioni dell’audit del server. safe-fetch non si blocca più su una risposta oltre il limite — il percorso del limite di dimensione ora risolve la promise direttamente invece di attendere un evento 'end' che uno stream distrutto non emette mai (corregge i recuperi di pagine grandi su /api/pipeline/preview + auto-pipeline). Il logging di attività SSE stream.* è di nuovo raggiungibile (il controllo di /api/stream/ è stato spostato sopra la guardia generale «salta GET»).
  • Correzioni dell’audit della SPA. Il selettore di schede di #/stats si protegge da una corsa di rendering asincrona — il risultato di una scheda lenta non può più sovrascrivere una scheda più recente a cui l’utente è già passato. Le conferme di eliminazione del colloquio simulato e del networking ora passano un titolo + corpo adeguati (niente più finestra di dialogo con corpo vuoto).
  • Correzioni di traduzione. Corretti valori del dizionario non tradotti — ucraino config.modes* (Adaptive Framing / Exit Narrative / Location Policy), russo eval.jdLbl («Job Description»), italiano dash.quick.contactoSub («referral» → «segnalazione») — oltre alla localizzazione del testo fisso inglese **16 locales** nei CHANGELOG di ru/uk/ja/ko/zh-CN/zh-TW.
  • Nuovo: server/lib/sources/dassault.mjs; server/lib/portals/adapters/dassault.mjs.

[1.96.0] — 2026-07-04

Aggiunto

  • Orientamento professionale (Epic 27). Una nuova pagina #/orientation risponde alla domanda «quali direzioni fanno davvero per me?» — la lettura che otterresti da un test di orientamento, ma dedotta dal tuo stesso CV e profilo anziché da un questionario. Fai clic su Genera profilo e il modello restituisce i tuoi vettori di carriera più adatti (quali degli otto archetipi — Funzionalista, Amministratore, Comunicatore, Specialista, Analista, Innovatore, Manager, Imprenditore — si adattano, con evidenze), una inclinazione di tipo professionale, ruoli consigliati, punti di forza professionali legati al tuo CV, tendenze di stile di lavoro e raccomandazioni di sviluppo. È una riflessione dell’IA su come si legge il tuo CV — non un test psicometrico: non inventa mai risultati e non riporta mai punteggi numerici come se fossero misurati. Esportalo in Markdown o PDF; nulla viene scritto sul disco.
    • Nuova route server/lib/routes/orientation.mjs (24° modulo di route) — POST /api/orientation/generate costruisce il prompt del profilo da CV+profilo+two-pager+memoria tramite la cascata di provider condivisa, con un fallback manuale da copiare e incollare e nessuna scrittura di file.
    • Riutilizza report-export.js per Markdown/PDF/copia, all’interno del gruppo di navigazione Sviluppo.
    • Test: tests/orientation-routes.test.mjs (delimitazione a riflessione / nessun punteggio inventato, modo manuale con seed da CV/profilo). 7 nuove chiavi i18n ×16 lingue, Aiuto §28 ×16.
  • Nuovo: #/orientation; server/lib/routes/orientation.mjs.

[1.95.0] — 2026-07-04

Aggiunto

  • Piano di carriera (Epic 26). Una nuova pagina #/career-plan trasforma il tuo CV e il tuo profilo in un piano di sviluppo concreto e personalizzato. Scegli un orizzonte (6/12/24 mesi) e un focus opzionale, e il modello — leggendo il tuo CV, il profilo, il two-pager e la nota di memoria — scrive un’istantanea del punto di partenza, una SWOT di punti di forza/crescita, obiettivi come SMART / OKR / WOOP, traiettorie alternative, un piano di competenze hard/soft, una roadmap mese per mese, metodi di monitoraggio dei progressi, insidie e mosse di supporto. Pianifica in avanti a partire da ciò che i tuoi materiali mostrano davvero e non inventa mai fatti sulla tua storia. Modificalo inline, Salvalo nel livello utente (config/career-plan.md) ed esportalo in Markdown o PDF.
    • Nuova route server/lib/routes/career-plan.mjs (23° modulo di route) — GET/PUT /api/career-plan (scrive config/career-plan.md) + POST /api/career-plan/generate (cascata di provider condivisa, fallback manuale, nessuna invenzione). PATHS.careerPlan.
    • Riutilizza l’helper condiviso report-export.js (v1.94.0) per Markdown/PDF/copia, e un nuovo gruppo di navigazione Crescita.
    • Test: tests/career-plan-routes.test.mjs (delimitazione, round-trip GET/PUT, prompt consapevole dell’orizzonte e con seed da CV/profilo). 20 nuove chiavi i18n ×16 lingue, Aiuto §27 ×16.
  • Nuovo: #/career-plan; server/lib/routes/career-plan.mjs; PATHS.careerPlan.

[1.94.0] — 2026-07-04

Aggiunto

  • Statistiche, rielaborate (Epic 25). La pagina #/stats è ora una sezione Statistiche a tre schede, con grafici veri e molti più dati. Una nuova scheda Report di mercato chiede al modello un’analisi delle retribuzioni e del mercato del lavoro per i tuoi ruoli target in una regione e valuta che scegli — sintesi esecutiva, retribuzione per livello con percentili P10/P25/P75/P90, principali datori di lavoro, una tabella delle competenze richieste, frequenza dei benefit, la ripartizione ufficio/ibrido/remoto, tendenze a 12–24 mesi e indicazioni per la negoziazione. Ogni cifra è etichettata come una stima orientativa dalla conoscenza del modello, mai presentata come dati estratti. Una nuova scheda La mia pipeline rappresenta il tuo tracker: distribuzione dei punteggi, imbuto degli stati, principali aziende e ruoli, candidature nel tempo e tassi di conversione. La vista originale dei ruoli target (posti vacanti/retribuzione per paese + tendenza degli snapshot salvati) si sposta in una terza scheda, ora con un selettore di valuta e una panoramica annunci-per-ruolo.
    • Esporta qualsiasi report in Markdown o PDF, oppure copialo — tramite l’helper condiviso report-export.js (download del blob Markdown; PDF tramite l’esistente runner inline-PDF).
    • Nuova route server/lib/routes/market.mjs (22° modulo di route) — POST /api/stats/market costruisce un prompt di analisi di mercato dal tuo CV/profilo (così conosce i tuoi ruoli target), regione e valuta, lo esegue attraverso la cascata di provider condivisa e ripiega su un prompt copia-e-incolla senza chiave. Nessuna scrittura di file.
    • Test: tests/market-routes.test.mjs (delimitazione regione/valuta, prompt etichettato per onestà, modalità manuale con seed da CV/profilo). 36 nuove chiavi i18n ×16 lingue, Aiuto §26 ×16.
  • Nuovo: #/stats rielaborata in schede; server/lib/routes/market.mjs; public/js/lib/report-export.js.

[1.93.0] — 2026-07-04

Aggiunto

  • Livello di memoria (Epic 24). Una nuova pagina #/memory conserva una breve nota modificabile «ricorda questo su di me» che l’assistente tiene a mente in ogni attività:
    • Una nota, ovunque — poiché è inserita in bundleProjectContext, la nota raggiunge automaticamente ogni richiesta AI (valutazione, colloquio simulato, networking, CV Studio) su tutti i provider. Scrivila una volta; orienta tutto.
    • Orientamento, non fatti — cattura le tue preferenze e il modo in cui ti piace lavorare (tono, formato, deal-breaker, cadenza), mai nuove affermazioni fattuali sulla tua esperienza — quelle vivono ancora solo nel tuo CV, nel tuo profilo e nel tuo two-pager. Salvata nel livello utente in config/memory.md, mai sovrascritta dagli aggiornamenti.
    • Suggerisci dai tuoi datiPOST /api/memory/suggest esamina il tuo tracker delle candidature alla ricerca di schemi comportamentali e abbozza punti elenco che puoi rivedere e modificare. Legge il tuo tracker; non inventa mai fatti e non effettua alcuna chiamata live.
  • Nuovo: server/lib/routes/memory.mjs (21° modulo di route — GET/PUT /api/memory + POST /api/memory/suggest), public/js/views/memory.js, PATHS.memory e un blocco config/memory.md aggiunto a bundleProjectContext. 11 nuove chiavi i18n in tutte le 16 lingue. Test: tests/memory-routes.test.mjs.

[1.92.0] — 2026-07-04

Aggiunto

  • CV Studio (Epic 21). Una nuova pagina #/cv-studio offre al tuo CV tre strumenti onesti e per lo più locali:
    • Diagnostica del curriculum — un punteggio deterministico da 0 a 100 con spiegazioni per ogni controllo (impatto quantificato, verbi deboli, buzzword, lunghezza, sezioni fondamentali, informazioni di contatto). Puramente lato client (window.CvDiagnostics) — nessun LLM, nulla di inventato, ogni riscontro spiegato così che tu decida cosa cambiare.
    • Maschera privacy — oscura i PII (email, telefono, link/handle, indirizzo civico e facoltativamente il tuo nome → iniziali) prima di condividere il tuo CV come campione o screenshot. Gira interamente nel browser (window.CvPrivacy); segnala esattamente cosa ha oscurato e non conserva mai l’originale.
    • Rendilo umano / abbina la voce — incolla una riga o un paragrafo rigido e riscrivilo nella tua voce, ancorato lato server a voice-dna.md e writing-samples/. Guardrail rigido: può riordinare, snellire e rimodulare la voce, ma non introduce mai un fatto, una metrica o un risultato non già presente nel testo. Gira live tramite la cascata condivisa dei provider, oppure restituisce un prompt da copiare-incollare senza chiave.
  • Nuovo: server/lib/routes/cv-studio.mjs (20° modulo di route — POST /api/cv-studio/humanize), public/js/views/cv-studio.js, public/js/lib/cv-diagnostics.js, public/js/lib/cv-privacy.js, PATHS.voiceDna + PATHS.writingSamplesDir. 29 nuove chiavi i18n in tutte le 16 lingue. Test: tests/cv-diagnostics.test.mjs, tests/cv-studio-routes.test.mjs. (La galleria di modelli, l’esportazione Word e l’archivio PDF degli annunci sono tracciati come lavoro successivo di CV Studio.)

[1.91.0] — 2026-07-04

Aggiunto

  • Networking e ricerca approfondita sulle aziende (Epic 16). Una nuova pagina #/networking trasforma un’azienda in un piano attuabile per ottenere un colloquio, ancorato al tuo CV, al profilo e al two-pager:
    • Dossier aziendale — un brief conciso su cosa fa l’azienda, i segnali recenti degni di citazione e gli agganci “perché sono adatto” tratti dal tuo percorso reale.
    • Chi contattare — 3–5 persona target (hiring manager, recruiter interno, un IC senior del team, un contatto caldo/ex compagno di studi) con una stringa di ricerca LinkedIn concreta per trovare ciascuno. Non inventa mai nomi reali.
    • La via di presentazione più calda — il percorso caldo più realistico per il tuo profilo (datore di lavoro/scuola/community in comune, un percorso di secondo grado o un DM a freddo ad alto segnale) e il perché.
    • Bozze di contatto — messaggi brevi e specifici per le persona principali, ancorati ai tuoi punti di prova reali.
    • Live o manuale — gira live tramite la cascata condivisa dei provider con una chiave qualsiasi, oppure restituisce un prompt pronto da copiare-incollare (ripiego onesto, nulla di inventato). Salva piano conserva un piano concluso nel livello utente (networking/net-{company}-{role}-{date}.md); la pagina elenca, apre ed elimina i piani salvati.
  • Nuovo: server/lib/routes/networking.mjs (19° modulo di route), public/js/views/networking.js, PATHS.networkingDir. Riutilizza la cascata server/lib/llm-dispatch.mjs della v1.90.0. 24 nuove chiavi i18n in tutte le 16 lingue. Test: tests/networking-routes.test.mjs.

[1.90.0] — 2026-07-04

Aggiunto

  • Mock Interview 2.0 (Epic 15). Una nuova pagina #/mock-interview trasforma il tuo CV, il profilo, il two-pager e la story bank in una simulazione di colloquio turno per turno:
    • Pratica conversazionale — indica un ruolo target (+ azienda / descrizione dell’annuncio opzionali) e l’intervistatore apre con una domanda mirata. Ogni risposta inviata riceve una replica strutturata: Feedback (punti di forza + la lacuna STAR+R), un Score (N/5) e una Prossima domanda che sonda la parte più debole della tua ultima risposta. Ancorato lato server ai tuoi materiali reali — non inventa mai esperienze che non hai.
    • Consapevole della story bankinterview-prep/story-bank.md è integrato nel prompt (stesso livello di fiducia di cv.md), così il feedback può indirizzarti verso le tue storie migliori.
    • Live o manuale — con una chiave del provider il turno gira live tramite la cascata condivisa (Anthropic → Gemini → OpenAI → Qwen → OpenRouter → GitHub Models); senza chiave ottieni un prompt pronto da copiare-incollare (ripiego onesto, nessuna risposta inventata).
    • Sessioni salvate — clicca Salva trascrizione per conservare un colloquio concluso nel livello utente (interview-prep/mock-{company}-{role}-{date}.md); la pagina elenca, apre ed elimina le sessioni salvate.
  • Nuovo: server/lib/routes/interview.mjs (18° modulo di route), public/js/views/mock-interview.js, server/lib/llm-dispatch.mjs (cascata di provider condivisa), PATHS.storyBank, bundleProjectContext({ extraFiles }). 30 nuove chiavi i18n in tutte le 16 lingue. Test: tests/interview-routes.test.mjs.

[1.89.0] — 2026-07-04

Aggiunto

  • Fit di mercato del candidato — il two-pager (Epic 14). Una nuova pagina #/two-pager ti permette di catturare ciò che tu vuoi davvero dal tuo prossimo ruolo, modellata sul “two-pager di Mnookin” da Never Search Alone:
    • Builder guidato — una narrazione in prima persona “Chi sono”, una nota “Ambiente di destinazione” e cinque editor a chip: cosa amo, must-have, cosa detesto, deal-breaker e non negoziabili. Salvato nel livello utente del progetto padre (config/two-pager.yml) via PUT /api/two-pager — mai sovrascritto dagli aggiornamenti di sistema.
    • Assistente di compilazione AI (POST /api/two-pager/draft) — costruisce un prompt Mnookin pronto all’uso con il tuo CV + profilo inline, da eseguire in qualsiasi LLM e reincollare. Usa solo i tuoi materiali; nulla è inventato.
    • Badge di fit — ogni annuncio su #/scan mostra ora un punteggio di fit ◎ N (lato client, via window.FitScore) che confronta tipo di lavoro, paese, soglia salariale e trasferimento dell’annuncio con il tuo two-pager. Onesto per progetto: quando un annuncio non offre alcun segnale confrontabile, nessun badge viene mostrato (mai un numero inventato). Le violazioni dei deal-breaker pesano più delle semplici avversioni.
    • Alimenta ogni valutazione — il two-pager salvato è inline in bundleProjectContext, così tutte le valutazioni LLM a valle fondono le tue preferenze dichiarate con il match CV-vs-JD.
  • Nuovo: server/lib/routes/two-pager.mjs, public/js/views/two-pager.js, public/js/lib/fit-score.js, PATHS.twoPager. 27 nuove chiavi i18n su tutti i 16 locale. Test: tests/two-pager-routes.test.mjs, tests/fit-score.test.mjs.

[1.88.0] — 2026-07-04

Modificato

  • Rifinitura dell’issue #29 — lacune i18n nella Scansione + igiene dell’API.
  • Localizzate le ultime stringhe di Scansione hardcoded (roadmap v1.69.4): le pillole di riepilogo per fonte (N nuove / M corrispondenti), i toast N nuove offerte e il badge reloc ora passano per t() — 4 nuove chiavi (scan.pillNew, scan.pillMatching, scan.newOffers, scan.relocBadge) su tutti i 16 locale. Gli utenti non anglofoni non vedono più inglese sparso nel flusso di scansione principale.
  • Disabilitato l’header X-Powered-By (roadmap v1.69.5): app.disable('x-powered-by') in createApp() — il server non pubblicizza più Express. (Il resto di quell’epica era già stato consegnato: parentVersion rimuove il suo commento release-please, l’interruttore del tema in modalità chiara, la chiusura delle modali al cambio di rotta e la localizzazione di «Score» (rep.score) nei Report.)
  • Test: tests/scan-i18n-gaps.test.mjs + un’asserzione di assenza di X-Powered-By in tests/security-headers.test.mjs.

[1.87.0] — 2026-07-04

Aggiunto

  • 4 nuovi provider di scansione senza autenticazione (parità con il career-ops padre v1.16.0). Il registro dello scanner cresce da 41 → 45 adattatori (40 EN + 5 RU) — tutti pubblici, senza autenticazione, con host fissato, redirect:'error' (sicuri da SSRF), ciascuno con un test isolato per la CI:
    • Get on Board (getonbrd) — JSON:API pubblico dell’intero portale (tecnologia LATAM/remoto), selezionato per provider, paginato. server/lib/sources/getonbrd.mjs.
    • Amazon (amazon) — JSON di ricerca pubblico di amazon.jobs, rilevato per host o provider: amazon, paginato per offset. server/lib/sources/amazon.mjs.
    • Avature (avature) — ATS *.avature.net per tenant, analizzato da HTML, rilevato per host o provider: avature. server/lib/sources/avature.mjs.
    • SAP SuccessFactors (successfactors) — elenco di riquadri RMK per tenant (*.successfactors.eu/.com, jobs2web.com), analizzato da HTML. server/lib/sources/successfactors.mjs.
  • Ciascuno fornisce un sources/<slug>.mjs (con meta auto-rilevato → menu a discesa #/scan) e un portals/adapters/<slug>.mjs in ALL_ADAPTERS (la regola dei due registri) + tests/sources-<slug>.test.mjs. Il conteggio di ALL_ADAPTERS e le asserzioni di id ordinato e dell’insieme EN di /api/scan/sources sono saliti da 36→40; GET /api/scan/sources ora elenca 45.

[1.86.0] — 2026-07-03

Aggiunto

  • Statistiche per ruoli target (#/stats) — statistiche di mercato su offerte e retribuzioni per i TUOI ruoli target. Una nuova pagina Analytics legge i tuoi ruoli target dal profilo (config/profile.yml → non hardcoded) e le offerte dell’ultima scansione, quindi mostra, per ruolo e paese: offerte per paese e retribuzione mediana per paese (USD) — aggregate lato client (public/js/lib/role-stats.js, riutilizzando window.Countries) a partire dai dati sparsi che gli scanner già raccolgono.
  • Le retribuzioni in qualsiasi valuta vengono normalizzate in USD tramite una tabella FX esplicitamente approssimativa, con un avviso sulla dimensione del campione — mai inventate. Inoltre filtri per ruolo e paese e grafici a barre e di tendenza in SVG inline scritti a mano (nessuna nuova dipendenza, sicuri per la CSP — solo addEventListener).
  • Salva snapshot (POST /api/stats/snapshot) persiste l’aggregato corrente in data/role-stats.jsonl; il grafico di tendenza (GET /api/stats/trend) traccia il numero di offerte nel tempo — la vista «dinamica». Ibrido onesto: gli snapshot provengono da dati di scansione locali, aggiornati su richiesta.
  • Completamente localizzato in tutti i 16 locale (26 nuove chiavi i18n). Novità: server/lib/routes/stats.mjs (16° modulo di rotte), public/js/lib/role-stats.js, public/js/views/stats.js, PATHS.roleStats; test role-stats.test.mjs (7) + stats-routes.test.mjs (5).

[1.85.0] - 2026-07-03

Aggiunto

  • Localizzazione tedesca (de), italiana (it) e turca (tr) — l’interfaccia, la guida integrata, il README e il CHANGELOG sono ora disponibili anche in queste tre lingue (portate dal set di locale di career-ops 1.16.0). L’interfaccia supporta ora 16 lingue.
  • Il selettore della lingua ora elenca Deutsch 🇩🇪, Italiano 🇮🇹 e Türkçe 🇹🇷; il rilevamento automatico della lingua del browser riconosce de, it, tr.
  • Le impalcature dei prompt (server/lib/prompts.mjs) sono state localizzate per le tre nuove lingue.