mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-15 07:21:10 +02:00
feat(spaces): rename legacy spaceId → contextSpaceId (v31 migration)
Resolves the name collision flagged in the Spaces RFC: four tables owned the term "spaceId" before the multi-tenancy Spaces foundation landed in v28 (conversations, documents, spaceMembers, memoSpaces — chat's context-folder reference, context's parent context-space, and memoro's membership/join tables). After v28, the scope wrapper started filtering on a field that meant something different in these tables, which would have hidden their records from the UI. Dexie v31 migration: - Renames the index from spaceId → contextSpaceId on all four tables. - upgrade() copies each existing `spaceId` value to `contextSpaceId` (when it's a real context-space reference and not already the v28 `_personal:<userId>` sentinel), then resets `spaceId` to the personal-space sentinel so the scope wrapper picks the row up on the active-space boot pass. Type changes: - LocalConversation, Conversation: spaceId → contextSpaceId - LocalDocument: spaceId → contextSpaceId - LocalSpaceMember, LocalMemoSpace (memoro): spaceId → contextSpaceId Code updates: - chat/queries.ts: toConversation + filterBySpace renamed to filterByContextSpace (exports updated in chat/index.ts). - chat/stores/conversations.svelte.ts: create() param + write site. - context/queries.ts: toDocument + useSpaceDocuments signature. - context/collections.ts: seed data. - context/ListView.svelte + route pages: form data. - dashboard/widgets/ContextDocsWidget.svelte: read site. Table names stay: `spaceMembers` and `memoSpaces` still carry their old names because they belong to the memoro module's context-space concept and table renames also require sync-routing updates. A dedicated cleanup can rebrand those once memoro's data model is revisited. 0 errors across 7148 files. Plan: docs/plans/spaces-foundation.md Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
a12928b7d8
commit
4ff95b2315
13 changed files with 94 additions and 23 deletions
|
|
@ -56,8 +56,10 @@
|
|||
<span>{typeIcons[doc.type ?? 'text'] ?? '📄'}</span>
|
||||
<div class="min-w-0 flex-1">
|
||||
<p class="truncate text-sm font-medium">{doc.title}</p>
|
||||
{#if getSpaceName(doc.spaceId)}
|
||||
<p class="truncate text-xs text-muted-foreground">{getSpaceName(doc.spaceId)}</p>
|
||||
{#if getSpaceName(doc.contextSpaceId)}
|
||||
<p class="truncate text-xs text-muted-foreground">
|
||||
{getSpaceName(doc.contextSpaceId)}
|
||||
</p>
|
||||
{/if}
|
||||
</div>
|
||||
<span class="flex-shrink-0 text-xs text-muted-foreground">
|
||||
|
|
|
|||
|
|
@ -672,6 +672,72 @@ db.version(30).stores({
|
|||
_serverIterationExecutions: 'iterationId, missionId, executedAt',
|
||||
});
|
||||
|
||||
// v31 — Rename the legacy `spaceId` field to `contextSpaceId` on four
|
||||
// tables that owned the term before the multi-tenancy Spaces foundation
|
||||
// arrived (v28):
|
||||
// - conversations (chat module's reference to a context-space folder)
|
||||
// - documents (context module's parent context-space)
|
||||
// - spaceMembers (memoro's members of a context-space)
|
||||
// - memoSpaces (memoro's memo ↔ context-space join)
|
||||
//
|
||||
// The v28 upgrade did NOT overwrite pre-existing `spaceId` values, so
|
||||
// records in these tables still carry context-space references under
|
||||
// the old name. After this migration, `spaceId` belongs exclusively to
|
||||
// the multi-tenancy primitive; the context-space reference has its own
|
||||
// disambiguated field. Scope queries that previously would have been
|
||||
// confused by the collision now work cleanly.
|
||||
//
|
||||
// The upgrade also stamps the fresh `spaceId` with the personal-space
|
||||
// sentinel for these rows so they immediately participate in scope
|
||||
// filtering instead of staying invisible until the next write.
|
||||
//
|
||||
// See docs/plans/spaces-foundation.md §"Legacy spaceId collision".
|
||||
db.version(31)
|
||||
.stores({
|
||||
conversations: 'id, isArchived, isPinned, contextSpaceId, templateId, updatedAt',
|
||||
documents: 'id, contextSpaceId, type, pinned, title, [contextSpaceId+type], updatedAt',
|
||||
spaceMembers: 'id, contextSpaceId, userId',
|
||||
memoSpaces: 'id, memoId, contextSpaceId',
|
||||
})
|
||||
.upgrade(async (tx) => {
|
||||
const tables = ['conversations', 'documents', 'spaceMembers', 'memoSpaces'] as const;
|
||||
for (const name of tables) {
|
||||
await tx
|
||||
.table(name)
|
||||
.toCollection()
|
||||
.modify((record: Record<string, unknown>) => {
|
||||
if (record.contextSpaceId !== undefined) return;
|
||||
const legacy = record.spaceId;
|
||||
if (typeof legacy === 'string' && legacy && !legacy.startsWith('_personal:')) {
|
||||
// Genuine context-space reference — move to the new field name.
|
||||
record.contextSpaceId = legacy;
|
||||
} else {
|
||||
record.contextSpaceId = null;
|
||||
}
|
||||
const ownerId =
|
||||
typeof record.userId === 'string' && record.userId ? record.userId : GUEST_USER_ID;
|
||||
// Reset spaceId so scope filtering matches the user's personal
|
||||
// space (post-bootstrap it's rewritten to the real personal-space id).
|
||||
record.spaceId = `_personal:${ownerId}`;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// v32 — Broadcast module: 1:N email campaigns (newsletters).
|
||||
// See docs/plans/broadcast-module.md. Three tables:
|
||||
// - broadcastCampaigns: the campaigns themselves. status + scheduledAt
|
||||
// indexed because the two hot queries are "show me drafts" and
|
||||
// "what's scheduled in the next 24h" (server cron picks those up).
|
||||
// - broadcastTemplates: reusable content templates. isBuiltIn indexed
|
||||
// so the picker can split user-created vs. shipped-with-app.
|
||||
// - broadcastSettings: singleton per user (sender defaults + DNS check
|
||||
// cache). id is the BROADCAST_SETTINGS_ID sentinel.
|
||||
db.version(32).stores({
|
||||
broadcastCampaigns: 'id, status, scheduledAt, sentAt',
|
||||
broadcastTemplates: 'id, isBuiltIn',
|
||||
broadcastSettings: 'id',
|
||||
});
|
||||
|
||||
// ─── Sync Routing ──────────────────────────────────────────
|
||||
// SYNC_APP_MAP, TABLE_TO_SYNC_NAME, TABLE_TO_APP, SYNC_NAME_TO_TABLE,
|
||||
// toSyncName() and fromSyncName() are now derived from per-module
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ export {
|
|||
toTemplate,
|
||||
toMessage,
|
||||
sortConversations,
|
||||
filterBySpace,
|
||||
filterByContextSpace,
|
||||
filterBySearch,
|
||||
splitPinned,
|
||||
} from './queries';
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ export function toConversation(local: LocalConversation): Conversation {
|
|||
id: local.id,
|
||||
modelId: local.modelId ?? '',
|
||||
templateId: local.templateId ?? undefined,
|
||||
spaceId: local.spaceId ?? undefined,
|
||||
contextSpaceId: local.contextSpaceId ?? undefined,
|
||||
conversationMode: local.conversationMode,
|
||||
documentMode: local.documentMode,
|
||||
title: local.title ?? undefined,
|
||||
|
|
@ -131,8 +131,11 @@ export function sortConversations(list: Conversation[]): Conversation[] {
|
|||
}
|
||||
|
||||
/** Filter conversations by space. */
|
||||
export function filterBySpace(conversations: Conversation[], spaceId: string): Conversation[] {
|
||||
return conversations.filter((c) => c.spaceId === spaceId);
|
||||
export function filterByContextSpace(
|
||||
conversations: Conversation[],
|
||||
contextSpaceId: string
|
||||
): Conversation[] {
|
||||
return conversations.filter((c) => c.contextSpaceId === contextSpaceId);
|
||||
}
|
||||
|
||||
/** Filter conversations by search query on title. */
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ export const conversationsStore = {
|
|||
async create(data: {
|
||||
modelId?: string;
|
||||
templateId?: string;
|
||||
spaceId?: string;
|
||||
contextSpaceId?: string;
|
||||
mode?: 'free' | 'guided' | 'template';
|
||||
documentMode?: boolean;
|
||||
title?: string;
|
||||
|
|
@ -34,7 +34,7 @@ export const conversationsStore = {
|
|||
title: data.title ?? null,
|
||||
modelId: data.modelId ?? null,
|
||||
templateId: data.templateId ?? null,
|
||||
spaceId: data.spaceId ?? null,
|
||||
contextSpaceId: data.contextSpaceId ?? null,
|
||||
conversationMode: data.mode ?? 'free',
|
||||
documentMode: data.documentMode ?? false,
|
||||
isArchived: false,
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ export interface LocalConversation extends BaseRecord {
|
|||
title?: string | null;
|
||||
modelId?: string | null;
|
||||
templateId?: string | null;
|
||||
spaceId?: string | null;
|
||||
contextSpaceId?: string | null;
|
||||
conversationMode: 'free' | 'guided' | 'template';
|
||||
documentMode: boolean;
|
||||
isArchived: boolean;
|
||||
|
|
@ -38,7 +38,7 @@ export interface Conversation {
|
|||
id: string;
|
||||
modelId: string;
|
||||
templateId?: string;
|
||||
spaceId?: string;
|
||||
contextSpaceId?: string;
|
||||
conversationMode: 'free' | 'guided' | 'template';
|
||||
documentMode: boolean;
|
||||
title?: string;
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
const id = crypto.randomUUID();
|
||||
const row: LocalDocument = {
|
||||
id,
|
||||
spaceId: null,
|
||||
contextSpaceId: null,
|
||||
title: 'Neues Dokument',
|
||||
content: '# Neues Dokument\n\n',
|
||||
type: 'text',
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ export const CONTEXT_GUEST_SEED = {
|
|||
documents: [
|
||||
{
|
||||
id: 'doc-welcome',
|
||||
spaceId: DEMO_SPACE_ID,
|
||||
contextSpaceId: DEMO_SPACE_ID,
|
||||
title: 'Willkommen bei Context',
|
||||
content:
|
||||
'Context ist dein KI-gestütztes Dokumenten-Management. Erstelle Texte, sammle Kontexte und nutze KI-Prompts.\n\nMelde dich an, um deine Dokumente zu synchronisieren.',
|
||||
|
|
@ -40,7 +40,7 @@ export const CONTEXT_GUEST_SEED = {
|
|||
},
|
||||
{
|
||||
id: 'doc-prompt',
|
||||
spaceId: DEMO_SPACE_ID,
|
||||
contextSpaceId: DEMO_SPACE_ID,
|
||||
title: 'Beispiel-Prompt',
|
||||
content: 'Fasse den folgenden Text in 3 Stichpunkten zusammen:\n\n{text}',
|
||||
type: 'prompt' as const,
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ export function toDocument(local: LocalDocument): Document {
|
|||
title: local.title,
|
||||
content: local.content,
|
||||
type: local.type,
|
||||
space_id: local.spaceId ?? null,
|
||||
space_id: local.contextSpaceId ?? null,
|
||||
user_id: 'local',
|
||||
created_at: local.createdAt ?? new Date().toISOString(),
|
||||
updated_at: local.updatedAt ?? new Date().toISOString(),
|
||||
|
|
@ -73,13 +73,13 @@ export function useAllDocuments() {
|
|||
}, [] as Document[]);
|
||||
}
|
||||
|
||||
/** Documents for a specific space. Auto-updates on any change. */
|
||||
export function useSpaceDocuments(spaceId: string) {
|
||||
/** Documents for a specific context-space. Auto-updates on any change. */
|
||||
export function useSpaceDocuments(contextSpaceId: string) {
|
||||
return useLiveQueryWithDefault(async () => {
|
||||
const locals = await db
|
||||
.table<LocalDocument>('documents')
|
||||
.where('spaceId')
|
||||
.equals(spaceId)
|
||||
.where('contextSpaceId')
|
||||
.equals(contextSpaceId)
|
||||
.toArray();
|
||||
const visible = locals.filter((d) => !d.deletedAt);
|
||||
const decrypted = await decryptRecords('documents', visible);
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ export interface LocalContextSpace extends BaseRecord {
|
|||
}
|
||||
|
||||
export interface LocalDocument extends BaseRecord {
|
||||
spaceId?: string | null;
|
||||
contextSpaceId?: string | null;
|
||||
title: string;
|
||||
content: string;
|
||||
type: DocumentType;
|
||||
|
|
|
|||
|
|
@ -62,14 +62,14 @@ export interface LocalSpace extends BaseRecord {
|
|||
}
|
||||
|
||||
export interface LocalSpaceMember extends BaseRecord {
|
||||
spaceId: string;
|
||||
contextSpaceId: string;
|
||||
userId: string;
|
||||
role: 'owner' | 'member';
|
||||
}
|
||||
|
||||
export interface LocalMemoSpace extends BaseRecord {
|
||||
memoId: string;
|
||||
spaceId: string;
|
||||
contextSpaceId: string;
|
||||
}
|
||||
|
||||
// ─── View Types ────────────────────────────────────────────
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@
|
|||
const id = crypto.randomUUID();
|
||||
const row: LocalDocument = {
|
||||
id,
|
||||
spaceId: null,
|
||||
contextSpaceId: null,
|
||||
title: 'Neues Dokument',
|
||||
content: '# Neues Dokument\n\n',
|
||||
type: 'text',
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@
|
|||
const id = crypto.randomUUID();
|
||||
const row: LocalDocument = {
|
||||
id,
|
||||
spaceId,
|
||||
contextSpaceId: spaceId,
|
||||
title: 'Neues Dokument',
|
||||
content: '# Neues Dokument\n\n',
|
||||
type: 'text',
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue