From be1b0637181544db122c8ac82157f906505e0704 Mon Sep 17 00:00:00 2001 From: Kai Chen Date: Fri, 17 Apr 2026 05:43:19 -0700 Subject: [PATCH 1/2] docs: expand README and contributor help files Rewrite README with full TOC, routes, APIs, env groups, MDX notes, CI/ Dependabot/automerge, git hooks, deployment, and doc map. Refresh CONTRIBUTING, SECURITY, CLAUDE, AGENTS; annotate .env.example; extend PR template checklist. Stop ignoring CLAUDE.md so it can live in-repo with the rest of the documentation. Made-with: Cursor Co-authored-by: Claude --- .env.example | 33 ++- .github/pull_request_template.md | 5 +- .gitignore | 1 - AGENTS.md | 83 +++--- CLAUDE.md | 54 ++++ CONTRIBUTING.md | 102 ++++++-- README.md | 420 ++++++++++++++++++++++--------- SECURITY.md | 83 +++++- 8 files changed, 583 insertions(+), 198 deletions(-) create mode 100644 CLAUDE.md diff --git a/.env.example b/.env.example index 372768b..f819923 100644 --- a/.env.example +++ b/.env.example @@ -1,24 +1,41 @@ -# Copy to .env.local — never commit secrets. +# ============================================================================= +# Copy this file to `.env.local` and fill in values. Never commit `.env.local`. +# Full documentation: README.md → "Environment variables" +# ============================================================================= -# Site URL (used for OAuth redirectTo — set to production URL in Vercel env vars) +# --- Site & OAuth ------------------------------------------------------------- +# Production: set to your canonical origin (e.g. https://kaichen.dev) in Vercel. +# Used by Supabase OAuth redirect URLs from the browser client. NEXT_PUBLIC_SITE_URL= -# Supabase (listening_history, listening_stats, gallery_photos + storage) +# --- Supabase ---------------------------------------------------------------- +# Project URL and anon key (safe to expose in the browser bundle). +# Used by getSupabaseAnon(), guestbook POST, gallery reads, /admin client, etc. NEXT_PUBLIC_SUPABASE_URL= NEXT_PUBLIC_SUPABASE_ANON_KEY= + +# Server-only: bypasses Row Level Security — NEVER expose to the client. +# Required for /api/lastfm/now-playing DB writes (listening_history / listening_stats). SUPABASE_SERVICE_ROLE_KEY= -# Last.fm (now playing / last played) +# --- Last.fm ----------------------------------------------------------------- +# API key from https://www.last.fm/api/account/create +# If unset, /api/lastfm/now-playing still responds with graceful fallbacks. LASTFM_API_KEY= -# GitHub GraphQL (contributions API + optional star counts on /projects) +# --- GitHub ------------------------------------------------------------------ +# Personal access token or fine-grained token with repo read access. +# Powers /api/github/contributions and /api/github/stars. GITHUB_TOKEN= -# Sentry (optional — error monitoring; omit for local dev / CI with no reporting) -# Client bundle reads NEXT_PUBLIC_SENTRY_DSN; Node/Edge may use SENTRY_DSN (falls back to NEXT_PUBLIC). +# --- Sentry (optional) ------------------------------------------------------- +# Omit entirely for local dev / CI with no error reporting. +# Client bundle reads NEXT_PUBLIC_SENTRY_DSN; server/edge can use SENTRY_DSN. NEXT_PUBLIC_SENTRY_DSN= SENTRY_DSN= -# Build: upload source maps (readable stacks in Sentry) — org/project slugs from Sentry → Settings → Projects + +# Build-time only: upload source maps during `next build` (e.g. on Vercel). +# Get org/project slugs from Sentry → Settings. Do not commit SENTRY_AUTH_TOKEN. SENTRY_AUTH_TOKEN= SENTRY_ORG= SENTRY_PROJECT= diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index e2f6a18..a04452a 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,6 +1,6 @@ ## Summary - + ## Type of change @@ -34,4 +34,5 @@ - [ ] I have read [CONTRIBUTING.md](CONTRIBUTING.md) and [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md). - [ ] No secrets, `.env.local`, or production tokens are included in this PR. -- [ ] **README** / **`.env.example`** updated if user-facing behavior or required env vars changed. +- [ ] **[README.md](../README.md)** / **[`.env.example`](../.env.example)** updated if routes, APIs, user-facing behavior, or env vars changed. +- [ ] **[CONTRIBUTING.md](../CONTRIBUTING.md)** / **[AGENTS.md](../AGENTS.md)** updated if contribution or agent workflow rules changed. diff --git a/.gitignore b/.gitignore index 78f1ac0..480fbc0 100644 --- a/.gitignore +++ b/.gitignore @@ -23,7 +23,6 @@ # misc .DS_Store *.pem -CLAUDE.md # debug npm-debug.log* diff --git a/AGENTS.md b/AGENTS.md index a5faee2..dca2ae4 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,37 +1,40 @@ # AGENTS.md — kaichen.dev -Shared instructions for any AI coding agent (Cursor, Claude Code, Codex, etc.) -working on this repo. +Instructions for **AI coding agents** (Cursor, Claude Code, Codex, Copilot, etc.) and anyone running **automated** edits against this repository. -## Git workflow — ALWAYS branch + PR +**Human-oriented** contributor docs: [CONTRIBUTING.md](CONTRIBUTING.md). **Full project reference:** [README.md](README.md) (architecture, APIs, environment variables, CI, MDX, deployment). -**Never push directly to `main`.** `main` is protected; pushes must go through -a pull request with required status checks. Even if the agent has bypass -permissions, use the PR flow. +--- -For every change: +## Git workflow — always branch + PR + +**Never push directly to `main`.** Treat `main` as protected: every change goes through a **pull request** with CI green. Even with bypass permissions, use the PR flow unless the repository owner explicitly overrides with a written reason. + +### Steps + +1. **Branch** from latest `main`: -1. Create a feature branch off the latest `main`: ```bash git checkout main && git pull --ff-only git checkout -b / ``` - Branch name format: `fix/…`, `feat/…`, `chore/…`, `docs/…`, `refactor/…`. -2. Commit with a conventional-style message. **Every commit in this repo must - credit Claude as a co-author** — `.githooks/prepare-commit-msg` appends the - trailer automatically once `npm install` has run, but include it explicitly - anyway (e.g. when you commit via an API/web editor that skips git hooks): + Prefix examples: `fix/`, `feat/`, `chore/`, `docs/`, `refactor/`. + +2. **Commit** using conventional-style messages: ``` : - + Co-authored-by: Claude ``` -3. Push the branch and open a PR with `gh pr create`: + The `Co-authored-by` line is **required** for this repo’s policy (see [Git hooks](#git-hooks) below). If your tool cannot run hooks, append the trailer manually. + +3. **Push** and **open a PR**: + ```bash git push -u origin HEAD gh pr create --title ": " --body "$(cat <<'EOF' @@ -44,33 +47,51 @@ For every change: )" ``` -4. Wait for CI (lint + typecheck + test + build) to pass. Do **not** force-push - to `main`. Do **not** bypass required status checks. +4. **Wait for CI** — lint, typecheck, test, build. Do not force-push to `main` or skip required checks without owner approval. -5. If the user asks for a direct commit to `main`, push back on it and default - to the PR flow unless they explicitly override with a clear reason. +5. If the user asks to **commit straight to `main`**, default to explaining the PR workflow and ask for explicit exception. + +--- ## Before you commit -Run the same sequence CI runs — fail fast locally: +Mirror CI locally: ```bash npm run lint && npm run typecheck && npm run test && npm run build ``` +Update **[README.md](README.md)** and/or **[`.env.example`](.env.example)** when you change routes, APIs, or required environment variables. + +--- + ## Git hooks (co-author trailer) -This repo ships a `prepare-commit-msg` hook at `.githooks/prepare-commit-msg` -that appends `Co-authored-by: Claude ` on every commit -(skipped for merge commits). `npm install` sets `core.hooksPath = .githooks` -via the `postinstall` script, so the hook activates automatically after a -fresh clone + install. +- Hook path: **`.githooks/prepare-commit-msg`** +- Activation: `npm install` runs `postinstall` → `git config core.hooksPath .githooks` (best effort; may fail in some sandboxes — set manually if needed). +- Behavior: appends `Co-authored-by: Claude ` via `git interpret-trailers` (skipped for **merge** commits; idempotent if trailer exists). + +If hooks do not run (web editor, `--no-verify`, CI-only commits), **add the trailer by hand** so policy is not dropped silently. + +--- + +## Secrets — never commit + +Do not stage or commit: + +- `.env.local`, `.env.vercel.check`, `.env.sentry-build-plugin` +- Any `*.local`, `*.secret`, or raw tokens + +Run `git status` and `git diff --staged` before committing. -If you commit in an environment where the hook can't run (GitHub web editor, -some sandboxed agents, `-n` / `--no-verify` situations), add the trailer -manually so the policy is never silently dropped. +--- -## Secrets +## Where to read more -Never commit `.env.local`, `.env.vercel.check`, `.env.sentry-build-plugin`, or -anything matching `*.local` / `*.secret`. Check `git status` before staging. +| Topic | Document | +| --- | --- | +| APIs, env, CI, MDX, fork checklist | [README.md](README.md) | +| Human contribution process | [CONTRIBUTING.md](CONTRIBUTING.md) | +| Short Claude Code stack summary | [CLAUDE.md](CLAUDE.md) | +| Security reporting | [SECURITY.md](SECURITY.md) | +| Cursor IDE git reminder | [`.cursor/rules/git-workflow.mdc`](.cursor/rules/git-workflow.mdc) | diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..ca61d93 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,54 @@ +# kaichen.dev — Claude Code context + +@AGENTS.md — **read first** for Git workflow (branch + PR only, co-author trailer, secrets). + +--- + +## What this repo is + +Personal website of Kai Chen — **Next.js 16** (App Router), **React 19**, **TypeScript**, **Tailwind CSS 4**, deployed on **Vercel**. + +Canonical documentation: **[README.md](README.md)** (setup, routes, APIs, env, CI, MDX, forking). + +--- + +## Architecture snapshot + +| Concern | Implementation | +| --- | --- | +| **Now playing** | `GET /api/lastfm/now-playing` — Last.fm + optional iTunes art; optional Supabase writes (`listening_*`) with service role | +| **Client polling** | `app/hooks/use-now-playing.ts` — polls every **10s**; `Cache-Control` on API allows short CDN cache | +| **GitHub** | `GET /api/github/contributions` — GraphQL calendar + REST; `GET /api/github/stars` — star counts | +| **Weather** | `GET /api/weather` — Open-Meteo, fixed coordinates (Berkeley), revalidated fetch | +| **Gallery** | Supabase Storage + `gallery_photos`; admin at `/admin` (OAuth + allowlisted email in app — **RLS must match in Supabase**) | +| **Notes** | MDX under `app/notes/**/page.mdx`; pipeline in `next.config.ts` (Webpack + `@mdx-js/loader`); components in `mdx-components.tsx` and `components/notes/` | +| **Observability** | Sentry via `instrumentation*.ts` + `sentry.*.config.ts` (DSN optional); Vercel Analytics / Speed Insights in root layout | +| **Theme** | `app/components/theme-provider.tsx` + inline script in `app/layout.tsx` — default **light** when unset | + +--- + +## Commands + +```bash +npm install # also sets core.hooksPath → .githooks (co-author trailer) +npm run dev # next dev --webpack — required for MDX parity +npm run lint && npm run typecheck && npm run test && npm run build +``` + +--- + +## Important paths + +- [`lib/supabase.ts`](lib/supabase.ts) — `getSupabaseAnon()` lazy singleton +- [`lib/now-playing.ts`](lib/now-playing.ts) — types for Last.fm payload +- [`app/lib/substack.ts`](app/lib/substack.ts) — RSS fetch + parse (tested) +- [`next.config.ts`](next.config.ts) — MDX webpack rule, `withSentryConfig` + +--- + +## Conventions + +- **Dark mode** — support `dark:` for new UI. +- **MDX** — do not assume Turbopack; dev/build use `--webpack`. +- **Secrets** — never commit `.env.local` or tokens; see AGENTS.md and README. +- **User prompts** — when the user sends copy-paste blocks, keep them as single fenced blocks when echoing back. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d874bd7..5992c90 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,19 +1,34 @@ # Contributing to kaichen.dev -Thanks for your interest. This repo is a **personal site** ([kaichen.dev](https://kaichen.dev)), but fixes, docs improvements, and small enhancements are welcome. +Thank you for reading this. [kaichen.dev](https://kaichen.dev) is primarily a **personal site**, but **bug fixes**, **documentation improvements**, and **small, focused enhancements** are welcome. -Please read [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) and [SECURITY.md](SECURITY.md) first. +Before you contribute: + +1. Read [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md). +2. Read [SECURITY.md](SECURITY.md) — **never** file public issues with exploit details. +3. Skim the [README](README.md) for architecture, scripts, and environment variables. + +--- + +## Who this guide is for + +| Audience | Also read | +| --- | --- | +| Human contributors | This file + [README](README.md) | +| AI coding agents (Cursor, Claude Code, etc.) | [AGENTS.md](AGENTS.md) — **branch + PR workflow** and commit conventions are mandatory | + +Maintainers use **pull requests to `main`**; direct pushes to `main` are discouraged even when permissions allow. --- ## Prerequisites -- **Node.js 20** (aligned with CI and `@types/node`) -- **npm** (lockfile is `package-lock.json` — use `npm ci` in CI-like runs) +- **Node.js 20** (matches [CI](.github/workflows/ci.yml) and `@types/node` in `package.json`). +- **npm** — the repo uses `package-lock.json`. For reproducible installs use `npm ci` (as CI does). --- -## Local setup +## Clone and install ```bash git clone https://github.com/kaiiiichen/kaichen.dev.git @@ -22,15 +37,24 @@ npm install cp .env.example .env.local ``` -Edit `.env.local` with your own keys. You do **not** need every integration to run the app locally (see [README.md](README.md)); missing API keys may disable some sections or APIs. +Edit `.env.local` as needed. You **do not** need every integration to run the app locally: + +- Missing **Last.fm** / **GitHub** keys usually degrade specific widgets. +- **Supabase** placeholders are injected in CI for `next build`; locally, real or dummy public keys may be required for pages that call `createBrowserClient` at import time — see [README — CI placeholders](README.md#ci-placeholders). -**Do not commit** `.env.local`, tokens, or Sentry auth tokens. +**Never commit** `.env.local`, `.env.vercel.check`, tokens, or `SENTRY_AUTH_TOKEN`. --- -## Commands (same as CI) +## Git hooks (after `npm install`) -Before opening a PR, run: +`package.json` runs `postinstall` to set `git config core.hooksPath .githooks`. The [prepare-commit-msg](.githooks/prepare-commit-msg) hook appends a `Co-authored-by` trailer for Claude on each commit (see [AGENTS.md](AGENTS.md)). If your environment skips hooks (e.g. some UIs), add the trailer manually when required by project policy. + +--- + +## Mandatory checks (same as CI) + +Before opening or updating a PR, run: ```bash npm run lint @@ -39,43 +63,67 @@ npm run test npm run build ``` -Our [GitHub Actions workflow](.github/workflows/ci.yml) runs these steps on every push/PR to `main`. PRs should keep CI green. +[`.github/workflows/ci.yml`](.github/workflows/ci.yml) runs these steps on **push** and **pull_request** to `main`. Keep CI green. --- -## Project conventions +## Branch and pull request workflow -- **Stack:** Next.js 16 App Router, React 19, TypeScript, Tailwind CSS 4. -- **MDX:** Content uses Webpack-based `@mdx-js/loader` — `npm run dev` and `npm run build` use **`--webpack`** so MDX behavior matches production. Avoid relying on Turbopack-only behavior for `.mdx` files. -- **Style:** Match existing patterns (components, naming, Tailwind). Prefer small, focused changes over large refactors unless discussed. -- **Dark mode:** New UI should respect light/dark (`dark:` variants and theme patterns used elsewhere). -- **Secrets:** Never add real API keys, DSN secrets, or `SENTRY_AUTH_TOKEN` to the repo. Use `.env.example` for **names only** (empty or placeholder values). +1. Branch from up-to-date `main`: ---- + ```bash + git checkout main && git pull --ff-only + git checkout -b / + ``` + + Prefix examples: `fix/`, `feat/`, `chore/`, `docs/`, `refactor/`. -## Fork-specific content +2. Commit with clear messages (conventional style is appreciated: `type: summary`). -If you fork for your own site, you will typically replace copy, nav links, Last.fm user, Supabase project, GitHub usage, and Substack feeds — see the **Forking** section in [README.md](README.md). +3. Push and open a PR against **`main`**. GitHub will suggest the [pull request template](.github/pull_request_template.md). + +4. Describe **what** changed and **why**. Link issues with `Fixes #123` when applicable. + +5. Update **[README.md](README.md)** and/or **[`.env.example`](.env.example)** if you change user-visible behavior, new routes, or required/optional environment variables. --- -## Pull requests +## Project conventions + +### Stack and rendering + +- **Next.js 16** App Router, **React 19**, **TypeScript**, **Tailwind CSS 4**. +- **MDX** uses the **Webpack** loader configured in [`next.config.ts`](next.config.ts). Local `dev` and `build` scripts use **`--webpack`**. Do not rely on Turbopack-only behavior for `.mdx` files. + +### Style + +- Match existing component patterns, naming, and Tailwind usage. +- Prefer **small, reviewable diffs** over large refactors unless discussed in an issue first. +- **Dark mode:** new UI should work in light and dark (`dark:` variants and existing theme patterns). + +### Secrets and config + +- Add **variable names** (and short comments) to [`.env.example`](.env.example) when introducing new configuration — never real secrets. +- Do not add production keys, personal tokens, or Sentry auth tokens to the repository. + +### Security-sensitive areas + +If you touch **OAuth callbacks**, **API routes**, or **Supabase policies**, coordinate with the maintainer and document behavior in README or SECURITY as appropriate. + +--- -- Open a PR against **`main`**. -- Describe **what** changed and **why** (short is fine). -- Link related issues if any. -- If you change user-facing behavior or env vars, update **README.md** or **`.env.example`** when appropriate. +## Forking for your own site -Use the [pull request template](.github/pull_request_template.md) when you open a PR (GitHub will pre-fill it). +See the **Forking this project** section in [README.md](README.md) for a checklist of files to replace (Last.fm user, GitHub repos, Supabase, Substack, etc.). --- ## Issues -Use [issue templates](.github/ISSUE_TEMPLATE/) for bugs and feature ideas. For **security issues**, follow [SECURITY.md](SECURITY.md) instead of filing a public issue with exploit details. +Use [issue templates](.github/ISSUE_TEMPLATE/) for bugs and features. For **security issues**, follow [SECURITY.md](SECURITY.md) instead of posting exploit details in public issues. --- ## Questions -For small questions, a GitHub Discussion or Issue is fine. This is a side project; response time may vary. +For small questions, open a GitHub Issue or Discussion. This is a side project; response time may vary. diff --git a/README.md b/README.md index 9b19f91..a1781f5 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,59 @@ # kaichen.dev -Personal website of Kai Chen — [kaichen.dev](https://kaichen.dev). Built with **Next.js 16** (App Router), **React 19**, **Tailwind CSS 4**, and **TypeScript**. Deployed on **Vercel**. +Personal website of **Kai Chen** — production: [kaichen.dev](https://kaichen.dev). + +This repository is a **[Next.js 16](https://nextjs.org/)** application using the **App Router**, **React 19**, **TypeScript**, and **Tailwind CSS 4**. It is deployed on **[Vercel](https://vercel.com/)**. | Resource | URL | | --- | --- | -| Production | https://kaichen.dev | -| Repository | https://github.com/kaiiiichen/kaichen.dev | +| Production site | https://kaichen.dev | +| Source | https://github.com/kaiiiichen/kaichen.dev | + +--- + +## Table of contents + +1. [Overview](#overview) +2. [Requirements](#requirements) +3. [Quick start](#quick-start) +4. [npm scripts](#npm-scripts) +5. [Repository layout](#repository-layout) +6. [Technology stack](#technology-stack) +7. [Routes and features](#routes-and-features) +8. [API routes](#api-routes) +9. [Environment variables](#environment-variables) +10. [MDX lecture notes](#mdx-lecture-notes) +11. [External integrations](#external-integrations) +12. [Local development](#local-development) +13. [Testing](#testing) +14. [CI, Dependabot, and auto-merge](#ci-dependabot-and-auto-merge) +15. [Git hooks](#git-hooks) +16. [Deployment](#deployment) +17. [Documentation map](#documentation-map) +18. [Forking this project](#forking-this-project) +19. [License](#license) + +--- + +## Overview + +The site combines: + +- A **marketing-style home page** (identity, listening status, weather, projects, Substack headlines). +- **Dynamic data** from Last.fm, GitHub, Open-Meteo, and optional Supabase-backed gallery and listening history. +- **MDX-powered notes** under `/notes` with math (KaTeX), GitHub-flavored Markdown, and syntax-highlighted code blocks. +- **Optional observability** via Sentry (client, server, edge) and Vercel Analytics / Speed Insights. + +There is **no** `middleware.ts` in this repo; auth for admin flows uses Supabase OAuth and route handlers under `app/auth/`. + +--- + +## Requirements + +| Tool | Version / notes | +| --- | --- | +| **Node.js** | **20.x** (matches [CI](.github/workflows/ci.yml) and `@types/node`) | +| **npm** | 9+; lockfile is `package-lock.json` — use `npm ci` for reproducible installs | --- @@ -16,210 +64,336 @@ git clone https://github.com/kaiiiichen/kaichen.dev.git cd kaichen.dev npm install cp .env.example .env.local -# Edit .env.local — see [Environment variables](#environment-variables) +``` + +Edit `.env.local` following [Environment variables](#environment-variables). You do **not** need every key to run the app locally; missing keys typically degrade or hide features rather than crash the build (exceptions: pages that import Supabase at module scope use **placeholder** values in CI — see below). + +Start the dev server: + +```bash npm run dev ``` -Open [http://localhost:3000](http://localhost:3000). The dev server uses **`--webpack`** so MDX matches the production Webpack loader (see [Development notes](#development-notes)). +Open [http://localhost:3000](http://localhost:3000). + +**Important:** `dev` and `build` both pass **`--webpack`** to Next.js. MDX is wired through a **custom `webpack()` block** in [`next.config.ts`](next.config.ts) (`@mdx-js/loader` + remark/rehype plugins). Using Webpack for dev/build keeps MDX behavior aligned with production. Do not assume Turbopack-only behavior for `.mdx` files. --- ## npm scripts -| Script | Command | When to use | +| Script | Command | Purpose | | --- | --- | --- | -| `dev` | `next dev --webpack` | Local development | -| `build` | `next build --webpack` | Production bundle (run before `start` or in CI) | -| `start` | `next start` | Serve the last `build` output locally | -| `lint` | `eslint` | Lint the whole repo | -| `typecheck` | `tsc --noEmit` | Type-check without emitting JS | -| `test` | `vitest run` | Unit tests once (CI uses this) | -| `test:watch` | `vitest` | Unit tests in watch mode | +| `dev` | `next dev --webpack` | Local development with Webpack (MDX-compatible). | +| `build` | `next build --webpack` | Production bundle (also runs type checking as part of Next). | +| `start` | `next start` | Serve the last `build` output (run `build` first). | +| `lint` | `eslint` | ESLint across the repo ([`eslint.config.mjs`](eslint.config.mjs)). | +| `typecheck` | `tsc --noEmit` | TypeScript without emitting JS. | +| `test` | `vitest run` | Unit tests once (CI uses this). | +| `test:watch` | `vitest` | Vitest in watch mode. | +| `postinstall` | `git config core.hooksPath .githooks …` | Points Git at [`.githooks/`](.githooks) so the [prepare-commit-msg](.githooks/prepare-commit-msg) hook runs after `npm install` (see [Git hooks](#git-hooks)). | -Before pushing, run the same sequence as CI: `npm run lint` → `npm run typecheck` → `npm run test` → `npm run build`. +**Before opening a PR**, run the same sequence as CI: + +```bash +npm run lint && npm run typecheck && npm run test && npm run build +``` --- -## Tech stack +## Repository layout -- **Framework**: Next.js 16.2 (App Router) -- **UI**: React 19, Tailwind CSS 4, TypeScript -- **Content**: MDX with KaTeX + syntax highlighting (`@mdx-js/loader` in `next.config.ts`) -- **Fonts**: Nunito, Bitter, Geist Sans / Mono, JetBrains Mono (`@fontsource/*`, `geist`) -- **Theme**: Light default; dark mode via `ThemeProvider` + `localStorage` -- **Analytics**: Vercel Analytics + Speed Insights -- **Errors / performance**: Sentry (`@sentry/nextjs`) — optional; see [Environment variables](#environment-variables) +High-level map (not every file): + +```text +kaichen.dev/ +├── app/ # App Router +│ ├── layout.tsx # Root layout: fonts, theme script, Nav, Providers, Analytics +│ ├── page.tsx # Home +│ ├── globals.css +│ ├── global-error.tsx # Root error boundary + Sentry +│ ├── opengraph-image.tsx # OG image for / +│ ├── about/ # Bio / CV-style page + OG +│ ├── projects/ # Projects + GitHub heatmap + OG +│ ├── notes/ # Notes index, course pages, MDX note routes +│ ├── gallery/ # Public gallery + OG +│ ├── admin/ # Supabase-auth gallery admin; /admin/gallery → redirect /admin +│ ├── api/ # Route handlers (Last.fm, GitHub, weather, guestbook) +│ ├── auth/callback/ # Supabase OAuth exchange → redirect +│ ├── components/ # UI: nav, cards, theme, weather, listening, GitHub heatmap, … +│ ├── hooks/ # e.g. use-now-playing.ts +│ └── lib/ # og.tsx, substack RSS helpers +├── components/notes/ # MDX shortcodes: Theorem, Proof, Definition, Example, NoteBlock +├── lib/ # Shared server-oriented helpers + Vitest tests +│ ├── supabase.ts # Lazy anon Supabase client (getSupabaseAnon) +│ ├── now-playing.ts # Types for Last.fm payload +│ ├── weather-open-meteo.ts +│ └── *.test.ts +├── mdx-components.tsx # MDX element mapping + shortcode registration +├── next.config.ts # MDX webpack rule + withSentryConfig +├── instrumentation.ts # Sentry Node/Edge registration +├── instrumentation-client.ts # Sentry browser + router transition hooks +├── sentry.server.config.ts +├── sentry.edge.config.ts +├── vitest.config.ts +├── eslint.config.mjs +├── .githooks/ # Git hooks (co-author trailer) +├── .github/ +│ ├── workflows/ # ci.yml, auto-merge.yml +│ ├── dependabot.yml +│ ├── ISSUE_TEMPLATE/ +│ └── pull_request_template.md +├── .env.example +├── AGENTS.md # AI agent / automation git rules +├── CLAUDE.md # Short context for Claude Code (points here + AGENTS) +├── CONTRIBUTING.md +├── SECURITY.md +├── CODE_OF_CONDUCT.md +└── LICENSE # GPL-3.0 +``` --- -## Pages & routes +## Technology stack -| Route | Description | +| Layer | Choices | | --- | --- | -| `/` | Home — identity, Last.fm, Berkeley weather, projects + GitHub stars, Substack RSS | -| `/about` | Education, experience, volunteering, courses | -| `/projects` | Projects + GitHub contribution heatmap | -| `/notes` | Notes index; nested routes use MDX (e.g. CS61A) | -| `/gallery` | Photo grid + lightbox (Supabase `gallery_photos` + Storage) | -| `/admin` | Gallery upload (Supabase Auth); `/admin/gallery` redirects here | +| Framework | Next.js **16.2** (App Router), React **19**, TypeScript **5** | +| Styling | Tailwind CSS **4** (`@tailwindcss/postcss`), custom CSS in `app/globals.css` | +| Content | **MDX** via `@mdx-js/loader` + `remark-gfm`, `remark-math`, `rehype-katex`, `rehype-highlight` | +| Fonts | `@fontsource/*` (Nunito, Bitter, JetBrains Mono), `geist` (sans/mono CSS variables) | +| Auth / data | Supabase (`@supabase/supabase-js`, `@supabase/ssr`) for OAuth, gallery, optional listening DB writes | +| Monitoring | `@sentry/nextjs` (optional DSN), Vercel Analytics + Speed Insights | +| Testing | Vitest **3** | -The nav **Blog** link points to [Substack](https://substack.com/@kaiiiichen); there is no `/blog` route in this repo. +Pinned versions are in [`package.json`](package.json). --- -## API routes (`app/api/`) +## Routes and features -| Path | Role | +| Route | What it does | | --- | --- | -| `GET /api/lastfm/now-playing` | Last.fm → optional iTunes art → optional Supabase `listening_*` writes | -| `GET /api/github/contributions` | GitHub GraphQL contribution calendar | -| `GET /api/github/stars` | GitHub REST star counts for listed repos | -| `GET /api/weather` | Open-Meteo (Berkeley), no API key | -| `POST /api/guestbook` | Inserts into Supabase `guestbook` | +| `/` | Identity block, Last.fm line + card, Berkeley weather, project list with live GitHub stars, Substack RSS snippets | +| `/about` | Education, experience, courses, volunteering | +| `/projects` | Project cards + **GitHub contribution calendar** (client component, data from `/api/github/contributions`) | +| `/notes` | Index of courses / note collections | +| `/notes/...` | Nested segments; individual notes are often `page.mdx` (e.g. CS61A Scheme topics) | +| `/gallery` | Photo grid + lightbox; data from Supabase `gallery_photos` + Storage | +| `/admin` | Google OAuth via Supabase; **restricted to an allowlisted email** in client code — **you must enforce the same rules in Supabase RLS** for production safety | +| `/admin/gallery` | Redirects to `/admin` | + +The main nav **Blog** link points to external [Substack](https://substack.com/@kaiiiichen); there is no `/blog` route in-app. + +**Open Graph:** several routes ship `opengraph-image` route handlers for social previews. Set [`metadataBase`](https://nextjs.org/docs/app/api-reference/functions/generate-metadata#metadatabase) in `app/layout.tsx` if you see build warnings about resolving OG image URLs. + +--- + +## API routes + +All handlers live under `app/api/`. + +| Method & path | Behavior | Caching / notes | +| --- | --- | --- | +| `GET /api/lastfm/now-playing` | Last.fm `user.getrecenttracks`; optional iTunes artwork fallback; optional **service-role** writes to `listening_history` / `listening_stats` when a track is “now playing” | `Cache-Control: public, s-maxage=10, stale-while-revalidate=5`; uses `LASTFM_API_KEY`; in-memory `lastKnownTrack` fallback | +| `GET /api/github/contributions` | GraphQL contribution calendar + REST search for latest commit + REST repo metadata for star counts | `dynamic = force-dynamic`; `Cache-Control: no-store`; requires `GITHUB_TOKEN` | +| `GET /api/github/stars?repo=owner/name` | Returns `stargazers_count` and `archived` for a repo | `revalidate = 3600`; optional `GITHUB_TOKEN` for rate limits | +| `GET /api/weather` | Open-Meteo forecast for fixed Berkeley coordinates | `fetch` with `next.revalidate = 600` | +| `POST /api/guestbook` | JSON body `{ email, message }` → insert into Supabase `guestbook` via anon client | No auth; relies on **Supabase RLS** and sensible limits in the database | + +**Guestbook** is only referenced from API + docs; ensure any front-end or future form respects abuse concerns (rate limits, validation) at the edge or in Supabase policies. --- ## Environment variables -Copy [`.env.example`](.env.example) to `.env.local` and fill values. **Never commit** `.env.local` or tokens. +Copy [`.env.example`](.env.example) to `.env.local`. **Never commit** real secrets. -### Core (most site features) +### Always safe to document (names only) -| Variable | Purpose | +| Variable | Role | | --- | --- | -| `NEXT_PUBLIC_SITE_URL` | Site origin; used for Supabase OAuth `redirectTo` (set production URL on Vercel) | -| `NEXT_PUBLIC_SUPABASE_URL` | Supabase project URL | -| `NEXT_PUBLIC_SUPABASE_ANON_KEY` | Supabase anon key (browser + server routes using `getSupabaseAnon()`) | -| `SUPABASE_SERVICE_ROLE_KEY` | Server-only; Last.fm route writes to `listening_history` / `listening_stats` | -| `LASTFM_API_KEY` | Last.fm API | -| `GITHUB_TOKEN` | GitHub GraphQL + REST (contributions + stars) | +| `NEXT_PUBLIC_SITE_URL` | Canonical site origin; used for Supabase OAuth `redirectTo` (set production URL on Vercel). | +| `NEXT_PUBLIC_SUPABASE_URL` | Supabase project URL. | +| `NEXT_PUBLIC_SUPABASE_ANON_KEY` | Supabase anonymous key (browser + server routes using `getSupabaseAnon()`). | +| `SUPABASE_SERVICE_ROLE_KEY` | **Server-only.** Used by `/api/lastfm/now-playing` for DB writes and any server path that must bypass RLS — keep off the client bundle. | +| `LASTFM_API_KEY` | Last.fm API. If unset, the now-playing API returns a graceful “not playing” / DB fallback without calling Last.fm. | +| `GITHUB_TOKEN` | Fine-grained or classic PAT for GitHub API (contributions + stars). If missing, some features error or return empty data. | ### Sentry (optional) -| Variable | Purpose | +| Variable | Role | | --- | --- | -| `NEXT_PUBLIC_SENTRY_DSN` | Client SDK; also fallback for server/edge if `SENTRY_DSN` is unset | -| `SENTRY_DSN` | Optional separate DSN for Node/Edge | -| `SENTRY_AUTH_TOKEN` | **Build-only** — upload source maps (secret; store in Vercel) | -| `SENTRY_ORG` | Sentry organization **slug** | -| `SENTRY_PROJECT` | Sentry project **slug** | - -Use **no spaces** around `=` in `.env.local` (e.g. `SENTRY_ORG=my-org`). Runtime reporting works with DSN only; **readable stack traces** in Sentry need `SENTRY_AUTH_TOKEN` + org + project on the machine that runs `next build` (e.g. Vercel Production). - -[`app/global-error.tsx`](app/global-error.tsx) reports uncaught root React render errors to Sentry when DSN is set. - -### Compare with Vercel +| `NEXT_PUBLIC_SENTRY_DSN` / `SENTRY_DSN` | Error reporting; see [`instrumentation.ts`](instrumentation.ts) and Sentry configs. | +| `SENTRY_AUTH_TOKEN`, `SENTRY_ORG`, `SENTRY_PROJECT` | **Build-time** source map upload for readable stack traces in Sentry (configure on Vercel, not in git). | -To pull remote env into a **local scratch file** (optional): +### Vercel CLI (optional) ```bash vercel env pull .env.vercel.check ``` -That file is ignored by git (see `.gitignore`). Do not commit it. +That path is gitignored — do not commit it. + +### CI placeholders + +[`.github/workflows/ci.yml`](.github/workflows/ci.yml) sets dummy `NEXT_PUBLIC_SUPABASE_*` values so `next build` can prerender pages that call `createBrowserClient` at module scope (e.g. `/admin`). These are **not** real credentials. --- -## Development notes +## MDX lecture notes + +- Notes are **route segments** with `page.mdx` files (e.g. `app/notes/cs61a/scheme-quote/page.mdx`), not a separate `content/` directory. +- Shared layout: [`app/notes/layout.tsx`](app/notes/layout.tsx) (imports KaTeX CSS, width/padding). +- MDX components and typography are centralized in [`mdx-components.tsx`](mdx-components.tsx). +- Custom shortcodes (Theorem, Definition, Proof, Example, NoteBlock) live in [`components/notes/`](components/notes/) and are registered globally for MDX. +- Metadata in MDX files often uses `export const metadata = { title, description }` (Next.js metadata), not always YAML frontmatter. -- **MDX**: `next dev` / `next build` use **`--webpack`** so the custom `webpack()` block (MDX loader + `remark-gfm` / `remark-math` / `rehype-katex` / `rehype-highlight`) matches production. -- **Last.fm**: Responses use `Cache-Control: public, s-maxage=10` so the CDN can coalesce polls. The `useNowPlaying` hook polls every **10s** with slide transitions. -- **Supabase in CI**: [`.github/workflows/ci.yml`](.github/workflows/ci.yml) sets **placeholder** `NEXT_PUBLIC_SUPABASE_*` values so `next build` can prerender pages that call `createBrowserClient` at module scope. They are not real keys. -- **Guestbook**: [`lib/supabase.ts`](lib/supabase.ts) exposes `getSupabaseAnon()` — lazy init so builds without Supabase env do not fail at import time. +To add a new note: create a folder + `page.mdx` under `app/notes/`, match existing note headers (breadcrumb, title block) for visual consistency, and run `npm run build` to validate the MDX pipeline. --- ## External integrations -| Service | Purpose | +| Service | Use in this repo | | --- | --- | -| Last.fm API | Now playing / last played | -| iTunes Search API | Album art fallback when Last.fm has no art | -| GitHub GraphQL API | Contribution calendar on `/projects` | -| GitHub REST API | Star counts (`/api/github/stars`) | -| Open-Meteo | Berkeley weather (no key) | -| Supabase | `listening_history`, `listening_stats`, `gallery_photos`, `guestbook`, Storage | -| Substack RSS | Latest posts on the home page | +| **Last.fm** | Recent / now-playing track | +| **Apple iTunes Search API** | Album art fallback | +| **GitHub GraphQL** | Contribution calendar | +| **GitHub REST** | Repo stars, commit search | +| **Open-Meteo** | Weather (no API key) | +| **Supabase** | Auth, gallery tables + storage, guestbook insert, listening history (optional) | +| **Substack RSS** | Home page “latest posts” (`app/lib/substack.ts`) | + +--- + +## Local development + +- **Node 20**, **npm install** then **`npm run dev`**. +- If MDX fails to compile, confirm you did not remove the `--webpack` flag from scripts. +- **Supabase:** for full gallery/admin behavior, configure a project and env vars; for static pages only, you can omit keys where the build allows (see CI placeholders for build-time behavior). + +### Common issues + +| Symptom | Things to check | +| --- | --- | +| MDX differs between dev and prod | Ensure both use Webpack (`--webpack`). | +| GitHub widgets empty | `GITHUB_TOKEN` set and not expired; API rate limits. | +| OAuth redirect wrong host | `NEXT_PUBLIC_SITE_URL` and Supabase redirect URLs match Vercel domain. | +| Sentry noisy locally | DSN unset disables reporting; or lower sample rate in `instrumentation-client.ts`. | --- ## Testing -Unit tests live under `lib/*.test.ts` (Vitest): Substack RSS parsing, Open-Meteo mapping, Last.fm helpers. +Unit tests use **Vitest** and live next to helpers under `lib/*.test.ts` (RSS parsing, weather mapping, Last.fm helpers). ```bash -npm run test # once -npm run test:watch # watch mode +npm run test +npm run test:watch ``` +There are currently **no** Playwright/E2E tests in this repo; manual browser checks matter for layout and OAuth flows. + --- -## CI & automation +## CI, Dependabot, and auto-merge -- **CI** ([`.github/workflows/ci.yml`](.github/workflows/ci.yml)): on every **push** and **pull_request** to `main` — `npm ci` → `lint` → `typecheck` → `test` → `build` (Node 20, `ubuntu-latest`). -- **Dependabot** ([`.github/dependabot.yml`](.github/dependabot.yml)): weekly updates for **npm** and **GitHub Actions**. -- **Auto-merge** ([`.github/workflows/auto-merge.yml`](.github/workflows/auto-merge.yml)): optional squash merge after checks — maintainer convenience only. +### CI ([`.github/workflows/ci.yml`](.github/workflows/ci.yml)) + +Triggers on **push** and **pull_request** to `main`: + +`npm ci` → `lint` → `typecheck` → `test` → `build` on `ubuntu-latest`, **Node 20**, with npm cache. + +### Dependabot ([`.github/dependabot.yml`](.github/dependabot.yml)) + +- **npm** and **github-actions** ecosystems, **weekly** (Monday 09:00 America/Los_Angeles). +- **Grouped** updates (fonts, Sentry, Supabase, MDX-related, Vercel, types, catch-all minor/patch). +- **Ignored** semver-**major** bumps for core tooling (`next`, `react`, `eslint`, `typescript`, `tailwindcss`, …) so those upgrades stay manual. + +### Auto-merge ([`.github/workflows/auto-merge.yml`](.github/workflows/auto-merge.yml)) + +Runs only when the PR author is **`dependabot[bot]`**: + +1. Reads semver classification via `dependabot/fetch-metadata`. +2. For **patch** and **minor** updates: enables **`gh pr merge --auto --squash`** (respects branch protection when checks pass). +3. On **open** / **reopen**, posts an **idempotent** PR comment that includes the official **`@dependabot squash and merge`** line (documentation + redundancy; primary merge path is still GitHub auto-merge). + +`pull_request` types include **`synchronize`** so Dependabot force-pushes re-enable auto-merge. **Concurrency** is scoped per PR number to avoid overlapping runs. --- -## Deploy +## Git hooks -- **Vercel**: Connect the GitHub repo; pushes to `main` trigger production deploys. Configure env vars in the Vercel dashboard (especially `NEXT_PUBLIC_SITE_URL` for OAuth). -- **Manual CLI** (after `vercel link`): `vercel --prod`. +After `npm install`, `postinstall` runs: + +```bash +git config core.hooksPath .githooks +``` + +[`.githooks/prepare-commit-msg`](.githooks/prepare-commit-msg) appends: + +`Co-authored-by: Claude ` + +to non-merge commits via `git interpret-trailers` (idempotent). Automation that cannot run hooks should add the same trailer manually — see [`AGENTS.md`](AGENTS.md). --- -## Project structure +## Deployment -```text -kaichen.dev/ -├── app/ -│ ├── layout.tsx, page.tsx, globals.css -│ ├── global-error.tsx # Root error UI + Sentry.captureException -│ ├── about/, projects/, notes/, gallery/, admin/ -│ ├── api/ # lastfm, github, weather, guestbook -│ ├── auth/callback/ # Supabase OAuth -│ ├── components/ # nav, cards, theme, weather, listening, … -│ ├── hooks/use-now-playing.ts -│ ├── lib/substack.ts, og.tsx -│ └── notes/ # MDX course content -├── components/notes/ # MDX shortcodes (Theorem, Proof, …) -├── lib/ -│ ├── supabase.ts # getSupabaseAnon() — lazy singleton -│ ├── now-playing.ts # Types for Last.fm API -│ ├── weather-open-meteo.ts # Open-Meteo → API payload (tested) -│ ├── lastfm-now-playing-helpers.ts -│ └── *.test.ts # Vitest -├── mdx-components.tsx -├── instrumentation.ts # Sentry server/edge registration -├── instrumentation-client.ts # Sentry browser + router transitions -├── sentry.server.config.ts, sentry.edge.config.ts -├── next.config.ts # MDX webpack + withSentryConfig -├── vitest.config.ts -├── .github/workflows/ci.yml, auto-merge.yml -├── .github/dependabot.yml -├── .github/ISSUE_TEMPLATE/ # Bug / feature issue forms -├── .github/pull_request_template.md -├── .env.example -├── CODE_OF_CONDUCT.md, CONTRIBUTING.md -└── SECURITY.md +1. Connect the GitHub repository to **Vercel**. +2. Set environment variables in the Vercel project (production + preview as needed), especially `NEXT_PUBLIC_SITE_URL` and Supabase URLs for OAuth. +3. Pushes to `main` typically deploy production; preview deployments use PR branches. + +Manual CLI (after `vercel link`): + +```bash +vercel --prod ``` --- -## Community +## Documentation map + +| File | Audience | Contents | +| --- | --- | --- | +| **README.md** (this file) | Everyone | Setup, architecture, APIs, env, CI | +| [`CONTRIBUTING.md`](CONTRIBUTING.md) | Human contributors | How to PR, conventions, CI parity | +| [`AGENTS.md`](AGENTS.md) | AI agents / automation | Branch + PR only, co-author trailer, secrets | +| [`CLAUDE.md`](CLAUDE.md) | Claude Code | Short pointer + stack summary | +| [`SECURITY.md`](SECURITY.md) | Security researchers | How to report issues responsibly | +| [`CODE_OF_CONDUCT.md`](CODE_OF_CONDUCT.md) | Contributors | Contributor Covenant | +| [`.env.example`](.env.example) | Developers | Variable names and brief comments | +| [`.github/pull_request_template.md`](.github/pull_request_template.md) | PR authors | Checklist | + +Cursor-specific rules live under [`.cursor/rules/`](.cursor/rules/) (IDE-only, not required reading for all contributors). + +--- + +## Forking this project + +Replace at minimum: + +| Area | Where to look | +| --- | --- | +| Copy, links, projects list | `app/page.tsx`, `app/projects/page.tsx`, `app/about/page.tsx` | +| Last.fm username | `app/api/lastfm/now-playing/route.ts` | +| GitHub login / repos | `app/api/github/contributions/route.ts`, `app/components/project-stars.tsx`, home page `PROJECTS` | +| Supabase project + admin allowlist | `lib/supabase.ts`, `app/admin/page.tsx`, Supabase dashboard (RLS, Storage) | +| Substack feeds | `app/lib/substack.ts` | +| Weather location | `app/api/weather/route.ts`, weather UI components | +| Theme / fonts | `app/layout.tsx`, `app/globals.css`, `app/components/theme-provider.tsx` | -- [Contributing](CONTRIBUTING.md) — local setup, CI commands, conventions -- [Code of Conduct](CODE_OF_CONDUCT.md) +Keep **LICENSE** compliance if you redistribute (GPL-3.0). --- -## Forking this repo +## License -Replace copy, nav links, Last.fm username in [`app/api/lastfm/now-playing/route.ts`](app/api/lastfm/now-playing/route.ts), Supabase project + allowed emails in [`app/admin/page.tsx`](app/admin/page.tsx), GitHub usage, and Substack feeds in [`app/lib/substack.ts`](app/lib/substack.ts). +This project is licensed under the **GNU General Public License v3.0** — see [`LICENSE`](LICENSE). --- ## Security -See [`SECURITY.md`](SECURITY.md) for how to report issues responsibly. +Please read [`SECURITY.md`](SECURITY.md) before reporting vulnerabilities. diff --git a/SECURITY.md b/SECURITY.md index f652956..330d4b1 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,10 +1,81 @@ -# Security +# Security policy -## Secrets +This document describes how we handle **security-sensitive information** for the [kaichen.dev](https://kaichen.dev) site and this repository. -- Do not commit `.env.local` or tokens in source. -- Rotate any credential that was ever committed or exposed. +--- -## Reporting +## Supported scope -If you find a security issue affecting this site or its APIs, please contact the owner via the email on the [About](https://kaichen.dev/about) page (or repository owner on GitHub) instead of opening a public issue for sensitive details. +We care about vulnerabilities that affect: + +- **Production** behavior at `kaichen.dev` (including Vercel-hosted Next.js routes and static assets). +- **Repository automation** (GitHub Actions workflows, Dependabot-related automation) when they could lead to secret exfiltration or unauthorized repository changes. +- **User data** processed through **Supabase** (auth sessions, gallery metadata, guestbook rows, listening history) when issues stem from **this application’s** code or documented deployment practices. + +We do **not** provide a formal bug bounty program. Reports are handled **best-effort**. + +--- + +## How to report a vulnerability + +**Do not** open a public GitHub issue with exploit details, payloads, or step-by-step instructions that put other users at risk. + +Instead: + +1. **Contact the maintainer privately**, using one of: + - The email listed on the [About](https://kaichen.dev/about) page, or + - GitHub **private security advisories** for this repository (if enabled), or + - A direct message to [@kaiiiichen](https://github.com/kaiiiichen) for **non-sensitive** coordination only (not for long exploit write-ups). + +2. Include **what component** is affected (e.g. route path, API handler name), **impact**, and **minimal reproduction** steps where safe. + +3. Allow reasonable time for triage before public disclosure. Coordinated disclosure is appreciated. + +--- + +## What we will do + +- Acknowledge receipt when possible. +- Investigate and patch or mitigate **critical** issues in production configuration or code when they fall within project control. +- Credit reporters in release notes or advisories if they want attribution (optional). + +--- + +## Secrets hygiene (for contributors) + +- **Never commit** `.env.local`, API tokens, `SENTRY_AUTH_TOKEN`, Supabase **service role** keys, or GitHub personal access tokens. +- If a secret was ever committed — even briefly — **rotate it** in the provider (Supabase, GitHub, Last.fm, Vercel, Sentry) and **purge** from git history if the repo was public. +- Files such as `.env.vercel.check` from `vercel env pull` are **gitignored**; treat them like secrets on disk. + +See also [README.md — Environment variables](README.md#environment-variables) and [CONTRIBUTING.md](CONTRIBUTING.md). + +--- + +## Dependency updates + +Automated **Dependabot** PRs and the [auto-merge workflow](.github/workflows/auto-merge.yml) only affect **semver patch/minor** dependency updates; **major** upgrades for core frameworks are intentionally manual (see [`.github/dependabot.yml`](.github/dependabot.yml)). Report **supply-chain** concerns through the same private channels if you believe automation is unsafe. + +--- + +## Out of scope (examples) + +Reports may be **declined** or redirected when they concern: + +- Third-party services’ policies (Last.fm, GitHub, Vercel, Supabase product bugs) — use their official channels. +- **Social engineering** or account takeover of maintainer accounts outside this codebase. +- **Theoretical** issues without a plausible attack path against deployed configuration. +- Content **spam** on optional features (e.g. guestbook) unless tied to a clear application defect; operational mitigations (rate limits, RLS) may be tracked separately. + +--- + +## Safe harbor + +If you make a **good-faith** effort to avoid privacy violations, data destruction, or service disruption — and you report issues responsibly — we will not pursue legal action against you. Do not access data that is not yours, and do not perform destructive tests on production. + +--- + +## Related documents + +- [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) — community behavior. +- [CONTRIBUTING.md](CONTRIBUTING.md) — how to submit code changes. +- [README.md](README.md) — architecture and API overview. From 629404f07ff8c7f17b120ddefe3e05c38c4e0528 Mon Sep 17 00:00:00 2001 From: Kai Chen Date: Fri, 17 Apr 2026 05:46:30 -0700 Subject: [PATCH 2/2] docs(readme): add Shields.io badge strip Centered three-row badge block (CI, Vitest, ESLint / stack / Vercel, license, Dependabot, site) using shields.io with repo-appropriate links. Made-with: Cursor Co-authored-by: Claude --- README.md | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/README.md b/README.md index a1781f5..d77f57a 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,49 @@ # kaichen.dev +

+ + + CI + + + Vitest + + + ESLint + +
+ + + Next.js 16 + + + React 19 + + + TypeScript 5 + + + Tailwind CSS 4 + + + Node.js 20 + +
+ + + Deployed on Vercel + + + License GPL-3.0 + + + Dependabot + + + kaichen.dev + +

+ Personal website of **Kai Chen** — production: [kaichen.dev](https://kaichen.dev). This repository is a **[Next.js 16](https://nextjs.org/)** application using the **App Router**, **React 19**, **TypeScript**, and **Tailwind CSS 4**. It is deployed on **[Vercel](https://vercel.com/)**.