feat: Phases 4-7 — Full Feature Expansion (26 features)

Phase 4 — New Widget Types:
- Clock/Weather, System Stats, RSS/Feed, Calendar, Markdown,
  Metric/Counter, Link Group, Camera/Stream widgets
- Backend services with caching for each data source
- Full creation form with dynamic config fields per type

Phase 5 — Visual & Styling Enhancements:
- Glassmorphism card style (solid/glass/outline)
- Board-level themes with per-board hue/saturation
- Animated SVG status rings replacing static dots
- Card size options (compact/medium/large)
- Custom CSS injection (admin + per-board, sanitized)
- Wallpaper backgrounds with blur/overlay/parallax

Phase 6 — Functional Features:
- Favorites bar with drag-and-drop reordering
- Recent apps tracking with privacy toggle
- Uptime dashboard page (/status, guest-accessible)
- Notifications system (Discord/Slack/Telegram/HTTP webhooks)
- App tags with filtering in board view
- Multi-URL app cards with expandable sub-links
- Personal API tokens with scoped permissions
- Audit log with retention and admin viewer

Phase 7 — Quality of Life:
- Onboarding wizard (5-step first-launch setup)
- App URL health preview with favicon/title detection
- Board templates (4 built-in + custom import/export)
- Keyboard shortcut overlay (j/k nav, 1-9 boards, ? help)

212 files changed, 15641 insertions, 980 deletions.
Build, lint, type check, and 222 tests all pass.
This commit is contained in:
2026-03-25 14:18:10 +03:00
parent 8d7847889e
commit 1c0a7cb850
212 changed files with 15642 additions and 981 deletions
+22 -2
View File
@@ -4,9 +4,11 @@ const THEME_STORAGE_KEY = 'wal-theme-mode';
const PRIMARY_HUE_KEY = 'wal-primary-hue';
const PRIMARY_SAT_KEY = 'wal-primary-sat';
const BG_TYPE_KEY = 'wal-bg-type';
const CARD_STYLE_KEY = 'wal-card-style';
export type ThemeMode = 'dark' | 'light' | 'system';
export type BackgroundType = 'mesh' | 'particles' | 'aurora' | 'none';
export type BackgroundType = 'mesh' | 'particles' | 'aurora' | 'none' | 'wallpaper';
export type CardStyle = 'solid' | 'glass' | 'outline';
function getStoredValue<T>(key: string, fallback: T): T {
if (typeof window === 'undefined') return fallback;
@@ -36,6 +38,7 @@ class ThemeStore {
primaryHue = $state(220);
primarySaturation = $state(70);
backgroundType = $state<BackgroundType>('mesh');
cardStyle = $state<CardStyle>('solid');
#systemPreference: 'dark' | 'light' = 'dark';
#suppressBroadcast = false;
@@ -52,6 +55,7 @@ class ThemeStore {
this.primaryHue = getStoredNumber(PRIMARY_HUE_KEY, 220);
this.primarySaturation = getStoredNumber(PRIMARY_SAT_KEY, 70);
this.backgroundType = getStoredValue<BackgroundType>(BG_TYPE_KEY, 'mesh');
this.cardStyle = getStoredValue<CardStyle>(CARD_STYLE_KEY, 'solid');
const mql = window.matchMedia('(prefers-color-scheme: dark)');
this.#systemPreference = mql.matches ? 'dark' : 'light';
@@ -83,6 +87,11 @@ class ThemeStore {
localStorage.setItem(BG_TYPE_KEY, this.backgroundType);
});
$effect(() => {
if (typeof window === 'undefined') return;
localStorage.setItem(CARD_STYLE_KEY, this.cardStyle);
});
$effect(() => {
if (typeof document === 'undefined') return;
const html = document.documentElement;
@@ -109,7 +118,8 @@ class ThemeStore {
mode: this.mode,
primaryHue: this.primaryHue,
primarySaturation: this.primarySaturation,
backgroundType: this.backgroundType
backgroundType: this.backgroundType,
cardStyle: this.cardStyle
};
if (typeof window === 'undefined') return;
if (this.#suppressBroadcast) return;
@@ -131,6 +141,10 @@ class ThemeStore {
this.backgroundType = bg;
}
setCardStyle(style: CardStyle) {
this.cardStyle = style;
}
setPrimaryColor(hue: number, saturation: number) {
this.primaryHue = Math.max(0, Math.min(360, hue));
this.primarySaturation = Math.max(0, Math.min(100, saturation));
@@ -145,12 +159,14 @@ class ThemeStore {
primaryHue: number;
primarySaturation: number;
backgroundType: BackgroundType;
cardStyle?: CardStyle;
}) {
this.#suppressBroadcast = true;
this.mode = values.mode;
this.primaryHue = values.primaryHue;
this.primarySaturation = values.primarySaturation;
this.backgroundType = values.backgroundType;
if (values.cardStyle) this.cardStyle = values.cardStyle;
// Use setTimeout to ensure all Svelte 5 effects have fired before re-enabling broadcast
setTimeout(() => {
this.#suppressBroadcast = false;
@@ -166,6 +182,7 @@ class ThemeStore {
primaryHue?: number | null;
primarySaturation?: number | null;
backgroundType?: string | null;
cardStyle?: string | null;
}) {
if (prefs.themeMode != null) {
this.mode = prefs.themeMode as ThemeMode;
@@ -179,6 +196,9 @@ class ThemeStore {
if (prefs.backgroundType != null) {
this.backgroundType = prefs.backgroundType as BackgroundType;
}
if (prefs.cardStyle != null) {
this.cardStyle = prefs.cardStyle as CardStyle;
}
}
}