From 8e57789dd7304f50b95cfcf9dd818c36f26b044f Mon Sep 17 00:00:00 2001 From: Anders Olsson Date: Sat, 6 Jun 2026 10:20:26 +0200 Subject: [PATCH] docs(specs): searchable term/authority combobox picker (#27) Co-Authored-By: Claude Opus 4.8 (1M context) --- ...searchable-term-authority-picker-design.md | 128 ++++++++++++++++++ 1 file changed, 128 insertions(+) create mode 100644 docs/superpowers/specs/2026-06-06-searchable-term-authority-picker-design.md diff --git a/docs/superpowers/specs/2026-06-06-searchable-term-authority-picker-design.md b/docs/superpowers/specs/2026-06-06-searchable-term-authority-picker-design.md new file mode 100644 index 0000000..caf2fac --- /dev/null +++ b/docs/superpowers/specs/2026-06-06-searchable-term-authority-picker-design.md @@ -0,0 +1,128 @@ +# Searchable Term/Authority Picker — Design + +**Date:** 2026-06-06 +**Status:** Approved (brainstorming) — ready for implementation planning. +**Issue:** #27. + +## Context + +The object authoring form renders **term** and **authority** flexible fields as native +``). + +## Error handling / edges + +- **Options still loading** (`useTerms`/`useAuthorities` pending): the combobox shows the + placeholder and is effectively empty/disabled until options arrive — same as the current + ``, which also can't render a missing option. +- **Empty vocabulary**: the combobox shows the placeholder and an empty list. + +## Testing + +- **`web/src/objects/field-input.test.tsx`** (update): drive the combobox instead of the + native select — open it, type to filter, assert only matching options show, select one and + assert the rhf value is the option **id**; clear and assert `""`. The popup renders in a + **portal**, so query it via `within(document.body)` (the established pattern from the + dialog work). Keep the existing non-term/authority field-type tests (text/integer/date/ + boolean/localized_text) untouched. +- **Storybook** (`web/src/objects/field-input.stories.tsx` or a focused + `combobox.stories.tsx`): stories for the combobox — default/placeholder, filter-as-you-type, + a selected value. Mirror the established story format (`@storybook/react-vite`, + `storybook/test`, `tags: ['ai-generated']`, single quotes). (Per the standing rule: stories + for meaningful interactive components.) +- **Bundle:** `pnpm check:size` — the **index** chunk stays ≤ 150 KB gz (combobox weight is in + the lazy object-form chunk; confirm the index didn't grow materially). + +## Acceptance criteria + +1. Term and authority fields render a **searchable combobox** that filters the loaded options + by active-locale label as you type; selecting commits the term/authority **id** (value + contract unchanged); the field is clearable when not required. +2. Built on **Base UI's `combobox`** primitive via a `ui/combobox.tsx` wrapper — no new npm + dependency. +3. `useTerms`/`useAuthorities` unchanged (client-side filtering; no backend change). +4. `field-input.test.tsx` covers open/filter/select/clear; a Storybook story exists; other + field types unchanged. +5. `pnpm typecheck` + `pnpm lint` (no `any`/`eslint-disable`/`@ts-ignore`) + `pnpm test` + + `pnpm build` green; index bundle ≤ 150 KB gz; en/sv parity for any new i18n keys; no codename. + +## Out of scope → follow-up issue + +- **Server-side term/authority search** (`GET /api/admin/vocabularies/{id}/terms?q=`, + `authorities?q=`, debounced, top-N) for genuinely large vocabularies, **and** resolving a + selected id→label when the item isn't in the filtered/loaded set (needs a by-id lookup). + File this as a new issue when a vocabulary actually grows large. +- Multi-select term/authority fields (the schema is single-value today).