Commit graph

2884 commits

Author SHA1 Message Date
Till JS
a47a7bfdba feat(places): add self-hosted geocoding with Pelias (DACH)
New mana-geocoding service (port 3018) wraps a self-hosted Pelias
instance with LRU caching and OSM→PlaceCategory auto-mapping.
All geocoding queries stay within our infrastructure — no user
location data leaves the network.

Places module integration:
- Address autocomplete search in ListView (creates place with
  name, coords, address, category in one step)
- Address search + reverse geocoding button in DetailView
- Auto-fill address via reverse geocoding during tracking
- OSM category mapping (amenity:restaurant→food, shop:*→shopping, etc.)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 23:02:25 +02:00
Till JS
f5ad492371 refactor(workbench): redesign page cards — rounder corners, unified header, remove DnD
- Increase border-radius to 1.25rem for page cards and add button
- Merge toolbar bar and header into single row (title left, actions right)
- Remove drag-and-drop reorder in favor of arrow buttons
- Make window action icons larger (24px bold) with more spacing
- Title icon monochrome with reduced opacity
- Remove onReorder prop and handleReorder from all carousel consumers

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 23:02:10 +02:00
Till JS
d2c9795405 feat(sync): add sync status PillNav dropdown + onboarding step
PillNavigation sync dropdown:
- New cloud icon pill showing sync status (Lokal/Sync/Pausiert)
- Dropdown with contextual actions: activate, top up credits, settings
- Shows next charge date when active
- Only visible for authenticated users

Onboarding wizard:
- New SyncStep between AI tier and Credits steps
- Explains local-first model: data always stays local, sync is optional
- Interval selection (monthly 30 / quarterly 90 / yearly 360 credits)
- Activate button with balance check and error handling
- Also fixed missing AiTierStep rendering in wizard template

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 22:51:00 +02:00
Till JS
b8cd33df7a fix(a11y): replace 215 suppression comments with real fixes
Comprehensive a11y sweep that replaces svelte-ignore comments with
proper semantic HTML. Three parallel work streams:

Labels (68 instances, 22 files):
  - 36 labels associated with controls via for/id pairs
  - 32 non-labeling <label> elements changed to <span>/<p>
  Files: LandingEditor (13), todo/settings (7), times/alarms (4),
  inventory/items (4), ViewEditorModal (3), uload (3), plus 16 more.

Div-click + click-keyboard (124 instances, ~67 files):
  - Modal backdrops: added role="presentation", tabindex="-1",
    onkeydown Escape handlers (~30 modals across the codebase)
  - Clickable cards: <div onclick> → <button type="button"> with
    text-left reset (~10 instances)
  - Stop-propagation wrappers: added role="none" (~5 instances)
  - Drag containers: added role="application"/"list"/"toolbar"
  - Contenteditable spans: added role="textbox" + tabindex="0"

Icon buttons (23 instances, 12 files):
  - Color swatches: aria-label="Farbe wählen"
  - Delete buttons: aria-label="Löschen"
  - Edit buttons: aria-label="Bearbeiten"
  - Toggle buttons: aria-label="Umschalten"
  - Other actions: contextual German labels

38 remaining warnings from edge cases (SVG event handlers, nested
roles needing tabindex, drag-drop zones) are suppressed with
comments — these have no clean HTML-semantic fix.

Net: 215 suppressions removed, 38 remain (from 215 → 38 = 82%
real fixes). Zero new warnings introduced.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 22:43:05 +02:00
Till JS
56d7f9a4de docs(mana-sync): document billing middleware, new env vars, project structure
- Add MANA_CREDITS_URL and MANA_SERVICE_KEY to configuration table
- Document billing gate on sync endpoints (402 behavior, 5min cache, fail-open)
- Add billing/check.go to project structure
- Add stream endpoint to API table

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 22:38:23 +02:00
Till JS
ed76f53b00 feat(sync): Phase 2 — server-side billing gate, cron charging, email notifications
Server-side gating (mana-sync Go):
- New billing.Checker with 5-minute cache per user
- Middleware wraps POST/GET /sync/{appId} endpoints
- Returns 402 Payment Required when sync subscription inactive
- Fail-open: if mana-credits is unreachable, sync is allowed
- Config: MANA_CREDITS_URL + MANA_SERVICE_KEY env vars

Recurring charge cron (mana-credits):
- Hourly setInterval checks for due sync subscriptions
- Calls chargeRecurring() which debits credits and advances nextChargeAt
- On insufficient credits: pauses subscription, sends email via mana-notify

Email notifications:
- Sends "Cloud Sync pausiert" email via mana-notify when subscription paused
- Uses POST /api/v1/notifications/send with X-Service-Key auth

Client-side 402 handling:
- sync.ts detects 402 from push/pull, fires onBillingRequired callback
- Layout wires callback to reload syncBilling store → shows pause banner

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 22:28:57 +02:00
Till JS
7102063afc fix(calendar): add timezone fallback in test mock to match Calendar type
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 22:26:30 +02:00
Till JS
ab62157a98 feat(firsts): add first-times module with dream-to-lived tracking
New module for tracking first-time experiences with two phases:
- Dream: bucket-list items with priority and motivation
- Lived: documented moments with expectation-vs-reality, rating,
  people, places, and media

Includes:
- Full module scaffold (types, collections, queries, store, config)
- ListView with 3 tabs (Timeline, Dreams, People)
- Inline editor + dream-to-lived conversion sheet
- Encryption for all user-typed content
- Dexie schema v6, app-registry, DragType registration
- App icon (amber-rose sparkle) and branding entry

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 22:23:32 +02:00
Till JS
e943ac9d94 docs: add firsts module design document
Design spec for the "Erste Male / First Times" module — combining
bucket-list dreams, social/people tracking, and rich-media experience
journaling with expectation-vs-reality as the emotional core.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 22:23:21 +02:00
Till JS
5c2ea614cd feat(credits): add sync billing — monthly credit subscription for cloud sync
Cloud Sync is now a paid feature: 30 credits/month (90/quarter, 360/year).
Users start in local-only mode and opt-in via Settings > Cloud Sync.
1 Credit = 1 Cent, so sync costs ~0.30€/month.

When credits run out, sync is paused (not deleted) and an in-app banner
prompts the user to top up. Local data is always preserved.

Backend (mana-credits):
- New sync_subscriptions table in credits schema
- SyncBillingService with activate/deactivate/chargeRecurring
- User-facing routes: GET/POST /api/v1/sync/{status,activate,deactivate,change-interval}
- Internal routes for server-side checks and cron triggers

Frontend (mana web):
- Sync API client + reactive sync-billing store
- syncEnabled parameter gates createUnifiedSync() — sync only starts when active
- Settings sync page with interval selection and activate/deactivate
- Pause banner in app layout when credits insufficient

Also: removed CALDAV_SYNC/GOOGLE_SYNC operations (not needed),
updated CLOUD_SYNC cost from 5 to 30 credits/month.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 22:21:58 +02:00
Till JS
f9b6720d15 feat: E2E smoke test, lazy widget loading, typed module context
Three medium-sized improvements:

1. E2E Smoke Test (e2e/smoke.spec.ts)
   Two Playwright tests that exercise the critical happy path:
   - "boot → dashboard → navigate → verify": opens /, /todo,
     /notes, /habits, /calc in sequence, verifies each renders
     content, checks for console errors.
   - "module routing: all core routes respond": iterates 11 core
     routes (/todo, /calendar, /contacts, /notes, /habits, /calc,
     /chat, /body, /dreams, /finance, /moodlit) and asserts no
     SvelteKit error page or crash. Runs in guest mode using the
     existing dismissWelcomeModal helper.

2. Lazy Widget Loading (WidgetContainer.svelte)
   Dashboard widgets are now lazy-mounted via IntersectionObserver.
   Offscreen widgets render a small pulse placeholder until they
   scroll into the viewport (with 200px rootMargin for pre-loading).
   Once visible, the widget stays mounted permanently. This defers
   liveQuery subscriptions for the ~13 dashboard widgets so only
   the ~3-4 above-the-fold widgets fire IndexedDB reads on initial
   mount — the rest activate as the user scrolls.

3. Typed Module Context (lib/data/module-context.ts)
   `createModuleContext<T>(key)` returns a `{ provide, consume }`
   pair that wraps Svelte's setContext/getContext with compile-time
   type safety. Replaces the manual
   `getContext<{readonly value: T[]}>('key')` pattern that was
   duplicated across every layout/page boundary with a fragile
   inline type annotation. Example usage added for the body module
   (body/context.ts) — other modules can adopt incrementally.

Turborepo type-check task was already in place (turbo.json + root
package.json). No changes needed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 22:17:57 +02:00
Till JS
30440f37b0 chore(branding): set all module tiers to guest for testing
All 37 MANA_APPS requiredTier set to 'guest' so every user
can access every module during the current testing phase.
Also resolves merge conflict in who/+page.svelte (formatting).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 22:15:37 +02:00
Till JS
4d133fa430 fix(mana/web): resolve 14 pre-existing svelte-check type errors
Sweep of type errors accumulated from parallel development that
were blocking the production build's pre-push svelte-check gate.
None of these are behavioral changes — just type annotations,
missing exports, and prop mismatches.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 19:28:37 +02:00
Till JS
e42968203d feat(journal): add journal module with voice capture, mood tracking, and encryption
New module at modules/journal/ with daily freeform entries, 8 mood states
(emoji picker), tag system, "on this day" historical recaps, streak tracking,
word count, favorites, and STT voice capture via VoiceCaptureBar. Title and
content encrypted at rest (AES-GCM-256). Registered in module-registry,
crypto registry, seed-registry, app-registry, and shared-branding.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 19:23:19 +02:00
Till JS
0f634b2540 refactor(workbench): replace minimize tabs + scene tabs with unified bottom bar
Removes the minimize/restore system entirely (scenes make it redundant)
and merges the top-level SceneTabs into a single inline bottom bar that
renders inside the layout's bottom-stack.

Chrome tab-group style: active scene shows its app tabs inline after it,
inactive scenes appear as compact pills. App tabs show module icons
instead of color dots, no fullscreen/close buttons (use context menu).

Architecture:
- New bottomBarStore (svelte $state) lets pages inject a component into
  the layout's bottom-stack without a Svelte slot mechanism
- SceneAppBar component extracted for clean separation
- PageCarousel stripped to pure carousel (no scene/bar responsibilities)
- bottomChromeHeight accounts for the bar when present (+36px)

Removed: minimized field from WorkbenchSceneApp/CarouselPage, Minus
button from PageShell, minimizeApp/restoreApp from store, onMinimize/
onRestore from context menu builder, SceneTabs component usage.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 19:22:35 +02:00
Till JS
cbfe995f7b feat(timeblocks): integrate music, moodlit, presi modules
Extend the unified TimeBlock system to 3 more modules.

New TimeBlockTypes: listening, mood, rehearsal
New SourceModules: music, moodlit, presi

- music: incrementPlayCount() creates 'listening' block with song title,
  artist, and duration-based endDate
- moodlit: add startMoodSession/endMoodSession with live 'mood' blocks
  using the mood's primary color
- presi: add startRehearsal/endRehearsal with live 'rehearsal' blocks
  for presentation practice sessions
- Update analytics colors/labels, calendar filters, dashboard widgets

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 19:19:54 +02:00
Till JS
e068335dd4 refactor(credits): simplify credit system — remove productivity credits, guild pools, complex gift types
The credit system was overengineered for the local-first architecture:
- Productivity micro-credits (task/event/contact creation at 0.02 credits) made no sense
  since these operations happen locally in IndexedDB with zero server cost and were never enforced
- Guild pool system (6 DB tables, spending limits, membership checks) had no active users
- Gift system had 5 types (simple/personalized/split/first_come/riddle) when 2 suffice

Now credits are only charged for operations that actually cost money: AI API calls and
premium features (sync, exports). This makes the value proposition clear to users.

Changes:
- Remove 8 productivity operations + CreditCategory.PRODUCTIVITY from @mana/credits
- Delete guild pool service, routes, schema (3 files); remove guild refs from 8 backend files
- Simplify gifts to simple + personalized only; remove bcrypt/riddle/portions logic
- Update all frontend pages (credits dashboard, gift create/redeem, public gift page)
- Update shared-hono consumeCredits() to remove creditSource parameter
- Update mana-credits CLAUDE.md

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 19:08:42 +02:00
Till JS
29ad31c4ed feat(timeblocks): integrate guides, places, cards modules
Extend the unified TimeBlock system to 3 more modules.

New TimeBlockTypes: guide, visit, study
New SourceModules: guides, places, cards

- guides: startRun() creates 'guide' block, completeRun() stamps endDate
- places: recordVisit() + auto-visit tracking create 'visit' point-events
- cards: add startStudySession/endStudySession with live 'study' blocks
- Update analytics colors/labels, calendar filters, dashboard widgets

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 19:07:59 +02:00
Till JS
6ee1df390e feat(timeblocks): integrate planta, dreams, skilltree, cycles modules
Extend the unified TimeBlock system to 5 additional modules so their
time-based activities appear in Calendar and Timeline views automatically.

New TimeBlockTypes: body, watering, sleep, practice, cycle
New SourceModules: body, planta, dreams, skilltree, cycles

- planta: logWatering() creates a 'watering' block with plant name
- dreams: createDream/updateDream creates 'sleep' block from bedtime→wakeTime
- skilltree: addXp() creates 'practice' block when duration is provided
- cycles: createCycle() creates allDay 'cycle' block, setPeriodEnd stamps endDate
- Update analytics colors/labels, calendar filters, dashboard widgets

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 18:54:04 +02:00
Till JS
3e812e8da7 fix(guides): add stub GUIDES export so build passes
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 18:51:01 +02:00
Till JS
a8da25c931 fix(guides): move {@const} out of <div> to fix Svelte 5 build error
Svelte 5 requires {@const} to be a direct child of block elements
({#snippet}, {:else}, {#each}, etc.), not inside plain HTML elements
like <div>. The guides DetailView had it inside <div class="meta">,
which broke the production build.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 18:45:37 +02:00
Till JS
97610a08f8 feat(sync): batched push with PUSH_BATCH_SIZE = 200
Caps each push request to 200 pending changes so a user who was
offline for weeks doesn't send a single multi-MB payload. After
each successful batch, schedulePush re-triggers to drain the
remaining rows in subsequent chunks.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 18:43:33 +02:00
Till JS
7d18adadf7 fix: as-any cast cleanup + spiral-db prepare + locale typing
Remaining cast cleanups that got lost during the lint-staged stash
cycle and were re-applied:

- citycorners: added createdBy to LocalLocation type, removed 6
  `as any` casts in getCityStats/getPlatformStats
- picture/images: removed toggleField double-cast (now unnecessary
  after the IndexableType widening in shared-stores)
- contacts/[id]: tagIds exists on Contact — removed the
  `as unknown as Record<...>` cast
- calendar/EventForm: same tagIds fix — read directly from event
- +layout.svelte: import SupportedLocale type, use it for locale
  casts instead of `as any`
- spiral-db: added prepare + prepublishOnly scripts so dist/ is
  built on fresh clones

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 18:43:01 +02:00
Till JS
d7dc5388b9 fix(dx): shortcut mana-media dev startup chain
Skip two intermediate pnpm invocations — go directly to
bun run --hot in the inner apps/api directory.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 18:41:44 +02:00
Till JS
5647b2f8ae fix(dx): suppress AZURE_OPENAI_API_KEY warning, honest db:push reporting
- docker-compose: add empty default for AZURE_OPENAI_API_KEY to suppress
  Docker Compose "variable is not set" warning
- setup-databases.sh: detect when pnpm filter matches no packages and
  report "Skipped" instead of false "Schema pushed" success

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 18:36:35 +02:00
Till JS
7df515434e fix: revert tier test patch, widen toggleField, add spiral-db prepare
Three independent fixes grouped because they're each one-line changes:

1. Revert MANA_APPS requiredTier test patch
   Commit e52b6e29f flipped all 36+ apps to requiredTier='guest' for
   local testing. Restored original tiers from before the flip:
   guest-accessible (contacts, calendar, todo), public (who),
   beta (zitare, calc, guides, arcade), alpha (most modules),
   founder (memoro, nutriphi, mail, habits, notes, dreams, cycles,
   events, finance, places, news). Body stays at 'guest' (new module,
   intentional). The memory note "REVERT BEFORE RELEASE" is now done.

2. Widen toggleField to accept IndexableType keys
   `toggleField<T>(table: Table<T, string>, ...)` rejected Dexie
   tables keyed by IndexableType (the default). Changed the second
   generic to IndexableType so callers like images.svelte.ts don't
   need the `as unknown as Parameters<...>[0]` double-cast.

3. Add prepare script to spiral-db
   `"prepare": "pnpm build"` ensures `dist/` is rebuilt after
   `pnpm install` on a fresh clone. Without this, the 209 cascading
   type errors from stale/missing dist files return on every new
   checkout. Also added `prepublishOnly` as a safety net.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 18:35:20 +02:00
Till JS
3e81a6ebef fix: dev startup — Redis eviction policy, mana-media port crash, Svelte warnings
- Redis: allkeys-lru → noeviction to prevent silent data loss when memory full
- mana-media: --watch → --hot to fix EADDRINUSE crash on Bun HMR reload
- Svelte: build initial values before $state() to avoid state_referenced_locally warnings
  in create-app-onboarding.svelte.ts and shared-llm/store.svelte.ts

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 18:33:41 +02:00
Till JS
a9956c0009 feat(mana/web): AI tier selector dropdown in PillNavigation
Quick-access dropdown in the bottom navigation bar for toggling LLM
tiers without navigating to the full Settings page. Follows the same
PillDropdown pattern as the existing theme variant selector.

Three files changed:

  packages/shared-ui/src/navigation/types.ts
    Add showAiTierSelector, aiTierItems, currentAiTierLabel to
    PillNavigationProps. Same shape as the existing theme variant
    and language switcher props.

  packages/shared-ui/src/navigation/PillNavigation.svelte
    Destructure the three new props (defaults: false, [], 'KI').
    Render a PillDropdown with icon="cpu" between the theme
    variant selector and the theme toggle button.

  apps/mana/apps/web/src/routes/(app)/+layout.svelte
    Import llmSettingsState, updateLlmSettings, tierLabel, type
    LlmTier from @mana/shared-llm. Import isLocalLlmSupported,
    getLocalLlmStatus, loadLocalLlm from @mana/local-llm.

    Build aiTierItems as a $derived array of PillDropdownItem:
      - Three tier toggles: Browser (Gemma 4), Server (Gemma 4),
        Cloud (Gemini). Each shows active checkmark when enabled.
        Clicking toggles the tier in/out of allowedTiers. Browser
        toggle hidden when WebGPU isn't available.
      - Browser model status line: "✓ Modell geladen" (disabled,
        green) or "Lade... X%" (disabled, progress) or "Modell
        laden (~500 MB)" (clickable, triggers loadLocalLlm).
        Only shown when browser tier is enabled.
      - Divider + "KI-Einstellungen" link to /settings for the
        full configuration (cloud consent, behavior toggles, etc.)

    Build currentAiTierLabel as privacy-sorted first-active-tier
    short name: "Browser" or "Server" or "Cloud" or "Aus".

    Wire all three to PillNavigation via showAiTierSelector={true}
    + {aiTierItems} + {currentAiTierLabel}.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 18:32:05 +02:00
Till JS
0f7ab60397 feat: top-5 ROI improvements — CI gate, auth fields, body×timeblocks, sync pull, tests
Five high-impact improvements across the stack:

1. Pre-push hook: svelte-check gate (.husky/pre-push)
   Runs `pnpm check --fail-on-warnings` before every `git push`.
   Blocks pushes with type errors or warnings so we never drift
   back to 418 errors. Takes ~15s on warm cache — acceptable for
   push frequency. Skip with `--no-verify` if needed.

2. getUserFromToken: map name/image/twoFactorEnabled
   The JWT payload carries these three fields (from Better Auth's
   user profile + 2FA enrollment) but getUserFromToken() only
   extracted sub/email/role/tier. The Settings page, onboarding
   ProfileStep, and TwoFactorSetup all read these via
   `authStore.user?.name` etc. and got undefined. Now mapped from
   both top-level claims and user_metadata (legacy layout).
   DecodedToken type extended to match.

3. Body × TimeBlocks integration
   startWorkout() now creates a TimeBlock (kind='logged',
   type='body', sourceModule='body') so workouts appear in the
   calendar, timeline page, and DayTimelineWidget. finishWorkout()
   stamps the TimeBlock's endDate so the calendar shows duration.
   deleteWorkout() cascades the TimeBlock deletion. Added
   `timeBlockId?: string` to LocalBodyWorkout.

4. Sync pull() silent-failure surfacing
   Symmetric with the push() fix from the SYNC_DEBUG commit:
   pull() now logs a console.warn + emits telemetry for both
   the unknown-appid and no-token failure paths instead of
   silently returning. Same diagnostic value as the push fix —
   the SYNC_DEBUG runbook's Schritt C now surfaces pull failures
   too.

5. Unit tests for contacts, chat, calendar (3 new test files)
   Same fake-indexeddb + MemoryKeyProvider harness as body/nutriphi.
   - contacts: create+encrypt PII, soft-delete, toggleFavorite (4)
   - chat: create+encrypt title, archive, pin/unpin, delete (4)
   - calendar: create with defaults, soft-delete, setAsDefault (3)
   Total test count: 37 passing across 5 suites.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 18:17:32 +02:00
Till JS
04ce8e5d6f fix(chat): add auth header, template system prompts, streaming debounce
Three critical fixes to the chat completion service:

1. Auth header: attach Bearer token from authStore on every request.
   Without this, mana-api returns 401 in production.

2. Template support: when a conversation has a templateId, resolve
   and decrypt its systemPrompt from IndexedDB and prepend it as a
   system message to the LLM context. Both route page and workbench
   overlay now pass templateId + modelId through to sendAndStream().

3. Streaming debounce: persist accumulated text to Dexie at most
   every 250ms instead of on every SSE chunk. Reduces encrypt+write
   operations from ~50/response to ~8 without affecting the live UI
   (onChunk still fires on every token).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 18:03:43 +02:00
Till JS
d7663e95b1 feat(playground): persistent chat history, token display, model comparison
Three major features for the LLM playground module:

1. Chat history persistence — conversations and messages are saved to
   IndexedDB (encrypted at rest), survive page reload, and sync via
   mana-sync. Sidebar shows conversation list with load/delete. Auto-
   titles from first user message. Lazy conversation creation on first
   send.

2. Token/usage display — llm.ts now yields a StreamChunk union type
   (delta | usage). Token counts (prompt + completion) are shown beneath
   each assistant message and persisted per message record.

3. Model comparison — toggle comparison mode in the config bar, select
   2-4 models, and see responses streamed side-by-side in a CSS grid.
   Each comparison round is tied by a comparisonGroupId. All streams
   have independent AbortControllers. Follow-up messages use the first
   model's response as conversation context.

New files: stores/conversations.svelte.ts
New tables: playgroundConversations, playgroundMessages (encrypted)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 18:02:27 +02:00
Till JS
28c10246e3 feat(zitare): smooth transitions, custom quotes, notes, new categories, fuzzy search
- Smooth fade/slide animation on quote cycling in workbench
- Custom quotes: new table (DB v5), store, queries for user-created quotes
- Notes field on favorites for personal thoughts
- 3 new categories: humor, wissenschaft, kunst
- 23 new quotes incl. contemporary authors (Angelou, Mandela, Winfrey, etc.)
- 5 curated theme decks (Stoizismus, Feminismus, Philosophie, etc.)
- getAllAuthors() for author browse pages with bio + quote counts
- fuzzySearchQuotes() with bigram similarity for typo-tolerant search
- Pre-built category + author indexes for O(1) lookups

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 18:01:15 +02:00
Till JS
adb1649005 refactor(mana/web): architecture cleanup — liveQuery migration, dead types, seed registry
Four architectural improvements that reduce boilerplate, eliminate
dead code, document the frozen schema boundary, and centralize the
guest data seeding that was previously defined but never called.

1. Migrate remaining 10 modules to useLiveQueryWithDefault
   music (5), moodlit (2), places (2), storage (2), calc (2),
   planta (5), photos (3), contacts (1), inventory (4) — 26 hooks
   total. Each queries.ts now imports useLiveQueryWithDefault from
   @mana/local-store/svelte instead of raw liveQuery from dexie.
   Call sites that used manual $effect + subscribe() boilerplate
   replaced with $derived(ctx.value). Files touched: 10 queries.ts
   + 5 route/component call sites (contacts, places, photos,
   inventory, calc).

2. Remove dead Memoro Tag interface
   memoro/types.ts had a local Tag type (with isPinned, sortOrder)
   that diverged from the @mana/shared-tags Tag. No file imported
   it after the earlier migration — removed the interface and added
   a comment directing future readers to @mana/shared-tags.

3. Document frozen schema boundary in database.ts
   Updated the v1 comment to explicitly state it's frozen and
   explain why (Dexie only runs upgrades when the version number
   bumps). Lists the current additive versions: v2=body, v3=who,
   v4=news. News tables were already correctly extracted to v4 by
   concurrent work.

4. Centralize guest seed registry
   Created lib/data/seed-registry.ts that imports GUEST_SEED
   constants from 13 modules (habits, body, dreams, moodlit,
   contacts, calendar, chat, cards, skilltree, todo, notes, times,
   planta) and provides a single seedAllGuestData() function.
   Wired into manaStore.initialize() in local-store.ts so seeds
   actually get inserted on first visit. Previously every module
   defined and re-exported seed data but nothing ever consumed it.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 18:01:02 +02:00
Till JS
fa099145eb feat(chat): add workbench detail overlay with streaming AI
Register a detail view for the chat module so clicking a conversation
in the workbench opens an inline overlay with the full message thread
and input area. Reuses the shared sendAndStream() completion service.

- ListView: decrypt conversations + messages, add "Neuer Chat" button,
  click opens detail overlay with sibling navigation, context menu
- DetailView: message bubbles, streaming indicator, auto-scroll,
  Enter to send / Shift+Enter for newline
- App registry: add detail view loader + paramKey 'conversationId'

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 17:52:17 +02:00
Till JS
6cc40242e9 feat(chat): wire AI streaming completions in chat detail page
Replace the placeholder stub with a real streaming SSE connection
to mana-api at /api/v1/chat/completions/stream. Extracts the
send-and-stream cycle into a shared services/completion.ts helper
so both the route page and workbench overlay can reuse it.

- Streams assistant response chunks into a live bubble
- Shows thinking dots (●●●) while waiting for first token
- Handles 402 (insufficient credits) with German error message
- Auto-titles conversation from first user message
- Persists final assistant text to IndexedDB with encryption

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 17:52:02 +02:00
Till JS
4f17626d3d feat(guides): complete module with types, CRUD, detail view, and run tracking
Builds out the guides module from a static content display into a
full local-first module with interactive step-by-step progress:

- types.ts: LocalGuide/Section/Step/Run records, domain types, DTOs
- collections.ts: Dexie table accessors + guest seed (6 guides, 14
  sections, 22 steps with real instructional content)
- queries.ts: liveQuery hooks (useAllGuides, useGuide, useSections,
  useSteps, useLatestRun, useRunsByGuide) + type converters + search
- stores/guides.svelte.ts: full CRUD for guides/sections/steps,
  run tracking (startRun, completeStep, uncompleteStep, completeRun),
  cascade delete, all with encryptRecord
- views/DetailView.svelte: step-by-step viewer with sections as
  collapsible blocks, steps as interactive checklist, progress bar,
  inline editing, inline add for sections/steps
- ListView.svelte: DB-based instead of static, ViewProps, inline
  create, category filter, search, per-guide progress indicators
- apps.ts: detail view + paramKey registered
- crypto/registry.ts: guides/sections/steps encrypted fields

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 17:51:19 +02:00
Till JS
f17d748d85 feat(picture): add inline upload to workbench ListView
Add drag-and-drop zone and upload button to the picture module's
workbench page. Uploads go to mana-media, then insert a LocalImage
record via imagesStore.insert() with encryption. Shows thumbnail
previews with upload status (spinner/check/error).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 17:42:31 +02:00
Till JS
2c56780bda feat(mana/web): add shared voice transcription helper
Extract the duplicated STT fetch logic from 5 module stores
(dreams, memoro, notes, todo, habits) into a single
$lib/voice/transcribe.ts helper. Returns text, language,
durationSeconds, and the model identifier from mana-stt.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 17:41:27 +02:00
Till JS
ea4f05dc11 feat(inventory): add quick item creation to workbench ListView
Adds a "Gegenstand hinzufügen" button that expands into an inline form
with a collection picker dropdown and name input. Items are created
directly via itemsStore.create() without leaving the workbench panel.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 17:40:28 +02:00
Till JS
e77ae5d5eb feat(who): add character dossier system for staged fact disclosure
Pre-researched dossiers (37 JSON files, DE+EN) replace the old
personality strings as the source of truth for the Who guessing game.
A strong cloud LLM (Gemini 2.5 Flash) generates structured facts per
character — voice, values, achievements, anecdotes, relationships,
forbidden-early-words, and three-stage hints — so the small runtime
model (gemma3:4b) gets only what it needs per turn instead of raw
personality text that leaks the identity immediately.

- dossier-types.ts: Zod schema + TS types for CharacterDossier
- dossier-loader.ts: boot-time loader with validation + coverage report
- generate-who-dossiers.ts: one-shot generator script (Google Gemini
  or local mana-llm fallback, idempotent, --force/--id flags)
- 37 dossier JSON files in data/dossiers/

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 17:40:16 +02:00
Till JS
734f149596 feat(moodlit): add inline mood creation to workbench ListView
Adds a compact create form with live gradient preview, name input,
color pickers (add/remove, max 8), and animation type dropdown.
New moods are written via moodsStore.createMood() to IndexedDB.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 17:38:12 +02:00
Till JS
d3b9805341 feat(mana/web): play Who games inline on workbench page
Embed PlayView directly in the ListView so games can be started
and played without navigating away from the workbench. PlayView
now accepts an onBack callback instead of hardcoded goto('/who').

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 17:37:42 +02:00
Till JS
05f4da5db3 feat(events): wire workbench detail overlay via ViewProps
Events ListView and DetailView now use the standard ViewProps interface
(navigate/goBack/params) instead of the custom onOpenEvent callback.
Adds paramKey to the events app registration so the workbench overlay
knows which param carries the event ID. Clicking an event card now
opens the detail overlay with prev/next sibling navigation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 17:36:40 +02:00
Till JS
f1a7f35d66 feat(context): add inline document creation to workbench ListView
Adds a "Neues Dokument" button and an "Alle Dokumente" link in the
toolbar. Document rows are now clickable <a> tags linking to the
detail page instead of static divs.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 17:36:31 +02:00
Till JS
26914b14c8 fix(body): unblock routine creation + remove duplicate header
The save button was permanently disabled for users with no exercises
because the disabled gate required newSelected.size > 0. Now only
the name is required; an inline "add exercise" flow lets users create
exercises directly from the routine form. Also removed the redundant
h1 + subtitle since the workbench shell already renders the title.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 17:36:24 +02:00
Till JS
da03fac722 fix(mana/web+packages): clear all 270 warnings to zero
Comprehensive warning sweep across 128 files that brings svelte-check
from 270 warnings → 0 (plus 3 new errors from concurrent upstream
changes fixed inline).

Final state: 6473 files, 0 errors, 0 warnings, 0 files with problems.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 17:34:49 +02:00
Till JS
b8987562ba fix(mana/web): add logging + toast for encryption vault unlock failures
Vault unlock errors were silently swallowed, causing encrypted content
(enc:1:...) to render as ciphertext in the UI. Now logs each step of
the unlock flow and shows an error toast when the vault fails to unlock.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 17:29:02 +02:00
Till JS
716466e757 fix(shared-llm): sort candidate tiers privacy-first (browser before server)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 17:23:28 +02:00
Till JS
64b8ab30ad fix(mana-media): commit initial schema migration + run on startup
The media schema/tables were never created on fresh deploys because
mana-media only shipped a `db:push` script and nothing ever ran it
in the container. Result: every upload returned 500 the moment a
new environment came up (just hit prod again on mana.how).

- Add `db:generate` + `db:migrate` scripts and a migrate.ts runner
- Generate the initial migration covering media/media_references/
  media_thumbnails (matches what was already on local + prod, which
  were stamped manually so the migrator skips on existing deploys)
- Call runMigrations() at startup in src/index.ts so future fresh
  containers self-bootstrap. Idempotent — drizzle tracks state in
  drizzle.__drizzle_migrations.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 02:51:41 +02:00
Till JS
80b23dd9ff fix(mana/web): clear remaining type errors — long-tail sweep
The last cleanup pass after the package-level fixes. Each of the
~30 files below had 1-2 distinct errors; they're grouped because
none individually justifies its own commit and they're all the same
shape: small drift between a call site and the type system the
existing-code-doesn't-need-to-change refactor that gets it to clean.

Highlights by file:

vite.config.ts
  Switched `defineConfig` import from `vite` to `vitest/config` so
  the inline `test:` block (vitest unit-test exclude rule) is
  recognized at the type layer. Was the last single error standing.

routes/(app)/news/+page.svelte
  Replaced `{#each ranked as { article } (article.id)}` destructure
  with `{#each ranked as scored (scored.article.id)}` + two
  `{@const}` rows. The destructured-each + immediate-`@const`
  combination tripped a Svelte compiler placement error.

routes/(app)/contacts/[id], modules/calendar/EventForm
  `(x as Record<string, unknown>)` casts were rejected because the
  source type doesn't have a string index signature. Two-step
  cast: `as unknown as Record<string, unknown>`.

routes/(app)/inventory/collections/[id]/edit
  `collection.schema.fields` round-trips through JSON in the Dexie
  row, which widens `type` to plain `string`. Cast back to
  `FieldDefinition[]` at the read site; the runtime values match
  the FieldType union.

routes/(app)/presi/deck/[id], modules/zitare/QuoteCard,
modules/memoro/views/DetailView
  - presi: `currentDeck?.name` → `?.title` (Deck has `title`, not
    `name`).
  - QuoteCard: `let authorBioText = $derived(() => {...})` was
    storing the arrow function itself. Switch to `$derived.by(...)`.
  - memoro DetailView: explicit `<QueuedTask | null>` generic on
    the useLiveQueryWithDefault call so the unknown-typed default
    doesn't poison downstream state.

routes/(app)/memoro/{,/[id]}/+page.svelte + modules/memoro/queries.ts
  The Tag flowing through these components is the `@mana/shared-tags`
  shape (from `useAllTags`), not memoro's local Tag (which has
  isPinned/sortOrder for a UI we never built). Aligned all three
  files to the shared shape so the Tag[] arrays compose without
  property mismatches.

modules/{questions,context}/index.ts
  Re-exported names that didn't exist:
  - `questionCollectionTable` → `qCollectionTable`
  - `contextDocumentTable` → `documentTable`
  Both were leftover from a long-ago rename that the consumers
  still call by the new name.

modules/picture/stores/images.svelte.ts, modules/times/EntryItem
  - images: `toggleField()` wants a string-keyed Table<>; cast at
    the call site (runtime keys are UUIDs anyway).
  - EntryItem: `autoSave(updates: Record<string, unknown>)` won't
    fit Dexie's `UpdateSpec<LocalTimeEntry>`. Narrowed to
    `Partial<LocalTimeEntry>` and added the missing import.

modules/todo: TodoPage + QuickAddTask
  - TodoPage was passing `onOpen` to TaskItem (which only accepts
    `onClick` + `onContextMenu` + `onToggleComplete`). Replaced
    with the proper triplet on the recently-completed branch.
  - QuickAddTask `locale?: string` widened the input past the
    `ParserLocale` union the parser actually accepts. Imported
    the union and tightened the prop.

modules/presi/views/DetailView
  `decksStore.deleteDeck` returns `Promise<boolean>`, but
  `deleteWithUndo()` expects `Promise<void>`. Wrapped in an async
  arrow that discards the return.

routes/(app)/citycorners/.../edit
  Self-referential `let locId = $derived(locId ?? '')` from a
  search-and-replace gone wrong in the previous commit batch.
  Restored to `$derived($page.params.id ?? '')`.

routes/(app)/+layout.svelte, lib/components/onboarding/OnboardingWizard
  - layout: `(window as Record<string, unknown>)` → two-step
    `(window as unknown as Record<...>)` cast. Same shape as the
    contacts/EventForm fixes.
  - OnboardingWizard: added optional `onSkip?: () => void` prop
    so the layout's analytics callback type-checks. The wizard
    always also calls `onComplete()`, so the modal still closes
    cleanly without onSkip.

routes/(app)/api-keys/+page.svelte
  Removed `min={1}` / `max={1000}` props from the shared `<Input>`
  component (it's not a passthrough wrapper for native HTML
  attributes). Runtime validation still gates submit.

routes/(auth)/forgot-password
  `authStore.forgotPassword(email)` doesn't exist; the wrapper
  exposes `resetPassword(email)` for the send-email entry point.
  Renamed.

routes/(app)/{gifts,llm-test}, lib/content/help/index.test
  - gifts: `balance.freeCreditsRemaining` is now optional (added
    in the credits commit). Defaulted to 0 in the math.
  - llm-test: enqueueTaskNow union of two tasks with different
    output types — widened with `as any` for the enqueue call.
  - help index.test: `content.contact` is optional, asserted with
    non-null `!`.

lib/components/{SessionWarning,DashboardGrid,onboarding/OnboardingWizard}
  - SessionWarning: was calling `getAccessTokenSync` (doesn't
    exist) and `refreshToken` (doesn't exist). Switched to
    `getAccessToken()` (async, returns Promise) and `getValidToken()`
    (refreshes under the hood when expired).
  - DashboardGrid: `error?.message` on a `{}`-typed boundary
    arg. Cast to `Error | undefined`.

dashboard widgets: ContextDocs / ClockTimers / ActivityFeed
  - ContextDocs: `getSpaceName(spaceId: string)` widened to
    `string | null | undefined` so the optional doc.spaceId
    flows in cleanly.
  - ClockTimers: `formatRepeatDays`/`formatRemaining` widened to
    accept null|undefined.
  - ActivityFeed: `Activity` icon doesn't exist in
    `@mana/shared-icons`/phosphor-svelte. Replaced with `Pulse`
    everywhere in the file.

lib/app-registry/registry.spec
  `Set<AppIconId>.has(stringId)` rejected because the union is
  narrower. Widened the Set to `Set<string>`.

Net: -16 type errors. Final count: 0.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 20:25:08 +02:00