From 30e124e609eeec9b21903d0a5f3ad504781b351f Mon Sep 17 00:00:00 2001 From: Till JS Date: Sat, 28 Mar 2026 02:27:46 +0100 Subject: [PATCH] feat(local-first): migrate 9 apps to reactive useLiveQuery reads Replace manual $state + fetchX() pattern with Dexie liveQuery hooks across 9 apps. All data reads now auto-update on IndexedDB changes (local writes, sync, other tabs). Stores reduced to mutation-only. Apps migrated: - Zitare: favorites, lists - Contacts: contacts - Calendar: calendars, events - Chat: conversations, templates - Clock: alarms, timers, worldClocks - ManaDeck: decks, cards - Presi: decks, slides - Context: spaces, documents - Storage: files, folders Pattern per app: 1. New queries.ts with useLiveQuery hooks + pure filter helpers 2. Stores slimmed to mutation-only (no $state arrays, no fetch methods) 3. Layout sets context via setContext() for child components 4. Components use getContext() for reactive reads Co-Authored-By: Claude Opus 4.6 (1M context) --- .../lib/components/agenda/AgendaItem.svelte | 12 +- .../lib/components/calendar/AgendaView.svelte | 30 +- .../calendar/CalendarSidebar.svelte | 11 +- .../lib/components/calendar/DateStrip.svelte | 9 +- .../lib/components/calendar/MonthView.svelte | 41 +- .../calendar/PillCalendarSelector.svelte | 15 +- .../lib/components/calendar/WeekView.svelte | 86 +++-- .../components/event/EventDetailModal.svelte | 13 +- .../src/lib/components/event/EventForm.svelte | 15 +- .../components/event/QuickEventOverlay.svelte | 21 +- .../components/settings/SettingsModal.svelte | 14 +- .../calendar/apps/web/src/lib/data/queries.ts | 220 +++++++++++ .../web/src/lib/stores/calendars.svelte.ts | 153 +------- .../apps/web/src/lib/stores/events.svelte.ts | 240 +----------- .../apps/web/src/routes/(app)/+layout.svelte | 26 +- .../apps/web/src/routes/(app)/+page.svelte | 44 +-- .../web/src/routes/(app)/mana/+page.svelte | 2 +- .../src/routes/(app)/settings/+page.svelte | 14 +- .../(app)/settings/sharing/+page.svelte | 12 +- .../src/lib/components/chat/ChatLayout.svelte | 37 +- apps/chat/apps/web/src/lib/data/queries.ts | 134 +++++++ .../src/lib/stores/conversations.svelte.ts | 261 +++---------- .../web/src/lib/stores/templates.svelte.ts | 97 ++--- .../web/src/routes/(protected)/+layout.svelte | 16 +- .../routes/(protected)/archive/+page.svelte | 29 +- .../src/routes/(protected)/chat/+page.svelte | 18 +- .../src/routes/(protected)/mana/+page.svelte | 2 +- .../routes/(protected)/spaces/+page.svelte | 1 - .../(protected)/spaces/[id]/+page.svelte | 14 +- .../routes/(protected)/templates/+page.svelte | 31 +- apps/clock/apps/web/src/lib/data/queries.ts | 106 ++++++ .../apps/web/src/lib/stores/alarms.svelte.ts | 110 +----- .../apps/web/src/lib/stores/timers.svelte.ts | 133 ++----- .../web/src/lib/stores/world-clocks.svelte.ts | 93 +---- .../apps/web/src/routes/(app)/+layout.svelte | 70 ++-- .../web/src/routes/(app)/alarms/+page.svelte | 123 +++--- .../web/src/routes/(app)/timers/+page.svelte | 22 +- .../src/routes/(app)/world-clock/+page.svelte | 44 +-- .../lib/components/ContactDetailModal.svelte | 19 - .../web/src/lib/components/ContactList.svelte | 225 +---------- .../src/lib/components/NewContactModal.svelte | 4 +- .../lib/components/import/GoogleImport.svelte | 5 +- .../contacts/apps/web/src/lib/data/queries.ts | 87 +++++ .../web/src/lib/stores/contacts.svelte.ts | 352 +++--------------- .../apps/web/src/routes/(app)/+layout.svelte | 16 +- .../web/src/routes/(app)/data/+page.svelte | 4 +- .../web/src/routes/(app)/mana/+page.svelte | 2 +- .../web/src/routes/(app)/spiral/+page.svelte | 23 +- .../src/lib/components/MentionInput.svelte | 6 +- apps/context/apps/web/src/lib/data/queries.ts | 147 ++++++++ .../web/src/lib/stores/documents.svelte.ts | 195 ++++------ .../apps/web/src/lib/stores/spaces.svelte.ts | 95 +++-- .../apps/web/src/routes/(app)/+layout.svelte | 10 +- .../apps/web/src/routes/(app)/+page.svelte | 194 +++++----- .../src/routes/(app)/documents/+page.svelte | 44 ++- .../routes/(app)/documents/[id]/+page.svelte | 34 +- .../web/src/routes/(app)/spaces/+page.svelte | 16 +- .../src/routes/(app)/spaces/[id]/+page.svelte | 50 ++- .../manadeck/apps/web/src/lib/data/queries.ts | 90 +++++ .../web/src/lib/stores/cardStore.svelte.ts | 155 +------- .../web/src/lib/stores/deckStore.svelte.ts | 130 +------ .../apps/web/src/routes/(app)/+layout.svelte | 12 +- .../web/src/routes/(app)/decks/+page.svelte | 30 +- .../src/routes/(app)/decks/[id]/+page.svelte | 33 +- apps/presi/apps/web/src/lib/data/queries.ts | 73 ++++ .../apps/web/src/lib/stores/decks.svelte.ts | 129 +------ .../apps/web/src/routes/(app)/+layout.svelte | 9 +- .../apps/web/src/routes/(app)/+page.svelte | 18 +- .../src/routes/(app)/deck/[id]/+page.svelte | 32 +- .../web/src/routes/(app)/mana/+page.svelte | 2 +- .../routes/(app)/present/[id]/+page.svelte | 37 +- .../web/src/routes/(app)/profile/+page.svelte | 131 +++---- .../lib/components/files/BulkActionBar.svelte | 14 +- apps/storage/apps/web/src/lib/data/queries.ts | 135 +++++++ .../apps/web/src/lib/stores/files.svelte.ts | 172 +++++---- .../apps/web/src/routes/files/+page.svelte | 49 +-- .../src/routes/files/[folderId]/+page.svelte | 55 +-- .../apps/web/src/routes/login/+page.svelte | 1 + .../web/src/lib/components/QuoteCard.svelte | 7 +- apps/zitare/apps/web/src/lib/data/queries.ts | 91 +++++ .../web/src/lib/stores/favorites.svelte.ts | 99 +---- .../apps/web/src/lib/stores/lists.svelte.ts | 210 ++++------- .../apps/web/src/routes/(app)/+layout.svelte | 13 +- .../src/routes/(app)/favorites/+page.svelte | 15 +- .../web/src/routes/(app)/lists/+page.svelte | 21 +- .../src/routes/(app)/lists/[id]/+page.svelte | 41 +- .../web/src/routes/(app)/spiral/+page.svelte | 13 +- 87 files changed, 2528 insertions(+), 3136 deletions(-) create mode 100644 apps/calendar/apps/web/src/lib/data/queries.ts create mode 100644 apps/chat/apps/web/src/lib/data/queries.ts create mode 100644 apps/clock/apps/web/src/lib/data/queries.ts create mode 100644 apps/contacts/apps/web/src/lib/data/queries.ts create mode 100644 apps/context/apps/web/src/lib/data/queries.ts create mode 100644 apps/manadeck/apps/web/src/lib/data/queries.ts create mode 100644 apps/presi/apps/web/src/lib/data/queries.ts create mode 100644 apps/storage/apps/web/src/lib/data/queries.ts create mode 100644 apps/zitare/apps/web/src/lib/data/queries.ts diff --git a/apps/calendar/apps/web/src/lib/components/agenda/AgendaItem.svelte b/apps/calendar/apps/web/src/lib/components/agenda/AgendaItem.svelte index 60ccf29db..0d157dd06 100644 --- a/apps/calendar/apps/web/src/lib/components/agenda/AgendaItem.svelte +++ b/apps/calendar/apps/web/src/lib/components/agenda/AgendaItem.svelte @@ -1,8 +1,9 @@
@@ -123,7 +128,7 @@
- {#each calendarsStore.calendars as calendar} + {#each calendarsCtx.value as calendar}
diff --git a/apps/calendar/apps/web/src/lib/components/calendar/WeekView.svelte b/apps/calendar/apps/web/src/lib/components/calendar/WeekView.svelte index fbcf07539..54aacf4d3 100644 --- a/apps/calendar/apps/web/src/lib/components/calendar/WeekView.svelte +++ b/apps/calendar/apps/web/src/lib/components/calendar/WeekView.svelte @@ -1,9 +1,16 @@ @@ -208,11 +184,7 @@
- {#if !initialized} - - {:else} - - {/if} +
diff --git a/apps/calendar/apps/web/src/routes/(app)/mana/+page.svelte b/apps/calendar/apps/web/src/routes/(app)/mana/+page.svelte index 01449270b..5e4a7f873 100644 --- a/apps/calendar/apps/web/src/routes/(app)/mana/+page.svelte +++ b/apps/calendar/apps/web/src/routes/(app)/mana/+page.svelte @@ -24,7 +24,7 @@ pageTitle="Wähle dein Abo" subscriptionsTitle="Abonnements" packagesTitle="Einmal-Pakete" - yearlyDiscount="2 Monate gratis" + yearlyDiscount="20% Rabatt" /> diff --git a/apps/calendar/apps/web/src/routes/(app)/settings/+page.svelte b/apps/calendar/apps/web/src/routes/(app)/settings/+page.svelte index db7e702af..60efdb364 100644 --- a/apps/calendar/apps/web/src/routes/(app)/settings/+page.svelte +++ b/apps/calendar/apps/web/src/routes/(app)/settings/+page.svelte @@ -6,7 +6,9 @@ import { userSettings } from '$lib/stores/user-settings.svelte'; import { settingsStore } from '$lib/stores/settings.svelte'; import type { TimeFormat, AllDayDisplayMode } from '$lib/stores/settings.svelte'; + import { getContext } from 'svelte'; import { calendarsStore } from '$lib/stores/calendars.svelte'; + import { getDefaultCalendar } from '$lib/data/queries'; import { toastStore as toast, GlobalSettingsSection, @@ -18,6 +20,9 @@ import { APP_VERSION } from '$lib/version'; import type { CalendarViewType, Calendar } from '@calendar/shared'; + // Get calendars from layout context (live query) + const calendarsCtx: { readonly value: Calendar[] } = getContext('calendars'); + // Calendar management state let editingCalendar = $state(null); let editName = $state(''); @@ -89,7 +94,10 @@ // If setting as default and it wasn't before, use setAsDefault if (editIsDefault && !editingCalendar.isDefault) { - const defaultResult = await calendarsStore.setAsDefault(editingCalendar.id); + const defaultResult = await calendarsStore.setAsDefault( + editingCalendar.id, + calendarsCtx.value + ); if (defaultResult?.error) { toast.error(`${$_('common.error')}: ${defaultResult.error.message}`); return; @@ -264,7 +272,7 @@ {/if}
- {#each calendarsStore.calendars as calendar} + {#each calendarsCtx.value as calendar} {#if editingCalendar?.id === calendar.id}

{$_('settings.noCalendars')}

diff --git a/apps/calendar/apps/web/src/routes/(app)/settings/sharing/+page.svelte b/apps/calendar/apps/web/src/routes/(app)/settings/sharing/+page.svelte index 67a48276d..674ef07f2 100644 --- a/apps/calendar/apps/web/src/routes/(app)/settings/sharing/+page.svelte +++ b/apps/calendar/apps/web/src/routes/(app)/settings/sharing/+page.svelte @@ -3,7 +3,8 @@ import { goto } from '$app/navigation'; import { _ } from 'svelte-i18n'; import { authStore } from '$lib/stores/auth.svelte'; - import { calendarsStore } from '$lib/stores/calendars.svelte'; + import { getContext } from 'svelte'; + import type { Calendar } from '@calendar/shared'; import { sharesStore } from '$lib/stores/shares.svelte'; import { CaretLeft, @@ -19,6 +20,9 @@ import { Modal, Input } from '@manacore/shared-ui'; import { PERMISSION_DESCRIPTIONS, type SharePermission } from '@calendar/shared'; + // Get calendars from layout context (live query) + const calendarsCtx: { readonly value: Calendar[] } = getContext('calendars'); + // Share form state let showShareForm = $state(false); let selectedCalendarId = $state(''); @@ -50,7 +54,7 @@ return; } await Promise.all([ - calendarsStore.calendars.length === 0 ? calendarsStore.fetchCalendars() : Promise.resolve(), + Promise.resolve(), // Calendars loaded via live query sharesStore.fetchInvitations(), sharesStore.fetchSharedWithMe(), ]); @@ -137,7 +141,7 @@ {$_('sharing.shareMyCalendars')} - {#each calendarsStore.calendars as calendar (calendar.id)} + {#each calendarsCtx.value as calendar (calendar.id)}
{:else} - - {#if contactsStore.selfContact} - {@const self = contactsStore.selfContact} - - {/if} - {#if viewModeStore.mode === 'grid'} {/if} - - {#if contactsStore.hasMore} -
- {#if contactsStore.loadingMore} -
-
- {$_('common.loadingMore')} -
- {/if} -
- {/if} -

- {contactsStore.contacts.length} / {contactsStore.total} - {contactsStore.total === 1 ? $_('contacts.contact') : $_('contacts.contactsPlural')} + {contacts.length} + {contacts.length === 1 ? $_('contacts.contact') : $_('contacts.contactsPlural')}

{/if}
diff --git a/apps/contacts/apps/web/src/lib/components/NewContactModal.svelte b/apps/contacts/apps/web/src/lib/components/NewContactModal.svelte index 3372fffb4..989c9d312 100644 --- a/apps/contacts/apps/web/src/lib/components/NewContactModal.svelte +++ b/apps/contacts/apps/web/src/lib/components/NewContactModal.svelte @@ -1,7 +1,6 @@ @@ -183,9 +184,9 @@