For Claude: REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
Goal: Surface document matches on the detail page so users can navigate between matched documents.
Architecture: Fetch matches in the existing get-document handler via a new DB query that joins document_match with document to get matched document metadata. Pass matches through opts to the renderer. Add a "Matches" tab and collapsible section at the top of the content area, conditionally rendered only when matches exist.
Tech Stack: Clojure, HoneySQL, Hiccup, HTMX (existing patterns)
Files:
src/com/getorcha/db/document_matching.clj:60-70The existing get-matches-for-document selects only from document_match and orders by a nonexistent :confidence column. Replace it with a query that joins the matched document to return its type, structured-data, and file-original-name.
Step 1: Replace get-matches-for-document
The query must handle canonical ordering (a < b) — the current document could be either document_a_id or document_b_id. Use a UNION approach via two queries combined, or use a CASE expression. The simplest approach: query both sides with a union-style OR, then use a subselect/join to get the other document's info.
Replace lines 60-70 in document_matching.clj:
(defn get-matched-documents
"Get documents matched to `document-id`, with their type and structured data
for display. Returns document rows for the *other* side of each match edge."
[db document-id]
(db.sql/execute! db
{:union-all
[{:select [[:matched.id :document/id]
[:matched.type :document/type]
[:matched.structured-data :document/structured-data]
[:matched.file-original-name :document/file-original-name]
[:document-match.blended-score :document-match/blended-score]]
:from [:document-match]
:join [[:document :matched] [:= :matched.id :document-match.document-b-id]]
:where [:= :document-match.document-a-id document-id]}
{:select [[:matched.id :document/id]
[:matched.type :document/type]
[:matched.structured-data :document/structured-data]
[:matched.file-original-name :document/file-original-name]
[:document-match.blended-score :document-match/blended-score]]
:from [:document-match]
:join [[:document :matched] [:= :matched.id :document-match.document-a-id]]
:where [:= :document-match.document-b-id document-id]}]}))
Step 2: Verify in REPL
(require '[com.getorcha.db.document-matching :as db.matching] :reload)
(def db (:com.getorcha.db/pool integrant.repl.state/system))
(db.matching/get-matched-documents db (parse-uuid "019c9dfe-af27-70f6-909d-2f649b13ef5e"))
;; Expected: vector with one map containing :document/id, :document/type, :document/structured-data, :document/file-original-name, :document-match/blended-score
Step 3: Commit
git add src/com/getorcha/db/document_matching.clj
git commit -m "feat(matching): add get-matched-documents query with document metadata"
Files:
src/com/getorcha/erp/http/documents/view.clj:1-204Step 1: Add require
Add [com.getorcha.db.document-matching :as db.matching] to the ns requires (alphabetically ordered).
Step 2: Fetch matches in handler
Inside the let block of get-document (after the supplier-verification binding around line 187), add:
matches (db.matching/get-matched-documents db-pool (:document/id document))
Step 3: Pass matches in opts
Add :matches matches to the opts map passed to view.shared/detail-page (around line 198-204):
{:tenants tenants
:current-tenant-name current-tenant-name
:is-super-admin? is-super-admin?
:in-qa-dataset? in-qa-dataset?
:has-datev-connection? has-datev-connection?
:export-audit export-audit
:list-state list-state
:excel-preview excel-preview
:supplier-verification supplier-verification
:matches matches}
Step 4: Commit
git add src/com/getorcha/erp/http/documents/view.clj
git commit -m "feat(matching): fetch matches in document detail handler"
Files:
src/com/getorcha/erp/http/documents/view/shared.cljThe matches must flow through: detail-page → detail-area-content → detail-page-content.
Step 1: Destructure matches in detail-page
In detail-page (line 480), add :matches to the opts destructuring:
(let [{:keys [tenants current-tenant-name is-super-admin? in-qa-dataset?
has-datev-connection? export-audit list-state excel-preview supplier-verification matches]} opts
And pass it through to detail-area-content — add :matches matches to the opts map at line 525:
(detail-area-content router document {:is-super-admin? is-super-admin?
:in-qa-dataset? in-qa-dataset?
:has-datev-connection? has-datev-connection?
:export-audit export-audit
:awaiting-auto-export? false
:excel-preview excel-preview
:supplier-verification supplier-verification
:matches matches})
Step 2: Commit
git add src/com/getorcha/erp/http/documents/view/shared.clj
git commit -m "feat(matching): thread matches through rendering pipeline"
Files:
src/com/getorcha/erp/http/documents/view/shared.cljStep 1: Add matches parameter to section-tabs-for-type
Change the signature from:
(defn ^:private section-tabs-for-type
"Returns section navigation tabs appropriate for the document type."
[doc-type structured-data has-datev-connection?]
To:
(defn ^:private section-tabs-for-type
"Returns section navigation tabs appropriate for the document type."
[doc-type structured-data has-datev-connection? matches]
At the end of each case branch's (list ...), append:
(when (seq matches)
(tab "section-matches" "Matches"))
For the :invoice branch (line 69-78), change to:
:invoice
(list
(when (seq matches)
(tab "section-matches" "Matches"))
(when has-datev-connection?
(tab "section-datev-export" "Export"))
(tab "section-invoice-details" "Details")
(tab "section-validation" "Validation")
(when (or (:shipping-address structured-data) (:incoterm-code structured-data))
(tab "section-delivery" "Delivery"))
(when (:line-items structured-data)
(tab "section-line-items" "Items"))
(tab "section-payment-summary" "Payment"))
Apply the same pattern for :contract, :purchase-order, and :goods-received-note — add the matches tab as the first item in each list.
Step 2: Update the call site in detail-page-content
Around line 286, the call is:
(when-let [tabs (section-tabs-for-type doc-type structured-data has-datev-connection?)]
The opts map is already destructured at the top of this function. Add :matches to the destructuring of opts (around line 158):
{:keys [is-super-admin? in-qa-dataset? has-datev-connection? export-audit awaiting-auto-export? excel-preview supplier-verification matches]
:as _opts}
Then update the call:
(when-let [tabs (section-tabs-for-type doc-type structured-data has-datev-connection? matches)]
Step 3: Commit
git add src/com/getorcha/erp/http/documents/view/shared.clj
git commit -m "feat(matching): add Matches tab to section navigation"
Files:
src/com/getorcha/erp/http/documents/view/shared.cljStep 1: Add the display-name helper and matches section
Add a require for com.getorcha.erp.http.documents.shared (it's already required as shared). Add a require for com.getorcha.erp.http.routes (already required as erp.http.routes).
Add this function before detail-page-content:
(defn ^:private match-display-name
"Extracts a human-readable display name from a matched document."
[{:document/keys [type structured-data file-original-name] :as _matched-doc}]
(let [doc-type (keyword type)]
(case doc-type
:invoice (or (:invoice-number structured-data)
(get-in structured-data [:issuer :name])
file-original-name
"Invoice")
:contract (or (:title structured-data)
(:contract-number structured-data)
file-original-name
"Contract")
:purchase-order (or (:po-number structured-data)
file-original-name
"Purchase Order")
:goods-received-note (or (:grn-number structured-data)
file-original-name
"GRN")
:financial-notice (or (some-> (:notice-type structured-data)
(string/replace "-" " ")
string/capitalize)
file-original-name
"Notice")
(or file-original-name "Document"))))
(defn ^:private matches-section
"Renders the document matches section with cards linking to matched documents."
[router matches]
(erp.ui.components/collapsible-section
"Matches"
"section-matches"
[:div.matches-list
(for [{:document/keys [id type] :as matched-doc} matches]
(let [doc-type (keyword type)
type-label (get shared/management-type-labels (name doc-type)
(case doc-type
:invoice "Invoice"
:financial-notice "Notice"
(string/capitalize (name doc-type))))
view-url (erp.http.routes/path-for router :com.getorcha.erp.http.documents.view/detail {:document-id id})]
[:a.match-card {:href view-url}
[:span {:class (str "badge badge-" (name doc-type))} type-label]
[:span.match-title (match-display-name matched-doc)]
[:iconify-icon {:icon "lucide:arrow-up-right" :style "font-size: 14px;"}]]))]))
Step 2: Render matches section in detail-page-content
In the .panel-body section (around line 289-305), insert the matches section before type-specific-view. Change:
[:div.panel-body
(if processing?
;; Processing state
(erp.ui.components/spinner "Processing document...")
;; Extracted data display
(if structured-data
(or (type-specific-view ...)
[:div.empty-state
[:p "Unknown document type"]])
[:div.empty-state
[:p "No data extracted yet."]]))]
To:
[:div.panel-body
(if processing?
;; Processing state
(erp.ui.components/spinner "Processing document...")
;; Extracted data display
(list
(when (seq matches)
(matches-section router matches))
(if structured-data
(or (type-specific-view ...)
[:div.empty-state
[:p "Unknown document type"]])
[:div.empty-state
[:p "No data extracted yet."]])))]
Step 3: Commit
git add src/com/getorcha/erp/http/documents/view/shared.clj
git commit -m "feat(matching): render matches section on document detail page"
Files:
resources/public/css/ or similar)Step 1: Find the CSS file
grep -rl "collapsible-section" resources/public/
or
find resources/public -name "*.css" | head -20
Step 2: Add match card styles
/* Document Matches */
.matches-list {
display: flex;
flex-direction: column;
gap: 8px;
}
.match-card {
display: flex;
align-items: center;
gap: 10px;
padding: 10px 14px;
background: var(--bg-secondary, #f6f8fa);
border: 1px solid var(--border-color, #d0d7de);
border-radius: 6px;
text-decoration: none;
color: inherit;
transition: background-color 0.15s;
}
.match-card:hover {
background: var(--bg-hover, #eaeef2);
}
.match-card .match-title {
flex: 1;
font-weight: 500;
}
.match-card iconify-icon {
color: var(--text-secondary, #656d76);
}
Step 3: Commit
git add <css-file>
git commit -m "feat(matching): add match card CSS styles"
Step 1: Reset system and test
Reload the system to pick up all changes:
(integrant.repl/reset)
Step 2: Navigate to the test invoice
Open the browser and navigate to:
/documents/view/019c9dfe-af27-70f6-909d-2f649b13ef5e
Expected:
Step 3: Test a document with no matches
Navigate to any document that has no matches (check with a quick DB query).
Expected:
Step 4: Commit and lint
clj-kondo --lint src test dev --fail-level warning