67 lines
3.6 KiB
Markdown
67 lines
3.6 KiB
Markdown
# Context-Aware Cmd-K Search
|
|
|
|
## Goal
|
|
|
|
Make the cmd-k command palette context-aware: when the user is on admin pages, search users, groups, and roles instead of operational data (applications, routes, exchanges, agents).
|
|
|
|
## Background
|
|
|
|
The `CommandPalette` component from `@cameleer/design-system` currently searches five categories: application, exchange, attribute, route, agent. All data is assembled in `LayoutShell.tsx` and passed as a flat `SearchResult[]` array. The `SearchCategory` type is a closed union in the DS.
|
|
|
|
The DS team is already updating `CommandPalette` to accept an open `SearchCategory` type (string instead of fixed union) and handle unknown categories gracefully (title-cased tab labels, default icons).
|
|
|
|
## Approach
|
|
|
|
Route-based data swap in `LayoutShell`. The component already tracks `isAdminPage` via `useLocation()`. When true, it provides admin search data instead of operational search data. No new providers, contexts, or abstractions.
|
|
|
|
## Design
|
|
|
|
### Search Data Swap (LayoutShell.tsx)
|
|
|
|
When `isAdminPage` is true:
|
|
|
|
1. **Skip operational data assembly** — do not build catalog/agent/attribute search items, do not fire the debounced `useSearchExecutions` query
|
|
2. **Fetch admin data** — call existing hooks: `useUsers()`, `useGroups()`, `useRoles()` from `api/queries/admin/rbac.ts`
|
|
3. **Build admin search items** — map each entity to a `SearchResult`:
|
|
- **Users**: `{ id: 'user:{userId}', category: 'user', title: displayName, meta: userId, path: '/admin/rbac' }`
|
|
- **Groups**: `{ id: 'group:{id}', category: 'group', title: name, meta: parentGroupId ?? 'top-level group', path: '/admin/rbac' }`
|
|
- **Roles**: `{ id: 'role:{id}', category: 'role', title: name, meta: scope, path: '/admin/rbac' }`
|
|
4. **Swap** — `searchData = isAdminPage ? adminSearchData : operationalSearchData`
|
|
|
|
All three admin collections are small (dozens of items at most) — client-side filtering is sufficient. No server-side search endpoint needed.
|
|
|
|
### Navigation on Select
|
|
|
|
When the user selects an admin search result:
|
|
|
|
1. Navigate to `/admin/rbac` with location state: `{ tab: 'users' | 'groups' | 'roles', highlight: itemId }`
|
|
2. Tab mapping from category: `'user' → 'users'`, `'group' → 'groups'`, `'role' → 'roles'`
|
|
3. `RbacPage` reads location state, switches to the correct tab, and highlights the matching item
|
|
4. The `highlight` state is consumed once — after reading, clear it so back-navigation doesn't re-trigger
|
|
|
|
### Submit Handler (Enter without selection)
|
|
|
|
When the user presses Enter without explicitly selecting a result, navigate to the top (first visible) result — same behavior as selecting it. This matches standard command palette UX.
|
|
|
|
### Operational search behavior (unchanged)
|
|
|
|
When not on admin pages, search data assembly remains exactly as-is: catalog apps/routes, agents, exchanges (server-side), and attributes.
|
|
|
|
## Files Changed
|
|
|
|
| File | Change |
|
|
|------|--------|
|
|
| `ui/src/components/LayoutShell.tsx` | Import admin hooks, build admin search data, swap based on `isAdminPage`, update `handlePaletteSelect` for admin categories |
|
|
| `ui/src/pages/Admin/RbacPage.tsx` | Read location state `{ tab, highlight }`, switch tab and highlight item on mount |
|
|
|
|
## Dependencies
|
|
|
|
- `@cameleer/design-system` update: `SearchCategory` opened to `string`, unknown categories handled gracefully (DS team already working on this)
|
|
|
|
## Out of Scope
|
|
|
|
- Audit log search via cmd-k (has its own dedicated filter UI)
|
|
- App config search via cmd-k (already accessible via operational sidebar)
|
|
- Server-side admin search endpoint (not needed for small collections)
|
|
- Admin-specific submit behavior beyond navigating to top result
|