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
+13 -13
View File
@@ -46,8 +46,8 @@ function InviteModal({ onClose, onCreated }: { onClose: () => void; onCreated: (
<div className="fixed inset-0 z-50 bg-black/70 flex items-center justify-center p-4">
<Card padding="lg" className="w-full max-w-md space-y-4">
<h2 className="text-white font-semibold">Member invited</h2>
<p className="text-xs text-gray-400">Share this one-time invite link so they can set their password. It expires after use.</p>
<div className="bg-gray-800 border border-gray-700 rounded-lg p-3">
<p className="text-xs text-ink-2">Share this one-time invite link so they can set their password. It expires after use.</p>
<div className="bg-raised border border-line-strong rounded-lg p-3">
<p className="text-xs text-indigo-300 break-all">{inviteLink}</p>
</div>
<Button onClick={onClose} fullWidth>Done</Button>
@@ -62,18 +62,18 @@ function InviteModal({ onClose, onCreated }: { onClose: () => void; onCreated: (
<h2 className="text-white font-semibold mb-4">Invite a member</h2>
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label className="text-xs text-gray-400 block mb-1">Email</label>
<label className="text-xs text-ink-2 block mb-1">Email</label>
<input
type="email" required value={email} onChange={(e) => setEmail(e.target.value)}
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"
placeholder="teammate@example.com"
/>
</div>
<div>
<label className="text-xs text-gray-400 block mb-1">Role</label>
<label className="text-xs text-ink-2 block mb-1">Role</label>
<select
value={role} onChange={(e) => setRole(e.target.value as UserRole)}
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"
>
<option value="admin">Admin — full access</option>
<option value="operator">Operator — owns nodes</option>
@@ -130,7 +130,7 @@ export default function MembersSettingsPage() {
)}
<div className="flex items-center justify-between">
<p className="text-sm text-gray-500">
<p className="text-sm text-ink-muted">
{loading ? "Loading members…" : `${users.length} member${users.length !== 1 ? "s" : ""}`}
</p>
<Button size="sm" onClick={() => setShowInvite(true)}>+ Invite member</Button>
@@ -144,7 +144,7 @@ export default function MembersSettingsPage() {
<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">Member</th>
<th className="px-4 py-3 text-left">Role</th>
<th className="px-4 py-3 text-left hidden sm:table-cell">Owned nodes</th>
@@ -155,10 +155,10 @@ export default function MembersSettingsPage() {
{loading
? Array.from({ length: 3 }).map((_, i) => <SkeletonRow key={i} cols={4} />)
: users.map((u) => (
<tr key={u.uid} className="border-b border-gray-800 last:border-0">
<tr key={u.uid} className="border-b border-line last:border-0">
<td className="px-4 py-3">
<p className="text-white">{u.display_name || u.email}</p>
{u.display_name && <p className="text-gray-600 text-xs">{u.email}</p>}
{u.display_name && <p className="text-ink-muted text-xs">{u.email}</p>}
</td>
<td className="px-4 py-3">
{u.uid === user?.uid ? (
@@ -168,7 +168,7 @@ export default function MembersSettingsPage() {
value={u.role}
disabled={savingUid === u.uid}
onChange={(e) => handleRoleChange(u, e.target.value as UserRole)}
className="bg-gray-800 border border-gray-700 rounded-lg px-2 py-1 text-xs text-white focus:outline-none focus:border-indigo-500 disabled:opacity-50"
className="bg-raised border border-line-strong rounded-lg px-2 py-1 text-xs text-white focus:outline-none focus:border-indigo-500 disabled:opacity-50"
>
<option value="admin">Admin</option>
<option value="operator">Operator</option>
@@ -176,7 +176,7 @@ export default function MembersSettingsPage() {
</select>
)}
</td>
<td className="px-4 py-3 text-gray-400 text-xs hidden sm:table-cell">
<td className="px-4 py-3 text-ink-2 text-xs hidden sm:table-cell">
{u.role === "operator" ? u.owned_node_ids.length : "—"}
</td>
<td className="px-4 py-3 hidden md:table-cell">
@@ -189,7 +189,7 @@ export default function MembersSettingsPage() {
</Card>
)}
<p className="text-xs text-gray-600 font-mono">
<p className="text-xs text-ink-muted font-mono">
Need to disable or delete a member? Use the full user admin panel under Admin → Users.
</p>
</div>