Files
canvas-bot 9502e36c32
build-and-publish / test (push) Has been cancelled
build-and-publish / image (push) Has been cancelled
fix(wave3): wizard config soft-warn, llm telemetry, catalog dedupe, mission primer
Oracle's wave-2 watch-outs:

1. Wizard config persistence — the follow-up PUT was silently swallowed.
   Empirical curl reproducer confirmed config.wizard DOES land after the
   split create/PUT (config.wizard.marker=EA2_DRAFT_PROCESS visible on
   re-GET). But to satisfy the 'no silent loss' concern: the catch now
   warns to console AND surfaces a soft toast 'Draft saved, but its
   wizard state didn't attach. You can keep working — save will retry
   on Confirm.' so failure is loud without blocking the user.

2. LLM telemetry — fallback was masking infra health. llmClient now
   logs every outcome with elapsed time: 503/404/502/504/non-2xx/parse
   failures all console.warn with reason + ms. Success path
   console.info with provider + content length. Friendly fallback to
   the user stays the same; ops/devs see the real story.

3. Catalog hygiene — duplicate 'Laptop Procurement' rows. list_processes
   now dedupes by normalized display_name after the existing _key
   dedupe. EA2 seeds + tenant imports both publishing the same name
   collapse to one row in the assistant output.

4. Mission intuitiveness — user said 'I don't understand anything that's
   going on there.' Added a one-line first-visit primer strip
   ('What you're looking at. Each tab below is a process running in
   your company...') dismissible with an X, sticky to localStorage.
   Also fixed the stale 'showing snapshot' fallback copy in the
   liveError banner (snapshot mode was removed in wave 1; banner now
   says 'last known state shown').

30/30 vitest pass. tsc + vite build green.
2026-06-15 21:01:14 +04:00

45 lines
1.7 KiB
JavaScript

// Targeted LLM path verification — ask a question that no deterministic tool matches.
import { chromium } from "playwright";
const b = await chromium.launch({ headless: true });
const ctx = await b.newContext({ viewport: { width: 1440, height: 900 } });
const p = await ctx.newPage();
await ctx.addInitScript(() => localStorage.setItem("fm.canvas.tour.seen", String(Date.now())));
const calls = [];
p.on("response", (r) => {
const u = r.url();
if (u.includes("/api/v1/llm/")) calls.push({ status: r.status(), url: u.replace("https://canvas.flow-master.ai", "") });
});
await p.goto("https://canvas.flow-master.ai/", { waitUntil: "domcontentloaded" });
await p.waitForTimeout(2500);
await p.locator(".persona-chip").filter({ hasText: /Mariana/ }).click();
await p.waitForSelector(".topbar", { timeout: 30000 });
await p.waitForTimeout(2000);
await p.locator(".tab").filter({ hasText: /Assistant/i }).first().click();
await p.waitForTimeout(3000);
// A question no tool matches
const input = p.locator(".agent-composer textarea").first();
const queries = [
"Explain what EA2 means in two sentences.",
"What is the difference between a flow and a view?",
"Tell me a joke about procurement.",
];
for (const q of queries) {
console.log(`\n→ "${q}"`);
await input.fill(q);
await input.press("Enter");
await p.waitForTimeout(8000);
const replies = await p.locator(".agent-turn-body").allTextContents();
console.log(" last reply:", replies.slice(-1)[0]?.slice(0, 280));
}
console.log("\n=== /api/v1/llm/* calls ===");
for (const c of calls) console.log(` ${c.status} ${c.url}`);
await p.screenshot({ path: "/tmp/canvas-evidence/llm-path.png" });
await b.close();