feat(ui): gallery feedback round 1 — destructive revert, status bar fills, running=blue

Three user rulings from the tune-session gallery review (all intentional
visible changes; snapshots NOT re-baselined):

1. Dark-mode --destructive reverted to master's original
   oklch(0.637 0.237 25.331) (preset's softer red rejected); light
   untouched.
2. Budget/quota progress-bar FILLS re-pointed at canonical status hues
   (bar-fill elements only): healthy -> bg-(--status-task-done),
   warning -> bg-(--status-task-todo), exceeded/hard-stop ->
   bg-(--status-task-blocked) in BudgetPolicyCard, QuotaBar (covers
   ProviderQuotaCard/BillerSpendCard surfaces), Costs, and
   CodexSubscriptionPanel (escalation tiers; healthy stays bg-primary/70
   by design — flagged). Left: Org.tsx status dot, chip washes/borders.
3. RUNNING chips/labels cyan -> canonical blue: IssueChatThread running
   chip now composes brandChipBadge.blue (layout unchanged);
   RunTranscriptView running label via new runningLabelText export
   (hexes stay in lib/status-colors.ts, gate-clean); statusBadge.running,
   agentStatusDot.running, AgentDetail runStatusIcons.running -> blue.
   Left + flagged: externalObjectStatus* running (open already blue in
   those maps) and the cyan 'Live' branding family (dedicated decision
   recommended).

DECISION-SHEET.md: 'Gallery feedback round 1' section added. Gates 3/3
CLEAN, typecheck green, ui build green (token bar-fill classes verified
in built CSS), vitest 103/103 on touched components.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
scotttong 2026-07-06 18:05:35 -07:00
parent 46d9a3c0c9
commit 59e536fc08
10 changed files with 43 additions and 17 deletions

View File

@ -38,6 +38,14 @@ Every open question from TOKEN-AUDIT.md §8 + batch logs and COMPONENT-INVENTORY
| C9 | Toast system (no shadcn primitive installed) | Keep custom toast; document as permanent choice (working tone/variant system; sonner migration = churn without user-visible gain) | DEFERRED to Run 4 — decide when toast palette colors get retokenized; sonner-behind-a-pushToast-facade is the alternative to evaluate |
| C10 | FeatureGate wrapper pattern (3 near-identical gates) | Nice-to-have shared primitive; backlog, not a run | APPROVED — backlog nice-to-have, not a run |
## Gallery feedback round 1 (preset-tune session, Jul 6) — executed
User rulings from the tune-session gallery review; all intentionally visible, snapshots NOT re-baselined (fresh before/after triplets regenerated in tests/storybook-visual/test-results/ against the old baseline):
1. **Dark destructive red reverted**`.dark --destructive` back to master's original `oklch(0.637 0.237 25.331)` (preset's softer `oklch(0.704 0.191 22.216)` rejected); light mode untouched.
2. **Budget/quota BAR FILLS reuse status colors** — moving-fill elements only: healthy → `bg-(--status-task-done)`, warning → `bg-(--status-task-todo)`, exceeded/hard-stop → `bg-(--status-task-blocked)` in BudgetPolicyCard.tsx, QuotaBar.tsx (feeds ProviderQuotaCard/BillerSpendCard pages), Costs.tsx, CodexSubscriptionPanel.tsx (escalation tiers only). Inspected and deliberately LEFT: Org.tsx status dot (not a bar), BudgetPolicyCard chip washes/notice borders (not fills), CodexSubscriptionPanel healthy `bg-primary/70` + null `bg-zinc-700` (healthy tier uses brand primary by design — flagged as ambiguous, not emerald).
3. **RUNNING = status blue, not cyan/teal** — IssueChatThread running chip now composes `brandChipBadge.blue` (layout classes unchanged); RunTranscriptView running label uses new `runningLabelText` export (`text-[#1D4ED8] dark:text-[#2563EB]`, hexes kept in lib/status-colors.ts for gate cleanliness); `statusBadge.running` + `agentStatusDot.running` maps and AgentDetail `runStatusIcons.running` re-pointed cyan→blue. Deliberately LEFT + flagged: `externalObjectStatusIcon/Badge.running` (same-map collision — `open` is already blue there; documented UX-spec tone system), and the cyan "Live" branding family (LiveRunWidget theme, AgentDetail live-card border + Live pulse dots, DesignGuide Live sample) — "Live" is a distinct motif from RUNNING chips; note AgentDetail's mobile Live pill is already blue, so a dedicated Live-color decision is recommended.
## Verification status (this review)
- `pnpm check:token-gates` — re-run independently: 3/3 CLEAN (468 files, 31 allowlist entries).

View File

@ -104,10 +104,10 @@ export function BudgetPolicyCard({
className={cn(
"h-full rounded-full transition-(--tp-width-background-color) duration-200",
summary.status === "hard_stop"
? "bg-red-400"
? "bg-(--status-task-blocked)"
: summary.status === "warning"
? "bg-amber-300"
: "bg-emerald-300",
? "bg-(--status-task-todo)"
: "bg-(--status-task-done)",
)}
style={{ width: `${progress}%` }}
/>

View File

@ -42,8 +42,8 @@ function detailText(window: QuotaWindow): string | null {
function fillClass(usedPercent: number | null): string {
if (usedPercent == null) return "bg-zinc-700";
if (usedPercent >= 90) return "bg-red-400";
if (usedPercent >= 70) return "bg-amber-400";
if (usedPercent >= 90) return "bg-(--status-task-blocked)";
if (usedPercent >= 70) return "bg-(--status-task-todo)";
return "bg-primary/70";
}

View File

@ -162,6 +162,7 @@ import {
} from "../lib/transcriptPresentation";
import { buildAgentMentionHref } from "@paperclipai/shared";
import { cn, formatDateTime, formatShortDate } from "../lib/utils";
import { brandChipBadge } from "../lib/status-colors";
import { nextWorkMode, titleForPendingWorkMode, workModeMetaFor, workModeMetaList } from "../lib/work-mode-meta";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
@ -1896,7 +1897,14 @@ function IssueChatAssistantMessage({
</Badge>
) : null}
{isRunning ? (
<span className="inline-flex items-center gap-1 rounded-full border border-cyan-400/40 bg-cyan-500/10 px-2 py-0.5 text-(length:--text-nano) font-medium uppercase tracking-(--tracking-eyebrow) text-cyan-700 dark:text-cyan-200">
// Gallery feedback r1: running chip uses the canonical brand blue
// (brandChipBadge.blue), not cyan; layout/size classes unchanged.
<span
className={cn(
"inline-flex items-center gap-1 rounded-full border px-2 py-0.5 text-(length:--text-nano) font-medium uppercase tracking-(--tracking-eyebrow)",
brandChipBadge.blue,
)}
>
<Loader2 className="h-3 w-3 animate-spin" />
Running
</span>

View File

@ -12,9 +12,9 @@ interface QuotaBarProps {
}
function fillColor(pct: number): string {
if (pct > 90) return "bg-red-400";
if (pct > 70) return "bg-yellow-400";
return "bg-green-400";
if (pct > 90) return "bg-(--status-task-blocked)";
if (pct > 70) return "bg-(--status-task-todo)";
return "bg-(--status-task-done)";
}
export function QuotaBar({

View File

@ -2,6 +2,7 @@ import { useEffect, useMemo, useRef, useState } from "react";
import type { TranscriptEntry } from "../../adapters";
import { MarkdownBody, type MarkdownExternalReferenceMap } from "../MarkdownBody";
import { cn, formatTokens } from "../../lib/utils";
import { runningLabelText } from "../../lib/status-colors";
import {
Check,
ChevronDown,
@ -1057,7 +1058,8 @@ function TranscriptToolGroup({
{humanizeLabel(item.name)}
</span>
<span className={cn("text-(length:--text-nano) font-semibold uppercase tracking-(--tracking-eyebrow)",
item.status === "running" ? "text-cyan-700 dark:text-cyan-300"
// Gallery feedback r1: running label uses brand blue, not cyan.
item.status === "running" ? runningLabelText
: item.status === "error" ? "text-red-700 dark:text-red-300"
: "text-emerald-700 dark:text-emerald-300"
)}>

View File

@ -197,7 +197,7 @@
--muted-foreground: oklch(0.708 0 0);
--accent: oklch(0.269 0 0);
--accent-foreground: oklch(0.985 0 0);
--destructive: oklch(0.704 0.191 22.216);
--destructive: oklch(0.637 0.237 25.331); /* Gallery feedback round 1: user preferred master's original dark destructive red over the preset's softer 0.704 0.191 22.216. */
--destructive-foreground: oklch(0.985 0 0);
--border: oklch(1 0 0 / 10%);
--input: oklch(1 0 0 / 15%);

View File

@ -49,7 +49,7 @@ export const issueStatusTextDefault = "text-muted-foreground";
export const statusBadge: Record<string, string> = {
// Agent statuses
active: "bg-green-100 text-green-700 dark:bg-green-900/50 dark:text-green-300",
running: "bg-cyan-100 text-cyan-700 dark:bg-cyan-900/50 dark:text-cyan-300",
running: "bg-blue-100 text-blue-700 dark:bg-blue-900/50 dark:text-blue-300", // Gallery feedback r1: running = status blue (matches in_progress liveness), not cyan.
scheduled_retry: "bg-sky-100 text-sky-700 dark:bg-sky-900/50 dark:text-sky-300",
paused: "bg-orange-100 text-orange-700 dark:bg-orange-900/50 dark:text-orange-300",
idle: "bg-yellow-100 text-yellow-700 dark:bg-yellow-900/50 dark:text-yellow-300",
@ -146,6 +146,14 @@ export const brandChipBadge: Record<BrandChipColor, string> = {
red: "bg-[#FEE2E2] text-[#991B1B] border-[#DC2626] dark:bg-[#dc26262e] dark:text-[#DC2626] dark:border-[#dc262673]",
};
/**
* Brand blue TEXT pair (the text hues of `brandChipBadge.blue`) for non-chip
* "Running" labels Gallery feedback round 1: running-state copy uses the
* canonical status blue, not cyan/teal. Kept here so components stay free of
* hex literals (token-gate scope).
*/
export const runningLabelText = "text-[#1D4ED8] dark:text-[#2563EB]";
/**
* Issue/task status brand colour name (PAP-75). `in_progress` is blue
* (liveness), `todo` amber (queued), `in_review` violet (awaiting review),
@ -219,7 +227,7 @@ export const taskStatusIconVarDefault = "--status-task-icon-backlog";
// ---------------------------------------------------------------------------
export const agentStatusDot: Record<string, string> = {
running: "bg-cyan-400 animate-pulse",
running: "bg-blue-400 animate-pulse", // Gallery feedback r1: running dot = blue, not cyan.
active: "bg-green-400",
paused: "bg-yellow-400",
idle: "bg-yellow-400",

View File

@ -116,7 +116,7 @@ import {
const runStatusIcons: Record<string, { icon: typeof CheckCircle2; color: string }> = {
succeeded: { icon: CheckCircle2, color: "text-green-600 dark:text-green-400" },
failed: { icon: XCircle, color: "text-red-600 dark:text-red-400" },
running: { icon: Loader2, color: "text-cyan-600 dark:text-cyan-400" },
running: { icon: Loader2, color: "text-blue-600 dark:text-blue-400" }, // Gallery feedback r1: running = status blue, not cyan.
queued: { icon: Clock, color: "text-yellow-600 dark:text-yellow-400" },
scheduled_retry: { icon: Clock, color: "text-sky-600 dark:text-sky-400" },
timed_out: { icon: Timer, color: "text-orange-600 dark:text-orange-400" },

View File

@ -689,10 +689,10 @@ export function Costs() {
className={cn(
"h-full transition-(--tp-width-background-color) duration-150",
spendData.summary.utilizationPercent > 90
? "bg-red-400"
? "bg-(--status-task-blocked)"
: spendData.summary.utilizationPercent > 70
? "bg-yellow-400"
: "bg-emerald-400",
? "bg-(--status-task-todo)"
: "bg-(--status-task-done)",
)}
style={{ width: `${Math.min(100, spendData.summary.utilizationPercent)}%` }}
/>