docs: update README and AI_AGENT.md with platform architecture, selective content generation, and deployment details
All checks were successful
On Push to Main / test (push) Successful in 28s
On Push to Main / publish (push) Successful in 54s
On Push to Main / deploy-dev (push) Successful in 1m27s

This commit is contained in:
RaymondVerhoef
2026-05-17 20:35:12 +02:00
parent 06eb974825
commit 228d0d7a54
2 changed files with 116 additions and 17 deletions

View File

@@ -2,9 +2,11 @@
Welcome, fellow AI agent! If you are reading this, you are tasked with maintaining or extending the Respellion Learning Platform. This document provides the critical context, architectural patterns, and design standards you need to successfully work on this codebase. Welcome, fellow AI agent! If you are reading this, you are tasked with maintaining or extending the Respellion Learning Platform. This document provides the critical context, architectural patterns, and design standards you need to successfully work on this codebase.
> **Last updated:** 2026-05-17 — Reflects selective content generation (3 types), ISO week alignment, GitHub sync folder change, and AI extraction token limits.
## 1. Architectural Overview ## 1. Architectural Overview
This is a single-page React application built with **Vite**, backed by **PocketBase** as the database and auth layer. This is a single-page React application built with **Vite**, backed by **PocketBase** as the database and auth layer.
* **Frontend:** React, React Router, Tailwind CSS. * **Frontend:** React, React Router, Vanilla CSS (via CSS variables) + Tailwind utility classes mapped to those variables.
* **Backend:** PocketBase (self-hosted). All data is stored in PocketBase collections, not localStorage. * **Backend:** PocketBase (self-hosted). All data is stored in PocketBase collections, not localStorage.
* **Animations:** Framer Motion (used extensively for page transitions, podium effects, and gamification feedback). * **Animations:** Framer Motion (used extensively for page transitions, podium effects, and gamification feedback).
* **Icons:** Lucide React. * **Icons:** Lucide React.
@@ -16,7 +18,7 @@ All persistent data lives in **PocketBase**. The data access layer is in `src/li
**PocketBase Collections:** **PocketBase Collections:**
* `topics` — Knowledge graph nodes (`id`, `label`, `type`, `description`). * `topics` — Knowledge graph nodes (`id`, `label`, `type`, `description`).
* `relations` — Knowledge graph edges (`source`, `target`, `type`). * `relations` — Knowledge graph edges (`source`, `target`, `type`).
* `content` — AI-generated learning modules per topic (`topic_id`, `data`). * `content` — AI-generated learning modules per topic (`topic_id`, `data`). The `data` field is a **merged JSON object** containing only the content types that have been generated for that topic (e.g. `{ article: {...}, slides: [...] }`). New types are shallow-merged into the existing object by `learningService.js`; nothing is ever overwritten.
* `quiz_banks` — Banks of AI-generated questions per topic (`topic_id`, `questions[]`). * `quiz_banks` — Banks of AI-generated questions per topic (`topic_id`, `questions[]`).
* `quiz_results` — User test scores (`user_id`, `week_number`, `score`, `total`, `percentage`, etc.). * `quiz_results` — User test scores (`user_id`, `week_number`, `score`, `total`, `percentage`, etc.).
* `quiz_cache` — Cached weekly quiz for a user (`user_id`, `week_number`, `questions[]`, `meta`). * `quiz_cache` — Cached weekly quiz for a user (`user_id`, `week_number`, `questions[]`, `meta`).
@@ -36,6 +38,8 @@ All persistent data lives in **PocketBase**. The data access layer is in `src/li
**Session:** User login is PIN-based. The logged-in user's ID is stored in `sessionStorage` under `respellion_session` and resolved against `team_members` on app load (see `src/store/AppContext.jsx`). **Session:** User login is PIN-based. The logged-in user's ID is stored in `sessionStorage` under `respellion_session` and resolved against `team_members` on app load (see `src/store/AppContext.jsx`).
**Week Number:** The current ISO-8601 week number is calculated dynamically on app load via `getWeekNumber(new Date())` in `src/store/AppContext.jsx`. It is **not** stored in the database. The `ADVANCE_WEEK` action still exists for admin use, but initial state always reflects the real calendar week.
**Important:** All `db.js` functions are `async`. Always `await` them — omitting `await` will silently pass a Promise where data is expected. **Important:** All `db.js` functions are `async`. Always `await` them — omitting `await` will silently pass a Promise where data is expected.
**Auto-Cancellation:** The PocketBase JS SDK has auto-cancellation enabled by default. This causes concurrent identical requests (like `db.getTopics()` during React StrictMode renders or concurrent Promise.all) to abort with `ClientResponseError 0`. This feature is **globally disabled** in `src/lib/pb.js` via `pb.autoCancellation(false)` to prevent UI crashes during concurrent fetching. **Auto-Cancellation:** The PocketBase JS SDK has auto-cancellation enabled by default. This causes concurrent identical requests (like `db.getTopics()` during React StrictMode renders or concurrent Promise.all) to abort with `ClientResponseError 0`. This feature is **globally disabled** in `src/lib/pb.js` via `pb.autoCancellation(false)` to prevent UI crashes during concurrent fetching.
@@ -43,7 +47,8 @@ All persistent data lives in **PocketBase**. The data access layer is in `src/li
## 3. The AI Integration (Anthropic) ## 3. The AI Integration (Anthropic)
The application calls the Anthropic API via a proxy to avoid CORS issues. The application calls the Anthropic API via a proxy to avoid CORS issues.
* **Location:** `src/lib/api.js`. * **Location:** `src/lib/api.js`.
* **Proxy:** In Docker, `/api/anthropic` is proxied via Nginx to the Anthropic API endpoint. In local dev, configure `vite.config.js` to proxy the same path. * **Proxy:** In Docker, `/api/anthropic` is proxied via Caddy to the Anthropic API endpoint. In local dev, configure `vite.config.js` to proxy the same path. The API key is injected server-side by Caddy; there is **no client-side API key**.
* **Token limit:** `generateContent` uses `max_tokens: 8192`. Do not lower this — large knowledge-base files need the headroom.
* **JSON Enforcement:** Prompts *must* strictly enforce that Claude returns *only* raw JSON. Do not let the AI wrap the response in markdown blocks — a regex strip via `.match(/\{[\s\S]*\}/)` is already applied in all service files. * **JSON Enforcement:** Prompts *must* strictly enforce that Claude returns *only* raw JSON. Do not let the AI wrap the response in markdown blocks — a regex strip via `.match(/\{[\s\S]*\}/)` is already applied in all service files.
## 4. Design System & Aesthetics ## 4. Design System & Aesthetics
@@ -54,21 +59,44 @@ Respellion relies on a premium, modern aesthetic.
* **Tailwind:** Tailwind is mapped to these CSS variables. Avoid hardcoding random hex codes. Use the existing Tailwind classes like `bg-teal`, `text-fg-muted`, and `border-bg-warm`. * **Tailwind:** Tailwind is mapped to these CSS variables. Avoid hardcoding random hex codes. Use the existing Tailwind classes like `bg-teal`, `text-fg-muted`, and `border-bg-warm`.
* **Components:** Always reuse the UI primitives in `src/components/ui/` (`Card.jsx`, `Button.jsx`, `Tag.jsx`, `Input.jsx`). * **Components:** Always reuse the UI primitives in `src/components/ui/` (`Card.jsx`, `Button.jsx`, `Tag.jsx`, `Input.jsx`).
## 5. Gamification Rules ## 5. Learning Content Types
`src/lib/learningService.js` supports **three** content types for selective generation:
| Type | Schema key | Description |
|---|---|---|
| `article` | `content.article` | Title, intro, sections, key takeaways |
| `slides` | `content.slides` | Array of slides with bullets and speaker notes |
| `infographic` | `content.infographic` | Headline, tagline, stats, steps, quote |
**There is no podcast type.** It was removed. Do not re-add it.
`generateLearningContent(topic, force, selectedType)` accepts one of the three types above, or `'all'` (admin regeneration). Cache-hit logic checks `content[selectedType]` directly. On generation, the new data is shallow-merged into the existing cached object so other types are preserved.
The `LearningContentViewer` tab bar reflects exactly these three modes. The empty-state for an un-generated tab shows a "Generate [Type]" button that calls `onGenerate(activeMode)` passed from the parent.
## 6. Gamification Rules
If you are extending the Gamification system (`src/pages/Leaderboard.jsx`): If you are extending the Gamification system (`src/pages/Leaderboard.jsx`):
* Tests grant **+2 points** per correct answer (handled in `testService.js → saveTestResult`). * Tests grant **+2 points** per correct answer (handled in `testService.js → saveTestResult`).
* A 100% score grants the **Perfectionist** badge (computed at render time in `Leaderboard.jsx`). * A 100% score grants the **Perfectionist** badge (computed at render time in `Leaderboard.jsx`).
* Completing 1 test grants **First Steps**, completing 5 grants **Veteran**. * Completing 1 test grants **First Steps**, completing 5 grants **Veteran**.
* Points are accumulated in the `leaderboard` collection via `db.upsertLeaderboardEntry`. * Points are accumulated in the `leaderboard` collection via `db.upsertLeaderboardEntry`.
## 6. Docker & Deployment ## 7. Docker & Deployment
The app is fully containerized. PocketBase runs as a sidecar service. The app is fully containerized. PocketBase runs as a sidecar service.
* **Build:** `docker build -t respellion-app:latest .` * **Build:** `docker build -t respellion-app:latest .`
* **Run:** `docker compose up -d` (see `docker-compose.yml`). * **Run:** `docker compose up -d` (see `docker-compose.yml`).
* **Nginx:** Ensures any frontend route not matching a static file falls back to `index.html` (SPA support). The `/api/anthropic` path is proxied to the Anthropic API. The `/pb` path is proxied to the PocketBase service. * **Caddy (reverse proxy):** Handles SPA fallback, injects the Anthropic API key via a `Authorization` header on `/api/anthropic/*` requests, and proxies `/pb/*` to the PocketBase service. Config lives in `Caddyfile`.
* **PocketBase URL:** Resolved from `VITE_PB_URL` env var, or falls back to `window.location.origin + '/pb'` (see `src/lib/pb.js`). * **PocketBase URL:** Resolved from `VITE_PB_URL` env var, or falls back to `window.location.origin + '/pb'` (see `src/lib/pb.js`).
## 8. R42 Chatbot ## 8. GitHub Knowledge-Base Sync
The Admin upload panel (`src/components/admin/UploadZone.jsx`) can sync markdown/text files directly from a GitHub repository.
* **Default folder:** `docs/knowledge-base/` of the `respellion/employee-handbook` repo. This is persisted in the `settings` collection under the key `github:url` so admins can change it from the UI.
* **Change detection:** Each file's SHA is stored as `github:sha:<filename>` in `settings`. Files whose SHA differs are marked *Updated*; new files are *New*. Up-to-date files are skipped.
* **Extraction pipeline (`src/lib/extractionPipeline.js`):** Calls `anthropicApi.generateContent` with a strict JSON-only system prompt. To prevent truncated responses on large files, the prompt limits extraction to **max 15 topics** and their most important relations. If the AI returns non-JSON, the file is marked `failed` in the `sources` collection.
* **Deduplication:** A file already in `sources` with `status: completed` will throw and not be re-processed. Delete the source record first to force a re-analysis.
* **Do not increase topic cap beyond 15** without also verifying the `max_tokens: 8192` budget is sufficient for the expected file size.
## 9. R42 Chatbot
The platform ships a global chatbot avatar called **R42**, rendered as the Respellion `{ r }` brand mark in three states (idle / typing / error). The platform ships a global chatbot avatar called **R42**, rendered as the Respellion `{ r }` brand mark in three states (idle / typing / error).
* **Mark component:** `src/components/ui/Mark.jsx`. Pure SVG; renders the brand mark with `state`, `size`, `theme`, `showFrame`, `brace`, `letter` props. Requires the `BallPill` font (loaded via `@font-face` in `src/index.css`, served from `public/fonts/BallPill-light.otf`). Falls back to JetBrains Mono (already loaded). * **Mark component:** `src/components/ui/Mark.jsx`. Pure SVG; renders the brand mark with `state`, `size`, `theme`, `showFrame`, `brace`, `letter` props. Requires the `BallPill` font (loaded via `@font-face` in `src/index.css`, served from `public/fonts/BallPill-light.otf`). Falls back to JetBrains Mono (already loaded).
@@ -86,11 +114,18 @@ The platform ships a global chatbot avatar called **R42**, rendered as the Respe
* **Admin approval UI:** `src/components/admin/SuggestionsQueue.jsx`, mounted at the top of the Knowledge Graph admin panel. Approve calls `kbStore.approveSuggestion(id)` which runs the same `applyDelta` merge (async, PocketBase) and flips status to `approved`. Reject flips to `rejected` for audit. * **Admin approval UI:** `src/components/admin/SuggestionsQueue.jsx`, mounted at the top of the Knowledge Graph admin panel. Approve calls `kbStore.approveSuggestion(id)` which runs the same `applyDelta` merge (async, PocketBase) and flips status to `approved`. Reject flips to `rejected` for audit.
* **kbStore:** `src/lib/kbStore.js` is the single source of truth for KB mutations from the chatbot path. Topics/relations go to PocketBase; suggestions queue goes to localStorage. Dispatches `respellion:kb-updated` after any write so the D3 graph and the queue panel refresh without a reload. * **kbStore:** `src/lib/kbStore.js` is the single source of truth for KB mutations from the chatbot path. Topics/relations go to PocketBase; suggestions queue goes to localStorage. Dispatches `respellion:kb-updated` after any write so the D3 graph and the queue panel refresh without a reload.
## 9. How to Add New Features ## 10. How to Add New Features
1. **Define Schema:** Add a new PocketBase collection via the Admin UI or the init script (`scripts/setup-pb-collections.mjs`). 1. **Define Schema:** Add a new PocketBase collection via the Admin UI or the init script (`scripts/setup-pb-collections.mjs`).
2. **Add DB Helpers:** Add async CRUD functions in `src/lib/db.js`. 2. **Add DB Helpers:** Add async CRUD functions in `src/lib/db.js`.
3. **Build UI:** Create components using `Card`, `Button`, and `framer-motion` for smooth entry (`animate-in fade-in slide-in-from-bottom-4`). 3. **Build UI:** Create components using `Card`, `Button`, and `framer-motion` for smooth entry (`animate-in fade-in slide-in-from-bottom-4`).
4. **Wire Logic:** Connect to `src/store/AppContext.jsx` if it requires global user context, otherwise manage state locally. 4. **Wire Logic:** Connect to `src/store/AppContext.jsx` if it requires global user context, otherwise manage state locally.
5. **Test:** Run the Docker stack (`docker compose up`) to ensure no build errors exist. 5. **Test:** Run the Docker stack (`docker compose up`) to ensure no build errors exist.
## 11. Known Gotchas & Decisions
* **No podcast type.** The podcast learning type was deliberately removed. The three supported types are `article`, `slides`, and `infographic`.
* **Week number is computed, not stored.** Do not add a `admin:current_week` setting — the ISO week is always derived from `new Date()`.
* **Caddy, not Nginx.** Older docs/comments may reference Nginx. The reverse proxy is Caddy. Update any references you encounter.
* **AI token budget.** If you see `[Pipeline] AI returned non-JSON response` in the logs, the response was truncated. Increase the topic cap prompt constraint before raising `max_tokens`.
* **PocketBase auto-cancellation is OFF.** `pb.autoCancellation(false)` is set globally in `src/lib/pb.js`. Never re-enable it — it causes abort errors during concurrent fetches in React StrictMode.
Good luck! You are building a platform that empowers continuous learning. Keep it fast, keep it beautiful, and keep the user engaged. Good luck! You are building a platform that empowers continuous learning. Keep it fast, keep it beautiful, and keep the user engaged.

View File

@@ -1,16 +1,80 @@
# React + Vite # Respellion Learning Platform
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. An internal AI-powered learning platform that keeps Respellion employees up to date with the company's evolving knowledge base.
Currently, two official plugins are available: ## Features
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Oxc](https://oxc.rs) - **Weekly Learning Station** — Each employee is assigned a topic each week (via deterministic hash of user ID + week number). They choose their preferred format: Article, Slides, or Infographic. Content is generated on-demand by Claude and cached per topic.
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) - **Weekly Test** — AI-generated quiz based on the knowledge graph. Results are stored and feed the leaderboard.
- **Leaderboard & Gamification** — Points for correct answers, badges for streaks and perfect scores.
- **R42 Chatbot** — An always-available AI assistant (backed by Claude) with access to the full knowledge graph. Can propose graph updates that admins approve or reject.
- **Admin Panel** — Manage the knowledge graph, sync from GitHub, review generated content, refine it with AI, and monitor team progress.
## React Compiler ## Tech Stack
The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation). | Layer | Technology |
|---|---|
| Frontend | React 18 + Vite |
| Styling | Vanilla CSS (custom properties) + Tailwind utility classes |
| Animations | Framer Motion |
| Icons | Lucide React |
| Graph viz | D3.js (admin knowledge graph only) |
| Backend / DB | PocketBase (self-hosted) |
| AI | Anthropic Claude (via Caddy reverse proxy) |
| Infra | Docker + Caddy |
## Expanding the ESLint configuration ## Getting Started (local dev)
If you are developing a production application, we recommend using TypeScript with type-aware lint rules enabled. Check out the [TS template](https://github.com/vitejs/vite/tree/main/packages/create-vite/template-react-ts) for information on how to integrate TypeScript and [`typescript-eslint`](https://typescript-eslint.io) in your project. ```bash
# 1. Install dependencies
npm install
# 2. Start PocketBase (Windows)
./pocketbase.exe serve
# 3. Start the dev server
npm run dev
```
The Vite dev server proxies `/api/anthropic` and `/pb` — see `vite.config.js`.
## Deployment (Docker)
```bash
docker compose up -d
```
The `Caddyfile` handles:
- SPA fallback routing
- `/pb/*` → PocketBase sidecar
- `/api/anthropic/*` → Anthropic API (with server-side API key injection)
## Key Files
| File | Purpose |
|---|---|
| `src/lib/learningService.js` | Selective content generation (article / slides / infographic) |
| `src/lib/extractionPipeline.js` | GitHub file → knowledge graph extraction |
| `src/lib/api.js` | Anthropic API wrapper (`generateContent` + `chat`) |
| `src/lib/db.js` | All PocketBase data access |
| `src/lib/giteaService.js` | GitHub API client (folder listing + raw file fetch) |
| `src/store/AppContext.jsx` | Global state; computes ISO week number on load |
| `src/components/admin/UploadZone.jsx` | GitHub sync UI (default folder: `docs/knowledge-base/`) |
| `AI_AGENT.md` | Detailed context guide for AI coding agents |
## Content Types
Learning content is generated **on demand per type** and merged into the cached object:
| Type | Key in DB | Description |
|---|---|---|
| Article | `content.article` | Long-form reading |
| Slides | `content.slides` | Slide deck with speaker notes |
| Infographic | `content.infographic` | Visual summary with stats and steps |
> The podcast type was removed. Do not re-add it.
## Documentation
- **`AI_AGENT.md`** — Full architectural guide for AI coding agents (patterns, gotchas, decisions).
- **`CHANGELOG.md`** — PocketBase upstream changelog (not application changelog).