fix(ui): mark sidebar focus with an accent colour, not bold (#25)

The focused sidebar panel border (ADR-0046 DC3) was bright `fg` plus
`Modifier::BOLD`. Bold box-drawing glyphs render as broken/gapped
line-art in the asciinema cast player and are fragile in some terminals.

`panel_border_style` now marks focus with a non-bold accent colour
(`theme.mode_simple`, blue); the unfocused border stays muted. Bold is
untouched on text spans (titles, key hints) — the constraint is
specifically that box-drawing borders carry no bold attribute.

Pure style change: the Tier-2 snapshots are text-only so none needed
re-accepting; the Tier-1 assertion was updated and a render-level test
now checks the rendered border cells carry the accent and no bold.

ADR-0046 Amendment 1.
This commit is contained in:
claude@clouddev1
2026-06-12 15:01:26 +00:00
parent 3d4a0fd45e
commit fde50ce3bf
3 changed files with 95 additions and 9 deletions
@@ -554,3 +554,27 @@ All tiers green, zero skips; clippy clean (nursery).
and is accepted: 90 is the screencast width, real terminals sit well and is accepted: 90 is the screencast width, real terminals sit well
to one side of it, and `Ctrl-O` peek covers the in-between case. The to one side of it, and `Ctrl-O` peek covers the in-between case. The
`90` threshold is a tunable constant. `90` threshold is a tunable constant.
## Amendment 1 — focus accent is a colour, not bold (2026-06-12)
Issue #25. DC3's "accent border" on the focused sidebar panel was
first implemented as bright `theme.fg` **plus `Modifier::BOLD`** on
the box-drawing border. Bold box-drawing glyphs render as broken /
gapped line-art in the asciinema player used for the website casts
(vertical strokes don't connect to the corner glyphs) and are
fragile in some terminals.
**`panel_border_style` now marks focus with a non-bold accent
colour — `theme.mode_simple` (blue) — and never `Modifier::BOLD` on
a border.** The unfocused border stays muted `theme.border`. This
makes the ADR's "accent border (lazygit convention)" wording
literal — it is now a true accent hue rather than bold bright-fg —
and is what renders cleanly in casts. Bold remains fine on *text*
spans (titles, key hints); the constraint is specifically that
box-drawing borders carry no bold attribute.
Note: this is a pure style change. The Tier-2 snapshots are
text-only (`render_to_string` captures cell symbols, not styles),
so none needed re-accepting; the Tier-1 `panel_border_style`
assertion was updated and a render-level test now checks the actual
border cells carry the accent colour and no bold.
+1 -1
View File
File diff suppressed because one or more lines are too long
+70 -8
View File
@@ -275,13 +275,15 @@ fn render_nav_sidebar_overlay(app: &mut App, theme: &Theme, frame: &mut Frame<'_
render_relationships_panel(app, theme, frame, parts[1]); render_relationships_panel(app, theme, frame, parts[1]);
} }
/// Border style for a sidebar panel: an accented, bold border when it /// Border style for a sidebar panel: a non-bold **accent colour**
/// holds navigation focus (ADR-0046 DC3), the muted border otherwise. /// border when it holds navigation focus (ADR-0046 DC3, refined by
/// Amendment 1 / issue #25), the muted border otherwise. The focus
/// cue is the accent hue, NOT `Modifier::BOLD` — bold box-drawing
/// glyphs render as broken/gapped line-art in the asciinema player
/// and are fragile in some terminals.
fn panel_border_style(theme: &Theme, focused: bool) -> Style { fn panel_border_style(theme: &Theme, focused: bool) -> Style {
if focused { if focused {
Style::default() Style::default().fg(theme.mode_simple)
.fg(theme.fg)
.add_modifier(Modifier::BOLD)
} else { } else {
Style::default().fg(theme.border) Style::default().fg(theme.border)
} }
@@ -3027,16 +3029,76 @@ mod tests {
#[test] #[test]
fn focused_panel_gets_an_accent_border() { fn focused_panel_gets_an_accent_border() {
// ADR-0046 DC3: the focused sidebar panel is accent-bordered. // ADR-0046 DC3 (Amendment 1, issue #25): the focused sidebar
// panel is marked by a non-bold accent COLOUR, not bold. Bold
// box-drawing glyphs render as broken/gapped line-art in the
// asciinema player (and are fragile in some terminals), so the
// focus cue is the accent hue against the muted unfocused
// border — never a `Modifier::BOLD` on the border.
let theme = Theme::dark(); let theme = Theme::dark();
let focused = panel_border_style(&theme, true); let focused = panel_border_style(&theme, true);
let normal = panel_border_style(&theme, false); let normal = panel_border_style(&theme, false);
assert_eq!(focused.fg, Some(theme.fg)); assert_eq!(focused.fg, Some(theme.mode_simple));
assert!(focused.add_modifier.contains(Modifier::BOLD)); assert!(
!focused.add_modifier.contains(Modifier::BOLD),
"the focused border must NOT be bold (issue #25)",
);
assert_eq!(normal.fg, Some(theme.border)); assert_eq!(normal.fg, Some(theme.border));
assert!(!normal.add_modifier.contains(Modifier::BOLD)); assert!(!normal.add_modifier.contains(Modifier::BOLD));
} }
#[test]
fn focused_panel_border_cells_are_accent_colour_not_bold() {
// Full-stack guard for issue #25: the accent colour (and the
// absence of bold) must reach the actual rendered border cells,
// not just `panel_border_style` in isolation. With the Tables
// panel focused, its box-drawing border cells carry
// `theme.mode_simple` and never `Modifier::BOLD`; with no panel
// focused, no border cell wears the accent colour.
const BOX_DRAWING: &[char] = &['╭', '╮', '╰', '╯', '─', '│'];
let is_border = |sym: &str| sym.chars().all(|c| BOX_DRAWING.contains(&c));
let theme = Theme::dark();
let mut app = App::new();
app.tables = vec!["Customers".to_string(), "Orders".to_string()];
app.nav_focus = NavFocus::SidebarTables;
let buf = render_to_buffer(&mut app, &theme, 110, 24);
let mut accent_border_cells = 0;
for y in 0..buf.area.height {
for x in 0..buf.area.width {
let cell = &buf[(x, y)];
if is_border(cell.symbol()) && cell.fg == theme.mode_simple {
accent_border_cells += 1;
assert!(
!cell.modifier.contains(Modifier::BOLD),
"focused border cell at ({x},{y}) must not be bold (issue #25)",
);
}
}
}
assert!(
accent_border_cells > 0,
"the focused Tables panel must render accent-coloured border cells",
);
// With nothing focused (Input), no border cell wears the accent.
let mut app2 = App::new();
app2.tables = vec!["Customers".to_string()];
app2.nav_focus = NavFocus::Input;
let buf2 = render_to_buffer(&mut app2, &theme, 110, 24);
for y in 0..buf2.area.height {
for x in 0..buf2.area.width {
let cell = &buf2[(x, y)];
if is_border(cell.symbol()) {
assert_ne!(
cell.fg, theme.mode_simple,
"no border cell may wear the focus accent when nothing is focused (at {x},{y})",
);
}
}
}
}
#[test] #[test]
fn focused_tables_panel_scrolls_and_clamps() { fn focused_tables_panel_scrolls_and_clamps() {
// ADR-0046 DC3: more tables than fit → a large offset reveals the // ADR-0046 DC3: more tables than fit → a large offset reveals the