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>
This commit is contained in:
Till JS 2026-04-09 18:12:15 +02:00
parent 0426b6677b
commit ff6118fc3b
16 changed files with 82 additions and 74 deletions

View file

@ -7,7 +7,7 @@
* to the public types so consumers see plaintext.
*/
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 {
@ -68,18 +68,18 @@ export function toMessage(local: LocalMessage): Message {
/** All non-archived conversations, sorted by pinned first then updatedAt desc. */
export function useAllConversations() {
return liveQuery(async () => {
return useLiveQueryWithDefault(async () => {
const visible = (await db.table<LocalConversation>('conversations').toArray()).filter(
(c) => !c.deletedAt && !c.isArchived
);
const decrypted = await decryptRecords('conversations', visible);
return sortConversations(decrypted.map(toConversation));
});
}, [] as Conversation[]);
}
/** All archived conversations, sorted by updatedAt desc. */
export function useArchivedConversations() {
return liveQuery(async () => {
return useLiveQueryWithDefault(async () => {
const visible = (await db.table<LocalConversation>('conversations').toArray()).filter(
(c) => !c.deletedAt && c.isArchived
);
@ -87,23 +87,23 @@ export function useArchivedConversations() {
return decrypted
.map(toConversation)
.sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime());
});
}, [] as Conversation[]);
}
/** All templates, sorted by name. */
export function useAllTemplates() {
return liveQuery(async () => {
return useLiveQueryWithDefault(async () => {
const visible = (await db.table<LocalTemplate>('chatTemplates').toArray()).filter(
(t) => !t.deletedAt
);
const decrypted = await decryptRecords('chatTemplates', visible);
return decrypted.map(toTemplate).sort((a, b) => a.name.localeCompare(b.name));
});
}, [] as Template[]);
}
/** Messages for a specific conversation, sorted by createdAt asc. */
export function useConversationMessages(conversationId: string) {
return liveQuery(async () => {
return useLiveQueryWithDefault(async () => {
const visible = (
await db
.table<LocalMessage>('messages')
@ -115,7 +115,7 @@ export function useConversationMessages(conversationId: string) {
return decrypted
.map(toMessage)
.sort((a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime());
});
}, [] as Message[]);
}
// ─── Pure Sort / Filter Functions (for $derived) ───────────