feat(db): Phase 2b — Dexie v34 schema for space-scoped data model

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) <noreply@anthropic.com>
This commit is contained in:
Till JS 2026-04-22 17:15:48 +02:00
parent 430aa30cbf
commit 07e35d79f0
2 changed files with 28 additions and 0 deletions

View file

@ -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

View file

@ -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