mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-28 20:21:50 +01:00
Settle published, successful Slack turns as Idle; resume the same conversation on an admitted message. Preserve unfinished work, delivery errors, and explicit dispositions. Verified through focused lifecycle/API/UI tests, full CI, and a real staging Slack conversation in the embedded browser. Co-Authored-By: Paperclip <noreply@paperclip.ing>
398 lines
19 KiB
TypeScript
398 lines
19 KiB
TypeScript
/**
|
||
* 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
|
||
// ---------------------------------------------------------------------------
|
||
|
||
// PAP-75 brand mapping ("blue = liveness"): todo → amber (queued), in_progress
|
||
// → blue (live). See `issueStatusColor` below for the canonical chip palette.
|
||
//
|
||
// The brand mapping is the default status palette. Chat-specific gating stays
|
||
// isolated to the Conference Room route/nav/API and does not control task
|
||
// status presentation.
|
||
|
||
/** StatusIcon circle: text + border classes */
|
||
export const issueStatusIcon: Record<string, string> = {
|
||
backlog: "text-muted-foreground border-muted-foreground",
|
||
todo: "text-amber-600 border-amber-600 dark:text-amber-400 dark:border-amber-400",
|
||
in_progress: "text-blue-600 border-blue-600 dark:text-blue-400 dark:border-blue-400",
|
||
in_review: "text-violet-600 border-violet-600 dark:text-violet-400 dark:border-violet-400",
|
||
done: "text-green-600 border-green-600 dark:text-green-400 dark:border-green-400",
|
||
cancelled: "text-neutral-500 border-neutral-500",
|
||
blocked: "text-red-600 border-red-600 dark:text-red-400 dark: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-amber-600 dark:text-amber-400",
|
||
in_progress: "text-blue-600 dark:text-blue-400",
|
||
in_review: "text-violet-600 dark:text-violet-400",
|
||
done: "text-green-600 dark:text-green-400",
|
||
cancelled: "text-neutral-500",
|
||
blocked: "text-red-600 dark:text-red-400",
|
||
};
|
||
|
||
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
|
||
// 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: `border ${brandChipBadge.amber}`,
|
||
idle: `border ${brandChipBadge.gray}`,
|
||
archived: "bg-muted text-muted-foreground",
|
||
|
||
// Goal statuses
|
||
planned: "bg-muted text-muted-foreground",
|
||
achieved: "bg-green-100 text-green-700 dark:bg-green-900/50 dark:text-green-300",
|
||
completed: "bg-green-100 text-green-700 dark:bg-green-900/50 dark:text-green-300",
|
||
|
||
// Run statuses
|
||
failed: "bg-red-100 text-red-700 dark:bg-red-900/50 dark:text-red-300",
|
||
timed_out: "bg-orange-100 text-orange-700 dark:bg-orange-900/50 dark:text-orange-300",
|
||
succeeded: "bg-green-100 text-green-700 dark:bg-green-900/50 dark:text-green-300",
|
||
ok: "bg-green-100 text-green-700 dark:bg-green-900/50 dark:text-green-300",
|
||
warning: "bg-amber-100 text-amber-700 dark:bg-amber-900/50 dark:text-amber-300",
|
||
error: "bg-red-100 text-red-700 dark:bg-red-900/50 dark:text-red-300",
|
||
info: "bg-sky-100 text-sky-700 dark:bg-sky-900/50 dark:text-sky-300",
|
||
terminated: "bg-red-100 text-red-700 dark:bg-red-900/50 dark:text-red-300",
|
||
pending: "bg-yellow-100 text-yellow-700 dark:bg-yellow-900/50 dark:text-yellow-300",
|
||
// Skill Studio test-run "queued" aligns with pending (yellow). PAP-12962 D6.
|
||
queued: "bg-yellow-100 text-yellow-700 dark:bg-yellow-900/50 dark:text-yellow-300",
|
||
|
||
// Approval statuses
|
||
pending_approval: "bg-amber-100 text-amber-700 dark:bg-amber-900/50 dark:text-amber-300",
|
||
revision_requested: "bg-amber-100 text-amber-700 dark:bg-amber-900/50 dark:text-amber-300",
|
||
approved: "bg-green-100 text-green-700 dark:bg-green-900/50 dark:text-green-300",
|
||
rejected: "bg-red-100 text-red-700 dark:bg-red-900/50 dark:text-red-300",
|
||
|
||
// Case statuses (PAP-12968 E3) — `draft` is the neutral pre-work state,
|
||
// rendered as a muted gray alias of `backlog`/`planned`. The other case
|
||
// statuses (in_progress/in_review/approved/done/cancelled) already map above.
|
||
draft: "bg-muted text-muted-foreground",
|
||
|
||
// Issue statuses — consistent hues with issueStatusIcon above (PAP-75 brand
|
||
// mapping: todo → amber, in_progress → blue "liveness").
|
||
backlog: "bg-muted text-muted-foreground",
|
||
todo: "bg-amber-100 text-amber-700 dark:bg-amber-900/50 dark:text-amber-300",
|
||
in_progress: "bg-blue-100 text-blue-700 dark:bg-blue-900/50 dark:text-blue-300",
|
||
in_review: "bg-violet-100 text-violet-700 dark:bg-violet-900/50 dark:text-violet-300",
|
||
blocked: "bg-red-100 text-red-700 dark:bg-red-900/50 dark:text-red-300",
|
||
done: "bg-green-100 text-green-700 dark:bg-green-900/50 dark:text-green-300",
|
||
cancelled: "bg-muted text-muted-foreground",
|
||
|
||
// Tool access — policy decisions, catalog, and runtime health (Tools & Access, PAP-10389)
|
||
allowed: "bg-green-100 text-green-700 dark:bg-green-900/50 dark:text-green-300",
|
||
denied: "bg-red-100 text-red-700 dark:bg-red-900/50 dark:text-red-300",
|
||
block: "bg-red-100 text-red-700 dark:bg-red-900/50 dark:text-red-300",
|
||
"require-approval": "bg-amber-100 text-amber-700 dark:bg-amber-900/50 dark:text-amber-300",
|
||
redacted: "bg-violet-100 text-violet-700 dark:bg-violet-900/50 dark:text-violet-300",
|
||
"rate-limit": "bg-orange-100 text-orange-700 dark:bg-orange-900/50 dark:text-orange-300",
|
||
deferred: "bg-sky-100 text-sky-700 dark:bg-sky-900/50 dark:text-sky-300",
|
||
hidden: "bg-muted text-muted-foreground",
|
||
quarantined: "bg-red-100 text-red-700 dark:bg-red-900/50 dark:text-red-300",
|
||
"runtime-error": "bg-red-100 text-red-700 dark:bg-red-900/50 dark:text-red-300",
|
||
healthy: "bg-green-100 text-green-700 dark:bg-green-900/50 dark:text-green-300",
|
||
degraded: "bg-amber-100 text-amber-700 dark:bg-amber-900/50 dark:text-amber-300",
|
||
unchecked: "bg-muted text-muted-foreground",
|
||
};
|
||
|
||
export const statusBadgeDefault = "bg-muted text-muted-foreground";
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Agent status — brand state system (PAP-75)
|
||
// ---------------------------------------------------------------------------
|
||
|
||
export type AgentBadgeColor = "gray" | "blue" | "amber" | "red";
|
||
|
||
/** Agent status → brand colour name. `active` aliases idle (never assigned). */
|
||
export const agentStatusColor: Record<string, AgentBadgeColor> = {
|
||
idle: "gray",
|
||
active: "gray",
|
||
running: "blue",
|
||
paused: "amber",
|
||
error: "red",
|
||
};
|
||
|
||
export const agentStatusColorDefault: AgentBadgeColor = "gray";
|
||
|
||
// Brand `.task-chip` styles per colour name live in `brandChipBadge` below —
|
||
// `AgentBadgeColor` is a subset of `BrandChipColor`, so agent badges index
|
||
// straight into it. (A byte-identical `agentStatusBadge` duplicate map was
|
||
// collapsed into `brandChipBadge` in the Run 2 review; DECISION-SHEET.md A1.)
|
||
|
||
/** Heartbeat-capsule fill (solid) per colour name. gray darkens in dark mode. */
|
||
export const agentStatusCapsule: Record<AgentBadgeColor, string> = {
|
||
gray: "bg-[#A8AEB2] dark:bg-[#6E6960]",
|
||
blue: "bg-[#2563EB]",
|
||
amber: "bg-[#F59E0B]",
|
||
red: "bg-[#DC2626]",
|
||
};
|
||
|
||
/** Per-status capsule motion (running pulses, error blinks). Honors reduced-motion. */
|
||
export const agentStatusMotion: Record<string, string> = {
|
||
running: "hb-pulse",
|
||
error: "hb-blink",
|
||
};
|
||
|
||
|
||
/**
|
||
* 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]";
|
||
|
||
/**
|
||
* Liveness-blue badge recipe — the shared "Live" / "Running" pill treatment
|
||
* (translucent blue fill + border + blue text). One source of truth so every
|
||
* live/running indicator reads as the same blue.
|
||
*/
|
||
export const liveBlueBadge = "bg-blue-500/10 border-blue-500/30 text-blue-600 dark:text-blue-400";
|
||
|
||
/**
|
||
* Issue/task status → brand colour name (PAP-75). `in_progress` is blue
|
||
* (liveness), `todo` amber (queued), `in_review` violet (awaiting review),
|
||
* `done` green, `blocked` red, `backlog`/`cancelled` gray (inert).
|
||
*/
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Inline banner tones (built-in agents provenance / paused notices)
|
||
//
|
||
// Softer, full-width banner surface derived from the same brand hue anchors as
|
||
// `brandChipBadge`. `info` carries provenance/informational context, `warning`
|
||
// carries paused/attention context, and `danger` carries failed actions. Consumed by
|
||
// `<InlineBanner>` so feature banners stay token-backed instead of hand-rolling
|
||
// per-instance `bg-yellow-*`/`bg-blue-*` recipes.
|
||
// ---------------------------------------------------------------------------
|
||
|
||
export type BannerTone = "info" | "warning" | "danger";
|
||
|
||
export const brandBanner: Record<BannerTone, string> = {
|
||
info: "border-[#2563EB]/40 bg-[#DBEAFE]/50 text-[#1D4ED8] dark:border-[#2563eb59] dark:bg-[#2563eb14] dark:text-[#93C5FD]",
|
||
warning: "border-[#F59E0B]/50 bg-[#FEF3C7]/60 text-[#B45309] dark:border-[#f59e0b59] dark:bg-[#f59e0b12] dark:text-[#F59E0B]",
|
||
// PAP-14031: aligned to the proven `failed`/`error` chip recipe (bg-red-100 /
|
||
// text-red-700 pair) so title + body both clear WCAG AA 4.5:1 in light and
|
||
// dark on either `--background` or `--card`. The prior `text-destructive` on
|
||
// `bg-destructive/10` measured ~3.7–4.3:1 — under AA for normal text. Border
|
||
// keeps the destructive hue for continuity with other danger surfaces.
|
||
danger: "border-destructive/40 bg-red-100 text-red-700 dark:bg-red-900/50 dark:text-red-300",
|
||
};
|
||
|
||
export const issueStatusColor: Record<string, BrandChipColor> = {
|
||
backlog: "gray",
|
||
todo: "amber",
|
||
in_progress: "blue",
|
||
in_review: "violet",
|
||
done: "green",
|
||
blocked: "red",
|
||
cancelled: "gray",
|
||
};
|
||
|
||
export const issueStatusColorDefault: BrandChipColor = "gray";
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Status → base-hue CSS variable
|
||
//
|
||
// Each status chip / icon sets a local `--sc` to the matching var below, and
|
||
// the `.status-chip` / `.status-fill` helpers (index.css) derive the rendered
|
||
// fill/text/border from it for both light and dark. Agent and task keep
|
||
// independent vars so each can be tuned without touching the other, even where
|
||
// their defaults coincide.
|
||
// ---------------------------------------------------------------------------
|
||
|
||
/** Agent status → base-hue CSS var. `active` aliases idle (never assigned). */
|
||
export const agentStatusVar: Record<string, string> = {
|
||
idle: "--status-agent-idle",
|
||
active: "--status-agent-idle",
|
||
running: "--status-agent-running",
|
||
paused: "--status-agent-paused",
|
||
error: "--status-agent-error",
|
||
};
|
||
export const agentStatusVarDefault = "--status-agent-idle";
|
||
|
||
/** Task/issue status → base-hue CSS var (drives both the chip and the icon). */
|
||
export const taskStatusVar: Record<string, string> = {
|
||
idle: "--status-task-backlog",
|
||
backlog: "--status-task-backlog",
|
||
todo: "--status-task-todo",
|
||
in_progress: "--status-task-in_progress",
|
||
in_review: "--status-task-in_review",
|
||
done: "--status-task-done",
|
||
blocked: "--status-task-blocked",
|
||
cancelled: "--status-task-cancelled",
|
||
};
|
||
export const taskStatusVarDefault = "--status-task-backlog";
|
||
|
||
/**
|
||
* Task/issue status → AA-tuned ICON-hue CSS var (PAP-238). Drives the standalone
|
||
* {@link StatusGlyph} colour. Separate from {@link taskStatusVar} (the chip base
|
||
* hue) because a bare glyph next to text needs a stronger hue to clear WCAG 3:1;
|
||
* see the `--status-task-icon-*` block in `index.css`. `in_queue` is the blocked
|
||
* shape recoloured blue, so it maps to its own var.
|
||
*/
|
||
export const taskStatusIconVar: Record<string, string> = {
|
||
idle: "--status-task-icon-backlog",
|
||
backlog: "--status-task-icon-backlog",
|
||
todo: "--status-task-icon-todo",
|
||
in_progress: "--status-task-icon-in_progress",
|
||
in_review: "--status-task-icon-in_review",
|
||
done: "--status-task-icon-done",
|
||
blocked: "--status-task-icon-blocked",
|
||
cancelled: "--status-task-icon-cancelled",
|
||
in_queue: "--status-task-icon-in_queue",
|
||
};
|
||
export const taskStatusIconVarDefault = "--status-task-icon-backlog";
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Agent status dot — solid background for small indicator dots
|
||
// ---------------------------------------------------------------------------
|
||
|
||
export const agentStatusDot: Record<string, string> = {
|
||
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",
|
||
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-600 dark:text-red-400",
|
||
high: "text-orange-600 dark:text-orange-400",
|
||
medium: "text-yellow-600 dark:text-yellow-400",
|
||
low: "text-blue-600 dark:text-blue-400",
|
||
};
|
||
|
||
export const priorityColorDefault = "text-yellow-600 dark:text-yellow-400";
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// External object status — colors & severity ranking
|
||
// ---------------------------------------------------------------------------
|
||
//
|
||
// Categories come from `EXTERNAL_OBJECT_STATUS_CATEGORIES` in @paperclipai/shared.
|
||
// The map keys here intentionally mirror the union — keep them in sync.
|
||
//
|
||
// Tone reuse rationale (see UX spec §1):
|
||
// unknown → backlog hue (muted, dashed circle)
|
||
// open → todo / blue
|
||
// waiting → amber (distinct from internal in_progress yellow)
|
||
// running → status blue (gallery r2; was cyan), animated when motion is allowed
|
||
// succeeded → done / green
|
||
// failed → red
|
||
// blocked → red
|
||
// closed → muted neutral
|
||
// archived → muted neutral
|
||
// auth_required → amber + dashed
|
||
// unreachable → red + dashed
|
||
|
||
export const externalObjectStatusIcon: Record<string, string> = {
|
||
unknown: "text-muted-foreground border-muted-foreground",
|
||
open: "text-blue-600 border-blue-600 dark:text-blue-400 dark:border-blue-400",
|
||
waiting: "text-amber-600 border-amber-600 dark:text-amber-400 dark:border-amber-400",
|
||
running: "text-blue-600 border-blue-600 dark:text-blue-400 dark:border-blue-400", // Gallery feedback r2: running = status blue (pulse animation still distinguishes it from static blue `open`).
|
||
succeeded: "text-green-600 border-green-600 dark:text-green-400 dark:border-green-400",
|
||
merged: "text-violet-600 border-violet-600 dark:text-violet-400 dark:border-violet-400",
|
||
failed: "text-red-600 border-red-600 dark:text-red-400 dark:border-red-400",
|
||
blocked: "text-red-600 border-red-600 dark:text-red-400 dark:border-red-400",
|
||
closed: "text-neutral-500 border-neutral-500",
|
||
archived: "text-neutral-500 border-neutral-500",
|
||
auth_required: "text-amber-600 border-amber-600 dark:text-amber-400 dark:border-amber-400",
|
||
unreachable: "text-red-600 border-red-600 dark:text-red-400 dark:border-red-400",
|
||
};
|
||
|
||
export const externalObjectStatusIconDefault = "text-muted-foreground border-muted-foreground";
|
||
|
||
export const externalObjectStatusBadge: Record<string, string> = {
|
||
unknown: "bg-muted text-muted-foreground",
|
||
open: "bg-blue-100 text-blue-700 dark:bg-blue-900/50 dark:text-blue-300",
|
||
waiting: "bg-amber-100 text-amber-700 dark:bg-amber-900/50 dark:text-amber-300",
|
||
running: "bg-blue-100 text-blue-700 dark:bg-blue-900/50 dark:text-blue-300", // Gallery feedback r2: running = status blue (now shares tint with `open`; liveness animation differentiates).
|
||
succeeded: "bg-green-100 text-green-700 dark:bg-green-900/50 dark:text-green-300",
|
||
failed: "bg-red-100 text-red-700 dark:bg-red-900/50 dark:text-red-300",
|
||
blocked: "bg-red-100 text-red-700 dark:bg-red-900/50 dark:text-red-300",
|
||
closed: "bg-muted text-muted-foreground",
|
||
archived: "bg-muted text-muted-foreground",
|
||
auth_required: "bg-amber-100 text-amber-700 dark:bg-amber-900/50 dark:text-amber-300",
|
||
unreachable: "bg-red-100 text-red-700 dark:bg-red-900/50 dark:text-red-300",
|
||
};
|
||
|
||
export const externalObjectStatusBadgeDefault = "bg-muted text-muted-foreground";
|
||
|
||
/**
|
||
* Liveness overlay applied on top of the base status tone. We deliberately
|
||
* encode it as utility classes (not a tone change) so callers can append the
|
||
* overlay to any pill, icon, or marker without redefining colors.
|
||
*
|
||
* The dashed border + reduced opacity guarantees a non-color differentiator
|
||
* for stale / auth_required / unreachable per WCAG 1.4.1.
|
||
*/
|
||
export const externalObjectLivenessOverlay: Record<string, string> = {
|
||
unknown: "",
|
||
fresh: "",
|
||
stale: "opacity-70 [border-style:dashed]",
|
||
auth_required: "[border-style:dashed]",
|
||
unreachable: "[border-style:dashed]",
|
||
};
|
||
|
||
/**
|
||
* Severity ranking used by sidebar/list rollups. Higher number = more
|
||
* attention-worthy. Anything ≤ `muted` should be hidden when summarising.
|
||
*/
|
||
export const externalObjectStatusToneSeverity: Record<string, number> = {
|
||
muted: 0,
|
||
neutral: 1,
|
||
success: 2,
|
||
info: 3,
|
||
warning: 4,
|
||
danger: 5,
|
||
};
|
||
|
||
export const externalObjectStatusToneSeverityDefault = 0;
|