Skip to content

feat: add version history page - #2025

Merged
shuuji3 merged 30 commits into
npmx-dev:mainfrom
ShroXd:linkable-version-page
Mar 16, 2026
Merged

feat: add version history page#2025
shuuji3 merged 30 commits into
npmx-dev:mainfrom
ShroXd:linkable-version-page

Conversation

@ShroXd

@ShroXdShroXd commented Mar 10, 2026

Copy link
Copy Markdown
Contributor

🔗 Linked issue

Resolves#1846

🧭 Context

Like the original issues mentioned, it would be useful to have a full version history page with filtering functionality.

@WilcoSp is currently working on the changelog page feature in #1233. We've discussed this and agreed that both pages will be implemented and shipped separately, with the interaction between them designed based on real-world usage.

📚 Description

Since it's common for a package to have hundreds of versions, some package like typescript even have 3,700 versions. To provide good performance and user experience, I did some optimization and trade off during developing.

1. Two phase data fetching

Phase 1 (on page load) calls getVersions to fetch only a lightweight summary — version strings, dist-tags, and publish times. This is enough to immediately render the "Current Tags" section and all group headers without waiting for full metadata.
Phase 2 (lazy, on first group expand) calls fetchAllPackageVersions to retrieve full metadata (deprecated status, provenance). The result is cached in fullVersionMap as a shallowRef — once loaded it's reused across all subsequent group expansions, never re-fetched.

The tradeoff: a short one-time loading delay on first expand, in exchange for a significantly faster initial page load.

2. Virtual scrolling + SSR fallback

Since WindowVirtualizer is client-only, it's wrapped in <ClientOnly>, with a #fallback slot providing a static SSR substitute — just the first 20 group headers, no interactivity.

The SSR fallback iterates versionGroups directly, a flat list of group objects, each containing an array of versions
The client-side virtualizer consumes flatItems — a flattened array that interleaves group headers and their expanded version rows into a single indexed list.

This follows existing pattern in package list page.

3. Debounced version filter

Since the filtering work is done entirely on the frontend, it can put significant performance pressure on packages with a large number of versions, such as typescript. And to support virtual scrolling, version groups are flattened into a flatItems array — a single indexed list of group headers and version rows that WindowVirtualizer can render efficiently. Therefore, a debounce on the filter input is necessary to avoid triggering expensive recomputation on every keystroke.

Some further optimization options include chunked flatItems construction with requestIdleCallback and moving the filtering work into a Web Worker. However, both require non-trivial design and implementation to handle potential race conditions. We also plan to add changelog support to this page, which will introduce additional matching overhead. For now, the current implementation is "good enough" — once the full feature set is complete, we can do thorough performance profiling to find the best optimization approach.

4. Disable prefetching for package page of each version

Per-version links in the history list have prefetching disabled, as expanding a group could generate a lot of simultaneous prefetch requests and unnecessarily strain the network and significantly impact page performance. Although this makes package page navigation slightly slower, considering most user behavior on this page is browsing and filtering, the tradeoff is acceptable.

5. Some interesting cases

📸 Screenshots

ScenarioScreenshot
Light modeversion-history-page-light
Dark modeversion-history-page-dark
Expand version groupversion-history-expand-group
Filterversion-history-filtering

@vercel

vercelBot commented Mar 10, 2026

Copy link
Copy Markdown
Contributor

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

ProjectDeploymentActionsUpdated (UTC)
npmx.devReadyReadyPreview, CommentMar 16, 2026 4:03am
2 Skipped Deployments
ProjectDeploymentActionsUpdated (UTC)
docs.npmx.devIgnoredIgnoredPreviewMar 16, 2026 4:03am
npmx-lunariaIgnoredIgnoredMar 16, 2026 4:03am

Request Review

Comment threadapp/pages/package/[[org]]/[name]/versions.vue Outdated
@codecov

codecovBot commented Mar 13, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 87.02290% with 17 lines in your changes missing coverage. Please review.
✅ All tests successful. No failed tests found.

Files with missing linesPatch %Lines
app/pages/package/[[org]]/[name]/versions.vue89.34%13 Missing ⚠️
app/components/Package/Versions.vue71.42%2 Missing ⚠️
app/pages/package/[[org]]/[name].vue0.00%2 Missing ⚠️

📢 Thoughts on this report? Let us know!

@ShroXd
ShroXdforce-pushed the linkable-version-page branch from 35bdb74 to 03ed3c3CompareMarch 14, 2026 07:23
@github-actions

github-actionsBot commented Mar 14, 2026

Copy link
Copy Markdown

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

FileNote
i18n/locales/en.jsonSource changed, localizations will be marked as outdated.
Warnings reference
IconDescription
🔄️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.

@coderabbitai

coderabbitaiBot commented Mar 14, 2026

Copy link
Copy Markdown
Contributor

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: 165dcc0d-e3ba-4bfb-8bce-5b2a3d48fbd9

📥 Commits

Reviewing files that changed from the base of the PR and between bab8140 and 1466e8b.

📒 Files selected for processing (7)
  • app/components/Package/Versions.vue
  • app/pages/package/[[org]]/[name].vue
  • app/pages/package/[[org]]/[name]/versions.vue
  • i18n/locales/en.json
  • i18n/schema.json
  • test/nuxt/components/PackageVersions.spec.ts
  • test/nuxt/pages/PackageVersionsPage.spec.ts
🚧 Files skipped from review as they are similar to previous changes (5)
  • test/nuxt/components/PackageVersions.spec.ts
  • i18n/schema.json
  • app/components/Package/Versions.vue
  • app/pages/package/[[org]]/[name]/versions.vue
  • app/pages/package/[[org]]/[name].vue

📝 Walkthrough

Walkthrough

Introduces a new package versions page at app/pages/package/[[org]]/[name]/versions.vue providing grouped, virtualised version history with two-phase data loading and per‑group lazy metadata fetch. Updates the main package page to detect the versions route and gate template rendering while data loads. Modifies the Versions component header to add a View all versions link alongside the existing distribution action. Adds i18n keys and schema entries for the new UI strings. Adds/updates unit tests for the versions page and component.

Possibly related PRs

Suggested labels

front

Suggested reviewers

  • shuuji3
  • danielroe
🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check nameStatusExplanation
Description check✅ PassedThe pull request description is comprehensive and directly related to the changeset, describing the version history page implementation with detailed context about optimizations and tradeoffs.
Linked Issues check✅ PassedThe PR successfully implements the core requirement from #1846: a linkable, dedicated package version history page with comprehensive version listing and filtering, enabling users to share version information via URL.
Out of Scope Changes check✅ PassedAll changes are directly scoped to implementing the version history feature: new page component, header button integration, i18n translations, and comprehensive test coverage. No unrelated modifications detected.

✏️ 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
📝 Coding Plan
  • Generate coding plan for human review comments

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.

@coderabbitaicoderabbitaiBot 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: 2

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

377-387: Consider adding safety check for array access.

item.versions[0] is accessed without a guard. While the grouping logic ensures each group has at least one version, adding a safety check would satisfy strict type-safety guidelines and prevent potential runtime issues if the logic changes.

🔧 Suggested fix
 <span class="ms-auto flex items-center gap-3 shrink-0">
- <span class="text-xs text-fg-muted" dir="ltr">{{ item.versions[0] }}</span>+ <span v-if="item.versions[0]" class="text-xs text-fg-muted" dir="ltr">{{ item.versions[0] }}</span>
<DateTime
- v-if="getVersionTime(item.versions[0])"- :datetime="getVersionTime(item.versions[0])!"+ v-if="item.versions[0] && getVersionTime(item.versions[0])"+ :datetime="getVersionTime(item.versions[0])"
class="text-xs text-fg-subtle hidden sm:block"

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: 9477326f-b47e-43c8-81a2-07b1d0698711

📥 Commits

Reviewing files that changed from the base of the PR and between a170292 and bba4380.

📒 Files selected for processing (7)
  • app/components/Package/Versions.vue
  • app/pages/package/[[org]]/[name].vue
  • app/pages/package/[[org]]/[name]/versions.vue
  • i18n/locales/en.json
  • i18n/schema.json
  • test/nuxt/components/PackageVersions.spec.ts
  • test/nuxt/pages/PackageVersionsPage.spec.ts

Comment threadapp/components/Package/Versions.vue
Comment threadapp/pages/package/[[org]]/[name].vue
Comment on lines +346 to +348
expandedGroups.has(item.groupKey)
? $t('package.versions.collapse', { tag: item.label })
: $t('package.versions.expand', { tag: item.label })

@knowlerknowlerMar 14, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Don’t worry about updating these labels. Using aria-expanded is already sufficient for AT users to know what state the button is in and consequently what state it will change to if they activate it. Doing both can be confusing (e.g. screen reader might read Collapse 4.x versions, button expanded or Expand 4.x versions, button collapsed).

This goes for anywhere else in the PR that includes the words “expand” or “collapse” inside of the aria-label.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Thanks for pointing it out!

@coderabbitaicoderabbitaiBot 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.

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

175-176: TODO: Changelog side panel.

This TODO tracks a future enhancement for showing GitHub release notes or parsed CHANGELOG.md content per version.

Would you like me to open a GitHub issue to track this feature, or generate a skeleton implementation for the changelog side panel?


373-381: Minor inconsistency in defensive array access.

The main template accesses item.versions[0] directly (line 373), whilst the SSR fallback uses item.versions[0] ?? '' defensively (line 479). Although groups are guaranteed to have at least one version by construction, consider aligning the defensive coding style for consistency:

Suggested alignment
 <span class="ms-auto flex items-center gap-3 shrink-0">
- <span class="text-xs text-fg-muted" dir="ltr">{{ item.versions[0] }}</span>+ <span class="text-xs text-fg-muted" dir="ltr">{{ item.versions[0] ?? '' }}</span>
<DateTime
- v-if="getVersionTime(item.versions[0])"- :datetime="getVersionTime(item.versions[0])!"+ v-if="item.versions[0] && getVersionTime(item.versions[0])"+ :datetime="getVersionTime(item.versions[0])!"

Also applies to: 477-480


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c74b64d5-2ea0-41a6-b9c3-e9963f9f4458

📥 Commits

Reviewing files that changed from the base of the PR and between bba4380 and bab8140.

📒 Files selected for processing (2)
  • app/pages/package/[[org]]/[name].vue
  • app/pages/package/[[org]]/[name]/versions.vue
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/pages/package/[[org]]/[name].vue

@serhalpserhalp added the needs review This PR is waiting for a review from a maintainer label Mar 15, 2026
Comment threadtest/nuxt/components/PackageVersions.spec.ts Outdated

@graphierosgraphieros left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM
Thank you :)

@btea

btea commented Mar 16, 2026

Copy link
Copy Markdown
Contributor
image

Perhaps, like npmjs.org, we could also display the download count for each version.

@ShroXd

Copy link
Copy Markdown
ContributorAuthor

@btea Thanks for mentioning that — download counts would be helpful for users to find the best version. That said, I'd prefer to implement this in a separate PR.

The download data requires an additional network request, and there are a few details worth thinking through carefully: the data only covers the past 7 days, truly inactive versions will show no data, etc. There are also UX decisions around how to display and sort by download counts. We also plan to support changelog viewing on this page.

Shipping a good enough but not perfect version page first will give us a good baseline for future iteration.

@btea

btea commented Mar 16, 2026

Copy link
Copy Markdown
Contributor

That sounds amazing. I'm looking forward to this feature. 👍

@shuuji3shuuji3 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Great new feature in npmx 🙌 (packed with so many implementation details!)

I agree with all the points about the trade-offs. A Web Worker might be too much for now. I feel the current version is fast enough, and the loading indicator also helps.

@shuuji3
shuuji3 added this pull request to the merge queueMar 16, 2026
Merged via the queue into npmx-dev:main with commit deec5b5Mar 16, 2026
20 checks passed
@github-actionsgithub-actionsBot mentioned this pull request Mar 16, 2026
@ShroXd
ShroXd deleted the linkable-version-page branch March 16, 2026 12:46
@serhalpserhalp added this to the npm.js.com feature parity milestone Mar 23, 2026
@serhalpserhalp removed the needs review This PR is waiting for a review from a maintainer label Apr 12, 2026
Sign up for freeto 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.

Linkable package page to show list of versions

6 participants

@ShroXd@btea@shuuji3@knowler@graphieros@serhalp