From 2ff7ea80c5658318e120544f7f8343d70bd139aa Mon Sep 17 00:00:00 2001 From: Jack Hall Date: Mon, 30 Mar 2026 09:30:51 -0500 Subject: [PATCH] docs: restructure agentic context for progressive disclosure - Rewrite AGENTS.md: Quick Start at top with worktree requirement, add git workflow and safety boundaries sections, add testing patterns, add references to deeper docs (~150 lines, down from 248) - Move planning docs to doc/: audit.md, authentication.md, improvements.md, seo.md (root now only README + CLAUDE.md) - Create .claude/README.md documenting hooks, skills, and settings - Expand backend/README.md with local setup, collections, migrations, and deployment steps - Add "For AI Agents" pointer in README.md Co-Authored-By: Claude Opus 4.6 (1M context) --- .claude/README.md | 34 +++ AGENTS.md | 289 +++++++-------------- README.md | 4 + backend/README.md | 78 +++++- AUDIT.md => doc/audit.md | 0 AUTHENTICATION.md => doc/authentication.md | 0 IMPROVEMENTS.md => doc/improvements.md | 0 SEO.md => doc/seo.md | 0 8 files changed, 205 insertions(+), 200 deletions(-) create mode 100644 .claude/README.md rename AUDIT.md => doc/audit.md (100%) rename AUTHENTICATION.md => doc/authentication.md (100%) rename IMPROVEMENTS.md => doc/improvements.md (100%) rename SEO.md => doc/seo.md (100%) diff --git a/.claude/README.md b/.claude/README.md new file mode 100644 index 0000000..59fdddf --- /dev/null +++ b/.claude/README.md @@ -0,0 +1,34 @@ +# .claude/ Directory + +Configuration for Claude Code agents working in this repository. + +## Settings + +| File | Committed | Purpose | +|------|-----------|---------| +| `settings.json` | Yes | Deny rules (yarn/pnpm/bun), hook definitions | +| `settings.local.json` | No (.gitignored) | Allow rules for MCP tools, Playwright, gcloud, podman | + +## Hooks + +Hooks run before tool execution. Defined in `settings.json`, scripts in `hooks/`. + +| Script | Triggers On | Purpose | +|--------|-------------|---------| +| `guard-paths.sh` | Edit, Write | Hard-blocks edits to `dist/`, `node_modules/`, `.astro/`. Soft-asks before editing `catalyst/` components. | +| `require-worktree.sh` | Edit, Write | Blocks edits outside a git worktree. Exempts `.claude/` directory for config changes. | +| `prefer-github-mcp.sh` | Bash | Blocks `gh pr`, `gh issue`, `gh search` commands and redirects to GitHub MCP tools with a mapping table. | + +## Skills + +| Skill | Invocation | Purpose | +|-------|-----------|---------| +| `work-on-issue` | `/work-on-issue ` | Full issue lifecycle: triage → worktree → plan mode → implement → verify → PR | + +Skill definitions live in `skills//SKILL.md`. + +## Worktrees + +All implementation work happens in `.claude/worktrees/` (gitignored). `EnterWorktree` creates them automatically. One worktree per unit of work. + +Example path: `.claude/worktrees/issue-42-add-dark-mode/` diff --git a/AGENTS.md b/AGENTS.md index 6156c03..a52b42c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,251 +1,152 @@ # AGENTS.md -Instructions for AI coding agents working in this repository. +Instructions for AI coding agents. `CLAUDE.md` is a symlink to this file. -## Project Overview +## Quick Start -College Station Computer Science (CSCS) community website - a static site with blog, events, and authentication. Built with Astro 5 + React 19, Tailwind CSS v4, PocketBase backend, and Catalyst UI Kit. +- **Project**: CSCS community website — Astro 5 + React 19, Tailwind CSS v4, PocketBase backend, Catalyst UI Kit +- **Worktree required**: All implementation work must happen in a git worktree. Use `EnterWorktree` before editing project files. A hook enforces this. +- **GitHub MCP tools**: Use MCP tools (not `gh` CLI) for PRs, issues, etc. A hook redirects `gh` commands. +- **Package manager**: npm only (yarn/pnpm/bun are denied by settings) -## Build Commands +## Commands ```bash -npm run dev # Start dev server at localhost:4321 -npm run build # Build production site to ./dist/ -npm run preview # Preview production build - -npm run lint # Run ESLint -npm run format # Run Prettier (writes changes) - -npm run test # Run Vitest in watch mode -npm run test:run # Run tests once (CI) -npm run test:coverage # Run tests with v8 coverage +npm run dev # Dev server at localhost:4321 +npm run build # Production build to ./dist/ +npm run preview # Preview production build +npm run lint # ESLint +npm run format # Prettier (writes changes) +npm run test:run # Vitest (single run) +npm run test:coverage # Vitest with v8 coverage ``` -## Code Style - -### Formatting - -Prettier handles formatting via `npm run format`. Configuration: +## Code Conventions -- Uses `prettier-plugin-astro` for `.astro` files -- Uses `prettier-plugin-tailwindcss` for class sorting -- Default Prettier settings (no explicit config for quotes, semicolons, etc.) +### Formatting & Linting -### Linting - -ESLint with TypeScript, Astro, and jsx-a11y plugins. Key rules: - -- `@typescript-eslint/no-explicit-any` is disabled -- Astro recommended + jsx-a11y recommended rules enabled -- Ignored directories: `.astro/`, `dist/`, `node_modules/`, `backend/` +- **Prettier** with `prettier-plugin-astro` and `prettier-plugin-tailwindcss` +- **ESLint** with TypeScript, Astro, and jsx-a11y plugins. `@typescript-eslint/no-explicit-any` is disabled. ### TypeScript -Strict mode enabled via `astro/tsconfigs/strict`. React JSX configured. - -- Use explicit types for function parameters and return values -- Interfaces preferred for object shapes (see `EventData`, `AuthUser`) -- Use `type` for unions and simple type aliases - -### Imports +- Strict mode via `astro/tsconfigs/strict` +- Interfaces for object shapes, `type` for unions/aliases +- Explicit types for function parameters and return values -1. External packages (`react`, `astro:content`, `@headlessui/react`) -2. Internal lib (`../lib/pocketbase`) -3. Components (`./catalyst/button`, `../components/Header`) - -### Naming Conventions +### Naming - **Files**: kebab-case for pages (`create-event.astro`), PascalCase for components (`EventForm.tsx`) -- **Components**: PascalCase (`Header`, `Newsletter`, `LoginForm`) -- **Functions**: camelCase (`handleSubmit`, `getCurrentUser`, `formatDate`) -- **Interfaces**: PascalCase with descriptive names (`AuthUser`, `EventData`, `RegisterData`) -- **Constants**: camelCase for module-level (`navigation`, `styles`) - -### React Components - -- Use function components with hooks (no class components) -- Export default for page-level components (`export default function Header()`) -- Named exports for utility components (`export const Button`, `export function TouchTarget`) -- Use `"use client"` directive for components that need client-side features - -### Dark Mode +- **Components**: PascalCase. Default export for page-level, named exports for utilities. +- **Functions**: camelCase +- **Interfaces**: PascalCase with descriptive names (`AuthUser`, `EventData`, `BookData`) -All components must support dark mode: +### React -- Add `dark:` variants to Tailwind classes -- Use zinc color palette for neutrals (works in both modes) +- Function components with hooks only +- Hydration directives: `client:only="react"` for auth-dependent, `client:load` for interactive, `client:visible` for lazy, none for static +- All components must support dark mode (`dark:` variants, `zinc` neutrals + `indigo` accent) -### Error Handling +### Error Handling & Async -Use try/catch with typed error handling. Display user-friendly messages in UI: - -```typescript -try { - await someAsyncOperation(); -} catch (err) { - setError(err instanceof Error ? err.message : "Operation failed"); -} -``` - -### Async Operations - -Use async/await with loading state. Disable submit buttons during operations: - -```typescript -const [isLoading, setIsLoading] = useState(false); -async function handleSubmit(e: React.FormEvent) { - e.preventDefault(); - setIsLoading(true); - try { - await apiCall(); - } finally { - setIsLoading(false); - } -} -``` +- try/catch with `err instanceof Error ? err.message : "fallback"` pattern +- Loading state with disabled submit buttons during async operations ## Architecture -### File Structure - ``` src/ - components/ # React components - catalyst/ # UI kit (don't modify unless necessary) - content/blog/ # Markdown blog posts - layouts/ # Astro layouts - lib/ # Shared utilities (pocketbase.ts) - pages/ # File-based routing (app/, blog/) - stores/ # React state (authStore.ts) - styles/ # Global CSS - test/ # Test setup, mocks, and factories + components/ # React components + catalyst/ # UI kit — don't modify, use as-is + content/blog/ # Markdown blog posts (Zod schema in content/config.ts) + layouts/ # Astro layouts (Layout.astro accepts title, description) + lib/ # PocketBase client and helpers (pocketbase.ts) + pages/ # File-based routing + app/ # Authenticated pages (dashboard, events, books) + stores/ # React state (authStore.ts → useAuth() hook) + test/ # Mocks and factories ``` ### Routing -File-based routing in `src/pages/`: - -- `/` - Home page with hero, newsletter, footer -- `/blog` - Blog listing (sorted by date, newest first) -- `/blog/[slug]` - Individual posts (dynamic routes from content collection) - -### Astro Pages - -- Use `.astro` files for pages -- Import `Layout` from `../layouts/Layout.astro` — accepts `title` and `description` props for SEO -- Import `Header` and `Footer` for consistent page structure -- Frontmatter goes between `---` fences at top +- `/` — Home | `/blog`, `/blog/[slug]` — Blog | `/book-club` — Book club | `/schedule` — Events +- `/app/dashboard`, `/app/events`, `/app/books` — Authenticated app pages +- `/login`, `/register`, `/verify-email`, `/account` — Auth flows -### React Hydration +### PocketBase Integration -Use the minimal hydration directive needed. Prefer static rendering for performance. - -- `client:only="react"` - Auth-dependent components (Header) -- `client:load` - Interactive on page load (use sparingly) -- `client:visible` - Lazy load when scrolled into view -- No directive - Static, no JavaScript shipped +- Client: `src/lib/pocketbase.ts` exports `pb` instance + typed helper functions +- Collections: `users` (auth), `events`, `rsvps`, `books` +- Auth: `useAuth()` hook for React components, `user?.role === "moderator"` for admin checks +- RBAC: Public list/view, moderator-only create/update/delete (enforced via API rules) ### Catalyst UI Kit -Located in `src/components/catalyst/`. Use these for consistency: - -- `Button` - All buttons (supports `color`, `outline`, `plain` variants) -- `Input`, `Textarea`, `Select` - Form inputs -- `Field`, `Label`, `ErrorMessage` - Form field wrappers -- `Heading`, `Text` - Typography -- `Alert`, `Dialog` - Modals and notifications - -### Styling - -Tailwind CSS v4 with utilities-first approach: +Located in `src/components/catalyst/`. Use these for UI consistency: +- `Button`, `Input`, `Textarea`, `Select` — Form elements +- `Field`, `Label`, `ErrorMessage` — Form field wrappers +- `Heading`, `Text` — Typography +- `Dialog`, `Alert` — Modals -- Color palette: `zinc` (neutrals) + `indigo` (accent) -- Container: `max-w-7xl` for layout, `max-w-3xl` for content -- Spacing: `px-6 lg:px-8` (horizontal), `py-24 sm:py-32` (vertical) -- Responsive breakpoints: `sm:` (640px), `lg:` (1024px) -- Typography: Use Catalyst ``, `` components; blog content uses Tailwind Typography (`prose` classes with dark mode) +A hook soft-asks before editing catalyst/ files. Prefer wrapping over modifying. -### Backend Integration - -PocketBase client in `src/lib/pocketbase.ts`: +## Key Patterns -- Use `pb.collection('name')` for CRUD operations -- Auth functions: `login()`, `logout()`, `register()`, `getCurrentUser()` -- Use `useAuth()` hook in React components for auth state +### Testing -## Content Collections +Vitest + React Testing Library. Reference implementations: +- **Component tests**: `src/components/EventForm.test.tsx`, `src/components/BookClubPage.test.tsx` +- **Library tests**: `src/lib/pocketbase.test.ts` +- **Mock factories**: `src/test/mocks/factories.ts` (createMockEvent, createMockBook, etc.) +- **PocketBase mock**: `src/test/mocks/pocketbase.ts` (createMockPocketBaseModule) -Blog posts are Markdown files in `src/content/blog/` with Zod schema (defined in `src/content/config.ts`): +Pattern: `vi.mock("../lib/pocketbase", ...)` with `vi.hoisted()` for variables used in mock factories. -```typescript -{ - title: string - description: string - pubDate: Date - author: string - image?: string - tags: string[] (default: []) -} -``` +### PocketBase Migrations -Required frontmatter example: +Migration files in `backend/pb_migrations/` with timestamp prefixes: +- New collection: see `1768138900_created_rsvps.js` pattern +- Update collection: see `1768098549_updated_events_recurring.js` pattern +- Each migration has up/down functions for reversibility -```markdown ---- -title: "Post Title" -description: "Brief description" -pubDate: 2025-01-10 -author: "Author Name" -tags: ["tag1", "tag2"] ---- -``` - -Posts use `getStaticPaths()` for build-time generation. - -## Key Patterns +### Blog Posts -### Adding a New Page +Markdown in `src/content/blog/` with required frontmatter: `title`, `description`, `pubDate`, `author`, `tags`. -1. Create `src/pages/your-page.astro` -2. Import and use `Layout` wrapper with title/description -3. Import `Header` and `Footer` for consistency -4. Use Catalyst components for UI elements +### Date Handling -### Adding a Blog Post +Store dates as text (`YYYY-MM-DD`). Parse with `new Date(year, month - 1, day)` — never `new Date("YYYY-MM-DD")` which parses as UTC and shows wrong day in US timezones. -1. Create `src/content/blog/your-slug.md` -2. Add frontmatter with required fields (see schema above) -3. Write content in Markdown -4. Build will generate `/blog/your-slug` automatically +## Git Workflow -### Creating Interactive Components +- **Branches**: one branch per worktree, descriptive names (`fix-event-date-bug`, `feat-book-club-page`) +- **Commits**: conventional commits — `feat:`, `fix:`, `chore:`, `docs:`. Brief subject line, body for "why". +- **PRs**: create via GitHub MCP tools. Title under 70 chars. Body with `## Summary` and `## Test plan`. +- **CI**: PRs run lint, format check, tests, build via GitHub Actions. All must pass before merge. -1. Use `.tsx` files for React components with state/interactivity -2. Import from `src/components/catalyst/` for UI primitives -3. Add to Astro pages with appropriate `client:*` directive -4. Keep components small and focused +## Boundaries -## Important Notes +**Free** (no confirmation needed): read files, run tests/lint/build, git status/log/diff, search code -- **Testing** — Run `npm run test:run` to verify changes; see test files in `src/` for patterns -- **No backend** - Forms don't submit anywhere yet -- **Content is Markdown** - Not a CMS or database -- **Static output** - All routes generated at build time -- Uses **npm** (not yarn/pnpm) — enforced by deny rules in `.claude/settings.json` -- **TypeScript** enabled with strict mode -- PocketBase backend runs separately (see `backend/README.md`) +**Ask first** (confirm with user): git push, create/merge PRs, modify CI config, delete files/branches, run deployment commands -## Worktree Workflow (Required) +**Forbidden**: store secrets in code, skip pre-commit hooks (`--no-verify`), force-push to main, modify `node_modules/` or `dist/` -All new units of work (features, bugfixes, refactors) **must** be performed in a discrete git worktree — never directly in the main working tree. +## References -### Canonical Directory +- **Architecture decisions**: `doc/adr/` (11 ADRs covering hosting, runtime, CI/CD, etc.) +- **Backend setup & deployment**: `backend/README.md` +- **Auth system details**: `doc/authentication.md` +- **Project audit & known issues**: `doc/audit.md` +- **Feature roadmap**: `doc/improvements.md` +- **CI pipeline**: `.github/workflows/ci.yml` (lint, format, test, build) +- **Hook & skill docs**: `.claude/README.md` -Worktrees live in `.claude/worktrees/` (gitignored). `EnterWorktree` creates them here automatically. Example path: `.claude/worktrees/issue-42-add-dark-mode/`. +## Worktree Workflow -### Rules +All work happens in `.claude/worktrees/` (gitignored). `EnterWorktree` creates them automatically. -- Use `EnterWorktree` before beginning any implementation work -- A `PreToolUse` hook enforces this: edits to project files outside a worktree are **denied** -- **Exception**: `.claude/` configuration changes (hooks, settings) are allowed in the main tree -- One worktree per unit of work — keeps changes isolated and reviewable +**Rules**: +- Use `EnterWorktree` before any implementation work +- One worktree per unit of work (feature, bugfix, refactor) +- `.claude/` config changes are exempt from the worktree requirement diff --git a/README.md b/README.md index 126f335..ed274db 100644 --- a/README.md +++ b/README.md @@ -175,6 +175,10 @@ The site uses Tailwind CSS v4 with a consistent design system: All Catalyst components include dark mode support by default. +## For AI Agents + +See [CLAUDE.md](./CLAUDE.md) for coding conventions and workflow requirements. + ## Learn More - [Astro Documentation](https://docs.astro.build) diff --git a/backend/README.md b/backend/README.md index 9c8d1e8..76170f5 100644 --- a/backend/README.md +++ b/backend/README.md @@ -1,11 +1,77 @@ # CSCS.dev Backend -The CSCS.dev backend is hosted on a single [Google Cloud GCE e2-micro](https://gcloud-compute.com/e2-micro.html) instance which costs roughly $8 (including block device) per month. It is automatically deployed during CI/CD by a Github Action located in this project's `.github` directory. +PocketBase backend for the CSCS community website. -The cloud-init configuration in this directory ensures that the instance is bootstrapped with the Podman runtime which provisions the container. Volume mounts in this container's configuration ensure the container data (pocketbase users and migrations) are persisted a block storage devices that undergoes scheduled backups. +## Local Development -## Strategy +Start PocketBase alongside the frontend with Podman Compose from the project root: -1. Podman runs the `pocketbase` container from arguments supplied as a [Podman Quadlet](https://docs.podman.io/en/latest/markdown/podman-quadlet.1.html) (.container unit file). -2. All application data is persisted to the block device designated for this purpose. -3. Podman will automatically pull and replace the running container with any new container images published to the container registry using the specified tag. +```bash +podman-compose up --build -d +``` + +- PocketBase: `http://localhost:8090` +- Admin dashboard: `http://localhost:8090/_/` +- Frontend: `http://localhost:4321` + +## Collections + +| Collection | Purpose | Public Read | Write Access | +|-----------|---------|-------------|--------------| +| `users` | Authentication (email/password) | No | Self-registration, self-update | +| `events` | Event listings (meetups, book club) | Yes | Moderators only | +| `rsvps` | Attendance tracking (user + event relation) | Authenticated | Self only | +| `books` | Book club reading list | Yes | Moderators only | + +Roles: `user` (default), `moderator` (set via admin dashboard). See `doc/authentication.md` for details. + +## Migrations + +Migration files live in `backend/pb_migrations/` with timestamp prefixes. PocketBase applies them automatically on startup. + +**Creating a new collection**: See `1768138900_created_rsvps.js` for the pattern (Collection constructor with fields, indexes, and RBAC rules). + +**Updating a collection**: See `1768098549_updated_events_recurring.js` for the pattern (findCollectionByNameOrId + field mutations). + +Each migration has an up function and a down function for reversibility. + +## Deployment + +### Infrastructure + +Hosted on a GCE e2-micro instance (~$8/month) running Podman via Quadlet. Application data persists on a separate block device with scheduled backups. + +### Deploy Steps + +1. **Build and push** the container image (includes migrations): + ```bash + cd backend + make build-push + ``` + +2. **SSH into the VM** and restart: + ```bash + ssh jackvincenthall@34.67.31.86 + gcloud auth print-access-token | sudo podman login -u oauth2accesstoken --password-stdin https://us-central1-docker.pkg.dev + sudo podman pull us-central1-docker.pkg.dev/cscsdotdev/website/backend:latest + sudo systemctl restart pocketbase.service + ``` + +3. **Verify**: `sudo systemctl status pocketbase.service` should show `active (running)`. + +### VM Management + +```bash +cd backend +./deploy.sh up # Provision VM (idempotent) +./deploy.sh down # Tear down VM (preserves disk and IP) +``` + +## Architecture + +- **Containerfile**: Multi-arch Alpine image with PocketBase binary + migrations baked in +- **startup.sh**: VM cloud-init script — installs Podman, mounts persistent disk, creates Quadlet unit +- **Makefile**: Build/push shortcuts for the container image +- **deploy.sh**: GCE VM lifecycle management (create/delete, preserves data) + +PocketBase serves HTTPS directly via Let's Encrypt at `https://api.cscs.dev`. diff --git a/AUDIT.md b/doc/audit.md similarity index 100% rename from AUDIT.md rename to doc/audit.md diff --git a/AUTHENTICATION.md b/doc/authentication.md similarity index 100% rename from AUTHENTICATION.md rename to doc/authentication.md diff --git a/IMPROVEMENTS.md b/doc/improvements.md similarity index 100% rename from IMPROVEMENTS.md rename to doc/improvements.md diff --git a/SEO.md b/doc/seo.md similarity index 100% rename from SEO.md rename to doc/seo.md