Commit graph

2000 commits

Author SHA1 Message Date
Till JS
334c36a68e docs: document reasoning loop, research pre-step, debug log, new tools
Updates apps/mana/CLAUDE.md AI Workbench section with:
- Reasoning loop (5-round auto→propose chain)
- Cross-module proposal inbox in mission detail
- Kontext auto-inject
- Web-research pre-step (RSS via news-research)
- Debug log (local-only _aiDebugLog + AiDebugBlock panel)
- New proposable tools: save_news_article, list_notes, update_note,
  append_to_note, add_tag_to_note

Adds §23 to COMPANION_BRAIN_ARCHITECTURE.md covering the full
architecture: loop algorithm pseudocode, research pre-step rationale
(RSS over deep-research), kontext auto-inject privacy boundary,
debug log schema + UI + toggle mechanics, and new tool inventory.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 11:50:21 +02:00
Till JS
9161c0b3ab feat(templates): two more non-AI templates + split gallery into two sections
Closes out T1 with three templates per category as discussed. The
gallery now renders agent-templates and workbench-templates as two
distinct labeled sections — the earlier implicit "everything's a
template for an agent" framing is gone.

Seed handlers (new):
- apps/mana/apps/web/src/lib/modules/habits/seed.ts — title-based
  idempotency (there's no description column on LocalHabit). If a
  non-deleted habit with the same title exists, the seed is skipped.
- apps/mana/apps/web/src/lib/companion/goals/seed.ts — title-based
  idempotency on companionGoals where status !== 'abandoned'.
- Both pulled in via side-effect imports in missions/setup.ts so the
  handler registry is populated before any apply.

New templates:
- 🏋️ Fitness (wellness) — scene body/habits/stretch/sleep + 3 habit
  seeds (Täglich 30min Bewegung, 3× Woche Training, 2L Wasser) + 1
  goal seed (3 Workouts pro Woche). No agent.
- 💻 Deep Work (work) — scene todo/calendar/notes/times + 2 habit
  seeds (1 wichtigste Aufgabe pro Tag, 4h Deep Work pro Tag) + 1
  goal seed (20h Deep Work pro Woche). No agent.

Gallery two-section layout:
- Title "Templates" (not "Agent-Templates") — broader framing.
- Section 1: "🤖 Agent-Templates" — filters ALL_TEMPLATES where
  category ∈ {'ai','delight'}: Recherche-Agent, Kontext-Agent,
  Today-Agent.
- Section 2: "🎨 Workbench-Templates" — filters to the rest:
  Calmness, Fitness, Deep Work.
- Each section gets a short intro paragraph so users understand the
  distinction before scanning the cards.
- Cards themselves unchanged; rendering extracted into a
  {#snippet templateCard(t)} shared between both sections.
- Per-category arrays computed once at module-load time (const in
  <script>); no per-render filter cost.

Result: each section has 3 templates, categorised by "does this
create an AI agent" rather than by use-case. Keeps the separation
honest — Agent-Templates set up autonomous work; Workbench-Templates
set up the user's own workspace.

Tests: shared-ai 26/26, webapp svelte-check 0 errors, 0 warnings.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 11:45:40 +02:00
Till JS
4f76d3926e feat(workbench): ?app=<appId> deep-links into the active scene
After consolidating Settings into a workbench app, links from the
command menu, pill-nav, AI-tier dropdown, onboarding CTAs, and the
sub-route back-buttons had nowhere specific to go — they pointed at
`/` and dumped users at their home scene with no way to auto-open the
settings app.

- Home (+page.svelte) reads ?app=<id> on mount. If the id is a
  registered workbench app and not already in the active scene,
  addApp() it. Then scroll the carousel to that page. Strip the
  query out of history.replaceState so refresh doesn't re-open.
- Update all settings redirects (command menu, onboarding,
  AI-settings dropdown, settings/sync + settings/my-data back-links
  and breadcrumbs) to `/?app=settings`.

Unlocks: future deep-links to any app (profile, themes, spiral,
credits, …) without needing a standalone route. `?app=X&section=Y`
is the natural next step — left for when an app needs it.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 01:10:10 +02:00
Till JS
a08e45ca16 feat(templates): generalise to WorkbenchTemplate + ship Calmness pilot (T1)
First pass of the workbench-templates plan (docs/plans/workbench-
templates.md) — templates are no longer agent-centric but a general
"starter kit" bundle: optional agent + optional scene + optional
missions + optional per-module seeds. Pilot non-AI template "Calmness"
ships alongside.

Shape generalisation (packages/shared-ai/src/agents/templates/types.ts):
- AgentTemplate renamed to WorkbenchTemplate; all fields now optional
  (agent, scene, missions, seeds). Back-compat AgentTemplate alias
  kept so research/context/today keep compiling.
- Added `category: 'ai'|'wellness'|'work'|'lifeEvent'|'delight'` +
  `icon` (for non-agent templates that have no avatar) + `version`
  field (for future update-detection).
- New WorkbenchTemplateSeedItem shape: `{stableId?, data: unknown}`.
  Module-specific seed payloads are typed at the handler side.
- Existing three AI templates nachgezogen: category='ai' (or
  'delight' for today-agent), icon, version='1'.

Seed infrastructure:
- apps/mana/apps/web/src/lib/data/ai/agents/seed-registry.ts — in-
  memory handler map keyed by module name; module-local seed.ts files
  register themselves at import time.
- apps/mana/apps/web/src/lib/modules/meditate/seed.ts — first handler:
  createPreset-based, idempotent via stableId embedded as HTML
  comment in the preset description (T1 pragmatism; T2 adds a proper
  column on the preset schema).
- data/ai/missions/setup.ts pulls `import '$lib/modules/meditate/seed'`
  so the handler is registered before any template is applied.

Applicator upgrades (data/ai/agents/apply-template.ts):
- Agent step now optional — skipped cleanly when template has no
  agent part.
- New step 4: seeds. Walks template.seeds, looks up the handler for
  each module, aggregates per-item outcomes (created/skipped-exists/
  failed) into result.seedOutcomes. Missing handler = warning, not
  fatal. Crypto/encryption unchanged — seeds go through the same
  module stores that module code already uses.
- Result shape gains `seedOutcomes: Record<string, SeedOutcome[]>`
  so the gallery can show "3 new, 1 already there".

Calmness pilot (packages/shared-ai/src/agents/templates/calmness.ts):
- category='wellness', NO agent, scene with meditate/mood/journal/
  sleep apps, two meditate preset seeds:
  * 4-7-8 Atmung (breathing preset)
  * Body-Scan 10min (bodyscan preset with 9 scan steps)
- Each seed has a stableId so re-apply is idempotent.

Gallery updates (routes/(app)/agents/templates/+page.svelte):
- Card avatar falls back to t.icon when no agent. "Agent" chip shows
  only for agent-templates; "N Seeds" chip shows for templates with
  seeds.
- Detail header shows "Workbench-Setup ohne AI-Agent" when no agent.
- New "Seeds" preview section: lists per-module counts + item names.
- Options section gains a "Seed-Daten in Module einpflegen" checkbox.
- Success panel shows seed summary: "3 Seeds neu, 1 bereits
  vorhanden".

Tests: shared-ai 26/26, webapp svelte-check 0 errors, 0 warnings.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 01:07:41 +02:00
Till JS
a524997a2f refactor(tailwind): centralize @source list in @mana/shared-tailwind/sources.css
Each consuming app was duplicating eight @source directives with hand-
counted relative paths (../../../../../packages/…). The mana web app's
were off-by-one for months before anyone noticed, silently disabling the
scan for every shared-ui file.

Tailwind v4 resolves @source paths relative to the CSS file that declares
them, so we can drop the list once into packages/shared-tailwind/src/
sources.css. Consumer apps now just add one more @import next to themes.css:

  @import "tailwindcss";
  @import "@mana/shared-tailwind/themes.css";
  @import "@mana/shared-tailwind/sources.css";

New package.json export: "./sources.css". Drop the local paths from the
mana web app's app.css.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 00:59:33 +02:00
Till JS
8a5d200c84 fix(ai): bump planner maxTokens 1024→4096 + teach prompt about the loop
Debug log from a "tag 4 notes" mission showed the planner's second-round
response truncated mid-step: it was proposing one add_tag_to_note per
listed note but ran out of tokens halfway through note #2. Parser
rejected the malformed JSON → loop exited with 0 staged, user saw
nothing to approve.

Raising maxTokens to 4096 fits ~15-20 step objects, which covers the
batch-tagging / batch-save pattern the reasoning loop is designed for.

Also updating the system prompt so the planner actually knows about
the loop it's running inside: read-only tools are announced as
auto-executing with outputs visible next turn, and a new rule makes
explicit that batch jobs must emit all write-steps in one plan (because
staging a propose-tool ends the turn). Step count raised 1-5 → 1-10.

Prompt snapshot tests still pass (they check structure, not text).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 00:55:18 +02:00
Till JS
8299bf004d feat(ai): reasoning loop — agent chains auto tools before asking for approval
The runner was one-shot: one planner call per iteration, no feedback
from tool outputs. "Lies alle Notizen und tagge sie" needed two manual
runs (list_notes, then tagging) because the planner never saw the
list_tasks output.

Now runMission loops up to MAX_REASONING_LOOP_ITERATIONS (5):

  loop: plan → classify steps by policy
        │
        ├─ auto  → execute inline, capture {message, data}, feed back
        │          as a synthetic ResolvedInput for the next planner call
        │
        ├─ propose → stage proposal, mark humanInLoop, EXIT after this round
        │            (human has to approve before we plan further — we don't
        │             know what they'll accept yet)
        │
        └─ none/0-steps → agent considers the task done, EXIT

Tool outputs become a ResolvedInput titled "Zwischenergebnisse (Runde N)"
so the planner sees them structured and labelled. StageOutcome gains
`autoData` + `autoMessage` so the loop can thread the executor's
payload back through without a second call.

AiDebugEntry now holds `plannerCalls[]` and `loopSteps[]` instead of a
single planner snapshot — so Debug-Panel shows every LLM round + every
auto-tool output, each collapsible. Summary chip shows "3× LLM · 4200ms
· 2× Auto-Tool" when a loop ran.

Side-effects for existing use cases:
- One-shot missions (single propose tool) behave identically (loop
  exits after round 1 with humanInLoop=true).
- "Tag all notes" missions now finish in a single run: loop iter 1
  runs list_notes auto, iter 2 stages N add_tag_to_note proposals,
  exits.
- Server-side mana-ai runner NOT touched — this is foreground-only
  for now; the server still runs one plan/tick.

All 8 runner.test.ts tests pass unchanged (the existing test suite
only exercises the single-step path, which is a subset of the loop).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 00:43:52 +02:00
Till JS
e440f13867 feat(workbench): welcome placeholder for the Home scene description
When the seeded default scene ("Home") has no description yet, the
empty contenteditable placeholder shows "Willkommen Zuhause im Mana
Hub" instead of the generic "Beschreibung hinzufügen…". Rename the
scene → generic prompt returns.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 00:41:07 +02:00
Till JS
531190953f chore(theme): theme-track shared-auth-ui primaryColor + drop dead tailwind.config.js
shared-auth-ui components (PasskeyManager, SessionManager, TwoFactorSetup,
AuditLog, ChangePassword, SecurityOnboarding) take a primaryColor prop
that gets written as inline --primary-color on the component root. The
mana app was passing a literal "#6366f1" (indigo) everywhere, so the
Security tab and auth pages used a fixed indigo accent that ignored the
user's theme. Pass "hsl(var(--color-primary))" instead — the nested var
resolves live, so theme switches carry through.

tailwind.config.js in apps/mana/apps/web was fully vestigial:
- no @config directive in app.css, no reference from vite/svelte configs
- imported @mana/shared-tailwind/preset, which the package doesn't export
- only themes.css is exported, and it's loaded directly from app.css
Tailwind v4 with CSS-first config is the real setup. Delete the file
and remove its entry from SETUP.md.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 00:37:11 +02:00
Till JS
7822340ea0 feat(ai-agents): Template gallery — 3 ready-to-use agent bundles
First pass of the Multi-Agent discoverability UX. A new /agents/
templates route showcases pre-configured agents; clicking one creates
agent + scene + starter mission(s) as a single bundle. Addresses the
"blank form anxiety" + "user doesn't know what agents are for"
observations from the UX brainstorm.

Three templates for v1 (shared-ai/src/agents/templates/):
- 🔍 Recherche-Agent — reads sources one by one, writes a note per
  source, summarizes into a report. Manual-cadence mission; all
  writes propose so user curates.
- 🧭 Kontext-Agent — learns about the user via a weekly check-in.
  Reads kontext/notes/goals, asks 2-3 questions, proposes a diff-
  style context update. Weekly Sunday cadence.
- 🌅 Today-Agent — researches "on this day" history each morning,
  writes a 4-8 line German poem, proposes a journal note. Daily 7am
  cadence. A "delight" agent, not a productive one.

Each template packs (agent config, scene layout, starter mission):
- AgentTemplate type lives in @mana/shared-ai — pure data, no runtime
  imports. Adding a new template = drop a file in templates/ and
  extend ALL_TEMPLATES.
- Template-specific policies derive from the proposable-tool list so
  drift-guard catches divergence from the canonical set.
- Starter missions default to startPaused=true — user sees the
  mission ready-to-go and hits Play when ready. Prevents surprise
  autonomous work on first apply.

Applicator (data/ai/agents/apply-template.ts):
- Creates agent → scene (if template defines one) → missions in
  order. Agent failure = abort; scene/mission failures surface as
  warnings in the result without blocking.
- Duplicate-name handling: falls through to findByName, returns
  existing agent with wasExisting=true; scene is skipped in that
  case to avoid clone-proliferation.

Gallery page /(app)/agents/templates/+page.svelte:
- Three large cards side-by-side (stacks on mobile) with avatar /
  label / tagline / meta chips (Scene, N Missionen).
- Click opens detail panel with full description, scene preview
  (app-ids + widths), mission preview (title / objective / cadence),
  and override checkboxes (create scene, create missions, start
  active vs paused).
- Success panel shows what landed with warnings inline; CTA back to
  workbench.

Discoverability in /ai-agents module:
- Bar now has two buttons: "Aus Template" (primary, goto templates
  route) + "Eigener Agent" (secondary, opens the existing blank-form
  create mode).
- When only the default "Mana" agent exists, render a dashed promo
  banner at the top linking to the template gallery. Disappears as
  soon as the user has a second agent.

Tests: webapp svelte-check 0 errors, 0 warnings. shared-ai 26/26.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 00:36:39 +02:00
Till JS
4d9b16a683 feat(notes): list + update + append + add_tag tools for the AI
Makes the "read all notes and tag them #Natur/#Technologie/…" use case
fully functional. Four new ModuleTool entries in notes/tools.ts:

- list_notes(limit?, query?, includeArchived?) — auto, read-only. Returns
  id + title + excerpt so the planner can reference concrete notes
  without dumping full bodies.
- update_note(noteId, title?, content?) — proposable. Destructive full
  overwrite. Docstring nudges toward append_to_note when applicable.
- append_to_note(noteId, content) — proposable, non-destructive. Handles
  the trailing-newline separator so markdown stays clean.
- add_tag_to_note(noteId, tag) — proposable, idempotent, case-insensitive.
  Strips leading #, replaces spaces with _, skips if already present.
  Exactly the categorization primitive the user asked for.

All three writes are added to AI_PROPOSABLE_TOOL_NAMES so both the
webapp policy and mana-ai's boot-time drift guard agree (now 11 tools).
Mirrored in services/mana-ai/src/planner/tools.ts.

AiProposalInbox mounted on /notes so approvals land inline in the
notes module too (already appears in the mission-detail cross-module
inbox via the earlier commit).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 00:24:48 +02:00
Till JS
cd22e42afc refactor(theme): migrate all remaining bare shadcn tokens to --color-*
Follow-up to the shared-packages migration. Covers 61 more files
across the mana web app (lib/components, lib/modules, routes) and
the same handful of packages, including surface / error / success /
warning tokens that were missed in the first pass.

Two patterns migrated:
1. hsl(var(--X[, raw-channels])) → hsl(var(--color-X[, raw-channels]))
2. var(--X, #hex)               → hsl(var(--color-X))
   (the hex fallback was only doing work because the CSS var was
   undefined; now that the theme resolves cleanly, the fallback is
   unnecessary and actively wrong — it never tracked the active theme)

Module-literal accents (news-research's #0891b2 / #10b981) left
untouched per the themes.css convention for brand-semantic colors.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 22:56:59 +02:00
Till JS
fdd643f4b4 feat(news-research): RSS feed discovery, filter, and AI-context export
New sibling module to news/. Discovers topic-matched RSS feeds via
SearXNG (mana-search) or rel="alternate" probing of a site URL,
filters articles by keyword with a recency + title-match boost,
and exports the top hits as a markdown context block for the AI.

- API: /api/v1/news-research/{discover,validate,search,extract}
- Frontend: /news-research route + workbench ListView (compact card)
- Tool: research_news LLM tool (read-only, runs auto)
- Pin feeds → newsPreferences.customFeeds (encrypted) — covers the
  long-missing custom-RSS subscription gap; reading-list saves still
  go through articlesStore.saveFromUrl into the existing newsArticles
- shared-branding: new news-research entry + binoculars icon

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 22:31:07 +02:00
Till JS
b768a0ffce refactor(shared-rss): extract RSS parsing + Readability into one package
news-ingester and apps/api both shipped their own copy of rss-parser
+ jsdom + Readability glue. Single source now in packages/shared-rss.
Adds discoverFeeds (rel=alternate + common-paths probe) and validateFeed
which News Research will use. JSDOM virtualConsole is silenced once,
in the package, instead of in two parallel call sites.

- packages/shared-rss: parse, extract, discover, validate
- services/news-ingester: drop local parsers, depend on @mana/shared-rss
- apps/api: drop @mozilla/readability + jsdom direct deps, use shared

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 22:30:44 +02:00
Till JS
5ae7f99fe1 fix(css): correct @source paths — Tailwind was scanning no shared packages
app.css lives at apps/mana/apps/web/src/app.css — five directory
levels below the repo root. All existing @source directives used
four ../ segments, resolving to apps/packages/... which does not
exist. Tailwind silently scanned nothing.

Consequence: arbitrary-value utilities only used inside shared
packages (e.g. bg-[hsl(var(--color-muted))] in GlobalSettingsSection)
were never generated. Standard utilities kept working because they
also appear in app sources, but the language / week-start / sounds
buttons in the settings Allgemein tab rendered transparent.

- Bump every @source to 5x ../ so it points into /packages
- Drop the non-existent shared-subscription-ui entry
- Add subscriptions and credits, whose .svelte files also use
  arbitrary-value classes

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 22:17:15 +02:00
Till JS
6da317d071 refactor(theme): migrate shared packages to --color-* token naming
Several shared-ui / shared-auth-ui / subscriptions / credits
components used shadcn-style bare CSS variables (--muted, --primary,
--foreground, etc.), but the Mana theme system standardized on
--color-*. The mismatch meant bg-[hsl(var(--muted))] classes
resolved to an invalid color and rendered transparent — most
visible on the Allgemein settings tab where language and week-start
buttons had no background.

Mechanical prefix across ~30 files. Two semantic renames:
- --destructive → --color-error (Mana uses "error" as the token name)
- --popover     → --color-card  (no popover token; card is the closest)

With shared packages on the correct naming, drop the shadcn-compat
alias shim from app.css.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 22:11:42 +02:00
Till JS
7c89eb625e feat(ai): workbench agent filter + proposal agent chip + docs (Phase 6+7)
Phase 6 — Multi-Agent observability:
- AI Workbench timeline gets a per-agent filter (dropdown with avatars)
  alongside module + mission. TimelineBucket gains agentId +
  agentDisplayName, projected off the bucket's first AI actor.
- Bucket header now leads with the agent's avatar + name (lookup via
  the live useAgents query so renamed agents reflect instantly) and
  falls back to Actor.displayName for deleted agents.
- AiProposalInbox card header replaces the generic Sparkle + "KI
  schlägt vor" with an agent chip "🤖 Cashflow Watcher schlägt vor"
  using the cached Actor.displayName. Ghost-agent label preserved
  via the cached displayName even when the agent record is gone.

Phase 7 — Docs:
- docs/architecture/COMPANION_BRAIN_ARCHITECTURE.md §22 added:
  data model, identity flow, tick gate order, Scene-Agent binding
  semantics, non-goals.
- services/mana-ai/CLAUDE.md status bumped to v0.5 (Multi-Agent
  Workbench) with the per-agent runner features + metrics listed.
- apps/mana/CLAUDE.md AI Workbench section rewritten to cover the
  Agent primitive, per-agent policy, scene lens, and the updated
  timeline header.

Multi-Agent rollout is code-complete end-to-end:
  Phase 0 Plan ✓  Phase 4 Policy-per-agent ✓
  Phase 1 Actor identity ✓  Phase 5 Agent UI + Scene lens ✓
  Phase 2 Agent CRUD ✓  Phase 6 Observability ✓
  Phase 3 Tick agent-aware ✓  Phase 7 Docs ✓

Tests: webapp svelte-check 0 errors, 0 warnings.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 22:08:42 +02:00
Till JS
98668b69a2 fix(ai): swap web-research pre-step from deep-research → news-research RSS
Debug log on a "Recherchiere News über Adult Learning" mission showed
the deep-research pipeline (mana-search + LLM synthesis) returned 0
sources but reported success — planner then either hallucinated or
fell back to create_task. The webapp already has a documented
RSS-based research path in news-research/{discoverByQuery,searchFeeds}
that's faster, free (no credits), and matches the companion-flow
contract written in news-research/tools.ts: "research_news → save_news_article".

Now:
- Pre-step calls discoverByQuery + searchFeeds directly.
- Empty discovery / empty results throw an explicit error so the
  failure-injection branch surfaces it to the planner instead of
  silently feeding a "0 sources" success message.
- Injected ResolvedInput now carries explicit instructions ("rufe für
  jeden relevanten Artikel save_news_article auf — erfinde keine
  URLs") so the planner doesn't have to infer the next move.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 22:07:29 +02:00
Till JS
cda70b6a81 fix(css): alias bare --muted/--primary etc. to --color-* tokens
GlobalSettingsSection (and a few other shared-ui/auth-ui components)
reference shadcn-style bare CSS variables like --muted and --primary,
but this app standardized on --color-muted/--color-primary. The
mismatch meant classes like bg-[hsl(var(--muted))] resolved to an
invalid color → buttons in the Allgemein settings tab rendered
without a background.

Add one-way aliases in :root so the bare names resolve to the
color-prefixed ones. No changes to shared-ui required.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 21:57:29 +02:00
Till JS
51e6a20daf feat(ai-agents): Agents UI + Scene binding + Mission picker (Phase 5)
The Multi-Agent Workbench becomes visible. User can now create named
agents with their own persona (name, avatar, role), policy, memory,
and budgets; missions are created against a specific agent; scenes
can be bound to an agent so the Workbench tab shows who's at home.

New UI:
- /ai-agents module (ListView with list/create/detail modes, mirroring
  the ai-missions pattern). Detail view exposes:
  * Profile (name, avatar emoji, role) + pause/resume/archive/delete
  * Behavior (systemPrompt + memory — both encrypted at rest per the
    registry)
  * Limits (maxConcurrentMissions, maxTokensPerDay)
  * Policy editor: global default (auto/propose/deny) + per-module
    overrides for the usual suspects (todo / calendar / notes / …).
    Three quick-apply templates: Standard, Cautious, Aggressive.
  * Clear save path with duplicate-name handling from the store.
- Registered under the 'ai' category alongside the existing AI apps.

New component: AgentPicker — compact <select> over active agents plus
an optional "— keiner —" slot. Reused in:
- ai-missions create flow (new "Agent" fieldset — who executes this
  mission; passes agentId through to createMission so the very first
  server-iteration writes land under the right principal).
- BindAgentDialog — scene context-menu entry "An Agent binden…"
  opens this dialog. Purely a UI lens: scene.viewingAsAgentId ends up
  set, SceneAppBar now renders the agent's avatar on the scene tab
  next to the name (tooltip on hover). No data-scope change.

Store updates:
- WorkbenchScene gains viewingAsAgentId?; patchScene accepts it;
  workbenchScenesStore.setSceneAgent(id, agentId|undefined) helper.
- CreateMissionInput gains agentId?; the ai-missions create form
  passes it. MissionPatch also extended so future editors can reassign
  a mission to a different agent.

Tests: webapp svelte-check 0 errors, 0 warnings.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 21:56:02 +02:00
Till JS
cacbfb0764 fix(settings): wire ListView to GeneralSection instead of inlining
ListView rendered GlobalSettingsSection directly for the 'general' tab,
so the header unification in GeneralSection.svelte never took effect —
the sticky "App-Einstellungen" pill still leaked through.

- Swap inline block for <GeneralSection />
- Carry showTheme={false} into GeneralSection (Themes is its own app)
- Drop now-unused GlobalSettingsSection/userSettings/onMount/SettingsPanel imports

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 21:52:18 +02:00
Till JS
aebbcdd1c3 refactor(settings): unify section headers across tabs
Every tab now leads with the same SettingsSectionHeader
(icon-circle + title + description), matching Credits and Data:

- AiSection wraps AiSettings with SettingsSectionHeader (Robot, indigo);
  AiSettings's inline icon+title is removed
- GeneralSection wraps GlobalSettingsSection with SettingsSectionHeader
  (Gear); suppresses the inner sticky pill header via title=""
- SecuritySection adds one top-level SettingsSectionHeader
  (ShieldCheck, blue) above the four sub-panels

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 21:48:41 +02:00
Till JS
f2faaf1387 refactor(pill-nav): drop Settings entry, guard on settingsHref
Settings lives in the Workbench now — no longer needs its own pill.
settingsHref becomes optional; both places that render the entry
(user-menu link list + nav overflow dropdown) skip it when unset.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 21:43:29 +02:00
Till JS
f7426ab40f feat(ai): policy is read from the owning agent (Phase 4)
Until now AiPolicy lived as a user-global setting consulted for every
AI action. With agents as the principal unit of AI behavior, policy
belongs on the agent — different agents can be aggressive about tasks
but conservative about calendar edits, etc.

Webapp (tools/executor.ts):
- When an AI actor invokes a tool, the executor looks up the owning
  agent via getAgent(actor.principalId) and passes agent.policy into
  resolvePolicy. Falls back to DEFAULT_AI_POLICY when the agent record
  is missing (legacy write, deleted agent, race) so no tool call can
  silently bypass the propose/deny path.
- resolvePolicy already accepted an optional policy arg, so the call
  site change is a single line plus the agent load.

Server (mana-ai):
- ServerAgent gains an optional policy field, projected off the same
  plaintext JSONB that the webapp writes.
- Tick loop filters AI_AVAILABLE_TOOLS through filterToolsByAgentPolicy
  before passing them to the planner prompt. Resolution order mirrors
  the webapp: tools[name] → defaultsByModule → defaultForAi; 'deny'
  drops the tool so the LLM never even sees it.

Phase 5 will surface a per-agent policy editor on the agent-detail
UI. Until then all agents inherit DEFAULT_AI_POLICY (baked in during
createAgent), which means no behavior change for existing users —
every tool that was 'propose' before is still 'propose' now, just
reached via agent.policy instead of the user-level singleton.

Tests: mana-ai 41/41, webapp svelte-check clean.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 21:43:04 +02:00
Till JS
968e08059f chore(workbench): delete dead SceneTabs component
SceneTabs.svelte had no remaining importers — the homepage
switched to SceneAppBar long ago, and nothing else in the repo
referenced it (grep for imports comes back empty). It still
carried its own rendering logic, context menu, and rename-dialog
wiring, which kept drifting from the real bar (e.g. the scene
icon field removal yesterday had to touch it even though the
file is never mounted). Remove it outright; any future compact
scene tab ever needed can be re-added fresh against the current
store shape.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 21:34:54 +02:00
Till JS
32f4c0d10d feat(page-carousel): empty-state message when scene has no apps
When the scene has zero apps the carousel used to show just a
small "+ Hinzufügen" button centered in 60vh of whitespace —
visually underpowered and easy to miss on mobile. Replace with a
proper empty state:

- Heading: "Diese Szene ist leer"
- Hint line: "Füge eine App hinzu, um loszulegen — oder drücke [0]"
- The existing add-card button sits underneath

The kbd chip styles match the layout's typical keyboard hint
pattern and quietly teach the '0' shortcut that already opens
the picker (registered at +page.svelte keybind handler).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 21:34:39 +02:00
Till JS
db959b6f8f feat(workbench): auto-scroll on scene switch, unify rename to inline
Two tightly coupled UX fixes on the homepage:

1. Switching scenes via SceneAppBar used to leave the carousel
   parked wherever it was before — the new scene's SceneHeader
   appeared off-screen to the left, and the user saw a seemingly
   stale row of cards until they manually scrolled. Now an
   $effect watches activeSceneId, tracks the last seen value, and
   smooth-scrolls the .fokus-track to left=0 on every real change
   (ignoring the initial hydration tick so we don't fight the
   carousel's own centering).

2. Scene rename had two concurrent paths: the SceneHeader
   contenteditable <h1> (live DOM) and a SceneRenameDialog modal
   opened from the scene-pill context menu (reads from the store).
   If a user was mid-edit inline and right-clicked Umbenennen,
   the dialog opened with the pre-edit value and on save clobbered
   the inline change. The modal is gone. The context-menu
   "Umbenennen" entry now switches to the target scene, scrolls
   the carousel to the header, and focuses the contenteditable
   after a 120ms tick so the scroll has time to start. Single
   source of truth, single code path.

SceneRenameDialog.svelte + the sceneDialog state machine in
+page.svelte are removed. ConfirmDialog (used for delete) is
untouched.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 21:34:22 +02:00
Till JS
f06ca2c7c3 feat(ai-missions): inline AiProposalInbox in mission detail (cross-module)
Mission detail now renders all pending proposals for that mission,
across every module, directly above the iteration list. No more jumping
between /todo, /news, /calendar to approve what the agent staged.

AiProposalInbox.module is now optional; when omitted, every card grows
a small lowercase module badge (e.g. "news", "todo") so the user knows
where each proposal will land on approve. The existing per-module
inboxes on /todo, /news, /calendar still work — proposals show in both
places via the same live query, so approving in one auto-clears the
other.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 21:33:06 +02:00
Till JS
988c17a678 feat(quiz): edit existing questions + wire up guest seed
- Pencil button on each question opens the bottom form pre-filled;
  submit updates in place instead of appending.
- Guest users now see the demo quiz on first visit (QUIZ_GUEST_SEED
  registered with seedAllGuestData).
- Silence state_referenced_locally warnings with svelte-ignore to
  match the pattern used in cards / landing / rsvp views.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 21:24:53 +02:00
Till JS
3b99356464 feat(quiz): new Quiz module — build & play private quizzes (Phase 1)
Four question types (single/multi/truefalse/text), inline editor,
play view with per-question feedback + final score review. Attempts
are persisted per quiz. Encrypted at rest: title/description/tags on
the container, questionText/explanation/options on questions.
Attempts stay plaintext. Dexie v21, appId 'quiz', tier 'guest'.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 20:54:07 +02:00
Till JS
8b6b73627c feat(page-carousel): five-preset width picker replaces drag handle
PageShell drops the bottom-right drag handle and its pointer/touch
tracking entirely, and adds a small header button (ArrowsOutLineHorizontal)
that opens a menu with five discrete widths: XS 340 · S 440 · M 540 ·
L 720 · XL 960. Each entry shows its label and pixel value and
highlights whichever one is currently closest to the persisted
widthPx — so legacy freehand values (e.g. 823) still light up the
nearest entry when the menu first opens.

New module width-presets.ts holds the preset array and a
nearestPresetIndex() helper so the same snapping logic isn't
duplicated between PageShell and the store.

Also drops the .page-shell.resizing style (and the `resizing`
state, the shellEl ref, and the MIN/MAX width/height constants)
that only existed to colour the shell while a drag was active.

The five presets cover the useful width range without wasting
space on tiny viewport-sized differences; anyone wanting an
uncommon width can adjust widthPx directly in Dexie for now.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 20:37:19 +02:00
Till JS
681136266b refactor(workbench): drop per-card height and free-form resize
Height was practically unused — most WorkbenchSceneApp rows had
heightPx: undefined and scrolled internally, and the few explicit
values nobody ever revisited were just noise in the sync ledger.
Same goes for pixel-precise drag: users would produce widths like
823px and then never touch them again. Both paths come out in
favour of a fixed set of five width presets (next commit) that
guarantee cards sit on sensible sizes without a decision per card.

- types/workbench-scenes: heightPx removed from WorkbenchSceneApp
- page-carousel/types: heightPx removed from CarouselPage
- stores/workbench-scenes: resizeApp(appId, widthPx) loses its
  height param; patchScene allowlist no longer lists heightPx.
  Existing Dexie rows with heightPx set simply stop being read
  — the field ages out the next time the row is written.
- PageCarousel: placeholder renders width-only; comment updated
- AppPage: heightPx prop + onResize height param gone
- +page.svelte: carouselPages no longer carries heightPx, handler
  signature narrowed to (id, widthPx)

This commit removes the data; the next commit rewires PageShell's
UI so the drag handle is replaced by the five-preset picker.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 20:37:03 +02:00
Till JS
bc77b36234 feat(agents): Agent CRUD + default bootstrap + Mission.agentId (Phase 2)
Second phase of the Multi-Agent Workbench rollout (docs/plans/
multi-agent-workbench.md). Builds on Phase 1's identity-aware Actor.

Adds the Agent primitive — a named AI persona that owns Missions,
carries its own policy + memory, and (from Phase 3 on) drives the
Workbench lens. Everything is wired; a single user currently has one
"Mana" default agent until the UI (Phase 5) lets them create more.

Shared types (@mana/shared-ai):
- agents/types.ts: Agent, AgentState, DEFAULT_AGENT_ID/NAME constants
- policy/types.ts: AiPolicy + PolicyDecision (moved from webapp so
  Agent.policy can reference it without a runtime dep on the web app)
- missions/types.ts: new optional Mission.agentId field

Webapp data layer:
- data/ai/agents/{types,store,queries,bootstrap}.ts
- Dexie schema v19 adds `agents` table (indexes on state, name,
  [state+name]); sync registered under the existing ai app-id
- Encryption registry: agents.systemPrompt + agents.memory encrypted;
  name/role/avatar/policy stay plaintext for search + UI rendering
- DuplicateAgentNameError thrown at write time (not a Dexie unique
  index — bootstrap races between tabs would otherwise hit
  ConstraintError; store now resolves via getOrCreateAgent)
- bootstrap.ts: ensureDefaultAgent + backfillMissionsAgentId. The
  backfill runs once per device (localStorage sentinel) so missions
  that pre-date the rollout get stamped with the default agent's id.
  Called fire-and-forget from startMissionTick() during layout init.

Runner threading (already merged into d5c351d63 via Till's debug-log
commit that picked up my uncommitted edits):
- runner.ts + server-iteration-staging.ts now resolve mission.agentId
  to the real Agent and build makeAgentActor with agent.name as
  displayName. Missing-agent fallback keeps using LEGACY_AI_PRINCIPAL
  so historical writes still attribute cleanly.

Tests: shared-ai 26/26, mana-ai 35/35, svelte-check 0 errors.
Agent store vitest suite is present but blocked by a pre-existing
\$lib alias resolution issue in the webapp vitest config that
predates this phase (proposals/store.test.ts is broken the same way
on HEAD). Will address separately.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 20:35:49 +02:00
Till JS
d5c351d63e feat(ai): per-iteration debug log — capture prompt + response + inputs
New local-only Dexie table _aiDebugLog (v20, never synced) holds one
row per mission iteration with the full system+user prompt, raw LLM
response, latency, every ResolvedInput the planner saw, and pre-step
state (kontext-injected? web-research-ok-or-error?). Capped at 50
newest rows.

aiPlanTask always returns the captured prompt/response on AiPlanOutput.
debug; the runner persists it only when isAiDebugEnabled() — toggled
via a checkbox in the Mission detail header (defaults to on in DEV
builds, off in prod, override via localStorage 'mana.ai.debug').

New <AiDebugBlock> component renders below each iteration card:
expandable sections for Pre-Step, Resolved Inputs (each input
individually collapsible), System Prompt, User Prompt, Raw Response,
plus a "📋 JSON" copy-to-clipboard button for bug reports.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 20:33:17 +02:00
Till JS
6e842a83c9 fix(workbench): set SceneHeader text via refs instead of inline mustache
Prettier kept reformatting the <h1>/<p> bodies onto their own
indented lines, which contenteditable renders verbatim — the user
saw leading/trailing whitespace inside the edit buffer.
prettier-ignore only applies to the immediately next node, and the
svelte-ignore comment was taking that slot, so the directive never
reached the element.

Rewire with bind:this + two $effects that set textContent whenever
the scene prop changes and the element isn't currently focused.
Side benefit: a scene name synced from another device now updates
the header live without interrupting an in-progress edit.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 20:16:21 +02:00
Till JS
e2ea0cd3b8 fix(workbench): tighten SceneHeader spacing and keep contenteditable clean
Two small follow-ups to the inline-edit header:

- Reduce the right padding from 2.5rem to 0.25rem so the scene name
  sits visually adjacent to the first card. With the carousel's own
  1rem gap between flex children the total breathing room is now
  ~20px instead of ~56px, which matches how hero titles usually
  relate to the cards below / beside them.
- prettier-ignore on the <h1> and <p> element blocks. Prettier's
  default Svelte formatting moved {scene.name} onto its own line
  with indentation whitespace, which contenteditable renders
  verbatim as literal leading / trailing spaces in the editor
  content. Keep the mustache on the same line as the tag so the
  text value is what the store actually holds.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 20:14:31 +02:00
Till JS
1771063df4 refactor(actor): identity-aware Actor for Multi-Agent Workbench (Phase 1)
Foundation for the Multi-Agent Workbench roadmap
(docs/plans/multi-agent-workbench.md). Every event, record, and
sync_changes row now carries a principal identity + cached display
name in addition to the three-kind discriminator.

Shape change (source of truth in @mana/shared-ai):
  Before: { kind: 'user' | 'ai' | 'system', ...kind-specific fields }
  After:  discriminated union on kind, with
            - common:  principalId, displayName
            - 'user':  principalId = userId
            - 'ai':    principalId = agentId + missionId/iterationId/rationale
            - 'system': principalId = one of SYSTEM_* sentinel strings
                        ('system:projection', 'system:mission-runner', etc.)

Key design calls (from the plan's Q&A):
- System sub-sources get distinct principalIds (not a shared 'system'
  bucket) — lets Workbench filter + revert distinguish projection
  writes from migration writes from server-iteration writes
- displayName cached on the record so renaming an agent doesn't
  rewrite history
- normalizeActor() compat shim fills principalId/displayName on
  legacy rows with 'legacy:*' sentinels so historical events never
  crash the timeline

New exports:
- BaseActor / UserActor / AiActor / SystemActor (narrowed types)
- makeUserActor, makeAgentActor, makeSystemActor (factories with
  typed return)
- SYSTEM_PROJECTION, SYSTEM_RULE, SYSTEM_MIGRATION, SYSTEM_STREAM,
  SYSTEM_MISSION_RUNNER (principalId constants)
- LEGACY_USER_PRINCIPAL, LEGACY_AI_PRINCIPAL, LEGACY_SYSTEM_PRINCIPAL
- isUserActor / isFromMissionRunner predicates

Webapp:
- data/events/actor.ts now re-exports from shared-ai, keeps runtime
  ambient-context (runAs, getCurrentActor) local
- bindDefaultUser(userId, displayName) lets the auth layer replace
  the legacy placeholder with the real logged-in user actor at login
- Mission runner + server-iteration-staging stamp LEGACY_AI_PRINCIPAL
  as the agentId placeholder — Phase 2 will thread the real agent
- Streaks projection uses makeSystemActor(SYSTEM_PROJECTION)
- All test fixtures migrated to factories

Service:
- mana-ai/db/iteration-writer.ts stamps makeSystemActor(
  SYSTEM_MISSION_RUNNER) instead of the old { kind:'system',
  source:'mission-runner' } shape. Phase 3 will switch this to an
  agent actor per mission.

Tests: 26 shared-ai + 21 webapp vitest + 35 mana-ai — all green.
svelte-check: 0 errors, 0 warnings.

No behavior change; purely a type + shape upgrade. Old sync_changes
rows parse via the normalizeActor compat shim at read time.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 20:13:57 +02:00
Till JS
f7b5c9b3a4 feat(workbench): inline-edit the scene header, no modal
SceneHeader switches from a button-that-opens-a-dialog to inline
contenteditable fields. Click the title → caret appears, type →
Enter commits, Escape reverts, blur commits. Same for the
description except Enter is allowed (multi-line).

- contenteditable="plaintext-only" keeps pasted rich text from
  leaking styles into the store
- onfocus selects the existing text so the first keystroke
  replaces rather than appends (matches the expected rename feel)
- :empty-based ::before placeholder shows "Beschreibung
  hinzufügen…" on blank descriptions, including while focused
  (slightly dimmer) so the edit target is still visible before
  the first character
- Empty name on blur reverts to the previous value (empty scene
  names aren't a valid state the rest of the UI handles)
- Empty description commits as null, matching the store's
  setSceneDescription contract

handleEditActiveScene and the SceneHeader.onEdit prop are gone —
the dialog is no longer reachable from the header. It stays
hooked up to SceneTabs' right-click → Umbenennen path for users
who prefer the modal.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 20:13:41 +02:00
Till JS
db8e681120 feat(workbench): render scene header left of the first page
New SceneHeader component: big scene name (clamp 2.75rem–4.5rem
responsive) plus a muted description underneath, or an italic
"Beschreibung hinzufügen…" placeholder when empty. The whole block
is a button — clicking it opens the existing scene edit dialog,
now pulling double duty for both name and description.

Wired through PageCarousel's new leading snippet from the previous
commit, so the header scrolls with the track and stays anchored to
the visual start of the carousel without needing a second scroll
container.

SceneRenameDialog grows a description textarea (maxlength 240,
3 rows, vertically resizable) and onSubmit now passes (name,
description). The caller translates an empty description to null
so the DB column reflects "no description set" rather than an
empty string — keeps WorkbenchScene.description truthy checks
honest.

handleEditActiveScene resolves the currently-active scene and
opens the dialog pre-filled; used by the SceneHeader click.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 19:42:20 +02:00
Till JS
8f3ffefdf1 feat(page-carousel): optional leading snippet before first page
Consumers of PageCarousel can now pass a \`leading\` Snippet that
renders as the first flex child inside .fokus-track, ahead of the
page wrappers. Used on the workbench homepage for the scene header
(name + description). Scrolls with the track rather than sticking
in place — reads as an intro block, not app chrome, and doesn't
steal viewport from the cards on narrow screens.

Styled as flex-aligned, align-self:stretch so its intrinsic layout
decides the height and it centres vertically against the cards.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 19:42:04 +02:00
Till JS
714c235798 feat(workbench): scene description field, drop scene icon
Schema + store preparation for the new scene header on the homepage
(next commit). Scenes get an optional free-text description stamped
as its own field — LWW through the existing mana-sync pipeline, no
new sync contract. The unused icon field is removed everywhere:

- types/workbench-scenes.ts: description?: string | null replaces icon
- stores/workbench-scenes.svelte.ts: createScene, renameScene,
  duplicateScene, toScene, patchScene all updated. New method
  setSceneDescription(id, value) mirrors renameScene so the caller
  can change just the description without re-submitting the name.
- components/workbench/scenes/SceneTabs.svelte: the tab-bar rendered
  {#if scene.icon} before the name — scene.name is unique enough to
  identify a scene, and the UI direction is away from emoji chrome.

Existing scene rows in Dexie simply omit description (undefined → null
on read); the icon field on old rows is ignored and will age out of
the schema the next time the row is written.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 19:41:50 +02:00
Till JS
0da74587ce fix(ai): surface web-research failures to the planner instead of hallucinating
When the pre-step research call throws (mana-search down, missing tier,
402 credits, etc.), the runner used to swallow the error and feed an
empty input to the planner — which then made up a story about a "failed
web search" and fell back to create_task. Now we inject an explicit
"research failed" ResolvedInput with the actual error message, plus
write the truncated message into phaseDetail so it's visible in the
mission card without DevTools.

Bundles an in-flight actor refactor merge in runner.ts (makeAgentActor
+ LEGACY_AI_PRINCIPAL) — those lines came from the parallel Phase-1
identity work, not this fix.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 19:23:59 +02:00
Till JS
fdb8e60d07 feat(ai): web-research pre-step + auto-kontext + save_news_article tool
Mission objectives matching /recherch|research|news|finde|suche|aktuelle|neueste/i
trigger a synchronous deep-research call (mana-search + mana-llm via the
existing /api/v1/research/start-sync pipeline) before the planner runs;
the summary plus top-8 source URLs are injected as a synthetic ResolvedInput
so the planner can stage save_news_article proposals against real URLs.

The kontext singleton is auto-attached to every mission's planner input
(decrypted client-side, gated on non-empty content + not already linked).

save_news_article is a new proposable tool routed through articlesStore
.saveFromUrl (Readability via /api/v1/news/extract/save). AiProposalInbox
mounted on /news so the user can approve/reject inline. mana-ai planner
tool list mirrors the new tool to keep the boot-time drift guard happy.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 19:10:13 +02:00
Till JS
eaf97aeebf fix(api): unblock tsc by dropping rootDir and allowing .ts imports
Running pnpm type-check inside apps/api failed before any real
error could run, blocked by two structural errors: drizzle.presi.config.ts
and scripts/generate-who-dossiers.ts are deliberately outside src/
but are matched by the include pattern, tripping TS6059 against
rootDir=src. And @mana/shared-types imports peer files with explicit
.ts extensions, which needs allowImportingTsExtensions under
moduleResolution=bundler.

Remove rootDir (we're noEmit anyway — Bun runs src/index.ts
directly, tsc is only a lint pass), drop the unused outDir, add
noEmit explicitly, and enable allowImportingTsExtensions. Type-check
now completes cleanly.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 18:51:26 +02:00
Till JS
6b1e8e878e fix(web): guard PwaUpdatePrompt against SSR navigator access
useRegisterSW() reaches for navigator.serviceWorker at call time, which
crashes SvelteKit SSR with "ReferenceError: navigator is not defined"
(Node has no navigator). The prod mana-web container crash-looped on
every request after the rebuild because the layout mounts this
component unconditionally.

Fix: branch on \$app/environment's \`browser\` flag. On the server,
hand back a noop writable + async-noop updater so the downstream
template code stays unchanged.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 15:33:35 +02:00
Till JS
98347cfd89 fix(web): add workbox-window as explicit devDependency
vite-plugin-pwa's \`virtual:pwa-register/svelte\` imports workbox-window
at build time. The package was resolved transitively via @vite-pwa/
sveltekit but not installed in the webapp's own node_modules when
pnpm fetches only the workspace deps in a restricted Docker context.
Result: Rollup 'failed to resolve import "workbox-window"' at build.

Pinning the direct dep so the Docker build picks it up in the
filtered pnpm install.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 15:12:33 +02:00
Till JS
eb30d49501 fix(web-docker): copy packages/shared-ai into build context
Webapp package.json gained a @mana/shared-ai workspace dep (Mission
Grant types + canonical HKDF derivation). Without the package in the
Dockerfile COPY list, pnpm install aborts with
ERR_PNPM_WORKSPACE_PKG_NOT_FOUND. Caught during the first mana-web
rebuild after the Mission Grant rollout.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 15:07:41 +02:00
Till JS
2497a65937 feat(ai-missions): richer error surfacing + retry button on failed runs
Replaces the single-line summary ("Planner failed: fetch …") with
full diagnostic detail: error name + message + last-active phase +
stack trace, all persisted onto the iteration itself. UI expands a
collapsed details block next to each failed iteration, so the user
can see *where* it broke ("TypeError in calling-llm") without opening
DevTools.

Paired with a one-click Retry button that re-runs the mission under
the same config — useful while debugging a flaky backend (GPU server
down, Gemini quota, etc.).

- `packages/shared-ai/src/missions/types.ts` — new
  `MissionIteration.errorDetails: { name, message, phase?, stack? }`
- `finishIteration` accepts the field, deep-clones it, and also now
  clears the transient phase markers (currentPhase/phaseStartedAt/
  phaseDetail/cancelRequested) whenever an iteration finalises — keeps
  the schema honest (phases are sub-state of \`running\` only).
- `runMission` tracks \`lastPhase\` via a new \`enterPhase\` helper that
  wraps setIterationPhase. The catch handler populates errorDetails
  with lastPhase + message + stack.
- ListView: \`<details>\` block under each failed iteration + Retry
  button (disabled while another run is in-flight).

77/77 webapp tests still green; svelte-check clean.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 14:37:15 +02:00
Till JS
f0f5b7dcf6 fix(infra): relocate mana-ai from 3066 to 3067 — port clash with news-ingester
news-ingester already owns 3066 (see docker-compose.macmini.yml:1620).
Moving mana-ai to 3067 — the next free slot in the 306x services block
(credits 3061, user 3062, subscriptions 3063, analytics 3064,
events 3065, news-ingester 3066, mana-ai 3067).

Updated: Dockerfile EXPOSE + HEALTHCHECK, config.ts default,
compose service/healthcheck/port mapping, webapp getManaAiUrl()
fallback, root CLAUDE.md service list, mana-ai/CLAUDE.md, and
COMPANION_BRAIN_ARCHITECTURE.md §20 file map.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 14:32:07 +02:00
Till JS
6acb044230 feat(kontext,notes): cross-module handoff — save Kontext as a Note
Wires the "Als Notiz speichern" action at the bottom of the Kontext
widget (UI itself landed in 003f75f7e) to actually open Notes next
to Kontext and focus the new note:

- workbench-scenes: new addAppAfter(appId, anchorAppId). addApp()
  always appended, which pushed Notes to the far end of the
  carousel; addAppAfter inserts directly after the anchor (Kontext)
  and no-ops if the target is already open so the user's current
  position isn't yanked around.
- notes/stores/selection: new transient in-memory focus signal
  (focusedNoteId) that cross-module callers populate. Kept
  non-persistent intentionally — surviving a remount would re-open
  random notes after page loads.
- notes/ListView: $effect reads focusedNoteId, waits for the
  Dexie liveQuery to surface the just-created row, opens it in
  the inline editor, clears the focus signal, then scrolls the
  matching data-note-id element into view via queueMicrotask so
  the DOM has rendered the editor variant.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 14:24:56 +02:00