web: the sidebar heading is the space's name (#1014)
* web: show the space's name above Chats in the sidebar header The sidebar header now says WHERE you are: the space's name renders as a small muted line above the bold Chats heading, next to the profile avatar. The line mirrors document.title โ served substituted via __GROUP_NAME__ and corrected at runtime when sealed branding decrypts or the name is edited โ via a paintSpaceName() helper called after every title update. Web-only by nature: the native apps hide the web sidebar and draw their own. Co-Authored-By: Claude <noreply@anthropic.com> * web: the sidebar heading IS the space's name (drop the Chats label) Change of plan from the two-line stack: the space's name replaces the Chats heading outright, reusing the title's existing ellipsis so the sidebar width never depends on the name. style.css returns to exactly its pre-branch rule. Co-Authored-By: Claude <noreply@anthropic.com> ---------
Co-authored-by: Claude <noreply@anthropic.com>
3 files changed,
+26
−3
changelog/2026-07-15-space-name-in-sidebar.md+3 −0web/pages/index.html+3 −1web/static/shell.js+20 −2
diff --git a/changelog/2026-07-15-space-name-in-sidebar.md b/changelog/2026-07-15-space-name-in-sidebar.md| new file mode 100644 |
| index 0000000..f330cab |
| --- /dev/null |
| +++ b/changelog/2026-07-15-space-name-in-sidebar.md |
| @@ -0,0 +1,3 @@ |
| +๐ค The heading at the top of the sidebar is now your space's name instead of |
| +"Chats", so you can always see at a glance which space you're in โ handy if |
| +you're a member of more than one. |
diff --git a/web/pages/index.html b/web/pages/index.html| index 48f4aa1..ed7ef8f 100644 |
| --- a/web/pages/index.html |
| +++ b/web/pages/index.html |
| @@ -127,7 +127,9 @@ |
| <button class="avatar-btn" id="me-btn" aria-label="Edit profile" title="Edit profile"> |
| <span class="avatar" id="me-avatar"></span> |
| </button> |
| - <div class="title">Chats</div> |
| + <!-- The heading is the space's name, so the top of the sidebar says WHERE |
| + you are โ kept in step with document.title (sealed branding updates both). --> |
| + <div class="title" id="side-space-name">__GROUP_NAME__</div> |
| <div class="head-actions"> |
| <div class="menu-wrap"> |
| <button class="icon-btn" id="menu-btn" aria-label="Menu" aria-haspopup="true">โฎ</button> |
diff --git a/web/static/shell.js b/web/static/shell.js| index cf5df70..1773bdc 100644 |
| --- a/web/static/shell.js |
| +++ b/web/static/shell.js |
| @@ -624,6 +624,17 @@ async function bootstrapBranding() { |
| } |
| } |
| if (sealedBranding && sealedBranding.name) document.title = sealedBranding.name; |
| + paintSpaceName(); |
| +} |
| + |
| +// paintSpaceName: the sidebar heading IS the space's name (it ellipsizes, so |
| +// the sidebar width never depends on it). document.title is the one source of |
| +// the name on this page (served substituted, corrected at runtime when sealed |
| +// branding decrypts), so the heading just mirrors it โ call after every |
| +// document.title update. |
| +function paintSpaceName() { |
| + const el = document.getElementById("side-space-name"); |
| + if (el) el.textContent = document.title; |
| } |
| // resealBranding: the sealed counterpart of saveGroupSettings โ merge the |
| @@ -649,7 +660,10 @@ async function resealBranding(patch) { |
| }); |
| if (r.ok) { |
| setSealedBranding(next); |
| - if (next.name) document.title = next.name; |
| + if (next.name) { |
| + document.title = next.name; |
| + paintSpaceName(); |
| + } |
| } |
| return r.ok ? "ok" : r.status === 400 ? "invalid" : "error"; |
| } catch { |
| @@ -2557,6 +2571,7 @@ async function onInstanceKeyReady() { |
| function renderApp() { |
| app.innerHTML = ""; |
| app.appendChild(tpl("app-tpl")); |
| + paintSpaceName(); // the template carries the served name; sealed branding may already have corrected the title |
| document.getElementById("new-chat").onclick = showNewChat; |
| document.getElementById("back-btn").onclick = showList; |
| @@ -4988,7 +5003,10 @@ async function saveGroupSettings(patch) { |
| ...patch, |
| }; |
| const r = await fetch("/api/admin/group", { method: "POST", headers: adminAuth(), body: JSON.stringify(body) }); |
| - if (r.ok && patch.name) document.title = patch.name; // header text updates on next load |
| + if (r.ok && patch.name) { |
| + document.title = patch.name; |
| + paintSpaceName(); |
| + } |
| return r.ok ? "ok" : r.status === 400 ? "invalid" : "error"; |
| } catch { |
| return "error"; |