# Quality scorecard (the ratchet) Cheap to measure (`grep -c`, `wc -l`, `clj-kondo`), recorded **before/after each migration** in the commit message and in the results table below. **No metric may regress for the touched modal** without a written exception in `gotchas.md`. These are directional evidence, not targets to game — always paired with the e2e parity gate. ## Heuristics | # | Heuristic | Measure | Target | |---|-----------|---------|--------| | 1 | Faked cursor positions (not cursors themselves) | `grep -cE 'with-cursor\|MapCursor\.'` re-roots + `grep -c 'defn.*-no-cursor'` | → 0 (top-rooted cursors are fine) | | 2 | Implicit state merges (snapshot/cursor) | count merge sites | → 0 (forms); explicit `put-step` only (wizards) | | 3 | Branching complexity | `clj-kondo`, or count `cond`/`condp`/`case`/nested `if` + max depth | net ↓ | | 4 | Lines of code | `wc -l` on the modal's file(s) | net ↓ | | 5 | Reuse / cross-form similarity | cookbook components reused; duplicated-block count | reuse ↑, dup ↓ | | 6 | Route count | count routes for the modal | → 2 (+1 for add-row) | | 7 | OOB swaps | `grep -c hx-swap-oob` | → 0 unless a justified disjoint-region case is documented | | 8 | Attribute consistency | mixed `:x-`/`"x-"` encodings in migrated template | → 0 | ## How to measure (copy/paste) ```bash F=src/clj/auto_ap/ssr/.clj echo "LOC $(wc -l < $F)" echo "no-cursor twins $(grep -c 'defn.*-no-cursor' $F)" echo "faked-cursor roots $(grep -cE 'with-cursor|MapCursor\.' $F)" echo "snapshot merges $(grep -c ':multi-form-state :snapshot' $F)" echo "branch forms $(grep -cE '\(cond |\(condp |\(case |\(when-not ' $F)" echo "hx-swap-oob $(grep -c 'hx-swap-oob' $F)" echo "mixed string hx- $(grep -cE '\"hx-[a-z]' $F)" # route count: count this modal's entries in src/cljc/auto_ap/routes/*.cljc ``` ## Results Each migration appends one row (after-numbers), referencing the before in the diff. | 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 | | 2 | Transaction Edit `transaction/edit.clj` | 1593 | **~5** | **0** | **0** | **0 round-trip** | 0 | 8 (shared) | location-select / **1 Selmer** | | 2-final | Transaction Edit (full Selmer + wizard removed) | 1548 | **5** | 0 | 0 | 0 | 0 | **0** | full `sc/*` lib / **~30 partials** | ### New heuristics introduced at 2-final (full Selmer) | # | Heuristic | Measure | Target | |---|-----------|---------|--------| | 9 | Hiccup HTML tags in the render path | `grep -cE '\[:(div\|span\|p\|a\|button\|input\|h[1-6]\|ul\|li\|label\|select\|option\|t(able\|head\|body\|r\|d\|h)\|form\|svg\|template)'` over the modal's render fns | → 0 (success-modal confirmation dialogs may keep the shared Hiccup component) | | 10 | mm wizard coupling | `grep -c 'mm/' the modal file` + `grep -c 'defrecord.*Wizard\|ModalWizardStep'` | → 0 for a single-step modal | > **Phase 2 progress.** Achieved with parity held (swap spec **6/6**, transaction-edit > spec **8/8**, full suite **38 pass / 1 unrelated fail / 0 skip**, up from 30/2/7): > - deleted the dead `*-no-cursor*` twin (no-cursor 1→0); > - **de-faked the simple-mode cursor** (faked roots 2→0) via explicit data + explicit > field names (`account-field-name`) + explicit error lookup — the render-fn rewrite the > `with-field-default` shortcut couldn't do; > - **collapsed the 5 manual-coding operation routes into one `edit-form-changed` > dispatcher** (routes ~12→~5; the operations are now pure `apply-*` fns); > - fixed a real production bug (`:mode` → 500 on every advanced manual save); > - greened `transaction-edit.spec.ts` (8/8) and matured the skill. > > **Phase 2 complete.** The wizard→plain-form rewrite removed the snapshot round-trip > (heuristic 2 → 0) and the first interactive component (`location-select`) is migrated to > a Selmer template (`selmer-conventions.md` validated). Remaining for *later phases*: drop > the now-thin `mm/ModalWizardStep` protocol wrappers, and the cross-cutting Phase 11 > Selmer sweep of the shared `com/typeahead`/`com/select`/`com/button-group-button` (those > shared call sites hold the last 8 mixed `@`/`:`-attr offenders; they clear when the > shared components move to Selmer — not a single-modal task, per Open decision 2). > **Phase 2-final — full Selmer + wizard removed.** Every component the modal renders > through was ported to a Selmer partial under `resources/templates/components/` with a > thin Clojure wrapper in `auto-ap.ssr.components.selmer` (`sc/*`); the modal's own > structure lives under `resources/templates/transaction-edit/`. The `mm` wizard > abstraction (`EditWizard`/`LinksStep` records, `MultiStepFormState`, `step-params[...]` > field names, `wrap-wizard`/`wrap-decode-multi-form-state` middleware) was deleted — there > was only ever one step, so it was pure overhead. Result: heuristic 8 (mixed hx-) and 9 > (Hiccup in render) and 10 (mm coupling) all → **0**; the `edit-wizard-navigate` route is > gone (routes 5). Parity held: swap spec **6/6**, transaction-edit spec **8/8**, full > suite **38 pass / 1 pre-existing unrelated fail** (serial, fresh seed). The only Hiccup > left in the file is the post-save `com/success-modal` confirmation dialogs (terminal, > shared component — out of the form's render path). See `form-vs-wizard.md` (drop-the- > wizard test), `selmer-conventions.md` (composition mechanics), and `gotchas.md` > (stray-field decode leak; jetty reload staleness).