Add comprehensive documentation for key organizational aspects
- Introduced "Pension Scheme & Benefits" detailing secondary employment benefits and pension specifics. - Created "Roles & Accountabilities" outlining the Holacracy role structure and responsibilities within Respellion. - Added "Security" section covering GDPR compliance and workplace safety protocols. - Established "Spending and Contracting" policy detailing expense categories and submission processes. - Documented "Who We Are" to define Respellion's identity, services, and operational model under Holacracy and ISO 9001.
This commit is contained in:
@@ -1,701 +1,93 @@
|
||||
# Frontend spec
|
||||
|
||||
## Responsibility
|
||||
|
||||
Single Next.js 14 codebase serving two distinct role-based experiences:
|
||||
- `/admin/*` — content administration (document upload, KB review, curriculum)
|
||||
- `/app/*` — employee learning experience (sessions, library, R42, gamification)
|
||||
|
||||
Mobile-first. Designed for 375px width, scales up. Installable as a PWA.
|
||||
A React 19 SPA built with Vite, routed by React Router 7. Entry: `src/main.jsx` →
|
||||
`src/App.jsx`. Global state lives in `src/store/AppContext.jsx`.
|
||||
|
||||
---
|
||||
|
||||
## Location
|
||||
## Routing & access control (`src/App.jsx`)
|
||||
|
||||
```
|
||||
app/frontend/
|
||||
├── src/
|
||||
│ ├── app/ Next.js app router
|
||||
│ │ ├── layout.tsx root layout — global stylesheet import
|
||||
│ │ ├── page.tsx redirect → role-based landing
|
||||
│ │ ├── admin/
|
||||
│ │ │ ├── layout.tsx admin shell (sidebar nav)
|
||||
│ │ │ ├── page.tsx admin dashboard
|
||||
│ │ │ ├── documents/
|
||||
│ │ │ │ └── page.tsx document upload + ingestion status
|
||||
│ │ │ ├── knowledge/
|
||||
│ │ │ │ ├── page.tsx theme batch review list
|
||||
│ │ │ │ └── [themeId]/page.tsx theme detail + topic edit
|
||||
│ │ │ └── curriculum/
|
||||
│ │ │ └── page.tsx curriculum editor + regeneration
|
||||
│ │ ├── app/
|
||||
│ │ │ ├── layout.tsx employee shell (bottom nav + R42)
|
||||
│ │ │ ├── page.tsx redirect → /app/session
|
||||
│ │ │ ├── session/
|
||||
│ │ │ │ └── page.tsx current week session
|
||||
│ │ │ ├── library/
|
||||
│ │ │ │ ├── page.tsx knowledge library browse
|
||||
│ │ │ │ └── [topicId]/page.tsx topic detail
|
||||
│ │ │ └── profile/
|
||||
│ │ │ └── page.tsx gamification profile + heatmap + badges
|
||||
│ │ ├── auth/
|
||||
│ │ │ └── page.tsx login (PocketBase auth)
|
||||
│ │ └── api/ Next.js API routes (thin proxies only)
|
||||
│ ├── components/
|
||||
│ │ ├── admin/ admin-specific components
|
||||
│ │ ├── employee/ employee-specific components
|
||||
│ │ ├── micro-learnings/ one component per micro learning type
|
||||
│ │ ├── r42/ R42 chatbot components
|
||||
│ │ ├── gamification/ heatmap, badges, leaderboard
|
||||
│ │ └── ui/ shared primitives
|
||||
│ ├── lib/
|
||||
│ │ ├── pocketbase.ts PocketBase client (browser)
|
||||
│ │ ├── services.ts typed API calls to backend services
|
||||
│ │ ├── auth.ts auth helpers + role guards
|
||||
│ │ └── hooks/ custom React hooks
|
||||
│ └── types/
|
||||
│ └── index.ts shared TypeScript types
|
||||
├── public/
|
||||
│ ├── manifest.json PWA manifest
|
||||
│ ├── sw.js service worker (generated)
|
||||
│ └── icons/ PWA icons (192, 512)
|
||||
├── next.config.js
|
||||
├── tailwind.config.ts
|
||||
├── tsconfig.json
|
||||
└── .env.example
|
||||
```
|
||||
| Route | Screen | Access |
|
||||
|---|---|---|
|
||||
| `/login` | `Login.jsx` | public |
|
||||
| `/onboarding` | `Onboarding.jsx` | logged-in, not yet enrolled |
|
||||
| `/` | `Dashboard.jsx` | enrolled user |
|
||||
| `/learn` | `Leren.jsx` | enrolled user |
|
||||
| `/test` | `Testen.jsx` | enrolled user |
|
||||
| `/leaderboard` | `Leaderboard.jsx` | enrolled user |
|
||||
| `/admin/*` | `Admin/index.jsx` | `role === 'admin'` |
|
||||
|
||||
`ProtectedRoute`:
|
||||
- redirects to `/login` if not authenticated;
|
||||
- redirects to `/onboarding` if `enrollment_status !== 'active'` — **except** admins
|
||||
heading to the admin panel, who are exempt;
|
||||
- enforces `requireAdmin` for `/admin`.
|
||||
|
||||
Navigation chrome (top bar + mobile bottom nav) is rendered by `ProtectedRoute`.
|
||||
`ChatLauncher` (R42) is mounted globally.
|
||||
|
||||
---
|
||||
|
||||
## Stylesheet integration
|
||||
## Auth & global state (`AppContext.jsx`)
|
||||
|
||||
`/stylesheet.css` lives at the repo root — not inside `app/frontend/`.
|
||||
|
||||
Import it as the first global stylesheet in `src/app/layout.tsx`:
|
||||
|
||||
```tsx
|
||||
import '../../../stylesheet.css' // path from app/frontend/src/app/
|
||||
import './globals.css' // Tailwind directives second
|
||||
```
|
||||
|
||||
Rules:
|
||||
- stylesheet.css is the authoritative visual style — never override it
|
||||
- Where Tailwind utility classes conflict with stylesheet.css rules,
|
||||
stylesheet.css wins
|
||||
- Tailwind is used for layout, spacing, and elements not covered by the
|
||||
stylesheet — match the visual language (spacing scale, colour, type) of
|
||||
the existing stylesheet when doing so
|
||||
- Inspect stylesheet.css before implementing any component — use its CSS
|
||||
custom properties (if any) rather than hardcoding values
|
||||
- Loads `team_members` on mount; auto-creates an `Admin` (PIN `0000`) if the table is empty.
|
||||
- PIN login resolves a member and stores the id in `sessionStorage.respellion_session`.
|
||||
- `state.currentUser` holds the member; `state.weekNumber` is the user's **absolute
|
||||
curriculum week**, derived from `curriculum_started_at` via `getPersonalWeekNumber`
|
||||
(0 until enrolled).
|
||||
- `enrollCurrentUser()` stamps `curriculum_started_at = now`, sets
|
||||
`enrollment_status = 'active'`, and updates state.
|
||||
|
||||
---
|
||||
|
||||
## PWA configuration
|
||||
## Onboarding (`Onboarding.jsx`)
|
||||
|
||||
### next.config.js
|
||||
|
||||
Use `next-pwa` package to generate service worker and manifest wiring:
|
||||
|
||||
```javascript
|
||||
const withPWA = require('next-pwa')({
|
||||
dest: 'public',
|
||||
register: true,
|
||||
skipWaiting: true,
|
||||
disable: process.env.NODE_ENV === 'development'
|
||||
})
|
||||
|
||||
module.exports = withPWA({
|
||||
reactStrictMode: true,
|
||||
})
|
||||
```
|
||||
|
||||
### public/manifest.json
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "Learning Platform",
|
||||
"short_name": "Learn",
|
||||
"description": "Employee knowledge and learning",
|
||||
"start_url": "/app",
|
||||
"display": "standalone",
|
||||
"background_color": "#ffffff",
|
||||
"theme_color": "#ffffff",
|
||||
"orientation": "portrait",
|
||||
"icons": [
|
||||
{ "src": "/icons/192.png", "sizes": "192x192", "type": "image/png" },
|
||||
{ "src": "/icons/512.png", "sizes": "512x512", "type": "image/png" }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Note: set `theme_color` and `background_color` to match stylesheet.css
|
||||
primary background after inspecting the file.
|
||||
|
||||
### Service worker caching strategy
|
||||
|
||||
- Static assets: cache-first
|
||||
- PocketBase API calls: network-first, fall back to cache
|
||||
- Backend service calls: network-only (no caching for dynamic content)
|
||||
A blocking first-login screen. One CTA — "Start my journey" — calls
|
||||
`enrollCurrentUser()` and routes to `/`. Week 1 begins immediately. Users already
|
||||
enrolled are redirected to `/`. See `docs/curriculum-spec.md`.
|
||||
|
||||
---
|
||||
|
||||
## Auth
|
||||
## Employee screens
|
||||
|
||||
PocketBase handles auth. Two roles: `admin` and `employee`.
|
||||
- **Dashboard** — current cycle/week, assigned topic, cycle progress ring, quick
|
||||
links to Learn and Test, mini leaderboard, recent activity.
|
||||
- **Learning Station (`/learn`)** — the week's required topic + the rest of the
|
||||
knowledge library; opening a topic shows the micro-learning selector
|
||||
(`src/components/micro_learning/`). Completing ≥1 micro-learning marks the week done.
|
||||
- **Test (`/test`)** — 5-question quiz with a 5-minute timer, per-question feedback,
|
||||
and a results/review screen. Sets the `quiz:active` flag to hide R42.
|
||||
- **Leaderboard (`/leaderboard`)** — podium + ranked list + badges (see
|
||||
`docs/gamification-spec.md`).
|
||||
|
||||
### Login flow
|
||||
|
||||
```
|
||||
/auth page → email + password form
|
||||
↓
|
||||
PocketBase authWithPassword()
|
||||
↓
|
||||
Store token in PocketBase SDK (persists in localStorage)
|
||||
↓
|
||||
Read user.role from auth record
|
||||
↓
|
||||
role === 'admin' → redirect to /admin
|
||||
role === 'employee' → redirect to /app
|
||||
```
|
||||
|
||||
### Route guards
|
||||
|
||||
Implement as Next.js middleware (`middleware.ts` at app root):
|
||||
|
||||
```typescript
|
||||
// Admin routes: require role === 'admin'
|
||||
// Employee routes: require role === 'employee'
|
||||
// Unauthenticated: redirect to /auth
|
||||
// Wrong role: redirect to correct landing
|
||||
```
|
||||
|
||||
### PocketBase client (browser)
|
||||
|
||||
```typescript
|
||||
// lib/pocketbase.ts
|
||||
import PocketBase from 'pocketbase'
|
||||
export const pb = new PocketBase(process.env.NEXT_PUBLIC_POCKETBASE_URL)
|
||||
```
|
||||
|
||||
Use `pb.authStore` for auth state. Use `pb.collection().getFullList()` etc.
|
||||
for direct PocketBase reads. The frontend reads KB content (topics, micro
|
||||
learnings) directly from PocketBase — it does not proxy through backend services.
|
||||
|
||||
### Service calls
|
||||
|
||||
Backend services (ingestion, generation, curriculum, chat, progress) are called
|
||||
via typed fetch wrappers in `lib/services.ts`:
|
||||
|
||||
```typescript
|
||||
// Example
|
||||
export async function postComplete(payload: CompletePayload) {
|
||||
const res = await fetch(`${PROGRESS_URL}/complete`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(payload)
|
||||
})
|
||||
if (!res.ok) throw new Error(`Complete failed: ${res.status}`)
|
||||
return res.json() as Promise<CompleteResponse>
|
||||
}
|
||||
```
|
||||
|
||||
All service response types imported from `types/index.ts`.
|
||||
Labels show `Cycle X · Week Y of 26`, where Y/X come from `getCurriculumWeek` /
|
||||
`getCurriculumCycle` applied to `state.weekNumber`.
|
||||
|
||||
---
|
||||
|
||||
## Admin app
|
||||
## Admin panel (`Admin/index.jsx`)
|
||||
|
||||
### Shell layout (`admin/layout.tsx`)
|
||||
|
||||
Sidebar navigation on desktop, top navigation on mobile.
|
||||
|
||||
Nav items:
|
||||
- Documents
|
||||
- Knowledge base
|
||||
- Curriculum
|
||||
- (link back to employee app)
|
||||
|
||||
### Documents page (`admin/documents/page.tsx`)
|
||||
|
||||
**Upload section**
|
||||
- Drag-and-drop file input: accepts .pdf, .md, .txt
|
||||
- On upload: POST file to PocketBase storage →
|
||||
then POST to ingestion service `/ingest` with document metadata
|
||||
- Show upload confirmation with filename
|
||||
|
||||
**Job status list**
|
||||
- Poll GET /status/:jobId every 3 seconds while status is not done/failed
|
||||
- Show per-job progress:
|
||||
- Status badge: queued / extracting / chunking / structuring / embedding / done / failed
|
||||
- Progress bar derived from chunksEmbedded / chunksTotal
|
||||
- On done: "N themes, N topics ready for review" → link to knowledge base
|
||||
- On failed: error reason in red, no retry (admin re-uploads)
|
||||
- Stop polling when status === 'done' or 'failed'
|
||||
|
||||
**Document history**
|
||||
- List of all source_documents from PocketBase
|
||||
- Columns: filename, format, status, ingested_at, chunk_count
|
||||
Tabbed: **Sources** (upload + extraction), **Content** (review/refine generated
|
||||
content), **Quizzes**, **Curriculum** (generate/preview/activate a schedule),
|
||||
**Graph** (D3 knowledge graph + R42 suggestions queue), **Team** (manage members),
|
||||
**Settings** (per-tier model overrides, simulation toggle, smoke-test reset).
|
||||
|
||||
---
|
||||
|
||||
### Knowledge base page (`admin/knowledge/page.tsx`)
|
||||
## Design system
|
||||
|
||||
Lists all Themes with status indicator.
|
||||
|
||||
**Theme card**
|
||||
```
|
||||
[Theme title] [status badge: draft / published]
|
||||
N topics · from: filename.pdf
|
||||
[Approve batch] [Edit] [Reject]
|
||||
```
|
||||
|
||||
Approve batch:
|
||||
- Calls PocketBase to set theme.status → 'published', all child topics → 'published'
|
||||
- Triggers generation service: POST /generate-all with themeId
|
||||
- Shows toast: "Generation queued for N topics"
|
||||
|
||||
Reject:
|
||||
- Sets theme.status → 'rejected'
|
||||
- Removes from list
|
||||
|
||||
Edit → navigates to `/admin/knowledge/[themeId]`
|
||||
|
||||
**Theme detail page (`admin/knowledge/[themeId]/page.tsx`)**
|
||||
|
||||
Displays all Topics in the Theme as editable cards.
|
||||
|
||||
Topic card fields (all editable inline):
|
||||
- title (text input)
|
||||
- body (textarea — rich enough for paragraphs, no full rich text editor needed)
|
||||
- difficulty (select: introductory / intermediate / advanced)
|
||||
- key_terms (tag input — comma-separated)
|
||||
- related_topics (multi-select from published topics)
|
||||
- prerequisite_topics (multi-select)
|
||||
|
||||
Save button per card — calls PocketBase PATCH on the topic record.
|
||||
|
||||
Below topic list: [Approve batch] button — approves all topics in the theme.
|
||||
|
||||
**Micro learning generation status**
|
||||
After batch approval, show generation status per topic:
|
||||
|
||||
```
|
||||
Concept explainer ✓ published
|
||||
Scenario quiz ⏳ generating
|
||||
Comparison card ✓ published
|
||||
...
|
||||
```
|
||||
|
||||
Poll micro_learnings collection filtered by topic until all 10 are published.
|
||||
- CSS variables in `src/index.css`: colors (`--color-bg`, `--color-paper`,
|
||||
`--color-teal`, `--color-accent`), radii (`--r-sm`, `--r-lg`, `--r-org`).
|
||||
- Tailwind v4 utilities map to those variables (`bg-teal`, `text-fg-muted`,
|
||||
`border-bg-warm`, …). Avoid raw hex.
|
||||
- `stylesheet.css` (repo root) is the authoritative visual reference — frozen.
|
||||
- Reuse `src/components/ui/` primitives: `Card`, `Button`, `Tag`, `Input`.
|
||||
- Framer Motion for entry animations; mobile-first (target 375px).
|
||||
|
||||
---
|
||||
|
||||
### Curriculum page (`admin/curriculum/page.tsx`)
|
||||
## Build (`vite.config.js`)
|
||||
|
||||
**Current curriculum view**
|
||||
26 weeks displayed as a list. Each week shows:
|
||||
```
|
||||
Week 7
|
||||
[Theme: Holacratic roles]
|
||||
Topics: Role definitions · Circle structure · Lead link responsibilities
|
||||
Estimated: 25 min
|
||||
[Edit week] [Admin notes]
|
||||
```
|
||||
|
||||
**Regeneration banner**
|
||||
When a pending regeneration is queued:
|
||||
```
|
||||
⚠ 8 new topics added. A new curriculum version is ready to preview.
|
||||
[Preview changes] [Confirm regeneration] [Dismiss]
|
||||
```
|
||||
|
||||
Preview: shows proposed schedule with diff highlighting — weeks that changed
|
||||
are highlighted, weeks that stay the same are dimmed.
|
||||
|
||||
Confirm: calls POST /generate confirm on curriculum service →
|
||||
applies new version to all active employees.
|
||||
|
||||
**Drag-to-reorder**
|
||||
Each week row is draggable. Reordering calls PATCH /weeks/:weekId on the
|
||||
curriculum service to swap theme assignments.
|
||||
|
||||
**Admin notes**
|
||||
Inline text input per week — saved to curriculum_weeks.admin_notes.
|
||||
|
||||
---
|
||||
|
||||
## Employee app
|
||||
|
||||
### Shell layout (`app/layout.tsx`)
|
||||
|
||||
Bottom navigation bar (mobile-first):
|
||||
|
||||
```
|
||||
[Session] [Library] [Profile]
|
||||
```
|
||||
|
||||
R42 floating button: fixed position, bottom-right, above the nav bar.
|
||||
Z-index above all content.
|
||||
|
||||
### Session page (`app/session/page.tsx`)
|
||||
|
||||
**Week header**
|
||||
```
|
||||
Week 7 of 26 · Cycle 1
|
||||
[Theme title: Holacratic roles]
|
||||
[Progress bar: N of 26 weeks complete]
|
||||
```
|
||||
|
||||
**Topic list**
|
||||
Each topic in the week's theme rendered as a card:
|
||||
|
||||
```
|
||||
[Topic title]
|
||||
[difficulty badge] [estimated: 10 min]
|
||||
|
||||
Choose how to learn this topic:
|
||||
[Concept explainer] [Scenario quiz] [How-to] ...
|
||||
(only published types shown as buttons)
|
||||
|
||||
[Completed types: ✓ Concept explainer]
|
||||
```
|
||||
|
||||
Selecting a type opens the micro learning inline (no navigation — expands in
|
||||
place on mobile). Employee reads/completes it, then taps [Mark complete].
|
||||
|
||||
On mark complete:
|
||||
- POST to progress service `/complete`
|
||||
- Response displays: commits earned + any new badges as a toast notification
|
||||
- Topic card updates to show type as completed (✓)
|
||||
- All types in topic completable in one session
|
||||
|
||||
**Week complete state**
|
||||
When all topics in the week have at least one completed type:
|
||||
```
|
||||
🚀 Week 7 complete
|
||||
You earned N commits
|
||||
[Continue to Week 8]
|
||||
```
|
||||
|
||||
Continue button calls POST /advance/:userId on curriculum service.
|
||||
|
||||
---
|
||||
|
||||
### Micro learning components
|
||||
|
||||
One component per type in `components/micro-learnings/`.
|
||||
Each receives the `content` JSON field from the micro_learnings record.
|
||||
|
||||
| Component | Key interactions |
|
||||
|---|---|
|
||||
| ConceptExplainer | Render paragraphs + example — read only |
|
||||
| ScenarioQuiz | Select option → reveal explanation — stateful |
|
||||
| Misconceptions | Accordion: tap misconception to reveal correction |
|
||||
| HowTo | Numbered steps — tap step to check it off |
|
||||
| ComparisonCard | Two-column table — swipeable on mobile |
|
||||
| ReflectionPrompt | Open text area → reveal model answer on submit |
|
||||
| FlashcardSet | Flip card interaction — swipe through deck |
|
||||
| CaseStudy | Scenario text + open questions — read only |
|
||||
| GlossaryAnchor | Term card with definition + examples |
|
||||
| MythVsEvidence | Myth card → tap to reveal evidence |
|
||||
|
||||
All components are self-contained. They receive content JSON and emit an
|
||||
`onComplete` callback. They do not call any services directly.
|
||||
|
||||
```typescript
|
||||
type MicroLearningProps = {
|
||||
content: unknown // typed per component
|
||||
onComplete: () => void
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Knowledge library (`app/library/page.tsx`)
|
||||
|
||||
**Browse view**
|
||||
All published topics, grouped by Theme.
|
||||
Search input: filters by title and key_terms in real time (client-side).
|
||||
Filter chips: by difficulty (introductory / intermediate / advanced).
|
||||
|
||||
Each topic shown as a card:
|
||||
```
|
||||
[Topic title]
|
||||
[Theme] · [difficulty badge]
|
||||
[key terms as chips]
|
||||
```
|
||||
|
||||
Tap → navigate to topic detail.
|
||||
|
||||
**Topic detail (`app/library/[topicId]/page.tsx`)**
|
||||
|
||||
```
|
||||
[Topic title]
|
||||
[Theme] · [difficulty]
|
||||
|
||||
[Topic body — rendered as paragraphs]
|
||||
|
||||
Key terms: [chip] [chip] [chip]
|
||||
|
||||
Related topics: [card] [card]
|
||||
Prerequisite for: [card] [card]
|
||||
|
||||
How to learn this topic:
|
||||
[micro learning type buttons — same as session view]
|
||||
```
|
||||
|
||||
Completing a micro learning from the library records the completion via
|
||||
progress service. Week_number is set to the employee's current week.
|
||||
|
||||
---
|
||||
|
||||
### Profile page (`app/profile/page.tsx`)
|
||||
|
||||
**Header**
|
||||
```
|
||||
[Display name]
|
||||
[Level badge: Junior] [N commits]
|
||||
[Current streak: 5 weeks] [Longest: 8 weeks]
|
||||
```
|
||||
|
||||
**Heatmap**
|
||||
GitHub-style contribution graph.
|
||||
26 columns (weeks) × rows implied by completions per week.
|
||||
Cell colour: 0 completions = lightest, 5+ completions = darkest.
|
||||
Tap a cell → tooltip: "Week N · N completions".
|
||||
Scrollable horizontally on mobile if needed.
|
||||
|
||||
Implementation: render as SVG or CSS grid — no charting library required.
|
||||
|
||||
```typescript
|
||||
// Data from GET /profile/:userId → heatmap[]
|
||||
// Colour scale: 4 levels based on completions count
|
||||
// 0: var(--heatmap-0)
|
||||
// 1: var(--heatmap-1)
|
||||
// 2-3: var(--heatmap-2)
|
||||
// 4+: var(--heatmap-3)
|
||||
// Use CSS custom properties — values derived from stylesheet.css palette
|
||||
```
|
||||
|
||||
**Badges**
|
||||
Grid of earned badges. Unearned badges shown as locked (greyed out).
|
||||
Tap badge → tooltip with award condition.
|
||||
|
||||
```
|
||||
🥉 First commit ✓
|
||||
🥈 Five sessions ✓
|
||||
🥇 On a streak 🔒 (13 week streak needed)
|
||||
⭐ Shipped 🔒
|
||||
---
|
||||
🏷 Governance nerd ✓
|
||||
🏷 Deep reader 🔒 (3/5 case studies)
|
||||
```
|
||||
|
||||
**Leaderboard tab**
|
||||
Toggle between "My profile" and "Leaderboard".
|
||||
|
||||
Leaderboard: table of all employees from GET /leaderboard.
|
||||
Columns: Name · Commits · Streak · Types used · Badges · Level.
|
||||
Not ranked 1–N. No sorting by the user — display order is commits descending.
|
||||
Current employee row is highlighted.
|
||||
|
||||
**Activity feed tab**
|
||||
Third tab: "Feed".
|
||||
Milestone cards from GET /feed.
|
||||
Most recent first.
|
||||
|
||||
```
|
||||
🚀 Alex shipped the full curriculum
|
||||
26 weeks · 847 commits · 3 badges
|
||||
Longest streak: 18 weeks
|
||||
[timestamp]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## R42 chatbot components (`components/r42/`)
|
||||
|
||||
### R42Button
|
||||
|
||||
Fixed position, bottom-right, above bottom nav bar.
|
||||
Circle button with R42 label or icon.
|
||||
Tap → opens R42Drawer.
|
||||
|
||||
```tsx
|
||||
// Position: fixed, bottom: calc(nav-height + 16px), right: 16px
|
||||
// Z-index: above all content, below modals
|
||||
```
|
||||
|
||||
### R42Drawer
|
||||
|
||||
Slides up from bottom on mobile (sheet pattern).
|
||||
On desktop: expands to a side panel.
|
||||
|
||||
```
|
||||
[R42 header bar] [close ×]
|
||||
─────────────────────────────────────────────
|
||||
[Response area — scrollable]
|
||||
|
||||
Based on: [Holacratic roles ×] [Circle structure ×]
|
||||
─────────────────────────────────────────────
|
||||
[Type a question...] [Send →]
|
||||
```
|
||||
|
||||
**State machine:**
|
||||
```
|
||||
idle → loading (query sent) → streaming → done
|
||||
↘ out_of_scope
|
||||
```
|
||||
|
||||
**Streaming implementation:**
|
||||
|
||||
```typescript
|
||||
// POST /chat with fetch, read SSE stream
|
||||
const response = await fetch(`${CHAT_URL}/chat`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ query, userId })
|
||||
})
|
||||
|
||||
const reader = response.body!.getReader()
|
||||
const decoder = new TextDecoder()
|
||||
|
||||
while (true) {
|
||||
const { done, value } = await reader.read()
|
||||
if (done) break
|
||||
const lines = decoder.decode(value).split('\n')
|
||||
for (const line of lines) {
|
||||
if (!line.startsWith('data: ')) continue
|
||||
const event = JSON.parse(line.slice(6))
|
||||
if (event.type === 'chunk') appendText(event.text)
|
||||
if (event.type === 'citations') setCitations(event.topics)
|
||||
if (event.type === 'out_of_scope') setOutOfScope(event.text)
|
||||
if (event.type === 'done') setDone()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Citations**
|
||||
Rendered as tappable pills below the response.
|
||||
Tap → closes R42Drawer, navigates to `/app/library/[topicId]`.
|
||||
|
||||
**Out of scope response**
|
||||
Render as a muted message (not an error state):
|
||||
"This doesn't appear to be covered in the knowledge base.
|
||||
You can browse the full library in the Knowledge section."
|
||||
|
||||
**Stateless by design**
|
||||
Conversation cleared on drawer close. No history persisted.
|
||||
Input cleared on send.
|
||||
|
||||
---
|
||||
|
||||
## Mobile-first layout rules
|
||||
|
||||
All layout decisions start at 375px and scale up.
|
||||
|
||||
- Bottom navigation: fixed, height 56px, icons + labels
|
||||
- R42 button: 48px circle, positioned above nav bar
|
||||
- Session topic cards: full width, stack vertically
|
||||
- Micro learning components: full width, no horizontal scroll except
|
||||
ComparisonCard (swipeable)
|
||||
- Heatmap: horizontal scroll container on narrow screens
|
||||
- Leaderboard table: horizontally scrollable on mobile, sticky name column
|
||||
- Drawer/sheet pattern for R42 on mobile, side panel on desktop (breakpoint: 768px)
|
||||
- Tap targets: minimum 44×44px on all interactive elements
|
||||
- No hover-only interactions — all hover states have tap equivalents
|
||||
|
||||
---
|
||||
|
||||
## Environment variables
|
||||
|
||||
```
|
||||
NEXT_PUBLIC_POCKETBASE_URL=http://localhost:8090
|
||||
NEXT_PUBLIC_INGESTION_URL=http://localhost:3001
|
||||
NEXT_PUBLIC_GENERATION_URL=http://localhost:3002
|
||||
NEXT_PUBLIC_CURRICULUM_URL=http://localhost:3003
|
||||
NEXT_PUBLIC_CHAT_URL=http://localhost:3004
|
||||
NEXT_PUBLIC_PROGRESS_URL=http://localhost:3005
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Dependencies
|
||||
|
||||
```json
|
||||
{
|
||||
"dependencies": {
|
||||
"next": "14",
|
||||
"react": "^18",
|
||||
"react-dom": "^18",
|
||||
"pocketbase": "^0.21",
|
||||
"next-pwa": "^5",
|
||||
"zod": "^3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5",
|
||||
"tailwindcss": "^3",
|
||||
"autoprefixer": "^10",
|
||||
"postcss": "^8",
|
||||
"@types/react": "^18",
|
||||
"@types/node": "^20"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
No component library. No charting library. No drag-and-drop library —
|
||||
implement curriculum drag-to-reorder with native HTML5 drag API.
|
||||
The heatmap is SVG or CSS grid — no D3.
|
||||
|
||||
---
|
||||
|
||||
## TypeScript strict mode requirements
|
||||
|
||||
- No `any` types
|
||||
- All PocketBase collection responses typed against data-model.md schemas
|
||||
- All service API responses typed against response types from each service spec
|
||||
- Micro learning content JSON typed per type using discriminated union:
|
||||
|
||||
```typescript
|
||||
type MicroLearningContent =
|
||||
| { type: 'concept_explainer'; paragraphs: string[]; example: string }
|
||||
| { type: 'scenario_quiz'; scenario: string; options: QuizOption[] }
|
||||
| { type: 'misconceptions'; items: MisconceptionItem[] }
|
||||
// ... all 10 types
|
||||
```
|
||||
|
||||
- SSE event types as discriminated union
|
||||
- No implicit any on event handlers
|
||||
|
||||
---
|
||||
|
||||
## What the frontend does NOT do
|
||||
|
||||
- Does not run AI calls directly — all AI goes through backend services
|
||||
- Does not write to Qdrant — embedding is the ingestion service's responsibility
|
||||
- Does not implement auth logic — delegates entirely to PocketBase SDK
|
||||
- Does not implement curriculum generation — calls curriculum service
|
||||
|
||||
---
|
||||
|
||||
## Testing checkpoints
|
||||
|
||||
### Admin app
|
||||
1. Upload a PDF → ingestion job created → status polls and updates → done state shows link
|
||||
2. Theme batch appears after ingestion → approve → generation queued
|
||||
3. Edit a topic title and body → save → changes persisted in PocketBase
|
||||
4. Curriculum renders 26 weeks → drag week 3 and week 5 → order persists
|
||||
5. Regeneration banner appears → preview shows → confirm applies new version
|
||||
|
||||
### Employee app
|
||||
6. Login as employee → redirected to /app/session → correct week shown
|
||||
7. Select micro learning type → content renders → mark complete → commits toast shown
|
||||
8. Complete all topics in week → week complete state shown → advance to next week
|
||||
9. Library browse → search filters results → topic detail renders body + related topics
|
||||
10. Profile page → heatmap renders for current cycle → badges show locked/unlocked state
|
||||
11. Leaderboard tab → all employees shown → current employee row highlighted
|
||||
12. R42 button visible on every screen → opens drawer → question answered with citations
|
||||
13. R42 citation tap → navigates to correct topic in library
|
||||
14. Out-of-scope question → muted message shown, no citations
|
||||
15. All screens render correctly at 375px width — no horizontal overflow except
|
||||
intentional scroll containers
|
||||
- Injects `__BUILD_SHA__` / `__BUILD_TIME__` (shown by `BuildStamp`).
|
||||
- Dev server proxies `/api/anthropic` to Anthropic and injects `ANTHROPIC_API_KEY`.
|
||||
- Source maps on; production build outputs to `dist/`.
|
||||
|
||||
Reference in New Issue
Block a user