mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 20:41:09 +02:00
refactor(profile,tool-registry): flip meImages from user-scoped to space-scoped (v40)
Flips `meImages` out of USER_LEVEL_TABLES so it lives under the same
tenancy model as every other data table (tags, scenes, tasks, …).
Precursor to the Wardrobe module, which is space-scoped across all
six space types — leaving meImages user-global would leave an
inconsistency where the Wardrobe catalog is per-space but its
reference input is cross-space, plus a latent privacy leak in shared
spaces (agents in a brand-space would see the owner's entire pool).
Plan: docs/plans/me-images-space-scope-migration.md.
Key decisions:
- Strict scope, no cross-space fallback. Switching into a brand-space
with no uploaded face shows an empty state and links back to
/profile/me-images; it does not quietly reach into the personal-
space pool. Keeps the mental model clean.
- auth.users.image remains pinned to personal-space primary-avatar.
Only a primary change inside personal space triggers the Better
Auth sync; brand/club/family/team/practice primaries stay local.
- Single Dexie v40 upgrade: stamps `spaceId=_personal:<uid>`
sentinel, `authorId=<uid>`, `visibility='space'` on every existing
row and drops the legacy `userId` column. Dexie upgrades block app
startup, so by the time the new code's scopedForModule reads run,
every row is already space-stamped. reconcileSentinels() on the
next active-space bootstrap rewrites `_personal:<uid>` to the real
personal-space id, same path v28 used.
- Legacy-avatar migration (M2.5) now pins its row to
`_personal:<uid>` explicitly — the legacy avatar is the user's
global SSO identity and belongs in the personal space even if the
migration happens to fire while the user is in a brand space.
Code changes:
- types.ts: LocalMeImage gains spaceId/authorId/visibility (all
optional — stamped by hook). Public MeImage exposes spaceId for
queries that want to branch on space type.
- database.ts: meImages out of USER_LEVEL_TABLES; new v40 upgrade
block that stamps sentinels + drops userId in one pass.
- queries.ts: all four hooks (useAllMeImages, useMeImagesByKind,
useReferenceImages, useImageByPrimary) read via scopedForModule.
Scope-switch triggers automatic re-render via the existing
scopedTable filter path.
- stores/me-images.svelte.ts: setPrimaryInTx uses scopedForModule so
a setPrimary in Brand-space never clears Personal-space's holder.
syncAvatarToAuth gates on activeSpace.type==='personal' so non-
personal primary changes don't leak into Better Auth.
createMeImage accepts optional spaceId override — the legacy-
avatar migration uses it, regular uploads let the hook stamp the
active space.
- migration/legacy-avatar.ts: explicitly passes
spaceId=_personal:<uid> to pin the legacy row into personal space.
- MeImagesView.svelte: subtle badge in the intro card shows the
active space ("Persönlich" for personal, space name otherwise) so
users notice when the pool changes on space switch.
- packages/mana-tool-registry/src/modules/me.ts: me.listReferenceImages
filters pulled rows by row.spaceId === ctx.spaceId. mana-sync
returns all spaces the user belongs to; the tool only wants the
active space's subset.
No schema/index change on meImages (non-indexed fields, pool size
small enough for in-memory scopedTable filter). If perf matters
later, adding [spaceId+kind] is a 5-minute follow-up.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
004b3b7fca
commit
cb9a9bb42e
8 changed files with 245 additions and 46 deletions
|
|
@ -70,6 +70,11 @@ interface RawMeImageRow {
|
|||
height?: number | null;
|
||||
usage?: { aiReference?: boolean } | null;
|
||||
deletedAt?: string | null;
|
||||
// Added by the web-app's Dexie v40 migration (docs/plans/
|
||||
// me-images-space-scope-migration.md). Rows pulled from mana-sync
|
||||
// for a user may span multiple spaces — the tool must filter down
|
||||
// to the caller's active space to match the web-app's behaviour.
|
||||
spaceId?: string | null;
|
||||
}
|
||||
|
||||
// ─── me.listReferenceImages ───────────────────────────────────────
|
||||
|
|
@ -103,10 +108,15 @@ export const meListReferenceImages: ToolSpec<typeof listInput, typeof listOutput
|
|||
const key = await ctx.getMasterKey();
|
||||
|
||||
const res = await pullAll<RawMeImageRow>(syncCfg(ctx), APP_ID, TABLE);
|
||||
// mana-sync returns all of the caller's meImages across every
|
||||
// space they belong to — filter down to the active space so
|
||||
// agents in a brand-space never see the personal-space pool
|
||||
// (and vice-versa). Matches the web-app's scopedForModule cut.
|
||||
const alive = res.changes
|
||||
.filter((c) => c.op !== 'delete' && c.data)
|
||||
.map((c) => c.data as RawMeImageRow)
|
||||
.filter((row) => !row.deletedAt);
|
||||
.filter((row) => !row.deletedAt)
|
||||
.filter((row) => row.spaceId === ctx.spaceId);
|
||||
|
||||
const optedIn = alive.filter((row) => row.usage?.aiReference === true);
|
||||
const kindFiltered = input.kind ? optedIn.filter((row) => row.kind === input.kind) : optedIn;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue