mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-14 22:50:51 +00:00
refactor(ui): retire the raw dracula utilities for semantic tokens
Migrate every remaining *-dracula-* utility site (127 lint warnings, 193 class occurrences) onto the semantic vocabulary. The swaps are pixel-identical in both themes — each target token aliases the same primitive the utility read, including the light-theme AA overrides: - green/orange/cyan/red → success/warning/info/destructive - purple → primary (same value in both themes) - pink → the new --brand-pink token (Coach gradient, Apple-Health badge, recording mic): dark #ff79c6 — 6.86:1 card / 5.97:1 bg; light #a3134d — 7.32:1 card / 6.83:1 bg (computed) - yellow sites resolve to their status meaning: warn-level log icon, update-available pill and offline-geo hint → warning; the admin helpful-rate ramp → success/warning/destructive; the confidence meter mirrors the coverage-meter precedent (medium + low share the caution tint, the lit count carries the distinction) - the confidence-badge low tier dropped dead *-dracula-comment/* classes (no generated utility) for border-border + bg-muted/50 Close two rule blind spots the sweep surfaced — directional borders (border-l-dracula-red) and the parenthesised CSS-var shorthand (bg-(--dracula-green)) — and flip healthlog/no-dracula-utility from warn to error so the class cannot regrow. var(--dracula-*) references in chart code stay: they are token references the light theme overrides, and the chart palette is pinned visually.
This commit is contained in:
1 parent
68b4dd08a7
commit
a4b204ffe4
59 files changed
+272
-214
No files matched your search
@@ -89,9 +89,9 @@ describe("CoachFeedbackSection", () => {
|
||||
// Two bucket rows in the table.
|
||||
const matches = (html.match(/coach-feedback-bucket/g) ?? []).length;
|
||||
expect(matches).toBe(2);
|
||||
// Helpful-rate column carries the band tint.
|
||||
expect(html).toContain("text-dracula-green"); // 80% bucket
|
||||
expect(html).toContain("text-dracula-yellow"); // 50% bucket
|
||||
// Helpful-rate column carries the band tint (semantic urgency ramp).
|
||||
expect(html).toContain("text-success"); // 80% bucket
|
||||
expect(html).toContain("text-warning"); // 50% bucket
|
||||
// Numbers visible in the markup.
|
||||
expect(html).toContain("80%");
|
||||
expect(html).toContain("50%");
|
||||
|
||||
@@ -122,7 +122,7 @@ export interface ApiTokenInfo {
|
||||
export function ConfiguredBadge() {
|
||||
const { t } = useTranslations();
|
||||
return (
|
||||
<Badge className="border-dracula-green/30 bg-dracula-green/15 text-dracula-green">
|
||||
<Badge className="border-success/30 bg-success/15 text-success">
|
||||
{t("admin.configured")}
|
||||
</Badge>
|
||||
);
|
||||
@@ -488,11 +488,14 @@ export function useSystemStatus() {
|
||||
}
|
||||
|
||||
/** Helpful-rate tint shared by the AI-quality + Coach-feedback tables.
|
||||
* Green ≥80 %, yellow ≥50 %, orange below. */
|
||||
* The standard urgency ramp (the medication-status-pill precedent):
|
||||
* ≥80 % success, ≥50 % warning, below destructive — a sub-50 % helpful
|
||||
* rate is a genuine quality alarm, and the percent label always carries
|
||||
* the exact value alongside the tint. */
|
||||
export function helpfulRateColour(rate: number): string {
|
||||
if (rate >= 0.8) return "text-dracula-green";
|
||||
if (rate >= 0.5) return "text-dracula-yellow";
|
||||
return "text-dracula-orange";
|
||||
if (rate >= 0.8) return "text-success";
|
||||
if (rate >= 0.5) return "text-warning";
|
||||
return "text-destructive";
|
||||
}
|
||||
|
||||
export interface PublicVersion {
|
||||
|
||||
@@ -46,10 +46,10 @@ interface FeedbackSummary {
|
||||
}
|
||||
|
||||
const SEVERITY_TINT: Record<string, string> = {
|
||||
info: "text-dracula-cyan",
|
||||
suggestion: "text-dracula-purple",
|
||||
important: "text-dracula-orange",
|
||||
urgent: "text-dracula-red",
|
||||
info: "text-info",
|
||||
suggestion: "text-primary",
|
||||
important: "text-warning",
|
||||
urgent: "text-destructive",
|
||||
};
|
||||
|
||||
export function AiQualitySection() {
|
||||
|
||||
@@ -92,9 +92,7 @@ function TokenStatusBadge({
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Badge
|
||||
className={`bg-dracula-green/15 text-dracula-green shrink-0 ${sizeClass}`}
|
||||
>
|
||||
<Badge className={`bg-success/15 text-success shrink-0 ${sizeClass}`}>
|
||||
{t("common.active")}
|
||||
</Badge>
|
||||
);
|
||||
|
||||
@@ -75,12 +75,12 @@ function levelIcon(level: LogLevel) {
|
||||
case "error":
|
||||
return <AlertCircle className="text-destructive h-4 w-4" />;
|
||||
case "warn":
|
||||
return <AlertTriangle className="text-dracula-yellow h-4 w-4" />;
|
||||
return <AlertTriangle className="text-warning h-4 w-4" />;
|
||||
case "debug":
|
||||
return <Info className="text-muted-foreground h-4 w-4" />;
|
||||
case "info":
|
||||
default:
|
||||
return <CheckCircle2 className="text-dracula-green h-4 w-4" />;
|
||||
return <CheckCircle2 className="text-success h-4 w-4" />;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -171,7 +171,7 @@ export function DangerZoneSection() {
|
||||
{wipeMsg && (
|
||||
<p
|
||||
className={`mt-3 pl-7 text-sm ${
|
||||
wipeAllData.isError ? "text-destructive" : "text-dracula-green"
|
||||
wipeAllData.isError ? "text-destructive" : "text-success"
|
||||
}`}
|
||||
>
|
||||
{wipeMsg}
|
||||
|
||||
@@ -145,20 +145,20 @@ const STATUS_STYLES: Record<InviteStatus, { dot: string; chip: string }> = {
|
||||
// Quiet tinted chips with a colour dot — the dot carries the state at
|
||||
// a glance, the tint stays low so a table of mixed states reads calm.
|
||||
active: {
|
||||
dot: "bg-dracula-green",
|
||||
chip: "border-dracula-green/30 bg-dracula-green/10 text-dracula-green",
|
||||
dot: "bg-success",
|
||||
chip: "border-success/30 bg-success/10 text-success",
|
||||
},
|
||||
expired: {
|
||||
dot: "bg-muted-foreground/60",
|
||||
chip: "border-border bg-muted/40 text-muted-foreground",
|
||||
},
|
||||
exhausted: {
|
||||
dot: "bg-dracula-cyan",
|
||||
chip: "border-dracula-cyan/30 bg-dracula-cyan/10 text-dracula-cyan",
|
||||
dot: "bg-info",
|
||||
chip: "border-info/30 bg-info/10 text-info",
|
||||
},
|
||||
revoked: {
|
||||
dot: "bg-dracula-red",
|
||||
chip: "border-dracula-red/30 bg-dracula-red/10 text-dracula-red",
|
||||
dot: "bg-destructive",
|
||||
chip: "border-destructive/30 bg-destructive/10 text-destructive",
|
||||
},
|
||||
};
|
||||
|
||||
@@ -451,7 +451,7 @@ export function InviteTokensSection() {
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="text-dracula-red hover:text-dracula-red"
|
||||
className="text-destructive hover:text-destructive"
|
||||
aria-label={t("admin.invites.revokeAria")}
|
||||
disabled={revoke.isPending}
|
||||
onClick={() => setRevokeTarget(invite)}
|
||||
@@ -519,7 +519,7 @@ export function InviteTokensSection() {
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="text-dracula-red hover:text-dracula-red"
|
||||
className="text-destructive hover:text-destructive"
|
||||
disabled={revoke.isPending}
|
||||
onClick={() => setRevokeTarget(invite)}
|
||||
>
|
||||
@@ -582,7 +582,7 @@ export function InviteTokensSection() {
|
||||
className={cn(
|
||||
"min-h-11 rounded-lg border px-3 py-2 text-sm font-medium transition-colors",
|
||||
ttlDays === days
|
||||
? "border-dracula-purple/50 bg-dracula-purple/10 text-dracula-purple"
|
||||
? "border-primary/50 bg-primary/10 text-primary"
|
||||
: "border-border text-foreground hover:bg-accent",
|
||||
)}
|
||||
>
|
||||
@@ -647,7 +647,7 @@ export function InviteTokensSection() {
|
||||
</DialogHeader>
|
||||
{/* The ticket — dashed edge marks the one-time nature. */}
|
||||
<div
|
||||
className="border-dracula-purple/40 bg-background rounded-xl border border-dashed p-4"
|
||||
className="border-primary/40 bg-background rounded-xl border border-dashed p-4"
|
||||
data-testid="admin-invites-minted"
|
||||
>
|
||||
<div className="flex flex-col items-center gap-4">
|
||||
@@ -671,7 +671,7 @@ export function InviteTokensSection() {
|
||||
>
|
||||
{copied ? (
|
||||
<Check
|
||||
className="text-dracula-green size-4"
|
||||
className="text-success size-4"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
) : (
|
||||
|
||||
@@ -464,7 +464,7 @@ export function LoginOverviewSection() {
|
||||
{isFailed ? (
|
||||
<XCircle className="text-destructive h-4 w-4" />
|
||||
) : (
|
||||
<CheckCircle2 className="text-dracula-green h-4 w-4" />
|
||||
<CheckCircle2 className="text-success h-4 w-4" />
|
||||
)}
|
||||
</td>
|
||||
<td
|
||||
@@ -503,7 +503,7 @@ export function LoginOverviewSection() {
|
||||
</span>
|
||||
{copiedIp === entry.id ? (
|
||||
<Check
|
||||
className="text-dracula-green h-3 w-3 shrink-0"
|
||||
className="text-success h-3 w-3 shrink-0"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
) : (
|
||||
|
||||
@@ -115,7 +115,7 @@ export function RecentAuditPreview() {
|
||||
{isFailed ? (
|
||||
<XCircle className="text-destructive h-4 w-4" />
|
||||
) : (
|
||||
<CheckCircle2 className="text-dracula-green h-4 w-4" />
|
||||
<CheckCircle2 className="text-success h-4 w-4" />
|
||||
)}
|
||||
</span>
|
||||
<span
|
||||
|
||||
@@ -70,7 +70,7 @@ export function SystemStatusSection() {
|
||||
}
|
||||
className={
|
||||
status.database === "connected"
|
||||
? "text-dracula-green"
|
||||
? "text-success"
|
||||
: "text-destructive"
|
||||
}
|
||||
/>
|
||||
@@ -108,9 +108,7 @@ export function SystemStatusSection() {
|
||||
: t("admin.workerStopped")
|
||||
}
|
||||
className={
|
||||
status.worker.running
|
||||
? "text-dracula-green"
|
||||
: "text-destructive"
|
||||
status.worker.running ? "text-success" : "text-destructive"
|
||||
}
|
||||
/>
|
||||
{status.worker.lastReminderCheck && (
|
||||
@@ -131,7 +129,7 @@ export function SystemStatusSection() {
|
||||
}
|
||||
className={
|
||||
status.integrations.umami.enabled
|
||||
? "text-dracula-green"
|
||||
? "text-success"
|
||||
: "text-destructive"
|
||||
}
|
||||
/>
|
||||
@@ -147,7 +145,7 @@ export function SystemStatusSection() {
|
||||
}
|
||||
className={
|
||||
status.integrations.glitchtip.enabled
|
||||
? "text-dracula-green"
|
||||
? "text-success"
|
||||
: "text-destructive"
|
||||
}
|
||||
/>
|
||||
@@ -157,7 +155,7 @@ export function SystemStatusSection() {
|
||||
icon={BellRing}
|
||||
label={t("admin.integrationWebPush")}
|
||||
value={t("admin.configured")}
|
||||
className="text-dracula-green"
|
||||
className="text-success"
|
||||
/>
|
||||
)}
|
||||
{/* v1.4.27 R5 — offline GeoLite2 availability. Renders only
|
||||
@@ -175,9 +173,7 @@ export function SystemStatusSection() {
|
||||
})
|
||||
}
|
||||
className={
|
||||
version.offlineGeoEnabled
|
||||
? "text-dracula-green"
|
||||
: "text-dracula-yellow"
|
||||
version.offlineGeoEnabled ? "text-success" : "text-warning"
|
||||
}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -61,7 +61,7 @@ export function SystemStatusSummary() {
|
||||
}
|
||||
className={
|
||||
status.database === "connected"
|
||||
? "text-dracula-green"
|
||||
? "text-success"
|
||||
: "text-destructive"
|
||||
}
|
||||
/>
|
||||
@@ -74,7 +74,7 @@ export function SystemStatusSummary() {
|
||||
: t("admin.workerStopped")
|
||||
}
|
||||
className={
|
||||
status.worker.running ? "text-dracula-green" : "text-destructive"
|
||||
status.worker.running ? "text-success" : "text-destructive"
|
||||
}
|
||||
/>
|
||||
<StatusItem
|
||||
@@ -112,9 +112,7 @@ export function SystemStatusSummary() {
|
||||
})
|
||||
}
|
||||
className={
|
||||
version.offlineGeoEnabled
|
||||
? "text-dracula-green"
|
||||
: "text-dracula-yellow"
|
||||
version.offlineGeoEnabled ? "text-success" : "text-warning"
|
||||
}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -190,7 +190,7 @@ export function UserManagementSection() {
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className={`min-h-11 min-w-11 px-3 text-xs ${u.mfaEnforced ? "text-dracula-green" : ""}`}
|
||||
className={`min-h-11 min-w-11 px-3 text-xs ${u.mfaEnforced ? "text-success" : ""}`}
|
||||
onClick={() =>
|
||||
updateUser.mutate({
|
||||
id: u.id,
|
||||
@@ -554,7 +554,7 @@ export function UserManagementSection() {
|
||||
</div>
|
||||
{resetMsg && (
|
||||
<p
|
||||
className={`text-sm ${resetMsg === t("admin.passwordReset") ? "text-dracula-green" : "text-destructive"}`}
|
||||
className={`text-sm ${resetMsg === t("admin.passwordReset") ? "text-success" : "text-destructive"}`}
|
||||
>
|
||||
{resetMsg}
|
||||
</p>
|
||||
|
||||
@@ -97,7 +97,7 @@ export function VersionTileSection() {
|
||||
}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="bg-dracula-yellow/15 text-dracula-yellow border-dracula-yellow/30 inline-flex items-center gap-1.5 rounded-full border px-2.5 py-1 text-xs font-medium transition-opacity hover:opacity-80"
|
||||
className="bg-warning/15 text-warning border-warning/30 inline-flex items-center gap-1.5 rounded-full border px-2.5 py-1 text-xs font-medium transition-opacity hover:opacity-80"
|
||||
>
|
||||
<ArrowUpCircle className="h-3.5 w-3.5" aria-hidden="true" />
|
||||
{t("admin.overview.versionTileUpdateAvailable", {
|
||||
@@ -106,7 +106,7 @@ export function VersionTileSection() {
|
||||
})}
|
||||
</a>
|
||||
) : upToDate ? (
|
||||
<span className="text-dracula-green inline-flex items-center gap-1.5 text-xs font-medium">
|
||||
<span className="text-success inline-flex items-center gap-1.5 text-xs font-medium">
|
||||
<CheckCircle2 className="h-3.5 w-3.5" aria-hidden="true" />
|
||||
{t("admin.overview.versionTileUpToDate")}
|
||||
</span>
|
||||
|
||||
Reference in new issue
Block a user