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
+74 -2
View File
@@ -1,10 +1,18 @@
"""Shared Playwright auth helper (phase 16).
"""Shared Playwright auth helpers (phase 16 + phase 79).
``login`` drives the REAL form login on /login.html (fill → submit →
redirect) so every story that needs the admin does exactly what a human
would — no cookie surgery. ``password=None`` uses the shared E2E admin
password (success path); pass a wrong value to drive the error state
(no redirect, ``#login-error`` role=alert visible, still anonymous).
``login_with_token`` (phase 79) drives the REAL in-app token gate on the
shell the same way (fill → submit): pass an admin-issued token for the
success contract (the gate hides, the app is interactive); call it with
the default all-zeros sentinel — or ``expect_error=True`` for a token
that USED to be valid (revocation) — for the wrong-token contract
(``#auth-gate-error`` role=alert visible, the gate stays, still
anonymous). Task 07's story suite is the first consumer.
"""
from __future__ import annotations
@@ -14,6 +22,53 @@ from e2e.conftest import ADMIN_PASSWORD # noqa: F401 (re-exported for tests)
DEFAULT_NEXT = "/sources.html"
#: The wrong-token sentinel (phase 79): the ``bor_`` prefix plus 32 zero
#: hex chars. The admin issues ``bor_`` + ``secrets.token_hex(16)``, so
#: an all-zeros token is never a live one — the DEFAULT call drives the
#: wrong-token contract without needing a real token at all.
WRONG_TOKEN = "bor_" + "0" * 32
def login_with_token(
page: Page,
app_url: str,
token: str = WRONG_TOKEN,
next: str = "/",
expect_error: bool | None = None,
) -> None:
"""Drive the real in-app token gate (phase 79) on the shell.
``page.goto`` the shell — the test contexts are fresh, so no
``bor.token`` is cached in localStorage and ``#auth-gate`` is the
visible surface — then fill ``#auth-gate-input`` with ``token`` and
submit the gate's form:
* success (an explicitly-passed valid, unrevoked token — with
``expect_error`` left ``None`` the branch is derived from the
all-zeros ``WRONG_TOKEN`` sentinel) → ``#auth-gate`` hides and
the app is interactive (the composer is reachable) at ``next``;
* wrong / revoked (``expect_error=True``, or the sentinel default)
→ ``#auth-gate-error`` (role=alert) is visible, the gate stays
visible — the visible gate IS the anonymity proof, the UI mirror
of ``login``'s wrong-password branch — and the visitor is still
anonymous.
"""
page.goto(app_url + next)
gate = page.locator("#auth-gate")
expect(gate).to_be_visible(timeout=30_000)
page.fill("#auth-gate-input", token)
page.click("#auth-gate-form button")
error_expected = token == WRONG_TOKEN if expect_error is None else expect_error
if error_expected:
error = page.locator("#auth-gate-error")
expect(error).to_have_attribute("role", "alert")
expect(error).to_be_visible(timeout=15_000)
expect(gate).to_be_visible() # the gate is the anonymity proof
return
expect(gate).to_be_hidden(timeout=30_000)
expect(page.locator("#message-input")).to_be_visible() # app is interactive
expect(page).to_have_url(app_url + next)
def login(page: Page, app_url: str, password: str | None = None, next: str | None = None) -> None:
"""Perform the real form login and wait for its outcome.
@@ -22,13 +77,30 @@ def login(page: Page, app_url: str, password: str | None = None, next: str | Non
→ redirects to ``next`` (default ``/sources.html``);
* wrong password → ``#login-error`` (role=alert) is visible, the URL
never changes, and the visitor is still anonymous.
Idempotent (phase 79): in a context that is ALREADY signed in the
login page boots straight to ``next`` (login.js's whoami redirect —
no form) and the call returns as a success; a test may sign in once
per context and call it again from a later section without breaking.
"""
attempt = ADMIN_PASSWORD if password is None else password
url = f"{app_url}/login.html"
if next is not None:
url += f"?next={next}"
page.goto(url)
expect(page.locator("#login-password")).to_be_visible()
if attempt == ADMIN_PASSWORD:
# Phase 79 (task 04): the helper must be IDEMPOTENT — a context
# that is ALREADY signed in never sees the form (login.js's
# boot whoami redirect goes straight to `next`), and a test that
# signs in twice (two sections, one context) must not break on
# the second call.
try:
expect(page.locator("#login-password")).to_be_visible(timeout=2_000)
except AssertionError:
expect(page).to_have_url(app_url + (next or DEFAULT_NEXT), timeout=30_000)
return
else:
expect(page.locator("#login-password")).to_be_visible()
page.fill("#login-password", attempt)
page.click("#login-form button[type=submit]")
if attempt != ADMIN_PASSWORD: