Skip to content

refactor: split high-complexity components into focused files#4062

Merged
clubanderson merged 2 commits intomainfrom
copilot/auto-qa-split-high-complexity-components-again
Apr 1, 2026
Merged

refactor: split high-complexity components into focused files#4062
clubanderson merged 2 commits intomainfrom
copilot/auto-qa-split-high-complexity-components-again

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 1, 2026

Auto-QA flagged several files exceeding 400+ lines with many hooks. This PR addresses two of the most structurally clear cases by extracting sub-components and utilities into dedicated modules.


📌 Fixes


📝 Summary of Changes

  • SnoozedCards.tsx: 419 → 150 lines — three inline sub-components and a useHoverState hook extracted into separate files
  • GitHubCIMonitor.tsx: 541 → 510 lines — localStorage utilities and formatTimeAgo moved to a collocated utils module

Changes Made

  • Extracted useHoverState()web/src/hooks/useHoverState.ts (now reusable)
  • Extracted SnoozedItemweb/src/components/layout/SnoozedItem.tsx
  • Extracted SnoozedRecommendationItemweb/src/components/layout/SnoozedRecommendationItem.tsx
  • Extracted SnoozedMissionItemweb/src/components/layout/SnoozedMissionItem.tsx
  • Extracted formatTimeAgo, loadRepos, saveRepos, REPOS_STORAGE_KEY, DEFAULT_REPOSweb/src/components/cards/workload-monitor/gitHubCIUtils.ts

Checklist

  • I used a coding agent (Claude Code, Copilot, Gemini, or Codex) to generate/review this code
  • I have reviewed the project's contribution guidelines
  • New cards target console-marketplace, not this repo
  • isDemoData is wired correctly (cards show Demo badge when using demo data)
  • I have written unit tests for the changes (if applicable)
  • I have tested the changes locally and ensured they work as expected
  • All commits are signed with DCO (git commit -s)

Screenshots or Logs (if applicable)

N/A — pure refactor, no behavioral changes.


👀 Reviewer Notes

No logic was changed — this is purely mechanical extraction. Each sub-component retains its original props interface and rendering logic verbatim. The useHoverState hook is a candidate for broader reuse across the layout layer.

@kubestellar-prow kubestellar-prow bot added do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. dco-signoff: no Indicates the PR's author has not signed the DCO. labels Apr 1, 2026
Copilot AI linked an issue Apr 1, 2026 that may be closed by this pull request
@netlify
Copy link
Copy Markdown

netlify bot commented Apr 1, 2026

Deploy Preview for kubestellarconsole ready!

Name Link
🔨 Latest commit cde18de
🔍 Latest deploy log https://app.netlify.com/projects/kubestellarconsole/deploys/69cc8042e93faf0008e55634
😎 Deploy Preview https://deploy-preview-4062.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 @Copilot — 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 github-actions bot removed the dco-signoff: no Indicates the PR's author has not signed the DCO. label Apr 1, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 1, 2026

❌ PR Title Verification Failed

Your PR title does not follow the required format.

Current title: refactor: split high-complexity components into focused files

Required Format

PR titles must start with one of these emoji prefixes:

Emoji Meaning
⚠️ Breaking change
Non-breaking feature
🐛 Patch fix / Bug fix
📖 Documentation
🚀 Release
🌱 Infra/Tests/Other

How to Fix

Edit your PR title to start with the appropriate emoji. For example:

  • ✨ Add new feature for user authentication
  • 🐛 Fix crash when loading empty config
  • 📖 Update installation guide

You can edit the title by clicking the Edit button next to your PR title.


This comment was automatically posted by the PR Title Verifier workflow.

@kubestellar-prow kubestellar-prow bot added the size/XS Denotes a PR that changes 0-9 lines, ignoring generated files. label Apr 1, 2026
@github-actions github-actions bot added dco-signoff: yes Indicates the PR's author has signed the DCO. copilot labels Apr 1, 2026
- Extract useHoverState hook from SnoozedCards.tsx → hooks/useHoverState.ts
- Extract SnoozedItem sub-component → layout/SnoozedItem.tsx
- Extract SnoozedRecommendationItem sub-component → layout/SnoozedRecommendationItem.tsx
- Extract SnoozedMissionItem sub-component → layout/SnoozedMissionItem.tsx
- Extract GitHubCI utilities (formatTimeAgo, loadRepos, saveRepos) → gitHubCIUtils.ts

SnoozedCards.tsx: 419 → 150 lines; GitHubCIMonitor.tsx: 541 → 510 lines

Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>

Agent-Logs-Url: https://github.com/kubestellar/console/sessions/c7fe5c37-1522-4aaa-9588-1f0415dd4d26

Co-authored-by: clubanderson <407614+clubanderson@users.noreply.github.com>
@kubestellar-prow kubestellar-prow bot added dco-signoff: no Indicates the PR's author has not signed the DCO. and removed dco-signoff: yes Indicates the PR's author has signed the DCO. labels Apr 1, 2026
@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 ask for approval from clubanderson. 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
Copy link
Copy Markdown
Contributor

Thanks for your pull request. Before we can look at it, you'll need to add a 'DCO signoff' to your commits.

📝 Please follow instructions in the contributing guide to update your commits with the DCO

Full details of the Developer Certificate of Origin can be found at developercertificate.org.

The list of commits missing DCO signoff:

Details

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. I understand the commands that are listed here.

@kubestellar-prow kubestellar-prow bot added size/XL Denotes a PR that changes 500-999 lines, ignoring generated files. and removed size/XS Denotes a PR that changes 0-9 lines, ignoring generated files. labels Apr 1, 2026
@github-actions github-actions bot added dco-signoff: yes Indicates the PR's author has signed the DCO. and removed dco-signoff: no Indicates the PR's author has not signed the DCO. labels Apr 1, 2026
Copilot AI changed the title [WIP] Split high-complexity components for better manageability refactor: split high-complexity components into focused files Apr 1, 2026
Copilot AI requested a review from clubanderson April 1, 2026 02:18
@clubanderson clubanderson marked this pull request as ready for review April 1, 2026 02:20
Copilot AI review requested due to automatic review settings April 1, 2026 02:20
@clubanderson clubanderson removed the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. 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

Refactors two high-complexity UI areas by extracting inline subcomponents and utilities into dedicated modules, reducing file size and improving reuse/maintainability within the web/ React + TypeScript frontend.

Changes:

  • Extracted useHoverState into a reusable hook (web/src/hooks/useHoverState.ts).
  • Split SnoozedCards inline item renderers into dedicated layout components (SnoozedItem, SnoozedRecommendationItem, SnoozedMissionItem).
  • Moved GitHub CI monitor localStorage + time formatting helpers into a collocated utility module (gitHubCIUtils.ts).

Reviewed changes

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

Show a summary per file
File Description
web/src/hooks/useHoverState.ts Introduces a shared hover-state hook used by the extracted snoozed item components.
web/src/components/layout/SnoozedItem.tsx New extracted component for snoozed card swap items.
web/src/components/layout/SnoozedRecommendationItem.tsx New extracted component for snoozed recommendation items.
web/src/components/layout/SnoozedMissionItem.tsx New extracted component for snoozed mission items.
web/src/components/layout/SnoozedCards.tsx Updated to consume extracted item components and removed inline implementations.
web/src/components/cards/workload-monitor/gitHubCIUtils.ts New utility module for repo persistence and relative-time formatting.
web/src/components/cards/workload-monitor/GitHubCIMonitor.tsx Updated to import helpers from the new utility module.

Comment on lines +3 to +13
/**
* Simple hook for tracking hover state with mouse event handlers.
* Returns isHovered boolean and hoverProps to spread onto an element.
*/
export function useHoverState() {
const [isHovered, setIsHovered] = useState(false)
return {
isHovered,
hoverProps: {
onMouseEnter: () => setIsHovered(true),
onMouseLeave: () => setIsHovered(false),
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.

useHoverState only wires mouse enter/leave, so hover-only UI (e.g., action buttons) can’t be revealed via keyboard focus and is unreliable on touch. Consider adding focus/blur and pointer enter/leave handlers (and/or exposing a way to force-hover) so consumers can make the same interactions accessible without duplicating logic.

Suggested change
/**
* Simple hook for tracking hover state with mouse event handlers.
* Returns isHovered boolean and hoverProps to spread onto an element.
*/
export function useHoverState() {
const [isHovered, setIsHovered] = useState(false)
return {
isHovered,
hoverProps: {
onMouseEnter: () => setIsHovered(true),
onMouseLeave: () => setIsHovered(false),
type UseHoverStateOptions = {
/**
* When true, forces the hook to report a hovered state regardless of
* incoming events. Event handlers still update internal state so that
* the hook behaves correctly if this flag is later removed or toggled.
*/
forceHover?: boolean
}
/**
* Simple hook for tracking hover-like state with event handlers.
* Returns isHovered boolean and hoverProps to spread onto an element.
* Supports mouse, pointer, and focus interactions.
*/
export function useHoverState(options?: UseHoverStateOptions) {
const [isHoveredInternal, setIsHoveredInternal] = useState(false)
const isHovered = options?.forceHover ?? isHoveredInternal
const setHoveredTrue = () => setIsHoveredInternal(true)
const setHoveredFalse = () => setIsHoveredInternal(false)
return {
isHovered,
hoverProps: {
// Mouse events (existing behavior)
onMouseEnter: setHoveredTrue,
onMouseLeave: setHoveredFalse,
// Pointer events (better touch / pen support)
onPointerEnter: setHoveredTrue,
onPointerLeave: setHoveredFalse,
// Focus events (keyboard accessibility)
onFocus: setHoveredTrue,
onBlur: setHoveredFalse,

Copilot uses AI. Check for mistakes.
Comment on lines +33 to +34
>
<X className="w-3 h-3" />
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.

Icon-only dismiss button is missing an accessible name. Please add an aria-label (and typically mark the icon aria-hidden) so screen readers announce the control meaningfully.

Suggested change
>
<X className="w-3 h-3" />
aria-label={t('actions.dismissSnoozedItem')}
>
<X className="w-3 h-3" aria-hidden="true" />

Copilot uses AI. Check for mistakes.
Comment on lines +43 to +44
>
<X className="w-3 h-3" />
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.

Icon-only dismiss button is missing an accessible name. Please add an aria-label (and typically mark the icon aria-hidden) so screen readers announce the control meaningfully.

Suggested change
>
<X className="w-3 h-3" />
aria-label="Dismiss snoozed recommendation"
>
<X className="w-3 h-3" aria-hidden="true" />

Copilot uses AI. Check for mistakes.
Comment on lines +59 to +60
>
<X className="w-3 h-3" />
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.

Icon-only dismiss button is missing an accessible name. Please add an aria-label (and typically mark the icon aria-hidden) so screen readers announce the control meaningfully.

Suggested change
>
<X className="w-3 h-3" />
aria-label={t('actions.dismissSnoozedMission', 'Dismiss snoozed mission')}
>
<X className="w-3 h-3" aria-hidden="true" />

Copilot uses AI. Check for mistakes.
Comment on lines +16 to +24
/** Loads the configured repo list from localStorage, falling back to DEFAULT_REPOS. */
export function loadRepos(): string[] {
try {
const stored = localStorage.getItem(REPOS_STORAGE_KEY)
if (stored) {
const parsed = JSON.parse(stored)
if (Array.isArray(parsed) && parsed.length > 0) {
return parsed
}
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.

loadRepos() returns the raw parsed JSON array without validating element types. Since localStorage is user-controllable, consider validating/coercing to a bounded string[] (e.g., filter to strings and cap length) to avoid unexpected values causing downstream fetch/URL issues.

Copilot uses AI. Check for mistakes.

/** Persists the repo list to localStorage. */
export function saveRepos(repos: string[]) {
localStorage.setItem(REPOS_STORAGE_KEY, JSON.stringify(repos))
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.

saveRepos() writes to localStorage without a try/catch. In environments where storage is unavailable/blocked (privacy mode, disabled storage, SSR/tests), this can throw and crash the component. Wrap the write in a try/catch (and consider no-op fallback) to keep the UI resilient.

Suggested change
localStorage.setItem(REPOS_STORAGE_KEY, JSON.stringify(repos))
try {
localStorage.setItem(REPOS_STORAGE_KEY, JSON.stringify(repos))
} catch {
// Ignore storage errors to keep UI resilient (e.g. privacy mode, SSR/tests)
}

Copilot uses AI. Check for mistakes.
Comment on lines +1 to +2
export const REPOS_STORAGE_KEY = 'github_ci_repos'
export const DEFAULT_REPOS = ['kubestellar/kubestellar', 'kubestellar/console']
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 module filename gitHubCIUtils.ts uses mixed casing that’s easy to mistype and is inconsistent with common github... casing patterns. Consider renaming to a more conventional filename (e.g., githubCIUtils.ts) to reduce import-path friction on case-sensitive filesystems.

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

🔄 Auto-Applying Copilot Code Review

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

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

  • web/src/hooks/useHoverState.ts (line 13): `type UseHoverStateOptions = {
    /**
    • When true, forces the hook to report a ...`
  • web/src/components/layout/SnoozedItem.tsx (line 34): aria-label={t('actions.dismissSnoozedItem')} > <X className="w-3 h...
  • web/src/components/layout/SnoozedRecommendationItem.tsx (line 44): aria-label="Dismiss snoozed recommendation" > <X className="w-3 h-...
  • web/src/components/layout/SnoozedMissionItem.tsx (line 60): aria-label={t('actions.dismissSnoozedMission', 'Dismiss snoozed mission')} ...
  • web/src/components/cards/workload-monitor/gitHubCIUtils.ts (line 34): try { localStorage.setItem(REPOS_STORAGE_KEY, JSON.stringify(repos)) } cat...

Also address these general comments:

  • web/src/components/cards/workload-monitor/gitHubCIUtils.ts (line 24): loadRepos() returns the raw parsed JSON array without validating element types. Since localStorage is user-controllabl
  • web/src/components/cards/workload-monitor/gitHubCIUtils.ts (line 2): The module filename gitHubCIUtils.ts uses mixed casing that’s easy to mistype and is inconsistent with common `github.

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.

@github-actions github-actions bot mentioned this pull request Apr 1, 2026
@clubanderson clubanderson merged commit de41103 into main Apr 1, 2026
27 of 39 checks passed
@kubestellar-prow kubestellar-prow bot deleted the copilot/auto-qa-split-high-complexity-components-again branch April 1, 2026 11:15
@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

clubanderson added a commit that referenced this pull request Apr 1, 2026
Rebased the type safety PR (#4085) on current main to resolve merge
conflicts with the component split (#4062). Also fixed 5 new type
errors introduced by the any→unknown change:
- kubectlProxy.ts: guard optional waitReason with ?? ''
- Marketplace.tsx: narrow unknown data with type guard before access

Original changes (from Copilot):
- 3 source files: any → unknown/proper types
- 62 test files: remove {} as any spreads, use typed props

Signed-off-by: Andrew Anderson <andy@clubanderson.com>
clubanderson added a commit that referenced this pull request Apr 1, 2026
…#4098)

Rebased the type safety PR (#4085) on current main to resolve merge
conflicts with the component split (#4062). Also fixed 5 new type
errors introduced by the any→unknown change:
- kubectlProxy.ts: guard optional waitReason with ?? ''
- Marketplace.tsx: narrow unknown data with type guard before access

Original changes (from Copilot):
- 3 source files: any → unknown/proper types
- 62 test files: remove {} as any spreads, use typed props

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

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

[Auto-QA] High-complexity components could be split

3 participants