refactor(ssr): Phase 3 — full Selmer migration of Transaction Bulk Code; remove the wizard
Migrates the Transaction Bulk Code modal (a single-step form wearing a full wizard costume) to a plain Selmer form, cold-applying the ssr-form-migration skill. Almost entirely reuse of the Phase-2 work: the whole `sc/*` Selmer component library, `account-typeahead*` / `location-select*`, and the `edit-modal` / `transitioner` chrome are imported wholesale. What changed - Wizard removed: deleted `BulkCodeWizard` / `AccountsStep` records, `MultiStepFormState`, the `step-params[...]` prefix, and all `mm/*` middleware. Replaced with a plain handler + flat `wrap-bulk-state` (decode straight into `bulk-code-schema`, no snapshot round-trip). - Selection round-trip: the non-editable transaction selection is resolved to a concrete not-locked id vector at open and ridden back in hidden `ids[]` fields (the bulk analog of edit's single `db/id`) — no EDN snapshot, no filter re-query, and more correct (codes exactly the rows the user saw). - 100% Selmer render path (only the shared terminal `com/success-modal` keeps Hiccup — heuristic-9 exception). New shared component `sc/select` (`location-select.html` generalized) for the status dropdown. - Routes 4 -> 3: GET `bulk-code` (open), POST `bulk-code-submit`, POST `bulk-code-form-changed` (one whole-form op dispatcher folding the old `new-account` + `vendor-changed` routes). Location swap moved off `find *` onto explicit `#account-location-<index>` + `hx-select`. - Fixed a latent correctness bug surfaced by the migration: the vendor typeahead needs `:id` (value-keyed `:key`) or its value-bound hidden goes stale across a whole-form swap and posts blank. Scorecard delta (transaction/bulk_code.clj): mm coupling 19->0, snapshot merges 4->0, wizard records 3->0, step-params 10->0, routes 4->3, OOB 0, Hiccup-in-render ->0 (bar success-modal). LOC 420->506 (documented exception: the wizard was a thin shell over mm/* defaults, so explicitness moves shared plumbing into the file). Cookbook: reused the entire Phase-2 sc/* lib + chrome, added sc/select. Verification: bulk-code-transactions.spec.ts 13/13; full Playwright suite 39/39; cljfmt clean. Skill fed: scorecard row + narrative + LOC exception; gotchas (value-bound typeahead keying, selection-as-ids round-trip); cookbook (sc/select). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -198,8 +198,45 @@ Post-adoption baseline is **39 pass / 0 fail** — the previously-flaky
|
||||
`transaction-navigation.spec.ts` date-range test is now green, because `/test-reset` removes
|
||||
the residual mutation it was tripping over.
|
||||
|
||||
## A value-bound typeahead hidden goes stale across a whole-form swap unless keyed
|
||||
|
||||
A typeahead (`sc/typeahead`) posts its value through a hidden `<input :value="value.value">`
|
||||
whose DOM `.value` is set by Alpine, not by the server-rendered static `value` attr. After a
|
||||
**whole-form `outerHTML` swap** that re-renders the typeahead, Alpine may preserve the *previous*
|
||||
component's empty `.value` instead of binding the new server value — so the field posts blank
|
||||
on the next submit. Fix: pass **`:id`** to `sc/typeahead` (the account typeahead already does).
|
||||
`:id` makes the wrapper emit `:key (str id "--" value)`, and the value-keyed `:key` forces a
|
||||
clean Alpine re-init that lands the server value. The bulk-code *vendor* typeahead hit this
|
||||
(account rows didn't, because they pass `:id`) — symptom: "vendor not preserved on a validation
|
||||
re-render." Note the testing trap: reading the hidden's `.value` in isolation
|
||||
(`inputValue()` / `toHaveValue`) is an unreliable probe — it lags Alpine. Assert what the form
|
||||
**actually posts** instead: `new FormData(form).get('vendor')` (wrap in `expect.poll`).
|
||||
|
||||
## Round-trip a multi-row selection as `ids[]`, not as an EDN/filter snapshot
|
||||
|
||||
A bulk modal acts on a *selection* of N entities (bulk-code: the checked transactions), the
|
||||
analog of a single modal's one `db/id`. The wizard stashed the whole search-params blob (filters
|
||||
+ `selected` + `all-selected`) in the EDN snapshot and re-ran the filter query on every post.
|
||||
Don't carry that forward. Instead **resolve the selection to a concrete id vector once at open**
|
||||
(`selected->ids` → the not-locked set) and ride it back in hidden `ids[0..n]` fields; re-read it
|
||||
on each post (`[:vector {:coerce? true} entity-id]` + the `coerce-vector` transformer turns the
|
||||
`{"0" "123"}` index-map into `[123]`). No snapshot, no filter round-trip, and it's *more* correct
|
||||
— you code exactly the rows the user saw, immune to data changing between open and submit. This
|
||||
is heuristic 2 → 0 for a multi-select modal.
|
||||
|
||||
## Scorecard exceptions (ratchet violations with a reason)
|
||||
|
||||
**Heuristic 4 (LOC net ↓) — exception (Phase 3, Transaction Bulk Code: 420→506).** When the
|
||||
modal's wizard was a *thin* shell that delegated almost everything to `mm/*` defaults
|
||||
(`default-render-step`, `default-render-wizard`, `submit-handler`, `open-wizard-handler`),
|
||||
ripping the wizard out moves that previously-shared plumbing **into the file** as explicit
|
||||
render/decode/submit/handler code, so the single-file LOC rises even though total system
|
||||
complexity drops. This is the opposite of a fat wizard (edit went 1608→1548). The trade is
|
||||
intended and every other heuristic improved sharply (mm coupling 19→0, snapshot merges 4→0,
|
||||
wizard records 3→0, routes 4→3, `find *`→explicit-id swap). Watch for it on the small
|
||||
"single-step wearing a wizard costume" modals — LOC is the wrong headline metric there;
|
||||
the mm-coupling / snapshot / route counts are.
|
||||
|
||||
**Heuristic 9 (Hiccup in render path) — partial exception (Phase 2-final).** The post-save
|
||||
`com/success-modal` confirmation dialogs in `save-handler` keep ~6 `[:p …]` Hiccup lines.
|
||||
They are terminal responses (shown after the form closes), reuse a shared dialog component,
|
||||
|
||||
Reference in New Issue
Block a user