chore(release): v1.12.11 — unify settings notice colours and callout style

Route every Settings callout, badge and status note through the semantic
colour tokens (success / warning / info / primary) instead of raw palette
classes, so the same meaning reads identically across cards and stays
legible in light mode. Add a single SettingsInfoTile primitive — icon +
tone + text on one fixed surface — and adopt it for the threshold-override
caution note, replacing a bespoke box.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Marc-André BombeckandClaude Opus 4.8 committed 2026-06-05 18:42:18 +02:00
1 parent 94b2d6f910
commit 038be6ac57
10 files changed
+112 -16

No files matched your search

+90
View File
@@ -0,0 +1,90 @@
"use client";
/**
* `<SettingsInfoTile>` — the one callout shape for Settings.
*
* Before this primitive, every section hand-rolled its own notice box:
* `border-l-2`/`border-l-4`, `rounded-md`/`rounded-lg`, mismatched
* border/background tone intensities, and raw palette colours used as
* text (which fail AA in light mode). The tile fixes the surface to a
* single contract — icon left, short text right, a tone-coloured left
* border — and routes every tone through the semantic tokens so the
* same notice reads identically in light and dark.
*
* Tones map to the project's semantic colour tokens:
* - `info` → `--info` (neutral guidance)
* - `warning` → `--warning` (caution / override / reconnect)
* - `success` → `--success` (confirmation)
* - `primary` → `--primary` (brand-accented instruction)
* - `neutral` → muted surface (low-emphasis aside)
*/
import * as React from "react";
import type { LucideIcon } from "lucide-react";
import { cn } from "@/lib/utils";
export type SettingsInfoTileTone =
| "info"
| "warning"
| "success"
| "primary"
| "neutral";
const TONE_SURFACE: Record<SettingsInfoTileTone, string> = {
info: "border-info/40 bg-info/10",
warning: "border-warning/40 bg-warning/10",
success: "border-success/40 bg-success/10",
primary: "border-primary/40 bg-primary/10",
neutral: "border-border bg-muted/30",
};
const TONE_ICON: Record<SettingsInfoTileTone, string> = {
info: "text-info",
warning: "text-warning",
success: "text-success",
primary: "text-primary",
neutral: "text-muted-foreground",
};
export interface SettingsInfoTileProps {
/** Lucide icon component; rendered in the tone colour. */
icon: LucideIcon;
/** Visual tone — routes through the semantic colour tokens. */
tone?: SettingsInfoTileTone;
/** Optional emphasised first line. */
title?: React.ReactNode;
/** Body content — muted text. */
children?: React.ReactNode;
/** Optional extra classes on the outer wrapper. */
className?: string;
}
export function SettingsInfoTile({
icon: Icon,
tone = "info",
title,
children,
className,
}: SettingsInfoTileProps) {
return (
<div
className={cn(
"flex items-start gap-2 rounded-md border-l-4 p-3 text-sm",
TONE_SURFACE[tone],
className,
)}
>
<Icon
className={cn("mt-0.5 h-4 w-4 shrink-0", TONE_ICON[tone])}
aria-hidden="true"
/>
<div className="min-w-0 space-y-1">
{title ? <p className="text-foreground font-medium">{title}</p> : null}
{children ? (
<div className="text-muted-foreground">{children}</div>
) : null}
</div>
</div>
);
}
+3 -3
View File
@@ -156,7 +156,7 @@ function ResearchModeCard() {
data-slot="settings-research-mode-card"
>
<div className="flex items-center gap-2">
<BookOpenCheck className="text-dracula-purple h-5 w-5" />
<BookOpenCheck className="text-primary h-5 w-5" />
<h2 className="text-lg font-semibold">
{t("settings.researchMode.sectionTitle")}
</h2>
@@ -349,7 +349,7 @@ function DataResetCard() {
{msg && (
<p
role="alert"
className={`mt-3 text-sm ${msgType === "success" ? "text-dracula-green" : "text-destructive"}`}
className={`mt-3 text-sm ${msgType === "success" ? "text-success" : "text-destructive"}`}
>
{msg}
</p>
@@ -493,7 +493,7 @@ function AccountDeleteCard() {
{msg && (
<p
role="alert"
className={`mt-3 text-sm ${msgType === "success" ? "text-dracula-green" : "text-destructive"}`}
className={`mt-3 text-sm ${msgType === "success" ? "text-success" : "text-destructive"}`}
>
{msg}
</p>
+3 -3
View File
@@ -520,7 +520,7 @@ function ProviderStatusBadges({
{settings?.codexStatus !== "connected" &&
settings?.hasAdminKey &&
activeProvider !== "codex" && (
<Badge className="border-dracula-purple/30 bg-dracula-purple/15 text-dracula-purple">
<Badge className="border-primary/30 bg-primary/15 text-primary">
{t("settings.ai.adminAiActiveBadge")}
</Badge>
)}
@@ -837,7 +837,7 @@ function CodexProviderForm({
{t("settings.ai.codex.disconnectButton")}
</Button>
) : deviceCode ? (
<div className="border-dracula-purple bg-dracula-purple/5 space-y-3 rounded-lg border-l-4 p-4">
<div className="border-primary bg-primary/5 space-y-3 rounded-lg border-l-4 p-4">
<p className="text-sm font-medium">
{t("settings.ai.deviceCodeHeading")}
</p>
@@ -848,7 +848,7 @@ function CodexProviderForm({
href={deviceCode.verificationUrl}
target="_blank"
rel="noopener noreferrer"
className="text-dracula-purple font-medium underline"
className="text-primary font-medium underline"
>
{deviceCode.verificationUrl}
</a>
@@ -201,7 +201,7 @@ export function HealthRecordExportPanel() {
>
<div className="mb-2 flex items-center gap-2">
<FileText
className="text-dracula-purple h-5 w-5 shrink-0"
className="text-primary h-5 w-5 shrink-0"
aria-hidden="true"
/>
<h2
@@ -324,7 +324,7 @@ function stateBadgeFor(
label: t("settings.notificationStatus.stateActive"),
variant: "default",
className:
"border-dracula-green/30 bg-dracula-green/15 text-dracula-green",
"border-success/30 bg-success/15 text-success",
icon: <CheckCircle2 className="h-3 w-3" aria-hidden />,
};
case "auto_disabled":
@@ -338,7 +338,7 @@ function stateBadgeFor(
return {
label: t("settings.notificationStatus.stateSendingPaused"),
variant: "outline",
className: "border-dracula-orange/40 text-dracula-orange",
className: "border-warning/40 text-warning",
icon: <Clock className="h-3 w-3" aria-hidden />,
};
case "manually_disabled":
@@ -100,7 +100,7 @@ export function TestConnectionButton({
{result?.kind === "ok" && (
<p
role="status"
className="text-dracula-green flex items-center gap-1.5 text-xs"
className="text-success flex items-center gap-1.5 text-xs"
>
<CheckCircle2 className="h-3.5 w-3.5" />
{t("settings.testConnection.ok", { latency: result.latency })}
@@ -10,6 +10,7 @@ import { Label } from "@/components/ui/label";
import { Skeleton } from "@/components/ui/skeleton";
import { Switch } from "@/components/ui/switch";
import { Badge } from "@/components/ui/badge";
import { SettingsInfoTile } from "./_info-tile";
import { useTranslations, useFormatters } from "@/lib/i18n/context";
import { queryKeys } from "@/lib/query-keys";
import {
@@ -350,10 +351,9 @@ function MetricRow({
defaultRange &&
(override!.min < defaultRange.greenMin * 0.7 ||
override!.max > defaultRange.greenMax * 1.3) && (
<div className="border-dracula-orange/50 bg-dracula-orange/5 flex items-start gap-2 rounded-md border-l-2 p-2 text-xs">
<AlertTriangle className="text-dracula-orange mt-0.5 h-3 w-3 shrink-0" />
<span>{t("thresholds.overrideWarning")}</span>
</div>
<SettingsInfoTile tone="warning" icon={AlertTriangle}>
{t("thresholds.overrideWarning")}
</SettingsInfoTile>
)}
</>
)}