first commit

This commit is contained in:
Justin Visser 2026-03-02 20:09:27 +01:00
commit b47f825d54
83 changed files with 10016 additions and 0 deletions

214
prompts/slice-7-polish.md Normal file
View file

@ -0,0 +1,214 @@
# Slice 7: Polish + Final Validation
## Role
You are a senior developer performing a final quality sweep on an academic group submission website. The backend (FastAPI + SQLite) and frontend (Vue 3 + TypeScript + Tailwind CSS v4) are fully implemented through Slices 06. Your job is to run every verification check, fix any issues found, and ensure the app works end-to-end.
## Objective
Run the full verification suite, fix any failures, and validate the complete user flow. The app must pass all automated checks with zero errors and work correctly in Docker.
## Architecture Reference
Read [PLAN.md](../PLAN.md) for full architecture context. Key deviations from the original plan:
### Layout
The layout uses a **horizontal top bar with tabs** (not a sidebar):
- `AppShell.vue` — sticky header (title bar `bg-primary-300` + nav bar) + dark mode toggle + TokenPrompt + AdminToolbar + back-to-top button + main content slot
- `SidebarNav.vue` — horizontal tab bar (desktop, `md+`)
- `MobileHeader.vue` — hamburger dropdown (mobile, `<md`)
- The entire header (title bar + nav) is `sticky top-0 z-20` — always visible when scrolling
- A floating back-to-top button appears after scrolling 300px (bottom-right, `bg-primary-300`)
### Backend Routes
- Collection routes use **trailing slashes** (`/api/segments/`, `/api/site/`, `/api/assets/`)
- Single-resource routes have **no trailing slash** (`/api/segments/{id}`, `/api/auth/verify`)
- Segment updates use **PATCH** (not PUT) — partial updates only
### Segment Types
Seven types: `markdown`, `pdf`, `video`, `audio`, `iframe`, `gallery`, `link`.
- `link` is nav-only — appears in tab bar/mobile menu as an external `<a target="_blank">`, not in the content area.
### Site Title
- Configurable via `HANDIN_SITE_TITLE` env var (default: `"Untitled Site"`)
- Stored in `config.py` as `Settings.site_title`, passed to `site_service.get_site_title()` as `default` kwarg
- Can also be overridden at runtime via `PUT /api/site/` (stored in DB, takes precedence over env var)
### Admin UI
- Lock icon in title bar → inline token input → admin controls appear
- Header icons (lock, dark mode toggle) use `text-slate-700` in both light and dark mode (yellow header stays consistent)
- AdminToolbar below tabs: "Editing" badge + "Add Segment" (modal) + "Logout"
- Inline SegmentEditor below each card with type-aware editing
- Grip-dot drag handles for reorder via `vuedraggable`
- Custom styled delete confirmation modal (not browser `confirm()`)
## Step 1: Backend Verification
Run these checks and fix any failures:
```bash
# Tests
uv run pytest backend/tests/ -v
# Linting
uv run ruff check backend/
# Type checking
uv run mypy --strict backend/app/
```
All must pass with zero errors. If any tests fail:
1. Read the failing test to understand what it expects
2. Read the relevant source code
3. Fix the source (not the test) unless the test itself is wrong
4. Re-run until clean
### Known Backend Files
```
backend/app/
├── main.py
├── config.py # Settings: admin_token, site_title, data_dir, etc.
├── auth.py
├── models.py # SegmentType enum includes LINK = "link"
├── database.py
├── routes/
│ ├── auth.py
│ ├── segments.py
│ ├── site.py # Passes settings.site_title as default to service
│ └── assets.py
└── services/
├── segment_service.py
├── site_service.py # get_site_title(db_path, *, default=DEFAULT_SITE_TITLE)
└── asset_service.py
```
## Step 2: Frontend Verification
```bash
cd frontend && npm run build
```
This runs `vue-tsc --noEmit && vite build`. Must succeed with zero errors.
### Known Frontend Files
```
frontend/src/
├── composables/
│ ├── useSegments.ts
│ ├── useAdmin.ts # Module-level reactive state, sessionStorage
│ ├── useAssetUpload.ts
│ └── useDarkMode.ts # Class-based dark mode, localStorage persistence, system preference fallback
├── types/
│ ├── segment.ts # SegmentType includes "link"
│ └── vuedraggable.d.ts
├── components/
│ ├── layout/
│ │ ├── AppShell.vue # Sticky header, back-to-top button, scroll listener
│ │ ├── SidebarNav.vue # Actually horizontal tab bar
│ │ └── MobileHeader.vue # Hamburger dropdown
│ ├── segments/
│ │ ├── SegmentList.vue # Draggable (admin) or plain (read-only), filters out link segments from content
│ │ ├── SegmentRenderer.vue
│ │ ├── MarkdownSegment.vue
│ │ ├── PdfSegment.vue
│ │ ├── VideoSegment.vue
│ │ ├── AudioSegment.vue
│ │ ├── IframeSegment.vue
│ │ └── GallerySegment.vue
│ └── admin/
│ ├── TokenPrompt.vue
│ ├── AdminToolbar.vue
│ ├── SegmentEditor.vue
│ ├── AssetUploader.vue
│ └── AddSegmentModal.vue
└── views/
└── HomePage.vue
```
## Step 3: Docker Build + Smoke Test
```bash
docker compose up --build -d
```
Must build and start without errors. Then verify:
```bash
# Health check — site loads
curl -s http://localhost:8000/api/site/ | python3 -c "import sys,json; d=json.load(sys.stdin); print(d)"
# Auth verify works
curl -s http://localhost:8000/api/auth/verify -H "Authorization: Bearer changeme" | python3 -c "import sys,json; d=json.load(sys.stdin); assert d['valid']==True; print('Auth OK')"
# Create a test segment
curl -s -X POST http://localhost:8000/api/segments/ \
-H "Authorization: Bearer changeme" \
-H "Content-Type: application/json" \
-d '{"type":"markdown","title":"Test","content":"# Hello"}' | python3 -c "import sys,json; d=json.load(sys.stdin); print(f'Created segment {d[\"id\"]}')"
# Create a link segment
curl -s -X POST http://localhost:8000/api/segments/ \
-H "Authorization: Bearer changeme" \
-H "Content-Type: application/json" \
-d '{"type":"link","title":"GitHub","content":"https://github.com"}' | python3 -c "import sys,json; d=json.load(sys.stdin); print(f'Created link {d[\"id\"]}')"
# List segments
curl -s http://localhost:8000/api/segments/ | python3 -c "import sys,json; d=json.load(sys.stdin); print(f'{len(d)} segments')"
# Frontend loads
curl -s http://localhost:8000/ | head -5
```
Clean up test data after verification:
```bash
docker compose down
```
## Step 4: Edge Case Review
Check these specific scenarios and fix if broken:
1. **Empty state**: With no segments, the page should show "No content yet" (not crash or show blank).
2. **Link segments in read-only mode**: Should appear in nav tabs but NOT in the content area.
3. **Link segments in admin mode**: Should appear in the draggable list with URL displayed, editable via SegmentEditor.
4. **Gallery segment with no images**: Should not crash — show empty state or just the upload zone.
5. **Admin toolbar visibility**: Must be completely hidden when not authenticated.
6. **Token persistence**: After page reload, if token was valid, admin state should restore from sessionStorage.
7. **Concurrent safety**: Two simultaneous segment creates should not corrupt sort_order.
8. **Site title env var**: With `HANDIN_SITE_TITLE=MyCourseName`, the default title should be "MyCourseName" (not "Untitled Site"). Once overridden via `PUT /api/site/`, the DB value takes precedence.
## Step 5: Visual Polish Check
Review the Tailwind classes in these components for consistency:
1. **Spacing**: All segment cards should have consistent padding (`p-6`), gap between cards (`space-y-6`).
2. **Colors**: Primary color usage should be consistent — `bg-primary-300` for brand, `primary-50`/`primary-100` for editor backgrounds, `primary-400` for hover states. The header bar keeps its yellow `bg-primary-300` in both light and dark mode (university branding).
3. **Typography**: Title bar uses `text-xl font-bold`, segment titles use `text-xl font-semibold`, body text uses default size.
4. **Responsive**: Tab bar hidden on mobile, mobile hamburger hidden on desktop. Content area is `max-w-6xl` centered.
5. **Shadows**: Segment cards use `shadow-md` (+ `dark:shadow-slate-950/30` in dark mode).
6. **Dark mode**: All components have `dark:` variant classes. Toggle is a sun/moon icon in the title bar. Uses class-based dark mode via `@custom-variant dark (&:where(.dark, .dark *))` in style.css. Preference stored in localStorage, falls back to system preference.
7. **Sticky header**: Entire header (title bar + nav) sticks to top on scroll. Back-to-top button appears after 300px scroll.
8. **Admin controls**: Edit pencil icons should be `text-slate-300 hover:text-primary-300` (muted by default). Drag handles should match.
9. **Header icons**: Lock icon and dark mode toggle use `text-slate-700` (no dark: overrides) since they sit on the always-yellow header.
## Verification Checklist
All of these must be true when you're done:
- [ ] `uv run pytest backend/tests/ -v` — all pass
- [ ] `uv run ruff check backend/` — clean
- [ ] `uv run mypy --strict backend/app/` — clean
- [ ] `cd frontend && npm run build` — zero errors
- [ ] `docker compose up --build` — builds and runs
- [ ] Dark mode toggle works, persists across reload, respects system preference on first visit
- [ ] Header bar stays yellow (`bg-primary-300`) in both light and dark mode
- [ ] Sticky header stays visible when scrolling, back-to-top button appears
- [ ] `HANDIN_SITE_TITLE` env var sets the default site title
- [ ] Read-only mode shows zero admin UI (only lock icon + dark mode toggle in header)
- [ ] Admin mode: all CRUD operations work
- [ ] Link segments appear in nav only
- [ ] Drag-and-drop reorder persists after refresh
## Files You May Modify
Any file in the project. Prefer minimal, targeted fixes. Do not refactor working code unnecessarily.