chore(agent): phase 93-95 roadmap from TODO.md — theme completion, ls tree drill-down, read truncation cap
Convert the three unchecked TODO.md items into an executable phase roadmap (Protocol B, appended after phase 92): - 93_theme_semantic_completion (TODO L3): the ok/err/accent state families become Theme-tab-controlled (B3 revised, owner permission 2026-09-10) + surface panels behind every page head - 94_ls_tree_drilldown (TODO L4): ls becomes a source -> folder -> file tree with sync-time lite-model folder summaries; controlled tool-calling battery as the accuracy/performance gate - 95_read_truncation_cap (TODO L5): read capped at BOR_READ_MAX_CHARS (128k chars ~= 32k tokens, spec'd on the 128k-token minimum context), LLM-visible truncation notice pointing at grep, new tool_result SSE event (A15 extension) + the visible UI marker Owner decisions (B3 / A7 scope / A15) are recorded in the phase files; .agents/PLAN.md is being redone separately per the owner.
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
# Phase 93 — Theme completion: semantic state colors become tab-controlled + readable page heads
|
||||
|
||||
**Source:** `TODO.md` L3 — "I created a black/white/gray theme for brain of reese and found multiple cases of color still in the UI which tells me the customization is not complete. Screenshots are in the theme_fixes/ folder. Note the green text "Theme saved", the red "Revoked" tag, the red "Stale" tag, The green "Local" tag, The yellow "Listing documents" and "Reading" tool calls. Also the header and description of each page needs a background - the grid makes it hard to read."
|
||||
**Story:** n/a (owner TODO item — theme-customization completion on `91_admin_theme_tab` / `92_theme_save_and_coverage`).
|
||||
**Context:** The admin Theme tab (phases 91/92) persists the **9 identity variables** (`bg, surface, ink, ink_soft, line, grid_line, brand, brand_soft, brand_ink`) in the single-row `ui_settings` table (`app/models.py` `UiSettings`), resolves them in `app/core/theming.py` (`BUILTIN_COLORS` / `COLOR_FIELDS` / `effective_settings`), serves them via `GET/PUT /api/ui-settings` (`app/api/ui_settings.py`), injects them pre-paint as `<style id="bor-theme">:root{…}</style>` with a matching CSP hash (`theme_style_tag` / `theme_csp_hash` / `app/core/caching.py`), and edits them live in `frontend/assets/theme.js` (the `FIELDS` array drives pickers, live preview, PUT body, contrast warnings). Phase 92 removed every hardcoded color literal, but the **semantic state families are raw `:root` variables the tab cannot reach** — `--ok-bg/--ok-ink` (the green "Theme saved." result text, the green `LOCAL` badge `.git-source-kind.is-local`), `--err-bg/--err-ink/--err-line` (the rose `Stale` / `Revoked` pills `.stale-pill`), `--accent-bg/--accent-ink/--accent-line` (the amber agent tool-call lines `.tool-call`), built-in values at `frontend/assets/styles.css` L29–36. Separately, every page's `h1` + description (`.page-head`) sits directly on the background grid texture and is hard to read (all 8 screenshots in `theme_fixes/`).
|
||||
|
||||
## Objective
|
||||
Make theme customization **complete**: a monochrome (black/white/gray) theme can be saved with zero residual color, because the three semantic state families become Theme-tab-controlled (17 palette variables total) with `NULL = built-in` (the default theme renders byte-identical to today); and every page's header + description block gets a solid surface background so the grid never fights the heading text. State stays honest: every state element keeps its **text** label (the "text + color, never color alone" house rule), so a grayscale theme conveys state by words, not hues.
|
||||
|
||||
## Owner-permitted decision recorded here (PLAN.md is being redone by the owner)
|
||||
- **B3 revised (owner permission 2026-09-10, TODO.md L3):** the semantic families `--ok-*`, `--err-*`, `--accent-*` become **storable and Theme-tab-controlled** (the 2026-09-09 lock that they "are NOT identity … not configurable from the tab" is lifted). `NULL = built-in`, so the built-in theme's look and all existing AA ratios are unchanged; only the tab's reachable surface grows.
|
||||
- Header panel design (owner-permitted by the same TODO line — "needs a background"): a solid `var(--surface)` panel behind each page head, not a full-bleed band (ASSUMPTION in task 03).
|
||||
|
||||
## Dependencies
|
||||
- `92_theme_save_and_coverage` (complete) — the whole phase builds on it: the `ui_settings` row + resolver + admin API, the pre-paint injection + CSP hash in `app/core/caching.py`, the `#view-theme` form (`frontend/index.html`) + editor (`frontend/assets/theme.js`), and the E2E suites `tests/e2e/test_admin_theme_tab.py` / `test_theme_save_and_coverage.py` / `test_configurable_brand.py` (all stay green — a no-op theme must remain byte-identical).
|
||||
|
||||
## Design (shared by all tasks — the executor reads this, not the chat)
|
||||
|
||||
### 17-variable palette (tasks 01, 02)
|
||||
The 9 identity variables are untouched. **8 semantic variables** join the storable palette, column names mirroring the CSS variables:
|
||||
|
||||
| Column / field | CSS var | Built-in (from `styles.css` `:root`) | UI role |
|
||||
|---|---|---|---|
|
||||
| `ok_bg` | `--ok-bg` | `#10241b` | success pill/text background ("Theme saved.") |
|
||||
| `ok_ink` | `--ok-ink` | `#6ee7a8` | success text |
|
||||
| `err_bg` | `--err-bg` | `#2d0a0a` | Stale/Revoked pill background |
|
||||
| `err_ink` | `--err-ink` | `#fca5a5` | Stale/Revoked pill text |
|
||||
| `err_line` | `--err-line` | `#ef4444` | Stale/Revoked pill border (decorative — no contrast duty) |
|
||||
| `accent_bg` | `--accent-bg` | `#2b2110` | deflection banner / tool-line background |
|
||||
| `accent_ink` | `--accent-ink` | `#fbbf24` | tool-call line text ("Listing…", "Reading…") |
|
||||
| `accent_line` | `--accent-line` | `#f59e0b` | tool-line left border / deflection border (decorative) |
|
||||
|
||||
Server plumbing mirrors the phase-92 `grid_line` pattern exactly: migration `0016` (after `0015_grid_line.py`), 8 nullable `String(7)` columns, `BUILTIN_COLORS` extended (the `:root`-drift test in `tests/unit/test_theming.py` parses the built-ins from `styles.css` — no new hardcoded palette copy), `COLOR_FIELDS` order = the 9 identity vars **then** the 8 semantic vars (structural first, brand middle, state last). Everything downstream is `COLOR_FIELDS`-driven and picks the 8 up with **zero logic change**: `effective_settings`, `theme_style_tag` (the pre-paint tag gains 8 declarations when any var is non-default; the byte-identical no-op contract holds — the tag is still `""` only when all 17 equal their built-ins), `theme_csp_hash` (recomputed per theme, runtime), the `app/core/caching.py` injection, and the `COLOR_FIELDS`-driven loops in `app/api/ui_settings.py`.
|
||||
|
||||
### Contrast pairs 5 → 8 (task 02)
|
||||
The tab's client-side AA warnings (`theme.js` `PAIRS`, L184) gain three ink-on-bg pairs: `ok_ink` on `ok_bg`, `err_ink` on `err_bg`, `accent_ink` on `accent_bg`. The two `_line` vars stay excluded (decorative borders, no contrast duty — same rule as `--line`/`--grid-line`). The authoritative table lives in `app/core/theming.py`'s docstring — extend it there and in `theme.js`'s pair list together.
|
||||
|
||||
### Page-head panel (task 03)
|
||||
`.page-head` (the shell's standard frame: `h1` + description — `styles.css` L1536 ff.) gets `background: var(--surface)`, `padding`, `border: 1px solid var(--line)` and the house card radius, on EVERY page: the seven shell views (`#view-chat`, `#view-tuning`, `#view-rag`, `#view-git-sources`, `#view-history`, `#view-tokens`, `#view-theme` — one shared rule covers them) plus the standalone pages' header blocks (`frontend/login.html`, `frontend/document.html`, `frontend/shared.html`, `frontend/doc-edit.html` — audit each; apply the same panel treatment to whatever class carries their h1 + lede, including the shared-page head that reuses the `.page-head h1` size per the `styles.css` L3189 note). The panel must not break: the `#view-history .page-head` flex row (title left, stale pill right — `styles.css` L2550), the mobile wrap of `.page-head-row` (L4221), or any existing header/nav/responsive E2E.
|
||||
|
||||
### Monochrome E2E (task 04)
|
||||
One dedicated story suite: as admin, `PUT /api/ui-settings` an all-gray 17-color theme (every channel R=G=B; pairs still ≥ 4.5:1), then assert per element that the **computed** color is grayscale and the **text** state label is still present: "Theme saved." result (`#theme-result`), a `Stale` pill (History — seed an out-of-generation saved chat, the phase-53 pattern in `tests/e2e/test_stale_saved_chats.py`), a `Revoked` pill (Tokens — create + revoke via the UI), the `LOCAL` badge (Git sources — register a local-directory source, the `tests/e2e/test_local_directory_sources.py` pattern), and a `.tool-call` line (Chat — a mock-LLM turn that executes a tool, the `tests/e2e/test_agent_document_tools.py` pattern). Plus: every page head's computed `background-color` is non-transparent, and a fresh `page.goto` of each page paints the gray palette pre-paint (the `#bor-theme` tag carries all 17 when non-default).
|
||||
|
||||
## Tasks
|
||||
1. `01_semantic_columns.md` — migration 0016 + `UiSettings` columns + `BUILTIN_COLORS`/`effective_settings` + schema + API validation
|
||||
2. `02_theme_tab_state_section.md` — `#view-theme` form fields + `theme.js` `FIELDS`/`PAIRS` (5→8 pairs) + live preview/Reset coverage
|
||||
3. `03_page_head_background.md` — `.page-head` (and standalone-page) surface panel across every page
|
||||
4. `04_e2e_monochrome_theme.md` — dedicated Playwright suite: full-gray theme → zero residual color, state text intact, heads readable
|
||||
|
||||
## Testing & Quality
|
||||
- Unit/integration: resolver round-trip for all 17 (NULL=clear, built-in→NULL normalization, 422 on bad hex names the field); pre-paint tag + CSP hash with the 17 vars (non-default subset, no-op byte-identical); drift test covers the 17; `PUT/GET /api/ui-settings` integration for the 8 new fields; existing theming suites (`tests/unit/test_theming.py`, `test_ui_settings.py`, `test_caching.py`, `tests/integration/test_ui_settings_api.py`, `test_security_headers.py`) stay green.
|
||||
- Coverage: **>90%** on `app/` (`uv run pytest --cov=app --cov-report=term-missing`).
|
||||
- E2E: `uv run pytest tests/e2e/test_theme_semantic_completion.py -v --no-cov` in isolation; the existing theme suites (`test_admin_theme_tab.py`, `test_theme_save_and_coverage.py`, `test_configurable_brand.py`, `test_dark_tech_theme.py`, `test_header_consistency.py`, `test_nav_*`, responsive) stay green in isolation.
|
||||
- Lint/types: `uv run ruff check . && uv run pyright`.
|
||||
|
||||
## Completion Criteria
|
||||
- [ ] An all-gray 17-color theme saved from the tab renders with **no colored element anywhere** (verified by the new E2E's computed-style assertions) while Stale/Revoked/Local/Tool state text remains visible
|
||||
- [ ] Default (no row / all NULL) deployment serves byte-identical HTML (no `#bor-theme` tag) and the built-in theme's 8 semantic look is unchanged
|
||||
- [ ] Every page head (7 shell views + 4 standalone pages) has a solid surface background over the grid
|
||||
- [ ] test suite green, coverage >90%, ruff + pyright clean, dedicated E2E green in isolation
|
||||
- [ ] no behavior change in completed phases (phases 01–92 suites green)
|
||||
- [ ] one atomic Conventional Commit, `--no-gpg-sign` (e.g. `feat(theme): make semantic state colors tab-controlled and give page heads a surface panel`)
|
||||
Reference in New Issue
Block a user