diff --git a/.dockerignore b/.dockerignore index e692198..8d994c2 100644 --- a/.dockerignore +++ b/.dockerignore @@ -7,3 +7,6 @@ __pycache__ *.py[cod] argus.db *.db +frontend/node_modules +frontend/.next +frontend/out diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2bbd117..91a2dfd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -37,3 +37,44 @@ jobs: uses: codecov/codecov-action@v7 with: files: coverage.xml + + frontend: + name: Frontend + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v7 + + - name: Set up Node + uses: actions/setup-node@v5 + with: + node-version: "22" + + - name: Enable corepack + run: corepack enable + + - name: Install dependencies + working-directory: frontend + run: pnpm install --frozen-lockfile + + - name: Lint + working-directory: frontend + run: pnpm lint + + # Scoped to the "unit" vitest project only — the Storybook/Playwright + # browser-based component tests require installing a browser and are + # deliberately left as a local-only check for now. + - name: Test + working-directory: frontend + run: pnpm test:coverage + + - name: Upload coverage report + uses: codecov/codecov-action@v7 + with: + files: frontend/coverage/lcov.info + flags: frontend + + - name: Build + working-directory: frontend + run: pnpm build diff --git a/.gitignore b/.gitignore index 00582e2..1c79f2d 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,13 @@ dist/ .claude/ CLAUDE.md AGENTS.md + +# Built frontend, copied into the backend package at build time +src/argus/dashboard/frontend/ + +# Superpowers scratch workspaces (SDD ledgers, brainstorm mockups) — local only +.superpowers/ + +# Superpowers specs/plans — useful during active design/implementation, +# not worth keeping once the work they describe has landed +docs/superpowers/ diff --git a/Dockerfile b/Dockerfile index b1d48c7..aad8020 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,3 +1,15 @@ +FROM node:22-slim AS frontend-build + +WORKDIR /frontend +RUN corepack enable && corepack prepare pnpm@10.33.0 --activate + +COPY frontend/package.json frontend/pnpm-lock.yaml frontend/pnpm-workspace.yaml ./ +RUN pnpm install --frozen-lockfile + +COPY frontend ./ +RUN pnpm build + + FROM python:3.12-slim-bookworm LABEL org.opencontainers.image.source="https://github.com/sciwork/argus" @@ -9,6 +21,7 @@ WORKDIR /app COPY pyproject.toml README.md ./ COPY src ./src +COPY --from=frontend-build /frontend/out ./src/argus/dashboard/frontend RUN pip install --no-cache-dir . diff --git a/README.md b/README.md index 1c26f73..f474245 100644 --- a/README.md +++ b/README.md @@ -68,7 +68,7 @@ URL: `https://your-domain/webhook/kktix/sprint`, env var: `DISCORD_WEBHOOK_SPRIN A Google-OAuth-protected web UI for viewing per-event registration time series. - **Event list:** `/dashboard` -- **Per-event chart:** `/dashboard/events/{slug}` — line chart of Total + each ticket type, with capacity (horizontal dashed) and event start (vertical dashed) reference lines. +- **Per-event chart:** `/dashboard/events?slug=` — line chart of Total + each ticket type, with capacity (horizontal dashed) and event start (vertical dashed) reference lines. ### One-time Google OAuth setup @@ -87,8 +87,11 @@ A Google-OAuth-protected web UI for viewing per-event registration time series. ### Try it locally +The dashboard UI is a Next.js app (`frontend/`) built to static files and served same-origin by the backend. `uvicorn` alone won't serve it in a fresh checkout — build the frontend first (or use the `next dev` proxy workflow), see [Development](#development) below. + ```bash set -a && source .env && set +a +cd frontend && pnpm install && pnpm build && cd .. # one-time (or after frontend changes) uv run uvicorn argus.main:app --host 0.0.0.0 --port 8000 # open http://localhost:8000/dashboard ``` @@ -100,6 +103,7 @@ You will be redirected to Google to sign in. Only emails in `ALLOWED_EMAILS` are When deploying (e.g. to Railway): - **Railway builds the Dockerfile** using `python:3.12-slim-bookworm`, installs the package with `pip install .`, and starts uvicorn via `railway.json` `startCommand`. Railway injects `$PORT` and the start command binds to it. +- **The frontend build is automatic** — the Dockerfile's first stage builds `frontend/` (`pnpm install && pnpm build`) and copies its static export into `src/argus/dashboard/frontend/` before the Python stage installs the package. No manual frontend build step is needed for Docker/Railway deploys. - **For SQLite, mount a persistent volume** at `/data` and set `DATABASE_URL=sqlite:////data/argus.db`. SQLite written to the container's local filesystem will be wiped on every redeploy. - **`SESSION_SECRET` is required** — the app refuses to boot without it. Generate with `python -c "import secrets; print(secrets.token_hex(32))"`. - **Port:** the Dockerfile's `CMD` binds to a fixed port 8000. Railway overrides this via `railway.json`'s `startCommand`, which substitutes its injected `$PORT`. To change the port in non-Railway environments, override the container command (e.g. `docker run … argus-image uvicorn argus.main:app --host 0.0.0.0 --port 9000`). @@ -124,3 +128,27 @@ uv run ruff format src tests # format # Visual inspection of Discord report (sends a real webhook): ARGUS_MANUAL_TEST=1 uv run pytest tests/test_discord_format_manual.py -v -s ``` + +### Frontend (`frontend/`) + +The dashboard UI lives in `frontend/` — a Next.js app using `pnpm` (not `npm`), statically exported (`next build`) into `src/argus/dashboard/frontend/`, and served same-origin by the backend under `/dashboard` (no separate frontend server or CORS setup in production). This copy step happens automatically in Docker's multi-stage build; locally you have two options: + +1. **Build once, run `uvicorn` normally** — full same-origin experience, matches production: + ```bash + cd frontend && pnpm install && pnpm build && cd .. + uv run uvicorn argus.main:app --host 0.0.0.0 --port 8000 + # open http://localhost:8000/dashboard + ``` + Re-run `pnpm build` after frontend changes to see them. + +2. **`next dev` + dev proxy** — for active frontend development with hot reload, run both processes side by side: + ```bash + # terminal 1 + uv run uvicorn argus.main:app --host 0.0.0.0 --port 8000 + # terminal 2 + cd frontend && pnpm install && pnpm dev + # open http://localhost:3000/dashboard + ``` + `frontend/next.config.ts` proxies `/dashboard/api/*` calls from the `next dev` server (port 3000) to `uvicorn` (port 8000), so no CORS configuration is needed. + +Other frontend commands (run from `frontend/`): `pnpm lint`, `pnpm test` (vitest unit/component tests), `pnpm exec tsc --noEmit`. diff --git a/SPEC.md b/SPEC.md index bc8d94b..85f79fb 100644 --- a/SPEC.md +++ b/SPEC.md @@ -53,9 +53,10 @@ Argus uses a **vertical slice** layout: each feature owns its full stack (HTTP r | `GET` | `/dashboard/login` | — | Start Google OAuth flow | [Dashboard](#dashboard) | | `GET` | `/dashboard/oauth/callback` | — | OAuth redirect target | [Dashboard](#dashboard) | | `GET` | `/dashboard/logout` | — | Clear session, redirect to login | [Dashboard](#dashboard) | -| `GET` | `/dashboard` | session (HTML) | Event list page | [Dashboard](#dashboard) | -| `GET` | `/dashboard/events/{slug}` | session (HTML) | Per-event chart page | [Dashboard](#dashboard) | -| `GET` | `/dashboard/webhook-logs` | session (HTML) | Webhook log viewer page | [Dashboard](#dashboard) | +| `GET` | `/dashboard` | public (shell) | Event list page (static Next.js export) | [Dashboard](#dashboard) | +| `GET` | `/dashboard/events` | public (shell) | Per-event chart page, `?slug=` (static Next.js export) | [Dashboard](#dashboard) | +| `GET` | `/dashboard/webhook-logs` | public (shell) | Webhook log viewer page (static Next.js export) | [Dashboard](#dashboard) | +| `GET` | `/dashboard/api/me` | session (401) | JSON: currently authenticated user | [Dashboard](#dashboard) | | `GET` | `/dashboard/api/events` | session (401) | JSON: event list | [Dashboard](#dashboard) | | `GET` | `/dashboard/api/events/{slug}/timeseries` | session (401) | JSON: per-event time series | [Dashboard](#dashboard) | | `DELETE` | `/dashboard/api/events/{slug}` | session (401) | Permanently delete event + its tickets | [Dashboard](#dashboard) | @@ -66,6 +67,7 @@ Argus uses a **vertical slice** layout: each feature owns its full stack (HTTP r **Auth column legend:** - `x-kktix-secret header` — request must include header matching `WEBHOOK_SECRET` (constant-time compared) +- `public (shell)` — served with no server-side session check at all; the page itself is a public static shell, and it protects its own data by calling `/dashboard/api/*` routes, which each require the session cookie client-side (a request without a valid session gets a 401 from those API calls, not from the page) - `session (HTML)` — protected by signed session cookie; missing/invalid → 302 to `/dashboard/login` - `session (401)` — same protection but JSON routes return 401 instead of redirecting @@ -91,10 +93,9 @@ argus/ │ │ ├── __init__.py │ │ ├── router.py # /dashboard/* routes │ │ ├── queries.py # time series queries -│ │ └── templates/ -│ │ ├── _base.html # shared layout -│ │ ├── index.html # event list -│ │ └── event.html # per-event chart +│ │ └── frontend/ # built Next.js static export, copied in at +│ │ # build time (gitignored); served by main.py's +│ │ # StaticFiles mount at /dashboard │ │ │ │ # ── shared infrastructure ── │ ├── auth.py # OAuth client + require_login dependency (reusable) @@ -113,6 +114,8 @@ argus/ │ ├── conftest.py │ ├── test_*.py # automated tests │ └── test_discord_format_manual.py # opt-in test that sends real Discord webhooks +├── frontend/ # Next.js dashboard UI (source); builds into +│ # src/argus/dashboard/frontend/ (see above) ├── .env.example ├── pyproject.toml ├── railway.json @@ -391,7 +394,7 @@ A web UI that visualizes registration trends per event over time. Implemented as ### Routes -See [API Reference](#api-reference) for the canonical list. All routes under `/dashboard/*` (except `login` and `oauth/callback`) require an authenticated session. HTML routes redirect to `/dashboard/login` on failure; JSON API routes return `401`. +See [API Reference](#api-reference) for the canonical list. The dashboard UI itself is a statically-exported Next.js app served same-origin under `/dashboard`, `/dashboard/events`, and `/dashboard/webhook-logs` — these three pages are public shells with no server-side session check; each gates its own content client-side by calling `/dashboard/api/me` (and other `/dashboard/api/*` routes) and redirecting to `/dashboard/login` in the browser if that call 401s. Every `/dashboard/api/*` route requires the session cookie and returns `401` if it's missing or invalid. ### Authentication @@ -399,11 +402,11 @@ Server-side OAuth 2.0 with Google as the identity provider. After successful OAu **Flow:** -1. Visit `/dashboard` (or any protected route) without session → 302 to `/dashboard/login` +1. Visit `/dashboard` without a session → the static shell loads, its client-side `/dashboard/api/me` call 401s, and the browser is redirected to `/dashboard/login` 2. `/dashboard/login` → 302 to Google consent screen 3. Google → `/dashboard/oauth/callback?code=...` 4. Backend exchanges code for `id_token`, verifies email is in `ALLOWED_EMAILS` -5. On success: session cookie written, 302 to original destination (or `/dashboard`) +5. On success: session cookie written, 302 to `/dashboard` 6. On rejection: 403 page Session is signed using `SESSION_SECRET` via Starlette's `SessionMiddleware`. diff --git a/frontend/.agents/skills/shadcn/SKILL.md b/frontend/.agents/skills/shadcn/SKILL.md new file mode 100644 index 0000000..1b4c414 --- /dev/null +++ b/frontend/.agents/skills/shadcn/SKILL.md @@ -0,0 +1,277 @@ +--- +name: shadcn +description: Manages shadcn components and projects — adding, searching, fixing, debugging, styling, and composing UI, including chat interfaces. Provides project context, component docs, and usage examples. Applies when working with shadcn/ui, component registries, presets, --preset codes, or any project with a components.json file. Also triggers for "shadcn init", "create an app with --preset", or "switch to --preset". +user-invocable: false +allowed-tools: Bash(npx shadcn@latest *), Bash(pnpm dlx shadcn@latest *), Bash(bunx --bun shadcn@latest *) +--- + +# shadcn/ui + +A framework for building ui, components and design systems. Components are added as source code to the user's project via the CLI. + +> **IMPORTANT:** Run all CLI commands using the project's package runner: `npx shadcn@latest`, `pnpm dlx shadcn@latest`, or `bunx --bun shadcn@latest` — based on the project's `packageManager`. Examples below use `npx shadcn@latest` but substitute the correct runner for the project. + +## Current Project Context + +```json +!`npx shadcn@latest info --json` +``` + +The JSON above contains the project config and installed components. Use `npx shadcn@latest docs ` to get documentation and example URLs for any component. + +## Principles + +1. **Use existing components first.** Use `npx shadcn@latest search` to check registries before writing custom UI. Check community registries too. +2. **Compose, don't reinvent.** Settings page = Tabs + Card + form controls. Dashboard = Sidebar + Card + Chart + Table. +3. **Use built-in variants before custom styles.** `variant="outline"`, `size="sm"`, etc. +4. **Use semantic colors.** `bg-primary`, `text-muted-foreground` — never raw values like `bg-blue-500`. + +## Critical Rules + +These rules are **always enforced**. Each links to a file with Incorrect/Correct code pairs. + +### Styling & Tailwind → [styling.md](./rules/styling.md) + +- **`className` for layout, not styling.** Never override component colors or typography. +- **No `space-x-*` or `space-y-*`.** Use `flex` with `gap-*`. For vertical stacks, `flex flex-col gap-*`. +- **Use `size-*` when width and height are equal.** `size-10` not `w-10 h-10`. +- **Use `truncate` shorthand.** Not `overflow-hidden text-ellipsis whitespace-nowrap`. +- **No manual `dark:` color overrides.** Use semantic tokens (`bg-background`, `text-muted-foreground`). +- **Use `cn()` for conditional classes.** Don't write manual template literal ternaries. +- **No manual `z-index` on overlay components.** Dialog, Sheet, Popover, etc. handle their own stacking. + +### Forms & Inputs → [forms.md](./rules/forms.md) + +- **Forms use `FieldGroup` + `Field`.** Never use raw `div` with `space-y-*` or `grid gap-*` for form layout. +- **`InputGroup` uses `InputGroupInput`/`InputGroupTextarea`.** Never raw `Input`/`Textarea` inside `InputGroup`. +- **Buttons inside inputs use `InputGroup` + `InputGroupAddon`.** +- **Option sets (2–7 choices) use `ToggleGroup`.** Don't loop `Button` with manual active state. +- **`FieldSet` + `FieldLegend` for grouping related checkboxes/radios.** Don't use a `div` with a heading. +- **Field validation uses `data-invalid` + `aria-invalid`.** `data-invalid` on `Field`, `aria-invalid` on the control. For disabled: `data-disabled` on `Field`, `disabled` on the control. + +### Component Structure → [composition.md](./rules/composition.md) + +- **Items always inside their Group.** `SelectItem` → `SelectGroup`. `DropdownMenuItem` → `DropdownMenuGroup`. `CommandItem` → `CommandGroup`. +- **Use `asChild` (radix) or `render` (base) for custom triggers.** Check `base` field from `npx shadcn@latest info`. → [base-vs-radix.md](./rules/base-vs-radix.md) +- **Dialog, Sheet, and Drawer always need a Title.** `DialogTitle`, `SheetTitle`, `DrawerTitle` required for accessibility. Use `className="sr-only"` if visually hidden. +- **Use full Card composition.** `CardHeader`/`CardTitle`/`CardDescription`/`CardContent`/`CardFooter`. Don't dump everything in `CardContent`. +- **Button has no `isPending`/`isLoading`.** Compose with `Spinner` + `data-icon` + `disabled`. +- **`TabsTrigger` must be inside `TabsList`.** Never render triggers directly in `Tabs`. +- **`Avatar` always needs `AvatarFallback`.** For when the image fails to load. + +### Use Components, Not Custom Markup → [composition.md](./rules/composition.md) + +- **Use existing components before custom markup.** Check if a component exists before writing a styled `div`. +- **Callouts use `Alert`.** Don't build custom styled divs. +- **Empty states use `Empty`.** Don't build custom empty state markup. +- **Toast follows the project base.** Use `toast` from the `toast` component for + Base UI projects. Use `toast()` from `sonner` for Radix and React Aria + projects. +- **Use `Separator`** instead of `
` or `
`. +- **Use `Skeleton`** for loading placeholders. No custom `animate-pulse` divs. +- **Use `Badge`** instead of custom styled spans. + +### Icons → [icons.md](./rules/icons.md) + +- **Icons in `Button` use `data-icon`.** `data-icon="inline-start"` or `data-icon="inline-end"` on the icon. +- **No sizing classes on icons inside components.** Components handle icon sizing via CSS. No `size-4` or `w-4 h-4`. +- **Pass icons as objects, not string keys.** `icon={CheckIcon}`, not a string lookup. + +### Chat & Messaging → [chat.md](./rules/chat.md) + +- **Chat UI composes the chat primitives.** Conversations use `MessageScroller`, rows use `Message`, surfaces use `Bubble`. Never hand-rolled bubble `div`s or a raw scroll container. +- **`MessageScroller` owns scroll behavior.** Streaming follow, anchoring, and jump-to-latest (`MessageScrollerButton`) are built in. Don't write a `useStickToBottom`/`ResizeObserver` hook. +- **Attachments use `Attachment`; system notes and dividers use `Marker`.** Not `Item` cards or `Separator` + a label. + +### CLI + +- **Never decode preset codes or build preset URLs manually.** Use `npx shadcn@latest preset decode `, `preset url `, or `preset open `. For project-aware preset detection, use `npx shadcn@latest preset resolve`. +- **Apply preset codes directly with the CLI.** Use `npx shadcn@latest apply ` for existing projects, or `npx shadcn@latest init --preset ` when initializing. + +## Key Patterns + +These are the most common patterns that differentiate correct shadcn/ui code. For edge cases, see the linked rule files above. + +```tsx +// Form layout: FieldGroup + Field, not div + Label. + + + Email + + + + +// Validation: data-invalid on Field, aria-invalid on the control. + + Email + + Invalid email. + + +// Icons in buttons: data-icon, no sizing classes. + + +// Spacing: gap-*, not space-y-*. +
// correct +
// wrong + +// Equal dimensions: size-*, not w-* h-*. + // correct + // wrong + +// Status colors: Badge variants or semantic tokens, not raw colors. ++20.1% // correct ++20.1% // wrong +``` + +## Component Selection + +| Need | Use | +| -------------------------- | --------------------------------------------------------------------------------------------------- | +| Button/action | `Button` with appropriate variant | +| Form inputs | `Input`, `Select`, `Combobox`, `Switch`, `Checkbox`, `RadioGroup`, `Textarea`, `InputOTP`, `Slider` | +| Toggle between 2–5 options | `ToggleGroup` + `ToggleGroupItem` | +| Data display | `Table`, `Card`, `Badge`, `Avatar` | +| Navigation | `Sidebar`, `NavigationMenu`, `Breadcrumb`, `Tabs`, `Pagination` | +| Overlays | `Dialog` (modal), `Sheet` (side panel), `Drawer` (bottom sheet), `AlertDialog` (confirmation) | +| Feedback | `toast` (Base UI), `sonner` (Radix/Aria), `Alert`, `Progress`, `Skeleton`, `Spinner` | +| Command palette | `Command` inside `Dialog` | +| Charts | `Chart` (wraps Recharts) | +| Layout | `Card`, `Separator`, `Resizable`, `ScrollArea`, `Accordion`, `Collapsible` | +| Empty states | `Empty` | +| Menus | `DropdownMenu`, `ContextMenu`, `Menubar` | +| Tooltips/info | `Tooltip`, `HoverCard`, `Popover` | +| Chat / conversation UI | `MessageScroller`, `Message`, `Bubble`, `Attachment`, `Marker` | + +## Key Fields + +The injected project context contains these key fields: + +- **`aliases`** → use the actual alias prefix for imports (e.g. `@/`, `~/`), never hardcode. +- **`isRSC`** → when `true`, components using `useState`, `useEffect`, event handlers, or browser APIs need `"use client"` at the top of the file. Always reference this field when advising on the directive. +- **`tailwindVersion`** → `"v4"` uses `@theme inline` blocks; `"v3"` uses `tailwind.config.js`. +- **`tailwindCssFile`** → the global CSS file where custom CSS variables are defined. Always edit this file, never create a new one. +- **`style`** → component visual treatment (e.g. `nova`, `vega`). +- **`base`** → primitive library (`radix` or `base`). Affects component APIs and available props. +- **`iconLibrary`** → determines icon imports. Use `lucide-react` for `lucide`, `@tabler/icons-react` for `tabler`, etc. Never assume `lucide-react`. +- **`resolvedPaths`** → exact file-system destinations for components, utils, hooks, etc. +- **`framework`** → routing and file conventions (e.g. Next.js App Router vs Vite SPA). +- **`packageManager`** → use this for any non-shadcn dependency installs (e.g. `pnpm add date-fns` vs `npm install date-fns`). +- **`preset`** → resolved preset code and values for the current project. Use `npx shadcn@latest preset resolve --json` when you only need preset information. + +See [cli.md — `info` command](./cli.md) for the full field reference. + +## Component Docs, Examples, and Usage + +Run `npx shadcn@latest docs ` to get the URLs for a component's documentation, examples, and API reference. Fetch these URLs to get the actual content. + +```bash +npx shadcn@latest docs button dialog select +``` + +**When creating, fixing, debugging, or using a component, always run `npx shadcn@latest docs` and fetch the URLs first.** This ensures you're working with the correct API and usage patterns rather than guessing. + +## Workflow + +1. **Get project context** — already injected above. Run `npx shadcn@latest info` again if you need to refresh. +2. **Check installed components first** — before running `add`, always check the `components` list from project context or list the `resolvedPaths.ui` directory. Don't import components that haven't been added, and don't re-add ones already installed. +3. **Find components** — `npx shadcn@latest search`. +4. **Get docs and examples** — run `npx shadcn@latest docs ` to get URLs, then fetch them. Use `npx shadcn@latest view` to browse registry items you haven't installed. To preview changes to installed components, use `npx shadcn@latest add --diff`. +5. **Install or update** — `npx shadcn@latest add`. When updating existing components, use `--dry-run` and `--diff` to preview changes first (see [Updating Components](#updating-components) below). +6. **Fix imports in third-party components** — After adding components from community registries (e.g. `@bundui`, `@magicui`), check the added non-UI files for hardcoded import paths like `@/components/ui/...`. These won't match the project's actual aliases. Use `npx shadcn@latest info` to get the correct `ui` alias (e.g. `@workspace/ui/components`) and rewrite the imports accordingly. The CLI rewrites imports for its own UI files, but third-party registry components may use default paths that don't match the project. +7. **Review added components** — After adding a component or block from any registry, **always read the added files and verify they are correct**. Check for missing sub-components (e.g. `SelectItem` without `SelectGroup`), missing imports, incorrect composition, or violations of the [Critical Rules](#critical-rules). Also replace any icon imports with the project's `iconLibrary` from the project context (e.g. if the registry item uses `lucide-react` but the project uses `hugeicons`, swap the imports and icon names accordingly). Fix all issues before moving on. +8. **Registry must be explicit** — When the user asks to add a block or component, **do not guess the registry**. If no registry is specified (e.g. user says "add a login block" without specifying `@shadcn`, `@tailark`, `owner/repo`, etc.), ask which registry to use. Never default to a registry on behalf of the user. +9. **Switching presets** — Ask the user first: **overwrite**, **partial**, **merge**, or **skip**? + - **Inspect current preset**: `npx shadcn@latest preset resolve`. Use `--json` when you need structured values. + - **Inspect incoming preset**: `npx shadcn@latest preset decode `. Use `preset url ` or `preset open ` to share or open the preset builder. + - **Overwrite**: `npx shadcn@latest apply `. Overwrites detected components, fonts, and CSS variables. + - **Partial**: `npx shadcn@latest apply --only theme,font`. Updates only the selected preset parts without reinstalling UI components. Supported values are `theme` and `font`; comma-separated combinations are allowed. `icon` is intentionally not supported, because icon changes may require full component reinstall and transforms. + - **Merge**: `npx shadcn@latest init --preset --force --no-reinstall`, then run `npx shadcn@latest info` to list installed components, then for each installed component use `--dry-run` and `--diff` to [smart merge](#updating-components) it individually. + - **Skip**: `npx shadcn@latest init --preset --force --no-reinstall`. Only updates config and CSS, leaves components as-is. + - **Important**: Always run preset commands inside the user's project directory. `apply` only works in an existing project with a `components.json` file. The CLI automatically preserves the current base (`base` vs `radix`) from `components.json`. If you must use a scratch/temp directory (e.g. for `--dry-run` comparisons), pass `--base ` explicitly — preset codes do not encode the base. + +## Updating Components + +When the user asks to update a component from upstream while keeping their local changes, use `--dry-run` and `--diff` to intelligently merge. **NEVER fetch raw files from GitHub manually — always use the CLI.** + +1. Run `npx shadcn@latest add --dry-run` to see all files that would be affected. +2. For each file, run `npx shadcn@latest add --diff ` to see what changed upstream vs local. +3. Decide per file based on the diff: + - No local changes → safe to overwrite. + - Has local changes → read the local file, analyze the diff, and apply upstream updates while preserving local modifications. + - User says "just update everything" → use `--overwrite`, but confirm first. +4. **Never use `--overwrite` without the user's explicit approval.** + +## Quick Reference + +```bash +# Create a new project. +npx shadcn@latest init --name my-app --preset base-nova +npx shadcn@latest init --name my-app --preset a2r6bw --template vite + +# Create a monorepo project. +npx shadcn@latest init --name my-app --preset base-nova --monorepo +npx shadcn@latest init --name my-app --preset base-nova --template next --monorepo + +# Initialize existing project. +npx shadcn@latest init --preset base-nova +npx shadcn@latest init --defaults # shortcut: --template=next --preset=nova (base style implied) + +# Apply a preset to an existing project. +npx shadcn@latest apply a2r6bw +npx shadcn@latest apply a2r6bw --only theme +npx shadcn@latest apply a2r6bw --only font +npx shadcn@latest apply a2r6bw --only theme,font + +# Inspect preset codes and project preset state. +npx shadcn@latest preset decode a2r6bw +npx shadcn@latest preset url a2r6bw +npx shadcn@latest preset open a2r6bw +npx shadcn@latest preset resolve +npx shadcn@latest preset resolve --json + +# Add components. +npx shadcn@latest add button card dialog +npx shadcn@latest add @magicui/shimmer-button +npx shadcn@latest add owner/repo/item +npx shadcn@latest add --all + +# Preview changes before adding/updating. +npx shadcn@latest add button --dry-run +npx shadcn@latest add button --diff button.tsx +npx shadcn@latest add @acme/form --view button.tsx +npx shadcn@latest add owner/repo/item --dry-run + +# Search registries. +npx shadcn@latest search @shadcn -q "sidebar" +npx shadcn@latest search @tailark -q "stats" +npx shadcn@latest search owner/repo -q "login" +npx shadcn@latest search # all configured registries +npx shadcn@latest search @shadcn -q "menu" -t ui # filter by item type + +# Get component docs and example URLs. +npx shadcn@latest docs button dialog select + +# View registry item details (for items not yet installed). +npx shadcn@latest view @shadcn/button +npx shadcn@latest view owner/repo/item +``` + +**Named presets:** `nova`, `vega`, `maia`, `lyra`, `mira`, `luma` +**Templates:** `next`, `vite`, `start`, `react-router`, `astro` (all support `--monorepo`) and `laravel` (not supported for monorepo) +**Preset codes:** Version-prefixed base62 strings (e.g. `a2r6bw` or `b0`), from [ui.shadcn.com](https://ui.shadcn.com). + +## Detailed References + +- [rules/forms.md](./rules/forms.md) — FieldGroup, Field, InputGroup, ToggleGroup, FieldSet, validation states +- [rules/composition.md](./rules/composition.md) — Groups, overlays, Card, Tabs, Avatar, Alert, Empty, Toast, Separator, Skeleton, Badge, Button loading +- [rules/chat.md](./rules/chat.md) — MessageScroller, Message, Bubble, Attachment, Marker; streaming, anchoring, jump-to-latest +- [rules/icons.md](./rules/icons.md) — data-icon, icon sizing, passing icons as objects +- [rules/styling.md](./rules/styling.md) — Semantic colors, variants, className, spacing, size, truncate, dark mode, cn(), z-index +- [rules/base-vs-radix.md](./rules/base-vs-radix.md) — asChild vs render, Select, ToggleGroup, Slider, Accordion +- [cli.md](./cli.md) — Commands, flags, presets, templates +- [registry.md](./registry.md) — Authoring source registries, `include`, item definitions, dependencies, GitHub registry rules +- [customization.md](./customization.md) — Theming, CSS variables, extending components diff --git a/frontend/.agents/skills/shadcn/agents/openai.yml b/frontend/.agents/skills/shadcn/agents/openai.yml new file mode 100644 index 0000000..ab636da --- /dev/null +++ b/frontend/.agents/skills/shadcn/agents/openai.yml @@ -0,0 +1,5 @@ +interface: + display_name: "shadcn/ui" + short_description: "Manages shadcn/ui components — adding, searching, fixing, debugging, styling, and composing UI." + icon_small: "./assets/shadcn-small.png" + icon_large: "./assets/shadcn.png" diff --git a/frontend/.agents/skills/shadcn/assets/shadcn-small.png b/frontend/.agents/skills/shadcn/assets/shadcn-small.png new file mode 100644 index 0000000..547154b Binary files /dev/null and b/frontend/.agents/skills/shadcn/assets/shadcn-small.png differ diff --git a/frontend/.agents/skills/shadcn/assets/shadcn.png b/frontend/.agents/skills/shadcn/assets/shadcn.png new file mode 100644 index 0000000..b7b6814 Binary files /dev/null and b/frontend/.agents/skills/shadcn/assets/shadcn.png differ diff --git a/frontend/.agents/skills/shadcn/cli.md b/frontend/.agents/skills/shadcn/cli.md new file mode 100644 index 0000000..8a1d195 --- /dev/null +++ b/frontend/.agents/skills/shadcn/cli.md @@ -0,0 +1,290 @@ +# shadcn CLI Reference + +Configuration is read from `components.json`. + +> **IMPORTANT:** Always run commands using the project's package runner: `npx shadcn@latest`, `pnpm dlx shadcn@latest`, or `bunx --bun shadcn@latest`. Check `packageManager` from project context to choose the right one. Examples below use `npx shadcn@latest` but substitute the correct runner for the project. + +> **IMPORTANT:** Only use the flags documented below. Do not invent or guess flags — if a flag isn't listed here, it doesn't exist. The CLI auto-detects the package manager from the project's lockfile; there is no `--package-manager` flag. + +## Contents + +- Commands: init, apply, add (dry-run, smart merge), search, view, docs, info, build +- Templates: next, vite, start, react-router, astro +- Presets: named, code, URL formats and fields +- Switching presets + +--- + +## Commands + +### `init` — Initialize or create a project + +```bash +npx shadcn@latest init [components...] [options] +``` + +Initializes shadcn/ui in an existing project or creates a new project (when `--name` is provided). Optionally installs components in the same step. + +| Flag | Short | Description | Default | +| ----------------------- | ----- | --------------------------------------------------------- | ------- | +| `--template