Skip to content

fix(dashboard): render error and unknown-type states instead of an infinite spinner - #3098

Merged
myasnikovdaniil merged 1 commit into
mainfrom
fix/dashboard-spinner-states
Jun 29, 2026
Merged

myasnikovdaniil merged 1 commit into
mainfrom
fix/dashboard-spinner-states

Conversation

@myasnikovdaniil

@myasnikovdaniil myasnikovdaniil commented Jun 26, 2026 •

Copy link
Copy Markdown
Contributor

What this PR does

Two console pages ordered their loading guard ahead of the terminal state, making the latter unreachable. Both are pre-existing upstream behaviors carried in by the cozystack-ui vendoring (#2963) and flagged as non-blocking findings in that PR's review.

  • ApplicationDetailPage checked isLoading || !instance || !ad before if (error). On a failed GET (403/404/5xx) isLoading is false and instance stays undefined, so the page rendered the loading spinner forever and never reached the "not found" branch. The error branch now runs first.
  • ApplicationListPage's defsLoading || (!ad && plural) guard stayed true for any unrecognized plural once definitions had loaded, so the "Unknown application type." branch was unreachable. The guard now only spins while definitions are loading.

Verification

  • Added co-located regression tests for the failed-GET (detail) and unknown-plural (list) paths.
  • pnpm typecheck clean; full console suite green (pnpm test: 317 passed, +4 new).
fix(dashboard): the console now shows a not-found message on a failed application GET and an unknown-type message for an unrecognized resource, instead of spinning forever

Summary by CodeRabbit

  • Bug Fixes
    • Improved loading and error handling on application pages so users see the correct message at the right time.
    • “Unknown application type” now appears only after application definitions finish loading, instead of being masked by a loading state.
    • Application detail pages now show a “not found” message when the requested app can’t be loaded, rather than continuing to display a spinner.

…finite spinner

Two console pages ordered their loading guard ahead of the terminal state, making the latter unreachable:

- ApplicationDetailPage checked `isLoading || !instance || !ad` before `if (error)`. On a failed GET, isLoading is false and instance stays undefined, so the page spun forever and never showed the not-found message. The error branch now runs first.

- ApplicationListPage's `defsLoading || (!ad && plural)` guard stayed true for any unrecognized plural once definitions had loaded, so 'Unknown application type.' was unreachable. The guard now only spins while definitions are loading.

Both are pre-existing upstream behaviors carried in by the cozystack-ui vendoring (#2963). Adds co-located regression tests for the failed-GET and unknown-plural paths.

Assisted-By: Claude <[email protected]>
Signed-off-by: Myasnikov Daniil <[email protected]>
@github-actions github-actions Bot added area/dashboard Issues or PRs related to the dashboard / UI kind/bug Categorizes issue or PR as related to a bug size/L This PR changes 100-499 lines, ignoring generated files labels Jun 26, 2026
@dosubot dosubot Bot added the kind/backport Categorizes issue or PR as requiring a backport to the current release line label Jun 26, 2026
@coderabbitai

coderabbitai Bot commented Jun 26, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Application list and detail routes now separate loading states from unresolved or failed application lookups. The list page shows unknown-type only after definitions load, and the detail page shows not found when the instance fetch returns an error.

Changes

Console route guards

Layer / File(s) Summary
List route loading guard
packages/system/dashboard/images/console/apps/console/src/routes/ApplicationListPage.tsx, packages/system/dashboard/images/console/apps/console/src/routes/ApplicationListPage.test.tsx
ApplicationListPage now returns loading only while definitions are loading, and the new tests cover loaded unknown plurals and in-flight definition loading.
Detail route error guard
packages/system/dashboard/images/console/apps/console/src/routes/detail/ApplicationDetailPage.tsx, packages/system/dashboard/images/console/apps/console/src/routes/detail/ApplicationDetailPage.test.tsx
ApplicationDetailPage now renders not found on fetch errors before the loading branch, and the new tests cover error and loading states.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~15 minutes

Poem

(^_^)/
A bunny hops through console light,
With loading states now tucked in right.
Unknown, not found, and spinner too,
Each guard now knows just what to do.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main fix: dashboard pages now render terminal error and unknown-type states instead of spinning forever.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/dashboard-spinner-states

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@gemini-code-assist

Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request addresses UI issues where certain console pages would remain stuck in a loading state indefinitely when encountering errors or unknown resource types. By reordering the conditional rendering logic, the application now correctly prioritizes error handling and 'not found' states, improving the user experience and providing better feedback on failed requests.

Highlights

  • Fixed infinite loading states: Updated ApplicationDetailPage and ApplicationListPage to prioritize error and unknown-type handling over loading guards, ensuring users see meaningful feedback instead of a perpetual spinner.
  • Added regression testing: Introduced co-located unit tests for both pages to verify that error states are correctly reached and rendered when data fetching fails or resource types are unrecognized.
New Features

🧠 You can now enable Memory (public preview) to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment Gemini (@gemini-code-assist) Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize the Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counterproductive. You can react with 👍 and 👎 on Gemini (@gemini-code-assist) comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution. ↩

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request introduces unit tests and fixes loading guard bugs in both ApplicationListPage and ApplicationDetailPage to prevent infinite loading spinners when an application type is unknown or a fetch error occurs. The reviewer suggested a further improvement in ApplicationDetailPage to prevent an infinite spinner when the application definitions have loaded but the specific application definition (ad) is not found (e.g., due to an unrecognized plural parameter).

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +72 to 88
// Check the fetch error before the loading guard: on a failed GET, isLoading
// is false and instance stays undefined, so a !instance-first guard would
// spin forever and never reach this branch.
if (error) {
return (
<div className="flex items-center gap-2 p-8 text-slate-500">
<Spinner /> Loading…
<div className="p-8 text-red-600">
Application <code>{name}</code> not found.
</div>
)
}
if (error) {
if (isLoading || !instance || !ad) {
return (
<div className="p-8 text-red-600">
Application <code>{name}</code> not found.
<div className="flex items-center gap-2 p-8 text-slate-500">
<Spinner /> Loading…
</div>
)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

If the application definitions (defs) have finished loading but the specific application definition (ad) is not found (e.g., due to an unrecognized or invalid plural parameter), and there is no fetch error, the page will still render the loading spinner forever because !ad remains true.\n\nBy checking !defs instead of !ad in the loading guard, we can correctly show the spinner only while definitions are still loading. Once they have loaded, if ad is still undefined, we can render a clear "Unknown application type" error message instead of spinning indefinitely.

  // Check the fetch error before the loading guard: on a failed GET, isLoading
  // is false and instance stays undefined, so a !instance-first guard would
  // spin forever and never reach this branch.
  if (error) {
    return (
      <div className="p-8 text-red-600">
        Application <code>{name}</code> not found.
      </div>
    )
  }
  if (isLoading || !instance || !defs) {
    return (
      <div className="flex items-center gap-2 p-8 text-slate-500">
        <Spinner /> Loading…
      </div>
    )
  }
  if (!ad) {
    return (
      <div className="p-8 text-red-600">
        Unknown application type <code>{plural}</code>.
      </div>
    )
  }

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@packages/system/dashboard/images/console/apps/console/src/routes/detail/ApplicationDetailPage.tsx`:
- Around line 75-79: The error branch in ApplicationDetailPage currently treats
every GET failure as if the application is missing. Update the conditional
around the existing error handling to distinguish 404 responses from other
failures, and use the application not found copy only for that case. For other
errors, keep the same component but render a generic “could not be loaded”
message, using the existing ApplicationDetailPage logic and the error state from
the fetch.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 24600e59-ca40-48e0-8cd7-bdd429c4f56c

📥 Commits

Reviewing files that changed from the base of the PR and between 01addfc and 4838baf.

📒 Files selected for processing (4)
  • packages/system/dashboard/images/console/apps/console/src/routes/ApplicationListPage.test.tsx
  • packages/system/dashboard/images/console/apps/console/src/routes/ApplicationListPage.tsx
  • packages/system/dashboard/images/console/apps/console/src/routes/detail/ApplicationDetailPage.test.tsx
  • packages/system/dashboard/images/console/apps/console/src/routes/detail/ApplicationDetailPage.tsx

Comment on lines +75 to 79
if (error) {
return (
<div className="flex items-center gap-2 p-8 text-slate-500">
<Spinner /> Loading…
<div className="p-8 text-red-600">
Application <code>{name}</code> not found.
</div>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Don’t label every fetch failure as “not found.”

This branch now catches all GET errors, and the new test drives it with 403 Forbidden, so permission and transient backend failures will be shown as if the resource does not exist. Prefer 404-specific copy when you can detect it, or a generic “could not be loaded” message otherwise.

Suggested direction
   if (error) {
     return (
       <div className="p-8 text-red-600">
-        Application <code>{name}</code> not found.
+        Application <code>{name}</code> could not be loaded.
       </div>
     )
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (error) {
return (
<div className="flex items-center gap-2 p-8 text-slate-500">
<Spinner /> Loading…
<div className="p-8 text-red-600">
Application <code>{name}</code> not found.
</div>
if (error) {
return (
<div className="p-8 text-red-600">
Application <code>{name}</code> could not be loaded.
</div>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/system/dashboard/images/console/apps/console/src/routes/detail/ApplicationDetailPage.tsx`
around lines 75 - 79, The error branch in ApplicationDetailPage currently treats
every GET failure as if the application is missing. Update the conditional
around the existing error handling to distinguish 404 responses from other
failures, and use the application not found copy only for that case. For other
errors, keep the same component but render a generic “could not be loaded”
message, using the existing ApplicationDetailPage logic and the error state from
the fetch.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM — the guard reordering correctly makes the terminal states reachable, and both fixed paths have regression tests.

  • ApplicationDetailPage: moving if (error) ahead of the if (isLoading || !instance || !ad) guard is the right fix. On a failed initial GET the loading guard's !instance was true (isLoading=false, instance=undefined), so the error branch was dead and the page spun forever. I checked the data-retained-during-refetch case (the 5s refetchInterval): the old code already fell through to the same error branch there, so error-first changes only the genuinely-broken initial-failure path — no new regression. Both branches return before any instance/ad field access, so no nil-deref.
  • ApplicationListPage: dropping (!ad && plural) from the loading guard makes the "Unknown application type." branch reachable once definitions have loaded; the instance-list spinner remains separately handled, so no infinite spin after the guard.

Two non-blocking notes:

  1. The error branch renders "not found." for every GET failure including 403/5xx — pre-existing copy, just now reachable; a generic "could not be loaded" would be more accurate (follow-up).
  2. The detail page folds !ad into the loading guard rather than having an explicit unknown-type branch like the list page; in practice an unknown plural 404s and hits the error branch, so it terminates — the only residual is the orthogonal case where definitions fail to load but the instance GET succeeds. Out of scope here.

Both new states are covered by co-located vitest files, and the UI typecheck/test job is green.

@myasnikovdaniil
myasnikovdaniil merged commit 80e3fa9 into main Jun 29, 2026
15 checks passed
@myasnikovdaniil
myasnikovdaniil deleted the fix/dashboard-spinner-states branch June 29, 2026 09:02
@github-actions

Copy link
Copy Markdown

Backport failed for release-1.5, because it was unable to cherry-pick the commit(s).

Please cherry-pick the changes locally and resolve any conflicts.

git fetch origin release-1.5
git worktree add -d .worktree/backport-3098-to-release-1.5 origin/release-1.5
cd .worktree/backport-3098-to-release-1.5
git switch --create backport-3098-to-release-1.5
git cherry-pick -x 4838bafbc1fde8c505d58d403edc9f03138132ba

@myasnikovdaniil

Copy link
Copy Markdown
Contributor Author

Not backportable to release-1.5, recording the reason here so the next patch cut does not re-open the question.

The console is not in this branch's tree. release-1.5 consumes it as a prebuilt image, pinned in packages/system/dashboard/values.yaml as ghcr.io/cozystack/cozystack/cozystack-ui:v1.5.2@sha256:..., and the only thing under packages/system/dashboard/images/ there is token-proxy. The console was vendored into the monorepo at packages/system/dashboard/images/console after this branch was cut, by #2963.

So the .tsx paths this change touches do not exist on release-1.5 — a cherry-pick reports them as modify/delete conflicts rather than merging. Landing the fix on 1.5.x would mean building a new cozystack-ui image from the archived repository's release branch and re-pinning the digest here, which is a different piece of work from a backport and touches an archived repository.

Removing the backport label so cmd/backport-audit stops reporting it as outstanding for this line. Note the tool can only represent a deliberate drop when a backport PR exists to close — there was never one here, which is why the record lives in this comment.

@myasnikovdaniil myasnikovdaniil removed the kind/backport Categorizes issue or PR as requiring a backport to the current release line label Aug 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/dashboard Issues or PRs related to the dashboard / UI kind/bug Categorizes issue or PR as related to a bug size/L This PR changes 100-499 lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants