All gates re-verified green. Report: - **Verified (all 4 tasks already complete; this was the final pass):** `effective_settings` resolves the 3 brand strings panel-row → tab-row → env (`applied_override_keys`, presence-not-value); `PUT /api/ui-settings` ignores string body fields; Theme-tab form is colors-only (17 pickers, no `#theme-app-name`/`#theme-placeholder`/`#theme-footer`); the ONE shared `applyBrandFromConfig` re-applies title/`.brand-text`/footer/placeholder after a panel save-reset and a tab save-reset; boot path byte-identical (single `/api/config`). - **Completion criteria:** - strings order panel→tab→env, both branches — ✅ `tests/unit/test_theming.py::test_effective_app_name_prefers_settings_panel_row_over_ui_settings_row`, `..._fall_back_to_ui_settings_row_without_panel_row`, `..._per_field_panel_then_tab_then_env`, `..._ignore_panel_rows_when_the_overlay_is_off` - PUT ignores string fields + no 3 inputs in the tab — ✅ `tests/integration/test_ui_settings_api.py::test_put_ignores_string_fields_in_body` (+2) and the E2E `test_theme_tab_dom_is_colors_only` - live apply without reload + incident regression — ✅ `tests/e2e/test_app_name_settings_fix.py` tests 1–4 - byte-identical boot, no double-apply — ✅ `tests/unit/test_brand_live.py` markers + E2E "exactly one /api/config" - suite green, coverage >90%, ruff+pyright clean — ✅ - no behavior change in prior phases — ✅ (palette, phase-168 panel, env fallback suites all green) - atomic commit — **left to the harness** (pipeline rule overrides per-phase committing; no git write run) - **Gates:** `uv run pytest --cov=app --cov-report=term-missing` → **4630 passed, TOTAL 6941 16 99%**, exit 0 · `uv run pytest tests/e2e/test_app_name_settings_fix.py -v --no-cov` → **6 passed** in isolation · regression E2E (isolated, one process each, all exit 0): `test_smoke` `test_configurable_brand` `test_admin_theme_tab` `test_theme_save_and_coverage` `test_theme_semantic_completion` `test_themed_favicon` `test_dark_tech_theme` `test_admin_settings_panel` `test_admin_settings_layout` `test_settings_mobile_value_width` · `uv run ruff check .` → All checks passed · `uv run pyright` → 0 errors. - **Documentation:** README — fixed "saving one repaints *every* open page" (only the open document repaints; other pages on next load) and a broken paragraph wrap in *Customizing the look*; AGENTS.md rule 16 — `test_chunks_hnsw_recall.py` now described accurately (index paths forced with `enable_seqscan`/`indexonlyscan`/`bitmapscan` off, unforced plan asserted only on a pristine relation); `tests/unit/test_theme_frontend.py` — two stale section headers ("20 form fields"/"20 inputs" → 17 color). No AGENTS.md settings-overlay/resolution-order clause exists, so per the phase none was added. Full env-var cross-check (`BOR_*` in `app/config.py` vs README) → 0 gaps; 0 missing file references. - **Notable:** the working tree also carries this phase's flake hardening of the phase-188 `tests/integration/test_chunks_hnsw_recall.py` (verified legit and green). Live infra untouched; shared DB left clean (0 `app_settings`, 0 `ui_settings`). **Flagged, not changed:** `settings_overlay.applied_override_keys` lets a `ProgrammingError` (missing `app_settings`) propagate from public `GET /api/config`, where `load_overrides` deliberately fails soft — only reachable on an un-migrated (0027) DB, outside this phase's pinned A1 semantics, so left for the owner to rule on rather than silently deviating. - **Next pending phase:** `191_source_name_labels_tags`.
63 lines
2.9 KiB
Python
63 lines
2.9 KiB
Python
"""Public app metadata (display name + version) for the frontend brand
|
|
layer, the phase-59 docs-push flag (the "Save as doc" gating), the
|
|
phase-122 image flag (UI affordance gating), and the phase-62 UI
|
|
customization strings (composer placeholder, footer line).
|
|
|
|
Phase 91 (task 01): the three UI strings are now the EFFECTIVE values,
|
|
resolved by the SAME :func:`app.core.theming.effective_settings` resolver
|
|
the ``/api/ui-settings`` API uses, so the brand layer and the tab can
|
|
never disagree. Phase 190 (task 02, A1) fixed WHO owns them: the admin
|
|
Settings panel's ``app_settings`` row (phase 168) first, then the
|
|
legacy ``ui_settings`` (Theme-tab) row, then the env value — so a name
|
|
set in the panel is what this endpoint serves, whatever an old Theme-tab
|
|
save baked into the other row. The route opens a short-lived session (the sync-endpoint
|
|
house pattern — the route is sync, matching the middleware world).
|
|
|
|
Phase 91 (task 03): the retired CSS-file theming's ``theme`` key is
|
|
deleted with the mechanism — the five keys below are the entire
|
|
contract (the colors never rode this endpoint; the server injects
|
|
them pre-paint, :mod:`app.core.theming`).
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from fastapi import APIRouter, Depends
|
|
|
|
from app.config import Settings, get_settings
|
|
from app.core import theming
|
|
from app.db import SessionLocal
|
|
|
|
router = APIRouter(tags=["config"])
|
|
|
|
|
|
@router.get("/config")
|
|
def app_config(settings: Settings = Depends(get_settings)) -> dict[str, str | bool]: # noqa: B008
|
|
"""Public app metadata for the frontend brand layer (phase 39) +
|
|
the phase-59 ``docs_repo_configured`` flag + the phase-122
|
|
``images`` flag + the phase-62 UI customization keys
|
|
(``input_placeholder``, ``footer_text``) — all display strings,
|
|
the SAME boot fetch (no new network surface) and the same public
|
|
posture as ``app_name`` (no secrets). Phase 91:
|
|
``app_name`` / ``input_placeholder`` / ``footer_text`` are the
|
|
EFFECTIVE values (the admin Theme tab's ``ui_settings`` row over
|
|
the env values — DB-over-env, B1); the frontend brand layer treats
|
|
an empty string as "keep the template default" (the unset =>
|
|
byte-identical contract). Phase 91 (task 03): the retired
|
|
CSS-file theming's ``theme`` key is gone. Phase 122 (task 01):
|
|
``images`` mirrors ``settings.images`` (the ``BOR_IMAGES`` master
|
|
switch) — consumed by the chat composer (phase 123) to show/hide
|
|
the image-attach control, optionally by the Sources page (an
|
|
"images off" hint). The six keys are the entire response."""
|
|
db = SessionLocal()
|
|
try:
|
|
effective = theming.effective_settings(db, settings)
|
|
finally:
|
|
db.close()
|
|
return {
|
|
"app_name": effective["app_name"],
|
|
"version": settings.app_version,
|
|
"docs_repo_configured": settings.docs_configured,
|
|
"images": settings.images,
|
|
"input_placeholder": effective["input_placeholder"],
|
|
"footer_text": effective["footer_text"],
|
|
}
|