← Back to brief

reference pwa dismiss anti regression

memory · reference_pwa_dismiss_anti_regression.md

The bug class (caught + fixed 2026-05-04)

Symptom: Operator clicks Dismiss on an approval card. Button changes to "Retry" and stays stuck. Clicking again reproduces. Card never goes away.

Root cause: queue items written to pending_*.jsonl without an id field. build_pwa.py rendered them as <button class="approval-dismiss" data-id="" data-channel="">. POST /api/dismiss returned 400 "missing id or channel". UI fell back to "Retry" — but every retry re-fails identically.

Affected: 24+ cards across pending_drafts (14/29 missing id), pending_smartlead_actions (24/24 missing id), pending_hs_updates (39/62 missing id) at the time of discovery.

The fix architecture

build_pwa.py now has _synthesize_card_id(item, channel_id) which produces a stable 12-char content hash for any item missing an id. IDs follow pattern <channel>-auto-<12hex>. Same content → same ID across rebuilds, so a dismiss recorded yesterday still hides the card today.

render_approval_item(item, channel_id="") accepts channel from the loop (the queue file determines channel — items don't always carry it). NEVER renders with empty data-id or data-channel.

Four layers of permanence

Layer 1 — Build-time assertion (build_pwa.py)

Before writing index.html, the build script greps the HTML for <button class="approval-dismiss" data-id="">. If ANY are found, raises RuntimeError and aborts the build. The PWA never ships with the regression.

Layer 2 — Gate-proof anti-regression (gate_proof_runner.sh PROOF 14)

4 gates verify the fix is intact every time the gate-proof fires: 1. _synthesize_card_id helper present in build_pwa.py 2. render_approval_item signature accepts channel_id parameter 3. Rendered pwa/index.html has zero cards with empty data-id 4. Rendered pwa/index.html has zero cards referencing closed-lost lead names (Tim Beegle, Sara Schulze, Fred Surganty)

Layer 3 — Nightly stale-drain (pwa_stale_drain.py)

Wired into nightly-consolidation Section G0 (runs BEFORE the dismiss-queue reconciliation). Drains:

Archives to dismissed_archive.jsonl with _archived_reason metadata. Idempotent.

Layer 4 — This memory file

Future agent sessions reading this memory know: 1. The bug class exists and is documented 2. The fix is layered and any single layer compromise is detected by the others 3. Modifying render_approval_item or _synthesize_card_id requires updating PROOF 14 gates accordingly 4. Adding a new queue type requires ensuring write-time IDs OR reliance on the synthesis fallback

What NOT to do (regression vectors to avoid)

Joseph's verbatim feedback (the trigger)

> "PWA is a mess and I'm still not sure what dismiss does when I hit it and most still say retry when I hit the dismiss button. Fully fix the PWA today"

> "Make sure all fixes are permanent going forward and regression in anyway is prevented."

Files that need to stay in sync

If any of these drifts (e.g., a refactor of build_pwa.py removes the synthesizer), PROOF 14 gates fail next gate-proof run → fix_queue gets a manual_fix entry → operator surfaces the regression before customers notice.