Katachi is a Next.js App Router application for Japanese conjugation practice. It is local-first, installable as a PWA, and can optionally sync study progress through Supabase.
Core stack:
- Next.js 16, React 19, TypeScript
- Tailwind CSS 4
- Zustand with
localStoragepersistence - Supabase Auth and
study_statessync - Serwist for the service worker and PWA caching
- Vitest for tests
npm run devstarts the app on port4399.npm run buildcreates a production build and generates the Serwist service worker.npm run startstarts the production server.npm run lintruns ESLint.npm run testruns Vitest once.npm run test:watchruns Vitest in watch mode.npm run check:supabasechecks whether the Supabasestudy_statestable exists.npm run generate:iconsregenerates PNG app icons frompublic/icon.svg.
Use the npm scripts rather than calling next directly. The scripts include project-specific flags.
This branch adds PWA support. Treat PWA behavior as a first-class constraint when changing routing, build config, layout metadata, static assets, or persistence.
Important files:
next.config.ts: wraps Next with@serwist/next.src/app/sw.ts: Serwist service worker source.public/sw.js: generated service worker output. Do not hand-edit it.public/manifest.json: web app manifest.src/app/layout.tsx: manifest, mobile web app, Apple web app, icon, viewport, splash, and install prompt wiring.src/components/IOSInstallPrompt.tsx: iOS Safari install guidance.src/components/SplashScreen.tsx: first-session splash overlay.src/app/pwa-config.test.ts: protects important PWA build assumptions.src/components/IOSInstallPrompt.test.ts: protects iOS prompt behavior and localization.
PWA-specific rules:
- Keep
npm run devandnpm run buildon Webpack. Serwist service worker generation is protected by--webpack; do not switch these scripts to Turbopack unless Serwist support is revalidated and tests are updated. next.config.tsdisables Serwist outside production. Do not expect a generated service worker in normal development mode.public/sw.jsis ignored by ESLint because it is generated. If service worker behavior changes, editsrc/app/sw.ts, then run a production build.- Keep manifest and Apple icon metadata in sync between
public/manifest.jsonandsrc/app/layout.tsx. - Preserve iOS-specific install behavior. iOS does not support a normal browser install prompt, so
IOSInstallPromptshows localized Share/Add to Home Screen instructions and suppresses itself in standalone mode. SplashScreenandIOSInstallPromptare client components and usesessionStorage. Guard browser-only APIs behind client components/effects.- When changing PWA UI copy, update all supported languages in
src/lib/i18n.tsand keep the tests that count prompt translations passing. - Offline behavior depends on local-first data and cached static assets. Avoid adding required network calls to the practice path.
The app is local-first. Guest mode must remain fully usable without Supabase credentials or network access.
Important files:
src/lib/store.ts: Zustand app store andkatachi-storagepersistence.src/lib/study/types.ts: canonical study state shape.src/lib/study/migrate.ts: migration from older persisted state shapes.src/lib/supabase/studySync.ts: remote merge and sync logic.src/components/StudySync.tsx: sync side effects.supabase/migrations/20260423000000_create_study_states.sql: database schema.
Rules:
- Do not break existing
localStorageusers. IfStudyStatechanges, add or update migrations and tests. activeSessionis transient; durable learning progress belongs instudyState.- Keep legacy aliases in
store.tscoherent:dailyStreak,lastPracticeDate,progress,language, andconfigmirror values insidestudyState. - Supabase config is optional.
getSupabaseConfig()returnsnullwhen env vars are absent; UI must keep working. - Google auth is opt-in through
NEXT_PUBLIC_ENABLE_GOOGLE_AUTH=true. Email/password remains the default universal path. - Sync merge is conservative: counts generally use max values, history is merged by id, and remote state is not allowed to erase local guest progress.
Important files:
src/components/PracticeSession.tsx: practice UI and answer flow.src/lib/sessionBuilder.ts: session construction and daily budget behavior.src/lib/distractorEngine.ts: plausible incorrect answer generation.src/lib/practiceChoiceInteraction.ts: multiple-choice interaction rules.src/lib/audioPlayback.tsandsrc/lib/audioPreload.ts: TTS/audio behavior.
Rules:
- Incorrect answers are re-queued. A session finishes only after every initial item has eventually been answered correctly.
- Answer attempts update unit progress, attempt history, session history, daily streaks, and summary counts. Test store changes carefully.
- Distractors must never include the correct answer.
- Keep audio and TTS failures non-blocking for practice.
Supported app languages live in src/lib/i18n.ts: English, Chinese, Vietnamese, Nepali, Burmese, and Korean.
Rules:
- Add every user-facing string to all supported languages.
- Use
useTranslation(language)in client UI that depends on the persisted language. - Server metadata currently selects a default language from
accept-language; do not assume it matches the client store after hydration. - Landing-page text may use
src/lib/landing-i18n.ts; app UI usessrc/lib/i18n.ts.
Important files:
src/data/dictionaries/dictionary.json: main conjugation data.src/data/dictionaries/{en,zh,vi,ne,my}.json: localized meanings.generate_dictionary.py,generate_dictionary_jisho.py, andtranslate_test.py: dictionary generation helpers.jlpt-all.zip: source/archive data.
Rules:
- Prefer structured JSON generation or parsing over manual edits to large dictionary files.
- Keep word ids stable; progress and sync use ids and unit keys.
- If dictionary shape changes, update loaders, tests, and any persisted-state assumptions.
- Tailwind CSS 4 is the default styling approach.
- Global theme variables and motion classes live in
src/app/globals.css. - Branding should go through
src/components/Logo.tsxand the public icon assets. - Icons should use
lucide-reactwhen an icon is needed. - Keep mobile layouts and installable standalone mode in mind. The app may run without browser chrome, with safe-area constraints, and offline.
Run focused tests for the area you change, plus broader checks when touching shared state, PWA config, routing, or persistence.
High-value test targets:
npm run test -- src/app/pwa-config.test.tsnpm run test -- src/components/IOSInstallPrompt.test.tsnpm run test -- src/lib/store.test.tsnpm run test -- src/lib/sessionBuilder.test.tsnpm run test -- src/lib/supabase/studySync.test.tsnpm run test -- src/lib/study/migrate.test.tsnpm run lintnpm run buildfor PWA, metadata, service worker, or asset changes
When touching PWA or service worker behavior, a passing unit test run is not enough; run npm run build to verify Serwist generation.
Expected public env vars:
NEXT_PUBLIC_SUPABASE_URL=
NEXT_PUBLIC_SUPABASE_ANON_KEY=
NEXT_PUBLIC_ENABLE_GOOGLE_AUTH=false
NEXT_PUBLIC_SITE_URL=These are public browser env vars. Do not introduce service-role keys or private credentials into client code.
.next/**,out/**,build/**,next-env.d.ts,.worktrees/**, andpublic/sw.jsare ignored by ESLint.- PNG icons under
public/are generated from the SVG icon workflow. Regenerate them rather than editing binary assets manually when possible. - Do not commit local
.envfiles or machine-specific workspace artifacts.
GEMINI.mdhas a compact project overview and should stay consistent with this file.docs/superpowers/specs/anddocs/superpowers/plans/contain design specs and implementation plans for major features.docs/learning-architecture.mdexplains learning-flow decisions such as interleaving and re-queueing.
When an implementation conflicts with these docs, either update the relevant doc in the same change or call out the mismatch explicitly.