# Phase 7: Quality of Life
**Status:** ✅ Complete
**Parent plan:** [PLAN.md](./PLAN.md)
**Domain:** fullstack
## Objective
Implement 4 quality-of-life features: onboarding wizard, app URL health preview, board templates, and keyboard shortcut overlay.
## Tasks
### 7.1 Onboarding Wizard
- [x] Create `src/lib/components/onboarding/OnboardingWizard.svelte`
- Full-screen overlay triggered on first launch (no users in DB or onboardingComplete=false in SystemSettings)
- Steps with progress indicator:
1. **Welcome** — intro text, app branding
2. **Create Admin Account** — email, password, display name form
3. **Auth Mode** — choose local/oauth/both, configure OAuth if selected
4. **Theme & Background** — pick theme mode, primary color, background type
5. **Add First Apps** — manual add form OR auto-discover button (if Docker available)
6. **Create First Board** — name, pick a template or start blank
- Skippable steps for advanced users
- Stores completion in SystemSettings.onboardingComplete
- [x] Create `src/routes/api/onboarding/+server.ts`
- POST: complete step (validates, creates entities)
- GET: check onboarding status
- [x] Create `src/lib/server/services/onboardingService.ts`
- `isOnboardingNeeded()` — check if any users exist and if onboarding is complete
- `completeOnboarding()` — mark SystemSettings.onboardingComplete = true
- [x] Add onboarding check to root layout server load function
- If onboarding needed, pass flag to layout → show wizard
### 7.2 App URL Health Preview
- [x] Create `src/lib/components/app/AppUrlPreview.svelte`
- "Test Connection" button in app create/edit form
- On click: calls backend to test the URL
- Shows: HTTP status code, response time (ms), auto-detected favicon URL, page title
- If no icon selected, offers to use the detected favicon
- If no name entered, offers to use the detected page title
- Loading state while testing, error state on failure
- [x] Create `src/routes/api/apps/preview/+server.ts`
- POST with `{ url }` body
- Server-side fetch: HEAD request for status/timing, GET for HTML parsing
- Extract: favicon (from `` or `/favicon.ico`), page title (from `
`)
- Return: `{ status, responseTime, favicon, title }`
- Timeout: 10s, handle errors gracefully
- [x] Integrate preview into existing AppForm.svelte (add preview section below URL input)
### 7.3 Board Templates
- [x] Create `src/lib/server/services/templateService.ts`
- `getBuiltinTemplates()` — return hardcoded built-in templates
- `getUserTemplates(userId)` — custom templates from DB
- `createTemplate(input)` — save board layout as template
- `applyTemplate(templateId, boardId)` — create sections from template config
- `exportTemplate(boardId)` — export board layout as JSON
- `importTemplate(json)` — import template from JSON
- [x] Create built-in templates (hardcoded in service):
- "Home Server" — sections: Media, Networking, Storage, Monitoring
- "Media Stack" — sections: Streaming, Downloads, Management
- "Dev Tools" — sections: Git, CI/CD, Databases, Docs
- "Monitoring" — sections: Metrics, Logs, Alerts, Status
- [x] Create `src/lib/components/board/TemplatePicker.svelte`
- Grid of template cards (icon, name, description, section preview)
- Built-in templates + user-created templates
- Click to select → creates board with template sections
- "Blank Board" option
- "Import Template" button (file upload JSON)
- [x] Create `src/routes/api/templates/+server.ts` — GET (list), POST (create from board)
- [x] Create `src/routes/api/templates/[id]/+server.ts` — GET (single), DELETE
- [x] Create `src/routes/api/templates/import/+server.ts` — POST (import JSON)
- [x] Integrate TemplatePicker into board creation flow
### 7.4 Keyboard Shortcut Overlay
- [x] Create `src/lib/components/ui/KeyboardShortcutOverlay.svelte`
- Modal triggered by pressing `?` key
- Context-aware sections:
- **Global:** Cmd/Ctrl+K (search), ? (shortcuts), 1-9 (switch board), f (toggle favorites)
- **Board View:** j/k (navigate apps), Enter (open selected), e (edit mode)
- **Admin:** (admin-specific shortcuts if any)
- Organized in categorized columns
- Close on Escape or clicking outside
- Small `?` hint icon in footer
- [x] Create `src/lib/stores/keyboard.svelte.ts`
- Register global keyboard listeners
- j/k navigation: track selected app index in current board
- Enter: open selected app URL
- 1-9: switch to board by index
- f: toggle favorites bar visibility
- e: toggle board edit mode
- ?: show shortcut overlay
- Disable shortcuts when input/textarea is focused
- [x] Integrate keyboard store into root layout
- [x] Add `?` hint icon to footer component
## Files to Modify/Create
- `src/lib/components/onboarding/OnboardingWizard.svelte` — new
- `src/routes/api/onboarding/+server.ts` — new
- `src/lib/server/services/onboardingService.ts` — new
- `src/lib/components/app/AppUrlPreview.svelte` — new
- `src/routes/api/apps/preview/+server.ts` — new
- `src/lib/components/app/AppForm.svelte` — modify (add preview)
- `src/lib/server/services/templateService.ts` — new
- `src/lib/components/board/TemplatePicker.svelte` — new
- `src/routes/api/templates/+server.ts` — new
- `src/routes/api/templates/[id]/+server.ts` — new
- `src/routes/api/templates/import/+server.ts` — new
- `src/lib/components/ui/KeyboardShortcutOverlay.svelte` — new
- `src/lib/stores/keyboard.svelte.ts` — new
- `src/routes/+layout.svelte` — modify (onboarding check, keyboard store)
- `src/routes/+layout.server.ts` — modify (onboarding status)
## Acceptance Criteria
- Onboarding wizard triggers on first launch, creates admin user and basic setup
- Steps can be skipped, wizard can be completed partially
- App URL preview shows status, timing, favicon, and title extraction
- Board templates create correct section structure when applied
- Built-in templates are always available (not stored in DB)
- Template import/export produces valid JSON that can round-trip
- Keyboard shortcuts work globally, disabled in text inputs
- Shortcut overlay shows context-appropriate shortcuts
- All features work in both dark and light mode
## Notes
- Onboarding detection: simplest approach is checking User count === 0
- URL preview: use node's fetch with timeout, parse HTML response for favicon/title
- Board templates: config JSON structure: `{ sections: [{ title, icon, order }] }`
- Keyboard navigation: use data attributes on app widgets to track position
- The `?` shortcut must not interfere with typing in inputs/textareas
## Review Checklist
- [x] All tasks completed
- [x] Code follows project conventions
- [ ] No unintended side effects
- [ ] Build passes (Big Bang: code quality check only)
- [ ] Tests pass (Big Bang: skipped for intermediate phase)
## Handoff to Next Phase
### What was done
- **7.1 Onboarding Wizard**: Created `OnboardingWizard.svelte` with 5-step full-screen overlay (Welcome, Create Admin, Auth Mode, Theme, Create Board). Created `onboardingService.ts` with `isOnboardingNeeded()`, `completeOnboarding()`, and `getOnboardingStatus()`. Created `/api/onboarding` route with GET (status check) and POST (step completion with per-step Zod validation). Added `onboardingNeeded` flag to root layout server load. Wizard renders as fixed overlay in `+layout.svelte` when flag is true.
- **7.2 App URL Health Preview**: Created `AppUrlPreview.svelte` with "Test Connection" button showing HTTP status, response time, favicon preview, and page title extraction. Offers "Use as name" and "Use as icon" buttons when fields are empty. Created `/api/apps/preview` route with server-side HEAD + GET requests, 10s timeout, HTML parsing (first 64KB), favicon extraction from `` or `/favicon.ico` fallback. Integrated into `AppForm.svelte` below the URL input field.
- **7.3 Board Templates**: Created `templateService.ts` with 4 built-in templates (Home Server, Media Stack, Dev Tools, Monitoring) hardcoded as constants, plus CRUD for user templates via `BoardTemplate` Prisma model. Supports `getBuiltinTemplates()`, `getAllTemplates()`, `createTemplate()`, `applyTemplate()`, `exportTemplate()`, and `importTemplate()`. Created 3 API routes: `/api/templates` (GET list, POST create), `/api/templates/[id]` (GET, DELETE), `/api/templates/import` (POST). Created `TemplatePicker.svelte` with grid UI showing blank board + all templates with section previews and JSON file import. Integrated into board creation page with hidden `templateId` input and server-side `applyTemplate()` call after board creation.
- **7.4 Keyboard Shortcut Overlay**: Created `keyboard.svelte.ts` store with global keydown listener, input-focus detection, j/k app navigation (using `data-app-widget` attributes), Enter to open selected, 1-9 board switching (via sidebar link click), `f` for favorites toggle (custom event), `e` for edit mode toggle, `?` for overlay toggle. Created `KeyboardShortcutOverlay.svelte` modal with categorized shortcuts (Global + Board View). Added `data-keyboard-selected` CSS rule to `app.css` for visual selection ring. Added `?` hint icon button to Sidebar footer next to collapse toggle. Integrated keyboard store init/destroy into root `+layout.svelte`.
### What the next phase needs to know
- The onboarding wizard is a simple full-screen overlay that blocks the UI — it does NOT redirect. Once completed, the page needs a full reload (or `invalidateAll()`) to re-evaluate `onboardingNeeded`.
- The wizard has 5 steps (Welcome, Admin, Auth, Theme, Complete) — the original plan had 6 steps but "Add First Apps" was consolidated into the board creation step for simplicity.
- The onboarding API has NO authentication requirement (since it runs before any user exists).
- The URL preview endpoint requires authentication and returns `{ status, responseTime, favicon, title, error }`.
- Built-in templates use `builtin-` prefixed IDs and are not stored in the database. The `deleteTemplate` function blocks deletion of builtins.
- Template application in board creation uses `request.clone().formData()` to read the `templateId` hidden input alongside the superforms data.
- The keyboard store's `init()` must be called from a component context (it adds a global `keydown` listener). `destroy()` must be called in `onDestroy`.
- j/k navigation relies on elements having `data-app-widget` attribute — this needs to be added to `AppWidget.svelte` in Phase 8 integration.
- The `f` shortcut dispatches a `toggle-favorites` custom event on `window` — the FavoritesBar or board page needs to listen for this.
- The `e` shortcut toggles `keyboard.editMode` state — board components can read this to enter/exit edit mode.
### Potential concerns
- The onboarding wizard completes steps via sequential API calls — if the browser is closed mid-wizard, partial state (e.g., admin created but onboarding not marked complete) can occur. The wizard handles this by checking `adminCreated` state and skipping re-creation.
- The URL preview endpoint makes server-side HTTP requests to arbitrary URLs, which could be used for SSRF. Consider adding URL validation (e.g., blocking private IP ranges) in a security review.
- Template import accepts arbitrary JSON — validation checks structure but does not limit section count or title length beyond Zod schema bounds.
- The keyboard store adds a `keydown` listener on `window` — if multiple layout instances exist (unlikely), listeners could duplicate. The `init()` method guards against this.
- Board creation page now reads `request` twice (superValidate + manual formData) — uses `request.clone()` to avoid "body already consumed" errors.