mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 22:21:10 +02:00
feat(workbench): Scene.scopeTagIds + reactive scene-scope store
WorkbenchScene grows an optional scopeTagIds field so scenes can act
as data-scope lenses: when set, module queries filter records to those
tagged with at least one of the scene's tags (+ untagged = global).
New reactive store scene-scope.svelte.ts:
- setSceneScopeTagIds(ids) — called by scene store on switch/init
- getSceneScopeTagIds() — read by module queries
- filterBySceneScope() — reusable filter (same semantics as
AI scope-context's filterByScope)
Wired into workbench-scenes.svelte.ts:
- setActiveScene() syncs scope on manual switch
- liveQuery subscription syncs scope on init/sync/tab-focus
Module queries can now opt into scene scoping by calling
filterBySceneScope in their useAll* hooks — not wired yet (each
module opts in as needed). The foundation is in place.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
fad7f4bea3
commit
0ddaab53e4
3 changed files with 70 additions and 0 deletions
54
apps/mana/apps/web/src/lib/stores/scene-scope.svelte.ts
Normal file
54
apps/mana/apps/web/src/lib/stores/scene-scope.svelte.ts
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
/**
|
||||
* Reactive scene scope — propagates the active scene's scopeTagIds to
|
||||
* module queries so the UI can filter records by the scene's tags.
|
||||
*
|
||||
* Updated by the workbench layout whenever the active scene changes.
|
||||
* Module queries read `currentScopeTagIds` and filter accordingly.
|
||||
* Undefined = no filtering (everything visible).
|
||||
*
|
||||
* This is the UI-facing counterpart of the AI-facing `withAgentScope`
|
||||
* in `data/ai/scope-context.ts`. Both use the same tag system; the AI
|
||||
* scope is ephemeral (duration of a mission run), this one is long-
|
||||
* lived (persists as long as the scene is active).
|
||||
*/
|
||||
|
||||
/** Reactive scope state — set by workbench layout, read by module queries. */
|
||||
let _scopeTagIds = $state<readonly string[] | undefined>(undefined);
|
||||
|
||||
/** Set the active scene's scope. Called from the workbench layout
|
||||
* when the active scene changes. Pass undefined to clear. */
|
||||
export function setSceneScopeTagIds(tagIds: readonly string[] | undefined): void {
|
||||
_scopeTagIds = tagIds?.length ? tagIds : undefined;
|
||||
}
|
||||
|
||||
/** Read the current scene scope. Returns undefined when no scope is
|
||||
* active (= show everything). Module queries use this to filter. */
|
||||
export function getSceneScopeTagIds(): readonly string[] | undefined {
|
||||
return _scopeTagIds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility: filter records by the active scene scope. Same semantics as
|
||||
* `filterByScope` in AI scope-context: untagged records pass through,
|
||||
* tagged records must match at least one scope tag.
|
||||
*
|
||||
* Synchronous if tagIds are already loaded; async variant for
|
||||
* junction-table lookups (same signature as the AI version).
|
||||
*/
|
||||
export async function filterBySceneScope<T>(
|
||||
records: T[],
|
||||
getTagIdsForRecord: (record: T) => Promise<string[]>
|
||||
): Promise<T[]> {
|
||||
const scope = _scopeTagIds;
|
||||
if (!scope) return records;
|
||||
|
||||
const scopeSet = new Set(scope);
|
||||
const results: T[] = [];
|
||||
for (const r of records) {
|
||||
const tagIds = await getTagIdsForRecord(r);
|
||||
if (tagIds.length === 0 || tagIds.some((id) => scopeSet.has(id))) {
|
||||
results.push(r);
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
|
@ -21,6 +21,7 @@ import type {
|
|||
WorkbenchScene,
|
||||
WorkbenchSceneApp,
|
||||
} from '$lib/types/workbench-scenes';
|
||||
import { setSceneScopeTagIds } from './scene-scope.svelte';
|
||||
|
||||
const TABLE = 'workbenchScenes';
|
||||
const ACTIVE_SCENE_LS_KEY = 'mana:workbench:activeSceneId';
|
||||
|
|
@ -163,6 +164,9 @@ export const workbenchScenesStore = {
|
|||
activeSceneIdState = next;
|
||||
writeActiveIdToStorage(next);
|
||||
}
|
||||
// Sync scope when scenes reload (init, sync pull, tab focus).
|
||||
const activeScope = visible.find((s) => s.id === (next ?? activeSceneIdState));
|
||||
setSceneScopeTagIds(activeScope?.scopeTagIds);
|
||||
initializedState = true;
|
||||
},
|
||||
error: (err) => {
|
||||
|
|
@ -183,6 +187,9 @@ export const workbenchScenesStore = {
|
|||
if (!scenesState.some((s) => s.id === id)) return;
|
||||
activeSceneIdState = id;
|
||||
writeActiveIdToStorage(id);
|
||||
// Sync scene scope for module queries
|
||||
const scene = scenesState.find((s) => s.id === id);
|
||||
setSceneScopeTagIds(scene?.scopeTagIds);
|
||||
},
|
||||
|
||||
async createScene(opts: {
|
||||
|
|
|
|||
|
|
@ -42,6 +42,15 @@ export interface WorkbenchScene {
|
|||
* settings. See docs/plans/multi-agent-workbench.md §Phase 5d.
|
||||
*/
|
||||
viewingAsAgentId?: string;
|
||||
/**
|
||||
* Tag-based data scope for this scene (Phase 6). When set, module
|
||||
* queries in apps rendered within this scene filter records to only
|
||||
* those tagged with at least one of these global tag IDs (+ untagged
|
||||
* records which are globally visible). Auto-inferred from the bound
|
||||
* agent's `scopeTagIds` if the user hasn't overridden it explicitly.
|
||||
* Undefined = no filtering, everything visible.
|
||||
*/
|
||||
scopeTagIds?: string[];
|
||||
}
|
||||
|
||||
/** Dexie row shape (adds the BaseRecord audit fields stamped by hooks). */
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue