feat(brand): configurable app name — BOR_APP_NAME drives /api/config + the frontend brand layer
Build and Push Containers / build-and-push (push) Successful in 1m50s

One env var (BOR_APP_NAME, default "Brain of Reese") now drives the app's
display name everywhere (TODO.md L12 — owner ask: "a way to customize the
name for 'Brain of'. Should be an env var."). The existing app_name setting
is the source of truth (phase locked decision — no new variable, no rename);
with the variable unset the app is byte-identical to before.

Endpoint (A10 public/stateless, no secrets):
  GET /api/config → exactly {app_name, version} (app/api/config.py, the
  health.py pattern; registered before the static mount). Integration tests:
  anonymous 200, default values, a Settings override follows, key set is
  exactly two keys — no other setting may leak in later.

Frontend brand layer (A11 — runtime fetch, static templates stay static):
  assets/brand.js — a CLASSIC script, first on all six pages, so its top
  level runs at parse time: window.BOR_BRAND = "Brain of Reese"
  synchronously (the default renders immediately, no blank flash), then a
  no-store fetch of /api/config applies the name — document.title (global
  replace), every .brand-text (a name starting "Brain of " keeps the bold
  split Brain of <strong>rest</strong>, any other name renders plain; the
  operator-controlled name is HTML-escaped before innerHTML), a TreeWalker
  over text nodes (script/style rejected — page source never rewritten),
  and the aria-label/placeholder/meta-content attributes. Fetch failure
  keeps the default + console.warn (the loadHealth house style).
  app.js (status labels, typing label, elapsed-hint aria, tool labels) and
  document.js (viewer titles) read window.BOR_BRAND at CALL time via
  brand() — a label set after the fetch lands carries the configured name.
  Containerfile: esbuild minify line for brand.js (classic, like markdown.js);
  the phase-33 ?v= cache-busting picks the new asset ref up automatically.

E2E (A16 — one story, one file, isolated): test_configurable_brand.py boots
a SECOND app instance (same DB/mock-LLM/admin-auth env block, port APP_PORT+1,
BOR_APP_NAME="Brain of Testy") — the shared conftest server keeps the
default name so every other suite's title/label assertions stay untouched —
and asserts /api/config on both instances, the index title/brand/greeting/
#messages aria-label, the sources + login page titles, and one pre-token
chat turn (think out loud marker) whose #send-status reads "Brain of Testy
is thinking"; the no-op regression pins the shared server's default bytes.

Docs: .env.example App section + README configuration reference — what it
affects (titles, header brand, status labels, aria text), the default, the
bold-split rendering rule.

Gates: 695 unit+integration passed, app/ coverage 99% (>90%), story E2E
green in isolation (two consecutive runs), brand-string suites (smoke,
shared header, header consistency, chat persistence) green, ruff + pyright
clean.
This commit is contained in:
2026-08-27 02:24:16 -04:00
parent 94d7228510
commit fe55be0c35
23 changed files with 788 additions and 20 deletions
+11 -2
View File
@@ -47,9 +47,18 @@ def test_state_machine_has_all_four_states() -> None:
def test_typing_indicator_contract_strings() -> None:
"""The indicator's accessible label and the 10s elapsed-seconds hint
are the strings screen readers (and the E2E) rely on."""
are the strings screen readers (and the E2E) rely on.
Phase 39: the label is BUILT at call time from window.BOR_BRAND
(the classic brand.js layer) — `TYPING_LABEL()` — with the default
name as the only fallback, so the default path renders the same
bytes as before."""
js = _js()
assert 'TYPING_LABEL = "Brain of Reese is thinking"' in js
assert "TYPING_LABEL = () =>" in js, "the label must be built at call time (phase 39)"
assert "`${brand()} is thinking`" in js, "the label must resolve the name via brand()"
assert 'window.BOR_BRAND || "Brain of Reese"' in js, (
"the no-config fallback must keep the default name"
)
assert "still thinking" in js, "elapsed-seconds hint must update the aria label"
assert "secs < 10" in js, "the hint must only appear after 10s of silence"
assert "role=\"status\"" in js, "typing bubble must be role=status"