From 3a8c019ab03d2a400b404b70b093bafa83a25c1c Mon Sep 17 00:00:00 2001 From: Till JS Date: Tue, 14 Apr 2026 21:34:11 +0200 Subject: [PATCH] feat(ai): Missions UI under /companion/missions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Create, review, and control AI Missions from the app. Closes the last UX gap in the end-to-end pipeline — users no longer need the Dexie console to drive the Runner. - `data/ai/missions/queries.ts` — `useMissions({ state? })` live query + single-mission `useMission(id)`. Decryption-ready via `decryptRecords` wrapper (no-op today, future-proof when/if Missions get added to the crypto registry). - `routes/(app)/companion/missions/+page.svelte` - Inline create form: title + objective + markdown concept + cadence picker (manual / interval-minutes / daily-hour). Weekly + cron are wired in state but not exposed until a richer picker is worth it. - List / detail layout, responsive to narrow viewports. - Detail view: run-now button (invokes runMission with productionDeps), pause/resume/complete/delete lifecycle actions, iteration history with per-iteration feedback form. - Uses shared-icons + scoped CSS with theme-token fallbacks. - `routes/(app)/companion/+page.svelte` — footer nav links to /companion/missions and /companion/rituals from the chat sidebar. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../web/src/lib/data/ai/missions/queries.ts | 39 + .../src/routes/(app)/companion/+page.svelte | 21 + .../(app)/companion/missions/+page.svelte | 680 ++++++++++++++++++ 3 files changed, 740 insertions(+) create mode 100644 apps/mana/apps/web/src/lib/data/ai/missions/queries.ts create mode 100644 apps/mana/apps/web/src/routes/(app)/companion/missions/+page.svelte diff --git a/apps/mana/apps/web/src/lib/data/ai/missions/queries.ts b/apps/mana/apps/web/src/lib/data/ai/missions/queries.ts new file mode 100644 index 000000000..34a7a2069 --- /dev/null +++ b/apps/mana/apps/web/src/lib/data/ai/missions/queries.ts @@ -0,0 +1,39 @@ +/** + * Svelte 5 reactive queries over the `aiMissions` Dexie table. + */ + +import { useLiveQueryWithDefault } from '@mana/local-store/svelte'; +import { db } from '../../database'; +import { decryptRecords } from '../../crypto'; +import type { Mission, MissionState } from './types'; +import { MISSIONS_TABLE } from './types'; + +export interface UseMissionsOptions { + state?: MissionState; +} + +/** All non-deleted missions, newest first. */ +export function useMissions(options: UseMissionsOptions = {}) { + const { state } = options; + return useLiveQueryWithDefault(async () => { + const all = await db.table(MISSIONS_TABLE).orderBy('createdAt').reverse().toArray(); + const visible = all.filter((m) => !m.deletedAt); + const filtered = state ? visible.filter((m) => m.state === state) : visible; + // Decrypt user-typed fields if the table ever gets added to the crypto + // registry. Today it isn't, so this is a no-op; keeps the hook future-proof. + return decryptRecords(MISSIONS_TABLE, filtered) as Promise; + }, [] as Mission[]); +} + +/** Single mission by id, reactively. */ +export function useMission(id: string) { + return useLiveQueryWithDefault( + async () => { + const m = await db.table(MISSIONS_TABLE).get(id); + if (!m || m.deletedAt) return null; + const [decrypted] = await decryptRecords(MISSIONS_TABLE, [m]); + return decrypted as Mission; + }, + null as Mission | null + ); +} 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 e9e9f54b5..f53435f20 100644 --- a/apps/mana/apps/web/src/routes/(app)/companion/+page.svelte +++ b/apps/mana/apps/web/src/routes/(app)/companion/+page.svelte @@ -88,6 +88,11 @@

Noch keine Gespraeche. Starte mit dem + Button.

{/if} + + @@ -283,4 +288,20 @@ .start-btn:hover { filter: brightness(1.1); } + + .sidebar-footer { + padding: 0.75rem 1rem; + border-top: 1px solid var(--color-border, #ddd); + display: flex; + flex-direction: column; + gap: 0.375rem; + } + .sidebar-footer a { + color: var(--color-muted, #888); + text-decoration: none; + font-size: 0.8125rem; + } + .sidebar-footer a:hover { + color: var(--color-primary, #6b5bff); + } diff --git a/apps/mana/apps/web/src/routes/(app)/companion/missions/+page.svelte b/apps/mana/apps/web/src/routes/(app)/companion/missions/+page.svelte new file mode 100644 index 000000000..78d503965 --- /dev/null +++ b/apps/mana/apps/web/src/routes/(app)/companion/missions/+page.svelte @@ -0,0 +1,680 @@ + + + + + Missions - Companion + + +
+ + + {#if showForm} +
(e.preventDefault(), handleCreate())}> + + + + + + +
+ Cadence +
+ + + +
+
+ +
+ +
+
+ {/if} + +
+ + +
+ {#if selected} +
+

{selected.title}

+
+ + {#if selected.state === 'active'} + + {:else if selected.state === 'paused'} + + {/if} + {#if selected.state !== 'done'} + + {/if} + +
+
+ +
+
Ziel
+
{selected.objective}
+
Cadence
+
{describeCadence(selected.cadence)}
+
Nächster Run
+
{formatRelative(selected.nextRunAt)}
+
+ + {#if selected.conceptMarkdown} +
+

Konzept

+
{selected.conceptMarkdown}
+
+ {/if} + +
+

Iterationen

+ {#if selected.iterations.length === 0} +

Noch keine Iteration gelaufen.

+ {:else} + {#each [...selected.iterations].reverse() as it (it.id)} +
+
+ {new Date(it.startedAt).toLocaleString('de-DE')} + {it.overallStatus} +
+ {#if it.summary} +

{it.summary}

+ {/if} + {#if it.plan.length > 0} +
    + {#each it.plan as step} +
  • + [{step.status}] + {#if step.summary} + {step.summary} + {:else if step.intent.kind === 'toolCall'} + {step.intent.toolName} + {:else} + Notiz + {/if} +
  • + {/each} +
+ {/if} + {#if it.userFeedback} + + {:else if !it.finishedAt || it.overallStatus === 'awaiting-review'} + + {/if} +
+ {/each} + {/if} +
+ {:else} +

Wähle links eine Mission aus, oder erstelle eine neue.

+ {/if} +
+
+
+ +