feat(local-first): migrate tags + task stores to reactive liveQuery across all apps

- Todo: Replace manual fetch/state stores with useLiveQuery() for tasks,
  projects, and tags. Components use Svelte context instead of store imports.
  Stores reduced to mutation-only services. Removes ~200 lines of manual
  state management. Enables multi-tab sync and auto-refresh on data changes.

- Tags (all 16 apps): Migrate from API-based createTagStore() to shared
  local-first IndexedDB ('manacore-tags'). Tags now work offline and in
  guest mode with default seed data. All apps share the same tag DB via
  tagLocalStore + useAllTags() + setContext pattern.

- Cleanup: Delete unused Todo API files (projects.ts, labels.ts,
  reminders.ts), remove dead labels store, clean up barrel exports.

Apps migrated: Todo, Zitare, Questions, Planta, Clock, Presi, Mukke,
Context, CityCorners, ManaDeck, Chat, Contacts, Calendar, Picture,
Storage, Photos

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Till JS 2026-03-28 02:02:52 +01:00
parent 32939fbfb5
commit 5c33962439
83 changed files with 1896 additions and 3937 deletions

View file

@ -1,20 +1,13 @@
/**
* Tag Store - Uses shared createTagStore backed by central mana-core-auth
* Tag Store Local-First via Shared Tag Store
* Tags are stored in shared IndexedDB ('manacore-tags'), accessible across all apps.
* Use context ('tags') for reads, tagMutations for writes.
*/
import { browser } from '$app/environment';
import { createTagStore } from '@manacore/shared-stores';
import { authStore } from '$lib/stores/auth.svelte';
function getAuthUrl(): string {
if (browser && typeof window !== 'undefined') {
const injectedUrl = (window as unknown as { __PUBLIC_MANA_CORE_AUTH_URL__?: string })
.__PUBLIC_MANA_CORE_AUTH_URL__;
return injectedUrl || 'http://localhost:3001';
}
return 'http://localhost:3001';
}
export const tagStore = createTagStore({
authUrl: getAuthUrl(),
getToken: () => authStore.getValidToken(),
});
export {
tagMutations,
useAllTags,
getTagById,
getTagsByIds,
getTagColor,
getTagsByGroup,
} from '@manacore/shared-stores';

View file

@ -1,7 +1,7 @@
<script lang="ts">
import { goto } from '$app/navigation';
import { page } from '$app/stores';
import { onMount } from 'svelte';
import { onMount, setContext } from 'svelte';
import { locale } from 'svelte-i18n';
import { PillNavigation, CommandBar, TagStrip } from '@manacore/shared-ui';
import type {
@ -31,10 +31,17 @@
import { AuthGate, GuestWelcomeModal } from '@manacore/shared-auth-ui';
import { shouldShowGuestWelcome } from '@manacore/shared-auth-ui';
import { contextStore } from '$lib/data/local-store';
import { tagStore } from '$lib/stores/tags.svelte';
import {
tagLocalStore,
tagMutations,
useAllTags as useAllSharedTags,
} from '@manacore/shared-stores';
const appItems = getPillAppItems('context');
const allTags = useAllSharedTags();
setContext('tags', allTags);
let { children } = $props();
let commandBarOpen = $state(false);
@ -218,9 +225,11 @@
let showGuestWelcome = $state(false);
async function handleAuthReady() {
await contextStore.initialize();
await Promise.all([contextStore.initialize(), tagLocalStore.initialize()]);
if (authStore.isAuthenticated) {
contextStore.startSync(() => authStore.getValidToken());
const getToken = () => authStore.getValidToken();
contextStore.startSync(getToken);
tagMutations.startSync(getToken);
}
if (!authStore.isAuthenticated && shouldShowGuestWelcome('context')) {
showGuestWelcome = true;
@ -234,7 +243,6 @@
if (authStore.isAuthenticated) {
await userSettings.load();
await tagStore.fetchTags();
await Promise.all([spacesStore.load(), documentsStore.load()]);
}
}
@ -280,7 +288,7 @@
<!-- TagStrip (above PillNav, toggled via Tags pill) -->
{#if isTagStripVisible}
<TagStrip
tags={tagStore.tags.map((t) => ({
tags={allTags.value.map((t) => ({
id: t.id,
name: t.name,
color: t.color || '#3b82f6',
@ -289,7 +297,6 @@
onToggle={() => {}}
onClear={() => {}}
managementHref="/tags"
loading={tagStore.loading}
/>
{/if}

View file

@ -1,12 +1,9 @@
<script lang="ts">
import { tagStore } from '$lib/stores/tags.svelte';
import { onMount } from 'svelte';
import { getContext } from 'svelte';
import { tagMutations } from '@manacore/shared-stores';
import type { Tag } from '@manacore/shared-tags';
onMount(() => {
if (tagStore.tags.length === 0) {
tagStore.fetchTags();
}
});
const tagsCtx: { readonly value: Tag[] } = getContext('tags');
</script>
<svelte:head>
@ -19,13 +16,11 @@
Tags sind app-übergreifend — Änderungen gelten in allen ManaCore-Apps.
</p>
{#if tagStore.loading}
<p>Lädt...</p>
{:else if tagStore.tags.length === 0}
{#if tagsCtx.value.length === 0}
<p>Keine Tags vorhanden.</p>
{:else}
<div class="grid gap-2">
{#each tagStore.tags as tag}
{#each tagsCtx.value as tag}
<div class="flex items-center gap-2 p-2 rounded-lg bg-card">
<span class="w-3 h-3 rounded-full" style="background-color: {tag.color}"></span>
<span>{tag.name}</span>