handin-website/prompts/slice-7-polish.md
2026-03-02 20:09:27 +01:00

9.7 KiB
Raw Blame History

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 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:

# 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

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:

  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.