diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index 5961efd..373bfa3 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -78,6 +78,7 @@ Reguli de contract (detalii in `docs/api-rar-contract.md`): `FINALIZATA` e termi | 3.2 | Filtrare pe cont a GET-urilor de listare | DONE | 2026-06-17 | scope cheie pe `/v1/prezentari(/{id})`, `/v1/mapari(/pending)`, `/v1/audit/export` (NULL→cont 1); nomenclator global; 404 cross-account identic (B3) + allowlist campuri detaliu (B4) + helper `account_scope_clause` (B2) + index (B5). 14 teste noi, 313 pass. PRD: [prd-3.2](prd/prd-3.2-filtrare-cont-get.md) | | 3.3a | Self-onboarding web (core) | DONE | 2026-06-17 | `users` (scrypt) + sesiune (`SessionMiddleware`, same_site=strict) + CSRF (enforce prod, inclusiv login/signup) + rate-limit signup/login + signup/login/logout + dashboard & import scoped pe sesiune (NULL→1, anti-leak C6) + gate worker `active=0` (`COALESCE`). 2 runde VERIFY (leak `/_fragments/mapari` prins+reparat) + code-review (csrf erori, scrypt_params, login rate-limit). 361 teste. PRD: [prd-3.3](prd/prd-3.3-self-onboarding-web.md) | | 3.3b | Self-service cheie/creds + admin web + email | DONE | 2026-06-18 | US-007 (rute web proprii `/cont/roteste-cheie`+`/cont/rar-creds` scoped sesiune, C13), US-010 (rol admin `is_admin` + `require_admin`→403 + CLI `set-admin` + bootstrap primul cont=admin), US-011 (`/admin` activare/dezactivare cu CSRF+PRG, link doar pt admini + logout), US-012 (`app/email.py` notify best-effort degradat fara SMTP + log `SIGNUP`). Fix migrare defensiva `users.is_admin`/`email_verified`. 2 runde VERIFY context curat (r1 a prins migrarea lipsa, reparat; r2 PASS) + `/code-review` high (TOCTOU bootstrap admin mutat in tranzactie + extras `_render_admin` anti-duplicare/N+1). 393 teste. PRD: [prd-3.3](prd/prd-3.3-self-onboarding-web.md) | +| 3.4 | Interfata web ergonomica (tab-uri + wizard + microcopy uman) | TODO | | Reorganizare dashboard: tab-uri sus (Acasa/Import/Mapari/Cont/Nomenclator), import ca stepper 4 pasi, ghid de pornire auto-bifat, etichete umane (`labels.py`) in loc de "worker viu". Doar stratul de prezentare (Jinja2+HTMX), fara backend de trimitere. PRD: [prd-3.4](prd/prd-3.4-ux-dashboard-web.md) | ### Etapa 4 — Viitor (Treapta 3) diff --git a/docs/prd/prd-3.4-ux-dashboard-web.md b/docs/prd/prd-3.4-ux-dashboard-web.md new file mode 100644 index 0000000..47a393c --- /dev/null +++ b/docs/prd/prd-3.4-ux-dashboard-web.md @@ -0,0 +1,273 @@ +# PRD 3.4 — Interfata web ergonomica (tab-uri + wizard + microcopy uman) + +**Stare**: aprobat + +> 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). +> Aceasta e o livrabila de **UI/UX** — atinge interfata web (Jinja2 + HTMX, zero build), nu logica de +> trimitere catre RAR. Nu modifica `worker/`, `mapping.py`, `idempotency.py`, masina de stari. + +## 1. Obiectiv + +Dashboard-ul curent (`app/web/templates/dashboard.html`) ingramadeste toate sectiunile intr-un +singur scroll lung: import, status, mapari, cont, coada, nomenclator. Utilizatorul nou nu stie ce +pas urmeaza, iar etichetele sunt scrise pentru dezvoltator ("Worker viu", "RAR ok"), nu pentru +operatorul de service. + +Livrabila reorganizeaza interfata pe **trei principii**, fara a schimba comportamentul backend: +1. **Tab-uri sus** — un singur panou activ la un moment dat (Acasa, Import, Mapari, Cont, Nomenclator); + restul incarcate lazy prin HTMX. Bara de status ramane mereu vizibila, indiferent de tab. +2. **Flux tip wizard** — pasii sunt numerotati si auto-explicativi; utilizatorul nu ghiceste ce + are de facut. Importul devine un stepper explicit (Incarca → Mapeaza → Verifica → Confirma), iar + pagina "Acasa" arata un ghid de pornire care bifeaza singur ce e deja configurat. +3. **Microcopy uman** — etichete scrise pentru oameni, nu pentru cod: "Trimitere automata catre RAR: + activa" in loc de "Worker viu". Vezi tabelul din §3 (US-001). + +Decizie de layout confirmata cu utilizatorul (AskUserQuestion): **tab-uri sus**, cu cerinta explicita +de "pagini ca un wizard, intuitive" si "caption-uri utile, relevante, simple, pentru oameni". + +## 2. Non-Goals (anti scope-creep) + +- **Fara schimbari de backend de trimitere**: worker, mapare op→cod, idempotenta, reconciliere, + masina de stari submissions raman neatinse. Doar stratul de prezentare web. +- **Fara framework JS / build step**: ramane Jinja2 + HTMX + CSS in `base.html`. Fara React/Vue/Tailwind, + fara bundler. Eventualul JS e vanilla inline, minim (deja exista pattern: clipboard in `_cont.html`). +- **Fara endpoint-uri API noi `/v1/*`** si fara schimbari de schema SQL. Tab-urile folosesc fragmentele + HTMX existente (`/_fragments/*`) plus, la nevoie, fragmente de prezentare noi sub `/_fragments/`. +- **Fara rescriere a fluxului de import** (parsare, mapare coloane, preview, commit raman ca logica) — + doar se imbraca intr-un stepper vizual. +- **Fara redesign al paginilor `login.html` / `signup.html` / `admin.html`** dincolo de aplicarea + acelorasi etichete/clase daca e trivial. Focusul e dashboard-ul autentificat. +- **Fara i18n / multi-limba**: textele raman in romana, hardcodate (ca tot proiectul). +- **Fara tema light / toggle de tema**: pastram paleta dark din `base.html`. + +## 3. Stories atomice + +> Fiecare story: cea mai mica unitate care lasa sistemul functional. Backend + UI pentru acelasi +> comportament = 2 stories. Toate atingerile sunt in `app/web/` (templates + routes + un modul de +> etichete). Verificare E2E = browser HTMX pe `http://localhost:8000/` (Playwright MCP sau `/browse`). +> **Regula de aur**: fluxul import → commit → worker → FINALIZATA la RAR test NU are voie sa se strice. + +### US-001: Microcopy uman pentru status si stari (modul de etichete + teste) +**Ca** operator de service **vreau** etichete pe care le inteleg fara sa fiu programator +**pentru ca** sa stiu dintr-o privire ce face sistemul si ce am eu de facut. + +- **Depinde de**: — +- **Fisiere**: `app/web/labels.py` (nou), `tests/test_web_labels.py` (nou) (~2 fisiere) +- **Test intai (RED)**: `tests/test_web_labels.py` — `test_eticheta_worker_viu`, `test_eticheta_worker_mort`, + `test_eticheta_stare_submission`, `test_toate_starile_au_eticheta` (nicio stare din `schema.sql` fara mapare). +- **Continut**: un singur loc (`labels.py`) care traduce starile tehnice in text uman + clasa CSS de + culoare. Tabelul de adevar (caption-uri propuse, de finalizat cu utilizatorul daca difera): + + | Tehnic (azi) | Eticheta umana propusa | Sub-text / tooltip | + |---|---|---| + | Worker `viu` | **Trimitere automata: activa** | Sistemul verifica coada si trimite la RAR la fiecare cateva secunde. | + | Worker `mort` | **Trimitere automata: oprita** | Nimic nu pleaca spre RAR pana reporneste. Anunta administratorul. | + | RAR `ok` | **Legatura cu RAR: functionala** | Portalul AUTOPASS raspunde. | + | RAR `indisponibil` | **Legatura cu RAR: indisponibila** | Portalul RAR nu raspunde acum; coada se reia automat cand revine. | + | `Ultimul login RAR` | **Ultima autentificare la RAR** | — | + | `In coada` / `queued` | **In asteptare sa fie trimise** | — | + | `Trimise` / `sent` | **Declarate la RAR (finalizate)** | Confirmate cu numar de prezentare; nu se mai pot modifica. | + | `Blocate` | **Necesita atentia ta** | Defalcare pe motiv (lipsa cod / date incomplete / eroare). | + | `sending` | **Se trimite acum** | — | + | `needs_mapping` | **Lipseste codul prestatiei** | Alege codul RAR in tab-ul Mapari. | + | `needs_data` | **Date incomplete (respinse de RAR)** | Corecteaza randul si reimporta. | + | `error` | **Eroare la trimitere** | Vezi detaliul randului; se reincearca automat sau necesita corectie. | + +- **Acceptance criteria**: + - [ ] `labels.py` expune o functie/dict care, pentru fiecare stare din `schema.sql`, da `(text, css_class)`. + - [ ] Nicio stare de submission existenta nu ramane fara eticheta (test parametrizat care iese rosu daca se adauga o stare noua nemapata). + - [ ] Functiile sunt pure (fara DB, fara request) — usor de testat unitar. +- **Verificare E2E**: indirect, prin US-002/US-003 (etichetele apar in UI). + +### US-002: Bara de status persistenta cu etichete umane (fragment) +**Ca** operator **vreau** sa vad mereu, sus, starea sistemului in cuvinte clare +**pentru ca** sa am incredere ca trimiterile chiar pleaca, fara sa stiu ce e un "worker". + +- **Depinde de**: US-001 +- **Fisiere**: `app/web/templates/_status.html` (nou), `app/web/routes.py` (endpoint `/_fragments/status`), + `tests/test_web_status_fragment.py` (nou) (~3 fisiere) +- **Test intai (RED)**: `tests/test_web_status_fragment.py` — `test_status_fragment_text_uman` + (contine "Trimitere automata", nu "worker viu"), `test_status_blocate_defalcare`, + `test_status_se_reincarca_htmx` (are `hx-trigger` periodic). +- **Continut**: extrage blocul de status din `dashboard.html` intr-un fragment dedicat care foloseste + `labels.py`. Ramane sticky/vizibil sus indiferent de tab-ul activ. Defalca "Necesita atentia ta" + pe motive. Pastreaza poll-ul HTMX (`every 15s`) deja existent pentru banner. +- **Acceptance criteria**: + - [ ] `/_fragments/status` randeaza bara cu etichetele din US-001 (scoped pe cont, ca restul UI). + - [ ] Bara ramane vizibila sus cand se schimba tab-ul (nu e inghitita de panoul activ). + - [ ] Cand exista submissions blocate, bara arata defalcarea pe motiv, nu doar un numar. +- **Verificare E2E**: browser — incarca `/`, bara de status arata text uman; opreste workerul → + "Trimitere automata: oprita". + +### US-003: Navigare cu tab-uri (shell dashboard) +**Ca** operator **vreau** sectiuni separate pe tab-uri, nu un scroll lung +**pentru ca** sa gasesc rapid ce caut fara sa fiu coplesit. + +- **Depinde de**: US-002 +- **Fisiere**: `app/web/templates/dashboard.html` (restructurare), `app/web/templates/_tabs.html` (nou, + optional), `app/web/routes.py` (ruta `/` + suport deep-link tab), `tests/test_web_tabs.py` (nou) (~4 fisiere) +- **Test intai (RED)**: `tests/test_web_tabs.py` — `test_dashboard_are_tabbar`, + `test_tab_implicit_acasa`, `test_deeplink_tab_import` (`/?tab=import` randeaza panoul Import + server-side la full load), `test_tab_activ_randat_server_side` (panoul activ e in HTML-ul initial, + nu doar cerut prin HTMX dupa load), `test_fragmentele_inactive_lazy` (panourile inactive nu se cer + la load), `test_tabbar_aria` (atribute `role=tablist`/`tab`/`tabpanel` + `aria-selected` prezente). +- **Continut**: bara de tab-uri sub header: **Acasa · Import · Coada · Mapari · Cont · Nomenclator** + (+ "Panou admin" / "Iesi" pastrate in coltul din dreapta sus). Panoul de submissions existent muta + din scroll in tab-ul **Coada**. Un singur panou activ; tab-ul isi incarca fragmentul prin HTMX la + activare (lazy), nu toate la load. **Randare server-side a panoului activ** la full load (din `?tab=`): + fara palpaire la refresh si degradare gratioasa daca JS lipseste — utilizatorul vede macar panoul + curent. Bara de status (US-002) sta deasupra tab-bar-ului, mereu vizibila. Tab activ marcat vizual. + **Accesibilitate reala**: `role="tablist"` pe bara, `role="tab"` + `aria-selected` pe fiecare tab, + `role="tabpanel"` pe panou, navigare cu sageti intre tab-uri (JS vanilla minim). Mobil: tab-bar se + ruleaza orizontal / se sparge curat (fara meniu hamburger — pastram simplu). +- **Acceptance criteria**: + - [ ] Tab-bar cu cele 6 tab-uri (Acasa · Import · Coada · Mapari · Cont · Nomenclator); "Acasa" implicit la prima incarcare. + - [ ] Un singur panou randat la un moment dat; celelalte fragmente NU se cer pana la activarea tab-ului. + - [ ] Panoul activ (inclusiv din `?tab=`) e randat **server-side** la full load — fara palpaire la refresh, vizibil si fara JS. + - [ ] Accesibilitate: `role=tablist/tab/tabpanel`, `aria-selected` pe tab-ul activ, navigare cu sageti (nu doar focus vizibil). + - [ ] Refresh pe un tab non-implicit revine pe acelasi tab (deep-link prin query string `?tab=`). + - [ ] Toate functiile existente raman accesibile dintr-un tab (nimic pierdut fata de pagina veche). +- **Verificare E2E**: browser — click pe fiecare tab incarca panoul corect; refresh pe `?tab=import` + ramane pe Import; navigare cu sageti intre tab-uri functioneaza (citior de ecran anunta tab-ul activ). + +### US-004: Importul ca wizard cu pasi numerotati (stepper) +**Ca** operator care incarca un fisier **vreau** sa vad clar in ce pas sunt si ce urmeaza +**pentru ca** sa nu ma blochez intrebandu-ma "si acum ce fac?". + +- **Depinde de**: US-003 +- **Fisiere**: `app/web/templates/_stepper.html` (nou, include partajat), + `app/web/templates/_upload.html`, `_mapcoloane.html`, `_preview_import.html` (adauga antet stepper), + `tests/test_web_import_stepper.py` (nou) (~5 fisiere) +- **Test intai (RED)**: `tests/test_web_import_stepper.py` — `test_stepper_pas1_la_upload`, + `test_stepper_pas2_la_mapare`, `test_stepper_pas3_la_preview`, `test_stepper_marcheaza_pasii_facuti`, + `test_import_hx_target_in_tab` (fragmentele de import au `hx-target` care se rezolva in panoul de tab, + nu pe vechiul container de pagina), `test_import_forms_pastreaza_csrf` (formularele mutate isi pastreaza + `csrf_token` valid). +- **Continut**: un antet de stepper reutilizabil cu 4 pasi vizibili in toate fragmentele de import: + **1. Incarca fisier → 2. Potriveste coloanele → 3. Verifica → 4. Confirma trimiterea**. Pasul curent + evidentiat, pasii facuti bifati, pasii viitori estompati. Fiecare ecran are un titlu-actiune si o + fraza de ajutor ("Trage un fisier xlsx/csv aici" / "Spune-ne ce coloana e ce" / "Verifica inainte sa + trimiti"). Stepper-ul e PUR vizual — nu schimba endpoint-urile sau ordinea logica existenta. + **Granita cu US-003 (risc tehnic principal)**: cand fragmentele de import se randeaza in tab-ul Import, + `hx-target`/`hx-swap` din upload→mapare→preview trebuie sa tinteasca un container din interiorul + panoului de tab (nu vechiul container de la radacina paginii), iar `csrf_token` din formularele de + import trebuie sa ramana corect. Verificat prin testele de mai sus + regula de aur. +- **Acceptance criteria**: + - [ ] Acelasi stepper apare in upload, mapare-coloane si preview, cu pasul corect evidentiat. + - [ ] Pasii deja parcursi sunt marcati ca facuti; cei viitori sunt estompati. + - [ ] Fiecare pas are un titlu-actiune + o fraza scurta de ajutor (microcopy din US-001 unde se aplica). + - [ ] `hx-target` din fragmentele de import se rezolva in panoul de tab; `csrf_token` pastrat in formulare. + - [ ] Fluxul de import functioneaza identic (upload → mapare → preview → confirma) — fara regresie. +- **Verificare E2E**: browser — urca `test_data.csv`, parcurge cei 4 pasi; stepper-ul avanseaza corect; + commit → randuri in coada → worker → FINALIZATA la RAR test (regula de aur). + +### US-005: Pagina "Acasa" cu ghid de pornire (checklist auto-bifat) +**Ca** utilizator nou **vreau** sa mi se spuna exact ce am de configurat ca sa pot trimite +**pentru ca** sa ajung la prima declaratie reusita fara sa ghicesc pasii. + +- **Depinde de**: US-003 +- **Fisiere**: `app/web/templates/_acasa.html` (nou), `app/web/routes.py` (context: are_creds, are_cheie_folosita, + are_trimiteri), `tests/test_web_onboarding.py` (nou) (~3 fisiere) +- **Test intai (RED)**: `tests/test_web_onboarding.py` — `test_checklist_pas_creds_neconfigurat`, + `test_checklist_pas_creds_bifat_cand_exista`, `test_checklist_ascuns_cand_totul_gata`, + `test_linkuri_ghid_duc_la_taburi`, `test_empty_state_coada_gol` (tab Coada fara submissions arata + indemn catre Import, nu un tabel gol), `test_empty_state_mapari_gol`. +- **Continut**: tab-ul "Acasa" (implicit) arata un card "Primii pasi" cu o lista bifabila: + 1. **Conecteaza-ti contul RAR** (email + parola portal AUTOPASS) — link la tab Cont. + 2. **(Optional) Ia-ti cheia API** daca trimiti din soft propriu — link la tab Cont. + 3. **Importa primul fisier** — link la tab Import. + Fiecare pas se bifeaza automat cand e indeplinit (creds configurate → pas 1 bifat; exista cel putin + o trimitere → pas 3 bifat). Cand toti pasii esentiali sunt gata, ghidul se colapseaza intr-o linie + discreta ("Totul e configurat — vezi coada"), ca sa nu deranjeze utilizatorul experimentat. Sub ghid, + pe acelasi tab, un rezumat scurt + scurtaturi (coada recenta / actiuni rapide). +- **Acceptance criteria**: + - [ ] Pasul "Conecteaza contul RAR" e nebifat fara creds, bifat cand `are_creds` e adevarat. + - [ ] Pasul "Importa primul fisier" se bifeaza cand contul are cel putin un submission. + - [ ] Cand toti pasii esentiali sunt gata, ghidul e colapsat/discret (nu ocupa tot ecranul). + - [ ] Link-urile din ghid duc la tab-ul corect (Cont / Import). + - [ ] **Empty states prietenoase**: tab Coada gol → "Nicio trimitere inca — incepe cu Import" (link la + tab Import); tab Mapari gol → mesaj scurt + indemn, nu un tabel/lista goala fara context. +- **Verificare E2E**: browser — cont nou (fara creds): ghid vizibil cu pasi nebifati + tab Coada arata + empty state cu indemn la Import; dupa setarea credentialelor si un import, pasii se bifeaza si ghidul se restrange. + +## 4. Riscuri + +- **Regresie pe fluxul de import** (cel mai mare risc): stepper-ul (US-004) atinge cele 3 fragmente + de import. Mitigare: stepper PUR vizual, endpoint-urile si ordinea logica raman; regula de aur in + fiecare VERIFY (import → FINALIZATA la RAR test). +- **Lazy-load gresit**: daca tab-urile cer toate fragmentele la load, dispare castigul de aglomerare + si creste load-ul. Mitigare: test explicit `test_fragmentele_inactive_lazy` (US-003). +- **Scope pe cont pierdut la mutarea in fragmente noi**: fragmentele existente sunt scoped pe sesiune + (regula NULL→cont 1, anti-leak C6 din 3.3a). Orice fragment nou (status, acasa) trebuie sa pastreze + acelasi scope. Mitigare: testele de fragment verifica izolarea pe cont; VERIFY reia sweep-ul anti-leak. +- **CSRF / form-uri**: tab Cont muta form-urile de rotire cheie / creds RAR — trebuie pastrate + `csrf_token` si `hx-target` corecte. Mitigare: reutilizam `_cont.html` ca atare in tab. +- **Microcopy "definitiv"**: etichetele din §3 sunt propuneri; daca utilizatorul vrea alt ton, se + ajusteaza in `labels.py` (un singur loc). Risc mic. + +## 5. Intrebari deschise — REZOLVATE + +> Rezolvate cu utilizatorul inainte de executie (poarta de aprobare PRD). Deciziile de mai jos sunt +> obligatorii pentru EXECUTE. + +1. **Tab-uri** — DECIS: **6 tab-uri**: **Acasa · Import · Coada · Mapari · Cont · Nomenclator**. + Fata de propunerea initiala (5), "Coada submissions" devine tab propriu (e ecranul operational + principal, nu se ascunde sub Acasa). Nomenclatorul ramane tab separat (referinta consultata rar, + dar cautata explicit). Comasarea Nomenclator-sub-Mapari respinsa: sunt actiuni diferite (editezi + mapari vs. doar consulti coduri). +2. **Tab implicit** — DECIS: **Acasa** pentru toti, dar ghidul de pornire (US-005) se **restrange + automat** cand contul e configurat (creds RAR setate + cel putin o trimitere), lasand un rezumat + scurt + scurtatura la Coada. Utilizatorul experimentat vede de fapt un mini-rezumat, nu un wizard. +3. **Etichete (§3 US-001)** — DECIS: se **adopta tabelul din US-001** ca atare. Ton: descriptiv-functional + ("Trimitere automata: activa"), nu colocvial ("Coada se trimite singura"). Toate textele intr-un + singur loc (`labels.py`) — ajustabile ulterior fara atingerea template-urilor. +4. **Persistenta tab activ** — DECIS: **query string** (`/?tab=import`). Supravietuieste refresh-ului, + e testabil server-side (criteriul din US-003), si permite link-uri directe din ghid (US-005). Hash-ul + respins: nu ajunge la server, deci netestabil in `test_deeplink_tab_import`. +5. **Stepper import** — DECIS: **4 pasi ficsi** (Incarca · Potriveste coloanele · Verifica · Confirma). + Cand semnatura de coloane e deja memorata, **pasul 2 apare bifat automat** si fluxul sare vizual la + pasul 3, dar pasul 2 ramane afisat in stepper (estompat/bifat) pentru orientare — utilizatorul vede + ca maparea a fost recunoscuta, nu ca a disparut un pas. + +> Impact asupra stories: US-003 listeaza acum **6 tab-uri** (adaugat "Coada" ca tab propriu — panoul de +> submissions existent muta din scroll in tab-ul Coada). Restul stories raman neschimbate. + +## 6. Valuri de executie (graful de dependente) + +``` +Val 1: [US-001] ← modul pur de etichete, fisiere distincte → poate porni singur +Val 2: [US-002] ← bara de status, foloseste US-001 +Val 3: [US-003] ← shell-ul de tab-uri, are nevoie de bara de status (US-002) +Val 4: [US-004] [US-005] ← ambele depind de shell-ul de tab-uri (US-003); fisiere distincte + (US-004 = fragmentele de import; US-005 = _acasa.html) → paralel +``` + +> US-004 si US-005 ating fisiere disjuncte (import vs acasa) — pot rula in paralel (max 2 teammates). +> Atentie: US-003 si US-004 ating amandoua zona de import in `dashboard.html`/include-uri — NU paralel +> cu US-003; de aceea US-004 e in valul urmator. + +## 7. Plan-reviews aplicate (CEO / Eng / Design) + +> Aplicate IN PRD inainte de cod (ROADMAP §5.3). Constatari pliate cu acordul utilizatorului. + +- **CEO (valoare/scope)**: premisa validata — frecarea de activare pe Treapta 2 (service-uri non-ROAAUTO) + e wedge-ul real; US-005 (ghid de pornire) are cel mai mare ROI. Scope sanatos (5 stories, doar strat + de prezentare), fara expansiune. Nota de secventiere: daca timpul scade, US-001/002 + US-005 (microcopy + + activare) au prioritate peste estetica de tab-uri (US-003). +- **Eng (fezabilitate/teste)**: cel mai mare risc tehnic = granita US-003↔US-004 (hx-target/CSRF din + fragmentele de import mutate in tab) → AC + teste dedicate adaugate in US-004. Adaugat: randare + server-side a panoului activ (degradare gratioasa fara JS) in US-003. Constrangere de testare: + TestClient nu executa JS — testele unitare verifica atribute + stare initiala randata server-side; + interactivitatea (swap, navigare cu sageti) cade pe E2E Playwright. +- **Design (UI/UX)**: accesibilitate reala a tab-urilor (`role=tablist/tab/tabpanel` + `aria-selected` + + navigare cu sageti) in loc de "focus vizibil" — pliat in US-003. Empty states prietenoase (Coada/Mapari + goale) — pliat in US-005. Tabelul de microcopy validat. + +--- + +## Raport VERIFY + +> Completat de subagentul verificator (context curat) in faza VERIFY — vezi ROADMAP §5.6. +> PASS/FAIL per criteriu, cu dovezi (output pytest citat, E2E browser pe `http://localhost:8000/`, +> plus regula de aur: import → worker → FINALIZATA la RAR test). Lipseste pana la VERIFY.