feat(ui): terminal, charts and low-contrast text follow the selected theme
- The lane terminal (xterm) takes its palette from the selected DARK theme — it stays dark in light mode, as a terminal should — and repaints live when the theme changes (`client/src/lib/terminalTheme.ts`). - Charts on the Dashboard and Workflows pages draw their grid, axes, labels and tooltips from theme tokens instead of hardcoded near-black/white, and the activity heatmap ramps from the page surface to the accent, so it reads on every theme instead of going near-black on light ones. - Status/brand text that only worked on dark backgrounds (`text-*-300/400`) gets a darker light-mode shade (`text-*-700 dark:text-*-300`) across sessions, conversations, workflows, config and settings. Categorical chart colours are kept.
This commit is contained in:
@@ -341,7 +341,9 @@ export function ActivityFeed() {
|
||||
<p className="text-xs text-fg-muted">
|
||||
{t("subtitle")}
|
||||
{paused && (
|
||||
<span className="ml-2 text-yellow-400">{t("paused", { count: bufferCount })}</span>
|
||||
<span className="ml-2 text-yellow-700 dark:text-yellow-400">
|
||||
{t("paused", { count: bufferCount })}
|
||||
</span>
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -970,7 +970,7 @@ type Tone =
|
||||
const TONES: Record<Tone, { iconBg: string; iconText: string; bar: string; ring: string }> = {
|
||||
sky: {
|
||||
iconBg: "bg-sky-500/10",
|
||||
iconText: "text-sky-300",
|
||||
iconText: "text-sky-700 dark:text-sky-300",
|
||||
bar: "bg-sky-500/40",
|
||||
ring: "ring-sky-500/20",
|
||||
},
|
||||
@@ -982,7 +982,7 @@ const TONES: Record<Tone, { iconBg: string; iconText: string; bar: string; ring:
|
||||
},
|
||||
violet: {
|
||||
iconBg: "bg-violet-500/10",
|
||||
iconText: "text-violet-300",
|
||||
iconText: "text-violet-700 dark:text-violet-300",
|
||||
bar: "bg-violet-500/40",
|
||||
ring: "ring-violet-500/20",
|
||||
},
|
||||
@@ -994,37 +994,37 @@ const TONES: Record<Tone, { iconBg: string; iconText: string; bar: string; ring:
|
||||
},
|
||||
fuchsia: {
|
||||
iconBg: "bg-fuchsia-500/10",
|
||||
iconText: "text-fuchsia-300",
|
||||
iconText: "text-fuchsia-700 dark:text-fuchsia-300",
|
||||
bar: "bg-fuchsia-500/40",
|
||||
ring: "ring-fuchsia-500/20",
|
||||
},
|
||||
cyan: {
|
||||
iconBg: "bg-cyan-500/10",
|
||||
iconText: "text-cyan-300",
|
||||
iconText: "text-cyan-700 dark:text-cyan-300",
|
||||
bar: "bg-cyan-500/40",
|
||||
ring: "ring-cyan-500/20",
|
||||
},
|
||||
pink: {
|
||||
iconBg: "bg-pink-500/10",
|
||||
iconText: "text-pink-300",
|
||||
iconText: "text-pink-700 dark:text-pink-300",
|
||||
bar: "bg-pink-500/40",
|
||||
ring: "ring-pink-500/20",
|
||||
},
|
||||
indigo: {
|
||||
iconBg: "bg-indigo-500/10",
|
||||
iconText: "text-indigo-300",
|
||||
iconText: "text-indigo-700 dark:text-indigo-300",
|
||||
bar: "bg-indigo-500/40",
|
||||
ring: "ring-indigo-500/20",
|
||||
},
|
||||
orange: {
|
||||
iconBg: "bg-orange-500/10",
|
||||
iconText: "text-orange-300",
|
||||
iconText: "text-orange-700 dark:text-orange-300",
|
||||
bar: "bg-orange-500/40",
|
||||
ring: "ring-orange-500/20",
|
||||
},
|
||||
teal: {
|
||||
iconBg: "bg-teal-500/10",
|
||||
iconText: "text-teal-300",
|
||||
iconText: "text-teal-700 dark:text-teal-300",
|
||||
bar: "bg-teal-500/40",
|
||||
ring: "ring-teal-500/20",
|
||||
},
|
||||
@@ -1036,7 +1036,7 @@ const TONES: Record<Tone, { iconBg: string; iconText: string; bar: string; ring:
|
||||
},
|
||||
rose: {
|
||||
iconBg: "bg-rose-500/10",
|
||||
iconText: "text-rose-300",
|
||||
iconText: "text-rose-700 dark:text-rose-300",
|
||||
bar: "bg-rose-500/40",
|
||||
ring: "ring-rose-500/20",
|
||||
},
|
||||
@@ -2721,7 +2721,7 @@ function CurrentConfigPanel({ sources }: { sources: CcSettingsSource[] }) {
|
||||
return (
|
||||
<div className="rounded-lg border border-border bg-surface-2">
|
||||
<div className="border-b border-border px-4 py-2.5 flex items-center gap-2">
|
||||
<SettingsIcon className="w-3.5 h-3.5 text-violet-300/80" />
|
||||
<SettingsIcon className="w-3.5 h-3.5 text-violet-700/80 dark:text-violet-300/80" />
|
||||
<span className="text-sm font-medium text-fg-primary">Current configuration</span>
|
||||
<span className="text-[11px] text-fg-muted ml-auto">
|
||||
{setCount} option{setCount !== 1 ? "s" : ""} set · the rest use defaults
|
||||
@@ -3233,7 +3233,7 @@ function renderMemoryIndex(
|
||||
type="button"
|
||||
onClick={() => onJump(capturedTarget)}
|
||||
title={`${jumpTitle}: ${normalizeMemoryTarget(capturedTarget)}`}
|
||||
className="text-teal-300 hover:text-teal-200 underline decoration-dotted underline-offset-2 hover:decoration-solid focus:outline-none focus:ring-1 focus:ring-teal-400/60 rounded-sm"
|
||||
className="text-teal-700 dark:text-teal-300 hover:text-teal-800 dark:hover:text-teal-200 underline decoration-dotted underline-offset-2 hover:decoration-solid focus:outline-none focus:ring-1 focus:ring-teal-400/60 rounded-sm"
|
||||
>
|
||||
{label}
|
||||
</button>
|
||||
@@ -3386,7 +3386,7 @@ function MemoryPanel({
|
||||
{totalAuto > 0 && (
|
||||
<section className="pt-1">
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<BookOpen className="w-3.5 h-3.5 text-teal-300" />
|
||||
<BookOpen className="w-3.5 h-3.5 text-teal-700 dark:text-teal-300" />
|
||||
<h3 className="text-[11px] font-semibold uppercase tracking-wider text-fg-secondary">
|
||||
{t("memory.autoTitle")}
|
||||
</h3>
|
||||
@@ -3499,7 +3499,7 @@ function MemoryProjectGroup({
|
||||
open ? "" : "-rotate-90"
|
||||
}`}
|
||||
/>
|
||||
<FolderTree className="w-3.5 h-3.5 text-teal-300 flex-shrink-0" />
|
||||
<FolderTree className="w-3.5 h-3.5 text-teal-700 dark:text-teal-300 flex-shrink-0" />
|
||||
<span className="font-mono text-xs text-fg-secondary truncate flex-1 min-w-0">
|
||||
{project}
|
||||
</span>
|
||||
@@ -3618,7 +3618,7 @@ function MemoryIndexCard({
|
||||
return (
|
||||
<div className="rounded-md border border-teal-500/20 bg-teal-500/5">
|
||||
<div className="flex items-center gap-2 px-3 py-1.5 border-b border-teal-500/15">
|
||||
<BookOpen className="w-3 h-3 text-teal-300 flex-shrink-0" />
|
||||
<BookOpen className="w-3 h-3 text-teal-700 dark:text-teal-300 flex-shrink-0" />
|
||||
<span className="font-mono text-[11px] text-fg-secondary truncate flex-1 min-w-0">
|
||||
{item.name}
|
||||
</span>
|
||||
|
||||
@@ -212,27 +212,16 @@ function useTooltip() {
|
||||
return { show, move, hide, node };
|
||||
}
|
||||
|
||||
function cellColor(count: number, max: number) {
|
||||
if (count === 0) return "#161625";
|
||||
// Log scale + RGB interpolation across a wide color ramp for maximum perceptual range
|
||||
/** Heatmap cell fill: the page surface mixed toward the accent on a log
|
||||
* scale. Built from theme tokens so the ramp reads surface → accent in every
|
||||
* theme; the old fixed indigo ramp went near-black on light themes. Exported
|
||||
* for its test. */
|
||||
export function cellColor(count: number, max: number) {
|
||||
if (count === 0) return "rgb(var(--surface-0))";
|
||||
const t = Math.log(count + 1) / Math.log(Math.max(max, 1) + 1);
|
||||
// Ramp: near-black indigo → deep indigo → bright indigo → lavender
|
||||
type RGB = [number, number, number];
|
||||
const stops: RGB[] = [
|
||||
[22, 20, 60], // near-black indigo
|
||||
[55, 48, 163], // deep indigo
|
||||
[99, 102, 241], // bright indigo
|
||||
[199, 210, 254], // lavender
|
||||
];
|
||||
const scaled = t * (stops.length - 1);
|
||||
const lo = Math.min(Math.floor(scaled), stops.length - 2);
|
||||
const frac = scaled - lo;
|
||||
const [r1, g1, b1]: RGB = stops[lo] as RGB;
|
||||
const [r2, g2, b2]: RGB = stops[lo + 1] as RGB;
|
||||
const r = Math.round(r1 + (r2 - r1) * frac);
|
||||
const g = Math.round(g1 + (g2 - g1) * frac);
|
||||
const b = Math.round(b1 + (b2 - b1) * frac);
|
||||
return `rgb(${r},${g},${b})`;
|
||||
// Floor at 20% so a single event still reads as distinct from an empty day.
|
||||
const pct = Math.round(20 + 80 * t);
|
||||
return `color-mix(in srgb, rgb(var(--accent)) ${pct}%, rgb(var(--surface-0)))`;
|
||||
}
|
||||
|
||||
function Heatmap({ weeks }: { weeks: Array<Array<{ date: string; count: number }>> }) {
|
||||
@@ -352,7 +341,7 @@ function Heatmap({ weeks }: { weeks: Array<Array<{ date: string; count: number }
|
||||
height: 13,
|
||||
borderRadius: 2,
|
||||
backgroundColor: cellColor(cell.count, maxCount),
|
||||
border: "1px solid rgba(255,255,255,0.04)",
|
||||
border: "1px solid rgb(var(--border) / 0.4)",
|
||||
flexShrink: 0,
|
||||
cursor: "default",
|
||||
}}
|
||||
@@ -374,7 +363,7 @@ function Heatmap({ weeks }: { weeks: Array<Array<{ date: string; count: number }
|
||||
height: 13,
|
||||
borderRadius: 2,
|
||||
backgroundColor: cellColor(v, maxCount),
|
||||
border: "1px solid rgba(255,255,255,0.06)",
|
||||
border: "1px solid rgb(var(--border) / 0.5)",
|
||||
}}
|
||||
/>
|
||||
);
|
||||
@@ -593,7 +582,14 @@ function DonutChart({
|
||||
<div className="flex items-center justify-center gap-6 w-full">
|
||||
{node}
|
||||
<svg width={128} height={128} viewBox="0 0 128 128" className="flex-shrink-0">
|
||||
<circle cx={cx} cy={cy} r={r} fill="none" stroke="#1e1e2e" strokeWidth={stroke} />
|
||||
<circle
|
||||
cx={cx}
|
||||
cy={cy}
|
||||
r={r}
|
||||
fill="none"
|
||||
stroke="rgb(var(--border))"
|
||||
strokeWidth={stroke}
|
||||
/>
|
||||
{segments.map(({ label, value, color }, i) => {
|
||||
const dash = (value / total) * circumference;
|
||||
const gap = circumference - dash;
|
||||
@@ -1010,7 +1006,7 @@ function AnalyticsTab() {
|
||||
raw={data ? totalTokens.toLocaleString() : undefined}
|
||||
sub={data ? `${cacheHitPct}${t("cacheHitRate")}` : undefined}
|
||||
icon={Cpu}
|
||||
color="text-violet-400"
|
||||
color="text-violet-700 dark:text-violet-400"
|
||||
loading={!data}
|
||||
/>
|
||||
<StatPill
|
||||
@@ -1032,7 +1028,7 @@ function AnalyticsTab() {
|
||||
raw={data ? data.overview.total_events.toLocaleString() : undefined}
|
||||
sub={data ? `~${data.avg_events_per_session}${t("perSession")}` : undefined}
|
||||
icon={Zap}
|
||||
color="text-yellow-400"
|
||||
color="text-yellow-700 dark:text-yellow-400"
|
||||
loading={!data}
|
||||
/>
|
||||
</div>
|
||||
@@ -1155,7 +1151,9 @@ function AnalyticsTab() {
|
||||
</div>
|
||||
<div className="flex justify-between text-xs text-fg-muted">
|
||||
<span>{t("cacheEfficiency")}</span>
|
||||
<span className="text-violet-400 font-mono">{cacheHitPct}%</span>
|
||||
<span className="text-violet-700 dark:text-violet-400 font-mono">
|
||||
{cacheHitPct}%
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1180,12 +1178,12 @@ function AnalyticsTab() {
|
||||
{
|
||||
label: t("common:token.cacheRead"),
|
||||
value: data?.tokens.total_cache_read ?? 0,
|
||||
color: "text-violet-400",
|
||||
color: "text-violet-700 dark:text-violet-400",
|
||||
},
|
||||
{
|
||||
label: t("common:token.cacheWrite"),
|
||||
value: data?.tokens.total_cache_write ?? 0,
|
||||
color: "text-yellow-400",
|
||||
color: "text-yellow-700 dark:text-yellow-400",
|
||||
},
|
||||
{ label: t("common:total"), value: totalTokens, color: "text-fg-primary" },
|
||||
].map(({ label, value, color }) => (
|
||||
@@ -1331,7 +1329,7 @@ function AnalyticsTab() {
|
||||
</div>
|
||||
<div className="mt-4 pt-4 border-t border-border text-xs flex justify-between">
|
||||
<span className="text-fg-muted">{t("common:total")}</span>
|
||||
<span className="text-cyan-400 font-mono">
|
||||
<span className="text-cyan-700 dark:text-cyan-400 font-mono">
|
||||
<Tip raw={fmtCostFull(totalCost30d)}>{fmtCost(totalCost30d)}</Tip>
|
||||
</span>
|
||||
</div>
|
||||
@@ -1761,7 +1759,14 @@ function SystemHealthTab() {
|
||||
viewBox="0 0 96 96"
|
||||
className="flex-shrink-0 cursor-default"
|
||||
>
|
||||
<circle cx="48" cy="48" r="38" fill="none" stroke="#1e1e2e" strokeWidth="14" />
|
||||
<circle
|
||||
cx="48"
|
||||
cy="48"
|
||||
r="38"
|
||||
fill="none"
|
||||
stroke="rgb(var(--border))"
|
||||
strokeWidth="14"
|
||||
/>
|
||||
{(() => {
|
||||
const r = 38,
|
||||
cx = 48,
|
||||
@@ -1882,7 +1887,14 @@ function SystemHealthTab() {
|
||||
raw={`Score: ${healthScore.toFixed(1)} / 100\n\n• Success Rate (40%): ${successRate.toFixed(1)}%\n• Cache Hit (25%): ${cacheHitRate.toFixed(1)}%\n• Error Avoidance (25%): ${(100 - errorRate).toFixed(1)}%\n• Memory Health (10%): ${(100 - heapUsedPct).toFixed(1)}%`}
|
||||
>
|
||||
<svg width="120" height="120" viewBox="0 0 120 120" className="cursor-default">
|
||||
<circle cx="60" cy="60" r="48" fill="none" stroke="#1e1e2e" strokeWidth="10" />
|
||||
<circle
|
||||
cx="60"
|
||||
cy="60"
|
||||
r="48"
|
||||
fill="none"
|
||||
stroke="rgb(var(--border))"
|
||||
strokeWidth="10"
|
||||
/>
|
||||
<circle
|
||||
cx="60"
|
||||
cy="60"
|
||||
@@ -1954,7 +1966,7 @@ function SystemHealthTab() {
|
||||
>
|
||||
<div className="text-center cursor-default">
|
||||
<p className="text-[9px] text-fg-muted uppercase">Compact</p>
|
||||
<p className="text-xs font-mono font-bold text-violet-400">
|
||||
<p className="text-xs font-mono font-bold text-violet-700 dark:text-violet-400">
|
||||
{workflow.compaction?.totalCompactions ?? 0}
|
||||
</p>
|
||||
</div>
|
||||
@@ -2036,7 +2048,7 @@ function SystemHealthTab() {
|
||||
<div className="card p-5 flex flex-col gap-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<BarChart3 className="w-4 h-4 text-violet-400" />
|
||||
<BarChart3 className="w-4 h-4 text-violet-700 dark:text-violet-400" />
|
||||
<span className="text-xs text-fg-muted uppercase tracking-wider">Concurrency</span>
|
||||
</div>
|
||||
<span className="text-[10px] font-mono text-fg-muted">{lanes.length} intervals</span>
|
||||
@@ -2058,7 +2070,7 @@ function SystemHealthTab() {
|
||||
? "#fbbf24"
|
||||
: lane.count > 0
|
||||
? "#34d399"
|
||||
: "#1e1e2e";
|
||||
: "rgb(var(--border))";
|
||||
return (
|
||||
<div
|
||||
key={i}
|
||||
@@ -2172,7 +2184,7 @@ function SystemHealthTab() {
|
||||
<div className="card p-5 flex flex-col gap-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<GitBranch className="w-4 h-4 text-violet-400" />
|
||||
<GitBranch className="w-4 h-4 text-violet-700 dark:text-violet-400" />
|
||||
<span className="text-xs text-fg-muted uppercase tracking-wider">
|
||||
Subagent Effectiveness
|
||||
</span>
|
||||
@@ -2282,7 +2294,7 @@ function SystemHealthTab() {
|
||||
<div className="card p-5 flex flex-col gap-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<Cpu className="w-4 h-4 text-cyan-400" />
|
||||
<Cpu className="w-4 h-4 text-cyan-700 dark:text-cyan-400" />
|
||||
<span className="text-xs text-fg-muted uppercase tracking-wider">Platform</span>
|
||||
</div>
|
||||
<span className="text-[10px] font-mono text-fg-muted">{info.server.node_version}</span>
|
||||
@@ -2620,7 +2632,7 @@ export function Dashboard() {
|
||||
label={t("activeSubagents")}
|
||||
value={stats ? allSubagents.filter((a) => a.status === "working").length : ""}
|
||||
icon={GitBranch}
|
||||
accentColor="text-violet-400"
|
||||
accentColor="text-violet-700 dark:text-violet-400"
|
||||
trend={stats ? `${allSubagents.length}${t("totalTrend")}` : undefined}
|
||||
loading={!stats}
|
||||
/>
|
||||
@@ -2629,7 +2641,7 @@ export function Dashboard() {
|
||||
value={stats ? fmt(stats.events_today) : ""}
|
||||
raw={stats ? stats.events_today.toLocaleString() : undefined}
|
||||
icon={Zap}
|
||||
accentColor="text-yellow-400"
|
||||
accentColor="text-yellow-700 dark:text-yellow-400"
|
||||
loading={!stats}
|
||||
/>
|
||||
<StatCard
|
||||
@@ -2637,7 +2649,7 @@ export function Dashboard() {
|
||||
value={stats ? fmt(stats.total_events) : ""}
|
||||
raw={stats ? stats.total_events.toLocaleString() : undefined}
|
||||
icon={Activity}
|
||||
accentColor="text-violet-400"
|
||||
accentColor="text-violet-700 dark:text-violet-400"
|
||||
loading={!stats}
|
||||
/>
|
||||
<StatCard
|
||||
@@ -2721,7 +2733,7 @@ export function Dashboard() {
|
||||
empty rather than as a misalignment. */}
|
||||
{!hasChildren && !isSubagent && (
|
||||
<span
|
||||
className="w-6 h-6 flex-shrink-0 flex items-center justify-center text-violet-400/70"
|
||||
className="w-6 h-6 flex-shrink-0 flex items-center justify-center text-violet-700/70 dark:text-violet-400/70"
|
||||
aria-hidden="true"
|
||||
title={t("common:noSubagents", "No subagents")}
|
||||
>
|
||||
@@ -2729,7 +2741,7 @@ export function Dashboard() {
|
||||
</span>
|
||||
)}
|
||||
{isSubagent && (
|
||||
<GitBranch className="w-3 h-3 text-violet-400 flex-shrink-0" />
|
||||
<GitBranch className="w-3 h-3 text-violet-700 dark:text-violet-400 flex-shrink-0" />
|
||||
)}
|
||||
<div className="flex-1 min-w-0">
|
||||
<AgentCard
|
||||
@@ -2758,7 +2770,7 @@ export function Dashboard() {
|
||||
onClick={() =>
|
||||
setExpandedAgents((prev) => new Set([...prev, agent.id]))
|
||||
}
|
||||
className="ml-7 mt-1 text-[11px] text-violet-400 hover:text-violet-300 transition-colors"
|
||||
className="ml-7 mt-1 text-[11px] text-violet-700 dark:text-violet-400 hover:text-violet-700 dark:hover:text-violet-300 transition-colors"
|
||||
>
|
||||
{t("common:subagent_label", { count: totalDesc })}
|
||||
{activeDesc > 0 && (
|
||||
|
||||
@@ -664,23 +664,29 @@ export function SessionDetail() {
|
||||
}`}
|
||||
>
|
||||
<ReasonIcon
|
||||
className={`w-3.5 h-3.5 ${urgent ? "text-status-warning" : "text-yellow-300"}`}
|
||||
className={`w-3.5 h-3.5 ${urgent ? "text-status-warning" : "text-yellow-700 dark:text-yellow-300"}`}
|
||||
/>
|
||||
</span>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div
|
||||
className={`text-sm font-medium ${urgent ? "text-status-warning" : "text-yellow-200"}`}
|
||||
className={`text-sm font-medium ${urgent ? "text-status-warning" : "text-yellow-800 dark:text-yellow-200"}`}
|
||||
>
|
||||
{t("detail.waitingBanner.title")}
|
||||
{cfg && (
|
||||
<span className={urgent ? "text-status-warning/90" : "text-yellow-300/80"}>
|
||||
<span
|
||||
className={
|
||||
urgent
|
||||
? "text-status-warning/90"
|
||||
: "text-yellow-700/80 dark:text-yellow-300/80"
|
||||
}
|
||||
>
|
||||
{" · "}
|
||||
{t(cfg.labelKey)}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
className={`text-[11px] ${urgent ? "text-status-warning/70" : "text-yellow-400/60"}`}
|
||||
className={`text-[11px] ${urgent ? "text-status-warning/70" : "text-yellow-700/60 dark:text-yellow-400/60"}`}
|
||||
>
|
||||
{cfg ? t(cfg.descKey) : t("detail.waitingBanner.generic")}
|
||||
</div>
|
||||
@@ -688,7 +694,7 @@ export function SessionDetail() {
|
||||
{session.awaiting_input_since && (
|
||||
<span
|
||||
className={`text-[11px] flex-shrink-0 flex items-center gap-1.5 ${
|
||||
urgent ? "text-status-warning/80" : "text-yellow-400/70"
|
||||
urgent ? "text-status-warning/80" : "text-yellow-700/70 dark:text-yellow-400/70"
|
||||
}`}
|
||||
>
|
||||
<span
|
||||
@@ -736,7 +742,7 @@ export function SessionDetail() {
|
||||
}}
|
||||
className={`flex items-center gap-2 px-4 py-2.5 text-sm font-medium border-b-2 transition-colors ${
|
||||
activeTab === "agents"
|
||||
? "border-violet-500 text-violet-400"
|
||||
? "border-violet-500 text-violet-700 dark:text-violet-400"
|
||||
: "border-transparent text-fg-muted hover:text-fg-secondary"
|
||||
}`}
|
||||
>
|
||||
@@ -750,7 +756,7 @@ export function SessionDetail() {
|
||||
}}
|
||||
className={`flex items-center gap-2 px-4 py-2.5 text-sm font-medium border-b-2 transition-colors ${
|
||||
activeTab === "conversation"
|
||||
? "border-violet-500 text-violet-400"
|
||||
? "border-violet-500 text-violet-700 dark:text-violet-400"
|
||||
: "border-transparent text-fg-muted hover:text-fg-secondary"
|
||||
}`}
|
||||
>
|
||||
@@ -764,7 +770,7 @@ export function SessionDetail() {
|
||||
}}
|
||||
className={`flex items-center gap-2 px-4 py-2.5 text-sm font-medium border-b-2 transition-colors ${
|
||||
activeTab === "timeline"
|
||||
? "border-violet-500 text-violet-400"
|
||||
? "border-violet-500 text-violet-700 dark:text-violet-400"
|
||||
: "border-transparent text-fg-muted hover:text-fg-secondary"
|
||||
}`}
|
||||
>
|
||||
@@ -794,7 +800,7 @@ export function SessionDetail() {
|
||||
{workflows.length > 0 && (
|
||||
<div className="mb-4">
|
||||
<h3 className="text-xs font-semibold text-fg-secondary uppercase tracking-wider mb-2 flex items-center gap-1.5">
|
||||
<Workflow className="w-3.5 h-3.5 text-violet-400" />
|
||||
<Workflow className="w-3.5 h-3.5 text-violet-700 dark:text-violet-400" />
|
||||
{wfT("runs.sessionTitle")}
|
||||
<span className="text-fg-muted font-mono">· {workflows.length}</span>
|
||||
</h3>
|
||||
@@ -807,7 +813,7 @@ export function SessionDetail() {
|
||||
) : (
|
||||
<>
|
||||
<h3 className="text-xs font-semibold text-fg-secondary uppercase tracking-wider mb-3 flex items-center gap-1.5">
|
||||
<Bot className="w-3.5 h-3.5 text-violet-400" />
|
||||
<Bot className="w-3.5 h-3.5 text-violet-700 dark:text-violet-400" />
|
||||
{t("detail.agents")}
|
||||
<span className="text-fg-muted font-mono">· {agents.length}</span>
|
||||
</h3>
|
||||
@@ -885,7 +891,7 @@ export function SessionDetail() {
|
||||
)}
|
||||
{isSubagent && !hasChildren && <span className="w-6 flex-shrink-0" />}
|
||||
{isSubagent && (
|
||||
<GitBranch className="w-3 h-3 text-violet-400 flex-shrink-0" />
|
||||
<GitBranch className="w-3 h-3 text-violet-700 dark:text-violet-400 flex-shrink-0" />
|
||||
)}
|
||||
<div className="flex-1 min-w-0">
|
||||
<AgentCard
|
||||
@@ -928,7 +934,7 @@ export function SessionDetail() {
|
||||
onClick={() =>
|
||||
setExpandedAgents((prev) => new Set([...prev, agent.id]))
|
||||
}
|
||||
className="ml-7 mt-1 text-[11px] text-violet-400 hover:text-violet-300 transition-colors"
|
||||
className="ml-7 mt-1 text-[11px] text-violet-700 dark:text-violet-400 hover:text-violet-700 dark:hover:text-violet-300 transition-colors"
|
||||
>
|
||||
{t("common:subagent_label", { count: totalDesc })}
|
||||
</button>
|
||||
|
||||
@@ -436,7 +436,7 @@ export function Sessions() {
|
||||
</p>
|
||||
{session.source && session.source !== "local" && (
|
||||
<span
|
||||
className="inline-flex items-center gap-1 text-[10px] font-semibold text-sky-300 bg-sky-500/10 border border-sky-500/25 px-1.5 py-0.5 rounded-full"
|
||||
className="inline-flex items-center gap-1 text-[10px] font-semibold text-sky-700 dark:text-sky-300 bg-sky-500/10 border border-sky-500/25 px-1.5 py-0.5 rounded-full"
|
||||
title={t("remoteSourceBadgeTitle", "Collected from a remote machine")}
|
||||
>
|
||||
<Server className="w-2.5 h-2.5" />
|
||||
|
||||
@@ -880,7 +880,9 @@ export function Settings() {
|
||||
// just Sonnet 5), so a future model with a launch promo needs no code change.
|
||||
const introField = (key: keyof EditRow, labelKey: string, opts: { date?: boolean } = {}) => (
|
||||
<label className="flex flex-col gap-1">
|
||||
<span className="text-[10px] uppercase tracking-wider text-violet-300/70">{t(labelKey)}</span>
|
||||
<span className="text-[10px] uppercase tracking-wider text-violet-700/70 dark:text-violet-300/70">
|
||||
{t(labelKey)}
|
||||
</span>
|
||||
<input
|
||||
type={opts.date ? "text" : "number"}
|
||||
{...(opts.date ? { placeholder: "YYYY-MM-DD" } : { step: "0.01", min: "0" })}
|
||||
@@ -896,7 +898,7 @@ export function Settings() {
|
||||
<td colSpan={10} className="px-4 pb-3 pt-2">
|
||||
<div className="mt-2 rounded-md border border-violet-500/20 bg-violet-500/[0.04] px-3 py-2.5">
|
||||
<div className="flex items-center gap-1.5 mb-2">
|
||||
<span className="text-[11px] font-semibold text-violet-300 uppercase tracking-wider">
|
||||
<span className="text-[11px] font-semibold text-violet-700 dark:text-violet-300 uppercase tracking-wider">
|
||||
{t("pricing.introRatesTitle")}
|
||||
</span>
|
||||
<span className="text-[11px] text-fg-muted">{t("pricing.introRatesHint")}</span>
|
||||
@@ -1187,7 +1189,7 @@ export function Settings() {
|
||||
<td className="px-4 py-3 text-sm text-fg-secondary">
|
||||
{rule.display_name}
|
||||
{rule.intro_until && (
|
||||
<div className="text-[11px] font-normal text-violet-400/80 mt-0.5">
|
||||
<div className="text-[11px] font-normal text-violet-700/80 dark:text-violet-400/80 mt-0.5">
|
||||
{t("pricing.introNote", {
|
||||
input: rule.intro_input_per_mtok,
|
||||
output: rule.intro_output_per_mtok,
|
||||
@@ -1199,7 +1201,7 @@ export function Settings() {
|
||||
<td className="px-4 py-3 text-sm text-fg-secondary text-right font-mono">
|
||||
${rule.input_per_mtok}
|
||||
{rule.intro_until && (
|
||||
<span className="block text-[11px] text-violet-400/80">
|
||||
<span className="block text-[11px] text-violet-700/80 dark:text-violet-400/80">
|
||||
${rule.intro_input_per_mtok}
|
||||
</span>
|
||||
)}
|
||||
@@ -1207,7 +1209,7 @@ export function Settings() {
|
||||
<td className="px-4 py-3 text-sm text-fg-secondary text-right font-mono">
|
||||
${rule.output_per_mtok}
|
||||
{rule.intro_until && (
|
||||
<span className="block text-[11px] text-violet-400/80">
|
||||
<span className="block text-[11px] text-violet-700/80 dark:text-violet-400/80">
|
||||
${rule.intro_output_per_mtok}
|
||||
</span>
|
||||
)}
|
||||
@@ -1215,7 +1217,7 @@ export function Settings() {
|
||||
<td className="px-4 py-3 text-sm text-fg-secondary text-right font-mono">
|
||||
${rule.cache_read_per_mtok}
|
||||
{rule.intro_until && (
|
||||
<span className="block text-[11px] text-violet-400/80">
|
||||
<span className="block text-[11px] text-violet-700/80 dark:text-violet-400/80">
|
||||
${rule.intro_cache_read_per_mtok}
|
||||
</span>
|
||||
)}
|
||||
@@ -1223,7 +1225,7 @@ export function Settings() {
|
||||
<td className="px-4 py-3 text-sm text-fg-secondary text-right font-mono">
|
||||
${rule.cache_write_per_mtok}
|
||||
{rule.intro_until && (
|
||||
<span className="block text-[11px] text-violet-400/80">
|
||||
<span className="block text-[11px] text-violet-700/80 dark:text-violet-400/80">
|
||||
${rule.intro_cache_write_per_mtok}
|
||||
</span>
|
||||
)}
|
||||
@@ -1231,7 +1233,7 @@ export function Settings() {
|
||||
<td className="px-4 py-3 text-sm text-fg-secondary text-right font-mono">
|
||||
${rule.cache_write_1h_per_mtok}
|
||||
{rule.intro_until && (
|
||||
<span className="block text-[11px] text-violet-400/80">
|
||||
<span className="block text-[11px] text-violet-700/80 dark:text-violet-400/80">
|
||||
${rule.intro_cache_write_1h_per_mtok}
|
||||
</span>
|
||||
)}
|
||||
@@ -1580,7 +1582,7 @@ export function Settings() {
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center gap-3 bg-surface-2 rounded-lg px-3.5 py-3">
|
||||
<CheckCircle className="w-4 h-4 text-violet-400 flex-shrink-0" />
|
||||
<CheckCircle className="w-4 h-4 text-violet-700 dark:text-violet-400 flex-shrink-0" />
|
||||
<Toggle
|
||||
checked={notifPrefs.onSessionComplete}
|
||||
onChange={(v) => updateNotifPrefs({ onSessionComplete: v })}
|
||||
@@ -1688,9 +1690,11 @@ export function Settings() {
|
||||
const tableIcons: Record<string, React.ReactNode> = {
|
||||
sessions: <Layers className="w-4 h-4 text-blue-500" />,
|
||||
agents: <Users className="w-4 h-4 text-status-success" />,
|
||||
events: <Activity className="w-4 h-4 text-violet-400" />,
|
||||
events: <Activity className="w-4 h-4 text-violet-700 dark:text-violet-400" />,
|
||||
token_usage: <Coins className="w-4 h-4 text-status-warning" />,
|
||||
model_pricing: <BarChart3 className="w-4 h-4 text-cyan-400" />,
|
||||
model_pricing: (
|
||||
<BarChart3 className="w-4 h-4 text-cyan-700 dark:text-cyan-400" />
|
||||
),
|
||||
};
|
||||
const tableLabels: Record<string, string> = {
|
||||
sessions: t("tables.sessions"),
|
||||
@@ -1725,7 +1729,7 @@ export function Settings() {
|
||||
})()}
|
||||
<div className="bg-surface-2 rounded-lg px-3 py-3 border-l-2 border-indigo-500/20">
|
||||
<div className="flex items-center gap-2 mb-1.5">
|
||||
<HardDrive className="w-4 h-4 text-indigo-400" />
|
||||
<HardDrive className="w-4 h-4 text-indigo-700 dark:text-indigo-400" />
|
||||
<p className="text-[11px] text-fg-muted uppercase tracking-wider">
|
||||
{t("data.dbSize")}
|
||||
</p>
|
||||
@@ -1870,7 +1874,7 @@ export function Settings() {
|
||||
<div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-5 gap-3">
|
||||
<div className="bg-surface-2 rounded-lg px-4 py-3">
|
||||
<div className="flex items-center gap-2 mb-1.5">
|
||||
<Server className="w-4 h-4 text-indigo-400" />
|
||||
<Server className="w-4 h-4 text-indigo-700 dark:text-indigo-400" />
|
||||
<p className="text-[11px] text-fg-muted uppercase tracking-wider">
|
||||
{t("about.release")}
|
||||
</p>
|
||||
@@ -1908,7 +1912,7 @@ export function Settings() {
|
||||
</div>
|
||||
<div className="bg-surface-2 rounded-lg px-4 py-3">
|
||||
<div className="flex items-center gap-2 mb-1.5">
|
||||
<Globe className="w-4 h-4 text-violet-400" />
|
||||
<Globe className="w-4 h-4 text-violet-700 dark:text-violet-400" />
|
||||
<p className="text-[11px] text-fg-muted uppercase tracking-wider">
|
||||
{t("about.platform")}
|
||||
</p>
|
||||
|
||||
@@ -224,7 +224,7 @@ export function Workflows() {
|
||||
<div className="card p-4 space-y-3">
|
||||
<div>
|
||||
<h2 className="text-sm font-semibold text-fg-secondary flex items-center gap-2">
|
||||
<Workflow className="w-4 h-4 text-violet-400" />
|
||||
<Workflow className="w-4 h-4 text-violet-700 dark:text-violet-400" />
|
||||
{t("runs.title")}
|
||||
</h2>
|
||||
<p className="text-xs text-fg-muted mt-0.5">{t("runs.subtitle")}</p>
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* @file Dashboard.heatmap.test.ts
|
||||
* @description Pins the activity heatmap's colour ramp to the theme tokens: it
|
||||
* runs from the page surface to the accent in every theme, instead of a fixed
|
||||
* near-black indigo that vanished into dark and blotted out light themes.
|
||||
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
|
||||
*/
|
||||
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { cellColor } from "../Dashboard";
|
||||
|
||||
describe("heatmap cellColor", () => {
|
||||
it("paints an empty day as the bare surface", () => {
|
||||
expect(cellColor(0, 10)).toBe("rgb(var(--surface-0))");
|
||||
});
|
||||
|
||||
it("mixes the accent into the surface, more for busier days, and never a fixed rgb", () => {
|
||||
const low = cellColor(1, 100);
|
||||
const high = cellColor(100, 100);
|
||||
for (const c of [low, high]) {
|
||||
expect(c).toMatch(
|
||||
/^color-mix\(in srgb, rgb\(var\(--accent\)\) \d+%, rgb\(var\(--surface-0\)\)\)$/
|
||||
);
|
||||
}
|
||||
const pct = (c: string) => Number(/(\d+)%/.exec(c)?.[1]);
|
||||
expect(pct(high)).toBe(100);
|
||||
expect(pct(low)).toBeGreaterThan(0);
|
||||
expect(pct(low)).toBeLessThan(pct(high));
|
||||
});
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user