feat: phases 77–80 — navbar view refresh, static background, API tokens, history suggestion chips
Build and Push Containers / build-and-push-app (push) Successful in 1m45s
Build and Push Containers / build-and-push-db (push) Successful in 13s

Single consolidated commit for four completed, validated phases (77, 78,
79, 80). The pipeline run left all work uncommitted because the harness
commits only with PHASE_COMMIT=1 while child executors are forbidden from
committing; the phases themselves all passed validation and moved to
.agents/phases/complete/.

Phase 77 — navbar view refresh
- router.js dispatches bor:view-refresh on re-show / active re-click /
  popstate (gated on wasMounted; first show and boot exempt)
- History / RAG / Sources / Tuning re-fetch on refresh (admin branch);
  Chat deliberately excluded (stream survival)
- History "Refresh" button (admin-only, in-flight disable + status line)
- New story suite tests/e2e/test_navbar_refresh.py (7 tests)

Phase 78 — static background
- Removed the animated glow layers; static 44px grid over the flat --bg
  canvas; default and reduced-motion renders byte-identical
- Updated background/theme E2E suites; removed bg-glow test pins

Phase 79 — API tokens
- api_tokens model + migration 0012; hash-only token service
- Admin tokens API + Tokens admin view; POST /api/token-auth;
  live-revoking require_user on chat / suggestions / document content
- Frontend token gate with localStorage cache; anonymous E2E suites
  migrated to token login
- New story suite tests/e2e/test_api_tokens.py (9 tests)

Phase 80 — history suggestion chips
- last_questions() endpoint with SEED fallback; startNewChat() refetch
- Seed-semantics docs (config.py, .env.example, README)
- Integration state matrix + E2E suite rewritten to the 4 chip states

Also included: phase-76 report artifacts and the repo restore-test-db
skill (previously untracked), scripts/* ruff fixes from phase 77.

Final gate state (phase 80 final pass, covers everything above):
- uv run pytest --cov=app → 1637 passed, 0 failed, app/ coverage 99%
- uv run ruff check . && uv run pyright → clean, 0 errors
- Per-phase story E2E suites green in isolation
This commit is contained in:
2026-09-07 12:39:01 -04:00
parent 495d042a98
commit 7fce6572d0
215 changed files with 10142 additions and 1643 deletions
+35 -4
View File
@@ -516,6 +516,12 @@ def test_anonymous_cannot_manage(
expect(page.locator("#nav-sources")).to_be_hidden()
expect(page.locator("#nav-tuning")).to_be_hidden()
# Phase 79 (task 05): the anonymous visitor meets the token gate —
# the whole page (list AND create form) sits inert behind it, so
# "anonymous cannot manage" is enforced by the gate first.
expect(page.locator("#auth-gate")).to_be_visible(timeout=15_000)
assert page.evaluate("() => document.getElementById('main').inert") is True
# The list stays on its empty state even though a note exists…
expect(page.locator("#tune-list .tuning-note")).to_have_count(0)
expect(page.locator("#tune-empty")).to_be_visible()
@@ -536,15 +542,40 @@ def test_anonymous_cannot_manage(
row = db.get(SteeringNote, note_id)
assert row is not None and row.note == SEED_NOTE, "the note must stay untouched"
# The create form 403s gracefully: an inline error (role=alert) with
# the API detail, and the typed instruction survives in the textarea.
page.fill("#tune-note", "an anonymous attempt")
# The gate is only the first lock: the create form 403s gracefully
# for a NON-ADMIN (token user) too — an inline error (role=alert)
# with the API detail, and the typed instruction survives in the
# textarea. Admin signs in via the header, mints a token, signs out
# again, then unlocks the page through the gate as that user.
login(page, app_url, next=TUNING_URL)
expect(page.locator("#nav-tuning")).to_be_visible(timeout=15_000)
token = page.evaluate(
"""async () => {
const r = await fetch('/api/tokens', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({label: 'e2e-tuning-user'}),
});
if (!r.ok) throw new Error('token create failed: ' + r.status);
return (await r.json()).token;
}"""
)
page.click("#sign-out-btn")
page.wait_for_selector("#auth-gate:not([hidden])", timeout=15_000)
page.fill("#auth-gate-input", token)
page.click("#auth-gate-form button")
page.wait_for_selector("#auth-gate", state="hidden", timeout=15_000)
# The token user unlocked the page but is still NOT admin: the
# Tuning nav link stays hidden, the form is reachable only by
# direct URL — and the API refuses the create with 403.
expect(page.locator("#nav-tuning")).to_be_hidden()
page.fill("#tune-note", "a non-admin attempt")
page.click("#tune-save")
error = page.locator("#tune-form .tuning-error")
expect(error).to_have_attribute("role", "alert")
expect(error).to_be_visible(timeout=15_000)
assert "admin only" in (error.inner_text() or "").lower()
expect(page.locator("#tune-note")).to_have_value("an anonymous attempt")
expect(page.locator("#tune-note")).to_have_value("a non-admin attempt")
# Nothing was created: the DB still holds only the seeded note.
with SessionLocal() as db: