Skip to content

✨ Mission Explorer UX: resizable sidebar, file icons, source/PR links, YAML formatting#4065

Merged
clubanderson merged 1 commit intomainfrom
feat/mission-browser-ux-improvements
Apr 1, 2026
Merged

✨ Mission Explorer UX: resizable sidebar, file icons, source/PR links, YAML formatting#4065
clubanderson merged 1 commit intomainfrom
feat/mission-browser-ux-improvements

Conversation

@clubanderson
Copy link
Copy Markdown
Collaborator

Summary

UX improvements for the Mission Explorer file browsing experience:

Tree sidebar:

  • File-type icons: orange for YAML, green for MD, blue for JSON
  • Hover tooltips on truncated filenames
  • Resizable sidebar (drag the edge, 180px–500px range)
  • Source (↗) and PR (⑂) buttons on GitHub files — opens view/edit on GitHub
  • Refresh button properly re-fetches contents instead of just toggling

Detail view:

  • step.yaml now renders as a separate formatted YAML block with proper indentation
  • step.command uses <pre> for newline preservation
  • kubectl command is a simple kubectl apply -f <name>.yaml instead of heredoc

Test plan

  • Expand sample-runbooks → YAML files show orange icon, MD files show green
  • Hover a truncated filename → full name appears in tooltip
  • Drag sidebar edge → resizes between 180–500px
  • Click ↗ on a file → opens GitHub source in new tab
  • Click ⑂ on a file → opens GitHub edit view in new tab
  • Click ray-cluster.yaml → YAML section shows properly indented content
  • Click refresh → re-fetches and shows files (not just collapse/expand)

Tree sidebar:
- File-type icons: orange (YAML), green (MD), blue (JSON)
- Hover tooltips on truncated filenames (title attribute)
- Resizable sidebar with drag handle (180px–500px)
- Source button (↗) opens file on GitHub in new tab
- PR button (⑂) opens GitHub edit view to create a PR
- Refresh button properly re-fetches instead of toggle

Detail view:
- step.yaml now renders as a separate formatted YAML block
  with proper whitespace-pre-wrap and copy button
- step.command uses pre instead of code for newline preservation
- Simpler kubectl command (not heredoc with embedded YAML)

Signed-off-by: Andrew Anderson <andy@clubanderson.com>
Copilot AI review requested due to automatic review settings April 1, 2026 02:25
@clubanderson clubanderson merged commit 1cf086d into main Apr 1, 2026
@kubestellar-prow kubestellar-prow bot added the dco-signoff: yes Indicates the PR's author has signed the DCO. label Apr 1, 2026
@kubestellar-prow kubestellar-prow bot deleted the feat/mission-browser-ux-improvements branch April 1, 2026 02:25
@kubestellar-prow
Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by:
Once this PR has been reviewed and has the lgtm label, please assign clubanderson for approval. For more information see the Code Review Process.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@netlify
Copy link
Copy Markdown

netlify bot commented Apr 1, 2026

Deploy Preview for kubestellarconsole ready!

Name Link
🔨 Latest commit 368eb19
🔍 Latest deploy log https://app.netlify.com/projects/kubestellarconsole/deploys/69cc8209ec1fb400087be32e
😎 Deploy Preview https://deploy-preview-4065.console-deploy-preview.kubestellar.io
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 1, 2026

👋 Hey @clubanderson — thanks for opening this PR!

🤖 This project is developed exclusively using AI coding assistants.

Please do not attempt to code anything for this project manually.
All contributions should be authored using an AI coding tool such as:

This ensures consistency in code style, architecture patterns, test coverage,
and commit quality across the entire codebase.


This is an automated message.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 1, 2026

Thank you for your contribution! Your PR has been merged.

Check out what's new:

Stay connected: Slack #kubestellar-dev | Multi-Cluster Survey

@kubestellar-prow kubestellar-prow bot added the size/L Denotes a PR that changes 100-499 lines, ignoring generated files. label Apr 1, 2026
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

UX improvements to the Mission Explorer experience in the web console, focused on making mission/file browsing and step details easier to read and act on.

Changes:

  • Improves step detail rendering by preserving command newlines and showing a dedicated YAML block with copy support.
  • Enhances the mission tree with file-type icons, filename tooltips, and GitHub “view source” / “edit/create PR” links for GitHub-backed files.
  • Adds a resizable left sidebar and updates refresh behavior to re-fetch tree contents.

Reviewed changes

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

File Description
web/src/lib/missions/fileParser.ts Simplifies generated kubectl command for wrapped Kubernetes resources.
web/src/components/missions/MissionDetailView.tsx Renders step.command and step.yaml in <pre> blocks with copy buttons for better formatting.
web/src/components/missions/MissionBrowser.tsx Adds resizable sidebar UI and updates tree refresh behavior.
web/src/components/missions/browser/TreeNodeItem.tsx Adds file-type icons, title tooltips, and GitHub source/edit links for GitHub files.

Comment on lines 1427 to +1447
onRefresh={(node.id === 'github' || node.id === 'local') ? (child) => {
// Mark node as unloaded to force re-fetch
// Reset node to unloaded state and collapse
setTreeNodes((prev) =>
updateNodeInTree(prev, child.id, {
loaded: false,
loading: false,
children: [],
isEmpty: false,
})
)
// Collapse and re-expand to trigger load
setExpandedNodes((prev) => {
const next = new Set(prev)
next.delete(child.id)
return next
})
// Re-expand after a tick to trigger the useEffect
// Re-expand with a fresh node reference so toggleNode sees loaded=false
setTimeout(() => {
toggleNode(child)
selectNode(child)
}, 50)
const freshNode: TreeNode = { ...child, loaded: false, loading: false, children: [] }
toggleNode(freshNode)
selectNode(freshNode)
}, 100)
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

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

The refresh handler schedules toggleNode(child) via setTimeout, but toggleNode reads expandedNodes from its closure. Because expandedNodes is updated just before the timeout, the toggleNode function captured here can be stale and may treat the node as still-expanded (collapsing it instead of reloading). Consider refactoring toggleNode to avoid relying on captured expandedNodes (e.g., use a functional setExpandedNodes to decide expand/collapse), or avoid the timeout + toggleNode indirection by explicitly triggering the reload after state updates (e.g., a dedicated reloadNode(nodeId) that fetches children regardless of prior expansion state).

Copilot uses AI. Check for mistakes.
Comment on lines +1550 to +1567
onMouseDown={(e) => {
e.preventDefault()
isDraggingRef.current = true
const startX = e.clientX
const startWidth = sidebarWidth
const onMouseMove = (moveEvent: MouseEvent) => {
if (!isDraggingRef.current) return
const delta = moveEvent.clientX - startX
const newWidth = Math.min(MAX_SIDEBAR_WIDTH, Math.max(MIN_SIDEBAR_WIDTH, startWidth + delta))
setSidebarWidth(newWidth)
}
const onMouseUp = () => {
isDraggingRef.current = false
document.removeEventListener('mousemove', onMouseMove)
document.removeEventListener('mouseup', onMouseUp)
}
document.addEventListener('mousemove', onMouseMove)
document.addEventListener('mouseup', onMouseUp)
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

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

The sidebar resize handle adds mousemove/mouseup listeners on document inside onMouseDown, but those listeners won't be removed if the component unmounts while dragging (e.g., dialog closes mid-drag), potentially leaving dangling listeners and calling setSidebarWidth on an unmounted component. Suggest storing handlers in refs and removing them in a useEffect cleanup (or using pointer events + setPointerCapture / AbortController) to guarantee cleanup on unmount/cancel.

Suggested change
onMouseDown={(e) => {
e.preventDefault()
isDraggingRef.current = true
const startX = e.clientX
const startWidth = sidebarWidth
const onMouseMove = (moveEvent: MouseEvent) => {
if (!isDraggingRef.current) return
const delta = moveEvent.clientX - startX
const newWidth = Math.min(MAX_SIDEBAR_WIDTH, Math.max(MIN_SIDEBAR_WIDTH, startWidth + delta))
setSidebarWidth(newWidth)
}
const onMouseUp = () => {
isDraggingRef.current = false
document.removeEventListener('mousemove', onMouseMove)
document.removeEventListener('mouseup', onMouseUp)
}
document.addEventListener('mousemove', onMouseMove)
document.addEventListener('mouseup', onMouseUp)
onPointerDown={(e) => {
e.preventDefault()
isDraggingRef.current = true
const startX = e.clientX
const startWidth = sidebarWidth
const handle = e.currentTarget
const pointerId = e.pointerId
if (handle.setPointerCapture) {
handle.setPointerCapture(pointerId)
}
const onPointerMove = (moveEvent: PointerEvent) => {
if (!isDraggingRef.current) return
const delta = moveEvent.clientX - startX
const newWidth = Math.min(
MAX_SIDEBAR_WIDTH,
Math.max(MIN_SIDEBAR_WIDTH, startWidth + delta),
)
setSidebarWidth(newWidth)
}
const onPointerUp = (upEvent: PointerEvent) => {
isDraggingRef.current = false
if (handle.releasePointerCapture) {
handle.releasePointerCapture(pointerId)
}
handle.removeEventListener('pointermove', onPointerMove)
handle.removeEventListener('pointerup', onPointerUp)
}
handle.addEventListener('pointermove', onPointerMove)
handle.addEventListener('pointerup', onPointerUp)

Copilot uses AI. Check for mistakes.
Comment on lines +132 to +133
const sourceUrl = `https://github.com/${owner}/${repo}/blob/main/${filePath}`
const editUrl = `https://github.com/${owner}/${repo}/edit/main/${filePath}`
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

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

The generated GitHub URLs are hardcoded to the main branch (blob/main and edit/main). Repos can have a different default branch (e.g., master), which will make these links 404. Consider using a branch-agnostic ref like HEAD in the URL, or retrieving and storing the repo default branch when listing repos and using that here.

Suggested change
const sourceUrl = `https://github.com/${owner}/${repo}/blob/main/${filePath}`
const editUrl = `https://github.com/${owner}/${repo}/edit/main/${filePath}`
const sourceUrl = `https://github.com/${owner}/${repo}/blob/HEAD/${filePath}`
const editUrl = `https://github.com/${owner}/${repo}/edit/HEAD/${filePath}`

Copilot uses AI. Check for mistakes.
Comment on lines +136 to +155
<a
href={sourceUrl}
target="_blank"
rel="noopener noreferrer"
onClick={(e) => e.stopPropagation()}
className="p-1 min-h-7 min-w-7 rounded hover:bg-blue-500/20 text-muted-foreground hover:text-blue-400 transition-colors flex-shrink-0 flex items-center justify-center"
title="View source on GitHub"
>
<ExternalLink className="w-3 h-3" />
</a>
<a
href={editUrl}
target="_blank"
rel="noopener noreferrer"
onClick={(e) => e.stopPropagation()}
className="p-1 min-h-7 min-w-7 rounded hover:bg-green-500/20 text-muted-foreground hover:text-green-400 transition-colors flex-shrink-0 flex items-center justify-center"
title="Edit and create PR on GitHub"
>
<GitPullRequest className="w-3 h-3" />
</a>
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

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

These GitHub action links are icon-only. They have title, but they should also include accessible names (e.g., aria-label) so screen readers can announce their purpose consistently with other icon-only controls in the codebase.

Copilot uses AI. Check for mistakes.
@clubanderson
Copy link
Copy Markdown
Collaborator Author

🔄 Auto-Applying Copilot Code Review

Copilot 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:

  • web/src/components/missions/MissionBrowser.tsx (line 1447): The refresh handler schedules toggleNode(child) via setTimeout, but toggleNode reads expandedNodes from its clos
  • web/src/components/missions/browser/TreeNodeItem.tsx (line 155): These GitHub action links are icon-only. They have title, but they should also include accessible names (e.g., `aria-l

Push all fixes in a single commit. Run cd web && npm run build && npm run lint before committing.


Auto-generated by copilot-review-apply workflow.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dco-signoff: yes Indicates the PR's author has signed the DCO. size/L Denotes a PR that changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants