diff --git a/qa/_measure_topbar.mjs b/qa/_measure_topbar.mjs new file mode 100644 index 0000000..44b3a48 --- /dev/null +++ b/qa/_measure_topbar.mjs @@ -0,0 +1,65 @@ +// DOM-introspection — measure topbar item count, sizes, overlap. +import { chromium } from "playwright"; + +const URL = "https://canvas.flow-master.ai/"; +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()))); + +await p.goto(URL, { waitUntil: "domcontentloaded" }); +await p.waitForTimeout(2500); + +console.log("=== LOGIN PAGE ==="); +const loginHas = await p.evaluate(() => ({ + topbar: !!document.querySelector(".topbar"), + personaChips: document.querySelectorAll(".persona-chip").length, + ssoBtn: !!document.querySelector(".sso-btn"), + emailInput: !!document.querySelector("input[type='email']"), + banner: document.querySelector(".global-banner, .login-banner")?.textContent?.slice(0, 100) || null, +})); +console.log(JSON.stringify(loginHas, null, 2)); + +console.log("\n=== POST-LOGIN ==="); +await p.locator(".persona-chip").filter({ hasText: /Mariana/ }).click(); +await p.waitForSelector(".topbar", { timeout: 15000 }).catch(() => {}); +await p.waitForTimeout(3000); + +const topbar = await p.evaluate(() => { + const tb = document.querySelector(".topbar"); + if (!tb) return null; + const items = Array.from(tb.children).map((el) => ({ + tag: el.tagName.toLowerCase(), + class: el.className, + text: el.textContent?.trim().slice(0, 50), + width: el.getBoundingClientRect().width, + })); + const tabs = Array.from(tb.querySelectorAll(".tab")).map((el) => el.textContent?.trim()); + const actions = Array.from(tb.querySelectorAll(".topbar-actions > *")).map((el) => ({ + tag: el.tagName.toLowerCase(), + class: el.className.slice(0, 40), + text: el.textContent?.trim().slice(0, 30), + })); + return { + totalWidth: tb.getBoundingClientRect().width, + children: items, + tabs, + actionsCount: actions.length, + actions, + overflows: tb.scrollWidth > tb.clientWidth, + }; +}); +console.log(JSON.stringify(topbar, null, 2)); + +console.log("\n=== USER MENU ==="); +await p.locator(".user-avatar").first().click().catch(() => {}); +await p.waitForTimeout(800); +const menu = await p.evaluate(() => { + const m = document.querySelector(".user-menu-pop"); + if (!m) return { open: false }; + const items = Array.from(m.querySelectorAll(".user-menu-item")).map((el) => el.textContent?.trim().slice(0, 50)); + return { open: true, items, width: m.getBoundingClientRect().width }; +}); +console.log(JSON.stringify(menu, null, 2)); + +await b.close(); diff --git a/qa/_probe_dom.mjs b/qa/_probe_dom.mjs new file mode 100644 index 0000000..1eef659 --- /dev/null +++ b/qa/_probe_dom.mjs @@ -0,0 +1,46 @@ +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(); +const calls = []; +p.on("response", async (r) => { + const u = r.url(); + if (u.includes("/api/")) { + calls.push({ status: r.status(), method: r.request().method(), url: u.replace("https://canvas.flow-master.ai", "") }); + } +}); +p.on("console", (m) => { if (m.type() === "error") console.log("CONSOLE-ERR:", m.text().slice(0, 200)); }); +p.on("pageerror", (e) => console.log("PAGE-ERR:", e.message.slice(0, 200))); +await ctx.addInitScript(() => localStorage.setItem("fm.canvas.tour.seen", String(Date.now()))); + +await p.goto("https://canvas.flow-master.ai/", { waitUntil: "domcontentloaded" }); +await p.waitForTimeout(2500); +await p.locator(".persona-chip").filter({ hasText: /Mariana/ }).click(); +// Live auth can take 6-10s; wait for shell to flip +await p.waitForFunction(() => !document.querySelector("[class*='shell-login']"), null, { timeout: 30000 }).catch(() => {}); +await p.waitForTimeout(3000); + +const state = await p.evaluate(() => { + const shell = document.querySelector("[class*='shell-']")?.className; + const topbar = document.querySelector("header.topbar"); + const tabs = Array.from(document.querySelectorAll(".tab")).map(t => t.textContent?.trim()); + const headerByTag = document.querySelector("header")?.outerHTML?.slice(0, 200); + return { + shell, + hasTopbar: !!topbar, + headerByTag, + tabCount: tabs.length, + tabs, + sceneText: document.querySelector(".scene")?.innerText?.slice(0, 300) || null, + }; +}); +console.log(JSON.stringify(state, null, 2)); + +await p.screenshot({ path: "/tmp/canvas-evidence/measure.png", fullPage: false }); +console.log("screenshot /tmp/canvas-evidence/measure.png"); + +console.log("\n=== api calls ==="); +for (const c of calls) console.log(` ${c.status} ${c.method} ${c.url.slice(0, 100)}`); + +await b.close(); diff --git a/src/state/store.ts b/src/state/store.ts index 652b6d5..a25ff06 100644 --- a/src/state/store.ts +++ b/src/state/store.ts @@ -272,7 +272,9 @@ export const useApp = create((set, get) => { isAuthed: true, }); get().pushToast("ok", `Signed in as ${me.email}`); - if (get().mode === "live") await get().refreshLive(); + // Fire-and-forget the live scenario fetch. Login completion must NOT + // block on every EA2 read; the Mission scene re-fetches on mount. + void get().refreshLive().catch(() => { /* surfaced via liveError */ }); } catch (e) { get().pushToast("err", `Login failed: ${(e as Error).message.slice(0, 80)}`); throw e;