Skip to content

Tree search UX: hide irrelevant toolbar buttons during search, expose search mode, and multi-field matching #210

Description

@JohnRDOrazio

Problem

Several distinct UX gaps in tree search compound to make the feature feel inconsistent and partially broken. Filing as one issue because they share the same surface (EntityTreeToolbar + useTreeSearch) and the right design treats them together.

1. Expand / Collapse buttons stay visible (and active) during search

components/editor/shared/EntityTreeToolbar.tsx:102-150 renders the Expand and Collapse split-button groups regardless of showSearch. Once the user opens search and the tree switches to filtered-results mode (via useFilteredTree), Expand/Collapse no longer have any meaningful effect — the rendered tree is the filtered tree, not the live class hierarchy. The buttons just sit there occupying space and inviting confusion.

2. Search input renders below the buttons

The search input drops in below the toolbar row when active, leaving the now-meaningless Expand/Collapse buttons above it. The visual hierarchy reads as "primary controls on top, search secondary" — the opposite of reality once search is engaged.

3. The search behavior is opaque — same backend, very different results depending on query length

Reproducible behavior on the demo dataset:

Query Result
son of god Jesus (individual) is returned
son of non-exact class matches plus Jesus (individual)
son only class results — Jesus is gone
son (trailing space) only class results

The query passes straight to projectOntologyApi.searchEntities (useTreeSearch.ts:74-79) with no entity_types filter, so the backend is what's making the call. Whether it's switching ranking modes by query length, applying a relevance threshold, or splitting tokens differently, the user has no way to see what's happening — and the inconsistency is a real bug from the user's perspective.

4. Multi-field matching is unclear

The user expects to be able to search across rdfs:label, skos:prefLabel, skos:altLabel, rdfs:comment, and skos:definition — not just the one label that happens to be displayed as the tree-row's text. When a hit is via a non-displayed field, the user has no way to tell why a node was returned.

Proposal

Treat this as a single small redesign of the search mode rather than four separate tweaks.

Replace Expand/Collapse with a search-mode group while search is active

[ Add ] [ Search input.................. ] [ Exact | Partial | FTS | Semantic ] [ X ]

When showSearch is true:

  • Hide the Expand and Collapse split-button groups entirely.
  • Render the search input inline (full width of the toolbar minus the mode group and close button), instead of dropping it to a row below.
  • Render a small grouped control (role="group", similar shape to the existing ModeSwitcher) with Exact | Partial | FTS | Semantic segments. The active segment styles like the active mode in ModeSwitcher / the new ViewerEditorSwitcher.

When showSearch is false:

  • Render the existing Expand/Collapse groups exactly as today.

This gives Expand/Collapse and the search-mode group "alternate uses for the same toolbar slot," which is the right metaphor — they're both about how to interpret the tree below.

Make search mode explicit and stable

Define four modes in a typed enum that maps to existing or new backend behavior:

Mode Meaning Backend translation
exact label equals query (case-insensitive, trimmed) entity_types=any&q=...&match=exact
partial label contains query (substring) today's default — q=...
fts full-text search across label + altLabel + comment + definition q=...&fields=label,altLabel,comment,definition (or backend FTS index if available)
semantic embedding-based similarity (already wired in useSemanticSearch.ts) useSemanticSearch

The default on first open is Partial (today's behavior). The selected mode persists per session in the same store as the editor mode (useEditorModeStore-style) so users don't have to re-pick it on every search session.

The token-length / query-shape inconsistency reported above goes away because the user explicitly picks the mode rather than the backend silently switching strategies.

Show why a node matched when the match isn't on the displayed label

When a result comes back and the displayed rdfs:label doesn't contain the query (because the match was on altLabel, definition, etc.), render the matching field's value as a small caption beneath the row, with the matching substring highlighted:

Document Collection Event
↳ also: alt — "doc collection ev." [matched on altLabel]

The backend would need to return matchedField + matchedValue per result (already easy if the search endpoint switches to FTS). The frontend just renders a single subdued line. When the match IS on rdfs:label, no caption is rendered — same as today.

This piece is independent of the toolbar redesign and could ship in a second PR if the backend work isn't ready in time.

Mixed entity-type results: opt-in via the existing entity tabs

The Classes tree should only return classes; the Properties tree only properties; the Individuals list only individuals. Today the call doesn't pass entity_types, so backends apply their own defaults — which explains why Jesus (individual) sometimes shows up in class-tree search results and sometimes doesn't.

Pass entity_types="class" from useTreeSearch when scoped to the Classes tree, entity_types="property" from PropertyTree's search, etc. Cross-tab results live in a separate global search affordance (out of scope for this issue).

Plan

1. Toolbar slot reuse

Refactor EntityTreeToolbar.tsx so the inner toolbar row branches on showSearch:

{showSearch ? (
  <SearchToolbar
    query={searchQuery}
    onChange={setSearchQuery}
    mode={searchMode}
    onModeChange={setSearchMode}
    onClose={onCloseSearch}
  />
) : (
  <>
    {/* existing Add / Expand / Collapse / Search-toggle */}
  </>
)}

The new SearchToolbar is a small component with <input> + <SearchModeSwitcher> + close button. Same height as today's row so the layout doesn't shift.

2. Search-mode plumbing

  • Add SearchMode enum in lib/ontology/searchMode.ts.
  • Extend useTreeSearch with mode: SearchMode state and pass it (translated to backend args) to projectOntologyApi.searchEntities.
  • Persist the chosen mode in a small Zustand store (useSearchPreferenceStore) so it sticks per-session.

3. Per-tree entity-type scoping

Each caller of useTreeSearch (ClassTree, PropertyTree, IndividualList) passes its own entityType. Cross-tab search remains a separate concern.

4. Highlighting non-label matches

Two parts (can ship independently):

  • Backend: searchEntities returns matchedField: "label" | "altLabel" | "comment" | "definition" and matchedValue: string per result.
  • Frontend: in EntityTreeNodeRow, render an aria-describedby'd caption when matchedField is not "label". Styling consistent with the existing tree-search-match class.

Done criteria

  • When search is active, Expand and Collapse are not visible.
  • When search is active, the search input + mode group + close button occupy the toolbar's row at the same height as the inactive layout.
  • Toggling between the four search modes changes the backend query in a predictable way visible to the user.
  • The selected search mode persists across opens within a session.
  • Searching son and son of god in Partial mode produces results consistent with each other (no silent "if your query has 3+ tokens we widen the search" backend tricks).
  • Class-tree search returns only classes; Properties search only properties; Individuals search only individuals.
  • When a match is on altLabel / comment / definition, the row shows a small "matched on <field>" caption with the matching value highlighted.
  • Existing keyboard shortcut Ctrl+K still toggles search.

Out of scope

  • A global cross-tree search UI ("search everything"). Today's per-tree scope is correct; widening it is a separate feature.
  • Saved searches / search history.
  • Scoring & ranking improvements within each mode (e.g. boosting prefLabel matches over altLabel matches in Partial mode). The mode toggle gives the user the right knob; refining each mode's internal ranking is its own ticket.
  • Replacing the native search input with a Combobox that shows suggestions while typing. The existing one-shot search-then-render-tree flow is fine for ontology-scale searches; suggestion-style autocomplete is a separate UX direction.

Related

Other v0.5.0 polish items in the editor chrome: #204, #205, #206, #207, #208, #209. This one is the largest of that set and probably warrants its own PR; #209 (search highlight on intermediate ancestors) is closely related and could land in the same change.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions