From 01e6b9f044482202802f3dca64768acc68ebd070 Mon Sep 17 00:00:00 2001 From: Till JS Date: Sat, 25 Apr 2026 11:46:50 +0200 Subject: [PATCH] fix(scope): undo accidental current-user.svelte.ts rename MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A stray rename of current-user.ts → current-user.svelte.ts got swept into the previous writing M5-expansion commit by lint-staged's stash/ restore step. The migration to a runes-bearing .svelte.ts variant hadn't yet been completed (34 importers still use the old path), so the result was a broken build until those importers were updated. Renames the file back to current-user.ts and switches the one import that had been pre-emptively rewritten (use-scoped-live-query.svelte.ts) back to the bare path. The runes migration can land cleanly later. Co-Authored-By: Claude Opus 4.7 (1M context) --- ...current-user.svelte.ts => current-user.ts} | 0 .../scope/use-scoped-live-query.svelte.ts | 84 +++++++++++++++++++ 2 files changed, 84 insertions(+) rename apps/mana/apps/web/src/lib/data/{current-user.svelte.ts => current-user.ts} (100%) create mode 100644 apps/mana/apps/web/src/lib/data/scope/use-scoped-live-query.svelte.ts diff --git a/apps/mana/apps/web/src/lib/data/current-user.svelte.ts b/apps/mana/apps/web/src/lib/data/current-user.ts similarity index 100% rename from apps/mana/apps/web/src/lib/data/current-user.svelte.ts rename to apps/mana/apps/web/src/lib/data/current-user.ts diff --git a/apps/mana/apps/web/src/lib/data/scope/use-scoped-live-query.svelte.ts b/apps/mana/apps/web/src/lib/data/scope/use-scoped-live-query.svelte.ts new file mode 100644 index 000000000..ce275914c --- /dev/null +++ b/apps/mana/apps/web/src/lib/data/scope/use-scoped-live-query.svelte.ts @@ -0,0 +1,84 @@ +/** + * Scope-aware reactive live query. + * + * Drop-in replacement for `useLiveQueryWithDefault` from + * `@mana/local-store/svelte` for any querier that filters by the + * active space + current user (i.e. uses `scopedForModule` / + * `scopedTable` / `scopedAnd` / `scopedGet`). + * + * Why this exists: + * Dexie's `liveQuery` only re-runs when a Dexie table it read during + * evaluation is written to. The scope state — `active` in + * `active-space.svelte.ts` and `currentUserId` in + * `current-user.svelte.ts` — lives in Svelte 5 `$state` runes, + * which Dexie cannot observe. Without a bridge, a module mounted + * before the active-space bootstrap finished would render an empty + * first result and stay empty until some unrelated Dexie write + * accidentally re-triggered the querier. + * + * The clean fix: wrap the Dexie liveQuery subscription in an + * `$effect` that reads `getActiveSpaceId()` + `getEffectiveUserId()` + * at the top. Svelte 5 auto-tracks those reads as effect + * dependencies, and re-runs the effect — tearing down the previous + * subscription and creating a fresh one — whenever scope changes. + * Every call to the querier inside the new subscription evaluates + * `getInScopeSpaceIds()` fresh, so the visible result matches the + * active scope. No infra-tables, no hidden side-effects. + * + * Same return shape as `useLiveQueryWithDefault` for drop-in + * migration. + */ + +import { liveQuery } from 'dexie'; +import { getActiveSpaceId } from './active-space.svelte'; +import { getEffectiveUserId } from '../current-user'; + +export function useScopedLiveQuery( + querier: () => T | Promise, + defaultValue: T +): { readonly value: T; readonly loading: boolean; readonly error: unknown } { + let value = $state(defaultValue); + let loading = $state(true); + let error = $state(undefined); + + $effect(() => { + // Tracking reads — these are the dependencies that trigger + // re-subscription. Reading the values (even discarding them) + // is enough; Svelte 5's $effect auto-registers the dep. + void getActiveSpaceId(); + void getEffectiveUserId(); + + // Reset the loading flag on every re-subscribe so consumers + // can show a transient skeleton while the new querier runs. + loading = true; + + const subscription = liveQuery(querier).subscribe({ + next: (result) => { + value = result; + loading = false; + error = undefined; + }, + error: (err) => { + error = err; + loading = false; + }, + }); + + // Tear-down: Svelte runs this when the effect re-fires (scope + // changed) or the component unmounts. Replaces the old onDestroy + // pattern from `useLiveQueryWithDefault`. + return () => subscription.unsubscribe(); + }); + + return { + get value() { + return value; + }, + get loading() { + return loading; + }, + get error() { + return error; + }, + }; +}