From fe0ff4079c09c5d46ad728ab0ee80affcbef0935 Mon Sep 17 00:00:00 2001 From: Tienson Qin Date: Sun, 1 Nov 2026 00:41:03 +0000 Subject: [PATCH] fix(android): accept preserved wrapping text whitespace --- docs/api-migrations.md | 13 +++++++++++ .../src/main/kotlin/dev/lui/LuiKindRules.kt | 2 +- .../src/test/kotlin/dev/lui/LuiStoreTest.kt | 22 +++++++++++++++++++ 3 files changed, 36 insertions(+), 1 deletion(-) diff --git a/docs/api-migrations.md b/docs/api-migrations.md index 3f740bf4..49d34a80 100644 --- a/docs/api-migrations.md +++ b/docs/api-migrations.md @@ -13,6 +13,19 @@ inventory check is temporarily deferred until that inventory recognizes that renderer. The MyGo, Web, Apple, Android and shared ABI regressions remain covered separately. +## Unreleased: Android accepts wrapping preserved text + +Android's retained text-flow validator now accepts `white-space: "pre-wrap"`, +matching the existing shared OCaml and Apple vocabulary for this mode. Previously +a valid batch such as `{"op":"set-prop","id":2,"property":"white-space","value":"pre-wrap"}` +was rejected atomically, including authoritative snapshots, even though Compose +`Text` already preserves the supplied text and wraps by default. + +No application wire rewrite is required: keep sending `pre-wrap` for canonical +text whose whitespace must be retained. This only corrects validation; it does +not add CSS whitespace processing or change rendering of `normal`/`nowrap`. +Android still rejects `pre-line`, `pre`, unknown strings and non-string values. + ## Unreleased: Linux native clipboard ownership result `Lui_shell_linux.clipboard_write : string -> bool` keeps its signature but diff --git a/platform/android/lui/src/main/kotlin/dev/lui/LuiKindRules.kt b/platform/android/lui/src/main/kotlin/dev/lui/LuiKindRules.kt index f05e179c..7c5dd192 100644 --- a/platform/android/lui/src/main/kotlin/dev/lui/LuiKindRules.kt +++ b/platform/android/lui/src/main/kotlin/dev/lui/LuiKindRules.kt @@ -797,7 +797,7 @@ internal object LuiKindRules { "user-select" -> value.stringValue in setOf("none", "text") && isTextAppearanceKind(kind) "white-space" -> - value.stringValue in setOf("normal", "nowrap") && isTextFlowKind(kind) + value.stringValue in setOf("normal", "nowrap", "pre-wrap") && isTextFlowKind(kind) "text-overflow" -> value.stringValue in setOf("clip", "ellipsis") && isTextFlowKind(kind) "overflow" -> diff --git a/platform/android/lui/src/test/kotlin/dev/lui/LuiStoreTest.kt b/platform/android/lui/src/test/kotlin/dev/lui/LuiStoreTest.kt index d1795b33..427c6f42 100644 --- a/platform/android/lui/src/test/kotlin/dev/lui/LuiStoreTest.kt +++ b/platform/android/lui/src/test/kotlin/dev/lui/LuiStoreTest.kt @@ -14,6 +14,28 @@ class LuiStoreTest { private fun batch(generation: Int, vararg ops: LuiPatchOp) = LuiPatchBatch(generation, ops.toList()) + @Test + fun `wrapping text retains pre-wrap and rejects unsupported whitespace modes`() { + fun apply(value: LuiWireValue): LuiRetainedTree = tree().also { + it.apply(batch(1, + LuiPatchOp.CreateNode(1L, LuiNodeKind.text), + LuiPatchOp.SetProp(1L, "white-space", value), + )) + } + for (mode in listOf("normal", "nowrap", "pre-wrap")) { + val value = LuiWireValue.Str(mode) + assertEquals(value, apply(value).node(1L)!!.properties["white-space"]) + } + for (value in listOf( + LuiWireValue.Str("pre"), + LuiWireValue.Str("pre-line"), + LuiWireValue.Str("wrap"), + LuiWireValue.IntValue(1), + )) { + assertThrows(LuiBackendException::class.java) { apply(value) } + } + } + @Test fun `native menu accepts semantic label alignments and rejects invalid values`() { fun apply(value: LuiWireValue) {