fix(oracle-l8-r3): wizard 'Manual review' + chat cold-start seed
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).
This commit is contained in:
+24
@@ -102,6 +102,28 @@ export default function App() {
|
|||||||
}
|
}
|
||||||
return n;
|
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<string, { at: string; by: string }> = {};
|
||||||
|
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 () => {
|
const refresh = async () => {
|
||||||
try {
|
try {
|
||||||
const { chatApi } = await import("./lib/chatApi");
|
const { chatApi } = await import("./lib/chatApi");
|
||||||
@@ -111,6 +133,8 @@ export default function App() {
|
|||||||
apiMod.workItems().catch(() => []),
|
apiMod.workItems().catch(() => []),
|
||||||
]);
|
]);
|
||||||
if (cancelled) return;
|
if (cancelled) return;
|
||||||
|
await seedHeadsIfMissing(threads);
|
||||||
|
if (cancelled) return;
|
||||||
setChatUnreadCount(computeUnread(threads.map((t) => t._key)));
|
setChatUnreadCount(computeUnread(threads.map((t) => t._key)));
|
||||||
setQueueCount(items.filter((it) => (it as any).tenant_id === tenantId).length);
|
setQueueCount(items.filter((it) => (it as any).tenant_id === tenantId).length);
|
||||||
} catch { /* ignore */ }
|
} catch { /* ignore */ }
|
||||||
|
|||||||
@@ -281,7 +281,7 @@ export default function Wizard() {
|
|||||||
<li key={n._key || i} className="wizard-preview-chain-item">
|
<li key={n._key || i} className="wizard-preview-chain-item">
|
||||||
<span className="wizard-preview-chain-idx">{i + 1}</span>
|
<span className="wizard-preview-chain-idx">{i + 1}</span>
|
||||||
<span className="wizard-preview-chain-name">{n.display_name || "Untitled step"}</span>
|
<span className="wizard-preview-chain-name">{n.display_name || "Untitled step"}</span>
|
||||||
<span className={`wizard-preview-chain-kind kind-${n.dispatch_kind || "human"}`}>{({ human: "Person", agent: "Assistant", system: "System" } as Record<string, string>)[n.dispatch_kind || "human"] || "Person"}</span>
|
<span className={`wizard-preview-chain-kind kind-${n.dispatch_kind || "human"}`}>{({ human: "Manual review", agent: "Assistant", system: "System" } as Record<string, string>)[n.dispatch_kind || "human"] || "Manual review"}</span>
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ol>
|
</ol>
|
||||||
@@ -412,7 +412,7 @@ export default function Wizard() {
|
|||||||
updateDraft({ nodes: newNodes });
|
updateDraft({ nodes: newNodes });
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<option value="human">Person</option>
|
<option value="human">Manual review</option>
|
||||||
<option value="agent">Assistant</option>
|
<option value="agent">Assistant</option>
|
||||||
<option value="system">System</option>
|
<option value="system">System</option>
|
||||||
</select>
|
</select>
|
||||||
|
|||||||
Reference in New Issue
Block a user