android: a checklist is lines of the note here too
M304 step 6, and the surface with the least room to hide: Android has no markdown renderer at all, so the card was about to show every list twice — once as literal `- [ ] milk` in the body preview, and again as the glyph rows underneath. Same bug the web had, one commit later. The card now renders the body LINE BY LINE and draws a checkbox where one belongs, which is what puts a list between two paragraphs instead of always after them. The glyphs became tappable while they were being rewritten: ticking something off from the board without opening the note is the common gesture, and the web just gained it. The tap target is the glyph, not the row — tapping the TEXT still opens the note, the way tapping anywhere else on a card does. Kotlin gets no parser. Three implementations of the grammar is the price already paid; a fourth in Compose would be a fourth place for a checklist to change shape when it syncs. So the core exposes three pure functions instead — `checklist_lines`, `checklist_continuation`, `checklist_toggle_at` — and Kotlin does the caret arithmetic around them. Those are FREE functions, not methods, and that is the interesting constraint. The editor's body field is LOCAL state on an idle-debounced autosave, so anything that edits a checklist there has to rewrite the text the field is holding, not a row the store would hand back a moment later. Going through the store would overwrite whatever was being typed. The BOARD has no such problem — nothing there is holding a half-typed body — so the card's toggle goes through the store as usual. `toggle_at` addresses an item by LINE and COLUMN rather than a text offset, because the two sides do not count the same way: Compose measures in UTF-16 units and Rust in bytes, so the same number means different places in a note with an emoji in it. A line number is identical in every encoding, and so is a column inside the marker, which is ASCII at the start of its line. In the editor: the toolbar button inserts `- [ ] ` at the caret — the only toolbar action needing no saved note, so it works on an empty compose box the moment it opens — and Enter continues the list, or ends it on an empty item. Continuation is recognised by SHAPE inside onValueChange (exactly one more character, and it is a newline) rather than by a key event, so a paste or an autocorrect falls through untouched. EditorChecklist.kt and the four item actions are gone (rule 22). Adding, renaming, ticking or deleting an item is editing text now, and the editor already does that — through SaveText, with the same autosave and the same revision window as any other edit. KNOWN GAP, not an oversight: tapping a checkbox inside the EDITOR does nothing yet. Material3's TextField does not expose onTextLayout, so mapping a tap to a character offset means either moving the body to BasicTextField or intercepting pointer events ahead of the field — both real changes to the surface this operator uses most, and neither verifiable without a device. `checklist_toggle_at` lands here, tested, so that task is pure UI. Ticking from the board works today.
This commit is contained in:
@@ -499,6 +499,40 @@ impl ThoughtSync {
|
||||
}
|
||||
}
|
||||
|
||||
// ── checklist text, as pure functions ───────────────────────────────────────
|
||||
//
|
||||
// Free functions rather than methods, because these touch no database. The editor's
|
||||
// body field is LOCAL state — it is autosaved on an idle debounce, not written on
|
||||
// every keystroke — so a checkbox tapped in the editor has to rewrite the text the
|
||||
// field is holding, not a row the store would hand back a moment later. Routing that
|
||||
// through the store would overwrite whatever was being typed.
|
||||
//
|
||||
// They also keep the grammar out of Kotlin. Three implementations of it is the price
|
||||
// already being paid (Rust, Python, TypeScript); a fourth in Compose would be one
|
||||
// more place for a checklist to change shape when it syncs.
|
||||
|
||||
/// Which body lines carry checklist items, in item order — so a renderer walking the
|
||||
/// body line by line knows which of them to draw a checkbox on.
|
||||
#[uniffi::export]
|
||||
pub fn checklist_lines(body: String) -> Vec<u32> {
|
||||
local::derive::item_lines(&body)
|
||||
}
|
||||
|
||||
/// The body with the item at `line`/`column` ticked or unticked, or null if that is
|
||||
/// not a checkbox. See `derive::toggle_at` for why the address is line + column and
|
||||
/// not a text offset.
|
||||
#[uniffi::export]
|
||||
pub fn checklist_toggle_at(body: String, line: u32, column: u32) -> Option<String> {
|
||||
local::derive::toggle_at(&body, line as usize, column as usize)
|
||||
}
|
||||
|
||||
/// What pressing Enter at the end of `line` should leave behind: null to let Enter be
|
||||
/// Enter, "" to end the list, or the marker to start the next item.
|
||||
#[uniffi::export]
|
||||
pub fn checklist_continuation(line: String) -> Option<String> {
|
||||
local::derive::continuation(&line)
|
||||
}
|
||||
|
||||
/// Helpers, deliberately NOT exported — uniffi only binds what an `#[uniffi::export]`
|
||||
/// block names, so these stay Rust-side.
|
||||
impl ThoughtSync {
|
||||
|
||||
Reference in New Issue
Block a user