Skip to content

feat: maintainer page#55

Merged
Flo0806 merged 3 commits intomainfrom
feat/maintainer-page
Apr 16, 2026
Merged

feat: maintainer page#55
Flo0806 merged 3 commits intomainfrom
feat/maintainer-page

Conversation

@Flo0806
Copy link
Copy Markdown
Owner

@Flo0806 Flo0806 commented Apr 16, 2026

Summary

Maintainer page!

Changes

  • Add a maintainer page to show maintainer/contributor what to do to make modules score much better

Related Issue

Resolves: #53

Checklist

  • Tested locally
  • No console errors

Summary by CodeRabbit

  • New Features

    • Added "My modules" dropdown menu option linking to the new maintainer dashboard
    • Launched maintainer dashboard displaying owned and contributed modules with health scores and actionable improvement suggestions
    • Added informational banner notifying users of the new maintainer feature
  • Tests

    • Added test coverage for action hint generation and module categorization logic

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 16, 2026

Warning

Rate limit exceeded

@Flo0806 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 52 minutes and 19 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 52 minutes and 19 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3ddf478a-32ea-4f19-a61c-7e1f17e4ae19

📥 Commits

Reviewing files that changed from the base of the PR and between e0c2c0a and a42817b.

📒 Files selected for processing (1)
  • app/utils/actionHints.ts
📝 Walkthrough

Walkthrough

This PR introduces a complete Maintainer Dashboard feature enabling authenticated users to view modules they own or contribute to, along with health scores and actionable hints for improvement. The implementation includes new page and component files, utility functions for module categorization and hint generation, UI integration points, and comprehensive test coverage.

Changes

Cohort / File(s) Summary
Navigation & UI Integration
app/components/AuthButton.vue
Added dropdown menu item linking to /maintainer route for logged-in users.
Core Utilities
app/utils/actionHints.ts, app/composables/useMaintainerModules.ts
New utilities: actionHints.ts converts health signals to remediation hints with gain calculations and capped potential scoring; useMaintainerModules.ts categorizes modules as owned or contributor roles using case-insensitive username matching.
Main Dashboard Page & Components
app/pages/maintainer.vue, app/components/maintainer/Hero.vue, app/components/maintainer/ModuleCard.vue
New maintainer dashboard page with authentication gating, data fetching, and module categorization. Hero component displays aggregate stats with Bluesky share button and clipboard copy. ModuleCard component renders per-module health score, action hints with expandable details, code snippet copy-to-clipboard, and award badge for perfect modules.
Homepage Integration
app/pages/index.vue
Added dismissible "New: Maintainer Dashboard" banner with client-side persistence via localStorage.
Test Coverage
test/unit/actionHints.test.ts, test/unit/maintainerModules.test.ts
Comprehensive tests for hint generation (signal-to-hint conversion, vulns-penalty merging, gain calculations, 100-point cap), scoring, and module categorization (owner/contributor role assignment, case-insensitive matching).

Sequence Diagram

sequenceDiagram
    actor User
    participant Page as Maintainer Page
    participant Auth as useAuth()
    participant API as /api/modules
    participant Composable as useMaintainerModules()
    participant Utils as actionHints Utils
    participant Hero as Hero Component
    participant ModuleCard as ModuleCard Component

    User->>Page: Navigate to /maintainer
    Page->>Auth: Check isLoggedIn
    Auth-->>Page: User authenticated
    Page->>API: useFetch('/api/modules')
    API-->>Page: ModuleData[] returned
    Page->>Composable: useMaintainerModules(modules)
    Composable->>Composable: categorizeMaintainerModules()
    Composable-->>Page: maintainerModules, ownedModules, contributorModules
    Page->>Page: Sort by potentialScore - currentScore
    Page->>Hero: Pass entries (all modules)
    Hero->>Utils: Compute avgScore & potentialAvg
    Hero-->>User: Render stats + share buttons
    Page->>ModuleCard: Render ownedModules section
    ModuleCard->>Utils: getActionHints(module)
    Utils-->>ModuleCard: ActionHint[] with gain/snippets/links
    ModuleCard-->>User: Render health score + expandable hints
    Page->>ModuleCard: Render contributorModules section
    ModuleCard-->>User: Same hint rendering flow
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • PR #43: Adds the key field to HealthSignal type, which is required by the new actionHints.ts logic for signal-to-hint mapping and vulns-penalty merging.

Poem

🐰 A dashboard blooms for keepers of code,
With hints that shimmer along the road,
Modules sorted by potential gain,
Golden awards for hints maintained—
Share your stats on Bluesky's flight! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 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 (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: maintainer page' clearly and concisely summarizes the main change—adding a new maintainer dashboard page—which aligns with the primary objective and the majority of the changeset.
Linked Issues check ✅ Passed The PR implements all core requirements from issue #53: /maintainer page with hero stats, owned/contributor module split, action hint cards with expandable details, copy-able snippets, Bluesky share button, homepage banner, categorizeMaintainerModules pure function, and 12 unit tests covering categorization and hint generation with vuln merging.
Out of Scope Changes check ✅ Passed All changes are directly aligned with the maintainer dashboard feature scope defined in issue #53; no unrelated refactoring, unplanned features, or out-of-scope modifications are present.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/maintainer-page

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
Copy Markdown

@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 (4)
app/pages/maintainer.vue (1)

114-117: Avoid fetching /api/modules when the user is logged out.

The fetch runs unconditionally on mount (including for anonymous visitors who only see the "sign in" CTA). Since this endpoint returns the full module dataset, it's a noticeable chunk to pull for users who will never see it.

Consider immediate: false + execute() once isLoggedIn flips true, or guard the fetch behind the auth check.

♻️ Sketch
-const { data: modules, pending } = await useFetch<ModuleData[]>('/api/modules', {
-  key: 'modules',
-  server: false,
-})
+const { data: modules, pending, execute } = await useFetch<ModuleData[]>('/api/modules', {
+  key: 'modules',
+  server: false,
+  immediate: false,
+})
+
+watchEffect(() => {
+  if (isLoggedIn.value) execute()
+})
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/pages/maintainer.vue` around lines 114 - 117, The current unconditional
useFetch call for modules fetches '/api/modules' even for anonymous users;
change it to not run immediately by setting immediate: false on useFetch (the
call that returns { data: modules, pending }) and trigger the fetch via
execute() once the auth state flips true (watch the isLoggedIn reactive/computed
and call execute() when it becomes true), or alternatively wrap the useFetch
invocation behind the isLoggedIn check so the fetch only runs for authenticated
users.
app/components/maintainer/Hero.vue (1)

92-100: Consider sourcing the site URL from runtime config.

https://nuxt.care is hardcoded in both blueskyUrl and copyShareText. Using useRuntimeConfig().public.siteUrl (or similar) would keep preview/staging builds from posting production links when someone shares from a non-prod environment.

♻️ Suggested refactor
+const siteUrl = useRuntimeConfig().public.siteUrl ?? 'https://nuxt.care'
+
 const shareText = computed(() => {
   const moduleCount = props.entries.length
   const moduleWord = moduleCount === 1 ? 'Nuxt module' : 'Nuxt modules'
-  return `I'm maintaining ${moduleCount} ${moduleWord} at ${avgScore.value}/100 avg on nuxt.care.`
+  return `I'm maintaining ${moduleCount} ${moduleWord} at ${avgScore.value}/100 avg on nuxt.care.`
 })

 const blueskyUrl = computed(() =>
-  `https://bsky.app/intent/compose?text=${encodeURIComponent(`${shareText.value} https://nuxt.care`)}`,
+  `https://bsky.app/intent/compose?text=${encodeURIComponent(`${shareText.value} ${siteUrl}`)}`,
 )
@@
-    await navigator.clipboard.writeText(`${shareText.value} https://nuxt.care`)
+    await navigator.clipboard.writeText(`${shareText.value} ${siteUrl}`)

Also applies to: 103-105

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/components/maintainer/Hero.vue` around lines 92 - 100, The hardcoded
production URL appears in the shareText and blueskyUrl computed properties (and
the related copyShareText usage) which can leak production links from
preview/staging; replace the literal "https://nuxt.care" by reading the site URL
from runtime config (useRuntimeConfig().public.siteUrl or equivalent) and
interpolate that value into shareText and blueskyUrl (and any copyShareText
reference) so environment-appropriate URLs are used at runtime.
app/components/maintainer/ModuleCard.vue (1)

230-235: Remove redundant Set reassignment.

In Vue 3, ref(new Set()) automatically tracks mutations from Set.add() and Set.delete(), making the line expanded.value = new Set(expanded.value) unnecessary. This removes dead work on each toggle.

♻️ Cleanup
 function toggleExpanded(key: string) {
   if (expanded.value.has(key)) expanded.value.delete(key)
   else expanded.value.add(key)
-  expanded.value = new Set(expanded.value)
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/components/maintainer/ModuleCard.vue` around lines 230 - 235, The
toggleExpanded function is doing unnecessary work by recreating the Set after
every mutation; remove the line that reassigns expanded.value = new
Set(expanded.value) and just call expanded.value.add(key) or
expanded.value.delete(key) on the ref-held Set (i.e., update the existing Set in
the toggleExpanded function that mutates the ref new Set<string>() stored in
expanded) so you avoid redundant Set reconstruction while preserving reactivity.
app/utils/actionHints.ts (1)

174-178: Consider memoizing hints to avoid repeated work when sorting.

potentialScore calls getActionHints (which maps over all signals, filters, searches for vulns-penalty, and sorts) on every invocation. Per the PR, app/pages/maintainer.vue sorts modules by potentialScore(...), so this runs O(n log n) times across the module list, each call re-doing the full hint pipeline. For a maintainer with many modules this is wasteful.

Two easy options:

  • Compute getActionHints(mod) once per module in a computed and derive the score + sort key from it.
  • Or skip the sort inside getActionHints when only potentialScore needs the sum (the sort result is unused here).
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/utils/actionHints.ts` around lines 174 - 178, potentialScore recomputes
getActionHints (which maps/filters/sorts) on every call causing repeated O(n log
n) work when maintainer.vue sorts modules; fix by changing potentialScore to
accept an optional precomputed hints parameter (e.g., potentialScore(data:
ModuleData, hints?: ActionHint[])) and have app/pages/maintainer.vue compute
getActionHints(module) once in a computed property and pass those hints into
both potentialScore and the sort key; alternatively, if you prefer internal
caching, implement a small WeakMap cache inside potentialScore keyed by
ModuleData to store/retrieve getActionHints results so the heavy pipeline runs
only once per 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/utils/actionHints.ts`:
- Around line 154-169: The merge of the 'vulns-penalty' hint should skip
non-positive gaps: compute penaltyGap = vulnsPenalty.maxPoints -
vulnsPenalty.points and only apply or push to hints when penaltyGap > 0 (or use
Math.max(0, penaltyGap) and skip if zero) so you don't add a zero-gain hint or
decrement existing security.gain when points > maxPoints; update the logic
around vulnsPenalty, penaltyGap, hints, and the existing 'security' object so
security.gain is increased only with a positive penaltyGap and no hint is pushed
when penaltyGap <= 0.

---

Nitpick comments:
In `@app/components/maintainer/Hero.vue`:
- Around line 92-100: The hardcoded production URL appears in the shareText and
blueskyUrl computed properties (and the related copyShareText usage) which can
leak production links from preview/staging; replace the literal
"https://nuxt.care" by reading the site URL from runtime config
(useRuntimeConfig().public.siteUrl or equivalent) and interpolate that value
into shareText and blueskyUrl (and any copyShareText reference) so
environment-appropriate URLs are used at runtime.

In `@app/components/maintainer/ModuleCard.vue`:
- Around line 230-235: The toggleExpanded function is doing unnecessary work by
recreating the Set after every mutation; remove the line that reassigns
expanded.value = new Set(expanded.value) and just call expanded.value.add(key)
or expanded.value.delete(key) on the ref-held Set (i.e., update the existing Set
in the toggleExpanded function that mutates the ref new Set<string>() stored in
expanded) so you avoid redundant Set reconstruction while preserving reactivity.

In `@app/pages/maintainer.vue`:
- Around line 114-117: The current unconditional useFetch call for modules
fetches '/api/modules' even for anonymous users; change it to not run
immediately by setting immediate: false on useFetch (the call that returns {
data: modules, pending }) and trigger the fetch via execute() once the auth
state flips true (watch the isLoggedIn reactive/computed and call execute() when
it becomes true), or alternatively wrap the useFetch invocation behind the
isLoggedIn check so the fetch only runs for authenticated users.

In `@app/utils/actionHints.ts`:
- Around line 174-178: potentialScore recomputes getActionHints (which
maps/filters/sorts) on every call causing repeated O(n log n) work when
maintainer.vue sorts modules; fix by changing potentialScore to accept an
optional precomputed hints parameter (e.g., potentialScore(data: ModuleData,
hints?: ActionHint[])) and have app/pages/maintainer.vue compute
getActionHints(module) once in a computed property and pass those hints into
both potentialScore and the sort key; alternatively, if you prefer internal
caching, implement a small WeakMap cache inside potentialScore keyed by
ModuleData to store/retrieve getActionHints results so the heavy pipeline runs
only once per module.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 17f49873-6580-4113-80d8-3e5011b82108

📥 Commits

Reviewing files that changed from the base of the PR and between 1ad8cfd and e0c2c0a.

📒 Files selected for processing (9)
  • app/components/AuthButton.vue
  • app/components/maintainer/Hero.vue
  • app/components/maintainer/ModuleCard.vue
  • app/composables/useMaintainerModules.ts
  • app/pages/index.vue
  • app/pages/maintainer.vue
  • app/utils/actionHints.ts
  • test/unit/actionHints.test.ts
  • test/unit/maintainerModules.test.ts

Comment thread app/utils/actionHints.ts
@Flo0806 Flo0806 merged commit 95b6ccf into main Apr 16, 2026
2 checks passed
@Flo0806 Flo0806 deleted the feat/maintainer-page branch April 16, 2026 21:14
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.

Maintainer Dashboard

1 participant