# Gotchas GROWS every migration. One entry per surprise. Also the home for any **written exception** to the scorecard ratchet (a metric that regressed for a documented reason). --- ## Stale `$refs` / `tippy` after a swap A whole-form swap can run an Alpine event handler *before* the component re-initialises, so a handler that dereferences `$refs.input.__x_tippy` or calls `tippy.show()` throws. **Always null-guard:** `$refs.input?.__x_tippy?.hide()`, `tippy?.show()`. The `transaction-edit-swap.spec.ts` `trackErrors()` helper fails the test on any `pageerror` or `console.error`, which is exactly how a stale-ref throw surfaces. ## Let the server value win — don't preserve Alpine state across a server-driven change When a server change should update a component (e.g. choosing a vendor sets its default account), rebuild that section fresh on the swap so the server-provided value lands without keying tricks. The bug this prevents: "changing the vendor a *second* time doesn't update the account" because preserved Alpine state shadowed the new server value. If you *must* preserve a component, key it by value so a change forces re-init: `(assoc attrs :key (str id "--" current-value))`. ## Focus dies if the typed input is inside its own swapped region The single most important invariant. Amount field → swap a sibling tbody, not the row. Memo → swap nothing. If a caret test (`sameNode`) fails, the input is in its own swap region — re-target to a sibling/ancestor that excludes it. ## Faked cursors breed duplicate render fns A `with-cursor`/`MapCursor` re-root to fake a deep start forces a `*-no-cursor*` twin. Removing the fake lets you delete the twin. Don't "fix" a faked cursor in place — top-root it and collapse to one render fn. (See `render-functions.md`.) ## Edit Clojure with clojure-mcp tools, not the file editor `clojure_edit` / `clojure_edit_replace_sexp`. If a file won't compile: `clj-paren-repair` the file, then retry; if still broken, `lein cljfmt check`. Run tests via `clojure-eval` / `clj-nrepl-eval -p PORT`, never `lein test` (slow, last resort). ## Solr/typeahead in tests Account/vendor search is backed by Solr, unavailable in tests. To drive a typeahead in e2e: type under the 3-char threshold, then inject a result into Alpine state (`Alpine.$data(el).elements = [{value, label}]`) and click it — the real click handler, `tippy.hide()`, Alpine reactivity, and the HTMX swap all run as in production. Entity ids come from `GET /test-info`. --- ## Scorecard exceptions (ratchet violations with a reason) _None yet._ Append here if a migration must let a metric regress for a documented reason.