Skip to content

feat(ui): controlled-vocab inputs — combobox + multiselect + renames#198

Open
gerchowl wants to merge 5 commits into
feat/tailwind-daisyuifrom
feat/vocab-inputs
Open

feat(ui): controlled-vocab inputs — combobox + multiselect + renames#198
gerchowl wants to merge 5 commits into
feat/tailwind-daisyuifrom
feat/vocab-inputs

Conversation

@gerchowl

@gerchowl gerchowl commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

Stacked on #197. Controlled-vocabulary inputs for data quality + a popup row editor.

What

  • Combobox-with-create + fuzzy (ui/components/combobox.ts) for vendor / location: typing "digikey" surfaces the canonical "Digi-Key" (Fuse) so spellings converge; or deliberately create a new value. Accessible (role=combobox/listbox, ↑/↓/Enter/Esc).
  • Tags-input multiselect (ui/components/tags-input.ts) for components: chips picked from existing / staged-for-mint part IDs instead of ;-typed text. Storage stays ;-joined per the contract.
  • Vocab store (registry/vocab.ts + vocab-files.ts): suggestions = data-repo vocabularies/*.json ∪ contract seeds ∪ registry values ∪ session-staged. New values are written back to the data-repo JSON on submit (committed onto the same proposal branch — the addition shows up in the review PR diff). JSON so entries are expandable later. Best-effort: a vocab hiccup never fails a submit.
  • Header renames (contract SSoT → everywhere): Part numberPart #, Manufacturer IDExternal ID.
  • Popup row editor (ui/components/row-editor.ts): a pencil per Bind row opens a roomy modal form (same controls) — easier than editing across the scrolling table. Also fixed the base .modal-card to be a real card (it was position:relative only).

Verification

290 unit + 78 e2e green. New: 36 unit tests (combobox 9, tags 8, vocab 4, vocab-files 9, row-editor 5, +data-table 7 from #197 base) + 3 Bind e2e (fuzzy/create, tags, popup-edit-persists). Combobox + popup verified live via screenshots.

Note

The data-repo vocabularies/*.json are absent until first written, so the read path logs two benign 404s (handled gracefully; filtered in the smoke test). Seeding vendors.json/locations.json in the sandbox removes them.

🤖 Generated with Claude Code

gerchowl added 5 commits June 1, 2026 16:07
Building blocks for controlled-vocabulary inputs (PR3):

- ui/components/combobox.ts — fuzzy-match + create-new control for vendor /
  location. Operators pick an existing value (typing "digikey" surfaces the
  canonical "Digi-Key" via Fuse) or deliberately create a new one, flagged
  `isNew` so the caller can persist it. Accessible (role=combobox/listbox,
  aria-expanded/activedescendant, ↑/↓/Enter/Esc); closing commits typed text.
- ui/components/tags-input.ts — multiselect chips for the components field
  (existing / staged-for-mint IDs instead of ";"-separated text). Storage
  stays ";"-joined; formatTag prettifies the chip while the value stays
  canonical. Backspace removes the last chip.
- Contract label renames (SSoT → propagates to every table header + form):
  "Part number" → "Part #", "Manufacturer ID" → "External ID".

17 new unit tests (9 combobox + 8 tags-input). 273 unit green; build clean.
Wiring into the Bind cells + vocab write-back lands next.
Wire the PR3 controlled-vocab components into the Bind queue cells:

- vendor / location → makeCombobox: fuzzy pick-or-create. Suggestions come
  from the new registry/vocab.ts store (data-repo vocab ∪ contract seeds ∪
  registry values ∪ session-staged). A created value is staged via
  stageVocabValue for write-back to the data-repo vocabulary.
- components → makeTagsInput: chips picked from existing / staged-for-mint
  part IDs (componentCandidates), displayed grouped via fmtId, stored
  ";"-joined per the contract.
- renderBindRow now receives ctx (for vocab); showFieldErrors widened to
  HTMLElement so the tags wrapper can carry validation state.

New registry/vocab.ts (sourcing + session staging; data-repo JSON fetch +
submit write-back land next). 4 vocab unit tests + a Bind e2e asserting
fuzzy-surfaces-canonical, create-new, and chip-add. 281 unit + 77 e2e green.
Persist the controlled vocabularies as structured JSON in the data repo so
additions are reviewable and entries are expandable:

- registry/vocab-files.ts: vocabularies/{vendors,locations}.json shape +
  pure parse/merge/serialize helpers. loadVocabularies() enriches combobox
  suggestions on startup (non-blocking; degrades to contract seeds + registry
  values if the files are absent — a 404 is expected pre-seed).
- submit.ts: writeBackVocab() merges session-created values into the data-repo
  JSON and commits them onto the same proposal branch, so a brand-new
  vendor/location shows up in the review PR diff. Best-effort — a vocab hiccup
  never fails a submit whose registry.csv already committed; staged values
  clear on success.

JSON (not flat text, per the design decision) so an entry can later carry
{ url, aliases, parent, ... } without a format change.

smoke.spec: optional-resource 404s (the absent vocab files) are filtered from
the boot-error assertion — they're handled by app logic, not JS errors, and
the pageerror handler still catches real exceptions.

9 vocab-files unit tests (parse/merge/serialize). 285 unit + 77 e2e green.
"Edit as a popup, like Lookup" — a pencil button on each Bind row opens a
roomy vertical form (the same combobox/tags controls as the inline cells)
instead of editing across the scrolling 11-column table.

- ui/components/row-editor.ts: openRowEditor({title, fields, values, ctx,
  onSave}) over a shared modal. vendor/location → combobox, components →
  tags-input, the rest → plain inputs. Operates on a flat values map; the
  caller persists on Save. metadata/typeFields stay on the inline Properties
  sub-row (fields = the json-excluded editable set).
- Bind rows gain a pencil action that seeds the editor from the queue item
  and writes all fields back on Save.
- SSoT fix: the base .modal-card was only `position:relative` (every feature
  modal hand-rolled its own card), so a new modal rendered transparent. Gave
  .modal-card a default card appearance (bg/border/padding/shadow/max-height);
  .detail-modal/.recovery-dialog keep their own specifics.

5 row-editor unit tests + a Bind e2e (open → edit → Save persists, reopen
confirms). 290 unit + 78 e2e green; popup verified clean via screenshot.
Give part-registry a distinctive aesthetic (replacing generic DaisyUI
defaults), executed on the new theme substrate:

- Self-hosted fonts (bundled → offline-safe PWA): Saira Semi Condensed for
  the wordmark/headers, Saira (variable) for UI, IBM Plex Mono for IDs and
  readouts. No external CDN.
- DaisyUI themes reworked: "dark" = coal front-panel (#16181C) with a single
  signal-amber accent (#FFC23C) — the default + hero; "light" = a warm-paper
  variant for the toggle. Sharp 2-3px radii (machined), hairline borders.
- Instrument detailing in style.css: faint grid + top amber wash + film-grain
  overlay; a measurement-tick ruler under the header; an amber pulse-dot on
  the wordmark; tracked-uppercase display headers; monospace IDs; status
  rendered as glowing LEDs (amber=bound, dim=unbound, red=void); kind badges
  in the same LED language; recessed inputs with an amber focus ring; modals
  as lifted faceplates.
- Motion: a staggered power-on boot reveal (header → nav → body), a subtle
  per-tab redraw, amber engaged-underline on the active tab, signal-amber row
  hover (inset edge). All gated behind prefers-reduced-motion.

290 unit + 78 e2e green (fonts bundled, no new network). Verified across
Lookup/Bind/Print + the popup editor via screenshots.
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