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.
- 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.
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=selfhostedenables 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).
- Read this file.
- Quick start:
just install && just docker up && just db push && just dev registry find docs -name "*.md" | sortand open only the docs needed for the task.find idd -name "*.md" | sortif intent or active initiatives matter.just --listfor commands.- Read first:
docs/core/where-to-look.mddocs/core/deps.mddocs/process/methodology.md
- Read when relevant:
docs/core/architecture.mddocs/core/anti-patterns.mddocs/core/security.mddocs/process/testing-reference.mddocs/reference/packages/*
git log --oneline -20for recent decisions.- Scan relevant source dirs. If docs and code conflict, code wins.
- 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
Apps (apps/):
registry— TanStack Start registry app, docs, API, dashboard/admin Routes: TanStack Router file-based. API: Hono handlers insrc/api/. Server fns:src/query/. Auth: Better Auth insrc/lib/auth/.python-api— Python 6-stage security scanner
Packages (packages/):
cli—tankcommand: install, publish, scan, verifymcp-server— editor integration, CLI parityinternals-schemas— shared Zod schemas, types, contract constants. Pure, zero side effectsinternals-helpers— pure shared helpers
Non-obvious:
- Supabase = file storage only. DB = Drizzle ORM → PostgreSQL.
auth-schema.tsis 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_MODEgates on-prem features. Defaultcloud= 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.ts→src/generated/docs.json). Zero runtime Shiki/unified processing.
| 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 |
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.
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 PRTag 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-sdkandpublish-pypi-corerun in the GitHub Actions environmentpypi. 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 assetsRecovery — 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 stableOr via GitHub Actions UI: Actions → Create Release → enter version.
docker-nightly.yml— daily 4am UTC + push to main →ghcr.io/:nightlycli-nightly.yml— daily 4:30am UTC + push to main →@tankpkg/cli@nightly- Vercel — every push to main → nightly.tankpkg.dev
| 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 |
- IT whitelists
ghcr.io/tankpkg/tank-web+tank-scannerimages. - Download
docker-compose.onprem.yml, create.env,docker compose up. - Admin opens
/setup→ 7-step wizard configures everything. - Users install CLI from
/install-cli(binary pre-configured with instance URL).
| 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 |
safeParse(), neverparse().- No cross-package imports outside
@internals/*. - Supabase is not the database.
- Auth belongs in layouts/guards, not page-only checks.
- No refactoring during bugfixes.
- Browser tests must verify actual DOM before reusing selectors.
www.tankpkg.devnottankpkg.dev— Cloudflare redirects apex to www. Auth Origin must match.- TanStack Router generated-types errors for new routes resolve at build time. Not real errors.
auth-schema.tsis auto-generated. Never edit.ci-merge-stablemay fail if branch protection rulesets block bot pushes. Merge manually:git checkout stable && git merge main && git push origin stable.- Admin role is NOT cached — every
isAdmin()check queries the DB. Consider Redis caching for high-traffic. - Legacy API keys (pre-scope enforcement) have empty scopes → treated as unrestricted. Intentional backward-compat.
- Moderation status is append-only. To change status, insert new row. Queries always fetch latest by
createdAt. - S3 provider creates TWO clients:
internalClient(MinIO endpoint) andpublicClient(CDN/public). Signed URLs use public, uploads use internal. - Hono middleware order matters. Routes mounted BEFORE
requireAdmin()are unprotected. Always.use('/admin/*', ...)before.route('/admin', ...). - Non-blocking side effects (GitHub API, email sends) fail silently. Client sees success even if side effect failed.
- Env vars are validated at startup via Zod. If validation fails, process exits. No partial startup.
TANK_MODEis checked as rawprocess.envstring, not via the parsedenvobject. Runtime changes work.- Filesystem storage is single-instance only. Not suitable for multi-replica deployments.
- S3 bucket auto-creation silently ignores "BucketAlreadyOwnedByYou" errors. Can mask permission issues.
- TanStack Router
redirect()must be thrown, not returned. Returning it does nothing. - Docs are pre-rendered — changes to
public/docs/*.mdrequirebun run scripts/build-docs.ts(auto-runs inbun run build).
- 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/canprefix. - 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.
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
justrecipe first, then call it from YAML. Never inline bash in workflows.
- 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.
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.
| 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 |
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-tightfor primary section headings. - Always
mt-1 text-muted-foregroundon the description line below. - No
font-displayin 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>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-4as default inside<CardContent>. - Use
space-y-6when the card has denser content or multiple sub-sections. - Default padding comes from shadcn Card (
py-6on Card,px-6on CardContent).
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).
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 is default. Test all UI in dark mode first.
- Glow effects (
--glow-sm/md/lg/text) only active in dark mode. matrix-gridbackground visible in both modes (stronger in dark).
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.
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.
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>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>;| 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 |
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>;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>;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>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-mediumon key identifier columns. - Wrap the table inside a
<Card>with a section label above it.
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.
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.
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/*.
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/*.
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>.
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 page title (
h1): Usefont-display font-semibold tracking-tight. Never usefont-sansfor article titles. - Section headings (
H2,H3): Alsofont-display font-semibold tracking-tight. Keep sizes big (text-2xl/text-3xlfor H2,text-xlfor 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.
- 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 >= radiusfor edge elements. - Add
className="max-w-full"to every<svg>. - Use
fontFamily="'Space Grotesk', sans-serif"andfontWeight="600"for all SVG text. - Standard SVG color palette:
#10b981for highlights/brand,currentColorfor primary,#64748bfor secondary,#16a34afor success,#dc2626for errors. - Center glyph clusters with
textAnchor="middle"and computed positions.
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-shellused for page-level max-width containers - Muted text uses
text-muted-foregroundortext-ink-soft - No
font-displayin body text — only headings - Responsive: no bare
grid-cols-Nwithout mobile breakpoint - Errors use
text-destructivewithrole="alert" - Copy buttons use
useClipboardhook — no custom clipboard logic - Mode selectors use Button toggles; content tabs use shadcn
Tabs - Progress bars use the shared
<ProgressBar>component or MagicUI equivalent
Every feature must have:
idd/modules/<capability>/INTENT.md— what and why, acceptance criteria.bdd/features/— Gherkin scenarios for behavioral verification.- Code implementation.
- 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 layeridd/modules/<capability>/INTENT.mdidd/active/for cross-cutting initiatives
bdd/— executable behavior layerbdd/features/system/bdd/features/browser/shared/bdd/features/browser/tanstack/
e2e/— full-stack regression layere2e/cli/,e2e/api/,e2e/admin/,e2e/onprem/
docs/— reference, process, product, and ops docs
- 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.