feat(spaces): migrate calendar module to scoped-db wrapper (pilot)

First module to consume the scope layer — proves the model end-to-end
on a real query path.

Changes in calendar/queries.ts:
- db.table('calendars')   → scopedForModule<LocalCalendar>('calendar', 'calendars')
- db.table('timeBlocks')  → scopedForModule<LocalTimeBlock>('calendar', 'timeBlocks')
- db.table('events')      → scopedForModule<LocalEvent>('calendar', 'events')
- applyVisibility() wrapper runs on each read to drop private records
  authored by other members of a shared space.

Scope wrapper tweaks:
- getInScopeSpaceIds is now lenient during boot: if no active space has
  loaded yet, falls back to the user's personal sentinel so sentinel-
  stamped records from the v28 migration still render. Returns [] only
  when fully unauthenticated, which yields an empty-match filter.
- applyVisibility is no longer generic-constrained — T is inferred
  exactly as the input type; visibility/authorId are read via runtime
  duck-typing so arbitrary record shapes pass through cleanly.

Known follow-ups:
- Root-layout bootstrap (load active space + reconcile sentinels on
  login) is intentionally not wired up yet — needs a separate pass on
  the already-crowded (app) layout to avoid collateral damage.
- Four legacy tables (conversations, documents, spaceMembers,
  memoSpaces) carry a pre-existing `spaceId` field that points to the
  older context-space concept, not our multi-tenancy space. Renaming
  those to contextSpaceId is a tracked follow-up in the RFC — calendar
  is unaffected.

Plan: docs/plans/spaces-foundation.md (updated with the legacy-spaceId
note + lenient-scope rationale).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Till JS 2026-04-20 16:42:10 +02:00
parent 1cd559ca34
commit 80dbb3b3b6
4 changed files with 54 additions and 22 deletions

View file

@ -317,6 +317,28 @@ Via `SPACE_MODULES` in Nicht-Personal-Spaces gar nicht erst erreichbar. Kein Cod
---
## Bekannte Altlast: `spaceId`-Namenskollision
Vier bestehende Dexie-Tabellen nutzen das Feld `spaceId` bereits für das
**ältere** Context-Space-Konzept (chat-/memoro-interne Kontext-Ordner,
nicht das neue Multi-Tenancy-Space):
- `conversations` (chat) — `spaceId``contextSpaces.id`
- `documents` (context) — `spaceId``contextSpaces.id`
- `spaceMembers` (memoro) — `spaceId``contextSpaces.id`
- `memoSpaces` (memoro) — `spaceId``contextSpaces.id`
Die v28-Migration hat diese Tabellen **nicht korrumpiert**, weil der
Stamping-Code nur fehlende `spaceId`-Felder setzt (`if undefined/null`).
Bestehende Records mit Context-Space-Referenzen sind unverändert.
**Follow-up**: Rename `spaceId``contextSpaceId` auf diesen vier Tabellen
+ ihren Modulen + Dexie-v29-Migration, damit das Namensfeld eindeutig der
neuen Space-Primitive gehört. Bis dahin ist der Scope-Wrapper für diese
Tabellen nicht verwendbar — entweder Kollision erst fixen oder das
Wrapper-Filter per Modul-Ausnahme deaktivieren. Calendar, Todo, Notes etc.
sind nicht betroffen.
## Offene Fragen
- **Slug-Uniqueness-Kollision**: User Till mit `@till` kollidiert mit potentiellem Brand `@till`. Lösungsraum: Slugs global unique (einfach, aber Race um beliebte Namen) vs. Slug-Präfixe (`@user/till` vs. `@org/till` — hässlich). Vorschlag: global unique, First-Come-First-Served, User-Slug bei Signup aus E-Mail-Local-Part + Suffix bei Kollision.