diff --git a/.claude/commands/testing.md b/.claude/commands/testing.md index 2568a82..13b0079 100644 --- a/.claude/commands/testing.md +++ b/.claude/commands/testing.md @@ -1,6 +1,6 @@ --- name: testing -description: "Run tests, write new tests, debug test failures." +description: "Run tests, write new tests, debug test failures. Use when working with Vitest unit tests or Playwright E2E." --- ## Testing @@ -8,25 +8,44 @@ description: "Run tests, write new tests, debug test failures." ### Commands ```bash -npm run test # Vitest unit tests -npm run test:watch # Watch mode -npm run test:e2e # Playwright E2E (start dev server first) -npm test -- tests/unit/pds.test.ts # Single file +npm run test # All unit tests (91 tests) +npm run test:watch # Watch mode +npm test -- tests/unit/pds.test.ts # Single file +npm run test:e2e # Playwright E2E (needs running server) ``` ### Test Structure ``` tests/ -├── unit/ # Vitest (node env) -│ ├── auth.test.ts, drafts.test.ts, pds.test.ts, utils.test.ts, rss.test.ts -└── e2e/ - └── blog.spec.ts # Homepage, posts, RSS, write auth, 404, mobile +├── unit/ # Vitest (node env) — 91 tests +│ ├── auth.test.ts # ATAuth login, ticket verify, isOwner +│ ├── drafts.test.ts # SQLite CRUD, rkey generation, upsert, blobs +│ ├── pds.test.ts # PDS fetching, caching, blobs, pagination, draft integration +│ ├── utils.test.ts # formatDate, excerpt, escapeXml +│ └── rss.test.ts # RSS XML escaping +└── e2e/ # Playwright (chromium) + └── blog.spec.ts # Homepage, posts, RSS, write auth, 404, mobile ``` -### Key Patterns +### Patterns -- **Mock fetch** for PDS calls: `vi.stubGlobal("fetch", mockFetch)` -- **SQLite in-memory**: Set `process.env.DRAFTS_DB_PATH = ":memory:"` BEFORE imports -- **Test env vars** configured in `vitest.config.ts` (BLOG_URL, PDS_URL, DID, HANDLE) -- **E2E base URL** configurable via `BASE_URL` env var in `playwright.config.ts` +**Mock fetch** for PDS calls: +```typescript +const mockFetch = vi.fn(); +vi.stubGlobal("fetch", mockFetch); +mockFetch.mockResolvedValueOnce({ ok: true, json: async () => ({ records: [] }) }); +``` + +**SQLite in-memory** — set BEFORE imports: +```typescript +process.env.DRAFTS_DB_PATH = ":memory:"; +import { saveDraft, getDraft, closeDb } from "../../src/lib/drafts"; +beforeEach(() => closeDb()); // Fresh DB each test +``` + +**Test env vars** in `vitest.config.ts` — **E2E base URL** via `BASE_URL` env var in `playwright.config.ts` + +### After making changes + +Always run `npm test && npm run build` to verify nothing breaks. diff --git a/.claude/settings.json b/.claude/settings.json index 5993f6c..923d257 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -5,6 +5,10 @@ "Bash(npm test *)", "Bash(npm ci)", "Bash(npm install)", + "Bash(npm update)", + "Bash(npm audit *)", + "Bash(npm outdated)", + "Bash(npx @astrojs/upgrade *)", "Bash(node *)", "Bash(git *)", "Bash(gh *)", diff --git a/CLAUDE.md b/CLAUDE.md index ff7b206..418aa3a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,51 +1,53 @@ # at://press -Astro SSR blog engine powered by AT Protocol. Your PDS is your CMS. +Astro 6 SSR blog engine on AT Protocol. Your PDS is your CMS. + +## Stack + +Astro 6, @astrojs/node 10, TypeScript, Tailwind 4, better-sqlite3, marked, isomorphic-dompurify ## Commands ```bash -npm run setup # Interactive first-time setup (writes .env) -npm run dev # localhost:4321 -npm run build # Production build -npm run test # Vitest unit tests -npm run test:e2e # Playwright E2E (needs running server) -npm start # Run built server (port 4000) +npm run setup # Interactive first-time setup (writes .env) +npm run dev # localhost:4321 +npm run build # Production build +npm run test # Vitest (91 tests) +npm run test:e2e # Playwright E2E +npm start # Built server on port 4000 ``` ## Architecture -``` -Published posts → AT Protocol PDS (user-configured) -Drafts → Local SQLite (optional, /data/drafts.db) -Auth → ATAuth (optional, user-configured) -``` +- **Published posts** → PDS records (default: `com.whtwnd.blog.entry`, configurable) +- **Drafts** → SQLite (`/data/drafts.db`, optional) +- **Auth** → ATAuth (optional, degrades gracefully) +- **Deploy** → Docker or bare Node.js ## Key Files -``` -src/lib/constants.ts # All shared constants: URLs, limits, cache TTLs -src/lib/pds.ts # PDS fetching, caching, blog entries -src/lib/drafts.ts # SQLite draft CRUD, migration -src/lib/api.ts # Auth helpers, session creation, request parsing -src/lib/auth.ts # ATAuth login/verify, owner check -src/middleware.ts # CSP headers, one-time migration trigger -src/pages/api/ # publish, update, delete, upload-image, about, logout -``` - -## Key Patterns - -- All config in `constants.ts` — reads from `import.meta.env` (Astro) or `process.env` (Node) -- Drafts in SQLite, published posts on PDS -- Theme config imported from constants in Base.astro; FOUC script keeps `"blog-theme"` hardcoded -- Client-side constants passed via Astro `define:vars` -- All PDS catch blocks log errors before falling back to stale cache -- ATAuth is optional — write page degrades gracefully when not configured - -## Environment - -See `.env.example`. Required: `PDS_URL`, `DID`, `HANDLE`, `PDS_APP_PASSWORD`, `BLOG_URL`. - -## Lexicon Collections - -Default: WhiteWind (`com.whtwnd.blog.entry`). Configurable via `BLOG_COLLECTION` env var. +| File | Purpose | +|------|---------| +| `src/lib/constants.ts` | All config: URLs, limits, cache TTLs, env vars | +| `src/lib/pds.ts` | PDS fetching, caching, blog/about/profile | +| `src/lib/drafts.ts` | SQLite CRUD, migration, rkey generation | +| `src/lib/auth.ts` | ATAuth login/verify, owner check | +| `src/lib/api.ts` | Session creation, request parsing | +| `src/middleware.ts` | CSP headers, migration trigger | +| `src/pages/api/` | publish, update, delete, upload-image, about, logout | + +## Rules + +- All config centralized in `constants.ts` — never hardcode URLs or limits elsewhere +- `import.meta.env` for build-time vars, `process.env` for runtime (API routes) +- PDS catch blocks must log errors before falling back to stale cache +- Theme FOUC script in Base.astro keeps `"blog-theme"` hardcoded (can't import in `is:inline`) +- Client constants via Astro `define:vars` (write.astro: `MAX_IMAGE_SIZE`, `editBlobs`) +- Never commit `.env` — see `.env.example` for required vars + +## Slash Commands + +| Command | When to use | +|---------|-------------| +| `/atproto` | PDS records, blobs, collections, drafts, state transitions | +| `/testing` | Writing tests, running suites, mocking patterns |