Skip to content

Tooling pass: TS5, Sentry 8, ESLint 8, Husky 9, Prettier 3, small bumps#312

Open
ankit0504 wants to merge 28 commits intoLanguage-Mapping:masterfrom
ankit0504:agupta/upgrade-phase-7-tooling
Open

Tooling pass: TS5, Sentry 8, ESLint 8, Husky 9, Prettier 3, small bumps#312
ankit0504 wants to merge 28 commits intoLanguage-Mapping:masterfrom
ankit0504:agupta/upgrade-phase-7-tooling

Conversation

@ankit0504
Copy link
Copy Markdown
Collaborator

Summary

Phase 7 of the dependency-upgrade series — the tooling/hygiene pass. Stacks on #311 (phase 6) — please merge that one first; the diff here will include phase-6 commits until it lands.

  • TypeScript 4.9 → 5 (clean, no code changes)
  • Sentry 5 → 8 (Severity enum gone, ErrorBoundary types fixed)
  • Husky 4 → 9 (config moved to .husky/)
  • Prettier 2 → 3 (no formatting churn — codebase already conforms)
  • lint-staged 10 → 15 (drop-in)
  • ESLint 7 → 8 + airbnb v19 + @typescript-eslint v7 (kept airbnb-style rules; ESLint 9 + flat config is deferred)
  • airtable 0.10 → 0.12, match-sorter 4 → 6, clsx 1 → 2, history minor (small drop-ins)

Why ESLint 8 instead of 9

ESLint 9 requires flat config, and eslint-config-airbnb-typescript hasn't shipped a flat-config-compatible release. Going to ESLint 9 cleanly means dropping airbnb and rewriting .eslintrc as eslint.config.js from scratch. That's a separate decision worth making deliberately rather than baking into a tooling-bumps PR.

ESLint 8.57 is the last 8.x release; it's in maintenance only. A future phase can do the flat-config migration with whichever ruleset replaces airbnb (the standard recommended presets, or @antfu's config, or a Biome migration).

Notable code changes

  • Sentry's Severity enum was removed in v7+ — pass severity strings directly ('warning' instead of Sentry.Severity.Warning).
  • The v6 @ts-ignore for Sentry's ErrorBoundary children prop is no longer needed; v8 types it correctly.
  • Husky 9 dropped the package.json husky.hooks config; pre-commit now lives in .husky/pre-commit and re-installs via the prepare npm lifecycle.
  • airtable@0.12+ types select().all() as Records<FieldSet> rather than Records<TResult>. airtableQuery is now a generic async function (TSX requires a function declaration to disambiguate the generic from JSX) that casts the result into { fields: TResult }[].
  • match-sorter@6 removed its default export — now import { matchSorter, rankings } from 'match-sorter'.
  • A handful of pre-existing lint issues that airbnb v19's stricter @typescript-eslint/no-redeclare, no-use-before-define, and no-unnecessary-type-constraint rules surfaced — all small reorganizations.
  • Dropped eslint-plugin-flowtype (codebase has no Flow), eslint-plugin-jest (no Jest tests), prettier-eslint/prettier-eslint-cli (never invoked from scripts).

What's next

After this phase, the remaining items are mostly opportunistic:

  • MUI v5 → v7@mui/styles was dropped in v6. Migrating means rewriting all 91 makeStyles/withStyles call sites to sx/styled(). Substantial enough to deserve its own phase.
  • mapbox-gl v1 → v3 — Mapbox went paid-tier for SDK usage at v2. Needs a product/cost decision before bumping.
  • react-markdown 5 → 9, react-router-dom 6 → 7, react-window 1 → 2 — medium-sized API rewrites; do as wanted.
  • Testing Library suite — only matters if tests are revived.
  • Abandoned helpers (filefy, webfontloader, jest-environment-jsdom-sixteen) — replace or delete.
  • ESLint 9 + flat config — deferred from this PR. Pick a ruleset (recommended presets, antfu, or Biome) and rewrite the config.

Test plan

  • App boots without console errors.
  • Sentry init succeeds (no errors at app load).
  • Search the omnibox — match-sorter still ranks results correctly.
  • Make a change that fails lint, try to commit — .husky/pre-commit runs lint-staged and blocks the commit.
  • yarn build produces a clean bundle.
  • yarn lint is clean.

ankit0504 and others added 28 commits April 20, 2026 12:43
Replace react-scripts 3.4.1 with Vite 6 + @vitejs/plugin-react. This
drops the NODE_OPTIONS=--openssl-legacy-provider workaround required
for Node 17+ on CRA, and shrinks cold dev start to ~125ms and prod
build to ~5s (from ~33s).

Changes:
- Swap scripts: yarn start -> vite; yarn build -> tsc --noEmit &&
  vite build; yarn preview -> vite preview; drop eject/test.
- Replace CRA deps with vite, @vitejs/plugin-react, vite-plugin-svgr,
  vite-tsconfig-paths.
- Add vite.config.ts configuring the React and SVGR plugins, the
  REACT_APP_ env prefix (keeps .env working as-is, with VITE_ also
  accepted), port 3000, and build output to build/ (preserving the
  Netlify publish dir).
- Move public/index.html to project root. Replace %PUBLIC_URL%/ with
  / and add the Vite module entry script.
- Remove the CRA eslintConfig stanza in package.json; real config
  lives in .eslintrc.
- Add a direct eslint@^7.32.0 devDep (previously hoisted by CRA),
  drop the react-app extends that no longer resolves, and remove the
  deleted serviceWorker.ts path from ignorePatterns.
- Delete src/serviceWorker.ts and its import; it was already calling
  unregister() only, so removal is a no-op at runtime.
- Rewrite six process.env.REACT_APP_* reads as import.meta.env.
- Change Logo.tsx SVG import to use the vite-plugin-svgr ?react
  suffix.
- Update tsconfig.json: target ES2020, add vite/client and
  vite-plugin-svgr/client types, exclude *.test.ts/tsx files from
  the build since there is no test runner anymore.
- Delete src/setupTests.ts (orphaned CRA jest setup).

Known follow-ups (deliberately out of scope for this PR):
- @mapbox/mapbox-gl-geocoder imports the Node events module, which
  Vite externalizes for the browser. If the geocoder search breaks at
  runtime, it can be addressed by swapping react-map-gl-geocoder for
  a direct mapbox-gl-geocoder integration in a later phase.
- yarn lint surfaces 55 pre-existing errors across the codebase that
  were previously masked by CRA's react-app eslint preset. These are
  unrelated to the build migration and should be addressed in a
  follow-up. Pre-commit lint-staged only runs against staged files,
  so new commits are unaffected.
Removing react-scripts in the previous commit unmasked pre-existing
eslint errors that CRA's react-app preset had been silencing.

Cleanup by category:
- 40 @typescript-eslint/no-unused-vars: set { args: "none" } in the
  rule config. These were all unused callback parameters (mostly
  createStyles theme args and event/props args in handlers) required
  by library type signatures. The project-level policy is to not
  enforce unused function arguments; genuine unused variables and
  imports are still checked.
- 5 @typescript-eslint/no-use-before-define: reorder the five helper
  function definitions so each precedes its call site. Affected:
  createLayerStyles (hooks.points.ts), handleClose (SplitCrumbs.tsx),
  scrollToTop (ResultsTable.tsx), clearFiltersBtnClick
  (ResultsToolbar.tsx), excludeUTFtext (exporting.ts).
- 6 jsx-a11y/anchor-is-valid: five MUI <Link> usages that are
  semantically buttons now use component="button" and drop the dummy
  href="#" plus its e.preventDefault() plumbing. The sixth
  (MapOptionsMenu) already uses component={Button}. All six get an
  eslint-disable-next-line comment because the rule inspects JSX
  element names and doesn't analyze MUI's component prop -- the
  markup is correct, the rule is a false positive.
- 3 parser errors in excluded test files: add src/**/*.test.ts(x) to
  .eslintrc ignorePatterns. The tsconfig already excludes these from
  the build.
The airtable package references process.env at runtime, which
webpack/CRA provided but Vite does not. Defining an empty object
prevents the ReferenceError without leaking server-side env vars.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Rename vite.config.ts to vite.config.mts for ESM compatibility
- Replace MUI Link-as-button with Button in 6 components
- Pass Airtable API key explicitly instead of relying on process.env
- Migrate remaining process.env reference to import.meta.env

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Bump react/react-dom to 17.0.2 and @types/react/@types/react-dom to
the latest 17.x. Also bump @types/react-router-dom to 5.3.3 (5.1.5
was typed against React 16 and triggered TS2786 'cannot be used as
a JSX component' errors for RouterLink passed to MUI component
props).

Add @types/react and @types/react-dom to the resolutions field so
that transitively-installed type packages (eight of them — MUI,
react-map-gl, react-swipeable-views, etc. all vendored their own
@types/react@16) do not conflict with the top-level version. Without
this, tsc sees multiple React type identities and cannot reconcile
JSX element returns across package boundaries.

React 17 has no API surface changes that affect this project, so no
component code changes are required. React 18 is intentionally
deferred to a later phase, after MUI v5 lands — react-swipeable-views
is archived and breaks in React 18 StrictMode, and MUI v4 has React
18 peer-dep conflicts.

Known-ignorable peer-dep warnings on install: react-query@2,
react-share@4, react-swipeable-views, react-window, and
react-router-dom all still declare React 16 as their max in their
peer range, but each of them works with React 17 in practice.
Mechanical rename of @material-ui/core, @material-ui/lab, and
@material-ui/styles imports to their @mui/* equivalents. Adds Emotion
(MUI v5's default styling engine) and keeps @mui/styles as a JSS shim
so the existing makeStyles/withStyles call sites keep working.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
react-swipeable-views was used in exactly one place (SearchTabs.tsx)
to wrap two TabPanel children. Removed the swipe affordance and let
TabPanel's existing index/value gating handle which pane is shown.
The library is unmaintained and incompatible with React 17+.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
material-table is unmaintained and incompatible with MUI v5. Swap it
for @mui/x-data-grid (community) which covers filtering, sorting,
column visibility, pagination, and quick filter out of the box.

Notable behavior changes:
- The "click cell to filter by its value" interaction is dropped. It
  was implemented via material-table's imperative dataManager API and
  has no clean Data Grid equivalent. Filters are now driven by the
  built-in column filter UI plus the quick-filter search box.
- The MediaColumnFilter checkbox is reimplemented as a custom
  GridFilterOperator on the Audio/Video columns.
- Custom CSV/PDF export buttons (exporting.ts) are no longer wired up
  in the toolbar. CSV is available via the column menu; if PDF export
  is still needed it can be re-added as a slot button later.
- Sticky first/second columns are dropped — Data Grid pinning is a
  Pro feature and the actions columns work fine without it.

Move clsx, filefy, history, jspdf, jspdf-autotable, and mapbox-gl
from peerDependencies (where yarn auto-installed them) into regular
dependencies. With material-table removed they're no longer pulled
transitively.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The codemod handles renames but leaves several v5 API changes for
manual cleanup:

- theme: replace post-creation `customTheme.overrides = {...}` with
  the v5 `components: { ComponentName: { styleOverrides: {...} } }`
  shape passed into a second createTheme call.
- ProvidersWrap: import Theme/StyledEngineProvider from
  @mui/material/styles (not @mui/styles, which no longer re-exports
  them in v5).
- palette.text.hint is removed in v5 — swap to palette.text.disabled.
- Autocomplete renderOption: new signature receives (props, option),
  so wrap children in <li {...props}>.
- BottomNavigationAction: drop the `wrapper` and `label` class slots
  (no longer part of the component's classes shape).
- Fab: drop the `label` class slot and inline its sizing on root.
- TimelineDot: variant `'default'` is gone, use `'filled'`.
- ToggleButton: remove non-standard `color="blue"`; the default works.
- Slide TransitionComponent: children must be required, not optional.
- Demo files: minor typing fixes for ClickAwayListener and Select
  onChange handlers.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ns buttons

The previous commit dropped the export buttons because Data Grid
doesn't expose a custom format hook the way material-table did, but
the existing exporting.ts utilities still work — they just need to
be called with the currently-visible rows from apiRef. Pass the
column config through slot props.

Also surface Data Grid's built-in Filters and Columns toggles in the
toolbar. They were technically reachable through each column's
hover-only menu, which was hard to find.

Inline the localIndicator styles in LocalColumnTitle to avoid an
import cycle (config -> LocalColumnTitle -> ResultsToolbar -> exporting
-> config) introduced by the toolbar now needing exporting.ts.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Data Grid uses a fixed row height by default. Cells with multiple
values per row (Country with two flags, Endonym wrapping non-Latin
scripts, etc.) were getting clipped and bleeding into adjacent rows.
Setting getRowHeight to 'auto' lets each row grow to fit its
tallest cell; getEstimatedRowHeight keeps virtualization fast on
the 1000+ row dataset. Cell content is top-aligned so single-line
columns line up cleanly with multi-line ones.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The MediaFilterInput rendered the checkbox too high because the
default FormControlLabel doesn't share Data Grid's filter input
baseline. Bottom-align it and pull the original 'http' marker color
in via the secondary palette so the checkbox tone matches the other
filter controls.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Data Grid's free tier (community v6) hard-codes
disableMultipleColumnsFiltering: true, so users could only filter
on one column at a time. Multi-column filtering is paywalled to the
Pro plan in v6, and v5 — the last version that had it free — was
last released March 2023 and is no longer maintained.

TanStack Table v8 is headless and actively maintained, supports
React 16/17/18+, and its filter model has no paywall. The trade-off
is that we render the table primitives ourselves with MUI Table /
TableHead / TableBody, which lets us bring back material-table's
per-column filter row UX (text input by default, Select for lookup
columns, the Has-media checkbox for Audio/Video).

Other features carried over:
- Global search box (matches across visible columns)
- Column visibility menu in the toolbar
- CSV / PDF export wired to filtered+sorted rows
- "View in map" and "Clear filters" buttons unchanged

Column config moved to TanStack column defs via createColumnHelper.
Cell renderers now receive a CellContext and read row.original;
filter logic for Audio/Video lives in mediaUrlFilterFn.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
React throws when a function passed to flexRender returns undefined.
Several columns (Glottocode, ISO 639-3, Additional Neighborhoods,
Endonym, etc.) are nullable in the dataset, and their default cell
renderers were `info.getValue()` — which returns undefined for any
row where the field is missing.

Coerce all simple-text cells with ?? '' and add a guard in
renderCountryColumn / renderEndoColumn for rows without those fields.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The column header already says "Audio" / "Video"; the checkbox in
the filter row beneath it is unambiguous. Replace the labelled
FormControlLabel with a bare Checkbox and put the explanatory text
in the title attribute for hover discoverability.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The toolbar was passing the static column config to exportCsv/exportPdf
regardless of which columns the user had hidden via the column
visibility menu. Filter the config by columnVisibility before handing
it to the export utilities so a downloaded CSV/PDF reflects exactly
what's on screen.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
react-query v2 has been unmaintained for years; the package was
renamed to @tanstack/react-query at v3, and v4 is the latest line
that still supports React 17 (v5 requires React 18+).

Key shape changes:
- useQuery now takes a single object: { queryKey, queryFn, ...options }
  rather than positional (key, fn, options).
- queryFn receives a QueryFunctionContext ({ queryKey, signal, ... })
  instead of having the key spread as positional args. airtableQuery
  destructures the key out of the context.
- ReactQueryCacheProvider + new QueryCache(...) is replaced by
  QueryClientProvider + new QueryClient({ defaultOptions: {...} }).
- A top-level QueryClientProvider is now required for any useQuery to
  work; added in App.tsx with reactQueryDefaults applied. The
  WordPress-info-panel and the media-modal each get their own
  QueryClient since they had isolated caches before.
- ReactQueryDevtools wired up under import.meta.env.DEV (was
  previously commented out).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
v6 ships its own types so @types/react-router-dom is no longer needed.

Mechanical renames applied across ~30 files:
- Switch -> Routes
- <Route ...><Component /></Route> -> <Route ... element={<Component />} />
- exact prop is gone (exact is the default in v6); paths that used to
  match descendants get a /* suffix
- useHistory -> useNavigate; history.push(x) -> navigate(x);
  history.push({ pathname, state }) -> navigate(pathname, { state })
- useRouteMatch -> useMatch (single path argument; array paths split
  into separate useMatch calls so hook order stays stable)
- useParams<{ id: string }>() typing changed: v6 returns string |
  undefined for every key. Cast to the expected shape with
  `as { id: string }` and default missing keys to '' where the value
  is forwarded into a query string.
- Standalone <Route> outside of <Routes> doesn't render in v6 — those
  guard patterns ("show this if path matches") are now expressed with
  useMatch + a conditional render.
- useLocation no longer accepts a generic; cast loc.state where the
  call site needs typed state.

Top-level <BrowserRouter> in index.tsx is unchanged — same name and
prop shape across major versions.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
react-map-gl v6 reorganised everything around hooks and a controlled
viewport API; v7 trimmed it further and is the line still being
maintained. Key changes:

- Default `MapGL` import becomes a named `Map as MapGL` import.
- Refs typed as `React.RefObject<MapRef>` instead of InteractiveMap.
- `mapboxApiAccessToken` -> `mapboxAccessToken`.
- Drop the manual viewport state. v7 manages view state internally;
  `initialViewState` seeds the map and `flyTo` / `zoomIn` / `zoomOut`
  on the underlying mapbox-gl Map handle programmatic moves. The v5
  custom `forceViewportUpdate` event payload (used to gate moveend
  re-syncs) is no longer necessary.
- `onLoad` receives a `MapboxEvent` from mapbox-gl directly.
- `onClick` / `onMouseMove` (renamed from `onHover`) receive
  `MapLayerMouseEvent`. The cursor target is now the canvas, not the
  HTML wrapper, so `target.style.cursor` becomes
  `map.getCanvas().style.cursor` in events.ts.
- `Popup`'s `tipSize` prop is gone; remove it.
- `Marker`'s `offsetTop` becomes `offset={[x, y]}`.
- `WebMercatorViewport` moved out of react-map-gl into
  `@math.gl/web-mercator` (now a direct dep).

react-map-gl-geocoder is unmaintained and incompatible with v7.
Replaced with `@mapbox/mapbox-gl-geocoder` directly: GeocoderPopout
instantiates a MapboxGeocoder, attaches it into a ref'd container
with `addTo`, and listens for `result` / `clear` events. Cleanup
removes the listeners and the rendered DOM on unmount.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
I missed a couple of standalone <Route> usages outside <Routes> in
the v6 sweep, which v6 throws on at render time:
- ResultsTable.tsx had a guarded <Route path={routes.dataDetail}>
  rendering the DetailsModal — wrap it in <Routes>.
- BasicExploreIntro.tsx had <Route path={routes.explore}> as a
  visibility guard for FiltersWarning — convert to useMatch +
  conditional render to match the pattern used elsewhere.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Default bottom-left collides with the panel content. The toggle is
purely a dev affordance; out of the way is better.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Major version bump. Touches a lot of files but the patterns are
mechanical:

- ReactDOM.render -> createRoot from react-dom/client
- @types/react / @types/react-dom -> v19; lock with `resolutions`
  so transitively-pulled v17 copies don't sneak back in via
  react-markdown / @types/react-window / etc.
- React 18+ removed implicit `children` from FunctionComponent.
  Every `: FC = (props) => { const { children } = props; ... }`
  call site now explicitly types children via PropsWithChildren
  (either `: FC<PropsWithChildren>` for prop-less components or
  `: FC<PropsWithChildren<MyProps>>` when there are existing props).
- React 19 returns `RefObject<T | null>` from useRef instead of
  `RefObject<T>`. Updated MapProps.mapRef and useResetCache return
  type to match.
- Sentry 5.x's ErrorBoundary class component types don't include
  children; suppressed with a single ts-ignore until phase 7 bumps
  Sentry to v6+ which fixes it.
- AboutPageView's outer <div> with mixed conditional+element children
  needed an explicit fragment wrapper for React 19's tightened JSX
  child typing.

Dependency bumps:
- react / react-dom 17 -> 19
- @types/react / @types/react-dom 17 -> 19
- react-icons 4 -> 5 (with a patch-package patch fixing the
  IconType return type from ReactNode to ReactElement, since v5
  declared it as ReactNode and React 19 rejects ReactNode as a
  valid JSX component return)
- react-share 4 -> 5 (v4's prop types were missing
  onPointerEnterCapture/onPointerLeaveCapture which React 19 now
  requires; v5 also drops the long-deprecated FacebookShareButton
  `quote` prop, which was no-op since 2017)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
v5 requires React 18+, which we now have from the React 19 bump.

API changes:
- cacheTime -> gcTime on QueryClient defaultOptions.
- ReactQueryDevtools toggle prop renamed from `position` to
  `buttonPosition` (the v4 `position` set both panel side and button
  corner; v5 splits these — the panel docks via `position`
  ('top'|'left'|'bottom'|'right'), the toggle button corner via
  `buttonPosition`).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
No code changes required. TS5's stricter checks didn't surface any new
errors against the existing types.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
API changes touched:
- `Sentry.Severity` enum was removed in v7+. Pass severity strings
  directly: `'warning'` instead of `Sentry.Severity.Warning`.
- `ErrorBoundary` now properly types `children` so the v5 ts-ignore
  workaround in App.tsx can come out.

Sentry.init / captureException / withScope APIs are unchanged enough
to compile without changes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Husky 9 dropped the package.json `husky.hooks` config in favor of
`.husky/<hook>` files. Add a minimal pre-commit that runs lint-staged
and a `prepare` script so `yarn install` re-hydrates the hook.

Prettier 3 changed defaults — trailing commas now `all`, arrow parens
always — but the codebase already conforms; running `prettier --check`
is clean across `src/`.

lint-staged 15 only changed how it locates the binary; config is
compatible.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ESLint 9 + flat config is a big rewrite (and airbnb-typescript hasn't
caught up to it). The pragmatic step is ESLint 8.57 + airbnb v19 +
@typescript-eslint v7, which keeps the existing .eslintrc shape
working and unblocks plugin updates.

Config tidy:
- eslint-config-prettier 8+ merged the separate `prettier/react` and
  `prettier/@typescript-eslint` configs into a single `prettier`
  entry; updated extends accordingly.
- Drop eslint-plugin-jest (not currently lint-relevant since no Jest
  tests run) and the abandoned eslint-plugin-flowtype.
- Drop prettier-eslint / prettier-eslint-cli — never invoked from
  scripts and superseded by eslint-plugin-prettier alone.
- Add explicit `plugins:` array; legacy implicit-from-extends
  resolution is gone in ESLint 8.

Code fixes triggered by stricter rules:
- defaultQueryFn dropped its now-unnecessary `T extends unknown`.
- FlagWithTitle / LoadingBackdrop type aliases renamed so they no
  longer collide with the same-named exported components.
- DetailsPanel.tsx reordered so Details / DetailsWrap are defined
  before they're referenced.
- ResultsToolbar.tsx: a single eslint-disable-next-line for
  ColumnVisibilityMenu, defined later in the file.
- ScrollToTopOnMount: trigger prop typed `unknown` instead of `any`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- airtable 0.10 -> 0.12: select().all() now types as Records<FieldSet>
  rather than the generic record shape, so airtableQuery is now an
  async function (so we can declare a TResult generic without TSX
  parse ambiguity) that casts the records into our { fields: TResult }
  shape.
- match-sorter 4 -> 6: default export removed, use named imports
  { matchSorter, rankings } from 'match-sorter'.
- clsx 1 -> 2 and history 5 minor bump: drop-in.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant