From 88eca8a75935b634cae42363c340cf4c11d47acb Mon Sep 17 00:00:00 2001 From: Till JS Date: Tue, 21 Apr 2026 18:53:03 +0200 Subject: [PATCH] feat(spaces): Spaces as workbench card + canonical /spaces route MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extract member management from /spaces/members into a reusable workbench-card ListView so users can drop the surface into any scene. - lib/modules/spaces/ListView.svelte — hint + invite + members + pending invitations, all theme-token driven - APP_ICONS.spaces icon (three-silhouette cluster, teal→indigo) - MANA_APPS entry id=spaces (beta tier, shared-space management) - registerApp({ id: 'spaces' }) so the card is scene-droppable - /spaces/+page.svelte as the new canonical route wrapper - /spaces/members/+page.svelte kept as legacy alias - SpaceSwitcher menu now links to /spaces Co-Authored-By: Claude Opus 4.7 (1M context) --- .../apps/web/src/lib/app-registry/apps.ts | 10 + .../components/layout/SpaceSwitcher.svelte | 2 +- .../src/lib/modules/spaces/ListView.svelte | 501 ++++++++++++++++++ .../web/src/routes/(app)/spaces/+page.svelte | 13 + .../routes/(app)/spaces/members/+page.svelte | 497 +---------------- packages/shared-branding/src/app-icons.ts | 7 + packages/shared-branding/src/mana-apps.ts | 17 + 7 files changed, 555 insertions(+), 492 deletions(-) create mode 100644 apps/mana/apps/web/src/lib/modules/spaces/ListView.svelte create mode 100644 apps/mana/apps/web/src/routes/(app)/spaces/+page.svelte diff --git a/apps/mana/apps/web/src/lib/app-registry/apps.ts b/apps/mana/apps/web/src/lib/app-registry/apps.ts index 672910175..c40b57c7b 100644 --- a/apps/mana/apps/web/src/lib/app-registry/apps.ts +++ b/apps/mana/apps/web/src/lib/app-registry/apps.ts @@ -1278,6 +1278,16 @@ registerApp({ ], }); +registerApp({ + id: 'spaces', + name: 'Spaces', + color: '#14b8a6', + icon: UserCircle, + views: { + list: { load: () => import('$lib/modules/spaces/ListView.svelte') }, + }, +}); + registerApp({ id: 'quiz', name: 'Quiz', diff --git a/apps/mana/apps/web/src/lib/components/layout/SpaceSwitcher.svelte b/apps/mana/apps/web/src/lib/components/layout/SpaceSwitcher.svelte index e0e3c3e61..4797c6ab0 100644 --- a/apps/mana/apps/web/src/lib/components/layout/SpaceSwitcher.svelte +++ b/apps/mana/apps/web/src/lib/components/layout/SpaceSwitcher.svelte @@ -211,7 +211,7 @@ {#if active && active.type !== 'personal'} - +
- - {#snippet breadcrumb()} - Workbench Mitglieder verwalten - {/snippet} - - - {#if !activeSpace} -

Lade aktiven Space …

- {:else if activeSpace.type === 'personal'} -
-

- Dein Personal-Space kann keine weiteren Mitglieder haben — er ist bewusst nur für dich. Für - geteilte Bereiche (Familie, Team, Marke, Verein) lege einen neuen Space an und lade dann - hier ein. -

-
- {:else} - {#if canManage} -
-

Einladen

-
- - - -
- {#if inviteError}

{inviteError}

{/if} - {#if inviteSuccess}

{inviteSuccess}

{/if} -
- {/if} - -
-

Mitglieder ({members.length})

- {#if loading} -

Lädt …

- {:else if loadError} -

{loadError}

- {:else if members.length === 0} -

Nur du bist Mitglied.

- {:else} -
    - {#each members as m (m.id)} -
  • -
    - -
    -
    {m.user?.name ?? m.user?.email ?? m.userId}
    - {#if m.user?.email && m.user?.name} -
    {m.user.email}
    - {/if} -
    -
    -
    - {m.role} - {#if canManage && m.role !== 'owner'} - - {/if} -
    -
  • - {/each} -
- {/if} -
- - {#if invitations.length > 0} -
-

Offene Einladungen ({invitations.length})

-
    - {#each invitations.filter((i) => i.status === 'pending') as inv (inv.id)} -
  • -
    -
    {inv.email}
    -
    - {inv.role} · verschickt {relativeDate(inv.expiresAt)} -
    -
    - {#if canManage} - - {/if} -
  • - {/each} -
-
- {/if} - {/if} -
- - + diff --git a/packages/shared-branding/src/app-icons.ts b/packages/shared-branding/src/app-icons.ts index f029e3c4c..3db382492 100644 --- a/packages/shared-branding/src/app-icons.ts +++ b/packages/shared-branding/src/app-icons.ts @@ -260,6 +260,13 @@ export const APP_ICONS = { // while still reading as "chronological" in the AI Workbench family. `` ), + spaces: svgToDataUrl( + // Three people-silhouettes clustered in the tile — the Spaces primitive + // is about shared workspaces, so the icon emphasises "group". Teal→indigo + // gradient sits next to contacts (green) and chat (indigo) in the + // communication family without competing with either. + `` + ), } as const; export type AppIconId = keyof typeof APP_ICONS; diff --git a/packages/shared-branding/src/mana-apps.ts b/packages/shared-branding/src/mana-apps.ts index be48c8de6..a0df94963 100644 --- a/packages/shared-branding/src/mana-apps.ts +++ b/packages/shared-branding/src/mana-apps.ts @@ -1105,6 +1105,23 @@ export const MANA_APPS: ManaApp[] = [ status: 'beta', requiredTier: 'beta', }, + { + id: 'spaces', + name: 'Spaces', + description: { + de: 'Geteilte Bereiche & Mitglieder', + en: 'Shared spaces & members', + }, + longDescription: { + de: 'Verwalte den aktiven Space und seine Mitglieder — lade Personen per E-Mail ein, vergib Rollen (Admin/Mitglied) und widerrufe offene Einladungen. Personal-Spaces zeigen nur den Hinweis, dass sie bewusst nur für dich sind; geteilte Spaces (Familie, Team, Marke, Verein, Praxis) bekommen das volle Member-Management.', + en: 'Manage the active Space and its members — invite people by email, assign roles (admin/member) and revoke pending invitations. Personal spaces are single-user by design; shared spaces (family, team, brand, club, practice) get full member management.', + }, + icon: APP_ICONS.spaces, + color: '#14b8a6', + comingSoon: false, + status: 'beta', + requiredTier: 'beta', + }, ]; /**