Skip to content

fix: resolve multiple UI, UX, and a11y issues#2201

Closed
thealxlabs wants to merge 22 commits intonpmx-dev:mainfrom
thealxlabs:main
Closed

fix: resolve multiple UI, UX, and a11y issues#2201
thealxlabs wants to merge 22 commits intonpmx-dev:mainfrom
thealxlabs:main

Conversation

@thealxlabs
Copy link

🔗 Linked issue

🧭 Context

Fixed and improved a bunch of long-standing UI issues found while going through the issue tracker — layout bugs, broken links, mobile fixes, and a new dependents page.

19 issues closed across two sessions of work.

📚 Description

Went through the open issue list and tackled everything that was clearly broken or had an obvious fix. Here's what changed:

Layout & visual bugs

Package page

Code viewer

Search

Clipboard & mobile

Playgrounds / README

Other

@vercel
Copy link

vercel bot commented Mar 22, 2026

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

Project Deployment Actions Updated (UTC)
npmx.dev (canary) Ready Ready Preview, Comment Mar 22, 2026 6:29pm
2 Skipped Deployments
Project Deployment Actions Updated (UTC)
docs.npmx.dev Ignored Ignored Preview Mar 22, 2026 6:29pm
npmx-lunaria Ignored Ignored Mar 22, 2026 6:29pm

Request Review

@github-actions
Copy link

github-actions bot commented Mar 22, 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
i18n/locales/en.json Source changed, localizations will be marked as outdated.
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.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 22, 2026

📝 Walkthrough

Walkthrough

This pull request adds a new package dependents feature, improves code viewing capabilities, and refines UI components and styling across the application. Changes include a new dependents page and server endpoint for discovering packages that depend on a given package, word-wrap functionality for the code viewer with a UI toggle, npm alias parsing in the dependencies component, licence change detection on the package page, footer restructuring into a responsive grid layout, enhanced touch device support for copy buttons, and improved search pagination. Additionally, numerous i18n translation keys are added to support these new features.

Possibly related PRs

Suggested reviewers

  • danielroe
  • alexdln
  • 43081j
🚥 Pre-merge checks | ✅ 1
✅ Passed checks (1 passed)
Check name Status Explanation
Description check ✅ Passed The pull request description comprehensively details the changes made across multiple components and pages, directly corresponding to the file-level summaries and aligning with the stated objectives.

✏️ 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

CodeRabbit can use Trivy to scan for security misconfigurations and secrets in Infrastructure as Code files.

Add a .trivyignore file to your project to customize which findings Trivy reports.

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

🧹 Nitpick comments (4)
app/components/CollapsibleSection.vue (1)

109-109: Remove inline focus-visible utility from this button class.

Use the project’s global button focus-visible styling instead of per-element utility classes to keep focus behaviour consistent.

Suggested fix
-          class="text-start focus-visible:outline-accent/70 rounded"
+          class="text-start rounded"

Based on learnings: in this repo, button/select focus-visible styling is handled globally in app/assets/main.css, and inline focus-visible:outline-accent/70 should not be added on individual buttons.

app/components/Compare/FacetBarChart.vue (1)

348-354: Remove non-essential template comments in this block.

The comments at Line 348 and Line 353 describe obvious markup and can be dropped to keep the template lean.

♻️ Suggested cleanup
-      <!-- Title skeleton -->
       <div class="flex flex-col gap-2 justify-center items-center mb-4" aria-hidden="true">
         <SkeletonInline class="h-4 w-20 rounded" />
         <SkeletonInline class="h-3 w-36 rounded" />
       </div>
-      <!-- Bar skeletons with varying widths for visual realism -->
       <div class="flex flex-col gap-2 px-2" aria-hidden="true">

As per coding guidelines, "Add comments only to explain complex logic or non-obvious implementations".

app/components/Code/Viewer.vue (1)

159-172: Dead CSS selector: .word-wrap-active is never applied.

The class word-wrap-active is never added to any element in the template. Line 117 adds overflow classes directly, and line 119 adds word-wrap to .code-lines, not .code-content. The selector .code-content.word-wrap-active will never match.

The :has(.word-wrap) selector on line 160 and .code-lines.word-wrap on line 167 both work correctly, making lines 159-165 partially redundant.

🧹 Proposed fix to remove dead selector
-.code-content.word-wrap-active :deep(.line),
 .code-content:has(.word-wrap) :deep(.line) {
   white-space: pre-wrap;
   overflow-wrap: break-word;
   max-height: none;
   overflow: visible;
 }
app/pages/package/[[org]]/[name]/dependents.vue (1)

66-78: Consider using scrollMargin from definePageMeta instead of manual scroll.

The page defines scrollMargin: 200 in definePageMeta (line 4), but prevPage and nextPage use window.scrollTo({ top: 0 }). If the scrollMargin is intended to affect pagination scrolling, consider using it consistently. Otherwise, the current approach is fine for a simple scroll-to-top behaviour.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: cb889fd3-6312-4b3a-ad6e-fe5b5a537079

📥 Commits

Reviewing files that changed from the base of the PR and between 7f2fc1a and a7d3613.

📒 Files selected for processing (17)
  • app/components/AppFooter.vue
  • app/components/AppHeader.vue
  • app/components/Button/Base.vue
  • app/components/Code/Viewer.vue
  • app/components/CollapsibleSection.vue
  • app/components/Compare/FacetBarChart.vue
  • app/components/CopyToClipboardButton.vue
  • app/components/Package/Dependencies.vue
  • app/components/Package/Header.vue
  • app/pages/blog/index.vue
  • app/pages/package-code/[[org]]/[packageName]/v/[version]/[...filePath].vue
  • app/pages/package/[[org]]/[name].vue
  • app/pages/package/[[org]]/[name]/dependents.vue
  • app/pages/search.vue
  • i18n/locales/en.json
  • server/api/registry/dependents/[...pkg].get.ts
  • server/utils/readme.ts

Comment on lines +107 to +111
<button
type="button"
class="text-start focus-visible:outline-accent/70 rounded"
:aria-label="`${isOpen ? 'Collapse' : 'Expand'} ${title}`"
@click="toggle"
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

Expose disclosure semantics on the title toggle button.

This button also controls the collapsible content, but it currently omits the standard aria-expanded and aria-controls relationship/state on this control.

Suggested fix
         <button
           type="button"
           class="text-start focus-visible:outline-accent/70 rounded"
-          :aria-label="`${isOpen ? 'Collapse' : 'Expand'} ${title}`"
+          :aria-expanded="isOpen"
+          :aria-controls="contentId"
+          :aria-label="ariaLabel"
           `@click`="toggle"
         >
📝 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
<button
type="button"
class="text-start focus-visible:outline-accent/70 rounded"
:aria-label="`${isOpen ? 'Collapse' : 'Expand'} ${title}`"
@click="toggle"
<button
type="button"
class="text-start focus-visible:outline-accent/70 rounded"
:aria-expanded="isOpen"
:aria-controls="contentId"
:aria-label="ariaLabel"
`@click`="toggle"

Comment on lines +324 to +333
// Detect license changes between current version and latest
const licenseChanged = computed(() => {
const currentLicense = displayVersion.value?.license
const latestLicense = latestVersion.value?.license ?? pkg.value?.license
if (!currentLicense || !latestLicense) return false
// Normalize: compare string representations
const normalize = (l: unknown): string =>
typeof l === 'string' ? l : (l as { type?: string })?.type ?? ''
return normalize(currentLicense) !== normalize(latestLicense)
})
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 | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find SlimVersion type definition
ast-grep --pattern 'interface SlimVersion {
  $$$
}'

# Also check PackumentVersion for comparison
ast-grep --pattern 'interface PackumentVersion {
  $$$
}'

Repository: npmx-dev/npmx.dev

Length of output: 43


🏁 Script executed:

#!/bin/bash
# Search for SlimVersion type definition
rg "interface SlimVersion|type SlimVersion" -A 10

# Also search for PackumentVersion
rg "interface PackumentVersion|type PackumentVersion" -A 10

Repository: npmx-dev/npmx.dev

Length of output: 1674


🏁 Script executed:

#!/bin/bash
# Check the vue file for displayVersion and latestVersion declarations
head -350 app/pages/package/[[org]]/[name].vue | tail -50

Repository: npmx-dev/npmx.dev

Length of output: 1556


🏁 Script executed:

#!/bin/bash
# Search for where SlimVersion is imported or used in the codebase
rg "SlimVersion" -B 2 -A 2

Repository: npmx-dev/npmx.dev

Length of output: 4706


🏁 Script executed:

#!/bin/bash
# Read the vue file around lines 300-350 to see variable declarations and type annotations
sed -n '280,340p' app/pages/package/[[org]]/[name].vue

Repository: npmx-dev/npmx.dev

Length of output: 1893


🏁 Script executed:

#!/bin/bash
# Find where displayVersion and latestVersion are typed in the vue file
rg "displayVersion|latestVersion" app/pages/package/[[org]]/[name].vue -B 2 -A 2 | head -80

Repository: npmx-dev/npmx.dev

Length of output: 2561


🏁 Script executed:

#!/bin/bash
# Check the type of pkg in the vue file
rg "const pkg|interface.*Pkg|type.*Pkg" app/pages/package/[[org]]/[name].vue -A 3

Repository: npmx-dev/npmx.dev

Length of output: 178


🏁 Script executed:

#!/bin/bash
# Check what SlimPackumentVersion includes (to understand SlimVersion better)
rg "interface SlimPackumentVersion|type SlimPackumentVersion" shared/types/npm-registry.ts -A 15

Repository: npmx-dev/npmx.dev

Length of output: 580


🏁 Script executed:

#!/bin/bash
# Find where pkg is defined in the page component - look for computed or ref declarations
rg "const pkg|ref<|computed<" app/pages/package/[[org]]/[name].vue | head -20

Repository: npmx-dev/npmx.dev

Length of output: 175


🏁 Script executed:

#!/bin/bash
# Get more context around pkg definition
sed -n '1,100p' app/pages/package/[[org]]/[name].vue | grep -A 5 "const pkg"

Repository: npmx-dev/npmx.dev

Length of output: 43


🏁 Script executed:

#!/bin/bash
# Check what PackageResponse or the pkg's interface actually is
rg "interface PackageResponse|type PackageResponse" shared/types -A 20

Repository: npmx-dev/npmx.dev

Length of output: 43


🏁 Script executed:

#!/bin/bash
# Search for useAsyncData or useFetch that assigns pkg
rg "useAsyncData|useFetch" app/pages/package/[[org]]/[name].vue -A 3 | head -40

Repository: npmx-dev/npmx.dev

Length of output: 43


🏁 Script executed:

#!/bin/bash
# Search for where pkg.value.requestedVersion is defined - check the API route
rg "requestedVersion" shared/types -B 5 -A 5

Repository: npmx-dev/npmx.dev

Length of output: 853


🏁 Script executed:

#!/bin/bash
# Look at the entire start of the script section to find pkg definition
head -200 app/pages/package/[[org]]/[name].vue | grep -A 20 "const pkg\|definePageMeta\|useAsyncData"

Repository: npmx-dev/npmx.dev

Length of output: 43


🏁 Script executed:

#!/bin/bash
# Find the complete Packument type definition
rg "export type Packument|export interface Packument" shared/types/npm-registry.ts -A 30

Repository: npmx-dev/npmx.dev

Length of output: 1468


🏁 Script executed:

#!/bin/bash
# Check if there's a response type for the package page endpoint
rg "requestedVersion" app/ -B 10 -A 2 | head -50

Repository: npmx-dev/npmx.dev

Length of output: 3265


🏁 Script executed:

#!/bin/bash
# Verify PackumentVersionWithoutAttestations has license property
rg "interface PackumentVersionWithoutAttestations|type PackumentVersionWithoutAttestations" shared/types/npm-registry.ts -A 30

Repository: npmx-dev/npmx.dev

Length of output: 43


🏁 Script executed:

#!/bin/bash
# Check the actual computed return type by looking at type inference
# See if there are any explicit type annotations on these computeds
sed -n '200,350p' app/pages/package/[[org]]/[name].vue | grep -B 3 -A 8 "const displayVersion\|const latestVersion"

Repository: npmx-dev/npmx.dev

Length of output: 892


🏁 Script executed:

#!/bin/bash
# Run TypeScript to check for actual compilation errors on this file
# First, find tsconfig
find . -name "tsconfig.json" -o -name "tsconfig.*.json" | head -5

Repository: npmx-dev/npmx.dev

Length of output: 117


🏁 Script executed:

#!/bin/bash
# Let's check if there are TypeScript errors in this file
# Look at the package.json to understand the build setup
cat package.json | grep -A 20 "scripts"

Repository: npmx-dev/npmx.dev

Length of output: 3031


🏁 Script executed:

#!/bin/bash
# Check if the vue file has explicit type annotations for these computed properties
sed -n '200,250p' app/pages/package/[[org]]/[name].vue | cat -n

Repository: npmx-dev/npmx.dev

Length of output: 2508


🏁 Script executed:

#!/bin/bash
# Most importantly, let's trace the exact types:
# displayVersion comes from pkg.value?.requestedVersion
# requestedVersion is SlimPackumentVersion | null
# latestVersion comes from pkg.value.versions[latestTag]
# versions is Record<string, SlimVersion>
# So let's verify if SlimPackumentVersion has license

rg "interface SlimPackumentVersion|type SlimPackumentVersion" shared/types/npm-registry.ts -B 5 -A 3

Repository: npmx-dev/npmx.dev

Length of output: 297


🏁 Script executed:

#!/bin/bash
# Check what PackumentVersionWithoutAttestations includes
rg "PackumentVersionWithoutAttestations" shared/types/npm-registry.ts -B 10 -A 15 | head -80

Repository: npmx-dev/npmx.dev

Length of output: 1525


🏁 Script executed:

#!/bin/bash
# Confirm that PackumentVersionWithoutAttestations has license by checking `@npm/types`
# First let's see what the actual node_modules has
find node_modules/@npm/types -name "*.d.ts" 2>/dev/null | head -5

Repository: npmx-dev/npmx.dev

Length of output: 43


🏁 Script executed:

#!/bin/bash
# Let's check if there's a way to run the type checker on this specific file
# Look for the vue-tsc command that's in the test:types script
which vue-tsc || echo "vue-tsc not in PATH, checking package.json"

Repository: npmx-dev/npmx.dev

Length of output: 164


🏁 Script executed:

#!/bin/bash
# Let me check the exact structure of what's available on these types
# Create a simple test to understand the type chain
cat > /tmp/test-types.ts << 'EOF'
import type { SlimVersion, SlimPackumentVersion, Packument } from './shared/types/npm-registry'

// Check what properties are available
type SVProps = keyof SlimVersion
type SPVProps = keyof SlimPackumentVersion

// This would error if license doesn't exist
// type TestSV = SlimVersion['license']
// type TestSPV = SlimPackumentVersion['license']
EOF
cat /tmp/test-types.ts

Repository: npmx-dev/npmx.dev

Length of output: 408


🏁 Script executed:

#!/bin/bash
# Let's trace PackumentVersionWithoutAttestations from `@npm/types`
# It's imported from `@npm/types` package, let's check what properties it should have
# according to npm's type definition

# Search for any local overrides or extensions
rg "PackumentVersionWithoutAttestations" shared/types/ -A 5 -B 5

Repository: npmx-dev/npmx.dev

Length of output: 1713


🏁 Script executed:

#!/bin/bash
# Let me trace the actual type of pkg to confirm it has license
# usePackage composable should return Packument-like type
rg "export.*usePackage|function usePackage" app/composables -A 20 | head -50

Repository: npmx-dev/npmx.dev

Length of output: 3394


🏁 Script executed:

#!/bin/bash
# Check what usePackage actually returns
rg "usePackage" app/composables/npm/usePackage.ts -A 30 | head -80

Repository: npmx-dev/npmx.dev

Length of output: 789


🏁 Script executed:

#!/bin/bash
# Now let's check transformPackument to see how it converts types
rg "transformPackument" app/composables/npm/usePackage.ts -B 5 -A 50

Repository: npmx-dev/npmx.dev

Length of output: 2719


🏁 Script executed:

#!/bin/bash
# Search for transformPackument definition
rg "export.*transformPackument|function transformPackument" app/ -A 30

Repository: npmx-dev/npmx.dev

Length of output: 2336


🏁 Script executed:

#!/bin/bash
# Check SlimPackument type definition
rg "interface SlimPackument|type SlimPackument" shared/types/npm-registry.ts -B 5 -A 30

Repository: npmx-dev/npmx.dev

Length of output: 2031


🏁 Script executed:

#!/bin/bash
# Let me check the full transformPackument to see what SlimPackument structure is
rg "SlimPackument" app/composables/npm/usePackage.ts -B 10 -A 10

Repository: npmx-dev/npmx.dev

Length of output: 1742


🏁 Script executed:

#!/bin/bash
# Check if SlimPackument is defined in types
rg "SlimPackument" shared/types -B 2 -A 20

Repository: npmx-dev/npmx.dev

Length of output: 4082


🏁 Script executed:

#!/bin/bash
# Let's check if there are any actual TypeScript errors reported when building
# Look for the actual `@npm/types` to understand what PackumentVersion includes
rg "license" node_modules/@npm/types/index.d.ts 2>/dev/null | head -20

Repository: npmx-dev/npmx.dev

Length of output: 43


🏁 Script executed:

#!/bin/bash
# Simpler approach: check if `@npm/types` is installed and what it exports
ls -la node_modules/@npm/types/ 2>/dev/null | head -20

Repository: npmx-dev/npmx.dev

Length of output: 43


🏁 Script executed:

#!/bin/bash
# Let's look for any test files that might show the expected types or behaviors
rg "displayVersion.*license|latestVersion.*license" test/ app/ --type ts --type vue

Repository: npmx-dev/npmx.dev

Length of output: 88


🏁 Script executed:

#!/bin/bash
# Check if there are any existing TypeScript errors or type definitions for this component
# Look at the vue file more carefully for any explicit type annotations
sed -n '1,50p' app/pages/package/[[org]]/[name].vue

Repository: npmx-dev/npmx.dev

Length of output: 1500


🏁 Script executed:

#!/bin/bash
# Check if there are any TypeScript errors reported for this file
# Look for any type checking configuration
cat tsconfig.json | grep -A 10 '"compilerOptions"'

Repository: npmx-dev/npmx.dev

Length of output: 43


🏁 Script executed:

#!/bin/bash
# Search for any existing TypeScript errors or suppressions near the licenseChanged computed
rg "licenseChanged" app/pages/package/[[org]]/[name].vue -B 5 -A 15

Repository: npmx-dev/npmx.dev

Length of output: 2043


🏁 Script executed:

#!/bin/bash
# Let me check the template more carefully to see the actual usage of these properties
# and confirm this is actually being accessed
rg "latestVersion\?\.license|displayVersion\?\.license" app/pages/package/[[org]]/[name].vue

Repository: npmx-dev/npmx.dev

Length of output: 298


Type error: license property does not exist on SlimVersion.

The code accesses latestVersion.value?.license (both in the computed property at line 331 and in the template), but latestVersion is typed as SlimVersion | null. SlimVersion explicitly picks only 'version', 'deprecated', and 'tags' from SlimPackumentVersion to reduce payload size, so accessing .license violates type safety and will cause a TypeScript compilation failure.

The normalize function can also be extracted outside the computed since it has no dependency on the parent scope.

🧰 Tools
🪛 GitHub Check: 💪 Type check

[failure] 327-327:
Property 'license' does not exist on type 'SlimVersion'.

🪛 GitHub Check: 🤖 Autofix code

[warning] 330-330: eslint-plugin-unicorn(consistent-function-scoping)
Function normalize does not capture any variables from its parent scope

Comment on lines +612 to 625
<dd class="font-mono text-sm text-fg flex items-center gap-2 flex-wrap">
<LicenseDisplay v-if="displayVersion?.license ?? pkg.license" :license="displayVersion?.license ?? pkg.license" />
<span v-else>{{ $t('package.license.none') }}</span>
<TooltipApp
v-if="licenseChanged"
:text="$t('package.license.changed', { latest: latestVersion?.license ?? pkg.license })"
position="bottom"
>
<span class="inline-flex items-center gap-1 px-1.5 py-0.5 text-2xs font-sans rounded bg-amber-500/15 text-amber-700 dark:text-amber-400 border border-amber-500/30 cursor-help">
<span class="i-lucide:triangle-alert w-3 h-3" aria-hidden="true" />
{{ $t('package.license.changed_badge') }}
</span>
</TooltipApp>
</dd>
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

Type mismatch on :license prop.

The LicenseDisplay component's :license prop receives displayVersion?.license ?? pkg.license, but:

  1. displayVersion is SlimVersion | null which may not have license
  2. The expression evaluates to string | undefined, but the prop likely expects string

This aligns with the type errors flagged at lines 327 and 617.

🔧 Proposed fix with type narrowing
 <dd class="font-mono text-sm text-fg flex items-center gap-2 flex-wrap">
-  <LicenseDisplay v-if="displayVersion?.license ?? pkg.license" :license="displayVersion?.license ?? pkg.license" />
+  <LicenseDisplay 
+    v-if="(displayVersion as PackumentVersion | null)?.license ?? pkg.license" 
+    :license="((displayVersion as PackumentVersion | null)?.license ?? pkg.license) as string" 
+  />
   <span v-else>{{ $t('package.license.none') }}</span>
   <TooltipApp
     v-if="licenseChanged"
-    :text="$t('package.license.changed', { latest: latestVersion?.license ?? pkg.license })"
+    :text="$t('package.license.changed', { latest: (latestVersion as PackumentVersion | null)?.license ?? pkg.license })"
     position="bottom"
   >

Alternatively, consider adding license to the SlimVersion type if it's always present in practice.

📝 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
<dd class="font-mono text-sm text-fg flex items-center gap-2 flex-wrap">
<LicenseDisplay v-if="displayVersion?.license ?? pkg.license" :license="displayVersion?.license ?? pkg.license" />
<span v-else>{{ $t('package.license.none') }}</span>
<TooltipApp
v-if="licenseChanged"
:text="$t('package.license.changed', { latest: latestVersion?.license ?? pkg.license })"
position="bottom"
>
<span class="inline-flex items-center gap-1 px-1.5 py-0.5 text-2xs font-sans rounded bg-amber-500/15 text-amber-700 dark:text-amber-400 border border-amber-500/30 cursor-help">
<span class="i-lucide:triangle-alert w-3 h-3" aria-hidden="true" />
{{ $t('package.license.changed_badge') }}
</span>
</TooltipApp>
</dd>
<dd class="font-mono text-sm text-fg flex items-center gap-2 flex-wrap">
<LicenseDisplay
v-if="(displayVersion as PackumentVersion | null)?.license ?? pkg.license"
:license="((displayVersion as PackumentVersion | null)?.license ?? pkg.license) as string"
/>
<span v-else>{{ $t('package.license.none') }}</span>
<TooltipApp
v-if="licenseChanged"
:text="$t('package.license.changed', { latest: (latestVersion as PackumentVersion | null)?.license ?? pkg.license })"
position="bottom"
>
<span class="inline-flex items-center gap-1 px-1.5 py-0.5 text-2xs font-sans rounded bg-amber-500/15 text-amber-700 dark:text-amber-400 border border-amber-500/30 cursor-help">
<span class="i-lucide:triangle-alert w-3 h-3" aria-hidden="true" />
{{ $t('package.license.changed_badge') }}
</span>
</TooltipApp>
</dd>
🧰 Tools
🪛 GitHub Check: 💪 Type check

[failure] 617-617:
Property 'license' does not exist on type 'SlimVersion'.


[failure] 613-613:
Type 'string | undefined' is not assignable to type 'string'.

Comment on lines +90 to +97
<PackageHeader
:pkg="pkg ?? null"
:resolved-version="resolvedVersion"
:display-version="displayVersion"
:latest-version="latestVersion"
:version-url-pattern="versionUrlPattern"
page="dependents"
/>
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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check displayVersion computation in main package page
rg -n "displayVersion.*computed" app/pages/package/

Repository: npmx-dev/npmx.dev

Length of output: 267


🏁 Script executed:

# First, view the displayVersion computation in dependents.vue (lines 20-30)
head -n 30 app/pages/package/[[org]]/[name]/dependents.vue | tail -n 15

Repository: npmx-dev/npmx.dev

Length of output: 513


🏁 Script executed:

# Find and examine PackageHeader component definition
fd -name "PackageHeader*" -t f

Repository: npmx-dev/npmx.dev

Length of output: 291


🏁 Script executed:

# Check the type definitions for SlimVersion and PackumentVersion
rg -n "type (SlimVersion|PackumentVersion)" --type ts --type tsx --type vue

Repository: npmx-dev/npmx.dev

Length of output: 88


🏁 Script executed:

# View the PackageHeader prop definition to see what type is expected
fd -name "PackageHeader*" -t f | head -5 | xargs -I {} sh -c 'echo "=== {} ===" && head -n 100 {}'

Repository: npmx-dev/npmx.dev

Length of output: 291


🏁 Script executed:

# Find PackageHeader component
find . -name "*PackageHeader*" -type f

Repository: npmx-dev/npmx.dev

Length of output: 104


🏁 Script executed:

# Search for PackageHeader component definition in common locations
rg "export.*PackageHeader" --type vue

Repository: npmx-dev/npmx.dev

Length of output: 88


🏁 Script executed:

# Find where PackageHeader is defined by searching for its definition
rg "defineProps.*display-version" --type vue

Repository: npmx-dev/npmx.dev

Length of output: 88


🏁 Script executed:

# Check the types in the pkg object
rg "type.*Package" --type ts | head -20

Repository: npmx-dev/npmx.dev

Length of output: 2011


🏁 Script executed:

# Search for PackageHeader component more broadly
rg "PackageHeader" --max-count 5

Repository: npmx-dev/npmx.dev

Length of output: 1036


🏁 Script executed:

# Find the component file
find . -type f -name "*.vue" | xargs rg "PackageHeader" | head -10

Repository: npmx-dev/npmx.dev

Length of output: 785


🏁 Script executed:

# Look for PackageHeader in components
rg "export.*default" | grep -i "packageheader"

Repository: npmx-dev/npmx.dev

Length of output: 43


🏁 Script executed:

# Search in components directory
find ./app/components -type f | xargs rg "PackageHeader" 2>/dev/null | head -5

Repository: npmx-dev/npmx.dev

Length of output: 239


🏁 Script executed:

# View the PackageHeader component (Package/Header.vue)
cat app/components/Package/Header.vue

Repository: npmx-dev/npmx.dev

Length of output: 12283


🏁 Script executed:

# Also check what types are imported in dependents.vue
head -n 50 app/pages/package/[[org]]/[name]/dependents.vue

Repository: npmx-dev/npmx.dev

Length of output: 1228


🏁 Script executed:

# Find type definitions for SlimVersion, PackumentVersion, and SlimPackument
rg "export type (SlimVersion|PackumentVersion|SlimPackument)" --max-count 10

Repository: npmx-dev/npmx.dev

Length of output: 266


🏁 Script executed:

# Search for type definitions without 'export'
rg "type SlimVersion|type PackumentVersion|type SlimPackument" --max-count 10

Repository: npmx-dev/npmx.dev

Length of output: 266


🏁 Script executed:

# Look for interface definitions
rg "interface (SlimVersion|PackumentVersion|SlimPackument)" --max-count 10

Repository: npmx-dev/npmx.dev

Length of output: 231


🏁 Script executed:

# Check what the pkg type has for versions property
rg "versions.*:" | grep -i "slim\|packument" | head -10

Repository: npmx-dev/npmx.dev

Length of output: 830


🏁 Script executed:

# View the full type definitions for SlimPackument and the versions property
sed -n '/export interface SlimPackument/,/^}/p' shared/types/npm-registry.ts

Repository: npmx-dev/npmx.dev

Length of output: 1472


🏁 Script executed:

# View the full Packument type definition
sed -n '/export interface Packument/,/^}/p' shared/types/npm-registry.ts

Repository: npmx-dev/npmx.dev

Length of output: 312


🏁 Script executed:

# Also check SlimPackumentVersion vs PackumentVersion
sed -n '/export type SlimPackumentVersion/,/^}/p' shared/types/npm-registry.ts

Repository: npmx-dev/npmx.dev

Length of output: 156


🏁 Script executed:

# Verify SlimVersion structure to confirm it's a subset
rg -A 5 "export type SlimVersion" shared/types/npm-registry.ts

Repository: npmx-dev/npmx.dev

Length of output: 218


🏁 Script executed:

# Check if there are any existing usages of SlimVersion with PackageHeader elsewhere
rg "displayVersion.*SlimVersion" --max-count 5

Repository: npmx-dev/npmx.dev

Length of output: 43


Type mismatch: displayVersion is SlimVersion but PackageHeader expects PackumentVersion.

In dependents.vue, the computation pkg.value.versions[resolvedVersion.value] ?? null returns SlimVersion, which is only a subset of SlimPackumentVersion. Since SlimPackumentVersion extends PackumentVersion, but SlimVersion is just a pick of certain properties (version, deprecated, tags, plus optional hasProvenance and trustLevel), it cannot satisfy the :display-version prop type.

The main package page avoids this by using pkg.value?.requestedVersion ?? null, which returns SlimPackumentVersion | null and is assignable to PackumentVersion. Consider using the same approach for consistency.

🧰 Tools
🪛 GitHub Check: 💪 Type check

[failure] 93-93:
Type 'SlimVersion | null' is not assignable to type 'PackumentVersion | null | undefined'.

@@ -0,0 +1,87 @@
import { CACHE_MAX_AGE_FIVE_MINUTES, NPM_REGISTRY } from '#shared/utils/constants'
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

Remove unused import NPM_REGISTRY.

Static analysis correctly identifies that NPM_REGISTRY is imported but never used. The endpoint uses NPM_SEARCH_BASE instead.

🔧 Proposed fix
-import { CACHE_MAX_AGE_FIVE_MINUTES, NPM_REGISTRY } from '#shared/utils/constants'
+import { CACHE_MAX_AGE_FIVE_MINUTES } from '#shared/utils/constants'
📝 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
import { CACHE_MAX_AGE_FIVE_MINUTES, NPM_REGISTRY } from '#shared/utils/constants'
import { CACHE_MAX_AGE_FIVE_MINUTES } from '#shared/utils/constants'
🧰 Tools
🪛 GitHub Check: 💪 Type check

[failure] 1-1:
'NPM_REGISTRY' is declared but its value is never read.

🪛 GitHub Check: 🤖 Autofix code

[failure] 1-1: eslint(no-unused-vars)
Identifier 'NPM_REGISTRY' is imported but never used.

@codecov
Copy link

codecov bot commented Mar 22, 2026

Codecov Report

❌ Patch coverage is 72.91667% with 13 lines 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].vue 11.11% 7 Missing and 1 partial ⚠️
app/components/Compare/FacetBarChart.vue 66.66% 2 Missing ⚠️
app/components/AppHeader.vue 50.00% 1 Missing ⚠️
app/components/Package/Dependencies.vue 90.90% 1 Missing ⚠️
app/components/Package/Header.vue 87.50% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

Copy link
Contributor

@graphieros graphieros left a comment

Choose a reason for hiding this comment

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

Thank you for your PR!

Please consider proposing one PR per issue.
Also, new features should be covered by tests.

We recommend you check out our contribution guide.

@thealxlabs thealxlabs changed the title fix/feat: 19 issues resolved — UI fixes, new dependents page, word wrap, aliased deps, and more fix: resolve multiple UI, UX, and a11y issues Mar 22, 2026
@thealxlabs thealxlabs requested a review from graphieros March 22, 2026 18:03
Copy link
Contributor

@graphieros graphieros left a comment

Choose a reason for hiding this comment

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

As stated previously, this PR requires to be split per issue to be considered.
Kindly close this PR and propose one PR per issue, this will help maintainers to review your contributions. 🙏

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.

2 participants