commit 6d415ac97dae9609eb94059a487f569365843e34 Author: alexei.dolgolyov Date: Tue Mar 24 19:42:21 2026 +0300 docs: add project plan prompt diff --git a/PLAN_PROMPT.md b/PLAN_PROMPT.md new file mode 100644 index 0000000..4e544db --- /dev/null +++ b/PLAN_PROMPT.md @@ -0,0 +1,469 @@ +# 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) +- 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