Files
brain-of-reese/.agent/phases/todo/62_ui_customization/01_config_settings.md
T
ducoterra 4971e2859d
Build and Push Containers / build-and-push-app (push) Successful in 12s
Build and Push Containers / build-and-push-db (push) Successful in 10s
chore(agent): track .agent/ planning tree in git
Remove the blanket .agent/ gitignore so the phase roadmap, user
stories, reports, and PLAN.md are versioned with the code. Only
runtime artifacts (.agent/phase-sessions/, .agent/pipeline.log)
remain ignored. Update AGENTS.md git protocol rule to match.
2026-09-01 10:18:22 -04:00

4.2 KiB
Raw Blame History

Task 01 — Three new Settings + /api/config keys (+ validator)

Phase: 62_ui_customization · Source: TODO.md:3 — "…an environment variable to call it 'Brain of ' along with custom message-input placeholder, custom footer-inner text, custom color themes…" Story: n/a (TODO-derived)

Objective

The backend half of the customization layer: three new BOR_-prefixed settings with phase-61's neutral strings as DEFAULTS, surfaced through the existing public GET /api/config boot fetch — with the existing exact-key-set contract tests moved to the new six-key set so the suite stays green from this task on.

Work

  1. app/config.py — in the # --- App --- block (after static_dir, ~L47), add:
    # --- UI customization (phase 62, TODO L3) ---
    # Defaults are the phase-61 neutral copy — UNSET => byte-identical UI.
    input_placeholder: str = "Ask me anything…"
    footer_text: str = "Powered by self-hosted models"
    #: Theme file NAME under frontend/assets/themes/ (e.g. "indigo.css");
    #: empty = the built-in dark-tech palette. Validated: bare filename
    #: only — no paths, no ".." (no-CDN: served from the static dir).
    theme: str = ""
    
    plus a @field_validator("theme") (pydantic v2, mode="after"): empty string passes; the value must match ^[a-z0-9_-]+\.css$ (ASCII, lowercase, bare filename) or the validator raises ValueError naming the offending value and the allowed shape (phase-56 fail-loud house style — a typo in .env must kill startup, not silently 404).
    • input_placeholder / footer_text: no validation beyond being strings (empty ⇒ the template default stands, handled by brand.js treating empty as "skip").
  2. app/api/config.py — the GET /api/config response grows to exactly six keys:
    return {
        "app_name": settings.app_name,
        "version": settings.app_version,
        "docs_repo_configured": settings.docs_configured,
        "input_placeholder": settings.input_placeholder,
        "footer_text": settings.footer_text,
        "theme": settings.theme,
    }
    
    (values passed through verbatim — the frontend brand layer decides "empty = keep the template default"; NO new secrets surface: these are display strings, same public posture as app_name). Update the module + endpoint docstrings to cite phase 62.
  3. Contract-test updates (MUST land with this task or the suite goes red):
    • tests/integration/test_api.py L21–46: both exact-key-set assertions become {"app_name", "version", "docs_repo_configured", "input_placeholder", "footer_text", "theme"}; assert the DEFAULT values ("Ask me anything…", "Powered by self-hosted models", ""); extend test_config_follows_overridden_app_name's pattern with a test_config_serves_ui_customization_overrides test (fresh app with Settings(input_placeholder=…, footer_text=…, theme="indigo.css") → the three keys reflect the overrides).
    • tests/e2e/test_configurable_brand.py L150 + L158: the two exact-key-set assertions grow to the six-key set (values untouched — that suite's instance has no phase-62 overrides, so the new keys are their defaults).
  4. Unit tests — tests/unit/test_config.py (existing file, add cases): theme validator — "" ok; "indigo.css" ok; "Indigo.css" rejected; "../evil.css" rejected; "a/b.css" rejected; "indigo" (no extension) rejected — each rejection names the value. Plus one test that GET /api/config (integration) default response carries the three new keys with the locked defaults.

Testing & Quality

  • uv run pytest green (unit + integration); uv run ruff check . && uv run pyright clean.
  • Coverage: >90% on app/ (validate.sh gate).

Completion Criteria

  • Settings exposes input_placeholder / footer_text / theme with the locked defaults; BOR_THEME=../evil.css in the env fails app startup, naming the value (verified once via a unit/validator test — the E2E boots-check lands in task 05).
  • GET /api/config returns exactly the six keys (defaults + overrides) — integration tests green.
  • tests/e2e/test_configurable_brand.py's key-set assertions updated (suite itself re-run in task 05's regression pass, DB up).
  • Full suite green, lint + types clean, coverage >90%.