AI Manual

Common patterns

"How do I add a new X?" recipes for the most common changes.

Add a new field to Storefront theme

Touches 4 files in slabtrack-storefront. Skipping any = silent partial save.

  1. Schemaprisma/schema.prisma: add the field to the Storefront model with a default
  2. Resolversrc/lib/storefront-theme.ts: add to StorefrontTheme interface, BASE_DEFAULTS, resolveStorefrontTheme(), and to each preset that overrides it
  3. API validatorsrc/app/api/storefronts/[slug]/theme/route.ts: add validation + data[field] = ... assignment
  4. Studio formsrc/app/dashboard/[slug]/theme/ThemeStudio.tsx: add to initial + state shape, render a form control, include in the Save state spread
  5. (if visual) Update src/app/[slug]/page.tsx to read theme.newField and render conditionally

Add a new Stripe webhook event handler

  1. Open src/app/api/stripe/webhook/route.ts in the relevant repo
  2. Add a case 'event.type': in the switch
  3. Cast: const obj = event.data.object as Stripe.RelevantType
  4. Do work. Use prisma.$transaction if multiple rows update together.
  5. Add the event name to the Stripe dashboard webhook endpoint config (manual step in Stripe UI)
  6. Update /docs/payments/stripe-setup.html events list

Add a new channel

  1. Registryslabtrack/backend/services/channels/registry.js: add an entry with slug, name, accent, exclusive flag, etc.
  2. Adapterslabtrack/backend/services/channels/{slug}.js: implement engage(card, ctx) + disengage(card, ctx)
  3. Strategist scoringbackend/services/channel-scoring/index.js: add scoring logic for the new channel (when does the engine recommend this channel for a card?)
  4. Allocatorbackend/services/strategist/allocator.js: add a bucket for the new channel + its commit handling
  5. UI: add to Strategist's quadrant view + Command Bridge's channel grid
  6. Webhook: if it's a satellite, point its inbound webhook at SlabTrack's ecosystem-transfer endpoint

Add a new game type to ToT

  1. Schemathisorthat/prisma/schema.prisma: extend GameType enum, add a new config table (FlipConfig / PullConfig precedent)
  2. Game logicsrc/lib/games/{name}.ts: pure function that takes a seed + listing + buyer state and returns an outcome
  3. Commit endpointsrc/app/api/play/{name}/route.ts: handles commit + reveal, follows the Pull/Flip provably-fair pattern (seed → hash → anchor → reveal)
  4. Stripe: payment intent with manual capture, settled via webhook on outcome
  5. UI: game-specific commit page + result page + buyer onboarding
  6. Listing creation: SlabTrack's allocator + commit endpoints need to know about the new game type

Add a new Prisma model

  1. Edit prisma/schema.prisma
  2. Run npx prisma generate locally to update the client types
  3. Test type-check passes (npx tsc --noEmit -p .)
  4. Commit. Railway's start script runs prisma db push on deploy.
  5. Don't forget: update Prisma indexes for foreign keys + frequent query patterns

Add idempotency to a Stripe call

// Pattern: { action }-{ stable-id }
const idempotencyKey = `refund-${order.id}`;

await stripe.refunds.create(
  { payment_intent: order.paymentIntentId, ... },
  { stripeAccount: sf.stripeAccountId, idempotencyKey }
);

Stripe will return the same Refund/Session/PaymentIntent object for repeat calls with the same key (within 24h). The retry-safe semantic.

Add a new API endpoint

  1. Pick the right repo + route file
  2. For SlabTrack (Express): backend/routes/{topic}.routes.js — define the route, mount in server.js if new file
  3. For ToT/Storefront (Next 16): src/app/api/{path}/route.ts — export GET / POST / etc.
  4. Auth: pull from cookie via verifyToken(token) (Next) or authenticate middleware (Express)
  5. Owner check: if mutating a shop/order/etc., verify req.user.id === resource.ownerUserId
  6. Return shape: { success: true, ...data } or { success: false, error: '...' }

Add a Cmd+K command to a palette

  1. For ToT: edit src/components/CommandPalette.tsx, add to the actions useMemo array
  2. For Storefront: edit src/components/ShopCommandPalette.tsx
  3. Each action needs: id, label, section (nav/filter/recent), icon, run()
  4. The run() typically calls router.push(...) with a URL the target page parses on mount

Update this manual

  1. Identify which manual the change belongs to (operator/seller/buyer/collector/ai)
  2. Find the existing page that covers the area, OR create a new one
  3. If new page: copy boilerplate from a sibling page, swap the title, replace content
  4. Update src/components/sidebar.js (in /docs/assets/) NAV constant if adding a new entry
  5. Add a "Recent decisions" entry to /docs/ai/recent-decisions.html if architecturally significant