Dev Toolbar Demo Pages Implementation Plan

For Claude: REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.

Goal: Move prototype demo pages from prototype-march into the dev toolbar system with nav injection and page toggles — zero production code changes.

Architecture: Demo pages live in dev/dev/getorcha/demo/ with dev.getorcha.demo.* namespaces. toolbar.clj requires them, registers routes under /dev/demo/..., owns a page-activation-state atom, and the toolbar middleware injects nav <li> items into the sidebar HTML via string replacement. The expanded toolbar panel gains toggle checkboxes for each demo page.

Tech Stack: Clojure, Reitit, Hiccup2, HTMX (for toggles), Ring.

Scope: This plan ports the 5 self-contained pages (controlling, payroll, tax-filing, monthly-closing, supplier-communication). The approvals and purchase-request pages have deep dependencies (approvals.core, slack-integration.*, openrouter) that only exist on prototype-march — they're out of scope and should be ported separately with their dependency trees.


Task 1: Port controlling page

Files:

Step 1: Extract file from prototype-march

git show origin/prototype-march:orcha/src/com/getorcha/app/http/controlling.clj > dev/dev/getorcha/demo/controlling.clj

Step 2: Update namespace and route path

Change the namespace declaration:

;; Old:
(ns com.getorcha.app.http.controlling ...)
;; New:
(ns dev.getorcha.demo.controlling ...)

Change the route path prefix:

;; Old:
["/controlling"
;; New:
["/demo/controlling"

The routes function signature stays the same: (defn routes [_config] ...).

Step 3: Verify it compiles

clj-kondo --lint dev/dev/getorcha/demo/controlling.clj

Step 4: Commit

git add dev/dev/getorcha/demo/controlling.clj
git commit -m "feat(demo): port controlling page from prototype-march"

Task 2: Port payroll page

Same pattern as Task 1.

Files:

Step 1: Extract and rename

git show origin/prototype-march:orcha/src/com/getorcha/app/http/payroll.clj > dev/dev/getorcha/demo/payroll.clj

Step 2: Update namespace and route path

Step 3: Lint

clj-kondo --lint dev/dev/getorcha/demo/payroll.clj

Step 4: Commit

git add dev/dev/getorcha/demo/payroll.clj
git commit -m "feat(demo): port payroll page from prototype-march"

Task 3: Port tax-filing page

Same pattern. This is the largest page (3085 lines).

Files:

Step 1: Extract and rename

git show origin/prototype-march:orcha/src/com/getorcha/app/http/tax_filing.clj > dev/dev/getorcha/demo/tax_filing.clj

Step 2: Update namespace and route path

Step 3: Lint and commit


Task 4: Port monthly-closing page

Files:

Step 1: Extract and rename

git show origin/prototype-march:orcha/src/com/getorcha/app/http/monthly_closing.clj > dev/dev/getorcha/demo/monthly_closing.clj

Step 2: Update namespace and route path

Step 3: Lint and commit


Task 5: Port supplier-communication page

Files:

Step 1: Extract and rename

git show origin/prototype-march:orcha/src/com/getorcha/app/http/supplier_communication.clj > dev/dev/getorcha/demo/supplier_communication.clj

Step 2: Update namespace and route path

Step 3: Lint and commit


Task 6: Add demo page registry and nav injection to toolbar

This is the core integration task. Modify dev/dev/getorcha/toolbar.clj.

Files:

Step 1: Add requires for demo namespaces

Add to the :require block:

[dev.getorcha.demo.controlling :as demo.controlling]
[dev.getorcha.demo.monthly-closing :as demo.monthly-closing]
[dev.getorcha.demo.payroll :as demo.payroll]
[dev.getorcha.demo.supplier-communication :as demo.supplier-communication]
[dev.getorcha.demo.tax-filing :as demo.tax-filing]

Step 2: Add page-activation-state atom and demo-pages config

After the list-snapshots function, add:

;; ---------------------------------------------------------------------------
;; Demo page activation
;; ---------------------------------------------------------------------------

(def ^:private demo-pages
  "Demo page definitions: key, label, icon, path, and routes function."
  [{:key :controlling    :label "Pre-Accounting"            :icon "lucide:pie-chart"      :path "/dev/demo/controlling"}
   {:key :payroll        :label "Payroll"                   :icon "lucide:wallet"         :path "/dev/demo/payroll"}
   {:key :tax-filing     :label "Tax Filing"                :icon "lucide:landmark"       :path "/dev/demo/tax"}
   {:key :monthly-closing :label "Monthly Closing"          :icon "lucide:calendar-check" :path "/dev/demo/monthly-closing"}
   {:key :supplier-comm  :label "Supplier Communication"    :icon "lucide:mail"           :path "/dev/demo/supplier-communication"}])


(defonce page-activation-state
  (atom (into {} (map (fn [{:keys [key]}] [key true]) demo-pages))))

Use defonce so the atom survives namespace reloads.

Step 3: Add render-demo-nav-items function

This renders the <li> elements to inject into the sidebar:

(defn ^:private render-demo-nav-items
  "Renders demo page nav items as an HTML string for sidebar injection."
  []
  (let [active-pages (filter #(get @page-activation-state (:key %)) demo-pages)]
    (when (seq active-pages)
      (str (hiccup/html
            [:li {:style "padding:6px 16px;margin-top:8px"}
             [:span {:style "color:#484f58;font-size:11px;text-transform:uppercase;letter-spacing:0.5px"} "Demo"]]
            (for [{:keys [label icon path]} active-pages]
              [:li [:a {:href path :title label}
                    [:iconify-icon {:icon icon}]
                    [:span label]]]))))))

Step 4: Update middleware to inject nav items

In wrap-dev-toolbar, after the toolbar injection, also inject nav items into the sidebar. Change the body replacement in the middleware's respond callback:

;; In the (if (instance? RawString (:body response)) ...) branch:
(let [html-str     (str (:body response))
      toolbar-str  (render-toolbar elapsed-ms metrics request')
      nav-str      (render-demo-nav-items)
      injected     (cond-> html-str
                     nav-str     (str/replace "</ul>\n</nav>" (str nav-str "</ul>\n</nav>"))
                     true        (str/replace "</body>" (str toolbar-str "</body>")))]
  (assoc response :body injected))

The injection target is </ul> that closes the .nav-items list, just before </nav>. Check the actual rendered HTML to find the exact string — it may be </ul></nav> without newline. Use the REPL to render a page and inspect the HTML around the nav closing tags:

;; In REPL: render a page and search for the nav closing pattern
(let [html (str (layout/base {} {:title "test"} [:p "x"]))]
  (re-find #"</ul>.*?</nav>" html))

Use whatever string the REPL returns as the injection anchor.

Step 5: Lint

clj-kondo --lint dev/dev/getorcha/toolbar.clj

Step 6: Commit

git add dev/dev/getorcha/toolbar.clj
git commit -m "feat(dev-toolbar): add demo page nav injection and activation state"

Task 7: Add demo page toggle route and toolbar UI

Files:

Step 1: Add toggle handler

(defn ^:private handle-toggle-page
  "Toggles a demo page's visibility in the sidebar."
  [request respond _raise]
  (let [page-key (keyword (get-in request [:form-params "page"]))]
    (swap! page-activation-state update page-key not)
    (let [referer (get-in request [:headers "referer"] "/ap")]
      (respond (ring.resp/found referer)))))

Step 2: Add demo toggles to expanded toolbar panel

In render-toolbar, add a "Demo" section to the expanded panel, after the snapshot/route-actions area. Add toggle checkboxes that POST to /dev/demo/toggle:

;; Inside the expanded panel div, after the metrics/actions row:
[:div {:style "display:flex;gap:8px;flex-wrap:wrap;margin-top:8px;padding-top:8px;border-top:1px solid #21262d"}
 [:span {:style "color:#484f58;font-size:11px;margin-right:4px"} "DEMO"]
 (for [{:keys [key label]} demo-pages]
   (let [active? (get @page-activation-state key)]
     [:form {:method "post" :action "/dev/demo/toggle" :style "display:inline"}
      [:input {:type "hidden" :name "page" :value (name key)}]
      [:button {:type "submit"
                :style (str "background:" (if active? "#238636" "#21262d")
                            ";color:#c9d1d9;border:1px solid #30363d;padding:1px 6px;border-radius:3px;cursor:pointer;font:11px monospace")}
       label]]))]

Step 3: Register demo routes and toggle route

Update the routes function to include the toggle endpoint and all demo page routes:

(defn routes
  "Dev-only routes for snapshot/restore/delete/demo actions."
  [config]
  ["/dev" {:middleware [(app.http.middleware.auth/wrap-authentication)]}
   ["/snapshot"
    ...]
   ["/documents/:document-id/delete"
    ...]
   ["/demo/toggle"
    {:name ::toggle-demo-page
     :post {:handler #'handle-toggle-page}}]
   (demo.controlling/routes config)
   (demo.payroll/routes config)
   (demo.tax-filing/routes config)
   (demo.monthly-closing/routes config)
   (demo.supplier-communication/routes config)])

Note: the routes function parameter changes from _config to config since it's now passed to demo route functions.

Step 4: Lint

clj-kondo --lint dev/dev/getorcha/toolbar.clj

Step 5: Commit

git add dev/dev/getorcha/toolbar.clj
git commit -m "feat(dev-toolbar): add demo page toggles and route registration"

Task 8: Test the full flow

Step 1: Reload the system

In the REPL, run (reset) to pick up the new routes and middleware. This will take ~10 seconds.

Step 2: Verify demo pages load

Open in browser:

Step 3: Verify nav injection

On any app page (e.g., /ap), the sidebar should show a "Demo" section with nav items for all 5 demo pages below the regular nav items.

Step 4: Verify toggle controls

Click the toolbar bubble → expanded panel should show "DEMO" row with green/gray toggle buttons for each page. Click a toggle → page reloads → that nav item disappears/reappears from the sidebar.

Step 5: Verify nav item links work

Click a demo nav item in the sidebar → navigates to the demo page → page renders correctly with the full layout.

Step 6: Commit any fixes

If any adjustments were needed during testing, commit them.


Out of Scope (future tasks)