From 26e1c4774faf76281f8b1d2c1674da4e182a9b6a Mon Sep 17 00:00:00 2001 From: Till JS Date: Thu, 16 Apr 2026 15:22:47 +0200 Subject: [PATCH] feat(scene-scope): wire filterBySceneScope into notes/todo/contacts/calendar queries MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The four modules with tag junctions now filter their main useAll* queries through filterBySceneScope: when a scene has scopeTagIds set, only records tagged with at least one matching tag (+ untagged records) appear in the UI. Modules without tag junctions (drink, food, habits, journal, dreams, places) are unaffected — their records are always globally visible. - useAllNotes → noteTagOps.getTagIds - useAllTasks → taskTagTable junction lookup - useAllContacts → contactTagOps.getTagIds - useAllCalendarItems → eventTagOps.getTagIds (calendar-sourced blocks only; task/habit blocks pass through unfiltered) When no scene scope is active (scopeTagIds undefined), filterBySceneScope is a no-op identity pass — zero overhead for unscoped scenes. Co-Authored-By: Claude Opus 4.6 (1M context) --- apps/mana/apps/web/src/lib/modules/calendar/queries.ts | 9 ++++++++- apps/mana/apps/web/src/lib/modules/contacts/queries.ts | 5 ++++- apps/mana/apps/web/src/lib/modules/notes/queries.ts | 5 ++++- apps/mana/apps/web/src/lib/modules/todo/queries.ts | 8 +++++++- 4 files changed, 23 insertions(+), 4 deletions(-) diff --git a/apps/mana/apps/web/src/lib/modules/calendar/queries.ts b/apps/mana/apps/web/src/lib/modules/calendar/queries.ts index dee6ddb17..cf47d5062 100644 --- a/apps/mana/apps/web/src/lib/modules/calendar/queries.ts +++ b/apps/mana/apps/web/src/lib/modules/calendar/queries.ts @@ -13,6 +13,8 @@ import { useLiveQueryWithDefault } from '@mana/local-store/svelte'; import { db } from '$lib/data/database'; import { decryptRecords } from '$lib/data/crypto'; +import { filterBySceneScope } from '$lib/stores/scene-scope.svelte'; +import { eventTagOps } from './stores/tags.svelte'; import type { LocalCalendar, LocalEvent, Calendar, CalendarEvent } from './types'; import { timeBlockToCalendarEvent } from './types'; import type { LocalTimeBlock } from '$lib/data/time-blocks/types'; @@ -65,8 +67,13 @@ export function useAllCalendarItems() { eventsById.set(e.id, e); } + // Scene scope filter: only calendar-sourced blocks are tag-filterable. + const scopedBlocks = await filterBySceneScope(decryptedBlocks, async (b) => + b.sourceModule === 'calendar' && b.sourceId ? eventTagOps.getTagIds(b.sourceId) : [] + ); + // Convert to CalendarEvent, joining event data for calendar blocks - return decryptedBlocks.map((block) => { + return scopedBlocks.map((block) => { const tb = toTimeBlock(block); const eventData = block.sourceModule === 'calendar' ? (eventsById.get(block.sourceId) ?? null) : null; 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 fd1f59b04..68a6a6751 100644 --- a/apps/mana/apps/web/src/lib/modules/contacts/queries.ts +++ b/apps/mana/apps/web/src/lib/modules/contacts/queries.ts @@ -5,6 +5,8 @@ import { useLiveQueryWithDefault } from '@mana/local-store/svelte'; import { db } from '$lib/data/database'; import { decryptRecords } from '$lib/data/crypto'; +import { filterBySceneScope } from '$lib/stores/scene-scope.svelte'; +import { contactTagOps } from './stores/tags.svelte'; import type { LocalContact, Contact, SortField, ContactFilter } from './types'; // ─── Type Converter ─────────────────────────────────────── @@ -55,7 +57,8 @@ export function useAllContacts() { (c) => !c.deletedAt ); const decrypted = await decryptRecords('contacts', visible); - return decrypted.map(toContact); + const scoped = await filterBySceneScope(decrypted, (c) => contactTagOps.getTagIds(c.id)); + return scoped.map(toContact); }, []); } diff --git a/apps/mana/apps/web/src/lib/modules/notes/queries.ts b/apps/mana/apps/web/src/lib/modules/notes/queries.ts index b11e44405..17fb9cf75 100644 --- a/apps/mana/apps/web/src/lib/modules/notes/queries.ts +++ b/apps/mana/apps/web/src/lib/modules/notes/queries.ts @@ -17,6 +17,8 @@ import { useLiveQueryWithDefault } from '@mana/local-store/svelte'; import { db } from '$lib/data/database'; import { decryptRecords } from '$lib/data/crypto'; +import { filterBySceneScope } from '$lib/stores/scene-scope.svelte'; +import { noteTagOps } from './stores/tags.svelte'; import type { LocalNote, Note } from './types'; // ─── Type Converters ─────────────────────────────────────── @@ -49,7 +51,8 @@ export function useAllNotes() { // Locked vault returns the blobs untouched so the UI can render // a "🔒" placeholder where title/content would be. const decrypted = await decryptRecords('notes', visible); - return decrypted.map(toNote).sort((a, b) => { + const scoped = await filterBySceneScope(decrypted, (n) => noteTagOps.getTagIds(n.id)); + return scoped.map(toNote).sort((a, b) => { if (a.isPinned !== b.isPinned) return a.isPinned ? -1 : 1; return b.updatedAt.localeCompare(a.updatedAt); }); diff --git a/apps/mana/apps/web/src/lib/modules/todo/queries.ts b/apps/mana/apps/web/src/lib/modules/todo/queries.ts index 4b58249b4..671515a63 100644 --- a/apps/mana/apps/web/src/lib/modules/todo/queries.ts +++ b/apps/mana/apps/web/src/lib/modules/todo/queries.ts @@ -5,6 +5,8 @@ import { useLiveQueryWithDefault } from '@mana/local-store/svelte'; import { db } from '$lib/data/database'; import { decryptRecords } from '$lib/data/crypto'; +import { filterBySceneScope } from '$lib/stores/scene-scope.svelte'; +import { taskTagTable } from './collections'; import type { LocalTask, LocalBoardView, @@ -46,7 +48,11 @@ export function useAllTasks() { const locals = await db.table('tasks').orderBy('order').toArray(); const visible = locals.filter((t) => !t.deletedAt); const decrypted = await decryptRecords('tasks', visible); - return decrypted.map(toTask); + const scoped = await filterBySceneScope(decrypted, async (t) => { + const links = await taskTagTable.where('taskId').equals(t.id).toArray(); + return links.filter((l) => !l.deletedAt).map((l) => l.tagId); + }); + return scoped.map(toTask); }, [] as Task[]); }