From 871a1c3bba5527ccd14db6ddf397a5d6b6e3ae1c Mon Sep 17 00:00:00 2001 From: Till JS Date: Mon, 20 Apr 2026 18:10:46 +0200 Subject: [PATCH] feat(spaces): wire active-space boot into (app) layout MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Calls loadActiveSpace() + reconcileSentinels() in the Phase-B critical boot block, right after the user identity is bound to the ambient actor and before sync starts. This means: - Pending-change rows pushed to mana-sync carry the real organization id, not the `_personal:` sentinel the v28 migration uses as a placeholder. - Sentinel records (written pre-boot or by the v28 upgrade on an existing db) get rewritten to the real personal-space id in a single pass once Better Auth responds. - The scope wrapper in module queries now partitions by the active space instead of degrading to sentinel-only filtering. Failure is non-fatal — an offline boot or a Better Auth hiccup just means the sentinel path stays live and the next boot retries. A count log surfaces the reconciliation count so migrations are visible in devtools. Plan: docs/plans/spaces-foundation.md Co-Authored-By: Claude Opus 4.7 (1M context) --- .../apps/web/src/routes/(app)/+layout.svelte | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/apps/mana/apps/web/src/routes/(app)/+layout.svelte b/apps/mana/apps/web/src/routes/(app)/+layout.svelte index d81c53d3e..85083200f 100644 --- a/apps/mana/apps/web/src/routes/(app)/+layout.svelte +++ b/apps/mana/apps/web/src/routes/(app)/+layout.svelte @@ -558,6 +558,25 @@ const uid = authStore.user?.id ?? 'unknown'; const name = authStore.user?.name || authStore.user?.email || 'Du'; bindDefaultUser(uid, name); + + // Spaces foundation: resolve the user's active Space from Better + // Auth before sync runs so pending-change rows get stamped with a + // real organization id (not the `_personal:` sentinel the + // v28 migration leaves behind). Failure is non-fatal — the scope + // wrapper falls back to the sentinel, sync still pushes with a + // NULL space_id, and the next boot retries. See + // docs/plans/spaces-foundation.md. + try { + const { loadActiveSpace, reconcileSentinels } = await import('$lib/data/scope'); + await loadActiveSpace(); + const rewritten = await reconcileSentinels(); + if (rewritten > 0) { + console.info(`[spaces] reconciled ${rewritten} sentinel records to active space`); + } + } catch (err) { + console.warn('[spaces] active-space boot failed — sync will use sentinel scope', err); + } + await syncBilling.load(); const getToken = () => authStore.getValidToken(); unifiedSync = createUnifiedSync(SYNC_SERVER_URL, getToken, syncBilling.active);