Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion specs/engineering/features/symbols-semantics/registry.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
---

Expand Down Expand Up @@ -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. |

Expand Down
73 changes: 44 additions & 29 deletions src/components/Duplicate.tsx
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -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";
Expand All @@ -44,7 +49,9 @@ import { MathHubDuplicateDialog } from "./MathHubDuplicateDialog";
export function Duplicate({ symbolName }: { symbolName: string }) {
const [pendingMathHubDuplicate, setPendingMathHubDuplicate] =
useState<PendingMathHubDuplicate | null>(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<ConfirmDialogKind | null>(null);
const [dialogLoading, setDialogLoading] = useState(false);
const [editingId, setEditingId] = useState<string | null>(null);
Expand Down Expand Up @@ -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));
}
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -380,39 +387,47 @@ export function Duplicate({ symbolName }: { symbolName: string }) {
</Box>

<Stack w="55%" gap="sm">
{isSearching && <Loader size="xs" />}
<TextInput
aria-label={`Search MathHub for ${symbolName}`}
value={searchQuery}
onChange={(event) => setSearchQuery(event.currentTarget.value)}
placeholder="Search MathHub"
/>

{visibleResults.map((r) => {
const parsed = parseUri(r.uri);
const safeUri = parsed.conceptUri;
return (
<MathHubSearchResult
key={safeUri}
safeUri={safeUri}
floDownBlock={floDownBlock!}
localSymbolUri={symbol?.id ?? selectedDefiniendum?.uri ?? ""}
setPendingMathHubDuplicate={setPendingMathHubDuplicate}
/>
);
})}
{isSearching && <Loader size="xs" />}

{mathHubResults.length > 2 && (
{floDownBlock &&
visibleResults.map((r) => {
const parsed = parseUri(r.uri);
const safeUri = parsed.conceptUri;
return (
<MathHubSearchResult
key={safeUri}
safeUri={safeUri}
floDownBlock={floDownBlock}
localSymbolUri={symbol?.id ?? selectedDefiniendum?.uri ?? ""}
setPendingMathHubDuplicate={setPendingMathHubDuplicate}
/>
);
})}

{mathHubResults.length > DEDUP_SEARCH_PAGE_SIZE && (
<Button
size="xs"
variant="subtle"
onClick={() =>
setVisibleCount((prev) =>
prev >= mathHubResults.length ? 2 : prev + 3,
nextDedupSearchVisibleCount(prev, mathHubResults.length),
)
}
>
{visibleCount >= mathHubResults.length
? "Show Less"
: "Show More"}
? "Show less"
: `Show ${DEDUP_SEARCH_PAGE_SIZE} more`}
</Button>
)}

{mathHubResults.length === 0 && !isSearching && (
{searchReady && mathHubResults.length === 0 && !isSearching && (
<Text size="xs" c="dimmed">
No results found in MathHub
</Text>
Expand Down
21 changes: 21 additions & 0 deletions src/lib/dedupSearchWindow.test.ts
Original file line number Diff line number Diff line change
@@ -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);
});
});
9 changes: 9 additions & 0 deletions src/lib/dedupSearchWindow.ts
Original file line number Diff line number Diff line change
@@ -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);
}
Loading