# Render functions: explicit data, or a top-rooted cursor **One function, data in, markup out.** The data can arrive as a plain map *or* via a cursor — as long as the cursor was rooted at the top of the form and walked down to here, never faked to start at this depth. The rule is about *where the cursor starts*, not whether you use one. ## GOOD — explicit data, pure, testable without setup ```clojure (defn account-row [{:keys [account index client-id amount-mode]}] (com/data-grid-row (com/hidden {:name (str "accounts[" index "][db/id]") :value (or (:db/id account) "")}) (com/data-grid-cell (account-typeahead* {:value (:transaction-account/account account) :name (str "accounts[" index "][account]") :client-id client-id})) ...)) ``` ## ALSO FINE — a cursor that started at the form root and was advanced naturally ```clojure ;; The top-level render walks the cursor; the row fn receives the dereferenced row ;; (or the advanced cursor). No rebinding of *current*/*prefix* to fake depth. (defn account-rows [accounts-cursor] (for [row-cursor (fc/each accounts-cursor)] ; advanced from the root, not faked (account-row {:account @row-cursor :index (fc/index row-cursor) ...}))) ``` `transaction/edit.clj`'s `transaction-account-row*` is the cursor form done right: the caller (`account-grid-body*`) holds a top-rooted cursor via `fc/cursor-map` and hands each row cursor to one render fn. --- ## The SMELL this migration removes ### 1. Faking the cursor's starting position A "form cursor" is fine. The pain is **rebinding the dynamic root deeper in the tree** so a deeply nested render fn can run against a fragment. Real example from `transaction/edit.clj`'s `simple-mode-fields*` (the thing to delete): ```clojure ;; SMELL: re-roots the cursor to a synthetic MapCursor pointed at accounts[0] so a ;; fragment can render "deep". Fragile, and the source of the *-no-cursor* twin below. (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)))) ...)) ``` Target: the cursor begins at the top level of what the form consumes and walks down naturally. Because the **whole form is re-rendered each time** (swap doctrine), there is no longer any reason to fake a deep starting position. ### 2. The `*-no-cursor*` twin Faking the deep cursor forces a *second copy of the same markup* — one that reads the faked cursor and one that takes plain params for the cases where the fake can't be set up. `transaction/edit.clj` has exactly this pair: ```clojure (defn transaction-account-row* [{:keys [value index client-id ...]}] ...) ; cursor form (defn transaction-account-row-no-cursor* [{:keys [account index client-id ...]}] ...) ; duplicate markup ``` **Fix:** keep one render fn. If a caller already holds a top-rooted cursor, advance it and hand the row data (or the advanced cursor) to that one fn. Delete the `*-no-cursor*` copy. Heuristic 1 targets `grep -c 'defn.*-no-cursor'` → 0 and faked-cursor re-roots → 0. ## Scorecard hooks (heuristics 1, 2) ```bash grep -c 'defn.*-no-cursor' $F # → 0 grep -cE 'with-cursor|MapCursor\.' $F # faked re-roots → 0 (top-rooted cursors are fine) ``` Top-rooted cursors do **not** count against heuristic 1 — only *re-roots that fake depth* and the `*-no-cursor*` twins do.