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:
Logan Cusano
2026-10-25 21:29:13 -04:00
co-authored by Claude Opus 5.5
parent 3968b49b80
commit bf206334e3
39 changed files with 655 additions and 655 deletions
+14 -14
View File
@@ -114,7 +114,7 @@ function EnrollmentTokensPanel() {
</p>
<div className="flex items-start gap-2">
<p className="flex-1 text-xs text-indigo-100 font-mono break-all bg-gray-900 rounded px-2 py-1.5">{justMinted}</p>
<p className="flex-1 text-xs text-indigo-100 font-mono break-all bg-surface rounded px-2 py-1.5">{justMinted}</p>
<button
type="button"
onClick={() => navigator.clipboard?.writeText(justMinted).then(() => {
@@ -133,7 +133,7 @@ function EnrollmentTokensPanel() {
: <>(check the broker host)</>}:
</p>
<div className="flex items-start gap-2">
<pre className="flex-1 text-xs text-indigo-100 font-mono whitespace-pre-wrap break-all bg-gray-900 rounded px-2 py-1.5">{installCmd}</pre>
<pre className="flex-1 text-xs text-indigo-100 font-mono whitespace-pre-wrap break-all bg-surface rounded px-2 py-1.5">{installCmd}</pre>
<button
type="button"
onClick={() => navigator.clipboard?.writeText(installCmd).then(() => {
@@ -163,7 +163,7 @@ function EnrollmentTokensPanel() {
value={label}
onChange={(e) => setLabel(e.target.value)}
placeholder="Node ID, e.g. node-003"
className="flex-1 min-w-[12rem] bg-gray-800 border border-gray-700 rounded-lg px-3 py-1.5 text-white text-sm focus:outline-none focus:border-indigo-500"
className="flex-1 min-w-[12rem] bg-raised border border-line-strong rounded-lg px-3 py-1.5 text-white text-sm focus:outline-none focus:border-indigo-500"
/>
<Button type="submit" size="sm" disabled={minting || !label.trim()}>
{minting ? "Minting…" : "New token"}
@@ -176,11 +176,11 @@ function EnrollmentTokensPanel() {
<SkeletonRow cols={1} />
</div>
) : tokens.length === 0 ? (
<p className="text-gray-600 text-xs">No enrollment tokens yet.</p>
<p className="text-ink-muted text-xs">No enrollment tokens yet.</p>
) : (
<table className="w-full text-sm">
<thead>
<tr className="text-xs text-gray-500 uppercase tracking-wider border-b border-gray-800">
<tr className="text-xs text-ink-muted uppercase tracking-wider border-b border-line">
<th className="py-2 text-left">Label</th>
<th className="py-2 text-left hidden sm:table-cell">Created</th>
<th className="py-2 text-left">Status</th>
@@ -189,14 +189,14 @@ function EnrollmentTokensPanel() {
</thead>
<tbody>
{tokens.map((t) => (
<tr key={t.token_id} className="border-b border-gray-800 last:border-0">
<tr key={t.token_id} className="border-b border-line last:border-0">
<td className="py-2 text-white">{t.label}</td>
<td className="py-2 text-gray-500 text-xs hidden sm:table-cell">
<td className="py-2 text-ink-muted text-xs hidden sm:table-cell">
{new Date(t.created_at).toLocaleDateString()}
</td>
<td className="py-2 text-xs">
{t.revoked ? (
<span className="text-gray-600">Revoked</span>
<span className="text-ink-muted">Revoked</span>
) : (
<span className="text-green-400">Active · {t.uses} use{t.uses !== 1 ? "s" : ""}</span>
)}
@@ -283,7 +283,7 @@ export default function NodeOwnershipSettingsPage() {
<EnrollmentTokensPanel />
<div className="space-y-4">
<p className="text-sm text-gray-500">
<p className="text-sm text-ink-muted">
Assign each node to the operator responsible for it. Operators only see and manage the nodes assigned to them here.
</p>
@@ -292,7 +292,7 @@ export default function NodeOwnershipSettingsPage() {
<Card padding="none" className="overflow-hidden">
<table className="w-full text-sm">
<thead>
<tr className="text-xs text-gray-500 uppercase tracking-wider border-b border-gray-800 bg-gray-900">
<tr className="text-xs text-ink-muted uppercase tracking-wider border-b border-line bg-surface">
<th className="px-4 py-3 text-left">Node</th>
<th className="px-4 py-3 text-left hidden sm:table-cell">Status</th>
<th className="px-4 py-3 text-left">Owner</th>
@@ -302,17 +302,17 @@ export default function NodeOwnershipSettingsPage() {
{loading ? (
Array.from({ length: 3 }).map((_, i) => <SkeletonRow key={i} cols={3} />)
) : nodes.length === 0 ? (
<tr><td colSpan={3} className="px-4 py-8 text-center text-gray-600 text-sm">No nodes registered yet.</td></tr>
<tr><td colSpan={3} className="px-4 py-8 text-center text-ink-muted text-sm">No nodes registered yet.</td></tr>
) : (
nodes.map((n) => {
const owner = ownerByNode.get(n.node_id);
return (
<tr key={n.node_id} className="border-b border-gray-800 last:border-0">
<tr key={n.node_id} className="border-b border-line last:border-0">
<td className="px-4 py-3">
<Link href={`/nodes/${n.node_id}`} className="text-white hover:text-indigo-300 transition-colors">
{n.name}
</Link>
<p className="text-gray-600 text-xs font-mono">{n.node_id}</p>
<p className="text-ink-muted text-xs font-mono">{n.node_id}</p>
</td>
<td className="px-4 py-3 hidden sm:table-cell"><StatusBadge status={n.status} /></td>
<td className="px-4 py-3">
@@ -320,7 +320,7 @@ export default function NodeOwnershipSettingsPage() {
value={owner?.uid ?? UNASSIGNED}
disabled={savingNodeId === n.node_id}
onChange={(e) => reassign(n.node_id, e.target.value)}
className="bg-gray-800 border border-gray-700 rounded-lg px-2 py-1.5 text-xs text-white focus:outline-none focus:border-indigo-500 disabled:opacity-50 max-w-[14rem]"
className="bg-raised border border-line-strong rounded-lg px-2 py-1.5 text-xs text-white focus:outline-none focus:border-indigo-500 disabled:opacity-50 max-w-[14rem]"
>
<option value={UNASSIGNED}>Unassigned</option>
{assignable.map((u) => (