Files
rar-autopass/docs/prd/prd-3.6-editare-preview-acasa-unificata.md
Claude Agent 35f35d03cc fix(web): protejeaza decriptarea override_json in preview + inchide 3.6 (CLOSE)
decrypt_creds(override_json) era in afara try/except-ului care protejeaza
raw_json in preview_import (import_router) si _web_compute_preview (routes).
La rotatie cheie Fernet (risc acceptat R4) sau token corupt, raw_json degrada
gratios la {} dar override_json arunca 500 pe tot batch-ul. Acum ambalat
identic (fallback None -> {}).

Prins de /code-review high la CLOSE. Writeback: ROADMAP 3.6 -> DONE,
PRD -> inchis + Raport CLOSE. Duplicare _override_of/canonicalize notata
ca cleanup viitor (disciplina backend-neatins). 523 teste pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 09:09:03 +00:00

595 lines
48 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!-- /autoplan restore point: /home/claude/.gstack/projects/romfast-rar-autopass/main-autoplan-restore-20260619-093652.md -->
# PRD 3.6 — Editare celule in preview + Acasa unificata (Trimiteri inline, upload slim, Mapari tabelar)
**Stare**: inchis (2026-06-22 — CLOSE: `/code-review` high a prins 1 bug real, reparat; dashboard ROADMAP → DONE. Toate US-001..007 implementate, 523 teste pass, VERIFY E2E browser + LIVE RAR test PASS; vezi `## Raport VERIFY` si `## Raport CLOSE`)
> Proces complet: `docs/ROADMAP.md` §5. Contract RAR (sursa de adevar): `docs/api-rar-contract.md`.
> Starea trece: `draft → aprobat → in-executie → verify-pass → inchis` (actualizata de lead).
> Continua 3.5 ([prd-3.5](prd-3.5-dashboard-compact-trimiteri-mapari.md)). **Backend trimitere (worker,
> masina stari, idempotenta-logica, mapping-rezolvare) NU se atinge** — doar canalul de import si stratul web.
> Exceptie schema decisa la poarta autoplan: UNA coloana nullable `import_rows.override_json` (Approach B), cu
> migrare defensiva `_migrate` ca la 3.3b/3.5. Worker/idempotenta/mapare raman neatinse.
## 1. Obiectiv
Dupa upload, utilizatorul putea citi randurile cu probleme dar nu le putea **corecta inainte de trimitere**
singura editare de continut era post-confirmare, in alt tab. Livram: (a) editare de celule direct in preview
(buton "Editeaza" pe rand), si (b) reunificarea fluxului pe pagina **Acasa** — Trimiterile devin o sectiune
permanenta sub upload (tab-ul "Trimiteri" dispare), zona de upload se comprima la o bara slim, iar "Mapari"
trece pe format tabelar compact cu eticheta auto-send reformulata ca un comutator Automat/Manual.
Motivatie: microcopia spune "vezi mai jos trimiterile", dar trimiterile erau intr-un alt tab; cazul real de
utilizare (fisier cu o data/VIN lipsa sau gresit) cerea re-upload in loc de o corectie pe loc.
## 2. Non-Goals (anti scope-creep)
- **Fara editare a operatiei/codului RAR in celula** — codul de operatie ramane rezolvat prin panoul de mapare
existent (`needs_mapping`). Editarea de celule acopera campurile de continut: VIN, nr. inmatriculare,
data prestatie, odometru initial/final. (Operatia se schimba din panoul de mapare, nu din tabel.)
- **Fara editare in bloc / multi-rand** — un rand pe rand, mod editare explicit.
- **Schema: o singura coloana noua** — `import_rows.override_json` (nullable, criptat Fernet), patch CANONIC peste
randul mapat (Approach B). NU se modifica `raw_json` (ramane cheiat pe anteturile fisierului). Restul schemei neatins.
- **Fara atingerea logicii de idempotenta, validare, mapare sau a worker-ului** — editarea = mutatie pura de stocare;
recalculul de stare merge OBLIGATORIU prin `_resolve_row_for_preview` (un singur clasificator, fara drift).
- **Fara reorganizarea tab-urilor ramase** (Mapari/Cont/Nomenclator) dincolo de scoaterea tab-ului Trimiteri.
- **Fara paginare noua pe Trimiteri** — sectiunea de pe Acasa refoloseste filtrarea existenta (US-009 din 3.5).
## 3. Stories atomice
> Backend + UI pentru acelasi comportament = stories separate. Toate rutele web noi sunt sub `require_login`
> si **scoped pe contul din sesiune** (gard cross-account 404, identic cu rutele existente). CSRF pe toate POST-urile.
### US-001: Backend — persista editarea unui rand de preview
**Ca** utilizator care vede un rand cu date gresite/lipsa in preview **vreau** sa salvez valori corectate
pentru acel rand **pentru ca** sa-l trimit fara re-upload de fisier.
- **Depinde de**: —
- **Fisiere**: `app/schema.sql` (coloana `override_json` + `_migrate`), `app/api/v1/import_router.py`
(`_resolve_row_for_preview` + `commit_import` aplica override; ruta noua), `tests/test_import_edit_row.py`,
`tests/fixtures/import_antet_necanonic.csv` (NOU), `tests/fixtures/import_lipsa_coloana.csv` (NOU) (~5 fisiere)
- **Stocare (Approach B, decis la poarta autoplan):** editarea scrie un dict CANONIC in `import_rows.override_json`
(nullable, criptat Fernet). `_resolve_row_for_preview` si `commit_import` aplica `mapped.update(override_json)`
ULTIMUL, dupa maparea `json_mapare`. Astfel se poate completa si un camp a carui coloana LIPSESTE din fisier
(cazul "completez informatii lipsa"), iar `raw_json`/idempotency raman neatinse.
- **Test intai (RED)**: `tests/test_import_edit_row.py`
`test_editeaza_rand_antet_necanonic_devine_ok` (fixture cu antet `Serie sasiu`/`Data` — prinde bug-ul de stocare),
`test_editeaza_completeaza_coloana_absenta` (fisier fara coloana data → editarea adauga data → ok),
`test_editeaza_status_identic_cu_GET_preview` (ruta editare NU re-deriva status; egal cu `GET /preview`),
`test_editeaza_rand_scoped_alt_cont_404`, `test_editeaza_batch_inexistent_404`,
`test_editeaza_row_index_invalid_pe_batch_valid_404`,
`test_editeaza_pastreaza_campuri_neatinse` (operatie/prestatii raman),
`test_editeaza_batch_committed_409` (guard post-commit),
`test_editeaza_raw_corupt_no_op` (decrypt fail → 422/no-op, fara crash),
`test_editeaza_empty_input_sterge_campul` (semantica empty = CLEAR, pentru cazul "corectez o valoare gresita").
- **Acceptance criteria**:
- [ ] Migrare: `import_rows.override_json TEXT` (nullable), `_migrate` defensiv (idempotent, ca `is_admin` in 3.3b).
- [ ] Ruta `POST /v1/import/{import_id}/rand/{row_index}/editeaza` (`resolve_account_id`) + alias web
`POST /_import/{import_id}/rand/{row_index}/editeaza` (`require_login`). Campuri: `vin`, `nr_inmatriculare`,
`data_prestatie`, `odometru_initial`, `odometru_final`.
- [ ] **Mutatie pura**: decripteaza `override_json` curent (sau {}), aplica campurile (vezi semantica empty), re-cripteaza,
`UPDATE`. NU recalculeaza statusul in ruta — preview-ul il rederiva via `_resolve_row_for_preview`.
- [ ] **Semantica empty**: input gol = STERGE cheia din override (revine la valoarea din fisier daca exista). Documentat + testat.
- [ ] **Scoping intr-o singura interogare**: `import_rows r JOIN import_batches b ON b.id=r.batch_id
WHERE b.id=? AND b.account_id=? AND r.row_index=?` → 404 pe gol (acopera alt cont, batch inexistent, row_index invalid).
- [ ] **Guard committed**: batch cu `status='committed'` → 409 (editarea n-ar avea efect downstream).
- [ ] `decrypt_creds` → None/exceptie → 422/no-op defensiv (ca import_router.py:602-606), niciodata scriere goala.
- [ ] Coercion: nu se afirma `canonicalize_row` pe `odometru_initial` (normeaza doar `_final`); validarea (`_parse_int`)
tolereaza ".0" — testul verifica prin validare, nu prin canonicalize.
- [ ] NU atinge `submissions`.
- **Verificare E2E**: TestClient — (a) fixture cu antet ne-canonic, rand needs_data → editeaza → preview = `ok`;
(b) fixture fara coloana data → editeaza data → `ok`.
### US-002: UI — buton "Editeaza" pe rand in tabelul de preview
**Ca** utilizator **vreau** sa pun un rand in mod editare si sa-i corectez celulele pe loc **pentru ca**
sa nu reincarc tot fisierul pentru o singura valoare.
- **Depinde de**: US-001
- **Fisiere**: `app/web/templates/_preview_import.html`, `app/web/templates/_preview_rand.html` (NOU — fragment rand),
`app/web/routes.py` (handler fragment rand), `tests/test_preview_edit_ui.py`
- **Test intai (RED)**: `tests/test_preview_edit_ui.py` —
`test_preview_are_buton_editeaza_pe_rand`,
`test_editeaza_intra_in_mod_editare_form_propriu` (randul devine FORM separat, NU in `#confirm-form`),
`test_salveaza_reda_doar_randul` (raspuns = fragment rand + OOB contoare, NU tot `#import-section`),
`test_enter_in_camp_editare_nu_declanseaza_confirm`,
`test_eroare_validare_pastreaza_valorile_introduse` (data invalida → ramane in editare, mesaj pe camp).
- **Acceptance criteria**:
- [ ] Fiecare rand are un buton "Editeaza" (coloana de actiuni la final).
- [ ] **Swap pe rand, NU pe sectiune (D-3.1):** Editeaza/Salveaza tintesc randul `<tr>` (`hx-target` pe rand,
`hx-swap="outerHTML"`), iar rezumatul + bara de confirmare se actualizeaza prin **OOB swap**. NU se re-randeaza
`#import-section` (altfel se pierd bifele `reviewed_rows`, `n_confirmat`, filtrul activ, alte randuri in editare).
- [ ] **Form propriu (D-3.3):** input-urile de editare stau intr-un `<form>` separat (sau `form=` attribute),
NU in `#confirm-form`. Butonul "Trimite la RAR" e DEZACTIVAT cat un rand e in mod editare. Enter intr-un camp
de editare salveaza randul, niciodata nu declanseaza confirmarea (ireversibila).
- [ ] **Refolosire `_trimitere_detaliu.html` (DRY, rezolva mobil+eroare):** modul de editare foloseste aceeasi grila
responsiva `repeat(auto-fit, minmax(200px,1fr))` + error-map + scroll-to-row ca formul de corectie existent.
Pe viewport ingust randul devine card stacked (label deasupra input), nu celule in scroll orizontal.
- [ ] **Stari (D-2.1/D-2.2):** `hx-indicator` "se salveaza…" pe rand + butoane dezactivate in timpul cererii;
`hx-on::response-error` pastreaza randul + valorile introduse + banner ne-distructiv la 500/CSRF/timeout.
- [ ] **Mutual-exclusion (D-3.2/D-3.6):** cat un rand e in editare, butoanele Editeaza ale celorlalte randuri sunt
dezactivate; "Incarca alt fisier" / schimbarea de tab cu editare dirty cer confirmare (sau auto-cancel cu toast).
- [ ] La eroare de validare a valorii, randul ramane in editare cu mesajul pe campul vinovat (tipar `corectie_errors`).
- [ ] Dupa Salveaza: scroll la + evidentiaza randul editat (reuse scriptul de outline din `_trimitere_detaliu.html`).
- [ ] "Anuleaza" inchide editarea fara scriere. Accesibil: butoane min 44px, `aria-label` per camp cu nr. rand + VIN.
- **Verificare E2E**: browser HTMX pe `/` (Playwright MCP) — upload fixture cu un rand fara data → Editeaza →
completeaza data → Salveaza → DOAR randul se schimba pe `ok`, contorul "gata de trimis" creste, bifele altui rand raman.
### US-003: Acasa unificata — Trimiteri ca sectiune permanenta, fara tab "Trimiteri"
**Ca** utilizator **vreau** sa vad trimiterile pe aceeasi pagina cu upload-ul **pentru ca** "vezi mai jos
trimiterile" sa fie adevarat si sa nu navighez intre tab-uri.
- **Depinde de**: —
- **Fisiere**: `app/web/templates/dashboard.html`, `app/web/templates/_acasa.html`, `app/web/routes.py`,
`tests/test_acasa_trimiteri.py`
- **Test intai (RED)**: `tests/test_acasa_trimiteri.py` —
`test_tab_bar_fara_trimiteri`,
`test_acasa_contine_sectiunea_trimiteri` (tabel + filtre),
`test_tab_coada_redirect_la_acasa` (`?tab=coada` nu da 404, serveste Acasa),
`test_acasa_fara_linkuri_ajutor` (randul de linkuri catre tab-uri eliminat),
`test_badge_trimiteri_scoped_pe_acasa`.
- **Test intai (RED)** (in plus): `test_fragment_coada_serveste_acasa` (fragmentul, nu doar pagina),
`test_sectiune_trimiteri_are_heading` ("Trimiterile tale"), `test_acasa_pastreaza_wayfinding_mapari_coduri`.
- **Acceptance criteria**:
- [ ] Tab-ul "Trimiteri" (`coada`) eliminat din `tab-bar`; raman Acasa·Mapari·Cont·Nomenclator.
- [ ] `_acasa.html` randeaza, sub upload + primii pasi, sectiunea Trimiteri completa cu heading **"Trimiterile tale"**
(divizor vizual fata de upload): filtrele (US-009), tabelul (`_submissions.html`), panoul `#trimitere-detaliu`.
- [ ] **Un singur sticky bar pe ecran (D-1.2):** cat un preview de import e activ (`#import-section` randat),
sectiunea Trimiteri e ascunsa/colapsata; dupa commit, se reveleaza si scroll la ea. Nu coexista doua bare sticky.
- [ ] **Wayfinding pastrat (D-5.3):** se scoate DOAR linkul redundant "Trimiteri"; "Mapari" si "Coduri RAR" raman
ca o linie de ajutor discreta (utile pentru operatori non-tehnici).
- [ ] `GET /?tab=coada` **si** `GET /_fragments/coada` nu dau 404 — ambele servesc continutul Acasa (fragmentul nu mai
randeaza `_coada.html` orfan; `_coada.html` sters/repurposat).
- [ ] **Anti dublu-poll (M5):** poll-ul de trimiteri din sectiune are `hx-trigger` gated pe
`document.visibilityState==='visible'` (sau aliniat la 15s ca status-ul) — nu doua timere perpetue pe pagina mereu deschisa.
- [ ] Contorul de atentie (badge) se reflecta in heading-ul sectiunii, nu pe un tab disparut.
- [ ] Microcopia post-confirmare tinteste sectiunea "Trimiterile tale" de pe Acasa.
- **Verificare E2E**: browser pe `/` — dupa confirmarea unui import, trimiterile apar in aceeasi pagina sub upload, cu heading;
click pe rand blocat deschide corectia inline; `?tab=coada` si `/_fragments/coada` servesc Acasa.
### US-004: UI — zona de upload comprimata la o bara slim
**Ca** utilizator **vreau** un upload care ocupa putin spatiu **pentru ca** trimiterile de sub el sa fie vizibile fara scroll.
- **Depinde de**: US-003 (pentru a avea ce sta sub bara)
- **Fisiere**: `app/web/templates/_upload.html`, `tests/test_upload_slim.py`
- **Test intai (RED)**: `tests/test_upload_slim.py` —
`test_upload_slim_pe_un_rand` (markup compact, fara caseta mare drop-zone dominanta),
`test_upload_pastreaza_drag_drop_si_input` (input file + handler drag-drop raman),
`test_upload_pastreaza_select_foaie` (cazul multi-sheet inca functioneaza).
- **Acceptance criteria**:
- [ ] Upload-ul devine o bara pe un rand: eticheta "Importa:" + buton "Alege fisier (xlsx/csv)" + zona "sau trage aici",
microcopy scurt ("NU se trimite nimic pana confirmi" pastrat, dar discret).
- [ ] **Slim DAR accentuat (D-1.1/D-5.2):** bara pastreaza un tratament distinct (border/fundal de accent) ca sa ramana
punctul de intrare evident chiar cu un tabel lung dedesubt.
- [ ] **First-run pastreaza hero (D-5.1):** daca `not are_trimiteri`, bara ramane usor mai inalta cu copy-ul
"Primul fisier? Trage-l aici."; se colapseaza la slim doar dupa ce contul are trimiteri. Empty-state-ul redundant
al tabelului Trimiteri se suprima cand sunt zero trimiteri (bara de upload acopera deja CTA-ul).
- [ ] Drag-drop pe bara si `input[type=file]` ascuns raman functionale (JS-ul existent refolosit).
- [ ] Cazul multi-sheet (select foaie) inca apare cand fisierul are mai multe foi.
- **Verificare E2E**: browser pe `/` — bara de upload ocupa ~1 rand cand exista trimiteri; first-run pastreaza hero-ul.
### US-005: UI — Mapari "De rezolvat" + "Operatii salvate" ca tabel
**Ca** utilizator cu multe mapari **vreau** sa le vad tabelar **pentru ca** stiva de carduri/forme ocupa prea mult loc.
- **Depinde de**: —
- **Fisiere**: `app/web/templates/_mapari.html`, `tests/test_mapari_tabel.py`
- **Test intai (RED)**: `tests/test_mapari_tabel.py` —
`test_mapari_de_rezolvat_in_tabel`,
`test_mapari_salvate_in_tabel`,
`test_mapari_salvare_si_stergere_inca_functioneaza` (POST-urile `/mapari`, `/mapari/salvate`, `/mapari/salvate/sterge` neschimbate).
- **Acceptance criteria**:
- [ ] Sectiunea "De rezolvat" (operatii `needs_mapping`) randata ca tabel: coloane operatie/denumire + nr. blocate,
sugestii, select cod RAR, comutator (US-007), actiune Salveaza — un rand de tabel per operatie.
- [ ] Sectiunea "Mapari operatii salvate" randata ca tabel cu aceleasi coloane + Sterge.
- [ ] **Starea stocata redata (H4):** comutatorul (US-007) din tabelul salvate reflecta valoarea `auto_send` STOCATA
per mapare (din `_load_saved_op_mappings`, routes.py:738), nu un default hard "Automat".
- [ ] Comportamentul existent neschimbat: re-rezolvare automata a blocatelor la salvare/edit cod; endpoints identice; csrf.
- [ ] Tabelele folosesc `.tablewrap` (scroll orizontal pe mobil) ca Trimiteri.
- **Verificare E2E**: browser pe tab Mapari — operatiile nemapate si cele salvate apar ca tabele; salvare + stergere OK.
### US-006: UI — Formate de coloane ca tabel
**Ca** utilizator **vreau** formatele de coloane salvate ca tabel **pentru ca** sa le compar dintr-o privire.
- **Depinde de**: —
- **Fisiere**: `app/web/templates/_mapari.html`, `tests/test_formate_tabel.py`
- **Test intai (RED)**: `tests/test_formate_tabel.py` —
`test_formate_coloane_in_tabel`,
`test_formate_editare_data_si_stergere_inca_functioneaza`.
- **Acceptance criteria**:
- [ ] Sectiunea "Formate de coloane salvate" randata ca tabel: nr. coloane / maparile col→camp / format data (editabil) / Sterge.
- [ ] POST-urile `/formate-coloane/editeaza` si `/formate-coloane/sterge` neschimbate; csrf pastrat.
- [ ] `.tablewrap` pentru consistenta cu celelalte tabele.
- **Verificare E2E**: browser pe tab Mapari — formatele apar ca tabel; editarea formatului de data + stergerea functioneaza.
### US-007: UI — comutator pe COADA in loc de bifa "auto-send" (framing sigur, nu "Manual/trimitere")
**Ca** operator **vreau** sa stiu clar ce se intampla cu o operatie la fisierele viitoare **pentru ca**
"auto-send" e jargon, iar "Manual/Automat" suna ca si cum sistemul ar trimite singur la RAR (fals — periculos).
> **Decis la poarta autoplan (UC-A):** framing pe **punerea in coada**, NU pe trimitere. Toti reviewerii (CEO/Eng/Design):
> "Automat/Manual" citit global peste declaratii ireversibile = risc de send-safety. Etichetele poarta singure sensul.
- **Depinde de**: US-005 (acelasi markup de mapari)
- **Fisiere**: `app/web/templates/_mapari.html`, `app/web/templates/_preview_import.html`, `tests/test_autosend_toggle.py`
- **Test intai (RED)**: `tests/test_autosend_toggle.py` —
`test_comutator_coada_prezent` (textul contine "in coada" / "verificare", NU "trimite"/"Manual" gol),
`test_eticheta_scoped_pe_operatie` (microcopy contine "aceasta operatie"),
`test_pune_automat_mapeaza_auto_send_true`,
`test_tine_pentru_verificare_mapeaza_auto_send_false`,
`test_default_pune_automat` (comportament identic cu `checked` de azi).
- **Acceptance criteria**:
- [ ] Bifa `auto_send` inlocuita cu un comutator cu doua stari, etichetat pe COADA, in: panoul de mapare din preview
(`_preview_import.html`) si ambele locuri din `_mapari.html`. Antet: **"La fisierele viitoare cu aceasta operatie:"**
Optiuni: **"Pune automat in coada"** / **"Tine pentru verificare"**.
- [ ] Microcopy scoped pe operatie (NU global): "...doar pentru aceasta operatie; nimic nu pleaca la RAR pana confirmi."
Niciun cuvant "Manual"/"trimite" izolat care sa implice bypass al confirmarii RAR. Caption prezent si in preview
(azi checkbox-ul din preview nu are caption).
- [ ] Maparea valoare→backend pastreaza semantica `auto_send` existenta ("Pune automat"=true, "Tine"=false);
default **"Pune automat in coada"** (mirror la `checked` de azi).
- [ ] **`name="auto_send"` pastrat** cu `value`-uri ce produc bool corect — zero atingere backend (cale aleasa).
- **Verificare E2E**: browser — salvez o operatie pe "Tine pentru verificare" → la urmatorul import randul cu acea operatie
ramane blocat (nu intra automat in coada); pe "Pune automat in coada" → intra in coada (tot cu gate-ul de confirmare la trimitere).
## 4. Riscuri
- **Stocare editare (REZOLVAT prin Approach B)**: `raw_json` e cheiat pe anteturile fisierului; o editare pe cheie
canonica ar fi ignorata pe fisiere cu antet ne-canonic, iar un camp fara coloana-sursa n-ar putea fi completat.
Mitigare: `override_json` (patch canonic aplicat ultimul). Test obligatoriu cu fixture antet ne-canonic + coloana absenta.
- **Swap pe rand vs sectiune (REZOLVAT)**: editarea tinteste randul + OOB contoare, NU `#import-section`; form propriu;
confirm dezactivat la editare. Previne pierderea starii si Enter→trimitere ireversibila.
- **Dublu-poll pe Acasa**: status 15s + trimiteri 10s pe pagina mereu deschisa. Mitigare: poll-ul de trimiteri gated pe
`document.visibilityState` (sau 15s). Nu doua timere perpetue.
- **`?tab=coada` + `/_fragments/coada` vechi**: fallback la continutul Acasa (nu 404, nu fragment orfan).
- **Export "randuri cu probleme"**: trebuie sa reflecte valorile editate. Recalculul prin `_resolve_row_for_preview`
(cu override aplicat) => export consistent automat.
- **Recalcul preview pe fisier mare**: cu swap pe rand, doar randul + contoarele se re-randeaza (nu toate 5000).
## 5. Intrebari deschise
> Rezolvate cu utilizatorul inainte de executie (poarta de aprobare PRD). Toate cele de mai jos sunt **inchise**
> prin sesiunea de planificare (AskUserQuestion 2026-06-19):
- Editare preview: **buton "Editeaza" pe rand** (mod editare explicit pe rand), nu click-pe-celula. [INCHIS]
- Trimiteri pe Acasa: **mutare completa**, tab-ul "Trimiteri" eliminat (linkul redundant "Trimiteri" scos;
wayfinding "Mapari"/"Coduri RAR" pastrat). [INCHIS]
- Upload: **bara slim pe un rand**, accentuata + hero pastrat la first-run. [INCHIS]
- auto-send: framing pe **coada** ("Pune automat in coada" / "Tine pentru verificare"), NU "Automat/Manual" global. [INCHIS — poarta autoplan UC-A]
- Stocare editare: **Approach B** (`override_json`), relaxeaza Non-Goal-ul de schema. [INCHIS — poarta autoplan]
- Structura: **un singur PRD 3.6, valuri secventiate** (Acasa intai, editare dupa storage). [INCHIS — poarta autoplan]
## 6. Valuri de executie (secventiate post-autoplan)
```
Val 1 (Acasa unificata — livreaza 80% din valoare): [US-003] [US-004]
Val 2 (Editare preview — dupa storage redesign B): [US-001] → [US-002]
Val 3 (Cosmetic mapari + toggle sigur): [US-005] → [US-006] [US-007]
```
> Secventiere ceruta la poarta autoplan: US-003 (+US-004 ca sub-task, ii face loc) intai — fixeaza microcopia care
> minte azi. US-001/002 dupa ce `override_json` (Approach B) e in loc. US-005 si US-007 ating `_mapari.html`/`_preview_import.html`
> impreuna — acelasi worker (sectiuni distincte). Regula de aur: la fiecare val, regresia `pytest -q` verde + bifare in PRD.
---
## Raport VERIFY
> Verificare 2026-06-19 (lead-driven: suita automata + E2E browser Playwright + trimitere LIVE pe RAR test).
### Rezultat global: **PASS**
**Teste automate**: `python3 -m pytest -q` → **523 passed, 0 failed** (baseline pre-3.6: 483; +33 stories echipe, +7 US-007).
**Per story (E2E browser pe `/`, mediu test, auth dev):**
- **US-003 PASS** — tab "Trimiteri" absent din tab-bar (Acasa·Mapari·Cont·Nomenclator); sectiunea "Trimiterile tale" randata sub upload cu heading + badge + filtre + tabel; `GET /?tab=coada` si `GET /_fragments/coada` servesc continut Acasa (200, contin "Trimiterile tale", fara fragment `_coada.html` orfan); cat preview-ul de import e activ, sectiunea Trimiteri e ascunsa (un singur sticky bar — D-1.2); wayfinding "Mapari"/"Coduri RAR" pastrat.
- **US-004 PASS** — upload comprimat la bara slim ("Importa:" + buton + "sau trage aici" + microcopy discret); drag-drop + select foaie pastrate; multi-sheet ok.
- **US-001 PASS** — `import_rows.override_json` (nullable, Fernet, `_migrate` defensiv) aplicat ULTIMUL in `_resolve_row_for_preview` (l.194) si `commit_import` (l.1070); ruta scoped JOIN→404, guard committed→409, empty=clear; verificat LIVE ca override COMPLETEAZA o coloana ABSENTA din fisier (fixture `import_lipsa_coloana.csv` fara coloana data → editarea adauga data → `ok`).
- **US-002 PASS** — buton "Editeaza" pe rand (aria-label cu nr.+VIN); swap pe `<tr>` + OOB pe rezumat/contoare (NU pe `#import-section`); form propriu (Enter nu declanseaza confirm); mutual-exclusion (Editeaza celorlalte randuri + "Trimite la RAR" dezactivate cat un rand e in editare); `hx-indicator` "se salveaza…"; reuse grila responsiva `_trimitere_detaliu.html`.
- **US-005/006 PASS** — "De rezolvat", "Operatii salvate" si "Formate de coloane" ca tabele `.tablewrap`; H4 confirmat (checkbox auto_send reflecta valoarea STOCATA: OP-AUTO bifat, OP-MANUAL nebifat); POST-uri neschimbate.
- **US-007 PASS** — comutator etichetat pe COADA ("La fisierele viitoare cu aceasta operatie:" + "Pune automat in coada" / caption "Tine pentru verificare … nimic nu pleaca la RAR pana confirmi"); fara "Manual"/"trimite"/"auto-send"; scoped pe operatie; `name="auto_send"` pastrat (semantica de prezenta → bool corect cu ambele parsere, zero backend); prezent in preview + ambele locuri din Mapari.
**E2E LIVE pe RAR test (trimitere reala):** import `import_lipsa_coloana.csv` (fara coloana data) → preview `needs_data` → Editeaza → completare data `2026-06-08` (override pe coloana absenta) → `ok` → commit "Trimite la RAR" → worker (`send_enabled=true`, creds `<test>`) login RAR test + nomenclator refresh (18 coduri) → `postPrezentare` → submission `sent`, `id_prezentare=68696`. Confirmat independent: `python3 -m tools.rar_finalizate` listeaza `68696 WVWZZZ1KZAW000456 2026-06-08 98765`. Dovedeste lantul complet override→commit→worker→RAR pe mediul real de test.
**Bug-uri prinse la VERIFY (JS, invizibile la TestClient) si reparate (htmx 1.9.12):**
1. `htmx.config.useTemplateFragments=true` (`base.html`) — raspunsul de editare (`<tr>` + OOB `#preview-rezumat`/`#preview-ok-count`) era parsat in context de tabel (`<table><tbody>`), foster-parenta div-urile OOB → `htmx:swapError` + contoare pierdute.
2. Re-activarea `#confirm-btn` dupa salvare deferita pe `setTimeout(0)` (`_preview_rand.html`) — altfel `updateN` rula cat inca exista `tr[data-editing="1"]` tranzitoriu → butonul "Trimite la RAR" ramanea blocat dupa o editare reusita.
3. `updateN` actualizeaza si numarul ok din `#n-hint` (`_preview_import.html`) — textul "(N ok)" ramanea stale dupa editare.
**Non-Goals respectate:** worker / masina stari / idempotenta-logica / mapping-rezolvare NEATINSE; singura atingere de schema = 1 coloana nullable `override_json` cu migrare defensiva (relaxare decisa la poarta autoplan, Approach B); `submissions` neatins de ruta de editare.
---
# AUTOPLAN REVIEW — APROBAT (2026-06-19)
> Pipeline /autoplan: CEO -> Design -> Eng. Mod: SELECTIVE EXPANSION. Vocile: codex `[indisponibil — usage limit]`
> + subagent Claude independent per faza. 12 corecturi auto-decise (audit trail mai jos) + 3 decizii la poarta,
> toate aprobate de user: (UC-A) US-007 framing pe coada; (poarta) Approach B `override_json`; (poarta) un PRD,
> valuri secventiate. **Stories revizuite cu toate fix-urile** (sectiunile 2-6 de mai sus reflecta decizia finala).
## FAZA 1 — CEO (strategie & scope)
### 0A. Premise Challenge
- **Premisa centrala** ("operatorii trebuie sa corecteze valori in preview, inainte de trimitere"):
VALIDA si confirmata de durerea reala — azi un VIN/data lipsa cere re-upload de fisier intreg.
- **Premisa de implementare** ("editarea = suprascriere `import_rows.raw_json`"): SUSPECTA.
`raw_json` e cheiat pe **numele coloanelor din fisier** (antet original), nu pe campuri canonice;
`_resolve_row_for_preview` (import_router.py:138-140) si commit-ul (import_router.py:922-945) re-aplica
`json_mapare` peste raw_json. O editare care scrie cheia canonica `vin` e ignorata pe orice fisier al
carui antet difera de numele canonic. Fixture-ul `tests/fixtures/test_data_mapping.csv` are din intamplare
antet = nume canonice -> testele ar trece, realul ar esua tacit. (vezi Eng pentru fix.)
- **Premisa "completez informatiile lipsa"**: cazul in care campul lipsa e o **coloana inexistenta in fisier**
nu are `col_f` in care sa scrie -> raw_json nu poate exprima valoarea. Premisa promisa userului depaseste
ce poate face stocarea actuala fara un strat de override. (escaladata la poarta — vezi UC-1.)
- **Premisa "muta Trimiteri pe Acasa"**: VALIDA — microcopia "vezi mai jos trimiterile" deja minte azi.
- **Premisa de impachetare** (4 schimbari intr-un PRD): cele 4 NU sunt cuplate tehnic. Singura legatura e
"incap pe ecranul Acasa". Risc de scope: editarea celulelor (cu fix-ul de stocare) e de departe cea mai
riscanta; restul sunt cosmetice. (vezi 0F + decizie de gust DG-1: split.)
### 0B. Existing Code Leverage (ce exista deja)
| Sub-problema | Cod existent care o rezolva partial/total |
|---|---|
| Editare campuri de continut + re-validare + re-enqueue | **US-010 corectie inline** (`routes.py:583` `post_corectie_trimitere`, `_trimitere_detaliu.html:44-84`) — exact aceleasi campuri (vin/nr/data/odo), validare, detectie coliziune idempotency. **US-001/002 trebuie sa REFOLOSEASCA acest tipar, nu sa-l reconstruiasca.** |
| Tabele compacte | `.tablewrap` + `_submissions.html` (deja tabel cu scroll orizontal) |
| Sectiunea Trimiteri pe Acasa | `_submissions.html` + filtre US-009 + `#trimitere-detaliu` — se includ ca atare |
| Upload drag-drop | JS din `_upload.html:74-107` — se pastreaza, doar markup-ul se comprima |
| Re-randare preview cu stare recalculata | `web_preview_import` + `_resolve_row_for_preview` — editarea doar schimba sursa |
Concluzie: PRD-ul e in mare parte re-cablare a unor fluxuri existente. Singurul cod nou real e ruta de
editare (US-001) + modul-editare pe rand (US-002). Bun pentru DRY, dar US-001 NU trebuie sa-si scrie propria
logica de validare/idempotency — o are in `corectie`.
### 0C. Dream State
```
CURRENT THIS PLAN 12-MONTH IDEAL
Import = ecran read-only; Editezi randul in preview; Import = foaie editabila live,
Trimiteri in alt tab; --> Trimiteri sub upload pe Acasa; --> mapare AI (Etapa 4.1),
corectie doar post-trimitere; un singur ecran operational o singura suprafata, zero re-upload
```
Planul muta sistemul CATRE idealul "o singura suprafata operationala". Aliniat.
### 0C-bis. Implementation Alternatives (pentru editarea de celule — nucleul riscant)
```
APPROACH A: Suprascriere raw_json cu reverse-map canonical->col_fisier
Summary: ruta editare mapeaza campul canonic inapoi la coloana de fisier via json_mapare, scrie raw_json.
Effort: M Risk: High
Pros: fara schema noua (respecta Non-Goal); o singura sursa de adevar
Cons: NU poate completa un camp a carui coloana lipseste din fisier (cazul "informatii lipsa" real);
reverse-map ambiguu daca doua coloane mapeaza pe acelasi camp; fragil
APPROACH B: Coloana noua import_rows.override_json (patch canonic), aplicata ultima in resolver+commit
Summary: editarea scrie un dict canonic de override; resolver si commit il suprapun peste mapped.
Effort: M Risk: Low
Pros: exprima ORICE camp, inclusiv cele absente din fisier; nu atinge semantica raw_json/idempotency;
migrare defensiva _migrate ca la PRD-urile 3.3b/3.5
Cons: incalca Non-Goal "fara modificari schema" (1 ALTER, mic); 2 puncte unde se aplica override-ul
APPROACH C: Fara editare in preview — extinde US-010 corectie sa fie accesibila si pre-commit
Summary: confirma intai, apoi corecteaza in Trimiteri (fluxul existent), fara editare in preview.
Effort: S Risk: Low
Cons: contrazice decizia explicita a userului (editare IN preview); pune o trimitere "rea" in coada intai
```
**RECOMMENDATION: Approach B** — singurul care onoreaza promisiunea "completez informatii lipsa" (campuri
absente din fisier), cu risc mic si fara sa atinga idempotency/worker. Costul: relaxarea Non-Goal-ului de schema
(1 coloana nullable + migrare defensiva). -> poarta finala UC-1.
### 0D. Selective Expansion — complexity check
- 7 stories, ~8 fisiere atinse, 0 clase/servicii noi. Sub pragul de smell (8 fisiere / 2 clase). OK.
- Minimum care livreaza valoare: US-001+US-002 (editarea) SAU US-003 (Acasa unificata) — sunt independente.
- Expansiuni candidate (cherry-pick, auto-decise spre DEFER per P3/P6): buton "repara toate needs_review",
editare bulk, undo editare. Toate -> TODOS (nu in blast radius imediat).
### 0E. Temporal Interrogation
- HOUR 1: userul importa, vede un rand needs_data, da Editeaza, completeaza data, Salveaza -> ok. Functioneaza
DOAR daca stocarea editarii e corecta (Approach B). Cu Approach A + fisier cu antet ne-canonic: editarea pare
ca se salveaza dar randul ramane needs_data -> incredere distrusa.
- HOUR 6+: zeci de fisiere cu formate diverse de antet. Approach A esueaza pe majoritatea; Approach B tine.
### 0F. Mode Selection: **SELECTIVE EXPANSION** confirmat. Scope-ul de baza tinut; o singura expansiune ceruta
(override_json), restul deferite. Decizie de structura (split 4-in-1) -> DG-1 la poarta.
### Step 0.5 — Voci duale CEO
- **Codex (CLI)**: `[codex-unavailable: usage limit]` — a citit contextul dar a esuat la analiza pe limita de cont.
- **Subagent Claude (independent)**: a confirmat din cod premisa centrala (`web_confirma_import` -> `_upload.html`
cu "urmareste coada de mai jos", dar coada e in alt tab => microcopia minte azi). Findings principale:
US-001 dubleaza motorul existent `post_corectie_trimitere` (routes.py:583-717); US-007 "Automat/Manual"
risca interpretare gresita de send-safety (auto_send e per-operatie, nu global, peste declaratii ireversibile);
US-004 e consecinta US-003, nu peer; Acasa ajunge sa ruleze DOUA poll-uri (status 15s + trimiteri ~10s);
recomanda split (groful de valuri din PRD demonstreaza independenta).
```
CEO DUAL VOICES — CONSENSUS TABLE (Codex N/A — subagent-only)
═══════════════════════════════════════════════════════════════
Dimensiune Claude Codex Consensus
─────────────────────────────────── ─────── ─────── ─────────
1. Premise valide? partial N/A DG: editare-in-staging vs reuse corectie
2. Problema corecta? da N/A CONFIRMAT (US-003 e valoarea reala)
3. Scope calibrat? nu N/A FLAG: 4-in-1, recomandare split (DG-1)
4. Alternative explorate? nu N/A FLAG: reuse post_corectie_trimitere ne-analizat
5. Riscuri (send-safety)? da N/A FLAG CRITIC: US-007 Automat/Manual (UC-2)
6. Traiectorie 6 luni sound? partial N/A US-003 da; US-001 = tax de mentenanta daca dubleaza
═══════════════════════════════════════════════════════════════
Single critical din subagent (US-007 send-safety, US-001 DRY) = flagged regardless.
```
### CEO Sections 1-10 (rezumat per sectiune — examinat, nu doar numit)
1. **Strategic fit**: US-003 aliniat la idealul "o suprafata operationala"; restul cosmetic. OK.
2. **Error & Rescue**: editarea poate introduce valori care re-blocheaza randul (data invalida) — trebuie eroare
pe camp, nu pierdere de input (acoperit US-002 AC). Nicio cale de eroare tacuta noua daca se reda preview-ul.
3. **DRY (CRITIC)**: US-001 vs `post_corectie_trimitere` — aceeasi validare/canonicalizare/idempotency. Reuse obligatoriu.
4. **Send-safety (CRITIC)**: US-007 — "Manual" suna global peste un sistem cu declaratii ireversibile. Reformulare ceruta.
5. **Observability**: editarea ar trebui sa lase un log (cine/cand a editat un rand de staging) — minor, GDPR-friendly.
6. **Scope/impachetare**: cele 4 sunt independente (graf valuri). Recomandare split (DG-1).
7. **Reuse UI**: `.tablewrap`, `_submissions.html`, filtre US-009, upload JS — reutilizate corect.
8. **Page-weight**: dublu-poll pe Acasa (status 15s + trimiteri 10s) — de consolidat/confirmat (Eng#4).
9. **Migrare deep-link**: `?tab=coada` trebuie sa nu dea 404 (acoperit US-003).
10. **6-month**: US-001 fara reuse = doua locuri de intretinut; US-005/006 cosmetice = risc minim.
### Mandatory outputs — FAZA 1
**NOT in scope (deferite la TODOS):** buton "repara toate needs_review", editare bulk multi-rand, undo editare,
log de audit pe editare de staging. **Ce exista deja (refolosit):** `post_corectie_trimitere`, `.tablewrap`,
`_submissions.html`+filtre US-009, upload JS, `_resolve_row_for_preview`. **Dream delta:** planul muta catre
"o singura suprafata"; gap ramas = mapare AI (Etapa 4.1). **Failure modes:** (a) editare ignorata tacit pe fisier
cu antet ne-canonic [CRITIC, Eng]; (b) camp lipsa fara coloana sursa nu poate fi completat [CRITIC]; (c) "Manual"
interpretat global [CRITIC]; (d) dublu-poll pe pagina mereu deschisa [mediu].
> **FAZA 1 COMPLETE.** Codex: indisponibil (usage limit). Subagent Claude: 6 findings (2 critice).
> Consensus: 1/6 confirmat clar, 4 flag-uri -> poarta. Trec la Faza 2 (Design).
## FAZA 3 — Eng (arhitectura, teste, securitate)
> Codex `[codex-unavailable: usage limit]`. Subagent Claude independent (context curat).
### Step 0.5 — Eng consensus
```
ENG DUAL VOICES — CONSENSUS TABLE (Codex N/A — subagent-only)
═══════════════════════════════════════════════════════════════
Dimensiune Claude Codex Consensus
─────────────────────────────────── ─────── ─────── ─────────
1. Arhitectura sound? nu N/A FLAG: US-001 stocare gresita (C1)
2. Acoperire teste suficienta? nu N/A FLAG: fixture mascheaza bug; lipsesc anteturi ne-canonice
3. Riscuri performanta? partial N/A FLAG: dublu-poll Acasa (M5)
4. Securitate (scoping)? da N/A OK cu nota: query scoped JOIN (M7)
5. Cai de eroare? nu N/A FLAG: decrypt fail, empty edit, batch committed (L9-L11)
6. Risc deploy? da N/A OK: 1 ALTER defensiv (override_json) ca 3.3b/3.5
═══════════════════════════════════════════════════════════════
```
### Findings Eng (cu dovezi cod)
- **C1 [CRITIC] — US-001 stocare gresita.** `raw_json` cheiat pe anteturi fisier; resolver+commit re-aplica
`json_mapare` (import_router.py:138-140, 928-930). Scrierea cheii canonice e ignorata pe antet ne-canonic;
camp fara coloana-sursa nu poate fi exprimat deloc. Fix = **Approach B** (`import_rows.override_json` nullable,
Fernet, aplicat ULTIMUL in resolver + commit). Cere rescrierea AC US-001 (liniile actuale incoded Approach A)
+ relaxare Non-Goal schema (1 ALTER + `_migrate` defensiv). -> poarta UC-1.
- **H2 [HIGH] — DRY.** US-001 nu trebuie sa-si re-deriveze statusul; recalcul OBLIGATORIU prin `_resolve_row_for_preview`
(altfel drift de clasificare vs preview, iar clasificarea controleaza send-ul). Ruta editare = mutatie pura de stocare.
- **H3 [HIGH] — US-007 eticheta.** Scopeaza la operatie: "Aceasta operatie: Automat / Manual" + microcopy "pentru
aceasta operatie". `name="auto_send"` pastrat (zero backend) e corect.
- **H4 [MED] — US-005/007.** Tabelul "mapari salvate" trebuie sa randeze starea `auto_send` STOCATA (routes.py:738),
nu sa hard-defaulteze Automat.
- **M5 [MED] — dublu-poll Acasa.** status 15s (dashboard.html:14-17) + submissions 10s (_coada.html:43-45) pe pagina
mereu deschisa. Fix: aliniaza submissions la 15s SAU `hx-trigger` gated pe `document.visibilityState==='visible'`.
- **M6 [MED] — `/_fragments/coada`.** `?tab=coada` cade deja pe Acasa (routes.py:266), dar fragmentul `/_fragments/coada`
(routes.py:309-313) inca randeaza `_coada.html` orfan. Fix: fragmentul redirectioneaza la continutul Acasa; test dedicat.
- **M7 [MED] — scoping editare.** O singura interogare scoped JOIN `import_rows r JOIN import_batches b ... WHERE
b.account_id=? AND r.row_index=?` -> 404 pe gol. Web alias sub `require_login`, `/v1/...` sub `resolve_account_id`.
- **M8 [MED] — coercion.** `canonicalize_row` normeaza DOAR `odometru_final` (idempotency.py:44), nu `_initial`.
Testul de coercion sa nu afirme strip prin canonicalize pe `odometru_initial`.
- **L9-L12 [LOW/MED]:** test decrypt-fail (raw corupt -> 422/no-op); semantica empty-input (ignore vs clear — defineste
explicit, cazul "completez" poate cere clear); guard editare pe batch `committed` (409, ca import_router.py:822-823);
setup test panel preview (seed unmapped_op).
### Mandatory outputs — FAZA 3
**Diagrama arhitectura (override_json, Approach B):**
```
upload ─▶ import_rows(raw_json[antet fisier], override_json[canonic]=NULL)
editare rand ─────────┘ scrie override_json[camp_canonic]=valoare (mutatie pura)
GET preview ─▶ _resolve_row_for_preview:
mapped = json_mapare(raw_json) ◀── pas existent
mapped.update(override_json) ◀── PAS NOU (aplicat ultimul)
─▶ validate ─▶ status (ok/needs_data/...)
commit ─▶ acelasi merge override peste mapped ─▶ submissions
```
**Test diagram (coverage):** randuri noi de acoperit — (a) fisier antet ne-canonic editat -> ok [LIPSESTE azi];
(b) camp fara coloana-sursa completat -> ok [LIPSESTE]; (c) edit == GET preview status [LIPSESTE]; (d) decrypt
fail; (e) empty-input semantics; (f) batch committed -> 409; (g) scoped 404 alt cont; (h) `/_fragments/coada` fallback.
**NOT in scope:** undo, bulk. **Ce exista:** `_resolve_row_for_preview`, `post_corectie_trimitere`, `_migrate`.
**Failure modes critice:** C1 (acoperit de Approach B); fixture verde-dar-stricat (acoperit de fixture antet ne-canonic).
> **FAZA 3 COMPLETE.** Subagent: 1 critic, 3 high, 4 medium, 4 low. Trec la sinteza + poarta.
## FAZA 2 — Design (ierarhie, stari, interactiune)
> Codex `[codex-unavailable]`. Subagent Claude independent (a citit toate cele 8 template-uri).
### Design litmus scorecard (0-10)
| Dimensiune | Scor | Nota |
|---|---|---|
| Ierarhie informatie | 5 | upload demotat la slim + tabel sub el => monitorizarea domina actiunea principala |
| Stari (loading/error/partial) | 4 | lipsesc spinner save, eroare server, scroll-to-row, empty unificat |
| Interactiune edit-mode | 3 | cea mai slaba zona — swap pe toata sectiunea distruge starea (CRITIC) |
| Copy Automat/Manual | 4 | "Manual" citit global => contrazice promisiunea de siguranta |
| First-run vs returning | 5 | first-run pierde hero "Primul fisier?", 3 nudge-uri redundante |
### Findings Design (severitate)
- **D-3.1 [CRITIC] — swap distructiv.** `hx-target="#import-section"` + `outerHTML` la editare re-randeaza tot;
pierde bife `reviewed_rows`, `n_confirmat`, filtrul activ, alte randuri in editare. Fix: swap DOAR randul `<tr>`
+ OOB pe rezumat/contoare.
- **D-3.3 [HIGH] — Enter = trimitere ireversibila.** Input-urile de editare in acelasi `#confirm-form` cu butonul
"Trimite la RAR". Fix: randul de editare = FORM PROPRIU; dezactiveaza confirm cat un rand e in editare. Test:
Enter in camp editare NU declanseaza confirm.
- **D-2.1 [HIGH] — fara stare "se salveaza".** Re-randare completa fara `hx-indicator`/buton dezactivat. Fix: spinner pe rand.
- **D-2.2 [HIGH] — eroare server = pierdere tacuta de date.** `outerHTML` pe sectiune la 500/CSRF/timeout poate goli
`#import-section`. Fix: `hx-on::response-error` pastreaza randul + banner; test dedicat.
- **D-3.4 [HIGH] — integritate N la editare.** Re-randarea reseteaza `n_confirmat`. Fix: blocheaza confirm la editare + recalcul autoritar.
- **D-3.5 [HIGH] — mobil inutilizabil.** ~10 coloane cu input-uri in container cu scroll orizontal. Fix: pe viewport ingust,
randul de editare = card stacked (refoloseste grila din `_trimitere_detaliu.html`).
- **D-4.1 [HIGH] — "Automat/Manual" contrazice "nu se trimite nimic pana confirmi".** Fix: framing pe COADA, nu pe trimitere:
"La fisierele viitoare cu aceasta operatie: [Pune automat in coada] / [Tine pentru verificare]". -> poarta UC-2.
- **D-1.1/1.2 [HIGH/MED] — upload demotat + coexistenta preview vs Trimiteri.** Fix: upload slim DAR accentuat/distinct;
ascunde Trimiteri cat un preview de import e activ (un singur sticky bar pe ecran); heading "Trimiterile tale".
- **D-3.2/3.6/5.1/5.3 [MED/LOW]:** mutual-exclusion + debounce pe Editeaza; warn pe navigare cu editare dirty;
first-run pastreaza hero "Primul fisier?" cand nu exista trimiteri (suprima empty-state redundant); pastreaza wayfinding Mapari/Coduri.
- **Lauda:** refolosirea literala a markup-ului din `_trimitere_detaliu.html` (grila responsiva + error-map + scroll-to-row)
rezolva D-2.2, D-3.5, D-2.5 gratis. -> AC HARD pe US-002.
```
DESIGN CONSENSUS (Codex N/A — subagent-only): 1 critic, 7 high, 5 med, 3 low.
Gate de design: D-3.1 + D-3.3 (swap distructiv + Enter-trimite) = problema de corectitudine/siguranta, nu rafinament.
```
> **FAZA 2 COMPLETE.** Trec la sinteza cross-faza + poarta finala.
## SINTEZA CROSS-FAZA
**Teme care apar in 2+ faze (semnal de incredere mare):**
1. **Refolosire `post_corectie_trimitere` / `_trimitere_detaliu.html`** — CEO (0B), Eng (H2), Design (lauda). Reuse = AC hard pe US-001/US-002.
2. **US-007 "Automat/Manual" periculos** — CEO (UC-2), Eng (H3), Design (D-4.1). Reformulare ceruta. -> poarta UC-A.
3. **Stocare editare gresita (C1)** — CEO (0A), Eng (C1). Approach B (override_json) + fixture cu antet ne-canonic.
## DECISION AUDIT TRAIL (auto-decise pe cele 6 principii)
| # | Faza | Decizie | Clasificare | Principiu | Rationament |
|---|---|---|---|---|---|
| 1 | Eng | C1: adopta Approach B (override_json) in loc de raw_json | Auto | P1,P5 | Approach A e stricat (ignora editari pe antet ne-canonic; nu poate completa coloane absente) |
| 2 | Eng | US-001 = mutatie pura; status recalculat prin `_resolve_row_for_preview` | Auto | P4 DRY | evita drift de clasificare care controleaza send-ul |
| 3 | Design | Edit swap pe rand `<tr>` + OOB contoare, NU pe `#import-section` | Auto | P1,P5 | swap pe sectiune distruge starea neclickata |
| 4 | Design | Rand editare = FORM propriu + confirm dezactivat la editare | Auto | P1 | previne Enter->trimitere ireversibila |
| 5 | Design | Refolosire grila `_trimitere_detaliu.html` (responsiv + error-map + scroll) | Auto | P4 DRY | rezolva mobil+eroare+scroll gratis |
| 6 | Design | Spinner save + `hx-on::response-error` pastreaza randul | Auto | P1 | fara pierdere tacuta de date |
| 7 | Eng | M5: poll trimiteri gated pe `document.visibilityState` (sau 15s) | Auto | P3 | reduce load perpetuu pe pagina mereu deschisa |
| 8 | Eng | M6: `/_fragments/coada` -> continut Acasa (nu doar `?tab=coada`) | Auto | P1 | evita fragment orfan din bookmark vechi |
| 9 | Eng | M7: query scoped JOIN import_rows×import_batches -> 404 | Auto | P1 sec | fara scope-leak/TOCTOU |
| 10 | Eng | M8: scoate afirmatia canonicalize pe odometru_initial | Auto | P5 | `canonicalize_row` normeaza doar `_final` |
| 11 | Eng | L11: guard editare pe batch `committed` -> 409 | Auto | P1 | editare post-commit nu are efect downstream |
| 12 | Design | First-run pastreaza hero "Primul fisier?"; slim accentuat; pastreaza wayfinding Mapari/Coduri | Auto | P1 | discoverability first-run |
| UC-A | CEO/Design | US-007 reformulare labels (user a ales explicit "Automat/Manual") | USER CHALLENGE | — | toti reviewerii: risc send-safety. User decide. |
| DG-1 | CEO | Split: ship US-003(+004) intai, US-001/002 dupa reuse, US-005/006 batch, US-007 redesign | Taste | P6 | cele 4 sunt independente (graf valuri) |
---
## Raport CLOSE (2026-06-22)
CLOSE conform ROADMAP §5.8 pe diff-ul livrabilei (`ead6324..178bc87`, doar `app/`).
**`/code-review` high** (8 unghiuri finder + verificare, recall-biased):
- **REPARAT (1 bug real, corectitudine).** Decriptarea `override_json` era in afara `try/except`-ului
care protejeaza `raw_json` in ambele cai de preview:
- `app/api/v1/import_router.py` `preview_import` (linia 699)
- `app/web/routes.py` `_web_compute_preview` (linia 1061)
La rotatie cheie Fernet (risc acceptat R4) sau token corupt, `raw_json` degrada gratios la `{}`
dar `override_json` arunca exceptie -> 500 pe TOT batch-ul in loc de preview cu override gol.
Fix: `override_json` ambalat in `try/except` identic cu `raw_json` (fallback `None` -> `{}`).
523 teste pass dupa fix.
- **NOTAT, nereparat (cleanup viitor — disciplina "backend trimitere neatins").** Duplicare
`_override_of` (decriptare override) + blocul "canonicalize dupa override" in 3-4 locuri
(preview/commit pe canalul API vs. web). Refactor intr-un resolver partajat = candidat de cleanup,
in afara scopului unui pas de CLOSE. Verificat ca cele 4 cai sunt logic identice (preview si commit
produc aceeasi cheie de idempotenta), deci nu e bug azi.
- Restul candidatelor (form-binding HTML, fallback tab `coada`->`acasa`, falsy-zero pe override)
REFUTED la verificare: comportament intentionat (US-003/004) sau deja corect in cod.
**Convenții (CLAUDE.md):** fara incalcari (RO peste tot, fara emoji, invariante idempotenta/scoping/
422-no-echo pastrate).
Toate PASS -> writeback dashboard (DONE, 2026-06-22) + PRD `**Stare**: inchis`.