diff --git a/server/spaces.ts b/server/spaces.ts index 455f7b4..2f38020 100644 --- a/server/spaces.ts +++ b/server/spaces.ts @@ -240,8 +240,12 @@ spaces.get("/", async (c) => { const isPermissioned = vis === "permissioned"; const accessible = isPublicSpace || isOwner || isMember || (isPermissioned && !!claims); - // For unauthenticated: only show public spaces - if (!claims && !isPublicSpace) continue; + // For unauthenticated: only show demo + if (!claims && slug !== "demo") continue; + + // For authenticated: skip public spaces the user has no role in + // (demo is shown separately, other public spaces are noise) + if (claims && isPublicSpace && !isOwner && !isMember && slug !== "demo") continue; // Determine relationship const relationship = isOwner diff --git a/shared/components/rstack-identity.ts b/shared/components/rstack-identity.ts index 7dc954f..f42449a 100644 --- a/shared/components/rstack-identity.ts +++ b/shared/components/rstack-identity.ts @@ -223,6 +223,18 @@ function autoResolveSpace(token: string, username: string): void { .catch(() => {}); } +// ── Silent provisioning (no redirect) — ensures user's space exists ── + +function autoProvisionSpace(token: string): void { + fetch("/api/spaces/auto-provision", { + method: "POST", + headers: { + Authorization: `Bearer ${token}`, + "Content-Type": "application/json", + }, + }).catch(() => {}); +} + // ── Inline URL helpers (avoid import cycle with url-helpers) ── const _RESERVED = ["www", "rspace", "create", "new", "start", "auth"]; function _isSubdomain(): boolean { @@ -264,12 +276,12 @@ export class RStackIdentity extends HTMLElement { this.#refreshIfNeeded(); this.#render(); - // Belt-and-suspenders: if a session already exists on page load, - // ensure the user's personal space is provisioned (catches edge - // cases like iframe embedding or direct navigation). + // If a session already exists on page load, provision the + // user's personal space in the background (but do NOT redirect — + // the user intentionally navigated to this space). const session = getSession(); if (session?.accessToken && session.claims.username) { - autoResolveSpace(session.accessToken, session.claims.username); + autoProvisionSpace(session.accessToken); } } diff --git a/shared/components/rstack-space-switcher.ts b/shared/components/rstack-space-switcher.ts index ace79d6..9b89c08 100644 --- a/shared/components/rstack-space-switcher.ts +++ b/shared/components/rstack-space-switcher.ts @@ -171,9 +171,8 @@ export class RStackSpaceSwitcher extends HTMLElement { return; } - // 3-section split — exclude demo from public since we show it separately + // Split: user's own spaces vs permissioned spaces they can discover const mySpaces = this.#spaces.filter((s) => s.role); - const publicSpaces = this.#spaces.filter((s) => s.accessible !== false && !s.role && s.slug !== "demo"); const discoverSpaces = this.#spaces.filter((s) => s.accessible === false); const hasOwnedSpace = mySpaces.some((s) => s.relationship === "owner"); @@ -214,25 +213,7 @@ export class RStackSpaceSwitcher extends HTMLElement { .join(""); } - // ── Public spaces ── - if (publicSpaces.length > 0) { - if (mySpaces.length > 0) html += `
`; - html += `