Warm, friendly redesign replacing the generic cold-shadcn look. Built as a
swappable token bundle so other presets can be added later; dark mode and the
user-tunable accent hue are retained.
Foundation
- app.css: warm cream (light) + "dusk" (dark) token system; terracotta accent
(default hue 16); pastel --room-* palette; vivid --status-* (dots/bars) plus
AA-legible --status-*-ink (text); soft warm shadows; --radius 1rem; font tokens
- Fonts: Fraunces (display) + Figtree (body), self-hosted in static/fonts
(no Google CDN) so offline/LAN installs work; system-ui fallbacks kept
- h1/h2/h3 render in Fraunces via base layer
Chrome and surfaces
- Sidebar, Header, home, AppCard/BoardCard, BoardHeader, sections, favorites
- 29 widgets + integration renderers: cozy card shells, room-palette charts
- Default background is a static warm "cozy" glow (mesh demoted, rAF gated on
prefers-reduced-motion)
System-wide
- Status colors tokenized (no raw bg/text-*-500 or status hex); success/warning
to status tokens, categorical to room palette, errors to destructive
- Inputs rounded-xl; buttons rounded-xl; cards/dialogs rounded-[1.4rem];
soft-shadow vocabulary only; focus-visible:ring-primary/30
- Forms, admin tables (now cozy cards), dialogs, popovers, auth screens
a11y: reduced-motion guards; darker status "ink" text for AA on cream.
Known tradeoff: terracotta primary + white button text ~2.96:1 (signature color,
user-tunable).
Verified: svelte-check 0/0, build ok, 274 tests pass, eslint 0 errors.
Design refs + system sheet in design-mockups/.
Replaces the blunt registrationEnabled toggle with per-invite access.
Invites are tokenized, single-use, optionally locked to an email, can
grant user or admin role, and expire (default 7d, max 90d).
- Invite model with tokenHash (bcrypt), email, role, expiresAt,
usedAt/usedByUserId.
- inviteService: create, list, revoke, findInviteByToken, consumeInvite.
Token is shown exactly once at creation.
- /admin/invites page: list with status (Active/Used/Expired), generate
with email lock + role + custom expiry, copy one-shot URL, revoke.
- /register?invite=TOKEN: accepts invite even when registrationEnabled
is false; shows a banner; enforces email lock; applies the invite's
role on creation; consumes the invite on success.
- Linked from the admin navbar.
- /settings/sessions: list user's active sessions with label, IP,
last-used, expires, user-agent. Highlights the current device.
- Revoke one session (/api/sessions/:id DELETE) or all-other sessions
(/api/sessions DELETE). Admins can revoke any session.
- Revoking the current session clears cookies and kicks the user to
/login.
- Wired into the main settings page.
Replace the single `user.refreshToken` column with a proper Session
table so users can have multiple concurrent sessions (phone, laptop,
etc.), each with their own refresh token, expiry, label, and
remember-me flag.
- Add Session model (id, userId, tokenHash, label, userAgent,
ipAddress, rememberMe, lastUsedAt, expiresAt).
- Drop `User.refreshToken` and `User.refreshTokenExpiresAt`.
- authService: new createSession/validateSession/rotateSession/
revokeSession/listUserSessions helpers; remove refresh-token-on-user
functions.
- sessionCookies helper now issues a session_id cookie alongside
access_token and refresh_token; rotateSessionCookies keeps the same
session id on refresh.
- Login form adds a "Keep me signed in for 30 days" checkbox;
TTL is 7d by default, 30d with remember-me.
- User-Agent parsed into a friendly label ("Chrome on Windows") for
the upcoming sessions page.
- hooks.server.ts, refresh endpoint, logout, register, oauth callback,
and onboarding all switched to the new session API.
- Extract session cookie issuance into sessionCookies.ts helper; remove
duplicated COOKIE_BASE blocks from login, register, oauth callback/authorize,
refresh handler, hooks.server.ts, and onboarding.
- Derive cookie secure flag from ORIGIN (https://...) instead of NODE_ENV so
plain-HTTP production deploys don't silently drop cookies.
- Auto-login admin after onboarding completes; UI does a full reload so
hooks.server.ts picks up the new session.
- Harden onboarding: reject duplicate admin creation, flip onboardingComplete
atomically to prevent concurrent completions, error out if no admin found.
- Fix Dockerfile CMD operator precedence: node build now always runs after
migrate deploy || db push.
- Wire ORIGIN env through docker-compose.
- Replace 3 partial migrations with single init migration from schema
- Fixes missing backupEnabled, integrationType, and other columns
- Move @prisma/client to dependencies for adapter-node externalization
- Add ssr.external to prevent Vite bundling Prisma (fixes __dirname error)
Move @prisma/client and prisma to dependencies so adapter-node
externalizes them instead of bundling. Add ssr.external config
for Vite to prevent inlining Prisma's CJS engine loader.
- Rename CI workflow to test.yml (lint & test only)
- Add release.yml (Docker push + Gitea release on v* tag)
- Add README.md and RELEASE_NOTES.md
- Bump version to 0.0.1
- Load all app sparkline history in a single server query
- Pass preloadedHistory to AppCard to skip client-side fetch
- Polish empty state with icon, hint text, and add button
- Replace manual click-outside menu with DropdownMenu from bits-ui
- Add collapsible boards section in sidebar with chevron toggle
- Add max-height scroll for boards list
- Create MultiEntityPicker component with search, checkboxes, keyboard nav
- Replace plain checkbox list in status widget config with MultiEntityPicker
- Render app icons properly by type (lucide, simple, url, emoji)
- Replace barrel `import * as icons` in DynamicIcon with dynamic per-icon imports
- Eagerly connect Prisma client at startup to avoid first-request latency
- Parallelize 4 sequential DB queries in layout server load with Promise.all
- Replace AppIconPicker text input with visual IconPickerButton for
lucide icons (grid with search)
- Add AutocompleteInput component for category field with existing
category suggestions
- Add TagsInput component for tags field with tag pills, autocomplete
from existing tags, and keyboard navigation
- Add GET /api/apps/suggestions endpoint returning all categories/tags
- Add getAllTags() to appService (merges Tag model + comma-separated)
- Install @tailwindcss/typography plugin to fix prose rendering
(headings, lists, blockquotes now render in Note/Markdown widgets)
- Fix note widget validator test for new html format
Replace the disconnected board edit page with inline editing directly
on the board view. Toggle with Ctrl+E or the Edit button. Features:
- Edit mode store with changeset accumulation and batch save
- Floating toolbar (save, discard, add section, board settings, exit)
- Widget hover overlays with edit/delete/drag controls
- Type-specific widget config panels for all 14 widget types
- Section inline editing (title, icon picker, delete)
- "+" buttons for adding widgets and sections inline
- Section-level drag-and-drop reordering via svelte-dnd-action
- Batch save API endpoint (single Prisma transaction)
- Board properties side panel with live theme/wallpaper preview
- Modal widget type picker with search filtering
- Icon picker component with visual grid and search
- Confirmation dialog modal for all destructive actions
- HTML format support for Note widget (in addition to markdown/text)
- Full i18n support (en + ru) for all new UI strings
- Legacy edit page banner linking to new inline mode
Replace the JSON-based import/export with a proper backup system that copies
the SQLite database file directly. Supports manual on-demand backups, periodic
scheduled backups via node-cron, configurable retention, file download, and
full database restore.
- Add backupService with VACUUM INTO for safe DB copies
- Add backupScheduler following healthcheckScheduler pattern
- Add 6 admin API endpoints (create, list, download, restore, delete, schedule)
- Add BackupPanel UI with backup table, confirmation dialogs, schedule config
- Add backup fields to SystemSettings schema
- Remove old ImportExportPanel, exportService, importService, and related code
Add /apps/[id]/edit route that loads existing app data into the form,
allowing users to update app properties. Adds edit pencil button to
AppCard (visible on hover) and i18n keys for both EN and RU.
- Fix CreateAppInput nullable types for integration fields
- Add type casts in IntegrationWidget renderer dispatching
- Guard decryptAppIntegration against missing fields in test mocks
- Emby: now playing, library stats, recently added, active streams
- Immich: library stats, recent uploads with formatted storage
- Deluge: active torrents with progress, transfer speed, disk space gauge
- MeTube: download queue progress (no auth required)
- Planka: my cards, overdue cards with red badges, board summary
- All 11 integrations registered in registry
- NUT/UPS: TCP protocol client, battery/load gauges, runtime card, power alert banner
- Pi-hole: DNS stats summary, top blocked domains, query log, gravity status
- Portainer: Container summary/list, stack status via Docker API
- Gitea: Recent commits, open PRs, releases across repos
- Nginx Proxy Manager: Proxy hosts, SSL certificate expiry alerts, upstream status
- Authentik: User stats, login events feed, brute force detection alerts
- All integrations registered in registry with auto-discovery
- Add Integration interfaces, registry, cache, encryption, and base helpers
- Add integrationType, integrationConfig, integrationEnabled to App model
- Add integration widget type to constants and validators
- Add integration fields to AppRecord, CreateAppInput, UpdateAppInput
- Update appService with encryption/decryption for integration config
- Add API routes: list integrations, test connection, fetch endpoint data
The search API returns { success, data: [...] } but the store was
looking for data.apps and data.boards (which don't exist). Fixed to
read from data.data[] and also added url/icon fields to search API
response so app results are clickable and show icons.
Previously each AppWidget fetched /api/apps/{id}/history individually
on mount, causing N sequential HTTP requests. Now the board page
server load fetches all app histories in a single Prisma query via
getBatchStatusHistory() and passes them to AppWidget via Svelte
context. AppWidget uses the pre-loaded data immediately with a
fallback fetch for non-board contexts.
Block requests to private/reserved IP ranges (10.x, 172.16-31.x,
192.168.x, 127.x, 169.254.x, localhost, ::1) and non-http(s)
schemes in the /api/apps/preview endpoint to prevent server-side
request forgery.
Fullscreen HLS video now initializes HLS.js via bind:this + $effect
instead of raw <video src>, which only works in Safari. Non-Safari
browsers now correctly play HLS streams in fullscreen mode.
- Add apiTokenScope to App.Locals type definition
- Store token scope in event.locals during API token auth
- Block write operations (POST/PATCH/PUT/DELETE) for read-scoped tokens
- Block admin paths for non-admin-scoped tokens
- Returns 403 with descriptive error message
- Add /api/onboarding and /status to PUBLIC_PATHS in hooks.server.ts
so onboarding wizard and status page work for unauthenticated users
- Add isOnboardingNeeded() guard to POST /api/onboarding to reject
calls after onboarding is complete (security hardening)
- Add data-app-widget attribute to all AppWidget card variants to
enable j/k keyboard navigation
Port icon grid and entity picker patterns from wled-screen-controller.
IconGrid replaces plain <select> elements with visual icon grids for
known item sets (widget type, icon type, healthcheck method, permission
level). EntityPicker replaces search dropdowns with a command-palette
style overlay with keyboard navigation and filtering.
Enhance SearchDialog with keyboard navigation (arrow keys, Enter,
Escape), grouped results with section headers, active highlight,
and a footer with shortcut hints.
- Import/Export: JSON export/import with conflict resolution (skip/overwrite)
- Ping history sparklines: 24h bar charts on app widgets with uptime %
- User theme overrides: per-user preferences (hue, saturation, mode, bg, locale)
- PWA: service worker, manifest, offline page, install prompt
- Auto-discovery: Docker socket + Traefik API scanning with approval UI
- Quick-add bookmarklet: popup-based add from any page
- Multi-tab sync: BroadcastChannel for theme + data changes
- Security: execFile for Docker commands, Zod on all API inputs, import limits
- 222 tests across 20 test files, all passing