# Component cookbook GROWS every migration. Each entry: what it is, the swap rule it uses, and the canonical snippet. Reuse these before writing anything new; the success signal is *more reuse each migration*. Seeded from `transaction/edit.clj` (Hiccup form — Selmer versions land in Phase 2). --- ## typeahead (account / vendor) — Alpine + tippy, survives swaps Used for account and vendor selection. Click-to-select (not a live text caret), so a whole-form swap on change is safe. Null-guard `tippy?`/`$refs.input?`. ```clojure (defn account-typeahead* [{:keys [name value client-id x-model]}] [:div.flex.flex-col (com/typeahead {:name name :placeholder "Search..." :url (hu/url (bidi/path-for ssr-routes/only-routes :account-search) (cond-> {:purpose "transaction"} client-id (assoc :client-id client-id))) :id name :x-model x-model ; binds selected value into the row's Alpine scope :value value :content-fn (fn [v] (:account/name (d-accounts/clientize ... v client-id)))})]) ``` Reuse note: `:x-model` lets the *parent* row read the selected id (e.g. `accountId`) to gate a targeted location swap. See account-row. ## account-row — cursor render fn + per-row targeted location swap + whole-form remove The canonical "row in a repeated grid" pattern. One render fn, top-rooted cursor. - account typeahead binds `accountId` into row Alpine scope; - **location cell** swaps *only itself* (`#account-location-`) on `changed` (swap-doctrine Rule 2); - **amount cell** swaps *only* `#account-totals` (Rule 4, sibling tbody); - **remove** swaps the whole form (Rule 3). ```clojure (defn transaction-account-row* [{:keys [value client-id amount-mode index]}] (com/data-grid-row (-> {:class "account-row" :id (str "account-row-" index) :x-data (hx/json {:show ... :accountId (fc/field-value (:transaction-account/account value))}) :data-key "show" :x-ref "p"} hx/alpine-mount-then-appear) (fc/with-field :db/id (com/hidden {:name (fc/field-name) :value (fc/field-value)})) (fc/with-field :transaction-account/account (com/data-grid-cell {} (com/validated-field {:errors (fc/field-errors)} (account-typeahead* {:value (fc/field-value) :client-id client-id :name (fc/field-name) :x-model "accountId"})))) (fc/with-field :transaction-account/location (com/data-grid-cell {:id (str "account-location-" index)} ...Rule 2 targeted swap...)) (fc/with-field :transaction-account/amount (com/data-grid-cell {} ...Rule 4 totals swap...)) (com/data-grid-cell {:class "align-top"} ...Rule 3 whole-form remove...))) ``` TODO Phase 2: drop the `transaction-account-row-no-cursor*` twin; this is the only kept form. ## totals in a sibling `` — Rule 4 instead of OOB Running totals live in their own ``, a sibling of the input-bearing rows, so an amount edit refreshes them with a plain targeted swap and never replaces the amount input (caret survives). ```clojure (com/data-grid {:footer-tbody [:tbody {:id "account-totals"} (com/data-grid-row {:class "account-total-row"} ... (account-total* request) ...) (com/data-grid-row {:class "account-balance-row"} ... (account-balance* request) ...)]} ...input rows...) ``` ## money-input / text-input amount field — Rule 4 targeted totals swap ```clojure (com/money-input {:name (fc/field-name) :id (str "account-amount-" index) :class "w-16 account-amount-field" :hx-post (bidi/path-for ssr-routes/only-routes ::route/edit-form-changed) :hx-target "#account-totals" :hx-select "#account-totals" :hx-swap "outerHTML" :hx-trigger "keyup changed delay:300ms" :hx-include "closest form"}) ``` `%` mode swaps to `com/text-input {:type "number" :step "0.01"}` with the same swap attrs. ## memo field — Rule 1, no request ```clojure (com/text-input {:value (fc/field-value) :name (fc/field-name) :id "edit-memo" :placeholder "Optional note"}) ; no hx-* — rides along to save ``` ## location-select — first Selmer-migrated component (validated) The account row's location `` with `:value="value.value"` + the `x-init` watcher | | `sc/modal` | `modal.html` | the `@click.outside="open=false"` wrapper | | SVGs | `spinner`/`svg-x`/`svg-external-link`/`svg-drop-down`.html | static, `{% include %}`d so the markup isn't duplicated | Modal-specific structure lives under `resources/templates/transaction-edit/` (`edit-form`, `edit-modal`, `links-body`, `manual-coding`, `simple-mode`, `account-totals`, `details-panel`, the four match panels, `transitioner`). The render fns in `edit.clj` gather data, call `sc/*`, and interpolate the fragments into these layout templates as `{{ frag|safe }}`. **Verify each wrapper by class-set equality + e2e, never byte-parity** (`hh/add-class` is set-based, so class order differs from the Hiccup output).