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
11 changes: 11 additions & 0 deletions CODE_OF_CONDUCT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Code of Conduct

The pit crew works fast because it trusts each other. Same rules here.

**Expected:** Be direct about the work and generous with the person. Critique components, patterns, and prose as hard as you like; never the human who made them. Assume competence and good faith, especially across the designer/engineer divide this project exists to bridge. Scars and real product stories are the most valued contribution; share yours and respect others'.

**Not tolerated:** Harassment, discrimination, or personal attacks of any kind; gatekeeping ("that's not real engineering / real design"); sustained disruption of discussions; publishing others' private information.

**Enforcement:** Report conduct issues to ac.design.px@gmail.com. The maintainer will review and respond within a week. Consequences scale from a warning to a permanent ban from the project's spaces, at the maintainer's judgment.

Adapted in spirit from the [Contributor Covenant](https://www.contributor-covenant.org/) v2.1.
4 changes: 4 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ Thanks for pulling into the pit. This doc is the whole contract: what a contribu
- **Fixes** — bugs, typos, broken demos. No proposal needed; just PR.
- **Principle chapters** are held to the highest bar: a real claim plus a working component as evidence. Open a discussion before writing one.

## Start from the template

Copy `registry/_template/component-name.tsx` — it encodes every convention below (tokens, states-in-words, tap targets, className pass-through) so your first five minutes are building, not reverse-engineering.

## Definition of done (components)

Every component PR ships all five, or it isn't done:
Expand Down
37 changes: 37 additions & 0 deletions app/how-it-works/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,43 @@ export default function HowItWorksPage() {
product; what happens when a human leans in is what we make excellent.
</P>

<H2>Make it yours</H2>
<P>
Every component speaks in semantic tokens, never raw values. That means the
whole system rethemes from one place: define the tokens in your Tailwind theme
with your own values, and every card, gate, and meter follows. No wrapping, no
forking, no component edits.
</P>
<div className="mt-4">
<CodeBlock
title="globals.css"
code={`@theme {
/* surfaces */
--color-track: #0a0a0b; /* page */
--color-asphalt: #131316; /* component surface */
--color-carbon: #0e0e10; /* card surface */
--color-barrier: #1f1f24; /* raised surface */
--color-line: #2c2c33; /* borders */
/* text */
--color-chalk: #ececf1; /* primary */
--color-smoke: #9a9aa3; /* secondary */
--color-ash: #7f7f8a; /* tertiary (keep >= 4.5:1) */
/* signals */
--color-pit: #60a5fa; /* accent: swap for your brand */
--color-signal: #34d399; /* success */
--color-caution: #fbbf24; /* attention */
--color-flag: #f87171; /* danger */
}`}
/>
</div>
<P>
Past tokens, the path escalates one step at a time: pass{" "}
<code className="rounded bg-asphalt px-1.5 py-0.5 font-mono text-[13px] text-chalk">className</code>{" "}
for layout and overrides, or take the single source file and own it entirely.
Keep the contrast honest when you retheme; the tokens above all clear WCAG AA on
their surfaces.
</P>

<H2>The name</H2>
<P>
A pit stop is the moment a fast autonomous thing submits to human hands: twelve
Expand Down
90 changes: 58 additions & 32 deletions app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,40 @@ import Link from "next/link";
import { PitFlag } from "@/components/docs/pit-flag";
import { TrackLoop } from "@/components/docs/track-loop";
import { Reveal } from "@/components/docs/reveal";
import { minis } from "@/app/components/gallery-minis";

const principles = [
{ title: "Legible thinking", href: "/principles/legible-thinking", status: "Live" },
{ title: "Interruptibility", href: "/principles/interruptibility", status: "Live" },
{ title: "Delegation contracts", href: "/principles/delegation-contracts", status: "Live" },
{ title: "Calibrated trust", href: "/principles/calibrated-trust", status: "Live" },
{ title: "Graceful failure", href: "/principles/graceful-failure", status: "Live" },
{
title: "Legible thinking",
claim: "Agents don't lose trust by being wrong. They lose it by being wrong invisibly.",
href: "/principles/legible-thinking",
},
{
title: "Interruptibility",
claim: "Users delegate more to agents they can stop.",
href: "/principles/interruptibility",
},
{
title: "Delegation contracts",
claim: "Delegation fails at the handoff, not at execution.",
href: "/principles/delegation-contracts",
},
{
title: "Calibrated trust",
claim: "The goal is accurate trust, not more trust.",
href: "/principles/calibrated-trust",
},
{
title: "Graceful failure",
claim: "Software fails like glass. Agents fail like weather.",
href: "/principles/graceful-failure",
},
];

const components = [
{ title: "Tool Call Card", href: "/components/tool-call-card", status: "Live" },
{ title: "Approval Gate", href: "/components/approval-gate", status: "Live" },
{ title: "Agent Task List", href: "/components/agent-task-list", status: "Live" },
{ title: "Interrupt Bar", href: "/components/interrupt-bar", status: "Live" },
{ title: "Confidence Meter", href: "/components/confidence-meter", status: "Live" },
{ title: "Agent Roster", href: "/components/agent-roster", status: "Live" },
{ title: "Agent Inbox", href: "/components/agent-inbox", status: "Live" },
{ title: "Context Budget", href: "/components/context-budget", status: "Live" },
const teasers = [
{ slug: "tool-call-card", title: "Tool Call Card", category: "Legibility" },
{ slug: "approval-gate", title: "Approval Gate", category: "Control" },
{ slug: "confidence-meter", title: "Confidence Meter", category: "Trust" },
];

/* two-tone icons in the site accent */
Expand Down Expand Up @@ -81,17 +97,13 @@ const heroCards = [
},
];

function Row({ title, href, status }: { title: string; href: string | null; status: string }) {
function PrincipleRow({ title, claim, href }: { title: string; claim: string; href: string }) {
return (
<li className="flex items-center justify-between py-3">
{href ? (
<Link href={href} className="text-[15px] text-chalk hover:text-pit">
{title}
</Link>
) : (
<span className="text-[15px] text-smoke">{title}</span>
)}
<span className={`text-[12px] ${status === "Live" ? "text-signal" : "text-ash"}`}>{status}</span>
<li>
<Link href={href} className="group block py-3.5">
<span className="text-[15px] text-chalk group-hover:text-pit">{title}</span>
<span className="mt-0.5 block text-[13px] leading-relaxed text-smoke">{claim}</span>
</Link>
</li>
);
}
Expand Down Expand Up @@ -176,27 +188,41 @@ export default function Home() {
</h2>
<ul className="divide-y divide-line">
{principles.map((p) => (
<Row key={p.title} {...p} />
<PrincipleRow key={p.title} {...p} />
))}
</ul>
</section>
</Reveal>

<Reveal delay={0.1}>
<section className="mt-16 grid gap-4 md:grid-cols-2 md:gap-12">
<div className="flex items-start justify-between md:flex-col md:gap-3">
<section className="mt-20">
<div className="flex items-center justify-between">
<h2 className="font-mono text-sm font-medium uppercase tracking-[0.2em] text-ash">
Components
</h2>
<Link href="/components" className="text-[13px] text-smoke hover:text-pit">
Browse all →
Browse all 8
</Link>
</div>
<ul className="divide-y divide-line">
{components.map((c) => (
<Row key={c.title} {...c} />
<div className="mt-4 grid gap-3 md:grid-cols-3">
{teasers.map((t) => (
<Link
key={t.slug}
href={`/components/${t.slug}`}
className="group flex flex-col gap-4 rounded-2xl border border-line bg-carbon p-5 transition-colors hover:border-ash"
>
<div className="pointer-events-none">{minis[t.slug]}</div>
<div className="mt-auto flex items-baseline justify-between">
<span className="text-[14px] font-medium text-chalk group-hover:text-pit">
{t.title}
</span>
<span className="font-mono text-[11px] uppercase tracking-wide text-ash">
{t.category}
</span>
</div>
</Link>
))}
</ul>
</div>
</section>
</Reveal>

Expand Down
63 changes: 63 additions & 0 deletions registry/_template/component-name.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
"use client";

/**
* ComponentName — Agent Pit Stop
* One sentence: the sync moment this serves and the stance it takes.
*
* Principles applied:
* - <Principle>: how this component embodies it, concretely
* - <Principle>: state is never color alone; failures keep their inputs
*
* Contributor notes (delete before submitting):
* - Single file. Dependencies: React, Tailwind tokens, Motion. Nothing else.
* - Tokens only: bg-asphalt / bg-carbon surfaces, text-chalk / text-smoke /
* text-ash text, border-line, accents pit / signal / caution / flag.
* - Every state gets an icon or label beside its color.
* - Action buttons: min-h-9 + pointer-coarse:min-h-11.
* - Accept className, merged onto the root.
* - Pair with app/components/<name>/{demo,page}.tsx, register in lib/nav.ts
* and scripts/build-registry.mjs. See CONTRIBUTING.md for the full
* definition of done.
*/

import { useState } from "react";
import { motion } from "motion/react";

export type ComponentNameState = "idle" | "active";

export interface ComponentNameProps {
/** Document every prop with the concrete example it expects */
label: string;
state?: ComponentNameState;
/** Extra classes merged onto the root element */
className?: string;
}

export function ComponentName({ label, state: controlled, className }: ComponentNameProps) {
const [internal, setInternal] = useState<ComponentNameState>("idle");
const state = controlled ?? internal;

return (
<div className={`w-full rounded-xl border border-line bg-asphalt px-4 py-3 ${className ?? ""}`}>
<div className="flex items-center justify-between gap-3">
<span className="text-[13px] text-chalk">{label}</span>
<button
type="button"
onClick={() => setInternal(state === "idle" ? "active" : "idle")}
className="min-h-9 rounded-lg border border-line px-3 py-1.5 text-[13px] font-medium text-smoke transition-colors hover:text-chalk pointer-coarse:min-h-11"
>
{state === "idle" ? "Start" : "Stop"}
</button>
</div>
{state === "active" && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
className="mt-2 text-[12px] text-smoke"
>
Active. States are words, never color alone.
</motion.div>
)}
</div>
);
}
6 changes: 4 additions & 2 deletions registry/agent-inbox/agent-inbox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ export interface AgentInboxProps {
/** Called when the human opens a run for review */
onOpen?: (id: string) => void;
label?: string;
/** Extra classes merged onto the root element */
className?: string;
}

const statusMeta: Record<InboxStatus, { label: string; dot: string; text: string }> = {
Expand All @@ -45,11 +47,11 @@ const statusMeta: Record<InboxStatus, { label: string; dot: string; text: string
failed: { label: "Failed", dot: "bg-flag", text: "text-flag" },
};

export function AgentInbox({ items, onOpen, label = "Agent inbox" }: AgentInboxProps) {
export function AgentInbox({ items, onOpen, label = "Agent inbox", className }: AgentInboxProps) {
const needsYou = items.filter((i) => i.status === "needs_review").length;

return (
<div className="w-full rounded-xl border border-line bg-asphalt">
<div className={`w-full rounded-xl border border-line bg-asphalt ${className ?? ""}`}>
<div className="flex items-center justify-between border-b border-line px-4 py-2.5">
<span className="text-[13px] font-medium text-chalk">{label}</span>
<span className="font-mono text-[12px] text-smoke">
Expand Down
6 changes: 4 additions & 2 deletions registry/agent-roster/agent-roster.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ export interface RosterAgent {
export interface AgentRosterProps {
agents: RosterAgent[];
label?: string;
/** Extra classes merged onto the root element */
className?: string;
}

const stateMeta: Record<AgentState, { label: string; text: string; pulse: boolean }> = {
Expand All @@ -59,11 +61,11 @@ function StateDot({ state }: { state: AgentState }) {
);
}

export function AgentRoster({ agents, label = "Crew" }: AgentRosterProps) {
export function AgentRoster({ agents, label = "Crew", className }: AgentRosterProps) {
const working = agents.filter((a) => a.state === "working").length;

return (
<div className="w-full rounded-xl border border-line bg-asphalt">
<div className={`w-full rounded-xl border border-line bg-asphalt ${className ?? ""}`}>
<div className="flex items-center justify-between border-b border-line px-4 py-2.5">
<span className="text-[13px] font-medium text-chalk">{label}</span>
<span className="font-mono text-[12px] text-smoke">
Expand Down
6 changes: 4 additions & 2 deletions registry/agent-task-list/agent-task-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ export interface AgentTaskListProps {
/** Short label for the overall plan, e.g. "Booking your trip" */
label?: string;
tasks: AgentTask[];
/** Extra classes merged onto the root element */
className?: string;
}

function StatusIcon({ status }: { status: AgentTaskStatus }) {
Expand Down Expand Up @@ -82,11 +84,11 @@ function StatusIcon({ status }: { status: AgentTaskStatus }) {
);
}

export function AgentTaskList({ label, tasks }: AgentTaskListProps) {
export function AgentTaskList({ label, tasks, className }: AgentTaskListProps) {
const done = tasks.filter((t) => t.status === "done").length;

return (
<div className="w-full rounded-xl border border-line bg-asphalt">
<div className={`w-full rounded-xl border border-line bg-asphalt ${className ?? ""}`}>
{label && (
<div className="flex items-center justify-between border-b border-line px-4 py-2.5">
<span className="text-[13px] font-medium text-chalk">{label}</span>
Expand Down
5 changes: 4 additions & 1 deletion registry/approval-gate/approval-gate.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ export interface ApprovalGateProps {
onDeny?: () => void;
approveLabel?: string;
denyLabel?: string;
/** Extra classes merged onto the root element */
className?: string;
}

export function ApprovalGate({
Expand All @@ -50,6 +52,7 @@ export function ApprovalGate({
onDeny,
approveLabel = "Approve",
denyLabel = "Deny",
className,
}: ApprovalGateProps) {
const [internal, setInternal] = useState<ApprovalState>("awaiting");
const state = controlled ?? internal;
Expand All @@ -60,7 +63,7 @@ export function ApprovalGate({
};

return (
<div className="w-full overflow-hidden rounded-xl border border-line bg-asphalt">
<div className={`w-full overflow-hidden rounded-xl border border-line bg-asphalt ${className ?? ""}`}>
<AnimatePresence mode="wait" initial={false}>
{state === "awaiting" ? (
<motion.div
Expand Down
5 changes: 4 additions & 1 deletion registry/confidence-meter/confidence-meter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ export interface ConfidenceMeterProps {
threshold?: number;
onVerify?: () => void;
verifyLabel?: string;
/** Extra classes merged onto the root element */
className?: string;
}

export function bandOf(confidence: number): ConfidenceBand {
Expand All @@ -50,13 +52,14 @@ export function ConfidenceMeter({
threshold = 0.75,
onVerify,
verifyLabel = "Check this",
className,
}: ConfidenceMeterProps) {
const band = bandOf(confidence);
const meta = bandMeta[band];
const needsVerification = confidence < threshold;

return (
<div className="w-full rounded-xl border border-line bg-asphalt px-4 py-3">
<div className={`w-full rounded-xl border border-line bg-asphalt px-4 py-3 ${className ?? ""}`}>
<div className="flex items-start justify-between gap-4">
<div className="min-w-0">
<div className="text-[13px] leading-snug text-chalk">{claim}</div>
Expand Down
Loading
Loading