After importing 22M OSM objects for the DACH extract:
- Disable adminLookup (no WOF data needed for address search)
- Configure leveldb path inside the data volume
- Specify planet-latest.osm.pbf as the import filename
- Convert libpostal service config from string to object form
- Drop boundary.country default — Pelias only accepts a single
country value, and our index only contains DACH data anyway
Verified forward + reverse geocoding work end-to-end for Konstanz
test queries via the mana-geocoding wrapper on port 3018.
Known limitation: OSM category/type (amenity:restaurant etc.) is
not yet populated in Pelias responses — will require whitelisting
those tags in the importer config and re-running the import.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The Dockerfile copied only its own package.json, causing bun install to
fail on @mana/shared-hono workspace dependency. Now copies workspace root
package.json and shared-hono/shared-types packages.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Blackbox web probes were missing: body, journal, dreams, firsts,
cycles, events, finance, places, who, news, mail. These modules
exist in mana-apps.ts and are deployed but were never added to
prometheus.yml — so they didn't show on status.mana.how.
Also adds mana-geocoding and mana-events to the internal SvelteKit
status page health checks.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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>
- 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>
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>
- 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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
- 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>
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>
- 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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>