Commit Graph

57 Commits

Author SHA1 Message Date
a01dfc197e refactor(ssr): full Selmer migration of Transaction Edit; remove the wizard
Migrate every part of the Transaction Edit modal's HTML to Selmer templates
(zero Hiccup in the render path) and delete the mm multi-modal "wizard"
abstraction entirely -- there was only ever one step.

- New auto-ap.ssr.components.selmer (sc) + ~22 shared component partials under
  resources/templates/components/ (typeahead, button-group, radio-card,
  data-grid, validated-field, modal, buttons, inputs, SVGs). Each wrapper renders
  its own partial; dynamic HTMX/Alpine attrs bridge via attrs->str -> {{attrs|safe}}.
- 15 modal templates under resources/templates/transaction-edit/.
- Delete EditWizard/LinksStep records + all mm/* usage. Plain handlers: flat
  wrap-decode-edit (fields renamed off step-params[...], stray keys stripped),
  flat wrap-derive-state, *errors*-based field errors, generic wrap-form-4xx-2.
- Drop the edit-wizard-navigate route (routes ~12 -> 5).
- Fix: stray `method` (tab button-group hidden) leaked into the upsert -> 500;
  strip decoded map to schema keys.
- e2e selectors updated (#wizard-form->#edit-form, #wizardmodal->#editmodal,
  step-params[...] field names). Parity: swap 6/6, edit 8/8, suite 38/1
  (1 pre-existing unrelated nav test).
- ssr-form-migration skill updated with the learnings (composition mechanics,
  sc/* library, drop-the-wizard recipe, scorecard row, 3 new gotchas).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 07:47:47 -07:00
c892719bd1 feat(ssr): migrate location-select to a Selmer template (Phase 2 — Selmer validated)
First interactive Transaction Edit component rendered from a Selmer template instead of
Hiccup/com/select, proving the render-file path + the Hiccup<->Selmer interop bridge on
real, e2e-covered markup.

- resources/templates/components/location-select.html: plain-HTML <select> with a
  {% for %} over option maps + {% if opt.selected %}.
- location-select*: build options/selected/classes in Clojure (reusing
  inputs/default-input-classes so styling can't drift), render via
  sel/render->hiccup, and embed the fragment back into the still-Hiccup account row.
- skill: finalize selmer-conventions.md from this validated example (was a stub); add the
  cookbook entry; scorecard marks the first Selmer component.

Verified on a fresh server: full suite 38 pass / 1 unrelated fail, swap 6/6,
transaction-edit 8/8 -- the Shared Location test selects through the Selmer <select>,
saves, and spreads Shared -> DT. Verified by string-match + e2e (not byte-parity:
hh/add-class is set-based so class order differs, CSS is order-independent).

Scope note: the modal's remaining attribute-heavy components delegate to the shared
com/typeahead / com/select / com/button-group-button; converting those is the
cross-cutting Phase 11 Selmer sweep, not a single-modal change (Open decision 2).
2026-06-03 17:28:53 -07:00
d0fad63e24 refactor(ssr): remove the EDN snapshot round-trip; transaction edit is a plain form (heuristic 2)
The wizard serialized the whole accumulating form state into a `snapshot` hidden field
(pr-str EDN + custom readers), decoded it every request, and merged step-params back in.
For this single-step modal the snapshot is pure redundancy: every value is either in the
entity or the live posted form. Remove it:

- render: EditWizard.render-wizard renders a plain form -- no snapshot / edit-path /
  current-step hidden fields; a single `db/id` hidden rides in the form instead.
- middleware: wrap-derive-state rebuilds :multi-form-state per request from the entity
  (loaded by the db/id hidden) overlaid with the live step-params, replacing
  wrap-init-multi-form-state + wrap-entity. The ~34 :snapshot reads are unchanged --
  :snapshot is now a derived map, not a round-tripped blob.
- editable fields (accounts, vendor, memo, approval, action, mode, amount-mode) come ONLY
  from the posted form (absent = cleared) so removing all account rows doesn't resurrect
  the entity's persisted accounts; only entity-only fields (db/id, client, amount, ...)
  come from the entity.
- delete the dead initial-edit-wizard-state and render-account-grid-body.
- e2e: make removeAllAccounts re-query each iteration (whole-form swaps stale a captured
  row index) and restore the percentage test to type-then-add ordering.

Scorecard: snapshot EDN round-trip + custom readers + merge-multi-form-state -> gone
(snapshot-field renders 0). Verified on a fresh server: full suite 38 pass / 1 unrelated
fail, swap 6/6, transaction-edit 8/8 -- same green as before, snapshot removed.
2026-06-03 15:20:26 -07:00
0b5bfd9c84 fix(ssr): operation handlers read live step-params, not the stale snapshot (rewrite stage 1)
apply-new-account / apply-remove-account / apply-toggle-amount-mode rebuilt the account
rows from the decoded :snapshot, dropping any value the user typed but that had not yet
round-tripped into the snapshot (type 50%, click "New account" -> first row reverts,
giving a 66.67/33.33 split instead of 50/50). Read the live :step-params rows instead
(already schema-decoded by mm/wrap-wizard, so typed), falling back to snapshot only when
absent. First stage of removing the snapshot round-trip; fixes a real user-facing bug
(typed amounts lost on add/remove/$%-toggle).

Restore the percentage-split e2e to the realistic type-then-add ordering as a regression
guard. Modal stays green: swap 6/6, transaction-edit 8/8.
2026-06-03 08:18:58 -07:00
0f5650b73e refactor(ssr): collapse 5 manual-coding operation routes into edit-form-changed (heuristic 6)
The 5 manual-coding operations (vendor change, simple/advanced toggle, add row, remove
row, $/% toggle) each had their own route + handler, all doing "mutate form state ->
render-full-form". Fold them into the single edit-form-changed endpoint, which now
dispatches on an `op` form-param to the relevant pure apply-* mutation fn (apply-vendor-
changed / apply-toggle-mode / apply-new-account / apply-remove-account /
apply-toggle-amount-mode) then re-renders. A missing/unknown op (a plain dependent-field
change, e.g. account->location or amount->totals) just re-renders, as before.

- edit.clj: 6 handlers -> 1 dispatcher + 5 pure apply-* fns; markup posts to
  edit-form-changed with :hx-vals {:op "..."}.
- routes/transactions.cljc: remove the 5 now-unused route keys.
- e2e specs: retarget the vendor selector by op (div[hx-vals*="vendor-changed"]) and
  point the toggle-amount-mode / vendor response waits at edit-form-changed, since the
  old per-op route names are gone. (Behavioral assertions unchanged.)

Scorecard: manual-coding routes ~10 -> ~5 (operations now one dispatcher). Parity held:
swap spec 6/6, full suite 32 pass (Shared Location green; no new regression).
2026-06-03 07:07:52 -07:00
07159dc221 refactor(ssr): drop dead account-total / account-balance routes (heuristic 6)
The hx-select reference moved running totals into an inline #account-totals tbody that
refreshes via edit-form-changed, so nothing posts to ::route/account-total or
::route/account-balance anymore -- their route handlers were referenced only by their own
registrations. Remove the two handler fns, their route registrations, and the now-unused
route keys from routes/transactions.cljc. The pure account-total* / account-balance* fns
(used inline to render the totals) are untouched.

Scorecard: modal routes ~12 -> ~10. Full suite 31 pass / no regression.
2026-06-03 06:33:52 -07:00
57f3b63b6a refactor(ssr): de-fake simple-mode account cursor via explicit render (heuristic 1)
simple-mode-fields* rendered its single account row by rebinding the form cursor to a
synthetic MapCursor rooted at accounts[0] (faking a deep starting position). Replace that
with explicit-data rendering: account-field-name builds the exact field names the cursor
would produce at [:step-params :transaction/accounts 0 field] (via path->name2), and
account-field-errors reads errors from the same path -- no re-rooted cursor.

This is the render-fn rewrite the earlier with-field-default shortcut couldn't be (that
mutated the cursor and broke the simple-mode swap). Scorecard: faked cursor roots 2 -> 0
(both heuristic-1 items now clear for this modal). Parity held: swap spec 6/6 (its vendor
tests run in simple mode), Shared Location save green, full suite 31 pass / no regression.
2026-06-03 06:29:25 -07:00
32056bf396 refactor(ssr): delete dead transaction-account-row-no-cursor* twin (heuristic 1)
transaction-account-row-no-cursor* and its only helper account-field-name were
unreferenced anywhere in src/ or test/ -- the *-no-cursor* duplicate the plan targets
for removal. The live row renderer is the top-rooted cursor form transaction-account-row*
(driven by fc/cursor-map from the accounts cursor). Deleting the twin: no-cursor twins
1 -> 0, ~53 LOC removed. Swap spec stays 6/6.
2026-06-03 06:15:13 -07:00
69eed1f8a6 fix(ssr): strip UI-only :mode before transaction upsert (500 on advanced manual save)
The :manual save handler builds its tx-data from the wizard snapshot and stripped the
control fields :action and :amount-mode, but not :mode (simple/advanced) added by the
recent manual-coding work. manual-coding-section* emits step-params[mode] on every
render, so EVERY advanced manual save posted :mode "advanced" into :upsert-transaction
and 500'd with ":db.error/not-an-entity :mode". Strip :mode alongside :action so the
upsert only sees real schema attributes.

Also fix the e2e helper that masked this: selectAccountFromTypeahead poked the Alpine v2
internal `el.__x.$data`, which is undefined on Alpine v3 (this app loads alpinejs@3.x),
so it silently no-op'd and the account posted empty. Drive the typeahead via the real
Alpine v3 path (Alpine.$data + tippy dropdown + click), mirroring transaction-edit-swap.

Unmasks the previously-failing "Shared Location spread on save" test (was first in a
serial file, hiding 7 siblings). Verified: that test passes; transaction-edit-swap stays
6/6. Skill gotchas.md records the :mode-strip rule, the Alpine-v3 API requirement, and
the modal-won't-close diagnosis recipe.
2026-06-03 06:05:42 -07:00
482b4802ff Make swaps precise: drop no-op requests, swap only the affected field
Refine per-trigger granularity now that the swap target is explicit:

- Memo issues no request at all -- it affects nothing else, so its value just
  rides along in the form and is merged into the snapshot on save. (Changing the
  Location *value* likewise issues no request -- it never did; that cell's request
  is the account->location dependency.)
- Account select swaps only that row's Location cell (#account-location-<index> /
  #simple-account-location) instead of the whole form. Selecting an account only
  affects the valid Location options (computed from the posted account-id), so a
  precise cell swap is safe -- no snapshot dependency.

Account-structural changes (vendor, add/remove row, mode toggle, $/% radio) keep
swapping the whole form: their accounts+amount-mode state is interdependent and
round-trips through the single form-level snapshot hidden field, so a whole-form
swap is what keeps it consistent with zero OOB.

Update the memo test to assert it fires no request and keeps its value/caret.
Full e2e suite: 27 passed / 2 failed (same pre-existing, unrelated failures).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 20:12:22 -07:00
5f1bb6db82 Whole-form hx-select swaps with zero out-of-band swaps
Replace the section-swap + OOB approach with uniform whole-form swaps,
eliminating both out-of-band swaps:

- Discrete edits (vendor, account, location, mode, add/remove row) now swap all
  of #wizard-form via hx-select. The active action/tab already round-trips
  (:action is in edit-form-schema and the tab x-data inits from it), so a
  whole-form swap re-creates the tab state from the server value and the active
  tab is preserved -- no #wizard-snapshot OOB needed, since the snapshot hidden
  field rides along inside the form.
- Move the totals into their own <tbody id="account-totals"> (new optional
  :footer-tbody param on data-grid-) so the amount field updates them with a
  plain targeted swap instead of an OOB swap of #total,#balance. The totals tbody
  is a sibling of the input rows, so the amount input is never replaced.
- Memo unchanged (hx-swap=none).

Net: 0 hx-select-oob, 0 morph. The focus invariant is unchanged -- the typed
field is never inside a region it swaps. Tab clicks stay Alpine (instant); only
the action value round-trips. Revert the now-unneeded #wizard-snapshot id.

Full e2e suite: 27 passed / 2 failed (same pre-existing, unrelated failures).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 11:01:37 -07:00
a2684bf5c1 Replace alpine-morph with targeted hx-select / OOB swaps
Drop the whole-form alpine-morph swap in favour of posting the whole form
but swapping back only what changed, never the input the user is editing --
so focus and caret survive a plain swap with no morph extension.

- Discrete changes (vendor, account, location, mode, add/remove row) swap the
  #manual-coding-section fragment via hx-select, plus an OOB refresh of the
  #wizard-snapshot hidden field so the round-tripped wizard state stays in sync
  (the snapshot lives at #wizard-form level, outside the swapped fragment, and
  the new/remove-account handlers read it).
- The amount field OOB-swaps only #total/#balance (hx-swap=none); memo posts
  with hx-swap=none. Neither input is ever replaced.
- Give the BALANCE cell a unique id (#balance) so the OOB selector is unambiguous.
- Remove the alpine-morph ext + @alpinejs/morph plugin and all the key/x-data
  re-init tricks they required. Rebuilding the fragment fresh makes vendor->account
  population and repeat vendor changes work without any keying.
- Rename e2e/transaction-edit-morph.spec.ts -> -swap.spec.ts; assertions unchanged
  (focus/caret preservation, vendor->account, repeat vendor changes all hold).

Full e2e suite: 27 passed / 2 failed (both pre-existing and unrelated -- the
legacy save-flow test and the date-range filter test).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 08:10:05 -07:00
cdb6bb6fe3 Whole-form htmx + Alpine morph for transaction edit
Re-render the entire #wizard-form on each field edit and swap with
hx-swap="morph" so the focused input keeps focus/caret/value while typing.

- Field-level routes return the full form and target #wizard-form
- Key state-owning wrappers (account rows, simple-mode wrapper, vendor
  typeahead) so server-driven value changes re-init across the morph
- Guard tippy/$refs access in typeahead against stale post-morph state
- Round-trip simple/advanced mode via step-params[mode]
- Add e2e/transaction-edit-morph.spec.ts covering focus/caret preservation,
  vendor->account population, and repeated vendor changes
- Seed a second vendor/account for test isolation

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 07:40:30 -07:00
38ae6f460f Cleanup of simple/advanced mode 2026-05-31 08:30:11 -07:00
e156d8bfd8 fixes vendor selection bug 2026-05-30 09:21:39 -07:00
3441ae63b4 fix: normalize account ref map to db/id in simple mode rendering
When a transaction is pre-coded, the snapshot stores :transaction-account/account
as a Datomic ref map {:db/id N} rather than a bare integer. simple-mode-fields*
and the simpleAccountId Alpine initializer both need the integer id, not the map,
to correctly populate the account typeahead value and the x-hx-val binding.
2026-05-28 00:10:15 -07:00
79ddda624a fix: change toggle-mode links from hx-get to hx-post so form data is submitted 2026-05-27 23:53:43 -07:00
cbb9bc750d fix: prefer step-params over snapshot for row-count and existing-row 2026-05-27 23:49:59 -07:00
9997d60de1 fix: handle absent :transaction/accounts in simple-mode-fields* (ValCursor crash) 2026-05-27 23:07:52 -07:00
06fb0ea067 feat: update edit-vendor-changed-handler to support simple/advanced mode 2026-05-27 22:03:13 -07:00
9a7d0b8b18 feat: add edit-wizard-toggle-mode-handler 2026-05-27 21:59:33 -07:00
70a3db9a64 fix: repair vendor-changed swap target and remove unused declare 2026-05-27 21:57:11 -07:00
4e22fb1d82 feat: extract manual-coding-section* with simple/advanced mode selection 2026-05-27 21:52:09 -07:00
a88dcf4122 fix: safe cursor indexing in simple-mode-fields* 2026-05-27 21:44:54 -07:00
00b5303c28 feat: add simple-mode-fields* for transaction edit modal 2026-05-27 21:40:04 -07:00
de73233a08 transaction dialog now uses sidebar. 2026-05-26 21:58:24 -07:00
85652a7ce7 horizontal 2026-05-26 11:57:59 -07:00
ae0788e6dd improvements 2026-05-26 11:18:52 -07:00
f239b114c3 Merge branch 'integreat-fix-errors' into staging 2026-05-24 21:54:54 -07:00
8e3aa13f4d fixes 2026-05-24 21:54:37 -07:00
3715910029 adds invoices dates 2026-05-23 12:28:21 -07:00
ba87805d4c Add vendor pre-population for bulk code and individual edit forms
- Add vendor-changed HTMX handlers for both bulk code and individual edit
- Pre-populate default account at 100% when vendor is selected and no accounts exist
- Fix render-accounts-section to render from step-params correctly
- Change bulk code vendor-changed from hx-get to hx-post to include form data
- Add routes for vendor-changed endpoints
- Update e2e tests to cover vendor pre-population
- Run lein cljfmt fix across codebase
2026-05-21 14:45:19 -07:00
4221d6a0d6 more fixes 2026-05-21 11:46:45 -07:00
918ddd14ff Fix format error in toggle-amount-mode: ensure double values for %f format
The format specifier $%,.2f requires floating-point values but
(reduce + 0 ...) can return a Long when all amounts sum to an integer.
Added explicit (double ...) casts and changed initial value to 0.0
to ensure the format call always receives a double.
2026-05-21 09:14:20 -07:00
acd4184ef0 Fix toggle-amount-mode: avoid cursor context entirely for HTMX re-render
The previous attempts to set up form cursor context in toggle-amount-mode
were failing because the cursor library's dynamic binding model is complex
and requires specific initialization through fc/start-form.

Instead of trying to recreate the cursor context, this fix:
1. Creates transaction-account-row-no-cursor* that renders rows with explicit
   field names and values (no cursor functions)
2. Rewrites toggle-amount-mode to directly construct the data-grid HTML
   using map-indexed over the accounts vector
3. Removes the broken manual cursor binding attempts
4. Removes unused auto-ap.cursor import

This ensures the toggle handler works independently of the wizard's cursor
context while still producing identical HTML output.
2026-05-21 07:44:43 -07:00
857a1536ef Fix toggle-amount-mode: set up form cursor context for grid re-render
The toggle-amount-mode handler was failing because account-grid-body*
uses fc/cursor-map which requires the form cursor context to be set up.
Added manual cursor binding in toggle-amount-mode to create a cursor
pointing to the transaction/accounts vector and bind it to fc/*current*
before rendering the grid.
2026-05-21 07:33:15 -07:00
351659f8eb Fix toggle-amount-mode: properly update request state with converted accounts and new mode
The assoc-in call had too many arguments, so the request state wasn't being
updated with the new mode or converted accounts. Using -> threading with
separate assoc-in calls ensures both the accounts and mode are properly set
before re-rendering the grid.
2026-05-20 23:18:35 -07:00
4739769297 Fix $/% toggle: handle nil amounts and make toggle horizontal
- Fix Math/abs nil error when adding new accounts by using (or value 0.0)
- Fix Math/abs nil in account-grid-body* and save-handler for safety
- Make $/% radio toggle display side-by-side using :orientation :horizontal
- Apply fixes to edit-wizard-new-account render, account-grid-body*, and save-handler
2026-05-20 23:15:05 -07:00
567db50a66 Add $/% toggle for transaction account amounts in manual edit
When editing a transaction manually, users can now toggle between viewing
account amounts as dollar values or percentages. The toggle appears in the
table header as a radio button group ($ / %).

Key features:
- Global toggle switches all accounts simultaneously
- $→%: amounts are converted to percentages of the transaction total
- %→$: uses percentages->dollars with spread-cents for accurate cent distribution
- Form state preserves vendor, memo, approval status when toggling
- Save handler converts % back to $ before persisting to Datomic
- Uses HTMX to re-render only the account grid body on toggle

New route: /toggle-amount-mode
New functions: ->percentage, percentages->dollars, convert-accounts-mode,
              account-grid-body*, toggle-amount-mode
2026-05-20 23:07:11 -07:00
0692089e39 Flash transaction row after editing through modal
When a transaction is saved via the edit modal, return the updated row
HTML with {:flash? true} instead of an empty div. This makes the row
flash with the live-added animation, matching the behavior of other
pages like invoices and accounts.

Uses hx-retarget to swap the specific row in the table while also
triggering modalclose to close the modal.
2026-05-20 21:39:30 -07:00
8189a7648b Fix Datomic conflict when spreading shared location across accounts
When a transaction account had 'Shared' location, spread-account was
creating multiple accounts with the same :db/id but different locations,
causing a datoms-conflict error in Datomic.

Now generates unique tempids for all spread accounts beyond the first,
preventing entity ID collisions while preserving the original account's
tempid for the first location.

Fixes: Two datoms in the same transaction conflict on :transaction-account/location
2026-05-20 21:33:44 -07:00
dd4d1a6d4f Add error message to require-approval validator
Ensure the validation error message shows up properly when users try
to approve a manual transaction without assigning financial accounts.
2026-05-20 21:29:03 -07:00
0811771ae6 Fix manual transaction validation: require accounts when approving
- Add require-approval schema validation for :manual action
- Fix keyword comparison to use :transaction-approval-status/approved
- Move require-approval function before schema definition
- Also fix save handler validation to use correct keyword
2026-05-20 21:23:20 -07:00
5a05c144ea progress on bulk activities 2025-03-31 10:55:47 -07:00
0bae8f3d1b Improvements for transaction page 2025-03-22 23:21:21 -07:00
f3ca8afcc9 progress 2025-03-15 21:55:41 -07:00
dd2f6508fe fixes 2025-03-15 21:20:19 -07:00
e22beb149e much smaller build (hopefully) and transactions progress. 2025-03-10 22:13:53 -07:00
fcb46bf756 msot 2025-03-10 15:04:39 -07:00
7373ef41d1 Removes old login page, more progress on transactions 2025-03-10 14:09:45 -07:00