docs(5.9): PRD corectie modal mobil + scaffold Ralph (prd.json cu R1-R12)

- docs/prd/prd-5.9-ux-corectie-modal-mobil.md: PRD aprobat (8 stories, raport AUTOPLAN)
- scripts/ralph/: ralph.sh + prompt.md (smart-gates) + progress.txt
- scripts/ralph/prd.json: 8 user stories cu reviziile obligatorii R1-R12 incorporate
  in acceptance criteria; priority encodeaza valurile de dependente
- .gitignore: runtime Ralph (logs/screenshots/archive/.last-branch/usage.jsonl)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Claude Agent
2026-06-24 21:09:08 +00:00
parent 32408ed3b5
commit 0ba728cab5
6 changed files with 1240 additions and 0 deletions

8
.gitignore vendored
View File

@@ -81,3 +81,11 @@ venv/
# --- Playwright MCP: artefacte sesiune browser (snapshot-uri, stare locala) ---
.playwright-mcp/
# --- Ralph: runtime loop autonom (stare locala, nu artefact de proiect) ---
scripts/ralph/logs/
scripts/ralph/screenshots/
scripts/ralph/archive/
scripts/ralph/.last-branch
scripts/ralph/.ralph.pid
scripts/ralph/usage.jsonl

View File

@@ -0,0 +1,482 @@
<!-- /autoplan restore point: /home/claude/.gstack/projects/romfast-rar-autopass/main-autoplan-restore-20260624-203156.md -->
# PRD 5.9 — Corectie inline in modal + consolidare actiuni + design mobil
**Stare**: draft
> 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
Curatenie UX pe ecranul de Trimiteri, fara nicio schimbare a fluxului de trimitere catre RAR.
Raportat de utilizator dupa 5.8: containerul de detaliu s-a aglomerat si nu mai e clar la ce sa
te uiti. Sase corectii, toate UI/prezentare:
1. **Detaliul + corectia trec intr-un formular MODAL** (overlay centrat, in afara zonei
`#submissions-wrap` reincarcata la 15s). Rezolva direct bug-ul „formularul se inchide singur dupa
cateva secunde / se sterg bifele" (azi randul-sibling de detaliu traieste IN regiunea de poll, iar
bifele de bulk se pierd la fiecare refresh) si „nu se distinge marginea containerului de restul
tabelului" (overlay clar delimitat). **Inlocuieste** abordarea inline-row din 5.8 US-008.
2. **Editare in-place fara dublarea valorilor**: azi fiecare camp apare de DOUA ori in detaliu — o
data read-only (`_trimitere_detaliu.html:19-32`) si inca o data ca input in „Corecteaza si
re-trimite" (`:130-156`). Modalul afiseaza fiecare camp O SINGURA DATA, direct editabil.
3. **Consolidarea butoanelor**: azi „Re-pune in coada", „Sterge" si „Salveaza si re-pune in coada"
sunt imprastiate in 2 locuri cu culori similare (`:53-69` + `:153-155`), plus „Sterge selectate"
bulk pe tabel. Ramane **un** singur buton principal „Salveaza si retrimite" (salvarea repune mereu
in coada; retry pur = salvezi fara modificari) + **un** „Sterge" rosu, vizibil distantat. Bulk
„Sterge selectate" ramane DOAR pe tabel.
4. **Fara chevron pe randuri** (`_submissions.html:53`) — inutil odata ce detaliul e modal.
5. **Codul de eroare in tabel**, sub eticheta de stare (ex. `RAR_EROARE_SERVER`, `COD_NEMAPAT`), doar
pe randurile cu problema — fara coloana noua (prietenos mobil).
6. **Cod RAR simplu** in tabel (fara prefixul „cod RAR:", `_submissions.html:66`) si in modal (fara
campul etichetat „Cod RAR" separat — codul apare langa operatie).
Plus, transversal: **design responsive/mobil pentru TOATE paginile dashboard** (azi exista carduri
doar pe tabelul de trimiteri sub 768px, din 5.8; restul paginilor — Mapari, Cont, Nomenclator,
Integrare, Jurnal, Admin, login/signup — si modalul nou nu au tratament mobil).
## 2. Non-Goals (anti scope-creep)
- Fara modificari la worker, masina de stari, contractul RAR, idempotenta sau logica de mapare.
Rutele backend (`/corecteaza`, `/repune`, `/sterge`, `/mapeaza`, `/sterge-bulk`) raman ca semnatura
si comportament — se schimba DOAR `hx-target`-ul (modalul) si prezentarea.
- Fara redesign vizual al continutului paginilor (culori, tipografie, structura de informatie) —
doar **adaptare responsive** (stivuire, latimi, tinte touch) pe layout-ul existent.
- Fara animatii elaborate de deschidere modal — un fade/scale simplu e destul (APP UI).
- Nu schimbam ce campuri sunt editabile (raman `nr_inmatriculare`, `vin`, `data_prestatie`,
`odometru_final`, `odometru_initial`) si nici validarea lor (`corecteaza` neatins logic).
- Fara stergerea sectiunii de mapare inline (5.7) — se muta ca atare in modal.
- Fara framework CSS nou (Bootstrap/Tailwind) — extindem variabilele si `base.html` existente.
## 3. Stories atomice
> Backend/date + UI pentru acelasi comportament = stories separate. `Fisiere` + `Depinde de`
> complete decid paralelizarea.
---
### US-001: `cod_eroare` scurt disponibil pe randul de afisare
**Ca** dezvoltator **vreau** codul de eroare (cheia din catalog) expus pe randul tabelului
**pentru ca** sa-l pot afisa sub eticheta de stare fara sa re-parsez `rar_error` in template.
- **Depinde de**: —
- **Fisiere**: `app/web/routes.py` (`_submission_row_view`), `app/web/labels.py` (helper extragere cod),
`tests/test_web_submissions.py`
- **Test intai (RED)**: `tests/test_web_submissions.py`
`test_cod_eroare_prezent_pe_rand_error`, `test_cod_eroare_prezent_pe_needs_mapping`,
`test_cod_eroare_gol_pe_rand_ok`
- **Acceptance criteria**:
- [ ] `_submission_row_view` intoarce in plus `cod_eroare: str` = codul scurt din catalog
(`app/errors.py` `cod`, ex. `RAR_EROARE_SERVER`, `COD_NEMAPAT`) extras din `rar_error`
(superset 5.4) sau derivat din `status` cand nu exista `rar_error` (ex. `needs_mapping →
COD_NEMAPAT`). Sir gol pe stari fara problema (`queued/sending/sent`).
- [ ] Extragerea e un helper pur in `labels.py` (ex. `cod_eroare_din(status, rar_error) -> str`),
defensiv la `rar_error` lipsa/JSON invalid (intoarce `""`, nu ridica).
- [ ] `prez`, `motiv`, `stare_*` raman neschimbate (aditiv).
- [ ] `python3 -m pytest tests/test_web_submissions.py -q` trece.
- **Verificare E2E**: —
### US-002: Tabel trimiteri — cod eroare sub stare, fara chevron, cod RAR simplu
**Ca** operator **vreau** sa vad codul problemei direct in tabel si un rand mai curat **pentru ca**
sa stiu dintr-o privire ce rand are nevoie de mine.
- **Depinde de**: US-001, US-003 (randul declanseaza modalul)
- **Fisiere**: `app/web/templates/_submissions.html`, `app/web/templates/base.html` (CSS),
`tests/test_web_submissions.py`
- **Test intai (RED)**: `tests/test_web_submissions.py`
`test_cod_eroare_apare_sub_pill`, `test_fara_chevron_in_rand`,
`test_cod_rar_fara_prefix_text`, `test_rand_deschide_modal`
- **Acceptance criteria**:
- [ ] Sub pill-ul de Stare apare `r.cod_eroare` (text mic, muted, `s-error` pe `error`/`needs_*`),
DOAR cand e ne-gol. Nu se transmite stare doar prin culoare (codul e text).
- [ ] **Chevron eliminat** din coloana `#` (`_submissions.html:52-53`) si din CSS/JS asociat.
- [ ] Coloana Operatie linia 2: doar codul (ex. `OE-2`), **fara** prefixul „cod RAR:" — un
`<span>` muted/pill; cand nemapat afiseaza „nemapat" muted (comportament 5.8 pastrat).
- [ ] Randul (`tr.trimitere-row`) declanseaza deschiderea modalului (US-003): `hx-get
/_fragments/trimitere/{id}` cu `hx-target` pe corpul modalului, NU pe un rand-sibling.
Randul-sibling `<tr class="detaliu-rand">` din 5.8 este **eliminat**.
- [ ] Pe touch tinta randului ramane >=44px; randul ramane `role="button"`, `tabindex="0"`,
focusabil; Enter/Space deschid modalul.
- [ ] `python3 -m pytest tests/test_web_submissions.py -q` trece.
- **Verificare E2E**: gstack browser pe `/` — un rand `error` arata codul sub pill; nu exista
chevron; codul RAR apare fara „cod RAR:"; click pe rand deschide modalul (nu rand-sibling).
### US-003: Component modal reutilizabil (overlay, focus-trap, a11y)
**Ca** operator **vreau** ca detaliul sa apara intr-o fereastra modala clar delimitata **pentru ca**
sa nu mai confund marginea cu tabelul si sa nu mi se mai inchida formularul la refresh.
- **Depinde de**: —
- **Fisiere**: `app/web/templates/base.html` (markup `#modal-detaliu` + CSS + JS),
`app/web/templates/_coada.html` (ancora globala in afara `#submissions-wrap`),
`tests/test_web_modal.py`
- **Test intai (RED)**: `tests/test_web_modal.py` —
`test_modal_container_in_afara_submissions_wrap`,
`test_fragment_detaliu_tinteste_modalul`
- **Acceptance criteria**:
- [ ] Container modal global `<div id="modal-detaliu" role="dialog" aria-modal="true" hidden>` cu
backdrop, plasat **in afara** `#submissions-wrap` (deci poll-ul de 15s nu-l atinge). Corpul
modalului (`#detaliu-modal-body`) e tinta de swap pentru fragmentul de detaliu si pentru
rutele de corectie/mapare/lifecycle.
- [ ] Deschidere: la `htmx:beforeRequest` de pe un `tr.trimitere-row`, se arata backdrop+dialog
(placeholder „Se incarca…" prin `hx-indicator` pana raspunde HTMX) si se muta focusul in
modal. Un singur modal o data.
- [ ] Inchidere prin: buton `x`, tasta `Esc`, click pe backdrop. La inchidere: `hidden`, corpul
golit, focus readus pe randul declansator.
- [ ] **Focus-trap**: Tab/Shift+Tab cicleaza in interiorul modalului cat e deschis; restul paginii
primeste `aria-hidden`/`inert` (sau echivalent). Scroll-ul body blocat cat modalul e deschis.
- [ ] Tot codul inline-expand din 5.8 (chevron, `tr.detaliu-rand`, `marcheazaDetaliuDeschis`,
`inchideDetaliu` pe rand, toggle single-open pe rand) este **inlocuit** de logica modalului;
`window.inchideDetaliu(id)` ramane expus (apelat de butonul „Inchide") dar inchide modalul.
- [ ] `#trimitere-detaliu` global inert din `_coada.html` este eliminat (rolul lui e preluat de
`#modal-detaliu`).
- [ ] `python3 -m pytest tests/test_web_modal.py -q` trece.
- **Verificare E2E**: gstack browser pe `/` — click pe rand deschide modal centrat cu fundal
estompat; Esc / click-afara / x inchid si readuc focusul; Tab nu paraseste modalul.
### US-004: Detaliu editabil in-place + butoane consolidate (in modal)
**Ca** operator **vreau** sa corectez datele direct, o singura aparitie per camp, cu un buton clar
**pentru ca** sa nu mai vad valorile de doua ori si sa nu mai ezit intre butoane.
- **Depinde de**: US-003
- **Fisiere**: `app/web/templates/_trimitere_detaliu.html`, `app/web/routes.py` (`_detaliu_ctx` —
doar context de prezentare, fara logica noua), `tests/test_web_corectie.py`
- **Test intai (RED)**: `tests/test_web_corectie.py` —
`test_camp_apare_o_singura_data`, `test_nr_si_vin_pe_randuri_separate`,
`test_un_singur_buton_salveaza`, `test_fara_buton_repune_separat`,
`test_sterge_prezent_si_distinct`, `test_corectie_pastreaza_comportament` (regresie pe ruta)
- **Acceptance criteria**:
- [ ] **Zero dublare**: blocul read-only de grila (`_trimitere_detaliu.html:19-32`) si formularul
separat de corectie se contopesc intr-un singur formular cu campuri editabile pre-completate.
Campurile editabile (`nr_inmatriculare`, `vin`, `data_prestatie`, `odometru_final`,
`odometru_initial`) apar O SINGURA DATA.
- [ ] **Layout cerut**: `Numar inmatriculare` pe propriul rand (latime plina), `VIN (serie sasiu)`
pe randul de DEDESUBT (latime plina). Restul campurilor pot ramane in grila.
- [ ] **Cod RAR simplu**: nu mai exista campul etichetat „Cod RAR" separat (`:23`); operatia +
codul RAR rezolvat apar impreuna, ca context read-only (ex. „Operatie: Revizie · OE-2"),
fara eticheta „Cod RAR". Campurile pur contextuale (Nr. prezentare RAR, Cod HTTP, Reincercari,
timestampuri) raman read-only.
- [ ] **Un singur buton principal** `Salveaza si retrimite` (clasa primara) — posteaza pe
`/trimitere/{id}/corecteaza` (neschimbat); salvarea repune mereu in coada (retry pur =
salvezi fara a schimba nimic). Butonul „Re-pune in coada" gol (`:55-59`) este **eliminat**.
- [ ] **Un singur `Sterge`** (clasa distructiva, rosu, `var(--err)`), plasat **vizibil distantat**
de butonul principal (ex. `margin-left:auto` / rand separat), cu `hx-confirm`. Posteaza pe
`/trimitere/{id}/sterge` (neschimbat).
- [ ] Sectiunea de **mapare inline** (5.7, `:71-116`) ramane functional identica, mutata in modal,
tintind corpul modalului.
- [ ] Erorile de camp (`err_map`, `aria-invalid`, mesaj inline) si flash-ul de corectie raman.
- [ ] Comportamentul rutei `/corecteaza` (validare, idempotency, re-rezolvare) e **neschimbat** —
testul de regresie confirma acelasi rezultat ca azi.
- [ ] `python3 -m pytest tests/test_web_corectie.py -q` trece.
- **Verificare E2E**: gstack browser — deschid un rand `needs_data`, vad fiecare camp o singura data
(nr. pe un rand, VIN dedesubt), corectez data, `Salveaza si retrimite` → rand `queued`; `Sterge` e
clar separat si rosu.
### US-005: Poll-ul nu mai inchide modalul si nu mai sterge bifele
**Ca** operator **vreau** ca selectia si modalul sa supravietuiasca refresh-ului de 15s **pentru ca**
azi formularul se inchide singur si bifele se sterg dupa cateva secunde.
- **Depinde de**: US-002, US-003
- **Fisiere**: `app/web/templates/base.html` (JS poll-guard), `app/web/templates/_coada.html`,
`tests/test_web_modal.py`
- **Test intai (RED)**: `tests/test_web_modal.py` —
`test_poll_pauzat_cat_modal_deschis`, `test_poll_pauzat_cat_exista_bifa`
- **Acceptance criteria**:
- [ ] Modalul traieste in afara `#submissions-wrap` → un swap de poll NU il mai atinge (rezolvat
structural de US-003). Test: cu modalul deschis, un refresh al listei nu-l inchide.
- [ ] Poll-ul de 15s (`#submissions-wrap`, `_coada.html:62-65`) se pune pe **pauza** la
`htmx:beforeRequest` cat timp: (a) modalul e deschis SAU (b) exista cel putin un checkbox de
bulk bifat. Se reia automat cand ambele conditii dispar.
- [ ] La pauza nu se pierde nici scroll, nici focus, nici selectia de checkbox-uri.
- [ ] Logica veche de pauza pe „rand expandat" (5.8, `base.html:478-483`) este inlocuita de aceasta.
- [ ] `python3 -m pytest tests/test_web_modal.py -q` trece.
- **Verificare E2E**: gstack browser — bifez 2 trimiteri, astept >15s: bifele raman; deschid modalul,
astept >15s: modalul ramane deschis cu datele intacte.
### US-006: Fundatie responsive — header, nav, modal, breakpoint-uri
**Ca** utilizator pe telefon **vreau** ca shell-ul aplicatiei sa fie utilizabil **pentru ca** azi
header-ul, tab-bar-ul si modalul nou nu au tratament sub 768px.
- **Depinde de**: US-003 (modalul exista)
- **Fisiere**: `app/web/templates/base.html` (CSS responsive + `<meta viewport>` verificat),
`tests/test_web_responsive.py`
- **Test intai (RED)**: `tests/test_web_responsive.py` —
`test_viewport_meta_prezent`, `test_modal_fullscreen_clasa_mobil`,
`test_nav_colapsabil_sub_breakpoint`
- **Acceptance criteria**:
- [ ] `<meta name="viewport" content="width=device-width, initial-scale=1">` prezent (confirmat/adaugat).
- [ ] **Modal full-screen pe mobil**: sub 768px modalul ocupa tot ecranul (fara backdrop lateral),
cu buton `x` mare (>=44px) si scroll intern; pe desktop ramane centrat cu latime marginita.
- [ ] **Header + nav**: meniul de cont (☰) si tab-bar-ul nu depasesc latimea pe 375px; tintele
touch >=44px; fara scroll orizontal pe pagina.
- [ ] Breakpoint-uri consistente definite o data (ex. `--bp-mobil: 768px`) si reutilizate; fara
regresie pe desktop (>=1024px arata identic).
- [ ] `python3 -m pytest tests/test_web_responsive.py -q` trece.
- **Verificare E2E**: gstack browser la 375px pe `/` — fara scroll orizontal; ☰ deschide meniul;
modalul deschis ocupa tot ecranul si se inchide din `x`.
### US-007: Responsive — paginile de continut (Mapari, Cont, Nomenclator, Integrare, Jurnal, Admin)
**Ca** operator pe telefon **vreau** ca paginile de lucru sa fie lizibile **pentru ca** tabelele si
formularele lor ies azi din ecran sub 768px.
- **Depinde de**: US-006
- **Fisiere**: `app/web/templates/_mapari.html`, `_cont.html`, `_nomenclator.html`, `_integrare.html`,
`_jurnal.html`, `admin.html`, `base.html` (utilitar `.tablewrap`/card), `tests/test_web_responsive.py`
- **Test intai (RED)**: `tests/test_web_responsive.py` —
`test_tabele_continut_au_clasa_responsive`, `test_formulare_full_width_mobil`
- **Acceptance criteria**:
- [ ] Fiecare tabel din paginile listate fie devine **card per rand** sub 768px (model 5.8
`.tabel-trimiteri`), fie e incadrat intr-un `.tablewrap` cu scroll orizontal **contained**
(nu deborda pagina) — alegere per tabel, documentata in §7.
- [ ] Formularele (Cont, Mapari add-row, filtre, Integrare) stiveaza campurile pe o coloana sub
768px, inputuri full-width, butoane >=44px.
- [ ] La 375px niciuna din pagini nu produce scroll orizontal de pagina (`scrollWidth <=
clientWidth` pe `body`).
- [ ] Fara regresie la >=1024px (layout actual pastrat).
- [ ] `python3 -m pytest tests/test_web_responsive.py -q` trece.
- **Verificare E2E**: gstack browser la 375px pe `/?tab=mapari`, `?tab=jurnal`, meniul Cont/Nomenclator/
Integrare si `/admin` — fiecare fara scroll orizontal, tabele lizibile, formulare pe o coloana.
### US-008: Responsive — Acasa (upload, status, filtre) + login/signup
**Ca** utilizator pe telefon **vreau** ca prima pagina si autentificarea sa fie utilizabile **pentru
ca** sunt punctele de intrare si azi nu au tratament mobil complet.
- **Depinde de**: US-006
- **Fisiere**: `app/web/templates/_acasa.html`, `_upload.html`, `_status.html`, `_coada.html`
(filtre), `login.html`, `signup.html`, `tests/test_web_responsive.py`
- **Test intai (RED)**: `tests/test_web_responsive.py` —
`test_acasa_fara_scroll_orizontal_mobil`, `test_login_signup_full_width_mobil`
- **Acceptance criteria**:
- [ ] Zona de upload, bara de status (US-002 5.x) si bara de filtre (`_coada.html:25-59`) stiveaza
pe o coloana sub 768px; inputuri/butoane full-width >=44px.
- [ ] Tabelul de trimiteri pastreaza cardurile din 5.8 (verificat, nereparat daca deja ok).
- [ ] `login.html` si `signup.html`: card centrat care nu depaseste latimea pe 375px, inputuri
full-width, fara scroll orizontal.
- [ ] Fara regresie la >=1024px.
- [ ] `python3 -m pytest tests/test_web_responsive.py -q` trece.
- **Verificare E2E**: gstack browser la 375px pe `/`, `/login`, `/signup` — fara scroll orizontal;
upload + filtre pe o coloana; carduri de trimiteri intacte.
---
## 4. Riscuri
- **Inlocuirea inline-expand (5.8 US-008) cu modal** atinge JS partajat din `base.html` (toggle, poll,
focus). Risc de regresie pe a11y/keyboard. Mitigare: testele US-003/US-005 + E2E pe tastatura;
pastram `window.inchideDetaliu` ca punct de inchidere stabil.
- **Focus-trap + `inert`/`aria-hidden`** pe restul paginii: implementat gresit, poate „inchide"
tastatura sau lasa scroll body. Mitigare: pattern dialog standard, test de ciclare Tab.
- **Poll-pauza pe „exista bifa"** (US-005): daca verificarea citeste DOM la fiecare `beforeRequest`,
costul e neglijabil, dar trebuie sa prinda si bifele adaugate dupa randare (delegare pe body).
- **Carduri vs scroll contained** (US-007): tabelele de continut sunt eterogene (Jurnal e lung,
Nomenclator e dens). Alegerea per tabel trebuie sa nu rupa `.tablewrap` partajat — scopare prin
clasa, ca in 5.8.
- **Reversibilitate**: 5.8 a livrat inline-row recent; 5.9 il scoate. De confirmat ca nicio alta
pagina nu mai depinde de `tr.detaliu-rand` / `#trimitere-detaliu` inainte de stergere.
## 5. Intrebari deschise
> Rezolvate cu utilizatorul INAINTE de executie (poarta de aprobare PRD).
- ~~Inline reproiectat vs modal vs drawer pentru detaliu?~~ **REZOLVAT** (sesiune 2026-06-24):
**modal**.
- ~~Editare: in-place vs citire+toggle Editeaza?~~ **REZOLVAT**: **editare in-place** (campuri o
singura data).
- ~~Pastram un buton separat „Re-pune fara modificari" pentru randuri error?~~ **REZOLVAT**: NU —
un singur „Salveaza si retrimite" (retry pur = salvezi fara modificari).
- ~~Codul de eroare in tabel: coloana noua sau sub stare?~~ **REZOLVAT**: **sub eticheta de stare**.
- Tabele de continut (US-007): care devin carduri si care raman scroll-contained? — de decis per
tabel la executie (default: tabele scurte/actionabile = carduri; Jurnal/Nomenclator = scroll
contained). Neblocant.
## 6. Valuri de executie (graful de dependente)
```
Val 1: [US-001] [US-003] [US-006] ← fara dependente intre ele (US-006 dep US-003 pt modal mobil
dar poate incepe pe shell); fisiere majoritar disjuncte
Val 2: [US-002] [US-004] [US-007] [US-008]
US-002 dep US-001+US-003 · US-004 dep US-003 · US-007/US-008 dep US-006
Val 3: [US-005] ← dep US-002+US-003 (selectie + modal)
```
## 7. Decizii de design (din sesiunea de clarificare, 2026-06-24)
### Container detaliu = MODAL
Overlay centrat, fundal estompat, in afara zonei de poll. Rezolva simultan trei reclamatii:
(a) „marginea nu se distinge de tabel" (overlay delimitat clar), (b) „formularul se inchide singur /
se sterg bifele" (modal in afara `#submissions-wrap` + pauza poll), (c) aglomerarea (un singur set de
campuri editabile). Pe mobil = full-screen.
### Editare in-place (campuri o singura data)
Campurile editabile SUNT formularul, pre-completate. Nr. inmatriculare pe propriul rand, VIN dedesubt.
Contextul pur read-only (Nr. prezentare RAR, Cod HTTP, reincercari, timestampuri) ramane, dar
operatie+cod RAR se afiseaza impreuna, fara eticheta „Cod RAR".
### Actiuni consolidate
Un primar `Salveaza si retrimite` (albastru) + un `Sterge` distructiv (rosu) clar distantat. „Re-pune
in coada" gol eliminat (salvarea repune). Bulk „Sterge selectate" ramane DOAR pe tabel. Culori clar
diferentiate primar vs distructiv (azi ambele aproape de `--card`/outline).
### Tabel: cod eroare sub stare, fara chevron, cod RAR simplu
Sub pill apare codul scurt (`cod_eroare`) doar pe randuri cu problema. Chevron scos. Codul RAR in
coloana Operatie fara prefixul „cod RAR:".
### Responsive (transversal)
- `>=1024px`: layout actual neschimbat (fara regresie).
- `<768px`: tabelul de trimiteri = carduri (5.8, pastrat); modal = full-screen; nav/header colapsate;
formulare pe o coloana, inputuri/butoane full-width, tinte touch >=44px; alte tabele = carduri sau
scroll contained (per tabel). Fara scroll orizontal de pagina pe niciuna din pagini.
### Tabel stari interactiune
```
FEATURE | LOADING | EMPTY | ERROR | SUCCESS
---------------------|------------------------|------------------|------------------------|------------------
Rand -> Modal | placeholder „Se incarca| n/a | fragment eroare HTMX | detaliu in modal
| …" (hx-indicator) | | in corpul modalului | centrat/full-screen
Cod eroare sub pill | — | (ascuns pe rand | cod scurt (s-error) | (ascuns pe rand OK)
| | OK) | |
Corectie in modal | buton disabled la post | — | mesaj flash + campuri | rand -> queued, modal
| | | invalide marcate | se inchide / reincarca
```
## 8. NU in scope (design — deferat explicit)
- Redesign de continut/IA al paginilor (doar adaptare responsive).
- Drawer lateral ca alternativa la modal (respins in favoarea modalului).
- Buton „Re-pune fara modificari" separat (respins — un singur flux de salvare).
- Coloana dedicata „Problema" in tabel (respins — cod sub pill).
- Animatii de expand/collapse / tranzitii elaborate de modal.
## 9. Ce exista deja (de reutilizat, nu reinventat)
- Fragment detaliu `GET /_fragments/trimitere/{id}` + `_detaliu_ctx` (`routes.py:819`,`:880`) — corpul
ramane, se schimba doar containerul-tinta (modal).
- Rute lifecycle/corectie/mapare: `/trimitere/{id}/corecteaza|sterge|mapeaza` + bulk
`/trimiteri/sterge-bulk` — neatinse logic, doar `hx-target`.
- `eticheta_stare`/`eticheta_scurta`/`motiv_uman` (`labels.py`) + catalog `app/errors.py` (cod scurt).
- Carduri responsive `.tabel-trimiteri` (<768px) din 5.8 — model reutilizat pentru celelalte tabele.
- Macro `auto_send` toggle + select nomenclator + `csrf_token` — sectiunea mapare inline le pastreaza.
- Variabile CSS / teme dark-light (`base.html`), clase pill `s-*`, clasa `.flash`.
---
## Raport VERIFY
> Completat de subagentul verificator (context curat) in faza VERIFY — vezi ROADMAP §5.6.
> PASS/FAIL per criteriu, cu dovezi (output pytest citat, E2E pe RAR test). Lipseste pana la VERIFY.
---
<!-- ================= AUTOPLAN REVIEW REPORT ================= -->
# AUTOPLAN REVIEW REPORT
Branch: main · Commit: 32408ed · Generat: 2026-06-24 · Voci: Claude subagent (Codex indisponibil — limita de utilizare)
## Faza 1 — CEO (Strategie & Scope) [subagent-only]
### CEO DUAL VOICES — CONSENSUS TABLE
```
Dimensiune Claude Codex Consensus
───────────────────────────────────────────────────────────────────
1. Premise valide? PARTIAL N/A PARTIAL (vezi F3,F8)
2. Problema corecta de rezolvat? DA* N/A DA cu rezerve (F1)
3. Calibrare scope corecta? NU N/A NU (F5/F6 responsive mis-bundled)
4. Alternative explorate suficient? NU N/A NU (F4 rezolvat prin fiat)
5. Riscuri competitive/piata acoperite? PARTIAL N/A PARTIAL (F6 mobil nevalidat)
6. Traiectorie 6 luni sanatoasa? PARTIAL N/A PARTIAL (F2 churn 5.8->5.9)
```
Codex N/A (limita de utilizare). Constatari critice de la subagent flagate oricum.
### Constatari CEO (subagent independent)
- **F1 (critical, strategic)**: produs cu zero utilizatori reali; a 9-a slefuire UX interna fara semnal de cerere. Recomanda validare cu un operator real inainte de redesign. → context la poarta, nu blocant (userul a ales /autoplan pe acest PRD).
- **F2 (high)**: scoaterea UI-ului inline-row din 5.8 o singura iteratie mai tarziu = churn vizibil; bug-ul real e ca plan-review-ul a lasat 5.8 sa livreze un UI care se auto-distruge sub poll-ul existent. → retro, nu blocant.
- **F3 (high)**: premisa-titlu (modalul rezolva „formularul se inchide singur") e partial gresita — un poll-pause + scoaterea selectiei din `#submissions-wrap` rezolva bug-ul fara modal. Modalul e preferinta de design, nu cerinta de bug. → POARTA premise.
- **F4 (medium)**: alternativele (inline reproiectat, drawer) respinse fara rationament inregistrat. → auto: pastram modalul (decizie utilizator 2026-06-24), dar notam ca preferinta de produs.
- **F5 (high, scope)**: responsive-pentru-toate-paginile (US-006/007/008) mis-bundled cu cleanup-ul modal; e jumatatea mai mare si mai riscanta, aproape disjuncta in graful de dependente. → POARTA scope (split vs bundle).
- **F6 (high, piata)**: mobil sub 768px probabil rezolva o non-problema — operatorii service-auto declara de la birou pe PC (inlocuiesc o app VFP desktop). → POARTA (legat de F5).
- **F7 (medium)**: consolidarea butoanelor ruteaza retry pur prin `/corecteaza` in loc de `/repune` — schimbare de comportament ascunsa intr-un PRD „doar prezentare". → auto (Eng): test de regresie explicit pe retry-fara-editare + idempotenta.
- **F8 (medium)**: codul brut de catalog (`RAR_EROARE_SERVER`, `COD_NEMAPAT`) sub pill contrazice investitia 5.4 in erori umane (problema/cauza/fix). → POARTA premise (cod brut vs eticheta umana).
### POARTA PREMISE (decizii utilizator, 2026-06-24)
- **D1 = Keep all 8 bundled**: responsive (US-006/007/008) RAMANE in 5.9. Risc F5/F6 acceptat constient.
- **D2 = Eticheta umana pe rand**: US-001/US-002 REVIZUITE — sub pill apare o eticheta umana scurta (nu codul brut `RAR_EROARE_SERVER`); codul brut de catalog ramane in modal pentru suport. (Schimbare fata de PRD original.)
- **D3 = Modal (confirmat)**: se construieste modalul (US-003) + poll-pause (US-005). Decizia 2026-06-24 sustinuta.
## Faza 2 — Design (UI/UX) [subagent-only] · Completitudine: 6.5/10
### Design litmus (consensus, Codex N/A)
```
Hierarchy: ISSUE ordinea verticala a modalului nedefinita -> reproduce „zidul" de campuri (F1)
States: ISSUE load-error poate ramane blocat; succes corectie „inchide/reincarca" contradictoriu; mapare/sent absente (F2)
Consolidation: ISSUE margin-left:auto gresit (se prabuseste pe mobil); primar --accent + disable-on-submit neimpuse (F4)
A11y: ISSUE focus dupa swap, inert+aria-hidden ambele, aria-labelledby lipsa (F5)
Responsive: ISSUE card-vs-scroll punt ascunde cost 5x (data-eticheta) + poate ascunde controale (F6)
Specificity: ISSUE contradictie D2 vs US-001/002 (F3)
```
Constatari: F1(critical) ordine modal; F2(high) stari modal; F3(high) D2 vs US-001/002; F4(high) separare buton distructiv; F5(med/high) a11y post-swap; F6(med) card-vs-scroll + data-eticheta; F7(med) operatie+cod nemapat; F8(low) afordanta rand fara chevron.
## Faza 3 — Eng (Arhitectura & Test) [subagent-only]
### ENG DUAL VOICES — CONSENSUS TABLE
```
Dimensiune Claude Codex Consensus
───────────────────────────────────────────────────────
1. Arhitectura solida? ISSUE N/A ISSUE (F1,F2 stale tests + script fragment)
2. Acoperire test suf.? ISSUE N/A ISSUE (teste stale nelistate + lipsa focus/poll/keyboard)
3. Riscuri perf? OK N/A OK (poll-pause cost neglijabil)
4. Securitate? OK N/A OK (CSRF/scoping neatins)
5. Cai de eroare? ISSUE N/A ISSUE (F3 /sterge bare-flash, F4 corectie succes)
6. Risc deploy? ISSUE N/A ISSUE (F7 error rows pierd retry)
```
### Constatari Eng (verificate pe cod real)
- **F7 (CRITICAL, top risk) [VERIFICAT]**: consolidarea pe `/corecteaza` scoate retry-ul pentru randuri `error`. `_CORECTABILE=(needs_data,needs_mapping)`; `post_corectie` da 403 pe `error` (routes.py:974). `error` se re-pune DOAR prin `/repune` (`_GESTIONABILE_WEB`). In plus `error` nu e `editabil` -> nu are deloc formular de corectie. „Un singur buton" + Non-Goals (fara backend) sunt in conflict. → POARTA FINALA (taste, dar e corectitudine).
- **F1 (CRITICAL) [VERIFICAT]**: `tests/test_web_detaliu_inline.py` + `tests/test_acasa_trimiteri.py:71` asserteaza markup/JS sterse (detaliu-rand, aria-expanded, #trimitere-detaliu). Nelistate in niciun US. `pytest -q` nu poate trece. → auto: adauga-le in scope US-002/003 + AC „niciun template/test nu mai refera ID-urile sterse".
- **F2 (CRITICAL)**: `<script>` inline din `_trimitere_detaliu.html:161-176` cheama `marcheazaDetaliuDeschis` (sters) + `scrollIntoView` pe randul de fundal -> TypeError dupa swap in modal. → auto: rescrie scriptul fragmentului pentru modal.
- **F3 (HIGH)**: `/sterge` intoarce bare-flash; in modal lasa overlay blocat. → auto: AC modal se inchide pe succes sterge (listener pe `trimiteriChanged`).
- **F4 (HIGH)**: succes corectie fara mecanism de inchidere/AC. → auto: pe succes (queued) modalul se inchide + `trimiteriChanged` reincarca lista.
- **F5 (HIGH)**: poll-pause pe „bifa exista" se poate bloca permanent daca randul bifat paraseste filtrul. → auto: pauza DOAR pe trigger periodic `every 15s`; `trimiteriChanged`/submit filtru trec; resume pe checkbox change.
- **F6 (HIGH)**: D2 — eticheta umana pe rand; reutilizeaza `motiv_uman`/`eticheta_scurta` (NU adauga `cod_eroare_din` care re-parseaza acelasi JSON — DRY). Cod brut in modal din `parse_erori(...)[0]['cod']`.
- **F8 (MED)**: in modal foloseste `prez.cod_rar` (fallback „nemapat"), nu `prez.cod` (care cade pe cod_op_service intern).
- **F9 (MED)**: `inert` pe ancestor stabil (`<main>`), nu pe `#submissions-wrap` (swap target).
- **F10 (MED)**: sterge logica toggle (re-click inchide) din `htmx:beforeRequest`; test keyboard Enter/Space deschide modal, Esc readuce focus.
- **F11 (MED)**: stergerea chevron-ului scoate SINGURA regula touch 44px (`@media pointer:coarse .chevron`). → auto: regula min-height/padding 44px pe rand; scopare `.tablewrap` sa nu strice cardurile 5.8 `.tabel-trimiteri`.
- **F12 (LOW)**: `_submission_row_view` deja intoarce `motiv` nerandat — reutilizeaza-l pentru eticheta umana (evita al 3-lea decoder).
### Cross-Phase Themes
- **Tema A — Contradictia D2 (cod brut vs eticheta umana)**: semnalata independent in Design F3 SI Eng F6. Semnal de incredere ridicat. → rezolvat de POARTA D2 (eticheta umana pe rand).
- **Tema B — Coregrafia succes/eroare in modal (close-vs-reload)**: Design F2 SI Eng F3/F4. → auto: modalul se inchide pe succes (corectie/sterge) + `trimiteriChanged`.
## Decision Audit Trail
| # | Faza | Decizie | Clasificare | Principiu | Rationament |
|---|------|---------|-------------|-----------|-------------|
| 1 | CEO | Pastram modalul (nu doar poll-pause) | Premise gate | utilizator | D3: decizie 2026-06-24 sustinuta; poll-pause inclus via US-005 |
| 2 | CEO | Responsive ramane in 5.9 (toate 8) | Premise gate | utilizator | D1: risc F5/F6 acceptat constient |
| 3 | CEO | Eticheta umana sub pill (nu cod brut) | Premise gate | utilizator | D2: consistent cu investitia 5.4; cod brut in modal |
| 4 | Eng | Reutilizeaza motiv_uman/eticheta_scurta, NU cod_eroare_din nou | Mechanical | P4 DRY | exista deja 2 decodoare rar_error; al 3-lea = duplicare |
| 5 | Eng | Adauga test_web_detaliu_inline.py + test_acasa_trimiteri.py in scope | Mechanical | P1 | altfel pytest nu trece (verificat) |
| 6 | Eng | Rescrie scriptul inline din _trimitere_detaliu.html | Mechanical | P1 | TypeError pe marcheazaDetaliuDeschis sters |
| 7 | Eng | Modal se inchide pe succes corectie+sterge (trimiteriChanged) | Taste->auto | P5/P1 | rezolva close-vs-reload + bare-flash strand |
| 8 | Eng | Poll-pause doar pe trigger periodic; resume pe checkbox change | Mechanical | P5 | evita blocaj permanent (F5) |
| 9 | Eng | modal foloseste prez.cod_rar (nu prez.cod) | Mechanical | P5 | consistent cu tabelul; nemapat -> „—" |
| 10 | Eng | inert pe <main>, nu pe #submissions-wrap | Mechanical | P5 | swap target ar pierde inert |
| 11 | Eng | regula touch 44px pe rand inlocuieste chevron | Mechanical | P1 | chevron (singura regula 44px) e sters |
| 12 | Design | AC ordine verticala modal (motiv->fix->actiuni->tehnic colapsat) | Mechanical | P1/P5 | altfel se reproduce „zidul" de campuri |
| 13 | Design | Buton Sterge pe rand separat (nu margin-left:auto), primar --accent | Mechanical | P5 | margin-left:auto se prabuseste pe mobil |
| 14 | Design | A11y: focus dupa swap + inert&aria-hidden + aria-labelledby + x aria-label | Mechanical | P1 | completeaza pattern-ul dialog |
| 15 | Design | Card-vs-scroll decis in PRD: actionabile=card, dense read-only=scroll | Mechanical | P3 | actionabilele nu trebuie sa ascunda controale |
| 16 | DX | Faza 3.5 sarita | Mechanical | — | fara scope developer-facing (UI operator, backend neatins) |
## REVIZUIRI OBLIGATORII (din review — de aplicat la executie)
Aprobat 2026-06-24. Acestea modifica AC-urile din §3; au prioritate unde difera de textul original.
- **R1 (US-001/002, din D2+EngF6/DesignF3)**: sub pill apare o **eticheta umana scurta**, NU codul brut. Reutilizeaza `motiv_uman`/`eticheta_scurta` (deja in `labels.py`; `_submission_row_view` deja intoarce `motiv` nerandat — randeaza-l). NU adauga `cod_eroare_din` care re-parseaza acelasi JSON. Codul brut de catalog ramane DOAR in modal, derivat din `parse_erori(...)[0]['cod']`. Redenumeste testele (`test_eticheta_umana_sub_pill`).
- **R2 (US-004, din D4)**: buton primar **conditionat de stare** — `error` -> „Re-pune in coada" (`/repune`, neschimbat); `needs_data`/`needs_mapping` -> „Salveaza si retrimite" (`/corecteaza`). Un singur buton primar per stare. Teste: retry-fara-editare pe needs_data ramane idempotent-echivalent; retry pe error functioneaza (`/repune`).
- **R3 (US-002/003, din EngF1)**: adauga in scope `tests/test_web_detaliu_inline.py` (rescrie/sterge) si `tests/test_acasa_trimiteri.py:71` (scoate assert `#trimitere-detaliu`). AC nou: niciun template/test nu mai refera `tr.detaliu-rand`/`#trimitere-detaliu`/`marcheazaDetaliuDeschis`/`aria-expanded` pe rand.
- **R4 (US-004, din EngF2)**: rescrie `<script>`-ul inline din `_trimitere_detaliu.html:161-176` pentru modal — fara `marcheazaDetaliuDeschis`, fara `scrollIntoView` pe randul de fundal; muta focusul in corpul modalului.
- **R5 (US-003/004, din EngF3/F4 + DesignF2, Tema B)**: pe succes corectie SI sterge, modalul **se inchide** + listener pe `HX-Trigger: trimiteriChanged` reincarca lista. Pe load-error al fragmentului: stare „Nu s-a putut incarca [Reincearca]/[Inchide]" (HTMX `responseError`), nu placeholder blocat. Disable-on-submit pe toate cele 3 forme (corectie/sterge/mapare).
- **R6 (US-005, din EngF5)**: poll-pause scopat DOAR pe trigger periodic `every 15s`; `trimiteriChanged` si submit-ul de filtru trec mereu. Resume pe checkbox `change`->gol via delegare pe body. Test: bifa + `trimiteriChanged` inca reincarca.
- **R7 (US-003, din EngF9 + DesignF5)**: `inert` pe ancestor stabil (`<main>`), NU pe `#submissions-wrap`. A11y: focus dupa fiecare swap in modal (primul camp `aria-invalid`, altfel heading); `inert` SI `aria-hidden` pe fundal; dialog `aria-labelledby` heading; buton `x` cu `aria-label`.
- **R8 (US-002, din EngF11 + EngF10)**: regula touch `min-height/padding >=44px` pe rand inlocuieste `@media pointer:coarse .chevron`. Sterge logica toggle (re-click inchide) din `htmx:beforeRequest`. Rand: `aria-haspopup="dialog"`, scoate `aria-expanded`; pastreaza hover/focus ca afordanta. Test keyboard Enter/Space deschide modal + Esc readuce focus.
- **R9 (US-004, din EngF8 + DesignF7)**: operatie+cod in modal foloseste `prez.cod_rar` (fallback „nemapat"), nu `prez.cod`. Cand `nemapate_inline`: linia „Operatie: X · nemapat" urmata de picker; dupa mapare, re-render arata codul rezolvat.
- **R10 (US-004, din DesignF1)**: ordine verticala modal: (1) header `#id`+pill+motiv uman; (2) bloc eroare blocanta cand exista; (3) mapare inline cand `nemapate_inline`; (4) formular editabil (Nr. rand propriu, VIN dedesubt, apoi grila), cu operatie+cod read-only deasupra campurilor; (5) actiuni (primar, apoi Sterge separat); (6) `<details>` „Detalii tehnice" (colapsat) cu Nr. prezentare RAR, Cod HTTP, Reincercari, timestamps, mesaj RAR brut + cod brut. Context read-only NU precede formularul.
- **R11 (US-004, din DesignF4)**: Sterge pe **rand separat** (nu `margin-left:auto`), gap vizibil, full-width stivuit pe mobil; AC care fixeaza primarul pe `--accent` umplut si Sterge pe outline distructiv; `hx-confirm` specific („Stergi definitiv trimiterea #{id}? Nu se poate anula.").
- **R12 (US-007, din DesignF6)**: politica per-tabel decisa AICI — tabele actionabile (Mapari) = **card** (adauga `data-eticheta` la `<td>`-uri, listeaza in Fisiere); dense read-only (Jurnal, Nomenclator) = **scroll contained**. Scopare `.tablewrap` sa nu atinga blocul `.tabel-trimiteri @media(max-width:767px)` din 5.8; test ca acele carduri supravietuiesc.
STATUS: APROBAT (auto-decizii + D1-D4 utilizator). Restore: vezi comentariul din capul fisierului.

232
scripts/ralph/prd.json Normal file
View File

@@ -0,0 +1,232 @@
{
"projectName": "autopass-5.9-ux-corectie-modal-mobil",
"branchName": "ralph/5.9-ux-corectie-modal-mobil",
"description": "Curatenie UX pe ecranul Trimiteri, fara schimbare a fluxului de trimitere catre RAR: detaliul+corectia trec intr-un MODAL global (in afara zonei de poll de 15s), editare in-place fara dublarea valorilor, butoane consolidate, fara chevron, eticheta umana scurta sub pill + cod brut in modal, cod RAR simplu. Plus design responsive/mobil (<768px) pentru toate paginile dashboard. Backend (worker, masina de stari, contract RAR, idempotenta, mapare) NEATINS logic; rutele isi pastreaza semnatura, se schimba doar hx-target si prezentarea. APLICA REVIZIILE OBLIGATORII R1-R12 din raportul AUTOPLAN (au prioritate unde difera de AC original). Sursa: docs/prd/prd-5.9-ux-corectie-modal-mobil.md.",
"techStack": {
"type": "python",
"commands": {
"start": "uvicorn app.main:app --reload --port 8010",
"build": "",
"lint": "",
"typecheck": "",
"test": "python3 -m pytest -q -m 'not live'"
},
"port": 8010
},
"userStories": [
{
"id": "US-001",
"title": "Eticheta umana scurta pe randul de afisare + cod brut disponibil pentru modal (R1)",
"description": "Ca dezvoltator vreau ca randul tabelului sa expuna o eticheta umana scurta a problemei (NU codul brut de catalog) pentru ca sub pill sa apara un text prietenos, consistent cu investitia 5.4 in erori umane.",
"priority": 10,
"acceptanceCriteria": [
"R1 OVERRIDE: NU se adauga helper nou `cod_eroare_din` care re-parseaza acelasi JSON (DRY). Se reutilizeaza `motiv_uman`/`eticheta_scurta` din `app/web/labels.py` deja existente.",
"`_submission_row_view` (`app/web/routes.py`) expune o eticheta umana scurta pe rand reutilizand `motiv` (deja intors nerandat) / `eticheta_scurta` — fara al 3-lea decoder al `rar_error`.",
"Eticheta este sir gol pe stari fara problema (`queued/sending/sent`) si ne-goala pe `error`/`needs_data`/`needs_mapping`.",
"Codul BRUT de catalog (ex. `RAR_EROARE_SERVER`, `COD_NEMAPAT`) ramane disponibil DOAR pentru modal, derivat din `parse_erori(...)[0]['cod']` (app/errors.py) — nu se pune pe rand.",
"Helperele sunt defensive la `rar_error` lipsa/JSON invalid (intorc sir gol, nu ridica).",
"`prez`, `motiv`, `stare_*` raman neschimbate (aditiv/non-regresie).",
"Teste in `tests/test_web_submissions.py`: `test_eticheta_umana_sub_pill` (R1 rename), eticheta prezenta pe error/needs_mapping, goala pe rand ok.",
"`python3 -m pytest tests/test_web_submissions.py -q` trece."
],
"tags": ["backend"],
"dependsOn": [],
"requiresBrowserCheck": false,
"requiresDesignReview": false,
"passes": false,
"failed": false,
"blocked": false,
"retries": 0,
"failureReason": "",
"notes": ""
},
{
"id": "US-003",
"title": "Component modal reutilizabil (overlay, focus-trap, a11y, inchidere pe succes)",
"description": "Ca operator vreau ca detaliul sa apara intr-o fereastra modala clar delimitata, in afara zonei de poll, pentru ca sa nu mai confund marginea cu tabelul si sa nu mi se mai inchida formularul la refresh.",
"priority": 15,
"acceptanceCriteria": [
"Container modal global `<div id=\"modal-detaliu\" role=\"dialog\" aria-modal=\"true\" hidden>` cu backdrop, plasat IN AFARA `#submissions-wrap` (poll-ul de 15s nu-l atinge). Corpul `#detaliu-modal-body` e tinta de swap pentru fragment + rutele corectie/mapare/lifecycle.",
"Ancora globala in `_coada.html` in afara `#submissions-wrap`; `#trimitere-detaliu` global inert vechi din `_coada.html` este ELIMINAT (rol preluat de `#modal-detaliu`).",
"Deschidere la `htmx:beforeRequest` de pe `tr.trimitere-row`: backdrop+dialog vizibile cu placeholder `Se incarca...` (`hx-indicator`), focus mutat in modal. Un singur modal o data.",
"Inchidere prin buton `x`, `Esc`, click pe backdrop: `hidden`, corp golit, focus readus pe randul declansator.",
"R7: focus-trap (Tab/Shift+Tab cicleaza in modal); `inert` SI `aria-hidden` pe ancestor STABIL `<main>` (NU pe `#submissions-wrap` care e swap target); scroll body blocat cat modalul e deschis; dialog cu `aria-labelledby` pe heading; buton `x` cu `aria-label`.",
"R3: tot codul inline-expand din 5.8 (chevron, `tr.detaliu-rand`, `marcheazaDetaliuDeschis`, `inchideDetaliu` pe rand, toggle single-open) ELIMINAT/inlocuit de logica modalului; `window.inchideDetaliu(id)` ramane expus dar inchide modalul.",
"R3 (EngF1): adauga in scope `tests/test_web_detaliu_inline.py` (rescrie/sterge) si `tests/test_acasa_trimiteri.py:71` (scoate assert `#trimitere-detaliu`). Niciun template/test nu mai refera `tr.detaliu-rand`/`#trimitere-detaliu`/`marcheazaDetaliuDeschis`/`aria-expanded` pe rand.",
"R5: pe succes corectie SI sterge, modalul se inchide + listener pe `HX-Trigger: trimiteriChanged` reincarca lista; pe load-error al fragmentului stare `Nu s-a putut incarca [Reincearca]/[Inchide]` (HTMX `responseError`), nu placeholder blocat.",
"Teste in `tests/test_web_modal.py`: `test_modal_container_in_afara_submissions_wrap`, `test_fragment_detaliu_tinteste_modalul`.",
"`python3 -m pytest tests/test_web_modal.py -q` trece."
],
"tags": ["ui"],
"dependsOn": [],
"requiresBrowserCheck": true,
"requiresDesignReview": true,
"passes": false,
"failed": false,
"blocked": false,
"retries": 0,
"failureReason": "",
"notes": ""
},
{
"id": "US-006",
"title": "Fundatie responsive — viewport, header, nav, modal full-screen mobil, breakpoint-uri",
"description": "Ca utilizator pe telefon vreau ca shell-ul aplicatiei sa fie utilizabil pentru ca azi header-ul, tab-bar-ul si modalul nou nu au tratament sub 768px.",
"priority": 20,
"acceptanceCriteria": [
"`<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">` prezent (confirmat/adaugat) in `base.html`.",
"Modal full-screen pe mobil: sub 768px modalul ocupa tot ecranul (fara backdrop lateral), buton `x` mare (>=44px), scroll intern; pe desktop ramane centrat cu latime marginita.",
"Header + nav: meniul de cont (un buton hamburger) si tab-bar-ul nu depasesc latimea pe 375px; tinte touch >=44px; fara scroll orizontal pe pagina.",
"Breakpoint-uri consistente definite o data (ex. `--bp-mobil: 768px`) si reutilizate; fara regresie pe desktop (>=1024px arata identic).",
"Teste in `tests/test_web_responsive.py`: `test_viewport_meta_prezent`, `test_modal_fullscreen_clasa_mobil`, `test_nav_colapsabil_sub_breakpoint`.",
"`python3 -m pytest tests/test_web_responsive.py -q` trece."
],
"tags": ["ui"],
"dependsOn": ["US-003"],
"requiresBrowserCheck": true,
"requiresDesignReview": true,
"passes": false,
"failed": false,
"blocked": false,
"retries": 0,
"failureReason": "",
"notes": ""
},
{
"id": "US-002",
"title": "Tabel trimiteri — eticheta umana sub stare, fara chevron, cod RAR simplu, rand declanseaza modalul",
"description": "Ca operator vreau sa vad problema direct in tabel si un rand mai curat pentru ca sa stiu dintr-o privire ce rand are nevoie de mine.",
"priority": 30,
"acceptanceCriteria": [
"R1 OVERRIDE: sub pill-ul de Stare apare ETICHETA UMANA scurta (din US-001, NU codul brut), text mic muted (`s-error` pe `error`/`needs_*`), DOAR cand e ne-goala. Stare transmisa prin text, nu doar culoare.",
"R8: chevron ELIMINAT din coloana `#` (`_submissions.html:52-53`) si din CSS/JS asociat; regula touch `min-height/padding >=44px` pe rand INLOCUIESTE `@media pointer:coarse .chevron` (singura regula 44px); scoate logica toggle (re-click inchide) din `htmx:beforeRequest`.",
"Coloana Operatie linia 2: doar codul RAR (ex. `OE-2`), FARA prefixul `cod RAR:` — un `<span>` muted/pill; cand nemapat afiseaza `nemapat` muted (comportament 5.8 pastrat).",
"Randul `tr.trimitere-row` declanseaza deschiderea modalului (US-003): `hx-get /_fragments/trimitere/{id}` cu `hx-target` pe corpul modalului, NU pe rand-sibling. Randul-sibling `<tr class=\"detaliu-rand\">` din 5.8 este ELIMINAT.",
"R8: rand `role=\"button\"`, `tabindex=\"0\"`, `aria-haspopup=\"dialog\"`, FARA `aria-expanded`; focusabil; Enter/Space deschid modalul; hover/focus pastrate ca afordanta; tinta touch >=44px.",
"Teste in `tests/test_web_submissions.py`: `test_eticheta_umana_apare_sub_pill`, `test_fara_chevron_in_rand`, `test_cod_rar_fara_prefix_text`, `test_rand_deschide_modal`, plus test keyboard Enter/Space deschide modal + Esc readuce focus.",
"`python3 -m pytest tests/test_web_submissions.py -q` trece.",
"E2E (requiresBrowserCheck): gstack browser pe `/` — rand `error` arata eticheta umana sub pill; fara chevron; cod RAR fara `cod RAR:`; click pe rand deschide modalul (nu rand-sibling)."
],
"tags": ["ui"],
"dependsOn": ["US-001", "US-003"],
"requiresBrowserCheck": true,
"requiresDesignReview": false,
"passes": false,
"failed": false,
"blocked": false,
"retries": 0,
"failureReason": "",
"notes": ""
},
{
"id": "US-004",
"title": "Detaliu editabil in-place + butoane consolidate (in modal), ordine verticala definita",
"description": "Ca operator vreau sa corectez datele direct, o singura aparitie per camp, cu un buton clar pentru ca sa nu mai vad valorile de doua ori si sa nu mai ezit intre butoane.",
"priority": 35,
"acceptanceCriteria": [
"Zero dublare: blocul read-only de grila (`_trimitere_detaliu.html:19-32`) si formularul separat de corectie se contopesc intr-un singur formular cu campuri editabile pre-completate. Campurile editabile (`nr_inmatriculare`, `vin`, `data_prestatie`, `odometru_final`, `odometru_initial`) apar O SINGURA DATA.",
"Layout: `Numar inmatriculare` pe propriul rand (latime plina), `VIN (serie sasiu)` pe randul de DEDESUBT (latime plina). Restul campurilor pot ramane in grila.",
"R10 ordine verticala modal: (1) header `#id`+pill+motiv uman; (2) bloc eroare blocanta cand exista; (3) mapare inline cand `nemapate_inline`; (4) formular editabil (Nr. rand propriu, VIN dedesubt, apoi grila), cu operatie+cod read-only deasupra campurilor; (5) actiuni (primar, apoi Sterge separat); (6) `<details>` `Detalii tehnice` colapsat (Nr. prezentare RAR, Cod HTTP, Reincercari, timestamps, mesaj RAR brut + cod brut). Context read-only NU precede formularul.",
"Cod RAR simplu: nu mai exista campul etichetat `Cod RAR` separat (`:23`). R9: operatie+cod folosesc `prez.cod_rar` (fallback `nemapat`), NU `prez.cod`; afisate impreuna ca read-only (ex. `Operatie: Revizie · OE-2`), fara eticheta `Cod RAR`.",
"R2 OVERRIDE (buton primar conditionat de stare): `error` -> buton `Re-pune in coada` care posteaza pe `/trimitere/{id}/repune` (neschimbat); `needs_data`/`needs_mapping` -> buton `Salveaza si retrimite` care posteaza pe `/trimitere/{id}/corecteaza` (neschimbat). UN SINGUR buton primar per stare (clasa primara `--accent` umplut). Butonul `Re-pune in coada` gol vechi (`:55-59`) eliminat.",
"R11: UN SINGUR `Sterge` (outline distructiv, `var(--err)`/rosu) pe RAND SEPARAT (NU `margin-left:auto`), gap vizibil, full-width stivuit pe mobil; `hx-confirm` specific (`Stergi definitiv trimiterea #{id}? Nu se poate anula.`); posteaza pe `/trimitere/{id}/sterge` (neschimbat).",
"R9: sectiunea de mapare inline (5.7, `:71-116`) ramane functional identica, mutata in modal, tintind corpul modalului; cand `nemapate_inline`: linia `Operatie: X · nemapat` urmata de picker; dupa mapare re-render arata codul rezolvat.",
"R4: rescrie `<script>`-ul inline din `_trimitere_detaliu.html:161-176` pentru modal — FARA `marcheazaDetaliuDeschis`, FARA `scrollIntoView` pe randul de fundal; muta focusul in corpul modalului.",
"R5: disable-on-submit pe toate cele 3 forme (corectie/sterge/mapare); pe succes (queued sau sterge) modalul se inchide + `trimiteriChanged`.",
"Erorile de camp (`err_map`, `aria-invalid`, mesaj inline) si flash-ul de corectie raman. Comportamentul rutelor `/corecteaza` si `/repune` (validare, idempotency, re-rezolvare) NESCHIMBAT — test de regresie confirma acelasi rezultat ca azi.",
"Teste in `tests/test_web_corectie.py`: `test_camp_apare_o_singura_data`, `test_nr_si_vin_pe_randuri_separate`, `test_un_singur_buton_primar_per_stare` (R2), `test_error_foloseste_repune` (R2), `test_sterge_prezent_si_distinct`, `test_corectie_pastreaza_comportament` (regresie).",
"`python3 -m pytest tests/test_web_corectie.py -q` trece.",
"E2E (requiresBrowserCheck): gstack browser — `needs_data` arata fiecare camp o data (nr. pe un rand, VIN dedesubt), corectez data, `Salveaza si retrimite` -> rand `queued`; `error` arata `Re-pune in coada`; `Sterge` clar separat si rosu."
],
"tags": ["ui"],
"dependsOn": ["US-003"],
"requiresBrowserCheck": true,
"requiresDesignReview": true,
"passes": false,
"failed": false,
"blocked": false,
"retries": 0,
"failureReason": "",
"notes": ""
},
{
"id": "US-007",
"title": "Responsive — paginile de continut (Mapari, Cont, Nomenclator, Integrare, Jurnal, Admin)",
"description": "Ca operator pe telefon vreau ca paginile de lucru sa fie lizibile pentru ca tabelele si formularele lor ies azi din ecran sub 768px.",
"priority": 40,
"acceptanceCriteria": [
"R12 politica per-tabel: tabele actionabile (Mapari) = CARD per rand sub 768px (adauga `data-eticheta` la `<td>`-uri, model 5.8 `.tabel-trimiteri`); dense read-only (Jurnal, Nomenclator) = `.tablewrap` cu scroll orizontal CONTAINED (nu deborda pagina).",
"R12: scopare `.tablewrap` sa NU atinga blocul `.tabel-trimiteri @media(max-width:767px)` din 5.8; test ca acele carduri 5.8 supravietuiesc.",
"Formularele (Cont, Mapari add-row, filtre, Integrare) stiveaza campurile pe o coloana sub 768px, inputuri full-width, butoane >=44px.",
"La 375px niciuna din pagini nu produce scroll orizontal de pagina (`scrollWidth <= clientWidth` pe `body`).",
"Fara regresie la >=1024px (layout actual pastrat).",
"Fisiere: `app/web/templates/_mapari.html`, `_cont.html`, `_nomenclator.html`, `_integrare.html`, `_jurnal.html`, `admin.html`, `base.html` (utilitar `.tablewrap`/card).",
"Teste in `tests/test_web_responsive.py`: `test_tabele_continut_au_clasa_responsive`, `test_formulare_full_width_mobil`.",
"`python3 -m pytest tests/test_web_responsive.py -q` trece.",
"E2E (requiresBrowserCheck): gstack browser la 375px pe `/?tab=mapari`, `?tab=jurnal`, Cont/Nomenclator/Integrare si `/admin` — fiecare fara scroll orizontal, tabele lizibile, formulare pe o coloana."
],
"tags": ["ui"],
"dependsOn": ["US-006"],
"requiresBrowserCheck": true,
"requiresDesignReview": false,
"passes": false,
"failed": false,
"blocked": false,
"retries": 0,
"failureReason": "",
"notes": ""
},
{
"id": "US-008",
"title": "Responsive — Acasa (upload, status, filtre) + login/signup",
"description": "Ca utilizator pe telefon vreau ca prima pagina si autentificarea sa fie utilizabile pentru ca sunt punctele de intrare si azi nu au tratament mobil complet.",
"priority": 45,
"acceptanceCriteria": [
"Zona de upload, bara de status si bara de filtre (`_coada.html:25-59`) stiveaza pe o coloana sub 768px; inputuri/butoane full-width >=44px.",
"Tabelul de trimiteri pastreaza cardurile din 5.8 (verificat, nereparat daca deja ok).",
"`login.html` si `signup.html`: card centrat care nu depaseste latimea pe 375px, inputuri full-width, fara scroll orizontal.",
"Fara regresie la >=1024px.",
"Fisiere: `app/web/templates/_acasa.html`, `_upload.html`, `_status.html`, `_coada.html` (filtre), `login.html`, `signup.html`.",
"Teste in `tests/test_web_responsive.py`: `test_acasa_fara_scroll_orizontal_mobil`, `test_login_signup_full_width_mobil`.",
"`python3 -m pytest tests/test_web_responsive.py -q` trece.",
"E2E (requiresBrowserCheck): gstack browser la 375px pe `/`, `/login`, `/signup` — fara scroll orizontal; upload + filtre pe o coloana; carduri de trimiteri intacte."
],
"tags": ["ui"],
"dependsOn": ["US-006"],
"requiresBrowserCheck": true,
"requiresDesignReview": false,
"passes": false,
"failed": false,
"blocked": false,
"retries": 0,
"failureReason": "",
"notes": ""
},
{
"id": "US-005",
"title": "Poll-ul nu mai inchide modalul si nu mai sterge bifele",
"description": "Ca operator vreau ca selectia si modalul sa supravietuiasca refresh-ului de 15s pentru ca azi formularul se inchide singur si bifele se sterg dupa cateva secunde.",
"priority": 50,
"acceptanceCriteria": [
"Modalul traieste in afara `#submissions-wrap` -> un swap de poll NU il mai atinge (rezolvat structural de US-003). Test: cu modalul deschis, un refresh al listei nu-l inchide.",
"R6 OVERRIDE: poll-ul de 15s (`#submissions-wrap`, `_coada.html:62-65`) se pune pe pauza SCOPAT DOAR pe trigger periodic `every 15s` cat timp: (a) modalul e deschis SAU (b) exista cel putin un checkbox de bulk bifat. `trimiteriChanged` si submit-ul de filtru TREC MEREU (nu se blocheaza). Resume pe checkbox `change`->gol via delegare pe body.",
"R6: evita blocajul permanent — daca randul bifat paraseste filtrul, pauza nu ramane lipita (pauza doar pe trigger periodic).",
"La pauza nu se pierde nici scroll, nici focus, nici selectia de checkbox-uri.",
"Logica veche de pauza pe `rand expandat` (5.8, `base.html:478-483`) inlocuita de aceasta.",
"Teste in `tests/test_web_modal.py`: `test_poll_pauzat_cat_modal_deschis`, `test_poll_pauzat_cat_exista_bifa`, `test_trimiteriChanged_inca_reincarca_cu_bifa` (R6).",
"`python3 -m pytest tests/test_web_modal.py -q` trece.",
"E2E (requiresBrowserCheck): gstack browser — bifez 2 trimiteri, astept >15s: bifele raman; deschid modalul, astept >15s: modalul ramane deschis cu datele intacte."
],
"tags": ["ui"],
"dependsOn": ["US-002", "US-003"],
"requiresBrowserCheck": true,
"requiresDesignReview": false,
"passes": false,
"failed": false,
"blocked": false,
"retries": 0,
"failureReason": "",
"notes": ""
}
]
}

View File

@@ -0,0 +1,7 @@
# Ralph Progress Log
Started: 2026-06-24
Project: autopass-5.9-ux-corectie-modal-mobil
Branch: ralph/5.9-ux-corectie-modal-mobil
Source PRD: docs/prd/prd-5.9-ux-corectie-modal-mobil.md
Note: PRD APROBAT 2026-06-24 cu revizii obligatorii R1-R12 (raport AUTOPLAN). R1-R12 au prioritate unde difera de AC original — sunt deja incorporate in acceptance criteria.
---

124
scripts/ralph/prompt.md Normal file
View File

@@ -0,0 +1,124 @@
# Ralph - Instrucțiuni pentru Iterație (smart gates)
Ești un agent autonom care implementează user stories dintr-un PRD. Aceasta este O SINGURĂ iterație — implementezi UN singur story, validezi prin gate-urile relevante, apoi te oprești.
## Workflow per iterație (4 faze, gates condiționale pe `story.tags`)
### Faza 0: Citește contextul
- PRD-ul (`prd.json`) și `progress.txt` sunt furnizate în context.
- Identifică următorul story candidate:
- `passes != true` ȘI `failed != true` ȘI `blocked != true`
- DAG: toate ID-urile din `dependsOn[]` au `passes == true` (altfel sare la următorul independent)
- Cea mai mică `priority` printre cele eligibile.
- Notează `techStack.commands` (lint, typecheck, test, start) și `techStack.port`.
- Notează `story.tags[]` — alegerea Faza 3 depinde de ele.
### Faza 1: IMPLEMENTARE (mereu)
1.1. **Branch management** — verifică să fii pe `branchName` din PRD; checkout/create dacă lipsește.
1.2. **Citește acceptance criteria** — fiecare criteriu e un test mental concret de trecut.
1.3. **Implementează cod minimal** — DOAR ce cere story-ul. Urmează patterns existente. Fără over-engineering, fără side features.
1.4. **Update `notes`** în `prd.json` cu fișierele atinse (pentru audit ulterior).
### Faza 2: QUALITY BASE (mereu, înainte de gates)
Folosește `techStack.commands`:
```bash
{techStack.commands.typecheck} # ex: npm run typecheck / mypy .
{techStack.commands.lint} # ex: npm run lint / ruff check .
{techStack.commands.test} # ex: npm test / pytest
```
**Loop intern**: dacă vreuna eșuează → repară și repetă, max 3 retries în această fază. Dacă încă fail după 3, ieși cu sumar de erori în `progress.txt` (ralph.sh va decide retry-ul iterației).
### Faza 3: SMART GATES (dispatcher pe `story.tags`)
Tags posibile: `ui`, `db`, `vercel`, `refactor`, `docs`, `backend`, `infra`.
Aplică DOAR gate-urile potrivite — **nu rulează toate**:
| Tag | Gate |
|-------------|---------------------------------------------------------------------------------------|
| `refactor` | `/workflow:simplify` pe diff (reduce complexity fără behavior change) |
| `ui` | `/qa` Playwright/agent-browser snapshot pe `localhost:{techStack.port}` + screenshot |
| `ui` + `requiresDesignReview` | `/plan-design-review` pe screenshot capturat |
| `vercel` | push branch + `gh pr checks --watch` (timeout 5 min); fail dacă PR checks eșuează |
| `db` | verify schema diff (alembic / prisma migrate diff / `psql \\d+ tablename`) |
| `docs` | doar typecheck base (Faza 2 e suficient); skip gate dedicat |
| `backend` | `/review` pe diff (intern — second pass review pe API contracts, error handling) |
| `infra` | `/review` pe diff + manual smoke test al modificărilor (CI config, Dockerfile, etc.) |
| _(tags vide)_ | **run-all-gates fallback**`/review` + `/qa` + `/workflow:simplify` (safe default) |
**Mecanism**: skill-urile gstack se invocă prin text mention în prompt — Claude (subprocess `claude -p`) le vede ca tool-uri disponibile via `~/.claude/skills/gstack/`.
**Multi-tag**: rulează gate-uri pentru fiecare tag (ex: `["ui", "backend"]` → atât `/qa` cât și `/review`).
**Important**: dacă vreun gate eșuează, NU marca `passes=true`. Repară (max 3 fix-uri în iterație) sau lasă pentru iterația următoare (ralph.sh se ocupă de retry counter).
### Faza 4: COMMIT + MARK
4.1. **Commit** cu mesaj descriptiv:
```
feat: [Story ID] - [Story Title]
- ce ai schimbat (1-3 bullets)
- gates rulate: typecheck PASS, lint PASS, /qa PASS
```
4.2. **Update `prd.json`**:
- `passes: true` DOAR DACĂ toate gate-urile relevante au pasat
- `notes` populat cu rezultate gate (ex: "qa: ok, design-review: 8/10")
4.3. **Append `progress.txt`**:
```markdown
## Iterație: [timestamp]
### Story implementat: [ID] - [Title] (tags: [ui, backend])
### Status: Complete / Partial / Failed
### Gates rulate:
- Typecheck: PASS
- Lint: PASS
- Tests: PASS/SKIP
- /qa (ui): PASS — screenshot la scripts/ralph/screenshots/...
- /review (backend): PASS
### Learnings:
- [Patterns descoperite, gotchas]
### Next:
- [Stories eligibile pentru iterația următoare]
---
```
## Reguli importante
1. **UN SINGUR STORY PE ITERAȚIE** — nu implementa mai mult de un story.
2. **DAG STRICT** — nu sări peste `dependsOn` neîmplinite.
3. **GATES PE TAGS** — rulează doar ce e relevant; tags vide = run-all-gates fallback.
4. **NU MARCA `passes=true` cu gate failed** — altfel ralph.sh nu va relua story-ul.
5. **FII CONCIS** — fără over-engineering, fără docs auto-generate dacă story-ul nu cere.
## Comenzi agent-browser (referință rapidă pentru gate `ui`)
```bash
agent-browser navigate "http://localhost:{techStack.port}"
agent-browser snapshot # listă elemente compactă (@e1, @e2...)
agent-browser click @e5
agent-browser fill @e3 "value"
agent-browser screenshot ./scripts/ralph/screenshots/US-{id}-$(date +%Y%m%d-%H%M%S).png
agent-browser console # erori JS
agent-browser wait-for "Loading complete"
```
## Condiție de terminare
Dacă TOATE story-urile au `passes: true` (sau combinat cu `failed: true` / `blocked: true` astfel că nimic nu mai e eligibil):
```
<promise>COMPLETE</promise>
```
---
ÎNCEPE IMPLEMENTAREA ACUM.

387
scripts/ralph/ralph.sh Executable file
View File

@@ -0,0 +1,387 @@
#!/bin/bash
# Ralph pentru Claude Code - Loop autonom de agent AI (W3: smart gates + DAG + rate limit)
# Adaptat din Ralph original (snarktank/ralph) pentru Claude Code CLI
# Usage: ./ralph.sh [max_iterations] [project_dir]
#
# Env vars (opționale):
# RALPH_MAX_TURNS — --max-turns per iter (default 30)
# RALPH_RATE_LIMIT_SLEEP — sleep după rate limit detection (default 1800 = 30min)
# RALPH_DAG_HELPER — path la tools/ralph_dag.py (auto-detect default)
# RALPH_PYTHON — interpreter Python pentru DAG helper (default python3)
set -e
MAX_ITERATIONS=${1:-10}
PROJECT_DIR=${2:-$(pwd)}
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PRD_FILE="$SCRIPT_DIR/prd.json"
PROGRESS_FILE="$SCRIPT_DIR/progress.txt"
ARCHIVE_DIR="$SCRIPT_DIR/archive"
SCREENSHOTS_DIR="$SCRIPT_DIR/screenshots"
LAST_BRANCH_FILE="$SCRIPT_DIR/.last-branch"
PROMPT_FILE="$SCRIPT_DIR/prompt.md"
# W3 config
MAX_TURNS=${RALPH_MAX_TURNS:-30}
RATE_LIMIT_SLEEP=${RALPH_RATE_LIMIT_SLEEP:-1800}
RALPH_PYTHON=${RALPH_PYTHON:-python3}
# DAG helper auto-detect: prefer co-located cu echo-core; fallback la $SCRIPT_DIR
if [ -n "$RALPH_DAG_HELPER" ] && [ -f "$RALPH_DAG_HELPER" ]; then
DAG_HELPER="$RALPH_DAG_HELPER"
elif [ -f "/home/moltbot/echo-core/tools/ralph_dag.py" ]; then
DAG_HELPER="/home/moltbot/echo-core/tools/ralph_dag.py"
elif [ -f "/home/moltbot/echo-core-qc/tools/ralph_dag.py" ]; then
DAG_HELPER="/home/moltbot/echo-core-qc/tools/ralph_dag.py"
elif [ -f "$SCRIPT_DIR/ralph_dag.py" ]; then
DAG_HELPER="$SCRIPT_DIR/ralph_dag.py"
else
DAG_HELPER=""
fi
# Usage helper auto-detect (rate limit budget tracking — best effort, niciodată
# blochează rularea Ralph dacă lipsește)
if [ -n "$RALPH_USAGE_HELPER" ] && [ -f "$RALPH_USAGE_HELPER" ]; then
USAGE_HELPER="$RALPH_USAGE_HELPER"
elif [ -f "/home/moltbot/echo-core/tools/ralph_usage.py" ]; then
USAGE_HELPER="/home/moltbot/echo-core/tools/ralph_usage.py"
elif [ -f "/home/moltbot/echo-core-instr/tools/ralph_usage.py" ]; then
USAGE_HELPER="/home/moltbot/echo-core-instr/tools/ralph_usage.py"
elif [ -f "$SCRIPT_DIR/ralph_usage.py" ]; then
USAGE_HELPER="$SCRIPT_DIR/ralph_usage.py"
else
USAGE_HELPER=""
fi
USAGE_FILE="$SCRIPT_DIR/usage.jsonl"
# Verifică că jq este instalat
if ! command -v jq &> /dev/null; then
echo "Eroare: jq nu este instalat. Rulează: apt install jq"
exit 1
fi
# Verifică că claude este instalat
if ! command -v claude &> /dev/null; then
echo "Eroare: Claude Code CLI nu este instalat."
echo "Instalează cu: npm install -g @anthropic-ai/claude-code"
exit 1
fi
# Verifică agent-browser (opțional, pentru verificări UI)
if ! command -v agent-browser &> /dev/null; then
echo "Notă: agent-browser nu este instalat."
echo "Pentru verificări vizuale UI, instalează cu: npm install -g agent-browser && agent-browser install"
echo "Continuăm fără verificări browser..."
echo ""
fi
# Verifică existența fișierelor necesare
if [ ! -f "$PRD_FILE" ]; then
echo "Eroare: prd.json nu există în $SCRIPT_DIR"
echo "Generează mai întâi un PRD folosind skill-ul /prd și apoi /ralph"
exit 1
fi
if [ ! -f "$PROMPT_FILE" ]; then
echo "Eroare: prompt.md nu există în $SCRIPT_DIR"
exit 1
fi
# Arhivare rulare anterioară dacă branch-ul s-a schimbat
if [ -f "$PRD_FILE" ] && [ -f "$LAST_BRANCH_FILE" ]; then
CURRENT_BRANCH=$(jq -r '.branchName // empty' "$PRD_FILE" 2>/dev/null || echo "")
LAST_BRANCH=$(cat "$LAST_BRANCH_FILE" 2>/dev/null || echo "")
if [ -n "$CURRENT_BRANCH" ] && [ -n "$LAST_BRANCH" ] && [ "$CURRENT_BRANCH" != "$LAST_BRANCH" ]; then
DATE=$(date +%Y-%m-%d)
FOLDER_NAME=$(echo "$LAST_BRANCH" | sed 's|^ralph/||')
ARCHIVE_FOLDER="$ARCHIVE_DIR/$DATE-$FOLDER_NAME"
echo "Arhivare rulare anterioară: $LAST_BRANCH"
mkdir -p "$ARCHIVE_FOLDER"
[ -f "$PRD_FILE" ] && cp "$PRD_FILE" "$ARCHIVE_FOLDER/"
[ -f "$PROGRESS_FILE" ] && cp "$PROGRESS_FILE" "$ARCHIVE_FOLDER/"
echo " Arhivat în: $ARCHIVE_FOLDER"
# Reset progress file
echo "# Ralph Progress Log" > "$PROGRESS_FILE"
echo "Started: $(date)" >> "$PROGRESS_FILE"
echo "Branch: $CURRENT_BRANCH" >> "$PROGRESS_FILE"
echo "---" >> "$PROGRESS_FILE"
fi
fi
# Salvează branch-ul curent
if [ -f "$PRD_FILE" ]; then
CURRENT_BRANCH=$(jq -r '.branchName // empty' "$PRD_FILE" 2>/dev/null || echo "")
if [ -n "$CURRENT_BRANCH" ]; then
echo "$CURRENT_BRANCH" > "$LAST_BRANCH_FILE"
fi
fi
# Creează directoare necesare
mkdir -p "$SCRIPT_DIR/logs" "$SCRIPT_DIR/archive" "$SCRIPT_DIR/screenshots"
# Creează .gitignore dacă nu există
if [ ! -f "$PROJECT_DIR/.gitignore" ]; then
cat > "$PROJECT_DIR/.gitignore" << 'GITIGNORE'
# Python
__pycache__/
*.py[cod]
*.pyo
*.egg-info/
dist/
build/
.coverage
htmlcov/
.pytest_cache/
# Virtual environment
venv/
.venv/
# Ralph runtime
scripts/ralph/.ralph.pid
scripts/ralph/.last-branch
scripts/ralph/logs/
scripts/ralph/screenshots/
scripts/ralph/archive/
# IDE
.vscode/
.idea/
# OS
.DS_Store
Thumbs.db
GITIGNORE
echo "Created .gitignore"
fi
# Inițializare progress file dacă nu există
if [ ! -f "$PROGRESS_FILE" ]; then
echo "# Ralph Progress Log" > "$PROGRESS_FILE"
echo "Started: $(date)" >> "$PROGRESS_FILE"
echo "---" >> "$PROGRESS_FILE"
fi
# Funcție pentru a verifica dacă toate story-urile sunt complete
check_all_complete() {
local incomplete=$(jq '[.userStories[] | select(.passes != true)] | length' "$PRD_FILE" 2>/dev/null || echo "999")
[ "$incomplete" -eq 0 ]
}
# W3: nimic eligibil = toate sunt fie passes, fie failed, fie blocked
check_no_eligible() {
local n=$(jq '[.userStories[] | select(.passes != true and .failed != true and .blocked != true)] | length' "$PRD_FILE" 2>/dev/null || echo "999")
[ "$n" -eq 0 ]
}
# W3: alege next eligible story via DAG helper. Print story ID sau "" dacă nimic.
dag_next_story() {
if [ -n "$DAG_HELPER" ]; then
"$RALPH_PYTHON" "$DAG_HELPER" next-story "$PRD_FILE" 2>/dev/null || echo ""
else
# Fallback simplu (fără DAG): primul story cu passes!=true && failed!=true && blocked!=true, priority asc
jq -r '[.userStories[] | select(.passes != true and .failed != true and .blocked != true)] | sort_by(.priority) | .[0].id // ""' "$PRD_FILE"
fi
}
dag_incr_retry() {
local sid="$1"
if [ -n "$DAG_HELPER" ]; then
"$RALPH_PYTHON" "$DAG_HELPER" incr-retry "$PRD_FILE" "$sid" 2>/dev/null || echo "0"
else
echo "0"
fi
}
dag_mark_failed() {
local sid="$1" reason="$2"
if [ -n "$DAG_HELPER" ]; then
"$RALPH_PYTHON" "$DAG_HELPER" mark-failed "$PRD_FILE" "$sid" "$reason" 2>/dev/null || true
fi
}
dag_force_tags() {
local sid="$1"
if [ -n "$DAG_HELPER" ]; then
"$RALPH_PYTHON" "$DAG_HELPER" force-tags "$PRD_FILE" "$sid" "$PROJECT_DIR" 2>/dev/null || true
fi
}
# W3: detectează rate limit în output Claude (heuristic — Anthropic nu are exit code dedicat)
is_rate_limited() {
local output="$1"
echo "$output" | grep -qiE "rate limit|rate_limit_exceeded|429|too many requests"
}
# Afișare status inițial
echo ""
echo "======================================================================="
echo " RALPH pentru Claude Code - Agent Autonom "
echo "======================================================================="
PROJECT_NAME=$(jq -r '.projectName // "Unknown"' "$PRD_FILE")
BRANCH_NAME=$(jq -r '.branchName // "N/A"' "$PRD_FILE")
TOTAL_STORIES=$(jq '.userStories | length' "$PRD_FILE")
COMPLETE_STORIES=$(jq '[.userStories[] | select(.passes == true)] | length' "$PRD_FILE")
echo " Proiect: $PROJECT_NAME"
echo " Branch: $BRANCH_NAME"
echo " Stories: $COMPLETE_STORIES / $TOTAL_STORIES complete"
echo " Max iterații: $MAX_ITERATIONS"
echo " Screenshots: $SCREENSHOTS_DIR"
echo "======================================================================="
echo ""
# Verificare rapidă - poate toate sunt deja complete?
if check_all_complete; then
echo "Toate story-urile sunt deja complete!"
exit 0
fi
# Tracker pentru rate limit retry (max 1 retry de iterație-rate-limit per rulare)
RATE_LIMIT_RETRY_USED=0
# Loop principal
for i in $(seq 1 $MAX_ITERATIONS); do
echo ""
echo "==================================================================="
echo " Ralph Iterația $i din $MAX_ITERATIONS"
echo "==================================================================="
# W3: alege next story via DAG (propagă blocked dacă vreun dep a eșuat)
CURRENT_STORY=$(dag_next_story)
if [ -z "$CURRENT_STORY" ]; then
echo ""
echo "==================================================================="
if check_all_complete; then
echo " TOATE STORY-URILE DIN PRD SUNT COMPLETE!"
exit 0
else
echo " NICIUN STORY ELIGIBIL (toate fie complete, fie failed, fie blocked)"
echo " Stories incomplete:"
jq -r '.userStories[] | select(.passes != true) | " - \(.id): \(.title) [failed=\(.failed // false) blocked=\(.blocked // false) retries=\(.retries // 0)]"' "$PRD_FILE"
exit 0
fi
fi
# Status curent
COMPLETE_NOW=$(jq '[.userStories[] | select(.passes == true)] | length' "$PRD_FILE")
NEXT_TITLE=$(jq -r --arg id "$CURRENT_STORY" '.userStories[] | select(.id == $id) | "\(.id): \(.title)"' "$PRD_FILE")
STORY_TAGS=$(jq -r --arg id "$CURRENT_STORY" '.userStories[] | select(.id == $id) | (.tags // []) | join(",")' "$PRD_FILE")
STORY_RETRIES=$(jq -r --arg id "$CURRENT_STORY" '.userStories[] | select(.id == $id) | (.retries // 0)' "$PRD_FILE")
echo " Progress: $COMPLETE_NOW / $TOTAL_STORIES stories complete"
echo " Next: $NEXT_TITLE [tags: ${STORY_TAGS:-<none>}, retries: $STORY_RETRIES]"
echo ""
# Pregătește prompt-ul cu context
FULL_PROMPT=$(cat <<EOF
# Context pentru această iterație Ralph
## Story țintă (DAG-eligible):
$CURRENT_STORY (tags: ${STORY_TAGS:-<none>})
## PRD (prd.json):
$(cat "$PRD_FILE")
## Progress până acum (progress.txt):
$(cat "$PROGRESS_FILE")
## Instrucțiuni pentru această iterație:
$(cat "$PROMPT_FILE")
EOF
)
# Execută Claude Code în modul non-interactiv
LOG_FILE="$SCRIPT_DIR/logs/iteration-$i-$(date +%Y%m%d-%H%M%S).log"
mkdir -p "$SCRIPT_DIR/logs"
# --output-format json + --max-turns pentru control runtime
set +e
echo "$FULL_PROMPT" | claude -p \
--dangerously-skip-permissions \
--output-format json \
--max-turns "$MAX_TURNS" \
2>&1 | tee "$LOG_FILE"
CLAUDE_EXIT=${PIPESTATUS[1]}
set -e
OUTPUT=$(cat "$LOG_FILE")
# Rate limit budget tracking (best-effort, never blocks Ralph)
if [ -n "$USAGE_HELPER" ]; then
"$RALPH_PYTHON" "$USAGE_HELPER" append \
"$USAGE_FILE" "$LOG_FILE" \
--slug "$PROJECT_NAME" \
--story-id "$CURRENT_STORY" \
--iter "$i" 2>/dev/null || true
fi
# W3: rate limit detection (max 1 retry per rulare)
if is_rate_limited "$OUTPUT" || [ "$CLAUDE_EXIT" = "29" ]; then
if [ "$RATE_LIMIT_RETRY_USED" = "0" ]; then
echo ""
echo " ⏸️ Rate limit detectat. Sleep ${RATE_LIMIT_SLEEP}s, apoi retry o dată."
RATE_LIMIT_RETRY_USED=1
echo "## Rate limit la iter $i — sleep $RATE_LIMIT_SLEEP" >> "$PROGRESS_FILE"
sleep "$RATE_LIMIT_SLEEP"
continue # retry aceeași iterație
else
echo " ❌ Rate limit din nou — abort run, mark $CURRENT_STORY rate_limited"
dag_mark_failed "$CURRENT_STORY" "rate_limited"
echo "## Rate limit final la iter $i — abort" >> "$PROGRESS_FILE"
exit 2
fi
fi
# Verifică dacă toate task-urile sunt complete
if echo "$OUTPUT" | grep -q "<promise>COMPLETE</promise>"; then
echo ""
echo "==================================================================="
echo " RALPH A TERMINAT TOATE TASK-URILE!"
echo " Completat la iterația $i din $MAX_ITERATIONS"
echo "==================================================================="
exit 0
fi
# Verifică și prin prd.json
if check_all_complete; then
echo ""
echo "==================================================================="
echo " TOATE STORY-URILE DIN PRD SUNT COMPLETE!"
echo "==================================================================="
exit 0
fi
# W3: tag validation post-iter — chiar dacă Opus a marcat docs, dacă diff atinge .vue/.tsx, force ui
dag_force_tags "$CURRENT_STORY" >/dev/null 2>&1 || true
# W3: dacă story-ul curent ÎNCĂ nu trece (passes==false), incrementăm retries
STILL_INCOMPLETE=$(jq -r --arg id "$CURRENT_STORY" '.userStories[] | select(.id == $id) | (.passes == true)' "$PRD_FILE")
if [ "$STILL_INCOMPLETE" != "true" ]; then
NEW_RETRY=$(dag_incr_retry "$CURRENT_STORY")
echo " Story $CURRENT_STORY încă incomplet. Retries: $NEW_RETRY/3"
if [ "$NEW_RETRY" -ge 3 ] 2>/dev/null; then
echo "$CURRENT_STORY failed: max_retries — sare la următorul"
# mark-failed e deja făcut de incr-retry când >=3, dar idempotent o re-aplicăm
dag_mark_failed "$CURRENT_STORY" "max_retries"
fi
else
echo " ✅ Story $CURRENT_STORY marcat passes=true în iterația asta."
fi
echo " Iterația $i completă. Continuăm..."
sleep 2
done
echo ""
echo "==================================================================="
echo " Ralph a atins limita de iterații ($MAX_ITERATIONS)"
echo " Verifică progress.txt pentru status."
echo "==================================================================="
echo ""
# Afișează stories incomplete
echo "Stories incomplete:"
jq -r '.userStories[] | select(.passes != true) | " - \(.id): \(.title)"' "$PRD_FILE"
exit 1