review(p9-fb-10): 회차 2 지적 반영

- TAGS_COL_W const 추출 (truncate + pad 동시 사용 — drift 방지).
- format_doc_row 직접 unit test 2개 (Hangul title / Hangul tag) 가
  display column 정렬을 정확히 pin. `<title_w$>` 으로 되돌리는
  회귀가 unit-level 에서 catch 됨.
This commit is contained in:
2026-05-03 08:59:25 +00:00
parent 672cce3312
commit 3962be2952

View File

@@ -17,6 +17,11 @@ use ratatui::widgets::{Block, Borders, List, ListItem, ListState, Paragraph};
use crate::app::{App, KeyOutcome, Pane};
use crate::input::{display_width, truncate_to_display_width};
/// Width (in display columns) of the `tags` column in the doc-list
/// row. Used twice — truncate input + pad calculation — so a const
/// keeps them in sync.
const TAGS_COL_W: usize = 12;
/// Internal state owned by `LibraryState`. Public-by-crate so
/// `handle_key_library` can mutate it without crossing the
/// `pub`-visibility boundary `LibraryState` exposes.
@@ -193,7 +198,7 @@ pub(crate) fn format_doc_row(d: &DocSummary, title_w: usize) -> String {
} else {
d.tags.join(",")
};
let tags = truncate_to_display_width(&tags, 12);
let tags = truncate_to_display_width(&tags, TAGS_COL_W);
let updated = d
.updated_at
.format(&time::format_description::well_known::Rfc3339)
@@ -206,7 +211,7 @@ pub(crate) fn format_doc_row(d: &DocSummary, title_w: usize) -> String {
// from `display_width`, then concatenate — the truncate above
// already guarantees `display_width(title) <= title_w`.
let title_pad = title_w.saturating_sub(display_width(&title));
let tags_pad = 12usize.saturating_sub(display_width(&tags));
let tags_pad = TAGS_COL_W.saturating_sub(display_width(&tags));
format!(
"{title}{:title_pad$} {tags}{:tags_pad$} {updated_short:<10} {chunk_count}",
"",
@@ -404,3 +409,56 @@ pub(crate) fn refresh_docs(state: &mut App) -> anyhow::Result<()> {
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use kebab_core::{
ChunkerVersion, DocSummary, DocumentId, Lang, ParserVersion, SourceType, TrustLevel,
WorkspacePath,
};
use time::OffsetDateTime;
fn doc(title: &str, tags: &[&str]) -> DocSummary {
DocSummary {
doc_id: DocumentId("a".repeat(32)),
doc_path: WorkspacePath::new("x.md".into()).unwrap(),
title: title.into(),
lang: Lang("en".into()),
tags: tags.iter().map(|s| (*s).into()).collect(),
trust_level: TrustLevel::Primary,
source_type: SourceType::Note,
byte_len: 1,
chunk_count: 1,
created_at: OffsetDateTime::from_unix_timestamp(1_700_000_000).unwrap(),
updated_at: OffsetDateTime::from_unix_timestamp(1_700_000_000).unwrap(),
parser_version: ParserVersion("p".into()),
chunker_version: ChunkerVersion("c".into()),
}
}
/// p9-fb-10: format_doc_row pads by display width (not char
/// count) so wide-char titles don't shift downstream columns.
/// Regression pin — `<title_w$>` (std::fmt char-count form)
/// would fail this for any Hangul title.
#[test]
fn format_doc_row_pads_by_display_width_for_hangul_title() {
let row = format_doc_row(&doc("러스트로 만드는 KB", &["rust"]), 30);
// Expected layout (display cols):
// title 30 + " "(2) + tags 12 + " "(2) + date 10 + " "(2) + chunk
// chunk = "1" → 1 col. Total = 30+2+12+2+10+2+1 = 59.
assert_eq!(
display_width(&row),
59,
"row must align to display columns, not char count: {row:?}"
);
}
/// p9-fb-10: Hangul tag also pads by display width.
#[test]
fn format_doc_row_pads_by_display_width_for_hangul_tag() {
let row = format_doc_row(&doc("ascii", &["한글"]), 20);
// title 20 + " " + tags 12 + " " + date 10 + " " + "1" = 49
assert_eq!(display_width(&row), 49, "row: {row:?}");
}
}