Skip to content

feat: implement nuxt.care#2189

Open
Flo0806 wants to merge 7 commits intonuxt:mainfrom
Flo0806:feat/nuxt-care
Open

feat: implement nuxt.care#2189
Flo0806 wants to merge 7 commits intonuxt:mainfrom
Flo0806:feat/nuxt-care

Conversation

@Flo0806
Copy link
Member

@Flo0806 Flo0806 commented Feb 27, 2026

📚 Description

It would be great if nuxt.care were integrated to display the health status of modules.

image image

@Flo0806 Flo0806 requested a review from atinux as a code owner February 27, 2026 08:27
@vercel
Copy link
Contributor

vercel bot commented Feb 27, 2026

@Flo0806 is attempting to deploy a commit to the Nuxt Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link

coderabbitai bot commented Feb 27, 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

Adds nuxt.care health integration: introduces a new ModuleHealth type and optional health on Module in shared types. Adds fetchBulkModuleHealth in server/utils/module.ts to bulk-fetch and cache nuxt.care data. Server endpoints server/api/v1/modules/index.get.ts and server/api/v1/modules/[name].get.ts call the bulk health fetch and attach module.health to responses. Client components app/components/module/ModuleItem.vue and app/pages/modules/[slug].vue conditionally render a nuxt.care score/status badge when module.health exists. Narrows an admin fetch call to useFetch<FeedbackItem[]>(). README badge for nuxt.care added.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: implement nuxt.care' directly and clearly describes the main change—integrating nuxt.care health status display for modules throughout the codebase.
Description check ✅ Passed The description is directly related to the changeset, explaining the motivation for integrating nuxt.care and showing visual examples of the desired outcome.

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

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


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

@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: 3

🧹 Nitpick comments (1)
server/utils/module.ts (1)

125-127: Avoid repeated linear lookup when mapping health items.

Line 126 performs uncached.find(...) for every API item, which makes this section O(n²) for large module lists.

Suggested refactor
-    for (const item of data) {
-      const module = uncached.find(m => m.npm === item.npm)
+    const moduleByNpm = new Map(uncached.map(m => [m.npm, m]))
+    for (const item of data) {
+      const module = moduleByNpm.get(item.npm)
       if (!module) continue
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/utils/module.ts` around lines 125 - 127, The loop over data uses
uncached.find(m => m.npm === item.npm) inside for (const item of data), causing
O(n²) behavior; fix by precomputing a Map keyed by module.npm (e.g., const
uncachedByNpm = new Map(uncached.map(m => [m.npm, m]))) and then inside the loop
replace uncached.find(...) with a constant-time lookup
uncachedByNpm.get(item.npm), preserving the existing skip logic (if (!module)
continue) and any subsequent usage of module.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/components/module/ModuleItem.vue`:
- Around line 157-161: The NuxtLink that opens an external Nuxt.care URL
(`<NuxtLink ... :to="`https://nuxt.care/?search=npm:${module.npm}`"
target="_blank">`) is missing a rel attribute; update that NuxtLink to include
rel="noopener noreferrer" to prevent window.opener attacks and ensure proper
referrer behavior when using target="_blank".

In `@app/pages/modules/`[slug].vue:
- Around line 158-162: The NuxtLink that opens an external URL in a new tab (the
NuxtLink component rendering :to="`https://nuxt.care/?search=npm:${module.npm}`"
with target="_blank") must include rel="noopener noreferrer" to prevent
tabnabbing; update the NuxtLink element to add rel="noopener noreferrer" (or
equivalent attributes) whenever target="_blank" is used for external links such
as this one.

In `@server/utils/module.ts`:
- Around line 117-123: The degraded color in the statusColorMap constant is too
light for white badge text; update the 'degraded' entry in statusColorMap to use
a darker, accessible color token (for example a darker amber like `#b45309` or
your app's theme token for degraded/amber-700) so contrast meets accessibility
for white text; change the value assigned to the 'degraded' key in
statusColorMap accordingly.

---

Nitpick comments:
In `@server/utils/module.ts`:
- Around line 125-127: The loop over data uses uncached.find(m => m.npm ===
item.npm) inside for (const item of data), causing O(n²) behavior; fix by
precomputing a Map keyed by module.npm (e.g., const uncachedByNpm = new
Map(uncached.map(m => [m.npm, m]))) and then inside the loop replace
uncached.find(...) with a constant-time lookup uncachedByNpm.get(item.npm),
preserving the existing skip logic (if (!module) continue) and any subsequent
usage of module.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0b9aa30 and 8c8464a.

📒 Files selected for processing (8)
  • README.md
  • app/components/AdminDashboard.vue
  • app/components/module/ModuleItem.vue
  • app/pages/modules/[slug].vue
  • server/api/v1/modules/[name].get.ts
  • server/api/v1/modules/index.get.ts
  • server/utils/module.ts
  • shared/types/modules.ts

Comment on lines +117 to +123
const statusColorMap: Record<string, string> = {
optimal: '#22c55e',
stable: '#84cc16',
degraded: '#eab308',
critical: '#ef4444',
unknown: '#6b7280'
}
Copy link

@coderabbitai coderabbitai bot Feb 27, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Use an accessible degraded color token.

Line 120 sets degraded to #eab308, which is too light against white badge text and can fail readability in the new health badges.

Suggested fix
 const statusColorMap: Record<string, string> = {
   optimal: '#22c55e',
   stable: '#84cc16',
-  degraded: '#eab308',
+  degraded: '#a16207',
   critical: '#ef4444',
   unknown: '#6b7280'
 }
📝 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
const statusColorMap: Record<string, string> = {
optimal: '#22c55e',
stable: '#84cc16',
degraded: '#eab308',
critical: '#ef4444',
unknown: '#6b7280'
}
const statusColorMap: Record<string, string> = {
optimal: '#22c55e',
stable: '#84cc16',
degraded: '#a16207',
critical: '#ef4444',
unknown: '#6b7280'
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/utils/module.ts` around lines 117 - 123, The degraded color in the
statusColorMap constant is too light for white badge text; update the 'degraded'
entry in statusColorMap to use a darker, accessible color token (for example a
darker amber like `#b45309` or your app's theme token for degraded/amber-700) so
contrast meets accessibility for white text; change the value assigned to the
'degraded' key in statusColorMap accordingly.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yellow color is nuxt.care! But I'll change the contrast for the text.

Copy link

Choose a reason for hiding this comment

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

@Flo0806 That makes perfect sense! Keeping the nuxt.care brand colors is important. Adjusting the text color instead is a great solution to maintain both brand consistency and accessibility. 👍

@vercel
Copy link
Contributor

vercel bot commented Feb 27, 2026

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

Project Deployment Actions Updated (UTC)
nuxt Error Error Feb 27, 2026 5:13pm

Copy link

@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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@server/utils/module.ts`:
- Around line 112-135: The current implementation builds one long URL from
uncached and then matches each returned item using uncached.find (causing
possible URL-length failures and O(n²) matching); change this by chunking
uncached into smaller batches (e.g., 50-100 packages) and issuing multiple
$fetch calls for each chunk, and replace the per-item find with a lookup map
keyed by npm (build a Map from uncached items using m.npm) so you can directly
map each response item to its module; keep the same statusColorMap, result
assignment, and kv.set(`module:health:${module.name}`, ...) logic for each
matched module and respect the existing TTL.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1161c31 and 3e66186.

📒 Files selected for processing (4)
  • app/pages/modules/[slug].vue
  • server/api/v1/modules/[name].get.ts
  • server/api/v1/modules/index.get.ts
  • server/utils/module.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/pages/modules/[slug].vue

Copy link

@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)
server/utils/module.ts (1)

130-140: Parallelize KV writes per chunk to cut response latency.

kv.set is awaited one-by-one in the loop, which adds unnecessary per-item delay.

♻️ Proposed refactor
-      for (const item of data) {
+      const kvWrites: Promise<unknown>[] = []
+      for (const item of data) {
         const module = npmToModule.get(item.npm)
         if (!module) continue
         const health: ModuleHealth = {
           score: item.score,
           color: statusColorMap[item.status] || '#6b7280',
           status: item.status
         }
         result[module.name] = health
-        await kv.set(`module:health:${module.name}`, health, { ttl: 60 * 60 * 24 })
+        kvWrites.push(kv.set(`module:health:${module.name}`, health, { ttl: 60 * 60 * 24 }))
       }
+      await Promise.all(kvWrites)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/utils/module.ts` around lines 130 - 140, The loop currently awaits
kv.set for each item serially which slows the response; instead, inside the loop
over data (where you access npmToModule, build ModuleHealth, and assign
result[module.name]) push the kv.set promise into an array (e.g., promises)
rather than awaiting it, and after the loop await Promise.all(promises) to run
writes in parallel; keep the same ModuleHealth shape and TTL and ensure you
still populate result before awaiting the aggregated promises so behavior is
unchanged (refer to npmToModule, ModuleHealth, result, and kv.set).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@server/utils/module.ts`:
- Around line 124-130: The $fetch call to `https://nuxt.care/api/v1/modules`
lacks timeout/retry controls; update the call in server/utils/module.ts (where
`data = await $fetch<NuxtCareModuleSlim[]>(...)`) to pass explicit options such
as a timeout (e.g., timeout: 5000 ms) and retry policy (e.g., retry: { limit: 2
}) so the request will fail fast and retry a bounded number of times; keep the
existing error handling and adjust values as needed for your service SLAs.

---

Nitpick comments:
In `@server/utils/module.ts`:
- Around line 130-140: The loop currently awaits kv.set for each item serially
which slows the response; instead, inside the loop over data (where you access
npmToModule, build ModuleHealth, and assign result[module.name]) push the kv.set
promise into an array (e.g., promises) rather than awaiting it, and after the
loop await Promise.all(promises) to run writes in parallel; keep the same
ModuleHealth shape and TTL and ensure you still populate result before awaiting
the aggregated promises so behavior is unchanged (refer to npmToModule,
ModuleHealth, result, and kv.set).

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3e66186 and 3cbbbd1.

📒 Files selected for processing (1)
  • server/utils/module.ts

Copy link

@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)
server/utils/module.ts (1)

99-106: Parallelize KV cache reads to reduce tail latency.

The current loop does kv.get sequentially for every module. For larger lists, this adds avoidable request-time latency.

⚡ Proposed refactor
-  for (const module of modules) {
-    const cached = await kv.get<ModuleHealth>(`module:health:${module.name}`)
-    if (cached) {
-      result[module.name] = cached
-    } else {
-      uncached.push(module)
-    }
-  }
+  const cachedEntries = await Promise.all(
+    modules.map(async (module) => {
+      const cached = await kv.get<ModuleHealth>(`module:health:${module.name}`)
+      return { module, cached }
+    })
+  )
+  for (const { module, cached } of cachedEntries) {
+    if (cached) result[module.name] = cached
+    else uncached.push(module)
+  }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/utils/module.ts` around lines 99 - 106, The loop over modules does
sequential kv.get calls causing tail latency; change to run KV reads in parallel
by mapping modules to an array of promises (calling
kv.get<ModuleHealth>(`module:health:${module.name}`) for each module) and await
Promise.all (or Promise.allSettled if you want to tolerate individual failures),
then iterate the resolved results to populate result[module.name] for hits and
push corresponding modules into uncached for misses; update variable references:
modules, kv.get, ModuleHealth, result, uncached.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@server/utils/module.ts`:
- Around line 118-143: npmToModule currently maps each npm name to a single
module entry so modules that share the same npm (aliases/duplicates) lose their
health info; change the grouping so npmToModule maps an npm string to an array
of Module entries (build it from uncached into arrays), then in the response
loop (where you access npmToModule.get(item.npm)) iterate over the array of
modules and assign the same ModuleHealth to each (setting result[module.name]
and calling kv.set(`module:health:${module.name}`, health, { ttl: 60*60*24 })
for each module) so all aliases receive the health update while leaving
CHUNK_SIZE, $fetch call, statusColorMap lookup, and ModuleHealth structure
unchanged.

---

Nitpick comments:
In `@server/utils/module.ts`:
- Around line 99-106: The loop over modules does sequential kv.get calls causing
tail latency; change to run KV reads in parallel by mapping modules to an array
of promises (calling kv.get<ModuleHealth>(`module:health:${module.name}`) for
each module) and await Promise.all (or Promise.allSettled if you want to
tolerate individual failures), then iterate the resolved results to populate
result[module.name] for hits and push corresponding modules into uncached for
misses; update variable references: modules, kv.get, ModuleHealth, result,
uncached.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3cbbbd1 and a21434e.

📒 Files selected for processing (1)
  • server/utils/module.ts

Comment on lines +118 to +143
const npmToModule = new Map(uncached.map(m => [m.npm, m]))

console.info(`Fetching health for ${uncached.length} modules from nuxt.care (${Math.ceil(uncached.length / CHUNK_SIZE)} chunks)...`)
for (let i = 0; i < uncached.length; i += CHUNK_SIZE) {
const chunk = uncached.slice(i, i + CHUNK_SIZE)
try {
const query = new URLSearchParams()
query.set('slim', 'true')
for (const m of chunk) {
query.append('package', m.npm)
}
const data = await $fetch<NuxtCareModuleSlim[]>(`https://nuxt.care/api/v1/modules?${query.toString()}`, {
timeout: 10_000,
retry: 2,
retryDelay: 1000
})
for (const item of data) {
const module = npmToModule.get(item.npm)
if (!module) continue
const health: ModuleHealth = {
score: item.score,
color: statusColorMap[item.status] || '#6b7280',
status: item.status
}
result[module.name] = health
await kv.set(`module:health:${module.name}`, health, { ttl: 60 * 60 * 24 })
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Handle multiple modules sharing the same npm package.

npmToModule keeps only one module per npm, so aliases/duplicates silently drop health assignment for others.

💡 Proposed fix
-  const npmToModule = new Map(uncached.map(m => [m.npm, m]))
+  const npmToModules = new Map<string, BaseModule[]>()
+  for (const m of uncached) {
+    const existing = npmToModules.get(m.npm)
+    if (existing) existing.push(m)
+    else npmToModules.set(m.npm, [m])
+  }
...
-      for (const item of data) {
-        const module = npmToModule.get(item.npm)
-        if (!module) continue
+      for (const item of data) {
+        const matchedModules = npmToModules.get(item.npm)
+        if (!matchedModules?.length) continue
         const health: ModuleHealth = {
           score: item.score,
           color: statusColorMap[item.status] || '#6b7280',
           status: item.status
         }
-        result[module.name] = health
-        await kv.set(`module:health:${module.name}`, health, { ttl: 60 * 60 * 24 })
+        for (const module of matchedModules) {
+          result[module.name] = health
+          await kv.set(`module:health:${module.name}`, health, { ttl: 60 * 60 * 24 })
+        }
       }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/utils/module.ts` around lines 118 - 143, npmToModule currently maps
each npm name to a single module entry so modules that share the same npm
(aliases/duplicates) lose their health info; change the grouping so npmToModule
maps an npm string to an array of Module entries (build it from uncached into
arrays), then in the response loop (where you access npmToModule.get(item.npm))
iterate over the array of modules and assign the same ModuleHealth to each
(setting result[module.name] and calling kv.set(`module:health:${module.name}`,
health, { ttl: 60*60*24 }) for each module) so all aliases receive the health
update while leaving CHUNK_SIZE, $fetch call, statusColorMap lookup, and
ModuleHealth structure unchanged.

@HugoRCD
Copy link
Member

HugoRCD commented Mar 2, 2026

Really cool idea, but i think we could make the badge looks better IMO 🤔 (btw these are mock data to test the PR locally)

CleanShot 2026-03-02 at 11 26 19@2x

@Flo0806
Copy link
Member Author

Flo0806 commented Mar 2, 2026

all right! I will play a bit

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.

3 participants