From 44b3bcb2b278d02cd6d70659b049980566d2d83f Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Wed, 26 Aug 2026 07:44:31 -0400 Subject: [PATCH] Correct a claim about the operator's data, and the first-row delete MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two things, one of which I got wrong in a place that outlives the session. **The claim.** Migration 0027's docstring said "The Google Keep import is genuine content on this instance, not fixtures." That is not true and I had no basis for it. Note 2916's headline is the opposite — "there is no work that anyone has done that isn't test data" — and its clause about imports is CONDITIONAL: text arriving from another app would be real, and any import path has to treat it that way. I read a rule about how import code must behave as a fact about what is in the database, then repeated it in a migration that will be read long after anyone remembers this week. The operator has never run the importer. They did not know it existed. Nothing about the migration changes. Content-preserving was cheap and is right for anything that rewrites somebody's text — and it is what the rule will demand the day an import does happen. Only the reason recorded in the file was wrong, and a wrong reason in a migration is how a later decision gets made on a false premise. **The delete.** Removing the FIRST checklist row asked to focus `index - 1`, which is -1, so nothing took focus and the keyboard stayed up over a list with no cursor in it. It now focuses whichever row takes the deleted one's place, which also does the right thing when the deleted row was the only one — `withoutIndex` leaves a fresh empty block behind, and that block is what gets the caret. Found by reading the path the operator said they were about to test, rather than by waiting for them to find it. --- alembic/versions/0027_checklist_items_into_body.py | 14 +++++++++++--- .../com/fabledsword/thoughtsync/ui/BlockBody.kt | 8 ++++++-- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/alembic/versions/0027_checklist_items_into_body.py b/alembic/versions/0027_checklist_items_into_body.py index ed6e5d2..bae2368 100644 --- a/alembic/versions/0027_checklist_items_into_body.py +++ b/alembic/versions/0027_checklist_items_into_body.py @@ -10,10 +10,18 @@ position in the text, so a separate list could only ever render AFTER the prose. the items in the body, a list can sit between two paragraphs — which is the thing that could not be built before and no amount of restyling would have delivered. -## This migration rewrites real content +## This migration rewrites note bodies -Every note that has items gets its body appended to. The Google Keep import is genuine -content on this instance, not fixtures, so the rules here are strict: +Every note that has items gets its body appended to. The rules below are strict +because rewriting somebody's text deserves it — not, as an earlier draft of this +docstring claimed, because this instance holds imported Google Keep notes. It does +not; note 2916's headline is that nothing here is anyone's work but the operator's +test data. What 2916 actually says about imports is conditional — text arriving from +another app WOULD be real, and any import path has to treat it that way — and the +importer this migration shares a format with is one nobody here has run. + +Careful was still the right call. It cost little, and the same care is what the rule +demands the day someone does import something: * Rows are read BEFORE the table is dropped, in this one transaction. * The existing body is never rewritten, only appended to. diff --git a/android/app/src/main/java/com/fabledsword/thoughtsync/ui/BlockBody.kt b/android/app/src/main/java/com/fabledsword/thoughtsync/ui/BlockBody.kt index 4f3a530..c609eeb 100644 --- a/android/app/src/main/java/com/fabledsword/thoughtsync/ui/BlockBody.kt +++ b/android/app/src/main/java/com/fabledsword/thoughtsync/ui/BlockBody.kt @@ -83,8 +83,12 @@ fun BlockBody( onFocus(if (blocks[index].value.text.isBlank()) block.id else next) }, onDelete = { - onChange(blocks.withoutIndex(index)) - onFocus(blocks.getOrNull(index - 1)?.id) + val remaining = blocks.withoutIndex(index) + onChange(remaining) + // The row above — or, for the FIRST row, whichever one takes + // its place. `index - 1` alone is -1 there, which left the + // keyboard up with nothing focused. + onFocus(remaining.getOrNull((index - 1).coerceAtLeast(0))?.id) }, ) } else {