Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 42 additions & 23 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,47 +1,66 @@
# Copy to .env.local. Never commit real values. All variables are validated in config/env.ts at boot.
# Copy to .env.local. Never commit real values. Every variable is validated in config/env.ts at boot.
# Free-tier sign-up notes are in docs/TECH_STACK.md.
#
# Sections are grouped by when they become required. A variable is added to config/env.ts by the
# PR that introduces the service, never earlier — so an unused key here is documentation only.

# ── Firebase (server: service account; Spark plan) ─────────────────────────────
# ══════════════════════════════════════════════════════════════════════════════
# REQUIRED NOW (config/env.ts validates these)
# ══════════════════════════════════════════════════════════════════════════════

# ── Firebase Admin (server; service account; Spark plan) ──────────────────────
FIREBASE_PROJECT_ID=
FIREBASE_CLIENT_EMAIL=
# Single line, keep the \n escapes exactly as exported from the JSON key.
FIREBASE_PRIVATE_KEY=

# ── Firebase (client; public by design, restricted by Firestore rules + Auth domains)
# ── Firebase web config (client; public by design, governed by rules + Auth domains)
NEXT_PUBLIC_FIREBASE_API_KEY=
NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN=
NEXT_PUBLIC_FIREBASE_PROJECT_ID=
NEXT_PUBLIC_FIREBASE_APP_ID=

# ── LLM providers (free tiers) ─────────────────────────────────────────────────
GEMINI_API_KEY= # Google AI Studio
GROQ_API_KEY= # Groq console (fallback)
OLLAMA_BASE_URL=http://localhost:11434 # local dev only, optional
# ── LLM: Google AI Studio (free tier) ─────────────────────────────────────────
GEMINI_API_KEY=

# ── Vapi (legacy voice pipeline; removed in P4.6 — RISKS R12) ────────────────
NEXT_PUBLIC_VAPI_WEB_TOKEN=
NEXT_PUBLIC_VAPI_WORKFLOW_ID=

# ── App ────────────────────────────────────────────────────────────────────────
NEXT_PUBLIC_APP_URL=http://localhost:3000
# Set to 1 only in CI build steps that have no secrets.
SKIP_ENV_VALIDATION=

# ══════════════════════════════════════════════════════════════════════════════
# ADDED IN LATER PHASES (documented ahead; not yet read by config/env.ts)
# ══════════════════════════════════════════════════════════════════════════════

# ── Rate limiting / quotas (Upstash Redis free tier) ───────────────────────────
# P1.5 Rate limiting / quotas — Upstash Redis free tier
UPSTASH_REDIS_REST_URL=
UPSTASH_REDIS_REST_TOKEN=

# ── Object storage (Cloudflare R2, S3-compatible; or Supabase Storage) ─────────
STORAGE_PROVIDER=r2 # r2 | supabase
R2_ACCOUNT_ID=
R2_ACCESS_KEY_ID=
R2_SECRET_ACCESS_KEY=
R2_BUCKET=
NEXT_PUBLIC_STORAGE_PUBLIC_URL= # public bucket domain for images/audio (also added to CSP)

# ── Observability ──────────────────────────────────────────────────────────────
# P1.6 Observability — Sentry developer tier, PostHog free tier
SENTRY_DSN=
NEXT_PUBLIC_SENTRY_DSN=
NEXT_PUBLIC_POSTHOG_KEY=
NEXT_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com

# ── Email (Resend free tier) ───────────────────────────────────────────────────
# P2.3 LLM fallback — Groq free tier; Ollama for local dev (optional)
GROQ_API_KEY=
OLLAMA_BASE_URL=http://localhost:11434

# P2.6 Object storage — Cloudflare R2 (S3-compatible) or Supabase Storage (ADR-0004)
STORAGE_PROVIDER=r2
R2_ACCOUNT_ID=
R2_ACCESS_KEY_ID=
R2_SECRET_ACCESS_KEY=
R2_BUCKET=
NEXT_PUBLIC_STORAGE_PUBLIC_URL=

# P3.4 Email — Resend free tier
RESEND_API_KEY=
EMAIL_FROM=JustPrep <no-reply@your-domain>

# ── App ────────────────────────────────────────────────────────────────────────
NEXT_PUBLIC_APP_URL=http://localhost:3000
CRON_SECRET= # random 32+ bytes; used by GitHub Actions cron → /api/cron/*
SESSION_COOKIE_NAME=session # becomes __Host-session in production automatically
SKIP_ENV_VALIDATION= # set to 1 only in CI build steps that lack secrets
# P5.7 Signed cron — random 32+ bytes; used by GitHub Actions → /api/cron/*
CRON_SECRET=
16 changes: 16 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Normalise line endings (RISKS R19). Text files are LF in the repo and in working trees.
* text=auto eol=lf

# Lockfiles and generated files
pnpm-lock.yaml -diff

# Binary assets
*.png binary
*.jpg binary
*.jpeg binary
*.ico binary
*.woff binary
*.woff2 binary
*.webp binary
*.mp3 binary
*.wav binary
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/config.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: Security vulnerability
url: https://github.com/Jeston10/JustPrep_Ai_interview/blob/main/SECURITY.md
url: https://github.com/Jeston10/JustPrep/blob/main/SECURITY.md
about: Please report security issues privately as described in SECURITY.md.
73 changes: 54 additions & 19 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,15 @@
# Until then this workflow is expected to fail on the legacy codebase; do not weaken it to make it pass.
name: CI

# Branching model (PROCESS.md §Branching): PR branch → milestone/* → dev → main.
# The pipeline runs on every PR (any base) and on pushes to the three integration tiers.
on:
pull_request:
push:
branches: [main]
branches:
- main
- dev
- "milestone/**"

concurrency:
group: ci-${{ github.ref }}
Expand All @@ -16,23 +21,25 @@ permissions:

env:
NODE_VERSION: 22
PNPM_VERSION: 10
# pnpm version comes from package.json#packageManager (pnpm/action-setup reads it).

jobs:
static:
name: Lint · Typecheck · Secrets
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with: { fetch-depth: 0 }
with:
fetch-depth: 0
- uses: pnpm/action-setup@v4
with: { version: ${{ env.PNPM_VERSION }} }
- uses: actions/setup-node@v4
with: { node-version: ${{ env.NODE_VERSION }}, cache: pnpm }
with:
node-version: ${{ env.NODE_VERSION }}
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm lint
- run: pnpm typecheck
- run: pnpm dlx knip
- run: pnpm exec knip
- name: Secret scan
uses: gitleaks/gitleaks-action@v2
env:
Expand All @@ -45,15 +52,18 @@ jobs:
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with: { version: ${{ env.PNPM_VERSION }} }
- uses: actions/setup-node@v4
with: { node-version: ${{ env.NODE_VERSION }}, cache: pnpm }
with:
node-version: ${{ env.NODE_VERSION }}
cache: pnpm
- run: pnpm install --frozen-lockfile
- uses: actions/setup-java@v4 # Firebase emulators require JDK 21+
with:
distribution: temurin
java-version: "21"
- run: pnpm test -- --coverage
- name: Integration (Firestore emulator)
run: pnpm dlx firebase-tools emulators:exec --only auth,firestore "pnpm test:integration"
env:
FIREBASE_PROJECT_ID: demo-justprep
run: pnpm dlx firebase-tools emulators:exec --only auth,firestore --project demo-justprep "pnpm test:integration"

build:
name: Build · Audit · Size
Expand All @@ -62,15 +72,16 @@ jobs:
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with: { version: ${{ env.PNPM_VERSION }} }
- uses: actions/setup-node@v4
with: { node-version: ${{ env.NODE_VERSION }}, cache: pnpm }
with:
node-version: ${{ env.NODE_VERSION }}
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm audit --audit-level=high
- run: pnpm build
env:
SKIP_ENV_VALIDATION: "1"
- run: pnpm dlx size-limit
- run: pnpm exec size-limit

e2e:
name: E2E · Responsive · A11y
Expand All @@ -80,15 +91,39 @@ jobs:
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with: { version: ${{ env.PNPM_VERSION }} }
- uses: actions/setup-node@v4
with: { node-version: ${{ env.NODE_VERSION }}, cache: pnpm }
with:
node-version: ${{ env.NODE_VERSION }}
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm dlx playwright install --with-deps chromium webkit
- run: pnpm dlx firebase-tools emulators:exec --only auth,firestore "pnpm test:e2e"
- uses: actions/setup-java@v4 # Firebase emulators require JDK 21+
with:
distribution: temurin
java-version: "21"
- run: pnpm exec playwright install --with-deps chromium
# Demo values: the Firebase web config is public by design and the emulator accepts any project id.
# Server secrets are absent, so SKIP_ENV_VALIDATION=1; nothing in the e2e suite needs them yet.
- run: pnpm build
env:
SKIP_ENV_VALIDATION: "1"
NEXT_PUBLIC_FIREBASE_API_KEY: demo-api-key
NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN: demo-justprep.firebaseapp.com
NEXT_PUBLIC_FIREBASE_PROJECT_ID: demo-justprep
NEXT_PUBLIC_FIREBASE_APP_ID: "1:0:web:demo"
NEXT_PUBLIC_VAPI_WEB_TOKEN: demo
NEXT_PUBLIC_VAPI_WORKFLOW_ID: demo
NEXT_PUBLIC_APP_URL: http://localhost:3000
- run: pnpm dlx firebase-tools emulators:exec --only auth,firestore --project demo-justprep "pnpm test:e2e"
env:
FIREBASE_PROJECT_ID: demo-justprep
SKIP_ENV_VALIDATION: "1"
NEXT_PUBLIC_FIREBASE_API_KEY: demo-api-key
NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN: demo-justprep.firebaseapp.com
NEXT_PUBLIC_FIREBASE_PROJECT_ID: demo-justprep
NEXT_PUBLIC_FIREBASE_APP_ID: "1:0:web:demo"
NEXT_PUBLIC_VAPI_WEB_TOKEN: demo
NEXT_PUBLIC_VAPI_WORKFLOW_ID: demo
NEXT_PUBLIC_APP_URL: http://localhost:3000
CI: "true"
- uses: actions/upload-artifact@v4
if: failure()
with:
Expand Down
28 changes: 28 additions & 0 deletions .github/workflows/promote-guard.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Enforces the promotion path: only `dev` may open a PR into `main`, and only
# `milestone/*` or hotfix branches may open a PR into `dev`. Pair with branch
# protection (required checks: CI + this job) so the rule cannot be bypassed.
name: Promotion guard

on:
pull_request:
branches: [main, dev]

permissions:
contents: read

jobs:
guard:
name: Check source branch
runs-on: ubuntu-latest
steps:
- name: main accepts only dev
if: github.base_ref == 'main' && github.head_ref != 'dev'
run: |
echo "::error::PRs into main must come from dev (got '${{ github.head_ref }}')."
exit 1
- name: dev accepts milestone/* and hotfix/*
if: github.base_ref == 'dev' && !startsWith(github.head_ref, 'milestone/') && !startsWith(github.head_ref, 'hotfix/')
run: |
echo "::error::PRs into dev must come from milestone/* or hotfix/* (got '${{ github.head_ref }}')."
exit 1
- run: echo "Promotion path OK"
7 changes: 7 additions & 0 deletions .npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# pnpm settings
# eslint-config-next references its plugins by name; pnpm 10 stopped hoisting them by default.
public-hoist-pattern[]=*eslint*
public-hoist-pattern[]=*prettier*
# Fail on peer-dependency conflicts instead of silently resolving them.
strict-peer-dependencies=false
auto-install-peers=true
1 change: 1 addition & 0 deletions .nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
22
11 changes: 11 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Prettier ignore — generated, vendored, and binary paths
node_modules
.next
out
build
coverage
playwright-report
test-results
pnpm-lock.yaml
public
*.md
8 changes: 8 additions & 0 deletions .size-limit.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[
{
"name": "client JS, all static chunks, gzip — ratchet: lower this as legacy code is removed (P3/P4); per-route budgets arrive with Lighthouse CI (P8.7)",
"path": ".next/static/chunks/**/*.js",
"limit": "560 kB",
"gzip": true
}
]
9 changes: 9 additions & 0 deletions .vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"recommendations": [
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"bradlc.vscode-tailwindcss",
"vitest.explorer",
"ms-playwright.playwright"
]
}
17 changes: 17 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.codeActionsOnSave": { "source.fixAll.eslint": "explicit" },
"eslint.useFlatConfig": true,
"eslint.validate": ["javascript", "typescript", "typescriptreact"],
"files.eol": "\n",
"files.insertFinalNewline": true,
"files.trimTrailingWhitespace": true,
"typescript.tsdk": "node_modules/typescript/lib",
"typescript.preferences.importModuleSpecifier": "non-relative",
"tailwindCSS.experimental.classRegex": [
["cn\(([^)]*)\)", "[\"'`]([^\"'`]*)[\"'`]"],
["cva\(([^)]*)\)", "[\"'`]([^\"'`]*)[\"'`]"]
],
"search.exclude": { "**/node_modules": true, "**/.next": true, "pnpm-lock.yaml": true }
}
12 changes: 11 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
<!-- BEGIN:nextjs-agent-rules -->

# This is NOT the Next.js you know

This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` (resolved from this file's directory; in monorepos the `next` package may not be visible from the repo root) before writing any code. Heed deprecation notices.

This block is written and re-added by `next dev` — verify at `node_modules/next/dist/server/lib/generate-agent-files.js`. Removing it from a diff only re-creates the uncommitted change; committing it with your work keeps the tree clean.

<!-- END:nextjs-agent-rules -->

# AGENTS.md — Operating manual for JustPrep

This file is the single entry point for any engineer or AI agent working in this repository. Read it fully before touching code. It is intentionally short; it links to the authoritative documents in `docs/`.
Expand Down Expand Up @@ -36,7 +46,7 @@ docs/ Handbook: architecture, standards, security, design, decisions (
## 4. How to work

- **Before starting a task**: read the relevant `docs/` page and the ADRs it references. Check `docs/MILESTONES.md` for where the task sits and what it depends on, and **read the `docs/RISKS.md` rows tagged for that phase** (MILESTONES §13). A PR that closes a risk row says so (`Closes R7`) and updates the row's status.
- **Branching**: `main` is protected. Branch as `type/short-description` (`feat/text-interview-mode`, `fix/profile-cookie-await`). One concern per branch.
- **Branching**: `main` and `dev` are protected. Cut work branches from the current `milestone/p<N>-<name>` branch as `type/short-description` (`feat/text-interview-mode`, `fix/profile-cookie-await`) and PR back into it. Promotion is `milestone/* → dev → main`, each by PR with green CI (`PROCESS.md`). One concern per branch.
- **Commits**: Conventional Commits (`feat:`, `fix:`, `refactor:`, `docs:`, `test:`, `chore:`, `perf:`, `security:`). Imperative mood, ≤ 72 chars subject, body explains *why*.
- **Pull requests**: use the template. Include screenshots for UI at 375 px and 1440 px minimum. Link the plan item or issue. Self-review the diff before requesting review.
- **Definition of done** (all required):
Expand Down
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,27 @@ All notable changes to this project are documented here. Format follows [Keep a
- Engineering handbook: `AGENTS.md`, `CONTRIBUTING.md`, `PROCESS.md`, `SECURITY.md`, and `docs/` (guardrails, architecture, tech stack, security, design system, component library, responsive rules, coding standards, quality, implementation plan, milestones execution plan with legacy → target migration map, risk register/watch-list, ADRs 0001–0010).
- GitHub templates (issues, PR), `CODEOWNERS`, CI workflow, `lefthook.yml`, `.env.example`.

- Toolchain (PR 0.1): pnpm 10, strict type-aware ESLint with layer rules, Prettier, lefthook hooks, commitlint, `.gitattributes`, `.nvmrc`, VS Code settings.
- Strict TypeScript flags; production build fails on type or lint errors (PR 0.2).
- `config/env.ts` — validated environment, the only `process.env` reader, lint-enforced (PR 0.3).
- Test pipeline (PR 0.5): vitest with coverage thresholds, Playwright at 375/768/1280 on a production build with horizontal-overflow assertions, size-limit ratchet, Firebase emulator config, first unit/e2e tests. CI fully green.
- Branching model: `milestone/* → dev → main` with a promotion-guard workflow and branch protection (`scripts/github/protect-branches.sh`).

### Changed
- README rewritten to describe the product, stack, and how to work in the repo.
- Legacy code brought to lint-clean under the strict config (356 violations → 0).
- Dependencies (PR 0.4): Next 15.3 → 16.3 (Turbopack default, native flat ESLint config), React 19.3, AI SDK 4 → 7 with `@ai-sdk/google` 4 (`generateText` + `Output.object`, `createGoogle`), zod 3 → 4, firebase 12, firebase-admin 14. `pnpm audit`: 27 findings → 0. ESLint 10 and TypeScript 7 deferred until plugin peer ranges allow.
- Dead dependencies and files removed (`pdfjs-dist`, `next-themes`, `lucide-react`, `@radix-ui/react-label`, `tailwindcss-animate`, unused shadcn primitives) — `knip` is clean.
- Firebase Admin initialised lazily so secret-less CI builds succeed.
- `/api/news` reduced to an empty stub (it only ever served fabricated articles); widget removed in P3.5.

### Fixed
- Profile update always returned 500 (`cookies()` not awaited).
- Duplicate object keys in `DisplayTechIcons`; stateful `/g` regex in `InterviewCard`; `signIn` returned nothing on success; conflicting `AgentProps` definitions.

### Security
- Removed the server log that printed the raw session cookie and decoded claims (RISKS R1).
- `/api/vapi/generate` no longer echoes internal errors to the client and rejects malformed bodies (full auth + zod lands in P1.4).

## [0.1.0] — 2025-09

Expand Down
Loading
Loading