Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions .claude/skills/coding-standards/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
---
name: coding-standards
description: This repo's coding conventions covering TypeScript style, database (Drizzle/SQLite) schema rules, backend routing/services/validation/auth patterns, frontend/UI conventions, and testing requirements. Use whenever writing, editing, or reviewing code in this repo, before implementing any feature or fix, when doing a code review, or when asked about coding standards/conventions.
---

# Coding Standards

This project's conventions, split by domain. Load only the reference file(s) relevant to the code you're touching — don't read all of them for a one-line change.

## Reference files

| File | Covers |
|---|---|
| [general.md](general.md) | Object params for same-type args, no `any`, `~/*` import alias — applies to all TS code |
| [database.md](database.md) | Drizzle/SQLite: primary keys, timestamps, booleans, soft deletes, connections, price storage |
| [backend.md](backend.md) | Route Router v7 routing, auth, form/param/body validation, multi-intent actions, service result pattern, service test requirement |
| [frontend.md](frontend.md) | `cn()`, shadcn/component file locations, price display formatting |
| [testing.md](testing.md) | Which files need tests, the vitest + db-mock pattern |

## When to use this skill

- **Implementing**: before writing route, service, schema, or component code, check the reference file(s) for that domain and follow them.
- **Reviewing**: when reviewing a diff/PR, check changed files against the applicable reference file(s) and flag violations explicitly.
- **Answering "what's our convention for X"**: look it up in the relevant file rather than guessing or inferring from a single example.

## Routing by file type

- Touching `app/db/schema.ts` or migrations → [database.md](database.md)
- Touching `app/routes/**` → [backend.md](backend.md) (+ [general.md](general.md))
- Touching `app/services/**` or `*Service.ts` → [backend.md](backend.md) + [testing.md](testing.md)
- Touching `app/components/**` → [frontend.md](frontend.md)
- Touching `*.test.ts` → [testing.md](testing.md)
- Anything else TypeScript → [general.md](general.md)
35 changes: 35 additions & 0 deletions .claude/skills/coding-standards/backend.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Backend Conventions (Routes & Services)

## Routing

We use React Router v7 (file-based routing). Routes go in `app/routes/`. Each route file can export `loader`, `action`, `default` (component), `meta`, and `ErrorBoundary`. Don't put business logic directly in routes — call into services instead.

## Auth

Auth is cookie-based via `~/lib/session`. Use `getCurrentUserId(request)` in loaders/actions. Returns `number | null`. Redirect to `/login` if null.

## Form/param/body validation

For form validation in route actions, use `parseFormData(formData, zodSchema)` from `~/lib/validation`. It returns `{ success, data, errors }`. For route params use `parseParams`. For JSON request bodies use `parseJsonBody`.

## Multi-intent actions

When a single route action needs to handle multiple different form submissions (e.g. a page with both a "mark complete" button and a "delete comment" button), use a Zod discriminated union on an `intent` field:

```ts
const schema = z.discriminatedUnion("intent", [
z.object({ intent: z.literal("mark-complete") }),
z.object({
intent: z.literal("delete-comment"),
commentId: z.coerce.number(),
}),
]);
```

## Service result pattern

When returning tagged/discriminated results from services (not validation), use the `{ ok: true, ... } | { ok: false, error: string }` pattern. See `couponService` for reference.

## Service tests are required

Anything named as a "service" (e.g. `authTokenService.ts`) must have tests in an accompanying `.test.ts` file. See [testing.md](testing.md) for the required mocking pattern.
31 changes: 31 additions & 0 deletions .claude/skills/coding-standards/database.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Database Conventions (SQLite + Drizzle)

Database is SQLite via `better-sqlite3` + Drizzle. The `db` instance is initialized once in `app/db/index.ts` with WAL mode and foreign keys enabled. Don't create new `Database` connections in service code unless you have a really good reason — import the shared instance instead.

## Primary keys

DB ids are always `integer().primaryKey({ autoIncrement: true })`. Don't use UUIDs.

## Timestamps

Timestamps are stored as ISO strings in `text` columns, not as unix timestamps or integers.

```ts
$defaultFn(() => new Date().toISOString())
```

## Booleans

Booleans are stored as integers with Drizzle's `mode: "boolean"`:

```ts
integer("ppp_enabled", { mode: "boolean" })
```

## Soft deletes

Use a nullable `text("deleted_at")` column. Don't actually delete rows. See `lessonComments` in the schema for an example.

## Price values

Prices are stored in cents (integers), not decimal amounts. For display conventions see [frontend.md](frontend.md#price-formatting).
13 changes: 13 additions & 0 deletions .claude/skills/coding-standards/frontend.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Frontend Conventions

## Combining Tailwind classes

Use `cn()` from `~/lib/utils` for combining Tailwind classes. It's `clsx` + `tailwind-merge`.

## Component locations

Shadcn components live in `app/components/ui/`. Custom components go directly in `app/components/`. Don't nest component folders deeper than that.

## Price formatting

Price values are stored in cents (integers, see [database.md](database.md#price-values)). Use `formatPrice()` from `~/lib/utils` to display them — it handles the "Free" case for 0/null.
23 changes: 23 additions & 0 deletions .claude/skills/coding-standards/general.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# General TypeScript Conventions

Applies to all TypeScript code in the repo, backend or frontend.

## Object params over positional params

When a function has more than one parameter of the same type (e.g. two `string`s), use a single object parameter instead of positional parameters.

```ts
// BAD
const addUserToPost = (userId: string, postId: string) => {};

// GOOD
const addUserToPost = (opts: { userId: string; postId: string }) => {};
```

## No `any`

Don't use `any`. If you need a type you're not sure about, check the Drizzle schema or use `typeof` inference instead.

## Import alias

Use the `~/*` alias for anything inside `/app`. Don't use relative imports like `../../lib/utils` — use `~/lib/utils` instead.
21 changes: 21 additions & 0 deletions .claude/skills/coding-standards/testing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Testing Conventions

## Service tests are required

Anything named as a "service" (e.g. `authTokenService.ts`) must have tests in an accompanying `.test.ts` file.

## Vitest db mocking pattern

Tests use vitest with globals. Every test file needs to mock the db module like this:

```ts
let testDb: ReturnType<typeof createTestDb>;

vi.mock("~/db", () => ({
get db() {
return testDb;
},
}));
```

The mock MUST come before importing the service under test. Use `createTestDb()` and `seedBaseData()` from `~/test/setup` in `beforeEach`.
10 changes: 10 additions & 0 deletions .claude/skills/grill-me/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
name: grill-me
description: Interview the user relentlessly about a plan or design until reaching shared understanding, resolving each branch of the decision tree. Use when user wants to stress-test a plan, get grilled on their design, or mentions "grill me".
---

Interview me relentlessly about every aspect of this plan until we reach a shared understanding. Walk down each branch of the design tree, resolving dependencies between decisions one-by-one. For each question, provide your recommended answer.

Ask the questions one at a time.

If a question can be answered by exploring the codebase, explore the codebase instead.
13 changes: 13 additions & 0 deletions .claude/skills/handoff/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
name: handoff
description: Compact the current conversation into a handoff document for another agent to pick up.
argument-hint: "What will the next session be used for?"
---

Write a handoff document summarising the current conversation so a fresh agent can continue the work. Save it to a path produced by `mktemp -t handoff-XXXXXX.md` (read the file before you write to it).

Suggest the skills to be used, if any, by the next session.

Do not duplicate content already captured in other artifacts (PRDs, plans, ADRs, issues, commits, diffs). Reference them by path or URL instead.

If the user passed arguments, treat them as a description of what the next session will focus on and tailor the doc accordingly.
112 changes: 112 additions & 0 deletions .claude/skills/write-a-skill/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
# Writing Skills

## Process

1. **Gather requirements** - ask user about:
- What task/domain does the skill cover?
- What specific use cases should it handle?
- Does it need executable scripts or just instructions?
- Any reference materials to include?

2. **Draft the skill** - create:
- SKILL.md with concise instructions
- Additional reference files if content exceeds 500 lines
- Utility scripts if deterministic operations needed

3. **Review with user** - present draft and ask:
- Does this cover your use cases?
- Anything missing or unclear?
- Should any section be more/less detailed?

## Skill Structure

```
skill-name/
├── SKILL.md # Main instructions (required)
├── REFERENCE.md # Detailed docs (if needed)
├── EXAMPLES.md # Usage examples (if needed)
└── scripts/ # Utility scripts (if needed)
└── helper.js
```

## SKILL.md Template

```md
---
name: skill-name
description: Brief description of capability. Use when [specific triggers].
---

# Skill Name

## Quick start

[Minimal working example]

## Workflows

[Step-by-step processes with checklists for complex tasks]

## Advanced features

[Link to separate files: See [REFERENCE.md](REFERENCE.md)]
```

## Description Requirements

The description is **the only thing your agent sees** when deciding which skill to load. It's surfaced in the system prompt alongside all other installed skills. Your agent reads these descriptions and picks the relevant skill based on the user's request.

**Goal**: Give your agent just enough info to know:

1. What capability this skill provides
2. When/why to trigger it (specific keywords, contexts, file types)

**Format**:

- Max 1024 chars
- Write in third person
- First sentence: what it does
- Second sentence: "Use when [specific triggers]"

**Good example**:

```
Extract text and tables from PDF files, fill forms, merge documents. Use when working with PDF files or when user mentions PDFs, forms, or document extraction.
```

**Bad example**:

```
Helps with documents.
```

The bad example gives your agent no way to distinguish this from other document skills.

## When to Add Scripts

Add utility scripts when:

- Operation is deterministic (validation, formatting)
- Same code would be generated repeatedly
- Errors need explicit handling

Scripts save tokens and improve reliability vs generated code.

## When to Split Files

Split into separate files when:

- SKILL.md exceeds 100 lines
- Content has distinct domains (finance vs sales schemas)
- Advanced features are rarely needed

## Review Checklist

After drafting, verify:

- [ ] Description includes triggers ("Use when...")
- [ ] SKILL.md under 100 lines
- [ ] No time-sensitive info
- [ ] Consistent terminology
- [ ] Concrete examples included
- [ ] References one level deep
2 changes: 2 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Coding standards for this repo (TypeScript conventions, database schema rules, backend routing/services/validation/auth patterns, frontend conventions, and testing requirements) live in the `coding-standards` skill — see `.claude/skills/coding-standards/SKILL.md`.

Loading