Skip to content

feat(search): merged layout in search index#38

Merged
Hyperkid123 merged 7 commits into
fleetshift:mainfrom
platex-rehor-bot:bot/OME-157
Jul 2, 2026
Merged

feat(search): merged layout in search index#38
Hyperkid123 merged 7 commits into
fleetshift:mainfrom
platex-rehor-bot:bot/OME-157

Conversation

@platex-rehor-bot

Copy link
Copy Markdown
Contributor

Summary

OME-157 — Phase 6: Search + AppNav updates

  • SearchProvider now uses the merged layout (backend + user override) instead of raw backend layout, so custom groups, moved modules, and hidden items are correctly reflected in search
  • Custom groups appear as nav-group category entries in search with their description, keywords, and icon indexed by Orama for better matching
  • Hidden items ("More" bucket children) remain searchable — forEachEntry helper recursively walks layout entries including "more" children
  • Search index rebuilds when the nav layout override changes (override added to useEffect deps)
  • AppNavGroup renders custom group icons via dynamic import (loadPfIcon + getCachedPfIcon)
  • iconSlugToName utility converts kebab-case icon slugs (e.g. folder-open) to PascalCase icon component names (e.g. FolderOpenIcon)

Changes

File What
packages/common/src/pfIconLoader.ts Add iconSlugToName helper
packages/common/src/index.ts Export iconSlugToName
packages/gui/src/components/Search/SearchProvider.tsx Use merged layout, walk "more" children, index custom group metadata, rebuild on override change
packages/gui/src/components/Search/searchIndex.ts Add nav-group to SearchCategory type
packages/gui/src/components/Search/FleetSearch.tsx Add "Custom groups" category label
packages/gui/src/components/AppNav/AppNavGroup.tsx Dynamic icon loading for custom groups
packages/common/src/__tests__/pfIconLoader.test.ts Unit tests for icon name conversions

Test plan

  • All 238 existing unit tests pass
  • New pfIconLoader.test.ts tests pass (iconSlugToName roundtrips, edge cases)
  • ESLint + Stylelint clean
  • Verify custom groups appear in search results under "Custom groups" category
  • Verify modules moved to custom groups show custom group as parent in search
  • Verify hidden ("More") items are still findable via search
  • Verify search index rebuilds when nav layout override changes (edit in settings → search reflects changes)
  • Verify custom group icons render in AppNav sidebar

🤖 Generated with Claude Code

OME-157

SearchProvider now uses the merged layout (backend + user override)
instead of the raw backend layout. Custom groups appear as "nav-group"
category entries with their description, keywords, and icon indexed by
Orama. Hidden "more" children remain searchable. Search index rebuilds
when the nav layout override changes. AppNavGroup renders custom group
icons via dynamic import.

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

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@platex-rehor-bot, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 56 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Enterprise

Run ID: 738b7576-8111-4525-b462-2974f8da95cf

📥 Commits

Reviewing files that changed from the base of the PR and between 34249e7 and ee5c3f7.

📒 Files selected for processing (1)
  • packages/gui/src/components/Search/FleetSearch.tsx
📝 Walkthrough

Walkthrough

The PR adds slug-to-name icon conversion, updates search indexing to merge override navigation layouts and resolve custom group icons, and changes search rendering to lazy-load icons, render nested trees recursively, and support a new nav-group search category.

Changes

Search icon and indexing flow

Layer / File(s) Summary
Icon slug conversion
packages/common/src/pfIconLoader.ts, packages/common/src/__tests__/pfIconLoader.test.ts
Adds iconSlugToName and test coverage for icon name, slug, and keyword conversions.
Merged layout indexing
packages/gui/src/components/Search/SearchProvider.tsx
Merges navLayout with overrides, traverses nested entries, resolves page and group icons from merged data, and inserts missing parents into the matching result category.
Search tree rendering
packages/gui/src/components/Search/searchIndex.ts, packages/gui/src/components/Search/FleetSearch.tsx, packages/gui/src/components/Search/FleetSearch.scss
Adds nav-group to the search category union, switches result icons to lazy loading, renders grouped results recursively, and updates tree guide styling to use theme variables.

Estimated code review effort: 4 (Complex) | ~45 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately reflects the main change: using the merged layout in search indexing.
Description check ✅ Passed The description is clearly related to the PR and summarizes the search and AppNav changes accurately.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

* Convert a kebab-case icon slug to PascalCase icon component name.
* E.g. "folder-open" → "FolderOpenIcon", "cog" → "CogIcon"
*/
export function iconSlugToName(slug: string): string {

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.

I believe this function already exists somewhere. In the nav editor. Can we deduplicate? Are there any duplications introduced?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good catch — I searched the codebase and iconSlugToName is actually new (no existing duplicate). The closest related functions are iconNameToFile and iconNameToKeywords in the same file, but they go the opposite direction (PascalCase → kebab/keywords). The nav editor (GroupFormModal, IconGalleryModal) works with PascalCase names directly.

Since the function is already in packages/common/src/pfIconLoader.ts, it's available as a shared utility across all packages.

setGroupIcon(() => cached);
return;
}
loadPfIcon(iconName).then((comp) => {

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.

Can we make the loading function stand alone? Not within a component file. Or even better, could we have a DynamicIcon component that handles the loading internally? And used in all the places that require the custom icons?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Great suggestion! I've extracted a standalone DynamicPfIcon component in packages/common/src/DynamicPfIcon.tsx that handles async loading internally. AppNavGroup now uses it instead of the inline useState/useEffect pattern.

GroupIconPicker in the settings plugin has a similar loading pattern and could also be migrated to use DynamicPfIcon — happy to do that in a follow-up if you'd like.

cb: (entry: CommonNavLayoutEntry) => void,
): void {
for (const entry of entries) {
if (entry.type === "more") {

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.

I think we have an enum for this, right? For these magic strings

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The existing `NodeKind` enum covers FlatNode kinds (`Page`, `Group`, `Section`) but not `NavLayoutEntry.type` values — `"more"` specifically isn't in `NodeKind` because the flatten step strips it. The `NavLayoutEntry` discriminated union enforces valid type values at compile time, and the literal string checks are consistent with how `navLayout.ts` uses them throughout (lines 388, 409, 485, 648).

If you'd prefer entry-type constants for readability, that would be a broader refactor across `navLayout.ts` — happy to tackle it as a separate PR.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/gui/src/components/Search/SearchProvider.tsx`:
- Around line 418-428: The async custom icon load in SearchProvider’s result
rendering can still write into iconMapRef after a newer override-triggered index
rebuild, causing stale icons to overwrite the current group icon state. Update
the icon-loading path around loadPfIcon and the index rebuild logic in
SearchProvider to clear per-index icon maps when rebuilding and to ignore late
Promise completions from older layouts, using the existing
entryId/group-${groupId} mapping to verify the completion is still current
before calling iconMapRef.current.set.
- Around line 430-436: Keep injected custom-group parents in the nav-group
bucket instead of letting query() prepend them into the child result’s category
bucket. Update the parent-injection logic in SearchProvider.tsx, around
featureParentRef.current.set and the groupFeatureId handling, so custom-group
parents retain their original nav-group category and still flow through the
dynamic icon map rather than being treated as nav.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Enterprise

Run ID: b50141a5-bb2b-4d73-a478-3c6a13b66f6f

📥 Commits

Reviewing files that changed from the base of the PR and between 70e3817 and f402ef8.

📒 Files selected for processing (7)
  • packages/common/src/__tests__/pfIconLoader.test.ts
  • packages/common/src/index.ts
  • packages/common/src/pfIconLoader.ts
  • packages/gui/src/components/AppNav/AppNavGroup.tsx
  • packages/gui/src/components/Search/FleetSearch.tsx
  • packages/gui/src/components/Search/SearchProvider.tsx
  • packages/gui/src/components/Search/searchIndex.ts

Comment thread packages/gui/src/components/Search/SearchProvider.tsx
Comment thread packages/gui/src/components/Search/SearchProvider.tsx
platex-rehor-bot and others added 2 commits July 2, 2026 09:19
… icons

OME-157
- Add DynamicPfIcon component in common for reusable async icon loading
- Use DynamicPfIcon in AppNavGroup instead of inline useState/useEffect
- Guard SearchProvider icon loads against stale rebuilds (cancelled flag)
- Clear component/icon/parent maps on index rebuild
- Fix parent injection: use parent.category to keep nav-group parents
  in correct bucket, attach IconComponent from iconMapRef
- Deduplicate parent entries in query results

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
OME-157
ComponentType defaults to ComponentType<{}> which rejects className.
Introduce PfIconComponent type alias with { className?: string } and
cast loader results to satisfy strict overload checks.

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

Copy link
Copy Markdown
Contributor

/retest

@Hyperkid123

Copy link
Copy Markdown
Contributor

@platex-rehor-bot I have found an issue, the icon, custom icon that is, is not surfaced in the search result. The new structure of custom groups is also not reflected in the search. Meaning the search index does not appear to be rebuilt when a navigation layout changes. In our nav editor.

OME-157
Fix three issues with search not reflecting nav editor changes:
1. Group parent-child tree rendering broken — toFeatureId stripped
   "group-" prefix but children use "group." dot notation, so groups
   were never matched as parents. Fix: map "group-X" → "group.X".
2. Page iconOverride from nav editor ignored — search always showed
   CubesIcon. Now reads iconOverride from merged layout and loads
   the overridden PF icon component dynamically.
3. Module extensions in custom groups without their own icon fell back
   to SearchIcon. Now inherit the parent custom group's icon.

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

Copy link
Copy Markdown
Contributor Author

@Hyperkid123 Good catch! I found three bugs in the search indexing that caused these issues:

  1. Group parent-child tree rendering was brokentoFeatureId in FleetSearch.tsx stripped the group- prefix (producing e.g. core-clusters), but children reference their parent using dot notation (group.core-clusters). These never matched, so groups were never rendered as tree parents in search results — their children appeared as flat/orphaned items instead.

  2. Page iconOverride from nav editor was ignored — The search index always used CubesIcon for pages regardless of any custom icon set via the editor. Now it reads iconOverride from the merged layout and dynamically loads the correct PF icon component.

  3. Custom group children without their own icon fell back to SearchIcon — Module extensions placed inside a custom group that don't define their own icon now inherit the parent group's custom icon instead of showing the generic search fallback.

All three are fixed in commit 19f50a0. Lint clean, 238 tests pass.

- Custom nav groups now nest pages recursively in search results
  (Foobar → Clusters → Create cluster → Kind/GCP) instead of
  showing them as a flat "Custom groups" category.
- Renderer supports multi-level parent-child trees via recursive
  renderTree, replacing the previous single-level parent/children
  split.
- Guide lines use PF TreeView standards: fixed 1.125rem offset
  instead of 50%, proper last-child height, divider-width tokens.
- Remove stray console.log from FleetSearch.
- Remove unused "nav-group" category — custom groups stay under
  "Pages" with deeper nesting.

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

Copy link
Copy Markdown
Contributor

Search results now reflect custom nav group structures

What changed:

  1. Multi-level recursive tree nesting — Custom nav groups (e.g. "Foobar") add an extra nesting level under "Pages" so the hierarchy matches what the sidebar shows (Foobar → Clusters → Create cluster → Kind/GCP HCP). Previously search results were flat.

  2. Guide line alignment — Horizontal/vertical tree guides now use PF TreeView standards: fixed 1.125rem offset from top (not 50%), last-child vertical guide stops at the correct height, uses PF divider-width and border-color tokens.

  3. Cleanup — Removed stray console.log({results, query}) from FleetSearch. Removed unused "nav-group" category — custom groups stay under "Pages" with deeper nesting via the feature field for parent-child linking.

Files:

  • packages/gui/src/components/Search/FleetSearch.tsx — recursive renderTree replaces flat parent/children split
  • packages/gui/src/components/Search/FleetSearch.scss — PF-aligned tree guide styles
  • packages/gui/src/components/Search/SearchProvider.tsx — group entries use category: "nav" + feature: groupFeature linking

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/gui/src/components/Search/SearchProvider.tsx (1)

353-361: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Preserve the injected parent chain for nested results.

When only a nested child matches, the injected module parent loses feature: groupFeature, and the insertion loop does not enqueue that parent’s group ancestor. The recursive renderer then shows the module as a root instead of under its custom group.

Proposed fix
       featureParentRef.current.set(globalFeatureId, {
         id: entryId,
         title: label,
         description: description ?? `Navigate to ${label}`,
         category: "nav",
         pathname: basePath,
-        icon: "",
+        icon: resolvedIconName,
         status: moduleStatus,
+        feature: groupFeature,
       });
-    for (const [, items] of Object.entries(results)) {
-      const resultIds = new Set(items.map((i) => i.id));
-      const needed = new Set<string>();
+    for (const items of Object.values(results)) {
+      const needed: string[] = [];
+      const seen = new Set<string>();
       for (const item of items) {
-        if (!item.feature) continue;
-        const parent = featureParentRef.current.get(item.feature);
-        if (parent && !resultIds.has(parent.id)) {
-          needed.add(item.feature);
-        }
+        if (item.feature) needed.push(item.feature);
       }
-      for (const featureId of needed) {
+      while (needed.length > 0) {
+        const featureId = needed.shift();
+        if (!featureId || seen.has(featureId)) continue;
+        seen.add(featureId);
         const parent = featureParentRef.current.get(featureId);
         if (!parent) continue;
         const targetCat = parent.category;
         const targetItems = results[targetCat] ?? [];
         if (targetItems.some((i) => i.id === parent.id)) continue;
         const parentItem = { ...parent };
         const comp = componentMapRef.current.get(parentItem.id);
         if (comp) parentItem.Component = comp;
         const icon = iconMapRef.current.get(parentItem.id);
         if (icon) parentItem.IconComponent = icon;
         results[targetCat] = [parentItem, ...targetItems];
+        if (parentItem.feature) needed.push(parentItem.feature);
       }
     }

Also applies to: 522-543

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/gui/src/components/Search/SearchProvider.tsx` around lines 353 -
361, The nested search result injection is dropping the custom parent chain, so
a matched child can lose its `feature: groupFeature` parent and the group
ancestor is never enqueued. Update the parent insertion logic in
SearchProvider’s nested result handling and the recursive renderer helpers so
injected module parents preserve their existing parent metadata and continue to
enqueue the custom group ancestor before rendering. Keep the fix focused around
the nested result builder and insertion loop that creates the `featureParentRef`
entries.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/gui/src/components/Search/FleetSearch.tsx`:
- Around line 47-55: The lazy icon component in FleetSearch is being frozen by
useState, so it does not update when name or fallback changes. Replace the
useState-based initialization of IconC with useMemo so the lazy component is
recomputed whenever name changes, and keep fallback as part of the memo
dependencies. Update the logic around loadPfIcon and the F fallback in
FleetSearch to preserve the same behavior while ensuring overrides render the
current icon.

---

Outside diff comments:
In `@packages/gui/src/components/Search/SearchProvider.tsx`:
- Around line 353-361: The nested search result injection is dropping the custom
parent chain, so a matched child can lose its `feature: groupFeature` parent and
the group ancestor is never enqueued. Update the parent insertion logic in
SearchProvider’s nested result handling and the recursive renderer helpers so
injected module parents preserve their existing parent metadata and continue to
enqueue the custom group ancestor before rendering. Keep the fix focused around
the nested result builder and insertion loop that creates the `featureParentRef`
entries.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Enterprise

Run ID: 3ddb5009-0435-4c05-b5de-857f18048970

📥 Commits

Reviewing files that changed from the base of the PR and between 19f50a0 and 27a9e5f.

📒 Files selected for processing (3)
  • packages/gui/src/components/Search/FleetSearch.scss
  • packages/gui/src/components/Search/FleetSearch.tsx
  • packages/gui/src/components/Search/SearchProvider.tsx
✅ Files skipped from review due to trivial changes (1)
  • packages/gui/src/components/Search/FleetSearch.scss

Comment thread packages/gui/src/components/Search/FleetSearch.tsx Outdated
platex-rehor-bot and others added 2 commits July 2, 2026 12:54
OME-157
Injected module parents now carry feature and resolvedIconName so the
recursive parent-injection loop walks up to the group ancestor.
Switched from a flat Set iteration to a queue-based walk that enqueues
each parent's own feature, ensuring multi-level nesting (e.g.
Group → Module → Create cluster) surfaces the full tree in results.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
OME-157
LazyIcon used useState initializer which freezes the lazy component
on first render — icon overrides never update. Switch to useMemo
keyed on name+Fallback so the lazy component re-creates when the
icon name changes.

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

Copy link
Copy Markdown
Contributor Author

Addressed both CodeRabbit findings in 34249e7 + ee5c3f7:

  1. SearchProvider.tsxfeatureParentRef entries for module extensions now preserve feature: groupFeature and icon: resolvedIconName. The parent-injection loop in query() uses a queue-based walk that follows the chain up to the group ancestor, so multi-level nesting (Group → Module → child) renders the full tree in search results.

  2. FleetSearch.tsx — Replaced useState initializer in LazyIcon with useMemo keyed on name + Fallback, so the lazy icon component recomputes when the icon name changes (e.g. after a nav override).

@Hyperkid123 Hyperkid123 merged commit 1e71b79 into fleetshift:main Jul 2, 2026
4 of 8 checks passed
@platex-rehor-bot platex-rehor-bot deleted the bot/OME-157 branch July 2, 2026 13:03
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.

2 participants