Skip to main content

max / goingson

perf(ui): stream task pages and lazy-load truncated email bodies Close the two ultra-fuzz Run #26 Performance items (silent truncation): - Task list no longer caps at 500 rows. VirtualScroller gains an onNeedMore infinite-scroll hook; tasks.js streams 200-row pages via the existing list_tasks_filtered offset/limit. Count chip reads "X of N" until fully loaded. - JMAP bodies over 100KB are no longer silently truncated. Migration 052 adds jmap_id + body_truncated; sync captures BodyValue.isTruncated; a new fetch_email_full_body command (shared all_commands! macro) re-fetches the full body on demand, surfaced via a "Load full message" affordance in the reader. JMAP client construction extracted to a shared build_jmap_client. Gate: cargo test --workspace green, node --check clean on touched JS.
Co-Authored-By
Claude Opus 4.8 <noreply@anthropic.com>
Author: Max Johnson <me@maxj.phd> · 2026-06-16 21:59 UTC
Signed with PGP, not checked
Commit: 67894afbf48a2795756b394eba4b4bac7d510901
Parent: 2aba1ec
15 files changed, +307 insertions, -45 deletions
@@ -110,6 +110,7 @@
110 110 $crate::commands::list_emails,
111 111 $crate::commands::list_emails_threaded,
112 112 $crate::commands::get_email,
113 + $crate::commands::fetch_email_full_body,
113 114 $crate::commands::open_email_in_browser,
114 115 $crate::commands::create_email,
115 116 $crate::commands::send_email,
@@ -32,6 +32,10 @@
32 32 pub imap_uid: Option<i64>,
33 33 pub is_archived: bool,
34 34 pub attachment_meta: Option<String>,
35 + /// Whether the body was truncated at fetch (JMAP >100KB cap).
36 + pub body_truncated: bool,
37 + /// Provider email id (JMAP), used to re-fetch a truncated body. `None` for IMAP.
38 + pub jmap_id: Option<String>,
35 39 }
36 40
37 41 /// Result of processing a batch of fetched emails.
@@ -126,6 +130,8 @@
126 130 email_account_id: Some(account_id),
127 131 is_outgoing: false,
128 132 attachment_meta: email.attachment_meta,
133 + body_truncated: email.body_truncated,
134 + jmap_id: email.jmap_id,
129 135 });
130 136 }
131 137
@@ -173,6 +179,8 @@
173 179 imap_uid: Some(42),
174 180 is_archived: false,
175 181 attachment_meta: None,
182 + body_truncated: false,
183 + jmap_id: None,
176 184 };
177 185 assert_eq!(email.from, "sender@example.com");
178 186 assert_eq!(email.imap_uid, Some(42));
@@ -337,6 +337,10 @@
337 337 /// Retrieves an email by ID.
338 338 async fn get_by_id(&self, id: EmailId, user_id: UserId) -> Result<Option<Email>>;
339 339
340 + /// Replaces an email's body with a full (re-fetched) version and clears the
341 + /// `body_truncated` flag. Used to lazily load a body truncated at sync.
342 + async fn set_full_body(&self, id: EmailId, user_id: UserId, body: &str) -> Result<()>;
343 +
340 344 /// Creates a new email record.
341 345 async fn create(&self, user_id: UserId, email: NewEmail) -> Result<Email>;
342 346
@@ -47,7 +47,7 @@
47 47 sqlx::query("PRAGMA foreign_keys = ON")
48 48 .execute(pool)
49 49 .await
50 - .map_err(|e| sqlx::migrate::MigrateError::Execute(e))?;
50 + .map_err(sqlx::migrate::MigrateError::Execute)?;
51 51
52 52 Ok(())
53 53 }