From 4895af19db61d8094719eb2ad4591166112e14ad Mon Sep 17 00:00:00 2001 From: Jeff Emmett Date: Wed, 25 Feb 2026 19:04:22 -0800 Subject: [PATCH] feat: r-prefix module slugs, landing page, clickable rStack header MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Rename all 23 module IDs to r-prefixed slugs (canvas→rspace, notes→rnotes, etc.) - Root rspace.online/ now serves the landing page instead of redirecting to demo - rStack header in app switcher dropdown is now a clickable link to rstack.online - Update all internal navigation links, badge maps, and URL helpers - Space root redirects to /rspace instead of /canvas Co-Authored-By: Claude Opus 4.6 --- modules/books/components/folk-book-reader.ts | 2 +- modules/books/components/folk-book-shelf.ts | 6 +- modules/books/mod.ts | 12 +- modules/cal/mod.ts | 4 +- modules/canvas/mod.ts | 4 +- modules/cart/mod.ts | 4 +- .../components/folk-choices-dashboard.ts | 6 +- modules/choices/mod.ts | 4 +- modules/data/mod.ts | 4 +- modules/files/mod.ts | 4 +- modules/forum/mod.ts | 4 +- modules/funds/mod.ts | 8 +- modules/inbox/mod.ts | 4 +- modules/maps/mod.ts | 6 +- modules/network/mod.ts | 4 +- modules/notes/mod.ts | 4 +- modules/photos/mod.ts | 6 +- modules/providers/mod.ts | 4 +- modules/pubs/mod.ts | 4 +- modules/splat/components/folk-splat-viewer.ts | 12 +- modules/splat/mod.ts | 12 +- modules/swag/mod.ts | 4 +- modules/trips/mod.ts | 6 +- modules/tube/mod.ts | 4 +- modules/vote/mod.ts | 4 +- modules/wallet/mod.ts | 4 +- modules/work/mod.ts | 4 +- server/index.ts | 20 ++- shared/components/rstack-app-switcher.ts | 127 +++++++++--------- shared/components/rstack-identity.ts | 2 +- shared/components/rstack-tab-bar.ts | 54 ++++---- shared/url-helpers.ts | 4 +- website/index.html | 2 +- 33 files changed, 181 insertions(+), 172 deletions(-) diff --git a/modules/books/components/folk-book-reader.ts b/modules/books/components/folk-book-reader.ts index f8b2867..0d08a34 100644 --- a/modules/books/components/folk-book-reader.ts +++ b/modules/books/components/folk-book-reader.ts @@ -235,7 +235,7 @@ export class FolkBookReader extends HTMLElement { ${this.getStyles()}
- \u2190 Library + \u2190 Library ${this.escapeHtml(this._title)} ${this._author ? `by ${this.escapeHtml(this._author)}` : ""} diff --git a/modules/books/components/folk-book-shelf.ts b/modules/books/components/folk-book-shelf.ts index fb8e21d..7a654dd 100644 --- a/modules/books/components/folk-book-shelf.ts +++ b/modules/books/components/folk-book-shelf.ts @@ -389,7 +389,7 @@ export class FolkBookShelf extends HTMLElement {
` : `
${books.map((b) => ` - +
${this.escapeHtml(b.title)} ${b.featured ? 'Featured' : ""} @@ -553,7 +553,7 @@ export class FolkBookShelf extends HTMLElement { } try { - const res = await fetch(`/${this._spaceSlug}/books/api/books`, { + const res = await fetch(`/${this._spaceSlug}/rbooks/api/books`, { method: "POST", headers: { Authorization: `Bearer ${token}` }, body: formData, @@ -565,7 +565,7 @@ export class FolkBookShelf extends HTMLElement { } // Navigate to the new book - window.location.href = `/${this._spaceSlug}/books/read/${data.slug}`; + window.location.href = `/${this._spaceSlug}/rbooks/read/${data.slug}`; } catch (e: any) { errorEl.textContent = e.message; errorEl.hidden = false; diff --git a/modules/books/mod.ts b/modules/books/mod.ts index c9490fa..ad4f5ad 100644 --- a/modules/books/mod.ts +++ b/modules/books/mod.ts @@ -206,7 +206,7 @@ routes.get("/", (c) => { const spaceSlug = c.req.param("space") || "personal"; return c.html(renderShell({ title: `${spaceSlug} — Library | rSpace`, - moduleId: "books", + moduleId: "rbooks", spaceSlug, modules: getModuleInfoList(), theme: "dark", @@ -229,9 +229,9 @@ routes.get("/read/:id", async (c) => { if (rows.length === 0) { const html = renderShell({ title: "Book not found | rSpace", - moduleId: "books", + moduleId: "rbooks", spaceSlug, - body: ``, + body: `

Book not found

Back to library

`, modules: getModuleInfoList(), }); return c.html(html, 404); @@ -246,11 +246,11 @@ routes.get("/read/:id", async (c) => { ); // Build the PDF URL relative to this module's mount point - const pdfUrl = `/${spaceSlug}/books/api/books/${book.slug}/pdf`; + const pdfUrl = `/${spaceSlug}/rbooks/api/books/${book.slug}/pdf`; const html = renderShell({ title: `${book.title} | rSpace`, - moduleId: "books", + moduleId: "rbooks", spaceSlug, body: ` { const space = c.req.param("space") || "demo"; return c.html(renderShell({ title: `${space} — Calendar | rSpace`, - moduleId: "cal", + moduleId: "rcal", spaceSlug: space, modules: getModuleInfoList(), theme: "dark", @@ -388,7 +388,7 @@ routes.get("/", (c) => { }); export const calModule: RSpaceModule = { - id: "cal", + id: "rcal", name: "rCal", icon: "\u{1F4C5}", description: "Temporal coordination calendar with lunar, solar, and seasonal systems", diff --git a/modules/canvas/mod.ts b/modules/canvas/mod.ts index 1bbcd72..0026e1d 100644 --- a/modules/canvas/mod.ts +++ b/modules/canvas/mod.ts @@ -38,7 +38,7 @@ routes.get("/", async (c) => { const html = renderShell({ title: `${spaceSlug} — Canvas | rSpace`, - moduleId: "canvas", + moduleId: "rspace", spaceSlug, body: canvasBody, modules: getModuleInfoList(), @@ -50,7 +50,7 @@ routes.get("/", async (c) => { }); export const canvasModule: RSpaceModule = { - id: "canvas", + id: "rspace", name: "rSpace", icon: "🎨", description: "Real-time collaborative canvas", diff --git a/modules/cart/mod.ts b/modules/cart/mod.ts index f52227b..df10abb 100644 --- a/modules/cart/mod.ts +++ b/modules/cart/mod.ts @@ -443,7 +443,7 @@ routes.get("/", (c) => { const space = c.req.param("space") || "demo"; return c.html(renderShell({ title: `Shop | rSpace`, - moduleId: "cart", + moduleId: "rcart", spaceSlug: space, modules: getModuleInfoList(), theme: "dark", @@ -454,7 +454,7 @@ routes.get("/", (c) => { }); export const cartModule: RSpaceModule = { - id: "cart", + id: "rcart", name: "rCart", icon: "\u{1F6D2}", description: "Cosmolocal print-on-demand shop", diff --git a/modules/choices/components/folk-choices-dashboard.ts b/modules/choices/components/folk-choices-dashboard.ts index 911e6ce..8684cd5 100644 --- a/modules/choices/components/folk-choices-dashboard.ts +++ b/modules/choices/components/folk-choices-dashboard.ts @@ -78,7 +78,7 @@ class FolkChoicesDashboard extends HTMLElement { @@ -96,14 +96,14 @@ class FolkChoicesDashboard extends HTMLElement { return `
\u2611

No choices in this space yet.

-

Open the canvas and use the Poll, Rank, or Spider buttons to create one.

+

Open the canvas and use the Poll, Rank, or Spider buttons to create one.

`; } private renderGrid(icons: Record, labels: Record): string { return `
${this.choices.map((ch) => ` - +
${icons[ch.type] || "\u2611"}
${labels[ch.type] || ch.type}

${this.esc(ch.title)}

diff --git a/modules/choices/mod.ts b/modules/choices/mod.ts index c4310bb..db15100 100644 --- a/modules/choices/mod.ts +++ b/modules/choices/mod.ts @@ -50,7 +50,7 @@ routes.get("/", (c) => { const spaceSlug = c.req.param("space") || "demo"; return c.html(renderShell({ title: `${spaceSlug} — Choices | rSpace`, - moduleId: "choices", + moduleId: "rchoices", spaceSlug, modules: getModuleInfoList(), theme: "dark", @@ -61,7 +61,7 @@ routes.get("/", (c) => { }); export const choicesModule: RSpaceModule = { - id: "choices", + id: "rchoices", name: "rChoices", icon: "☑", description: "Polls, rankings, and multi-criteria scoring", diff --git a/modules/data/mod.ts b/modules/data/mod.ts index 426d029..a28e72c 100644 --- a/modules/data/mod.ts +++ b/modules/data/mod.ts @@ -122,7 +122,7 @@ routes.get("/", (c) => { const space = c.req.param("space") || "demo"; return c.html(renderShell({ title: `${space} — Data | rSpace`, - moduleId: "data", + moduleId: "rdata", spaceSlug: space, modules: getModuleInfoList(), theme: "dark", @@ -133,7 +133,7 @@ routes.get("/", (c) => { }); export const dataModule: RSpaceModule = { - id: "data", + id: "rdata", name: "rData", icon: "\u{1F4CA}", description: "Privacy-first analytics for the r* ecosystem", diff --git a/modules/files/mod.ts b/modules/files/mod.ts index 47f83f0..872817e 100644 --- a/modules/files/mod.ts +++ b/modules/files/mod.ts @@ -368,7 +368,7 @@ routes.get("/", (c) => { const spaceSlug = c.req.param("space") || "demo"; return c.html(renderShell({ title: `${spaceSlug} — Files | rSpace`, - moduleId: "files", + moduleId: "rfiles", spaceSlug, modules: getModuleInfoList(), theme: "dark", @@ -379,7 +379,7 @@ routes.get("/", (c) => { }); export const filesModule: RSpaceModule = { - id: "files", + id: "rfiles", name: "rFiles", icon: "\uD83D\uDCC1", description: "File sharing, share links, and memory cards", diff --git a/modules/forum/mod.ts b/modules/forum/mod.ts index b166b6d..64d2eae 100644 --- a/modules/forum/mod.ts +++ b/modules/forum/mod.ts @@ -159,7 +159,7 @@ routes.get("/", (c) => { const spaceSlug = c.req.param("space") || "demo"; return c.html(renderShell({ title: `${spaceSlug} — Forum | rSpace`, - moduleId: "forum", + moduleId: "rforum", spaceSlug, modules: getModuleInfoList(), theme: "dark", @@ -170,7 +170,7 @@ routes.get("/", (c) => { }); export const forumModule: RSpaceModule = { - id: "forum", + id: "rforum", name: "rForum", icon: "\uD83D\uDCAC", description: "Deploy and manage Discourse forums", diff --git a/modules/funds/mod.ts b/modules/funds/mod.ts index 353d10e..7669328 100644 --- a/modules/funds/mod.ts +++ b/modules/funds/mod.ts @@ -198,7 +198,7 @@ routes.get("/", (c) => { const spaceSlug = c.req.param("space") || "demo"; return c.html(renderShell({ title: `rFunds — TBFF Flow Funding | rSpace`, - moduleId: "funds", + moduleId: "rfunds", spaceSlug, modules: getModuleInfoList(), theme: "dark", @@ -213,7 +213,7 @@ routes.get("/demo", (c) => { const spaceSlug = c.req.param("space") || "demo"; return c.html(renderShell({ title: `TBFF Demo — rFunds | rSpace`, - moduleId: "funds", + moduleId: "rfunds", spaceSlug, modules: getModuleInfoList(), theme: "dark", @@ -229,7 +229,7 @@ routes.get("/flow/:flowId", (c) => { const flowId = c.req.param("flowId"); return c.html(renderShell({ title: `Flow — rFunds | rSpace`, - moduleId: "funds", + moduleId: "rfunds", spaceSlug, modules: getModuleInfoList(), theme: "dark", @@ -240,7 +240,7 @@ routes.get("/flow/:flowId", (c) => { }); export const fundsModule: RSpaceModule = { - id: "funds", + id: "rfunds", name: "rFunds", icon: "\uD83C\uDF0A", description: "Budget flows, river visualization, and treasury management", diff --git a/modules/inbox/mod.ts b/modules/inbox/mod.ts index 6edb0b9..65d960c 100644 --- a/modules/inbox/mod.ts +++ b/modules/inbox/mod.ts @@ -532,7 +532,7 @@ routes.get("/", (c) => { const space = c.req.param("space") || "demo"; return c.html(renderShell({ title: `${space} — Inbox | rSpace`, - moduleId: "inbox", + moduleId: "rinbox", spaceSlug: space, modules: getModuleInfoList(), theme: "dark", @@ -543,7 +543,7 @@ routes.get("/", (c) => { }); export const inboxModule: RSpaceModule = { - id: "inbox", + id: "rinbox", name: "rInbox", icon: "\u{1F4E8}", description: "Collaborative email with multisig approval", diff --git a/modules/maps/mod.ts b/modules/maps/mod.ts index b828032..31b2f96 100644 --- a/modules/maps/mod.ts +++ b/modules/maps/mod.ts @@ -135,7 +135,7 @@ routes.get("/", (c) => { const space = c.req.param("space") || "demo"; return c.html(renderShell({ title: `${space} — Maps | rSpace`, - moduleId: "maps", + moduleId: "rmaps", spaceSlug: space, modules: getModuleInfoList(), theme: "dark", @@ -151,7 +151,7 @@ routes.get("/:room", (c) => { const room = c.req.param("room"); return c.html(renderShell({ title: `${room} — Maps | rSpace`, - moduleId: "maps", + moduleId: "rmaps", spaceSlug: space, modules: getModuleInfoList(), theme: "dark", @@ -162,7 +162,7 @@ routes.get("/:room", (c) => { }); export const mapsModule: RSpaceModule = { - id: "maps", + id: "rmaps", name: "rMaps", icon: "\u{1F5FA}", description: "Real-time collaborative location sharing and indoor/outdoor maps", diff --git a/modules/network/mod.ts b/modules/network/mod.ts index 972d82b..b0bfef3 100644 --- a/modules/network/mod.ts +++ b/modules/network/mod.ts @@ -218,7 +218,7 @@ routes.get("/", (c) => { const space = c.req.param("space") || "demo"; return c.html(renderShell({ title: `${space} — Network | rSpace`, - moduleId: "network", + moduleId: "rnetwork", spaceSlug: space, modules: getModuleInfoList(), theme: "dark", @@ -229,7 +229,7 @@ routes.get("/", (c) => { }); export const networkModule: RSpaceModule = { - id: "network", + id: "rnetwork", name: "rNetwork", icon: "\u{1F310}", description: "Community relationship graph visualization with CRM sync", diff --git a/modules/notes/mod.ts b/modules/notes/mod.ts index a9fed40..bde88b0 100644 --- a/modules/notes/mod.ts +++ b/modules/notes/mod.ts @@ -363,7 +363,7 @@ routes.get("/", (c) => { const space = c.req.param("space") || "demo"; return c.html(renderShell({ title: `${space} — Notes | rSpace`, - moduleId: "notes", + moduleId: "rnotes", spaceSlug: space, modules: getModuleInfoList(), theme: "dark", @@ -374,7 +374,7 @@ routes.get("/", (c) => { }); export const notesModule: RSpaceModule = { - id: "notes", + id: "rnotes", name: "rNotes", icon: "\u{1F4DD}", description: "Notebooks with rich-text notes, voice transcription, and collaboration", diff --git a/modules/photos/mod.ts b/modules/photos/mod.ts index b96a1af..1e0e6fe 100644 --- a/modules/photos/mod.ts +++ b/modules/photos/mod.ts @@ -110,7 +110,7 @@ routes.get("/", (c) => { const spaceSlug = c.req.param("space") || "demo"; return c.html(renderShell({ title: `${spaceSlug} — Photos | rSpace`, - moduleId: "photos", + moduleId: "rphotos", spaceSlug, modules: getModuleInfoList(), theme: "dark", @@ -121,7 +121,7 @@ routes.get("/", (c) => { }); export const photosModule: RSpaceModule = { - id: "photos", + id: "rphotos", name: "rPhotos", icon: "📸", description: "Community photo commons", @@ -129,7 +129,7 @@ export const photosModule: RSpaceModule = { standaloneDomain: "rphotos.online", feeds: [ { - id: "photos", + id: "rphotos", name: "Recent Photos", kind: "data", description: "Stream of recently uploaded photos", diff --git a/modules/providers/mod.ts b/modules/providers/mod.ts index 4ce9f55..70694bd 100644 --- a/modules/providers/mod.ts +++ b/modules/providers/mod.ts @@ -350,7 +350,7 @@ routes.get("/", (c) => { const space = c.req.param("space") || "demo"; return c.html(renderShell({ title: `Providers | rSpace`, - moduleId: "providers", + moduleId: "rproviders", spaceSlug: space, modules: getModuleInfoList(), theme: "dark", @@ -361,7 +361,7 @@ routes.get("/", (c) => { }); export const providersModule: RSpaceModule = { - id: "providers", + id: "rproviders", name: "rProviders", icon: "\u{1F3ED}", description: "Local provider directory for cosmolocal production", diff --git a/modules/pubs/mod.ts b/modules/pubs/mod.ts index d09c531..80e7afb 100644 --- a/modules/pubs/mod.ts +++ b/modules/pubs/mod.ts @@ -323,7 +323,7 @@ routes.get("/", (c) => { const spaceSlug = c.req.param("space") || "personal"; return c.html(renderShell({ title: `${spaceSlug} — rPubs Editor | rSpace`, - moduleId: "pubs", + moduleId: "rpubs", spaceSlug, modules: getModuleInfoList(), theme: "dark", @@ -336,7 +336,7 @@ routes.get("/", (c) => { // ── Module export ── export const pubsModule: RSpaceModule = { - id: "pubs", + id: "rpubs", name: "rPubs", icon: "📖", description: "Drop in a document, get a pocket book", diff --git a/modules/splat/components/folk-splat-viewer.ts b/modules/splat/components/folk-splat-viewer.ts index 2b9c971..1fcd518 100644 --- a/modules/splat/components/folk-splat-viewer.ts +++ b/modules/splat/components/folk-splat-viewer.ts @@ -80,7 +80,7 @@ export class FolkSplatViewer extends HTMLElement { const status = s.processing_status || "ready"; const isReady = status === "ready"; const tag = isReady ? "a" : "div"; - const href = isReady ? ` href="/${this._spaceSlug}/splat/view/${s.slug}"` : ""; + const href = isReady ? ` href="/${this._spaceSlug}/rsplat/view/${s.slug}"` : ""; const statusClass = !isReady ? ` splat-card--${status}` : ""; let overlay = ""; @@ -259,7 +259,7 @@ export class FolkSplatViewer extends HTMLElement { try { const token = localStorage.getItem("encryptid_token") || ""; - const res = await fetch(`/${this._spaceSlug}/splat/api/splats`, { + const res = await fetch(`/${this._spaceSlug}/rsplat/api/splats`, { method: "POST", headers: token ? { Authorization: `Bearer ${token}` } : {}, body: formData, @@ -287,7 +287,7 @@ export class FolkSplatViewer extends HTMLElement { const splat = await res.json() as SplatItem; status.textContent = "Uploaded!"; setTimeout(() => { - window.location.href = `/${this._spaceSlug}/splat/view/${splat.slug}`; + window.location.href = `/${this._spaceSlug}/rsplat/view/${splat.slug}`; }, 500); } catch (e) { status.textContent = "Network error"; @@ -347,7 +347,7 @@ export class FolkSplatViewer extends HTMLElement { try { const token = localStorage.getItem("encryptid_token") || ""; - const res = await fetch(`/${this._spaceSlug}/splat/api/splats/from-media`, { + const res = await fetch(`/${this._spaceSlug}/rsplat/api/splats/from-media`, { method: "POST", headers: token ? { Authorization: `Bearer ${token}` } : {}, body: formData, @@ -374,7 +374,7 @@ export class FolkSplatViewer extends HTMLElement { status.textContent = "Uploaded! Queued for processing."; setTimeout(() => { - window.location.href = `/${this._spaceSlug}/splat`; + window.location.href = `/${this._spaceSlug}/rsplat`; }, 1000); } catch (e) { status.textContent = "Network error"; @@ -393,7 +393,7 @@ export class FolkSplatViewer extends HTMLElement {
Loading splat...
${this._splatTitle ? `
diff --git a/modules/splat/mod.ts b/modules/splat/mod.ts index 334086c..c8b986b 100644 --- a/modules/splat/mod.ts +++ b/modules/splat/mod.ts @@ -429,7 +429,7 @@ routes.get("/", async (c) => { const html = renderShell({ title: `${spaceSlug} — rSplat | rSpace`, - moduleId: "splat", + moduleId: "rsplat", spaceSlug, body: ``, modules: getModuleInfoList(), @@ -464,9 +464,9 @@ routes.get("/view/:id", async (c) => { if (rows.length === 0) { const html = renderShell({ title: "Splat not found | rSpace", - moduleId: "splat", + moduleId: "rsplat", spaceSlug, - body: `

Splat not found

Back to gallery

`, + body: `

Splat not found

Back to gallery

`, modules: getModuleInfoList(), theme: "dark", }); @@ -481,11 +481,11 @@ routes.get("/view/:id", async (c) => { [splat.id] ); - const fileUrl = `/${spaceSlug}/splat/api/splats/${splat.slug}/${splat.slug}.${splat.file_format}`; + const fileUrl = `/${spaceSlug}/rsplat/api/splats/${splat.slug}/${splat.slug}.${splat.file_format}`; const html = renderShell({ title: `${splat.title} | rSplat`, - moduleId: "splat", + moduleId: "rsplat", spaceSlug, body: ` { // ── Module export ── export const splatModule: RSpaceModule = { - id: "splat", + id: "rsplat", name: "rSplat", icon: "🔮", description: "3D Gaussian splat viewer", diff --git a/modules/swag/mod.ts b/modules/swag/mod.ts index 684793d..306fc16 100644 --- a/modules/swag/mod.ts +++ b/modules/swag/mod.ts @@ -230,7 +230,7 @@ routes.get("/", (c) => { const space = c.req.param("space") || "demo"; return c.html(renderShell({ title: `Swag Designer | rSpace`, - moduleId: "swag", + moduleId: "rswag", spaceSlug: space, modules: getModuleInfoList(), theme: "dark", @@ -241,7 +241,7 @@ routes.get("/", (c) => { }); export const swagModule: RSpaceModule = { - id: "swag", + id: "rswag", name: "rSwag", icon: "\u{1F3A8}", description: "Design print-ready swag: stickers, posters, tees", diff --git a/modules/trips/mod.ts b/modules/trips/mod.ts index 63c311c..8284f15 100644 --- a/modules/trips/mod.ts +++ b/modules/trips/mod.ts @@ -240,7 +240,7 @@ routes.get("/routes", (c) => { const space = c.req.param("space") || "demo"; return c.html(renderShell({ title: `${space} — Route Planner | rTrips`, - moduleId: "trips", + moduleId: "rtrips", spaceSlug: space, modules: getModuleInfoList(), theme: "dark", @@ -255,7 +255,7 @@ routes.get("/", (c) => { const space = c.req.param("space") || "demo"; return c.html(renderShell({ title: `${space} — Trips | rSpace`, - moduleId: "trips", + moduleId: "rtrips", spaceSlug: space, modules: getModuleInfoList(), theme: "dark", @@ -266,7 +266,7 @@ routes.get("/", (c) => { }); export const tripsModule: RSpaceModule = { - id: "trips", + id: "rtrips", name: "rTrips", icon: "\u{2708}\u{FE0F}", description: "Collaborative trip planner with itinerary, bookings, and expense splitting", diff --git a/modules/tube/mod.ts b/modules/tube/mod.ts index aea5863..cac5656 100644 --- a/modules/tube/mod.ts +++ b/modules/tube/mod.ts @@ -193,7 +193,7 @@ routes.get("/", (c) => { const space = c.req.param("space") || "demo"; return c.html(renderShell({ title: `${space} — Tube | rSpace`, - moduleId: "tube", + moduleId: "rtube", spaceSlug: space, modules: getModuleInfoList(), theme: "dark", @@ -204,7 +204,7 @@ routes.get("/", (c) => { }); export const tubeModule: RSpaceModule = { - id: "tube", + id: "rtube", name: "rTube", icon: "\u{1F3AC}", description: "Community video hosting & live streaming", diff --git a/modules/vote/mod.ts b/modules/vote/mod.ts index d0aadff..6d36285 100644 --- a/modules/vote/mod.ts +++ b/modules/vote/mod.ts @@ -329,7 +329,7 @@ routes.get("/", (c) => { const space = c.req.param("space") || "demo"; return c.html(renderShell({ title: `${space} — Vote | rSpace`, - moduleId: "vote", + moduleId: "rvote", spaceSlug: space, modules: getModuleInfoList(), theme: "dark", @@ -340,7 +340,7 @@ routes.get("/", (c) => { }); export const voteModule: RSpaceModule = { - id: "vote", + id: "rvote", name: "rVote", icon: "\u{1F5F3}", description: "Conviction voting engine for collaborative governance", diff --git a/modules/wallet/mod.ts b/modules/wallet/mod.ts index 3c435b3..6714ee0 100644 --- a/modules/wallet/mod.ts +++ b/modules/wallet/mod.ts @@ -96,7 +96,7 @@ routes.get("/", (c) => { const spaceSlug = c.req.param("space") || "demo"; return c.html(renderShell({ title: `${spaceSlug} — Wallet | rSpace`, - moduleId: "wallet", + moduleId: "rwallet", spaceSlug, modules: getModuleInfoList(), theme: "dark", @@ -107,7 +107,7 @@ routes.get("/", (c) => { }); export const walletModule: RSpaceModule = { - id: "wallet", + id: "rwallet", name: "rWallet", icon: "\uD83D\uDCB0", description: "Multichain Safe wallet visualization and treasury management", diff --git a/modules/work/mod.ts b/modules/work/mod.ts index 0ec97e1..ba03d2a 100644 --- a/modules/work/mod.ts +++ b/modules/work/mod.ts @@ -219,7 +219,7 @@ routes.get("/", (c) => { const space = c.req.param("space") || "demo"; return c.html(renderShell({ title: `${space} — Work | rSpace`, - moduleId: "work", + moduleId: "rwork", spaceSlug: space, modules: getModuleInfoList(), theme: "dark", @@ -230,7 +230,7 @@ routes.get("/", (c) => { }); export const workModule: RSpaceModule = { - id: "work", + id: "rwork", name: "rWork", icon: "\u{1F4CB}", description: "Kanban workspace boards for collaborative task management", diff --git a/server/index.ts b/server/index.ts index 042b568..5e9a7ed 100644 --- a/server/index.ts +++ b/server/index.ts @@ -209,7 +209,7 @@ function generateFallbackResponse( } if (q.includes("help") || q.includes("what can")) { - return `rSpace has ${modules.length} apps you can use. Some popular ones: **rSpace** (canvas), **rNotes** (notes), **rChat** (messaging), **rFunds** (community funding), and **rVote** (governance). What would you like to explore?`; + return `rSpace has ${modules.length} apps you can use. Some popular ones: **rSpace** (spatial canvas), **rNotes** (notes), **rChat** (messaging), **rFunds** (community funding), and **rVote** (governance). What would you like to explore?`; } if (q.includes("search") || q.includes("find")) { @@ -474,8 +474,14 @@ for (const mod of getAllModules()) { // ── Page routes ── -// Landing page: rspace.online/ → redirect to demo canvas (overlay shows there) -app.get("/", (c) => c.redirect("/demo/canvas", 302)); +// Landing page: rspace.online/ → serve marketing/info page +app.get("/", async (c) => { + const file = Bun.file(resolve(DIST_DIR, "index.html")); + if (await file.exists()) { + return new Response(file, { headers: { "Content-Type": "text/html" } }); + } + return c.text("rSpace", 200); +}); // About/info page (full landing content) app.get("/about", async (c) => { @@ -507,12 +513,12 @@ app.get("/admin", async (c) => { return c.text("Admin", 200); }); -// Space root: /:space → redirect to /:space/canvas +// Space root: /:space → redirect to /:space/rspace app.get("/:space", (c) => { const space = c.req.param("space"); // Don't redirect for static file paths if (space.includes(".")) return c.notFound(); - return c.redirect(`/${space}/canvas`); + return c.redirect(`/${space}/rspace`); }); // ── WebSocket types ── @@ -743,9 +749,9 @@ const server = Bun.serve({ if (subdomain) { const pathSegments = url.pathname.split("/").filter(Boolean); - // Root: redirect to default module (canvas) + // Root: redirect to default module (rspace) if (pathSegments.length === 0) { - return Response.redirect(`${url.protocol}//${host}/canvas`, 302); + return Response.redirect(`${url.protocol}//${host}/rspace`, 302); } // Global routes pass through without subdomain prefix diff --git a/shared/components/rstack-app-switcher.ts b/shared/components/rstack-app-switcher.ts index 64b4aa6..6d2440c 100644 --- a/shared/components/rstack-app-switcher.ts +++ b/shared/components/rstack-app-switcher.ts @@ -19,76 +19,76 @@ export interface AppSwitcherModule { // Pastel badge abbreviations & colors for each module const MODULE_BADGES: Record = { // Creating - canvas: { badge: "rS", color: "#5eead4" }, // teal-300 - notes: { badge: "rN", color: "#fcd34d" }, // amber-300 - pubs: { badge: "rP", color: "#fda4af" }, // rose-300 - swag: { badge: "rSw", color: "#fda4af" }, // rose-300 - splat: { badge: "r3", color: "#d8b4fe" }, // purple-300 + rspace: { badge: "rS", color: "#5eead4" }, // teal-300 + rnotes: { badge: "rN", color: "#fcd34d" }, // amber-300 + rpubs: { badge: "rP", color: "#fda4af" }, // rose-300 + rswag: { badge: "rSw", color: "#fda4af" }, // rose-300 + rsplat: { badge: "r3", color: "#d8b4fe" }, // purple-300 // Planning - cal: { badge: "rC", color: "#7dd3fc" }, // sky-300 - trips: { badge: "rT", color: "#6ee7b7" }, // emerald-300 - maps: { badge: "rM", color: "#86efac" }, // green-300 + rcal: { badge: "rC", color: "#7dd3fc" }, // sky-300 + rtrips: { badge: "rT", color: "#6ee7b7" }, // emerald-300 + rmaps: { badge: "rM", color: "#86efac" }, // green-300 // Communicating - chats: { badge: "rCh", color: "#6ee7b7" }, // emerald-200 - inbox: { badge: "rI", color: "#a5b4fc" }, // indigo-300 - mail: { badge: "rMa", color: "#93c5fd" }, // blue-200 - forum: { badge: "rFo", color: "#fcd34d" }, // amber-200 + rchats: { badge: "rCh", color: "#6ee7b7" }, // emerald-200 + rinbox: { badge: "rI", color: "#a5b4fc" }, // indigo-300 + rmail: { badge: "rMa", color: "#93c5fd" }, // blue-200 + rforum: { badge: "rFo", color: "#fcd34d" }, // amber-200 // Deciding - choices: { badge: "rCo", color: "#f0abfc" }, // fuchsia-300 - vote: { badge: "rV", color: "#c4b5fd" }, // violet-300 + rchoices: { badge: "rCo", color: "#f0abfc" }, // fuchsia-300 + rvote: { badge: "rV", color: "#c4b5fd" }, // violet-300 // Funding & Commerce - funds: { badge: "rF", color: "#bef264" }, // lime-300 - wallet: { badge: "rW", color: "#fde047" }, // yellow-300 - cart: { badge: "rCt", color: "#fdba74" }, // orange-300 - auctions: { badge: "rA", color: "#fca5a5" }, // red-300 - providers: { badge: "rPr", color: "#fdba74" }, // orange-300 - tube: { badge: "rTu", color: "#f9a8d4" }, // pink-300 + rfunds: { badge: "rF", color: "#bef264" }, // lime-300 + rwallet: { badge: "rW", color: "#fde047" }, // yellow-300 + rcart: { badge: "rCt", color: "#fdba74" }, // orange-300 + rauctions: { badge: "rA", color: "#fca5a5" }, // red-300 + rproviders: { badge: "rPr", color: "#fdba74" }, // orange-300 + rtube: { badge: "rTu", color: "#f9a8d4" }, // pink-300 // Sharing - photos: { badge: "rPh", color: "#f9a8d4" }, // pink-200 - network: { badge: "rNe", color: "#93c5fd" }, // blue-300 - socials: { badge: "rSo", color: "#7dd3fc" }, // sky-200 - files: { badge: "rFi", color: "#67e8f9" }, // cyan-300 - books: { badge: "rB", color: "#fda4af" }, // rose-300 + rphotos: { badge: "rPh", color: "#f9a8d4" }, // pink-200 + rnetwork: { badge: "rNe", color: "#93c5fd" }, // blue-300 + rsocials: { badge: "rSo", color: "#7dd3fc" }, // sky-200 + rfiles: { badge: "rFi", color: "#67e8f9" }, // cyan-300 + rbooks: { badge: "rB", color: "#fda4af" }, // rose-300 // Observing - data: { badge: "rD", color: "#d8b4fe" }, // purple-300 + rdata: { badge: "rD", color: "#d8b4fe" }, // purple-300 // Work & Productivity - work: { badge: "rWo", color: "#cbd5e1" }, // slate-300 + rwork: { badge: "rWo", color: "#cbd5e1" }, // slate-300 // Identity & Infrastructure - ids: { badge: "rId", color: "#6ee7b7" }, // emerald-300 - stack: { badge: "r*", color: "" }, // gradient (handled separately) + rids: { badge: "rId", color: "#6ee7b7" }, // emerald-300 + rstack: { badge: "r*", color: "" }, // gradient (handled separately) }; // Category definitions for the rApp dropdown (display-only grouping) const MODULE_CATEGORIES: Record = { - canvas: "Creating", - notes: "Creating", - pubs: "Creating", - tube: "Creating", - swag: "Creating", - splat: "Creating", - cal: "Planning", - trips: "Planning", - maps: "Planning", - chats: "Communicating", - inbox: "Communicating", - mail: "Communicating", - forum: "Communicating", - choices: "Deciding", - vote: "Deciding", - funds: "Funding & Commerce", - wallet: "Funding & Commerce", - cart: "Funding & Commerce", - auctions: "Funding & Commerce", - providers: "Funding & Commerce", - photos: "Sharing", - network: "Sharing", - socials: "Sharing", - files: "Sharing", - books: "Sharing", - data: "Observing", - work: "Work & Productivity", - ids: "Identity & Infrastructure", - stack: "Identity & Infrastructure", + rspace: "Creating", + rnotes: "Creating", + rpubs: "Creating", + rtube: "Creating", + rswag: "Creating", + rsplat: "Creating", + rcal: "Planning", + rtrips: "Planning", + rmaps: "Planning", + rchats: "Communicating", + rinbox: "Communicating", + rmail: "Communicating", + rforum: "Communicating", + rchoices: "Deciding", + rvote: "Deciding", + rfunds: "Funding & Commerce", + rwallet: "Funding & Commerce", + rcart: "Funding & Commerce", + rauctions: "Funding & Commerce", + rproviders: "Funding & Commerce", + rphotos: "Sharing", + rnetwork: "Sharing", + rsocials: "Sharing", + rfiles: "Sharing", + rbooks: "Sharing", + rdata: "Observing", + rwork: "Work & Productivity", + rids: "Identity & Infrastructure", + rstack: "Identity & Infrastructure", }; const CATEGORY_ORDER = [ @@ -150,15 +150,15 @@ export class RStackAppSwitcher extends HTMLElement { } } - // rStack header + // rStack header (clickable) let html = ` - + `; for (const cat of CATEGORY_ORDER) { @@ -304,10 +304,13 @@ const STYLES = ` } /* rStack header */ -.rstack-header { +a.rstack-header { display: flex; align-items: center; gap: 10px; padding: 12px 14px; border-bottom: 1px solid rgba(128,128,128,0.15); + text-decoration: none; color: inherit; cursor: pointer; + transition: background 0.12s; } +a.rstack-header:hover { background: rgba(255,255,255,0.05); } .rstack-badge { display: flex; align-items: center; justify-content: center; width: 28px; height: 28px; border-radius: 8px; diff --git a/shared/components/rstack-identity.ts b/shared/components/rstack-identity.ts index 9172528..9d66671 100644 --- a/shared/components/rstack-identity.ts +++ b/shared/components/rstack-identity.ts @@ -146,7 +146,7 @@ function _getCurrentSpace(): string { } function _getCurrentModule(): string { const parts = window.location.pathname.split("/").filter(Boolean); - return _isSubdomain() ? (parts[0] || "canvas") : (parts[1] || "canvas"); + return _isSubdomain() ? (parts[0] || "rspace") : (parts[1] || "rspace"); } function _navUrl(space: string, moduleId: string): string { const h = window.location.host.split(":")[0].split("."); diff --git a/shared/components/rstack-tab-bar.ts b/shared/components/rstack-tab-bar.ts index 997f543..2bfb6b5 100644 --- a/shared/components/rstack-tab-bar.ts +++ b/shared/components/rstack-tab-bar.ts @@ -25,33 +25,33 @@ import { FLOW_COLORS, FLOW_LABELS } from "../../lib/layer-types"; // Re-export badge info so the tab bar can show module colors const MODULE_BADGES: Record = { - canvas: { badge: "rS", color: "#5eead4" }, - notes: { badge: "rN", color: "#fcd34d" }, - pubs: { badge: "rP", color: "#fda4af" }, - swag: { badge: "rSw", color: "#fda4af" }, - splat: { badge: "r3", color: "#d8b4fe" }, - cal: { badge: "rC", color: "#7dd3fc" }, - trips: { badge: "rT", color: "#6ee7b7" }, - maps: { badge: "rM", color: "#86efac" }, - chats: { badge: "rCh", color: "#6ee7b7" }, - inbox: { badge: "rI", color: "#a5b4fc" }, - mail: { badge: "rMa", color: "#93c5fd" }, - forum: { badge: "rFo", color: "#fcd34d" }, - choices: { badge: "rCo", color: "#f0abfc" }, - vote: { badge: "rV", color: "#c4b5fd" }, - funds: { badge: "rF", color: "#bef264" }, - wallet: { badge: "rW", color: "#fde047" }, - cart: { badge: "rCt", color: "#fdba74" }, - auctions: { badge: "rA", color: "#fca5a5" }, - providers: { badge: "rPr", color: "#fdba74" }, - tube: { badge: "rTu", color: "#f9a8d4" }, - photos: { badge: "rPh", color: "#f9a8d4" }, - network: { badge: "rNe", color: "#93c5fd" }, - socials: { badge: "rSo", color: "#7dd3fc" }, - files: { badge: "rFi", color: "#67e8f9" }, - books: { badge: "rB", color: "#fda4af" }, - data: { badge: "rD", color: "#d8b4fe" }, - work: { badge: "rWo", color: "#cbd5e1" }, + rspace: { badge: "rS", color: "#5eead4" }, + rnotes: { badge: "rN", color: "#fcd34d" }, + rpubs: { badge: "rP", color: "#fda4af" }, + rswag: { badge: "rSw", color: "#fda4af" }, + rsplat: { badge: "r3", color: "#d8b4fe" }, + rcal: { badge: "rC", color: "#7dd3fc" }, + rtrips: { badge: "rT", color: "#6ee7b7" }, + rmaps: { badge: "rM", color: "#86efac" }, + rchats: { badge: "rCh", color: "#6ee7b7" }, + rinbox: { badge: "rI", color: "#a5b4fc" }, + rmail: { badge: "rMa", color: "#93c5fd" }, + rforum: { badge: "rFo", color: "#fcd34d" }, + rchoices: { badge: "rCo", color: "#f0abfc" }, + rvote: { badge: "rV", color: "#c4b5fd" }, + rfunds: { badge: "rF", color: "#bef264" }, + rwallet: { badge: "rW", color: "#fde047" }, + rcart: { badge: "rCt", color: "#fdba74" }, + rauctions: { badge: "rA", color: "#fca5a5" }, + rproviders: { badge: "rPr", color: "#fdba74" }, + rtube: { badge: "rTu", color: "#f9a8d4" }, + rphotos: { badge: "rPh", color: "#f9a8d4" }, + rnetwork: { badge: "rNe", color: "#93c5fd" }, + rsocials: { badge: "rSo", color: "#7dd3fc" }, + rfiles: { badge: "rFi", color: "#67e8f9" }, + rbooks: { badge: "rB", color: "#fda4af" }, + rdata: { badge: "rD", color: "#d8b4fe" }, + rwork: { badge: "rWo", color: "#cbd5e1" }, }; export class RStackTabBar extends HTMLElement { diff --git a/shared/url-helpers.ts b/shared/url-helpers.ts index 7b01d64..91407f1 100644 --- a/shared/url-helpers.ts +++ b/shared/url-helpers.ts @@ -35,9 +35,9 @@ export function getCurrentSpace(): string { export function getCurrentModule(): string { const parts = window.location.pathname.split("/").filter(Boolean); if (isSubdomain()) { - return parts[0] || "canvas"; + return parts[0] || "rspace"; } - return parts[1] || "canvas"; + return parts[1] || "rspace"; } /** diff --git a/website/index.html b/website/index.html index f0bc8b5..91ffff9 100644 --- a/website/index.html +++ b/website/index.html @@ -379,7 +379,7 @@