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:
@@ -1,51 +0,0 @@
|
||||
# Phase 190 — The Settings-panel app name is the app name again (and saves apply live)
|
||||
|
||||
**Source:** `TODO.md` L3 — "changing the App name in settings broke, it no longer updates the name."
|
||||
**Story:** n/a (owner report)
|
||||
**Depends on:** none functionally (builds on the completed phase-168 admin Settings panel and phase-91/92 admin Theme tab).
|
||||
|
||||
## Evidence (diagnosed from the code, read this not the chat)
|
||||
|
||||
1. **Two writers for one setting.** `app_name` (and `input_placeholder` / `footer_text`) is now writable from TWO admin surfaces: the Settings panel (`/api/admin/settings/app_name` → `app_settings` table → the `app.config` DB-over-env overlay, phase 168) and the Theme tab (`/api/ui-settings` PUT → the single `ui_settings` row, phase 91).
|
||||
2. **The Theme tab row WINS.** `app.core.theming.effective_settings` (L144–177) resolves each string as: the `ui_settings` row value when it is a non-empty string, **else** `settings.<field>` (the env value, which — via the phase-168 overlay — is the Settings-panel row value when one exists). So the order is Theme-tab row → Settings-panel row → env.
|
||||
3. **A Theme-tab save bakes the strings in.** `theme.js` `collectBody()` (L526) collects ALL 20 fields — the 3 string inputs are pre-populated with the EFFECTIVE value on load — and `PUT /api/ui-settings` writes every non-empty string into the row. So the moment an admin saves ANY color from the Theme tab, the current app name is baked into `ui_settings.app_name`, and from then on **every later Settings-panel app-name change is shadowed** by that baked row. This is exactly "it no longer updates the name" — the Settings panel shows the new value with its `db` badge, but `/api/config` (which serves `effective_settings`) reports the Theme tab's baked name.
|
||||
4. **No live brand update, on either surface.** `settings.js` re-GETs the catalogue after a save but never re-applies the brand strings to the open document; `theme.js` after a save runs `applyServedTheme(settings)` which reconciles the COLOR palette (phase 92) but never touches `document.title`, `.brand-text`, the footer or the composer placeholder. `brand.js` runs the brand path once at boot from `/api/config`. So even when the server-side value is right, the open page keeps showing the old name until a manual reload.
|
||||
|
||||
## Objective
|
||||
The Settings panel is the single writable owner of the three brand strings and always wins; the Theme tab becomes colors-only (its PUT no longer writes the strings, its form drops them — for legacy baked-in rows the panel still wins); and a save in either surface re-applies the brand strings to the open document immediately, so the change is visible the moment "save" is clicked (the phase-168 contract extended to the display).
|
||||
|
||||
## ASSUMPTIONS (owner-confirmed 2026-10-18)
|
||||
- **A1 — Panel wins, strings stripped from the Theme tab.** The Settings-panel row beats the Theme-tab row for `app_name` / `input_placeholder` / `footer_text` (no data migration — existing baked-in rows simply stop shadowing the panel; with no panel row they still apply as the documented fallback). The Theme tab's form drops the three string inputs and its PUT body no longer carries them (the server ignores string fields in the PUT body defensively — an old client's save can no longer re-bake a string). The Theme tab's GET keeps reporting the effective strings (read-only, old-client compat).
|
||||
- **A2 — Live brand on save.** After a successful Settings-panel save/clear or Theme-tab save/reset, the open document's `document.title`, `.brand-text`, the footer and the composer placeholder are re-applied from a fresh `/api/config` — no reload. The boot path (`brand.js`) is byte-identical.
|
||||
|
||||
## Context
|
||||
- `app/core/theming.py` — `effective_settings` (L144–177: the string resolution to change), `STRING_FIELDS`, `COLOR_FIELDS`, `BUILTIN_COLORS`; `app/api/config.py` — `GET /api/config` (serves `effective_settings` + `source` per string); `app/api/ui_settings.py` — the phase-91 admin API (GET reports 20 fields, PUT writes 3 strings + 17 colors, DELETE resets the row); `app/core/settings_overlay.py` + `app/config.py` `invalidate_settings` (L1129) — the phase-168 DB-over-env overlay (the panel rows; `settings.app_name` already resolves through it); `app/api/admin_settings.py` (phase 168, the panel surface).
|
||||
- `frontend/assets/brand.js` — the boot-time brand path (title / `.brand-text` / footer / placeholder from `/api/config`); `frontend/assets/theme.js` — `FIELDS` (L183–185 the 3 string fields), `collectBody` (L526), `saveTheme` (L580, `applyServedTheme` L589), `resetTheme` (L597); `frontend/assets/settings.js` — the phase-168 panel (its header names the phase-168 contract "the change should take effect on clicking 'save'"); `frontend/index.html` — the Theme-tab markup (`#theme-app-name` L1354 et al., the three string rows to remove).
|
||||
- `tests/unit/test_theming.py` (the `effective_settings` pins), `tests/integration/test_ui_settings_api.py` (the phase-91 API pins), `tests/e2e/test_admin_theme_tab.py` (fills `#theme-app-name` — must move with the strip), `tests/e2e/test_settings_panel.py` (the phase-168 pins), `tests/unit/test_save_chat_ui.py` (JS marker-pin convention).
|
||||
|
||||
## Tasks
|
||||
1. `01_reproduce_shadow_and_staleness.md` — failing tests pinning both defects (the shadowing through `effective_settings` / `/api/config`, the Theme-tab PUT string bake, the no-live-update behavior).
|
||||
2. `02_precedence_and_strip_theme_tab.md` — A1: the resolution order flips to panel → tab → env; the Theme tab drops the 3 string inputs and its PUT stops writing them; the pinned theme-tab suites move.
|
||||
3. `03_live_brand_on_save.md` — A2: one shared `applyBrandFromConfig` pass (the `brand.js` DOM path extracted, boot behavior byte-identical) re-applied after a Settings-panel save/clear and a Theme-tab save/reset.
|
||||
4. `04_e2e_and_gates.md` — the dedicated Playwright suite (the incident regression through the real UI), the full gates, the docs pass, the atomic commit.
|
||||
|
||||
## Testing & Quality
|
||||
- **Unit:** `tests/unit/test_theming.py` — the NEW resolution order pins (panel row beats a conflicting `ui_settings` row; with no panel row the tab row still applies — the A1 fallback); the pre-phase pins that do not involve a panel row stay green.
|
||||
- **Integration:** `tests/integration/test_ui_settings_api.py` — a Theme-tab PUT with string fields in the body **ignores** them (the stored row's string columns are untouched / the response no longer round-trips a changed string); `tests/integration/` Settings-panel pins (phase 168) green — the panel write + `invalidate_settings` path is untouched.
|
||||
- **E2E (AGENTS rule 4, task 04):** `tests/e2e/test_app_name_settings_fix.py`, run in isolation — (a) the incident regression: set the name in the Settings panel → save a color in the Theme tab → the name still shows the panel's value (pre-fix: the baked tab value); (b) live apply: change the app name in the Settings panel → `document.title`, `.brand-text`, the footer and the composer placeholder update WITHOUT a reload; (c) a Theme-tab save no longer bakes the name (a later panel change is visible on reload); (d) a panel reset (DELETE) falls back to env live.
|
||||
- **Regression:** `tests/e2e/test_settings_panel.py`, `tests/e2e/test_admin_theme_tab.py` (moved in task 02), `tests/e2e/test_theme_save_and_coverage.py` green.
|
||||
- **Coverage:** **>90%** on `app/` (`uv run pytest --cov=app --cov-report=term-missing`); the frontend change is marker-pinned per the house JS convention.
|
||||
- **Lint/types:** `uv run ruff check . && uv run pyright`.
|
||||
|
||||
## Documentation & Consistency
|
||||
- Final pass reviews `README.md` and `AGENTS.md` in their entirety against the post-phase app and fixes every inconsistency found, pre-existing ones included.
|
||||
- `README.md`: the settings/theme section notes the single-writer ownership (the Settings panel owns the brand strings; the Theme tab is colors-only) and the live-apply behavior (task 04). `AGENTS.md`: the phase-168 "settings overlay" description gains the resolution-order clause if it names one (verify, do not add if absent).
|
||||
|
||||
## Completion Criteria
|
||||
- [ ] `effective_settings` resolves the 3 strings panel-row → tab-row → env (unit pins, both branches).
|
||||
- [ ] `PUT /api/ui-settings` with string fields in the body does not write them (integration pin); the Theme-tab form no longer renders the 3 inputs (E2E pin).
|
||||
- [ ] A Settings-panel app-name change is visible in `document.title`, `.brand-text`, the footer and the placeholder without a reload (E2E); the Theme-tab color save no longer shadows it (E2E regression of the incident).
|
||||
- [ ] the `brand.js` boot path is byte-identical (marker pin) — no double-apply at boot.
|
||||
- [ ] test suite green, coverage >90%; `uv run ruff check . && uv run pyright` clean.
|
||||
- [ ] no behavior change in completed phases (the 17-color palette, the phase-168 panel, the env fallback all behave as before).
|
||||
- [ ] one atomic Conventional Commits commit with `--no-gpg-sign`.
|
||||
@@ -1,31 +0,0 @@
|
||||
# Task 01 — Reproduce and pin the two defects (shadowing + no live update)
|
||||
|
||||
**Phase:** `190_app_name_settings_fix` · **Source:** `TODO.md:4 — "changing the App name in settings broke, it no longer updates the name."`
|
||||
**Story:** n/a
|
||||
|
||||
## Objective
|
||||
Before touching behavior: pin BOTH defects with failing tests — (a) the `ui_settings` row shadows the Settings-panel row in `effective_settings` and therefore in `GET /api/config`, and a Theme-tab PUT bakes the effective strings into the row (the mechanism of "it no longer updates"); (b) a Settings-panel save never re-applies the brand strings to the open document (marker pin on the ABSENT behavior, inverted in task 03).
|
||||
|
||||
## Work
|
||||
1. `tests/unit/test_theming.py` — NEW pins (failing until task 02):
|
||||
- `test_effective_app_name_prefers_settings_panel_row_over_ui_settings_row` — an `app_settings` row (via the phase-168 overlay / a `Settings` with `app_name="Panel Name"`) + a `ui_settings` row with `app_name="Tab Name"` → `effective_settings["app_name"] == "Panel Name"` (today: `"Tab Name"` — the documented A1 order). Same pair for `input_placeholder` and `footer_text`.
|
||||
- `test_effective_strings_fall_back_to_ui_settings_row_without_panel_row` — `ui_settings` row set, no panel row (the `Settings` at the env default) → the tab value applies (the A1 fallback this pin protects — it must stay GREEN pre-phase and post-phase).
|
||||
- The pre-existing pins that assume tab-over-env with NO panel row stay green unedited.
|
||||
2. `tests/integration/test_ui_settings_api.py` — NEW pins (failing until task 02):
|
||||
- `test_put_ignores_string_fields_in_body` — a `PUT /api/ui-settings` whose body carries `app_name` / `input_placeholder` / `footer_text` DIFFERENT from the stored row's values and from the effective value → 200; the stored row's string columns are UNCHANGED (re-GET shows the old values); the 17 colors in the same body ARE stored (the colors half of the contract survives).
|
||||
- `test_get_reports_effective_strings_read_only` — with a conflicting panel row, `GET /api/ui-settings` reports the EFFECTIVE (panel) value for the strings (read-only view; the A1 order reaches the API).
|
||||
3. `tests/unit/test_brand_live.py` (NEW module) — marker pins on the CURRENT (absent) behavior, documented as "inverted in task 03":
|
||||
- `settings.js` contains NO call that re-applies `document.title` / `.brand-text` after a save (assert the marker is ABSENT pre-phase — the test asserts the file's save path does not reference the brand DOM ids; task 03 inverts the expectation);
|
||||
- `theme.js` `saveTheme`'s success path calls `applyServedTheme` (colors only) and does NOT touch the brand DOM (marker pin — stays green through the phase; the live-apply added in task 03 is asserted there, not here).
|
||||
4. Verify: `uv run pytest tests/unit/test_theming.py tests/integration/test_ui_settings_api.py tests/unit/test_brand_live.py -v` — the NEW pins FAIL (documented), everything else green.
|
||||
|
||||
## Testing & Quality
|
||||
- Unit/integration: the pins above — the shadowing mechanism, the string-bake, the fallback (green pre-phase), the absent-live-behavior markers.
|
||||
- Coverage: **>90%** on `app/` (no app code changes this task — the pins exercise existing code).
|
||||
|
||||
## Completion Criteria
|
||||
- [ ] the 3 `effective_settings` order pins FAIL pre-fix (documented in the test docstrings: "fixed by task 02").
|
||||
- [ ] the string-bake integration pin FAILS pre-fix (documented); the colors-half pin is GREEN pre-fix.
|
||||
- [ ] the live-behavior marker pins pass (asserting the ABSENT behavior, inverted in task 03).
|
||||
- [ ] full test suite **100% passing** except the documented failing pins (the failing pins are the task's artifact — the phase's E2E gate runs them post-fix).
|
||||
- [ ] no behavior change in completed work (app/ untouched).
|
||||
@@ -1,45 +0,0 @@
|
||||
# Task 02 — The panel wins; the Theme tab becomes colors-only (A1)
|
||||
|
||||
**Phase:** `190_app_name_settings_fix` · **Source:** `TODO.md:4 — "changing the App name in settings broke, it no longer updates the name."`
|
||||
**Story:** n/a
|
||||
|
||||
## Objective
|
||||
Flip the string resolution order to Settings-panel row → Theme-tab row → env (no data migration — existing baked-in rows stop shadowing the panel), and strip the three string inputs from the Theme tab: the form drops them, `collectBody` stops collecting them, and the server's `PUT /api/ui-settings` persists ONLY the 17 colors (string fields in the body are ignored defensively — an old client's save can no longer re-bake a string; string columns are left untouched, not cleared, so the A1 fallback survives).
|
||||
|
||||
## Work
|
||||
1. `app/core/theming.py` — `effective_settings` (L170–173), the string loop becomes a three-way resolution per field:
|
||||
- **panel row** — `getattr(settings, field)` (the `Settings` passed by the route; the phase-168 overlay makes it the panel value when an `app_settings` row exists, else env) is used when a panel row exists for that key. The panel presence is read through the SAME overlay source the `Settings` object carries: the overlay's `app_settings` row map (`app.core.settings_overlay.load_overrides()` — the one query the overlay already caches per process; the route passes the settled `Settings`, so the check is `field in the override map` — implement as `has_override(field)` on the overlay module, one cached read, no second query per field);
|
||||
- **else the tab row** — the `ui_settings` row value when it is a non-empty string (the phase-91 fallback, now second);
|
||||
- **else the env value** — `getattr(settings, field)` (identical expression to today's fallback).
|
||||
- The 17-color half is UNTOUCHED (row → `BUILTIN_COLORS`). The docstring's resolution table is rewritten (the old "DB value when non-empty, else env" becomes the three-way table).
|
||||
- `app/api/config.py` — the per-string `source` badge (`db` / `env`): `db` now means the PANEL row; the tab row yields the existing non-`db` value (verify the current `source` vocabulary — if it is a two-value `db`/`env` pair, `db` = any stored row wins, unchanged; do NOT grow the vocabulary).
|
||||
2. `app/api/ui_settings.py` — the PUT handler:
|
||||
- persist ONLY the 17 `COLOR_FIELDS` from the body; the three string columns are NEVER written (the request model keeps accepting the fields — old-client compat — the handler drops them before persisting); the stored row's string values are left exactly as stored (a legacy baked-in row keeps its value; the A1 order decides visibility);
|
||||
- the DELETE (reset) path: UNCHANGED (it nulls the whole row — including the strings; a reset is a user decision, and the A1 fallback then falls through to env, which is the documented reset behavior).
|
||||
- the GET: unchanged (reports the effective strings — with the new order, a conflicting panel value is what the tab page pre-populates its (now removed) inputs with in an old client; the read-only report stays).
|
||||
3. `frontend/assets/theme.js`:
|
||||
- `FIELDS` (L183–185): the three `kind: "string"` entries are REMOVED — `FIELDS` becomes the 17 color fields only; everything FIELDS-driven (live preview, `collectBody`, `applyServedTheme`'s tag content) stays in `COLOR_FIELDS` order (the comment at L180–181 already says so — it becomes true);
|
||||
- `collectBody` (L526): the body is the 17 colors only (no string keys at all);
|
||||
- the string-specific docstring lines (L183–185 region + any "20 fields" counts in the module header) become "17 colors."
|
||||
4. `frontend/index.html` — the Theme-tab markup: the three string rows (`#theme-app-name` L1354 et al., the placeholder + footer rows) are REMOVED from the form; the section count / any "3 strings + 17 colors" comment becomes colors-only.
|
||||
5. `tests/unit/test_theming.py` — the task-01 failing pins now PASS unedited (they name the A1 order); the no-panel-row fallback pin stays green.
|
||||
6. `tests/integration/test_ui_settings_api.py` — the task-01 string-bake pin now PASSES (body strings ignored, colors stored, string columns untouched).
|
||||
7. The pinned theme-tab E2E suites that fill the removed inputs move:
|
||||
- `tests/e2e/test_admin_theme_tab.py` — every step that fills `#theme-app-name` / `#theme-placeholder` / `#theme-footer` (or asserts their presence) is RETARGETED: the tab renders 17 color inputs only (a new pin: the three string inputs are ABSENT from the tab DOM — `page.locator("#theme-app-name")` count 0); a save still round-trips the 17 colors through the GET pre-population.
|
||||
- `tests/e2e/test_theme_save_and_coverage.py` / any other suite that seeds a string through the tab: the string is now seeded through the Settings panel (the `test_settings_panel.py` helper) or the API (the integration test covers the wire; the E2E keeps its color assertions).
|
||||
8. `tests/unit/test_theme_ui.py` (or the module that marker-pins `theme.js` — verify the name in `tests/unit/`) — marker pins: `FIELDS` has no `kind: "string"` entry; `collectBody`'s body has no string keys (the source-marker convention); the `saveTheme` success path still calls `applyServedTheme`.
|
||||
9. Verify: `uv run pytest tests/unit/test_theming.py tests/integration/test_ui_settings_api.py -v` green; `uv run pytest tests/e2e/test_admin_theme_tab.py tests/e2e/test_settings_panel.py -v --no-cov` green; `uv run pytest` green; `uv run ruff check . && uv run pyright` clean.
|
||||
|
||||
## Testing & Quality
|
||||
- Unit: the A1 order pins (both branches) + the marker pins (tab has no string fields).
|
||||
- Integration: string-ignored PUT pin + the phase-91 color pins green unedited.
|
||||
- E2E (regression, run in isolation): `test_admin_theme_tab.py` (moved) + `test_settings_panel.py` green.
|
||||
- Coverage: **>90%** on `app/`.
|
||||
|
||||
## Completion Criteria
|
||||
- [ ] `effective_settings` resolves the 3 strings panel → tab → env (both-branch unit pins green).
|
||||
- [ ] `PUT /api/ui-settings` with string fields in the body stores no string change and stores the colors (integration pin green).
|
||||
- [ ] the Theme-tab DOM renders 17 color inputs only; the 3 string inputs are absent (E2E pin).
|
||||
- [ ] the phase-91 color contract (GET pre-population, save, reset) is green unedited.
|
||||
- [ ] full test suite **100% passing**.
|
||||
- [ ] no behavior change in completed work (the 17 colors, the phase-168 panel, the env fallback, the DELETE-reset behavior all as before).
|
||||
@@ -1,37 +0,0 @@
|
||||
# Task 03 — The save is visible without a reload (A2)
|
||||
|
||||
**Phase:** `190_app_name_settings_fix` · **Source:** `TODO.md:4 — "changing the App name in settings broke, it no longer updates the name."` (the phase-168 header contract: "the change should take effect on clicking 'save'")
|
||||
**Story:** n/a
|
||||
|
||||
## Objective
|
||||
A successful save in the Settings panel (or a per-row reset) and a save/reset in the Theme tab re-applies the brand strings to the OPEN document immediately — `document.title`, `.brand-text`, the footer and the composer placeholder — from a fresh `GET /api/config`, with no reload. The boot path in `brand.js` is extracted into the shared pass byte-identically, so boot and live-apply are ONE function and the never-stale contract (PLAN §7.4) covers the strings the way it already covers the palette (phase 92 `applyServedTheme`).
|
||||
|
||||
## Work
|
||||
1. `frontend/assets/brand.js` — extract the DOM path (title / `.brand-text` / footer / placeholder assignment from the `/api/config` payload) into ONE named function `applyBrandFromConfig(cfg)` (same file, same module scope — no new file, no new CDN; `brand.js` stays the single owner of the brand DOM):
|
||||
- the BOOT call site (`initBrand` / the boot fetch-then-apply) calls `applyBrandFromConfig(cfg)` — the observable boot behavior is byte-identical (same request, same assignments, same order);
|
||||
- the function is exported to the module's existing export surface (the `window` / module pattern `brand.js` already uses — verify the convention; `theme.js` and `settings.js` import from the same pattern).
|
||||
2. `frontend/assets/settings.js` — the save path:
|
||||
- after a SUCCESSFUL per-key PUT (`PUT /api/admin/settings/<key>`, the phase-168 success note) and after a SUCCESSFUL per-row reset (DELETE) and the reset-all: `await fetch("/api/config")` → `applyBrandFromConfig(cfg)` — ONLY when the saved/removed key is one of the three brand strings (`app_name` / `input_placeholder` / `footer_text`); a save of any other key does NOT re-apply the brand (no wasted request, no DOM churn on 72 other rows);
|
||||
- the §7.4 guard: while the re-apply fetch is in flight, a second save does not stack a second fetch (the existing `saveInFlight` guard's scope — extend, do not fork);
|
||||
- the existing post-save re-GET of the catalogue is UNTOUCHED (it stays the catalogue's own refresh; the brand re-apply is additive and independent).
|
||||
3. `frontend/assets/theme.js` — the save/reset path:
|
||||
- `saveTheme` success (after the existing `applyServedTheme(settings)` at L589) and `resetTheme` success: `await fetch("/api/config")` → `applyBrandFromConfig(cfg)`. (The tab no longer edits the strings, but a RESET nulls the stored row — with a legacy baked-in string that reset makes the effective string FALL BACK (panel row → env), so the open document must re-read it; the save path keeps the same call — one code path for both, the body change is the colors.)
|
||||
- the fetch-failure path: the brand keeps its last applied state (never a blanked title/footer — the phase-168 "never a blanked panel" precedent, applied to the brand: a failed re-apply leaves the DOM as-is; the save's own error note is the visibility).
|
||||
4. `tests/unit/test_brand_live.py` — the task-01 absent-behavior marker pins are INVERTED:
|
||||
- `settings.js`'s save path references `applyBrandFromConfig` (the marker is now PRESENT) and gates the re-apply on the three string keys (the source marker names the key check);
|
||||
- `theme.js`'s `saveTheme`/`resetTheme` success paths reference `applyBrandFromConfig` (marker present);
|
||||
- `brand.js` exports `applyBrandFromConfig` and the boot call site calls it (marker — the extraction, not a duplication: `initBrand`/boot contains no second copy of the four DOM assignments — they exist only in the shared function).
|
||||
5. Verify: `uv run pytest tests/unit/test_brand_live.py -v` green; `uv run pytest tests/e2e/test_settings_panel.py -v --no-cov` green (the phase-168 suite — its save flow now also triggers the brand re-apply; its pins must stay green — the re-apply is additive); `uv run pytest` green.
|
||||
|
||||
## Testing & Quality
|
||||
- Unit: the inverted marker pins (shared function exists; both surfaces call it; the boot path is the extraction — single copy).
|
||||
- Integration: none (pure frontend — the wire contract is task 02's; `/api/config` is unchanged).
|
||||
- E2E: covered by task 04 (the owner-visible arm).
|
||||
- Coverage: **>90%** on `app/` (unchanged — the task is frontend; the app-side gate stays green).
|
||||
|
||||
## Completion Criteria
|
||||
- [ ] `applyBrandFromConfig` is ONE function in `brand.js`; the boot call site uses it; the four DOM assignments exist in exactly one place (marker pins).
|
||||
- [ ] `settings.js` re-applies the brand after a save/reset of a brand-string key only; `theme.js` re-applies after save and reset (marker pins).
|
||||
- [ ] a failed re-apply leaves the DOM at its last applied state (marker pin on the failure path).
|
||||
- [ ] `test_settings_panel.py` green unedited.
|
||||
- [ ] no behavior change in completed work (the boot path, the phase-168 catalogue refresh, the phase-92 palette sync all as before).
|
||||
@@ -1,35 +0,0 @@
|
||||
# Task 04 — The phase E2E suite, the full gates, the docs pass, the commit
|
||||
|
||||
**Phase:** `190_app_name_settings_fix` · **Source:** `TODO.md:4`
|
||||
**Story:** n/a
|
||||
|
||||
## Objective
|
||||
The dedicated Playwright suite proves the incident regression and the live-apply contract through the real UI (run in isolation per AGENTS rule 4); the full gates pass; `README.md` / `AGENTS.md` are consistent with the post-phase app; one atomic commit.
|
||||
|
||||
## Work
|
||||
1. `tests/e2e/test_app_name_settings_fix.py` (NEW — the module docstring names the incident: "a Theme-tab save used to bake the effective name into `ui_settings` and shadow every later Settings-panel change") — the app under test is the standard module-app pattern (the phase-168/91 suites' fixture — verify the convention in `tests/e2e/conftest.py` / the sibling suites):
|
||||
- **(a) the incident regression (server-side, at the wire):** with an env default name, the admin sets the app name via the Settings panel UI (type + save, the phase-168 flow) → `GET /api/config` (via `page.request`) reports the panel value with the `db` source; then the admin opens the Theme tab, changes ONE color and saves → `GET /api/config` STILL reports the panel value (pre-fix: the baked tab value); the stored `ui_settings` row's string columns are unchanged (the API-level assertion — `page.request` GET `/api/ui-settings`).
|
||||
- **(b) live apply, panel:** change the app name in the Settings panel (a brand-string row) → after the save note, WITHOUT a reload: `document.title`, `.brand-text` (the header), the footer text and the composer placeholder all carry the new name (the four assertions in one test — the A2 contract).
|
||||
- **(c) live apply, tab reset:** with a legacy `ui_settings` row pre-seeded (the API seeds the row directly — a baked `app_name`), a Theme-tab Reset → the open document's brand falls back (panel row or env — seed NO panel row so the fallback is env) WITHOUT a reload.
|
||||
- **(d) the boot path is byte-identical:** a fresh page load (no save) shows the env/panel name exactly as the pre-phase `brand.js` did (the `test_admin_theme_tab.py` boot pin, moved here if the moved suite no longer covers it).
|
||||
- **(e) the tab DOM is colors-only:** the Theme tab renders 17 color inputs and NO `#theme-app-name` / placeholder / footer string inputs (the task-02 pin, asserted in the isolated suite as well).
|
||||
2. Regression, run in isolation: `tests/e2e/test_admin_theme_tab.py` (moved in task 02), `tests/e2e/test_settings_panel.py`, `tests/e2e/test_theme_save_and_coverage.py` — all green unedited after the moves.
|
||||
3. The full gates, in order:
|
||||
- `uv run pytest` (unit + integration) — 100% passing;
|
||||
- `uv run pytest --cov=app --cov-report=term-missing` — **>90%** on `app/`;
|
||||
- `uv run pytest tests/e2e/test_app_name_settings_fix.py -v --no-cov` — green in isolation;
|
||||
- `uv run ruff check . && uv run pyright` — clean.
|
||||
4. Documentation pass (the phase file's Documentation section): `README.md` + `AGENTS.md` reviewed in their entirety against the post-phase app; the settings/theme section notes the single-writer ownership (Settings panel owns the 3 brand strings, panel-row → tab-row → env order) and the live-apply behavior; `AGENTS.md` corrected only where it names the old order.
|
||||
5. Commit: one atomic, professional commit (Conventional Commits, `--no-gpg-sign`), e.g.
|
||||
`fix(settings): the Settings-panel app name wins over the Theme tab and saves apply live (phase 190)` — the body names the two defects (the `ui_settings` shadow bake, the absent live re-apply), the A1/A2 decisions, and the moved theme-tab suites. `.agents/phases/todo/190_app_name_settings_fix/` moves to `.agents/phases/complete/` in the SAME commit (AGENTS rule 8).
|
||||
|
||||
## Testing & Quality
|
||||
- E2E: the suite above, isolated; the three regression suites green.
|
||||
- Coverage: **>90%** on `app/`.
|
||||
- Lint/types: clean.
|
||||
|
||||
## Completion Criteria
|
||||
- [ ] `tests/e2e/test_app_name_settings_fix.py` green in isolation — (a) the incident regression (the panel value survives a Theme-tab color save), (b) the four-DOM live apply without reload, (c) the tab-reset fallback live, (d) the byte-identical boot, (e) the colors-only tab DOM.
|
||||
- [ ] `uv run pytest` 100% passing; `uv run pytest --cov=app --cov-report=term-missing` >90%; `uv run ruff check . && uv run pyright` clean.
|
||||
- [ ] `README.md` / `AGENTS.md` consistent (the single-writer + live-apply clauses).
|
||||
- [ ] the phase directory is in `.agents/phases/complete/`; one atomic commit with `--no-gpg-sign`.
|
||||
Reference in New Issue
Block a user