# Plan: tui-panel-polish Status: done -- current TUI panels **What you'll get:** All 5 panels get consistent bordered containers with visible gaps. Verdict Mix popup goes from 40→50 chars so the table renders properly. **Why:** Panels are flat widgets (`\n`) with no containers, no margins. Only the quota panel uses a `Vertical` container. Boring fix: wrap everything in `Vertical{border, margin-top: 1}` — same pattern that already works for quota. **Not doing:** No Grid, no Horizontal, no Footer, no colors/themes, no new features, no backend changes. **Effort:** Short | **Risk:** Low | **Files:** `tui.py` + `tui_screens.py` only ## Todos | # | Scope | |---|-------| | 1 | Wrap all 5 panels in bordered `Vertical` containers with `margin-top: 1` on 4 new ones | | 2 | CSS: `.panel-title` margin 1→2, add `margin-top: 1` selectors for new containers | | 3 | `VerdictMixScreen` width: 50 (was 40) | | 4 | Warnings render: target inner `#warnings-content` `Static` instead of `#warnings-panel` | | 5 | `python -m pytest tests/test_tui.py -v` — fix DOM mismatches | | 6 | Import smoke test: all 3 modules, exit 0 | ## Success criteria 1. All 5 panels bordered consistently 2. ≥1-line gap between each panel border 3. Warnings panel matches border style 4. `VerdictMixScreen` table renders without cramped wrapping 5. `tests/test_tui.py` — 0 failures 6. No new lint warnings --- ## Quick sanity check against the current code (not a full review — flagging one gap before execution) Verified the plan's diagnosis directly against `tui.py`/`tui_screens.py`, since it's cheap and this is about to be executed: - **Confirmed accurate:** only `quota-panel` is wrapped in a `Vertical` today (`tui.py:209`); the other four panels really are a bare `Static(..., classes="panel-title")` immediately followed by a `DataTable`/`Static` with no wrapping container (`tui.py:212-219`). `VerdictMixScreen { width: 40; }` is exact (`tui_screens.py:83`). - **Todo 4 is necessary, not optional:** `#warnings-panel` is currently the `Static` itself (`tui.py:219`), and two call sites type it as `Static` directly — `query_one("#warnings-panel", Static).can_focus = True` (`tui.py:317`) and the render path at `tui.py:527`. Once todo 1 makes `#warnings-panel` the new *outer* `Vertical` container (to keep the `_panels`/number-key focus list at `tui.py:194-200` pointing at the right id), both of those call sites break unless they're repointed at a new inner `#warnings-content` `Static` — todo 4 catches exactly this, good. - **One thing the plan doesn't address:** `DataTable` already has its own border today — `DataTable { border: round $primary; ... }` (`tui.py:145-149`), a blanket rule hitting all three table panels (decision/model/category). `#quota-panel` gets its border from the *wrapping* `Vertical` (`tui.py:150-154`), and its children (`quota-progress`/`quota-legend`) have no border of their own — so quota has exactly one border today. If todo 1 wraps the three `DataTable` panels in a new bordered `Vertical` without also dropping the `DataTable`'s own `border: round $primary`, those three panels will end up with two nested rounded borders (the new wrapper's + the table's existing one), while quota and warnings get one — the opposite of success criterion 1 ("bordered consistently"). Worth folding into todo 1 or 2: either remove the border from the blanket `DataTable` rule (letting the new wrapper supply it uniformly) or give the wrapper no border for those three and rely on the table's own — either works, but the plan should say which before execution rather than leaving it to be discovered mid-todo.