From 2f501fc9c5d355c99ccb4a7cf258e08e72887223 Mon Sep 17 00:00:00 2001 From: John Hu Date: Sun, 21 Jun 2026 07:48:07 +0800 Subject: [PATCH 1/6] feat(console): warm tactile design tokens + paper grain --- apps/console/app/globals.css | 142 +++++++++++++++++++++++++---------- 1 file changed, 102 insertions(+), 40 deletions(-) diff --git a/apps/console/app/globals.css b/apps/console/app/globals.css index fc3189e..fc48e3b 100644 --- a/apps/console/app/globals.css +++ b/apps/console/app/globals.css @@ -1,59 +1,69 @@ @import "tailwindcss"; /* - * Auriga console design tokens. One design system: a calm, dense, neutral base - * with a single near-black accent; semantic state colors live with the Badge. - * Dark mode follows the OS via prefers-color-scheme (Tailwind v4's default - * `dark:` variant keys on the same media query, so both stay in sync). + * Auriga console design tokens. One design system: a warm, tactile, layered base — + * a soft cream / charcoal neutral ramp (no brand color), depth from layered shadows + * and hairline rings, and a faint paper grain over the page. Semantic state colors + * live with the Badge. Dark mode follows the OS via prefers-color-scheme (Tailwind + * v4's default `dark:` variant keys on the same media query, so both stay in sync). */ :root { - --background: 0 0% 100%; - --foreground: 240 10% 4%; - --card: 0 0% 100%; - --card-foreground: 240 10% 4%; - --popover: 0 0% 100%; - --popover-foreground: 240 10% 4%; - --primary: 240 6% 10%; - --primary-foreground: 0 0% 98%; - --secondary: 240 5% 96%; - --secondary-foreground: 240 6% 10%; - --muted: 240 5% 96%; - --muted-foreground: 240 4% 44%; - --accent: 240 5% 95%; - --accent-foreground: 240 6% 10%; + --background: 40 22% 97%; + --foreground: 28 9% 12%; + --card: 40 40% 99%; + --card-foreground: 28 9% 12%; + --popover: 40 40% 99%; + --popover-foreground: 28 9% 12%; + --primary: 28 11% 15%; + --primary-foreground: 40 30% 98%; + --secondary: 40 16% 93%; + --secondary-foreground: 28 11% 15%; + --muted: 40 16% 93%; + --muted-foreground: 30 6% 40%; + --accent: 38 18% 90%; + --accent-foreground: 28 11% 15%; --destructive: 0 72% 51%; --destructive-foreground: 0 0% 98%; - --border: 240 6% 90%; - --input: 240 6% 88%; - --ring: 240 6% 30%; - --radius: 0.625rem; + --border: 36 16% 87%; + --input: 36 15% 84%; + --ring: 30 9% 32%; + --radius: 0.75rem; + + /* A single faint paper grain, reused by the body and elevated surfaces. */ + --grain: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.82' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E"); + --grain-opacity: 0.03; + --grain-opacity-surface: 0.035; } @media (prefers-color-scheme: dark) { :root { - --background: 240 10% 4%; - --foreground: 0 0% 96%; - --card: 240 9% 7%; - --card-foreground: 0 0% 96%; - --popover: 240 9% 7%; - --popover-foreground: 0 0% 96%; - --primary: 0 0% 96%; - --primary-foreground: 240 6% 10%; - --secondary: 240 4% 14%; - --secondary-foreground: 0 0% 96%; - --muted: 240 4% 14%; - --muted-foreground: 240 5% 64%; - --accent: 240 4% 16%; - --accent-foreground: 0 0% 96%; + --background: 30 8% 7%; + --foreground: 40 16% 92%; + --card: 30 7% 11%; + --card-foreground: 40 16% 92%; + --popover: 30 7% 11%; + --popover-foreground: 40 16% 92%; + --primary: 40 18% 92%; + --primary-foreground: 28 11% 12%; + --secondary: 30 6% 16%; + --secondary-foreground: 40 16% 92%; + --muted: 30 6% 16%; + --muted-foreground: 38 9% 62%; + --accent: 30 6% 19%; + --accent-foreground: 40 16% 92%; --destructive: 0 62% 47%; --destructive-foreground: 0 0% 98%; - --border: 240 4% 16%; - --input: 240 4% 18%; - --ring: 240 5% 70%; + --border: 36 7% 20%; + --input: 36 7% 22%; + --ring: 40 12% 70%; + + --grain-opacity: 0.05; + --grain-opacity-surface: 0.05; } } -/* Map the tokens onto Tailwind color utilities (bg-background, text-muted-foreground, …). */ +/* Map the tokens onto Tailwind color utilities (bg-background, text-muted-foreground, …) + * plus the layered shadow + radius scale used by the primitives. */ @theme inline { --color-background: hsl(var(--background)); --color-foreground: hsl(var(--foreground)); @@ -74,9 +84,14 @@ --color-border: hsl(var(--border)); --color-input: hsl(var(--input)); --color-ring: hsl(var(--ring)); + --radius-xl: calc(var(--radius) + 4px); --radius-lg: var(--radius); --radius-md: calc(var(--radius) - 2px); --radius-sm: calc(var(--radius) - 4px); + /* Soft, warm-tinted layered shadows. Light-mode tuned; on dark the hairline + * ring + border carry the elevation, so the faint shadow simply recedes. */ + --shadow-card: 0 1px 2px -1px hsl(28 20% 12% / 0.06), 0 4px 12px -4px hsl(28 20% 12% / 0.08); + --shadow-card-lg: 0 2px 6px -2px hsl(28 20% 12% / 0.08), 0 12px 28px -8px hsl(28 20% 12% / 0.12); } @layer base { @@ -87,12 +102,59 @@ } body { background-color: hsl(var(--background)); + /* A soft warm wash from the top gives the page gentle depth. */ + background-image: radial-gradient( + 48rem 32rem at 50% -8%, + hsl(40 36% 88% / 0.55), + transparent 70% + ); color: hsl(var(--foreground)); font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; + line-height: 1.55; + letter-spacing: -0.005em; -webkit-font-smoothing: antialiased; text-rendering: optimizeLegibility; } + @media (prefers-color-scheme: dark) { + body { + background-image: radial-gradient( + 48rem 32rem at 50% -8%, + hsl(40 18% 40% / 0.12), + transparent 70% + ); + } + } + h1, + h2, + h3 { + letter-spacing: -0.013em; + } +} + +/* Faint paper grain over the whole page (behind content). */ +body::before { + content: ""; + position: fixed; + inset: 0; + z-index: -1; + pointer-events: none; + background-image: var(--grain); + opacity: var(--grain-opacity); +} + +/* Opt-in grain for elevated surfaces (cards, panels) — gives them a tactile face. */ +.auriga-grain { + position: relative; +} +.auriga-grain::before { + content: ""; + position: absolute; + inset: 0; + border-radius: inherit; + pointer-events: none; + background-image: var(--grain); + opacity: var(--grain-opacity-surface); } /* A soft pulse for the live "running" indicator dot. */ From 1d51c34a1df58e6d1a81f2d9afdc979c7ba2e8de Mon Sep 17 00:00:00 2001 From: John Hu Date: Sun, 21 Jun 2026 07:51:45 +0800 Subject: [PATCH 2/6] feat(console): refresh shared UI primitives --- apps/console/components/sparkline.tsx | 7 ++++- apps/console/components/ui/badge.tsx | 21 +++++-------- apps/console/components/ui/bar.tsx | 9 ++++-- apps/console/components/ui/button.tsx | 14 ++++----- apps/console/components/ui/card.tsx | 11 +++++-- apps/console/components/ui/empty-state.tsx | 35 ++++++++++++++++++++++ apps/console/components/ui/input.tsx | 2 +- apps/console/components/ui/select.tsx | 2 +- apps/console/components/ui/table.tsx | 24 +++++++++++---- apps/console/components/ui/textarea.tsx | 2 +- 10 files changed, 93 insertions(+), 34 deletions(-) create mode 100644 apps/console/components/ui/empty-state.tsx diff --git a/apps/console/components/sparkline.tsx b/apps/console/components/sparkline.tsx index e16877c..ac6d490 100644 --- a/apps/console/components/sparkline.tsx +++ b/apps/console/components/sparkline.tsx @@ -14,19 +14,24 @@ export function Sparkline({ const H = 40; const points = sparklinePoints(values, W, H); if (!points) return no data; + // Close the line into a filled area: drop to the baseline at both ends. A flat + // translucent fill (no gradient) keeps this SSR-safe — no generated ids. + const area = `0,${H} ${points} ${W},${H}`; return ( + diff --git a/apps/console/components/ui/badge.tsx b/apps/console/components/ui/badge.tsx index ca33841..5529477 100644 --- a/apps/console/components/ui/badge.tsx +++ b/apps/console/components/ui/badge.tsx @@ -6,12 +6,12 @@ import type { JobState } from "@/lib/types"; // ring read calmly on both light and dark; `dark:` keys on prefers-color-scheme to // match the token theme. const TONE: Record = { - done: "bg-emerald-500/15 text-emerald-700 dark:text-emerald-400 ring-emerald-500/25", - failed: "bg-red-500/15 text-red-700 dark:text-red-400 ring-red-500/25", - running: "bg-blue-500/15 text-blue-700 dark:text-blue-400 ring-blue-500/25", - planning: "bg-indigo-500/15 text-indigo-700 dark:text-indigo-400 ring-indigo-500/25", - verifying: "bg-amber-500/15 text-amber-700 dark:text-amber-400 ring-amber-500/25", - paused: "bg-amber-500/15 text-amber-700 dark:text-amber-400 ring-amber-500/25", + done: "bg-emerald-500/15 text-emerald-700 dark:text-emerald-300 ring-emerald-500/30", + failed: "bg-red-500/15 text-red-700 dark:text-red-300 ring-red-500/30", + running: "bg-blue-500/15 text-blue-700 dark:text-blue-300 ring-blue-500/30", + planning: "bg-indigo-500/15 text-indigo-700 dark:text-indigo-300 ring-indigo-500/30", + verifying: "bg-amber-500/15 text-amber-700 dark:text-amber-300 ring-amber-500/30", + paused: "bg-amber-500/15 text-amber-700 dark:text-amber-300 ring-amber-500/30", pending: "bg-muted text-muted-foreground ring-border", cancelled: "bg-muted text-muted-foreground ring-border", }; @@ -36,18 +36,13 @@ export function Badge({ return ( {dot && ( - + )} {children} diff --git a/apps/console/components/ui/bar.tsx b/apps/console/components/ui/bar.tsx index 00f1af8..51aa7d7 100644 --- a/apps/console/components/ui/bar.tsx +++ b/apps/console/components/ui/bar.tsx @@ -18,14 +18,17 @@ export function Bar({ }) { const pct = max > 0 ? Math.min(100, (value / max) * 100) : 0; return ( -
+
{label} {valueLabel ?? value}
-
+
diff --git a/apps/console/components/ui/button.tsx b/apps/console/components/ui/button.tsx index 974e1c9..359cd59 100644 --- a/apps/console/components/ui/button.tsx +++ b/apps/console/components/ui/button.tsx @@ -4,23 +4,23 @@ import type { ButtonHTMLAttributes } from "react"; import { cn } from "@/lib/utils"; const buttonVariants = cva( - "inline-flex items-center justify-center gap-1.5 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-50 [&_svg]:size-4 [&_svg]:shrink-0", + "inline-flex items-center justify-center gap-1.5 whitespace-nowrap rounded-lg text-sm font-medium transition-[background-color,box-shadow,transform,color,border-color] duration-150 active:translate-y-px focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/50 focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-50 disabled:active:translate-y-0 [&_svg]:size-4 [&_svg]:shrink-0", { variants: { variant: { // "primary"/"secondary" kept as the canonical names the existing call sites use. - primary: "bg-primary text-primary-foreground hover:bg-primary/90", + primary: "bg-primary text-primary-foreground shadow-sm hover:bg-primary/90", secondary: - "border border-input bg-background text-foreground hover:bg-accent hover:text-accent-foreground", + "border border-input bg-card text-foreground shadow-sm hover:bg-accent hover:text-accent-foreground", outline: "border border-input bg-transparent hover:bg-accent hover:text-accent-foreground", ghost: "hover:bg-accent hover:text-accent-foreground", - destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90", + destructive: "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90", }, size: { - default: "h-8 px-3 py-1.5", - sm: "h-7 rounded-sm px-2.5 text-xs", - icon: "h-8 w-8", + default: "h-9 px-3.5 py-2", + sm: "h-8 rounded-md px-2.5 text-xs", + icon: "h-9 w-9", }, }, defaultVariants: { variant: "primary", size: "default" }, diff --git a/apps/console/components/ui/card.tsx b/apps/console/components/ui/card.tsx index 726c69a..645c99d 100644 --- a/apps/console/components/ui/card.tsx +++ b/apps/console/components/ui/card.tsx @@ -5,7 +5,9 @@ export function Card({ className, children }: { className?: string; children: Re return (
@@ -16,7 +18,12 @@ export function Card({ className, children }: { className?: string; children: Re export function CardTitle({ className, children }: { className?: string; children: ReactNode }) { return ( -

+

{children}

); diff --git a/apps/console/components/ui/empty-state.tsx b/apps/console/components/ui/empty-state.tsx new file mode 100644 index 0000000..3ff75bb --- /dev/null +++ b/apps/console/components/ui/empty-state.tsx @@ -0,0 +1,35 @@ +import type { ComponentType, ReactNode } from "react"; +import { cn } from "@/lib/utils"; + +/** A calm placeholder for empty / unavailable data — replaces bare `

` messages. */ +export function EmptyState({ + icon: Icon, + title, + description, + className, + children, +}: { + icon?: ComponentType<{ className?: string }>; + title: string; + description?: ReactNode; + className?: string; + children?: ReactNode; +}) { + return ( +

+ {Icon && ( +
+ +
+ )} +

{title}

+ {description &&

{description}

} + {children} +
+ ); +} diff --git a/apps/console/components/ui/input.tsx b/apps/console/components/ui/input.tsx index 4d747b0..eab5bc7 100644 --- a/apps/console/components/ui/input.tsx +++ b/apps/console/components/ui/input.tsx @@ -5,7 +5,7 @@ export function Input({ className, ...props }: InputHTMLAttributes{children}; + // Allow wide tables to scroll horizontally on small screens without breaking the card. + return ( +
+ {children}
+
+ ); } export function THead({ children }: { children: ReactNode }) { return ( - + {children} ); @@ -18,17 +23,26 @@ export function TBody({ children }: { children: ReactNode }) { } export function TR({ children, className }: { children: ReactNode; className?: string }) { - return {children}; + return ( + + {children} + + ); } export function TH({ children }: { children: ReactNode }) { return ( - + {children} ); } export function TD({ children, className }: { children: ReactNode; className?: string }) { - return {children}; + return {children}; } diff --git a/apps/console/components/ui/textarea.tsx b/apps/console/components/ui/textarea.tsx index 77dbd1b..dd5bec6 100644 --- a/apps/console/components/ui/textarea.tsx +++ b/apps/console/components/ui/textarea.tsx @@ -5,7 +5,7 @@ export function Textarea({ className, ...props }: TextareaHTMLAttributes Date: Sun, 21 Jun 2026 07:52:59 +0800 Subject: [PATCH 3/6] feat(console): redesign app shell and navigation --- apps/console/app/layout.tsx | 63 +++++++++++------------ apps/console/components/logout-button.tsx | 2 +- apps/console/components/nav-links.tsx | 43 ++++++++++++++++ 3 files changed, 73 insertions(+), 35 deletions(-) create mode 100644 apps/console/components/nav-links.tsx diff --git a/apps/console/app/layout.tsx b/apps/console/app/layout.tsx index 744e880..831faa2 100644 --- a/apps/console/app/layout.tsx +++ b/apps/console/app/layout.tsx @@ -1,7 +1,9 @@ import type { Metadata } from "next"; -import type { ReactNode } from "react"; import Link from "next/link"; +import type { ReactNode } from "react"; import { LogoutButton } from "@/components/logout-button"; +import { NavLinks } from "@/components/nav-links"; +import { Button } from "@/components/ui/button"; import { getSession } from "@/lib/session"; import "./globals.css"; @@ -10,47 +12,40 @@ export const metadata: Metadata = { description: "Auriga control-plane console", }; -const NAV = [ - { href: "/", label: "Dashboard" }, - { href: "/jobs", label: "Jobs" }, - { href: "/skills", label: "Skills" }, - { href: "/config", label: "Config" }, -]; - export default async function RootLayout({ children }: { children: ReactNode }) { const session = await getSession(); return ( -
-
- - Auriga +
+
+ + + A + + Auriga - - {session && ( -
- - {session.factio} · {session.role} - - -
- )} +
-
{children}
+
{children}
); diff --git a/apps/console/components/logout-button.tsx b/apps/console/components/logout-button.tsx index 35911d5..feb284b 100644 --- a/apps/console/components/logout-button.tsx +++ b/apps/console/components/logout-button.tsx @@ -15,7 +15,7 @@ export function LogoutButton() { router.push("/login"); })} disabled={pending} - className="text-sm text-neutral-600 hover:underline" + className="text-sm text-muted-foreground transition-colors hover:text-foreground" > {pending ? "…" : "logout"} diff --git a/apps/console/components/nav-links.tsx b/apps/console/components/nav-links.tsx new file mode 100644 index 0000000..6a983b1 --- /dev/null +++ b/apps/console/components/nav-links.tsx @@ -0,0 +1,43 @@ +"use client"; + +import Link from "next/link"; +import { usePathname } from "next/navigation"; +import { cn } from "@/lib/utils"; + +const NAV = [ + { href: "/", label: "Dashboard" }, + { href: "/jobs", label: "Jobs" }, + { href: "/skills", label: "Skills" }, + { href: "/config", label: "Config" }, +]; + +function isActive(pathname: string, href: string) { + return href === "/" ? pathname === "/" : pathname === href || pathname.startsWith(`${href}/`); +} + +/** Primary nav with active-route highlighting (client-side via usePathname). */ +export function NavLinks() { + const pathname = usePathname() ?? "/"; + return ( + + ); +} From db42b08f620d05e22f59295a31c0a669fcceea87 Mon Sep 17 00:00:00 2001 From: John Hu Date: Sun, 21 Jun 2026 07:56:22 +0800 Subject: [PATCH 4/6] feat(console): polish dashboard, jobs, and job detail --- apps/console/app/jobs/[id]/page.tsx | 50 ++++++-- apps/console/app/jobs/page.tsx | 43 +++++-- apps/console/app/page.tsx | 113 +++++++++++------- apps/console/components/live-run.tsx | 4 +- .../console/components/run-timeline-panel.tsx | 10 +- apps/console/components/run-timeline.tsx | 8 +- apps/console/components/workspace-viewer.tsx | 4 +- 7 files changed, 159 insertions(+), 73 deletions(-) diff --git a/apps/console/app/jobs/[id]/page.tsx b/apps/console/app/jobs/[id]/page.tsx index 78099ca..a68651e 100644 --- a/apps/console/app/jobs/[id]/page.tsx +++ b/apps/console/app/jobs/[id]/page.tsx @@ -1,3 +1,5 @@ +import { ChevronLeft } from "lucide-react"; +import Link from "next/link"; import { notFound } from "next/navigation"; import { api } from "@/lib/api"; import { ApproveButton } from "@/components/approve-button"; @@ -25,28 +27,41 @@ export default async function JobDetailPage({ params }: { params: Promise<{ id: const trace = live ? null : await api.trace(id); const ws = await api.workspace(id); const tokens = job.usage.input_tokens + job.usage.output_tokens; + const hasActions = a.needsApproval || a.runnable || a.pausable || a.cancellable; return (
+ + + Jobs + + -
+

{job.id}

{job.state} {job.reason && {job.reason}}
-

{job.spec.goal}

-

- model {job.model ?? "—"} · attempts {job.attempts} · steps {job.steps} · tokens{" "} - {tokens.toLocaleString()} -

-
- {a.needsApproval && } - {a.runnable && } - {a.pausable && } - {a.cancellable && } -
+

{job.spec.goal}

+
+ + + + +
+ {hasActions && ( +
+ {a.needsApproval && } + {a.runnable && } + {a.pausable && } + {a.cancellable && } +
+ )} @@ -76,3 +91,14 @@ export default async function JobDetailPage({ params }: { params: Promise<{ id:
); } + +function Meta({ label, value }: { label: string; value: string | number }) { + return ( +
+
+ {label} +
+
{value}
+
+ ); +} diff --git a/apps/console/app/jobs/page.tsx b/apps/console/app/jobs/page.tsx index c720643..a442cda 100644 --- a/apps/console/app/jobs/page.tsx +++ b/apps/console/app/jobs/page.tsx @@ -1,10 +1,12 @@ +import { CloudOff, Inbox } from "lucide-react"; import Link from "next/link"; import { api } from "@/lib/api"; import { JobRowActions } from "@/components/job-row-actions"; import { JobsFilterBar } from "@/components/jobs-filter-bar"; import { JobsPager } from "@/components/jobs-pager"; import { Badge } from "@/components/ui/badge"; -import { Card, CardTitle } from "@/components/ui/card"; +import { Card } from "@/components/ui/card"; +import { EmptyState } from "@/components/ui/empty-state"; import { Table, TBody, TD, TH, THead, TR } from "@/components/ui/table"; export const dynamic = "force-dynamic"; @@ -30,19 +32,38 @@ export default async function JobsPage({ searchParams }: { searchParams: SearchP offset, }); - if (!page) return

API unavailable or unauthorized.

; + if (!page) { + return ( + + ); + } return ( - -
- Jobs +
+
+
+

Jobs

+

+ {page.total.toLocaleString()} total +

+
{page.jobs.length === 0 ? ( -

No jobs match.

+ + + ) : ( - <> + @@ -58,7 +79,7 @@ export default async function JobsPage({ searchParams }: { searchParams: SearchP
{j.id} @@ -78,11 +99,11 @@ export default async function JobsPage({ searchParams }: { searchParams: SearchP ))}
-
+
- + )} - +
); } diff --git a/apps/console/app/page.tsx b/apps/console/app/page.tsx index 16acc3b..498b7a9 100644 --- a/apps/console/app/page.tsx +++ b/apps/console/app/page.tsx @@ -1,33 +1,51 @@ +import { CloudOff, DollarSign, Layers, Users } from "lucide-react"; import Link from "next/link"; +import type { ComponentType } from "react"; import { api } from "@/lib/api"; import { Sparkline } from "@/components/sparkline"; import { Badge } from "@/components/ui/badge"; import { Bar } from "@/components/ui/bar"; import { Card, CardTitle } from "@/components/ui/card"; +import { EmptyState } from "@/components/ui/empty-state"; import { Table, TBody, TD, TH, THead, TR } from "@/components/ui/table"; export const dynamic = "force-dynamic"; export default async function DashboardPage() { const dash = await api.dashboard(); - if (!dash) return

API unavailable.

; + if (!dash) { + return ( + + ); + } const totalActive = dash.active.reduce((sum, a) => sum + a.active, 0); const maxModelCost = Math.max(...dash.byModel.map((m) => m.cost_usd), 0.0001); return ( -
+
+
+

Overview

+

+ Control-plane activity across all tenants. +

+
+
- - - + + +
Cost trend p.cost_usd)} ariaLabel="daily cost trend" /> -

+

{dash.costTrend.length > 0 ? `${dash.costTrend[0]?.bucket} → ${dash.costTrend.at(-1)?.bucket}` : "no data"}{" "} @@ -40,7 +58,7 @@ export default async function DashboardPage() { {dash.byModel.length === 0 ? (

no data

) : ( -
+
{dash.byModel.slice(0, 6).map((m) => ( Quota utilization -
+
Recent audit - - - - - - - - - - - {dash.recentAudit.map((e) => ( - - - - - + {dash.recentAudit.length === 0 ? ( +

No recent activity.

+ ) : ( +
timefactioactionjob
- {new Date(e.ts).toLocaleString()} - {e.factio}{e.action} - {e.job_id ? ( - - {e.job_id} - - ) : ( - "" - )} -
+ + + + + + - ))} - -
timefactioactionjob
+ + + {dash.recentAudit.map((e) => ( + + + {new Date(e.ts).toLocaleString()} + + {e.factio} + {e.action} + + {e.job_id ? ( + + {e.job_id} + + ) : ( + "" + )} + + + ))} + + + )}
); } -function Stat({ label, value }: { label: string; value: string }) { +function Stat({ + label, + value, + icon: Icon, +}: { + label: string; + value: string; + icon?: ComponentType<{ className?: string }>; +}) { return ( - {label} -

{value}

+
+ {label} + {Icon && } +
+

{value}

); } diff --git a/apps/console/components/live-run.tsx b/apps/console/components/live-run.tsx index 41caaa8..9604ea8 100644 --- a/apps/console/components/live-run.tsx +++ b/apps/console/components/live-run.tsx @@ -56,7 +56,7 @@ export function LiveRun({ jobId, seed }: { jobId: string; seed: LiveRunSeed }) { function LiveStatus({ state }: { state: LiveRunState }) { const tokens = state.usage.input_tokens + state.usage.output_tokens; return ( -
+
{state.state} @@ -65,7 +65,7 @@ function LiveStatus({ state }: { state: LiveRunState }) { {!state.terminal && ( - + live diff --git a/apps/console/components/run-timeline-panel.tsx b/apps/console/components/run-timeline-panel.tsx index c643554..4e3ec79 100644 --- a/apps/console/components/run-timeline-panel.tsx +++ b/apps/console/components/run-timeline-panel.tsx @@ -16,7 +16,11 @@ export function RunTimelinePanel({ events }: { events: TraceEvent[] }) { return (
-
+
{TABS.map((t) => ( - {error && {error}} + {error && {error}}
); From 899c62d2b7a636499ab14854f842076e4c4dafc9 Mon Sep 17 00:00:00 2001 From: John Hu Date: Sun, 21 Jun 2026 08:45:39 +0800 Subject: [PATCH 6/6] style(console): align form control padding and row backgrounds --- apps/console/components/job-form.tsx | 2 +- apps/console/components/ui/select.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/console/components/job-form.tsx b/apps/console/components/job-form.tsx index 8424ca9..044749f 100644 --- a/apps/console/components/job-form.tsx +++ b/apps/console/components/job-form.tsx @@ -276,7 +276,7 @@ function CriterionRow({ onRemove?: () => void; }) { return ( -
+