diff --git a/crates/ork-server/src/state.rs b/crates/ork-server/src/state.rs index 1942098..eb452a6 100644 --- a/crates/ork-server/src/state.rs +++ b/crates/ork-server/src/state.rs @@ -1126,6 +1126,18 @@ fn section_body_text(content: &str, s: &Section) -> String { continue; } + // Handle drawer end first: when inside a drawer, consume lines until + // :END:. This must precede the drawer-start check below, because + // :END: also matches the generic :NAME: drawer-start pattern and + // would otherwise be misread as opening a new drawer (swallowing the + // body that follows). + if in_drawer { + if trimmed == ":END:" { + in_drawer = false; + } + continue; + } + // Handle drawer start if trimmed.starts_with(':') && trimmed.ends_with(':') && trimmed.len() > 2 { let drawer_name = &trimmed[1..trimmed.len() - 1]; @@ -1135,14 +1147,6 @@ fn section_body_text(content: &str, s: &Section) -> String { } } - // Handle drawer end - if in_drawer { - if trimmed == ":END:" { - in_drawer = false; - } - continue; - } - // This is actual content seen_content = true; result_lines.push(line); @@ -2408,6 +2412,29 @@ mod tests { (dir, state) } + #[test] + fn body_after_drawer_is_not_swallowed() { + // Regression: :END: matched the generic :NAME: drawer-start pattern, + // re-opening a drawer and swallowing the body that followed. A section + // whose only content follows a property drawer must still expose it. + let (_d, state) = state_with( + "#+TITLE: W\n* Task A\n:PROPERTIES:\n:ID: a1\n:END:\nThis is the body of A.\n", + ); + let body = state.section_body("w", "a1").unwrap(); + assert_eq!(body, "This is the body of A."); + } + + #[test] + fn body_after_planning_and_drawer() { + // Planning line + property drawer, then body. All three preamble parts + // are stripped; only the body remains. + let (_d, state) = state_with( + "#+TITLE: W\n* TODO Blocked\nDEADLINE: <2025-01-01>\n:PROPERTIES:\n:BLOCKER: approval\n:END:\nWaiting on external input.\n", + ); + let body = state.section_body("w", "blocked").unwrap(); + assert_eq!(body, "Waiting on external input."); + } + #[test] fn test_delete_section() { let (_d, state) = state_with("#+TITLE: W\n* One\n* Two\n* Three\n");