phase: 190_app_name_settings_fix
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`.
This commit is contained in:
@@ -176,26 +176,20 @@ def _parse_roles(svg: str) -> dict[str, str]:
|
||||
}
|
||||
|
||||
|
||||
def _template_defaults() -> dict[str, str]:
|
||||
"""The 3 template strings from the CODE defaults (derived from
|
||||
the class fields — never drifts from ``app/config.py``; the
|
||||
module server pins the same values, so the effective strings
|
||||
start exactly here)."""
|
||||
return {
|
||||
"app_name": Settings.model_fields["app_name"].default,
|
||||
"input_placeholder": Settings.model_fields["input_placeholder"].default,
|
||||
"footer_text": Settings.model_fields["footer_text"].default,
|
||||
}
|
||||
def _login_to_theme(page: Page, app_url: str) -> None:
|
||||
"""Log in straight into the Theme view AND wait for its mount-time
|
||||
``GET /api/ui-settings`` to ANSWER — the settle signal.
|
||||
|
||||
|
||||
def _wait_mount_settled(page: Page) -> None:
|
||||
"""The theme view's mount-time load has SETTLED: its populate
|
||||
is the last visible step — the app-name input carries the
|
||||
effective default (the static markup ships it empty, so only a
|
||||
settled ``loadSettings`` can have written it)."""
|
||||
expect(page.locator("#theme-app-name")).to_have_value(
|
||||
_template_defaults()["app_name"], timeout=15_000
|
||||
)
|
||||
Phase 190 (task 02) moved it with the tab's string fieldset: the
|
||||
settle used to be read off the (now removed) ``#theme-app-name``
|
||||
input, the only input the static markup shipped empty. The network
|
||||
is the source of truth now."""
|
||||
with page.expect_response(
|
||||
lambda r: "/api/ui-settings" in r.url, timeout=30_000
|
||||
) as info:
|
||||
login(page, app_url, next="/theme.html")
|
||||
assert info.value.status == 200, "the mount's GET /api/ui-settings failed"
|
||||
expect(page.locator("#theme-content")).to_be_visible(timeout=15_000)
|
||||
|
||||
|
||||
def _favicon_href(page: Page) -> str:
|
||||
@@ -519,9 +513,7 @@ def test_live_preview_repaints_the_favicon(
|
||||
a non-driving pick (``#theme-bg``) never touches the href."""
|
||||
page.set_default_timeout(30_000)
|
||||
externals, _data_requests, failed_data, csp_violations = _zero_external(page)
|
||||
login(page, app_url, next="/theme.html")
|
||||
expect(page.locator("#theme-content")).to_be_visible(timeout=15_000)
|
||||
_wait_mount_settled(page)
|
||||
_login_to_theme(page, app_url)
|
||||
|
||||
# A clean start: the href is the route (no row → the golden
|
||||
# art served by the route).
|
||||
@@ -568,9 +560,7 @@ def test_save_restores_the_route_url(
|
||||
re-reads the DB per request, no process cache)."""
|
||||
page.set_default_timeout(30_000)
|
||||
externals, _d, failed_data, csp = _zero_external(page)
|
||||
login(page, app_url, next="/theme.html")
|
||||
expect(page.locator("#theme-content")).to_be_visible(timeout=15_000)
|
||||
_wait_mount_settled(page)
|
||||
_login_to_theme(page, app_url)
|
||||
|
||||
# A live pick first — so the save has a data: href to restore.
|
||||
for field in ("surface", "brand", "brand_ink"):
|
||||
@@ -588,9 +578,10 @@ def test_save_restores_the_route_url(
|
||||
assert _favicon_href(page) == "/favicon.svg"
|
||||
|
||||
# The settled palette governs: the route serves the saved
|
||||
# palette's bytes (the PUT saved all 20 form values — the 3
|
||||
# driving colors + the 14 built-ins (→ NULL) + the 3 template
|
||||
# strings; the effective palette is the 3 + the built-ins).
|
||||
# palette's bytes (the PUT saved the 17 form colors — the 3
|
||||
# driving colors + the 14 built-ins (→ NULL); the brand strings are
|
||||
# the Settings panel's since phase 190 (A1) and this body carried
|
||||
# none, so the effective palette is the 3 + the built-ins).
|
||||
effective = {**theming.BUILTIN_COLORS, **DRIVING}
|
||||
r = httpx.get(f"{app_url}/favicon.svg", timeout=10)
|
||||
assert r.status_code == 200
|
||||
@@ -614,9 +605,7 @@ def test_reset_restores_the_route_url(
|
||||
(the reset nulled the row — the built-ins govern)."""
|
||||
page.set_default_timeout(30_000)
|
||||
externals, _d, failed_data, csp = _zero_external(page)
|
||||
login(page, app_url, next="/theme.html")
|
||||
expect(page.locator("#theme-content")).to_be_visible(timeout=15_000)
|
||||
_wait_mount_settled(page)
|
||||
_login_to_theme(page, app_url)
|
||||
|
||||
# Save first — the route is themed (the reset then has a real
|
||||
# state to settle back from).
|
||||
@@ -672,9 +661,7 @@ def test_view_refresh_restores_the_route_url(
|
||||
golden bytes)."""
|
||||
page.set_default_timeout(30_000)
|
||||
externals, _d, failed_data, csp = _zero_external(page)
|
||||
login(page, app_url, next="/theme.html")
|
||||
expect(page.locator("#theme-content")).to_be_visible(timeout=15_000)
|
||||
_wait_mount_settled(page)
|
||||
_login_to_theme(page, app_url)
|
||||
|
||||
# A live pick — the href is the data: one the re-show restores.
|
||||
for field in ("surface", "brand", "brand_ink"):
|
||||
|
||||
Reference in New Issue
Block a user