# PRD 3.4 — Interfata web ergonomica (tab-uri + wizard + microcopy uman) **Stare**: inchis > 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**: - [x] `labels.py` expune o functie/dict care, pentru fiecare stare din `schema.sql`, da `(text, css_class)`. - [x] Nicio stare de submission existenta nu ramane fara eticheta (test parametrizat care iese rosu daca se adauga o stare noua nemapata). - [x] 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**: - [x] `/_fragments/status` randeaza bara cu etichetele din US-001 (scoped pe cont, ca restul UI). - [x] Bara ramane vizibila sus cand se schimba tab-ul (nu e inghitita de panoul activ). - [x] 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**: - [x] Tab-bar cu cele 6 tab-uri (Acasa · Import · Coada · Mapari · Cont · Nomenclator); "Acasa" implicit la prima incarcare. - [x] Un singur panou randat la un moment dat; celelalte fragmente NU se cer pana la activarea tab-ului. - [x] Panoul activ (inclusiv din `?tab=`) e randat **server-side** la full load — fara palpaire la refresh, vizibil si fara JS. - [x] Accesibilitate: `role=tablist/tab/tabpanel`, `aria-selected` pe tab-ul activ, navigare cu sageti (nu doar focus vizibil). - [x] Refresh pe un tab non-implicit revine pe acelasi tab (deep-link prin query string `?tab=`). - [x] 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**: - [x] Acelasi stepper apare in upload, mapare-coloane si preview, cu pasul corect evidentiat. - [x] Pasii deja parcursi sunt marcati ca facuti; cei viitori sunt estompati. - [x] Fiecare pas are un titlu-actiune + o fraza scurta de ajutor (microcopy din US-001 unde se aplica). - [x] `hx-target` din fragmentele de import se rezolva in panoul de tab; `csrf_token` pastrat in formulare. - [x] 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**: - [x] Pasul "Conecteaza contul RAR" e nebifat fara creds, bifat cand `are_creds` e adevarat. - [x] Pasul "Importa primul fisier" se bifeaza cand contul are cel putin un submission. - [x] Cand toti pasii esentiali sunt gata, ghidul e colapsat/discret (nu ocupa tot ecranul). - [x] Link-urile din ghid duc la tab-ul corect (Cont / Import). - [x] **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 > Verificare condusa de lead (utilizatorul a respins E2E cu browser/server). Acoperire: suita > pytest completa + verificare ACs prin FastAPI TestClient + spot-check de integrare. E2E cu browser > live si regula de aur LIVE (FINALIZATA la RAR test) NU au fost rulate in aceasta sesiune — vezi nota. **Suita**: `python3 -m pytest -q` → **434 passed** (de la 400 baseline: +34 teste noi 3.4). Fara regresie. ### PASS/FAIL per story - **US-001 (labels.py)** — PASS. `tests/test_web_labels.py` (11 teste). `test_toate_starile_au_eticheta` parseaza CHECK-ul din `schema.sql` → iese rosu la stare noua nemapata. Functii pure (fara DB/request). - **US-002 (bara status)** — PASS. `tests/test_web_status_fragment.py`. `/_fragments/status` randeaza "Trimitere automata" (nu "worker viu"), defalcare blocate pe motiv, poll `every 15s`, scoped pe cont. - **US-003 (tab-uri)** — PASS. `tests/test_web_tabs.py` (6 teste). TestClient: `role="tablist"` + 6 tab-uri, Acasa implicit (`aria-selected="true"`), `/?tab=import` randeaza `#import-section` server-side, panou activ in HTML initial, role=tab/tabpanel + aria-selected, navigare cu sageti (JS vanilla). Fragmentele inactive NU se cer la load (swap pe click). Deep-link `?tab=` supravietuieste refresh-ului. - **US-004 (stepper)** — PASS. `tests/test_web_import_stepper.py` (8 teste). Stepper 4 pasi in upload(1)/mapcoloane(2)/preview(3), pasii facuti marcati, `aria-current="step"` pe activ, `hx-target="#import-section"` si `csrf_token` pastrate. Fluxul import (upload→mapare→preview→confirma) neatins — endpointuri intacte. - **US-005 (Acasa onboarding)** — PASS. `tests/test_web_onboarding.py` (6 teste). Checklist auto-bifat (are_creds/are_trimiteri), ghid colapsat cand totul gata, linkuri `?tab=cont`/`?tab=import`, empty states prietenoase pe Coada (indemn Import) si Mapari, scoped pe cont. ### Regula de aur (regresie) Backend de trimitere (worker, mapping, idempotency, import_router, masina de stari) **neatins** — confirmat prin diff. Fluxul de import pana la enqueue (`queued`) ramane verde prin `tests/test_import_ui.py` + `tests/test_import_e2e.py`. **Trimiterea LIVE la RAR test (FINALIZATA) NU a fost probata in aceasta sesiune** (fara browser/creds RAR test) — recomandata o probare manuala `./start.sh test both --send` inainte de prod. ### Defecte gasite si reparate in cursul VERIFY/CLOSE 1. **Izolare teste (429)**: fixturile noi nu reseteau rate-limiterul de login in-proces (`ratelimit._hits`); rulate impreuna, login-urile depaseau pragul → 429 → 5 teste rosii la rulare subset. Reparat: `ratelimit._hits.clear()` in fixturile celor 4 fisiere noi (pattern din `test_web_login.py`). Suita completa trecea din noroc de ordine. 2. **Regresie UX** (code-review): avertismentul "Cont in asteptare de activare" (vechiul `_banner.html`) nu mai era afisat dupa scoaterea `/_fragments/banner`. Re-introdus in bara de status (`account_active`). 3. **Consistenta tema**: culori hardcodate in `_acasa.html` → variabile paletei (`--muted`/`--accent`/`--ok`). ### Cleanup notat, neremediat (non-blocant) - Duplicare intre `_render_panel_{mapari,cont,nomenclator}` si endpointurile fragment existente. - `/_fragments/banner` + `_banner.html` raman dead code dupa mutarea avertismentelor in bara de status. - Ramura moarta in `_render_panel_for_tab` (fallback acasa fara conn) — inaccesibila (tab pre-validat). - Bara de status e lazy (HTMX `load`), nu server-side: fara JS arata "se incarca…". AC formale indeplinite (panoul activ e server-side); de reconsiderat la o iteratie viitoare daca conteaza no-JS pe status.