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
14 changes: 14 additions & 0 deletions .github/ISSUE_TEMPLATE/bug_report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
name: Bug report
about: Something broken in a component, demo, or the site
title: "bug: <short description>"
labels: bug
---

## What happened

## What you expected

## Where

<!-- Component/page + browser/OS. A screenshot or recording helps a lot. -->
26 changes: 26 additions & 0 deletions .github/ISSUE_TEMPLATE/component-proposal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
---
name: Component proposal
about: Propose a new component for an agent↔human sync moment
title: "proposal: <component name>"
labels: component, proposal
---

## The sync moment

<!-- What agent↔human moment does this serve? Approval, inspection, handoff, recovery, trust? -->

## Where you've needed it

<!-- The real product situation that made you want this. Scars welcome. -->

## Proposed behavior

<!-- States, key interactions, what the human can do with it. Sketches or references help. -->

## Which principles it applies

<!-- Legible Thinking / Interruptibility / Delegation Contracts / Calibrated Trust / Graceful Failure -->

## Formats

<!-- web / mobile / chat / cli — which apply? -->
23 changes: 23 additions & 0 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
## What

<!-- One sentence: what does this PR do? -->

## Why

<!-- The problem or sync moment this addresses. Link the issue if one exists. -->

## Definition of done (components only)

- [ ] Single-file registry component (React + Tailwind + Motion only)
- [ ] Interactive demo showing every state
- [ ] Doc page (intro, when to use, behavior, usage)
- [ ] Registered in `lib/nav.ts` and `scripts/build-registry.mjs`
- [ ] Header comment cites the principles applied
- [ ] `npm run build` passes clean

## Principle check

- [ ] State is never communicated by color alone
- [ ] Failures keep their inputs visible
- [ ] Nothing irreversible happens without consent
- [ ] Any confidence display uses bands, not decimals
20 changes: 20 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
name: CI

on:
pull_request:
branches: [main]
push:
branches: [main]

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
cache-dependency-path: package.json
- run: npm install
- run: npm run build
43 changes: 43 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Contributing to Agent Pit Stop

Thanks for pulling into the pit. This doc is the whole contract: what a contribution needs to be, and how it gets in.

## What we accept

- **New components** — patterns for agent↔human sync moments. Propose first via a [component proposal issue](.github/ISSUE_TEMPLATE/component-proposal.md) so we agree on scope before you build.
- **Improvements** to existing components: accessibility, states, API clarity, motion.
- **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.

## Definition of done (components)

Every component PR ships all five, or it isn't done:

1. **Registry component** — one self-contained file in `registry/<name>/<name>.tsx`. Dependencies: React, Tailwind, Motion. Nothing else.
2. **Demo** — `app/components/<name>/demo.tsx`, interactive, showing every state.
3. **Doc page** — `app/components/<name>/page.tsx` using `ComponentDoc`: intro, when to use, behavior, usage snippet.
4. **Registrations** — entry in `lib/nav.ts` (category + supported formats) and `scripts/build-registry.mjs` (title + description).
5. **Principles applied** — a header comment citing which principles the component applies, and honest adherence to them: state never by color alone, failures keep their inputs, interruptions acknowledged.

## Code standards

- TypeScript strict; exported props interfaces with doc comments on every prop.
- Design tokens only (`bg-asphalt`, `text-smoke`, `var(--color-pit)`); no hardcoded colors or spacing.
- Accessible by default: keyboard operable, `aria-*` where state changes, honest labels.
- Motion is choreography, not decoration: durations ≤ 400ms, respect the existing easing vocabulary.
- Components must work in every format they declare (`web`, `mobile`, `chat`, `cli`).

## 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.
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.

## Writing standards (docs and chapters)

Concise and concrete. Every sentence earns the next; no filler, no "delightful experiences." Claims need either a demo or a scar behind them. If a paragraph could appear in any design system's docs, cut it.

## Questions

Open a [discussion](https://github.com/acaspx/agent-pitstop/discussions) or an issue. Telling us which sync moment your product struggles with is itself a contribution; it drives the roadmap.
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,11 @@ npm run dev

## Status

Early. v0.1 ships with 2 principles and 3 components. One new chapter or component lands weekly. Watch the repo or follow [@acaspx](https://github.com/acaspx).
Five principles, five components, three categories. One new chapter or component lands weekly. Watch the repo or follow [@acaspx](https://github.com/acaspx).

## Contributing

Component proposals, fixes, and scars from real agent products are all welcome. Start with [CONTRIBUTING.md](./CONTRIBUTING.md) — it's short, and the definition of done keeps the system clean as it grows.

## License

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

import { useState } from "react";
import { AgentInbox, type InboxItem } from "@/registry/agent-inbox/agent-inbox";

const initial: InboxItem[] = [
{
id: "1",
title: "Reconcile March invoices",
summary: "14 matched, 2 need a decision",
status: "needs_review",
reason: "2 matches below confidence threshold",
time: "2h ago",
},
{
id: "2",
title: "Draft outreach for 8 leads",
summary: "Drafts ready, held before sending",
status: "needs_review",
reason: "sending email is irreversible",
time: "4h ago",
},
{
id: "3",
title: "Weekly competitor scan",
summary: "Reading 12 changelog pages…",
status: "running",
time: "now",
},
{
id: "4",
title: "Archive stale tickets",
summary: "62 archived, log attached",
status: "done",
time: "1d ago",
},
];

export function AgentInboxDemo() {
const [opened, setOpened] = useState<string | null>(null);

return (
<div className="space-y-3">
<AgentInbox items={initial} onOpen={(id) => setOpened(id)} />
<p className="text-[12px] text-smoke" aria-live="polite">
{opened
? `Opened run #${opened} for review. In a product, this routes to the run's transcript with its approval gates.`
: "Tap a run to open it for review."}
</p>
</div>
);
}
37 changes: 37 additions & 0 deletions app/components/agent-inbox/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { ComponentDoc } from "@/components/docs/doc-page";
import { AgentInboxDemo } from "./demo";

export const metadata = {
title: "Agent Inbox — Agent Pit Stop",
description: "The review queue for background agents: results wait for human eyes, ordered by what needs you most.",
};

export default function Page() {
return (
<ComponentDoc
slug="agent-inbox"
intro="Background agents finish laps while you're elsewhere. The inbox is where their results wait: runs that need a decision surface first with the reason named, running work stays visible, and finished work files itself quietly. It's the difference between agents you check on and agents that report to you."
chatPrompt="What did my agents get done today?"
preview={<AgentInboxDemo />}
sections={[
{
heading: "When to use",
body: "Any product with agents that outlive the session: overnight runs, scheduled jobs, long research tasks. Skip it if every run completes while the user watches; a queue of one is ceremony.",
},
{
heading: "Behavior",
body: "Four statuses: needs you, running, done, failed. Needs-review items carry the reason (low confidence, irreversible step, failure), because a red dot without a why just manufactures anxiety. Nothing auto-completes past a consent point while sitting in the queue.",
},
{
heading: "Usage",
code: `import { AgentInbox } from "@/components/agent-inbox";

<AgentInbox
items={runs}
onOpen={(id) => router.push(\`/runs/\${id}\`)}
/>`,
},
]}
/>
);
}
36 changes: 36 additions & 0 deletions app/components/agent-roster/demo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
"use client";

import { useEffect, useState } from "react";
import { AgentRoster, type RosterAgent } from "@/registry/agent-roster/agent-roster";

const frames: RosterAgent[][] = [
[
{ id: "o", name: "Orchestrator", role: "plans and delegates", state: "working", activity: "assigning research tasks" },
{ id: "r", name: "Researcher", role: "finds and verifies sources", state: "working", activity: "reading 4 filings" },
{ id: "w", name: "Writer", role: "drafts the report", state: "waiting", activity: "waiting on research" },
{ id: "c", name: "Checker", role: "verifies claims", state: "idle" },
],
[
{ id: "o", name: "Orchestrator", role: "plans and delegates", state: "waiting", activity: "monitoring the crew" },
{ id: "r", name: "Researcher", role: "finds and verifies sources", state: "blocked", activity: "paywalled source, needs a decision" },
{ id: "w", name: "Writer", role: "drafts the report", state: "working", activity: "drafting section 2 of 5" },
{ id: "c", name: "Checker", role: "verifies claims", state: "idle" },
],
[
{ id: "o", name: "Orchestrator", role: "plans and delegates", state: "waiting", activity: "monitoring the crew" },
{ id: "r", name: "Researcher", role: "finds and verifies sources", state: "idle" },
{ id: "w", name: "Writer", role: "drafts the report", state: "working", activity: "drafting section 4 of 5" },
{ id: "c", name: "Checker", role: "verifies claims", state: "working", activity: "checking 11 citations" },
],
];

export function AgentRosterDemo() {
const [frame, setFrame] = useState(0);

useEffect(() => {
const t = setInterval(() => setFrame((f) => (f + 1) % frames.length), 3200);
return () => clearInterval(t);
}, []);

return <AgentRoster label="Report crew" agents={frames[frame]} />;
}
40 changes: 40 additions & 0 deletions app/components/agent-roster/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { ComponentDoc } from "@/components/docs/doc-page";
import { AgentRosterDemo } from "./demo";

export const metadata = {
title: "Agent Roster — Agent Pit Stop",
description: "The team sheet for multi-agent runs: who's working, who's waiting, who's blocked, and on what.",
};

export default function Page() {
return (
<ComponentDoc
slug="agent-roster"
intro="Multi-agent products love to say 'a team of agents is working for you' and then show a spinner. The roster is the team sheet: one line per agent with its role, state, and live activity. A blocked agent stays on the sheet with its blocker named, because a team that hides its stuck members isn't a team you can manage."
chatPrompt="Put a crew on the competitive report."
preview={<AgentRosterDemo />}
sections={[
{
heading: "When to use",
body: "Orchestrator-and-workers runs where more than one agent acts at once. For a single agent, use the Agent Task List instead; a roster of one is an org chart for a solo founder.",
},
{
heading: "Behavior",
body: "Four states: working (pulsing), waiting, blocked, idle. Every state is written in words next to its dot. Blocked rows name the blocker in the activity line and read as the human's cue to intervene; pair with the Interrupt Bar for targeted steering.",
},
{
heading: "Usage",
code: `import { AgentRoster } from "@/components/agent-roster";

<AgentRoster
label="Report crew"
agents={[
{ id: "r", name: "Researcher", state: "working", activity: "reading 4 filings" },
{ id: "w", name: "Writer", state: "waiting", activity: "waiting on research" },
]}
/>`,
},
]}
/>
);
}
31 changes: 31 additions & 0 deletions app/components/context-budget/demo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
"use client";

import { useState } from "react";
import { ContextBudget } from "@/registry/context-budget/context-budget";

export function ContextBudgetDemo() {
const [spendCap, setSpendCap] = useState(5);
const [raised, setRaised] = useState(false);

return (
<div className="space-y-3">
<ContextBudget
label="Research run budget"
lines={[
{ label: "Spend", used: 5, cap: spendCap, format: (n) => `$${n.toFixed(2)}` },
{ label: "Tokens", used: 412_000, cap: 1_000_000, format: (n) => `${Math.round(n / 1000)}k` },
{ label: "Time", used: 21, cap: 45, format: (n) => `${n}m` },
]}
onRaise={() => {
setSpendCap(10);
setRaised(true);
}}
/>
<p className="text-[12px] text-smoke" aria-live="polite">
{raised
? "Cap raised to $10.00 by you. The run resumes; the receipt records who raised it."
: "The run hit its $5 spend cap and paused. Raising the cap is a human decision."}
</p>
</div>
);
}
40 changes: 40 additions & 0 deletions app/components/context-budget/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { ComponentDoc } from "@/components/docs/doc-page";
import { ContextBudgetDemo } from "./demo";

export const metadata = {
title: "Context Budget — Agent Pit Stop",
description: "A delegation contract's limits made visible: tokens, spend, or time against their caps.",
};

export default function Page() {
return (
<ComponentDoc
slug="context-budget"
intro="Every delegation contract has limits; almost no interface shows them. The budget renders consumption against caps in honest bands, and when a cap is hit, the run pauses into a rendered state with a human decision attached. Budgets nobody can see are budgets nobody trusts, and surprise bills are how agent products lose customers."
chatPrompt="Research this market, but keep it under $5."
preview={<ContextBudgetDemo />}
sections={[
{
heading: "When to use",
body: "Any run where the user set or implied a limit: spend, tokens, time, API calls. Show it during the run, not in a postmortem. Skip it for instant, effectively-free actions.",
},
{
heading: "Behavior",
body: "Three bands, each labeled in words: fine, near cap, at cap. Hitting a cap pauses the run and states that nothing was spent past it; raising the cap is an explicit human action that belongs in the receipt trail. Bars carry ARIA progressbar semantics.",
},
{
heading: "Usage",
code: `import { ContextBudget } from "@/components/context-budget";

<ContextBudget
lines={[
{ label: "Spend", used: 4.1, cap: 5, format: (n) => \`$\${n.toFixed(2)}\` },
{ label: "Time", used: 21, cap: 45, format: (n) => \`\${n}m\` },
]}
onRaise={(which) => askUserToRaise(which)}
/>`,
},
]}
/>
);
}
Loading
Loading