Skip to content
BoringStack
Star

First feature in 10 minutes

4 min read

Build your first feature end-to-end: database table, API route, typed UI page, tests on both sides, all passing bun run validate. The agent writes most of the code. You read diffs and check output.

Clone a fresh fork from boringstack-xyz/boringstack, then:

Terminal window
./setup.sh --up

This copies .env, generates the GlitchTip secret, and brings up the full Compose stack: Postgres, Valkey, api-dev with migrations, ui-dev on localhost:7331. No local Bun or Postgres needed.

Open http://localhost:7331 and create an account — signup is open in dev, and because the dev stack sends mail through Mailpit the verification email lands in the local inbox at http://localhost:8025. Click the link there to finish signing in.

Fast path - use /add-full-feature directly. The agent drafts a spec, you approve, it builds. Best for small slices and exploratory work.

Spec-loop path - run bun run spec:init once, then use /spec explore, /spec slice, /spec approve, /spec build. The agent cannot write source files until you set status: approved in .specs/next.md. Best for anything touching auth, billing, multi-tenant data, or cross-app contracts.

Both paths land identical code. The spec loop adds one explicit approval gate. This page uses the fast path.

Say you want support tickets. Each account lists tickets, creates them, closes them. Backend: table + service + routes. Frontend: list page + create form.

In your agent, type:

/add-full-feature
Resource: tickets
Account-scoped: yes
Fields: subject (text, required), body (text, optional), status (open/closed, default open)
UI: list page + create form

The agent writes a spec and asks you to confirm. Read it before saying yes.

On the API side the agent:

  1. Adds the tickets table to apps/api/src/clients/postgres/schema/app.schema.ts with accountId FK, indexes, and the @account-scoped marker.
  2. Runs bun run db:generate to create a migration.
  3. Writes the five-file resource pattern: service, routes, utils, constants, types, schemas. Each file gets a sibling test.
  4. Wires auditLogService.record(...) on every mutation.
  5. Runs bun run validate. If anything fails, it loops back and fixes.

Then it regenerates the OpenAPI types in apps/ui/ so apiClient.GET("/api/v1/tickets") is typed.

On the UI side:

  1. New feature folder: apps/ui/src/features/tickets/ with components, queries, mutations, schemas.
  2. TanStack Query + typed apiClient.
  3. shadcn components for the list and create form.
  4. Every JSX string lifted to i18n (English and German).
  5. RTL tests for the page.
  6. bun run validate again.

Open http://localhost:7331/tickets. The page renders, the form posts, the list updates.

From the terminal:

Terminal window
curl http://localhost:7330/api/v1/tickets
Terminal window
git checkout -b feat/tickets
git add -A
git commit -m "feat: tickets resource end-to-end"
git push -u origin feat/tickets
gh pr create --fill

CI runs the same bun run validate. Local green means CI green. Squash-merge.

Building a feature usually means holding the project’s conventions in your head: which files, which order, which sibling tests lint:meta demands, which lint rules block which patterns. The skill holds all that.

The merge gate is bun run validate, the same script in CI. The OpenAPI client is generated from the running API, so a missing field on one side is a type error on the other. Nothing is hand-wired.

  • /add-audit-event to instrument something the agent missed.
  • /add-notification-event to fire a notification when a ticket closes.
  • /security-review before opening the PR.
  • Spec loop when the next slice deserves an explicit approval gate.
  • Add Stripe Checkout to put the feature behind a paid plan.