Skip to content

Comments

feat: show recently viewed packages/orgs/users on homepage#1594

Open
serhalp wants to merge 3 commits intomainfrom
feat/recent-viewed-packages
Open

feat: show recently viewed packages/orgs/users on homepage#1594
serhalp wants to merge 3 commits intomainfrom
feat/recent-viewed-packages

Conversation

@serhalp
Copy link
Member

@serhalp serhalp commented Feb 23, 2026

🔗 Linked issue

N/A but see https://discord.com/channels/1464542801676206113/1464542802565140506/1475323380226854953

🧭 Context

This replaces the hardcoded list of quick links to frameworks, which provides limited value and isn't scalable.

📚 Description

Instead, we now track up to 5 most recently viewed entities (packages, orgs, users) in localStorage and show them on the homepage. This allows users to quickly navigate back to previously viewed pages. If no recently viewed items are known, the section is hidden.

npmx.recently.viewed.demo.mp4

Note: There's potential for this to eventually be powered by atproto and tied to a user (and therefore to sync across devices), but we can tackle that later (among other things, there are data privacy implications).

@vercel
Copy link

vercel bot commented Feb 23, 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 23, 2026 3:54am
2 Skipped Deployments
Project Deployment Actions Updated (UTC)
docs.npmx.dev Ignored Ignored Preview Feb 23, 2026 3:54am
npmx-lunaria Ignored Ignored Feb 23, 2026 3:54am

Request Review

@github-actions
Copy link

github-actions bot commented Feb 23, 2026

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
lunaria/files/ar-EG.json Localization changed, will be marked as complete.
lunaria/files/az-AZ.json Localization changed, will be marked as complete.
lunaria/files/bg-BG.json Localization changed, will be marked as complete.
lunaria/files/bn-IN.json Localization changed, will be marked as complete.
lunaria/files/cs-CZ.json Localization changed, will be marked as complete.
lunaria/files/de-DE.json Localization changed, will be marked as complete.
lunaria/files/en-GB.json Localization changed, will be marked as complete.
lunaria/files/en-US.json Source changed, localizations will be marked as outdated.
lunaria/files/es-419.json Localization changed, will be marked as complete.
lunaria/files/es-ES.json Localization changed, will be marked as complete.
lunaria/files/fr-FR.json Localization changed, will be marked as complete.
lunaria/files/hi-IN.json Localization changed, will be marked as complete.
lunaria/files/hu-HU.json Localization changed, will be marked as complete.
lunaria/files/id-ID.json Localization changed, will be marked as complete.
lunaria/files/it-IT.json Localization changed, will be marked as complete.
lunaria/files/ja-JP.json Localization changed, will be marked as complete.
lunaria/files/mr-IN.json Localization changed, will be marked as complete.
lunaria/files/nb-NO.json Localization changed, will be marked as complete.
lunaria/files/ne-NP.json Localization changed, will be marked as complete.
lunaria/files/pl-PL.json Localization changed, will be marked as complete.
lunaria/files/pt-BR.json Localization changed, will be marked as complete.
lunaria/files/ru-RU.json Localization changed, will be marked as complete.
lunaria/files/ta-IN.json Localization changed, will be marked as complete.
lunaria/files/te-IN.json Localization changed, will be marked as complete.
lunaria/files/uk-UA.json Localization changed, will be marked as complete.
lunaria/files/zh-CN.json Localization changed, will be marked as complete.
lunaria/files/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.

@codecov
Copy link

codecov bot commented Feb 23, 2026

Codecov Report

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

Files with missing lines Patch % Lines
app/composables/useRecentlyViewed.ts 17.64% 11 Missing and 3 partials ⚠️
app/pages/index.vue 18.75% 12 Missing and 1 partial ⚠️
app/pages/package/[[org]]/[name].vue 0.00% 4 Missing and 2 partials ⚠️

📢 Thoughts on this report? Let us know!

This replaces the hardcoded list of quick links to frameworks.

Instead, we now track up to 5 most recently viewed entities (packages, orgs, users) in localStorage
and show them on the homepage. This allows users to quickly navigate back to previously viewed
pages. If no recently viewed items are known, the section is hidden.

There's potential for this to eventually be powered by atproto and tied to a user (and therefore to
sync across devices), but we can tackle that later (among other things, there are data privacy
implications).
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 23, 2026

📝 Walkthrough

Walkthrough

This PR introduces a recently viewed tracking feature that records when users view packages, organisations, and users. It adds a new composable for managing a localStorage-backed recently viewed list with a maximum of 5 items, integrates tracking calls into package, organisation, and user detail pages, replaces the popular packages navigation section on the home page with a recently viewed section, updates locale files across multiple languages, and includes comprehensive unit and end-to-end tests.

Possibly related PRs

Based on the provided information, no PRs with strong code-level connections are identified. The retrieved PR (#972) touches the same Italian locale file but lacks functional code-level integration with the recently viewed feature.

Suggested labels

front

Suggested reviewers

  • danielroe
🚥 Pre-merge checks | ✅ 1
✅ Passed checks (1 passed)
Check name Status Explanation
Description check ✅ Passed The pull request description clearly relates to the changeset, explaining the feature to replace hardcoded framework links with a recently viewed section tracking up to 5 entities in localStorage.

✏️ 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 feat/recent-viewed-packages

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

Caution

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

⚠️ Outside diff range comments (1)
i18n/locales/en.json (1)

1046-1052: ⚠️ Potential issue | 🟡 Minor

Privacy policy no longer accurately describes localStorage usage.

The local_storage.p1 copy describes localStorage as being for "display preferences" (theme, settings), and bold2 states it "contains no personal data nor is it used to track you". The new npmx-recent key now stores browsing history (visited package/org/user names and Unix timestamps), which is a meaningfully different category of data.

While the data never leaves the device, the policy description is now factually inaccurate. Consider updating p1 and bold2 to reflect that localStorage also stores recently-visited navigation history — even if only the user's own device is involved — before or alongside this feature shipping.

🧹 Nitpick comments (1)
test/unit/app/composables/use-recently-viewed.spec.ts (1)

86-96: Consider adding afterEach(() => vi.restoreAllMocks()) to clean up the Date.now spy.

mockReturnValueOnce is consumed on the first call, so subsequent tests see the real Date.now in practice. However, the spy handle remains registered on Date.now after this test. Adding explicit cleanup prevents any latent cross-test contamination if the suite grows.

♻️ Suggested addition
+import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
-import { beforeEach, describe, expect, it, vi } from 'vitest'

 describe('useRecentlyViewed', () => {
   beforeEach(() => {
     storageRef.value = []
   })
+
+  afterEach(() => {
+    vi.restoreAllMocks()
+  })

Comment on lines +34 to +41
export function trackRecentView(item: Omit<RecentItem, 'viewedAt'>) {
if (import.meta.server) return
const items = getRecentRef()
const filtered = items.value.filter(
existing => !(existing.type === item.type && existing.name === item.name),
)
filtered.unshift({ ...item, viewedAt: Date.now() })
items.value = filtered.slice(0, MAX_RECENT_ITEMS)
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

Guard against corrupted localStorage payloads before filtering.

npmx-recent is user‑controlled; if it contains a non‑array value (or an older schema), .filter() will throw and break tracking. Normalise to a safe array before mutation.

Suggested fix
 export function trackRecentView(item: Omit<RecentItem, 'viewedAt'>) {
   if (import.meta.server) return
   const items = getRecentRef()
-  const filtered = items.value.filter(
+  const current = Array.isArray(items.value) ? items.value : []
+  if (current !== items.value) items.value = current
+  const filtered = current.filter(
     existing => !(existing.type === item.type && existing.name === item.name),
   )
   filtered.unshift({ ...item, viewedAt: Date.now() })
   items.value = filtered.slice(0, MAX_RECENT_ITEMS)
 }
📝 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
export function trackRecentView(item: Omit<RecentItem, 'viewedAt'>) {
if (import.meta.server) return
const items = getRecentRef()
const filtered = items.value.filter(
existing => !(existing.type === item.type && existing.name === item.name),
)
filtered.unshift({ ...item, viewedAt: Date.now() })
items.value = filtered.slice(0, MAX_RECENT_ITEMS)
export function trackRecentView(item: Omit<RecentItem, 'viewedAt'>) {
if (import.meta.server) return
const items = getRecentRef()
const current = Array.isArray(items.value) ? items.value : []
if (current !== items.value) items.value = current
const filtered = current.filter(
existing => !(existing.type === item.type && existing.name === item.name),
)
filtered.unshift({ ...item, viewedAt: Date.now() })
items.value = filtered.slice(0, MAX_RECENT_ITEMS)
}

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.

1 participant