diff --git a/apps/mana/apps/web/src/lib/app-registry/apps.ts b/apps/mana/apps/web/src/lib/app-registry/apps.ts
index b388e5d62..b54e8effa 100644
--- a/apps/mana/apps/web/src/lib/app-registry/apps.ts
+++ b/apps/mana/apps/web/src/lib/app-registry/apps.ts
@@ -56,6 +56,9 @@ import {
Pulse,
Robot,
Target,
+ Flag,
+ Notebook,
+ Heartbeat,
Smiley,
Gear,
Palette,
@@ -988,6 +991,71 @@ registerApp({
},
});
+// ── AI Workbench apps — each feature is its own top-level app so it
+// can live alongside other modules in the user's scene. Colors loosely
+// group them: green=missions, amber=audit, pink=rituals, orange=policy,
+// violet=insights, emerald=health.
+
+registerApp({
+ id: 'ai-missions',
+ name: 'AI Missions',
+ color: '#22C55E',
+ icon: Flag,
+ views: {
+ list: { load: () => import('$lib/modules/ai-missions/ListView.svelte') },
+ },
+});
+
+registerApp({
+ id: 'ai-workbench',
+ name: 'AI Workbench',
+ color: '#F59E0B',
+ icon: Notebook,
+ views: {
+ list: { load: () => import('$lib/modules/ai-workbench/ListView.svelte') },
+ },
+});
+
+registerApp({
+ id: 'ai-rituals',
+ name: 'AI Rituale',
+ color: '#EC4899',
+ icon: Lightning,
+ views: {
+ list: { load: () => import('$lib/modules/ai-rituals/ListView.svelte') },
+ },
+});
+
+registerApp({
+ id: 'ai-policy',
+ name: 'AI Policy',
+ color: '#F97316',
+ icon: Flag,
+ views: {
+ list: { load: () => import('$lib/modules/ai-policy/ListView.svelte') },
+ },
+});
+
+registerApp({
+ id: 'ai-insights',
+ name: 'AI Insights',
+ color: '#8B5CF6',
+ icon: Notebook,
+ views: {
+ list: { load: () => import('$lib/modules/ai-insights/ListView.svelte') },
+ },
+});
+
+registerApp({
+ id: 'ai-health',
+ name: 'AI Health',
+ color: '#10B981',
+ icon: Heartbeat,
+ views: {
+ list: { load: () => import('$lib/modules/ai-health/ListView.svelte') },
+ },
+});
+
registerApp({
id: 'goals',
name: 'Ziele',
diff --git a/apps/mana/apps/web/src/lib/modules/ai-health/ListView.svelte b/apps/mana/apps/web/src/lib/modules/ai-health/ListView.svelte
new file mode 100644
index 000000000..da2095895
--- /dev/null
+++ b/apps/mana/apps/web/src/lib/modules/ai-health/ListView.svelte
@@ -0,0 +1,134 @@
+
+
+
+
+
+ Foreground-Runner (dieser Tab)
+
+ - Status
+ -
+ {#if isMissionTickRunning()}
+ ● aktiv · 60 s Interval
+ {:else}
+ ● nicht aktiv
+ {/if}
+
+ {#if lastRunStats}
+ - Letzter Manual-Tick
+ -
+ {lastRunStats.at} · {lastRunStats.plansProduced} Plans · {lastRunStats.errors} Fehler
+
+ {/if}
+
+
+
+
+
+ Server-Runner (mana-ai)
+
+ Läuft unabhängig vom Browser. Status + Uptime werden von Prometheus gescrapet und auf dem
+ Status-Dashboard angezeigt.
+
+
+ status.mana.how öffnen →
+
+
+
+
+ Datenlage
+
+ Alles was hier angezeigt wird kommt lokal aus IndexedDB. Kein Server-Call außer beim Planner
+ selbst.
+
+
+
+
+
diff --git a/apps/mana/apps/web/src/lib/modules/companion/pages/InsightsPage.svelte b/apps/mana/apps/web/src/lib/modules/ai-insights/ListView.svelte
similarity index 57%
rename from apps/mana/apps/web/src/lib/modules/companion/pages/InsightsPage.svelte
rename to apps/mana/apps/web/src/lib/modules/ai-insights/ListView.svelte
index 3998ed07f..615131090 100644
--- a/apps/mana/apps/web/src/lib/modules/companion/pages/InsightsPage.svelte
+++ b/apps/mana/apps/web/src/lib/modules/ai-insights/ListView.svelte
@@ -1,38 +1,13 @@
-
-
-
- Approval-Rate
- {#if approvalRate === null}
- Noch nicht genug Daten.
- {:else}
- {approvalRate}%
- über alle Missions + alle Iterationen
- {/if}
-
+
+
+ Approval-Rate
+ {#if approvalRate === null}
+ Noch nicht genug Daten.
+ {:else}
+ {approvalRate}%
+ über alle Missions + alle Iterationen
+ {/if}
+
-
- AI-Events / Tag (14 Tage)
-
- {#each perDay as b (b.day)}
-
- 0}
- >
-
+
+ AI-Events / Tag (14 Tage)
+
+ {#each perDay as b (b.day)}
+
+ 0}
+ >
+
+ {/each}
+
+
+
+
+ Pro Mission
+ {#if missionStats.length === 0}
+ Keine Missions angelegt.
+ {:else}
+
+ {#each missionStats as m (m.id)}
+ -
+ {m.title}
+
+ {m.approved}·
+ {m.rejected}·
+ {m.awaiting}·
+ {m.failed}
+
+
{/each}
-
-
+
+
+ ●approved ·
+ ●rejected ·
+ ●awaiting ·
+ ●failed
+
+ {/if}
+
-
- Pro Mission
- {#if missionStats.length === 0}
- Keine Missions angelegt.
- {:else}
-
- {#each missionStats as m (m.id)}
- -
- {m.title}
-
- {m.approved}
- ·
- {m.rejected}
- ·
- {m.awaiting}
- ·
- {m.failed}
-
-
- {/each}
-
-
- ●approved ·
- ●rejected ·
- ●awaiting ·
- ●failed
-
- {/if}
-
-
-
- Häufigstes Feedback
- {#if topFeedback.length === 0}
- Noch keine Freitext-Reviews.
- {:else}
-
- {#each topFeedback as fb}
- -
- {fb.count}×
- "{fb.text}"
-
- {/each}
-
- {/if}
-
-
-
+
+ Häufigstes Feedback
+ {#if topFeedback.length === 0}
+ Noch keine Freitext-Reviews.
+ {:else}
+
+ {#each topFeedback as fb}
+ -
+ {fb.count}×
+ "{fb.text}"
+
+ {/each}
+
+ {/if}
+
+
diff --git a/apps/mana/apps/web/src/lib/modules/companion/pages/ChatPage.svelte b/apps/mana/apps/web/src/lib/modules/companion/pages/ChatPage.svelte
deleted file mode 100644
index 0cdcc4db1..000000000
--- a/apps/mana/apps/web/src/lib/modules/companion/pages/ChatPage.svelte
+++ /dev/null
@@ -1,224 +0,0 @@
-
-
-
-
-
-
-
-
-
-
- {#if showList}
-
- {#each conversations.value as c (c.id)}
-
-
-
-
- {/each}
- {#if conversations.value.length === 0}
-
Noch keine Gespräche — klick auf „Neu".
- {/if}
-
- {:else if activeConversation}
- {#key activeConversation.id}
-
- {/key}
- {:else}
-
-
-
Mana Companion
-
-
- {/if}
-
-
-
-
diff --git a/apps/mana/apps/web/src/lib/modules/companion/pages/HealthPage.svelte b/apps/mana/apps/web/src/lib/modules/companion/pages/HealthPage.svelte
deleted file mode 100644
index 9cb2b8bad..000000000
--- a/apps/mana/apps/web/src/lib/modules/companion/pages/HealthPage.svelte
+++ /dev/null
@@ -1,179 +0,0 @@
-
-
-
-
-
-
- Foreground-Runner (dieser Tab)
-
- - Status
- -
- {#if isMissionTickRunning()}
- ● aktiv · 60 s Interval
- {:else}
- ● nicht aktiv
- {/if}
-
- {#if lastRunStats}
- - Letzter Manual-Tick
- -
- {lastRunStats.at} · {lastRunStats.plansProduced} Plans ·
- {lastRunStats.errors} Fehler
-
- {/if}
-
-
-
-
-
- Server-Runner (mana-ai)
-
- Läuft unabhängig vom Browser. Status + Uptime werden von Prometheus gescrapet und auf dem
- Status-Dashboard angezeigt.
-
-
- status.mana.how öffnen →
-
-
-
-
- Datenlage (lokal)
-
- Alles in diesem Carousel kommt aus IndexedDB. Kein Server-Call außer beim Planner selbst.
-
-
-
-
-
-
diff --git a/apps/mana/apps/web/src/lib/modules/companion/pages/PagePicker.svelte b/apps/mana/apps/web/src/lib/modules/companion/pages/PagePicker.svelte
deleted file mode 100644
index f6cb91bc8..000000000
--- a/apps/mana/apps/web/src/lib/modules/companion/pages/PagePicker.svelte
+++ /dev/null
@@ -1,138 +0,0 @@
-
-
-
-
-
-
- {#if availableIds.length === 0}
-
Alle Pages sind bereits geöffnet.
- {:else}
-
- {#each availableIds as id (id)}
- {@const m = COMPANION_PAGE_META[id]}
- -
-
-
- {/each}
-
- {/if}
-
-
-
diff --git a/apps/mana/apps/web/src/lib/modules/companion/pages/page-meta.ts b/apps/mana/apps/web/src/lib/modules/companion/pages/page-meta.ts
deleted file mode 100644
index 43394ab0e..000000000
--- a/apps/mana/apps/web/src/lib/modules/companion/pages/page-meta.ts
+++ /dev/null
@@ -1,99 +0,0 @@
-/**
- * Static metadata for every page the Companion carousel can render.
- *
- * Matches the shape modules like `/todo` use in their
- * `PAGE_META: Record` — adapted here to the
- * Companion's own page set. Colors map loosely to the activity type
- * (data: muted, creative: primary-ish, health: green, policy: orange).
- */
-
-import { Sparkle, ChatCircle, Flag, Notebook, Lightning, Heartbeat } from '@mana/shared-icons';
-import type { Component } from 'svelte';
-import type { CompanionPageId } from '../stores/workbench-settings.svelte';
-
-export interface CompanionPageMeta {
- id: CompanionPageId;
- title: string;
- shortLabel: string;
- color: string;
- icon: Component;
- description: string;
-}
-
-export const COMPANION_PAGE_META: Record = {
- home: {
- id: 'home',
- title: 'Companion',
- shortLabel: 'Home',
- color: '#6B5BFF',
- icon: Sparkle,
- description: 'Übersicht, schnelle Einstiege, letzte Aktivität.',
- },
- chat: {
- id: 'chat',
- title: 'Chat',
- shortLabel: 'Chat',
- color: '#3B82F6',
- icon: ChatCircle,
- description: 'Gespräch mit der KI.',
- },
- missions: {
- id: 'missions',
- title: 'Missions',
- shortLabel: 'Missions',
- color: '#22C55E',
- icon: Flag,
- description: 'Langlebige Aufträge an die KI anlegen, pausieren, ausführen.',
- },
- workbench: {
- id: 'workbench',
- title: 'Workbench',
- shortLabel: 'Workbench',
- color: '#F59E0B',
- icon: Notebook,
- description: 'Timeline aller KI-Aktivität; rückgängig machen.',
- },
- rituals: {
- id: 'rituals',
- title: 'Rituale',
- shortLabel: 'Rituale',
- color: '#EC4899',
- icon: Lightning,
- description: 'Geführte Routinen (Morgen, Abend, …).',
- },
- policy: {
- id: 'policy',
- title: 'Policy',
- shortLabel: 'Policy',
- color: '#F97316',
- icon: Flag,
- description: 'Pro Tool festlegen: auto / propose / deny.',
- },
- insights: {
- id: 'insights',
- title: 'Insights',
- shortLabel: 'Insights',
- color: '#8B5CF6',
- icon: Notebook,
- description: 'Approval-Raten, Feedback-Muster, Stats pro Mission.',
- },
- health: {
- id: 'health',
- title: 'Health',
- shortLabel: 'Health',
- color: '#10B981',
- icon: Heartbeat,
- description: 'Runner-Status, letzter Tick, LLM-Backend.',
- },
-};
-
-export const ALL_COMPANION_PAGE_IDS: readonly CompanionPageId[] = [
- 'home',
- 'chat',
- 'missions',
- 'workbench',
- 'rituals',
- 'policy',
- 'insights',
- 'health',
-];
diff --git a/apps/mana/apps/web/src/lib/modules/companion/stores/workbench-settings.svelte.ts b/apps/mana/apps/web/src/lib/modules/companion/stores/workbench-settings.svelte.ts
deleted file mode 100644
index b2e285f5a..000000000
--- a/apps/mana/apps/web/src/lib/modules/companion/stores/workbench-settings.svelte.ts
+++ /dev/null
@@ -1,100 +0,0 @@
-/**
- * Companion workbench settings — which pages the user currently has
- * open + their widths. Persisted to localStorage so layout survives
- * reloads; keep it tiny on purpose (no Dexie round-trip for every
- * resize).
- */
-
-export type CompanionPageId =
- | 'home'
- | 'chat'
- | 'missions'
- | 'workbench'
- | 'rituals'
- | 'policy'
- | 'insights'
- | 'health';
-
-export interface CompanionOpenPage {
- id: CompanionPageId;
- widthPx: number;
- heightPx?: number;
- maximized?: boolean;
-}
-
-const STORAGE_KEY = 'companion:openPages';
-const DEFAULT_WIDTH = 520;
-
-/** Fresh users open the home page with shortcuts to everything else. */
-const DEFAULT_OPEN_PAGES: CompanionOpenPage[] = [{ id: 'home', widthPx: DEFAULT_WIDTH }];
-
-function loadOpenPages(): CompanionOpenPage[] {
- if (typeof localStorage === 'undefined') return DEFAULT_OPEN_PAGES;
- try {
- const raw = localStorage.getItem(STORAGE_KEY);
- if (!raw) return DEFAULT_OPEN_PAGES;
- const parsed = JSON.parse(raw);
- if (!Array.isArray(parsed) || parsed.length === 0) return DEFAULT_OPEN_PAGES;
- return parsed as CompanionOpenPage[];
- } catch {
- return DEFAULT_OPEN_PAGES;
- }
-}
-
-function persist(pages: CompanionOpenPage[]): void {
- if (typeof localStorage === 'undefined') return;
- try {
- localStorage.setItem(STORAGE_KEY, JSON.stringify(pages));
- } catch {
- // ignore quota / private-mode failures — layout falls back to default next load
- }
-}
-
-function createStore() {
- let openPages = $state(loadOpenPages());
-
- return {
- get openPages() {
- return openPages;
- },
- openPage(id: CompanionPageId) {
- if (openPages.some((p) => p.id === id)) return;
- openPages = [...openPages, { id, widthPx: DEFAULT_WIDTH }];
- persist(openPages);
- },
- closePage(id: CompanionPageId) {
- openPages = openPages.filter((p) => p.id !== id);
- persist(openPages);
- },
- resize(id: CompanionPageId, widthPx: number, heightPx?: number) {
- openPages = openPages.map((p) => (p.id === id ? { ...p, widthPx, heightPx } : p));
- persist(openPages);
- },
- toggleMaximized(id: CompanionPageId) {
- openPages = openPages.map((p) => (p.id === id ? { ...p, maximized: !p.maximized } : p));
- persist(openPages);
- },
- moveLeft(id: CompanionPageId) {
- const idx = openPages.findIndex((p) => p.id === id);
- if (idx <= 0) return;
- const next = [...openPages];
- [next[idx - 1], next[idx]] = [next[idx], next[idx - 1]];
- openPages = next;
- persist(openPages);
- },
- moveRight(id: CompanionPageId) {
- const idx = openPages.findIndex((p) => p.id === id);
- if (idx === -1 || idx >= openPages.length - 1) return;
- const next = [...openPages];
- [next[idx], next[idx + 1]] = [next[idx + 1], next[idx]];
- openPages = next;
- persist(openPages);
- },
- reset() {
- openPages = [...DEFAULT_OPEN_PAGES];
- persist(openPages);
- },
- };
-}
-
-export const companionWorkbenchSettings = createStore();
diff --git a/apps/mana/apps/web/src/routes/(app)/companion/+page.svelte b/apps/mana/apps/web/src/routes/(app)/companion/+page.svelte
index b7ec0f7dc..5c1b18967 100644
--- a/apps/mana/apps/web/src/routes/(app)/companion/+page.svelte
+++ b/apps/mana/apps/web/src/routes/(app)/companion/+page.svelte
@@ -1,66 +1,39 @@
-
@@ -68,51 +41,268 @@
Companion - Mana
- (showPicker = !showPicker)}
- addLabel="Page hinzufügen"
->
- {#snippet page(p)}
- {@const pageId = p.id as CompanionPageId}
- {@const idx = openPages.findIndex((o) => o.id === pageId)}
- {@const first = idx === 0}
- {@const last = idx === openPages.length - 1}
- {@const shellProps = {
- widthPx: p.widthPx,
- maximized: p.maximized,
- onClose: () => close(pageId),
- onMaximize: () => maximize(pageId),
- onResize: (w: number, h?: number) => resize(pageId, w, h),
- onMoveLeft: first ? undefined : () => moveLeft(pageId),
- onMoveRight: last ? undefined : () => moveRight(pageId),
- }}
+
+
+
+
+
+
+ {#if activeConversation}
+ {#key activeConversation.id}
+
+ {/key}
+ {:else}
+
+
+
Mana Companion
+
+ Dein persoenlicher Assistent. Frag nach deinem Tag, lass Tasks erstellen oder Getraenke
+ loggen.
+
+
+
{/if}
- {/snippet}
+
+
- {#snippet picker()}
- p.id)}
- onPick={pick}
- onClose={() => (showPicker = false)}
- />
- {/snippet}
-
+