diff --git a/ui/src/components/OnboardingWizard.tsx b/ui/src/components/OnboardingWizard.tsx index 8737f7939f..76f7421169 100644 --- a/ui/src/components/OnboardingWizard.tsx +++ b/ui/src/components/OnboardingWizard.tsx @@ -2155,7 +2155,7 @@ function OnboardingWizardInner({ name + mission steps) */}
diff --git a/ui/src/components/OnboardingWizardTheme.test.tsx b/ui/src/components/OnboardingWizardTheme.test.tsx new file mode 100644 index 0000000000..a49004d0e2 --- /dev/null +++ b/ui/src/components/OnboardingWizardTheme.test.tsx @@ -0,0 +1,69 @@ +// @vitest-environment node + +import { readFileSync } from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import { describe, expect, it } from "vitest"; + +// The onboarding wizard's decorative right-hand panel (which renders the +// ASCII paperclip illustration) must follow the active shadcn theme instead of +// hardcoding a dark surface. Otherwise a light/cream deployer theme (set via +// the PAPERCLIP_DEFAULT_THEME bootstrap) renders a jarring cream form next to a +// solid dark panel. The illustration glyphs already use `text-muted-foreground`, +// so the panel must sit on the paired `bg-muted` surface to read as an +// intentional ink-on-surface texture in every theme. +// +// Asserting against the source keeps this guard cheap: the panel className is a +// static string literal, and the full wizard dialog is too heavy to mount here. +const here = path.dirname(fileURLToPath(import.meta.url)); + +describe("OnboardingWizard decorative panel theming", () => { + const source = readFileSync( + path.join(here, "OnboardingWizard.tsx"), + "utf8" + ); + + /** + * The className expression on the wrapper around . + * Anchoring here keeps both guards on the decorative panel itself - the same + * tokens appear elsewhere in the wizard. `[^<>]` stops the match spanning + * into other JSX elements. + */ + function panelClassNames(): string | null { + return ( + source.match(/className=\{cn\(([^<>]*)\)\}\s*>\s*/)?.[1] ?? + null + ); + } + + it("gives the decorative panel no background but the muted token", () => { + // Scoped to the panel, not the file. A file-wide scan would fail this + // case for a hardcoded colour anywhere else in the wizard, reporting a + // panel regression that had not happened. + // + // Asserted as the complete set of `bg-` classes rather than a list of + // spellings to forbid. The previous version scanned for `bg-[#rrggbb]` + // alone, and passed unchanged once master migrated this class to + // `bg-(--hex-1d1d1d)` - so it guarded nothing at all for a while. A named + // colour like `bg-black` or `bg-zinc-900` would have slipped through the + // same way. Naming what is allowed cannot rot like that. + const panel = panelClassNames(); + expect(panel).not.toBeNull(); + const backgrounds = panel!.match(/\bbg-[^\s"'`,]+/g) ?? []; + expect(backgrounds).toEqual(["bg-muted"]); + }); + + it("themes the decorative panel with shadcn surface tokens", () => { + // Anchor to the wrapper around so the guard checks + // the decorative panel itself (the same tokens appear elsewhere in the + // wizard), then assert each token independently so a class reorder or a + // utility inserted between them cannot false-fail the test. `[^<>]` + // keeps the match from spanning across other JSX elements. + const panel = source.match( + /className=\{cn\(([^<>]*)\)\}\s*>\s*/ + ); + expect(panel).not.toBeNull(); + expect(panel?.[1]).toMatch(/\bbg-muted\b(?!-)/); + expect(panel?.[1]).toMatch(/\btext-muted-foreground\b/); + }); +});