Commit graph

338 commits

Author SHA1 Message Date
Till JS
89258eb451 feat(profile,api): meImages foundation for AI reference generation (M1)
M1 of docs/plans/me-images-and-reference-generation.md — a user-owned
pool of reference images (face, fullbody, hands, …) that will back
image generation where the user appears as themselves (outfit try-on,
glasses, portraits) via OpenAI /v1/images/edits. Data layer only in
this commit; UI lands in M2, the edits endpoint in M3.

- Dexie v38: meImages table with id/kind/primaryFor/createdAt indices.
  Added to USER_LEVEL_TABLES so the hook stamps userId and skips the
  spaceId/authorId/visibility trio (one human = one face across every
  Space, not per-Space).
- Encryption registry: label + tags encrypted; kind/primaryFor/usage
  stay plaintext because they drive the indexed queries and the
  Reference picker's filtering. mediaId/URLs/dimensions are structural.
- Profile module store: createMeImage, updateMeImage,
  setAiReferenceEnabled (per-image KI opt-in — plan decision #5),
  setPrimary (transactional slot swap — only one row per primary slot),
  deleteMeImage. Emits MeImage* domain events.
- Queries: useAllMeImages, useMeImagesByKind, useReferenceImages
  (only the rows the user opted in for KI), useImageByPrimary.
- POST /api/v1/profile/me-images/upload: thin wrapper over mana-media
  with app='me' as the reference tag. No new MinIO bucket — plan
  decision #1 revised after verifying mana-media uses one bucket and
  only tags references by app.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 13:50:53 +02:00
Till JS
16c8818338 feat(mcp): M1+M1.5 MCP gateway + tool-registry + shared-crypto
Foundation for autonomous Claude-driven testing. Plan:
docs/plans/mana-mcp-and-personas.md.

New packages
- @mana/tool-registry — schema-first ToolSpec<InputSchema, OutputSchema>
  with zod generics, scope ('user-space' | 'admin') and policyHint
  ('read' | 'write' | 'destructive'). sync-client helpers speak the
  mana-sync push/pull protocol directly so RLS and field-level LWW are
  preserved. MasterKeyClient fetches per-user MKs via the existing
  mana-auth GET /api/v1/me/encryption-vault/key endpoint (JWT-gated,
  ZK-aware, already audited) — no new service-key endpoint built.
  ZeroKnowledgeUserError surfaced as a typed throw.
- @mana/shared-crypto — AES-GCM-256 primitives extracted from the web
  app's $lib/data/crypto/aes.ts so the server-side tool handlers and the
  browser produce byte-for-byte identical wire format
  (enc:1:{b64(iv)}.{b64(ct)}). Web app aes.ts now re-exports from
  shared-crypto — 5 existing importers unchanged, svelte-check stays
  green.

New service
- services/mana-mcp (:3069, Bun/Hono) — MCP Streamable HTTP gateway.
  JWKS auth against mana-auth, per-user session isolation (session-id
  belongs to the user who opened it — cross-user access returns 403),
  admin-scoped tools filtered out before registration. MasterKeyClient
  cached per process with a 5-minute TTL.

11 tools registered
- habits.{create,list,update,archive}, spaces.list (plaintext, M1)
- todo.{create,list,complete}, notes.{create,search}, journal.add
  (encrypted — field lists match
  apps/mana/apps/web/src/lib/data/crypto/registry.ts verbatim)

Infra
- Port 3069 added to docs/PORT_SCHEMA.md
- services/mana-mcp/CLAUDE.md with architecture, auth model,
  tool-authoring recipe, local smoke-test steps
- Root CLAUDE.md services list updated

Type-check green across shared-crypto, mana-tool-registry, mana-mcp.
svelte-check on apps/mana/apps/web stays at 0 errors / 0 warnings.
Boot smoke verified: /health returns registry.loaded=true, unauthed
/mcp → 401, invalid-JWT /mcp → 401 with descriptive message.

Decisions locked in for later milestones (per plan D1–D10):
- Personas will be real mana-auth users (users.kind='persona'), no
  service-key bypass (D1, D2)
- Tool-registry is the SSOT; mana-ai and the legacy
  apps/api/src/mcp/server.ts get merged into it in M4 (three current
  parallel tool catalogs collapse to one)
- Persona-runner (:3070) will be a separate service using the Claude
  Agent SDK + MCP client (D5)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 13:18:35 +02:00
Till JS
4c2fbece56 docs(plans): point at-rest-sweep row at the restored commit
c413ab7dd was reverted by c31dcdd66; the re-apply (3a7bc7f1c) only
brought back the mana-research tests, not my sweep. Restored in
af4fd2776. Update the shipping-log row + the attribution note so
future readers find the actual payload.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 22:43:19 +02:00
Till JS
4250747523 docs(plans): log v35+v36 hard cleanups + backend-coherence audit
- Adds the 2c-followup #1 (f4c66241c, Dexie v35 data-table userId
  drop) and #2 (ce5d1f1a2, Dexie v36 user-level table space-field
  strip) to the shipping table.

- Adds a "Backend coherence" section documenting the 2026-04-22
  post-migration audit of mana-sync. Key finding: mana-sync is
  single-table event-sourcing (one sync_changes table with a
  table_name discriminator, already space_id-indexed + RLS-scoped),
  so the 7 newly-migrated client tables need zero server-side DDL.
  Flag is removed from the open-follow-ups list.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 19:16:34 +02:00
Till JS
59a679a2f9 docs(optimizable): add manual-test-backlog as the "zu testen" register
Single file tracking code-complete features still awaiting a human
click-through. Distinct from test-health.md (auto-test coverage) and
TESTING_DEPLOYMENT_CHECKLIST.md (CI system).

Seeded with 4 open entries: data-export-v2 roundtrip, shared-space
two-user smoke (links to existing walkthrough), articles bookmarklet
on consent-walled sites, PWA share-target. Entries get deleted once
verified in a real browser.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 19:02:55 +02:00
Till JS
fd1ea47075 feat(backup): client-driven v2 snapshot export, drop server-side backup
Replaces the mana-sync event-stream export (GET /backup/export) with a
fully client-driven `.mana` v2 archive: webapp reads Dexie, decrypts
per-field, packages JSONL + manifest, optionally PBKDF2+AES-GCM seals
with a passphrase.

- New: backup/v2/{format,passphrase,export,import}.ts + format.test.ts
  (10 tests: round-trip, sealed path, 3 failure modes incl. wrong-
  passphrase vs. tamper distinction).
- UI: ExportImportPanel with module multi-select, optional passphrase,
  progress + sealed-file detection — replaces the old backup flow in
  Settings → MyData.
- Removes services/mana-sync/internal/backup/ and the corresponding
  client helpers + v1 tests. No parallel paths, no legacy shim.
- Why client-driven: zero-knowledge users hold their vault key only
  client-side, so a server exporter cannot produce plaintext archives;
  GDPR Art. 20 portability is better served by plaintext-by-default.
- Cross-account restore works via re-encryption under the target
  vault key (no MK transfer needed).

DATA_LAYER_AUDIT.md §8 rewritten to reflect the new architecture.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 18:46:29 +02:00
Till JS
be9213ae5d docs(plans): log 2d.5/2d.6/2e/2c/2e-followup to the shipping table
Brings the shipping log up to date with everything shipped since the
last doc commit, plus flags the c413ab7dd attribution race (same
lint-staged rollback pattern that caught 3b85d7d3d) so future
searches find the at-rest-sweep payload under a misleading
test(mana-research) title.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 18:35:51 +02:00
Till JS
d5b889ac58 docs(gemini-deep-research): Mac-Mini deploy log 2026-04-22
Capture the surprises from the first deploy so the next rollout
(or rollback) has the full picture without spelunking logs:

- mana-research had never been started on the Mac-Mini, even though
  it was defined in compose. First-boot via `docker compose up -d`.
- research.* schema is not auto-migrated on service boot — drizzle
  push must be triggered explicitly: `docker exec mana-research
  bun run db:push`. 5 tables created.
- GOOGLE_GENAI_API_KEY was missing in /Users/mana/.../mana-monorepo/.env.
  Copied the local key over, with `.env.bak.pre-gemini-deep-research`
  as rollback anchor.
- Redis NOAUTH fix (commit 4867300d0) referenced here.
- Smoke-test outcome documented: the 500 was mana-credits 404 on a
  test user without a wallet row — expected, and it proves the whole
  auth/dispatch chain up to the credits hop works.
- Also noted: mana-llm has the same bare REDIS_URL in compose
  (out-of-scope for this deploy), and /providers/health does not list
  async providers (known design gap).

Status header updated to reflect deploy completion. Flag stays off
(MANA_AI_DEEP_RESEARCH_ENABLED=false) pending explicit enablement.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 18:22:31 +02:00
Till JS
ef76815eb2 docs(optimizable): add README index + refresh frontend-consistency tracker
The frontend-consistency-improvements.md was stale — it still listed
the 13 Tailwind-based ListViews as "to migrate" even though all 21
flagged modules shipped today (a2a43b1d5, 86c205ffc, 7d6a340b1, 52af8c0ce,
3e09ff66d). Rewrite to reflect the SHIPPED state and list the next
layer of open consistency work (i18n, Phosphor icons, cross-surface
theme parity).

New docs/optimizable/README.md is the master index. Consolidates:
  - 🔴 Release blocker: tier-patch revert (links memory entry)
  - 🟠 Tracked trackers: per-topic links with status
  - 🟡 Small open items without a dedicated file: module-structure
    audit, plan-inventory hygiene, memory-hygiene post-release,
    cross-surface theme parity
  - How-to: list of `pnpm run audit:*` commands for live metrics

Doesn't introduce new work items beyond what's already been discussed
this session — just gives them a home so future sessions can pick up
any one without re-discovery.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 18:07:44 +02:00
Till JS
4d5a96e21b perf(invoices): lazy-load pdf-lib + swissqrbill, -516 KB on route
/(app)/invoices/[id] route bundle drops from **534 KB → 18.6 KB** by
moving PDF rendering behind dynamic imports.

Changes:
  - views/DetailView.svelte: `await import('../pdf/renderer')` inside
    renderPdf() + downloadPdf(), cached in a module-local ref.
  - components/SendModal.svelte: same for openAndDownload().
  - pdf/scor.ts (new): generateSCORReference extracted so the
    invoices store can derive a reference string without pulling
    swissqrbill/svg + pdf-lib into the list-view bundle.
  - pdf/qr-bill.ts: re-exports generateSCORReference from scor.ts
    for backward compatibility.
  - stores/invoices.svelte.ts: imports from ../pdf/scor (light) instead
    of ../pdf/qr-bill (heavy).
  - index.ts: drop re-export of the PDF renderer from the module
    barrel so `import ... from '$lib/modules/invoices'` never drags
    pdf-lib in.

The heavy chunk (pdf-lib + swissqrbill, ~576 KB) now only loads when
a user actually opens an invoice detail — list views, create flow, and
all other routes stay lean.

20/20 qr-bill tests pass; svelte-check clean.

Bonus: scripts/audit-icon-usage.mjs (+ pnpm run audit:icon-usage)
audits @mana/shared-icons imports. Reveals 204 distinct icons across
the codebase, 199 of them at default weight but paying for all 6
Phosphor weights. Biggest offender: app-registry/apps.ts with 69
static icon imports accounting for ~290 KB of the shared 466 KB icon
chunk. Migration path for that is documented in
docs/optimizable/bundle-analysis.md §2 — next session's work.

docs/optimizable/bundle-analysis.md also updated with the root (app)
layout (260 KB) investigation notes (start/stop lifecycle hooks to
defer via idleCallback).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 18:03:53 +02:00
Till JS
9f4ebd8dad docs(plans): log shipping status for space-scoped phases 2a–2d.4
Two purposes:

1. Make the phase-by-phase progress discoverable — future readers can
   see at a glance what's shipped, which commit hash lands each
   phase, and what's still open.

2. Flag the 2d.4 attribution oddity: the active-space handler API +
   per-Space workbench-scenes localStorage + scene spaceId filter +
   runAgentsBootstrap-on-space-change wiring landed inside commit
   3b85d7d3d ("chore(bundle): add bundle-size audit") by accident,
   when a parallel terminal session's git add -A scooped up those
   staged files during a lint-staged rollback race. The commit
   message understates the contents; code is correct and tests pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 17:56:30 +02:00
Till JS
2a18cb5ee4 feat(mana-ai): v0.7 — cross-tick Deep Research Max pre-planning
Opt-in path for missions that want Gemini Deep Research Max (up to 60 min
per task) instead of the shallow RSS pre-research. Because Max runs well
past a single 60-second tick, the state is carried across ticks:

  tick N:   submit → INSERT mission_research_jobs row → skip planner
  tick N+k: poll → still running → skip planner (metric pending_skips)
  tick N+m: poll → completed → inject as ResolvedInput, DELETE row, plan

- ManaResearchClient talks to mana-research's new internal
  /v1/internal/research/async endpoints with X-Service-Key +
  X-User-Id. Graceful-null on transport errors so a flaky
  mana-research never crashes the tick loop.
- New table mana_ai.mission_research_jobs with PK (user_id, mission_id)
  — presence is the "pending" flag; delete-on-terminal keeps queries
  trivial.
- handleDeepResearch() encapsulates the state machine; planOneMission
  now returns a discriminated union (planned | skipped | failed) so
  "research pending" isn't miscounted as a parse failure.
- Opt-in at TWO gates to keep cost in check ($3–7/task, 1500 credits
  per run):
    1. MANA_AI_DEEP_RESEARCH_ENABLED=true server-side (default off)
    2. DEEP_RESEARCH_TRIGGER regex matches the mission objective
       (strict: "deep research", "tiefe recherche", "umfassende
       recherche", "hintergrundrecherche", "deep dive")
  Falls back to shallow RSS when either gate fails or the submit
  errors upstream.
- Prom metrics: mana_ai_research_jobs_{submitted,completed,failed}_total
  labelled by provider, plus _pending_skips_total.
- docker-compose wires MANA_RESEARCH_URL + the opt-in flag and adds
  mana-research to depends_on.
- Full write-up with real API response shape (outputs plural, not
  OpenAI-style), step-3 MCP-server plan (security-gated, not built),
  ops + kill-switch: docs/reports/gemini-deep-research.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 17:56:06 +02:00
Till JS
3b85d7d3d2 chore(bundle): add bundle-size audit + snapshot inventory
scripts/audit-bundle.mjs reads `.svelte-kit/output/client/_app/immutable`
after a prod build and reports:
  - Total size + category breakdown (entry / nodes / chunks / workers /
    assets).
  - Top N JS files with content heuristics (transformers.js, zxcvbn,
    tiptap, pdf-lib, swissqrbill, rrule, suncalc, Phosphor icon paths,
    Vite __vite__mapDeps metadata, etc).
  - Route mapping for `nodes/*.js` by parsing the server manifest's
    `leaf:` entries, so node 118 is identified as /(app)/invoices/[id].
  - ⚠ flag on chunks/ ≥ 200 KB (shared, potentially eager).

Current snapshot (docs/optimizable/bundle-analysis.md):
  entry   92 KB  |  nodes   2.77 MB  |  chunks   5.59 MB
  workers 22.3 MB (ONNX WASM, lazy)  |  total   31.8 MB

Already healthy:
  - 92 KB entry (no critical-path bloat).
  - 22 MB transformers.js WASM is worker-scoped — only fetched on first
    /llm-test or memoro voice use.
  - zxcvbn (1.25 MB combined dict + keyboard graphs) correctly behind a
    dynamic import in PasswordStrength.svelte.

Follow-up opportunities logged:
  1. /invoices/[id] = 534 KB — split swissqrbill + pdf-lib via dynamic
     import.
  2. @mana/shared-icons = 317 + 149 KB SVG path chunks — migrate to
     tree-shakable per-icon imports or lazy-load.
  3. Root (app) layout = 260 KB — check for module bleed into shared
     shell.

Report-only. Run with `pnpm run audit:bundle`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 17:52:08 +02:00
Till JS
72a5995fa5 feat(articles): M9 workbench homepage — 4-tab shell + QuickAdd + StatsView
Articles ist jetzt als Workbench-App in apps.ts registriert
(icon BookOpen, collection 'articles', paramKey 'articleId') und
landet damit im Scene-App-Picker. HomeView/ListView/HighlightsView/
StatsView teilen sich eine neue ArticlesTabShell, die sowohl als
SvelteKit-Route als auch als Workbench-Karte rendert.

Shell (ArticlesTabShell.svelte):
 - Top-Bar mit QuickAddInput (URL einfügen + Enter = Save + goto
   Reader; kein Preview-Schritt) und Settings-Gear.
 - Tab-Leiste darunter: Leseliste | Highlights | Favoriten | Stats.
   Leseliste ist Default (initialTab='list').
 - Tab-Wechsel läuft intern via $state + Svelte-Context — kritisch
   für die Workbench-Karte, wo goto() den User aus der Karte kicken
   würde. getArticlesTabContext() aus tab-context.ts gibt tief
   verschachtelten Sektionen eine switchTo(tab)-API.
 - Padding 1rem 1.25rem auf der Shell selbst — PageShell.page-body
   hat null padding, sonst klebt QuickAdd am Card-Rand. Im Route-
   Kontext addiert's sich zum (app)-Layout-Padding ohne zu viel.

Tabs:
 - Leseliste (list): bestehende ListView mit optionalem
   initialFilter-Prop. Continue-Reading-Strip (HomeSectionWeiterlesen
   horizontal carousel) erscheint über den Filter-Chips wenn
   status='reading'-Artikel existieren und filter ∈ {all, reading}.
   Filter-Chips sind einzeilig + horizontal scrollbar mit
   scroll-snap-Einrast; inaktive Chips haben jetzt sichtbare
   Background-Füllung + Border via color-mix(currentColor) — adaptiv
   fürs Theme.
 - Highlights (highlights): HighlightsView unverändert (nur der
   eigene Header + Zurück-Button raus, liegt jetzt in der Shell).
 - Favoriten (favorites): ListView mit initialFilter='favorites' —
   Shell-Shortcut auf den Filter.
 - Stats (stats): neue StatsView mit Stats-Strip (savedThisWeek,
   finishedThisWeek, avg reading time), Highlight-Counter, Top-
   Sources und Archiv-Link.

Routes (unter (tabs)-Gruppe):
 - /articles                → initialTab="list"   (Default)
 - /articles/list           → initialTab="list"   (alias)
 - /articles/highlights     → initialTab="highlights"
 - /articles/favorites      → initialTab="favorites"
 - /articles/stats          → initialTab="stats"
 Detail/Add/Settings bleiben bewusst ausserhalb — die haben ihren
 eigenen Reader/Form-Chrome und sollen die Tab-Leiste nicht zeigen.

Neue Files:
 - ArticlesTabShell.svelte   (Tab-Host)
 - tab-context.ts            (Cross-Tab-Switch-Context)
 - components/ArticleCard.svelte (shared Card aus ListView extrahiert,
                                  row + compact Varianten)
 - components/QuickAddInput.svelte (URL-Input aus HomeView extrahiert)
 - components/HomeSectionSources.svelte
 - components/HomeSectionStats.svelte
 - components/HomeSectionWeiterlesen.svelte
 - views/StatsView.svelte
 - routes/(app)/articles/(tabs)/{+page,list,highlights,favorites,stats}

Gelöscht:
 - HomeView.svelte (Overview-Tab wurde rausgenommen auf User-Feedback)
 - HomeSectionFrisch/Highlights/Favorites (durch eigene Tabs ersetzt)

docs/plans/articles-homepage.md dokumentiert den Architektur-Plan,
inklusive der Entscheidung für "eine Card pro Domain, interne Tabs"
statt zwei separater App-Registrierungen.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 17:50:38 +02:00
Till JS
68c0eb2892 chore(test + audit): add test-coverage audit + wire audit:all
#6 test coverage (pivot to reporting): 34/653 tests currently fail
(in-flight spaces-foundation migrations). Hard coverage thresholds
aren't enforceable until the suite is green, so this session ships a
file-presence audit instead of line-coverage gates.

  - scripts/audit-test-coverage.mjs — counts .svelte + .ts source files
    vs .test.ts + .spec.ts per module. Reports total ratio, lists
    modules with 0 tests + ≥3 source files (prioritised by size).
  - pnpm run audit:test-coverage  wires it into audit:*.
  - docs/optimizable/test-health.md — state + prevention path + top
    untested modules ranked by impact.

Current baseline: 2.6% file-level coverage. 66/78 modules have zero
tests. Biggest untested: times (32 src), articles (29), events (27),
inventory + skilltree (20 each).

#8 audit:all: single entry point for the reporting audits. Runs
port-drift + i18n-coverage + test-coverage in --summary mode. Distinct
from validate:all (which is gates, not reports).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 17:38:12 +02:00
Till JS
eec369bd04 chore(i18n): add coverage audit + migration inventory
Translation infrastructure (@mana/shared-i18n + svelte-i18n + 35
per-module locale files with ~3500 lines across de/en/it/fr/es) is fully
wired, but 65/78 modules still hardcode German in .svelte templates
rather than calling {$_('module.key')}.

Adds:
  - scripts/audit-i18n-coverage.mjs — scans lib/modules/**/*.svelte for
    hardcoded German keywords (Abbrechen, Speichern, Löschen, etc.) in
    files that don't import $_(). Reports per-module hit counts,
    bucket (FULL/PARTIAL/NONE), and whether the locale file exists.
    Supports --summary and --top N flags.
  - pnpm run audit:i18n-coverage  wires it into the audit:* family
    (reporting only, not a CI gate — existing debt would fail
    validate:all otherwise).
  - docs/optimizable/i18n-migration-inventory.md — priority list,
    per-module workflow, and prevention plan.

Top offenders: broadcast (26 hits), articles (24), events (23),
invoices (22), quiz (20), stretch (20), library (19), profile (17),
skilltree (15, PARTIAL), calendar (14, PARTIAL). Modules without a
locale file (broadcast/articles/events/invoices/…) need the locale
stubs scaffolded first.

Real string migration is per-site careful work (key naming, 5-language
parity, UI visual QA) and is left for per-module follow-up sessions.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 17:16:55 +02:00
Till JS
9db044178c docs(plans): Phase 1 audit — space-scoped migration
Audit of every Dexie table in apps/mana/apps/web/src/lib/data/database.ts
+ crypto registry finds no blockers for Phase 2, with two scope
adjustments to fold in:

1. Add agentKontextDocs (v22) to the to-migrate list. Per-agent
   context docs reference the aiAgents table; migration order must
   be agents first, then backfill agentKontextDocs.spaceId via
   parent-agent lookup.

2. The 46 already-space-scoped tables from the Spaces-Foundation
   sprint all still carry userId alongside spaceId. To hit the
   "no table has both userId and spaceId" invariant, Phase 2
   extends from just the 7 newly-migrated tables to a ~53-table
   sweep dropping userId everywhere. Mechanically identical per
   table, so the extra scope is cheap.

Also confirmed:
- All 19 junction tables have space-scoped parents — no dangling
  refs. Safe to migrate parents.
- Actor columns (__lastActor / __fieldActors) stamped everywhere by
  the Dexie creating hook — userId can be dropped confidently.
- userContext (v23 profile hub) is distinct from kontextDoc (AI
  planner injection). userContext stays user-level; kontextDoc
  moves per-Space. No collision.
- 10 user-level singleton tables correctly identified to stay
  user-level (userSettings, newsPreferences, meditateSettings, …).
- 10 internal/infra tables (_pendingChanges, _events, _aiDebugLog,
  …) get per-table treatment; mostly no spaceId needed.

Phase 2 can proceed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 16:28:00 +02:00
Till JS
129971ffc3 docs(plans): revise space-scoped plan — remove legacy residues
Self-audit of the previous draft surfaced 7 legacy residues that would
have left the rebuild short of the "optimal architecture" bar. Rewrite
the plan with those addressed:

1. Drop userId from data records entirely. Attribution lives in the
   Actor system (__lastActor / __fieldActors). userId stays only on
   explicitly user-scoped tables.
2. Active scene localStorage key becomes per-Space:
   `mana:workbench:activeSceneId:${spaceId}` — switch Space A → scene
   X, to B → scene Y, back to A → X restored.
3. New user-level userTagPresets table replaces the "copy from
   Personal" checkbox hack. First-class templates for seeding new
   Spaces with a named tag set; CRUD in Settings.
4. Encryption decision made in-line: globalTags + tagGroups names
   encrypted during migration, not deferred (tag names like
   "Therapie" or "Finanzen-privat" can leak personal categorization).
5. kontextDoc moves from user-level singleton to per-Space. AI runner
   pulls the active Space's kontextDoc; Shared-Spaces start without
   one until the user writes one.
6. Default-agent bootstrap uses SpaceType-aware names (Mana for
   personal, Familien-Helfer for family, Team-Assistent for team,
   etc.) so users don't end up with "three Mana" in their agent list.
7. Phase 1 explicitly audits every junction table to verify parent
   records carry spaceId — no silent user-global references.

Also: an explicit "No legacy residues" section anchors these as
intentional anti-patterns to prevent drift. Success criteria now
includes "no table has both userId AND spaceId" as a testable
invariant.

Timeline grows from 3–4 to 4–5 days; the delta is encryption wiring
+ userTagPresets CRUD + the userId→Actor cleanup.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 16:23:42 +02:00
Till JS
470f3b1b6c docs(plans): space-scoped data model (Modell β) — commit plan
Supersedes per-space-vs-user-global-tags.md (which recommended defer
under "ship fast" assumptions). Pre-live + unlimited resources changes
the calculus: build the clean architecture now.

Decision: tags, tag-groups, workbench scenes, AI agents, and AI
missions all become Space-scoped. Only identity (user, session,
profile, MK key) and per-device UI prefs stay user-level.

Plan covers 8 phases across ~3–4 days:
1. Audit + schema design
2. Dexie migration (with backfill to user's Personal-Space)
3. Store APIs (implicit via scopedForModule wrapper)
4. Space-switch side-effects (reset active scene, bootstrap defaults)
5. Space-creation seeding (one-shot copy tags from Personal)
6. Backend (mana-sync + Postgres + RLS)
7. Docs + memory updates
8. Delete the old deferred plan

Includes edge cases, success criteria, and reasoning for why β over γ
(two clear levels beat one recursive primitive for user clarity).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 15:54:31 +02:00
Till JS
db2023a77f docs(plans): per-Space vs user-global tags — decision deferred
Strategic decision doc covering whether the central tag system
(@mana/shared-stores → globalTags) should move from user-global to
per-Space, prompted by integration debt between Spaces (hard tenancy)
and Scene-Scope (tag-based view filter).

Surveyed current state: no spaceId column on globalTags or any of the
19 junction tables, 68 consumer imports, plaintext sync, guest-mode
seed.

Evaluated three options:
- A — status quo (user-global, no migration)
- B — fully per-Space (clean, but loses follow-me-everywhere)
- C — hybrid (nullable spaceId, recommended target if migration)

Recommendation: defer. Stay on A until one of five trigger signals
fires (first shared-Space tagging, user-reported clutter, scope
mismatch bug, >50 tags, or encryption/compliance need). Phase-by-phase
work breakdown included for when we revisit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 15:32:15 +02:00
Till JS
dc22240483 docs(plans): revise workbench-cards plan for cards-for-workflows rule
User feedback after the first batch shipped: the scene-picker got
cluttered when every admin/settings subpage became its own card.
Revise the plan to codify the sharper rule instead:

- Cards are for daily workflows
- Power-user domains get ONE card with internal tabs (initialTab prop
  for route deep-links)
- Config/settings stay as routes opened from the parent module's ⚙

Document the tabbed-card pattern (lib/modules/admin/tabs/*Tab.svelte
+ ListView container with role guard + initialTab), rewrite the
backlog around this principle, and fold batches 2/3/4 into a single
consolidated history that makes the scope revision explicit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 15:05:48 +02:00
Till JS
8647bfd100 Create tipps-module.md 2026-04-22 14:21:57 +02:00
Till JS
7611d109be feat(articles): M8 highlights view + stats + dashboard widget
useStats() live-query aggregates total / per-status / savedThisWeek /
finishedThisWeek / topSites / totalHighlights in one scoped Dexie pass.
useAllHighlights() joins cross-article highlights with article-header
info (title, siteName, originalUrl) for rendering.

/articles/highlights — HighlightsView groups chronologically-sorted
highlights per article with color-accented stripes, click-to-reader
jumps, and two export actions:
  - Copy as Markdown (clipboard)
  - Download .md (file)
Export logic lives in lib/markdown-export.ts as a pure function
(renderHighlightsMarkdown) so future snapshot tests don't need the
render tree.

Dashboard widget: ArticlesUnreadWidget mirrors NewsUnreadWidget's
pattern — self-contained live query, top-3 unread/reading, stats
strip ("N ungelesen · M diese Woche gespeichert"), empty state
CTA to /articles/add. Registered in:
  - lib/types/dashboard.ts (WidgetType union + WIDGET_REGISTRY)
  - lib/components/dashboard/widget-registry.ts (component map)
  - lib/i18n/locales/dashboard/{de,en}.json (translations)
  fr/it/es intentionally left untranslated — consistent with how
  invoices_open and broadcasts are handled.

ListView gains a pencil button next to the settings gear linking
to /articles/highlights.

Also: plan doc marks M7 + M8 done with commit refs; M1–M8 scope is
now complete.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 14:12:18 +02:00
Till JS
8a991f7c39 feat(articles): M7 share-target + bookmarklet — save from anywhere
@mana/shared-pwa gains PWAShareTarget + PWAShareTargetParams types
plus ManifestConfig.share_target pass-through. createPWAConfig now
accepts an optional `shareTarget` and threads it into the generated
manifest. Other apps keep working unchanged — the field is omitted
unless set.

Web app wiring:
 - vite.config.ts passes shareTarget: { action: '/articles/add',
   method: 'GET', params: { title, text, url } } so the installed PWA
   shows up as a destination in the Android / Chromium share sheet.
 - AddUrlForm reads ?url / ?text / ?title in onMount; falls back to
   the first URL-shaped token in ?text because some senders (Chrome
   Android, WhatsApp) put the shared link there instead of ?url. When
   a URL is pre-filled the Readability preview auto-triggers, so the
   user just hits "In Leseliste speichern" to confirm.
 - New /articles/settings route hosts the bookmarklet (drag-to-
   bookmarks-bar button + copy-to-clipboard + expandable snippet
   viewer) and a short Share-Target explainer with an iOS-Safari
   caveat. Linked from the ListView via a new gear button next to
   "+ Neu speichern".

Bookmarklet form (origin-prefixed so it works across tenants):
  javascript:void(window.open('${origin}/articles/add?url='+…))

Not in scope (plan marked optional): _pendingUrls offline queue.
Share without internet shows the existing error + retry state today;
can slot in as M7b if users hit it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 19:03:33 +02:00
Till JS
831c30eaa7 docs(plans): workbench-cards migration plan
Document the user's preference (cards over subroutes), the migration
pattern (module ListView + registerApp + thin route wrapper), what's
already shipped in batches 1 + 2, and the remaining backlog.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 18:57:22 +02:00
Till JS
5924f4fac3 feat(articles): M6 AI tools — list / save / archive / tag / highlight
Five new entries in AI_TOOL_CATALOG (shared-ai/src/tools/schemas.ts):

  list_articles            auto     Read-only listing with status +
                                    query filter. Default hides
                                    archived; 'all' includes them.
  save_article             propose  URL → Readability → encrypted save.
                                    Delegates to articlesStore.saveFromUrl
                                    which already handles scope-aware
                                    dedupe. Duplicates surface as
                                    success:true with duplicate:true.
  archive_article          propose  setStatus('archived') after
                                    scoped existence check.
  tag_article              propose  Case-insensitive dedupe over
                                    globalTags; tagMutations.createTag
                                    fills in when missing. Junction
                                    write via articleTagOps.addTag.
  add_article_highlight    propose  Snaps to the first verbatim
                                    occurrence of `text` in the
                                    decrypted article.content. Fails
                                    cleanly when the snippet isn't
                                    found — no orphan highlights.

Policy, client executor, and server planner derive automatically from
the catalog (see root CLAUDE.md §"AI Tool Catalog") so no manual
registration in policy.ts / services/mana-ai is needed.

Skipped from the M6 plan: <AiProposalInbox module="articles" />. The
component doesn't exist in the current codebase — after the
pendingProposals-table drop in Dexie v29 the inbox surface moved to
the mission-detail cross-module view, and articles proposals show up
there automatically. Documented in docs/plans/articles-module.md.

Also updated: plan doc now marks M1–M6 as DONE with commit refs and
the next-step pointer.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 18:46:13 +02:00
Till JS
3357e88a1c feat(articles): new read-it-later module — save / read / highlight
Pocket-style module for saving arbitrary web URLs, extracting readable
content server-side via @mana/shared-rss (Readability + JSDOM), and
storing it AES-GCM encrypted in IndexedDB for offline reading.

M1 skeleton: Dexie v33 (articles, articleHighlights, articleTags),
crypto registry entries, module registration, app-registry entry with
orange icon, empty-state ListView. articleTags is a pure junction
into the existing globalTags system (appId 'tags') — same pattern as
noteTags, eventTags, placeTags.

M2 URL save + reader: POST /api/v1/articles/extract (one endpoint,
not two — client caches the preview payload to avoid a double
server fetch). AddUrlForm with scope-aware dedupe, DetailView with
ReaderView typography shell (serif/sans, light/sepia/dark, size
slider), auto-tracked reading progress with scroll restore.

M3 highlights: TreeWalker-based plain-text offset resolution
(lib/offsets.ts), highlights store, floating HighlightMenu with
create + edit modes, HighlightLayer orchestrator that wraps/unwraps
highlight spans whenever highlights or htmlVersion changes. Four
colours (yellow/green/blue/pink), optional notes, click-to-edit,
dark-mode-aware overlay colours.

Drive-by: removed stale 'pendingProposals' entry from the plaintext
allowlist — the table was dropped in Dexie v29 and the allowlist
audit was flagging it as a dead entry.

Plan: docs/plans/articles-module.md. M4 (tags + filter + progress),
M5 (news:type='saved' migration), M6 (AI tools), M7 (share target),
M8 (highlights view + stats) still open.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 16:20:23 +02:00
Till JS
38d35247cd feat(spaces): end-to-end shared-space sync (membership lookup + plaintext)
Closes the gap between "invite flow UI exists" and "two users in the
same space actually see each other's data". Three pieces land together
because they're meaningless without each other.

mana-auth — new internal endpoint:
  GET /api/v1/internal/users/:userId/memberships
  Returns [{organizationId, role}, ...] for the user. mana-sync uses
  this to populate the multi-member RLS session config.

mana-sync — membership lookup:
  new internal/memberships package with an HTTP client + 5 min
  per-user cache, fail-open (empty list = pre-Spaces behavior).
  Config gets MANA_AUTH_URL (default http://localhost:3001).
  Handler.NewHandler takes the Lookup. Every Push/Pull/Stream call
  now passes spaceIDsFor(userID) to Store methods.
  GetChangesSince + GetAllChangesSince extend their WHERE clause:
    WHERE (user_id = $1 OR space_id = ANY($memberSpaces))
  so co-members see each other's rows, not just the author.

apps/web — encryption skip for shared-space records:
  encryptRecord now checks record.spaceId:
    - `_personal:<userId>` sentinel OR no active shared space → encrypt
      with user master key (E2E as today).
    - Active space resolves to non-personal type AND spaceId matches
      that space → skip encryption; write lands plaintext.
  decryptRecord is unchanged because its per-field isEncrypted() guard
  already passes plaintext through.
  Phase-1 compromise: shared-space data is protected by server RLS
  only, not E2E. Phase 2 adds per-Space shared keys with per-member
  wrap — tracked in docs/plans/spaces-foundation.md.

Plus docs/plans/shared-space-smoketest.md: step-by-step Zwei-User-Test
mit erwarteten Ergebnissen und Debugging-Hinweisen bei Problemen.

Build + go test + web check all green.

Plan: docs/plans/spaces-foundation.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 20:46:53 +02:00
Till JS
b13eb449b3 docs(broadcast): plan newsletter / broadcast module
MVP scope: campaigns CRUD, audience filter from contacts, Tiptap editor,
bulk-send via mana-mail extension, per-recipient tracking (open/click/
unsubscribe), DSGVO-compliant footer, DNS-check.

Key decisions made up-front:
- Tracking endpoints live in mana-mail (public, token-HMAC signed) —
  not in apps/api, because mana-mail already owns SMTP + auth plumbing
- Per-recipient state stays Postgres-only; no Dexie mirror (could be
  millions of events for big lists, no cross-device benefit)
- Tiptap over Unlayer/Lexical: MIT, Svelte wrapper exists, extension-
  based so bundle stays lean via tree-shaking
- juice for CSS-inlining runs server-side — keeps the client bundle
  light and concentrates email-compat knowledge in one place
- Explicitly NOT zero-knowledge compatible; server needs plaintext
  recipient lists to send. Warning in onboarding.
- 10 milestones, ~17 days MVP. M1-M4 builds the core send path,
  M5-M8 adds tracking + DSGVO + deliverability.

Related: docs/reports/clubdesk-vs-mana-comparison.md §7.2 Paket D.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 19:32:53 +02:00
Till JS
394aa79328 feat(invoices): close Phase-2 gaps — finance cross-link + structured addresses
Three items from docs/plans/invoices-module.md §"Offene Punkte" that
actually block real-world dogfooding:

1. Bezahlte Rechnung → Finance-Einnahme

  - financeStore.upsertTransactionFromInvoice(): deterministic id
    (invoice-tx-{invoiceId}) so marking the same invoice paid twice
    updates instead of duplicating. Uses table.put for the upsert.
  - invoicesStore.markPaid() calls it after the status transition,
    decrypts to get the gross + snapshot, converts minor→major for
    the finance row, formats description as "Rechnung {number} — {client}".
  - Best-effort: the call is try/catched so the invoice write (the
    thing the user initiated) never fails because of a finance bridge
    hiccup. Logs a warning instead.
  - Multi-currency caveat: finance's bare-number model loses the
    currency — documented in the upsert helper's comment. Works for
    single-currency freelancers (the 95% case).

2. Strukturierte Adressen für QR-Bill

  - LocalInvoiceSettings gains senderStreet/Zip/City/Country (nullable,
    so existing rows don't need a migration). Encryption registry
    updated to cover the new fields — same sensitivity tier as the
    legacy senderAddress blob.
  - InvoiceClientSnapshot gains street/zip/city/country, same shape
    as Debtor.
  - qr-bill.buildQRBillData prefers structured fields; falls back to
    parseAddress(senderAddress) for users who haven't touched the new
    settings form. Same preference chain on the client/debtor side.
  - PDF header + DetailView recipient block prefer structured too —
    stays in lockstep with what the QR-Bill reads.
  - SenderProfileForm replaces the single textarea with four labeled
    inputs. Legacy free-text address moves behind a <details> as a
    "weird edge case" escape hatch (Postfach, c/o etc.).
  - ClientPicker: same split, with contacts-source mapping using
    structured fields directly (contacts already have street/postalCode/
    city so no info loss).
  - Three new qr-bill tests cover the preference order: structured
    wins, legacy falls back, malformed snapshot omits debtor.

3. MODULE_REGISTRY.md

  - Added `invoices` under "Finanzen" with the cross-link note.

Tests: 48/48 green (up from 45), 0 type errors. Open Phase-2/3 items
still parked: camt.053 bank reconciliation, number-sequence multi-
device collision, unfreezing the paid→void edge.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 18:58:18 +02:00
Till JS
3194180efb docs(plans): mark shared-llm tool-call integration as deferred
B1 (token usage) and B2 (server-iteration auto-execution) shipped in
the follow-up session. B3 — extending the LlmBackend interface with
tool-call passthrough and wiring both runners through the orchestrator
instead of direct-fetch — was scoped out after honest re-evaluation:

- Browser-local Gemma can't do tool-calling reliably, so the tier-
  fallback value is low (the tool-tier collapses to mana-server/cloud
  anyway).
- BYOK/cloud routing via mana-llm proxy is functionally equivalent
  between direct-fetch and orchestrator paths.
- ~6 h of work across 8 files with no concrete user-facing unblock.

Kept the entry point documented for whenever a use-case actually
needs tier-routing of planner calls.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 18:36:07 +02:00
Till JS
dc1a0a65fb docs(invoices): mark all milestones done, list Phase-2/3 open items
Quick status sync after M8. M1–M8 all landed; what's left are the
Phase-2/3 items (multi-device number collision, structured address
schema, finance cross-link, camt bank reconciliation) and the
Spaces-SSR-unblock-then-dogfood step.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 18:23:02 +02:00
Till JS
80dbb3b3b6 feat(spaces): migrate calendar module to scoped-db wrapper (pilot)
First module to consume the scope layer — proves the model end-to-end
on a real query path.

Changes in calendar/queries.ts:
- db.table('calendars')   → scopedForModule<LocalCalendar>('calendar', 'calendars')
- db.table('timeBlocks')  → scopedForModule<LocalTimeBlock>('calendar', 'timeBlocks')
- db.table('events')      → scopedForModule<LocalEvent>('calendar', 'events')
- applyVisibility() wrapper runs on each read to drop private records
  authored by other members of a shared space.

Scope wrapper tweaks:
- getInScopeSpaceIds is now lenient during boot: if no active space has
  loaded yet, falls back to the user's personal sentinel so sentinel-
  stamped records from the v28 migration still render. Returns [] only
  when fully unauthenticated, which yields an empty-match filter.
- applyVisibility is no longer generic-constrained — T is inferred
  exactly as the input type; visibility/authorId are read via runtime
  duck-typing so arbitrary record shapes pass through cleanly.

Known follow-ups:
- Root-layout bootstrap (load active space + reconcile sentinels on
  login) is intentionally not wired up yet — needs a separate pass on
  the already-crowded (app) layout to avoid collateral damage.
- Four legacy tables (conversations, documents, spaceMembers,
  memoSpaces) carry a pre-existing `spaceId` field that points to the
  older context-space concept, not our multi-tenancy space. Renaming
  those to contextSpaceId is a tracked follow-up in the RFC — calendar
  is unaffected.

Plan: docs/plans/spaces-foundation.md (updated with the legacy-spaceId
note + lenient-scope rationale).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 16:42:10 +02:00
Till JS
2ee3a1a93a feat(invoices): M7 dashboard widget + tests + plan status
Adds the missing bits that turn M1–M6 into a coherent shippable
product rather than a pile of commits.

Dashboard widget (M7)
- InvoicesOpenWidget.svelte: open + overdue totals in the primary
  currency, top-3 oldest overdue with "X Tage überfällig" under each,
  empty-state CTA for first-time users
- Registered as `invoices-open` in WIDGET_REGISTRY and the component
  map. Default size medium, no requiredBackend (local-first, no API)
- Fixed pre-existing test gap: validBackends list was missing 'body'
  (body-stats widget has been failing silently) — added so the check
  protects against drift for real

Tests (45 total, all green)
- totals.test.ts (9): computeLineTotal with discount+vat, grouping
  invariant (breakdown sums == invoice totals), rounding edges
- pdf/qr-bill.test.ts (17): generateSCORReference stability +
  spec-validity via swissqrbill's own isSCORReferenceValid, buildQRBillData
  eligibility gates (currency, IBAN, address, amount), CH + DE address
  parser paths, referenceNumber-preferred-over-regen invariant
- mail-template.test.ts (12): subject/body composition (with/without
  subject, CHF vs EUR QR-hint, empty recipient fallback), mailto
  spaces-as-%20 patch, looksLikeEmail edge cases

Plan (docs/plans/invoices-module.md)
- Updated with commit SHAs per milestone, testing status, and the
  explicit list of open items (Logo-Upload, AI-Tools, sync collision,
  structured addresses, finance cross-link, camt bankabgleich) so the
  next coder knows exactly what's parked where

Unresolved: browser smoke test couldn't run — SSR is broken for all
module routes in the current tree (pre-existing, likely from the
parallel Spaces refactor; /library, /todo, /contacts all return 500
the same way). Unit tests + clean bundle build (M4) + type-check are
the coverage we have.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 16:38:18 +02:00
Till JS
b249345174 feat(spaces): add space types + module allowlist as multi-tenancy foundation
Introduces SpaceType ('personal' | 'brand' | 'club' | 'family' | 'team' |
'practice') and SPACE_MODULE_ALLOWLIST as the shared-branding primitives
for the Spaces refactor that replaces the user-vs-org polymorphy with a
single tenancy primitive (Notion/Linear pattern).

Pure additive — no runtime behaviour change yet. Better Auth config,
Dexie migration, scope wrapper and rolling module migration follow in
separate commits.

Plan: docs/plans/spaces-foundation.md
Social-relay plan now defers brand storage to the Spaces primitive.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 15:57:57 +02:00
Till JS
11f768b8e5 docs(invoices): ClubDesk vs. Mana comparison + invoices module plan
Competitive analysis of ClubDesk (reeweb ag, ~20'000 DACH clubs) with a
dual-use roadmap identifying features that benefit both clubs and general
users (freelancers/creators). First chosen step: invoices module with
Swiss QR-Bill as the CH-differentiator.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 15:27:57 +02:00
Till JS
da47f534bc docs(plans): function-calling migration + removal of propose/approve gate
Plan for ripping out the fragile text-JSON parser and the propose-approve
flow in one atomic PR. Key shifts:

- LLM uses native function calling — SDK-guaranteed structure, no parser
- Tool policy becomes auto | deny (no propose, no confirm for now)
- Timeline + per-iteration revert replace the proposal inbox as the
  review surface; missions run end-to-end without human approval
- Safety via mission-budget, manual-cadence, agent-policy, revert
- No _rationale meta-param (tool name + params are self-explanatory)

Applies to webapp runner, mana-ai server runner, and companion chat —
all three share one runPlannerLoop from @mana/shared-ai after migration.
Net: ~1000 LoC deleted, ~600 added.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 15:10:23 +02:00
Till JS
5d67179842 docs(workbench): plan for scene-scope empty state
Captures the UX gap — a scoped scene that filters out everything shows
the generic "Keine Treffer" with no hint that the scope is the reason
or how to clear it. Plan lays out a minimal Phase 1 (shared
ScopeEmptyState component + clearSceneScope helper, ~10 LOC per
ListView) with optional Phase 2/3 extensions. No code yet.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 16:23:13 +02:00
Till JS
b5d55fdb21 feat(events): add Event Discovery — Phase 1 + 2
Phase 1: Manual iCal feeds + Discovery tab
- 5 new DB tables in event_discovery schema (regions, interests,
  sources, discovered_events, user_actions)
- iCal parser (node-ical) with deduplication (SHA-256 hash)
- Crawl scheduler (15-min interval, auto-deactivate after 5 errors)
- CRUD routes for regions, interests, sources + paginated feed endpoint
- Frontend: "Meine Events" / "Entdecken" tab navigation in ListView
- Discovery setup wizard (regions via mana-geocoding + interests)
- DiscoveredEventCard with save/dismiss, SourceManager for iCal feeds
- "Merken" creates a local socialEvent from discovered event

Phase 2: Auto source discovery + LLM extraction + relevance scoring
- Source discoverer: web search via mana-research to auto-find iCal
  feeds and venue websites for a region
- Website extractor: crawl via mana-research /extract, then LLM-based
  event extraction via mana-llm with structured JSON output
- Flexible date parsing (ISO, DD.MM.YYYY), markdown fence stripping
- Relevance scorer: category match, freetext match, haversine distance,
  time proximity, weekend bonus (0-100 clamped)
- Routes: POST regions/:id/discover-sources, PUT/DELETE sources/:id/activate|reject
- Frontend: "Automatisch finden" button, suggested vs active sources UI

107 tests (all passing), no regressions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-18 15:30:46 +02:00
Till JS
38b9fdb91b docs: add Apr 16 devlog, remove duplicate devlogs, update MODULE_REGISTRY
- Add devlog for April 16 (Library, Wetter, Voice-Interview, Research-Lab)
- Remove duplicate devlog files for Apr 9 and Apr 10
- Update MODULE_REGISTRY: profile voice interview, wetter, wishes modules

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 15:27:00 +02:00
Till JS
49f315f6be feat(research): Phase 3a — 4 sync research agents
Adds Perplexity Sonar, Claude web_search, OpenAI Responses, and Gemini
Grounding as ResearchAgents behind the same comparison interface as the
search and extract providers.

New endpoints:
  POST /v1/research          — single-agent (or auto-routed to the first
                               provider with a configured key)
  POST /v1/research/compare  — fan-out across N agents, persist all
                               answers + citations in research.eval_*

Each agent normalizes its native response into a common AgentAnswer shape
(answer text + citations[] + tokenUsage), storing the provider's raw
response alongside for later inspection. Implementations use direct HTTP
against each vendor's public API — no SDK deps added.

Auto-routing preference: perplexity-sonar → gemini-grounding →
openai-responses → claude-web-search → (openai-deep-research stubbed for
Phase 3b). Credits orchestration reuses the search/extract executor
pattern (reserve → call → commit/refund).

Deferred to Phase 3b: openai-deep-research (async job queue), migration
of mana-ai + mana-api news-research to call this service directly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 15:06:12 +02:00
Till JS
2bdb48bdd1 feat(research): add mana-research service — Phase 1 + 2
New Bun/Hono service on port 3068 that bundles many web-research providers
behind a unified interface for side-by-side comparison. All eval runs
persist in research.* (mana_platform) so quality can be reviewed later.

Providers (Phase 1+2):
  search:  searxng, duckduckgo, brave, tavily, exa, serper
  extract: readability (via mana-search), jina-reader, firecrawl

Endpoints:
  POST /v1/search, /v1/search/compare       — single + fan-out
  POST /v1/extract, /v1/extract/compare     — single + fan-out
  GET  /v1/runs, /v1/runs/:id               — history
  POST /v1/runs/:run/results/:id/rate       — manual eval
  GET  /v1/providers, /v1/providers/health  — catalog + readiness

Auto-routing: when `provider` is omitted, queries are classified via regex
(fast path, 0ms) with optional mana-llm fallback, then routed to the first
available provider for that query type (news → tavily, academic → exa,
semantic → exa, etc.).

Credits: server-key calls go through mana-credits reserve → commit/refund
so failed provider calls don't charge the user. BYO-keys supported via
research.provider_configs (UI arrives in Phase 4).

Cache: Redis with graceful degradation (1h TTL for search, 24h for
extract). Pay-per-use APIs only — no subscription-gated providers.

Docs: docs/plans/mana-research-service.md + docs/reports/web-research-capabilities.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 14:42:25 +02:00
Till JS
2df9ecdcaa feat(rituals): rename ai-rituals → rituals, add ceremony step types
The module was named "ai-rituals" because every step was a tool call
(log drink, show tasks, create task from text input). That framing
excluded a whole class of rituals that *don't* capture data —
personal ceremonies that just want to hold the user's attention for a
minute: the morning coffee, the Sunday reset, the before-bed shutdown.

Changes:

  - Renamed the module: apps/web/src/lib/modules/ai-rituals → rituals
  - App id 'ai-rituals' → 'rituals' in app-registry/apps.ts
  - Moved the category from 'ai' to 'life' in app-registry/categories.ts
    (personal practice, not an AI subsystem)
  - Added RitualCategory = 'utility' | 'ceremony' | 'mixed' on both
    LocalRitual and RitualTemplate. Defaults to 'utility' on read so
    existing data from before this change stays accessible.
  - 3 new step types in the RitualStepConfig union:
      - presence : markdown body + optional countdown, no tool call.
                   Use case: "Fünf Minuten still trinken."
      - breath   : guided breathing with a circle that expands/contracts
                   on inhale/exhale. Presets: box (4-4-4-4), 4-7-8,
                   coherent (5-0-5-0), plus custom timings.
      - media    : image + caption (mantra / photo / quote) with
                   optional linger timer.
  - RitualRunner extended: timer teardown on step change, breath state
    machine with phase-driven scaling animation, stop/early-exit for
    both.
  - 3 ceremony templates seeded:
      - Morgenkaffee    : Wasser → Aufbrühen → 3 tiefe Atemzüge →
                          5 Min still trinken
      - Sonntag-Reset   : Ankommen → Streaks → Was nehme ich mit? →
                          Nächste Woche → Handy weg (mixed)
      - Vor dem Schlaf  : Bildschirme aus → 4-7-8 Atmung → Journal-
                          Eintrag → Loslassen
  - ListView: category filter chips (Alle / Utility / Zeremoniell),
    templates grouped by category in the picker, category pill on each
    ritual row (hidden for the default 'utility').
  - docs/MODULE_REGISTRY.md: moved from AI-System (now 8) to Gesundheit
    & Wellness (now 11).

No schema migration — the new `category` field is optional on
LocalRitual and falls back to 'utility' when undefined, so Dexie
doesn't need a version bump. Existing rituals (none in production)
keep working.

Heads-up for scenes: anyone who had 'ai-rituals' pinned to a workbench
scene will need to re-add it as 'rituals'. Acceptable given
pre-launch state.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 14:41:26 +02:00
Till JS
a252160585 feat(library): M3 — progress tracking (pages, episodes, issues) + restart
ProgressControls.svelte renders typ-spezifische Fortschritts-UI:
  - book   → range slider + page input + "Fertig"-Button; auto-completes
             the entry (status=completed, times++) when current == total
  - series → collapsible season/episode grid; each episode is a toggleable
             pill that writes into details.watched with a watchedAt stamp;
             auto-completes once watched.length == totalEpisodes
  - comic  → ±1 issue bumper; auto-completes on issueCount reach
  - movie  → atomic, no progress widget

libraryEntriesStore.restartEntry: flips a completed entry back to active,
stamps startedAt=today, clears completedAt. Preserves the per-episode
watched list so users keep the history of the previous run-through; they
can reset individual episodes via the tracker if they want a fresh pass.

DetailView embeds <ProgressControls {entry}> below the status row and
renders a "↻ Nochmal lesen/sehen" button whenever status === 'completed'.

docs/plans/library-module.md: M1 + M2 + M3 marked DONE with commit IDs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 13:17:22 +02:00
Till JS
8c6502d0ff feat(library): add Bibliothek module — books/movies/series/comics log
M1 skeleton for a new media-consumption module. Single-table design with
a `kind: 'book' | 'movie' | 'series' | 'comic'` discriminator and a
discriminated `details` union for kind-specific fields (pages / runtime /
episode tracker / issue count). Shared kern: status, rating, review,
favourites, times counter, completedAt — which enables cross-media
queries like a year-in-review.

Dexie migration v26 was already registered in module-registry.ts /
database.ts via the preceding wetter commit (62aac6dfd); this commit
adds the actual module code, encryption registry entry, app-icon,
MANA_APPS entry, Kreativität & Medien category row, and the module
plan at docs/plans/library-module.md.

Encrypted fields (via ENCRYPTION_REGISTRY):
  title, originalTitle, creators, review, tags
Plaintext (intentional):
  kind, status, year, rating, genres, completedAt, isFavorite, times,
  externalIds, details — all needed for the tab filter, status chips,
  Jahresrückblick range-scan, and progress UIs.

Product decisions (frozen in the plan):
  - audiobooks = kind='book' with details.format='audio'
  - manga     = kind='comic' (no sub-discriminator)
  - metadata lookup (M7) lands as an endpoint in apps/api, not a
    standalone service

Guest seed ships one example per kind (Dune, Arrival, Severance, Saga)
so first-run users immediately see what the module does.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 03:49:01 +02:00
Till JS
4b21e3f448 docs: add MODULE_REGISTRY.md — index of all 72 webapp modules
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 16:36:26 +02:00
Till JS
93358ed002 fix(ai): P0 — tool exception handling + mission run mutex
Two critical fixes from the AI Workbench audit:

1. Tool exceptions in the reasoning loop:
   stage(ps, aiActor) is now wrapped in try-catch. If a tool throws
   (Dexie error, vault locked, network timeout), the step is recorded
   as failed with the error message in the summary, and the loop
   continues with the next step. Previously, one broken tool crashed
   the entire iteration.

2. Concurrent mission scope interleaving:
   runMission() now serializes through a promise-based mutex. Two
   concurrent calls (double-click, cadence overlap) queue instead of
   interleaving — prevents the ambient withAgentScope() from stomping
   a running mission's scope with a different agent's tags.

   scope-context.ts also gains filterByScopeExplicit(records, scopeTagIds,
   getTagIds) — the explicit, race-safe variant that doesn't read
   ambient state. Callers that already have the scope should prefer it.

Also adds docs/optimizable/ai-workbench-audit-2026-04-16.md with the
full audit (P0–P2, 12 items).

Runner tests: 8/8.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 16:17:08 +02:00
Till JS
fe141e193e docs: add langfristige Roadmap-Punkte zu AI_AGENTS_IDEAS.md
Update the AI agents backlog with:
- Status update: mark SSE streaming, dynamic tool catalog, MCP server,
  guardrails, OTel tracing, budget enforcement as done (with commit refs)
- New item #9: Agent-to-Agent Delegation (the biggest remaining gap)
- New item #10: A2A Agent Cards (depends on #9)
- New item #11: Graph-based Mission Workflows (low priority)
- New item #12: Agent Long-Term Memory via Embeddings

Each item includes problem statement, industry comparison, implementation
sketch, dependencies, effort estimate, and impact assessment.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 15:27:38 +02:00
Till JS
a1bb703086 docs: final report update — 7/10 roadmap items done, all tables consistent
Fix missing strikethroughs in §6 table (#1, #2, #6) and update Fazit
to reflect final state: 7 of 10 items done. Document remaining 3
langfristige Punkte with context on dependencies and priorities.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 15:23:37 +02:00
Till JS
62fc566693 docs: mark OTel tracing (#7) as done in architecture report
7 of 10 roadmap items now complete. Remaining: Agent-to-Agent (#4),
A2A Agent Cards (#8), Graph Workflows (#9), Agent Memory (#10).

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