You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
docs: rewrite README and update project documentation
- Rewrite README with tool tables, tech stack, architecture highlights,
getting started guide, and project structure
- Add Logs section to CLAUDE.md (dev log path, prod via Vercel)
- Add E2E testing section to CLAUDE.md with pitfalls guide
- Update all docs to remove ToolPageShell/Breadcrumbs references
- Update test counts and command listings
@@ -22,8 +23,10 @@ PhotoTools is an educational photography application — free calculators, simul
22
23
-`npm run dev` — start dev server with Turbopack at `http://localhost:3000`
23
24
-`npm run build` — production build via `next build`
24
25
-`npm run start` — serve production build locally
25
-
-`npm test` — run Vitest tests (351 tests across 24 files)
26
+
-`npm test` — run Vitest tests (353 tests across 25 files)
26
27
-`npm run test:watch` — run tests in watch mode
28
+
-`npm run test:e2e` — run Playwright e2e tests (requires a build first)
29
+
-`npm run test:e2e:ui` — run Playwright tests with interactive UI
27
30
-`npm run lint` — run ESLint
28
31
29
32
## Architecture
@@ -32,11 +35,11 @@ All source code lives under `src/`, with `@/` aliased to `src/` in tsconfig.json
32
35
33
36
-**App Router**: all routes under `src/app/`. Homepage (`src/app/page.tsx`) is the tool hub. Each tool lives at `src/app/[slug]/page.tsx` with co-located `_components/` for tool-specific UI. Glossary at `src/app/learn/glossary/page.tsx`. Additional pages: `/about`, `/contact`, `/privacy`, `/terms`. API route: `src/app/api/contact/route.ts`.
34
37
-**Tool Co-location**: Each tool's components live in `src/app/[slug]/_components/` alongside its `page.tsx`. The `_` prefix makes it a private folder (not a route segment). Page files import from `./_components/...` using relative paths.
-**Tool Registry**: `src/lib/data/tools.ts` defines all tools with slug, name, description, `dev`/`prod` status fields (`'live'`/`'draft'`/`'disabled'`), and category. `getLiveTools()` returns live tools. `getVisibleTools()` returns live + draft. `getToolBySlug()` looks up by slug. `getAllTools()` returns all tools regardless of status.
37
-
-**Education System**: `src/lib/data/education/` contains per-tool educational content (beginner/deeper explanations, key factors, pro tips, tooltips, challenges). `LearnPanel`renders as a right sidebar on every tool page.
40
+
-**Education System**: `src/lib/data/education/` contains per-tool education skeletons (non-translatable data: IDs, difficulty levels, correct answers, option values). All translatable education text lives in `src/lib/i18n/messages/en/education/*.json`. `LearnPanel`and `ChallengeCard` render by combining skeleton data with translations.
38
41
-**Pure Math Modules**: `src/lib/math/` contains pure functions for FOV, DOF, exposure (including shader math for CoC, motion blur, noise), diffraction, star trails, color, histogram, compression, frame, and grid calculations. Each has co-located `.test.ts` files. TDD approach — math is tested independently from UI.
39
-
-**Data**: `src/lib/data/`contains tool registry, education content, sensors (with dimensions/colors), focal lengths, scenes, glossary, camera settings (apertures/shutter speeds/ISOs), ND filters, and white balance presets — each with tests.
42
+
-**Data**: `src/lib/data/`centralizes all pure data. Shared data files (with tests): tool registry, education skeletons, sensors, focal lengths, scenes, glossary, camera settings (apertures/shutter speeds/ISOs), ND filters, white balance presets. Per-tool data files: `frameStudio.ts`, `exposureScenes.ts`, `fovSimulator.ts`, `colorSchemeGenerator.ts`, `exifViewer.ts`, `starTrailCalculator.ts`, `dofCalculator.ts`, `hyperfocalSimulator.ts`, `perspectiveCompression.ts`.
40
43
41
44
## Key Directories
42
45
@@ -49,17 +52,77 @@ src/
49
52
api/contact/ Contact form API route
50
53
components/
51
54
layout/ Nav (mega-menu), Footer, ThemeProvider, ThemeToggle
52
-
shared/ ToolPageShell, LearnPanel, ControlPanel, AdUnit, MobileAdBanner, etc.
55
+
shared/ LearnPanel, ChallengeCard, ControlPanel, FocalLengthField, AdUnit, MobileAdBanner, etc.
56
+
i18n/
57
+
request.ts Message loader — merges all JSON files at request time
Uses **next-intl** with a single-locale (English) setup, ready for multi-locale expansion.
74
+
75
+
### How it works
76
+
77
+
All translatable strings live in JSON files under `src/lib/i18n/messages/en/`. The message loader (`src/lib/i18n/request.ts`) imports every JSON file and merges them into namespaces. The root layout's `NextIntlClientProvider` makes the merged messages available to all components.
78
+
79
+
**Namespaces** (top-level key in each JSON file → how components access strings):
Education files are merged into a single `education` namespace; tool UI files are merged into a single `toolUI` namespace. Both use `<tool-slug>` as a sub-key.
93
+
94
+
### Adding new strings
95
+
96
+
**To an existing tool** — add keys to the tool's JSON file and use them in the component:
97
+
1. Add key to `src/lib/i18n/messages/en/tools/<tool-slug>.json` under the `toolUI.<tool-slug>` object
98
+
2. In component: `const t = useTranslations('toolUI.<tool-slug>')` → `t('newKey')`
99
+
100
+
**To a new tool** — 3 files must be created and registered:
3.**Register both files in `src/lib/i18n/request.ts`**: add an import to the `Promise.all` array, add the variable to the `toolUIMessages` and/or `educationMessages` reducer array. **If you skip this step, the strings will not load and you'll get `MISSING_MESSAGE` errors at runtime.**
104
+
105
+
**To shared UI** (nav, footer, actions, etc.) — add keys to `messages/en/common.json` under the appropriate sub-object.
106
+
107
+
**Server components / metadata** — use `const t = await getTranslations('namespace')` (async).
-**Canvas/WebGL data** — sensors, scenes, focal lengths, ND filters, WB presets keep English text directly in `src/lib/data/` files because `useTranslations` isn't available in draw functions. When adding locales, pass resolved names into drawing functions.
114
+
-**Education skeletons** (`src/lib/data/education/`) store only non-translatable data (IDs, difficulty, correctOption, optionValues). All text is in the education JSON files.
115
+
-**Glossary** (`src/lib/data/glossary.ts`) stores entry IDs and optional relatedTool slugs. Terms and definitions are in `messages/en/glossary.json`.
116
+
117
+
## Data Management
118
+
119
+
All pure data (constants, presets, lookup tables, configuration arrays) lives in `src/lib/data/`. UI plumbing (`PARAM_SCHEMA`, `DEFAULT_STATE`, component-specific config) stays co-located in `_components/`.
120
+
121
+
-**Shared data** — data used by 2+ tools gets its own file (e.g. `camera.ts` exports `APERTURES` used by star-trail and hyperfocal tools).
122
+
-**Per-tool data** — tool-specific constants get a file named after the tool (e.g. `fovSimulator.ts`, `frameStudio.ts`).
123
+
-**i18n for data** — user-facing prose goes to i18n JSON files (`src/lib/i18n/messages/en/`), not data files. Data files consumed by canvas/WebGL keep English text directly (sensors, scenes, etc.) since `useTranslations` isn't available in draw functions.
124
+
-**When adding a new tool**: extract all constants, presets, and lookup tables into `src/lib/data/<toolSlug>.ts`. Only leave UI wiring in `_components/`.
125
+
63
126
## Tool Visibility
64
127
65
128
Each tool in `src/lib/data/tools.ts` has separate `dev` and `prod` status fields with three states: `'live'`, `'draft'`, or `'disabled'`.
@@ -81,13 +144,13 @@ Each tool has a **LearnPanel** (right sidebar) with:
81
144
-**Challenges** — 3-5 progressive multiple-choice questions with pass/fail feedback, try-again on wrong answers, reset all progress, persisted to localStorage
82
145
-**Tooltips** — hover info icons on control labels (via `InfoTooltip` component)
83
146
84
-
Content is defined as structured data in `src/lib/data/education/content.ts`, `content2.ts`, and `frame-studio.ts`. Types in `types.ts`, barrel export via `index.ts`. To add education content for a new tool, add a `ToolEducation` entry matching the tool's slug.
147
+
Education skeletons in `src/lib/data/education/content*.ts` define non-translatable structure (IDs, difficulty, correct answers). All user-facing text is in `src/lib/i18n/messages/en/education/*.json`. To add education content for a new tool, add a `ToolEducationSkeleton` entry and a corresponding JSON file.
85
148
86
149
## Design
87
150
88
151
-**No page scroll (desktop only).** On desktop, the application must fit within the viewport (100vh). The page never scrolls — only individual panels (controls, canvas, LearnPanel) scroll internally via `overflow-y: auto`. On mobile, pages are allowed to scroll naturally.
89
152
-**FOV Simulator is the reference implementation.** All tools should match its look and feel: dark surface panels, compact controls, same spacing/typography tokens, and consistent use of `var(--accent)` for interactive elements.
90
-
-**Three-column layout**: ToolPageShell renders tool content (left/center) + LearnPanel (right sidebar, collapsible). Full-height tools (FOV Simulator, Color Harmony, Exposure Simulator, Sensor Size Comparison) manage their own layout but include LearnPanel directly.
153
+
-**Three-column layout**: Tool pages render content (left/center) + LearnPanel (right sidebar, collapsible). Each tool manages its own layout and includes LearnPanel directly.
91
154
-**Tool icons**: Each tool has an inline SVG icon (`components/shared/ToolIcon.tsx`) displayed on homepage cards, nav mega-menu items, and tool page headers. Icons are mapped by slug.
92
155
-**Nav mega-menu**: Tools dropdown groups tools by category (Visualizers, Calculators, Reference, File Tools) with icon + name + description per item.
93
156
@@ -111,11 +174,57 @@ GoogleAdSense integration managed via `src/lib/ads.ts` (configuration and featur
111
174
-**Named exports** for all components
112
175
-**Shared components first**: Before building tool-specific UI, check `src/components/shared/` for existing components. When two or more tools need similar UI (e.g. file upload, scene selector, share modal), extract a shared component. Familiarize yourself with what each shared component does so you reuse them consistently.
113
176
-**DRY**: Avoid duplicating logic, styles, constants, or markup. Extract shared utilities, components, and data modules. When adding a feature, check if similar patterns already exist in the codebase and reuse them.
114
-
-**200-line file limit**: Keep all `.ts`/`.tsx` files under 200 lines. If a file grows beyond this, break it into smaller focused modules (e.g. extract hooks, sub-components, helpers, constants, or types into separate files).
177
+
-**200-line file limit**: Keep all `.ts`/`.tsx` files under 200 lines (test files exempt). If a file grows beyond this, break it into smaller focused modules (e.g. extract hooks, sub-components, helpers, constants, or types into separate files).
115
178
-**Test files** co-located next to source files (`*.test.ts`)
116
-
-**24 test files, 351 tests** covering math, data, education, ads, and component integration
179
+
-**25 test files, 353 tests** covering math, data, education, ads, and component integration
117
180
-**Privacy Sandbox is deprecated** — do not discuss, recommend, or implement any Privacy Sandbox APIs (Topics, Attribution Reporting, Protected Audience, etc.)
118
181
182
+
## E2E Testing (Playwright)
183
+
184
+
Playwright integration tests live in `src/e2e/` and run against a production build (`npm run build` + `npm run start`). Config: `playwright.config.ts`.
185
+
186
+
### Structure
187
+
188
+
```
189
+
src/e2e/
190
+
smoke/all-pages.spec.ts Parameterized smoke tests for all pages (200 status, no console errors, content rendering, no desktop scroll)
fixtures/test-image.jpg Minimal JPEG fixture for upload tests
195
+
```
196
+
197
+
### Running
198
+
199
+
1. Build first: `npm run build`
200
+
2. Run tests: `npm run test:e2e` (or `npx playwright test`)
201
+
3. If port 3000 is in use: `lsof -ti:3000 | xargs kill -9` then retry
202
+
4. Run a single spec: `npx playwright test src/e2e/tools/fov-simulator.spec.ts`
203
+
5. Debug mode: `npx playwright test --debug`
204
+
6. View last report: `npx playwright show-report`
205
+
206
+
### Common Pitfalls When Writing/Fixing Tests
207
+
208
+
-**Duplicate DOM elements**: Every tool page renders controls twice — once in the desktop sidebar (`<aside>`) and once in a mobile controls section. Always scope selectors to avoid matching both. Use `page.locator('aside').first()` or `page.locator('[class*="sidebar"]').first()`.
209
+
-**LearnPanel challenge buttons**: The LearnPanel sidebar contains challenge questions with answer buttons (e.g. "24mm", "Complementary") that share text with tool control buttons. Use `sidebar.locator('button:text-is("...")')` scoped to the controls panel, not the full page.
210
+
-**CSS Modules hashed classes**: Class names are hashed at build time. Never match exact class names. Use `[class*="partialName"]` attribute selectors (e.g. `[class*="paletteBarSwatch"]`, `[class*="editForm"]`, `[class*="copyGroup"]`).
211
+
-**Hidden file inputs**: File upload uses a hidden `<input type="file">` inside `FileDropZone`. Use `page.locator('input[type="file"]').setInputFiles(...)` directly.
212
+
-**Ref-based state (no re-render)**: Some components use refs instead of state (e.g. `PhotoPicker`'s `imageLoaded`). When a ref controls visibility, the DOM won't update after async operations. Fix with `page.evaluate()` to force styles, then `click({ force: true })`.
213
+
-**Canvas/WebGL tools**: These render to `<canvas>`, not DOM elements. Use screenshot comparison (`canvas.screenshot()` + `Buffer.compare`) to verify visual changes.
214
+
-**ESM module context**: Test files use ESM. Use `import { fileURLToPath } from 'url'` and `path.dirname(fileURLToPath(import.meta.url))` instead of `__dirname`.
215
+
-**Console error filtering**: Smoke tests assert no console errors, but filter benign ones: favicon 404, cookieyes, adsense, adsbygoogle, `_vercel/speed-insights`. Add new benign patterns to the filter in `src/e2e/smoke/all-pages.spec.ts` as needed.
216
+
-**i18n translated text**: UI labels come from `next-intl` message files (`src/lib/i18n/messages/en/`). If a placeholder or label changes, check the translation JSON, not just the component source.
217
+
-**Select elements use value, not label**: For `<select>` dropdowns (e.g. sensor picker), use `selectOption('value_id')` not `selectOption({ label: '...' })`.
218
+
219
+
### CI
220
+
221
+
Playwright runs in GitHub Actions after `npm run build`. See `.github/workflows/deploy.yml`. CI installs Chromium + Firefox, runs all tests, and uploads `playwright-report/` as an artifact on failure.
222
+
223
+
## Logs
224
+
225
+
-**Dev logs**: `.next/dev/logs/next-development.log` — written automatically by Turbopack when `npm run dev` is running. Read this file when asked to "check the dev logs".
226
+
-**Prod logs**: Use `vercel logs <url>` or check the Vercel dashboard. Use `vercel logs --follow` for live tailing.
Standard three-column layout (Controls | Main Visualizer | LearnPanel). Provides consistent header actions (Share, Export, Info).
31
+
### 4. Layout
32
+
Each tool manages its own layout (Controls | Main Visualizer | LearnPanel). Consistent header actions (Share, Export, Info) via `ToolActions` component.
0 commit comments