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…