From adb1649005722b6ecc7d1c3402e10187361a2e51 Mon Sep 17 00:00:00 2001 From: Till JS Date: Fri, 10 Apr 2026 18:01:02 +0200 Subject: [PATCH] =?UTF-8?q?refactor(mana/web):=20architecture=20cleanup=20?= =?UTF-8?q?=E2=80=94=20liveQuery=20migration,=20dead=20types,=20seed=20reg?= =?UTF-8?q?istry?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- apps/mana/apps/web/src/lib/data/database.ts | 21 +++-- .../mana/apps/web/src/lib/data/local-store.ts | 5 ++ .../apps/web/src/lib/data/seed-registry.ts | 81 +++++++++++++++++++ .../apps/web/src/lib/modules/calc/queries.ts | 10 +-- .../web/src/lib/modules/contacts/queries.ts | 6 +- .../web/src/lib/modules/inventory/queries.ts | 18 ++--- .../modules/inventory/views/DetailView.svelte | 17 ++-- .../apps/web/src/lib/modules/memoro/types.ts | 14 ++-- .../web/src/lib/modules/moodlit/queries.ts | 10 +-- .../apps/web/src/lib/modules/music/queries.ts | 22 ++--- .../src/lib/modules/photos/ListView.svelte | 34 ++------ .../web/src/lib/modules/photos/queries.ts | 14 ++-- .../src/lib/modules/places/ListView.svelte | 32 +++----- .../web/src/lib/modules/places/queries.ts | 10 +-- .../web/src/lib/modules/planta/queries.ts | 22 ++--- .../web/src/lib/modules/storage/queries.ts | 10 +-- .../routes/(app)/calc/standard/+page.svelte | 2 +- .../src/routes/(app)/contacts/+page.svelte | 16 +--- .../routes/(app)/contacts/[id]/+page.svelte | 13 +-- 19 files changed, 198 insertions(+), 159 deletions(-) create mode 100644 apps/mana/apps/web/src/lib/data/seed-registry.ts diff --git a/apps/mana/apps/web/src/lib/data/database.ts b/apps/mana/apps/web/src/lib/data/database.ts index 158cbd7b5..f153c9e5b 100644 --- a/apps/mana/apps/web/src/lib/data/database.ts +++ b/apps/mana/apps/web/src/lib/data/database.ts @@ -44,10 +44,15 @@ export { export const db = new Dexie('mana'); -// Single canonical schema. The pre-launch cleanup collapsed historical -// versions 1–10 into this one block — see docs/PRE_LAUNCH_CLEANUP.md for -// rationale. After the system goes live, any further schema change MUST -// be added as a new `db.version(N)` block; never edit this one. +// Schema version 1 — the pre-launch canonical schema. Collapsed from +// historical v1–v10 during cleanup (see docs/PRE_LAUNCH_CLEANUP.md). +// +// IMPORTANT: this block is FROZEN. Any new tables MUST go into a new +// `db.version(N)` block below (currently v2=body, v3=who, v4=news). +// Adding tables here instead of in a new version causes silent schema +// drift: Dexie only runs the upgrade if the version number bumps, so +// existing IndexedDB instances would never see the new tables until +// the user clears storage. db.version(1).stores({ // ─── Sync Infrastructure (local-only, NOT in SYNC_APP_MAP) ─── _pendingChanges: '++id, appId, collection, recordId, createdAt', @@ -216,7 +221,8 @@ db.version(1).stores({ guideTags: 'id, guideId, tagId, [guideId+tagId]', // ─── Playground (appId: 'playground') ─── - // No persistent data — stateless LLM playground + playgroundConversations: 'id, model, isPinned, updatedAt', + playgroundMessages: 'id, conversationId, role, order, [conversationId+order]', // ─── Habits (appId: 'habits') ─── habits: 'id, order, isArchived, color', @@ -350,6 +356,11 @@ db.version(4).stores({ newsCachedFeed: 'id, topic, sourceSlug, language, publishedAt, [topic+publishedAt]', }); +// v5: Zitare custom quotes — user-created quotes stored locally. +db.version(5).stores({ + zitareCustomQuotes: 'id, author, category', +}); + // ─── Sync Routing ────────────────────────────────────────── // SYNC_APP_MAP, TABLE_TO_SYNC_NAME, TABLE_TO_APP, SYNC_NAME_TO_TABLE, // toSyncName() and fromSyncName() are now derived from per-module diff --git a/apps/mana/apps/web/src/lib/data/local-store.ts b/apps/mana/apps/web/src/lib/data/local-store.ts index 5224ed84b..2cee8f121 100644 --- a/apps/mana/apps/web/src/lib/data/local-store.ts +++ b/apps/mana/apps/web/src/lib/data/local-store.ts @@ -13,6 +13,7 @@ import type { WidgetConfig } from '$lib/types/dashboard'; import type { TileNode } from '$lib/types/tiling'; import { db } from './database'; import { guestSettings, guestDashboardConfigs } from './guest-seed.js'; +import { seedAllGuestData } from './seed-registry'; // ─── Types ────────────────────────────────────────────────── @@ -123,6 +124,10 @@ export const manaStore = { if (dashboardCount === 0 && guestDashboardConfigs.length > 0) { await db.table('dashboardConfigs').bulkPut(guestDashboardConfigs); } + + // Seed per-module guest data (habits presets, body exercises, dream + // examples, etc.). Idempotent: only inserts into empty tables. + await seedAllGuestData(); }, // No-ops — sync is handled by the unified sync engine diff --git a/apps/mana/apps/web/src/lib/data/seed-registry.ts b/apps/mana/apps/web/src/lib/data/seed-registry.ts new file mode 100644 index 000000000..bc23fe76b --- /dev/null +++ b/apps/mana/apps/web/src/lib/data/seed-registry.ts @@ -0,0 +1,81 @@ +/** + * Guest Seed Registry — central aggregation point for all module seed data. + * + * Each module defines a `*_GUEST_SEED` constant in its `collections.ts` + * file. This registry imports them all and provides a single + * `seedAllGuestData()` function that the local-store initialization + * path calls on first visit (when IndexedDB is empty). + * + * Adding a new module's seed: import its GUEST_SEED constant and add + * an entry to `MODULE_SEEDS` below. The table names must match the + * Dexie schema in database.ts. + */ + +import { db } from './database'; + +// ─── Module Seed Imports ───────────────────────────────────── +import { HABITS_GUEST_SEED } from '$lib/modules/habits/collections'; +import { BODY_GUEST_SEED } from '$lib/modules/body/collections'; +import { DREAMS_GUEST_SEED } from '$lib/modules/dreams/collections'; +import { MOODLIT_GUEST_SEED } from '$lib/modules/moodlit/collections'; +import { CONTACTS_GUEST_SEED } from '$lib/modules/contacts/collections'; +import { CALENDAR_GUEST_SEED } from '$lib/modules/calendar/collections'; +import { CHAT_GUEST_SEED } from '$lib/modules/chat/collections'; +import { CARDS_GUEST_SEED } from '$lib/modules/cards/collections'; +import { SKILLTREE_GUEST_SEED } from '$lib/modules/skilltree/collections'; +import { TODO_GUEST_SEED } from '$lib/modules/todo/collections'; +import { NOTES_GUEST_SEED } from '$lib/modules/notes/collections'; +import { TIMES_GUEST_SEED } from '$lib/modules/times/collections'; +import { PLANTA_GUEST_SEED } from '$lib/modules/planta/collections'; + +/** + * Flat list of { tableName, rows } entries. Only modules with non-empty + * seed arrays are listed — modules whose GUEST_SEED has only empty + * arrays (e.g. calc, storage, finance) are omitted because there's + * nothing to insert. + */ +const MODULE_SEEDS: { table: string; rows: Record[] }[] = []; + +function register(seed: Record[]>) { + for (const [table, rows] of Object.entries(seed)) { + if (rows.length > 0) { + MODULE_SEEDS.push({ table, rows }); + } + } +} + +// Register all module seeds +register(HABITS_GUEST_SEED); +register(BODY_GUEST_SEED); +register(DREAMS_GUEST_SEED); +register(MOODLIT_GUEST_SEED); +register(CONTACTS_GUEST_SEED); +register(CALENDAR_GUEST_SEED); +register(CHAT_GUEST_SEED); +register(CARDS_GUEST_SEED); +register(SKILLTREE_GUEST_SEED); +register(TODO_GUEST_SEED); +register(NOTES_GUEST_SEED); +register(TIMES_GUEST_SEED); +register(PLANTA_GUEST_SEED); + +/** + * Seed all module guest data into empty tables. Idempotent: tables + * that already have rows are skipped. Called once during + * `manaStore.initialize()`. + */ +export async function seedAllGuestData(): Promise { + for (const { table, rows } of MODULE_SEEDS) { + try { + const count = await db.table(table).count(); + if (count === 0) { + await db.table(table).bulkPut(rows); + } + } catch (err) { + // Non-fatal: seed failure shouldn't block app startup. + // The table might not exist yet (schema drift) or the DB + // might be in a read-only state (quota exceeded). + console.debug(`[seed-registry] failed to seed ${table}:`, err); + } + } +} diff --git a/apps/mana/apps/web/src/lib/modules/calc/queries.ts b/apps/mana/apps/web/src/lib/modules/calc/queries.ts index b8d8111ad..83f2bd89d 100644 --- a/apps/mana/apps/web/src/lib/modules/calc/queries.ts +++ b/apps/mana/apps/web/src/lib/modules/calc/queries.ts @@ -2,7 +2,7 @@ * Reactive queries for Calc — uses Dexie liveQuery on the unified DB. */ -import { liveQuery } from 'dexie'; +import { useLiveQueryWithDefault } from '@mana/local-store/svelte'; import { db } from '$lib/data/database'; import type { LocalCalculation, LocalSavedFormula } from './types'; import type { Calculation, SavedFormula } from '@calc/shared'; @@ -38,19 +38,19 @@ export function toSavedFormula(local: LocalSavedFormula): SavedFormula { /** All calculations (history), newest first. */ export function useAllCalculations() { - return liveQuery(async () => { + return useLiveQueryWithDefault(async () => { const locals = await db.table('calculations').toArray(); return locals .filter((c) => !c.deletedAt) .map(toCalculation) .reverse(); - }); + }, []); } /** All saved formulas. */ export function useAllSavedFormulas() { - return liveQuery(async () => { + return useLiveQueryWithDefault(async () => { const locals = await db.table('savedFormulas').toArray(); return locals.filter((f) => !f.deletedAt).map(toSavedFormula); - }); + }, []); } diff --git a/apps/mana/apps/web/src/lib/modules/contacts/queries.ts b/apps/mana/apps/web/src/lib/modules/contacts/queries.ts index 91f82ad4f..e1763d96a 100644 --- a/apps/mana/apps/web/src/lib/modules/contacts/queries.ts +++ b/apps/mana/apps/web/src/lib/modules/contacts/queries.ts @@ -2,7 +2,7 @@ * Reactive queries & pure helpers for Contacts — uses Dexie liveQuery on the unified DB. */ -import { liveQuery } from 'dexie'; +import { useLiveQueryWithDefault } from '@mana/local-store/svelte'; import { db } from '$lib/data/database'; import { decryptRecords } from '$lib/data/crypto'; import type { LocalContact, Contact, SortField, ContactFilter } from './types'; @@ -48,13 +48,13 @@ export function toContact(local: LocalContact): Contact { // ─── Live Queries ────────────────────────────────────────── export function useAllContacts() { - return liveQuery(async () => { + return useLiveQueryWithDefault(async () => { const visible = (await db.table('contacts').toArray()).filter( (c) => !c.deletedAt ); const decrypted = await decryptRecords('contacts', visible); return decrypted.map(toContact); - }); + }, []); } // ─── Display Helpers ────────────────────────────────────── diff --git a/apps/mana/apps/web/src/lib/modules/inventory/queries.ts b/apps/mana/apps/web/src/lib/modules/inventory/queries.ts index c6d869d8b..0974cbca6 100644 --- a/apps/mana/apps/web/src/lib/modules/inventory/queries.ts +++ b/apps/mana/apps/web/src/lib/modules/inventory/queries.ts @@ -4,7 +4,7 @@ * Uses prefixed table names: invCollections, invItems, invLocations, invCategories. */ -import { liveQuery } from 'dexie'; +import { useLiveQueryWithDefault } from '@mana/local-store/svelte'; import { db } from '$lib/data/database'; import { decryptRecords } from '$lib/data/crypto'; import type { LocalCollection, LocalItem, LocalLocation, LocalCategory } from './types'; @@ -160,32 +160,32 @@ export function toCategory(local: LocalCategory): Category { // ─── Live Queries ────────────────────────────────────────── export function useAllCollections() { - return liveQuery(async () => { + return useLiveQueryWithDefault(async () => { const locals = await db.table('invCollections').toArray(); return locals.filter((c) => !c.deletedAt).map(toCollection); - }); + }, []); } export function useAllItems() { - return liveQuery(async () => { + return useLiveQueryWithDefault(async () => { const visible = (await db.table('invItems').toArray()).filter((i) => !i.deletedAt); const decrypted = await decryptRecords('invItems', visible); return decrypted.map(toItem); - }); + }, []); } export function useAllLocations() { - return liveQuery(async () => { + return useLiveQueryWithDefault(async () => { const locals = await db.table('invLocations').toArray(); return locals.filter((l) => !l.deletedAt).map(toLocation); - }); + }, []); } export function useAllCategories() { - return liveQuery(async () => { + return useLiveQueryWithDefault(async () => { const locals = await db.table('invCategories').toArray(); return locals.filter((c) => !c.deletedAt).map(toCategory); - }); + }, []); } // ─── Pure Collection Helpers ────────────────────────────── diff --git a/apps/mana/apps/web/src/lib/modules/inventory/views/DetailView.svelte b/apps/mana/apps/web/src/lib/modules/inventory/views/DetailView.svelte index 7e1330384..481a56460 100644 --- a/apps/mana/apps/web/src/lib/modules/inventory/views/DetailView.svelte +++ b/apps/mana/apps/web/src/lib/modules/inventory/views/DetailView.svelte @@ -3,7 +3,7 @@ Collection details, always editable, auto-save on blur. -->