feat(kebab-tui): p9-fb-24 task 5 — Library column header row
Wire `format_doc_header` into `render_doc_list`: render the block independently, split block_inner into a 1-row header + list via vertical Layout, and drop the `.block(block)` from the List widget. Remove `#[allow(dead_code)]` from `format_doc_header` now that it is consumed. Add `library_renders_column_header_row` integration test. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -199,13 +199,29 @@ fn render_doc_list(f: &mut Frame, area: Rect, state: &App) {
|
||||
"Library"
|
||||
};
|
||||
let block = Block::default().title(header_text).borders(Borders::ALL);
|
||||
let block_inner = block.inner(area);
|
||||
f.render_widget(block, area);
|
||||
|
||||
if inner.docs.is_empty() {
|
||||
f.render_widget(block, area);
|
||||
return;
|
||||
}
|
||||
|
||||
let title_w = (area.width as usize).saturating_sub(40).max(20);
|
||||
// p9-fb-24: split the inner area into a 1-row column header on top
|
||||
// and the doc list below. Header reuses the same width math as
|
||||
// `format_doc_row` so labels line up with their data columns.
|
||||
let layout = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.constraints([Constraint::Length(1), Constraint::Min(0)])
|
||||
.split(block_inner);
|
||||
let header_area = layout[0];
|
||||
let list_area = layout[1];
|
||||
|
||||
let title_w = (list_area.width as usize).saturating_sub(40).max(20);
|
||||
|
||||
let header_para = Paragraph::new(format_doc_header(title_w))
|
||||
.style(state.theme.style(crate::theme::Role::Heading));
|
||||
f.render_widget(header_para, header_area);
|
||||
|
||||
let items: Vec<ListItem> = inner
|
||||
.docs
|
||||
.iter()
|
||||
@@ -213,12 +229,11 @@ fn render_doc_list(f: &mut Frame, area: Rect, state: &App) {
|
||||
.collect();
|
||||
|
||||
let list = List::new(items)
|
||||
.block(block)
|
||||
.highlight_style(state.theme.style(crate::theme::Role::Selected))
|
||||
.highlight_symbol("> ");
|
||||
|
||||
let mut list_state = inner.list_state.clone();
|
||||
f.render_stateful_widget(list, area, &mut list_state);
|
||||
f.render_stateful_widget(list, list_area, &mut list_state);
|
||||
}
|
||||
|
||||
/// p9-fb-24: render the column-label row that sits directly above
|
||||
@@ -229,9 +244,6 @@ fn render_doc_list(f: &mut Frame, area: Rect, state: &App) {
|
||||
/// Layout: `TITLE<title_pad> TAGS<tags_pad> UPDATED CHUNKS`.
|
||||
/// The title column width matches `area.width.saturating_sub(40).max(20)`
|
||||
/// — the same calculation `render_doc_list` uses for `title_w`.
|
||||
///
|
||||
/// Task 5 wires it into render_doc_list.
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn format_doc_header(title_w: usize) -> Line<'static> {
|
||||
let title_label = "TITLE";
|
||||
let tags_label = "TAGS";
|
||||
|
||||
@@ -286,6 +286,52 @@ fn filter_overlay_render_places_cursor_on_focused_field() {
|
||||
);
|
||||
}
|
||||
|
||||
/// p9-fb-24: rendered Library pane shows the column header row above
|
||||
/// the data rows. Header is in `Role::Heading` style; data rows in
|
||||
/// the `Role::Body` / `Role::Selected` defaults.
|
||||
#[test]
|
||||
fn library_renders_column_header_row() {
|
||||
let docs = vec![
|
||||
make_doc("notes/alpha.md", "doc-alpha", vec!["rust"]),
|
||||
make_doc("notes/beta.md", "doc-beta", vec!["docs"]),
|
||||
make_doc("notes/gamma.md", "doc-gamma", vec![]),
|
||||
];
|
||||
let app = app_with_docs(docs);
|
||||
let backend = TestBackend::new(80, 20);
|
||||
let mut terminal = Terminal::new(backend).unwrap();
|
||||
terminal
|
||||
.draw(|f| {
|
||||
let area = Rect::new(0, 0, 80, 20);
|
||||
render_library(f, area, &app);
|
||||
})
|
||||
.unwrap();
|
||||
let buffer = terminal.backend().buffer().clone();
|
||||
let rendered: String = (0..buffer.area.height)
|
||||
.map(|y| {
|
||||
(0..buffer.area.width)
|
||||
.map(|x| buffer[(x, y)].symbol())
|
||||
.collect::<String>()
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n");
|
||||
assert!(
|
||||
rendered.contains("TITLE")
|
||||
&& rendered.contains("TAGS")
|
||||
&& rendered.contains("UPDATED")
|
||||
&& rendered.contains("CHUNKS"),
|
||||
"header row labels not visible in:\n{rendered}"
|
||||
);
|
||||
let title_line_idx = rendered
|
||||
.lines()
|
||||
.position(|line| line.contains("TITLE"))
|
||||
.expect("TITLE header should be present");
|
||||
let lines_after = rendered.lines().skip(title_line_idx + 1).collect::<Vec<_>>();
|
||||
assert!(
|
||||
lines_after.iter().any(|line| line.contains("doc-")),
|
||||
"no data rows after header:\n{rendered}"
|
||||
);
|
||||
}
|
||||
|
||||
/// p9-fb-10: Library renders Hangul / CJK titles without overflowing
|
||||
/// the title column. Smoke pin — render with a mixed Korean fixture
|
||||
/// and confirm no panic + the truncated width fits the column.
|
||||
|
||||
Reference in New Issue
Block a user