Files
rar-autopass/scripts/ralph/prd.json
Claude Agent 6d10f92452 feat(5.9): US-001 - eticheta umana scurta pe rand + cod brut pentru modal (R1)
- _submission_row_view expune eticheta_problema (motiv || eticheta_scurta), gol pe queued/sending/sent, fara decoder nou (R1 DRY)
- parse_erori expune cheia `cod` (cod brut catalog) pe ramurile imbogatite, pentru derivare in modal
- 5 teste US-001 in tests/test_web_submissions.py
- gates: tests PASS (819), /review (backend) PASS

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 21:17:53 +00:00

233 lines
19 KiB
JSON

{
"projectName": "autopass-5.9-ux-corectie-modal-mobil",
"branchName": "ralph/5.9-ux-corectie-modal-mobil",
"description": "Curatenie UX pe ecranul Trimiteri, fara schimbare a fluxului de trimitere catre RAR: detaliul+corectia trec intr-un MODAL global (in afara zonei de poll de 15s), editare in-place fara dublarea valorilor, butoane consolidate, fara chevron, eticheta umana scurta sub pill + cod brut in modal, cod RAR simplu. Plus design responsive/mobil (<768px) pentru toate paginile dashboard. Backend (worker, masina de stari, contract RAR, idempotenta, mapare) NEATINS logic; rutele isi pastreaza semnatura, se schimba doar hx-target si prezentarea. APLICA REVIZIILE OBLIGATORII R1-R12 din raportul AUTOPLAN (au prioritate unde difera de AC original). Sursa: docs/prd/prd-5.9-ux-corectie-modal-mobil.md.",
"techStack": {
"type": "python",
"commands": {
"start": "uvicorn app.main:app --reload --port 8010",
"build": "",
"lint": "",
"typecheck": "",
"test": "python3 -m pytest -q -m 'not live'"
},
"port": 8010
},
"userStories": [
{
"id": "US-001",
"title": "Eticheta umana scurta pe randul de afisare + cod brut disponibil pentru modal (R1)",
"description": "Ca dezvoltator vreau ca randul tabelului sa expuna o eticheta umana scurta a problemei (NU codul brut de catalog) pentru ca sub pill sa apara un text prietenos, consistent cu investitia 5.4 in erori umane.",
"priority": 10,
"acceptanceCriteria": [
"R1 OVERRIDE: NU se adauga helper nou `cod_eroare_din` care re-parseaza acelasi JSON (DRY). Se reutilizeaza `motiv_uman`/`eticheta_scurta` din `app/web/labels.py` deja existente.",
"`_submission_row_view` (`app/web/routes.py`) expune o eticheta umana scurta pe rand reutilizand `motiv` (deja intors nerandat) / `eticheta_scurta` — fara al 3-lea decoder al `rar_error`.",
"Eticheta este sir gol pe stari fara problema (`queued/sending/sent`) si ne-goala pe `error`/`needs_data`/`needs_mapping`.",
"Codul BRUT de catalog (ex. `RAR_EROARE_SERVER`, `COD_NEMAPAT`) ramane disponibil DOAR pentru modal, derivat din `parse_erori(...)[0]['cod']` (app/errors.py) — nu se pune pe rand.",
"Helperele sunt defensive la `rar_error` lipsa/JSON invalid (intorc sir gol, nu ridica).",
"`prez`, `motiv`, `stare_*` raman neschimbate (aditiv/non-regresie).",
"Teste in `tests/test_web_submissions.py`: `test_eticheta_umana_sub_pill` (R1 rename), eticheta prezenta pe error/needs_mapping, goala pe rand ok.",
"`python3 -m pytest tests/test_web_submissions.py -q` trece."
],
"tags": ["backend"],
"dependsOn": [],
"requiresBrowserCheck": false,
"requiresDesignReview": false,
"passes": true,
"failed": false,
"blocked": false,
"retries": 0,
"failureReason": "",
"notes": "Atins: app/web/routes.py (_eticheta_problema + _STARI_CU_PROBLEMA, camp eticheta_problema in _submission_row_view, motiv hoisted), app/web/labels.py (parse_erori expune `cod` brut aditiv pe ramurile imbogatite), tests/test_web_submissions.py (5 teste US-001). gates: tests PASS (819 suite, 22 fisier), /review (backend) PASS (no issues — DRY respectat, defensiv, aditiv non-regresie)."
},
{
"id": "US-003",
"title": "Component modal reutilizabil (overlay, focus-trap, a11y, inchidere pe succes)",
"description": "Ca operator vreau ca detaliul sa apara intr-o fereastra modala clar delimitata, in afara zonei de poll, pentru ca sa nu mai confund marginea cu tabelul si sa nu mi se mai inchida formularul la refresh.",
"priority": 15,
"acceptanceCriteria": [
"Container modal global `<div id=\"modal-detaliu\" role=\"dialog\" aria-modal=\"true\" hidden>` cu backdrop, plasat IN AFARA `#submissions-wrap` (poll-ul de 15s nu-l atinge). Corpul `#detaliu-modal-body` e tinta de swap pentru fragment + rutele corectie/mapare/lifecycle.",
"Ancora globala in `_coada.html` in afara `#submissions-wrap`; `#trimitere-detaliu` global inert vechi din `_coada.html` este ELIMINAT (rol preluat de `#modal-detaliu`).",
"Deschidere la `htmx:beforeRequest` de pe `tr.trimitere-row`: backdrop+dialog vizibile cu placeholder `Se incarca...` (`hx-indicator`), focus mutat in modal. Un singur modal o data.",
"Inchidere prin buton `x`, `Esc`, click pe backdrop: `hidden`, corp golit, focus readus pe randul declansator.",
"R7: focus-trap (Tab/Shift+Tab cicleaza in modal); `inert` SI `aria-hidden` pe ancestor STABIL `<main>` (NU pe `#submissions-wrap` care e swap target); scroll body blocat cat modalul e deschis; dialog cu `aria-labelledby` pe heading; buton `x` cu `aria-label`.",
"R3: tot codul inline-expand din 5.8 (chevron, `tr.detaliu-rand`, `marcheazaDetaliuDeschis`, `inchideDetaliu` pe rand, toggle single-open) ELIMINAT/inlocuit de logica modalului; `window.inchideDetaliu(id)` ramane expus dar inchide modalul.",
"R3 (EngF1): adauga in scope `tests/test_web_detaliu_inline.py` (rescrie/sterge) si `tests/test_acasa_trimiteri.py:71` (scoate assert `#trimitere-detaliu`). Niciun template/test nu mai refera `tr.detaliu-rand`/`#trimitere-detaliu`/`marcheazaDetaliuDeschis`/`aria-expanded` pe rand.",
"R5: pe succes corectie SI sterge, modalul se inchide + listener pe `HX-Trigger: trimiteriChanged` reincarca lista; pe load-error al fragmentului stare `Nu s-a putut incarca [Reincearca]/[Inchide]` (HTMX `responseError`), nu placeholder blocat.",
"Teste in `tests/test_web_modal.py`: `test_modal_container_in_afara_submissions_wrap`, `test_fragment_detaliu_tinteste_modalul`.",
"`python3 -m pytest tests/test_web_modal.py -q` trece."
],
"tags": ["ui"],
"dependsOn": [],
"requiresBrowserCheck": true,
"requiresDesignReview": true,
"passes": false,
"failed": false,
"blocked": false,
"retries": 0,
"failureReason": "",
"notes": ""
},
{
"id": "US-006",
"title": "Fundatie responsive — viewport, header, nav, modal full-screen mobil, breakpoint-uri",
"description": "Ca utilizator pe telefon vreau ca shell-ul aplicatiei sa fie utilizabil pentru ca azi header-ul, tab-bar-ul si modalul nou nu au tratament sub 768px.",
"priority": 20,
"acceptanceCriteria": [
"`<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">` prezent (confirmat/adaugat) in `base.html`.",
"Modal full-screen pe mobil: sub 768px modalul ocupa tot ecranul (fara backdrop lateral), buton `x` mare (>=44px), scroll intern; pe desktop ramane centrat cu latime marginita.",
"Header + nav: meniul de cont (un buton hamburger) si tab-bar-ul nu depasesc latimea pe 375px; tinte touch >=44px; fara scroll orizontal pe pagina.",
"Breakpoint-uri consistente definite o data (ex. `--bp-mobil: 768px`) si reutilizate; fara regresie pe desktop (>=1024px arata identic).",
"Teste in `tests/test_web_responsive.py`: `test_viewport_meta_prezent`, `test_modal_fullscreen_clasa_mobil`, `test_nav_colapsabil_sub_breakpoint`.",
"`python3 -m pytest tests/test_web_responsive.py -q` trece."
],
"tags": ["ui"],
"dependsOn": ["US-003"],
"requiresBrowserCheck": true,
"requiresDesignReview": true,
"passes": false,
"failed": false,
"blocked": false,
"retries": 0,
"failureReason": "",
"notes": ""
},
{
"id": "US-002",
"title": "Tabel trimiteri — eticheta umana sub stare, fara chevron, cod RAR simplu, rand declanseaza modalul",
"description": "Ca operator vreau sa vad problema direct in tabel si un rand mai curat pentru ca sa stiu dintr-o privire ce rand are nevoie de mine.",
"priority": 30,
"acceptanceCriteria": [
"R1 OVERRIDE: sub pill-ul de Stare apare ETICHETA UMANA scurta (din US-001, NU codul brut), text mic muted (`s-error` pe `error`/`needs_*`), DOAR cand e ne-goala. Stare transmisa prin text, nu doar culoare.",
"R8: chevron ELIMINAT din coloana `#` (`_submissions.html:52-53`) si din CSS/JS asociat; regula touch `min-height/padding >=44px` pe rand INLOCUIESTE `@media pointer:coarse .chevron` (singura regula 44px); scoate logica toggle (re-click inchide) din `htmx:beforeRequest`.",
"Coloana Operatie linia 2: doar codul RAR (ex. `OE-2`), FARA prefixul `cod RAR:` — un `<span>` muted/pill; cand nemapat afiseaza `nemapat` muted (comportament 5.8 pastrat).",
"Randul `tr.trimitere-row` declanseaza deschiderea modalului (US-003): `hx-get /_fragments/trimitere/{id}` cu `hx-target` pe corpul modalului, NU pe rand-sibling. Randul-sibling `<tr class=\"detaliu-rand\">` din 5.8 este ELIMINAT.",
"R8: rand `role=\"button\"`, `tabindex=\"0\"`, `aria-haspopup=\"dialog\"`, FARA `aria-expanded`; focusabil; Enter/Space deschid modalul; hover/focus pastrate ca afordanta; tinta touch >=44px.",
"Teste in `tests/test_web_submissions.py`: `test_eticheta_umana_apare_sub_pill`, `test_fara_chevron_in_rand`, `test_cod_rar_fara_prefix_text`, `test_rand_deschide_modal`, plus test keyboard Enter/Space deschide modal + Esc readuce focus.",
"`python3 -m pytest tests/test_web_submissions.py -q` trece.",
"E2E (requiresBrowserCheck): gstack browser pe `/` — rand `error` arata eticheta umana sub pill; fara chevron; cod RAR fara `cod RAR:`; click pe rand deschide modalul (nu rand-sibling)."
],
"tags": ["ui"],
"dependsOn": ["US-001", "US-003"],
"requiresBrowserCheck": true,
"requiresDesignReview": false,
"passes": false,
"failed": false,
"blocked": false,
"retries": 0,
"failureReason": "",
"notes": ""
},
{
"id": "US-004",
"title": "Detaliu editabil in-place + butoane consolidate (in modal), ordine verticala definita",
"description": "Ca operator vreau sa corectez datele direct, o singura aparitie per camp, cu un buton clar pentru ca sa nu mai vad valorile de doua ori si sa nu mai ezit intre butoane.",
"priority": 35,
"acceptanceCriteria": [
"Zero dublare: blocul read-only de grila (`_trimitere_detaliu.html:19-32`) si formularul separat de corectie se contopesc intr-un singur formular cu campuri editabile pre-completate. Campurile editabile (`nr_inmatriculare`, `vin`, `data_prestatie`, `odometru_final`, `odometru_initial`) apar O SINGURA DATA.",
"Layout: `Numar inmatriculare` pe propriul rand (latime plina), `VIN (serie sasiu)` pe randul de DEDESUBT (latime plina). Restul campurilor pot ramane in grila.",
"R10 ordine verticala modal: (1) header `#id`+pill+motiv uman; (2) bloc eroare blocanta cand exista; (3) mapare inline cand `nemapate_inline`; (4) formular editabil (Nr. rand propriu, VIN dedesubt, apoi grila), cu operatie+cod read-only deasupra campurilor; (5) actiuni (primar, apoi Sterge separat); (6) `<details>` `Detalii tehnice` colapsat (Nr. prezentare RAR, Cod HTTP, Reincercari, timestamps, mesaj RAR brut + cod brut). Context read-only NU precede formularul.",
"Cod RAR simplu: nu mai exista campul etichetat `Cod RAR` separat (`:23`). R9: operatie+cod folosesc `prez.cod_rar` (fallback `nemapat`), NU `prez.cod`; afisate impreuna ca read-only (ex. `Operatie: Revizie · OE-2`), fara eticheta `Cod RAR`.",
"R2 OVERRIDE (buton primar conditionat de stare): `error` -> buton `Re-pune in coada` care posteaza pe `/trimitere/{id}/repune` (neschimbat); `needs_data`/`needs_mapping` -> buton `Salveaza si retrimite` care posteaza pe `/trimitere/{id}/corecteaza` (neschimbat). UN SINGUR buton primar per stare (clasa primara `--accent` umplut). Butonul `Re-pune in coada` gol vechi (`:55-59`) eliminat.",
"R11: UN SINGUR `Sterge` (outline distructiv, `var(--err)`/rosu) pe RAND SEPARAT (NU `margin-left:auto`), gap vizibil, full-width stivuit pe mobil; `hx-confirm` specific (`Stergi definitiv trimiterea #{id}? Nu se poate anula.`); posteaza pe `/trimitere/{id}/sterge` (neschimbat).",
"R9: sectiunea de mapare inline (5.7, `:71-116`) ramane functional identica, mutata in modal, tintind corpul modalului; cand `nemapate_inline`: linia `Operatie: X · nemapat` urmata de picker; dupa mapare re-render arata codul rezolvat.",
"R4: rescrie `<script>`-ul inline din `_trimitere_detaliu.html:161-176` pentru modal — FARA `marcheazaDetaliuDeschis`, FARA `scrollIntoView` pe randul de fundal; muta focusul in corpul modalului.",
"R5: disable-on-submit pe toate cele 3 forme (corectie/sterge/mapare); pe succes (queued sau sterge) modalul se inchide + `trimiteriChanged`.",
"Erorile de camp (`err_map`, `aria-invalid`, mesaj inline) si flash-ul de corectie raman. Comportamentul rutelor `/corecteaza` si `/repune` (validare, idempotency, re-rezolvare) NESCHIMBAT — test de regresie confirma acelasi rezultat ca azi.",
"Teste in `tests/test_web_corectie.py`: `test_camp_apare_o_singura_data`, `test_nr_si_vin_pe_randuri_separate`, `test_un_singur_buton_primar_per_stare` (R2), `test_error_foloseste_repune` (R2), `test_sterge_prezent_si_distinct`, `test_corectie_pastreaza_comportament` (regresie).",
"`python3 -m pytest tests/test_web_corectie.py -q` trece.",
"E2E (requiresBrowserCheck): gstack browser — `needs_data` arata fiecare camp o data (nr. pe un rand, VIN dedesubt), corectez data, `Salveaza si retrimite` -> rand `queued`; `error` arata `Re-pune in coada`; `Sterge` clar separat si rosu."
],
"tags": ["ui"],
"dependsOn": ["US-003"],
"requiresBrowserCheck": true,
"requiresDesignReview": true,
"passes": false,
"failed": false,
"blocked": false,
"retries": 0,
"failureReason": "",
"notes": ""
},
{
"id": "US-007",
"title": "Responsive — paginile de continut (Mapari, Cont, Nomenclator, Integrare, Jurnal, Admin)",
"description": "Ca operator pe telefon vreau ca paginile de lucru sa fie lizibile pentru ca tabelele si formularele lor ies azi din ecran sub 768px.",
"priority": 40,
"acceptanceCriteria": [
"R12 politica per-tabel: tabele actionabile (Mapari) = CARD per rand sub 768px (adauga `data-eticheta` la `<td>`-uri, model 5.8 `.tabel-trimiteri`); dense read-only (Jurnal, Nomenclator) = `.tablewrap` cu scroll orizontal CONTAINED (nu deborda pagina).",
"R12: scopare `.tablewrap` sa NU atinga blocul `.tabel-trimiteri @media(max-width:767px)` din 5.8; test ca acele carduri 5.8 supravietuiesc.",
"Formularele (Cont, Mapari add-row, filtre, Integrare) stiveaza campurile pe o coloana sub 768px, inputuri full-width, butoane >=44px.",
"La 375px niciuna din pagini nu produce scroll orizontal de pagina (`scrollWidth <= clientWidth` pe `body`).",
"Fara regresie la >=1024px (layout actual pastrat).",
"Fisiere: `app/web/templates/_mapari.html`, `_cont.html`, `_nomenclator.html`, `_integrare.html`, `_jurnal.html`, `admin.html`, `base.html` (utilitar `.tablewrap`/card).",
"Teste in `tests/test_web_responsive.py`: `test_tabele_continut_au_clasa_responsive`, `test_formulare_full_width_mobil`.",
"`python3 -m pytest tests/test_web_responsive.py -q` trece.",
"E2E (requiresBrowserCheck): gstack browser la 375px pe `/?tab=mapari`, `?tab=jurnal`, Cont/Nomenclator/Integrare si `/admin` — fiecare fara scroll orizontal, tabele lizibile, formulare pe o coloana."
],
"tags": ["ui"],
"dependsOn": ["US-006"],
"requiresBrowserCheck": true,
"requiresDesignReview": false,
"passes": false,
"failed": false,
"blocked": false,
"retries": 0,
"failureReason": "",
"notes": ""
},
{
"id": "US-008",
"title": "Responsive — Acasa (upload, status, filtre) + login/signup",
"description": "Ca utilizator pe telefon vreau ca prima pagina si autentificarea sa fie utilizabile pentru ca sunt punctele de intrare si azi nu au tratament mobil complet.",
"priority": 45,
"acceptanceCriteria": [
"Zona de upload, bara de status si bara de filtre (`_coada.html:25-59`) stiveaza pe o coloana sub 768px; inputuri/butoane full-width >=44px.",
"Tabelul de trimiteri pastreaza cardurile din 5.8 (verificat, nereparat daca deja ok).",
"`login.html` si `signup.html`: card centrat care nu depaseste latimea pe 375px, inputuri full-width, fara scroll orizontal.",
"Fara regresie la >=1024px.",
"Fisiere: `app/web/templates/_acasa.html`, `_upload.html`, `_status.html`, `_coada.html` (filtre), `login.html`, `signup.html`.",
"Teste in `tests/test_web_responsive.py`: `test_acasa_fara_scroll_orizontal_mobil`, `test_login_signup_full_width_mobil`.",
"`python3 -m pytest tests/test_web_responsive.py -q` trece.",
"E2E (requiresBrowserCheck): gstack browser la 375px pe `/`, `/login`, `/signup` — fara scroll orizontal; upload + filtre pe o coloana; carduri de trimiteri intacte."
],
"tags": ["ui"],
"dependsOn": ["US-006"],
"requiresBrowserCheck": true,
"requiresDesignReview": false,
"passes": false,
"failed": false,
"blocked": false,
"retries": 0,
"failureReason": "",
"notes": ""
},
{
"id": "US-005",
"title": "Poll-ul nu mai inchide modalul si nu mai sterge bifele",
"description": "Ca operator vreau ca selectia si modalul sa supravietuiasca refresh-ului de 15s pentru ca azi formularul se inchide singur si bifele se sterg dupa cateva secunde.",
"priority": 50,
"acceptanceCriteria": [
"Modalul traieste in afara `#submissions-wrap` -> un swap de poll NU il mai atinge (rezolvat structural de US-003). Test: cu modalul deschis, un refresh al listei nu-l inchide.",
"R6 OVERRIDE: poll-ul de 15s (`#submissions-wrap`, `_coada.html:62-65`) se pune pe pauza SCOPAT DOAR pe trigger periodic `every 15s` cat timp: (a) modalul e deschis SAU (b) exista cel putin un checkbox de bulk bifat. `trimiteriChanged` si submit-ul de filtru TREC MEREU (nu se blocheaza). Resume pe checkbox `change`->gol via delegare pe body.",
"R6: evita blocajul permanent — daca randul bifat paraseste filtrul, pauza nu ramane lipita (pauza doar pe trigger periodic).",
"La pauza nu se pierde nici scroll, nici focus, nici selectia de checkbox-uri.",
"Logica veche de pauza pe `rand expandat` (5.8, `base.html:478-483`) inlocuita de aceasta.",
"Teste in `tests/test_web_modal.py`: `test_poll_pauzat_cat_modal_deschis`, `test_poll_pauzat_cat_exista_bifa`, `test_trimiteriChanged_inca_reincarca_cu_bifa` (R6).",
"`python3 -m pytest tests/test_web_modal.py -q` trece.",
"E2E (requiresBrowserCheck): gstack browser — bifez 2 trimiteri, astept >15s: bifele raman; deschid modalul, astept >15s: modalul ramane deschis cu datele intacte."
],
"tags": ["ui"],
"dependsOn": ["US-002", "US-003"],
"requiresBrowserCheck": true,
"requiresDesignReview": false,
"passes": false,
"failed": false,
"blocked": false,
"retries": 0,
"failureReason": "",
"notes": ""
}
]
}