+
+
+
+
+
diff --git a/apps/mana/apps/web/src/lib/components/settings/SettingsSectionHeader.svelte b/apps/mana/apps/web/src/lib/components/settings/SettingsSectionHeader.svelte
new file mode 100644
index 000000000..cbfa14c75
--- /dev/null
+++ b/apps/mana/apps/web/src/lib/components/settings/SettingsSectionHeader.svelte
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
+
+
+
{title}
+ {#if description}
+
{description}
+ {/if}
+
+
+ {#if action}
+
{@render action()}
+ {/if}
+
diff --git a/apps/mana/apps/web/src/lib/components/settings/SettingsSidebar.svelte b/apps/mana/apps/web/src/lib/components/settings/SettingsSidebar.svelte
new file mode 100644
index 000000000..53dd680e7
--- /dev/null
+++ b/apps/mana/apps/web/src/lib/components/settings/SettingsSidebar.svelte
@@ -0,0 +1,368 @@
+
+
+
+
+
+
diff --git a/apps/mana/apps/web/src/lib/components/settings/searchIndex.ts b/apps/mana/apps/web/src/lib/components/settings/searchIndex.ts
new file mode 100644
index 000000000..358d3f41e
--- /dev/null
+++ b/apps/mana/apps/web/src/lib/components/settings/searchIndex.ts
@@ -0,0 +1,215 @@
+/**
+ * settings/searchIndex — single source of truth for the settings sidebar
+ * categories and the in-page search index. Editing a single entry here
+ * updates both the navigation and the search results.
+ */
+import type { Component } from 'svelte';
+import { User, Gear, Robot, ShieldCheck, CurrencyCircleDollar, Cloud } from '@mana/shared-icons';
+
+export type CategoryId = 'profile' | 'general' | 'ai' | 'security' | 'credits' | 'data';
+
+export interface Category {
+ id: CategoryId;
+ label: string;
+ description: string;
+ icon: Component;
+ /** Anchor ids in this category — used for hash-based deep-links. */
+ anchors: string[];
+}
+
+export const categories: Category[] = [
+ {
+ id: 'profile',
+ label: 'Profil',
+ description: 'Persönliche Daten & Konto',
+ icon: User,
+ anchors: ['profile', 'account'],
+ },
+ {
+ id: 'general',
+ label: 'Allgemein',
+ description: 'Theme, Sprache, Benachrichtigungen',
+ icon: Gear,
+ anchors: ['global'],
+ },
+ {
+ id: 'ai',
+ label: 'KI',
+ description: 'Compute-Backend & Modelle',
+ icon: Robot,
+ anchors: ['ai-options'],
+ },
+ {
+ id: 'security',
+ label: 'Sicherheit',
+ description: 'Passkeys, 2FA & Sitzungen',
+ icon: ShieldCheck,
+ anchors: ['passkeys', 'sessions', 'two-factor', 'security-log'],
+ },
+ {
+ id: 'credits',
+ label: 'Credits',
+ description: 'Guthaben & Transaktionen',
+ icon: CurrencyCircleDollar,
+ anchors: ['credits'],
+ },
+ {
+ id: 'data',
+ label: 'Daten & Sync',
+ description: 'Cloud-Sync, Export & DSGVO',
+ icon: Cloud,
+ anchors: ['cloud-sync', 'my-data'],
+ },
+];
+
+export interface SearchEntry {
+ /** Display label shown in the result list */
+ label: string;
+ /** Extra search keywords (the label is always searched too) */
+ keywords?: string[];
+ category: CategoryId;
+ anchor: string;
+}
+
+export const searchIndex: SearchEntry[] = [
+ // Profile
+ { label: 'E-Mail', keywords: ['email', 'mail'], category: 'profile', anchor: 'profile' },
+ { label: 'Vorname', keywords: ['name'], category: 'profile', anchor: 'profile' },
+ { label: 'Nachname', keywords: ['name'], category: 'profile', anchor: 'profile' },
+ {
+ label: 'Konto-Status',
+ keywords: ['rolle', 'role', 'aktiv'],
+ category: 'profile',
+ anchor: 'account',
+ },
+ { label: 'Benutzer-ID', keywords: ['id', 'uid'], category: 'profile', anchor: 'account' },
+
+ // General
+ {
+ label: 'Theme',
+ keywords: ['dark', 'light', 'farbe', 'design'],
+ category: 'general',
+ anchor: 'global',
+ },
+ {
+ label: 'Sprache',
+ keywords: ['language', 'i18n', 'deutsch', 'english'],
+ category: 'general',
+ anchor: 'global',
+ },
+ {
+ label: 'Benachrichtigungen',
+ keywords: ['notification', 'sound'],
+ category: 'general',
+ anchor: 'global',
+ },
+
+ // AI
+ {
+ label: 'KI-Optionen',
+ keywords: ['llm', 'ai', 'compute'],
+ category: 'ai',
+ anchor: 'ai-options',
+ },
+ {
+ label: 'Browser-Modell (Gemma)',
+ keywords: ['gemma', 'webgpu', 'lokal', 'offline'],
+ category: 'ai',
+ anchor: 'ai-options',
+ },
+ {
+ label: 'Mana-Server (KI)',
+ keywords: ['server', 'self-hosted'],
+ category: 'ai',
+ anchor: 'ai-options',
+ },
+ {
+ label: 'Cloud-KI (Gemini)',
+ keywords: ['google', 'cloud', 'gemini'],
+ category: 'ai',
+ anchor: 'ai-options',
+ },
+
+ // Security
+ {
+ label: 'Passkeys',
+ keywords: ['webauthn', 'fido', 'biometrie'],
+ category: 'security',
+ anchor: 'passkeys',
+ },
+ {
+ label: 'Aktive Sessions',
+ keywords: ['logout', 'gerät', 'device'],
+ category: 'security',
+ anchor: 'sessions',
+ },
+ {
+ label: 'Zwei-Faktor (2FA)',
+ keywords: ['totp', '2fa', 'mfa'],
+ category: 'security',
+ anchor: 'two-factor',
+ },
+ {
+ label: 'Sicherheits-Log',
+ keywords: ['audit', 'history', 'verlauf'],
+ category: 'security',
+ anchor: 'security-log',
+ },
+
+ // Credits
+ {
+ label: 'Credits-Guthaben',
+ keywords: ['balance', 'geld'],
+ category: 'credits',
+ anchor: 'credits',
+ },
+ {
+ label: 'Credits kaufen',
+ keywords: ['buy', 'pakete', 'kaufen'],
+ category: 'credits',
+ anchor: 'credits',
+ },
+ { label: 'Transaktionen', keywords: ['history'], category: 'credits', anchor: 'credits' },
+
+ // Data
+ {
+ label: 'Cloud Sync',
+ keywords: ['sync', 'backup', 'geräte'],
+ category: 'data',
+ anchor: 'cloud-sync',
+ },
+ {
+ label: 'Daten exportieren',
+ keywords: ['export', 'dsgvo', 'gdpr', 'json'],
+ category: 'data',
+ anchor: 'my-data',
+ },
+ {
+ label: 'Konto löschen',
+ keywords: ['delete', 'gdpr', 'dsgvo'],
+ category: 'data',
+ anchor: 'my-data',
+ },
+];
+
+/** Tiny case-insensitive ranker — exact > prefix > contains. */
+export function searchSettings(query: string, limit = 8): SearchEntry[] {
+ const q = query.trim().toLowerCase();
+ if (!q) return [];
+ const results: { entry: SearchEntry; score: number }[] = [];
+ for (const entry of searchIndex) {
+ const haystacks = [
+ entry.label.toLowerCase(),
+ ...(entry.keywords ?? []).map((k) => k.toLowerCase()),
+ ];
+ let score = 0;
+ for (const h of haystacks) {
+ if (h === q) score = Math.max(score, 100);
+ else if (h.startsWith(q)) score = Math.max(score, 50);
+ else if (h.includes(q)) score = Math.max(score, 20);
+ }
+ if (score > 0) results.push({ entry, score });
+ }
+ results.sort((a, b) => b.score - a.score);
+ return results.slice(0, limit).map((r) => r.entry);
+}
diff --git a/apps/mana/apps/web/src/lib/components/settings/sections/AiSection.svelte b/apps/mana/apps/web/src/lib/components/settings/sections/AiSection.svelte
new file mode 100644
index 000000000..dc63a43f2
--- /dev/null
+++ b/apps/mana/apps/web/src/lib/components/settings/sections/AiSection.svelte
@@ -0,0 +1,8 @@
+
+
+
+
+
diff --git a/apps/mana/apps/web/src/lib/components/settings/sections/CreditsSection.svelte b/apps/mana/apps/web/src/lib/components/settings/sections/CreditsSection.svelte
new file mode 100644
index 000000000..2ae591b5b
--- /dev/null
+++ b/apps/mana/apps/web/src/lib/components/settings/sections/CreditsSection.svelte
@@ -0,0 +1,75 @@
+
+
+
+
+ {#snippet action()}
+ Alle Details
+ {/snippet}
+
+
+
+
+
Verfügbar
+
+ {creditBalance ? formatCredits(creditBalance.balance) : '...'}
+
+
+
+
Gratis heute
+
+ {creditBalance
+ ? `${creditBalance.freeCreditsRemaining}/${creditBalance.dailyFreeCredits}`
+ : '...'}
+
+
+
+
Gesamt verbraucht
+
+ {creditBalance ? formatCredits(creditBalance.totalSpent) : '...'}
+
+
+
+
+
+
diff --git a/apps/mana/apps/web/src/lib/components/settings/sections/DataSection.svelte b/apps/mana/apps/web/src/lib/components/settings/sections/DataSection.svelte
new file mode 100644
index 000000000..e659f4a4f
--- /dev/null
+++ b/apps/mana/apps/web/src/lib/components/settings/sections/DataSection.svelte
@@ -0,0 +1,64 @@
+
+
+
+
+ {#snippet action()}
+ Einstellungen
+ {/snippet}
+
+
+
+
+
+
+
+
+
+
Daten ansehen & exportieren
+
+ Sieh alle deine gespeicherten Daten ein und exportiere sie als JSON
+
+
+
+ Meine Daten
+
+
+
+
+
+
+
+
Konto löschen
+
+ Das Löschen deines Kontos kann nicht rückgängig gemacht werden.
+
+
+
+ Verwalten
+
+
+
+
+
diff --git a/apps/mana/apps/web/src/lib/components/settings/sections/GeneralSection.svelte b/apps/mana/apps/web/src/lib/components/settings/sections/GeneralSection.svelte
new file mode 100644
index 000000000..99f8a2b39
--- /dev/null
+++ b/apps/mana/apps/web/src/lib/components/settings/sections/GeneralSection.svelte
@@ -0,0 +1,14 @@
+
+
+
+
+
diff --git a/apps/mana/apps/web/src/lib/components/settings/sections/ProfileSection.svelte b/apps/mana/apps/web/src/lib/components/settings/sections/ProfileSection.svelte
new file mode 100644
index 000000000..2275820c8
--- /dev/null
+++ b/apps/mana/apps/web/src/lib/components/settings/sections/ProfileSection.svelte
@@ -0,0 +1,134 @@
+
+
+
+
+
+ {#if profileSuccess}
+
+ Profil erfolgreich aktualisiert!
+
+ {/if}
+ {#if profileError}
+
+ {profileError}
+
+ {/if}
+
+
+
+
+
+
E-Mail kann nicht geändert werden
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Konto-Status
+
Dein aktueller Kontostatus
+
+
+ Aktiv
+
+
+
+
+
+
Rolle
+
Deine Berechtigungsstufe
+
+
+ {authStore.user?.role || 'user'}
+
+
+
+
+
+
Benutzer-ID
+
Deine eindeutige Kennung
+
+
+ {authStore.user?.id?.slice(0, 8) || '...'}...
+
+
+
+
diff --git a/apps/mana/apps/web/src/lib/components/settings/sections/SecuritySection.svelte b/apps/mana/apps/web/src/lib/components/settings/sections/SecuritySection.svelte
new file mode 100644
index 000000000..0dfc18f96
--- /dev/null
+++ b/apps/mana/apps/web/src/lib/components/settings/sections/SecuritySection.svelte
@@ -0,0 +1,80 @@
+
+
+
+ authStore.registerPasskey(name)}
+ onDelete={(id) => authStore.deletePasskey(id)}
+ onRename={(id, name) => authStore.renamePasskey(id, name)}
+ onRefresh={async () => {
+ passkeys = await authStore.listPasskeys();
+ }}
+ primaryColor="#6366f1"
+ />
+
+
+
+ authStore.revokeSession(id)}
+ onRefresh={async () => {
+ sessionsLoading = true;
+ sessions = await authStore.listSessions();
+ sessionsLoading = false;
+ }}
+ primaryColor="#6366f1"
+ />
+
+
+
+ authStore.enableTwoFactor(password)}
+ onDisable={(password) => authStore.disableTwoFactor(password)}
+ onGenerateBackupCodes={(password) => authStore.generateBackupCodes(password)}
+ primaryColor="#6366f1"
+ />
+
+
+
+ {
+ securityEventsLoading = true;
+ securityEvents = await authStore.getSecurityEvents();
+ securityEventsLoading = false;
+ }}
+ primaryColor="#6366f1"
+ />
+
diff --git a/apps/mana/apps/web/src/routes/(app)/+layout.svelte b/apps/mana/apps/web/src/routes/(app)/+layout.svelte
index 5d4bf43f3..e8608b154 100644
--- a/apps/mana/apps/web/src/routes/(app)/+layout.svelte
+++ b/apps/mana/apps/web/src/routes/(app)/+layout.svelte
@@ -184,10 +184,10 @@
updateLlmSettings({ allowedTiers: next });
}
- const TIER_TOGGLE_LIST: Array<{ tier: LlmTier; shortLabel: string }> = [
- { tier: 'browser', shortLabel: 'Browser (Gemma 4)' },
- { tier: 'mana-server', shortLabel: 'Server (Gemma 4)' },
- { tier: 'cloud', shortLabel: 'Cloud (Gemini)' },
+ const TIER_TOGGLE_LIST: Array<{ tier: LlmTier; shortLabel: string; icon: string }> = [
+ { tier: 'browser', shortLabel: 'Browser (Gemma 4)', icon: 'cpu' },
+ { tier: 'mana-server', shortLabel: 'Server (Gemma 4)', icon: 'server' },
+ { tier: 'cloud', shortLabel: 'Cloud (Gemini)', icon: 'cloud' },
];
let aiTierItems = $derived
([
@@ -195,6 +195,7 @@
...TIER_TOGGLE_LIST.filter((t) => t.tier !== 'browser' || webgpuSupported).map((t) => ({
id: `ai-tier-${t.tier}`,
label: t.shortLabel,
+ icon: t.icon,
active: llmSettings.allowedTiers.includes(t.tier),
onClick: () => toggleAiTier(t.tier),
})),
@@ -209,6 +210,7 @@
: localLlmStatus.current.state === 'downloading'
? `Lade… ${((localLlmStatus.current as { progress: number }).progress * 100).toFixed(0)}%`
: 'Modell laden (~500 MB)',
+ icon: localLlmStatus.current.state === 'ready' ? 'check' : 'download',
disabled: localLlmStatus.current.state === 'ready',
onClick:
localLlmStatus.current.state !== 'ready' ? () => void loadLocalLlm() : undefined,
@@ -221,7 +223,7 @@
id: 'ai-settings',
label: 'KI-Einstellungen',
icon: 'settings',
- onClick: () => goto('/settings'),
+ onClick: () => goto('/settings#ai-options'),
},
]);
@@ -238,6 +240,18 @@
return first ? first.shortLabel.split(' (')[0] : 'KI';
});
+ let currentAiTierIcon = $derived.by(() => {
+ const active = llmSettings.allowedTiers;
+ if (active.length === 0) return 'power';
+ const sorted = [...active].sort(
+ (a, b) =>
+ TIER_TOGGLE_LIST.findIndex((t) => t.tier === a) -
+ TIER_TOGGLE_LIST.findIndex((t) => t.tier === b)
+ );
+ const first = TIER_TOGGLE_LIST.find((t) => t.tier === sorted[0]);
+ return first ? first.icon : 'cpu';
+ });
+
// ── Sync status dropdown ────────────────────────────────
let syncStatusItems = $derived.by(() => {
const items: import('@mana/shared-ui').PillDropdownItem[] = [];
@@ -816,6 +830,7 @@
showAiTierSelector={true}
{aiTierItems}
{currentAiTierLabel}
+ {currentAiTierIcon}
showSyncStatus={authStore.isAuthenticated}
{syncStatusItems}
{currentSyncLabel}
diff --git a/apps/mana/apps/web/src/routes/(app)/settings/+page.svelte b/apps/mana/apps/web/src/routes/(app)/settings/+page.svelte
index e6150ad52..f28878edf 100644
--- a/apps/mana/apps/web/src/routes/(app)/settings/+page.svelte
+++ b/apps/mana/apps/web/src/routes/(app)/settings/+page.svelte
@@ -1,433 +1,78 @@
-
+
- {#if loading}
-
-
+
+
(activeCategory = id)} onJump={jumpTo} />
+
+
+ {#if activeCategory === 'profile'}
+
+ {:else if activeCategory === 'general'}
+
+ {:else if activeCategory === 'ai'}
+
+ {:else if activeCategory === 'security'}
+
+ {:else if activeCategory === 'credits'}
+
+ {:else if activeCategory === 'data'}
+
+ {/if}
- {:else}
-
-
-
-
-
-
-
-
-
-
Profil
-
Deine persönlichen Informationen
-
-
+
- {#if profileSuccess}
-
- Profil erfolgreich aktualisiert!
-
- {/if}
-
- {#if profileError}
-
- {profileError}
-
- {/if}
-
-
-
-
-
-
E-Mail kann nicht geändert werden
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- ☁️
-
-
-
Cloud Sync
-
- Synchronisiere deine Daten über alle Geräte
-
-
-
-
- Einstellungen
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Credits
-
Dein Guthaben für Mana Apps
-
-
-
Alle Details
-
-
-
-
-
Verfügbar
-
- {creditBalance ? formatCredits(creditBalance.balance) : '...'}
-
-
-
-
Gratis heute
-
- {creditBalance
- ? `${creditBalance.freeCreditsRemaining}/${creditBalance.dailyFreeCredits}`
- : '...'}
-
-
-
-
Gesamt verbraucht
-
- {creditBalance ? formatCredits(creditBalance.totalSpent) : '...'}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Konto
-
Konto- und Sicherheitsinformationen
-
-
-
-
-
-
-
Konto-Status
-
Dein aktueller Kontostatus
-
-
- Aktiv
-
-
-
-
-
-
Rolle
-
Deine Berechtigungsstufe
-
-
- {authStore.user?.role || 'user'}
-
-
-
-
-
-
Benutzer-ID
-
Deine eindeutige Kennung
-
-
- {authStore.user?.id?.slice(0, 8) || '...'}...
-
-
-
-
-
-
-
-
-
-
authStore.registerPasskey(name)}
- onDelete={(id) => authStore.deletePasskey(id)}
- onRename={(id, name) => authStore.renamePasskey(id, name)}
- onRefresh={async () => {
- passkeys = await authStore.listPasskeys();
- }}
- primaryColor="#6366f1"
- />
-
-
-
-
-
-
- authStore.revokeSession(id)}
- onRefresh={async () => {
- sessionsLoading = true;
- sessions = await authStore.listSessions();
- sessionsLoading = false;
- }}
- primaryColor="#6366f1"
- />
-
-
-
-
-
-
- authStore.enableTwoFactor(password)}
- onDisable={(password) => authStore.disableTwoFactor(password)}
- onGenerateBackupCodes={(password) => authStore.generateBackupCodes(password)}
- primaryColor="#6366f1"
- />
-
-
-
-
-
-
-
{
- securityEventsLoading = true;
- securityEvents = await authStore.getSecurityEvents();
- securityEventsLoading = false;
- }}
- primaryColor="#6366f1"
- />
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Meine Daten (DSGVO)
-
Datenschutz und Datenexport
-
-
-
-
-
-
-
Daten ansehen & exportieren
-
- Sieh alle deine gespeicherten Daten ein und exportiere sie als JSON
-
-
-
- Meine Daten
-
-
-
-
-
-
-
-
Konto loschen
-
- Das Loschen deines Kontos kann nicht ruckgangig gemacht werden.
-
-
-
- Verwalten
-
-
-
-
-
-
-
-
-
v{APP_VERSION}
- {/if}
+
v{APP_VERSION}
diff --git a/packages/shared-ui/src/navigation/PillDropdown.svelte b/packages/shared-ui/src/navigation/PillDropdown.svelte
index 693a6060d..4dfd892ce 100644
--- a/packages/shared-ui/src/navigation/PillDropdown.svelte
+++ b/packages/shared-ui/src/navigation/PillDropdown.svelte
@@ -128,6 +128,14 @@
help: 'M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10-4.477 10-10 10zm-1-7v2h2v-2h-2zm2-1.645A3.502 3.502 0 0012 6.5 3.501 3.501 0 008.645 9h2.012A1.5 1.5 0 0112 8.5c.828 0 1.5.672 1.5 1.5 0 .828-.672 1.5-1.5 1.5a1 1 0 00-1 1V14h2v-.645z',
// Mana icon (water drop)
mana: 'M12.3 1c.03.05 7.3 9.67 7.3 13.7 0 4.03-3.27 7.3-7.3 7.3S5 18.73 5 14.7C5 10.66 12.3 1 12.3 1zm0 6.4c-.02.03-3.65 4.83-3.65 6.84 0 2.02 1.64 3.65 3.65 3.65s3.65-1.64 3.65-3.65c0-2.01-3.62-6.81-3.65-6.84z',
+ // Compute / AI tier icons
+ cpu: 'M9 3v2m6-2v2M9 19v2m6-2v2M5 9H3m2 6H3m18-6h-2m2 6h-2M7 19h10a2 2 0 002-2V7a2 2 0 00-2-2H7a2 2 0 00-2 2v10a2 2 0 002 2zM9 9h6v6H9V9z',
+ server:
+ 'M5 12V7a2 2 0 012-2h10a2 2 0 012 2v5M5 12h14M5 12v5a2 2 0 002 2h10a2 2 0 002-2v-5M9 8h.01M9 16h.01',
+ cloud:
+ 'M2.25 15a4.5 4.5 0 004.5 4.5H18a3.75 3.75 0 001.332-7.257 3 3 0 00-3.758-3.848 5.25 5.25 0 00-10.233 2.33A4.502 4.502 0 002.25 15z',
+ power: 'M12 3v9m6.364-6.364a9 9 0 11-12.728 0',
+ download: 'M4 16v2a2 2 0 002 2h12a2 2 0 002-2v-2M7 10l5 5 5-5M12 15V3',
};
function getIcon(iconName: string) {
diff --git a/packages/shared-ui/src/navigation/PillNavigation.svelte b/packages/shared-ui/src/navigation/PillNavigation.svelte
index f7eb2e037..0920b6fc1 100644
--- a/packages/shared-ui/src/navigation/PillNavigation.svelte
+++ b/packages/shared-ui/src/navigation/PillNavigation.svelte
@@ -247,6 +247,8 @@
aiTierItems?: PillDropdownItem[];
/** Current AI tier label, e.g. "Browser" or "Server" */
currentAiTierLabel?: string;
+ /** Current AI tier icon name (passed to the dropdown trigger) */
+ currentAiTierIcon?: string;
/** Show sync status dropdown */
showSyncStatus?: boolean;
/** Sync status dropdown items */
@@ -348,6 +350,7 @@
showAiTierSelector = false,
aiTierItems = [],
currentAiTierLabel = 'KI',
+ currentAiTierIcon = 'cpu',
showSyncStatus = false,
syncStatusItems = [],
currentSyncLabel = 'Sync',
@@ -675,7 +678,7 @@
items={aiTierItems}
direction={dropdownDirection}
label={currentAiTierLabel}
- icon="cpu"
+ icon={currentAiTierIcon}
/>
{/if}