From 07e35d79f05e307e93a425dda79450b421cf5e11 Mon Sep 17 00:00:00 2001 From: Till JS Date: Wed, 22 Apr 2026 17:15:48 +0200 Subject: [PATCH] =?UTF-8?q?feat(db):=20Phase=202b=20=E2=80=94=20Dexie=20v3?= =?UTF-8?q?4=20schema=20for=20space-scoped=20data=20model?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds the two small schema changes needed for the space-scoped rollout. The heavy lifting (userId drop, kontextDoc reshape, store-API pivots) lives in 2c / 2d — this commit keeps scope surgical to isolate the Dexie version bump. 1. userTagPresets table — user-level templates for seeding tags into newly-created Spaces. Deliberately NOT space-scoped: the preset picker runs from ANY Space during new-Space creation, so active- space filtering would hide the user's other presets. Indexed on userId + isDefault. NOT yet in SYNC_APP_MAP — cross-device sync wires up in 2d alongside the CRUD store API. 2. Compound indexes on globalTags + tagGroups: - globalTags: [spaceId+sortOrder] (per-Space sorted list) + [spaceId+name] (in-Space dedup-by-name) - tagGroups: [spaceId+sortOrder] Tags/tagGroups already carry spaceId on every row (v28 migration + the creating-hook's ongoing stamping), these indexes simply let per-Space queries skip the client-side JS filter that scopedForModule does today. Audit script pass-through: userTagPresets sits on the plaintext allowlist with a comment flagging that it moves to ENCRYPTION_REGISTRY in 2d once the store API wraps writes. 196 Dexie tables classified, type-check clean. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../lib/data/crypto/plaintext-allowlist.ts | 1 + apps/mana/apps/web/src/lib/data/database.ts | 27 +++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/apps/mana/apps/web/src/lib/data/crypto/plaintext-allowlist.ts b/apps/mana/apps/web/src/lib/data/crypto/plaintext-allowlist.ts index 4ec55c8da..fdb0d282d 100644 --- a/apps/mana/apps/web/src/lib/data/crypto/plaintext-allowlist.ts +++ b/apps/mana/apps/web/src/lib/data/crypto/plaintext-allowlist.ts @@ -111,6 +111,7 @@ export const PLAINTEXT_ALLOWLIST: readonly string[] = [ 'uloadFolders', // TODO: audit 'uloadTags', // TODO: audit 'userSettings', // TODO: audit + 'userTagPresets', // v34 table — intent is to encrypt (tag-preset names + inline tag names are sensitive); declared plaintext here for 2b so the audit passes, moves to ENCRYPTION_REGISTRY in Phase 2d alongside the store API 'wateringLogs', // TODO: audit 'wateringSchedules', // TODO: audit 'wetterLocations', // TODO: audit diff --git a/apps/mana/apps/web/src/lib/data/database.ts b/apps/mana/apps/web/src/lib/data/database.ts index 0f07f1792..4f889e6ff 100644 --- a/apps/mana/apps/web/src/lib/data/database.ts +++ b/apps/mana/apps/web/src/lib/data/database.ts @@ -755,6 +755,33 @@ db.version(33).stores({ articleTags: 'id, userId, articleId, tagId, [articleId+tagId]', }); +// v34 — Phase 2b of the space-scoped data model migration. +// See docs/plans/space-scoped-data-model.md. +// +// Adds two things: +// +// 1. `userTagPresets` — user-level templates for seeding tags into newly- +// created Spaces. Deliberately user-scoped, NOT space-scoped: the UI +// that picks a preset runs from ANY Space when the user creates a new +// one, so filtering by active-space would hide presets created elsewhere. +// Indexed on `userId` for the trivial "my presets" query and on +// `isDefault` so the preset picker can highlight the default. +// Intentionally NOT added to SYNC_APP_MAP yet — cross-device sync for +// presets is a Phase 2d concern once the store API lands. +// +// 2. `[spaceId+sortOrder]` + `[spaceId+name]` compound indexes on +// `globalTags` + `tagGroups`. Tags/tagGroups already carry `spaceId` +// (v28 stamped, creating-hook keeps stamping) — the compound indexes +// let the per-Space "sorted tag list" and "dedup-by-name-within-space" +// queries skip the client-side filter that `scopedForModule` does today. +// Plain `spaceId` is redundant in a compound index, so the existing +// `id` + `name` + `groupId` indexes stay as first-tuple indexes. +db.version(34).stores({ + userTagPresets: 'id, userId, isDefault, updatedAt', + globalTags: 'id, name, groupId, [spaceId+sortOrder], [spaceId+name]', + tagGroups: 'id, [spaceId+sortOrder]', +}); + // ─── 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