# 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 `<link rel="icon">` 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.