diff --git a/ONTOLOGY.md b/ONTOLOGY.md
index 4b30007..cb1612d 100644
--- a/ONTOLOGY.md
+++ b/ONTOLOGY.md
@@ -154,8 +154,8 @@ A **space** is a collaborative context — a team, community, project, or
individual workspace. Each space has:
- **Slug** + optional subdomain (`alice.rspace.online`)
-- **Visibility**: `public` | `public_read` | `authenticated` | `members_only`
-- **Members**: `viewer` → `participant` → `moderator` → `admin`
+- **Visibility**: `public` (👁 green — anyone reads, sign in to write) | `permissioned` (🔑 yellow — sign in to read & write) | `private` (🔒 red — invite-only)
+- **Members**: `viewer` → `member` → `moderator` → `admin`
- **Enabled modules**: which rApps are available in this space
- **Module scoping**: per-module `space` (data lives in space) vs
`global` (data follows identity)
diff --git a/modules/rinbox/mod.ts b/modules/rinbox/mod.ts
index 71bf9d2..a2686c4 100644
--- a/modules/rinbox/mod.ts
+++ b/modules/rinbox/mod.ts
@@ -280,7 +280,7 @@ routes.post("/api/mailboxes", async (c) => {
try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
const body = await c.req.json();
- const { slug, name, email, description, visibility = "members_only", imap_user, imap_password } = body;
+ const { slug, name, email, description, visibility = "private", imap_user, imap_password } = body;
if (!slug || !name || !email) return c.json({ error: "slug, name, email required" }, 400);
if (!/^[a-z0-9-]+$/.test(slug)) return c.json({ error: "Invalid slug" }, 400);
diff --git a/modules/rvote/components/folk-vote-dashboard.ts b/modules/rvote/components/folk-vote-dashboard.ts
index 645200c..60ce32d 100644
--- a/modules/rvote/components/folk-vote-dashboard.ts
+++ b/modules/rvote/components/folk-vote-dashboard.ts
@@ -58,7 +58,7 @@ class FolkVoteDashboard extends HTMLElement {
slug: "community",
name: "Community Governance",
description: "Proposals for the rSpace ecosystem",
- visibility: "public_read",
+ visibility: "public",
promotion_threshold: 100,
voting_period_days: 7,
credits_per_day: 10,
@@ -328,7 +328,7 @@ class FolkVoteDashboard extends HTMLElement {
${this.esc(s.name)}
-
${s.visibility === "public_read" ? "Public" : s.visibility}
+
${s.visibility === "public" ? "👁 Public" : s.visibility === "permissioned" ? "🔑 Permissioned" : s.visibility === "private" ? "🔒 Private" : s.visibility}
${this.esc(s.description || "")}
diff --git a/modules/rvote/mod.ts b/modules/rvote/mod.ts
index a37dd6c..a7ee473 100644
--- a/modules/rvote/mod.ts
+++ b/modules/rvote/mod.ts
@@ -65,7 +65,7 @@ function ensureSpaceConfigDoc(space: string): ProposalDoc {
name: '',
description: '',
ownerDid: '',
- visibility: 'public_read',
+ visibility: 'public',
promotionThreshold: 100,
votingPeriodDays: 7,
creditsPerDay: 10,
@@ -281,7 +281,7 @@ routes.post("/api/spaces", async (c) => {
try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
const body = await c.req.json();
- const { name, slug, description, visibility = "public_read" } = body;
+ const { name, slug, description, visibility = "public" } = body;
if (!name || !slug) return c.json({ error: "name and slug required" }, 400);
if (!/^[a-z0-9-]+$/.test(slug)) return c.json({ error: "Invalid slug" }, 400);
diff --git a/server/shell.ts b/server/shell.ts
index e116ccd..6a5750e 100644
--- a/server/shell.ts
+++ b/server/shell.ts
@@ -291,6 +291,8 @@ export function renderShell(opts: ShellOptions): string {
// Render all tabs with the current one active
tabBar.setLayers(layers);
tabBar.setAttribute('active', 'layer-' + currentModuleId);
+ // Track current module as recently used
+ if (tabBar.trackRecent) tabBar.trackRecent(currentModuleId);
// Helper: save current tab list to localStorage
function saveTabs() {
@@ -576,6 +578,7 @@ export function renderExternalAppShell(opts: ExternalAppShellOptions): string {
localStorage.setItem(TABS_KEY, JSON.stringify(layers));
tabBar.setLayers(layers);
tabBar.setAttribute('active', 'layer-' + currentModuleId);
+ if (tabBar.trackRecent) tabBar.trackRecent(currentModuleId);
function saveTabs() { localStorage.setItem(TABS_KEY, JSON.stringify(layers)); }
tabBar.addEventListener('layer-switch', (e) => { saveTabs(); window.location.href = window.__rspaceNavUrl(spaceSlug, e.detail.moduleId); });
tabBar.addEventListener('layer-add', (e) => { const { moduleId } = e.detail; if (!layers.find(l => l.moduleId === moduleId)) layers.push(makeLayer(moduleId, layers.length)); saveTabs(); window.location.href = window.__rspaceNavUrl(spaceSlug, moduleId); });
diff --git a/shared/components/rstack-identity.ts b/shared/components/rstack-identity.ts
index ead7482..631307c 100644
--- a/shared/components/rstack-identity.ts
+++ b/shared/components/rstack-identity.ts
@@ -1300,17 +1300,17 @@ export class RStackIdentity extends HTMLElement {
};
const visInfo = (v: string) =>
- v === "members_only" ? { icon: "🔒", cls: "vis-private", label: "private" }
- : v === "authenticated" ? { icon: "🔑", cls: "vis-permissioned", label: "permissioned" }
+ v === "private" ? { icon: "🔒", cls: "vis-private", label: "private" }
+ : v === "permissioned" ? { icon: "🔑", cls: "vis-permissioned", label: "permissioned" }
: { icon: "👁", cls: "vis-public", label: "public" };
const displayName = (s: any) => {
- const v = s.visibility || "public_read";
- if (v === "members_only") {
+ const v = s.visibility || "public";
+ if (v === "private") {
const username = getUsername();
- return username ? `${username}'s (you)rSpace` : "(you)rSpace";
+ return username ? `${username}'s Space` : "My Space";
}
- return `${(s.name || s.slug).replace(/ {
@@ -1318,7 +1318,7 @@ export class RStackIdentity extends HTMLElement {
const publicSpaces = spaces.filter((s) => !s.role && s.accessible);
const cardHTML = (s: any) => {
- const vis = visInfo(s.visibility || "public_read");
+ const vis = visInfo(s.visibility || "public");
return `