From f1324b753930e1c89c9a319917db2532214fe59f Mon Sep 17 00:00:00 2001 From: Shad Date: Sun, 14 Jun 2026 11:52:02 +0400 Subject: [PATCH] fix(theme): dark theme + landing toggle now actually applies Two bugs: 1. [data-theme="dark"] block was defined BEFORE :root in source CSS, so :root cascade overrode it. Moved to AFTER :root and bumped specificity to html[data-theme="dark"] to beat plain :root. 2. Theme toggle button only existed in the topbar, which is hidden on landing/login/sso-callback scenes. Added a ThemeToggle to the Landing header so users can flip theme from the home page. Confidence: high Scope-risk: narrow --- qa/probe_dom.mjs | 23 +++++++++++++++++++++++ qa/probe_theme.mjs | 29 +++++++++++++++++++++++++++++ qa/probe_theme2.mjs | 28 ++++++++++++++++++++++++++++ qa/probe_theme3.mjs | 28 ++++++++++++++++++++++++++++ qa/probe_topbar.mjs | 17 +++++++++++++++++ src/index.css | 20 ++++++++++---------- src/scenes/Landing.tsx | 28 ++++++++++++++++++++++++---- 7 files changed, 159 insertions(+), 14 deletions(-) create mode 100644 qa/probe_dom.mjs create mode 100644 qa/probe_theme.mjs create mode 100644 qa/probe_theme2.mjs create mode 100644 qa/probe_theme3.mjs create mode 100644 qa/probe_topbar.mjs diff --git a/qa/probe_dom.mjs b/qa/probe_dom.mjs new file mode 100644 index 0000000..6d4ebed --- /dev/null +++ b/qa/probe_dom.mjs @@ -0,0 +1,23 @@ +import { chromium } from "playwright"; +const b = await chromium.launch({ + headless: true, + args: ["--host-resolver-rules=MAP canvas.flow-master.ai 65.21.71.186", "--ignore-certificate-errors"], +}); +const ctx = await b.newContext({ viewport: { width: 1440, height: 900 } }); +const p = await ctx.newPage(); +await p.goto("https://canvas.flow-master.ai/", { waitUntil: "networkidle" }); +await p.waitForTimeout(1500); +await p.locator("input[type='email']").fill("dev@flow-master.ai"); +const devBtn = p.locator("button", { hasText: /developer|dev-login/i }); +if (await devBtn.count() > 0) await devBtn.click(); +await p.waitForTimeout(3000); + +console.log("=== ALL .link-btn buttons ==="); +const buttons = await p.$$eval(".link-btn", (els) => els.map((e) => ({ + class: e.className, + text: (e.innerText || "").slice(0, 50), + aria: e.getAttribute("aria-label"), + title: e.getAttribute("title"), +}))); +buttons.forEach(b => console.log(JSON.stringify(b))); +await b.close(); diff --git a/qa/probe_theme.mjs b/qa/probe_theme.mjs new file mode 100644 index 0000000..f216fe8 --- /dev/null +++ b/qa/probe_theme.mjs @@ -0,0 +1,29 @@ +import { chromium } from "playwright"; +const b = await chromium.launch({ + headless: true, + args: ["--host-resolver-rules=MAP canvas.flow-master.ai 65.21.71.186", "--ignore-certificate-errors"], +}); +const ctx = await b.newContext({ viewport: { width: 1440, height: 900 } }); +const p = await ctx.newPage(); +await p.goto("https://canvas.flow-master.ai/", { waitUntil: "networkidle" }); +await p.waitForTimeout(2000); + +// Sign in first +await p.locator("input[type='email']").fill("dev@flow-master.ai"); +const devBtn = p.locator("button", { hasText: /developer|dev-login/i }); +if (await devBtn.count() > 0) await devBtn.click(); +await p.waitForTimeout(2500); + +console.log("after login theme:", await p.evaluate(() => document.documentElement.dataset.theme)); +console.log("after login body bg:", await p.evaluate(() => getComputedStyle(document.body).backgroundColor)); + +const toggle = p.locator(".theme-toggle, .link-btn.theme-toggle, button[aria-label*='theme']").first(); +const count = await toggle.count(); +console.log("toggle count:", count); +if (count > 0) { + await toggle.click(); + await p.waitForTimeout(800); + console.log("after click theme:", await p.evaluate(() => document.documentElement.dataset.theme)); + console.log("after click body bg:", await p.evaluate(() => getComputedStyle(document.body).backgroundColor)); +} +await b.close(); diff --git a/qa/probe_theme2.mjs b/qa/probe_theme2.mjs new file mode 100644 index 0000000..0ac9b26 --- /dev/null +++ b/qa/probe_theme2.mjs @@ -0,0 +1,28 @@ +import { chromium } from "playwright"; +const b = await chromium.launch({ + headless: true, + args: ["--host-resolver-rules=MAP canvas.flow-master.ai 65.21.71.186", "--ignore-certificate-errors"], +}); +const ctx = await b.newContext({ viewport: { width: 1440, height: 900 } }); +const p = await ctx.newPage(); +await p.goto("https://canvas.flow-master.ai/", { waitUntil: "networkidle" }); +await p.waitForTimeout(1500); +// Get the computed --bp-paper at root +console.log("html dataset.theme:", await p.evaluate(() => document.documentElement.dataset.theme)); +console.log("--bp-paper (root):", await p.evaluate(() => getComputedStyle(document.documentElement).getPropertyValue("--bp-paper"))); +console.log("body background:", await p.evaluate(() => getComputedStyle(document.body).backgroundColor)); + +// Force toggle data-theme via JS +await p.evaluate(() => document.documentElement.dataset.theme = "dark"); +await p.waitForTimeout(200); +console.log("after force dark:"); +console.log(" --bp-paper:", await p.evaluate(() => getComputedStyle(document.documentElement).getPropertyValue("--bp-paper"))); +console.log(" body bg:", await p.evaluate(() => getComputedStyle(document.body).backgroundColor)); + +await p.evaluate(() => document.documentElement.dataset.theme = "light"); +await p.waitForTimeout(200); +console.log("after force light:"); +console.log(" --bp-paper:", await p.evaluate(() => getComputedStyle(document.documentElement).getPropertyValue("--bp-paper"))); +console.log(" body bg:", await p.evaluate(() => getComputedStyle(document.body).backgroundColor)); + +await b.close(); diff --git a/qa/probe_theme3.mjs b/qa/probe_theme3.mjs new file mode 100644 index 0000000..4c38076 --- /dev/null +++ b/qa/probe_theme3.mjs @@ -0,0 +1,28 @@ +import { chromium } from "playwright"; +const b = await chromium.launch({ + headless: true, + args: ["--host-resolver-rules=MAP canvas.flow-master.ai 65.21.71.186", "--ignore-certificate-errors"], +}); +const ctx = await b.newContext({ viewport: { width: 1440, height: 900 } }); +const p = await ctx.newPage(); +await p.goto("https://canvas.flow-master.ai/", { waitUntil: "domcontentloaded" }); +await p.waitForLoadState("networkidle"); +await p.waitForTimeout(2000); + +// Read the data-theme attribute repeatedly +console.log("docElement.dataset.theme:", await p.evaluate(() => document.documentElement.dataset.theme)); +console.log("docElement attributes:", await p.evaluate(() => document.documentElement.getAttributeNames().join(","))); +console.log("html outerHTML head:", await p.evaluate(() => document.documentElement.outerHTML.slice(0, 200))); +console.log("computed --bp-paper:", await p.evaluate(() => getComputedStyle(document.documentElement).getPropertyValue("--bp-paper"))); + +// Try setting dark via JS and reading +await p.evaluate(() => { + document.documentElement.setAttribute("data-theme", "dark"); +}); +await p.waitForTimeout(500); +console.log("\nafter setAttribute('data-theme','dark'):"); +console.log("dataset.theme:", await p.evaluate(() => document.documentElement.dataset.theme)); +console.log("--bp-paper:", await p.evaluate(() => getComputedStyle(document.documentElement).getPropertyValue("--bp-paper"))); +console.log("body bg:", await p.evaluate(() => getComputedStyle(document.body).backgroundColor)); + +await b.close(); diff --git a/qa/probe_topbar.mjs b/qa/probe_topbar.mjs new file mode 100644 index 0000000..4fa1322 --- /dev/null +++ b/qa/probe_topbar.mjs @@ -0,0 +1,17 @@ +import { chromium } from "playwright"; +const b = await chromium.launch({ + headless: true, + args: ["--host-resolver-rules=MAP canvas.flow-master.ai 65.21.71.186", "--ignore-certificate-errors"], +}); +const ctx = await b.newContext({ viewport: { width: 1440, height: 900 } }); +const p = await ctx.newPage(); +await p.goto("https://canvas.flow-master.ai/", { waitUntil: "networkidle" }); +await p.waitForTimeout(1500); +await p.locator("input[type='email']").fill("dev@flow-master.ai"); +const devBtn = p.locator("button", { hasText: /developer|dev-login/i }); +if (await devBtn.count() > 0) await devBtn.click(); +await p.waitForTimeout(3000); +console.log("topbar exists:", await p.locator(".topbar").count()); +console.log("topbar HTML:"); +console.log(await p.evaluate(() => document.querySelector(".topbar")?.outerHTML?.slice(0, 2000))); +await b.close(); diff --git a/src/index.css b/src/index.css index ad83fb4..8b51093 100644 --- a/src/index.css +++ b/src/index.css @@ -9,16 +9,6 @@ - 1px rules, square edges, monospace operational density ===================================================================== */ -[data-theme="dark"] { - --bp-paper: #0c1322; - --bp-paper-2: #1a2740; - --bp-paper-3: #243453; - --bp-navy: #e6edf7; - --bp-navy-2: #d5dde9; - --bp-muted: #7a8aa8; - --bp-muted-2: #4a5b80; -} - :root { /* Doctrinal hex tokens — declared once, referenced everywhere via var(). */ --bp-paper: #f5f7fb; @@ -80,6 +70,16 @@ --radius-xl: 0; } +html[data-theme="dark"] { + --bp-paper: #0c1322; + --bp-paper-2: #1a2740; + --bp-paper-3: #243453; + --bp-navy: #e6edf7; + --bp-navy-2: #d5dde9; + --bp-muted: #7a8aa8; + --bp-muted-2: #4a5b80; +} + /* ===================================================================== Base ===================================================================== */ diff --git a/src/scenes/Landing.tsx b/src/scenes/Landing.tsx index cc4901f..c8459bc 100644 --- a/src/scenes/Landing.tsx +++ b/src/scenes/Landing.tsx @@ -2,7 +2,7 @@ import { motion } from "framer-motion"; import { useApp } from "../state/store"; import { liveMeta } from "../data/scenarios"; -import { Sparkles, Arrow, Cmd, Bot, Pulse } from "../components/icons"; +import { Sparkles, Arrow, Cmd, Bot, Pulse, Sun, Moon } from "../components/icons"; export default function Landing() { const setScene = useApp((s) => s.setScene); @@ -28,9 +28,12 @@ export default function Landing() { Mission Control - +
+ + +
@@ -132,3 +135,20 @@ export default function Landing() { ); } + +function ThemeToggle() { + const theme = useApp((s) => s.theme); + const setTheme = useApp((s) => s.setTheme); + const isDark = theme === "dark"; + return ( + + ); +}