feat(5.12): modal editare + cont obligatoriu la import; design.md + PRD 5.13 revizuit (/autoplan)

5.12 (livrat): editare in modal a randurilor de preview, cont obligatoriu inainte de
import, formular editare extras (_form_editare, _editare_preview_modal), plus suita de
teste aferenta (preview edit/compact, mapare op, form editare, signup, admin panel).

Design + planificare:
- docs/design.md: sistem de design (tokeni, breakpoints, scara control, componente, a11y).
- docs/prd/prd-5.12-* si prd-5.13-* (5.13 cu raport /autoplan: CEO+Design+Eng, audit trail).

Curatare: sterse PNG-urile de test/mockup temporare din radacina.

Nota: implementarea CSS 5.13 (responsive compact + sistem butoane) NU e inca facuta —
planul revizuit cere refactorul testelor fragile din test_web_responsive.py INAINTE de CSS.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Claude Agent
2026-06-27 18:52:20 +00:00
parent 283299ff20
commit b26dbb79e1
44 changed files with 4852 additions and 305 deletions

File diff suppressed because one or more lines are too long

232
docs/design.md Normal file
View File

@@ -0,0 +1,232 @@
# design.md — Sistemul de design Gateway RAR AUTOPASS
Sursa de adevar pentru deciziile vizuale ale aplicatiei web. **Orice plan de design
(`/plan-design-review`, `/design-consultation`, `/design-review`) si orice modificare
de UI trebuie sa porneasca de aici.** Unde un mockup sau o propunere difera de acest
document, documentul are dreptate (sau se actualizeaza explicit, intr-un commit separat).
Limba UI: romana, fara diacritice in cod/atribute tehnice, cu diacritice acceptate in
textul vizibil (fontul are `latin-ext`). Fara emoji.
CSS-ul traieste inline in `app/web/templates/base.html` (un singur `<style>`). Nu exista
build step. Tokenii de mai jos sunt variabile CSS reale definite acolo.
---
## 1. Principii
1. **Compact, nu inghesuit.** Densitate mare de informatie utila, dar cu ritm si spatiu.
Pe ecrane mici aratam ESENTIALUL, nu tot ce incape pe desktop. Restul intra in detaliu
(modal) sau in linii secundare mici.
2. **Compactarea e si pentru desktop.** Cand o componenta e mai lizibila compacta (ex.
wizard-ul de import), forma compacta se aplica pe toate latimile, nu doar pe mobil.
3. **Mobile-first ca verificare, nu ca scuza.** Orice ecran trebuie sa fie complet
utilizabil la 360px latime, fara scroll orizontal de pagina si fara text rupt vertical.
4. **Starea prin text + culoare, niciodata doar culoare** (accesibilitate; pill-uri cu
eticheta umana, glife ✓/✗ cu text).
5. **O singura zona de actiune dominanta pe ecran.** Un singur buton primar vizibil per
context (ex. „Trimite la RAR"). Restul sunt secundare/ghost.
6. **Tinte de atins generoase pe touch, sobre pe desktop.** Vezi scara de control.
---
## 2. Tokeni
### 2.1 Culoare (variabile CSS, 4 teme)
Paleta e definita pe `:root` (dark, default) si suprascrisa pe `[data-theme="light|petrol"]`.
`auto` se rezolva la light/dark dupa `prefers-color-scheme`. **Nu folosi culori hardcodate;
foloseste mereu variabilele.** Pentru tente, `color-mix(in srgb, var(--x) N%, transparent|var(--card))`.
| Token | Rol | dark | light | petrol |
|-------|-----|------|-------|--------|
| `--bg` | fundal pagina | `#0f1218` | `#f5f7fa` | `#0e1416` |
| `--card` | suprafata card/meniu | `#181c24` | `#ffffff` | `#161e20` |
| `--ink` | text principal | `#e6e9ef` | `#1a1d24` | `#e6e9ef` |
| `--muted` | text secundar | `#8b93a7` | `#5c6473` | `#8b93a7` |
| `--line` | borduri/separatoare | `#262b36` | `#e2e5ea` | `#232c2e` |
| `--accent` | actiune primara / link | `#2E74D6` | `#1F66C9` | `#0E7C7B` |
| `--ok` | succes / trimis | `#2FBF8F` | `#15803d` | `#2FBF8F` |
| `--warn` | atentie / de verificat | `#E0A93B` | `#b45309` | `#E0A93B` |
| `--err` | eroare / distructiv | `#E05D5D` | `#dc2626` | `#E05D5D` |
Accentul light (`#1F66C9`) e ales pentru contrast AA pe alb (5.51:1). Orice text colorat
pe `--card` trebuie sa ramana >= 4.5:1 in toate cele 3 palete.
### 2.2 Tipografie
Font: **IBM Plex Sans** (UI), **IBM Plex Mono** (VIN, coduri, ID-uri). Self-hosted, `latin-ext`
pentru diacritice, `font-display:swap`. Greutati disponibile: 400, 500, 700.
Scara (px / weight) — folosita consecvent, nu inventa marimi noi:
| Rol | size | weight | note |
|-----|------|--------|------|
| Titlu pagina (header) | 20 (desktop) / 17 (mobil) | 700 | letter-spacing -.01em |
| Titlu sectiune / card | 15 | 600 | `h2.sec` |
| Subtitlu / `h3` | 14 | 600 | |
| Corp / controale | 14 | 400/500 | inputuri, butoane |
| Eticheta camp, link card | 13 | 400/500 | `.cardlink`, label form |
| Secundar / meta | 12 | 400 | text muted, sub-linii |
| Micro (coduri, badge) | 11 | 500/700 | mono pentru coduri |
Numerele tabulare: `font-variant-numeric: tabular-nums` pe tabele (aliniere coloane).
Coduri/VIN/ID: `font-family: "IBM Plex Mono"`.
### 2.3 Spatiere
Scara 4px: **4, 6, 8, 10, 12, 14, 16, 20, 24**. Padding card desktop `16px 20px`, mobil `16px`.
Gap intre carduri `1416px`. Gap intre controale pe o linie `812px`.
### 2.4 Radius
| Valoare | Uz |
|---------|-----|
| `6px` | controale: butoane, input, select |
| `78px` | carduri-rand, meniuri, butoane icon |
| `10px` | carduri de sectiune |
| `12px` | modal (desktop) |
| `99px` | pill-uri, badge-uri, bara de progres |
### 2.5 Elevatie
Plat implicit (border `1px solid var(--line)`). Umbra DOAR pentru elemente plutitoare:
meniuri/kebab `0 8px 24px rgba(0,0,0,.18)`, modal `0 16px 48px rgba(0,0,0,.35)`.
---
## 3. Breakpoints
Un singur prag conceptual mobil la **768px**; un prag de densitate la **1024px**.
| Interval | Numit | Regula |
|----------|-------|--------|
| `>= 1024px` | desktop | layout complet; aplica si compactarile globale (wizard) |
| `7681024px` | tableta | **card-uri** pentru tabelele actionabile (preview, mapari), 2 pe rand; tabelele dense read-only raman cu scroll contained |
| `< 768px` | mobil | un card pe rand, o coloana, tinte touch 44px |
CSS custom properties NU functioneaza in `@media`; pragul se scrie literal
(`@media (max-width:767px)`, `@media (max-width:1024px)`). Reutilizeaza aceste praguri,
nu introduce altele noi.
---
## 4. Scara de control (tinte de atins)
| Context | min-height | Uz |
|---------|-----------|-----|
| Touch (`< 768px`) | **44px** | orice buton/link/select interactiv |
| Desktop standard | **36px** | butoane, icon-btn, cardlink, intrari meniu |
| Compact (desktop) | **32px** | kebab summary, butoane pager, pill-cat |
Pe desktop nu fortam 44px peste tot (devine greoi); pe mobil da. Latimea butoanelor:
**auto, nu full-width**, cu exceptia butonului primar de actiune dintr-o bara dedicata
(ex. „Trimite la RAR" in bara sticky, „Salveaza si continua").
---
## 5. Componente
### 5.1 Butoane — sistem unificat
Patru variante. Toate: `font:inherit` (IBM Plex Sans), `font-weight:500`, `border-radius:6px`,
`padding:8px 14px` (desktop), tinta conform scarii de control. Tranzitie `filter/background .15s`.
| Varianta | Clasa | Fundal | Text | Bordura | Uz |
|----------|-------|--------|------|---------|-----|
| Primar | `.btn` (default `<button>`) | `--accent` | `#fff` | `--accent` | actiunea dominanta |
| Secundar | `.btn-secondary` | transparent | `--ink` | `--line` | actiuni neutre (Editeaza, Filtreaza) |
| Ghost | `.btn-ghost` | transparent | `--accent` | transparent | actiuni tertiare/linkuri-actiune |
| Distructiv | `.btn-danger` | transparent | `--err` | `--err` | Sterge; hover → fundal `--err`, text `#fff` |
**Iconite in butoane:** label text + iconita optionala la stanga (16px, `fill:currentColor`,
`aria-hidden`). **Butoanele icon-only sunt interzise pentru actiuni cu text echivalent**
(ex. Salveaza/Sterge in tabele) — au cauzat „bloc colorat cu iconita invizibila" pe mobil.
Cand spatiul e strans, foloseste un grup compact `[ Salveaza ] [ Sterge ]` cu text scurt,
nu doua blocuri full-width unul sub altul. Icon-only ramane permis DOAR pentru: comutator
tema, hamburger cont, kebab, inchidere modal — toate cu `aria-label`.
Stari: `:hover``filter:brightness(1.08)` (primar) sau `background:var(--line)` (secundar/ghost);
`:focus-visible``outline:2px solid var(--accent); outline-offset:2px`; `:disabled`
`opacity:.45; cursor:default`. Stare „dirty" (modificari nesalvate) pe butonul de salvare:
fundal `--accent`.
### 5.2 Card
`background:var(--card); border:1px solid var(--line); border-radius:10px`. Carduri de
sectiune cu titlu `h2.sec` (15/600). Carduri-rand (lista pe mobil/tableta) cu radius 810px,
stivuite vertical, gap intern 78px.
### 5.3 Tabel → card-uri (responsive)
Tabelele **actionabile** (Trimiteri, Preview import, Mapari) devin card-uri sub 1024px.
Regula de card (vezi §3): NU folosi pattern-ul „eticheta cu `min-width` fix + valoare in
flex" — sparge valorile pe verticala. In schimb:
- **Stivuieste**: eticheta mica deasupra valorii (`display:block`), SAU
- **Card semantic**: linie titlu (identificator + stare), linii secundare mici. Preferat
pentru liste lungi (Trimiteri, Preview).
Sub 768px: un card pe rand. 7681024px: grid 2 carduri pe rand
(`grid-template-columns:repeat(2,1fr); gap:12px`).
Tabelele **dense read-only** (Jurnal, Nomenclator, Admin) raman tabel cu scroll orizontal
**contained in card** (`.tablewrap { overflow-x:auto }`), nu se cardifica.
### 5.4 Stepper / wizard import — COMPACT pe toate latimile
Patru pasi: Incarca fisier · Potriveste coloanele · Verifica · Confirma trimiterea.
- **Desktop**: o bara slim orizontala — pastila numar (sau ✓) + titlu scurt pe O linie,
pasul activ evidentiat. **Fara paragraf de ajutor inalt** in bara (ajutorul, daca e
nevoie, e text mic sub bara, o singura linie). Inaltime tinta ~44px, nu blocuri inalte.
- **Tableta/mobil**: colapsat la o singura linie — `Pasul N din 4 · <Titlu>` + bara de
progres (`height:5px; border-radius:99px`, umplere `--accent` la `N/4`). Ajutorul pasului
activ sub bara, text 12px muted.
Niciodata 4 coloane egale cu text — se taie/se rupe pe ecrane inguste.
### 5.5 Pill-uri si badge-uri
Stare: `.pill` 12px, radius 99px, cu clasa de culoare (`.s-ok/.s-warn/...`). Filtre de
stare: `.pill-cat` (contur inactiv, umplere activa pe culoarea categoriei). Badge contor:
cerc 18px, `--err`, text alb 11/700.
### 5.6 Formulare
Label 1213px muted deasupra controlului. Input/select: `--bg`, bordura `--line`, radius 6px,
padding `7px 10px`. Pe mobil controalele de formular din sectiunile de continut sunt
full-width (tinta 44px). Pe desktop pastreaza latimi rezonabile (`select` max ~340px).
### 5.7 Modal
Desktop: dialog centrat `max-width:680px`, radius 12px, backdrop `rgba(0,0,0,.55)`,
scroll intern. Mobil (`< 768px`): full-screen (fara colturi/umbra), buton inchidere 44px,
focus-trap + scroll-lock + `inert` pe `<main>`.
---
## 6. Accesibilitate (obligatoriu)
- Contrast text >= 4.5:1 (normal), >= 3:1 (>=18px bold) in toate cele 3 palete.
- Stare comunicata prin text, nu doar culoare.
- `:focus-visible` vizibil pe tot ce e interactiv (outline `--accent`).
- Tinte touch >= 44px pe mobil.
- Icon-only obligatoriu cu `aria-label`; SVG decorativ `aria-hidden="true"`.
- Modale: `role="dialog"`, `aria-modal`, focus-trap, focus return pe trigger.
- `prefers-reduced-motion`: scurteaza/elimina tranzitiile non-esentiale.
---
## 7. Pentru planurile de design (cum se foloseste acest fisier)
Inainte de orice propunere vizuala:
1. Citeste acest fisier integral. Foloseste DOAR tokenii de aici (culoare, type, radius, spatiu).
2. Verifica fiecare ecran la 360 / 768 / 1024 / 1280px.
3. Aplica compactarile globale (wizard, butoane) si pe desktop, nu doar pe mobil.
4. Respecta „un singur primar per context" si scara de control.
5. Daca o propunere cere un token nou (culoare/marime/radius), justifica si adauga-l AICI
in acelasi PR — nu introduce valori ad-hoc in template.
Stadiul de implementare a regulilor responsive se urmareste in PRD-ul activ
(`docs/prd/prd-5.13-responsive-compact.md`) si in `docs/ROADMAP.md`.

View File

@@ -0,0 +1,598 @@
<!-- /autoplan restore point: /home/claude/.gstack/projects/romfast-rar-autopass/main-autoplan-restore-20260626-201417.md -->
# PRD 5.12 — Editare unificata in modal + cont cu companie/email/CUI obligatorii + rafinari import (calendar data, mapare cu antet+prima inregistrare, un singur Salveaza, preview compact) + responsive tableta/mobil
**Stare**: inchis (verify-pass 2026-06-26; 8 stories TDD prin agent team, VERIFY context curat PASS + 1 FAIL remediat, /code-review high 3 buguri reparate; regresie 987 passed/1 skipped/0 failed; asteapta confirmare commit — poarta umana)
> Proces complet: `docs/ROADMAP.md` §5. Contractul RAR (sursa de adevar de contract):
> `docs/api-rar-contract.md`. Starea trece: `draft → aprobat → in-executie → verify-pass → inchis`
> (actualizata de lead). Acest PRD nu repeta strategia/contractul — le linkeaza.
>
> Continua 5.11 ([prd-5.11](prd-5.11-ux-import-compact-preview-navigatie.md)). **Backendul de
> trimitere (worker, masina de stari de trimitere, idempotenta, contract RAR) ramane NEATINS.**
> Atingeri de schema permise (ambele coloane noi, migrare defensiva `_migrate`, ca la 3.3b/3.5/3.6):
> `accounts.email` (US-001) si `import_rows.reviewed` (US-007, marcaj „verificat" per rand de preview).
> Vezi Non-Goals.
## 1. Obiectiv
Continuam dogfooding-ul de first-run inceput in 5.11. Sase frictiuni confirmate E2E in browser
(Playwright pe `exemple/prezentari_test.csv`, 2026-06-26) — toate UI/UX, plus o regula de date pe
conturi:
1. **Editarea unui rand din preview e rupta vizual si arunca o eroare JS.** Modul de editare inline
(`tr.preview-edit` cu `display:block` intr-un tabel `table-layout:fixed`) colapseaza coloanele —
antetul si formularul se randeaza pe verticala, caracter cu caracter (reprodus identic cu
`image copy.png`). La click pe **Anuleaza** se arunca in consola
`TypeError: Cannot read properties of null (reading 'htmx-internal-data')` (reprodus live).
Decizie utilizator: **editarea trebuie sa fie un MODAL, ca la Trimiteri, refolosind ACELASI
formular** (fara cod duplicat).
2. **Data prestatiei se scrie doar manual** (input text cu hint `YYYY-MM-DD`). Trebuie sa se poata
alege si din **calendar** (`<input type="date">` nativ, decizie utilizator — zero dependinte JS).
3. **Conturile nu au reguli minime de identitate.** Confirmat in baza: toate conturile au `cui=NULL`,
iar conturile create din CLI/teste nu au niciun utilizator → fara email. Decizie utilizator:
un cont inregistrat trebuie sa aiba **obligatoriu companie, email si CUI**.
4. **Maparea coloanelor nu arata datele.** Pasul 2 listeaza nume de coloana + 2 exemple stivuite, dar
nu se vede clar **capul de tabel (numele coloanelor) + valorile primei inregistrari**, ca operatorul
sa stie ce mapeaza.
5. **Panoul „Operatii de mapat la cod RAR" cere un Salveaza per rand.** La un fisier cu N operatii
nemapate sunt N butoane „Salveaza" si N submit-uri. Trebuie **un singur buton Salveaza** care
salveaza toate maparile odata.
6. **Tabelul de preview (pasul 3) nu e compact si are o coloana neclara.** Randurile sunt foarte inalte
(VIN-ul se sparge pe verticala), iar coloana **„Verificat?"** nu are sens evident in acest pas
(operatorul nu intelege bifa). Trebuie lista mai compacta si coloana clarificata/eliminata.
7. **Pe tableta si mobil interfata arata prost si articolele din header se suprapun.** Header-ul are grila
desktop `1fr auto 1fr` (`min-height:92px`, logo 60px) si un singur prag mobil `@media (max-width:767px)`,
dar **nimic pentru tableta (7681024px)** — acolo logo + titlu + badge mediu + comutator tema + versiune
+ hamburger se inghesuie si se suprapun. Tot fluxul (header, import, preview, modal, Trimiteri, Mapari,
Cont) trebuie **compact, functional si ergonomic** pe tableta si telefon, cu tinte touch si fara suprapuneri.
Toate sunt **UI/UX**, cu o singura exceptie de date controlata: identitatea contului (companie/email/CUI
obligatorii, US-001/002).
## 2. Non-Goals (anti scope-creep)
- **Nu** atingem worker-ul, reconcilierea, idempotenta, `build_key`, masina de stari de trimitere sau
contractul RAR.
- **Nu** schimbam canalul API (`POST /v1/prezentari` / `/valideaza`) si nici logica de mapare
(`mapping.py` `resolve_prestatii`). Maparea operatie→cod ramane neschimbata; doar UI-ul de mapare din
pasul de import se reorganizeaza (US-005) si reuseaza `save_mapping`/`reresolve_account` existente.
- **Nu** stergem coloanele DB `auto_send` (deja neutralizate in 5.11) si nu reintroducem conceptul.
- **Nu** schimbam stocarea editarii de preview: ramane `import_rows.override_json` (Approach B din 3.6),
ruta `POST /_import/{id}/rand/{i}/editeaza` ramane sursa de adevar; doar **suprafata** de editare trece
din rand-inline in modal.
- **Nu** facem editare in bloc / multi-rand si nici editare a operatiei/codului RAR din modalul de rand
(codul se mapeaza din panoul „Operatii de mapat", ca azi).
- **Nu** schimbam fluxul de login/parola; un cont poate avea in continuare mai multe loginuri (`users`),
dar primeste un email canonic de contact pe `accounts` (US-001).
- **Nu** rescriem validarea de continut (`validation.py`); `<input type="date">` produce tot `YYYY-MM-DD`,
acceptat azi.
## 3. Stories atomice
> Fiecare story: cea mai mica unitate care lasa sistemul functional. Backend + UI pentru acelasi
> comportament = 2 stories. Toate rutele web noi sub `require_login`, scoped pe contul din sesiune
> (404 cross-account), CSRF pe toate POST-urile.
>
> **Cerinta transversala (toate story-urile cu UI): responsive obligatoriu.** Fiecare suprafata noua/atinsa
> (US-002..007) se verifica E2E pe **3 viewport-uri: desktop (≥1280px), tableta (7681024px) si mobil
> (≤767px / ~390px)** — fara overflow orizontal (`scrollWidth <= clientWidth`), fara suprapuneri, tinte
> touch ≥44px, modal full-screen pe mobil. US-008 acopera header-ul + cadrul global; fiecare story isi
> verifica propria suprafata pe cele 3 viewport-uri.
### US-001: Backend — companie/email/CUI obligatorii pe cont (`accounts.email` + validari)
**Ca** administrator al gateway-ului **vreau** ca orice cont sa aiba companie, email si CUI **pentru ca**
azi conturile pot exista fara email (CLI/teste) si fara CUI, deci nu pot fi identificate fiscal/contactate.
- **Depinde de**: —
- **Fisiere**: `app/schema.sql` (coloana `accounts.email` + `_migrate` defensiv), `app/accounts.py`
(`create_account` accepta+valideaza `email`; helper `account_is_complete`), `app/web/auth_routes.py`
(signup: CUI devine obligatoriu; scrie `accounts.email`), `tools/account.py` (CLI create cere
`--email` + `--cui`), `tests/test_accounts.py`, `tests/test_signup.py` (~6 fisiere)
- **Test intai (RED)**: `tests/test_accounts.py`
`test_create_account_fara_email_ridica`, `test_create_account_fara_cui_ridica`,
`test_email_normalizat_lowercase_trim`, `test_migrare_adauga_coloana_email_idempotent`,
`test_account_is_complete_false_pe_legacy_incomplet`;
`tests/test_signup.py``test_signup_fara_cui_422`, `test_signup_scrie_email_pe_account`,
`test_signup_cui_existent_mesaj_prietenos` (NU mesajul tehnic cu `activate --account`).
- **Acceptance criteria**:
- [x] Migrare: `accounts.email TEXT` (nullable la nivel de schema pentru conturile legacy), `_migrate`
defensiv idempotent (ca `users.is_admin` la 3.3b). Contul de sistem id=1 ramane fara email (exceptat).
- [x] `create_account(conn, name, cui, email, active)``name`/`cui`/`email` goale → `ValueError`
cu cauza+fix (catalog `errors.py` daca exista cod potrivit); `email` normalizat (trim+lower);
`cui` normalizat (trim+upper, ca azi). CUI duplicat → mesajul existent.
- [x] Signup web: `cui` devine **obligatoriu** (azi optional); la succes scrie `accounts.email = email`-ul
utilizatorului. Lipsa CUI → re-randare formular cu eroare (422), pastrand campurile.
- [x] **CUI duplicat la signup = mesaj prietenos, NU cel tehnic** (decizie user 2026-06-26, optiunea 1):
„Aceasta firma (CUI …) e deja inregistrata. Cere accesul de la administratorul contului." — fara
referinta la CLI `activate --account`. **Model: 1 firma = 1 cont = 1 login**; fluxul de
invitatie/alaturare a unui al doilea email pe aceeasi firma e deferit la TODOS (optiunea 2).
- [x] **Canal de contact concret in mesaj** (T3 gate /autoplan, aprobat 2026-06-26): mesajul include un
email/canal de suport configurabil din settings (ex. `support_email`); daca setarea lipseste,
fallback la formularea de mai sus. Operatorul primeste un pas urmator real, nu doar „cere accesul".
Nu mai lasam mesajul tehnic ridicat de `create_account` sa ajunga verbatim in signup — detectam
CUI duplicat in handler-ul de signup si compunem mesajul prietenos acolo (NU `error=str(exc)`).
- [x] CLI `tools/account.py create` cere `--email` + `--cui` (refuza fara ele); `--with-key` neschimbat.
- [x] `account_is_complete(row)` (companie + email + CUI ne-goale) — helper pur, fara efecte.
- [x] **NU** atinge `users`, `submissions`, worker-ul sau idempotenta.
- **Verificare E2E**: TestClient — signup fara CUI → 422; signup complet → `accounts.email` populat;
`create_account` fara email/cui → ValueError.
### US-002: UI — gate de activare + pagina Cont editeaza companie/email/CUI + banner legacy
**Ca** operator/administrator **vreau** sa vad si sa completez companie/email/CUI **pentru ca**
conturile incomplete (legacy) trebuie aduse la regula fara re-inregistrare.
- **Depinde de**: US-001
- **Fisiere**: `app/web/templates/_cont.html` (sectiune noua „Date firma"), `app/web/routes.py`
(ruta `POST /cont/date-firma` scoped sesiune + CSRF; context `account_meta`+`cont_incomplet`),
`app/web/templates/admin.html` + `app/web/routes.py` (gate activare pe `account_is_complete`),
`app/web/templates/_banner.html` sau `_acasa.html` (banner „Completeaza datele firmei"),
`tests/test_web_cont.py`, `tests/test_admin.py` (~6 fisiere)
- **Test intai (RED)**: `tests/test_web_cont.py`
`test_cont_afiseaza_companie_email_cui`, `test_post_date_firma_actualizeaza`,
`test_post_date_firma_cui_duplicat_eroare`, `test_banner_cont_incomplet_pe_legacy`;
`tests/test_admin.py``test_activare_cont_incomplet_refuzata`.
- **Acceptance criteria**:
- [x] `_cont.html` are o sectiune „Date firma" (deasupra cheii API) cu companie + email + CUI editabile,
prefilled din `accounts`; `POST /cont/date-firma` valideaza (reuse `create_account`-style) + CSRF +
scoped sesiune; eroare pe CUI duplicat / camp gol, mesaj 3-niveluri.
- [x] Banner ne-blocant „Completeaza datele firmei (email/CUI)" pe Acasa cand `account_is_complete` e fals;
dispare dupa completare. NU blocheaza importul/uploadul.
- [x] In panoul admin, butonul **Activeaza** e dezactivat (cu tooltip) pe conturi incomplete —
nu activam la RAR un cont fara identitate completa.
- [x] Fara regresie pe rutele existente din `_cont.html` (cheie API, creds RAR).
- **Verificare E2E**: browser pe `/?tab=cont` — completez email+CUI → banner dispare; admin nu poate
activa un cont incomplet.
### US-003: UI — pasul „Potriveste coloanele" arata antet + prima inregistrare
**Ca** operator **vreau** sa vad numele coloanelor din fisier si valorile primului rand **pentru ca**
sa stiu exact ce date mapez la fiecare camp RAR.
- **Depinde de**: —
- **Fisiere**: `app/web/templates/_mapcoloane.html`, `app/web/routes.py` (`web_upload_import` /
`web_save_mapare_coloane` paseaza deja `sample_rows`; expune `prima_inregistrare`),
`tests/test_web_mapcoloane.py` (~3 fisiere)
- **Test intai (RED)**: `tests/test_web_mapcoloane.py`
`test_mapcoloane_arata_cap_tabel_coloane`, `test_mapcoloane_arata_valori_prima_inregistrare`,
`test_mapcoloane_fara_randuri_degradeaza` (fisier cu antet, fara randuri de date → fara crash).
- **Acceptance criteria**:
- [x] Deasupra (sau langa) randurile de mapare, un mic tabel orizontal cu **un cap de tabel = numele
coloanelor din fisier** si **un rand = valorile primei inregistrari** (truncate la o lungime
rezonabila, `title` pe valoare integrala). Foloseste `.tablewrap` pentru scroll orizontal pe mobil.
- [x] Fiecare coloana din capul de tabel ramane vizual asociata cu select-ul ei de mapare (ex. aceeasi
ordine, sau evidentiere la hover) — operatorul vede „coloana X (valoare „...") → campul canonic Y".
- [x] Fisier fara randuri de date → se arata doar capul de tabel, fara „prima inregistrare" (fara crash).
- [x] Nicio schimbare de backend de parsare/mapare; doar randare (datele exista deja in `sample_rows`).
- **Verificare E2E**: browser pasul 2 — upload `prezentari_test.csv` → vad antetul real + valorile randului 1.
### US-004: UI+backend — un singur „Salveaza" pe „Operatii de mapat la cod RAR"
**Ca** operator **vreau** sa salvez toate maparile de operatii dintr-un singur click **pentru ca**
azi e cate un buton per operatie si trebuie apasat pe fiecare.
- **Depinde de**: —
- **Fisiere**: `app/web/templates/_preview_import.html` (panoul de mapare → un singur `<form>`),
`app/web/routes.py` (ruta noua `POST /_import/{id}/mapare-operatii` plural; pastreaza
`mapare-operatie` singular pentru compat sau o inlocuieste — vezi AC), `tests/test_web_mapare_op.py`
(~3 fisiere)
- **Test intai (RED)**: `tests/test_web_mapare_op.py`
`test_mapare_operatii_salveaza_multiple_intr_un_post`,
`test_mapare_operatii_ignora_randuri_neselectate` (op fara cod ales → nesalvata, nu eroare),
`test_mapare_operatii_re_rezolva_blocatele` (randurile cu cod ales trec din `needs_mapping`).
- **Acceptance criteria**:
- [x] Panoul „Operatii de mapat la cod RAR" devine UN singur `<form>` cu un select per operatie +
**un singur buton „Salveaza maparile"** la final.
- [x] `POST /_import/{id}/mapare-operatii` primeste perechi `(cod_op_service, cod_prestatie)` (liste
paralele), apeleaza `save_mapping` pentru fiecare operatie cu cod ales (reuse exact, fara logica
noua de mapare), apoi **o singura** recompute `_web_compute_preview` + re-randare `#import-section`.
- [x] Operatiile fara cod ales (`— alege cod RAR —`) sunt ignorate (nu produc eroare, nu se salveaza).
- [x] Toggle-ul auto_send NU reapare (eliminat in 5.11).
- [x] CSRF + scoped sesiune + guard batch committed (409) pastrate.
- **Verificare E2E**: browser pasul 3 — aleg coduri pentru toate operatiile, un click pe „Salveaza
maparile" → toate randurile trec din „Cod RAR lipsa", o singura re-randare.
### US-005: Refactor — formular de editare partajat (DRY) intre Trimiteri si preview
**Ca** dezvoltator **vreau** un singur formular de editare de continut **pentru ca** sa nu existe cod
duplicat intre modalul Trimiteri si editarea de preview (sursa bug-urilor inline din 3.6/5.11).
- **Depinde de**: —
- **Fisiere**: `app/web/templates/_form_editare.html` (NOU — partial cu campurile vehicul/data/odo),
`app/web/templates/_trimitere_detaliu.html` (consuma partial-ul), `app/web/templates/_macros.html`
(macro `camp` extins cu `tip='date'`), `tests/test_web_form_editare.py` (~4 fisiere)
- **Test intai (RED)**: `tests/test_web_form_editare.py`
`test_form_editare_are_input_date_pe_data_prestatie`,
`test_trimitere_detaliu_foloseste_form_partajat`,
`test_camp_macro_randeaza_type_date`.
- **Acceptance criteria**:
- [x] Partial `_form_editare.html` randeaza grila responsiva existenta
(`repeat(auto-fit, minmax(200px,1fr))`) cu campurile: `nr_inmatriculare`, `vin`, `data_prestatie`,
`odometru_final`, `odometru_initial`, plus map de erori per-camp (tipar `corectie_errors`).
Parametrizat prin: URL de POST, valorile curente, harta de erori, eticheta butonului primar.
- [x] **`data_prestatie` = `<input type="date">`** (calendar nativ); valoarea ramane `YYYY-MM-DD`.
Daca valoarea curenta nu e `YYYY-MM-DD` valid, inputul degradeaza grijuliu (gol + hint), fara crash.
- [x] `_trimitere_detaliu.html` randeaza acelasi partial in ramura `editabil` — comportamentul modalului
Trimiteri (post `/corecteaza`, select cod RAR pe needs_data/needs_mapping) ramane identic.
- [x] Macro `camp` suporta `tip='date'` fara sa strice apelurile `type='text'` existente.
- **Verificare E2E**: browser — modalul Trimiteri (rand `needs_data`) arata un calendar la Data prestatie;
salvarea+revalidarea functioneaza ca azi.
### US-006: UI — „Editeaza" din preview deschide MODALUL (acelasi formular), nu rand inline
**Ca** operator **vreau** sa editez un rand de preview intr-un modal curat **pentru ca** editarea inline
e rupta vizual si arunca eroare la Anuleaza.
- **Depinde de**: US-005
- **Fisiere**: `app/web/templates/_preview_rand.html` (scoate ramura `editing`/`tr.preview-edit` +
scriptul de mutual-exclusion; butonul „Editeaza" tinteste modalul global), `app/web/routes.py`
(ruta GET fragment editare preview → randeaza `_form_editare.html` in `#detaliu-modal-body`;
POST `/_import/{id}/rand/{i}/editeaza` ramane, dar raspunde cu inchidere modal + OOB pe rand+contoare),
`app/web/templates/_preview_import.html` (foloseste modalul global `#detaliu-modal`),
`tests/test_web_preview_edit.py` (~5 fisiere)
- **Test intai (RED)**: `tests/test_web_preview_edit.py`
`test_editeaza_preview_serveste_fragment_modal` (NU `tr.preview-edit`),
`test_salvare_preview_inchide_modal_si_oob_rand`,
`test_anuleaza_nu_lasa_rand_orfan` (regresie pe eroarea htmx null),
`test_editare_preview_scoped_404_alt_cont`, `test_editare_batch_committed_409`.
- **Acceptance criteria**:
- [x] Butonul „Editeaza" pe rand face `hx-get` catre fragmentul de editare cu `hx-target="#detaliu-modal-body"`
(acelasi mecanism de modal ca la Trimiteri, deschis prin clasa/markup existent in `base.html`).
- [x] Fragmentul randeaza `_form_editare.html` cu POST la `/_import/{id}/rand/{i}/editeaza`,
`hx-target="#detaliu-modal-body"`. La succes: **modalul se inchide** (`HX-Trigger: inchideModal`,
ca la `/corecteaza`) si randul + contoarele se actualizeaza prin **OOB swap** (reuse `include_oob`).
- [x] **Ramura `editing` / `tr.preview-edit` + scriptul inline de mutual-exclusion sunt ELIMINATE** din
`_preview_rand.html` (sursa colapsarii pe verticala + a erorii `htmx-internal-data` la Anuleaza).
- [x] „Anuleaza" = inchiderea modalului (mecanismul global), fara cerere catre `/_import/.../rand/{i}`,
deci fara eroarea JS reprodusa. Test de regresie pe consola curata.
- [x] Mutatie pura pe `override_json` pastrata (ruta neschimbata logic); scoping JOIN→404,
guard committed→409 raman.
- [x] Pe eroare de validare, modalul ramane deschis cu valorile + erorile per-camp (tipar Trimiteri).
- **Verificare E2E**: browser pasul 3 — Editeaza → modal cu calendar + campuri; completez data → Salveaza →
modal se inchide, randul trece pe „Gata de trimis", contoarele cresc; Anuleaza → modal se inchide,
**0 erori in consola**.
### US-007: UI — preview compact + scoaterea coloanei „Verificat?"
**Ca** operator **vreau** o lista de preview compacta si fara coloane neclare **pentru ca** randurile
sunt prea inalte (VIN pe verticala) si nu inteleg bifa „Verificat?".
- **Depinde de**: US-006
- **Fisiere**: `app/schema.sql` (coloana `import_rows.reviewed` + `_migrate` defensiv),
`app/web/templates/_preview_rand.html`, `app/web/templates/_preview_import.html`
(scoate coloana `col-verificat` + logica inline `reviewed_rows` din tabel),
`app/web/templates/_form_editare.html` / fragmentul modal (buton „Confirma valorile" pe `needs_review`),
`app/web/templates/base.html` (latimi `col-*` recalibrate, anti-overflow),
`app/api/v1/import_router.py` + `app/web/routes.py` (citesc `reviewed` in `_resolve_row_for_preview` /
`_web_compute_preview` ca `needs_review`-confirmat → `ok`; gate `n_confirmat` la commit foloseste
`reviewed`, nu bife inline; ruta care seteaza `reviewed=1`), `tests/test_web_preview_compact.py`,
`tests/test_import_review.py` (~7 fisiere)
- **Test intai (RED)**: `tests/test_web_preview_compact.py`
`test_preview_fara_coloana_verificat`,
`test_preview_vin_nu_se_sparge_pe_verticala` (VIN intr-o singura linie / wrap controlat);
`tests/test_import_review.py`
`test_needs_review_exclus_din_gata_pana_la_confirmare`,
`test_confirmare_in_modal_seteaza_reviewed_si_devine_ok`,
`test_reviewed_nu_intra_in_payload_sau_idempotency` (marcaj separat, NU camp de continut),
`test_migrare_adauga_coloana_reviewed_idempotent`,
`test_editare_valoare_pe_needs_review_reseteaza_reviewed` (daca schimbi valoarea, re-cere confirmare).
- **Acceptance criteria**:
- [x] Coloana **„Verificat?" eliminata** din tabelul de preview; antetul si celulele scad la 8 coloane.
- [x] Randuri compacte: VIN nu se mai sparge pe verticala (latime minima pe coloana Vehicul / `white-space`
controlat); fara overflow orizontal la 1280px (`scrollWidth <= clientWidth`); cardurile <768px raman.
- [x] **Decizie inchisa (Q1): confirmare in modal, rand exclus pana confirmi.** Un rand `needs_review`:
- apare cu pill Verifica valori" + motivul concret in Note" (data ambigua / formule Excel / coercion);
- este **exclus din „gata de trimis"** (nu intra in `n_confirmat`) pana cand operatorul il deschide in
modal (US-006) si apasa **Confirma valorile"** (sau il corecteaza), ceea ce seteaza
`import_rows.reviewed=1`; abia atunci randul devine `ok` la recalculul `_resolve_row_for_preview`.
- [x] **Banner discoverability deasupra tabelului** (T1 gate /autoplan, aprobat 2026-06-26): cand exista randuri
`needs_review`, un banner ne-blocant deasupra tabelului explica: Randurile cu <pill>Verifica valori</pill>
nu pleaca la RAR pana le deschizi si confirmi in modal." Fara el, gate-ul mutat din coloana vizibila in
modal devine usor de ratat (operatorul crede ca pill-ul e informativ). Bannerul dispare cand
`summary.needs_review == 0`.
- [x] **Buton explicit „Confirma valorile"** (T2 gate /autoplan, aprobat 2026-06-26): in modal (US-006), randurile
`needs_review` au un buton SEPARAT „Confirma valorile" care seteaza `reviewed=1` — atestare explicita,
distincta de salvarea unei corectii de continut. NU se seteaza `reviewed=1` implicit la orice save
(altfel operatorul ar atesta o valoare ambigua fara intentie). Salvarea unei CORECTII pe un rand deja
confirmat reseteaza `reviewed` (vezi AC urmator).
- [x] **Marcaj separat, nu camp de continut**: `import_rows.reviewed` (nullable/int, migrare defensiva) NU
intra in payload, in `override_json` sau in cheia de idempotenta. Daca utilizatorul **schimba** o
valoare a unui rand deja confirmat, `reviewed` se reseteaza (re-cere confirmare).
- [x] Comitul ramane gate HARD pe `n_confirmat` (niciun rand ambiguu nu pleaca la RAR fara confirmare
umana explicita) — acum derivat din `reviewed`, nu din bife inline `reviewed_rows`.
- [x] Bara de confirmare („Trimite la RAR") si contoarele raman corecte dupa editari/confirmari (OOB),
fara coloana Verificat?.
- [x] Guard committed→409 si scoping JOIN→404 pe ruta de confirmare (acelasi tipar ca `/editeaza`).
- **Verificare E2E**: browser pasul 3 (cu un xlsx cu data ambigua / VIN numeric) — lista compacta, fara
coloana Verificat?, VIN pe o linie; randul `needs_review` ramane exclus din „gata de trimis" pana il
confirm in modal („Confirma valorile") → devine „Gata de trimis", contorul creste.
### US-008: UI — responsive tableta + mobil (header fara suprapuneri + cadru compact/ergonomic)
**Ca** operator pe telefon/tableta **vreau** o interfata compacta, fara articole de header suprapuse
**pentru ca** azi pe mobil arata prost si elementele din header se calca unele pe altele.
- **Depinde de**: — (header/cadru global); coordonat cu US-006/007 pentru modal+preview pe mobil
- **Fisiere**: `app/web/templates/base.html` (header grid + media queries tableta 7681024 + mobil ≤767,
modal full-screen, `.cont-menu`, tinte touch), eventual `app/web/templates/_status.html` /
`_acasa.html` (contoare + nav pe randuri inguste), `tests/test_web_responsive.py` (~3 fisiere)
- **Test intai (RED)**: `tests/test_web_responsive.py`
`test_header_are_breakpoint_tableta` (exista reguli `@media` intre 768 si 1024 pentru header),
`test_header_elemente_nu_au_min_height_fix_pe_mobil`,
`test_modal_full_screen_pe_mobil` (clasa/regula prezenta). (Testele de markup/CSS; pixel-level la E2E.)
- **Acceptance criteria**:
- [x] **Header fara suprapuneri pe tableta (7681024px)**: logo + titlu + badge mediu + comutator tema +
versiune + hamburger se aseaza fara sa se calce (grid/flex care wrap-uieste sau ascunde versiunea/
titlul lung); `min-height:92px` nu forteaza inghesuirea. Pe mobil (≤767px) raman regulile existente,
verificate ca nu se suprapun la ~390px latime.
- [x] **Compact + ergonomic**: spatieri reduse pe mobil, tinte interactive ≥44px (butoane, pill-uri,
linkuri nav, intrari hamburger), fara dublu-scroll; modalul de editare (US-006) e **full-screen**
pe mobil (nu o casuta minuscula).
- [x] **Fara overflow orizontal** pe niciuna din paginile principale (Acasa/import, preview pas 3, Mapari,
Cont, login/signup) la 768px si la ~390px (`scrollWidth <= clientWidth`).
- [x] Contoarele de status + nav-ul „Trimiteri/Mapari" se aseaza pe randuri lizibile pe mobil (fara taiere).
- [x] Light/Dark/Petrol/Auto raman corecte pe toate viewport-urile (fara regresie de tema).
- **Verificare E2E**: browser Playwright cu `browser_resize` la **390×844 (mobil)**, **820×1180 (tableta)**
si **1280×800 (desktop)** — screenshot pe Acasa/import, preview pas 3 (cu modal deschis) si Cont; header
fara suprapuneri pe toate trei; 0 overflow orizontal; tinte touch ok.
## 4. Riscuri
- **R1 — Gate `needs_review` la scoaterea coloanei.** Coloana „Verificat?" era gate-ul HARD prin care
randurile cu valori ambigue (data ambigua, formula Excel) intrau in trimitere doar dupa bifa umana.
Scoaterea ei naiva ar auto-include randuri ambigue (declaratie ireversibila la RAR). Mitigare:
confirmarea se muta in modalul de editare (US-007 AC); `n_confirmat` ramane gate HARD. Vezi Q1.
- **R2 — Refactor formular partajat (US-005) atinge modalul Trimiteri (cale LIVE).** `_trimitere_detaliu.html`
e folosit pentru corectii reale care re-trimit la RAR. Mitigare: US-005 = refactor fara schimbare de
comportament; teste byte-compat pe post `/corecteaza` + regresia existenta verde inainte de US-006/007.
- **R3 — `<input type="date">` si valori ne-`YYYY-MM-DD`.** Fisiere cu data in alt format ajung in editare
ca text ne-valid pentru inputul date (s-ar goli). Mitigare: AC US-005 — degradare grijulie (gol + hint),
fara pierdere tacuta; data ramane editabila si re-validata la salvare.
- **R4 — Migrare `accounts.email`.** Conturi legacy raman cu `email=NULL`. Mitigare: coloana nullable +
`account_is_complete` (banner + gate activare), nu hard-block; contul de sistem id=1 exceptat.
- **R5 — Eroarea htmx `htmx-internal-data`.** Reprodusa la Anuleaza pe editarea inline. Mitigare: US-006
elimina complet ramura inline + scriptul; test de regresie pe consola curata.
- **R6 — Responsive = fisier fierbinte `base.html`.** US-008 atinge header + media queries, fisier partajat
cu alte story-uri (US-007 latimi `col-*`). Mitigare: serializare la lead (NU paralel pe `base.html`);
verificare pixel pe 3 viewport-uri ca breakpoint-ul de tableta nu strica desktop-ul/mobilul existent.
## 5. Intrebari deschise
> Se rezolva cu utilizatorul ÎNAINTE de executie (poarta de aprobare PRD).
- **Q1 (gate `needs_review`) — INCHIS (user, 2026-06-26): confirmare in modal, rand exclus pana confirmi.**
Context — `needs_review` apare cand validarea TRECE dar parsarea fisierului a fost incerta, in 3 cazuri
(sursa: `import_parse.py` + `import_router.py:201-230`), aproape exclusiv la **xlsx** (la CSV nu se
declanseaza — de-aceea coloana e goala in cazul comun):
1. **Data ambigua** — zi ≤12 si format neclar (`05.06` = 5 iun. sau 6 mai?).
2. **Coloana cu formule Excel** fara valori calculate (rata mare de celule goale).
3. **Coercion suspect** la citire xlsx — VIN numeric (pierde zerourile din fata) / odometru ca float.
Decizie: scoatem coloana mereu-prezenta „Verificat?"; randul `needs_review` ramane **exclus din „gata de
trimis"** pana e deschis in modal si **confirmat** („Confirma valorile") sau corectat, persistand
`import_rows.reviewed=1` (marcaj separat, NU camp de continut → nu intra in payload/idempotenta).
Implementat in US-007.
- **Q2 (model cont-email) — INCHIS (user, 2026-06-26): model A** (email canonic pe `accounts`), cu
**1 firma = 1 cont = 1 login**. CUI ramane unic; al doilea email pe acelasi CUI e respins la signup cu
mesaj prietenos (US-001). Fluxul de invitatie/alaturare (mai multi utilizatori per firma) → TODOS.
- **Q3 (CLI legacy `tools/account.py`)**: facem `--email`/`--cui` obligatorii rupe scripturile vechi de
test? Daca da, pastram un flag `--allow-incomplete` doar pentru teste, sau actualizam fixture-urile.
## 6. Valuri de executie (graful de dependente)
```
Val 1 (paralel, fisiere disjuncte):
[US-001] accounts.email + validari companie/email/CUI (schema/accounts/auth_routes/cli)
[US-003] mapcoloane: antet + prima inregistrare (_mapcoloane.html/routes)
[US-004] un singur Salveaza pe operatii (_preview_import.html/routes)
[US-005] formular de editare partajat (DRY) + input date (_form_editare/_trimitere_detaliu/_macros)
Val 2 (deblocate de Val 1):
[US-002] Cont editeaza date firma + gate activare + banner (dep US-001; _cont/admin/routes)
[US-006] Editeaza preview → modal (acelasi formular) (dep US-005; _preview_rand/_preview_import/routes)
Val 3 (deblocat de US-006; ating base.html → serializate):
[US-007] preview compact + scoate „Verificat?" + gate review (dep US-006; _preview_*/base.css/import_router)
[US-008] responsive tableta+mobil + header fara suprapuneri (base.html media queries; coordonat cu US-006/007)
```
Fisiere fierbinti partajate (serializate de lead, NU paralel pe acelasi fisier): `routes.py`
(US-001/002/003/004/006), `_preview_import.html` (US-004/006/007), `_preview_rand.html` (US-006/007),
`base.html` (US-007 latimi `col-*` + US-008 header/media queries — serializate strict intre ele). Vezi ROADMAP §5.5.
---
## Raport VERIFY
> Faza VERIFY rulata de subagent verificator independent (context curat, PRD-only, ROADMAP §5.6),
> 2026-06-26. Lead orchestrare prin agent team (8 teammates Sonnet TDD pe valuri cu fisiere disjuncte;
> `routes.py` si `base.html` serializate ca fisiere fierbinti). Backend trimitere (worker, masina de
> stari de trimitere, idempotenta `build_key`, contract RAR, canal API) NEATINS — confirmat
> `git diff --stat` (app/worker/, app/idempotency.py, app/mapping.py, app/validation.py = 0 modificari).
### Rezultat: PASS (toate 8 stories)
- **Suita**: `python3 -m pytest -q` -> **987 passed, 1 skipped, 0 failed** (baseline 934 -> +53 teste noi).
Live RAR `FINALIZATA` = opt-in indisponibil in mediu (normal, ca la livrabilele anterioare).
- **PASS/FAIL per story** (dovezi cod + teste, verificator independent):
- US-001 accounts email/CUI — PASS (migrare defensiva, create_account valideaza, account_is_complete
id=1 exceptat, signup CUI obligatoriu + mesaj prietenos T3, CLI --email/--cui).
- US-002 Cont date firma + gate activare + banner — PASS.
- US-003 mapcoloane antet + prima inregistrare — PASS (confirmat E2E browser).
- US-004 un singur Salveaza pe operatii — PASS (ruta plurala, D#12 skip invalid).
- US-005 formular editare partajat + input date — PASS (D#5/D#6/D#10).
- US-006 Editeaza preview -> MODAL — PASS (ramura inline eliminata, Anuleaza fara eroare htmx, E2E 0 erori consola).
- US-007 preview compact + gate review in modal — PASS (reviewed marcaj separat, NU in payload/idempotenta;
gate HARD pe ambele canale; T1 banner; T2 buton Confirma; D#9 reset).
- US-008 responsive tableta + mobil — PASS (E2E pe 390/820/1280, header fara suprapuneri, D#13 verificat).
- **Invariante critice**: R2 (submissions neatins dupa editare preview) PASS; reviewed in afara
payload/override/idempotency PASS; migrari idempotente PASS; ramura inline `tr.preview-edit` eliminata PASS.
### VERIFY a gasit 1 FAIL -> remediat TDD, re-confirmat
- FAIL: `signup.html` eticheta CUI „(optional)" + input fara `required` (contrazicea AC US-001 „CUI obligatoriu";
serverul respingea corect 422 dar UI comunica gresit). Reparat TDD (eticheta `*` + `required`),
test de lock `test_signup_html_cui_obligatoriu_ui`.
### Faza CLOSE — `/code-review high` (8 unghiuri prin subagenti, verificare cod first-hand)
3 buguri reale reparate TDD (regresie finala 987 passed):
1. **HIGH**`confirma-review` folosea `hx-swap="none"` -> scriptul `updateN()` din continutul principal nu
se executa -> `n_confirmat` ramanea stale -> „Trimite la RAR" pica pe gate HARD 422 (fluxul confirma->commit
US-007 rupt la prima incercare). Fix: formularul Confirma valorile aliniat la `hx-target="#detaliu-modal-body"`
`hx-swap="innerHTML"` (ca /editeaza).
2. **MEDIUM** — email duplicat la signup arata mesajul gresit „firma e deja inregistrata" (`"deja folosit"`
prindea si `ValueError("email deja folosit")` din `create_user`). Fix: detectie email-dup inaintea CUI-dup,
mesaj specific emailului.
3. **MEDIUM (a11y)** — butonul Editeaza din preview deschidea modalul ocolind `open()` (fara inert/focus-trap/
focus-return). Fix: handler-ul global `htmx:beforeRequest` trateaza si `.btn-editeaza` -> `open()`; JS inline eliminat.
Notat ca debt (neblocant): API preview re-deriva needs_review peste DB `resolved_status` cross-channel (web commit
numara oricum `reviewed=1`); mesaje prietenoase „camp gol" dead-code in cont_date_firma/signup (edge mascat de HTML
required); `zip()` truncheaza la liste POST inegale; `id` cont in mesajul CUI-duplicat; duplicari de cleanup
(context modal, markup banner, N query nomenclator).
### Nedovedit in sesiune
- Live RAR `FINALIZATA` prin `--send` (opt-in, lipsa creds/mediu) — risc minim, backend trimitere NEATINS.
---
## GSTACK REVIEW REPORT (/autoplan, 2026-06-26)
Branch: main · Commit: 283299f · Voci: Claude subagents (CEO/Design/Eng/DX) + verificare cod first-hand.
**Codex = INDISPONIBIL** (usage limit, reset 2026-07-18) -> mod `[subagent-only]` pe toate fazele.
Restore point: vezi comentariul HTML din capul fisierului. Test plan: `~/.gstack/projects/romfast-rar-autopass/main-prd5.12-test-plan-20260626.md`.
### Rezumat
PRD matur: Q1/Q2 inchise de user, Non-Goals clare, graf de valuri, R1-R6. Rutele si fisierele citate exista
toate in cod. Review-ul a confirmat fezabilitatea si a gasit **4 lacune de specificatie reale** (nu blocante,
dar de inchis inainte de executie) + cateva rafinari. Niciun User Challenge (un singur model activ -> nu se
poate forma consens cross-model; recomandarile de mai jos sunt sugestii, nu provocari).
### Decision Audit Trail
| # | Faza | Decizie | Clasificare | Principiu | Rationament | Respins |
|---|------|---------|-------------|-----------|-------------|---------|
| 1 | CEO | NU splitam in 5.12a/5.12b | Taste | P3/P6 | Valurile izoleaza deja US-001/002 (Val1/Val2) pe fisiere disjuncte; split adauga overhead de release fara castig tehnic pt. echipa mica | Split in 2 release-uri (CEO subagent) |
| 2 | CEO | Respins „testeaza worker-ul intai" (F1/F3/F8) | Mechanical | P3 | Non-Goals ingheata explicit worker/contract/idempotenta; conflateaza acest PRD UI cu munca de backend separata | CEO subagent F1/F3/F8 |
| 3 | CEO | First-run E2E smoke -> ramane in TODOS (deja listat) | Mechanical | P3 | Deja deferat din 5.11; recomandat, neblocant | A bloca 5.12 pe el |
| 4 | CEO | needs_review: pastram gate-ul, nu cerem date de utilizare | Mechanical | P1 | Gate-ul e safety-critical (declaratie ireversibila RAR); US-007 muta UI-ul, nu sterge gate-ul | CEO F5 (gather usage data first) |
| 5 | Eng | Partial partajat = DOAR campuri vehicul+data+err/fix; cod_prestatie select + nemapate_inline RAMAN in `_trimitere_detaliu` | Mechanical | P5/P4 | `_trimitere_detaliu` are 2 surse de cod (select + sectiune mapare inline) imposibil de absorbit fara branching fragil | Partial „atotcuprinzator" |
| 6 | Eng | US-005 parametrizeaza si `fix_map` (+ aria-label cu VIN) | Mechanical | P1 | Forma preview are fix-hints + aria-label cu context VIN; lista PRD le omitea -> ar pierde info la extractie | A lasa lista PRD ca atare |
| 7 | Eng | `import_rows.reviewed INTEGER DEFAULT 0` (nu NULL) | Mechanical | P5 | Gate-ul devine `reviewed=0` clar, fara ambiguitate NULL vs 0 | DEFAULT NULL |
| 8 | Eng | Gate commit derivat din DB `reviewed` pe AMBELE canale; API `reviewed_rows` pastrat dar seteaza `reviewed=1` (contract stabil) | Mechanical | P1/P5 | Evita divergenta web/API si pastreaza contractul `/v1/import/.../commit` | A schimba doar web-ul (divergenta tacuta) |
| 9 | Eng | reset `reviewed` la schimbare valoare se implementeaza in calea editeaza/override | Mechanical | P1 | E un AC US-007 fara loc de implementare numit; `apply_row_override` e locul | A-l lasa nespecificat |
| 10 | Design | `<input type=date>` ne-ISO: gol + hint + valoare bruta in hidden, fara pierdere | Taste->auto | P1 | Previne pierderea tacuta de date pe formate Excel; backend deja marcheaza needs_review | A goli pur si simplu inputul |
| 11 | Design | US-003 fisier fara randuri: mesaj explicit „antet fara randuri de date" + blocheaza Continua | Mechanical | P1 | Edge case altfel = esec tacut | Doar „fara crash" |
| 12 | US-004 | Bulk mapping: validare per-item, skip invalid + sumar, restul salvate, 1 re-render | Mechanical | P1 | PRD acopera „fara cod = ignorat" dar nu „cod invalid pe 1 din N" | All-or-nothing |
| 13 | US-008 | Modal full-screen mobil: VERIFICA, nu re-adauga (exista base.html:407) | Mechanical | P4 | Regula deja prezenta la `@media max-width:767px` | A re-implementa |
| 14 | DX | Q3: actualizam fixture-urile via factory in `conftest.py`, FARA `--allow-incomplete` in prod | Taste | P5/P4 | Escape-hatch lasa o veruca in codul de productie; factory centralizat e curat si mai bun pe termen lung | `--allow-incomplete` flag |
| T1 | Design | needs_review: banner persistent deasupra tabelului | Taste -> APROBAT user 2026-06-26 | P1/P5 | Gate-ul mutat in modal devine usor de ratat; bannerul il face explicit | Doar pill+tooltip |
| T2 | Design/Eng | „Confirma valorile" = buton explicit separat (nu implicit pe save) | Taste -> APROBAT user 2026-06-26 | P5 | Atestare explicita pe valori ambigue; evita confirmarea accidentala | Implicit pe orice save |
| T3 | DX | Mesaj CUI duplicat include canal de contact configurabil (fallback la actual) | Taste -> APROBAT user 2026-06-26 | P1 | Operatorul primeste un pas urmator real; detectie in handler signup, nu `str(exc)` | Pastreaza mesajul ca in PRD |
### NOT in scope (confirmat)
- Worker, reconciliere, idempotenta, `build_key`, masina de stari de trimitere, contract RAR (Non-Goals).
- Canal API `POST /v1/prezentari` / `/valideaza` si `mapping.resolve_prestatii` — neschimbate.
- Multi-utilizatori per firma (flux invitatie/alaturare) -> TODOS.
- First-run E2E smoke ca poarta de release -> TODOS (deja deferat din 5.11).
- Split 5.12a/5.12b -> respins (vezi D#1).
### What already exists (de refolosit, nu reconstruit)
- Modal global `#detaliu-modal` + `inchideModal` (`HX-Trigger-After-Settle`, routes.py:1235/1394) — US-006 il refoloseste.
- `include_oob` pentru OOB swap rand+contoare — US-006/007 il refolosesc.
- `save_mapping` / `reresolve_account` — US-004 le refoloseste (fara logica noua de mapare).
- Macro `camp` exista INLINE in ambele forme (`_preview_rand.html:51`, `_trimitere_detaliu.html:98`) — US-005 il EXTRAGE (nu „extinde in `_macros.html`" cum spune lista de fisiere; `_macros.html` are azi doar `autosend_toggle` gol).
- Modal full-screen mobil + tinte touch 44px — deja in base.html (`@media max-width:767px`, liniile 407-427). US-008 = tableta + verificare, nu rescriere.
- `_migrate` defensiv idempotent (tipar `users.is_admin` 3.3b) — US-001/007 il urmeaza.
---
## Faza 1 — CEO (strategie & scope)
CEO DUAL VOICES — CONSENSUS:
```
Dimensiune Claude Codex Consens
------------------------------------- -------- ------- ---------
1. Premise valide? DA* N/A n/a (1 voce)
2. Problema corecta? DA N/A n/a
3. Calibrare scope? DISAGREE N/A -> taste (split?)
4. Alternative explorate suficient? PARTIAL N/A n/a
5. Riscuri competitive acoperite? DA N/A n/a
6. Traiectorie 6 luni sanatoasa? DA N/A n/a
* premise = decizii user deja luate (Q1/Q2 inchise, modal/calendar = „decizie utilizator")
```
**Examinat, nimic blocant pe strategie.** PRD-ul rezolva first-run friction confirmat E2E; scope-ul e calibrat
prin valuri. CEO subagent a recomandat split-ul in 2 release-uri (D#1, respins) si a ridicat findings de „testeaza
worker-ul" care cad in afara Non-Goals (D#2, respinse). Single-critical pastrat: Q3 (backward-compat CLI) — real,
mutat la faza DX/Eng. Dream-state delta: 5.12 inchide first-run UX; ramane (separat) poarta E2E smoke + flux
multi-user firma.
## Faza 2 — Design (UI/UX)
Litmus (Claude design; Codex n/a):
```
Dimensiune Scor Nota
-------------------------------- ----- -------------------------------------------
Ierarhie informatie (mapcoloane) 7/10 US-003 ok; recomandat cap-tabel sticky pe fisiere cu 15+ coloane
Stari (load/empty/error/partial) 6/10 empty-file (US-003) si date ne-ISO (US-005) sub-specificate
Gate needs_review in modal 6/10 LANDMINE: gate HARD mutat dintr-o coloana vizibila intr-un modal
Responsive tableta (US-008) 7/10 breakpoint lipseste azi; spec sa fie pixel-exact, nu aspirational
Interactiune modal (US-006/007) 6/10 „Confirma valorile" = buton separat vs implicit-pe-save (ambiguu)
```
**Issue-uri auto-decise:** D#10 (date ne-ISO), D#11 (empty-file mesaj). **Taste surfaced la gata:** banner
discoverability pe needs_review (T1) + buton „Confirma valorile" explicit (T2).
## Faza 3 — Eng (arhitectura & teste)
ENG DUAL VOICES — CONSENSUS:
```
Dimensiune Claude Codex Consens
--------------------------- -------- ------- ---------
1. Arhitectura sanatoasa? DA(cond) N/A n/a — cond. pe partial corect parametrizat
2. Acoperire teste? PARTIAL N/A n/a — vezi test plan, 4 gap-uri
3. Riscuri performanta? DA N/A n/a — irelevant (UI/CRUD mic)
4. Securitate? DA N/A n/a — CSRF+scoped pastrate pe rute noi
5. Cai de eroare? PARTIAL N/A n/a — bulk mapping partial, date ne-ISO
6. Risc deployment? DA N/A n/a — 2 migrari nullable defensive
```
Diagrama arhitectura (componente noi vs existente):
```
US-001 create_account(+email) -> auth_routes.signup ──┐
accounts.email (migrare) -> tools/account.py CLI ┤ (3 call-sites de actualizat)
account_is_complete ┘
US-005 _form_editare.html (NOU) <── _trimitere_detaliu.html (cod_prestatie select + nemapate_inline RAMAN aici)
└──< _preview_rand.html (US-006: ramura inline ELIMINATA)
US-006 preview „Editeaza" -> #detaliu-modal-body (GET fragment) -> POST /editeaza -> inchideModal + OOB
US-007 import_rows.reviewed (migrare) -> _resolve_row_for_preview -> gate n_confirmat
├── web routes.py /confirma (citeste DB reviewed)
└── API import_router commit_import (reviewed_rows -> seteaza reviewed=1; contract stabil)
US-008 base.html @media (768-1024) NOU + verificare 767 existent
```
**Findings auto-decise:** D#5 (partial scope), D#6 (fix_map), D#7 (reviewed DEFAULT 0), D#8 (gate pe 2 canale),
D#9 (reset reviewed in apply_row_override), D#12 (bulk partial). Test plan scris pe disc (44 codepath-uri,
4 gap-uri marcate). **Invariant critic confirmat (R2):** editarea preview ramane override-only, NU re-queue —
test obligatoriu (#24/#25 in test plan): dupa editare preview, `submissions` neatins.
## Faza 3.5 — DX (CLI + erori + contract API)
DX CONSENSUS (Claude; Codex n/a):
```
Dimensiune Nota
--------------------------------- -------------------------------------------
CLI create --email/--cui Q3 nerezolvat: a face flag-uri obligatorii rupe fixture-urile
Mesaj eroare CUI duplicat prietenos da, dar „cere accesul de la admin" nu spune CUM
Contract API commit reviewed_rows risc de divergenta tacuta -> rezolvat de D#8 + test #39
Migrare fixture-uri recomandat factory in conftest.py (DX gap real)
```
**Auto-decis:** D#14 (Q3 -> factory, fara `--allow-incomplete`). **Taste surfaced:** mesaj CUI duplicat sa includa
un canal de contact concret (T3).
### Cross-Phase Themes
- **Tema A — Gate-ul needs_review (Design + Eng).** Design: mutarea in modal ascunde gate-ul (discoverability).
Eng: gate-ul trebuie sa fie DB-backed pe ambele canale + reset la editare. Semnal high-confidence: tratati
needs_review ca feature de sine statator in US-007, nu ca „stergere de coloana". -> T1 + D#8/D#9.
- **Tema B — Sub-specificarea „Confirma valorile" (Design + Eng).** Ambii: cand se seteaza `reviewed=1`?
Buton separat vs implicit pe save. -> T2.
- **Tema C — Q3 backward-compat (CEO + DX + Eng).** Toate trei: a face email/CUI obligatorii rupe fixture-uri.
-> D#14 (factory).
### Implementation Tasks (aggregate)
_Niciun fisier `tasks-*.jsonl` per faza (autoplan ruleaza review-urile inline, nu skill-urile standalone)._
Task-urile concrete = AC-urile din US-001..008 + cele 14 decizii din audit trail + 4 gap-urile din test plan.
### Status: DONE_WITH_CONCERNS
Concerns (de inchis inainte de executie, niciunul blocant): cele 3 taste decisions de la gate (T1 banner,
T2 buton confirma, T3 contact CUI) + integrarea celor 14 decizii in AC-urile US. Codex indisponibil -> review
single-voice; re-ruleaza dupa 2026-07-18 daca vrei al doilea unghi adversarial.

View File

@@ -0,0 +1,334 @@
<!-- /autoplan restore point: /home/claude/.gstack/projects/romfast-rar-autopass/feat-5.12-5.13-responsive-autoplan-restore-20260627-182914.md -->
# PRD 5.13 — Responsive compact (mobil/tableta) + sistem de butoane + design.md
**Stare**: DRAFT — pentru /autoplan (implementarea NU e facuta inca)
**Data**: 2026-06-27
**Sursa de design**: [docs/design.md](../design.md) (sursa de adevar pentru planurile de design)
> Nota: o sesiune anterioara a explorat o implementare + mockup-uri si a fost REVENITA
> (working tree readus la 5.12). Acest PRD ramane ca specificatie de planificat prin
> `/autoplan`. Sectiunile „Livrabile" / „Raport VERIFY" de mai jos descriu directia
> propusa si dovezile din explorare, NU stare livrata — re-validati prin autoplan.
## Context / problema
PRD 5.12 a marcat „responsive tableta/mobil" ca livrat, dar dogfooding-ul real
(screenshot-uri `localhost_8010_.png`, `step3-preview.png`, `tablet-820.png`,
`mobile-*.png`) a aratat ca paginile arata execrabil pe ecrane mici:
- **P0 break vertical**: in cardurile de Trimiteri pe mobil, eticheta lua `min-width:120px`
fix iar valoarea (nod text intr-un flex) se strangea la ~0 si `word-break:break-word`
o spargea **caracter cu caracter pe verticala** („B 0 7 5 8" pe coloana). Ilizibil.
- **P0 stepper import**: 4 coloane egale cu text + `overflow:hidden` → pe tableta „Confirma
trimiterea" era taiat; pe mobil 4 benzi minuscule cu text pe 3 randuri.
- **P0 tabel preview pe tableta** (7681024px): 8 coloane `table-layout:fixed` cu latimi
fixe storceau vehicul+operatie → text rupt.
- **P1 „afiseaza tot ca pe desktop"**: cardurile aratau toate cele 68 campuri, nu esentialul.
- **P1 butoane exagerate**: `.tabel-card td button { width:100%; min-height:44px }` facea
Salveaza + Sterge **doua blocuri full-width** unul sub altul; butoanele icon-only din
„Mapari salvate" aveau **iconite invizibile** (SVG mic intr-un bloc colorat mare).
- **P2**: mapare coloane cu scroll orizontal pe mobil; versiunea `vX.Y.Z` ocupa spatiu in
header pe mobil; bara sticky de confirmare se rupea necontrolat.
Feedback user pe mockup-uri (2026-06-27):
- Compactarea sa fie **si pe desktop** (ex. wizard-ul mai compact peste tot).
- Pe **desktop** butoanele Salveaza/Sterge = **doar text** (fara iconita).
- Pe **mobil** = iconite, dar un set modern, **recognoscibil** (Lucide stroke), nu cele vechi.
- Nevoie de un **design.md** pe care planurile de design sa-l foloseasca.
## Decizii (confirmate cu user prin AskUserQuestion + mockup-uri)
1. **Directie**: carduri compacte, esential vizibil, butoane mici. (D: „da, dar ajustez")
2. **Tableta (7681024px)**: tabelele actionabile devin **carduri, 2 pe rand** (grid).
3. **Scope**: pachet complet P0+P1+P2 + teste + acest PRD + ROADMAP + design.md.
4. **Butoane**: desktop = text; mobil = iconita patrata 44px, set **Lucide stroke** (contur).
5. **Wizard**: compact **peste tot** (inclusiv desktop): bara slim pe o linie; pe mobil
„Pasul N din 4 · Titlu" + bara de progres.
## Livrabile
### design.md (nou)
Sistemul de design: principii, tokeni (culoare 4 teme, tipografie IBM Plex, spatiere, radius,
elevatie), breakpoints (768/1024), scara de control (44/36/32), componente (butoane, card,
tabel→card, stepper, pill, formulare, modal), accesibilitate, si o sectiune „Pentru planurile
de design". Sursa de adevar; planurile pornesc de aici.
### Cod (doar CSS + markup template; backend NEATINS)
- **`_macros.html`**: macro `icon(name)` (Lucide save/trash/edit/plus, stroke) + `act_btn(label, ic, kind, attrs)`
(buton de actiune responsiv: desktop text / mobil iconita).
- **`base.html`** (CSS, inline):
- Sistem de butoane `.btn-secondary/.btn-ghost/.btn-danger/.btn-sm` + default primar imbunatatit
(font-weight 500, focus-visible).
- Sistem `.act` / `.act-save` / `.act-del` / `.act-group`: desktop = text, mobil = iconita 44px.
- Stepper compact `.stepper*` (track slim desktop/tableta; rezumat + bara progres mobil).
- Card mobil Trimiteri/Preview **rescris**: stivuit compact, vehicul = titlu, stare = pill,
`#`/checkbox ascunse, **fara gap fix de 120px** (fix break vertical).
- **Tableta 7681024px**: `.tabel-trimiteri` + `.tabel-card` → grid 2 carduri/rand.
- Versiune ascunsa pe mobil; bara sticky confirmare compacta; mapare coloane stivuita full-width.
- Coloana actiuni preview 92→104px + `.btn-editeaza { white-space:nowrap }`.
- **`_stepper.html`**: rescris pe clasele compacte (fara stiluri inline inalte).
- **`_mapari.html`**: butoanele icon-only inlocuite cu `act_btn` (salvate + reguli-text + formate-coloane).
### Teste
- `test_web_responsive.py`: aserturile existente pastrate (toate trec).
- `test_web_mapari_actiuni.py`: actualizat de la `.icon-btn` la sistemul `.act` (act-save/act-del,
aria-label pe fiecare, `.act-ic` prezent), docstring marcat „superseda 5.10".
## Invariante respectate
- Breakpoint unic 768px + densitate 1024px; un singur bloc `@media (max-width:767px) {` principal
pe care se bazeaza testele (regulile noi adaugate inauntru, nu in blocuri noi inaintea lui).
- Tabelele dense read-only (Jurnal/Nomenclator/Admin) raman scroll-contained, NU se cardifica.
- Backend trimitere (worker, masina stari, idempotenta, contract RAR, canal API, mapping,
validation) NEATINS. Zero schema. Pur CSS + markup.
## Raport VERIFY (live, app pornit cu DB seedata, Playwright 390/820/1280)
- **Trimiteri mobil 390**: carduri compacte, pill stare + vehicul bold + operatie+cod, fara
break vertical. Header compact, versiune ascunsa. PASS.
- **Trimiteri tableta 820**: grid 2 carduri/rand, fara scroll orizontal. PASS.
- **Trimiteri desktop 1280**: tabel complet neschimbat (fara regresie). PASS.
- **Mapari mobil 390**: Salveaza = iconita discheta albastra, Sterge = iconita cos rosu (Lucide,
patrate 44px, recognoscibile), NU blocuri full-width. PASS.
- **Wizard import**: compact pe o linie pe desktop (✓ Incarca · ✓ Coloane · 3 Verifica · 4 Confirma)
+ ajutor sub; pe mobil „Pasul 3 din 4 · Verifica" + bara progres. PASS.
- **Preview import mobil 390**: carduri compacte per rand + bara confirmare compacta. PASS.
- Regresie pytest: vezi ROADMAP (suita verde).
## Debt notat (neblocant)
- Duplicarea pill stare + `eticheta_problema` pe error/needs_* arata redundant in carduri
(„Eroare / Eroare") — logica de continut in `routes.py`/`labels.py`, nu responsive.
- Filtrele de data (Azi/7zile/30zile/Custom) stivuiesc full-width pe mobil (4 randuri); ar putea
fi grid 2x2 — imbunatatire viitoare.
---
<!-- ============================================================ -->
<!-- GSTACK REVIEW REPORT — /autoplan (subagent-only; codex usage-limited) -->
<!-- ============================================================ -->
# GSTACK REVIEW REPORT
> Pipeline: CEO -> Design -> Eng -> Final Gate (DX skipped: produs end-user, nu unealta de developer).
> Voci: Claude subagent independent pe fiecare faza. **Codex INDISPONIBIL** (usage limit
> OpenAI pana la 2026-07-18) -> toate fazele ruleaza `[subagent-only]`. Consensul nu poate
> fi "CONFIRMED de ambele modele"; o singura voce.
## Faza 1 — CEO (strategie & scope)
### CEO dual voices
- **Codex (CEO):** `[codex-unavailable: usage limit]`.
- **Claude subagent (CEO):** rulat foreground, independent. Findings:
- **F1 (high):** premisa "utilizarea pe mobil e reala" e asumata, nedovedita. Submiterea RAR
e data-entry din xlsx/csv la receptia service-ului = task desktop/tastatura. Wizardul
(upload -> mapare -> preview -> commit) e greu utilizabil pe telefon indiferent de CSS.
Niciun semnal real (analytics/cerere user) citat; doar screenshot-uri ale propriului render urat.
- **F2 (high):** reframe — durerea reala sunt bug-urile de layout de pe DESKTOP (break vertical,
stepper taiat, butoane full-width, iconite invizibile), nu "responsive". Tier 1 = bug-uri +
sistem butoane + design.md (valoare clara, zilnica). Tier 2 = cardificare mobil/tableta (speculativ).
- **F3 (medium):** "CSS inline intr-un singur <style>, no build" e enuntat ca axioma, fara a fi
cantarit vs alternative (Tailwind CDN, Pico.css). Pentru HTMX e o alegere aparabila, dar
netestata/nelintata -> un blob `<style>` in crestere unde o editare de media-query strica tacit alte ecrane.
- **F4 (critical, REZOLVAT partial):** risc de churn — re-implementare din proza. CEO recomanda
recuperarea commit-ului revertit. **Verificat in git reflog: nu exista commit 5.13** (a fost
explorare in working tree, revertita, niciodata commis). Deci nu e nimic de recuperat din git ->
singura cale e re-implementarea din PRD -> PRD-ul TREBUIE facut mai specific (vezi Eng §2/§5).
- **F5 (medium):** scenariul de regret la 6 luni — rewrite CSS multi-template pentru audienta cu
mobil ~zero; `<style>` se dubleaza; un viitor content-change reintroduce clasa de bug "break vertical"
pentru ca nimic structural nu o previne; modul "card pe tableta" nevazut de un user real. Ce NU va
parea prost: design.md + sistemul de butoane.
- **F6 (pozitiv):** design.md e bun (tokeni, ratii de contrast AA, gotcha CSS-var-in-@media). De
pastrat/commis independent de soarta lui 5.13.
### CEO consensus table
```
CEO DUAL VOICES — CONSENSUS TABLE (codex N/A: usage limit)
═══════════════════════════════════════════════════════════════
Dimension Claude Codex Consensus
──────────────────────────────────── ─────── ───── ─────────
1. Premises valid? NO (F1) N/A 1-voce: NU (mobil nedovedit)
2. Right problem to solve? PARTIAL N/A 1-voce: reframe la bug+butoane (F2)
3. Scope calibration correct? NO N/A 1-voce: full P0+P1+P2 supradimensionat
4. Alternatives explored? NO (F3) N/A 1-voce: framework nediscutat
5. Competitive/market risks? N/A N/A irelevant (tool intern B2B)
6. 6-month trajectory sound? PARTIAL N/A 1-voce: design.md+butoane da; cardificare risc
═══════════════════════════════════════════════════════════════
O singura voce -> nimic "CONFIRMED de ambele". Findings critice tratate ca atare oricum.
```
### NOT in scope (confirmat / deferat)
- Backend (worker, masina stari, idempotenta, contract RAR, canal API, mapping, validation): NEATINS. [PRD]
- Tabele dense read-only (Jurnal/Nomenclator/Admin): raman scroll-contained, NU se cardifica. [PRD]
- Refactor `routes.py`/`labels.py` pentru "Eroare/Eroare": deferat (debt). Dar vezi Design §2 — un guard
de template (pill-only cand eticheta == stare) e ieftin si in-scope.
- Adoptarea unui framework CSS: respins (P4 DRY + no-build potrivit stack-ului HTMX), dar de notat explicit ca decizie.
### What already exists (grounding pe cod real, nu pe "Raport VERIFY")
- `docs/design.md` (232 linii): **FACUT**, calitate buna. Deliverabilul "design.md" e in esenta livrat.
- Sistem butoane (`.act`/`.act-save`/`.btn-secondary`/`.btn-ghost`/`.btn-danger`/`.btn-sm`): **NEFACUT** (absent in base.html).
- Macro `icon()` / `act_btn()` + Lucide: **NEFACUT** (absent in `_macros.html`; doar `camp`+`autosend_toggle`).
- Stepper compact: **NEFACUT** (`_stepper.html` inca flex 4-coloane inline = exact anti-patternul P0).
- Grid 2 carduri/rand tableta 768-1024px: **NEFACUT** (blocul @media 1024 doar ascunde `.col-actualizat`).
- Card mobil Trimiteri (fara gap fix 120px): **NEFACUT** (`base.html:410-412` inca `td{display:flex}`+`::before{min-width:120px}`).
- "Raport VERIFY ... PASS" din PRD = din explorarea revertita, NU stare curenta. De NU tratat ca acceptanta.
### Dream-state delta
- CURRENT: 5.12 livrat (modal cont obligatoriu) + fundatie responsive 5.9/5.11; bug-uri P0 inca prezente in tree.
- THIS PLAN: compactare + butoane + cardificare mobil/tableta + design.md (deja partial).
- 12-MONTH IDEAL: un sistem de design tokenizat (design.md) aplicat consecvent, cu teste pe COMPORTAMENT
la breakpoint (nu doar string-match pe clase), astfel incat editarile CSS sa nu mai poata reverti tacit.
Delta: planul nu instituie teste pe comportament; ramane string-match fragil (vezi Eng §2).
### CEO completion summary
Plan corect ca directie de design, dar (a) supra-incadrat ca "responsive" cand miezul de valoare e
fix-bug-desktop + butoane + design.md; (b) premisa "mobil real" nedovedita; (c) re-implementare din
proza fara specificul CSS/test -> risc mare de a reverti din nou. design.md e cel mai durabil activ.
---
## DECIZIE PREMISA (gate user, 2026-06-27)
**Scope: pachet complet FARA grid 2-carduri/rand pe tableta.** Actionable lists (Trimiteri,
Preview, Mapari) raman **o coloana pana la 1024px**. Reverseaza Decizie #2 din PRD.
-> `design.md` §3 si §5.3 trebuie editate la "o coloana pana la 1024px" in acelasi PR.
-> Deliverabilul "grid 2/rand" si testul lui aferent SCOT din scope; se adauga guard ca raman 1 coloana.
## Faza 2 — Design (subagent-only; codex usage-limited)
### Design dual voices
- **Codex (design):** `[codex-unavailable: usage limit]`.
- **Claude subagent (design):** independent. Findings:
- **Meta-hazard (high):** sectiunea "Raport VERIFY ... PASS" se citeste ca raport de finalizare
desi munca e nelivrata -> relabel "Criterii de acceptanta (de dovedit)" sau sterge.
- **Ierarhie card mobil 7/10:** `vehicul=titlu, stare=pill, operatie+cod` corect. RISC pe ce se
ascunde: (a) `actualizat` (timestamp) = singurul semnal "se misca / e blocat?" pe mobil ->
pastreaza o linie meta 12px muted SAU garanteaza in modal si spune-o; (b) `checkbox` ascuns ->
omoara multi-select pe mobil; daca bulk "Trimite la RAR" e workflow real = regresie functionala
tacuta (high). PRD trebuie sa declare explicit daca bulk e desktop-only by design.
- **Stari lipsa (high):** empty/loading/error/partial ale listelor cardificate nespecificate
(fragmente HTMX-swapped). "Eroare/Eroare" (pill+eticheta) e cel mai vizibil exact in cardul nou
-> guard de TEMPLATE ieftin (pill-only cand `eticheta_problema == stare_text`), nu refactor routes.py.
- **Sistem iconite 8/10 (sound):** desktop-text/mobil-icon corect, rezolva "SVG invizibil in bloc".
Gap: (a) `act_btn` TREBUIE sa emita `aria-label={{label}}` in ramura icon-only (invariant de macro);
(b) Sterge fara confirmare pe 44px touch = risc data-loss la mis-tap -> confirm/undo pe `act-del`.
- **Stepper 7/10:** "Pasul N din 4" + bara progres clar. CONTRADICTIE tableta: design.md §3 = tableta
distincta, §5.4 baga "Tableta/mobil" impreuna in forma colapsata, iar PRD linia 5 zice "compact peste
tot". La 820px = track slim sau "Pasul N din 4"? De ales explicit.
- **Specificitate 9/10:** cel mai puternic punct; PRD referentiaza tokenii design.md. Contradictie minora:
stepper inline foloseste `rgba(91,141,239,.10)` hardcodat vs design.md §2.1 (color-mix obligatoriu).
### Design litmus scorecard
```
DESIGN LITMUS (codex N/A: usage limit)
═══════════════════════════════════════════════════════════════
Dimensiune Claude Codex Nota
────────────────────────────────── ────── ───── ──────────────
1. Ierarhie informatie 7/10 N/A risc pe campuri ascunse (timestamp/checkbox)
2. Stari (empty/loading/error) 3/10 N/A nespecificate (high)
3. Sistem iconite/butoane 8/10 N/A aria-label macro + confirm delete
4. Stepper / wizard 7/10 N/A contradictie tableta de rezolvat
5. Specificitate + aliniere md 9/10 N/A 1 culoare hardcodata de scos
6. Tabel->card responsive (2-up scos) N/A o coloana pana la 1024px (decizie user)
7. Accesibilitate (design.md §6) 8/10 N/A solid; lipseste pattern confirm distructiv
═══════════════════════════════════════════════════════════════
```
### Auto-decizii Design (6 principii)
- Guard template "Eroare/Eroare" (pill-only): **INCLUS** (P2 boil-lakes, <1 fisier, in cardul rescris). [TASTE -> gate]
- `act_btn` invariant aria-label in ramura icon-only: **INCLUS** (P1 completeness + a11y obligatoriu). [mecanic]
- Confirm/undo pe `act-del` mobil: **INCLUS** ca cerinta (P1; data-loss). Implementare = confirm nativ simplu, nu modal nou (P5). [TASTE -> gate]
- Linie meta `actualizat` pe card mobil (12px muted, timp relativ): **INCLUS** (P1; semnal "blocat"). [TASTE -> gate]
- Bulk-select pe mobil: **DECLARAT desktop-only by design** (P3 pragmatic; checkbox ascuns pe mobil ramane), de notat in PRD. [TASTE -> gate]
- Relabel "Raport VERIFY" -> "Criterii de acceptanta": **INCLUS** (mecanic, evita falsa finalizare).
- Rezolvare contradictie stepper tableta: **la 768-1024px = forma colapsata "Pasul N din 4"** (P5 explicit, consecvent cu mobil; track slim doar >=1024px). [mecanic]
- Scoatere culoare hardcodata stepper -> `color-mix(var(--accent))`: **INCLUS** (DRY/tokeni design.md §2.1). [mecanic]
## Faza 3 — Eng (subagent-only; codex usage-limited)
### Eng dual voices
- **Codex (eng):** `[codex-unavailable: usage limit]`.
- **Claude subagent (eng):** independent, grounded pe cod real. Findings:
- **§2 Fragilitate teste (CRITICAL):** vezi artefact test-plan. Invariantul PRD ("un singur bloc
@media principal") e FACTUAL GRESIT — exista DOUA blocuri (377, 404); testele feliaza ferestre
fixe `[idx:idx+5000]` de la PRIMA aparitie; rescrierea cardului impinge `min-height:0`/`100vw`
peste fereastra -> `test_header...` + `test_modal...` PIC. Cauza probabila a revert-ului.
FIX OBLIGATORIU INAINTE de CSS: refactor cele 2 teste sa ancoreze pe sentinel + slice pana la EOF.
- **§1 Arhitectura (medium):** `act_btn` randeaza si textul si SVG-ul inline, ascunzand unul per
breakpoint -> fiecare rand de tabel duce `<path>` Lucide chiar pe desktop (bloat DOM/octeti pe
toate viewporturile). Acceptabil (P5 simplu) sau `<use href="#sprite">` definit o data.
- **§3 Edge cases (medium):** VIN 17 car. (`_preview_rand.html:33` nowrap) — verifica sa nu produca
scroll orizontal la 360px dupa scoaterea gap-ului 120px.
- **§4 Teste (high):** `test_web_mapari_actiuni.py` are 3 aserturi pe `.icon-btn` (toate se rup);
lipsesc teste pentru: card mobil fara 120px (P0 fara guard!), `act_btn` aria-label, stepper compact.
Plus (post-decizie): guard ca actionable lists raman 1 coloana pana la 1024px (nu 2-up).
- **§5 Risc re-implementare (high):** PRD da intentie, nu CSS exact/offset-uri; fara fix-ul de teste
re-implementarea reverteaza din nou. `git reflog`: NU exista commit 5.13 de recuperat (explorare necommisa).
- **§6 Complexitate ascunsa:** `_stepper.html` e ~70 linii inline -> mutarea in base.html = CRESTERE
base.html; plaseaz-o DEPARTE de blocul mobil 404 ca sa nu strangi bugetul de octeti al ferestrei.
### Eng consensus table
```
ENG DUAL VOICES — CONSENSUS (codex N/A: usage limit)
═══════════════════════════════════════════════════════════════
Dimensiune Claude Codex Nota
────────────────────────────────── ────── ───── ─────────────────
1. Arhitectura sound? DA* N/A inline-CSS ok; SVG bloat minor
2. Test coverage suficient? NU N/A fragil + lipsuri (high)
3. Riscuri performanta? minor N/A SVG inline pe randuri
4. Securitate? N/A N/A pur CSS/markup, fara suprafata noua
5. Error paths? NU N/A stari card nespecificate (cu Design)
6. Risc deploy/regresie? RIDICAT N/A revert auto daca testele nu se repara intai
═══════════════════════════════════════════════════════════════
```
### Arhitectura (diagrama)
```
base.html (un singur <style>, no build)
├─ :root tokeni (design.md §2) [exista]
├─ .btn / .btn-secondary/.btn-ghost/.btn-danger/.btn-sm [DE ADAUGAT]
├─ .act / .act-save / .act-del / .act-group [DE ADAUGAT]
├─ .stepper* (track slim >=1024; colapsat <1024) [DE ADAUGAT, departe de blocul 404]
├─ @media (min-width:768px)and(max-width:1024px) { ... } [exista; FARA grid 2-up]
├─ @media (max-width:767px) { #377 mic } [exista — sursa fragilitatii]
└─ @media (max-width:767px) { #404 PRINCIPAL } [card rescris AICI, dupa header/modal]
_macros.html : icon(name) + act_btn(label,ic,kind,attrs) [DE ADAUGAT; aria-label invariant]
_stepper.html: rescris pe .stepper* (fara inline inalt) [DE RESCRIS]
_mapari.html : .icon-btn -> act_btn [DE MIGRAT]
TESTE : refactor ferestre fixe INAINTE de CSS [BLOCANT]
```
### Auto-decizii Eng (6 principii)
- Refactor cele 2 teste fragile INAINTE de CSS: **OBLIGATORIU, BLOCANT** (P1+P5; altfel revert garantat). [mecanic]
- Corectare invariant PRD "un singur bloc @media": **CORECTAT** (sunt doua; regula reala = adauga dupa header/modal + slice EOF). [mecanic]
- SVG inline vs `<use>` sprite: **inline acum** (P5 explicit/simplu); sprite notat ca optimizare. [TASTE -> gate]
- Teste noi (#2 card fara 120px, #3 act_btn aria, #4 stepper, #5 window-guard, +1-coloana guard): **INCLUSE** (P1). [mecanic]
- "Aserturile existente pastrate (toate trec)" din PRD: **CORECTAT** la "refactor + pastreaza intentia" (imposibil ca atare). [mecanic]
## Decision Audit Trail
| # | Faza | Decizie | Clasif. | Principiu | Rationament |
|---|------|---------|---------|-----------|-------------|
| 1 | CEO | Scope: complet FARA grid 2/rand tableta | GATE user | - | user a ales la premise gate; ambele voci design contestau 2-up |
| 2 | CEO | Backend NEATINS, pur CSS/markup | mecanic | P4 | confirmat de PRD; zero schema |
| 3 | CEO | Fara framework CSS (inline ramane) | mecanic | P4/P5 | no-build potrivit HTMX; notat ca decizie |
| 4 | CEO | Commit revertit de recuperat? NU exista | mecanic | - | git reflog: explorare necommisa; re-impl din PRD |
| 5 | Design | Guard template "Eroare/Eroare" pill-only | taste | P2 | in cardul rescris, <1 fisier |
| 6 | Design | act_btn aria-label invariant icon-only | mecanic | P1 | a11y obligatoriu design.md §6 |
| 7 | Design | Confirm/undo pe act-del mobil | taste | P1 | data-loss la mis-tap 44px |
| 8 | Design | Linie meta `actualizat` pe card mobil | taste | P1 | singurul semnal "blocat" pe mobil |
| 9 | Design | Bulk-select = desktop-only by design | taste | P3 | checkbox ascuns pe mobil; de declarat |
| 10 | Design | Relabel "Raport VERIFY" -> "acceptanta" | mecanic | - | evita falsa finalizare |
| 11 | Design | Stepper 768-1024 = forma colapsata | mecanic | P5 | consecvent cu mobil; track slim >=1024 |
| 12 | Design | Stepper color-mix in loc de hardcodat | mecanic | P4 | design.md §2.1 tokeni |
| 13 | Eng | Refactor teste fragile INAINTE de CSS | mecanic | P1/P5 | BLOCANT; cauza probabila revert |
| 14 | Eng | SVG inline acum (sprite deferat) | taste | P5 | simplu > optimizare prematura |
| 15 | Eng | 5 teste noi + migrare mapari | mecanic | P1 | acopera P0 + a11y + scope guard |
| 16 | Eng | design.md §3/§5.3 -> "1 coloana <=1024" | mecanic | - | consecventa cu decizia de scope |
## Cross-Phase Themes
- **"Raport VERIFY se citeste ca facut, dar nu e"** — semnalat de Design (meta-hazard) SI implicit de
Eng (§5 re-impl). Semnal high-confidence: relabel + grounding pe cod real, nu pe raport.
- **Stari/erori nespecificate** — Design §2 (stari card) + Eng §4 (error paths). De adaugat o matrice de stari.
- **Fragilitate -> revert** — Eng §2 (teste) + CEO F5 (nimic structural nu previne re-bug). Repara testele intai.
## Eng completion summary
Directia de design e fina; planul livreaza DOAR daca fragilitatea testelor (§2) e rezolvata INTAI
(refactor cele 2 teste pe ancora+EOF), altfel rescrierea cardului mobil reverteaza singura din nou.
Invariantul PRD despre "un singur bloc @media" e gresit si trebuie corectat. design.md ramane activul durabil.