From 0c6f3a97a1d3935066abfe5fd42a0f41b5aad00c Mon Sep 17 00:00:00 2001 From: Jorge Rivera Date: Wed, 21 Oct 2026 19:03:35 +0200 Subject: [PATCH] refactor(search): type the search spec, part and binding parameters The DataSearch components and the src/lib/search helpers take the page's Spec, Part and Slot types where they took any, so a reader that skips an absent member fails to compile. SpecReading is Partial | UnreadableSpec, and an unreadable spec's stat_vars is unknown, so StatVars reach a reader only through readStatVars. A spec flagged statVarsUnreadable supplies no StatVar count, years or members from the value that failed validation. priceBasis reads a prices value other than current or constant as unknown, and withResolvedSlotEdit returns the identity unchanged for a spec with no slots. Test fixtures go through partialSpec and partialSlot, which take a deep partial of the wire type; two fixtures that named non-members of ObservationSeries now name stat_var. --- .../organisms/DataSearch/ReadingCards.tsx | 7 +- .../organisms/DataSearch/SearchCards.tsx | 9 +- .../organisms/DataSearch/SearchPanel.tsx | 4 +- .../organisms/DataSearch/SearchSentence.tsx | 47 ++- .../organisms/DataSearch/SearchTable.tsx | 5 +- .../organisms/DataSearch/UnitPicker.tsx | 5 +- .../organisms/DataSearch/WhyPanel.tsx | 4 +- .../organisms/DataSearch/editors.tsx | 43 +- .../organisms/DataSearch/useSearchRun.ts | 23 +- src/lib/search/measureMenu.ts | 6 +- src/lib/search/promote.ts | 5 +- src/lib/search/refineMenu.ts | 13 +- src/lib/search/resultNotes.ts | 12 +- src/lib/search/searchClient.ts | 4 +- src/lib/search/test/fixtures/partial.ts | 23 + src/lib/search/test/fixtures/served-answer.ts | 22 +- src/lib/search/test/origin.test.ts | 76 ++-- src/lib/search/test/previewColumns.test.ts | 2 +- src/lib/search/test/refineMenu.test.ts | 8 +- src/lib/search/test/resultNotes.test.ts | 30 +- src/lib/search/test/tokenAbout.test.ts | 70 ++-- src/lib/search/test/unitLabel.test.ts | 44 +- src/lib/search/test/unitPicker.test.ts | 30 +- src/lib/search/test/view.test.ts | 89 ++-- src/lib/search/test/why.test.ts | 171 +++++--- src/lib/search/tokenAbout.ts | 81 ++-- src/lib/search/types.ts | 12 +- src/lib/search/unitLabel.ts | 31 +- src/lib/search/unitPicker.ts | 19 +- src/lib/search/view.ts | 393 +++++++++++------- 30 files changed, 776 insertions(+), 512 deletions(-) create mode 100644 src/lib/search/test/fixtures/partial.ts diff --git a/src/components/organisms/DataSearch/ReadingCards.tsx b/src/components/organisms/DataSearch/ReadingCards.tsx index a91c00c..4ce4081 100644 --- a/src/components/organisms/DataSearch/ReadingCards.tsx +++ b/src/components/organisms/DataSearch/ReadingCards.tsx @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-explicit-any -- takes candidate specs as `any` and reads only the members it renders, tolerating any that is missing */ 'use client'; import { useMemo } from 'react'; @@ -10,7 +9,7 @@ import Button from '@/components/atoms/Button/Button'; import { isClosestMatch } from '@/lib/search/departures'; import { basisNote, readingOf, readingsSummary } from '@/lib/search/readings'; import { tokenAbout } from '@/lib/search/tokenAbout'; -import type { CandidatesPart } from '@/lib/search/types'; +import type { CandidatesPart, Spec } from '@/lib/search/types'; import { statVarsUnreadable } from '@/lib/search/view'; /** @@ -32,11 +31,11 @@ const ReadingCards = ({ part: CandidatesPart; // The part's variable text, when the answer has several parts. prefix: string | null; - onPick: (spec: any) => void; + onPick: (spec: Spec) => void; // Re-runs the question, for a reading the client could not read. onRetry: () => void; }) => { - const specs: any[] = useMemo(() => part.candidates.specs, [part]); + const specs = useMemo(() => part.candidates.specs, [part]); const readings = useMemo( () => specs.map((spec) => { diff --git a/src/components/organisms/DataSearch/SearchCards.tsx b/src/components/organisms/DataSearch/SearchCards.tsx index a7e6627..24ead24 100644 --- a/src/components/organisms/DataSearch/SearchCards.tsx +++ b/src/components/organisms/DataSearch/SearchCards.tsx @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-explicit-any -- takes specs as `any` and reads only the members it renders, tolerating any that is missing */ 'use client'; import classNames from 'classnames'; @@ -21,12 +20,12 @@ import { specOrigin, statVarsUnreadable, } from '@/lib/search/view'; -import { knownPart, type ParsedPart, type Part } from '@/lib/search/types'; +import { knownPart, type ParsedPart, type Part, type PartSpec } from '@/lib/search/types'; // A card's meta line, its coverage then its origin (specOrigin beside the // spec's own sentence, shapeOrigin beside any other title). The line clamps // at two lines, so the full text goes in the title attribute. -const CardMeta = ({ spec, origin }: { spec: any; origin: string | null }) => { +const CardMeta = ({ spec, origin }: { spec: PartSpec; origin: string | null }) => { const meta = [coverageSummary(spec), origin].filter(Boolean).join(' · '); return ( @@ -97,7 +96,7 @@ const SearchCards = ({ Multiple possible interpretations found. Select the best fit…

- {specs.map((spec: any, index: number) => ( + {specs.map((spec, index) => (