SSR modernization: ssr-form-migration skill + Transaction Edit plain-form/Selmer migration #14
Reference in New Issue
Block a user
Delete Branch "integreat-execute-refactor"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
SSR form/wizard modernization — the skill + the Transaction Edit migration
Executes the first phases of the SSR Form & Wizard Simplification plan: distils the migration method into a reusable skill, then proves it by migrating the Transaction Edit modal from a single-step "wizard" into a plain, whole-form-swap form — with a measurable simplification and zero user-facing behaviour change (parity proven by e2e).
What changed
The reference doctrine (merged in): whole-form HTMX
hx-selectswaps with zero out-of-band swaps — discrete changes re-render#wizard-form, typed fields swap only a sibling region so focus/caret survive.New skill
ssr-form-migration(.claude/skills/…): the per-migration playbook + references (swap doctrine, render functions, form-vs-wizard, Selmer conventions) and growing cookbooks/gotchas/scorecard — distilled from the real code, so the next modal (Bulk Code) can apply it cold.Transaction Edit modal — the simplification:
*-no-cursor*duplicate and the faked deep cursor (MapCursorre-root); rows now render from explicit data / a top-rooted cursor.edit-form-changedendpoint that dispatches on anopparam; deadaccount-total/account-balanceroutes removed.db/idhidden rides in the form; state is rebuilt server-side fromentity ∪ live form(wrap-derive-state). No custom readers, no merge logic. It's a plain form now.location-selectrenders from a Selmer template via a new Hiccup↔Selmer interop bridge (auto-ap.ssr.selmer), proving the path for the rest.Bugs fixed along the way (both real, user-facing):
:mode(a UI-only field) leaked into the Datomic upsert.Verification
<select>, targeted + whole-form swaps, and add-row all work with 0 console errors.Scorecard (Transaction Edit)
*-no-cursor*twinsNot in scope (later phases)
Dropping the now-thin
mm/ModalWizardStepwrappers, and the cross-cutting Phase 11 Selmer sweep of the sharedcom/typeahead/com/select/com/button-group-button(those shared call sites hold the last mixed-attribute offenders).🤖 Generated with Claude Code
The strangler foundation for migrating interactive SSR components from Hiccup to Selmer (plain-HTML Alpine/HTMX attributes instead of mixed keyword/string encodings). - project.clj: add [selmer "1.12.61"]. - auto-ap.ssr.selmer: render / render-str (selmer/render-file + string), hiccup->html (Hiccup -> string for {{ frag|safe }}), raw (wrap a rendered fragment for embedding in a Hiccup tree without double-escaping), render->hiccup. - resources/templates/interop-smoke.html: proves render-file from the classpath and that plain-HTML alpine attrs (x-model, @keydown, tippy?.show()) pass through verbatim. - selmer_test: 4 tests / 8 assertions covering both interop directions; all green. Proven via REPL + tests: a Hiccup component renders inside a Selmer template, and a Selmer fragment renders inside a Hiccup tree. Both valid during the transition.The 5 manual-coding operations (vendor change, simple/advanced toggle, add row, remove row, $/% toggle) each had their own route + handler, all doing "mutate form state -> render-full-form". Fold them into the single edit-form-changed endpoint, which now dispatches on an `op` form-param to the relevant pure apply-* mutation fn (apply-vendor- changed / apply-toggle-mode / apply-new-account / apply-remove-account / apply-toggle-amount-mode) then re-renders. A missing/unknown op (a plain dependent-field change, e.g. account->location or amount->totals) just re-renders, as before. - edit.clj: 6 handlers -> 1 dispatcher + 5 pure apply-* fns; markup posts to edit-form-changed with :hx-vals {:op "..."}. - routes/transactions.cljc: remove the 5 now-unused route keys. - e2e specs: retarget the vendor selector by op (div[hx-vals*="vendor-changed"]) and point the toggle-amount-mode / vendor response waits at edit-form-changed, since the old per-op route names are gone. (Behavioral assertions unchanged.) Scorecard: manual-coding routes ~10 -> ~5 (operations now one dispatcher). Parity held: swap spec 6/6, full suite 32 pass (Shared Location green; no new regression).First interactive Transaction Edit component rendered from a Selmer template instead of Hiccup/com/select, proving the render-file path + the Hiccup<->Selmer interop bridge on real, e2e-covered markup. - resources/templates/components/location-select.html: plain-HTML <select> with a {% for %} over option maps + {% if opt.selected %}. - location-select*: build options/selected/classes in Clojure (reusing inputs/default-input-classes so styling can't drift), render via sel/render->hiccup, and embed the fragment back into the still-Hiccup account row. - skill: finalize selmer-conventions.md from this validated example (was a stub); add the cookbook entry; scorecard marks the first Selmer component. Verified on a fresh server: full suite 38 pass / 1 unrelated fail, swap 6/6, transaction-edit 8/8 -- the Shared Location test selects through the Selmer <select>, saves, and spreads Shared -> DT. Verified by string-match + e2e (not byte-parity: hh/add-class is set-based so class order differs, CSS is order-independent). Scope note: the modal's remaining attribute-heavy components delegate to the shared com/typeahead / com/select / com/button-group-button; converting those is the cross-cutting Phase 11 Selmer sweep, not a single-modal change (Open decision 2).Migrate every part of the Transaction Edit modal's HTML to Selmer templates (zero Hiccup in the render path) and delete the mm multi-modal "wizard" abstraction entirely -- there was only ever one step. - New auto-ap.ssr.components.selmer (sc) + ~22 shared component partials under resources/templates/components/ (typeahead, button-group, radio-card, data-grid, validated-field, modal, buttons, inputs, SVGs). Each wrapper renders its own partial; dynamic HTMX/Alpine attrs bridge via attrs->str -> {{attrs|safe}}. - 15 modal templates under resources/templates/transaction-edit/. - Delete EditWizard/LinksStep records + all mm/* usage. Plain handlers: flat wrap-decode-edit (fields renamed off step-params[...], stray keys stripped), flat wrap-derive-state, *errors*-based field errors, generic wrap-form-4xx-2. - Drop the edit-wizard-navigate route (routes ~12 -> 5). - Fix: stray `method` (tab button-group hidden) leaked into the upsert -> 500; strip decoded map to schema keys. - e2e selectors updated (#wizard-form->#edit-form, #wizardmodal->#editmodal, step-params[...] field names). Parity: swap 6/6, edit 8/8, suite 38/1 (1 pre-existing unrelated nav test). - ssr-form-migration skill updated with the learnings (composition mechanics, sc/* library, drop-the-wizard recipe, scorecard row, 3 new gotchas). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.