From fb7344584de45a0ed6c964cb89431bc87bea2f31 Mon Sep 17 00:00:00 2001 From: shad Date: Sun, 14 Jun 2026 17:18:29 +0400 Subject: [PATCH] fix(approvals): friendly case title (no 32-hex business_subject leaks) Buyer-script audit showed queue row titles falling through to raw case-key hex when business_subject was empty / set to the case key itself. New friendlyCaseTitle helper: - prefer display_name if not machine-shaped - prefer business_subject if not machine-shaped - else 'Case in ' - else 'Untitled case' isMachineId catches 20+-char hex AND process_ pattern. --- qa/audit_buyer_script.mjs | 18 ++++++++++++++++-- src/scenes/Approvals.tsx | 17 ++++++++++++++++- 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/qa/audit_buyer_script.mjs b/qa/audit_buyer_script.mjs index e47150e..8229a23 100644 --- a/qa/audit_buyer_script.mjs +++ b/qa/audit_buyer_script.mjs @@ -88,11 +88,25 @@ if (await btn.count() > 0) { await p.waitForTimeout(3000); } const tail = (await p.locator("body").innerText()).toLowerCase(); - const tailFindings = FORBIDDEN_PATTERNS.filter((f) => f.re.test(tail)).map((f) => f.name).slice(0, 3); + const tailFindings = FORBIDDEN_PATTERNS.filter((f) => f.re.test(tail)).map((f) => ({ name: f.name, sample: (tail.match(f.re) || [""])[0].slice(0, 80) })).slice(0, 3); + // Find where the 32-hex actually lives — was it the case-row title, the + // active-step body, the toast, or something else? + const hex32Loc = await p.evaluate(() => { + const re = /[a-f0-9]{32}/i; + const out = []; + document.querySelectorAll("*").forEach((el) => { + if (el.children.length > 0) return; + const t = (el.textContent || "").trim(); + if (t && re.test(t)) { + out.push({ tag: el.tagName, cls: (el.className || "").toString().slice(0, 60), text: t.slice(0, 120) }); + } + }); + return out.slice(0, 5); + }); record( "buyer_approvals_action_click_no_embarrassing_copy", tailFindings.length === 0, - tailFindings.length ? `LEAK: ${tailFindings.join(", ")} (was disabled? ${isDisabled})` : `clean (disabled? ${isDisabled})` + tailFindings.length ? `LEAK [${tailFindings.map((f) => `${f.name}=${f.sample}`).join(", ")}] (disabled? ${isDisabled}) | sites: ${hex32Loc.map((h) => `${h.tag}.${h.cls}:"${h.text}"`).join(" || ")}` : `clean (disabled? ${isDisabled})` ); } diff --git a/src/scenes/Approvals.tsx b/src/scenes/Approvals.tsx index 39c3d9e..38ae71c 100644 --- a/src/scenes/Approvals.tsx +++ b/src/scenes/Approvals.tsx @@ -24,6 +24,21 @@ function ageLabel(item: WorkItem): string { return `${Math.floor(days / 30)} mo`; } +function isMachineId(s?: string | null): boolean { + if (!s) return true; + return /^[a-f0-9]{20,}$/i.test(s) || /^process_\d{10,}$/.test(s); +} + +function friendlyCaseTitle(item: WorkItem): string { + const display = (item as any).display_name as string | undefined; + const subject = item.business_subject; + if (display && !isMachineId(display)) return display; + if (subject && !isMachineId(subject)) return subject; + const stepName = item.active_step_display_name || item.next_action; + if (stepName) return `Case in ${stepName}`; + return "Untitled case"; +} + export default function Approvals() { const userEmail = useApp((s) => s.userEmail); const actor = useApp((s) => s.actor); @@ -145,7 +160,7 @@ export default function Approvals() { onClick={() => setActiveKey(it.transaction_id)} >
- {(it as any).display_name || it.business_subject || "Untitled case"} + {friendlyCaseTitle(it)} {it.age_label}