Skip to content

feat: add semver range filter and element title support#2176

Open
ShroXd wants to merge 9 commits intonpmx-dev:mainfrom
ShroXd:feat/semver-range-filter
Open

feat: add semver range filter and element title support#2176
ShroXd wants to merge 9 commits intonpmx-dev:mainfrom
ShroXd:feat/semver-range-filter

Conversation

@ShroXd
Copy link
Contributor

@ShroXd ShroXd commented Mar 21, 2026

🔗 Linked issue

n/a

🧭 Context

Following work of #2105 , was mentioned in the PR review comment.
cc: @mbtools

📚 Description

Add description for semver range filtering and validator. The normal error message would make the page shake during input, so an inline icon is used to indicate the error state instead.

Screen.Recording.2026-03-21.at.17.02.21.mov

Also added titles to elements.

Element Screenshot
Version QQ20260321-170411
Time QQ20260321-170358

@vercel
Copy link

vercel bot commented Mar 21, 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 22, 2026 4:29am
2 Skipped Deployments
Project Deployment Actions Updated (UTC)
docs.npmx.dev Ignored Ignored Preview Mar 22, 2026 4:29am
npmx-lunaria Ignored Ignored Mar 22, 2026 4:29am

Request Review

@codecov
Copy link

codecov bot commented Mar 21, 2026

Codecov Report

❌ Patch coverage is 94.44444% with 1 line in your changes missing coverage. Please review.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
app/pages/package/[[org]]/[name]/versions.vue 94.44% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

@ShroXd ShroXd marked this pull request as ready for review March 21, 2026 09:18
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 21, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 30a6a0f6-3167-45d7-ab5f-994d2520a7db

📥 Commits

Reviewing files that changed from the base of the PR and between b69ef86 and fa536fa.

📒 Files selected for processing (5)
  • i18n/locales/en.json
  • i18n/locales/pt-BR.json
  • i18n/locales/zh-CN.json
  • i18n/locales/zh-TW.json
  • i18n/schema.json
💤 Files with no reviewable changes (5)
  • i18n/locales/pt-BR.json
  • i18n/locales/en.json
  • i18n/locales/zh-TW.json
  • i18n/schema.json
  • i18n/locales/zh-CN.json

📝 Walkthrough

Walkthrough

Replaces per-group lazy full-metadata fetch with an immediate watcher on versionSummary that calls ensureFullDataLoaded() after Phase 1 completes. Removes loadingGroup and conditional fetch logic from toggleGroup, making group toggles purely local. Adds deprecatedGroupKeys derived from fullVersionMap and versionGroups. Introduces semver-range validation (isInvalidRange) and related UI/ARIA attributes, autocomplete="off", invalid styling and an error tooltip. Updates i18n keys for the filter, broadens deprecated badges and titles, refactors date/provenance layout, and adjusts group header icon logic during filtering.

Possibly related PRs

Suggested labels

front, a11y

Suggested reviewers

  • serhalp
  • danielroe
  • alexdln
🚥 Pre-merge checks | ✅ 1
✅ Passed checks (1 passed)
Check name Status Explanation
Description check ✅ Passed The pull request description is related to the changeset, explaining the addition of semver range filtering, validation, inline error indication, and element titles.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

Tip

You can customize the tone of the review comments and chat replies.

Configure the tone_instructions setting to customize the tone of the review comments and chat replies. For example, you can set the tone to Act like a strict teacher, Act like a pirate and more.

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 (1)
app/pages/package/[[org]]/[name]/versions.vue (1)

521-521: ⚠️ Potential issue | 🟡 Minor

SSR fallback lacks title attribute.

The main template (line 409) adds :title="item.versions[0]" to the version span, but the SSR fallback omits it. For consistency:

-<span class="text-xs text-fg-muted" dir="ltr">{{ item.versions[0] }}</span>
+<span class="text-xs text-fg-muted" :title="item.versions[0]" dir="ltr">{{ item.versions[0] }}</span>
🧹 Nitpick comments (1)
app/pages/package/[[org]]/[name]/versions.vue (1)

409-411: Consider defensive access for array index.

Whilst filteredGroups filters out empty groups (line 149), the coding guidelines recommend always checking when accessing array values by index. Consider using optional chaining for defensive coding:

-<span class="text-xs text-fg-muted" :title="item.versions[0]" dir="ltr">{{
-  item.versions[0]
-}}</span>
+<span class="text-xs text-fg-muted" :title="item.versions[0] ?? ''" dir="ltr">{{
+  item.versions[0] ?? ''
+}}</span>

As per coding guidelines: "ensure you always check when accessing an array value by index".


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 2eef2dd3-3a98-4cd5-aa58-00da50acb7a7

📥 Commits

Reviewing files that changed from the base of the PR and between 8cd4074 and 2eb4b8a.

📒 Files selected for processing (1)
  • app/pages/package/[[org]]/[name]/versions.vue

@mbtools
Copy link

mbtools commented Mar 21, 2026

Works well. Try ^6.0.0 || ^7.0.0 || ^8.0.0-0 for vite package. The input box should be a bit wider and the placeholder "Filter by semver".

After filtering, this icons keeps spinning. Maybe replace with a fixed "funnel" icon indicating that the list was filtered.

image

My final wish:

I'm testing with verdaccio package which has lots of versions. A small improvement would be to mark the majors as deprecated, for example, 5.x, 4.x, and below, in case all versions underneath it are deprecated.

image

Similarly, if a tag points to a deprecated version, we could add a "deprecated" badge in the tag list:

image image

@serhalp serhalp added the needs review This PR is waiting for a review from a maintainer label Mar 21, 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 (2)
test/nuxt/pages/PackageVersionsPage.spec.ts (2)

144-153: Assert fetch sequencing, not just fetch count.

At the moment, this only proves “called once”. It does not verify the “after phase 1 completes” part from the test title. Add an explicit order assertion to lock in the intended behaviour.

Suggested test hardening
 it('fetches full metadata automatically after phase 1 completes, exactly once', async () => {
-  mockGetVersions.mockResolvedValue(makeVersionData(['2.0.0', '1.0.0'], { latest: '2.0.0' }))
-  mockFetchAllPackageVersions.mockResolvedValue([
+  const callOrder: string[] = []
+  mockGetVersions.mockImplementation(async () => {
+    callOrder.push('phase1')
+    return makeVersionData(['2.0.0', '1.0.0'], { latest: '2.0.0' })
+  })
+  mockFetchAllPackageVersions.mockImplementation(async () => {
+    callOrder.push('phase2')
+    return [
       { version: '2.0.0', time: '2024-01-15T00:00:00.000Z', hasProvenance: false },
       { version: '1.0.0', time: '2024-01-10T00:00:00.000Z', hasProvenance: false },
-  ])
+    ]
+  })

   await mountPage()

-  await vi.waitFor(() => expect(mockFetchAllPackageVersions).toHaveBeenCalledTimes(1))
+  await vi.waitFor(() => {
+    expect(mockGetVersions).toHaveBeenCalledTimes(1)
+    expect(mockFetchAllPackageVersions).toHaveBeenCalledTimes(1)
+    expect(callOrder).toEqual(['phase1', 'phase2'])
+  })
 })

170-170: Prefer a more specific selector for the semver input.

input[autocomplete="off"] can become ambiguous if another input with the same attribute is added. A dedicated attribute (e.g. title, aria-label, or data-testid) would make this test less brittle.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: dabc6865-b9da-431e-ba05-e5a802c1c56e

📥 Commits

Reviewing files that changed from the base of the PR and between 2eb4b8a and 3ea5f92.

📒 Files selected for processing (2)
  • app/pages/package/[[org]]/[name]/versions.vue
  • test/nuxt/pages/PackageVersionsPage.spec.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/pages/package/[[org]]/[name]/versions.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: 1

🧹 Nitpick comments (1)
app/pages/package/[[org]]/[name]/versions.vue (1)

431-433: Consider guarding array access for strict type-safety.

item.versions[0] is accessed without an explicit bounds check. While filteredGroups filters out empty groups (ensuring this is safe in practice), TypeScript cannot guarantee the array is non-empty at compile time.

♻️ Optional: Add fallback for stricter safety
-<span class="text-xs text-fg-muted" :title="item.versions[0]" dir="ltr">{{
-  item.versions[0]
-}}</span>
+<span class="text-xs text-fg-muted" :title="item.versions[0] ?? ''" dir="ltr">{{
+  item.versions[0] ?? ''
+}}</span>

As per coding guidelines: "Ensure you write strictly type-safe code, for example by ensuring you always check when accessing an array value by index".


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b81a0f4b-33c4-4f2b-8faf-daabd75ae3e2

📥 Commits

Reviewing files that changed from the base of the PR and between 3ea5f92 and b69ef86.

📒 Files selected for processing (2)
  • app/pages/package/[[org]]/[name]/versions.vue
  • test/nuxt/pages/PackageVersionsPage.spec.ts
✅ Files skipped from review due to trivial changes (1)
  • test/nuxt/pages/PackageVersionsPage.spec.ts

Comment on lines +121 to +129
watch(
versionSummary,
async summary => {
if (summary) {
await ensureFullDataLoaded()
} finally {
loadingGroup.value = null
}
}
}
},
{ immediate: true },
)
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

Unhandled promise rejection in async watch callback.

If fetchAllPackageVersions throws (e.g. network error), the error will be silently swallowed or cause an unhandled rejection. Consider adding error handling to improve resilience.

🛡️ Proposed fix to handle errors gracefully
 watch(
   versionSummary,
   async summary => {
     if (summary) {
-      await ensureFullDataLoaded()
+      try {
+        await ensureFullDataLoaded()
+      } catch (err) {
+        console.error('[versions] Failed to load full metadata:', err)
+      }
     }
   },
   { immediate: true },
 )

As per coding guidelines: "Use error handling patterns consistently".

📝 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
watch(
versionSummary,
async summary => {
if (summary) {
await ensureFullDataLoaded()
} finally {
loadingGroup.value = null
}
}
}
},
{ immediate: true },
)
watch(
versionSummary,
async summary => {
if (summary) {
try {
await ensureFullDataLoaded()
} catch (err) {
console.error('[versions] Failed to load full metadata:', err)
}
}
},
{ immediate: true },
)

@ShroXd
Copy link
Contributor Author

ShroXd commented Mar 22, 2026

Hi @mbtools, thanks for the feedback! I have updated my PR.

Because of the design of fetching, the information about whether a version is deprecated can only be known after phase 2 is finished. Therefore I changed the behavior of the network request, making phase 2 fire automatically after phase 1 is finished.

This puts additional rendering pressure on the main thread. The performance profiling shows a significant number of runIfDirty calls, which is caused by the long reactive chain of fullVersionMap and derived data. Some potential optimizations include batch updating of fullVersionMap or even using markRaw to skip the reactive process for fullVersionMap. However, the page is still fast, so I would avoid early optimization and address it if we detect any performance issues on this page later.

@github-actions
Copy link

Lunaria Status Overview

🌕 This pull request will trigger status changes.

Learn more

By default, every PR changing files present in the Lunaria configuration's files property will be considered and trigger status changes accordingly.

You can change this by adding one of the keywords present in the ignoreKeywords property in your Lunaria configuration file in the PR's title (ignoring all files) or by including a tracker directive in the merged commit's description.

Tracked Files

File Note
i18n/locales/en.json Source changed, localizations will be marked as outdated.
i18n/locales/pt-BR.json Localization changed, will be marked as complete. 🔄️
i18n/locales/zh-CN.json Localization changed, will be marked as complete. 🔄️
i18n/locales/zh-TW.json Localization changed, will be marked as complete. 🔄️
Warnings reference
Icon Description
🔄️ The source for this localization has been updated since the creation of this pull request, make sure all changes in the source have been applied.

@mbtools
Copy link

mbtools commented Mar 22, 2026

I tried on my phone and it's super fast even with over a thousand versions in a package like @nx/js.

LGTM 👍

PS wishlist: two toggle buttons to hide deprecated and pre-release versions. I would hide them by default since these versions are only relevant for maintainers or early adopters ie a fraction of the users.

@ShroXd
Copy link
Contributor Author

ShroXd commented Mar 22, 2026

@mbtools Happy to hear you like it!

The idea of a toggle button to hide some versions is great! I would move it to a separate PR because although the implementation is simple, some business logic needs to be discussed, like:

  1. Should we allow users to configure it in the settings page?
  2. The scope of hiding versions. Some packages like astro have a lot of tagged versions.
  3. Although some versions are deprecated, they are still used by many projects, like Vue 2. So the default value should be considered carefully.

I can write an issue later. Once we gather enough thoughts from the community, I can finish the remaining things.

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

Labels

needs review This PR is waiting for a review from a maintainer

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants