# PRD 5.8 — UX tabel trimiteri (detaliu inline, fara scroll, cod RAR) + reguli mapare pe text

**Stare**: verify-pass

> Proces: `docs/ROADMAP.md` §5. Contract RAR (sursa de adevar): `docs/api-rar-contract.md`.
> Starea trece: `draft → aprobat → in-executie → verify-pass → inchis`.

## 1. Obiectiv

Patru imbunatatiri UX, fara schimbarea fluxului de trimitere:
1. Panoul de detaliu al unei trimiteri apare **sub randul selectat** (rand expandabil), nu la
   baza tabelului (azi `#trimitere-detaliu` traieste dupa `<table>` in `_coada.html:72`).
2. Tabelul de trimiteri **nu mai are scroll orizontal** (confirmat live: `.tablewrap`
   `scrollWidth=1189 > clientWidth=1010` la 1280px). Solutie agreata: mut **Motiv** in detaliu,
   stiveasc **cod RAR sub codul operatiei** in coloana Operatie, **VIN sub nr. inmatriculare**
   (deja stivuit) si **scurtez etichetele de Stare**.
3. In coloana Operatie apare si **codul de operatie RAR rezolvat** (`cod_prestatie`), nu doar
   codul/denumirea venita din API/import.
4. Pagina **Mapari** capata **reguli automate pe text** (substring): ex. operatie care *contine*
   „verificare" → cod RAR `OE-2`. Se aplica la ingestie si la re-rezolvarea blocajelor.

## 2. Non-Goals (anti scope-creep)

- Fara `match_type` multiplu (starts_with / regex). Doar **contains** (substring), insensibil la
  diacritice si majuscule. Daca apare nevoia, e alt PRD.
- Fara modificari la masina de stari, la worker, la contractul RAR sau la idempotenta.
- Fara schimbarea logicii de mapare exacta `cod_op_service → cod_prestatie` (ramane, are precedenta).
- Fara redesign general al dashboard-ului; doar tabelul de trimiteri + sectiunea noua din Mapari.
- Fara reguli text per-import sau globale: regulile sunt **per cont** (ca `operations_mapping`).

## 3. Stories atomice

> Backend + UI pentru acelasi comportament = stories separate. Regulile pe text (US-001..004) si
> UX-ul tabelului (US-005..008) sunt **independente** — se pot livra in paralel.

---

### US-001: Schema + persistenta reguli text de mapare
**Ca** dezvoltator **vreau** o tabela `operation_text_rules` **pentru ca** regulile pe substring
sa fie durabile per cont, langa `operations_mapping`.

- **Depinde de**: —
- **Fisiere**: `app/schema.sql`, `app/mapping.py`, `tests/test_mapping_text_rules.py`
- **Test intai (RED)**: `tests/test_mapping_text_rules.py` —
  `test_save_text_rule_persista`, `test_load_text_rules_per_cont`,
  `test_delete_text_rule`, `test_unic_per_cont_pattern`
- **Acceptance criteria**:
  - [x] Tabela noua: `id, account_id (NOT NULL, FK accounts ON DELETE CASCADE), pattern TEXT NOT NULL,
        cod_prestatie TEXT NOT NULL, auto_send INTEGER NOT NULL DEFAULT 0, priority INTEGER NOT NULL
        DEFAULT 0, created_at`, cu `UNIQUE(account_id, pattern)`.
  - [x] **`auto_send` DEFAULT 0** (decizie CEO 2026-06-24, siguranta): o regula pe substring are blast
        radius mai mare decat o mapare exacta (potriveste si operatii viitoare nevazute), iar
        `FINALIZATA` e ireversibil la RAR. O regula noua rezolva codul dar TINE randul pentru verificare
        umana (`needs_mapping`) pana cand operatorul activeaza explicit „In coada". UI-ul (US-004) reflecta
        toggle-ul pe 0 implicit.
  - [x] `load_text_rules(conn, account_id)` intoarce lista ordonata `[{pattern, cod_prestatie,
        auto_send, priority}]` (ordine: `priority ASC, id ASC`), aplicand `account_or_default`.
  - [x] `save_text_rule(conn, account_id, pattern, cod_prestatie, auto_send)` face upsert pe
        `(account_id, pattern)`; `delete_text_rule(...)` sterge.
  - [x] Schema e idempotenta (`CREATE TABLE IF NOT EXISTS`) si nu strica DB-uri existente.
  - [x] `python3 -m pytest tests/test_mapping_text_rules.py -q` trece.
- **Verificare E2E**: —

### US-002: `resolve_prestatii` aplica reguli text (substring, dupa maparea exacta)
**Ca** integrator **vreau** ca o operatie nemapata sa primeasca cod RAR din prima regula text care
da match **pentru ca** sa nu mai intre in editor degeaba.

- **Depinde de**: US-001
- **Fisiere**: `app/mapping.py`, `tests/test_mapping.py`
- **Test intai (RED)**: `tests/test_mapping.py` —
  `test_regula_text_contains_rezolva`, `test_mapare_exacta_bate_regula_text`,
  `test_regula_text_insensibila_diacritice_caz`, `test_regula_text_cod_invalid_in_nomenclator_ramane_nemapat`,
  `test_prima_regula_dupa_priority_castiga`
- **Acceptance criteria**:
  - [x] `resolve_prestatii(prestatii, mapping, valid_codes=None, text_rules=None)` — semnatura
        **aditiva** (param nou optional, default `None` = comportament actual neschimbat).
  - [x] **Precedenta** stricta: `cod_prestatie` direct valid > mapare exacta `cod_op_service` >
        **reguli text** > nemapat. Regula text se incearca DOAR cand nu exista cod valid si op nu e
        in `mapping`.
  - [x] Match = substring pe textul operatiei (`denumire` daca exista, altfel `cod_op_service`),
        normalizat cu `normalize_for_match` (fara diacritice, uppercase, spatii colapsate) — pe
        ambele parti (pattern si text).
  - [x] Daca regula da match dar `cod_prestatie`-ul ei **nu** e in `valid_codes` → operatia ramane
        **nemapata** (nu trimitem cod invalid; coerent cu regula „RAR accepta doar coduri din
        nomenclator", `mapping.py:101-105`).
  - [x] La match multiplu castiga prima dupa ordinea `load_text_rules` (priority, id).
  - [x] `python3 -m pytest tests/test_mapping.py -q` trece (inclusiv testele vechi).
- **Verificare E2E**: —

### US-003: Reguli text active la ingestie + la re-rezolvare
**Ca** service **vreau** ca regulile text sa actioneze pe prezentari noi (API + import) **si** sa
deblocheze randurile `needs_mapping` existente cand salvez o regula noua.

- **Depinde de**: US-002
- **Fisiere**: `app/mapping.py` (`classify_prezentare` + `reresolve_account`), `app/api/v1/router.py`,
  `app/api/v1/import_router.py`, `app/web/routes.py`, `tests/test_reresolve_text_rules.py`
- **Test intai (RED)**: `tests/test_reresolve_text_rules.py` —
  `test_ingestie_api_aplica_regula_text`, `test_ingestie_import_aplica_regula_text`,
  `test_corectie_web_aplica_regula_text`, `test_salvare_regula_rerezolva_blocate`
- **Acceptance criteria**:
  - [x] **TOATE cele 6 apeluri `resolve_prestatii` primesc `text_rules` SI `valid_codes`** (decizie
        eng D-eng-1): `mapping.py:276` (in `classify_prezentare`), `mapping.py:441` (reresolve_account),
        `import_router.py:204`, `import_router.py:1079`, `routes.py:984` (corectie web `needs_data`),
        `routes.py:2278` (import web). Cele 4 care azi paseaza `valid_codes=None` incep sa paseze
        `valid_codes=load_nomenclator_codes(conn) or None` — altfel AC-ul de validare (US-002) nu se
        onoreaza pe acele cai.
  - [x] **`classify_prezentare` capata param `text_rules`** (A3): seam-ul partajat API + `/valideaza`
        (invariant 5.2) il primeste si ambii apelanti (`create_prezentari` + ruta `/valideaza`) fac
        `load_text_rules(...)` si il paseaza — altfel dry-run-ul diverge de trimiterea reala.
  - [x] **`text_rules`/`valid_codes` se incarca o data per cerere/batch, NU per rand** (T2):
        `routes.py:2278` (loop import) si `reresolve_account` (loop randuri) le incarca inainte de bucla.
  - [x] O prezentare API/import a carei operatie da match pe o regula intra `queued` (sau respectand
        `auto_send`), NU `needs_mapping`.
  - [x] La salvarea unei reguli noi, `reresolve_account` re-evalueaza blocajele si deblocheaza
        randurile care acum dau match (acelasi mecanism ca la `save_mapping`).
  - [x] `cod_prestatie`-ul rezolvat din regula respecta validarea fata de nomenclator (US-002).
  - [x] `python3 -m pytest -q` trece integral.
- **Verificare E2E**: `POST /v1/prezentari` cu operatie „Verificare X" (fara mapare exacta), regula
  `contine "verificare" → OE-2` salvata in prealabil → raspuns cu `status=queued`, fara `nemapate`.

### US-004: UI Mapari — sectiune „Reguli automate (text)"
**Ca** operator **vreau** sa adaug/sterg reguli text din pagina Mapari **pentru ca** sa automatizez
maparea fara cod intern per operatie.

- **Depinde de**: US-001 (UI poate merge in paralel cu US-002/003; rezolvarea efectiva cere US-003)
- **Fisiere**: `app/web/routes.py`, `app/web/templates/_mapari.html`,
  `tests/test_web_mapari_text_rules.py`
- **Test intai (RED)**: `tests/test_web_mapari_text_rules.py` —
  `test_post_regula_text_salveaza_si_rerezolva`, `test_post_sterge_regula`,
  `test_regula_text_scoped_pe_cont_sesiune`, `test_csrf_necesar`
- **Acceptance criteria**:
  - [x] Sectiune noua in `_mapari.html` (a 4-a, langa „De rezolvat" / „Mapari salvate" / „Formate"):
        tabel cu coloanele **Daca operatia contine** (text) | **Cod RAR** (select din nomenclator) |
        **In coada** (toggle `auto_send`, **implicit OFF** — vezi US-001 decizia CEO) | **Actiuni**
        (sterge), plus un rand de adaugare.
  - [x] Formularul foloseste `auto_send`-toggle si `select` din nomenclator existente (reuse macro),
        cu `csrf_token`; rutele `POST /mapari/reguli-text` si `POST /mapari/reguli-text/sterge`
        sunt **account-scoped pe sesiune** (`require_login`), ca rutele Mapari actuale.
  - [x] La salvare se cheama `save_text_rule` + `reresolve_account`, cu mesaj „Regula salvata.
        Deblocate: N" si trigger `trimiteriChanged` (refresh lista), exact ca maparea inline (5.7).
  - [x] Textul afisat e clar ca match-ul e „contine" (substring), nu egalitate.
  - [x] **Empty state** (nicio regula): o linie explicativa cu exemplu — „Inca nu ai reguli.
        Ex: operatia contine «verificare» → OE-2. Mapeaza automat operatii similare fara cod
        intern." — urmata de randul de adaugare gata afisat ca placeholder. (Pass 2)
  - [x] **Stari**: succes = mesaj „Regula salvata. Deblocate: N"; eroare cod invalid in
        nomenclator = mesaj inline „Cod RAR necunoscut in nomenclator", fara salvare. (Pass 2)
  - [x] `python3 -m pytest tests/test_web_mapari_text_rules.py -q` trece.
- **Verificare E2E**: gstack browser pe `/?tab=mapari` — adaug regula `verificare → OE-2`,
  confirm ca apare in lista si ca un rand `De rezolvat` cu „verificare" dispare dupa salvare.

---

### US-005: `cod_rar` distinct in datele de afisare ale randului
**Ca** dezvoltator **vreau** un camp separat pentru codul RAR rezolvat **pentru ca** sa-l pot afisa
sub codul operatiei fara sa-l confund cu `cod_op_service`.

- **Depinde de**: —
- **Fisiere**: `app/payload_view.py`, `tests/test_payload_view.py`
- **Test intai (RED)**: `tests/test_payload_view.py` —
  `test_cod_rar_prezent_cand_mapat`, `test_cod_rar_gol_cand_nemapat`,
  `test_operatie_ramane_denumire_sau_op`
- **Acceptance criteria**:
  - [x] `prezentare_din_payload` intoarce in plus `cod_rar` = `cod_prestatie` (uppercase, strip
        „.0") sau `EMPTY` cand lipseste. `operatie` si `cod` raman neschimbate (compat).
  - [x] Cand operatia e nemapata (`cod_prestatie` None), `cod_rar` == `EMPTY` (nu cade pe
        `cod_op_service`).
  - [x] `python3 -m pytest tests/test_payload_view.py -q` trece.
- **Verificare E2E**: —

### US-006: Etichete de Stare compacte (pill)
**Ca** operator **vreau** etichete scurte in coloana Stare **pentru ca** randul sa fie ingust si
lizibil.

- **Depinde de**: —
- **Fisiere**: `app/web/labels.py`, `tests/test_labels.py`
- **Test intai (RED)**: `tests/test_labels.py` —
  `test_eticheta_scurta_pentru_fiecare_stare`, `test_eticheta_lunga_ramane_pentru_subtext`
- **Acceptance criteria**:
  - [x] Eticheta scurta expusa ca **functie noua separata `eticheta_scurta(status) -> str`** (dict
        propriu + garda `KeyError`), NU ca al 4-lea element in tuple-ul `Eticheta` (3 elemente azi,
        despachetat in template-uri — l-ar rupe). `eticheta_stare` ramane neatins (text lung pentru
        subtext/tooltip in detaliu). Set propus:
        `queued→"In coada"`, `sending→"Se trimite"`, `sent→"Finalizat"`,
        `needs_mapping→"De mapat"`, `needs_data→"Date lipsa"`, `error→"Eroare"`.
  - [x] Pill-ul din tabel foloseste eticheta scurta; clasele CSS `s-*` (culorile) raman.
  - [x] Stare neacoperita ridica `KeyError` (ca azi, ca sa prinda stari noi).
  - [x] `python3 -m pytest tests/test_labels.py -q` trece.
- **Verificare E2E**: —

### US-007: Tabel trimiteri fara scroll orizontal (coloane re-aranjate)
**Ca** operator **vreau** sa vad tot randul fara scroll lateral **pentru ca** sa citesc starea si
operatia dintr-o privire.

- **Depinde de**: US-005, US-006
- **Fisiere**: `app/web/templates/_submissions.html`, `app/web/routes.py` (builder rand),
  `app/web/templates/base.html` (CSS), `tests/test_web_submissions.py`
- **Test intai (RED)**: `tests/test_web_submissions.py` —
  `test_tabel_nu_are_coloana_motiv`, `test_operatie_contine_cod_rar`,
  `test_pill_eticheta_scurta`
- **Acceptance criteria**:
  - [x] Coloana **Motiv** eliminata din `<thead>`/`<tbody>` (continutul ei se vede in detaliu —
        `_trimitere_detaliu.html:36-39` deja afiseaza Motiv).
  - [x] Coloana **Operatie**: linia 1 = `prez.operatie` (denumire/cod API), linia 2 = `prez.cod_rar`
        muted (`cod RAR: XXX`); cand nemapat afiseaza „nemapat" muted.
  - [x] Coloana **Vehicul**: nr. inmatriculare + VIN scurt stivuit (deja exista, pastrat).
  - [x] Pill Stare = eticheta scurta (US-006).
  - [x] CSS: la 1280px `.tablewrap` **nu** mai depaseste (`scrollWidth <= clientWidth`); se permite
        `white-space: normal` controlat pe coloanele text si latimi rezonabile, fara a rupe alte
        tabele care folosesc `.tablewrap` (scopare prin clasa, ex. `.tabel-trimiteri`).
  - [x] **Pill compact pastreaza info lunga**: eticheta scurta (US-006) e in pill; textul lung
        (subtext din `eticheta_stare`) ramane disponibil ca `title=` (tooltip) pe pill si in
        panoul de detaliu — nu se pierde informatie. (Pass 5)
  - [x] **Responsive** (Pass 6): `>=1024px` toate cele 8 coloane; `768-1024px` ascund coloana
        **Actualizat** (e in detaliu) → 7 coloane fara scroll; `<768px` randurile devin **carduri**
        (eticheta:valoare stivuit, pill + chevron sus), nu tabel — fara scroll orizontal.
  - [x] Secundarul „cod RAR" / „nemapat" e >=12px cu contrast >=4.5:1 (coerent cu `vin_scurt`
        existent); nu se transmite stare DOAR prin culoare (textul „nemapat" o spune). (Pass 6)
  - [x] `python3 -m pytest tests/test_web_submissions.py -q` trece.
- **Verificare E2E**: gstack browser pe `/` la 1280px, 1024px, 900px si 375px — `scrollWidth <=
  clientWidth` la fiecare (sau carduri sub 768px); coloana Operatie arata ambele coduri.

### US-008: Detaliu trimitere ca rand expandabil sub randul selectat
**Ca** operator **vreau** ca detaliul sa apara imediat sub randul pe care l-am dat click **pentru
ca** sa nu mai caut la baza tabelului.

- **Depinde de**: US-007
- **Fisiere**: `app/web/templates/_submissions.html`, `app/web/templates/_coada.html`,
  `app/web/templates/_trimitere_detaliu.html`, `app/web/routes.py`,
  `tests/test_web_detaliu_inline.py`
- **Test intai (RED)**: `tests/test_web_detaliu_inline.py` —
  `test_fragment_detaliu_se_randeaza_in_container_pe_rand`,
  `test_un_singur_detaliu_deschis`
- **Acceptance criteria**:
  - [x] Fiecare rand are un rand-sibling de detaliu `<tr class="detaliu-rand">` cu
        `<td colspan=N><div id="detaliu-{{r.id}}"></div></td>`, ascuns implicit.
  - [x] Click pe rand face `hx-get="/_fragments/trimitere/{id}"` cu `hx-target="#detaliu-{id}"`,
        `hx-swap="innerHTML"`; la deschidere se inchid celelalte detalii deschise (un singur rand
        expandat o data).
  - [x] **Indicator vizual** (Pass 1/4): chevron `▸`/`▾` la inceputul randului (rotit cand e
        deschis) + randul deschis primeste **fundal evidentiat** (`#1d212b` dark / echivalent light).
        Detaliul se leaga prin **fundal subtil + `border-top`** (stilul existent al sectiunii de
        mapare inline `_trimitere_detaliu.html`), NU `border-left` accent (evita pattern AI-slop).
  - [x] **Stare loading** (Pass 2): la click, pana raspunde HTMX, containerul randului arata un
        placeholder discret („Se incarca…") prin `hx-indicator`, ca operatorul sa vada ca s-a
        inregistrat clicul.
  - [x] **Accesibilitate** (Pass 6): randul clickabil e focusabil la tastatura (`tabindex="0"`,
        `role="button"`, `aria-expanded` sincronizat); Enter/Space deschid/inchid; tinta de atins
        (chevron/checkbox) >=44px pe touch.
  - [x] Butonul „Inchide" din `_trimitere_detaliu.html` goleste containerul randului curent (nu mai
        tinteste `#trimitere-detaliu` global) si readuce focusul pe randul declansator.
  - [x] `#trimitere-detaliu` global din `_coada.html:72` este eliminat sau golit de rol.
  - [x] **Poll-ul de refresh (15s, `_coada.html`) se pune pe pauza cat timp un rand e expandat** si
        se reia la inchidere (decizie eng D-eng-2). Fara flicker, fara pierdere de scroll/focus in
        timpul citirii; lista nu se misca sub operator. (NU re-fetch dupa swap, NU excludere de rand.)
  - [x] `python3 -m pytest tests/test_web_detaliu_inline.py -q` trece.
- **Verificare E2E**: gstack browser pe `/` — click pe randul #N: detaliul apare imediat sub el
  (`detaliuTop` intre `rowTop` si randul urmator), nu la baza; click pe alt rand muta detaliul.

---

> **Stories de expansiune (CEO review 2026-06-24, SELECTIVE EXPANSION).** US-009..011 acceptate in scope.
> Cresc increderea si observabilitatea regulilor text; nu schimba fluxul de trimitere. Livrabile in Val 4.

### US-009: Preview pre-salvare regula text (cate operatii potriveste)
**Ca** operator **vreau** sa vad cate operatii potriveste o regula INAINTE sa o salvez **pentru ca**
sa nu salvez un pattern prea lacom (perechea naturala a `auto_send=0`).

- **Depinde de**: US-001 (reuse agregarea `pending_unmapped`); independent de US-002/003
- **Fisiere**: `app/web/routes.py`, `app/web/templates/_mapari.html`, `tests/test_web_mapari_preview_regula.py`
- **Test intai (RED)**: `tests/test_web_mapari_preview_regula.py` —
  `test_preview_numara_potriviri`, `test_preview_intoarce_exemple`, `test_preview_pattern_gol`,
  `test_preview_scoped_pe_cont`
- **Acceptance criteria**:
  - [x] Ruta `POST /mapari/reguli-text/preview` (account-scoped pe sesiune, CSRF) primeste `pattern`,
        normalizeaza cu `normalize_for_match`, numara operatiile distincte nemapate ale contului
        (`needs_mapping`, reuse `pending_unmapped`) al caror text **contine** pattern-ul + intoarce
        pana la 3 exemple `{cod_op_service, denumire}`. **NU salveaza nimic**.
  - [x] Fragment HTMX afisat sub randul de adaugare la schimbarea pattern-ului (`hx-trigger="keyup
        delay:400ms"`): „Potriveste N operatii nemapate: «...», «...»" sau „Nicio potrivire acum".
  - [x] Pattern gol → fara apel/fragment gol (nu numara „tot").
  - [x] `python3 -m pytest tests/test_web_mapari_preview_regula.py -q` trece.
- **Verificare E2E**: gstack browser pe `/?tab=mapari` — tastez „verificare" in pattern → vad „Potriveste
  N operatii: ..." inainte de Salveaza.

### US-010: Telemetrie hit regula text in `app_events`
**Ca** operator/admin **vreau** sa stiu ce regula a rezolvat ce submission **pentru ca** sa pot
raspunde la „de ce a primit randul asta codul OE-2?".

- **Depinde de**: US-002 (rezolvare) + US-003 (active la ingestie)
- **Fisiere**: `app/mapping.py` (adnotare item rezolvat-prin-regula), `app/observ.py` (reuse `log_event`),
  apelantii cu `conn` (`create_prezentari`, `reresolve_account`, import), `tests/test_text_rule_telemetry.py`
- **Test intai (RED)**: `tests/test_text_rule_telemetry.py` —
  `test_hit_regula_emite_app_event`, `test_mapare_exacta_nu_emite_text_rule_hit`,
  `test_event_redactat_si_scoped_pe_cont`
- **Acceptance criteria**:
  - [x] `resolve_prestatii` adnoteaza itemul rezolvat-prin-regula cu pattern-ul sursa (camp aditiv pe dict,
        ex. `cod_sursa="text_rule:<pattern>"`; payload-harmless — RAR citeste doar `cod_prestatie`).
  - [x] Apelantii cu `conn` emit `log_event("text_rule_hit", {...})` in `app_events` cu
        `{submission_id, account_id, pattern, cod_prestatie}`, redactat prin `app/security` (fara PII).
        Maparea exacta NU emite `text_rule_hit`.
  - [x] Vizibil in tab-ul „Jurnal" (5.6), scoped pe cont pentru non-admin (mecanismul existent).
  - [x] `python3 -m pytest tests/test_text_rule_telemetry.py -q` trece.
- **Verificare E2E**: `POST /v1/prezentari` cu operatie ce da match pe regula → eveniment `text_rule_hit`
  vizibil in Jurnal cu pattern-ul si codul.

### US-011: Avertizare overlap intre reguli text (neblocant)
**Ca** operator **vreau** un avertisment cand o regula noua se suprapune cu una existenta **pentru ca**
sa inteleg de ce o regula „castiga" inaintea alteia.

- **Depinde de**: US-001 (load) + US-004 (UI salvare)
- **Fisiere**: `app/mapping.py` (helper pur `text_rules_overlap`), `app/web/routes.py`,
  `app/web/templates/_mapari.html`, `tests/test_mapping_overlap.py`, `tests/test_web_mapari_overlap.py`
- **Test intai (RED)**: `tests/test_mapping_overlap.py` — `test_overlap_substring_ambele_directii`,
  `test_fara_overlap`, `test_overlap_normalizat_diacritice`; `tests/test_web_mapari_overlap.py` —
  `test_salvare_cu_overlap_arata_avertisment_dar_salveaza`
- **Acceptance criteria**:
  - [x] Helper pur `text_rules_overlap(pattern, existing_rules) -> list[dict]`: overlap = un pattern
        normalizat e substring al celuilalt (oricare directie). Determinist, fara DB.
  - [x] La salvare (ruta US-004), daca exista overlap, mesaj inline **neblocant**: „Se suprapune cu
        regula «X» → COD; ordinea (priority, id) decide care se aplica prima." **Salvarea continua**
        (avertisment, NU eroare).
  - [x] `python3 -m pytest tests/test_mapping_overlap.py tests/test_web_mapari_overlap.py -q` trece.
- **Verificare E2E**: gstack browser — adaug „verificare", apoi „verificare faruri" → avertisment overlap,
  ambele reguli salvate.

## 4. Riscuri

- **Poll de 15s vs. detaliu deschis (US-008)**: `_coada.html` reincarca periodic `#submissions-wrap`;
  un re-render naiv ar inchide detaliul expandat. Mitigare: pastreaza id-ul randului deschis si
  re-cere fragmentul dupa swap, sau exclude randul deschis din inlocuire. De decis la executie.
- **`white-space: normal` global (US-007)**: `.tablewrap`/`table` sunt partajate de mai multe
  tabele (Mapari, Formate). Schimbarile CSS trebuie scopate la tabelul de trimiteri ca sa nu
  strice celelalte. Verificare vizuala pe toate tab-urile.
- **Cod RAR invalid intr-o regula text (US-002)**: daca nomenclatorul nu e inca populat,
  `valid_codes` poate fi gol → regula n-ar rezolva nimic. Coerent cu `load_nomenclator_codes`
  (nu valida cand e gol); de confirmat ca seed-ul fallback (18 coduri) acopera cazul uzual.
- **Colizii de reguli text** (mai multe pattern-uri match): rezolvat determinist prin ordine
  (priority, id); fara ordine, comportamentul ar fi nedeterminist.

## 5. Intrebari deschise

> Se rezolva cu utilizatorul INAINTE de executie (poarta de aprobare PRD).

- ~~Indiciu „rezolvat prin regula text" pe rand?~~ **REZOLVAT** (design review): suficient codul RAR;
  re-evaluat post-livrare (vezi §8).
- ~~Pastram „Actualizat" in tabel la 1024px?~~ **REZOLVAT** (Pass 6): ascuns la `<=1024px`, ramane in
  detaliu (vezi §7 Responsive).
- ~~Ordonarea regulilor: priority editabila?~~ **REZOLVAT**: v1 = ordine de creare, fara editare
  priority (vezi §8).

## 6. Valuri de executie (graful de dependente)

```
Val 1: [US-001] [US-005] [US-006]      ← fara dependente, fisiere distincte → paralel
Val 2: [US-002] [US-007]               ← US-002 dep US-001; US-007 dep US-005+US-006
Val 3: [US-003] [US-004] [US-008]      ← US-003 dep US-002; US-004 dep US-001; US-008 dep US-007
Val 4: [US-009] [US-010] [US-011]      ← expansiuni CEO; US-009 dep US-001; US-010 dep US-002+US-003;
                                          US-011 dep US-001+US-004
```

## 7. Decizii de design (din /plan-design-review, 2026-06-23)

### Arhitectura informatiei (tabel trimiteri)
Ierarhie pe rand, stanga→dreapta: **Stare** (pill colorat, ancora vizuala) → **Vehicul**
(nr. mare + VIN muted dedesubt) → **Operatie** (denumire/cod API + „cod RAR: X" muted). Coloana
Operatie devine purtatoarea celor doua coduri; restul (Data, Nr. RAR, Actualizat) sunt context
secundar. Motiv iese din tabel (zgomot pe randurile OK) si traieste in detaliu.

### Tabel stari interactiune (Pass 2)
```
FEATURE                 | LOADING            | EMPTY            | ERROR                  | SUCCESS              | PARTIAL
------------------------|--------------------|------------------|------------------------|----------------------|--------------------
Celula Operatie         | —                  | „nemapat" (red)  | —                      | „cod RAR: X" muted    | „nemapat" pana la mapare
Rand expandabil (detaliu)| placeholder „Se   | n/a              | fragment de eroare HTMX| detaliu sub rand,     | n/a
                        | incarca…" hx-indic.|                  | in container           | chevron rotit + fundal|
Sectiune reguli text    | —                  | explicatie+exemplu| „Cod RAR necunoscut"  | „Regula salvata.      | —
                        |                    | + rand adaugare  | (inline, fara salvare) | Deblocate: N"         |
```

### Responsive (Pass 6)
- `>=1024px`: 8 coloane complete, fara scroll orizontal.
- `768-1024px`: ascunde **Actualizat** (e in detaliu) → 7 coloane, fara scroll.
- `<768px`: **card per rand** (eticheta:valoare stivuit, pill+chevron sus). Cardurile sunt
  justificate aici pentru ca **cardul ESTE interactiunea** (tap → expand), nu decor.

### Accesibilitate (Pass 6)
Randul expandabil: `tabindex=0`, `role="button"`, `aria-expanded`, activare Enter/Space, focus
readus la inchidere, tinta touch >=44px. Secundarul muted >=12px, contrast >=4.5:1; starea nu se
comunica DOAR prin culoare.

### Aliniere stil (Pass 4/5) — clasificat APP UI
Tabel dens, limbaj utilitar, putine culori, fara mozaic de carduri decorative. Conectorul
detaliului = fundal subtil + `border-top` (vocabular existent), NU `border-left` accent
(pattern AI-slop). Pill-urile pastreaza clasele `s-*`; eticheta lunga ramane in `title=`/detaliu.

## 8. NU in scope (design — deferat explicit)
- `match_type` multiplu (starts_with/regex) la reguli text — substring acopera „contine". Alt PRD.
- Editare `priority` reguli in UI (drag/numar) — v1 e ordine de creare.
- Redesign vizual al celorlalte tab-uri/tabele — doar tabelul de trimiteri + sectiunea Mapari noua.
- Indicator „rezolvat prin regula text" pe rand — codul RAR e suficient (de re-evaluat post-livrare).
- Animatii de expand/collapse elaborate — un toggle simplu (rotire chevron) e destul pentru un APP UI.

## 9. Ce exista deja (de reutilizat, nu reinventat)
- Sistem de culori prin CSS variables + teme dark/light (`base.html`), clase pill `s-*`.
- Macro toggle `auto_send` + `select` nomenclator + `csrf_token` (`_mapari.html`,
  `_trimitere_detaliu.html`) — refolosite la sectiunea reguli text.
- `reresolve_account` (re-rezolvare blocaje), `save_mapping` (pattern upsert+rerez), mesaj
  „Deblocate: N" + trigger `trimiteriChanged` din maparea inline (5.7).
- `eticheta_stare` (labels.py) — extins cu eticheta scurta, fara a pierde textul lung.
- `prezentare_din_payload` (payload_view.py) — extins cu `cod_rar`.

## Approved Mockups

| Screen/Section | Mockup Path | Direction | Notes |
|----------------|-------------|-----------|-------|
| Tabel trimiteri (desktop + mobil) | ~/.gstack/projects/romfast-rar-autopass/designs/trimiteri-tabel-20260623/mockup-trimiteri.png | Tabel dens cu pill compact, „cod RAR" stivuit sub operatie, rand expandat inline cu chevron+fundal; card per rand sub 768px | Conectorul detaliului in implementare = fundal subtil + border-top (NU border-left accent ca in mockup); teme calibrate pe CSS vars existente |

---

## Raport VERIFY

> Completat de subagentul verificator (context curat) in faza VERIFY — vezi ROADMAP §5.6.

**VERDICT: PASS** (toate US-001..011), dupa 1 fix critic descoperit la `/code-review` (CLOSE).

### Suita
`python3 -m pytest -q` → **814 passed, 1 skipped** (skip = `tests/test_live_rar.py`, live RAR opt-in).
Toate testele numite in PRD exista si trec.

### PASS/FAIL per story (verificator independent, context curat)
- US-001..011: **PASS**, cu dovezi de cod (vezi mai jos). Subset 5.8 = 81+ teste verzi.
- US-001 PASS — `operation_text_rules` UNIQUE(account_id,pattern), `auto_send DEFAULT 0`, FK ON DELETE CASCADE, `CREATE TABLE IF NOT EXISTS`; load/save/delete corecte (ordine priority,id).
- US-002 PASS — semnatura aditiva `resolve_prestatii(...,text_rules=None)`; precedenta stricta cod valid > mapare exacta > regula text > nemapat; match substring normalizat ambele parti; cod regula invalid -> ramane nemapat; prima dupa ordine castiga.
- US-003 PASS — toate **6** callsite resolve_prestatii primesc text_rules + valid_codes; `classify_prezentare` param nou + ambii apelanti (create_prezentari + /valideaza) incarca; T2 incarcare o data per cerere/batch.
- US-004 PASS — rute `/mapari/reguli-text` (+sterge) scoped+CSRF; save+reresolve+„Deblocate: N"+trimiteriChanged; empty state; cod invalid respins inline; sectiune a 4-a cu „contine".
- US-005 PASS — `cod_rar` uppercase/strip „.0" sau EMPTY; nemapat -> EMPTY (fara fallback pe cod_op_service).
- US-006 PASS — `eticheta_scurta` functie separata + dict propriu + KeyError; tuple `Eticheta` ramane 3.
- US-007 PASS — Motiv eliminata; Operatie cu „cod RAR: X"/„nemapat"; pill scurt + title lung; CSS scopat `.tabel-trimiteri`; responsive (ascunde Actualizat ≤1024, carduri <768).
- US-008 PASS — `<tr class="detaliu-rand">` colspan=8; click hx-target=#detaliu-{id}; single-open + poll-pauza via `htmx:beforeRequest` preventDefault; Enter/Space; Inchide goleste+focus; #trimitere-detaliu global golit.
- US-009 PASS — `/mapari/reguli-text/preview` scoped+CSRF, numara distinct nemapate ce contin pattern, max 3 exemple (HTML-escaped), zero scriere; pattern gol -> fragment gol.
- US-010 PASS — `cod_sursa="text_rule:<pattern>"` doar pe hit; `_emite_text_rule_hits` din TOATE caile (API create, reresolve, import_router commit, + corectie web routes.py:1001 si import web commit routes.py:2482 — paritate adaugata post-VERIFY); maparea exacta nu emite.
- US-011 PASS — `text_rules_overlap` pur (substring ambele directii, exclude identic); mesaj neblocant, salvarea continua.

### E2E
Browser pixel-level **neprobat** (sandbox ucide serverul persistent; dashboard in spatele /login). Compensat cu **E2E functional prin HTTP (TestClient, login real)**: tab Mapari S4 + empty state; US-009 preview „Potriveste N..."; US-004 save → trimiteriChanged + „Deblocate:" + lista; US-011 overlap; cod invalid respins; fragment submissions fara Motiv + detaliu-rand colspan=8 + pill role=button + „cod RAR/nemapat". Live RAR `FINALIZATA` neprobat (lipsa creds) — backend trimitere NEATINS.

### Regresia de aur (Non-Goals)
CONFIRMAT: worker / idempotency / validation / reconcile / crypto / auth NEMODIFICATE (git). `schema.sql` = pur aditiv (tabela noua + index). Masina de stari si fluxul de trimitere neatinse.

### Fix critic la `/code-review high` (CLOSE) — 1 bug real reparat
- **Regula text cu `auto_send=0` (DEFAULT, decizia CEO) trimitea automat la RAR.** `has_no_auto_send`
  inspecta DOAR `operations_mapping`, nu si regula text care a rezolvat itemul → un rand rezolvat
  prin regula cu auto_send=0 trecea pe `queued` (trimis automat), incalcand AC-ul central de siguranta
  US-001/US-003/US-004 (blast radius substring + `FINALIZATA` ireversibil). Repro confirmat:
  `classify_prezentare(..., auto_send=0)` → `queued`. **Reparat** (`app/mapping.py`, TDD,
  `tests/test_text_rule_autosend.py`): `_rezolva_din_reguli_text` intoarce si `auto_send`;
  `resolve_prestatii` marcheaza itemul cu `regula_fara_autosend` cand regula are auto_send falsy
  SI curata adnotarile stale (`cod_sursa`/flag) la fiecare rezolvare (repara si o telemetrie falsa
  latenta la re-rezolvare prin mapare exacta); `has_no_auto_send` prinde flagul. Acum auto_send=0 →
  `needs_mapping` (review), auto_send=1 → `queued`. Operatorul elibereaza randul comutand „In coada"
  pe regula (US-004) → reresolve cu auto_send=1 → queued. `pytest -q` **814 passed**.
  VERIFY-ul initial (context curat) a RATAT acest caz (nu testase hold-ul pe auto_send=0); prins la code-review.

---

## GSTACK REVIEW REPORT

| Review | Trigger | Why | Runs | Status | Findings |
|--------|---------|-----|------|--------|----------|
| CEO Review | `/plan-ceo-review` | Scope & strategy | 1 | clean | SELECTIVE EXPANSION: 1 fix siguranta (auto_send default 0) + 3 stories acceptate (US-009/010/011) |
| Codex Review | `/codex review` | Independent 2nd opinion | 0 | — | — |
| Eng Review | `/plan-eng-review` | Architecture & tests (required) | 1 | findings | 4 arhitectura (A1-A4) + 3 code/test (C1, T1-T2); 2 decizii deschise (D-eng-1/2) |
| Design Review | `/plan-design-review` | UI/UX gaps | 1 | clean | score: 6/10 → 9/10, 6 decisions |
| DX Review | `/plan-devex-review` | Developer experience gaps | 0 | — | — |

Pasele 1-7 evaluate cu mockup tintit (calibrat pe tema reala a app-ului). Decizii adaugate in plan:
empty state reguli text, conector detaliu (fundal+border-top, nu border-left slop), banda responsive
768-1024 (ascunde Actualizat), card per rand <768px, chevron+fundal pe rand expandat, keyboard a11y
(`role=button`/`aria-expanded`/Enter-Space) + pastrarea textului lung de stare in `title=`/detaliu.

- **VERDICT (design):** DESIGN CLEARED (9/10).

### Eng Review (2026-06-24)

Plan corect si bine descompus (8 stories atomice, 3 valuri, fisiere disjuncte). Referintele de
linie din PRD sunt exacte. Constatari, verificate fata de cod:

- **A1 (corectitudine) — US-003 numara gresit callsite-urile `resolve_prestatii`.** Sunt **6**, nu 4:
  pe langa `mapping.py:276` (in `classify_prestatie`), `mapping.py:441`, `import_router.py:204`,
  `import_router.py:1079`, mai exista `routes.py:984` (corectie web `needs_data`) si `routes.py:2278`
  (import web, intr-un loop pe randuri). Daca nu se threadeaza `text_rules` si acolo, regulile text
  NU se aplica pe caile web. → **D-eng-1**.
- **A2 — `valid_codes=None` pe 4 din 6 callsite-uri** (import_router 204/1079, routes 984/2278) intra
  in conflict cu AC-ul US-003 „cod rezolvat din regula respecta validarea fata de nomenclator": pe
  acele cai validarea e oprita. Pentru a onora AC-ul trebuie threadat si `valid_codes`. Nota
  (pre-existent, semnalat, nereparat aici): promovarea codului direct necunoscut (garda ORA-12899)
  ruleaza azi DOAR pe calea API, pentru ca import/web rezolva fara `valid_codes`.
- **A3 — face explicit seam-ul `classify_prezentare`.** `mapping.py:276` e in `classify_prezentare`
  (helperul partajat care garanteaza ca `/valideaza` da acelasi verdict ca trimiterea reala —
  invariant 5.2). Threading `text_rules` cere param nou pe `classify_prezentare` + incarcare in AMBII
  apelanti (create_prezentari + ruta `/valideaza`), altfel dry-run diverge. PRD spune „create_prezentari"
  — de corectat in US-003.
- **A4 (decizia principala) — detaliu inline (US-008) vs poll 15s.** Azi detaliul traieste in afara
  `#submissions-wrap` (`_coada.html:72`) tocmai ca poll-ul `every 15s` sa nu-l stearga. Inline
  reintroduce stergerea-la-poll. → **D-eng-2**.
- **C1 — US-006: nu mari tuple-ul `Eticheta`** (3 elemente, despachetat in template-uri). Adauga
  `eticheta_scurta(status)->str` separat (dict propriu + garda `KeyError`).
- **T1 — adauga stories/teste pentru cele 2 callsite-uri web** (corectie + import) care aplica o regula text.
- **T2 — incarca `text_rules`/`valid_codes` o data per cerere/batch**, NU per rand (`routes.py:2278`
  e in loop; la fel disciplina in `reresolve_account`).

Decizii rezolvate cu utilizatorul (2026-06-24):
- **D-eng-1 → TOATE cele 6 callsite-uri + `valid_codes`.** Incorporat in US-003 (Fisiere += `routes.py`;
  AC-uri pentru cele 6 apeluri + `classify_prezentare` param + incarcare per-cerere/batch).
- **D-eng-2 → pauza poll cat e un rand deschis.** Incorporat in US-008 AC.

A2/A3/C1/T1/T2 incorporate in stories (US-003 callsite-uri+validare+per-batch, US-006 `eticheta_scurta`
separat, US-008 pauza poll, teste web pe corectie/import).

- **VERDICT (eng):** PLAN SOUND. Cele 2 decizii rezolvate, constatarile incorporate in stories.

### CEO Review (2026-06-24) — SELECTIVE EXPANSION

Premisa corecta: regulile text sunt levierul de adoptie al Etapei 5 (mai putine mapari manuale =
onboarding mai rapid pentru service-urile din VFP). Scop bine subtras, reuse puternic peste
`operations_mapping`/`reresolve_account`. Abordare confirmata (tabela separata + threading
`text_rules`, varianta A). Constatare principala (inversiune/blast-radius) + 3 cherry-pick-uri,
toate rezolvate cu utilizatorul:

- **Fix siguranta — `auto_send` DEFAULT 0** (nu 1). O regula pe substring potriveste si operatii
  viitoare nevazute; `FINALIZATA` e ireversibil la RAR. Regula noua rezolva codul dar TINE randul
  pentru verificare umana pana cand operatorul activeaza „In coada". Incorporat in US-001 + US-004.
- **US-009 (acceptat) — preview pre-salvare**: „aceasta regula potriveste N operatii: ..." inainte de
  commit. Perechea naturala a `auto_send=0` → onboarding aproape fara risc.
- **US-010 (acceptat) — telemetrie hit regula** in `app_events`: ce pattern a rezolvat ce submission.
- **US-011 (acceptat) — avertizare overlap** neblocanta intre reguli.

Cele 3 expansiuni livrabile in Val 4 (dupa nucleul US-001..008). Plan CEO persistat in
`~/.gstack/projects/.../ceo-plans/`.

- **VERDICT (ceo):** SCOPE CLEARED. 4 decizii incorporate in stories. Ramane poarta umana de aprobare
  PRD inainte de EXECUTE.

NO UNRESOLVED DECISIONS
