feat(ui): flat filled rectangles for demo overlays (#22, ADR-0047 D4)

Render the keystroke badge and step caption as a solid yellow rectangle
with no border glyphs and a one-cell text margin, instead of a
rounded-border box — deliberately unlike the app's bordered panels so
the demo overlays read as a distinct, eye-catching callout. Shared
fill_overlay_rect helper (borderless Block fill + inset Paragraph).
Snapshots regenerated; ADR-0047 D4 wording updated.
This commit is contained in:
claude@clouddev1
2026-06-11 08:40:07 +00:00
parent 241f60c503
commit 2d0f4b2958
7 changed files with 76 additions and 71 deletions
@@ -11,12 +11,12 @@ expression: snapshot
│ │
│ │
│ │
╭───────╮
[TAB]
╰───────╯
╭─────────────────────╮
Completing the name
╰─────────────────────╯
[TAB] │
Completing the name │
│ │
╰────────────────────────────────────────────────────────────────────────────────────────╯
╭ SIMPLE ────────────────────────────────────────────────────────────────────────────────╮
@@ -14,9 +14,9 @@ expression: snapshot
│ │
│ │
│ │
╭─────────╮
[ENTER]
╰─────────╯
[ENTER] │
│ │
╰────────────────────────────────────────────────────────────────────────────────────────╯
╭ SIMPLE ────────────────────────────────────────────────────────────────────────────────╮
@@ -14,9 +14,9 @@ expression: snapshot
│ │
│ │
│ │
╭───────╮
[TAB]
╰───────╯
[TAB] │
│ │
╰────────────────────────────────────────────────────────────────────────────────────────╯
╭ SIMPLE ────────────────────────────────────────────────────────────────────────────────╮
@@ -14,9 +14,9 @@ expression: snapshot
│ │
│ │
│ │
╭──────────────────────────────────────────╮
Now press Tab to complete the table name
╰──────────────────────────────────────────╯
Now press Tab to complete the table name │
│ │
╰────────────────────────────────────────────────────────────────────────────────────────╯
╭ SIMPLE ────────────────────────────────────────────────────────────────────────────────╮
@@ -12,11 +12,11 @@ expression: snapshot
│ │
│ │
│ │
╭──────────────────────────────────────────╮
This is a deliberately long step caption
that must wrap onto several lines and
then be clipped to three with an…
╰──────────────────────────────────────────╯
This is a deliberately long step caption │
that must wrap onto several lines and │
then be clipped to three with an… │
│ │
╰────────────────────────────────────────────────────────────────────────────────────────╯
╭ SIMPLE ────────────────────────────────────────────────────────────────────────────────╮
+31 -35
View File
@@ -152,15 +152,31 @@ fn render_demo_overlays(app: &App, frame: &mut Frame<'_>) {
}
}
/// Paint a flat filled overlay rectangle — a solid yellow block with no
/// border glyphs (ADR-0047 D4) — and lay `body` inside a one-cell
/// margin. The borderless solid block is deliberately *unlike* the app's
/// bordered panels, so the demo overlays read as a distinct callout.
fn fill_overlay_rect(rect: Rect, body: String, frame: &mut Frame<'_>) {
frame.render_widget(ratatui::widgets::Clear, rect);
// `Block` with no borders fills the whole rect with the overlay
// background (same mechanism as `paint_background`).
frame.render_widget(Block::default().style(demo_overlay_style()), rect);
let inner = rect.inner(Margin {
horizontal: 1,
vertical: 1,
});
frame.render_widget(Paragraph::new(body).style(demo_overlay_style()), inner);
}
/// A small high-contrast keystroke badge (`[TAB]`, `[ENTER]`, …) inset
/// one cell from the bottom-right of `area` (ADR-0047 D2/D4). When a
/// caption box is present (`above`), the badge sits directly on top of
/// it, right-aligned; otherwise it takes the bottom-right corner.
/// Skipped rather than overflowing if it cannot fit.
/// one cell from the bottom-right of `area` (ADR-0047 D2/D4) — the label
/// on a flat yellow rectangle with a one-cell margin. When a caption box
/// is present (`above`), the badge sits directly on top of it, right
/// edges aligned; otherwise it takes the bottom-right corner. Skipped
/// rather than overflowing if it cannot fit.
fn render_badge_box(label: &str, area: Rect, above: Option<Rect>, frame: &mut Frame<'_>) {
// ` [LABEL] ` (one pad each side) inside a rounded border.
let box_w = label.chars().count() as u16 + 4;
let box_h = 3;
let box_w = label.chars().count() as u16 + 2; // one-cell margin each side
let box_h = 3; // text row + a margin row above and below
if box_w + 1 > area.width {
return;
}
@@ -180,34 +196,25 @@ fn render_badge_box(label: &str, area: Rect, above: Option<Rect>, frame: &mut Fr
area.y + area.height - box_h - 1
}
};
let rect = Rect { x, y, width: box_w, height: box_h };
let block = Block::default()
.borders(Borders::ALL)
.border_type(BorderType::Rounded)
.style(demo_overlay_style());
let para = Paragraph::new(format!(" {label} "))
.style(demo_overlay_style())
.block(block);
frame.render_widget(ratatui::widgets::Clear, rect);
frame.render_widget(para, rect);
fill_overlay_rect(Rect { x, y, width: box_w, height: box_h }, label.to_string(), frame);
}
/// A step-caption box inset one cell from the bottom-right of `area`
/// (ADR-0047 D3/D4): the text word-wrapped to at most 3 lines within a
/// corner-sized width, bold black on yellow. Returns the rect it drew,
/// or `None` if it was too small to place (so the badge can fall back to
/// the bottom-right corner).
/// corner-sized width, bold black on a flat yellow rectangle. Returns
/// the rect it drew, or `None` if it was too small to place (so the
/// badge can fall back to the bottom-right corner).
fn render_caption_box(text: &str, area: Rect, frame: &mut Frame<'_>) -> Option<Rect> {
// Content width capped so the box stays corner-sized; the caption
// wraps to ≤ 3 lines and ellipsises beyond (D4).
let content_w = 40.min(area.width.saturating_sub(6)) as usize;
let content_w = 40.min(area.width.saturating_sub(4)) as usize;
if content_w < 4 {
return None; // output too narrow for a useful caption
}
let lines = clamp_wrapped(text, content_w, 3);
let inner_w = lines.iter().map(|l| l.chars().count()).max().unwrap_or(0);
let box_w = inner_w as u16 + 4; // 2 border + 1 pad each side
let box_h = lines.len() as u16 + 2; // 2 border
let box_w = inner_w as u16 + 2; // one-cell margin each side
let box_h = lines.len() as u16 + 2; // text rows + a margin row above and below
if box_w + 1 > area.width || box_h + 1 > area.height {
return None;
}
@@ -217,18 +224,7 @@ fn render_caption_box(text: &str, area: Rect, frame: &mut Frame<'_>) -> Option<R
width: box_w,
height: box_h,
};
let body = lines
.iter()
.map(|l| format!(" {l}"))
.collect::<Vec<_>>()
.join("\n");
let block = Block::default()
.borders(Borders::ALL)
.border_type(BorderType::Rounded)
.style(demo_overlay_style());
let para = Paragraph::new(body).style(demo_overlay_style()).block(block);
frame.render_widget(ratatui::widgets::Clear, rect);
frame.render_widget(para, rect);
fill_overlay_rect(rect, lines.join("\n"), frame);
Some(rect)
}