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
+16 -10
View File
@@ -10,7 +10,8 @@ Pinned design (PLAN §7.4 note / phase 14):
* save points: user message on send, brain message on ``done``;
* every ``localStorage`` access wrapped in try/catch (failure-safe);
* size budget ~700k chars, oldest dropped first;
* ``#new-chat-btn`` in the chat header (chat page only), ≥44px, ghost pill.
* ``#new-chat-btn`` in the shared header bar (chat, sources, viewer —
phase 19, owner permission 2026-08-23), ≥44px, ghost pill.
"""
from __future__ import annotations
@@ -144,24 +145,29 @@ def test_new_chat_clears_key_and_ui() -> None:
assert "removeItem(STORAGE_KEY)" in js
def test_new_chat_button_in_chat_header_only() -> None:
"""#new-chat-btn lives in the chat header (index.html) as a real
type=button with an accessible name — and nowhere else (A10: chat-page
only control)."""
def test_new_chat_button_in_the_shared_header_bar() -> None:
"""#new-chat-btn is a real type=button with an accessible name in the
chat header (index.html). Phase 19 (owner permission 2026-08-23): it
is part of the SHARED bar — it also appears in sources.html and the
document viewer, where it means "go to the chat, fresh" (the
clear-storage + navigate binding is pinned in test_shared_header.py)."""
for html in (INDEX_HTML, SOURCES_HTML, DOCUMENT_HTML):
text = html.read_text(encoding="utf-8")
btn = re.search(r'<button[^>]*id="new-chat-btn"[^>]*>', text)
assert btn, f"{html.name} must contain #new-chat-btn"
tag = btn.group(0)
assert 'type="button"' in tag
assert 'aria-label="New chat"' in tag
# Chat page specifics: the button sits after the nav, inside the header.
html = _index()
btn = re.search(r'<button[^>]*id="new-chat-btn"[^>]*>', html)
assert btn, "index.html must contain #new-chat-btn"
tag = btn.group(0)
assert 'type="button"' in tag
assert 'aria-label="New chat"' in tag
nav_idx = html.find('<nav class="app-nav"')
assert nav_idx != -1 and btn.start() > nav_idx, (
"the button belongs after the nav, inside .header-inner"
)
main_idx = html.find('main id="main"')
assert main_idx != -1 and btn.start() < main_idx, "the button belongs in the header"
assert 'id="new-chat-btn"' not in SOURCES_HTML.read_text(encoding="utf-8")
assert 'id="new-chat-btn"' not in DOCUMENT_HTML.read_text(encoding="utf-8")
def test_new_chat_button_style_contract() -> None: