-
Run History
+
+ Run History
+
{rows.length === 0 ? (
@@ -196,15 +198,15 @@ export default async function RunsPage({ searchParams }: RunsPageProps) {
-
+
{run.triggeredBy}
-
+
{run.startedAt
? new Date(run.startedAt).toLocaleString()
: "-"}
-
+
{formatDuration(run.startedAt, run.endedAt)}
diff --git a/src/app/globals.css b/src/app/globals.css
index 5de9a9e..4f200d7 100644
--- a/src/app/globals.css
+++ b/src/app/globals.css
@@ -9,8 +9,14 @@
--color-background: var(--background);
--color-foreground: var(--foreground);
--font-sans: var(--font-sans);
- --font-mono: var(--font-geist-mono);
- --font-heading: var(--font-sans);
+ --font-mono: var(--font-mono);
+ --font-heading: var(--font-display);
+ --color-status-queued: var(--status-queued);
+ --color-status-running: var(--status-running);
+ --color-status-succeeded: var(--status-succeeded);
+ --color-status-failed: var(--status-failed);
+ --color-status-cancelled: var(--status-cancelled);
+ --color-status-warn: var(--status-warn);
--color-sidebar-ring: var(--sidebar-ring);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
@@ -49,73 +55,90 @@
--radius-4xl: calc(var(--radius) * 2.6);
}
+/*
+ * Palette - "instrument panel": cool graphite ink/paper with a warm brass signal accent.
+ * The brass tone doubles as the "running" status color, so a live run literally lights the UI
+ * up with the brand color - see run-status-badge.tsx and run-terminal.tsx.
+ */
:root {
- --background: oklch(1 0 0);
- --foreground: oklch(0.145 0 0);
- --card: oklch(1 0 0);
- --card-foreground: oklch(0.145 0 0);
- --popover: oklch(1 0 0);
- --popover-foreground: oklch(0.145 0 0);
- --primary: oklch(0.205 0 0);
- --primary-foreground: oklch(0.985 0 0);
- --secondary: oklch(0.97 0 0);
- --secondary-foreground: oklch(0.205 0 0);
- --muted: oklch(0.97 0 0);
- --muted-foreground: oklch(0.556 0 0);
- --accent: oklch(0.97 0 0);
- --accent-foreground: oklch(0.205 0 0);
- --destructive: oklch(0.577 0.245 27.325);
- --border: oklch(0.922 0 0);
- --input: oklch(0.922 0 0);
- --ring: oklch(0.708 0 0);
- --chart-1: oklch(0.87 0 0);
- --chart-2: oklch(0.556 0 0);
- --chart-3: oklch(0.439 0 0);
- --chart-4: oklch(0.371 0 0);
- --chart-5: oklch(0.269 0 0);
- --radius: 0.625rem;
- --sidebar: oklch(0.985 0 0);
- --sidebar-foreground: oklch(0.145 0 0);
- --sidebar-primary: oklch(0.205 0 0);
- --sidebar-primary-foreground: oklch(0.985 0 0);
- --sidebar-accent: oklch(0.97 0 0);
- --sidebar-accent-foreground: oklch(0.205 0 0);
- --sidebar-border: oklch(0.922 0 0);
- --sidebar-ring: oklch(0.708 0 0);
+ --background: #f5f6f7;
+ --foreground: #15171a;
+ --card: #ffffff;
+ --card-foreground: #15171a;
+ --popover: #ffffff;
+ --popover-foreground: #15171a;
+ --primary: #c8842e;
+ --primary-foreground: #17130a;
+ --secondary: #ececee;
+ --secondary-foreground: #15171a;
+ --muted: #ececee;
+ --muted-foreground: #6b7280;
+ --accent: #ececee;
+ --accent-foreground: #15171a;
+ --destructive: #b3402e;
+ --border: #dfe1e3;
+ --input: #dfe1e3;
+ --ring: #c8842e;
+ --status-queued: #6b7280;
+ --status-running: #c8842e;
+ --status-succeeded: #3f8f5f;
+ --status-failed: #b3402e;
+ --status-cancelled: #6b7280;
+ --status-warn: #8a7530;
+ --chart-1: #c8842e;
+ --chart-2: #3f8f5f;
+ --chart-3: #6b7280;
+ --chart-4: #8a7530;
+ --chart-5: #b3402e;
+ --radius: 0.5rem;
+ --sidebar: #f5f6f7;
+ --sidebar-foreground: #15171a;
+ --sidebar-primary: #c8842e;
+ --sidebar-primary-foreground: #17130a;
+ --sidebar-accent: #ececee;
+ --sidebar-accent-foreground: #15171a;
+ --sidebar-border: #dfe1e3;
+ --sidebar-ring: #c8842e;
}
.dark {
- --background: oklch(0.145 0 0);
- --foreground: oklch(0.985 0 0);
- --card: oklch(0.205 0 0);
- --card-foreground: oklch(0.985 0 0);
- --popover: oklch(0.205 0 0);
- --popover-foreground: oklch(0.985 0 0);
- --primary: oklch(0.922 0 0);
- --primary-foreground: oklch(0.205 0 0);
- --secondary: oklch(0.269 0 0);
- --secondary-foreground: oklch(0.985 0 0);
- --muted: oklch(0.269 0 0);
- --muted-foreground: oklch(0.708 0 0);
- --accent: oklch(0.269 0 0);
- --accent-foreground: oklch(0.985 0 0);
- --destructive: oklch(0.704 0.191 22.216);
+ --background: #14161a;
+ --foreground: #edeef0;
+ --card: #1b1e23;
+ --card-foreground: #edeef0;
+ --popover: #1b1e23;
+ --popover-foreground: #edeef0;
+ --primary: #e9a857;
+ --primary-foreground: #1a1305;
+ --secondary: #23262c;
+ --secondary-foreground: #edeef0;
+ --muted: #23262c;
+ --muted-foreground: #9aa0a8;
+ --accent: #23262c;
+ --accent-foreground: #edeef0;
+ --destructive: #e2685a;
--border: oklch(1 0 0 / 10%);
- --input: oklch(1 0 0 / 15%);
- --ring: oklch(0.556 0 0);
- --chart-1: oklch(0.87 0 0);
- --chart-2: oklch(0.556 0 0);
- --chart-3: oklch(0.439 0 0);
- --chart-4: oklch(0.371 0 0);
- --chart-5: oklch(0.269 0 0);
- --sidebar: oklch(0.205 0 0);
- --sidebar-foreground: oklch(0.985 0 0);
- --sidebar-primary: oklch(0.488 0.243 264.376);
- --sidebar-primary-foreground: oklch(0.985 0 0);
- --sidebar-accent: oklch(0.269 0 0);
- --sidebar-accent-foreground: oklch(0.985 0 0);
+ --input: oklch(1 0 0 / 14%);
+ --ring: #e9a857;
+ --status-queued: #9aa0a8;
+ --status-running: #e9a857;
+ --status-succeeded: #5fb983;
+ --status-failed: #e2685a;
+ --status-cancelled: #9aa0a8;
+ --status-warn: #c9a344;
+ --chart-1: #e9a857;
+ --chart-2: #5fb983;
+ --chart-3: #9aa0a8;
+ --chart-4: #c9a344;
+ --chart-5: #e2685a;
+ --sidebar: #1b1e23;
+ --sidebar-foreground: #edeef0;
+ --sidebar-primary: #e9a857;
+ --sidebar-primary-foreground: #1a1305;
+ --sidebar-accent: #23262c;
+ --sidebar-accent-foreground: #edeef0;
--sidebar-border: oklch(1 0 0 / 10%);
- --sidebar-ring: oklch(0.556 0 0);
+ --sidebar-ring: #e9a857;
}
@layer base {
diff --git a/src/app/icon.svg b/src/app/icon.svg
index c59af93..f379ed5 100644
--- a/src/app/icon.svg
+++ b/src/app/icon.svg
@@ -1,5 +1,5 @@
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index e2c2539..4bf2910 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -1,18 +1,28 @@
import type { Metadata } from "next";
-import { Geist, Geist_Mono } from "next/font/google";
+import { Bricolage_Grotesque, IBM_Plex_Mono, IBM_Plex_Sans } from "next/font/google";
import { ThemeProvider } from "next-themes";
import { TooltipProvider } from "@/components/ui/tooltip";
import { Toaster } from "@/components/ui/sonner";
import "./globals.css";
-const geistSans = Geist({
- variable: "--font-geist-sans",
+// Display face for page titles and card headings (wired via --font-heading in globals.css) - used
+// with restraint, never for body copy. Body/UI text and technical data (run IDs, commands,
+// timestamps) share the IBM Plex family so the two registers still read as one system.
+const displayFont = Bricolage_Grotesque({
+ variable: "--font-display",
subsets: ["latin"],
});
-const geistMono = Geist_Mono({
- variable: "--font-geist-mono",
+const sansFont = IBM_Plex_Sans({
+ variable: "--font-sans",
subsets: ["latin"],
+ weight: ["400", "500", "600", "700"],
+});
+
+const monoFont = IBM_Plex_Mono({
+ variable: "--font-mono",
+ subsets: ["latin"],
+ weight: ["400", "500", "600"],
});
export const metadata: Metadata = {
@@ -31,7 +41,7 @@ export default function RootLayout({
return (
-
+
TriggerShell
diff --git a/src/components/layout/nav.tsx b/src/components/layout/nav.tsx
index 273609d..5c1bc3d 100644
--- a/src/components/layout/nav.tsx
+++ b/src/components/layout/nav.tsx
@@ -34,9 +34,9 @@ export function Nav({
-
+
TriggerShell