# Web App Launcher — Implementation Plan Prompt ## Project Overview Build a **self-hosted web application launcher / dashboard** for a TrueNAS server environment. The app serves as a centralized portal to organize, discover, and navigate to self-hosted services (Plex, Nextcloud, Gitea, Home Assistant, etc.) via customizable boards with live health indicators. **Repository:** `https://git.dolgolyov-family.by/alexei.dolgolyov/web-app-launcher` **Git user:** `alexei.dolgolyov` / `dolgolyov.alexei@gmail.com` (Gitea instance) --- ## Tech Stack ### Framework (Full-Stack) - **SvelteKit** — all-in-one framework: SSR, routing, API routes (`+server.ts`), form actions — single process, no separate backend needed - **Svelte 5** (runes mode) — `$state`, `$derived`, `$effect` for reactive state; compiler-based, no virtual DOM, minimal runtime - **TypeScript** — strict mode throughout ### UI & Styling - **Tailwind CSS v4** — utility-first styling with smooth animation support - **shadcn-svelte** (Bits UI primitives) — accessible, unstyled component library; each component is a separate file - **Svelte built-in transitions** — `transition:`, `animate:`, `in:/out:` directives for page transitions, expand/collapse, hover effects - **svelte/motion** — `tweened` and `spring` stores for ambient background animations ### Data & State - **SvelteKit load functions** — server-side data loading with automatic invalidation - **Svelte runes** (`$state`, `$derived`) — client-side reactive state (theme, sidebar, UI) - **Superforms + Zod** — type-safe form handling with progressive enhancement and server-side validation - **Prisma ORM** — type-safe database access, migrations, seeding - **SQLite** — zero-config, file-based, perfect for single-server deployment (easy Docker volume mount, simple backups). Migrate to PostgreSQL later if needed. ### Auth - **openid-client** — Authentik OIDC/OAuth2 integration - **bcrypt + JWT** — local auth with refresh token rotation via HTTP-only cookies - **SvelteKit hooks** (`handle`) — auth middleware, session management ### Icons - **Lucide Svelte** — 1500+ clean SVG icons for UI chrome - **Simple Icons** (via `simple-icons` npm package) — 3000+ brand/service icons (Plex, Nextcloud, Docker, Grafana, etc.) — perfect for self-hosted app logos - **Dashboard Icons** (CDN fallback) — community-maintained self-hosted app icon set - **Custom upload** — allow users to upload SVG/PNG icons as a fallback - **No emojis** — strictly SVG/image icons only ### Background Jobs - **node-cron** — scheduled healthcheck pings (runs in SvelteKit server process) ### DevOps - **Docker** — multi-stage build (SvelteKit build → Node adapter → lightweight runtime) - **docker-compose.yml** — single-command deployment with volume mounts for SQLite + uploads - **Gitea Actions** — CI/CD workflows (lint, type-check, test, Docker image push to Gitea Container Registry) --- ## Functional Requirements ### 1. Authentication & Authorization #### Auth Modes (Admin-Configurable) The system supports three auth modes, selectable by admin in settings: - **OAuth only** — all users authenticate via Authentik (OIDC/OAuth2) - **Local only** — email/password login with optional registration - **Both** — user chooses OAuth or local login on the login page - **Guest mode** — unauthenticated users see boards marked as `guest-accessible` #### User Management - Admin can create/edit/delete users manually - Self-registration is **disabled by default**; admin toggles it on/off - OAuth auto-provisions users on first login (maps Authentik groups to local groups) - Users have: `id`, `email`, `displayName`, `avatarUrl`, `authProvider`, `role`, `groupIds[]` #### Groups & Access Control - **Default groups:** `admin`, `user` - Admin can create custom groups - Permissions are hierarchical: **User-level overrides > Group-level > Default** - Permission model per entity (board, section, app): - `view` — can see the entity - `edit` — can modify the entity - `admin` — full control (delete, manage access) - Guest access is a separate boolean flag per board #### Session Management - JWT access tokens stored in HTTP-only cookies (managed by SvelteKit hooks) - Refresh token rotation (7-day expiry) - Server-side session validation in `hooks.server.ts` - Logout invalidates refresh token ### 2. Apps (Service Registry) Each app represents a self-hosted service: - **url** (required) — base URL of the service - **name** (required) — display name - **icon** — one of: Simple Icons slug, Lucide icon name, Dashboard Icons ID, or uploaded image path - **description** (optional) — short text shown on hover/expand - **category/tags** (optional) — for filtering/search - **healthcheck** configuration: - `enabled` (default: true) - `interval` (default: 60s, min: 10s, max: 3600s) - `method` — HTTP HEAD/GET to the URL (or custom endpoint) - `expectedStatus` — default 200, configurable (e.g., 401 is "alive" for auth-protected services) - `timeout` — max wait before marking as down (default: 5s) - **Status**: `online | offline | degraded | unknown` — derived from healthcheck results - Backend runs healthchecks on a per-app schedule; results are cached and pushed to frontend via polling (SvelteKit invalidation) or SSE ### 3. Boards Boards are the primary organizational unit — each board is a full-page layout of sections and widgets. #### Board Properties - `name`, `icon`, `description` - `accessLevel` — per-user, per-group, guest-accessible (boolean) - `isDefault` — one board can be marked as the landing page - `layout` — grid-based responsive layout - `backgroundConfig` — ambient background settings (see Appearance) #### Sections (Groups) - Collapsible/expandable containers within a board - Properties: `title`, `icon`, `isExpanded` (default state), `order` - Contain an ordered list of widgets - Smooth expand/collapse animation (Svelte `slide` transition) #### Widgets Widgets are the atomic content units inside sections. **App Widget (MVP):** - Displays app icon, name, status indicator (colored dot/ring), optional description - Click opens the app URL in a new tab - Hover shows description tooltip + last healthcheck time - Visual states: online (green pulse), offline (red), degraded (yellow), unknown (gray) **Future widget types (post-MVP, design the schema to support them):** - **Bookmark widget** — simple URL + label (no healthcheck) - **Note widget** — rich text or markdown note - **Embed widget** — iframe embed of a service - **Status widget** — aggregated status of multiple apps ### 4. Search & Navigation - Global search bar (Cmd/Ctrl+K) — searches across all accessible apps and boards - Keyboard navigation support - Sidebar with board list (collapsible) - Breadcrumb navigation within nested views ### 5. Admin Panel - User management (CRUD, group assignment) - Group management (CRUD, permission templates) - App management (CRUD, healthcheck config, bulk import/export) - Board management (CRUD, access control) - System settings: - Auth mode selection - Registration toggle - OAuth provider configuration (client ID, secret, discovery URL) - Default theme / primary color - Healthcheck global defaults --- ## Non-Functional Requirements ### Appearance & UX - **Modern, clean design** — inspired by Homarr / Heimdall / Organizr but with smoother polish - **Ambient animated backgrounds** — subtle mesh gradient, particle field, or aurora effect (configurable, can be disabled); implemented with Svelte `tweened`/`spring` + CSS/Canvas - **Customizable primary color** — HSL-based theme system; admin sets default, users can override - **Dark / Light / System theme** — with smooth CSS transition - **Smooth animations everywhere** (using Svelte built-in transitions): - Page transitions (`in:fade`, `out:fly`) - Section expand/collapse (`transition:slide`) - Card hover effects (subtle scale + shadow lift via CSS + Svelte `spring`) - Status indicator pulse (CSS `@keyframes`) - Skeleton loading states - **Responsive** — works on desktop, tablet, and mobile - **No emojis as icons** — use SVG icon libraries exclusively ### File Structure (Modularity) SvelteKit route-based structure with one component per file: ``` src/ routes/ +layout.svelte # Root layout (sidebar, header, ambient bg) +layout.server.ts # Root auth check, load user session +page.svelte # Dashboard / default board redirect login/ +page.svelte # Login page +page.server.ts # Login form action register/ +page.svelte # Registration page (if enabled) +page.server.ts # Register form action auth/ oauth/ authorize/+server.ts # Redirect to Authentik callback/+server.ts # Handle OAuth callback refresh/+server.ts # Token refresh boards/ +page.svelte # Board list +page.server.ts # Load boards (filtered by permissions) [boardId]/ +page.svelte # Board view +page.server.ts # Load board with sections/widgets edit/ +page.svelte # Board editor +page.server.ts # Board update actions apps/ +page.svelte # App registry list +page.server.ts # Load apps [appId]/ +server.ts # App CRUD API status/+server.ts # Healthcheck status API admin/ +layout.svelte # Admin layout (admin-only guard) +layout.server.ts # Admin auth check users/ +page.svelte # User management +page.server.ts # User CRUD actions groups/ +page.svelte # Group management +page.server.ts # Group CRUD actions settings/ +page.svelte # System settings +page.server.ts # Settings update actions api/ apps/+server.ts # App CRUD REST endpoints apps/[id]/+server.ts # Single app operations apps/[id]/status/+server.ts # Healthcheck status boards/+server.ts # Board CRUD boards/[id]/+server.ts # Single board operations boards/[id]/sections/+server.ts # Section CRUD boards/[id]/sections/[sid]/+server.ts # Single section boards/[id]/sections/[sid]/widgets/+server.ts # Widget CRUD users/+server.ts # User management (admin) groups/+server.ts # Group management (admin) admin/settings/+server.ts # System settings (admin) search/+server.ts # Global search health/+server.ts # App healthcheck endpoint lib/ components/ ui/ # shadcn-svelte primitives (Button, Dialog, etc.) layout/ Sidebar.svelte Header.svelte MainLayout.svelte ThemeToggle.svelte board/ Board.svelte BoardHeader.svelte BoardCard.svelte section/ Section.svelte SectionHeader.svelte SectionCollapsible.svelte widget/ AppWidget.svelte AppWidgetStatus.svelte WidgetContainer.svelte WidgetGrid.svelte app/ AppForm.svelte AppIconPicker.svelte AppHealthBadge.svelte AppCard.svelte auth/ LoginForm.svelte OAuthButton.svelte RegisterForm.svelte admin/ UserTable.svelte GroupTable.svelte SettingsForm.svelte PermissionEditor.svelte search/ SearchDialog.svelte SearchResult.svelte SearchTrigger.svelte background/ AmbientBackground.svelte MeshGradient.svelte ParticleField.svelte AuroraEffect.svelte server/ services/ authService.ts appService.ts boardService.ts healthcheckService.ts userService.ts groupService.ts permissionService.ts middleware/ authenticate.ts authorize.ts guestAccess.ts jobs/ healthcheckScheduler.ts utils/ jwt.ts password.ts iconResolver.ts stores/ theme.svelte.ts # Svelte 5 rune-based store ui.svelte.ts search.svelte.ts utils/ constants.ts validators.ts helpers.ts types/ auth.ts app.ts board.ts widget.ts user.ts group.ts permission.ts hooks.server.ts # Auth middleware, session injection hooks.client.ts # Client-side error handling app.css # Tailwind base + theme variables app.d.ts # SvelteKit type augmentation (locals, session) prisma/ schema.prisma migrations/ seed.ts static/ uploads/ # User-uploaded icons ``` ### Database Schema (Prisma) Key models: - `User` — id, email, password (nullable for OAuth), displayName, avatarUrl, authProvider, role - `Group` — id, name, description, isDefault - `UserGroup` — userId, groupId (many-to-many) - `App` — id, name, url, icon, iconType, description, category, healthcheckEnabled, healthcheckInterval, healthcheckMethod, healthcheckExpectedStatus, healthcheckTimeout, createdById - `AppStatus` — id, appId, status, responseTime, checkedAt (latest N records for history) - `Board` — id, name, icon, description, isDefault, isGuestAccessible, backgroundConfig (JSON), createdById - `Section` — id, boardId, title, icon, order, isExpandedByDefault - `Widget` — id, sectionId, type, order, config (JSON — for AppWidget: `{ appId }`) - `Permission` — id, entityType (board/app), entityId, targetType (user/group), targetId, level (view/edit/admin) - `SystemSettings` — singleton row: authMode, registrationEnabled, oauthConfig (encrypted JSON), defaultTheme, defaultPrimaryColor, healthcheckDefaults (JSON) ### API Design RESTful JSON API via SvelteKit `+server.ts` routes with consistent envelope: ```json { "success": true, "data": { ... }, "error": null, "meta": { "total": 100, "page": 1, "limit": 20 } } ``` Key endpoints (all under `src/routes/api/`): - `POST /api/auth/login` — local login - `GET /api/auth/oauth/authorize` — redirect to Authentik - `GET /api/auth/oauth/callback` — handle OAuth callback - `POST /api/auth/register` — self-registration (if enabled) - `POST /api/auth/refresh` — token refresh - `GET/POST/PATCH/DELETE /api/apps` — app CRUD - `GET /api/apps/:id/status` — latest healthcheck status - `GET/POST/PATCH/DELETE /api/boards` — board CRUD (filtered by user permissions) - `GET/POST/PATCH/DELETE /api/boards/:id/sections` — section CRUD - `GET/POST/PATCH/DELETE /api/boards/:id/sections/:sid/widgets` — widget CRUD - `GET/POST/PATCH/DELETE /api/users` — user management (admin) - `GET/POST/PATCH/DELETE /api/groups` — group management (admin) - `GET/PATCH /api/admin/settings` — system settings (admin) - `GET /api/search?q=...` — global search - `GET /api/health` — app health endpoint for Docker healthcheck ### Docker Deployment ```dockerfile # Multi-stage: build SvelteKit with Node adapter → slim runtime FROM node:22-alpine AS builder WORKDIR /app COPY package*.json ./ RUN npm ci COPY . . RUN npx prisma generate RUN npm run build FROM node:22-alpine AS runtime WORKDIR /app COPY --from=builder /app/build ./build COPY --from=builder /app/node_modules ./node_modules COPY --from=builder /app/package.json ./ COPY --from=builder /app/prisma ./prisma EXPOSE 3000 HEALTHCHECK CMD wget -q --spider http://localhost:3000/api/health || exit 1 ENTRYPOINT ["sh", "-c", "npx prisma migrate deploy && node build"] ``` ```yaml # docker-compose.yml services: web-app-launcher: build: . ports: - "3000:3000" volumes: - ./data:/app/data # SQLite DB - ./uploads:/app/uploads # Custom icons environment: - DATABASE_URL=file:/app/data/launcher.db - JWT_SECRET=changeme - OAUTH_CLIENT_ID= - OAUTH_CLIENT_SECRET= - OAUTH_DISCOVERY_URL= - ORIGIN=http://localhost:3000 restart: unless-stopped ``` ### CI/CD (Gitea Actions) `.gitea/workflows/ci.yml`: - **On push to any branch:** lint (`eslint`), type-check (`svelte-check`), unit tests (`vitest`) - **On push to main:** build Docker image, push to Gitea Container Registry (`git.dolgolyov-family.by/alexei.dolgolyov/web-app-launcher`) - **On tag (vX.Y.Z):** build + push tagged image, create Gitea release --- ## Additional Features ### Cool Ideas (Included in Phases) 1. **Drag-and-drop board editor** — reorder sections and widgets with `svelte-dnd-action` (Svelte-native, accessible, performant) 2. **Auto-discovery** — scan a Docker socket or Traefik API to auto-register running containers as apps 3. **Favicon auto-fetch** — if no icon is selected, attempt to fetch the favicon from the app's URL 4. **Ping history sparkline** — tiny inline SVG chart showing uptime over last 24h per app 5. **Import/Export** — JSON export of entire config for backup/migration 6. **PWA support** — installable on mobile home screen with offline shell (SvelteKit service worker support) 7. **Multi-tab sync** — broadcast theme/board changes across tabs via BroadcastChannel API 8. **Quick-add bookmarklet** — browser bookmarklet that adds current page as an app via the API 9. **Keyboard-first navigation** — Vim-style `j/k` to move between apps, `Enter` to open ### MVP Scope (Phase 1) To avoid scope creep, the MVP should include: - Local auth + guest mode (OAuth in Phase 2) - App CRUD + healthcheck with status display - Single default board with sections and app widgets - Admin panel (user/app/board management) - Dark theme + ambient background - Docker deployment - Basic Gitea CI ### Phase 2 - OAuth/Authentik integration - Multi-board support with per-board access control - Custom groups + granular permissions - Drag-and-drop reordering - Global search (Cmd+K) - Additional widget types ### Phase 3 - ~~Auto-discovery (Docker/Traefik)~~ **DONE** — Phase 5 implementation: discoveryService.ts, /api/admin/discover endpoints, DiscoveryPanel.svelte, SettingsForm discovery config, i18n EN/RU - Import/Export - PWA - Ping history sparklines - User theme overrides - Quick-add bookmarklet - Multi-tab sync --- ## Constraints & Preferences - **Immutable data patterns** — never mutate objects in place; return new copies - **Small files** — one component/service per file, 200-400 lines typical, 800 max - **Comprehensive error handling** — every API call, every user action - **Input validation** — Zod schemas shared between client (Superforms) and server - **No hardcoded secrets** — all sensitive config via environment variables - **Conventional commits** — `feat:`, `fix:`, `refactor:`, etc. - **80%+ test coverage target** — unit tests with Vitest, integration tests for API routes, E2E with Playwright - **Progressive enhancement** — SvelteKit form actions work without JavaScript where possible