refactor(ssr): re-author form/wizard modals — whole-form swap, session wizard engine, Hiccup rendering #14

Open
notid wants to merge 35 commits from integreat-execute-refactor into staging
Owner

SSR form & wizard modal re-authoring

Re-authors every server-rendered form/wizard modal around three patterns, with no user-facing behavior change (parity gated by characterization + e2e specs). Originally this branch also moved rendering from Hiccup to Selmer templates — that step was explored and then reverted; rendering stays in Hiccup (com/*).

The three patterns kept

  1. Whole-form HTMX swap doctrine. Each interaction re-renders the whole form and swaps it (hx-target/hx-select the <form>, outerHTML), with targeted partial swaps where it matters so the caret/focus survives:
    • per-row location cell → #account-location-<index>
    • running totals → #account-totals / #expense-totals / #summary-totals (their own swappable <tbody>)
    • inline account editor → closest .account-cell
      No out-of-band swaps.
  2. Top-rooted render functions. One render fn per component taking explicit data or a top-rooted cursor; the *-no-cursor* twins and faked cursor re-roots are gone.
  3. Data-driven, session-backed wizard engine (components/wizard2 + wizard_state, the Django formtools model). Per-step state lives in the Ring session — no EDN-snapshot round-trip, no step-params[…] prefix, no per-interaction routes. The legacy mm/* multi-step machinery is deleted.

Single-step modals (Transaction Edit, Bulk Code, Invoice Bulk Edit, Sales Summary) become plain forms; genuinely multi-step ones (Transaction Rule, Invoice Pay, New/Edit Invoice, Vendor, Client) run on the engine.

Rendering: Hiccup (Selmer reverted)

An earlier iteration templated the interactive modals in Selmer. That has been reverted (commit 8b43017d): the four form modals render through the shared Hiccup components again, and the Selmer layer is removed entirely —

  • deleted auto-ap.ssr.selmer, auto-ap.ssr.components.selmer, selmer_test, the whole resources/templates/ tree (55 .html), the selmer dependency, and the tailwind resources/templates content glob;
  • added com/single-modal-card to centralize the md:w-[950px] md:h-[650px] modal chrome that had lived only in the Selmer templates;
  • stripped Selmer guidance from the ssr-form-migration skill + modernization plan.

Also included

  • Wizard step-card slide/fade animations restored in the engine.
  • Modal regression fixes: New Invoice + Transaction Edit 500s, vendor/client vertical-timeline layout, bulk-edit empty-selection 500, modal sizing.
  • New ssr-form-migration skill capturing the doctrine; characterization specs per modal; /test-reset e2e isolation.

Verification

All four reverted namespaces compile and render with no stringified-hiccup leaks; output.css rebuilds byte-identically (no Tailwind class loss); 60 e2e specs pass — the four reverted modals (incl. the whole-form-swap focus/caret tests) plus the untouched wizard / pay / new-invoice / rule modals.

## SSR form & wizard modal re-authoring Re-authors every server-rendered form/wizard modal around three patterns, with **no user-facing behavior change** (parity gated by characterization + e2e specs). Originally this branch also moved rendering from Hiccup to Selmer templates — **that step was explored and then reverted**; rendering stays in Hiccup (`com/*`). ### The three patterns kept 1. **Whole-form HTMX swap doctrine.** Each interaction re-renders the whole form and swaps it (`hx-target`/`hx-select` the `<form>`, `outerHTML`), with **targeted partial swaps** where it matters so the caret/focus survives: - per-row location cell → `#account-location-<index>` - running totals → `#account-totals` / `#expense-totals` / `#summary-totals` (their own swappable `<tbody>`) - inline account editor → `closest .account-cell` No out-of-band swaps. 2. **Top-rooted render functions.** One render fn per component taking explicit data or a top-rooted cursor; the `*-no-cursor*` twins and faked cursor re-roots are gone. 3. **Data-driven, session-backed wizard engine** (`components/wizard2` + `wizard_state`, the Django *formtools* model). Per-step state lives in the Ring session — no EDN-snapshot round-trip, no `step-params[…]` prefix, no per-interaction routes. The legacy `mm/*` multi-step machinery is deleted. Single-step modals (Transaction Edit, Bulk Code, Invoice Bulk Edit, Sales Summary) become **plain forms**; genuinely multi-step ones (Transaction Rule, Invoice Pay, New/Edit Invoice, Vendor, Client) run on the engine. ### Rendering: Hiccup (Selmer reverted) An earlier iteration templated the interactive modals in Selmer. That has been **reverted** (commit `8b43017d`): the four form modals render through the shared Hiccup components again, and the Selmer layer is removed entirely — - deleted `auto-ap.ssr.selmer`, `auto-ap.ssr.components.selmer`, `selmer_test`, the whole `resources/templates/` tree (55 `.html`), the `selmer` dependency, and the tailwind `resources/templates` content glob; - added `com/single-modal-card` to centralize the `md:w-[950px] md:h-[650px]` modal chrome that had lived only in the Selmer templates; - stripped Selmer guidance from the `ssr-form-migration` skill + modernization plan. ### Also included - Wizard step-card slide/fade animations restored in the engine. - Modal regression fixes: New Invoice + Transaction Edit 500s, vendor/client vertical-timeline layout, bulk-edit empty-selection 500, modal sizing. - New `ssr-form-migration` skill capturing the doctrine; characterization specs per modal; `/test-reset` e2e isolation. ### Verification All four reverted namespaces compile and render with no stringified-hiccup leaks; `output.css` rebuilds byte-identically (no Tailwind class loss); **60 e2e specs pass** — the four reverted modals (incl. the whole-form-swap focus/caret tests) plus the untouched wizard / pay / new-invoice / rule modals.
notid added 1 commit 2026-06-24 09:03:45 -07:00
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>
notid force-pushed integreat-execute-refactor from a01dfc197e to 70c178de83 2026-06-24 09:03:45 -07:00 Compare
notid changed title from SSR modernization: ssr-form-migration skill + Transaction Edit plain-form/Selmer migration to refactor(ssr): full Selmer migration of Transaction Edit; remove the wizard 2026-06-24 10:21:16 -07:00
notid added 1 commit 2026-06-24 20:40:22 -07:00
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>
notid changed title from refactor(ssr): full Selmer migration of Transaction Edit; remove the wizard to refactor(ssr): full Selmer migration of Transaction Edit + Bulk Code; remove the wizard 2026-06-24 20:58:48 -07:00
notid added 2 commits 2026-06-24 22:42:51 -07:00
Establishes the behavior-parity safety net required before migrating the POS
Sales Summary edit modal off the wizard (the modal had zero test coverage and the
test server seeded no POS data).

- test_server.clj: seed a balanced sales summary ($500 credit = $500 debit) with
  two auto items referencing the existing test client + accounts; surface its id
  via /test-info (`salesSummaryId`).
- e2e/sales-summary-edit.spec.ts: characterization spec (6 tests) capturing current
  behavior — open modal (debit/credit columns, categories, resolved account names,
  amounts), balanced state, inline account editor (pencil -> typeahead editor ->
  cancel restores / save re-renders the cell), and Save (PUT round-trip closes the
  modal + keeps the grid row). Exercises the edit-wizard, edit/save/cancel-item-account,
  and edit-wizard-submit routes.

Notable finding: the "New Summary Item" button is currently BROKEN (its Alpine
handler throws "newRowIndex is not defined" and hx-target="closest .new-row"
matches no ancestor, so the new-summary-item route never fires). The spec documents
this as inert rather than asserting it works; the migration will decide fix-vs-preserve.

Full Playwright suite 45/45 (39 prior + 6 new).

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>
notid added 1 commit 2026-06-24 22:54:03 -07:00
Behavior-parity safety net before migrating the Invoice Bulk Edit modal off the
wizard. The modal had no e2e coverage; the existing seeded invoice is bulk-editable
as-is, so no seed change was needed (avoids interfering with the transaction-link
spec).

e2e/invoice-bulk-edit.spec.ts (4 tests): open the modal (expense-account grid with
Account/Location/%/TOTAL/BALANCE + a default row + New account), add an account row,
save a 100% coding (modalclose), and the percentage-validation rejection. Models the
bulk-code-transactions spec.

Full Playwright suite 50/50 (46 prior + 4 new).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
notid added 1 commit 2026-06-24 23:35:57 -07:00
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>
notid added 3 commits 2026-06-25 14:16:07 -07:00
Builds the reusable multi-step wizard engine the plan front-loads in Phase 6, as
two protocol-free namespaces. This replaces the EDN-snapshot-in-a-hidden-field
round-trip for genuine multi-step flows: per-step validated data lives in the Ring
session and is combined only at the end — only an opaque wizard-id rides in the form.

- components/wizard_state.clj — pure session storage (Django formtools SessionStorage
  model): create-wizard!, instance, exists?, current-step, context, step-data,
  put-step (REPLACE not merge), set-step, get-all (combine at end), forget. State
  namespaced by wizard-id at [:wizards <id> ...]; :context holds read-only step inputs
  outside :step-data so it never merges into the result. Each fn is session -> session'.
- components/wizard2.clj — the engine: open-wizard, render-wizard, handle-step-submit,
  wizard-form. A wizard is a config map (steps with :decode/:validate/:render/:next,
  plus :init-fn/:done-fn/:submit-route). Steps' :render get {wizard-id, current-step,
  context, all-data, step-data, errors, request}; nav posts a `direction` field
  (next/back/submit). Two routes per wizard (open + submit); the engine threads the
  session into the response itself — no wrap-wizard / wrap-decode-multi-form-state stack.

REPL-proven lifecycle (before wiring any modal):
  1. OPEN     -> seeds session state, renders step 1, form leaks NO accumulated data
  2. NEXT     -> stores {:info {:name "Acme"}}, advances to :terms
  3. INVALID  -> re-renders the same step with errors, no advance
  4. DONE     -> done-fn gets combined {:name "Acme" :days 30} (get-all), instance forgotten
  5. BACK     -> :terms -> :info, no validation
  6. EXPIRED  -> unknown wizard-id re-opens fresh instead of 500-ing

Inert infrastructure — nothing imports it yet (Transaction Rule migrates onto it next),
so the e2e suite is unaffected. cljfmt clean. Skill: form-vs-wizard.md updated from
aspirational to the realized engine API + the Phase-6 fit note (Transaction Rule
exercises render/nav/preview; the cross-step merge gets its workout in Phase 7+).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Behavior-parity safety net before migrating the Transaction Rule modal onto the
session-backed wizard engine. The modal had no e2e coverage; the test server seeded
no rules.

- test_server.clj: seed a transaction rule (under client TEST2, in a SEPARATE
  transaction so the first transaction's tempid->entity-id allocation — and thus the
  TEST transaction grid order the other specs depend on — is byte-identical); surface
  its id via /test-info (ruleId).
- e2e/transaction-rule.spec.ts (4 tests): the new-rule edit step renders (description,
  account grid, approval radios, Test control), the edit dialog pre-populates the
  seeded rule, advancing to the test step renders the matching-transactions preview,
  and saving from the test step creates the rule + closes the modal. Covers both entry
  points (new/edit), both steps (edit + test), and save.

Note: deliberately NOT seeding a recent matching transaction — a date-NOW txn perturbs
an unrelated transaction-edit save spec (pre-existing fragility), and the test-table
query/render is reused unchanged by the migration, so characterizing that the preview
renders is sufficient parity.

Full Playwright suite 55/55 (51 prior + 4 new).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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>
notid added 2 commits 2026-06-25 19:36:36 -07:00
Adversarial review of Phase 6 found the engine's coupling had relocated rather than
dissolved: every wizard consumer had to hand-build a decode allowlist, re-implement the
open-handler modal wrap, mint temp ids for added rows, and hand-roll the nav buttons +
Enter guard. The engine had the information to prevent all four. Now it does:

- handle-step-submit strips its own nav fields (wizard-id/current-step/direction) from
  form-params before calling a step's :decode -- no per-consumer allowlist, and they can
  no longer leak into the saved entity (the Phase-6 "500 on save" class of bug is
  structurally impossible).
- open-wizard takes an :open-response config fn and owns the create!/render/wrap/thread
  flow, so modal wizards route through (partial wizard2/open-wizard config) directly.
- wizard2/blank-row supplies a temp :db/id (+ :new?) so an added row passes schema
  validation and the step actually advances.
- wizard2/nav-footer emits the direction buttons (Back/advance/Save), marks the primary,
  and wizard-form guards Enter to trigger the primary button.

Consumer (transaction_rules.clj) gets correspondingly leaner: deleted rule-form-keys +
the decode allowlist, rule-nav, and the hand-rolled open-rule-wizard; new/edit routes are
now (partial wizard2/open-wizard config). A new wizard is now just a config map + the step
:render fns. LOC 964 -> 932, and the deleted code was exactly the cross-consumer
boilerplate, not modal-specific logic.

Verification: rule spec 4/4; full suite 55/55; cljfmt clean. Skill gotchas updated from
"three traps" to "use the engine's primitives" (the engine now absorbs them).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The Invoice Pay wizard is the first GENUINE multi-data-step wizard: choose-method
(collects bank-account + method) -> payment-details (collects check-number /
handwritten-date / amounts), merged only at submit. This gate characterizes that
flow before migrating it onto the session-backed engine, so the merge can be proven
behavior-preserving.

- Seed: make the TEST client's check bank account visible (+ name "Test Checking")
  so the choose-method step renders a usable method card. The pay flow had no e2e
  coverage, so the bank account was never visible in tests before.
- Spec drives the real 2-step flow against the unmodified wizard: choose-method
  renders the bank account + its methods (print-check/debit/handwrite-check, in the
  card tooltip); picking handwrite-check advances to payment-details (check-number +
  date + Pay); filling the check number and submitting shows the completion modal.
  The handwrite-check path is used because it transacts a pending check payment
  directly (no PDF/S3), making the success assertion stable.

Notes for the migration: the method controls live in a <template x-ref="tooltip">
revealed by the card button; the footer Pay submit is x-ref="next"; both the grid
filters and the modal carry a check-number input, so the modal selectors are scoped
to #wizard-form.

Verification: invoice-pay spec 3/3; full suite 58/58 (no regressions from the seed
change).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
notid added 1 commit 2026-06-25 19:59:32 -07:00
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>
notid added 1 commit 2026-06-25 21:09:40 -07:00
The New Invoice wizard's basic-details step needs a client + vendor in its Solr
typeaheads; tests inject the seeded ids. accounts.vendor was already exposed; add
:clientIds (TEST / TEST2) so the upcoming Phase 8 parity gate can drive the create flow.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
notid added 1 commit 2026-06-25 22:05:19 -07:00
The hardest modal in the app: one wizard that both creates and edits invoices,
with a conditional middle step (basic-details → [accounts] → next-steps, where
the expense-accounts step is skipped on the default-accounts path). Migrated off
mm/* + form-cursor + the EDN snapshot onto the session-backed engine (wizard2).

Finding: the OLD basic-details "Save" was broken. It hx-puts /invoice/new/navigate,
whose `[:to {:optional true} …]` query-schema 500s on empty query-params — Ring's
wrap-params yields {} for a no-query PUT, and main-transformer's parse-empty-as-nil
decodes {} → nil, which the bare [:map] rejects. Production uses the identical
wrap-params, so it was broken there too. So e2e/invoice-new.spec.ts is an ACCEPTANCE
gate (red on the old code, green on the engine, whose submit is a POST with no
query-schema): the migration fixes a latent bug. Create semantics (default → vendor
default account, location-spread; customize → posted grid; edit → prefill + updated
row) were pinned at the REPL.

What changed:
- defrecord 4 → 0 (NewWizard2 / BasicDetailsStep / AccountsStep / NextSteps), mm/ 0,
  fc/ cursor refs 0, step-params[…] field names 0.
- Conditional `:next` `(if (= :customize …) :accounts :done)` replaces mm/CustomNext +
  the broken 308-to-submit. Dual-purpose new+edit = one :init-fn branching on a route
  :db/id; create-wizard! seeds :init-data as per-step step-data so edit opens populated.
- The broken new-wizard-navigate route is deleted; the genuine async helpers
  (account-prediction, due/scheduled-payment-date, location-select, expense total/balance,
  add-row) remain but read the posted flat form (+ ws/get-all for the cross-step total).
- next-steps becomes the done-fn's returned modal (Pay now / Add another / Close).
- Dates ride as java.util.Date (#inst) in step-data so it's EDN-safe across the
  non-terminal step (clj-time DateTimes break the cookie store).

Verification: full e2e suite 61/61 (58 prior + 3 new); maybe-spread-locations unit
test 6/6; create semantics + edit prefill confirmed at the REPL. Skill fed
(scorecard Phase 8, gotchas {}→nil 500 + #inst dates, form-vs-wizard conditional
:next + dual-purpose).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
notid added 1 commit 2026-06-25 22:39:13 -07:00
A five-step linear wizard (info → terms → account → address → legal) plus a
separate Merge dialog, migrated off mm/* + form-cursor + the EDN snapshot onto the
session-backed engine (wizard2), following the Phase 8 template.

Latent bug found + fixed: the old "Next" PUT /admin/vendor/navigat carried a
[:map [:db/id entity-id]] route-schema on a route with no :db/id path param, so empty
route-params {} → main-transformer's parse-empty-as-nil → nil → 500 on every advance
(the same quirk as Phase 8's query-params, now via route-params). The engine's submit
is a POST with no such schema; the dead navigate route is deleted.

What changed:
- defrecord 5 → 0 (InfoModal/TermsModal/AccountModal/AddressModal/LegalEntityModal +
  VendorWizard), mm/ 0, fc/ cursor refs 0 (wizard AND the de-cursored Merge dialog),
  step-params[…] 0.
- 5 de-cursored step renders (plain data + path->name2 + a *errors* binding); the 3
  repeated grids became add-row-handler + a blank-row row render; the timeline is
  preserved as a per-step side panel.
- :init-fn branches new (empty) vs edit (entity split across the 5 steps' :init-data,
  seeded as per-step step-data so edit opens populated); per-step :validate via
  mc/validate + me/humanize replaces wrap-ensure-step; vendor-step wraps
  handle-step-submit in try+ to surface create-time validation as a 4xx.

Two new gotchas found + fixed + documented:
- empty-step decode: an all-blank step collapses to nil (parse-empty-as-nil), which a
  schema :validate rejects as "invalid type"; decode-with coerces nil → {} so optional-
  only steps advance while required-field steps still fail on the missing key.
- blank nested entity: an untouched Address (all-nil, no :db/id) makes :upsert-entity
  mint a tempid used only as value (datomic error); blank-address? drops it.

Verification: full e2e suite 65/65 (61 prior + 4 new: info renders + timeline; create
across all 5 steps persists; edit opens prefilled and a rename persists; a too-short
name blocks advancing). Create + edit confirmed at the REPL incl. the cookie-session
EDN round-trip. Skill fed (scorecard Phase 9; gotchas for both new traps).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
notid added 1 commit 2026-06-26 00:13:09 -07:00
Pin the current (pre-migration) behavior of the Client wizard — the largest SSR
modal: seven linear steps (info → matches → contact → bank-accounts → integrations
→ cash-flow → other-settings) plus the parameterized bank-account sub-editor — so
the upcoming engine migration preserves it.

- e2e/client-wizard.spec.ts: new dialog renders info + the 7-step timeline; edit
  opens prefilled with a disabled code; the bank-accounts step shows the seeded
  account card and the add-account affordance (the crux sub-step); and an
  edit-through-to-save round-trip keeps the client in the grid.
- test_server.clj: give the seeded TEST client a :client/name so the row is
  selectable in the admin grid (its base query requires :client/name); also honor
  a TEST_SERVER_PORT env var so a from-disk e2e JVM can run on a free port
  alongside a REPL-held one (the same parallel-run need the playwright config notes).

Full e2e suite green (69/69) against a fresh from-disk server.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
notid added 1 commit 2026-06-26 00:49:57 -07:00
The largest SSR modal, moved off the mm/* multi-step wizard protocol machinery
(ClientWizard/*Modal records, MultiStepFormState, fc/* form-cursors, EDN-snapshot
round-trip) onto the session-backed engine (wizard2 + wizard-state): flat de-cursored
field names, whole-form HTMX swaps, per-step session state combined by the done-fn.

Seven linear steps (info → matches → contact → bank-accounts → integrations → cash-flow
→ other-settings), each a data-driven {:decode :validate :render :next}. The grid, form
schemas, and the sales power-query export are preserved unchanged.

The parameterized [:bank-account which] mm sub-step (which the linear engine can't model)
becomes a sub-editor of the bank-accounts step: the list view and per-account editor are
whole-form swaps of #wizard-form, driven by dedicated routes (new/edit/accept/discard/
sort) that mutate the :bank-accounts step-data in the session directly and re-render via
the engine's render-wizard. The bank-accounts step's :decode is a pass-through that
re-affirms the session-managed list (read via a `wiz` hidden the engine doesn't strip),
so Next never wipes it.

Notable fixes carried over from prior phases:
- New vs edit is keyed off :db/id presence (the engine always POSTs, so the old PUT/POST
  split no longer distinguishes them).
- Client + bank-account dates are coerced to #inst for EDN-safe session storage
  (clj-time DateTime has no cookie-session reader).
- An empty Contact-step address posts blank fields → decodes to an all-nil, db/id-less
  map; blank-address? drops it before upsert (else datomic: "tempid used only as value").

Routes: drop ::navigate/::discard; add the four bank-account sub-editor routes.
Full e2e suite green (71/71); client-wizard acceptance spec rewritten for the engine
(flat field names, data-primary nav, bank-account open/accept/discard sub-flows).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
notid added 1 commit 2026-06-26 00:50:58 -07:00
- 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>
notid added 1 commit 2026-06-26 07:41:51 -07:00
With all 11 plan modals migrated onto the session-backed engine, the mm multi-step
wizard framework has zero runtime callers (transaction/edit.clj builds its
:multi-form-state map from its own wrap-derive-state and never required the namespace).
Delete it:

- src/.../components/multi_modal.clj — the ModalWizardStep/LinearModalWizard/
  Initializable/Discardable protocols, the MultiStepFormState record, and the
  wrap-wizard / wrap-decode-multi-form-state / default-render-step / encode-step-key
  middleware + helpers (~22KB).
- test/.../transaction/edit_simple_advanced_mode_test.clj — the last importer of mm; it
  was already broken (refers edit-vendor-changed-handler / edit-wizard-toggle-mode-handler,
  both removed when Transaction Edit migrated, so it no longer loaded) and tests the old
  mm interface that no longer exists.
- test_server.clj: drop the stale unused mm require.

form-cursor (fc/*) stays — still used by ~18 non-wizard forms outside this plan. The
alpine-morph focus mechanism stays — it belongs to the whole-form-swap doctrine, not the
wizard machinery.

Fresh from-disk JVM compiles clean without the namespace; full e2e suite 71/71.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
notid added 1 commit 2026-06-26 08:37:49 -07:00
Locks in the sub-editor → session → save-client! round-trip the earlier blank-address
fix unblocked: edit the seeded account's nickname via the bank-account sub-editor, Accept,
save the client, reopen, walk back to the bank-accounts step, and confirm the new nickname
is shown and the old one is gone (renamed in place, not duplicated). Full e2e suite 72/72.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
notid added 1 commit 2026-06-26 09:20:48 -07:00
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>
notid added 4 commits 2026-06-27 13:40:01 -07:00
Three regressions from the SSR rendering-modernization migration, all verified
live via agent-browser:

- BUG A — New Invoice: choosing a client 500'd from /invoice/new/due-date
  (ClassCastException: DateTime cannot be cast to java.util.Date). `due-date`
  and `scheduled-payment-date` called `coerce/from-date` on values already
  decoded to clj-time DateTimes. Drop the coerce; use the decoded dates.

- BUG C — Transaction Edit: any whole-form swap (mode toggle, vendor change,
  add/remove row) 500'd whenever the txn had >=1 autopay-invoice match
  (ClassCastException at links-body*: PersistentVector cannot be cast to Named).
  The autopay link-panel's hidden `action` input was missing `:form ""`, so it
  serialized alongside the main `action` hidden, producing a duplicate param
  that Ring collapsed to a vector. Add `:form ""` to match the unpaid/rule panels.

- Modal sizes: Vendor/Client/Invoice-Pay modals ballooned to full width because
  resources/public/output.css was missing their arbitrary Tailwind size classes.
  Root cause: tailwind.config.js `content` never scanned resources/templates/**/*.html
  (46 Selmer templates the migration introduced), so a rebuild also dropped
  template-only classes like md:w-[950px]. Add the templates glob and rebuild;
  all modal size classes now present, no working modal regressed.

Docs: add 2026-06-27 QA findings + resumable fix task list; cross-link from the
migration plan. Remaining (per the new plan): Vendor/Client inner step-body
overflow, wizard step animations, bulk-edit empty-selection 500, footer EDN leak.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
BUG E/F: the Vendor and Client wizard step cards overflowed — horizontal
scrollbar, clipped fields, a huge empty grey left region. The step-card lays the
step timeline in a vertical left sidebar (grow-0, self-stretch), but
vendor-timeline/client-timeline rendered the HORIZONTAL `timeline` component,
which forced the shrink-to-fit sidebar to ~full card width and pushed the body
off-screen.

- vendor-timeline / client-timeline: use timeline/vertical-timeline +
  vertical-timeline-step (the components already existed) instead of the
  horizontal pair.
- vendors.clj: step bodies w-[600px] h-[350px] -> w-full h-[350px] so the body
  fills the width left of the now-narrow vertical timeline.

Verified live (agent-browser): Vendor 760x520, Client 820x560, vertical timeline,
no horizontal overflow, Info->Terms navigation + validation re-render lay out
correctly. Both files pass lein cljfmt check.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- BUG D: clicking "Bulk Edit" with no invoices selected 500'd. selected->ids
  returns nil with no selection, and all-ids-not-locked fed that nil into a
  Datomic `:in $ [?i ...]` query ("Unable to find data source"). Guard the body
  with (when (seq all-ids) ...) so an empty selection yields [] and the modal
  opens cleanly. Verified live: no "Oh, drat" toast, no 500.

- BUG B: modal-footer- called (hx/alpine-appear ...) twice; the 2nd return value
  (an attribute map) landed in child position and rendered as literal EDN
  ({:x-show ...}) in the red error banner whenever unexpectedError flipped true.
  Delete the duplicate. Verified: rendered HTML now has one alpine-appear and no
  EDN-text child. (Pre-existing defect, also present on master.)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
§3 animations: the migrated wizard step cards had no transition, so step→step
swaps and modal open were flat. The old mm/* slide system was deleted in Phase
11 (and its classes purged from CSS), and the transaction-edit "reference" uses
an undefined `last-modal-step` no-op — so there was no clean slide to restore.

Apply the codebase's existing `fade-in transition-opacity duration-300`
primitive (`.htmx-added .fade-in` in input.css) to all three wizard step cards
(new-invoice basic-details + accounts, vendor step-card, client step-card). Each
card now fades in on open and on every step swap. Verified live: cards always
settle to opacity 1 (never stuck invisible) on both open and step navigation.

Richer directional (forward/back) slide transitions are left for a design pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
notid added 1 commit 2026-06-27 21:20:48 -07:00
The engine migration replaced the old mm/* modal-stack wizards (which slid
forward/back between steps) with wizard2, but never carried the slide over — step
transitions went flat. Restore the original mechanism in the shared engine so all
wizards (new-invoice, vendor, client, pay, transaction-rule) get it:

- wizard2/step-slide-classes: the group-[.forward]/transition:htmx-* and
  group-[.backward]/* slide variants, applied to the swapped <form>.
- wizard2/transitioner: the #transitioner wrapper whose @htmx:after-request hook
  reads the x-transition-type response header and toggles group/transition +
  forward|backward on itself. All 5 configs' :open-response now use it.
- wizard2/handle-step-submit sets x-transition-type (forward on advance, backward
  on Back, none on a same-step validation re-render) + HX-reswap "outerHTML
  swap:0.16s" so the slide-out plays before the swap. Direction computed from
  step order (transition-type).
- Removed the interim per-card fade-in in favor of this.
- Rebuilt output.css so the 16 fwd + 16 back slide variants are compiled.

REPL-verified: open-wizard emits the transitioner, the form carries the slide
classes, and submit responses carry the transition headers. Live verification
needs a server refresh (the dev server froze its route table at startup).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
notid added 1 commit 2026-06-28 00:32:04 -07:00
The pay wizard modal was w-[50em] (800px) but its bank-account cards are
w-[30em] (480px), so once the CSS rebuild activated the real width it rendered
with a large empty right region — visible especially via the "Pay now" step
after creating an invoice. It had effectively been content-width (~543px) before
and looked fine. Set the pay modal to w-[34em] (544px) so it fits its content
and stays consistent across both pay steps. Verified live: modal is 544px and
the method cards fill it, centered.

Also fix the post-create "next steps" modal's stale header text
("Invoice accounts " -> "Invoice created").

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
notid added 1 commit 2026-06-28 07:00:23 -07:00
It was auto-sized to its content (~390px) and left-aligned, looking like a
stray box. Match it to the pay modal width (w-[34em] / 544px) so the
create → confirm → pay flow is visually consistent, center the content, and
make the Pay now / Add another / Close buttons full-width. Verified live: 544px,
centered, header "Invoice created".

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
notid added 1 commit 2026-06-28 08:47:48 -07:00
The bank-account editor (cash/credit/checking) is a full sub-modal that swaps
into the client wizard at the same 820px width, but its single-column form
(~430px of fields) hugged the far-left edge, leaving the whole right half empty —
the same "too wide / empty" look as the pay modal. Constrain the form to max-w-xl
and center it (mx-auto) so the whitespace is balanced; keeps the 820px card so
there's no size jump entering/leaving the editor.

Audit context: walked all 7 client steps + the pay wizard (step 1 + step 2
simple/advanced grid) at 1440x900 — all fit with no overflow; the regular steps
stay balanced via the vertical timeline sidebar. This sub-editor was the one
unbalanced spot.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
notid added 1 commit 2026-06-28 09:43:27 -07:00
A missing required field (e.g. the Info step with no Location added) surfaced
Malli's raw "missing required key". Override the missing-key message in
validate-with's me/humanize call so it reads "This is required." for every
required field across the client wizard. Verified live on the Info step.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
notid added 1 commit 2026-06-29 19:22:44 -07:00
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>
notid added 1 commit 2026-06-29 19:44:54 -07:00
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>
notid added 1 commit 2026-06-29 22:23:59 -07:00
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>
notid added 1 commit 2026-06-29 22:48:55 -07:00
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>
notid added 1 commit 2026-06-30 00:37:29 -07:00
Abandons the Selmer-templating step of the SSR re-authoring and moves the four
migrated form/wizard modals back to Hiccup (com/* components), keeping the
whole-form HTMX swap doctrine, top-rooted render functions, and the
session-backed wizard engine unchanged.

- transaction/edit, transaction/bulk_code, invoices (bulk-edit group), and
  pos/sales_summaries render via com/* again; every hx-* swap (whole-form +
  targeted location-cell / totals-tbody / inline account-cell swaps) is
  preserved exactly.
- add com/single-modal-card to centralize the md:w-[950px] md:h-[650px] modal
  chrome that previously lived only in the Selmer modal-card templates.
- delete auto-ap.ssr.selmer, auto-ap.ssr.components.selmer, selmer_test, the
  whole resources/templates tree (55 files), the selmer dependency, and the
  tailwind resources/templates content glob.
- strip Selmer guidance from the ssr-form-migration skill + modernization plan.

Verified: all four namespaces compile and render with no stringified-hiccup
leaks; output.css rebuilds byte-identically (no Tailwind class loss); 60 e2e
specs pass — the four reverted modals (incl. whole-form-swap focus/caret tests)
plus the untouched wizard/pay/new/rule modals.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Author
Owner

Update: the Hiccup→Selmer step has been reverted (commit 8b43017d). The four migrated modals (transaction edit, bulk-code, invoice bulk-edit, sales-summary) render in Hiccup (com/*) again; the whole-form swap doctrine, top-rooted render fns, and the session-backed wizard engine are unchanged. Deleted the selmer namespaces, the resources/templates tree (55 files), the selmer dep, and the tailwind templates glob. Verified: namespaces compile + render leak-free, output.css byte-identical, 60 e2e specs green. Note: this PR's title is now stale — happy to retitle.

Update: the Hiccup→Selmer step has been **reverted** (commit 8b43017d). The four migrated modals (transaction edit, bulk-code, invoice bulk-edit, sales-summary) render in Hiccup (`com/*`) again; the whole-form swap doctrine, top-rooted render fns, and the session-backed wizard engine are unchanged. Deleted the selmer namespaces, the `resources/templates` tree (55 files), the selmer dep, and the tailwind templates glob. Verified: namespaces compile + render leak-free, output.css byte-identical, 60 e2e specs green. Note: this PR's title is now stale — happy to retitle.
notid changed title from refactor(ssr): full Selmer migration of Transaction Edit + Bulk Code; remove the wizard to refactor(ssr): re-author form/wizard modals — whole-form swap, session wizard engine, Hiccup rendering 2026-06-30 06:50:59 -07:00
This pull request has changes conflicting with the target branch.
  • resources/public/output.css
  • src/clj/auto_ap/ssr/pos/sales_summaries.clj
  • src/clj/auto_ap/ssr/transaction/edit.clj
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin integreat-execute-refactor:integreat-execute-refactor
git checkout integreat-execute-refactor
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: notid/integreat#14