14 stories TDD prin echipa de workeri (lead orchestreaza, 3 teammates pe valuri cu fisiere disjuncte; routes.py + base.html serializate ca fisiere fierbinti). - US-001 fix filtrare data (_iso_date_prefix pe garda+comparatie, prinde timestamp cu ora) - US-002/007 operatie service distincta in payload_view + afisare in detaliu - US-003 pill-uri categorii (button/aria-pressed; needs_mapping --warn, needs_data/error --err); fara lista ID-uri/dropdown - US-004 paginare numerotata 25/pag (total ramificat SQL-COUNT vs fetch-all+slice, clamp page, poll pastreaza pagina) - US-005 VIN block-level sub nr - US-006/006b editare cod RAR + validare nomenclator + recalcul idempotency (needs_data/needs_mapping via /corecteaza, error via /repune) - US-008 card eroare 3-niveluri doar pe read-only + rezumat top-of-form - US-009 Mapari in meniu hamburger; scoatere tab-bar + role=tablist orfan - US-010/011 pagina Mapari consolidata + butoane icon SVG + dirty-state (fara kebab/emoji) - US-012/012b header centrat + logo ROMFAST (/static/romfast_logo.png) in header - US-013 paleta azur ROMFAST (#2E74D6/#1F66C9) + IBM Plex Sans/Mono self-host (woff2 reale) - US-014 selector tema ciclic Light/Dark/Petrol/Auto + anti-FOUC pe 4 stari Backend trimitere (worker/masina stari/idempotenta/mapping) + schema NEATINSE (UI/UX pur + 1 fix de filtrare). VERIFY context curat PASS; /code-review high: 1 finding material reparat (US-006b). Regresie 896 passed, 1 skipped, 0 failed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
42 KiB
PRD 5.10 — UX trimiteri (pill filtre, paginare, detaliu) + Mapari in meniu
Stare: inchis (2026-06-25 — 14 stories + fix US-006b TDD prin echipa; VERIFY PASS; /code-review high 1 finding material reparat; regresie 896 passed / 1 skipped / 0 failed; fonturi IBM Plex reale)
Proces complet:
docs/ROADMAP.md§5. Contractul RAR (sursa de adevar):docs/api-rar-contract.md. Starea trece:draft → aprobat → in-executie → verify-pass → inchis(actualizata de lead).
1. Obiectiv
Curatare UX a dashboard-ului pe doua zone: (a) tabelul de trimiteri — pill-uri de filtrare pe categorii de problema in locul listei expandate de ID-uri, filtrare pe interval de data care chiar functioneaza pe timestamp-uri, paginare numerotata, VIN sub numarul de inmatriculare, editarea operatiei RAR si afisarea operatiei de service in detaliu, plus erori mai putin verbose in formularul de editare; (b) Mapari — mutata in meniul hamburger (fara tab-uri pe pagina principala), o singura pagina cu sectiunile consolidate si butoane de salvare/stergere vizibile.
Pur UI/UX + un fix de filtrare backend. Backend-ul de trimitere (worker, masina de stari, idempotenta, mapping-rezolvare) ramane NEATINS.
2. Non-Goals (anti scope-creep)
- Nu atingem worker-ul, masina de stari, idempotenta sau logica de mapare operatie→cod.
- Nu permitem editarea operatiei RAR pe stari trimise/in curs (
sent/sending/queued) — la RARFINALIZATAe terminal; editarea apare doar peneeds_data/needs_mapping/error. - Nu schimbam paginarea client-side existenta din tabelele Mapari (datatable
data-dt). - Nu schimbam mecanismul de persistenta/anti-FOUC al temei (PRD 5.3) — il extindem cu teme noi, nu il rescriem.
- Nu adaugam filtre noi (doar pill-uri pe categoriile de problema deja existente + fix data).
- Nu modificam contractul API
POST /v1/prezentari/GET /v1/prezentari(operatia de service afisata vine din payload-ul deja stocat, nu cere camp nou). - Nu reproiectam meniul hamburger in sine — doar adaugam intrarea „Mapari" si scoatem tab-bar-ul.
- Acceptat explicit (review C4): doua idiomuri de paginare coexista — server-side numerotat pe Trimiteri (US-004) vs client-side
data-dt(Inapoi/Inainte) pe Mapari. Diferenta e intentionata, nu „de reparat" ulterior. - Acceptat explicit (review C2): butonul ciclic de tema (US-014) are cost de descoperire (4 teme fara optiuni vizibile). Compensat prin
aria-labelcurenta+urmatoarea + tooltip; nu trecem la meniu/popover in aceasta livrabila.
3. Stories atomice
Fiecare story: cea mai mica unitate care lasa sistemul functional. Backend + UI pentru acelasi comportament = 2 stories.
US-001: Fix filtrare pe interval de data (backend)
Ca operator vreau ca filtrul „Data de la / pana la" sa returneze toate trimiterile din
acea perioada pentru ca acum, fiindca data_prestatie poate avea ora/minut/secunda,
comparatia de string exclude randurile si tabelul apare gol.
- Depinde de: —
- Fisiere:
app/web/routes.py(fragment_submissions,_is_iso_date),tests/test_web_filtre_submissions.py(~2 fisiere) - Test intai (RED):
tests/test_web_filtre_submissions.py—test_filtru_data_include_timestamp_cu_ora,test_filtru_data_interval_inclusiv_capete,test_filtru_data_ignora_valori_ne_data - Acceptance criteria:
- (must-fix, review C1) Cauza reala = garda
_is_iso_datecerelen(s)==10(routes.py:640-649), deci oricedata_prestatiecu ora e exclusa inainte de comparatie. Fix: introdu_iso_date_prefix(value) -> str | None(intoarcevalue[:10]daca parseaza caYYYY-MM-DD, altfelNone) si foloseste-l atat pentru garda cat si pentru comparatie. Nu modifica doar liniile de comparatie — altfel bug-ul ramane. - Filtrarea compara doar portiunea de data (primele 10 caractere,
YYYY-MM-DD) a luidata_prestatie, chiar daca valoarea contine ora/minut/secunda (ex.2026-06-20 14:35:07sau2026-06-20T14:35:07). - Intervalul e inclusiv la ambele capete:
data_de <= data(rand) <= data_pana. - O singura limita (doar
data_desau doardata_pana) functioneaza corect. - Valorile care nu incep cu o data ISO valida raman excluse din filtru (comportament actual pastrat).
python3 -m pytest tests/test_web_filtre_submissions.py -qtrece.
- (must-fix, review C1) Cauza reala = garda
- Verificare E2E: browser pe
/— import/seed cu o trimitere cudata_prestatiece contine ora; filtru pe acea zi → randul apare.
US-002: Expune operatia de service in view-ul de rand si detaliu (backend)
Ca operator vreau sa vad operatia de service originala (codul intern / denumirea venita prin API sau import CSV) pentru ca vreau sa stiu ce a cerut service-ul, nu doar codul RAR mapat.
- Depinde de: —
- Fisiere:
app/payload_view.py(sauapp/web/routes.py_detaliu_ctx/prezentare_din_payload),tests/test_payload_view.py(~2 fisiere) - Test intai (RED):
tests/test_payload_view.py—test_operatie_service_din_cod_op_service,test_operatie_service_din_denumire,test_fara_operatie_service_cand_lipseste - Acceptance criteria:
- (must-fix, review M1)
prezentare_din_payloadcolapseaza acum service-op si RAR-op in chei suprapuse (operatie = denumire or cod,cod_rar = cod_prestatie;payload_view.py:111-113). Adauga chei noi distincte (ex.op_service_cod+op_service_denumire) dincod_op_service/denumire, separate de operatia RAR mapata. - Conventie goala (must-fix, review M1): payload_view emite
EMPTY="—"pentru valori lipsa (payload_view.py:119-127), dar US-007 cere „randul nu apare deloc (fara — gol)". Alege O conventie: fie campul nou intoarce""/None(nu—), fie template-ul testeaza fata de'—'(ca randul VIN,_submissions.html:63). Documenteaz-o aici si in US-007. - Cand payload-ul nu contine operatie de service (a venit direct cu
cod_prestatie), campul e gol conform conventiei de mai sus, fara a arunca. - Helper-ul ramane pur (fara DB), defensiv la payload lipsa/corupt.
python3 -m pytest tests/test_payload_view.py -qtrece.
- (must-fix, review M1)
- Verificare E2E:
POST /v1/prezentaricucod_op_service+denumire→ randul are operatia de service in context.
US-003: Pill-uri de filtrare pe categorii de problema (UI)
Ca operator vreau pill-uri „Date incomplete / Lipsa cod / Eroare" cu numar in sectiunea de
filtrare, in loc de lista expandata #40 TMB...3456 / B28ERR ...si inca 3 pentru ca lista de
ID-uri e zgomotoasa; vreau sa apas un pill ca sa filtrez direct pe acea categorie.
- Depinde de: —
- Fisiere:
app/web/templates/_coada.html,app/web/templates/_status.html,app/web/routes.py(counts per categorie pe fragmentul de filtre),tests/test_web_pill_filtre.py(~4 fisiere) - Test intai (RED):
tests/test_web_pill_filtre.py—test_pill_per_categorie_cu_numar,test_pill_click_seteaza_status,test_fara_lista_id_uri - Acceptance criteria:
- In sectiunea de filtrare apar pill-uri: Date incomplete (
needs_data), Lipsa cod (needs_mapping), Eroare (error), fiecare cu numarul total scoped pe cont; pill-ul lipseste/e ascuns cand numarul e 0. - (must-fix, review M3/design-H1) Lista de ID-uri si blocul
blocate_actionabilsunt in_status.html:68-77(nu_coada.html), generate de_blocate_actionabil(routes.py:562-594). La eliminarea ID-urilor, scoate si codul mort care calculaprezentare_din_payload/vin_partialper rand. Pill-urile reutilizeaza contoarele deja calculate (_status_counts/n) — fara filtru backend nou. - (must-fix, review design-M1) Decide explicit soarta intregului bloc „Necesita atentia ta" + a contorului „Blocate": pill-urile inlocuiesc link-urile de categorie (nu triplu-encoda aceeasi informatie cu pill + link + dropdown).
- (must-fix, review M3/A3) Pill-urile sunt elemente focalizabile reale (
<button>/<a>, nu<span onclick>), cufocus-visible, activare Enter/Space, si stare activa viaaria-pressed(nu doar culoare). - Click pe un pill filtreaza tabelul pe acea stare (seteaza
status=pefragment_submissions, deja suportatroutes.py:699); pill activ evidentiat; click pe activ revine la „toate". - (must-fix, review M3) Sincronizare cu dropdown-ul
statusdin_coada.html:36-44: ori dropdown-ul reflecta pill-ul activ, ori e eliminat — fara desync (click pill vs valoare dropdown stale). - Exista si un control „Toate" (toggle) care reseteaza filtrul de categorie. Stare „toate zero" (steady-state sanatos): definita explicit (rand ascuns vs afisaj muted „nicio problema"), nu gap neexplicat.
- (must-fix, review S1/A5) Matrice de stil pill rezolvata fara contradictie rosu/galben vs accent: inactiv = contur/text pe culoarea categoriei (
--err/--warn); activ = umplere pe culoarea categoriei (NU accent albastru — altfel pill rosu „Date incomplete" devine albastru cand e activ). Contrast text pe pill-ul activ verificat AA in toate cele 3 teme. python3 -m pytest tests/test_web_pill_filtre.py -qtrece.
- In sectiunea de filtrare apar pill-uri: Date incomplete (
- Verificare E2E: browser pe
/— pill-uri cu numere; click pe „Date incomplete" → tabel filtrat; click din nou → toate.
US-004: Paginare numerotata pe tabelul de trimiteri (backend + UI)
Ca operator vreau paginare numerotata pe tabelul de trimiteri pentru ca acum se incarca max 200 randuri fara navigare si nu pot ajunge la cele mai vechi.
- Depinde de: US-001 (filtrul de data corect intra in acelasi handler)
- Fisiere:
app/web/routes.py(fragment_submissions: parampage, total + slice ramificat — vezi C1),app/web/templates/_submissions.html,app/web/templates/_coada.html(pollhx-include+page),tests/test_web_paginare_submissions.py(~3 fisiere) - Test intai (RED):
tests/test_web_paginare_submissions.py—test_pagina_implicita_25,test_pagina_2_offset,test_total_si_numar_pagini,test_paginarea_pastreaza_filtrele,test_pagina_peste_total_revine_la_ultima,test_poll_pastreaza_pagina - Acceptance criteria:
- (must-fix, review H1) Numararea totalului ramifica dupa tipul de filtru: fara filtru Python (status-only / niciun filtru) → SQL
COUNT(*)+LIMIT 25 OFFSET (page-1)*25; cu filtru vehicul/data activ → fetch-all (fara LIMIT, ca azi) → filtreaza in Python →total=len(filtrat)→ slice[offset:offset+25]. SQLCOUNT(*)/LIMIT/OFFSETe gresit cand filtrul Python e activ. Plafonul de 200 randuri din bucla se inlocuieste cu „fetch-all-then-slice" pe calea filtrata (altfel paginile >8 dispar silentios). - Marime pagina fixa 25 randuri; raspunsul include numarul total si pagina curenta.
- (must-fix, review H2)
pagein afara intervalului se clampeaza la[1, ceil(total/25)](nu pagina goala); schimbarea unui filtru reseteazapagela 1. - Controale:
‹ Inapoi, numere de pagina,Inainte ›; pagina curenta evidentiata cuaria-current="page"; capetele dezactivate (disabled) la prima/ultima pagina; pager-ul e ascuns candpages<=1sautotal==0(empty state inlocuieste tabelul). „afiseaza X-Y din N" intr-o regiunearia-live="polite". - Schimbarea paginii pastreaza filtrele active (status/pill, vehicul, data_de, data_pana) — link-urile de paginare poarta toti parametrii de filtru curenti.
- (must-fix, review L2) Poll-ul de 15s (
_coada.htmlhx-include="#filtre-trimiteri") NU trebuie sa reseteze pagina: includepagecurent in include-ul de poll (hidden input actualizat de paginare) SAU documenteaza explicit reset-pe-poll. Test:test_poll_pastreaza_pagina. python3 -m pytest tests/test_web_paginare_submissions.py -qtrece.
- (must-fix, review H1) Numararea totalului ramifica dupa tipul de filtru: fara filtru Python (status-only / niciun filtru) → SQL
- Verificare E2E: browser pe
/cu >25 trimiteri — navigare intre pagini, filtru aplicat ramane la schimbarea paginii, poll-ul nu te scoate de pe pagina curenta.
US-005: VIN sub numarul de inmatriculare in tabel (UI)
Ca operator vreau ca VIN-ul sa apara pe rand propriu sub numarul de inmatriculare pentru ca pe aceeasi linie e ingramadit si greu de citit.
- Depinde de: —
- Fisiere:
app/web/templates/_submissions.html,tests/test_web_submissions_layout.py(~2 fisiere) - Test intai (RED):
tests/test_web_submissions_layout.py—test_vin_pe_rand_separat_sub_nr - Acceptance criteria:
- (nota, review L1) VIN-ul e deja randat in
_submissions.html:61-65, dar inline (<span>dupa nr, aceeasi celula). Schimbarea e mica: element block sub nr (nu structura noua). Testul asserteaza un element block-level, nu doar prezenta. - In coloana Vehicul, numarul de inmatriculare e pe primul rand; VIN-ul (sau partiala VIN) apare dedesubt, in stil muted, nu pe aceeasi linie.
- Cand VIN-ul lipseste, nu apare rand gol (garda
!= '—'exista deja,_submissions.html:63). - Layout-ul ramane fara scroll orizontal pe tabel (scopat
.tabel-trimiteri, consistent cu PRD 5.8). python3 -m pytest tests/test_web_submissions_layout.py -qtrece.
- (nota, review L1) VIN-ul e deja randat in
- Verificare E2E: browser pe
/— VIN sub numar in fiecare rand.
US-006: Editare operatie RAR in formularul de detaliu (UI + backend corectie)
Ca operator vreau sa pot schimba operatia RAR (cod_prestatie) din formularul de detaliu pe
trimiterile blocate pentru ca uneori codul mapat e gresit si vreau sa-l corectez inainte de re-trimitere.
- Depinde de: —
- Fisiere:
app/web/templates/_trimitere_detaliu.html,app/web/routes.py(post_corectie_trimitere~979-1124),tests/test_web_editare_op_rar.py(~3 fisiere) - Test intai (RED):
tests/test_web_editare_op_rar.py—test_editabil_arata_select_cod_rar,test_salvare_schimba_cod_si_repune_in_coada,test_idempotency_key_se_schimba,test_cod_invalid_respins,test_sent_nu_arata_select - Acceptance criteria:
- Pe
needs_data/needs_mapping/error, formularul afiseaza un select cu codurile din nomenclator pentru operatia RAR, pre-selectat pe codul curent. - (must-fix, review H3) Riscul „refoloseste exact calea existenta" e GRESIT formulat:
post_corectie_trimitereaplica DOAR campurile vehiculului (vin/nr/data/odometru), nu citeste niciun cod de prestatie (routes.py:1009-1012). Story-ul cere logica noua in handler: (a) camp noucod_prestatiein form; (b) validare fata de nomenclator (oglindesteroutes.py:953-961); (c) injectare incontent["prestatii"][i]["cod_prestatie"]inainte deresolve_prestatii. DUPA injectare, restul caii existente (canonicalize →build_key→ check coliziune idempotency → re-queue,routes.py:1028-1104) recalculeaza corect payload+cheie (build_keyhashuiestecod_prestatie,idempotency.py:34). - Codul nou e validat fata de nomenclator; un cod necunoscut e respins (nu se injecteaza, nu se trimite raw — vezi invariantul
cod_prestatievalidat la ingestie din CLAUDE.md). - Test
test_idempotency_key_se_schimba: cheia de idempotency difera dupa schimbarea codului (nu doarstatus=queued). - Daca payload-ul are mai multe prestatii, story-ul tinteste prima/operatia editabila explicit (nu ambiguu).
- Pe
sent/sending/queuedoperatia RAR ramane read-only (fara select). - Scoped pe sesiune + CSRF, 404 cross-account.
python3 -m pytest tests/test_web_editare_op_rar.py -qtrece.
- Pe
- Verificare E2E: browser pe
/— pe o trimitereneeds_data, schimba codul RAR din select, salveaza → cod nou aplicat + cheie idempotency noua; cod invalid → respins; pe o trimiteresent, codul e read-only.
US-007: Afisare operatie de service in detaliu (UI)
Ca operator vreau sa vad operatia de service originala in formularul de detaliu pentru ca vreau sa stiu ce a cerut service-ul prin API/CSV, alaturi de codul RAR.
- Depinde de: US-002
- Fisiere:
app/web/templates/_trimitere_detaliu.html,tests/test_web_detaliu_op_service.py(~2 fisiere) - Test intai (RED):
tests/test_web_detaliu_op_service.py—test_detaliu_arata_operatie_service,test_detaliu_omite_cand_lipseste - Acceptance criteria:
- In detaliu apare „Operatie service" (cod intern + denumire) cand a existat in payload, distinct de „Operatie RAR".
- Cand operatia de service lipseste (a venit direct cu
cod_prestatie), randul nu apare deloc (fara „—" gol). - Apare atat in contextul editabil cat si in cel read-only.
python3 -m pytest tests/test_web_detaliu_op_service.py -qtrece.
- Verificare E2E: browser pe
/— trimitere venita cucod_op_servicearata operatia de service in detaliu; una venita cucod_prestatiedirect nu o arata.
US-008: Simplificare eroare in formularul de editare (UI)
Ca operator vreau ca in formularul de editare sa apara doar textul simplu al erorii (subliniat), nu blocul verbose pe 3 niveluri pentru ca „De ce / Cum repari" + prefixul tehnic dubleaza un mesaj deja descriptiv (ex. „odometruFinal trebuie sa fie un numar intreg (ca string).").
- Depinde de: —
- Fisiere:
app/web/templates/_trimitere_detaliu.html(zona editabila),tests/test_web_detaliu_eroare_simpla.py(~2 fisiere) - Test intai (RED):
tests/test_web_detaliu_eroare_simpla.py—test_form_editare_fara_card_3niveluri,test_eroare_pe_camp_doar_text_simplu - Acceptance criteria:
- (must-fix, review M4) Blocul
erori_3n/card_erorie acum randat inainte de form, in afara ramurii{% if editabil %}(_trimitere_detaliu.html:22-27) — deci apare si pe read-only. Ascunde-l DOAR in editare: muta-l/infasoara-l in{% if not editabil %}ca sa-l pastrezi pe contextele read-only (catalogul PRD 5.4). - Eroarea per camp ramane sub input ca text simplu subliniat (mesajul descriptiv). Nota: macro-ul
campprinteaza deja doarerr_map.get(nume)(mesajul, fara cod de camp) — verifica doar camessagenu inglobeaza numele campului. - (must-fix, review M6/design) Erorile fara camp (
field is None) nu trebuie sa dispara silentios cand scoatem cardul 3n: defineste unde apar (ex. un rezumat simplu top-of-form) in formularul de editare. - Restul contextelor (lista compacta / detaliu read-only) raman neschimbate — simplificarea e scopata doar pe formularul de editare.
python3 -m pytest tests/test_web_detaliu_eroare_simpla.py -qtrece.
- (must-fix, review M4) Blocul
- Verificare E2E: browser pe
/— corectie cu odometru invalid → sub camp apare doar textul erorii subliniat, fara cardul pe 3 niveluri.
US-009: Mapari in meniul hamburger + scoatere tab-uri (UI)
Ca operator vreau ca Mapari sa fie o intrare in meniul hamburger, nu tab pe pagina principala pentru ca vreau pagina principala curata (doar Acasa), iar tab-bar-ul Acasa/Mapari incurca.
- Depinde de: —
- Fisiere:
app/web/templates/base.html(meniu#cont-menu),app/web/templates/dashboard.html(tab-bar),app/web/routes.py(rutare pagina Mapari, ex./?tab=maparisau/mapari),tests/test_web_mapari_meniu.py(~3 fisiere) - Test intai (RED):
tests/test_web_mapari_meniu.py—test_meniu_contine_mapari,test_pagina_principala_fara_tabbar_mapari,test_ruta_mapari_randeaza_sectiunea - Acceptance criteria:
- Meniul hamburger contine o intrare Mapari (cu badge pentru
needs_mappingdaca exista contoare). Badge-ul muta din tab-bar pe item-ul de meniu — sursa contorului ramanebadges.mapari(routes.py:423-424); verifica sa nu ramana badge dangling. - Tab-bar-ul Acasa/Mapari de pe pagina principala e eliminat; Acasa devine continutul principal direct.
- (must-fix, review design-C1) Scoate si schela ARIA
role="tablist"orfana + JS-ul de navigare cu sageti dindashboard.html:16-88(unrole="tablist"cu un singur tab e o violare ARIA). Curata plumbing-ulactive_tabramas fara sens daca nu mai e folosit. - Exista o ruta dedicata care randeaza pagina Mapari (server-side, deep-link).
?tab=mapariramane valid (_TABS_VALIDEincludemapari,routes.py:155; randat de_render_panel_for_tab) — nu da 404. Verifica sa nu ramanahx-getcatre un element de tab eliminat (banner/badge). python3 -m pytest tests/test_web_mapari_meniu.py -qtrece.
- Meniul hamburger contine o intrare Mapari (cu badge pentru
- Verificare E2E: browser pe
/— fara tab-bar; din ☰ → Mapari → pagina Mapari.
US-010: Restructurare pagina Mapari intr-o singura pagina consolidata (UI)
Ca operator vreau o singura pagina Mapari cu „De rezolvat" prima, apoi salvate / reguli text / formate coloane, fara sectiunea de ajutor si fara textul gol pentru ca sectiunile separate si textele de ajutor ma incurca.
- Depinde de: US-009
- Fisiere:
app/web/templates/_mapari.html,tests/test_web_mapari_layout.py(~2 fisiere) - Test intai (RED):
tests/test_web_mapari_layout.py—test_de_rezolvat_prima,test_fara_ajutor_si_empty_text,test_ordine_sectiuni - Acceptance criteria:
- Ordinea sectiunilor: (1) De rezolvat prima, (2) Mapari operatii salvate, (3) Reguli automate (text), (4) Formate de coloane salvate.
- Sectiunea de ajutor (
<details class="ajutor-mapari">) e eliminata. - Textul empty-state „Nicio operatie nemapata — tot ce a venit s-a tradus in coduri RAR. Importa un fisier nou..." e eliminat (sectiunea „De rezolvat" goala nu mai afiseaza acel paragraf).
- Fiecare rand din „De rezolvat" pastreaza select-ul + butonul de salvare vizibil (vezi US-011).
python3 -m pytest tests/test_web_mapari_layout.py -qtrece.
- Verificare E2E: browser pe pagina Mapari — ordine corecta, fara ajutor, fara empty-text.
US-012: Branding header „by ROMFAST" + titlu centrat (UI)
Ca utilizator vreau ca header-ul sa aiba branding-ul „by ROMFAST" si titlul pe mijloc pentru ca vreau identitate vizuala clara, parte din familia ROMFAST/ROA, nu un header anonim.
Sistem de design complet:
DESIGN.md(sectiunile „Header & branding" + „Culori de brand").
- Depinde de: —
- Fisiere:
app/web/templates/base.html(header + CSS),tests/test_web_header_branding.py(~2 fisiere) - Test intai (RED):
tests/test_web_header_branding.py—test_header_contine_by_romfast,test_titlu_centrat - Acceptance criteria:
- Titlul „Gateway RAR AUTOPASS" e centrat in header (grila 3 coloane: controale — tema, versiune, ☰ — la dreapta, fara a strica centrarea optica a titlului).
- (must-fix, review S2) Plaseaza explicit badge-ul
env(test/prod, azibase.html:320) in grila — celula stanga (echilibru) sau langa titlu — si defineste ordinea de colaps pe mobil (3-col cu titlu centrat + controale care wrap e fragil). - Sub titlu, mic: wordmark „by ROMFAST" redat ca text stilizat —
byin--muted,ROMin#D1342F(rosu logo),FASTin#2E74D6(albastru logo). NU se foloseste PNG-ul 3D al logo-ului. - Responsiv: pe mobil wordmark-ul ramane sub titlu, controalele nu se suprapun (degrada elegant).
- Light + dark OK (wordmark pe culori proprii, lizibil pe ambele fundaluri).
python3 -m pytest tests/test_web_header_branding.py -qtrece.
- Verificare E2E: browser pe
/desktop + mobil — titlu centrat, „by ROMFAST" cu ROM rosu / FAST albastru, controale la dreapta.
US-013: Tema de culori ROMFAST + tipografie (UI)
Ca utilizator vreau o paleta cu accent albastru ROMFAST (ca romfast.ro) si o tipografie coerenta pentru ca acum totul e gri si fara identitate, iar produsul e parte din familia ROA.
Valori complete:
DESIGN.md(sectiunile „Decizie cromatica" + „Tipografie").
- Depinde de: design-consultation (DONE —
DESIGN.mdscris) - Fisiere:
app/web/templates/base.html(variabile:root+[data-theme="light"]+font-family),app/web/static/fonts/(woff2 IBM Plex),tests/test_web_tema_culori.py(~3 fisiere) - Test intai (RED):
tests/test_web_tema_culori.py—test_paleta_accent_azur_definita,test_font_ibm_plex_aplicat,test_contrast_aa_pe_text_principal - Acceptance criteria:
- Accentul devine azur ROMFAST:
--accent:#2E74D6(dark) /#1F66C9(light), aplicat prin variabile CSS (butoane primare, pill activ, linkuri, focus) — fara culori noi hardcodate imprastiate. - Neutrele actualizate conform
DESIGN.md(dark--bg:#0f1218/--card:#181c24; light--bg:#f5f7fa/--card:#ffffff); stari--ok/--warn/--errpastrate AA per tema. - Tipografie:
font-familyUI = IBM Plex Sans (fallbacksystem-ui); monospace (coduri RAR/VIN/nr.) = IBM Plex Mono. Self-host woff2 cufont-display:swap(subset latin-ext pentru diacritice). Fallback de sistem nu strica layout-ul. - (must-fix, review M3/design)
font-display:swapproduce un FOUT system-ui→IBM Plex; pe coloanele tabulare (VIN/coduri,tabular-numsbase.html:54) o nepotrivire de metrici da reflow. Defineste fallback cu metrici ajustate (size-adjust/ascent-override) SAU accepta explicit FOUT-ul si confirma ca tabularele nu fac reflow vizibil. - Contrastul textului principal ramane AA in ambele teme; accentul-ca-text pe alb foloseste varianta inchisa (
#1F66C9). - Comutatorul light/dark existent (PRD 5.3) + anti-FOUC functioneaza in continuare.
python3 -m pytest tests/test_web_tema_culori.py -qtrece.
- Accentul devine azur ROMFAST:
- Verificare E2E: browser pe
/— accent azur + IBM Plex in light si dark, pill-uri rotunjite ca pe romfast.ro, contrast verificat.
US-014: Selector de tema ciclic (Light/Dark/Petrol/Auto) (UI)
Ca utilizator vreau ca butonul de tema sa cicleze prin mai multe teme (inclusiv una petrol) pentru ca vreau sa-mi aleg aspectul, nu doar light/dark, ca pe demoanaf.ro.
Spec complet:
DESIGN.md(sectiunile „Selector de tema" + paleta „Petrol").
- Depinde de: US-013 (paleta azur + variabilele de baza)
- Fisiere:
app/web/templates/base.html(script anti-FOUC +[data-theme="petrol"]+ JS comutator + buton),tests/test_web_selector_tema.py(~2 fisiere) - Test intai (RED):
tests/test_web_selector_tema.py—test_petrol_theme_definit,test_buton_cicleaza_temele - Acceptance criteria:
- Butonul de tema cicleaza la click: Light → Dark → Petrol → Auto → Light; iconita +
aria-label/titlereflecta tema curenta. - (must-fix, review A2/design) Buton accesibil:
aria-labelanunta curenta + urmatoarea („Tema: Petrol, apasa pentru Auto"); schimbarea anuntata via regiunearia-live="polite". Tooltip enumera ciclul. Cost de descoperire (4 teme intr-un buton fara optiuni vizibile) acceptat explicit in Non-Goals. - Tema noua Petrol definita ca
[data-theme="petrol"]cu valorile dinDESIGN.md(accent--accent:#0E7C7B, neutre petrol-inchise). - Auto urmeaza
prefers-color-scheme(rezolva la Light/Dark azur); nu seteazadata-themefix. - (must-fix, review M4/design) Scriptul anti-FOUC din
<head>(base.html:19-29) cunoaste azi doarlight/dark. Extinde-l atomic sa enumere toate cele 4 stari; o valoarelocalStorage.themelegacy/necunoscuta are fallback definit (nu blink, nu stare invalida). Rezolva „Auto" la light/dark inainte de primul paint. - (must-fix, review S3/design) Wordmark-ul „FAST"
#2E74D6(albastru) coexista pe ecranul Petrol cu accentul teal#0E7C7B— verifica armonia/contrastul wordmark-ului (ROM rosu + FAST albastru) pe toate cele 3 teme concrete, nu doar light/dark. - Toate suprafetele raman lizibile (AA pe text principal) in fiecare din cele 3 teme concrete.
python3 -m pytest tests/test_web_selector_tema.py -qtrece.
- Butonul de tema cicleaza la click: Light → Dark → Petrol → Auto → Light; iconita +
- Verificare E2E: browser pe
/— click pe buton cicleaza Light→Dark→Petrol→Auto; refresh pastreaza tema; Petrol are accent teal, fara blink la load.
US-011: Butoane icon salvare/stergere vizibile + evidentiere modificari nesalvate (UI)
Ca operator vreau butoane mici cu icon de salvare/stergere mereu vizibile pe randurile de mapari, evidentiate cand am modificari nesalvate pentru ca acum trebuie sa intru intr-un meniu contextual (kebab) si nu imi dau seama ca trebuie sa apas „Salveaza".
- Depinde de: US-010
- Fisiere:
app/web/templates/_mapari.html,app/web/templates/base.html(CSS/JS mic pentru stare „dirty"),tests/test_web_mapari_actiuni.py(~3 fisiere) - Test intai (RED):
tests/test_web_mapari_actiuni.py—test_butoane_icon_vizibile_pe_rand,test_fara_kebab_menu - Acceptance criteria:
- Pe „Mapari operatii salvate" si „Reguli automate (text)", actiunile Salveaza/Sterge sunt butoane mici cu icon mereu vizibile pe rand, nu ascunse in meniu kebab.
- (must-fix, review A1 — decizie de taste, vezi poarta) Icon-urile sunt SVG/text stilizate ca icon (reuse
.icon-btn), NU emoji brute 💾/🗑: emoji-ul nu se recoloreaza pe teme/dirty-state si randeaza inconsistent intre OS-uri. Numele accesibil dinaria-label; glifa decorativaaria-hidden. - Meniul kebab (
position:fixed) e eliminat. - (must-fix, review S4) Cand utilizatorul schimba select-ul unui rand, butonul de salvare devine evidentiat concret (ex. fundal
--accent+ nu emoji) ca semnal de „modificari nesalvate"; in starea normala e discret. Starea „dirty" e efemera per-render (un swapouterHTMLo reseteaza — fara persistenta asteptata). - Confirmarea la stergere (
hx-confirm) se pastreaza. - (must-fix, review A6) Accesibil: butoanele au
aria-labeldescriptiv; pe mobil regula 44px e scopata ca icon-urile sa ramana icon-size (nu full-width ca butoanele normalemin-height:44px;width:100%), cu zona de atingere adecvata. python3 -m pytest tests/test_web_mapari_actiuni.py -qtrece.
- Verificare E2E: browser pe pagina Mapari — schimba un cod RAR la o mapare salvata → butonul de salvare se evidentiaza; salveaza/sterge din icon-uri vizibile.
4. Riscuri
- Filtrarea pe data (US-001):
data_prestatiepoate veni in formate diferite (ISO cu/ fara ora,Tvs spatiu). Mitigare: normalizeaza la primele 10 caractere doar cand sunt o data ISO valida; pastreaza excluderea valorilor ne-ISO (comportament existent) + test parametrizat pe formate. - Paginare + filtru in Python (US-004): filtrele vehicul/data se aplica post-SQL; numararea
totalului trebuie sa respecte filtrul, nu doar
COUNT(*)SQL. Mitigare: numara dupa filtrare la scara actuala (plafon perf deja notat in cod) si testeazatotalcu filtru activ. - Editare operatie RAR (US-006): schimbarea codului trebuie sa recalculeze payload + idempotency
ca la corectia
needs_dataexistenta, altfel risca chei divergente. Mitigare: refoloseste exact calea de corectie/mapare existenta, nu una noua. - Mutare Mapari din tab (US-009): deep-link-uri vechi
?tab=maparipot exista. Mitigare: alias/redirect, test ca nu da 404. - Regresie pe contexte de eroare (US-008): simplificarea trebuie scopata DOAR pe formularul de editare; lista compacta + detaliu read-only + API raman pe catalogul 3-niveluri (PRD 5.4).
5. Intrebari deschise
Rezolvate cu utilizatorul inainte de executie (poarta de aprobare PRD). Toate clarificate prin AskUserQuestion in sesiunea de planificare:
- Pill filtre: un pill per categorie (Date incomplete / Lipsa cod / Eroare) cu numar, nu un singur pill combinat.
- Paginare: pagini numerotate, 25 randuri/pagina, total vizibil, pastreaza filtrele.
- Editare operatie RAR: doar pe stari editabile (
needs_data/needs_mapping/error); read-only pesent/sending/queued. - Layout Mapari: De rezolvat prima, apoi salvate / reguli text / formate coloane; fara ajutor, fara empty-text.
- Butoane icon: evidentiere la modificare (dirty state) pe langa vizibilitatea permanenta.
- Branding + paleta (US-012/US-013): rezolvate prin
design-consultation→DESIGN.md. Wordmark „by ROMFAST" sub titlu (ROM rosu#D1342F+ FAST albastru#2E74D6); accent functional azur ROMFAST (#2E74D6/#1F66C9), consistent cu romfast.ro; font IBM Plex Sans + Mono. - Selector de tema (US-014): buton ciclic ca demoanaf.ro, set Light/Dark/Petrol/Auto; petrolul (directia initiala) revine ca tema selectabila.
6. Valuri de executie (graful de dependente)
Val 1 (paralel, fisiere disjuncte):
[US-001] fix filtrare data (routes.py)
[US-002] operatie service in view (payload_view.py)
[US-003] pill-uri filtre (_coada.html / _status.html)
[US-005] VIN sub nr (_submissions.html)
[US-008] eroare simpla in editare (_trimitere_detaliu.html)
[US-009] Mapari in meniu (base.html / dashboard.html)
Val 2 (deblocate de Val 1):
[US-004] paginare ← US-001 (acelasi handler)
[US-006] editare op RAR ← (independent, dar atinge _trimitere_detaliu.html → dupa US-008)
[US-007] op service detaliu ← US-002
[US-010] restructurare Mapari ← US-009
Val 3:
[US-011] butoane icon + dirty state ← US-010
Branding/tema (dupa design-consultation, ating base.html → serializeaza intre ele):
[US-013] paleta azur ROMFAST + IBM Plex ← DESIGN.md (DONE)
[US-012] header „by ROMFAST" + titlu centrat ← (independent, dar atinge base.html → dupa US-013)
[US-014] selector tema ciclic + Petrol/Auto ← US-013
Nota orchestrare: US-006, US-007, US-008 ating toate
_trimitere_detaliu.html→ serializeaza-le pe acelasi fisier (nu in worktree-uri paralele). La fel US-010/US-011 pe_mapari.html, si US-012/US-013/US-014 pebase.html(toate trei ating header/CSS/script — un singur autor secvential).
Raport autoplan (review CEO + Design + Eng)
Rulat 2026-06-25. Codex indisponibil (usage limit pe contul OpenAI pana 2026-07-18) → toate fazele degradate la subagent-only (
[codex-unavailable], single-model). Voci independente Claude pe fiecare faza (fara context prealabil). Constatarile tehnice critice verificate direct in cod de lead inainte de aplicare.
Consensus (subagent-only — o singura voce per dimensiune)
CEO : Premise valide (bug US-001 confirmat real). Recomandare = split branding/tema.
DESIGN : Tare pe backend; lacune in stari vizuale + a11y controale noi (emoji, ciclu tema, pill).
ENG : 4 must-fix verificate in cod (C1 _is_iso_date prefix; H1 total post-filtru;
H3 US-006 nu e reuse pur; M4 card 3n in afara ramurii editabil).
Cross-phase themes (semnalate de 2+ voci independent)
- US-006 e cel mai riscant story — CEO (riscant, confirma calea) + Eng (H3: NU e reuse pur, cere logica noua + validare nomenclator + assert pe cheia idempotency). Aplicat in AC.
- A11y a controalelor noi — Design (emoji, ciclu tema, pill fara semantica) e clasa de probleme pe care TDD pe substring Jinja o trece dar livreaza un experience rupt la tastatura/screen-reader. Aplicat (SVG/text, aria-pressed, aria-live).
- Triplu-encoding contoare problema — Design (pills + „Necesita atentia ta" + badge Mapari). Aplicat: pills inlocuiesc link-urile.
Decizii auto (principiile 6) — must-fix aplicate in AC
| # | Faza | Decizie | Clasificare | Principiu | Rationament |
|---|---|---|---|---|---|
| 1 | Eng | US-001: _iso_date_prefix (garda+comparatie pe [:10]) |
Mecanic | P1/P5 | Fara asta bug-ul ramane; o singura solutie corecta |
| 2 | Eng | US-004: total ramificat (SQL count fara filtru Python / fetch-all+slice cu filtru) | Mecanic | P1 | Contradictie Fisiere↔AC; SQL COUNT gresit cu filtru Python |
| 3 | Eng | US-004: clamp page, ascunde pager la pages<=1, poll pastreaza pagina |
Mecanic | P1 | Edge-cases + regresie poll |
| 4 | Eng | US-006: camp cod_prestatie + validare nomenclator + assert cheie idempotency (NU reuse pur) |
Mecanic | P1/P5 | Verificat in routes.py:1009-1012 |
| 5 | Eng | US-002/007: cheie payload distincta + conventie goala (— vs None) |
Mecanic | P5 | Chei suprapuse azi; conventie ambigua intre stories |
| 6 | Eng | US-008: muta cardul 3n in {% if not editabil %} + erori fara camp |
Mecanic | P1 | Cardul e azi in afara ramurii editabil |
| 7 | Design | US-003: pill-uri butoane reale (aria-pressed), inlocuiesc link-urile, matrice culori categorie (nu accent), sincron cu dropdown | Mecanic | P1 | A11y + anti triplu-encoding |
| 8 | Design | US-009: scoate schela role="tablist" orfana + JS |
Mecanic | P1 | Un tab in tablist = violare ARIA |
| 9 | Design | US-011: icon-uri SVG/text (nu emoji brute), dirty-state concret, 44px scopat | Taste→aplicat | P1/P5 | User a scris emoji; SVG satisface intentia + a11y + recolorare (vezi poarta) |
| 10 | Design | US-012/013/014: env badge in grila; FOUT pe tabulare; legacy localStorage.theme; ciclu a11y; wordmark pe 3 teme |
Mecanic | P1 | Stari vizuale lipsa |
Decizii de taste / User Challenge — la poarta (NU auto-decise)
- UC-1 (CEO, single-voice): split branding/tema (US-012/013/014) intr-un PRD separat + taie Petrol & selectorul ciclic. Default = PASTRAM (user a ales explicit in sesiune: Petrol ca tema, ciclu ca demoanaf, IBM Plex). CEO ruleaza ca o singura voce (codex jos) — recomandare, nu decizie.
- UC-2 (CEO): paginarea (US-004) poate fi prematura la scara actuala (purge 90z). Default = PASTRAM (alegere user); cost low (1 query de validat numarul max randuri/cont inainte de build).
- T-1 (Design A1): emoji 💾/🗑 (scrise de user) → SVG/text icon. Aplicat ca SVG (satisface ambele), surfata la poarta fiindca user a scris emoji literal.
Deferat (TODOS, nu in 5.10)
- Hardening GET-uri de listare globale/neprotejate (CLAUDE.md, semnalat de CEO ca valoare mai mare) — separat, nu expandam acest PRD.
- Validare empirica „>200 randuri/cont" pentru US-004 si „cerere reala >2 teme" pentru US-014.
Verdict poarta (2026-06-25)
- UC-1 → PASTRAM tot in 5.10 (14 stories, functional + branding/tema). User a confirmat la poarta; CEO single-voice = recomandare, nu verdict. Riscul FOUC/FOUT mitigat prin AC-urile adaugate.
- UC-2 → PASTRAM paginarea (US-004). Cost mic, specificata corect dupa review.
- T-1 → icon-uri SVG/text (nu emoji brute), aplicat in US-011.
- APROBAT. Toate must-fix-urile aplicate in AC. Gata de executie (TDD prin echipa, vezi valuri §6) sau
/ralph.
Raport VERIFY
Completat de subagentul verificator (context curat, fara transcriptul executiei) — ROADMAP §5.6. 2026-06-25.
Verdict: PASS — toate cele 14 stories PASS, 0 FAIL. Regresie completa 892 passed / 1 skipped / 0 failed (+49 teste fata de baseline 843; skipped = test live RAR, opt-in). Backend trimitere (worker/masina stari/idempotenta/mapping) + schema-send NEATINSE (Non-Goals respectate).
PASS per story cu dovezi verificate direct in cod:
- US-001 PASS —
_iso_date_prefixfolosit ATAT pentru garda CAT si comparatie (routes.py), interval inclusiv, ne-ISO excluse. - US-002 PASS — chei distincte
op_service_cod/op_service_denumire, conventie goala""(nu—). - US-003 PASS — pill-uri
<button>cuaria-pressed; needs_mapping--warn, needs_data/error--err; dropdown status eliminat (fara desync); lista ID-uri eliminata. - US-004 PASS — total ramificat (SQL COUNT fara filtru Python / fetch-all+slice cu filtru); clamp page; poll pastreaza pagina (OOB
f-page); pager ascuns lapages<=1. - US-005 PASS — VIN element block-level (
<div>) sub nr, garda VIN lipsa. - US-006 PASS — logica noua in
post_corectie_trimitere(citestecod_prestatie, valideaza nomenclator, injecteaza incontent["prestatii"][0]inainte deresolve_prestatii); cheia idempotency difera; read-only pe sent/sending/queued. - US-007 PASS — Operatie service distinct de Operatie RAR, absent cand lipseste, in editabil + read-only.
- US-008 PASS — card 3n in
{% if not editabil %}; erori fara camp = rezumat top-of-form in editare. - US-009 PASS — Mapari in
#cont-menucu badge; tab-bar +role=tablist+ JS sageti eliminate;?tab=maparinu da 404. - US-010 PASS — ordine sectiuni corecta;
ajutor-mapari+ empty-text eliminate. - US-011 PASS — butoane
.icon-btnSVG (fara emoji); kebab eliminat; dirty-state pechangeselect;hx-confirmpastrat. - US-012 PASS — header grid 3col centrat; env badge in grila; wordmark
by ROMFAST(ROM#D1342F, FAST#2E74D6) text, nu PNG. - US-013 PASS — accent
#2E74D6/#1F66C9/#0E7C7B;@font-faceIBM Plex Sans/Mono. Rezerva fonturi woff2 placeholder REZOLVATA post-VERIFY: cele 8 fisiere au fost inlocuite cu subseturile reale IBM Plex (fontsource@fontsource/ibm-plex-*@5.0.8, woff2 valide latin + latin-ext, 400/500/700 sans + 400 mono). - US-014 PASS —
[data-theme=petrol]#0E7C7B; ciclu Light->Dark->Petrol->Auto; anti-FOUC extins la 4 stari + fallback legacy;aria-labelcurenta+urmatoarea +aria-live.
Limitari documentate:
- E2E browser NEPROBAT in sandbox (fara browser interactiv, consistent cu 5.8/5.9). Recomandat la deploy:
./start.sh test both --send+ browser pehttp://localhost:8000/pentru proba vizuala (pill-uri/click, paginare >25, VIN sub nr, ciclu tema fara blink, header centrat, Mapari din ☰). - Live RAR
--sendneprobat (UI pur; backend trimitere neatins — risc minim).
CLOSE — /code-review high (2026-06-25)
1 finding material reparat TDD (US-006b): US-006 acoperea doar needs_data/needs_mapping, dar AC + intrebarea deschisa cer si error. Fix: constanta separata _EDITABILE_OP=(needs_data,needs_mapping,error) pentru selectul de cod RAR; pe error editarea codului trece prin /repune (re-queue), cu validare nomenclator, re-rezolvare, canonicalize + build_key (cheie idempotency noua), check coliziune (pre-UPDATE + IntegrityError), error→queued (rar_error NULL / retry_count 0). _CORECTABILE + post_corectie_trimitere neatinse (fara regresie US-010/US-011). 9/9 teste editare; regresie completa 896 passed / 1 skipped / 0 failed.
Findings minore (debt acceptat, non-blocante): (a) comparatie data bruta d_prefix > data_pana — sigura prin UI (<input type=date> zero-padded), vulnerabila doar la URL fabricat; (b) dublu load_nomenclator cand nomenclatorul e legitim gol; (c) linkurile de paginare reafiseaza vehicul cu majuscule (cosmetic); (d) duplicare cale validare+inject+idempotency intre post_corectie_trimitere si post_repune_trimitere (candidat de extras intr-un helper).
US-012b (decizie user post-review): in header se foloseste LOGO-ul PNG real (/static/romfast_logo.png, .brand-logo ~28px) in loc de wordmark-ul text din US-012. Fundal transparent + culori proprii -> lizibil pe toate temele. test_web_header_branding.py actualizat sa verifice <img romfast_logo.png> + alt. DESIGN.md actualizat. Regresie ramane 896 passed.
Stare finala: VERIFY PASS + fix code-review (US-006b) + logo header (US-012b) aplicate. Gata de commit (poarta umana).