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>
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>
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>
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>
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>
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>
Third naming convention uncovered: six shared-auth-ui components
(PasskeyManager, SessionManager, TwoFactorSetup, AuditLog,
ChangePassword, SecurityOnboarding) use --theme-muted, --theme-border,
etc. These tokens are not defined anywhere in the Mana theme system —
they only rendered because of inline raw-channel fallbacks, so they
never tracked the active theme variant.
Map to the canonical --color-* tokens:
--theme-background → --color-background
--theme-border → --color-border
--theme-foreground → --color-foreground
--theme-muted → --color-muted
--theme-muted-foreground → --color-muted-foreground
--theme-surface → --color-surface
Visible on the Security tab in the unified settings page, where the
inner passkey/session/2FA/audit cards can now pick up theme changes.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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>
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>
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>
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>
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>
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>
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>
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>
The standalone "Cloud Sync" pill sat fifth in the bar-mode pill row
on mobile / narrow layouts, pushing the pill row to 5+ items and
duplicating actions the user menu already surfaces (Sync-Einstellungen,
credits link). Move the sync status + actions into userMenuBarItems
so they appear as a labelled section inside the bar that opens when
the user pill is tapped.
Render order for signed-in users now goes:
account · settings · theme-mode · theme · [Sync section] · logout
The section header uses the existing `divider: true, label: 'Sync'`
shape PillDropdownBar already understands, so the status line ("Cloud
Sync aktiv", etc.) sits under a real heading. For guests the section
is skipped entirely — sync doesn't apply before login.
Left the non-barMode popover path alone (showSyncStatus + syncStatusItems
still drive the inline PillDropdown when a popover is in use) since
the complaint was specifically about the bar's fifth pill on mobile.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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>
Two visible issues on /login and /register in the Mana web app:
- The "Zum Anmeldeformular springen" skip link was visible in the
top-left corner instead of only on keyboard focus. It's an a11y
nicety that in practice clutters the page for every user. Remove
the button (and the dead skipToForm() handler in LoginPage) rather
than try to fix an sr-only variant that apparently doesn't survive
the Tailwind v4 @source pipeline here.
- The form card rendered ~full-width on desktop even though the
existing max-w-[440px] / max-w-[480px] arbitrary-value classes
should have constrained it. Replace those with style:max-width
inline so the 440px cap is guaranteed regardless of how Tailwind
picks up classes from the shared-auth-ui package.
Same changes applied to RegisterPage (only width — no skip link
there) so the two pages stay visually identical.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The 2rem linear-gradient mask at each end of the tag strip was
supposed to hint at scrollable overflow, but it cuts off the
leading + trailing tags on narrow layouts where they're already
just barely visible. Dropping both mask-image declarations lets
the pills render edge-to-edge. Overflow scrolling + hidden
scrollbar stay as they were, plus the hover-lift removal from
the previous commit.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The whole pill family used a translateY(-1px / -2px) on :hover that
made the chrome jitter every time the cursor crossed anything —
especially noticeable in the TagStrip, PillNav, and the user-menu
bar where pills sit close together and a 1-2px jump reads as
twitching rather than polish.
Removed the hover transform (and the matching :active reset on
the base Pill) from:
- Pill.svelte — base primitive, covers PillNav + every bar pill
- AppDrawer.svelte — .glass-pill in the app switcher
- PillTagSelector.svelte — .glass-pill in the quick-input tag selector
- PillTimeRangeSelector.svelte — .glass-pill in the calendar range picker
- UserMenuPanel.svelte — .chip hover in the popover variant
Background, border, and shadow hover states are kept — only the
vertical displacement is gone.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When the right-hand user pill is clicked as a guest (barMode), the
opening bar used to show just a decorative "Menü" label pill on
the left and then settings + theme toggles — none of which are a
useful first action for someone not logged in.
- phosphor-icon-map: register SignIn as `login`, so PillDropdownBar
can render it the same way as the existing `logout` glyph.
- PillNavigation.userMenuBarItems: prepend an "Anmelden" item for
guests (no userEmail + loginHref given) that navigates to
loginHref. It becomes the first CTA in the bar, above settings /
theme / language.
- PillNavigation.userBarConfig: drop the bar-header label when the
viewer is a guest. The decorative "Menü" pill added nothing
alongside the new Anmelden action and just cluttered the leading
edge of the bar.
The popover variant of the user menu (UserMenuPanel) already had
its own guest login button — this mirrors that behaviour for the
bottom-bar variant used on mobile / narrow layouts.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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>
Closes the "iteration is running, no feedback" black hole. The user now
sees, per running iteration:
⏳ Frage Planner · frage Planner an ⏱ 23s
[Abbrechen]
Phases (\`IterationPhase\`):
resolving-inputs → calling-llm → parsing-response →
staging-proposals → finalizing
The runner advances through these via \`setIterationPhase\` between each
await, writing currentPhase + phaseDetail + phaseStartedAt onto the
iteration. UI reads them via Dexie liveQuery — no polling.
Cancel:
- \`requestIterationCancel\` writes cancelRequested=true on the iteration
- runner polls \`isCancelRequested\` between every phase + per stage step
- cancellation finalises as \`failed\` with summary \`'cancelled by user'\`
- UI button is disabled + relabelled "Wird abgebrochen…" until the next
poll picks it up
Hard timeout: 90 s wall-clock per iteration via Promise.race against a
CancelledError. Wedged backends (e.g. flaky mana-llm) fail fast with
"timeout after 90s" instead of sitting in \`running\` forever.
Elapsed counter is a \$state variable ticking once a second, scoped to
the ListView component — Dexie isn't touched. Auto-cleaned on
component destroy.
shared-ai re-exports \`IterationPhase\` so server-side mana-ai can
inspect the same phase enum (no consumer there yet, but the type is
ready for the run-status endpoint planned in HEALTH page).
77/77 webapp tests still green; svelte-check clean.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Foundation for Phase 2+ of the Mission Key-Grant flow: lets mana-ai
execute missions that depend on encrypted inputs (notes/tasks/events/
journal/kontext) without needing an open browser tab. Opt-in per
mission, Zero-Knowledge users excluded.
- Canonical HKDF-SHA256 derivation (scope-bound via tables + recordIds
in the HKDF info string → scope changes invalidate the grant
cryptographically, not just via a runtime check)
- Mission.grant field on the shared Mission type
- Golden snapshot + drift-guard test so webapp wrap path and mana-auth
wrap endpoint can't silently diverge
- Ideas backlog at docs/future/AI_AGENTS_IDEAS.md
- Full rollout plan at docs/plans/ai-mission-key-grant.md
- COMPANION_BRAIN_ARCHITECTURE.md §21 captures the flow + privacy
guarantees + non-goals
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sync status, user menu (bar-mode + overlay fallback) and logout now use
the shared Pill component like the rest of PillNavigation. All pill
styling now lives in a single place.
- Pill gains an escape-hatch `data?: Record<string, string>` prop for
arbitrary data-* attributes (used by the user-menu trigger which is
click()-ed via querySelector from the consuming layout) and a
bindable `element` binding for imperative focus/positioning
(replaces the old bind:this={userMenuTrigger}).
- Remove the now-dead inline .pill / .glass-pill / .logout-pill /
.pill-label / .pill.active / .pill.icon-only CSS from PillNavigation
(all living in Pill.svelte now). ~110 lines of CSS gone.
- The mobile override that forced 44px min-height on .pill is also gone;
Pill sizes are controlled via the size prop.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
PillNavigation rendered three near-identical inline pill blocks (prepended
elements, main nav items, appended elements). Consolidate onto the Pill
component so the visual base stays in lockstep with the bottom-stack bars.
- Extend Pill with size='sm'|'md'. sm = 36px with 18px icons (PillNav
style); md = 44px with 20px icons (bar pills, default).
- Move the icon-only padding override into Pill itself.
- Extract the Mana-Logo SVG (duplicated inline) to ManaLogoIcon.svelte.
- Replace the three inline pill loops in PillNavigation with <Pill size='sm'>.
Mana-logo and iconSvg cases ride the `leading` snippet. onClick vs href
disambiguation is collapsed into a single Pill call per item.
- Remove the now-unreachable .pill-icon scoped CSS that was only meaningful
for the removed inline SVGs (Phosphor icon sizing comes from the size
prop).
Net: ~70 lines removed from PillNavigation.svelte without changing the
render output. Bar-mode triggers (sync / ai / theme / user) still render
inline because their logic is too entangled with activeBarId — leave for
a follow-up.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
CommandBar was a near-duplicate of QuickInputBar's InputBar with the UX
of a Cmd+K modal. Only arcade still used it. Migrate arcade onto the
existing GlobalSpotlight (hosted by PillNavigation) so there is a single
Cmd+K modal across all mana apps, then remove CommandBar entirely.
Arcade changes (games/arcade/apps/web/src/routes/(app)/+layout.svelte):
- Merge commandBarQuickActions into spotlightActions (nav-level items)
- Convert handleCommandBarSearch into a ContentSearcher that returns the
game list grouped under a single 'Spiele' category
- Drop the standalone Cmd+K handler; PillNavigation + GlobalSpotlight
handle the shortcut
- Remove the <CommandBar> render; add `contentSearcher` +
spotlightPlaceholder to <PillNavigation>
shared-ui cleanup:
- Delete packages/shared-ui/src/command-bar/ (CommandBar.svelte,
CommandBar.types.ts, index.ts)
- Drop CommandBar / CommandBarItem from the public @mana/shared-ui export
- Delete docs/central-services/COMMAND-BAR.md (stale)
No more duplication: highlight + debounce live in search-core, and the
only remaining Cmd+K surface is GlobalSpotlight.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Both QuickInputBar (InputBar.svelte), CommandBar.svelte, and GlobalSpotlight
were duplicating syntax highlighting and the 150ms search debounce. Pull
these into a new `packages/shared-ui/src/search-core/` module so the two
input surfaces stay in sync on feel and matching rules.
- search-core/highlight.ts — HighlightPattern type, locale-aware
getHighlightPatterns(), and the shared highlightText() (HTML-escape +
span wrap). Patterns were previously in quick-input/highlightPatterns.ts
+ inline in CommandBar.svelte.
- search-core/config.ts — SEARCH_DEBOUNCE_MS = 150. Used from InputBar,
CommandBar, GlobalSpotlight, and apps/mana web SearchEngine.
- quick-input/highlightPatterns.ts + types.ts become thin back-compat
re-exports.
- Public surface: @mana/shared-ui now exports getHighlightPatterns,
highlightText, SEARCH_DEBOUNCE_MS, and the HighlightPattern type.
No UX change. UIs still live in their own files (per earlier split
recommendation: shared backend, separate surfaces).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Extract Pill.svelte as the single visual primitive (44px, icon+label,
active/primary/danger variants) used by PillDropdownBar and TagStrip.
PillNav keeps its own internal .pill class (36px, icon-only-oriented).
- Extract phosphor-icon-map.ts to deduplicate the icon lookup tables
that previously lived inline in PillDropdownBar.
- Unify bar slot heights in (app)/+layout.svelte: 56px PillNav,
64px for tags / quickinput / tabbar / dropdown-bar. Remove debug
outlines. Collapse bottom-stack gap so bars sit flush below PillNav.
- SceneAppBar wrapped in 64px slot, scene-pill/app-tab 40px to match.
- Enforce single-bar policy: opening one bar closes the others.
- QuickInputBar strip-down: remove leading CheckSquare icon and trailing
nav-toggle snippet; bar is pure search input now.
- Move user-menu (last PillNav pill) to bar-mode with short content:
Einstellungen, Light/Dark/System segmented, Theme, Logout.
- Swap tabs nav icon from Columns to Cards for better readability.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- 1–9 scroll to the Nth open app on the workbench homepage; 0 opens the
app picker.
- q/w/e toggle the bottom bars (workbench tabs / search / tags); r opens
the user-menu PillDropdownBar (expanding the PillNav first if needed);
t toggles the PillNav visibility.
Adds a `data-user-menu-trigger` hook on the user pill so the layout can
drive the menu bar programmatically without duplicating its config.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Makes the webapp's AI policy and the server's tool allow-list physically
impossible to drift. Adds the missing entries the guard caught on first
run: `complete_tasks_by_title`, `visit_place`, `undo_drink` now have
parameter schemas server-side too.
- `packages/shared-ai/src/policy/proposable-tools.ts`
- `AI_PROPOSABLE_TOOL_NAMES` as `const` array + literal union type
- `AI_PROPOSABLE_TOOL_SET` for set-membership checks
- Webapp `DEFAULT_AI_POLICY` derives its `propose` entries from the
shared list via `Object.fromEntries(...)` — adding a tool there is now
a one-line change in `@mana/shared-ai`
- mana-ai `AI_AVAILABLE_TOOLS`: module-load assertion compares its
hardcoded names against `AI_PROPOSABLE_TOOL_SET` and throws with a
pointed error on drift (extras in one direction, missing in the
other). Service refuses to start on mismatch — better than silent
degradation.
- Bun test (`tools.test.ts`) runs the same contract plus sanity checks
(non-empty description, required params carry docs). Vitest policy
test adds the symmetric check on the webapp side.
All three runtimes now green: webapp 66/66, shared-ai 2/2,
mana-ai 9/9 Bun tests.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Completes the off-tab AI pipeline. mana-ai now writes produced plans
back to `sync_changes` as a server-sourced Mission iteration; the webapp
picks it up on next sync and translates each PlanStep into a local
Proposal via the existing createProposal flow. User sees the resulting
ghost cards in the matching module's AiProposalInbox with full mission
attribution.
Server (mana-ai v0.3):
- `db/connection.ts` — `withUser(sql, userId, fn)` RLS-scoped tx helper
mirroring the Go `withUser` pattern (SET LOCAL app.current_user_id)
- `db/iteration-writer.ts`
- `planToIteration(plan, id, now)` — shared-ai AiPlanOutput → inline
MissionIteration with `source: 'server'` + status='awaiting-review'
- `appendServerIteration(sql, input)` — INSERT sync_changes row with
op=update, data={iterations: [...]} + field_timestamps + actor
JSONB={kind:'system', source:'mission-runner'}
- `cron/tick.ts` — after parse success: build iteration, append to
mission.iterations, persist via appendServerIteration. Stats now
include `plansWrittenBack`.
Actor union:
- `packages/shared-ai/src/actor.ts` + webapp actor: `system.source` gains
`'mission-runner'` so the server's own writes are attributed correctly
and distinguishable from projection/rule writes
Webapp:
- `data/ai/missions/server-iteration-staging.ts`
- `startServerIterationStaging()` subscribes to aiMissions via Dexie
liveQuery; on each Mission update, walks iterations looking for
`source='server'` entries that haven't been staged yet
- For each such iteration: creates a Proposal per PlanStep under
`{kind:'ai', missionId, iterationId, rationale}` so policy + hooks
fire correctly
- Writes proposalIds back into plan[].proposalId + status='staged' so
other tabs and app restarts skip re-staging
- Idempotent: in-memory `processedIterations` Set + durable
proposalId marker
- Wired into (app)/+layout.svelte alongside startMissionTick
- 3 unit tests: translate server iteration → proposal, skip
already-staged, ignore browser iterations
Full pipeline now: user creates Mission in /companion/missions →
mana-ai tick picks it up → calls mana-llm → parses plan →
writes iteration → synced to webapp → staging effect creates
proposals → user approves in /todo (or any module) → task lands with
`{actor: ai, missionId, iterationId, rationale}` attribution.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Single source of truth for AI Workbench types shared between the webapp
(Vite/SvelteKit) and the server-side mana-ai Bun service. Prevents the
two runtimes from drifting on prompt shape or mission structure.
- `@mana/shared-ai` package:
- `actor.ts` — Actor union (user | ai | system) + helpers, mirrors the
webapp's runtime type so server-side consumers parse incoming actors
without re-declaring
- `missions/types.ts` — Mission, MissionCadence, MissionInputRef,
MissionIteration, PlanStep, MissionState. Adds optional
`iteration.source: 'browser' | 'server'` to distinguish foreground
vs server-produced iterations (groundwork for proposal write-back)
- `planner/prompt.ts` — `buildPlannerPrompt` pure function
- `planner/parser.ts` — `parsePlannerResponse` strict JSON validator
- Vitest smoke tests (2) cover prompt → parse round-trip + unknown-
tool rejection
- Webapp:
- `missions/types.ts` re-exports from shared-ai, keeps webapp-local
`MISSIONS_TABLE` constant + `planStepStatusFromProposal` bridge
- `missions/planner/{types,prompt,parser}.ts` become re-export stubs
so existing imports keep working unchanged
- Existing webapp tests (60) continue to pass — the wire code didn't
move, just its home
Next: mana-ai service imports buildPlannerPrompt/parsePlannerResponse
from shared-ai + wires mana-llm + writes iteration back as a
'source=server' row (tracked in services/mana-ai/CLAUDE.md).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The prior dance (93bb94a12 drop .ts, bb278fb3c switch to .js) kept
breaking one consumer or the other:
- bare specifiers (no extension) satisfied svelte-check but broke the
Node ESM loader invoked via @tailwindcss/node during SSR — SSR of
every (app) route 500'd with ERR_MODULE_NOT_FOUND on 'theme'.
- .js extensions satisfied svelte-check and Vite but still broke the
Tailwind loader, because the files on disk are .ts — Node ESM walks
the actual filesystem and can't rewrite .js → .ts the way tsc does
at type-check time.
Flip the web app's tsconfig to "allowImportingTsExtensions": true and
put the .ts extensions back. tsc now accepts the imports, and Node's
loader finds the real file on disk. No build step, no emit, and the
shared-types package stays a pure source-only TS workspace.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Commit 93bb94a12 dropped the extensions on shared-types re-exports
to make the web app's svelte-check pass (its tsconfig has no
allowImportingTsExtensions). That satisfied tsc but broke SSR: the
dev server tripped with ERR_MODULE_NOT_FOUND on every (app) route
because Node's native ESM loader (used by downstream tooling like
@tailwindcss/node) cannot resolve bare relative specifiers without
an extension, and only Vite-owned paths got the bundler-style
resolution the fix relied on.
Switch to the TypeScript-ESM idiomatic `.js` extension. tsc with
moduleResolution: "bundler" still type-checks against the actual
.ts source, and at runtime both Vite and Node resolve `.js` the
same way — no tsconfig flag flip required.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- shared-types/index.ts: drop .ts import extensions (web-app tsconfig
has no allowImportingTsExtensions; bundler resolution handles it)
- backup/format.test.ts: narrow buildZip return to Uint8Array<ArrayBuffer>
so the Blob() constructor accepts it under strict lib.dom
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
eventstream was confusingly branded "Events" in the app registry,
colliding with the real events calendar module. Renamed to activity
(DE: Aktivität) since it's a live activity feed across all modules.
cycles -> period (DE: Periode) makes the menstrual-tracking module
self-describing. Tables cycles/cycleDayLogs/cycleSymptoms renamed to
periods/periodDayLogs/periodSymptoms; field cycleId -> periodId;
TimeBlockType 'cycle' -> 'period'; domain event CycleDayLogged ->
PeriodDayLogged. Generic "cycle" usages (billing, lifecycle, breath,
bicycle, import cycles) left untouched.
Constant disambiguation: prior DEFAULT_PERIOD_LENGTH (bleeding days)
renamed to DEFAULT_BLEEDING_DAYS; prior DEFAULT_CYCLE_LENGTH (28d full
cycle) is now DEFAULT_PERIOD_LENGTH.
Pre-launch, no data migration needed.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Following the shared-icons fix (d5cabed14), audit every workspace
package's src/index.ts for top-level side effects and flag the
ones that are safe to tree-shake:
- Pure TS re-export barrels (types, theme, utils, llm, storage):
"sideEffects": false — lets Vite prune entire submodules when a
consumer only imports a subset of named exports. Matters most for
shared-llm where the orchestrator/BYOK branch isn't needed on
every route.
- Packages that ship .svelte components (branding, ui, links):
"sideEffects": ["**/*.svelte", "**/*.css"] — same tree-shaking
benefit for TS modules, but keeps Svelte component CSS injection
intact.
The state-holding submodules (shared-ui drag-state/toast,
shared-llm store, shared-links mutations) are still evaluated
whenever their exports are referenced, so behaviour is unchanged —
the flag only lets the bundler skip modules that aren't in the
dependency graph at all.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
phosphor-svelte ships a 28 MB lib of per-icon Svelte components and
does not declare "sideEffects" in its own package.json. When
@mana/shared-icons re-exports that package without its own
"sideEffects" hint, Vite/Rollup conservatively assume every
transitive module evaluation might matter and cannot aggressively
prune unused icons across chunk boundaries.
Our re-exports (index.ts: `export * from 'phosphor-svelte'` + a small
name→component registry) are pure ESM barrels with no top-level
runtime code, so flagging the package as side-effect-free is safe
and lets the bundler drop unused icons and skip evaluating the
icon-registry module from chunks that only want a named icon.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Bug: setting taskOverrides['companion.chat'] = 'byok' didn't work
when the user's allowedTiers was empty/['none']. The tier-too-low
check in run() compared task.minTier ('browser') against userMaxTier
('none') and threw TierTooLowError before the override was even read.
Same issue in canRun() and candidateTiers().
Fix: when a per-task override exists, treat it as opt-in to that tier
even if not in the global allowedTiers. The override is the user's
explicit per-task signal — overriding the global default is exactly
what an override is for.
- run(): effectiveMaxTier = max(override, userMaxTier)
- candidateTiers(task, override): adds override to baseTiers
- canRun(): now passes the override to candidateTiers
The Companion chat now correctly uses BYOK when selected from the
toolbar, even if the user hasn't enabled BYOK in their global LLM
settings.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Phase 1-3 of BYOK support. Introduces a 5th LLM tier 'byok' that
routes to user-provided API keys via direct browser fetches.
shared-llm additions:
- LlmTier extended with 'byok' (rank 3, between mana-server and cloud)
- ByokBackend: LlmBackend implementation that delegates key lookup
to an app-provided resolver callback, then dispatches to the right
provider adapter
- 4 provider adapters:
- OpenAI (gpt-5, gpt-4o, o1 family)
- Anthropic (Claude Opus/Sonnet/Haiku 4.6) with CORS header
- Gemini (2.5 Pro/Flash) — REST API with different message format
- Mistral — OpenAI-compatible, reuses shared openai-compat adapter
- Pricing table for 20+ models with USD per 1M tokens
- estimateCost() + formatCost() helpers
Keys stay device-local (IndexedDB in next phase). Browser-direct
fetches mean keys never touch Mana's server.
Updates two existing tier maps (memoro DetailView, SourceBadge) to
include the new tier.
Planning doc at docs/architecture/BYOK_PLAN.md.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The PWA was configured end-to-end in vite.config.ts and built the
manifest + service worker correctly, but neither was ever loaded by
the browser — no <link rel="manifest"> in the HTML and no script
registering the generated registerSW.js. Chrome therefore never fired
beforeinstallprompt, no install icon appeared in the URL bar, and the
hand-rolled PwaUpdatePrompt hung on navigator.serviceWorker.ready
because no SW had been registered.
Changes:
- Render pwaInfo.webManifest.linkTag into <svelte:head> in the root
layout so Chrome finds the hashed manifest.
- Replace the hand-rolled SW-update logic in PwaUpdatePrompt with
useRegisterSW() from virtual:pwa-register/svelte — it registers the
worker (immediate: true) and exposes reactive needRefresh +
updateServiceWorker stores that match registerType: 'prompt'.
- Add triple-slash refs to vite-plugin-pwa/info and /svelte in
app.d.ts for the virtual module types.
- Set manifest.id = startUrl in @mana/shared-pwa so Chrome doesn't
warn and keeps the install identity stable across start_url edits.
- Keep devEnabled: false and expand the comment: the 2026-04-08
dreams mic-button bug and the /offline navigateFallback both
misbehave when Workbox precache doesn't run under vite dev. Test
the install flow via `pnpm build && pnpm preview` instead.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Final optimization pass for the Companion Brain.
New modules (31 total):
- Meditate: MeditationCompleted event + log_meditation tool
- Sleep: SleepLogged event + log_sleep tool
Performance: DaySnapshot buildSnapshot() now runs all 6 Dexie
queries + 4 decryption passes in parallel via Promise.all instead
of sequentially. Estimated 3-5x speedup on first render.
Cleanup: trackActivity() in database.ts is now a no-op — the
_activity table is no longer written to. getRecentActivity() in
activity.ts delegates to queryEvents() from the Domain Event Store,
converting domain events to the legacy ActivityEntry shape.
Totals: 69 event types, 49 tools across 31 modules.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Registers Mein Tag, Event Stream, Companion Chat, and Ziele as
workbench apps so they can be added to scenes alongside existing
modules like Todo, Calendar, etc.
New workbench pages:
- Mein Tag (myday): DaySnapshot overview — tasks, events, water
progress, nutrition, streaks at a glance
- Events (eventstream): live domain event feed with icons, labels,
and timestamps — shows the system "pulse" in real-time
- Companion (companion): embedded chat interface that auto-creates
a conversation on first use
- Ziele (goals): goal cards with progress bars, template picker
for quick goal creation, pause/resume/delete
Each page registered in both app-registry (workbench views) and
shared-branding (app metadata, icons, descriptions, tier=guest).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Backend: Hono/Bun service on port 3042 with JMAP client for Stalwart,
account provisioning (@mana.how addresses on user registration),
thread/message/send/label API endpoints, and JWT + service-key auth.
Frontend: Mail module with 3-column inbox UI (mailboxes, thread list,
detail/compose), local-first encrypted drafts in Dexie, and API-driven
thread fetching. Scoped CSS with theme tokens.
Integration: Dexie v11 schema, mail pgSchema in mana_platform,
mana-auth fire-and-forget hook for account provisioning,
getManaMailUrl() in API config, app registry + branding update.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
New "Dehnen/Stretch" module for guided stretching with timer-based sessions,
mobility self-assessments, streak tracking, and configurable reminders.
Includes: 22 seed exercises, 5 preset routines (morning, desk break, evening,
upper body, lower body), fullscreen session player with Performance.now() timer
and Wake Lock, 6-step mobility assessment wizard with scoring, 30-day heatmap,
body region balance chart, custom routine builder, and reminder management.
Registered in module-registry, encryption registry (5 tables), database v9,
seed-registry, app-icons, mana-apps, and workbench app-registry.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>