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>
This commit is contained in:
Claude Agent
2026-06-18 22:26:10 +00:00
parent ccd26115f8
commit 4a1d28749a
22 changed files with 1889 additions and 96 deletions

View File

@@ -1,6 +1,6 @@
# PRD 3.4 — Interfata web ergonomica (tab-uri + wizard + microcopy uman)
**Stare**: aprobat
**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).
@@ -75,9 +75,9 @@ de "pagini ca un wizard, intuitive" si "caption-uri utile, relevante, simple, pe
| `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.
- [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)
@@ -94,9 +94,9 @@ de "pagini ca un wizard, intuitive" si "caption-uri utile, relevante, simple, pe
`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.
- [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".
@@ -122,12 +122,12 @@ de "pagini ca un wizard, intuitive" si "caption-uri utile, relevante, simple, pe
`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).
- [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).
@@ -154,11 +154,11 @@ de "pagini ca un wizard, intuitive" si "caption-uri utile, relevante, simple, pe
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.
- [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).
@@ -182,11 +182,11 @@ de "pagini ca un wizard, intuitive" si "caption-uri utile, relevante, simple, pe
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
- [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.
@@ -268,6 +268,46 @@ Val 4: [US-004] [US-005] ← ambele depind de shell-ul de tab-uri (US-003
## 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.
> 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.