Repository navigation
quick search - #260
quick search#260
Conversation
Reviewer's GuideIntroduces a provider-extensible Cmd+K/Ctrl+K search experience across bundle base pages, combining translated menu and application results through relevance-weighted ranking, a Symfony UX Live Component, and Stimulus modal controls, with installation documentation and unit tests. Sequence diagram for quick search query and ranked resultssequenceDiagram
actor User
participant Stimulus as search-modal
participant LiveComponent as QuickSearchComponent
participant Service as QuickSearch
participant Menu as MenuQuickSearchProvider
participant App as ApplicationQuickSearchProvider
User->>Stimulus: open()
Stimulus->>LiveComponent: update query
LiveComponent->>Service: search(query, limit)
Service->>Menu: search(query, limit)
Service->>App: search(query, limit)
Menu-->>Service: QuickSearchResult list
App-->>Service: QuickSearchResult list
Service->>Service: getMultiplier()
Service-->>LiveComponent: ranked results
LiveComponent-->>User: render results and source badges
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Hey - I've found 7 issues
Prompt for AI Agents
Please address the comments from this code review:
## Individual Comments
### Comment 1
<location path="src/Service/QuickSearch/Provider/MenuQuickSearchProvider.php" line_range="63-65" />
<code_context>
+ }
+
+ $path = [...$parents, $this->translateLabel($item)];
+ $url = $item->getUri();
+ // Parents of a submenu link to '#'; only their children lead somewhere.
+ if ($url !== null && $url !== '#' && mb_stripos(implode(' ', $path), $query) !== false) {
+ $icon = $item->getLabelAttribute('icon');
+ $matches[] = [
</code_context>
<issue_to_address>
**Route-based menu links disappear**
When a menu item is configured with a Symfony `route` instead of a literal `uri`, `getUri()` does not resolve a menu item's configured Symfony route, so `$url` is null and the condition skips that item; route-based links shown in the menu therefore do not appear in quick-search results.
Resolve route-configured menu items to their generated URLs before filtering and returning results.
</issue_to_address>
### Comment 2
<location path="src/Service/QuickSearch/Provider/MenuQuickSearchProvider.php" line_range="32" />
<code_context>
+ $matches = [];
+ $this->collect($this->menuBuilder->createMainMenu(), [], $query, $matches);
+
+ // All matches, not capped at $limit: the menu is short and every hit is useful.
+ $scores = AlphabeticalScorer::score(array_column($matches, 'text'));
+ $source = $this->translator->trans('core.quick_search.source.navigation');
</code_context>
<issue_to_address>
**Search returns too many results**
When more menu entries match than the requested limit, `MenuQuickSearchProvider::search()` scores every match and returns them all, so `QuickSearch::search()` exceeds the per-provider limit and live searches do unnecessary scoring work.
Apply the requested limit to menu matches and score only the results that will be returned.
Also at `src/Service/QuickSearch/Scorer/AlphabeticalScorer.php:23`.
</issue_to_address>
### Comment 3
<location path="src/Service/QuickSearch/Provider/MenuQuickSearchProvider.php" line_range="91" />
<code_context>
+ return ucfirst($this->translator->trans(
+ $item->getLabel(),
+ is_array($parameters) ? $parameters : [],
+ is_string($domain) ? $domain : 'messages',
+ ));
+ }
</code_context>
<issue_to_address>
**Search labels differ from navigation**
When a menu item sets `translation_domain` to `false`, `translateLabel()` replaces `false` with `messages`, so the provider translates a label that the menu renders literally; search results can differ from the navigation and fail to match its text.
Preserve the `false` translation domain and return the label without translating it.
Also at `src/Service/QuickSearch/Provider/MenuQuickSearchProvider.php:92`.
</issue_to_address>
### Comment 4
<location path="src/Service/QuickSearch/Provider/MenuQuickSearchProvider.php" line_range="70" />
<code_context>
+ $matches[] = [
+ 'text' => implode(' › ', $path),
+ 'url' => $url,
+ 'icon' => is_string($icon) ? str_replace('bi bi-', '', $icon) : '',
+ ];
+ }
</code_context>
<issue_to_address>
**Search results show broken icons**
When a menu item omits its icon or uses a documented Font Awesome icon, the provider stores an empty string for a missing icon and passes Font Awesome classes through unchanged, while the results template always prefixes the value with `bi bi-`; search results therefore show a broken icon instead of the navigation icon.
Provide the navigation fallback for missing icons and render Font Awesome classes without the Bootstrap icon prefix.
Also at `templates/components/QuickSearch.html.twig:16`.
</issue_to_address>
### Comment 5
<location path="src/Service/QuickSearch/QuickSearch.php" line_range="32" />
<code_context>
+ $results = [];
+ $scores = [];
+ foreach ($this->providers as $provider) {
+ foreach ($provider->search($query, $limit) as $result) {
+ $results[] = $result;
+ $scores[] = $result->relevanceScore * $provider->getMultiplier();
</code_context>
<issue_to_address>
**One provider blocks all results**
When a registered provider's dependency is slow or throws during a search, the provider call runs synchronously inside `QuickSearch::search()` with no timeout or per-provider error handling, so a slow or failing provider prevents results from other sources from being returned and the user sees a stalled or failed update.
Require providers to bound dependency calls and isolate/log provider failures so one source cannot abort the whole search.
</issue_to_address>
### Comment 6
<location path="src/ValueObject/QuickSearchResult.php" line_range="22" />
<code_context>
+ public float $relevanceScore = 0.5,
+ ) {
+ assert(
+ $relevanceScore >= 0.0 && $relevanceScore <= 1.0,
+ 'The relevance score of a quick search result must be between 0 and 1.',
+ );
</code_context>
<issue_to_address>
**Invalid scores distort result ranking**
When production runs with assertions disabled and a provider supplies a score outside 0–1, `QuickSearchResult` accepts the out-of-range score, and `QuickSearch::search()` uses it directly for sorting, so the provider's result ranks incorrectly.
Validate the relevance score at runtime and reject values outside 0–1.
</issue_to_address>
### Comment 7
<location path="src/Service/QuickSearch/Provider/MenuQuickSearchProvider.php" line_range="88-92" />
<code_context>
+ // @mago-expect analysis:mixed-assignment
+ $domain = $item->getExtra('translation_domain', 'messages');
+
+ return ucfirst($this->translator->trans(
+ $item->getLabel(),
+ is_array($parameters) ? $parameters : [],
+ is_string($domain) ? $domain : 'messages',
+ ));
+ }
+}
</code_context>
<issue_to_address>
**Accented menu labels stay lowercase**
When a displayed menu label begins with a lowercase non-ASCII character, `ucfirst()` only uppercases ASCII, so translated labels beginning with a lowercase accented character are not capitalized in search results, unlike the menu rendering; callers see inconsistent labels.
Use Unicode-aware capitalization matching the Twig `ucfirst` behavior.
</issue_to_address>Sourcery assessment
Approval pending. 6 findings to address first.
Blocking findings: src/Service/QuickSearch/Provider/MenuQuickSearchProvider.php:65, src/Service/QuickSearch/Provider/MenuQuickSearchProvider.php:32, src/Service/QuickSearch/Provider/MenuQuickSearchProvider.php:91, src/Service/QuickSearch/Provider/MenuQuickSearchProvider.php:70, src/Service/QuickSearch/QuickSearch.php:32, and 1 more
| import { Controller } from '@hotwired/stimulus' | ||
| import { Modal } from 'bootstrap' | ||
|
|
||
| // Opens the quick search modal (see docs/quick-search.md) on Cmd+K / Ctrl+K and moves focus through its results |
There was a problem hiding this comment.
De Cmd+K / Ctrl+K wordt hier niet gedefinieerd. Dus vind het verwarrend om dat hier te vermelden.
There was a problem hiding this comment.
Klopt, de shortcut wordt in templates/quick_search.html.twig gebonden. De comment in de controller en de uitleg in docs/stimulus.md zeggen dat nu, zie abc3f37.
Cmd+K / Ctrl+K opens a search modal on every page that extends the bundle's
base.html.twig. Results come from providers: the bundle ships one for the main menu, applications add their own. Everything is shown in one list ranked on relevance, each result with a badge naming its source. Ported from the LA Systems app.symfony/ux-live-component.QuickSearchservice merges the results of all taggedQuickSearchProviders, sorted onrelevanceScore(0–1) × the provider's multiplier, ties alphabetical. Every autoconfigured implementation is tagged by the bundle extension (registerForAutoconfiguration).AlphabeticalScorerandGivenOrderScorer(1, 0.9, 0.81, …).MenuQuickSearchProvider: translated menu labels, submenus as "Parent › Child", hidden items skipped.SumoCodersFrameworkCore:QuickSearchlive component,search-modalStimulus controller, modal in a{% block quick_search %}inbase.html.twig(override it empty to switch off).docs/quick-search.md, stimulus.md, index, UPGRADE.md ("To 19.2": register the controller, check the live-component route).Tested with unit tests (scorers,
QuickSearch, menu provider) and in the LA Systems app via a path repository: menu and app-provider results, re-rendering through/_components, one modal per page.Skeleton follow-up: sumocoders/application-skeleton#177 (sumocoders/application-skeleton#177) registers the
search-modalcontroller in new projects and raises the bundle constraint to^19.2; merge it after this is released.Summary by Sourcery
Add an extensible, ranked quick-search experience to bundle-based application pages.
New Features:
Enhancements:
Build:
Documentation:
Tests:
Chores: