Skip to content

Add typeahead + multi-select opt-ins for controlled compound members#7540

Merged
orangewolf merged 9 commits into
mainfrom
compound-controlled-typeahead-multiselect
Jul 17, 2026
Merged

Add typeahead + multi-select opt-ins for controlled compound members#7540
orangewolf merged 9 commits into
mainfrom
compound-controlled-typeahead-multiselect

Conversation

@laritakr

@laritakr laritakr commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Issue:

Summary

Adds two opt-in, per-member form: flags for compound controlled metadata members in the M3 profile. Both default off, so existing controlled members render exactly as before.

  • autocomplete: true — binds a select2 typeahead over the member's <select>. The full option list is already in the DOM, so filtering is client-side (no extra request). Works for single- and multi-select.
  • multiple: true — renders a <select multiple>. At submit time each selected value fans out into its own compound entry, sharing the row's other members (cartesian product when a row has more than one multiple member). A contributor picked with three roles is stored as three single-role entries, not one entry holding an array.

Screenshots

Deposit form — Name and Role controls aligned (empty state):
pr-shot-1-empty-matched

Multi-select typeahead with several roles chosen (pills wrap in one box, no overflow):

image

Edit round-trip — one saved row's roles reopen as separate preselected entries:

pr-shot-3-edit-roundtrip-fixed

Show pg

image

** MOBILE VIEW **

pr-shot-3-mobile

Details

The key design decision is fan-out: the stored shape stays uniform — every member value is a scalar, whether or not the member is multi-select. Consumers never branch on array-vs-scalar, and each term indexes and facets independently. The array only exists transiently between form submit and fan-out; the indexer and show renderer were hardened to tolerate a transient array value because an invalid submission is echoed back before fan-out runs.

Changed (all form / index / render path — nothing touches persistence):

  • app/services/hyrax/compound_schema.rbnormalize_subproperty reads form.autocomplete / form.multiple into the spec (default false).
  • app/forms/concerns/hyrax/compound_field_behavior.rb — the load-bearing change: fans a multi-select row out into one entry per value.
  • app/views/hyrax/compounds/_compound_row.html.erbmultiple<select multiple> with [] name; autocompletedata-hyrax-compound-controlled + placeholder.
  • app/assets/javascripts/hyrax/compound_metadata.jsbindControlledSelects binds select2 v3 to native selects, on load and on cloned rows.
  • app/indexers/.../compound_indexer.rb, app/renderers/.../compound_attribute_renderer.rb, app/helpers/hyrax/compound_fields_helper.rb, app/services/hyrax/compound_subproperty_labeler.rb — tolerate/represent an array member value.
  • config/locales/hyrax.en.yml — new compound_fields.search placeholder key.
  • documentation/compound_fields.md — new section + type-table row.

Behavior holds in both flexible and non-flexible modes (the compound list comes from the effective schema either way). The Enact profile opt-in (adding these flags to contributor_role) is a separate follow-up in enact_knapsack, not part of this PR.

Testing

Unit/spec coverage added for: schema flag parsing, fan-out (several values → several entries; single value → one scalar; blank handling), array-tolerant indexer/renderer/helper/labeler, and a new view spec for the multi/autocomplete markup. Green on all CI test apps (koppie, dassie, allinson, sirenia).

Manual end-to-end verification in a flexible-mode deposit form (allinson), with autocomplete: true + multiple: true on a controlled compound member:

  • role control renders as a select2 typeahead over a native <select multiple>; typing filters client-side;
  • picking two roles for one participant, saving → the row fans out into two separate scalar entries on the show page (Name / Role per entry), each faceting independently;
  • reopening edit → the two entries re-collapse into their rows, each preselected;
  • "Add" clones a row whose select is also a searchable multi-select.

select2 styling

select2 v3 copies the control's .form-control classes onto its own container (Bootstrap border + fixed .form-control-sm height) while rendering its own bordered inner box, producing a "box within a box" and clipping a multi-select's chosen pills. _compound_metadata.scss flattens the inner box so only the form-control frame shows and lets the frame grow to fit the selected values. Verified in a flexible-mode deposit form (single clean box; multiple pills wrap without overflowing into the next field).

Add two opt-in `form:` flags for a compound `controlled` member:
`autocomplete: true` binds a select2 typeahead over the options
(filtered client-side, since the full list is already in the DOM),
and `multiple: true` renders a multi-select. Both default off, so
existing controlled members are unchanged.

A `multiple` member fans each selected value out into its own
compound entry at submit time, sharing the row's other members.
This keeps every stored member value a scalar — consumers never
branch on array-vs-scalar, and each term indexes and facets on its
own. The indexer and show renderer still tolerate a transient array
value, since an invalid submission is echoed back before fan-out.

WIP: specs are written but not yet run green in a stack; the Enact
profile opt-in and the PR are follow-ups.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@laritakr laritakr added the notes-minor Release Notes: Non-breaking features label Jul 15, 2026
@github-actions

github-actions Bot commented Jul 15, 2026

Copy link
Copy Markdown

Test Results

    17 files      17 suites   3h 29m 46s ⏱️
 7 978 tests  7 671 ✅ 307 💤 0 ❌
27 275 runs  26 676 ✅ 599 💤 0 ❌

Results for commit 7c49539.

♻️ This comment has been updated with latest results.

@ShanaLMoore ShanaLMoore changed the title 🚧 WIP: typeahead + multi-select for controlled compounds Add typeahead + multi-select opt-ins for controlled compound members Jul 15, 2026
select2 v3 copies the native control's `.form-control` classes onto its
container while rendering its own bordered inner box, producing a
"box within a box" and clipping a multi-select's chosen pills at the fixed
`.form-control-sm` height. Flatten the inner box so only the form-control
frame shows, size the empty control to match a sibling `.form-control-sm`
input, and let the frame grow to fit the selected values.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Comment thread app/assets/stylesheets/hyrax/_compound_metadata.scss Outdated
Comment thread app/assets/stylesheets/hyrax/_compound_metadata.scss Outdated
Comment thread app/assets/stylesheets/hyrax/_compound_metadata.scss Outdated
Comment thread app/assets/stylesheets/hyrax/_compound_metadata.scss Outdated

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds opt-in enhancements for compound controlled members so profiles can enable (1) a select2-backed typeahead over an existing <select> and (2) multi-select submission with server-side fan-out into scalar-valued compound entries, while preserving default/backward-compatible rendering when the flags are off.

Changes:

  • Parse new per-subproperty form.autocomplete / form.multiple flags into the compound schema spec and render controlled members accordingly.
  • Implement multi-select fan-out in compound form deserialization and harden indexing/show rendering paths to tolerate transient array values.
  • Add JS/CSS to bind select2 for controlled selects and adjust styling; add locale/docs + comprehensive specs.

Reviewed changes

Copilot reviewed 17 out of 17 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
spec/views/hyrax/compounds/_compound_row_controlled_multiple_spec.rb Adds view coverage for controlled multi-select + typeahead markup and backward-compat single-select behavior.
spec/services/hyrax/compound_schema_spec.rb Verifies schema parsing for the new form flags and updated normalized specs.
spec/renderers/hyrax/renderers/compound_attribute_renderer_spec.rb Covers show rendering when a controlled value is temporarily an array.
spec/indexers/hyrax/indexers/compound_indexer_spec.rb Covers indexing behavior when a member value is an array (flattening into facet values).
spec/helpers/hyrax/compound_fields_helper_spec.rb Adds helper coverage for array current values (options + forced-value detection).
spec/forms/concerns/hyrax/compound_field_behavior_spec.rb Adds coverage for multi-select fan-out behavior during deserialization.
documentation/compound_fields.md Documents the two new controlled-member opt-ins and fan-out semantics.
config/locales/hyrax.en.yml Adds i18n placeholder text for controlled select2 search.
app/views/hyrax/compounds/_compound_row.html.erb Renders multiple controlled selects ([] name), adds select2 hook + placeholder for autocomplete.
app/services/hyrax/compound_subproperty_labeler.rb Makes label resolution tolerant of array values by joining term labels.
app/services/hyrax/compound_schema.rb Normalizes form.autocomplete / form.multiple into subproperty specs (default false).
app/renderers/hyrax/renderers/compound_attribute_renderer.rb Renders array controlled values as comma-separated term markup.
app/indexers/hyrax/indexers/compound_indexer.rb Flattens array member values during indexing to avoid nested arrays in Solr fields.
app/helpers/hyrax/compound_fields_helper.rb Ensures option lists / forced-value logic handle array current values.
app/forms/concerns/hyrax/compound_field_behavior.rb Implements multi-select fan-out and normalization in compound deserialization.
app/assets/stylesheets/hyrax/_compound_metadata.scss Adds select2 styling adjustments for controlled members (to avoid “double box” + pill clipping).
app/assets/javascripts/hyrax/compound_metadata.js Adds select2 binding for controlled selects, including on cloned rows.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread app/forms/concerns/hyrax/compound_field_behavior.rb
Comment thread app/assets/stylesheets/hyrax/_compound_metadata.scss Outdated
ShanaLMoore and others added 4 commits July 15, 2026 15:03
Drop narration that restated the code and keep only non-obvious reasoning:
the select2 v3 form-control quirk, the Reform populator detail, fan-out's
cartesian product and nil-collapse edge case, why off-list values still
render, and the html_safe precondition.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Scope the select2 styling to a container class tagged by bindControlledSelects
so it no longer applies to other select2 controls (work_or_url / linked_record)
in the same compound row.

Fix scss-lint nits: property order, drop the element-qualified selector, and
avoid the 4-value margin shorthand; also match the empty multi-select's height
to a sibling form-control-sm input.

expand_multiple_members now only collapses `multiple` members to nil, so it
never wipes other members that happen to arrive as arrays and never leaks a
`[]` for an unselected `multiple` member.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Extend the select2 form-control flattening to every select2 control in a
compound row (Item / work_or_url, linked_record, and the new controlled
typeahead), bounded to `.hyrax-compound-row`, so the whole compound form reads
consistently instead of the work_or_url pickers keeping a nested border. This
drops the per-container marker class in favor of the row-scoped selector.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@ShanaLMoore
ShanaLMoore requested a review from orangewolf July 16, 2026 18:15
@ShanaLMoore
ShanaLMoore marked this pull request as ready for review July 16, 2026 20:22
@ShanaLMoore

ShanaLMoore commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

I ran this locally and can confirm this is working. cc @laritakr screenshots attached in the description.

<% end %>
<% case spec[:type] %>
<% when 'controlled' %>
<%# `multiple` submits an array (`[]` suffix) that fans out server-side;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there is a lot of variable setting in this view. it makes me think the whole thing needs to be a helper (and therefor tested)

@ShanaLMoore ShanaLMoore Jul 17, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call; extracted the whole controlled branch into a compound_controlled_select helper (plus a small compound_controlled_data), so the view is now just <%= compound_controlled_select(spec, value, input_name, input_id) %>. The multiple / autocomplete / force-select logic is now unit-tested in spec/helpers/hyrax/compound_fields_helper_spec.rb. Done in 90e7b01.

Comment thread config/metadata/compound_metadata.yaml
Drop the remaining what-narration so the code speaks for itself; keep comments
only where the behavior is unusual: the select2 v3 box-in-box, the allowClear
v3 placeholder warning, the fan-out nil-collapse edge case, an array arriving
before fan-out, and why off-list values still render.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@ShanaLMoore
ShanaLMoore force-pushed the compound-controlled-typeahead-multiselect branch from b7b3b58 to 8ca5db2 Compare July 17, 2026 16:19
Per review: the `controlled` branch of _compound_row.html.erb set several
variables inline. Move it into `compound_controlled_select` (plus
`compound_controlled_data`) so the view is a single call and the
multiple / autocomplete / force-select logic is unit-tested.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@orangewolf
orangewolf merged commit 5cc41d6 into main Jul 17, 2026
20 checks passed
@orangewolf
orangewolf deleted the compound-controlled-typeahead-multiselect branch July 17, 2026 18:12
paulwalk pushed a commit to antleaf/antleaf-hyku that referenced this pull request Jul 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

notes-minor Release Notes: Non-breaking features

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants