perf: truncate content_text preview in history/search queries

GetHistory and SearchHistory selected the full content_text column for
every row, so a page of large text items serialized up to megabytes of
JSON over D-Bus and parsed it on the GJS main loop, only to render a
~140-char snippet. The client-side cut never bounded the wire cost.

Truncate to PREVIEW_CHARS (200) with substr() in SQL on both list paths.
Full content is still served on demand by GetItemContent for paste-back.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Eduard Tolosa 2026-05-26 13:08:43 -05:00
parent ab230856a4
commit a4a6f1a3f9
2 changed files with 20 additions and 10 deletions

View file

@ -4,6 +4,13 @@ use serde::{Deserialize, Serialize};
use std::path::Path;
use std::sync::{Mutex, MutexGuard};
/// Characters of `content_text` returned as a display preview by the paginated
/// and search list queries. The full payload is never sent over these paths --
/// it is fetched on demand via `get_raw_item` / `GetItemContent` for paste-back.
/// Keeping the trim in SQL means a page of large text items costs a few KB of
/// JSON, not megabytes, and the work stays in the daemon.
const PREVIEW_CHARS: usize = 200;
/// Metadata for a clipboard entry - does NOT include thumbnail bytes.
/// Thumbnails are fetched separately via `get_thumbnail` for lazy loading.
#[derive(Debug, Clone, Serialize, Deserialize)]
@ -174,13 +181,14 @@ impl Db {
return Ok(Vec::new());
}
let mut stmt = conn.prepare(
"SELECT id, mime_type, content_text, source_app, created_at,
let sql = format!(
"SELECT id, mime_type, substr(content_text, 1, {PREVIEW_CHARS}), source_app, created_at,
thumbnail_blob IS NOT NULL AS has_thumb
FROM clipboard_history
ORDER BY created_at DESC
LIMIT ?1 OFFSET ?2",
)?;
LIMIT ?1 OFFSET ?2"
);
let mut stmt = conn.prepare(&sql)?;
let items = stmt
.query_map(params![effective_limit as i64, offset as i64], |row| {
@ -220,14 +228,15 @@ impl Db {
}
let fts_query = tokens.join(" ");
let mut stmt = conn.prepare(
"SELECT h.id, h.mime_type, h.content_text, h.source_app, h.created_at,
let sql = format!(
"SELECT h.id, h.mime_type, substr(h.content_text, 1, {PREVIEW_CHARS}), h.source_app, h.created_at,
h.thumbnail_blob IS NOT NULL AS has_thumb
FROM clipboard_history h
WHERE h.rowid IN (SELECT rowid FROM clipboard_fts WHERE clipboard_fts MATCH ?1)
ORDER BY h.created_at DESC
LIMIT ?2",
)?;
LIMIT ?2"
);
let mut stmt = conn.prepare(&sql)?;
let items = stmt
.query_map(params![fts_query, limit as i64], |row| {

View file

@ -69,8 +69,9 @@ impl StrataManager {
})
.map_err(|e| zbus::fdo::Error::Failed(e.to_string()))
}
/// Return a page of recent items as JSON metadata (no inline thumbnail bytes).
/// Clients call GetThumbnail(id) lazily for items with has_thumbnail=true.
/// Return a page of recent items as JSON metadata (no inline thumbnail bytes,
/// content_text truncated to a preview). Clients call GetThumbnail(id) lazily
/// for items with has_thumbnail=true and GetItemContent(id) for full content.
/// `offset` is from the most recent item (0 = newest).
async fn get_history(&self, offset: u32, limit: u32) -> zbus::fdo::Result<String> {
let db = self.db.clone();