From 948cfa25b093d85f5f67a4b2b483a7c2f2ae088d Mon Sep 17 00:00:00 2001 From: MrAlders0n Date: Fri, 24 Jul 2026 06:19:19 -0400 Subject: [PATCH] Fix public-key node search field switching and non-hex input --- src/features/nodes/NodeTable.tsx | 21 ++++++++++++++++----- src/features/nodes/node-search.ts | 12 ++++++++++++ tests/features/nodes/node-search.test.ts | 23 +++++++++++++++++++++++ 3 files changed, 51 insertions(+), 5 deletions(-) create mode 100644 src/features/nodes/node-search.ts create mode 100644 tests/features/nodes/node-search.test.ts diff --git a/src/features/nodes/NodeTable.tsx b/src/features/nodes/NodeTable.tsx index dbfc0ed..60179f7 100644 --- a/src/features/nodes/NodeTable.tsx +++ b/src/features/nodes/NodeTable.tsx @@ -14,6 +14,7 @@ import { ObserverIcon } from "../../components/ObserverIcon"; import { DataTable, type Column } from "../../components/DataTable"; import { LoadingPill } from "../../components/LoadingPill"; import { NodeFilterBar, type MultibyteFilter } from "./NodeFilterBar"; +import { nodeSearchParams } from "./node-search"; import { patchNodeSummary } from "./node-updates"; import type { NodeSummary } from "./types"; import type { CursorPage } from "../../types/api"; @@ -138,9 +139,19 @@ export function NodeTable({ wsManager, selectedNodeId, onSelectNode }: NodeTable useTick(); + // switching the field flips what the box means (a name vs a hex prefix), so stale text mustn't carry over + const handleSearchFieldChange = useCallback((field: string) => { + setSearchField(field); + setSearch(""); + }, []); + + // derive the actual server params (name vs pubkeyPrefix, hex-guarded) and key the query on THOSE, + // so toggling the field with an empty box is a no-op and a name never gets sent as a hex prefix + const { name: nameParam, pubkeyPrefix: pubkeyPrefixParam } = nodeSearchParams(searchField, search); + const queryKey = useMemo( - () => ["nodes", regionKey, typeFilter, pathsFilter, tracesFilter, search, searchField], - [regionKey, typeFilter, pathsFilter, tracesFilter, search, searchField], + () => ["nodes", regionKey, typeFilter, pathsFilter, tracesFilter, nameParam, pubkeyPrefixParam], + [regionKey, typeFilter, pathsFilter, tracesFilter, nameParam, pubkeyPrefixParam], ); // page the region's nodes 50 at a time (filters stay server-side, in the query key); rows stream @@ -151,8 +162,8 @@ export function NodeTable({ wsManager, selectedNodeId, onSelectNode }: NodeTable getNodesPage(iatas, { cursor, type: typeFilter || undefined, - name: searchField === "name" ? search || undefined : undefined, - pubkeyPrefix: searchField === "pubkey" ? search || undefined : undefined, + name: nameParam, + pubkeyPrefix: pubkeyPrefixParam, supportsMultibytePaths: pathsFilter || undefined, supportsMultibyteTraces: tracesFilter || undefined, }), @@ -189,7 +200,7 @@ export function NodeTable({ wsManager, selectedNodeId, onSelectNode }: NodeTable search={search} onSearchChange={setSearch} searchField={searchField} - onSearchFieldChange={setSearchField} + onSearchFieldChange={handleSearchFieldChange} typeFilter={typeFilter} onTypeChange={setTypeFilter} pathsFilter={pathsFilter} diff --git a/src/features/nodes/node-search.ts b/src/features/nodes/node-search.ts new file mode 100644 index 0000000..aa2a3c5 --- /dev/null +++ b/src/features/nodes/node-search.ts @@ -0,0 +1,12 @@ +// Maps the Nodes-table search box (one shared input + a field selector) to the server params. +// The Public Key field is a hex prefix; non-hex input is dropped rather than sent, because the +// backend 400s on a non-hex pubkeyPrefix — so a stray character shows the unfiltered list instead +// of erroring the whole table. +export function nodeSearchParams(searchField: string, search: string): { name?: string; pubkeyPrefix?: string } { + const value = search.trim(); + if (searchField === "pubkey") { + const hex = value.toLowerCase(); + return { pubkeyPrefix: /^[0-9a-f]+$/.test(hex) ? hex : undefined }; + } + return { name: value || undefined }; +} diff --git a/tests/features/nodes/node-search.test.ts b/tests/features/nodes/node-search.test.ts new file mode 100644 index 0000000..1f85434 --- /dev/null +++ b/tests/features/nodes/node-search.test.ts @@ -0,0 +1,23 @@ +import { describe, it, expect } from "vitest"; +import { nodeSearchParams } from "../../../src/features/nodes/node-search"; + +describe("nodeSearchParams", () => { + it("maps the name field to the name param", () => { + expect(nodeSearchParams("name", "alpha")).toEqual({ name: "alpha" }); + }); + + it("maps the pubkey field to a lowercased hex prefix", () => { + expect(nodeSearchParams("pubkey", "AB12")).toEqual({ pubkeyPrefix: "ab12" }); + }); + + it("drops a non-hex pubkey prefix instead of firing a request the server 400s", () => { + // names/spaces are non-hex; sending them as pubkeyPrefix would 400 the whole table + expect(nodeSearchParams("pubkey", "alice")).toEqual({ pubkeyPrefix: undefined }); + expect(nodeSearchParams("pubkey", "de ad")).toEqual({ pubkeyPrefix: undefined }); + }); + + it("treats blank input as no filter on either field", () => { + expect(nodeSearchParams("name", " ")).toEqual({ name: undefined }); + expect(nodeSearchParams("pubkey", "")).toEqual({ pubkeyPrefix: undefined }); + }); +});