From 30d7b642d31302744645b25b70884ba35e6acb6e Mon Sep 17 00:00:00 2001 From: Keerthan K <79328813+keerthnn@users.noreply.github.com> Date: Mon, 5 Oct 2026 11:04:54 +0530 Subject: [PATCH] Search option for each symbol --- .../features/symbols-semantics/registry.md | 4 +- src/components/Duplicate.tsx | 73 +++++++++++-------- src/lib/dedupSearchWindow.test.ts | 21 ++++++ src/lib/dedupSearchWindow.ts | 9 +++ 4 files changed, 77 insertions(+), 30 deletions(-) create mode 100644 src/lib/dedupSearchWindow.test.ts create mode 100644 src/lib/dedupSearchWindow.ts diff --git a/specs/engineering/features/symbols-semantics/registry.md b/specs/engineering/features/symbols-semantics/registry.md index 2598765..acac82d 100644 --- a/specs/engineering/features/symbols-semantics/registry.md +++ b/specs/engineering/features/symbols-semantics/registry.md @@ -13,6 +13,7 @@ code: - src/routes/symbols.tsx - src/routes/Deduplication.tsx - src/lib/dedupCatalogDisplay.ts + - src/lib/dedupSearchWindow.ts - src/server/auth/requireAdminOrCurator.ts --- @@ -41,7 +42,8 @@ Out of scope: | `src/serverFns/symbol.server.ts` | Creates definienda using client-supplied FloDown URIs and lists or deletes declaration records by scanning `declaredSymbolsInfo`. | | `src/serverFns/symbolDuplicate.server.ts` | Sets confirmation fields on the matching `declaredSymbolsInfo` object. Curator/Admin only. | | `src/lib/dedupCatalogDisplay.ts` | Splits Deduplication catalog into unconfirmed vs confirmed-not-duplicate and paginates grouped names. | -| `src/routes/Deduplication.tsx` | Curator/Admin UI: unconfirmed list then **Confirmed not a duplicate**; 20 grouped names per page. | +| `src/lib/dedupSearchWindow.ts` | Shows the first 5 MathHub hits for a Deduplication symbol, then 5 more from the current search response (at most 15). | +| `src/routes/Deduplication.tsx` | Curator/Admin UI: unconfirmed list then **Confirmed not a duplicate**; 20 grouped names per page. Each symbol has a MathHub search field; hits render with **Same as this**. | | `src/routes/symbols.tsx` | Curator/Admin UI for the local symbol registry. | | `scripts/backfill-declared-symbols-info.mjs` | One-shot production backfill of short names into URIs. Temporary mint lives only in this script. | diff --git a/src/components/Duplicate.tsx b/src/components/Duplicate.tsx index 87b6525..b6501ee 100644 --- a/src/components/Duplicate.tsx +++ b/src/components/Duplicate.tsx @@ -1,4 +1,8 @@ import { SemanticPanel } from "@/components/semantic-panel/SemanticPanel"; +import { + DEDUP_SEARCH_PAGE_SIZE, + nextDedupSearchVisibleCount, +} from "@/lib/dedupSearchWindow"; import { floDownDeclareSymbolUri } from "@/lib/floDownDeclareSymbolUri"; import { queryClient } from "@/queryClient"; import { parseUri, ReplacePayload, normalizeSymRef } from "@/server/parseUri"; @@ -29,9 +33,10 @@ import { } from "@/serverFns/updateFloDownBlock.server"; import { assertFloDownStatement, FloDownStatement } from "@/types/floDown.types"; import { FloDownBlockSemantic } from "@/types/Semantic.types"; -import { Box, Button, Group, Loader, Paper, Stack, Text } from "@mantine/core"; +import { Box, Button, Group, Loader, Paper, Stack, Text, TextInput } from "@mantine/core"; +import { useDebouncedValue } from "@mantine/hooks"; import { useQuery } from "@tanstack/react-query"; -import { useMemo, useState } from "react"; +import { useEffect, useMemo, useState } from "react"; import { ConfirmationModal, ConfirmDialogKind } from "./ConfirmationModal"; import { ConfirmedIcon } from "./ConfirmedIcon"; import { DefiniendumDialog } from "./DefiniendumDialog"; @@ -44,7 +49,9 @@ import { MathHubDuplicateDialog } from "./MathHubDuplicateDialog"; export function Duplicate({ symbolName }: { symbolName: string }) { const [pendingMathHubDuplicate, setPendingMathHubDuplicate] = useState(null); - const [visibleCount, setVisibleCount] = useState(2); + const [searchQuery, setSearchQuery] = useState(symbolName); + const [debouncedSearchQuery] = useDebouncedValue(searchQuery, 300); + const [visibleCount, setVisibleCount] = useState(DEDUP_SEARCH_PAGE_SIZE); const [dialogKind, setDialogKind] = useState(null); const [dialogLoading, setDialogLoading] = useState(false); const [editingId, setEditingId] = useState(null); @@ -145,12 +152,15 @@ export function Duplicate({ symbolName }: { symbolName: string }) { ); }, [floDownBlock, symbolName]); - const searchQuery = `${symbolName} definition`; const { results, isLoading: isSearching } = useSymbolSearch( - searchQuery, + debouncedSearchQuery, true, ); + useEffect(() => { + setVisibleCount(DEDUP_SEARCH_PAGE_SIZE); + }, [debouncedSearchQuery]); + function handleToggleEdit(id: string) { setEditingId((prev) => (prev === id ? null : id)); } @@ -311,15 +321,12 @@ export function Duplicate({ symbolName }: { symbolName: string }) { r.source === "MATHHUB" && typeof r.uri === "string", ); - if ( - !isLoading && - !isSearching && - (!floDownBlock || mathHubResults.length === 0) - ) { + if (!isLoading && !isSearching && !floDownBlock) { return null; } const visibleResults = mathHubResults.slice(0, visibleCount); + const searchReady = debouncedSearchQuery.trim().length >= 2; const isConfirmed = symbol?.hasConfirmed === true; async function handleConfirmAction() { @@ -380,39 +387,47 @@ export function Duplicate({ symbolName }: { symbolName: string }) { - {isSearching && } + setSearchQuery(event.currentTarget.value)} + placeholder="Search MathHub" + /> - {visibleResults.map((r) => { - const parsed = parseUri(r.uri); - const safeUri = parsed.conceptUri; - return ( - - ); - })} + {isSearching && } - {mathHubResults.length > 2 && ( + {floDownBlock && + visibleResults.map((r) => { + const parsed = parseUri(r.uri); + const safeUri = parsed.conceptUri; + return ( + + ); + })} + + {mathHubResults.length > DEDUP_SEARCH_PAGE_SIZE && ( )} - {mathHubResults.length === 0 && !isSearching && ( + {searchReady && mathHubResults.length === 0 && !isSearching && ( No results found in MathHub diff --git a/src/lib/dedupSearchWindow.test.ts b/src/lib/dedupSearchWindow.test.ts new file mode 100644 index 0000000..0a413cb --- /dev/null +++ b/src/lib/dedupSearchWindow.test.ts @@ -0,0 +1,21 @@ +import { describe, expect, it } from "vitest"; +import { + DEDUP_SEARCH_PAGE_SIZE, + nextDedupSearchVisibleCount, +} from "./dedupSearchWindow"; + +describe("nextDedupSearchVisibleCount", () => { + it("starts at five results", () => { + expect(DEDUP_SEARCH_PAGE_SIZE).toBe(5); + }); + + it("reveals five more and stops at the result count", () => { + expect(nextDedupSearchVisibleCount(5, 12)).toBe(10); + expect(nextDedupSearchVisibleCount(10, 12)).toBe(12); + }); + + it("returns to five when every result is already visible", () => { + expect(nextDedupSearchVisibleCount(12, 12)).toBe(5); + expect(nextDedupSearchVisibleCount(15, 15)).toBe(5); + }); +}); diff --git a/src/lib/dedupSearchWindow.ts b/src/lib/dedupSearchWindow.ts new file mode 100644 index 0000000..9459e6d --- /dev/null +++ b/src/lib/dedupSearchWindow.ts @@ -0,0 +1,9 @@ +export const DEDUP_SEARCH_PAGE_SIZE = 5; + +export function nextDedupSearchVisibleCount( + visibleCount: number, + resultCount: number, +): number { + if (visibleCount >= resultCount) return DEDUP_SEARCH_PAGE_SIZE; + return Math.min(resultCount, visibleCount + DEDUP_SEARCH_PAGE_SIZE); +}