refactor(ssr): collapse 5 manual-coding operation routes into edit-form-changed (heuristic 6)

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).
This commit is contained in:
2026-06-03 07:07:52 -07:00
parent 1d5a95196f
commit 0f5650b73e
5 changed files with 63 additions and 79 deletions

View File

@@ -263,7 +263,8 @@
:value total})]]
[:div.mt-1
[:a.text-sm.text-blue-600.hover:underline.cursor-pointer
{:hx-post (bidi/path-for ssr-routes/only-routes ::route/edit-wizard-toggle-mode)
{:hx-post (bidi/path-for ssr-routes/only-routes ::route/edit-form-changed)
:hx-vals (hx/json {:op "toggle-mode"})
:hx-include "closest form"
:hx-target "#wizard-form"
:hx-select "#wizard-form"
@@ -344,8 +345,8 @@
(com/text-input (assoc amount-attrs :type "number" :step "0.01"))
(com/money-input amount-attrs))))))
(com/data-grid-cell {:class "align-top"}
(com/a-icon-button {:hx-post (bidi/path-for ssr-routes/only-routes ::route/edit-wizard-remove-account)
:hx-vals (hx/json {:row-index (or index 0)})
(com/a-icon-button {:hx-post (bidi/path-for ssr-routes/only-routes ::route/edit-form-changed)
:hx-vals (hx/json {:op "remove-account" :row-index (or index 0)})
:hx-target "#wizard-form"
:hx-select "#wizard-form"
:hx-swap "outerHTML"
@@ -428,7 +429,8 @@
:value amount-mode
:name "step-params[amount-mode]"
:orientation :horizontal
:hx-post (bidi/path-for ssr-routes/only-routes ::route/toggle-amount-mode)
:hx-vals (hx/json {:op "toggle-amount-mode"})
:hx-post (bidi/path-for ssr-routes/only-routes ::route/edit-form-changed)
:hx-target "#wizard-form"
:hx-select "#wizard-form"
:hx-swap "outerHTML"
@@ -468,7 +470,8 @@
(com/data-grid-row {:class "new-row"}
(com/data-grid-cell {:colspan 4}
(com/a-button {:hx-post (bidi/path-for ssr-routes/only-routes ::route/edit-wizard-new-account)
(com/a-button {:hx-post (bidi/path-for ssr-routes/only-routes ::route/edit-form-changed)
:hx-vals (hx/json {:op "new-account"})
:hx-target "#wizard-form"
:hx-select "#wizard-form"
:hx-swap "outerHTML"
@@ -490,7 +493,8 @@
[:div#manual-coding-section
(com/hidden {:name "step-params[mode]" :value (name mode)})
[:div {:hx-trigger "change"
:hx-post (bidi/path-for ssr-routes/only-routes ::route/edit-vendor-changed)
:hx-post (bidi/path-for ssr-routes/only-routes ::route/edit-form-changed)
:hx-vals (hx/json {:op "vendor-changed"})
:hx-target "#wizard-form"
:hx-select "#wizard-form"
:hx-swap "outerHTML"
@@ -516,7 +520,8 @@
(when (<= row-count 1)
[:div.mb-2
[:a.text-sm.text-blue-600.hover:underline.cursor-pointer
{:hx-post (bidi/path-for ssr-routes/only-routes ::route/edit-wizard-toggle-mode)
{:hx-post (bidi/path-for ssr-routes/only-routes ::route/edit-form-changed)
:hx-vals (hx/json {:op "toggle-mode"})
:hx-include "closest form"
:hx-target "#wizard-form"
:hx-select "#wizard-form"
@@ -528,17 +533,17 @@
[:div#account-grid-body
(account-grid-body* request)]))])]))
(defn toggle-amount-mode [request]
(defn apply-toggle-amount-mode
"edit-form-changed op: convert account amounts between $ and % and record the new mode."
[request]
(let [snapshot (-> request :multi-form-state :snapshot)
old-mode (or (:amount-mode snapshot) "$")
new-mode (or (get-in request [:multi-form-state :step-params :amount-mode]) "$")
total (Math/abs (or (:transaction/amount snapshot) 0.0))
accounts (convert-accounts-mode (:transaction/accounts snapshot) old-mode new-mode total)
updated-request (-> request
(assoc-in [:multi-form-state :snapshot :transaction/accounts] accounts)
(assoc-in [:multi-form-state :snapshot :amount-mode] new-mode))]
(html-response
(render-full-form updated-request))))
accounts (convert-accounts-mode (:transaction/accounts snapshot) old-mode new-mode total)]
(-> request
(assoc-in [:multi-form-state :snapshot :transaction/accounts] accounts)
(assoc-in [:multi-form-state :snapshot :amount-mode] new-mode))))
(defn transaction-details-panel [tx]
[:div.p-4.space-y-4
@@ -1372,14 +1377,7 @@
[request]
(mm/render-wizard edit-wizard request))
(defn edit-form-changed-handler
"Generic handler that re-renders the whole form. Used when any field changes
and we need the server to re-compute dependent fields."
[request]
(html-response
(render-full-form request)))
(defn edit-vendor-changed-handler [request]
(defn apply-vendor-changed [request]
(let [multi-form-state (:multi-form-state request)
snapshot (:snapshot multi-form-state)
step-params (:step-params multi-form-state)
@@ -1421,10 +1419,9 @@
(assoc-in [:multi-form-state :step-params :transaction/accounts] [new-account])))
request)
(assoc-in [:multi-form-state :step-params :transaction/vendor] vendor-id))]
(html-response
(render-full-form render-request))))
render-request))
(defn edit-wizard-toggle-mode-handler [request]
(defn apply-toggle-mode [request]
(let [step-params (-> request :multi-form-state :step-params)
snapshot (-> request :multi-form-state :snapshot)
current-mode (keyword (or (:mode step-params) "simple"))
@@ -1453,11 +1450,10 @@
(if first-row [first-row] []))
(assoc-in [:multi-form-state :step-params :mode]
(name target-mode)))))]
(html-response
(render-full-form render-request))))
render-request))
(defn edit-wizard-new-account-handler
"Adds a new account row and re-renders the whole form."
(defn apply-new-account
"edit-form-changed op: append a fresh account row."
[request]
(let [snapshot (-> request :multi-form-state :snapshot)
amount-mode (or (:amount-mode snapshot) "$")
@@ -1471,12 +1467,10 @@
updated-request (-> request
(assoc-in [:multi-form-state :snapshot :transaction/accounts] updated-accounts)
(assoc-in [:multi-form-state :step-params :transaction/accounts] updated-accounts))]
(html-response
(render-full-form updated-request))))
updated-request))
(defn edit-wizard-remove-account-handler
"Removes an account row and re-renders the whole form.
Expects a row-index in the form params."
(defn apply-remove-account
"edit-form-changed op: remove the account row at form-param row-index."
[request]
(let [row-index (some-> request :form-params (get "row-index") Integer/parseInt)
snapshot (-> request :multi-form-state :snapshot)
@@ -1488,8 +1482,24 @@
updated-request (-> request
(assoc-in [:multi-form-state :snapshot :transaction/accounts] updated-accounts)
(assoc-in [:multi-form-state :step-params :transaction/accounts] updated-accounts))]
updated-request))
(defn edit-form-changed-handler
"Single whole-form re-render endpoint. Dispatches on the `op` form-param to apply the
relevant state mutation (vendor change, mode toggle, add/remove row, $/% toggle), then
re-renders the whole form. A missing/unknown op (a plain dependent-field change) just
re-renders. Replaces the per-operation edit-wizard-* / toggle-amount-mode routes."
[request]
(let [op (get-in request [:form-params "op"])
request' (case op
"vendor-changed" (apply-vendor-changed request)
"toggle-mode" (apply-toggle-mode request)
"new-account" (apply-new-account request)
"remove-account" (apply-remove-account request)
"toggle-amount-mode" (apply-toggle-amount-mode request)
request)]
(html-response
(render-full-form updated-request))))
(render-full-form request'))))
(def key->handler
(apply-middleware-to-all-handlers
@@ -1509,10 +1519,6 @@
(wrap-entity [:multi-form-state :snapshot :db/id] d-transactions/default-read)
(mm/wrap-wizard edit-wizard)
(mm/wrap-decode-multi-form-state))
::route/edit-vendor-changed (-> edit-vendor-changed-handler
(mm/wrap-wizard edit-wizard)
(wrap-entity [:multi-form-state :snapshot :db/id] d-transactions/default-read)
(mm/wrap-decode-multi-form-state))
::route/location-select (-> location-select
(wrap-schema-enforce :query-schema [:map
[:name :string]
@@ -1524,23 +1530,6 @@
(mm/wrap-wizard edit-wizard)
(wrap-entity [:multi-form-state :snapshot :db/id] d-transactions/default-read)
(mm/wrap-decode-multi-form-state))
::route/toggle-amount-mode (-> toggle-amount-mode
(mm/wrap-wizard edit-wizard)
(wrap-entity [:multi-form-state :snapshot :db/id] d-transactions/default-read)
(mm/wrap-decode-multi-form-state))
::route/edit-wizard-toggle-mode (-> edit-wizard-toggle-mode-handler
(mm/wrap-wizard edit-wizard)
(wrap-entity [:multi-form-state :snapshot :db/id] d-transactions/default-read)
(mm/wrap-decode-multi-form-state))
::route/edit-wizard-new-account (-> edit-wizard-new-account-handler
(mm/wrap-wizard edit-wizard)
(wrap-entity [:multi-form-state :snapshot :db/id] d-transactions/default-read)
(mm/wrap-decode-multi-form-state))
::route/edit-wizard-remove-account (-> edit-wizard-remove-account-handler
(mm/wrap-wizard edit-wizard)
(wrap-entity [:multi-form-state :snapshot :db/id] d-transactions/default-read)
(mm/wrap-decode-multi-form-state))
::route/unlink-payment (-> unlink-payment
(wrap-must {:activity :edit :subject :transaction} (fn get-client [request] (-> request :entity :transaction/client)))
(wrap-entity [:multi-form-state :snapshot :db/id] d-transactions/default-read)