refactor(ssr): Phase 6b — migrate Transaction Rule wizard onto the session engine; de-cursor
Proves the Phase-6a wizard engine against a real 2-step modal: the Transaction Rule wizard (edit step + read-only test/preview step) now runs on wizard2 / wizard-state, fully de-cursored. What changed - Wizard machinery removed: deleted the EditModal / TestModal / TransactionRuleWizard defrecords (mm/ModalWizardStep + LinearModalWizard), MultiStepFormState, the EDN snapshot, and the step-params[...] prefix. Replaced with a data-driven `transaction-rule-wizard-config` (two steps + init-fn + done-fn) driven by the engine. - De-cursored the whole edit form (82 fc/ refs -> 0): every field reads explicit data + path->name2; errors via a bound *errors* / ferr. The account row's Alpine cross-field dispatch wiring (clientId -> accountId -> location) is preserved verbatim — only the data plumbing moved off the cursor. - The test step's :render reads :all-data (the engine's get-all), so the formtools "combine at the end" mechanism feeds the preview table. - Routes 4 -> 2: open-rule-wizard (new + edit), save-step (every transition via the engine's `direction` field). The dedicated `navigate` route is deleted. - decode-rule-form select-keys to the schema's known keys so the engine's nav fields (wizard-id/current-step/direction) don't leak into the upserted entity. Scorecard (admin/transaction_rules.clj): fc/ 82->0, mm/ 20->0, defrecords 3->0, LOC 1000->964, routes 4->2. Scope note: the de-cursored edit step keeps com/* Hiccup leaf components (not yet sc/* Selmer); the value here was removing fc/ + mm/ and proving the engine, not re-templating the conditional/Alpine-cross-field layout. Hiccup-in-render is a documented partial; the com/ -> sc/ swap is a mechanical follow-up. Verification: rule spec 4/4 (new + edit dialogs, advance-to-test preview, save); full Playwright suite 55/55; cljfmt clean. Skill fed: scorecard row + narrative (engine's first real modal; generalizes for a one-data-step wizard); gotchas (strip engine nav fields in decode, new-row temp-id, direction-button nav). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -265,6 +265,24 @@ carve-out. Verify with `load-file` (compile) + `lein cljfmt check`, not by eyeba
|
||||
diff is contained with `git diff -U0 <file> | grep '^@@'` — the hunks should cluster only where you
|
||||
edited (requires + the modal region), nothing else.
|
||||
|
||||
## Wiring a modal onto the wizard2 engine — three traps that cost a debug cycle each
|
||||
|
||||
1. **Strip the engine's nav fields in the step `:decode`.** The posted form carries
|
||||
`wizard-id` / `current-step` / `direction` alongside the real fields. If the step schema is
|
||||
an open `:map` (most are), `mc/decode` keeps them, they ride into `get-all`, and the save's
|
||||
`:upsert-entity` dies with `:db.error/not-an-entity ... :current-step`. Fix: `select-keys`
|
||||
the decode to the schema's known top-level keys (the same allowlist trick as the flat-form
|
||||
migrations). Symptom is a **500 on save**, not a validation message.
|
||||
2. **New repeated-row needs a temp `:db/id` or the step can't advance.** If the row schema
|
||||
requires `[:db/id [:or entity-id temp-id]]`, an added row with no id fails per-step
|
||||
validation, so the engine re-renders the *same* step instead of advancing — looks like "the
|
||||
Next/Test button does nothing." Give new rows `(str (java.util.UUID/randomUUID))`.
|
||||
3. **Nav is a `direction` field, and Back/Save are both submit buttons.** The footer buttons
|
||||
are plain `<button type="submit" name="direction" value="next|back|submit">`; the clicked
|
||||
one's value rides in the POST and the engine branches on it. In tests, a selector like
|
||||
`button:has-text("Save"), button[type=submit]` also matches **Back** (also a submit) and
|
||||
`.first()` clicks Back — target the button by its text/value precisely.
|
||||
|
||||
## Scorecard exceptions (ratchet violations with a reason)
|
||||
|
||||
**Heuristic 4 (LOC net ↓) — exception (Phase 3, Transaction Bulk Code: 420→506).** When the
|
||||
|
||||
@@ -161,3 +161,29 @@ Each migration appends one row (after-numbers), referencing the before in the di
|
||||
> helper. Per-row ids are generated from the row index the form already uses for field names
|
||||
> (`path->name2`), so server and markup agree by construction. Whole-form swap (Rule 3) covers
|
||||
> structural changes (add/remove row). This is now the cookbook default; see `swap-doctrine.md`.
|
||||
|
||||
> **Phase 6 — the wizard engine, and its first real modal (Transaction Rule).** The inflection
|
||||
> phase. (a) **Engine** (`6a`, committed separately): `wizard-state` + `wizard2`, the Django
|
||||
> `formtools` SessionStorage model, REPL-proven before any modal touched it. (b) **First real
|
||||
> modal** (`6b`): the Transaction Rule wizard (edit step + read-only test/preview step) migrated
|
||||
> onto the engine and **fully de-cursored** like Phases 2-5. Scorecard (`admin/transaction_rules.clj`):
|
||||
> `fc/` cursor refs **82 -> 0**, `mm/` coupling **20 -> 0**, defrecords **3 -> 0** (EditModal /
|
||||
> TestModal / TransactionRuleWizard all gone), LOC 1000 -> 964, the 4 wizard routes
|
||||
> (open/navigate/save + per-dialog) collapse to **2** (`open-rule-wizard` for new+edit,
|
||||
> `save-step` for every transition). Parity held: rule spec **4/4**, full suite **55/55**.
|
||||
>
|
||||
> **The engine generalizes even for a one-data-step "wizard".** Transaction Rule is *edit + a
|
||||
> read-only preview of the same entity*, not two independent data steps — so it exercises the
|
||||
> engine's render / navigation / `:all-data`-preview path but not the cross-step *merge* (that
|
||||
> waits for Phase 7's Invoice Pay). The test step's `:render` reads `:all-data` (the engine's
|
||||
> `get-all`), which here is just the edit step's rule — so the formtools "combine at the end"
|
||||
> mechanism is exactly what feeds the preview table. Nav is the engine's `direction` field
|
||||
> (plain submit buttons `name="direction" value="next|back|submit"`), so the per-step
|
||||
> `navigate` route is deleted.
|
||||
>
|
||||
> **Note (scope):** the de-cursored edit step keeps `com/*` Hiccup leaf components rather than
|
||||
> porting to `sc/*` Selmer partials — the modal's value was removing `fc/` + `mm/` and proving
|
||||
> the engine, not re-templating its (conditional, Alpine-cross-field) layout. Hiccup-in-render
|
||||
> (heuristic 9) is therefore a documented partial here; the leaf-component `com/ -> sc/` swap is
|
||||
> a mechanical follow-up. The Alpine cross-field dispatch wiring (clientId -> accountId ->
|
||||
> location) was preserved verbatim — de-cursoring touched only the data plumbing.
|
||||
|
||||
Reference in New Issue
Block a user