core: notes show their real created and edited times, and desktop search works
CI & Build / Build now, or wait for Android? (push) Canceled after 0s
CI & Build / TypeScript typecheck (push) Canceled after 0s
CI & Build / Python lint (push) Canceled after 0s
CI & Build / Python tests (push) Canceled after 0s
CI & Build / integration (push) Canceled after 0s
CI & Build / Build & push image (push) Canceled after 0s
Android / Build, or is the channel already serving this? (push) Successful in 2s
Desktop (Tauri) / Build, or is the channel already serving this? (push) Successful in 2s
Desktop (Tauri) / Windows installer (cross-compiled) (push) Successful in 4m26s
Desktop (Tauri) / Tauri desktop (Linux) (push) Successful in 7m41s
Desktop (Tauri) / Update manifest (push) Successful in 5s
Android / Kotlin + Rust (APK) (push) Successful in 10m41s

Both bugs were caught red by the tests in 732fd7a (run 8418: 5 failed, 147
passed) before this fix.

- load_note reads columns by NAME. Dropping `color` (fa89da1) shifted every
  column after it and the two timestamps were missed, so created_at showed the
  last edit and updated_at showed the trash time — null on any live note. Only
  the read was wrong; nothing stored is, so no data needs repairing.
- The board's text facet binds its pattern once for its one placeholder. It
  pushed it twice after the title column went (95aa10c), and rusqlite refused
  every query with InvalidParameterCount — every desktop search failed.
  Android searches through store::search and was never affected.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-06 19:42:51 -04:00
co-authored by Claude Opus 5.5
parent 732fd7a827
commit be200641cd
+23 -20
View File
@@ -144,25 +144,28 @@ fn load_note(conn: &Connection, id: &str) -> rusqlite::Result<Note> {
"SELECT id, body, position, pinned, archived, trashed, remind_at, recurrence, created_at, updated_at, trashed_at
FROM notes WHERE id = ?1",
[id],
// By NAME, not by position. Dropping `color` shifted every column after it
// and the two timestamps were missed (fa89da1): created_at read updated_at
// and updated_at read trashed_at, with nothing failing. A name either
// resolves or errors.
|r| {
let body: String = r.get(1)?;
Ok(Note {
id: r.get(0)?,
display_title: String::new(), // filled below — it may need a query
body,
position: r.get(2)?,
pinned: r.get(3)?,
archived: r.get(4)?,
trashed: r.get(5)?,
deleted_at: r.get(10)?,
remind_at: r.get(6)?,
recurrence: r.get(7)?,
id: r.get("id")?,
display_title: String::new(), // derived below, from the body
body: r.get("body")?,
position: r.get("position")?,
pinned: r.get("pinned")?,
archived: r.get("archived")?,
trashed: r.get("trashed")?,
deleted_at: r.get("trashed_at")?,
remind_at: r.get("remind_at")?,
recurrence: r.get("recurrence")?,
labels: Vec::new(),
items: Vec::new(),
attachments: Vec::new(),
previews: Vec::new(),
created_at: r.get(9)?,
updated_at: r.get(10)?,
created_at: r.get("created_at")?,
updated_at: r.get("updated_at")?,
})
},
)?;
@@ -321,10 +324,11 @@ pub fn list_notes(conn: &Connection, q: &ListQuery) -> rusqlite::Result<Vec<Note
if let Some(f) = &q.facets {
if let Some(text) = f.q.as_deref().filter(|s| !s.is_empty()) {
// One placeholder, one bind. This pushed the pattern twice after the
// title column went (95aa10c), and rusqlite refuses a count mismatch,
// so every desktop search failed.
sql.push_str(" AND body LIKE ? ESCAPE '\\'");
let pat = format!("%{}%", escape_like(text));
binds.push(pat.clone());
binds.push(pat);
binds.push(format!("%{}%", escape_like(text)));
}
if f.has_reminder == Some(true) {
sql.push_str(" AND remind_at IS NOT NULL");
@@ -371,10 +375,9 @@ pub fn reminders(conn: &Connection) -> rusqlite::Result<Vec<Note>> {
}
pub fn titles(conn: &Connection) -> rusqlite::Result<Vec<TitleEntry>> {
// Names come from `load_note` rather than from a bare row, because a note whose
// body is empty is named by its first checklist item — which a row here doesn't
// have. The command palette reads this; correctness beats one query per note at
// personal scale.
// Names come from `load_note` so the palette and the card can never disagree
// about what a note is called. The command palette reads this; correctness
// beats one query per note at personal scale.
let ids: Vec<String> = {
let mut stmt = conn.prepare("SELECT id FROM notes WHERE trashed = 0")?;
let rows = stmt.query_map([], |r| r.get(0))?;