phase: 92_theme_save_and_coverage
**Phase 92 final verification pass — all green.** This pass re-verified the completed tasks (all 5 task files already in `complete/`) against every completion criterion; no defects found, nothing to fix. - Verified: 9th identity var `grid_line` end-to-end (migration `0015` at head, model/`theming.py`/schemas/API, 422 + built-in→NULL tests present); `styles.css` zero hardcoded literals outside `:root` + derived `--brand-*` vars; 9th picker in theme form; wordmark themed; `theme.js` save/reset/re-show/mount live-sync; dedicated E2E suite + phase-91 suite updated. - `uv run pytest --cov=app --cov-report=term-missing` → **1845 passed, exit 0, TOTAL 99%** (>90%) - `uv run ruff check .` → clean; `uv run pyright` → 0 errors, 0 warnings - `uv run pytest tests/e2e/test_theme_save_and_coverage.py -v --no-cov` → **3 passed** (save-live, reset-live, whole-site) - `uv run pytest tests/e2e/test_admin_theme_tab.py -v --no-cov` → **5 passed** - Criteria: (1) Save/Reset repaint open page, no nav, SPA-nav survives, pre-paint intact ✅; (2) both `rg` gates green (only `:root` + documented `#fff` Stop label; zero SVG hex attrs), grid/selection/hovers/wash/wordmark E2E-proven ✅; (3) no-op contract live-checked: row-less `/` = no tag + exact A1 CSP, grid-only row = 9-var tag in `COLOR_FIELDS` order + sha256 CSP, with-row ≡ row-less bytes ✅; (4) full suite/coverage/lint/both E2E ✅; (5) commit left to the harness per instructions. - Deviations (previously made, probe-verified, kept): live repaint uses CSSOM `<html>` overrides because Chromium blocks `<style>` textContent mutations under the locked sha256-only CSP (tag text still mirrors the next load; `<html>` style exact-saved after Save, empty after Reset); wordmark themed via 3 `.brand-mark` CSS rules instead of inline styles (task 03's inline attrs were CSP-blocked — fixed during task 04). - Next pending phase: none — `todo/` contains only `92_theme_save_and_coverage`.
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
# Phase 92 — Theme defects: save applies live (no reload) + the theme controls drive the ENTIRE site
|
||||
|
||||
**Source:** Owner chat defect report (post-phase-91): (1) "Clicking 'save theme' reverts the theme back to the previous theme, a refresh is required to see the new theme."; (2) "Not everything is controllable via the theme controls. Certain buttons and text are still light pink on highlight, for example. The background grid never changes color." — "The theme controls should allow manipulating the entire site's theme."
|
||||
**Story:** n/a (owner-chat defect fix on `91_admin_theme_tab`).
|
||||
**Context:** Phase 91 shipped the admin Theme tab: 8 identity color pickers + 3 strings, persisted to the single-row `ui_settings` table (`app/api/ui_settings.py`, `app/core/theming.py`), injected pre-paint as `<style id="bor-theme">:root{…}</style>` by the `CachingMiddleware` (`app/core/caching.py`), edited live in `frontend/assets/theme.js`. **Defect 1 root cause (confirmed in code):** after a successful Save/Reset, `theme.js::saveTheme()` runs `loadSettings()` then `clearPreview()` — the live-preview overrides (inline custom properties on `<html>`) are removed, and the page falls back to the `#bor-theme` tag that was baked into THIS document at page load — i.e. the PREVIOUS theme. The new palette only appears after a full reload (the server then injects the new tag). The phase-91 E2E masked this: its save test never asserts the live computed palette after save, and its reset test only checks computed values after a fresh `page.goto`. **Defect 2 root cause:** `frontend/assets/styles.css` still carries ~45 hardcoded color literals outside `:root` that no control can reach: the background grid (`body::before`, `rgb(74 38 38 / 0.6)` — never themes), `::selection` (brand at 45%, hardcoded), the header hairline gradient (hardcoded rose→orange→amber), every primary-button hover (`#f55a72` rose / legacy indigo `#7d88f5`), the disabled/busy state (indigo `#a5b4fc`), the Stop state (`#be123c`/`#9f1239`), three chip hovers (indigo `#2a345f`), six hardcoded `#1a0f0f` surface backgrounds + `#e6d0d0` code text, the ok/err alpha derivatives, four modal backdrops, the spinner track, and the static brand-mark SVG in all five HTML files (`fill="#1a0f0f" stroke="#f43f5e" … stroke="#fca5a5"` — the wordmark never themes).
|
||||
|
||||
## Objective
|
||||
Fix both phase-91 defects so the Theme tab controls the entire site's theme: (1) Save/Reset must apply the new palette to the OPEN page immediately — no reload, no revert — by syncing the live document's `#bor-theme` tag to the saved state; (2) every color in the UI must be driven by the identity palette (directly or via `color-mix()` derivations) — zero hardcoded literals outside `:root` — with the background grid promoted to a 9th tab-controlled identity variable (`--grid-line`).
|
||||
|
||||
## Dependencies
|
||||
- `91_admin_theme_tab` (complete) — the entire phase builds on it: the `ui_settings` table + resolver + admin API (`app/api/ui_settings.py`), `app/core/theming.py` (`BUILTIN_COLORS` / `COLOR_FIELDS` / `effective_settings` / `theme_style_tag` / `inject_theme` / `theme_csp_hash`), the pre-paint injection + CSP hash in `app/core/caching.py`, the tab shell + editor (`frontend/index.html` `#view-theme`, `frontend/assets/theme.js`), and the E2E suite `tests/e2e/test_admin_theme_tab.py` (updated in place, task 05). Its contracts (byte-identical no-op tag, admin-only gate, pre-paint first paint, §7.4 save lifecycle) all stay green.
|
||||
|
||||
## Design (shared by all tasks — the executor reads this, not the chat)
|
||||
|
||||
### Defect 1 — Save/Reset applies the theme to the open document (task 04)
|
||||
The server-side pre-paint injection is untouched. The fix is entirely client-side in `theme.js`: after any settled read of the effective values, SYNC THE DOCUMENT'S `#bor-theme` TAG to those values, mirroring what the server would inject on the next load.
|
||||
|
||||
- `theme.js` gains two small pure helpers + one DOM sync:
|
||||
- **Built-in capture:** `BUILTINS` — the 9 built-in hexes captured from the color inputs' STATIC values at mount (BEFORE the first `loadSettings()` repopulates). The static values are the built-ins by the house contract (the phase-91 E2E asserts them against `styles.css` `:root`), so this keeps ONE source — no third hardcoded palette copy.
|
||||
- `themeRootContent(colors) -> string | null` — `null` when all 9 effective colors equal their built-ins (the no-op case), else `":root{" + Σ f"--{field.replace(/_/g,'-')}:value;" (COLOR_FIELDS/FIELDS order) + "}"` — byte-identical to the CONTENT of `app.core.theming.theme_style_tag`'s tag (same fields, same order, lowercased hex from the resolver).
|
||||
- `applyServedTheme(effective)`: content `null` → `document.getElementById("bor-theme")?.remove()`; else if the tag exists → set `el.textContent = content` (only when different); else create it with `document.createElement("style")` + `el.id = "bor-theme"` + `textContent` and append to `document.head`.
|
||||
- **CSP (why this shape):** the phase-82/91 CSP (`style-src 'self' 'sha256-…'`, no `'unsafe-inline'`) blocks inline `<style>` at PARSE time and parser-inserted styles (`innerHTML` / `insertAdjacentHTML`). CSSOM mutations are not style-source checkpoints: `textContent` on an EXISTING style element, and `createElement` + `textContent` (never parsed as markup), are both CSP-clean — and `createElement` + `textContent` also satisfies the house "this file never builds HTML" rule. The existing preview mechanism (`documentElement.style.setProperty`, CSSOM) is likewise CSP-clean (it already works today).
|
||||
- **Call sites (all after a SETTLED read):**
|
||||
1. `saveTheme()` — on PUT 200: `showResult` → `const s = await loadSettings()` → `if (s) { applyServedTheme(s); clearPreview(); }` (replaces today's bare `loadSettings(); clearPreview();` — the defect).
|
||||
2. `resetTheme()` — same shape (all-null PUT → effective = built-ins → content `null` → the tag is REMOVED from the live document).
|
||||
3. `bor:view-refresh` (phase-77 re-show hook) — `loadSettings()` → if settled: `applyServedTheme(s)` THEN `clearPreview()` (the re-show case had the SAME latent revert — today it clears the preview onto the stale tag).
|
||||
4. Initial mount — after the first `loadSettings()` settles: `applyServedTheme(s)` (self-heal: a row changed in another browser since page load is reflected the moment the admin opens the tab; idempotent — a normal load is a no-op because the served tag already matches).
|
||||
- `loadSettings()` changes to RETURN the settings object on success (`null` on failure); `populate` + `updateContrast` behavior unchanged.
|
||||
- `FIELDS` gains the 9th entry `{ field: "grid_line", id: "theme-grid-line", kind: "color" }` (between `line` and `brand`) — the preview, `collectBody`, `clearPreview`, `updateContrast`-exclusion, and `applyServedTheme` all iterate `FIELDS`, so they pick it up; the contrast pairs are UNCHANGED (the grid line is decorative — no contrast duty, like `--line`).
|
||||
|
||||
### Defect 2 — whole-site variable coverage (tasks 01, 02, 03)
|
||||
New technique (owner-permitted by the chat request; pure native CSS, no dependency): **CSS `color-mix(in srgb, …)`** for derived state colors (Chromium baseline since 111 — the Playwright target). `.agents/PLAN.md` is absent from the repo (dangling AGENTS.md reference — no anchor table exists to sign off), so this is recorded here for the owner.
|
||||
|
||||
**A 9th IDENTITY variable (stored + tab-controlled):** `--grid-line` — the background grid texture color, built-in `#4a2626` (today's hardcoded grid, exact). Server plumbing exactly mirrors the existing 8 (task 01): migration `0015`, `UiSettings.grid_line` column, `BUILTIN_COLORS["grid_line"]` (the drift test picks it up from `:root`), `COLOR_FIELDS` order = `bg, surface, ink, ink_soft, line, grid_line, brand, brand_soft, brand_ink` (structural colors first, brand last — the tag's byte layout changes accordingly), `UiSettingsIn/Out` fields, and the API is already `COLOR_FIELDS`-driven (no loop changes). `theme_style_tag` / `inject_theme` / `theme_csp_hash` / the middleware are all `COLOR_FIELDS`-driven — zero logic changes, the 9th var flows automatically.
|
||||
|
||||
**Derived state variables (computed in `:root` — NOT stored, NOT tab-controlled, NOT in `BUILTIN_COLORS`):**
|
||||
```css
|
||||
--brand-hover: color-mix(in srgb, var(--brand) 86%, white); /* replaces #f55a72 hover (default ≈ #f55a75, ±3/255) AND the legacy indigo #7d88f5 hovers */
|
||||
--brand-busy: color-mix(in srgb, var(--brand) 40%, white); /* replaces legacy indigo #a5b4fc disabled/busy; --bg spinner arc on it keeps ≥ 9:1 */
|
||||
--brand-stop: color-mix(in srgb, var(--brand) 75%, var(--bg)); /* replaces #be123c (default ≈ rgb(188,42,62)); #fff label holds 5.9:1 (AA) */
|
||||
```
|
||||
The Stop hover is inline: `color-mix(in srgb, var(--brand) 55%, var(--bg))` (replaces `#9f1239`; #fff on it 8.6:1).
|
||||
|
||||
**The replacement table** (task 02 — mechanical; anchor by selector, line numbers drift):
|
||||
|
||||
| Site (selector) | Today | Becomes |
|
||||
|---|---|---|
|
||||
| `body::before` grid (×2 gradient stops) | `rgb(74 38 38 / 0.6)` | `color-mix(in srgb, var(--grid-line) 60%, transparent)` — EXACT default |
|
||||
| `::selection` background | `rgb(244 63 94 / 0.45)` | `color-mix(in srgb, var(--brand) 45%, transparent)` — EXACT |
|
||||
| `.app-header::after, .doc-header::after` hairline (3 stops) | `rgb(244 63 94 / 0.55)`, `rgb(251 146 60 / 0.30) 45%`, `rgb(251 191 36 / 0.05) 90%` | brand fade: `color-mix(in srgb, var(--brand) 55%, transparent)`, `… 30%, transparent) 45%`, `… 5%, transparent) 90%` — DELIBERATE default change (the pre-theme rose→orange→amber art direction retires; the 2px hairline now follows the brand) |
|
||||
| `.bubble pre`, `.doc-md pre` | `background: #1a0f0f; color: #e6d0d0` | `background: var(--surface); color: var(--ink)` — minor (code text slightly brighter) |
|
||||
| the 6 hardcoded `#1a0f0f` backgrounds (`.tune-form textarea`, the two card blocks ~L881/~L1060, the login card ~L1462, + the two above) | `#1a0f0f` | `var(--surface)` — EXACT |
|
||||
| 8 brand-button hovers (`.new-chat-btn:hover`, `.share-chat-btn:hover`, `.stale-regenerate:hover`, `.ignore-editor-save:hover`, `.history-refresh:hover`, `.token-generate:hover`, `#theme-save:hover:not(:disabled)`, `.doc-summary-save:hover`) | `#f55a72` | `var(--brand-hover)` — ≈ EXACT |
|
||||
| 9 legacy-indigo hovers (`.tune-save:hover:not(:disabled)`, `#tune-save:hover:not(:disabled)`, `.send-btn:hover:not(:disabled)`, `.login-submit:hover:not(:disabled)`, `.sources-gate-link:hover`, `.auth-gate-submit:hover:not(:disabled)`, `#git-source-add:hover:not(:disabled), #archive-upload-btn:hover:not(:disabled)`, `.doc-open-sources:hover`, `#push-doc-btn:hover:not(:disabled)`) | `#7d88f5` | `var(--brand-hover)` — DELIBERATE FIX (an indigo hover under the rose brand was incoherent legacy) |
|
||||
| `.send-btn:disabled` | `#a5b4fc` | `var(--brand-busy)` — DELIBERATE FIX |
|
||||
| 3 chip hovers (`.source-chip:hover`, `.suggestion-chip:hover`, `.doc-back:hover`) | `#2a345f` | `var(--brand-soft)` — DELIBERATE FIX (the house nav-link wash pattern; `.source-chip` keeps its underline) |
|
||||
| `.send-btn.is-stop` | `background: #be123c; color: #fff` | `background: var(--brand-stop); color: #fff` — ≈ EXACT; the `#fff` label is the ONE permitted literal outside `:root` (bg-ink on the darkened brand is only 3.2:1 — fails AA; white is the AA label, rationale in a comment) |
|
||||
| `.send-btn.is-stop:hover` | `#9f1239` | `color-mix(in srgb, var(--brand) 55%, var(--bg))` |
|
||||
| `.tune-saved`, `.tuning-saved` border | `rgb(110 231 168 / 0.35)` | `color-mix(in srgb, var(--ok-ink) 35%, transparent)` — EXACT |
|
||||
| `.sync-modal-close:hover` / `#git-sources-retry:hover` / `.history-confirm-yes:hover:not(:disabled)` | `rgb(239 68 68 / 0.15)` / `0.12` / `0.18` | `color-mix(in srgb, var(--err-line) 15% / 12% / 18%, transparent)` — EXACT |
|
||||
| `.sync-modal-backdrop`, `.doc-modal-backdrop` | `rgba(10, 14, 23, 0.82)` | `color-mix(in srgb, var(--bg) 82%, transparent)` — micro-delta ((10,14,23)→(15,10,10) at 82% alpha, invisible in a dark scrim) |
|
||||
| `.remove-confirm-backdrop`, `.ignore-editor-backdrop` | `rgba(15, 10, 10, 0.82)` | `color-mix(in srgb, var(--bg) 82%, transparent)` — EXACT |
|
||||
| `.spinner` track border | `rgb(10 14 23 / 0.30)` | `color-mix(in srgb, var(--bg) 30%, transparent)` — micro-delta |
|
||||
| `.msg.user .bubble code` background | `rgb(10 14 23 / 0.16)` | `color-mix(in srgb, var(--bg) 16%, transparent)` — micro-delta |
|
||||
| black shadows (`.doc-modal-panel` box-shadow `rgb(0 0 0 / 0.55)`, the `--shadow`/`--shadow-lg` vars) | black | **UNCHANGED** (depth, not theme) |
|
||||
|
||||
Update the inline contrast/rationale comments that cite the old hexes (the spinner `#a5b4fc` note, the is-stop `#be123c` note, the grid comment) to the new mechanism. **Gate:** `rg -n -E "#[0-9a-fA-F]{3,6}\b|rgba?\(" frontend/assets/styles.css` returns matches ONLY inside the first `:root` block plus the one documented `#fff` Stop-label line.
|
||||
|
||||
**The brand-mark SVG (task 03):** all five HTML files (`index.html`, `login.html`, `document.html`, `shared.html`, `doc-edit.html`) carry the identical inline wordmark SVG with `fill="#1a0f0f"`, `stroke="#f43f5e"` (path + circle) and `stroke="#fca5a5"` (second path). Presentation attributes lose to CSS, but the minimal robust change is swapping each literal for an inline style that resolves from `:root`: `style="fill: var(--surface); stroke: var(--brand)"` / `style="fill: var(--brand)"` / `style="stroke: var(--brand-ink)"` (attribute order/style merged into the existing `style` where present; default render is identical — same values).
|
||||
|
||||
**The 9th picker (task 03):** in `frontend/index.html` `#theme-form`, after the Border cell: a `theme-color` cell with `<label for="theme-grid-line">Grid lines (--grid-line)</label>` + `<input id="theme-grid-line" name="grid_line" type="color" value="#4a2626">` (static built-in, the house convention). User-visible copy counts update where they exist ("11 inputs" → 12, "8" → "9" in the theme view sub-copy/comments, `theme.js` docstring, and the test comments — the form fieldset legend stays as-is).
|
||||
|
||||
**Non-goals (owner-locked phase-91 decisions hold):**
|
||||
- **B3 — the semantic families stay non-identity:** `--accent-*` / `--ok-*` / `--err-*` remain fixed built-in state colors, NOT tab pickers (they encode states — error stays red under any theme; the alpha derivatives become exact `color-mix` of the variables, so they at least resolve from the family). If the owner wants them as pickers, that is a later, explicitly-requested phase.
|
||||
- **B4 — the 3 strings stay runtime-applied** via `/api/config` + `brand.js` (next page load); only colors are pre-paint/live.
|
||||
- `favicon.svg` stays the static built-in brand (browser chrome cannot inherit page CSS) — documented, out of scope.
|
||||
- The server-side pre-paint injection, the CSP hash machinery, and the byte-identical no-op contract are untouched (a defaults row still serves no tag; the 9th var changes the tag bytes only when set).
|
||||
|
||||
## Tasks
|
||||
1. `01_grid_line_server.md` — the 9th identity variable end-to-end server-side: migration `0015`, model column, `theming.py` (`BUILTIN_COLORS` / `COLOR_FIELDS` / docstring table), `UiSettingsIn/Out`, affected unit + integration tests (drift guard, field order, exact tag bytes, API validation/normalization for `grid_line`).
|
||||
2. `02_css_variable_coverage.md` — `styles.css`: `--grid-line` + the derived state vars in `:root`, the full replacement table applied, comments updated, the `rg` gate green.
|
||||
3. `03_tab_ninth_picker_and_brand_mark.md` — the `#theme-grid-line` picker in the theme form + copy counts, and the brand-mark SVG var() theming across the five HTML files.
|
||||
4. `04_live_theme_apply.md` — `theme.js`: `BUILTINS` capture, `themeRootContent`, `applyServedTheme`, the four call sites, the 9th `FIELDS` entry, docstring.
|
||||
5. `05_e2e_save_and_coverage.md` — this phase's dedicated Playwright suite `tests/e2e/test_theme_save_and_coverage.py` (save applies live without reload; reset applies live; the controls drive the grid/selection/hovers/wordmark) + the phase-91 suite updated in place (9th palette key, count comments).
|
||||
|
||||
## Testing & Quality
|
||||
- Unit/integration (task 01): the drift guard (9 built-ins incl. `--grid-line` parsed from `:root`), `COLOR_FIELDS` order (9), `theme_style_tag` exact bytes with the 9th var, PUT/GET `grid_line` validation (422 naming the field, built-in→NULL normalization), admin gate unchanged; every existing `COLOR_FIELDS`/`BUILTIN_COLORS`-driven assertion (caching, security-headers, ui-settings unit + integration) stays green — the "8" count comments are updated in place.
|
||||
- Coverage: **>90%** on new/modified code (`uv run pytest --cov=app --cov-report=term-missing`) — the server delta is small table/schema plumbing; `theme.js` / `styles.css` / the HTML are frontend (outside `app/` coverage, covered by E2E).
|
||||
- This phase's Playwright E2E suite: `tests/e2e/test_theme_save_and_coverage.py`, run in isolation (`uv run pytest tests/e2e/test_theme_save_and_coverage.py -v --no-cov`); the phase-91 suite `tests/e2e/test_admin_theme_tab.py` (updated in place) must also pass in isolation.
|
||||
|
||||
## Completion Criteria
|
||||
- [ ] **Defect 1 gone:** in the Theme tab, Save (and Reset) updates the OPEN page's computed palette within the E2E timeout with NO navigation — `#bor-theme` carries the new 9-var `:root` (or is removed on reset), the inline preview overrides are cleared, and an SPA nav to another view keeps the new theme; the phase-91 pre-paint contract (fresh load paints the saved palette on first paint, for everyone) still holds.
|
||||
- [ ] **Defect 2 gone:** `rg -n -E "#[0-9a-fA-F]{3,6}\b|rgba?\(" frontend/assets/styles.css` → matches only in the first `:root` block + the documented `#fff` Stop label; `rg -n -E "fill=\"#|stroke=\"#" frontend/*.html` → nothing; changing `--brand` + `--grid-line` via the tab moves the hovers, `::selection`, the hairline, the chip washes, the grid texture, and the wordmark (E2E-proven on computed values).
|
||||
- [ ] An unset/defaults deployment still serves byte-identical HTML (no `#bor-theme`, plain A1 CSP) — the no-op contract holds with 9 vars.
|
||||
- [ ] Full test suite green, `app/` coverage >90%, `uv run ruff check . && uv run pyright` clean, both E2E suites green in isolation.
|
||||
- [ ] One atomic Conventional Commits commit for the phase (`--no-gpg-sign`), `.agents/` phase files moved to `complete/` by the pipeline.
|
||||
@@ -0,0 +1,41 @@
|
||||
# Task 01 — `--grid-line`: the 9th identity variable, end-to-end server-side
|
||||
|
||||
**Phase:** `92_theme_save_and_coverage` · **Source:** owner chat (defect 2 — "the background grid never changes color") · **Story:** n/a (owner-chat defect fix).
|
||||
|
||||
## Objective
|
||||
The background grid becomes a tab-controlled identity variable: `grid_line` flows through the migration, the model, `app/core/theming.py`, the schemas, and the admin API exactly like the existing 8 — so every `COLOR_FIELDS`-driven surface (resolver, `theme_style_tag`, `inject_theme`, the CSP hash, the middleware) picks it up with zero logic changes.
|
||||
|
||||
## Work
|
||||
1. `alembic/versions/0015_grid_line.py` — new migration (revises `0014`, the house docstring style): `op.add_column("ui_settings", sa.Column("grid_line", sa.String(7), nullable=True))`; downgrade `op.drop_column("ui_settings", "grid_line")` (fully reversible — the column is the only 0015 artefact).
|
||||
2. `app/models.py` — `UiSettings`: add `grid_line: Mapped[str | None] = mapped_column(String(7), nullable=True)` (NULL = built-in, B1 — alongside the other 8 color columns, after `line`); docstring "eight identity" → "nine identity" (and the value list).
|
||||
3. `app/core/theming.py`:
|
||||
- `BUILTIN_COLORS` — add `"grid_line": "#4a2626"` (the grid's current hardcoded color, exact — task 02 re-anchors the CSS to it; the drift test re-parses `styles.css` `:root`, so task 02's `:root` declaration MUST match this value byte-for-byte).
|
||||
- `COLOR_FIELDS` — `("bg", "surface", "ink", "ink_soft", "line", "grid_line", "brand", "brand_soft", "brand_ink")` (structural colors first, brand last).
|
||||
- Module docstring: the identity table gains the `grid_line` row (`#4a2626`, "background grid texture — decorative, no contrast duty, like ``line``"); "8 identity variables" → "9" everywhere it appears; the five contrast pairs are UNCHANGED (the grid line has no pair).
|
||||
- `theme_style_tag` / `effective_settings` / `inject_theme` / `theme_csp_hash` — NO logic changes (all iterate `COLOR_FIELDS` / `BUILTIN_COLORS`); verify the docstring examples only if they enumerate 8 vars explicitly.
|
||||
4. `app/schemas.py` — `UiSettingsIn`: `grid_line: str | None = None` (after `line`); `UiSettingsOut`: `grid_line: str` (after `line`). The docstrings' "11 values" → "12".
|
||||
5. `app/api/ui_settings.py` — NO loop changes (`_validate_colors` / PUT iterate `COLOR_FIELDS`); module/route docstrings' "8 identity colors" / "11 values" → 9 / 12.
|
||||
6. `app/core/caching.py` — no changes (reads `COLOR_FIELDS`); if a comment says "8 colors", update it.
|
||||
7. Tests (update in place — the house pattern; keep every assertion's intent):
|
||||
- `tests/unit/test_theming.py` — the drift guard set → the 9 names (incl. `grid_line`); `test_color_fields_are_the_eight_keys_in_readme_order` → the 9-tuple (rename to `_nine_…` if it hardcodes "eight"); `test_theme_style_tag_one_changed_carries_all_eight_in_order` → the exact tag now carries `--grid-line:#4a2626;` between `--line:#2d1a1a;` and `--brand:…` (update the exact-string assertion + rename); the `multiple_changed` order assertion is `COLOR_FIELDS`-driven (auto).
|
||||
- `tests/unit/test_caching.py` — the "all 8 ``--*`` vars" comments → 9 (assertions are `COLOR_FIELDS`-driven, auto); add ONE assertion that a themed page's tag contains `--grid-line:` when only the grid color changed (the no-op tag contract still holds for the other 8-at-built-ins + grid-set case: the tag is NON-empty and carries all 9).
|
||||
- `tests/unit/test_ui_settings.py` — `COLOR_FIELDS`/`BUILTIN_COLORS`-driven assertions auto; extend the 422-naming case with a `grid_line` variant (e.g. `{"grid_line": "nope"}` → 422 "grid_line must be a #rrggbb hex color") and the built-in→NULL normalization case with `grid_line` (PUT `{"grid_line": "#4a2626"}` → row `grid_line` is NULL, response reports the built-in).
|
||||
- `tests/integration/test_ui_settings_api.py` — same two additions against the live API (follow the file's existing per-field case style); the admin-gate assertions unchanged.
|
||||
- `tests/integration/test_security_headers.py` — `BUILTIN_COLORS`-driven (auto); check for a hardcoded "8".
|
||||
- Repo-wide: `rg -n "eight identity|8 identity|all 8|11 inputs|11 values" app/ tests/` → update remaining count comments to the new numbers (behavior-neutral).
|
||||
8. Verify (DB up): `uv run alembic upgrade head` (and `uv run alembic downgrade -1 && uv run alembic upgrade head` — the column round-trips); after a row-less GET, the response JSON has 12 keys incl. `grid_line: "#4a2626"`.
|
||||
|
||||
- ASSUMPTION: `grid_line` is a stored, tab-controlled IDENTITY variable (owner-locked in `00_phase.md`) — the owner explicitly called out the grid as unthemed; the built-in `#4a2626` preserves today's grid byte-for-byte.
|
||||
- ASSUMPTION: `color-mix()` (task 02's derived state vars) needs NO server-side knowledge — derivations live in `styles.css` `:root` and resolve in the browser; the tag only ever carries the 9 identity hexes.
|
||||
|
||||
## Testing & Quality
|
||||
- Unit/integration: Work item 7 (drift guard with the 9th var, field order, exact tag bytes, `grid_line` 422 + normalization, admin gate unchanged).
|
||||
- Coverage: **>90%** on this task's new/modified code (`uv run pytest --cov=app --cov-report=term-missing`).
|
||||
|
||||
## Completion Criteria
|
||||
- [ ] `uv run alembic upgrade head` applies 0015 cleanly; downgrade/upgrade round-trip leaves the schema consistent.
|
||||
- [ ] `GET /api/ui-settings` (admin) returns 12 keys; row-less deployment reports `grid_line: "#4a2626"`; `PUT` with `grid_line` set/non-built-in stores it, built-in value stores NULL, bad hex → 422 naming `grid_line`.
|
||||
- [ ] `theme_style_tag` with one changed color emits all 9 vars in `COLOR_FIELDS` order; all-built-in → `""` (byte-identical contract intact).
|
||||
- [ ] `uv run pytest tests/unit/test_theming.py tests/unit/test_caching.py tests/unit/test_ui_settings.py tests/integration/test_ui_settings_api.py tests/integration/test_security_headers.py -v --no-cov` green.
|
||||
- [ ] Full test suite green; `uv run ruff check . && uv run pyright` clean.
|
||||
- [ ] No behavior change in completed work (phase-91 API shape is additive only — existing clients sending 8 colors still work; `grid_line` absent → NULL → built-in).
|
||||
@@ -0,0 +1,48 @@
|
||||
# Task 02 — `styles.css`: every color driven by the identity palette (zero hardcoded literals outside `:root`)
|
||||
|
||||
**Phase:** `92_theme_save_and_coverage` · **Source:** owner chat (defect 2 — "Certain buttons and text are still light pink on highlight… the background grid never changes color… the theme controls should allow manipulating the entire site's theme") · **Story:** n/a (owner-chat defect fix).
|
||||
|
||||
## Objective
|
||||
`frontend/assets/styles.css` has no hardcoded color left outside the first `:root` block (one documented exception): the grid texture, `::selection`, the header hairline, every button hover/stop/disabled state, chip washes, code-block colors, ok/err alpha derivatives, backdrops, and the spinner track all resolve from the 9 identity variables (directly or via `color-mix()` derivations), so any tab save repaints the whole site — including the background grid.
|
||||
|
||||
## Work
|
||||
1. `frontend/assets/styles.css` — `:root` (the FIRST block only):
|
||||
- Add `--grid-line: #4a2626;` immediately after `--line` (value MUST equal `app.core.theming.BUILTIN_COLORS["grid_line"]` — the task-01 drift guard parses this block; comment: "background grid texture — decorative, no contrast duty (like ``line``)").
|
||||
- After `--brand_ink`, add the derived state block (a comment explaining: computed — NOT stored, NOT tab-controlled, NOT in `BUILTIN_COLORS`; every state follows the theme via `color-mix()`; built-in defaults reproduce the pre-phase-92 look or are the deliberate legacy-indigo fixes):
|
||||
```css
|
||||
--brand-hover: color-mix(in srgb, var(--brand) 86%, white);
|
||||
--brand-busy: color-mix(in srgb, var(--brand) 40%, white);
|
||||
--brand-stop: color-mix(in srgb, var(--brand) 75%, var(--bg));
|
||||
```
|
||||
- The `--shadow` / `--shadow-lg` black vars and `.doc-modal-panel`'s `rgb(0 0 0 / 0.55)` box-shadow are UNCHANGED (depth, not theme).
|
||||
2. Apply the replacement table from `00_phase.md` "Design → Defect 2" — anchor by selector (line numbers drift). Every `#hex` / `rgb()` / `rgba()` literal outside `:root` must be gone or be the one documented exception. Concretely (the table is authoritative — this is the site list):
|
||||
- `body::before` grid gradient (×2): `color-mix(in srgb, var(--grid-line) 60%, transparent)`; refresh the grid comment (phase 78/25 history kept, the color is now `--grid-line` at 60%).
|
||||
- `::selection` background: `color-mix(in srgb, var(--brand) 45%, transparent)`.
|
||||
- `.app-header::after, .doc-header::after` gradient (3 stops): `color-mix(in srgb, var(--brand) 55%, transparent)`, `color-mix(in srgb, var(--brand) 30%, transparent) 45%`, `color-mix(in srgb, var(--brand) 5%, transparent) 90%`; the comment's "brand→cyan" history is reworded to the brand fade (the orange/amber hue retires — deliberate).
|
||||
- `.bubble pre` + `.doc-md pre`: `background: var(--surface); color: var(--ink)`.
|
||||
- The 6 hardcoded `#1a0f0f` backgrounds (`.bubble pre`, `.doc-md pre`, `.tune-form textarea`, the two card blocks at ~L881/~L1060, the login card at ~L1462): `background: var(--surface)`.
|
||||
- The 8 `#f55a72` hovers + the 9 `#7d88f5` hovers: `background: var(--brand-hover)` (the `color: var(--bg)` legs stay).
|
||||
- `.send-btn:disabled`: `background: var(--brand-busy); cursor: not-allowed;` — and the spinner comment ("dark arc (--bg) on the #a5b4fc busy button = 9.7:1") is re-anchored: `--bg` on `--brand-busy` keeps ≥ 9:1 (recompute + state the new ratio).
|
||||
- `.send-btn.is-stop`: `background: var(--brand-stop); color: #fff;` — the comment keeps the WCAG rationale, re-anchored: `#fff` on `--brand-stop` (built-in default ≈ rgb(188,42,62)) = 5.9:1, and `#fff` is the ONE literal allowed outside `:root` (bg-ink would be 3.2:1 — fails AA). `.send-btn.is-stop:hover`: `background: color-mix(in srgb, var(--brand) 55%, var(--bg));` (#fff on it 8.6:1).
|
||||
- `.source-chip:hover`, `.suggestion-chip:hover`, `.doc-back:hover`: `background: var(--brand-soft);` (`.source-chip` keeps `text-decoration: underline`).
|
||||
- `.tune-saved` + `.tuning-saved` borders: `color-mix(in srgb, var(--ok-ink) 35%, transparent)`.
|
||||
- `.sync-modal-close:hover` / `#git-sources-retry:hover` / `.history-confirm-yes:hover:not(:disabled)`: `color-mix(in srgb, var(--err-line) 15% / 12% / 18%, transparent)`.
|
||||
- `.sync-modal-backdrop`, `.doc-modal-backdrop` (was `rgba(10, 14, 23, 0.82)`) + `.remove-confirm-backdrop`, `.ignore-editor-backdrop` (was `rgba(15, 10, 10, 0.82)`): `background: color-mix(in srgb, var(--bg) 82%, transparent);`.
|
||||
- `.spinner` track: `border: 2.5px solid color-mix(in srgb, var(--bg) 30%, transparent);`
|
||||
- `.msg.user .bubble code`: `background: color-mix(in srgb, var(--bg) 16%, transparent);`
|
||||
3. Sweep + gate: run `rg -n -E "#[0-9a-fA-F]{3,6}\b|rgba?\(" frontend/assets/styles.css` — every remaining match must be (a) inside the first `:root` block or (b) the single documented `#fff` Stop-label declaration; fix any stragglers (the sweep may surface a literal the table above did not list — same treatment: resolve it from the nearest identity variable, exact or the minimal documented delta).
|
||||
4. Visual sanity (dev server, optional but cheap): default theme renders as today (grid, hovers, selection), and with a saved palette (e.g. brand `#4f46e5`, grid `#2b3550`) the grid texture, button hovers, `::selection`, and the hairline all follow.
|
||||
|
||||
- ASSUMPTION (owner-locked in `00_phase.md`): the derived state vars are computed, not controls — the 9 pickers drive every visible color; the semantic families (`--accent-*`/`--ok-*`/`--err-*`) stay fixed built-ins (B3), only their hardcoded ALPHA derivatives move to `color-mix` of the family vars (exact).
|
||||
- ASSUMPTION: `color-mix(in srgb, …)` is the derivation mechanism (pure native CSS, Chromium baseline ≥111 — the Playwright target); no new dependency.
|
||||
|
||||
## Testing & Quality
|
||||
- Unit: no new Python — the task-01 drift guard already pins `--grid-line: #4a2626` in `:root`; the CSS itself is gated by the `rg` command (Work item 3) and by this phase's E2E (task 05) on computed values.
|
||||
- Coverage: **>90%** on new/modified code (unchanged for this task — CSS is outside `app/` coverage; the suite must stay green).
|
||||
|
||||
## Completion Criteria
|
||||
- [ ] `rg -n -E "#[0-9a-fA-F]{3,6}\b|rgba?\(" frontend/assets/styles.css` → matches ONLY in the first `:root` block + the one commented `#fff` Stop-label line.
|
||||
- [ ] `:root` declares `--grid-line: #4a2626` (the drift guard passes: `uv run pytest tests/unit/test_theming.py -v --no-cov` green) and the three derived `--brand-*` state vars.
|
||||
- [ ] `body::before` grid, `::selection`, the hairline, all button hover/stop/disabled states, chip washes, code blocks, ok/err derivatives, backdrops, and the spinner resolve from identity variables (the E2E in task 05 proves the grid + selection + two hovers on computed values).
|
||||
- [ ] Full test suite green; `uv run ruff check . && uv run pyright` clean (CSS-only change — no Python touched).
|
||||
- [ ] Default-theme rendering is visually unchanged apart from the documented deltas (indigo hovers → brand hover; hairline hue; code text `#e6d0d0` → `var(--ink)`; backdrop (10,14,23) → `--bg` at 82%).
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
# Task 03 — The tab's 9th picker + the brand-mark wordmark themes too
|
||||
|
||||
**Phase:** `92_theme_save_and_coverage` · **Source:** owner chat (defect 2 — the grid needs a control; "not everything is controllable") · **Story:** n/a (owner-chat defect fix).
|
||||
|
||||
## Objective
|
||||
The admin Theme tab exposes the 9th identity variable (`#theme-grid-line`, static built-in value, E2E-stable markup), and the static brand-mark SVG in all five HTML files resolves its fills/strokes from the identity variables instead of hardcoded hexes — the wordmark follows the theme.
|
||||
|
||||
## Work
|
||||
1. `frontend/index.html` — in `#theme-form`'s palette fieldset, insert ONE new cell immediately AFTER the Border cell (`<input id="theme-line" …>`), matching the existing cell shape exactly:
|
||||
```html
|
||||
<div class="theme-color">
|
||||
<label for="theme-grid-line">Grid lines (--grid-line)</label>
|
||||
<input id="theme-grid-line" name="grid_line" type="color" value="#4a2626">
|
||||
</div>
|
||||
```
|
||||
(static `value` = the built-in — the house contract the phase-91 E2E asserts against `styles.css` `:root`; `theme.js` re-populates the EFFECTIVE value on mount). The fieldset legend ("Palette — five pairs checked…") is unchanged (the grid has no contrast pair).
|
||||
2. `frontend/index.html` — theme-view copy: update the static-form comment block and any user-visible count in the view that says 8 colors / 11 inputs (the sub-copy "Changes preview live…" mentions the palette generically — update only where a count is stated).
|
||||
3. The brand-mark SVG — in ALL FIVE files `frontend/index.html`, `frontend/login.html`, `frontend/document.html`, `frontend/shared.html`, `frontend/doc-edit.html` (the identical inline wordmark in each `<header>`): replace the hardcoded presentation attributes with `:root`-resolving inline styles (SVG presentation attributes lose to CSS, but the inline `style` keeps the change local, identical-by-default, and needs no new selector):
|
||||
- first `<path … fill="#1a0f0f" stroke="#f43f5e" …>` → `style="fill: var(--surface); stroke: var(--brand)"` (drop the two attributes, keep `stroke-width`/`stroke-linejoin`);
|
||||
- `<circle … fill="#f43f5e">` → `style="fill: var(--brand)"`;
|
||||
- second `<path … stroke="#fca5a5" …>` → `style="stroke: var(--brand-ink)"` (keep `stroke-width`/`stroke-linecap`; the path has no fill — `fill="none"` is absent and the default is black, so ADD `fill: none` to the style: `style="fill: none; stroke: var(--brand-ink)"`).
|
||||
- Keep every other attribute byte-identical (the wordmark's geometry/timing are untouched).
|
||||
4. `tests/unit/test_frontend_router.py` — `test_theme_view_scaffold_in_the_shell`: the docstring's "8 labeled type=color palette inputs (the 8 identity variables…)" → 9, and the id loop gains `"theme-grid-line"` (the assertion list is explicit — add the entry after `theme-line`).
|
||||
5. Gate: `rg -n -E "fill=\"#|stroke=\"#" frontend/*.html` → nothing; `rg -n "theme-grid-line" frontend/` → the label + input in `index.html` (theme.js's `FIELDS` entry lands in task 04).
|
||||
6. Verify (dev server, admin): the Theme tab shows 9 pickers; the new one ships `#4a2626` and previews the grid live on `input` (the preview binding is task 04's `FIELDS` loop — until then it is inert markup, which is fine and matches the phase-91 task-04/task-05 split).
|
||||
|
||||
- ASSUMPTION: the picker order (after Border, before Brand accent) mirrors `COLOR_FIELDS` (structural colors first, brand last) — the E2E helper `COLOR_INPUT_IDS` is derived from `COLOR_FIELDS`, so it needs no change.
|
||||
|
||||
## Testing & Quality
|
||||
- Unit: Work item 4 (the shell-skeleton pin now covers the 9th picker + label).
|
||||
- Coverage: **>90%** on new/modified code (no Python app code — HTML/test only; the suite must stay green).
|
||||
|
||||
## Completion Criteria
|
||||
- [ ] `rg -n "theme-grid-line" frontend/index.html` → the labeled `type="color"` input with `value="#4a2626"`, placed after the Border cell; the label uses `for="theme-grid-line"`.
|
||||
- [ ] `rg -n -E "fill=\"#|stroke=\"#" frontend/*.html` → zero matches; each of the five files' wordmark uses `var(--surface)` / `var(--brand)` / `var(--brand-ink)` via inline style (default render identical — same values resolve from the built-in `:root`).
|
||||
- [ ] `uv run pytest tests/unit/test_frontend_router.py -v --no-cov` green.
|
||||
- [ ] Full test suite green; `uv run ruff check . && uv run pyright` clean.
|
||||
- [ ] No behavior change in completed work (the wordmark is byte-identical in the default theme; the form gains exactly one cell).
|
||||
@@ -0,0 +1,49 @@
|
||||
# Task 04 — `theme.js`: Save/Reset apply the theme to the open document (no reload, no revert)
|
||||
|
||||
**Phase:** `92_theme_save_and_coverage` · **Source:** owner chat (defect 1 — "Clicking 'save theme' reverts the theme back to the previous theme, a refresh is required to see the new theme") · **Story:** n/a (owner-chat defect fix).
|
||||
|
||||
## Objective
|
||||
After any settled read of the effective theme, the OPEN document's `#bor-theme` tag is synced to those values — mirroring what the server would inject on the next load — so Save/Reset/re-show/mount all paint the CURRENT theme immediately, with the live-preview overrides cleared onto the (now-current) tag instead of a stale one.
|
||||
|
||||
## Work
|
||||
1. `frontend/assets/theme.js` — implement per `00_phase.md` "Design → Defect 1":
|
||||
- **`FIELDS`** — insert `{ field: "grid_line", id: "theme-grid-line", kind: "color" }` between the `line` and `brand` entries (the task-03 markup). The `input`-event preview, `collectBody`, `clearPreview`, and the new `themeRootContent` all iterate `FIELDS` — the 9th picker is wired automatically. The contrast `PAIRS` are UNCHANGED (the grid line is decorative — no contrast duty).
|
||||
- **`BUILTINS` capture** — at the top of the admin branch, BEFORE the first `loadSettings()` (which repopulates the inputs): `const BUILTINS = {};` then for each color `f` in `FIELDS`, `BUILTINS[f.field] = inputs[f.field].value;` — the static input values ARE the built-ins (the house contract, asserted in-test against `styles.css` `:root`), so no third hardcoded palette copy.
|
||||
- **`themeRootContent(colors) -> string | null`** (pure): returns `null` when every color field in `FIELDS` equals its `BUILTINS` value (the no-op case); else `":root{" + FIELDS(color, in order).map(f => `--${f.field.replace(/_/g, "-")}:${colors[f.field]};`).join("") + "}"`. The content is byte-identical to the inner content of `app.core.theming.theme_style_tag`'s tag (same 9 fields, same order, lowercased hex from the resolver) — a saved theme never jumps between the client view and a fresh load.
|
||||
- **`applyServedTheme(effective)`** —
|
||||
```js
|
||||
const content = themeRootContent(effective);
|
||||
const el = document.getElementById("bor-theme");
|
||||
if (content === null) { if (el) el.remove(); return; }
|
||||
if (el === null) {
|
||||
const style = document.createElement("style");
|
||||
style.id = "bor-theme";
|
||||
style.textContent = content;
|
||||
document.head.appendChild(style);
|
||||
return;
|
||||
}
|
||||
if (el.textContent !== content) el.textContent = content;
|
||||
```
|
||||
`createElement` + `textContent` ONLY — never `innerHTML`/`insertAdjacentHTML` (the house no-HTML rule, and CSP3: parser-inserted `<style>` IS style-src-checked and would be blocked by the phase-82/91 policy; CSSOM `textContent` on a style element is not a style-source checkpoint — same class of mutation as the preview's existing `setProperty`).
|
||||
- **`loadSettings()`** — return the parsed settings object on success; `null` on the three failure paths (populate + `updateContrast` calls unchanged).
|
||||
- **Call sites** (each after a SETTLED read):
|
||||
1. `saveTheme()` — replace the trailing `await loadSettings(); clearPreview();` with `const s = await loadSettings(); if (s) { applyServedTheme(s); clearPreview(); }` (apply BEFORE clear so the page never shows the stale tag for a frame).
|
||||
2. `resetTheme()` — same shape (effective = built-ins → `null` → the tag is REMOVED from the live document; the built-in palette paints).
|
||||
3. The `bor:view-refresh` listener — `loadSettings().then((s) => { if (s) { applyServedTheme(s); clearPreview(); } })` (the re-show had the SAME latent revert).
|
||||
4. The initial `await loadSettings()` at the end of `mount` — if settled: `applyServedTheme(settings)` (self-heal: a row changed elsewhere since page load is reflected the moment the admin opens the tab; a normal load is a no-op — the served tag already matches).
|
||||
2. `frontend/assets/theme.js` — module docstring: update the "11 inputs" / "8 color pickers" counts (12 / 9), and reword the "live preview" + "Save" + "Reset" + "re-show" bullets to document the phase-92 behavior — the root cause (the old code cleared the preview onto the tag baked in at PAGE LOAD, i.e. the previous theme — the owner had to reload) and the fix (the document's `#bor-theme` is synced to the settled effective values on save / reset / re-show / mount; CSP note: CSSOM mutation only).
|
||||
3. Sanity (dev server, admin): save a palette → the whole page repaints the new palette with NO reload and the `#bor-theme` tag in DevTools now carries the 9 new vars; Reset → tag gone, built-in palette; navigate to Chat and back → still current.
|
||||
|
||||
- ASSUMPTION (owner-locked in `00_phase.md`): the server-side pre-paint injection + CSP hash are UNTOUCHED — the fix lives entirely in the client; a fresh load still proves first-paint (phase-91 E2E test 2, unchanged).
|
||||
|
||||
## Testing & Quality
|
||||
- Unit: none (frontend JS — outside `app/` coverage); the behavior is E2E-pinned by task 05's dedicated suite (the save-applies-live + reset-applies-live tests fail on the pre-task code: they wait on the live computed palette with no navigation, which the old `clearPreview()`-only path never satisfies).
|
||||
- Coverage: **>90%** on new/modified code (unchanged for this task; the suite must stay green).
|
||||
|
||||
## Completion Criteria
|
||||
- [ ] After Save: with NO navigation, all 9 computed `:root` custom properties on `<html>` equal the saved hexes, `#bor-theme`'s `textContent` equals the 9-var `:root` string, and `document.documentElement`'s inline `style` attribute is empty (preview overrides cleared).
|
||||
- [ ] After Reset (from a saved state): with NO navigation, `#bor-theme` is absent from the document and all 9 computed properties equal the built-ins.
|
||||
- [ ] SPA navigation (Theme → Chat, no reload) keeps the saved theme computed; re-showing the Theme view re-syncs (no stale tag, no stale preview).
|
||||
- [ ] `themeRootContent` is byte-identical to the server tag content for any palette (a fresh load of the same state shows no visual delta — the phase-91 E2E `_assert_raw_tag` still passes on fresh loads).
|
||||
- [ ] Full test suite green; `uv run ruff check . && uv run pyright` clean (JS-only change — no Python touched).
|
||||
- [ ] No behavior change in completed work (the §7.4 Save/Reset lifecycle — busy states, labels, result lines, the contrast re-check — is unchanged; only the post-settle page state differs, which IS the fix).
|
||||
@@ -0,0 +1,46 @@
|
||||
# Task 05 — Dedicated E2E: save/reset apply live (no reload) + the controls drive the whole site
|
||||
|
||||
**Phase:** `92_theme_save_and_coverage` · **Source:** owner chat (both defects — the permanent proof) · **Story:** n/a (owner-chat defect fix — this phase's dedicated suite).
|
||||
|
||||
## Objective
|
||||
`tests/e2e/test_theme_save_and_coverage.py` — this phase's Playwright suite (run in isolation per AGENTS.md): (1) Save and Reset repaint the OPEN page without any navigation — the pre-fix code fails these (it cleared the preview onto the stale served tag, so the live computed palette stayed at the previous theme); (2) the tab's variables drive every themed surface — grid texture, `::selection`, button hovers, the wordmark — asserted on browser-computed values. Plus the phase-91 suite `tests/e2e/test_admin_theme_tab.py` updated in place for the 9th field.
|
||||
|
||||
## Work
|
||||
1. `tests/e2e/test_theme_save_and_coverage.py` (new) — module scaffold mirrors `tests/e2e/test_admin_theme_tab.py` EXACTLY (the house per-module pattern): module docstring (phase 92, both defects, isolation note), per-module `app_server` fixture (branding vars pinned to CODE defaults via `Settings.model_fields[...].default`, `BOR_GIT_SOURCES` empty, mock LLM base URL — copy the fixture body), `app_url`, the autouse `_clean` fixture (`TRUNCATE ui_settings` + delete `e2e-` tokens, before AND after), the `_cookies` / `_hold_theme_puts` (PUT-hold, the §7.4 determinism pattern) / `_release_theme_puts` helpers, and the constants:
|
||||
- `PALETTE` — the 9-color indigo set: phase-91's 8 values PLUS `"grid_line": "#2b3550"` (distinct from its built-in `#4a2626` and from `line` `#232a4a`), `SAVED_STRINGS` (3 strings), `COLOR_INPUT_IDS` derived from `app.core.theming.COLOR_FIELDS` (`#theme-{field}`), `_builtin_colors()` parsed from `styles.css` `:root`, `_expected_tag(colors)` (all 9 vars, `COLOR_FIELDS` order, no whitespace — byte-identical to `theme_style_tag`'s content+wrapper), `_wait_theme_computed(page, colors)` (wait until all 9 computed `:root` custom properties equal the hexes — 15s), and `_expected_tag_content(colors)` (the `:root{…}` inner string, for the DOM-tag assertions).
|
||||
- A small Python `color-mix` helper for the resolved-surface assertions (css-color: `color-mix(in srgb, A p%, B)` = per-channel `A*p/100 + B*(1-p/100)`, `transparent` = `(0,0,0,0)`, rounded): `_mix(a_hex, p, b)` → tuple; assertions parse the browser's `rgb(r, g, b[, a])` serialization and compare with ±1 per channel (browser rounding is not pinned by the spec — the tolerance absorbs it; the ±1 window still fails any legacy hardcoded value by orders of magnitude).
|
||||
2. **Test 1 — `test_save_applies_live_without_reload`** (defect 1, Save):
|
||||
- Admin login → `/theme.html`; `#theme-content` visible; fill the 12 inputs (`SAVED_STRINGS` + `PALETTE`, the phase-91 `_fill_theme_form` shape).
|
||||
- Hold the PUT, click `#theme-save`, expect `#theme-result` "Theme saved." (the §7.4 lifecycle — same assertions as phase 91), release.
|
||||
- **NO navigation.** `_wait_theme_computed(page, PALETTE)` — all 9 live; `document.getElementById("bor-theme").textContent` == `_expected_tag_content(PALETTE)`; `(document.documentElement.getAttribute("style") || "").trim()` == `""` (preview overrides cleared onto the synced tag, not a stale one).
|
||||
- Server agrees: `httpx.get(app_url + "/")` raw carries the 9-var tag (phase-91 `_assert_raw_tag` shape).
|
||||
- SPA navigation: click `a.nav-link[href="/"]` (Chat — same document, no reload) → `#view-theme` hidden, the chat view shown, computed `--brand` still the saved hex, and `.send-btn` computed `background-color` == the saved `brand` (exact `rgb` — `background: var(--brand)` resolves to the used color).
|
||||
3. **Test 2 — `test_reset_applies_live_without_reload`** (defect 1, Reset):
|
||||
- Seed the theme via API (`PUT /api/ui-settings`, the phase-91 `_seed_theme_via_api` shape — body = `PALETTE` + strings), THEN `page.goto("/theme.html")` (the load carries the tag); `_wait_theme_computed(page, PALETTE)` (served state sanity).
|
||||
- Hold the PUT, click `#theme-reset`, expect "Reset to the built-in theme." + the restored lifecycle, release.
|
||||
- **NO navigation.** `document.getElementById("bor-theme")` is `None` (the tag REMOVED from the live document); `_wait_theme_computed(page, builtin)` — all 9 built-ins; `httpx.get(app_url + "/")` has no `bor-theme`; the byte-identical contract end to end: with-row bytes == rowless bytes (TRUNCATE then compare, the phase-91 pattern).
|
||||
4. **Test 3 — `test_theme_controls_drive_the_whole_site`** (defect 2):
|
||||
- Seed `PALETTE` via API; `page.goto("/")` (fresh load — first paint is the themed paint).
|
||||
- **Grid** (the owner's named defect): `getComputedStyle(document.body, "::before").backgroundImage` contains the grid line at 60% — expected `_mix(grid_line, 0.60, (0,0,0,0))` → `rgba(r, g, b, 0.6)` present (±1/channel).
|
||||
- **Selection**: `getComputedStyle(document.documentElement, "::selection").backgroundColor` ≈ `rgba(brand*0.45, …, 0.45)` (±1/channel, alpha exact 0.45).
|
||||
- **Hovers** (the "light pink on highlight" defect): `page.hover(".new-chat-btn")` → computed `background-color` ≈ `_mix(brand, 0.86, white)` AND `!= "rgb(125, 136, 245)"` (the legacy indigo `#7d88f5` — explicit, documented); `page.hover(".send-btn")` → the same expected mix; `page.hover("#nav-sources")` (the RAG link — visible for admin, the house nav-link hover wash) → computed `background-color` == saved `brand_soft` exact (the wash now themes).
|
||||
- **Wordmark**: `getComputedStyle(document.querySelector(".brand-mark path")).fill` == saved `surface` as `rgb(r, g, b)` (the inline `style="fill: var(--surface)…"`, task 03).
|
||||
- **Pre-paint with the 9th var**: the raw served HTML carries the 9-var tag immediately before `</head>` (`_assert_raw_tag` shape) and the CSP `style-src 'self' 'sha256-'` is present (phase-91 contract, now 9-wide).
|
||||
5. `tests/e2e/test_admin_theme_tab.py` (phase 91 — updated IN PLACE, additive only):
|
||||
- `PALETTE` gains `"grid_line": "#2b3550"` (REQUIRED: `_seed_theme_via_api` asserts `r.json() == body` and the DB-row assertion iterates the now-9-long `COLOR_FIELDS` — an 8-key dict KeyErrors).
|
||||
- Count comments only: "11 inputs" → "12 inputs" (module docstring, `_fill_theme_form`, `_expect_form_values`, test 1), "all 11 values" → 12, "8 built-in hexes" / "all 8 vars" / "all 8 computed" → 9 (module docstring, `_builtin_colors`, `_expected_tag`, `_assert_raw_tag`, `_wait_theme_computed`). NO assertion logic changes beyond the `PALETTE` key — the suite's phase-91 contract (pre-paint, gate, byte-identical reset, contrast) stands.
|
||||
6. Run: `uv run pytest tests/e2e/test_theme_save_and_coverage.py -v --no-cov` (in isolation) green, then `uv run pytest tests/e2e/test_admin_theme_tab.py -v --no-cov` (in isolation) green.
|
||||
|
||||
- ASSUMPTION: the suite reuses the shared `mock_llm` / `db_ready` fixtures and `e2e.auth_helpers.login` (the phase-91 file is the copy source — keep the two modules' scaffolds in lockstep so a future conftest refactor touches both at once).
|
||||
- ASSUMPTION: computed-value assertions read the browser's serialization (`rgb(…)` / `rgba(…)` / the `background-image` string) with the ±1/channel tolerance helper — never a raw `color-mix(…)` token (custom properties return tokens, USED properties resolve).
|
||||
|
||||
## Testing & Quality
|
||||
- E2E: Work items 2–4 (this phase's dedicated suite — the permanent proof of both defects) + Work item 5 (phase-91 suite in lockstep).
|
||||
- Coverage: **>90%** on new/modified code — unchanged for this task (test-only; `app/` coverage untouched, the full gate runs in the phase's final pass).
|
||||
|
||||
## Completion Criteria
|
||||
- [ ] `uv run pytest tests/e2e/test_theme_save_and_coverage.py -v --no-cov` green in isolation (3 tests: save-live, reset-live, whole-site).
|
||||
- [ ] Test 1 FAILS on the pre-task-04 code (the live computed palette waits out the 15s timeout on the stale tag) — i.e. the test genuinely pins the fix, verified by a one-line `git stash` of `theme.js` (or by code review of the wait target) before finalizing.
|
||||
- [ ] `uv run pytest tests/e2e/test_admin_theme_tab.py -v --no-cov` green in isolation (phase-91 contract intact with the 9th field).
|
||||
- [ ] Full test suite green; `uv run ruff check . && uv run pyright` clean.
|
||||
- [ ] No behavior change in completed work (both E2E suites' existing assertions are untouched except the phase-91 `PALETTE` key + count comments).
|
||||
@@ -0,0 +1,10 @@
|
||||
**Phase 92 final verification pass — all green.** This pass re-verified the completed tasks (all 5 task files already in `complete/`) against every completion criterion; no defects found, nothing to fix.
|
||||
|
||||
- Verified: 9th identity var `grid_line` end-to-end (migration `0015` at head, model/`theming.py`/schemas/API, 422 + built-in→NULL tests present); `styles.css` zero hardcoded literals outside `:root` + derived `--brand-*` vars; 9th picker in theme form; wordmark themed; `theme.js` save/reset/re-show/mount live-sync; dedicated E2E suite + phase-91 suite updated.
|
||||
- `uv run pytest --cov=app --cov-report=term-missing` → **1845 passed, exit 0, TOTAL 99%** (>90%)
|
||||
- `uv run ruff check .` → clean; `uv run pyright` → 0 errors, 0 warnings
|
||||
- `uv run pytest tests/e2e/test_theme_save_and_coverage.py -v --no-cov` → **3 passed** (save-live, reset-live, whole-site)
|
||||
- `uv run pytest tests/e2e/test_admin_theme_tab.py -v --no-cov` → **5 passed**
|
||||
- Criteria: (1) Save/Reset repaint open page, no nav, SPA-nav survives, pre-paint intact ✅; (2) both `rg` gates green (only `:root` + documented `#fff` Stop label; zero SVG hex attrs), grid/selection/hovers/wash/wordmark E2E-proven ✅; (3) no-op contract live-checked: row-less `/` = no tag + exact A1 CSP, grid-only row = 9-var tag in `COLOR_FIELDS` order + sha256 CSP, with-row ≡ row-less bytes ✅; (4) full suite/coverage/lint/both E2E ✅; (5) commit left to the harness per instructions.
|
||||
- Deviations (previously made, probe-verified, kept): live repaint uses CSSOM `<html>` overrides because Chromium blocks `<style>` textContent mutations under the locked sha256-only CSP (tag text still mirrors the next load; `<html>` style exact-saved after Save, empty after Reset); wordmark themed via 3 `.brand-mark` CSS rules instead of inline styles (task 03's inline attrs were CSP-blocked — fixed during task 04).
|
||||
- Next pending phase: none — `todo/` contains only `92_theme_save_and_coverage`.
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
........................................................................ [ 3%]
|
||||
........................................................................ [ 7%]
|
||||
........................................................................ [ 11%]
|
||||
........................................................................ [ 15%]
|
||||
........................................................................ [ 19%]
|
||||
........................................................................ [ 23%]
|
||||
........................................................................ [ 27%]
|
||||
........................................................................ [ 31%]
|
||||
........................................................................ [ 35%]
|
||||
........................................................................ [ 39%]
|
||||
........................................................................ [ 42%]
|
||||
........................................................................ [ 46%]
|
||||
........................................................................ [ 50%]
|
||||
........................................................................ [ 54%]
|
||||
........................................................................ [ 58%]
|
||||
........................................................................ [ 62%]
|
||||
........................................................................ [ 66%]
|
||||
........................................................................ [ 70%]
|
||||
........................................................................ [ 74%]
|
||||
........................................................................ [ 78%]
|
||||
........................................................................ [ 81%]
|
||||
........................................................................ [ 85%]
|
||||
........................................................................ [ 89%]
|
||||
........................................................................ [ 93%]
|
||||
........................................................................ [ 97%]
|
||||
............................................ [100%]
|
||||
=============================== warnings summary ===============================
|
||||
.venv/lib/python3.13/site-packages/fastapi/testclient.py:1
|
||||
/var/home/ducoterra/Projects/Personal/brain_of_reese/.venv/lib/python3.13/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
|
||||
from starlette.testclient import TestClient as TestClient # noqa
|
||||
|
||||
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
|
||||
================================ tests coverage ================================
|
||||
_______________ coverage: platform linux, python 3.13.13-final-0 _______________
|
||||
|
||||
Name Stmts Miss Cover
|
||||
--------------------------------------------------
|
||||
app/__init__.py 1 0 100%
|
||||
app/api/__init__.py 0 0 100%
|
||||
app/api/auth.py 52 0 100%
|
||||
app/api/chat.py 178 0 100%
|
||||
app/api/chats.py 110 0 100%
|
||||
app/api/config.py 13 0 100%
|
||||
app/api/doc_drafts.py 94 0 100%
|
||||
app/api/docs.py 50 0 100%
|
||||
app/api/git_sources.py 229 0 100%
|
||||
app/api/health.py 10 0 100%
|
||||
app/api/steering.py 42 0 100%
|
||||
app/api/suggestions.py 29 0 100%
|
||||
app/api/sync.py 101 0 100%
|
||||
app/api/tokens.py 28 0 100%
|
||||
app/api/ui_settings.py 55 0 100%
|
||||
app/config.py 132 0 100%
|
||||
app/core/__init__.py 0 0 100%
|
||||
app/core/auth.py 45 0 100%
|
||||
app/core/caching.py 124 0 100%
|
||||
app/core/debugging.py 29 2 93%
|
||||
app/core/docs_push.py 39 0 100%
|
||||
app/core/errors.py 5 0 100%
|
||||
app/core/logging.py 13 0 100%
|
||||
app/core/rate_limit.py 44 0 100%
|
||||
app/core/security_headers.py 20 0 100%
|
||||
app/core/theming.py 38 0 100%
|
||||
app/core/tokens.py 33 0 100%
|
||||
app/db.py 21 0 100%
|
||||
app/main.py 66 0 100%
|
||||
app/models.py 110 0 100%
|
||||
app/rag/__init__.py 0 0 100%
|
||||
app/rag/agent.py 222 0 100%
|
||||
app/rag/archive_upload.py 128 0 100%
|
||||
app/rag/chunker.py 206 4 98%
|
||||
app/rag/git_sources.py 14 0 100%
|
||||
app/rag/importer.py 190 3 98%
|
||||
app/rag/llm.py 217 0 100%
|
||||
app/rag/overview.py 71 0 100%
|
||||
app/rag/prompts.py 88 0 100%
|
||||
app/rag/retriever.py 150 3 98%
|
||||
app/rag/scaffolding.py 55 0 100%
|
||||
app/rag/source_removal.py 41 0 100%
|
||||
app/rag/sources_meta.py 16 0 100%
|
||||
app/rag/suggestions.py 27 0 100%
|
||||
app/rag/summarizer.py 24 0 100%
|
||||
app/schemas.py 253 0 100%
|
||||
--------------------------------------------------
|
||||
TOTAL 3413 12 99%
|
||||
coverage gate: app/ 99% (>90%) OK
|
||||
All checks passed!
|
||||
0 errors, 0 warnings, 0 informations
|
||||
WARNING: there is a new pyright version available (v1.1.411 -> v1.1.413).
|
||||
Please install the new version or set PYRIGHT_PYTHON_FORCE_VERSION to `latest`
|
||||
|
||||
validation OK
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
All remaining rg matches are legitimate (0014-migration schema descriptions, unrelated llm-batch comment, and the template-pinning comments owned by task 03). The full suite, coverage, and linters all ran green on the final code.
|
||||
|
||||
**Task 01 complete — `--grid-line` 9th identity variable, end-to-end server-side**
|
||||
|
||||
- `alembic/versions/0015_grid_line.py`: additive `grid_line VARCHAR(7) NULL` on `ui_settings`; upgrade → downgrade → upgrade round-trips cleanly (DB now at 0015 head).
|
||||
- `app/models.py` `UiSettings.grid_line` (after `line`), `app/core/theming.py` `BUILTIN_COLORS["grid_line"] = "#4a2626"` (→ 9-tuple `COLOR_FIELDS`, structural-first order), docstring tables updated; `app/schemas.py` `UiSettingsIn/Out` fields; `app/api/ui_settings.py` docstrings only — all logic was `COLOR_FIELDS`-driven, zero loop changes.
|
||||
- `frontend/assets/styles.css`: `--grid-line: #4a2626;` added to `:root` (required by the drift guard to pass this task; `body::before` re-anchoring stays with task 02).
|
||||
- Tests: drift guard 9-names, 9-tuple field order, exact 9-var tag bytes, grid-only middleware tag test (new, `test_caching.py`), `grid_line` 422 + built-in→NULL normalization (unit + live API), `test_models.py` key set; stale-row resilience added to `test_theming.py` DB tests (defect found: they assumed row-missing with no cleanup).
|
||||
- Verified live: row-less GET → 12 keys incl. `grid_line: "#4a2626"`; grid-only PUT → exact 9-var tag with CSP hash; built-in (uppercased) PUT → column NULL, tag removed, plain A1.
|
||||
|
||||
Results: `uv run pytest --cov=app --cov-report=term-missing` → 1844 passed, **99%** app coverage; `uv run ruff check . && uv run pyright` → clean.
|
||||
|
||||
Notable: phase-91 E2E (`test_admin_theme_tab.py`) will fail until its owned 9th-palette-key update in task 05 (8-key `PALETTE` vs 9-field tag); E2E is outside the default suite and task 03 owns the `test_frontend_router.py` picker-count comments.
|
||||
|
||||
Next pending task: `.agents/phases/todo/92_theme_save_and_coverage/02_css_variable_coverage.md`
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
........................................................................ [ 3%]
|
||||
........................................................................ [ 7%]
|
||||
........................................................................ [ 11%]
|
||||
........................................................................ [ 15%]
|
||||
........................................................................ [ 19%]
|
||||
........................................................................ [ 23%]
|
||||
........................................................................ [ 27%]
|
||||
........................................................................ [ 31%]
|
||||
........................................................................ [ 35%]
|
||||
........................................................................ [ 39%]
|
||||
........................................................................ [ 42%]
|
||||
........................................................................ [ 46%]
|
||||
........................................................................ [ 50%]
|
||||
........................................................................ [ 54%]
|
||||
........................................................................ [ 58%]
|
||||
........................................................................ [ 62%]
|
||||
........................................................................ [ 66%]
|
||||
........................................................................ [ 70%]
|
||||
........................................................................ [ 74%]
|
||||
........................................................................ [ 78%]
|
||||
........................................................................ [ 81%]
|
||||
........................................................................ [ 85%]
|
||||
........................................................................ [ 89%]
|
||||
........................................................................ [ 93%]
|
||||
........................................................................ [ 97%]
|
||||
............................................ [100%]
|
||||
=============================== warnings summary ===============================
|
||||
.venv/lib/python3.13/site-packages/fastapi/testclient.py:1
|
||||
/var/home/ducoterra/Projects/Personal/brain_of_reese/.venv/lib/python3.13/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
|
||||
from starlette.testclient import TestClient as TestClient # noqa
|
||||
|
||||
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
|
||||
================================ tests coverage ================================
|
||||
_______________ coverage: platform linux, python 3.13.13-final-0 _______________
|
||||
|
||||
Name Stmts Miss Cover
|
||||
--------------------------------------------------
|
||||
app/__init__.py 1 0 100%
|
||||
app/api/__init__.py 0 0 100%
|
||||
app/api/auth.py 52 0 100%
|
||||
app/api/chat.py 178 0 100%
|
||||
app/api/chats.py 110 0 100%
|
||||
app/api/config.py 13 0 100%
|
||||
app/api/doc_drafts.py 94 0 100%
|
||||
app/api/docs.py 50 0 100%
|
||||
app/api/git_sources.py 229 0 100%
|
||||
app/api/health.py 10 0 100%
|
||||
app/api/steering.py 42 0 100%
|
||||
app/api/suggestions.py 29 0 100%
|
||||
app/api/sync.py 101 0 100%
|
||||
app/api/tokens.py 28 0 100%
|
||||
app/api/ui_settings.py 55 0 100%
|
||||
app/config.py 132 0 100%
|
||||
app/core/__init__.py 0 0 100%
|
||||
app/core/auth.py 45 0 100%
|
||||
app/core/caching.py 124 0 100%
|
||||
app/core/debugging.py 29 2 93%
|
||||
app/core/docs_push.py 39 0 100%
|
||||
app/core/errors.py 5 0 100%
|
||||
app/core/logging.py 13 0 100%
|
||||
app/core/rate_limit.py 44 0 100%
|
||||
app/core/security_headers.py 20 0 100%
|
||||
app/core/theming.py 38 0 100%
|
||||
app/core/tokens.py 33 0 100%
|
||||
app/db.py 21 0 100%
|
||||
app/main.py 66 0 100%
|
||||
app/models.py 110 0 100%
|
||||
app/rag/__init__.py 0 0 100%
|
||||
app/rag/agent.py 222 0 100%
|
||||
app/rag/archive_upload.py 128 0 100%
|
||||
app/rag/chunker.py 206 4 98%
|
||||
app/rag/git_sources.py 14 0 100%
|
||||
app/rag/importer.py 190 3 98%
|
||||
app/rag/llm.py 217 0 100%
|
||||
app/rag/overview.py 71 0 100%
|
||||
app/rag/prompts.py 88 0 100%
|
||||
app/rag/retriever.py 150 3 98%
|
||||
app/rag/scaffolding.py 55 0 100%
|
||||
app/rag/source_removal.py 41 0 100%
|
||||
app/rag/sources_meta.py 16 0 100%
|
||||
app/rag/suggestions.py 27 0 100%
|
||||
app/rag/summarizer.py 24 0 100%
|
||||
app/schemas.py 253 0 100%
|
||||
--------------------------------------------------
|
||||
TOTAL 3413 12 99%
|
||||
coverage gate: app/ 99% (>90%) OK
|
||||
All checks passed!
|
||||
0 errors, 0 warnings, 0 informations
|
||||
WARNING: there is a new pyright version available (v1.1.411 -> v1.1.413).
|
||||
Please install the new version or set PYRIGHT_PYTHON_FORCE_VERSION to `latest`
|
||||
|
||||
validation OK
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
All green. Final report:
|
||||
|
||||
**Task 02 complete — `styles.css` whole-site variable coverage**
|
||||
|
||||
- `:root`: kept `--grid-line: #4a2626` (comment reworded to spec), added derived block `--brand-hover/-busy/-stop` (color-mix, documented as computed/not-stored)
|
||||
- Full replacement table applied: grid texture, `::selection`, hairline (brand fade, comment reworded), 2 `pre` blocks, 6 surface backgrounds, 17 button hovers → `var(--brand-hover)`, disabled/busy/stop states, 3 chip washes, ok/err alpha derivatives, 4 backdrops, spinner track, user-code bg; comments re-anchored (grid, is-stop, spinner)
|
||||
- Gate green: `rg -e '#[0-9a-fA-F]{3,6}\b|rgba?\(' frontend/assets/styles.css` → matches only in first `:root` + the single documented `#fff` Stop-label line (rg 15.2 rejects `-E`; used `-e`, same pattern)
|
||||
- Updated 7 pre-existing CSS guardrail unit tests in place (old-literal pins → new mechanism, intent preserved); real-Chromium sanity: PASS (default + saved palette drive grid/hover/hairline)
|
||||
|
||||
**Results**
|
||||
- `uv run pytest --cov=app --cov-report=term-missing` → 1844 passed, TOTAL **99%** (>90%)
|
||||
- `uv run ruff check .` → clean; `uv run pyright` → 0 errors, 0 warnings
|
||||
- E2E: `test_background_no_motion` + `test_dark_tech_theme` green; `test_admin_theme_tab.py` has **3 pre-existing failures from task 01** (9-var server vs 8-picker form — verified by stashing only my files; suite update + 9th picker are task 03/05 scope)
|
||||
|
||||
**Deviations**
|
||||
- `.doc-modal-panel` shadow: `rgb(0 0 0 / 0.55)` → `color-mix(in srgb, black 55%, transparent)` — design said "unchanged" but the gate forbids `rgb(` outside `:root`; rendered color identical (depth-black, not themed), documented inline
|
||||
- is-stop comment cites exact computed ratios (5.8:1/8.5:1/3.4:1, default ≈(187,50,73)) instead of the doc's approximation (5.9:1)
|
||||
|
||||
**Next pending task:** `03_tab_ninth_picker_and_brand_mark.md`
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
........................................................................ [ 3%]
|
||||
........................................................................ [ 7%]
|
||||
........................................................................ [ 11%]
|
||||
........................................................................ [ 15%]
|
||||
........................................................................ [ 19%]
|
||||
........................................................................ [ 23%]
|
||||
........................................................................ [ 27%]
|
||||
........................................................................ [ 31%]
|
||||
........................................................................ [ 35%]
|
||||
........................................................................ [ 39%]
|
||||
........................................................................ [ 42%]
|
||||
........................................................................ [ 46%]
|
||||
........................................................................ [ 50%]
|
||||
........................................................................ [ 54%]
|
||||
........................................................................ [ 58%]
|
||||
........................................................................ [ 62%]
|
||||
........................................................................ [ 66%]
|
||||
........................................................................ [ 70%]
|
||||
........................................................................ [ 74%]
|
||||
........................................................................ [ 78%]
|
||||
........................................................................ [ 81%]
|
||||
........................................................................ [ 85%]
|
||||
........................................................................ [ 89%]
|
||||
........................................................................ [ 93%]
|
||||
........................................................................ [ 97%]
|
||||
............................................ [100%]
|
||||
=============================== warnings summary ===============================
|
||||
.venv/lib/python3.13/site-packages/fastapi/testclient.py:1
|
||||
/var/home/ducoterra/Projects/Personal/brain_of_reese/.venv/lib/python3.13/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
|
||||
from starlette.testclient import TestClient as TestClient # noqa
|
||||
|
||||
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
|
||||
================================ tests coverage ================================
|
||||
_______________ coverage: platform linux, python 3.13.13-final-0 _______________
|
||||
|
||||
Name Stmts Miss Cover
|
||||
--------------------------------------------------
|
||||
app/__init__.py 1 0 100%
|
||||
app/api/__init__.py 0 0 100%
|
||||
app/api/auth.py 52 0 100%
|
||||
app/api/chat.py 178 0 100%
|
||||
app/api/chats.py 110 0 100%
|
||||
app/api/config.py 13 0 100%
|
||||
app/api/doc_drafts.py 94 0 100%
|
||||
app/api/docs.py 50 0 100%
|
||||
app/api/git_sources.py 229 0 100%
|
||||
app/api/health.py 10 0 100%
|
||||
app/api/steering.py 42 0 100%
|
||||
app/api/suggestions.py 29 0 100%
|
||||
app/api/sync.py 101 0 100%
|
||||
app/api/tokens.py 28 0 100%
|
||||
app/api/ui_settings.py 55 0 100%
|
||||
app/config.py 132 0 100%
|
||||
app/core/__init__.py 0 0 100%
|
||||
app/core/auth.py 45 0 100%
|
||||
app/core/caching.py 124 0 100%
|
||||
app/core/debugging.py 29 2 93%
|
||||
app/core/docs_push.py 39 0 100%
|
||||
app/core/errors.py 5 0 100%
|
||||
app/core/logging.py 13 0 100%
|
||||
app/core/rate_limit.py 44 0 100%
|
||||
app/core/security_headers.py 20 0 100%
|
||||
app/core/theming.py 38 0 100%
|
||||
app/core/tokens.py 33 0 100%
|
||||
app/db.py 21 0 100%
|
||||
app/main.py 66 0 100%
|
||||
app/models.py 110 0 100%
|
||||
app/rag/__init__.py 0 0 100%
|
||||
app/rag/agent.py 222 0 100%
|
||||
app/rag/archive_upload.py 128 0 100%
|
||||
app/rag/chunker.py 206 4 98%
|
||||
app/rag/git_sources.py 14 0 100%
|
||||
app/rag/importer.py 190 3 98%
|
||||
app/rag/llm.py 217 0 100%
|
||||
app/rag/overview.py 71 0 100%
|
||||
app/rag/prompts.py 88 0 100%
|
||||
app/rag/retriever.py 150 3 98%
|
||||
app/rag/scaffolding.py 55 0 100%
|
||||
app/rag/source_removal.py 41 0 100%
|
||||
app/rag/sources_meta.py 16 0 100%
|
||||
app/rag/suggestions.py 27 0 100%
|
||||
app/rag/summarizer.py 24 0 100%
|
||||
app/schemas.py 253 0 100%
|
||||
--------------------------------------------------
|
||||
TOTAL 3413 12 99%
|
||||
coverage gate: app/ 99% (>90%) OK
|
||||
All checks passed!
|
||||
0 errors, 0 warnings, 0 informations
|
||||
WARNING: there is a new pyright version available (v1.1.411 -> v1.1.413).
|
||||
Please install the new version or set PYRIGHT_PYTHON_FORCE_VERSION to `latest`
|
||||
|
||||
validation OK
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
All complete and green. The working tree also carries tasks 01/02's changes (the harness makes one atomic phase commit); my delta for task 03 is the five wordmarks, the picker cell, the theme.js count comments, and the test pin.
|
||||
|
||||
**Report — task 03 (tab's 9th picker + brand-mark wordmark theming)**
|
||||
|
||||
- `frontend/index.html`: added the 9th picker cell (`theme-grid-line`, `Grid lines (--grid-line)`, `type="color"`, static built-in `value="#4a2626"`) immediately after the Border cell, mirroring `COLOR_FIELDS` order; fieldset legend untouched (no stated 8/11 counts existed in the theme view — "update only where stated")
|
||||
- Brand-mark SVG in all five files (`index`, `login`, `document`, `shared`, `doc-edit`): hex presentation attrs → inline styles resolving from `:root` (`fill: var(--surface); stroke: var(--brand)` / `fill: var(--brand)` / `fill: none; stroke: var(--brand-ink)`); still byte-identical across the five files; default render identical (same built-in values)
|
||||
- `theme.js`: docstring + comment counts updated per the phase overview (11→12 inputs/values, 8→9 pickers/overrides)
|
||||
- `tests/unit/test_frontend_router.py`: scaffold pin now 9 (docstring + comment) and the id loop gained `theme-grid-line` after `theme-line`
|
||||
|
||||
**Results**
|
||||
- Gates: `rg 'fill="#|stroke="#' frontend/*.html` → 0 matches; `rg "theme-grid-line" frontend/` → only the index.html label+input
|
||||
- `uv run pytest tests/unit/test_frontend_router.py -v --no-cov` → 28 passed
|
||||
- `uv run pytest --cov=app` → 1844 passed, app coverage 99% (>90%)
|
||||
- `uv run ruff check .` → clean; `uv run pyright` → 0 errors, 0 warnings
|
||||
|
||||
**Notes:** live dev-server check (work item 6) is deferred by design — the preview binding lands in task 04; E2E proof is task 05's. Left for later tasks (not touched): `tests/e2e/test_admin_theme_tab.py` count comments (task 05).
|
||||
|
||||
**Next pending task:** `04_live_theme_apply.md`
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
........................................................................ [ 3%]
|
||||
........................................................................ [ 7%]
|
||||
........................................................................ [ 11%]
|
||||
........................................................................ [ 15%]
|
||||
........................................................................ [ 19%]
|
||||
........................................................................ [ 23%]
|
||||
........................................................................ [ 27%]
|
||||
........................................................................ [ 31%]
|
||||
........................................................................ [ 35%]
|
||||
........................................................................ [ 39%]
|
||||
........................................................................ [ 42%]
|
||||
........................................................................ [ 46%]
|
||||
........................................................................ [ 50%]
|
||||
........................................................................ [ 54%]
|
||||
........................................................................ [ 58%]
|
||||
........................................................................ [ 62%]
|
||||
........................................................................ [ 66%]
|
||||
........................................................................ [ 70%]
|
||||
........................................................................ [ 74%]
|
||||
........................................................................ [ 78%]
|
||||
........................................................................ [ 81%]
|
||||
........................................................................ [ 85%]
|
||||
........................................................................ [ 89%]
|
||||
........................................................................ [ 93%]
|
||||
........................................................................ [ 97%]
|
||||
............................................ [100%]
|
||||
=============================== warnings summary ===============================
|
||||
.venv/lib/python3.13/site-packages/fastapi/testclient.py:1
|
||||
/var/home/ducoterra/Projects/Personal/brain_of_reese/.venv/lib/python3.13/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
|
||||
from starlette.testclient import TestClient as TestClient # noqa
|
||||
|
||||
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
|
||||
================================ tests coverage ================================
|
||||
_______________ coverage: platform linux, python 3.13.13-final-0 _______________
|
||||
|
||||
Name Stmts Miss Cover
|
||||
--------------------------------------------------
|
||||
app/__init__.py 1 0 100%
|
||||
app/api/__init__.py 0 0 100%
|
||||
app/api/auth.py 52 0 100%
|
||||
app/api/chat.py 178 0 100%
|
||||
app/api/chats.py 110 0 100%
|
||||
app/api/config.py 13 0 100%
|
||||
app/api/doc_drafts.py 94 0 100%
|
||||
app/api/docs.py 50 0 100%
|
||||
app/api/git_sources.py 229 0 100%
|
||||
app/api/health.py 10 0 100%
|
||||
app/api/steering.py 42 0 100%
|
||||
app/api/suggestions.py 29 0 100%
|
||||
app/api/sync.py 101 0 100%
|
||||
app/api/tokens.py 28 0 100%
|
||||
app/api/ui_settings.py 55 0 100%
|
||||
app/config.py 132 0 100%
|
||||
app/core/__init__.py 0 0 100%
|
||||
app/core/auth.py 45 0 100%
|
||||
app/core/caching.py 124 0 100%
|
||||
app/core/debugging.py 29 2 93%
|
||||
app/core/docs_push.py 39 0 100%
|
||||
app/core/errors.py 5 0 100%
|
||||
app/core/logging.py 13 0 100%
|
||||
app/core/rate_limit.py 44 0 100%
|
||||
app/core/security_headers.py 20 0 100%
|
||||
app/core/theming.py 38 0 100%
|
||||
app/core/tokens.py 33 0 100%
|
||||
app/db.py 21 0 100%
|
||||
app/main.py 66 0 100%
|
||||
app/models.py 110 0 100%
|
||||
app/rag/__init__.py 0 0 100%
|
||||
app/rag/agent.py 222 0 100%
|
||||
app/rag/archive_upload.py 128 0 100%
|
||||
app/rag/chunker.py 206 4 98%
|
||||
app/rag/git_sources.py 14 0 100%
|
||||
app/rag/importer.py 190 3 98%
|
||||
app/rag/llm.py 217 0 100%
|
||||
app/rag/overview.py 71 0 100%
|
||||
app/rag/prompts.py 88 0 100%
|
||||
app/rag/retriever.py 150 3 98%
|
||||
app/rag/scaffolding.py 55 0 100%
|
||||
app/rag/source_removal.py 41 0 100%
|
||||
app/rag/sources_meta.py 16 0 100%
|
||||
app/rag/suggestions.py 27 0 100%
|
||||
app/rag/summarizer.py 24 0 100%
|
||||
app/schemas.py 253 0 100%
|
||||
--------------------------------------------------
|
||||
TOTAL 3413 12 99%
|
||||
coverage gate: app/ 99% (>90%) OK
|
||||
All checks passed!
|
||||
0 errors, 0 warnings, 0 informations
|
||||
WARNING: there is a new pyright version available (v1.1.411 -> v1.1.413).
|
||||
Please install the new version or set PYRIGHT_PYTHON_FORCE_VERSION to `latest`
|
||||
|
||||
validation OK
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
All verified. The diff is coherent and complete.
|
||||
|
||||
## Report — Task 04: live theme apply (no reload, no revert)
|
||||
|
||||
**Implemented**
|
||||
- `theme.js`: `FIELDS` + 9th entry (`grid_line`), `BUILTINS` capture (static input values, pre-`loadSettings`), `themeRootContent` (pure; byte-identical to `theme_style_tag`'s inner content; `null` = no-op), `applyServedTheme` (syncs `#bor-theme` DOM text: remove/create/update), `loadSettings()` now returns the settled settings or `null`, all four call sites (save / reset / `bor:view-refresh` / mount self-heal), docstring reworked.
|
||||
- **Deviation (flagged, per AGENTS.md rule 3):** the task's stated mechanism ("CSSOM on `<style>` is CSP-clean") is **empirically false** — E2E probe against this repo's live phase-82/91 CSP: Chromium re-checks a `<style>` element's content against `style-src` on *every* DOM-API change (`textContent` on the served tag, `createElement`+append, `replaceChildren` all blocked; a fresh palette's sha256 can never be in the page's policy). Reworked fix, server + CSP untouched: tag DOM text still mirrors the next load, and the **9 identity vars as `<html>` inline custom properties** (the preview's CSSOM mechanism — verified clean under both A1 and themed hash policies) repaint the open page. Hence after Save the `<html>` style attribute holds exactly the saved non-default values (never a stale pick; empty after Reset) — the "attribute empty" letter of the criterion is unsatisfiable under the locked CSP while computed = saved hex.
|
||||
- **Fixed a defect in completed work (task 03):** the wordmark's inline `style="…"` attributes in the 5 HTML files are CSP-blocked (hashes never cover style attributes) — 3 console violations per page, wordmark rendered black, `test_security_headers.py` (phase 82) red. Moved the 3 `var()` declarations into `styles.css` (`.brand-mark path:nth-child(1)`, `circle`, `path:nth-child(3)`), removed the attributes; zero violations, default render identical, wordmark follows the saved theme (E2E-verified).
|
||||
|
||||
**Test / lint / coverage**
|
||||
- `uv run pytest --cov=app --cov-report=term` → **1844 passed**, app coverage **99%** (>90%).
|
||||
- `uv run ruff check . && uv run pyright` → **clean** (0 errors).
|
||||
- Live E2E (scratch suite, deleted after green): save applies with NO navigation (tag text + all 9 computed + byte-identical served tag), reset removes tag + paints built-ins, re-show re-syncs an externally-changed row, mount self-heals; SPA nav keeps the theme. E2E regression set (security-headers, header/nav/shared-header, dark-tech, configurable-brand, ui-customization, background, smoke) → **45 passed**.
|
||||
|
||||
**Known pre-existing, NOT fixed (explicitly task 05's scope, work item 5):** `test_admin_theme_tab.py::test_theme_tab_admin_save` + `test_saved_theme_is_pre_paint_for_everyone` — suite's `PALETTE` dict lacks the 9th `grid_line` key (task 01's server change); verified failing identically before my change; other 3 phase-91 tests pass.
|
||||
|
||||
**Next pending task:** `.agents/phases/todo/92_theme_save_and_coverage/05_e2e_save_and_coverage.md`
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
........................................................................ [ 3%]
|
||||
........................................................................ [ 7%]
|
||||
........................................................................ [ 11%]
|
||||
........................................................................ [ 15%]
|
||||
........................................................................ [ 19%]
|
||||
........................................................................ [ 23%]
|
||||
........................................................................ [ 27%]
|
||||
........................................................................ [ 31%]
|
||||
........................................................................ [ 35%]
|
||||
........................................................................ [ 39%]
|
||||
........................................................................ [ 42%]
|
||||
........................................................................ [ 46%]
|
||||
........................................................................ [ 50%]
|
||||
........................................................................ [ 54%]
|
||||
........................................................................ [ 58%]
|
||||
........................................................................ [ 62%]
|
||||
........................................................................ [ 66%]
|
||||
........................................................................ [ 70%]
|
||||
........................................................................ [ 74%]
|
||||
........................................................................ [ 78%]
|
||||
........................................................................ [ 81%]
|
||||
........................................................................ [ 85%]
|
||||
........................................................................ [ 89%]
|
||||
........................................................................ [ 93%]
|
||||
........................................................................ [ 97%]
|
||||
............................................ [100%]
|
||||
=============================== warnings summary ===============================
|
||||
.venv/lib/python3.13/site-packages/fastapi/testclient.py:1
|
||||
/var/home/ducoterra/Projects/Personal/brain_of_reese/.venv/lib/python3.13/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
|
||||
from starlette.testclient import TestClient as TestClient # noqa
|
||||
|
||||
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
|
||||
================================ tests coverage ================================
|
||||
_______________ coverage: platform linux, python 3.13.13-final-0 _______________
|
||||
|
||||
Name Stmts Miss Cover
|
||||
--------------------------------------------------
|
||||
app/__init__.py 1 0 100%
|
||||
app/api/__init__.py 0 0 100%
|
||||
app/api/auth.py 52 0 100%
|
||||
app/api/chat.py 178 0 100%
|
||||
app/api/chats.py 110 0 100%
|
||||
app/api/config.py 13 0 100%
|
||||
app/api/doc_drafts.py 94 0 100%
|
||||
app/api/docs.py 50 0 100%
|
||||
app/api/git_sources.py 229 0 100%
|
||||
app/api/health.py 10 0 100%
|
||||
app/api/steering.py 42 0 100%
|
||||
app/api/suggestions.py 29 0 100%
|
||||
app/api/sync.py 101 0 100%
|
||||
app/api/tokens.py 28 0 100%
|
||||
app/api/ui_settings.py 55 0 100%
|
||||
app/config.py 132 0 100%
|
||||
app/core/__init__.py 0 0 100%
|
||||
app/core/auth.py 45 0 100%
|
||||
app/core/caching.py 124 0 100%
|
||||
app/core/debugging.py 29 2 93%
|
||||
app/core/docs_push.py 39 0 100%
|
||||
app/core/errors.py 5 0 100%
|
||||
app/core/logging.py 13 0 100%
|
||||
app/core/rate_limit.py 44 0 100%
|
||||
app/core/security_headers.py 20 0 100%
|
||||
app/core/theming.py 38 0 100%
|
||||
app/core/tokens.py 33 0 100%
|
||||
app/db.py 21 0 100%
|
||||
app/main.py 66 0 100%
|
||||
app/models.py 110 0 100%
|
||||
app/rag/__init__.py 0 0 100%
|
||||
app/rag/agent.py 222 0 100%
|
||||
app/rag/archive_upload.py 128 0 100%
|
||||
app/rag/chunker.py 206 4 98%
|
||||
app/rag/git_sources.py 14 0 100%
|
||||
app/rag/importer.py 190 3 98%
|
||||
app/rag/llm.py 217 0 100%
|
||||
app/rag/overview.py 71 0 100%
|
||||
app/rag/prompts.py 88 0 100%
|
||||
app/rag/retriever.py 150 3 98%
|
||||
app/rag/scaffolding.py 55 0 100%
|
||||
app/rag/source_removal.py 41 0 100%
|
||||
app/rag/sources_meta.py 16 0 100%
|
||||
app/rag/suggestions.py 27 0 100%
|
||||
app/rag/summarizer.py 24 0 100%
|
||||
app/schemas.py 253 0 100%
|
||||
--------------------------------------------------
|
||||
TOTAL 3413 12 99%
|
||||
coverage gate: app/ 99% (>90%) OK
|
||||
All checks passed!
|
||||
0 errors, 0 warnings, 0 informations
|
||||
WARNING: there is a new pyright version available (v1.1.411 -> v1.1.413).
|
||||
Please install the new version or set PYRIGHT_PYTHON_FORCE_VERSION to `latest`
|
||||
|
||||
validation OK
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
Everything is green in the final state. Final verification summary complete.
|
||||
|
||||
**Report — Phase 92, task 05 (E2E: save/reset apply live + whole-site coverage)**
|
||||
|
||||
- Implemented `tests/e2e/test_theme_save_and_coverage.py` (3 tests, house per-module scaffold mirroring the phase-91 file): save-applies-live-no-reload (9-var computed palette + synced `#bor-theme` DOM text + exact `<html>` override set, server raw tag, SPA nav to Chat keeps theme), reset-applies-live (tag removed from live doc, built-ins painted, byte-identical with-row vs row-less), whole-site (grid `body::before` 60% `--grid-line`, `::selection` 45% brand, `--brand-hover` hovers ≠ legacy indigo, nav wash = brand-soft exact, wordmark = surface exact, pre-paint raw tag + CSP hash) — with a premultiplied sRGB `color-mix` helper parsing the browser's `color(srgb …)` serialization at ±1/channel.
|
||||
- Updated `tests/e2e/test_admin_theme_tab.py` in place: `PALETTE` gains `grid_line: "#2b3550"` (required — 12-key PUT echo + 9-field DB row assert) and count comments 8→9 / 11→12 only.
|
||||
- Deviation (documented in the new file's docstring): the task's literal "empty `<html>` style after Save" is impossible under the repo CSP — probe-verified that Chromium re-checks `style-src` on every `<style>` DOM-text mutation (textContent/createElement/replaceChildren all blocked), so task 04's CSSOM `<html>` overrides are the only CSP-clean repaint path; test 1 asserts "overrides are EXACTLY the saved palette" (no stale pick) instead. Also added mount-settle gates (race: out-of-order mount `loadSettings` could re-reconcile onto a stale read), and used an `""` absent-tag sentinel (Playwright `wait_for_function` serializes Python `None` as JS `undefined`).
|
||||
- Pin verified: test 1 FAILS (15s timeout) with the phase-91 `theme.js` swapped in, passes with task 04's code.
|
||||
- Results: `uv run pytest tests/e2e/test_theme_save_and_coverage.py -v --no-cov` → 3 passed (isolation); `uv run pytest tests/e2e/test_admin_theme_tab.py -v --no-cov` → 5 passed (isolation); `uv run pytest --cov=app --cov-report=term-missing` → 1844 passed, app coverage 99%; `uv run ruff check . && uv run pyright` → clean (0 errors).
|
||||
- No changes to `app/`, `.agents/`, or other completed work; no commits (left for the harness).
|
||||
- Next pending task: none in `92_theme_save_and_coverage` (task 05 is the last — phase complete).
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
........................................................................ [ 3%]
|
||||
........................................................................ [ 7%]
|
||||
........................................................................ [ 11%]
|
||||
........................................................................ [ 15%]
|
||||
........................................................................ [ 19%]
|
||||
........................................................................ [ 23%]
|
||||
........................................................................ [ 27%]
|
||||
........................................................................ [ 31%]
|
||||
........................................................................ [ 35%]
|
||||
........................................................................ [ 39%]
|
||||
........................................................................ [ 42%]
|
||||
........................................................................ [ 46%]
|
||||
........................................................................ [ 50%]
|
||||
........................................................................ [ 54%]
|
||||
........................................................................ [ 58%]
|
||||
........................................................................ [ 62%]
|
||||
........................................................................ [ 66%]
|
||||
........................................................................ [ 70%]
|
||||
........................................................................ [ 74%]
|
||||
........................................................................ [ 78%]
|
||||
........................................................................ [ 81%]
|
||||
........................................................................ [ 85%]
|
||||
........................................................................ [ 89%]
|
||||
........................................................................ [ 93%]
|
||||
........................................................................ [ 97%]
|
||||
............................................ [100%]
|
||||
=============================== warnings summary ===============================
|
||||
.venv/lib/python3.13/site-packages/fastapi/testclient.py:1
|
||||
/var/home/ducoterra/Projects/Personal/brain_of_reese/.venv/lib/python3.13/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
|
||||
from starlette.testclient import TestClient as TestClient # noqa
|
||||
|
||||
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
|
||||
================================ tests coverage ================================
|
||||
_______________ coverage: platform linux, python 3.13.13-final-0 _______________
|
||||
|
||||
Name Stmts Miss Cover
|
||||
--------------------------------------------------
|
||||
app/__init__.py 1 0 100%
|
||||
app/api/__init__.py 0 0 100%
|
||||
app/api/auth.py 52 0 100%
|
||||
app/api/chat.py 178 0 100%
|
||||
app/api/chats.py 110 0 100%
|
||||
app/api/config.py 13 0 100%
|
||||
app/api/doc_drafts.py 94 0 100%
|
||||
app/api/docs.py 50 0 100%
|
||||
app/api/git_sources.py 229 0 100%
|
||||
app/api/health.py 10 0 100%
|
||||
app/api/steering.py 42 0 100%
|
||||
app/api/suggestions.py 29 0 100%
|
||||
app/api/sync.py 101 0 100%
|
||||
app/api/tokens.py 28 0 100%
|
||||
app/api/ui_settings.py 55 0 100%
|
||||
app/config.py 132 0 100%
|
||||
app/core/__init__.py 0 0 100%
|
||||
app/core/auth.py 45 0 100%
|
||||
app/core/caching.py 124 0 100%
|
||||
app/core/debugging.py 29 2 93%
|
||||
app/core/docs_push.py 39 0 100%
|
||||
app/core/errors.py 5 0 100%
|
||||
app/core/logging.py 13 0 100%
|
||||
app/core/rate_limit.py 44 0 100%
|
||||
app/core/security_headers.py 20 0 100%
|
||||
app/core/theming.py 38 0 100%
|
||||
app/core/tokens.py 33 0 100%
|
||||
app/db.py 21 0 100%
|
||||
app/main.py 66 0 100%
|
||||
app/models.py 110 0 100%
|
||||
app/rag/__init__.py 0 0 100%
|
||||
app/rag/agent.py 222 0 100%
|
||||
app/rag/archive_upload.py 128 0 100%
|
||||
app/rag/chunker.py 206 4 98%
|
||||
app/rag/git_sources.py 14 0 100%
|
||||
app/rag/importer.py 190 3 98%
|
||||
app/rag/llm.py 217 0 100%
|
||||
app/rag/overview.py 71 0 100%
|
||||
app/rag/prompts.py 88 0 100%
|
||||
app/rag/retriever.py 150 3 98%
|
||||
app/rag/scaffolding.py 55 0 100%
|
||||
app/rag/source_removal.py 41 0 100%
|
||||
app/rag/sources_meta.py 16 0 100%
|
||||
app/rag/suggestions.py 27 0 100%
|
||||
app/rag/summarizer.py 24 0 100%
|
||||
app/schemas.py 253 0 100%
|
||||
--------------------------------------------------
|
||||
TOTAL 3413 12 99%
|
||||
coverage gate: app/ 99% (>90%) OK
|
||||
All checks passed!
|
||||
0 errors, 0 warnings, 0 informations
|
||||
validation OK
|
||||
Reference in New Issue
Block a user