🐛 fix: missions search, mission state UX, namespace dropdown#3962
🐛 fix: missions search, mission state UX, namespace dropdown#3962clubanderson merged 1 commit intomainfrom
Conversation
- Add search/filter input to the AI Missions sidebar list so users can quickly find missions by title or description (#3944) - Show imported mission steps in the saved-mission view so users can review the mission plan without requiring an AI provider (#3917) - Merge cluster cache namespaces with pod-based namespaces in the REST API fallback so namespaces without running pods still appear in the GPU reservation namespace dropdown (#3945) Fixes #3944 #3917 #3945 Signed-off-by: Andrew Anderson <andy@clubanderson.com>
|
[APPROVALNOTIFIER] This PR is NOT APPROVED This pull-request has been approved by: The full list of commands accepted by this bot can be found here. DetailsNeeds approval from an approver in each of these files:Approvers can indicate their approval by writing |
|
👋 Hey @clubanderson — thanks for opening this PR!
This is an automated message. |
✅ Deploy Preview for kubestellarconsole ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
|
Thank you for your contribution! Your PR has been merged. Check out what's new:
Stay connected: Slack #kubestellar-dev | Multi-Cluster Survey |
There was a problem hiding this comment.
Pull request overview
This PR improves the AI Missions UX and fixes a namespace discovery gap by adding mission list search, showing saved mission steps without requiring an AI provider, and enhancing namespace dropdown population by merging cached and live-discovered namespaces.
Changes:
- Added a mission search input to filter saved and active missions in real time, including a “no results” state.
- Added an inline “Mission Steps” overview panel for saved missions with no conversation, so users can review steps without an AI provider.
- Updated namespace discovery to merge cluster-cache namespaces with pod-discovered namespaces so namespaces without pods still appear.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
web/src/components/layout/mission-sidebar/MissionSidebar.tsx |
Adds mission search state/UI and applies filtering to mission lists. |
web/src/components/layout/mission-sidebar/MissionChat.tsx |
Displays imported mission steps for saved missions before any chat starts. |
web/src/hooks/mcp/namespaces.ts |
Merges cached namespaces with REST pod-discovered namespaces for dropdown completeness. |
| // Split missions into saved (library) and active, applying search filter | ||
| const matchesSearch = useCallback((m: Mission) => { | ||
| if (!missionSearchQuery.trim()) return true | ||
| const q = missionSearchQuery.toLowerCase() | ||
| return m.title.toLowerCase().includes(q) || m.description.toLowerCase().includes(q) | ||
| }, [missionSearchQuery]) | ||
| const savedMissions = missions.filter(m => m.status === 'saved' && matchesSearch(m)) | ||
| const activeMissions = missions.filter(m => m.status !== 'saved' && matchesSearch(m)) |
There was a problem hiding this comment.
Filtering savedMissions / activeMissions at the source changes their semantics across the component (they’re used for more than just rendering the list). For example, the Saved Mission Detail modal’s onImport logic later searches within savedMissions; with an active search query (or after a rename while the modal is open), the mission may no longer be present and the action can silently no-op. Consider keeping unfiltered savedMissionsAll/activeMissionsAll for internal logic and deriving separate savedMissionsFiltered/activeMissionsFiltered for list rendering only.
| if (!missionSearchQuery.trim()) return true | ||
| const q = missionSearchQuery.toLowerCase() | ||
| return m.title.toLowerCase().includes(q) || m.description.toLowerCase().includes(q) |
There was a problem hiding this comment.
The search query is checked with trim() for emptiness, but the value used for matching isn’t trimmed. Queries with leading/trailing whitespace (e.g., pasted text) won’t match even when the underlying terms do. Normalize the query once (e.g., missionSearchQuery.trim().toLowerCase()) before running includes.
| if (!missionSearchQuery.trim()) return true | |
| const q = missionSearchQuery.toLowerCase() | |
| return m.title.toLowerCase().includes(q) || m.description.toLowerCase().includes(q) | |
| const normalizedQuery = missionSearchQuery.trim().toLowerCase() | |
| if (!normalizedQuery) return true | |
| return ( | |
| m.title.toLowerCase().includes(normalizedQuery) || | |
| m.description.toLowerCase().includes(normalizedQuery) | |
| ) |
| <button | ||
| onClick={() => setMissionSearchQuery('')} | ||
| className="absolute right-3.5 top-1/2 -translate-y-1/2 p-0.5 hover:bg-secondary rounded transition-colors" | ||
| title={t('common.clear', { defaultValue: 'Clear' })} |
There was a problem hiding this comment.
The clear (X) control is an icon-only <button> with only a title. For screen readers this should have an explicit accessible name (e.g., aria-label) and should be type="button" to avoid accidental form submission if this component is ever rendered inside a <form>. Alternatively, use the shared <Button> component which already handles aria-label defaults.
| <button | |
| onClick={() => setMissionSearchQuery('')} | |
| className="absolute right-3.5 top-1/2 -translate-y-1/2 p-0.5 hover:bg-secondary rounded transition-colors" | |
| title={t('common.clear', { defaultValue: 'Clear' })} | |
| <button | |
| type="button" | |
| onClick={() => setMissionSearchQuery('')} | |
| className="absolute right-3.5 top-1/2 -translate-y-1/2 p-0.5 hover:bg-secondary rounded transition-colors" | |
| title={t('common.clear', { defaultValue: 'Clear' })} | |
| aria-label={t('common.clear', { defaultValue: 'Clear' })} |
| // Fall back to REST API — merge pod-based namespaces with cluster cache | ||
| // to include namespaces that have no running pods (#3945) | ||
| try { | ||
| const { data } = await api.get<{ pods: PodInfo[] }>(`/api/mcp/pods?cluster=${encodeURIComponent(cluster)}`) | ||
| const nsSet = new Set<string>() | ||
| data.pods?.forEach(pod => { | ||
| if (pod.namespace) nsSet.add(pod.namespace) | ||
| }) | ||
| setNamespaces(Array.from(nsSet).sort()) | ||
| setError(null) | ||
| } catch (err) { | ||
| // Try cluster cache namespaces as last resort | ||
|
|
||
| // Seed with cluster cache namespaces (discovered during health checks) | ||
| // so namespaces without pods still appear in the dropdown | ||
| const cachedCluster = clusterCacheRef.clusters.find(c => c.name === cluster) | ||
| if (cachedCluster?.namespaces && cachedCluster.namespaces.length > 0) { | ||
| setNamespaces(cachedCluster.namespaces) | ||
| if (cachedCluster?.namespaces) { | ||
| for (const ns of cachedCluster.namespaces) { | ||
| nsSet.add(ns) | ||
| } | ||
| } | ||
|
|
||
| // Also add namespaces from pods (may discover recently created namespaces | ||
| // that the cache hasn't seen yet) | ||
| try { | ||
| const { data } = await api.get<{ pods: PodInfo[] }>(`/api/mcp/pods?cluster=${encodeURIComponent(cluster)}`) | ||
| for (const pod of (data.pods || [])) { | ||
| if (pod.namespace) nsSet.add(pod.namespace) | ||
| } | ||
| } catch { | ||
| // Non-fatal: we may still have cache namespaces above | ||
| } | ||
|
|
||
| if (nsSet.size > 0) { | ||
| setNamespaces(Array.from(nsSet).sort()) | ||
| setError(null) |
There was a problem hiding this comment.
This change introduces new behavior (merging cached cluster namespaces with pod-discovered namespaces) but the existing useNamespaces unit tests don’t cover the merge case. Add a test that seeds clusterCacheRef with namespaces not present in pods and asserts they still appear in the returned list (and that the result is sorted/deduped).
🔄 Auto-Applying Copilot Code ReviewCopilot code review found 2 code suggestion(s) and 2 general comment(s). @copilot Please apply all of the following code review suggestions:
Also address these general comments:
Push all fixes in a single commit. Run Auto-generated by copilot-review-apply workflow. |
Summary
Changes
web/src/components/layout/mission-sidebar/MissionSidebar.tsxweb/src/components/layout/mission-sidebar/MissionChat.tsxweb/src/hooks/mcp/namespaces.tsTest plan
npm run buildFixes #3944 #3917 #3945