- Node.js 20+
- A GitHub account (for OAuth)
- A Neon account (free tier: https://neon.tech)
git clone <repo-url>
cd Techy
npm installThe repo includes:
- Tailwind CSS v4 via
@tailwindcss/vite - Melt UI for headless Svelte 5 UI primitives
- GSAP for optional motion and animation work
No extra Tailwind init command is required. The Tailwind entrypoint lives at src/app.css.
Create an OAuth App at https://github.com/settings/developers → "OAuth Apps" → "New OAuth App"
| Field | Value |
|---|---|
| Application name | Techy (or anything) |
| Homepage URL | http://localhost:5173 (dev) or your prod URL |
| Authorization callback URL | http://localhost:5173/auth/callback/github |
Copy the Client ID and generate a Client Secret.
For production, create a second OAuth App with the production domain, or update the callback URL.
- Sign in at https://console.neon.tech
- Create a new project (PostgreSQL 16)
- From the dashboard, copy the pooled connection string (not the direct one)
- It looks like:
postgresql://user:[email protected]/dbname?sslmode=require
- It looks like:
cp .env.example .envEdit .env:
# Generate with: openssl rand -hex 32
AUTH_SECRET=<your-secret>
GITHUB_CLIENT_ID=<from-step-2>
GITHUB_CLIENT_SECRET=<from-step-2>
# Your GitHub username — only this account can log in
ALLOWED_GITHUB_USERNAME=<your-github-username>
# Optional debug bypass for local and agent-driven Playwright testing
# Set this to true only when you want the bypass available
DEBUG_AUTH_BYPASS_ENABLED=true
# Use the same strong secret locally and in Vercel if you want the agent to access both
DEBUG_AUTH_BYPASS_SECRET=<long-random-secret>
# Optional display name shown in the app when using the bypass
DEBUG_AUTH_BYPASS_NAME=Agent
# Neon pooled connection string from step 3
DATABASE_URL=postgresql://...?sslmode=require
# Required for assistant/provider work once the assistant-first phase lands
ANTHROPIC_API_KEY=
OPENAI_API_KEY=
OPENROUTER_API_KEY=
# Optional kill switch for unofficial LeetCode daily-practice fetching.
# Manual JSON practice import should still work when this is false.
LEETCODE_DAILY_FETCH_ENABLED=false
# Optional speech-to-text fallback provider for voice input
# Leave unset to use browser speech recognition only
SPEECH_TRANSCRIPTION_PROVIDER=
SPEECH_TRANSCRIPTION_API_KEY=# Generate migration SQL from schema
npm run db:generate
# Apply migrations to Neon
npm run db:migrateMigrations are output to drizzle/. Commit them.
npm run devVisit http://localhost:5173. You'll be redirected to the standalone /signin page. After sign-in, you'll enter the protected app shell.
npm run checknpm run testUse npm run test:watch while iterating locally.
Use the repo-local backfill script only when you intentionally want to clean up older note rows that were saved before the canonical category taxonomy landed:
npm run normalize:note-categories -- --applyThe command is a dry run unless --apply is provided. It rewrites only safe legacy category labels, keeps tags and bodies unchanged, and prints ambiguous or unknown categories for manual review.
- Tailwind is configured through the Vite plugin in
vite.config.ts - Global Tailwind import and shared theme tokens live in
src/app.css - Root layout imports
src/app.cssfromsrc/routes/+layout.svelte - Melt UI is available for new interactive UI work; it does not provide Techy's final visual styling on its own
- GSAP is installed and currently wired into restrained chat and nav motion; keep any new usage scoped to meaningful state transitions
The app is wired as an installable online-first PWA via @vite-pwa/sveltekit. The PWA layer is active in production builds (npm run build) and not active in dev.
What is included:
@vite-pwa/sveltekitconfigured invite.config.ts— generates the service worker and manifest at build time@vite-pwa/assets-generatorused to generate icons fromstatic/pwa-icon-source.svg- Generated icon assets in
static/:pwa-64x64.png,pwa-192x192.png,pwa-512x512.png,maskable-icon-512x512.png,apple-touch-icon-180x180.png,favicon.ico manifest.webmanifestoutput with name "Techy",display: standalone,start_url: /,theme_color: #09090b,background_color: #09090b
Service-worker scope:
- Static app-shell and build assets (
client/**) are precached - Authenticated routes (
/api/,/auth/,/debug/,/signin) are excluded from navigation fallback - No runtime caching — all authenticated data requests (notes, chat, AI) go to the network
- The auth flow and session boundary are unchanged after install
Icons can be regenerated at any time from the source SVG:
npx @vite-pwa/assets-generator --preset minimal-2023 static/pwa-icon-source.svg-
Push the repo to GitHub
-
Go to vercel.com → Add New Project → import the repo
-
In Project Settings → Environment Variables, add every variable from
.env:Variable Notes AUTH_SECRETGenerate with openssl rand -hex 32GITHUB_CLIENT_IDFrom your production GitHub OAuth App GITHUB_CLIENT_SECRETFrom your production GitHub OAuth App ALLOWED_GITHUB_USERNAMEYour GitHub login (case-sensitive) DEBUG_AUTH_BYPASS_ENABLEDOptional. Set to trueonly when you want the debug bypass availableDEBUG_AUTH_BYPASS_SECRETOptional. Strong shared secret for local and deployed debug login DEBUG_AUTH_BYPASS_NAMEOptional. Display name for the debug session, e.g. AgentDATABASE_URLNeon pooled connection string (see below) ANTHROPIC_API_KEYRequired for Anthropic models on /chatOPENAI_API_KEYRequired for OpenAI models on /chatOPENROUTER_API_KEYRequired for OpenRouter models on /chatand/practicetutoringLEETCODE_DAILY_FETCH_ENABLEDOptional. Enables unofficial /practicedaily fetch when set totrue; keep manual JSON import available either waySPEECH_TRANSCRIPTION_PROVIDEROptional. Enables server speech-to-text fallback when implemented SPEECH_TRANSCRIPTION_API_KEYOptional. Provider key for server speech-to-text fallback AUTH_TRUST_HOSTSet to true— required on Vercel so Auth.js trusts the host header -
Create a second GitHub OAuth App (or update the existing one) with:
- Callback URL:
https://<your-vercel-domain>/auth/callback/github
- Callback URL:
-
Click Deploy —
adapter-autodetects Vercel automatically
Important: Use the Neon pooled connection string for DATABASE_URL in production. The direct connection string does not work reliably in serverless environments.
Build note: The build requires DATABASE_URL to be set in the Vercel environment variables — the DB client is initialised at module load time. Vercel injects env vars into the build, so this works automatically once the variable is configured.
Env-loading note: App server modules use SvelteKit's private env access, while standalone tooling such as drizzle.config.ts still reads from plain Node environment variables. Keep running Drizzle commands from the repo root so .env is loaded as expected.
The /practice direction includes an unofficial LeetCode daily challenge fetch path plus a manual JSON import fallback. Treat the automated fetch as fragile: it may break if LeetCode changes its internal API or content shape, and it should remain disabled unless intentionally enabled for personal use.
Practice tutoring uses OpenRouter and requires OPENROUTER_API_KEY. Tutor turns are transient and are not persisted in chat history or a practice transcript table.
For agent-driven Playwright testing, Techy can mint a signed debug session without going through GitHub OAuth.
- Set
DEBUG_AUTH_BYPASS_ENABLED=true - Set a long random
DEBUG_AUTH_BYPASS_SECRET - Use the same secret in your local
.envand Vercel if you want the agent to work against both environments - Hit
/debug/auth/loginwith the secret and an optionalredirectTo
Example:
curl -i "http://localhost:5173/debug/auth/login?secret=<DEBUG_AUTH_BYPASS_SECRET>&redirectTo=/notes"This bypass is app-level, not GitHub-level. It does not use your personal GitHub password, OAuth token, or browser session.
npm run db:studioOpens Drizzle Studio at http://local.drizzle.studio — a GUI for browsing and editing the database.
If Neon reports unexpectedly high monthly compute usage while Techy is idle, first confirm whether the compute is repeatedly waking rather than assuming data storage is growing. The Techy database is small enough that this warning is usually about compute time, not table size.
Useful checks:
neonctl projects list --output json
neonctl branches list --project-id <project-id> --output json
neonctl operations list --project-id <project-id> --output json
vercel logs --project techy --environment production --no-branch --since <iso-start> --until <iso-end> --jsonIn June 2026, the techy Neon endpoint was found with suspend_timeout_seconds: 0, and Neon rejected changing it to 300 on the active account with modifying the suspend interval is not permitted on this account. Manual suspend worked, but the endpoint restarted again without matching local processes, TCP database connections, Vercel cron jobs, or Vercel request logs at the restart timestamp.
The fix was to rotate the neondb_owner role password, then suspend the endpoint and wait without polling Neon. After rotation, the endpoint stayed idle, confirming that an old or unknown DATABASE_URL client had been waking the compute. Vercel production DATABASE_URL and local .env were then updated to the new Neon URL, followed by a production redeploy so the deployed app used the new secret.
Safe recovery sequence:
- Close Neon console tabs and stop local dev servers.
- Suspend the endpoint, wait 3-5 minutes without polling, then check operations once.
- If a new
start_computeappears without matching Vercel logs, reset the Neon role password. - Suspend again and repeat the no-poll wait.
- If the endpoint stays idle, update
DATABASE_URLonly in the intended consumers: local.env, Vercel production env, and any other known deploy targets. - Redeploy Vercel after updating production env vars.
- Remove any temporary files that held the rotated connection string.
Do not print or commit the new database URL. Prefer passing it through stdin or a 0600 temp file when updating deployment env vars.
| Issue | Fix |
|---|---|
DATABASE_URL environment variable is not set |
.env file missing or not loaded |
AccessDenied on GitHub sign-in |
ALLOWED_GITHUB_USERNAME doesn't match your GitHub login (case-sensitive) |
Debug bypass returns 404 |
DEBUG_AUTH_BYPASS_ENABLED is not set to true |
Debug bypass returns 403 |
DEBUG_AUTH_BYPASS_SECRET is missing or does not match |
AUTH_SECRET errors |
Run openssl rand -hex 32 and set the output as AUTH_SECRET |
| CSRF / bad request errors on Vercel sign-in | AUTH_TRUST_HOST env var not set to true in Vercel settings |
| D3 graph blank in production | Check browser console — likely a CSP issue with inline SVG |
| Wikilinks show as broken (red) | The linked note's title must match exactly — check capitalisation. See docs/NOTES.md. |
| Neon compute usage spikes while app is idle | Check for repeated start_compute operations and compare with Vercel logs. If manual suspend does not hold, rotate the Neon role password and update only known DATABASE_URL consumers. |
docs/NOTES.md— Note schema, template, wikilink format, authoring rulesdocs/schema.md— Database tables, relationships, and persistence boundariesdocs/API.md— All routes, form actions, and API endpointsdocs/ARCHITECTURE.md— System design and data flowdocs/test.md— Test commands, scope, and CI pipelinedocs/STYLE-GUIDE.md— High-level UI direction, theming, and motion rulesdocs/DECISIONS.md— Why the stack was chosen