feat(ui): shared header — Sign in/Sign out and New Chat on every page; hide the Sources nav link from anonymous users

This commit is contained in:
2026-08-24 12:32:45 -04:00
parent fd7f02ce68
commit 2afc77ee56
14 changed files with 904 additions and 59 deletions
+22 -26
View File
@@ -63,6 +63,8 @@
* All DOM ids match frontend/index.html.
*/
import { fetchIsAdmin, initSharedHeader } from "/assets/header.js";
const messagesEl = document.querySelector("#messages");
const emptyState = document.querySelector("#empty-state");
const suggestionsEl = document.querySelector("#suggestions");
@@ -756,9 +758,19 @@ function rememberBrainTurn(rawText, meta) {
* tuning surface at all — the Tuning toggle + panel are removed from the
* DOM (the story says "absent", not just hidden), /api/steering is never
* fetched, and appendTuneButton injects nothing (new or restored
* messages). Admin → Sign out (POST /api/logout + reload) + the full
* phase-15 UI. Whoami is awaited BEFORE the phase-14 restore, so restored
* brain bubbles never flash a Tune button that should not be there.
* messages). Admin → Sign out + the full phase-15 UI. Whoami is awaited
* BEFORE the phase-14 restore, so restored brain bubbles never flash a
* Tune button that should not be there.
*
* Phase 19: the whoami fetch, the Sign in / Sign out / Sources-nav
* toggling, and the #sign-out-btn click binding (POST /api/logout +
* reload) all moved to the shared header module (assets/header.js) —
* initSharedHeader() does the header toggling on every page, and
* fetchIsAdmin() is the single cached whoami, so this page still makes
* exactly one request per load. applyAuthState keeps only the
* chat-page-specific work (removing the tuning surface for anonymous
* visitors) — idempotent alongside the header module's own link/button
* toggling.
*/
const signInLink = document.querySelector("#sign-in-link");
const signOutBtn = document.querySelector("#sign-out-btn");
@@ -773,27 +785,6 @@ function applyAuthState() {
}
}
async function loadAuthState() {
try {
const r = await fetch("/api/whoami");
if (r.ok) isAdmin = (await r.json()).authenticated === true;
} catch {
isAdmin = false; // API unreachable: anonymous-safe defaults
}
applyAuthState();
return isAdmin;
}
if (signOutBtn) {
signOutBtn.addEventListener("click", async () => {
signOutBtn.disabled = true;
try {
await fetch("/api/logout", { method: "POST" });
} catch { /* the reload resets the UI either way */ }
window.location.reload();
});
}
const newChatBtn = document.querySelector("#new-chat-btn");
function startNewChat() {
if (uiState === UI_STATE.thinking || uiState === UI_STATE.streaming) return;
@@ -983,9 +974,14 @@ composer.addEventListener("submit", handleSend);
/* Boot: auth state FIRST — it decides whether the restored conversation
gets Tune buttons and whether the steering UI exists at all (phase 16).
Phase 14: the conversation then comes back exactly as left. */
Phase 14: the conversation then comes back exactly as left. Phase 19:
the shared header module runs the whoami (cached — exactly one
request per page load) and toggles the Sign in/out pair + the Sources
nav link; applyAuthState() then applies the chat-page-only gating. */
(async () => {
await loadAuthState();
await initSharedHeader(); // header.js: whoami + Sign in/out + #nav-sources
isAdmin = await fetchIsAdmin(); // the same cached promise — one whoami
applyAuthState(); // chat page: the admin-only tuning surface
restoreConversation();
loadSuggestions();
loadHealth();