Always use Conventional Commits format:
<type>(<scope>): <short summary>
<optional body>
Types: feat, fix, docs, style, refactor, test, chore, ci, build, perf
Scopes: api, mobile, db, auth, groups, spots, infra, e2e, ci
Examples:
feat(api): add spots CRUD routes with visibility access controlfix(auth): handle expired Clerk JWT tokens gracefullytest(api): cover group membership edge caseschore: update dependencies
Monorepo with npm workspaces (apps/*, packages/*, workers/*):
stash-spot/
├── apps/
│ └── mobile/ # Expo SDK 54 (React Native 0.81, React 19)
│ ├── app/ # Expo Router v6 file-based routing
│ │ ├── _layout.tsx # Root layout — ClerkProvider + StatusBar
│ │ ├── index.tsx # Entry redirect (onboarding → auth → tabs)
│ │ ├── onboarding.tsx # First-launch onboarding carousel (4 slides)
│ │ ├── (auth)/ # Sign-in / sign-up screens
│ │ │ ├── _layout.tsx
│ │ │ ├── sign-in.tsx
│ │ │ └── sign-up.tsx
│ │ ├── (tabs)/ # Main tab navigator
│ │ │ ├── _layout.tsx # Tab bar config (map, feed, profile)
│ │ │ ├── map.tsx # MapView with spot markers
│ │ │ ├── feed.tsx # Spot feed list
│ │ │ └── profile.tsx # User profile
│ │ └── spot/ # Spot detail stack
│ │ ├── _layout.tsx
│ │ ├── [id].tsx # Spot detail (view/delete)
│ │ └── add.tsx # Add new spot form
│ ├── components/
│ │ └── SpotCard.tsx
│ ├── hooks/
│ │ ├── useSpots.ts
│ │ └── useGroups.ts
│ └── lib/
│ ├── api.ts # API base URL config
│ ├── clerk.ts # Clerk tokenCache via expo-secure-store
│ └── theme.ts # Colors, fonts, spacing constants
│
├── workers/
│ └── api/ # Cloudflare Worker — Hono framework
│ └── src/
│ ├── index.ts # App entry — mounts routes on /api/v1
│ ├── types.ts # Env type (D1, R2, Clerk keys, DO)
│ ├── middleware/
│ │ └── auth.ts # Clerk JWT verification → c.set("userId")
│ ├── repos/
│ │ ├── spots.repo.ts # Spot DB operations (queries, inserts, updates)
│ │ ├── groups.repo.ts # Group DB operations
│ │ └── users.repo.ts # User DB operations
│ ├── services/
│ │ ├── errors.ts # ServiceError class (NOT_FOUND, FORBIDDEN, etc.)
│ │ ├── spots.service.ts # Spot business logic + validation
│ │ ├── groups.service.ts # Group business logic + validation
│ │ └── users.service.ts # User business logic + validation
│ ├── routes/
│ │ ├── spots.ts # Thin HTTP handlers — delegates to spots.service
│ │ ├── spots.test.ts # Vitest unit tests (7 tests)
│ │ ├── groups.ts # Thin HTTP handlers — delegates to groups.service
│ │ └── users.ts # Thin HTTP handlers — delegates to users.service
│ ├── durable-objects/
│ │ └── SpotPresence.ts # Tracks live viewers per spot
│ ├── db/
│ │ ├── schema.ts # TS interfaces, VALID_TAGS only
│ │ ├── migrations/
│ │ │ └── 0001_initial.sql # users, spots, groups, group_members, spot_groups, spot_tags
│ │ ├── seed.ts # Creates test users via Clerk + seed spots
│ │ ├── migrate.ts # Applies migrations via Cloudflare D1 REST API
│ │ └── teardown.ts # Drops all tables (for CI cleanup)
│ └── test-utils.ts # Test helpers
│
├── packages/
│ ├── api-types/ # Shared type re-exports
│ │ └── index.ts # Re-exports AppType, schema types, VALID_TAGS from worker
│ └── api-client/ # Typed API client
│ └── src/
│ └── index.ts # createApiClient(baseUrl, token) using hono/client
│
├── sst.config.ts # SST v3 infra — D1 (StashDb), R2 (Photos), Worker (Api)
├── .github/workflows/
│ ├── preview.yml # PR preview: deploy → migrate → seed → E2E
│ ├── deploy.yml # Production deploy on merge to main
│ └── teardown.yml # Cleanup preview stages on PR close
└── package.json # Root — workspaces, scripts, engines: node>=22
- D1 database (
StashDb) — SQLite at the edge, linked to worker - R2 bucket (
Photos) — spot photo storage, linked to worker - Worker (
Api) — Hono app with D1 + R2 bindings + Clerk env vars - Outputs:
api(worker URL),dbId(D1 database UUID)
| Method | Path | Description |
|---|---|---|
| GET | /spots |
List visible spots (respects visibility + group membership) |
| GET | /spots/:id |
Get spot detail with access check |
| POST | /spots |
Create spot (with optional tags) |
| PATCH | /spots/:id |
Update spot (owner only) |
| DELETE | /spots/:id |
Delete spot (owner only) |
| POST | /spots/:id/groups |
Share spot to a group |
| DELETE | /spots/:id/groups/:groupId |
Unshare spot from group |
| GET | /groups |
List user's groups |
| POST | /groups |
Create group (generates SK8-XXXXXX invite code) |
| POST | /groups/join |
Join group via invite code |
| GET | /groups/:id/members |
List group members |
| DELETE | /groups/:id/members/:userId |
Remove member (owner only) |
| GET | /users/me |
Get or auto-create user record |
| PATCH | /users/me |
Update username / avatar |
Six tables: users, spots, groups, group_members, spot_groups, spot_tags.
Visibility is enforced at query level — see repos/spots.repo.ts (findVisibleSpots and findSpotByIdForUser).
Tags are normalized in spot_tags (not comma-separated). Valid tags: ledge, rail, gap, stairs, manual_pad, transition, other.
sst dev— local development (starts worker + bindings)npm run test:unit— worker unit tests (vitest + @cloudflare/vitest-pool-workers)npm run test:e2e— Playwright API testsnpm run test:mobile— Maestro mobile E2E flowsnpm run typecheck— TypeScript check across all workspaces- Never deploy to preview or production — leave that to CI/humans
- preview.yml — On PR: SST deploy to preview stage → apply D1 migrations via REST API → seed test data → run E2E tests → comment results on PR
- deploy.yml — On merge to main: SST deploy to production
- teardown.yml — On PR close: SST remove preview stage
D1 migrations are applied via the Cloudflare REST API (/client/v4/accounts/.../d1/database/.../query) because SST's migrations arg doesn't apply them at the version we use.
- Never trust client-supplied user IDs — always derive from verified Clerk JWT
- Enforce spot visibility (private/group/public) at the DB query level
- Use
nanoidfor spot and group IDs - Spot tags are normalized in
spot_tagstable, not comma-separated - Invite codes: 6-char alphanumeric uppercase, formatted as
SK8-XXXXXX
This file is the source of truth for agents working on this codebase. Update it whenever you make a structural change, including but not limited to:
- Adding, removing, or renaming routes — update the API Routes table
- Adding or modifying DB tables/columns — update the Database Schema section and the directory tree if a new migration file is added
- Adding new workspaces, apps, or packages — update the Project Structure tree
- Adding new screens, components, or hooks to the mobile app — update the tree
- Changing infrastructure resources in
sst.config.ts— update the Infrastructure section - Adding or modifying CI/CD workflows — update the CI/CD section
- Changing dev commands or test runners — update the Development section
- Adding new key rules or conventions — add them to the relevant section
A quick grep for the thing you changed in this file will tell you if it's mentioned. If it is, update it. If you add something new that a future agent would need to know about, add it.