Skip to content

feat(ui): add keytrace-style identity visualizer (poc)#2616

Open
BittuBarnwal7479 wants to merge 23 commits intonpmx-dev:mainfrom
BittuBarnwal7479:feat/adopt-keytrace
Open

feat(ui): add keytrace-style identity visualizer (poc)#2616
BittuBarnwal7479 wants to merge 23 commits intonpmx-dev:mainfrom
BittuBarnwal7479:feat/adopt-keytrace

Conversation

@BittuBarnwal7479
Copy link
Copy Markdown
Contributor

@BittuBarnwal7479 BittuBarnwal7479 commented Apr 23, 2026

🔗 Linked issue

resolves #2583


🧭 Context

This PR started as a UI POC. It is now updated to integrate real Keytrace claim data on /profile/{domain}.


📚 Description

What changed?

  • Integrated real Keytrace claim fetching via @keytrace/claims
  • Profile header now renders real identity data (name/avatar; banner support in UI)
  • Linked accounts now come from real claims (not synthetic account generation)
  • Reverify flow is wired end-to-end to real claims
  • Added safer proofMethod mapping with validation (no unsafe cast)
  • Fallback behavior is now explicit:
    • real data when available
    • no-claims state when user has no claims
    • service-unavailable state when Keytrace cannot be reached
    • removed misleading mock profile fallback

Verification

  • Tested real handle lookup (example: zeu.dev) returns verified claims
  • Confirmed profile page renders linked accounts from live Keytrace data
  • Confirmed no-claims and unavailable states show empty/non-misleading account output
  • Reverify updates status/last-checked using real claim matching

Notes

  • This PR remains read-only UI (no linking flow creation yet)
  • Account-link creation/verification authoring flow can be follow-up work

@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 23, 2026

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

Project Deployment Actions Updated (UTC)
npmx.dev Ready Ready Preview, Comment Apr 26, 2026 8:16pm
2 Skipped Deployments
Project Deployment Actions Updated (UTC)
docs.npmx.dev Ignored Ignored Preview Apr 26, 2026 8:16pm
npmx-lunaria Ignored Ignored Apr 26, 2026 8:16pm

Request Review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 23, 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

Integrates Keytrace profile linking functionality into the application by adding Vue components for displaying linked accounts, API endpoints for fetching profile data and reverifying accounts, TypeScript types for type safety, internationalised UI text across 30+ locales, and server utilities for platform mapping and verification status determination.

Changes

Cohort / File(s) Summary
Vue Components for Keytrace UI
app/components/AccountItem.vue, app/components/LinkedAccounts.vue, app/components/ProfileHeader.vue
Three new components displaying profile headers with avatar/banner fallbacks, linked accounts lists with verification status legends, and individual account items with re-verify functionality including progress indicators and error handling.
Profile Data Composable
app/composables/useKeytraceProfile.ts
New composable fetching Keytrace profile and accounts data, providing sorted/filtered computed properties (verified, non-verified, sorted by status priority and platform name).
Keytrace API Endpoints
server/api/keytrace/[domain].ts, server/api/keytrace/reverify.post.ts
Two server endpoints: first fetches profile and linked accounts from Keytrace service with fallback handling; second reverifies a single account and returns updated status/timestamps/failure reasons.
Keytrace Types and Utilities
shared/types/keytrace.ts, server/utils/keytrace.ts
Type definitions for profiles, accounts, verification statuses, proof methods, and reverification requests/responses; utility functions for bidirectional platform-to-claimtype mapping, claim field extraction, staleness determination (30-day threshold), and verification status computation.
Profile Page Integration
app/pages/profile/[identity]/index.vue
Modified profile page to integrate Keytrace profile header and linked accounts sections using the new composable, restructured likes section with new error and empty state messaging.
Internationalisation Updates
i18n/locales/{ar,az,bg,bn,cs,de,en,es,fr,hi,hu,id,it,ja,kn,mr,nb,ne,nl,pl,pt,ru,sr,ta,te,tr,uk,vi,zh}*.json, i18n/schema.json
Added profile-scoped UI text keys across 30 locale files for public interests descriptions, likes error/empty states, linked accounts titles/summaries/status labels, and avatar labels; removed deprecated common.error key; updated schema to enforce new keys.
Accessibility and Dependencies
test/nuxt/a11y.spec.ts, package.json
Added accessibility audit coverage for ProfileHeader, LinkedAccounts, and AccountItem components; added @keytrace/claims (v1.4.0) runtime dependency.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Browser Client
    participant Page as Profile Page
    participant Composable as useKeytraceProfile
    participant API as /api/keytrace/[domain]
    participant Keytrace as Keytrace Service
    
    Client->>Page: Navigate to profile/[identity]
    Page->>Composable: useKeytraceProfile(identity)
    Composable->>API: useFetch(/api/keytrace/{domain})
    API->>Keytrace: getClaimsForHandle(domain)
    Keytrace-->>API: ClaimVerificationResult[]
    API->>API: Map claims to KeytraceProfile + accounts
    API-->>Composable: KeytraceResponse
    Composable->>Composable: Compute sorted/filtered accounts
    Composable-->>Page: profile, accounts, loading state
    Page->>Page: Render ProfileHeader + LinkedAccounts
Loading
sequenceDiagram
    participant User as User
    participant AccountItem as AccountItem Component
    participant API as /api/keytrace/reverify
    participant Keytrace as Keytrace Service
    
    User->>AccountItem: Click "Re-verify" button
    AccountItem->>AccountItem: Show tooltip panel
    AccountItem->>AccountItem: Start 4-step progress indicator
    AccountItem->>API: POST reverify request
    API->>Keytrace: getClaimsForHandle(identity)
    Keytrace-->>API: Updated ClaimVerificationResult
    API->>API: Map claim to reverification response
    API-->>AccountItem: KeytraceReverifyResponse
    AccountItem->>AccountItem: Update status/lastCheckedAt/failureReason
    AccountItem->>AccountItem: Complete progress, close tooltip
    Note over User,AccountItem: On error: show error message, adjust auto-close delay
Loading

Suggested reviewers

  • serhalp
  • ghostdevv
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarises the main change: adding a keytrace-style identity visualizer UI component, which is the primary focus of this PR.
Description check ✅ Passed The description is directly related to the changeset, providing context about Keytrace integration, real claim data fetching, and the UI components added.
Linked Issues check ✅ Passed The PR fulfils the primary objectives from issue #2583: implements a Keytrace visualizer on profile pages displaying identity data and linked accounts from real Keytrace claims.
Out of Scope Changes check ✅ Passed All changes align with the stated objectives. The PR implements Keytrace visualisation and reverify flows (read-only), with account-linking creation properly deferred as future work.

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

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 23, 2026

Lunaria Status Overview

🌕 This pull request will trigger status changes.

Learn more

By default, every PR changing files present in the Lunaria configuration's files property will be considered and trigger status changes accordingly.

You can change this by adding one of the keywords present in the ignoreKeywords property in your Lunaria configuration file in the PR's title (ignoring all files) or by including a tracker directive in the merged commit's description.

Tracked Files

File Note
i18n/locales/ar-EG.json Localization changed, will be marked as complete. 🔄️
i18n/locales/ar.json Localization changed, will be marked as complete. 🔄️
i18n/locales/az-AZ.json Localization changed, will be marked as complete. 🔄️
i18n/locales/bg-BG.json Localization changed, will be marked as complete. 🔄️
i18n/locales/bn-IN.json Localization changed, will be marked as complete. 🔄️
i18n/locales/cs-CZ.json Localization changed, will be marked as complete. 🔄️
i18n/locales/de-AT.json Localization changed, will be marked as complete. 🔄️
i18n/locales/de-DE.json Localization changed, will be marked as complete. 🔄️
i18n/locales/de.json Localization changed, will be marked as complete. 🔄️
i18n/locales/en-GB.json Localization changed, will be marked as complete. 🔄️
i18n/locales/en.json Source changed, localizations will be marked as outdated.
i18n/locales/es-419.json Localization changed, will be marked as complete. 🔄️
i18n/locales/es.json Localization changed, will be marked as complete. 🔄️
i18n/locales/fr-FR.json Localization changed, will be marked as complete. 🔄️
i18n/locales/hi-IN.json Localization changed, will be marked as complete. 🔄️
i18n/locales/hu-HU.json Localization changed, will be marked as complete. 🔄️
i18n/locales/id-ID.json Localization changed, will be marked as complete. 🔄️
i18n/locales/it-IT.json Localization changed, will be marked as complete. 🔄️
i18n/locales/ja-JP.json Localization changed, will be marked as complete. 🔄️
i18n/locales/kn-IN.json Localization changed, will be marked as complete. 🔄️
i18n/locales/mr-IN.json Localization changed, will be marked as complete. 🔄️
i18n/locales/nb-NO.json Localization changed, will be marked as complete. 🔄️
i18n/locales/ne-NP.json Localization changed, will be marked as complete. 🔄️
i18n/locales/nl.json Localization changed, will be marked as complete. 🔄️
i18n/locales/pl-PL.json Localization changed, will be marked as complete. 🔄️
i18n/locales/pt-BR.json Localization changed, will be marked as complete. 🔄️
i18n/locales/ru-RU.json Localization changed, will be marked as complete. 🔄️
i18n/locales/sr-Latn-RS.json Localization changed, will be marked as complete. 🔄️
i18n/locales/ta-IN.json Localization changed, will be marked as complete. 🔄️
i18n/locales/te-IN.json Localization changed, will be marked as complete. 🔄️
i18n/locales/tr-TR.json Localization changed, will be marked as complete. 🔄️
i18n/locales/uk-UA.json Localization changed, will be marked as complete. 🔄️
i18n/locales/vi-VN.json Localization changed, will be marked as complete. 🔄️
i18n/locales/zh-CN.json Localization changed, will be marked as complete. 🔄️
i18n/locales/zh-TW.json Localization changed, will be marked as complete. 🔄️
Warnings reference
Icon Description
🔄️ The source for this localization has been updated since the creation of this pull request, make sure all changes in the source have been applied.

Copy link
Copy Markdown
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: 10

🧹 Nitpick comments (4)
CONTRIBUTING.md (1)

351-351: Documentation update correctly reflects the routing change.

Swapping the unplugin-vue-router reference for Nuxt's experimental typedPages option is consistent with the dependency changes in package.json. Minor nit: typedPages is flagged as experimental in Nuxt — it may be worth a brief parenthetical note so contributors know the type-safety guarantee depends on that flag being enabled in nuxt.config.ts.

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

In `@CONTRIBUTING.md` at line 351, Update the CONTRIBUTING.md note to mention that
Nuxt's typedPages option is experimental and that the type-safety guarantees for
using object-syntax named routes depend on enabling typedPages in
nuxt.config.ts; specifically, add a short parenthetical after the sentence that
recommends "object syntax with named routes" referencing the experimental nature
of the typedPages option and instructing contributors to enable typedPages in
nuxt.config.ts to get the typed route benefits.
.github/workflows/ci.yml (1)

117-118: Use $GITHUB_WORKSPACE instead of hard-coding the Actions workspace path.

Line 118 bakes in the current repository path. Using the runtime variable maintains compatibility if the repository name or checkout location changes.

Proposed change
       - name: 👑 Fix Git ownership
-        run: git config --global --add safe.directory /__w/npmx.dev/npmx.dev
+        run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/ci.yml around lines 117 - 118, Replace the hard-coded path
in the "👑 Fix Git ownership" step by using the GitHub Actions runtime variable:
update the git config --global --add safe.directory command to use
$GITHUB_WORKSPACE instead of the literal "/__w/npmx.dev/npmx.dev" so the
safe.directory is set dynamically for the checked-out repo.
app/components/LinkedAccounts.vue (1)

4-9: Legend colours bypass the app's design tokens.

Other components in the app style via semantic tokens (bg-bg-subtle, text-fg-muted, border-border) so they respond to accent/background theme settings. The raw palette utilities here (bg-emerald-500/15, text-yellow-300, etc.) will look identical across themes and may clash on light backgrounds. Consider introducing status-specific semantic classes or at minimum verifying contrast on both light and dark themes.

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

In `@app/components/LinkedAccounts.vue` around lines 4 - 9, The statusLegend array
uses raw Tailwind color utilities (statusLegend, className) which bypass the
app's semantic design tokens; replace these hardcoded palette classes with
semantic token-based classes (e.g., create status-specific utility classes like
.status-verified, .status-unverified, .status-stale, .status-failed that map to
tokens such as bg-bg-subtle / text-fg-muted / border-border or token variants
for each status) and update statusLegend.className to reference those semantic
classes so the badges respect theme/accent settings and verify contrast in both
light and dark themes.
app/composables/useKeytraceProfile.ts (1)

1-1: Rely on the project’s shared-type auto-imports here.

This composable is under app/composables, so the explicit #shared/types/keytrace import duplicates the repo’s Nuxt auto-import convention for shared/types/*.

♻️ Proposed cleanup
-import type { KeytraceAccount, KeytraceResponse } from '#shared/types/keytrace'
-

Based on learnings, “exports from shared/types/* … are auto-imported by Nuxt for composables and components. Do not add explicit import statements … in files under app/composables”.

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

In `@app/composables/useKeytraceProfile.ts` at line 1, The explicit import of
KeytraceAccount and KeytraceResponse at the top of useKeytraceProfile.ts
duplicates the repo's Nuxt auto-imports for shared/types in composables; remove
the line "import type { KeytraceAccount, KeytraceResponse } from
'#shared/types/keytrace'" and rely on the project-wide auto-imported types
(references to KeytraceAccount and KeytraceResponse inside the
useKeytraceProfile composable should continue to work without the explicit
import).
🤖 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/AccountItem.vue`:
- Around line 202-206: getStepState currently treats a step as 'active' when
currentVerificationStep === stepIndex AND (isReverifying OR reverifyError),
which keeps the spinner running after a failure; update the condition in
getStepState (and the duplicate logic around the other block) to require
isReverifying to be true and reverifyError to be false (e.g., use
isReverifying.value && !reverifyError.value alongside the
currentVerificationStep/stepIndex check) so the step becomes non-active when a
reverifyError is set.
- Around line 165-175: The responsePromise created from $fetch (typed
KeytraceReverifyResponse) may reject while the subsequent runStep() delay calls
execute, causing unhandled rejection warnings; attach a rejection handler
immediately by calling Promise.allSettled([responsePromise]) (or otherwise
attach .catch) before invoking runStep(0..3), then await the original
responsePromise later as before so your existing try/catch still handles the
outcome; update the code around responsePromise and the runStep sequence in
AccountItem.vue accordingly.

In `@app/components/Compare/FacetSelector.vue`:
- Around line 108-113: The hover styles conflict between the static class
attribute and the conditional :class for facet.comingSoon, causing enabled hover
utilities (hover:(text-fg-muted border-border)) to potentially override the
muted hover (hover:(text-fg-subtle/50 border-border-subtle)); fix by moving
hover:(text-fg-muted border-border) out of the base class and into the
non-coming-soon branch of the :class binding (so only the enabled branch adds
enabled hover utilities), or alternatively replace with a disabled: modifier
pattern consistent with Button/Base.vue and Select/Base.vue so coming-soon chips
only receive the muted hover styles via facet.comingSoon conditional logic.

In `@app/components/LinkedAccounts.vue`:
- Around line 22-49: Replace hardcoded UI strings in the LinkedAccounts.vue
template with i18n lookups: use $t('profile.linked_accounts.title') for "Linked
Accounts"; replace the verified summary line that uses
verifiedCount/accounts.length with a translated key like
$t('profile.linked_accounts.verified_summary', { verified: verifiedCount, total:
accounts.length }); map each entry in statusLegend to use
$t('profile.linked_accounts.status.<key>') for the four legend labels and use
$t('profile.linked_accounts.legend_label') for the container aria-label; replace
"No linked accounts" with $t('profile.linked_accounts.empty'); ensure you update
the template expressions around verifiedCount, accounts, statusLegend, loading,
SkeletonBlock and AccountItem to call $t(...) consistently with the new
profile.* keys added to i18n/locales/*.json.

In `@app/components/ProfileHeader.vue`:
- Around line 53-67: Replace the hardcoded English strings in ProfileHeader.vue
with i18n lookups: change the alt fallback "Profile avatar" and the name
fallback "Unknown Profile" to use this.$t (or $t) with dedicated keys (e.g.
profile.avatarAlt and profile.unknown) so they respect the active locale; update
any template references that use profile.name || 'Unknown Profile' and
:alt="profile.name || 'Profile avatar'" to use the translated fallbacks, and add
corresponding keys under the profile.* namespace in the translation files to
match the project's i18n pattern.

In `@app/pages/profile/`[identity]/index.vue:
- Around line 101-105: The Keytrace UI (ProfileHeader and LinkedAccounts) is
being rendered for all identity routes because useKeytraceProfile(identity) is
invoked unconditionally and the mock API returns synthesized profiles for
unknown domains; update the rendering to gate the Keytrace-related components by
detecting domain-like identities (e.g. a simple regex check on identity) or by
checking a real Keytrace signal before rendering: call
useKeytraceProfile(identity) only when identity matches a domain pattern (or add
a boolean like isDomainFromIdentity) and wrap ProfileHeader and LinkedAccounts
with that condition (references: useKeytraceProfile, ProfileHeader,
LinkedAccounts, identity) so non-domain handles do not show fabricated linked
accounts.

In `@i18n/locales/de-AT.json`:
- Around line 27-28: The strings for keys "likes_error" and "likes_empty"
currently say "Beliebte Pakete" (popular packages) but should refer to the
user's liked packages; update the translations for likes_error and likes_empty
so they use a German phrase meaning "liked packages" (e.g., use a localized term
such as "Gemerkte Pakete" or "Gelikte Pakete") to correctly reflect the
profile's liked-packages section.

In `@i18n/locales/te-IN.json`:
- Around line 126-128: The three Telugu locale entries
"public_interests_description", "likes_error", and "likes_empty" are still in
English; update their values to proper Telugu translations (replace the English
strings with Telugu equivalents) or remove these keys from te-IN.json until
translations are available so Telugu users don't see English UI; locate these
keys in the i18n/locales/te-IN.json file and either provide accurate Telugu text
for public_interests_description, likes_error, and likes_empty or revert/delete
them to keep the locale as a placeholder.

In `@server/api/keytrace/reverify.post.ts`:
- Around line 21-26: The current reverify handler always returns a
KeytraceReverifyResponse with status: 'unverified', which will incorrectly
downgrade verified accounts; update the handler that builds the response (the
code creating the response object in reverify.post) to preserve the account's
current status instead of hardcoding 'unverified' — e.g., read the incoming
request's currentStatus (or fetch the account's status from the same source the
UI uses) and set response.status = currentStatus, adjust failureReason to
null/empty when status is unchanged, and only set failureReason and 'unverified'
if a real verification attempt fails.

In `@test/e2e/interactions.spec.ts`:
- Around line 293-310: The test '"s" does not navigate when any modifier key is
pressed' is ambiguous because it focuses '#header-search' causing typed
modifiers like 'Shift+s' to insert text and potentially trigger navigation;
remove the focus and the subsequent expect(searchInput).toBeFocused() lines from
this test so it mirrors the '"c"' modifier test (no editable focused) and
reliably asserts the URL stays on /settings; if you need coverage for shortcuts
being suppressed while an editable is focused, add a separate test named e.g.
'"s" is suppressed while editable is focused' that focuses '#header-search' and
verifies an unmodified 's' does not trigger navigation.

---

Nitpick comments:
In @.github/workflows/ci.yml:
- Around line 117-118: Replace the hard-coded path in the "👑 Fix Git ownership"
step by using the GitHub Actions runtime variable: update the git config
--global --add safe.directory command to use $GITHUB_WORKSPACE instead of the
literal "/__w/npmx.dev/npmx.dev" so the safe.directory is set dynamically for
the checked-out repo.

In `@app/components/LinkedAccounts.vue`:
- Around line 4-9: The statusLegend array uses raw Tailwind color utilities
(statusLegend, className) which bypass the app's semantic design tokens; replace
these hardcoded palette classes with semantic token-based classes (e.g., create
status-specific utility classes like .status-verified, .status-unverified,
.status-stale, .status-failed that map to tokens such as bg-bg-subtle /
text-fg-muted / border-border or token variants for each status) and update
statusLegend.className to reference those semantic classes so the badges respect
theme/accent settings and verify contrast in both light and dark themes.

In `@app/composables/useKeytraceProfile.ts`:
- Line 1: The explicit import of KeytraceAccount and KeytraceResponse at the top
of useKeytraceProfile.ts duplicates the repo's Nuxt auto-imports for
shared/types in composables; remove the line "import type { KeytraceAccount,
KeytraceResponse } from '#shared/types/keytrace'" and rely on the project-wide
auto-imported types (references to KeytraceAccount and KeytraceResponse inside
the useKeytraceProfile composable should continue to work without the explicit
import).

In `@CONTRIBUTING.md`:
- Line 351: Update the CONTRIBUTING.md note to mention that Nuxt's typedPages
option is experimental and that the type-safety guarantees for using
object-syntax named routes depend on enabling typedPages in nuxt.config.ts;
specifically, add a short parenthetical after the sentence that recommends
"object syntax with named routes" referencing the experimental nature of the
typedPages option and instructing contributors to enable typedPages in
nuxt.config.ts to get the typed route benefits.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: b8efc060-b9db-4969-8d02-e8ed9e9c58f0

📥 Commits

Reviewing files that changed from the base of the PR and between 3c3bfeb and d142652.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (54)
  • .github/workflows/ci.yml
  • CONTRIBUTING.md
  • app/components/AccountItem.vue
  • app/components/Compare/FacetSelector.vue
  • app/components/LinkedAccounts.vue
  • app/components/ProfileHeader.vue
  • app/composables/useConnector.ts
  • app/composables/useKeytraceProfile.ts
  • app/pages/profile/[identity]/index.vue
  • docs/package.json
  • i18n/locales/ar-EG.json
  • i18n/locales/ar.json
  • i18n/locales/az-AZ.json
  • i18n/locales/bg-BG.json
  • i18n/locales/bn-IN.json
  • i18n/locales/cs-CZ.json
  • i18n/locales/de-AT.json
  • i18n/locales/de-DE.json
  • i18n/locales/de.json
  • i18n/locales/en-GB.json
  • i18n/locales/en.json
  • i18n/locales/es-419.json
  • i18n/locales/es.json
  • i18n/locales/fr-FR.json
  • i18n/locales/hi-IN.json
  • i18n/locales/hu-HU.json
  • i18n/locales/id-ID.json
  • i18n/locales/it-IT.json
  • i18n/locales/ja-JP.json
  • i18n/locales/kn-IN.json
  • i18n/locales/mr-IN.json
  • i18n/locales/nb-NO.json
  • i18n/locales/ne-NP.json
  • i18n/locales/nl.json
  • i18n/locales/pl-PL.json
  • i18n/locales/pt-BR.json
  • i18n/locales/ru-RU.json
  • i18n/locales/sr-Latn-RS.json
  • i18n/locales/ta-IN.json
  • i18n/locales/te-IN.json
  • i18n/locales/tr-TR.json
  • i18n/locales/uk-UA.json
  • i18n/locales/vi-VN.json
  • i18n/locales/zh-CN.json
  • i18n/locales/zh-TW.json
  • i18n/schema.json
  • knip.ts
  • lighthouse-setup.cjs
  • package.json
  • pnpm-workspace.yaml
  • server/api/keytrace/[domain].ts
  • server/api/keytrace/reverify.post.ts
  • shared/types/keytrace.ts
  • test/e2e/interactions.spec.ts
💤 Files with no reviewable changes (1)
  • knip.ts

Comment thread app/components/AccountItem.vue
Comment thread app/components/AccountItem.vue Outdated
Comment thread app/components/Compare/FacetSelector.vue Outdated
Comment thread app/components/LinkedAccounts.vue
Comment thread app/components/ProfileHeader.vue Outdated
Comment thread app/pages/profile/[identity]/index.vue Outdated
Comment thread i18n/locales/de-AT.json Outdated
Comment thread i18n/locales/te-IN.json Outdated
Comment thread server/api/keytrace/reverify.post.ts Outdated
Comment thread test/e2e/interactions.spec.ts Outdated
@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 23, 2026

Codecov Report

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

Files with missing lines Patch % Lines
app/components/AccountItem.vue 51.69% 47 Missing and 10 partials ⚠️
app/composables/useKeytraceProfile.ts 66.66% 5 Missing and 1 partial ⚠️

📢 Thoughts on this report? Let us know!

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
@BittuBarnwal7479 BittuBarnwal7479 changed the title feat(profile): add Keytrace-style identity visualizer (POC) feat(ui): add keytrace-style identity visualizer (poc) Apr 23, 2026
Copy link
Copy Markdown
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: 3

Caution

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

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

305-349: ⚠️ Potential issue | 🟡 Minor

Keep common.error while it is still referenced.

app/storybook/mocks/handlers/lunaria-status.ts still references common.error; removing it from the source locale will surface a missing translation key there unless the consumer is updated.

Proposed fix
   "common": {
+    "error": "Error",
     "loading": "Loading...",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@i18n/locales/en.json` around lines 305 - 349, Restore the removed translation
key by re-adding "error" under the "common" object in the locale (so
common.error exists) with an appropriate string (e.g., "Error") because
app/storybook/mocks/handlers/lunaria-status.ts still references common.error;
alternatively update that handler to use an existing key if you intend to remove
common.error, but do one of these so the missing translation key is resolved.
i18n/locales/de.json (1)

300-344: ⚠️ Potential issue | 🟠 Major

Restore common.error for existing translation consumers.

app/storybook/mocks/handlers/lunaria-status.ts:30-50 still requests common.error, so deleting the shared key risks missing translations in that mock path.

Proposed fix
   "common": {
     "loading": "Lädt...",
     "loading_more": "Lädt mehr...",
     "loading_packages": "Pakete werden geladen...",
     "end_of_results": "Keine weiteren Ergebnisse",
     "try_again": "Erneut versuchen",
+    "error": "Fehler",
     "close": "Schließen",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@i18n/locales/de.json` around lines 300 - 344, Restore the removed
"common.error" key in the German locale so existing consumers (e.g., the
lunaria-status mock that references common.error) continue to find a
translation; add an appropriate German string like "Fehler" or a full sentence
under the "common" object with the key "error" to mirror other locales, ensuring
the key name is exactly "common.error" so code paths referencing common.error
(such as the handler in app/storybook/mocks/handlers/lunaria-status.ts) work
without changes.
i18n/locales/ja-JP.json (1)

171-210: ⚠️ Potential issue | 🟠 Major

Restore common.error for existing translation consumers.

app/storybook/mocks/handlers/lunaria-status.ts:30-50 still includes common.error; removing it from this locale can make the mock translation payload fail or render a missing key.

Proposed fix
   "common": {
     "loading": "読み込み中...",
     "loading_more": "さらに読み込み中...",
     "loading_packages": "パッケージを読み込み中...",
     "end_of_results": "結果の最後です",
     "try_again": "もう一度お試しください",
+    "error": "エラー",
     "close": "閉じる",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@i18n/locales/ja-JP.json` around lines 171 - 210, The ja-JP locale removed the
common.error key which breaks consumers expecting it (storybook mock in
app/storybook/mocks/handlers/lunaria-status.ts references common.error); restore
the common.error string in the "common" object of i18n/locales/ja-JP.json with
an appropriate Japanese translation (e.g., "エラーが発生しました") so the mock translation
payload and any runtime lookups find the key.
🧹 Nitpick comments (2)
test/nuxt/a11y.spec.ts (1)

910-968: Consider expanding state coverage for the new a11y suites.

Current tests only exercise the "happy path" (populated profile, single verified GitHub account). Since ProfileHeader has distinct avatar/initials fallback branches and LinkedAccounts/AccountItem render differently per status (verified / unverified / stale / failed) and loading, adding a few more cases would catch real accessibility regressions (e.g. missing alt on the fallback avatar, colour-contrast issues on non-verified status badges, skeleton/loading ARIA).

Suggested additional cases:

  • ProfileHeader: loading: true, and a profile with no avatar (initials fallback).
  • LinkedAccounts: empty accounts: [], loading: true, and a mix containing unverified/stale/failed statuses.
  • AccountItem: one test per non-verified status.

Not blocking for the POC, but cheap to add here so the audit actually covers the new visual states.

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

In `@test/nuxt/a11y.spec.ts` around lines 910 - 968, Add a few more a11y test
cases to exercise non-happy-path states: for ProfileHeader add tests for
loading: true and for a profile without an avatar (initials fallback) using
mountSuspended(ProfileHeader) and runAxe; for LinkedAccounts add tests for
accounts: [] (empty list), loading: true, and a mixed list containing statuses
'unverified', 'stale', and 'failed'; for AccountItem add separate tests
rendering AccountItem for each non-verified status
('unverified','stale','failed') and run runAxe against each mounted component to
assert no violations. Ensure each test uses the same mountSuspended/props
pattern so the axe audit covers fallback avatar, status badges, and loading
skeleton ARIA states.
i18n/locales/de-AT.json (1)

24-43: Avoid duplicating base German strings in the Austrian variant.

These profile entries are identical to de.json, so de-AT.json can inherit them from the base locale and only override Austria-specific wording.

Proposed cleanup
-  "search": {
+  "search": {
     "instant_search": "Schnellsuche",
     "instant_search_on": "Schnellsuche aktiviert",
     "instant_search_off": "Schnellsuche deaktiviert",
     "instant_search_turn_on": "Schnellsuche aktivieren",
     "instant_search_turn_off": "Schnellsuche deaktivieren",
     "instant_search_advisory": "Die Schnellsuche sendet bei jedem Tastendruck eine Anfrage."
-  },
-  "profile": {
-    "public_interests_description": "Öffentliche Paketinteressen und Aktivitätssignale.",
-    "likes_error": "Gelikte Pakete konnten nicht geladen werden.",
-    "likes_empty": "Noch keine gelikten Pakete.",
-    "avatar_alt": "Profilavatar",
-    "unknown_profile": "Unbekanntes Profil",
-    "linked_accounts": {
-      "status": {
-        "verified": "Verifiziert",
-        "unverified": "Nicht verifiziert",
-        "stale": "Veraltet",
-        "failed": "Fehlgeschlagen"
-      },
-      "title": "Verknüpfte Konten",
-      "verified_summary": "Verifizierte Konten",
-      "legend_aria_label": "Legende zum Verifizierungsstatus von Konten",
-      "empty": "Keine verknüpften Konten"
-    }
   }

Based on learnings, “when using country locale variants (e.g., es-419, es-ES), place only translations that differ from the base language in variant JSON files.”

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

In `@i18n/locales/de-AT.json` around lines 24 - 43, The de-AT locale duplicates
the entire "profile" block from the base German locale; remove the redundant
keys under "profile" in de-AT.json and leave only Austria-specific overrides (if
none, remove the "profile" object entirely) so the app falls back to de.json for
shared strings; target the "profile" object and its nested keys like
"public_interests_description", "likes_error", "likes_empty", "avatar_alt",
"unknown_profile", and the "linked_accounts" block when pruning duplicates.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@i18n/locales/de-DE.json`:
- Around line 16-17: The German locale entry "verified_summary" currently is a
static label and must include the interpolation placeholders used by
app/components/LinkedAccounts.vue; update the "verified_summary" value in
i18n/locales/de-DE.json to include the {verified} and {total} placeholders (e.g.
a German phrase using {verified} and {total}) so the component's passed values
are rendered in the UI.

In `@i18n/locales/en.json`:
- Around line 378-379: The "verified_summary" translation currently returns a
static label; update the en.json "verified_summary" value to include
interpolation placeholders for the counts (e.g., {verified} and {total}) so the
string uses the { verified, total } values passed from
app/components/LinkedAccounts.vue; keep the placeholder names matching what
LinkedAccounts.vue supplies and preserve pluralization/formatting conventions
used by your i18n setup.

In `@i18n/schema.json`:
- Around line 1114-1130: The AccountItem.vue component is still using hardcoded
English labels for account status; update it to read the new i18n keys under
profile.linked_accounts.status (e.g. profile.linked_accounts.status.verified,
.unverified, .stale, .failed) instead of literal strings. Locate the status
display logic in AccountItem.vue (the template or computed that currently
returns "Verified", "Unverified", "Stale", "Failed") and replace those literals
with calls to the translation helper (e.g. this.$t or $t in the template) so the
UI uses profile.linked_accounts.status.* keys for all four statuses. Ensure any
mapping logic (status code -> label) uses the translation keys consistently and
falls back to a safe default if a key is missing.

---

Outside diff comments:
In `@i18n/locales/de.json`:
- Around line 300-344: Restore the removed "common.error" key in the German
locale so existing consumers (e.g., the lunaria-status mock that references
common.error) continue to find a translation; add an appropriate German string
like "Fehler" or a full sentence under the "common" object with the key "error"
to mirror other locales, ensuring the key name is exactly "common.error" so code
paths referencing common.error (such as the handler in
app/storybook/mocks/handlers/lunaria-status.ts) work without changes.

In `@i18n/locales/en.json`:
- Around line 305-349: Restore the removed translation key by re-adding "error"
under the "common" object in the locale (so common.error exists) with an
appropriate string (e.g., "Error") because
app/storybook/mocks/handlers/lunaria-status.ts still references common.error;
alternatively update that handler to use an existing key if you intend to remove
common.error, but do one of these so the missing translation key is resolved.

In `@i18n/locales/ja-JP.json`:
- Around line 171-210: The ja-JP locale removed the common.error key which
breaks consumers expecting it (storybook mock in
app/storybook/mocks/handlers/lunaria-status.ts references common.error); restore
the common.error string in the "common" object of i18n/locales/ja-JP.json with
an appropriate Japanese translation (e.g., "エラーが発生しました") so the mock translation
payload and any runtime lookups find the key.

---

Nitpick comments:
In `@i18n/locales/de-AT.json`:
- Around line 24-43: The de-AT locale duplicates the entire "profile" block from
the base German locale; remove the redundant keys under "profile" in de-AT.json
and leave only Austria-specific overrides (if none, remove the "profile" object
entirely) so the app falls back to de.json for shared strings; target the
"profile" object and its nested keys like "public_interests_description",
"likes_error", "likes_empty", "avatar_alt", "unknown_profile", and the
"linked_accounts" block when pruning duplicates.

In `@test/nuxt/a11y.spec.ts`:
- Around line 910-968: Add a few more a11y test cases to exercise non-happy-path
states: for ProfileHeader add tests for loading: true and for a profile without
an avatar (initials fallback) using mountSuspended(ProfileHeader) and runAxe;
for LinkedAccounts add tests for accounts: [] (empty list), loading: true, and a
mixed list containing statuses 'unverified', 'stale', and 'failed'; for
AccountItem add separate tests rendering AccountItem for each non-verified
status ('unverified','stale','failed') and run runAxe against each mounted
component to assert no violations. Ensure each test uses the same
mountSuspended/props pattern so the axe audit covers fallback avatar, status
badges, and loading skeleton ARIA states.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: ab559b41-ee63-4e79-a588-a0829e9bca5d

📥 Commits

Reviewing files that changed from the base of the PR and between d142652 and ad9e428.

📒 Files selected for processing (46)
  • app/components/AccountItem.vue
  • app/components/Compare/FacetSelector.vue
  • app/components/LinkedAccounts.vue
  • app/components/ProfileHeader.vue
  • app/composables/useKeytraceProfile.ts
  • app/pages/profile/[identity]/index.vue
  • i18n/locales/ar-EG.json
  • i18n/locales/ar.json
  • i18n/locales/az-AZ.json
  • i18n/locales/bg-BG.json
  • i18n/locales/bn-IN.json
  • i18n/locales/cs-CZ.json
  • i18n/locales/de-AT.json
  • i18n/locales/de-DE.json
  • i18n/locales/de.json
  • i18n/locales/en-GB.json
  • i18n/locales/en.json
  • i18n/locales/es-419.json
  • i18n/locales/es.json
  • i18n/locales/fr-FR.json
  • i18n/locales/hi-IN.json
  • i18n/locales/hu-HU.json
  • i18n/locales/id-ID.json
  • i18n/locales/it-IT.json
  • i18n/locales/ja-JP.json
  • i18n/locales/kn-IN.json
  • i18n/locales/mr-IN.json
  • i18n/locales/nb-NO.json
  • i18n/locales/ne-NP.json
  • i18n/locales/nl.json
  • i18n/locales/pl-PL.json
  • i18n/locales/pt-BR.json
  • i18n/locales/ru-RU.json
  • i18n/locales/sr-Latn-RS.json
  • i18n/locales/ta-IN.json
  • i18n/locales/te-IN.json
  • i18n/locales/tr-TR.json
  • i18n/locales/uk-UA.json
  • i18n/locales/vi-VN.json
  • i18n/locales/zh-CN.json
  • i18n/locales/zh-TW.json
  • i18n/schema.json
  • server/api/keytrace/[domain].ts
  • server/api/keytrace/reverify.post.ts
  • shared/types/keytrace.ts
  • test/nuxt/a11y.spec.ts
✅ Files skipped from review due to trivial changes (10)
  • i18n/locales/kn-IN.json
  • i18n/locales/it-IT.json
  • i18n/locales/te-IN.json
  • i18n/locales/en-GB.json
  • i18n/locales/ta-IN.json
  • i18n/locales/es-419.json
  • i18n/locales/id-ID.json
  • app/components/LinkedAccounts.vue
  • shared/types/keytrace.ts
  • app/pages/profile/[identity]/index.vue
🚧 Files skipped from review as they are similar to previous changes (23)
  • i18n/locales/bn-IN.json
  • i18n/locales/ne-NP.json
  • i18n/locales/es.json
  • i18n/locales/nl.json
  • i18n/locales/ru-RU.json
  • i18n/locales/pt-BR.json
  • i18n/locales/mr-IN.json
  • i18n/locales/fr-FR.json
  • i18n/locales/hi-IN.json
  • i18n/locales/zh-CN.json
  • i18n/locales/pl-PL.json
  • i18n/locales/tr-TR.json
  • i18n/locales/ar-EG.json
  • server/api/keytrace/reverify.post.ts
  • app/components/Compare/FacetSelector.vue
  • server/api/keytrace/[domain].ts
  • app/components/ProfileHeader.vue
  • i18n/locales/az-AZ.json
  • i18n/locales/bg-BG.json
  • app/components/AccountItem.vue
  • i18n/locales/hu-HU.json
  • i18n/locales/cs-CZ.json
  • i18n/locales/uk-UA.json

Comment thread i18n/locales/de-DE.json
Comment on lines +16 to +17
"title": "Verknüpfte Konten",
"verified_summary": "Verifizierte Konten",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Render the verified-account counts in the summary.

Line 17 ignores the { verified, total } values passed by app/components/LinkedAccounts.vue, so the German UI will only show a static label instead of the verification summary.

Proposed fix
-      "verified_summary": "Verifizierte Konten",
+      "verified_summary": "{verified}/{total} verifiziert",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@i18n/locales/de-DE.json` around lines 16 - 17, The German locale entry
"verified_summary" currently is a static label and must include the
interpolation placeholders used by app/components/LinkedAccounts.vue; update the
"verified_summary" value in i18n/locales/de-DE.json to include the {verified}
and {total} placeholders (e.g. a German phrase using {verified} and {total}) so
the component's passed values are rendered in the UI.

Comment thread i18n/locales/en.json Outdated
Comment thread i18n/schema.json
Your Name and others added 2 commits April 24, 2026 03:44
@github-actions
Copy link
Copy Markdown

e18e dependency analysis

No dependency warnings found.

@socket-security
Copy link
Copy Markdown

socket-security Bot commented Apr 24, 2026

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Added@​keytrace/​claims@​1.4.07610010094100

View full report

Copy link
Copy Markdown
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: 3

🧹 Nitpick comments (6)
server/api/keytrace/reverify.post.ts (1)

9-15: Unnecessary one-line wrappers.

mapReverifyStatus just forwards to mapKeytraceVerificationStatus, and getReverifyLastCheckedAt is a one-line expression used only once. Inlining both would remove indirection without hurting readability.

♻️ Proposed refactor
-function mapReverifyStatus(claim: ClaimVerificationResult): KeytraceReverifyResponse['status'] {
-  return mapKeytraceVerificationStatus(claim)
-}
-
-function getReverifyLastCheckedAt(claim: ClaimVerificationResult): string {
-  return getOptionalClaimField(claim, 'lastVerifiedAt') || claim.claim.createdAt
-}
-
 function matchesAccount(
@@
     const response: KeytraceReverifyResponse = {
-      status: mapReverifyStatus(matchedClaim),
-      lastCheckedAt: getReverifyLastCheckedAt(matchedClaim),
+      status: mapKeytraceVerificationStatus(matchedClaim),
+      lastCheckedAt:
+        getOptionalClaimField(matchedClaim, 'lastVerifiedAt') || matchedClaim.claim.createdAt,
       failureReason: matchedClaim.error || undefined,
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/api/keytrace/reverify.post.ts` around lines 9 - 15, Remove the
unnecessary one-line wrapper functions: replace calls to
mapReverifyStatus(claim) with mapKeytraceVerificationStatus(claim) and replace
calls to getReverifyLastCheckedAt(claim) with the expression
getOptionalClaimField(claim, 'lastVerifiedAt') || claim.claim.createdAt; then
delete the mapReverifyStatus and getReverifyLastCheckedAt function declarations
to eliminate indirection and keep only the original helper functions
mapKeytraceVerificationStatus and getOptionalClaimField.
server/api/keytrace/[domain].ts (2)

130-131: Stale comment referencing removed mock mode.

The inline comment mentions "mock mode isn't allowed", but per the PR description the misleading mock profile fallback was explicitly removed. Consider rewording to reflect current behaviour.

✏️ Proposed fix
-  // If Keytrace is unavailable and mock mode isn't allowed, return a neutral profile.
+  // Keytrace is unavailable — return a neutral service-unavailable profile.
   return buildServiceUnavailableProfile(domain)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/api/keytrace/`[domain].ts around lines 130 - 131, The inline comment
above the return of buildServiceUnavailableProfile(domain) is stale—remove the
"mock mode isn't allowed" wording and rephrase to reflect current behavior:
state simply that when Keytrace is unavailable the service returns a
neutral/unavailable profile. Update the comment text near
buildServiceUnavailableProfile(domain) to something like "If Keytrace is
unavailable, return a neutral service-unavailable profile" so it no longer
references removed mock mode.

52-69: Add explicit fallback for addedAt to ensure non-null value.

addedAt is assigned directly from claim.claim.createdAt without a fallback. Whilst TypeScript compilation passes with strict mode enabled, adding a defensive fallback (e.g., claim.claim.createdAt ?? new Date(0).toISOString()) would protect against runtime scenarios where the external package's actual data differs from its type definitions. The lastCheckedAt field already has a sensible fallback chain, but addedAt should follow the same pattern for consistency.

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

In `@server/api/keytrace/`[domain].ts around lines 52 - 69, The
mapClaimsToAccounts function assigns addedAt directly from claim.claim.createdAt
without a defensive fallback; update the addedAt assignment in
mapClaimsToAccounts to use a fallback (e.g., claim.claim.createdAt ?? new
Date(0).toISOString()) so addedAt is never null/undefined at runtime and matches
the string type expected by KeytraceResponse['accounts'].
app/components/AccountItem.vue (1)

244-282: Duplicated avatar/name markup across the v-if/v-else branches.

The only difference between the LinkBase (when account.url exists) and the plain div branches is the wrapping element. The avatar container, <img>/icon fallback, and display-name <p> are copy-pasted identically. Extracting a <component :is="..."> or an inner template block would remove ~18 lines of duplication and prevent future drift.

♻️ Proposed refactor
-          <LinkBase
-            v-if="account.url"
-            :to="account.url"
-            noUnderline
-            class="inline-flex items-center gap-3 min-w-0 hover:text-accent"
-          >
-            <div
-              class="size-10 rounded-full border border-border overflow-hidden bg-bg-muted shrink-0 flex items-center justify-center"
-            >
-              <img
-                v-if="accountAvatar"
-                :src="accountAvatar"
-                :alt="accountDisplayName"
-                class="w-full h-full object-cover"
-              />
-              <span v-else :class="platformIconClass" class="size-4" aria-hidden="true" />
-            </div>
-            <p class="font-mono text-base sm:text-lg font-medium min-w-0 break-words">
-              {{ accountDisplayName }}
-            </p>
-          </LinkBase>
-
-          <div v-else class="inline-flex items-center gap-3 min-w-0">
-            <div
-              class="size-10 rounded-full border border-border overflow-hidden bg-bg-muted shrink-0 flex items-center justify-center"
-            >
-              <img
-                v-if="accountAvatar"
-                :src="accountAvatar"
-                :alt="accountDisplayName"
-                class="w-full h-full object-cover"
-              />
-              <span v-else :class="platformIconClass" class="size-4" aria-hidden="true" />
-            </div>
-            <p class="font-mono text-base sm:text-lg font-medium min-w-0 break-words">
-              {{ accountDisplayName }}
-            </p>
-          </div>
+          <component
+            :is="account.url ? 'LinkBase' : 'div'"
+            v-bind="account.url ? { to: account.url, noUnderline: true } : {}"
+            class="inline-flex items-center gap-3 min-w-0"
+            :class="account.url ? 'hover:text-accent' : ''"
+          >
+            <div
+              class="size-10 rounded-full border border-border overflow-hidden bg-bg-muted shrink-0 flex items-center justify-center"
+            >
+              <img
+                v-if="accountAvatar"
+                :src="accountAvatar"
+                :alt="accountDisplayName"
+                class="w-full h-full object-cover"
+              />
+              <span v-else :class="platformIconClass" class="size-4" aria-hidden="true" />
+            </div>
+            <p class="font-mono text-base sm:text-lg font-medium min-w-0 break-words">
+              {{ accountDisplayName }}
+            </p>
+          </component>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/components/AccountItem.vue` around lines 244 - 282, The avatar + name
block is duplicated between the LinkBase (when account.url) and the plain div
branch; extract that repeated markup into a single reusable wrapper and render
it with a dynamic root instead of duplicating it. Replace the two branches with
one element that chooses its tag/component via :is (or a small functional
subcomponent) using the same props and bindings (preserve accountAvatar,
accountDisplayName, platformIconClass, and the noUnderline/class props when
using LinkBase) so the inner structure (avatar container, <img> fallback, and
<p> display-name) exists only once and is shared for both the LinkBase and
non-link cases.
server/utils/keytrace.ts (1)

6-42: Derive the reverse map from the forward map to prevent drift.

platformToClaimTypeMap and claimTypeToPlatformMap are maintained as two separate literals with overlapping data. Any platform added to one but not the other will silently cause round-trip mismatches (e.g. a reverify request and a [domain] response disagreeing on the platform label). Deriving the reverse map once guarantees consistency.

♻️ Proposed refactor
 const platformToClaimTypeMap: Record<string, string> = {
   github: 'github',
   dns: 'dns',
   mastodon: 'activitypub',
   bluesky: 'bsky',
   npm: 'npm',
   tangled: 'tangled',
   pgp: 'pgp',
   twitter: 'twitter',
   linkedin: 'linkedin',
   instagram: 'instagram',
   reddit: 'reddit',
   hackernews: 'hackernews',
   orcid: 'orcid',
   itchio: 'itchio',
   discord: 'discord',
   steam: 'steam',
 }

-const claimTypeToPlatformMap: Record<string, string> = {
-  github: 'github',
-  dns: 'dns',
-  activitypub: 'mastodon',
-  bsky: 'bluesky',
-  npm: 'npm',
-  tangled: 'tangled',
-  pgp: 'pgp',
-  twitter: 'twitter',
-  linkedin: 'linkedin',
-  instagram: 'instagram',
-  reddit: 'reddit',
-  hackernews: 'hackernews',
-  orcid: 'orcid',
-  itchio: 'itchio',
-  discord: 'discord',
-  steam: 'steam',
-}
+const claimTypeToPlatformMap: Record<string, string> = Object.fromEntries(
+  Object.entries(platformToClaimTypeMap).map(([platform, claimType]) => [claimType, platform]),
+)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/utils/keytrace.ts` around lines 6 - 42, platformToClaimTypeMap and
claimTypeToPlatformMap are duplicated and can drift; replace the hand-maintained
reverse literal by deriving claimTypeToPlatformMap from platformToClaimTypeMap
(e.g. invert the entries) so every platform->claimType mapping automatically
produces the claimType->platform mapping. Update the declaration for
claimTypeToPlatformMap to be created from Object.entries/platformToClaimTypeMap
(or equivalent) and keep the same Record<string,string> typing, ensuring any
duplicate keys are handled or asserted if needed.
app/pages/profile/[identity]/index.vue (1)

241-248: Keytrace section renders for every identity, relying on server-side empty fallback.

This section is still unconditional — for non-domain handles (e.g. atproto handles routed through /profile/[identity]) the /api/keytrace/[domain] endpoint will return buildFallbackProfile, so the user sees a ProfileHeader populated with a Dicebear avatar and a “No Keytrace claims found for …” description plus an empty LinkedAccounts panel. That is arguably better than the previous fabricated-accounts behaviour, but it still surfaces a Keytrace-branded block on handles that shouldn’t have one. Consider gating the whole section behind a simple domain-shape check or keytraceAccounts.length > 0 once loading completes.

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

In `@app/pages/profile/`[identity]/index.vue around lines 241 - 248, The Keytrace
UI is rendered unconditionally; change the template to only render the section
when the identity looks like a domain or when keytrace data is present after
loading—e.g., wrap the <section> containing ProfileHeader and LinkedAccounts in
a v-if that checks a domain-shape test on prop identity (simple regex) OR
(keytraceLoading === false && keytraceAccounts && keytraceAccounts.length > 0).
Update references to ProfileHeader, LinkedAccounts, keytraceAccounts,
keytraceLoading and identity so the block is gated and fallback
buildFallbackProfile results won’t surface Keytrace UI for non-domain handles.
🤖 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/AccountItem.vue`:
- Around line 292-302: Several user-facing strings in AccountItem.vue are
hard-coded: the inline strings "via", "Added", "Last checked", the button/aria
texts "Re-verify Claim", "Checking...", "Re-verify", and the aria-label
'Re-verify claim'; also formatDate is forced to 'en-US'. Replace those literals
to use the translation helper (t()) with keys under profile.linked_accounts.*
(follow the pattern used by statusLabelMap) for proofMethodLabel context and all
button/aria labels (e.g., use t('profile.linked_accounts.via'),
t('profile.linked_accounts.added'), etc.), and ensure the aria-label and button
text bind to t() values; finally, stop pinning formatDate to 'en-US' and instead
supply the app/user locale (e.g., use the i18n locale or a locale prop) so
formatDate(account.addedAt) and formatDate(localLastCheckedAt) render with the
user’s locale.
- Around line 314-321: The loader icon on the re-verify button isn't animated;
update the ButtonBase usage in AccountItem.vue so the classicon binding includes
the animation utility when isReverifying is true (i.e., add animate-spin to the
true branch of :classicon). Locate the ButtonBase component instance that uses
the isReverifying prop and reverifyAccount handler and change the classicon
expression to include "animate-spin" for the loader state so users see a
spinning loader during re-verification.

In `@server/api/keytrace/reverify.post.ts`:
- Around line 77-81: The block that unconditionally sets response.status =
'unverified' when matchedClaim.claim.retractedAt exists should be changed to
preserve the status determined by mapKeytraceVerificationStatus (e.g., do not
overwrite a 'failed' status); instead, only set response.retractedAt =
matchedClaim.claim.retractedAt and ensure response.failureReason is populated
(response.failureReason = response.failureReason || 'Keytrace claim was
retracted.') without changing response.status. Locate the code around
matchedClaim.claim.retractedAt / response.status and remove or guard the
assignment so existing statuses from mapKeytraceVerificationStatus remain
intact.

---

Nitpick comments:
In `@app/components/AccountItem.vue`:
- Around line 244-282: The avatar + name block is duplicated between the
LinkBase (when account.url) and the plain div branch; extract that repeated
markup into a single reusable wrapper and render it with a dynamic root instead
of duplicating it. Replace the two branches with one element that chooses its
tag/component via :is (or a small functional subcomponent) using the same props
and bindings (preserve accountAvatar, accountDisplayName, platformIconClass, and
the noUnderline/class props when using LinkBase) so the inner structure (avatar
container, <img> fallback, and <p> display-name) exists only once and is shared
for both the LinkBase and non-link cases.

In `@app/pages/profile/`[identity]/index.vue:
- Around line 241-248: The Keytrace UI is rendered unconditionally; change the
template to only render the section when the identity looks like a domain or
when keytrace data is present after loading—e.g., wrap the <section> containing
ProfileHeader and LinkedAccounts in a v-if that checks a domain-shape test on
prop identity (simple regex) OR (keytraceLoading === false && keytraceAccounts
&& keytraceAccounts.length > 0). Update references to ProfileHeader,
LinkedAccounts, keytraceAccounts, keytraceLoading and identity so the block is
gated and fallback buildFallbackProfile results won’t surface Keytrace UI for
non-domain handles.

In `@server/api/keytrace/`[domain].ts:
- Around line 130-131: The inline comment above the return of
buildServiceUnavailableProfile(domain) is stale—remove the "mock mode isn't
allowed" wording and rephrase to reflect current behavior: state simply that
when Keytrace is unavailable the service returns a neutral/unavailable profile.
Update the comment text near buildServiceUnavailableProfile(domain) to something
like "If Keytrace is unavailable, return a neutral service-unavailable profile"
so it no longer references removed mock mode.
- Around line 52-69: The mapClaimsToAccounts function assigns addedAt directly
from claim.claim.createdAt without a defensive fallback; update the addedAt
assignment in mapClaimsToAccounts to use a fallback (e.g., claim.claim.createdAt
?? new Date(0).toISOString()) so addedAt is never null/undefined at runtime and
matches the string type expected by KeytraceResponse['accounts'].

In `@server/api/keytrace/reverify.post.ts`:
- Around line 9-15: Remove the unnecessary one-line wrapper functions: replace
calls to mapReverifyStatus(claim) with mapKeytraceVerificationStatus(claim) and
replace calls to getReverifyLastCheckedAt(claim) with the expression
getOptionalClaimField(claim, 'lastVerifiedAt') || claim.claim.createdAt; then
delete the mapReverifyStatus and getReverifyLastCheckedAt function declarations
to eliminate indirection and keep only the original helper functions
mapKeytraceVerificationStatus and getOptionalClaimField.

In `@server/utils/keytrace.ts`:
- Around line 6-42: platformToClaimTypeMap and claimTypeToPlatformMap are
duplicated and can drift; replace the hand-maintained reverse literal by
deriving claimTypeToPlatformMap from platformToClaimTypeMap (e.g. invert the
entries) so every platform->claimType mapping automatically produces the
claimType->platform mapping. Update the declaration for claimTypeToPlatformMap
to be created from Object.entries/platformToClaimTypeMap (or equivalent) and
keep the same Record<string,string> typing, ensuring any duplicate keys are
handled or asserted if needed.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: c6720fd9-4b3b-459c-aefa-3489fec5a7f0

📥 Commits

Reviewing files that changed from the base of the PR and between 0fbd549 and c7717a5.

📒 Files selected for processing (8)
  • app/components/AccountItem.vue
  • app/pages/profile/[identity]/index.vue
  • i18n/locales/en.json
  • server/api/keytrace/[domain].ts
  • server/api/keytrace/reverify.post.ts
  • server/utils/keytrace.ts
  • shared/types/keytrace.ts
  • test/nuxt/a11y.spec.ts
✅ Files skipped from review due to trivial changes (1)
  • shared/types/keytrace.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • i18n/locales/en.json
  • test/nuxt/a11y.spec.ts

Comment on lines +292 to +302
<p class="mt-2 text-sm text-fg-muted min-w-0 break-words">
via {{ proofMethodLabel }}
<span aria-hidden="true" class="mx-1">&middot;</span>
Added {{ formatDate(account.addedAt) }}
<span aria-hidden="true" class="mx-1">&middot;</span>
Last checked {{ formatDate(localLastCheckedAt) }}
</p>

<p v-if="shouldShowFailureReason" class="mt-2 text-sm text-fg-muted min-w-0 break-words">
{{ localFailureReason }}
</p>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Several user-facing strings are hard-coded and not translatable.

via, Added, Last checked (lines 293/295/297), plus Re-verify Claim (line 325), Checking... / Re-verify (line 320), and aria-label: 'Re-verify claim' (line 312) are all English literals. The rest of the component correctly uses t() with statusLabelMap/fallback — these strings should follow the same pattern and be added to the profile.linked_accounts.* i18n namespace.

Also note formatDate is pinned to 'en-US' (line 231), which will produce English month abbreviations even for users on other locales.

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

In `@app/components/AccountItem.vue` around lines 292 - 302, Several user-facing
strings in AccountItem.vue are hard-coded: the inline strings "via", "Added",
"Last checked", the button/aria texts "Re-verify Claim", "Checking...",
"Re-verify", and the aria-label 'Re-verify claim'; also formatDate is forced to
'en-US'. Replace those literals to use the translation helper (t()) with keys
under profile.linked_accounts.* (follow the pattern used by statusLabelMap) for
proofMethodLabel context and all button/aria labels (e.g., use
t('profile.linked_accounts.via'), t('profile.linked_accounts.added'), etc.), and
ensure the aria-label and button text bind to t() values; finally, stop pinning
formatDate to 'en-US' and instead supply the app/user locale (e.g., use the i18n
locale or a locale prop) so formatDate(account.addedAt) and
formatDate(localLastCheckedAt) render with the user’s locale.

Comment on lines +314 to +321
<ButtonBase
size="sm"
:disabled="isReverifying"
:classicon="isReverifying ? 'i-lucide:loader-circle' : 'i-lucide:refresh-cw'"
@click="reverifyAccount"
>
{{ isReverifying ? 'Checking...' : 'Re-verify' }}
</ButtonBase>
Copy link
Copy Markdown
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
# Confirm whether ButtonBase supports combining animation utility classes on classicon.
fd -t f 'ButtonBase.vue' -x cat -n {}

Repository: npmx-dev/npmx.dev

Length of output: 43


🏁 Script executed:

# Find ButtonBase component - it may use a different naming convention
fd -i 'button' -t f app/components --extension vue | head -20

Repository: npmx-dev/npmx.dev

Length of output: 209


🏁 Script executed:

# Read AccountItem.vue to examine the code at lines 314-321 and around line 354
wc -l app/components/AccountItem.vue

Repository: npmx-dev/npmx.dev

Length of output: 96


🏁 Script executed:

# Search for ButtonBase usage and definition across the codebase
rg 'ButtonBase' -t vue app/components --max-count 5

Repository: npmx-dev/npmx.dev

Length of output: 89


🏁 Script executed:

# Search for ButtonBase in all files
rg 'ButtonBase' app/components --max-count 10

Repository: npmx-dev/npmx.dev

Length of output: 9022


🏁 Script executed:

# Read AccountItem.vue around lines 314-321 to see the re-verify button
sed -n '310,325p' app/components/AccountItem.vue

Repository: npmx-dev/npmx.dev

Length of output: 691


🏁 Script executed:

# Read AccountItem.vue around line 354 to see the step-list version
sed -n '350,365p' app/components/AccountItem.vue

Repository: npmx-dev/npmx.dev

Length of output: 680


🏁 Script executed:

# Read ButtonBase.vue to understand the classicon prop
cat -n app/components/Button/Base.vue

Repository: npmx-dev/npmx.dev

Length of output: 3135


Add animate-spin to the loader icon for visual feedback during re-verification.

The re-verify button swaps the icon using :classicon="isReverifying ? 'i-lucide:loader-circle' : 'i-lucide:refresh-cw'", but the loader appears static without animation. The step-list implementation (line ~354) demonstrates the pattern by using class="i-lucide:loader-circle size-3 animate-spin". Update the classicon binding to include the animation utility: :classicon="isReverifying ? 'i-lucide:loader-circle animate-spin' : 'i-lucide:refresh-cw'" so the pending state is visually obvious to users.

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

In `@app/components/AccountItem.vue` around lines 314 - 321, The loader icon on
the re-verify button isn't animated; update the ButtonBase usage in
AccountItem.vue so the classicon binding includes the animation utility when
isReverifying is true (i.e., add animate-spin to the true branch of :classicon).
Locate the ButtonBase component instance that uses the isReverifying prop and
reverifyAccount handler and change the classicon expression to include
"animate-spin" for the loader state so users see a spinning loader during
re-verification.

Comment on lines +77 to +81
if (matchedClaim.claim.retractedAt) {
response.status = 'unverified'
response.retractedAt = matchedClaim.claim.retractedAt
response.failureReason = response.failureReason || 'Keytrace claim was retracted.'
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Retracted-claim override downgrades 'failed' to 'unverified'.

When a claim has retractedAt set, this block unconditionally overwrites response.status with 'unverified'. However mapKeytraceVerificationStatus already returns 'failed' for retracted claims (when the raw status field is 'retracted'), and the KeytraceVerificationStatus union treats 'failed' as a stronger/more accurate signal than 'unverified'. This override therefore masks legitimate failure signalling and creates an inconsistency with the GET /api/keytrace/[domain] endpoint, which would return 'failed' for the same claim.

Consider keeping whatever status mapKeytraceVerificationStatus produced and only setting retractedAt + a default failureReason:

🐛 Proposed fix
-    if (matchedClaim.claim.retractedAt) {
-      response.status = 'unverified'
-      response.retractedAt = matchedClaim.claim.retractedAt
-      response.failureReason = response.failureReason || 'Keytrace claim was retracted.'
-    }
+    if (matchedClaim.claim.retractedAt) {
+      response.retractedAt = matchedClaim.claim.retractedAt
+      response.failureReason = response.failureReason || 'Keytrace claim was retracted.'
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/api/keytrace/reverify.post.ts` around lines 77 - 81, The block that
unconditionally sets response.status = 'unverified' when
matchedClaim.claim.retractedAt exists should be changed to preserve the status
determined by mapKeytraceVerificationStatus (e.g., do not overwrite a 'failed'
status); instead, only set response.retractedAt = matchedClaim.claim.retractedAt
and ensure response.failureReason is populated (response.failureReason =
response.failureReason || 'Keytrace claim was retracted.') without changing
response.status. Locate the code around matchedClaim.claim.retractedAt /
response.status and remove or guard the assignment so existing statuses from
mapKeytraceVerificationStatus remain intact.

@BittuBarnwal7479
Copy link
Copy Markdown
Contributor Author

@patak-cat please review.

data-facet-category-facets
>
<ButtonBase
<button
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Would you revert this change and do it in a separate PR if you see value with it? Let's keep this PR only about adding the read-only keytrace claims

Comment thread app/pages/profile/[identity]/index.vue Outdated
{{ $t('profile.likes') }}
<span v-if="likes">({{ likes.records?.length ?? 0 }})</span>
</h2>
<p class="text-fg-muted text-sm">{{ $t('profile.public_interests_description') }}</p>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Let's remove this explanation, too. Please propose it in a separate PR so it is easier to merge each of these.

@patak-cat
Copy link
Copy Markdown
Contributor

Thanks @BittuBarnwal7479! This is a great first step:
image

The avatar in the profile above it isn't loading for me:
image

I think we should remove this box and integrate it with the main profile info above it. But I'll let other folks that are working on design check things like spacing and overall UI.

@BittuBarnwal7479
Copy link
Copy Markdown
Contributor Author

Thanks @BittuBarnwal7479! This is a great first step: image

The avatar in the profile above it isn't loading for me: image

I think we should remove this box and integrate it with the main profile info above it. But I'll let other folks that are working on design check things like spacing and overall UI.

Hii @patak-cat thanks for point out this.
For some claim types (for example DNS identities like zeu.dev), Keytrace does not return [avatarUrl] so the avatar was empty and appeared to disappear.

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

🧹 Nitpick comments (2)
app/pages/profile/[identity]/index.vue (2)

251-260: Redundant wrapper and title attribute on the likes heading.

  • The <div class="flex flex-col gap-2"> wraps a single <h2>; gap-2 has no effect with one child, so the wrapper can be dropped (or the surrounding <section class="flex flex-col gap-8"> adjusted) without behavioural change.
  • :title="$t('profile.likes')" mirrors the visible text inside the same <h2>, producing a tooltip that just repeats what is already shown. Tooltips that duplicate visible content add noise for sighted users and are typically announced redundantly by AT.
♻️ Proposed cleanup
-      <div class="flex flex-col gap-2">
-        <h2
-          class="font-mono text-2xl sm:text-3xl font-medium min-w-0 break-words"
-          :title="$t('profile.likes')"
-          dir="ltr"
-        >
-          {{ $t('profile.likes') }}
-          <span v-if="likes">({{ likes.records?.length ?? 0 }})</span>
-        </h2>
-      </div>
+      <h2
+        class="font-mono text-2xl sm:text-3xl font-medium min-w-0 break-words"
+        dir="ltr"
+      >
+        {{ $t('profile.likes') }}
+        <span v-if="likes">({{ likes.records?.length ?? 0 }})</span>
+      </h2>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/pages/profile/`[identity]/index.vue around lines 251 - 260, Remove the
unnecessary wrapper div and the redundant title binding on the likes heading:
delete the <div class="flex flex-col gap-2"> that only contains the <h2> and
remove :title="$t('profile.likes')" from the <h2>; if vertical spacing was
intended, adjust the surrounding container (the section with class "flex
flex-col gap-8") instead of adding a single-child gap. This targets the likes
heading block and the wrapper around it so the visible text remains unchanged
and no duplicate tooltip is emitted.

241-248: Consider folding the Keytrace ProfileHeader into the main <header> above.

This block renders a second header-like surface immediately below the existing profile <header> (Lines 171-239), duplicating the visual "header" role and showing avatar/name twice for handles that have both an atproto profile and Keytrace claims. Reviewer feedback on the PR also flagged this. Since the design refinement was punted to the design team, this is non-blocking, but worth tracking as a follow-up so we don't ship two stacked profile headers.

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

In `@app/pages/profile/`[identity]/index.vue around lines 241 - 248, The Keytrace
ProfileHeader block duplicates the main profile header UI; remove the separate
<ProfileHeader :profile="keytraceProfile" :loading="keytraceLoading" /> block
and instead surface Keytrace data inside the existing header component by
passing keytrace props (e.g., keytraceProfile, keytraceAccounts, keytraceLoading
or a single keytrace object) into the main header component and rendering
Keytrace-specific elements (accounts/claims) conditionally inside that header to
avoid duplicating avatar/name; update the main header component to accept and
render these optional keytrace props (and ensure LinkedAccounts stays rendered
once, e.g., move LinkedAccounts into the main header or keep it below but fed
from the new keytrace props) so the UI shows a single header surface.
🤖 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/pages/profile/`[identity]/index.vue:
- Around line 251-260: Remove the unnecessary wrapper div and the redundant
title binding on the likes heading: delete the <div class="flex flex-col gap-2">
that only contains the <h2> and remove :title="$t('profile.likes')" from the
<h2>; if vertical spacing was intended, adjust the surrounding container (the
section with class "flex flex-col gap-8") instead of adding a single-child gap.
This targets the likes heading block and the wrapper around it so the visible
text remains unchanged and no duplicate tooltip is emitted.
- Around line 241-248: The Keytrace ProfileHeader block duplicates the main
profile header UI; remove the separate <ProfileHeader :profile="keytraceProfile"
:loading="keytraceLoading" /> block and instead surface Keytrace data inside the
existing header component by passing keytrace props (e.g., keytraceProfile,
keytraceAccounts, keytraceLoading or a single keytrace object) into the main
header component and rendering Keytrace-specific elements (accounts/claims)
conditionally inside that header to avoid duplicating avatar/name; update the
main header component to accept and render these optional keytrace props (and
ensure LinkedAccounts stays rendered once, e.g., move LinkedAccounts into the
main header or keep it below but fed from the new keytrace props) so the UI
shows a single header surface.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 682b43f6-8711-44d6-81bf-874a52d96d51

📥 Commits

Reviewing files that changed from the base of the PR and between c7717a5 and 4af28ee.

📒 Files selected for processing (1)
  • app/pages/profile/[identity]/index.vue

@patak-cat
Copy link
Copy Markdown
Contributor

For some claim types (for example DNS identities like zeu.dev), Keytrace does not return [avatarUrl] so the avatar was empty and appeared to disappear.

This is fine. The one that isn't showing is the one in the Profile Header above the Linked Accounts. The image for zeu.dev links to this svg from dicebear and it is broken when it is used in the site. I don't know what is the idea for this section:
image

I think you can remove it.

@BittuBarnwal7479
Copy link
Copy Markdown
Contributor Author

For some claim types (for example DNS identities like zeu.dev), Keytrace does not return [avatarUrl] so the avatar was empty and appeared to disappear.

This is fine. The one that isn't showing is the one in the Profile Header above the Linked Accounts. The image for zeu.dev links to this svg from dicebear and it is broken when it is used in the site. I don't know what is the idea for this section: image

I think you can remove it.

Hi @patak-cat , thanks for the review.
I’ve now addressed:

  • Removed the Identity profile header
  • Kept real Keytrace avatars when present and only use fallback when missing.
  • Fixed avatar rendering for identities where Keytrace does not return avatarUrl (for example DNS claims) by using deterministic fallback avatars.
  • Routed both profile and account avatars through our internal image proxy, which resolves the CSP blocking issue from direct Dicebear URLs.

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.

Adopt keytrace

2 participants