list: send snippets, not whole bodies
Every list row carried its message's entire body and let CSS clip it
to one line, so each page shipped every listed body: 11.4 MB of HTML
for 3,000 messages with ~4 KB bodies. Rows now get the start of what
the message says — quote split off, whitespace collapsed, 160
characters — and the same page is 1.0 MB.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
diff --git a/src/body.rs b/src/body.rs
index d906f8a..c0efcbc 100644
--- a/src/body.rs
+++ b/src/body.rs
@@ -1,6 +1,6 @@
//! Message bodies as the thread view shows them: plain text, escaped,
//! with its URLs made into links and the quoted history it trails split
-//! off to hide.
+//! off to hide — and the one-line snippet a list row shows.
use maud::{Markup, html};
@@ -96,6 +96,21 @@ pub fn linked(text: &str) -> Markup {
}
}
+/// How much of a body a list row carries: more than a row ever shows, so
+/// CSS still does the clipping, without shipping the whole body.
+const SNIPPET: usize = 160;
+
+/// The start of what a body says, on one line, for a list row.
+pub fn snippet(text: &str) -> String {
+ let (said, _) = split_quoted(text);
+ said.split_whitespace()
+ .flat_map(|word| [" ", word])
+ .skip(1)
+ .flat_map(str::chars)
+ .take(SNIPPET)
+ .collect()
+}
+
/// Splits a reply into what it says and the quoted history it trails:
/// from an Outlook-style separator ("-----Original Message-----", or a
/// rule over a "From:" line) to the end, or else a closing run of `>`
@@ -311,6 +326,19 @@ mod tests {
assert_eq!(split_quoted(rule), (rule, None));
}
+ #[test]
+ fn snippets_are_one_capped_line_without_the_quote() {
+ assert_eq!(
+ snippet(
+ " Sounds\tgood.\n\nSee you\r\nthen.\n\nOn Mon, Oct 5, Sam wrote:\n> Thursday?"
+ ),
+ "Sounds good. See you then."
+ );
+ let long = "é".repeat(500);
+ assert_eq!(snippet(&long).chars().count(), SNIPPET);
+ assert_eq!(snippet(""), "");
+ }
+
#[test]
fn inline_and_all_quote_bodies_stay_whole() {
// Replies between quoted lines need them for context.
diff --git a/src/views.rs b/src/views.rs
index 992b09f..be8589c 100644
--- a/src/views.rs
+++ b/src/views.rs
@@ -260,7 +260,7 @@ fn solo(p: &Page<'_>, group: &Group, m: &Message, compact: bool) -> Markup {
span.line {
span.from { (sender(&p.users, m)) }
@if !compact {
- span.snip { " — " (m.body) }
+ span.snip { " — " (body::snippet(&m.body)) }
}
}
}
@@ -287,7 +287,7 @@ fn row(p: &Page<'_>, m: &Message, compact: bool) -> Markup {
span.from { (sender(&p.users, m)) }
span.type-figure.age { (dates::short(p.now, m.at)) }
@if !compact {
- span.snip { (m.body) }
+ span.snip { (body::snippet(&m.body)) }
}
}
}