fix(frontend): finish the token migration — raw grey classes -> theme tokens
UI_REDESIGN chunk 4 was never finished: 39 app/component files still used raw Tailwind greys, kept readable in light mode only by the !important override sheet. In dark mode text-gray-600 is ~2.3:1 on surface, and the light override maps it to #94a3b8 (~2.6:1 on white) — unreadable in both. Mechanical mapping (886 classes): gray-100/200 -> ink, 300/400 -> ink-2, 500/600/700 -> ink-muted, bg 950/900/800 -> page/surface/raised, border 800 -> line, 700/600 -> line-strong, plus hover/divide/placeholder. Alpha suffixes kept (tokens are color-mix, so /60 works). bg-gray-700, text-white and marketing pages left alone. text-[9px] -> text-[10px]. Frontend unverified: no Node on the authoring box (#207). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
3968b49b80
commit
bf206334e3
@@ -74,39 +74,39 @@ function RulesTab({ isAdmin }: { isAdmin: boolean }) {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{isAdmin && (
|
||||
<form onSubmit={handleCreate} className="bg-gray-900 border border-gray-800 rounded-xl p-5 space-y-4">
|
||||
<form onSubmit={handleCreate} className="bg-surface border border-line rounded-xl p-5 space-y-4">
|
||||
<h3 className="text-white font-mono text-sm font-bold">New Alert Rule</h3>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="text-xs text-gray-400 block mb-1">Name</label>
|
||||
<label className="text-xs text-ink-2 block mb-1">Name</label>
|
||||
<input
|
||||
required value={name} onChange={(e) => setName(e.target.value)}
|
||||
placeholder="e.g. Structure Fire"
|
||||
className="w-full bg-gray-800 border border-gray-700 rounded-lg px-3 py-2 text-white text-sm focus:outline-none focus:border-indigo-500"
|
||||
className="w-full bg-raised border border-line-strong rounded-lg px-3 py-2 text-white text-sm focus:outline-none focus:border-indigo-500"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-xs text-gray-400 block mb-1">Keywords (comma-separated)</label>
|
||||
<label className="text-xs text-ink-2 block mb-1">Keywords (comma-separated)</label>
|
||||
<input
|
||||
value={keywords} onChange={(e) => setKeywords(e.target.value)}
|
||||
placeholder="fire, smoke, structure"
|
||||
className="w-full bg-gray-800 border border-gray-700 rounded-lg px-3 py-2 text-white text-sm focus:outline-none focus:border-indigo-500"
|
||||
className="w-full bg-raised border border-line-strong rounded-lg px-3 py-2 text-white text-sm focus:outline-none focus:border-indigo-500"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-xs text-gray-400 block mb-1">Talkgroup IDs (comma-separated)</label>
|
||||
<label className="text-xs text-ink-2 block mb-1">Talkgroup IDs (comma-separated)</label>
|
||||
<input
|
||||
value={tgIds} onChange={(e) => setTgIds(e.target.value)}
|
||||
placeholder="9048, 9600"
|
||||
className="w-full bg-gray-800 border border-gray-700 rounded-lg px-3 py-2 text-white text-sm focus:outline-none focus:border-indigo-500"
|
||||
className="w-full bg-raised border border-line-strong rounded-lg px-3 py-2 text-white text-sm focus:outline-none focus:border-indigo-500"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-xs text-gray-400 block mb-1">Discord Webhook URL (optional)</label>
|
||||
<label className="text-xs text-ink-2 block mb-1">Discord Webhook URL (optional)</label>
|
||||
<input
|
||||
value={webhook} onChange={(e) => setWebhook(e.target.value)}
|
||||
placeholder="https://discord.com/api/webhooks/…"
|
||||
className="w-full bg-gray-800 border border-gray-700 rounded-lg px-3 py-2 text-white text-sm focus:outline-none focus:border-indigo-500"
|
||||
className="w-full bg-raised border border-line-strong rounded-lg px-3 py-2 text-white text-sm focus:outline-none focus:border-indigo-500"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -120,13 +120,13 @@ function RulesTab({ isAdmin }: { isAdmin: boolean }) {
|
||||
</form>
|
||||
)}
|
||||
|
||||
<div className="bg-gray-900 border border-gray-800 rounded-xl overflow-hidden">
|
||||
<div className="bg-surface border border-line rounded-xl overflow-hidden">
|
||||
{rules.length === 0 ? (
|
||||
<p className="text-gray-600 text-sm font-mono p-4">No alert rules configured.</p>
|
||||
<p className="text-ink-muted text-sm font-mono p-4">No alert rules configured.</p>
|
||||
) : (
|
||||
<table className="w-full text-left">
|
||||
<thead>
|
||||
<tr className="border-b border-gray-800 text-xs text-gray-500 uppercase">
|
||||
<tr className="border-b border-line text-xs text-ink-muted uppercase">
|
||||
<th className="px-4 py-3">Name</th>
|
||||
<th className="px-4 py-3">Keywords</th>
|
||||
<th className="px-4 py-3">Talkgroups</th>
|
||||
@@ -137,11 +137,11 @@ function RulesTab({ isAdmin }: { isAdmin: boolean }) {
|
||||
</thead>
|
||||
<tbody>
|
||||
{rules.map((rule) => (
|
||||
<tr key={rule.rule_id} className="border-b border-gray-800">
|
||||
<tr key={rule.rule_id} className="border-b border-line">
|
||||
<td className="px-4 py-3 text-white text-sm">{rule.name}</td>
|
||||
<td className="px-4 py-3 text-gray-400 text-xs font-mono">{rule.keywords.join(", ") || "—"}</td>
|
||||
<td className="px-4 py-3 text-gray-400 text-xs font-mono">{rule.talkgroup_ids.join(", ") || "—"}</td>
|
||||
<td className="px-4 py-3 text-gray-400 text-xs">
|
||||
<td className="px-4 py-3 text-ink-2 text-xs font-mono">{rule.keywords.join(", ") || "—"}</td>
|
||||
<td className="px-4 py-3 text-ink-2 text-xs font-mono">{rule.talkgroup_ids.join(", ") || "—"}</td>
|
||||
<td className="px-4 py-3 text-ink-2 text-xs">
|
||||
{rule.discord_webhook ? (
|
||||
<span className="text-green-400">configured</span>
|
||||
) : "—"}
|
||||
@@ -153,13 +153,13 @@ function RulesTab({ isAdmin }: { isAdmin: boolean }) {
|
||||
className={`text-xs px-2 py-0.5 rounded-full transition-colors ${
|
||||
rule.enabled
|
||||
? "bg-green-900 text-green-300 hover:bg-green-800"
|
||||
: "bg-gray-800 text-gray-400 hover:bg-gray-700"
|
||||
: "bg-raised text-ink-2 hover:bg-gray-700"
|
||||
}`}
|
||||
>
|
||||
{rule.enabled ? "enabled" : "disabled"}
|
||||
</button>
|
||||
) : (
|
||||
<span className={`text-xs ${rule.enabled ? "text-green-400" : "text-gray-500"}`}>
|
||||
<span className={`text-xs ${rule.enabled ? "text-green-400" : "text-ink-muted"}`}>
|
||||
{rule.enabled ? "enabled" : "disabled"}
|
||||
</span>
|
||||
)}
|
||||
@@ -209,13 +209,13 @@ export default function AlertsPage() {
|
||||
</div>
|
||||
|
||||
{/* Tabs */}
|
||||
<div className="flex gap-1 bg-gray-900 border border-gray-800 rounded-lg p-1 w-fit">
|
||||
<div className="flex gap-1 bg-surface border border-line rounded-lg p-1 w-fit">
|
||||
{(["events", ...(isAdmin ? ["rules"] : [])] as const).map((t) => (
|
||||
<button
|
||||
key={t}
|
||||
onClick={() => setTab(t as "events" | "rules")}
|
||||
className={`text-sm font-mono px-4 py-1.5 rounded-md transition-colors capitalize ${
|
||||
tab === t ? "bg-gray-800 text-white" : "text-gray-500 hover:text-gray-300"
|
||||
tab === t ? "bg-raised text-white" : "text-ink-muted hover:text-ink"
|
||||
}`}
|
||||
>
|
||||
{t === "events" ? "Triggered Alerts" : "Rules"}
|
||||
@@ -225,7 +225,7 @@ export default function AlertsPage() {
|
||||
|
||||
{tab === "events" && (
|
||||
loading ? (
|
||||
<p className="text-gray-500 text-sm font-mono">Loading…</p>
|
||||
<p className="text-ink-muted text-sm font-mono">Loading…</p>
|
||||
) : error ? (
|
||||
<p className="text-red-400 text-sm font-mono">
|
||||
{/requires an index|PERMISSION_DENIED|insufficient permissions/i.test(error)
|
||||
@@ -233,7 +233,7 @@ export default function AlertsPage() {
|
||||
: `Couldn't load alerts: ${error}`}
|
||||
</p>
|
||||
) : alerts.length === 0 ? (
|
||||
<p className="text-gray-600 text-sm font-mono">No alerts triggered yet.</p>
|
||||
<p className="text-ink-muted text-sm font-mono">No alerts triggered yet.</p>
|
||||
) : (
|
||||
<div className="space-y-3">
|
||||
{/* Gate A / A2 (server-26#46) — the Snippet column is transcript text,
|
||||
@@ -242,10 +242,10 @@ export default function AlertsPage() {
|
||||
variant="inline"
|
||||
detail="alerts match against automated transcripts and may fire on, or miss, the wrong words."
|
||||
/>
|
||||
<div className="bg-gray-900 border border-gray-800 rounded-xl overflow-hidden">
|
||||
<div className="bg-surface border border-line rounded-xl overflow-hidden">
|
||||
<table className="w-full text-left">
|
||||
<thead>
|
||||
<tr className="border-b border-gray-800 text-xs text-gray-500 uppercase">
|
||||
<tr className="border-b border-line text-xs text-ink-muted uppercase">
|
||||
<th className="px-4 py-3">Rule</th>
|
||||
<th className="px-4 py-3">Talkgroup</th>
|
||||
<th className="px-4 py-3">Matched</th>
|
||||
@@ -258,10 +258,10 @@ export default function AlertsPage() {
|
||||
{alerts.map((alert) => (
|
||||
<tr
|
||||
key={alert.alert_id}
|
||||
className={`border-b border-gray-800 ${alert.acknowledged ? "opacity-50" : ""}`}
|
||||
className={`border-b border-line ${alert.acknowledged ? "opacity-50" : ""}`}
|
||||
>
|
||||
<td className="px-4 py-3 text-white text-sm">{alert.rule_name}</td>
|
||||
<td className="px-4 py-3 text-gray-400 text-xs font-mono">
|
||||
<td className="px-4 py-3 text-ink-2 text-xs font-mono">
|
||||
{alert.talkgroup_name || alert.talkgroup_id || "—"}
|
||||
</td>
|
||||
<td className="px-4 py-3">
|
||||
@@ -273,17 +273,17 @@ export default function AlertsPage() {
|
||||
))}
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-4 py-3 text-gray-400 text-xs font-mono max-w-xs truncate">
|
||||
<td className="px-4 py-3 text-ink-2 text-xs font-mono max-w-xs truncate">
|
||||
{alert.transcript_snippet || "—"}
|
||||
</td>
|
||||
<td className="px-4 py-3 text-gray-400 text-xs font-mono">{fmtTime(alert.triggered_at)}</td>
|
||||
<td className="px-4 py-3 text-ink-2 text-xs font-mono">{fmtTime(alert.triggered_at)}</td>
|
||||
<td className="px-4 py-3">
|
||||
{alert.acknowledged ? (
|
||||
<span className="text-xs text-gray-500">acked</span>
|
||||
<span className="text-xs text-ink-muted">acked</span>
|
||||
) : (
|
||||
<button
|
||||
onClick={() => handleAcknowledge(alert.alert_id)}
|
||||
className="text-xs bg-gray-800 hover:bg-gray-700 text-gray-300 px-2 py-1 rounded transition-colors"
|
||||
className="text-xs bg-raised hover:bg-gray-700 text-ink-2 px-2 py-1 rounded transition-colors"
|
||||
>
|
||||
Acknowledge
|
||||
</button>
|
||||
|
||||
Reference in New Issue
Block a user