feat(sync): proxy /api/user/* to EncryptID for cross-session tab state

Adds catch-all proxy route so client tab sync requests (saveTabs/syncTabsFromServer)
reach the EncryptID container. Also fixes rstack-mi panel positioning and
Shadow DOM click-outside handling.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jeff Emmett 2026-03-21 16:22:37 -07:00
parent 9ed39d5cd0
commit b7aadf66cd
2 changed files with 28 additions and 7 deletions

View File

@ -452,6 +452,25 @@ app.all("/encryptid/*", async (c) => {
} }
}); });
// ── User API proxy (forward /api/user/* to EncryptID for tab state, prefs) ──
app.all("/api/user/*", async (c) => {
const targetUrl = `${ENCRYPTID_INTERNAL}${c.req.path}${new URL(c.req.url).search}`;
const headers = new Headers(c.req.raw.headers);
headers.delete("host");
try {
const res = await fetch(targetUrl, {
method: c.req.method,
headers,
body: c.req.method !== "GET" && c.req.method !== "HEAD" ? c.req.raw.body : undefined,
// @ts-ignore duplex needed for streaming request bodies
duplex: "half",
});
return new Response(res.body, { status: res.status, headers: res.headers });
} catch (e: any) {
return c.json({ error: "EncryptID service unavailable" }, 502);
}
});
// ── Existing /api/communities/* routes (backward compatible) ── // ── Existing /api/communities/* routes (backward compatible) ──
/** Resolve a community slug to SpaceAuthConfig for the SDK guard */ /** Resolve a community slug to SpaceAuthConfig for the SDK guard */

View File

@ -236,17 +236,18 @@ export class RStackMi extends HTMLElement {
} }
}); });
// Close panel on outside click // Close panel on outside click — use composedPath to pierce Shadow DOM
document.addEventListener("click", (e) => { document.addEventListener("click", (e) => {
if (!this.contains(e.target as Node) && !pill.contains(e.target as Node)) { const path = e.composedPath();
if (!path.includes(this)) {
panel.classList.remove("open"); panel.classList.remove("open");
bar.classList.remove("focused"); bar.classList.remove("focused");
} }
}); });
// Prevent internal clicks from closing // Prevent internal clicks from closing
panel.addEventListener("click", (e) => e.stopPropagation()); panel.addEventListener("mousedown", (e) => e.stopPropagation());
bar.addEventListener("click", (e) => e.stopPropagation()); bar.addEventListener("mousedown", (e) => e.stopPropagation());
// Minimize button // Minimize button
this.#shadow.getElementById("mi-minimize")!.addEventListener("click", () => this.#minimize()); this.#shadow.getElementById("mi-minimize")!.addEventListener("click", () => this.#minimize());
@ -661,7 +662,7 @@ export class RStackMi extends HTMLElement {
const STYLES = ` const STYLES = `
:host { display: contents; } :host { display: contents; }
.mi { position: relative; flex: 1; max-width: 480px; min-width: 0; } .mi { position: relative; flex: 1; max-width: 480px; min-width: 0; z-index: 300; }
/* ── Search bar in header ── */ /* ── Search bar in header ── */
.mi-bar { .mi-bar {
@ -703,12 +704,13 @@ const STYLES = `
/* ── Rich panel ── */ /* ── Rich panel ── */
.mi-panel { .mi-panel {
position: fixed; top: 56px; right: 16px; position: absolute; top: calc(100% + 8px); left: 50%;
transform: translateX(-50%);
width: 520px; max-width: calc(100vw - 32px); width: 520px; max-width: calc(100vw - 32px);
height: 65vh; max-height: calc(100vh - 72px); min-height: 300px; height: 65vh; max-height: calc(100vh - 72px); min-height: 300px;
border-radius: 14px; overflow: hidden; border-radius: 14px; overflow: hidden;
box-shadow: 0 12px 40px rgba(0,0,0,0.3); box-shadow: 0 12px 40px rgba(0,0,0,0.3);
display: none; z-index: 300; display: none;
background: var(--rs-bg-surface); border: 1px solid var(--rs-border); background: var(--rs-bg-surface); border: 1px solid var(--rs-border);
resize: vertical; resize: vertical;
} }