Skip to content

CS-10380: Fetch type picker options from _federated-types endpoint#4171

Merged
FadhlanR merged 19 commits intomainfrom
cs-10380-fetch-type-picker-options
Apr 2, 2026
Merged

CS-10380: Fetch type picker options from _federated-types endpoint#4171
FadhlanR merged 19 commits intomainfrom
cs-10380-fetch-type-picker-options

Conversation

@FadhlanR
Copy link
Copy Markdown
Contributor

@FadhlanR FadhlanR commented Mar 12, 2026

Screen.Recording.2026-03-16.at.13.11.49.mov

Summary

  • Fetch type picker options from the _federated-types endpoint instead of deriving them from search results
  • Move loading indicators and infinite scroll behavior from the host TypePicker into the boxel-ui Picker component
  • Split type loading into two distinct states: full overlay for initial/search fetches, bottom spinner for load-more pagination
  • Implement server-side search and pagination for type picker options (page size: 25)
  • Types with the same display name but different code refs are merged into a single picker option

Changes

packages/boxel-ui/addon/src/components/picker/index.gts

  • Added isLoading, isLoadingMore, hasMore, onLoadMore args to Picker
  • Built-in PickerAfterOptions component with full overlay loading for initial/search and bottom spinner for load-more
  • Scroll-based infinite scroll detection with immediate check via requestAnimationFrame
  • dropdownClass adds --loading modifier for z-index layering (search input stays above overlay)

packages/host/app/components/type-picker/index.gts

  • Now passes @isLoading, @isLoadingMore, @hasMore, @onLoadMore directly to Picker

packages/host/app/components/card-search/panel.gts

  • Added fetchTypeSummaries restartable task calling realmServer.fetchCardTypeSummaries()
  • Resource-based reactivity: realm URL or search key changes trigger re-fetch automatically
  • Split loading into _isLoadingTypes (initial/search) and _isLoadingMoreTypes (pagination)
  • _typeCodeRefs map tracks multiple code refs per display name for accurate search filtering

packages/host/app/components/card-search/search-bar.gts

  • Added isLoadingTypes arg, passed through to TypePicker

Tests

  • Updated type counts and added waitFor for paginated type options in operator-mode UI tests

Test plan

  • Open type picker — verify loading indicator shows during initial fetch
  • Type in type picker search — verify loading overlay covers options area (not search input)
  • Scroll to bottom of type options — verify more types load with bottom spinner
  • Select a realm filter — verify type options refresh from the endpoint
  • Select specific type(s) — verify card search results filter correctly
  • Run packages/host integration tests for operator-mode UI

🤖 Generated with Claude Code

FadhlanR and others added 2 commits March 12, 2026 15:25
Instead of deriving type picker options from search results and recent
cards, fetch all available types from the _federated-types endpoint so
the picker is populated with all types across selected realms regardless
of search state.

Types with the same display_name but different code_refs appear as one
picker option, but all associated code_refs are used when filtering
search results.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown

github-actions bot commented Mar 12, 2026

@github-actions
Copy link
Copy Markdown

github-actions bot commented Mar 12, 2026

Host Test Results

    1 files  ±    0      1 suites  ±0   3h 40m 7s ⏱️ + 1h 25m 43s
2 066 tests +    4  2 051 ✅ +    4  15 💤 ±0  0 ❌ ±0 
3 556 runs  +1 479  3 532 ✅ +1 470  24 💤 +9  0 ❌ ±0 

Results for commit 8c39a7d. ± Comparison against base commit 20a185a.

This pull request removes 3 and adds 7 tests. Note that renamed tests count towards both.
Chrome ‑ Integration | operator-mode | ui: empty state shows no type options besides Any Type
Chrome ‑ Integration | operator-mode | ui: type options derived from recent cards when no search term, sorted alphabetically
Chrome ‑ Integration | operator-mode | ui: type options update when search term changes and deduplicate
Chrome ‑ Integration | ai-assistant-panel | sending: attach card from AI assistant shows all types in type picker
Chrome ‑ Integration | operator-mode | card catalog > recents section: type picker auto-selects constrained type and disables Any Type when baseFilter specifies a specific type
Chrome ‑ Integration | operator-mode | ui: clearing type picker search restores all types and preserves selection
Chrome ‑ Integration | operator-mode | ui: selected types are preserved after clearing type search when selection is beyond first page
Chrome ‑ Integration | operator-mode | ui: type options derived from realm types when no search term, sorted alphabetically
Chrome ‑ Integration | operator-mode | ui: type options show all realm types even without recent cards
Chrome ‑ Integration | operator-mode | ui: type picker total count reflects API total, not loaded options count

♻️ This comment has been updated with latest results.

FadhlanR and others added 7 commits March 12, 2026 20:54
- Add built-in infinite scroll and loading indicators to the boxel-ui
  Picker component (isLoading, isLoadingMore, hasMore, onLoadMore args)
- Full overlay loading covers options area during initial/search fetch,
  bottom spinner shows during load-more pagination
- Simplify TypePicker by removing custom afterOptionsComponent,
  infiniteScroll modifier, and pickerExtra - now passes args directly
- Split _typeSummariesLoading into _isLoadingTypes and
  _isLoadingMoreTypes for distinct loading states
- Update tests for pagination (waitFor specific types) and type count

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@FadhlanR FadhlanR marked this pull request as ready for review March 16, 2026 08:05
@FadhlanR FadhlanR requested a review from a team March 16, 2026 08:05
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

const validIds = new Set(seen.keys());
const kept = prev
.filter((opt) => opt.type !== 'select-all' && validIds.has(opt.id))
.map((opt) => seen.get(opt.id)!);
value.selected = kept.length > 0 ? kept : [];

P1 Badge Preserve selections across server-side type searches

When the picker is using server-side search, this branch drops any previously selected type whose label is not present in the currently returned option set, so selecting Pet and then searching for Person clears Pet as soon as the filtered response arrives. That makes multi-select across different search terms impossible and unexpectedly reverts users back toward Any Type without an explicit deselect action.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +289 to +292
if (!codeRefsByDisplayName.has(name)) {
codeRefsByDisplayName.set(name, []);
}
codeRefsByDisplayName.get(name)!.push(codeRef);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Merge type code refs beyond the current paged slice

The typeCodeRefs map is built only from _typeSummariesData, which is fetched in 25-item pages, so a selected display name can end up mapped to only the code refs from already-loaded pages. In multi-realm or large-type sets, cards whose same display name appears on later pages get filtered out incorrectly because their code refs were never added to this map.

Useful? React with 👍 / 👎.

@backspace
Copy link
Copy Markdown
Contributor

I don’t know what best practice is for this kind of thing, I assume pagination is a reason but I find it confusing that I scroll to the bottom and see “WelcomeToBoxel” as the last item, then loading, then it’s still the last item, what were the new ones? And then after enough of that, “YouTube Thumbnail Composer” becomes the last item. Is it possible to trigger the load of them all instead of needing to scroll to the bottom? There may be constraints here I’m unaware of, it just seems confusing.

screencast 2026-03-16 15-27-22

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR updates the card search “Type” filter to fetch options from the realm server’s /_federated-types endpoint (with server-side search + pagination) instead of deriving types from search results/recent cards, and moves the loading + infinite-scroll UX into the shared boxel-ui Picker.

Changes:

  • Add isLoading/isLoadingMore/hasMore/onLoadMore + server-search support to boxel-ui Picker, including built-in loading UI and scroll-based pagination triggers.
  • Implement type summaries fetching in host card-search panel via realmServer.fetchCardTypeSummaries() with search + pagination, and filter search results by resolved type code refs.
  • Update operator-mode UI tests to reflect realm-derived type options and the new loading/pagination behavior.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
packages/boxel-ui/addon/src/components/picker/index.gts Adds loading + infinite-scroll behavior and server-search toggles to the shared Picker.
packages/host/app/components/type-picker/index.gts Wires TypePicker to Picker’s new server-search/loading/pagination args.
packages/host/app/components/card-search/panel.gts Fetches and dedupes federated type summaries; manages loading states and pagination.
packages/host/app/components/card-search/search-content.gts Changes type filtering to use code refs (via internalKeyFor) rather than display-name strings.
packages/host/app/components/card-search/search-bar.gts Threads new type loading/search/pagination args into TypePicker.
packages/host/app/services/realm-server.ts Adds fetchCardTypeSummaries() that queries /_federated-types.
packages/host/tests/helpers/realm-server-mock/routes.ts Adds mock /_federated-types endpoint for host tests.
packages/host/tests/integration/components/operator-mode-ui-test.gts Updates expectations and waits for realm-derived type options.

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

FadhlanR and others added 3 commits March 20, 2026 13:55
When the server paginates by code_ref but the client deduplicates by
displayName, a page of 25 code_refs can yield 0 new visible picker
options. This adds a while loop that automatically fetches the next page
until new visible items appear or the last page is reached.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@FadhlanR
Copy link
Copy Markdown
Contributor Author

FadhlanR commented Mar 30, 2026

@backspace Thanks for the feedback! The confusing behavior you saw was caused by two issues:

  1. Client-side deduplication was collapsing multiple types with the same display name into one picker option. So a page of 25 raw items from the server might yield far fewer visible options after dedup, making it seem like nothing loaded.

  2. No server-side sorting — the server returned types in an arbitrary order, so after the client sorted alphabetically, newly loaded items could end up scattered throughout the list rather than appended at the bottom.

In the latest commit I've fixed both:

  • Removed deduplication: Each code_ref is now its own picker option (with a tooltip showing the code_ref on hover so users can distinguish same-named types). This means 25 items per page = 25 visible items, always.
  • Added server-side alphabetical sort: Types are now sorted by displayName before pagination, so load-more always appends items that come later alphabetically. No more confusing reordering.

Why we still need pagination: The type list can be very large (hundreds of types across multiple realms). Loading them all upfront would mean a slower initial render and a large payload on first open of the type picker. Incremental pagination (25 at a time) keeps the picker responsive. With the server-side sort fix, the UX should now feel natural — scroll down, see 25 more types in alphabetical order.

*) You cannot try this in the preview because we need to deploy the server-side sorting.

Screen.Recording.2026-03-30.at.12.26.13.mov

new visible options due to display-name deduplication), the first
scroll-to-bottom now fetches all remaining types at once. Initial load
still fetches only the first page (25 items) to keep it fast.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

Remove client-side type deduplication, add code_ref tooltip, fix pagination

- Stop deduplicating type picker entries by displayName. Each code_ref
  is now its own picker option, so pagination works naturally (25 items
  per page = 25 visible items, no more fetch-all workaround).

- Add tooltip (using BoxelTooltip) on type picker options showing the
  code_ref so users can distinguish same-named types from different modules.

- Fix pagination to be truly incremental: loadMoreTypes now fetches the
  next page and appends results instead of fetching all remaining at once.

- Sort types by displayName server-side before paginating so page
  boundaries are stable and load-more appends in correct alphabetical order.

- Remove typeCodeRefs plumbing (no longer needed since picker option IDs
  are now the code_refs directly).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@FadhlanR FadhlanR force-pushed the cs-10380-fetch-type-picker-options branch from db01b90 to 590965b Compare March 30, 2026 05:30
@lukemelia
Copy link
Copy Markdown
Contributor

Minor issue: the dropdown needs some extra bottom padding so that the hover state is not flush against the bottom edge:

image

@lukemelia
Copy link
Copy Markdown
Contributor

Looks like types are not loading as expected from AI Assistant "+" > "Attach a Card" context

@lukemelia
Copy link
Copy Markdown
Contributor

Height of picker is changing when loading spinner appears. It should be stable.

@lukemelia
Copy link
Copy Markdown
Contributor

This should auto-choose "Author" as the type filter:
image

@lukemelia
Copy link
Copy Markdown
Contributor

Bug:

  1. Type "Skill" into type picker search box (list shows types matching "Skill"
  2. Check "Skill" (Skill shows as checked at top of picker)
  3. Clear type picker search box

Expected behavior: Show the latest paginated load of all types
Actual behavior: shows only types matching "Skill"

Copy link
Copy Markdown
Contributor

@lukemelia lukemelia left a comment

Choose a reason for hiding this comment

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

Left comments and discussed interactively

- Replace PickerAfterOptions with minimal PickerLoadingOverlay (initial load only)
- Move infinite scroll sentinel + loading spinner inline in the Picker template
- Fix types not loading from AI Assistant "Attach a Card" by skipping
  client-side filtering when baseFilter is a root type (CardDef/FieldDef)
- Auto-select constrained types when baseFilter specifies a specific type
  (e.g., Author) instead of defaulting to "Any Type"

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown

github-actions bot commented Mar 31, 2026

Realm Server Test Results

  1 files  ±0    1 suites  ±0   11m 56s ⏱️ -11s
802 tests ±0  802 ✅ ±0  0 💤 ±0  0 ❌ ±0 
873 runs  ±0  873 ✅ ±0  0 💤 ±0  0 ❌ ±0 

Results for commit 8c39a7d. ± Comparison against base commit 20a185a.

♻️ This comment has been updated with latest results.

FadhlanR and others added 3 commits March 31, 2026 16:28
- Hide root types (CardDef, FieldDef, BaseDef) from type picker options
- Disable "Any Type" when baseFilter constrains to a specific type
- Add disabled state to PickerOption with visual styling (opacity + no interaction)
- Skip client-side search result filtering for non-root baseFilter types
  to avoid excluding subtypes (e.g. BoomPet when filtering by Pet)
- Keep client-side filtering for root type baseFilter (CardDef) so type
  selection in "Attach a Card" modal still works
- Fix search clearing bug by directly performing fetch task
- Add microtask yield to avoid autotracking assertion on empty search
- Add tests for type picker with baseFilter, auto-select, disabled Any Type,
  search clearing, and attach card type selection flow
- Remove pauseTest() debugging leftover

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…unt, and add tests

Recursively fetch additional pages when clearing the type search if selected
types aren't in the first page of results. Use the API's meta.page.total
for the "Any Type (N)" count instead of the client-side options length.
Added tests for both behaviors with a reduced PAGE_SIZE.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@FadhlanR
Copy link
Copy Markdown
Contributor Author

Fixes in latest commits

1. Simplified loading spinner (picker component)

Replaced the PickerAfterOptions / afterOptionsComponent pattern with an inline loading spinner rendered directly in the Picker template after the picker-divider. The spinner only displays when the option is the last option and isLoadingMore is true. A minimal PickerLoadingOverlay is kept only for the initial full-page loading state.

2. Fixed types not loading from AI Assistant "Attach a Card"

Root cause: When "Attach a Card" is clicked, the baseFilter is { type: CardDef }. The baseFilterCodeRefs getter was doing an exact match against each type summary's code_ref, but since all summaries are specific types (Author, Skill, etc.) — none literally match CardDef. This filtered out all types.

Fix: Skip client-side type filtering when the only refs in the baseFilter are root types (CardDef / FieldDef), since all card types inherit from them.

3. Auto-select type when baseFilter constrains to a specific type

When the baseFilter constrains to a specific type (e.g., Author for a linksTo field), the type picker now auto-selects that type instead of defaulting to "Any Type".

4. Root types hidden from type picker

CardDef, FieldDef, and BaseDef are now filtered out from type picker options — users should never see these internal meta types as selectable options.

5. "Any Type" disabled when baseFilter constrains to specific type

When the card catalog is opened from a linksTo field (e.g., Pet), the "Any Type" option is now visually disabled (grayed out, non-interactive). The constrained type (e.g., Pet) is auto-selected. This prevents users from switching to "Any Type" in a context where the search is already type-constrained.

For root type baseFilters (CardDef — e.g., "Attach a Card"), "Any Type" remains enabled so users can still select specific types to filter results.

6. Search result filtering moved to server-side query

Type filtering of search results is now done server-side via buildSearchQuery instead of client-side filtering. Selected type IDs are included in the query filter, which avoids SQL conflicts by stripping the baseFilter's type constraint when a type picker selection is active. This also correctly handles subtypes (e.g., BoomPet under Pet).

7. Search clearing bug fix — selected types preserved across pages

Directly performing the fetch task in onTypeSearchChange ensures clearing the type picker search reliably re-fetches all types. When selected types exist, the fetch now recursively loads additional pages until all selected types are found in the results (or no more pages remain). This prevents selected types from being lost when they fall beyond the first page (25 items).

8. "Any Type (N)" total count fix

The total count shown in "Any Type (N)" now uses meta.page.total from the API response instead of the client-side typeFilter.options.length. This ensures the count reflects all available types, not just the currently loaded page.

9. Tests added

  • Type picker with baseFilter shows constrained types and disabled "Any Type"
  • Auto-select constrained type when baseFilter specifies a specific type
  • Attach card from AI assistant: types load, "Any Type" enabled, type selection filters results
  • Clearing type picker search restores all types and preserves selection
  • Selected types preserved after clearing type search when selection is beyond first page
  • Type picker total count reflects API total, not loaded options count

@FadhlanR FadhlanR force-pushed the cs-10380-fetch-type-picker-options branch from ece938e to 8c39a7d Compare March 31, 2026 15:34
@FadhlanR FadhlanR requested a review from lukemelia April 1, 2026 02:53
@lukemelia
Copy link
Copy Markdown
Contributor

lukemelia commented Apr 1, 2026

Needs space between the "Show only" text and the scrollbar, as well as the sort widget and the scrollbar:

image

@lukemelia
Copy link
Copy Markdown
Contributor

I expected to be able to click where it says "Search for a type" and focus the field and type, but I can't.

image

@lukemelia
Copy link
Copy Markdown
Contributor

Horizontal scrollbar should not appear when choosing the "strip" size:

image

@FadhlanR FadhlanR merged commit e6f2b06 into main Apr 2, 2026
75 of 82 checks passed
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.

4 participants