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
This commit is contained in:
@@ -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();
|
||||||
@@ -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();
|
||||||
@@ -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();
|
||||||
@@ -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();
|
||||||
@@ -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();
|
||||||
+10
-10
@@ -9,16 +9,6 @@
|
|||||||
- 1px rules, square edges, monospace operational density
|
- 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 {
|
:root {
|
||||||
/* Doctrinal hex tokens — declared once, referenced everywhere via var(). */
|
/* Doctrinal hex tokens — declared once, referenced everywhere via var(). */
|
||||||
--bp-paper: #f5f7fb;
|
--bp-paper: #f5f7fb;
|
||||||
@@ -80,6 +70,16 @@
|
|||||||
--radius-xl: 0;
|
--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
|
Base
|
||||||
===================================================================== */
|
===================================================================== */
|
||||||
|
|||||||
+21
-1
@@ -2,7 +2,7 @@
|
|||||||
import { motion } from "framer-motion";
|
import { motion } from "framer-motion";
|
||||||
import { useApp } from "../state/store";
|
import { useApp } from "../state/store";
|
||||||
import { liveMeta } from "../data/scenarios";
|
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() {
|
export default function Landing() {
|
||||||
const setScene = useApp((s) => s.setScene);
|
const setScene = useApp((s) => s.setScene);
|
||||||
@@ -28,9 +28,12 @@ export default function Landing() {
|
|||||||
<span className="brand-divider" />
|
<span className="brand-divider" />
|
||||||
<span className="brand-sub">Mission Control</span>
|
<span className="brand-sub">Mission Control</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="landing-top-actions">
|
||||||
|
<ThemeToggle />
|
||||||
<button className="link-btn" onClick={() => setCmdOpen(true)}>
|
<button className="link-btn" onClick={() => setCmdOpen(true)}>
|
||||||
<Cmd size={13} /> Command <kbd>⌘K</kbd>
|
<Cmd size={13} /> Command <kbd>⌘K</kbd>
|
||||||
</button>
|
</button>
|
||||||
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<main className="landing-main">
|
<main className="landing-main">
|
||||||
@@ -132,3 +135,20 @@ export default function Landing() {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function ThemeToggle() {
|
||||||
|
const theme = useApp((s) => s.theme);
|
||||||
|
const setTheme = useApp((s) => s.setTheme);
|
||||||
|
const isDark = theme === "dark";
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
className="link-btn theme-toggle"
|
||||||
|
onClick={() => setTheme(isDark ? "light" : "dark")}
|
||||||
|
title={isDark ? "Switch to light theme" : "Switch to dark theme"}
|
||||||
|
aria-label="Toggle theme"
|
||||||
|
>
|
||||||
|
{isDark ? <Sun size={13} /> : <Moon size={13} />}
|
||||||
|
<span className="theme-toggle-label">{isDark ? "LIGHT" : "DARK"}</span>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user