A note's name is one rule, pinned by the shared fixture
The server and the core each derived display_title and disagreed twice: the server cut it at 200 characters and the core didn't, and the server split lines with splitlines(), which also breaks on a lone \r or a U+2028, where the core and every other reading of the grammar split on \n alone. - grammar.json gains a display_titles section: blank lines, markers, an empty item, \r\n, a lone \r, U+2028, and a 201-character line of 'é' (the cut is characters, not bytes). - derive::display_title and DISPLAY_TITLE_CAP are the core's half, moved next to strip_marker. The server splits on "\n". Both suites run the cases. Behaviour: a device now names a note with a first line over 200 characters the way the web always has, and the server names a note containing a lone \r or a U+2028 the way devices always have. Fixes #5398. DRY pass #2, batch 3 (#5372). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -341,6 +341,32 @@ fn render_task_line(indent: &str, bullet: char, checked: bool, text: &str) -> St
|
||||
}
|
||||
}
|
||||
|
||||
/// The longest a note's name runs, in characters (not bytes): the server's
|
||||
/// `DISPLAY_TITLE_CAP`.
|
||||
pub const DISPLAY_TITLE_CAP: usize = 200;
|
||||
|
||||
/// The note's NAME: the first line of its body that says anything, cut to
|
||||
/// [`DISPLAY_TITLE_CAP`] characters.
|
||||
///
|
||||
/// Mirrors `derive_display_title` in the server's notes/helpers.py — one rule written
|
||||
/// twice, and they have to agree or a synced note is called different things on either
|
||||
/// side of the wire. Both run the `display_titles` cases in testdata/grammar.json.
|
||||
///
|
||||
/// It no longer needs the items, because the items ARE lines of the body now (M304).
|
||||
/// What it needs instead is to strip the task marker off: a list-only note is still
|
||||
/// named by its first item, and calling that note "- [ ] milk" would be showing
|
||||
/// someone the storage rather than the note. An empty item is skipped rather than
|
||||
/// naming the note "", which is what a half-typed list would otherwise do.
|
||||
pub fn display_title(body: &str) -> String {
|
||||
for line in body.split('\n') {
|
||||
let text = strip_marker(line.trim()).trim();
|
||||
if !text.is_empty() {
|
||||
return text.chars().take(DISPLAY_TITLE_CAP).collect();
|
||||
}
|
||||
}
|
||||
String::new()
|
||||
}
|
||||
|
||||
/// The text of a line with its task marker removed, or the line as it was.
|
||||
///
|
||||
/// For naming a note: a list-only note is named by its first item, and calling one
|
||||
@@ -759,6 +785,15 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fixture_display_titles() {
|
||||
for case in fixture()["display_titles"].as_array().unwrap() {
|
||||
let body = case["body"].as_str().unwrap();
|
||||
let want = case["title"].as_str().unwrap();
|
||||
assert_eq!(display_title(body), want, "body {body:?}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fixture_lifts() {
|
||||
for case in fixture()["lifts"].as_array().unwrap() {
|
||||
|
||||
Reference in New Issue
Block a user