From 216176eeb7c090e3116e5b4502c22c519db8a3aa Mon Sep 17 00:00:00 2001 From: m Date: Thu, 28 May 2026 04:46:55 +1000 Subject: [PATCH] fix(search): route d/ and id/ Namecoin namespaces through the resolution row MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The inline Namecoin resolution row in the global search bar (and the on-chain zap recipient field) is gated by looksLikeNamecoinIdentifier, which previously only matched the '.bit' shapes. Direct namespace references like 'id/mstrofnone' or 'd/mstrofnone' — both accepted by NamecoinNameResolver.isNamecoinIdentifier — fell through the gate and the resolver was never called, so no on-chain feedback appeared in the search bar. Bring the UI gate in line with the resolver: - accept 'd/' (domain namespace, direct reference) - accept 'id/' (identity namespace) - lower the length floor so single-character labels (valid, if expensive, on chain) still trigger; '.bit' inputs keep their 5-char floor Doc comment for the row's behaviour and the NamecoinResolutionRow KDoc are updated to reflect the broader accepted set. New unit tests cover both new prefixes (with case-insensitivity and leading '@'), short single-label names, bare-prefix rejection, and explicit non-routing for other Namecoin namespaces ('a/', 'u/'). --- .../namecoin/NamecoinResolutionRow.kt | 16 ++++++- .../namecoin/NamecoinResolutionRowTest.kt | 45 +++++++++++++++++++ 2 files changed, 60 insertions(+), 1 deletion(-) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/namecoin/NamecoinResolutionRow.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/namecoin/NamecoinResolutionRow.kt index e3bca2104d..fc870e5a98 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/namecoin/NamecoinResolutionRow.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/namecoin/NamecoinResolutionRow.kt @@ -102,9 +102,22 @@ fun mapOutcomeToResolveState(outcome: NamecoinResolveOutcome): NamecoinResolveSt * Lightweight syntactic check: does this look like something we should * route to Namecoin? Mirrors [com.vitorpamplona.quartz.nip05DnsIdentifiers.namecoin.NamecoinNameResolver.isNamecoinIdentifier] * but tolerates a leading `@` (matches the dropdown's [com.vitorpamplona.amethyst.ui.note.creators.userSuggestions.UserSuggestionState.userSearchTermOrNull]). + * + * Accepted shapes: + * - `host.bit` and `user@host.bit` (domain namespace) + * - `d/` (domain namespace, direct reference) + * - `id/` (identity namespace) + * + * The length floor matches the dropdown's `>2 chars` rule while still + * requiring at least one character after a `d/`/`id/` prefix, so single- + * character Namecoin labels (which are valid, if expensive, on chain) + * still trigger the on-chain lookup. */ fun looksLikeNamecoinIdentifier(raw: String): Boolean { val trimmed = raw.trim().removePrefix("@").lowercase() + if (trimmed.length < 3) return false + if (trimmed.startsWith("d/") && trimmed.length > 2) return true + if (trimmed.startsWith("id/") && trimmed.length > 3) return true if (trimmed.length < 5) return false return trimmed.endsWith(".bit") || trimmed.contains("@") && trimmed.substringAfter('@').endsWith(".bit") @@ -117,7 +130,8 @@ fun looksLikeNamecoinIdentifier(raw: String): Boolean { * field and the global search bar both have this race). * * Behaviour: - * - Renders nothing when [searchInput] is not a `.bit` identifier. + * - Renders nothing when [searchInput] is not a Namecoin-shaped + * identifier (`.bit`, `d/`, or `id/`). * - Shows a small spinner row ("Resolving on Namecoin…") while the * ElectrumX lookup is in flight (after a 300 ms debounce to match * typical input-field debounce intervals). diff --git a/amethyst/src/test/java/com/vitorpamplona/amethyst/ui/components/namecoin/NamecoinResolutionRowTest.kt b/amethyst/src/test/java/com/vitorpamplona/amethyst/ui/components/namecoin/NamecoinResolutionRowTest.kt index af91222256..6436ea254f 100644 --- a/amethyst/src/test/java/com/vitorpamplona/amethyst/ui/components/namecoin/NamecoinResolutionRowTest.kt +++ b/amethyst/src/test/java/com/vitorpamplona/amethyst/ui/components/namecoin/NamecoinResolutionRowTest.kt @@ -90,6 +90,51 @@ class NamecoinResolutionRowTest { assertFalse(looksLikeNamecoinIdentifier("foo@bar.com")) } + @Test + fun `d slash name is a namecoin identifier`() { + // Direct domain-namespace references are accepted both via the + // search bar (mirrors NamecoinNameResolver.isNamecoinIdentifier) + // and via the dropdown's leading-at-sign convention. + assertTrue(looksLikeNamecoinIdentifier("d/mstrofnone")) + assertTrue(looksLikeNamecoinIdentifier("D/Example")) + assertTrue(looksLikeNamecoinIdentifier("@d/mstrofnone")) + } + + @Test + fun `id slash name is a namecoin identifier`() { + // Identity-namespace references must also flow to the resolver; + // the parser at NamecoinNameResolver.parseIdentifier maps these + // to (id/, localPart="_"). + assertTrue(looksLikeNamecoinIdentifier("id/mstrofnone")) + assertTrue(looksLikeNamecoinIdentifier("ID/Alice")) + assertTrue(looksLikeNamecoinIdentifier("@id/alice")) + } + + @Test + fun `short single-label d and id names still trigger`() { + // Namecoin allows single-character labels in both namespaces; + // "d/a" / "id/a" should not be filtered out by the length floor. + assertTrue(looksLikeNamecoinIdentifier("d/a")) + assertTrue(looksLikeNamecoinIdentifier("id/a")) + } + + @Test + fun `bare namespace prefix with no name does not match`() { + // "d/" and "id/" alone are not lookups. + assertFalse(looksLikeNamecoinIdentifier("d/")) + assertFalse(looksLikeNamecoinIdentifier("id/")) + } + + @Test + fun `other namecoin namespaces are not surfaced by the search row`() { + // The Nostr-NIP05 contract only spans the domain (d/) and + // identity (id/) namespaces. Other Namecoin namespaces exist on + // chain but aren't part of the resolution surface, so the row + // should stay quiet. + assertFalse(looksLikeNamecoinIdentifier("a/somealias")) + assertFalse(looksLikeNamecoinIdentifier("u/someuser")) + } + // ── mapOutcomeToResolveState ─────────────────────────────────────────── // Reuses the shared NamecoinResolveState already used by // NamecoinNameService and the desktop SearchScreen so all surfaces