A weekly mood check-in that turns your curiosity into a personalized quest — a web app and a self-contained Chrome extension, sharing the same check-in → quest → feedback flow but with two independent backends.
- Check in — report your mood, energy, and progress, then pick up to 3 topics you're curious about (science, tech, space, art, nature, culture, music, history, economics, literature, philosophy, psychology).
- Get a quest — an LLM turns that into a short challenge: a title, a description with a concrete search query and journal prompt, a time estimate, and up to 3 recommended article/paper/video links. An assigned quest stays active for up to 7 days.
- Give feedback — rate it, mark it complete, optionally journal about it. This earns XP (base 100, +50 for completion, +25 for a rating ≥ 4, +30 for a journal entry) and updates your streak, and feeds into future prompts so quests don't repeat.
- Get reminded — the Chrome extension fires a weekly notification via
chrome.alarmsso the habit doesn't depend on remembering to open the app.
CoachApi (src/lib/api/coach-api.ts) is the
shared interface; which implementation backs it is picked by
isExtensionRuntime() (VITE_COACH_RUNTIME env define):
- Web app (
coach-api.web.ts) — calls TanStack Start server functions (src/lib/api/*.functions.ts) →coach/service.server.ts, which builds a prompt (llm/prompt.server.ts, with per-topic niche hints so quests don't stay generic) and calls OpenAIgpt-4o-miniusing the server-sideOPENAI_API_KEY. Falls back to a static per-topic quest bank (lib/quests/fallback.ts) if the key is missing or the call fails. - Chrome extension (
coach-api.extension.ts) — fully client-side. Messages the background service worker (extension/src/background.ts), which keeps profile/check-ins/quests/feedback inchrome.storage.syncand calls Groq's OpenAI-compatible API (llama-3.1-8b-instant) directly using a key the user pastes into the extension's own Settings page — never a server round-trip.
The web app has its own /settings route (src/settings.tsx);
the extension has a separate options page
(extension/src/settings.tsx, wired via
options_page in the manifest) with a "test key" button and a "clear all
data" reset. They're independent implementations, not shared code.
- TanStack Start (React 19) with file-based
routing — see
src/routes/README.mdfor routing conventions - Vite 7, Tailwind v4, Radix UI primitives (shadcn-style components in
src/components/ui) - TanStack Query,
react-hook-form+zod - Chrome extension (Manifest V3), built as a separate Vite target
(
vite.extension.config.ts) with its own popup/settings/background entry points
src/
components/CuriosityCoach.tsx # the check-in -> quest -> feedback UI (shared by both runtimes)
settings.tsx # web app's /settings route (API-key UI for the web runtime)
lib/
coach/service.server.ts # web path: check-in -> LLM prompt -> quest -> save
llm/
client.server.ts # OpenAI call (gpt-4o-mini) for the web path
prompt.server.ts # prompt builder incl. per-topic niche hints
quests/fallback.ts # static per-topic quest bank (no-LLM fallback)
api/
coach-api.ts # shared CoachApi interface + isExtensionRuntime()
coach-api.web.ts # web implementation -> server functions
coach-api.extension.ts # extension implementation -> chrome.runtime messages
*.functions.ts # TanStack server functions (web path only)
store/store.server.ts # web path persistence
types/coach.ts # Mood/Topic/Quest/UserProfile schemas (zod)
extension/
manifest.json # MV3 manifest: popup + options_page + background worker
popup.html / settings.html # extension entry pages
src/
background.ts # extension's own storage + Groq call + weekly alarm/notification
popup.tsx # renders CuriosityCoach in extension runtime mode
settings.tsx # extension options page: Groq key entry, test, clear data
scripts/prepare-extension.mjs # copies manifest/icons into dist/extension after build
npm installWeb app — server-side env vars (create a .env, none are committed):
| Variable | Required | Purpose |
|---|---|---|
OPENAI_API_KEY |
No | Enables LLM-generated quests for the web app. Without it, every quest comes from the fallback bank. |
CORS_ORIGINS |
No | Comma-separated allowed origins for the REST handler. Defaults to http://localhost:5173,http://127.0.0.1:5173. |
Extension — no env vars; the user pastes their own Groq API key
(free, from console.groq.com) into the
extension's options page, stored in chrome.storage.sync.
npm run dev # web app (vite dev)
npm run build # web app production build
npm run lint
npm run format # prettier --write .npm run dev:extension # watch build to dist/extension
npm run build:extension # one-off build to dist/extensionLoad dist/extension as an unpacked extension in Chrome
(chrome://extensions → Developer mode → Load unpacked). Host permissions
currently cover local dev ports (5173/3000) plus api.groq.com — update
extension/manifest.json if the web app is deployed elsewhere.