Skip to main content

Slice development pattern

Each slice is built end-to-end before the next slice begins. This page is the recipe for "how do I build a slice."

Drafted from planning · v0.1

The seven steps

  1. Spec — what does the slice deliver, end to end?
  2. Schema — migration files for any new tables/columns
  3. Bible — entity pages, lifecycle (if applicable), slice page
  4. Backend — endpoints, helpers, audit-wiring
  5. Frontend — screens, in-situ editing, server-rendered fallbacks
  6. Tests — unit + integration; the cron path if applicable
  7. Migration — AIM ETL update for inbound data

Then deploy, smoke test, ship.

Step 1: Spec

In the slice page (docs/domain/slices/slice-N-{name}.md), write:

  • Scope: what's in / what's out
  • The endpoints (HTTP routes)
  • The UI (which screens, what changes)
  • Acceptance criteria

This forces clarity before writing code. ~30-60 minutes of writing saves 1-2 hours of "I don't actually know what I'm building" later.

Step 2: Schema

Migration file: migrations/0NN-{slice-name}.sql. Include:

  • All new tables for the slice
  • Indexes (FK indexes mandatory; covering indexes where queries justify)
  • Any FK additions to existing tables
  • Seed rows for stable enumerations
# Verify locally
wrangler d1 migrations apply helm-dev --local
# Inspect
wrangler d1 execute helm-dev --local --command "SELECT * FROM {new_table} LIMIT 3;"

Step 3: Bible

Update or create:

  • docs/domain/entities/{entity}.md — for each new entity in the slice
  • docs/domain/lifecycles/{lifecycle}.mdx — if a new state machine
  • docs/domain/slices/slice-N-{name}.md — the slice page

Write these before the code. They double as the design doc.

Step 4: Backend

In src/index.js:

  • Add the handler functions (named for the endpoint)
  • Add the switch entries

In src/lib/:

  • Helpers shared across handlers
  • Adapters for new external services

Every mutation goes through withAudit. Every external write uses an idempotency key. Every endpoint emits a structured log line.

async function apiTicketsCreate(req, env, ctx) {
const body = await req.json();
validateTicketCreate(body);
const ticket = await db.run(`INSERT INTO service_tickets ...`).bind(...);
await withAudit({ table: 'service_tickets', row_id: ticket.id, action: 'ticket.created', after: ticket, ctx });
return json(ticket);
}

Step 5: Frontend

In public/index.html (or a new partial):

In public/js/{slice}.js:

  • The screen's enhancer module
  • In-situ editing handlers (if applicable, per in-situ editing)

Test in the browser at localhost:8787. Click through the golden path. Try the error paths. Check the audit log in D1 after each mutation.

Step 6: Tests

In test/{slice}.test.js:

  • Unit tests for the helpers
  • Integration tests for the endpoints (via wrangler dev's in-process testing, or Vitest with @cloudflare/vitest-pool-workers)
  • Smoke tests for the cron paths

Run: npm test.

Step 7: Migration (if applicable)

If the slice has inbound AIM data, update migrate_aim.py:

  • Add a slice_N_* function
  • Add the --slice N CLI flag
  • Update migrate_aim.py --all to include it

Test against a fresh local D1:

wrangler d1 execute helm-dev --local --command "DROP TABLE IF EXISTS {tables};"
wrangler d1 migrations apply helm-dev --local
python migrate_aim.py --slice N --target=local
python verify_schema.py --slice N

Smoke and ship

After 1-7:

  • wrangler dev --env=development --local and click through the slice
  • Run the audit chain verification (planned daily cron, but trigger manually for this PR)
  • npm run build && npm test clean
  • Commit on a feature branch; PR; review; merge
  • Deploy (deploy to production)

When a slice takes longer than a week

Stop. Reassess. Either:

  • The slice is too big — split it
  • The spec was wrong — fix the spec first
  • The pattern is fighting you — flag a tech-debt note and proceed with the right amount of mess

Don't let a slice drift for two weeks. Better to ship a smaller slice and start the next one.

See also