feat(5.9): US-005 - poll-guard modal/bife pe trigger periodic
- base.html: listener htmx:beforeRequest scopat la #submissions-wrap care anuleaza (preventDefault) DOAR poll-ul periodic (fara requestConfig.triggeringEvent) cat timp modalul de detaliu e deschis SAU exista checkbox de bulk bifat. - F5/R6: trimiteriChanged si submit-ul de filtru au triggeringEvent -> trec mereu, deci pauza nu ramane lipita permanent daca randul bifat paraseste filtrul. - Resume automat (anularea nu opreste timer-ul htmx) + resume explicit pe checkbox change via delegare pe body -> trimiteriChanged from:body (pastreaza filtrul). - Vechea pauza pe „rand expandat" (5.8) era deja inlocuita de modalul global (US-003). - 3 teste noi in tests/test_web_modal.py; suita 843 passed, 1 deselected. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -646,5 +646,47 @@
|
|||||||
});
|
});
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
|
<script>
|
||||||
|
// Poll-guard (PRD 5.9 US-005, R6). Inlocuieste vechea pauza pe „rand expandat" (5.8):
|
||||||
|
// randul-sibling de detaliu nu mai exista (US-003 l-a mutat in modalul global, care
|
||||||
|
// traieste in afara #submissions-wrap -> un swap de poll nu-l atinge). Aici oprim
|
||||||
|
// poll-ul de 15s de a REINCARCA lista cat timp (a) modalul e deschis SAU (b) exista
|
||||||
|
// cel putin un checkbox de bulk bifat — altfel modalul s-ar reseta / bifele s-ar sterge.
|
||||||
|
//
|
||||||
|
// CRITIC (F5): blocam DOAR trigger-ul periodic. In htmx `load`/`every 15s` declanseaza
|
||||||
|
// requestul FARA `triggeringEvent`; `trimiteriChanged` (HX-Trigger dupa corectie/stergere)
|
||||||
|
// si submit-ul/filtrul AU `triggeringEvent` -> TREC MEREU. Asa evitam blocajul permanent:
|
||||||
|
// daca randul bifat paraseste filtrul, pauza nu ramane lipita (pauza e legata strict de
|
||||||
|
// trigger-ul periodic, nu de o stare „sticky"). Anularea unui `htmx:beforeRequest` NU
|
||||||
|
// opreste timer-ul htmx (se reprogrameaza singur) -> poll-ul reia automat la urmatorul
|
||||||
|
// tic cand ambele conditii dispar; nu se pierde scroll, focus sau selectia de bife.
|
||||||
|
(function() {
|
||||||
|
function modalDeschis() {
|
||||||
|
var o = document.getElementById('modal-detaliu');
|
||||||
|
return !!(o && !o.hidden);
|
||||||
|
}
|
||||||
|
function existaBifa() {
|
||||||
|
return !!document.querySelector('#submissions-wrap input[name="submission_id"]:checked');
|
||||||
|
}
|
||||||
|
document.body.addEventListener('htmx:beforeRequest', function(evt) {
|
||||||
|
var d = evt.detail || {};
|
||||||
|
if (!d.elt || d.elt.id !== 'submissions-wrap') return; // doar poll-ul listei
|
||||||
|
var rc = d.requestConfig || {};
|
||||||
|
if (rc.triggeringEvent) return; // trimiteriChanged / filtru: TREC MEREU
|
||||||
|
if (modalDeschis() || existaBifa()) evt.preventDefault(); // pauza scopata pe periodic
|
||||||
|
});
|
||||||
|
// Resume pe checkbox `change`->gol: delegare pe body ca sa prinda si checkbox-urile
|
||||||
|
// randate dupa swap. Cand modalul e inchis si nu mai exista nicio bifa, fortam un
|
||||||
|
// refresh imediat (nu mai asteptam ticul de 15s) prin `trimiteriChanged from:body`,
|
||||||
|
// care pastreaza filtrul curent (hx-include #filtre-trimiteri) si trece de guard.
|
||||||
|
document.body.addEventListener('change', function(evt) {
|
||||||
|
var t = evt.target;
|
||||||
|
if (!(t && t.name === 'submission_id')) return;
|
||||||
|
if (!modalDeschis() && !existaBifa() && window.htmx) {
|
||||||
|
htmx.trigger(document.body, 'trimiteriChanged');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -251,12 +251,12 @@
|
|||||||
],
|
],
|
||||||
"requiresBrowserCheck": true,
|
"requiresBrowserCheck": true,
|
||||||
"requiresDesignReview": false,
|
"requiresDesignReview": false,
|
||||||
"passes": false,
|
"passes": true,
|
||||||
"failed": false,
|
"failed": false,
|
||||||
"blocked": false,
|
"blocked": false,
|
||||||
"retries": 0,
|
"retries": 0,
|
||||||
"failureReason": "",
|
"failureReason": "",
|
||||||
"notes": ""
|
"notes": "Poll-guard adaugat in base.html: htmx:beforeRequest scopat la #submissions-wrap, anuleaza (preventDefault) DOAR trigger-ul periodic (fara requestConfig.triggeringEvent) cat timp modalul e deschis SAU exista bifa de bulk. trimiteriChanged si submit-ul de filtru au triggeringEvent -> trec mereu (F5: fara blocaj permanent). Anularea nu opreste timer-ul htmx (se reprogrameaza) -> resume automat; resume explicit pe checkbox change via delegare pe body. Vechea pauza pe rand expandat (5.8) era deja inlocuita de modal (US-003). 3 teste noi in test_web_modal.py; suita 843 passed."
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -231,3 +231,44 @@ Status: PASS (loop). E2E browser deferat la VERIFY.
|
|||||||
- login/signup mosteneau deja `width:100%` inline + `margin:40px auto`; lipsea doar plafonarea
|
- login/signup mosteneau deja `width:100%` inline + `margin:40px auto`; lipsea doar plafonarea
|
||||||
latimii pe mobil — rezolvata centralizat prin clasa `.auth-card` (fara duplicare de stil inline).
|
latimii pe mobil — rezolvata centralizat prin clasa `.auth-card` (fara duplicare de stil inline).
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## US-005 — Poll-ul nu mai inchide modalul si nu mai sterge bifele (R6)
|
||||||
|
|
||||||
|
### Implementare (app/web/templates/base.html, +1 bloc <script>):
|
||||||
|
- Poll-guard nou: listener `htmx:beforeRequest` pe body, scopat la `#submissions-wrap`
|
||||||
|
(`d.elt.id !== 'submissions-wrap'` -> iese). Anuleaza (`evt.preventDefault()`) DOAR
|
||||||
|
trigger-ul periodic — in htmx `load`/`every 15s` declanseaza request FARA
|
||||||
|
`requestConfig.triggeringEvent`; `trimiteriChanged` si submit-ul/filtrul AU
|
||||||
|
triggeringEvent -> `if (rc.triggeringEvent) return;` => TREC MEREU.
|
||||||
|
- Pauza activa cat timp `modalDeschis()` (#modal-detaliu nu e hidden) SAU `existaBifa()`
|
||||||
|
(`#submissions-wrap input[name="submission_id"]:checked`).
|
||||||
|
- Resume: anularea unui beforeRequest NU opreste timer-ul htmx (`ot(e,t,r)` reprogrameaza
|
||||||
|
poll-ul indiferent de firing) -> reia automat la urmatorul tic cand ambele conditii dispar.
|
||||||
|
Resume explicit/imediat pe checkbox `change` (delegare pe body, prinde bifele post-swap):
|
||||||
|
cand modal inchis + nicio bifa -> `htmx.trigger(document.body, 'trimiteriChanged')`
|
||||||
|
(pastreaza filtrul via hx-include si trece de guard).
|
||||||
|
- Vechea pauza pe „rand expandat" (5.8) era deja eliminata de US-003 (detaliul inline ->
|
||||||
|
modal global in afara #submissions-wrap); guard-ul o inlocuieste conceptual.
|
||||||
|
|
||||||
|
### F5 — evitarea blocajului permanent:
|
||||||
|
- Pauza e legata STRICT de trigger-ul periodic (lipsa triggeringEvent), nu de o stare sticky.
|
||||||
|
Daca randul bifat paraseste filtrul, `trimiteriChanged`/submit-ul de filtru au triggeringEvent
|
||||||
|
si nu sunt niciodata anulate -> lista se reincarca, pauza nu ramane lipita.
|
||||||
|
|
||||||
|
### Teste (tests/test_web_modal.py, +3):
|
||||||
|
- test_poll_pauzat_cat_modal_deschis — guard scopat la #submissions-wrap + modalDeschis + preventDefault.
|
||||||
|
- test_poll_pauzat_cat_exista_bifa — existaBifa (`...submission_id]:checked`) + resume delegat pe `change`.
|
||||||
|
- test_trimiteriChanged_inca_reincarca_cu_bifa (R6/F5) — `rc.triggeringEvent) return` => trimiteriChanged/filtru trec.
|
||||||
|
- Structural (modal in afara #submissions-wrap) ramane acoperit de test_modal_container_in_afara_submissions_wrap (US-003).
|
||||||
|
|
||||||
|
### Gates:
|
||||||
|
- Tests: PASS — suita completa 843 passed, 1 deselected (era 840; +3 US-005).
|
||||||
|
- Browser (E2E requiresBrowserCheck): DEFERAT la VERIFY (bifez 2 trimiteri + astept >15s -> bifele raman;
|
||||||
|
deschid modalul + astept >15s -> ramane deschis cu datele intacte).
|
||||||
|
|
||||||
|
### Learnings:
|
||||||
|
- htmx pune pe pauza un poll fara a opri timer-ul daca anulezi `htmx:beforeRequest`: requestul curent
|
||||||
|
e abandonat, dar `processPolling` se reprogrameaza singur -> pauza „leneasa" fara cleanup de timer.
|
||||||
|
- Distinctia sursa periodic vs user/HX-Trigger se face curat prin `requestConfig.triggeringEvent`
|
||||||
|
(null pe load/poll, setat pe evenimente user/custom) — fara flag-uri ad-hoc.
|
||||||
|
---
|
||||||
|
|||||||
@@ -159,3 +159,60 @@ def test_modal_hookuri_js_prezente(client):
|
|||||||
assert "inchideModal" in js
|
assert "inchideModal" in js
|
||||||
# API public pastrat (butoanele/rutele pot inchide modalul)
|
# API public pastrat (butoanele/rutele pot inchide modalul)
|
||||||
assert "window.inchideDetaliu" in js
|
assert "window.inchideDetaliu" in js
|
||||||
|
|
||||||
|
|
||||||
|
# --- PRD 5.9 US-005 (R6): poll-guard ---------------------------------------
|
||||||
|
# Modalul + selectia trebuie sa supravietuiasca poll-ului de 15s. Logica e JS in
|
||||||
|
# base.html: testam la nivel de markup/handler ca guard-ul exista si distinge corect
|
||||||
|
# sursa trigger-ului (periodic vs trimiteriChanged/filtru). Comportamentul runtime
|
||||||
|
# efectiv (anularea propriu-zisa) e validat E2E (requiresBrowserCheck) — aici asertam
|
||||||
|
# codul/atributele care il implementeaza.
|
||||||
|
|
||||||
|
|
||||||
|
def test_poll_pauzat_cat_modal_deschis(client):
|
||||||
|
"""Guard-ul de poll exista si, cat modalul de detaliu e deschis, anuleaza
|
||||||
|
reincarcarea periodica a listei (#submissions-wrap), nu pe restul."""
|
||||||
|
_create_account_user("poll1@test.com")
|
||||||
|
_login(client, "poll1@test.com")
|
||||||
|
js = client.get("/?tab=acasa").text
|
||||||
|
|
||||||
|
# Guard scopat la poll-ul listei, declansat pe htmx:beforeRequest.
|
||||||
|
assert "htmx:beforeRequest" in js
|
||||||
|
assert "d.elt.id !== 'submissions-wrap'" in js, "guard-ul trebuie scopat la #submissions-wrap"
|
||||||
|
# Conditia (a): modal deschis -> pauza (preventDefault).
|
||||||
|
assert "modalDeschis" in js
|
||||||
|
assert "modal-detaliu" in js and "hidden" in js
|
||||||
|
assert "evt.preventDefault()" in js, "pauza scopata se face prin preventDefault"
|
||||||
|
|
||||||
|
|
||||||
|
def test_poll_pauzat_cat_exista_bifa(client):
|
||||||
|
"""Conditia (b): macar un checkbox de bulk bifat -> poll-ul periodic e pus pe
|
||||||
|
pauza. Resume pe checkbox `change` prin delegare pe body (prinde si bifele
|
||||||
|
randate dupa swap)."""
|
||||||
|
_create_account_user("poll2@test.com")
|
||||||
|
_login(client, "poll2@test.com")
|
||||||
|
js = client.get("/?tab=acasa").text
|
||||||
|
|
||||||
|
# Detecteaza bifa de bulk in interiorul #submissions-wrap.
|
||||||
|
assert "existaBifa" in js
|
||||||
|
assert 'input[name="submission_id"]:checked' in js
|
||||||
|
# Resume: delegare pe body pe evenimentul `change` al checkbox-ului de bulk.
|
||||||
|
assert "addEventListener('change'" in js
|
||||||
|
assert "t.name === 'submission_id'" in js
|
||||||
|
|
||||||
|
|
||||||
|
def test_trimiteriChanged_inca_reincarca_cu_bifa(client):
|
||||||
|
"""R6 (F5): guard-ul NU anuleaza request-urile cu `triggeringEvent`
|
||||||
|
(trimiteriChanged / submit filtru) — acelea TREC MEREU, ca pauza sa nu ramana
|
||||||
|
lipita permanent daca randul bifat paraseste filtrul."""
|
||||||
|
_create_account_user("poll3@test.com")
|
||||||
|
_login(client, "poll3@test.com")
|
||||||
|
js = client.get("/?tab=acasa").text
|
||||||
|
|
||||||
|
# Numai trigger-ul periodic (fara triggeringEvent) e candidat la pauza;
|
||||||
|
# orice request cu triggeringEvent iese devreme din guard.
|
||||||
|
assert "triggeringEvent" in js
|
||||||
|
assert "rc.triggeringEvent) return" in js, \
|
||||||
|
"request-urile cu triggeringEvent (trimiteriChanged/filtru) trebuie sa treaca mereu"
|
||||||
|
# Resume explicit reutilizeaza acelasi canal `trimiteriChanged` (pastreaza filtrul).
|
||||||
|
assert "trimiteriChanged" in js
|
||||||
|
|||||||
Reference in New Issue
Block a user