refactor(ssr): de-fake simple-mode account cursor via explicit render (heuristic 1)
simple-mode-fields* rendered its single account row by rebinding the form cursor to a synthetic MapCursor rooted at accounts[0] (faking a deep starting position). Replace that with explicit-data rendering: account-field-name builds the exact field names the cursor would produce at [:step-params :transaction/accounts 0 field] (via path->name2), and account-field-errors reads errors from the same path -- no re-rooted cursor. This is the render-fn rewrite the earlier with-field-default shortcut couldn't be (that mutated the cursor and broke the simple-mode swap). Scorecard: faked cursor roots 2 -> 0 (both heuristic-1 items now clear for this modal). Parity held: swap spec 6/6 (its vendor tests run in simple mode), Shared Location save green, full suite 31 pass / no regression.
This commit is contained in:
@@ -39,14 +39,15 @@ Each migration appends one row (after-numbers), referencing the before in the di
|
|||||||
| Phase | Modal | LOC | Routes | no-cursor twins | faked roots | snapshot merges | OOB | mixed hx- | cookbook reused / added |
|
| Phase | Modal | LOC | Routes | no-cursor twins | faked roots | snapshot merges | OOB | mixed hx- | cookbook reused / added |
|
||||||
|-------|-------|-----|--------|-----------------|-------------|-----------------|-----|-----------|-------------------------|
|
|-------|-------|-----|--------|-----------------|-------------|-----------------|-----|-----------|-------------------------|
|
||||||
| 1 (baseline) | Transaction Edit `transaction/edit.clj` | 1608 | ~12 | 1 | 2 | ~75 | 0 | 8 | — / seeded 7 entries |
|
| 1 (baseline) | Transaction Edit `transaction/edit.clj` | 1608 | ~12 | 1 | 2 | ~75 | 0 | 8 | — / seeded 7 entries |
|
||||||
| 2 (in progress) | Transaction Edit `transaction/edit.clj` | 1555 | ~12 | **0** | 2 | ~75 | 0 | 8 | — / 0 |
|
| 2 (in progress) | Transaction Edit `transaction/edit.clj` | 1570 | ~12 | **0** | **0** | ~75 | 0 | 8 | — / 0 |
|
||||||
|
|
||||||
> **Phase 2 progress (partial).** Achieved with parity held (swap spec 6/6 + Shared
|
> **Phase 2 progress (partial).** Achieved with parity held (swap spec 6/6 + Shared
|
||||||
> Location green): deleted the dead `*-no-cursor*` twin (no-cursor 1→0, −53 LOC) and fixed
|
> Location green, full suite 31 pass / no regression): deleted the dead `*-no-cursor*`
|
||||||
> a real production bug (`:mode` leaking into the upsert → 500 on every advanced manual
|
> twin (no-cursor 1→0), **de-faked the simple-mode cursor** (faked roots 2→0) by rendering
|
||||||
> save). **Still open** for this modal — and intentionally *not* forced under parity risk:
|
> the row from explicit data with explicit field names (`account-field-name`) + explicit
|
||||||
> faked cursor roots (2 — de-faking needs the render-fn rewrite, see `gotchas.md`), the
|
> error lookup — the render-fn rewrite the `with-field-default` shortcut couldn't do — and
|
||||||
> snapshot round-trip (~75 — removed by the wizard→plain-form reclassification), Selmer
|
> fixed a real production bug (`:mode` leaking into the upsert → 500 on every advanced
|
||||||
> conversion of the render fns, and route collapse (~12 → ~3). These are the bulk of the
|
> manual save). **Still open** for this modal: the snapshot round-trip (~75 — removed by
|
||||||
> modal migration and require restructuring the modal's rendering wholesale rather than
|
> the wizard→plain-form reclassification), Selmer conversion of the render fns, and route
|
||||||
> isolated edits; track as the continuation of Phase 2.
|
> collapse (~12 → ~3). These remain the bulk of the migration and need wholesale
|
||||||
|
> restructuring of the modal's rendering; track as the continuation of Phase 2.
|
||||||
|
|||||||
@@ -183,10 +183,32 @@
|
|||||||
(:account/name (d-accounts/clientize (dc/pull (dc/db conn) d-accounts/default-read value)
|
(:account/name (d-accounts/clientize (dc/pull (dc/db conn) d-accounts/default-read value)
|
||||||
client-id)))})])
|
client-id)))})])
|
||||||
|
|
||||||
|
(defn- account-field-name
|
||||||
|
"Explicit form-field name for account row `index`, field `field` -- the same string
|
||||||
|
the form cursor produces at path [:step-params :transaction/accounts index field]
|
||||||
|
(via path->name2), without faking a deep cursor to get there."
|
||||||
|
[index field]
|
||||||
|
(str "step-params[transaction/accounts][" index "]["
|
||||||
|
(if (keyword? field)
|
||||||
|
(str (when (namespace field) (str (namespace field) "/")) (name field))
|
||||||
|
field)
|
||||||
|
"]"))
|
||||||
|
|
||||||
|
(defn- account-field-errors
|
||||||
|
"Errors for account row `index`, field `field`, read straight from the form errors
|
||||||
|
at the same path the cursor would walk -- avoids re-rooting a cursor to look them up."
|
||||||
|
[index field]
|
||||||
|
(when (bound? #'fc/*form-errors*)
|
||||||
|
(get-in fc/*form-errors* [:step-params :transaction/accounts index field])))
|
||||||
|
|
||||||
(defn simple-mode-fields*
|
(defn simple-mode-fields*
|
||||||
"Renders the simple-mode account + location row and the toggle-to-advanced link.
|
"Renders the simple-mode account + location row and the toggle-to-advanced link.
|
||||||
Must be called within a fc/start-form + fc/with-field :step-params context.
|
Must be called within a fc/start-form + fc/with-field :step-params context.
|
||||||
Caller must establish Alpine x-data with simpleAccountId in scope."
|
Caller must establish Alpine x-data with simpleAccountId in scope.
|
||||||
|
|
||||||
|
The single account row is rendered from explicit data with explicit field names
|
||||||
|
(account-field-name 0 ...) rather than faking a synthetic MapCursor rooted at
|
||||||
|
accounts[0] -- the row always lives at index 0 in simple mode."
|
||||||
[request]
|
[request]
|
||||||
(let [snapshot (-> request :multi-form-state :snapshot)
|
(let [snapshot (-> request :multi-form-state :snapshot)
|
||||||
step-params (-> request :multi-form-state :step-params)
|
step-params (-> request :multi-form-state :step-params)
|
||||||
@@ -204,34 +226,26 @@
|
|||||||
(:transaction/amount snapshot)
|
(:transaction/amount snapshot)
|
||||||
0.0))]
|
0.0))]
|
||||||
[:div
|
[:div
|
||||||
(fc/with-field :transaction/accounts
|
|
||||||
(fc/with-cursor (let [cur fc/*current*]
|
|
||||||
(if (sequential? @cur)
|
|
||||||
(nth cur 0 nil)
|
|
||||||
(auto_ap.cursor.MapCursor. {} (cursor/state cur) (conj (cursor/path cur) 0))))
|
|
||||||
[:span
|
[:span
|
||||||
(fc/with-field :db/id
|
(com/hidden {:name (account-field-name 0 :db/id)
|
||||||
(com/hidden {:name (fc/field-name)
|
:value row-id})
|
||||||
:value row-id}))
|
|
||||||
[:div.flex.gap-2.mt-2
|
[:div.flex.gap-2.mt-2
|
||||||
(fc/with-field :transaction-account/account
|
|
||||||
(com/validated-field
|
(com/validated-field
|
||||||
{:label "Account"
|
{:label "Account"
|
||||||
:errors (fc/field-errors)}
|
:errors (account-field-errors 0 :transaction-account/account)}
|
||||||
[:div.w-72
|
[:div.w-72
|
||||||
(account-typeahead* {:value account-val
|
(account-typeahead* {:value account-val
|
||||||
:client-id client-id
|
:client-id client-id
|
||||||
:name (fc/field-name)
|
:name (account-field-name 0 :transaction-account/account)
|
||||||
:x-model "simpleAccountId"})]))
|
:x-model "simpleAccountId"})])
|
||||||
(fc/with-field :transaction-account/location
|
|
||||||
;; Selecting the account only affects the valid Location options, so the
|
;; Selecting the account only affects the valid Location options, so the
|
||||||
;; change swaps just this cell -- nothing else needs to re-render.
|
;; change swaps just this cell -- nothing else needs to re-render.
|
||||||
[:div {:id "simple-account-location"}
|
[:div {:id "simple-account-location"}
|
||||||
(com/validated-field
|
(com/validated-field
|
||||||
{:label "Location"
|
{:label "Location"
|
||||||
:errors (fc/field-errors)
|
:errors (account-field-errors 0 :transaction-account/location)
|
||||||
:x-hx-val:account-id "simpleAccountId"
|
:x-hx-val:account-id "simpleAccountId"
|
||||||
:hx-vals (hx/json (cond-> {:name (fc/field-name)}
|
:hx-vals (hx/json (cond-> {:name (account-field-name 0 :transaction-account/location)}
|
||||||
client-id (assoc :client-id client-id)))
|
client-id (assoc :client-id client-id)))
|
||||||
:x-dispatch:changed "simpleAccountId"
|
:x-dispatch:changed "simpleAccountId"
|
||||||
:hx-trigger "changed"
|
:hx-trigger "changed"
|
||||||
@@ -241,13 +255,12 @@
|
|||||||
:hx-swap "outerHTML"
|
:hx-swap "outerHTML"
|
||||||
:hx-include "closest form"}
|
:hx-include "closest form"}
|
||||||
(location-select*
|
(location-select*
|
||||||
{:name (fc/field-name)
|
{:name (account-field-name 0 :transaction-account/location)
|
||||||
:account-location (:account/location account-id)
|
:account-location (:account/location account-id)
|
||||||
:client-locations (pull-attr (dc/db conn) :client/locations client-id)
|
:client-locations (pull-attr (dc/db conn) :client/locations client-id)
|
||||||
:value location-val}))])
|
:value location-val}))]
|
||||||
(fc/with-field :transaction-account/amount
|
(com/hidden {:name (account-field-name 0 :transaction-account/amount)
|
||||||
(com/hidden {:name (fc/field-name)
|
:value total})]]
|
||||||
:value total}))]]))
|
|
||||||
[:div.mt-1
|
[:div.mt-1
|
||||||
[:a.text-sm.text-blue-600.hover:underline.cursor-pointer
|
[: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-wizard-toggle-mode)
|
||||||
|
|||||||
Reference in New Issue
Block a user