Files
web-app-launcher/PLAN_PROMPT.md
T
alexei.dolgolyov dd6958b4d6 feat(phase3): PWA, auto-discovery, bookmarklet, multi-tab sync
- PWA: manifest, service worker (cache-first static, network-first API),
  offline page, install prompt banner
- Auto-discovery: Docker socket + Traefik API scanning, approval UI
- Quick-add bookmarklet: popup-based add page, favicon auto-detect
- Multi-tab sync: BroadcastChannel for theme + data changes
- i18n translations for all new strings (EN/RU)
2026-03-25 00:59:19 +03:00

19 KiB

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 transitionstransition:, animate:, in:/out: directives for page transitions, expand/collapse, hover effects
  • svelte/motiontweened 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:

{
  "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

# 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"]
# 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 commitsfeat:, 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