Closes the same vanishing-buyer-notes bug B12 fixed on the process
page, this time on the order detail page that operators see first.
- Inline row indicators (MessageSquare icon for a buyer note,
Paperclip + count for per-line attachments) inside the Description
TableCell. Hidden when the line has no signal. Click toggles a
QuoteLineContext panel below the row.
- QuoteLineContext panel wrapped in a Fragment-keyed extra row,
colSpan=8 (matches the eight TableHead columns: Description, Qty,
Selling, Supplier, Cost, Total, Status, Edit). The plan called
for colSpan=9, but the Driver column was removed 2026-08-24 and
the live count is 8.
- quoteAttachments intentionally empty in the detail-page panel —
order-level attachments have their own card below the table.
Migration off pb-helpers (AGENTS.md §'data access layer discipline'):
- /api/v1/orders/{id}/quote route now treats empty quote_id as the
unlink path (clears quote + potential_quote + matching fields),
and dropped assertAgentAuth so the page can call it from a
browser session (admin auth happens via the PB session cookie at
the PocketBase layer; the MCP forwarder still works without that
gate because the MCP route gates at its own boundary).
- /api/v1/orders/{id}/quote-match is a thin wrapper around
suggestQuoteForOrder (returns the new SuggestQuoteResult shape —
best.quoteId / best.quoteNumber, not the old best.quote.id /
best.quote.quote_number).
- Page migrated four pb-helpers calls: findMatchingQuotesForOrder →
POST quote-match, applyOrderQuoteMatch → merge into quote-match +
QuoteMatchCard accepts the result via SuggestionCard from the
fetched order, clearOrderQuoteMatch → POST quote with empty
quote_id, setOrderQuoteManually → POST quote with the picked id.
- runMatch signature simplified — accept-vs-suggest was the old
split; the new endpoint only ever suggests. 'Accept' now goes
through linkQuoteManually via POST /quote.
- Stale matcher block (4 functions + 3 helpers + re-exports +
imports) deleted from pb-helpers.ts. The pure scoring helpers now
import directly from @/lib/server/order-quote-match.
Known follow-up: QuoteMatchCard still reads from matchResult (a
QuoteMatchResult shape). Clearing matchResult after runMatch means
the SuggestionCard renders empty until the next fetchOrder lands.
Reads from order.expand.potential_quote after that. A future task
can rewrite the card to drop the in-memory result entirely.
Co-Authored-By: Claude <noreply@anthropic.com>