Skip to content

Commit 8f5ec97

Browse files
committed
fix: address CodeRabbit feedback on Android widget theming
Type fallback maps to the canvas token list, normalize 4-digit #RGBA, scope forced-color-adjust to forced-colors mode, and harden widget theme tests for style-block order and prototype cleanup.
1 parent 2353c98 commit 8f5ec97

2 files changed

Lines changed: 41 additions & 11 deletions

File tree

apps/web/src/components/WidgetRenderer.test.tsx

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,12 @@ describe("canvas widget isolation", () => {
4242
});
4343

4444
describe("canvas widget theme colors", () => {
45-
it("normalizes 8-digit hex colors to rgba for Android-safe injection", () => {
45+
it("normalizes hex-with-alpha colors to rgba for Android-safe injection", () => {
4646
expect(normalizeCssColorValue("#e4e4e4eb")).toBe(
4747
"rgba(228, 228, 228, 0.922)"
4848
);
4949
expect(normalizeCssColorValue("#141414f0")).toBe("rgba(20, 20, 20, 0.941)");
50+
expect(normalizeCssColorValue("#fff8")).toBe("rgba(255, 255, 255, 0.533)");
5051
expect(normalizeCssColorValue("#abc4ff")).toBe("#abc4ff");
5152
expect(normalizeCssColorValue(" rgba(1, 2, 3, 0.5) ")).toBe(
5253
"rgba(1, 2, 3, 0.5)"
@@ -71,16 +72,24 @@ describe("canvas widget theme colors", () => {
7172
expect(darkDoc).toContain('class="dark"');
7273
expect(darkDoc).toContain('name="color-scheme" content="dark"');
7374
expect(darkDoc).toContain("color-scheme: dark;");
75+
expect(darkDoc).toContain("@media (forced-colors: active)");
7476
expect(darkDoc).toContain("forced-color-adjust: none");
7577
expect(darkDoc).toContain(buildFallbackThemeBlock(true));
7678
expect(darkDoc).toContain("--color-bg-blue: #364954;");
7779
expect(darkDoc).toContain("--foreground: rgba(228, 228, 228, 0.92);");
7880
expect(darkDoc).not.toContain("color-mix(in oklch");
81+
// Host vars must win over fallbacks via later source order.
82+
expect(darkDoc.indexOf('id="avenire-theme-fallbacks"')).toBeLessThan(
83+
darkDoc.indexOf('id="avenire-css-vars"')
84+
);
7985

8086
expect(lightDoc).toContain('content="light"');
8187
expect(lightDoc).toContain("color-scheme: light;");
8288
expect(lightDoc).toContain(buildFallbackThemeBlock(false));
8389
expect(lightDoc).toContain("--color-bg-blue: #ddebf1;");
90+
expect(lightDoc.indexOf('id="avenire-theme-fallbacks"')).toBeLessThan(
91+
lightDoc.indexOf('id="avenire-css-vars"')
92+
);
8493
});
8594

8695
it("reads explicit theme tokens even when style iteration is empty", () => {
@@ -112,6 +121,8 @@ describe("canvas widget theme colors", () => {
112121
"length",
113122
originalLength
114123
);
124+
} else {
125+
Reflect.deleteProperty(CSSStyleDeclaration.prototype, "length");
115126
}
116127
document.documentElement.style.removeProperty("--foreground");
117128
document.documentElement.style.removeProperty("--background");

apps/web/src/components/WidgetRenderer.tsx

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,9 @@ export const CANVAS_THEME_VAR_NAMES = [
119119
"--color-pill-red",
120120
] as const;
121121

122-
const LIGHT_THEME_FALLBACKS: Record<string, string> = {
122+
type CanvasThemeVarName = (typeof CANVAS_THEME_VAR_NAMES)[number];
123+
124+
const LIGHT_THEME_FALLBACKS: Partial<Record<CanvasThemeVarName, string>> = {
123125
"--background": "#fcfcfc",
124126
"--foreground": "rgba(20, 20, 20, 0.94)",
125127
"--card": "#fcfcfc",
@@ -187,7 +189,7 @@ const LIGHT_THEME_FALLBACKS: Record<string, string> = {
187189
"--color-pill-red": "rgba(255, 0, 26, 0.2)",
188190
};
189191

190-
const DARK_THEME_FALLBACKS: Record<string, string> = {
192+
const DARK_THEME_FALLBACKS: Partial<Record<CanvasThemeVarName, string>> = {
191193
"--background": "#141414",
192194
"--foreground": "rgba(228, 228, 228, 0.92)",
193195
"--card": "#181818",
@@ -256,14 +258,21 @@ const DARK_THEME_FALLBACKS: Record<string, string> = {
256258
};
257259

258260
/**
259-
* Convert #RRGGBBAA (and equivalent forms) to rgba() for Android WebView /
260-
* Chrome versions that mishandle 8-digit hex inside sandboxed srcdoc iframes.
261+
* Convert #RRGGBBAA / #RGBA hex-with-alpha to rgba() for Android WebView /
262+
* Chrome versions that mishandle alpha hex inside sandboxed srcdoc iframes.
261263
*/
262264
export function normalizeCssColorValue(value: string): string {
263265
const trimmed = value.trim();
264-
const hex8 = /^#([0-9a-f]{8})$/i.exec(trimmed);
265-
if (hex8?.[1]) {
266-
const hex = hex8[1];
266+
const hexAlpha = /^#(?:([0-9a-f]{8})|([0-9a-f]{4}))$/i.exec(trimmed);
267+
const raw = hexAlpha?.[1] ?? hexAlpha?.[2];
268+
if (raw) {
269+
const hex =
270+
raw.length === 4
271+
? raw
272+
.split("")
273+
.map((channel) => channel + channel)
274+
.join("")
275+
: raw;
267276
const r = Number.parseInt(hex.slice(0, 2), 16);
268277
const g = Number.parseInt(hex.slice(2, 4), 16);
269278
const b = Number.parseInt(hex.slice(4, 6), 16);
@@ -274,8 +283,11 @@ export function normalizeCssColorValue(value: string): string {
274283
return trimmed;
275284
}
276285

277-
function serializeCssVarBlock(vars: Record<string, string>): string {
286+
function serializeCssVarBlock(
287+
vars: Record<string, string | undefined>
288+
): string {
278289
const declarations = Object.entries(vars)
290+
.filter((entry): entry is [string, string] => typeof entry[1] === "string")
279291
.map(([k, v]) => ` ${k}: ${v};`)
280292
.join("\n");
281293
return `:root {\n${declarations}\n}`;
@@ -590,11 +602,18 @@ html, body {
590602
overflow-x: auto;
591603
overflow-y: hidden;
592604
padding: 12px;
593-
/* Prevent Android forced-colors from wiping widget fills/text. */
594-
forced-color-adjust: none;
595605
-webkit-tap-highlight-color: transparent;
596606
}
597607
608+
/* Keep theme fills/text when forced-colors is active (Android/high-contrast). */
609+
@media (forced-colors: active) {
610+
html, body {
611+
forced-color-adjust: none;
612+
background: var(--canvas-background, var(--card));
613+
color: var(--canvas-text, var(--foreground));
614+
}
615+
}
616+
598617
/* ── Form element defaults matching shadcn aesthetic ── */
599618
input[type="range"] {
600619
-webkit-appearance: none;

0 commit comments

Comments
 (0)