# PRD 5.5 — Uniformizare & standardizare UI/UX **Stare**: aprobat (2026-06-23) > Proces: `docs/ROADMAP.md` §5. Contract RAR (sursa de adevar): `docs/api-rar-contract.md`. > **Design vizual (sursa de adevar pentru *cum arata*)**: `docs/design/5.5-uniformizare-ui.md`. > Stare: `draft → aprobat → in-executie → verify-pass → inchis`. ## 1. Obiectiv Aducem toate suprafetele dashboard-ului la acelasi vocabular de componente ca tabelul **Trimiteri** (referinta corecta), reasezam navigarea intr-un **meniu de cont** (hamburger) si dam panoului admin actiuni reale de ciclu de viata pe conturi. Tinta: aplicatia arata si se comporta uniform, fara tabele labartate, fara wayfinding redundant, fara scroll orizontal pentru actiuni. Detaliile vizuale si deciziile utilizatorului: `docs/design/5.5-uniformizare-ui.md` (§10). ## 2. Non-Goals (anti scope-creep) - **Fara redesign de estetica**: paleta/tipografia/tokenii din `base.html` (5.3) raman NESCHIMBATI la octet. - **Fara atingere a fluxului de trimitere**: worker (masina stari submissions, idempotenta, mapping-rezolvare) NEATINS, cu o singura exceptie controlata — gate-ul `claim_one` pe noua stare de cont (US-004), pastrand echivalenta `active=1 ⇔ status='active'`. - **Fara schimbare a semanticii `auto_send`**: comutatorul Auto/Manual ramane reskin la nivel de macro (`name="auto_send"`, semantica de prezenta). Zero atingere a parserelor `/mapari` si `/_import/...`. - **Fara rute noi de date / fara HTTP nou pe chei API**: lifecycle-ul conturilor e admin-only, sub `require_admin` + CSRF, exact ca rutele admin existente. - **Fara responsive/mobile nou** dincolo de ce ofera deja `.tablewrap` (scroll in card). - **Tabelul Trimiteri ramane neatins** — e referinta, nu tinta. ## 3. Stories atomice ### US-001: Elimina sectiunea "Ajutor" din Acasa **Ca** operator **vreau** o pagina Acasa fara wayfinding redundant **pentru ca** linkurile Mapari/Coduri RAR sunt deja in navigare. - **Depinde de**: — - **Fisiere**: `app/web/templates/_acasa.html`, `tests/test_web_acasa.py` (~2 fisiere) - **Test intai (RED)**: `tests/test_web_acasa.py` — `test_acasa_fara_sectiune_ajutor` (randul "Ajutor:" + linkurile inline lipsesc din HTML-ul Acasa) - **Acceptance criteria**: - [ ] Blocul `Ajutor: Mapari Coduri RAR` (liniile ~47-55) eliminat din `_acasa.html`. - [ ] Restul Acasa (upload, "Primii pasi", sectiunea Trimiteri) neschimbat. - [ ] `python3 -m pytest tests/test_web_acasa.py -q` verde. - **Verificare E2E**: browser HTMX pe `/` — Acasa nu mai afiseaza randul Ajutor; upload + Trimiteri intacte. ### US-002: Tabel Nomenclator cu aspectul tabelului Trimiteri **Ca** operator **vreau** ca nomenclatorul sa arate identic cu Trimiteri **pentru ca** consistenta reduce sarcina cognitiva. - **Depinde de**: — - **Fisiere**: `app/web/templates/_nomenclator.html`, `tests/test_web_nomenclator.py` (~2 fisiere) - **Test intai (RED)**: `tests/test_web_nomenclator.py` — `test_nomenclator_grila_standard` (`.tablewrap` + `table` + antet `th` standard + `.pill` pe cod; empty-state `.empty`) - **Acceptance criteria**: - [ ] Foloseste aceeasi structura `.tablewrap > table` cu antet `th` standard ca `_submissions.html`. - [ ] Codul prestatie ramane in `.pill`; coloanele aliniate, hover/aspect identice cu Trimiteri. - [ ] Empty-state pastrat (`Nomenclator gol...`), in `.empty`. - [ ] `python3 -m pytest tests/test_web_nomenclator.py -q` verde; AA light+dark (zero literali de culoare). - **Verificare E2E**: browser — Nomenclator si Trimiteri arata din aceeasi familie vizuala in dark si light. ### US-003: Macro `autosend_toggle` compact (Auto / Manual) **Ca** operator **vreau** un comutator scurt In coada, fara text repetat pe randuri **pentru ca** proza inline ingrasa randurile si impinge actiunile afara din ecran. - **Depinde de**: — - **Fisiere**: `app/web/templates/_macros.html`, `tests/test_web_macros.py` (~2 fisiere) - **Test intai (RED)**: `tests/test_web_macros.py` — `test_autosend_compact` (macro-ul randeaza control Auto/Manual, pastreaza `name="auto_send" value="true"` + `form=` + starea `checked`, si NU mai contine propozitiile explicative "La fisierele viitoare..."/"Nebifat = ...") - **Acceptance criteria**: - [ ] `autosend_toggle(form_id, checked)` randeaza un comutator compact etichetat **Auto** / **Manual** (radio sau switch), nowrap, fara propozitii inline. - [ ] Pastreaza EXACT `name="auto_send"`, `value="true"`, semantica de prezenta (bifat→true / absent→false), `form="{{form_id}}"`, `checked` reflecta `checked`. - [ ] Explicatia detaliata NU mai e in macro (se muta in panoul Ajutor, US-005). Tooltip scurt admis pe control. - [ ] `python3 -m pytest tests/test_web_macros.py tests/test_import_e2e.py -q` verde (parserele backend nealterate). - **Verificare E2E**: in fluxul import (mapcoloane) si in Mapari, comutatorul produce acelasi `auto_send` bool ca azi (queued vs needs_review neschimbat). ### US-004: Model de stare a contului (`accounts.status`) + gate worker **Ca** sistem **vreau** stari de cont distincte (pending/active/blocked/archived/deleted) **pentru ca** adminul are nevoie de blocare/arhivare/stergere, nu doar activ/inactiv. - **Depinde de**: — - **Fisiere**: `app/schema.sql`, `app/db.py` (migrare defensiva), `app/users.py`, `app/worker/...` (gate `claim_one`), `tests/test_account_status.py`, `tests/test_worker_*.py` (~5 fisiere) - **Test intai (RED)**: `tests/test_account_status.py` — `test_migrare_deriva_status_din_active`, `test_blocked_nu_e_claimuit`, `test_archived_nu_e_claimuit`, `test_dev_id1_protejat` - **Acceptance criteria**: - [ ] Coloana `accounts.status` TEXT cu CHECK pe `{pending,active,blocked,archived,deleted}` (stergere = soft, `status='deleted'` + purjare de catre jobul de retentie T16); migrare **defensiva si idempotenta** (pattern `_migrate` ca la `is_admin`), derivata din `active` la prima rulare: `active=1→active`, altfel `pending`. - [ ] Helperi puri in `users.py`: `set_account_status(id, status)`, `delete_account(id)`, cu protectia contului dev `id=1` (ridica/ignora, nu corupe). - [ ] Worker `claim_one` gate-uieste pe `status='active'`, pastrand echivalenta cu `COALESCE(active,1)=1` de azi (conturile blocked/archived NU sunt claimuite). - [ ] `active` ramane consistent (`active=1 ⇔ status='active'`) cat timp coexista, fara regresie pe testele worker. - [ ] `python3 -m pytest -q` verde (suita completa). - **Verificare E2E**: marcheaza un cont `blocked` → submission-urile lui nu pleaca la RAR; `active` → pleaca. ### US-005: Tabel Mapari standardizat + panou Ajutor **Ca** operator **vreau** tabelele Mapari compacte ca Trimiteri, cu actiunile vizibile fara scroll si ajutor intr-un singur loc **pentru ca** acum sunt labartate si butoanele Salveaza/Sterge ies din ecran. - **Depinde de**: US-003 - **Fisiere**: `app/web/templates/_mapari.html`, `tests/test_web_mapari.py` (~2 fisiere) - **Test intai (RED)**: `tests/test_web_mapari.py` — `test_mapari_grila_compacta` (coloane inguste nowrap, actiuni la dreapta), `test_mapari_ajutor_disclosure` (un singur `
`/link Ajutor in antet, fara proza pe randuri) - **Acceptance criteria**: - [ ] Cele 3 sectiuni (De rezolvat / Mapari salvate / Formate coloane) folosesc grila standard ca Trimiteri; coloana "In coada" foloseste macro-ul compact din US-003. - [ ] Butoanele **Salveaza**/**Sterge** vizibile fara scroll orizontal pe latime de dashboard normala (coloana Actiuni la dreapta, nowrap); sub-text (`N blocate`, `acum: COD`) ca `muted` 12px sub valoare. - [ ] Antetul "De rezolvat" contine un link/`
` **Ajutor** cu explicatia maparilor + Auto/Manual, scrisa O SINGURA DATA; proza inline de pe randuri eliminata. - [ ] CSRF, `hx-post`, `hx-target="#mapari-section"`, formularele si re-rezolvarea la edit cod — neschimbate. - [ ] `python3 -m pytest tests/test_web_mapari.py -q` verde; AA light+dark. - **Verificare E2E**: browser — mapezi o operatie (Salveaza vizibil fara scroll), comuti Auto/Manual, deschizi Ajutor; submission blocat se deblocheaza la salvarea codului (comportament neschimbat). ### US-006: Meniu hamburger in header + context de autentificare **Ca** utilizator **vreau** un meniu de cont in dreapta-sus cu Cont/Integrare/Nomenclator/Panou admin/logout **pentru ca** acestea nu sunt lucru zilnic si aglomereaza tab-bar-ul. - **Depinde de**: — - **Fisiere**: `app/web/templates/base.html`, `app/web/routes.py` (helper context partajat), `tests/test_web_header_menu.py` (~3 fisiere) - **Test intai (RED)**: `tests/test_web_header_menu.py` — `test_meniu_autentificat_are_linkuri_cont`, `test_meniu_admin_doar_pentru_admin`, `test_meniu_neautentificat_fara_logout` (login/signup → fara linkuri de cont) - **Acceptance criteria**: - [ ] Iconita `☰` in header (langa toggle tema), `aria-label`, `aria-expanded`, `aria-controls`; dropdown ancorat dreapta-sus; inchidere la click-afara + `Esc` (focus readus pe `☰`). Fara overlay. - [ ] Continut autentificat: Cont, Integrare, Nomenclator, **Panou admin** (doar `is_admin`), separator, **Iesi din cont** (form `POST /logout` cu `csrf_token`). - [ ] `base.html` primeste `is_authenticated`/`is_admin`/`csrf_token` printr-un helper de context partajat (un singur loc); **defensiv**: lipsa cheilor → meniu in stare neautentificata, nu eroare. - [ ] Pe login/signup meniul nu arata linkuri de cont/logout. - [ ] `python3 -m pytest tests/test_web_header_menu.py -q` verde. - **Verificare E2E**: browser — `☰` deschide/inchide (Esc + click-afara), linkurile navigheaza corect, logout iese; pe login meniul nu expune cont. ### US-007: Tab-bar redus la Acasa · Mapari **Ca** operator **vreau** un tab-bar doar cu suprafetele de lucru zilnic **pentru ca** Cont/Integrare/Nomenclator traiesc acum in meniul de cont. - **Depinde de**: US-006 - **Fisiere**: `app/web/templates/dashboard.html`, `tests/test_web_dashboard_tabs.py` (~2 fisiere) - **Test intai (RED)**: `tests/test_web_dashboard_tabs.py` — `test_tabbar_doar_acasa_mapari`, `test_fragmente_mutate_inca_accesibile` (`/_fragments/{cont,integrare,nomenclator}` raman 200 + deep-link `?tab=`) - **Acceptance criteria**: - [ ] `tabs` in `dashboard.html` = doar `acasa`, `mapari`; badge-urile de contoare raman pe Mapari. - [ ] Logout + link admin ad-hoc din coltul dreapta-sus al dashboard-ului eliminate (mutate in meniul US-006). - [ ] Rutele `/_fragments/cont|integrare|nomenclator` + `?tab=` raman valide (accesate din meniu); zero rute moarte, zero 404 pe deep-link existent. - [ ] Navigarea ARIA cu sageti pe tab-bar ramane corecta cu 2 tab-uri. - [ ] `python3 -m pytest tests/test_web_dashboard_tabs.py -q` verde. - **Verificare E2E**: browser — tab-bar arata doar Acasa/Mapari; deschizi Nomenclator/Cont/Integrare din `☰`, deep-link `/?tab=integrare` inca functioneaza. ### US-008: Rute admin pentru ciclul de viata al conturilor (block/archive/delete + bulk) **Ca** admin **vreau** endpointuri care blocheaza/arhiveaza/sterg conturi, individual si in bulk **pentru ca** panoul are nevoie sa actioneze pe selectie. - **Depinde de**: US-004 - **Fisiere**: `app/web/routes.py` (rute `/admin/*`), `tests/test_admin_lifecycle.py` (~2 fisiere) - **Test intai (RED)**: `tests/test_admin_lifecycle.py` — `test_block_archive_delete_single`, `test_bulk_pe_lista_account_id`, `test_bulk_sare_contul_dev`, `test_non_admin_403`, `test_csrf_obligatoriu` - **Acceptance criteria**: - [ ] Rute `POST /admin/block`, `/admin/archive`, `/admin/delete` (+ pastreaza `activate`) sub `require_admin` + CSRF, cu PRG (redirect inapoi la `/admin`), folosind helperii din US-004. - [ ] Accepta o LISTA de `account_id` (bulk) si o singura tinta (per-rand) prin acelasi handler. - [ ] Contul dev `id=1` e sarit in bulk (nu eroare) si refuzat individual; `delete` cere confirmare la nivel UI (US-009) si purjeaza datele conform retentiei (GDPR/L.142). - [ ] Non-admin → 403; lipsa CSRF → respins. - [ ] `python3 -m pytest tests/test_admin_lifecycle.py -q` verde. - **Verificare E2E**: POST autentificat ca admin pe fiecare verb (single + bulk) muta starea corect; contul dev neatins. ### US-009: Panou admin — selectie cu bife + bara bulk + actiuni per-rand **Ca** admin **vreau** sa selectez conturi si sa aplic actiuni pe selectie **pentru ca** activarea/blocarea una cate una e lenta. - **Depinde de**: US-008 - **Fisiere**: `app/web/templates/admin.html`, `tests/test_web_admin.py` (~2 fisiere) - **Test intai (RED)**: `tests/test_web_admin.py` — `test_admin_coloana_selectie_si_master`, `test_bara_bulk_cu_cele_4_verbe`, `test_actiuni_per_rand`, `test_fara_nota_cont_dev` - **Acceptance criteria**: - [ ] Tabel conturi in asteptare (si analog active): coloana checkbox + master "Selecteaza tot" (`aria-label` per rand + master). - [ ] Bara de actiuni bulk (ascunsa pana la selectie) cu **Activeaza / Blocheaza / Arhiveaza / Sterge**; `Sterge` cu `hx-confirm`/dialog; trimite lista de `account_id` la rutele US-008. - [ ] Actiuni per-rand (kebab `...`) cu aceleasi verbe; `Sterge` cu `color:var(--err)` + confirmare. - [ ] Nota "Cont dev implicit (id=1)" **eliminata** din pagina (protectia ramane in cod, US-004/US-008). - [ ] `python3 -m pytest tests/test_web_admin.py -q` verde; AA light+dark; tabel in grila standard. - **Verificare E2E**: browser ca admin — bifezi 2 conturi, bara bulk apare cu numarul selectat, Arhiveaza muta randurile; Sterge cere confirmare; contul dev nu poate fi selectat-distrus. ## 4. Riscuri - **Schema `accounts.status` (US-004)** = singura schimbare de date. Mitigare: migrare defensiva idempotenta (pattern `_migrate` deja folosit la `accounts.active`/`users.is_admin`), derivata din `active`, cu echivalenta `active=1 ⇔ status='active'` pana cand `active` poate fi retras intr-o livrabila viitoare. Testele worker existente sunt plasa. - **`base.html` partajat (US-006)**: e folosit de login/signup/admin/dashboard. Risc de context lipsa → meniu rupt. Mitigare: helper de context partajat + defaulturi defensive (lipsa → neautentificat), test pe toate cele 4 pagini. - **Coliziune pe fisiere intre stories** (lectia 5.1 clobber): US-006 si US-007 ating ambele zona de navigare (`base.html` vs `dashboard.html`, plus scoaterea logout-ului ad-hoc din `dashboard.html`). Mitigare: US-007 depinde de US-006 si ruleaza secvential (acelasi teammate recomandat), nu in worktree-uri paralele. - **`delete` cont (US-008)**: actiune distructiva ireversibila. Mitigare: confirmare UI obligatorie, contul dev protejat, purjare aliniata la retentia existenta (T16), nu stergere ad-hoc de date conexe fara plan. - **Macro autosend (US-003)**: orice schimbare de `name`/semantica ar rupe tacit clasificarea queued/needs_review. Mitigare: test care asereaza `name="auto_send"` + prezenta, plus `test_import_e2e` ramane verde. ## 5. Intrebari deschise — REZOLVATE (aprobare utilizator 2026-06-23) - **Stergere cont** → **soft delete**: `status='deleted'`, scos imediat din toate listele, date purjate de jobul de retentie existent (T16, GDPR/L.142). NU hard DELETE imediat (auditabil + fereastra de revenire). - **Blocheaza vs Arhiveaza** → `blocked` = suspendare **reversibila**, contul ramane in liste, marcat vizibil, worker nu trimite; `archived` = **scos din listele active**, date pastrate read-only. Etichete confirmate. - **Comutatorul In coada** → **radio etichetat Auto / Manual** (explicit), nu switch on/off. ## 6. Valuri de executie (graful de dependente) ``` Val 1: [US-001] [US-002] [US-003] [US-004] ← fara dependente, fisiere disjuncte → paralel Val 2: [US-005] ← dep US-003 (macro) [US-006] ← fara dep (navigare/header) [US-008] ← dep US-004 (rute admin pe model stare) Val 3: [US-007] ← dep US-006 (acelasi fisier de navigare → secvential, NU worktree paralel) [US-009] ← dep US-008 (UI admin pe rutele de lifecycle) ```