One tag-name fold on the web and on Android, beside the core's (#5373, #5385)
CI & Build / Python lint (push) Successful in 2s
CI & Build / Build now, or wait for Android? (push) Successful in 2s
Android / Build, or is the channel already serving this? (push) Successful in 3s
Desktop (Tauri) / Build, or is the channel already serving this? (push) Successful in 3s
CI & Build / Web typecheck and unit tests (push) Successful in 9s
CI & Build / Python tests (push) Successful in 11s
Android / Core and FFI clippy and tests (push) Successful in 1m0s
Desktop (Tauri) / Web tests, clippy, Rust tests and rustfmt (push) Successful in 2m16s
CI & Build / integration (push) Successful in 2m41s
CI & Build / Build & push image (push) Skipped
Desktop (Tauri) / Windows installer (cross-compiled) (push) Successful in 3m8s
Desktop (Tauri) / Tauri desktop (Linux) (push) Successful in 3m45s
Desktop (Tauri) / Update manifest (push) Successful in 4s
Android / Kotlin + Rust (APK) (push) Successful in 9m26s
Android / Build the server image (push) Successful in 1s
CI & Build / Python lint (push) Successful in 2s
CI & Build / Build now, or wait for Android? (push) Successful in 2s
Android / Build, or is the channel already serving this? (push) Successful in 3s
Desktop (Tauri) / Build, or is the channel already serving this? (push) Successful in 3s
CI & Build / Web typecheck and unit tests (push) Successful in 9s
CI & Build / Python tests (push) Successful in 11s
Android / Core and FFI clippy and tests (push) Successful in 1m0s
Desktop (Tauri) / Web tests, clippy, Rust tests and rustfmt (push) Successful in 2m16s
CI & Build / integration (push) Successful in 2m41s
CI & Build / Build & push image (push) Skipped
Desktop (Tauri) / Windows installer (cross-compiled) (push) Successful in 3m8s
Desktop (Tauri) / Tauri desktop (Linux) (push) Successful in 3m45s
Desktop (Tauri) / Update manifest (push) Successful in 4s
Android / Kotlin + Rust (APK) (push) Successful in 9m26s
Android / Build the server image (push) Successful in 1s
The web compared or keyed tag names with name.toLowerCase() at five sites: the labels store's merge check, LabelPicker's create check, NoteCard's colour map, and colors.ts twice. Android had three, plus TagsScreen's rename dialog using equals(ignoreCase = true). Each now calls foldTag(name), which says what the core's derive::fold and the server's labeling.named say. Android's rename dialog now predicts a merge the same way the core decides one. Per-character ignoreCase and full lowercasing disagree on letters like İ, whose lowercase is two characters. LabelPicker's search filter keeps its own lowercasing: that is search, not identity. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -86,9 +86,9 @@ fun derivedTint(id: String): String {
|
||||
* Deriving has no mint points to miss and no backfill for the tags already out there.
|
||||
* The cost is that renaming a tag recolours it, which is fair: the name IS the tag.
|
||||
*
|
||||
* Lowercased because tags dedupe case-insensitively — `#Todo` renamed to `#todo` is
|
||||
* the same tag and should not change colour. Kotlin's `lowercase()` and the web's
|
||||
* `toLowerCase()` are both locale-independent, so the mirror holds.
|
||||
* Folded because `#Todo` renamed to `#todo` is the same tag and should not change
|
||||
* colour. [foldTag] and the web's are both locale-independent lowercasing, so the
|
||||
* mirror holds.
|
||||
*/
|
||||
fun resolvedLabelColor(
|
||||
name: String,
|
||||
@@ -98,5 +98,12 @@ fun resolvedLabelColor(
|
||||
when {
|
||||
color.isNotEmpty() && color != "default" && color in known -> color
|
||||
name.isEmpty() -> "default"
|
||||
else -> derivedTint(name.lowercase())
|
||||
else -> derivedTint(foldTag(name))
|
||||
}
|
||||
|
||||
/**
|
||||
* How two tag names compare: equal once every letter is lowercased, accented ones
|
||||
* included, so `#Café` and `#CAFÉ` are one tag. The core's `derive::fold`, the server's
|
||||
* `labeling.named` and the web's `foldTag` fold the same way (#5385).
|
||||
*/
|
||||
fun foldTag(name: String): String = name.lowercase()
|
||||
|
||||
@@ -366,11 +366,11 @@ private fun tintTags(
|
||||
// A tag the note does not carry as a label yet — just typed, not yet
|
||||
// derived — still gets a colour: `labelTint` falls back to deriving one
|
||||
// from the name, which is what the chip would have shown anyway.
|
||||
val picked = note.labels.associate { it.name.lowercase() to it.color }
|
||||
val picked = note.labels.associate { foldTag(it.name) to it.color }
|
||||
buildAnnotatedString {
|
||||
append(text)
|
||||
spans.forEach { tag ->
|
||||
val tint = labelTint(tag.name, picked[tag.name.lowercase()].orEmpty())
|
||||
val tint = labelTint(tag.name, picked[foldTag(tag.name)].orEmpty())
|
||||
addStyle(
|
||||
SpanStyle(color = tint.tagInk(dark), fontWeight = FontWeight.Medium),
|
||||
tag.start.toInt(),
|
||||
|
||||
@@ -401,7 +401,7 @@ private fun RenameDialog(
|
||||
var text by remember(tag.id) { mutableStateOf(tag.name) }
|
||||
val trimmed = text.trim()
|
||||
val clash =
|
||||
others.firstOrNull { it.id != tag.id && it.name.equals(trimmed, ignoreCase = true) }
|
||||
others.firstOrNull { it.id != tag.id && foldTag(it.name) == foldTag(trimmed) }
|
||||
val submit = {
|
||||
when {
|
||||
trimmed.isEmpty() -> Unit
|
||||
|
||||
Reference in New Issue
Block a user