core: the body is the checklist, and checklist_items is gone
M304 steps 2 and the client half of 4, together — they cannot be separated. A
commit where the store writes items into the body while push.rs still reads them
from a table is one that silently pushes the wrong list, and dev publishes to the
dev channel on every green build.
Store:
* load_items becomes items_of(body) — a parse, not a query. An item's id is its
ORDINAL, which is all it ever amounted to: push.rs sent text and checked and
never an id, and both sides replaced the whole list on every sync.
* add_item / update_item / delete_item route through update_note, so they get
revision snapshotting, #tag re-derivation and the dirty/updated_at bookkeeping
without any of it being written a second time.
* create_note folds its items: input into the body, and syncs tags from the
FOLDED body — an item can carry a #tag too.
* display_title no longer takes items, because items ARE body lines now. It
strips the task marker instead: a list-only note is still named by its first
item, and calling that note "- [ ] milk" would show someone the storage.
Wire: items leave it. A second copy of data already in the body field of the same
message is how the two come to disagree. CLIENT_PROTOCOL_VERSION and
MIN_SERVER_PROTOCOL_VERSION go to 3, which is what makes this safe to land before
the server: a v3 client refuses a v2 server outright rather than pushing a body
whose list the old _apply_note_items would then delete.
Schema v8 folds every existing row into its note's body before dropping the
table. Written in Rust, not SQL: the fold has to produce exactly what
derive::append_item produces, and group_concat only gained a guaranteed ORDER BY
in SQLite 3.44 — a checklist that quietly reordered itself during a migration
would be a poor way to learn that. updated_at and dirty are deliberately left
alone, because the server's migration folds the same rows the same way and both
sides land on identical bodies; marking every note dirty would push a body the
server already has, from every device at once.
NOT deployable yet. The server still speaks v2 and still has note_items, so a
client built from this will refuse to sync until the server half lands.
This commit is contained in:
@@ -79,8 +79,6 @@ pub struct Change {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub position: Option<i64>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub items: Option<Vec<ItemOut>>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub label_ids: Option<Vec<String>>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub created_at: Option<String>,
|
||||
@@ -103,7 +101,6 @@ impl Change {
|
||||
remind_at: None,
|
||||
recurrence: None,
|
||||
position: None,
|
||||
items: None,
|
||||
label_ids: None,
|
||||
created_at: None,
|
||||
name: None,
|
||||
@@ -111,12 +108,6 @@ impl Change {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct ItemOut {
|
||||
pub text: String,
|
||||
pub checked: bool,
|
||||
}
|
||||
|
||||
// --- incoming results --------------------------------------------------------
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
@@ -201,7 +192,6 @@ fn collect_labels(conn: &Connection, out: &mut Vec<Change>, limit: usize) -> rus
|
||||
remind_at: None,
|
||||
recurrence: None,
|
||||
position: None,
|
||||
items: None,
|
||||
label_ids: None,
|
||||
created_at: None,
|
||||
})
|
||||
@@ -267,19 +257,6 @@ fn note_row(conn: &Connection, id: &str) -> rusqlite::Result<NoteRow> {
|
||||
fn note_change(conn: &Connection, id: &str) -> rusqlite::Result<Change> {
|
||||
let row = note_row(conn, id)?;
|
||||
|
||||
let items = {
|
||||
let mut stmt = conn.prepare(
|
||||
"SELECT text, checked FROM checklist_items WHERE note_id = ?1 ORDER BY position",
|
||||
)?;
|
||||
let rows = stmt.query_map(params![id], |r| {
|
||||
Ok(ItemOut {
|
||||
text: r.get(0)?,
|
||||
checked: r.get::<_, i64>(1)? != 0,
|
||||
})
|
||||
})?;
|
||||
rows.collect::<rusqlite::Result<Vec<ItemOut>>>()?
|
||||
};
|
||||
|
||||
// MANUAL memberships only. Tag-sourced ones (`via_tag = 1`) are re-derived by the
|
||||
// server from the body; sending them as label_ids would convert them into manual
|
||||
// assignments that no longer disappear when the #tag is removed from the text.
|
||||
@@ -305,7 +282,6 @@ fn note_change(conn: &Connection, id: &str) -> rusqlite::Result<Change> {
|
||||
remind_at: row.remind_at,
|
||||
recurrence: row.recurrence,
|
||||
position: Some(row.position),
|
||||
items: Some(items),
|
||||
label_ids: Some(label_ids),
|
||||
created_at: Some(row.created_at),
|
||||
name: None,
|
||||
|
||||
Reference in New Issue
Block a user