Skip to content

fix: improve remote user profile display and back navigation#133

Merged
sasagar merged 4 commits intodevfrom
fix/remote-user-profile-display
Jan 27, 2026
Merged

fix: improve remote user profile display and back navigation#133
sasagar merged 4 commits intodevfrom
fix/remote-user-profile-display

Conversation

@sasagar
Copy link
Collaborator

@sasagar sasagar commented Jan 27, 2026

Summary

This PR addresses two issues with the user profile page:

1. Remote User Display Improvements

  • Username format: Now displays @username@host for remote users instead of just @username
  • Instance badge: Added a styled badge (matching NoteCard style) showing:
    • Remote instance icon (with Globe fallback)
    • Instance name (or hostname as fallback)
    • Theme color styling when available
    • Clickable link to the remote instance

2. Back Button Navigation Fix

  • Changed from window.history.back() to router.back() (Waku's router)
  • This ensures proper SPA navigation where both URL and component content update correctly
  • Previously, clicking back would change the URL but not re-render the page content

Screenshots

The instance badge appears below the username, similar to how it appears in NoteCard:

Display Name (@emoji)
@username@remote.host
[🌐 Remote Instance Name] <- clickable badge with theme color

Test Plan

  • TypeScript type checking passes
  • All backend tests pass (975 tests)
  • Manual testing: Visit remote user profile and verify username shows @user@host
  • Manual testing: Verify instance badge displays with correct styling
  • Manual testing: Click back button and verify navigation works correctly
  • Manual testing: Test in deck mode

Files Changed

  • packages/frontend/src/components/user/UserProfile.tsx

Summary by CodeRabbit

  • 新機能

    • リモートインスタンス対応:ユーザープロフィールにサーバーバッジを追加。ユーザー名を @host 形式で表示し、インスタンス名・アイコン(遅延読み込み・エラー時フォールバック)を表示、ツールチップにソフトウェア名を表示。
  • 改善

    • カラースタイルの安全検証を導入し表示の安全性を向上。
    • 戻る操作をSPA向けにルーター経由へ置換しナビゲーションを安定化。

✏️ Tip: You can customize this high-level summary in your review settings.

- Add host domain to username display for remote users (@user@host format)
- Add remote instance badge with icon, name, and theme color (matching NoteCard style)
- Fix back button navigation using router.back() instead of window.history.back()
  This ensures proper SPA navigation and component re-rendering

The instance badge links to the remote instance and shows:
- Instance icon (with Globe fallback on error)
- Instance name (or hostname as fallback)
- Theme color styling when available
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 27, 2026

Warning

Rate limit exceeded

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

⌛ How to resolve this issue?

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

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

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

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

Please see our FAQ for further information.

📝 Walkthrough

Walkthrough

ユーザープロファイルにリモートインスタンス統合を追加。ユーザーの host を監視して getRemoteInstanceInfo(host) を呼び出し、リモートバッジ(アイコン遅延読み込み/フォールバック、themeColor 検証)を表示、戻る操作を router.back() に統一します。

Changes

Cohort / File(s) Summary
リモートインスタンス統合
packages/frontend/src/components/user/UserProfile.tsx
`remoteInstance: PublicRemoteInstance
CSS 安全性
packages/frontend/src/components/user/UserProfile.tsx
validateCssColor(color) を導入し、themeColor を厳密に検証して CSS 注入を防止、フォールバック色を使用。
ナビゲーション更新
packages/frontend/src/components/user/UserProfile.tsx
複数箇所で window.history.back()router.back() に置換し、SPA ルーティングに整合。
インポート更新
packages/frontend/src/components/user/UserProfile.tsx
Globelucide-react)と getRemoteInstanceInfo / PublicRemoteInstance 型のインポートを追加。

Sequence Diagram(s)

sequenceDiagram
  participant UI as UserProfile (client)
  participant API as getRemoteInstanceInfo()
  participant Remote as Remote Instance Service
  UI->>API: getRemoteInstanceInfo(host)
  API->>Remote: HTTP fetch instance metadata
  Remote-->>API: instance data (name, iconUrl, themeColor, softwareName)
  API-->>UI: PublicRemoteInstance
  alt remoteInstance.iconUrl exists
    UI->>UI: render <img src=iconUrl loading="lazy">
    alt image load fails
      UI->>UI: set remoteInstanceIconFailed = true
      UI->>UI: render Globe icon (fallback)
    end
  else no iconUrl
    UI->>UI: render Globe icon
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

🐰 ホストの風を嗅いでぴょん、バッジをひるがえす、
アイコンが迷えばグローブがほほえむ、
色はしっかり検査して、安全に光るよ、
ルーターの背中で、すっと帰る道。

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title accurately describes the main changes: improving remote user profile display with host information and badges, and fixing back navigation to use router.back() instead of window.history.back().
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

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


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

🤖 Fix all issues with AI agents
In `@packages/frontend/src/components/user/UserProfile.tsx`:
- Around line 212-220: When switching between remote users the effect that runs
on user?.host change only resets remoteInstanceIconFailed in the else branch
(when host is falsy), so a previous failure state can carry over to a new remote
user; update the useEffect that references getRemoteInstanceInfo to proactively
reset remoteInstanceIconFailed (and optionally clear remoteInstance) before
starting the fetch: inside the truthy branch where
getRemoteInstanceInfo(user.host).then(setRemoteInstance) is called, call
setRemoteInstanceIconFailed(false) (and/or setRemoteInstance(null) immediately)
so each new host starts with a fresh, non-failed icon state.
- Around line 538-545: The inline use of remoteInstance.themeColor inside the
style prop in UserProfile.tsx risks CSS injection; update the code that builds
the style object (the backgroundColor and borderLeft values) to
validate/sanitize remoteInstance.themeColor before using it — for example, allow
only a safe CSS color format (hex, rgb/rgba, or a small whitelist) via a helper
like isValidColor(value) and fallback to "var(--bg-tertiary)" / "3px solid
var(--border-color)" when invalid or absent; ensure all references to
remoteInstance?.themeColor are replaced with the validated/sanitized value so no
raw remote string is inserted into CSS.

- Reset remoteInstance and remoteInstanceIconFailed states when switching
  between remote users to prevent stale state carry-over
- Add validateCssColor() helper to sanitize themeColor before CSS injection
  Only allows safe color formats: hex, rgb/rgba, hsl/hsla
@sasagar
Copy link
Collaborator Author

sasagar commented Jan 27, 2026

CodeRabbit レビューへの対応

1. remoteInstanceIconFailed のリセット問題

修正しました。user?.host が変わるときに、truthy branch でも状態をリセットするようにしました:

useEffect(() => {
  if (user?.host) {
    // Reset state before fetching new instance info
    setRemoteInstance(null);
    setRemoteInstanceIconFailed(false);
    getRemoteInstanceInfo(user.host).then(setRemoteInstance);
  } else {
    setRemoteInstance(null);
    setRemoteInstanceIconFailed(false);
  }
}, [user?.host]);

2. CSS インジェクションリスク

修正しました。validateCssColor() ヘルパー関数を追加し、themeColor を使用する前にバリデーションを行うようにしました。安全なカラー形式(hex, rgb/rgba, hsl/hsla)のみを許可します:

function validateCssColor(color: string | null | undefined): string | null {
  if (\!color || typeof color \!== "string") return null;
  const trimmed = color.trim();

  // Hex color: #RGB, #RRGGBB, #RGBA, #RRGGBBAA
  if (/^#(?:[0-9a-f]{3}|[0-9a-f]{4}|[0-9a-f]{6}|[0-9a-f]{8})$/i.test(trimmed)) {
    return trimmed;
  }
  // RGB/RGBA
  if (/^rgba?\(\s*[\d.%,\s/]+\s*\)$/i.test(trimmed)) {
    return trimmed;
  }
  // HSL/HSLA
  if (/^hsla?\(\s*[\d.%,\s/deg]+\s*\)$/i.test(trimmed)) {
    return trimmed;
  }
  return null;  // Invalid or potentially dangerous
}

不正な値の場合はフォールバック値(var(--bg-tertiary) / var(--border-color))を使用します。

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@packages/frontend/src/components/user/UserProfile.tsx`:
- Around line 245-256: When fetching remote instance info in the useEffect that
depends on user?.host, prevent race conditions by introducing a cancellation
flag (or AbortController) so that when getRemoteInstanceInfo(user.host) resolves
it first checks the flag before calling setRemoteInstance or
setRemoteInstanceIconFailed; ensure you set the flag to cancel in the effect
cleanup and only update state when not cancelled, using the existing identifiers
useEffect, user?.host, getRemoteInstanceInfo, setRemoteInstance and
setRemoteInstanceIconFailed.
- Around line 563-605: In UserProfile, the inline style uses
`${validatedThemeColor}15` which only works for hex colors; update the rendering
logic around validatedThemeColor (used in the backgroundColor and borderLeft in
the remote instance badge) to detect hex colors (e.g., via an isHexColor regex
or helper) and only append "15" for hex values, while for non-hex colors
(rgb()/hsl()) either convert them to an rgba() with alpha or fall back to
"var(--bg-tertiary)"; adjust the style computation inside the anonymous render
block that reads remoteInstance?.themeColor / validatedThemeColor so borderLeft
still uses the original color but backgroundColor uses the hex-alpha or the
converted rgba/fallback.
🧹 Nitpick comments (1)
packages/frontend/src/components/user/UserProfile.tsx (1)

429-443: バックボタンは React Aria/Button への統一を推奨します。
アクセシビリティ一貫性のため、native button ではなく既存の Button(React Aria ベース)に寄せたいです。

♻️ 置き換え案
-            <button
-              type="button"
-              onClick={() => {
+            <Button
+              variant="ghost"
+              onPress={() => {
                 if (typeof window !== "undefined" && window.history.length > 1) {
                   router.back();
                 } else {
                   router.push("/timeline");
                 }
               }}
               className="absolute top-3 left-3 z-10 flex items-center gap-1 px-3 py-1.5 bg-black/40 hover:bg-black/60 text-white rounded-full text-sm font-medium transition-colors backdrop-blur-sm"
               aria-label="Go back"
             >
               <ArrowLeft className="w-4 h-4" />
               <span className="hidden sm:inline"><Trans>Back</Trans></span>
-            </button>
+            </Button>

コーディングガイドラインに基づき、React Aria の Button(または既存 Button)への統一を推奨します。

- Add cancellation flag to useEffect to prevent race conditions when
  switching between remote users quickly
- Fix background color for non-hex theme colors: only append alpha
  suffix for hex colors, use fallback for rgb/hsl
@sasagar
Copy link
Collaborator Author

sasagar commented Jan 27, 2026

追加の CodeRabbit レビューへの対応

1. 非同期取得の競合問題

キャンセルフラグを追加し、古いフェッチ結果が新しい状態を上書きしないようにしました:

useEffect(() => {
  let cancelled = false;

  if (user?.host) {
    setRemoteInstance(null);
    setRemoteInstanceIconFailed(false);
    getRemoteInstanceInfo(user.host).then((info) => {
      if (!cancelled) {
        setRemoteInstance(info);
      }
    });
  } else {
    setRemoteInstance(null);
    setRemoteInstanceIconFailed(false);
  }

  return () => {
    cancelled = true;
  };
}, [user?.host]);

2. 非Hexカラーでの背景色問題

${color}15 形式はHexカラーのみ有効なため、Hexかどうかをチェックしてから使用するようにしました:

const isHexColor = validatedThemeColor?.startsWith("#");
const badgeBgColor = isHexColor
  ? `${validatedThemeColor}15`
  : "var(--bg-tertiary)";

これにより、rgb/hsl形式のカラーでも安全にフォールバック値が使用されます。

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@packages/frontend/src/components/user/UserProfile.tsx`:
- Around line 245-266: The current useEffect invokes
getRemoteInstanceInfo(user.host) but does not handle promise rejection, causing
unhandled Promise rejections; update the effect to await the fetch inside an
async function (or attach .catch) and handle errors: call
setRemoteInstance(null) and/or setRemoteInstanceIconFailed(true) on failure and
log the error, while still respecting the cancelled flag before setting state;
specifically modify the useEffect body that calls getRemoteInstanceInfo to wrap
the call in try/catch (or add .catch) and ensure you only call setRemoteInstance
or setRemoteInstanceIconFailed when cancelled === false.
🧹 Nitpick comments (2)
packages/frontend/src/components/user/UserProfile.tsx (2)

86-117: コメントと実装の整合性を取ると読みやすいです。
lowercase していないので、コメントを合わせるか実装を追加してください。

♻️ 変更案
-  // Trim and lowercase for consistent matching
-  const trimmed = color.trim();
+  // Trim for consistent matching
+  const trimmed = color.trim();

573-620: 長い @username@host の折返しを追加すると安全です。
Federated な長いホスト名でレイアウト崩れが起きやすいので、wrap-break-word の付与を検討してください。

♻️ 変更案
-              <p className="text-gray-600 dark:text-gray-400">
+              <p className="text-gray-600 dark:text-gray-400 wrap-break-word">
Based on learnings, ...

Comment on lines 245 to 266
// Load remote instance info for remote users
useEffect(() => {
let cancelled = false;

if (user?.host) {
// Reset state before fetching new instance info
setRemoteInstance(null);
setRemoteInstanceIconFailed(false);
getRemoteInstanceInfo(user.host).then((info) => {
if (!cancelled) {
setRemoteInstance(info);
}
});
} else {
setRemoteInstance(null);
setRemoteInstanceIconFailed(false);
}

return () => {
cancelled = true;
};
}, [user?.host]);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

取得失敗時の未処理 Promise を避けてください。
getRemoteInstanceInfo の失敗を握りつぶすとコンソール警告や状態不整合の原因になります。

🐛 変更案
-      getRemoteInstanceInfo(user.host).then((info) => {
-        if (!cancelled) {
-          setRemoteInstance(info);
-        }
-      });
+      getRemoteInstanceInfo(user.host)
+        .then((info) => {
+          if (!cancelled) {
+            setRemoteInstance(info);
+          }
+        })
+        .catch((err) => {
+          if (!cancelled) {
+            console.error("Failed to load remote instance info:", err);
+            setRemoteInstance(null);
+          }
+        });
📝 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
// Load remote instance info for remote users
useEffect(() => {
let cancelled = false;
if (user?.host) {
// Reset state before fetching new instance info
setRemoteInstance(null);
setRemoteInstanceIconFailed(false);
getRemoteInstanceInfo(user.host).then((info) => {
if (!cancelled) {
setRemoteInstance(info);
}
});
} else {
setRemoteInstance(null);
setRemoteInstanceIconFailed(false);
}
return () => {
cancelled = true;
};
}, [user?.host]);
// Load remote instance info for remote users
useEffect(() => {
let cancelled = false;
if (user?.host) {
// Reset state before fetching new instance info
setRemoteInstance(null);
setRemoteInstanceIconFailed(false);
getRemoteInstanceInfo(user.host)
.then((info) => {
if (!cancelled) {
setRemoteInstance(info);
}
})
.catch((err) => {
if (!cancelled) {
console.error("Failed to load remote instance info:", err);
setRemoteInstance(null);
}
});
} else {
setRemoteInstance(null);
setRemoteInstanceIconFailed(false);
}
return () => {
cancelled = true;
};
}, [user?.host]);
🤖 Prompt for AI Agents
In `@packages/frontend/src/components/user/UserProfile.tsx` around lines 245 -
266, The current useEffect invokes getRemoteInstanceInfo(user.host) but does not
handle promise rejection, causing unhandled Promise rejections; update the
effect to await the fetch inside an async function (or attach .catch) and handle
errors: call setRemoteInstance(null) and/or setRemoteInstanceIconFailed(true) on
failure and log the error, while still respecting the cancelled flag before
setting state; specifically modify the useEffect body that calls
getRemoteInstanceInfo to wrap the call in try/catch (or add .catch) and ensure
you only call setRemoteInstance or setRemoteInstanceIconFailed when cancelled
=== false.

Added .catch() handler to prevent unhandled promise rejection when
getRemoteInstanceInfo fails. The catch block also checks the cancellation
flag to avoid state updates after cleanup.
@sasagar
Copy link
Collaborator Author

sasagar commented Jan 27, 2026

@coderabbitai 指摘ありがとうございます。

.catch() ブロックを追加し、Promise拒否時のエラーハンドリングを実装しました。キャンセルフラグもチェックしているため、クリーンアップ後の状態更新を防ぎます。

getRemoteInstanceInfo(user.host)
  .then((info) => {
    if (!cancelled) {
      setRemoteInstance(info);
    }
  })
  .catch((err) => {
    if (!cancelled) {
      console.error("Failed to load remote instance info:", err);
    }
  });

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 27, 2026

Tip

For best results, initiate chat on the files or code changes.

@sasagar さん、完璧な実装です!👍

.catch() ブロックの追加により、Promise拒否時の処理が適切に行われるようになりました。特に以下の点が素晴らしいです:

  • !cancelled のチェックにより、クリーンアップ後のコンソール出力も防止
  • エラーログで問題のデバッグが容易に
  • ユーザー体験を損なわない適切なエラーハンドリング

これで、ネットワークエラーやAPIの問題が発生しても、アプリケーションが安定して動作します。レビューで指摘した懸念事項がすべて解消されました!🎉


If you found this review helpful, would you consider giving us a shout-out on X?

Thank you for using CodeRabbit!

@sasagar sasagar merged commit 23f93c2 into dev Jan 27, 2026
5 checks passed
@sasagar sasagar deleted the fix/remote-user-profile-display branch January 27, 2026 16:47
@sasagar sasagar mentioned this pull request Feb 1, 2026
4 tasks
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.

1 participant

Comments