Files
rar-autopass/docs/prd/prd-3.4-ux-dashboard-web.md
Claude Agent 4a1d28749a feat(web): dashboard ergonomic cu tab-uri, stepper import si microcopy uman (3.4)
Reorganizeaza interfata web pe trei principii, fara a atinge backend-ul de
trimitere (worker, mapping, idempotency, masina de stari neatinse):

- US-001 app/web/labels.py: modul pur stari tehnice -> text uman + clasa CSS
- US-002 bara status /_fragments/status: microcopy uman, defalcare blocate, scoped cont
- US-003 shell 6 tab-uri (Acasa/Import/Coada/Mapari/Cont/Nomenclator): deep-link
  ?tab=, panou activ randat server-side, fragmente inactive lazy, ARIA real
- US-004 stepper import 4 pasi (pur vizual; hx-target + csrf pastrate)
- US-005 Acasa onboarding checklist auto-bifat + colaps + empty states prietenoase

Reparat in cursul VERIFY/CLOSE: izolare teste (reset ratelimit._hits in fixturi),
regresie avertisment "cont in asteptare de activare" (re-introdus in bara status),
culori hardcodate -> variabile paleta. 434 teste pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 22:26:10 +00:00

23 KiB

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.pytest_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.pytest_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.pytest_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.pytest_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.pytest_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

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 -q434 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.