all: delete the code nothing calls
CI & Build / Python lint (push) Successful in 2s
CI & Build / Build now, or wait for Android? (push) Successful in 2s
Android / Build, or is the channel already serving this? (push) Successful in 3s
Desktop (Tauri) / Build, or is the channel already serving this? (push) Successful in 2s
CI & Build / Web typecheck and unit tests (push) Successful in 8s
CI & Build / Python tests (push) Successful in 10s
CI & Build / integration (push) Failing after 1m21s
CI & Build / Build & push image (push) Skipped
Desktop (Tauri) / Web tests, clippy, Rust tests and rustfmt (push) Successful in 2m47s
Desktop (Tauri) / Windows installer (cross-compiled) (push) Successful in 2m48s
Desktop (Tauri) / Tauri desktop (Linux) (push) Successful in 3m39s
Desktop (Tauri) / Update manifest (push) Successful in 3s
Android / Kotlin + Rust (APK) (push) Successful in 8m37s
CI & Build / Python lint (push) Successful in 2s
CI & Build / Build now, or wait for Android? (push) Successful in 2s
Android / Build, or is the channel already serving this? (push) Successful in 3s
Desktop (Tauri) / Build, or is the channel already serving this? (push) Successful in 2s
CI & Build / Web typecheck and unit tests (push) Successful in 8s
CI & Build / Python tests (push) Successful in 10s
CI & Build / integration (push) Failing after 1m21s
CI & Build / Build & push image (push) Skipped
Desktop (Tauri) / Web tests, clippy, Rust tests and rustfmt (push) Successful in 2m47s
Desktop (Tauri) / Windows installer (cross-compiled) (push) Successful in 2m48s
Desktop (Tauri) / Tauri desktop (Linux) (push) Successful in 3m39s
Desktop (Tauri) / Update manifest (push) Successful in 3s
Android / Kotlin + Rust (APK) (push) Successful in 8m37s
From the audit (#5178). Each was unreachable from every client: - Checklist add-item and delete-item: REST POST /items and DELETE /items/<id>, the Tauri commands, the store, rest and local adapters, the core's add_item/delete_item, set_item_text and remove_item, and the FFI exports. Adding, rewording and removing an item are body edits in every editor. The checked toggle stays, and its rewriter is simpler without the drop branch. - Manual unfurl: POST /unfurl and its adapters. Previews arrive in the background after a save (unfurl_queue). - The /api/config `android_client` key, android_release() and the APK_NAME/MANIFEST_NAME aliases. Phones poll /api/client/android. - users.email_verified and users.avatar_path (migration 0037). Nothing set the first or read the second; the SMTP reset never checked verification. - derive::extract_tags (only tests used it; the shared fixture now runs through extract_tag_spans), the unused check and link icons, and the unused editor_add_item string. - The blob scheme is renamed tsblob -> inkblob. URLs are built as notes are read, so nothing stored carries the old one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+10
-26
@@ -756,14 +756,13 @@ pub fn set_labels(conn: &Connection, id: &str, label_ids: &[String]) -> rusqlite
|
||||
load_note(conn, id)
|
||||
}
|
||||
|
||||
// ---- checklist items: every one of these is a body edit ---------------------
|
||||
// ---- checklist items: a tick is a body edit ----------------------------------
|
||||
//
|
||||
// They keep their own names and signatures because the FFI, the Tauri commands and
|
||||
// the REST shape all speak in items, and a checklist is still a thing a note HAS.
|
||||
// What changed is where it is kept. Routing all three through `update_note` rather
|
||||
// than writing the body directly is what gives them revision snapshotting, `#tag`
|
||||
// re-derivation and the dirty/updated_at bookkeeping without any of it being
|
||||
// written a second time here.
|
||||
// It keeps the item's name and signature because the FFI, the Tauri commands and the
|
||||
// REST shape all speak in items, and a checklist is still a thing a note HAS. Routing
|
||||
// it through `update_note` rather than writing the body directly is what gives it
|
||||
// revision snapshotting, `#tag` re-derivation and the dirty/updated_at bookkeeping
|
||||
// without any of it being written a second time here.
|
||||
|
||||
fn note_body(conn: &Connection, id: &str) -> rusqlite::Result<String> {
|
||||
conn.query_row("SELECT body FROM notes WHERE id = ?1", [id], |r| r.get(0))
|
||||
@@ -779,11 +778,8 @@ fn set_body(conn: &Connection, id: &str, body: String) -> rusqlite::Result<Note>
|
||||
update_note(conn, id, &json!({ "body": body }))
|
||||
}
|
||||
|
||||
pub fn add_item(conn: &Connection, id: &str, text: &str) -> rusqlite::Result<Note> {
|
||||
let body = note_body(conn, id)?;
|
||||
set_body(conn, id, derive::append_item(&body, text, false))
|
||||
}
|
||||
|
||||
/// Tick or untick one item. The only item change that isn't an edit to the body's
|
||||
/// text: adding, rewording and removing an item happen in the editor.
|
||||
pub fn update_item(
|
||||
conn: &Connection,
|
||||
id: &str,
|
||||
@@ -795,24 +791,12 @@ pub fn update_item(
|
||||
None => return load_note(conn, id),
|
||||
};
|
||||
let mut body = note_body(conn, id)?;
|
||||
if let Some(text) = changes.get("text").and_then(Value::as_str) {
|
||||
body = derive::set_item_text(&body, index, text);
|
||||
}
|
||||
if let Some(checked) = changes.get("checked").and_then(Value::as_bool) {
|
||||
body = derive::set_item_checked(&body, index, checked);
|
||||
}
|
||||
set_body(conn, id, body)
|
||||
}
|
||||
|
||||
pub fn delete_item(conn: &Connection, id: &str, item_id: &str) -> rusqlite::Result<Note> {
|
||||
let index = match item_index(item_id) {
|
||||
Some(i) => i,
|
||||
None => return load_note(conn, id),
|
||||
};
|
||||
let body = note_body(conn, id)?;
|
||||
set_body(conn, id, derive::remove_item(&body, index))
|
||||
}
|
||||
|
||||
/// The stored form of a declared content type: the bare media type, lowercase.
|
||||
/// Matches the server's `normalize_mime`, so both sides file a type the same way.
|
||||
fn normalize_mime(raw: &str) -> String {
|
||||
@@ -1355,7 +1339,7 @@ mod tests {
|
||||
.expect("refused");
|
||||
// The words the person sees, exactly: the refusal prints as its message.
|
||||
assert_eq!(refused.to_string(), VIEW_ONLY);
|
||||
assert!(add_item(&conn, "n", "eggs").is_err());
|
||||
assert!(update_item(&conn, "n", "0", &json!({ "checked": true })).is_err());
|
||||
assert!(trash(&conn, "n").is_err());
|
||||
assert!(snooze_reminder(&conn, "n", 10).is_err());
|
||||
assert!(set_labels(&conn, "n", &[]).is_err());
|
||||
@@ -1374,7 +1358,7 @@ mod tests {
|
||||
assert!(edited.labels.is_empty());
|
||||
assert_eq!(edited.body, "their words, edited\n#mine");
|
||||
assert!(dirty(&conn, "n"));
|
||||
add_item(&conn, "n", "eggs").expect("an item is text");
|
||||
update_item(&conn, "n", "0", &json!({ "checked": true })).expect("a tick is text");
|
||||
|
||||
let reminded = update_note(
|
||||
&conn,
|
||||
|
||||
Reference in New Issue
Block a user