feat(manacore/web): unified IndexedDB sync via Dexie hooks, eliminate cross-app readers

Activate sync for the unified manacore IndexedDB by adding automatic change tracking
via Dexie hooks on all 120+ tables. This replaces the unused manual trackChange() approach
and eliminates the need for 12 separate cross-app IndexedDB reader instances.

Key changes:
- database.ts: Dexie hooks auto-record _pendingChanges for every write, TABLE_TO_SYNC_NAME mapping
- sync.ts: rewritten with correct backend URLs, auth token, table name translation, server change guard
- layout: unified sync engine replaces per-app manacoreStore/tag/link sync + 12 cross-app readers
- cross-app-queries.ts: rewritten to query unified DB directly instead of via cross-app-stores
- legacy-migration.ts: one-time migration from old per-app DBs (manacore-todo etc.) to unified DB
- local-store.ts: refactored to use unified DB with collection wrappers instead of createLocalStore()
- Deleted cross-app-stores.ts (383 lines) and change-tracker.ts (80 lines)
- Updated ActivityFeed, TasksTodayWidget, CalendarEventsWidget, ContactsFavoritesWidget, spiral/collect.ts
- Updated CLAUDE.md with unified IndexedDB architecture documentation

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Till JS 2026-04-02 12:40:31 +02:00
parent 88864fd3a1
commit 05e5e957e8
14 changed files with 782 additions and 853 deletions

105
CLAUDE.md
View file

@ -570,51 +570,67 @@ $: doubled = count * 2;
All web apps use a **local-first** data layer: reads/writes go to IndexedDB (Dexie.js) first, sync to server in the background. This enables guest mode, offline CRUD, and instant UI.
### Key Components
### Unified IndexedDB Architecture
| Component | Location | Purpose |
|-----------|----------|---------|
| `@manacore/local-store` | `packages/local-store/` | Dexie.js collections, sync engine, Svelte 5 reactive queries |
| `mana-sync` | `services/mana-sync/` | Go sync server (WebSocket push, field-level LWW conflict resolution) |
| Todo Hono Server | `apps/todo/apps/server/` | Lightweight compute server (RRULE, reminders, admin) on Bun |
### Data Flow
The ManaCore unified app uses a **single IndexedDB** (`manacore`) containing all 120+ collections from all apps. Table names that collide across apps are prefixed (e.g., `todoProjects`, `cardDecks`, `presiDecks`).
```
Guest: App → IndexedDB (Dexie.js) → UI (no sync)
Logged in: App → IndexedDB → UI → SyncEngine → mana-sync (Go) → PostgreSQL
← WebSocket push ←
┌─────────────────────────────────────────────┐
│ Unified IndexedDB: "manacore" │
│ │
│ tasks, todoProjects, labels, ... (todo) │
│ calendars, events (calendar) │
│ contacts (contacts) │
│ conversations, messages (chat) │
│ ... 120+ collections across 27 apps │
│ │
│ _pendingChanges (tagged with appId) │
│ _syncMeta (keyed by [appId+coll]) │
└──────────────────┬──────────────────────────┘
│ Dexie hooks auto-track
│ all writes as pending changes
┌──────────────────────────────────────────────┐
│ Unified Sync Engine (sync.ts) │
│ One sync channel per appId │
│ POST /sync/{appId} (push) │
│ GET /sync/{appId}/pull (pull) │
│ WS /ws/{appId} (real-time notifications) │
└──────────────────┬───────────────────────────┘
mana-sync (Go)
PostgreSQL (sync_changes)
```
### Migrated Apps (21/23)
#### Key Files
| App | Collections | Status |
|-----|------------|--------|
| Todo | tasks, projects, labels, taskLabels, reminders | Done |
| Zitare | favorites, lists | Done |
| Calendar | calendars, events | Done |
| Clock | alarms, timers, worldClocks | Done |
| Contacts | contacts | Done |
| Cards | decks, cards | Done |
| Picture | images, boards, boardItems, tags, imageTags | Done |
| Presi | decks, slides | Done |
| Inventar | collections, items, locations, categories | Done |
| NutriPhi | meals, goals, favorites | Done |
| Planta | plants, plantPhotos, wateringSchedules, wateringLogs | Done |
| Storage | files, folders, tags, fileTags | Done |
| Chat | conversations, messages, templates | Done |
| Questions | collections, questions, answers | Done |
| Mukke | songs, playlists, playlistSongs, projects, markers | Done |
| Context | spaces, documents | Done |
| Photos | albums, albumItems, favorites, tags, photoTags | Done |
| SkilltTree | skills, activities, achievements | Done |
| CityCorners | locations, favorites | Done |
| Times | clients, projects, timeEntries, tags, templates, settings | Done |
| uLoad | links, tags, folders, linkTags | Done |
| Calc | calculations, savedFormulas | Done |
| ManaCore | userSettings, dashboardConfigs | Done |
| File | Purpose |
|------|---------|
| `apps/manacore/apps/web/src/lib/data/database.ts` | Unified Dexie DB, SYNC_APP_MAP, table name mappings, Dexie hooks |
| `apps/manacore/apps/web/src/lib/data/sync.ts` | Unified sync engine (push/pull/WS per appId) |
| `apps/manacore/apps/web/src/lib/data/legacy-migration.ts` | One-time migration from old per-app DBs |
| `packages/local-store/` | Standalone local-store (used by individual apps, not the unified app) |
| `services/mana-sync/` | Go sync server (WebSocket push, field-level LWW) |
**Not migrated (no CRUD data model):** Matrix (protocol client), Playground (stateless)
#### How Sync Works
1. Module stores write directly to Dexie tables (`db.table('tasks').add(...)`)
2. Dexie hooks in `database.ts` automatically record each write to `_pendingChanges` with the correct `appId`
3. The unified sync engine groups pending changes by `appId` and pushes to `POST /sync/{appId}`
4. Table names are mapped between unified names (e.g., `todoProjects`) and backend names (e.g., `projects`) via `TABLE_TO_SYNC_NAME`
5. Server changes are pulled per collection and applied with a guard flag to prevent re-sync loops
#### Adding a New App Module
1. Add table definitions to `database.ts` schema (in `db.version(1).stores({...})`)
2. Add table-to-appId mapping in `SYNC_APP_MAP`
3. Add any renamed tables to `TABLE_TO_SYNC_NAME`
4. Create module in `src/lib/modules/{app}/` with collections, queries, stores
5. Dexie hooks automatically handle change tracking — no manual `trackChange()` needed
### Standalone Apps (Legacy)
Individual apps in `apps/*/apps/web/` still use `@manacore/local-store` with per-app IndexedDB databases (`manacore-{appId}`). When users first open the unified ManaCore app, `legacy-migration.ts` migrates data from these old DBs into the unified DB.
### Dev Commands (Local-First Stack)
@ -626,19 +642,6 @@ pnpm dev:todo:local # Web + sync + server (no auth needed)
pnpm dev:todo:full # Everything incl. auth + DB setup
```
### Adding Local-First to a New App
1. Create `apps/{app}/apps/web/src/lib/data/local-store.ts` — define collections with `createLocalStore()`
2. Create `apps/{app}/apps/web/src/lib/data/guest-seed.ts` — onboarding data
3. Rewrite stores to use `collection.getAll()` / `collection.insert()` instead of API calls
4. In layout: `await store.initialize()`, `store.startSync()` on login, `allowGuest={true}` on AuthGate
5. Set `userEmail = ''` for guests so PillNav shows login button
6. Add `GuestWelcomeModal` for first-visit experience
### Architecture Plan
Full migration plan: `.claude/plans/local-first-architecture-migration.md`
## Shared Packages (`packages/`)
| Package | Purpose |