Commit graph

57 commits

Author SHA1 Message Date
Till JS
e0820331b0 feat(wardrobe): solo-garment try-on + plan-doc status updates (M4.1)
Closes the one checklist item M4 left for later — "TryOnButton auf
DetailGarmentView (mit impliziten 'Solo-Outfit')". A user can now open
a single garment's detail page, see "An mir anprobieren · 10 Credits",
and get an inline preview of themselves wearing just that one item
(or just that accessory, for glasses/jewelry/hat/accessory).

Client:
- api/try-on.ts: extracts a shared callGenerateWithReference() helper
  and a dimsForSize() utility from runOutfitTryOn so the new
  runGarmentTryOn can share the HTTP-error matrix + picture.images
  row shape without a refactor of the outfit path.
- runGarmentTryOn({ garment, faceRefMediaId, bodyRefMediaId?, prompt?,
  quality? }): auto-detects accessoryOnly from the garment's category
  (FACE_ONLY_CATEGORIES), composes the DE default prompt ("im/in
  <Name>", "mit <Name>" für Accessoires), writes a picture.images row
  with wardrobeOutfitId=null so it doesn't pollute any outfit's
  try-on history. Does NOT update any outfit.lastTryOn — it's a
  standalone preview, on purpose.
- GarmentTryOnButton.svelte: thinner sibling of TryOnButton. Same
  three states (ready / missing-refs / loading), same non-personal-
  space disclaimer. Extra: inline preview panel showing the last
  rendered result, with a link to the Picture gallery ("Gefunden in
  der Picture-Galerie als normale Generierung.").
- DetailGarmentView now puts the try-on action above the existing
  wear-tracking button. Try-on is the more engaging action for this
  page; demoting "heute getragen" to a secondary-styled button
  respects that without removing it.

Plan docs:
- docs/plans/wardrobe-module.md — rewrites the Status block to M1-M5
  with actual commit hashes, and checks off the per-milestone task
  lists. Adds a new M4.1 block for solo-garment try-on.
- docs/plans/me-images-and-reference-generation.md — adds the v40
  space-scope migration (cb9a9bb42) as its own row in the commit
  table, with a pointer to the sub-plan.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 21:14:35 +02:00
Till JS
441f95697b docs(website): smoketest walkthrough + manual-test-backlog entry
The code is shipped (M1–M7) but nothing has run against real
Postgres + mana-sync + mana-media + a browser. This smoke-test doc is
the click-through a human needs to do before we trust the feature in
production.

- docs/plans/website-builder-smoketest.md — 10 scenarios end-to-end
  from migrations + dev-stack through create/publish, block coverage
  (image upload, gallery lightbox, columns container), forms with
  honeypot + rate-limit, moduleEmbed with public-flag enforcement,
  templates + AI tools, subdomain rewrite, custom-domain DNS verify,
  rollback + analytics, metrics + GC script, edge-cases + security.
  Lists bekannte Limits (CF SaaS gap, target-delivery, AiProposalInbox)
  explicitly so the tester knows what NOT to expect.

- docs/optimizable/manual-test-backlog.md — new release-blocker entry
  pointing at the walkthrough. Follows the same format as the Shared
  Space + Data Export entries.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 18:42:42 +02:00
Till JS
507af32bcc docs(plans): spec for per-Space destructive-tools opt-in
Bau-Plan für die M1-Polish-Lücke: heute lehnt evaluatePolicy() jeden
destructive-Call ab weil settingsFor() hardcoded { allowDestructive: [] }
zurückgibt. In POLICY_MODE=enforce würde das alle User von destructive
Tools aussperren. Bisher kein Problem — es gibt keine destructive Tools
in der Registry. Sobald das erste kommt, greift dieser Plan.

Kernentscheidungen:

  - Scope: per-SPACE, nicht per-User. Passt zum Space-scoped-data
    model; ein Admin opt-in'd, alle Members des Spaces profitieren.
  - Authority: Server-authoritative. Nicht in Dexie. JWT-gated
    PUT, Role-Check (nur owner/admin dürfen ändern), RLS.
  - Storage: eigene Tabelle mana_spaces.space_policy_preferences
    plus append-only space_policy_audit für Diff-Tracking. NICHT
    JSON-Column auf spaces — typisiert, indexierbar, mehr Raum für
    spätere per-Space-Rate-Limits etc.
  - Fail-closed: wenn mana-mcp apps-api nicht erreicht, wird
    destructive geblockt. 30s TTL-Cache, kurz genug für Revoke-Speed.
  - Acknowledgement enforced at API: PUT verlangt acknowledged:true
    wenn neue Tools zur Liste. Anti-Click-Through by construction.

Inkludiert:
  - Schema + RLS (Postgres)
  - GET/PUT/audit-GET + interner service-key-GET
  - SpacePolicyClient-Pattern in mana-mcp (wie MasterKeyClient)
  - UI /s/:space/settings/ai-policy mit Audit-Section
  - Metriken-Erweiterung (policy-changes counter)
  - Rollout-Reihenfolge + Tests + offene Fragen

Bau-Trigger: erster PR der ein Tool mit policyHint:'destructive'
in die Registry bringt.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 18:39:56 +02:00
Till JS
4fc9d6c59c feat(wardrobe): module foundation — garments + outfits space-scoped data layer (M1)
M1 of docs/plans/wardrobe-module.md — pure data layer + backend plumbing,
zero UI (that's M2). A user can now hold a digital wardrobe per space:
brand merch, club Trikots, family Kleiderschrank, team Kostüme, practice
Dresscode, and personal closet all live as separate pools under the same
Dexie tables, space-scoped like tags/scenes/agents after Phase 2c.

Data model — two tables, no join:

- wardrobeGarments (Dexie v41): single clothing items / accessories.
  Indexed on `category` + `createdAt` + `isArchived`. Encrypted:
  name/brand/color/size/material/tags/notes. Plaintext: category,
  mediaIds, counters, timestamps — all indexed or structural.
  `mediaIds[0]` is the primary photo used for try-on; additional
  ids are alternate views (back, detail) for M7.

- wardrobeOutfits (Dexie v41): named compositions referencing
  garment ids. Encrypted: name/description/tags. Plaintext:
  garmentIds (FK array), occasion (closed enum — useful for
  undecrypted filtering), season, booleans, lastTryOn snapshot.

- picture.images gains `wardrobeOutfitId?: string | null` as a
  plaintext back-reference. Try-on results land in the Picture
  gallery like any other generation; the outfit detail view
  queries them via this id rather than maintaining a third table.

Space scope:

- `wardrobe` added to all five explicit allowlists in shared-types/
  spaces.ts (personal is wildcard, no edit needed). Each space type
  gets a one-line comment explaining the real-world use case.
- App registry: `wardrobe` entry in shared-branding/mana-apps.ts
  with a rose→fuchsia gradient icon (T-shirt on hanger silhouette),
  color #e11d48, tier 'beta', status 'beta'.
- Module registry: wardrobeModuleConfig imported + appended to
  MODULE_CONFIGS so SYNC_APP_MAP picks it up automatically.

Backend:

- MAX_REFERENCE_IMAGES bumped 4 → 8 in picture/generate-with-
  reference (plus the client-side default in ReferenceImagePicker).
  Justified with a comment: face + body + top + bottom + shoes +
  outerwear + 2 accessories = 8. Cost doesn't scale with ref count
  (OpenAI bills per output), so the bump is a pure capability
  expansion with no credit-side risk.
- New POST /api/v1/wardrobe/garments/upload wraps uploadImageToMedia
  with app='wardrobe'. Registered under /api/v1/wardrobe in index.ts.
  Pattern 1:1 with the profile/me-images/upload endpoint; tier-gating
  falls out of wardrobe NOT being in RESOURCE_MODULES (tier='guest'
  works — consistent with picture's plain CRUD).

Stores emit domain events (WardrobeGarmentAdded, WardrobeOutfitCreated,
WardrobeOutfitTryOn, etc.) so later mana-ai missions can observe
activity without polling.

No UI in this commit. M2 (Garments-Grundlayer) wires the route + grid
+ upload-zone; M3 the Outfit composer; M4 the Try-On integration.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 18:27:37 +02:00
Till JS
2769241de3 docs(plans): agent-loop plan reflects M1 shipped + M2 core shipped
Update the plan doc to match reality:

  - Title + intro: "M1 + M2 (core)" instead of just M1.
  - Exit criteria: mark the two achievable ones DONE with commit
    refs; flag POLICY_MODE=enforce soak as ops-blocked; correct the
    parallel-read-speedup criterion that was misformulated (mana-ai
    SERVER_TOOLS are all propose-policy, so parallelisation
    actually kicks in on the webapp side, covered by 54a12ffd5).
  - New M2 section: 5-row status table (M2.1-M2.4 + bonus shipped;
    M2.5 Haiku-tier pending).
  - M2 config table (MANA_AI_COMPACT_MAX_CTX).
  - M2 metrics listed (compactions_triggered_total, compacted_turns).
  - Open polish items: allowDestructive still hardcoded to [].

No code changes. Future sessions reading the plan now see the
actual shipped surface instead of a stale M1-only snapshot.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 18:12:58 +02:00
Till JS
cb9a9bb42e refactor(profile,tool-registry): flip meImages from user-scoped to space-scoped (v40)
Flips `meImages` out of USER_LEVEL_TABLES so it lives under the same
tenancy model as every other data table (tags, scenes, tasks, …).
Precursor to the Wardrobe module, which is space-scoped across all
six space types — leaving meImages user-global would leave an
inconsistency where the Wardrobe catalog is per-space but its
reference input is cross-space, plus a latent privacy leak in shared
spaces (agents in a brand-space would see the owner's entire pool).

Plan: docs/plans/me-images-space-scope-migration.md.

Key decisions:

- Strict scope, no cross-space fallback. Switching into a brand-space
  with no uploaded face shows an empty state and links back to
  /profile/me-images; it does not quietly reach into the personal-
  space pool. Keeps the mental model clean.
- auth.users.image remains pinned to personal-space primary-avatar.
  Only a primary change inside personal space triggers the Better
  Auth sync; brand/club/family/team/practice primaries stay local.
- Single Dexie v40 upgrade: stamps `spaceId=_personal:<uid>`
  sentinel, `authorId=<uid>`, `visibility='space'` on every existing
  row and drops the legacy `userId` column. Dexie upgrades block app
  startup, so by the time the new code's scopedForModule reads run,
  every row is already space-stamped. reconcileSentinels() on the
  next active-space bootstrap rewrites `_personal:<uid>` to the real
  personal-space id, same path v28 used.
- Legacy-avatar migration (M2.5) now pins its row to
  `_personal:<uid>` explicitly — the legacy avatar is the user's
  global SSO identity and belongs in the personal space even if the
  migration happens to fire while the user is in a brand space.

Code changes:

- types.ts: LocalMeImage gains spaceId/authorId/visibility (all
  optional — stamped by hook). Public MeImage exposes spaceId for
  queries that want to branch on space type.
- database.ts: meImages out of USER_LEVEL_TABLES; new v40 upgrade
  block that stamps sentinels + drops userId in one pass.
- queries.ts: all four hooks (useAllMeImages, useMeImagesByKind,
  useReferenceImages, useImageByPrimary) read via scopedForModule.
  Scope-switch triggers automatic re-render via the existing
  scopedTable filter path.
- stores/me-images.svelte.ts: setPrimaryInTx uses scopedForModule so
  a setPrimary in Brand-space never clears Personal-space's holder.
  syncAvatarToAuth gates on activeSpace.type==='personal' so non-
  personal primary changes don't leak into Better Auth.
  createMeImage accepts optional spaceId override — the legacy-
  avatar migration uses it, regular uploads let the hook stamp the
  active space.
- migration/legacy-avatar.ts: explicitly passes
  spaceId=_personal:<uid> to pin the legacy row into personal space.
- MeImagesView.svelte: subtle badge in the intro card shows the
  active space ("Persönlich" for personal, space name otherwise) so
  users notice when the pool changes on space switch.
- packages/mana-tool-registry/src/modules/me.ts: me.listReferenceImages
  filters pulled rows by row.spaceId === ctx.spaceId. mana-sync
  returns all spaces the user belongs to; the tool only wants the
  active space's subset.

No schema/index change on meImages (non-indexed fields, pool size
small enough for in-memory scopedTable filter). If perf matters
later, adding [spaceId+kind] is a 5-minute follow-up.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 18:09:57 +02:00
Till JS
3eca5ac201 feat(website): M6 — subdomain publish + custom-domain foundation
SvelteKit hook + new DB table + founder-gated API + UI section. Ships
the code path for public-site routing on {slug}.mana.how and custom
hostnames. Cloudflare SaaS Hostnames integration is stubbed — see
plan §M6 "Offene Enden".

apps/api/src/modules/website:
- schema.ts: new `customDomains` table. Fields: id, site_id, hostname
  (unique), status (pending | verifying | verified | failed),
  verification_token, dns_target, verified_at.
- drizzle/website/0002_custom_domains.sql: manual migration with
  partial unique index on (hostname) WHERE status='verified'.
- domains.ts (new, authenticated + founder-gated via
  `requireTier('founder')`): POST/GET/DELETE /sites/:id/domains,
  POST /sites/:id/domains/:domainId/verify. Verify runs CNAME + TXT
  checks via node:dns/promises with an apex-domain A-record fallback.
  Reserved-hostname list prevents users from binding mana.how subdomains.
- public-routes.ts: new GET /public/resolve-host?host= — unauthenticated
  resolver used by hooks.server.ts. Returns { slug, siteId } only for
  verified bindings tied to a currently-published site.

apps/mana/apps/web/src/hooks.server.ts:
- After the existing https/app-subdomain guards, a new
  `resolveWebsiteRewrite()` step rewrites `event.url.pathname`:
    {slug}.mana.how/path → /s/{slug}/path     (pure string)
    custom-host.com/path → /s/{resolved}/path (API call, 60s LRU)
- Browser URL stays on the custom host — this is a server-side rewrite,
  not a 302. APP_SUBDOMAINS + RESERVED_WEBSITE_SUBDOMAINS win over
  website routing. Localhost and apex mana.how are skipped.

apps/mana/apps/web/src/lib/modules/website:
- domains.ts (new): typed client for list/add/verify/remove. Handles
  200 + expected 400 (verification-failed) separately.
- components/DomainsSection.svelte: add-input, per-domain status pill,
  DNS-instructions box (CNAME + TXT with copy-to-clipboard), Verify
  button. Mounted inside SiteSettingsDialog as its own section — the
  existing theme/footer controls stay put.

docs/plans/website-builder.md:
- M6 checklist updated with what shipped vs. ops-gap (CF SaaS).
- `mana-landing-builder` consolidation: DECIDED to keep parallel. Four
  reasons in the plan. Revisit-criterion stated.
- Shipping log table seeded with M1→M6 commits.

Validation:
- pnpm run validate:all: 6/6 gates green
- pnpm run check (web): 0 errors, 0 warnings
- apps/api type-check: green

Apply schema with:
  psql "$DATABASE_URL" -f apps/api/drizzle/website/0002_custom_domains.sql

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 15:29:42 +02:00
Till JS
aab1e3045b docs(plans): wardrobe is space-scoped, allow in all six space types
Revises the wardrobe plan's space-scope decision from "only personal"
to the full matrix — brand has merch, clubs have Trikots, families
have shared kids' wardrobes, teams have costumes/uniforms, practices
have Dresscode items. All six space types get wardrobe in the
allowlist; garments + outfits are stamped with spaceId/authorId/
visibility like tags/scenes/agents (post Phase 2c).

Adds a sixth decision block (Space-scoped catalog, user-scoped
Try-On subject): the catalog lives in its space, but Try-On
references are always the *calling user's* meImages — one human,
one identity, brought into every space. A brand team member trying
on merch sees themselves wearing it; a club member trying on a
Trikot sees themselves wearing it; natural and correct.

The single edge case is family spaces where a parent might want
"try on kid's shirt" — the plan punts that explicitly. The
catalog side (adding items, composing outfits) works unrestricted;
Try-On shows a hint that it renders the calling user. If real
demand shows up later, a separate plan can introduce per-space
subject references (spaceMembers[].faceMediaId or similar) —
today not speculating.

Membership gating falls out of the existing scopedForModule/
mana-sync-RLS stack; no extra code in wardrobe.

M1 checklist updated: wardrobe is NOT in USER_LEVEL_TABLES, queries
go through scopedForModule, allowlist entry covers all six types.
M4 checklist gains the "in non-personal spaces show the subject
hint" item.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 15:23:10 +02:00
Till JS
638f9c34d6 docs(plans): me-images M1-M5 status + new wardrobe plan
Two plan updates as a set:

- me-images-and-reference-generation.md: rewrites the "Status" block
  to reflect what actually shipped (M1 89258eb45, M2 a64a7e39c, M2.5
  e2b5ac38c, M3 in 38dc80654, M4 in d087b4744, M5 fc635f983) and
  adds an "Offen" section listing the small follow-ups that didn't
  make the M1-M5 cut — global aiUsesReferenceImages kill-switch,
  kind-editor on existing tiles, reference-display in picture
  detail view, legacy-avatar re-upload hint — plus the three
  optional later tracks (M6 local FLUX+PuLID, M7 inpainting masks,
  M8 zero-knowledge blobs). Milestones checklist is now
  -annotated per shipped item with actual decisions (Dexie v38
  instead of v27, no me-storage bucket after all, generation_log
  deferred, etc.).

- wardrobe-module.md: new plan. Data layer sketch (two tables:
  wardrobeGarments + wardrobeOutfits, reuses me-images + picture
  as dependencies), UI breakdown (/wardrobe, /wardrobe/compose,
  garment + outfit detail routes), Try-On as a thin wrapper over
  the M3 endpoint (with the cap bumped from 4 → 8 references, so
  face + body + up-to-6 garments fits one call), four MCP tools
  in a new wardrobe.ts module, and two optional later tracks
  (Persona Stil-Coach template, context-driven outfit suggestion
  mission). The explicit non-goals block keeps the scope tight:
  no product DB, no replacement for inventory, no shopping, no
  style-coaching that feels judgmental.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 15:08:45 +02:00
Till JS
79d112657c feat(personas): M5.a — Playwright visual suite scaffold
Smallest possible foundation for the persona-driven visual regression
suite (M5 in docs/plans/mana-mcp-and-personas.md). One flow, two
viewports, one persona — enough to prove the stack end-to-end:
seed-script → mana-auth → API login → cookie injection → web app →
screenshot → disk. Extending is copy-paste per flow.

tests/personas/
  playwright.config.ts
    Own config separate from the root tests/e2e/ suite. Two viewports
    (1440×900 desktop Chrome + Pixel 5 mobile) — more can be added
    once baselines settle without quadrupling the review load.
    Diff threshold 0.2 %, animations disabled, snapshots land under
    __snapshots__/{spec}/{arg}-{project}.png. No auto-webServer —
    the whole point is to catch regressions against the real stack
    the user runs, not a hermetic one; if the stack is down, tests
    fail loud.

  fixtures/persona-auth.ts
    Typed Playwright `test.extend` with a `personaKey` worker option
    and a `personaPage` fixture that returns a pre-logged-in Page
    pointed at `/`. Login is API-side: POST /api/v1/auth/login with
    the deterministic HMAC-SHA256 password, parse Set-Cookie headers,
    inject into the browser context. Derivation is a bit-identical
    mirror of scripts/personas/password.ts and
    services/mana-persona-runner/src/password.ts — a 3-way contract.
    Changing one without the others locks the suite out of every
    persona. PERSONAS map exports all 10 catalog emails for typed
    access.

  flows/home.spec.ts
    One smoke flow. Asserts the persona isn't redirected to /login,
    hides any [data-testid="live-time"] so clock widgets don't
    invalidate diffs, captures a full-page screenshot. When this
    goes green, the whole pipeline is plumbed. Copy this file to
    add per-module tours.

  package.json
    @mana/tests-personas workspace. Scripts: `test`, `test:update`,
    `report` (HTML diff viewer).

  README.md
    Prerequisites (stack up + seeded + ideally persona-runner ticked
    once), run recipe, env vars, architecture diagram, extension
    pattern.

root package.json: `pnpm test:personas` + `:update`.
.gitignore: playwright-report-personas/ + test-results/ so generated
artefacts never get committed.

Type-check / list: `playwright test --list` succeeds, 2 tests (one
per viewport) registered for home.spec.ts.

Not attempted in this commit (user action to run the stack):
- Actual baseline capture (needs docker up + db:push + seed:personas
  + ANTHROPIC_API_KEY + diag/tick).
- Additional flows (todo, journal, notes, habits, calendar). They're
  copy-paste per README. Land when the stack is smoked.
- Nightly CI job. Will land once baselines are stable.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 14:33:06 +02:00
Till JS
f07eae3c01 feat(personas): M3.b-d — tick loop + Claude Agent SDK + persistence (real)
Previous commit 38dc80654 carries this M3 title but its payload is an
unrelated apps/api/picture change — shared-.git-index race with a
parallel session (see feedback_git_workflow.md). This commit holds the
actual M3.b/c/d code. Leaving the misnamed commit for the user to
re-attribute / revert as they prefer.

Closes the M3 loop from docs/plans/mana-mcp-and-personas.md. The
runner picks up due personas, drives each through Claude + MCP for
one simulated turn, collects actions + ratings, persists through
service-key internal endpoints in mana-auth.

Internal endpoints (mana-auth, service-key-gated)

- GET  /api/v1/internal/personas/due
    Returns personas whose tickCadence + lastActiveAt say they're
    due. Rules: hourly > 1h, daily > 24h, weekdays > 24h mon-fri.
    NULLS FIRST so never-run personas go ahead of stale ones.

- POST /api/v1/internal/personas/:id/actions
    Batch ≤ 500. Row ids are deterministic
    `${tickId}-${i}-${toolName}` + ON CONFLICT DO NOTHING so the
    runner can retry a tick without doubling audit rows. Also
    bumps personas.last_active_at so the next /due call sees it.

- POST /api/v1/internal/personas/:id/feedback
    Batch ≤ 100. Row id is `${tickId}-${module}` — natural key is
    one rating per module per tick.

Runner tick pipeline (services/mana-persona-runner/src/runner/)

- claude-session.ts
    Two phases per tick. runMainTurn feeds the persona's system
    prompt + a German "simulate a day" user prompt to Claude Agent
    SDK's query(), with mana-mcp wired in as a streamable-HTTP MCP
    server. We iterate the returned AsyncGenerator and extract
    tool_use blocks into ActionRows; a tool_result with
    is_error=true flips the most recent action. runRatingTurn is a
    fresh query() with tools:[] asking Claude in character to rate
    each used module 1-5 as strict JSON. We parse with tolerance
    for whitespace / fences. Unparseable output becomes a synthetic
    '__parse' feedback row so operators see the failure.

- tick.ts
    Orchestrator. Skips when config.paused. Fetches /due, processes
    in batches of config.concurrency via Promise.allSettled so a
    single persona failure never kills the batch. Returns
    {due, ranSuccessfully, failed[], durationMs}.

- types.ts
    ActionRow + FeedbackRow shapes shared between claude-session
    and the internal client.

Runner bootstrap (src/index.ts)

- setInterval(config.tickIntervalMs) starts the tick loop on boot.
  tickInFlight guards against overlap when Claude latency >
  interval. If MANA_SERVICE_KEY or ANTHROPIC_API_KEY is missing,
  loop is disabled with a warn line — /health + /diag/login still
  work.
- POST /diag/tick (dev-only) fires one tick on demand, returns
  the result. Avoids waiting a full interval during testing.
- Graceful SIGTERM/SIGINT shutdown clears the interval.

Client

- clients/mana-auth-internal.ts
    X-Service-Key client for the three endpoints above.
    Constructor throws on empty serviceKey — fail loud.

Boot smoke verified: /health returns ok, /diag/tick 500s with
descriptive messages when keys absent. Warning lines on boot when
keys are missing. Type-check green across mana-auth, tool-registry,
mcp, persona-runner.

M3 exit gate is the end-to-end smoke recipe (docker up → db:push →
seed:personas → diag/tick → psql) documented in
services/mana-persona-runner/CLAUDE.md.

M2.d (cross-space family/team memberships) still deferred.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 14:18:31 +02:00
Till JS
54a12ffd5c feat(webapp): wire isParallelSafe in Companion chat + Mission runner
Enables the M1 parallel-reads optimisation on the webapp side. Both
consumers of runPlannerLoop pass an isParallelSafe predicate derived
from the tool catalog:

  isParallelSafe: (name) =>
    AI_TOOL_CATALOG_BY_NAME.get(name)?.defaultPolicy === 'auto'

Auto-policy tools (list_tasks, get_habits, nutrition_summary, …) run
via Promise.all in batches of 10 when the LLM fans them out in one
round. Propose-policy tools — which surface to the user as Proposal
cards — stay sequential so intent ordering in the inbox is preserved
and pre-execute guardrails can reason about prior-step state.

Tests: 31 existing companion + mission tests pass unchanged; the
parallel path is exercised via the new loop.test.ts cases shipped
with the M1 commit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 14:11:24 +02:00
Till JS
a64a7e39cf feat(profile): UI for me-images management at /profile/me-images (M2)
M2 of docs/plans/me-images-and-reference-generation.md — the Settings
surface that sits on top of the M1 data layer. Users can now upload
a Face and a Fullbody reference into two primary slots, toss extra
references into a grid, and toggle each image's "KI darf nutzen" flag
individually.

Route placement: /profile/me-images (not /settings/me-images as the
plan originally proposed). The repo convention is per-module subroutes
(/todo/settings, /invoices/settings, …) — there is no global /settings
namespace to hang this off. Plan doc updated accordingly.

- MeImageUploadZone: drag-and-drop + file-picker, pattern from
  picture/ListView but refactored into a reusable component. Fires
  onFiles(File[]) so the parent decides kind + slot.
- MeImageSlotCard: large card for Face / Fullbody primary slots.
  When filled it shows the portrait + the image's AI-toggle + delete
  + a compact "Neues Bild setzen" replacement zone. When empty it
  collapses into a large drop-zone.
- MeImageTile: grid tile for everything that isn't currently holding
  a primary slot — thumbnail, kind badge, Robot-AI-toggle, Star
  primary-promotion (only enabled for kinds that map to a slot),
  Trash delete.
- MeImagesView: orchestrates queries (useImageByPrimary for each
  slot + useAllMeImages for the rest), upload flow (readDimensions →
  uploadMeImageFile → store.createMeImage → optional setPrimary in
  the same tick), and the three write actions (toggleAi, togglePrimary,
  delete). Dropping a file on a slot drop-zone both uploads and claims
  the slot, so the old holder automatically falls into the grid.
- Client: profile/api/me-images.ts wraps the M1 endpoint with
  authStore.getValidToken() → Bearer header and a small
  readImageDimensions helper that exposes natural width/height
  synchronously (mana-media reports them later but we want them for
  the Dexie row's first write).
- Discoverability: profile ListView "Konto" tab gains a "Meine Bilder"
  action button that navigates to the new route with a one-line hint.

Still open (later commits): the hard-migration that rewrites
auth.users.image → meImages(primaryFor='avatar'), the global
aiUsesReferenceImages kill-switch (lives on profile singleton), and
the Picture-generator's Reference picker (M4, rides on top of M3's
backend endpoint).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 14:01:40 +02:00
Till JS
e5d230e599 feat(agent-loop): M1 — policy gate + reminder channel + parallel reads
Three Claude-Code-inspired primitives for runPlannerLoop, derived from the
reverse-engineering reports in docs/reports/:

1. **Policy gate** (@mana/tool-registry) — evaluatePolicy() gates every tool
   dispatch: denies admin-scope, denies destructive tools not in the user's
   opt-in list, rate-limits per tool (30/60s default), flags prompt-injection
   markers in freetext without blocking. Wired into mana-mcp with a
   per-user rolling invocation log and POLICY_MODE env (off|log-only|enforce,
   default log-only). mana-ai uses detectInjectionMarker only — tool dispatch
   there is plan-only, so rate-limit/destructive checks don't apply yet.

2. **Reminder channel** (packages/shared-ai/src/planner/loop.ts) — new
   reminderChannel callback in PlannerLoopInput. Called once per round with
   LoopState snapshot (round, toolCallCount, usage, lastCall); returned
   strings wrap in <reminder> tags and inject as transient system messages
   into THIS LLM request only. Never pushed to messages[] — the Claude-Code
   <system-reminder> pattern that keeps the KV-cache prefix stable.

3. **Parallel reads** (loop.ts) — isParallelSafe predicate enables
   Promise.all dispatch when every tool_call in a round is parallel-safe,
   in batches of PARALLEL_TOOL_BATCH_SIZE=10. Any non-safe call downgrades
   the whole round to sequential. messages[] always appends in source
   order, never completion order, so the debug log stays linear.
   Default-off (undefined predicate) preserves pre-M1 behaviour.

Tests: 21 new in tool-registry (policy), 9 new in shared-ai (5 parallel,
4 reminder). All 74 green, type-check clean across 4 packages.

Design/plan: docs/plans/agent-loop-improvements-m1.md
Reports: docs/reports/claude-code-architecture.md,
         docs/reports/mana-agent-improvements-from-claude-code.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 13:56:40 +02:00
Till JS
493db0c3b2 feat(personas): M2.a-c — persona schemas + admin endpoints + seed pipeline
Continuation of docs/plans/mana-mcp-and-personas.md. Personas are the
auto-test users the M3 runner will drive — they're real Mana users
(kind='persona', tier='founder'), registered through the same Better
Auth pipeline as humans, just stamped differently and metadata-tracked
so the persona-runner knows how to role-play them.

Schemas (auth namespace — personas are 1:1 with users, no reason for a
separate platform.* schema that the plan originally sketched)

- userKindEnum ('human' | 'persona' | 'system') + users.kind column,
  wired into better-auth additionalFields so the JWT/user object carry
  the flag. Default 'human' keeps every existing user untouched.
- auth.personas — 1:1 descriptor (archetype, systemPrompt, moduleMix
  jsonb, tickCadence, lastActiveAt). CASCADE from users.id.
- auth.persona_actions — tick-grouped audit of every tool call the
  runner makes (toolName, inputHash for dedup, result, latency).
- auth.persona_feedback — structured 1-5 ratings per module per tick,
  plus free-text notes. This is where the runner writes the
  self-reflection step at end of each tick.

Admin endpoints (/api/v1/admin/personas, admin-tier-gated)

- POST /            create-or-update by email. Uses auth.api.signUpEmail
                    if the user's new, then stamps kind+tier+verified
                    and upserts the personas row. Idempotent — safe to
                    re-run after catalog edits.
- GET  /            list with 7-day action count per persona.
- GET  /:id         detail + recent 20 actions + per-module feedback
                    aggregate.
- DELETE /:id       hard delete. Refuses non-persona users as
                    defense-in-depth: an admin typo here would cascade
                    through the full user-delete chain.

Catalog + seed pipeline (scripts/personas/)

- catalog.json      10 handwritten personas spanning 7 archetypes
                    (adhd-student, ceo-busy, creative-parent, solo-dev,
                    researcher, freelancer, overwhelmed-newbie).
                    Five pairs of personas that will later share
                    family/team spaces (cross-space setup is deferred
                    to M2.d per the plan).
- catalog.ts        zod-validated loader. Refines email to require
                    @mana.test TLD — non-existent, no bounce risk.
- password.ts       deterministic HMAC-SHA256(PERSONA_SEED_SECRET,
                    email). No stored per-persona credentials; the
                    runner re-derives on every login. Refuses the
                    dev-fallback secret in production.
- seed.ts           POST /admin/personas per catalog entry. Flags:
                    --auth=, --jwt=, --dry-run.
- cleanup.ts        Hard-delete every live persona. Warns when the
                    live set drifts from the catalog.

Root package.json:
  pnpm seed:personas
  pnpm seed:personas:cleanup

Extends the ESLint root-ignore list with `scripts/**` so Bun-typed
utility scripts don't fail the typed-parser check they weren't opted
into. Consistent with the rest of scripts/ being .mjs+.sh.

To go live (user action):
  pnpm docker:up
  cd services/mana-auth && bun run db:push
  export MANA_ADMIN_JWT=...
  pnpm seed:personas

M2.d deferred: cross-space (family/team/practice) memberships between
persona pairs. Better Auth's org-invite flow is multi-step and would
roughly double the M2 scope; the persona-runner (M3) can operate in
personal spaces first, shared-space tests land as their own milestone.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 13:55:14 +02:00
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
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
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
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
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
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
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
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
d83fc370a0 docs: update tool coverage table + server-side research + templates
Catches up all docs with the current state of the AI tool system.

services/mana-ai/CLAUDE.md:
- New v0.6 status section documenting NewsResearchClient,
  pre-planning research injection, config.manaApiUrl, and the full
  28-tool / 11-module inventory (17 propose + 11 auto).

apps/mana/CLAUDE.md:
- New "Tool Coverage" table in the AI Workbench section listing all
  tools per module with their policy (propose vs auto).
- New "Templates" subsection documenting the two-section gallery
  (agent vs workbench templates), the seed-handler registry, and
  the current handlers (meditate, habits, goals).
- Architecture cross-reference updated to include §23.

docs/architecture/COMPANION_BRAIN_ARCHITECTURE.md:
- §23.2 gains a "Server-Side Research (mana-ai, ab v0.6)" subsection
  explaining how NewsResearchClient mirrors the client-side research
  pre-step: same endpoints, same trigger regex, but HTTP-direct from
  the Docker network instead of SvelteKit-internal.

docs/plans/README.md:
- workbench-templates.md added to the roadmap table (T1 shipped).
- Multi-agent description updated to mention 28 tools + server-side
  web-research.
- Architecture cross-reference includes §23.

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