Use this when building a mobile-first study app where the card is the product.
react-swipeabledist/sw.js and manifest.jsonseenGestureHint flag in localStorage so the toast is not permanent.current/total)Reshuffle pill instead of a full control traySupport multiple orderings with a single createStudyDeck() function.
Recommended modes:
- canonical / King Wen
- user-provided custom sequence (e.g. Human Design)
- binary / structural
- random
For a custom sequence supplied as an explicit 64-number array:
1. Store the array as a constant.
2. Build const CUSTOM_INDEX: Map<number, number> = new Map(order.map((n, i) => [n, i] as const))
3. Sort with the map lookup instead of hardcoding many ifs.
4. Add a test that verifies:
- first several values match exactly
- last value matches exactly
- all 64 entries are unique
This avoids mistakes when users hand you a specific sacred/study order they care about.
This was the important deploy requirement.
sw.jsregistration.update() on loadregistration.update() every ~60scontrollerchange and reload once when a new SW takes control, but only if the page already had a controller before registration. Capture const hadController = Boolean(navigator.serviceWorker.controller) before register(), then in controllerchange do nothing on the first install. This avoids iOS standalone PWA first-launch reload loops/white screens.Update ready pill/button when updatefound firespublic/sw.jsself.skipWaiting() in installself.clients.claim() in activate/api/ responses as static app shellServe these with no-cache headers BEFORE express.static(distDir):
- /sw.js
- /manifest.json
Example headers:
- Cache-Control: no-cache, no-store, must-revalidate
- Service-Worker-Allowed: / for sw.js
When the user wants an LLM/API token in the app but not exposed to the browser:
1. Load env on the server with import 'dotenv/config'
2. Keep the token in .env only
3. Never surface the raw key to the frontend
4. If useful, expose only a boolean status endpoint like /api/config-status -> { hasOpenRouter: true }
If another app on the same VPS already uses OpenRouter:
- Check that app’s .env or PM2 ecosystem config
- Copy the key into the new app’s .env
- Keep .env gitignored
- Add .env.example with placeholder values only
For datasets that need generated study text (e.g. 64 cards):
- Write a standalone script under scripts/
- Use OpenRouter chat completions with a strict JSON prompt
- Request fields like:
- overview
- judgment
- image
- mnemonic
- Save incrementally after each item so long runs can recover
- Strip fenced code blocks before JSON.parse() because models may still return ```json wrappers
- Merge generated JSON into the main dataset at import time rather than hardcoding everything in one file
src/data/hexagrams.tssrc/data/...generated.jsontsc -b || true && vite builddist/assets/index-*.js modify timestamp changednpm testnpm run buildcurl http://127.0.0.1:PORT/api/healthcurl -I http://127.0.0.1:PORT/sw.js and confirm no-cache headersUse --update-env to update environment variables, restarting alone won’t refresh changed env vars.