Files
integreat/.claude/skills/ssr-form-migration/reference/scorecard.md
Bryce 03620e9d42 refactor(ssr): Phase 3 — full Selmer migration of Transaction Bulk Code; remove the wizard
Migrates the Transaction Bulk Code modal (a single-step form wearing a full
wizard costume) to a plain Selmer form, cold-applying the ssr-form-migration
skill. Almost entirely reuse of the Phase-2 work: the whole `sc/*` Selmer
component library, `account-typeahead*` / `location-select*`, and the
`edit-modal` / `transitioner` chrome are imported wholesale.

What changed
- Wizard removed: deleted `BulkCodeWizard` / `AccountsStep` records,
  `MultiStepFormState`, the `step-params[...]` prefix, and all `mm/*`
  middleware. Replaced with a plain handler + flat `wrap-bulk-state` (decode
  straight into `bulk-code-schema`, no snapshot round-trip).
- Selection round-trip: the non-editable transaction selection is resolved to
  a concrete not-locked id vector at open and ridden back in hidden `ids[]`
  fields (the bulk analog of edit's single `db/id`) — no EDN snapshot, no
  filter re-query, and more correct (codes exactly the rows the user saw).
- 100% Selmer render path (only the shared terminal `com/success-modal` keeps
  Hiccup — heuristic-9 exception). New shared component `sc/select`
  (`location-select.html` generalized) for the status dropdown.
- Routes 4 -> 3: GET `bulk-code` (open), POST `bulk-code-submit`, POST
  `bulk-code-form-changed` (one whole-form op dispatcher folding the old
  `new-account` + `vendor-changed` routes). Location swap moved off `find *`
  onto explicit `#account-location-<index>` + `hx-select`.
- Fixed a latent correctness bug surfaced by the migration: the vendor
  typeahead needs `:id` (value-keyed `:key`) or its value-bound hidden goes
  stale across a whole-form swap and posts blank.

Scorecard delta (transaction/bulk_code.clj): mm coupling 19->0, snapshot
merges 4->0, wizard records 3->0, step-params 10->0, routes 4->3, OOB 0,
Hiccup-in-render ->0 (bar success-modal). LOC 420->506 (documented exception:
the wizard was a thin shell over mm/* defaults, so explicitness moves shared
plumbing into the file). Cookbook: reused the entire Phase-2 sc/* lib + chrome,
added sc/select.

Verification: bulk-code-transactions.spec.ts 13/13; full Playwright suite
39/39; cljfmt clean.

Skill fed: scorecard row + narrative + LOC exception; gotchas (value-bound
typeahead keying, selection-as-ids round-trip); cookbook (sc/select).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 19:38:09 -07:00

8.2 KiB
Raw Blame History

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)

F=src/clj/auto_ap/ssr/<modal>.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
3 Transaction Bulk Code transaction/bulk_code.clj 506 (was 420 — see exception) 3 0 0 0 0 0† reused all of Phase-2's sc/* lib + account-typeahead*/location-select* + edit-modal/transitioner chrome / added sc/select

† The one "hx-..." string hit is a response-header map ({"hx-trigger" "refreshTable, reset-selection"}), not a mixed attribute encoding. mm coupling 19→0, wizard records 3→0, step-params 10→0 (the 2 hits are comments), Hiccup-in-render → 0 except the shared com/success-modal (heuristic-9 exception, as in Phase 2).

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).

Phase 3 — Transaction Bulk Code (first cold apply of the mature skill). Single-step form wearing a full wizard costume (BulkCodeWizard/AccountsStep, MultiStepFormState, the step-params[...] prefix, the old find * location swap). Migrated to a plain form by mirroring Phase 2 — and it was mostly reuse: the entire sc/* Selmer component library, account-typeahead*/location-select*, and the edit-modal/transitioner chrome were imported wholesale; the only new shared component was sc/select (the status dropdown — location-select.html generalized). Parity held: bulk-code spec 13/13, full suite 39/39 (up from the Phase-2 baseline of 3839). mm coupling 19→0, snapshot merges 4→0, wizard records 3→0, routes 4→3 (open / submit / form-changed — the per-op new-account + vendor-changed routes folded into one form-changed op dispatcher), the location swap moved off find * onto explicit #account-location-<index> + hx-select.

The one regression — LOC 420→506 (documented exception, see gotchas.md). Unlike edit (whose wizard held real custom code), bulk-code's wizard was a thin shell that delegated almost everything to mm/* defaults (default-render-step, default-render-wizard, submit-handler, open-wizard-handler). Ripping the wizard out moves that previously-shared plumbing into the file as explicit render/decode/submit/handler code. The trade is intended: every other heuristic improved and the modal is now self-contained and wizard-free. New patterns added to the cookbook: the selection-as-ids[] round-trip (resolve the non-editable selection to a concrete id vector at open, ride it in hidden fields — the bulk analog of edit's single db/id), and the :id-keyed vendor typeahead (a value-bound hidden must be keyed or its posted value goes stale across a whole-form swap).