Skip to content

fix(ui): prevent scroll jump during pagination#1645

Open
marlonwq wants to merge 8 commits intonpmx-dev:mainfrom
marlonwq:fix/issue-1640-scroll-jump
Open

fix(ui): prevent scroll jump during pagination#1645
marlonwq wants to merge 8 commits intonpmx-dev:mainfrom
marlonwq:fix/issue-1640-scroll-jump

Conversation

@marlonwq
Copy link

🔗 Linked issue

Resolves #1640

🧭 Context

Fixed the bug where scrolling jumps back to the top when the infinite pagination updates the URL.

📚 Description

  • Replaced router.replace with window.history.replaceState to update the page number without triggering Nuxt's default scroll-to-top behavior.
  • Prevented currentPage from resetting if only the pagination changes, not the search text.
  • Used v-show and CSS (overflow-anchor: none) to keep the results list in the DOM and prevent the page from collapsing while loading.

@vercel
Copy link

vercel bot commented Feb 25, 2026

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

Project Deployment Actions Updated (UTC)
npmx.dev Ready Ready Preview, Comment Feb 25, 2026 9:55pm
2 Skipped Deployments
Project Deployment Actions Updated (UTC)
docs.npmx.dev Ignored Ignored Preview Feb 25, 2026 9:55pm
npmx-lunaria Ignored Ignored Feb 25, 2026 9:55pm

Request Review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 25, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Replaces router-based URL updates with direct History API calls (window.history.replaceState) and debounced URL updates that are cancelled on unmount. Page query param is set only when page > 1. Introduces client-side displayResults and visibleResults to drive rendering and total/effectiveTotal logic. Tightens focusable-element selection to visible elements and updates focus/keyboard handling. Many conditional renderings shifted to rely on displayResults length (several v-if changed to v-show). Adds a search-page class, a results-layout wrapper and scoped styling. No public API signatures were changed.

Suggested reviewers

  • danielroe
  • OrbisK
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The package.json changes (author format, metadata fields) are unrelated to the scroll jump fix and appear to be scope creep not mentioned in the linked issue. Remove package.json metadata changes or address them in a separate PR focused on package manifest updates rather than mixing them with the scroll jump fix.
✅ Passed checks (2 passed)
Check name Status Explanation
Description check ✅ Passed The PR description clearly relates to the changeset, explaining the scroll jump bug fix and detailing the implementation approach with router.replace replacement and DOM persistence.
Linked Issues check ✅ Passed The changes address issue #1640 by replacing router.replace with window.history.replaceState to prevent scroll jumps, preventing currentPage reset on pagination-only changes, and using v-show with CSS to keep results in DOM.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

@marlonwq marlonwq changed the title fix(search): prevent scroll jump during pagination fix(ui): prevent scroll jump during pagination Feb 25, 2026
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.

🧹 Nitpick comments (3)
app/pages/search.vue (3)

24-34: Nuxt route object will be stale after replaceState.

Since window.history.replaceState bypasses the Nuxt router, route.query.page will not reflect the updated page number during the session. This is currently safe because initialPage (line 53–56) is only consumed inside onMounted for reload/restore. However, if any future code reactively reads route.query.page mid-session, it will get the stale value.

Consider adding a short comment near the initialPage computed (or here) noting this intentional divergence, so future contributors don't inadvertently rely on route.query.page for the live page number.


553-556: Redundant condition: displayResults.length === 0 is already inside showSearching.

The showSearching computed (line 265) already gates on displayResults.value.length === 0. Repeating the check in the template is harmless but adds noise and could drift if showSearching is later updated.

Proposed simplification
         <LoadingSpinner
-          v-if="showSearching && displayResults.length === 0"
+          v-if="showSearching"
           :text="$t('search.searching')"
         />

747-755: min-height: 100vh may produce excessive whitespace with few results.

When only a handful of results are returned (e.g. a highly specific query with 2–3 matches), .results-layout will still occupy at least the full viewport, leaving a noticeable empty gap below the content. Consider tying the minimum height to loading state or using a smaller fallback (e.g. min-height: 50vh) so that the layout reservation is enough to prevent collapse without creating a visual artefact.

Also, overflow-anchor: none on the entire .search-page disables all browser scroll-anchoring, not just during pagination. If the page later gains above-the-fold dynamic content (e.g. banners), the user could experience unexpected scroll shifts. A narrower scope (e.g. on .results-layout only) would limit the blast radius.

Proposed refinement
 .search-page {
-  overflow-anchor: none;
 }

 .results-layout {
-  min-height: 100vh;
+  min-height: 50vh;
+  overflow-anchor: none;
 }

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 26d967e and f916120.

📒 Files selected for processing (1)
  • app/pages/search.vue

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


ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f916120 and 307b733.

📒 Files selected for processing (2)
  • app/pages/search.vue
  • package.json

@codecov
Copy link

codecov bot commented Feb 25, 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!

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 307b733 and 32efa44.

📒 Files selected for processing (1)
  • app/pages/search.vue

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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
app/pages/search.vue (2)

449-452: ⚠️ Potential issue | 🟡 Minor

Remove debug console.log before merging.

This log fires on every displayResults update when pendingEnterQuery is set, producing noise in the browser console for all end users.

🐛 Proposed fix
-  // eslint-disable-next-line no-console
-  console.log('[search] watcher fired', {
-    pending: pendingEnterQuery.value,
-    firstResult: firstResult?.package.name,
-  })

597-603: ⚠️ Potential issue | 🟡 Minor

Remove focus-visible:outline-accent/70 from both claim <button> elements.

The project applies button:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 4px; } globally in app/assets/main.css. Adding per-element focus-visible:outline-accent/70 on <button> tags is redundant and deviates from the project convention.

♻️ Proposed fix (applies to both buttons)
-  class="shrink-0 px-4 py-2 font-mono text-sm text-bg bg-fg rounded-md motion-safe:transition-colors motion-safe:duration-200 hover:bg-fg/90 focus-visible:outline-accent/70"
+  class="shrink-0 px-4 py-2 font-mono text-sm text-bg bg-fg rounded-md motion-safe:transition-colors motion-safe:duration-200 hover:bg-fg/90"
-  class="px-4 py-2 font-mono text-sm text-bg bg-fg rounded-md transition-colors duration-200 hover:bg-fg/90 focus-visible:outline-accent/70"
+  class="px-4 py-2 font-mono text-sm text-bg bg-fg rounded-md transition-colors duration-200 hover:bg-fg/90"

Based on learnings: "In the npmx.dev project, focus-visible styling for buttons and selects is applied globally via main.css… individual buttons or selects in Vue components should not rely on inline focus-visible utility classes like focus-visible:outline-accent/70."

Also applies to: 703-710

🧹 Nitpick comments (1)
app/pages/search.vue (1)

40-62: Consider consolidating both onMounted hooks into one.

Two separate onMounted calls for closely related initialisation logic (page state + interaction flag) can be merged to reduce cognitive overhead.

♻️ Proposed consolidation
-onMounted(() => {
-  // Small delay to let view transition complete
-  setTimeout(() => {
-    hasInteracted.value = true
-  }, 300)
-})
-
 // Initialize current page from URL on mount
 onMounted(() => {
+  // Small delay to let view transition complete
+  setTimeout(() => {
+    hasInteracted.value = true
+  }, 300)
+
   if (initialPage.value > 1) {
     currentPage.value = initialPage.value
   }
 })

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 32efa44 and 41daab9.

📒 Files selected for processing (1)
  • app/pages/search.vue

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


ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 41daab9 and 402ccb0.

📒 Files selected for processing (1)
  • package.json

package.json Outdated
Comment on lines 167 to 181
"packageManager": "pnpm@10.30.1",
"description": "> A fast, modern browser for the npm registry.",
"main": "index.js",
"directories": {
"doc": "docs",
"test": "test"
},
"repository": {
"type": "git",
"url": "git+https://github.com/marlonwq/npmx.dev.git"
},
"bugs": {
"url": "https://github.com/marlonwq/npmx.dev/issues"
},
"homepage": "https://github.com/marlonwq/npmx.dev#readme"
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

These metadata additions are unrelated to the PR objective and appear to be npm init artefacts.

The PR is scoped to fixing a scroll-jump regression during pagination. All changes from line 167 onward (description, main, directories, repository, bugs, homepage) were not part of the stated fix and look like side-effects of running npm init on the fork. Please remove or split them into a separate, intentional PR.

package.json Outdated
},
"packageManager": "pnpm@10.30.1"
"packageManager": "pnpm@10.30.1",
"description": "> A fast, modern browser for the npm registry.",
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 | 🟡 Minor

Strip the Markdown blockquote prefix from description.

The value "> A fast, modern browser for the npm registry." contains a literal > (Markdown blockquote syntax). It will appear verbatim in registry UIs, IDE tooltips, and any tooling that reads this field.

🐛 Proposed fix
-  "description": "> A fast, modern browser for the npm registry.",
+  "description": "A fast, modern browser for the npm registry.",
📝 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
"description": "> A fast, modern browser for the npm registry.",
"description": "A fast, modern browser for the npm registry.",

package.json Outdated
Comment on lines 169 to 173
"main": "index.js",
"directories": {
"doc": "docs",
"test": "test"
},
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 | 🟡 Minor

main and directories are not applicable to a private Nuxt application.

"main": "index.js" implies a Node.js library entry point; no such file exists in a Nuxt SSR project and the package is already marked "private": true. directories is a legacy npm artefact. Both fields are likely unintentional npm init output and should be removed.

🐛 Proposed fix
-  "main": "index.js",
-  "directories": {
-    "doc": "docs",
-    "test": "test"
-  },
📝 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
"main": "index.js",
"directories": {
"doc": "docs",
"test": "test"
},

package.json Outdated
Comment on lines 174 to 181
"repository": {
"type": "git",
"url": "git+https://github.com/marlonwq/npmx.dev.git"
},
"bugs": {
"url": "https://github.com/marlonwq/npmx.dev/issues"
},
"homepage": "https://github.com/marlonwq/npmx.dev#readme"
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

Fork-scoped URLs must not be merged into the upstream repository.

repository.url, bugs.url, and homepage all point to marlonwq/npmx.dev — the contributor's personal fork — rather than the canonical upstream npmx-dev/npmx.dev. If merged, these would direct tooling, registry links, and issue trackers to the wrong repository.

🐛 Proposed fix
-  "repository": {
-    "type": "git",
-    "url": "git+https://github.com/marlonwq/npmx.dev.git"
-  },
-  "bugs": {
-    "url": "https://github.com/marlonwq/npmx.dev/issues"
-  },
-  "homepage": "https://github.com/marlonwq/npmx.dev#readme"
+  "repository": {
+    "type": "git",
+    "url": "git+https://github.com/npmx-dev/npmx.dev.git"
+  },
+  "bugs": {
+    "url": "https://github.com/npmx-dev/npmx.dev/issues"
+  },
+  "homepage": "https://github.com/npmx-dev/npmx.dev#readme"
📝 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
"repository": {
"type": "git",
"url": "git+https://github.com/marlonwq/npmx.dev.git"
},
"bugs": {
"url": "https://github.com/marlonwq/npmx.dev/issues"
},
"homepage": "https://github.com/marlonwq/npmx.dev#readme"
"repository": {
"type": "git",
"url": "git+https://github.com/npmx-dev/npmx.dev.git"
},
"bugs": {
"url": "https://github.com/npmx-dev/npmx.dev/issues"
},
"homepage": "https://github.com/npmx-dev/npmx.dev#readme"

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.

Scrolling doesn't work reliably and jumps back to top

1 participant