Three regressions from the SSR rendering-modernization migration, all verified live via agent-browser: - BUG A — New Invoice: choosing a client 500'd from /invoice/new/due-date (ClassCastException: DateTime cannot be cast to java.util.Date). `due-date` and `scheduled-payment-date` called `coerce/from-date` on values already decoded to clj-time DateTimes. Drop the coerce; use the decoded dates. - BUG C — Transaction Edit: any whole-form swap (mode toggle, vendor change, add/remove row) 500'd whenever the txn had >=1 autopay-invoice match (ClassCastException at links-body*: PersistentVector cannot be cast to Named). The autopay link-panel's hidden `action` input was missing `:form ""`, so it serialized alongside the main `action` hidden, producing a duplicate param that Ring collapsed to a vector. Add `:form ""` to match the unpaid/rule panels. - Modal sizes: Vendor/Client/Invoice-Pay modals ballooned to full width because resources/public/output.css was missing their arbitrary Tailwind size classes. Root cause: tailwind.config.js `content` never scanned resources/templates/**/*.html (46 Selmer templates the migration introduced), so a rebuild also dropped template-only classes like md:w-[950px]. Add the templates glob and rebuild; all modal size classes now present, no working modal regressed. Docs: add 2026-06-27 QA findings + resumable fix task list; cross-link from the migration plan. Remaining (per the new plan): Vendor/Client inner step-body overflow, wizard step animations, bulk-edit empty-selection 500, footer EDN leak. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
13 KiB
SSR Modal Regression Fixes — QA Findings & Resumable Task List
Status: BUG A, BUG C, and the CSS size root cause are FIXED + verified live (2026-06-27). Remaining: BUG E/F (wizard inner layout), animations (§3), BUG D/B. Resume from §4. Owner: Bryce Branch:
integreat-execute-refactorDate: 2026-06-27 Context: Follow-up to the SSR rendering-modernization migration (2026-06-02-001-refactor-ssr-rendering-modernization-plan.md, Phases 2–11 complete). The migration to simplified route rendering + whole-form/wizard swaps introduced modal size, animation, and a set of HTTP 500 regressions. This doc records a full browser QA pass (agent-browser, 9 migrated modals) with root causes and fixes.
0. How to resume / reproduce
- App is running on the port in
.http-port(was42987); nREPL innrepl-port(was35689). - Browser session:
agent-browser --session integreat .... Log in viahttp://localhost:<port>/dev-login→ "Continue to dashboard". - Test at a realistic viewport:
agent-browser --session integreat set viewport 1440 900(a short viewport exaggerates modal overflow and gives false positives). - Screenshots from this pass are in
./tmp/(gitignored):02..19-*.png. Raw running notes:./tmp/findings.md. - After rebuilding CSS, the browser caches the old
output.css; bust it by swapping the<link>href (?v=<n>) or hard-reloading, or you'll keep seeing the old sizes.
1. Test matrix (9 migrated modals)
| Modal | File | Type | Result |
|---|---|---|---|
| Transaction Edit | transaction/edit.clj |
form | Opens OK; BUG C — whole-form swap 500 |
| Transaction Bulk Code | transaction/bulk_code.clj |
form | ✅ Works |
| Sales Summary Edit | pos/sales_summaries.clj |
form | ✅ Works |
| Invoice Bulk Edit | invoices.clj |
form | ✅ Works (empty-selection 500 = BUG D) |
| Transaction Rule | admin/transaction_rules.clj |
wizard | ✅ Works |
| Invoice Pay | invoices.clj |
wizard | ✅ Works (narrow until CSS rebuild — see §2) |
| New Invoice | invoice/new_invoice_wizard.clj |
wizard | BUG A — choose-client 500 |
| Vendor | admin/vendors.clj |
wizard | Size broken (§2) + BUG E inner overflow |
| Client | admin/clients.clj |
wizard | Size broken (§2) + BUG F inner overflow |
Cross-cutting: §2 (stale CSS → sizes), BUG B (footer EDN leak), animations (§3).
2. ★ ROOT CAUSE of broken modal SIZES — stale compiled Tailwind CSS
resources/public/output.css (committed, last built Jun 24) is missing the migration's
newer arbitrary size classes. Tailwind only compiles classes present in source at build time,
so any size value added/changed after Jun 24 isn't in the CSS and the element falls back to its
w-full h-full sibling class → the modal balloons to full width.
- Present (these modals size correctly today):
md:w-[750px](New Invoice),md:w-[950px](Transaction Edit / Bulk Code / Sales Summary),md:h-[600px],md:h-[650px],w-[850px](Transaction Rule). - Missing (these balloon / mis-size):
md:w-[760px](Vendor),md:w-[820px](Client),md:h-[520px],md:h-[560px],w-[50em](Invoice Pay).
Verified: rebuilding to a temp file generates all missing classes; after rebuild + cache-bust, the Vendor card went 1257px → 760×520 and Client → 820×560 (exactly as declared).
DEEPER ROOT CAUSE found while fixing (2026-06-27): tailwind.config.js content only scanned
./src/**/*.{cljs,clj,cljc} — it never scanned resources/templates/**/*.html (the 46 Selmer
templates the migration introduced). So a naive rebuild drops every template-only class
(e.g. md:w-[950px] / md:h-[650px], used only in templates/transaction-edit/edit-modal.html,
which would have re-broken Transaction Edit / Bulk Code / Sales Summary). The durable fix is to add
the templates glob to the config, then rebuild.
FIX — DONE & verified:
- Added
"./resources/templates/**/*.html"totailwind.config.jscontent. npx tailwindcss -i resources/input.css -o resources/public/output.css(kept unminified to match the committed file; add--minifyonly if the prod pipeline minifies).
- Verified: all modal size classes now present (
md:w-[760px],md:w-[820px],md:h-[520px],md:h-[560px],w-[50em]and the template-sourcedmd:w-[950px]/md:h-[650px]). Class-diff vs the old CSS shows the only removed classes are orphaned (the deletedmm/*modal-stackforward/backward/group/transition/htmx-*:translate-x-2/3animation set + the unusedlg:w-[900px]sales size) — none still referenced in src/ or templates/. Vendor modal confirmed live at 760×520 (was 1257px). - Still TODO: confirm the prod/CI build runs this tailwind step (so output.css can't drift
again) — ideally wire it into
lein build/ buildspec.
Note: during QA I rebuilt
output.cssto verify, then reverted it so the working tree is clean for review. The verified rebuild is preserved attmp/output-rebuilt.css; backup attmp/output.css.bak.
3. Modal ANIMATIONS
The new wizard step cards swap via hx-target "this" hx-swap "outerHTML" but their
modal-card-advanced carries no transition classes, so step→step and modal enter/leave do not
animate:
- New Invoice steps (
render-basic-details,render-accounts): card class is onlymd:w-[750px] md:h-[600px] w-full h-full— nohtmx-swapping:/htmx-added:variants. next-steps-modal(new_invoice_wizard.clj ~678): only staticscale-100 translate-x-0 opacity-100— missing thehtmx-swapping:/htmx-added:swap variants.- By contrast,
dialog/success-modal-and the Invoice Pay card (last-modal-step transition duration-150) keep transitions, so the intended pattern still exists to copy from.
FIX: add the swap-transition classes to the wizard step cards (mirror success-modal-'s
transition duration-300 ease-in-out htmx-swapping:... htmx-added:... string, or the lighter
last-modal-step transition duration-150 used by the transaction-edit card). Confirm the
htmx-swapping:/htmx-added: custom variants survive the §2 CSS rebuild.
4. Bug list + fixes (prioritized task list)
P0 — functional 500s
BUG A — New Invoice: choosing a client → 500 from /invoice/new/due-date.
- File:
src/clj/auto_ap/ssr/invoice/new_invoice_wizard.clj, fndue-date(and same latent bug inscheduled-payment-date). - Exception:
ClassCastException: org.joda.time.DateTime cannot be cast to java.util.Date. - Cause:
form-vendor-clientdecodes:invoice/date/:invoice/dueto clj-timeDateTime(viaclj-date-schema), butdue-datethen calls(some-> date coerce/from-date)—coerce/from-dateexpects ajava.util.Date.scheduled-payment-datehas the same(some-> due coerce/from-date)but is masked becausedueis nil-guarded by awhen. - Repro: open New Invoice, pick any client (vendor still empty) → red "unexpected error" banner;
network shows PUT
/invoice/new/due-date= 500. REPL-confirmed the decoded date is already aDateTimeandtime/plusworks on it directly. - FIX — DONE & verified live: dropped the
coerce/from-datecalls —due-datenow uses the decodeddate/dueDateTimes directly (removed thedate (some-> date coerce/from-date)rebind and theduecoerce); same forscheduled-payment-date. Live: selecting a client now fires PUT/invoice/new/due-date+/scheduled-payment-date→ 200 (was 500).
BUG C — Transaction Edit: any whole-form swap (mode toggle, vendor change, add/remove row, $/%
toggle) → 500 from /transaction2/edit-form-changed, whenever the txn has ≥1 autopay-invoice
match. (HIGH severity — breaks the flagship modal's core swap doctrine.)
- File:
src/clj/auto_ap/ssr/transaction/edit.clj. - Exception:
ClassCastException: PersistentVector cannot be cast to Namedat edit.clj:849 (links-body*does(name (:action step-params))). - Cause: the link-panels each render a hidden
<input name="action">. The unpaid panel (line 690) and rule panel (line 730) add:form ""to exclude that hidden from form serialization. The autopay panel (line 661) is missing:form "", so itsaction="link-autopay-invoices"hidden is serialized alongside the mainactionhidden (line 862, value "manual"). Ring collapses the duplicateactionparams into a vector →(name vector)throws. - Repro: edit a transaction whose "Link to autopay invoices" tab shows a badge count ≥1, click
"Switch to advanced mode". REPL-confirmed: single
action→ 200;actionas a vector → the exact 500. (Txns with 0 autopay matches renderpanel-empty*with no action-hidden and swap fine — hence intermittent.) - FIX — DONE & verified live: added
:form ""to the autopay panel's action-hidden (edit.clj:661) to match the unpaid/rule panels. Live: on a txn with an autopay match, "Switch to advanced mode" now POSTsedit-form-changed→ 200 with a singleaction=manualparam (the duplicateaction=link-autopay-invoicesis gone) and the mode toggles correctly.- (Optional hardening, not applied: make
links-body*coerce a vector action via(some-> (:action step-params) (as-> a (if (coll? a) (last a) a)) name).)
- (Optional hardening, not applied: make
P1 — sizing / layout (after §2 CSS rebuild)
§2 CSS rebuild — do first; fixes Vendor/Client/Invoice-Pay widths.
BUG E — Vendor wizard: inner step bodies overflow the card. After the CSS rebuild the outer
card is correct (760×520), but the migrated step bodies keep the old fixed w-[600px] h-[350px]
wrappers (from when each step was its own modal card), causing a horizontal scrollbar, a clipped
"Name" field, a detached "Basic Info" header box, and a large empty left region.
- File:
src/clj/auto_ap/ssr/admin/vendors.clj— step bodies at ~393/495/528/555/583. - FIX: drop the per-step
w-[600px] h-[350px]wrappers; let the step body fill the engine's outer card (responsivew-full+ flex column, like the New Invoice steps).
BUG F — Client wizard: same inner-layout overflow (worse — step progress bar + fields clipped, horizontal scroll, detached "Info" header). Outer card correct (820×560) after rebuild.
- File:
src/clj/auto_ap/ssr/admin/clients.clj— step bodies (info step ~1426 etc.). - FIX: same as BUG E — rework migrated step bodies to fill the outer card.
P2 — robustness / cosmetic
BUG D — Invoice Bulk Edit with no selection → 500 (global "Oh, drat!" toast). Works fine with a selection. Should no-op or show a friendly "select invoices first" message.
- File:
src/clj/auto_ap/ssr/invoices.clj(bulk-edit open handler).
BUG B — Footer leaks a raw Hiccup attr-map as text in the red "unexpected error" banner
({:x-show "unexpectedError", ...}). Pre-existing in master (NOT a migration regression), but
visible whenever unexpectedError flips true (e.g. it showed during BUG A). Cause: modal-footer-
calls (hx/alpine-appear {...}) twice — the 2nd return value lands in child position and
renders as literal EDN.
- File:
src/clj/auto_ap/ssr/components/dialog.clj~line 59. - FIX: delete the duplicate
(hx/alpine-appear ...)line inmodal-footer-.
Cosmetic — over-tall empty modals. Invoice/Transaction Bulk Edit and similar use fixed
md:h-[650px]; with little content they show a large empty lower region. Consider letting height
hug content (cap with max-h) rather than a fixed height. Low priority.
5. Suggested order of work
- §2 CSS rebuild (unblocks all width checks; commit
output.css; verify prod build does this). - BUG A + BUG C (the two functional 500s; one-liners each, REPL-verified).
- BUG E / BUG F (wizard step-body layout rework; needs visual iteration per step).
- §3 animations (add swap-transition classes to wizard step cards).
- BUG D, BUG B (robustness / cosmetic).
- Re-run the full agent-browser pass (all 9 modals) + the Playwright e2e suite as the parity gate.
6. What was verified vs. inferred
- Verified in browser + REPL: BUG A (500 + exact exception + fix direction), BUG C (500 + exact exception + vector repro + line 661 cause), §2 CSS root cause (missing classes + rebuild fixes Vendor 1257→760 and Client→820), BUG D (500 on empty selection), Vendor/Client inner overflow (screenshots 16/17), and that Bulk Code / Sales Summary / Transaction Rule / Invoice Pay / Transaction Edit-open all render without errors.
- Inferred (static analysis, not yet visually A/B'd against master): §3 animation regression
(step cards lack
htmx-swapping:/htmx-added:classes) and BUG B being pre-existing.