# Project Rules

## Environment
- No git worktrees. Work inside the VS Code Dev Container (`.devcontainer/`).
- On "proceed" or session start: read `.claude/session_state.md` first.
- Container has limited memory — prefer Turbopack; watch for OOM before debugging hydration/auth.
- `DATABASE_URL` is baked into the container. Verify with `echo $DATABASE_URL` before seeds. Pass overrides inline: `DATABASE_URL="..." TENANT_CONFIG_PATH="..." pnpm db:seed`.
- Tenant name: `Confluent-CR` (hyphenated).
- Before debugging UI/auth/hydration: check container memory, dev server health, and `DATABASE_URL` first.

## Response Style
- Concise. No trailing summaries. No recap bullets unless asked.

## Session State
For typical small chores, tasks, and bug fixes, keep `.claude/session_state.md` empty.

Only write `.claude/session_state.md` when doing phased work such as "phase N of M" or a large feature spec/implementation in one shot. When session state is warranted, overwrite it with:
- **Objective** — high-level goal this session
- **Done** — completed steps (file paths, decisions)
- **Queue** — what's next, in order
- **Context** — gotchas and non-obvious constraints

## Development Workflow
- Conventional Commits: `feat:` `fix:` `docs:` `refactor:` `test:` `chore:`
- Read files in full before editing — never edit blind.
- Run `pnpm type-check && pnpm test` after any logic change; don't mark done if either is red.
- No concurrent `pnpm lint` + `pnpm build` — run sequentially.
- Prompt templates live in `prompts/`. Never hardcode long prompts in code.
- Clarify ambiguous requests before starting — don't guess and revert.
- Edit existing files; never create a new variant when one already exists.
- Medium/large features: write a spec doc in `docs/` first (ordered punchlist, clear done criteria).
- With a spec: follow `/tdd-from-spec` (`.claude/skills/tdd-from-spec/SKILL.md`) — failing tests (unit + integration + e2e) before implementation; full suite green twice before stopping.
- When using generic hooks (`useSyncExternalStore`, `useReducer`, etc.) always add an explicit return type annotation — TypeScript inference can widen union types silently under `next build`.

## Commit & Push Policy
- Never commit or push unless explicitly asked.
- Never auto-create placeholder/config files (e.g. `*.config.prod.yaml`) unprompted.
- Stop after completing a numbered session/task — don't auto-start the next.
- Commit message: concise one-line title, blank line, bulleted summary of changes.

## Deploy Model
- Production deploys are gated by `.github/workflows/deploy.yml`: migrations run first via `drizzle-kit migrate` (using `POSTGRES_URL_NON_POOLING`), then the Vercel deploy hook is triggered.
- `vercel.json` `ignoreCommand` is `exit 0` — Vercel's Git integration is fully disabled; all deploys go through the workflow.
- The Vercel build-time migration script (`scripts/migrate-for-vercel-build.ts`) remains as a fallback — it is a no-op once migrations have already been applied.
- Required GitHub secrets: `POSTGRES_URL_NON_POOLING` (direct DB URL), `VERCEL_DEPLOY_HOOK` (from Vercel dashboard → Settings → Git → Deploy Hooks).

## Testing
Every server-side change ships with tests in the same commit — non-negotiable.

**Requires a test:**
- New/modified API route → `tests/<feature>.integration.test.ts` (follow `respond-with-s-fields.integration.test.ts`)
- State machine change → update `tests/ticket-state-machine.test.ts`
- Config schema change → add cases to `tests/tenant-config-loader.test.ts`
- Inngest agent change → mock-LLM integration test (follow `completeness-agent.integration.test.ts`)

**No new test file needed:** pure UI/client components, email templates, config YAML values.

**Rules:**
- Curly apostrophes (`'` `'`) in schema strings break esbuild — use straight quotes.
- Never hollow out a failing test. Fix the code or update the test intentionally and explain in the commit.
- If a route already has a test, update it to cover the new behavior — don't leave it testing stale logic.

## Localization (i18n)

This project supports English (`en`) and Spanish (`es`). **Every user-visible string must be translated.**

### Rules
- Never hardcode English UI strings in components. Always use the `t()` helper with a key from the dictionary.
- When adding a new key to `src/lib/i18n/dictionaries/en.ts`, add the Spanish translation to `src/lib/i18n/dictionaries/es.ts` in the same commit.
- Server components that render before a session exists (e.g. login flows) should resolve the user's language via `getI18nByEmail(email)` from `@/lib/i18n/server` when an email is available, or fall back to `createI18n('en')`.
- Authenticated server components use `getI18nForUserId(session.user.id)` or `getI18n()` from `@/lib/i18n/server`.
- Client components receive translated strings as props from their server-component parent; they do not call the i18n server helpers directly.
- Translation keys use dot-notation nested under a page/feature namespace (e.g. `submit.description`, `loginConfirm.cta`).
- Interpolated values use `{placeholder}` syntax: `t('foo.bar', { name: 'Alice' })`. When a translated string must wrap a styled element (e.g. bold email), split the template string on the `{placeholder}` token rather than calling `t()` with the replacement.

### Dictionaries
- `src/lib/i18n/dictionaries/en.ts` — source of truth, typed as `EnglishDictionary`
- `src/lib/i18n/dictionaries/es.ts` — must mirror the shape of `en.ts`

## Observations log

While working on any task, if you notice something you would flag in a PR review — incorrect behavior, a security risk, a silent data issue, broken localization, or anything else a future developer should know about — append one row to [`OBSERVATIONS.md`](OBSERVATIONS.md). Do **not** search for issues proactively; only log things noticed in passing. Keep each entry to one line.

Use exactly one of these types:

| Type | Use for |
|------|---------|
| `bug` | Likely incorrect behavior |
| `security` | Auth, injection, data exposure |
| `data` | Silent data integrity or migration issue |
| `i18n` | Missing or broken localization |
| `debt` | Works today but will cause pain later |
| `ux` | Broken or confusing user-facing behavior |

Add new rows at the **top** of the table (below the header) so the most recent observations are seen first.

When you add a row, also output a single line to the chat in this format so the user knows something was logged:
> 📋 Observation logged (`<type>`): &lt;one-line summary&gt;

## Project References
- Architecture/schema: `docs/technical_spec.md`
- MVP scope: `docs/archive/mvp_punchlist.md`
- Standards: `.claude/project_rules.md`
- Tenant config: `tenant.config.yaml`

## Language
American English throughout. Prefer: organization, behavior, color, canceled, customize, analyze.

## Notifications
Ping via ntfy: `source .claude/ntfy.env && curl -s -d "$MESSAGE" "$NTFY_SERVER/$NTFY_TOPIC"`
If `.claude/ntfy.env` is missing, ask for the topic first.
