From a6198c38321250025daad3a9657915d603036fec Mon Sep 17 00:00:00 2001 From: shad Date: Sun, 14 Jun 2026 19:30:04 +0400 Subject: [PATCH] fix(oracle-l8-r3): wizard 'Manual review' + chat cold-start seed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Close Oracle's two standing caveats so they can't gate a future loop: 1. Wizard dispatch label 'Person' was vague (Oracle: could imply chat recipient, not work-routing). Now 'Manual review' in both dropdown and preview-pane badge. dispatch_kind storage value unchanged ('human'/'agent'/'system' for EA2). 2. Chat unread cold-start. Topbar badge depended entirely on localStorage being populated by Chat scene's first visit. Fresh browser / private window / cleared storage saw 0 unread until then. Added seedHeadsIfMissing in App.tsx topbar poll: on first refresh (and only when localStorage heads count < thread count), fetches last message per thread ONCE and persists to localStorage. Subsequent ticks reuse the cache — same idle-poll budget (test still PASS). --- src/App.tsx | 24 ++++++++++++++++++++++++ src/scenes/Wizard.tsx | 4 ++-- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 4ba0359..21f5e5c 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -102,6 +102,28 @@ export default function App() { } return n; }; + // Cold-start: if localStorage heads map is empty/stale (fresh browser, + // private window, cleared site data), fetch the last message per thread + // ONCE on first mount so the badge isn't lying. Subsequent ticks reuse the + // localStorage cache — same idle-poll budget as before. + const seedHeadsIfMissing = async (threads: { _key: string }[]) => { + try { + const existing = JSON.parse(localStorage.getItem(`fm.canvas.chat.heads.${userEmail}`) || "{}"); + if (Object.keys(existing).length >= threads.length) return; + } catch { /* fall through */ } + const { chatApi } = await import("./lib/chatApi"); + const heads: Record = {}; + await Promise.all( + threads.map(async (t) => { + try { + const rows = await chatApi.listMessages(t._key); + const last = rows[rows.length - 1]; + if (last) heads[t._key] = { at: last.created_at, by: last.author_email }; + } catch { /* ignore */ } + }) + ); + try { localStorage.setItem(`fm.canvas.chat.heads.${userEmail}`, JSON.stringify(heads)); } catch { /* ignore */ } + }; const refresh = async () => { try { const { chatApi } = await import("./lib/chatApi"); @@ -111,6 +133,8 @@ export default function App() { apiMod.workItems().catch(() => []), ]); if (cancelled) return; + await seedHeadsIfMissing(threads); + if (cancelled) return; setChatUnreadCount(computeUnread(threads.map((t) => t._key))); setQueueCount(items.filter((it) => (it as any).tenant_id === tenantId).length); } catch { /* ignore */ } diff --git a/src/scenes/Wizard.tsx b/src/scenes/Wizard.tsx index 2283deb..daa524a 100644 --- a/src/scenes/Wizard.tsx +++ b/src/scenes/Wizard.tsx @@ -281,7 +281,7 @@ export default function Wizard() {
  • {i + 1} {n.display_name || "Untitled step"} - {({ human: "Person", agent: "Assistant", system: "System" } as Record)[n.dispatch_kind || "human"] || "Person"} + {({ human: "Manual review", agent: "Assistant", system: "System" } as Record)[n.dispatch_kind || "human"] || "Manual review"}
  • ))} @@ -412,7 +412,7 @@ export default function Wizard() { updateDraft({ nodes: newNodes }); }} > - +