docs: add AGENTS.md knowledge base (root + generators + dm)

This commit is contained in:
emil
2026-05-15 04:35:00 +03:00
parent 592fa4cc3e
commit 11da171bfb
4 changed files with 190 additions and 2 deletions
@@ -1,10 +1,10 @@
{
"sessionID": "ses_1d89ad89affe987QEXPgCGcx4S",
"updatedAt": "2026-05-14T23:12:10.460Z",
"updatedAt": "2026-05-15T01:27:46.830Z",
"sources": {
"background-task": {
"state": "idle",
"updatedAt": "2026-05-14T23:12:10.460Z"
"updatedAt": "2026-05-15T01:27:46.830Z"
}
}
}
+94
View File
@@ -0,0 +1,94 @@
# Randify.pro — Knowledge Base
**Stack:** Astro 4 + Tailwind CSS 4 + TypeScript + PostgreSQL (Drizzle ORM)
**Deploy:** Docker Compose (app + postgres) on VPS, nginx reverse proxy, Let's Encrypt SSL
## Overview
Bilingual (EN/RU) static/hybrid site hosting random-value generators (10 live). Recently expanded with **DM Dashboard** — a server-rendered OAuth-authenticated subdomain (`/dm/`) with dice roller, initiative tracker, Open5e reference, and notes.
## Structure
```
/
├── src/
│ ├── pages/ # Astro pages (EN + RU mirrors)
│ │ ├── api/auth/ # OAuth 2.1 + PKCE (VK ID + Yandex)
│ │ ├── generators/ # Static generator pages
│ │ ├── dm/ # DM Dashboard (server-rendered, prerender=false)
│ │ └── ru/ # Russian mirror pages
│ ├── components/
│ │ ├── generators/ # Interactive generator components (see sub-AGENTS)
│ │ └── dm/ # DM Dashboard UI (see sub-AGENTS)
│ ├── layouts/ # BaseLayout, GeneratorLayout, DmLayout
│ ├── lib/
│ │ ├── auth/ # JWT, OAuth configs, session utils
│ │ ├── client/ # Browser utilities (dice, animations, random)
│ │ └── open5e/ # Open5e API client + cache
│ ├── db/ # Drizzle schema, migrations, client
│ ├── i18n/ # Translations (en/ru) + DM translations
│ ├── content/ # Astro Content Collections (generators, blog)
│ └── data/ # Static data (generator list, config)
├── tests/ # Vitest + Playwright tests
├── drizzle/ # SQL migrations
├── docker-compose.yml # App + PostgreSQL production setup
└── .github/workflows/ # CI/CD deploy via rsync
```
## Where to Look
| Task | Location | Notes |
|------|----------|-------|
| Add new generator | `src/content/generators/`, `src/components/generators/`, `src/pages/generators/` | Must create EN + RU simultaneously |
| Fix OAuth | `src/pages/api/auth/`, `src/lib/auth/`, `src/middleware/index.ts` | VK ID uses `id.vk.ru`, Yandex uses `oauth.yandex.com` |
| DB schema change | `src/db/schema.ts` → `drizzle/` | Run `npm run db:generate` then migrate |
| DM Dashboard UI | `src/components/dm/`, `src/pages/dm/` | Orange theme `#E87722`, RU-only |
| i18n strings | `src/i18n/translations.ts`, `src/i18n/dm-translations.ts` | `useT(lang)` returns typed object |
| Production deploy | `.github/workflows/deploy.yml`, `docker-compose.yml` | GitHub Actions → rsync → Docker Compose restart |
## Conventions
- **Path aliases:** `@/*` → `src/*` used everywhere
- **i18n routing:** Astro built-in. `Astro.currentLocale` drives locale. EN pages at root, RU under `/ru/`
- **Generator pages:** Each generator has its own `.astro` page file (never dynamic `[slug].astro` for generators — Astro bundles all imported scripts)
- **Color system:** `var(--accent)` in BaseLayout = `#534AB7` (purple). DM Dashboard overrides to `#E87722` (orange) via `.dm-theme` CSS class
- **Auth cookie name:** `auth_token` (unified across OAuth callback, middleware, logout)
- **DB connection:** `process.env.DATABASE_URL` — must match docker-compose `postgres` service hostname inside containers
## Anti-Patterns
- **Dynamic `[slug].astro` for generators:** Forbidden. Each generator must have its own page file to avoid script bundling issues
- **Using `url.origin` in OAuth callbacks:** Broken behind nginx proxy. Always use `process.env.PUBLIC_APP_URL || url.origin`
- **Joining multiple `Set-Cookie` with comma:** Browsers reject this. Use `Headers.append()` for each cookie
- **`SameSite=Strict` on session cookie:** Breaks cross-site OAuth redirects. Use `SameSite=Lax` + `Secure`
- **Static `prerender` for DM pages:** Middleware won't run, `Astro.locals.user` stays `null`. DM pages MUST have `export const prerender = false`
- **`Math.random()` for cryptographic purposes:** Generators should use `crypto.getRandomValues()` via `src/lib/client/random.ts`
- **Importing `import.meta.env` in server code:** Use `process.env` for server-side secrets (OAuth, JWT, DB)
- **Unknown fields in generator JSON:** `src/lib/generator-schema.ts` uses Zod `.strict()`. Any unknown field breaks the build
- **Unescaped `innerHTML` with user input:** Generators use `innerHTML` extensively. Always escape user input via `escapeHtml()` (currently duplicated in 3 files — consolidate to `src/lib/client/`)
- **OAuth empty-string fallbacks:** `process.env.VK_CLIENT_ID || ''` silently produces invalid requests. Validate env vars explicitly or fail fast
- **JWT secret non-null assertion:** `process.env.JWT_SECRET!` in `src/lib/auth/jwt.ts` crashes at runtime if env var is missing
## Commands
```bash
npm run dev # Dev server (localhost:4321)
npm run build # Static + server build → dist/
npm run preview # Preview built dist/
npm run test # Vitest unit tests
npm run test:e2e # Playwright E2E tests
npm run db:generate # Generate Drizzle migration
npm run db:migrate # Run migrations
```
## Notes
- **No test runner or linter** configured in CI (per CLAUDE.md)
- **Health endpoint** (`/api/health`) checks DB connectivity via `SELECT 1`
- **Analytics:** Yandex Metrika + Top.Mail.Ru counters, IDs in `src/data/config.ts`
- **Service Worker** registered in BaseLayout for PWA support
- **Blog** uses Astro Content Collections with MDX/Markdown
- **Dockerfile** is single-stage Node 20 Alpine (not multi-stage as CLAUDE.md claims)
- **ESLint override:** `public/sw.js` has `@typescript-eslint/no-unused-vars` disabled for service worker globals
- **Zero TODO/FIXME/HACK markers** in source code — codebase is clean
- **Explicit prerender required:** Hybrid output mode means every `.astro` page MUST declare `export const prerender = true/false`
+40
View File
@@ -0,0 +1,40 @@
# DM Dashboard — Knowledge Base
**Location:** `src/components/dm/`, `src/pages/dm/`, `src/pages/ru/dm/`
## Overview
Dungeon Master Dashboard — standalone RPG tool module within Randify. Server-rendered (hybrid Astro), RU-only, orange theme (`#E87722`). OAuth-authenticated via VK ID and Yandex.
## Components
| Component | Purpose |
|-----------|---------|
| `DiceRoller.astro` | Dice notation parser + roller with history |
| `InitiativeTracker.astro` | Combat initiative list with sorting |
| `Open5eReference.astro` | Open5e monster search + detail view |
| `NotesPanel.astro` | `localStorage`-backed notes editor |
| `AuthPanel.astro` | Login/logout UI, reads `Astro.locals.user` |
| `DmHeader.astro` | Header with back link to Randify |
| `DmCard.astro` | Styled container card |
| `DmButton.astro` | Reusable button with variants |
| `DmInput.astro` | Styled form input |
## Key Files
- **`src/layouts/DmLayout.astro`** — Wraps DM pages, imports `dm-theme.css`, hides `LanguageSwitcher`
- **`src/styles/dm-theme.css`** — CSS vars scoped to `.dm-theme` class (overrides BaseLayout purple)
- **`src/i18n/dm-translations.ts`** — RU-only strings for DM Dashboard
## Conventions
- **Theme override:** `.dm-theme` class on wrapper div sets `--accent: #E87722`
- **Server-rendered:** All DM pages have `export const prerender = false` (middleware sets `Astro.locals.user`)
- **Auth state:** Check `Astro.locals.user` in components; middleware validates `auth_token` cookie + JWT + DB session
- **History:** Dice roller uses `sessionStorage`, notes use `localStorage`
## Anti-Patterns
- **Using `:root` for DM CSS vars:** BaseLayout sets `--accent: #534AB7` on `:root`. DM theme MUST use `.dm-theme` selector for higher specificity
- **Static DM pages:** Will break auth. Always `prerender = false`
- **Forgetting `hideLanguageSwitcher` on `DmLayout`:** DM should feel like standalone product
+54
View File
@@ -0,0 +1,54 @@
# Generators — Knowledge Base
**Location:** `src/components/generators/`
## Overview
10 interactive random-value generator components. Each is an Astro `.astro` file with client-side `<script>` for interactivity. Shared patterns for i18n, history persistence, and copy-to-clipboard.
## Structure
```
src/components/generators/
├── DiceGenerator.astro # Dice notation parser + roller
├── MealGenerator.astro # Random meal picker
├── EmojiGenerator.astro # Random emoji with categories
├── HashGenerator.astro # Hash generators (MD5, SHA, etc.)
├── FontPairGenerator.astro # Google Font pairings
├── Magic8BallGenerator.astro # Magic 8-ball
├── TimeGenerator.astro # Random time generator
├── WheelSpinner.astro # Weighted wheel spinner
├── WeightedGenerator.astro # Weighted random picker
├── PaletteGenerator.astro # Color palette generator
└── (shared utilities in src/lib/client/)
```
## Conventions
- **Language detection (frontmatter):**
```astro
const isRu = Astro.url.pathname.startsWith('/ru');
const T = useT(isRu ? 'ru' : 'en');
```
- **Language detection (client script):**
```js
const isRu = document.documentElement.lang === 'ru';
```
- **History persistence:** Use `sessionStorage` (not `localStorage`) with prefixed keys
- **Copy feedback:** Show "Copied!" toast using `T.copyFeedback` / `T.copied`
- **Secure random:** Import from `@/lib/client/random` (uses `crypto.getRandomValues`)
## Anti-Patterns
- **Using `Math.random()` directly:** Always use `@/lib/client/random` for cryptographic quality
- **Hardcoding strings:** All user-visible text must go through `T.*` keys
- **Skipping RU version:** Every generator MUST have both EN and RU pages
## Where to Look
| Task | File | Notes |
|------|------|-------|
| Add new generator | Copy `DiceGenerator.astro` as template | Follow i18n + history + copy patterns |
| Fix dice logic | `src/lib/dice-engine.ts` | Parser + roller, shared across generators |
| Fix randomness | `src/lib/client/random.ts` | `crypto.getRandomValues` wrapper |
| Generator metadata | `src/content/generators/<slug>.json` | Zod-validated, `.strict()` — unknown fields fail build |