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 .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,5 @@ jobs:
cache: npm
cache-dependency-path: package.json
- run: npm install
- run: npm test
- run: npm run build
37 changes: 37 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Changelog

All notable changes to Agent Pit Stop. The format follows [Keep a Changelog](https://keepachangelog.com); versions follow semver.

## [0.1.0] — 2026-07-30

The first release: a complete, principled, contract-generating design system for agent interfaces.

### Principles

- Five argued chapters with live embedded demos: Legible Thinking, Interruptibility, Delegation Contracts, Calibrated Trust, Graceful Failure
- "Rules for the pit" overview: philosophy, ten rules, anti-patterns, the headlights design language, and motion rules

### Components (10, shadcn-registry installable)

- **Legibility:** Tool Call Card, Agent Task List, Agent Roster
- **Control:** Approval Gate, Interrupt Bar, Agent Inbox, Diff Review Card
- **Trust:** Confidence Meter, Context Budget, Citation Chip + Source Drawer
- Every component: single file, React + Tailwind tokens + Motion only, `className` pass-through, 44px touch targets, states labeled in words

### Agent contract (generated, never hand-typed)

- `/r/<name>.json` registry items with full source; `/r/index.json` catalog; `/r/contract.json` dense mode with per-component props, principle tags, and five behavioral guarantees; `llms.txt` — all parsed from component source at build time

### Enforcement

- Interaction test suite asserting the contract's guarantees (stop preserves work, nothing irreversible without consent, bands not decimals, failures keep inputs, states never color alone); runs in CI on every PR

### Theming

- Twelve semantic tokens, dark (headlights) and light sets, both contrast-verified ≥ 4.5:1; retheme by overriding CSS variables, no component edits
- Dark/light toggle on every gallery card and component preview

### Site

- The Full Lap: every component driven through one interruptible scripted run
- How-it-works, contributor guide, component template, code of conduct, public roadmap as issues
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ Every component PR ships all five, or it isn't done:
## How changes land

1. Fork, branch from `main` (`feat/<name>` or `fix/<name>`).
2. `npm install && npm run dev` to work; `npm run build` must pass clean.
2. `npm install && npm run dev` to work; `npm test` and `npm run build` must pass clean. The test suite asserts the contract's behavioral guarantees — if your change fails one, the fix is the change, not the test.
3. Open a PR using the template. CI runs the build; a maintainer reviews for principle adherence, not just code.
4. Squash-merged. Releases roll weekly.

Expand Down
9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ Most agentic design writing covers architecture (orchestration, tool use, memory

## What's inside

- **Principles** — essays on agent UX with live demos: legible thinking, interruptibility, delegation contracts, calibrated trust, graceful failure
- **Components** — production-quality React components, installable via the shadcn registry or copy-paste: tool call card, approval gate, agent task list, and more
- **Resources** — teardowns of production agent UIs, a Figma library, and a glossary
- **Principles** — five argued essays on agent UX with live demos: legible thinking, interruptibility, delegation contracts, calibrated trust, graceful failure
- **Components** — ten production React components across Legibility, Control, and Trust, installable via the shadcn registry or copy-paste
- **The contract** — machine-readable and generated from component source at build time: [llms.txt](https://agent-pitstop.vercel.app/llms.txt), a [catalog](https://agent-pitstop.vercel.app/r/index.json), and a [dense contract](https://agent-pitstop.vercel.app/r/contract.json) with behavioral guarantees that the test suite asserts in CI
- **The Full Lap** — [every component driven through one interruptible agent run](https://agent-pitstop.vercel.app/full-lap)

## Install a component

Expand All @@ -29,7 +30,7 @@ npm run dev

## Status

Five principles, five components, three categories. One new chapter or component lands weekly. Watch the repo or follow [@acaspx](https://github.com/acaspx).
Five principles, ten components, three categories, dark and light themes, CI-enforced behavioral guarantees. One new piece lands weekly. Watch the repo or follow [@acaspx](https://github.com/acaspx).

## Contributing

Expand Down
52 changes: 52 additions & 0 deletions app/components/gallery.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
"use client";

import { useState } from "react";
import Link from "next/link";
import { componentCategories, componentsIn, type ComponentEntry } from "@/lib/nav";
import { ThemeToggle, type PreviewTheme } from "@/components/docs/theme-toggle";
import { minis } from "./gallery-minis";

function GalleryCard({ entry }: { entry: ComponentEntry }) {
const [theme, setTheme] = useState<PreviewTheme>("dark");

return (
<div className="group rounded-3xl border border-line bg-carbon p-5 transition-all duration-300 ease-out hover:-translate-y-1 hover:border-ash hover:shadow-[0_12px_32px_rgba(0,0,0,0.45)]">
<div
data-theme={theme === "light" ? "light" : undefined}
className="pointer-events-none rounded-2xl bg-track p-3 transition-colors duration-300"
>
{minis[entry.slug]}
</div>

<div className="mt-4 flex items-start justify-between gap-3">
<div className="min-w-0">
<Link
href={`/components/${entry.slug}`}
className="text-[15px] font-medium text-chalk transition-colors hover:text-pit"
>
{entry.title}
</Link>
<p className="mt-1 text-[13px] leading-relaxed text-smoke">{entry.description}</p>
</div>
<ThemeToggle value={theme} onChange={setTheme} className="mt-0.5" />
</div>
</div>
);
}

export function Gallery() {
return (
<>
{componentCategories.map((cat) => (
<section key={cat} className="mt-12">
<h2 className="text-sm font-medium uppercase tracking-wide text-ash">{cat}</h2>
<div className="dim-siblings mt-4 grid gap-5 md:grid-cols-2">
{componentsIn(cat).map((c) => (
<GalleryCard key={c.slug} entry={c} />
))}
</div>
</section>
))}
</>
);
}
33 changes: 6 additions & 27 deletions app/components/page.tsx
Original file line number Diff line number Diff line change
@@ -1,42 +1,21 @@
import Link from "next/link";
import { componentCategories, componentsIn } from "@/lib/nav";
import { minis } from "./gallery-minis";
import { Gallery } from "./gallery";

export const metadata = {
title: "Components — Agent Pit Stop",
description: "Every component, with live previews. Installable via the shadcn registry.",
description: "Every component, with live previews in both themes. Installable via the shadcn registry.",
};


export default function ComponentsOverview() {
return (
<main>
<h1 className="text-2xl font-semibold tracking-tight">Components</h1>
<p className="mt-2 max-w-prose text-[15px] leading-relaxed text-smoke">
Every component, live. Each installs with one shadcn command or copies as a single
file. Built with React 19, Tailwind 4, and Motion.
Every component, live. Flip any preview between dark and light: the components
never change, only the twelve tokens underneath them. Each installs with one
shadcn command or copies as a single file.
</p>

{componentCategories.map((cat) => (
<section key={cat} className="mt-12">
<h2 className="text-sm font-medium uppercase tracking-wide text-ash">{cat}</h2>
<div className="dim-siblings mt-4 grid gap-5 md:grid-cols-2">
{componentsIn(cat).map((c) => (
<Link
key={c.slug}
href={`/components/${c.slug}`}
className="group rounded-3xl border border-line bg-carbon p-5 transition-all duration-300 ease-out hover:-translate-y-1 hover:border-ash hover:shadow-[0_12px_32px_rgba(0,0,0,0.45)]"
>
<div className="pointer-events-none">{minis[c.slug]}</div>
<div className="mt-4 text-[15px] font-medium text-chalk group-hover:text-pit">
{c.title}
</div>
<div className="mt-1 text-[13px] leading-relaxed text-smoke">{c.description}</div>
</Link>
))}
</div>
</section>
))}
<Gallery />
</main>
);
}
16 changes: 16 additions & 0 deletions app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,22 @@ html {
background: color-mix(in srgb, var(--color-pit) 30%, transparent);
}

/* light theme: same tokens, daylight values. All text pairs verified >= 4.5:1. */
[data-theme="light"] {
--color-track: #f7f7f8;
--color-asphalt: #ffffff;
--color-carbon: #fbfbfc;
--color-barrier: #eceef1;
--color-line: #d9dbe1;
--color-chalk: #17171a;
--color-smoke: #4b4b55;
--color-ash: #6d6d78;
--color-signal: #047857;
--color-caution: #a16207;
--color-flag: #dc2626;
--color-pit: #b45309;
}

/* headlights language: faint dot-grid texture for large surfaces */
.bg-dots {
background-image: radial-gradient(color-mix(in srgb, var(--color-line) 55%, transparent) 1px, transparent 1px);
Expand Down
20 changes: 20 additions & 0 deletions app/how-it-works/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,26 @@ export default function HowItWorksPage() {
--color-signal: #34d399; /* success */
--color-caution: #fde047; /* attention */
--color-flag: #f87171; /* danger */
}`}
/>
</div>
<P>
Light mode is the same twelve tokens with daylight values, every text pair
verified at 4.5:1 or better. Scope them under a{" "}
<code className="rounded bg-asphalt px-1.5 py-0.5 font-mono text-[13px] text-chalk">data-theme=&quot;light&quot;</code>{" "}
attribute and toggle it anywhere in the tree; every gallery card and component
preview has a dark/light toggle so you can check both.
</P>
<div className="mt-4">
<CodeBlock
title="globals.css (light)"
code={`[data-theme="light"] {
--color-track: #f7f7f8; --color-asphalt: #ffffff;
--color-carbon: #fbfbfc; --color-barrier: #eceef1;
--color-line: #d9dbe1; --color-chalk: #17171a;
--color-smoke: #4b4b55; --color-ash: #6d6d78;
--color-pit: #b45309; --color-signal: #047857;
--color-caution: #a16207; --color-flag: #dc2626;
}`}
/>
</div>
Expand Down
8 changes: 7 additions & 1 deletion components/docs/preview-pane.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

import { useState, type ReactNode } from "react";
import type { Format } from "@/lib/nav";
import { ThemeToggle, type PreviewTheme } from "./theme-toggle";

const formatMeta: Record<Format, { label: string; icon: ReactNode }> = {
web: {
Expand Down Expand Up @@ -88,11 +89,13 @@ export interface PreviewPaneProps {

export function PreviewPane({ formats, children, variants, chatPrompt }: PreviewPaneProps) {
const [format, setFormat] = useState<Format>(formats[0] ?? "web");
const [theme, setTheme] = useState<PreviewTheme>("dark");
const content = variants?.[format] ?? children;

return (
<div>
<div className="mb-2 flex items-center justify-end gap-1">
<ThemeToggle value={theme} onChange={setTheme} className="mr-2" />
{formats.map((f) => (
<button
key={f}
Expand All @@ -112,7 +115,10 @@ export function PreviewPane({ formats, children, variants, chatPrompt }: Preview
))}
</div>

<div className="rounded-3xl border border-line bg-carbon bg-dots p-6">
<div
data-theme={theme === "light" ? "light" : undefined}
className="rounded-3xl border border-line bg-carbon bg-dots p-6 transition-colors duration-300"
>
{format === "mobile" ? (
<div className="mx-auto w-[300px] rounded-[2rem] border border-line bg-track p-3 pt-6 shadow-2xl">
<div className="mx-auto mb-3 h-1.5 w-16 rounded-full bg-barrier" aria-hidden />
Expand Down
42 changes: 42 additions & 0 deletions components/docs/theme-toggle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
"use client";

/**
* ThemeToggle — docs chrome, not a registry component.
* A two-up segmented control for previewing a component on either
* surface. Labeled in words, per the system's own rule: never let a
* state be readable by color alone.
*/

export type PreviewTheme = "dark" | "light";

export function ThemeToggle({
value,
onChange,
className,
}: {
value: PreviewTheme;
onChange: (t: PreviewTheme) => void;
className?: string;
}) {
return (
<div
role="group"
aria-label="Preview theme"
className={`inline-flex shrink-0 overflow-hidden rounded-lg border border-line ${className ?? ""}`}
>
{(["dark", "light"] as const).map((t) => (
<button
key={t}
type="button"
onClick={() => onChange(t)}
aria-pressed={value === t}
className={`min-h-8 px-2.5 font-mono text-[10px] uppercase tracking-wide transition-colors pointer-coarse:min-h-11 ${
value === t ? "bg-barrier text-chalk" : "text-ash hover:text-smoke"
}`}
>
{t}
</button>
))}
</div>
);
}
6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
"prebuild": "node scripts/build-registry.mjs",
"build": "next build",
"start": "next start",
"lint": "next lint"
"lint": "next lint",
"test": "vitest run"
},
"dependencies": {
"geist": "^1.4.0",
Expand All @@ -23,6 +24,9 @@
},
"devDependencies": {
"@tailwindcss/postcss": "^4.1.0",
"@testing-library/react": "^16.1.0",
"jsdom": "^25.0.0",
"vitest": "^2.1.0",
"@types/mdx": "^2.0.13",
"@types/node": "^22.0.0",
"@types/react": "^19.0.0",
Expand Down
Loading
Loading