CI & Build / Build now, or wait for Android? (push) Successful in 3s
Android / Build, or is the channel already serving this? (push) Successful in 3s
CI & Build / Python lint (push) Successful in 2s
Desktop (Tauri) / Build, or is the channel already serving this? (push) Successful in 3s
CI & Build / TypeScript typecheck (push) Successful in 7s
Desktop (Tauri) / Tauri desktop (Linux) (push) Skipped
Desktop (Tauri) / Windows installer (cross-compiled) (push) Skipped
Desktop (Tauri) / Update manifest (push) Skipped
CI & Build / Python tests (push) Successful in 10s
CI & Build / integration (push) Successful in 15s
CI & Build / Build & push image (push) Skipped
Android / Kotlin + Rust (APK) (push) Successful in 7m0s
`core/src/local/store.rs` implements all seven label operations. The uniffi object exposed three of them, so Android could attach tags to a note and mint new ones, and could do nothing else with them ever. The four additions are pure passthrough, because reading the store showed both of the things #2963 said to check rather than assume are already handled there: * The note count exists. `Label` carries `count: Option<i64>` and `list_labels` computes it per row, excluding trashed notes — which is the number a delete confirmation should show. The single-label returns all end in `load_label` and leave it `None` on purpose, so a screen must read counts from the LIST and never from an operation's result. * Sync is free. `rename_label` and `set_label_color` set `dirty = 1`; `remove_label` records a pending delete; `merge_labels` records one for the source AND marks every note that carried it dirty before the delete cascades the membership rows away, because push sends `label_ids` per note. So no store change, no sync change, no count plumbing — the binding only. One divergence found and documented rather than fixed: renaming a tag onto an existing name is a 409 on the server (`labels.py:94`) and a silent duplicate in the local store. The desktop has always had this, calling the same `store::rename_label`; Android now inherits it. Deciding which side is right belongs with the screen (#2964), not with the binding. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01K3MMqUtzX1TJgA1oypvm1c