From 3b3eecdddb48ab31c0bcc89692006bc941b3c8b7 Mon Sep 17 00:00:00 2001 From: Jeff Emmett Date: Wed, 4 Mar 2026 18:02:00 -0800 Subject: [PATCH] =?UTF-8?q?fix:=20stop=20demo=E2=86=92personal=20space=20r?= =?UTF-8?q?edirect=20and=20clean=20up=20space=20switcher?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Logged-in users visiting demo.rspace.online were auto-redirected to their personal space on page load. Now only provisions the space silently without redirecting. Also removes the redundant "Public spaces" section from the dropdown and filters the /api/spaces endpoint to only return demo, user's own spaces, and permissioned spaces. Co-Authored-By: Claude Opus 4.6 --- server/spaces.ts | 8 ++++++-- shared/components/rstack-identity.ts | 20 +++++++++++++++---- shared/components/rstack-space-switcher.ts | 23 ++-------------------- 3 files changed, 24 insertions(+), 27 deletions(-) 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 += ``; - html += publicSpaces - .map((s) => { - const vis = this.#visibilityInfo(s); - return ` - - ${s.icon || "🌐"} - ${this.#displayName(s)} - ${vis.label} - `; - }) - .join(""); - } - - // ── Discover (inaccessible spaces) ── + // ── Discover (permissioned spaces the user can request access to) ── if (auth && discoverSpaces.length > 0) { html += `
`; html += ``;