From 229076c82d81a52298433461f06be9c261662e14 Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Sat, 22 Aug 2026 12:45:14 -0400 Subject: [PATCH] sync: stop deleting a note's checklist items because it isn't a "list" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `_apply_note_items` didn't ignore items on a non-list note — it deleted them. That was survivable only because nothing in the product could produce a note holding both a body and items. M13 makes exactly that the normal shape: a checklist is something a note HAS, not something a note IS. Against that shape this guard is a data-loss path — the first sync after adding a checklist to a note would wipe it. Landing it before the UI that can create the state, so there is never a window where the two disagree. `kind` itself, and the rest of the merge, follow. --- src/thoughtsync/sync.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/thoughtsync/sync.py b/src/thoughtsync/sync.py index 30a2fcd..5b09756 100644 --- a/src/thoughtsync/sync.py +++ b/src/thoughtsync/sync.py @@ -209,12 +209,22 @@ def _assign_note_fields(note: Note, ch: dict) -> None: async def _apply_note_items(db, note: Note, ch: dict) -> None: - """Replace the note's checklist items with the client's (items sync inline).""" - if note.kind != "list": - await db.execute(sa_delete(NoteItem).where(NoteItem.note_id == note.id)) - return + """Replace the note's checklist items with the client's (items sync inline). + + Applies to ANY note. This used to delete every item when the note wasn't + `kind == "list"`, which was survivable only because nothing could produce a note + holding both a body and items. M13 makes that the normal shape — a checklist is + something a note HAS, not something a note IS — and against that shape the old + guard was a data-loss path: the first sync after adding a checklist to a note + would have wiped it. + + Removed ahead of the UI that can create the state, deliberately, so there is no + window in which the two disagree. + """ items = ch.get("items") if not isinstance(items, list): + # Absent means "not telling us", not "empty". A client that omits the key + # leaves what the server has; only an explicit [] clears it. return await db.execute(sa_delete(NoteItem).where(NoteItem.note_id == note.id)) for pos, it in enumerate(items):