fix(oracle-r4-caveats): wizard rewires presentation edges, chat QA proves, Pi leak

Three Oracle round-7 caveats:

1. Wizard view replacement now deletes old presentation edges before
   creating new field-based ones. Previously handleDataSave added new
   edges additively, so runtime could still hit the generic Notes view.
2. Chat sidebar QA assertion was vacuously true (previewRows >= 0 &&
   timeBadges >= 0). Now: when threads exist, preview count must equal
   thread count AND at least one relative timestamp must render.
3. Agent composer placeholder rebranded from 'Ask Pi...' to 'Ask the
   assistant...'. Closes residual Pi/LLM framing leak.
This commit is contained in:
2026-06-14 15:14:59 +04:00
parent bfac76dcde
commit 05be9ca8dc
3 changed files with 30 additions and 5 deletions
+8 -1
View File
@@ -236,9 +236,16 @@ record("llm_unconfigured_falls_back_gracefully", unmatchedReply.length > 0 && !/
// other personas have written since last open. // other personas have written since last open.
await p.locator(".tab", { hasText: /^\s*Chat\s*$/ }).click(); await p.locator(".tab", { hasText: /^\s*Chat\s*$/ }).click();
await p.waitForTimeout(2500); await p.waitForTimeout(2500);
await p.waitForTimeout(2000);
const threadRowCount = await p.locator(".chat-thread-row").count();
const previewRows = await p.locator(".chat-thread-meta").count(); const previewRows = await p.locator(".chat-thread-meta").count();
const timeBadges = await p.locator(".chat-thread-time").count(); const timeBadges = await p.locator(".chat-thread-time").count();
record("chat_sidebar_shows_previews_and_timestamps", previewRows >= 0 && timeBadges >= 0, `previews=${previewRows}, times=${timeBadges}`); const sidebarHonest = threadRowCount === 0 || (previewRows === threadRowCount && timeBadges >= 1);
record(
"chat_sidebar_shows_previews_and_timestamps",
sidebarHonest,
`threads=${threadRowCount}, previews=${previewRows}, times=${timeBadges}`
);
// Summarise // Summarise
const fails = results.filter((r) => !r.ok); const fails = results.filter((r) => !r.ok);
+1 -1
View File
@@ -110,7 +110,7 @@ export default function Agent() {
submit(draft); submit(draft);
} }
}} }}
placeholder="Ask Pi to navigate, start a process, or message a teammate. Enter to send." placeholder="Ask the assistant to navigate, start a process, or message a teammate. Enter to send."
rows={2} rows={2}
disabled={working} disabled={working}
/> />
+21 -3
View File
@@ -149,14 +149,32 @@ export default function Wizard() {
}); });
if (draft.fields.length > 0 && draft.nodes.length > 0) { if (draft.fields.length > 0 && draft.nodes.length > 0) {
const token = sessionStorage.getItem("fm.mc.token.v1") || "";
const oldEdgeKeys: string[] = [];
for (const n of draft.nodes) {
try {
const r = await fetch(
`${(await import("../lib/api")).api.config.baseUrl}/api/ea2/edges/defines?from=flow/${n._key}&limit=50`,
{ headers: { Authorization: `Bearer ${token}` } },
);
if (!r.ok) continue;
const items = ((await r.json())?.items || []) as any[];
for (const e of items) {
if (e?.role === "presentation" && e?._key) oldEdgeKeys.push(e._key);
}
} catch { /* ignore */ }
}
const replaceOps: BatchOperation[] = draft.nodes.map(n => const replaceOps: BatchOperation[] = draft.nodes.map(n =>
wizardApi.ops.createView({ display_name: n.display_name, fields: draft.fields }) wizardApi.ops.createView({ display_name: n.display_name, fields: draft.fields })
); );
const res = await wizardApi.applyBatch(draft.flowKey, replaceOps, actor); const res = await wizardApi.applyBatch(draft.flowKey, replaceOps, actor);
const newViewKeys = (res?.result?.ops || []).map((o: any) => o.key); const newViewKeys = (res?.result?.ops || []).map((o: any) => o.key);
const wireOps: BatchOperation[] = draft.nodes.map((n, i) =>
wizardApi.ops.presentationEdge(n._key, newViewKeys[i]) const wireOps: BatchOperation[] = [
); ...oldEdgeKeys.map((k): BatchOperation => ({ op: "delete_edge", edge_coll: "defines", key: k })),
...draft.nodes.map((n, i) => wizardApi.ops.presentationEdge(n._key, newViewKeys[i])),
];
if (wireOps.length) await wizardApi.applyBatch(draft.flowKey, wireOps, actor); if (wireOps.length) await wizardApi.applyBatch(draft.flowKey, wireOps, actor);
updateDraft({ nodes: draft.nodes.map((n, i) => ({ ...n, viewKey: newViewKeys[i] })) }); updateDraft({ nodes: draft.nodes.map((n, i) => ({ ...n, viewKey: newViewKeys[i] })) });
} }