# PRD 5.12 — Editare unificata in modal + cont cu companie/email/CUI obligatorii + rafinari import (calendar data, mapare cu antet+prima inregistrare, un singur Salveaza, preview compact) + responsive tableta/mobil **Stare**: inchis (verify-pass 2026-06-26; 8 stories TDD prin agent team, VERIFY context curat PASS + 1 FAIL remediat, /code-review high 3 buguri reparate; regresie 987 passed/1 skipped/0 failed; asteapta confirmare commit — poarta umana) > Proces complet: `docs/ROADMAP.md` §5. Contractul RAR (sursa de adevar de contract): > `docs/api-rar-contract.md`. Starea trece: `draft → aprobat → in-executie → verify-pass → inchis` > (actualizata de lead). Acest PRD nu repeta strategia/contractul — le linkeaza. > > Continua 5.11 ([prd-5.11](prd-5.11-ux-import-compact-preview-navigatie.md)). **Backendul de > trimitere (worker, masina de stari de trimitere, idempotenta, contract RAR) ramane NEATINS.** > Atingeri de schema permise (ambele coloane noi, migrare defensiva `_migrate`, ca la 3.3b/3.5/3.6): > `accounts.email` (US-001) si `import_rows.reviewed` (US-007, marcaj „verificat" per rand de preview). > Vezi Non-Goals. ## 1. Obiectiv Continuam dogfooding-ul de first-run inceput in 5.11. Sase frictiuni confirmate E2E in browser (Playwright pe `exemple/prezentari_test.csv`, 2026-06-26) — toate UI/UX, plus o regula de date pe conturi: 1. **Editarea unui rand din preview e rupta vizual si arunca o eroare JS.** Modul de editare inline (`tr.preview-edit` cu `display:block` intr-un tabel `table-layout:fixed`) colapseaza coloanele — antetul si formularul se randeaza pe verticala, caracter cu caracter (reprodus identic cu `image copy.png`). La click pe **Anuleaza** se arunca in consola `TypeError: Cannot read properties of null (reading 'htmx-internal-data')` (reprodus live). Decizie utilizator: **editarea trebuie sa fie un MODAL, ca la Trimiteri, refolosind ACELASI formular** (fara cod duplicat). 2. **Data prestatiei se scrie doar manual** (input text cu hint `YYYY-MM-DD`). Trebuie sa se poata alege si din **calendar** (`` nativ, decizie utilizator — zero dependinte JS). 3. **Conturile nu au reguli minime de identitate.** Confirmat in baza: toate conturile au `cui=NULL`, iar conturile create din CLI/teste nu au niciun utilizator → fara email. Decizie utilizator: un cont inregistrat trebuie sa aiba **obligatoriu companie, email si CUI**. 4. **Maparea coloanelor nu arata datele.** Pasul 2 listeaza nume de coloana + 2 exemple stivuite, dar nu se vede clar **capul de tabel (numele coloanelor) + valorile primei inregistrari**, ca operatorul sa stie ce mapeaza. 5. **Panoul „Operatii de mapat la cod RAR" cere un Salveaza per rand.** La un fisier cu N operatii nemapate sunt N butoane „Salveaza" si N submit-uri. Trebuie **un singur buton Salveaza** care salveaza toate maparile odata. 6. **Tabelul de preview (pasul 3) nu e compact si are o coloana neclara.** Randurile sunt foarte inalte (VIN-ul se sparge pe verticala), iar coloana **„Verificat?"** nu are sens evident in acest pas (operatorul nu intelege bifa). Trebuie lista mai compacta si coloana clarificata/eliminata. 7. **Pe tableta si mobil interfata arata prost si articolele din header se suprapun.** Header-ul are grila desktop `1fr auto 1fr` (`min-height:92px`, logo 60px) si un singur prag mobil `@media (max-width:767px)`, dar **nimic pentru tableta (768–1024px)** — acolo logo + titlu + badge mediu + comutator tema + versiune + hamburger se inghesuie si se suprapun. Tot fluxul (header, import, preview, modal, Trimiteri, Mapari, Cont) trebuie **compact, functional si ergonomic** pe tableta si telefon, cu tinte touch si fara suprapuneri. Toate sunt **UI/UX**, cu o singura exceptie de date controlata: identitatea contului (companie/email/CUI obligatorii, US-001/002). ## 2. Non-Goals (anti scope-creep) - **Nu** atingem worker-ul, reconcilierea, idempotenta, `build_key`, masina de stari de trimitere sau contractul RAR. - **Nu** schimbam canalul API (`POST /v1/prezentari` / `/valideaza`) si nici logica de mapare (`mapping.py` `resolve_prestatii`). Maparea operatie→cod ramane neschimbata; doar UI-ul de mapare din pasul de import se reorganizeaza (US-005) si reuseaza `save_mapping`/`reresolve_account` existente. - **Nu** stergem coloanele DB `auto_send` (deja neutralizate in 5.11) si nu reintroducem conceptul. - **Nu** schimbam stocarea editarii de preview: ramane `import_rows.override_json` (Approach B din 3.6), ruta `POST /_import/{id}/rand/{i}/editeaza` ramane sursa de adevar; doar **suprafata** de editare trece din rand-inline in modal. - **Nu** facem editare in bloc / multi-rand si nici editare a operatiei/codului RAR din modalul de rand (codul se mapeaza din panoul „Operatii de mapat", ca azi). - **Nu** schimbam fluxul de login/parola; un cont poate avea in continuare mai multe loginuri (`users`), dar primeste un email canonic de contact pe `accounts` (US-001). - **Nu** rescriem validarea de continut (`validation.py`); `` produce tot `YYYY-MM-DD`, acceptat azi. ## 3. Stories atomice > Fiecare story: cea mai mica unitate care lasa sistemul functional. Backend + UI pentru acelasi > comportament = 2 stories. Toate rutele web noi sub `require_login`, scoped pe contul din sesiune > (404 cross-account), CSRF pe toate POST-urile. > > **Cerinta transversala (toate story-urile cu UI): responsive obligatoriu.** Fiecare suprafata noua/atinsa > (US-002..007) se verifica E2E pe **3 viewport-uri: desktop (≥1280px), tableta (768–1024px) si mobil > (≤767px / ~390px)** — fara overflow orizontal (`scrollWidth <= clientWidth`), fara suprapuneri, tinte > touch ≥44px, modal full-screen pe mobil. US-008 acopera header-ul + cadrul global; fiecare story isi > verifica propria suprafata pe cele 3 viewport-uri. ### US-001: Backend — companie/email/CUI obligatorii pe cont (`accounts.email` + validari) **Ca** administrator al gateway-ului **vreau** ca orice cont sa aiba companie, email si CUI **pentru ca** azi conturile pot exista fara email (CLI/teste) si fara CUI, deci nu pot fi identificate fiscal/contactate. - **Depinde de**: — - **Fisiere**: `app/schema.sql` (coloana `accounts.email` + `_migrate` defensiv), `app/accounts.py` (`create_account` accepta+valideaza `email`; helper `account_is_complete`), `app/web/auth_routes.py` (signup: CUI devine obligatoriu; scrie `accounts.email`), `tools/account.py` (CLI create cere `--email` + `--cui`), `tests/test_accounts.py`, `tests/test_signup.py` (~6 fisiere) - **Test intai (RED)**: `tests/test_accounts.py` — `test_create_account_fara_email_ridica`, `test_create_account_fara_cui_ridica`, `test_email_normalizat_lowercase_trim`, `test_migrare_adauga_coloana_email_idempotent`, `test_account_is_complete_false_pe_legacy_incomplet`; `tests/test_signup.py` — `test_signup_fara_cui_422`, `test_signup_scrie_email_pe_account`, `test_signup_cui_existent_mesaj_prietenos` (NU mesajul tehnic cu `activate --account`). - **Acceptance criteria**: - [x] Migrare: `accounts.email TEXT` (nullable la nivel de schema pentru conturile legacy), `_migrate` defensiv idempotent (ca `users.is_admin` la 3.3b). Contul de sistem id=1 ramane fara email (exceptat). - [x] `create_account(conn, name, cui, email, active)` — `name`/`cui`/`email` goale → `ValueError` cu cauza+fix (catalog `errors.py` daca exista cod potrivit); `email` normalizat (trim+lower); `cui` normalizat (trim+upper, ca azi). CUI duplicat → mesajul existent. - [x] Signup web: `cui` devine **obligatoriu** (azi optional); la succes scrie `accounts.email = email`-ul utilizatorului. Lipsa CUI → re-randare formular cu eroare (422), pastrand campurile. - [x] **CUI duplicat la signup = mesaj prietenos, NU cel tehnic** (decizie user 2026-06-26, optiunea 1): „Aceasta firma (CUI …) e deja inregistrata. Cere accesul de la administratorul contului." — fara referinta la CLI `activate --account`. **Model: 1 firma = 1 cont = 1 login**; fluxul de invitatie/alaturare a unui al doilea email pe aceeasi firma e deferit la TODOS (optiunea 2). - [x] **Canal de contact concret in mesaj** (T3 gate /autoplan, aprobat 2026-06-26): mesajul include un email/canal de suport configurabil din settings (ex. `support_email`); daca setarea lipseste, fallback la formularea de mai sus. Operatorul primeste un pas urmator real, nu doar „cere accesul". Nu mai lasam mesajul tehnic ridicat de `create_account` sa ajunga verbatim in signup — detectam CUI duplicat in handler-ul de signup si compunem mesajul prietenos acolo (NU `error=str(exc)`). - [x] CLI `tools/account.py create` cere `--email` + `--cui` (refuza fara ele); `--with-key` neschimbat. - [x] `account_is_complete(row)` (companie + email + CUI ne-goale) — helper pur, fara efecte. - [x] **NU** atinge `users`, `submissions`, worker-ul sau idempotenta. - **Verificare E2E**: TestClient — signup fara CUI → 422; signup complet → `accounts.email` populat; `create_account` fara email/cui → ValueError. ### US-002: UI — gate de activare + pagina Cont editeaza companie/email/CUI + banner legacy **Ca** operator/administrator **vreau** sa vad si sa completez companie/email/CUI **pentru ca** conturile incomplete (legacy) trebuie aduse la regula fara re-inregistrare. - **Depinde de**: US-001 - **Fisiere**: `app/web/templates/_cont.html` (sectiune noua „Date firma"), `app/web/routes.py` (ruta `POST /cont/date-firma` scoped sesiune + CSRF; context `account_meta`+`cont_incomplet`), `app/web/templates/admin.html` + `app/web/routes.py` (gate activare pe `account_is_complete`), `app/web/templates/_banner.html` sau `_acasa.html` (banner „Completeaza datele firmei"), `tests/test_web_cont.py`, `tests/test_admin.py` (~6 fisiere) - **Test intai (RED)**: `tests/test_web_cont.py` — `test_cont_afiseaza_companie_email_cui`, `test_post_date_firma_actualizeaza`, `test_post_date_firma_cui_duplicat_eroare`, `test_banner_cont_incomplet_pe_legacy`; `tests/test_admin.py` — `test_activare_cont_incomplet_refuzata`. - **Acceptance criteria**: - [x] `_cont.html` are o sectiune „Date firma" (deasupra cheii API) cu companie + email + CUI editabile, prefilled din `accounts`; `POST /cont/date-firma` valideaza (reuse `create_account`-style) + CSRF + scoped sesiune; eroare pe CUI duplicat / camp gol, mesaj 3-niveluri. - [x] Banner ne-blocant „Completeaza datele firmei (email/CUI)" pe Acasa cand `account_is_complete` e fals; dispare dupa completare. NU blocheaza importul/uploadul. - [x] In panoul admin, butonul **Activeaza** e dezactivat (cu tooltip) pe conturi incomplete — nu activam la RAR un cont fara identitate completa. - [x] Fara regresie pe rutele existente din `_cont.html` (cheie API, creds RAR). - **Verificare E2E**: browser pe `/?tab=cont` — completez email+CUI → banner dispare; admin nu poate activa un cont incomplet. ### US-003: UI — pasul „Potriveste coloanele" arata antet + prima inregistrare **Ca** operator **vreau** sa vad numele coloanelor din fisier si valorile primului rand **pentru ca** sa stiu exact ce date mapez la fiecare camp RAR. - **Depinde de**: — - **Fisiere**: `app/web/templates/_mapcoloane.html`, `app/web/routes.py` (`web_upload_import` / `web_save_mapare_coloane` paseaza deja `sample_rows`; expune `prima_inregistrare`), `tests/test_web_mapcoloane.py` (~3 fisiere) - **Test intai (RED)**: `tests/test_web_mapcoloane.py` — `test_mapcoloane_arata_cap_tabel_coloane`, `test_mapcoloane_arata_valori_prima_inregistrare`, `test_mapcoloane_fara_randuri_degradeaza` (fisier cu antet, fara randuri de date → fara crash). - **Acceptance criteria**: - [x] Deasupra (sau langa) randurile de mapare, un mic tabel orizontal cu **un cap de tabel = numele coloanelor din fisier** si **un rand = valorile primei inregistrari** (truncate la o lungime rezonabila, `title` pe valoare integrala). Foloseste `.tablewrap` pentru scroll orizontal pe mobil. - [x] Fiecare coloana din capul de tabel ramane vizual asociata cu select-ul ei de mapare (ex. aceeasi ordine, sau evidentiere la hover) — operatorul vede „coloana X (valoare „...") → campul canonic Y". - [x] Fisier fara randuri de date → se arata doar capul de tabel, fara „prima inregistrare" (fara crash). - [x] Nicio schimbare de backend de parsare/mapare; doar randare (datele exista deja in `sample_rows`). - **Verificare E2E**: browser pasul 2 — upload `prezentari_test.csv` → vad antetul real + valorile randului 1. ### US-004: UI+backend — un singur „Salveaza" pe „Operatii de mapat la cod RAR" **Ca** operator **vreau** sa salvez toate maparile de operatii dintr-un singur click **pentru ca** azi e cate un buton per operatie si trebuie apasat pe fiecare. - **Depinde de**: — - **Fisiere**: `app/web/templates/_preview_import.html` (panoul de mapare → un singur `
`), `app/web/routes.py` (ruta noua `POST /_import/{id}/mapare-operatii` plural; pastreaza `mapare-operatie` singular pentru compat sau o inlocuieste — vezi AC), `tests/test_web_mapare_op.py` (~3 fisiere) - **Test intai (RED)**: `tests/test_web_mapare_op.py` — `test_mapare_operatii_salveaza_multiple_intr_un_post`, `test_mapare_operatii_ignora_randuri_neselectate` (op fara cod ales → nesalvata, nu eroare), `test_mapare_operatii_re_rezolva_blocatele` (randurile cu cod ales trec din `needs_mapping`). - **Acceptance criteria**: - [x] Panoul „Operatii de mapat la cod RAR" devine UN singur `` cu un select per operatie + **un singur buton „Salveaza maparile"** la final. - [x] `POST /_import/{id}/mapare-operatii` primeste perechi `(cod_op_service, cod_prestatie)` (liste paralele), apeleaza `save_mapping` pentru fiecare operatie cu cod ales (reuse exact, fara logica noua de mapare), apoi **o singura** recompute `_web_compute_preview` + re-randare `#import-section`. - [x] Operatiile fara cod ales (`— alege cod RAR —`) sunt ignorate (nu produc eroare, nu se salveaza). - [x] Toggle-ul auto_send NU reapare (eliminat in 5.11). - [x] CSRF + scoped sesiune + guard batch committed (409) pastrate. - **Verificare E2E**: browser pasul 3 — aleg coduri pentru toate operatiile, un click pe „Salveaza maparile" → toate randurile trec din „Cod RAR lipsa", o singura re-randare. ### US-005: Refactor — formular de editare partajat (DRY) intre Trimiteri si preview **Ca** dezvoltator **vreau** un singur formular de editare de continut **pentru ca** sa nu existe cod duplicat intre modalul Trimiteri si editarea de preview (sursa bug-urilor inline din 3.6/5.11). - **Depinde de**: — - **Fisiere**: `app/web/templates/_form_editare.html` (NOU — partial cu campurile vehicul/data/odo), `app/web/templates/_trimitere_detaliu.html` (consuma partial-ul), `app/web/templates/_macros.html` (macro `camp` extins cu `tip='date'`), `tests/test_web_form_editare.py` (~4 fisiere) - **Test intai (RED)**: `tests/test_web_form_editare.py` — `test_form_editare_are_input_date_pe_data_prestatie`, `test_trimitere_detaliu_foloseste_form_partajat`, `test_camp_macro_randeaza_type_date`. - **Acceptance criteria**: - [x] Partial `_form_editare.html` randeaza grila responsiva existenta (`repeat(auto-fit, minmax(200px,1fr))`) cu campurile: `nr_inmatriculare`, `vin`, `data_prestatie`, `odometru_final`, `odometru_initial`, plus map de erori per-camp (tipar `corectie_errors`). Parametrizat prin: URL de POST, valorile curente, harta de erori, eticheta butonului primar. - [x] **`data_prestatie` = ``** (calendar nativ); valoarea ramane `YYYY-MM-DD`. Daca valoarea curenta nu e `YYYY-MM-DD` valid, inputul degradeaza grijuliu (gol + hint), fara crash. - [x] `_trimitere_detaliu.html` randeaza acelasi partial in ramura `editabil` — comportamentul modalului Trimiteri (post `/corecteaza`, select cod RAR pe needs_data/needs_mapping) ramane identic. - [x] Macro `camp` suporta `tip='date'` fara sa strice apelurile `type='text'` existente. - **Verificare E2E**: browser — modalul Trimiteri (rand `needs_data`) arata un calendar la Data prestatie; salvarea+revalidarea functioneaza ca azi. ### US-006: UI — „Editeaza" din preview deschide MODALUL (acelasi formular), nu rand inline **Ca** operator **vreau** sa editez un rand de preview intr-un modal curat **pentru ca** editarea inline e rupta vizual si arunca eroare la Anuleaza. - **Depinde de**: US-005 - **Fisiere**: `app/web/templates/_preview_rand.html` (scoate ramura `editing`/`tr.preview-edit` + scriptul de mutual-exclusion; butonul „Editeaza" tinteste modalul global), `app/web/routes.py` (ruta GET fragment editare preview → randeaza `_form_editare.html` in `#detaliu-modal-body`; POST `/_import/{id}/rand/{i}/editeaza` ramane, dar raspunde cu inchidere modal + OOB pe rand+contoare), `app/web/templates/_preview_import.html` (foloseste modalul global `#detaliu-modal`), `tests/test_web_preview_edit.py` (~5 fisiere) - **Test intai (RED)**: `tests/test_web_preview_edit.py` — `test_editeaza_preview_serveste_fragment_modal` (NU `tr.preview-edit`), `test_salvare_preview_inchide_modal_si_oob_rand`, `test_anuleaza_nu_lasa_rand_orfan` (regresie pe eroarea htmx null), `test_editare_preview_scoped_404_alt_cont`, `test_editare_batch_committed_409`. - **Acceptance criteria**: - [x] Butonul „Editeaza" pe rand face `hx-get` catre fragmentul de editare cu `hx-target="#detaliu-modal-body"` (acelasi mecanism de modal ca la Trimiteri, deschis prin clasa/markup existent in `base.html`). - [x] Fragmentul randeaza `_form_editare.html` cu POST la `/_import/{id}/rand/{i}/editeaza`, `hx-target="#detaliu-modal-body"`. La succes: **modalul se inchide** (`HX-Trigger: inchideModal`, ca la `/corecteaza`) si randul + contoarele se actualizeaza prin **OOB swap** (reuse `include_oob`). - [x] **Ramura `editing` / `tr.preview-edit` + scriptul inline de mutual-exclusion sunt ELIMINATE** din `_preview_rand.html` (sursa colapsarii pe verticala + a erorii `htmx-internal-data` la Anuleaza). - [x] „Anuleaza" = inchiderea modalului (mecanismul global), fara cerere catre `/_import/.../rand/{i}`, deci fara eroarea JS reprodusa. Test de regresie pe consola curata. - [x] Mutatie pura pe `override_json` pastrata (ruta neschimbata logic); scoping JOIN→404, guard committed→409 raman. - [x] Pe eroare de validare, modalul ramane deschis cu valorile + erorile per-camp (tipar Trimiteri). - **Verificare E2E**: browser pasul 3 — Editeaza → modal cu calendar + campuri; completez data → Salveaza → modal se inchide, randul trece pe „Gata de trimis", contoarele cresc; Anuleaza → modal se inchide, **0 erori in consola**. ### US-007: UI — preview compact + scoaterea coloanei „Verificat?" **Ca** operator **vreau** o lista de preview compacta si fara coloane neclare **pentru ca** randurile sunt prea inalte (VIN pe verticala) si nu inteleg bifa „Verificat?". - **Depinde de**: US-006 - **Fisiere**: `app/schema.sql` (coloana `import_rows.reviewed` + `_migrate` defensiv), `app/web/templates/_preview_rand.html`, `app/web/templates/_preview_import.html` (scoate coloana `col-verificat` + logica inline `reviewed_rows` din tabel), `app/web/templates/_form_editare.html` / fragmentul modal (buton „Confirma valorile" pe `needs_review`), `app/web/templates/base.html` (latimi `col-*` recalibrate, anti-overflow), `app/api/v1/import_router.py` + `app/web/routes.py` (citesc `reviewed` in `_resolve_row_for_preview` / `_web_compute_preview` ca `needs_review`-confirmat → `ok`; gate `n_confirmat` la commit foloseste `reviewed`, nu bife inline; ruta care seteaza `reviewed=1`), `tests/test_web_preview_compact.py`, `tests/test_import_review.py` (~7 fisiere) - **Test intai (RED)**: `tests/test_web_preview_compact.py` — `test_preview_fara_coloana_verificat`, `test_preview_vin_nu_se_sparge_pe_verticala` (VIN intr-o singura linie / wrap controlat); `tests/test_import_review.py` — `test_needs_review_exclus_din_gata_pana_la_confirmare`, `test_confirmare_in_modal_seteaza_reviewed_si_devine_ok`, `test_reviewed_nu_intra_in_payload_sau_idempotency` (marcaj separat, NU camp de continut), `test_migrare_adauga_coloana_reviewed_idempotent`, `test_editare_valoare_pe_needs_review_reseteaza_reviewed` (daca schimbi valoarea, re-cere confirmare). - **Acceptance criteria**: - [x] Coloana **„Verificat?" eliminata** din tabelul de preview; antetul si celulele scad la 8 coloane. - [x] Randuri compacte: VIN nu se mai sparge pe verticala (latime minima pe coloana Vehicul / `white-space` controlat); fara overflow orizontal la 1280px (`scrollWidth <= clientWidth`); cardurile <768px raman. - [x] **Decizie inchisa (Q1): confirmare in modal, rand exclus pana confirmi.** Un rand `needs_review`: - apare cu pill „Verifica valori" + motivul concret in „Note" (data ambigua / formule Excel / coercion); - este **exclus din „gata de trimis"** (nu intra in `n_confirmat`) pana cand operatorul il deschide in modal (US-006) si apasa **„Confirma valorile"** (sau il corecteaza), ceea ce seteaza `import_rows.reviewed=1`; abia atunci randul devine `ok` la recalculul `_resolve_row_for_preview`. - [x] **Banner discoverability deasupra tabelului** (T1 gate /autoplan, aprobat 2026-06-26): cand exista randuri `needs_review`, un banner ne-blocant deasupra tabelului explica: „Randurile cu Verifica valori nu pleaca la RAR pana le deschizi si confirmi in modal." Fara el, gate-ul mutat din coloana vizibila in modal devine usor de ratat (operatorul crede ca pill-ul e informativ). Bannerul dispare cand `summary.needs_review == 0`. - [x] **Buton explicit „Confirma valorile"** (T2 gate /autoplan, aprobat 2026-06-26): in modal (US-006), randurile `needs_review` au un buton SEPARAT „Confirma valorile" care seteaza `reviewed=1` — atestare explicita, distincta de salvarea unei corectii de continut. NU se seteaza `reviewed=1` implicit la orice save (altfel operatorul ar atesta o valoare ambigua fara intentie). Salvarea unei CORECTII pe un rand deja confirmat reseteaza `reviewed` (vezi AC urmator). - [x] **Marcaj separat, nu camp de continut**: `import_rows.reviewed` (nullable/int, migrare defensiva) NU intra in payload, in `override_json` sau in cheia de idempotenta. Daca utilizatorul **schimba** o valoare a unui rand deja confirmat, `reviewed` se reseteaza (re-cere confirmare). - [x] Comitul ramane gate HARD pe `n_confirmat` (niciun rand ambiguu nu pleaca la RAR fara confirmare umana explicita) — acum derivat din `reviewed`, nu din bife inline `reviewed_rows`. - [x] Bara de confirmare („Trimite la RAR") si contoarele raman corecte dupa editari/confirmari (OOB), fara coloana Verificat?. - [x] Guard committed→409 si scoping JOIN→404 pe ruta de confirmare (acelasi tipar ca `/editeaza`). - **Verificare E2E**: browser pasul 3 (cu un xlsx cu data ambigua / VIN numeric) — lista compacta, fara coloana Verificat?, VIN pe o linie; randul `needs_review` ramane exclus din „gata de trimis" pana il confirm in modal („Confirma valorile") → devine „Gata de trimis", contorul creste. ### US-008: UI — responsive tableta + mobil (header fara suprapuneri + cadru compact/ergonomic) **Ca** operator pe telefon/tableta **vreau** o interfata compacta, fara articole de header suprapuse **pentru ca** azi pe mobil arata prost si elementele din header se calca unele pe altele. - **Depinde de**: — (header/cadru global); coordonat cu US-006/007 pentru modal+preview pe mobil - **Fisiere**: `app/web/templates/base.html` (header grid + media queries tableta 768–1024 + mobil ≤767, modal full-screen, `.cont-menu`, tinte touch), eventual `app/web/templates/_status.html` / `_acasa.html` (contoare + nav pe randuri inguste), `tests/test_web_responsive.py` (~3 fisiere) - **Test intai (RED)**: `tests/test_web_responsive.py` — `test_header_are_breakpoint_tableta` (exista reguli `@media` intre 768 si 1024 pentru header), `test_header_elemente_nu_au_min_height_fix_pe_mobil`, `test_modal_full_screen_pe_mobil` (clasa/regula prezenta). (Testele de markup/CSS; pixel-level la E2E.) - **Acceptance criteria**: - [x] **Header fara suprapuneri pe tableta (768–1024px)**: logo + titlu + badge mediu + comutator tema + versiune + hamburger se aseaza fara sa se calce (grid/flex care wrap-uieste sau ascunde versiunea/ titlul lung); `min-height:92px` nu forteaza inghesuirea. Pe mobil (≤767px) raman regulile existente, verificate ca nu se suprapun la ~390px latime. - [x] **Compact + ergonomic**: spatieri reduse pe mobil, tinte interactive ≥44px (butoane, pill-uri, linkuri nav, intrari hamburger), fara dublu-scroll; modalul de editare (US-006) e **full-screen** pe mobil (nu o casuta minuscula). - [x] **Fara overflow orizontal** pe niciuna din paginile principale (Acasa/import, preview pas 3, Mapari, Cont, login/signup) la 768px si la ~390px (`scrollWidth <= clientWidth`). - [x] Contoarele de status + nav-ul „Trimiteri/Mapari" se aseaza pe randuri lizibile pe mobil (fara taiere). - [x] Light/Dark/Petrol/Auto raman corecte pe toate viewport-urile (fara regresie de tema). - **Verificare E2E**: browser Playwright cu `browser_resize` la **390×844 (mobil)**, **820×1180 (tableta)** si **1280×800 (desktop)** — screenshot pe Acasa/import, preview pas 3 (cu modal deschis) si Cont; header fara suprapuneri pe toate trei; 0 overflow orizontal; tinte touch ok. ## 4. Riscuri - **R1 — Gate `needs_review` la scoaterea coloanei.** Coloana „Verificat?" era gate-ul HARD prin care randurile cu valori ambigue (data ambigua, formula Excel) intrau in trimitere doar dupa bifa umana. Scoaterea ei naiva ar auto-include randuri ambigue (declaratie ireversibila la RAR). Mitigare: confirmarea se muta in modalul de editare (US-007 AC); `n_confirmat` ramane gate HARD. Vezi Q1. - **R2 — Refactor formular partajat (US-005) atinge modalul Trimiteri (cale LIVE).** `_trimitere_detaliu.html` e folosit pentru corectii reale care re-trimit la RAR. Mitigare: US-005 = refactor fara schimbare de comportament; teste byte-compat pe post `/corecteaza` + regresia existenta verde inainte de US-006/007. - **R3 — `` si valori ne-`YYYY-MM-DD`.** Fisiere cu data in alt format ajung in editare ca text ne-valid pentru inputul date (s-ar goli). Mitigare: AC US-005 — degradare grijulie (gol + hint), fara pierdere tacuta; data ramane editabila si re-validata la salvare. - **R4 — Migrare `accounts.email`.** Conturi legacy raman cu `email=NULL`. Mitigare: coloana nullable + `account_is_complete` (banner + gate activare), nu hard-block; contul de sistem id=1 exceptat. - **R5 — Eroarea htmx `htmx-internal-data`.** Reprodusa la Anuleaza pe editarea inline. Mitigare: US-006 elimina complet ramura inline + scriptul; test de regresie pe consola curata. - **R6 — Responsive = fisier fierbinte `base.html`.** US-008 atinge header + media queries, fisier partajat cu alte story-uri (US-007 latimi `col-*`). Mitigare: serializare la lead (NU paralel pe `base.html`); verificare pixel pe 3 viewport-uri ca breakpoint-ul de tableta nu strica desktop-ul/mobilul existent. ## 5. Intrebari deschise > Se rezolva cu utilizatorul ÎNAINTE de executie (poarta de aprobare PRD). - **Q1 (gate `needs_review`) — INCHIS (user, 2026-06-26): confirmare in modal, rand exclus pana confirmi.** Context — `needs_review` apare cand validarea TRECE dar parsarea fisierului a fost incerta, in 3 cazuri (sursa: `import_parse.py` + `import_router.py:201-230`), aproape exclusiv la **xlsx** (la CSV nu se declanseaza — de-aceea coloana e goala in cazul comun): 1. **Data ambigua** — zi ≤12 si format neclar (`05.06` = 5 iun. sau 6 mai?). 2. **Coloana cu formule Excel** fara valori calculate (rata mare de celule goale). 3. **Coercion suspect** la citire xlsx — VIN numeric (pierde zerourile din fata) / odometru ca float. Decizie: scoatem coloana mereu-prezenta „Verificat?"; randul `needs_review` ramane **exclus din „gata de trimis"** pana e deschis in modal si **confirmat** („Confirma valorile") sau corectat, persistand `import_rows.reviewed=1` (marcaj separat, NU camp de continut → nu intra in payload/idempotenta). Implementat in US-007. - **Q2 (model cont-email) — INCHIS (user, 2026-06-26): model A** (email canonic pe `accounts`), cu **1 firma = 1 cont = 1 login**. CUI ramane unic; al doilea email pe acelasi CUI e respins la signup cu mesaj prietenos (US-001). Fluxul de invitatie/alaturare (mai multi utilizatori per firma) → TODOS. - **Q3 (CLI legacy `tools/account.py`)**: facem `--email`/`--cui` obligatorii rupe scripturile vechi de test? Daca da, pastram un flag `--allow-incomplete` doar pentru teste, sau actualizam fixture-urile. ## 6. Valuri de executie (graful de dependente) ``` Val 1 (paralel, fisiere disjuncte): [US-001] accounts.email + validari companie/email/CUI (schema/accounts/auth_routes/cli) [US-003] mapcoloane: antet + prima inregistrare (_mapcoloane.html/routes) [US-004] un singur Salveaza pe operatii (_preview_import.html/routes) [US-005] formular de editare partajat (DRY) + input date (_form_editare/_trimitere_detaliu/_macros) Val 2 (deblocate de Val 1): [US-002] Cont editeaza date firma + gate activare + banner (dep US-001; _cont/admin/routes) [US-006] Editeaza preview → modal (acelasi formular) (dep US-005; _preview_rand/_preview_import/routes) Val 3 (deblocat de US-006; ating base.html → serializate): [US-007] preview compact + scoate „Verificat?" + gate review (dep US-006; _preview_*/base.css/import_router) [US-008] responsive tableta+mobil + header fara suprapuneri (base.html media queries; coordonat cu US-006/007) ``` Fisiere fierbinti partajate (serializate de lead, NU paralel pe acelasi fisier): `routes.py` (US-001/002/003/004/006), `_preview_import.html` (US-004/006/007), `_preview_rand.html` (US-006/007), `base.html` (US-007 latimi `col-*` + US-008 header/media queries — serializate strict intre ele). Vezi ROADMAP §5.5. --- ## Raport VERIFY > Faza VERIFY rulata de subagent verificator independent (context curat, PRD-only, ROADMAP §5.6), > 2026-06-26. Lead orchestrare prin agent team (8 teammates Sonnet TDD pe valuri cu fisiere disjuncte; > `routes.py` si `base.html` serializate ca fisiere fierbinti). Backend trimitere (worker, masina de > stari de trimitere, idempotenta `build_key`, contract RAR, canal API) NEATINS — confirmat > `git diff --stat` (app/worker/, app/idempotency.py, app/mapping.py, app/validation.py = 0 modificari). ### Rezultat: PASS (toate 8 stories) - **Suita**: `python3 -m pytest -q` -> **987 passed, 1 skipped, 0 failed** (baseline 934 -> +53 teste noi). Live RAR `FINALIZATA` = opt-in indisponibil in mediu (normal, ca la livrabilele anterioare). - **PASS/FAIL per story** (dovezi cod + teste, verificator independent): - US-001 accounts email/CUI — PASS (migrare defensiva, create_account valideaza, account_is_complete id=1 exceptat, signup CUI obligatoriu + mesaj prietenos T3, CLI --email/--cui). - US-002 Cont date firma + gate activare + banner — PASS. - US-003 mapcoloane antet + prima inregistrare — PASS (confirmat E2E browser). - US-004 un singur Salveaza pe operatii — PASS (ruta plurala, D#12 skip invalid). - US-005 formular editare partajat + input date — PASS (D#5/D#6/D#10). - US-006 Editeaza preview -> MODAL — PASS (ramura inline eliminata, Anuleaza fara eroare htmx, E2E 0 erori consola). - US-007 preview compact + gate review in modal — PASS (reviewed marcaj separat, NU in payload/idempotenta; gate HARD pe ambele canale; T1 banner; T2 buton Confirma; D#9 reset). - US-008 responsive tableta + mobil — PASS (E2E pe 390/820/1280, header fara suprapuneri, D#13 verificat). - **Invariante critice**: R2 (submissions neatins dupa editare preview) PASS; reviewed in afara payload/override/idempotency PASS; migrari idempotente PASS; ramura inline `tr.preview-edit` eliminata PASS. ### VERIFY a gasit 1 FAIL -> remediat TDD, re-confirmat - FAIL: `signup.html` eticheta CUI „(optional)" + input fara `required` (contrazicea AC US-001 „CUI obligatoriu"; serverul respingea corect 422 dar UI comunica gresit). Reparat TDD (eticheta `*` + `required`), test de lock `test_signup_html_cui_obligatoriu_ui`. ### Faza CLOSE — `/code-review high` (8 unghiuri prin subagenti, verificare cod first-hand) 3 buguri reale reparate TDD (regresie finala 987 passed): 1. **HIGH** — `confirma-review` folosea `hx-swap="none"` -> scriptul `updateN()` din continutul principal nu se executa -> `n_confirmat` ramanea stale -> „Trimite la RAR" pica pe gate HARD 422 (fluxul confirma->commit US-007 rupt la prima incercare). Fix: formularul Confirma valorile aliniat la `hx-target="#detaliu-modal-body"` `hx-swap="innerHTML"` (ca /editeaza). 2. **MEDIUM** — email duplicat la signup arata mesajul gresit „firma e deja inregistrata" (`"deja folosit"` prindea si `ValueError("email deja folosit")` din `create_user`). Fix: detectie email-dup inaintea CUI-dup, mesaj specific emailului. 3. **MEDIUM (a11y)** — butonul Editeaza din preview deschidea modalul ocolind `open()` (fara inert/focus-trap/ focus-return). Fix: handler-ul global `htmx:beforeRequest` trateaza si `.btn-editeaza` -> `open()`; JS inline eliminat. Notat ca debt (neblocant): API preview re-deriva needs_review peste DB `resolved_status` cross-channel (web commit numara oricum `reviewed=1`); mesaje prietenoase „camp gol" dead-code in cont_date_firma/signup (edge mascat de HTML required); `zip()` truncheaza la liste POST inegale; `id` cont in mesajul CUI-duplicat; duplicari de cleanup (context modal, markup banner, N query nomenclator). ### Nedovedit in sesiune - Live RAR `FINALIZATA` prin `--send` (opt-in, lipsa creds/mediu) — risc minim, backend trimitere NEATINS. --- ## GSTACK REVIEW REPORT (/autoplan, 2026-06-26) Branch: main · Commit: 283299f · Voci: Claude subagents (CEO/Design/Eng/DX) + verificare cod first-hand. **Codex = INDISPONIBIL** (usage limit, reset 2026-07-18) -> mod `[subagent-only]` pe toate fazele. Restore point: vezi comentariul HTML din capul fisierului. Test plan: `~/.gstack/projects/romfast-rar-autopass/main-prd5.12-test-plan-20260626.md`. ### Rezumat PRD matur: Q1/Q2 inchise de user, Non-Goals clare, graf de valuri, R1-R6. Rutele si fisierele citate exista toate in cod. Review-ul a confirmat fezabilitatea si a gasit **4 lacune de specificatie reale** (nu blocante, dar de inchis inainte de executie) + cateva rafinari. Niciun User Challenge (un singur model activ -> nu se poate forma consens cross-model; recomandarile de mai jos sunt sugestii, nu provocari). ### Decision Audit Trail | # | Faza | Decizie | Clasificare | Principiu | Rationament | Respins | |---|------|---------|-------------|-----------|-------------|---------| | 1 | CEO | NU splitam in 5.12a/5.12b | Taste | P3/P6 | Valurile izoleaza deja US-001/002 (Val1/Val2) pe fisiere disjuncte; split adauga overhead de release fara castig tehnic pt. echipa mica | Split in 2 release-uri (CEO subagent) | | 2 | CEO | Respins „testeaza worker-ul intai" (F1/F3/F8) | Mechanical | P3 | Non-Goals ingheata explicit worker/contract/idempotenta; conflateaza acest PRD UI cu munca de backend separata | CEO subagent F1/F3/F8 | | 3 | CEO | First-run E2E smoke -> ramane in TODOS (deja listat) | Mechanical | P3 | Deja deferat din 5.11; recomandat, neblocant | A bloca 5.12 pe el | | 4 | CEO | needs_review: pastram gate-ul, nu cerem date de utilizare | Mechanical | P1 | Gate-ul e safety-critical (declaratie ireversibila RAR); US-007 muta UI-ul, nu sterge gate-ul | CEO F5 (gather usage data first) | | 5 | Eng | Partial partajat = DOAR campuri vehicul+data+err/fix; cod_prestatie select + nemapate_inline RAMAN in `_trimitere_detaliu` | Mechanical | P5/P4 | `_trimitere_detaliu` are 2 surse de cod (select + sectiune mapare inline) imposibil de absorbit fara branching fragil | Partial „atotcuprinzator" | | 6 | Eng | US-005 parametrizeaza si `fix_map` (+ aria-label cu VIN) | Mechanical | P1 | Forma preview are fix-hints + aria-label cu context VIN; lista PRD le omitea -> ar pierde info la extractie | A lasa lista PRD ca atare | | 7 | Eng | `import_rows.reviewed INTEGER DEFAULT 0` (nu NULL) | Mechanical | P5 | Gate-ul devine `reviewed=0` clar, fara ambiguitate NULL vs 0 | DEFAULT NULL | | 8 | Eng | Gate commit derivat din DB `reviewed` pe AMBELE canale; API `reviewed_rows` pastrat dar seteaza `reviewed=1` (contract stabil) | Mechanical | P1/P5 | Evita divergenta web/API si pastreaza contractul `/v1/import/.../commit` | A schimba doar web-ul (divergenta tacuta) | | 9 | Eng | reset `reviewed` la schimbare valoare se implementeaza in calea editeaza/override | Mechanical | P1 | E un AC US-007 fara loc de implementare numit; `apply_row_override` e locul | A-l lasa nespecificat | | 10 | Design | `` ne-ISO: gol + hint + valoare bruta in hidden, fara pierdere | Taste->auto | P1 | Previne pierderea tacuta de date pe formate Excel; backend deja marcheaza needs_review | A goli pur si simplu inputul | | 11 | Design | US-003 fisier fara randuri: mesaj explicit „antet fara randuri de date" + blocheaza Continua | Mechanical | P1 | Edge case altfel = esec tacut | Doar „fara crash" | | 12 | US-004 | Bulk mapping: validare per-item, skip invalid + sumar, restul salvate, 1 re-render | Mechanical | P1 | PRD acopera „fara cod = ignorat" dar nu „cod invalid pe 1 din N" | All-or-nothing | | 13 | US-008 | Modal full-screen mobil: VERIFICA, nu re-adauga (exista base.html:407) | Mechanical | P4 | Regula deja prezenta la `@media max-width:767px` | A re-implementa | | 14 | DX | Q3: actualizam fixture-urile via factory in `conftest.py`, FARA `--allow-incomplete` in prod | Taste | P5/P4 | Escape-hatch lasa o veruca in codul de productie; factory centralizat e curat si mai bun pe termen lung | `--allow-incomplete` flag | | T1 | Design | needs_review: banner persistent deasupra tabelului | Taste -> APROBAT user 2026-06-26 | P1/P5 | Gate-ul mutat in modal devine usor de ratat; bannerul il face explicit | Doar pill+tooltip | | T2 | Design/Eng | „Confirma valorile" = buton explicit separat (nu implicit pe save) | Taste -> APROBAT user 2026-06-26 | P5 | Atestare explicita pe valori ambigue; evita confirmarea accidentala | Implicit pe orice save | | T3 | DX | Mesaj CUI duplicat include canal de contact configurabil (fallback la actual) | Taste -> APROBAT user 2026-06-26 | P1 | Operatorul primeste un pas urmator real; detectie in handler signup, nu `str(exc)` | Pastreaza mesajul ca in PRD | ### NOT in scope (confirmat) - Worker, reconciliere, idempotenta, `build_key`, masina de stari de trimitere, contract RAR (Non-Goals). - Canal API `POST /v1/prezentari` / `/valideaza` si `mapping.resolve_prestatii` — neschimbate. - Multi-utilizatori per firma (flux invitatie/alaturare) -> TODOS. - First-run E2E smoke ca poarta de release -> TODOS (deja deferat din 5.11). - Split 5.12a/5.12b -> respins (vezi D#1). ### What already exists (de refolosit, nu reconstruit) - Modal global `#detaliu-modal` + `inchideModal` (`HX-Trigger-After-Settle`, routes.py:1235/1394) — US-006 il refoloseste. - `include_oob` pentru OOB swap rand+contoare — US-006/007 il refolosesc. - `save_mapping` / `reresolve_account` — US-004 le refoloseste (fara logica noua de mapare). - Macro `camp` exista INLINE in ambele forme (`_preview_rand.html:51`, `_trimitere_detaliu.html:98`) — US-005 il EXTRAGE (nu „extinde in `_macros.html`" cum spune lista de fisiere; `_macros.html` are azi doar `autosend_toggle` gol). - Modal full-screen mobil + tinte touch 44px — deja in base.html (`@media max-width:767px`, liniile 407-427). US-008 = tableta + verificare, nu rescriere. - `_migrate` defensiv idempotent (tipar `users.is_admin` 3.3b) — US-001/007 il urmeaza. --- ## Faza 1 — CEO (strategie & scope) CEO DUAL VOICES — CONSENSUS: ``` Dimensiune Claude Codex Consens ------------------------------------- -------- ------- --------- 1. Premise valide? DA* N/A n/a (1 voce) 2. Problema corecta? DA N/A n/a 3. Calibrare scope? DISAGREE N/A -> taste (split?) 4. Alternative explorate suficient? PARTIAL N/A n/a 5. Riscuri competitive acoperite? DA N/A n/a 6. Traiectorie 6 luni sanatoasa? DA N/A n/a * premise = decizii user deja luate (Q1/Q2 inchise, modal/calendar = „decizie utilizator") ``` **Examinat, nimic blocant pe strategie.** PRD-ul rezolva first-run friction confirmat E2E; scope-ul e calibrat prin valuri. CEO subagent a recomandat split-ul in 2 release-uri (D#1, respins) si a ridicat findings de „testeaza worker-ul" care cad in afara Non-Goals (D#2, respinse). Single-critical pastrat: Q3 (backward-compat CLI) — real, mutat la faza DX/Eng. Dream-state delta: 5.12 inchide first-run UX; ramane (separat) poarta E2E smoke + flux multi-user firma. ## Faza 2 — Design (UI/UX) Litmus (Claude design; Codex n/a): ``` Dimensiune Scor Nota -------------------------------- ----- ------------------------------------------- Ierarhie informatie (mapcoloane) 7/10 US-003 ok; recomandat cap-tabel sticky pe fisiere cu 15+ coloane Stari (load/empty/error/partial) 6/10 empty-file (US-003) si date ne-ISO (US-005) sub-specificate Gate needs_review in modal 6/10 LANDMINE: gate HARD mutat dintr-o coloana vizibila intr-un modal Responsive tableta (US-008) 7/10 breakpoint lipseste azi; spec sa fie pixel-exact, nu aspirational Interactiune modal (US-006/007) 6/10 „Confirma valorile" = buton separat vs implicit-pe-save (ambiguu) ``` **Issue-uri auto-decise:** D#10 (date ne-ISO), D#11 (empty-file mesaj). **Taste surfaced la gata:** banner discoverability pe needs_review (T1) + buton „Confirma valorile" explicit (T2). ## Faza 3 — Eng (arhitectura & teste) ENG DUAL VOICES — CONSENSUS: ``` Dimensiune Claude Codex Consens --------------------------- -------- ------- --------- 1. Arhitectura sanatoasa? DA(cond) N/A n/a — cond. pe partial corect parametrizat 2. Acoperire teste? PARTIAL N/A n/a — vezi test plan, 4 gap-uri 3. Riscuri performanta? DA N/A n/a — irelevant (UI/CRUD mic) 4. Securitate? DA N/A n/a — CSRF+scoped pastrate pe rute noi 5. Cai de eroare? PARTIAL N/A n/a — bulk mapping partial, date ne-ISO 6. Risc deployment? DA N/A n/a — 2 migrari nullable defensive ``` Diagrama arhitectura (componente noi vs existente): ``` US-001 create_account(+email) -> auth_routes.signup ──┐ accounts.email (migrare) -> tools/account.py CLI ┤ (3 call-sites de actualizat) account_is_complete ┘ US-005 _form_editare.html (NOU) <── _trimitere_detaliu.html (cod_prestatie select + nemapate_inline RAMAN aici) └──< _preview_rand.html (US-006: ramura inline ELIMINATA) US-006 preview „Editeaza" -> #detaliu-modal-body (GET fragment) -> POST /editeaza -> inchideModal + OOB US-007 import_rows.reviewed (migrare) -> _resolve_row_for_preview -> gate n_confirmat ├── web routes.py /confirma (citeste DB reviewed) └── API import_router commit_import (reviewed_rows -> seteaza reviewed=1; contract stabil) US-008 base.html @media (768-1024) NOU + verificare 767 existent ``` **Findings auto-decise:** D#5 (partial scope), D#6 (fix_map), D#7 (reviewed DEFAULT 0), D#8 (gate pe 2 canale), D#9 (reset reviewed in apply_row_override), D#12 (bulk partial). Test plan scris pe disc (44 codepath-uri, 4 gap-uri marcate). **Invariant critic confirmat (R2):** editarea preview ramane override-only, NU re-queue — test obligatoriu (#24/#25 in test plan): dupa editare preview, `submissions` neatins. ## Faza 3.5 — DX (CLI + erori + contract API) DX CONSENSUS (Claude; Codex n/a): ``` Dimensiune Nota --------------------------------- ------------------------------------------- CLI create --email/--cui Q3 nerezolvat: a face flag-uri obligatorii rupe fixture-urile Mesaj eroare CUI duplicat prietenos da, dar „cere accesul de la admin" nu spune CUM Contract API commit reviewed_rows risc de divergenta tacuta -> rezolvat de D#8 + test #39 Migrare fixture-uri recomandat factory in conftest.py (DX gap real) ``` **Auto-decis:** D#14 (Q3 -> factory, fara `--allow-incomplete`). **Taste surfaced:** mesaj CUI duplicat sa includa un canal de contact concret (T3). ### Cross-Phase Themes - **Tema A — Gate-ul needs_review (Design + Eng).** Design: mutarea in modal ascunde gate-ul (discoverability). Eng: gate-ul trebuie sa fie DB-backed pe ambele canale + reset la editare. Semnal high-confidence: tratati needs_review ca feature de sine statator in US-007, nu ca „stergere de coloana". -> T1 + D#8/D#9. - **Tema B — Sub-specificarea „Confirma valorile" (Design + Eng).** Ambii: cand se seteaza `reviewed=1`? Buton separat vs implicit pe save. -> T2. - **Tema C — Q3 backward-compat (CEO + DX + Eng).** Toate trei: a face email/CUI obligatorii rupe fixture-uri. -> D#14 (factory). ### Implementation Tasks (aggregate) _Niciun fisier `tasks-*.jsonl` per faza (autoplan ruleaza review-urile inline, nu skill-urile standalone)._ Task-urile concrete = AC-urile din US-001..008 + cele 14 decizii din audit trail + 4 gap-urile din test plan. ### Status: DONE_WITH_CONCERNS Concerns (de inchis inainte de executie, niciunul blocant): cele 3 taste decisions de la gate (T1 banner, T2 buton confirma, T3 contact CUI) + integrarea celor 14 decizii in AC-urile US. Codex indisponibil -> review single-voice; re-ruleaza dupa 2026-07-18 daca vrei al doilea unghi adversarial.