diff --git a/apps/mana/apps/web/src/lib/components/workbench/ScopeEmptyState.svelte b/apps/mana/apps/web/src/lib/components/workbench/ScopeEmptyState.svelte new file mode 100644 index 000000000..1c5fc72ce --- /dev/null +++ b/apps/mana/apps/web/src/lib/components/workbench/ScopeEmptyState.svelte @@ -0,0 +1,82 @@ + + + +
+ +

{primaryLine}

+ +
+ + diff --git a/apps/mana/apps/web/src/lib/modules/calendar/ListView.svelte b/apps/mana/apps/web/src/lib/modules/calendar/ListView.svelte index f1e3fbc62..5c5885b72 100644 --- a/apps/mana/apps/web/src/lib/modules/calendar/ListView.svelte +++ b/apps/mana/apps/web/src/lib/modules/calendar/ListView.svelte @@ -17,6 +17,8 @@ import { useItemContextMenu } from '$lib/data/item-context-menu.svelte'; import { transcribeAudio } from '$lib/voice/transcribe'; import FloatingInputBar from '$lib/components/FloatingInputBar.svelte'; + import ScopeEmptyState from '$lib/components/workbench/ScopeEmptyState.svelte'; + import { hasActiveSceneScope } from '$lib/stores/scene-scope.svelte'; let { navigate, goBack, params }: ViewProps = $props(); @@ -219,7 +221,11 @@ {/each} {#if upcomingEvents.length === 0} -

Keine Termine

+ {#if hasActiveSceneScope()} + + {:else} +

Keine Termine

+ {/if} {/if} diff --git a/apps/mana/apps/web/src/lib/modules/contacts/ListView.svelte b/apps/mana/apps/web/src/lib/modules/contacts/ListView.svelte index 6eb025950..ec8223060 100644 --- a/apps/mana/apps/web/src/lib/modules/contacts/ListView.svelte +++ b/apps/mana/apps/web/src/lib/modules/contacts/ListView.svelte @@ -17,6 +17,8 @@ import { useAllTags, getTagsByIds } from '@mana/shared-stores'; import { addTagId } from '$lib/data/tag-mutations'; import { useItemContextMenu } from '$lib/data/item-context-menu.svelte'; + import ScopeEmptyState from '$lib/components/workbench/ScopeEmptyState.svelte'; + import { hasActiveSceneScope } from '$lib/stores/scene-scope.svelte'; let { navigate, goBack, params }: ViewProps = $props(); @@ -175,7 +177,11 @@ {/each} {#if filtered().length === 0} -

Keine Kontakte gefunden

+ {#if hasActiveSceneScope()} + + {:else} +

Keine Kontakte gefunden

+ {/if} {/if} diff --git a/apps/mana/apps/web/src/lib/modules/notes/ListView.svelte b/apps/mana/apps/web/src/lib/modules/notes/ListView.svelte index 5594fcfbf..ff5f209ee 100644 --- a/apps/mana/apps/web/src/lib/modules/notes/ListView.svelte +++ b/apps/mana/apps/web/src/lib/modules/notes/ListView.svelte @@ -13,6 +13,8 @@ import { PencilSimple, Trash, PushPin } from '@mana/shared-icons'; import FloatingInputBar from '$lib/components/FloatingInputBar.svelte'; import AgentDot from '$lib/components/ai/AgentDot.svelte'; + import ScopeEmptyState from '$lib/components/workbench/ScopeEmptyState.svelte'; + import { hasActiveSceneScope } from '$lib/stores/scene-scope.svelte'; let { navigate, goBack, params }: ViewProps = $props(); @@ -212,7 +214,11 @@ {#if notes.length === 0} -

Erstelle deine erste Notiz.

+ {#if hasActiveSceneScope()} + + {:else} +

Erstelle deine erste Notiz.

+ {/if} {/if} Keine Aufgaben

+ {#if hasActiveSceneScope()} + + {:else} +

Keine Aufgaben

+ {/if} {/if} diff --git a/apps/mana/apps/web/src/lib/stores/scene-scope.svelte.ts b/apps/mana/apps/web/src/lib/stores/scene-scope.svelte.ts index dd986d469..f80c5d3a7 100644 --- a/apps/mana/apps/web/src/lib/stores/scene-scope.svelte.ts +++ b/apps/mana/apps/web/src/lib/stores/scene-scope.svelte.ts @@ -20,6 +20,14 @@ export function getSceneScopeTagIds(): readonly string[] | undefined { return _scopeTagIds; } +/** + * True when the active scene is filtering by at least one scope tag. + * Reactive — safe to read inside a $derived or template. + */ +export function hasActiveSceneScope(): boolean { + return !!_scopeTagIds?.length; +} + /** * Batch filter using a pre-fetched tag map. Preferred for list queries * (1 Dexie call instead of N). diff --git a/apps/mana/apps/web/src/lib/stores/scene-scope.test.ts b/apps/mana/apps/web/src/lib/stores/scene-scope.test.ts new file mode 100644 index 000000000..d9c4745be --- /dev/null +++ b/apps/mana/apps/web/src/lib/stores/scene-scope.test.ts @@ -0,0 +1,44 @@ +/** + * Unit tests for the reactive scope-tag primitives backing the workbench + * scene scope. These live in the module scope of scene-scope.svelte.ts + * and are shared between AI scope-context, ListView empty states, and + * module queries via filterBySceneScopeBatch. + */ + +import { afterEach, describe, expect, it } from 'vitest'; +import { + setSceneScopeTagIds, + getSceneScopeTagIds, + hasActiveSceneScope, +} from './scene-scope.svelte'; + +afterEach(() => { + setSceneScopeTagIds(undefined); +}); + +describe('scene scope state', () => { + it('starts empty', () => { + expect(getSceneScopeTagIds()).toBeUndefined(); + expect(hasActiveSceneScope()).toBe(false); + }); + + it('setSceneScopeTagIds populates the state', () => { + setSceneScopeTagIds(['tag-1', 'tag-2']); + expect(getSceneScopeTagIds()).toEqual(['tag-1', 'tag-2']); + expect(hasActiveSceneScope()).toBe(true); + }); + + it('empty array is normalized to undefined', () => { + setSceneScopeTagIds([]); + expect(getSceneScopeTagIds()).toBeUndefined(); + expect(hasActiveSceneScope()).toBe(false); + }); + + it('explicit undefined clears any active scope', () => { + setSceneScopeTagIds(['tag-1']); + expect(hasActiveSceneScope()).toBe(true); + setSceneScopeTagIds(undefined); + expect(hasActiveSceneScope()).toBe(false); + expect(getSceneScopeTagIds()).toBeUndefined(); + }); +});