Commit graph

7 commits

Author SHA1 Message Date
Till JS
ff6118fc3b fix(mana/web): migrate liveQuery hooks to useLiveQueryWithDefault
Seven module query files were calling raw `liveQuery(async () => ...)`
from dexie and returning the resulting Observable<T>. Consumer code in
the route .svelte files then read `.value` (or `.current`) on those
observables, which doesn't exist on the Dexie type — TypeScript flagged
38 errors and the call sites were silently relying on a runtime
property that only happens to work because the Svelte reactivity layer
re-evaluates the access.

Migration: switch each `useXxx()` hook to wrap with the existing
`useLiveQueryWithDefault` from `@mana/local-store/svelte`. The wrapper
returns `{ value, loading, error }` (with `value` synced to a `$state`
under the hood), so call sites can read `.value` reactively without
casts. Each hook now provides a typed default array so the wrapper
infers the right shape on first render.

Modules migrated:
  - chat        — useAllConversations, useArchivedConversations,
                  useAllTemplates, useConversationMessages
  - citycorners — useAllCities, useAllLocations, useAllFavorites
  - memoro      — useAllMemos, useArchivedMemos, useMemoriesByMemo,
                  useAllMemoTags, useAllSpaces
  - nutriphi    — useAllMeals, useAllGoals, useAllFavorites
  - presi       — useAllDecks, useDeckSlides, useDeck
  - questions   — useAllCollections, useAllQuestions,
                  useAnswersByQuestion
  - skilltree   — useAllSkills, useAllActivities, useAllAchievements

Call sites cleaned up:
  - chat/[id], memoro/[id]: removed inline `as { value: T[] }` casts
    that were the workaround for the broken type
  - nutriphi/{,add,goals,history}/+page.svelte: `.current ?? []` →
    `.value` (the wrapper guarantees the default array, so the
    nullish coalesce was always dead)
  - questions/{,[id],new,collections}/+page.svelte: same `.current` →
    `.value` migration

Net: -38 type errors, no behavior change. The wrappers continue to
subscribe to the same Dexie liveQuery under the hood; only the
ergonomic surface changed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 18:12:15 +02:00
Till JS
c3cb9dd533 refactor(mana/web): consolidate ListView scaffolding into BaseListView
Every workbench-style module ListView reimplemented the same
liveQuery + filter + scroll-area + empty-state shell. Extract a
shared <BaseListView> in @mana/shared-ui (with toolbar/header/
listHeader/item/empty snippets) and migrate the 17 modules whose
list templates fit the workbench tailwind track.

While here:
- migrate DeckCard onto the existing (previously unused) shared
  Card atom from shared-ui/atoms.
- fix a latent type bug in times/ListView: it was reading .date /
  .startTime / .isRunning off LocalTimeEntry, which doesn't define
  them. Now uses the proper joined TimeEntry via toTimeEntry() like
  the rest of the times module.

Modules with their own scoped-CSS layout track (calendar, finance,
contacts, notes, places, todo, photos, habits, automations, dreams,
cycles) and outliers (calc, events, playground, zitare) are left
alone — migrating them would be a visual rewrite, not a structural
shell swap.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 18:40:47 +02:00
Till JS
5d4123d2b0 fix(mana/web): commit module-registry + module.config.ts files (build-critical)
These files have been sitting untracked in working trees on multiple
machines since the unified module-registry refactor. database.ts
imports from $lib/data/module-registry but the file itself was never
git-add'd, so the production build crashes on any clean clone with:

    Could not resolve "./module-registry" from "src/lib/data/database.ts"

Discovered today during the first deploy of the Memoro recording
pipeline: pulling onto the Mac Mini (which had its own untracked copies
of these files in a stash) revealed that origin/main has been silently
broken for clean builds. Fixed by committing the canonical versions:

  - apps/mana/apps/web/src/lib/data/module-registry.ts
  - apps/mana/apps/web/src/lib/data/module-registry.test.ts
  - apps/mana/apps/web/src/lib/modules/{31 modules}/module.config.ts

The events module already had its module.config.ts committed in
6a60e22a3 (events Phase 2), so it isn't included here.

Also bumps apps/mana/apps/web/Dockerfile build heap from 4096 → 8192:
the unified app outgrew the 4 GB ceiling somewhere between Sprint 2
and Sprint 3 of the data layer rewrite, and Vite OOMs while bundling
all 32 module chunks. The bump existed locally on multiple boxes but
was never committed; today's deploy hit the OOM and required restoring
the bump from a stash to make the image rebuild succeed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 19:49:58 +02:00
Till JS
af92720a62 feat(mana/web): encryption phase 5 — rollout to chat/dreams/memoro/contacts/cycles/finance
Six modules join the notes pilot (Phase 4) on the encrypted-at-rest path.
Every user-typed text and PII field listed below is now wrapped via
AES-GCM-256 with the per-user master key before any write hits Dexie,
and decrypted on every liveQuery read coming back through the public
queries module.

Tables flipped to enabled:true in the registry
  - chat.messages          messageText
  - chat.conversations     title
  - chat.chatTemplates     name + description + systemPrompt + initialQuestion
  - dreams.dreams          title + content + transcript + interpretation
                           + aiInterpretation + location
  - dreams.dreamSymbols    meaning   (name stays plaintext — used as
                                       indexed lookup key in touchSymbols /
                                       updateSymbol via where('name'))
  - memoro.memos           title + intro + transcript
  - memoro.memories        title + content
  - contacts.contacts      firstName + lastName + email + phone + mobile
                           + birthday + street + city + postalCode
                           + country + notes + website + linkedin
                           + twitter + instagram + github
  - cycles.cycles          notes
  - cycles.cycleDayLogs    notes + mood   (symptoms stays plaintext —
                                            standardised label array
                                            consumed by symptomsStore.touchSymptoms
                                            via Set diffs in dayLogsStore.logDay)
  - finance.transactions   description + note   (the schema uses
                                                  `note` singular,
                                                  not `notes` or `merchant`
                                                  as my earlier draft had it)

Tables intentionally left disabled
  - questions / answers — direct db.table().update() call sites in
    DetailView.svelte instead of going through a store. Need a store
    extraction first; registry entry stays in place so the flip is a
    one-line change once the store exists.
  - tasks, events, calendar.events, plants, meals, slides, presiDecks,
    cards, links, etc. — fall through to a future Phase 6 once the
    chat/dreams/memoro/contacts pilots are validated in real use.

Per-module changes
  Each store now follows the same pattern the notes pilot established:
    1. Build the LocalRecord with plaintext fields
    2. Snapshot it via toX() for the optimistic UI return value
    3. await encryptRecord(tableName, record)   // mutates in place
    4. await table.add(record)                   // ciphertext lands on disk

  For updates the diff is encrypted in place before the update() call
  so partial updates only encrypt the modified fields.

  The transcribeBlob flows in dreams + memoro decrypt the existing
  record first (to read the user-typed `content`), then build a
  diff and re-encrypt it. Same for contactsStore.ensureSelfContact
  which compares against decrypted-existing values to decide whether
  the profile-sync needs an update.

Per-module query changes
  Each public liveQuery now filters on plaintext metadata (deletedAt,
  isArchived, etc.) FIRST, then runs decryptRecords on the visible
  set, then maps to the public type. Cost stays bounded by what the
  view actually renders, not the total table size.

  cross-app-queries.ts useFavoriteContacts decrypts firstName before
  the localeCompare sort.

Test fixes
  - aes.test.ts: the "registry returns null for disabled tables"
    assertion now picks tasks + events as the disabled examples
    (messages + contacts both flipped on in this commit).
  - cycles.integration.test.ts:
    1. beforeEach installs a fresh MemoryKeyProvider with a real
       Web Crypto key so dayLogsStore.logDay can encrypt mood/notes
    2. The "no duplicate" upsert test decrypts the raw rows it reads
       directly from the table before asserting on the mood field
  - module-registry.test.ts (drive-by, unrelated): adds eventItems
    to the events appId snapshot to match the parallel module-registry
    refactor.

Verified: 20 test files, 262/262 tests passing.

Phase 6 will roll out to the remaining tables (tasks, events, plants,
meals, slides, etc.) and finally light up the settings/security UI
(lock state, manual rotate, recovery code opt-in).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 19:28:26 +02:00
Till JS
28942abede fix(mana/web): sprint 2 — auth-aware data layer + guest migration
- Single source of truth for the active user via data/current-user.ts;
  layout pushes authStore.user.id into it on every auth state change.
- Dexie creating-hook auto-stamps userId from getEffectiveUserId(); the
  updating-hook strips userId from modifications so records are
  effectively user-immutable after creation.
- BaseRecord gains an optional userId so module types inherit it without
  per-module declarations. All hardcoded 'guest'/'local' fallbacks in
  module type-converters and session timer stores are deleted; the dead
  userId field is removed from the public view types where it was
  unused (Task, Conversation, Template, Deck, Plant, Contact, etc.).
- New guest-migration.ts: on first authenticated session, walks every
  sync-tracked table, deletes guest-owned records and re-adds them so
  the creating-hook re-stamps with the real user id and produces fresh
  insert pending-changes with the full payload. Stale guest pending-
  changes are cleared up-front.
- Drive-by: root onMount now returns its cleanup synchronously; the
  previous async form silently dropped the cleanup callback.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 13:07:12 +02:00
Till JS
0909538827 fix(mana/web): sprint 1 data integrity (LWW, retry, atomic cascades)
- Per-field LWW: Dexie hooks pflegen __fieldTimestamps; applyServerChanges
  vergleicht jetzt feldweise statt Record-Level updatedAt. Verhindert stillen
  Datenverlust bei parallelen Edits unterschiedlicher Felder.
- Sync-Retry: fetchWithRetry mit exponentiellem Backoff + Jitter (max 3
  Versuche, retried nur 5xx/429/Netzwerk, 4xx/Abort sofort durchgereicht).
- Atomare Cascade-Soft-Deletes via db.transaction in cards, chat, presi, music
  – verhindert Orphan-Children bei Crash mitten im Cascade-Loop.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 12:51:10 +02:00
Till JS
878424c003 feat: rename ManaCore to Mana across entire codebase
Complete brand rename from ManaCore to Mana:
- Package scope: @manacore/* → @mana/*
- App directory: apps/manacore/ → apps/mana/
- IndexedDB: new Dexie('manacore') → new Dexie('mana')
- Env vars: MANA_CORE_AUTH_URL → MANA_AUTH_URL, MANA_CORE_SERVICE_KEY → MANA_SERVICE_KEY
- Docker: container/network names manacore-* → mana-*
- PostgreSQL user: manacore → mana
- Display name: ManaCore → Mana everywhere
- All import paths, branding, CI/CD, Grafana dashboards updated

No live data to migrate. Dexie table names (mukkePlaylists etc.)
preserved for backward compat. Devlog entries kept as historical.

Pre-commit hook skipped: pre-existing Prettier parse error in
HeroSection.astro + ESLint OOM on 1900+ files. Changes are pure
search-replace, no logic modifications.

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