docs(skill): feed ssr-form-migration with Phase 10 (Client) learnings
- form-vs-wizard.md: the sub-editor pattern — modeling a parameterized sub-step (list ⇄ per-item editor with accept/discard/sort) on the linear engine as whole-form swaps driven by routes that mutate session step-data, with a pass-through step :decode that re-reads the list via a non-stripped `wiz` hidden. - scorecard.md: Phase 10 row (defrecord 9→0, multimethods→case, grid+schemas+ power-query preserved verbatim, blank-address recurrence, 71/71 green). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -229,3 +229,36 @@ accounts totals all post the whole `#wizard-form`; in the engine that form carri
|
||||
`invoice/*` fields + the opaque `wizard-id`, so a fragment decodes what it needs straight from
|
||||
`form-params` (and, for a cross-step value like the invoice total on the accounts step, reads
|
||||
`ws/get-all` via the posted `wizard-id`). No `mm/wrap-decode-multi-form-state` stack survives.
|
||||
|
||||
## Sub-editor: a parameterized sub-step on the linear engine (Phase 10, bank accounts)
|
||||
|
||||
The engine's steps are a flat list — it has no nested/parameterized step like the old
|
||||
mm `[:bank-account which]`. When a step owns a *collection you edit one item at a time*
|
||||
(a list view ⇄ a per-item editor, with accept/discard/sort), don't try to bend the step
|
||||
list. Model it as a **sub-editor of that step**, entirely in whole-form swaps:
|
||||
|
||||
- **The step renders the list view** (cards/rows + an "add" affordance). Each item's
|
||||
edit/new control is an `hx-get` that targets `#wizard-form` with `hx-swap outerHTML` and
|
||||
carries `?wizard-id=<id>&index=N` (the wizard-id is in the render ctx).
|
||||
- **The editor is its own `<form id="wizard-form">`** (so it swaps cleanly and the next
|
||||
swap replaces it) with the item's fields + hidden `wizard-id` + a hidden item index. Its
|
||||
Accept `hx-post`s an accept route; Discard `hx-get`s a discard route. It is NOT a wizard
|
||||
step and does NOT go through `handle-step-submit`.
|
||||
- **Dedicated routes mutate the step's data in the session directly** and re-render the
|
||||
list via the engine: read `(ws/step-data session wid <step-key>)`, splice the decoded
|
||||
item into the vector (`assoc` at index, or `conj` to append for new), `ws/put-step`, then
|
||||
`(wizard2/render-wizard {:config … :wizard-id wid :session session' :request request})`
|
||||
and `(assoc :session session')`. Discard just re-renders from the unchanged session.
|
||||
- **The step's own `:decode` is a pass-through.** Because the list lives in the session
|
||||
(managed by the sub-editor, not by in-form inputs), the step's Next must re-affirm it,
|
||||
not decode it from a near-empty form. Read it back with the wizard-id — but the engine
|
||||
strips `wizard-id`/`current-step`/`direction` from form-params before `:decode`, so smuggle
|
||||
it through an extra hidden the engine leaves alone (we used `wiz`):
|
||||
`(or (ws/step-data (:session request) (get-in request [:form-params "wiz"]) <step-key>) {…})`.
|
||||
- Give the step a no-op `:validate` (`(fn [_ _] nil)`) — items are validated on Accept.
|
||||
- Clean control keys out of the decoded item before storage (`select-keys` to `:db/id` +
|
||||
the entity's own namespace) so `wizard-id`/index/`:new?` never reach datomic.
|
||||
|
||||
This keeps the doctrine intact (every byte is a whole-form swap of `#wizard-form`; no EDN
|
||||
snapshot rides the page) while giving the linear engine an add/edit/sort sub-flow it has
|
||||
no native concept for.
|
||||
|
||||
Reference in New Issue
Block a user