From 28609d9eb9ddaf6ade70749e1eb9949089d9727c Mon Sep 17 00:00:00 2001 From: altair823 Date: Sun, 3 May 2026 01:26:26 +0000 Subject: [PATCH] =?UTF-8?q?review(p9-fb-07):=20=ED=9A=8C=EC=B0=A8=201=20ni?= =?UTF-8?q?t=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - `derive_title` doc 의 step 5 표현 "kebab-case preserved" → "returned verbatim, no case transformation" (실제 동작과 일치) - `file_stem` NFC 변환 제거 — workspace_path 가 to_posix 단계에서 이미 NFC 정규화되므로 (§6.6) 이중 호출은 군더더기. 의도 명시 주석 추가. - M7 revised 테스트 docstring 의 "p9-fb-07 line 37" 참조를 인용문 ("빈 문자열 반환 금지") 으로 교체 — line number 변동에 안전. Co-Authored-By: Claude Opus 4.7 (1M context) --- crates/kebab-normalize/src/lib.rs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/crates/kebab-normalize/src/lib.rs b/crates/kebab-normalize/src/lib.rs index ae37ae8..4d6b095 100644 --- a/crates/kebab-normalize/src/lib.rs +++ b/crates/kebab-normalize/src/lib.rs @@ -346,7 +346,9 @@ fn flatten_inline(i: &Inline, out: &mut String) { /// 3. First `Heading` block at level 2 with non-blank text. /// 4. First `Paragraph` block (NOT `Quote`, `List`, `Code`, `Table`, /// `ImageRef`, `AudioRef`) with non-blank text — first 80 chars. -/// 5. `file_stem` (filename minus extension, kebab-case preserved). +/// 5. `file_stem` (filename minus extension — returned verbatim, no +/// case transformation; whatever the on-disk filename is becomes +/// the title text). /// /// The chosen string is NFC-normalized so the on-wire title is /// canonically equivalent to the source content. Never returns an @@ -367,9 +369,12 @@ pub fn derive_title(frontmatter_title: &str, blocks: &[Block], file_stem: &str) if let Some(excerpt) = first_paragraph_excerpt(blocks, 80) { return excerpt; } + // `file_stem` originates from `WorkspacePath`, which `to_posix` + // already NFC-normalizes (§6.6). No second NFC pass needed — pass + // through verbatim after a defensive `trim`. let stem = file_stem.trim(); if !stem.is_empty() { - return stem.nfc().collect(); + return stem.to_string(); } "untitled".to_string() } @@ -887,8 +892,8 @@ mod tests { /// M7 (revised by p9-fb-07) — `metadata.user["title"] = ""` lifts /// as an empty string but the new derive_title fallback chain - /// promotes the file stem so the resulting title is non-empty. The - /// spec forbids empty `CanonicalDocument.title` (p9-fb-07 line 37). + /// promotes the file stem so the resulting title is non-empty. + /// spec p9-fb-07: "빈 문자열 반환 금지". #[test] fn title_empty_string_in_user_map_falls_back_to_file_stem() { let asset = fixture_asset(); @@ -906,6 +911,7 @@ mod tests { /// M7 (revised by p9-fb-07) — `metadata.user["title"] = 42` is /// non-stringy and silently drops at the lift stage; derive_title /// then falls back through the chain to the file stem. + /// spec p9-fb-07: "빈 문자열 반환 금지". #[test] fn title_non_string_in_user_map_falls_back_to_file_stem() { let asset = fixture_asset();