Make swaps precise: drop no-op requests, swap only the affected field

Refine per-trigger granularity now that the swap target is explicit:

- Memo issues no request at all -- it affects nothing else, so its value just
  rides along in the form and is merged into the snapshot on save. (Changing the
  Location *value* likewise issues no request -- it never did; that cell's request
  is the account->location dependency.)
- Account select swaps only that row's Location cell (#account-location-<index> /
  #simple-account-location) instead of the whole form. Selecting an account only
  affects the valid Location options (computed from the posted account-id), so a
  precise cell swap is safe -- no snapshot dependency.

Account-structural changes (vendor, add/remove row, mode toggle, $/% radio) keep
swapping the whole form: their accounts+amount-mode state is interdependent and
round-trips through the single form-level snapshot hidden field, so a whole-form
swap is what keeps it consistent with zero OOB.

Update the memo test to assert it fires no request and keeps its value/caret.
Full e2e suite: 27 passed / 2 failed (same pre-existing, unrelated failures).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-01 20:12:22 -07:00
parent 5f1bb6db82
commit 482b4802ff
2 changed files with 45 additions and 53 deletions

View File

@@ -224,24 +224,27 @@
:name (fc/field-name)
:x-model "simpleAccountId"})]))
(fc/with-field :transaction-account/location
(com/validated-field
{:label "Location"
:errors (fc/field-errors)
:x-hx-val:account-id "simpleAccountId"
:hx-vals (hx/json (cond-> {:name (fc/field-name)}
client-id (assoc :client-id client-id)))
:x-dispatch:changed "simpleAccountId"
:hx-trigger "changed"
:hx-post (bidi/path-for ssr-routes/only-routes ::route/edit-form-changed)
:hx-target "#wizard-form"
:hx-select "#wizard-form"
:hx-swap "outerHTML"
:hx-include "closest form"}
(location-select*
{:name (fc/field-name)
:account-location (:account/location account-id)
:client-locations (pull-attr (dc/db conn) :client/locations client-id)
:value location-val})))
;; Selecting the account only affects the valid Location options, so the
;; change swaps just this cell -- nothing else needs to re-render.
[:div {:id "simple-account-location"}
(com/validated-field
{:label "Location"
:errors (fc/field-errors)
:x-hx-val:account-id "simpleAccountId"
:hx-vals (hx/json (cond-> {:name (fc/field-name)}
client-id (assoc :client-id client-id)))
:x-dispatch:changed "simpleAccountId"
:hx-trigger "changed"
:hx-post (bidi/path-for ssr-routes/only-routes ::route/edit-form-changed)
:hx-target "#simple-account-location"
:hx-select "#simple-account-location"
:hx-swap "outerHTML"
:hx-include "closest form"}
(location-select*
{:name (fc/field-name)
:account-location (:account/location account-id)
:client-locations (pull-attr (dc/db conn) :client/locations client-id)
:value location-val}))])
(fc/with-field :transaction-account/amount
(com/hidden {:name (fc/field-name)
:value total}))]]))
@@ -285,7 +288,9 @@
:x-model "accountId"}))))
(fc/with-field :transaction-account/location
(com/data-grid-cell
{}
{:id (str "account-location-" index)}
;; Selecting an account only affects this row's valid Location options, so the
;; change swaps just this cell -- nothing else needs to re-render.
(com/validated-field
{:errors (fc/field-errors)
:x-hx-val:account-id "accountId"
@@ -294,8 +299,8 @@
:x-dispatch:changed "accountId"
:hx-trigger "changed"
:hx-post (bidi/path-for ssr-routes/only-routes ::route/edit-form-changed)
:hx-target "#wizard-form"
:hx-select "#wizard-form"
:hx-target (str "#account-location-" index)
:hx-select (str "#account-location-" index)
:hx-swap "outerHTML"
:hx-include "closest form"}
(location-select* {:name (fc/field-name)
@@ -874,19 +879,14 @@
{:label "Memo"
:errors (fc/field-errors)}
[:div.w-96
;; Memo affects nothing else, so it issues no request at all -- its
;; value just rides along in the form (posted with the next dependent
;; change, and merged into the snapshot on save).
(com/text-input {:value (-> (fc/field-value))
:name (fc/field-name)
:id "edit-memo"
:error? (fc/field-errors)
:placeholder "Optional note"
:hx-post (bidi/path-for ssr-routes/only-routes ::route/edit-form-changed)
;; Memo has no dependent UI, so it posts only to keep the
;; server snapshot in sync and swaps nothing back in. With
;; hx-swap=none the input is never touched, so the caret
;; stays put with no morph.
:hx-swap "none"
:hx-trigger "keyup changed delay:300ms"
:hx-include "closest form"})]))
:placeholder "Optional note"})]))
[:div {:x-data (hx/json {:activeForm (if (:transaction/payment (:entity request))
"link-payment"
(or (fc/with-field :action (fc/field-value))