Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ coverage.xml
*.cover
*.py.cover
.hypothesis/
apps/ui/coverage/
.pytest_cache/
cover/

Expand Down
2 changes: 0 additions & 2 deletions apps/ui/app/automation/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ export const metadata = { title: "Automation · clevis" }

import { PageHeader } from "@/components/page-header"
import { EmptyState } from "@/components/empty-state"
import { Zap } from "lucide-react"

export default function AutomationPage() {
return (
Expand All @@ -13,7 +12,6 @@ export default function AutomationPage() {
/>
<div className="bg-card border border-border">
<EmptyState
icon={Zap}
title="Automation coming soon"
description="Trigger GitHub Actions workflows, dispatch events, and automate repo hygiene tasks — all from one place."
/>
Expand Down
2 changes: 0 additions & 2 deletions apps/ui/app/collaborators/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ export const metadata = { title: "Collaborators · clevis" }

import { PageHeader } from "@/components/page-header"
import { EmptyState } from "@/components/empty-state"
import { Users } from "lucide-react"

export default function CollaboratorsPage() {
return (
Expand All @@ -13,7 +12,6 @@ export default function CollaboratorsPage() {
/>
<div className="bg-card border border-border">
<EmptyState
icon={Users}
title="Collaborators coming soon"
description="View your organization roster, manage pending invitations, and fix member permissions from a single dashboard."
/>
Expand Down
181 changes: 162 additions & 19 deletions apps/ui/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
@theme inline {
--font-sans: var(--font-sans);
--font-mono: var(--font-mono);
--font-heading: var(--font-sans);
--font-heading: var(--font-heading);
--color-sidebar-ring: var(--sidebar-ring);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
Expand Down Expand Up @@ -45,6 +45,11 @@
--color-subtle-foreground: var(--subtle-foreground);
--color-border-muted: var(--border-muted);
--color-warning: var(--warning);
--color-alert: var(--alert);
/* Motion — strong custom easings (built-in CSS easings are too weak) */
--ease-out: var(--ease-out);
--ease-in-out: var(--ease-in-out);
--ease-drawer: var(--ease-drawer);
--radius-sm: calc(var(--radius) * 0.5);
--radius-md: var(--radius);
--radius-lg: calc(var(--radius) * 2);
Expand Down Expand Up @@ -78,8 +83,9 @@
/* Semantic */
--accent: #22c55e;
--accent-foreground: #0a0a0a;
--destructive: #ef4444;
--destructive: #e61919; /* aviation / hazard red — critical + alert accent */
--warning: #f59e0b;
--alert: var(--destructive); /* structural critical accent (dividers, failed status) */

/* Sidebar */
--sidebar: #060606;
Expand Down Expand Up @@ -107,11 +113,16 @@
--chart-4: #ef4444; /* red — negative */
--chart-5: #a78bfa; /* violet — tertiary (charts only, not UI) */

/* Sharp corners — terminal tools don't have soft corners */
--radius: 0.1875rem; /* 3px */
/* Square corners — tactical-telemetry surfaces reject border-radius */
--radius: 0;

/* Mono stack — system fonts, no custom load */
--font-mono: ui-monospace, "SF Mono", "Cascadia Code", "Fira Code", Consolas, "Courier New", monospace;
/* Motion — strong custom easing curves (emil-design-eng / impeccable: exponential ease-out) */
--ease-out: cubic-bezier(0.23, 1, 0.32, 1); /* enter/exit, press release */
--ease-in-out: cubic-bezier(0.77, 0, 0.175, 1); /* on-screen morph (sidebar collapse) */
--ease-drawer: cubic-bezier(0.32, 0.72, 0, 1); /* drawer / sheet slide */

/* Mono stack — JetBrains Mono (loaded in layout) with system fallbacks */
--font-mono: var(--font-jetbrains-mono), ui-monospace, "SF Mono", "Cascadia Code", "Fira Code", Consolas, "Courier New", monospace;
}

/*
Expand Down Expand Up @@ -220,7 +231,7 @@
--ring: #2563eb;
--accent: #16a34a;
--accent-foreground: #ffffff;
--destructive: #dc2626;
--destructive: #e61919;
--warning: #d97706;
--card-foreground: #18181b;
--popover: #ffffff;
Expand Down Expand Up @@ -255,7 +266,7 @@
--ring: #2563eb;
--accent: #16a34a;
--accent-foreground: #ffffff;
--destructive: #dc2626;
--destructive: #e61919;
--warning: #d97706;
--card-foreground: #1e2630;
--popover: #f8fafc;
Expand Down Expand Up @@ -288,6 +299,25 @@
html {
@apply font-sans;
}

/*
* Reduced motion is not optional (emil-design-eng / impeccable / redesign skills).
* Suppress movement + analog texture; keep opacity/content legible.
*/
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
.scanlines::after,
.noise-overlay {
display: none !important;
}
}
}

@layer utilities {
Expand All @@ -309,30 +339,143 @@
* Replaces the old .section-title. Used across Linear, Vercel, Raycast.
* Reads "engineering dashboard", not "generic page".
*/
.section-label {
/* .section-title kept as an alias during the transition */
.section-label,
.section-title {
font-size: 0.6875rem;
font-weight: 500;
text-transform: uppercase;
letter-spacing: 0.08em;
letter-spacing: 0.1em;
color: var(--muted-foreground);
font-family: var(--font-sans);
font-family: var(--font-mono);
}

/* Keep old name as alias during the transition */
.section-title {
.card {
@apply bg-card border border-border;
}

.stat-chip {
@apply inline-flex items-center gap-1 text-xs border border-border px-1.5 py-0.5 text-muted-foreground font-mono;
}

/*
* Telemetry label — monospace uppercase metadata (unit IDs, coordinates, field
* labels). The brutalist micro-typography register.
*/
.telemetry-label {
font-family: var(--font-mono);
font-size: 0.6875rem;
font-weight: 500;
text-transform: uppercase;
letter-spacing: 0.08em;
letter-spacing: 0.1em;
color: var(--muted-foreground);
font-family: var(--font-sans);
}

.card {
@apply bg-card border border-border;
/*
* Macro heading — heavy neo-grotesque (Archivo) at structural scale, tight
* tracking, compressed leading. The "telemetry header" moment on page headers.
*/
.macro-heading {
font-family: var(--font-heading);
font-weight: 800;
letter-spacing: -0.03em;
line-height: 0.95;
text-wrap: balance;
}

.stat-chip {
@apply inline-flex items-center gap-1 text-xs border border-border px-1.5 py-0.5 text-muted-foreground font-mono;
/* ASCII data framing — [ LABEL ]. Decorative; purely cosmetic brackets. */
.bracket::before {
content: "[ ";
color: var(--subtle-foreground);
}
.bracket::after {
content: " ]";
color: var(--subtle-foreground);
}

/*
* Blueprint grid — razor-thin 1px dividers via grid gap + contrasting bg.
* Children sit on --card; the gap reveals --border. (brutalist grid determinism)
*/
.grid-hairline {
display: grid;
gap: 1px;
background-color: var(--border);
}
.grid-hairline > * {
background-color: var(--card);
}

/* Press feedback — subtle scale, instant-feeling release (emil-design-eng). */
.press {
transition: transform 160ms var(--ease-out);
}
.press:active {
transform: scale(0.97);
}

/*
* Card hover elevation — gated to real pointers so touch taps don't false-trigger.
* Apply only to clickable/navigational cards (impeccable: cards = hierarchy).
*/
@media (hover: hover) and (pointer: fine) {
.card-interactive {
transition: border-color 200ms var(--ease-out), background-color 200ms var(--ease-out);
}
.card-interactive:hover {
border-color: var(--border);
background-color: var(--elevated);
}
}

/* Staggered list entrance — opacity + small rise, short per-item delay. */
.stagger-item {
opacity: 0;
animation: stagger-in 320ms var(--ease-out) forwards;
}
@keyframes stagger-in {
from {
opacity: 0;
transform: translateY(8px);
}
to {
opacity: 1;
transform: none;
}
}

/*
* CRT scanlines — applied to specific terminal panels only (Job Queue / Activity),
* never global scroll containers. Suppressed under reduced motion (see base layer).
*/
.scanlines {
position: relative;
}
.scanlines::after {
content: "";
position: absolute;
inset: 0;
pointer-events: none;
background: repeating-linear-gradient(
0deg,
transparent,
transparent 2px,
color-mix(in srgb, var(--foreground) 3%, transparent) 2px,
color-mix(in srgb, var(--foreground) 3%, transparent) 4px
);
z-index: 1;
}

/*
* Global mechanical noise — single fixed, pointer-events-none grain overlay
* rendered once in layout.tsx. Low opacity to break digital flatness.
*/
.noise-overlay {
position: fixed;
inset: 0;
pointer-events: none;
z-index: 40;
opacity: 0.025;
background-image: 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.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
8 changes: 4 additions & 4 deletions apps/ui/app/invite/[token]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { useParams, useRouter } from "next/navigation"
import { useMutation, useQuery } from "@tanstack/react-query"
import { PageHeader } from "@/components/page-header"
import { Button } from "@/components/ui/button"
import { AlertTriangle, CheckCircle2, Loader2 } from "lucide-react"
import { Warning, CheckCircle, CircleNotch } from "@phosphor-icons/react"
import { api } from "@/lib/api/client"
import { useAuth } from "@/lib/auth-context"

Expand Down Expand Up @@ -32,11 +32,11 @@ export default function InviteAcceptPage() {
<div className="p-4 flex flex-col gap-3">
{isLoading || authLoading ? (
<div className="flex items-center gap-2 text-sm text-muted-foreground">
<Loader2 className="size-3.5 animate-spin" /> Loading…
<CircleNotch className="size-3.5 animate-spin" /> Loading…
</div>
) : isError || !preview ? (
<p className="text-sm text-destructive flex items-center gap-1.5">
<AlertTriangle className="size-3.5" />
<Warning className="size-3.5" />
{error instanceof Error ? error.message : "Invitation not found"}
</p>
) : preview.status !== "pending" ? (
Expand All @@ -61,7 +61,7 @@ export default function InviteAcceptPage() {
</p>
) : accept.isSuccess ? (
<p className="text-sm text-primary flex items-center gap-1.5">
<CheckCircle2 className="size-3.5" /> Joined {preview.org_login} — redirecting…
<CheckCircle className="size-3.5" /> Joined {preview.org_login} — redirecting…
</p>
) : (
<Button onClick={() => accept.mutate()} disabled={accept.isPending}>
Expand Down
46 changes: 37 additions & 9 deletions apps/ui/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,32 @@
import "@/app/globals.css"
import { Inter } from "next/font/google"
import { Geist, Archivo, JetBrains_Mono } from "next/font/google"
import { cn } from "@/lib/utils"
import { TooltipProvider } from "@/components/ui/tooltip"
import { QueryProvider } from "@/components/query-provider"
import { AuthProvider } from "@/lib/auth-context"
import { AuthGuard } from "@/components/auth-guard"
import { ShellRouter } from "@/components/shell-router"
import { IconProvider } from "@/components/icon-provider"

const inter = Inter({
// Body / UI — Geist replaces Inter (an AI default per the design skills).
const geist = Geist({
subsets: ["latin"],
weight: ["300", "400", "500", "600", "700"],
variable: "--font-sans",
})

// Macro-typography — heavy neo-grotesque for telemetry headers (Archivo Black-ish).
const archivo = Archivo({
subsets: ["latin"],
weight: ["500", "600", "700", "800", "900"],
variable: "--font-heading",
})

// Data / telemetry — JetBrains Mono for stat values, IDs, timestamps, code.
const jetbrainsMono = JetBrains_Mono({
subsets: ["latin"],
variable: "--font-jetbrains-mono",
})

export const metadata = {
title: "Overview · clevis",
description: "GitHub analytics and cache management",
Expand All @@ -24,18 +38,32 @@ const themeScript = `(function(){try{var t=localStorage.getItem('clevis:theme')|

export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en" className={cn("dark font-sans", inter.variable)} data-theme="midnight" suppressHydrationWarning>
<html
lang="en"
className={cn(
"dark font-sans",
geist.variable,
archivo.variable,
jetbrainsMono.variable
)}
data-theme="midnight"
suppressHydrationWarning
>
<head>
<script dangerouslySetInnerHTML={{ __html: themeScript }} />
</head>
<body className="min-h-screen bg-background">
{/* Mechanical grain — single fixed overlay; suppressed under reduced motion */}
<div className="noise-overlay" aria-hidden="true" />
<QueryProvider>
<TooltipProvider>
<AuthProvider>
<AuthGuard>
<ShellRouter>{children}</ShellRouter>
</AuthGuard>
</AuthProvider>
<IconProvider>
<AuthProvider>
<AuthGuard>
<ShellRouter>{children}</ShellRouter>
</AuthGuard>
</AuthProvider>
</IconProvider>
</TooltipProvider>
</QueryProvider>
</body>
Expand Down
Loading
Loading