From b878ecfe1c9c5605cbfd6bba0e54debac155ef21 Mon Sep 17 00:00:00 2001 From: Till JS Date: Mon, 20 Apr 2026 18:16:47 +0200 Subject: [PATCH] feat(spaces): Space-Switcher + Create-Dialog in (app) layout MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit First user-visible surface for the Spaces foundation. Two components: SpaceSwitcher (header dropdown) - Shows the active space name + type badge - Opens a dropdown listing all user's spaces with per-type color chips (brand / club / family / team / practice / personal) - Click on a space → /organization/set-active + full page reload so every liveQuery re-evaluates against the new active space - "+ Neuer Space" entry at the bottom opens the Create dialog SpaceCreateDialog (modal) - Type picker with description per type (excluding personal — that one is auto-created at signup and never chosen manually) - Name input + live slug preview (same slugifier as the server) - Conditional fields: voiceDoc for brand/club, uid + legalEntity for brand/club/practice - POSTs to /api/auth/organization/create with metadata.type, then /set-active and reload. beforeCreateOrganization hook rejects malformed metadata server-side. Placement: compact bar at the top of the (app) max-w-7xl wrapper, only rendered when authenticated. Zero changes to PillNavigation so the rest of the nav surface stays untouched. Reactivity note: the switcher full-reloads on set-active because the scoped-db wrapper doesn't yet invalidate liveQueries on active-space change. A reactive-invalidation path can replace the reload once the wrapper is used across enough modules to make the UX friction matter. Plan: docs/plans/spaces-foundation.md Co-Authored-By: Claude Opus 4.7 (1M context) --- .../layout/SpaceCreateDialog.svelte | 401 ++++++++++++++++++ .../components/layout/SpaceSwitcher.svelte | 302 +++++++++++++ .../apps/web/src/routes/(app)/+layout.svelte | 12 + 3 files changed, 715 insertions(+) create mode 100644 apps/mana/apps/web/src/lib/components/layout/SpaceCreateDialog.svelte create mode 100644 apps/mana/apps/web/src/lib/components/layout/SpaceSwitcher.svelte diff --git a/apps/mana/apps/web/src/lib/components/layout/SpaceCreateDialog.svelte b/apps/mana/apps/web/src/lib/components/layout/SpaceCreateDialog.svelte new file mode 100644 index 000000000..ecc5333c9 --- /dev/null +++ b/apps/mana/apps/web/src/lib/components/layout/SpaceCreateDialog.svelte @@ -0,0 +1,401 @@ + + + + +{#if open} +
(e.key === 'Enter' || e.key === ' ' ? close() : null)} + >
+ +{/if} + + diff --git a/apps/mana/apps/web/src/lib/components/layout/SpaceSwitcher.svelte b/apps/mana/apps/web/src/lib/components/layout/SpaceSwitcher.svelte new file mode 100644 index 000000000..28821a0cb --- /dev/null +++ b/apps/mana/apps/web/src/lib/components/layout/SpaceSwitcher.svelte @@ -0,0 +1,302 @@ + + + + +
+ + + {#if open} + + {/if} +
+ + (createOpen = false)} /> + + diff --git a/apps/mana/apps/web/src/routes/(app)/+layout.svelte b/apps/mana/apps/web/src/routes/(app)/+layout.svelte index 85083200f..339cce1bb 100644 --- a/apps/mana/apps/web/src/routes/(app)/+layout.svelte +++ b/apps/mana/apps/web/src/routes/(app)/+layout.svelte @@ -49,6 +49,7 @@ import { useAiTierItems } from '$lib/components/layout/use-ai-tier-items.svelte'; import { useSyncStatusItems } from '$lib/components/layout/use-sync-status-items.svelte'; import RouteTierGate from '$lib/components/layout/RouteTierGate.svelte'; + import SpaceSwitcher from '$lib/components/layout/SpaceSwitcher.svelte'; import { useLocalStt } from '$lib/components/voice/use-local-stt.svelte'; import { Microphone, Stop } from '@mana/shared-icons'; import { @@ -988,6 +989,11 @@ class="pt-2" >
+ {#if authStore.isAuthenticated} +
+ +
+ {/if} {#if routeBlocked && routeAppId}