refactor(ssr): Phase 7 — migrate Invoice Pay onto the engine; prove the cross-step merge
Invoice Pay is the first GENUINE multi-data-step wizard, and migrating it exercises
the engine's central abstraction for the first time: choose-method collects
{:bank-account :method}, payment-details collects {:invoices :check-number
:handwritten-date :mode}, and the engine's get-all MERGES the two independent step
payloads for the per-method pay (handwrite-check transacts a pending check; the
others go through print-checks-internal). This is exactly the mechanism the Phase-6
adversarial review flagged as unproven.
What changed
- Deleted the 3 wizard records (PayWizard / ChoosePaymentMethodModal /
PaymentDetailsStep), MultiStepFormState, the EDN snapshot, and the step-params[...]
prefix. Replaced with pay-wizard-config (init-fn builds read-only :context;
two steps; done-fn = pay!) driven by wizard2.
- De-cursored the payment-details amounts grid (fc/cursor-map -> explicit
(map-indexed) over :context :invoices with path->name2 names).
- The bank-account cards' method controls now post {bank-account, method,
direction:next} straight to the engine submit-route (was a bespoke navigate route).
- Routes 3 -> 2: open-pay-wizard (GET), pay-step (every transition); the
pay-wizard-navigate route is deleted.
- Used the post-review engine primitives: :open-response (modal wrap), nav-footer
(with new :save-label "Pay"), auto nav-field stripping (flat decode, no allowlist),
Enter guard.
invoices.clj falls fully off the framework: Invoice Pay was the last mm/fc user
(bulk-edit went in Phase 5), so fc/ 0, mm/ 0, defrecord 0, step-params 0 — and the
multi-modal / form-cursor / malli.util requires are removed.
Gotcha discovered + documented: wizard session data must be EDN-safe (the cookie
session store has no clj-time readers), so the date default is computed in render,
not stored in context.
Verification: invoice-pay spec 3/3 (the merge end-to-end); full suite 58/58; load-file
clean; cljfmt clean. Skill fed: scorecard row (merge proven; whole-file zeroing) +
the EDN-session-safety gotcha.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -309,3 +309,27 @@ the mm-coupling / snapshot / route counts are.
|
||||
They are terminal responses (shown after the form closes), reuse a shared dialog component,
|
||||
and sit outside the form's interactive render path. Migrating them means porting the shared
|
||||
`success-modal` to Selmer — a Phase 11 cross-cutting task, not a single-modal one.
|
||||
|
||||
## Keep wizard session data EDN-safe (the cookie store has no custom readers)
|
||||
|
||||
The session-backed engine stores per-step data + context in the Ring session, and this app's
|
||||
session store is a **cookie-store** (`ring.middleware.session.cookie`) that serializes with
|
||||
`pr-str` and reads back with plain `clojure.edn/read-string` — **no custom tag readers**. So
|
||||
anything you put in a wizard's `:context` or that a step `:decode` returns (which `put-step`
|
||||
persists) must round-trip through bare EDN. A `clj-time` `DateTime` does not: it `pr-str`s as
|
||||
`#clj-time/date-time "…"` and the read side 500s with **"No reader function for tag
|
||||
clj-time/date-time"** on the *next* request that reads the cookie.
|
||||
|
||||
This first bit Invoice Pay (Phase 7), whose context defaulted `:handwritten-date (time/now)`.
|
||||
Rules of thumb:
|
||||
- **Context**: store only EDN-safe primitives (numbers, strings, keywords, vectors, maps,
|
||||
`#inst`/`java.util.Date`). Compute clj-time defaults in the *render* fn, not in context.
|
||||
- **Step data**: a `clj-time` value decoded by a step is fine *in memory* on the terminal
|
||||
(`:done`) path — `get-all` reads it before `forget` clears the wizard, so it never reaches
|
||||
the cookie. It only bites if a clj-time value survives in a step that gets re-persisted
|
||||
(a non-terminal `put-step`). When in doubt, decode dates to `#inst` or keep them as strings
|
||||
until the done-fn.
|
||||
- The old `mm` wizard dodged this because it read its EDN snapshot with
|
||||
`clojure.edn/read-string {:readers clj-time.coerce/data-readers}` (see `multi_modal.clj`) —
|
||||
the cookie store has no such readers. (A durable/typed session backend would remove this
|
||||
constraint; until then, EDN-safe is the rule. See `form-vs-wizard.md` open question.)
|
||||
|
||||
Reference in New Issue
Block a user