# Transaction Edit Modal: Simple / Advanced Mode Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** Replace the always-visible account split table in the transaction edit modal with a simple mode (single account + location fields) that is the default for uncoded or single-account transactions, with a toggle to the full advanced split table.

**Architecture:** HTMX-driven server-side swap. A new `edit-wizard-toggle-mode` GET endpoint re-renders the manual coding section in the requested mode. Mode is carried via a hidden `<input name="mode">` field included in all HTMX requests. `edit-vendor-changed` is updated to branch on mode. The `LinksStep` render function selects initial mode based on account row count.

**Tech Stack:** Clojure/Hiccup server-side rendering, HTMX, Alpine.js, Bidi routing, Datomic

**Spec:** `docs/superpowers/specs/2026-05-27-transaction-edit-simple-advanced-mode-design.md`

---

## File Map

| File | What changes |
|------|-------------|
| `src/cljc/auto_ap/routes/transactions.cljc` | Add `::edit-wizard-toggle-mode` route |
| `src/clj/auto_ap/ssr/transaction/edit.clj` | Add `simple-mode-fields*`, `manual-coding-section*`, `edit-wizard-toggle-mode-handler`; update `LinksStep` render; update `edit-vendor-changed-handler`; register new route handler |

---

## Task 1: Add the toggle-mode route

**Files:**
- Modify: `src/cljc/auto_ap/routes/transactions.cljc`

- [ ] **Step 1: Add the route entry**

Open `src/cljc/auto_ap/routes/transactions.cljc`. After the `"/edit-wizard-new-account"` line, add:

```clojure
"/edit-wizard-toggle-mode" ::edit-wizard-toggle-mode
```

The file should look like:

```clojure
"/edit-wizard-new-account" ::edit-wizard-new-account
"/edit-wizard-toggle-mode" ::edit-wizard-toggle-mode
"/match-payment" ::link-payment
```

- [ ] **Step 2: Verify the file compiles**

```bash
clj-nrepl-eval -p 9000 "(require '[auto-ap.routes.transactions] :reload)"
```

Expected: no errors.

- [ ] **Step 3: Commit**

```bash
git add src/cljc/auto_ap/routes/transactions.cljc
git commit -m "feat: add edit-wizard-toggle-mode route"
```

---

## Task 2: Add `simple-mode-fields*` — the simple-mode account/location UI

**Files:**
- Modify: `src/clj/auto_ap/ssr/transaction/edit.clj`

This function renders the account typeahead + location select + toggle link for simple mode. It goes near the existing `account-typeahead*` and `location-select*` helpers (around line 180).

- [ ] **Step 1: Add `simple-mode-fields*` after `account-typeahead*` (around line 180)**

```clojure
(defn simple-mode-fields*
  "Renders the simple-mode account + location row and the toggle link.
   request must have :multi-form-state and :entity bound."
  [request]
  (let [snapshot     (-> request :multi-form-state :snapshot)
        client-id    (or (-> request :entity :transaction/client :db/id)
                         (:transaction/client snapshot))
        existing-row (first (:transaction/accounts snapshot))
        account-val  (:transaction-account/account existing-row)
        location-val (or (:transaction-account/location existing-row) "Shared")
        account-id   (when (nat-int? account-val)
                       (dc/pull (dc/db conn) '[:account/location] account-val))
        row-id       (or (:db/id existing-row) (str (java.util.UUID/randomUUID)))]
    [:div
     ;; hidden inputs to encode the single row as transaction/accounts[0]
     (fc/with-field :transaction/accounts
       (fc/with-cursor-index 0
         [:span
          (fc/with-field :db/id
            (com/hidden {:name (fc/field-name) :value row-id}))
          [:div.flex.gap-2.mt-2
           (fc/with-field :transaction-account/account
             (com/validated-field
              {:label "Account" :errors (fc/field-errors)}
              [:div.w-72
               (account-typeahead* {:value    account-val
                                    :client-id client-id
                                    :name     (fc/field-name)
                                    :x-model  "simpleAccountId"})]))
           (fc/with-field :transaction-account/location
             (com/validated-field
              {:label    "Location"
               :errors   (fc/field-errors)
               :x-hx-val:account-id "simpleAccountId"
               :hx-vals  (hx/json (cond-> {:name (fc/field-name)}
                                    client-id (assoc :client-id client-id)))
               :x-dispatch:changed "simpleAccountId"
               :hx-trigger "changed"
               :hx-get   (bidi/path-for ssr-routes/only-routes ::route/location-select)
               :hx-target "find *"
               :hx-swap  "outerHTML"}
              (location-select*
               {:name             (fc/field-name)
                :account-location (:account/location account-id)
                :client-locations (pull-attr (dc/db conn) :client/locations client-id)
                :value            location-val})))
           ;; hidden amount — full transaction total
           (fc/with-field :transaction-account/amount
             (let [total (Math/abs (or (-> request :entity :transaction/amount)
                                       (:transaction/amount snapshot)
                                       0.0))]
               (com/hidden {:name (fc/field-name) :value total})))]))
     ;; toggle link
     [:div.mt-1
      [:a.text-sm.text-blue-600.hover:underline.cursor-pointer
       {:hx-get     (bidi/path-for ssr-routes/only-routes ::route/edit-wizard-toggle-mode)
        :hx-include "closest form"
        :hx-target  "#manual-coding-section"
        :hx-swap    "outerHTML"}
       "Switch to advanced mode"]]]))
```

- [ ] **Step 2: Verify the file has no parse errors**

```bash
clj-nrepl-eval -p 9000 "(require '[auto-ap.ssr.transaction.edit] :reload)"
```

Expected: no errors.

- [ ] **Step 3: Commit**

```bash
git add src/clj/auto_ap/ssr/transaction/edit.clj
git commit -m "feat: add simple-mode-fields* for transaction edit modal"
```

---

## Task 3: Extract `manual-coding-section*` and update `LinksStep`

**Files:**
- Modify: `src/clj/auto_ap/ssr/transaction/edit.clj`

Currently the manual coding block (vendor field + account grid) is inlined inside `LinksStep/render-step`. Extract it into `manual-coding-section*` which selects mode and renders accordingly. This also adds the `mode` hidden input and wraps the section in `#manual-coding-section`.

- [ ] **Step 1: Add `manual-mode-initial` helper** (determines initial mode from snapshot)

Add this function after `simple-mode-fields*`:

```clojure
(defn- manual-mode-initial
  "Returns :simple or :advanced based on existing account row count."
  [snapshot]
  (let [rows (seq (:transaction/accounts snapshot))]
    (if (and rows (> (count rows) 1))
      :advanced
      :simple)))
```

- [ ] **Step 2: Add `manual-coding-section*`**

Add after `manual-mode-initial`:

```clojure
(defn manual-coding-section*
  "Renders the vendor field + account/location section for the manual tab.
   mode is :simple or :advanced."
  [mode request]
  (let [snapshot  (-> request :multi-form-state :snapshot)
        row-count (count (:transaction/accounts snapshot))]
    [:div#manual-coding-section
     ;; hidden mode input — carried by all hx-include=\"closest form\" calls
     (com/hidden {:name "mode" :value (name mode)})
     ;; vendor field
     [:div {:hx-trigger "change"
            :hx-post    (bidi/path-for ssr-routes/only-routes ::route/edit-vendor-changed)
            :hx-target  "#manual-coding-section"
            :hx-swap    "outerHTML"
            :hx-include "closest form"}
      (fc/with-field :transaction/vendor
        (com/validated-field
         {:label "Vendor" :errors (fc/field-errors)}
         [:div.w-96
          (com/typeahead {:name        (fc/field-name)
                          :error?      (fc/error?)
                          :class       "w-96"
                          :placeholder "Search..."
                          :url         (bidi/path-for ssr-routes/only-routes :vendor-search)
                          :value       (fc/field-value)
                          :content-fn  (fn [c] (pull-attr (dc/db conn) :vendor/name c))})]))]
     ;; account/location section
     (if (= mode :simple)
       [:div {:x-data (hx/json {:simpleAccountId
                                (-> snapshot :transaction/accounts first
                                    :transaction-account/account)})}
        (fc/start-form (:multi-form-state request) nil
                       (fc/with-field :step-params
                         (simple-mode-fields* request)))]
       ;; advanced mode
       [:div
        (when (<= row-count 1)
          [:div.mb-2
           [:a.text-sm.text-blue-600.hover:underline.cursor-pointer
            {:hx-get     (bidi/path-for ssr-routes/only-routes ::route/edit-wizard-toggle-mode)
             :hx-include "closest form"
             :hx-target  "#manual-coding-section"
             :hx-swap    "outerHTML"}
            "Switch to simple mode"]])
        (fc/start-form (:multi-form-state request) nil
                       (fc/with-field :step-params
                         (fc/with-field :transaction/accounts
                           [:div#account-grid-body
                            (account-grid-body* request)])))])]))
```

- [ ] **Step 3: Update `LinksStep/render-step` to use `manual-coding-section*`**

In `LinksStep/render-step` (around line 826), replace the entire `[:div {}` block inside `[:div {:x-show "activeForm === 'manual'" ...}]` (which currently contains the vendor typeahead + approval status + `account-grid-body*`) with:

```clojure
[:div {:x-show "activeForm === 'manual'", :x-transition:enter "transition ease-out duration-500", :x-transition:enter-start "opacity-0 transform scale-95", :x-transition:enter-end "opacity-100 transform scale-100"}
 [:div {}
  (manual-coding-section* (manual-mode-initial snapshot) request)
  ;; Approval status field
  (fc/with-field :transaction/approval-status
    (com/validated-field
     {:label "Status"
      :errors (fc/field-errors)}
     (let [current-value (name (or (fc/field-value) :transaction-approval-status/unapproved))]
       [:div {:x-data (hx/json {:approvalStatus current-value})}
        (com/hidden {:name (fc/field-name)
                     :value current-value
                     ":value" "approvalStatus"})
        [:div {:class "inline-flex rounded-md shadow-sm", :role "group"}
         (com/button-group-button {"@click" "approvalStatus = 'approved'"
                                   ":class" "{ '!bg-primary-200 text-primary-800': approvalStatus === 'approved' }"
                                   :class "rounded-l-lg"}
                                  "Approved")
         (com/button-group-button {"@click" "approvalStatus = 'unapproved'"
                                   ":class" "{ '!bg-primary-200 text-primary-800': approvalStatus === 'unapproved' }"
                                   :class "rounded-r-lg"}
                                  "Unapproved")
         (com/button-group-button {"@click" "approvalStatus = 'suppressed'"
                                   ":class" "{ '!bg-primary-200 text-primary-800': approvalStatus === 'suppressed' }"
                                   :class "rounded-r-lg"}
                                  "Client Review")]]]))]]]
```

Also remove the now-redundant `(fc/with-field :transaction/accounts ...)` wrapper that previously wrapped `account-grid-body*` (it is now handled inside `manual-coding-section*`).

- [ ] **Step 4: Verify the file compiles**

```bash
clj-nrepl-eval -p 9000 "(require '[auto-ap.ssr.transaction.edit] :reload)"
```

Expected: no errors.

- [ ] **Step 5: Commit**

```bash
git add src/clj/auto_ap/ssr/transaction/edit.clj
git commit -m "feat: extract manual-coding-section* with simple/advanced mode selection"
```

---

## Task 4: Add `edit-wizard-toggle-mode-handler`

**Files:**
- Modify: `src/clj/auto_ap/ssr/transaction/edit.clj`

This handler re-renders `#manual-coding-section` in the opposite mode. It reads `mode` from the form params (via `step-params` in the decoded multi-form-state) and flips it.

- [ ] **Step 1: Add the handler function** (add after `edit-vendor-changed-handler`):

```clojure
(defn edit-wizard-toggle-mode-handler [request]
  (let [step-params (-> request :multi-form-state :step-params)
        current-mode (keyword (or (:mode step-params) "simple"))
        target-mode  (if (= current-mode :simple) :advanced :simple)
        snapshot     (-> request :multi-form-state :snapshot)
        ;; When switching simple→advanced, promote simple-mode values into accounts
        render-request
        (if (and (= target-mode :advanced)
                 (= current-mode :simple))
          ;; carry the simple-mode single row into snapshot so the table shows it
          (let [accounts (or (seq (:transaction/accounts step-params))
                             (seq (:transaction/accounts snapshot)))]
            (-> request
                (assoc-in [:multi-form-state :snapshot :transaction/accounts]
                          (vec accounts))
                (assoc-in [:multi-form-state :step-params :transaction/accounts]
                          (vec accounts))))
          ;; advanced→simple: take first row only
          (let [first-row (first (or (seq (:transaction/accounts step-params))
                                     (seq (:transaction/accounts snapshot))))]
            (-> request
                (assoc-in [:multi-form-state :snapshot :transaction/accounts]
                          (if first-row [first-row] []))
                (assoc-in [:multi-form-state :step-params :transaction/accounts]
                          (if first-row [first-row] [])))))]
    (html-response
     (fc/start-form (:multi-form-state render-request) nil
                    (fc/with-field :step-params
                      (manual-coding-section* target-mode render-request))))))
```

- [ ] **Step 2: Register the handler in `key->handler`**

In the `key->handler` map (around line 1357), add after the `::route/edit-wizard-new-account` entry:

```clojure
::route/edit-wizard-toggle-mode (-> edit-wizard-toggle-mode-handler
                                    (mm/wrap-wizard edit-wizard)
                                    (wrap-entity [:multi-form-state :snapshot :db/id] d-transactions/default-read)
                                    (mm/wrap-decode-multi-form-state))
```

- [ ] **Step 3: Verify the file compiles**

```bash
clj-nrepl-eval -p 9000 "(require '[auto-ap.ssr.transaction.edit] :reload)"
```

Expected: no errors.

- [ ] **Step 4: Commit**

```bash
git add src/clj/auto_ap/ssr/transaction/edit.clj
git commit -m "feat: add edit-wizard-toggle-mode-handler"
```

---

## Task 5: Update `edit-vendor-changed-handler` to support both modes

**Files:**
- Modify: `src/clj/auto_ap/ssr/transaction/edit.clj`

Currently this handler always returns `[:div#account-grid-body ...]`. It must now return `#manual-coding-section` in the correct mode.

- [ ] **Step 1: Replace `edit-vendor-changed-handler`**

Replace the entire `edit-vendor-changed-handler` function body with:

```clojure
(defn edit-vendor-changed-handler [request]
  (let [multi-form-state (:multi-form-state request)
        snapshot         (:snapshot multi-form-state)
        step-params      (:step-params multi-form-state)
        mode             (keyword (or (:mode step-params) "simple"))
        client-id        (or (:transaction/client snapshot)
                             (-> request :entity :transaction/client :db/id))
        vendor-id        (or (:transaction/vendor step-params)
                             (:transaction/vendor snapshot))
        total            (Math/abs (or (-> request :entity :transaction/amount)
                                       (:transaction/amount snapshot)
                                       0.0))
        amount-mode      (or (:amount-mode snapshot) "$")
        existing-accounts (or (seq (:transaction/accounts step-params))
                               (seq (:transaction/accounts snapshot)))
        default-account  (when (and (empty? existing-accounts) vendor-id client-id)
                           (vendor-default-account vendor-id client-id))
        render-request
        (if (and (empty? existing-accounts) vendor-id client-id)
          (let [new-account (cond-> {:db/id                        (str (java.util.UUID/randomUUID))
                                     :transaction-account/location  (or (:account/location default-account) "Shared")
                                     :transaction-account/amount    (if (= amount-mode "%") 100.0 total)}
                              default-account (assoc :transaction-account/account (:db/id default-account)))]
            (-> request
                (assoc-in [:multi-form-state :snapshot :transaction/accounts] [new-account])
                (assoc-in [:multi-form-state :step-params :transaction/accounts] [new-account])))
          request)]
    (html-response
     (fc/start-form (:multi-form-state render-request) nil
                    (fc/with-field :step-params
                      (manual-coding-section* mode render-request))))))
```

Note: the `hx-target` on the vendor field in `manual-coding-section*` must point to `#manual-coding-section` (not `#account-grid-body`) — this was set correctly in Task 3.

- [ ] **Step 2: Verify the file compiles**

```bash
clj-nrepl-eval -p 9000 "(require '[auto-ap.ssr.transaction.edit] :reload)"
```

Expected: no errors.

- [ ] **Step 3: Commit**

```bash
git add src/clj/auto_ap/ssr/transaction/edit.clj
git commit -m "feat: update edit-vendor-changed-handler to support simple/advanced mode"
```

---

## Task 6: Fix `fc/with-cursor-index` usage in `simple-mode-fields*`

**Context:** The simple-mode fields need to emit form names like `step-params[transaction/accounts][0][transaction-account/account]`. The existing `fc/cursor-map` used in `account-grid-body*` handles this automatically. For the single-row simple mode we need to manually set index 0.

Look up how `fc/with-cursor-index` (or equivalent) works in `src/clj/auto_ap/ssr/form_cursor.clj` before writing the code in Task 2. If no such helper exists, use `fc/cursor-nth` or replicate the index manually via the form cursor API.

- [ ] **Step 1: Inspect the form cursor API**

```bash
clj-nrepl-eval -p 9000 "(clj-mcp.repl-tools/list-vars 'auto-ap.ssr.form-cursor)"
```

Note the available functions.

- [ ] **Step 2: Update `simple-mode-fields*` if needed**

If `fc/with-cursor-index` does not exist, replace the `fc/with-cursor-index 0` call in Task 2 with the correct form-cursor idiom. The key requirement is that the hidden `db/id`, `transaction-account/account`, `transaction-account/location`, and `transaction-account/amount` fields emit names matching index 0 of `transaction/accounts`.

A known working pattern from `account-grid-body*`:

```clojure
(fc/cursor-map #(transaction-account-row* {:value % ...}))
```

For simple mode with a single synthetic row, build a one-element vector in the snapshot and let `fc/cursor-map` iterate it — but render a flat div instead of a table. Or pass the cursor manually:

```clojure
(fc/with-field :transaction/accounts
  (let [row-cursor (fc/cursor-nth 0)]  ; adjust to actual API
    (fc/with-cursor row-cursor
      ...field rendering...)))
```

Verify field names are correct in a browser after implementation.

- [ ] **Step 3: Verify the file compiles**

```bash
clj-nrepl-eval -p 9000 "(require '[auto-ap.ssr.transaction.edit] :reload)"
```

- [ ] **Step 4: Commit if any changes were made**

```bash
git add src/clj/auto_ap/ssr/transaction/edit.clj
git commit -m "fix: correct form-cursor indexing for simple-mode account field"
```

---

## Task 7: Manual smoke test

Before writing automated tests, verify the UI works end-to-end in a browser.

- [ ] **Step 1: Start the application**

```bash
INTEGREAT_JOB="" lein run
```

- [ ] **Step 2: Open a transaction with no accounts**

Navigate to a transaction with no coded accounts. Open the edit modal. Verify it opens in simple mode with blank account and location fields.

- [ ] **Step 3: Test vendor selection in simple mode**

Select a vendor. Verify the account field is populated with the vendor's default account and the location is set appropriately.

- [ ] **Step 4: Test toggle to advanced**

Click "Switch to advanced mode". Verify the full split table appears with one pre-populated row.

- [ ] **Step 5: Test toggle back to simple**

With 1 row, click "Switch to simple mode". Verify the single account/location fields appear with that row's values.

- [ ] **Step 6: Test with a split transaction**

Open a transaction that already has 2+ accounts. Verify it opens in advanced mode. Verify the "Switch to simple mode" link is absent.

- [ ] **Step 7: Test save round-trip**

In simple mode, set a vendor, account, and location. Save. Re-open. Verify the same values are pre-populated in simple mode.

---

## Task 8: Write e2e tests

**Files:**
- Create: `test/clj/auto_ap/ssr/transaction/edit_simple_advanced_mode_test.clj`

Check how existing e2e tests are structured first:

```bash
clj-nrepl-eval -p 9000 "(clj-mcp.repl-tools/list-ns)" 
```

Look for test namespaces matching `auto-ap.ssr.transaction.*` or `auto-ap.e2e.*`. Follow the same fixture/helper patterns.

The test file should cover all 20 acceptance criteria from the spec. Group them with `testing` blocks:

```clojure
(ns auto-ap.ssr.transaction.edit-simple-advanced-mode-test
  (:require
   [clojure.test :refer [deftest is testing use-fixtures]]
   ;; ... project-specific test helpers ...
   ))

(deftest simple-advanced-mode-initial-state
  (testing "AC1: uncoded transaction opens in simple mode"
    ;; create a transaction with no accounts
    ;; open edit modal
    ;; verify #manual-coding-section has mode=simple hidden input
    ;; verify no #account-grid-body present
    (is ...))

  (testing "AC2: single-account transaction opens in simple mode with values pre-populated"
    ...)

  (testing "AC3: multi-account transaction opens in advanced mode"
    ...))

(deftest simple-mode-vendor-selection
  (testing "AC4: selecting vendor populates account and location"
    ...)
  (testing "AC5: selecting vendor does not overwrite manually chosen account"
    ...))

(deftest mode-toggle
  (testing "AC9: switching to advanced carries account/location into first row"
    ...)
  (testing "AC10: switching to advanced from blank simple gives empty table"
    ...)
  (testing "AC11: switch-to-simple link visible with 0 or 1 rows"
    ...)
  (testing "AC12: switch-to-simple link absent with 2+ rows"
    ...)
  (testing "AC13: switching to simple pre-populates from first row"
    ...))

(deftest save-round-trip
  (testing "AC6: save in simple mode persists vendor/account/location"
    ...)
  (testing "AC18: switching modes mid-edit then saving produces valid transaction"
    ...)
  (testing "AC19: split transaction re-opens in advanced mode with splits intact"
    ...)
  (testing "AC20: single-account transaction re-opens in simple mode"
    ...))
```

Fill in actual test bodies using the project's test infrastructure (browser automation or ring mock depending on what exists).

- [ ] **Step 1: Check existing test conventions**

```bash
clj-nrepl-eval -p 9000 "(require '[auto-ap.ssr.testing-conventions] :reload)"
```

Also load the testing-conventions skill for guidance:

```
Load skill: testing-conventions
```

- [ ] **Step 2: Write the test file** following project conventions

- [ ] **Step 3: Run the tests**

```bash
clj-nrepl-eval -p 9000 "(clojure.test/run-tests 'auto-ap.ssr.transaction.edit-simple-advanced-mode-test)"
```

Expected: all tests pass.

- [ ] **Step 4: Commit**

```bash
git add test/clj/auto_ap/ssr/transaction/edit_simple_advanced_mode_test.clj
git commit -m "test: add e2e acceptance tests for simple/advanced mode"
```

---

## Self-Review Checklist (completed inline)

- **Spec coverage:** All 20 ACs addressed — Tasks 2–5 implement the behaviour; Task 8 tests all 20.
- **Placeholder scan:** Task 6 and Task 8 contain some "fill in" guidance — this is intentional because they depend on runtime API discovery. The instructions tell the engineer exactly where to look and what to verify.
- **Type consistency:** `manual-coding-section*` is used consistently by `LinksStep/render-step`, `edit-vendor-changed-handler`, and `edit-wizard-toggle-mode-handler`. `#manual-coding-section` is the swap target throughout. `mode` hidden input uses `(name mode)` for string serialization and `(keyword ...)` for deserialization.
