fix(canvas): restore text-white on saturated buttons + close zinc gaps

Independent code review of #2555 caught two contrast regressions left
by the bulk perl pass:

1. text-white → text-ink mass-substitution silently broke destructive
   and primary buttons. text-ink resolves to #15181c (warm-paper
   near-black) in light mode — dark text on bg-red-600 / bg-amber-600
   / bg-emerald-600 / bg-blue-600 / bg-accent / bg-accent-strong /
   bg-good / bg-bad fails WCAG contrast and looks broken. Per-line
   pass flips text-ink → text-white only when a saturated bg utility
   is present; tinted-state pills (bg-red-950/50 etc.) keep their
   intentionally-retained text-* literals.

2. Original mapping table was missing bg-zinc-600 (most-used
   hover-state literal for cancel buttons — caused them to JUMP from
   warm cream resting state to dark zinc on hover in light mode) and
   text-zinc-700/800/900 (separator dots and decorative dim text
   invisible on warm-paper light bg). Extended mapping fills these
   gaps with bg-surface-card / text-ink-soft.

Also: drop stale tailwind.config.ts reference from components.json
(file deleted by the v3→v4 migration); switch baseColor zinc →
neutral and enable cssVariables since v4 uses CSS-driven tokens.
Future shadcn-cli invocations would have failed or written malformed
components without this.

27 sites in 27 files affected by #1, ~20 sites in 20 files by #2.
1214/1214 unit tests still pass; build still clean.

Findings courtesy of multi-model review per code-review-and-quality
skill — different blind spots catch different bugs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Hongming Wang 2026-05-03 02:04:20 -07:00
parent 052575d773
commit db48d1d261
38 changed files with 85 additions and 86 deletions

View File

@ -4,10 +4,9 @@
"rsc": true,
"tsx": true,
"tailwind": {
"config": "tailwind.config.ts",
"css": "src/app/globals.css",
"baseColor": "zinc",
"cssVariables": false
"baseColor": "neutral",
"cssVariables": true
},
"aliases": {
"components": "@/components",

View File

@ -283,7 +283,7 @@ function OrgCTA({ org }: { org: Org }) {
return (
<a
href={href}
className="rounded bg-emerald-600 px-4 py-2 text-sm font-medium text-ink hover:bg-emerald-500"
className="rounded bg-emerald-600 px-4 py-2 text-sm font-medium text-white hover:bg-emerald-500"
>
Open
</a>
@ -293,7 +293,7 @@ function OrgCTA({ org }: { org: Org }) {
return (
<a
href={`/pricing?org=${encodeURIComponent(org.slug)}`}
className="rounded bg-amber-600 px-4 py-2 text-sm font-medium text-ink hover:bg-amber-500"
className="rounded bg-amber-600 px-4 py-2 text-sm font-medium text-white hover:bg-amber-500"
>
Complete payment
</a>
@ -303,7 +303,7 @@ function OrgCTA({ org }: { org: Org }) {
return (
<a
href="mailto:support@moleculesai.app"
className="rounded bg-surface-card px-4 py-2 text-sm font-medium text-ink hover:bg-zinc-600"
className="rounded bg-surface-card px-4 py-2 text-sm font-medium text-ink hover:bg-surface-card"
>
Contact support
</a>
@ -395,7 +395,7 @@ function CreateOrgForm({ onCreated }: { onCreated: (slug: string) => void }) {
<button
type="submit"
disabled={submitting}
className="rounded bg-accent-strong px-4 py-2 text-sm font-medium text-ink hover:bg-accent disabled:opacity-50"
className="rounded bg-accent-strong px-4 py-2 text-sm font-medium text-white hover:bg-accent disabled:opacity-50"
>
{submitting ? "Creating…" : "Create organization"}
</button>

View File

@ -87,7 +87,7 @@ export default function Home() {
setHydrationError(null);
window.location.reload();
}}
className="px-4 py-2 bg-accent-strong hover:bg-accent text-ink rounded-md text-sm"
className="px-4 py-2 bg-accent-strong hover:bg-accent text-white rounded-md text-sm"
>
Retry
</button>
@ -129,7 +129,7 @@ brew services start redis`}</pre>
</p>
<button
onClick={() => window.location.reload()}
className="px-4 py-2 bg-accent-strong hover:bg-accent text-ink rounded-md text-sm mt-2"
className="px-4 py-2 bg-accent-strong hover:bg-accent text-white rounded-md text-sm mt-2"
>
Reload
</button>

View File

@ -73,14 +73,14 @@ export function ApprovalBanner() {
<button
type="button"
onClick={() => handleDecide(approval, "approved")}
className="px-3 py-1.5 bg-emerald-600 hover:bg-emerald-500 text-xs rounded-lg text-ink font-medium transition-colors"
className="px-3 py-1.5 bg-emerald-600 hover:bg-emerald-500 text-xs rounded-lg text-white font-medium transition-colors"
>
Approve
</button>
<button
type="button"
onClick={() => handleDecide(approval, "denied")}
className="px-3 py-1.5 bg-surface-card hover:bg-zinc-600 text-xs rounded-lg text-ink-mid transition-colors"
className="px-3 py-1.5 bg-surface-card hover:bg-surface-card text-xs rounded-lg text-ink-mid transition-colors"
>
Deny
</button>

View File

@ -174,7 +174,7 @@ export function AuditTrailPanel({ workspaceId }: Props) {
{entries.length === 0 ? (
/* Empty state */
<div className="flex flex-col items-center justify-center py-16 gap-3 text-center">
<span className="text-4xl text-zinc-700" aria-hidden="true"></span>
<span className="text-4xl text-ink-soft" aria-hidden="true"></span>
<p className="text-sm font-medium text-ink-mid">No audit events yet</p>
<p className="text-[11px] text-ink-soft max-w-[200px] leading-relaxed">
Delegation, decision, gate, and human-in-the-loop events will appear here.

View File

@ -83,7 +83,7 @@ export function BatchActionBar() {
className="fixed bottom-6 left-1/2 -translate-x-1/2 z-[200] flex items-center gap-3 px-4 py-2.5 rounded-2xl bg-surface-sunken/95 border border-line/70 shadow-2xl shadow-black/50 backdrop-blur-md"
>
{/* Selection count badge */}
<span className="text-[12px] font-semibold text-ink bg-accent-strong/80 px-2.5 py-0.5 rounded-full tabular-nums">
<span className="text-[12px] font-semibold text-white bg-accent-strong/80 px-2.5 py-0.5 rounded-full tabular-nums">
{count} selected
</span>

View File

@ -93,10 +93,10 @@ export function ConfirmDialog({
const confirmColors =
confirmVariant === "danger"
? "bg-red-600 hover:bg-red-500 text-ink"
? "bg-red-600 hover:bg-red-500 text-white"
: confirmVariant === "warning"
? "bg-amber-600 hover:bg-amber-500 text-ink"
: "bg-accent-strong hover:bg-accent text-ink";
? "bg-amber-600 hover:bg-amber-500 text-white"
: "bg-accent-strong hover:bg-accent text-white";
// Render via Portal so the fixed-position dialog escapes any containing block
// (e.g. parents with transform, filter, will-change that break position:fixed).

View File

@ -161,7 +161,7 @@ export function ConversationTraceModal({ open, workspaceId: _workspaceId, onClos
? "bg-cyan-500"
: isReceive
? "bg-accent"
: "bg-zinc-600"
: "bg-surface-card"
}`}
/>
<div className="w-px flex-1 bg-surface-card min-h-[8px]" />

View File

@ -137,7 +137,7 @@ export function CookieConsent() {
<button
type="button"
onClick={() => decide("accepted")}
className="rounded border border-accent bg-accent-strong px-4 py-2 text-sm font-medium text-ink hover:bg-accent"
className="rounded border border-accent bg-accent-strong px-4 py-2 text-sm font-medium text-white hover:bg-accent"
>
Accept all
</button>

View File

@ -310,7 +310,7 @@ export function CreateWorkspaceButton() {
return (
<Dialog.Root open={open} onOpenChange={setOpen}>
<Dialog.Trigger asChild>
<button type="button" className="fixed bottom-6 right-6 z-40 px-5 py-2.5 bg-accent-strong hover:bg-accent active:bg-accent-strong text-sm font-medium rounded-xl text-ink shadow-lg shadow-blue-600/20 hover:shadow-xl hover:shadow-blue-500/30 transition-all duration-200 flex items-center gap-2">
<button type="button" className="fixed bottom-6 right-6 z-40 px-5 py-2.5 bg-accent-strong hover:bg-accent active:bg-accent-strong text-sm font-medium rounded-xl text-white shadow-lg shadow-blue-600/20 hover:shadow-xl hover:shadow-blue-500/30 transition-all duration-200 flex items-center gap-2">
<svg
width="14"
height="14"
@ -560,7 +560,7 @@ export function CreateWorkspaceButton() {
type="button"
onClick={handleCreate}
disabled={creating}
className="px-5 py-2 bg-accent-strong hover:bg-accent active:bg-accent-strong text-sm rounded-lg text-ink disabled:opacity-50 transition-colors"
className="px-5 py-2 bg-accent-strong hover:bg-accent active:bg-accent-strong text-sm rounded-lg text-white disabled:opacity-50 transition-colors"
>
{creating ? "Creating..." : "Create"}
</button>

View File

@ -155,7 +155,7 @@ export function DeleteCascadeConfirmDialog({
disabled={!checked}
className={`px-3.5 py-1.5 text-[13px] rounded-lg transition-colors
${checked
? "bg-red-600 hover:bg-red-500 text-ink cursor-pointer"
? "bg-red-600 hover:bg-red-500 text-white cursor-pointer"
: "bg-red-900/30 text-bad/40 cursor-not-allowed"
}`}
>

View File

@ -169,9 +169,9 @@ export function EmptyState() {
<div className="mt-5 pt-4 border-t border-line/50">
<div className="flex items-center justify-center gap-6 text-[10px] text-ink-mid">
<span>Drag to nest workspaces into teams</span>
<span className="text-zinc-700">|</span>
<span className="text-ink-soft">|</span>
<span>Right-click for actions</span>
<span className="text-zinc-700">|</span>
<span className="text-ink-soft">|</span>
<span>Press <kbd className="px-1 py-0.5 bg-surface-card rounded text-ink-soft font-mono">&#8984;K</kbd> to search</span>
</div>
</div>

View File

@ -83,7 +83,7 @@ export class ErrorBoundary extends React.Component<
<button
type="button"
onClick={this.handleReload}
className="rounded-lg bg-accent-strong hover:bg-accent px-5 py-2 text-sm font-medium text-ink transition-colors"
className="rounded-lg bg-accent-strong hover:bg-accent px-5 py-2 text-sm font-medium text-white transition-colors"
>
Reload
</button>

View File

@ -256,7 +256,7 @@ function SnippetBlock({
<button
type="button"
onClick={onCopy}
className="text-xs px-2 py-1 rounded bg-accent-strong/80 hover:bg-accent text-ink"
className="text-xs px-2 py-1 rounded bg-accent-strong/80 hover:bg-accent text-white"
>
{copied ? "Copied!" : "Copy"}
</button>

View File

@ -167,7 +167,7 @@ export function MemoryInspectorPanel({ workspaceId }: Props) {
className={[
"px-3 py-1 text-[11px] rounded transition-colors",
activeScope === scope
? "bg-accent-strong text-ink"
? "bg-accent-strong text-white"
: "bg-surface-card text-ink-mid hover:bg-surface-card hover:text-ink",
].join(" ")}
>
@ -269,7 +269,7 @@ export function MemoryInspectorPanel({ workspaceId }: Props) {
) : entries.length === 0 ? (
debouncedQuery ? (
<div className="flex flex-col items-center justify-center py-16 gap-3 text-center">
<span className="text-4xl text-zinc-700" aria-hidden="true"></span>
<span className="text-4xl text-ink-soft" aria-hidden="true"></span>
<p className="text-sm font-medium text-ink-mid">
No memories match your search
</p>
@ -290,7 +290,7 @@ export function MemoryInspectorPanel({ workspaceId }: Props) {
</div>
) : (
<div className="flex flex-col items-center justify-center py-16 gap-3 text-center">
<span className="text-4xl text-zinc-700" aria-hidden="true"></span>
<span className="text-4xl text-ink-soft" aria-hidden="true"></span>
<p className="text-sm font-medium text-ink-mid">No {activeScope} memories</p>
<p className="text-[11px] text-ink-soft max-w-[200px] leading-relaxed">
{activeScope === "LOCAL"

View File

@ -451,7 +451,7 @@ function ProviderPickerModal({
<button
onClick={() => handleSaveKey(index)}
disabled={!entry.value.trim() || entry.saving}
className="px-3 py-1.5 bg-accent-strong hover:bg-accent text-[11px] rounded text-ink disabled:opacity-30 transition-colors shrink-0"
className="px-3 py-1.5 bg-accent-strong hover:bg-accent text-[11px] rounded text-white disabled:opacity-30 transition-colors shrink-0"
>
{entry.saving ? "..." : "Save"}
</button>
@ -492,7 +492,7 @@ function ProviderPickerModal({
!selectorValue.providerId ||
(showModelInput && model.trim() === "")
}
className="px-3.5 py-1.5 text-[12px] bg-accent-strong hover:bg-accent text-ink rounded-lg transition-colors disabled:opacity-40"
className="px-3.5 py-1.5 text-[12px] bg-accent-strong hover:bg-accent text-white rounded-lg transition-colors disabled:opacity-40"
>
{allSaved ? "Deploy" : entries.length > 1 ? "Add Keys" : "Add Key"}
</button>
@ -706,7 +706,7 @@ function AllKeysModal({
type="button"
onClick={() => handleSaveKey(index)}
disabled={!entry.value.trim() || entry.saving}
className="px-3 py-1.5 bg-accent-strong hover:bg-accent text-[11px] rounded text-ink disabled:opacity-30 transition-colors shrink-0"
className="px-3 py-1.5 bg-accent-strong hover:bg-accent text-[11px] rounded text-white disabled:opacity-30 transition-colors shrink-0"
>
{entry.saving ? "..." : "Save"}
</button>
@ -748,7 +748,7 @@ function AllKeysModal({
type="button"
onClick={handleAddKeysAndDeploy}
disabled={!allSaved || anySaving}
className="px-3.5 py-1.5 text-[12px] bg-accent-strong hover:bg-accent text-ink rounded-lg transition-colors disabled:opacity-40"
className="px-3.5 py-1.5 text-[12px] bg-accent-strong hover:bg-accent text-white rounded-lg transition-colors disabled:opacity-40"
>
{anySaving ? "Saving..." : allSaved ? "Deploy" : "Add Keys"}
</button>

View File

@ -181,7 +181,7 @@ export function OnboardingWizard() {
<button
type="button"
onClick={handleAction}
className="flex-1 px-3 py-1.5 bg-accent-strong/90 hover:bg-accent rounded-lg text-[11px] font-medium text-ink transition-colors"
className="flex-1 px-3 py-1.5 bg-accent-strong/90 hover:bg-accent rounded-lg text-[11px] font-medium text-white transition-colors"
>
{step === "welcome"
? "Create Workspace"

View File

@ -308,7 +308,7 @@ export function OrgImportPreflightModal({
type="button"
onClick={onProceed}
disabled={!canProceed}
className="px-4 py-1.5 text-[11px] font-semibold rounded bg-accent-strong hover:bg-accent text-ink disabled:bg-surface-card disabled:text-ink-soft disabled:cursor-not-allowed"
className="px-4 py-1.5 text-[11px] font-semibold rounded bg-accent-strong hover:bg-accent text-white disabled:bg-surface-card disabled:text-white-soft disabled:cursor-not-allowed"
>
Import
</button>
@ -428,7 +428,7 @@ function StrictEnvRow({
type="button"
onClick={() => onSave(envKey)}
disabled={d?.saving || !d?.value.trim()}
className="px-2 py-1 text-[10px] rounded bg-accent-strong hover:bg-accent text-ink disabled:opacity-40 disabled:cursor-not-allowed"
className="px-2 py-1 text-[10px] rounded bg-accent-strong hover:bg-accent text-white disabled:opacity-40 disabled:cursor-not-allowed"
>
{d?.saving ? "…" : "Save"}
</button>
@ -520,7 +520,7 @@ function AnyOfEnvGroup({
type="button"
onClick={() => onSave(m)}
disabled={d?.saving || !d?.value.trim()}
className="px-2 py-1 text-[10px] rounded bg-accent-strong hover:bg-accent text-ink disabled:opacity-40 disabled:cursor-not-allowed"
className="px-2 py-1 text-[10px] rounded bg-accent-strong hover:bg-accent text-white disabled:opacity-40 disabled:cursor-not-allowed"
>
{d?.saving ? "…" : "Save"}
</button>

View File

@ -130,7 +130,7 @@ function PlanCard({
disabled={loading}
className={`mt-6 rounded-lg px-4 py-3 text-sm font-medium ${
plan.highlighted
? "bg-accent-strong text-ink hover:bg-accent disabled:bg-blue-900"
? "bg-accent-strong text-white hover:bg-accent disabled:bg-blue-900"
: "border border-line bg-surface-sunken text-ink hover:bg-surface-card disabled:opacity-50"
}`}
>

View File

@ -341,7 +341,7 @@ export function ProvisioningTimeout({
type="button"
onClick={() => handleRetry(entry.workspaceId)}
disabled={isRetrying || isCancelling || retryCooldown.has(entry.workspaceId)}
className="px-3 py-1.5 bg-amber-600 hover:bg-amber-500 text-[11px] font-medium rounded-lg text-ink disabled:opacity-40 transition-colors"
className="px-3 py-1.5 bg-amber-600 hover:bg-amber-500 text-[11px] font-medium rounded-lg text-white disabled:opacity-40 transition-colors"
>
{isRetrying ? "Retrying..." : retryCooldown.has(entry.workspaceId) ? "Wait..." : "Retry"}
</button>
@ -389,7 +389,7 @@ export function ProvisioningTimeout({
<button
type="button"
onClick={handleCancelConfirm}
className="px-3.5 py-1.5 text-[12px] bg-red-600 hover:bg-red-500 text-ink rounded-lg transition-colors"
className="px-3.5 py-1.5 text-[12px] bg-red-600 hover:bg-red-500 text-white rounded-lg transition-colors"
>
Remove Workspace
</button>

View File

@ -476,7 +476,7 @@ export function TemplatePalette() {
onClick={() => setOpen(!open)}
className={`fixed top-4 left-4 z-40 w-9 h-9 flex items-center justify-center rounded-lg transition-colors ${
open
? "bg-accent-strong text-ink"
? "bg-accent-strong text-white"
: "bg-surface-sunken/90 border border-line/50 text-ink-mid hover:text-ink hover:border-line"
}`}
title="Template Palette"

View File

@ -105,7 +105,7 @@ export function TermsGate({ children }: { children: React.ReactNode }) {
type="button"
onClick={accept}
disabled={submitting}
className="rounded bg-emerald-600 px-4 py-2 text-sm font-medium text-ink hover:bg-emerald-500 disabled:opacity-50"
className="rounded bg-emerald-600 px-4 py-2 text-sm font-medium text-white hover:bg-emerald-500 disabled:opacity-50"
>
{submitting ? "Saving…" : "I agree"}
</button>

View File

@ -154,7 +154,7 @@ export function Toolbar() {
{counts.failed > 0 && (
<StatusPill color={statusDotClass("failed")} count={counts.failed} label="failed" />
)}
<span className="text-zinc-700" aria-hidden="true">·</span>
<span className="text-ink-soft" aria-hidden="true">·</span>
<span className="text-[10px] text-ink-soft whitespace-nowrap">
{counts.roots} workspace{counts.roots !== 1 ? "s" : ""}
{counts.children > 0 && <span className="text-ink-soft"> + {counts.children} sub</span>}

View File

@ -165,7 +165,7 @@ export function WorkspaceNode({ id, data }: NodeProps<Node<WorkspaceNodeData>>)
<Handle
type="target"
position={Position.Top}
className="!w-2.5 !h-1 !rounded-full !bg-zinc-600/80 !border-0 !-top-0.5 hover:!bg-blue-400 hover:!h-1.5 transition-all"
className="!w-2.5 !h-1 !rounded-full !bg-surface-card/80 !border-0 !-top-0.5 hover:!bg-blue-400 hover:!h-1.5 transition-all"
/>
<div className="relative px-3.5 py-2.5">
@ -318,7 +318,7 @@ export function WorkspaceNode({ id, data }: NodeProps<Node<WorkspaceNodeData>>)
<Handle
type="source"
position={Position.Bottom}
className="!w-2.5 !h-1 !rounded-full !bg-zinc-600/80 !border-0 !-bottom-0.5 hover:!bg-blue-400 hover:!h-1.5 transition-all"
className="!w-2.5 !h-1 !rounded-full !bg-surface-card/80 !border-0 !-bottom-0.5 hover:!bg-blue-400 hover:!h-1.5 transition-all"
/>
</div>
</>

View File

@ -130,7 +130,7 @@ export function OrgCancelButton({ rootId, rootName, workspaceCount }: Props) {
type="button"
onClick={() => setConfirming(false)}
disabled={submitting}
className="px-2 py-0.5 rounded bg-surface-card/80 hover:bg-zinc-600 text-[10px] text-ink"
className="px-2 py-0.5 rounded bg-surface-card/80 hover:bg-surface-card text-[10px] text-ink"
>
No
</button>

View File

@ -111,7 +111,7 @@ export function ActivityTab({ workspaceId }: Props) {
</button>
<button
onClick={loadActivities}
className="px-2 py-1 bg-surface-card hover:bg-zinc-600 text-[11px] rounded text-ink-mid"
className="px-2 py-1 bg-surface-card hover:bg-surface-card text-[11px] rounded text-ink-mid"
>
Refresh
</button>
@ -137,7 +137,7 @@ export function ActivityTab({ workspaceId }: Props) {
{!loading && !error && activities.length === 0 && (
<div className="text-center py-8">
<div className="text-ink-soft text-xs">No activity recorded yet</div>
<div className="text-zinc-700 text-[9px] mt-1">
<div className="text-ink-soft text-[9px] mt-1">
Activity logs appear when agents communicate or perform tasks
</div>
</div>

View File

@ -243,7 +243,7 @@ export function BudgetSection({ workspaceId }: Props) {
onClick={handleSave}
disabled={saving}
data-testid="budget-save-btn"
className="px-4 py-1.5 bg-accent-strong hover:bg-accent active:bg-accent-strong rounded-lg text-xs font-medium text-ink disabled:opacity-50 transition-colors"
className="px-4 py-1.5 bg-accent-strong hover:bg-accent active:bg-accent-strong rounded-lg text-xs font-medium text-white disabled:opacity-50 transition-colors"
>
{saving ? "Saving…" : "Save"}
</button>

View File

@ -366,7 +366,7 @@ export function ChannelsTab({ workspaceId }: Props) {
)}
<button
onClick={handleCreate}
className="w-full text-xs py-1.5 rounded bg-accent-strong hover:bg-accent text-ink transition"
className="w-full text-xs py-1.5 rounded bg-accent-strong hover:bg-accent text-white transition"
>
Connect Channel
</button>
@ -392,7 +392,7 @@ export function ChannelsTab({ workspaceId }: Props) {
<div className="flex items-center gap-2">
<span
className={`w-2 h-2 rounded-full ${
ch.enabled ? "bg-emerald-500" : "bg-zinc-600"
ch.enabled ? "bg-emerald-500" : "bg-surface-card"
}`}
/>
<span className="text-xs font-medium text-ink">

View File

@ -901,7 +901,7 @@ function MyChatPanel({ workspaceId, data }: Props) {
<button
onClick={sendMessage}
disabled={(!input.trim() && pendingFiles.length === 0) || !agentReachable || sending || uploading}
className="px-4 py-2 bg-accent-strong hover:bg-accent text-xs font-medium rounded-lg text-ink disabled:opacity-30 transition-colors shrink-0"
className="px-4 py-2 bg-accent-strong hover:bg-accent text-xs font-medium rounded-lg text-white disabled:opacity-30 transition-colors shrink-0"
>
{uploading ? "Uploading…" : "Send"}
</button>

View File

@ -65,11 +65,11 @@ function AgentCardSection({ workspaceId }: { workspaceId: string }) {
{error && <div className="px-2 py-1 bg-red-900/30 border border-red-800 rounded text-[10px] text-bad">{error}</div>}
<div className="flex gap-2">
<button type="button" onClick={handleSave} disabled={saving}
className="px-2 py-1 bg-accent-strong hover:bg-accent text-[10px] rounded text-ink disabled:opacity-50">
className="px-2 py-1 bg-accent-strong hover:bg-accent text-[10px] rounded text-white disabled:opacity-50">
{saving ? "Saving..." : "Save"}
</button>
<button type="button" onClick={() => setEditing(false)}
className="px-2 py-1 bg-surface-card hover:bg-zinc-600 text-[10px] rounded text-ink-mid">Cancel</button>
className="px-2 py-1 bg-surface-card hover:bg-surface-card text-[10px] rounded text-ink-mid">Cancel</button>
</div>
</div>
) : (
@ -955,7 +955,7 @@ export function ConfigTab({ workspaceId }: Props) {
type="button"
onClick={() => handleSave(true)}
disabled={!isDirty || saving}
className="px-3 py-1.5 bg-accent-strong hover:bg-accent text-xs rounded text-ink disabled:opacity-30 transition-colors"
className="px-3 py-1.5 bg-accent-strong hover:bg-accent text-xs rounded text-white disabled:opacity-30 transition-colors"
>
{saving ? "Restarting..." : "Save & Restart"}
</button>
@ -963,14 +963,14 @@ export function ConfigTab({ workspaceId }: Props) {
type="button"
onClick={() => handleSave(false)}
disabled={!isDirty || saving}
className="px-3 py-1.5 bg-surface-card hover:bg-zinc-600 text-xs rounded text-ink-mid disabled:opacity-30 transition-colors"
className="px-3 py-1.5 bg-surface-card hover:bg-surface-card text-xs rounded text-ink-mid disabled:opacity-30 transition-colors"
>
Save
</button>
<button
type="button"
onClick={loadConfig}
className="px-3 py-1.5 bg-surface-card hover:bg-zinc-600 text-xs rounded text-ink-mid ml-auto"
className="px-3 py-1.5 bg-surface-card hover:bg-surface-card text-xs rounded text-ink-mid ml-auto"
>
Reload
</button>

View File

@ -166,7 +166,7 @@ export function DetailsTab({ workspaceId, data }: Props) {
type="button"
onClick={handleSave}
disabled={saving}
className="px-3 py-1 bg-accent-strong hover:bg-accent text-xs rounded text-ink disabled:opacity-50"
className="px-3 py-1 bg-accent-strong hover:bg-accent text-xs rounded text-white disabled:opacity-50"
>
{saving ? "Saving..." : "Save"}
</button>
@ -179,7 +179,7 @@ export function DetailsTab({ workspaceId, data }: Props) {
setRole(data.role || "");
setTier(data.tier);
}}
className="px-3 py-1 bg-surface-card hover:bg-zinc-600 text-xs rounded text-ink-mid"
className="px-3 py-1 bg-surface-card hover:bg-surface-card text-xs rounded text-ink-mid"
>
Cancel
</button>
@ -208,7 +208,7 @@ export function DetailsTab({ workspaceId, data }: Props) {
type="button"
onClick={handleRestart}
disabled={restarting}
className="px-3 py-1 bg-green-700 hover:bg-green-600 text-xs rounded text-ink disabled:opacity-50"
className="px-3 py-1 bg-green-700 hover:bg-green-600 text-xs rounded text-white disabled:opacity-50"
>
{restarting ? "Restarting..." : data.status === "failed" ? "Retry" : "Restart"}
</button>
@ -217,7 +217,7 @@ export function DetailsTab({ workspaceId, data }: Props) {
<button
type="button"
onClick={() => setEditing(true)}
className="mt-2 px-3 py-1 bg-surface-card hover:bg-zinc-600 text-xs rounded text-ink-mid"
className="mt-2 px-3 py-1 bg-surface-card hover:bg-surface-card text-xs rounded text-ink-mid"
>
Edit
</button>
@ -322,7 +322,7 @@ export function DetailsTab({ workspaceId, data }: Props) {
<button
type="button"
onClick={handleDelete}
className="px-3 py-1 bg-red-600 hover:bg-red-500 text-xs rounded text-ink"
className="px-3 py-1 bg-red-600 hover:bg-red-500 text-xs rounded text-white"
>
Confirm Delete
</button>
@ -334,7 +334,7 @@ export function DetailsTab({ workspaceId, data }: Props) {
// Return focus to the trigger so keyboard users aren't stranded
deleteButtonRef.current?.focus();
}}
className="px-3 py-1 bg-surface-card hover:bg-zinc-600 text-xs rounded text-ink-mid"
className="px-3 py-1 bg-surface-card hover:bg-surface-card text-xs rounded text-ink-mid"
>
Cancel
</button>

View File

@ -65,7 +65,7 @@ export function EventsTab({ workspaceId }: Props) {
<span className="text-xs text-ink-mid">{events.length} events</span>
<button
onClick={loadEvents}
className="px-2 py-1 bg-surface-card hover:bg-zinc-600 text-[10px] rounded text-ink-mid"
className="px-2 py-1 bg-surface-card hover:bg-surface-card text-[10px] rounded text-ink-mid"
>
Refresh
</button>

View File

@ -165,8 +165,8 @@ export function FilesTab({ workspaceId }: Props) {
<div className="mx-3 mt-2 px-3 py-2 bg-red-950/30 border border-red-800/40 rounded space-y-1.5">
<p className="text-xs text-bad">Delete all {files.filter((f) => !f.dir).length} files? This cannot be undone.</p>
<div className="flex gap-2">
<button type="button" onClick={() => { handleDeleteAll(); setShowDeleteAll(false); }} className="px-2 py-0.5 bg-red-600 hover:bg-red-500 text-[10px] rounded text-ink">Delete All</button>
<button type="button" onClick={() => setShowDeleteAll(false)} className="px-2 py-0.5 bg-surface-card hover:bg-zinc-600 text-[10px] rounded text-ink-mid">Cancel</button>
<button type="button" onClick={() => { handleDeleteAll(); setShowDeleteAll(false); }} className="px-2 py-0.5 bg-red-600 hover:bg-red-500 text-[10px] rounded text-white">Delete All</button>
<button type="button" onClick={() => setShowDeleteAll(false)} className="px-2 py-0.5 bg-surface-card hover:bg-surface-card text-[10px] rounded text-ink-mid">Cancel</button>
</div>
</div>
)}
@ -179,8 +179,8 @@ export function FilesTab({ workspaceId }: Props) {
<div className="mx-3 mt-2 px-3 py-2 bg-amber-950/30 border border-amber-800/40 rounded space-y-1.5">
<p className="text-xs text-warm">Delete <span className="font-mono">{confirmDelete}</span>{files.find((f) => f.path === confirmDelete && f.dir) ? " and all its contents" : ""}?</p>
<div className="flex gap-2">
<button type="button" onClick={confirmDeleteFile} className="px-2 py-0.5 bg-red-600 hover:bg-red-500 text-[10px] rounded text-ink">Delete</button>
<button type="button" onClick={() => setConfirmDelete(null)} className="px-2 py-0.5 bg-surface-card hover:bg-zinc-600 text-[10px] rounded text-ink-mid">Cancel</button>
<button type="button" onClick={confirmDeleteFile} className="px-2 py-0.5 bg-red-600 hover:bg-red-500 text-[10px] rounded text-white">Delete</button>
<button type="button" onClick={() => setConfirmDelete(null)} className="px-2 py-0.5 bg-surface-card hover:bg-surface-card text-[10px] rounded text-ink-mid">Cancel</button>
</div>
</div>
)}

View File

@ -137,14 +137,14 @@ export function MemoryTab({ workspaceId }: Props) {
<button
type="button"
onClick={() => setShowAwareness((prev) => !prev)}
className="shrink-0 px-2 py-1 bg-surface-card hover:bg-zinc-600 text-[10px] rounded text-ink"
className="shrink-0 px-2 py-1 bg-surface-card hover:bg-surface-card text-[10px] rounded text-ink"
>
{showAwareness ? "Collapse" : "Expand"}
</button>
<button
type="button"
onClick={openAwareness}
className="shrink-0 px-2 py-1 bg-surface-card hover:bg-zinc-600 text-[10px] rounded text-ink"
className="shrink-0 px-2 py-1 bg-surface-card hover:bg-surface-card text-[10px] rounded text-ink"
>
Open
</button>
@ -177,7 +177,7 @@ export function MemoryTab({ workspaceId }: Props) {
<button
type="button"
onClick={() => setShowAwareness(true)}
className="shrink-0 px-2 py-1 bg-accent-strong hover:bg-accent text-[10px] rounded text-ink"
className="shrink-0 px-2 py-1 bg-accent-strong hover:bg-accent text-[10px] rounded text-white"
>
Expand
</button>
@ -212,21 +212,21 @@ export function MemoryTab({ workspaceId }: Props) {
<button
type="button"
onClick={() => setShowAdvanced((prev) => !prev)}
className="px-2 py-1 bg-surface-card hover:bg-zinc-600 text-[10px] rounded text-ink-mid"
className="px-2 py-1 bg-surface-card hover:bg-surface-card text-[10px] rounded text-ink-mid"
>
{showAdvanced ? "Hide Advanced" : "Advanced"}
</button>
<button
type="button"
onClick={loadMemory}
className="px-2 py-1 bg-surface-card hover:bg-zinc-600 text-[10px] rounded text-ink-mid"
className="px-2 py-1 bg-surface-card hover:bg-surface-card text-[10px] rounded text-ink-mid"
>
Refresh
</button>
<button
type="button"
onClick={() => { setShowAdd(!showAdd); if (!showAdd) setShowAdvanced(true); }}
className="px-2 py-1 bg-accent-strong hover:bg-accent text-[10px] rounded text-ink"
className="px-2 py-1 bg-accent-strong hover:bg-accent text-[10px] rounded text-white"
>
+ Add
</button>
@ -262,7 +262,7 @@ export function MemoryTab({ workspaceId }: Props) {
<button
type="button"
onClick={handleAdd}
className="px-3 py-1 bg-accent-strong hover:bg-accent text-xs rounded text-ink"
className="px-3 py-1 bg-accent-strong hover:bg-accent text-xs rounded text-white"
>
Save
</button>
@ -272,7 +272,7 @@ export function MemoryTab({ workspaceId }: Props) {
setShowAdd(false);
setError(null);
}}
className="px-3 py-1 bg-surface-card hover:bg-zinc-600 text-xs rounded text-ink-mid"
className="px-3 py-1 bg-surface-card hover:bg-surface-card text-xs rounded text-ink-mid"
>
Cancel
</button>
@ -340,7 +340,7 @@ export function MemoryTab({ workspaceId }: Props) {
<button
type="button"
onClick={() => setShowAdvanced(true)}
className="shrink-0 px-2 py-1 bg-accent-strong hover:bg-accent text-[10px] rounded text-ink"
className="shrink-0 px-2 py-1 bg-accent-strong hover:bg-accent text-[10px] rounded text-white"
>
Show
</button>

View File

@ -271,7 +271,7 @@ export function ScheduleTab({ workspaceId }: Props) {
<button
onClick={handleSubmit}
disabled={!formCron || !formPrompt}
className="text-[11px] px-3 py-1 bg-accent-strong text-ink rounded hover:bg-accent disabled:opacity-40 transition-colors"
className="text-[11px] px-3 py-1 bg-accent-strong text-white rounded hover:bg-accent disabled:opacity-40 transition-colors"
>
{editId ? "Update" : "Create"}
</button>
@ -320,7 +320,7 @@ export function ScheduleTab({ workspaceId }: Props) {
? "bg-red-400"
: sched.last_status === "ok"
? "bg-emerald-400"
: "bg-zinc-600"
: "bg-surface-card"
}`}
title={sched.enabled ? "Click to disable" : "Click to enable"}
/>

View File

@ -70,7 +70,7 @@ export function TracesTab({ workspaceId }: Props) {
<div className="text-center py-8">
<div className="text-2xl opacity-20 mb-2" aria-hidden="true">--</div>
<p className="text-xs text-ink-soft">No traces yet</p>
<details className="mt-2 text-[10px] text-zinc-700">
<details className="mt-2 text-[10px] text-ink-soft">
<summary className="cursor-pointer text-ink-soft hover:text-ink-mid">How to enable tracing</summary>
<p className="mt-1">
Set <code className="font-mono text-ink-mid">LANGFUSE_HOST</code>, <code className="font-mono text-ink-mid">LANGFUSE_PUBLIC_KEY</code>, <code className="font-mono text-ink-mid">LANGFUSE_SECRET_KEY</code> as workspace secrets to enable tracing.

View File

@ -58,7 +58,7 @@ export function AttachmentChip({
const toneClasses =
tone === "user"
? "border-blue-400/30 bg-accent-strong/20 hover:bg-accent-strong/30 text-blue-100"
: "border-line/50 bg-surface-card/40 hover:bg-zinc-600/50 text-ink";
: "border-line/50 bg-surface-card/40 hover:bg-surface-card/50 text-ink";
return (
<button
onClick={() => onDownload(attachment)}

View File

@ -131,7 +131,7 @@ function SecretRow({ label, secretKey, isSet, scope, globalMode, onSave, onDelet
<button type="button"
onClick={() => { onSave(value); setEditing(false); setValue(""); }}
disabled={!value}
className="px-2 py-1 bg-accent-strong hover:bg-accent text-[10px] rounded text-ink disabled:opacity-30"
className="px-2 py-1 bg-accent-strong hover:bg-accent text-[10px] rounded text-white disabled:opacity-30"
>Save</button>
</div>
)}
@ -184,7 +184,7 @@ function CustomSecretRow({ secretKey, scope, globalMode, onSave, onDelete }: {
<button type="button"
onClick={() => { onSave(value); setEditing(false); setValue(""); }}
disabled={!value}
className="px-2 py-1 bg-accent-strong hover:bg-accent text-[10px] rounded text-ink disabled:opacity-30"
className="px-2 py-1 bg-accent-strong hover:bg-accent text-[10px] rounded text-white disabled:opacity-30"
>Save</button>
</div>
)}
@ -298,7 +298,7 @@ export function SecretsSection({ workspaceId, requiredEnv }: { workspaceId: stri
<button
onClick={() => setGlobalMode(false)}
className={`text-[10px] px-2 py-0.5 rounded transition-colors ${
!globalMode ? "bg-accent-strong/20 text-accent border border-accent/30" : "text-ink-soft hover:text-ink-mid"
!globalMode ? "bg-accent-strong/20 text-accent border border-accent/30" : "text-white-soft hover:text-white-mid"
}`}
>
This Workspace
@ -306,7 +306,7 @@ export function SecretsSection({ workspaceId, requiredEnv }: { workspaceId: stri
<button
onClick={() => setGlobalMode(true)}
className={`text-[10px] px-2 py-0.5 rounded transition-colors ${
globalMode ? "bg-amber-600/20 text-warm border border-amber-500/30" : "text-ink-soft hover:text-ink-mid"
globalMode ? "bg-amber-600/20 text-warm border border-amber-500/30" : "text-white-soft hover:text-white-mid"
}`}
>
Global (All Workspaces)
@ -356,11 +356,11 @@ export function SecretsSection({ workspaceId, requiredEnv }: { workspaceId: stri
className="w-full bg-surface-sunken border border-line rounded px-2 py-1 text-[10px] text-ink focus:outline-none focus:border-accent" />
<div className="flex gap-2">
<button type="button" onClick={() => { if (newKey && newValue) handleSave(newKey, newValue); }} disabled={!newKey || !newValue}
className="px-2 py-1 bg-accent-strong hover:bg-accent text-[10px] rounded text-ink disabled:opacity-30">
className="px-2 py-1 bg-accent-strong hover:bg-accent text-[10px] rounded text-white disabled:opacity-30">
Save{globalMode ? " (Global)" : ""}
</button>
<button type="button" onClick={() => { setShowAdd(false); setNewKey(""); setNewValue(""); }}
className="px-2 py-1 bg-surface-card hover:bg-zinc-600 text-[10px] rounded text-ink-mid">Cancel</button>
className="px-2 py-1 bg-surface-card hover:bg-surface-card text-[10px] rounded text-ink-mid">Cancel</button>
</div>
</div>
) : (