4.4 KiB
Phase 29 — Global Tuning nav link
Source: .agent/phases/complete/27_global_tuning — the /tuning.html page and its create/edit/delete machinery shipped, but the "Tuning" header link that was supposed to expose the page (27 task 04: *"Expose the tuning page via an admin-only header link") only exists on tuning.htmlitself. It is not present on the Chat or Sources pages, so there is no way to reach/tuning.htmlfrom anywhere except typing the URL. **Story:**.agent/user_stories/global-tuning.md**Context:** The shared header (phase 19) renders[Chat, Sources — admin only]in
on the Chat and Sources pages.frontend/assets/header.jsalready contains logic to reveal an admin-only#nav-tuninglink for admins (lines 69–70) — but that element is never inindex.html/sources.html` markup, so the reveal is a no-op. This phase adds the missing markup so the link the JS already handles actually exists.
Objective
Make the Global Tuning page reachable by adding the admin-only "Tuning" nav link to the shared headers of the Chat (index.html) and Sources (sources.html) pages — hidden by default, revealed for admins by the existing header.js whoami gate (the exact same pattern as the Sources link). No backend, schema, or API change.
Dependencies
19_shared_header(complete) — the<nav class="app-nav">contract, the shared header markup/ids, and the 64px header-height rule on the Chat and Sources pages.16_admin_auth(complete) — the/api/whoamigate +header.jsfetchIsAdmin()that reveals admin-only nav links.27_global_tuning(complete) — the/tuning.htmlpage andPUT/POST/DELETE /api/steeringendpoints that this link points to.
Tasks
01_tuning_links_markup.md— add the admin-only#nav-tuninglink to the Chat (index.html) and Sources (sources.html) headers, hidden by default.02_tuning_nav_e2e_and_regression.md— add the story E2E suite (test_tuning_nav_link.py) + verify no regressions in the header/shared-header/no-CDN suites and commit.
Testing & Quality
- Unit/integration: no new backend logic (frontend-only markup). The existing no-CDN integration test (
tests/integration/test_api.py::test_index_html_served_locally) must still pass — the new link is same-origin<a>, no new tags. - Coverage: frontend-only; the
app/>90% gate is unaffected (unchanged). - E2E (mandatory, A16):
tests/e2e/test_tuning_nav_link.py— the story gate, run in isolation.
Completion Criteria
- On the Chat page (
/), a signed-in admin sees a "Tuning" link (next to Chat/Sources) that navigates to/tuning.html; an anonymous visitor does not see it (shipshidden,header.jsreveals only for admin). - Same on the Sources page (
/sources.html). - The link is absent from the anonymous DOM-reveal (verified via
test_anonymousbehavior); header markup stays valid and semantic. uv run pytestgreen;uv run pytest --cov=app --cov-report=term-missingTOTAL unchanged (no backend change).- Existing header suites stay green in isolation:
test_header_consistency.py(64px height + nav structure unchanged),test_shared_header.py,test_smoke.py,test_global_tuning.py. uv run pytest tests/e2e/test_tuning_nav_link.py -v --no-covgreen in isolation.uv run ruff check . && uv run pyrightclean (no Python change, but run the gate).- UI Structure Check (AGENTS.md rule 5): the link is a labeled, focus-visible
<a>; header landmarks/contrast unchanged; no CDN. - One
--no-gpg-signcommit staging only this phase's files;.agent/phases/todo/29_tuning_nav_link/moved to.agent/phases/complete/.
Locked decisions
- A11 untouched — vanilla HTML/CSS/JS, no CDN, no new packages; the link is a same-origin
<a href="/tuning.html">. - A10 untouched — no new endpoint or auth surface; the link reuses the existing
#nav-sourcesreveal path (header.jsalready gates#nav-tuningonfetchIsAdmin()). - No schema / migration — purely a markup addition.
- A16 / A17 honoured — one new story E2E suite + one atomic
--no-gpg-signcommit. - Scope boundary — only the Chat and Sources pages (the two that share the standard
app-navwith the Sources link). The viewer page (document.html) uses a different.doc-header-actionsheader variant and the login page is the auth gate; both stay out of scope for this fix.