9.7 KiB
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 0–6. 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 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 barbg-primary-300+ nav bar) + dark mode toggle + TokenPrompt + AdminToolbar + back-to-top button + main content slotSidebarNav.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.
linkis 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_TITLEenv var (default:"Untitled Site") - Stored in
config.pyasSettings.site_title, passed tosite_service.get_site_title()asdefaultkwarg - 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-700in 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:
# 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:
- Read the failing test to understand what it expects
- Read the relevant source code
- Fix the source (not the test) unless the test itself is wrong
- 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
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
docker compose up --build -d
Must build and start without errors. Then verify:
# 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:
docker compose down
Step 4: Edge Case Review
Check these specific scenarios and fix if broken:
- Empty state: With no segments, the page should show "No content yet" (not crash or show blank).
- Link segments in read-only mode: Should appear in nav tabs but NOT in the content area.
- Link segments in admin mode: Should appear in the draggable list with URL displayed, editable via SegmentEditor.
- Gallery segment with no images: Should not crash — show empty state or just the upload zone.
- Admin toolbar visibility: Must be completely hidden when not authenticated.
- Token persistence: After page reload, if token was valid, admin state should restore from sessionStorage.
- Concurrent safety: Two simultaneous segment creates should not corrupt sort_order.
- Site title env var: With
HANDIN_SITE_TITLE=MyCourseName, the default title should be "MyCourseName" (not "Untitled Site"). Once overridden viaPUT /api/site/, the DB value takes precedence.
Step 5: Visual Polish Check
Review the Tailwind classes in these components for consistency:
- Spacing: All segment cards should have consistent padding (
p-6), gap between cards (space-y-6). - Colors: Primary color usage should be consistent —
bg-primary-300for brand,primary-50/primary-100for editor backgrounds,primary-400for hover states. The header bar keeps its yellowbg-primary-300in both light and dark mode (university branding). - Typography: Title bar uses
text-xl font-bold, segment titles usetext-xl font-semibold, body text uses default size. - Responsive: Tab bar hidden on mobile, mobile hamburger hidden on desktop. Content area is
max-w-6xlcentered. - Shadows: Segment cards use
shadow-md(+dark:shadow-slate-950/30in dark mode). - 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. - Sticky header: Entire header (title bar + nav) sticks to top on scroll. Back-to-top button appears after 300px scroll.
- Admin controls: Edit pencil icons should be
text-slate-300 hover:text-primary-300(muted by default). Drag handles should match. - 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 passuv run ruff check backend/— cleanuv run mypy --strict backend/app/— cleancd frontend && npm run build— zero errorsdocker 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_TITLEenv 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.