refactor(ui): standardize status/priority colors and improve text legibility
Create shared status-colors.ts module as single source of truth for all status and priority color definitions. Replace hardcoded color classes in StatusIcon, StatusBadge, PriorityIcon, NewIssueDialog, Agents, AgentDetail, and DesignGuide. Fix inconsistent hues (in_progress was yellow in StatusIcon but indigo in StatusBadge, blocked was red vs amber). Bump identifier text from text-xs to text-sm and improve MetricCard label legibility. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -34,6 +34,7 @@ export type IssueViewState = {
|
||||
statuses: string[];
|
||||
priorities: string[];
|
||||
assignees: string[];
|
||||
labels: string[];
|
||||
sortField: "status" | "priority" | "title" | "created" | "updated";
|
||||
sortDir: "asc" | "desc";
|
||||
groupBy: "status" | "priority" | "assignee" | "none";
|
||||
@@ -44,6 +45,7 @@ const defaultViewState: IssueViewState = {
|
||||
statuses: ["todo", "in_progress", "in_review", "blocked"],
|
||||
priorities: [],
|
||||
assignees: [],
|
||||
labels: [],
|
||||
sortField: "status",
|
||||
sortDir: "asc",
|
||||
groupBy: "status",
|
||||
@@ -85,6 +87,7 @@ function applyFilters(issues: Issue[], state: IssueViewState): Issue[] {
|
||||
if (state.statuses.length > 0) result = result.filter((i) => state.statuses.includes(i.status));
|
||||
if (state.priorities.length > 0) result = result.filter((i) => state.priorities.includes(i.priority));
|
||||
if (state.assignees.length > 0) result = result.filter((i) => i.assigneeAgentId != null && state.assignees.includes(i.assigneeAgentId));
|
||||
if (state.labels.length > 0) result = result.filter((i) => (i.labelIds ?? []).some((id) => state.labels.includes(id)));
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -115,6 +118,7 @@ function countActiveFilters(state: IssueViewState): number {
|
||||
if (state.statuses.length > 0) count++;
|
||||
if (state.priorities.length > 0) count++;
|
||||
if (state.assignees.length > 0) count++;
|
||||
if (state.labels.length > 0) count++;
|
||||
return count;
|
||||
}
|
||||
|
||||
@@ -148,6 +152,7 @@ export function IssuesList({
|
||||
initialAssignees,
|
||||
onUpdateIssue,
|
||||
}: IssuesListProps) {
|
||||
const { selectedCompanyId } = useCompany();
|
||||
const { openNewIssue } = useDialog();
|
||||
|
||||
const [viewState, setViewState] = useState<IssueViewState>(() => {
|
||||
@@ -174,6 +179,12 @@ export function IssuesList({
|
||||
return sortIssues(applyFilters(issues, viewState), viewState);
|
||||
}, [issues, viewState]);
|
||||
|
||||
const { data: labels } = useQuery({
|
||||
queryKey: queryKeys.issues.labels(selectedCompanyId!),
|
||||
queryFn: () => issuesApi.listLabels(selectedCompanyId!),
|
||||
enabled: !!selectedCompanyId,
|
||||
});
|
||||
|
||||
const activeFilterCount = countActiveFilters(viewState);
|
||||
|
||||
const groupedContent = useMemo(() => {
|
||||
@@ -254,7 +265,7 @@ export function IssuesList({
|
||||
className="h-3 w-3 ml-1 hidden sm:block"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
updateView({ statuses: [], priorities: [], assignees: [] });
|
||||
updateView({ statuses: [], priorities: [], assignees: [], labels: [] });
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
@@ -267,7 +278,7 @@ export function IssuesList({
|
||||
{activeFilterCount > 0 && (
|
||||
<button
|
||||
className="text-xs text-muted-foreground hover:text-foreground"
|
||||
onClick={() => updateView({ statuses: [], priorities: [], assignees: [] })}
|
||||
onClick={() => updateView({ statuses: [], priorities: [], assignees: [], labels: [] })}
|
||||
>
|
||||
Clear
|
||||
</button>
|
||||
@@ -354,6 +365,24 @@ export function IssuesList({
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{labels && labels.length > 0 && (
|
||||
<div className="space-y-1">
|
||||
<span className="text-xs text-muted-foreground">Labels</span>
|
||||
<div className="space-y-0.5 max-h-32 overflow-y-auto">
|
||||
{labels.map((label) => (
|
||||
<label key={label.id} className="flex items-center gap-2 px-2 py-1 rounded-sm hover:bg-accent/50 cursor-pointer">
|
||||
<Checkbox
|
||||
checked={viewState.labels.includes(label.id)}
|
||||
onCheckedChange={() => updateView({ labels: toggleInArray(viewState.labels, label.id) })}
|
||||
/>
|
||||
<span className="h-2.5 w-2.5 rounded-full" style={{ backgroundColor: label.color }} />
|
||||
<span className="text-sm">{label.name}</span>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -494,10 +523,30 @@ export function IssuesList({
|
||||
onChange={(s) => onUpdateIssue(issue.id, { status: s })}
|
||||
/>
|
||||
</div>
|
||||
<span className="text-xs text-muted-foreground font-mono shrink-0">
|
||||
<span className="text-sm text-muted-foreground font-mono shrink-0">
|
||||
{issue.identifier ?? issue.id.slice(0, 8)}
|
||||
</span>
|
||||
<span className="truncate flex-1 min-w-0">{issue.title}</span>
|
||||
{(issue.labels ?? []).length > 0 && (
|
||||
<div className="hidden md:flex items-center gap-1 max-w-[240px] overflow-hidden">
|
||||
{(issue.labels ?? []).slice(0, 3).map((label) => (
|
||||
<span
|
||||
key={label.id}
|
||||
className="inline-flex items-center rounded-full border px-1.5 py-0.5 text-[10px] font-medium"
|
||||
style={{
|
||||
borderColor: label.color,
|
||||
color: label.color,
|
||||
backgroundColor: `${label.color}1f`,
|
||||
}}
|
||||
>
|
||||
{label.name}
|
||||
</span>
|
||||
))}
|
||||
{(issue.labels ?? []).length > 3 && (
|
||||
<span className="text-[10px] text-muted-foreground">+{(issue.labels ?? []).length - 3}</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<div className="flex items-center gap-2 sm:gap-3 shrink-0 ml-auto">
|
||||
{liveIssueIds?.has(issue.id) && (
|
||||
<span className="inline-flex items-center gap-1 sm:gap-1.5 px-1.5 sm:px-2 py-0.5 rounded-full bg-blue-500/10">
|
||||
|
||||
@@ -23,11 +23,11 @@ export function MetricCard({ icon: Icon, value, label, description, to, onClick
|
||||
<p className={`text-lg sm:text-2xl font-bold${isClickable ? " cursor-pointer" : ""}`}>
|
||||
{value}
|
||||
</p>
|
||||
<p className={`text-xs sm:text-sm text-muted-foreground${isClickable ? " cursor-pointer" : ""}`}>
|
||||
<p className={`text-sm text-muted-foreground${isClickable ? " cursor-pointer" : ""}`}>
|
||||
{label}
|
||||
</p>
|
||||
{description && (
|
||||
<div className="text-[11px] sm:text-xs text-muted-foreground mt-1 hidden sm:block">{description}</div>
|
||||
<div className="text-xs sm:text-sm text-muted-foreground mt-1 hidden sm:block">{description}</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="bg-muted p-1.5 sm:p-2 rounded-md h-fit shrink-0">
|
||||
|
||||
@@ -33,6 +33,7 @@ import {
|
||||
Calendar,
|
||||
} from "lucide-react";
|
||||
import { cn } from "../lib/utils";
|
||||
import { issueStatusText, issueStatusTextDefault, priorityColor, priorityColorDefault } from "../lib/status-colors";
|
||||
import { MarkdownEditor, type MarkdownEditorRef } from "./MarkdownEditor";
|
||||
import { AgentIcon } from "./AgentIconPicker";
|
||||
import type { Project, Agent } from "@paperclip/shared";
|
||||
@@ -68,18 +69,18 @@ function clearDraft() {
|
||||
}
|
||||
|
||||
const statuses = [
|
||||
{ value: "backlog", label: "Backlog", color: "text-muted-foreground" },
|
||||
{ value: "todo", label: "Todo", color: "text-blue-400" },
|
||||
{ value: "in_progress", label: "In Progress", color: "text-yellow-400" },
|
||||
{ value: "in_review", label: "In Review", color: "text-violet-400" },
|
||||
{ value: "done", label: "Done", color: "text-green-400" },
|
||||
{ value: "backlog", label: "Backlog", color: issueStatusText.backlog ?? issueStatusTextDefault },
|
||||
{ value: "todo", label: "Todo", color: issueStatusText.todo ?? issueStatusTextDefault },
|
||||
{ value: "in_progress", label: "In Progress", color: issueStatusText.in_progress ?? issueStatusTextDefault },
|
||||
{ value: "in_review", label: "In Review", color: issueStatusText.in_review ?? issueStatusTextDefault },
|
||||
{ value: "done", label: "Done", color: issueStatusText.done ?? issueStatusTextDefault },
|
||||
];
|
||||
|
||||
const priorities = [
|
||||
{ value: "critical", label: "Critical", icon: AlertTriangle, color: "text-red-400" },
|
||||
{ value: "high", label: "High", icon: ArrowUp, color: "text-orange-400" },
|
||||
{ value: "medium", label: "Medium", icon: Minus, color: "text-yellow-400" },
|
||||
{ value: "low", label: "Low", icon: ArrowDown, color: "text-blue-400" },
|
||||
{ value: "critical", label: "Critical", icon: AlertTriangle, color: priorityColor.critical ?? priorityColorDefault },
|
||||
{ value: "high", label: "High", icon: ArrowUp, color: priorityColor.high ?? priorityColorDefault },
|
||||
{ value: "medium", label: "Medium", icon: Minus, color: priorityColor.medium ?? priorityColorDefault },
|
||||
{ value: "low", label: "Low", icon: ArrowDown, color: priorityColor.low ?? priorityColorDefault },
|
||||
];
|
||||
|
||||
export function NewIssueDialog() {
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
import { useState } from "react";
|
||||
import { ArrowUp, ArrowDown, Minus, AlertTriangle } from "lucide-react";
|
||||
import { cn } from "../lib/utils";
|
||||
import { priorityColor, priorityColorDefault } from "../lib/status-colors";
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
const priorityConfig: Record<string, { icon: typeof ArrowUp; color: string; label: string }> = {
|
||||
critical: { icon: AlertTriangle, color: "text-red-400", label: "Critical" },
|
||||
high: { icon: ArrowUp, color: "text-orange-400", label: "High" },
|
||||
medium: { icon: Minus, color: "text-yellow-400", label: "Medium" },
|
||||
low: { icon: ArrowDown, color: "text-blue-400", label: "Low" },
|
||||
critical: { icon: AlertTriangle, color: priorityColor.critical ?? priorityColorDefault, label: "Critical" },
|
||||
high: { icon: ArrowUp, color: priorityColor.high ?? priorityColorDefault, label: "High" },
|
||||
medium: { icon: Minus, color: priorityColor.medium ?? priorityColorDefault, label: "Medium" },
|
||||
low: { icon: ArrowDown, color: priorityColor.low ?? priorityColorDefault, label: "Low" },
|
||||
};
|
||||
|
||||
const allPriorities = ["critical", "high", "medium", "low"];
|
||||
|
||||
@@ -1,39 +1,12 @@
|
||||
import { cn } from "../lib/utils";
|
||||
|
||||
const statusColors: Record<string, string> = {
|
||||
active: "bg-green-900/50 text-green-300",
|
||||
running: "bg-cyan-900/50 text-cyan-300",
|
||||
paused: "bg-orange-900/50 text-orange-300",
|
||||
idle: "bg-yellow-900/50 text-yellow-300",
|
||||
archived: "bg-neutral-800 text-neutral-400",
|
||||
planned: "bg-neutral-800 text-neutral-400",
|
||||
achieved: "bg-green-900/50 text-green-300",
|
||||
completed: "bg-green-900/50 text-green-300",
|
||||
failed: "bg-red-900/50 text-red-300",
|
||||
timed_out: "bg-orange-900/50 text-orange-300",
|
||||
succeeded: "bg-green-900/50 text-green-300",
|
||||
error: "bg-red-900/50 text-red-300",
|
||||
pending_approval: "bg-amber-900/50 text-amber-300",
|
||||
backlog: "bg-neutral-800 text-neutral-400",
|
||||
todo: "bg-blue-900/50 text-blue-300",
|
||||
in_progress: "bg-indigo-900/50 text-indigo-300",
|
||||
in_review: "bg-violet-900/50 text-violet-300",
|
||||
blocked: "bg-amber-900/50 text-amber-300",
|
||||
done: "bg-green-900/50 text-green-300",
|
||||
terminated: "bg-red-900/50 text-red-300",
|
||||
cancelled: "bg-neutral-800 text-neutral-500",
|
||||
pending: "bg-yellow-900/50 text-yellow-300",
|
||||
revision_requested: "bg-amber-900/50 text-amber-300",
|
||||
approved: "bg-green-900/50 text-green-300",
|
||||
rejected: "bg-red-900/50 text-red-300",
|
||||
};
|
||||
import { statusBadge, statusBadgeDefault } from "../lib/status-colors";
|
||||
|
||||
export function StatusBadge({ status }: { status: string }) {
|
||||
return (
|
||||
<span
|
||||
className={cn(
|
||||
"inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium",
|
||||
statusColors[status] ?? "bg-neutral-800 text-neutral-400"
|
||||
statusBadge[status] ?? statusBadgeDefault
|
||||
)}
|
||||
>
|
||||
{status.replace("_", " ")}
|
||||
|
||||
@@ -1,18 +1,9 @@
|
||||
import { useState } from "react";
|
||||
import { cn } from "../lib/utils";
|
||||
import { issueStatusIcon, issueStatusIconDefault } from "../lib/status-colors";
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
const statusColors: Record<string, string> = {
|
||||
backlog: "text-muted-foreground border-muted-foreground",
|
||||
todo: "text-blue-400 border-blue-400",
|
||||
in_progress: "text-yellow-400 border-yellow-400",
|
||||
in_review: "text-violet-400 border-violet-400",
|
||||
done: "text-green-400 border-green-400",
|
||||
cancelled: "text-neutral-500 border-neutral-500",
|
||||
blocked: "text-red-400 border-red-400",
|
||||
};
|
||||
|
||||
const allStatuses = ["backlog", "todo", "in_progress", "in_review", "done", "cancelled", "blocked"];
|
||||
|
||||
function statusLabel(status: string): string {
|
||||
@@ -28,7 +19,7 @@ interface StatusIconProps {
|
||||
|
||||
export function StatusIcon({ status, onChange, className, showLabel }: StatusIconProps) {
|
||||
const [open, setOpen] = useState(false);
|
||||
const colorClass = statusColors[status] ?? "text-muted-foreground border-muted-foreground";
|
||||
const colorClass = issueStatusIcon[status] ?? issueStatusIconDefault;
|
||||
const isDone = status === "done";
|
||||
|
||||
const circle = (
|
||||
|
||||
108
ui/src/lib/status-colors.ts
Normal file
108
ui/src/lib/status-colors.ts
Normal file
@@ -0,0 +1,108 @@
|
||||
/**
|
||||
* Canonical status & priority color definitions.
|
||||
*
|
||||
* Every component that renders a status indicator (StatusIcon, StatusBadge,
|
||||
* agent status dots, etc.) should import from here so colors stay consistent.
|
||||
*/
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Issue status colors
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/** StatusIcon circle: text + border classes */
|
||||
export const issueStatusIcon: Record<string, string> = {
|
||||
backlog: "text-muted-foreground border-muted-foreground",
|
||||
todo: "text-blue-400 border-blue-400",
|
||||
in_progress: "text-yellow-400 border-yellow-400",
|
||||
in_review: "text-violet-400 border-violet-400",
|
||||
done: "text-green-400 border-green-400",
|
||||
cancelled: "text-neutral-500 border-neutral-500",
|
||||
blocked: "text-red-400 border-red-400",
|
||||
};
|
||||
|
||||
export const issueStatusIconDefault = "text-muted-foreground border-muted-foreground";
|
||||
|
||||
/** Text-only color for issue statuses (dropdowns, labels) */
|
||||
export const issueStatusText: Record<string, string> = {
|
||||
backlog: "text-muted-foreground",
|
||||
todo: "text-blue-400",
|
||||
in_progress: "text-yellow-400",
|
||||
in_review: "text-violet-400",
|
||||
done: "text-green-400",
|
||||
cancelled: "text-neutral-500",
|
||||
blocked: "text-red-400",
|
||||
};
|
||||
|
||||
export const issueStatusTextDefault = "text-muted-foreground";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Badge colors — used by StatusBadge for all entity types
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export const statusBadge: Record<string, string> = {
|
||||
// Agent statuses
|
||||
active: "bg-green-900/50 text-green-300",
|
||||
running: "bg-cyan-900/50 text-cyan-300",
|
||||
paused: "bg-orange-900/50 text-orange-300",
|
||||
idle: "bg-yellow-900/50 text-yellow-300",
|
||||
archived: "bg-neutral-800 text-neutral-400",
|
||||
|
||||
// Goal statuses
|
||||
planned: "bg-neutral-800 text-neutral-400",
|
||||
achieved: "bg-green-900/50 text-green-300",
|
||||
completed: "bg-green-900/50 text-green-300",
|
||||
|
||||
// Run statuses
|
||||
failed: "bg-red-900/50 text-red-300",
|
||||
timed_out: "bg-orange-900/50 text-orange-300",
|
||||
succeeded: "bg-green-900/50 text-green-300",
|
||||
error: "bg-red-900/50 text-red-300",
|
||||
terminated: "bg-red-900/50 text-red-300",
|
||||
pending: "bg-yellow-900/50 text-yellow-300",
|
||||
|
||||
// Approval statuses
|
||||
pending_approval: "bg-amber-900/50 text-amber-300",
|
||||
revision_requested: "bg-amber-900/50 text-amber-300",
|
||||
approved: "bg-green-900/50 text-green-300",
|
||||
rejected: "bg-red-900/50 text-red-300",
|
||||
|
||||
// Issue statuses — consistent hues with issueStatusIcon above
|
||||
backlog: "bg-neutral-800 text-neutral-400",
|
||||
todo: "bg-blue-900/50 text-blue-300",
|
||||
in_progress: "bg-yellow-900/50 text-yellow-300",
|
||||
in_review: "bg-violet-900/50 text-violet-300",
|
||||
blocked: "bg-red-900/50 text-red-300",
|
||||
done: "bg-green-900/50 text-green-300",
|
||||
cancelled: "bg-neutral-800 text-neutral-500",
|
||||
};
|
||||
|
||||
export const statusBadgeDefault = "bg-neutral-800 text-neutral-400";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Agent status dot — solid background for small indicator dots
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export const agentStatusDot: Record<string, string> = {
|
||||
running: "bg-cyan-400 animate-pulse",
|
||||
active: "bg-green-400",
|
||||
paused: "bg-yellow-400",
|
||||
idle: "bg-yellow-400",
|
||||
pending_approval: "bg-amber-400",
|
||||
error: "bg-red-400",
|
||||
archived: "bg-neutral-400",
|
||||
};
|
||||
|
||||
export const agentStatusDotDefault = "bg-neutral-400";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Priority colors
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export const priorityColor: Record<string, string> = {
|
||||
critical: "text-red-400",
|
||||
high: "text-orange-400",
|
||||
medium: "text-yellow-400",
|
||||
low: "text-blue-400",
|
||||
};
|
||||
|
||||
export const priorityColorDefault = "text-yellow-400";
|
||||
@@ -16,6 +16,7 @@ import { adapterLabels, roleLabels } from "../components/agent-config-primitives
|
||||
import { getUIAdapter, buildTranscript } from "../adapters";
|
||||
import type { TranscriptEntry } from "../adapters";
|
||||
import { StatusBadge } from "../components/StatusBadge";
|
||||
import { agentStatusDot, agentStatusDotDefault } from "../lib/status-colors";
|
||||
import { MarkdownBody } from "../components/MarkdownBody";
|
||||
import { CopyText } from "../components/CopyText";
|
||||
import { EntityRow } from "../components/EntityRow";
|
||||
@@ -1103,15 +1104,7 @@ function ConfigSummary({
|
||||
className="flex items-center gap-2 text-sm text-blue-400 hover:underline"
|
||||
>
|
||||
<span className="relative flex h-2 w-2">
|
||||
<span className={`absolute inline-flex h-full w-full rounded-full ${
|
||||
r.status === "active"
|
||||
? "bg-green-400"
|
||||
: r.status === "pending_approval"
|
||||
? "bg-amber-400"
|
||||
: r.status === "error"
|
||||
? "bg-red-400"
|
||||
: "bg-neutral-400"
|
||||
}`} />
|
||||
<span className={`absolute inline-flex h-full w-full rounded-full ${agentStatusDot[r.status] ?? agentStatusDotDefault}`} />
|
||||
</span>
|
||||
{r.name}
|
||||
<span className="text-muted-foreground text-xs">({roleLabels[r.role] ?? r.role})</span>
|
||||
|
||||
@@ -9,6 +9,7 @@ import { useBreadcrumbs } from "../context/BreadcrumbContext";
|
||||
import { useSidebar } from "../context/SidebarContext";
|
||||
import { queryKeys } from "../lib/queryKeys";
|
||||
import { StatusBadge } from "../components/StatusBadge";
|
||||
import { agentStatusDot, agentStatusDotDefault } from "../lib/status-colors";
|
||||
import { EntityRow } from "../components/EntityRow";
|
||||
import { EmptyState } from "../components/EmptyState";
|
||||
import { relativeTime, cn } from "../lib/utils";
|
||||
@@ -227,19 +228,7 @@ export function Agents() {
|
||||
leading={
|
||||
<span className="relative flex h-2.5 w-2.5">
|
||||
<span
|
||||
className={`absolute inline-flex h-full w-full rounded-full ${
|
||||
agent.status === "running"
|
||||
? "bg-cyan-400 animate-pulse"
|
||||
: agent.status === "active"
|
||||
? "bg-green-400"
|
||||
: agent.status === "paused"
|
||||
? "bg-yellow-400"
|
||||
: agent.status === "pending_approval"
|
||||
? "bg-amber-400"
|
||||
: agent.status === "error"
|
||||
? "bg-red-400"
|
||||
: "bg-neutral-400"
|
||||
}`}
|
||||
className={`absolute inline-flex h-full w-full rounded-full ${agentStatusDot[agent.status] ?? agentStatusDotDefault}`}
|
||||
/>
|
||||
</span>
|
||||
}
|
||||
@@ -325,18 +314,7 @@ function OrgTreeNode({
|
||||
}) {
|
||||
const agent = agentMap.get(node.id);
|
||||
|
||||
const statusColor =
|
||||
node.status === "running"
|
||||
? "bg-cyan-400 animate-pulse"
|
||||
: 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";
|
||||
const statusColor = agentStatusDot[node.status] ?? agentStatusDotDefault;
|
||||
|
||||
return (
|
||||
<div style={{ paddingLeft: depth * 24 }}>
|
||||
|
||||
@@ -69,6 +69,11 @@ import {
|
||||
DropdownMenuCheckboxItem,
|
||||
DropdownMenuShortcut,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import {
|
||||
Popover,
|
||||
PopoverTrigger,
|
||||
PopoverContent,
|
||||
} from "@/components/ui/popover";
|
||||
import {
|
||||
Sheet,
|
||||
SheetTrigger,
|
||||
@@ -110,6 +115,7 @@ import {
|
||||
import { StatusBadge } from "@/components/StatusBadge";
|
||||
import { StatusIcon } from "@/components/StatusIcon";
|
||||
import { PriorityIcon } from "@/components/PriorityIcon";
|
||||
import { agentStatusDot, agentStatusDotDefault } from "@/lib/status-colors";
|
||||
import { EntityRow } from "@/components/EntityRow";
|
||||
import { EmptyState } from "@/components/EmptyState";
|
||||
import { MetricCard } from "@/components/MetricCard";
|
||||
@@ -287,8 +293,8 @@ export function DesignGuide() {
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Tiny label — text-xs text-muted-foreground
|
||||
</p>
|
||||
<p className="text-xs font-mono text-muted-foreground">
|
||||
Mono identifier — text-xs font-mono text-muted-foreground
|
||||
<p className="text-sm font-mono text-muted-foreground">
|
||||
Mono identifier — text-sm font-mono text-muted-foreground
|
||||
</p>
|
||||
<p className="text-2xl font-bold">Large stat — text-2xl font-bold</p>
|
||||
<p className="font-mono text-xs">Log/code text — font-mono text-xs</p>
|
||||
@@ -435,16 +441,10 @@ export function DesignGuide() {
|
||||
|
||||
<SubSection title="Agent status dots">
|
||||
<div className="flex items-center gap-4 flex-wrap">
|
||||
{[
|
||||
["running", "bg-cyan-400 animate-pulse"],
|
||||
["active", "bg-green-400"],
|
||||
["paused", "bg-yellow-400"],
|
||||
["error", "bg-red-400"],
|
||||
["offline", "bg-neutral-400"],
|
||||
].map(([label, color]) => (
|
||||
{(["running", "active", "paused", "error", "archived"] as const).map((label) => (
|
||||
<div key={label} className="flex items-center gap-2">
|
||||
<span className="relative flex h-2.5 w-2.5">
|
||||
<span className={`inline-flex h-full w-full rounded-full ${color}`} />
|
||||
<span className={`inline-flex h-full w-full rounded-full ${agentStatusDot[label] ?? agentStatusDotDefault}`} />
|
||||
</span>
|
||||
<span className="text-xs text-muted-foreground">{label}</span>
|
||||
</div>
|
||||
|
||||
@@ -412,7 +412,7 @@ export function IssueDetail() {
|
||||
priority={issue.priority}
|
||||
onChange={(priority) => updateIssue.mutate({ priority })}
|
||||
/>
|
||||
<span className="text-xs font-mono text-muted-foreground">{issue.identifier ?? issue.id.slice(0, 8)}</span>
|
||||
<span className="text-sm font-mono text-muted-foreground">{issue.identifier ?? issue.id.slice(0, 8)}</span>
|
||||
|
||||
{issue.projectId ? (
|
||||
<Link
|
||||
@@ -606,7 +606,7 @@ export function IssueDetail() {
|
||||
<Link
|
||||
key={child.id}
|
||||
to={`/issues/${child.identifier ?? child.id}`}
|
||||
className="flex items-center justify-between px-3 py-2 text-xs hover:bg-accent/20 transition-colors"
|
||||
className="flex items-center justify-between px-3 py-2 text-sm hover:bg-accent/20 transition-colors"
|
||||
>
|
||||
<div className="flex items-center gap-2 min-w-0">
|
||||
<StatusIcon status={child.status} />
|
||||
|
||||
Reference in New Issue
Block a user