From f8fecfdcefd909392cb4a7fd3c13ca6303cc6db5 Mon Sep 17 00:00:00 2001 From: Eric Dahl Date: Tue, 14 Jul 2026 16:03:57 -0400 Subject: [PATCH] feat(discovery): recognise the headless X32Link (ESP-035) "I see kstouch. not the headless." The X32Link was powered, on the network, and following the Link session, and the app showed nothing. Adding `x32link` to the hostname prefix list is the small half of this. The real cause was in the firmware: that unit had NO mDNS WHATSOEVER -- it never advertised, so no matcher on this side could have found it, and `dns-sd` could not see it either. Fixed in link-devices (ESP-035), where it now announces `x32link-` like the rest of the fleet and serves the shared /status and /config.json. The prefix list stays a documented HEURISTIC, not a guarantee -- a TXT record is still the fix that would make identity a fact. Tests pin both halves: the headless is one of ours, and a stranger's box at a venue still is not. Verified against the real unit: both devices now appear in the list, live BPM. Claude-Session: https://claude.ai/code/session_01F7YsQgso3KNpeXbP5vNVj6 --- .../KitchenSyncApp/Discovery/DeviceMatch.swift | 11 ++++++++--- .../KitchenSyncAppTests/DeviceMatchTests.swift | 17 +++++++++++++++++ 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/Sources/KitchenSyncApp/Discovery/DeviceMatch.swift b/Sources/KitchenSyncApp/Discovery/DeviceMatch.swift index 459832b..bd973f4 100644 --- a/Sources/KitchenSyncApp/Discovery/DeviceMatch.swift +++ b/Sources/KitchenSyncApp/Discovery/DeviceMatch.swift @@ -30,11 +30,16 @@ enum DeviceMatch { /// units working, not because it is correct. /// /// The firmware advertises generic `_http._tcp` with no TXT record, so the hostname is all a - /// client has. `ks_hostname()` builds `-`: `kitchensync-a4f2` on - /// the P4, `kstouch-dfd0` on the Touch. Missing `kstouch` is what made a real device invisible. + /// client has. The hostname is `-`: `kitchensync-a4f2` on the + /// 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) } } - private static let knownHostPrefixes = ["kitchensync", "kstouch"] + /// `x32link` joined this list in ESP-035 — but adding it here was the SMALL half. That + /// unit had no mDNS whatsoever: it never advertised, so no matcher could have found it. + /// The user saw it exactly that way — "I see kstouch, not the headless" — while the box + /// sat there powered, on the network, and following the session. + private static let knownHostPrefixes = ["kitchensync", "kstouch", "x32link"] } diff --git a/Tests/KitchenSyncAppTests/DeviceMatchTests.swift b/Tests/KitchenSyncAppTests/DeviceMatchTests.swift index 914ea1b..e7bf692 100644 --- a/Tests/KitchenSyncAppTests/DeviceMatchTests.swift +++ b/Tests/KitchenSyncAppTests/DeviceMatchTests.swift @@ -47,4 +47,21 @@ final class DeviceMatchTests: XCTestCase { txt: ["dev": "some-other-product"]), "a TXT record that says it ISN'T ours must beat a hostname that says it is") } + + /// ESP-035: the headless X32Link. It was the last unit in the fleet with NO mDNS at + /// all -- it never advertised, so it could not be found no matter what this matcher + /// did. Now it announces `x32link-`, and the prefix list has to know the name. + /// + /// The user hit this exactly: "I see kstouch. not the headless." The device was + /// powered, on the network, and following the Link session. + func test_the_headless_x32link_is_one_of_ours() { + XCTAssertTrue(DeviceMatch.isKitchenSync(serviceName: "x32link-7a1c", txt: nil)) + } + + /// The prefix is still a HEURISTIC, and it must not swallow the whole network. A + /// stranger's box at a venue is not ours. + func test_a_stranger_is_still_not_one_of_ours() { + XCTAssertFalse(DeviceMatch.isKitchenSync(serviceName: "brother-printer", txt: nil)) + XCTAssertFalse(DeviceMatch.isKitchenSync(serviceName: "x32-mixer", txt: nil)) + } }