From abe7f1a1c3d0235d635895eed2a4bfee8a84eab9 Mon Sep 17 00:00:00 2001 From: Eric Dahl Date: Wed, 15 Jul 2026 02:00:18 -0400 Subject: [PATCH] =?UTF-8?q?fix(discovery):=20match=20device=20hostnames=20?= =?UTF-8?q?case-insensitively=20=E2=80=94=20the=20P4=20was=20invisible?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A real ESP32-P4 on the LAN found this. It advertises its Bonjour INSTANCE NAME as "KitchenSync" (capital K, a friendly name), where the Touch and X32Link use lowercase hostnames (kstouch-…, x32link-…). The hostname fallback used hasPrefix, which is case-sensitive, so `"KitchenSync".hasPrefix("kitchensync")` was false and the P4 never showed up. A Bonjour name's case is not meaningful. Lowercase before matching. (The P4 also publishes dev=kitchensync in TXT, so a TXT-aware client matches it anyway; this fixes the hostname fallback for units whose TXT the browse result doesn't carry.) Test written from the real device's advertisement. Claude-Session: https://claude.ai/code/session_01F7YsQgso3KNpeXbP5vNVj6 --- Sources/KitchenSyncApp/Discovery/DeviceMatch.swift | 4 +++- Tests/KitchenSyncAppTests/DeviceMatchTests.swift | 10 ++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/Sources/KitchenSyncApp/Discovery/DeviceMatch.swift b/Sources/KitchenSyncApp/Discovery/DeviceMatch.swift index bd973f4..d96e551 100644 --- a/Sources/KitchenSyncApp/Discovery/DeviceMatch.swift +++ b/Sources/KitchenSyncApp/Discovery/DeviceMatch.swift @@ -34,7 +34,9 @@ enum DeviceMatch { /// P4, `kstouch-dfd0` on the Touch, `x32link-7a1c` on the headless (ESP-035). Missing /// `kstouch` is what made a real device invisible once already. private static func matchesKnownHostname(_ name: String) -> Bool { - knownHostPrefixes.contains { name.hasPrefix($0) } + let lower = name.lowercased() // a Bonjour instance name's case is not meaningful — + // the P4 advertises "KitchenSync", the Touch "kstouch-…" + return knownHostPrefixes.contains { lower.hasPrefix($0) } } /// `x32link` joined this list in ESP-035 — but adding it here was the SMALL half. That diff --git a/Tests/KitchenSyncAppTests/DeviceMatchTests.swift b/Tests/KitchenSyncAppTests/DeviceMatchTests.swift index e7bf692..f8c9122 100644 --- a/Tests/KitchenSyncAppTests/DeviceMatchTests.swift +++ b/Tests/KitchenSyncAppTests/DeviceMatchTests.swift @@ -16,6 +16,16 @@ final class DeviceMatchTests: XCTestCase { "a real Touch on the bench was invisible to the app because of this") } + /// **A real P4 found this.** The ESP32-P4 advertises its Bonjour INSTANCE NAME as + /// "KitchenSync" (capital K, a friendly name), not the lowercase hostname the Touch + /// and X32Link use. hasPrefix is case-sensitive, so `"KitchenSync".hasPrefix( + /// "kitchensync")` is false and the P4 was invisible. A Bonjour name's case is not + /// meaningful; the match must be case-insensitive. + func test_the_p4_is_matched_despite_a_capitalised_instance_name() { + XCTAssertTrue(DeviceMatch.isKitchenSync(serviceName: "KitchenSync", txt: nil)) + XCTAssertTrue(DeviceMatch.isKitchenSync(serviceName: "KSTouch-DFD0", txt: nil)) + } + /// Units already in the field have no TXT record and won't until they're reflashed. Breaking /// them would be a worse bug than the one being fixed. func test_the_existing_devices_still_match_on_hostname() {