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:
@@ -75,6 +75,18 @@
|
||||
* button, Escape / No cancels) → DELETE /api/admin/settings →
|
||||
* the status line `Cleared N overrides.` (N from the
|
||||
* response) + a re-GET.
|
||||
* • the live brand re-apply (phase 190, task 03 — A2, the second half
|
||||
* of "takes effect on clicking save") — when a save / Reset / Clear
|
||||
* all touched one of the three brand strings (`app_name`,
|
||||
* `input_placeholder`, `footer_text` — the panel's own keys), the
|
||||
* OPEN document is re-branded from a fresh `GET /api/config`
|
||||
* through `window.applyBrandFromConfig` (the ONE pass brand.js owns
|
||||
* and the boot runs — this file never writes the title / brand /
|
||||
* footer / placeholder itself). No brand key in the batch → no
|
||||
* extra request; a failed re-apply leaves the document exactly as
|
||||
* it was (never a blanked brand); the catalogue re-GET stays the
|
||||
* catalogue's own refresh. The Theme tab (theme.js) runs the SAME
|
||||
* shared pass after its own save / reset.
|
||||
* • search — #settings-filter: a case-insensitive substring over
|
||||
* the label + BOR_* name cells, applied on input with NO fetch
|
||||
* (the phase-101 tokens D4 contract) and RE-APPLIED after every
|
||||
@@ -447,6 +459,48 @@ export async function mount(root) {
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------- the live brand re-apply (phase 190, task 03 — A2) ----------
|
||||
* The phase-168 header contract is "the change should take effect on
|
||||
* clicking 'save'" — for the three BRAND strings that means the OPEN
|
||||
* document too, not just the catalogue. One pass, ONE owner:
|
||||
* brand.js exports the boot's own function as
|
||||
* window.applyBrandFromConfig, and this helper feeds it a fresh
|
||||
* GET /api/config (the same no-store request the boot makes).
|
||||
*
|
||||
* • gated on the key — a save / Reset of any of the ~72 other rows
|
||||
* costs NO extra request and no DOM churn; Clear all passes all
|
||||
* three because clearing hands every one of them back to the
|
||||
* Theme-tab row or the environment;
|
||||
* • the §7.4 guard, EXTENDED not forked — the save itself is already
|
||||
* inside `saveInFlight` (this helper is awaited before that flag
|
||||
* drops, so a second Save can never start mid-re-apply) and the
|
||||
* one flag below covers the settle paths that share the pass, so a
|
||||
* second save / Reset / Clear all never stacks a second fetch;
|
||||
* • never a blanked brand — a non-2xx or an unreachable app returns
|
||||
* WITHOUT touching the DOM (the phase-168 "never a blanked panel"
|
||||
* precedent): the open page keeps its last applied name, footer
|
||||
* and placeholder, and the save's own status / error line stays
|
||||
* the visibility. */
|
||||
const BRAND_KEYS = ["app_name", "input_placeholder", "footer_text"];
|
||||
let brandReapplyInFlight = false;
|
||||
|
||||
async function reapplyBrand(keys) {
|
||||
if (!keys.some((key) => BRAND_KEYS.includes(key))) return; // not a brand row
|
||||
if (brandReapplyInFlight) return; // one re-apply at a time (§7.4)
|
||||
if (typeof window.applyBrandFromConfig !== "function") return; // brand.js is the owner
|
||||
brandReapplyInFlight = true;
|
||||
try {
|
||||
const r = await fetch("/api/config", { cache: "no-store" });
|
||||
if (!r.ok) return; // the brand keeps its last applied state
|
||||
window.applyBrandFromConfig(await r.json());
|
||||
} catch {
|
||||
/* unreachable app or a non-JSON body — nothing is written (the
|
||||
brand never blanks; the settle path's own line says it) */
|
||||
} finally {
|
||||
brandReapplyInFlight = false;
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------- Save (dirty rows only — stop at the first failure,
|
||||
K4; the §7.4 never-stale lifecycle) ---------- */
|
||||
async function saveSettings() {
|
||||
@@ -499,6 +553,9 @@ export async function mount(root) {
|
||||
);
|
||||
const input = root.querySelector("#set-" + failure.key);
|
||||
if (input) input.focus();
|
||||
/* A brand row that DID land before the failure changed the app
|
||||
(K4 saves the prefix) — the open document re-reads it too. */
|
||||
await reapplyBrand(saved);
|
||||
} else {
|
||||
/* One status line (role=status) — a saved restart-required
|
||||
row says so (the owner is never told "saved" and then sees
|
||||
@@ -511,6 +568,10 @@ export async function mount(root) {
|
||||
(anyRestart ? " — applies on the next restart" : ""),
|
||||
);
|
||||
await loadSettings(); // every badge/value is the settled state
|
||||
/* …and the brand strings apply to the OPEN document (no reload) —
|
||||
additive to the catalogue re-GET above, still inside the
|
||||
saveInFlight guard (phase 190, task 03). */
|
||||
await reapplyBrand(saved);
|
||||
}
|
||||
saveInFlight = false;
|
||||
if (saveBtn) {
|
||||
@@ -535,6 +596,7 @@ export async function mount(root) {
|
||||
}
|
||||
announce(`${envVar} is back to the environment value.`);
|
||||
await loadSettings(); // the badge falls back to env/default
|
||||
await reapplyBrand([key]); // …and the open document falls back too
|
||||
}
|
||||
|
||||
/* ---------- Clear all overrides (the house two-step inline
|
||||
@@ -570,6 +632,7 @@ export async function mount(root) {
|
||||
restore();
|
||||
announce(`Cleared ${cleared} overrides.`);
|
||||
await loadSettings(); // every badge falls back to env/default
|
||||
await reapplyBrand(BRAND_KEYS); // every brand string may have moved
|
||||
}
|
||||
|
||||
function showClearAllConfirm() {
|
||||
|
||||
Reference in New Issue
Block a user