diff --git a/modules/rmeets/components/folk-jitsi-room.ts b/modules/rmeets/components/folk-jitsi-room.ts
index 8ae607a..745206a 100644
--- a/modules/rmeets/components/folk-jitsi-room.ts
+++ b/modules/rmeets/components/folk-jitsi-room.ts
@@ -113,16 +113,20 @@ class FolkJitsiRoom extends HTMLElement {
disableDeepLinking: true,
hideConferenceSubject: false,
toolbarButtons: [
- "camera", "chat", "closedcaptions", "desktop",
- "fullscreen", "hangup", "microphone", "participants-pane",
- "raisehand", "select-background", "settings",
- "tileview", "toggle-camera",
+ "camera", "microphone", "desktop", "hangup",
+ "raisehand", "tileview", "toggle-camera",
+ "fullscreen", "select-background",
],
+ // Hide panels that add stray close (×) buttons
+ disableChat: false,
+ participantsPane: { enabled: false },
},
interfaceConfigOverwrite: {
SHOW_JITSI_WATERMARK: false,
SHOW_WATERMARK_FOR_GUESTS: false,
SHOW_BRAND_WATERMARK: false,
+ CLOSE_PAGE_GUEST_HINT: false,
+ SHOW_PROMOTIONAL_CLOSE_PAGE: false,
},
});
diff --git a/modules/rmeets/mod.ts b/modules/rmeets/mod.ts
index 0eed1af..81813da 100644
--- a/modules/rmeets/mod.ts
+++ b/modules/rmeets/mod.ts
@@ -104,30 +104,31 @@ const MI_STYLES = `
@@ -379,6 +426,21 @@ export class RStackIdentity extends HTMLElement {
${displayName}
+ ${otherPersonas.length > 0 ? `
+
+
Switch Persona
+ ${otherPersonas.map(p => `
+
+
+
+
+ `).join("")}
+ ` : ""}
+
+
@@ -446,6 +508,23 @@ export class RStackIdentity extends HTMLElement {
}
} else if (action === "settings") {
(document.getElementById("settings-btn") as HTMLElement)?.click();
+ } else if (action === "switch-persona") {
+ const targetDid = (el as HTMLElement).dataset.did || "";
+ const persona = getKnownPersonas().find(p => p.did === targetDid);
+ if (!persona) return;
+ clearSession();
+ resetDocBridge();
+ this.showAuthModal({ onSuccess: () => {}, onCancel: () => { this.#render(); } }, persona.username);
+ } else if (action === "add-persona") {
+ clearSession();
+ resetDocBridge();
+ this.#render();
+ this.dispatchEvent(new CustomEvent("auth-change", { bubbles: true, composed: true }));
+ this.showAuthModal();
+ } else if (action === "remove-persona") {
+ const targetDid = (el as HTMLElement).dataset.did || "";
+ removeKnownPersona(targetDid);
+ this.#render();
}
});
});
@@ -493,8 +572,9 @@ export class RStackIdentity extends HTMLElement {
return getSession() !== null;
}
- /** Public method: show the auth modal programmatically */
- showAuthModal(callbacks?: { onSuccess?: () => void; onCancel?: () => void }): void {
+ /** Public method: show the auth modal programmatically.
+ * Pass usernameHint to auto-trigger passkey sign-in for a specific persona. */
+ showAuthModal(callbacks?: { onSuccess?: () => void; onCancel?: () => void }, usernameHint?: string): void {
if (document.querySelector(".rstack-auth-overlay")) return;
const overlay = document.createElement("div");
@@ -542,17 +622,16 @@ export class RStackIdentity extends HTMLElement {
};
const handleSignIn = async () => {
- const errEl = overlay.querySelector("#auth-error") as HTMLElement;
- const btn = overlay.querySelector('[data-action="signin"]') as HTMLButtonElement;
- errEl.textContent = "";
- btn.disabled = true;
- btn.innerHTML = '
Authenticating...';
+ const errEl = overlay.querySelector("#auth-error") as HTMLElement | null;
+ const btn = overlay.querySelector('[data-action="signin"]') as HTMLButtonElement | null;
+ if (errEl) errEl.textContent = "";
+ if (btn) { btn.disabled = true; btn.innerHTML = '
Authenticating...'; }
try {
const startRes = await fetch(`${ENCRYPTID_URL}/api/auth/start`, {
method: "POST",
headers: { "Content-Type": "application/json" },
- body: JSON.stringify({}),
+ body: JSON.stringify(usernameHint ? { username: usernameHint } : {}),
});
if (!startRes.ok) throw new Error("Failed to start authentication");
const { options: serverOptions } = await startRes.json();
@@ -586,9 +665,11 @@ export class RStackIdentity extends HTMLElement {
// Auto-redirect to personal space
autoResolveSpace(data.token, data.username || "");
} catch (err: any) {
- btn.disabled = false;
- btn.innerHTML = "🔑 Sign In with Passkey";
- errEl.textContent = err.name === "NotAllowedError" ? "Authentication was cancelled." : err.message || "Authentication failed.";
+ if (btn) { btn.disabled = false; btn.innerHTML = "🔑 Sign In with Passkey"; }
+ const msg = err.name === "NotAllowedError" ? "Authentication was cancelled." : err.message || "Authentication failed.";
+ if (errEl) errEl.textContent = msg;
+ // If auto-triggered persona switch was cancelled, close modal and restore previous state
+ if (usernameHint) { close(); this.#render(); callbacks?.onCancel?.(); }
}
};
@@ -696,6 +777,11 @@ export class RStackIdentity extends HTMLElement {
document.body.appendChild(overlay);
render();
+
+ // If switching persona, auto-trigger sign-in immediately
+ if (usernameHint) {
+ handleSignIn();
+ }
}
// ── Account modal (consolidated) ──
@@ -1668,6 +1754,28 @@ const STYLES = `
padding: 0 4px; border: 2px solid var(--rs-bg-surface); line-height: 1;
}
+/* Persona switcher in dropdown */
+.dropdown-label {
+ padding: 6px 16px; font-size: 0.7rem; text-transform: uppercase;
+ letter-spacing: 0.05em; color: var(--rs-text-secondary); font-weight: 600;
+}
+.persona-row {
+ display: flex; align-items: center; padding-right: 8px;
+}
+.persona-row .persona-item { flex: 1; }
+.persona-avatar {
+ width: 24px; height: 24px; border-radius: 50%;
+ background: linear-gradient(135deg, #06b6d4, #7c3aed);
+ display: flex; align-items: center; justify-content: center;
+ font-weight: 700; font-size: 0.65rem; color: white; flex-shrink: 0;
+}
+.persona-remove {
+ opacity: 0.4; cursor: pointer; font-size: 0.75rem;
+ padding: 2px 6px; border-radius: 4px; border: none;
+ background: none; color: var(--rs-text-secondary); flex-shrink: 0;
+}
+.persona-remove:hover { opacity: 1; background: var(--rs-bg-hover); }
+
/* Notification items in dropdown */
.dropdown-section-label {
padding: 8px 16px 4px; font-size: 0.65rem; font-weight: 600;