Restyle the UI: instrument-panel palette, type system, and a signal accent
Replaces the default shadcn grayscale/Geist look with a deliberate identity built around the product's own subject - a control panel for running scripts and watching their output live: - Type: Bricolage Grotesque for page/card titles (via the existing --font-heading token, used with restraint), IBM Plex Sans for UI body text, IBM Plex Mono for technical data (run IDs, commands, timestamps, status labels) - all self-hosted at build time via next/font/google, no runtime CDN dependency for a self-hosted tool. - Color: a cool graphite ink/paper base with a warm brass signal accent in both themes. The brass tone doubles as the "running" status color, so an active run literally lights the UI up with the brand color. New --status-* tokens give queued/running/succeeded/failed/cancelled/ warn a single source of truth instead of ad-hoc Tailwind color classes. - Structural language: small tracked-out uppercase mono labels mark technical fields (Command, Variables, Triggered by...) consistently; the live-output terminal gets an instrument-bezel frame (header bar + panel) instead of floating loose above the xterm canvas. Layout/IA is unchanged throughout - this is a token- and detail-level pass, not a restructuring. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -11,11 +11,13 @@ export const metadata: Metadata = { title: "Docs" };
|
|||||||
export default function DocsIndexPage() {
|
export default function DocsIndexPage() {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
<h1 className="text-2xl font-semibold tracking-tight">Docs</h1>
|
<h1 className="font-heading text-2xl font-semibold tracking-tight">
|
||||||
|
Docs
|
||||||
|
</h1>
|
||||||
<div className="grid gap-3 sm:grid-cols-2">
|
<div className="grid gap-3 sm:grid-cols-2">
|
||||||
{DOCS.map((doc) => (
|
{DOCS.map((doc) => (
|
||||||
<Link key={doc.slug} href={`/docs/${doc.slug}`}>
|
<Link key={doc.slug} href={`/docs/${doc.slug}`}>
|
||||||
<Card className="hover:border-foreground/30 h-full transition-colors">
|
<Card className="hover:border-primary/50 h-full transition-colors">
|
||||||
<CardHeader className="flex flex-row items-start justify-between gap-2 space-y-0">
|
<CardHeader className="flex flex-row items-start justify-between gap-2 space-y-0">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<BookOpen className="text-muted-foreground size-4.5 shrink-0" />
|
<BookOpen className="text-muted-foreground size-4.5 shrink-0" />
|
||||||
|
|||||||
+16
-6
@@ -13,21 +13,31 @@ export default function DashboardPage() {
|
|||||||
|
|
||||||
if (config.scripts.length === 0) {
|
if (config.scripts.length === 0) {
|
||||||
return (
|
return (
|
||||||
<div className="text-muted-foreground py-24 text-center">
|
<div className="flex flex-col items-center gap-2 py-24 text-center">
|
||||||
No scripts configured yet. Add entries under{" "}
|
<PlayCircle className="text-muted-foreground/50 mb-2 size-8" />
|
||||||
<code className="bg-muted rounded px-1.5 py-0.5">scripts:</code> in your
|
<h1 className="font-heading text-lg font-semibold tracking-tight">
|
||||||
config file.
|
No scripts yet
|
||||||
|
</h1>
|
||||||
|
<p className="text-muted-foreground max-w-sm text-sm">
|
||||||
|
Add entries under{" "}
|
||||||
|
<code className="bg-muted rounded px-1.5 py-0.5 font-mono text-xs">
|
||||||
|
scripts:
|
||||||
|
</code>{" "}
|
||||||
|
in your config file to see them here.
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
<h1 className="text-2xl font-semibold tracking-tight">Scripts</h1>
|
<h1 className="font-heading text-2xl font-semibold tracking-tight">
|
||||||
|
Scripts
|
||||||
|
</h1>
|
||||||
<div className="grid gap-3 sm:grid-cols-2">
|
<div className="grid gap-3 sm:grid-cols-2">
|
||||||
{config.scripts.map((script) => (
|
{config.scripts.map((script) => (
|
||||||
<Link key={script.id} href={`/scripts/${script.id}`}>
|
<Link key={script.id} href={`/scripts/${script.id}`}>
|
||||||
<Card className="hover:border-foreground/30 h-full transition-colors">
|
<Card className="hover:border-primary/50 h-full transition-colors">
|
||||||
<CardHeader className="flex flex-row items-start justify-between gap-2 space-y-0">
|
<CardHeader className="flex flex-row items-start justify-between gap-2 space-y-0">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<PlayCircle className="text-muted-foreground size-4.5 shrink-0" />
|
<PlayCircle className="text-muted-foreground size-4.5 shrink-0" />
|
||||||
|
|||||||
@@ -81,36 +81,42 @@ export default async function RunDetailPage({ params }: RunDetailPageProps) {
|
|||||||
</Link>
|
</Link>
|
||||||
</CardAction>
|
</CardAction>
|
||||||
)}
|
)}
|
||||||
<dl className="text-muted-foreground grid grid-cols-2 gap-x-4 gap-y-1 text-xs sm:grid-cols-3">
|
<dl className="text-muted-foreground grid grid-cols-2 gap-x-4 gap-y-2 text-xs sm:grid-cols-3">
|
||||||
<div>
|
<div>
|
||||||
<dt className="font-medium">Triggered by</dt>
|
<dt className="font-mono text-[0.7rem] font-medium tracking-widest uppercase">
|
||||||
<dd>{run.triggeredBy}</dd>
|
Triggered by
|
||||||
|
</dt>
|
||||||
|
<dd className="font-mono">{run.triggeredBy}</dd>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<dt className="font-medium">Started</dt>
|
<dt className="font-mono text-[0.7rem] font-medium tracking-widest uppercase">
|
||||||
<dd>
|
Started
|
||||||
|
</dt>
|
||||||
|
<dd className="font-mono">
|
||||||
{run.startedAt ? new Date(run.startedAt).toLocaleString() : "-"}
|
{run.startedAt ? new Date(run.startedAt).toLocaleString() : "-"}
|
||||||
</dd>
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<dt className="font-medium">Run ID</dt>
|
<dt className="font-mono text-[0.7rem] font-medium tracking-widest uppercase">
|
||||||
|
Run ID
|
||||||
|
</dt>
|
||||||
<dd className="truncate font-mono">{run.id}</dd>
|
<dd className="truncate font-mono">{run.id}</dd>
|
||||||
</div>
|
</div>
|
||||||
</dl>
|
</dl>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="flex flex-col gap-4">
|
<CardContent className="flex flex-col gap-4">
|
||||||
<div className="flex flex-col gap-1.5">
|
<div className="flex flex-col gap-1.5">
|
||||||
<span className="text-muted-foreground text-xs font-medium">
|
<span className="text-muted-foreground font-mono text-[0.7rem] font-medium tracking-widest uppercase">
|
||||||
Command
|
Command
|
||||||
</span>
|
</span>
|
||||||
<pre className="bg-muted overflow-x-auto rounded-md p-3 font-mono text-xs whitespace-pre">
|
<pre className="bg-muted border-primary/30 overflow-x-auto rounded-md border-l-2 p-3 font-mono text-xs whitespace-pre">
|
||||||
{run.resolvedCommand}
|
{run.resolvedCommand}
|
||||||
</pre>
|
</pre>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{variableEntries.length > 0 && (
|
{variableEntries.length > 0 && (
|
||||||
<div className="flex flex-col gap-1.5">
|
<div className="flex flex-col gap-1.5">
|
||||||
<span className="text-muted-foreground text-xs font-medium">
|
<span className="text-muted-foreground font-mono text-[0.7rem] font-medium tracking-widest uppercase">
|
||||||
Variables
|
Variables
|
||||||
</span>
|
</span>
|
||||||
<dl className="grid gap-x-6 gap-y-2 rounded-md border p-3 text-xs sm:grid-cols-2">
|
<dl className="grid gap-x-6 gap-y-2 rounded-md border p-3 text-xs sm:grid-cols-2">
|
||||||
|
|||||||
@@ -161,7 +161,9 @@ export default async function RunsPage({ searchParams }: RunsPageProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
<h1 className="text-2xl font-semibold tracking-tight">Run History</h1>
|
<h1 className="font-heading text-2xl font-semibold tracking-tight">
|
||||||
|
Run History
|
||||||
|
</h1>
|
||||||
<RunsToolbar scripts={scripts} />
|
<RunsToolbar scripts={scripts} />
|
||||||
{rows.length === 0 ? (
|
{rows.length === 0 ? (
|
||||||
<p className="text-muted-foreground py-12 text-center">
|
<p className="text-muted-foreground py-12 text-center">
|
||||||
@@ -196,15 +198,15 @@ export default async function RunsPage({ searchParams }: RunsPageProps) {
|
|||||||
<TableCell>
|
<TableCell>
|
||||||
<RunStatusBadge status={run.status} />
|
<RunStatusBadge status={run.status} />
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className="text-muted-foreground">
|
<TableCell className="text-muted-foreground font-mono text-xs">
|
||||||
{run.triggeredBy}
|
{run.triggeredBy}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className="text-muted-foreground">
|
<TableCell className="text-muted-foreground font-mono text-xs">
|
||||||
{run.startedAt
|
{run.startedAt
|
||||||
? new Date(run.startedAt).toLocaleString()
|
? new Date(run.startedAt).toLocaleString()
|
||||||
: "-"}
|
: "-"}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className="text-muted-foreground">
|
<TableCell className="text-muted-foreground font-mono text-xs">
|
||||||
{formatDuration(run.startedAt, run.endedAt)}
|
{formatDuration(run.startedAt, run.endedAt)}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
|
|||||||
+86
-63
@@ -9,8 +9,14 @@
|
|||||||
--color-background: var(--background);
|
--color-background: var(--background);
|
||||||
--color-foreground: var(--foreground);
|
--color-foreground: var(--foreground);
|
||||||
--font-sans: var(--font-sans);
|
--font-sans: var(--font-sans);
|
||||||
--font-mono: var(--font-geist-mono);
|
--font-mono: var(--font-mono);
|
||||||
--font-heading: var(--font-sans);
|
--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-ring: var(--sidebar-ring);
|
||||||
--color-sidebar-border: var(--sidebar-border);
|
--color-sidebar-border: var(--sidebar-border);
|
||||||
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
||||||
@@ -49,73 +55,90 @@
|
|||||||
--radius-4xl: calc(var(--radius) * 2.6);
|
--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 {
|
:root {
|
||||||
--background: oklch(1 0 0);
|
--background: #f5f6f7;
|
||||||
--foreground: oklch(0.145 0 0);
|
--foreground: #15171a;
|
||||||
--card: oklch(1 0 0);
|
--card: #ffffff;
|
||||||
--card-foreground: oklch(0.145 0 0);
|
--card-foreground: #15171a;
|
||||||
--popover: oklch(1 0 0);
|
--popover: #ffffff;
|
||||||
--popover-foreground: oklch(0.145 0 0);
|
--popover-foreground: #15171a;
|
||||||
--primary: oklch(0.205 0 0);
|
--primary: #c8842e;
|
||||||
--primary-foreground: oklch(0.985 0 0);
|
--primary-foreground: #17130a;
|
||||||
--secondary: oklch(0.97 0 0);
|
--secondary: #ececee;
|
||||||
--secondary-foreground: oklch(0.205 0 0);
|
--secondary-foreground: #15171a;
|
||||||
--muted: oklch(0.97 0 0);
|
--muted: #ececee;
|
||||||
--muted-foreground: oklch(0.556 0 0);
|
--muted-foreground: #6b7280;
|
||||||
--accent: oklch(0.97 0 0);
|
--accent: #ececee;
|
||||||
--accent-foreground: oklch(0.205 0 0);
|
--accent-foreground: #15171a;
|
||||||
--destructive: oklch(0.577 0.245 27.325);
|
--destructive: #b3402e;
|
||||||
--border: oklch(0.922 0 0);
|
--border: #dfe1e3;
|
||||||
--input: oklch(0.922 0 0);
|
--input: #dfe1e3;
|
||||||
--ring: oklch(0.708 0 0);
|
--ring: #c8842e;
|
||||||
--chart-1: oklch(0.87 0 0);
|
--status-queued: #6b7280;
|
||||||
--chart-2: oklch(0.556 0 0);
|
--status-running: #c8842e;
|
||||||
--chart-3: oklch(0.439 0 0);
|
--status-succeeded: #3f8f5f;
|
||||||
--chart-4: oklch(0.371 0 0);
|
--status-failed: #b3402e;
|
||||||
--chart-5: oklch(0.269 0 0);
|
--status-cancelled: #6b7280;
|
||||||
--radius: 0.625rem;
|
--status-warn: #8a7530;
|
||||||
--sidebar: oklch(0.985 0 0);
|
--chart-1: #c8842e;
|
||||||
--sidebar-foreground: oklch(0.145 0 0);
|
--chart-2: #3f8f5f;
|
||||||
--sidebar-primary: oklch(0.205 0 0);
|
--chart-3: #6b7280;
|
||||||
--sidebar-primary-foreground: oklch(0.985 0 0);
|
--chart-4: #8a7530;
|
||||||
--sidebar-accent: oklch(0.97 0 0);
|
--chart-5: #b3402e;
|
||||||
--sidebar-accent-foreground: oklch(0.205 0 0);
|
--radius: 0.5rem;
|
||||||
--sidebar-border: oklch(0.922 0 0);
|
--sidebar: #f5f6f7;
|
||||||
--sidebar-ring: oklch(0.708 0 0);
|
--sidebar-foreground: #15171a;
|
||||||
|
--sidebar-primary: #c8842e;
|
||||||
|
--sidebar-primary-foreground: #17130a;
|
||||||
|
--sidebar-accent: #ececee;
|
||||||
|
--sidebar-accent-foreground: #15171a;
|
||||||
|
--sidebar-border: #dfe1e3;
|
||||||
|
--sidebar-ring: #c8842e;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark {
|
.dark {
|
||||||
--background: oklch(0.145 0 0);
|
--background: #14161a;
|
||||||
--foreground: oklch(0.985 0 0);
|
--foreground: #edeef0;
|
||||||
--card: oklch(0.205 0 0);
|
--card: #1b1e23;
|
||||||
--card-foreground: oklch(0.985 0 0);
|
--card-foreground: #edeef0;
|
||||||
--popover: oklch(0.205 0 0);
|
--popover: #1b1e23;
|
||||||
--popover-foreground: oklch(0.985 0 0);
|
--popover-foreground: #edeef0;
|
||||||
--primary: oklch(0.922 0 0);
|
--primary: #e9a857;
|
||||||
--primary-foreground: oklch(0.205 0 0);
|
--primary-foreground: #1a1305;
|
||||||
--secondary: oklch(0.269 0 0);
|
--secondary: #23262c;
|
||||||
--secondary-foreground: oklch(0.985 0 0);
|
--secondary-foreground: #edeef0;
|
||||||
--muted: oklch(0.269 0 0);
|
--muted: #23262c;
|
||||||
--muted-foreground: oklch(0.708 0 0);
|
--muted-foreground: #9aa0a8;
|
||||||
--accent: oklch(0.269 0 0);
|
--accent: #23262c;
|
||||||
--accent-foreground: oklch(0.985 0 0);
|
--accent-foreground: #edeef0;
|
||||||
--destructive: oklch(0.704 0.191 22.216);
|
--destructive: #e2685a;
|
||||||
--border: oklch(1 0 0 / 10%);
|
--border: oklch(1 0 0 / 10%);
|
||||||
--input: oklch(1 0 0 / 15%);
|
--input: oklch(1 0 0 / 14%);
|
||||||
--ring: oklch(0.556 0 0);
|
--ring: #e9a857;
|
||||||
--chart-1: oklch(0.87 0 0);
|
--status-queued: #9aa0a8;
|
||||||
--chart-2: oklch(0.556 0 0);
|
--status-running: #e9a857;
|
||||||
--chart-3: oklch(0.439 0 0);
|
--status-succeeded: #5fb983;
|
||||||
--chart-4: oklch(0.371 0 0);
|
--status-failed: #e2685a;
|
||||||
--chart-5: oklch(0.269 0 0);
|
--status-cancelled: #9aa0a8;
|
||||||
--sidebar: oklch(0.205 0 0);
|
--status-warn: #c9a344;
|
||||||
--sidebar-foreground: oklch(0.985 0 0);
|
--chart-1: #e9a857;
|
||||||
--sidebar-primary: oklch(0.488 0.243 264.376);
|
--chart-2: #5fb983;
|
||||||
--sidebar-primary-foreground: oklch(0.985 0 0);
|
--chart-3: #9aa0a8;
|
||||||
--sidebar-accent: oklch(0.269 0 0);
|
--chart-4: #c9a344;
|
||||||
--sidebar-accent-foreground: oklch(0.985 0 0);
|
--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-border: oklch(1 0 0 / 10%);
|
||||||
--sidebar-ring: oklch(0.556 0 0);
|
--sidebar-ring: #e9a857;
|
||||||
}
|
}
|
||||||
|
|
||||||
@layer base {
|
@layer base {
|
||||||
|
|||||||
+3
-3
@@ -1,5 +1,5 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24">
|
||||||
<rect width="24" height="24" rx="5" fill="#09090b" />
|
<rect width="24" height="24" rx="5" fill="#15171a" />
|
||||||
<path d="M12 19h8" stroke="#fafafa" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none" />
|
<path d="M12 19h8" stroke="#e9a857" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none" />
|
||||||
<path d="m4 17 6-6-6-6" stroke="#fafafa" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none" />
|
<path d="m4 17 6-6-6-6" stroke="#e9a857" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none" />
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 386 B After Width: | Height: | Size: 386 B |
+16
-6
@@ -1,18 +1,28 @@
|
|||||||
import type { Metadata } from "next";
|
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 { ThemeProvider } from "next-themes";
|
||||||
import { TooltipProvider } from "@/components/ui/tooltip";
|
import { TooltipProvider } from "@/components/ui/tooltip";
|
||||||
import { Toaster } from "@/components/ui/sonner";
|
import { Toaster } from "@/components/ui/sonner";
|
||||||
import "./globals.css";
|
import "./globals.css";
|
||||||
|
|
||||||
const geistSans = Geist({
|
// Display face for page titles and card headings (wired via --font-heading in globals.css) - used
|
||||||
variable: "--font-geist-sans",
|
// 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"],
|
subsets: ["latin"],
|
||||||
});
|
});
|
||||||
|
|
||||||
const geistMono = Geist_Mono({
|
const sansFont = IBM_Plex_Sans({
|
||||||
variable: "--font-geist-mono",
|
variable: "--font-sans",
|
||||||
subsets: ["latin"],
|
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 = {
|
export const metadata: Metadata = {
|
||||||
@@ -31,7 +41,7 @@ export default function RootLayout({
|
|||||||
return (
|
return (
|
||||||
<html
|
<html
|
||||||
lang="en"
|
lang="en"
|
||||||
className={`${geistSans.variable} ${geistMono.variable} h-full antialiased`}
|
className={`${displayFont.variable} ${sansFont.variable} ${monoFont.variable} h-full antialiased`}
|
||||||
suppressHydrationWarning
|
suppressHydrationWarning
|
||||||
>
|
>
|
||||||
<body
|
<body
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ export function LoginForm() {
|
|||||||
<Card className="w-full max-w-sm">
|
<Card className="w-full max-w-sm">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Terminal className="size-5" />
|
<Terminal className="text-primary size-5" />
|
||||||
<CardTitle>TriggerShell</CardTitle>
|
<CardTitle>TriggerShell</CardTitle>
|
||||||
</div>
|
</div>
|
||||||
<CardDescription>
|
<CardDescription>
|
||||||
|
|||||||
@@ -34,9 +34,9 @@ export function Nav({
|
|||||||
<div className="flex min-w-0 items-center gap-3 sm:gap-6">
|
<div className="flex min-w-0 items-center gap-3 sm:gap-6">
|
||||||
<Link
|
<Link
|
||||||
href="/"
|
href="/"
|
||||||
className="flex shrink-0 items-center gap-2 font-semibold tracking-tight"
|
className="font-heading flex shrink-0 items-center gap-2 text-[1.05rem] font-semibold tracking-tight"
|
||||||
>
|
>
|
||||||
<Terminal className="size-5" />
|
<Terminal className="text-primary size-5" />
|
||||||
TriggerShell
|
TriggerShell
|
||||||
</Link>
|
</Link>
|
||||||
<nav className="flex items-center gap-1">
|
<nav className="flex items-center gap-1">
|
||||||
@@ -45,8 +45,8 @@ export function Nav({
|
|||||||
key={href}
|
key={href}
|
||||||
href={href}
|
href={href}
|
||||||
className={cn(
|
className={cn(
|
||||||
"text-muted-foreground hover:text-foreground flex items-center gap-1.5 rounded-md px-2 py-1.5 text-sm font-medium transition-colors sm:px-3",
|
"text-muted-foreground hover:text-foreground hover:bg-muted/60 flex items-center gap-1.5 rounded-md px-2 py-1.5 text-sm font-medium transition-colors sm:px-3",
|
||||||
pathname === href && "bg-muted text-foreground",
|
pathname === href && "bg-primary/10 text-foreground",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Icon className="size-4" />
|
<Icon className="size-4" />
|
||||||
|
|||||||
@@ -3,13 +3,13 @@ import type { RunStatus } from "@/lib/db/schema";
|
|||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
const styles: Record<RunStatus, string> = {
|
const styles: Record<RunStatus, string> = {
|
||||||
queued: "bg-muted text-muted-foreground",
|
queued: "bg-status-queued/12 text-status-queued",
|
||||||
running: "bg-blue-500/15 text-blue-600 dark:text-blue-400",
|
running: "bg-status-running/15 text-status-running",
|
||||||
succeeded: "bg-emerald-500/15 text-emerald-600 dark:text-emerald-400",
|
succeeded: "bg-status-succeeded/12 text-status-succeeded",
|
||||||
failed: "bg-destructive/15 text-destructive",
|
failed: "bg-status-failed/12 text-status-failed",
|
||||||
cancelled: "bg-muted text-muted-foreground",
|
cancelled: "bg-status-cancelled/12 text-status-cancelled",
|
||||||
timed_out: "bg-amber-500/15 text-amber-600 dark:text-amber-400",
|
timed_out: "bg-status-warn/12 text-status-warn",
|
||||||
interrupted: "bg-amber-500/15 text-amber-600 dark:text-amber-400",
|
interrupted: "bg-status-warn/12 text-status-warn",
|
||||||
};
|
};
|
||||||
|
|
||||||
export const runStatusLabels: Record<RunStatus, string> = {
|
export const runStatusLabels: Record<RunStatus, string> = {
|
||||||
@@ -25,12 +25,18 @@ export const runStatusLabels: Record<RunStatus, string> = {
|
|||||||
export function RunStatusBadge({ status }: { status: RunStatus }) {
|
export function RunStatusBadge({ status }: { status: RunStatus }) {
|
||||||
return (
|
return (
|
||||||
<Badge
|
<Badge
|
||||||
className={cn("border-transparent font-medium", styles[status])}
|
className={cn(
|
||||||
|
"gap-1.5 border-transparent font-mono text-[0.7rem] font-medium tracking-wide uppercase",
|
||||||
|
styles[status],
|
||||||
|
)}
|
||||||
variant="outline"
|
variant="outline"
|
||||||
>
|
>
|
||||||
{status === "running" && (
|
<span
|
||||||
<span className="mr-1 inline-block size-1.5 animate-pulse rounded-full bg-current" />
|
className={cn(
|
||||||
)}
|
"size-1.5 shrink-0 rounded-full bg-current",
|
||||||
|
status === "running" && "animate-pulse",
|
||||||
|
)}
|
||||||
|
/>
|
||||||
{runStatusLabels[status]}
|
{runStatusLabels[status]}
|
||||||
</Badge>
|
</Badge>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -74,13 +74,13 @@ export function RunTerminal({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-3">
|
<div className="border-border overflow-hidden rounded-lg border">
|
||||||
<div className="flex items-center justify-between">
|
<div className="bg-muted/40 flex items-center justify-between gap-3 border-b px-3 py-2">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<RunStatusBadge status={status} />
|
<RunStatusBadge status={status} />
|
||||||
{exitCode !== null && (
|
{exitCode !== null && (
|
||||||
<span className="text-muted-foreground text-xs">
|
<span className="text-muted-foreground font-mono text-xs">
|
||||||
exit code {exitCode}
|
exit {exitCode}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -10,9 +10,9 @@ export interface XtermViewHandle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const theme = {
|
const theme = {
|
||||||
background: "#09090b",
|
background: "#101215",
|
||||||
foreground: "#f4f4f5",
|
foreground: "#edeef0",
|
||||||
cursor: "#f4f4f5",
|
cursor: "#e9a857",
|
||||||
selectionBackground: "#3f3f46",
|
selectionBackground: "#3f3f46",
|
||||||
black: "#18181b",
|
black: "#18181b",
|
||||||
red: "#f87171",
|
red: "#f87171",
|
||||||
@@ -95,7 +95,7 @@ export const XtermView = forwardRef<XtermViewHandle, XtermViewProps>(
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={containerRef}
|
ref={containerRef}
|
||||||
className="h-[60vh] overflow-hidden rounded-lg bg-zinc-950 p-2"
|
className="h-[60vh] overflow-hidden bg-[#101215] p-2"
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user