Display-name audit follow-up — vendor template clean-up + live-sync templates to transactions
Renumbered T-115 → T-118 (2026-06-25, renumber-on-merge). While this task was in flight on
claude/busy-dirac-QmUdM(PR #808), a parallel agent landed T-115 (Records — Receipt Register XLSX Substantiating Notes column) on main first. Per the AGENTS.md "whoever merges second renumbers" rule, this task moved to T-118 at merge time; the in-codeT-115comments were updated toT-118to match. Commit messages stay historicalT-115. Sibling T-114 → T-117.
Why (owner, 2026-06-25)¶
Continuation of the display-name audit that started in T-117. After the receipt-token loader closed the gap on receipt-matched txs, the owner asked for the same audit pass across every doc-type template AND for template edits in Settings → Routing to reflect immediately on a transaction (no page refresh).
Two pieces ship together:
-
Vendor-invoice template clean-up. The configured
vendor_invoicedoc-type template was:[{{accountCode}} - {{accountName}}] {{merchantCategory}} | {{merchant}} // {{invoiceNumber}}{{merchantCategory}}had no populator — vendor invoices don't carry a granular AI category like receipts do (the receipt token loader exists because receipts haveparsedReceipt.category; vendor docs have nothing equivalent). Token rendered blank, leading to a|floating with nothing left of it. Owner picked option A — drop the token from the template — rather than build a vendor-side category source. -
Live-sync templates → transactions invalidation. Display names are computed at READ time (never cached on the tx doc), so a template edit only needs to trigger a transactions refetch on the client for the new name to surface immediately. The existing
useAccountingLiveSyncalready invalidates transactions on COA changes (GL-level fallback templates are stored on eachaccounting/accounts/gl/{code}doc) — but doc-type templates live underaccounting/settings/docTypes/{recordType}, a separate path nothing was listening on. Edits sat unrendered until the next organic refetch.
What shipped¶
accounting/settings/docTypes/vendor_invoice(Firestore one-off):displayNameTemplateset to[{{accountCode}} - {{accountName}}] {{merchant}} // {{invoiceNumber}}. Audit-log marker on the updatedBy field references T-118.lib/firestore/useAccountingLiveSync.ts— added a thirduseEffectthat subscribes toaccounting/settings/docTypes(collection snapshot via the sharedfirestoreSubscriptionManager). On any change: invalidateaccountingKeys.transactions.all,accountingKeys.journalsAll(), and['accounting', 'reports']— the same cascade the COA listener fires. SameisWithinLocalMutationCooldowngate so an admin's own template save doesn't kick a redundant fetch mid-optimistic-update.
Audit summary (other doc-type / GL templates surveyed, no changes needed)¶
Cross-checked every configured template against what
enrichTransactionDisplayName actually populates per linkedRecordType:
| Doc-type template | Tokens it needs | Populator | Verdict |
|---|---|---|---|
receipt |
merchant, merchantCategory | getReceiptDisplayTokens (T-117) |
✅ |
vendor_invoice |
merchant, invoiceNumber | expenseMetadata.* from vendor matcher |
✅ (after T-118 drop of {{merchantCategory}}) |
workspace_invoice |
merchant, billingMonth, invoiceNumber | getBillingDisplayTokens |
✅ |
gcp_invoice |
gcpServices, gcpBillingMonth, gcpInvoiceNumber | getBillingDisplayTokens |
✅ |
cross_border_fee |
merchant, billingMonth, invoiceNumber | getCrossBorderFeeTokens |
✅ |
project_invoice |
clientCompany, workType, projectTitle, invoiceNumber | matched-invoice branch | ✅ |
coaching_invoice |
studentName, sessionOrdinal, sessionDate | matched-coaching branch | ✅ |
wopc_project |
workType, projectTitle, invoiceNumber, payee, wopcRef | matched-invoice + wopcReferenceNumber |
likely ✅ (no live sample to render against) |
wopc_coaching |
studentName, sessionOrdinal, invoiceNumber, payee, wopcRef | coaching match + WOPC ref | likely ✅ (no live sample) |
15/43 GL fallback templates were also surveyed; the {{subscription}}
token referenced by some (5000 / 6000 / 6006) has no current populator
either — but those templates only fire when no record is linked
(extremely rare for software/IT GLs which almost always have a vendor
invoice match), so the surfaced behaviour is acceptable until that
becomes a real issue.
Out of scope¶
- Vendor-side category populator. Could mirror T-117's receipt loader
by reading the vendor's
categoryfield fromaccounting/vendors/{id}— but vendors don't currently carry an AI-derived category, so the field would need to be human-curated. Worth opening if granular vendor P&L slicing becomes a desired UX; today the GL itself is the granular unit. {{subscription}}token populator for 5000/6000/6006 GL fallbacks. Same reasoning — the fallback path is rare (matched txs go through doc-type templates) and the token has no obvious source today.
Decision log¶
2026-06-25 — T-118 opened + shipped¶
- ✅ Attestation (Accounting (Diagnostics)): read
AGENTS.md(tip26330aac); board scanned by scope — T-117 just closed (receipt token loader); no overlap with this audit follow-up. - Source: Accounting (Diagnostics) · https://claude.ai/code/session_01G58Y71noihrYCDEDMexmea
- Owner direction (verbatim, 2026-06-25):
"Can you help me run thru other GL accounts too (Especially those that you've touched/ has to do with the receipts/invoice auto-matching)?" "Okay, A. wire the live-sync for templates → transactions invalidation"
Owner picked option A (drop {{merchantCategory}} from the vendor
template) over option B (build a vendor-side category source) after I
laid out the trade-off — option A is the proportional fix; the granular
vendor category UX can come later if it becomes a real ask.
- Tests + tsc: npx tsc --noEmit clean.
- Live verification: vendor template Firestore doc rewritten and
re-read shows the new value; no live vendor-matched tx in prod today
to render against (audit found 0 across all non-receipt linked types).
- Blast radius: the new live-sync listener fires per snapshot on the
docTypes subcollection — at most 9 docs currently, edits are rare
(admin-only). Invalidation cascade is identical to the COA listener
already in place, so any cooldown / refetch behaviour is consistent.
Vendor template change is a single-field merge on one Firestore doc;
next read picks up the new template (server cache TTL 60s).