Capsule turns a physical wardrobe into a searchable, AI-assisted inventory — so getting dressed, avoiding duplicate purchases, and rediscovering unused clothes stops depending on memory. This repository is the TypeScript monorepo for the product: a Next.js web app backed by Postgres, built on a Clean Architecture core, with a standalone design system.
Status: pre-product. The monorepo tooling, Docker environment, and design system below are real and working. The wardrobe app itself — auth, garment inventory, outfit suggestions — has not been built yet. The Project Map below shows exactly what exists vs. what's a placeholder.
People own more clothing than they actively use — most wardrobes go largely unworn most of the time — which shows up as morning decision fatigue, duplicate or incompatible purchases, and the feeling of "nothing to wear" despite a full closet. Capsule's answer, per the product brief, is "shop your own closet": catalog what you own (photo capture + AI recognition), surface it contextually (weather, calendar, what you haven't worn in months), and let AI expand your options rather than dictate them.
That direction isn't a guess — it came out of a structured discovery pass (problem statement, target users, competitive gaps, differentiation thesis) done before any product code was written. Full writeup in docs/project-overview.md and docs/internal/product-brief.md.
| Path | Purpose | Status |
|---|---|---|
apps/web |
Next.js 16 (App Router) — the eventual product surface | 🔴 Unmodified create-next-app template. No auth, routes, or business UI |
apps/docs |
Storybook 9 — documents @capsule/ui |
✅ Working — see Demo below |
packages/ui |
React design system, Atomic Design, CVA variants | ✅ 13 atoms shipped: Button, Input, Checkbox, Radio, Switch, Avatar, Badge, Tag, Divider, Link, Spinner, Skeleton, IconButton — each with tests + stories |
packages/domain |
Framework-free business logic (entities, use cases) | 🔴 Placeholder — one DomainError class, nothing else |
packages/db |
Drizzle ORM schemas + database access | 🔴 Placeholder — no schema, no migrations, no client |
packages/validators |
Shared Zod validation schemas | 🔴 Placeholder — dependency installed, no schemas defined |
packages/tailwind-config |
Shared Tailwind theme (color, type, spacing, radius, shadow tokens) | ✅ Working |
packages/eslint-config |
Shared ESLint configs (base, Next.js, React library, design system) | ✅ Working |
packages/typescript-config |
Shared tsconfig.json bases |
✅ Working |
| — | Monorepo tooling: Turborepo, pnpm workspaces, Husky, commitlint, CI | ✅ Working |
| — | Auth, state management (Zustand), AI recognition pipeline | 🔴 Architectural decisions on record in project-context.md, no code yet |
If you're picking up work here: packages/ui is what to read for this repo's code-quality bar. Everything else is scaffolding waiting on the domain model.
graph TD
subgraph "Working"
Docs["apps/docs (Storybook)"]
UI["packages/ui — 13 atoms"]
TW["packages/tailwind-config"]
PG[("Postgres 16 — Docker")]
end
subgraph "Placeholders — no logic yet"
Domain["packages/domain"]
DB["packages/db"]
Val["packages/validators"]
end
Web["apps/web (Next.js template)"]
Docs --> UI
UI --> TW
Web -.not imported.-> UI
Web -.not connected.-> PG
DB -.would connect.-> PG
apps/web doesn't import @capsule/ui yet, and nothing in the app talks to Postgres yet. The design system and the Docker database are both real and independently working — they just aren't wired into the product surface.
There's no product UI to screenshot yet, but packages/ui is real and running:
pnpm --filter docs dev # → http://localhost:6006That renders all 13 shipped components live, with every variant, size, and state as an interactive story.
| Layer | Choice |
|---|---|
| Monorepo | Turborepo 2.5, pnpm workspaces |
| Language | TypeScript 5.9 (strict mode everywhere) |
| Web framework | Next.js 16 (App Router), React 19 |
| Styling | Tailwind CSS 3.4 — shared JS theme config, not CSS-first @theme (see ADR 0001) |
| Component variants | class-variance-authority (CVA) + tailwind-merge |
| Design system docs | Storybook 9 (Vite builder) |
| Database | PostgreSQL 16 via Docker; Drizzle ORM planned, not yet wired up |
| Validation | Zod, installed, no schemas yet |
| Testing | Vitest + Testing Library (packages/ui only, 15 test files); Playwright planned for E2E |
| Lint/format | ESLint 9, Prettier, Husky + lint-staged, commitlint (Conventional Commits) |
Every internal package exports TypeScript source directly ("exports": { ".": "./src/index.ts" }) instead of a compiled dist/, since consuming apps already bundle TypeScript — one less build step, instant hot reload across package boundaries. Full rationale in ARCHITECTURE.md.
A decision that got reversed: the design tokens originally shipped as a CSS-first @theme setup (Tailwind v4 style). It got reverted in favor of a conventional tailwind.config.js — see docs/decisions/0001-tailwind-v3-vs-v4-tokens.md for what changed and what's on record about why.
packages/ui is the one part of this repo that's finished, not scaffolded, so it's the fastest way to see the actual code-quality bar. Every one of the 13 atoms follows the same shape:
// packages/ui/src/components/atoms/Button/Button.tsx
const buttonVariants = cva(
["inline-flex items-center justify-center gap-2", "squircle rounded-xl font-medium transition-colors", /* … */],
{
variants: {
variant: { primary: [...], secondary: [...], outline: [...], ghost: [...], danger: [...] },
size: { sm: "h-9 px-3 text-sm", md: "h-11 px-6 text-base", lg: "h-14 px-8 text-lg" },
},
defaultVariants: { variant: "primary", size: "md", fullWidth: false },
},
);
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
isLoading?: boolean;
}
export const Button = forwardRef<HTMLButtonElement, ButtonProps>((props, ref) => { /* … */ });
Button.displayName = createDisplayName("Button", "atom");What that pattern buys, applied consistently across all 13 components:
- Typed variants, not string props —
variant/sizeare inferred fromcva()viaVariantProps, so an invalid variant is a compile error, not a runtime CSS miss. forwardRefeverywhere — every atom is usable with refs (focus management, form libraries, animation) without exceptions to remember.- Native HTML props pass through —
ButtonProps extends ButtonHTMLAttributes<...>, so nothing about the underlying<button>is hidden from the consumer. createDisplayName()gives every component a real, debuggable name in React DevTools instead ofForwardRef(Anonymous).- Accessibility is checked at build time, not left to review — Storybook runs
@storybook/addon-a11yagainst every story. - Every atom ships with its test and its story —
Button.tsx,Button.test.tsx,Button.stories.tsxlive together; there's no "component exists but nobody wrote the story" gap.
- Node.js 22.15.0 — pinned in
.nvmrc, required for Storybook 9's native dependencies. Note: CI (ci.yml) andDockerfilecurrently run on Node 18 instead — neither of those runs Storybook, so it hasn't caused a documented failure, but the two numbers are unreconciled. Ifpnpm installfails with native-binary/gyperrors, you're likely on an incompatible Node version — seeTROUBLESHOOTING.md. - pnpm ≥ 9.0.0
- Docker & Docker Compose — only needed for the Postgres environment; the design system and Storybook run without it.
nvm use && corepack enable
pnpm install
pnpm --filter docs dev # Storybook (the design system) → http://localhost:6006To also run the web app (currently the unmodified Next.js template):
cp .env.example .env # non-secret local defaults, edit if needed
pnpm dev --filter=web # → http://localhost:3000cp .env.example .env
docker compose up -d # web (:3000) + postgres (:5432)
docker compose --profile tools up -d # + pgAdmin at :5050 (admin@capsule.local / admin)docker compose exec postgres psql -U capsule -d capsule_dev
# postgresql://capsule:capsule_dev_password@localhost:5432/capsule_devA Makefile wraps the common Docker commands — run make help for the full list (make dev, make up, make down, make db-shell, make db-reset).
pnpm dev # all apps in dev mode
pnpm build # build everything
pnpm lint # ESLint, all workspaces
pnpm check-types # tsc --noEmit, all workspaces
pnpm format # Prettier write
pnpm --filter ui test # @capsule/ui's Vitest suite (not yet run in CI — run before touching packages/ui)
pnpm --filter ui test:coveragecapsule/
├── apps/
│ ├── web/ # Next.js app (product surface)
│ └── docs/ # Storybook for @capsule/ui
├── packages/
│ ├── ui/ # Design system (13 atoms, tokens, utils)
│ ├── domain/ # Business logic (placeholder)
│ ├── db/ # Drizzle schemas (placeholder)
│ ├── validators/ # Zod schemas (placeholder)
│ ├── tailwind-config/ # Shared design tokens
│ ├── eslint-config/ # Shared lint rules
│ └── typescript-config/ # Shared tsconfig bases
├── docker/ # Postgres init/seed SQL
├── docs/ # Product & architecture docs (generated via BMAD)
├── .github/workflows/ # CI
└── turbo.json # Turborepo task graph
GitHub Flow, Conventional Commits (enforced by commitlint), CI checks, and where to find component/architecture conventions are all documented in CONTRIBUTING.md. Solo-founder project, not yet open to outside contributions — but the same rules apply internally, enforced by tooling rather than left to memory.
No LICENSE file exists yet, and every workspace is marked "private": true. Treat this as all rights reserved / not licensed for reuse until that changes.
| Document | Covers |
|---|---|
CONTRIBUTING.md |
Workflow, commit conventions, CI, where to find component/architecture rules |
ARCHITECTURE.md |
Source-vs-compiled package exports — the one architectural rationale still fully current |
docs/decisions/ |
Architecture Decision Records — currently one, on the Tailwind v3/v4 reversal |
TROUBLESHOOTING.md |
Step-by-step fixes for common local setup issues |
docs/archive/ |
Retired docs (old SETUP.md, FAQ.md) kept for historical context — not current references |
docs/project-overview.md |
Product vision, tech stack snapshot, roadmap (Spanish) |
docs/internal/product-brief.md |
Problem statement, target users, differentiators (Spanish) |
packages/ui/README.md |
Design system overview |
packages/ui/STRUCTURE.md |
Component file structure and conventions |
packages/ui/CONTRIBUTING.md |
How to add a new component |
Near-term (per docs/project-overview.md):
- Define domain entities (
User,Garment,Outfit) inpackages/domain - Design the Postgres schema and wire up Drizzle in
packages/db - Build the first real pages in
apps/web(auth, wardrobe dashboard) - Expand
packages/uiinto Molecules/Organisms asapps/webneeds them
Longer-term: AI garment recognition, outfit recommendation, Zustand-based client state, and CI test coverage.