diff --git a/apps/mobile/src/components/agents/attachment-preview-strip.tsx b/apps/mobile/src/components/agents/attachment-preview-strip.tsx index 1853d94e7e..690b7aa840 100644 --- a/apps/mobile/src/components/agents/attachment-preview-strip.tsx +++ b/apps/mobile/src/components/agents/attachment-preview-strip.tsx @@ -383,7 +383,7 @@ function AttachmentChip({ 'overflow-hidden rounded-md border border-border bg-card', isImage ? 'h-16 w-20' : 'h-12 w-48', description.showRetry && 'border-destructive', - isErrored && !description.showRetry && 'border-destructive/60' + isErrored && !description.showRetry && 'border-danger-tile-border' )} > {description.showRetry ? ( diff --git a/apps/mobile/src/components/agents/permission-card.tsx b/apps/mobile/src/components/agents/permission-card.tsx index c7d592bf73..7170e1c438 100644 --- a/apps/mobile/src/components/agents/permission-card.tsx +++ b/apps/mobile/src/components/agents/permission-card.tsx @@ -110,7 +110,7 @@ export function PermissionCard({ {presentation.errorMessage ? ( - + {presentation.errorMessage} ) : null} diff --git a/apps/mobile/src/components/agents/question-card.tsx b/apps/mobile/src/components/agents/question-card.tsx index 6c56bc37a0..d3eec82b6e 100644 --- a/apps/mobile/src/components/agents/question-card.tsx +++ b/apps/mobile/src/components/agents/question-card.tsx @@ -207,7 +207,7 @@ export function QuestionCard({ {presentation.errorMessage ? ( - + {presentation.errorMessage} ) : null} diff --git a/apps/mobile/src/components/pr-review/pr-review-overview-parts.tsx b/apps/mobile/src/components/pr-review/pr-review-overview-parts.tsx index 832fac0fa6..381c1976aa 100644 --- a/apps/mobile/src/components/pr-review/pr-review-overview-parts.tsx +++ b/apps/mobile/src/components/pr-review/pr-review-overview-parts.tsx @@ -68,10 +68,10 @@ export function describePrState(args: { return { labelKey: 'prReview.overview.stateOpen', tone: 'muted', icon: GitPullRequest }; } -// Theme colors are CSS variables — Tailwind opacity modifiers like -// `bg-good/10` don't work on them. The chip uses a flat muted background -// and lets the foreground color carry the tone so it stays legible in -// both themes without needing per-tone backgrounds. +// Theme colors are CSS variables, so Tailwind opacity modifiers on them are +// silently dropped. The chip uses a flat muted background and lets the +// foreground color carry the tone so it stays legible in both themes +// without needing per-tone backgrounds. const TONE_FG_CLASS = { good: 'text-good', warn: 'text-warn', diff --git a/tools/nativewind/check-classes.mjs b/tools/nativewind/check-classes.mjs index 384be42e9f..a1c9d7e830 100644 --- a/tools/nativewind/check-classes.mjs +++ b/tools/nativewind/check-classes.mjs @@ -51,6 +51,19 @@ const RULES = [ pattern: new RegExp(`\\b(?:${COLOR_UTILITIES})-black/\\d+\\b`, 'g'), advice: 'compiles to an unparseable #NaN colour; use a concrete value, e.g. bg-[#00000066]', }, + { + // A `/opacity` modifier on a theme colour compiles to + // `color-mix(in oklab, var(--color-…) n%, transparent)`. NativeWind v5 + // cannot decompose the variable, so the runtime drops the declaration and + // the element paints nothing, with no build error. The `*-tile-bg` / + // `*-tile-border` pairs pre-bake the alpha instead. + pattern: new RegExp( + `\\b(?:${COLOR_UTILITIES})-(?:background|foreground|card|popover|primary|secondary|muted-soft|muted|accent-soft|accent|destructive|border|input|ring|ink2|good|warn|info|danger|agent-[a-z]+)(?:-foreground)?/\\d+\\b`, + 'g' + ), + advice: + 'the /opacity modifier is dropped on CSS-variable theme colours; use the paired *-tile-bg / *-tile-border token or a concrete colour', + }, { // `text-align` only accepts auto/left/right/center/justify, so the // logical keywords are dropped and the text keeps its default alignment. @@ -226,6 +239,21 @@ function checkCompilerStillDropsThese() { if (!JSON.stringify(alignment).includes('text-align')) { fail('check-classes: text-align: start is no longer dropped — drop the text-start/end rule'); } + + // A `/opacity` modifier on an `@theme inline` variable is emitted as a + // deferred colorMix over the variable; the runtime cannot decompose it and + // drops the declaration. When the compiler resolves it instead, this stops + // matching and the rule above can go. + const themeOpacity = firstValue( + '@theme inline { --color-destructive: var(--destructive); }\n' + + ':root { --destructive: #b0483a; }\n' + + '.x { background-color: color-mix(in oklab, var(--color-destructive) 10%, transparent); }' + ); + if (!themeOpacity.includes('colorMix')) { + fail( + `check-classes: color-mix over an @theme inline variable now compiles to ${themeOpacity} instead of a deferred colorMix — drop the theme-token opacity rule` + ); + } } checkCompilerStillDropsThese();