# PRD 5.15 — Propagare design landing in aplicatie (dashboard compact + editare slim, VIN unic, prestatii multi-select) **Stare**: inchis (2026-06-28; CLOSE dupa `/code-review high` -> 8 buguri reparate TDD; regresie 1256 passed, 1 deselected live; E2E browser real ramane OPEN — mediu sandbox fara Playwright) > Proces complet: `docs/ROADMAP.md` §5. Contract RAR (sursa de adevar): `docs/api-rar-contract.md`. > Sistemul de design al landing-ului: `app/web/templates/landing.html` (commit 41aa385), `DESIGN.md`. > Mockup-uri piese fara design (REFERINTA VIZUALA OBLIGATORIE): `docs/mockups/prd-5.15-mockups.html` > — strip sanatate D6 (stari rosu/verde), picker prestatii E4 (op<->cod), reveal odometru initial. > Acopera exact piesele pe care mockup-urile landing nu le aratau si corecteaza contradictiile > mockup<->PRD (VIN unic, contor all-time, culori prin tokeni). > Starea trece: `draft -> aprobat -> in-executie -> verify-pass -> inchis`. ## 1. Obiectiv Propagam sistemul de design al landing-ului comercial (carduri/liste/formulare compacte, slim, si cele 4 teme grafit/cobalt/cupru/hartie) in aplicatia reala. Concret: dashboard-ul Acasa primeste cardurile-contor + lista de trimiteri slim din mockup-ul hero, iar formularul de editare trimitere primeste designul compact din mockup-ul "prestatie noua", cu **un singur camp VIN**, **Observatii** ca text liber pentru operatiile de service si **prestatii ca chips multi-select** de coduri RAR. Userul a cerut explicit replicarea acestor doua mockup-uri pentru ca ii place cat de compacte/slim sunt. Decizii de produs confirmate cu userul (poarta de aprobare a acestui PRD): - **D1**: cardurile-contor INLOCUIESC bara de status actuala (`_status.html`); pastram doar indicatorii de sanatate worker/RAR intr-o forma compacta. - **D2**: temele sunt ADITIVE — pastram light/dark/petrol + Auto SI adaugam cele 4 din landing (grafit/cobalt/cupru/hartie). Selectorul ciclic le parcurge pe toate. (grafit ~ dark si hartie ~ light raman optiuni separate, la cererea userului.) - **D3**: prestatiile sunt chips reale multi-select — utilizatorul poate adauga mai multe coduri din nomenclatorul RAR si poate sterge oricare; se trimite lista `prestatii` completa (RAR accepta lista `{codPrestatie, idPrezentare:null}` — `docs/api-rar-contract.md` §payload). - **D4** (contor Trimise): cardul "Trimise" arata trei valori temporale — **all-time** (principal) + **luna asta** + **azi** (secundar). Necesita extinderea numaratorilor cu `sent_today`/`sent_month`. - **D5** (Observatii = operatii service): in API-ul RAR, campul `obs` e DE FAPT denumirea operatiilor din service. Deci `obs` = text liber cu operatiile efectuate; la import, daca fisierul nu are coloana Observatii, **concatenam denumirea operatiei de service in `obs`**. `obs` ramane in `payload_json` (camp din contractul RAR), fara coloana noua. Decizii din /plan-ceo-review (2026-06-28, mod SELECTIVE EXPANSION): - **D6** (sanatate mereu-vizibila): cardurile-contor inlocuiesc bara de status, DAR sanatatea (worker viu? RAR accesibil? ultima autentificare) ramane intr-un **strip mereu-vizibil, colorat, deasupra contoarelor** (verde "declaratiile curg" / rosu "blocat: worker oprit / RAR inaccesibil"). Invariant: zero-silent-failures — semnalul critic NU se ingroapa sub volum. (Rafineaza D1.) - **D7** (operatie -> obs, fara regresie de mapare): la import, denumirea operatiei RAMANE in `op_service` (sursa pentru maparea op->cod) SI se COPIAZA in `obs`. `obs` e sink aditional, nu mutare; fluxul needs_mapping ramane neatins. (Rafineaza D5.) - **D8** (idempotenta obs): `obs` e EXCLUS din cheia de idempotenta (`idempotency.py:98`). Deci editarea `obs` NU schimba cheia si NU poate crea duplicate — corecteaza AC-ul gresit din US-005. `prestatii` ESTE in cheie (sortat dupa cod) — multi-select re-cheieaza randul (US-006). - **D9** (secventiere): 5.15 INAINTE de 5.14 (mapare LLM). Editorul manual defineste forma listei `prestatii` si UX-ul de confirmare; 5.14 umple codurile peste aceeasi forma. - **D10** (extinderi acceptate, SELECTIVE EXPANSION): toate 4 intra in scope — (a) salvare mapare din chip (US-009), (b) bulk-fix din lista (US-010), (c) require dinamic odometruInitial la chip R-ODO/I-ODO (US-007), (d) editare keyboard-first in form slim (US-007). Decizii din /plan-eng-review (2026-06-28, model claude/opus; outside-voice = Claude subagent, Codex a atins usage-limit). Fiecare confirmata cu userul: - **E1** (ARCH, /repune nu mai sterge operatia): `/repune` face azi `p0.pop("cod_op_service")` la `routes.py:1326` — sterge operatia cand se seteaza un cod direct, rupand D7 si US-009. US-006 ELIMINA acel `pop` si pastreaza `cod_op_service`; test de regresie obligatoriu (op_service supravietuieste unui /repune cu cod). (Rafineaza US-006.) - **E2** (DRY teme, fisier fierbinte): config-ul de teme e duplicat in ~7 locuri in base.html (anti-FOUC `VALID` la :22 + cinci literali paraleli `CYCLE`/`VALID`/`ICONS`/`LABELS`/`NEXT` la :758-765). US-001 CONSOLIDEAZA intr-o singura structura sursa-de-adevar (`THEMES` ordonata) din care se DERIVA ciclul/etichetele/iconitele + setul anti-FOUC. Adaugarea unei teme = o intrare. (Rafineaza US-001.) - **E3** (obs concat idempotent): la import, copierea denumirii operatiei in `obs` se face DERIVE-ON-EMPTY (doar cand `obs` e gol) ca sa fie idempotenta la re-import/re-editare. Test dedicat anti-dublu-concat ("Schimb ulei; Schimb ulei"). (Rafineaza US-005.) - **E4** (binding operatie<->cod in chips — HIGH): chip-urile NU sunt o lista plata de coduri. Cand exista operatii (`cod_op_service`), UI-ul randeaza UN picker PE operatie (eticheta op + chip-ul ei de cod), pastrand perechea per-item pe care modelul o are deja; lista plata de coduri libere DOAR pentru cazul fara operatie (corectie pura). Astfel US-009 citeste perechea direct, iar deduparea e PER-ITEM (nu "dupa cod" — doua operatii distincte pot mapa legitim la acelasi cod RAR). (Rafineaza US-006 AC2 + US-007 AC3 + US-009.) - **E5** (serializare Val 3 pe routes.py): US-005 si US-006 rescriu ACEEASI functie `post_corecteaza` (`routes.py:1120-1262`). Regula "un singur autor pe fisier fierbinte" se EXTINDE la routes.py in Val 3: US-005 INAINTE de US-006 (secvential, nu paralel). (Rafineaza §6.) - **E6** (US-007 HTMX server-driven PRIMARY): inversam abordarea — chips add/remove via `hx-post` care re-randeaza partial-ul chips+form; reveal-ul conditional `odometruInitial` rezulta GRATIS din re-randarea server; navigare tastatura = ``/`` nativ. JS custom DOAR ca progressive enhancement (snappiness), nu calea principala. Elimina path-ul dublu JS/no-JS pe care formularea anterioara il cerea. - [x] **Referinta vizuala (criteriu din mockup)**: `docs/mockups/prd-5.15-mockups.html` defineste aspectul-tinta — VIN unic (FARA al doilea camp "Confirma VIN" din mockup-ul landing); Observatii ca textarea slim; picker PE operatie cu DOUA stari vizuale: (a) operatie mapata = chip cod cu `×` + "+ alt cod" + link "salveaza regula op->cod" (US-009); (b) operatie ne-mapata = picker galben "alege cod RAR" cu eticheta "lipsa cod". OdometruInitial: ascuns implicit (doar hint discret "se cere doar pentru R-ODO/I-ODO") si DEZVALUIT cu bordura-stanga galbena + label "necesar pentru R-ODO" cand lista de chips contine R-ODO/I-ODO. - **Verificare E2E**: browser — editare trimitere needs_data: schimb VIN + scriu Observatii + adaug 2 coduri RAR (chips, cu tastatura) + adaug R-ODO (apare odometruInitial) + sterg un chip -> salvare -> persistat; identic in preview import. ### US-008: Teste de regresie + E2E final pe cele 4 teme **Ca** dezvoltator **vreau** acoperire si o trecere E2E completa **pentru ca** redesign-ul atinge fisiere fierbinti (base.html) si nu vreau regresii pe teme/liste/formular. - **Depinde de**: US-003, US-004, US-007 - **Fisiere**: `tests/test_web_responsive.py`, `tests/test_tema.py`, `tests/test_web_submissions.py` (~3 fisiere) - **Test intai (RED)**: completare scenarii lipsa (componente noi pe TOATE temele; slim list desktop+mobil) - **Acceptance criteria**: - [x] `pytest -q -m "not live"` verde (fara regresii fata de baseline). - [x] **Test de tema robust, nu esantion**: un test parametrizat verifica fiecare token critic (`--card2`, `--line2`, `--accent`, `--ok`, `--err`) e DEFINIT in TOATE cele 7+1 stari (light/dark/petrol/grafit/cobalt/cupru/hartie/Auto). Ancorare pe SENTINEL CSS (nu felii fixe `[idx:idx+N]`) — vezi regresia false-green din ROADMAP 5.13. - [ ] E2E Playwright pe 390/820/1280, pe un dark (grafit) + un light (hartie) + petrol (verificare ca temele vechi nu au regresat): strip sanatate, dashboard contoare, lista slim cu filtre/paginare/bulk, formular slim cu chips, fara overflow orizontal. ### US-009: Salvare mapare din chip (compounding cu fluxul de mapare) **Ca** operator **vreau** ca atunci cand adaug un cod RAR la o operatie sa-l pot salva ca regula **pentru ca** data viitoare operatia sa se auto-rezolve, fara sa re-mapez manual. - **Depinde de**: US-006, US-007 - **Fisiere**: `app/web/templates/_form_editare.html`, `app/web/routes.py` (reuse `save_mapping` + `reresolve_account` — fara logica noua), `tests/test_web_mapare_din_chip.py` (~3 fisiere) - **Test intai (RED)**: `tests/test_web_mapare_din_chip.py` — `test_salveaza_regula_din_chip`, `test_reresolve_deblocheaza_frate`, `test_optional_nu_forteaza` - **Acceptance criteria**: - [x] Cand operatia (`op_service`) e cunoscuta si userul adauga un cod RAR prin chip, apare optiunea "salveaza ca regula op->cod"; la confirmare reuse EXACT `save_mapping` + `reresolve_account` (acelasi mecanism ca maparea inline din 5.7), scoped pe cont + CSRF. - [x] Re-rezolvarea deblocheaza si alte submission-uri `needs_mapping` cu aceeasi operatie (pe `batch_id`). - [x] Optional: daca userul nu vrea sa salveze, editarea ramane one-off (fara regula). Se compune cu 5.14 (auto-maparea umple, salvarea din chip ramane fallback-ul uman). - **Verificare E2E**: adaug cod la operatie nemapata + salveaza regula -> al doilea rand cu aceeasi operatie se rezolva automat. ### US-010: Bulk-fix din lista (selectie multipla -> actiune unica) **Ca** operator **vreau** sa corectez mai multe randuri blocate dintr-o data **pentru ca** la 2-20 de corectat/zi nu vreau sa intru in fiecare individual. - **Depinde de**: US-004, US-006 - **Fisiere**: `app/web/templates/_submissions.html`, `app/web/routes.py` (reuse infra bulk existenta din `_submissions` + `submissions_admin`), `tests/test_web_bulk_fix.py` (~3 fisiere) - **Test intai (RED)**: `tests/test_web_bulk_fix.py` — `test_bulk_remapeaza_selectie`, `test_bulk_doar_blocate`, `test_bulk_scoped_cont` - **Acceptance criteria**: - [x] Pe randurile blocate (checkbox existent pe `gestionabil`), o actiune bulk noua: aplica un cod RAR / o remapare la toata selectia intr-o singura cerere (reuse forma `#bulk-trimiteri`). - [x] Scoped pe cont (404-before-409 ca la bulk-delete); doar randuri blocate eligibile. - [x] Fiecare rand re-validat + idempotenta recalculata individual (un cod invalid pe un rand nu pica tot lotul — sumar "N reusite, M esuate" ca la salvarea mapcoloane D#12). - **Verificare E2E**: selectez 3 randuri needs_mapping + aplic un cod -> toate 3 -> `queued`. - **Verificare E2E**: rulare completa documentata in Raportul VERIFY. ### US-011: Securitate — account-scope pe GET-urile de listare (interleave, E8) **Ca** operator **vreau** ca listarile sa-mi arate DOAR trimiterile contului meu **pentru ca** azi GET-urile de listare sunt globale + neprotejate (scurgere VIN/PII cross-cont, notata in CLAUDE.md). - **Depinde de**: — (backend pur, independent de UI; ruleaza in paralel cu valurile de design) - **Fisiere**: `app/web/routes.py` (GET-urile de listare trimiteri), `app/api/v1/router.py` (GET-urile API de listare daca sunt globale), `app/auth.py` (refolosire scope existent), `tests/test_web_scope.py`, `tests/test_api_scope.py` (~5 fisiere) - **Test intai (RED)**: `test_get_listare_scoped_cont` — un cont NU vede randuri ale altui cont; `test_get_listare_neautentificat_401`; `test_get_detaliu_scoped` (404-before-leak pe id strain). - **Acceptance criteria**: - [x] GET-urile de listare (trimiteri + orice listare globala) devin account-scoped, refolosind mecanismul de scope existent (ca POST-urile + bulk-delete: 404-before-409 pe id strain). - [x] Un cont nu poate enumera/citi VIN/PII al altui cont prin listare sau detaliu. - [x] Enforcement aliniat cu `AUTOPASS_REQUIRE_API_KEY` (dev vs prod), fara a rupe contul id=1 implicit in dev. - [x] Actualizeaza nota din CLAUDE.md ("GET-urile de listare ... de remediat") cand e inchis. - **Verificare E2E**: doua conturi cu trimiteri; contul A nu vede niciun rand al contului B in listare, filtre, paginare sau detaliu. ### US-012: Analytics device-mix (validare premisa mobil, in-PR) **Ca** owner **vreau** sa stiu raportul desktop/mobil al operatorilor **pentru ca** sa decid daca rafinarile mobil (390px) viitoare merita efortul (premisa nevalidata din TODOS 5.13/CEO-F1). - **Depinde de**: — (instrumentare backend, independenta de UI) - **Fisiere**: `app/web/routes.py` (sau middleware existent), `app/schema.sql` SAU `app_events` (reuse tabela de evenimente existenta — fara coloana noua daca `app_events` poarta semnalul), `tests/test_device_mix.py` (~3 fisiere) - **Test intai (RED)**: `test_device_mix_inregistrat`, `test_device_mix_fara_pii`. - **Acceptance criteria**: - [x] La acces dashboard, clasifica grosier viewport/UA in desktop/mobil si inregistreaza in `app_events` (semnal agregat, FARA PII suplimentar). Reuse tabela existenta — fara migrare daca `app_events` poarta semnalul. - [x] Un mod simplu de citire a raportului (query/admin), suficient pentru a decide investitia mobil. - [x] Zero PII nou; aliniat retentiei `app_events` existente. - **Verificare E2E**: acces dashboard de pe doua viewport-uri -> doua evenimente clasificate corect. ## 4. Riscuri - **base.html fisier fierbinte**: US-001/US-002 il ating amandoua + US-003/004/007 il citesc. Serializeaza pe valuri (un singur autor pe val pe base.html), ca la 5.12/5.13. - **Migrare teme legacy**: useri cu `localStorage.theme` = light/dark/petrol. Mitigare: maparea grafioasa din US-001 (light->hartie, dark->grafit, petrol->grafit) + test dedicat. - **Restyle lista = pierdere de functii**: filtre/paginare/bulk pot fi sparte de schimbarea de markup. Mitigare: US-004 are AC explicite pentru pastrarea lor + teste lock. - **Idempotenta la prestatii multiple**: schimbarea listei schimba cheia canonica. Mitigare: refolosim mecanismul existent de recalcul + prindere coliziune (3.5/5.10), zero logica noua. - **Densitate vizuala pe mobil**: randul slim cu 2 linii + pill poate aglomera. Mitigare: tinte 44px + verificare 390px in US-004/008. - **Premisa mobil nevalidata** (din TODOS 5.13, CEO F1): valoarea slim/compact pe mobil presupune utilizare reala pe mobil. Daca device-mix-ul e ~95% desktop, partea responsive e efort irosit. Mitigare: nu blocheaza (designul e bun si pe desktop), dar confirma analytics inainte de a investi in rafinari mobil viitoare. - **7 teme = suprafata de test/intretinere** pe fisierul cel mai fierbinte: fiecare componenta noua trebuie corecta in 7+1 stari. Istoricul (5.13) arata ca testele de tema au dat false-green o data. Mitigare: US-008 cere test parametrizat ancorat pe SENTINEL (nu felii fixe); deduparea grafit~dark / hartie~light ramana optiune de simplificare (reziduala, non-blocanta). - **Secventiere cu 5.14** (D9): 5.15 defineste forma listei `prestatii`; daca 5.14 (mapare LLM) porneste in paralel, sincronizeaza forma listei. Mitigare: 5.15 INAINTE de 5.14. ## 5. Intrebari deschise > Toate intrebarile au fost REZOLVATE cu userul (vezi D1-D5 §1). Pastrate aici ca istoric al deciziei. - **I1 — contor Trimise** [REZOLVAT]: arata all-time + luna asta + azi (D4). `_status_counts` extins. - **I2 — teme** [REZOLVAT]: aditiv — light/dark/petrol + Auto + grafit/cobalt/cupru/hartie (D2). - **I3 — stocare obs** [REZOLVAT]: in `payload_json`, fara coloana noua (D5). - **I4 — operatii la import -> obs** [REZOLVAT]: concatenam denumirea operatiei in `obs` cand fisierul nu are coloana Observatii (D5). - Reziduale minore (de decis la executie, non-blocante): formatul exact de concatenare a denumirilor in `obs`; rezolvarea "Auto" la light vs hartie; eventuala deduplicare grafit~dark / hartie~light in eticheta selectorului. ## 6. Valuri de executie (graful de dependente) ``` Val 0: [US-011] authz GET-listari (backend pur; ruleaza in paralel cu orice val) || Val 1: [US-001] base.html teme + tokeni (autor unic pe base.html) Val 2: [US-002] base.html componente (dupa US-001, autor unic pe base.html) Val 3: [US-003] [US-004] dashboard + strip sanatate + lista (consuma US-002; disjuncte) || [US-005] -> [US-006] backend obs APOI prestatii — SECVENTIAL (E5): ambele rescriu ACEEASI functie post_corecteaza (routes.py:1120-1262), autor unic Val 4: [US-007] formular slim cu chips (dupa US-002+US-005+US-006) Val 5: [US-009] [US-010] salvare mapare din chip || bulk-fix (dupa US-006/007 resp. US-004) — disjuncte la nivel de template (_form_editare vs _submissions) Val 6: [US-008] regresie + E2E final (dupa toate) ``` > **Regula autor-unic extinsa (E5)**: pe langa base.html, `routes.py` are autor unic in Val 3: > US-005 INAINTE de US-006 (ambele in `post_corecteaza`). US-009/US-010 in Val 5 sunt disjuncte > la nivel de template; adauga rute noi separate in routes.py (regiuni diferite, mergeabile). > Secventiere fata de alte PRD-uri (D9): **5.15 INAINTE de 5.14** (mapare LLM) — 5.15 fixeaza forma > listei `prestatii` si UX-ul de confirmare; 5.14 umple codurile peste aceeasi forma. --- ## Raport VERIFY Verificator independent (context curat, subagent Sonnet) — 2026-06-28. **VERDICT: PASS** (12/12 stories), cu 1 FAIL documentar remediat de lead + 1 OPEN limitat de mediu. - **Suita completa**: `python3 -m pytest -q -m "not live"` → **1230 passed, 1 deselected, 0 failed** (118s). Baseline initial 992 → +238 teste, zero regresii. - **AC per story (US-001..US-012)**: toate PASS cu dovezi (fisier:linie + test care le acopera). Puncte verificate explicit: 7+1 teme cu `--card2`/`--line2` in toate (US-001, DRY `THEMES`); componente slim doar cu tokeni, zero hex (US-002, ancorat pe `SENTINEL-COMPONENTE-SLIM`); strip sanatate D6 + 3 contoare + `sent_today`/`sent_month` bucketate timp local RO `+3 hours` (US-003, E7); lista slim cu filtre/paginare/bulk pastrate (US-004); `obs` editabil + EXCLUS din cheia idempotenta (`idempotency.py:98`) + concat derive-on-empty anti-dublu (US-005, D8/E3); prestatii multi-cod via `getlist` + **E1 IRON RULE** (`cod_op_service` supravietuieste `/repune` — test dedicat) + dedup per-item (US-006, E4); form slim VIN unic + picker chips pe operatie + reveal odo server-driven + select vechi redundant ELIMINAT (US-007/cleanup B); test tema parametrizat 5 tokeni x 7 teme ancorat pe selectori `[data-theme]` (US-008, anti false-green); salvare mapare din chip reuse `save_mapping`+`reresolve_account` (US-009); bulk-fix sumar "N reusite/M esuate" scoped (US-010); account-scope GET-listari 404-before-leak (US-011); device-mix fara PII reuse `app_events` (US-012). - **Fidelitate mockup** (`docs/mockups/prd-5.15-mockups.html`, cod-level): D6 strip, contoare D4, picker E4 cu 2 stari (mapata=chip+×+salveaza / nemapata=select galben "lipsa cod"), reveal odo border-left warn — toate conforme; toate culorile prin `var(--token)`, fara hex. - **Regresia de aur**: testele `POST /v1/prezentari` + worker + import→commit raman verzi in suita; E1 confirmat cu test. Live RAR real (`FINALIZATA`) = opt-in, indisponibil fara creds in sandbox (documentat). **FAIL 1 (remediat de lead)**: nota CLAUDE.md "GET-urile de listare globale + neprotejate (de remediat)" nu fusese actualizata (teammates instruiti sa NU atinga CLAUDE.md). **Remediat**: `CLAUDE.md:70` actualizat sa reflecte scope-ul implementat de US-011. **OPEN (mediu)**: E2E Playwright pe 390/820/1280 (grafit/hartie/petrol) — browserul MCP a returnat "already in use" in sandbox (ca la livrabilele anterioare). Serverul porneste OK (`/healthz` ok), ACs acoperite functional de pytest (`test_web_responsive.py`). Recomandat: rulat de operator cu browser real. --- ## GSTACK REVIEW REPORT Review: `/plan-ceo-review` — 2026-06-28. Mod: **SELECTIVE EXPANSION**. Model: claude (opus). Abordare aleasa de user: tot PRD-ul (8 stories) + 4 extinderi acceptate -> **10 stories**. | Pasaj | Status | Constatari materiale | |-------|--------|----------------------| | Audit sistem | OK | base.html cel mai fierbinte fisier (31x/30z); 5.15 = a 5-a iteratie pe acelasi UI (smell recurent); 5.14 in flight pe acelasi seam | | S1 Arhitectura | OK | Fara componente noi; fara migrare; rollback = revert template. Concentrare de risc pe base.html, nu coupling | | S2 Eroare/Rescue | 2 GAP | (a) coduri duplicate in chips nedefinit -> US-006 dedupe; (b) cod necunoscut: invariant ORA-12899 pastrat | | S4 Edge cases | 1 GAP HIGH | R-ODO/I-ODO cere odometruInitial; formularul nu il forta -> US-007 require dinamic (D10c) | | S2/Idempotenta | 1 FIX | `obs` EXCLUS din cheie (`idempotency.py:98`) -> AC US-005 corectat (D8); `prestatii` in cheie -> re-cheiere OK | | S6 Test | 1 GAP | 7 teme x componente pe fisier fierbinte; "esantion" prea lax -> US-008 test parametrizat ancorat pe SENTINEL | | S8/S11 Trust | 1 HIGH | carduri-contor ascundeau sanatatea -> strip mereu-vizibil deasupra contoarelor (D6) | | S9 Deploy | OK | Fara migrare; doar sent_today/sent_month (scoped). Rollback ieftin | | S10 Trajectorie | 1 DECIZIE | secventiere 5.15 inainte de 5.14 (D9) | | S11 Design/UX | OK + 4 EXT | strip trust; extinderi: salvare mapare din chip, bulk-fix, require dinamic odo, keyboard-first | **Decizii incorporate (D6-D10):** strip sanatate mereu-vizibil (D6); operatie ramane in op_service + copiata in obs (D7); obs exclus din idempotenta, AC corectat (D8); 5.15 inainte de 5.14 (D9); cele 4 extinderi acceptate (D10) -> US-007 imbogatit + US-009 (salvare mapare din chip) + US-010 (bulk-fix). **Risc rezidual notat (non-blocant):** premisa "utilizare mobil reala" nevalidata (TODOS 5.13 F1); 7 teme = suprafata de test pe fisier fierbinte (deduparea grafit~dark/hartie~light ramana optiune). **Spec-review loop (reviewer independent, context curat) — scor 7/10, verdict ISSUES -> remediat:** - #1 HIGH (contradictie): US-006 spunea "reconstruieste prestatii ca itemi goi `{cod_prestatie}`", ceea ce ar fi sters `cod_op_service`/`denumire` -> rupea D7 si US-009. **Remediat**: US-006 pastreaza itemii existenti, seteaza doar `cod_prestatie`; pereche operatie<->cod definita; `idPrezentare` se adauga in `payload.py`, nu in itemul intern. - #2 MEDIUM: `sent_today`/`sent_month` nu aveau sursa de timp (nu exista `sent_at`). **Remediat**: US-003 foloseste `status='sent' AND date(updated_at)` cu justificare (randul `sent` nu mai e scris pana la purge la +90z) -> fara migrare. - #3 MEDIUM: US-006 subestima rewrite-ul handler-elor (logica single-`prestatii[0]`). **Remediat**: Fisierele US-006 numesc liniile exacte de rescris. - #5/#6 LOW: suprafata JS reala (US-007) + tinta de click "De corectat" (US-003). **Remediat** (note adaugate). - #4 LOW (scope): US-009/US-010 sunt adiacente FUNCTIONALE (din SELECTIVE EXPANSION), dincolo de obiectivul pur de propagare design. **Acceptat constient** (alegerea userului); ramane optiunea de a le scoate intr-un PRD separat daca propagarea design e ce e urgent. ### /plan-eng-review — 2026-06-28 (model claude/opus; outside-voice = Claude subagent, Codex usage-limit) | Review | Trigger | Why | Runs | Status | Findings | |--------|---------|-----|------|--------|----------| | CEO Review | `/plan-ceo-review` | Scope & strategy | 1 | issues_open->remediat | 10 stories, 4 ext acceptate, spec-review remediat | | Eng Review | `/plan-eng-review` | Architecture & tests (required) | 1 | issues_open->remediat | 7 issues (2 HIGH), 1 regresie IRON-RULE, +2 stories noi | | Outside Voice | Claude subagent | Independent 2nd opinion | 1 | issues_found | 10 findings; 2 HIGH absorbite, restul foldate | **Step 0 scope:** acceptat ca-atare (10 stories). Gate de complexitate = breadth, nu depth (zero clase/servicii noi). User a confirmat pastrarea US-009/US-010. **Constatari eng-review (toate confirmate cu userul si foldate in AC):** - **E1 (ARCH, HIGH, conf 9/10)** `routes.py:1326` `/repune` face `p0.pop("cod_op_service")` — sterge operatia, rupe D7+US-009. US-006: elimina pop + test regresie (IRON RULE). - **E2 (Code-quality, conf 9/10)** config teme duplicat ~7 locuri pe base.html (anti-FOUC + 5 literali). US-001: o singura structura `THEMES`, restul derivat. - **E3 (Test, conf 7/10)** obs concat la import poate dubla textul la re-import. US-005: derive-on-empty + test anti-dublu. - **E7 (Perf/Correctness, conf 8/10 — outside-voice)** `date(updated_at)` UTC numara gresit `azi`/`luna` peste granita local RO. US-003: bucketare timp local + test granita. **Outside-voice (Claude subagent) — material absorbit:** - **OV/E4 (HIGH, conf 9/10)** chip-uri lista plata fara binding op<->cod -> rupe US-009; dedup-dupa-cod sterge operatie legala. US-006/007: picker PE operatie cand exista op; flat doar fara op; dedup per-item. - **OV/E5 (HIGH, conf 9/10)** Val 3 conflict same-function: US-005+US-006 rescriu `post_corecteaza`. §6: serializare US-005 -> US-006 pe routes.py. - **OV/E6 (MED, conf 8/10)** US-007 supraestimeaza JS custom intr-un app HTMX. US-007: hx-post server-driven primary; reveal odo gratis; select/datalist nativ. - **OV/E8 (MED securitate, conf 7/10)** GET-uri de listare globale neprotejate (scurgere VIN/PII cross-cont, CLAUDE.md). User a ales INTERLEAVE -> **US-011** (account-scope + teste). - **OV minore foldate:** reconcile drift pe `updated_at` (caveat US-003); cost poll non-sargabil (notat, non-blocant); cuplaj `EDIT_FIELDS` pentru obs preview (US-005 AC). **TODO (decizie user):** premisa mobil nevalidata -> user a ales BUILD-IN-PR -> **US-012** (analytics device-mix, fara PII, reuse `app_events`). **Scope actualizat:** 10 -> **12 stories** (+US-011 authz, +US-012 analytics). Fara migrare de schema. Outside-voice a confirmat "no migration" TRUE. **Failure modes — gap critic:** niciun gap critic ramas silent. Cel mai aproape: E1 (regresie tacuta op_service pe /repune) — acum acoperit de test obligatoriu. E7 (off-by-a-day tacut) — acum cu test de granita. **VERDICT:** CEO + ENG CLEARED — gata de executie. 12 stories. Outside-voice absorbit (2 HIGH foldate). Fara migrare de schema. NO UNRESOLVED DECISIONS