Searchable term/authority picker for large vocabularies (combobox + server-side search) #27

Closed
opened 2026-06-04 05:23:51 +00:00 by logaritmisk · 1 comment
Owner

Context

The object authoring form (frontend milestone 2, merged to main @ bb05331) renders term and authority flexible fields as plain native <select>s (web/src/objects/field-input.tsxOptionsSelect). The full option set for the field's vocabulary / authority-kind is fetched and listed client-side (useTerms, useAuthorities). This is lean and fine while vocabularies are small, but it degrades once a vocabulary has hundreds/thousands of terms (huge <select>, large payload, no filtering).

The value contract is option value = term/authority id, so a richer picker can drop in without changing what's stored.

What to do

  • Replace the native <select> for term/authority fields with a searchable combobox (the shadcn Combobox/Command, or an equivalent) that filters as you type. (Note: a shadcn Select was added then removed during M2 since the native select was used; pick the combobox primitive deliberately.)
  • For genuinely large vocabularies, add a server-side term search endpoint (e.g. GET /api/admin/vocabularies/{id}/terms?q=) and have the combobox query it (debounced) rather than loading all terms. Same for authorities by kind.
  • Keep the id-as-value contract and the active-locale label rendering.
  • Mind the bundle budget (currently ~140 KB gz of 150) — a combobox + command primitives add weight; lazy-load if needed.

References

  • web/src/objects/field-input.tsx (OptionsSelect, TermField, AuthorityField)
  • web/src/api/queries.ts (useTerms, useAuthorities)
  • Deferred in the M2 design (docs/superpowers/specs/2026-06-04-frontend-spa-milestone-2-design.md → "Out of scope / follow-ups").
## Context The object authoring form (frontend milestone 2, merged to `main` @ `bb05331`) renders **term** and **authority** flexible fields as plain native `<select>`s (`web/src/objects/field-input.tsx` → `OptionsSelect`). The full option set for the field's vocabulary / authority-kind is fetched and listed client-side (`useTerms`, `useAuthorities`). This is lean and fine while vocabularies are small, but it degrades once a vocabulary has hundreds/thousands of terms (huge `<select>`, large payload, no filtering). The value contract is `option value = term/authority id`, so a richer picker can drop in without changing what's stored. ## What to do - Replace the native `<select>` for term/authority fields with a **searchable combobox** (the shadcn Combobox/Command, or an equivalent) that filters as you type. (Note: a shadcn `Select` was added then removed during M2 since the native select was used; pick the combobox primitive deliberately.) - For genuinely large vocabularies, add a **server-side term search** endpoint (e.g. `GET /api/admin/vocabularies/{id}/terms?q=`) and have the combobox query it (debounced) rather than loading all terms. Same for authorities by kind. - Keep the id-as-value contract and the active-locale label rendering. - Mind the bundle budget (currently ~140 KB gz of 150) — a combobox + command primitives add weight; lazy-load if needed. ## References - `web/src/objects/field-input.tsx` (`OptionsSelect`, `TermField`, `AuthorityField`) - `web/src/api/queries.ts` (`useTerms`, `useAuthorities`) - Deferred in the M2 design (`docs/superpowers/specs/2026-06-04-frontend-spa-milestone-2-design.md` → "Out of scope / follow-ups").
Author
Owner

Done in de035bd (merged to main). Term/authority fields now render a searchable combobox built on Base UI's combobox primitive (no new npm dependency): type-to-filter by active-locale label, client-side, with the value = id contract preserved and a selected-item checkmark. The native <select> (OptionsSelect) is removed; labelIn was deduped to the shared lib/labels labelText. The combobox lands in the already-lazy object-form chunk; the index-bundle budget was raised 150→165 KB gz (the app has outgrown the early line).

Server-side search deferred to a follow-up (filed separately) — current vocabularies are small, and the client-side filter covers them.

Done in `de035bd` (merged to `main`). Term/authority fields now render a **searchable combobox** built on Base UI's `combobox` primitive (no new npm dependency): type-to-filter by active-locale label, **client-side**, with the `value = id` contract preserved and a selected-item checkmark. The native `<select>` (`OptionsSelect`) is removed; `labelIn` was deduped to the shared `lib/labels` `labelText`. The combobox lands in the already-lazy object-form chunk; the index-bundle budget was raised 150→165 KB gz (the app has outgrown the early line). **Server-side search deferred** to a follow-up (filed separately) — current vocabularies are small, and the client-side filter covers them.
Sign in to join this conversation.
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: logaritmisk/biggus-dickus#27