Move the remaining static markup out of the bulk-code form view-model and into
the templates, leaving form-ctx as plain data (plus a urls map and two button
contexts). The form/vendor hx-wiring, the status <option> list, the per-row
transition / location-swap / remove wiring, and the field names are now literal
in the templates, built from the row index and the shared urls.
- form.html: form attrs literal; ids render name="ids[N]" via forloop.counter0.
- body.html: vendor-changed wiring literal; status is an inline <select> with
literal options (selected via {% if status.value = ... %}); field wrappers use
{% if has_error %}has-error.
- account-row.html: the <tr> transitions, db/id hidden, location-cell swap and
remove <a> are literal with {{ row.index }} / {{ urls.changed }}; only the
Alpine x-data, errors, and the typeahead/location/money control contexts are
passed as data.
- form-ctx / account-row-vm reduced to data; drop the now-unused
sc/validated-field-classes.
Tradeoff: the status <select> and the remove <a> inline the shared base classes
(those partials can't take literal option labels / per-row wiring), so those two
class strings are duplicated in the bulk-code templates.
Verified: moved wiring correct by targeted checks (ids[N], form/vendor hx-*,
account-row-N, location swap + remove with index, status selected, no unrendered
tags); full browser flow green -- open (3 ids), vendor auto-populate, status
set+persist, add/remove row, submit "Transactions Coded", no JS errors. Shared
component class-sets unchanged (this commit only touches bulk-code).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Bake the Tailwind class base into the shared Selmer component partials so the
partials own their markup and callers pass only data + a small variant
(width / size / color). Applies across all four modals that share them
(bulk-code, invoices, sales-summaries, transaction-edit).
- typeahead / select / location-select / money-input / validated-field /
button / a-button / a-icon-button: the class base, the validated-field
has-error toggle, and the button color ladders now live in the .html. The
sc/*-ctx fns pass width / variant / extra / color plus the non-class attrs
(computed exactly as before, so every non-class attribute is unchanged).
- bulk-code templates updated to the new partial contracts; account-row pulls
money-input and a-icon-button in via includes.
Verified: every component's class SET is identical to before across all
variants (14/14 oracle match -- buttons reorder/dedupe classes, CSS is
order-independent); bulk-code full render is DOM-equivalent to the pre-sweep
baseline (class-set + attr-order normalized) for empty / populated / error;
browser QA of bulk-code (full flow) and transaction-edit (open + render) clean,
no JS errors; invoices + sales-summaries compile and render through the same
sc/* fns.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Each bulk-code route now ends in a single sel/render call; all composition
(modal chrome, body, account grid, rows, footer, errors) happens in the
templates via {% extends %}/{% block %}/{% include %}/{% with %}, reading one
nested view-model (form-ctx). No HTML is stitched together in Clojure.
- Add components/modal-card.html: a base chrome with head/body/footer blocks;
bulk-code/card.html extends it. (Transaction Edit keeps its string-slot
edit-modal.html for now.)
- New top-level templates: open.html, form.html, card.html, body.html; rework
account-grid/account-row/footer/head to pull the shared component partials in
via {% include %}+{% with %} instead of hardcoding class strings or receiving
pre-rendered HTML strings.
- render-form / open-handler collapse to one sel/render of form.html / open.html.
bulk-code-body*, footer*, form-errors-html, account-grid*, the *errors* dynamic
var and ferr are gone; field errors are read straight from :form-errors.
- Extract sc/{select,button,a-button,a-icon-button}-ctx so templates can include
those partials with computed context (the render wrappers now call the -ctx fns).
Verified: rendered output is DOM-identical to the prior version across empty /
populated / error scenarios (whitespace-normalized token compare), plus browser
QA (open, vendor auto-populate, add/remove row, typeahead, per-row location swap,
percentage validation, submit); no JS errors.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Move all markup in the Transaction Bulk Code modal out of Clojure and into
Selmer templates so bulk_code.clj only assembles data.
- Replace the inline sel/raw HTML strings and one Hiccup [:p] with templates:
head, form-errors, footer, account-entries, success-body.
- Render the expense-account grid from a {% for %} template (account-grid.html
+ account-row.html) driven by a per-row view-model (account-row-vm); the row
reuses the shared components/typeahead.html via a {% with %} include (no fork).
- Extract behaviour-preserving data-prep helpers reused by the view-model:
sc/typeahead-ctx, sc/money-input-attrs, sc/validated-field-classes,
sc/errors-str, edit/account-typeahead-ctx, edit/location-select-ctx.
Verified: REPL render parity + browser QA (add/remove row, typeahead select,
per-row location swap, percentage validation, submit, vendor auto-populate);
no JS errors.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Many templates were minified onto a single line. Reformatted every template under
resources/templates/ with djLint (django profile, 2-space indent) so the markup,
{% %} blocks, and {{ }} interpolations are human-readable, plus hand-split the two
multi-<span> option partials (invoice-option / rule-option). Pure reflow — no markup,
tag, or text content changed (the only content-adjacent delta is harmless trailing
whitespace inside single-interpolation elements). link.html / panel-empty.html stay on
one line (single element). resources/public/index.html left as-is (already readable,
non-template static).
Full e2e suite 72/72 green (no rendering regressions).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Migrates the Invoice Bulk Edit modal off the wizard to a plain Selmer form,
building on the parity gate. Structurally Phase 3's bulk-code applied to invoices
(selected entities -> expense-account rows), so near-pure reuse of bulk-code's
flat-state plumbing + edit's account-totals-tbody.
What changed
- Wizard removed: deleted BulkEditWizard/AccountsStep records, MultiStepFormState,
the step-params[...] prefix, the EDN snapshot, and all mm/* for this modal.
Replaced with a plain handler + flat wrap-bulk-state (decode straight into
bulk-edit-schema, no snapshot).
- Selection-as-ids round-trip: the non-editable invoice 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 filter re-query.
- De-cursored bulk-edit-account-row* to Selmer (sc/*), explicit-id location swap
(#account-location-<index>, replacing the old find * swap), reusing
tx-edit/location-select*.
- 100% Selmer modal render path; the surgical edit was done with the text-based
Edit tool (the clojure-mcp structural tools reformat the whole 1812-line file),
so the diff is contained to the requires + the bulk-edit region.
- Routes 5 -> 3: GET bulk-edit, PUT bulk-edit-submit, POST bulk-edit-form-changed
(one whole-form op dispatcher folding the old new-account route).
Implemented the dead totals
- The wizard's TOTAL/BALANCE percentage rows were commented out (#_(...)) with a
duplicate id="total". Implemented as a #expense-totals sibling-<tbody> refreshed by
a Rule-4 percentage-keyup swap (the new-account + total + balance routes all fold
into form-changed / the sibling-tbody).
Scorecard delta (bulk-edit modal): wizard records 2->0, bulk-edit routes 5->3,
step-params/fc-cursor (modal) ->0, location swap find *-> explicit-id, totals
dead->implemented.
Verification: invoice-bulk-edit spec 5/5 (incl. add-row, save, validation, the
implemented totals); full Playwright suite 50/50; cljfmt clean; diff confined to
the modal region. Skill fed: scorecard row + settled repeated-row target-selector
convention; gotcha (structural tools reformat large files -> use text Edit).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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>
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>
Squashed Phase-2 SSR work: migrate the Transaction Edit modal's render path
entirely to Selmer templates (zero Hiccup in the render path), rip out the
multi-step wizard abstraction (EditWizard/LinksStep records, MultiStepFormState,
step-params[...] field names, mm/* middleware) in favor of a plain form with
flat derived state, and promote shared UI components to reusable Selmer partials
under resources/templates/components/. Adds the Selmer interop bridge, the
auto-ap.ssr.components.selmer (sc) wrapper library, and the ssr-form-migration
skill capturing the learnings.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>