Files
integreat/.claude/skills/ssr-form-migration/reference/scorecard.md
Bryce 599b849e6f refactor(ssr): Phase 4 — full Selmer migration of POS Sales Summary; remove the wizard; fix add-item + totals
Migrates the POS Sales Summary edit modal off the wizard to a plain Selmer form,
building on the parity gate committed earlier. Largest migration so far and the
first with no prior test coverage.

What changed
- Wizard removed: deleted MainStep/EditWizard records, MultiStepFormState, the
  step-params[...] prefix, the EDN snapshot round-trip, and all mm/* middleware.
  Replaced with a plain handler + flat wrap-decode/wrap-derive-state. The 51 fc/
  cursor refs are de-cursored into explicit data + Selmer templates.
- db/id-keyed item merge: wrap-derive-state overlays posted items onto the
  persisted items by :db/id, so read-only fields the form doesn't post
  (ledger-side, amount) survive a re-render and the debit/credit split + totals
  stay correct. New manual rows (temp db/id) ride through as-is.
- Inline click-to-edit account cell preserved as three small targeted
  .account-cell-swap routes (edit/save/cancel-item-account), ported to Selmer
  with the new field-name scheme.
- 100% Selmer modal render path (the remaining Hiccup / hx-swap-oob / "hx-"
  strings are all grid-page code — grid render lambdas, the filters form, and the
  submit response-header map — not the modal).
- Routes: dropped edit-wizard-navigate + new-summary-item; added form-changed.

Fixes (two pre-existing bugs, per request)
- "New Summary Item" add button (was throwing `newRowIndex is not defined` and
  targeting a non-existent `.new-row`) is now a whole-form-swap op=new-item that
  adds an editable manual row (category + account typeahead + debit/credit money
  inputs + remove).
- The dead totals/balance display (malformed Hiccup that discarded its labels) is
  replaced by a proper #summary-totals block showing running Total +
  Balanced/Unbalanced, refreshed via a Rule-4 targeted swap on manual amount edits.

Scorecard delta (pos/sales_summaries.clj): LOC 790->732, mm coupling 20->0,
wizard records 4->0, fc/ cursor 51->0, step-params 27->0 (2 comments), modal
routes 8->6. (hx-swap-oob 1 and mixed-hx live in the grid page, not the modal.)

Verification: sales-summary spec 7/7 (incl. the two fixes); full Playwright suite
46/46; cljfmt clean. Skill fed: scorecard row + narrative; gotchas (parity-gate-
first, characterize-then-fix, keyup-trigger tests); cookbook (inline click-to-edit
cell, db/id-keyed item merge).

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

11 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). | 4 | POS Sales Summary pos/sales_summaries.clj | 732 (was 790) | 6 modal | 0 | 0 | 0 | 1✦ | 0✦ | reused sc/* lib + edit-modal/transitioner chrome / added the inline click-to-edit account-cell + manual-items patterns |

✦ The residual 1 hx-swap-oob and the "hx-..." string hits all live in the grid page code (the grid-page render lambdas + the filters form + the submit response-header map) — none are in the migrated modal render path, which is 100% Selmer. defrecord count 0 (all 4 wizard records gone), fc/ cursor refs 51→0, mm coupling 20→0, step-params 27→0 (2 comments). LOC dropped (this wizard held real custom code, unlike bulk-code's thin shell). Two pre-existing bugs fixed (per the user's call): the "New Summary Item" add button (was throwing newRowIndex is not defined) and the dead totals/balance display.

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

Phase 4 — POS Sales Summary (first modal with no prior test coverage). The largest migration so far and the first that required building the parity gate first: the modal had zero e2e/clj tests and the test server seeded no POS data, so the work began by seeding a balanced sales summary + writing a 7-test characterization spec (committed separately, ahead of the rewrite). Then the standard wizard→plain-Selmer migration: MainStep/EditWizard + MultiStepFormState deleted, the 51 fc/ cursor refs de-cursored into explicit data + Selmer, step-params dropped, the EDN snapshot replaced by flat wrap-decode/wrap-derive-state (with a db/id-keyed item merge so the read-only fields the form doesn't post — ledger-side, amount — survive a re-render). The inline click-to-edit account cell (pencil → typeahead editor → check/cancel) was preserved as three small targeted .account-cell-swap routes (a distinct feature, not folded into the form-changed dispatcher). LOC 790→732 (net ↓ — a fat wizard, opposite of bulk-code).

Characterize-then-fix. Writing the gate surfaced two pre-existing bugs: the "New Summary Item" button threw newRowIndex is not defined (dead since forever) and the totals/balance display was dead code (malformed Hiccup that discarded its labels). The spec first documented them as broken (never assert a bug as working); then, on the user's call, the migration fixed both — add-item is now a whole-form-swap op=new-item adding an editable manual row, and a proper #summary-totals block shows running Total + Balanced/Unbalanced (a Rule-4 targeted swap on manual amount edits). The spec was updated to assert the fixed behavior. New cookbook entries: the inline click-to-edit cell and the db/id-keyed item merge for partially-posted rows.