Skip to content

🐛 fix: frontend bugs — drilldown crash, demo banner, arcade resize, save resolution#3963

Merged
clubanderson merged 1 commit intomainfrom
fix/frontend-bugs-batch1
Mar 31, 2026
Merged

🐛 fix: frontend bugs — drilldown crash, demo banner, arcade resize, save resolution#3963
clubanderson merged 1 commit intomainfrom
fix/frontend-bugs-batch1

Conversation

@clubanderson
Copy link
Copy Markdown
Collaborator

Summary

Test plan

  • Open Cluster Drilldown, switch between unhealthy clusters rapidly — verify no crash
  • Enable demo mode, hover over the X/dismiss button — verify hover highlight is centered
  • Open any arcade game (2048, Snake), maximize the card — verify the game board scales to fill the modal
  • Open an AI Mission in fullscreen, click "Save This Resolution" in the Knowledge panel — verify the dialog opens

Fixes #3911 #3912 #3920 #3910

…ize, save resolution

- Guard `issue.issues.length` with `|| []` in ClusterDrillDown to prevent
  "Cannot read properties of undefined (reading 'length')" crash when
  switching between unhealthy clusters (#3911)

- Center the demo mode banner dismiss button hover effect by adding
  `flex items-center justify-center` and `rounded-full`, fixing the
  off-centre hover animation (#3912)

- Add ResizeObserver-based containerSize to CardExpandedContext so game
  cards dynamically scale their boards when maximized. Update Game2048 to
  compute cell size from container dimensions and KubeSnake to use CSS
  transform scale (#3920)

- Wire up the "Save This Resolution" button in ResolutionKnowledgePanel
  by replacing the empty `onSaveNewResolution={() => {}}` handler with
  state that opens SaveResolutionDialog (#3910)

Fixes #3911 #3912 #3920 #3910

Signed-off-by: Andrew Anderson <andy@clubanderson.com>
Copilot AI review requested due to automatic review settings March 31, 2026 12:50
@kubestellar-prow kubestellar-prow bot added the dco-signoff: yes Indicates the PR's author has signed the DCO. label Mar 31, 2026
@netlify
Copy link
Copy Markdown

netlify bot commented Mar 31, 2026

Deploy Preview for kubestellarconsole ready!

Name Link
🔨 Latest commit 9950bd6
🔍 Latest deploy log https://app.netlify.com/projects/kubestellarconsole/deploys/69cbc3223355030008401ddb
😎 Deploy Preview https://deploy-preview-3963.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

👋 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.

@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

@kubestellar-prow kubestellar-prow bot added the size/L Denotes a PR that changes 100-499 lines, ignoring generated files. label Mar 31, 2026
@clubanderson clubanderson merged commit 1c1d641 into main Mar 31, 2026
20 of 21 checks passed
@kubestellar-prow kubestellar-prow bot deleted the fix/frontend-bugs-batch1 branch March 31, 2026 12:51
@github-actions
Copy link
Copy Markdown
Contributor

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

Fixes several frontend UX/runtime issues across drilldown, layout banners, arcade card expansion behavior, and AI mission “Save This Resolution” functionality.

Changes:

  • Prevent Cluster Drilldown crash by guarding against missing issue.issues.
  • Improve banner dismiss button hover/shape alignment via flex centering + rounded-full.
  • Add expanded-card container sizing (ResizeObserver) and use it to scale arcade games (2048 + Snake).
  • Wire “Save This Resolution” from the Knowledge panel to open SaveResolutionDialog.

Reviewed changes

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

Show a summary per file
File Description
web/src/components/layout/mission-sidebar/MissionSidebar.tsx Adds state + dialog mounting to make “Save This Resolution” actionable from the Knowledge panel.
web/src/components/layout/Layout.tsx Centers icon-only banner buttons and makes hit/hover area circular.
web/src/components/drilldown/views/ClusterDrillDown.tsx Guards against issue.issues being undefined to avoid crashes.
web/src/components/cards/CardWrapper.tsx Extends expanded-card context with live container dimensions via ResizeObserver.
web/src/components/cards/Game2048.tsx Computes cell sizing from expanded container dimensions to better fill the modal.
web/src/components/cards/KubeSnake.tsx Scales the canvas in expanded mode based on container dimensions.

Comment on lines +1092 to +1098
{/* Save Resolution Dialog — triggered from ResolutionKnowledgePanel "Save This Resolution" button */}
{activeMission && (
<SaveResolutionDialog
mission={activeMission}
isOpen={showSaveResolutionDialog}
onClose={() => setShowSaveResolutionDialog(false)}
/>
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

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

showSaveResolutionDialog can remain true when activeMission changes (or becomes null). Because the dialog is only rendered when activeMission is truthy, this can lead to the dialog reopening unexpectedly for a different mission the next time activeMission is set. Consider resetting/closing the dialog in an effect when activeMission?.id changes or when activeMission becomes null.

Copilot uses AI. Check for mistakes.
Comment on lines +1092 to +1098
{/* Save Resolution Dialog — triggered from ResolutionKnowledgePanel "Save This Resolution" button */}
{activeMission && (
<SaveResolutionDialog
mission={activeMission}
isOpen={showSaveResolutionDialog}
onClose={() => setShowSaveResolutionDialog(false)}
/>
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

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

MissionSidebar renders a SaveResolutionDialog instance, but MissionChat already mounts its own SaveResolutionDialog for the same mission. Even if only one is opened at a time, duplicating modal instances increases maintenance cost and can cause overlapping overlays/z-index/focus issues if both states ever become true. Consider lifting the save-dialog state up and using a single dialog instance for the mission view.

Copilot uses AI. Check for mistakes.
Comment on lines +155 to +156
// Save Resolution dialog state (triggered from ResolutionKnowledgePanel "Save This Resolution" button)
const [showSaveResolutionDialog, setShowSaveResolutionDialog] = useState(false)
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

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

PR description requirement: the PR body is expected to start with Fixes #<issue> so GitHub auto-links/closes the issue, but the current description begins with a summary section and places Fixes #... at the end. Please move the Fixes #3911 #3912 #3920 #3910 line to be the first line of the PR description.

Copilot uses AI. Check for mistakes.
Comment on lines +369 to +383
useEffect(() => {
if (!isExpanded) {
setContainerSize({ width: 0, height: 0 })
return
}
const el = expandedContentRef.current
if (!el) return
const observer = new ResizeObserver((entries) => {
for (const entry of entries) {
const { width, height } = entry.contentRect
setContainerSize({ width, height })
}
})
observer.observe(el)
return () => observer.disconnect()
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

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

The ResizeObserver callback calls setContainerSize for every observation without checking whether width/height actually changed (and without rounding). This can trigger unnecessary re-renders and can contribute to ResizeObserver loop limit exceeded warnings if children reflow based on containerSize. Consider rounding dimensions and only updating state when values differ from the previous state (or batching via rAF).

Copilot uses AI. Check for mistakes.
if (!isExpanded || containerSize.width === 0 || containerSize.height === 0) return 1
const availW = containerSize.width
const availH = containerSize.height - SNAKE_CHROME_HEIGHT
return Math.max(1, Math.min(availW / DEFAULT_CANVAS_SIZE, availH / DEFAULT_CANVAS_SIZE))
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

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

canvasScale is clamped to a minimum of 1 (Math.max(1, ...)), which prevents downscaling when the expanded container is smaller than 400px (e.g., small screens or when chrome height reduces available space). In those cases the canvas may overflow or clip. Consider allowing canvasScale < 1 (and adjusting layout accordingly) so the game always fits within the available container.

Suggested change
return Math.max(1, Math.min(availW / DEFAULT_CANVAS_SIZE, availH / DEFAULT_CANVAS_SIZE))
return Math.min(availW / DEFAULT_CANVAS_SIZE, availH / DEFAULT_CANVAS_SIZE)

Copilot uses AI. Check for mistakes.
const availW = containerSize.width - GRID_PADDING * 2 - expandedGap * (GRID_COLS - 1)
const availH = containerSize.height - CHROME_HEIGHT - GRID_PADDING * 2 - expandedGap * (GRID_COLS - 1)
const maxCell = Math.floor(Math.min(availW, availH) / GRID_COLS)
return Math.max(maxCell, DEFAULT_CELL_SIZE)
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

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

computedCellSize is clamped to at least DEFAULT_CELL_SIZE even in expanded mode. If the expanded container is shorter/narrower than the minimum grid+chrome footprint (e.g., small viewports), the 2048 board can still overflow. Consider allowing cell sizes below the collapsed default when needed (or scaling the board) so the grid always fits within containerSize.

Suggested change
return Math.max(maxCell, DEFAULT_CELL_SIZE)
if (!Number.isFinite(maxCell) || maxCell <= 0) {
return DEFAULT_CELL_SIZE
}
return maxCell

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 4 general comment(s).

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

  • web/src/components/cards/KubeSnake.tsx (line 53): return Math.min(availW / DEFAULT_CANVAS_SIZE, availH / DEFAULT_CANVAS_SIZE)
  • web/src/components/cards/Game2048.tsx (line 295): if (!Number.isFinite(maxCell) || maxCell <= 0) { return DEFAULT_CELL_SIZE ...

Also address these general comments:

  • web/src/components/layout/mission-sidebar/MissionSidebar.tsx (line 1098): showSaveResolutionDialog can remain true when activeMission changes (or becomes null). Because the dialog is only
  • web/src/components/layout/mission-sidebar/MissionSidebar.tsx (line 1098): MissionSidebar renders a SaveResolutionDialog instance, but MissionChat already mounts its own `SaveResolutionDialog
  • web/src/components/layout/mission-sidebar/MissionSidebar.tsx (line 156): PR description requirement: the PR body is expected to start with Fixes #<issue> so GitHub auto-links/closes the issue
  • web/src/components/cards/CardWrapper.tsx (line 383): The ResizeObserver callback calls setContainerSize for every observation without checking whether width/height actuall

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 added a commit that referenced this pull request Mar 31, 2026
- Reset save resolution dialog when active mission changes (prevents
  stale dialog reopening for a different mission)
- Round ResizeObserver dimensions and skip no-op updates to avoid
  unnecessary rerenders and loop warnings
- Allow game scaling below 1x so games fit small viewports
- Allow 2048 cell size below default when expanded container is small

Signed-off-by: Andrew Anderson <andy@clubanderson.com>
clubanderson added a commit that referenced this pull request Mar 31, 2026
- Reset save resolution dialog when active mission changes (prevents
  stale dialog reopening for a different mission)
- Round ResizeObserver dimensions and skip no-op updates to avoid
  unnecessary rerenders and loop warnings
- Allow game scaling below 1x so games fit small viewports
- Allow 2048 cell size below default when expanded container is small

Signed-off-by: Andrew Anderson <andy@clubanderson.com>
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.

Cluster Drilldown Fails When Switching Between Unhealthy Clusters

3 participants