feat(ui): rename nav items — "Sources" becomes "RAG", "Git sources" becomes "Sources"
Owner request (2026-08-28): the two admin-only nav items read like the same thing, so they are relabeled — the document-catalog link (#nav-sources, /sources.html) becomes "RAG" and the source-manager link (#nav-git-sources, /git-sources.html) becomes "Sources". Phase 48 (48_nav_rename_sources), label-only per the locked decision: - all six pages (index, sources, git-sources, tuning, document, login): the two <a> texts swap; ids, hrefs, hidden defaults, is-active / aria-current placement, and nav order (Chat, RAG, Sources, Tuning) are byte-unchanged otherwise. - header.js: comment/docstring label mentions only — the reveal-by-id logic is untouched (ship-hidden/reveal contract intact). - test_git_sources_admin.py / test_mobile_hamburger_nav.py: the two suites that asserted the old label text are updated; comment-only label fixes in test_shared_header.py / test_nav_consistency.py. - tests/e2e/test_nav_rename_sources.py: the story E2E (green in isolation) — renamed labels + unchanged hrefs/order/markers on all six pages, click navigation with the active marker, the anonymous ship-hidden contract, and regression guards for the untouched controls (#sync-label "Sync sources", viewer #doc-back "Sources"). - All eight surrounding header/nav suites stay green in isolation; unit+integration green, app/ coverage 99% (frontend-only change), ruff + pyright clean. Note: per this phase file-level staging, the six page files and header.js also carry the same-day in-flight owner rework that was already in the working tree when phase 48 ran (mobile sign-in dropdown copy, sync button ship-hidden on the Sources page); the label rename itself is the two-text swap on each page.
This commit is contained in:
@@ -8,13 +8,15 @@ Run in isolation (DB must be up: ``podman compose up -d db``):
|
||||
Contract under test (owner report 2026-08-23, phase 19) — ONE bar per
|
||||
page, the same controls everywhere:
|
||||
|
||||
* chat / sources / viewer: brand + nav [Chat, Sources — admin only] +
|
||||
New Chat + Sign in / Sign out;
|
||||
* chat / sources / viewer: brand + nav [Chat, RAG — admin only, + the
|
||||
"Sources" link (phase 35, shipped as "Git sources")] + Sign in /
|
||||
Sign out. The New Chat button is chat-page only — it left the shared
|
||||
bar at owner request (2026-08-28, moved to index.html's .chat-shell);
|
||||
* document viewer: the standard bar (row 1) + back + title + meta in a
|
||||
second titlebar row (phase 34, owner confirmation 2026-08-26 — the
|
||||
viewer's old "no nav" single-row bar is superseded; it now carries
|
||||
the SAME nav contract as every other page);
|
||||
* the "Sources" nav link (``#nav-sources``) is HIDDEN for anonymous
|
||||
* the "RAG" nav link (``#nav-sources``) is HIDDEN for anonymous
|
||||
users on every page and shown for admin (phase-16 UX revision with
|
||||
owner permission; the soft-gate page and the A10 API split are
|
||||
untouched) — now on the viewer as well (phase 34);
|
||||
@@ -33,7 +35,7 @@ Test → story mapping (Playwright Mapping Rule):
|
||||
1. ``test_anonymous_bar_on_all_pages``
|
||||
2. ``test_admin_bar_on_all_pages``
|
||||
3. ``test_sources_nav_hidden_for_anonymous_everywhere``
|
||||
4. ``test_new_chat_from_sources_clears_and_navigates``
|
||||
4. ``test_new_chat_button_is_chat_page_only``
|
||||
5. ``test_sign_out_from_viewer_returns_to_anonymous``
|
||||
6. ``test_mobile_bar_fits_and_heights_held``
|
||||
"""
|
||||
@@ -136,17 +138,33 @@ def assert_shared_bar(page: Page, admin: bool, page_kind: str, mobile: bool = Fa
|
||||
pinned by ``test_mobile_hamburger_nav.py`` (phase 46, task 03); this
|
||||
helper pins the bar-level contract only.
|
||||
"""
|
||||
# Settled auth state: exactly one of Sign in / Sign out is visible
|
||||
# (phase-16 semantics, now owned by the shared module).
|
||||
# Settled auth state: exactly one of Sign in / Sign out is
|
||||
# revealed (phase-16 semantics, now owned by the shared module).
|
||||
# Both probes are viewport-independent attribute checks — at ≤640px
|
||||
# the bar auth copies are CSS-hidden behind the #sign-in-link-mobile
|
||||
# / #sign-out-btn-mobile dropdown copies (phase-46 UX revision), so
|
||||
# visibility is not a cross-viewport probe.
|
||||
if admin:
|
||||
expect(page.locator("#sign-out-btn")).to_be_visible(timeout=15_000)
|
||||
page.wait_for_function(
|
||||
"() => !document.querySelector('#nav-sources').hasAttribute('hidden')",
|
||||
timeout=15_000,
|
||||
)
|
||||
expect(page.locator("#sign-in-link")).to_be_hidden()
|
||||
else:
|
||||
expect(page.locator("#sign-in-link")).to_be_visible(timeout=15_000)
|
||||
page.wait_for_function(
|
||||
"() => !document.querySelector('#sign-in-link').hasAttribute('hidden')",
|
||||
timeout=15_000,
|
||||
)
|
||||
expect(page.locator("#sign-out-btn")).to_be_hidden()
|
||||
|
||||
# New Chat is on the bar on every page kind (the owner's ask).
|
||||
expect(page.locator("#new-chat-btn")).to_be_visible()
|
||||
# The New chat button is chat-page only (moved from the shared bar
|
||||
# to index.html's .chat-shell at owner request, 2026-08-28).
|
||||
if page_kind == "chat":
|
||||
expect(page.locator("#new-chat-btn")).to_be_visible()
|
||||
else:
|
||||
assert page.locator("#new-chat-btn").count() == 0, (
|
||||
f"{page_kind}: the New chat button is chat-page only"
|
||||
)
|
||||
|
||||
# Phase 34: EVERY page kind — viewer included — carries the SAME
|
||||
# nav contract: the Chat link always visible; the admin-only
|
||||
@@ -246,7 +264,7 @@ def test_admin_bar_on_all_pages(
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 3. The Sources nav link: hidden for anonymous everywhere, revealed
|
||||
# 3. The RAG nav link: hidden for anonymous everywhere, revealed
|
||||
# after a real login (a toggle, not just initial state)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@@ -282,51 +300,26 @@ def test_sources_nav_hidden_for_anonymous_everywhere(
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 4. New Chat from a non-chat page: clear the conversation, land on the
|
||||
# chat empty state
|
||||
# 4. The New chat button is chat-page only (owner rework 2026-08-28 —
|
||||
# it left the shared bar with the 'go to the chat, fresh' behavior)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_new_chat_from_sources_clears_and_navigates(
|
||||
page: Page, app_url: str, db_ready: None
|
||||
def test_new_chat_button_is_chat_page_only(
|
||||
page: Page, app_url: str, mock_llm: int, db_ready: None
|
||||
) -> None:
|
||||
page.set_viewport_size({"width": 1280, "height": 800})
|
||||
_seed_db(mock_llm)
|
||||
|
||||
# Seed the phase-14 conversation before any page script runs. The
|
||||
# init script runs on EVERY navigation, so it is scoped to the
|
||||
# sources page — the post-click navigation to "/" must start clean.
|
||||
page.add_init_script(
|
||||
"""(() => {
|
||||
if (location.pathname !== "/sources.html") return;
|
||||
try {
|
||||
localStorage.setItem("bor.chat.v1", JSON.stringify({
|
||||
v: 1,
|
||||
messages: [
|
||||
{ who: "user", text: "hello brain" },
|
||||
{ who: "brain", text: "hey there" }
|
||||
]
|
||||
}));
|
||||
} catch {}
|
||||
})();"""
|
||||
)
|
||||
# No non-chat page carries the button anymore…
|
||||
for path in (SOURCES_URL, VIEWER_URL, "/tuning.html", "/login.html", "/git-sources.html"):
|
||||
page.goto(app_url + path)
|
||||
expect(page.locator("#new-chat-btn")).to_have_count(0)
|
||||
|
||||
page.goto(app_url + SOURCES_URL)
|
||||
# The seeded conversation is in storage…
|
||||
assert (
|
||||
page.evaluate("() => localStorage.getItem('bor.chat.v1')") is not None
|
||||
), "init script must have seeded the phase-14 conversation key"
|
||||
|
||||
# New Chat from the sources page: a new chat means going to the
|
||||
# chat — fresh.
|
||||
page.click("#new-chat-btn")
|
||||
expect(page).to_have_url(app_url + "/", timeout=30_000)
|
||||
|
||||
# …and the chat lands on its empty state with the key removed.
|
||||
expect(page.locator("#empty-state")).to_be_visible()
|
||||
expect(page.locator(".msg")).to_have_count(0)
|
||||
assert page.evaluate("() => localStorage.getItem('bor.chat.v1')") is None, (
|
||||
"New Chat from a non-chat page must clear the localStorage key"
|
||||
)
|
||||
# …and the chat page has exactly one (visible, inside .chat-shell).
|
||||
page.goto(app_url + "/")
|
||||
expect(page.locator("#new-chat-btn")).to_have_count(1)
|
||||
expect(page.locator("#new-chat-btn")).to_be_visible()
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user