Conventions
Codebase-wide rules. Follow these unconditionally.
Git + commits
- Branch names: SlabTrack uses
main, ToT + Storefront usemaster. Always verify withgit statusbefore pushing. - Commit headlines: lowercase verb-first, scope-prefixed. Examples:
fix: ShopCommandPalette icon type — accept style propphase 2: faceted FilterRail with live counts + 2-col layouttheme: 3 dramatic presets + 12 curated color palettes
- Commit body: explain WHY, not WHAT. The diff shows what; the body should answer "why this change."
- Co-author trailer: every commit ends with
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> - Multi-line messages: use HEREDOC
git commit -m "$(cat <<'EOF' ... EOF)"for proper formatting - Atomic commits: one logical change per commit. Don't bundle a bug fix with a refactor.
- Never
--no-verify: pre-commit hooks must pass. Fix the issue, not the bypass. - Never
--amendon pushed commits: create a new commit instead. - Force-push protection: never
--forcepush tomain/master. Use--force-with-leaseonly on personal branches.
Code style
- Comments: write WHY-comments only. Don't explain WHAT the code does (the code does that). Comment hidden constraints, surprising behaviors, workarounds tied to specific bugs.
- Don't reference current task in comments: bad —
// added for the Pull rollout. Good —// flipEligible:false bypasses solo Flip but multi-card carts still accept this. - Indent the way the surrounding code does: don't reformat untouched lines. Match existing.
- Imports at the top, named first, type imports last.
- No emoji in code unless the user explicitly asks.
TypeScript
- Strict mode:
thisorthat+slabtrack-storefrontare TS-strict.npx tsc --noEmit -p .must pass before commit. - Type assertions: prefer narrowing over
as any.as unknown as Twhen truly needed. - No
anyon new code. Existinganystays unless you're refactoring that file. - Prisma types: regenerate via
npx prisma generateafter schema changes. Don't hand-edit generated code.
Stripe rules
- Use the singleton:
import { getStripe } from '@/lib/stripe'. Don'tnew Stripe()inline. - Pin API version once:
STRIPE_API_VERSIONconstant insrc/lib/stripe.ts. Bump only when intentionally migrating. - Idempotency keys on every create:
checkout.sessions.create,refunds.create,paymentIntents.create. Pattern:{action}-{stable-id}. - Verify webhook signatures: every
/api/stripe/webhookcall verifies viastripe.webhooks.constructEvent(rawBody, sig, STRIPE_WEBHOOK_SECRET). Don't trust unsigned events. - Dedupe webhooks: ToT has
processedWebhookEventtable. Storefront should add this if not present. - Never log full secret keys. Log key prefix only (e.g.
sk_test_xxx[:8]).
Shippo rules
- Env var name:
SHIPPO_API_TOKENcanonical. Code in ToT falls back toSHIPPO_API_KEYfor legacy compat — don't add new code that uses the legacy name. - Origin address: ToT uses
getOriginAddress()insrc/app/api/shipping/route.tswhich throws loud if missing required env vars. Don't reintroduce a silent fallback. Storefront reads per-shop ship-from fromStorefrontrow. - USPS only by default: parcel size hardcoded to one slabbed card (6×4×1.5 in, 4 oz). Multi-card lots may need bigger sizing — adjust at request time, not in the default.
Database rules
- Prisma
db pushin start script: Railway's start script runsprisma db push --accept-data-loss --skip-generate || true. New columns get added on deploy. Type generation happens at build time. - Don't drop columns on a live DB without a migration plan. Add new columns, deprecate old, drop later.
- Index foreign keys: every FK should have a matching index unless you've measured no impact.
- Use transactions for multi-row writes that should atomically succeed or fail.
Security
- Never log secrets. JWT, Stripe keys, Shippo tokens, Solana private keys — never in logs.
- JWT validation: every protected route validates the JWT before doing work. Don't trust upstream proxies.
- Owner-gated mutations: every "modify shop X" route confirms the JWT user owns shop X. Don't use slug-only routing.
- SQL injection: use parameterized queries. Never string-concat user input into SQL.
- Frame-ancestors CSP: ToT serves with frame-ancestors allowing only known SlabTrack domains. Don't widen this.
Deployment
- Three Railway services: one per repo. Each with its own Postgres.
- Don't deploy from a branch: deploy from
main/masteronly. Feature branches stay un-deployed until merged. - Watch the build logs: Railway sometimes silently fails (the
|| trueon db push). If env vars don't behave as expected, check the build output. - Type errors break deploys silently: Next 16 + Turbopack swallow some TS errors in worker logs. Run
tsc --noEmitlocally first.
When in doubt
- Read the existing pattern: 95% of decisions have precedent in the codebase. Match it.
- Ask the user: don't guess at architectural intent. If the task is ambiguous, surface the ambiguity.
- Don't refactor opportunistically: do the requested change cleanly. Note the smell, don't fix it unless asked.