Skip to content

Latest commit

 

History

History
793 lines (600 loc) · 35 KB

File metadata and controls

793 lines (600 loc) · 35 KB

Tank

Security-first package manager for AI agent skills. Monorepo: CLI + MCP server + web registries + shared schemas + Python scanner. Bun workspaces. Born from ClawHavoc — 341 malicious skills, 12% of a major marketplace.

AGENTS.md and CLAUDE.md are symlinked. This file is your system prompt. Loads every context window. Every token has a cost. Keep it holy — concise, tight, agents-only.

Philosophy

  • This file + docs/ + idd/ = memory. Fresh context = amnesia. Recover here first, then open only what you need.
  • docs/ holds reference/process truth. idd/ holds intent and active initiatives.
  • Tooling-enforced rules (Biome, tsconfig, EditorConfig) do not belong here.
  • Capabilities > file paths. git log, just --list, and source code are living docs.
  • Be extremely concise — sacrifice grammar, not information. Every interaction, plan, commit, doc.

Current State

  • apps/registry = TanStack Start registry app (UI + API). Full-featured: home, skills browse/detail/search, docs (pre-rendered), login (email + GitHub + OIDC), CLI auth, publish API, admin panel (users/packages/audit CRUD), tokens, orgs.
  • On-prem: TANK_MODE=selfhosted enables setup wizard, filesystem storage, CLI binary serving. Cloud mode (TANK_MODE=cloud, default) disables all on-prem features.
  • Two deploy channels: stable (www.tankpkg.dev) and nightly (nightly.tankpkg.dev).

Context Recovery

  1. Read this file.
  2. Quick start: just install && just docker up && just db push && just dev registry
  3. find docs -name "*.md" | sort and open only the docs needed for the task.
  4. find idd -name "*.md" | sort if intent or active initiatives matter.
  5. just --list for commands.
  6. Read first:
    • docs/core/where-to-look.md
    • docs/core/deps.md
    • docs/process/methodology.md
  7. Read when relevant:
    • docs/core/architecture.md
    • docs/core/anti-patterns.md
    • docs/core/security.md
    • docs/process/testing-reference.md
    • docs/reference/packages/*
  8. git log --oneline -20 for recent decisions.
  9. Scan relevant source dirs. If docs and code conflict, code wins.

Roles

  • Software Architect — boundaries, package ownership, auth/data/storage flow, failure surfaces.
  • TypeScript/Bun Engineer — CLI, MCP, shared schemas, web routes, build/test ergonomics.
  • QA/Test Engineer — IDD/BDD/TDD/E2E coverage, fixtures, real infra, target-specific regressions.

Full catalog: docs/core/roles.md

Architecture

Apps (apps/):

  • registry — TanStack Start registry app, docs, API, dashboard/admin Routes: TanStack Router file-based. API: Hono handlers in src/api/. Server fns: src/query/. Auth: Better Auth in src/lib/auth/.
  • python-api — Python 6-stage security scanner

Packages (packages/):

  • clitank command: install, publish, scan, verify
  • mcp-server — editor integration, CLI parity
  • internals-schemas — shared Zod schemas, types, contract constants. Pure, zero side effects
  • internals-helpers — pure shared helpers

Non-obvious:

  • Supabase = file storage only. DB = Drizzle ORM → PostgreSQL.
  • auth-schema.ts is generated by better-auth. Never edit it.
  • MCP shares auth with CLI via ~/.tank/config.json.
  • CLI/web/mcp-server do not import each other. Shared TS code only through @internals/*.
  • TANK_MODE gates on-prem features. Default cloud = safe for Vercel. selfhosted = wizard + migrations + CLI serving.
  • /api/setup/* returns 404 in cloud mode. POST endpoints locked (403) after first setup.
  • Docs are pre-rendered at build time (scripts/build-docs.tssrc/generated/docs.json). Zero runtime Shiki/unified processing.

Deployment & Versioning

Environments

Environment Web Scanner CLI Python SDK Docker
Stable www.tankpkg.dev scanner.tankpkg.dev npm i -g @tankpkg/cli pip install tank-sdk ghcr.io/tankpkg/tank-web:latest
Nightly nightly.tankpkg.dev nightly-scanner.tankpkg.dev npm i -g @tankpkg/cli@nightly (see follow-up) ghcr.io/tankpkg/tank-web:nightly
On-prem customer domain customer scanner served from web container pip install tank-sdk docker-compose.onprem.yml

Branches

  • main — development. Every push deploys to nightly.tankpkg.dev (Vercel preview).
  • stable — production. Only updated via release. Deploys to www.tankpkg.dev (Vercel production).
  • release/vX.Y.Z — permanent snapshot per version. Never deleted.

Release Process (Step-by-Step)

Prerequisites: All changes merged to main via PRs (main has branch protection — no direct pushes).

# 1. Ensure main is up to date
git checkout main && git pull origin main

# 2. Bump version in all packages (updates 6 package.json + Helm Chart.yaml)
just bump 0.11.0

# 3. Commit + PR the version bump (can't push directly to main)
git checkout -b chore/bump-0.11.0
git add -A && git commit -m "chore: bump version to 0.11.0"
git push -u origin chore/bump-0.11.0
gh pr create --title "chore: bump version to 0.11.0" --base main
gh pr merge --squash --admin --delete-branch

# 4. Sync main, create release branch + tag
git checkout main && git pull origin main
just ci-release-tag 0.11.0
#   → creates branch release/v0.11.0
#   → creates + pushes tag v0.11.0
#   → tag push triggers CI workflows automatically

# 5. Merge main → stable (deploys to www.tankpkg.dev via Vercel)
just ci-merge-stable
#   If pre-push hook fails: git push --no-verify origin stable
#   If branch protection blocks: merge manually via PR

Tag push cascades (automatic):

v0.11.0 tag push
├─ release.yml  → CLI binaries + GitHub Release + npm + Homebrew + .deb + PyPI (~4-8 min)
│   ├─ build (matrix: linux-x64/arm64, darwin-x64/arm64, windows-x64)
│   ├─ publish-npm            → `npm publish` to @tankpkg/cli@X.Y.Z
│   ├─ release                → GitHub Release with binaries + .deb + SHA256SUMS
│   ├─ update-homebrew        → PR to homebrew-tap repo
│   ├─ build-pypi-sdk         → build tank-sdk wheel + sdist (pure Python)
│   ├─ build-pypi-core-wheels → build tank-core wheels (matrix: 5 platforms via maturin)
│   ├─ build-pypi-core-sdist  → build tank-core sdist
│   ├─ publish-pypi-sdk       → Trusted Publishing → pypi.org/project/tank-sdk (env: pypi)
│   └─ publish-pypi-core      → Trusted Publishing → pypi.org/project/tank-core (env: pypi)
└─ publish.yml  → Docker images + Helm chart (~15-20 min)
    ├─ docker-tank-scanner  (~1 min)
    ├─ docker-tank-web      (~15 min, multi-arch + CLI binary compilation)
    ├─ helm-publish
    └─ smoke-test-compose   (runs after docker-tank-web)

All artifacts share the same version from the same tag push. CLI, npm, Docker, Helm, tank-sdk, tank-core — all 0.11.0.

PyPI publish notes:

  • publish-pypi-sdk and publish-pypi-core run in the GitHub Actions environment pypi. If the environment has "Required reviewers" enabled, the jobs pause until an approver clicks "Approve and deploy" in the Actions UI.
  • Authentication uses PyPI Trusted Publishing via OIDC — no long-lived tokens. Pending publishers must be registered on pypi.org before the first release (owner: tankpkg, repo: tank, workflow: release.yml, environment: pypi).
  • Filenames on PyPI are immutable. If a PyPI publish fails partway, cut a new patch version — do not delete the tag and re-push (that recipe does not work for PyPI).

CLI release artifacts (attached to GitHub Release):

Asset Description
tank-linux-x64 (+.tar.gz) Linux amd64 binary
tank-linux-arm64 (+.tar.gz) Linux arm64 binary
tank-darwin-x64 (+.tar.gz) macOS Intel binary
tank-darwin-arm64 (+.tar.gz) macOS Apple Silicon binary
tank-windows-x64.exe Windows binary
tank_X.Y.Z_amd64.deb Debian package (amd64)
tank_X.Y.Z_arm64.deb Debian package (arm64)
SHA256SUMS Checksums for all assets

CLI install channels:

Channel Command Source
npm npm i -g @tankpkg/cli release.yml → publish-npm
Homebrew brew install tankpkg/tap/tank release.yml → update-homebrew
Binary Download from GitHub Release release.yml → release
.deb dpkg -i tank_X.Y.Z_amd64.deb release.yml → release
On-prem /install-cli page on instance publish.yml → baked into Docker image

Python SDK install channels:

Channel Command Source
PyPI pip install tank-sdk release.yml → publish-pypi-sdk
PyPI (native) pip install "tank-sdk[native]" pulls tank-core (Rust-accelerated)
PyPI (core) pip install tank-core release.yml → publish-pypi-core

Docker image tags produced (semver pattern strips v prefix):

Registry Tags pushed
ghcr.io/tankpkg/tank-web 0.11.0, 0.11, 0, latest
docker.io/tankpkg/tank-web 0.11.0, 0.11, 0, latest

Monitoring a release:

gh run list --workflow publish.yml --limit 3        # check Docker status
gh run list --workflow release.yml --limit 3        # check CLI status
gh run watch <run-id> --exit-status                 # live tail
gh run view <run-id> --log-failed                   # debug failures
docker manifest inspect ghcr.io/tankpkg/tank-web:0.11.0  # verify image exists
gh release view v0.11.0 --json assets --jq '.assets[].name'  # verify CLI assets

Recovery — if publish.yml fails:

# 1. Fix the issue on main (via PR)
# 2. Delete old tag, re-tag on fixed commit, re-push
git checkout main && git pull origin main
git tag -d v0.11.0 && git push origin :refs/tags/v0.11.0
git tag v0.11.0 && git push origin v0.11.0
# 3. Merge fix to stable too
git checkout stable && git merge main --no-edit && git push --no-verify origin stable

Or via GitHub Actions UI: Actions → Create Release → enter version.

Nightly Builds (automatic)

  • docker-nightly.yml — daily 4am UTC + push to main → ghcr.io/:nightly
  • cli-nightly.yml — daily 4:30am UTC + push to main → @tankpkg/cli@nightly
  • Vercel — every push to main → nightly.tankpkg.dev

Environment Wiring (Vercel)

Env var Production Preview Development
APP_URL www.tankpkg.dev nightly.tankpkg.dev localhost:5555
BETTER_AUTH_URL www.tankpkg.dev nightly.tankpkg.dev localhost:5555
PYTHON_API_URL scanner.tankpkg.dev nightly-scanner.tankpkg.dev localhost:8000

On-prem Flow (no git clone)

  1. IT whitelists ghcr.io/tankpkg/tank-web + tank-scanner images.
  2. Download docker-compose.onprem.yml, create .env, docker compose up.
  3. Admin opens /setup → 7-step wizard configures everything.
  4. Users install CLI from /install-cli (binary pre-configured with instance URL).

Docker Image Tags

Tag Source Use
latest v* tag Production on-prem
v0.10.0 specific version Pinned deployments
nightly latest main Testing
sha-abc1234 specific commit Debugging

Gotchas

  1. safeParse(), never parse().
  2. No cross-package imports outside @internals/*.
  3. Supabase is not the database.
  4. Auth belongs in layouts/guards, not page-only checks.
  5. No refactoring during bugfixes.
  6. Browser tests must verify actual DOM before reusing selectors.
  7. www.tankpkg.dev not tankpkg.dev — Cloudflare redirects apex to www. Auth Origin must match.
  8. TanStack Router generated-types errors for new routes resolve at build time. Not real errors.
  9. auth-schema.ts is auto-generated. Never edit.
  10. ci-merge-stable may fail if branch protection rulesets block bot pushes. Merge manually: git checkout stable && git merge main && git push origin stable.
  11. Admin role is NOT cached — every isAdmin() check queries the DB. Consider Redis caching for high-traffic.
  12. Legacy API keys (pre-scope enforcement) have empty scopes → treated as unrestricted. Intentional backward-compat.
  13. Moderation status is append-only. To change status, insert new row. Queries always fetch latest by createdAt.
  14. S3 provider creates TWO clients: internalClient (MinIO endpoint) and publicClient (CDN/public). Signed URLs use public, uploads use internal.
  15. Hono middleware order matters. Routes mounted BEFORE requireAdmin() are unprotected. Always .use('/admin/*', ...) before .route('/admin', ...).
  16. Non-blocking side effects (GitHub API, email sends) fail silently. Client sees success even if side effect failed.
  17. Env vars are validated at startup via Zod. If validation fails, process exits. No partial startup.
  18. TANK_MODE is checked as raw process.env string, not via the parsed env object. Runtime changes work.
  19. Filesystem storage is single-instance only. Not suitable for multi-replica deployments.
  20. S3 bucket auto-creation silently ignores "BucketAlreadyOwnedByYou" errors. Can mask permission issues.
  21. TanStack Router redirect() must be thrown, not returned. Returning it does nothing.
  22. Docs are pre-rendered — changes to public/docs/*.md require bun run scripts/build-docs.ts (auto-runs in bun run build).

Code Standards

Clean Code Rules

  • Functions ≤ 20 lines, ≤ 3 params, ≤ 3 nesting levels.
  • No as any, @ts-ignore, @ts-expect-error. Ever.
  • No empty catch blocks. Handle or rethrow.
  • Guard clauses over nested if/else.
  • Name: variables = nouns, functions = verbs, booleans = is/has/can prefix.
  • Abstract on 3rd occurrence, not 1st. YAGNI > DRY.
  • Profile before optimizing. But avoid obvious N+1, O(n²) on unbounded data, allocation in hot loops.

Scripting via Just

All automation lives in the justfile. CI workflows are thin YAML wrappers around just recipes.

  • just ci-* recipes = CI logic. Testable locally.
  • just build *, just test *, just lint * = dev commands.
  • just docker *, just onprem * = infra commands.
  • New automation → add a just recipe first, then call it from YAML. Never inline bash in workflows.

Code Review

  • Delegate reviews to Oracle agent for security/architecture concerns.
  • Every PR: typecheck clean (tsc --noEmit), lint clean, tests pass.
  • Security-sensitive changes (auth, crypto, setup API) → Oracle review mandatory.

Design System

UI Component Stack

Two layers — shadcn/ui for core app primitives, MagicUI for animated/marketing components.

Layer What Import path Use for
shadcn/ui Button, Card, Input, Label, Table, Tabs, Badge, Dialog, Separator, Command ~/components/ui/* All app UI: admin, dashboard, forms, CRUD, data tables
MagicUI Marquee, Bento Grid, Number Ticker, Terminal, Shimmer Button, Animated List, Border Beam, Particles, Globe, etc. ~/components/ui/* Public-facing pages: home, landing, skill showcase, marketing sections

Both layers share the same ~/components/ui/ directory. MagicUI uses the shadcn CLI registry — components are added per-component, not as a monolithic install. The distinction is usage convention, not directory structure.

Rules:

  • Reuse existing shadcn primitives before creating custom components.
  • Use MagicUI components only for public/marketing surfaces — never in admin panels or forms.
  • Check MagicUI catalogue at https://magicui.design/docs/components before building custom animations.
  • Only fall back to custom HTML/CSS when the component genuinely does not exist in either library.
  • Do not introduce a third component system unless explicitly requested.

Fonts

Token Font Tailwind Usage
Body Inter Variable font-sans Body text, labels, buttons, form controls
Display/Headings Space Grotesk Variable font-display Page headings, hero text, section titles
Monospace JetBrains Mono Variable font-mono Code blocks, terminal output, CLI commands

Section Labels

The standard section label (e.g. "API Tokens", "Settings", "Packages") uses this exact pattern everywhere:

<h2 className="font-display text-xl font-semibold tracking-tight">Section Title</h2>
<p className="mt-1 text-muted-foreground">Description text.</p>
  • Always font-display + text-xl + font-semibold tracking-tight for primary section headings.
  • Always mt-1 text-muted-foreground on the description line below.
  • No font-display in body text — only headings.

Sub-labels (smaller metadata labels like field hints, column headers):

<span className="text-xs font-medium uppercase tracking-tight text-muted-foreground">Field Hint</span>

Card Containers

Every tool/screen section is wrapped in a shadcn <Card> + <CardContent>. Never use raw bordered <div> elements to recreate card-like containers.

Standard CardContent layout:

<Card>
  <CardHeader>
    <CardTitle>Section</CardTitle>
    <CardDescription>Description text.</CardDescription>
  </CardHeader>
  <CardContent className="space-y-4">{/* content */}</CardContent>
</Card>
  • Use space-y-4 as default inside <CardContent>.
  • Use space-y-6 when the card has denser content or multiple sub-sections.
  • Default padding comes from shadcn Card (py-6 on Card, px-6 on CardContent).

Borders and Shadows

All major containers use the shadcn/Tailwind border system. Do not invent custom border/shadow combos.

Element Border Shadow
Card border (via shadcn default) shadow-sm
Button border border-transparent (via CVA) none
Badge border (via shadcn default) none
Input border border-input none
Dialog border shadow-lg
Internal dividers border-border none
Empty state placeholders border border-dashed border-muted-foreground/25 none

Never use hard pixel shadows like shadow-[4px_4px_0_#000]. Use Tailwind's shadow scale (shadow-sm, shadow-md, shadow-lg).

Color Palette

Tank uses oklch-based CSS variables with a green hue channel (155). Dark mode is default.

Token Light Dark Usage
--background Near-white green tint Void black oklch(0.05) Page background
--card Pure white Dark green tint oklch(0.08) Card surfaces
--foreground Near-black Light green tint oklch(0.93) Primary text
--muted-foreground Mid gray Mid gray Secondary text (text-muted-foreground)
--primary Near-black Near-white Buttons, links, emphasis
--border Light green tint Dark green tint All borders
--destructive Red Red Error states

Brand colors:

Token Value Usage
--tank-green #00ff41 Matrix green — glow effects (dark mode only)
--tank-green-ui #10b981 Emerald — UI accents, badges, success states
text-ink-soft maps to --muted-foreground Muted text shorthand
bg-muted maps to --muted Card/section backgrounds

Do not introduce new background colors. Use bg-card, bg-muted, or bg-background.

Dark Mode

Dark mode is default. Test all UI in dark mode first.

  • Glow effects (--glow-sm/md/lg/text) only active in dark mode.
  • matrix-grid background visible in both modes (stronger in dark).

Buttons

Use shadcn <Button> for all interactive controls. Available variants: default, outline, secondary, ghost, destructive, link. Sizes: xs, sm, default, lg, icon, icon-xs, icon-sm, icon-lg.

import { Button } from "~/components/ui/button";
<Button variant="outline" size="sm">
  Action
</Button>;

Use MagicUI buttons (ShimmerButton, RainbowButton) only in hero/CTA sections on public pages.

Inline Action Buttons

For small action buttons that sit next to section labels (e.g. "Import JSON", "Expand All", "Import CSV"), use <Button size="sm" variant="secondary">:

<Button size="sm" variant="secondary" onClick={handleAction}>
  Import JSON
</Button>

Do not use raw <button> elements. Always use the shadcn <Button> component.

Copy Output Buttons

Use the useClipboard hook from ~/lib/useClipboard — never roll your own clipboard logic.

Pattern:

import { useClipboard } from "~/lib/useClipboard";

const { copiedLabel, copy } = useClipboard();

<div className="flex items-center gap-2">
  <h2 className="font-display text-xl font-semibold tracking-tight">Output</h2>
  <Button size="sm" variant="secondary" onClick={() => copy("Output", outputValue)}>
    {copiedLabel === "Output" ? "Copied!" : "Copy"}
  </Button>
</div>;

When a tool has multiple copyable outputs, add a copy button next to each value using distinct labels:

<Button size="sm" variant="secondary" onClick={() => copy("HEX", hexValue)}>
  {copiedLabel === "HEX" ? "Copied!" : "Copy"}
</Button>

Mode / Tab Selectors

Two distinct patterns — mode selectors and content tabs. Do not mix them.

Mode selectors (Light/Dark, format pickers, encode/decode) — use Button toggles:

<div className="flex flex-wrap gap-2">
  <Button variant={isSelected ? "default" : "secondary"} size="sm" onClick={() => setMode(value)}>
    Light
  </Button>
</div>

Content tabs (switching between entirely different content panels) — use the shadcn Tabs component:

import { Tabs, TabsList, TabsTrigger, TabsContent } from "~/components/ui/tabs";

<Tabs defaultValue="encode" className="w-full">
  <TabsList>
    <TabsTrigger value="encode">Encode</TabsTrigger>
    <TabsTrigger value="decode">Decode</TabsTrigger>
  </TabsList>
  <TabsContent value="encode">{/* encode content */}</TabsContent>
  <TabsContent value="decode">{/* decode content */}</TabsContent>
</Tabs>;

Spacing

Context Pattern
Page max-width container tank-shell
Page vertical spacing py-10
Between top-level cards space-y-8 or gap-6 in grid
Inside CardContent space-y-4 (standard)
Section label to content below mt-1 on description, content follows naturally
Controls row below content mt-3 on the wrapper div
Multi-column layout gap gap-6
Inline button / control gap gap-2

Cards

Use shadcn <Card> for all content containers. Never use raw bordered <div> elements.

import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "~/components/ui/card";
<Card>
  <CardHeader>
    <CardTitle>Title</CardTitle>
    <CardDescription>Subtitle</CardDescription>
  </CardHeader>
  <CardContent>{/* content */}</CardContent>
</Card>;

Input / Output Card Layout

The standard two-card layout for tools:

{
  /* Input Card */
}
<Card>
  <CardHeader>
    <CardTitle>Input</CardTitle>
  </CardHeader>
  <CardContent className="space-y-4">
    <Textarea placeholder="Paste content here..." />
    <div className="mt-3 flex flex-wrap items-center gap-2">
      <Button>Convert</Button>
    </div>
  </CardContent>
</Card>;

{
  /* Output Card */
}
<Card>
  <CardHeader>
    <CardTitle>Output</CardTitle>
  </CardHeader>
  <CardContent className="space-y-4">{/* results or empty state */}</CardContent>
</Card>;

Read-Only Output Areas

For text/code output displayed in a textarea:

<Textarea value={result} readOnly className="min-h-[200px] font-mono" />

For pre-formatted output inside an HTML container:

<div className="rounded-lg border bg-muted/50 p-4 font-mono text-sm">{content}</div>

Data Tables

Use shadcn <Table> for all tabular data. Never use raw <table> elements.

import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "~/components/ui/table";

<Table>
  <TableHeader>
    <TableRow>
      <TableHead className="text-xs font-medium uppercase tracking-wide">Year</TableHead>
      <TableHead className="text-right text-xs font-medium uppercase tracking-wide">Amount</TableHead>
    </TableRow>
  </TableHeader>
  <TableBody>
    {rows.map((row) => (
      <TableRow key={row.id}>
        <TableCell className="font-medium">{row.year}</TableCell>
        <TableCell className="text-right">{row.amount}</TableCell>
      </TableRow>
    ))}
  </TableBody>
</Table>;

Key rules:

  • Header text: text-xs font-medium uppercase tracking-wide text-muted-foreground.
  • Body row hover: default shadcn hover styling.
  • First column bold: use font-medium on key identifier columns.
  • Wrap the table inside a <Card> with a section label above it.

Error and Status Display

Errors — always use font-medium and role="alert":

<p role="alert" className="mt-2 text-sm font-medium text-destructive">
  {error}
</p>

Warnings (upload rejections, validation):

<p className="mt-2 text-sm font-medium text-amber-600 dark:text-amber-400">{warning}</p>

Empty state messages:

<p className="text-sm text-muted-foreground">No output yet.</p>

Progress — use the shared <ProgressBar> component or MagicUI's Animated Circular Progress Bar for public pages.

Contextual Help Tooltips

import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "~/components/ui/tooltip";

<div className="flex items-center gap-1.5">
  <Label htmlFor="some-field">Field Label</Label>
  <TooltipProvider delayDuration={200}>
    <Tooltip>
      <TooltipTrigger asChild>
        <button
          type="button"
          aria-label="More info"
          className="flex h-4 w-4 items-center justify-center rounded-full border text-[10px] font-medium leading-none text-muted-foreground">
          ?
        </button>
      </TooltipTrigger>
      <TooltipContent side="right" className="max-w-56 text-xs leading-relaxed">
        Short explanation of what this field means and why it matters.
      </TooltipContent>
    </Tooltip>
  </TooltipProvider>
</div>;

Install if missing: npx shadcn@latest add tooltip.

shadcn/ui Primitives (installed)

Button, Card (+Header/Title/Description/Content/Footer/Action), Input, Label, Table (+Header/Body/Row/Head/Cell), Tabs (+List/Trigger/Content), Badge, Dialog (+Content/Header/Title/Description/Footer/Trigger), Separator, Command (+Dialog/Input/List/Empty/Group/Item)

Import from ~/components/ui/*.

shadcn/ui Primitives (install as needed)

Checkbox, Select (+Trigger/Content/Item/Value), Slider, Tooltip (+Provider/Trigger/Content), Breadcrumb (+List/Item/Link/Separator), Textarea, Switch, Popover, DropdownMenu, ScrollArea, Progress

Install via npx shadcn@latest add <component>. Import from ~/components/ui/*.

MagicUI Components (use for public pages)

Layout & Showcase: Bento Grid, Marquee, Dock, Animated List, File Tree, Terminal Text animations: Text Animate, Typing Animation, Number Ticker, Aurora Text, Hyper Text, Morphing Text, Sparkles Text, Animated Gradient Text, Animated Shiny Text Effects: Border Beam, Shine Border, Magic Card, Particles, Confetti, Meteors, Animated Beam, Blur Fade Backgrounds: Animated Grid Pattern, Flickering Grid, Retro Grid, Dot Pattern, Ripple, Light Rays Buttons: Shimmer Button, Rainbow Button, Ripple Button, Pulsating Button, Shiny Button, Interactive Hover Button Device mocks: Safari, iPhone, Android Other: Globe, Icon Cloud, Orbiting Circles, Avatar Circles, Scroll Progress, Code Comparison, Neon Gradient Card

Import from ~/components/ui/*. Install via npx shadcn@latest add @magicui/<component>.

Mobile Responsiveness

Every new component must be mobile-first.

Rule 1: Always add min-w-0 to grid and flex children

// WRONG
<div className="grid grid-cols-2 gap-4">
  <div>...</div>
</div>

// CORRECT
<div className="grid grid-cols-2 gap-4">
  <div className="min-w-0">...</div>
</div>

Rule 2: Always use responsive breakpoints on multi-column grids

// WRONG
<div className="grid grid-cols-2 gap-4">

// CORRECT
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">

Rule 3: Guard fixed-width containers with max-w-full

// WRONG
<div className="w-80 border bg-card">

// CORRECT
<div className="w-80 max-w-[calc(100vw-2rem)] border bg-card">

Rule 4: Horizontal tab bars and button rows must wrap or scroll

// WRONG
<div className="flex gap-2">

// CORRECT (wrap)
<div className="flex flex-wrap gap-2">

// CORRECT (scroll)
<div className="flex gap-2 overflow-x-auto">

Article Visual Design Rules

  • Article page title (h1): Use font-display font-semibold tracking-tight. Never use font-sans for article titles.
  • Section headings (H2, H3): Also font-display font-semibold tracking-tight. Keep sizes big (text-2xl/text-3xl for H2, text-xl for H3).
  • Inline code: Light style only — rounded border border-border bg-muted px-1.5 py-0.5 font-mono text-sm.
  • Code blocks: Full treatment — rounded-lg border bg-card p-4 font-mono text-sm shadow-sm.

Inline SVG Diagram Rules

  • Always wrap SVG diagrams in <div className="my-6 flex justify-center overflow-x-auto">.
  • SVG content must be visually centered within its own viewBox — mirror padding on both sides.
  • Compute height dynamically — never hardcode H.
  • Ensure padding >= radius for edge elements.
  • Add className="max-w-full" to every <svg>.
  • Use fontFamily="'Space Grotesk', sans-serif" and fontWeight="600" for all SVG text.
  • Standard SVG color palette: #10b981 for highlights/brand, currentColor for primary, #64748b for secondary, #16a34a for success, #dc2626 for errors.
  • Center glyph clusters with textAnchor="middle" and computed positions.

Design Checklist

Before completing any UI task:

  • Dark mode tested (it's the default)
  • All containers use shadcn <Card>, not raw bordered divs
  • All buttons use shadcn <Button>, not raw <button>
  • All tables use shadcn <Table>, not raw <table>
  • All selects use shadcn <Select>, not raw <select>
  • All checkboxes use shadcn <Checkbox>, not raw <input type="checkbox">
  • MagicUI components only on public-facing pages, not admin/dashboard
  • No custom background colors — use bg-card, bg-muted, bg-background
  • tank-shell used for page-level max-width containers
  • Muted text uses text-muted-foreground or text-ink-soft
  • No font-display in body text — only headings
  • Responsive: no bare grid-cols-N without mobile breakpoint
  • Errors use text-destructive with role="alert"
  • Copy buttons use useClipboard hook — no custom clipboard logic
  • Mode selectors use Button toggles; content tabs use shadcn Tabs
  • Progress bars use the shared <ProgressBar> component or MagicUI equivalent

Agent Infra

IDD/BDD (keep populated)

Every feature must have:

  1. idd/modules/<capability>/INTENT.md — what and why, acceptance criteria.
  2. bdd/features/ — Gherkin scenarios for behavioral verification.
  3. Code implementation.
  4. Tests (unit + E2E where applicable).

When adding features: write INTENT.md first, then Gherkin, then code. When fixing bugs: skip IDD, write failing test, fix, verify.

  • idd/ — intent layer
    • idd/modules/<capability>/INTENT.md
    • idd/active/ for cross-cutting initiatives
  • bdd/ — executable behavior layer
    • bdd/features/system/
    • bdd/features/browser/shared/
    • bdd/features/browser/tanstack/
  • e2e/ — full-stack regression layer
    • e2e/cli/, e2e/api/, e2e/admin/, e2e/onprem/
  • docs/ — reference, process, product, and ops docs

Workflow

  • Conventional commits: feat:, fix:, docs:, chore:, test:, refactor:
  • Test commands: just test cli, just test bdd, just test e2e.
  • Git history is documentation. Make commits explain decisions, not only files.
  • Methodology: IDD → BDD → TDD → E2E
  • Plans must be self-contained enough for a fresh-context agent.
  • End plans with unresolved questions.