Skip to content
AdStack Engineering Docs
Esc
navigateopen⌘Jpreview
On this page

Conventions

The rules that exist because breaking them caused a real bug, plus the checklists for adding code.

Most of these are not style preferences. Each one exists because ignoring it produced a specific failure.

Names

Anything that must agree across code, the database, prompts, and Cloudflare lives in lib/registry.ts: module slugs, names and ids, worker names, queue names, domains, the R2 bucket, the AI Gateway alias. Code imports from it and never hardcodes. Renames happen there first, then in code, then in a migration.

Look modules up by MODULES[x].id. Never by slug or name, both of which are renameable in the admin UI.

Code

  • camelCase in code, DTOs, and API responses. snake_case only for database column names.
  • No any. Typecheck is enforced at zero errors, so there is no pre-existing noise to hide in.
  • No console.log. Use the structured logger in lib/logger.ts.
  • Types over prose for contracts. Define the schema (Zod, Drizzle) as the source and derive types from it. Reserve prose for why a system exists.
  • Prefer createServerFn over new API routes in new code.

UI

Never introduce a UI element type without grepping for an existing instance first. Default instincts (hand-drawn SVGs, ad-hoc tabs, a second icon set) are almost always wrong here.

Concern The one right answer
Icons Hugeicons only, <HugeiconsIcon icon={XIcon} size={..} />. Grep for an existing name before inventing one.
Listings Model on components/images/media-library-content.tsx. No dead disabled controls.
Sheets Right-side Sheet at w-[880px] max-w-[calc(100vw-1rem)], title font-semibold text-2xl tracking-tight.
Detail sheets DetailSheet plus DetailSheetSection, not an inline layout.
Prompt boxes TintedPrompt plus components/chat/composer.tsx. There is no second composer.
Output cards One OutputNavigator. One card design in options-selector.tsx.
Tokens #efefef card borders, no shadows, h-8 and h-7 compact buttons, CSS variable colors only.

Structured data (offers, research, personas, outputs) opens in the navigator sheet. Only genuine inline media (images, videos) renders directly in chat.

No em dashes and no horizontal rules in any user-facing copy. This applies to prompts too.

When asked to change something specific, change only that. A request to restyle buttons is not permission to touch the sidebar, badges, borders, or navigation.

Checklist: new API endpoint

Auth and ownership

requireUser first, then requireOwnedRow for any id you read or mutate.

Credits

Any generation cost goes through withCredits(..., waived: byok) so refunds are automatic.

Worker calls

submitToPipelineWorker, never a raw fetch without an ok check.

Names

From lib/registry.ts.

Casing

camelCase in code, snake_case only for columns.

Route tree

bun run build to regenerate routeTree.gen.ts, or createFileRoute("/api/x") will not typecheck.

Gates

bun run check, plus a unit test for any pure logic. Extract the logic (like lib/attachments.ts) so it tests without a database or a request.

Checklist: new agent

Covered in full on the Agents page. In short: registry, prompt files, migration, runtime loader with an inline fallback, agent definition and renderer, then check:truth and smoke.

Formatting trap

bun run format (ultracite fix --unsafe) rewrites the whole repo, not just your files. If you run it, revert the unrelated files before committing.

Known false positives

The dead-code scanners flag two things permanently. Both are correct as they are, and “cleaning” them breaks the build:

  • lib/registry.ts exports, which are the infrastructure source of truth and intentionally referenced by scripts and config.
  • workers/stubs/empty.js, which is a wrangler module alias.

Last updated on July 30, 2026

Was this page helpful?