chore: configure prettier with ignore rules and format scripts + running format
This commit is contained in:
parent
22bb8a1e4c
commit
ce42eb1811
12 changed files with 150 additions and 89 deletions
19
.prettierignore
Normal file
19
.prettierignore
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
.tmp/
|
||||||
|
|
||||||
|
# Build outputs
|
||||||
|
dist/
|
||||||
|
*.tsbuildinfo
|
||||||
|
|
||||||
|
# Dependencies
|
||||||
|
node_modules/
|
||||||
|
|
||||||
|
# Environment files
|
||||||
|
.env*
|
||||||
|
|
||||||
|
# Logs (if you create them)
|
||||||
|
logs/
|
||||||
|
|
||||||
|
# Coverage reports (when you add testing)
|
||||||
|
coverage/
|
||||||
|
|
||||||
|
pnpm-lock.yaml
|
||||||
13
.prettierrc
Normal file
13
.prettierrc
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
"printWidth": 80,
|
||||||
|
"tabWidth": 2,
|
||||||
|
"useTabs": false,
|
||||||
|
"semi": true,
|
||||||
|
"singleQuote": false,
|
||||||
|
"jsxSingleQuote": false,
|
||||||
|
"trailingComma": "all",
|
||||||
|
"bracketSpacing": true,
|
||||||
|
"objectWrap": "collapse",
|
||||||
|
"bracketSameLine": false,
|
||||||
|
"arrowParens": "always"
|
||||||
|
}
|
||||||
|
|
@ -2,11 +2,8 @@
|
||||||
"extends": "../../tsconfig.base.json",
|
"extends": "../../tsconfig.base.json",
|
||||||
"references": [
|
"references": [
|
||||||
{ "path": "../../packages/shared" },
|
{ "path": "../../packages/shared" },
|
||||||
{ "path": "../../packages/db" },
|
{ "path": "../../packages/db" }
|
||||||
],
|
],
|
||||||
"compilerOptions": {
|
"compilerOptions": { "module": "NodeNext", "moduleResolution": "NodeNext" },
|
||||||
"module": "NodeNext",
|
"include": ["src"]
|
||||||
"moduleResolution": "NodeNext",
|
|
||||||
},
|
|
||||||
"include": ["src"],
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
{
|
{
|
||||||
"extends": "../../tsconfig.base.json",
|
"extends": "../../tsconfig.base.json",
|
||||||
"references": [{ "path": "../../packages/shared" }],
|
"references": [{ "path": "../../packages/shared" }]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ Each phase produces a working, deployable increment. Nothing is built speculativ
|
||||||
---
|
---
|
||||||
|
|
||||||
## Phase 0 — Foundation
|
## Phase 0 — Foundation
|
||||||
|
|
||||||
**Goal**: Empty repo that builds, lints, and runs end-to-end.
|
**Goal**: Empty repo that builds, lints, and runs end-to-end.
|
||||||
**Done when**: `pnpm dev` starts both apps; `GET /api/health` returns 200; React renders a hello page.
|
**Done when**: `pnpm dev` starts both apps; `GET /api/health` returns 200; React renders a hello page.
|
||||||
|
|
||||||
|
|
@ -23,6 +24,7 @@ Each phase produces a working, deployable increment. Nothing is built speculativ
|
||||||
---
|
---
|
||||||
|
|
||||||
## Phase 1 — Vocabulary Data
|
## Phase 1 — Vocabulary Data
|
||||||
|
|
||||||
**Goal**: Word data lives in the DB and can be queried via the API.
|
**Goal**: Word data lives in the DB and can be queried via the API.
|
||||||
**Done when**: `GET /api/terms?pair=en-it&limit=10` returns 10 terms, each with 3 distractors attached.
|
**Done when**: `GET /api/terms?pair=en-it&limit=10` returns 10 terms, each with 3 distractors attached.
|
||||||
|
|
||||||
|
|
@ -39,6 +41,7 @@ Each phase produces a working, deployable increment. Nothing is built speculativ
|
||||||
---
|
---
|
||||||
|
|
||||||
## Phase 2 — Auth
|
## Phase 2 — Auth
|
||||||
|
|
||||||
**Goal**: Users can log in via Google or GitHub and stay logged in.
|
**Goal**: Users can log in via Google or GitHub and stay logged in.
|
||||||
**Done when**: JWT from OpenAuth is validated by the API; protected routes redirect unauthenticated users; user row is created on first login.
|
**Done when**: JWT from OpenAuth is validated by the API; protected routes redirect unauthenticated users; user row is created on first login.
|
||||||
|
|
||||||
|
|
@ -57,6 +60,7 @@ Each phase produces a working, deployable increment. Nothing is built speculativ
|
||||||
---
|
---
|
||||||
|
|
||||||
## Phase 3 — Single-player Mode
|
## Phase 3 — Single-player Mode
|
||||||
|
|
||||||
**Goal**: A logged-in user can complete a full solo quiz session.
|
**Goal**: A logged-in user can complete a full solo quiz session.
|
||||||
**Done when**: User sees 10 questions, picks answers, sees their final score.
|
**Done when**: User sees 10 questions, picks answers, sees their final score.
|
||||||
|
|
||||||
|
|
@ -71,6 +75,7 @@ Each phase produces a working, deployable increment. Nothing is built speculativ
|
||||||
---
|
---
|
||||||
|
|
||||||
## Phase 4 — Multiplayer Rooms (Lobby)
|
## Phase 4 — Multiplayer Rooms (Lobby)
|
||||||
|
|
||||||
**Goal**: Players can create and join rooms; the host sees all joined players in real time.
|
**Goal**: Players can create and join rooms; the host sees all joined players in real time.
|
||||||
**Done when**: Two browser tabs can join the same room and see each other's display names update live via WebSocket.
|
**Done when**: Two browser tabs can join the same room and see each other's display names update live via WebSocket.
|
||||||
|
|
||||||
|
|
@ -92,6 +97,7 @@ Each phase produces a working, deployable increment. Nothing is built speculativ
|
||||||
---
|
---
|
||||||
|
|
||||||
## Phase 5 — Multiplayer Game
|
## Phase 5 — Multiplayer Game
|
||||||
|
|
||||||
**Goal**: Host starts a game; all players answer simultaneously in real time; a winner is declared.
|
**Goal**: Host starts a game; all players answer simultaneously in real time; a winner is declared.
|
||||||
**Done when**: 2–4 players complete a 10-round game with correct live scores and a winner screen.
|
**Done when**: 2–4 players complete a 10-round game with correct live scores and a winner screen.
|
||||||
|
|
||||||
|
|
@ -110,6 +116,7 @@ Each phase produces a working, deployable increment. Nothing is built speculativ
|
||||||
---
|
---
|
||||||
|
|
||||||
## Phase 6 — Production Deployment
|
## Phase 6 — Production Deployment
|
||||||
|
|
||||||
**Goal**: App is live on Hetzner, accessible via HTTPS on all subdomains.
|
**Goal**: App is live on Hetzner, accessible via HTTPS on all subdomains.
|
||||||
**Done when**: `https://app.yourdomain.com` loads; `wss://api.yourdomain.com` connects; auth flow works end-to-end.
|
**Done when**: `https://app.yourdomain.com` loads; `wss://api.yourdomain.com` connects; auth flow works end-to-end.
|
||||||
|
|
||||||
|
|
@ -122,7 +129,7 @@ Each phase produces a working, deployable increment. Nothing is built speculativ
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Phase 7 — Polish & Hardening *(post-MVP)*
|
## Phase 7 — Polish & Hardening _(post-MVP)_
|
||||||
|
|
||||||
Not required to ship, but address before real users arrive.
|
Not required to ship, but address before real users arrive.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,33 +14,36 @@ A multiplayer English–Italian vocabulary trainer with a Duolingo-style quiz in
|
||||||
|
|
||||||
## 2. Technology Stack
|
## 2. Technology Stack
|
||||||
|
|
||||||
| Layer | Technology |
|
| Layer | Technology |
|
||||||
|---|---|
|
| -------------------- | ----------------------------- |
|
||||||
| Monorepo | pnpm workspaces |
|
| Monorepo | pnpm workspaces |
|
||||||
| Frontend | React 18, Vite, TypeScript |
|
| Frontend | React 18, Vite, TypeScript |
|
||||||
| Routing | TanStack Router |
|
| Routing | TanStack Router |
|
||||||
| Server state | TanStack Query |
|
| Server state | TanStack Query |
|
||||||
| Client state | Zustand |
|
| Client state | Zustand |
|
||||||
| Styling | Tailwind CSS + shadcn/ui |
|
| Styling | Tailwind CSS + shadcn/ui |
|
||||||
| Backend | Node.js, Express, TypeScript |
|
| Backend | Node.js, Express, TypeScript |
|
||||||
| Realtime | WebSockets (`ws` library) |
|
| Realtime | WebSockets (`ws` library) |
|
||||||
| Database | PostgreSQL 16 |
|
| Database | PostgreSQL 16 |
|
||||||
| ORM | Drizzle ORM |
|
| ORM | Drizzle ORM |
|
||||||
| Cache / Queue | Valkey 8 |
|
| Cache / Queue | Valkey 8 |
|
||||||
| Auth | OpenAuth (Google + GitHub) |
|
| Auth | OpenAuth (Google + GitHub) |
|
||||||
| Validation | Zod (shared schemas) |
|
| Validation | Zod (shared schemas) |
|
||||||
| Testing | Vitest, React Testing Library |
|
| Testing | Vitest, React Testing Library |
|
||||||
| Linting / Formatting | ESLint, Prettier |
|
| Linting / Formatting | ESLint, Prettier |
|
||||||
| Containerisation | Docker, Docker Compose |
|
| Containerisation | Docker, Docker Compose |
|
||||||
| Hosting | Hetzner VPS |
|
| Hosting | Hetzner VPS |
|
||||||
|
|
||||||
### Why `ws` over Socket.io
|
### Why `ws` over Socket.io
|
||||||
|
|
||||||
`ws` is the raw WebSocket library. For rooms of 2–4 players there is no need for Socket.io's transport fallbacks or room-management abstractions. The protocol is defined explicitly in `packages/shared`, which gives the same guarantees without the overhead.
|
`ws` is the raw WebSocket library. For rooms of 2–4 players there is no need for Socket.io's transport fallbacks or room-management abstractions. The protocol is defined explicitly in `packages/shared`, which gives the same guarantees without the overhead.
|
||||||
|
|
||||||
### Why Valkey
|
### Why Valkey
|
||||||
|
|
||||||
Valkey stores ephemeral room state that does not need to survive a server restart. It keeps the PostgreSQL schema clean and makes room lookups O(1).
|
Valkey stores ephemeral room state that does not need to survive a server restart. It keeps the PostgreSQL schema clean and makes room lookups O(1).
|
||||||
|
|
||||||
### Why pnpm workspaces without Turborepo
|
### Why pnpm workspaces without Turborepo
|
||||||
|
|
||||||
Turborepo adds parallel task running and build caching on top of pnpm workspaces. For a two-app monorepo of this size, the plain pnpm workspace commands (`pnpm -r run build`, `pnpm --filter`) are sufficient and there is one less tool to configure and maintain.
|
Turborepo adds parallel task running and build caching on top of pnpm workspaces. For a two-app monorepo of this size, the plain pnpm workspace commands (`pnpm -r run build`, `pnpm --filter`) are sufficient and there is one less tool to configure and maintain.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
@ -80,6 +83,7 @@ vocab-trainer/
|
||||||
### pnpm workspace config
|
### pnpm workspace config
|
||||||
|
|
||||||
`pnpm-workspace.yaml` declares:
|
`pnpm-workspace.yaml` declares:
|
||||||
|
|
||||||
```
|
```
|
||||||
packages:
|
packages:
|
||||||
- 'apps/*'
|
- 'apps/*'
|
||||||
|
|
@ -89,6 +93,7 @@ packages:
|
||||||
### Root scripts
|
### Root scripts
|
||||||
|
|
||||||
The root `package.json` defines convenience scripts that delegate to workspaces:
|
The root `package.json` defines convenience scripts that delegate to workspaces:
|
||||||
|
|
||||||
- `dev` — starts `api` and `web` in parallel
|
- `dev` — starts `api` and `web` in parallel
|
||||||
- `build` — builds all packages in dependency order
|
- `build` — builds all packages in dependency order
|
||||||
- `test` — runs Vitest across all workspaces
|
- `test` — runs Vitest across all workspaces
|
||||||
|
|
@ -123,23 +128,23 @@ Each layer only communicates with the layer directly below it. Business logic li
|
||||||
|
|
||||||
### Domain structure
|
### Domain structure
|
||||||
|
|
||||||
| Subdomain | Service |
|
| Subdomain | Service |
|
||||||
|---|---|
|
| --------------------- | ----------------------- |
|
||||||
| `app.yourdomain.com` | React frontend |
|
| `app.yourdomain.com` | React frontend |
|
||||||
| `api.yourdomain.com` | Express API + WebSocket |
|
| `api.yourdomain.com` | Express API + WebSocket |
|
||||||
| `auth.yourdomain.com` | OpenAuth service |
|
| `auth.yourdomain.com` | OpenAuth service |
|
||||||
|
|
||||||
### Docker Compose services (production)
|
### Docker Compose services (production)
|
||||||
|
|
||||||
| Container | Role |
|
| Container | Role |
|
||||||
|---|---|
|
| ---------------- | ------------------------------------------- |
|
||||||
| `postgres` | PostgreSQL 16, named volume |
|
| `postgres` | PostgreSQL 16, named volume |
|
||||||
| `valkey` | Valkey 8, ephemeral (no persistence needed) |
|
| `valkey` | Valkey 8, ephemeral (no persistence needed) |
|
||||||
| `openauth` | OpenAuth service |
|
| `openauth` | OpenAuth service |
|
||||||
| `api` | Express + WS server |
|
| `api` | Express + WS server |
|
||||||
| `web` | Nginx serving the Vite build |
|
| `web` | Nginx serving the Vite build |
|
||||||
| `nginx-proxy` | Automatic reverse proxy |
|
| `nginx-proxy` | Automatic reverse proxy |
|
||||||
| `acme-companion` | Let's Encrypt certificate automation |
|
| `acme-companion` | Let's Encrypt certificate automation |
|
||||||
|
|
||||||
```
|
```
|
||||||
nginx-proxy (:80/:443)
|
nginx-proxy (:80/:443)
|
||||||
|
|
@ -155,6 +160,7 @@ SSL is fully automatic via `nginx-proxy` + `acme-companion`. No manual Certbot n
|
||||||
## 6. Data Model
|
## 6. Data Model
|
||||||
|
|
||||||
### Design principle
|
### Design principle
|
||||||
|
|
||||||
Words are modelled as language-neutral **terms** with one or more **translations** per language. Adding a new language pair (e.g. English–French) requires **no schema changes** — only new rows in `translations` and `language_pairs`. The flat `english/italian` column pattern is explicitly avoided.
|
Words are modelled as language-neutral **terms** with one or more **translations** per language. Adding a new language pair (e.g. English–French) requires **no schema changes** — only new rows in `translations` and `language_pairs`. The flat `english/italian` column pattern is explicitly avoided.
|
||||||
|
|
||||||
### Core tables
|
### Core tables
|
||||||
|
|
@ -222,10 +228,12 @@ CREATE INDEX ON room_players (user_id);
|
||||||
## 7. Vocabulary Data — WordNet + OMW
|
## 7. Vocabulary Data — WordNet + OMW
|
||||||
|
|
||||||
### Source
|
### Source
|
||||||
|
|
||||||
- **Princeton WordNet** — English words + synset IDs
|
- **Princeton WordNet** — English words + synset IDs
|
||||||
- **Open Multilingual Wordnet (OMW)** — Italian translations keyed by synset ID
|
- **Open Multilingual Wordnet (OMW)** — Italian translations keyed by synset ID
|
||||||
|
|
||||||
### Extraction process
|
### Extraction process
|
||||||
|
|
||||||
1. Run `scripts/extract_omw.py` once locally using NLTK
|
1. Run `scripts/extract_omw.py` once locally using NLTK
|
||||||
2. Filter to the 1 000 most common nouns (by WordNet frequency data)
|
2. Filter to the 1 000 most common nouns (by WordNet frequency data)
|
||||||
3. Output: `packages/db/src/seed.json` — committed to the repo
|
3. Output: `packages/db/src/seed.json` — committed to the repo
|
||||||
|
|
@ -243,9 +251,9 @@ The API validates the JWT from OpenAuth on every protected request. User rows ar
|
||||||
|
|
||||||
**Auth endpoint on the API:**
|
**Auth endpoint on the API:**
|
||||||
|
|
||||||
| Method | Path | Description |
|
| Method | Path | Description |
|
||||||
|---|---|---|
|
| ------ | -------------- | --------------------------- |
|
||||||
| GET | `/api/auth/me` | Validate token, return user |
|
| GET | `/api/auth/me` | Validate token, return user |
|
||||||
|
|
||||||
All other auth flows (login, callback, token refresh) are handled entirely by OpenAuth — the frontend redirects to `auth.yourdomain.com` and receives a JWT back.
|
All other auth flows (login, callback, token refresh) are handled entirely by OpenAuth — the frontend redirects to `auth.yourdomain.com` and receives a JWT back.
|
||||||
|
|
||||||
|
|
@ -257,25 +265,25 @@ All endpoints prefixed `/api`. Request and response bodies validated with Zod on
|
||||||
|
|
||||||
### Vocabulary
|
### Vocabulary
|
||||||
|
|
||||||
| Method | Path | Description |
|
| Method | Path | Description |
|
||||||
|---|---|---|
|
| ------ | ---------------------------- | --------------------------------- |
|
||||||
| GET | `/language-pairs` | List active language pairs |
|
| GET | `/language-pairs` | List active language pairs |
|
||||||
| GET | `/terms?pair=en-it&limit=10` | Fetch quiz terms with distractors |
|
| GET | `/terms?pair=en-it&limit=10` | Fetch quiz terms with distractors |
|
||||||
|
|
||||||
### Rooms
|
### Rooms
|
||||||
|
|
||||||
| Method | Path | Description |
|
| Method | Path | Description |
|
||||||
|---|---|---|
|
| ------ | ------------------- | ----------------------------------- |
|
||||||
| POST | `/rooms` | Create a room → returns room + code |
|
| POST | `/rooms` | Create a room → returns room + code |
|
||||||
| GET | `/rooms/:code` | Get current room state |
|
| GET | `/rooms/:code` | Get current room state |
|
||||||
| POST | `/rooms/:code/join` | Join a room |
|
| POST | `/rooms/:code/join` | Join a room |
|
||||||
|
|
||||||
### Users
|
### Users
|
||||||
|
|
||||||
| Method | Path | Description |
|
| Method | Path | Description |
|
||||||
|---|---|---|
|
| ------ | ----------------- | ---------------------- |
|
||||||
| GET | `/users/me` | Current user profile |
|
| GET | `/users/me` | Current user profile |
|
||||||
| GET | `/users/me/stats` | Games played, win rate |
|
| GET | `/users/me/stats` | Games played, win rate |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -287,22 +295,22 @@ All messages are JSON: `{ type: string, payload: unknown }`. The full set of typ
|
||||||
|
|
||||||
### Client → Server
|
### Client → Server
|
||||||
|
|
||||||
| type | payload | Description |
|
| type | payload | Description |
|
||||||
|---|---|---|
|
| ------------- | -------------------------- | -------------------------------- |
|
||||||
| `room:join` | `{ code }` | Subscribe to a room's WS channel |
|
| `room:join` | `{ code }` | Subscribe to a room's WS channel |
|
||||||
| `room:leave` | — | Unsubscribe |
|
| `room:leave` | — | Unsubscribe |
|
||||||
| `room:start` | — | Host starts the game |
|
| `room:start` | — | Host starts the game |
|
||||||
| `game:answer` | `{ questionId, answerId }` | Player submits an answer |
|
| `game:answer` | `{ questionId, answerId }` | Player submits an answer |
|
||||||
|
|
||||||
### Server → Client
|
### Server → Client
|
||||||
|
|
||||||
| type | payload | Description |
|
| type | payload | Description |
|
||||||
|---|---|---|
|
| -------------------- | -------------------------------------------------- | ----------------------------------------- |
|
||||||
| `room:state` | Full room snapshot | Sent on join and on any player join/leave |
|
| `room:state` | Full room snapshot | Sent on join and on any player join/leave |
|
||||||
| `game:question` | `{ id, prompt, options[], timeLimit }` | New question broadcast to all players |
|
| `game:question` | `{ id, prompt, options[], timeLimit }` | New question broadcast to all players |
|
||||||
| `game:answer_result` | `{ questionId, correct, correctAnswerId, scores }` | Broadcast after all answer or timeout |
|
| `game:answer_result` | `{ questionId, correct, correctAnswerId, scores }` | Broadcast after all answer or timeout |
|
||||||
| `game:finished` | `{ scores[], winner }` | End of game summary |
|
| `game:finished` | `{ scores[], winner }` | End of game summary |
|
||||||
| `error` | `{ message }` | Protocol or validation error |
|
| `error` | `{ message }` | Protocol or validation error |
|
||||||
|
|
||||||
### Multiplayer game mechanic — simultaneous answers
|
### Multiplayer game mechanic — simultaneous answers
|
||||||
|
|
||||||
|
|
@ -382,16 +390,17 @@ TanStack Query handles all server data fetching. Zustand handles ephemeral UI an
|
||||||
|
|
||||||
## 13. Testing Strategy
|
## 13. Testing Strategy
|
||||||
|
|
||||||
| Type | Tool | Scope |
|
| Type | Tool | Scope |
|
||||||
|---|---|---|
|
| ----------- | -------------------- | --------------------------------------------------- |
|
||||||
| Unit | Vitest | Services, QuizService distractor logic, Zod schemas |
|
| Unit | Vitest | Services, QuizService distractor logic, Zod schemas |
|
||||||
| Component | Vitest + RTL | QuestionCard, OptionButton, auth forms |
|
| Component | Vitest + RTL | QuestionCard, OptionButton, auth forms |
|
||||||
| Integration | Vitest | API route handlers against a test DB |
|
| Integration | Vitest | API route handlers against a test DB |
|
||||||
| E2E | Out of scope for MVP | — |
|
| E2E | Out of scope for MVP | — |
|
||||||
|
|
||||||
Tests are co-located with source files (`*.test.ts` / `*.test.tsx`).
|
Tests are co-located with source files (`*.test.ts` / `*.test.tsx`).
|
||||||
|
|
||||||
**Critical paths to cover:**
|
**Critical paths to cover:**
|
||||||
|
|
||||||
- Distractor generation (correct POS, no duplicates, never includes answer)
|
- Distractor generation (correct POS, no duplicates, never includes answer)
|
||||||
- Answer validation (server-side, correct scoring)
|
- Answer validation (server-side, correct scoring)
|
||||||
- Game session lifecycle (create → play → complete)
|
- Game session lifecycle (create → play → complete)
|
||||||
|
|
@ -402,6 +411,7 @@ Tests are co-located with source files (`*.test.ts` / `*.test.tsx`).
|
||||||
## 14. Definition of Done
|
## 14. Definition of Done
|
||||||
|
|
||||||
### Functional
|
### Functional
|
||||||
|
|
||||||
- [ ] User can log in via Google or GitHub (OpenAuth)
|
- [ ] User can log in via Google or GitHub (OpenAuth)
|
||||||
- [ ] User can play singleplayer: 10 rounds, score, result screen
|
- [ ] User can play singleplayer: 10 rounds, score, result screen
|
||||||
- [ ] User can create a room and share a code
|
- [ ] User can create a room and share a code
|
||||||
|
|
@ -410,6 +420,7 @@ Tests are co-located with source files (`*.test.ts` / `*.test.tsx`).
|
||||||
- [ ] 1 000 English–Italian words seeded from WordNet + OMW
|
- [ ] 1 000 English–Italian words seeded from WordNet + OMW
|
||||||
|
|
||||||
### Technical
|
### Technical
|
||||||
|
|
||||||
- [ ] Deployed to Hetzner with HTTPS on all three subdomains
|
- [ ] Deployed to Hetzner with HTTPS on all three subdomains
|
||||||
- [ ] Docker Compose running all services
|
- [ ] Docker Compose running all services
|
||||||
- [ ] Drizzle migrations applied on container start
|
- [ ] Drizzle migrations applied on container start
|
||||||
|
|
@ -417,6 +428,7 @@ Tests are co-located with source files (`*.test.ts` / `*.test.tsx`).
|
||||||
- [ ] pnpm workspace build pipeline green
|
- [ ] pnpm workspace build pipeline green
|
||||||
|
|
||||||
### Documentation
|
### Documentation
|
||||||
|
|
||||||
- [ ] `SPEC.md` complete
|
- [ ] `SPEC.md` complete
|
||||||
- [ ] `.env.example` files for all apps
|
- [ ] `.env.example` files for all apps
|
||||||
- [ ] `README.md` with local dev setup instructions
|
- [ ] `README.md` with local dev setup instructions
|
||||||
|
|
@ -425,12 +437,12 @@ Tests are co-located with source files (`*.test.ts` / `*.test.tsx`).
|
||||||
|
|
||||||
## 15. Out of Scope (MVP)
|
## 15. Out of Scope (MVP)
|
||||||
|
|
||||||
- Difficulty levels *(`frequency_rank` column exists, ready to use)*
|
- Difficulty levels _(`frequency_rank` column exists, ready to use)_
|
||||||
- Additional language pairs *(schema already supports it — just add rows)*
|
- Additional language pairs _(schema already supports it — just add rows)_
|
||||||
- Leaderboards *(`games_played`, `games_won` columns exist)*
|
- Leaderboards _(`games_played`, `games_won` columns exist)_
|
||||||
- Streaks / daily challenges
|
- Streaks / daily challenges
|
||||||
- Friends / private invites
|
- Friends / private invites
|
||||||
- Audio pronunciation
|
- Audio pronunciation
|
||||||
- CI/CD pipeline (manual deploy for now)
|
- CI/CD pipeline (manual deploy for now)
|
||||||
- Rate limiting *(add before going public)*
|
- Rate limiting _(add before going public)_
|
||||||
- Admin panel for vocabulary management
|
- Admin panel for vocabulary management
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,9 @@
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "concurrently \"pnpm --filter @glossa/web run dev\" \"pnpm --filter @glossa/api run dev\"",
|
"dev": "concurrently \"pnpm --filter @glossa/web run dev\" \"pnpm --filter @glossa/api run dev\"",
|
||||||
"test": "echo \"Error: no test specified\" && exit 1",
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
"lint": "eslint ."
|
"lint": "eslint .",
|
||||||
|
"format": "prettier --write .",
|
||||||
|
"format:check": "prettier --check ."
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@10.32.1",
|
"packageManager": "pnpm@10.32.1",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
@ -14,6 +16,7 @@
|
||||||
"concurrently": "^9.2.1",
|
"concurrently": "^9.2.1",
|
||||||
"eslint": "^10.0.3",
|
"eslint": "^10.0.3",
|
||||||
"eslint-config-prettier": "^10.1.8",
|
"eslint-config-prettier": "^10.1.8",
|
||||||
|
"prettier": "^3.8.1",
|
||||||
"typescript": "^5.9.3",
|
"typescript": "^5.9.3",
|
||||||
"typescript-eslint": "^8.57.1"
|
"typescript-eslint": "^8.57.1"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"module": "NodeNext",
|
"module": "NodeNext",
|
||||||
"moduleResolution": "NodeNext",
|
"moduleResolution": "NodeNext",
|
||||||
"outDir": "./dist",
|
"outDir": "./dist"
|
||||||
},
|
},
|
||||||
"include": ["src"],
|
"include": ["src"]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"module": "NodeNext",
|
"module": "NodeNext",
|
||||||
"moduleResolution": "NodeNext",
|
"moduleResolution": "NodeNext",
|
||||||
"outDir": "./dist",
|
"outDir": "./dist"
|
||||||
},
|
},
|
||||||
"include": ["src"],
|
"include": ["src"]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
10
pnpm-lock.yaml
generated
10
pnpm-lock.yaml
generated
|
|
@ -20,6 +20,9 @@ importers:
|
||||||
eslint-config-prettier:
|
eslint-config-prettier:
|
||||||
specifier: ^10.1.8
|
specifier: ^10.1.8
|
||||||
version: 10.1.8(eslint@10.0.3)
|
version: 10.1.8(eslint@10.0.3)
|
||||||
|
prettier:
|
||||||
|
specifier: ^3.8.1
|
||||||
|
version: 3.8.1
|
||||||
typescript:
|
typescript:
|
||||||
specifier: ^5.9.3
|
specifier: ^5.9.3
|
||||||
version: 5.9.3
|
version: 5.9.3
|
||||||
|
|
@ -425,6 +428,11 @@ packages:
|
||||||
resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
|
resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
|
||||||
engines: {node: '>= 0.8.0'}
|
engines: {node: '>= 0.8.0'}
|
||||||
|
|
||||||
|
prettier@3.8.1:
|
||||||
|
resolution: {integrity: sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==}
|
||||||
|
engines: {node: '>=14'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
punycode@2.3.1:
|
punycode@2.3.1:
|
||||||
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
|
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
|
|
@ -917,6 +925,8 @@ snapshots:
|
||||||
|
|
||||||
prelude-ls@1.2.1: {}
|
prelude-ls@1.2.1: {}
|
||||||
|
|
||||||
|
prettier@3.8.1: {}
|
||||||
|
|
||||||
punycode@2.3.1: {}
|
punycode@2.3.1: {}
|
||||||
|
|
||||||
require-directory@2.1.1: {}
|
require-directory@2.1.1: {}
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,6 @@
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"target": "es2022",
|
"target": "es2022",
|
||||||
"verbatimModuleSyntax": true,
|
"verbatimModuleSyntax": true
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
{ "path": "./packages/shared" },
|
{ "path": "./packages/shared" },
|
||||||
{ "path": "./packages/db" },
|
{ "path": "./packages/db" },
|
||||||
{ "path": "./apps/web" },
|
{ "path": "./apps/web" },
|
||||||
{ "path": "./apps/api" },
|
{ "path": "./apps/api" }
|
||||||
],
|
],
|
||||||
"files": [],
|
"files": []
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue