various fixes

This commit is contained in:
2026-08-28 09:42:19 -04:00
parent 5d679f5184
commit 03bead092c
19 changed files with 983 additions and 1443 deletions
+12 -6
View File
@@ -213,14 +213,17 @@ def test_admin_login_unlocks_sources_and_tuning(
expect(page.locator("#docs-table")).to_be_visible()
expect(page.locator("#docs-tbody tr")).to_have_count(13)
# Chat: the tuning UI is back — header toggle with count badge,
# Sign out instead of Sign in, Tune under the answer.
# Chat: the tuning UI is back — Sign out instead of Sign in, Tune
# under the answer. The header toggle is NOT back: removed from the
# navbar at owner request (2026-08-28), the panel still ships
# hidden (note management lives on /tuning.html).
page.goto(app_url)
expect(page.locator("#sign-out-btn")).to_be_visible()
expect(page.locator("#sign-in-link")).to_be_hidden()
toggle = page.locator("#steering-toggle")
expect(toggle).to_be_visible()
expect(page.locator("#steering-count")).to_have_text("0")
assert page.locator("#steering-toggle").count() == 0, (
"the steering toggle was removed from the navbar (2026-08-28)"
)
expect(page.locator("#steering-panel")).to_be_hidden()
_ask(page, QUESTION)
tune = page.locator(".msg.brain .tune-btn").last
@@ -249,7 +252,10 @@ def test_logout_returns_to_anonymous(
login(page, app_url, next="/") # straight into the chat
expect(page).to_have_url(app_url + "/")
expect(page.locator("#sign-out-btn")).to_be_visible()
expect(page.locator("#steering-toggle")).to_be_visible()
# The steering toggle was removed from the navbar (2026-08-28) —
# absent for the admin too; the panel still ships hidden.
assert page.locator("#steering-toggle").count() == 0
expect(page.locator("#steering-panel")).to_be_hidden()
# One grounded turn as admin (persisted to localStorage by phase 14).
_ask(page, QUESTION)
+50 -47
View File
@@ -20,15 +20,18 @@ Per role, the VISIBLE inventory:
* admin: brand + nav [Chat, #nav-sources, #nav-git-sources, #nav-tuning]
(four links, that order — the Git sources link joined in phase 35,
owner permission 2026-08-26) + #steering-toggle + #sync-btn +
#new-chat-btn + #sign-out-btn (with #sign-in-link hidden) — on all
five pages, same id+class inventory, same DOM order;
owner permission 2026-08-26) + #sync-btn + #new-chat-btn +
#sign-out-btn (with #sign-in-link hidden) — on all five pages, same
id+class inventory, same DOM order. The #steering-toggle was removed
from the navbar at owner request (2026-08-28); note management lives
on /tuning.html;
* anonymous: brand + nav [Chat] (#nav-sources / #nav-git-sources /
#nav-tuning hidden — locked A10 UI revision) + #new-chat-btn +
#sign-in-link (with #sync-btn hidden, #sign-out-btn hidden) on all
five pages — and the steering toggle + panel are ABSENT from the DOM
(phase 16 "absent, not hidden" treatment, carried into phase 34 task
01; test_admin_auth pins it).
five pages — and the steering toggle (removed at owner request,
2026-08-28) + panel are ABSENT from the DOM (the panel via the phase
16 "absent, not hidden" treatment, carried into phase 34 task 01;
test_admin_auth pins it).
Normalization for the inventory comparison: the current-page ``is-active``
nav marker and the sign-in ``?next=`` value legitimately differ per page,
@@ -40,26 +43,28 @@ page's bar (64px / 58px) and row 2 (``.doc-titlebar``) is present with
(phase 13) is honored — ``back=`` accepted for same-origin relative
URLs, rejected (→ /sources.html) otherwise.
Steering works off-chat: on /tuning.html (admin, zero notes) the toggle
opens/closes #steering-panel with the empty state and a 0 count badge —
no chat needed. Sync is present, not triggered: #sync-btn is visible on
/tuning.html but is never clicked here (a real sync clones real repos —
the full state machine is test_sync_button.py's job).
Steering off-chat: on /tuning.html (admin, zero notes) the navbar
carries no steering toggle (removed at owner request, 2026-08-28) — the
header #steering-panel section still ships hidden and the Tuning
page's own note list shows the empty state — no chat needed. Sync is
present, not triggered: #sync-btn is visible on /tuning.html but is
never clicked here (a real sync clones real repos — the full state
machine is test_sync_button.py's job).
Determinism note: every assertion is settled-state — each page visit
first waits for the whoami toggle to land (exactly one of Sign in /
Sign out visible; the anonymous removal of the steering toggle happens
Sign out visible; the anonymous removal of the steering panel happens
in the SAME initSharedHeader pass) and, on the viewer, for the document
title to render. The seed truncates steering_notes, so the count badge is
0 on every admin page. No chat turn is ever submitted; #sync-btn is
never clicked.
title to render. The seed truncates steering_notes, so the Tuning page
shows the empty state on every admin page. No chat turn is ever
submitted; #sync-btn is never clicked.
Test → story mapping (Playwright Mapping Rule):
1. ``test_admin_inventory_identical_on_all_five_pages``
2. ``test_anonymous_inventory_identical_on_all_five_pages``
3. ``test_viewer_row1_height_matches_chat_and_titlebar_present``
4. ``test_viewer_back_link_honors_back_param``
5. ``test_steering_panel_works_off_chat_on_tuning_page``
5. ``test_steering_surface_off_chat_on_tuning_page``
6. ``test_sync_button_present_on_tuning_page_without_triggering``
"""
from __future__ import annotations
@@ -152,7 +157,6 @@ _INVENTORY_JS = """() => {
const sel = [
".brand",
".app-nav > a.nav-link",
"#steering-toggle",
"#sync-btn",
"#new-chat-btn",
"#sign-in-link",
@@ -180,8 +184,8 @@ def _header_inventory(page: Page) -> list[str]:
def _wait_settled(page: Page, admin: bool) -> None:
"""Wait for initSharedHeader's whoami toggle to land: exactly one of
Sign in / Sign out is visible (both ship hidden in the HTML). For
anonymous visitors the steering toggle + panel removal happens in
the SAME pass, so they are already gone when this returns."""
anonymous visitors the steering panel removal happens in the SAME
pass, so it is already gone when this returns."""
if admin:
expect(page.locator("#sign-out-btn")).to_be_visible(timeout=15_000)
expect(page.locator("#sign-in-link")).to_be_hidden()
@@ -220,7 +224,11 @@ def _visit(page: Page, app_url: str, name: str, url: str, admin: bool) -> list[s
# revealed on every page, between Sources and Tuning.
expect(page.locator("#nav-git-sources")).to_be_visible()
expect(page.locator("#nav-tuning")).to_be_visible()
expect(page.locator("#steering-toggle")).to_be_visible()
# The steering toggle was removed from the navbar at owner
# request (2026-08-28) — absent on every page, admin included.
assert page.locator("#steering-toggle").count() == 0, (
f"{name}: the steering toggle was removed from the navbar"
)
expect(page.locator("#sync-btn")).to_be_visible()
expect(page.locator("#sign-out-btn")).to_be_visible()
expect(page.locator("#sign-in-link")).to_be_hidden()
@@ -233,9 +241,11 @@ def _visit(page: Page, app_url: str, name: str, url: str, admin: bool) -> list[s
expect(page.locator("#sync-btn")).to_be_hidden()
expect(page.locator("#sign-out-btn")).to_be_hidden()
expect(page.locator("#sign-in-link")).to_be_visible()
# …and the steering surface is ABSENT (phase 16 "absent, not
# hidden", carried into the shared module by phase 34 task 01
# — test_admin_auth pins the same contract).
# …and the steering surface is ABSENT (the toggle was removed
# from the navbar at owner request, 2026-08-28; the panel via
# phase 16 "absent, not hidden", carried into the shared module
# by phase 34 task 01 — test_admin_auth pins the same
# contract).
assert page.locator("#steering-toggle").count() == 0, (
f"{name}: the steering toggle must be absent for anonymous"
)
@@ -289,7 +299,9 @@ def _admin_login_page_inventory(page: Page, app_url: str) -> list[str]:
expect(page.locator("#nav-sources")).to_be_visible()
expect(page.locator("#nav-git-sources")).to_be_visible()
expect(page.locator("#nav-tuning")).to_be_visible()
expect(page.locator("#steering-toggle")).to_be_visible()
assert page.locator("#steering-toggle").count() == 0, (
"login: the steering toggle was removed from the navbar"
)
expect(page.locator("#sync-btn")).to_be_visible()
expect(page.locator("#sign-out-btn")).to_be_visible()
expect(page.locator("#sign-in-link")).to_be_hidden()
@@ -434,13 +446,14 @@ def test_viewer_back_link_honors_back_param(
# ---------------------------------------------------------------------------
# 5. Steering works off-chat: on /tuning.html (admin, zero notes) the
# header toggle drives the panel — open/close cycle, empty state,
# count badge 0. No chat turn is needed.
# 5. Steering off-chat: on /tuning.html (admin, zero notes) the navbar
# carries no steering toggle (removed at owner request, 2026-08-28)
# — the header panel section still ships hidden and the Tuning
# page's own note list shows the empty state. No chat turn needed.
# ---------------------------------------------------------------------------
def test_steering_panel_works_off_chat_on_tuning_page(
def test_steering_surface_off_chat_on_tuning_page(
page: Page, app_url: str, mock_llm: int, db_ready: None
) -> None:
page.set_viewport_size({"width": 1280, "height": 800})
@@ -450,26 +463,16 @@ def test_steering_panel_works_off_chat_on_tuning_page(
expect(page).to_have_url(app_url + TUNING_URL, timeout=30_000)
expect(page.locator("#sign-out-btn")).to_be_visible(timeout=15_000)
# Settled admin state: the toggle is on the bar, the panel ships
# hidden, and the count badge reads 0 (zero seeded notes).
expect(page.locator("#steering-toggle")).to_be_visible()
# Settled admin state: the navbar toggle is GONE (removed at owner
# request, 2026-08-28), the header panel section still ships
# hidden, and the Tuning page's own note list shows the empty
# state (zero seeded notes).
assert page.locator("#steering-toggle").count() == 0, (
"the steering toggle was removed from the navbar"
)
expect(page.locator("#steering-panel")).to_be_hidden()
expect(page.locator("#steering-count")).to_have_text("0")
# Open: the panel shows, the toggle's aria-expanded follows, and
# the empty state is visible (the re-open refresh fetched 0 notes).
page.click("#steering-toggle")
expect(page.locator("#steering-panel")).to_be_visible()
expect(page.locator("#steering-toggle")).to_have_attribute("aria-expanded", "true")
expect(page.locator("#steering-empty")).to_be_visible()
expect(page.locator("#steering-list .steering-note")).to_have_count(0)
expect(page.locator("#steering-count")).to_have_text("0")
# Close: the cycle completes, the count badge still reads 0.
page.click("#steering-toggle")
expect(page.locator("#steering-panel")).to_be_hidden()
expect(page.locator("#steering-toggle")).to_have_attribute("aria-expanded", "false")
expect(page.locator("#steering-count")).to_have_text("0")
expect(page.locator("#tune-list .tuning-note")).to_have_count(0)
expect(page.locator("#tune-empty")).to_be_visible()
# ---------------------------------------------------------------------------
+52 -62
View File
@@ -13,8 +13,9 @@ stored in Postgres (``steering_notes``) → injected into the system prompt
of every subsequent turn as the ``<tuning>`` section. The mock LLM
echoes the first tuning note into its answer
(`` (tuning: <first note line>)``), so prompt injection is observable in
the UI deterministically. Notes are listed newest-first in the header
"Tuning" panel, where each can be deleted.
the UI deterministically. Notes are listed newest-first on the Tuning
page (``/tuning.html``), where each can be deleted — the header "Tuning"
toggle was removed from the navbar at owner request (2026-08-28).
Test → story mapping (Playwright Mapping Rule):
1. ``test_tune_under_answer_persists_and_steers``
@@ -45,8 +46,9 @@ QUESTION = "How is my Kubernetes cluster set up?"
MOCK_ANSWER_MARKER = "Deterministic mock answer for E2E"
NOTE = "STEEER-MARKER be concise"
XSS_NOTE = "<script>window.__xss = true; alert('xss')</script>"
#: index.html ships exactly three classic/module script tags: the
#: phase-39 brand.js classic layer + markdown.js + the app.js module.
#: Both index.html and tuning.html ship exactly three classic/module
#: script tags: the phase-39 brand.js classic layer + markdown.js + the
#: page module (app.js / tuning.js).
BASE_SCRIPT_COUNT = 3
@@ -113,11 +115,6 @@ def _tune_and_save(page: Page, note: str) -> None:
expect(saved).to_contain_text("Saved — future answers will follow this.", timeout=15_000)
def _open_panel(page: Page) -> None:
page.click("#steering-toggle")
expect(page.locator("#steering-panel")).to_be_visible()
# ---------------------------------------------------------------------------
# 1. Tune under an answer → persisted → next answer carries the note
# ---------------------------------------------------------------------------
@@ -147,14 +144,15 @@ def test_tune_under_answer_persists_and_steers(
rows = db.scalars(select(SteeringNote)).all()
assert [r.note for r in rows] == [NOTE]
# The header panel shows the note with an updated count badge.
_open_panel(page)
expect(page.locator("#steering-count")).to_have_text("1")
expect(page.locator("#steering-list .steering-note")).to_have_count(1)
expect(page.locator("#steering-list .steering-note-text")).to_have_text(NOTE)
page.click("#steering-toggle") # close again
# The Tuning page (the steering-notes manager — the navbar toggle
# was removed at owner request, 2026-08-28) lists the note.
page.goto(app_url + "/tuning.html")
expect(page.locator("#tune-list .tuning-note")).to_have_count(1)
expect(page.locator("#tune-list .tuning-note-text")).to_have_text(NOTE)
# The NEXT answer carries the note — it reached the system prompt.
page.goto(app_url + "/")
expect(page.locator("#send-btn")).to_be_enabled()
_ask(page, QUESTION)
bubble = page.locator(".msg.brain .bubble").last
expect(bubble).to_contain_text(f"(tuning: {NOTE})")
@@ -179,19 +177,21 @@ def test_delete_note_stops_steering(
_ask(page, QUESTION)
expect(page.locator(".msg.brain .bubble").last).to_contain_text(f"(tuning: {NOTE})")
# Delete the note from the panel.
_open_panel(page)
expect(page.locator("#steering-count")).to_have_text("1")
page.locator("#steering-list .steering-delete").click()
expect(page.locator("#steering-list .steering-note")).to_have_count(0)
expect(page.locator("#steering-count")).to_have_text("0")
expect(page.locator("#steering-empty")).to_be_visible()
expect(page.locator("#steering-announcer")).to_contain_text("deleted")
# Delete the note from the Tuning page (the header panel is no
# longer reachable — the navbar toggle is gone).
page.goto(app_url + "/tuning.html")
expect(page.locator("#tune-list .tuning-note")).to_have_count(1)
page.locator(".tuning-delete").click()
expect(page.locator("#tune-list .tuning-note")).to_have_count(0, timeout=15_000)
expect(page.locator("#tune-empty")).to_be_visible()
expect(page.locator("#tune-announcer")).to_contain_text("deleted")
with SessionLocal() as db:
assert db.scalars(select(SteeringNote)).all() == []
# The next answer no longer carries the marker.
page.goto(app_url + "/")
expect(page.locator("#send-btn")).to_be_enabled()
_ask(page, QUESTION)
bubble = page.locator(".msg.brain .bubble").last
expect(bubble).to_contain_text(MOCK_ANSWER_MARKER)
@@ -222,12 +222,13 @@ def test_note_rendered_as_text_xss_safe(
_ask(page, QUESTION)
_tune_and_save(page, XSS_NOTE)
# Panel: the payload is visible as LITERAL text…
_open_panel(page)
expect(page.locator("#steering-list .steering-note-text")).to_have_text(XSS_NOTE)
# Tuning page: the payload is visible as LITERAL text…
page.goto(app_url + "/tuning.html")
expect(page.locator("#tune-list .tuning-note-text")).to_have_text(XSS_NOTE)
# …never as an executed element: no script tag anywhere, no dialog.
assert page.locator("#steering-panel script").count() == 0
# …never as an executed element: no script tag in the list, the page
# still carries exactly its own three scripts, no dialog.
assert page.locator("#tune-list script").count() == 0
expect(page.locator("script")).to_have_count(BASE_SCRIPT_COUNT)
assert dialogs == [], f"the note must never execute as script: {dialogs}"
assert page.evaluate("() => window.__xss === undefined") is True
@@ -239,34 +240,27 @@ def test_note_rendered_as_text_xss_safe(
def test_tuning_panel_a11y(page: Page, app_url: str, db_ready: None) -> None:
_reset_db(mock_port=0, seed=False) # no KB seeding needed for the panel a11y
"""The steering surface's a11y now lives on the Tuning page (the
header toggle was removed from the navbar at owner request,
2026-08-28): the note list, the polite live region, and the labeled
per-note delete (≥44px)."""
_reset_db(mock_port=0, seed=False) # no KB seeding needed for the page a11y
page.set_default_timeout(30_000)
page.goto(app_url)
login(page, app_url, next="/") # phase 16: the panel is admin-only
login(page, app_url, next="/tuning.html") # phase 16: the notes are admin-only
toggle = page.locator("#steering-toggle")
panel = page.locator("#steering-panel")
announcer = page.locator("#steering-announcer")
# The navbar no longer carries a steering toggle — absent for the
# admin too — and the header panel section still ships hidden.
assert page.locator("#steering-toggle").count() == 0
expect(page.locator("#steering-panel")).to_be_hidden()
# Initial: closed, correctly wired, polite live region present.
expect(toggle).to_have_attribute("aria-expanded", "false")
expect(toggle).to_have_attribute("aria-controls", "steering-panel")
expect(panel).to_have_attribute("role", "region")
assert "Tuning notes" in (panel.get_attribute("aria-label") or "")
expect(panel).to_be_hidden()
# The page's own polite live region.
announcer = page.locator("#tune-announcer")
assert announcer.get_attribute("role") == "status"
assert announcer.get_attribute("aria-live") == "polite"
# Accessible name comes from its visible text (icon is aria-hidden).
assert "Tuning" in toggle.inner_text()
# Open: expanded + the designed empty state.
toggle.click()
expect(toggle).to_have_attribute("aria-expanded", "true")
expect(panel).to_be_visible()
expect(page.locator("#steering-empty")).to_be_visible()
expect(page.locator("#steering-count")).to_have_text("0")
# Add a note (API), then re-open the panel to refresh it.
# Add a note (API), reload to refresh the list (tuning.js fetches
# on boot).
page.evaluate(
"""async () => {
const r = await fetch('/api/steering', {
@@ -277,26 +271,22 @@ def test_tuning_panel_a11y(page: Page, app_url: str, db_ready: None) -> None:
if (!r.ok) throw new Error('steering POST failed: ' + r.status);
}"""
)
toggle.click() # close
toggle.click() # re-open (refreshes the list)
note_item = page.locator("#steering-list .steering-note")
page.reload()
expect(page.locator("#sign-out-btn")).to_be_visible(timeout=15_000)
note_item = page.locator("#tune-list .tuning-note")
expect(note_item).to_have_count(1)
expect(note_item.locator(".steering-note-text")).to_have_text("a11y note one")
expect(note_item.locator(".tuning-note-text")).to_have_text("a11y note one")
# The per-note delete is a real, labeled button (≥44px target).
delete = page.locator("#steering-list .steering-delete")
delete = page.locator(".tuning-delete")
expect(delete).to_have_attribute("type", "button")
assert (delete.get_attribute("aria-label") or "").startswith("Delete tuning note:")
box = delete.bounding_box()
assert box is not None and box["height"] >= 44
# Delete: list empties, count updates, the live region announces it.
# Delete: list empties, the empty state shows, the live region
# announces it.
delete.click()
expect(page.locator("#steering-list .steering-note")).to_have_count(0)
expect(page.locator("#steering-count")).to_have_text("0")
expect(page.locator("#tune-list .tuning-note")).to_have_count(0, timeout=15_000)
expect(page.locator("#tune-empty")).to_be_visible()
expect(announcer).to_contain_text("deleted")
# And the toggle closes cleanly again.
toggle.click()
expect(toggle).to_have_attribute("aria-expanded", "false")
expect(panel).to_be_hidden()
+67 -143
View File
@@ -6,27 +6,25 @@ toggle) to ANONYMOUS visitors — it shipped VISIBLE in all six pages'
markup and ``assets/header.js`` removed it only after ``/api/whoami``
resolved, so the button flashed for the whole whoami round-trip.
The fix mirrors the admin-only nav links (phase 19/29/35 contract):
the toggle now SHIPS ``hidden`` in every page and ``initSharedHeader``
unhides it only when whoami says admin; the anonymous end-state is
unchanged (toggle + panel REMOVED from the DOM — phase 16 "absent,
not hidden"). This suite proves the browser-level contract:
Phase 40 fixed it with the ship-hidden / reveal-for-admin contract
(the admin-only nav links). The owner then asked for the button to go
away entirely (2026-08-28): the navbar ``#steering-toggle`` is now
ABSENT from every page — for the admin AND anonymous — so the
never-visible contract holds by construction, and note management
lives on ``/tuning.html``. The anonymous end-state is unchanged: the
``#steering-panel`` is REMOVED from the DOM (phase 16 "absent, not
hidden") and ``/api/steering`` is never fetched. This suite proves the
browser-level contract:
* a MutationObserver (installed via ``add_init_script`` before any
page code runs) records every frame in which ``#steering-toggle``
is both in the DOM and visible (``offsetParent !== null`` or
``!hidden``) — an anonymous load records ZERO such frames, on
every page, from first paint to the settled state;
* after the whoami round-trip the toggle is ABSENT from the DOM for
anonymous visitors (removed, not hidden);
* the admin UX is untouched (phase 15/34 behavior): the toggle is
revealed, clicking opens ``#steering-panel``
(``aria-expanded="true"``) and the count badge matches the note
list — including a self-check that the observer records the admin
reveal, so the zero-frame anonymous assertions are not vacuous;
* the ship-hidden nav-link contract this phase relies on
(``#nav-sources`` / ``#nav-git-sources`` / ``#nav-tuning``) is
intact: hidden for anonymous, revealed for admin.
* ``#steering-toggle`` is absent from the DOM on every page, for
anonymous AND admin — nothing can flash, because nothing ships;
* the anonymous end-state survives the removal: ``#steering-panel``
is removed from the DOM, not just hidden;
* the admin UX around the removal is intact: the header panel section
still ships hidden (never opened from the header anymore), and the
ship-hidden nav-link contract this phase relies on
(``#nav-sources`` / ``#nav-git-sources`` / ``#nav-tuning``) holds:
hidden for anonymous, revealed for admin.
Story: ``.agent/user_stories/tuning-toggle-flash.md``
Run in isolation (DB must be up: ``podman compose up -d db``):
@@ -42,82 +40,27 @@ Test → story mapping (Playwright Mapping Rule):
from __future__ import annotations
from playwright.sync_api import Page, expect
from sqlalchemy import text
from app.db import SessionLocal
from e2e.auth_helpers import login
#: The pages the contract must hold on besides the chat page (mapping
#: rule 2). document.html / git-sources.html are covered by the
#: source-level unit pins (tests/unit/test_steering_toggle_visibility.py)
#: — the four pages here are the ones an anonymous visitor actually
#: lands on.
#: source-level unit pins (tests/unit/test_steering_toggle_removal.py)
#: — the three pages here are the other pages an anonymous visitor
#: actually lands on.
OTHER_PAGES = ("/sources.html", "/tuning.html", "/login.html")
ADMIN_NOTE = "PHASE40-E2E note — badge check"
#: Admin-only nav links — the ship-hidden / reveal-for-admin family the
#: steering toggle now belongs to (phase 19/29/35 contract).
#: steering toggle belonged to (phase 19/29/35 contract).
NAV_IDS = ("#nav-sources", "#nav-git-sources", "#nav-tuning")
#: Runs in every new document BEFORE any page script (addInitScript):
#: arms a MutationObserver over the whole DOM and records every frame
#: in which #steering-toggle is attached AND visible — visible meaning
#: rendered (offsetParent !== null) OR carrying no [hidden] attribute
#: (!el.hidden). A shipped-VISIBLE toggle (the old bug) is recorded the
#: moment the parser inserts it; a shipped-hidden toggle that is later
#: revealed is recorded at the reveal mutation. The array is fresh per
#: document, so each navigation asserts its own frames.
VISIBILITY_OBSERVER_JS = """
window.__tuningVisibleFrames = [];
(() => {
const visible = (el) =>
!!el && el.isConnected && (el.offsetParent !== null || !el.hidden);
const check = () => {
if (visible(document.getElementById("steering-toggle"))) {
window.__tuningVisibleFrames.push({
at: Math.round(performance.now()),
href: location.pathname,
});
}
};
const start = () => {
check();
new MutationObserver(check).observe(document.documentElement, {
childList: true,
subtree: true,
attributes: true,
attributeFilter: ["hidden"],
});
};
if (document.documentElement) start();
else document.addEventListener("DOMContentLoaded", start);
})();
"""
def install_visibility_observer(page: Page) -> None:
"""Arm the never-visible frame counter on every document this page
creates — the initial load, the post-login redirect, re-gotos."""
page.add_init_script(VISIBILITY_OBSERVER_JS)
def visible_frames(page: Page) -> list[dict[str, object]]:
"""The frames the observer recorded in the CURRENT document."""
return page.evaluate("() => window.__tuningVisibleFrames || []")
def _assert_no_flash(page: Page, path: str) -> None:
"""Zero visible frames + the phase-16 absent end-state, for one
anonymously loaded page."""
frames = visible_frames(page)
assert frames == [], f"{path}: the toggle was visible {len(frames)}x: {frames!r}"
def _assert_no_toggle(page: Page, path: str) -> None:
"""The navbar toggle is absent from the DOM for one page — nothing
to flash, for either role."""
assert page.locator("#steering-toggle").count() == 0, (
f"{path}: #steering-toggle must be REMOVED from the DOM for "
"anonymous (phase 16 'absent, not hidden')"
)
assert page.locator("#steering-panel").count() == 0, (
f"{path}: #steering-panel must be removed together with the toggle"
f"{path}: #steering-toggle must be ABSENT from the DOM "
"(removed from the navbar at owner request, 2026-08-28)"
)
@@ -125,14 +68,21 @@ def _wait_header_settled_anonymous(page: Page) -> None:
"""Whoami resolved on the current document: the anonymous state
reveals the Sign in link and keeps Sign out hidden (the pair is
decided by the SAME initSharedHeader pass that removes the
toggle)."""
steering panel)."""
page.wait_for_load_state("networkidle")
expect(page.locator("#sign-in-link")).to_be_visible(timeout=15_000)
expect(page.locator("#sign-out-btn")).to_be_hidden()
def _wait_header_settled_admin(page: Page) -> None:
"""Whoami resolved for a signed-in admin on the current document."""
page.wait_for_load_state("networkidle")
expect(page.locator("#sign-out-btn")).to_be_visible(timeout=15_000)
expect(page.locator("#sign-in-link")).to_be_hidden()
# ---------------------------------------------------------------------------
# 1. Anonymous chat load: zero visible frames, toggle absent afterwards
# 1. Anonymous chat load: zero toggle, panel removed (never visible)
# ---------------------------------------------------------------------------
@@ -140,14 +90,19 @@ def test_anonymous_never_sees_toggle(
page: Page, app_url: str, mock_llm: int, db_ready: None
) -> None:
page.set_default_timeout(30_000)
install_visibility_observer(page)
page.goto(app_url + "/")
_wait_header_settled_anonymous(page)
_assert_no_flash(page, "/")
_assert_no_toggle(page, "/")
# The phase-16 end-state survives the removal: the panel is REMOVED
# from the DOM, not just hidden.
assert page.locator("#steering-panel").count() == 0, (
"/: #steering-panel must be removed from the DOM for anonymous "
"(phase 16 'absent, not hidden')"
)
# ---------------------------------------------------------------------------
# 2. Anonymous loads of the other pages: same zero-flash contract
# 2. Anonymous loads of the other pages: same no-toggle contract
# ---------------------------------------------------------------------------
@@ -155,15 +110,19 @@ def test_anonymous_other_pages_never_flash(
page: Page, app_url: str, mock_llm: int, db_ready: None
) -> None:
page.set_default_timeout(30_000)
install_visibility_observer(page)
for path in OTHER_PAGES:
page.goto(app_url + path)
_wait_header_settled_anonymous(page)
_assert_no_flash(page, path)
_assert_no_toggle(page, path)
assert page.locator("#steering-panel").count() == 0, (
f"{path}: #steering-panel must be removed from the DOM for "
"anonymous (phase 16 'absent, not hidden')"
)
# ---------------------------------------------------------------------------
# 3. Admin: revealed, clickable, count badge matches the list
# 3. Admin: the toggle is gone too — the panel still ships hidden and
# the nav links are revealed
# ---------------------------------------------------------------------------
@@ -171,60 +130,26 @@ def test_admin_toggle_revealed_and_working(
page: Page, app_url: str, mock_llm: int, db_ready: None
) -> None:
page.set_default_timeout(30_000)
with SessionLocal() as db:
db.execute(text("TRUNCATE steering_notes"))
db.commit()
install_visibility_observer(page)
login(page, app_url, next="/")
page.wait_for_load_state("networkidle")
expect(page.locator("#sign-out-btn")).to_be_visible(timeout=15_000)
_wait_header_settled_admin(page)
toggle = page.locator("#steering-toggle")
panel = page.locator("#steering-panel")
# Revealed — the [hidden] attribute is gone and the button renders.
expect(toggle).to_be_visible()
assert toggle.get_attribute("hidden") is None
# Observer self-check: the reveal IS a recorded visible frame, so
# the zero-frame anonymous assertions above cannot be vacuous.
assert visible_frames(page) != [], "the admin reveal was not observed"
# Click: the panel opens, aria-expanded tracks it (phase 15/34).
toggle.click()
expect(panel).to_be_visible()
expect(toggle).to_have_attribute("aria-expanded", "true")
# Count badge matches the list — first the empty state…
expect(page.locator("#steering-count")).to_have_text("0")
expect(page.locator("#steering-list .steering-note")).to_have_count(0)
expect(page.locator("#steering-empty")).to_be_visible()
# …then with one note created through the real admin API.
page.evaluate(
"""async (note) => {
const r = await fetch("/api/steering", {
method: "POST",
headers: {"Content-Type": "application/json"},
body: JSON.stringify({note}),
});
if (!r.ok) throw new Error("steering POST failed: " + r.status);
}""",
ADMIN_NOTE,
)
toggle.click() # close
toggle.click() # re-open (refreshes the list)
expect(panel).to_be_visible()
expect(toggle).to_have_attribute("aria-expanded", "true")
expect(page.locator("#steering-count")).to_have_text("1")
expect(page.locator("#steering-list .steering-note")).to_have_count(1)
expect(page.locator("#steering-list .steering-note-text")).to_have_text(ADMIN_NOTE)
# The navbar toggle was removed at owner request (2026-08-28) —
# absent for the admin as well; nothing in the header can flash
# it back, because nothing ships it anymore.
_assert_no_toggle(page, "/")
# The header panel section survives (kept fresh by the chat page's
# per-bubble Tune form) but ships hidden — no header control opens
# it anymore; note management lives on /tuning.html.
assert page.locator("#steering-panel").count() == 1
expect(page.locator("#steering-panel")).to_be_hidden()
# The surviving path to the notes: the admin-only Tuning nav link
# is revealed.
expect(page.locator("#nav-tuning")).to_be_visible()
# ---------------------------------------------------------------------------
# 4. Nav-contract regression (phase 19/34): the ship-hidden family the
# toggle now belongs to is intact
# toggle used to belong to is intact
# ---------------------------------------------------------------------------
@@ -245,9 +170,8 @@ def test_nav_contract_regression(
)
# Admin: the same links are revealed — the exact contract the
# steering toggle now mirrors.
# steering toggle mirrored before it was removed.
login(page, app_url, next="/")
page.wait_for_load_state("networkidle")
expect(page.locator("#sign-out-btn")).to_be_visible(timeout=15_000)
_wait_header_settled_admin(page)
for nav in NAV_IDS:
expect(page.locator(nav)).to_be_visible()
+4 -3
View File
@@ -219,8 +219,9 @@ def test_header_z_index_is_20_so_the_dropdown_uses_21() -> None:
def test_mobile_block_renders_the_44px_toggle() -> None:
"""At ≤640px the toggle is a 44px×44px button (the phase-07 touch
floor), ghost look like the other pills, a hover state in the
.steering-toggle:hover family, and a SIZED icon (an unsized inline
SVG defaults to 300px and would blow the bar out at 360px).
shared pill hover family (brand-ink on brand-soft, as
.nav-link:hover), and a SIZED icon (an unsized inline SVG defaults
to 300px and would blow the bar out at 360px).
:focus-visible needs no rule — the global 3px outline applies."""
mobile = _media_block(_css(), "@media (max-width: 640px)")
block = _rule_block(mobile, ".nav-toggle")
@@ -239,7 +240,7 @@ def test_mobile_block_renders_the_44px_toggle() -> None:
hover = _rule_block(mobile, ".nav-toggle:hover")
assert "background: var(--brand-soft)" in hover
assert "color: var(--brand-ink)" in hover, (
"hover matches the .steering-toggle:hover family (≈6.9:1 pair)"
"hover matches the shared pill hover family (≈6.9:1 pair)"
)
icon = _rule_block(mobile, ".nav-toggle svg")
assert "width: 20px" in icon and "height: 20px" in icon, (
+20 -21
View File
@@ -125,8 +125,9 @@ def test_nav_sources_ships_hidden_on_every_nav_page() -> None:
def test_all_five_pages_share_the_header_control_order() -> None:
"""Phase 34 task 03 (owner confirmation 2026-08-26): every page
ships the IDENTICAL header control inventory in the IDENTICAL
order — brand, nav [Chat, Sources, Tuning], #steering-toggle,
#sync-btn, #new-chat-btn, Sign in, Sign out — inside the shared
order — brand, nav [Chat, Sources, Tuning], #sync-btn, #new-chat-btn,
Sign in, Sign out (the #steering-toggle was removed from the navbar
at owner request, 2026-08-28) — inside the shared
.header-inner row (the document viewer's row 1). Only the
current-page is-active nav marker and the static ?next= fallback
may differ per page (task 05's story E2E pins the rendered
@@ -137,7 +138,6 @@ def test_all_five_pages_share_the_header_control_order() -> None:
'href="/"',
'id="nav-sources"',
'id="nav-tuning"',
'id="steering-toggle"',
'id="sync-btn"',
'id="new-chat-btn"',
'id="sign-in-link"',
@@ -288,16 +288,16 @@ def test_login_page_carries_the_full_header() -> None:
"""Phase 34 task 03 (owner confirmation 2026-08-26): the noted
boundary is reversed by owner decision — the login page finally
carries the FULL shared header: the nav gains the #nav-tuning link
(same ship-hidden markup as the other pages), plus the Tuning
toggle, the admin-only Sync button, New chat, and the Sign in /
Sign out pair (ship hidden — initSharedHeader reveals exactly one
after whoami; the static ?next= fallback is the login page itself).
(same ship-hidden markup as the other pages), plus the admin-only
Sync button, New chat, and the Sign in / Sign out pair (ship hidden
— initSharedHeader reveals exactly one after whoami; the static
?next= fallback is the login page itself). The Tuning toggle is NOT
among them — removed from the navbar at owner request (2026-08-28).
No nav link is "current" on the auth page."""
text = _text(LOGIN_HTML)
for marker in (
'id="nav-sources"',
'id="nav-tuning"',
'id="steering-toggle"',
'id="sync-btn"',
'id="new-chat-btn"',
'id="sign-in-link"',
@@ -410,12 +410,11 @@ def test_header_module_owns_the_steering_panel() -> None:
"""header.js owns the steering panel behavior (moved from app.js in
phase 34 task 01): the module-level null-safe element refs, the
newest-first textContent render (XSS contract), the labeled
per-note delete, the count badge, the announcer, and the toggle
binding that runs at module import (like the sign-out binding)."""
per-note delete, and the announcer — and NO toggle wiring anymore
(the navbar #steering-toggle was removed at owner request,
2026-08-28; note management lives on /tuning.html)."""
js = _text(HEADER_JS)
for selector in (
"#steering-toggle",
"#steering-count",
"#steering-panel",
"#steering-list",
"#steering-empty",
@@ -430,9 +429,8 @@ def test_header_module_owns_the_steering_panel() -> None:
assert 'del.setAttribute("aria-label", `Delete tuning note: ${n.note}`)' in js
assert "async function deleteSteeringNote" in js
assert 'fetch(`/api/steering/${encodeURIComponent(id)}`, { method: "DELETE" })' in js
assert "steeringToggle.addEventListener" in js, "the toggle binding is module-owned"
assert 'setAttribute("aria-expanded"' in js
assert "refreshSteering()" in js # re-open refreshes the list
assert "refreshSteering()" in js # the admin boot refresh + the form's save
assert "steeringToggle" not in js, "the navbar toggle is gone (2026-08-28)"
def test_header_module_exports_refresh_and_announce_steering() -> None:
@@ -455,17 +453,18 @@ def test_header_module_exports_refresh_and_announce_steering() -> None:
def test_init_shared_header_gates_the_steering_surface() -> None:
"""Inside initSharedHeader: admin → the list refreshes (count badge
right before the panel is ever opened; only when the page ships the
panel markup); anonymous → the toggle + panel are REMOVED from the
DOM (phase-16 'absent, not hidden') and /api/steering is never
fetched."""
"""Inside initSharedHeader: admin → the list refreshes (the panel
ships hidden and is only kept fresh; only when the page ships the
panel markup); anonymous → the panel is REMOVED from the DOM
(phase-16 'absent, not hidden') and /api/steering is never fetched.
The navbar toggle was removed at owner request (2026-08-28) — no
unhide / remove of a toggle may be left behind."""
js = _text(HEADER_JS)
fn = js.find("function initSharedHeader")
assert fn != -1
body = js[fn : js.find("\n}", fn)]
assert "if (steeringPanel) refreshSteering();" in body
assert "steeringToggle?.remove();" in body
assert "steeringToggle" not in body
assert "steeringPanel?.remove();" in body
+126
View File
@@ -0,0 +1,126 @@
"""Unit: the navbar steering toggle is gone (owner request, 2026-08-28).
The "Tuning" steering toggle (``#steering-toggle``) used to sit in
every page's shared header — shipped hidden (phase 40), revealed for
the admin, removed from the DOM for anonymous. The owner asked for the
button to go away entirely: note management now lives on the
standalone Tuning page (``/tuning.html``, phase 27). This file pins the
removal at source level — the toggle + count badge are ABSENT from all
six pages, the ``#steering-panel`` section still ships hidden (kept
fresh by the chat page's per-bubble Tune form through header.js), the
shared module owns no toggle wiring anymore, and the admin-only
``#nav-tuning`` link — the surviving path to the notes — still ships
hidden.
"""
from __future__ import annotations
import re
from pathlib import Path
FRONTEND = Path(__file__).resolve().parents[2] / "frontend"
ASSETS = FRONTEND / "assets"
HEADER_JS = ASSETS / "header.js"
#: All six pages carry the shared header block (phase 34's five pages +
#: phase 35's git-sources page).
PAGES = (
FRONTEND / "index.html",
FRONTEND / "sources.html",
FRONTEND / "document.html",
FRONTEND / "git-sources.html",
FRONTEND / "login.html",
FRONTEND / "tuning.html",
)
def _text(path: Path) -> str:
assert path.is_file(), f"missing frontend file: {path}"
return path.read_text(encoding="utf-8")
def _init_body(js: str) -> str:
"""The source of initSharedHeader in header.js."""
fn = js.find("function initSharedHeader")
assert fn != -1, "initSharedHeader must be defined"
return js[fn : js.find("\n}", fn)]
# ---------- the removal: absent from every page's navbar ----------
def test_steering_toggle_removed_from_all_six_pages() -> None:
"""The #steering-toggle button (and its #steering-count badge) is
gone from the navbar of EVERY page — absent, not hidden."""
for html in PAGES:
text = _text(html)
assert 'id="steering-toggle"' not in text, (
f"{html.name}: #steering-toggle must be removed from the navbar"
)
assert 'class="steering-toggle"' not in text, (
f"{html.name}: the .steering-toggle button must be removed"
)
assert 'id="steering-count"' not in text, (
f"{html.name}: the #steering-count badge lived inside the toggle"
)
def test_steering_panel_still_ships_hidden_on_all_six_pages() -> None:
"""The #steering-panel section survives the toggle removal (the chat
page's per-bubble Tune form keeps it fresh through header.js) and
still ships hidden, with its list / empty state / announcer."""
for html in PAGES:
text = _text(html)
tag = re.search(r'<section[^>]*id="steering-panel"[^>]*>', text)
assert tag, f"{html.name}: missing the #steering-panel section"
assert re.search(r"\bhidden\b", tag.group(0)), "the panel ships hidden"
assert 'id="steering-list"' in text, f"{html.name}: missing #steering-list"
assert 'id="steering-empty"' in text, f"{html.name}: missing #steering-empty"
assert re.search(r'<p[^>]*id="steering-announcer"[^>]*role="status"[^>]*>', text), (
f"{html.name}: missing the #steering-announcer live region"
)
# ---------- header.js: no toggle wiring left, panel contract intact ----------
def test_header_js_carries_no_toggle_wiring() -> None:
"""header.js owns no #steering-toggle reference anymore: no element
lookup, no admin unhide, no anonymous removal, no click binding, no
open/close helper, no count badge."""
js = _text(HEADER_JS)
assert 'querySelector("#steering-toggle")' not in js
assert "steeringToggle" not in js
assert "setSteeringPanel" not in js
assert 'querySelector("#steering-count")' not in js
assert "steeringCount" not in js
def test_header_js_keeps_the_panel_contract() -> None:
"""What survives: inside initSharedHeader the admin branch still
refreshes the panel list (fire-and-forget) and the anonymous branch
still REMOVES the panel from the DOM (phase-16 "absent, not hidden")
so /api/steering is never fetched for anonymous visitors."""
body = _init_body(_text(HEADER_JS))
admin = body.find("if (admin)")
refresh = body.find("if (steeringPanel) refreshSteering();")
assert admin != -1 and refresh != -1, "the admin refresh is missing"
assert admin < refresh, "the refresh must live in the admin branch"
assert "steeringPanel?.remove();" in body, (
"the anonymous remove-from-DOM path must stay intact"
)
# ---------- the surviving path to the notes ----------
def test_nav_tuning_still_ships_hidden_on_all_six_pages() -> None:
"""The admin-only Tuning NAV LINK (#nav-tuning) — the surviving path
to the steering notes now that the navbar toggle is gone — still
ships hidden on every page (the phase-19/29/35 contract)."""
for html in PAGES:
tag = re.search(r'<a[^>]*id="nav-tuning"[^>]*>', _text(html))
assert tag, f"{html.name}: missing the #nav-tuning nav link"
assert re.search(r"\bhidden\b", tag.group(0)), (
f"{html.name}: #nav-tuning must ship hidden"
)
@@ -1,150 +0,0 @@
"""Unit: the tuning toggle ships hidden — the anonymous flash fix (phase 40).
The "Tuning" steering toggle (``#steering-toggle``) used to ship VISIBLE
in every page's shared header and was removed by ``assets/header.js``
only after ``/api/whoami`` resolved — so an anonymous user briefly saw
the button on every page load (``TODO.md`` L3). The fix mirrors the
admin-only NAV LINKS (phase 19/29/35 contract): the toggle now SHIPS
with the ``hidden`` attribute in all six pages and ``initSharedHeader``
unhides it only when whoami says admin. The browser behavior is
E2E-covered (``tests/e2e/test_tuning_toggle_flash.py``); here we pin
the source-level wiring — the ship-hidden markup on every page, the
admin unhide line inside ``initSharedHeader``, the intact anonymous
remove-from-DOM path (phase 16 "absent, not hidden"), and the
``#nav-tuning`` hidden contract this phase relies on — so a silent
regression is caught without a browser.
"""
from __future__ import annotations
import re
from pathlib import Path
FRONTEND = Path(__file__).resolve().parents[2] / "frontend"
ASSETS = FRONTEND / "assets"
HEADER_JS = ASSETS / "header.js"
#: All six pages carry the shared header block (phase 34's five pages +
#: phase 35's git-sources page).
PAGES = (
FRONTEND / "index.html",
FRONTEND / "sources.html",
FRONTEND / "document.html",
FRONTEND / "git-sources.html",
FRONTEND / "login.html",
FRONTEND / "tuning.html",
)
def _text(path: Path) -> str:
assert path.is_file(), f"missing frontend file: {path}"
return path.read_text(encoding="utf-8")
def _toggle_tag(html: Path) -> str:
tag = re.search(r"<button[^>]*id=\"steering-toggle\"[^>]*>", _text(html))
assert tag, f"{html.name}: missing the #steering-toggle button"
return tag.group(0)
def _toggle_body(html: Path) -> str:
"""The full <button>…</button> block, for the icon/label/badge pins."""
text = _text(html)
start = text.find('id="steering-toggle"')
assert start != -1, f"{html.name}: missing the #steering-toggle button"
return text[start : text.find("</button>", start)]
def _init_body(js: str) -> str:
"""The source of initSharedHeader in header.js."""
fn = js.find("function initSharedHeader")
assert fn != -1, "initSharedHeader must be defined"
return js[fn : js.find("\n}", fn)]
# ---------- ship-hidden markup: zero flash for anonymous ----------
def test_steering_toggle_ships_hidden_on_all_six_pages() -> None:
"""#steering-toggle carries the ``hidden`` attribute in ALL SIX
pages — the exact ship-hidden contract the admin-only nav links
use, so an anonymous user never sees the "Tuning" button for a
single frame, on any page."""
for html in PAGES:
tag = _toggle_tag(html)
assert re.search(r"\bhidden\b", tag), (
f"{html.name}: #steering-toggle must ship hidden"
)
def test_steering_toggle_keeps_its_existing_markup() -> None:
"""Only the ``hidden`` attribute was added: type, class, the
aria-expanded / aria-controls wiring, the decorative icon, the
"Tuning" label, and the #steering-count badge stay byte-identical
on every page — the admin UX is unchanged."""
for html in PAGES:
tag = _toggle_tag(html)
assert 'type="button"' in tag
assert 'class="steering-toggle"' in tag
assert 'aria-expanded="false"' in tag
assert 'aria-controls="steering-panel"' in tag
body = _toggle_body(html)
assert '<svg aria-hidden="true"' in body, f"{html.name}: icon is gone"
assert '<span class="steering-label">Tuning</span>' in body, (
f"{html.name}: label markup changed"
)
assert '<span class="steering-count" id="steering-count">0</span>' in body, (
f"{html.name}: count badge markup changed"
)
def test_steering_panel_still_ships_hidden_on_all_six_pages() -> None:
"""The #steering-panel section already shipped hidden and stays
that way (this phase never touches the panel)."""
for html in PAGES:
tag = re.search(r'<section[^>]*id="steering-panel"[^>]*>', _text(html))
assert tag, f"{html.name}: missing the #steering-panel section"
assert re.search(r"\bhidden\b", tag.group(0)), "the panel ships hidden"
# ---------- header.js: reveal-for-admin, anonymous removal intact ----------
def test_header_js_unhides_the_toggle_for_admin() -> None:
"""Inside initSharedHeader, the admin branch unhides the toggle —
and that line sits BEFORE the refreshSteering() call (the count
badge is right before the panel is ever opened)."""
body = _init_body(_text(HEADER_JS))
assert "if (steeringToggle) steeringToggle.hidden = false;" in body, (
"the admin unhide is missing from initSharedHeader"
)
admin = body.find("if (admin)")
unhide = body.find("steeringToggle.hidden = false")
refresh = body.find("if (steeringPanel) refreshSteering();")
assert -1 < admin < unhide, "the unhide must live in the admin branch"
assert unhide < refresh, "the unhide must precede the refreshSteering() call"
def test_anonymous_removal_path_is_intact() -> None:
"""The phase-16 "absent, not hidden" contract is preserved: the
anonymous branch still REMOVES the toggle + panel from the DOM —
the new hidden attribute only closes the pre-whoami flash window,
the end state (absent) is unchanged."""
body = _init_body(_text(HEADER_JS))
assert "steeringToggle?.remove();" in body
assert "steeringPanel?.remove();" in body
# ---------- the nav contract this phase relies on ----------
def test_nav_tuning_still_ships_hidden_on_all_six_pages() -> None:
"""The admin-only Tuning NAV LINK (#nav-tuning) — the contract the
toggle now mirrors — still ships hidden on every page: one
ship-hidden / reveal-for-admin family, nav link and toggle alike."""
for html in PAGES:
tag = re.search(r'<a[^>]*id="nav-tuning"[^>]*>', _text(html))
assert tag, f"{html.name}: missing the #nav-tuning nav link"
assert re.search(r"\bhidden\b", tag.group(0)), (
f"{html.name}: #nav-tuning must ship hidden"
)