Skip to content

refactor: split user page in components and composables#17

Merged
MatteoGabriele merged 6 commits intomainfrom
dev
Mar 8, 2026
Merged

refactor: split user page in components and composables#17
MatteoGabriele merged 6 commits intomainfrom
dev

Conversation

@MatteoGabriele
Copy link
Copy Markdown
Owner

@MatteoGabriele MatteoGabriele commented Mar 8, 2026

Minimize the code within a single component to improve clarity, maintainability, and ease of updates.

Summary by CodeRabbit

  • New Features

    • Activity classification labels for analyzed accounts (Organic, Mixed, Automation)
    • New Analysis card showing classification, activity summary, community-flag details, and activity signals
    • User profile card displaying avatar, followers, repos, and account creation date
    • Loading skeleton and generic error UI components
  • Improvements

    • Centralized SEO handling for user and analysis pages
    • Simplified user page flow and clearer error messaging (including 404 UX)

@netlify
Copy link
Copy Markdown

netlify bot commented Mar 8, 2026

Deploy Preview for agentscan ready!

Name Link
🔨 Latest commit b167106
🔍 Latest deploy log https://app.netlify.com/projects/agentscan/deploys/69addadbd868900008faf64c
😎 Deploy Preview https://deploy-preview-17--agentscan.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Refactors the /user/[name] page by extracting UI and logic into dedicated components and composables to reduce page complexity and improve maintainability.

Changes:

  • Split the user page into UserCard, UserError, UserAnalysisCard, and a skeleton component.
  • Extracted SEO logic into useSeoUser / useSeoAnalysis.
  • Extracted classification label/description logic into useClassificationDetails.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
app/pages/user/[name].vue Simplifies page: fetches account user, delegates rendering/analysis/SEO to composables/components.
app/composables/useSeo.ts New composables to set OG metadata for user + analysis.
app/composables/useClassificationDetails.ts New composable encapsulating score→label/description mapping.
app/components/UserError.vue New component for rendering 404 and generic error states.
app/components/UserCard.vue New component for rendering the GitHub user profile card.
app/components/UserAnalysisCardSkeleton.vue New skeleton/loading UI for analysis card.
app/components/UserAnalysisCard.vue New component handling analysis fetch + rendering results and flags.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 8, 2026

📝 Walkthrough

Walkthrough

Introduces new composables for score classification and SEO, adds several presentational components (UserCard, UserError, AnalysisCard, CardSkeleton, GenericError), and refactors the user page and index to use these components and new composables.

Changes

Cohort / File(s) Summary
Composables
app/composables/useClassificationDetails.ts, app/composables/useSeo.ts
Added useClassificationDetails(score) to map numeric scores to classification labels/descriptions using thresholds; added useSeoUser(user) and useSeoAnalysis(analysis, options) to compute and apply OG metadata (og:title, og:image, og:description) via useHead.
User Components
app/components/User/Card.vue, app/components/User/Error.vue
New presentational components: User/Card.vue renders GitHub user info; User/Error.vue renders 404-specific UI or delegates other errors to GenericError.
Analysis Components
app/components/Analysis/Card.vue, app/components/Analysis/CardSkeleton.vue
New Analysis/Card.vue fetches identify-replicant analysis, derives score, classification, community-flag state, exposes analysis data and wires SEO via useSeoAnalysis; CardSkeleton.vue provides loading skeleton UI.
Error Component
app/components/GenericError.vue
New generic error component rendering error.data?.message and an icon for non-404 error states.
Pages / Routing
app/pages/user/[name].vue, app/pages/index.vue
Refactored user page to use username binding, replaced inline logic with UserCard, UserError, and AnalysisCard, and switched to useSeoUser; index page now uses AnalysisForm (renamed from AnalyzeForm) in template only.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant Browser
    participant UserPage as user/[name].vue
    participant AnalysisCard
    participant API as /api/identify-replicant
    participant Head as useHead

    Browser->>UserPage: navigate to /user/:name
    UserPage->>AnalysisCard: render with username
    AnalysisCard->>API: GET /identify-replicant/{username}?created_at&repos_count
    API-->>AnalysisCard: analysis payload
    AnalysisCard->>AnalysisCard: compute score, classification (useClassificationDetails)
    AnalysisCard->>Head: update og:description (useSeoAnalysis)
    AnalysisCard-->>UserPage: display analysis UI / flags / links
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 I nibbled through code, found modular light,
Cards and composables stitched tidy and bright,
SEO whispers and scores classify,
Components hop in, each small and spry,
A rabbit cheers the refactor's sight! 🥕

🚥 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main change: refactoring the user page into separate components and composables.

✏️ 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
  • Commit unit tests in branch dev

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

🧹 Nitpick comments (1)
app/composables/useClassificationDetails.ts (1)

3-30: Let this composable own the full classification view model.

The same threshold cascade is still duplicated in app/components/UserAnalysisCard.vue for the icon and color classes. Returning those tokens here too would keep the classification UI on one source of truth and reduce drift the next time the thresholds change.

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

In `@app/composables/useClassificationDetails.ts` around lines 3 - 30, The
composable useClassificationDetails currently only returns classificationDetails
(label/description) but the same threshold logic is duplicated elsewhere; extend
useClassificationDetails to own the full classification view model by adding and
returning additional tokens (e.g., iconName and colorClass or similar) alongside
classificationDetails so callers like UserAnalysisCard.vue can consume a single
source of truth; update the computed in useClassificationDetails that inspects
toValue(score) (using CONFIG.THRESHOLD_HUMAN and CONFIG.THRESHOLD_SUSPICIOUS) to
return an object with { label, description, iconName, colorClass } (or
equivalent property names used by your UI) so icon and color logic is removed
from UserAnalysisCard.vue.
🤖 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/UserAnalysisCard.vue`:
- Around line 42-43: The classification UI functions ignore community flags:
compute hasCommunityFlag from data.value (e.g., data.value?.flags?.community)
and ensure useClassificationDetails and classificationIcon receive that flag (or
update useClassificationDetails to accept a second parameter) so
classificationDetails and classificationIcon derive state from both score and
hasCommunityFlag; also ensure scoreClasses still treats hasCommunityFlag as
top-level but that classificationDetails/classificationIcon logic short-circuits
to the community-flagged state when hasCommunityFlag is true.
- Around line 109-117: The wrapper currently uses a <span> that contains an <h3>
(invalid HTML); change the outer element in the block that renders the
classification (the element using :class="scoreClasses.text") from a span to a
block-level container (e.g., div) or alternatively move the icon
(classificationIcon) into the <h3> so the heading contains both the icon and the
label (keep the existing classes like "flex gap-2 items-center mb-2" and
"text-base" applied appropriately); update the template around
classificationIcon and {{ classificationDetails.label }} so the markup is
semantically correct and styling is preserved.
- Around line 11-21: The fetch call using useFetch should be made reactive:
replace the static query object and static key with computed properties so
changes to props.user.created_at, props.user.public_repos, and username.value
trigger refetch/cache variations; specifically, create a computed (e.g.,
queryParams) that returns { created_at: props.user.created_at, repos_count:
props.user.public_repos } and use a computed key (e.g., analysisKey) like
`analysis:${username.value}:${props.user.created_at}:${props.user.public_repos}`,
then pass query: queryParams.value (or the computed directly if useFetch accepts
it) and key: analysisKey to useFetch and update watch to include any reactive
refs used (username and the computed) so the request re-runs when those user
properties change.

In `@app/components/UserCard.vue`:
- Around line 33-35: The stats row is being hidden at all sizes because the ul
uses the global "hidden" utility alongside "@xl:flex"; remove the "hidden" class
(or replace it with a size-scoped hidden like "@sm:hidden" if you need to hide
only on certain breakpoints) so the <ul> with class "text-gh-muted `@md`:mt-4
text-sm hidden `@xl`:flex flex-wrap gap-4 flex-row" in UserCard.vue renders the
followers/repos/Since at non-xl sizes; ensure the final class string includes a
base "flex" (or no hidden) and the desired breakpoint-specific utilities.

In `@app/components/UserGenericError.vue`:
- Around line 16-17: The component currently only displays error.data?.message
which can be undefined leaving the UI blank; update the render logic in
UserGenericError.vue (the template that references error.data?.message) to
provide a non-empty fallback such as using error.data?.message || error.message
|| 'An unexpected error occurred' (or similar user-friendly text) so that
network failures or serialization issues still show a meaningful message; ensure
the fallback is used wherever error.data?.message is referenced in this
component.

In `@app/pages/user/`[name].vue:
- Around line 17-21: The cache key passed to useFetch is currently a static
string and doesn't update when username changes, causing shared async-data
across navigations; update the key to be reactive (e.g., provide a function or a
computed that returns `account:${username.value}`) so the key changes with
username.value and useFetch creates distinct cache entries; adjust the call site
(useFetch invocation and its key option) to reference the reactive key and keep
the existing URL getter/watch as-is.

---

Nitpick comments:
In `@app/composables/useClassificationDetails.ts`:
- Around line 3-30: The composable useClassificationDetails currently only
returns classificationDetails (label/description) but the same threshold logic
is duplicated elsewhere; extend useClassificationDetails to own the full
classification view model by adding and returning additional tokens (e.g.,
iconName and colorClass or similar) alongside classificationDetails so callers
like UserAnalysisCard.vue can consume a single source of truth; update the
computed in useClassificationDetails that inspects toValue(score) (using
CONFIG.THRESHOLD_HUMAN and CONFIG.THRESHOLD_SUSPICIOUS) to return an object with
{ label, description, iconName, colorClass } (or equivalent property names used
by your UI) so icon and color logic is removed from UserAnalysisCard.vue.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5696bf48-f029-4e9d-ada1-4da8652c3870

📥 Commits

Reviewing files that changed from the base of the PR and between de3babe and e888169.

📒 Files selected for processing (8)
  • app/components/UserAnalysisCard.vue
  • app/components/UserAnalysisCardSkeleton.vue
  • app/components/UserCard.vue
  • app/components/UserError.vue
  • app/components/UserGenericError.vue
  • app/composables/useClassificationDetails.ts
  • app/composables/useSeo.ts
  • app/pages/user/[name].vue

Comment on lines +11 to +21
const { data, status, error } = useFetch(
() => `/api/identify-replicant/${username.value}`,
{
query: {
created_at: props.user.created_at,
repos_count: props.user.public_repos,
},
key: `analysis:${username.value}`,
watch: [username],
lazy: true,
},
Copy link
Copy Markdown

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:

find . -type f -name "UserAnalysisCard.vue" | head -20

Repository: MatteoGabriele/agentscan

Length of output: 50


🏁 Script executed:

git ls-files | grep -i "useranaly" | head -20

Repository: MatteoGabriele/agentscan

Length of output: 50


🏁 Script executed:

git ls-files | grep -E "\.(vue|js|ts)$" | head -30

Repository: MatteoGabriele/agentscan

Length of output: 984


🏁 Script executed:

grep -r "useFetch" --include="*.vue" --include="*.ts" -l | head -20

Repository: MatteoGabriele/agentscan

Length of output: 126


🏁 Script executed:

cat -n app/components/Analysis/Card.vue

Repository: MatteoGabriele/agentscan

Length of output: 6744


🏁 Script executed:

grep -r "Analysis/Card" --include="*.vue" --include="*.ts" -B 2 -A 2 | head -50

Repository: MatteoGabriele/agentscan

Length of output: 50


🏁 Script executed:

cat -n app/pages/user/\[name\].vue

Repository: MatteoGabriele/agentscan

Length of output: 1267


🏁 Script executed:

cat -n app/components/User/Card.vue | head -50

Repository: MatteoGabriele/agentscan

Length of output: 1941


🏁 Script executed:

grep -r "AnalysisCard" --include="*.vue" -B 2 -A 2

Repository: MatteoGabriele/agentscan

Length of output: 644


🌐 Web query:

Nuxt 3 useFetch query reactivity computed property watch

💡 Result:

useFetch() will refetch when its reactive dependencies change if you pass refs/computeds into its options (e.g., query)—because query is typed as a MaybeRefOrGetter (so refs/computeds are supported) and Nuxt tracks them for reactivity. [1][2]

Pattern 1: reactive query (refetches automatically)

const page = ref(1)
const q = ref('nuxt')

const query = computed(() => ({
  page: page.value,
  q: q.value,
}))

const { data, pending, refresh } = await useFetch('/api/posts', { query })

When page or q changes, the fetch is re-run (you can still call refresh() manually). [1][2]

Pattern 2: use watch to control what triggers refetch

Use watch when the reactive dependency isn’t directly inside query/URL (or you want to be explicit):

const route = useRoute()

const { data, refresh } = await useFetch('/api/posts', {
  query: { q: () => route.query.q },  // or computed/ref
  watch: [() => route.query.q],       // explicit dependency
})

watch is specifically meant to “watch reactive sources and auto-refresh” the result. [1]

Common gotcha

If you set immediate: false, Nuxt won’t do the initial request until you call execute()/refresh(), and watch-triggered refresh behavior can surprise you—so keep immediate: true (default) unless you truly want manual control. [2]

Sources: Nuxt useFetch API docs [1] and discussion/notes on useFetch/useAsyncData watch + immediate behavior [2].

[1] (nuxt.com)
[2] (answeroverflow.com)

Citations:


🏁 Script executed:

rg "AnalysisCard" --type vue --type ts -B 5 -A 5

Repository: MatteoGabriele/agentscan

Length of output: 95


🏁 Script executed:

rg "AnalysisCard" -B 5 -A 5

Repository: MatteoGabriele/agentscan

Length of output: 1206


🏁 Script executed:

rg "watch:|polling|setInterval|refresh" app/components/Analysis/Card.vue

Repository: MatteoGabriele/agentscan

Length of output: 90


🏁 Script executed:

rg "props\.|user\s*=" app/pages/user/\[name\].vue -A 2 -B 2

Repository: MatteoGabriele/agentscan

Length of output: 50


🏁 Script executed:

rg "defineProps|user:" app/components/Analysis/Card.vue -B 2 -A 2

Repository: MatteoGabriele/agentscan

Length of output: 191


File path is incorrect and query should use reactive properties.

The file is app/components/Analysis/Card.vue (not UserAnalysisCard.vue).

On lines 11-21, the query object containing created_at and repos_count should be a computed property so it re-fetches when those fields change. Currently it's a plain object, and watch only tracks username, so if other user properties update while the component stays mounted, the query becomes stale. The key should also be reactive to cache correctly.

Suggested change
+const analysisQuery = computed(() => ({
+  created_at: props.user.created_at,
+  repos_count: props.user.public_repos,
+}));
+
+const analysisKey = computed(
+  () =>
+    `analysis:${username.value}:${props.user.created_at}:${props.user.public_repos}`,
+);
+
 const { data, status, error } = useFetch(
   () => `/api/identify-replicant/${username.value}`,
   {
-    query: {
-      created_at: props.user.created_at,
-      repos_count: props.user.public_repos,
-    },
-    key: `analysis:${username.value}`,
-    watch: [username],
+    query: analysisQuery,
+    key: analysisKey,
+    watch: [username, () => props.user.created_at, () => props.user.public_repos],
     lazy: true,
   },
 );
📝 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 { data, status, error } = useFetch(
() => `/api/identify-replicant/${username.value}`,
{
query: {
created_at: props.user.created_at,
repos_count: props.user.public_repos,
},
key: `analysis:${username.value}`,
watch: [username],
lazy: true,
},
const analysisQuery = computed(() => ({
created_at: props.user.created_at,
repos_count: props.user.public_repos,
}));
const analysisKey = computed(
() =>
`analysis:${username.value}:${props.user.created_at}:${props.user.public_repos}`,
);
const { data, status, error } = useFetch(
() => `/api/identify-replicant/${username.value}`,
{
query: analysisQuery,
key: analysisKey,
watch: [username, () => props.user.created_at, () => props.user.public_repos],
lazy: true,
},
);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/components/UserAnalysisCard.vue` around lines 11 - 21, The fetch call
using useFetch should be made reactive: replace the static query object and
static key with computed properties so changes to props.user.created_at,
props.user.public_repos, and username.value trigger refetch/cache variations;
specifically, create a computed (e.g., queryParams) that returns { created_at:
props.user.created_at, repos_count: props.user.public_repos } and use a computed
key (e.g., analysisKey) like
`analysis:${username.value}:${props.user.created_at}:${props.user.public_repos}`,
then pass query: queryParams.value (or the computed directly if useFetch accepts
it) and key: analysisKey to useFetch and update watch to include any reactive
refs used (username and the computed) so the request re-runs when those user
properties change.

Comment on lines +42 to +43
const score = computed<number>(() => data.value?.analysis.score ?? 0);
const { classificationDetails } = useClassificationDetails(score);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Community flags do not override the headline state.

scoreClasses treats hasCommunityFlag as a top-level warning, but classificationDetails and classificationIcon still derive only from score. A community-flagged account can therefore render a red card headed by the growth icon and “Organic activity”.

Also applies to: 45-87

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

In `@app/components/UserAnalysisCard.vue` around lines 42 - 43, The classification
UI functions ignore community flags: compute hasCommunityFlag from data.value
(e.g., data.value?.flags?.community) and ensure useClassificationDetails and
classificationIcon receive that flag (or update useClassificationDetails to
accept a second parameter) so classificationDetails and classificationIcon
derive state from both score and hasCommunityFlag; also ensure scoreClasses
still treats hasCommunityFlag as top-level but that
classificationDetails/classificationIcon logic short-circuits to the
community-flagged state when hasCommunityFlag is true.

Comment on lines +109 to +117
<span
class="flex gap-2 items-center mb-2"
:class="scoreClasses.text"
>
<span :class="classificationIcon" class="text-base" />
<h3 class="text-xl font-mono">
{{ classificationDetails.label }}
</h3>
</span>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Use valid heading markup here.

The wrapper is a <span>, but it contains an <h3>. Swap it to a block container or move the icon into the heading to avoid invalid HTML.

💡 Suggested change
-            <span
+            <div
               class="flex gap-2 items-center mb-2"
               :class="scoreClasses.text"
             >
               <span :class="classificationIcon" class="text-base" />
               <h3 class="text-xl font-mono">
                 {{ classificationDetails.label }}
               </h3>
-            </span>
+            </div>
📝 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
<span
class="flex gap-2 items-center mb-2"
:class="scoreClasses.text"
>
<span :class="classificationIcon" class="text-base" />
<h3 class="text-xl font-mono">
{{ classificationDetails.label }}
</h3>
</span>
<div
class="flex gap-2 items-center mb-2"
:class="scoreClasses.text"
>
<span :class="classificationIcon" class="text-base" />
<h3 class="text-xl font-mono">
{{ classificationDetails.label }}
</h3>
</div>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/components/UserAnalysisCard.vue` around lines 109 - 117, The wrapper
currently uses a <span> that contains an <h3> (invalid HTML); change the outer
element in the block that renders the classification (the element using
:class="scoreClasses.text") from a span to a block-level container (e.g., div)
or alternatively move the icon (classificationIcon) into the <h3> so the heading
contains both the icon and the label (keep the existing classes like "flex gap-2
items-center mb-2" and "text-base" applied appropriately); update the template
around classificationIcon and {{ classificationDetails.label }} so the markup is
semantically correct and styling is preserved.

Comment on lines +33 to +35
<ul
class="text-gh-muted @md:mt-4 text-sm hidden @xl:flex flex-wrap gap-4 flex-row"
>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

This hides the stats row below @xl.

hidden @XL:flex removes followers, repos, and “Since” from mobile and most desktop widths, and there isn't an alternate rendering in this component.

💡 Suggested change
-        class="text-gh-muted `@md`:mt-4 text-sm hidden `@xl`:flex flex-wrap gap-4 flex-row"
+        class="text-gh-muted `@md`:mt-4 text-sm flex flex-wrap gap-4 flex-row"
📝 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
<ul
class="text-gh-muted @md:mt-4 text-sm hidden @xl:flex flex-wrap gap-4 flex-row"
>
<ul
class="text-gh-muted `@md`:mt-4 text-sm flex flex-wrap gap-4 flex-row"
>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/components/UserCard.vue` around lines 33 - 35, The stats row is being
hidden at all sizes because the ul uses the global "hidden" utility alongside
"@xl:flex"; remove the "hidden" class (or replace it with a size-scoped hidden
like "@sm:hidden" if you need to hide only on certain breakpoints) so the <ul>
with class "text-gh-muted `@md`:mt-4 text-sm hidden `@xl`:flex flex-wrap gap-4
flex-row" in UserCard.vue renders the followers/repos/Since at non-xl sizes;
ensure the final class string includes a base "flex" (or no hidden) and the
desired breakpoint-specific utilities.

Comment on lines +16 to +17
<p class="text-gh-muted">
{{ error.data?.message }}
Copy link
Copy Markdown

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:

# Find and read UserGenericError.vue
find . -name "UserGenericError.vue" -type f 2>/dev/null | head -5

Repository: MatteoGabriele/agentscan

Length of output: 50


🏁 Script executed:

# Also locate the other referenced components
find . -name "UserError.vue" -o -name "UserAnalysisCard.vue" 2>/dev/null

Repository: MatteoGabriele/agentscan

Length of output: 50


🏁 Script executed:

# Check the structure of app/components directory
git ls-files app/components/ | grep -E "\.vue$" | head -20

Repository: MatteoGabriele/agentscan

Length of output: 396


🏁 Script executed:

# Read the GenericError.vue file
cat -n app/components/GenericError.vue

Repository: MatteoGabriele/agentscan

Length of output: 597


🏁 Script executed:

# Read User/Error.vue file
cat -n app/components/User/Error.vue

Repository: MatteoGabriele/agentscan

Length of output: 760


🏁 Script executed:

# Read Analysis/Card.vue file - checking size first
wc -l app/components/Analysis/Card.vue

Repository: MatteoGabriele/agentscan

Length of output: 104


🏁 Script executed:

# Read Analysis/Card.vue around line 100
sed -n '90,110p' app/components/Analysis/Card.vue | cat -n

Repository: MatteoGabriele/agentscan

Length of output: 780


🏁 Script executed:

# Search for GenericError usage throughout the codebase
rg "GenericError" app/components/ -A 1 -B 1

Repository: MatteoGabriele/agentscan

Length of output: 442


🏁 Script executed:

# Search for error handling patterns and how errors are passed
rg "useFetch\|error\?" app/components/Analysis/Card.vue -B 5 -A 3 | head -40

Repository: MatteoGabriele/agentscan

Length of output: 50


🏁 Script executed:

# Check the main page files to understand error object structure
find app/pages -name "*.vue" -type f | head -10

Repository: MatteoGabriele/agentscan

Length of output: 168


🏁 Script executed:

# Search for error handling in composables
rg "error\.data" app/ -t vue -B 2 -A 2

Repository: MatteoGabriele/agentscan

Length of output: 95


🏁 Script executed:

# Check error handling in pages without specifying file type
rg "error\.data\|useFetch" app/pages/ -A 3 -B 2 | head -50

Repository: MatteoGabriele/agentscan

Length of output: 50


🏁 Script executed:

# Look for error object structure in composables
find app -name "*.ts" -o -name "*.js" | xargs grep -l "error\|useFetch" 2>/dev/null | head -10

Repository: MatteoGabriele/agentscan

Length of output: 50


🏁 Script executed:

# Search for how errors are used more broadly
rg "error\.data" app/ -A 2 -B 2 | head -60

Repository: MatteoGabriele/agentscan

Length of output: 308


🏁 Script executed:

# Check the Analysis/Card.vue for the useFetch hook to understand error structure
head -50 app/components/Analysis/Card.vue | cat -n

Repository: MatteoGabriele/agentscan

Length of output: 1759


🏁 Script executed:

# Look for composables that handle data fetching
find app -name "*.ts" -path "*/composables/*" | head -20

Repository: MatteoGabriele/agentscan

Length of output: 179


🏁 Script executed:

# Check if there's any documentation about error handling
rg "error" app/composables/ -A 2 -B 1 | head -50

Repository: MatteoGabriele/agentscan

Length of output: 50


🏁 Script executed:

# Check User/[name].vue page to understand the overall error flow
cat -n app/pages/user/[name].vue | head -60

Repository: MatteoGabriele/agentscan

Length of output: 1267


🏁 Script executed:

# Look for how errors are actually structured coming from API
rg "data\.message\|\.message" app/pages/ -B 3 -A 1 | head -40

Repository: MatteoGabriele/agentscan

Length of output: 50


🏁 Script executed:

# Check how useFetch errors are structured in Nuxt documentation context
# Look for any error handling utilities or patterns in the codebase
find app -name "*.ts" -o -name "*.js" | xargs grep -l "error\." 2>/dev/null | head -5

Repository: MatteoGabriele/agentscan

Length of output: 50


🏁 Script executed:

# Check the API endpoints to understand what error responses look like
find app/server -o -find . -path "*/api/*" -name "*.ts" 2>/dev/null | head -10

Repository: MatteoGabriele/agentscan

Length of output: 50


🏁 Script executed:

# Search for any error response patterns in the codebase
rg "message\|error" app/server -B 2 -A 2 | head -50

Repository: MatteoGabriele/agentscan

Length of output: 118


🏁 Script executed:

# List the directory structure to understand the codebase layout
ls -la app/

Repository: MatteoGabriele/agentscan

Length of output: 467


🏁 Script executed:

# Search for server/api files
find . -path "*/server/*" -o -path "*/api/*" 2>/dev/null | grep -E "\.(ts|js)$" | head -15

Repository: MatteoGabriele/agentscan

Length of output: 278


🏁 Script executed:

# Check package.json or nuxt config for the framework being used
cat nuxt.config.ts | head -30

Repository: MatteoGabriele/agentscan

Length of output: 655


🏁 Script executed:

# Read the API endpoint that's being called from Analysis/Card.vue
cat -n server/api/identify-replicant/[username].get.ts

Repository: MatteoGabriele/agentscan

Length of output: 3155


🏁 Script executed:

# Read the other API endpoint from User page
cat -n server/api/account/[username].get.ts

Repository: MatteoGabriele/agentscan

Length of output: 1537


🏁 Script executed:

# Search for how useFetch errors are handled in Nuxt context
rg "useFetch" app/ -A 5 | grep -A 5 "error"

Repository: MatteoGabriele/agentscan

Length of output: 779


🏁 Script executed:

# Check if there are any tests that show error structure
find . -name "*.test.*" -o -name "*.spec.*" 2>/dev/null | head -10

Repository: MatteoGabriele/agentscan

Length of output: 50


🏁 Script executed:

# Look for any documentation or comments about error handling
rg "error\.data\|error\.message\|error\?" app/ -B 1 -A 1 | head -60

Repository: MatteoGabriele/agentscan

Length of output: 50


🏁 Script executed:

# Check the exact line numbers for GenericError and the surrounding components
sed -n '1,20p' app/components/GenericError.vue | cat -n

Repository: MatteoGabriele/agentscan

Length of output: 597


🏁 Script executed:

# Verify the exact lines where GenericError is used in both components
grep -n "GenericError" app/components/User/Error.vue app/components/Analysis/Card.vue

Repository: MatteoGabriele/agentscan

Length of output: 214


Add a non-empty fallback message to GenericError.vue.

This component only renders error.data?.message. Even though the API endpoints define error messages, edge cases like network failures or error serialization issues could result in error.data being undefined, leaving this fallback component blank.

💡 Suggested change
     <p class="text-gh-muted">
-      {{ error.data?.message }}
+      {{ error.data?.message || "Something went wrong. Please try again." }}
     </p>
📝 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
<p class="text-gh-muted">
{{ error.data?.message }}
<p class="text-gh-muted">
{{ error.data?.message || "Something went wrong. Please try again." }}
</p>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/components/UserGenericError.vue` around lines 16 - 17, The component
currently only displays error.data?.message which can be undefined leaving the
UI blank; update the render logic in UserGenericError.vue (the template that
references error.data?.message) to provide a non-empty fallback such as using
error.data?.message || error.message || 'An unexpected error occurred' (or
similar user-friendly text) so that network failures or serialization issues
still show a meaningful message; ensure the fallback is used wherever
error.data?.message is referenced in this component.

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

♻️ Duplicate comments (1)
app/pages/user/[name].vue (1)

17-21: ⚠️ Potential issue | 🟠 Major

Still unresolved: the user fetch key is static.

Line 20 is still evaluated once at setup, so when the same page instance is reused for another name param, useFetch refetches the new URL but keeps sharing the original async-data slot. That can bleed profile state across navigations. Please verify this against Nuxt useFetch key behavior before merge.

💡 Suggested change
+const accountKey = computed(() => `account:${username.value}`);
+
 const { data: user, error } = await useFetch(
   () => `/api/account/${username.value}`,
   {
-    key: `account:${username.value}`,
+    key: accountKey,
     watch: [username],
   },
 );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/pages/user/`[name].vue around lines 17 - 21, The fetch key passed to
useFetch is currently a static string evaluated once and causes async-data to be
shared across navigations; change the key to be reactive by providing a function
or a computed that returns `account:${username.value}` (e.g., use key: () =>
`account:${username.value}` or key: computed(() => `account:${username.value}`))
so the key updates when username changes, ensuring the useFetch call (involving
useFetch, username, and the user/error binding) creates distinct async-data
slots per route and prevents profile state bleed.
🤖 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/Analysis/Card.vue`:
- Around line 11-21: The async-data cache key for useFetch is static and causes
shared state across users; make the key reactive to username by replacing the
plain string `key: \`analysis:${username.value}\`` with a reactive/computed key
(or function) that derives from `username` (e.g., use a computed or a function
that returns `analysis:${username.value}`) so each username gets its own cache
entry; update the useFetch call that defines `key` to reference this reactive
key to prevent stale/mismatched analysis data when switching users.

In `@app/components/GenericError.vue`:
- Around line 16-18: The GenericError.vue component currently renders an empty
paragraph when error.data?.message is missing; update the template logic in
GenericError.vue (where error.data?.message is referenced) to conditionally
render the <p class="text-gh-muted"> only when error.data?.message is non-empty
(or provide a fallback string like "An unexpected error occurred" when absent)
so the error card shows useful feedback or hides the empty message instead of
rendering a blank card.

---

Duplicate comments:
In `@app/pages/user/`[name].vue:
- Around line 17-21: The fetch key passed to useFetch is currently a static
string evaluated once and causes async-data to be shared across navigations;
change the key to be reactive by providing a function or a computed that returns
`account:${username.value}` (e.g., use key: () => `account:${username.value}` or
key: computed(() => `account:${username.value}`)) so the key updates when
username changes, ensuring the useFetch call (involving useFetch, username, and
the user/error binding) creates distinct async-data slots per route and prevents
profile state bleed.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9dc89b30-955b-4886-817f-f0a34afc12e0

📥 Commits

Reviewing files that changed from the base of the PR and between e888169 and 3ec097d.

📒 Files selected for processing (11)
  • app/components/Analysis/Card.vue
  • app/components/Analysis/CardSkeleton.vue
  • app/components/Analysis/Form.vue
  • app/components/GenericError.vue
  • app/components/Main/Footer.vue
  • app/components/Main/Logo.vue
  • app/components/User/Card.vue
  • app/components/User/Error.vue
  • app/components/Verified/AutomationList.vue
  • app/pages/index.vue
  • app/pages/user/[name].vue

Comment on lines +16 to +18
<p class="text-gh-muted">
{{ error.data?.message }}
</p>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Don’t render an empty error card when data.message is missing.

This shared component is used by both app/components/User/Error.vue and app/components/Analysis/Card.vue, but useFetch errors do not always populate error.data?.message. In those cases, Lines 16-18 render a blank card instead of actionable feedback.

💡 Suggested change
-    <p class="text-gh-muted">
-      {{ error.data?.message }}
-    </p>
+    <p class="text-gh-muted">
+      {{
+        error.data?.message ??
+        error.statusMessage ??
+        "Something went wrong while loading this section."
+      }}
+    </p>
📝 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
<p class="text-gh-muted">
{{ error.data?.message }}
</p>
<p class="text-gh-muted">
{{
error.data?.message ??
error.statusMessage ??
"Something went wrong while loading this section."
}}
</p>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/components/GenericError.vue` around lines 16 - 18, The GenericError.vue
component currently renders an empty paragraph when error.data?.message is
missing; update the template logic in GenericError.vue (where
error.data?.message is referenced) to conditionally render the <p
class="text-gh-muted"> only when error.data?.message is non-empty (or provide a
fallback string like "An unexpected error occurred" when absent) so the error
card shows useful feedback or hides the empty message instead of rendering a
blank card.

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.

🧹 Nitpick comments (1)
app/components/Analysis/Card.vue (1)

99-101: Consider skeleton display during initial fetch.

status === 'pending' with lazy: true means the skeleton shows only while the fetch is in-flight. However, since the fetch starts lazily after the component mounts, there may be a brief moment where neither skeleton nor data displays. This is typically acceptable, but worth verifying the UX during initial page load.

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

In `@app/components/Analysis/Card.vue` around lines 99 - 101, The skeleton can be
missing briefly because lazy: true delays the fetch; update the template so
AnalysisCardSkeleton renders for both initial idle and in-flight states (e.g.,
change the v-if from status === 'pending' to status === 'pending' || status ===
'idle') or alternatively call the lazy fetch trigger on mounted (invoke the
composable's trigger/startFetch method) so status leaves 'idle' immediately;
reference status, AnalysisCardSkeleton, GenericError and the lazy fetch trigger
when making the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@app/components/Analysis/Card.vue`:
- Around line 99-101: The skeleton can be missing briefly because lazy: true
delays the fetch; update the template so AnalysisCardSkeleton renders for both
initial idle and in-flight states (e.g., change the v-if from status ===
'pending' to status === 'pending' || status === 'idle') or alternatively call
the lazy fetch trigger on mounted (invoke the composable's trigger/startFetch
method) so status leaves 'idle' immediately; reference status,
AnalysisCardSkeleton, GenericError and the lazy fetch trigger when making the
change.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c7e454e5-1ba6-4357-9317-b021f263ef63

📥 Commits

Reviewing files that changed from the base of the PR and between 3ec097d and b167106.

📒 Files selected for processing (2)
  • app/components/Analysis/Card.vue
  • app/pages/user/[name].vue

@MatteoGabriele MatteoGabriele merged commit 8f8cd45 into main Mar 8, 2026
5 checks passed
@MatteoGabriele MatteoGabriele deleted the dev branch March 10, 2026 07:52
This was referenced Mar 10, 2026
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