From c412508b9542f7b5d99944345e97b74a5dd303cb Mon Sep 17 00:00:00 2001 From: Till JS Date: Thu, 16 Apr 2026 15:06:11 +0200 Subject: [PATCH] feat(ai-scope): wire filterByScope into list_tasks/contacts/events + note tag UI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit All major list-returning auto-tools now filter by the ambient agent scope: list_notes (already done), list_tasks (via taskTagTable), get_contacts (via contactTagOps), get_todays_events (via eventTagOps). Untagged records pass through (globally visible); tagged records are only returned when at least one tag matches the agent's scopeTagIds. Notes detail view (/notes/[id]) grows a TagSelector widget between the content textarea and the color picker, powered by the new noteTagOps junction. Users can manually tag notes to scope them to specific agents — complements the AI's add_tag_to_note tool. Also: todo/stores/tags.svelte.ts created (taskLabelOps wrapper around the existing taskLabels junction for the scope filter). Co-Authored-By: Claude Opus 4.6 (1M context) --- .../web/src/lib/modules/calendar/tools.ts | 7 +++- .../web/src/lib/modules/contacts/tools.ts | 5 ++- .../lib/modules/todo/stores/tags.svelte.ts | 20 +++++++++++ .../apps/web/src/lib/modules/todo/tools.ts | 9 +++-- .../src/routes/(app)/notes/[id]/+page.svelte | 33 +++++++++++++++++++ 5 files changed, 70 insertions(+), 4 deletions(-) create mode 100644 apps/mana/apps/web/src/lib/modules/todo/stores/tags.svelte.ts diff --git a/apps/mana/apps/web/src/lib/modules/calendar/tools.ts b/apps/mana/apps/web/src/lib/modules/calendar/tools.ts index d1b74ae03..0687087c3 100644 --- a/apps/mana/apps/web/src/lib/modules/calendar/tools.ts +++ b/apps/mana/apps/web/src/lib/modules/calendar/tools.ts @@ -4,8 +4,10 @@ import type { ModuleTool } from '$lib/data/tools/types'; import { eventsStore } from './stores/events.svelte'; +import { eventTagOps } from './stores/tags.svelte'; import { db } from '$lib/data/database'; import { decryptRecords } from '$lib/data/crypto'; +import { filterByScope } from '$lib/data/ai/scope-context'; import type { LocalTimeBlock } from '$lib/data/time-blocks/types'; export const calendarTools: ModuleTool[] = [ @@ -65,7 +67,10 @@ export const calendarTools: ModuleTool[] = [ (b) => !b.deletedAt && b.type === 'event' && b.sourceModule === 'calendar' ); const decrypted = await decryptRecords('timeBlocks', eventBlocks); - const events = decrypted + const scoped = await filterByScope(decrypted, async (b) => + b.sourceId ? eventTagOps.getTagIds(b.sourceId) : [] + ); + const events = scoped .sort((a, b) => (a.startDate as string).localeCompare(b.startDate as string)) .map((b) => ({ id: b.sourceId, diff --git a/apps/mana/apps/web/src/lib/modules/contacts/tools.ts b/apps/mana/apps/web/src/lib/modules/contacts/tools.ts index 0b8e681d9..4d618463b 100644 --- a/apps/mana/apps/web/src/lib/modules/contacts/tools.ts +++ b/apps/mana/apps/web/src/lib/modules/contacts/tools.ts @@ -1,7 +1,9 @@ import type { ModuleTool } from '$lib/data/tools/types'; import { contactsStore } from './stores/contacts.svelte'; import { contactTable } from './collections'; +import { contactTagOps } from './stores/tags.svelte'; import { decryptRecords } from '$lib/data/crypto'; +import { filterByScope } from '$lib/data/ai/scope-context'; import { toContact } from './queries'; import type { LocalContact } from './types'; @@ -44,7 +46,8 @@ export const contactsTools: ModuleTool[] = [ const all = await contactTable.toArray(); const active = all.filter((c) => !c.deletedAt && !c.isArchived); const decrypted = await decryptRecords('contacts', active); - const contacts = decrypted.map(toContact); + const scoped = await filterByScope(decrypted, async (c) => contactTagOps.getTagIds(c.id)); + const contacts = scoped.map(toContact); return { success: true, data: contacts.map((c) => ({ diff --git a/apps/mana/apps/web/src/lib/modules/todo/stores/tags.svelte.ts b/apps/mana/apps/web/src/lib/modules/todo/stores/tags.svelte.ts new file mode 100644 index 000000000..3264ca0d3 --- /dev/null +++ b/apps/mana/apps/web/src/lib/modules/todo/stores/tags.svelte.ts @@ -0,0 +1,20 @@ +/** + * Todo Tags — Uses shared global tags via taskLabels junction table. + * Note: the junction uses 'labelId' (not 'tagId') for historical reasons. + */ + +import { db } from '$lib/data/database'; +import { createTagLinkOps } from '@mana/shared-stores'; + +export { + tagMutations, + useAllTags, + getTagById, + getTagsByIds, + getTagColor, +} from '@mana/shared-stores'; + +export const taskLabelOps = createTagLinkOps({ + table: () => db.table('taskLabels'), + entityIdField: 'taskId', +}); diff --git a/apps/mana/apps/web/src/lib/modules/todo/tools.ts b/apps/mana/apps/web/src/lib/modules/todo/tools.ts index 7d80a5ff7..d0e9d26f1 100644 --- a/apps/mana/apps/web/src/lib/modules/todo/tools.ts +++ b/apps/mana/apps/web/src/lib/modules/todo/tools.ts @@ -4,9 +4,10 @@ import type { ModuleTool } from '$lib/data/tools/types'; import { tasksStore } from './stores/tasks.svelte'; -import { taskTable } from './collections'; +import { taskTable, taskTagTable } from './collections'; import { toTask, getTaskStats } from './queries'; import { decryptRecords } from '$lib/data/crypto'; +import { filterByScope } from '$lib/data/ai/scope-context'; import type { LocalTask } from './types'; export const todoTools: ModuleTool[] = [ @@ -94,7 +95,11 @@ export const todoTools: ModuleTool[] = [ const all = await taskTable.toArray(); const active = all.filter((t) => !t.deletedAt); const decrypted = await decryptRecords('tasks', active); - const tasks = decrypted.map(toTask); + const scoped = await filterByScope(decrypted, async (t) => { + const links = await taskTagTable.where('taskId').equals(t.id).toArray(); + return links.filter((l) => !l.deletedAt).map((l) => l.tagId); + }); + const tasks = scoped.map(toTask); const filter = (params.filter as string) ?? 'open'; const today = new Date().toISOString().split('T')[0]; diff --git a/apps/mana/apps/web/src/routes/(app)/notes/[id]/+page.svelte b/apps/mana/apps/web/src/routes/(app)/notes/[id]/+page.svelte index 3134f79c6..d055de6c8 100644 --- a/apps/mana/apps/web/src/routes/(app)/notes/[id]/+page.svelte +++ b/apps/mana/apps/web/src/routes/(app)/notes/[id]/+page.svelte @@ -6,7 +6,9 @@ import type { Note } from '$lib/modules/notes/types'; import { NOTE_COLORS } from '$lib/modules/notes/types'; import { notesStore } from '$lib/modules/notes/stores/notes.svelte'; + import { noteTagOps, useAllTags } from '$lib/modules/notes/stores/tags.svelte'; import { formatRelativeTime } from '$lib/modules/notes/queries'; + import { TagSelector, type Tag } from '@mana/shared-ui'; const allNotes$: Observable = getContext('notes'); let notes = $state([]); @@ -18,6 +20,17 @@ let noteId = $derived($page.params.id); let note = $derived(notes.find((n) => n.id === noteId)); + const allTags = $derived(useAllTags()); + let noteTags = $state([]); + + // Sync tags when note changes + $effect(() => { + if (note && noteId) { + noteTagOps.getTagIds(noteId).then((ids: string[]) => { + noteTags = allTags.value.filter((t) => ids.includes(t.id)); + }); + } + }); let title = $state(''); let content = $state(''); @@ -123,6 +136,23 @@ oninput={autoSave} > + +
+ { + noteTags = tags; + await noteTagOps.setTags( + note.id, + tags.map((t) => t.id) + ); + }} + placeholder="Tags…" + addTagLabel="Tag hinzufügen" + /> +
+