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.
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").
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/labelslabelText. 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.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
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
<select>for term/authority fields with a searchable combobox (the shadcn Combobox/Command, or an equivalent) that filters as you type. (Note: a shadcnSelectwas added then removed during M2 since the native select was used; pick the combobox primitive deliberately.)GET /api/admin/vocabularies/{id}/terms?q=) and have the combobox query it (debounced) rather than loading all terms. Same for authorities by kind.References
web/src/objects/field-input.tsx(OptionsSelect,TermField,AuthorityField)web/src/api/queries.ts(useTerms,useAuthorities)docs/superpowers/specs/2026-06-04-frontend-spa-milestone-2-design.md→ "Out of scope / follow-ups").Done in
de035bd(merged tomain). Term/authority fields now render a searchable combobox built on Base UI'scomboboxprimitive (no new npm dependency): type-to-filter by active-locale label, client-side, with thevalue = idcontract preserved and a selected-item checkmark. The native<select>(OptionsSelect) is removed;labelInwas deduped to the sharedlib/labelslabelText. 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.