Skip to content

fix(ui): fix 2 loading glitches on profile page#1910

Open
serhalp wants to merge 2 commits intomainfrom
serhalp/fix-profile-loading-glitch
Open

fix(ui): fix 2 loading glitches on profile page#1910
serhalp wants to merge 2 commits intomainfrom
serhalp/fix-profile-loading-glitch

Conversation

@serhalp
Copy link
Member

@serhalp serhalp commented Mar 4, 2026

🔗 Linked issue

Closes #1781

🧭 Context

See the issue. There were 2 visual glitches on the profile page.

📚 Description

fix: show skeleton instead of 0 in like cards while loading

Like cards showed the "zero likes" default before the client-side fetch resolved, causing a visible 0 -> X jump. This changes it to show a skeleton loader with a neutral-ish heart icon during the pending state.

fix: wait for auth before showing profile invite section

The invite empty state briefly flashed on page load because the auth session (server: false) hadn't resolved yet, making the "not own profile" state flash briefly.

npmx.fix.1910.demo.mp4

Tip

We could probably avoid the slight layout shift when the number of likes comes in, but I don't see an obvious solution since the number has an unpredictable string length and the heart icon is to the left of it... We can fix that later.

serhalp added 2 commits March 3, 2026 20:21
Like cards showed totalLikes: 0 default before the client-side fetch
resolved, causing a visible 0 -> X jump. Now shows a pulse skeleton
and neutral heart icon during the pending state.
The invite empty state briefly flashed on page load because the auth
session (server: false) hadn't resolved yet, making the "not own profile"
check pass incorrectly.

Closes #1781
@vercel
Copy link

vercel bot commented Mar 4, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
npmx.dev Ready Ready Preview, Comment Mar 4, 2026 1:27am
2 Skipped Deployments
Project Deployment Actions Updated (UTC)
docs.npmx.dev Ignored Ignored Mar 4, 2026 1:27am
npmx-lunaria Ignored Ignored Mar 4, 2026 1:27am

Request Review

@serhalp serhalp marked this pull request as ready for review March 4, 2026 01:37
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 4, 2026

📝 Walkthrough

Walkthrough

This pull request introduces pending state handling across the package likes and user profile components to prevent UI glitches during data loading. The LikeCard component now captures and displays a loading state from the likes fetch, showing a pulsing placeholder instead of flickering values. The profile page guards the invite section visibility with a pending flag from the user fetch. Accompanying test coverage validates loading state rendering for both the likes card and profile invite section, addressing the empty state glitch reported in issue #1781.

Possibly related PRs

Suggested reviewers

  • danielroe
  • alexdln
  • trueberryless
🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description check ✅ Passed The PR description clearly explains two visual glitches being fixed: skeleton loading in like cards and delayed invite section display until auth resolves.
Linked Issues check ✅ Passed Changes address both objectives from #1781: (1) Like card loading state with skeleton and neutral icon prevents 0→X glitch, (2) auth pending check prevents invite section flash.
Out of Scope Changes check ✅ Passed All changes are directly scoped to fixing the two loading glitches mentioned in #1781; no unrelated functionality was altered.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch serhalp/fix-profile-loading-glitch

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 and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2


ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 036dfc2 and a6bd244.

📒 Files selected for processing (4)
  • app/components/Package/LikeCard.vue
  • app/pages/profile/[identity]/index.vue
  • test/nuxt/components/PackageLikeCard.spec.ts
  • test/nuxt/components/ProfileInviteSection.spec.ts

Comment on lines +20 to 23
const { data: likesData, status: likesStatus } = useFetch(() => `/api/social/likes/${name.value}`, {
default: () => ({ totalLikes: 0, userHasLiked: false }),
server: false,
})
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

rg -n "likesStatus|isLikeActionPending|likeAction|:disabled|aria-busy" app/components/Package/LikeCard.vue -C3

Repository: npmx-dev/npmx.dev

Length of output: 2014


Prevent user interactions whilst initial likes data is still pending.

The UI displays a pending indicator but does not block the button. Users can click during the loading window, and likeAction will use default fallback values to compute the state change, risking incorrect optimistic updates based on uninitialised data.

The likeAction function only guards isLikeActionPending (line 33) but does not check whether likesStatus is pending. Additionally, the button lacks both :disabled and :aria-busy attributes to reflect the pending state.

Suggested fix
 const likeAction = async () => {
   if (user.value?.handle == null) {
     authModal.open()
     return
   }

-  if (isLikeActionPending.value) return
+  if (likesStatus.value === 'pending' || isLikeActionPending.value) return
             <button
               `@click.prevent`="likeAction"
               type="button"
+              :disabled="likesStatus === 'pending' || isLikeActionPending"
+              :aria-busy="likesStatus === 'pending'"
               :title="
                 likesData?.userHasLiked ? $t('package.likes.unlike') : $t('package.likes.like')
               "

Comment on lines +48 to +65
it('shows a loading skeleton instead of zero while like data is pending', async () => {
wrapper = await mountLikeCard('https://npmx.dev/package/vue')

const button = wrapper.find('button')
expect(button.text()).not.toContain('0')

const skeleton = button.find('.animate-pulse')
expect(skeleton.exists()).toBe(true)
})

it('shows a neutral heart icon while like data is pending', async () => {
wrapper = await mountLikeCard('https://npmx.dev/package/vue')

const icon = wrapper.find('button span[aria-hidden]')
expect(icon.classes()).toContain('i-lucide-heart')
expect(icon.classes()).not.toContain('i-lucide-heart-plus')
expect(icon.classes()).not.toContain('i-lucide-heart-minus')
})
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Read the full spec file to understand setup and mocking
cat -n test/nuxt/components/PackageLikeCard.spec.ts

# Also search for any mock or endpoint setup patterns
rg -n "registerEndpoint|mockNuxtImport|useFakeTimers|setTimeout|new Promise|beforeEach|vi\." test/nuxt/components/PackageLikeCard.spec.ts || echo "No timing control patterns found"

Repository: npmx-dev/npmx.dev

Length of output: 2780


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find and read the LikeCard component
find . -name "LikeCard.vue" -type f | head -5

Repository: npmx-dev/npmx.dev

Length of output: 98


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Read the LikeCard component
cat -n ~/components/Package/LikeCard.vue 2>/dev/null || cat -n src/components/Package/LikeCard.vue 2>/dev/null || find . -path "*components/Package/LikeCard.vue" -exec cat -n {} \;

Repository: npmx-dev/npmx.dev

Length of output: 4465


Control fetch timing to stabilise pending-state assertions.

The tests at lines 48–65 check pending-state UI (skeleton, neutral heart icon) immediately after mount. However, useFetch() status in the component is not mocked, so its resolution is uncontrolled. If the endpoint responds before assertions run, the pending state disappears and tests fail.

Introduce a deliberate delay in the endpoint response using registerEndpoint():

Stabilisation example
+import { registerEndpoint } from '@nuxt/test-utils/runtime'
+
+registerEndpoint('/api/social/likes/vue', async () => {
+  await new Promise(resolve => setTimeout(resolve, 50))
+  return { totalLikes: 12, userHasLiked: false }
+})

@codecov
Copy link

codecov bot commented Mar 4, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ All tests successful. No failed tests found.

📢 Thoughts on this report? Let us know!

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Profile page: empty state glitches on Atmosphere account loading

1 participant