Skip to content

🐛 Make Mission Control a proper modal dialog#4339

Merged
clubanderson merged 1 commit intomainfrom
fix/4271-mission-control-ux
Apr 2, 2026
Merged

🐛 Make Mission Control a proper modal dialog#4339
clubanderson merged 1 commit intomainfrom
fix/4271-mission-control-ux

Conversation

@clubanderson
Copy link
Copy Markdown
Collaborator

@clubanderson clubanderson commented Apr 2, 2026

  • Understand existing MissionControlDialog.tsx and useModalNavigation.ts
  • Remove if (!open) return null so AnimatePresence exit animations run
  • Replace manual ESC / scroll-lock effects with useModalNavigation
  • Add useModalFocusTrap with a ref on the modal panel
  • Build + lint (build passes; 3 pre-existing lint errors in unchanged highestReachedRef pattern)
  • Code review (no comments)
  • CodeQL (no alerts)

Convert Mission Control from a full-screen opaque overlay to a proper
modal with backdrop, inset panel, rounded corners, and "Back to
Dashboard" navigation. This resolves user confusion about whether
Mission Control is a separate page or a layered UI element.

Changes:
- Add semi-transparent backdrop with blur so the underlying page is
  visible, reinforcing that this is an overlay
- Inset the dialog panel 16px from viewport edges with rounded corners
  and a drop shadow to distinguish it from the page
- Add "Back to Dashboard" link with arrow icon in the header
- Make close (X) button larger with a destructive hover state and
  "Close (Esc)" tooltip
- Lock body scroll while the modal is open
- Add proper ARIA attributes (role="dialog", aria-modal, aria-label)
- Clicking the backdrop also closes the modal
- Add scale/translate entrance animation for modal feel

Fixes #4271

Signed-off-by: Andy Anderson <andy@clubanderson.com>
Signed-off-by: Andrew Anderson <andy@clubanderson.com>
Copilot AI review requested due to automatic review settings April 2, 2026 21:57
@kubestellar-prow kubestellar-prow bot added the dco-signoff: yes Indicates the PR's author has signed the DCO. label Apr 2, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 2, 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.

@netlify
Copy link
Copy Markdown

netlify bot commented Apr 2, 2026

Deploy Preview for kubestellarconsole ready!

Name Link
🔨 Latest commit 93066c3
🔍 Latest deploy log https://app.netlify.com/projects/kubestellarconsole/deploys/69cee66695b2890008174347
😎 Deploy Preview https://deploy-preview-4339.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.

@kubestellar-prow kubestellar-prow bot added the size/XL Denotes a PR that changes 500-999 lines, ignoring generated files. label Apr 2, 2026
@clubanderson
Copy link
Copy Markdown
Collaborator Author

/lgtm
/approve

@kubestellar-prow
Copy link
Copy Markdown
Contributor

@clubanderson: you cannot LGTM your own PR.

Details

In response to this:

/lgtm
/approve

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository.

@kubestellar-prow
Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: clubanderson

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

The pull request process is described 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

@kubestellar-prow kubestellar-prow bot added the approved Indicates a PR has been approved by an approver from all required OWNERS files. label Apr 2, 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

Converts Mission Control from a full-screen, page-like overlay into a visually distinct modal dialog to address first-time-user confusion (Fixes #4271).

Changes:

  • Adds a semi-transparent, blurred backdrop and an inset, rounded modal panel with shadow.
  • Introduces clearer close/navigation affordances (Back to Dashboard control + more prominent close button).
  • Adds basic modal behaviors (backdrop click-to-close, Escape-to-close, and body scroll locking) plus entrance animation.

Comment on lines 105 to 106
if (!open) return null

Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

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

AnimatePresence exit animations won’t run as written because the component returns null immediately when open becomes false (if (!open) return null). That unmounts the AnimatePresence tree before Framer Motion can play the exit transitions. Consider always rendering AnimatePresence and moving the open conditional inside it (or controlling mount/unmount from the parent) so the backdrop/panel exit props actually take effect.

Suggested change
if (!open) return null

Copilot uses AI. Check for mistakes.
Comment on lines 73 to +103
// Escape to close
const handleKeyDown = useCallback(
(e: KeyboardEvent) => {
if (e.key === 'Escape') onClose()
},
[onClose]
)

// Track the highest phase the user has reached so they can click back to any visited phase
const currentStepIndex = PHASE_STEPS.findIndex((s) => s.key === state.phase)
const highestReachedRef = useRef(currentStepIndex)
if (currentStepIndex > highestReachedRef.current) {
highestReachedRef.current = currentStepIndex
}
const highestReached = highestReachedRef.current

useEffect(() => {
if (!open) return
document.addEventListener('keydown', handleKeyDown)
return () => document.removeEventListener('keydown', handleKeyDown)
}, [open, handleKeyDown])

// Lock body scroll while modal is open so users cannot scroll the page behind it
useEffect(() => {
if (!open) return
const prev = document.body.style.overflow
document.body.style.overflow = 'hidden'
return () => {
document.body.style.overflow = prev
}
}, [open])
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

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

This modal re-implements ESC handling and body scroll locking manually. The codebase already has a standardized, tested hook (useModalNavigation in web/src/lib/modals/useModalNavigation.ts) that covers Escape, scroll lock, and default-prevent behavior; using it here would reduce duplicated logic and keep modal behavior consistent across the app.

Copilot uses AI. Check for mistakes.
Comment on lines +150 to +163
{/* ── Modal panel ───────────────────────────────────────── */}
<motion.div
role="dialog"
aria-modal="true"
aria-label="Mission Control"
className="fixed z-[200] flex flex-col bg-background rounded-xl border border-border shadow-2xl shadow-black/30 overflow-hidden"
style={{
inset: `${MODAL_INSET_PX}px`,
}}
initial={{ opacity: 0, scale: 0.97, y: 12 }}
animate={{ opacity: 1, scale: 1, y: 0 }}
exit={{ opacity: 0, scale: 0.97, y: 12 }}
transition={{ duration: 0.2, ease: 'easeOut' }}
>
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

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

For a “proper modal dialog”, focus should be trapped within the dialog while it’s open; otherwise Tab/Shift+Tab can move focus to underlying page controls behind the backdrop. The repo already has useModalFocusTrap (and a combined useModal) in web/src/lib/modals/useModalNavigation.ts; consider wiring a ref to the modal panel and enabling focus trap here.

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

🔄 Auto-Applying Copilot Code Review

Copilot code review found 1 code suggestion(s) and 2 general comment(s).

@copilot Please apply all of the following code review suggestions:

  • web/src/components/mission-control/MissionControlDialog.tsx (line 106): ``

Also address these general comments:

  • web/src/components/mission-control/MissionControlDialog.tsx (line 103): This modal re-implements ESC handling and body scroll locking manually. The codebase already has a standardized, tested
  • web/src/components/mission-control/MissionControlDialog.tsx (line 163): For a “proper modal dialog”, focus should be trapped within the dialog while it’s open; otherwise Tab/Shift+Tab can move

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.

@clubanderson clubanderson merged commit 3bb16ce into main Apr 2, 2026
27 of 29 checks passed
@kubestellar-prow kubestellar-prow bot deleted the fix/4271-mission-control-ux branch April 2, 2026 22:02
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 2, 2026

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

Check out what's new:

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

Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 2, 2026

🔄 Auto-Applying Copilot Code Review

Copilot code review found 1 code suggestion(s) and 2 general comment(s)....

All three review points have been addressed in commit 46dd701 (local — the source branch was deleted after the PR merged before I could push):

  1. AnimatePresence exit animations — Removed if (!open) return null so the AnimatePresence tree stays mounted and Framer Motion can play the exit transitions when open becomes false. The {open && ...} conditional inside AnimatePresence now controls mount/unmount correctly.

  2. useModalNavigation — Replaced the manual handleKeyDown callback, ESC useEffect, and body-scroll-lock useEffect with a single useModalNavigation({ isOpen: open, onClose, enableBackspace: false }) call. enableBackspace: false keeps the wizard's own Back buttons in control of step navigation.

  3. Focus trap — Added const panelRef = useRef<HTMLDivElement>(null) wired to the modal panel <motion.div ref={panelRef}> and called useModalFocusTrap(panelRef, open) so Tab/Shift-Tab navigation is contained within the dialog.

Build passes (npm run build ✅). The three lint errors that show on MissionControlDialog.tsx are pre-existing (the highestReachedRef during-render pattern was already flagged in the original file before this PR). Since the branch was deleted post-merge, these fixes should be included in a follow-up PR if desired.

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

Labels

approved Indicates a PR has been approved by an approver from all required OWNERS files. dco-signoff: yes Indicates the PR's author has signed the DCO. size/XL Denotes a PR that changes 500-999 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants