A local-first Japanese conjugation practice app for JLPT learners.
Katachi runs as a Next.js App Router application, can be installed as a PWA, and optionally syncs study progress through Supabase without making cloud access required for practice.
- Builds daily, weakness-focused, and free practice sessions from the local dictionary.
- Supports multiple-choice recognition and typed active-recall answers.
- Generates plausible distractors for incorrect choices while excluding the correct answer.
- Re-queues missed items so a session finishes only after every original item is answered correctly.
- Tracks streaks, daily goals, unit progress, form mastery, pattern mastery, word stats, session history, and attempt history locally.
- Provides a progress dashboard with weakest forms, weakest items, recent activity, and drill shortcuts.
- Plays Japanese audio with browser TTS fallback and keeps audio failures non-blocking.
- Supports English, Chinese, Vietnamese, Nepali, and Burmese UI/localized meanings.
- Works in guest mode by default and can sync progress after Supabase sign-in.
- Ships PWA metadata, icons, iOS install guidance, a splash overlay, and a Serwist service worker for production builds.
- Next.js 16 App Router with React 19 and TypeScript
- Tailwind CSS 4
- Zustand with
localStoragepersistence - Supabase Auth and
study_statessync - Serwist for PWA service worker generation and runtime caching
- WanaKana for romaji-to-hiragana input normalization
- Vitest and ESLint
- Node.js 20 or newer is recommended.
- npm is supported by the documented scripts. The repository also contains a Yarn lockfile, but project commands below use npm.
npm installnpm run devOpen http://localhost:4399. The development script intentionally runs Next.js with Webpack because the PWA build path depends on Serwist.
npm run build
npm run startnpm run build creates the production Next.js build and generates public/sw.js from src/app/sw.ts.
Katachi is usable without any environment variables. Supabase only enables online account and sync features.
Create a local .env.local when you want sync:
NEXT_PUBLIC_SUPABASE_URL=
NEXT_PUBLIC_SUPABASE_ANON_KEY=
NEXT_PUBLIC_ENABLE_GOOGLE_AUTH=falseNEXT_PUBLIC_ENABLE_GOOGLE_AUTH=true enables Google OAuth in addition to email OTP sign-in. Keep these as public browser keys only; do not add service-role credentials to client code.
- Create a Supabase project.
- Add
NEXT_PUBLIC_SUPABASE_URLandNEXT_PUBLIC_SUPABASE_ANON_KEYto.env.local. - Apply the study-state migration:
supabase/migrations/20260423000000_create_study_states.sql- Check that the table is reachable:
npm run check:supabaseThe app stores progress locally first. When a user signs in, src/lib/supabase/studySync.ts merges local and remote StudyState conservatively so remote data does not erase guest progress.
src/app/ Next.js routes, metadata, PWA config tests, service worker source
src/components/ Practice UI, auth UI, install prompt, splash screen, progress panels
src/lib/ Store, session builder, distractor engine, i18n, audio, study logic
src/lib/study/ Study-state types, migration, scheduling, scoring, statistics
src/lib/supabase/ Supabase client, auth callback helpers, sync and database types
src/data/dictionaries/ Main dictionary and localized meanings
public/ Manifest, icons, generated service worker output
scripts/ Supabase check and icon generation utilities
docs/ Learning architecture and feature design notes
supabase/migrations/ Database schema for optional progress sync
Durable progress lives in studyState inside src/lib/store.ts and persists under katachi-storage. activeSession is transient session state. Legacy aliases such as dailyStreak, lastPracticeDate, progress, language, and config are kept coherent for older persisted data.
If the StudyState shape changes, update the migration path in src/lib/study/migrate.ts and add focused tests.
src/lib/sessionBuilder.ts selects practice units from the dictionary and learner history. Daily sessions honor the remaining daily budget, weakness sessions prioritize low-performing units, and free sessions use the selected configuration. Incorrect answers are recorded and re-queued by the store/session flow.
The dictionary files are structured JSON. Word ids are stable because progress and sync depend on them. Prefer generation or scripted transformations over manual edits to large dictionary files.
PWA support is production-only:
next.config.tswraps Next with@serwist/next.src/app/sw.tsis the editable service worker source.public/sw.jsis generated and should not be hand-edited.public/manifest.jsonandsrc/app/layout.tsxmust stay aligned for app metadata and icons.- iOS install behavior is handled by
src/components/IOSInstallPrompt.tsx.
Run a production build after changing PWA, metadata, routing, or static assets.
npm run dev # Start the development server on port 4399
npm run build # Create a production build and generate the Serwist service worker
npm run start # Start the production server
npm run lint # Run ESLint
npm run test # Run Vitest once
npm run test:watch # Run Vitest in watch mode
npm run check:supabase # Verify the Supabase study_states table
npm run generate:icons # Regenerate PNG icons from public/icon.svgRun focused tests for the area you changed:
npm run test -- src/lib/store.test.ts
npm run test -- src/lib/sessionBuilder.test.ts
npm run test -- src/lib/study/migrate.test.ts
npm run test -- src/lib/supabase/studySync.test.ts
npm run test -- src/app/pwa-config.test.ts
npm run test -- src/components/IOSInstallPrompt.test.tsUse broader checks before shipping shared-state, routing, persistence, or PWA changes:
npm run lint
npm run test
npm run build- Keep guest mode fully usable without network access or Supabase credentials.
- Add all user-facing app strings to every supported language in
src/lib/i18n.ts. - Use
useTranslation(language)for client UI tied to persisted language. - Keep audio/TTS failures non-blocking for practice.
- Use
lucide-reacticons when an icon is needed. - Preserve mobile, standalone PWA, and safe-area behavior when changing layout.
- Do not add required network calls to the practice path.
- Regenerate generated assets instead of hand-editing them.
The app can be deployed like a standard Next.js application. For PWA correctness, make sure the deployment runs:
npm run buildSet the Supabase public env vars only if online sync is desired. Without them, the app hides account controls and continues in local mode.
See CHANGELOG.md for release history.