mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-21 16:27:18 +02:00
feat(ui): gallery feedback round 3 — one toggle, canonical agent chips, light-wash text sweep
Four user rulings (intentional visible changes; snapshots NOT
re-baselined):
1+2. Toggle unification: the app's only second switch implementation —
ToggleField's hand-rolled h-5 w-9 pill with a bg-green-600 track in
agent-config-primitives.tsx — now renders the canonical ToggleSwitch
capsule (on = var(--status-task-done)); props/testid preserved. All
other suspects already used ToggleSwitch; every switch now shares
one form and one green.
3. Agent-status chips ('Org snippets and quick scan identity' section,
StatusBadge): statusBadge agent keys route through brandChipBadge —
running=blue, idle=gray (was yellow tint), paused=amber, and
active=green (no canonical agent status; user-ruled mapping to the
brand green family). brandChipBadge moved above statusBadge for
declaration order. Org.tsx status dots route through agentStatusDot.
4. Dark-tuned-text-on-light-wash class of bug: flagged BudgetPolicyCard
banner was already fixed in r2; systematic sweep fixed 58 more sites
in 14 files (50->950, 100->900, 200->800, 300->700 light tiers with
originals behind dark:, opacity suffixes preserved). Safe leftovers
documented (InviteLanding dark page, solid-bg badges).
DECISION-SHEET.md: 'Gallery feedback round 3' section. Gates 3/3 CLEAN,
typecheck green, ui build green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
3da1bbcc5a
commit
d8ace306d3
18 files changed
+104
-106
No files matched your search
@@ -53,6 +53,12 @@ User rulings from the tune-session gallery review; all intentionally visible, sn
|
||||
3. **Systematic cyan→status-blue liveness sweep** (~50 sites / 20 files; supersedes r1's "Live family left" note per user ruling): running-status tones (CommentThread, interrupt-handoff, runRetryState, AgentDetail run chip + status maps), live dots/pings (AgentDetail, OnboardingChat, ArtifactsPanel generating, ActiveAgentsPanel, IssueDetail Live pill, RunTranscriptView, IssueRunLedger live chip, DesignGuide sample), Live surfaces (LiveRunWidget theme, ActiveAgentsPanel Live-now box, AgentDetail live-card border), scheduled-retry family (IssueScheduledRetryCard, runRetryState, IssueRunLedger retry-pending), externalObjectStatus icon/badge `running` (now shares blue with `open`; liveness pulse differentiates — flagged), timeline "now" marker `#2dd4bf`→`#2563eb` (1.5px line vs `#5b9bf6` delegated bars, shape differentiates — flagged), and the liveness glow shadow tokens `--shadow-extract-1/11/14` value-edited `rgba(6,182,212,0.08)`→`rgba(37,99,235,0.08)` (kept-token VALUES changed, call sites unchanged).
|
||||
Deliberately LEFT (non-liveness cyan, one-line reasons): xterm terminal cursor (CompanyEnvironments — terminal chrome); `on_demand` invocation-source chips (AgentDetail x2 + DesignGuide sample — source tag, not liveness); CompanySkills "Includes assets" chip (content-type tag); CompanyImport renamed-file mono text x2 (rename annotation); BlockedReasonChip `recovery_required` (blocked-reason category); IssueRunLedger "Advanced" outcome + "Silence snoozed" tones (outcome/pause semantics, not live); UxLab decorative gradients (B1 allowlisted decoration).
|
||||
|
||||
## Gallery feedback round 3 (preset-tune session, Jul 6) — executed
|
||||
|
||||
1+2. **Toggle unification** — hunt found exactly ONE second switch implementation: `ToggleField` in agent-config-primitives.tsx (hand-rolled h-5 w-9 pill, `bg-green-600` track — the "other green"). It now renders the canonical `ToggleSwitch` (3da1bbcc5 capsule, on = `var(--status-task-done)`), same props/behavior/testid. All other named suspects (AgentConfigForm, Instance*Settings, RoutineDetail, PipelineSettings, story fixtures) already used ToggleSwitch; remaining `bg-green-600` hits are buttons, not tracks. Every switch now renders the one capsule + one green.
|
||||
3. **Agent-status chips → canonical colors** ("Org snippets and quick scan identity" = StatusBadge in control-plane-surfaces story): `statusBadge` agent keys now route through `brandChipBadge` families (bordered brand chips): running → blue, idle → GRAY (was yellow tint), paused → amber, **active → green (no canonical agent status exists — user-ruled mapping to the brand green/done family)**; error already rides the shared run-status red. brandChipBadge block moved above statusBadge in status-colors.ts (declaration order). Org.tsx's hand-rolled status-dot ternary now routes through `agentStatusDot` (same hues + gains the blue running dot). Left: Companies.tsx company-status chip (company entity, not agent), AgentConfigForm "current" model tag (not a status).
|
||||
4. **Dark-text-on-light-wash sweep** — the flagged BudgetPolicyCard banner was already fixed in round 2 (screenshot predated it). Systematic sweep (bare `text-{red,amber,emerald,sky,green,yellow,cyan}-{50..300}` without dark: protection): **58 sites fixed across 14 files** (Dashboard budget alert, BudgetPolicyCard remaining-amount, AgentConfigForm banners x3, AgentDetail banners/chips x9, ProjectDetail x3, CompanyAccess, IssueDocumentsSection x6, RoutineHistoryTab x11, DocumentDiffModal x2, PipelineItemBodyDocument x2, RoutineSaveBar, DocumentFrameHeader, OutputFileTile x3, CompanySkills x10) — pattern: 50→950, 100→900, 200→800, 300→700 in light + original behind `dark:`, opacity suffixes preserved. Verified-safe leftovers: InviteLanding x4 (dark-styled standalone page), SidebarNavItem badge + DocumentAnnotationLayer tail + DevRestartBanner (solid dark/colored bg or dark:-protected).
|
||||
|
||||
## Verification status (this review)
|
||||
|
||||
- `pnpm check:token-gates` — re-run independently: 3/3 CLEAN (468 files, 31 allowlist entries).
|
||||
|
||||
@@ -1012,7 +1012,7 @@ export function AgentConfigForm(props: AgentConfigFormProps) {
|
||||
}}
|
||||
/>
|
||||
</Field>
|
||||
<div className="rounded-md border border-amber-500/25 bg-amber-500/10 px-3 py-2 text-xs text-amber-100">
|
||||
<div className="rounded-md border border-amber-500/25 bg-amber-500/10 px-3 py-2 text-xs text-amber-900 dark:text-amber-100">
|
||||
Prompt template is replayed on every heartbeat. Keep it compact and dynamic to avoid recurring token cost and cache churn.
|
||||
</div>
|
||||
</>
|
||||
@@ -1041,7 +1041,7 @@ export function AgentConfigForm(props: AgentConfigFormProps) {
|
||||
{kubernetesEnvironment.name} · Kubernetes sandbox
|
||||
</div>
|
||||
) : (
|
||||
<div className="rounded-md border border-amber-500/25 bg-amber-500/10 px-3 py-2 text-xs text-amber-200">
|
||||
<div className="rounded-md border border-amber-500/25 bg-amber-500/10 px-3 py-2 text-xs text-amber-800 dark:text-amber-200">
|
||||
This instance requires the Kubernetes sandbox, but no managed Kubernetes
|
||||
environment is available for this company yet. Configure one before creating
|
||||
agents; execution will not fall back to local.
|
||||
@@ -1353,7 +1353,7 @@ export function AgentConfigForm(props: AgentConfigFormProps) {
|
||||
}}
|
||||
/>
|
||||
</Field>
|
||||
<div className="rounded-md border border-amber-500/25 bg-amber-500/10 px-3 py-2 text-xs text-amber-200">
|
||||
<div className="rounded-md border border-amber-500/25 bg-amber-500/10 px-3 py-2 text-xs text-amber-800 dark:text-amber-200">
|
||||
Bootstrap prompt is legacy and will be removed in a future release. Consider moving this content into the agent's prompt template or instructions file instead.
|
||||
</div>
|
||||
</>
|
||||
|
||||
@@ -166,9 +166,9 @@ export function BudgetPolicyCard({
|
||||
className={cn(
|
||||
"inline-flex items-center gap-2 text-(length:--text-micro) uppercase tracking-(--tracking-caps)",
|
||||
summary.status === "hard_stop"
|
||||
? "text-red-300"
|
||||
? "text-red-700 dark:text-red-300"
|
||||
: summary.status === "warning"
|
||||
? "text-amber-200"
|
||||
? "text-amber-800 dark:text-amber-200"
|
||||
: "text-muted-foreground",
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -73,8 +73,8 @@ export function DocumentDiffModal({
|
||||
|
||||
const lineClassesByKind: Record<DiffRow["kind"], string> = {
|
||||
context: "bg-transparent",
|
||||
removed: "bg-red-500/10 text-red-100",
|
||||
added: "bg-green-500/10 text-green-100",
|
||||
removed: "bg-red-500/10 text-red-900 dark:text-red-100",
|
||||
added: "bg-green-500/10 text-green-900 dark:text-green-100",
|
||||
};
|
||||
|
||||
const markerByKind: Record<DiffRow["kind"], string> = {
|
||||
|
||||
@@ -92,7 +92,7 @@ export function DocumentFrameHeader({
|
||||
size="sm"
|
||||
className={cn(
|
||||
"h-auto px-1.5 py-0 text-(length:--text-micro) font-normal text-muted-foreground hover:text-foreground",
|
||||
revisionMenu.historicalPreview && "text-amber-300 hover:text-amber-200",
|
||||
revisionMenu.historicalPreview && "text-amber-700 hover:text-amber-800 dark:text-amber-300 dark:hover:text-amber-200",
|
||||
)}
|
||||
>
|
||||
rev {revisionMenu.displayedRevisionNumber}
|
||||
|
||||
@@ -964,7 +964,7 @@ export function IssueDocumentsSection({
|
||||
size="icon-xs"
|
||||
className={cn(
|
||||
"text-muted-foreground transition-colors",
|
||||
isLocked && "text-amber-300 hover:text-amber-200",
|
||||
isLocked && "text-amber-700 hover:text-amber-800 dark:text-amber-300 dark:hover:text-amber-200",
|
||||
)}
|
||||
title={isLocked ? "Unlock document" : "Lock document"}
|
||||
aria-label={isLocked ? `Unlock ${doc.key} document` : `Lock ${doc.key} document`}
|
||||
@@ -974,7 +974,7 @@ export function IssueDocumentsSection({
|
||||
{isLocked ? <Lock className="h-3.5 w-3.5" /> : <Unlock className="h-3.5 w-3.5" />}
|
||||
</Button>
|
||||
) : isLocked ? (
|
||||
<span title="Locked document" aria-label="Locked document" className="inline-flex h-6 w-6 items-center justify-center text-amber-300">
|
||||
<span title="Locked document" aria-label="Locked document" className="inline-flex h-6 w-6 items-center justify-center text-amber-700 dark:text-amber-300">
|
||||
<Lock className="h-3.5 w-3.5" />
|
||||
</span>
|
||||
) : null}
|
||||
@@ -1063,7 +1063,7 @@ export function IssueDocumentsSection({
|
||||
<div className="rounded-md border border-amber-500/30 bg-amber-500/5 px-3 py-3">
|
||||
<div className="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
|
||||
<div className="space-y-1">
|
||||
<p className="text-sm font-medium text-amber-200">
|
||||
<p className="text-sm font-medium text-amber-800 dark:text-amber-200">
|
||||
Viewing revision {selectedHistoricalRevision.revisionNumber}
|
||||
</p>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
@@ -1100,7 +1100,7 @@ export function IssueDocumentsSection({
|
||||
<div className="rounded-md border border-amber-500/30 bg-amber-500/5 px-3 py-3">
|
||||
<div className="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
|
||||
<div className="space-y-1">
|
||||
<p className="text-sm font-medium text-amber-200">Out of date</p>
|
||||
<p className="text-sm font-medium text-amber-800 dark:text-amber-200">Out of date</p>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
This document changed while you were editing. Your local draft is preserved and autosave is paused.
|
||||
</p>
|
||||
@@ -1220,9 +1220,9 @@ export function IssueDocumentsSection({
|
||||
<span
|
||||
className={`text-(length:--text-micro) transition-opacity duration-150 ${
|
||||
isHistoricalPreview
|
||||
? "text-amber-300"
|
||||
? "text-amber-700 dark:text-amber-300"
|
||||
: activeConflict
|
||||
? "text-amber-300"
|
||||
? "text-amber-700 dark:text-amber-300"
|
||||
: autosaveState === "error"
|
||||
? "text-destructive"
|
||||
: "text-muted-foreground"
|
||||
|
||||
@@ -297,7 +297,7 @@ export function PipelineItemBodyDocument({
|
||||
<div className="rounded-md border border-amber-500/30 bg-amber-500/5 px-3 py-3">
|
||||
<div className="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
|
||||
<div className="space-y-1">
|
||||
<p className="text-sm font-medium text-amber-200">
|
||||
<p className="text-sm font-medium text-amber-800 dark:text-amber-200">
|
||||
Viewing revision {selectedHistoricalRevision.revisionNumber}
|
||||
</p>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
@@ -415,7 +415,7 @@ export function PipelineItemBodyDocument({
|
||||
/>
|
||||
) : null}
|
||||
actionsSlot={editing ? (
|
||||
<span className="text-(length:--text-micro) font-medium text-amber-300">● Editing · unsaved</span>
|
||||
<span className="text-(length:--text-micro) font-medium text-amber-700 dark:text-amber-300">● Editing · unsaved</span>
|
||||
) : (
|
||||
<Button variant="ghost" size="sm" className="h-auto gap-1.5 px-2 py-1 text-xs" onClick={beginEdit}>
|
||||
<FilePenLine className="h-3.5 w-3.5" />
|
||||
|
||||
@@ -335,7 +335,7 @@ function HistoricalPreviewBanner({
|
||||
<div className="rounded-md border border-amber-500/30 bg-amber-500/5 px-4 py-3">
|
||||
<div className="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
|
||||
<div className="space-y-1">
|
||||
<p className="text-sm font-medium text-amber-200">
|
||||
<p className="text-sm font-medium text-amber-800 dark:text-amber-200">
|
||||
Viewing revision {revisionNumber} (read-only)
|
||||
</p>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
@@ -374,7 +374,7 @@ function ConflictBanner({
|
||||
<div className="rounded-md border border-amber-500/30 bg-amber-500/5 px-4 py-3">
|
||||
<div className="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
|
||||
<div className="space-y-1">
|
||||
<p className="text-sm font-medium text-amber-200">Unsaved routine edits</p>
|
||||
<p className="text-sm font-medium text-amber-800 dark:text-amber-200">Unsaved routine edits</p>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
You changed {fieldsText} but haven't saved yet. Save or discard before previewing or
|
||||
restoring an older revision.
|
||||
@@ -465,7 +465,7 @@ function RevisionList({
|
||||
</span>
|
||||
)}
|
||||
{revision.restoredFromRevisionId && (
|
||||
<span className="rounded-full border border-amber-500/40 bg-amber-500/10 px-1.5 text-(length:--text-nano) uppercase tracking-(--tracking-eyebrow) text-amber-200">
|
||||
<span className="rounded-full border border-amber-500/40 bg-amber-500/10 px-1.5 text-(length:--text-nano) uppercase tracking-(--tracking-eyebrow) text-amber-800 dark:text-amber-200">
|
||||
Restored
|
||||
</span>
|
||||
)}
|
||||
@@ -611,7 +611,7 @@ function RevisionPreview({
|
||||
<p className="text-sm">
|
||||
{row.value || <span className="text-muted-foreground">—</span>}
|
||||
{row.differs && (
|
||||
<span className="ml-2 rounded-full border border-amber-500/40 bg-amber-500/10 px-1.5 text-(length:--text-nano) uppercase tracking-(--tracking-eyebrow) text-amber-200">
|
||||
<span className="ml-2 rounded-full border border-amber-500/40 bg-amber-500/10 px-1.5 text-(length:--text-nano) uppercase tracking-(--tracking-eyebrow) text-amber-800 dark:text-amber-200">
|
||||
differs from current
|
||||
</span>
|
||||
)}
|
||||
@@ -738,7 +738,7 @@ function RestoreConfirmDialog({
|
||||
Previous run history is preserved.
|
||||
</li>
|
||||
{recreatedWebhookLabels.map((label) => (
|
||||
<li key={label} className="flex items-start gap-2 text-amber-200">
|
||||
<li key={label} className="flex items-start gap-2 text-amber-800 dark:text-amber-200">
|
||||
<span className="mt-1 inline-block h-1.5 w-1.5 rounded-full bg-amber-400" />
|
||||
The webhook trigger {label} will be recreated with a new URL and secret. Paperclip will
|
||||
show the secret once after restore — copy it before closing.
|
||||
@@ -858,10 +858,10 @@ function RoutineRevisionDiffModal({
|
||||
{fieldChanges.map((change) => (
|
||||
<tr key={change.field} className="border-t border-border/60">
|
||||
<td className="px-3 py-2 align-top text-xs font-medium">{change.field}</td>
|
||||
<td className="px-3 py-2 align-top text-xs text-red-300">
|
||||
<td className="px-3 py-2 align-top text-xs text-red-700 dark:text-red-300">
|
||||
{change.oldValue ?? "—"}
|
||||
</td>
|
||||
<td className="px-3 py-2 align-top text-xs text-emerald-300">
|
||||
<td className="px-3 py-2 align-top text-xs text-emerald-700 dark:text-emerald-300">
|
||||
{change.newValue ?? "—"}
|
||||
</td>
|
||||
</tr>
|
||||
@@ -907,8 +907,8 @@ function RevisionPicker({
|
||||
tone: "red" | "green";
|
||||
}) {
|
||||
const toneClass = tone === "red"
|
||||
? "border-red-500/30 bg-red-500/10 text-red-300"
|
||||
: "border-green-500/30 bg-green-500/10 text-green-300";
|
||||
? "border-red-500/30 bg-red-500/10 text-red-700 dark:text-red-300"
|
||||
: "border-green-500/30 bg-green-500/10 text-green-700 dark:text-green-300";
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
<span
|
||||
@@ -941,8 +941,8 @@ function DiffTable({ rows }: { rows: DiffRow[] }) {
|
||||
}
|
||||
const lineClassesByKind: Record<DiffRow["kind"], string> = {
|
||||
context: "bg-transparent",
|
||||
removed: "bg-red-500/10 text-red-100",
|
||||
added: "bg-green-500/10 text-green-100",
|
||||
removed: "bg-red-500/10 text-red-900 dark:text-red-100",
|
||||
added: "bg-green-500/10 text-green-900 dark:text-green-100",
|
||||
};
|
||||
const markerByKind: Record<DiffRow["kind"], string> = {
|
||||
context: " ",
|
||||
|
||||
@@ -79,7 +79,7 @@ export function RoutineSaveBar({
|
||||
)}
|
||||
>
|
||||
{saveConflict ? (
|
||||
<div className="flex items-center gap-2 text-sm text-amber-200">
|
||||
<div className="flex items-center gap-2 text-sm text-amber-800 dark:text-amber-200">
|
||||
<AlertTriangle className="h-4 w-4" />
|
||||
<span>Routine changed elsewhere. Reload to merge.</span>
|
||||
</div>
|
||||
|
||||
@@ -116,23 +116,14 @@ export function ToggleField({
|
||||
<span className="text-xs text-muted-foreground">{label}</span>
|
||||
{hint && <HintIcon text={hint} />}
|
||||
</div>
|
||||
<button
|
||||
data-slot="toggle"
|
||||
{/* Gallery feedback r3: was a hand-rolled h-5 w-9 pill with a bg-green-600
|
||||
track — the app's second switch implementation. Converged on the one
|
||||
canonical ToggleSwitch (status-green on-state), DESIGN.md principle 1. */}
|
||||
<ToggleSwitch
|
||||
data-testid={toggleTestId}
|
||||
type="button"
|
||||
className={cn(
|
||||
"relative inline-flex h-5 w-9 items-center rounded-full transition-colors",
|
||||
checked ? "bg-green-600" : "bg-muted"
|
||||
)}
|
||||
onClick={() => onChange(!checked)}
|
||||
>
|
||||
<span
|
||||
className={cn(
|
||||
"inline-block h-3.5 w-3.5 rounded-full bg-white transition-transform",
|
||||
checked ? "translate-x-4.5" : "translate-x-0.5"
|
||||
)}
|
||||
/>
|
||||
</button>
|
||||
checked={checked}
|
||||
onCheckedChange={onChange}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,9 +3,9 @@ import { getOutputFileGlyph, type OutputFileTone } from "@/lib/issue-output";
|
||||
|
||||
const TONE_CLASSES: Record<OutputFileTone, string> = {
|
||||
video: "bg-indigo-500/15 text-indigo-300",
|
||||
pdf: "bg-red-500/15 text-red-300",
|
||||
zip: "bg-amber-500/15 text-amber-300",
|
||||
image: "bg-emerald-500/15 text-emerald-300",
|
||||
pdf: "bg-red-500/15 text-red-700 dark:text-red-300",
|
||||
zip: "bg-amber-500/15 text-amber-700 dark:text-amber-300",
|
||||
image: "bg-emerald-500/15 text-emerald-700 dark:text-emerald-300",
|
||||
bin: "bg-muted text-muted-foreground",
|
||||
};
|
||||
|
||||
|
||||
+29
-23
@@ -42,17 +42,42 @@ export const issueStatusText: Record<string, string> = {
|
||||
|
||||
export const issueStatusTextDefault = "text-muted-foreground";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Brand `.task-chip` status palette (PAP-75 / status-reference.html)
|
||||
//
|
||||
// Colour-named, 1px border, light + dark — values straight from paperclip.ing
|
||||
// `brand.css`. Shared by the agents section (PAP-80) and the All Projects page
|
||||
// (PAP-91); PAP-99 brings it to issue/task status chips, adding `violet` for
|
||||
// `in_review`.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export type BrandChipColor = "gray" | "blue" | "amber" | "green" | "violet" | "red";
|
||||
|
||||
export const brandChipBadge: Record<BrandChipColor, string> = {
|
||||
gray: "bg-[#F5F3F0] text-[#52585D] border-[#A8AEB2] dark:bg-[#6e696024] dark:text-[#9A958A] dark:border-[#9e958a73]",
|
||||
blue: "bg-[#DBEAFE] text-[#1D4ED8] border-[#2563EB] dark:bg-[#2563eb2e] dark:text-[#2563EB] dark:border-[#2563eb73]",
|
||||
amber: "bg-[#FEF3C7] text-[#B45309] border-[#F59E0B] dark:bg-[#f59e0b24] dark:text-[#F59E0B] dark:border-[#f59e0b73]",
|
||||
green: "bg-[#DCFCE7] text-[#188A3C] border-[#22C55E] dark:bg-[#22c55e1f] dark:text-[#22C55E] dark:border-[#22c55e73]",
|
||||
violet: "bg-[#EDE9FE] text-[#5B21B6] border-[#7C3AED] dark:bg-[#7c3aed2e] dark:text-[#7C3AED] dark:border-[#7c3aed73]",
|
||||
red: "bg-[#FEE2E2] text-[#991B1B] border-[#DC2626] dark:bg-[#dc26262e] dark:text-[#DC2626] dark:border-[#dc262673]",
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Badge colors — used by StatusBadge for all entity types
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
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-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.
|
||||
// Gallery feedback r3: agent chips route through the brand chip families
|
||||
// (bordered .task-chip recipes) instead of ad-hoc tints. NOTE: `active` has
|
||||
// no canonical agent status — user-ruled mapping to the brand GREEN family;
|
||||
// `idle` is the gray family (was a yellow/amber tint); `error` rides the
|
||||
// shared run-status red entry below.
|
||||
active: `border ${brandChipBadge.green}`,
|
||||
running: `border ${brandChipBadge.blue}`, // r1 made this blue; r3 routes through brandChipBadge.blue.
|
||||
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",
|
||||
paused: `border ${brandChipBadge.amber}`,
|
||||
idle: `border ${brandChipBadge.gray}`,
|
||||
archived: "bg-muted text-muted-foreground",
|
||||
|
||||
// Goal statuses
|
||||
@@ -126,25 +151,6 @@ export const agentStatusMotion: Record<string, string> = {
|
||||
error: "hb-blink",
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Brand `.task-chip` status palette (PAP-75 / status-reference.html)
|
||||
//
|
||||
// Colour-named, 1px border, light + dark — values straight from paperclip.ing
|
||||
// `brand.css`. Shared by the agents section (PAP-80) and the All Projects page
|
||||
// (PAP-91); PAP-99 brings it to issue/task status chips, adding `violet` for
|
||||
// `in_review`.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export type BrandChipColor = "gray" | "blue" | "amber" | "green" | "violet" | "red";
|
||||
|
||||
export const brandChipBadge: Record<BrandChipColor, string> = {
|
||||
gray: "bg-[#F5F3F0] text-[#52585D] border-[#A8AEB2] dark:bg-[#6e696024] dark:text-[#9A958A] dark:border-[#9e958a73]",
|
||||
blue: "bg-[#DBEAFE] text-[#1D4ED8] border-[#2563EB] dark:bg-[#2563eb2e] dark:text-[#2563EB] dark:border-[#2563eb73]",
|
||||
amber: "bg-[#FEF3C7] text-[#B45309] border-[#F59E0B] dark:bg-[#f59e0b24] dark:text-[#F59E0B] dark:border-[#f59e0b73]",
|
||||
green: "bg-[#DCFCE7] text-[#188A3C] border-[#22C55E] dark:bg-[#22c55e1f] dark:text-[#22C55E] dark:border-[#22c55e73]",
|
||||
violet: "bg-[#EDE9FE] text-[#5B21B6] border-[#7C3AED] dark:bg-[#7c3aed2e] dark:text-[#7C3AED] dark:border-[#7c3aed73]",
|
||||
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
|
||||
|
||||
@@ -963,7 +963,7 @@ export function AgentDetail() {
|
||||
return (
|
||||
<div className={cn("space-y-6", isMobile && showConfigActionBar && "pb-24")}>
|
||||
{showLeftAgentNotice ? (
|
||||
<div className="flex items-center gap-3 border border-yellow-300/35 bg-yellow-300/10 px-3 py-2 text-sm text-yellow-100">
|
||||
<div className="flex items-center gap-3 border border-yellow-300/35 bg-yellow-300/10 px-3 py-2 text-sm text-yellow-900 dark:text-yellow-100">
|
||||
<p className="min-w-0 flex-1">
|
||||
You left this agent. It no longer appears in your sidebar.
|
||||
</p>
|
||||
@@ -988,7 +988,7 @@ export function AgentDetail() {
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
className="h-6 w-6 shrink-0 text-yellow-100/70 hover:text-yellow-100"
|
||||
className="h-6 w-6 shrink-0 text-yellow-900/70 hover:text-yellow-900 dark:text-yellow-100/70 dark:hover:text-yellow-100"
|
||||
aria-label="Dismiss agent membership notice"
|
||||
onClick={() => setDismissedLeftAgentIds((current) => new Set(current).add(agent.id))}
|
||||
>
|
||||
@@ -997,20 +997,20 @@ export function AgentDetail() {
|
||||
</div>
|
||||
) : null}
|
||||
{hasInvalidOrgChain ? (
|
||||
<div className="flex items-start gap-3 border border-amber-300/35 bg-amber-300/10 px-3 py-2 text-sm text-amber-100">
|
||||
<div className="flex items-start gap-3 border border-amber-300/35 bg-amber-300/10 px-3 py-2 text-sm text-amber-900 dark:text-amber-100">
|
||||
<AlertTriangle className="mt-0.5 h-4 w-4 shrink-0" />
|
||||
<div className="min-w-0 space-y-1">
|
||||
<p className="font-medium">Invalid reporting chain</p>
|
||||
<p className="text-amber-100/90">
|
||||
<p className="text-amber-900/90 dark:text-amber-100/90">
|
||||
{agent.name} cannot accept tasks or start runs until its reporting chain is repaired.
|
||||
</p>
|
||||
<p className="break-words font-mono text-xs text-amber-100/80">
|
||||
<p className="break-words font-mono text-xs text-amber-900/80 dark:text-amber-100/80">
|
||||
{formatOrgChainHealthPath(agent)}
|
||||
</p>
|
||||
{agent.orgChainHealth?.repairGuidance ? (
|
||||
<p className="text-amber-100/85">{agent.orgChainHealth.repairGuidance}</p>
|
||||
<p className="text-amber-900/85 dark:text-amber-100/85">{agent.orgChainHealth.repairGuidance}</p>
|
||||
) : (
|
||||
<p className="text-amber-100/85">
|
||||
<p className="text-amber-900/85 dark:text-amber-100/85">
|
||||
Assign this agent to an active manager/root, or explicitly pause or terminate the affected agent/subtree.
|
||||
</p>
|
||||
)}
|
||||
@@ -2156,7 +2156,7 @@ function PromptsTab({
|
||||
{(bundle?.warnings ?? []).length > 0 && (
|
||||
<div className="space-y-2">
|
||||
{(bundle?.warnings ?? []).map((warning) => (
|
||||
<div key={warning} className="rounded-md border border-sky-500/25 bg-sky-500/10 px-3 py-2 text-xs text-sky-100">
|
||||
<div key={warning} className="rounded-md border border-sky-500/25 bg-sky-500/10 px-3 py-2 text-xs text-sky-900 dark:text-sky-100">
|
||||
{warning}
|
||||
</div>
|
||||
))}
|
||||
@@ -2435,7 +2435,7 @@ function PromptsTab({
|
||||
return (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<span className="ml-3 shrink-0 rounded border border-amber-500/40 bg-amber-500/10 text-amber-200 px-1.5 py-0.5 text-(length:--text-nano) uppercase tracking-wide cursor-help">
|
||||
<span className="ml-3 shrink-0 rounded border border-amber-500/40 bg-amber-500/10 text-amber-800 dark:text-amber-200 px-1.5 py-0.5 text-(length:--text-nano) uppercase tracking-wide cursor-help">
|
||||
virtual file
|
||||
</span>
|
||||
</TooltipTrigger>
|
||||
|
||||
@@ -249,7 +249,7 @@ export function CompanyAccess() {
|
||||
</div>
|
||||
|
||||
{access && !access.currentUserRole && (
|
||||
<div className="rounded-xl border border-amber-500/40 px-4 py-3 text-sm text-amber-200">
|
||||
<div className="rounded-xl border border-amber-500/40 px-4 py-3 text-sm text-amber-800 dark:text-amber-200">
|
||||
This account can manage access here through instance-admin privileges, but it does not currently hold an active company membership.
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -422,13 +422,13 @@ function TrustChip({ level }: { level: CompanySkillTrustLevel }) {
|
||||
icon: Folder,
|
||||
label: "Includes assets",
|
||||
tooltip: "Ships images, fonts, or other non-script files.",
|
||||
className: "border-cyan-500/30 bg-cyan-500/10 text-cyan-200",
|
||||
className: "border-cyan-500/30 bg-cyan-500/10 text-cyan-800 dark:text-cyan-200",
|
||||
},
|
||||
scripts_executables: {
|
||||
icon: AlertTriangle,
|
||||
label: "Includes scripts",
|
||||
tooltip: "Ships executable scripts. Review before installing.",
|
||||
className: "border-amber-500/40 bg-amber-500/10 text-amber-200",
|
||||
className: "border-amber-500/40 bg-amber-500/10 text-amber-800 dark:text-amber-200",
|
||||
},
|
||||
} as const;
|
||||
const config = map[level] ?? map.markdown_only;
|
||||
@@ -453,7 +453,7 @@ function CompatChip({ compatibility }: { compatibility: CompanySkillCompatibilit
|
||||
icon: HelpCircle,
|
||||
label: "Unknown format",
|
||||
tooltip: "Paperclip could not validate this skill as Agent Skills markdown. Install at your own risk.",
|
||||
className: "border-yellow-500/40 bg-yellow-500/10 text-yellow-200",
|
||||
className: "border-yellow-500/40 bg-yellow-500/10 text-yellow-800 dark:text-yellow-200",
|
||||
},
|
||||
invalid: {
|
||||
icon: XOctagon,
|
||||
@@ -864,7 +864,7 @@ function SkillCard({ card, onOpen }: { card: DiscoveryCard; onOpen: (card: Disco
|
||||
</div>
|
||||
<div className="mt-2 flex flex-wrap items-center gap-1">
|
||||
{card.installed ? (
|
||||
<span className="inline-flex items-center rounded-full border border-emerald-500/30 bg-emerald-500/10 px-2 py-0.5 text-(length:--text-nano) text-emerald-300">
|
||||
<span className="inline-flex items-center rounded-full border border-emerald-500/30 bg-emerald-500/10 px-2 py-0.5 text-(length:--text-nano) text-emerald-700 dark:text-emerald-300">
|
||||
Installed
|
||||
</span>
|
||||
) : null}
|
||||
@@ -1727,7 +1727,7 @@ function CatalogDetailPane({
|
||||
);
|
||||
} else if (hashOutOfSync) {
|
||||
cta = (
|
||||
<Button onClick={onUpdate} disabled={loadingPrimaryAction} className="border-amber-500/40 bg-amber-500/20 text-amber-100 hover:bg-amber-500/30">
|
||||
<Button onClick={onUpdate} disabled={loadingPrimaryAction} className="border-amber-500/40 bg-amber-500/20 text-amber-900 dark:text-amber-100 hover:bg-amber-500/30">
|
||||
<ArrowUpCircle className="mr-1.5 h-3.5 w-3.5" />
|
||||
Update from catalog
|
||||
</Button>
|
||||
@@ -1770,7 +1770,7 @@ function CatalogDetailPane({
|
||||
{hashOutOfSync ? (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<span className="inline-flex items-center gap-1 rounded-full border border-amber-500/40 bg-amber-500/10 px-2 py-0.5 text-(length:--text-micro) text-amber-200">
|
||||
<span className="inline-flex items-center gap-1 rounded-full border border-amber-500/40 bg-amber-500/10 px-2 py-0.5 text-(length:--text-micro) text-amber-800 dark:text-amber-200">
|
||||
<ArrowUpCircle className="h-3 w-3" aria-hidden="true" />
|
||||
Update available
|
||||
</span>
|
||||
@@ -1908,7 +1908,7 @@ function InstallPreviewDialog({
|
||||
{skill.trustLevel === "markdown_only" ? (
|
||||
<span className="text-muted-foreground">Safe</span>
|
||||
) : skill.trustLevel === "scripts_executables" ? (
|
||||
<span className="text-amber-200">Review required</span>
|
||||
<span className="text-amber-800 dark:text-amber-200">Review required</span>
|
||||
) : (
|
||||
<span className="text-muted-foreground">Non-script assets</span>
|
||||
)}
|
||||
@@ -1952,7 +1952,7 @@ function InstallPreviewDialog({
|
||||
</div>
|
||||
|
||||
{conflict ? (
|
||||
<div className="rounded-md border border-amber-500/40 bg-amber-500/10 p-3 text-xs text-amber-200">
|
||||
<div className="rounded-md border border-amber-500/40 bg-amber-500/10 p-3 text-xs text-amber-800 dark:text-amber-200">
|
||||
An existing skill with key <span className="font-mono">{conflict.key}</span> is installed (
|
||||
{conflict.sourceLabel ?? conflict.sourceType}). Installing will {defaultAction === "update" ? "overwrite the catalog content" : "replace the existing skill"}.
|
||||
</div>
|
||||
@@ -2456,8 +2456,8 @@ function SkillVersionDiffDialog({
|
||||
);
|
||||
const lineClassesByKind: Record<DiffRow["kind"], string> = {
|
||||
context: "bg-transparent",
|
||||
removed: "bg-red-500/10 text-red-100",
|
||||
added: "bg-green-500/10 text-green-100",
|
||||
removed: "bg-red-500/10 text-red-900 dark:text-red-100",
|
||||
added: "bg-green-500/10 text-green-900 dark:text-green-100",
|
||||
};
|
||||
const markerByKind: Record<DiffRow["kind"], string> = {
|
||||
context: " ",
|
||||
|
||||
@@ -221,17 +221,17 @@ export function Dashboard() {
|
||||
{data.budgets.activeIncidents > 0 ? (
|
||||
<div className="flex items-start justify-between gap-3 rounded-xl border border-red-500/20 bg-(image:--gradient-extract-1) px-4 py-3">
|
||||
<div className="flex items-start gap-2.5">
|
||||
<PauseCircle className="mt-0.5 h-4 w-4 shrink-0 text-red-300" />
|
||||
<PauseCircle className="mt-0.5 h-4 w-4 shrink-0 text-red-700 dark:text-red-300" />
|
||||
<div>
|
||||
<p className="text-sm font-medium text-red-50">
|
||||
<p className="text-sm font-medium text-red-950 dark:text-red-50">
|
||||
{data.budgets.activeIncidents} active budget incident{data.budgets.activeIncidents === 1 ? "" : "s"}
|
||||
</p>
|
||||
<p className="text-xs text-red-100/70">
|
||||
<p className="text-xs text-red-900/70 dark:text-red-100/70">
|
||||
{data.budgets.pausedAgents} agents paused · {data.budgets.pausedProjects} projects paused · {data.budgets.pendingApprovals} pending budget approvals
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<Link to="/costs" className="text-sm underline underline-offset-2 text-red-100">
|
||||
<Link to="/costs" className="text-sm underline underline-offset-2 text-red-900 dark:text-red-100">
|
||||
Open budgets
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
@@ -10,6 +10,7 @@ import { EmptyState } from "../components/EmptyState";
|
||||
import { PageSkeleton } from "../components/PageSkeleton";
|
||||
import { ChevronRight, GitBranch } from "lucide-react";
|
||||
import { cn } from "../lib/utils";
|
||||
import { agentStatusDot, agentStatusDotDefault } from "../lib/status-colors";
|
||||
|
||||
function OrgTree({
|
||||
nodes,
|
||||
@@ -67,15 +68,9 @@ function OrgTreeNode({
|
||||
<span
|
||||
className={cn(
|
||||
"h-2 w-2 rounded-full shrink-0",
|
||||
node.status === "active"
|
||||
? "bg-green-400"
|
||||
: node.status === "paused"
|
||||
? "bg-yellow-400"
|
||||
: node.status === "pending_approval"
|
||||
? "bg-amber-400"
|
||||
: node.status === "error"
|
||||
? "bg-red-400"
|
||||
: "bg-neutral-400"
|
||||
// Gallery feedback r3: route through the canonical agentStatusDot
|
||||
// map (identical hues for existing keys; adds the blue running dot).
|
||||
agentStatusDot[node.status] ?? agentStatusDotDefault,
|
||||
)}
|
||||
/>
|
||||
<span className="font-medium flex-1">{node.name}</span>
|
||||
|
||||
@@ -729,7 +729,7 @@ export function ProjectDetail() {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{showLeftProjectNotice ? (
|
||||
<div className="flex items-center gap-3 border border-yellow-300/35 bg-yellow-300/10 px-3 py-2 text-sm text-yellow-100">
|
||||
<div className="flex items-center gap-3 border border-yellow-300/35 bg-yellow-300/10 px-3 py-2 text-sm text-yellow-900 dark:text-yellow-100">
|
||||
<p className="min-w-0 flex-1">
|
||||
You left this project. It no longer appears in your sidebar.
|
||||
</p>
|
||||
@@ -754,7 +754,7 @@ export function ProjectDetail() {
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
className="h-6 w-6 shrink-0 text-yellow-100/70 hover:text-yellow-100"
|
||||
className="h-6 w-6 shrink-0 text-yellow-900/70 hover:text-yellow-900 dark:text-yellow-100/70 dark:hover:text-yellow-100"
|
||||
aria-label="Dismiss project membership notice"
|
||||
onClick={() => setDismissedLeftProjectIds((current) => new Set(current).add(project.id))}
|
||||
>
|
||||
@@ -779,7 +779,7 @@ export function ProjectDetail() {
|
||||
className="text-xl font-bold"
|
||||
/>
|
||||
{project.pauseReason === "budget" ? (
|
||||
<div className="inline-flex items-center gap-2 rounded-full border border-red-500/30 bg-red-500/10 px-3 py-1 text-(length:--text-micro) font-medium uppercase tracking-(--tracking-caps) text-red-200">
|
||||
<div className="inline-flex items-center gap-2 rounded-full border border-red-500/30 bg-red-500/10 px-3 py-1 text-(length:--text-micro) font-medium uppercase tracking-(--tracking-caps) text-red-800 dark:text-red-200">
|
||||
<span className="h-2 w-2 rounded-full bg-red-400" />
|
||||
Paused by budget hard stop
|
||||
</div>
|
||||
|
||||
Reference in new issue
Block a user