mirror of
https://github.com/paperclipai/paperclip
synced 2026-05-06 07:02:11 +02:00
Compare commits
22 Commits
pap-3598/o
...
run-a-clau
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eec4ff855a | ||
|
|
873f15d990 | ||
|
|
ec37967dd3 | ||
|
|
859523b58b | ||
|
|
917b72aaf4 | ||
|
|
0710f166da | ||
|
|
51dd51b98a | ||
|
|
774679bf50 | ||
|
|
4996295b07 | ||
|
|
568e1c15df | ||
|
|
6b0f407943 | ||
|
|
389f8105ac | ||
|
|
78eed3b197 | ||
|
|
aecf32e60b | ||
|
|
2ede0af41f | ||
|
|
31e9a92853 | ||
|
|
0f5895e4e5 | ||
|
|
2f9f657df5 | ||
|
|
53db6095b0 | ||
|
|
3907d6dc40 | ||
|
|
1d74e78a96 | ||
|
|
0875aa54ff |
@@ -177,12 +177,8 @@ real name or email). To find GitHub usernames:
|
||||
|
||||
**Never expose contributor email addresses.** Use `@username` only.
|
||||
|
||||
Exclude bot accounts (e.g. `lockfile-bot`, `dependabot`) from the list.
|
||||
Exclude Paperclip founders from the list (e.g. `cryppadotta`, `forgottendev`, `devinfoley`, `sockmonster`, `scotttong`)
|
||||
|
||||
List contributors in alphabetical order by GitHub username (case-insensitive).
|
||||
|
||||
If there are no contributors left after exclusions, then just skip this section and don't mention it.
|
||||
Exclude bot accounts (e.g. `lockfile-bot`, `dependabot`) from the list. List contributors
|
||||
in alphabetical order by GitHub username (case-insensitive).
|
||||
|
||||
## Step 6 — Review Before Release
|
||||
|
||||
|
||||
2
.github/workflows/docker.yml
vendored
2
.github/workflows/docker.yml
vendored
@@ -14,7 +14,7 @@ permissions:
|
||||
jobs:
|
||||
build-and-push:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
timeout-minutes: 30
|
||||
concurrency:
|
||||
group: docker-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
137
.github/workflows/pr.yml
vendored
137
.github/workflows/pr.yml
vendored
@@ -23,9 +23,7 @@ jobs:
|
||||
- name: Block manual lockfile edits
|
||||
if: github.head_ref != 'chore/refresh-lockfile'
|
||||
run: |
|
||||
# Diff the PR branch against its merge base so recent base-branch commits
|
||||
# do not masquerade as changes made by the PR itself.
|
||||
changed="$(git diff --name-only "${{ github.event.pull_request.base.sha }}...${{ github.event.pull_request.head.sha }}")"
|
||||
changed="$(git diff --name-only "${{ github.event.pull_request.base.sha }}" "${{ github.event.pull_request.head.sha }}")"
|
||||
if printf '%s\n' "$changed" | grep -qx 'pnpm-lock.yaml'; then
|
||||
echo "Do not commit pnpm-lock.yaml in pull requests. CI owns lockfile updates."
|
||||
exit 1
|
||||
@@ -43,20 +41,48 @@ jobs:
|
||||
node-version: 24
|
||||
|
||||
- name: Validate Dockerfile deps stage
|
||||
run: node ./scripts/check-docker-deps-stage.mjs
|
||||
|
||||
- name: Validate release package manifest
|
||||
run: node ./scripts/release-package-map.mjs check
|
||||
|
||||
- name: Verify release package bootstrap for changed manifests
|
||||
run: |
|
||||
mapfile -t changed_paths < <(git diff --name-only "${{ github.event.pull_request.base.sha }}...${{ github.event.pull_request.head.sha }}")
|
||||
PAPERCLIP_RELEASE_BOOTSTRAP_BASE_SHA="${{ github.event.pull_request.base.sha }}" \
|
||||
node ./scripts/check-release-package-bootstrap.mjs "${changed_paths[@]}"
|
||||
missing=0
|
||||
|
||||
# Extract only the deps stage from the Dockerfile
|
||||
deps_stage="$(awk '/^FROM .* AS deps$/{found=1; next} found && /^FROM /{exit} found{print}' Dockerfile)"
|
||||
|
||||
if [ -z "$deps_stage" ]; then
|
||||
echo "::error::Could not extract deps stage from Dockerfile (expected 'FROM ... AS deps')"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Derive workspace search roots from pnpm-workspace.yaml (exclude dev-only packages)
|
||||
search_roots="$(grep '^ *- ' pnpm-workspace.yaml | sed 's/^ *- //' | sed 's/\*$//' | grep -v 'examples' | grep -v 'create-paperclip-plugin' | tr '\n' ' ')"
|
||||
|
||||
if [ -z "$search_roots" ]; then
|
||||
echo "::error::Could not derive workspace roots from pnpm-workspace.yaml"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check all workspace package.json files are copied in the deps stage
|
||||
for pkg in $(find $search_roots -maxdepth 2 -name package.json -not -path '*/examples/*' -not -path '*/create-paperclip-plugin/*' -not -path '*/node_modules/*' 2>/dev/null | sort -u); do
|
||||
dir="$(dirname "$pkg")"
|
||||
if ! echo "$deps_stage" | grep -q "^COPY ${dir}/package.json"; then
|
||||
echo "::error::Dockerfile deps stage missing: COPY ${pkg} ${dir}/"
|
||||
missing=1
|
||||
fi
|
||||
done
|
||||
|
||||
# Check patches directory is copied if it exists
|
||||
if [ -d patches ] && ! echo "$deps_stage" | grep -q '^COPY patches/'; then
|
||||
echo "::error::Dockerfile deps stage missing: COPY patches/ patches/"
|
||||
missing=1
|
||||
fi
|
||||
|
||||
if [ "$missing" -eq 1 ]; then
|
||||
echo "Dockerfile deps stage is out of sync. Update it to include the missing files."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Validate dependency resolution when manifests change
|
||||
run: |
|
||||
changed="$(git diff --name-only "${{ github.event.pull_request.base.sha }}...${{ github.event.pull_request.head.sha }}")"
|
||||
changed="$(git diff --name-only "${{ github.event.pull_request.base.sha }}" "${{ github.event.pull_request.head.sha }}")"
|
||||
manifest_pattern='(^|/)package\.json$|^pnpm-workspace\.yaml$|^\.npmrc$|^pnpmfile\.(cjs|js|mjs)$'
|
||||
if printf '%s\n' "$changed" | grep -Eq "$manifest_pattern"; then
|
||||
pnpm install --lockfile-only --ignore-scripts --no-frozen-lockfile
|
||||
@@ -85,88 +111,16 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Typecheck workspaces whose build scripts skip TypeScript
|
||||
run: pnpm run typecheck:build-gaps
|
||||
- name: Typecheck
|
||||
run: pnpm -r typecheck
|
||||
|
||||
- name: Run general test suites
|
||||
run: pnpm test:run:general
|
||||
|
||||
- name: Verify release registry test coverage
|
||||
run: pnpm run test:release-registry
|
||||
- name: Run tests
|
||||
run: pnpm test:run
|
||||
|
||||
- name: Build
|
||||
run: pnpm build
|
||||
|
||||
verify_serialized_server:
|
||||
name: Verify serialized server suites (${{ matrix.shard_label }})
|
||||
needs: [policy]
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- shard_index: 0
|
||||
shard_count: 4
|
||||
shard_label: 1/4
|
||||
- shard_index: 1
|
||||
shard_count: 4
|
||||
shard_label: 2/4
|
||||
- shard_index: 2
|
||||
shard_count: 4
|
||||
shard_label: 3/4
|
||||
- shard_index: 3
|
||||
shard_count: 4
|
||||
shard_label: 4/4
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 9.15.4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 24
|
||||
cache: pnpm
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Run serialized server test shard
|
||||
run: pnpm test:run:serialized -- --shard-index ${{ matrix.shard_index }} --shard-count ${{ matrix.shard_count }}
|
||||
|
||||
canary_dry_run:
|
||||
name: Canary Dry Run
|
||||
needs: [policy]
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 9.15.4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 24
|
||||
cache: pnpm
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
# `release.sh` always executes its Step 2/7 workspace build, even when
|
||||
# `--skip-verify` bypasses the initial verification gate.
|
||||
- name: Release canary dry run via release.sh internal build
|
||||
- name: Release canary dry run
|
||||
run: |
|
||||
git checkout -B master HEAD
|
||||
git checkout -- pnpm-lock.yaml
|
||||
@@ -195,6 +149,9 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build
|
||||
run: pnpm build
|
||||
|
||||
- name: Install Playwright
|
||||
run: npx playwright install --with-deps chromium
|
||||
|
||||
|
||||
12
.github/workflows/release.yml
vendored
12
.github/workflows/release.yml
vendored
@@ -50,9 +50,6 @@ jobs:
|
||||
node-version: 24
|
||||
cache: pnpm
|
||||
|
||||
- name: Validate release package manifest
|
||||
run: node ./scripts/release-package-map.mjs check
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --no-frozen-lockfile
|
||||
|
||||
@@ -92,9 +89,6 @@ jobs:
|
||||
node-version: 24
|
||||
cache: pnpm
|
||||
|
||||
- name: Validate release package manifest
|
||||
run: node ./scripts/release-package-map.mjs check
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --no-frozen-lockfile
|
||||
|
||||
@@ -145,9 +139,6 @@ jobs:
|
||||
node-version: 24
|
||||
cache: pnpm
|
||||
|
||||
- name: Validate release package manifest
|
||||
run: node ./scripts/release-package-map.mjs check
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --no-frozen-lockfile
|
||||
|
||||
@@ -186,9 +177,6 @@ jobs:
|
||||
node-version: 24
|
||||
cache: pnpm
|
||||
|
||||
- name: Validate release package manifest
|
||||
run: node ./scripts/release-package-map.mjs check
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --no-frozen-lockfile
|
||||
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -3,7 +3,6 @@ node_modules/
|
||||
**/node_modules
|
||||
**/node_modules/
|
||||
dist/
|
||||
ui/storybook-static/
|
||||
.env
|
||||
*.tsbuildinfo
|
||||
drizzle/meta/
|
||||
|
||||
@@ -123,9 +123,7 @@ pnpm test:release-smoke
|
||||
|
||||
Run the browser suites only when your change touches them or when you are explicitly verifying CI/release flows.
|
||||
|
||||
For normal issue work, run the smallest relevant verification first. Do not default to repo-wide typecheck/build/test on every heartbeat when a narrower check is enough to prove the change.
|
||||
|
||||
Run this full check before claiming repo work done in a PR-ready hand-off, or when the change scope is broad enough that targeted checks are not sufficient:
|
||||
Run this full check before claiming done:
|
||||
|
||||
```sh
|
||||
pnpm -r typecheck
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
# syntax=docker/dockerfile:1.20
|
||||
FROM node:lts-trixie-slim AS base
|
||||
ARG USER_UID=1000
|
||||
ARG USER_GID=1000
|
||||
@@ -22,7 +21,6 @@ COPY packages/shared/package.json packages/shared/
|
||||
COPY packages/db/package.json packages/db/
|
||||
COPY packages/adapter-utils/package.json packages/adapter-utils/
|
||||
COPY packages/mcp-server/package.json packages/mcp-server/
|
||||
COPY packages/adapters/acpx-local/package.json packages/adapters/acpx-local/
|
||||
COPY packages/adapters/claude-local/package.json packages/adapters/claude-local/
|
||||
COPY packages/adapters/codex-local/package.json packages/adapters/codex-local/
|
||||
COPY packages/adapters/cursor-local/package.json packages/adapters/cursor-local/
|
||||
@@ -31,8 +29,6 @@ COPY packages/adapters/openclaw-gateway/package.json packages/adapters/openclaw-
|
||||
COPY packages/adapters/opencode-local/package.json packages/adapters/opencode-local/
|
||||
COPY packages/adapters/pi-local/package.json packages/adapters/pi-local/
|
||||
COPY packages/plugins/sdk/package.json packages/plugins/sdk/
|
||||
COPY --parents packages/plugins/sandbox-providers/./*/package.json packages/plugins/sandbox-providers/
|
||||
COPY packages/plugins/paperclip-plugin-fake-sandbox/package.json packages/plugins/paperclip-plugin-fake-sandbox/
|
||||
COPY patches/ patches/
|
||||
|
||||
RUN pnpm install --frozen-lockfile
|
||||
|
||||
113
README.md
113
README.md
@@ -6,8 +6,7 @@
|
||||
<a href="#quickstart"><strong>Quickstart</strong></a> ·
|
||||
<a href="https://paperclip.ing/docs"><strong>Docs</strong></a> ·
|
||||
<a href="https://github.com/paperclipai/paperclip"><strong>GitHub</strong></a> ·
|
||||
<a href="https://discord.gg/m4HZY7xNG3"><strong>Discord</strong></a> ·
|
||||
<a href="https://x.com/papercliping"><strong>Twitter</strong></a>
|
||||
<a href="https://discord.gg/m4HZY7xNG3"><strong>Discord</strong></a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
@@ -157,115 +156,6 @@ Paperclip handles the hard orchestration details correctly.
|
||||
|
||||
<br/>
|
||||
|
||||
## What's Under the Hood
|
||||
|
||||
Paperclip is a full control plane, not a wrapper. Before you build any of this yourself, know that it already exists:
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
│ PAPERCLIP SERVER │
|
||||
│ │
|
||||
│ ┌───────────┐ ┌───────────┐ ┌───────────┐ ┌───────────┐ │
|
||||
│ │Identity & │ │ Work & │ │ Heartbeat │ │Governance │ │
|
||||
│ │ Access │ │ Tasks │ │ Execution │ │& Approvals│ │
|
||||
│ └───────────┘ └───────────┘ └───────────┘ └───────────┘ │
|
||||
│ │
|
||||
│ ┌───────────┐ ┌───────────┐ ┌───────────┐ ┌───────────┐ │
|
||||
│ │ Org Chart │ │Workspaces │ │ Plugins │ │ Budget │ │
|
||||
│ │ & Agents │ │ & Runtime │ │ │ │ & Costs │ │
|
||||
│ └───────────┘ └───────────┘ └───────────┘ └───────────┘ │
|
||||
│ │
|
||||
│ ┌───────────┐ ┌───────────┐ ┌───────────┐ ┌───────────┐ │
|
||||
│ │ Routines │ │ Secrets & │ │ Activity │ │ Company │ │
|
||||
│ │& Schedules│ │ Storage │ │ & Events │ │Portability│ │
|
||||
│ └───────────┘ └───────────┘ └───────────┘ └───────────┘ │
|
||||
└──────────────────────────────────────────────────────────────┘
|
||||
▲ ▲ ▲ ▲
|
||||
┌─────┴─────┐ ┌─────┴─────┐ ┌─────┴─────┐ ┌─────┴─────┐
|
||||
│ Claude │ │ Codex │ │ CLI │ │ HTTP/web │
|
||||
│ Code │ │ │ │ agents │ │ bots │
|
||||
└───────────┘ └───────────┘ └───────────┘ └───────────┘
|
||||
```
|
||||
|
||||
### The Systems
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td width="50%">
|
||||
|
||||
**Identity & Access** — Two deployment modes (trusted local or authenticated), board users, agent API keys, short-lived run JWTs, company memberships, invite flows, and OpenClaw onboarding. Every mutating request is traced to an actor.
|
||||
|
||||
</td>
|
||||
<td width="50%">
|
||||
|
||||
**Org Chart & Agents** — Agents have roles, titles, reporting lines, permissions, and budgets. Adapter examples match the diagram: Claude Code, Codex, CLI agents such as Cursor/Gemini/bash, HTTP/webhook bots such as OpenClaw, and external adapter plugins. If it can receive a heartbeat, it's hired.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
**Work & Task System** — Issues carry company/project/goal/parent links, atomic checkout with execution locks, first-class blocker dependencies, comments, documents, attachments, work products, labels, and inbox state. No double-work, no lost context.
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
**Heartbeat Execution** — DB-backed wakeup queue with coalescing, budget checks, workspace resolution, secret injection, skill loading, and adapter invocation. Runs produce structured logs, cost events, session state, and audit trails. Recovery handles orphaned runs automatically.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
**Workspaces & Runtime** — Project workspaces, isolated execution workspaces (git worktrees, operator branches), and runtime services (dev servers, preview URLs). Agents work in the right directory with the right context every time.
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
**Governance & Approvals** — Board approval workflows, execution policies with review/approval stages, decision tracking, budget hard-stops, agent pause/resume/terminate, and full audit logging. You're the board — nothing ships without your sign-off.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
**Budget & Cost Control** — Token and cost tracking by company, agent, project, goal, issue, provider, and model. Scoped budget policies with warning thresholds and hard stops. Overspend pauses agents and cancels queued work automatically.
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
**Routines & Schedules** — Recurring tasks with cron, webhook, and API triggers. Concurrency and catch-up policies. Each routine execution creates a tracked issue and wakes the assigned agent — no manual kick-offs needed.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
**Plugins** — Instance-wide plugin system with out-of-process workers, capability-gated host services, job scheduling, tool exposure, and UI contributions. Extend Paperclip without forking it.
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
**Secrets & Storage** — Instance and company secrets, encrypted local storage, provider-backed object storage, attachments, and work products. Sensitive values stay out of prompts unless a scoped run explicitly needs them.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
**Activity & Events** — Mutating actions, heartbeat state changes, cost events, approvals, comments, and work products are recorded as durable activity so operators can audit what happened and why.
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
**Company Portability** — Export and import entire organizations — agents, skills, projects, routines, and issues — with secret scrubbing and collision handling. One deployment, many companies, complete data isolation.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<br/>
|
||||
|
||||
## What Paperclip is not
|
||||
|
||||
| | |
|
||||
@@ -410,7 +300,6 @@ We welcome contributions. See the [contributing guide](CONTRIBUTING.md) for deta
|
||||
## Community
|
||||
|
||||
- [Discord](https://discord.gg/m4HZY7xNG3) — Join the community
|
||||
- [Twitter / X](https://x.com/papercliping) — Follow updates and announcements
|
||||
- [GitHub Issues](https://github.com/paperclipai/paperclip/issues) — bugs and feature requests
|
||||
- [GitHub Discussions](https://github.com/paperclipai/paperclip/discussions) — ideas and RFC
|
||||
|
||||
|
||||
@@ -6,8 +6,7 @@
|
||||
<a href="#quickstart"><strong>Quickstart</strong></a> ·
|
||||
<a href="https://paperclip.ing/docs"><strong>Docs</strong></a> ·
|
||||
<a href="https://github.com/paperclipai/paperclip"><strong>GitHub</strong></a> ·
|
||||
<a href="https://discord.gg/m4HZY7xNG3"><strong>Discord</strong></a> ·
|
||||
<a href="https://x.com/papercliping"><strong>Twitter</strong></a>
|
||||
<a href="https://discord.gg/m4HZY7xNG3"><strong>Discord</strong></a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
@@ -279,7 +278,6 @@ We welcome contributions. See the [contributing guide](https://github.com/paperc
|
||||
## Community
|
||||
|
||||
- [Discord](https://discord.gg/m4HZY7xNG3) — Join the community
|
||||
- [Twitter / X](https://x.com/papercliping) — Follow updates and announcements
|
||||
- [GitHub Issues](https://github.com/paperclipai/paperclip/issues) — bugs and feature requests
|
||||
- [GitHub Discussions](https://github.com/paperclipai/paperclip/discussions) — ideas and RFC
|
||||
|
||||
|
||||
@@ -37,7 +37,6 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@clack/prompts": "^0.10.0",
|
||||
"@paperclipai/adapter-acpx-local": "workspace:*",
|
||||
"@paperclipai/adapter-claude-local": "workspace:*",
|
||||
"@paperclipai/adapter-codex-local": "workspace:*",
|
||||
"@paperclipai/adapter-cursor-local": "workspace:*",
|
||||
|
||||
@@ -14,7 +14,6 @@ function makeCompany(overrides: Partial<Company>): Company {
|
||||
issueCounter: 1,
|
||||
budgetMonthlyCents: 0,
|
||||
spentMonthlyCents: 0,
|
||||
attachmentMaxBytes: 10 * 1024 * 1024,
|
||||
requireBoardApprovalForNewAgents: false,
|
||||
feedbackDataSharingEnabled: false,
|
||||
feedbackDataSharingConsentAt: null,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { execFile, spawn } from "node:child_process";
|
||||
import { existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs";
|
||||
import net from "node:net";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
@@ -104,50 +104,20 @@ function writeTestConfig(configPath: string, tempRoot: string, port: number, con
|
||||
writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\n`, "utf8");
|
||||
}
|
||||
|
||||
interface TestPaperclipEnv {
|
||||
configPath: string;
|
||||
paperclipHome: string;
|
||||
instanceId: string;
|
||||
shellHome?: string;
|
||||
}
|
||||
|
||||
function createBasePaperclipEnv(options: TestPaperclipEnv) {
|
||||
function createServerEnv(configPath: string, port: number, connectionString: string) {
|
||||
const env = { ...process.env };
|
||||
for (const key of Object.keys(env)) {
|
||||
if (key.startsWith("PAPERCLIP_")) {
|
||||
delete env[key];
|
||||
}
|
||||
}
|
||||
|
||||
env.PAPERCLIP_CONFIG = options.configPath;
|
||||
env.PAPERCLIP_HOME = options.paperclipHome;
|
||||
env.PAPERCLIP_INSTANCE_ID = options.instanceId;
|
||||
env.PAPERCLIP_CONTEXT = path.join(options.paperclipHome, "context.json");
|
||||
env.PAPERCLIP_AUTH_STORE = path.join(options.paperclipHome, "auth.json");
|
||||
if (options.shellHome) {
|
||||
env.HOME = options.shellHome;
|
||||
}
|
||||
|
||||
return env;
|
||||
}
|
||||
|
||||
function createServerEnv(
|
||||
configPath: string,
|
||||
port: number,
|
||||
connectionString: string,
|
||||
options: Omit<TestPaperclipEnv, "configPath">,
|
||||
) {
|
||||
const env = createBasePaperclipEnv({
|
||||
configPath,
|
||||
...options,
|
||||
});
|
||||
|
||||
delete env.DATABASE_URL;
|
||||
delete env.PORT;
|
||||
delete env.HOST;
|
||||
delete env.SERVE_UI;
|
||||
delete env.HEARTBEAT_SCHEDULER_ENABLED;
|
||||
|
||||
env.PAPERCLIP_CONFIG = configPath;
|
||||
env.DATABASE_URL = connectionString;
|
||||
env.HOST = "127.0.0.1";
|
||||
env.PORT = String(port);
|
||||
@@ -160,8 +130,13 @@ function createServerEnv(
|
||||
return env;
|
||||
}
|
||||
|
||||
function createCliEnv(options: TestPaperclipEnv) {
|
||||
const env = createBasePaperclipEnv(options);
|
||||
function createCliEnv() {
|
||||
const env = { ...process.env };
|
||||
for (const key of Object.keys(env)) {
|
||||
if (key.startsWith("PAPERCLIP_")) {
|
||||
delete env[key];
|
||||
}
|
||||
}
|
||||
delete env.DATABASE_URL;
|
||||
delete env.PORT;
|
||||
delete env.HOST;
|
||||
@@ -208,25 +183,14 @@ async function api<T>(baseUrl: string, pathname: string, init?: RequestInit): Pr
|
||||
return text ? JSON.parse(text) as T : (null as T);
|
||||
}
|
||||
|
||||
async function runCliJson<T>(
|
||||
args: string[],
|
||||
opts: TestPaperclipEnv & { apiBase?: string; includeConfigArg?: boolean },
|
||||
) {
|
||||
async function runCliJson<T>(args: string[], opts: { apiBase: string; configPath: string }) {
|
||||
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../../..");
|
||||
const cliArgs = ["--silent", "paperclipai", ...args];
|
||||
if (opts.apiBase) {
|
||||
cliArgs.push("--api-base", opts.apiBase);
|
||||
}
|
||||
if (opts.includeConfigArg !== false) {
|
||||
cliArgs.push("--config", opts.configPath);
|
||||
}
|
||||
cliArgs.push("--json");
|
||||
const result = await execFileAsync(
|
||||
"pnpm",
|
||||
cliArgs,
|
||||
["--silent", "paperclipai", ...args, "--api-base", opts.apiBase, "--config", opts.configPath, "--json"],
|
||||
{
|
||||
cwd: repoRoot,
|
||||
env: createCliEnv(opts),
|
||||
env: createCliEnv(),
|
||||
maxBuffer: 10 * 1024 * 1024,
|
||||
},
|
||||
);
|
||||
@@ -271,9 +235,6 @@ describeEmbeddedPostgres("paperclipai company import/export e2e", () => {
|
||||
let configPath = "";
|
||||
let exportDir = "";
|
||||
let apiBase = "";
|
||||
let paperclipHome = "";
|
||||
let cliShellHome = "";
|
||||
let paperclipInstanceId = "";
|
||||
let serverProcess: ServerProcess | null = null;
|
||||
let tempDb: Awaited<ReturnType<typeof startEmbeddedPostgresTestDatabase>> | null = null;
|
||||
|
||||
@@ -281,11 +242,6 @@ describeEmbeddedPostgres("paperclipai company import/export e2e", () => {
|
||||
tempRoot = mkdtempSync(path.join(os.tmpdir(), "paperclip-company-cli-e2e-"));
|
||||
configPath = path.join(tempRoot, "config", "config.json");
|
||||
exportDir = path.join(tempRoot, "exported-company");
|
||||
paperclipHome = path.join(tempRoot, "paperclip-home");
|
||||
cliShellHome = path.join(tempRoot, "shell-home");
|
||||
paperclipInstanceId = "company-cli-e2e";
|
||||
mkdirSync(paperclipHome, { recursive: true });
|
||||
mkdirSync(cliShellHome, { recursive: true });
|
||||
|
||||
tempDb = await startEmbeddedPostgresTestDatabase("paperclip-company-cli-db-");
|
||||
|
||||
@@ -300,11 +256,7 @@ describeEmbeddedPostgres("paperclipai company import/export e2e", () => {
|
||||
["paperclipai", "run", "--config", configPath],
|
||||
{
|
||||
cwd: repoRoot,
|
||||
env: createServerEnv(configPath, port, tempDb.connectionString, {
|
||||
paperclipHome,
|
||||
instanceId: paperclipInstanceId,
|
||||
shellHome: cliShellHome,
|
||||
}),
|
||||
env: createServerEnv(configPath, port, tempDb.connectionString),
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
},
|
||||
);
|
||||
@@ -330,31 +282,6 @@ describeEmbeddedPostgres("paperclipai company import/export e2e", () => {
|
||||
it("exports a company package and imports it into new and existing companies", async () => {
|
||||
expect(serverProcess).not.toBeNull();
|
||||
|
||||
const cliContext = await runCliJson<{
|
||||
contextPath: string;
|
||||
profileName: string;
|
||||
profile: { apiBase?: string };
|
||||
}>(
|
||||
["context", "set", "--profile", "isolation-check", "--api-base", "https://example.test"],
|
||||
{
|
||||
configPath,
|
||||
paperclipHome,
|
||||
instanceId: paperclipInstanceId,
|
||||
shellHome: cliShellHome,
|
||||
includeConfigArg: false,
|
||||
},
|
||||
);
|
||||
|
||||
const expectedContextPath = path.join(paperclipHome, "context.json");
|
||||
const leakedContextPath = path.join(cliShellHome, ".paperclip", "context.json");
|
||||
expect(cliContext.contextPath).toBe(expectedContextPath);
|
||||
expect(cliContext.profileName).toBe("isolation-check");
|
||||
expect(cliContext.profile.apiBase).toBe("https://example.test");
|
||||
expect(existsSync(expectedContextPath)).toBe(true);
|
||||
expect(existsSync(leakedContextPath)).toBe(false);
|
||||
rmSync(expectedContextPath, { force: true });
|
||||
expect(existsSync(expectedContextPath)).toBe(false);
|
||||
|
||||
const sourceCompany = await api<{ id: string; name: string; issuePrefix: string }>(apiBase, "/api/companies", {
|
||||
method: "POST",
|
||||
headers: { "content-type": "application/json" },
|
||||
@@ -376,11 +303,8 @@ describeEmbeddedPostgres("paperclipai company import/export e2e", () => {
|
||||
name: "Export Engineer",
|
||||
role: "engineer",
|
||||
adapterType: "claude_local",
|
||||
adapterConfig: {},
|
||||
instructionsBundle: {
|
||||
files: {
|
||||
"AGENTS.md": "You verify company portability.",
|
||||
},
|
||||
adapterConfig: {
|
||||
promptTemplate: "You verify company portability.",
|
||||
},
|
||||
}),
|
||||
},
|
||||
@@ -431,13 +355,7 @@ describeEmbeddedPostgres("paperclipai company import/export e2e", () => {
|
||||
"--include",
|
||||
"company,agents,projects,issues",
|
||||
],
|
||||
{
|
||||
apiBase,
|
||||
configPath,
|
||||
paperclipHome,
|
||||
instanceId: paperclipInstanceId,
|
||||
shellHome: cliShellHome,
|
||||
},
|
||||
{ apiBase, configPath },
|
||||
);
|
||||
|
||||
expect(exportResult.ok).toBe(true);
|
||||
@@ -461,13 +379,7 @@ describeEmbeddedPostgres("paperclipai company import/export e2e", () => {
|
||||
"company,agents,projects,issues",
|
||||
"--yes",
|
||||
],
|
||||
{
|
||||
apiBase,
|
||||
configPath,
|
||||
paperclipHome,
|
||||
instanceId: paperclipInstanceId,
|
||||
shellHome: cliShellHome,
|
||||
},
|
||||
{ apiBase, configPath },
|
||||
);
|
||||
|
||||
expect(importedNew.company.action).toBe("created");
|
||||
@@ -486,11 +398,10 @@ describeEmbeddedPostgres("paperclipai company import/export e2e", () => {
|
||||
apiBase,
|
||||
`/api/companies/${importedNew.company.id}/issues`,
|
||||
);
|
||||
const importedMatchingIssues = importedIssues.filter((issue) => issue.title === sourceIssue.title);
|
||||
|
||||
expect(importedAgents.map((agent) => agent.name)).toContain(sourceAgent.name);
|
||||
expect(importedProjects.map((project) => project.name)).toContain(sourceProject.name);
|
||||
expect(importedMatchingIssues).toHaveLength(1);
|
||||
expect(importedIssues.map((issue) => issue.title)).toContain(sourceIssue.title);
|
||||
|
||||
const previewExisting = await runCliJson<{
|
||||
errors: string[];
|
||||
@@ -515,13 +426,7 @@ describeEmbeddedPostgres("paperclipai company import/export e2e", () => {
|
||||
"rename",
|
||||
"--dry-run",
|
||||
],
|
||||
{
|
||||
apiBase,
|
||||
configPath,
|
||||
paperclipHome,
|
||||
instanceId: paperclipInstanceId,
|
||||
shellHome: cliShellHome,
|
||||
},
|
||||
{ apiBase, configPath },
|
||||
);
|
||||
|
||||
expect(previewExisting.errors).toEqual([]);
|
||||
@@ -548,13 +453,7 @@ describeEmbeddedPostgres("paperclipai company import/export e2e", () => {
|
||||
"rename",
|
||||
"--yes",
|
||||
],
|
||||
{
|
||||
apiBase,
|
||||
configPath,
|
||||
paperclipHome,
|
||||
instanceId: paperclipInstanceId,
|
||||
shellHome: cliShellHome,
|
||||
},
|
||||
{ apiBase, configPath },
|
||||
);
|
||||
|
||||
expect(importedExisting.company.action).toBe("unchanged");
|
||||
@@ -572,13 +471,11 @@ describeEmbeddedPostgres("paperclipai company import/export e2e", () => {
|
||||
apiBase,
|
||||
`/api/companies/${importedNew.company.id}/issues`,
|
||||
);
|
||||
const twiceImportedMatchingIssues = twiceImportedIssues.filter((issue) => issue.title === sourceIssue.title);
|
||||
|
||||
expect(twiceImportedAgents).toHaveLength(2);
|
||||
expect(new Set(twiceImportedAgents.map((agent) => agent.name)).size).toBe(2);
|
||||
expect(twiceImportedProjects).toHaveLength(2);
|
||||
expect(twiceImportedMatchingIssues).toHaveLength(2);
|
||||
expect(new Set(twiceImportedMatchingIssues.map((issue) => issue.identifier)).size).toBe(2);
|
||||
expect(twiceImportedIssues).toHaveLength(2);
|
||||
|
||||
const zipPath = path.join(tempRoot, "exported-company.zip");
|
||||
const portableFiles: Record<string, string> = {};
|
||||
@@ -601,16 +498,10 @@ describeEmbeddedPostgres("paperclipai company import/export e2e", () => {
|
||||
"company,agents,projects,issues",
|
||||
"--yes",
|
||||
],
|
||||
{
|
||||
apiBase,
|
||||
configPath,
|
||||
paperclipHome,
|
||||
instanceId: paperclipInstanceId,
|
||||
shellHome: cliShellHome,
|
||||
},
|
||||
{ apiBase, configPath },
|
||||
);
|
||||
|
||||
expect(importedFromZip.company.action).toBe("created");
|
||||
expect(importedFromZip.agents.some((agent) => agent.action === "created")).toBe(true);
|
||||
}, 90_000);
|
||||
}, 60_000);
|
||||
});
|
||||
|
||||
@@ -160,7 +160,6 @@ describe("renderCompanyImportPreview", () => {
|
||||
path: "COMPANY.md",
|
||||
name: "Source Co",
|
||||
description: null,
|
||||
attachmentMaxBytes: null,
|
||||
brandColor: null,
|
||||
logoPath: null,
|
||||
requireBoardApprovalForNewAgents: false,
|
||||
@@ -376,7 +375,6 @@ describe("import selection catalog", () => {
|
||||
path: "COMPANY.md",
|
||||
name: "Source Co",
|
||||
description: null,
|
||||
attachmentMaxBytes: null,
|
||||
brandColor: null,
|
||||
logoPath: "images/company-logo.png",
|
||||
requireBoardApprovalForNewAgents: false,
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
import path from "node:path";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { collectEnvLabDoctorStatus, resolveEnvLabSshStatePath } from "../commands/env-lab.js";
|
||||
|
||||
describe("env-lab command", () => {
|
||||
it("resolves the default SSH fixture state path under the instance root", () => {
|
||||
const statePath = resolveEnvLabSshStatePath("fixture-test");
|
||||
|
||||
expect(statePath).toContain(
|
||||
path.join("instances", "fixture-test", "env-lab", "ssh-fixture", "state.json"),
|
||||
);
|
||||
});
|
||||
|
||||
it("reports doctor status for an instance without a running fixture", async () => {
|
||||
const status = await collectEnvLabDoctorStatus({ instance: "fixture-test-missing" });
|
||||
|
||||
expect(status.statePath).toContain(
|
||||
path.join("instances", "fixture-test-missing", "env-lab", "ssh-fixture", "state.json"),
|
||||
);
|
||||
expect(typeof status.ssh.supported).toBe("boolean");
|
||||
expect(status.ssh.running).toBe(false);
|
||||
expect(status.ssh.environment).toBeNull();
|
||||
});
|
||||
});
|
||||
@@ -190,9 +190,8 @@ describe("worktree helpers", () => {
|
||||
).toEqual(["worktree", "add", "-b", "my-worktree", "/tmp/my-worktree", "origin/main"]);
|
||||
});
|
||||
|
||||
it("rewrites auth URLs only when they already include a port", () => {
|
||||
it("rewrites loopback auth URLs to the new port only", () => {
|
||||
expect(rewriteLocalUrlPort("http://127.0.0.1:3100", 3110)).toBe("http://127.0.0.1:3110/");
|
||||
expect(rewriteLocalUrlPort("http://my-host.ts.net:3100", 3110)).toBe("http://my-host.ts.net:3110/");
|
||||
expect(rewriteLocalUrlPort("https://paperclip.example", 3110)).toBe("https://paperclip.example");
|
||||
});
|
||||
|
||||
@@ -600,7 +599,7 @@ describe("worktree helpers", () => {
|
||||
fs.rmSync(tempRoot, { recursive: true, force: true });
|
||||
}
|
||||
},
|
||||
30000,
|
||||
20000,
|
||||
);
|
||||
|
||||
it("avoids ports already claimed by sibling worktree instance configs", async () => {
|
||||
@@ -882,7 +881,7 @@ describe("worktree helpers", () => {
|
||||
}
|
||||
fs.rmSync(tempRoot, { recursive: true, force: true });
|
||||
}
|
||||
}, 30_000);
|
||||
}, 20_000);
|
||||
|
||||
it("restores the current worktree config and instance data if reseed fails", async () => {
|
||||
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-worktree-reseed-rollback-"));
|
||||
@@ -1039,7 +1038,7 @@ describe("worktree helpers", () => {
|
||||
execFileSync("git", ["worktree", "remove", "--force", worktreePath], { cwd: repoRoot, stdio: "ignore" });
|
||||
fs.rmSync(tempRoot, { recursive: true, force: true });
|
||||
}
|
||||
}, 15_000);
|
||||
});
|
||||
|
||||
it("creates and initializes a worktree from the top-level worktree:make command", async () => {
|
||||
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-worktree-make-"));
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import type { CLIAdapterModule } from "@paperclipai/adapter-utils";
|
||||
import { printAcpxStreamEvent } from "@paperclipai/adapter-acpx-local/cli";
|
||||
import { printClaudeStreamEvent } from "@paperclipai/adapter-claude-local/cli";
|
||||
import { printCodexStreamEvent } from "@paperclipai/adapter-codex-local/cli";
|
||||
import { printCursorStreamEvent } from "@paperclipai/adapter-cursor-local/cli";
|
||||
@@ -15,11 +14,6 @@ const claudeLocalCLIAdapter: CLIAdapterModule = {
|
||||
formatStdoutEvent: printClaudeStreamEvent,
|
||||
};
|
||||
|
||||
const acpxLocalCLIAdapter: CLIAdapterModule = {
|
||||
type: "acpx_local",
|
||||
formatStdoutEvent: printAcpxStreamEvent,
|
||||
};
|
||||
|
||||
const codexLocalCLIAdapter: CLIAdapterModule = {
|
||||
type: "codex_local",
|
||||
formatStdoutEvent: printCodexStreamEvent,
|
||||
@@ -52,7 +46,6 @@ const openclawGatewayCLIAdapter: CLIAdapterModule = {
|
||||
|
||||
const adaptersByType = new Map<string, CLIAdapterModule>(
|
||||
[
|
||||
acpxLocalCLIAdapter,
|
||||
claudeLocalCLIAdapter,
|
||||
codexLocalCLIAdapter,
|
||||
openCodeLocalCLIAdapter,
|
||||
|
||||
@@ -61,7 +61,6 @@ interface IssueUpdateOptions extends BaseClientOptions {
|
||||
interface IssueCommentOptions extends BaseClientOptions {
|
||||
body: string;
|
||||
reopen?: boolean;
|
||||
resume?: boolean;
|
||||
}
|
||||
|
||||
interface IssueCheckoutOptions extends BaseClientOptions {
|
||||
@@ -242,14 +241,12 @@ export function registerIssueCommands(program: Command): void {
|
||||
.argument("<issueId>", "Issue ID")
|
||||
.requiredOption("--body <text>", "Comment body")
|
||||
.option("--reopen", "Reopen if issue is done/cancelled")
|
||||
.option("--resume", "Request explicit follow-up and wake the assignee when resumable")
|
||||
.action(async (issueId: string, opts: IssueCommentOptions) => {
|
||||
try {
|
||||
const ctx = resolveCommandContext(opts);
|
||||
const payload = addIssueCommentSchema.parse({
|
||||
body: opts.body,
|
||||
reopen: opts.reopen,
|
||||
resume: opts.resume,
|
||||
});
|
||||
const comment = await ctx.api.post<IssueComment>(`/api/issues/${issueId}/comments`, payload);
|
||||
printOutput(comment, { json: ctx.json });
|
||||
|
||||
@@ -1,174 +0,0 @@
|
||||
import path from "node:path";
|
||||
import type { Command } from "commander";
|
||||
import * as p from "@clack/prompts";
|
||||
import pc from "picocolors";
|
||||
import {
|
||||
buildSshEnvLabFixtureConfig,
|
||||
getSshEnvLabSupport,
|
||||
readSshEnvLabFixtureStatus,
|
||||
startSshEnvLabFixture,
|
||||
stopSshEnvLabFixture,
|
||||
} from "@paperclipai/adapter-utils/ssh";
|
||||
import { resolvePaperclipInstanceId, resolvePaperclipInstanceRoot } from "../config/home.js";
|
||||
|
||||
export function resolveEnvLabSshStatePath(instanceId?: string): string {
|
||||
const resolvedInstanceId = resolvePaperclipInstanceId(instanceId);
|
||||
return path.resolve(
|
||||
resolvePaperclipInstanceRoot(resolvedInstanceId),
|
||||
"env-lab",
|
||||
"ssh-fixture",
|
||||
"state.json",
|
||||
);
|
||||
}
|
||||
|
||||
function printJson(value: unknown) {
|
||||
process.stdout.write(`${JSON.stringify(value, null, 2)}\n`);
|
||||
}
|
||||
|
||||
function summarizeFixture(state: {
|
||||
host: string;
|
||||
port: number;
|
||||
username: string;
|
||||
workspaceDir: string;
|
||||
sshdLogPath: string;
|
||||
}) {
|
||||
p.log.message(`Host: ${pc.cyan(state.host)}:${pc.cyan(String(state.port))}`);
|
||||
p.log.message(`User: ${pc.cyan(state.username)}`);
|
||||
p.log.message(`Workspace: ${pc.cyan(state.workspaceDir)}`);
|
||||
p.log.message(`Log: ${pc.dim(state.sshdLogPath)}`);
|
||||
}
|
||||
|
||||
export async function collectEnvLabDoctorStatus(opts: { instance?: string }) {
|
||||
const statePath = resolveEnvLabSshStatePath(opts.instance);
|
||||
const [sshSupport, sshStatus] = await Promise.all([
|
||||
getSshEnvLabSupport(),
|
||||
readSshEnvLabFixtureStatus(statePath),
|
||||
]);
|
||||
const environment = sshStatus.state ? await buildSshEnvLabFixtureConfig(sshStatus.state) : null;
|
||||
|
||||
return {
|
||||
statePath,
|
||||
ssh: {
|
||||
supported: sshSupport.supported,
|
||||
reason: sshSupport.reason,
|
||||
running: sshStatus.running,
|
||||
state: sshStatus.state,
|
||||
environment,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export async function envLabUpCommand(opts: { instance?: string; json?: boolean }) {
|
||||
const statePath = resolveEnvLabSshStatePath(opts.instance);
|
||||
const state = await startSshEnvLabFixture({ statePath });
|
||||
const environment = await buildSshEnvLabFixtureConfig(state);
|
||||
|
||||
if (opts.json) {
|
||||
printJson({ state, environment });
|
||||
return;
|
||||
}
|
||||
|
||||
p.log.success("SSH env-lab fixture is running.");
|
||||
summarizeFixture(state);
|
||||
p.log.message(`State: ${pc.dim(statePath)}`);
|
||||
}
|
||||
|
||||
export async function envLabStatusCommand(opts: { instance?: string; json?: boolean }) {
|
||||
const statePath = resolveEnvLabSshStatePath(opts.instance);
|
||||
const status = await readSshEnvLabFixtureStatus(statePath);
|
||||
const environment = status.state ? await buildSshEnvLabFixtureConfig(status.state) : null;
|
||||
|
||||
if (opts.json) {
|
||||
printJson({ ...status, environment, statePath });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!status.state || !status.running) {
|
||||
p.log.info(`SSH env-lab fixture is not running (${pc.dim(statePath)}).`);
|
||||
return;
|
||||
}
|
||||
|
||||
p.log.success("SSH env-lab fixture is running.");
|
||||
summarizeFixture(status.state);
|
||||
p.log.message(`State: ${pc.dim(statePath)}`);
|
||||
}
|
||||
|
||||
export async function envLabDownCommand(opts: { instance?: string; json?: boolean }) {
|
||||
const statePath = resolveEnvLabSshStatePath(opts.instance);
|
||||
const stopped = await stopSshEnvLabFixture(statePath);
|
||||
|
||||
if (opts.json) {
|
||||
printJson({ stopped, statePath });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!stopped) {
|
||||
p.log.info(`No SSH env-lab fixture was running (${pc.dim(statePath)}).`);
|
||||
return;
|
||||
}
|
||||
|
||||
p.log.success("SSH env-lab fixture stopped.");
|
||||
p.log.message(`State: ${pc.dim(statePath)}`);
|
||||
}
|
||||
|
||||
export async function envLabDoctorCommand(opts: { instance?: string; json?: boolean }) {
|
||||
const status = await collectEnvLabDoctorStatus(opts);
|
||||
|
||||
if (opts.json) {
|
||||
printJson(status);
|
||||
return;
|
||||
}
|
||||
|
||||
if (status.ssh.supported) {
|
||||
p.log.success("SSH fixture prerequisites are installed.");
|
||||
} else {
|
||||
p.log.warn(`SSH fixture prerequisites are incomplete: ${status.ssh.reason ?? "unknown reason"}`);
|
||||
}
|
||||
|
||||
if (status.ssh.state && status.ssh.running) {
|
||||
p.log.success("SSH env-lab fixture is running.");
|
||||
summarizeFixture(status.ssh.state);
|
||||
p.log.message(`Private key: ${pc.dim(status.ssh.state.clientPrivateKeyPath)}`);
|
||||
p.log.message(`Known hosts: ${pc.dim(status.ssh.state.knownHostsPath)}`);
|
||||
} else if (status.ssh.state) {
|
||||
p.log.warn("SSH env-lab fixture state exists, but the process is not running.");
|
||||
p.log.message(`State: ${pc.dim(status.statePath)}`);
|
||||
} else {
|
||||
p.log.info("SSH env-lab fixture is not running.");
|
||||
p.log.message(`State: ${pc.dim(status.statePath)}`);
|
||||
}
|
||||
|
||||
p.log.message(`Cleanup: ${pc.dim("pnpm paperclipai env-lab down")}`);
|
||||
}
|
||||
|
||||
export function registerEnvLabCommands(program: Command) {
|
||||
const envLab = program.command("env-lab").description("Deterministic local environment fixtures");
|
||||
|
||||
envLab
|
||||
.command("up")
|
||||
.description("Start the default SSH env-lab fixture")
|
||||
.option("-i, --instance <id>", "Paperclip instance id (default: current/default)")
|
||||
.option("--json", "Print machine-readable fixture details")
|
||||
.action(envLabUpCommand);
|
||||
|
||||
envLab
|
||||
.command("status")
|
||||
.description("Show the current SSH env-lab fixture state")
|
||||
.option("-i, --instance <id>", "Paperclip instance id (default: current/default)")
|
||||
.option("--json", "Print machine-readable fixture details")
|
||||
.action(envLabStatusCommand);
|
||||
|
||||
envLab
|
||||
.command("down")
|
||||
.description("Stop the default SSH env-lab fixture")
|
||||
.option("-i, --instance <id>", "Paperclip instance id (default: current/default)")
|
||||
.option("--json", "Print machine-readable stop details")
|
||||
.action(envLabDownCommand);
|
||||
|
||||
envLab
|
||||
.command("doctor")
|
||||
.description("Check SSH fixture prerequisites and current status")
|
||||
.option("-i, --instance <id>", "Paperclip instance id (default: current/default)")
|
||||
.option("--json", "Print machine-readable diagnostic details")
|
||||
.action(envLabDoctorCommand);
|
||||
}
|
||||
@@ -75,6 +75,11 @@ function nonEmpty(value: string | null | undefined): string | null {
|
||||
return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
|
||||
}
|
||||
|
||||
function isLoopbackHost(hostname: string): boolean {
|
||||
const value = hostname.trim().toLowerCase();
|
||||
return value === "127.0.0.1" || value === "localhost" || value === "::1";
|
||||
}
|
||||
|
||||
export function sanitizeWorktreeInstanceId(rawValue: string): string {
|
||||
const trimmed = rawValue.trim().toLowerCase();
|
||||
const normalized = trimmed
|
||||
@@ -163,8 +168,7 @@ export function rewriteLocalUrlPort(rawUrl: string | undefined, port: number): s
|
||||
if (!rawUrl) return undefined;
|
||||
try {
|
||||
const parsed = new URL(rawUrl);
|
||||
// The URL API normalizes default ports like :80/:443 to "", so treat them as stable URLs.
|
||||
if (!parsed.port) return rawUrl;
|
||||
if (!isLoopbackHost(parsed.hostname)) return rawUrl;
|
||||
parsed.port = String(port);
|
||||
return parsed.toString();
|
||||
} catch {
|
||||
|
||||
@@ -1311,7 +1311,6 @@ async function seedWorktreeDatabase(input: {
|
||||
backupDir: path.resolve(input.targetPaths.backupDir, "seed"),
|
||||
retention: { dailyDays: 7, weeklyWeeks: 4, monthlyMonths: 1 },
|
||||
filenamePrefix: `${input.instanceId}-seed`,
|
||||
backupEngine: "javascript",
|
||||
includeMigrationJournal: true,
|
||||
excludeTables: seedPlan.excludedTables,
|
||||
nullifyColumns: seedPlan.nullifyColumns,
|
||||
|
||||
@@ -8,7 +8,6 @@ import { heartbeatRun } from "./commands/heartbeat-run.js";
|
||||
import { runCommand } from "./commands/run.js";
|
||||
import { bootstrapCeoInvite } from "./commands/auth-bootstrap-ceo.js";
|
||||
import { dbBackupCommand } from "./commands/db-backup.js";
|
||||
import { registerEnvLabCommands } from "./commands/env-lab.js";
|
||||
import { registerContextCommands } from "./commands/client/context.js";
|
||||
import { registerCompanyCommands } from "./commands/client/company.js";
|
||||
import { registerIssueCommands } from "./commands/client/issue.js";
|
||||
@@ -148,7 +147,6 @@ registerDashboardCommands(program);
|
||||
registerRoutineCommands(program);
|
||||
registerFeedbackCommands(program);
|
||||
registerWorktreeCommands(program);
|
||||
registerEnvLabCommands(program);
|
||||
registerPluginCommands(program);
|
||||
|
||||
const auth = program.command("auth").description("Authentication and bootstrap utilities");
|
||||
|
||||
11
doc/CLI.md
11
doc/CLI.md
@@ -2,7 +2,7 @@
|
||||
|
||||
Paperclip CLI now supports both:
|
||||
|
||||
- instance setup/diagnostics (`onboard`, `doctor`, `configure`, `env`, `allowed-hostname`, `env-lab`)
|
||||
- instance setup/diagnostics (`onboard`, `doctor`, `configure`, `env`, `allowed-hostname`)
|
||||
- control-plane client operations (issues, approvals, agents, activity, dashboard)
|
||||
|
||||
## Base Usage
|
||||
@@ -45,15 +45,6 @@ Allow an authenticated/private hostname (for example custom Tailscale DNS):
|
||||
pnpm paperclipai allowed-hostname dotta-macbook-pro
|
||||
```
|
||||
|
||||
Bring up the default local SSH fixture for environment testing:
|
||||
|
||||
```sh
|
||||
pnpm paperclipai env-lab up
|
||||
pnpm paperclipai env-lab doctor
|
||||
pnpm paperclipai env-lab status --json
|
||||
pnpm paperclipai env-lab down
|
||||
```
|
||||
|
||||
All client commands support:
|
||||
|
||||
- `--data-dir <path>`
|
||||
|
||||
@@ -59,11 +59,11 @@ cp .env.example .env
|
||||
# DATABASE_URL=postgres://paperclip:paperclip@localhost:5432/paperclip
|
||||
```
|
||||
|
||||
Run migrations:
|
||||
Run migrations (once the migration generation issue is fixed) or use `drizzle-kit push`:
|
||||
|
||||
```sh
|
||||
DATABASE_URL=postgres://paperclip:paperclip@localhost:5432/paperclip \
|
||||
pnpm db:migrate
|
||||
npx drizzle-kit push
|
||||
```
|
||||
|
||||
Start the server:
|
||||
@@ -100,27 +100,37 @@ postgres://postgres.[PROJECT-REF]:[PASSWORD]@aws-0-[REGION].pooler.supabase.com:
|
||||
|
||||
### Configure
|
||||
|
||||
For the application runtime, use a direct PostgreSQL connection unless the database client has explicit prepared-statement configuration for your pooling mode:
|
||||
Set `DATABASE_URL` in your `.env`:
|
||||
|
||||
```sh
|
||||
DATABASE_URL=postgres://postgres.[PROJECT-REF]:[PASSWORD]@aws-0-[REGION].pooler.supabase.com:5432/postgres
|
||||
DATABASE_URL=postgres://postgres.[PROJECT-REF]:[PASSWORD]@aws-0-[REGION].pooler.supabase.com:6543/postgres
|
||||
```
|
||||
|
||||
If you later run the app with a pooled runtime URL, set `DATABASE_MIGRATION_URL` to the direct connection URL. Paperclip uses it for startup schema checks/migrations and plugin namespace migrations, while the app continues to use `DATABASE_URL` for runtime queries:
|
||||
For hosted deployments that use a pooled runtime URL, set
|
||||
`DATABASE_MIGRATION_URL` to the direct connection URL. Paperclip uses it for
|
||||
startup schema checks/migrations and plugin namespace migrations, while the app
|
||||
continues to use `DATABASE_URL` for runtime queries:
|
||||
|
||||
```sh
|
||||
DATABASE_URL=postgres://postgres.[PROJECT-REF]:[PASSWORD]@aws-0-[REGION].pooler.supabase.com:6543/postgres
|
||||
DATABASE_MIGRATION_URL=postgres://postgres.[PROJECT-REF]:[PASSWORD]@aws-0-[REGION].pooler.supabase.com:5432/postgres
|
||||
```
|
||||
|
||||
If your hosted database requires transaction-pooling-only connections, use a direct or session-pooled connection for Paperclip until runtime pooling support is documented in this guide. Do not edit database client source files as part of deployment setup.
|
||||
If using connection pooling (port 6543), the `postgres` client must disable prepared statements. Update `packages/db/src/client.ts`:
|
||||
|
||||
```ts
|
||||
export function createDb(url: string) {
|
||||
const sql = postgres(url, { prepare: false });
|
||||
return drizzlePg(sql, { schema });
|
||||
}
|
||||
```
|
||||
|
||||
### Push the schema
|
||||
|
||||
```sh
|
||||
# Use the direct connection (port 5432) for schema changes
|
||||
DATABASE_URL=postgres://postgres.[PROJECT-REF]:[PASSWORD]@...5432/postgres \
|
||||
pnpm db:migrate
|
||||
npx drizzle-kit push
|
||||
```
|
||||
|
||||
### Free tier limits
|
||||
@@ -143,22 +153,6 @@ The database mode is controlled by `DATABASE_URL`:
|
||||
|
||||
Your Drizzle schema (`packages/db/src/schema/`) stays the same regardless of mode.
|
||||
|
||||
## Plugin database namespaces
|
||||
|
||||
The plugin runtime tracks plugin-owned database namespaces and migrations in `plugin_database_namespaces` and `plugin_migrations`. Hosted deployments that separate runtime and migration connections should set `DATABASE_MIGRATION_URL`; plugin namespace migration work uses the migration connection when present.
|
||||
|
||||
## Backups
|
||||
|
||||
Paperclip supports automatic and manual logical database backups. These dumps include
|
||||
non-system database schemas such as `public`, the Drizzle migration journal, and
|
||||
plugin-owned database schemas. See `doc/DEVELOPING.md` for the current
|
||||
`paperclipai db:backup` / `pnpm db:backup` commands and backup retention
|
||||
configuration.
|
||||
|
||||
Database backups do not include non-database instance files such as local-disk
|
||||
uploads, workspace files, or the local encrypted secrets master key. Back those paths
|
||||
up separately when you need full instance disaster recovery.
|
||||
|
||||
## Secret storage
|
||||
|
||||
Paperclip stores secret metadata and versions in:
|
||||
|
||||
@@ -43,8 +43,6 @@ This starts:
|
||||
|
||||
`pnpm dev` and `pnpm dev:once` are now idempotent for the current repo and instance: if the matching Paperclip dev runner is already alive, Paperclip reports the existing process instead of starting a duplicate.
|
||||
|
||||
Issue execution may also use project execution workspace policies and workspace runtime services for per-project worktrees, preview servers, and managed dev commands. Configure those through the project workspace/runtime surfaces rather than starting long-running unmanaged processes when a task needs a reusable service.
|
||||
|
||||
## Storybook
|
||||
|
||||
The board UI Storybook keeps stories and Storybook config under `ui/storybook/` so component review files stay out of the app source routes.
|
||||
@@ -115,8 +113,6 @@ pnpm test:release-smoke
|
||||
|
||||
These browser suites are intended for targeted local verification and CI, not the default agent/human test command.
|
||||
|
||||
For normal issue work, start with the smallest targeted check that proves the change. Reserve repo-wide typecheck/build/test runs for PR-ready handoff or changes broad enough that narrow checks do not cover the risk.
|
||||
|
||||
## One-Command Local Run
|
||||
|
||||
For a first-time local install, you can bootstrap and run in one command:
|
||||
@@ -198,8 +194,6 @@ For `codex_local`, Paperclip also manages a per-company Codex home under the ins
|
||||
|
||||
If the `codex` CLI is not installed or not on `PATH`, `codex_local` agent runs fail at execution time with a clear adapter error. Quota polling uses a short-lived `codex app-server` subprocess: when `codex` cannot be spawned, that provider reports `ok: false` in aggregated quota results and the API server keeps running (it must not exit on a missing binary).
|
||||
|
||||
Local adapters require their corresponding CLI/session setup on the machine running Paperclip. External adapters are installed through the adapter/plugin flow and should not require hardcoded imports in `server/` or `ui/`.
|
||||
|
||||
## Worktree-local Instances
|
||||
|
||||
When developing from multiple git worktrees, do not point two Paperclip servers at the same embedded PostgreSQL data directory.
|
||||
@@ -421,9 +415,7 @@ If you set `DATABASE_URL`, the server will use that instead of embedded PostgreS
|
||||
|
||||
## Automatic DB Backups
|
||||
|
||||
Paperclip can run automatic logical database backups on a timer. These backups cover
|
||||
non-system database schemas, including migration history and plugin-owned database
|
||||
schemas. Defaults:
|
||||
Paperclip can run automatic DB backups on a timer. Defaults:
|
||||
|
||||
- enabled
|
||||
- every 60 minutes
|
||||
@@ -451,10 +443,6 @@ Environment overrides:
|
||||
- `PAPERCLIP_DB_BACKUP_RETENTION_DAYS=<days>`
|
||||
- `PAPERCLIP_DB_BACKUP_DIR=/absolute/or/~/path`
|
||||
|
||||
DB backups are not full instance filesystem backups. For full local disaster
|
||||
recovery, also back up local storage files and the local encrypted secrets key if
|
||||
those providers are enabled.
|
||||
|
||||
## Secrets in Dev
|
||||
|
||||
Agent env vars now support secret references. By default, secret values are stored with local encryption and only secret refs are persisted in agent config.
|
||||
|
||||
15
doc/GOAL.md
15
doc/GOAL.md
@@ -23,7 +23,7 @@ Paperclip is the command, communication, and control plane for a company of AI a
|
||||
- **Track work in real time** — see at any moment what every agent is working on
|
||||
- **Control costs** — token salary budgets per agent, spend tracking, burn rate
|
||||
- **Align to goals** — agents see how their work serves the bigger mission
|
||||
- **Preserve work context** — comments, documents, work products, attachments, and company state stay attached to the work
|
||||
- **Store company knowledge** — a shared brain for the organization
|
||||
|
||||
## Architecture
|
||||
|
||||
@@ -36,20 +36,17 @@ The central nervous system. Manages:
|
||||
- Agent registry and org chart
|
||||
- Task assignment and status
|
||||
- Budget and token spend tracking
|
||||
- Issue comments, documents, work products, attachments, and company state
|
||||
- Company knowledge base
|
||||
- Goal hierarchy (company → team → agent → task)
|
||||
- Heartbeat monitoring — know when agents are alive, idle, or stuck
|
||||
|
||||
It also enforces execution-control semantics such as single-assignee issues, atomic checkout and execution locks, blockers, recovery issues, and workspace/runtime controls.
|
||||
|
||||
### 2. Execution Services (adapters)
|
||||
|
||||
Agents run externally and report into the control plane. Adapters connect different execution environments and define how a heartbeat is invoked, observed, and cancelled:
|
||||
Agents run externally and report into the control plane. An agent is just Python code that gets kicked off and does work. Adapters connect different execution environments:
|
||||
|
||||
- **Local CLI/session adapters** — built-in adapters for tools such as Claude Code, Codex, Gemini, OpenCode, Pi, and Cursor
|
||||
- **HTTP/process-style adapters** — command or webhook/API integrations for custom runtimes
|
||||
- **OpenClaw gateway** — integration for OpenClaw-style remote agents
|
||||
- **External adapter plugins** — dynamically loaded adapters installed outside the core app
|
||||
- **OpenClaw** — initial adapter target
|
||||
- **Heartbeat loop** — simple custom Python that loops, checks in, does work
|
||||
- **Others** — any runtime that can call an API
|
||||
|
||||
The control plane doesn't run agents. It orchestrates them. Agents run wherever they run and phone home.
|
||||
|
||||
|
||||
@@ -32,14 +32,12 @@ Then you define who reports to the CEO: a CTO managing programmers, a CMO managi
|
||||
|
||||
### Agent Execution
|
||||
|
||||
Paperclip supports several ways to run an agent's heartbeat:
|
||||
There are two fundamental modes for running an agent's heartbeat:
|
||||
|
||||
1. **Local CLI/session adapters** — Paperclip starts or resumes local coding-tool sessions such as Claude Code, Codex, Gemini, OpenCode, Pi, and Cursor, then tracks the run.
|
||||
2. **Run a command** — Paperclip kicks off a process (shell command, Python script, etc.) and tracks it. The heartbeat is "execute this and monitor it."
|
||||
3. **Fire and forget a request** — Paperclip sends a webhook/API call to an externally running agent. The heartbeat is "notify this agent to wake up." OpenClaw-style hooks work this way.
|
||||
4. **External adapter plugins** — Paperclip loads adapter packages through the plugin/adapter flow so self-hosted installs can add runtimes without hardcoding them in core.
|
||||
1. **Run a command** — Paperclip kicks off a process (shell command, Python script, etc.) and tracks it. The heartbeat is "execute this and monitor it."
|
||||
2. **Fire and forget a request** — Paperclip sends a webhook/API call to an externally running agent. The heartbeat is "notify this agent to wake up." (OpenClaw hooks work this way.)
|
||||
|
||||
Agent runs can use project and execution workspaces, managed runtime services such as preview/dev servers, adapter-specific session state, and HTTP/webhook-style execution. We provide sensible defaults, but the adapter is still the boundary: if a runtime can be invoked, observed, and authorized, Paperclip can coordinate it.
|
||||
We provide sensible defaults — a default agent that shells out to Claude Code or Codex with your configuration, remembers session IDs, runs basic scripts. But you can plug in anything.
|
||||
|
||||
### Task Management
|
||||
|
||||
@@ -56,7 +54,7 @@ I am researching the Facebook ads Granola uses (current task)
|
||||
|
||||
Tasks have parentage. Every task exists in service of a parent task, all the way up to the company goal. This is what keeps autonomous agents aligned — they can always answer "why am I doing this?"
|
||||
|
||||
The current issue model includes stable issue identifiers, parent/sub-issues, blockers, a single assignee, comments, issue documents, attachments and work products, and review/approval handoffs. That structure keeps work inspectable by both the board and agents while still allowing agents to decompose work into smaller tasks.
|
||||
More detailed task structure TBD.
|
||||
|
||||
## Principles
|
||||
|
||||
@@ -117,7 +115,7 @@ Paperclip’s core identity is a **control plane for autonomous AI companies**,
|
||||
|
||||
- Do not make the core product a general chat app. The current product definition is explicitly task/comment-centric and “not a chatbot,” and that boundary is valuable.
|
||||
- Do not build a complete Jira/GitHub replacement. The repo/docs already position Paperclip as organization orchestration, not focused on pull-request review.
|
||||
- Do not build enterprise-grade RBAC first. Paperclip now has authenticated mode, company memberships, instance roles, and permission grants, but fine-grained enterprise governance should remain secondary to the core company control plane.
|
||||
- Do not build enterprise-grade RBAC first. The current V1 spec still treats multi-board governance and fine-grained human permissions as out of scope, so the first multi-user version should be coarse and company-scoped.
|
||||
- Do not lead with raw bash logs and transcripts. Default view should be human-readable intent/progress, with raw detail beneath.
|
||||
- Do not force users to understand provider/API-key plumbing unless absolutely necessary. There are active onboarding/auth issues already; friction here is clearly real.
|
||||
|
||||
@@ -138,14 +136,11 @@ Paperclip’s core identity is a **control plane for autonomous AI companies**,
|
||||
5. **Output-first**
|
||||
Work is not done until the user can see the result: file, document, preview link, screenshot, plan, or PR.
|
||||
|
||||
6. **Execution visibility without log worship**
|
||||
Active runs, recovery issues, productivity review states, blockers, and work products should be first-class surfaces. Raw transcripts are available when needed, but they are not the primary product surface.
|
||||
|
||||
7. **Local-first, cloud-ready**
|
||||
6. **Local-first, cloud-ready**
|
||||
The mental model should not change between local solo use and shared/private or public/cloud deployment.
|
||||
|
||||
8. **Safe autonomy**
|
||||
7. **Safe autonomy**
|
||||
Auto mode is allowed; hidden token burn is not.
|
||||
|
||||
9. **Thin core, rich edges**
|
||||
8. **Thin core, rich edges**
|
||||
Put optional chat, knowledge, and special surfaces into plugins/extensions rather than bloating the control plane.
|
||||
|
||||
@@ -143,13 +143,6 @@ This keeps the default install path unchanged while allowing explicit installs w
|
||||
npx paperclipai@canary onboard
|
||||
```
|
||||
|
||||
The release script now verifies two things after a canary publish:
|
||||
|
||||
- the `canary` dist-tag resolves to the version that was just published
|
||||
- every published internal `@paperclipai/*` dependency referenced by that manifest exists on npm
|
||||
|
||||
It also treats `latest -> canary` as a failure by default, because npm metadata can otherwise leave the default install path pointing at an unreleased canary dependency graph. Only pass `./scripts/release.sh canary --allow-canary-latest` when that `latest` behavior is explicitly intended.
|
||||
|
||||
### Stable
|
||||
|
||||
Stable publishes use the npm dist-tag `latest`.
|
||||
@@ -176,58 +169,6 @@ That means:
|
||||
|
||||
See [doc/RELEASE-AUTOMATION-SETUP.md](RELEASE-AUTOMATION-SETUP.md) for the GitHub/npm setup steps.
|
||||
|
||||
## Release enrollment for new public packages
|
||||
|
||||
Paperclip does not auto-publish every non-private workspace package anymore.
|
||||
CI publishing is controlled by [`scripts/release-package-manifest.json`](../scripts/release-package-manifest.json).
|
||||
|
||||
When you add a new public package:
|
||||
|
||||
1. add it to the manifest and decide whether CI should publish it immediately
|
||||
2. if CI should publish it, bootstrap the package on npm before merge
|
||||
3. if CI should not publish it yet, keep `"publishFromCi": false`
|
||||
4. only enable `"publishFromCi": true` after npm trusted publishing is configured for that package
|
||||
|
||||
PR CI now checks changed release-enabled package manifests against npm. That catches a missing first-publish bootstrap before the change reaches `master`.
|
||||
|
||||
### One-time bootstrap sequence for a new package
|
||||
|
||||
The first publish of a brand-new package still needs one human maintainer with npm write access.
|
||||
After that, trusted publishing can take over.
|
||||
|
||||
Example for `@paperclipai/adapter-acpx-local` from the repo root:
|
||||
|
||||
```bash
|
||||
# safe preview
|
||||
pnpm run release:bootstrap-package -- @paperclipai/adapter-acpx-local
|
||||
|
||||
# one-time first publish from an authenticated maintainer machine
|
||||
pnpm run release:bootstrap-package -- @paperclipai/adapter-acpx-local --publish --otp 123456
|
||||
```
|
||||
|
||||
The helper script:
|
||||
|
||||
- checks that the package does not already exist on npm
|
||||
- builds the target package unless `--skip-build` is passed
|
||||
- runs `npm pack --dry-run` in the package directory
|
||||
- only runs the real `npm publish --access public` when `--publish --otp <code>` is provided
|
||||
|
||||
For the real `--publish` step, the maintainer machine must already be authenticated to npm.
|
||||
If `npm whoami` returns `401`, first run `npm logout --registry=https://registry.npmjs.org/` to clear any stale local auth, then run `npm login` or `npm adduser` locally as an npm org member, and finally rerun the helper.
|
||||
That local human auth is fine for the one-time bootstrap publish; we just do not want the same auth model inside CI.
|
||||
The helper now requires `--otp <code>` up front for `--publish`, so it fails before the real publish attempt if the one-time password is missing.
|
||||
|
||||
After that first publish succeeds:
|
||||
|
||||
1. open `https://www.npmjs.com/package/@paperclipai/adapter-acpx-local`
|
||||
2. go to `Settings` → `Trusted publishing`
|
||||
3. add repository `paperclipai/paperclip`
|
||||
4. set workflow filename to `release.yml`
|
||||
5. optionally go to `Settings` → `Publishing access` and enable `Require two-factor authentication and disallow tokens`
|
||||
6. keep `publishFromCi: true` in [`scripts/release-package-manifest.json`](../scripts/release-package-manifest.json)
|
||||
|
||||
Once those steps are done, future canary and stable publishes for that package are automated through GitHub OIDC. The manual step is only the first package creation on npm.
|
||||
|
||||
## Rollback model
|
||||
|
||||
Rollback does not unpublish anything.
|
||||
|
||||
@@ -67,27 +67,6 @@ Why:
|
||||
- the single `release.yml` workflow handles both canary and stable publishing
|
||||
- GitHub environments `npm-canary` and `npm-stable` still enforce different approval rules on the GitHub side
|
||||
|
||||
### 2.2.1. Newly added public packages need a bootstrap phase
|
||||
|
||||
Trusted publishing is configured on the npm package itself, not at the repo scope.
|
||||
That means a brand-new public package must not be auto-enrolled into CI publishing until its npm package exists and its trusted publisher has been configured.
|
||||
|
||||
Repo policy:
|
||||
|
||||
1. add every non-private package to [`scripts/release-package-manifest.json`](../scripts/release-package-manifest.json)
|
||||
2. set `"publishFromCi": true` only when CI is expected to publish that package
|
||||
3. if the package is not ready for CI publishing yet, keep `"publishFromCi": false`
|
||||
4. complete the package bootstrap before merging any PR that changes a release-enabled new package
|
||||
|
||||
Bootstrap sequence for a new package:
|
||||
|
||||
1. publish the package once from a trusted maintainer machine using normal npm auth
|
||||
2. open that package on npm and add the `paperclipai/paperclip` trusted publisher for `.github/workflows/release.yml`
|
||||
3. rerun or dry-run the release flow as needed to confirm CI publishing now works
|
||||
4. only then enable `"publishFromCi": true`
|
||||
|
||||
PR CI enforces this by checking changed release-enabled package manifests against npm. That keeps `master` canary publishing healthy while preserving the no-long-lived-token model for normal CI releases.
|
||||
|
||||
### 2.3. Verify trusted publishing before removing old auth
|
||||
|
||||
After the workflows are live:
|
||||
|
||||
@@ -63,8 +63,6 @@ It:
|
||||
- verifies the pushed commit
|
||||
- computes the canary version for the current UTC date
|
||||
- publishes under npm dist-tag `canary`
|
||||
- verifies that `canary` resolves to the just-published version and that published internal dependencies exist on npm
|
||||
- fails by default if npm leaves `latest` pointing at a canary; use `--allow-canary-latest` only when that state is intentional
|
||||
- creates a git tag `canary/vYYYY.MDD.P-canary.N`
|
||||
|
||||
Users install canaries with:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Paperclip V1 Implementation Spec
|
||||
|
||||
Status: Implementation contract for first release (V1)
|
||||
Date: 2026-04-28
|
||||
Date: 2026-02-17
|
||||
Audience: Product, engineering, and agent-integration authors
|
||||
Source inputs: `GOAL.md`, `PRODUCT.md`, `SPEC.md`, `DATABASE.md`, current monorepo code
|
||||
|
||||
@@ -37,9 +37,8 @@ These decisions close open questions from `SPEC.md` for V1.
|
||||
| Visibility | Full visibility to board and all agents in same company |
|
||||
| Communication | Tasks + comments only (no separate chat system) |
|
||||
| Task ownership | Single assignee; atomic checkout required for `in_progress` transition |
|
||||
| Recovery | Liveness/watchdog recovery preserves explicit ownership: retry lost execution continuity where safe, otherwise create visible recovery issues or require human escalation (see `doc/execution-semantics.md`) |
|
||||
| Agent adapters | Built-in `process`, `http`, local CLI/session adapters, and OpenClaw gateway support; external adapters can also be loaded through the adapter plugin flow |
|
||||
| Plugin framework | Local/self-hosted early plugin runtime is in scope; cloud marketplace and packaged public distribution remain out of scope |
|
||||
| Recovery | No automatic reassignment; work recovery stays manual/explicit |
|
||||
| Agent adapters | Built-in `process` and `http` adapters |
|
||||
| Auth | Mode-dependent human auth (`local_trusted` implicit board in current code; authenticated mode uses sessions), API keys for agents |
|
||||
| Budget period | Monthly UTC calendar window |
|
||||
| Budget enforcement | Soft alerts + hard limit auto-pause |
|
||||
@@ -74,7 +73,7 @@ V1 implementation extends this baseline into a company-centric, governance-aware
|
||||
|
||||
## 5.2 Out of Scope (V1)
|
||||
|
||||
- Cloud-grade plugin marketplace/distribution beyond the local/self-hosted plugin runtime
|
||||
- Plugin framework and third-party extension SDK
|
||||
- Revenue/expense accounting beyond model/token costs
|
||||
- Knowledge base subsystem
|
||||
- Public marketplace (ClipHub)
|
||||
@@ -124,16 +123,6 @@ Human auth tables (`users`, `sessions`, and provider-specific auth artifacts) ar
|
||||
- `name` text not null
|
||||
- `description` text null
|
||||
- `status` enum: `active | paused | archived`
|
||||
- `pause_reason` text null
|
||||
- `paused_at` timestamptz null
|
||||
- `issue_prefix` text not null
|
||||
- `issue_counter` int not null
|
||||
- `budget_monthly_cents` int not null default 0
|
||||
- `spent_monthly_cents` int not null default 0
|
||||
- `attachment_max_bytes` int not null
|
||||
- `require_board_approval_for_new_agents` boolean not null default false
|
||||
- feedback sharing consent fields
|
||||
- branding fields such as `brand_color`
|
||||
|
||||
Invariant: every business record belongs to exactly one company.
|
||||
|
||||
@@ -144,21 +133,15 @@ Invariant: every business record belongs to exactly one company.
|
||||
- `name` text not null
|
||||
- `role` text not null
|
||||
- `title` text null
|
||||
- `icon` text null
|
||||
- `status` enum: `active | paused | idle | running | error | pending_approval | terminated`
|
||||
- `status` enum: `active | paused | idle | running | error | terminated`
|
||||
- `reports_to` uuid fk `agents.id` null
|
||||
- `capabilities` text null
|
||||
- `adapter_type` text; built-ins include `process`, `http`, `claude_local`, `codex_local`, `gemini_local`, `opencode_local`, `pi_local`, `cursor`, and `openclaw_gateway`
|
||||
- `adapter_type` enum: `process | http`
|
||||
- `adapter_config` jsonb not null
|
||||
- `runtime_config` jsonb not null default `{}`; may include Paperclip runtime policy such as `modelProfiles.cheap.adapterConfig` for an optional low-cost model lane that does not change the primary adapter config
|
||||
- `default_environment_id` uuid fk `environments.id` null
|
||||
- `context_mode` enum: `thin | fat` default `thin`
|
||||
- `budget_monthly_cents` int not null default 0
|
||||
- `spent_monthly_cents` int not null default 0
|
||||
- pause fields: `pause_reason`, `paused_at`
|
||||
- `permissions` jsonb not null default `{}`
|
||||
- `last_heartbeat_at` timestamptz null
|
||||
- `metadata` jsonb null
|
||||
|
||||
Invariants:
|
||||
|
||||
@@ -212,7 +195,6 @@ Invariant:
|
||||
- `id` uuid pk
|
||||
- `company_id` uuid fk not null
|
||||
- `project_id` uuid fk `projects.id` null
|
||||
- `project_workspace_id` uuid fk `project_workspaces.id` null
|
||||
- `goal_id` uuid fk `goals.id` null
|
||||
- `parent_id` uuid fk `issues.id` null
|
||||
- `title` text not null
|
||||
@@ -220,22 +202,13 @@ Invariant:
|
||||
- `status` enum: `backlog | todo | in_progress | in_review | done | blocked | cancelled`
|
||||
- `priority` enum: `critical | high | medium | low`
|
||||
- `assignee_agent_id` uuid fk `agents.id` null
|
||||
- `assignee_user_id` text null
|
||||
- checkout/execution locks: `checkout_run_id`, `execution_run_id`, `execution_agent_name_key`, `execution_locked_at`
|
||||
- `created_by_agent_id` uuid fk `agents.id` null
|
||||
- `created_by_user_id` uuid fk `users.id` null
|
||||
- identifier fields: `issue_number`, `identifier`
|
||||
- origin fields: `origin_kind`, `origin_id`, `origin_run_id`, `origin_fingerprint`
|
||||
- `request_depth` int not null default 0
|
||||
- `billing_code` text null
|
||||
- `assignee_adapter_overrides` jsonb null
|
||||
- `execution_policy` jsonb null
|
||||
- `execution_state` jsonb null
|
||||
- execution workspace fields: `execution_workspace_id`, `execution_workspace_preference`, `execution_workspace_settings`
|
||||
- `started_at` timestamptz null
|
||||
- `completed_at` timestamptz null
|
||||
- `cancelled_at` timestamptz null
|
||||
- `hidden_at` timestamptz null
|
||||
|
||||
Invariants:
|
||||
|
||||
@@ -288,10 +261,10 @@ Invariant: each event must attach to agent and company; rollups are aggregation,
|
||||
|
||||
- `id` uuid pk
|
||||
- `company_id` uuid fk not null
|
||||
- `type` enum: `hire_agent | approve_ceo_strategy | budget_override_required | request_board_approval`
|
||||
- `type` enum: `hire_agent | approve_ceo_strategy`
|
||||
- `requested_by_agent_id` uuid fk `agents.id` null
|
||||
- `requested_by_user_id` uuid fk `users.id` null
|
||||
- `status` enum: `pending | revision_requested | approved | rejected | cancelled`
|
||||
- `status` enum: `pending | approved | rejected | cancelled`
|
||||
- `payload` jsonb not null
|
||||
- `decision_note` text null
|
||||
- `decided_by_user_id` uuid fk `users.id` null
|
||||
@@ -390,15 +363,6 @@ Operational policy:
|
||||
- `document_id` uuid fk not null
|
||||
- `key` text not null (`plan`, `design`, `notes`, etc.)
|
||||
|
||||
## 7.16 Current Implementation Addenda
|
||||
|
||||
The current implementation includes additional V1-control-plane tables beyond the original February snapshot:
|
||||
|
||||
- Issue structure and review: `issue_relations` for blockers, `labels`/`issue_labels`, `issue_thread_interactions`, `issue_approvals`, `issue_execution_decisions`, `issue_work_products`, `issue_inbox_archives`, `issue_read_states`, and issue reference mention indexes.
|
||||
- Execution and workspace control: `execution_workspaces`, `project_workspaces`, `workspace_runtime_services`, `workspace_operations`, `environments`, `environment_leases`, `agent_task_sessions`, `agent_runtime_state`, `agent_wakeup_requests`, heartbeat events, and watchdog decision tables.
|
||||
- Plugins and routines: `plugins`, plugin config/state/entities/jobs/logs/webhooks, plugin database namespaces/migrations, plugin company settings, and `routines`.
|
||||
- Access and operations: company memberships, instance roles, principal permission grants, invites, join requests, board API keys, CLI auth challenges, budget policies/incidents, feedback exports/votes, company skills, sidebar preferences, and company logos.
|
||||
|
||||
## 8. State Machines
|
||||
|
||||
## 8.1 Agent Status
|
||||
@@ -431,14 +395,7 @@ Side effects:
|
||||
- entering `done` sets `completed_at`
|
||||
- entering `cancelled` sets `cancelled_at`
|
||||
|
||||
V1 non-terminal liveness rule:
|
||||
|
||||
- agent-owned `todo`, `in_progress`, `in_review`, and `blocked` issues must have a live execution path, an explicit waiting path, or an explicit recovery path
|
||||
- `in_review` is healthy only when a typed execution participant, pending issue-thread interaction or approval, user owner, active run, queued wake, or explicit recovery issue owns the next action
|
||||
- a blocked chain is covered only when each unresolved leaf issue is live or explicitly waiting
|
||||
- when Paperclip cannot safely infer the next action, it surfaces the problem through visible blocked/recovery work instead of silently completing or reassigning work
|
||||
|
||||
Detailed ownership, execution, blocker, active-run watchdog, crash-recovery, and non-terminal liveness semantics are documented in `doc/execution-semantics.md`.
|
||||
Detailed ownership, execution, blocker, and crash-recovery semantics are documented in `doc/execution-semantics.md`.
|
||||
|
||||
## 8.3 Approval Status
|
||||
|
||||
@@ -527,7 +484,6 @@ All endpoints are under `/api` and return JSON.
|
||||
- `DELETE /issues/:issueId/documents/:key`
|
||||
- `POST /issues/:issueId/checkout`
|
||||
- `POST /issues/:issueId/release`
|
||||
- `POST /issues/:issueId/admin/force-release` (board-only lock recovery)
|
||||
- `POST /issues/:issueId/comments`
|
||||
- `GET /issues/:issueId/comments`
|
||||
- `POST /companies/:companyId/issues/:issueId/attachments` (multipart upload)
|
||||
@@ -552,8 +508,6 @@ Server behavior:
|
||||
2. if updated row count is 0, return `409` with current owner/status
|
||||
3. successful checkout sets `assignee_agent_id`, `status = in_progress`, and `started_at`
|
||||
|
||||
`POST /issues/:issueId/admin/force-release` is an operator recovery endpoint for stale harness locks. It requires board access to the issue company, clears checkout and execution run lock fields, and may clear the agent assignee when `clearAssignee=true` is passed. The route must write an `issue.admin_force_release` activity log entry containing the previous checkout and execution run IDs.
|
||||
|
||||
## 10.5 Projects
|
||||
|
||||
- `GET /companies/:companyId/projects`
|
||||
@@ -599,17 +553,6 @@ Dashboard payload must include:
|
||||
- `422` semantic rule violation
|
||||
- `500` server error
|
||||
|
||||
## 10.10 Current Implementation API Addenda
|
||||
|
||||
The current app also exposes V1-supporting surfaces for:
|
||||
|
||||
- issue thread interactions (`suggest_tasks`, `ask_user_questions`, `request_confirmation`)
|
||||
- issue approvals, issue references/search, labels, read state, inbox/archive state, and work products
|
||||
- execution workspaces, project workspaces, workspace runtime services, and workspace operations
|
||||
- routines and scheduled/API/webhook triggers
|
||||
- plugin installation, configuration, state, jobs, logs, webhooks, and plugin database namespace migration
|
||||
- company import/export preview/apply, feedback export/vote routes, instance backup/config routes, invites, join requests, memberships, and permission grants
|
||||
|
||||
## 11. Heartbeat and Adapter Contract
|
||||
|
||||
## 11.1 Adapter Interface
|
||||
@@ -676,7 +619,7 @@ Per-agent schedule fields in `adapter_config`:
|
||||
|
||||
- `enabled` boolean
|
||||
- `intervalSec` integer (minimum 30)
|
||||
- `maxConcurrentRuns` integer; new agents default to `20`; scheduler clamps configured values to `1..50`
|
||||
- `maxConcurrentRuns` integer; new agents default to `5`
|
||||
|
||||
Scheduler must skip invocation when:
|
||||
|
||||
@@ -785,14 +728,13 @@ Required UX behaviors:
|
||||
|
||||
- Node 20+
|
||||
- `DATABASE_URL` optional
|
||||
- if unset, auto-use embedded PostgreSQL under `~/.paperclip/instances/default/db`
|
||||
- if unset, auto-use PGlite and push schema
|
||||
|
||||
## 15.2 Migrations
|
||||
|
||||
- Drizzle migrations are source of truth
|
||||
- local/dev startup applies pending migrations automatically where supported
|
||||
- `pnpm db:migrate` applies pending migrations manually
|
||||
- no destructive migration in-place for V1 upgrade path
|
||||
- provide migration script from existing minimal tables to company-scoped schema
|
||||
|
||||
## 15.3 Logging and Audit
|
||||
|
||||
@@ -847,8 +789,6 @@ A release candidate is blocked unless these pass:
|
||||
|
||||
## 18. Delivery Plan
|
||||
|
||||
Current implementation note: the milestones below describe the original V1 sequencing. Several systems originally framed as future work have since shipped or advanced materially, including issue documents/interactions, blockers, routines, execution workspaces, import/export portability, authenticated deployment modes, multi-user basics, and the local/self-hosted plugin runtime.
|
||||
|
||||
## Milestone 1: Company Core and Auth
|
||||
|
||||
- add `companies` and company scoping to existing entities
|
||||
@@ -901,7 +841,7 @@ V1 is complete only when all criteria are true:
|
||||
|
||||
## 20. Post-V1 Backlog (Explicitly Deferred)
|
||||
|
||||
- cloud-grade plugin marketplace/distribution
|
||||
- plugin architecture
|
||||
- richer workflow-state customization per team
|
||||
- milestones/labels/dependency graph depth beyond V1 minimum
|
||||
- realtime transport optimization (SSE/WebSockets)
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 174 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 174 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 177 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 177 KiB |
122
doc/design-system/REVIEW.md
Normal file
122
doc/design-system/REVIEW.md
Normal file
@@ -0,0 +1,122 @@
|
||||
# Paperclip DS Extraction — Review
|
||||
|
||||
- **Generated:** 2026-04-21
|
||||
- **Repo SHA:** `a26e1288b627e82c554445732c7d844648e6b5e1`
|
||||
- **Branch:** `sockmonster-ds-extraction`
|
||||
- **Discovery config:** [`_discovery.json`](./_discovery.json)
|
||||
- **Scope:** `ui/` (`@paperclipai/ui`). Plugin SDK (`packages/plugins/sdk/src/ui/`) treated as contract surface, not implementation surface.
|
||||
|
||||
This is the entry point. Everything else is linked from here. Contents are ordered by **expected human value**, not by stage.
|
||||
|
||||
---
|
||||
|
||||
## Bottom line
|
||||
|
||||
One finding sits upstream of most of the others — resolving it moves four pattern docs from "pending" to "codifiable" and unblocks the single biggest token gap.
|
||||
|
||||
> **The app has a canonical status/priority color catalog (`ui/src/lib/status-colors.ts`) that bypasses the DS token layer and uses raw Tailwind palette classes across 11 hues and ~24 status keys.** Status indicators (`StatusIcon`, `StatusBadge`, `PriorityIcon`, `agentStatusDot`), chart colors (`ActivityCharts.tsx`, hardcoded hex), budget severity indicators (`BudgetPolicyCard`, `BudgetIncidentCard`, `BudgetSidebarMarker`), and quota fills (`QuotaBar`) are **four distinct systems encoding the same red/amber/green severity concept**, none of which share DS tokens.
|
||||
|
||||
A `--signal-*` token family would collapse four surfaces onto one vocabulary and make [status-display.md](./patterns/status-display.md), [quota-display.md](./patterns/quota-display.md), and the severity-indicator pattern opportunity all codifiable. See [tokens-review.md §4](./tokens/tokens-review.md#4-status-colorsts-is-a-canonical-semantic-color-catalog-that-bypasses-the-ds) and [patterns-review.md §6](./patterns/patterns-review.md#6-severity-indicator-3-level-health-display--pattern-opportunity).
|
||||
|
||||
Three other findings are high-value but smaller in scope:
|
||||
- **`destructive-foreground` has a buggy light-mode value** equal to `destructive` itself (would render invisible if anyone used it — nobody does, so the bug is masked). [tokens-review.md §2](./tokens/tokens-review.md#2-destructive-foreground-has-a-wrong-light-mode-value-and-is-unused)
|
||||
- **13 color tokens are dead** (all 5 `chart-*`, all 8 `sidebar-*`). Consolidating would drop color-token count from 32 → 19. [tokens-review.md §1, §3](./tokens/tokens-review.md#1-chart--tokens-are-dead)
|
||||
- **The radius scale is non-monotonic and under-specified** — 227 uses of `rounded-lg` / `rounded-xl` resolve to square corners because `--radius-lg` / `--radius-xl` = 0. Needs a founder call on whether this is intentional flat-design or a stale migration state. [tokens-review.md §Radius scale](./tokens/tokens-review.md#radius-scale--under-founder-review)
|
||||
|
||||
---
|
||||
|
||||
## Recommended review order
|
||||
|
||||
Sequenced so each step unblocks the next. Total time estimated ~2–3 hours.
|
||||
|
||||
| # | Read | Decide | Est. |
|
||||
|---|---|---|---|
|
||||
| 1 | [tokens-review.md §High-confidence drift](./tokens/tokens-review.md#high-confidence-drift-likely-should-be-fixed) | Scope the signal-token work. Confirm dead-token deletions (chart-*, sidebar-*, destructive-foreground). | **25 min** |
|
||||
| 2 | [tokens-review.md §Radius scale](./tokens/tokens-review.md#radius-scale--under-founder-review) | One call: intentional flat lg/xl, or restore a monotonic scale. | **15 min** |
|
||||
| 3 | [components-review.md §Likely duplicates](./components/components-review.md#likely-duplicates) | Nine duplicate families. For each, note "merge/keep/defer" — patterns flow from the decisions. | **30 min** |
|
||||
| 4 | [components-review.md §Plugin SDK contract gap](./components/components-review.md#plugin-sdk-contract-gap) | Choose: fulfill the 9 missing contracts, shrink them, or hybrid. | **15 min** |
|
||||
| 5 | [patterns-review.md §Variance across documented patterns](./patterns/patterns-review.md#variance-across-documented-patterns-whats-inconsistent-between-instances) | Look at the status-element variance in detail pages (four different treatments across eight pages). | **15 min** |
|
||||
| 6 | [patterns-review.md §Paperclip-domain patterns](./patterns/patterns-review.md#paperclip-domain-patterns-worth-calling-out-opportunities-not-ratified-patterns) | Reality-check the run-transcript / heartbeat / metric-cell opportunities before any codify step. | **20 min** |
|
||||
| 7 | [components-review.md §Naming inconsistencies](./components/components-review.md#naming-inconsistencies) | Lower priority — no decision required today, but at least skim. | **10 min** |
|
||||
| 8 | [components-review.md §Story coverage gaps](./components/components-review.md#story-coverage-gaps) | Shadcn primitives missing from `foundations.stories.tsx` (`collapsible`, `dropdown-menu`, `avatar`, `skeleton`, `scroll-area`) is a small, targeted fix. | **10 min** |
|
||||
|
||||
---
|
||||
|
||||
## Confidence
|
||||
|
||||
### High confidence (probably correct, spot-check only)
|
||||
|
||||
- **32 color tokens** extracted from `ui/src/index.css` (19 semantic surfaces, 5 chart, 8 sidebar).
|
||||
- **5 radius tokens**, with value + definition-site recorded.
|
||||
- **Usage counts per color and radius token** computed by unioning Tailwind-utility occurrences and `var(--token)` references across `ui/src/**/*.{ts,tsx,css}` (excluding the definition file itself). Counts are rough by intent — within ±10%.
|
||||
- **135 component files** enumerated, classified into 22 primitives / 64 composites / 47 standalones / 2 non-component utilities.
|
||||
- **104 components** cross-referenced against 14 Storybook files via import-graph parsing.
|
||||
- **50 pages** enumerated; per-page import set captured.
|
||||
- **11 plugin SDK ambient components** enumerated with host-implementation status.
|
||||
- **4 components confirmed as storybook-only** (0 production uses): `AccountingModelCard`, `AgentProperties`, `CompanySwitcher`, `ExecutionParticipantPicker`.
|
||||
|
||||
### Medium confidence (review carefully)
|
||||
|
||||
- **Duplicate-family flags.** Eight families surfaced ([components-review.md §Likely duplicates](./components/components-review.md#likely-duplicates)) are based on name parallelism and/or shared imports. The strongest signals (entity-creation dialogs, subscription panels) need a side-by-side diff to confirm merge-ability; this extraction didn't do that.
|
||||
- **`BillerSpendCard` vs `FinanceBillerCard` as likely-true-duplicate.** Flagged per directive. Not confirmed without a diff.
|
||||
- **Pattern instance counts.** The `detail-page` and `list-page` patterns were identified by import-set intersection, which is a proxy for structural similarity. A page can import a component and not actually render it in the expected position; pattern shape is inferred, not verified pixel-by-pixel.
|
||||
- **CVA variant extraction for primitives.** Parsed 3 files successfully (`button`, `badge`, one more). The rest of the primitives likely have variants that the static parser missed.
|
||||
- **The severity-indicator pattern ([patterns-review.md §6](./patterns/patterns-review.md#6-severity-indicator-3-level-health-display--pattern-opportunity)).** Named as an *opportunity*, not a ratified pattern — cross-system evidence is strong but the four systems weren't compared pixel-for-pixel; they may not actually agree on what "warning" looks like.
|
||||
- **Story coverage set.** Computed by parsing imports in `.stories.tsx` files. A component that's imported by a story but never actually rendered would falsely appear covered. Low risk given story-file structure but not validated.
|
||||
|
||||
### Low confidence (likely wrong, incomplete, or judgment-heavy)
|
||||
|
||||
- **Motion tokens.** None exist as variables — motion is inline `@keyframes` + `cubic-bezier()`. Pattern docs don't describe motion. The 5 keyframes in `index.css` are listed; their callers are not cross-referenced.
|
||||
- **Typography.** No project-local font/type tokens found — the section is near-empty because Tailwind v4 defaults carry the load plus `@tailwindcss/typography`. If there are intended type-scale conventions in components that weren't captured by token extraction, those are missed.
|
||||
- **Elevation / shadows.** No tokens, so no inventory. Ad-hoc `shadow-[…]` values across polished surfaces were enumerated in [tokens-review.md §9](./tokens/tokens-review.md#9-arbitrary-shadow-values-in-production-surfaces), but the list is not exhaustive.
|
||||
- **Prop extraction for primitives using `React.ComponentProps<"button"> & VariantProps<...>`.** The static parser looks for `*Props` interfaces; inline-type components (most shadcn primitives) get "no Props interface found" in their detail files.
|
||||
- **Per-component token consumption cross-reference.** Components/detail files don't list which specific tokens each component consumes (would require per-file class-attribute parsing). Token usage counts are global; per-component token drift is flagged only where specific drift was found.
|
||||
- **Pattern: "detail-page header."** Called out as a sub-pattern inside detail-page doc but not given its own file — instances share only 4–5 imports, not a complete shape.
|
||||
|
||||
---
|
||||
|
||||
## Known scope limitations
|
||||
|
||||
- **Plugin SDK UI.** In-scope as a contract surface (documented in [components/index.md §Plugin SDK contracts](./components/index.md#plugin-sdk-contracts-11)). Not in-scope for pattern extraction — host implementations are covered; plugin-side usage patterns are not.
|
||||
- **Low-usage components (1–2 code imports, 76 of them).** Listed in [components/index.md](./components/index.md) with status marker `📘 below-threshold`; no dedicated detail file. Per the directive: *nothing gets silently dropped*.
|
||||
- **Pattern documentation:** capped at 10 real patterns. Eleven pattern files exist because the duplicate-family directive required documenting three below-threshold pairs (subscription-panel, sidebar-menu pair inside sidebar-chrome, quota-display). Pattern opportunities surfaced in patterns-review.md are not yet pattern files.
|
||||
- **UX Lab pages (`InviteUxLab`, `IssueChatUxLab`, `RunTranscriptUxLab`).** Acknowledged prototypes with distinct visual language. Excluded from pattern extraction. Their raw-palette usage is counted in drift stats but not pursued.
|
||||
- **Hermes / adapter code.** `ui/src/adapters/` contains per-adapter config fields. Not a DS concern; skipped.
|
||||
- **Mobile treatments.** `MobileBottomNav` and `SwipeToArchive` are noted but not extracted as their own pattern. Mobile patterns appear to live inside individual list/detail pages rather than as shared primitives.
|
||||
- **Diff mode.** This is a fresh run; `doc/design-system/` did not exist before. No diff was generated. Subsequent re-runs should run in diff mode (see [ds-extraction skill §Diff mode](../../.agents/skills/ds-extraction/SKILL.md#diff-mode)).
|
||||
|
||||
---
|
||||
|
||||
## What's on disk
|
||||
|
||||
```
|
||||
doc/design-system/
|
||||
├── REVIEW.md ← you are here
|
||||
├── _discovery.json ← Stage 0 output
|
||||
├── _pages.json ← Stage 2 scratch (50 pages)
|
||||
├── _composition-graph.json ← Stage 2 scratch (135 components)
|
||||
├── _stories.json ← Stage 2 scratch (14 stories)
|
||||
├── tokens/
|
||||
│ ├── tokens.md ← canonical human-readable inventory
|
||||
│ ├── tokens.json ← machine-readable for downstream tooling
|
||||
│ └── tokens-review.md ← the high-value drift artifact
|
||||
├── components/
|
||||
│ ├── index.md ← all 135 files + 11 SDK contracts, with status markers
|
||||
│ ├── components-review.md ← duplicates, naming, token non-compliance, story gaps, SDK gap
|
||||
│ └── [ComponentName].md × 53 ← per-component detail files (3+ uses threshold)
|
||||
└── patterns/
|
||||
├── index.md
|
||||
├── patterns-review.md ← variance, opportunities, what to resolve before re-running
|
||||
├── list-page.md ← 12 instances
|
||||
├── detail-page.md ← 8 instances
|
||||
├── sidebar-chrome.md ← 6 + 2 instances
|
||||
├── finance-card.md ← 5 instances
|
||||
├── entity-properties-panel.md ← 4 + 1 instances (open Q on generic)
|
||||
├── entity-creation-dialog.md ← 4 instances
|
||||
├── status-display.md ← 3 components + catalog (pending signal tokens)
|
||||
├── entity-row.md ← 3 instances
|
||||
├── subscription-panel.md ← 2 instances (below threshold — documented)
|
||||
└── quota-display.md ← 2 instances (below threshold — documented)
|
||||
```
|
||||
|
||||
Total: **~80 files**.
|
||||
2767
doc/design-system/_composition-graph.json
Normal file
2767
doc/design-system/_composition-graph.json
Normal file
File diff suppressed because it is too large
Load Diff
229
doc/design-system/_discovery.json
Normal file
229
doc/design-system/_discovery.json
Normal file
@@ -0,0 +1,229 @@
|
||||
{
|
||||
"generated_at": "2026-04-21T00:00:00Z",
|
||||
"repo_sha": "a26e1288b627e82c554445732c7d844648e6b5e1",
|
||||
"branch": "sockmonster-ds-extraction",
|
||||
"styling": {
|
||||
"tailwind_version": "v4",
|
||||
"tailwind_config": null,
|
||||
"tailwind_config_note": "No tailwind.config.* file. Tailwind v4 CSS-first config: theme is declared via @theme inline blocks in ui/src/index.css. Build integration via @tailwindcss/vite.",
|
||||
"css_variables_file": "ui/src/index.css",
|
||||
"uses_css_variables": true,
|
||||
"uses_cva": true,
|
||||
"uses_cn_helper": true,
|
||||
"cn_helper_location": "ui/src/lib/utils.ts:6",
|
||||
"shadcn_present": true,
|
||||
"shadcn_style": "new-york",
|
||||
"shadcn_base_color": "neutral",
|
||||
"shadcn_css_variables": true,
|
||||
"shadcn_rsc": false,
|
||||
"shadcn_icon_library": "lucide",
|
||||
"shadcn_aliases": {
|
||||
"components": "@/components",
|
||||
"ui": "@/components/ui",
|
||||
"lib": "@/lib",
|
||||
"hooks": "@/hooks",
|
||||
"utils": "@/lib/utils"
|
||||
},
|
||||
"shadcn_skill_path": ".agents/skills/shadcn/SKILL.md",
|
||||
"other_styling": [
|
||||
{
|
||||
"library": "@tailwindcss/typography",
|
||||
"usage": "@plugin in ui/src/index.css; prose class styling for markdown"
|
||||
}
|
||||
],
|
||||
"notes": "Tailwind v4 with shadcn/ui in new-york style. components.json present. cn() = clsx + tailwind-merge. Custom @custom-variant dark (&:is(.dark *)). No tailwindcss-animate plugin."
|
||||
},
|
||||
"tokens": {
|
||||
"color": {
|
||||
"sources": ["ui/src/index.css"],
|
||||
"authoritative_source": "ui/src/index.css",
|
||||
"definition_blocks": [
|
||||
{ "block": "@theme inline", "role": "exposes --color-* aliases to Tailwind", "line_range": "6-43" },
|
||||
{ "block": ":root", "role": "authoritative light-mode values", "line_range": "45-80" },
|
||||
{ "block": ".dark", "role": "dark-mode overrides", "line_range": "82-115" }
|
||||
],
|
||||
"count_estimate": 32,
|
||||
"categories": {
|
||||
"semantic_neutral_and_intent": [
|
||||
"background", "foreground", "card", "card-foreground", "popover", "popover-foreground",
|
||||
"primary", "primary-foreground", "secondary", "secondary-foreground",
|
||||
"muted", "muted-foreground", "accent", "accent-foreground",
|
||||
"destructive", "destructive-foreground",
|
||||
"border", "input", "ring"
|
||||
],
|
||||
"chart": ["chart-1", "chart-2", "chart-3", "chart-4", "chart-5"],
|
||||
"sidebar": [
|
||||
"sidebar", "sidebar-foreground",
|
||||
"sidebar-primary", "sidebar-primary-foreground",
|
||||
"sidebar-accent", "sidebar-accent-foreground",
|
||||
"sidebar-border", "sidebar-ring"
|
||||
]
|
||||
},
|
||||
"includes_signal_green": false,
|
||||
"value_format": "oklch",
|
||||
"dark_mode_convention": ".dark class selector via @custom-variant"
|
||||
},
|
||||
"spacing": {
|
||||
"sources": [],
|
||||
"authoritative_source": null,
|
||||
"count_estimate": 0,
|
||||
"note": "No project-local spacing tokens. Spacing uses Tailwind v4 defaults inherited from tailwindcss package."
|
||||
},
|
||||
"type": {
|
||||
"sources": [],
|
||||
"authoritative_source": null,
|
||||
"count_estimate": 0,
|
||||
"font_faces": [],
|
||||
"google_fonts": [],
|
||||
"note": "No project-local font/type tokens. Typography uses Tailwind v4 defaults + @tailwindcss/typography plugin. Markdown styling via `.paperclip-markdown` and `.paperclip-mdxeditor-content` classes with hardcoded font-size/line-height values in index.css."
|
||||
},
|
||||
"radius": {
|
||||
"sources": ["ui/src/index.css"],
|
||||
"authoritative_source": "ui/src/index.css",
|
||||
"count_estimate": 5,
|
||||
"tokens": [
|
||||
{ "name": "--radius", "value": "0", "defined_at": "ui/src/index.css:47", "scope": ":root" },
|
||||
{ "name": "--radius-sm", "value": "0.375rem", "defined_at": "ui/src/index.css:39", "scope": "@theme" },
|
||||
{ "name": "--radius-md", "value": "0.5rem", "defined_at": "ui/src/index.css:40", "scope": "@theme" },
|
||||
{ "name": "--radius-lg", "value": "0px", "defined_at": "ui/src/index.css:41", "scope": "@theme" },
|
||||
{ "name": "--radius-xl", "value": "0px", "defined_at": "ui/src/index.css:42", "scope": "@theme" }
|
||||
],
|
||||
"note": "Unusual: --radius-lg and --radius-xl are 0px while --radius-sm and --radius-md are non-zero. Likely intentional flat-design choice at the outer scale, but worth confirming. Also --radius (base, :root) = 0 used by MDXEditor integration; not part of @theme."
|
||||
},
|
||||
"motion": {
|
||||
"sources": ["ui/src/index.css"],
|
||||
"authoritative_source": null,
|
||||
"count_estimate": 0,
|
||||
"css_variable_tokens": [],
|
||||
"keyframes": [
|
||||
{ "name": "dashboard-activity-enter", "defined_at": "ui/src/index.css:228" },
|
||||
{ "name": "dashboard-activity-highlight", "defined_at": "ui/src/index.css:246" },
|
||||
{ "name": "cot-line-slide-in", "defined_at": "ui/src/index.css:272" },
|
||||
{ "name": "cot-line-slide-out", "defined_at": "ui/src/index.css:277" },
|
||||
{ "name": "shimmer-text-slide", "defined_at": "ui/src/index.css:298" }
|
||||
],
|
||||
"tailwindcss_animate_plugin": false,
|
||||
"note": "No --motion-* or --duration-* tokens. Motion is defined as inline @keyframes + inline cubic-bezier values (commonly cubic-bezier(0.16, 1, 0.3, 1) and cubic-bezier(0.4, 0, 0.2, 1)). prefers-reduced-motion respected."
|
||||
},
|
||||
"elevation": {
|
||||
"sources": [],
|
||||
"authoritative_source": null,
|
||||
"count_estimate": 0,
|
||||
"note": "No --shadow-* tokens and no theme.boxShadow. Project appears to avoid shadows as a design choice (borders and background shifts carry elevation). Verify during extraction by grepping for box-shadow usage in components."
|
||||
},
|
||||
"scoped_non_ds_variables": [
|
||||
{
|
||||
"group": "MDXEditor theme bridge",
|
||||
"selector": ".paperclip-mdxeditor-scope, .paperclip-mdxeditor",
|
||||
"line_range": "332-361",
|
||||
"variable_count": 24,
|
||||
"role": "Maps host DS tokens onto MDXEditor's internal token names (--baseBase, --accentSolid, etc.). Consumed alias layer, not authoritative DS tokens."
|
||||
},
|
||||
{
|
||||
"group": "Shimmer text effect",
|
||||
"selector": ".shimmer-text",
|
||||
"variable_count": 2,
|
||||
"role": "Component-local (--shimmer-base, --shimmer-highlight)."
|
||||
}
|
||||
]
|
||||
},
|
||||
"components": {
|
||||
"primary_root": "ui/src/components/",
|
||||
"layout": "mixed",
|
||||
"layout_notes": "Not purely flat and not purely nested. Subdirectories exist for a specific subset; the majority live flat at the top level. Naming convention for primitives is lowercase-kebab (button.tsx, dropdown-menu.tsx); composites/features use PascalCase (AgentConfigForm.tsx).",
|
||||
"subdirectories": [
|
||||
{ "path": "ui/src/components/ui/", "role": "shadcn primitives", "file_count": 22 },
|
||||
{ "path": "ui/src/components/access/", "role": "access-control feature cluster", "file_count": 3 },
|
||||
{ "path": "ui/src/components/transcript/", "role": "run transcript feature cluster", "file_count": 2 }
|
||||
],
|
||||
"top_level_tsx_count": 108,
|
||||
"primitives_in_components_ui": [
|
||||
"avatar", "badge", "breadcrumb", "button", "card", "checkbox", "collapsible",
|
||||
"command", "dialog", "dropdown-menu", "input", "label", "popover",
|
||||
"scroll-area", "select", "separator", "sheet", "skeleton", "tabs",
|
||||
"textarea", "toggle-switch", "tooltip"
|
||||
],
|
||||
"count_estimate": 133,
|
||||
"plugin_sdk_components": {
|
||||
"path": "packages/plugins/sdk/src/ui/components.ts",
|
||||
"status": "ambient-types-only",
|
||||
"count": 11,
|
||||
"declared_components": [
|
||||
"MetricCard", "StatusBadge", "DataTable", "TimeseriesChart", "MarkdownBlock",
|
||||
"KeyValueList", "ActionBar", "LogView", "JsonTree", "Spinner", "ErrorBoundary"
|
||||
],
|
||||
"runtime_model": "Host provides implementations via renderSdkUiComponent(name, props) runtime injection. Plugin bundles ship type declarations only.",
|
||||
"name_collision_check": "MetricCard.tsx and StatusBadge.tsx exist at ui/src/components/ top-level. The other 9 declared components have no obvious matching file — may exist under different names (e.g., MarkdownBody ≈ MarkdownBlock, JsonSchemaForm unrelated) or may not be implemented yet."
|
||||
}
|
||||
},
|
||||
"usage_surfaces": {
|
||||
"pages_root": "ui/src/pages/",
|
||||
"page_count_estimate": 50,
|
||||
"page_count_method": "find ui/src/pages -name '*.tsx' -not -name '*.test.tsx'",
|
||||
"other_surfaces": [
|
||||
{ "path": "ui/src/App.tsx", "role": "root router" },
|
||||
{ "path": "ui/src/plugins/", "role": "plugin slot & launcher rendering (slots.tsx, launchers.tsx)" }
|
||||
],
|
||||
"notable_pages": {
|
||||
"design_guide": "ui/src/pages/DesignGuide.tsx — an existing in-app design reference page. Imports shadcn primitives; worth cross-referencing during Stage 3 for intended-vs-actual primitive usage. Its presence means a partial DS narrative already exists in-code.",
|
||||
"ux_labs": [
|
||||
"ui/src/pages/InviteUxLab.tsx",
|
||||
"ui/src/pages/IssueChatUxLab.tsx",
|
||||
"ui/src/pages/RunTranscriptUxLab.tsx"
|
||||
]
|
||||
}
|
||||
},
|
||||
"storybook": {
|
||||
"present": true,
|
||||
"version": "10.3.5",
|
||||
"config_path": "ui/storybook/.storybook/main.ts",
|
||||
"stories_location": "centralized",
|
||||
"stories_glob": "ui/storybook/stories/**/*.stories.@(ts|tsx|mdx)",
|
||||
"story_file_count": 14,
|
||||
"story_organization": "thematic",
|
||||
"story_organization_note": "Stories are organized by domain/theme (foundations, navigation-layout, dialogs-modals, chat-comments, forms-editors, status-language, data-viz-misc, agent-management, issue-management, projects-goals-workspaces, budget-finance, control-plane-surfaces, ux-labs, overview) — NOT one-story-per-component. A single .stories.tsx file typically imports and composes many components. 'Component covered by story' must be computed by parsing import graphs of story files, not by file naming.",
|
||||
"story_files": [
|
||||
"foundations.stories.tsx",
|
||||
"overview.stories.tsx",
|
||||
"status-language.stories.tsx",
|
||||
"navigation-layout.stories.tsx",
|
||||
"dialogs-modals.stories.tsx",
|
||||
"forms-editors.stories.tsx",
|
||||
"chat-comments.stories.tsx",
|
||||
"data-viz-misc.stories.tsx",
|
||||
"agent-management.stories.tsx",
|
||||
"issue-management.stories.tsx",
|
||||
"projects-goals-workspaces.stories.tsx",
|
||||
"budget-finance.stories.tsx",
|
||||
"control-plane-surfaces.stories.tsx",
|
||||
"ux-labs.stories.tsx"
|
||||
],
|
||||
"addons": ["@storybook/addon-docs", "@storybook/addon-a11y"],
|
||||
"covered_components": null,
|
||||
"covered_components_note": "Deferred to Stage 2 (parse story imports). Discovery only confirms stories exist, not per-component coverage."
|
||||
},
|
||||
"existing_docs": {
|
||||
"design_system_dir_present": false,
|
||||
"locations": [
|
||||
{ "path": "ui/README.md", "role": "package readme; non-DS" },
|
||||
{ "path": "ui/src/pages/DesignGuide.tsx", "role": "in-app design reference page (component-level showcase)" }
|
||||
],
|
||||
"figma_sync_config": null,
|
||||
"style_dictionary_config": null,
|
||||
"tokens_studio_config": null
|
||||
},
|
||||
"known_gaps": [
|
||||
"Plugin SDK UI is a types-only ambient bridge (packages/plugins/sdk/src/ui/components.ts). The host-provided component kit promised by the SDK is partial: only MetricCard and StatusBadge have matching host implementations by name. 9 other declared components (DataTable, TimeseriesChart, MarkdownBlock, KeyValueList, ActionBar, LogView, JsonTree, Spinner, ErrorBoundary) have no obvious host implementation. PLUGIN_SPEC.md:30 confirms: 'The current runtime does not yet ship a real host-provided plugin UI component kit'.",
|
||||
"No dedicated spacing, type, or elevation tokens. Those categories rely on Tailwind v4 defaults. Extraction should not synthesize repo-specific tokens where none exist.",
|
||||
"No central motion token language. Motion is expressed as per-feature @keyframes with inline easing. Treat motion as a candidate for future tokenization rather than documenting a current system.",
|
||||
"No Figma/style-dictionary/tokens-studio integration. The design system is code-authored, not design-tool-synced."
|
||||
],
|
||||
"uncertainties": [
|
||||
"Storybook organization is thematic (14 composite stories), not per-component. The extraction skill's 'covered_by_story' signal needs to be computed by parsing each story file's import graph and surfacing components used inside render bodies. Flag before Stage 2 so the skill doesn't default to file-name matching.",
|
||||
"Radius scale is non-monotonic: --radius-sm = 0.375rem, --radius-md = 0.5rem, --radius-lg = 0px, --radius-xl = 0px. Flat-design choice or stale values? Also --radius (base) = 0 at :root coexists with the @theme tokens; which is canonical for Tailwind rounded utilities? Worth confirming before Stage 1 drift analysis flags every rounded-lg usage.",
|
||||
"Plugin SDK UI contracts 11 shared components but only 2 appear to be implemented by that name in ui/. For extraction scope, should we (a) treat the SDK declarations as DS contract and flag missing implementations as gaps, or (b) ignore the SDK and document only what exists in ui/? Recommendation: (a) — it's higher-value signal for the human reviewer.",
|
||||
"ui/src/components/ has a mixed layout: 22 shadcn primitives in a 'ui/' subdirectory and 108 components flat at the top level. The top-level mix contains features, composites, one-off pages pieces, and true reusable patterns. Stage 3 will need a heuristic (composition-graph-based) rather than directory-based category inference.",
|
||||
"Total component count (~133) is meaningfully larger than the skill's illustrative example (87). With the 3+-usage threshold for detail files, output should stay tractable, but the skill should confirm the threshold is right at this scale before Stage 3 starts generating per-component .md files.",
|
||||
"MDXEditor CSS variable bridge (.paperclip-mdxeditor-scope, 24 --base*/--accent* variables) is DS-adjacent — it consumes host tokens and maps them to MDXEditor internals. Should Stage 1 include these in tokens.json? Recommendation: no — they are consumed aliases, not authoritative tokens. Flag them in tokens-review.md as 'integration layer' rather than as drift."
|
||||
]
|
||||
}
|
||||
646
doc/design-system/_pages.json
Normal file
646
doc/design-system/_pages.json
Normal file
@@ -0,0 +1,646 @@
|
||||
{
|
||||
"generated_at": "2026-04-21T00:00:00Z",
|
||||
"repo_sha": "a26e1288b627e82c554445732c7d844648e6b5e1",
|
||||
"page_count": 50,
|
||||
"method": "Import-graph extraction: 'from \"@/components/<name>\"' and relative imports. Top-level JSX tree not parsed \u2014 import set is the proxy for rendered-components set. Components a page imports but never renders would inflate the count slightly; low risk here given app style.",
|
||||
"pages": {
|
||||
"Activity": {
|
||||
"path": "ui/src/pages/Activity.tsx",
|
||||
"components_imported": [
|
||||
"ActivityRow",
|
||||
"EmptyState",
|
||||
"PageSkeleton",
|
||||
"select"
|
||||
],
|
||||
"component_count": 4
|
||||
},
|
||||
"AdapterManager": {
|
||||
"path": "ui/src/pages/AdapterManager.tsx",
|
||||
"components_imported": [
|
||||
"PathInstructionsModal",
|
||||
"badge",
|
||||
"button",
|
||||
"card",
|
||||
"dialog",
|
||||
"input",
|
||||
"label"
|
||||
],
|
||||
"component_count": 7
|
||||
},
|
||||
"AgentDetail": {
|
||||
"path": "ui/src/pages/AgentDetail.tsx",
|
||||
"components_imported": [
|
||||
"ActivityCharts",
|
||||
"AgentActionButtons",
|
||||
"AgentConfigForm",
|
||||
"AgentIconPicker",
|
||||
"BudgetPolicyCard",
|
||||
"CopyText",
|
||||
"EntityRow",
|
||||
"Identity",
|
||||
"MarkdownBody",
|
||||
"MarkdownEditor",
|
||||
"PackageFileTree",
|
||||
"PageSkeleton",
|
||||
"PageTabBar",
|
||||
"RunTranscriptView",
|
||||
"ScrollToBottom",
|
||||
"StatusBadge",
|
||||
"agent-config-primitives",
|
||||
"button",
|
||||
"collapsible",
|
||||
"input",
|
||||
"popover",
|
||||
"skeleton",
|
||||
"tabs",
|
||||
"toggle-switch",
|
||||
"tooltip"
|
||||
],
|
||||
"component_count": 25
|
||||
},
|
||||
"Agents": {
|
||||
"path": "ui/src/pages/Agents.tsx",
|
||||
"components_imported": [
|
||||
"EmptyState",
|
||||
"EntityRow",
|
||||
"PageSkeleton",
|
||||
"PageTabBar",
|
||||
"StatusBadge",
|
||||
"button",
|
||||
"tabs"
|
||||
],
|
||||
"component_count": 7
|
||||
},
|
||||
"ApprovalDetail": {
|
||||
"path": "ui/src/pages/ApprovalDetail.tsx",
|
||||
"components_imported": [
|
||||
"ApprovalPayload",
|
||||
"Identity",
|
||||
"MarkdownBody",
|
||||
"PageSkeleton",
|
||||
"StatusBadge",
|
||||
"button",
|
||||
"textarea"
|
||||
],
|
||||
"component_count": 7
|
||||
},
|
||||
"Approvals": {
|
||||
"path": "ui/src/pages/Approvals.tsx",
|
||||
"components_imported": [
|
||||
"ApprovalCard",
|
||||
"PageSkeleton",
|
||||
"PageTabBar",
|
||||
"tabs"
|
||||
],
|
||||
"component_count": 4
|
||||
},
|
||||
"Auth": {
|
||||
"path": "ui/src/pages/Auth.tsx",
|
||||
"components_imported": [
|
||||
"AsciiArtAnimation",
|
||||
"button"
|
||||
],
|
||||
"component_count": 2
|
||||
},
|
||||
"BoardClaim": {
|
||||
"path": "ui/src/pages/BoardClaim.tsx",
|
||||
"components_imported": [
|
||||
"button"
|
||||
],
|
||||
"component_count": 1
|
||||
},
|
||||
"CliAuth": {
|
||||
"path": "ui/src/pages/CliAuth.tsx",
|
||||
"components_imported": [
|
||||
"button"
|
||||
],
|
||||
"component_count": 1
|
||||
},
|
||||
"Companies": {
|
||||
"path": "ui/src/pages/Companies.tsx",
|
||||
"components_imported": [
|
||||
"button",
|
||||
"dropdown-menu",
|
||||
"input"
|
||||
],
|
||||
"component_count": 3
|
||||
},
|
||||
"CompanyAccess": {
|
||||
"path": "ui/src/pages/CompanyAccess.tsx",
|
||||
"components_imported": [
|
||||
"badge",
|
||||
"button",
|
||||
"checkbox",
|
||||
"dialog"
|
||||
],
|
||||
"component_count": 4
|
||||
},
|
||||
"CompanyExport": {
|
||||
"path": "ui/src/pages/CompanyExport.tsx",
|
||||
"components_imported": [
|
||||
"EmptyState",
|
||||
"MarkdownBody",
|
||||
"PackageFileTree",
|
||||
"PageSkeleton",
|
||||
"button"
|
||||
],
|
||||
"component_count": 5
|
||||
},
|
||||
"CompanyImport": {
|
||||
"path": "ui/src/pages/CompanyImport.tsx",
|
||||
"components_imported": [
|
||||
"AgentConfigForm",
|
||||
"EmptyState",
|
||||
"MarkdownBody",
|
||||
"PackageFileTree",
|
||||
"agent-config-defaults",
|
||||
"agent-config-primitives",
|
||||
"button"
|
||||
],
|
||||
"component_count": 7
|
||||
},
|
||||
"CompanyInvites": {
|
||||
"path": "ui/src/pages/CompanyInvites.tsx",
|
||||
"components_imported": [
|
||||
"button"
|
||||
],
|
||||
"component_count": 1
|
||||
},
|
||||
"CompanySettings": {
|
||||
"path": "ui/src/pages/CompanySettings.tsx",
|
||||
"components_imported": [
|
||||
"CompanyPatternIcon",
|
||||
"agent-config-primitives",
|
||||
"button"
|
||||
],
|
||||
"component_count": 3
|
||||
},
|
||||
"CompanySkills": {
|
||||
"path": "ui/src/pages/CompanySkills.tsx",
|
||||
"components_imported": [
|
||||
"EmptyState",
|
||||
"MarkdownBody",
|
||||
"MarkdownEditor",
|
||||
"PageSkeleton",
|
||||
"button",
|
||||
"dialog",
|
||||
"input",
|
||||
"textarea",
|
||||
"tooltip"
|
||||
],
|
||||
"component_count": 9
|
||||
},
|
||||
"Costs": {
|
||||
"path": "ui/src/pages/Costs.tsx",
|
||||
"components_imported": [
|
||||
"BillerSpendCard",
|
||||
"BudgetIncidentCard",
|
||||
"BudgetPolicyCard",
|
||||
"EmptyState",
|
||||
"FinanceBillerCard",
|
||||
"FinanceKindCard",
|
||||
"FinanceTimelineCard",
|
||||
"Identity",
|
||||
"PageSkeleton",
|
||||
"PageTabBar",
|
||||
"ProviderQuotaCard",
|
||||
"StatusBadge",
|
||||
"button",
|
||||
"card",
|
||||
"tabs"
|
||||
],
|
||||
"component_count": 15
|
||||
},
|
||||
"Dashboard": {
|
||||
"path": "ui/src/pages/Dashboard.tsx",
|
||||
"components_imported": [
|
||||
"ActiveAgentsPanel",
|
||||
"ActivityCharts",
|
||||
"ActivityRow",
|
||||
"EmptyState",
|
||||
"Identity",
|
||||
"MetricCard",
|
||||
"PageSkeleton",
|
||||
"StatusIcon"
|
||||
],
|
||||
"component_count": 8
|
||||
},
|
||||
"DesignGuide": {
|
||||
"path": "ui/src/pages/DesignGuide.tsx",
|
||||
"components_imported": [
|
||||
"EmptyState",
|
||||
"EntityRow",
|
||||
"FilterBar",
|
||||
"Identity",
|
||||
"InlineEditor",
|
||||
"IssueReferencePill",
|
||||
"MetricCard",
|
||||
"PageSkeleton",
|
||||
"PriorityIcon",
|
||||
"StatusBadge",
|
||||
"StatusIcon",
|
||||
"avatar",
|
||||
"badge",
|
||||
"breadcrumb",
|
||||
"button",
|
||||
"card",
|
||||
"checkbox",
|
||||
"collapsible",
|
||||
"command",
|
||||
"dialog",
|
||||
"dropdown-menu",
|
||||
"input",
|
||||
"label",
|
||||
"popover",
|
||||
"scroll-area",
|
||||
"select",
|
||||
"separator",
|
||||
"sheet",
|
||||
"skeleton",
|
||||
"tabs",
|
||||
"textarea",
|
||||
"tooltip"
|
||||
],
|
||||
"component_count": 32
|
||||
},
|
||||
"ExecutionWorkspaceDetail": {
|
||||
"path": "ui/src/pages/ExecutionWorkspaceDetail.tsx",
|
||||
"components_imported": [
|
||||
"CopyText",
|
||||
"ExecutionWorkspaceCloseDialog",
|
||||
"IssuesList",
|
||||
"PageTabBar",
|
||||
"WorkspaceRuntimeControls",
|
||||
"button",
|
||||
"card",
|
||||
"input",
|
||||
"separator",
|
||||
"tabs",
|
||||
"textarea"
|
||||
],
|
||||
"component_count": 11
|
||||
},
|
||||
"GoalDetail": {
|
||||
"path": "ui/src/pages/GoalDetail.tsx",
|
||||
"components_imported": [
|
||||
"EntityRow",
|
||||
"GoalProperties",
|
||||
"GoalTree",
|
||||
"InlineEditor",
|
||||
"PageSkeleton",
|
||||
"StatusBadge",
|
||||
"button",
|
||||
"tabs"
|
||||
],
|
||||
"component_count": 8
|
||||
},
|
||||
"Goals": {
|
||||
"path": "ui/src/pages/Goals.tsx",
|
||||
"components_imported": [
|
||||
"EmptyState",
|
||||
"GoalTree",
|
||||
"PageSkeleton",
|
||||
"button"
|
||||
],
|
||||
"component_count": 4
|
||||
},
|
||||
"Inbox": {
|
||||
"path": "ui/src/pages/Inbox.tsx",
|
||||
"components_imported": [
|
||||
"ApprovalPayload",
|
||||
"EmptyState",
|
||||
"IssueColumns",
|
||||
"IssueFiltersPopover",
|
||||
"IssueGroupHeader",
|
||||
"IssueRow",
|
||||
"PageSkeleton",
|
||||
"PageTabBar",
|
||||
"StatusBadge",
|
||||
"StatusIcon",
|
||||
"SwipeToArchive",
|
||||
"button",
|
||||
"dialog",
|
||||
"input",
|
||||
"popover",
|
||||
"select",
|
||||
"separator",
|
||||
"tabs"
|
||||
],
|
||||
"component_count": 18
|
||||
},
|
||||
"InstanceAccess": {
|
||||
"path": "ui/src/pages/InstanceAccess.tsx",
|
||||
"components_imported": [
|
||||
"button",
|
||||
"checkbox"
|
||||
],
|
||||
"component_count": 2
|
||||
},
|
||||
"InstanceExperimentalSettings": {
|
||||
"path": "ui/src/pages/InstanceExperimentalSettings.tsx",
|
||||
"components_imported": [
|
||||
"toggle-switch"
|
||||
],
|
||||
"component_count": 1
|
||||
},
|
||||
"InstanceGeneralSettings": {
|
||||
"path": "ui/src/pages/InstanceGeneralSettings.tsx",
|
||||
"components_imported": [
|
||||
"ModeBadge",
|
||||
"button",
|
||||
"toggle-switch"
|
||||
],
|
||||
"component_count": 3
|
||||
},
|
||||
"InstanceSettings": {
|
||||
"path": "ui/src/pages/InstanceSettings.tsx",
|
||||
"components_imported": [
|
||||
"EmptyState",
|
||||
"badge",
|
||||
"button",
|
||||
"card"
|
||||
],
|
||||
"component_count": 4
|
||||
},
|
||||
"InviteLanding": {
|
||||
"path": "ui/src/pages/InviteLanding.tsx",
|
||||
"components_imported": [
|
||||
"CompanyPatternIcon",
|
||||
"button"
|
||||
],
|
||||
"component_count": 2
|
||||
},
|
||||
"InviteUxLab": {
|
||||
"path": "ui/src/pages/InviteUxLab.tsx",
|
||||
"components_imported": [
|
||||
"CompanyPatternIcon",
|
||||
"badge",
|
||||
"button",
|
||||
"card"
|
||||
],
|
||||
"component_count": 4
|
||||
},
|
||||
"IssueChatUxLab": {
|
||||
"path": "ui/src/pages/IssueChatUxLab.tsx",
|
||||
"components_imported": [
|
||||
"IssueChatThread",
|
||||
"badge",
|
||||
"button",
|
||||
"card"
|
||||
],
|
||||
"component_count": 4
|
||||
},
|
||||
"IssueDetail": {
|
||||
"path": "ui/src/pages/IssueDetail.tsx",
|
||||
"components_imported": [
|
||||
"ApprovalCard",
|
||||
"Identity",
|
||||
"ImageGalleryModal",
|
||||
"InlineEditor",
|
||||
"IssueChatThread",
|
||||
"IssueContinuationHandoff",
|
||||
"IssueDocumentsSection",
|
||||
"IssueProperties",
|
||||
"IssueReferenceActivitySummary",
|
||||
"IssueRelatedWorkPanel",
|
||||
"IssueRunLedger",
|
||||
"IssueWorkspaceCard",
|
||||
"IssuesList",
|
||||
"MarkdownEditor",
|
||||
"PriorityIcon",
|
||||
"ScrollToBottom",
|
||||
"StatusIcon",
|
||||
"button",
|
||||
"popover",
|
||||
"scroll-area",
|
||||
"separator",
|
||||
"sheet",
|
||||
"skeleton",
|
||||
"tabs"
|
||||
],
|
||||
"component_count": 24
|
||||
},
|
||||
"Issues": {
|
||||
"path": "ui/src/pages/Issues.tsx",
|
||||
"components_imported": [
|
||||
"EmptyState",
|
||||
"IssuesList"
|
||||
],
|
||||
"component_count": 2
|
||||
},
|
||||
"JoinRequestQueue": {
|
||||
"path": "ui/src/pages/JoinRequestQueue.tsx",
|
||||
"components_imported": [
|
||||
"badge",
|
||||
"button"
|
||||
],
|
||||
"component_count": 2
|
||||
},
|
||||
"MyIssues": {
|
||||
"path": "ui/src/pages/MyIssues.tsx",
|
||||
"components_imported": [
|
||||
"EmptyState",
|
||||
"EntityRow",
|
||||
"PageSkeleton",
|
||||
"StatusIcon"
|
||||
],
|
||||
"component_count": 4
|
||||
},
|
||||
"NewAgent": {
|
||||
"path": "ui/src/pages/NewAgent.tsx",
|
||||
"components_imported": [
|
||||
"AgentConfigForm",
|
||||
"ReportsToPicker",
|
||||
"agent-config-defaults",
|
||||
"agent-config-primitives",
|
||||
"button",
|
||||
"checkbox",
|
||||
"popover"
|
||||
],
|
||||
"component_count": 7
|
||||
},
|
||||
"NotFound": {
|
||||
"path": "ui/src/pages/NotFound.tsx",
|
||||
"components_imported": [
|
||||
"button"
|
||||
],
|
||||
"component_count": 1
|
||||
},
|
||||
"Org": {
|
||||
"path": "ui/src/pages/Org.tsx",
|
||||
"components_imported": [
|
||||
"EmptyState",
|
||||
"PageSkeleton",
|
||||
"StatusBadge"
|
||||
],
|
||||
"component_count": 3
|
||||
},
|
||||
"OrgChart": {
|
||||
"path": "ui/src/pages/OrgChart.tsx",
|
||||
"components_imported": [
|
||||
"AgentIconPicker",
|
||||
"EmptyState",
|
||||
"PageSkeleton",
|
||||
"button"
|
||||
],
|
||||
"component_count": 4
|
||||
},
|
||||
"PluginManager": {
|
||||
"path": "ui/src/pages/PluginManager.tsx",
|
||||
"components_imported": [
|
||||
"badge",
|
||||
"button",
|
||||
"card",
|
||||
"dialog",
|
||||
"input",
|
||||
"label"
|
||||
],
|
||||
"component_count": 6
|
||||
},
|
||||
"PluginPage": {
|
||||
"path": "ui/src/pages/PluginPage.tsx",
|
||||
"components_imported": [
|
||||
"button"
|
||||
],
|
||||
"component_count": 1
|
||||
},
|
||||
"PluginSettings": {
|
||||
"path": "ui/src/pages/PluginSettings.tsx",
|
||||
"components_imported": [
|
||||
"JsonSchemaForm",
|
||||
"PageTabBar",
|
||||
"badge",
|
||||
"button",
|
||||
"card",
|
||||
"separator",
|
||||
"tabs"
|
||||
],
|
||||
"component_count": 7
|
||||
},
|
||||
"ProfileSettings": {
|
||||
"path": "ui/src/pages/ProfileSettings.tsx",
|
||||
"components_imported": [
|
||||
"avatar",
|
||||
"button",
|
||||
"input",
|
||||
"label"
|
||||
],
|
||||
"component_count": 4
|
||||
},
|
||||
"ProjectDetail": {
|
||||
"path": "ui/src/pages/ProjectDetail.tsx",
|
||||
"components_imported": [
|
||||
"BudgetPolicyCard",
|
||||
"InlineEditor",
|
||||
"IssuesList",
|
||||
"PageSkeleton",
|
||||
"PageTabBar",
|
||||
"ProjectProperties",
|
||||
"ProjectWorkspacesContent",
|
||||
"StatusBadge",
|
||||
"button",
|
||||
"tabs"
|
||||
],
|
||||
"component_count": 10
|
||||
},
|
||||
"ProjectWorkspaceDetail": {
|
||||
"path": "ui/src/pages/ProjectWorkspaceDetail.tsx",
|
||||
"components_imported": [
|
||||
"PathInstructionsModal",
|
||||
"WorkspaceRuntimeControls",
|
||||
"button",
|
||||
"separator"
|
||||
],
|
||||
"component_count": 4
|
||||
},
|
||||
"Projects": {
|
||||
"path": "ui/src/pages/Projects.tsx",
|
||||
"components_imported": [
|
||||
"EmptyState",
|
||||
"EntityRow",
|
||||
"PageSkeleton",
|
||||
"StatusBadge",
|
||||
"button"
|
||||
],
|
||||
"component_count": 5
|
||||
},
|
||||
"RoutineDetail": {
|
||||
"path": "ui/src/pages/RoutineDetail.tsx",
|
||||
"components_imported": [
|
||||
"AgentActionButtons",
|
||||
"AgentIconPicker",
|
||||
"EmptyState",
|
||||
"InlineEntitySelector",
|
||||
"LiveRunWidget",
|
||||
"MarkdownEditor",
|
||||
"PageSkeleton",
|
||||
"RoutineRunVariablesDialog",
|
||||
"RoutineVariablesEditor",
|
||||
"ScheduleEditor",
|
||||
"badge",
|
||||
"button",
|
||||
"collapsible",
|
||||
"input",
|
||||
"label",
|
||||
"select",
|
||||
"separator",
|
||||
"tabs",
|
||||
"toggle-switch"
|
||||
],
|
||||
"component_count": 19
|
||||
},
|
||||
"Routines": {
|
||||
"path": "ui/src/pages/Routines.tsx",
|
||||
"components_imported": [
|
||||
"AgentIconPicker",
|
||||
"EmptyState",
|
||||
"InlineEntitySelector",
|
||||
"IssuesList",
|
||||
"MarkdownEditor",
|
||||
"PageSkeleton",
|
||||
"PageTabBar",
|
||||
"RoutineRunVariablesDialog",
|
||||
"RoutineVariablesEditor",
|
||||
"button",
|
||||
"card",
|
||||
"collapsible",
|
||||
"dialog",
|
||||
"dropdown-menu",
|
||||
"popover",
|
||||
"select",
|
||||
"tabs",
|
||||
"toggle-switch"
|
||||
],
|
||||
"component_count": 18
|
||||
},
|
||||
"RunTranscriptUxLab": {
|
||||
"path": "ui/src/pages/RunTranscriptUxLab.tsx",
|
||||
"components_imported": [
|
||||
"Identity",
|
||||
"RunTranscriptView",
|
||||
"StatusBadge",
|
||||
"badge",
|
||||
"button"
|
||||
],
|
||||
"component_count": 5
|
||||
},
|
||||
"UserProfile": {
|
||||
"path": "ui/src/pages/UserProfile.tsx",
|
||||
"components_imported": [
|
||||
"EmptyState",
|
||||
"PageSkeleton",
|
||||
"StatusBadge",
|
||||
"avatar"
|
||||
],
|
||||
"component_count": 4
|
||||
},
|
||||
"Workspaces": {
|
||||
"path": "ui/src/pages/Workspaces.tsx",
|
||||
"components_imported": [
|
||||
"PageSkeleton",
|
||||
"ProjectWorkspacesContent"
|
||||
],
|
||||
"component_count": 2
|
||||
}
|
||||
}
|
||||
}
|
||||
338
doc/design-system/_stories.json
Normal file
338
doc/design-system/_stories.json
Normal file
@@ -0,0 +1,338 @@
|
||||
{
|
||||
"generated_at": "2026-04-21T00:00:00Z",
|
||||
"repo_sha": "a26e1288b627e82c554445732c7d844648e6b5e1",
|
||||
"story_files": {
|
||||
"agent-management.stories.tsx": {
|
||||
"path": "ui/storybook/stories/agent-management.stories.tsx",
|
||||
"components_imported": [
|
||||
"ActiveAgentsPanel",
|
||||
"AgentActionButtons",
|
||||
"AgentConfigForm",
|
||||
"AgentIconPicker",
|
||||
"AgentProperties",
|
||||
"agent-config-defaults",
|
||||
"agent-config-primitives",
|
||||
"badge",
|
||||
"button",
|
||||
"card",
|
||||
"select",
|
||||
"separator"
|
||||
],
|
||||
"component_count": 12
|
||||
},
|
||||
"budget-finance.stories.tsx": {
|
||||
"path": "ui/storybook/stories/budget-finance.stories.tsx",
|
||||
"components_imported": [
|
||||
"AccountingModelCard",
|
||||
"BillerSpendCard",
|
||||
"BudgetIncidentCard",
|
||||
"BudgetSidebarMarker",
|
||||
"ClaudeSubscriptionPanel",
|
||||
"CodexSubscriptionPanel",
|
||||
"FinanceBillerCard",
|
||||
"FinanceKindCard",
|
||||
"FinanceTimelineCard",
|
||||
"ProviderQuotaCard",
|
||||
"badge",
|
||||
"card"
|
||||
],
|
||||
"component_count": 12
|
||||
},
|
||||
"chat-comments.stories.tsx": {
|
||||
"path": "ui/storybook/stories/chat-comments.stories.tsx",
|
||||
"components_imported": [
|
||||
"CommentThread",
|
||||
"InlineEntitySelector",
|
||||
"IssueChatThread",
|
||||
"MarkdownEditor",
|
||||
"RunChatSurface",
|
||||
"badge",
|
||||
"card"
|
||||
],
|
||||
"component_count": 7
|
||||
},
|
||||
"control-plane-surfaces.stories.tsx": {
|
||||
"path": "ui/storybook/stories/control-plane-surfaces.stories.tsx",
|
||||
"components_imported": [
|
||||
"ActivityRow",
|
||||
"ApprovalCard",
|
||||
"BudgetPolicyCard",
|
||||
"Identity",
|
||||
"IssueRow",
|
||||
"PriorityIcon",
|
||||
"StatusBadge",
|
||||
"badge",
|
||||
"card"
|
||||
],
|
||||
"component_count": 9
|
||||
},
|
||||
"data-viz-misc.stories.tsx": {
|
||||
"path": "ui/storybook/stories/data-viz-misc.stories.tsx",
|
||||
"components_imported": [
|
||||
"ActivityCharts",
|
||||
"AsciiArtAnimation",
|
||||
"CompanyPatternIcon",
|
||||
"EntityRow",
|
||||
"FilterBar",
|
||||
"KanbanBoard",
|
||||
"LiveRunWidget",
|
||||
"OnboardingWizard",
|
||||
"PackageFileTree",
|
||||
"PageSkeleton",
|
||||
"StatusBadge",
|
||||
"SwipeToArchive",
|
||||
"badge",
|
||||
"button",
|
||||
"card"
|
||||
],
|
||||
"component_count": 15
|
||||
},
|
||||
"dialogs-modals.stories.tsx": {
|
||||
"path": "ui/storybook/stories/dialogs-modals.stories.tsx",
|
||||
"components_imported": [
|
||||
"DocumentDiffModal",
|
||||
"ExecutionWorkspaceCloseDialog",
|
||||
"ImageGalleryModal",
|
||||
"NewAgentDialog",
|
||||
"NewGoalDialog",
|
||||
"NewIssueDialog",
|
||||
"NewProjectDialog",
|
||||
"PathInstructionsModal",
|
||||
"badge"
|
||||
],
|
||||
"component_count": 9
|
||||
},
|
||||
"forms-editors.stories.tsx": {
|
||||
"path": "ui/storybook/stories/forms-editors.stories.tsx",
|
||||
"components_imported": [
|
||||
"EnvVarEditor",
|
||||
"ExecutionParticipantPicker",
|
||||
"InlineEditor",
|
||||
"InlineEntitySelector",
|
||||
"JsonSchemaForm",
|
||||
"MarkdownBody",
|
||||
"MarkdownEditor",
|
||||
"ReportsToPicker",
|
||||
"RoutineRunVariablesDialog",
|
||||
"RoutineVariablesEditor",
|
||||
"ScheduleEditor",
|
||||
"badge",
|
||||
"button"
|
||||
],
|
||||
"component_count": 13
|
||||
},
|
||||
"foundations.stories.tsx": {
|
||||
"path": "ui/storybook/stories/foundations.stories.tsx",
|
||||
"components_imported": [
|
||||
"badge",
|
||||
"button",
|
||||
"card",
|
||||
"checkbox",
|
||||
"dialog",
|
||||
"input",
|
||||
"label",
|
||||
"popover",
|
||||
"select",
|
||||
"separator",
|
||||
"tabs",
|
||||
"textarea",
|
||||
"toggle-switch",
|
||||
"tooltip"
|
||||
],
|
||||
"component_count": 14
|
||||
},
|
||||
"issue-management.stories.tsx": {
|
||||
"path": "ui/storybook/stories/issue-management.stories.tsx",
|
||||
"components_imported": [
|
||||
"Identity",
|
||||
"IssueColumns",
|
||||
"IssueContinuationHandoff",
|
||||
"IssueDocumentsSection",
|
||||
"IssueFiltersPopover",
|
||||
"IssueGroupHeader",
|
||||
"IssueLinkQuicklook",
|
||||
"IssueProperties",
|
||||
"IssueRunLedger",
|
||||
"IssueWorkspaceCard",
|
||||
"IssuesList",
|
||||
"IssuesQuicklook",
|
||||
"PriorityIcon",
|
||||
"StatusBadge",
|
||||
"badge",
|
||||
"button",
|
||||
"card"
|
||||
],
|
||||
"component_count": 17
|
||||
},
|
||||
"navigation-layout.stories.tsx": {
|
||||
"path": "ui/storybook/stories/navigation-layout.stories.tsx",
|
||||
"components_imported": [
|
||||
"BreadcrumbBar",
|
||||
"CommandPalette",
|
||||
"CompanyRail",
|
||||
"CompanySwitcher",
|
||||
"KeyboardShortcutsCheatsheet",
|
||||
"MobileBottomNav",
|
||||
"PageTabBar",
|
||||
"Sidebar",
|
||||
"SidebarAccountMenu",
|
||||
"SidebarCompanyMenu",
|
||||
"StatusBadge",
|
||||
"badge",
|
||||
"command",
|
||||
"tabs"
|
||||
],
|
||||
"component_count": 14
|
||||
},
|
||||
"overview.stories.tsx": {
|
||||
"path": "ui/storybook/stories/overview.stories.tsx",
|
||||
"components_imported": [
|
||||
"badge",
|
||||
"card"
|
||||
],
|
||||
"component_count": 2
|
||||
},
|
||||
"projects-goals-workspaces.stories.tsx": {
|
||||
"path": "ui/storybook/stories/projects-goals-workspaces.stories.tsx",
|
||||
"components_imported": [
|
||||
"GoalProperties",
|
||||
"GoalTree",
|
||||
"ProjectProperties",
|
||||
"ProjectWorkspaceSummaryCard",
|
||||
"ProjectWorkspacesContent",
|
||||
"WorkspaceRuntimeControls",
|
||||
"WorktreeBanner",
|
||||
"badge",
|
||||
"card"
|
||||
],
|
||||
"component_count": 9
|
||||
},
|
||||
"status-language.stories.tsx": {
|
||||
"path": "ui/storybook/stories/status-language.stories.tsx",
|
||||
"components_imported": [
|
||||
"CopyText",
|
||||
"EmptyState",
|
||||
"Identity",
|
||||
"MetricCard",
|
||||
"PriorityIcon",
|
||||
"QuotaBar",
|
||||
"StatusBadge",
|
||||
"card"
|
||||
],
|
||||
"component_count": 8
|
||||
},
|
||||
"ux-labs.stories.tsx": {
|
||||
"path": "ui/storybook/stories/ux-labs.stories.tsx",
|
||||
"components_imported": [],
|
||||
"component_count": 0
|
||||
}
|
||||
},
|
||||
"covered_components_count": 104,
|
||||
"covered_components": [
|
||||
"AccountingModelCard",
|
||||
"ActiveAgentsPanel",
|
||||
"ActivityCharts",
|
||||
"ActivityRow",
|
||||
"AgentActionButtons",
|
||||
"AgentConfigForm",
|
||||
"AgentIconPicker",
|
||||
"AgentProperties",
|
||||
"ApprovalCard",
|
||||
"AsciiArtAnimation",
|
||||
"BillerSpendCard",
|
||||
"BreadcrumbBar",
|
||||
"BudgetIncidentCard",
|
||||
"BudgetPolicyCard",
|
||||
"BudgetSidebarMarker",
|
||||
"ClaudeSubscriptionPanel",
|
||||
"CodexSubscriptionPanel",
|
||||
"CommandPalette",
|
||||
"CommentThread",
|
||||
"CompanyPatternIcon",
|
||||
"CompanyRail",
|
||||
"CompanySwitcher",
|
||||
"CopyText",
|
||||
"DocumentDiffModal",
|
||||
"EmptyState",
|
||||
"EntityRow",
|
||||
"EnvVarEditor",
|
||||
"ExecutionParticipantPicker",
|
||||
"ExecutionWorkspaceCloseDialog",
|
||||
"FilterBar",
|
||||
"FinanceBillerCard",
|
||||
"FinanceKindCard",
|
||||
"FinanceTimelineCard",
|
||||
"GoalProperties",
|
||||
"GoalTree",
|
||||
"Identity",
|
||||
"ImageGalleryModal",
|
||||
"InlineEditor",
|
||||
"InlineEntitySelector",
|
||||
"IssueChatThread",
|
||||
"IssueColumns",
|
||||
"IssueContinuationHandoff",
|
||||
"IssueDocumentsSection",
|
||||
"IssueFiltersPopover",
|
||||
"IssueGroupHeader",
|
||||
"IssueLinkQuicklook",
|
||||
"IssueProperties",
|
||||
"IssueRow",
|
||||
"IssueRunLedger",
|
||||
"IssueWorkspaceCard",
|
||||
"IssuesList",
|
||||
"IssuesQuicklook",
|
||||
"JsonSchemaForm",
|
||||
"KanbanBoard",
|
||||
"KeyboardShortcutsCheatsheet",
|
||||
"LiveRunWidget",
|
||||
"MarkdownBody",
|
||||
"MarkdownEditor",
|
||||
"MetricCard",
|
||||
"MobileBottomNav",
|
||||
"NewAgentDialog",
|
||||
"NewGoalDialog",
|
||||
"NewIssueDialog",
|
||||
"NewProjectDialog",
|
||||
"OnboardingWizard",
|
||||
"PackageFileTree",
|
||||
"PageSkeleton",
|
||||
"PageTabBar",
|
||||
"PathInstructionsModal",
|
||||
"PriorityIcon",
|
||||
"ProjectProperties",
|
||||
"ProjectWorkspaceSummaryCard",
|
||||
"ProjectWorkspacesContent",
|
||||
"ProviderQuotaCard",
|
||||
"QuotaBar",
|
||||
"ReportsToPicker",
|
||||
"RoutineRunVariablesDialog",
|
||||
"RoutineVariablesEditor",
|
||||
"RunChatSurface",
|
||||
"ScheduleEditor",
|
||||
"Sidebar",
|
||||
"SidebarAccountMenu",
|
||||
"SidebarCompanyMenu",
|
||||
"StatusBadge",
|
||||
"SwipeToArchive",
|
||||
"WorkspaceRuntimeControls",
|
||||
"WorktreeBanner",
|
||||
"agent-config-defaults",
|
||||
"agent-config-primitives",
|
||||
"badge",
|
||||
"button",
|
||||
"card",
|
||||
"checkbox",
|
||||
"command",
|
||||
"dialog",
|
||||
"input",
|
||||
"label",
|
||||
"popover",
|
||||
"select",
|
||||
"separator",
|
||||
"tabs",
|
||||
"textarea",
|
||||
"toggle-switch",
|
||||
"tooltip"
|
||||
],
|
||||
"covered_components_note": "Computed by parsing 'from \"@/components/...\"' and relative imports across all .stories.tsx files. Coverage is set membership \u2014 a component appears once if any story imports it, regardless of how many variants/states are rendered."
|
||||
}
|
||||
21
doc/design-system/components/ActivityCharts.md
Normal file
21
doc/design-system/components/ActivityCharts.md
Normal file
@@ -0,0 +1,21 @@
|
||||
# ActivityCharts
|
||||
|
||||
`ui/src/components/ActivityCharts.tsx`
|
||||
|
||||
[INFER] Standalone component. No file-level docstring.
|
||||
|
||||
## Quick facts
|
||||
|
||||
- **Category:** `standalone`
|
||||
- **Usage:** 3 imports (2 pages, 1 components)
|
||||
- **Storybook:** yes — see ui/storybook/stories/ (thematic stories, not per-component)
|
||||
- **File size:** 274 lines
|
||||
- **Sibling exports:** ChartCard, IssueStatusChart, PriorityChart, RunActivityChart, SuccessRateChart
|
||||
|
||||
## Props
|
||||
|
||||
[INFER] No `*Props` interface/type found by static extraction. See source file.
|
||||
|
||||
## Used by
|
||||
|
||||
- **Pages:** `AgentDetail`, `Dashboard`
|
||||
40
doc/design-system/components/AgentConfigForm.md
Normal file
40
doc/design-system/components/AgentConfigForm.md
Normal file
@@ -0,0 +1,40 @@
|
||||
# AgentConfigForm
|
||||
|
||||
`ui/src/components/AgentConfigForm.tsx`
|
||||
|
||||
[INFER] Composite component. No file-level docstring.
|
||||
|
||||
## Quick facts
|
||||
|
||||
- **Category:** `composite`
|
||||
- **Usage:** 5 imports (3 pages, 0 components)
|
||||
- **Storybook:** yes — see ui/storybook/stories/ (thematic stories, not per-component)
|
||||
- **File size:** 1403 lines
|
||||
|
||||
## Props
|
||||
|
||||
### `AgentConfigFormProps`
|
||||
|
||||
```ts
|
||||
adapterModels?: AdapterModel[];
|
||||
onDirtyChange?: (dirty: boolean) => void;
|
||||
onSaveActionChange?: (save: (() => void) | null) => void;
|
||||
onCancelActionChange?: (cancel: (() => void) | null) => void;
|
||||
hideInlineSave?: boolean;
|
||||
showAdapterTypeField?: boolean;
|
||||
showAdapterTestEnvironmentButton?: boolean;
|
||||
showCreateRunPolicySection?: boolean;
|
||||
hideInstructionsFile?: boolean;
|
||||
/** Hide the prompt template field from the Identity section (used when it's shown in a separate Prompts tab). */
|
||||
hidePromptTemplate?: boolean;
|
||||
/** "cards" renders each section as heading + bordered card (for settings pages). Default: "inline" (border-b dividers). */
|
||||
sectionLayout?: "inline" | "cards";
|
||||
```
|
||||
|
||||
## Composes
|
||||
|
||||
- **Primitives:** [button](./Button.md), [popover](./Popover.md)
|
||||
|
||||
## Used by
|
||||
|
||||
- **Pages:** `AgentDetail`, `CompanyImport`, `NewAgent`
|
||||
38
doc/design-system/components/AgentIconPicker.md
Normal file
38
doc/design-system/components/AgentIconPicker.md
Normal file
@@ -0,0 +1,38 @@
|
||||
# AgentIconPicker
|
||||
|
||||
`ui/src/components/AgentIconPicker.tsx`
|
||||
|
||||
[INFER] Composite component. No file-level docstring.
|
||||
|
||||
## Quick facts
|
||||
|
||||
- **Category:** `composite`
|
||||
- **Usage:** 13 imports (4 pages, 9 components)
|
||||
- **Storybook:** yes — see ui/storybook/stories/ (thematic stories, not per-component)
|
||||
- **File size:** 81 lines
|
||||
- **Sibling exports:** AgentIcon
|
||||
|
||||
## Props
|
||||
|
||||
### `AgentIconProps`
|
||||
|
||||
```ts
|
||||
icon: string | null | undefined;
|
||||
className?: string;
|
||||
```
|
||||
|
||||
### `AgentIconPickerProps`
|
||||
|
||||
```ts
|
||||
value: string | null | undefined;
|
||||
onChange: (icon: string) => void;
|
||||
children: React.ReactNode;
|
||||
```
|
||||
|
||||
## Composes
|
||||
|
||||
- **Primitives:** [input](./Input.md), [popover](./Popover.md)
|
||||
|
||||
## Used by
|
||||
|
||||
- **Pages:** `AgentDetail`, `OrgChart`, `RoutineDetail`, `Routines`
|
||||
24
doc/design-system/components/ApprovalCard.md
Normal file
24
doc/design-system/components/ApprovalCard.md
Normal file
@@ -0,0 +1,24 @@
|
||||
# ApprovalCard
|
||||
|
||||
`ui/src/components/ApprovalCard.tsx`
|
||||
|
||||
[INFER] Composite component. No file-level docstring.
|
||||
|
||||
## Quick facts
|
||||
|
||||
- **Category:** `composite`
|
||||
- **Usage:** 3 imports (2 pages, 1 components)
|
||||
- **Storybook:** yes — see ui/storybook/stories/ (thematic stories, not per-component)
|
||||
- **File size:** 153 lines
|
||||
|
||||
## Props
|
||||
|
||||
[INFER] No `*Props` interface/type found by static extraction. See source file.
|
||||
|
||||
## Composes
|
||||
|
||||
- **Primitives:** [badge](./Badge.md), [button](./Button.md)
|
||||
|
||||
## Used by
|
||||
|
||||
- **Pages:** `Approvals`, `IssueDetail`
|
||||
21
doc/design-system/components/ApprovalPayload.md
Normal file
21
doc/design-system/components/ApprovalPayload.md
Normal file
@@ -0,0 +1,21 @@
|
||||
# ApprovalPayload
|
||||
|
||||
`ui/src/components/ApprovalPayload.tsx`
|
||||
|
||||
[INFER] Standalone component. No file-level docstring.
|
||||
|
||||
## Quick facts
|
||||
|
||||
- **Category:** `standalone`
|
||||
- **Usage:** 4 imports (2 pages, 2 components)
|
||||
- **Storybook:** no
|
||||
- **File size:** 248 lines
|
||||
- **Sibling exports:** ApprovalPayloadRenderer, BoardApprovalPayload, BudgetOverridePayload, CeoStrategyPayload, HireAgentPayload
|
||||
|
||||
## Props
|
||||
|
||||
[INFER] No `*Props` interface/type found by static extraction. See source file.
|
||||
|
||||
## Used by
|
||||
|
||||
- **Pages:** `ApprovalDetail`, `Inbox`
|
||||
22
doc/design-system/components/Avatar.md
Normal file
22
doc/design-system/components/Avatar.md
Normal file
@@ -0,0 +1,22 @@
|
||||
# Avatar
|
||||
|
||||
`ui/src/components/ui/avatar.tsx`
|
||||
|
||||
[INFER] Primitive component. No file-level docstring.
|
||||
|
||||
## Quick facts
|
||||
|
||||
- **Category:** `primitive`
|
||||
- **Usage:** 7 imports (3 pages, 4 components)
|
||||
- **Storybook:** no
|
||||
- **File size:** 108 lines
|
||||
- **Sibling exports:** AvatarBadge, AvatarFallback, AvatarGroup, AvatarGroupCount, AvatarImage
|
||||
|
||||
## Props
|
||||
|
||||
[INFER] No `*Props` interface/type found by static extraction. See source file.
|
||||
|
||||
## Used by
|
||||
|
||||
- **Pages:** `DesignGuide`, `ProfileSettings`, `UserProfile`
|
||||
- **Components:** `CommentThread`, `Identity`, `IssueChatThread`, `SidebarAccountMenu`
|
||||
26
doc/design-system/components/Badge.md
Normal file
26
doc/design-system/components/Badge.md
Normal file
@@ -0,0 +1,26 @@
|
||||
# Badge
|
||||
|
||||
`ui/src/components/ui/badge.tsx`
|
||||
|
||||
[INFER] Primitive component. No file-level docstring.
|
||||
|
||||
## Quick facts
|
||||
|
||||
- **Category:** `primitive`
|
||||
- **Usage:** 18 imports (11 pages, 7 components)
|
||||
- **Storybook:** yes — see ui/storybook/stories/ (thematic stories, not per-component)
|
||||
- **File size:** 49 lines
|
||||
|
||||
## Props
|
||||
|
||||
[INFER] No `*Props` interface/type found by static extraction. See source file.
|
||||
|
||||
## Variants (CVA)
|
||||
|
||||
- **variant**: `default`, `secondary`, `destructive`, `outline`, `ghost`, `link`
|
||||
|
||||
|
||||
## Used by
|
||||
|
||||
- **Pages:** `AdapterManager`, `CompanyAccess`, `DesignGuide`, `InstanceSettings`, `InviteUxLab` … (+6 more)
|
||||
- **Components:** `ApprovalCard`, `BudgetIncidentCard`, `FilterBar`, `FinanceTimelineCard`, `IssueFiltersPopover` … (+2 more)
|
||||
24
doc/design-system/components/BudgetPolicyCard.md
Normal file
24
doc/design-system/components/BudgetPolicyCard.md
Normal file
@@ -0,0 +1,24 @@
|
||||
# BudgetPolicyCard
|
||||
|
||||
`ui/src/components/BudgetPolicyCard.tsx`
|
||||
|
||||
[INFER] Composite component. No file-level docstring.
|
||||
|
||||
## Quick facts
|
||||
|
||||
- **Category:** `composite`
|
||||
- **Usage:** 3 imports (3 pages, 0 components)
|
||||
- **Storybook:** yes — see ui/storybook/stories/ (thematic stories, not per-component)
|
||||
- **File size:** 220 lines
|
||||
|
||||
## Props
|
||||
|
||||
[INFER] No `*Props` interface/type found by static extraction. See source file.
|
||||
|
||||
## Composes
|
||||
|
||||
- **Primitives:** [button](./Button.md), [card](./Card.md), [input](./Input.md)
|
||||
|
||||
## Used by
|
||||
|
||||
- **Pages:** `AgentDetail`, `Costs`, `ProjectDetail`
|
||||
27
doc/design-system/components/Button.md
Normal file
27
doc/design-system/components/Button.md
Normal file
@@ -0,0 +1,27 @@
|
||||
# Button
|
||||
|
||||
`ui/src/components/ui/button.tsx`
|
||||
|
||||
[INFER] Primitive component. No file-level docstring.
|
||||
|
||||
## Quick facts
|
||||
|
||||
- **Category:** `primitive`
|
||||
- **Usage:** 81 imports (41 pages, 38 components)
|
||||
- **Storybook:** yes — see ui/storybook/stories/ (thematic stories, not per-component)
|
||||
- **File size:** 71 lines
|
||||
|
||||
## Props
|
||||
|
||||
[INFER] No `*Props` interface/type found by static extraction. See source file.
|
||||
|
||||
## Variants (CVA)
|
||||
|
||||
- **variant**: `default`, `destructive`, `outline`, `secondary`, `ghost`, `link`
|
||||
- **size**: `default`, `xs`, `sm`, `lg`, `icon`, `icon-xs`, `icon-sm`, `icon-lg`
|
||||
|
||||
|
||||
## Used by
|
||||
|
||||
- **Pages:** `AdapterManager`, `AgentDetail`, `Agents`, `ApprovalDetail`, `Auth` … (+36 more)
|
||||
- **Components:** `AgentActionButtons`, `AgentConfigForm`, `ApprovalCard`, `BreadcrumbBar`, `BudgetIncidentCard` … (+33 more)
|
||||
22
doc/design-system/components/Card.md
Normal file
22
doc/design-system/components/Card.md
Normal file
@@ -0,0 +1,22 @@
|
||||
# Card
|
||||
|
||||
`ui/src/components/ui/card.tsx`
|
||||
|
||||
[INFER] Primitive component. No file-level docstring.
|
||||
|
||||
## Quick facts
|
||||
|
||||
- **Category:** `primitive`
|
||||
- **Usage:** 18 imports (10 pages, 8 components)
|
||||
- **Storybook:** yes — see ui/storybook/stories/ (thematic stories, not per-component)
|
||||
- **File size:** 93 lines
|
||||
- **Sibling exports:** CardAction, CardContent, CardDescription, CardFooter, CardHeader, CardTitle
|
||||
|
||||
## Props
|
||||
|
||||
[INFER] No `*Props` interface/type found by static extraction. See source file.
|
||||
|
||||
## Used by
|
||||
|
||||
- **Pages:** `AdapterManager`, `Costs`, `DesignGuide`, `ExecutionWorkspaceDetail`, `InstanceSettings` … (+5 more)
|
||||
- **Components:** `AccountingModelCard`, `BillerSpendCard`, `BudgetIncidentCard`, `BudgetPolicyCard`, `FinanceBillerCard` … (+3 more)
|
||||
21
doc/design-system/components/Checkbox.md
Normal file
21
doc/design-system/components/Checkbox.md
Normal file
@@ -0,0 +1,21 @@
|
||||
# Checkbox
|
||||
|
||||
`ui/src/components/ui/checkbox.tsx`
|
||||
|
||||
[INFER] Primitive component. No file-level docstring.
|
||||
|
||||
## Quick facts
|
||||
|
||||
- **Category:** `primitive`
|
||||
- **Usage:** 6 imports (4 pages, 2 components)
|
||||
- **Storybook:** yes — see ui/storybook/stories/ (thematic stories, not per-component)
|
||||
- **File size:** 33 lines
|
||||
|
||||
## Props
|
||||
|
||||
[INFER] No `*Props` interface/type found by static extraction. See source file.
|
||||
|
||||
## Used by
|
||||
|
||||
- **Pages:** `CompanyAccess`, `DesignGuide`, `InstanceAccess`, `NewAgent`
|
||||
- **Components:** `IssueFiltersPopover`, `JsonSchemaForm`
|
||||
22
doc/design-system/components/Collapsible.md
Normal file
22
doc/design-system/components/Collapsible.md
Normal file
@@ -0,0 +1,22 @@
|
||||
# Collapsible
|
||||
|
||||
`ui/src/components/ui/collapsible.tsx`
|
||||
|
||||
[INFER] Primitive component. No file-level docstring.
|
||||
|
||||
## Quick facts
|
||||
|
||||
- **Category:** `primitive`
|
||||
- **Usage:** 8 imports (4 pages, 4 components)
|
||||
- **Storybook:** no
|
||||
- **File size:** 34 lines
|
||||
- **Sibling exports:** CollapsibleContent, CollapsibleTrigger
|
||||
|
||||
## Props
|
||||
|
||||
[INFER] No `*Props` interface/type found by static extraction. See source file.
|
||||
|
||||
## Used by
|
||||
|
||||
- **Pages:** `AgentDetail`, `DesignGuide`, `RoutineDetail`, `Routines`
|
||||
- **Components:** `IssuesList`, `RoutineVariablesEditor`, `SidebarAgents`, `SidebarProjects`
|
||||
28
doc/design-system/components/CompanyPatternIcon.md
Normal file
28
doc/design-system/components/CompanyPatternIcon.md
Normal file
@@ -0,0 +1,28 @@
|
||||
# CompanyPatternIcon
|
||||
|
||||
`ui/src/components/CompanyPatternIcon.tsx`
|
||||
|
||||
[INFER] Standalone component. No file-level docstring.
|
||||
|
||||
## Quick facts
|
||||
|
||||
- **Category:** `standalone`
|
||||
- **Usage:** 4 imports (3 pages, 1 components)
|
||||
- **Storybook:** yes — see ui/storybook/stories/ (thematic stories, not per-component)
|
||||
- **File size:** 218 lines
|
||||
|
||||
## Props
|
||||
|
||||
### `CompanyPatternIconProps`
|
||||
|
||||
```ts
|
||||
companyName: string;
|
||||
logoUrl?: string | null;
|
||||
brandColor?: string | null;
|
||||
className?: string;
|
||||
logoFit?: "cover" | "contain";
|
||||
```
|
||||
|
||||
## Used by
|
||||
|
||||
- **Pages:** `CompanySettings`, `InviteLanding`, `InviteUxLab`
|
||||
32
doc/design-system/components/CopyText.md
Normal file
32
doc/design-system/components/CopyText.md
Normal file
@@ -0,0 +1,32 @@
|
||||
# CopyText
|
||||
|
||||
`ui/src/components/CopyText.tsx`
|
||||
|
||||
[INFER] Standalone component. No file-level docstring.
|
||||
|
||||
## Quick facts
|
||||
|
||||
- **Category:** `standalone`
|
||||
- **Usage:** 3 imports (2 pages, 1 components)
|
||||
- **Storybook:** yes — see ui/storybook/stories/ (thematic stories, not per-component)
|
||||
- **File size:** 88 lines
|
||||
|
||||
## Props
|
||||
|
||||
### `CopyTextProps`
|
||||
|
||||
```ts
|
||||
text: string;
|
||||
/** What to display. Defaults to `text`. */
|
||||
children?: React.ReactNode;
|
||||
containerClassName?: string;
|
||||
className?: string;
|
||||
ariaLabel?: string;
|
||||
title?: string;
|
||||
/** Tooltip message shown after copying. Default: "Copied!" */
|
||||
copiedLabel?: string;
|
||||
```
|
||||
|
||||
## Used by
|
||||
|
||||
- **Pages:** `AgentDetail`, `ExecutionWorkspaceDetail`
|
||||
26
doc/design-system/components/Dialog.md
Normal file
26
doc/design-system/components/Dialog.md
Normal file
@@ -0,0 +1,26 @@
|
||||
# Dialog
|
||||
|
||||
`ui/src/components/ui/dialog.tsx`
|
||||
|
||||
[INFER] Primitive component. No file-level docstring.
|
||||
|
||||
## Quick facts
|
||||
|
||||
- **Category:** `primitive`
|
||||
- **Usage:** 21 imports (7 pages, 14 components)
|
||||
- **Storybook:** yes — see ui/storybook/stories/ (thematic stories, not per-component)
|
||||
- **File size:** 157 lines
|
||||
- **Sibling exports:** DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay
|
||||
|
||||
## Props
|
||||
|
||||
[INFER] No `*Props` interface/type found by static extraction. See source file.
|
||||
|
||||
## Composes
|
||||
|
||||
- **Primitives:** [button](./Button.md)
|
||||
|
||||
## Used by
|
||||
|
||||
- **Pages:** `AdapterManager`, `CompanyAccess`, `CompanySkills`, `DesignGuide`, `Inbox` … (+2 more)
|
||||
- **Components:** `DocumentDiffModal`, `IssueChatThread`, `KeyboardShortcutsCheatsheet`, `NewAgentDialog`, `NewGoalDialog` … (+9 more)
|
||||
22
doc/design-system/components/DropdownMenu.md
Normal file
22
doc/design-system/components/DropdownMenu.md
Normal file
@@ -0,0 +1,22 @@
|
||||
# DropdownMenu
|
||||
|
||||
`ui/src/components/ui/dropdown-menu.tsx`
|
||||
|
||||
[INFER] Primitive component. No file-level docstring.
|
||||
|
||||
## Quick facts
|
||||
|
||||
- **Category:** `primitive`
|
||||
- **Usage:** 8 imports (3 pages, 5 components)
|
||||
- **Storybook:** no
|
||||
- **File size:** 258 lines
|
||||
- **Sibling exports:** DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal
|
||||
|
||||
## Props
|
||||
|
||||
[INFER] No `*Props` interface/type found by static extraction. See source file.
|
||||
|
||||
## Used by
|
||||
|
||||
- **Pages:** `Companies`, `DesignGuide`, `Routines`
|
||||
- **Components:** `CompanySwitcher`, `IssueChatThread`, `IssueColumns`, `IssueDocumentsSection`, `SidebarCompanyMenu`
|
||||
31
doc/design-system/components/EmptyState.md
Normal file
31
doc/design-system/components/EmptyState.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# EmptyState
|
||||
|
||||
`ui/src/components/EmptyState.tsx`
|
||||
|
||||
[INFER] Composite component. No file-level docstring.
|
||||
|
||||
## Quick facts
|
||||
|
||||
- **Category:** `composite`
|
||||
- **Usage:** 20 imports (19 pages, 1 components)
|
||||
- **Storybook:** yes — see ui/storybook/stories/ (thematic stories, not per-component)
|
||||
- **File size:** 28 lines
|
||||
|
||||
## Props
|
||||
|
||||
### `EmptyStateProps`
|
||||
|
||||
```ts
|
||||
icon: LucideIcon;
|
||||
message: string;
|
||||
action?: string;
|
||||
onAction?: () => void;
|
||||
```
|
||||
|
||||
## Composes
|
||||
|
||||
- **Primitives:** [button](./Button.md)
|
||||
|
||||
## Used by
|
||||
|
||||
- **Pages:** `Activity`, `Agents`, `CompanyExport`, `CompanyImport`, `CompanySkills` … (+14 more)
|
||||
32
doc/design-system/components/EntityRow.md
Normal file
32
doc/design-system/components/EntityRow.md
Normal file
@@ -0,0 +1,32 @@
|
||||
# EntityRow
|
||||
|
||||
`ui/src/components/EntityRow.tsx`
|
||||
|
||||
[INFER] Standalone component. No file-level docstring.
|
||||
|
||||
## Quick facts
|
||||
|
||||
- **Category:** `standalone`
|
||||
- **Usage:** 6 imports (6 pages, 0 components)
|
||||
- **Storybook:** yes — see ui/storybook/stories/ (thematic stories, not per-component)
|
||||
- **File size:** 70 lines
|
||||
|
||||
## Props
|
||||
|
||||
### `EntityRowProps`
|
||||
|
||||
```ts
|
||||
leading?: ReactNode;
|
||||
identifier?: string;
|
||||
title: string;
|
||||
subtitle?: string;
|
||||
trailing?: ReactNode;
|
||||
selected?: boolean;
|
||||
to?: string;
|
||||
onClick?: () => void;
|
||||
className?: string;
|
||||
```
|
||||
|
||||
## Used by
|
||||
|
||||
- **Pages:** `AgentDetail`, `Agents`, `DesignGuide`, `GoalDetail`, `MyIssues` … (+1 more)
|
||||
32
doc/design-system/components/Identity.md
Normal file
32
doc/design-system/components/Identity.md
Normal file
@@ -0,0 +1,32 @@
|
||||
# Identity
|
||||
|
||||
`ui/src/components/Identity.tsx`
|
||||
|
||||
[INFER] Composite component. No file-level docstring.
|
||||
|
||||
## Quick facts
|
||||
|
||||
- **Category:** `composite`
|
||||
- **Usage:** 19 imports (7 pages, 12 components)
|
||||
- **Storybook:** yes — see ui/storybook/stories/ (thematic stories, not per-component)
|
||||
- **File size:** 40 lines
|
||||
|
||||
## Props
|
||||
|
||||
### `IdentityProps`
|
||||
|
||||
```ts
|
||||
name: string;
|
||||
avatarUrl?: string | null;
|
||||
initials?: string;
|
||||
size?: IdentitySize;
|
||||
className?: string;
|
||||
```
|
||||
|
||||
## Composes
|
||||
|
||||
- **Primitives:** [avatar](./Avatar.md)
|
||||
|
||||
## Used by
|
||||
|
||||
- **Pages:** `AgentDetail`, `ApprovalDetail`, `Costs`, `Dashboard`, `DesignGuide` … (+2 more)
|
||||
34
doc/design-system/components/InlineEditor.md
Normal file
34
doc/design-system/components/InlineEditor.md
Normal file
@@ -0,0 +1,34 @@
|
||||
# InlineEditor
|
||||
|
||||
`ui/src/components/InlineEditor.tsx`
|
||||
|
||||
[INFER] Standalone component. No file-level docstring.
|
||||
|
||||
## Quick facts
|
||||
|
||||
- **Category:** `standalone`
|
||||
- **Usage:** 6 imports (4 pages, 2 components)
|
||||
- **Storybook:** yes — see ui/storybook/stories/ (thematic stories, not per-component)
|
||||
- **File size:** 310 lines
|
||||
|
||||
## Props
|
||||
|
||||
### `InlineEditorProps`
|
||||
|
||||
```ts
|
||||
value: string;
|
||||
onSave: (value: string) => void | Promise<unknown>;
|
||||
as?: "h1" | "h2" | "p" | "span";
|
||||
className?: string;
|
||||
placeholder?: string;
|
||||
multiline?: boolean;
|
||||
imageUploadHandler?: (file: File) => Promise<string>;
|
||||
/** Called when a non-image file is dropped onto the editor. */
|
||||
onDropFile?: (file: File) => Promise<void>;
|
||||
mentions?: MentionOption[];
|
||||
nullable?: boolean;
|
||||
```
|
||||
|
||||
## Used by
|
||||
|
||||
- **Pages:** `DesignGuide`, `GoalDetail`, `IssueDetail`, `ProjectDetail`
|
||||
43
doc/design-system/components/InlineEntitySelector.md
Normal file
43
doc/design-system/components/InlineEntitySelector.md
Normal file
@@ -0,0 +1,43 @@
|
||||
# InlineEntitySelector
|
||||
|
||||
`ui/src/components/InlineEntitySelector.tsx`
|
||||
|
||||
[INFER] Composite component. No file-level docstring.
|
||||
|
||||
## Quick facts
|
||||
|
||||
- **Category:** `composite`
|
||||
- **Usage:** 8 imports (2 pages, 4 components)
|
||||
- **Storybook:** yes — see ui/storybook/stories/ (thematic stories, not per-component)
|
||||
- **File size:** 215 lines
|
||||
|
||||
## Props
|
||||
|
||||
### `InlineEntitySelectorProps`
|
||||
|
||||
```ts
|
||||
value: string;
|
||||
options: InlineEntityOption[];
|
||||
placeholder: string;
|
||||
noneLabel: string;
|
||||
searchPlaceholder: string;
|
||||
emptyMessage: string;
|
||||
onChange: (id: string) => void;
|
||||
onConfirm?: () => void;
|
||||
className?: string;
|
||||
renderTriggerValue?: (option: InlineEntityOption | null) => ReactNode;
|
||||
renderOption?: (option: InlineEntityOption, isSelected: boolean) => ReactNode;
|
||||
recentOptionIds?: string[];
|
||||
/** Skip the Portal so the popover stays in the DOM tree (fixes scroll inside Dialogs). */
|
||||
disablePortal?: boolean;
|
||||
/** Open the popover when the trigger receives keyboard/programmatic focus. */
|
||||
openOnFocus?: boolean;
|
||||
```
|
||||
|
||||
## Composes
|
||||
|
||||
- **Primitives:** [popover](./Popover.md)
|
||||
|
||||
## Used by
|
||||
|
||||
- **Pages:** `RoutineDetail`, `Routines`
|
||||
21
doc/design-system/components/Input.md
Normal file
21
doc/design-system/components/Input.md
Normal file
@@ -0,0 +1,21 @@
|
||||
# Input
|
||||
|
||||
`ui/src/components/ui/input.tsx`
|
||||
|
||||
[INFER] Primitive component. No file-level docstring.
|
||||
|
||||
## Quick facts
|
||||
|
||||
- **Category:** `primitive`
|
||||
- **Usage:** 20 imports (10 pages, 10 components)
|
||||
- **Storybook:** yes — see ui/storybook/stories/ (thematic stories, not per-component)
|
||||
- **File size:** 22 lines
|
||||
|
||||
## Props
|
||||
|
||||
[INFER] No `*Props` interface/type found by static extraction. See source file.
|
||||
|
||||
## Used by
|
||||
|
||||
- **Pages:** `AdapterManager`, `AgentDetail`, `Companies`, `CompanySkills`, `DesignGuide` … (+5 more)
|
||||
- **Components:** `AgentIconPicker`, `BudgetIncidentCard`, `BudgetPolicyCard`, `IssueDocumentsSection`, `IssueFiltersPopover` … (+5 more)
|
||||
73
doc/design-system/components/IssueChatThread.md
Normal file
73
doc/design-system/components/IssueChatThread.md
Normal file
@@ -0,0 +1,73 @@
|
||||
# IssueChatThread
|
||||
|
||||
`ui/src/components/IssueChatThread.tsx`
|
||||
|
||||
[INFER] Composite component. No file-level docstring.
|
||||
|
||||
## Quick facts
|
||||
|
||||
- **Category:** `composite`
|
||||
- **Usage:** 4 imports (2 pages, 2 components)
|
||||
- **Storybook:** yes — see ui/storybook/stories/ (thematic stories, not per-component)
|
||||
- **File size:** 2399 lines
|
||||
|
||||
## Props
|
||||
|
||||
### `IssueChatComposerProps`
|
||||
|
||||
```ts
|
||||
onImageUpload?: (file: File) => Promise<string>;
|
||||
onAttachImage?: (file: File) => Promise<void>;
|
||||
draftKey?: string;
|
||||
enableReassign?: boolean;
|
||||
reassignOptions?: InlineEntityOption[];
|
||||
currentAssigneeValue?: string;
|
||||
suggestedAssigneeValue?: string;
|
||||
mentions?: MentionOption[];
|
||||
agentMap?: Map<string, Agent>;
|
||||
composerDisabledReason?: string | null;
|
||||
issueStatus?: string;
|
||||
```
|
||||
|
||||
### `IssueChatThreadProps`
|
||||
|
||||
```ts
|
||||
comments: IssueChatComment[];
|
||||
feedbackVotes?: FeedbackVote[];
|
||||
feedbackDataSharingPreference?: FeedbackDataSharingPreference;
|
||||
feedbackTermsUrl?: string | null;
|
||||
linkedRuns?: IssueChatLinkedRun[];
|
||||
timelineEvents?: IssueTimelineEvent[];
|
||||
liveRuns?: LiveRunForIssue[];
|
||||
activeRun?: ActiveRunForIssue | null;
|
||||
blockedBy?: IssueRelationIssueSummary[];
|
||||
companyId?: string | null;
|
||||
projectId?: string | null;
|
||||
issueStatus?: string;
|
||||
agentMap?: Map<string, Agent>;
|
||||
currentUserId?: string | null;
|
||||
userLabelMap?: ReadonlyMap<string, string> | null;
|
||||
userProfileMap?: ReadonlyMap<string, CompanyUserProfile> | null;
|
||||
onVote?: (
|
||||
commentId: string,
|
||||
vote: FeedbackVoteValue,
|
||||
options?: { allowSharing?: boolean; reason?: string
|
||||
```
|
||||
|
||||
### `IssueChatErrorBoundaryProps`
|
||||
|
||||
```ts
|
||||
resetKey: string;
|
||||
messages: readonly ThreadMessage[];
|
||||
emptyMessage: string;
|
||||
variant: "full" | "embedded";
|
||||
children: ReactNode;
|
||||
```
|
||||
|
||||
## Composes
|
||||
|
||||
- **Primitives:** [avatar](./Avatar.md), [button](./Button.md), [dialog](./Dialog.md), `dropdown-menu`, [popover](./Popover.md), [textarea](./Textarea.md), [tooltip](./Tooltip.md)
|
||||
|
||||
## Used by
|
||||
|
||||
- **Pages:** `IssueChatUxLab`, `IssueDetail`
|
||||
24
doc/design-system/components/IssueFiltersPopover.md
Normal file
24
doc/design-system/components/IssueFiltersPopover.md
Normal file
@@ -0,0 +1,24 @@
|
||||
# IssueFiltersPopover
|
||||
|
||||
`ui/src/components/IssueFiltersPopover.tsx`
|
||||
|
||||
[INFER] Composite component. No file-level docstring.
|
||||
|
||||
## Quick facts
|
||||
|
||||
- **Category:** `composite`
|
||||
- **Usage:** 3 imports (1 pages, 2 components)
|
||||
- **Storybook:** yes — see ui/storybook/stories/ (thematic stories, not per-component)
|
||||
- **File size:** 366 lines
|
||||
|
||||
## Props
|
||||
|
||||
[INFER] No `*Props` interface/type found by static extraction. See source file.
|
||||
|
||||
## Composes
|
||||
|
||||
- **Primitives:** [badge](./Badge.md), [button](./Button.md), [checkbox](./Checkbox.md), [input](./Input.md), [popover](./Popover.md)
|
||||
|
||||
## Used by
|
||||
|
||||
- **Pages:** `Inbox`
|
||||
25
doc/design-system/components/IssueLinkQuicklook.md
Normal file
25
doc/design-system/components/IssueLinkQuicklook.md
Normal file
@@ -0,0 +1,25 @@
|
||||
# IssueLinkQuicklook
|
||||
|
||||
`ui/src/components/IssueLinkQuicklook.tsx`
|
||||
|
||||
[INFER] Composite component. No file-level docstring.
|
||||
|
||||
## Quick facts
|
||||
|
||||
- **Category:** `composite`
|
||||
- **Usage:** 3 imports (0 pages, 2 components)
|
||||
- **Storybook:** yes — see ui/storybook/stories/ (thematic stories, not per-component)
|
||||
- **File size:** 182 lines
|
||||
- **Sibling exports:** IssueQuicklookCard
|
||||
|
||||
## Props
|
||||
|
||||
[INFER] No `*Props` interface/type found by static extraction. See source file.
|
||||
|
||||
## Composes
|
||||
|
||||
- **Primitives:** [popover](./Popover.md)
|
||||
- **Composites:** [StatusIcon](./StatusIcon.md)
|
||||
|
||||
## Used by
|
||||
|
||||
20
doc/design-system/components/IssueReferencePill.md
Normal file
20
doc/design-system/components/IssueReferencePill.md
Normal file
@@ -0,0 +1,20 @@
|
||||
# IssueReferencePill
|
||||
|
||||
`ui/src/components/IssueReferencePill.tsx`
|
||||
|
||||
[INFER] Standalone component. No file-level docstring.
|
||||
|
||||
## Quick facts
|
||||
|
||||
- **Category:** `standalone`
|
||||
- **Usage:** 4 imports (1 pages, 3 components)
|
||||
- **Storybook:** no
|
||||
- **File size:** 56 lines
|
||||
|
||||
## Props
|
||||
|
||||
[INFER] No `*Props` interface/type found by static extraction. See source file.
|
||||
|
||||
## Used by
|
||||
|
||||
- **Pages:** `DesignGuide`
|
||||
38
doc/design-system/components/IssueRow.md
Normal file
38
doc/design-system/components/IssueRow.md
Normal file
@@ -0,0 +1,38 @@
|
||||
# IssueRow
|
||||
|
||||
`ui/src/components/IssueRow.tsx`
|
||||
|
||||
[INFER] Standalone component. No file-level docstring.
|
||||
|
||||
## Quick facts
|
||||
|
||||
- **Category:** `standalone`
|
||||
- **Usage:** 3 imports (1 pages, 2 components)
|
||||
- **Storybook:** yes — see ui/storybook/stories/ (thematic stories, not per-component)
|
||||
- **File size:** 169 lines
|
||||
|
||||
## Props
|
||||
|
||||
### `IssueRowProps`
|
||||
|
||||
```ts
|
||||
issue: Issue;
|
||||
issueLinkState?: unknown;
|
||||
selected?: boolean;
|
||||
mobileLeading?: ReactNode;
|
||||
desktopMetaLeading?: ReactNode;
|
||||
desktopLeadingSpacer?: boolean;
|
||||
mobileMeta?: ReactNode;
|
||||
desktopTrailing?: ReactNode;
|
||||
trailingMeta?: ReactNode;
|
||||
titleSuffix?: ReactNode;
|
||||
unreadState?: UnreadState | null;
|
||||
onMarkRead?: () => void;
|
||||
onArchive?: () => void;
|
||||
archiveDisabled?: boolean;
|
||||
className?: string;
|
||||
```
|
||||
|
||||
## Used by
|
||||
|
||||
- **Pages:** `Inbox`
|
||||
41
doc/design-system/components/IssueWorkspaceCard.md
Normal file
41
doc/design-system/components/IssueWorkspaceCard.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# IssueWorkspaceCard
|
||||
|
||||
`ui/src/components/IssueWorkspaceCard.tsx`
|
||||
|
||||
[INFER] Composite component. No file-level docstring.
|
||||
|
||||
## Quick facts
|
||||
|
||||
- **Category:** `composite`
|
||||
- **Usage:** 3 imports (1 pages, 2 components)
|
||||
- **Storybook:** yes — see ui/storybook/stories/ (thematic stories, not per-component)
|
||||
- **File size:** 522 lines
|
||||
|
||||
## Props
|
||||
|
||||
### `IssueWorkspaceCardProps`
|
||||
|
||||
```ts
|
||||
issue: Omit<
|
||||
Pick<
|
||||
Issue,
|
||||
| "companyId"
|
||||
| "projectId"
|
||||
| "projectWorkspaceId"
|
||||
| "executionWorkspaceId"
|
||||
| "executionWorkspacePreference"
|
||||
| "executionWorkspaceSettings"
|
||||
>,
|
||||
"companyId"
|
||||
> & {
|
||||
companyId: string | null;
|
||||
currentExecutionWorkspace?: ExecutionWorkspace | null;
|
||||
```
|
||||
|
||||
## Composes
|
||||
|
||||
- **Primitives:** [button](./Button.md), [skeleton](./Skeleton.md)
|
||||
|
||||
## Used by
|
||||
|
||||
- **Pages:** `IssueDetail`
|
||||
45
doc/design-system/components/IssuesList.md
Normal file
45
doc/design-system/components/IssuesList.md
Normal file
@@ -0,0 +1,45 @@
|
||||
# IssuesList
|
||||
|
||||
`ui/src/components/IssuesList.tsx`
|
||||
|
||||
[INFER] Composite component. No file-level docstring.
|
||||
|
||||
## Quick facts
|
||||
|
||||
- **Category:** `composite`
|
||||
- **Usage:** 6 imports (5 pages, 1 components)
|
||||
- **Storybook:** yes — see ui/storybook/stories/ (thematic stories, not per-component)
|
||||
- **File size:** 1170 lines
|
||||
|
||||
## Props
|
||||
|
||||
### `IssuesListProps`
|
||||
|
||||
```ts
|
||||
issues: Issue[];
|
||||
isLoading?: boolean;
|
||||
error?: Error | null;
|
||||
agents?: Agent[];
|
||||
projects?: ProjectOption[];
|
||||
liveIssueIds?: Set<string>;
|
||||
projectId?: string;
|
||||
viewStateKey: string;
|
||||
issueLinkState?: unknown;
|
||||
initialAssignees?: string[];
|
||||
initialWorkspaces?: string[];
|
||||
initialSearch?: string;
|
||||
searchFilters?: Omit<IssueListRequestFilters, "q" | "projectId" | "limit" | "includeRoutineExecutions">;
|
||||
baseCreateIssueDefaults?: Record<string, unknown>;
|
||||
createIssueLabel?: string;
|
||||
enableRoutineVisibilityFilter?: boolean;
|
||||
onSearchChange?: (search: string) => void;
|
||||
onUpdateIssue: (id: string, data: Record<string, unknown>) => void;
|
||||
```
|
||||
|
||||
## Composes
|
||||
|
||||
- **Primitives:** [button](./Button.md), [collapsible](./Collapsible.md), [input](./Input.md), [popover](./Popover.md)
|
||||
|
||||
## Used by
|
||||
|
||||
- **Pages:** `ExecutionWorkspaceDetail`, `IssueDetail`, `Issues`, `ProjectDetail`, `Routines`
|
||||
21
doc/design-system/components/Label.md
Normal file
21
doc/design-system/components/Label.md
Normal file
@@ -0,0 +1,21 @@
|
||||
# Label
|
||||
|
||||
`ui/src/components/ui/label.tsx`
|
||||
|
||||
[INFER] Primitive component. No file-level docstring.
|
||||
|
||||
## Quick facts
|
||||
|
||||
- **Category:** `primitive`
|
||||
- **Usage:** 8 imports (5 pages, 3 components)
|
||||
- **Storybook:** yes — see ui/storybook/stories/ (thematic stories, not per-component)
|
||||
- **File size:** 23 lines
|
||||
|
||||
## Props
|
||||
|
||||
[INFER] No `*Props` interface/type found by static extraction. See source file.
|
||||
|
||||
## Used by
|
||||
|
||||
- **Pages:** `AdapterManager`, `DesignGuide`, `PluginManager`, `ProfileSettings`, `RoutineDetail`
|
||||
- **Components:** `JsonSchemaForm`, `RoutineRunVariablesDialog`, `RoutineVariablesEditor`
|
||||
32
doc/design-system/components/MarkdownBody.md
Normal file
32
doc/design-system/components/MarkdownBody.md
Normal file
@@ -0,0 +1,32 @@
|
||||
# MarkdownBody
|
||||
|
||||
`ui/src/components/MarkdownBody.tsx`
|
||||
|
||||
[INFER] Standalone component. No file-level docstring.
|
||||
|
||||
## Quick facts
|
||||
|
||||
- **Category:** `standalone`
|
||||
- **Usage:** 11 imports (5 pages, 6 components)
|
||||
- **Storybook:** yes — see ui/storybook/stories/ (thematic stories, not per-component)
|
||||
- **File size:** 326 lines
|
||||
|
||||
## Props
|
||||
|
||||
### `MarkdownBodyProps`
|
||||
|
||||
```ts
|
||||
children: string;
|
||||
className?: string;
|
||||
style?: React.CSSProperties;
|
||||
softBreaks?: boolean;
|
||||
linkIssueReferences?: boolean;
|
||||
/** Optional resolver for relative image paths (e.g. within export packages) */
|
||||
resolveImageSrc?: (src: string) => string | null;
|
||||
/** Called when a user clicks an inline image */
|
||||
onImageClick?: (src: string) => void;
|
||||
```
|
||||
|
||||
## Used by
|
||||
|
||||
- **Pages:** `AgentDetail`, `ApprovalDetail`, `CompanyExport`, `CompanyImport`, `CompanySkills`
|
||||
39
doc/design-system/components/MarkdownEditor.md
Normal file
39
doc/design-system/components/MarkdownEditor.md
Normal file
@@ -0,0 +1,39 @@
|
||||
# MarkdownEditor
|
||||
|
||||
`ui/src/components/MarkdownEditor.tsx`
|
||||
|
||||
[INFER] Standalone component. No file-level docstring.
|
||||
|
||||
## Quick facts
|
||||
|
||||
- **Category:** `standalone`
|
||||
- **Usage:** 16 imports (5 pages, 9 components)
|
||||
- **Storybook:** yes — see ui/storybook/stories/ (thematic stories, not per-component)
|
||||
- **File size:** 1204 lines
|
||||
|
||||
## Props
|
||||
|
||||
### `MarkdownEditorProps`
|
||||
|
||||
```ts
|
||||
value: string;
|
||||
onChange: (value: string) => void;
|
||||
placeholder?: string;
|
||||
className?: string;
|
||||
contentClassName?: string;
|
||||
onBlur?: () => void;
|
||||
imageUploadHandler?: (file: File) => Promise<string>;
|
||||
/** Called when a non-image file is dropped onto the editor (e.g. .zip). */
|
||||
onDropFile?: (file: File) => Promise<void>;
|
||||
bordered?: boolean;
|
||||
/** List of mentionable entities. Enables @-mention autocomplete. */
|
||||
mentions?: MentionOption[];
|
||||
/** Called on Cmd/Ctrl+Enter */
|
||||
onSubmit?: () => void;
|
||||
/** Render the rich editor without allowing edits. */
|
||||
readOnly?: boolean;
|
||||
```
|
||||
|
||||
## Used by
|
||||
|
||||
- **Pages:** `AgentDetail`, `CompanySkills`, `IssueDetail`, `RoutineDetail`, `Routines`
|
||||
21
doc/design-system/components/PackageFileTree.md
Normal file
21
doc/design-system/components/PackageFileTree.md
Normal file
@@ -0,0 +1,21 @@
|
||||
# PackageFileTree
|
||||
|
||||
`ui/src/components/PackageFileTree.tsx`
|
||||
|
||||
[INFER] Standalone component. No file-level docstring.
|
||||
|
||||
## Quick facts
|
||||
|
||||
- **Category:** `standalone`
|
||||
- **Usage:** 3 imports (3 pages, 0 components)
|
||||
- **Storybook:** yes — see ui/storybook/stories/ (thematic stories, not per-component)
|
||||
- **File size:** 327 lines
|
||||
- **Sibling exports:** FRONTMATTER_FIELD_LABELS
|
||||
|
||||
## Props
|
||||
|
||||
[INFER] No `*Props` interface/type found by static extraction. See source file.
|
||||
|
||||
## Used by
|
||||
|
||||
- **Pages:** `AgentDetail`, `CompanyExport`, `CompanyImport`
|
||||
36
doc/design-system/components/PageSkeleton.md
Normal file
36
doc/design-system/components/PageSkeleton.md
Normal file
@@ -0,0 +1,36 @@
|
||||
# PageSkeleton
|
||||
|
||||
`ui/src/components/PageSkeleton.tsx`
|
||||
|
||||
[INFER] Composite component. No file-level docstring.
|
||||
|
||||
## Quick facts
|
||||
|
||||
- **Category:** `composite`
|
||||
- **Usage:** 23 imports (22 pages, 1 components)
|
||||
- **Storybook:** yes — see ui/storybook/stories/ (thematic stories, not per-component)
|
||||
- **File size:** 181 lines
|
||||
|
||||
## Props
|
||||
|
||||
### `PageSkeletonProps`
|
||||
|
||||
```ts
|
||||
variant?:
|
||||
| "list"
|
||||
| "issues-list"
|
||||
| "detail"
|
||||
| "dashboard"
|
||||
| "approvals"
|
||||
| "costs"
|
||||
| "inbox"
|
||||
| "org-chart";
|
||||
```
|
||||
|
||||
## Composes
|
||||
|
||||
- **Primitives:** [skeleton](./Skeleton.md)
|
||||
|
||||
## Used by
|
||||
|
||||
- **Pages:** `Activity`, `AgentDetail`, `Agents`, `ApprovalDetail`, `Approvals` … (+17 more)
|
||||
32
doc/design-system/components/PageTabBar.md
Normal file
32
doc/design-system/components/PageTabBar.md
Normal file
@@ -0,0 +1,32 @@
|
||||
# PageTabBar
|
||||
|
||||
`ui/src/components/PageTabBar.tsx`
|
||||
|
||||
[INFER] Composite component. No file-level docstring.
|
||||
|
||||
## Quick facts
|
||||
|
||||
- **Category:** `composite`
|
||||
- **Usage:** 10 imports (9 pages, 1 components)
|
||||
- **Storybook:** yes — see ui/storybook/stories/ (thematic stories, not per-component)
|
||||
- **File size:** 46 lines
|
||||
|
||||
## Props
|
||||
|
||||
### `PageTabBarProps`
|
||||
|
||||
```ts
|
||||
items: PageTabItem[];
|
||||
value?: string;
|
||||
onValueChange?: (value: string) => void;
|
||||
align?: "center" | "start";
|
||||
```
|
||||
|
||||
## Composes
|
||||
|
||||
- **Primitives:** [tabs](./Tabs.md)
|
||||
|
||||
## Used by
|
||||
|
||||
- **Pages:** `AgentDetail`, `Agents`, `Approvals`, `Costs`, `ExecutionWorkspaceDetail` … (+4 more)
|
||||
- **Components:** `CompanySettingsNav`
|
||||
30
doc/design-system/components/PathInstructionsModal.md
Normal file
30
doc/design-system/components/PathInstructionsModal.md
Normal file
@@ -0,0 +1,30 @@
|
||||
# PathInstructionsModal
|
||||
|
||||
`ui/src/components/PathInstructionsModal.tsx`
|
||||
|
||||
[INFER] Composite component. No file-level docstring.
|
||||
|
||||
## Quick facts
|
||||
|
||||
- **Category:** `composite`
|
||||
- **Usage:** 12 imports (2 pages, 3 components)
|
||||
- **Storybook:** yes — see ui/storybook/stories/ (thematic stories, not per-component)
|
||||
- **File size:** 144 lines
|
||||
- **Sibling exports:** ChoosePathButton
|
||||
|
||||
## Props
|
||||
|
||||
### `PathInstructionsModalProps`
|
||||
|
||||
```ts
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
```
|
||||
|
||||
## Composes
|
||||
|
||||
- **Primitives:** [dialog](./Dialog.md)
|
||||
|
||||
## Used by
|
||||
|
||||
- **Pages:** `AdapterManager`, `ProjectWorkspaceDetail`
|
||||
22
doc/design-system/components/Popover.md
Normal file
22
doc/design-system/components/Popover.md
Normal file
@@ -0,0 +1,22 @@
|
||||
# Popover
|
||||
|
||||
`ui/src/components/ui/popover.tsx`
|
||||
|
||||
[INFER] Primitive component. No file-level docstring.
|
||||
|
||||
## Quick facts
|
||||
|
||||
- **Category:** `primitive`
|
||||
- **Usage:** 27 imports (6 pages, 20 components)
|
||||
- **Storybook:** yes — see ui/storybook/stories/ (thematic stories, not per-component)
|
||||
- **File size:** 89 lines
|
||||
- **Sibling exports:** PopoverAnchor, PopoverContent, PopoverDescription, PopoverHeader, PopoverTitle, PopoverTrigger
|
||||
|
||||
## Props
|
||||
|
||||
[INFER] No `*Props` interface/type found by static extraction. See source file.
|
||||
|
||||
## Used by
|
||||
|
||||
- **Pages:** `AgentDetail`, `DesignGuide`, `Inbox`, `IssueDetail`, `NewAgent` … (+1 more)
|
||||
- **Components:** `AgentConfigForm`, `AgentIconPicker`, `ExecutionParticipantPicker`, `GoalProperties`, `InlineEntitySelector` … (+15 more)
|
||||
31
doc/design-system/components/PriorityIcon.md
Normal file
31
doc/design-system/components/PriorityIcon.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# PriorityIcon
|
||||
|
||||
`ui/src/components/PriorityIcon.tsx`
|
||||
|
||||
[INFER] Composite component. No file-level docstring.
|
||||
|
||||
## Quick facts
|
||||
|
||||
- **Category:** `composite`
|
||||
- **Usage:** 5 imports (2 pages, 3 components)
|
||||
- **Storybook:** yes — see ui/storybook/stories/ (thematic stories, not per-component)
|
||||
- **File size:** 78 lines
|
||||
|
||||
## Props
|
||||
|
||||
### `PriorityIconProps`
|
||||
|
||||
```ts
|
||||
priority: string;
|
||||
onChange?: (priority: string) => void;
|
||||
className?: string;
|
||||
showLabel?: boolean;
|
||||
```
|
||||
|
||||
## Composes
|
||||
|
||||
- **Primitives:** [button](./Button.md), [popover](./Popover.md)
|
||||
|
||||
## Used by
|
||||
|
||||
- **Pages:** `DesignGuide`, `IssueDetail`
|
||||
24
doc/design-system/components/RoutineRunVariablesDialog.md
Normal file
24
doc/design-system/components/RoutineRunVariablesDialog.md
Normal file
@@ -0,0 +1,24 @@
|
||||
# RoutineRunVariablesDialog
|
||||
|
||||
`ui/src/components/RoutineRunVariablesDialog.tsx`
|
||||
|
||||
[INFER] Composite component. No file-level docstring.
|
||||
|
||||
## Quick facts
|
||||
|
||||
- **Category:** `composite`
|
||||
- **Usage:** 3 imports (2 pages, 1 components)
|
||||
- **Storybook:** yes — see ui/storybook/stories/ (thematic stories, not per-component)
|
||||
- **File size:** 519 lines
|
||||
|
||||
## Props
|
||||
|
||||
[INFER] No `*Props` interface/type found by static extraction. See source file.
|
||||
|
||||
## Composes
|
||||
|
||||
- **Primitives:** [button](./Button.md), [dialog](./Dialog.md), [input](./Input.md), [label](./Label.md), [select](./Select.md), [textarea](./Textarea.md)
|
||||
|
||||
## Used by
|
||||
|
||||
- **Pages:** `RoutineDetail`, `Routines`
|
||||
32
doc/design-system/components/RunTranscriptView.md
Normal file
32
doc/design-system/components/RunTranscriptView.md
Normal file
@@ -0,0 +1,32 @@
|
||||
# RunTranscriptView
|
||||
|
||||
`ui/src/components/transcript/RunTranscriptView.tsx`
|
||||
|
||||
[INFER] Standalone component. No file-level docstring.
|
||||
|
||||
## Quick facts
|
||||
|
||||
- **Category:** `standalone`
|
||||
- **Usage:** 3 imports (2 pages, 1 components)
|
||||
- **Storybook:** no
|
||||
- **File size:** 1446 lines
|
||||
|
||||
## Props
|
||||
|
||||
### `RunTranscriptViewProps`
|
||||
|
||||
```ts
|
||||
entries: TranscriptEntry[];
|
||||
mode?: TranscriptMode;
|
||||
density?: TranscriptDensity;
|
||||
limit?: number;
|
||||
streaming?: boolean;
|
||||
collapseStdout?: boolean;
|
||||
emptyMessage?: string;
|
||||
className?: string;
|
||||
thinkingClassName?: string;
|
||||
```
|
||||
|
||||
## Used by
|
||||
|
||||
- **Pages:** `AgentDetail`, `RunTranscriptUxLab`
|
||||
22
doc/design-system/components/ScrollArea.md
Normal file
22
doc/design-system/components/ScrollArea.md
Normal file
@@ -0,0 +1,22 @@
|
||||
# ScrollArea
|
||||
|
||||
`ui/src/components/ui/scroll-area.tsx`
|
||||
|
||||
[INFER] Primitive component. No file-level docstring.
|
||||
|
||||
## Quick facts
|
||||
|
||||
- **Category:** `primitive`
|
||||
- **Usage:** 3 imports (2 pages, 1 components)
|
||||
- **Storybook:** no
|
||||
- **File size:** 57 lines
|
||||
- **Sibling exports:** ScrollBar
|
||||
|
||||
## Props
|
||||
|
||||
[INFER] No `*Props` interface/type found by static extraction. See source file.
|
||||
|
||||
## Used by
|
||||
|
||||
- **Pages:** `DesignGuide`, `IssueDetail`
|
||||
- **Components:** `PropertiesPanel`
|
||||
22
doc/design-system/components/Select.md
Normal file
22
doc/design-system/components/Select.md
Normal file
@@ -0,0 +1,22 @@
|
||||
# Select
|
||||
|
||||
`ui/src/components/ui/select.tsx`
|
||||
|
||||
[INFER] Primitive component. No file-level docstring.
|
||||
|
||||
## Quick facts
|
||||
|
||||
- **Category:** `primitive`
|
||||
- **Usage:** 10 imports (5 pages, 5 components)
|
||||
- **Storybook:** yes — see ui/storybook/stories/ (thematic stories, not per-component)
|
||||
- **File size:** 189 lines
|
||||
- **Sibling exports:** SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton
|
||||
|
||||
## Props
|
||||
|
||||
[INFER] No `*Props` interface/type found by static extraction. See source file.
|
||||
|
||||
## Used by
|
||||
|
||||
- **Pages:** `Activity`, `DesignGuide`, `Inbox`, `RoutineDetail`, `Routines`
|
||||
- **Components:** `DocumentDiffModal`, `JsonSchemaForm`, `RoutineRunVariablesDialog`, `RoutineVariablesEditor`, `ScheduleEditor`
|
||||
21
doc/design-system/components/Separator.md
Normal file
21
doc/design-system/components/Separator.md
Normal file
@@ -0,0 +1,21 @@
|
||||
# Separator
|
||||
|
||||
`ui/src/components/ui/separator.tsx`
|
||||
|
||||
[INFER] Primitive component. No file-level docstring.
|
||||
|
||||
## Quick facts
|
||||
|
||||
- **Category:** `primitive`
|
||||
- **Usage:** 11 imports (7 pages, 4 components)
|
||||
- **Storybook:** yes — see ui/storybook/stories/ (thematic stories, not per-component)
|
||||
- **File size:** 29 lines
|
||||
|
||||
## Props
|
||||
|
||||
[INFER] No `*Props` interface/type found by static extraction. See source file.
|
||||
|
||||
## Used by
|
||||
|
||||
- **Pages:** `DesignGuide`, `ExecutionWorkspaceDetail`, `Inbox`, `IssueDetail`, `PluginSettings` … (+2 more)
|
||||
- **Components:** `AgentProperties`, `GoalProperties`, `IssueProperties`, `ProjectProperties`
|
||||
33
doc/design-system/components/SidebarNavItem.md
Normal file
33
doc/design-system/components/SidebarNavItem.md
Normal file
@@ -0,0 +1,33 @@
|
||||
# SidebarNavItem
|
||||
|
||||
`ui/src/components/SidebarNavItem.tsx`
|
||||
|
||||
[INFER] Standalone component. No file-level docstring.
|
||||
|
||||
## Quick facts
|
||||
|
||||
- **Category:** `standalone`
|
||||
- **Usage:** 3 imports (0 pages, 3 components)
|
||||
- **Storybook:** no
|
||||
- **File size:** 95 lines
|
||||
|
||||
## Props
|
||||
|
||||
### `SidebarNavItemProps`
|
||||
|
||||
```ts
|
||||
to: string;
|
||||
label: string;
|
||||
icon: LucideIcon;
|
||||
end?: boolean;
|
||||
className?: string;
|
||||
badge?: number;
|
||||
badgeTone?: "default" | "danger";
|
||||
textBadge?: string;
|
||||
textBadgeTone?: "default" | "amber";
|
||||
alert?: boolean;
|
||||
liveCount?: number;
|
||||
```
|
||||
|
||||
## Used by
|
||||
|
||||
21
doc/design-system/components/Skeleton.md
Normal file
21
doc/design-system/components/Skeleton.md
Normal file
@@ -0,0 +1,21 @@
|
||||
# Skeleton
|
||||
|
||||
`ui/src/components/ui/skeleton.tsx`
|
||||
|
||||
[INFER] Primitive component. No file-level docstring.
|
||||
|
||||
## Quick facts
|
||||
|
||||
- **Category:** `primitive`
|
||||
- **Usage:** 6 imports (3 pages, 3 components)
|
||||
- **Storybook:** no
|
||||
- **File size:** 14 lines
|
||||
|
||||
## Props
|
||||
|
||||
[INFER] No `*Props` interface/type found by static extraction. See source file.
|
||||
|
||||
## Used by
|
||||
|
||||
- **Pages:** `AgentDetail`, `DesignGuide`, `IssueDetail`
|
||||
- **Components:** `IssueWorkspaceCard`, `PageSkeleton`, `ProviderQuotaCard`
|
||||
20
doc/design-system/components/StatusBadge.md
Normal file
20
doc/design-system/components/StatusBadge.md
Normal file
@@ -0,0 +1,20 @@
|
||||
# StatusBadge
|
||||
|
||||
`ui/src/components/StatusBadge.tsx`
|
||||
|
||||
[INFER] Standalone component. No file-level docstring.
|
||||
|
||||
## Quick facts
|
||||
|
||||
- **Category:** `standalone`
|
||||
- **Usage:** 19 imports (12 pages, 7 components)
|
||||
- **Storybook:** yes — see ui/storybook/stories/ (thematic stories, not per-component)
|
||||
- **File size:** 16 lines
|
||||
|
||||
## Props
|
||||
|
||||
[INFER] No `*Props` interface/type found by static extraction. See source file.
|
||||
|
||||
## Used by
|
||||
|
||||
- **Pages:** `AgentDetail`, `Agents`, `ApprovalDetail`, `Costs`, `DesignGuide` … (+7 more)
|
||||
32
doc/design-system/components/StatusIcon.md
Normal file
32
doc/design-system/components/StatusIcon.md
Normal file
@@ -0,0 +1,32 @@
|
||||
# StatusIcon
|
||||
|
||||
`ui/src/components/StatusIcon.tsx`
|
||||
|
||||
[INFER] Composite component. No file-level docstring.
|
||||
|
||||
## Quick facts
|
||||
|
||||
- **Category:** `composite`
|
||||
- **Usage:** 14 imports (5 pages, 9 components)
|
||||
- **Storybook:** no
|
||||
- **File size:** 72 lines
|
||||
|
||||
## Props
|
||||
|
||||
### `StatusIconProps`
|
||||
|
||||
```ts
|
||||
status: string;
|
||||
onChange?: (status: string) => void;
|
||||
className?: string;
|
||||
showLabel?: boolean;
|
||||
```
|
||||
|
||||
## Composes
|
||||
|
||||
- **Primitives:** [button](./Button.md), [popover](./Popover.md)
|
||||
|
||||
## Used by
|
||||
|
||||
- **Pages:** `Dashboard`, `DesignGuide`, `Inbox`, `IssueDetail`, `MyIssues`
|
||||
- **Components:** `IssueLinkQuicklook` … (+8 more)
|
||||
27
doc/design-system/components/Tabs.md
Normal file
27
doc/design-system/components/Tabs.md
Normal file
@@ -0,0 +1,27 @@
|
||||
# Tabs
|
||||
|
||||
`ui/src/components/ui/tabs.tsx`
|
||||
|
||||
[INFER] Primitive component. No file-level docstring.
|
||||
|
||||
## Quick facts
|
||||
|
||||
- **Category:** `primitive`
|
||||
- **Usage:** 15 imports (13 pages, 2 components)
|
||||
- **Storybook:** yes — see ui/storybook/stories/ (thematic stories, not per-component)
|
||||
- **File size:** 90 lines
|
||||
- **Sibling exports:** TabsContent, TabsList, TabsTrigger
|
||||
|
||||
## Props
|
||||
|
||||
[INFER] No `*Props` interface/type found by static extraction. See source file.
|
||||
|
||||
## Variants (CVA)
|
||||
|
||||
- **variant**: `default`, `line`
|
||||
|
||||
|
||||
## Used by
|
||||
|
||||
- **Pages:** `AgentDetail`, `Agents`, `Approvals`, `Costs`, `DesignGuide` … (+8 more)
|
||||
- **Components:** `CompanySettingsNav`, `PageTabBar`
|
||||
21
doc/design-system/components/Textarea.md
Normal file
21
doc/design-system/components/Textarea.md
Normal file
@@ -0,0 +1,21 @@
|
||||
# Textarea
|
||||
|
||||
`ui/src/components/ui/textarea.tsx`
|
||||
|
||||
[INFER] Primitive component. No file-level docstring.
|
||||
|
||||
## Quick facts
|
||||
|
||||
- **Category:** `primitive`
|
||||
- **Usage:** 9 imports (4 pages, 5 components)
|
||||
- **Storybook:** yes — see ui/storybook/stories/ (thematic stories, not per-component)
|
||||
- **File size:** 19 lines
|
||||
|
||||
## Props
|
||||
|
||||
[INFER] No `*Props` interface/type found by static extraction. See source file.
|
||||
|
||||
## Used by
|
||||
|
||||
- **Pages:** `ApprovalDetail`, `CompanySkills`, `DesignGuide`, `ExecutionWorkspaceDetail`
|
||||
- **Components:** `IssueChatThread`, `JsonSchemaForm`, `OutputFeedbackButtons`, `RoutineRunVariablesDialog`, `RoutineVariablesEditor`
|
||||
21
doc/design-system/components/ToggleSwitch.md
Normal file
21
doc/design-system/components/ToggleSwitch.md
Normal file
@@ -0,0 +1,21 @@
|
||||
# ToggleSwitch
|
||||
|
||||
`ui/src/components/ui/toggle-switch.tsx`
|
||||
|
||||
[INFER] Primitive component. No file-level docstring.
|
||||
|
||||
## Quick facts
|
||||
|
||||
- **Category:** `primitive`
|
||||
- **Usage:** 8 imports (5 pages, 3 components)
|
||||
- **Storybook:** yes — see ui/storybook/stories/ (thematic stories, not per-component)
|
||||
- **File size:** 60 lines
|
||||
|
||||
## Props
|
||||
|
||||
[INFER] No `*Props` interface/type found by static extraction. See source file.
|
||||
|
||||
## Used by
|
||||
|
||||
- **Pages:** `AgentDetail`, `InstanceExperimentalSettings`, `InstanceGeneralSettings`, `RoutineDetail`, `Routines`
|
||||
- **Components:** `NewIssueDialog`, `ProjectProperties`, `agent-config-primitives`
|
||||
22
doc/design-system/components/Tooltip.md
Normal file
22
doc/design-system/components/Tooltip.md
Normal file
@@ -0,0 +1,22 @@
|
||||
# Tooltip
|
||||
|
||||
`ui/src/components/ui/tooltip.tsx`
|
||||
|
||||
[INFER] Primitive component. No file-level docstring.
|
||||
|
||||
## Quick facts
|
||||
|
||||
- **Category:** `primitive`
|
||||
- **Usage:** 11 imports (3 pages, 7 components)
|
||||
- **Storybook:** yes — see ui/storybook/stories/ (thematic stories, not per-component)
|
||||
- **File size:** 58 lines
|
||||
- **Sibling exports:** TooltipContent, TooltipProvider, TooltipTrigger
|
||||
|
||||
## Props
|
||||
|
||||
[INFER] No `*Props` interface/type found by static extraction. See source file.
|
||||
|
||||
## Used by
|
||||
|
||||
- **Pages:** `AgentDetail`, `CompanySkills`, `DesignGuide`
|
||||
- **Components:** `CompanyRail`, `IssueChatThread`, `IssueColumns`, `NewProjectDialog`, `ProjectProperties` … (+2 more)
|
||||
38
doc/design-system/components/WorkspaceRuntimeControls.md
Normal file
38
doc/design-system/components/WorkspaceRuntimeControls.md
Normal file
@@ -0,0 +1,38 @@
|
||||
# WorkspaceRuntimeControls
|
||||
|
||||
`ui/src/components/WorkspaceRuntimeControls.tsx`
|
||||
|
||||
[INFER] Composite component. No file-level docstring.
|
||||
|
||||
## Quick facts
|
||||
|
||||
- **Category:** `composite`
|
||||
- **Usage:** 3 imports (2 pages, 1 components)
|
||||
- **Storybook:** yes — see ui/storybook/stories/ (thematic stories, not per-component)
|
||||
- **File size:** 454 lines
|
||||
|
||||
## Props
|
||||
|
||||
### `WorkspaceRuntimeControlsProps`
|
||||
|
||||
```ts
|
||||
sections: WorkspaceRuntimeControlSections;
|
||||
items?: never;
|
||||
isPending?: boolean;
|
||||
pendingRequest?: WorkspaceRuntimeControlRequest | null;
|
||||
serviceEmptyMessage?: string;
|
||||
jobEmptyMessage?: string;
|
||||
emptyMessage?: never;
|
||||
disabledHint?: string | null;
|
||||
onAction: (request: WorkspaceRuntimeControlRequest) => void;
|
||||
className?: string;
|
||||
square?: boolean;
|
||||
```
|
||||
|
||||
## Composes
|
||||
|
||||
- **Primitives:** [button](./Button.md)
|
||||
|
||||
## Used by
|
||||
|
||||
- **Pages:** `ExecutionWorkspaceDetail`, `ProjectWorkspaceDetail`
|
||||
25
doc/design-system/components/agent-config-primitives.md
Normal file
25
doc/design-system/components/agent-config-primitives.md
Normal file
@@ -0,0 +1,25 @@
|
||||
# agent-config-primitives
|
||||
|
||||
`ui/src/components/agent-config-primitives.tsx`
|
||||
|
||||
[INFER] Composite component. No file-level docstring.
|
||||
|
||||
## Quick facts
|
||||
|
||||
- **Category:** `composite`
|
||||
- **Usage:** 19 imports (4 pages, 3 components)
|
||||
- **Storybook:** yes — see ui/storybook/stories/ (thematic stories, not per-component)
|
||||
- **File size:** 464 lines
|
||||
- **Sibling exports:** AutoExpandTextarea, ChoosePathButton, CollapsibleSection, DraftInput, DraftNumberInput, DraftTextarea
|
||||
|
||||
## Props
|
||||
|
||||
[INFER] No `*Props` interface/type found by static extraction. See source file.
|
||||
|
||||
## Composes
|
||||
|
||||
- **Primitives:** [button](./Button.md), [dialog](./Dialog.md), `toggle-switch`, [tooltip](./Tooltip.md)
|
||||
|
||||
## Used by
|
||||
|
||||
- **Pages:** `AgentDetail`, `CompanyImport`, `CompanySettings`, `NewAgent`
|
||||
393
doc/design-system/components/components-review.md
Normal file
393
doc/design-system/components/components-review.md
Normal file
@@ -0,0 +1,393 @@
|
||||
# Components Review
|
||||
|
||||
- **Generated:** 2026-04-21
|
||||
- **Repo SHA:** a26e1288b627e82c554445732c7d844648e6b5e1
|
||||
- **Inventory:** [index.md](./index.md)
|
||||
- **Token drift feeding components:** [../tokens/tokens-review.md](../tokens/tokens-review.md)
|
||||
- **Pattern docs drawn from this review:** [../patterns/index.md](../patterns/index.md)
|
||||
|
||||
---
|
||||
|
||||
## How to read this document
|
||||
|
||||
This is a **persistent backlog**, not just a snapshot review. It captures opportunities identified during DS extraction. **All items are intentionally deferred** per the 2026-04-21 decision: no component merges, no renames, no file-casing changes were made. Address opportunistically when the relevant code is touched for other reasons, or schedule as standalone follow-up projects.
|
||||
|
||||
Each entry below includes:
|
||||
- A **problem statement** — what was observed and why it might matter.
|
||||
- **Affected files** — so the next developer doesn't have to re-trace.
|
||||
- A **suggested resolution** — or "needs discussion" when the right call is not obvious.
|
||||
|
||||
Entries are ordered by expected value, not by stage. Sections are self-contained — you can pick one up cold without reading the rest.
|
||||
|
||||
---
|
||||
|
||||
## Likely duplicates
|
||||
|
||||
Pairs or families whose names, compositions, or role overlap enough that they may be consolidatable. None of these are automatic merges — each is a judgment call. Items 1–7 and 9 are documented as single patterns in [../patterns/](../patterns/) so the family is visible to future pattern extraction; this section captures the consolidation opportunity.
|
||||
|
||||
### 1. Entity-creation dialog family: `NewAgentDialog` / `NewGoalDialog` / `NewIssueDialog` / `NewProjectDialog`
|
||||
|
||||
Four parallel "create new X" dialogs, each used 1–2 times, each a composite that wraps a form-in-a-dialog. Strong candidate for a single generic `NewEntityDialog` or a `useNewEntityForm` hook plus entity-specific field sets.
|
||||
|
||||
| File | Uses |
|
||||
|---|---|
|
||||
| `ui/src/components/NewAgentDialog.tsx` | 1 |
|
||||
| `ui/src/components/NewGoalDialog.tsx` | 1 |
|
||||
| `ui/src/components/NewIssueDialog.tsx` | 2 |
|
||||
| `ui/src/components/NewProjectDialog.tsx` | 1 |
|
||||
|
||||
**Verify:** open the four files and diff them. If ≥60% body overlap, consolidate. Impact on Stage 4: otherwise a "New-entity dialog" pattern would be proposed with 4 instances, which is better represented as a single component.
|
||||
|
||||
### 2. Properties-panel family: `AgentProperties` / `GoalProperties` / `IssueProperties` / `ProjectProperties` (+ generic `PropertiesPanel`)
|
||||
|
||||
**Problem.** Four entity-specific property-panel components sit next to a generic `PropertiesPanel`. It is unclear whether `PropertiesPanel` **composes** the four (i.e., hosts their body via a context slot — which is what the 29-line `PropertiesPanel.tsx` appears to do) or whether the four **duplicate** the outer-chrome work that `PropertiesPanel` could own. `AgentProperties` is also unused in production (Storybook-only).
|
||||
|
||||
**Affected files.**
|
||||
|
||||
| File | Uses | Notes |
|
||||
|---|---|---|
|
||||
| `ui/src/components/PropertiesPanel.tsx` | 1 | 29-line generic chrome; reads `panelContent` from `usePanel()` context |
|
||||
| `ui/src/components/IssueProperties.tsx` | 2 | 1370-line entity-specific body |
|
||||
| `ui/src/components/GoalProperties.tsx` | 1 | Entity-specific body |
|
||||
| `ui/src/components/ProjectProperties.tsx` | 1 | 1140-line entity-specific body |
|
||||
| `ui/src/components/AgentProperties.tsx` | **0** | Storybook-only — see §Unused components |
|
||||
|
||||
**Suggested resolution: needs discussion.** Per the 2026-04-21 decision, the open question of "composes vs duplicates" is **flagged but not resolved**. Reading the source suggests `PropertiesPanel` is the outer slot and the four bodies are what gets passed into it — so not a literal duplicate. But the four bodies each re-roll section headers, separators, save-state plumbing, and field layout; those might be factor-able into a shared `<PropertiesPanelBody>` helper. Open `IssueProperties.tsx` and `ProjectProperties.tsx` side-by-side before making any call. Documented as-is in [../patterns/entity-properties-panel.md](../patterns/entity-properties-panel.md).
|
||||
|
||||
### 3. Subscription panel pair: `ClaudeSubscriptionPanel` ↔ `CodexSubscriptionPanel`
|
||||
|
||||
**Problem.** Two components with parallel names, parallel props shape (both accept `windows: QuotaWindow[]` + optional `source` / `error`), rendering ordered subscription-quota windows for a single provider. Both used exactly once — always dispatched from `ProviderQuotaCard`. When a third vendor (Gemini? Cursor?) is added, the pattern becomes "a third copy-paste" unless consolidated. Today's cost of keeping them separate is minor because the pair is only 2.
|
||||
|
||||
**Affected files.**
|
||||
|
||||
- `ui/src/components/ClaudeSubscriptionPanel.tsx` (1 use — 140 lines)
|
||||
- `ui/src/components/CodexSubscriptionPanel.tsx` (1 use)
|
||||
- `ui/src/components/ProviderQuotaCard.tsx` — composes both, dispatches by vendor
|
||||
|
||||
**Suggested resolution.** Diff-test the two files. If ≥70% body overlap, collapse to `SubscriptionPanel({ vendor: "claude" | "codex" | … })` with per-vendor window-key config as data. If divergence is higher, keep the pair and let `ProviderQuotaCard` continue to dispatch. Documented as-is in [../patterns/subscription-panel.md](../patterns/subscription-panel.md).
|
||||
|
||||
### 4. Sidebar-menu pair: `SidebarAccountMenu` ↔ `SidebarCompanyMenu`
|
||||
|
||||
**Problem.** Two dropdown components anchored to the sidebar slot — one for account actions (user profile, sign out), one for company actions (switch company, settings). Same visual affordance (dropdown triggered from a sidebar button), different data subject. Each used twice; neither is a hotspot.
|
||||
|
||||
**Affected files.**
|
||||
|
||||
- `ui/src/components/SidebarAccountMenu.tsx` (2 uses)
|
||||
- `ui/src/components/SidebarCompanyMenu.tsx` (2 uses)
|
||||
|
||||
**Suggested resolution.** Read both and check the body overlap. Natural consolidations if similar: `<SidebarMenu kind="account" | "company">` driven by kind, or a more general `<SidebarMenu>` that accepts items via a `children` slot. If the bodies are genuinely different (different menu items, different trigger layout), the pair stays and the shared pattern is the sidebar-slot anchoring — which is already captured by `SidebarNavItem`. Documented as-is in [../patterns/sidebar-chrome.md — §The sidebar-menu pair](../patterns/sidebar-chrome.md#the-sidebar-menu-pair).
|
||||
|
||||
### 5. Finance card family: `BillerSpendCard` / `FinanceBillerCard` / `FinanceKindCard` / `FinanceTimelineCard` / `AccountingModelCard`
|
||||
|
||||
**Problem.** Five cards in the finance/accounting surface, each used 0–1 times, all sharing the shadcn `Card` family as substrate. Two specific flags surfaced per the 2026-04-21 directive:
|
||||
|
||||
1. **`BillerSpendCard` ↔ `FinanceBillerCard` — likely a true duplicate.** Both name "Biller" in their filename. Both render a per-biller financial summary. They consume **different data models** (`CostByBiller` vs `FinanceByBiller`) — either two genuinely different reporting concepts whose names fail to distinguish them, or one superseded the other and the predecessor survived. Line counts differ (145 vs 44), consistent with a "rich" / "slim" pair.
|
||||
2. **`AccountingModelCard` is unused.** Zero imports anywhere in the codebase; only appears in Storybook. Either abandoned or awaiting a page.
|
||||
|
||||
**Affected files.**
|
||||
|
||||
| File | Uses | Data model | Lines |
|
||||
|---|---|---|---|
|
||||
| `ui/src/components/BillerSpendCard.tsx` | 1 | `CostByBiller` + `CostByProviderModel` | 145 |
|
||||
| `ui/src/components/FinanceBillerCard.tsx` | 1 | `FinanceByBiller` | 44 |
|
||||
| `ui/src/components/FinanceKindCard.tsx` | 1 | `FinanceByKind` (inferred) | — |
|
||||
| `ui/src/components/FinanceTimelineCard.tsx` | 1 | timeline rollup | — |
|
||||
| `ui/src/components/AccountingModelCard.tsx` | **0** | — | — |
|
||||
|
||||
**Suggested resolution.**
|
||||
- Diff `BillerSpendCard` vs `FinanceBillerCard` side-by-side. Rename for clarity, merge, or confirm as distinct-but-adjacent.
|
||||
- Decide `AccountingModelCard`'s fate — adopt it into a page, or delete.
|
||||
- The broader family (4 cards) is naturally a "Finance / accounting card" pattern. Documented as-is in [../patterns/finance-card.md](../patterns/finance-card.md).
|
||||
|
||||
### 6. Row family: `ActivityRow` / `EntityRow` / `IssueRow`
|
||||
|
||||
**Problem.** Three row components, one generic and two entity-specific. `EntityRow` is the generic (6 uses), `IssueRow` is issue-specific with an extensive slot interface (3 uses — Inbox + SwipeToArchive), `ActivityRow` is activity-event-specific (2 uses — Activity page). `IssueRow`'s 15-field prop shape (six of them optional `ReactNode` slot props) looks like it could be expressed as `<EntityRow kind="issue" />` plus issue-specific defaults.
|
||||
|
||||
**Affected files.**
|
||||
|
||||
| File | Uses | Notes |
|
||||
|---|---|---|
|
||||
| `ui/src/components/EntityRow.tsx` | 6 | Truly generic — leading / identifier / title / subtitle / trailing slot API |
|
||||
| `ui/src/components/IssueRow.tsx` | 3 | Composes `StatusIcon`; unread-state + archive action + mobile/desktop split |
|
||||
| `ui/src/components/ActivityRow.tsx` | 2 | Composes `Identity`, `IssueReferenceActivitySummary` — activity-event-specific |
|
||||
|
||||
**Suggested resolution.** Compare `IssueRow` against `EntityRow` directly. If the six slot props plus `unreadState` / `onArchive` can be implemented as `EntityRow` extensions (default slots keyed on `issue`), collapse. `ActivityRow`'s activity-verb formatting is genuinely specialized and is likely worth keeping separate even if `IssueRow` consolidates. Also relevant: the main list pages (Issues, Agents, Projects, …) don't use `EntityRow` today — see [../patterns/list-page.md — Open questions](../patterns/list-page.md#open-questions--risks). Documented as-is in [../patterns/entity-row.md](../patterns/entity-row.md).
|
||||
|
||||
### 7. Sidebar triad: `Sidebar` / `InstanceSidebar` / `CompanySettingsSidebar` + `CompanyRail` + `CompanySettingsNav` + `MobileBottomNav`
|
||||
|
||||
**Problem.** Six components with overlapping responsibilities — all are "chrome around the main view." Not literal duplicates because each targets a different surface (main nav vs instance settings vs company settings vs company switcher vs tab nav vs mobile bottom bar), but the three-word vocabulary (Sidebar / Rail / Nav) obscures whether these are variants of one structural pattern or separate components that happen to live near each other. They share navigation primitives (`SidebarNavItem`, `SidebarSection`) but not a unifying wrapper. The dead `sidebar-*` tokens in `ui/src/index.css` were designed for this family and none of these consume them (see [tokens-review.md §3](../tokens/tokens-review.md#3-sidebar--tokens-are-dead)).
|
||||
|
||||
**Affected files.**
|
||||
|
||||
| File | Uses | Role (inferred) |
|
||||
|---|---|---|
|
||||
| `ui/src/components/Sidebar.tsx` | ≥3 | Main app navigation |
|
||||
| `ui/src/components/InstanceSidebar.tsx` | 1 | Instance-settings scope |
|
||||
| `ui/src/components/CompanySettingsSidebar.tsx` | 2 | Company-settings scope |
|
||||
| `ui/src/components/CompanyRail.tsx` | 1 (260 lines, dnd-kit-driven) | Sortable company switcher rail |
|
||||
| `ui/src/components/access/CompanySettingsNav.tsx` | 1 | Settings-page top tab nav |
|
||||
| `ui/src/components/MobileBottomNav.tsx` | ≥3 | Mobile-bottom-tab alternative |
|
||||
|
||||
**Suggested resolution: needs discussion.** Three possible framings:
|
||||
- **(a) They are genuinely different components** (different layouts, different primitives, different affordances) and the naming convergence is coincidental. Closest to how the code reads today.
|
||||
- **(b) They are variants of a `<Sidebar variant="main" | "settings" | "rail" | …>`** and should consolidate under one name. Requires auditing their visual shape.
|
||||
- **(c) They are three distinct patterns** — "sidebar" (persistent rail), "rail" (narrow-strip), "nav" (tab-bar) — and the current spread is correct but the naming convention for picking between them isn't written down anywhere.
|
||||
|
||||
Tied to [§Naming inconsistencies — Sidebar / Rail / Nav](#sidebar--rail--nav). Documented as-is in [../patterns/sidebar-chrome.md](../patterns/sidebar-chrome.md).
|
||||
|
||||
### 8. Status display triad: `StatusIcon` / `StatusBadge` / `PriorityIcon`
|
||||
|
||||
**Problem.** Three components render entity status/priority across the app with the same visual language but different affordances. All three consume `ui/src/lib/status-colors.ts` — a canonical TypeScript catalog mapping status strings to raw Tailwind-palette classes. Two of the three (`StatusIcon`, `PriorityIcon`) type their primary prop as an **untyped string**; `StatusBadge` uses a typed variant. This inconsistency plus the fact that the catalog bypasses the DS token layer makes this a pattern-shape-pending item rather than a straightforward duplicate flag.
|
||||
|
||||
**Affected files.**
|
||||
|
||||
| File | Uses | Prop type | Notes |
|
||||
|---|---|---|---|
|
||||
| `ui/src/components/StatusIcon.tsx` | 14 | `status: string` (untyped) | Circle + popover picker |
|
||||
| `ui/src/components/StatusBadge.tsx` | 19 | `{ status: string }` wrapping `statusBadge[status]` | 15-line pill |
|
||||
| `ui/src/components/PriorityIcon.tsx` | 5 | `priority: string` (untyped) | Arrow/triangle + popover picker |
|
||||
| `ui/src/lib/status-colors.ts` | — | — | Catalog consumed by all three |
|
||||
| `ui/src/components/AgentActionButtons.tsx` | — | — | Consumes `agentStatusDot` from the same catalog |
|
||||
|
||||
**2026-04-21 status.** The token-side of this problem was **partially addressed**: `--signal-success` / `--signal-success-foreground` landed as action-severity tokens paired with `--destructive` (see [tokens-review.md §4](../tokens/tokens-review.md#4-status-colorsts-is-a-canonical-semantic-color-catalog-that-bypasses-the-ds)). `status-colors.ts` itself is **not** touched — tokenizing its entity-state coloring into a `--status-*` family is a deferred future project. So the two problems here are now independent:
|
||||
- **Signal (action severity)** = tokens exist, no consumers yet, opt-in.
|
||||
- **Status (entity state)** = catalog stays as raw Tailwind palette, unchanged.
|
||||
|
||||
**Suggested resolution: do not codify a unified status-display pattern in this DS pass.** Pattern shape is explicitly pending the eventual signal-token / status-token scoping. When that project happens, the shape of these three components (typed enum props, class naming, default-fallback behavior) will want to change in sync. Documented as-is in [../patterns/status-display.md](../patterns/status-display.md).
|
||||
|
||||
Separately — and independent of the tokens — the `status: string` / `priority: string` untyped props could be typed as string literal unions over the keys of `status-colors.ts`'s records without touching colors. That's a small, self-contained follow-up.
|
||||
|
||||
### 9. Quota display: `ProviderQuotaCard` ↔ `QuotaBar`
|
||||
|
||||
**Problem.** Two components share the "quota" root name but differ in suffix and in role. `QuotaBar` is a rendering primitive — one horizontal bar with a percent-used fill and a three-level color threshold. `ProviderQuotaCard` is a card composer that *uses* `QuotaBar` (multiple times, for different time windows) plus `ClaudeSubscriptionPanel` / `CodexSubscriptionPanel`. Functionally different, but the `-Bar` / `-Card` naming spread suggests parallelism that isn't there.
|
||||
|
||||
Secondary concern: `QuotaBar` hardcodes three-level severity as raw Tailwind palette (`bg-red-400`, `bg-yellow-400`, `bg-green-400`). It is one of four places in the codebase that encode the same red/amber/green severity language without shared tokens — see [../tokens/tokens-review.md §4](../tokens/tokens-review.md#4-status-colorsts-is-a-canonical-semantic-color-catalog-that-bypasses-the-ds) and [../patterns/patterns-review.md §6 — Severity indicator](../patterns/patterns-review.md#6-severity-indicator-3-level-health-display--pattern-opportunity).
|
||||
|
||||
**Affected files.**
|
||||
|
||||
- `ui/src/components/QuotaBar.tsx` (2 uses — 65 lines — primitive bar)
|
||||
- `ui/src/components/ProviderQuotaCard.tsx` (1 use — 416 lines — card composer that embeds `QuotaBar`)
|
||||
|
||||
**Suggested resolution.** Not a consolidation target — different roles. The naming can be clearer if a future refactor happens (e.g., rename the composer to `ProviderQuotaSummary` or `QuotaOverviewCard`). Separate concern: the three-level severity coloring in `QuotaBar` would collapse onto signal/status tokens when that broader work lands. Documented as-is in [../patterns/quota-display.md](../patterns/quota-display.md).
|
||||
|
||||
---
|
||||
|
||||
## Naming inconsistencies
|
||||
|
||||
**Status: deferred (2026-04-21).** All vocabulary decisions below (Dialog-vs-Modal, Picker-vs-Selector, Editor-vs-Form, Sidebar-vs-Rail-vs-Nav, Card-vs-Panel-vs-Widget, file-name casing, prop-vocabulary conventions) are intentionally unresolved. Addressing opportunistically when the relevant code is touched for other reasons, or as a standalone naming-pass project. Each subsection is self-contained — pick any one up cold without reading the others. In pattern docs under `../patterns/`, vocabulary is noted as observed variance (e.g., "dialog pattern — some implementations named `*Modal`, same primitive") rather than as a canonical prescription.
|
||||
|
||||
### Container-word proliferation: `Card` vs `Panel` vs `Widget` vs `Modal` vs `Dialog`
|
||||
|
||||
Counts of components by suffix:
|
||||
|
||||
| Suffix | Count | Examples |
|
||||
|---|---|---|
|
||||
| `Card` | 12 | `ApprovalCard`, `BudgetPolicyCard`, `MetricCard`, … |
|
||||
| `Panel` | 5 | `ActiveAgentsPanel`, `ClaudeSubscriptionPanel`, `PropertiesPanel`, … |
|
||||
| `Widget` | 1 | `LiveRunWidget` |
|
||||
| `Modal` | 3 | `DocumentDiffModal`, `ImageGalleryModal`, `PathInstructionsModal` |
|
||||
| `Dialog` | 6 | `NewAgentDialog`, `ExecutionWorkspaceCloseDialog`, `RoutineRunVariablesDialog`, … |
|
||||
|
||||
**Dialog vs Modal:** both Modal- and Dialog-named components use the same `dialog.tsx` primitive. There's no structural distinction — just two names for the same thing. Pick one. The shadcn default is `Dialog`; keeping `Dialog` is the lower-friction move.
|
||||
|
||||
**Card vs Panel:** less clear-cut. Rough pattern in this codebase:
|
||||
- `*Card` when the thing is a discrete piece of content in a grid (`BudgetPolicyCard`, `FinanceBillerCard`).
|
||||
- `*Panel` when the thing is a larger region that groups related content (`ActiveAgentsPanel`, `PropertiesPanel`).
|
||||
- But there are violations: `ClaudeSubscriptionPanel` and `AccountingModelCard` look alike structurally and are adjacent in usage.
|
||||
|
||||
**Widget (1):** only `LiveRunWidget` uses this. Either absorb into `Card`/`Panel` or codify `Widget` as a distinct concept (e.g., "dashboard-tile with its own data fetch and refresh cadence") and use it consistently.
|
||||
|
||||
### `Picker` vs `Selector`
|
||||
|
||||
| Name | Uses | Picks what |
|
||||
|---|---|---|
|
||||
| `AgentIconPicker` | 13 | An icon |
|
||||
| `ExecutionParticipantPicker` | 0 (unused) | A participant |
|
||||
| `ReportsToPicker` | 2 | An agent |
|
||||
| `InlineEntitySelector` | 1 | An entity |
|
||||
|
||||
All four wrap a popover-plus-list. Picker and Selector are synonymous here. Pick one term.
|
||||
|
||||
### `Editor` vs `Form`
|
||||
|
||||
| Name | Uses | Purpose |
|
||||
|---|---|---|
|
||||
| `MarkdownEditor` | 16 | Markdown input |
|
||||
| `InlineEditor` | 6 | Generic text editor |
|
||||
| `EnvVarEditor` | 2 | Structured key=value list |
|
||||
| `RoutineVariablesEditor` | 2 | Structured variable list |
|
||||
| `ScheduleEditor` | 1 | Cron schedule |
|
||||
| `AgentConfigForm` | 5 | Full agent config |
|
||||
| `JsonSchemaForm` | 1 | Schema-driven form |
|
||||
|
||||
The line between Editor and Form is fuzzy: `RoutineVariablesEditor` looks like a form, `JsonSchemaForm` could have been named `JsonSchemaEditor`. Suggested rule: **Editor** for content inputs (text, schedule, markdown); **Form** for labeled-field structured forms. Audit whether any renaming is worth the churn; otherwise document the rule and enforce for new additions.
|
||||
|
||||
### Sidebar / Rail / Nav
|
||||
|
||||
For what are structurally all "chrome around the main content area":
|
||||
|
||||
- `Sidebar.tsx`
|
||||
- `InstanceSidebar.tsx`
|
||||
- `CompanySettingsSidebar.tsx`
|
||||
- `CompanyRail.tsx`
|
||||
- `MobileBottomNav.tsx`
|
||||
- `access/CompanySettingsNav.tsx`
|
||||
|
||||
Pick a default term (`Sidebar`) and use a prefix for variants (`InstanceSidebar`, `CompanySettingsSidebar`). Reserve `Rail` for genuinely different (narrow-strip) affordances, `Nav` for tab-bar-style navigation. The current split is inconsistent with itself.
|
||||
|
||||
### File-naming convention is inconsistent
|
||||
|
||||
Most composites are `PascalCase.tsx`. Shadcn primitives are `kebab-case.tsx`. But `agent-config-primitives.tsx` and `agent-config-defaults.ts` sit in the top-level composite directory in kebab-case — they don't belong with the shadcn primitives (not in `ui/`) but also don't match the PascalCase of their neighbors.
|
||||
|
||||
Suggested fix: rename `agent-config-primitives.tsx` → `AgentConfigPrimitives.tsx` (or split into per-component files if the 11 exports warrant it) and `agent-config-defaults.ts` → `agentConfigDefaults.ts` to match JS convention for non-component modules.
|
||||
|
||||
### Prop vocabulary is underspecified
|
||||
|
||||
A static scan for conventional variant-shaping props (`variant`, `size`, `intent`, `tone`, `kind`, `state`, `status`, `mode`, `level`, `severity`, `priority`) found them used in just **7** components across the codebase (excluding shadcn primitives where they're well-defined):
|
||||
|
||||
| Prop | Component | Type |
|
||||
|---|---|---|
|
||||
| `variant` | `IssueChatThread` | `"full" \| "embedded"` |
|
||||
| `variant` | `PageSkeleton` | `... \| "list"` (opaque) |
|
||||
| `size` | `Identity` | `IdentitySize` |
|
||||
| `kind` | `ProjectWorkspaceSummaryCard` | `"project_workspace" \| "execution_workspace"` |
|
||||
| `status` | `StatusIcon` | `string` (**untyped**) |
|
||||
| `mode` | `RunTranscriptView` | `TranscriptMode` |
|
||||
| `priority` | `PriorityIcon` | `string` (**untyped**) |
|
||||
|
||||
Observations:
|
||||
- `status: string` in `StatusIcon` and `priority: string` in `PriorityIcon` should be typed enums (matching the keys of `status-colors.ts`).
|
||||
- `variant` is used for completely unrelated concepts in different components — that's fine semantically, but reinforces that there's no shared prop-vocabulary convention.
|
||||
- Shadcn primitives (`button`, `badge`) use `variant`/`size` with well-defined CVA enums — these are the model.
|
||||
|
||||
---
|
||||
|
||||
## Token non-compliance
|
||||
|
||||
(Mirror of the Stage 1 token drift findings, but attributed per-component so Stage 3 reviewers can target the worst offenders.)
|
||||
|
||||
### Components that hardcode chart/status colors
|
||||
|
||||
From [tokens-review.md §1 and §4](../tokens/tokens-review.md):
|
||||
|
||||
| Component | Drift |
|
||||
|---|---|
|
||||
| `ActivityCharts.tsx` | 17 hardcoded Tailwind-palette hex values for status/priority chart colors; uses `chart-*` tokens zero times. |
|
||||
| `OrgChart.tsx` (a page) | 6 hardcoded hex values for agent status dot colors. |
|
||||
| `StatusIcon.tsx` (14 uses) | Consumes `issueStatusIcon` from `status-colors.ts` — raw Tailwind palette classes (`text-blue-600`, `border-violet-600`, etc.). |
|
||||
| `StatusBadge.tsx` (19 uses) | Consumes `statusBadge` from `status-colors.ts` — raw Tailwind palette classes. |
|
||||
| `PriorityIcon.tsx` (5 uses) | Consumes `priorityColor` — raw Tailwind palette. |
|
||||
| `AgentActionButtons.tsx` | Uses `agentStatusDot` — raw Tailwind palette. |
|
||||
|
||||
### Components with heavy raw-palette styling
|
||||
|
||||
From Stage 1's 659-hit analysis:
|
||||
|
||||
- `AgentDetail.tsx` (75 palette hits) — production page
|
||||
- `RunTranscriptView.tsx` (47 hits) — production component
|
||||
- `IssueChatThread.tsx` (22 hits) — production component
|
||||
|
||||
### Components with arbitrary radius values
|
||||
|
||||
See [tokens-review.md §7](../tokens/tokens-review.md#7-arbitrary-radius-values-bypass-the-scale-18-occurrences) for the full list. Production components in the list:
|
||||
- `CompanyRail.tsx` — `rounded-[14px]`, `rounded-[22px]`
|
||||
- Several UxLab pages (acceptable as prototypes)
|
||||
|
||||
### Recommendation
|
||||
|
||||
Do not extract per-component detail docs for `StatusIcon`, `StatusBadge`, `PriorityIcon`, `AgentActionButtons` as final specs — their color language is blocked on the signal-token decision from [tokens-review.md §4](../tokens/tokens-review.md#4-status-colorsts-is-a-canonical-semantic-color-catalog-that-bypasses-the-ds). Stage-4 patterns that lean on these (status indicator, priority indicator, agent card with status) will change shape once signal tokens land.
|
||||
|
||||
---
|
||||
|
||||
## Story coverage gaps
|
||||
|
||||
Components with **3+ code uses and no Storybook coverage** — the highest-priority story gaps:
|
||||
|
||||
| Component | Uses | Notes |
|
||||
|---|---|---|
|
||||
| `StatusIcon` | 14 | Central status-color consumer. Coverage gap tied to §Token non-compliance. |
|
||||
| `collapsible` (primitive) | 8 | Shadcn primitive. `foundations.stories.tsx` covers many primitives but skips collapsible. |
|
||||
| `dropdown-menu` (primitive) | 8 | Same. |
|
||||
| `avatar` (primitive) | 7 | Same — despite Avatar being one of the few components with sub-parts (`AvatarGroup`, `AvatarFallback`, `AvatarBadge`). |
|
||||
| `skeleton` (primitive) | 6 | Same. |
|
||||
| `scroll-area` (primitive) | 3 | Same. |
|
||||
| `ApprovalPayload` | 4 | Feature. |
|
||||
| `IssueReferencePill` | 4 | Feature. |
|
||||
| `SidebarNavItem` | 3 | Structural. |
|
||||
| `RunTranscriptView` | 3 | Feature — the transcript rendering. |
|
||||
|
||||
Two categories:
|
||||
1. **Shadcn primitives missing from `foundations.stories.tsx`.** Small, targeted fix — add them to the existing foundations story.
|
||||
2. **Production features without a story.** `StatusIcon` is the highest value given its role across 14 call sites.
|
||||
|
||||
---
|
||||
|
||||
## Unused / low-signal components
|
||||
|
||||
### Truly dead (0 imports, no Storybook coverage): 0
|
||||
|
||||
None. Every file in `ui/src/components/` either gets imported somewhere or appears in a story.
|
||||
|
||||
### Storybook-only (0 imports, appears in a story): 4
|
||||
|
||||
These are rendered in a story file but never imported by any page or other component:
|
||||
|
||||
| Component | Storybook location (approx.) |
|
||||
|---|---|
|
||||
| `AccountingModelCard` | financial/accounting-related story |
|
||||
| `AgentProperties` | agent-management story |
|
||||
| `CompanySwitcher` | navigation-layout story |
|
||||
| `ExecutionParticipantPicker` | (story-referenced; unused in app) |
|
||||
|
||||
**Interpretation:** these are either (a) abandoned experiments still living in Storybook, (b) components waiting for the page that uses them, or (c) genuinely unused and should be deleted. Recommend: owner disposition per file. `AgentProperties` is especially surprising given the existence of sibling `IssueProperties`/`GoalProperties`/`ProjectProperties` — possibly a planned-but-not-wired variant of the properties family.
|
||||
|
||||
### Below-threshold (1–2 code uses, no detail file): 76
|
||||
|
||||
Not drift — many are legitimately single-use (one-off dialogs, one-off banners). But at 76 out of ~130 it's worth noting: this codebase heavily favors single-use components. A generic-component consolidation pass would likely shrink this group by ~30%. The candidates from §Likely duplicates above are the best places to start.
|
||||
|
||||
---
|
||||
|
||||
## Plugin SDK hybrid status, prioritization deferred
|
||||
|
||||
**Status: RESOLVED as intentional hybrid (2026-04-21).** Not drift. The plugin SDK (`packages/plugins/sdk/src/ui/components.ts`) declares 11 ambient component types; the host implements 2 (`MetricCard`, `StatusBadge`) and leaves the other 9 as contract-only. The 9 unimplemented components now carry a `@status contract-only` JSDoc tag in the SDK source so plugin authors see the status in IDE tooltips at call sites.
|
||||
|
||||
Prioritization of which of the 9 to build first is a **separate plugin-SDK roadmap conversation** — not a DS decision and not in scope here. This section captures the current state and the most likely first-implementations when that conversation happens.
|
||||
|
||||
### Current state
|
||||
|
||||
| SDK contract | Host implementation | Status |
|
||||
|---|---|---|
|
||||
| `MetricCard` | [`ui/src/components/MetricCard.tsx`](../../../ui/src/components/MetricCard.tsx) | ✅ implemented |
|
||||
| `StatusBadge` | [`ui/src/components/StatusBadge.tsx`](../../../ui/src/components/StatusBadge.tsx) | ✅ implemented |
|
||||
| `DataTable` | — | 🔌 contract-only |
|
||||
| `TimeseriesChart` | — | 🔌 contract-only |
|
||||
| `MarkdownBlock` | — | 🔌 contract-only |
|
||||
| `KeyValueList` | — | 🔌 contract-only |
|
||||
| `ActionBar` | — | 🔌 contract-only |
|
||||
| `LogView` | — | 🔌 contract-only |
|
||||
| `JsonTree` | — | 🔌 contract-only |
|
||||
| `Spinner` | — | 🔌 contract-only |
|
||||
| `ErrorBoundary` | — | 🔌 contract-only |
|
||||
|
||||
Each of the 9 contract-only entries has a JSDoc block in [`packages/plugins/sdk/src/ui/components.ts`](../../../packages/plugins/sdk/src/ui/components.ts) (lines 253–316) that tells plugin authors the runtime will fail and points here.
|
||||
|
||||
`PLUGIN_SPEC.md:30` already acknowledged this before the DS extraction: _"The current runtime does not yet ship a real host-provided plugin UI component kit."_
|
||||
|
||||
### Implementation notes (for when prioritization happens)
|
||||
|
||||
Not prescriptive — candidates surfaced during the 2026-04 extraction:
|
||||
|
||||
- **`MarkdownBlock`** — thinnest wrapper around `ui/src/components/MarkdownBody.tsx`. Possibly an alias rather than a new component.
|
||||
- **`Spinner`** — no matching host component. A ~10-line shadcn-style primitive would be the simplest new build.
|
||||
- **`KeyValueList`** — patterns exist ad-hoc inside `EntityRow`, `PropertiesPanel` bodies, and `FinanceBillerCard`. Candidate for extraction into a shared primitive.
|
||||
- **`LogView`** — no counterpart. Transcript rendering is tightly coupled to `RunTranscriptView`; a generic log viewer is a genuine new build.
|
||||
- **`JsonTree`** — no counterpart. A new build.
|
||||
- **`ErrorBoundary`** — standard React pattern; a thin wrapper around a React error boundary class.
|
||||
- **`ActionBar`**, **`DataTable`**, **`TimeseriesChart`** — each is a real component's worth of surface area. Not thin builds.
|
||||
|
||||
### Affected files if the prioritization conversation opens
|
||||
|
||||
- `packages/plugins/sdk/src/ui/components.ts` (SDK declarations + @status tags)
|
||||
- `packages/plugins/sdk/src/ui/runtime.ts` (runtime bridge — `renderSdkUiComponent`)
|
||||
- New host files in `ui/src/components/` for each implemented contract
|
||||
- [`components/index.md` — Plugin SDK contracts table](./index.md#plugin-sdk-contracts-11) (update implementation column as each lands)
|
||||
228
doc/design-system/components/index.md
Normal file
228
doc/design-system/components/index.md
Normal file
@@ -0,0 +1,228 @@
|
||||
# Components — Index
|
||||
|
||||
- **Generated:** 2026-04-21
|
||||
- **Repo SHA:** a26e1288b627e82c554445732c7d844648e6b5e1
|
||||
- **Scope:** `ui/` + plugin SDK contracts
|
||||
- **Review:** [components-review.md](./components-review.md)
|
||||
|
||||
## Counts
|
||||
|
||||
- **Total component files:** 135
|
||||
- **With dedicated detail files (3+ code uses):** 53
|
||||
- **Below threshold (1–2 uses):** 76
|
||||
- **Storybook-only (in stories, 0 code uses):** 4
|
||||
- **Dead (no uses, no stories):** 0
|
||||
- **Non-component files (hooks, defaults):** 2
|
||||
- **Plugin SDK contracts:** 11 (2 implemented by name, 9 contract-only — see §Plugin SDK contracts below)
|
||||
|
||||
### By category
|
||||
|
||||
| Category | Count |
|
||||
|---|---|
|
||||
| composite | 64 |
|
||||
| primitive | 22 |
|
||||
| standalone | 47 |
|
||||
| utility-or-hook | 2 |
|
||||
|
||||
**Status markers in the tables below:**
|
||||
|
||||
- 📗 **documented** — ≥3 imports, has its own detail file in this directory
|
||||
- 📘 **below-threshold** — 1–2 imports, no detail file
|
||||
- 📙 **storybook-only** — 0 code imports, but appears in a story file
|
||||
- ☠️ **dead** — 0 imports, 0 stories
|
||||
- 🔌 **contract-only** — plugin SDK ambient declaration with no matching host implementation
|
||||
|
||||
---
|
||||
|
||||
## Primitives — `ui/src/components/ui/` (shadcn, 22)
|
||||
|
||||
All 22 shadcn primitives, by file name. These are the non-negotiable UI vocabulary — composites should consume these before reaching for custom markup.
|
||||
|
||||
| Component | Path | Uses | Pages / Comps | Story |
|
||||
|---|---|---|---|---|
|
||||
| 📗 [Button](./Button.md) | `ui/src/components/ui/button.tsx` | 81 | 41 / 38 | ✓ |
|
||||
| 📗 [Popover](./Popover.md) | `ui/src/components/ui/popover.tsx` | 27 | 6 / 20 | ✓ |
|
||||
| 📗 [Dialog](./Dialog.md) | `ui/src/components/ui/dialog.tsx` | 21 | 7 / 14 | ✓ |
|
||||
| 📗 [Input](./Input.md) | `ui/src/components/ui/input.tsx` | 20 | 10 / 10 | ✓ |
|
||||
| 📗 [Badge](./Badge.md) | `ui/src/components/ui/badge.tsx` | 18 | 11 / 7 | ✓ |
|
||||
| 📗 [Card](./Card.md) | `ui/src/components/ui/card.tsx` | 18 | 10 / 8 | ✓ |
|
||||
| 📗 [Tabs](./Tabs.md) | `ui/src/components/ui/tabs.tsx` | 15 | 13 / 2 | ✓ |
|
||||
| 📗 [Separator](./Separator.md) | `ui/src/components/ui/separator.tsx` | 11 | 7 / 4 | ✓ |
|
||||
| 📗 [Tooltip](./Tooltip.md) | `ui/src/components/ui/tooltip.tsx` | 11 | 3 / 7 | ✓ |
|
||||
| 📗 [Select](./Select.md) | `ui/src/components/ui/select.tsx` | 10 | 5 / 5 | ✓ |
|
||||
| 📗 [Textarea](./Textarea.md) | `ui/src/components/ui/textarea.tsx` | 9 | 4 / 5 | ✓ |
|
||||
| 📗 [Collapsible](./Collapsible.md) | `ui/src/components/ui/collapsible.tsx` | 8 | 4 / 4 | — |
|
||||
| 📗 [DropdownMenu](./DropdownMenu.md) | `ui/src/components/ui/dropdown-menu.tsx` | 8 | 3 / 5 | — |
|
||||
| 📗 [Label](./Label.md) | `ui/src/components/ui/label.tsx` | 8 | 5 / 3 | ✓ |
|
||||
| 📗 [ToggleSwitch](./ToggleSwitch.md) | `ui/src/components/ui/toggle-switch.tsx` | 8 | 5 / 3 | ✓ |
|
||||
| 📗 [Avatar](./Avatar.md) | `ui/src/components/ui/avatar.tsx` | 7 | 3 / 4 | — |
|
||||
| 📗 [Checkbox](./Checkbox.md) | `ui/src/components/ui/checkbox.tsx` | 6 | 4 / 2 | ✓ |
|
||||
| 📗 [Skeleton](./Skeleton.md) | `ui/src/components/ui/skeleton.tsx` | 6 | 3 / 3 | — |
|
||||
| 📗 [ScrollArea](./ScrollArea.md) | `ui/src/components/ui/scroll-area.tsx` | 3 | 2 / 1 | — |
|
||||
| 📘 Breadcrumb | `ui/src/components/ui/breadcrumb.tsx` | 2 | 1 / 1 | — |
|
||||
| 📘 Command | `ui/src/components/ui/command.tsx` | 2 | 1 / 1 | ✓ |
|
||||
| 📘 Sheet | `ui/src/components/ui/sheet.tsx` | 2 | 2 / 0 | — |
|
||||
|
||||
---
|
||||
|
||||
## Composites (64)
|
||||
|
||||
Components that import 1+ other component from `@/components/*`. Application-level feature UI.
|
||||
|
||||
| Component | Path | Uses | Pages / Comps | Story |
|
||||
|---|---|---|---|---|
|
||||
| 📗 [PageSkeleton](./PageSkeleton.md) | `ui/src/components/PageSkeleton.tsx` | 23 | 22 / 1 | ✓ |
|
||||
| 📗 [EmptyState](./EmptyState.md) | `ui/src/components/EmptyState.tsx` | 20 | 19 / 1 | ✓ |
|
||||
| 📗 [agent-config-primitives](./agent-config-primitives.md) | `ui/src/components/agent-config-primitives.tsx` | 19 | 4 / 3 | ✓ |
|
||||
| 📗 [Identity](./Identity.md) | `ui/src/components/Identity.tsx` | 19 | 7 / 12 | ✓ |
|
||||
| 📗 [StatusIcon](./StatusIcon.md) | `ui/src/components/StatusIcon.tsx` | 14 | 5 / 9 | — |
|
||||
| 📗 [AgentIconPicker](./AgentIconPicker.md) | `ui/src/components/AgentIconPicker.tsx` | 13 | 4 / 9 | ✓ |
|
||||
| 📗 [PathInstructionsModal](./PathInstructionsModal.md) | `ui/src/components/PathInstructionsModal.tsx` | 12 | 2 / 3 | ✓ |
|
||||
| 📗 [PageTabBar](./PageTabBar.md) | `ui/src/components/PageTabBar.tsx` | 10 | 9 / 1 | ✓ |
|
||||
| 📗 [InlineEntitySelector](./InlineEntitySelector.md) | `ui/src/components/InlineEntitySelector.tsx` | 8 | 2 / 4 | ✓ |
|
||||
| 📗 [IssuesList](./IssuesList.md) | `ui/src/components/IssuesList.tsx` | 6 | 5 / 1 | ✓ |
|
||||
| 📗 [AgentConfigForm](./AgentConfigForm.md) | `ui/src/components/AgentConfigForm.tsx` | 5 | 3 / 0 | ✓ |
|
||||
| 📗 [PriorityIcon](./PriorityIcon.md) | `ui/src/components/PriorityIcon.tsx` | 5 | 2 / 3 | ✓ |
|
||||
| 📗 [IssueChatThread](./IssueChatThread.md) | `ui/src/components/IssueChatThread.tsx` | 4 | 2 / 2 | ✓ |
|
||||
| 📗 [ApprovalCard](./ApprovalCard.md) | `ui/src/components/ApprovalCard.tsx` | 3 | 2 / 1 | ✓ |
|
||||
| 📗 [BudgetPolicyCard](./BudgetPolicyCard.md) | `ui/src/components/BudgetPolicyCard.tsx` | 3 | 3 / 0 | ✓ |
|
||||
| 📗 [IssueFiltersPopover](./IssueFiltersPopover.md) | `ui/src/components/IssueFiltersPopover.tsx` | 3 | 1 / 2 | ✓ |
|
||||
| 📗 [IssueLinkQuicklook](./IssueLinkQuicklook.md) | `ui/src/components/IssueLinkQuicklook.tsx` | 3 | 0 / 2 | ✓ |
|
||||
| 📗 [IssueWorkspaceCard](./IssueWorkspaceCard.md) | `ui/src/components/IssueWorkspaceCard.tsx` | 3 | 1 / 2 | ✓ |
|
||||
| 📗 [RoutineRunVariablesDialog](./RoutineRunVariablesDialog.md) | `ui/src/components/RoutineRunVariablesDialog.tsx` | 3 | 2 / 1 | ✓ |
|
||||
| 📗 [WorkspaceRuntimeControls](./WorkspaceRuntimeControls.md) | `ui/src/components/WorkspaceRuntimeControls.tsx` | 3 | 2 / 1 | ✓ |
|
||||
| 📘 AgentActionButtons | `ui/src/components/AgentActionButtons.tsx` | 2 | 2 / 0 | ✓ |
|
||||
| 📘 CommandPalette | `ui/src/components/CommandPalette.tsx` | 2 | 0 / 2 | ✓ |
|
||||
| 📘 IssueColumns | `ui/src/components/IssueColumns.tsx` | 2 | 1 / 1 | ✓ |
|
||||
| 📘 IssueContinuationHandoff | `ui/src/components/IssueContinuationHandoff.tsx` | 2 | 1 / 1 | ✓ |
|
||||
| 📘 IssueDocumentsSection | `ui/src/components/IssueDocumentsSection.tsx` | 2 | 1 / 1 | ✓ |
|
||||
| 📘 IssueProperties | `ui/src/components/IssueProperties.tsx` | 2 | 1 / 1 | ✓ |
|
||||
| 📘 NewIssueDialog | `ui/src/components/NewIssueDialog.tsx` | 2 | 0 / 2 | ✓ |
|
||||
| 📘 OutputFeedbackButtons | `ui/src/components/OutputFeedbackButtons.tsx` | 2 | 0 / 2 | — |
|
||||
| 📘 ProjectWorkspaceSummaryCard | `ui/src/components/ProjectWorkspaceSummaryCard.tsx` | 2 | 0 / 2 | ✓ |
|
||||
| 📘 ReportsToPicker | `ui/src/components/ReportsToPicker.tsx` | 2 | 1 / 1 | ✓ |
|
||||
| 📘 RoutineVariablesEditor | `ui/src/components/RoutineVariablesEditor.tsx` | 2 | 2 / 0 | ✓ |
|
||||
| 📘 Sidebar | `ui/src/components/Sidebar.tsx` | 2 | 0 / 2 | ✓ |
|
||||
| 📘 SidebarAccountMenu | `ui/src/components/SidebarAccountMenu.tsx` | 2 | 0 / 2 | ✓ |
|
||||
| 📘 SidebarCompanyMenu | `ui/src/components/SidebarCompanyMenu.tsx` | 2 | 0 / 2 | ✓ |
|
||||
| 📘 CompanySettingsNav | `ui/src/components/access/CompanySettingsNav.tsx` | 1 | 0 / 1 | — |
|
||||
| 📘 ModeBadge | `ui/src/components/access/ModeBadge.tsx` | 1 | 1 / 0 | — |
|
||||
| 📘 BillerSpendCard | `ui/src/components/BillerSpendCard.tsx` | 1 | 1 / 0 | ✓ |
|
||||
| 📘 BreadcrumbBar | `ui/src/components/BreadcrumbBar.tsx` | 1 | 0 / 1 | ✓ |
|
||||
| 📘 BudgetIncidentCard | `ui/src/components/BudgetIncidentCard.tsx` | 1 | 1 / 0 | ✓ |
|
||||
| 📘 CommentThread | `ui/src/components/CommentThread.tsx` | 1 | 0 / 1 | ✓ |
|
||||
| 📘 CompanyRail | `ui/src/components/CompanyRail.tsx` | 1 | 0 / 1 | ✓ |
|
||||
| 📘 DocumentDiffModal | `ui/src/components/DocumentDiffModal.tsx` | 1 | 0 / 1 | ✓ |
|
||||
| 📘 FilterBar | `ui/src/components/FilterBar.tsx` | 1 | 1 / 0 | ✓ |
|
||||
| 📘 FinanceBillerCard | `ui/src/components/FinanceBillerCard.tsx` | 1 | 1 / 0 | ✓ |
|
||||
| 📘 FinanceKindCard | `ui/src/components/FinanceKindCard.tsx` | 1 | 1 / 0 | ✓ |
|
||||
| 📘 FinanceTimelineCard | `ui/src/components/FinanceTimelineCard.tsx` | 1 | 1 / 0 | ✓ |
|
||||
| 📘 GoalProperties | `ui/src/components/GoalProperties.tsx` | 1 | 1 / 0 | ✓ |
|
||||
| 📘 IssuesQuicklook | `ui/src/components/IssuesQuicklook.tsx` | 1 | 0 / 1 | ✓ |
|
||||
| 📘 JsonSchemaForm | `ui/src/components/JsonSchemaForm.tsx` | 1 | 1 / 0 | ✓ |
|
||||
| 📘 KeyboardShortcutsCheatsheet | `ui/src/components/KeyboardShortcutsCheatsheet.tsx` | 1 | 0 / 1 | ✓ |
|
||||
| 📘 NewAgentDialog | `ui/src/components/NewAgentDialog.tsx` | 1 | 0 / 1 | ✓ |
|
||||
| 📘 NewGoalDialog | `ui/src/components/NewGoalDialog.tsx` | 1 | 0 / 1 | ✓ |
|
||||
| 📘 NewProjectDialog | `ui/src/components/NewProjectDialog.tsx` | 1 | 0 / 1 | ✓ |
|
||||
| 📘 OnboardingWizard | `ui/src/components/OnboardingWizard.tsx` | 1 | 0 / 0 | ✓ |
|
||||
| 📘 ProjectProperties | `ui/src/components/ProjectProperties.tsx` | 1 | 1 / 0 | ✓ |
|
||||
| 📘 PropertiesPanel | `ui/src/components/PropertiesPanel.tsx` | 1 | 0 / 1 | — |
|
||||
| 📘 ProviderQuotaCard | `ui/src/components/ProviderQuotaCard.tsx` | 1 | 1 / 0 | ✓ |
|
||||
| 📘 ScheduleEditor | `ui/src/components/ScheduleEditor.tsx` | 1 | 1 / 0 | ✓ |
|
||||
| 📘 SidebarAgents | `ui/src/components/SidebarAgents.tsx` | 1 | 0 / 1 | — |
|
||||
| 📘 SidebarProjects | `ui/src/components/SidebarProjects.tsx` | 1 | 0 / 1 | — |
|
||||
| 📙 AccountingModelCard | `ui/src/components/AccountingModelCard.tsx` | 0 | 0 / 0 | ✓ |
|
||||
| 📙 AgentProperties | `ui/src/components/AgentProperties.tsx` | 0 | 0 / 0 | ✓ |
|
||||
| 📙 CompanySwitcher | `ui/src/components/CompanySwitcher.tsx` | 0 | 0 / 0 | ✓ |
|
||||
| 📙 ExecutionParticipantPicker | `ui/src/components/ExecutionParticipantPicker.tsx` | 0 | 0 / 0 | ✓ |
|
||||
|
||||
---
|
||||
|
||||
## Standalones (47)
|
||||
|
||||
Components that import no other `@/components/*`. Usually: icons, self-contained widgets, components that only depend on radix / lucide / local libs. The fact that they import zero composites or primitives is itself a data point — some of these probably should be using primitives.
|
||||
|
||||
| Component | Path | Uses | Pages / Comps | Story |
|
||||
|---|---|---|---|---|
|
||||
| 📗 [StatusBadge](./StatusBadge.md) | `ui/src/components/StatusBadge.tsx` | 19 | 12 / 7 | ✓ |
|
||||
| 📗 [MarkdownEditor](./MarkdownEditor.md) | `ui/src/components/MarkdownEditor.tsx` | 16 | 5 / 9 | ✓ |
|
||||
| 📗 [MarkdownBody](./MarkdownBody.md) | `ui/src/components/MarkdownBody.tsx` | 11 | 5 / 6 | ✓ |
|
||||
| 📗 [EntityRow](./EntityRow.md) | `ui/src/components/EntityRow.tsx` | 6 | 6 / 0 | ✓ |
|
||||
| 📗 [InlineEditor](./InlineEditor.md) | `ui/src/components/InlineEditor.tsx` | 6 | 4 / 2 | ✓ |
|
||||
| 📗 [ApprovalPayload](./ApprovalPayload.md) | `ui/src/components/ApprovalPayload.tsx` | 4 | 2 / 2 | — |
|
||||
| 📗 [CompanyPatternIcon](./CompanyPatternIcon.md) | `ui/src/components/CompanyPatternIcon.tsx` | 4 | 3 / 1 | ✓ |
|
||||
| 📗 [IssueReferencePill](./IssueReferencePill.md) | `ui/src/components/IssueReferencePill.tsx` | 4 | 1 / 3 | — |
|
||||
| 📗 [ActivityCharts](./ActivityCharts.md) | `ui/src/components/ActivityCharts.tsx` | 3 | 2 / 1 | ✓ |
|
||||
| 📗 [CopyText](./CopyText.md) | `ui/src/components/CopyText.tsx` | 3 | 2 / 1 | ✓ |
|
||||
| 📗 [IssueRow](./IssueRow.md) | `ui/src/components/IssueRow.tsx` | 3 | 1 / 2 | ✓ |
|
||||
| 📗 [PackageFileTree](./PackageFileTree.md) | `ui/src/components/PackageFileTree.tsx` | 3 | 3 / 0 | ✓ |
|
||||
| 📗 [SidebarNavItem](./SidebarNavItem.md) | `ui/src/components/SidebarNavItem.tsx` | 3 | 0 / 3 | — |
|
||||
| 📗 [RunTranscriptView](./RunTranscriptView.md) | `ui/src/components/transcript/RunTranscriptView.tsx` | 3 | 2 / 1 | — |
|
||||
| 📘 ActivityRow | `ui/src/components/ActivityRow.tsx` | 2 | 2 / 0 | ✓ |
|
||||
| 📘 AsciiArtAnimation | `ui/src/components/AsciiArtAnimation.tsx` | 2 | 1 / 1 | ✓ |
|
||||
| 📘 BudgetSidebarMarker | `ui/src/components/BudgetSidebarMarker.tsx` | 2 | 0 / 2 | ✓ |
|
||||
| 📘 CloudAccessGate | `ui/src/components/CloudAccessGate.tsx` | 2 | 0 / 0 | — |
|
||||
| 📘 CompanySettingsSidebar | `ui/src/components/CompanySettingsSidebar.tsx` | 2 | 0 / 2 | — |
|
||||
| 📘 EnvVarEditor | `ui/src/components/EnvVarEditor.tsx` | 2 | 0 / 2 | ✓ |
|
||||
| 📘 ExecutionWorkspaceCloseDialog | `ui/src/components/ExecutionWorkspaceCloseDialog.tsx` | 2 | 1 / 1 | ✓ |
|
||||
| 📘 GoalTree | `ui/src/components/GoalTree.tsx` | 2 | 2 / 0 | ✓ |
|
||||
| 📘 IssueGroupHeader | `ui/src/components/IssueGroupHeader.tsx` | 2 | 1 / 1 | ✓ |
|
||||
| 📘 IssueReferenceActivitySummary | `ui/src/components/IssueReferenceActivitySummary.tsx` | 2 | 1 / 1 | — |
|
||||
| 📘 IssueRelatedWorkPanel | `ui/src/components/IssueRelatedWorkPanel.tsx` | 2 | 1 / 1 | — |
|
||||
| 📘 IssueRunLedger | `ui/src/components/IssueRunLedger.tsx` | 2 | 1 / 1 | ✓ |
|
||||
| 📘 Layout | `ui/src/components/Layout.tsx` | 2 | 0 / 1 | — |
|
||||
| 📘 MetricCard | `ui/src/components/MetricCard.tsx` | 2 | 2 / 0 | ✓ |
|
||||
| 📘 OpenCodeLogoIcon | `ui/src/components/OpenCodeLogoIcon.tsx` | 2 | 0 / 1 | — |
|
||||
| 📘 ProjectWorkspacesContent | `ui/src/components/ProjectWorkspacesContent.tsx` | 2 | 2 / 0 | ✓ |
|
||||
| 📘 QuotaBar | `ui/src/components/QuotaBar.tsx` | 2 | 0 / 2 | ✓ |
|
||||
| 📘 RunChatSurface | `ui/src/components/RunChatSurface.tsx` | 2 | 0 / 2 | ✓ |
|
||||
| 📘 ScrollToBottom | `ui/src/components/ScrollToBottom.tsx` | 2 | 2 / 0 | — |
|
||||
| 📘 SwipeToArchive | `ui/src/components/SwipeToArchive.tsx` | 2 | 1 / 1 | ✓ |
|
||||
| 📘 ActiveAgentsPanel | `ui/src/components/ActiveAgentsPanel.tsx` | 1 | 1 / 0 | ✓ |
|
||||
| 📘 ClaudeSubscriptionPanel | `ui/src/components/ClaudeSubscriptionPanel.tsx` | 1 | 0 / 1 | ✓ |
|
||||
| 📘 CodexSubscriptionPanel | `ui/src/components/CodexSubscriptionPanel.tsx` | 1 | 0 / 1 | ✓ |
|
||||
| 📘 DevRestartBanner | `ui/src/components/DevRestartBanner.tsx` | 1 | 0 / 1 | — |
|
||||
| 📘 HermesIcon | `ui/src/components/HermesIcon.tsx` | 1 | 0 / 0 | — |
|
||||
| 📘 ImageGalleryModal | `ui/src/components/ImageGalleryModal.tsx` | 1 | 1 / 0 | ✓ |
|
||||
| 📘 InstanceSidebar | `ui/src/components/InstanceSidebar.tsx` | 1 | 0 / 1 | — |
|
||||
| 📘 KanbanBoard | `ui/src/components/KanbanBoard.tsx` | 1 | 0 / 1 | ✓ |
|
||||
| 📘 LiveRunWidget | `ui/src/components/LiveRunWidget.tsx` | 1 | 1 / 0 | ✓ |
|
||||
| 📘 MobileBottomNav | `ui/src/components/MobileBottomNav.tsx` | 1 | 0 / 1 | ✓ |
|
||||
| 📘 SidebarSection | `ui/src/components/SidebarSection.tsx` | 1 | 0 / 1 | — |
|
||||
| 📘 ToastViewport | `ui/src/components/ToastViewport.tsx` | 1 | 0 / 1 | — |
|
||||
| 📘 WorktreeBanner | `ui/src/components/WorktreeBanner.tsx` | 1 | 0 / 1 | ✓ |
|
||||
|
||||
---
|
||||
|
||||
## Non-component files (2)
|
||||
|
||||
These live in `ui/src/components/` by convention but don't export React components.
|
||||
|
||||
| File | Path | Role |
|
||||
|---|---|---|
|
||||
| `agent-config-defaults` | `ui/src/components/agent-config-defaults.ts` | module with shared constants/defaults |
|
||||
| `useLiveRunTranscripts` | `ui/src/components/transcript/useLiveRunTranscripts.ts` | React hook |
|
||||
|
||||
---
|
||||
|
||||
## Plugin SDK contracts (11)
|
||||
|
||||
Ambient component declarations from [`packages/plugins/sdk/src/ui/components.ts`](../../../packages/plugins/sdk/src/ui/components.ts). These are types-only; the host provides implementations at runtime via `renderSdkUiComponent(name, props)`.
|
||||
|
||||
> **Hybrid status is intentional (2026-04-21 decision).** Two components are implemented by the host. The other nine are **contract-only** — the types exist so plugin authors can code against them, but rendering today will fail at runtime. The 9 contract-only components carry a `@status contract-only` JSDoc tag in the SDK source, which appears in IDE tooltips at call sites. Prioritization of which to implement first is a separate plugin-SDK roadmap conversation, not a DS decision. See [components-review.md §Plugin SDK hybrid status](./components-review.md#plugin-sdk-hybrid-status-prioritization-deferred).
|
||||
|
||||
| SDK Component | Implementation | Status |
|
||||
|---|---|---|
|
||||
| `MetricCard` | [`ui/src/components/MetricCard.tsx`](../../../ui/src/components/MetricCard.tsx) | 📗 **implemented** |
|
||||
| `StatusBadge` | [`ui/src/components/StatusBadge.tsx`](../../../ui/src/components/StatusBadge.tsx) | 📗 **implemented** |
|
||||
| `DataTable` | — | 🔌 **contract-only** |
|
||||
| `TimeseriesChart` | — | 🔌 **contract-only** (distinct from `ActivityCharts.tsx`, which has a different API) |
|
||||
| `MarkdownBlock` | — | 🔌 **contract-only** (`MarkdownBody.tsx` is the host's markdown renderer, name differs) |
|
||||
| `KeyValueList` | — | 🔌 **contract-only** |
|
||||
| `ActionBar` | — | 🔌 **contract-only** (`AgentActionButtons.tsx` is role-specific, not a match) |
|
||||
| `LogView` | — | 🔌 **contract-only** |
|
||||
| `JsonTree` | — | 🔌 **contract-only** |
|
||||
| `Spinner` | — | 🔌 **contract-only** |
|
||||
| `ErrorBoundary` | — | 🔌 **contract-only** |
|
||||
|
||||
All 9 contract-only entries carry `@status contract-only` in their JSDoc block (see [`packages/plugins/sdk/src/ui/components.ts`](../../../packages/plugins/sdk/src/ui/components.ts) lines 253–316).
|
||||
69
doc/design-system/patterns/detail-page.md
Normal file
69
doc/design-system/patterns/detail-page.md
Normal file
@@ -0,0 +1,69 @@
|
||||
# Detail Page
|
||||
|
||||
Full-page layout for viewing and editing a single entity (agent, issue, project, goal, routine, approval, or execution/project workspace).
|
||||
|
||||
**Instances: 8.** `AgentDetail`, `IssueDetail`, `ProjectDetail`, `GoalDetail`, `RoutineDetail`, `ApprovalDetail`, `ExecutionWorkspaceDetail`, `ProjectWorkspaceDetail`.
|
||||
|
||||
## Composition (shared baseline)
|
||||
|
||||
Measured across the 8 instances by import intersection:
|
||||
|
||||
- **`button`** — 8/8 (every detail page has actions in its header)
|
||||
- **`tabs`** — 6/8 (detail pages split sub-views by tab)
|
||||
- **`PageSkeleton`** — 5/8 (loading state while the entity is being fetched)
|
||||
- **`StatusBadge`** — 4/8 (status is surfaced in the header area)
|
||||
- **`separator`** — 4/8
|
||||
- Breadcrumb context (`useBreadcrumbs`) — all 8 set a breadcrumb trail for the entity.
|
||||
|
||||
[INFER] Structural template, from reading AgentDetail and IssueDetail:
|
||||
|
||||
```
|
||||
<PageSkeleton or <ContentLoaded>
|
||||
<Breadcrumb / back-nav>
|
||||
<Header>
|
||||
<Title> — entity name + identifier
|
||||
<StatusBadge> — where applicable (issue, run, approval, agent)
|
||||
<Actions> — edit, archive, more-menu
|
||||
</Header>
|
||||
<Tabs> — 2–5 tabs (overview, config, activity, …)
|
||||
<TabContent>
|
||||
… entity-specific body (properties, related work, charts, transcripts)
|
||||
</TabContent>
|
||||
</Tabs>
|
||||
</>
|
||||
```
|
||||
|
||||
## Canonical instance
|
||||
|
||||
`ui/src/pages/IssueDetail.tsx` is the most mature and most-cross-referenced implementation. `ui/src/pages/AgentDetail.tsx` is second and shows the tab-bar with many sub-surfaces.
|
||||
|
||||
## Variance across instances
|
||||
|
||||
Observed differences that may be intentional (different entity domain) or may be drift:
|
||||
|
||||
| Instance | Breadcrumbs | Tabs | Status element | Loading state | Notes |
|
||||
|---|---|---|---|---|---|
|
||||
| `IssueDetail` | yes | yes | `StatusIcon` + `StatusBadge` | custom | largest file; widely-referenced |
|
||||
| `AgentDetail` | yes | yes | `StatusBadge` + `agentStatusDot` | `PageSkeleton` | composes `AgentConfigForm`, `ActivityCharts`, `PackageFileTree`, `RunTranscriptView` |
|
||||
| `ProjectDetail` | yes | yes | `StatusBadge` | `PageSkeleton` | |
|
||||
| `GoalDetail` | yes | (unconfirmed) | `StatusBadge` | `PageSkeleton` | |
|
||||
| `RoutineDetail` | yes | yes | (unconfirmed) | `PageSkeleton` | |
|
||||
| `ApprovalDetail` | yes | (none) | tone-coded via `ApprovalCard` | `PageSkeleton` | diverges — simpler shape |
|
||||
| `ExecutionWorkspaceDetail` | yes | — | — | — | newer; diverges |
|
||||
| `ProjectWorkspaceDetail` | yes | — | — | — | newer; diverges |
|
||||
|
||||
- **3 of 8 don't use `PageSkeleton`** — worth confirming each has an equivalent loading state.
|
||||
- **`ApprovalDetail` skips tabs** — likely correct for its single-surface nature.
|
||||
- **Status element is inconsistent** — `StatusBadge` alone, `StatusIcon + StatusBadge`, `StatusBadge + agentStatusDot` (separate dot helper), `ApprovalCard` tone encoding. Tied to [status-display.md](./status-display.md) and [../tokens/tokens-review.md §4](../tokens/tokens-review.md#4-status-colorsts-is-a-canonical-semantic-color-catalog-that-bypasses-the-ds).
|
||||
|
||||
## Related components and patterns
|
||||
|
||||
- Loading state: [`PageSkeleton`](../components/index.md) (documented as a component — used in 22+ places)
|
||||
- Empty state: [`EmptyState`](../components/index.md) (mostly used on list pages)
|
||||
- Status badge: [`StatusBadge`](../components/StatusBadge.md), [`StatusIcon`](../components/index.md) — see [status-display.md](./status-display.md)
|
||||
- Tab bar: shadcn `tabs`, and a custom [`PageTabBar`](../components/PageTabBar.md) used on some pages
|
||||
|
||||
## Open questions / risks
|
||||
|
||||
- Whether to codify a `<DetailPageHeader>` composite (title + status + actions block) to reduce per-page drift. Four detail pages already diverge on which status element they use.
|
||||
- The new `*WorkspaceDetail` pages do not yet share much structure with the older four. Check before Stage-4 pattern extraction runs again in a future quarter.
|
||||
51
doc/design-system/patterns/entity-creation-dialog.md
Normal file
51
doc/design-system/patterns/entity-creation-dialog.md
Normal file
@@ -0,0 +1,51 @@
|
||||
# Entity-Creation Dialog
|
||||
|
||||
Dialog surface for creating a new entity (agent, goal, issue, project).
|
||||
|
||||
**Instances: 4.** `NewAgentDialog`, `NewGoalDialog`, `NewIssueDialog`, `NewProjectDialog`.
|
||||
|
||||
> **Extraction-only pass.** This pattern document records the family as it exists today. It does not prescribe a merge into a single generic `NewEntityDialog`. See [components-review.md §Likely duplicates #1](../components/components-review.md#1-entity-creation-dialog-family-newagentdialog--newgoaldialog--newissuedialog--newprojectdialog) for the open-question treatment.
|
||||
|
||||
## Instances
|
||||
|
||||
| File | Lines | Uses | Opened via |
|
||||
|---|---|---|---|
|
||||
| `ui/src/components/NewAgentDialog.tsx` | 210 | 1 | `useDialog().newAgentOpen` |
|
||||
| `ui/src/components/NewGoalDialog.tsx` | (unread) | 1 | `useDialog()` |
|
||||
| `ui/src/components/NewIssueDialog.tsx` | 1699 | 2 | `useDialog()` |
|
||||
| `ui/src/components/NewProjectDialog.tsx` | (unread) | 1 | `useDialog()` |
|
||||
|
||||
## Composition (shared)
|
||||
|
||||
All four:
|
||||
|
||||
- Import `Dialog` + `DialogContent` from `@/components/ui/dialog` (primitive).
|
||||
- Consume a central `useDialog()` context from `ui/src/context/DialogContext` that exposes open/close flags per entity type.
|
||||
- Call an entity-specific API on submit (`agentsApi.create`, `issuesApi.create`, …) via `useMutation`.
|
||||
- Dismiss via `closeNewX()` from the same context.
|
||||
|
||||
## Shape divergence
|
||||
|
||||
The instances are **not structurally equivalent.** Line counts alone:
|
||||
|
||||
- `NewAgentDialog` = 210 lines (adapter picker → create stub)
|
||||
- `NewIssueDialog` = 1699 lines (rich form: assignees, projects, policies, mentions, dragdrop, advanced panel)
|
||||
|
||||
Other divergence indicators from imports:
|
||||
|
||||
- `NewIssueDialog` imports `agent-config-primitives` (`DraftInput`, `ChoosePathButton`, etc.), `ToggleSwitch`, `Popover`, large piece of `@dnd-kit`, markdown editors — i.e. a full inline form.
|
||||
- `NewAgentDialog` imports `Dialog`, `Button`, adapter-registry helpers — a chooser, not a full form.
|
||||
- `NewGoalDialog` and `NewProjectDialog` not examined in detail here; their size is likely between the two extremes.
|
||||
|
||||
## Open questions / risks
|
||||
|
||||
- Is `NewIssueDialog` intended to be "the" form and the others are just chooser-stubs that redirect to a detail page? That shape would be load-bearing. Currently unclear from static reading.
|
||||
- Without a generic base, adding a fifth entity (e.g. `NewRoutineDialog`) means another copy of the dialog-open-context wiring. The `useDialog()` context already carries the per-entity open/close flags — it would be the natural integration point if consolidation is pursued.
|
||||
|
||||
## Pattern use in Stage 4 analysis
|
||||
|
||||
If Stage 4 were to name a composition "new-entity-dialog" for future reference, the canonical definition would be:
|
||||
|
||||
> A `<Dialog>` opened from the `useDialog()` context, closed via a per-entity handler, containing an entity-specific body that submits through the matching API and invalidates the matching `queryKeys` on success.
|
||||
|
||||
Not yet codified as code. Documented here only.
|
||||
62
doc/design-system/patterns/entity-properties-panel.md
Normal file
62
doc/design-system/patterns/entity-properties-panel.md
Normal file
@@ -0,0 +1,62 @@
|
||||
# Entity Properties Panel
|
||||
|
||||
Side-panel content that shows an entity's metadata, lets the user edit inline, and drives per-field save state.
|
||||
|
||||
**Instances: 4 entity-specific panels + 1 generic panel.**
|
||||
`AgentProperties`, `GoalProperties`, `IssueProperties`, `ProjectProperties` + `PropertiesPanel`.
|
||||
|
||||
> **Extraction-only pass.** This pattern does not prescribe a merge. The open question about whether `PropertiesPanel` composes or duplicates the four entity-specific panels is surfaced — not resolved. See [components-review.md §Likely duplicates #2](../components/components-review.md#2-properties-panel-family-agentproperties--goalproperties--issueproperties--projectproperties--generic-propertiespanel).
|
||||
|
||||
## Instances
|
||||
|
||||
| Component | Lines | Uses | Role |
|
||||
|---|---|---|---|
|
||||
| `PropertiesPanel.tsx` | 29 | 1 | **Generic chrome.** A slide-in `<aside>` that reads `panelContent` from `usePanel()` context and renders whatever the caller has set. |
|
||||
| `AgentProperties.tsx` | (unread) | **0** | Entity-specific body (currently unused in production — Storybook-only). |
|
||||
| `GoalProperties.tsx` | (unread) | 1 | Entity-specific body. |
|
||||
| `IssueProperties.tsx` | 1370 | 2 | Entity-specific body; imports `StatusIcon`, `PriorityIcon`, `Identity`, `IssueReferencePill`, plus form primitives. |
|
||||
| `ProjectProperties.tsx` | 1140 | 1 | Entity-specific body; imports `StatusBadge`, status-colors consumers, `InlineEditor`, `EnvVarEditor`. |
|
||||
|
||||
## Relationship: chrome vs content
|
||||
|
||||
Reading `PropertiesPanel.tsx` (29 lines):
|
||||
|
||||
```tsx
|
||||
export function PropertiesPanel() {
|
||||
const { panelContent, panelVisible, setPanelVisible } = usePanel();
|
||||
if (!panelContent) return null;
|
||||
return (
|
||||
<aside className="… bg-card …">
|
||||
<div className="… flex flex-col …">
|
||||
<Header><Button icon="X" onClick={close} /></Header>
|
||||
<ScrollArea><div className="p-4">{panelContent}</div></ScrollArea>
|
||||
</div>
|
||||
</aside>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
So `PropertiesPanel` is a **slot**, not a content template. The four `*Properties` components are **contents** that get passed into that slot via `usePanel().setPanelContent(...)`.
|
||||
|
||||
## Open question (not resolved here)
|
||||
|
||||
Does `PropertiesPanel` already compose the four entity-specific panels, or do the four duplicate work that `PropertiesPanel` could own?
|
||||
|
||||
Evidence either way from a static read:
|
||||
|
||||
- **For "composes"**: the four entity-specific panels don't render a dialog/drawer wrapper themselves — each emits just the body content. They rely on some parent (either `PropertiesPanel` or a page-owned slot) to provide the outer container.
|
||||
- **For "duplicates"**: the header layout (`Properties` title + close button) is in `PropertiesPanel`, but each of the four is 1100+ lines of its own scaffolding (section headers, separators, form fields, save-state handling) that *could* be factored into a `<PropertiesPanelBody>` helper.
|
||||
|
||||
**Not a call to make in this extraction.** The founder should open `IssueProperties.tsx` and `ProjectProperties.tsx` side-by-side and judge.
|
||||
|
||||
## Also noted
|
||||
|
||||
- `AgentProperties.tsx` has **0 production uses** (Storybook-only). Either abandoned or waiting for a page. See [components-review.md §Unused components](../components/components-review.md#unused--low-signal-components).
|
||||
- Each entity-specific panel consumes `status-colors.ts` for status-color rendering (directly or via `StatusBadge`/`StatusIcon`), inheriting the token drift from [tokens-review.md §4](../tokens/tokens-review.md#4-status-colorsts-is-a-canonical-semantic-color-catalog-that-bypasses-the-ds).
|
||||
- File sizes (1100–1370 lines each) suggest each panel handles its own save pipeline, field-level error states, mutations, and recent-selection tracking (indirectly observed via imports from `recent-assignees`, `recent-projects`, etc.). Whether that logic is shareable is the real design question underneath the styling question.
|
||||
|
||||
## Related components and patterns
|
||||
|
||||
- Chrome slot: `PropertiesPanel` and the [`usePanel()` context](../../../ui/src/context/PanelContext.tsx)
|
||||
- Status coloring: [status-display.md](./status-display.md)
|
||||
- Inline field editing primitives in [`agent-config-primitives.tsx`](../components/agent-config-primitives.md) (`DraftInput`, `InlineField`, `ToggleField`, `ToggleWithNumber`)
|
||||
80
doc/design-system/patterns/entity-row.md
Normal file
80
doc/design-system/patterns/entity-row.md
Normal file
@@ -0,0 +1,80 @@
|
||||
# Entity Row
|
||||
|
||||
Row element for listing items in a scrollable collection (inbox, activity feed, list pages).
|
||||
|
||||
**Instances: 3.** `ActivityRow`, `EntityRow`, `IssueRow`.
|
||||
|
||||
> **Extraction-only pass.** Documents the family; does not prescribe the merge suggested in components-review. See [components-review.md §Likely duplicates #6](../components/components-review.md#6-row-family-activityrow--entityrow--issuerow).
|
||||
|
||||
## Instances
|
||||
|
||||
| Component | Lines | Uses | Role |
|
||||
|---|---|---|---|
|
||||
| `EntityRow.tsx` | 69 | 6 | Generic slot-based row (`leading` / `identifier` / `title` / `subtitle` / `trailing`) |
|
||||
| `ActivityRow.tsx` | 92 | 2 | Activity-event-specific — renders an `ActivityEvent` with actor identity + action verb + entity link |
|
||||
| `IssueRow.tsx` | 168 | 3 | Issue-specific — renders an `Issue` with `StatusIcon`, mobile/desktop slot variants, unread state, archive action |
|
||||
|
||||
## Composition
|
||||
|
||||
**`EntityRow`** — truly generic:
|
||||
|
||||
```tsx
|
||||
interface EntityRowProps {
|
||||
leading?: ReactNode;
|
||||
identifier?: string;
|
||||
title: string;
|
||||
subtitle?: string;
|
||||
trailing?: ReactNode;
|
||||
selected?: boolean;
|
||||
to?: string;
|
||||
onClick?: () => void;
|
||||
className?: string;
|
||||
}
|
||||
```
|
||||
|
||||
Renders as `<Link>` or `<div>` depending on click-ability. No status, no unread state, no mobile/desktop split — just slots.
|
||||
|
||||
**`ActivityRow`** — imports `Identity`, `IssueReferenceActivitySummary`. Specific to activity events.
|
||||
|
||||
**`IssueRow`** — imports `StatusIcon`. Props interface has 15 fields, most of them optional `ReactNode` slots:
|
||||
|
||||
```ts
|
||||
interface IssueRowProps {
|
||||
issue: Issue;
|
||||
issueLinkState?: unknown;
|
||||
selected?: boolean;
|
||||
mobileLeading?: ReactNode; // slot
|
||||
desktopMetaLeading?: ReactNode; // slot
|
||||
desktopLeadingSpacer?: boolean;
|
||||
mobileMeta?: ReactNode; // slot
|
||||
desktopTrailing?: ReactNode; // slot
|
||||
trailingMeta?: ReactNode; // slot
|
||||
titleSuffix?: ReactNode; // slot
|
||||
unreadState?: UnreadState | null;
|
||||
onMarkRead?: () => void;
|
||||
onArchive?: () => void;
|
||||
archiveDisabled?: boolean;
|
||||
className?: string;
|
||||
}
|
||||
```
|
||||
|
||||
Six slot props plus an untyped `issueLinkState` — this shape is nearly "`<EntityRow>` + issue-specific defaults."
|
||||
|
||||
## Observations
|
||||
|
||||
- `EntityRow` is used in 6 composites (not examined here — see composition graph), but **not used on any main list page**. Main list pages roll their own row rendering.
|
||||
- `IssueRow` is only used in 2 places: the `Inbox` page and `SwipeToArchive`. Not used on the `Issues` page (which uses `IssueColumns` + custom row rendering per column).
|
||||
- The gap is: `EntityRow` covers the "slot-based row" role generically, but the list pages don't adopt it.
|
||||
|
||||
## Variance
|
||||
|
||||
- **Mobile/desktop split lives only in `IssueRow`.** Whether other pages need it or have their own responsive handling is unknown from static analysis.
|
||||
- **Unread state lives only in `IssueRow`.** Inbox-specific; would not generalize.
|
||||
- **Activity-specific text (verb, link target) lives only in `ActivityRow`.** Legitimate domain specialization.
|
||||
|
||||
## Open questions
|
||||
|
||||
- Could `IssueRow` be expressed as `<EntityRow kind="issue" ... />`? Its slot shape already matches `EntityRow`'s role; the issue-specific bits (StatusIcon, unread state, archive) are add-ons, not structural differences.
|
||||
- Why do the main list pages (`Issues`, `Agents`, `Projects`, …) avoid `EntityRow`? If there's a good reason it should be documented; if not, adoption would retire a lot of per-page row code.
|
||||
|
||||
Answers to these are not required for this extraction. The pattern is noted as documentation-relevant.
|
||||
76
doc/design-system/patterns/finance-card.md
Normal file
76
doc/design-system/patterns/finance-card.md
Normal file
@@ -0,0 +1,76 @@
|
||||
# Finance / Accounting Card
|
||||
|
||||
Card surface for summarizing a financial or accounting slice: per-biller spend, per-kind spend, timeline totals, accounting-model totals.
|
||||
|
||||
**Instances: 5.** `BillerSpendCard`, `FinanceBillerCard`, `FinanceKindCard`, `FinanceTimelineCard`, `AccountingModelCard`.
|
||||
|
||||
> **Extraction-only pass.** Documents the family as it exists. Two specific items are flagged for the founder below; neither is auto-resolved.
|
||||
|
||||
## Instances
|
||||
|
||||
| Component | Lines | Uses | Data type (inferred) |
|
||||
|---|---|---|---|
|
||||
| `BillerSpendCard` | 145 | 1 | `CostByBiller` (+ `CostByProviderModel` breakdown) |
|
||||
| `FinanceBillerCard` | 44 | 1 | `FinanceByBiller` |
|
||||
| `FinanceKindCard` | (unread) | 1 | `FinanceByKind` (inferred) |
|
||||
| `FinanceTimelineCard` | (unread) | 1 | timeline roll-up |
|
||||
| `AccountingModelCard` | (unread) | **0** | (unknown — unused) |
|
||||
|
||||
## Composition (shared)
|
||||
|
||||
All five are composites that import the shadcn `Card` family (`Card`, `CardHeader`, `CardTitle`, `CardDescription`, `CardContent`, `CardFooter`) and render a titled card with a body.
|
||||
|
||||
`BillerSpendCard` additionally composes `QuotaBar`. That's the richer card in the family — quota visualization + provider breakdown + billing-type breakdown.
|
||||
|
||||
`FinanceBillerCard` is a plain summary card with a three-cell metric grid (`debits` / `credits` / `estimated`).
|
||||
|
||||
## Flag 1 — Likely true duplicate: `BillerSpendCard` ↔ `FinanceBillerCard`
|
||||
|
||||
Per the founder's directive in Stage 3, this pair is flagged for diff review.
|
||||
|
||||
- Both have "Biller" in the name.
|
||||
- Both summarize per-biller financials.
|
||||
- They consume **different** data models (`CostByBiller` vs `FinanceByBiller`), which suggests either (a) two different reporting concepts the names fail to distinguish, or (b) one of them is a stale parallel implementation of the other.
|
||||
- Line counts differ significantly (145 vs 44), but that could mean `BillerSpendCard` is the richer one *and* `FinanceBillerCard` is the slimmed-down version of the same concept.
|
||||
|
||||
**Action suggested (not taken here):** open both side by side and judge whether they represent two legitimately different reports, or whether one superseded the other and the older survived.
|
||||
|
||||
## Flag 2 — `AccountingModelCard` is unused
|
||||
|
||||
Zero imports across the codebase. Storybook-only coverage. See [components-review.md §Unused](../components/components-review.md#unused--low-signal-components). Delete or adopt.
|
||||
|
||||
## Composition template (common shape)
|
||||
|
||||
[INFER] From `FinanceBillerCard` (the cleanest example):
|
||||
|
||||
```
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<div flex between>
|
||||
<div>
|
||||
<CardTitle>{providerDisplayName(row.biller)}</CardTitle>
|
||||
<CardDescription>{eventCount}, {kindCount} kinds</CardDescription>
|
||||
</div>
|
||||
<div text-right>
|
||||
<div text-lg tabular-nums>{formatCents(row.netCents)}</div>
|
||||
<div uppercase tracking-wide muted>net</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<grid 3-column>
|
||||
<Cell label="debits" value={formatCents(...)} />
|
||||
<Cell label="credits" value={formatCents(...)} />
|
||||
<Cell label="estimated" value={formatCents(...)} />
|
||||
</grid>
|
||||
</CardContent>
|
||||
</Card>
|
||||
```
|
||||
|
||||
The recurring sub-element — a small metric cell with uppercase-tracked muted label + tabular-num value — is a micro-pattern worth noting. It appears here and (less formally) on list pages. Candidate for a `<MetricCell>` helper.
|
||||
|
||||
## Variance
|
||||
|
||||
- `BillerSpendCard` composes `QuotaBar`; the others don't.
|
||||
- Different data models (`CostByBiller` vs `FinanceByBiller` vs `FinanceByKind`) — confirm whether the shared shape is intentional convergence or a sign that they should share a common `FinanceCardRow` interface.
|
||||
- Per-card formatting helpers (`formatCents`, `formatTokens`, `providerDisplayName`) live in `@/lib/utils` — shared. Good.
|
||||
43
doc/design-system/patterns/index.md
Normal file
43
doc/design-system/patterns/index.md
Normal file
@@ -0,0 +1,43 @@
|
||||
# Patterns — Index
|
||||
|
||||
- **Generated:** 2026-04-21
|
||||
- **Repo SHA:** a26e1288b627e82c554445732c7d844648e6b5e1
|
||||
- **Scope:** `ui/` (@paperclipai/ui) — pages + components
|
||||
- **Review:** [patterns-review.md](./patterns-review.md)
|
||||
|
||||
## What a pattern is
|
||||
|
||||
A *pattern* is a composition of components that recurs across pages or across composites — something that has a shape, not just a component name. Pattern documents describe the shape and list the current instances. They do not prescribe refactors.
|
||||
|
||||
Patterns were identified by:
|
||||
1. Reading `_pages.json` and `_composition-graph.json` (Stage 2 scratch).
|
||||
2. Looking for import-set intersections across pages or composition-graph neighborhoods.
|
||||
3. Cross-referencing the duplicate families surfaced in [components-review.md §Likely duplicates](../components/components-review.md#likely-duplicates).
|
||||
4. Checking the Paperclip-domain checklist from the extraction skill (heartbeat, run-transcript row, agent card, approval gate, cost display, metadata grid).
|
||||
|
||||
## Pattern inventory
|
||||
|
||||
Sorted by instance count. Patterns with ≥3 instances get their own detail doc; pairs below the threshold are included per directive but called out.
|
||||
|
||||
| Pattern | Instances | Doc |
|
||||
|---|---|---|
|
||||
| [List page](./list-page.md) | 12 | ✓ |
|
||||
| [Detail page](./detail-page.md) | 8 | ✓ |
|
||||
| [Sidebar chrome](./sidebar-chrome.md) | 6 outer + 2 menus | ✓ |
|
||||
| [Finance / accounting card](./finance-card.md) | 5 | ✓ |
|
||||
| [Entity properties panel](./entity-properties-panel.md) | 4 entity-specific + 1 generic chrome | ✓ |
|
||||
| [Entity-creation dialog](./entity-creation-dialog.md) | 4 | ✓ |
|
||||
| [Status display](./status-display.md) | 3 components + 1 catalog | ✓ |
|
||||
| [Entity row](./entity-row.md) | 3 | ✓ |
|
||||
| [Subscription panel](./subscription-panel.md) | 2 (below threshold — documented) | ✓ |
|
||||
| [Quota display](./quota-display.md) | 2 (below threshold — documented) | ✓ |
|
||||
|
||||
See [patterns-review.md](./patterns-review.md) for:
|
||||
- Pattern opportunities that don't yet meet the threshold but are domain-relevant (heartbeat, run-transcript row, agent card, approval gate, cost display, metric cell, severity indicator).
|
||||
- Variance analysis across patterns.
|
||||
- Which patterns are safe to codify and which are blocked on upstream token or naming decisions.
|
||||
|
||||
## Scope notes
|
||||
|
||||
- **Out of this pass:** deep pattern extraction from the UX Lab pages (`IssueChatUxLab`, `RunTranscriptUxLab`, `InviteUxLab`). Those are acknowledged prototypes — pattern work there should follow explicit founder direction, not auto-extraction.
|
||||
- **Out of this pass:** the plugin SDK contract surface. Patterns emerge from the host `ui/`; if the SDK contract is fulfilled (see [components-review.md §Plugin SDK contract gap](../components/components-review.md#plugin-sdk-contract-gap)), those host implementations become additional pattern instances and this doc will need a re-run.
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user