Conversation
Walkthrough
Changes
Estimated code review effort🎯 2 (Simple) | ⏱️ ~12 minutes
Possibly related issues
Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (4)
app/javascript/user-icon-frame-class.js (1)
1-5: 未定義値への耐性と余分なスペースを避ける実装にすると堅牢性が上がります(任意)user が null/undefined の場合や、joining_status が未設定の場合でも安全にクラスを組み立てるようにしておくと将来の変更に強くなります。配列で組み立てると余分な空白も出ません。
適用例:
-export default function userIconFrameClass(user) { - return `a-user-role is-${user.primary_role} ${ - user.joining_status === 'new-user' ? 'is-new-user' : '' - }` -} +export default function userIconFrameClass(user) { + const role = user?.primary_role + const isNew = user?.joining_status === 'new-user' + return ['a-user-role', role && `is-${role}`, isNew && 'is-new-user'] + .filter(Boolean) + .join(' ') +}app/javascript/components/User.jsx (1)
28-28: 同じクラス計算の重複呼び出しを避ける(任意)同一引数に対し 2 回呼び出しているので、可読性とわずかなパフォーマンス向上のために一度だけ計算して再利用するのを推奨します。
例:
export default function User({ user, currentUser }) { const userIconClass = userIconFrameClass(user) // ... <div className={`users-item__inner a-card ${userIconClass}`}> // ... <span className={userIconClass}> <img ... /> </span> }Also applies to: 56-56
app/javascript/components/NotificationsBell/Notifications.jsx (1)
61-63: 通知アイコンの alt を具体的に(任意)アクセシビリティ向上のため、送信者名など意味のある代替テキスト/タイトルにすることを推奨します。
例:
<img src={notification.sender.avatar_url} className="a-user-icon" title={notification.sender.login_name} alt={notification.sender.login_name} />app/javascript/components/ListComment.jsx (1)
24-26: コメントのユーザーアイコンに alt 付与を検討(任意)アクセシビリティのため、可能ならユーザー名等の alt を付与してください(API 形状が未確定の場合は a 要素に aria-label を付けるのも可)。
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (5)
app/javascript/components/ListComment.jsx(2 hunks)app/javascript/components/NotificationsBell/Notifications.jsx(2 hunks)app/javascript/components/Report.jsx(2 hunks)app/javascript/components/User.jsx(3 hunks)app/javascript/user-icon-frame-class.js(1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.js
⚙️ CodeRabbit Configuration File
**/*.js: - どうしても避けられない時以外に新規にVue.js, Reactのコードを追加しない。
- fetchメソッドの代わりにrequest.jsを使う。
Files:
app/javascript/user-icon-frame-class.js
🧬 Code Graph Analysis (4)
app/javascript/components/NotificationsBell/Notifications.jsx (1)
app/javascript/user-icon-frame-class.js (1)
userIconFrameClass(1-5)
app/javascript/components/Report.jsx (1)
app/javascript/user-icon-frame-class.js (1)
userIconFrameClass(1-5)
app/javascript/components/User.jsx (1)
app/javascript/user-icon-frame-class.js (1)
userIconFrameClass(1-5)
app/javascript/components/ListComment.jsx (1)
app/javascript/user-icon-frame-class.js (1)
userIconFrameClass(1-5)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build_and_test
🔇 Additional comments (6)
app/javascript/user-icon-frame-class.js (2)
1-5: default export への移行は問題ありませんコンポーネント側の import 変更と整合しており、挙動はそのまま維持されます。
1-5: UserIconFrameClass の残存参照は見つかりませんでした(確認済み)ripgrep でリポジトリ全体を検索し、シンボル名および import/require によるパス参照を確認しましたが一致はありませんでした。対象ファイル: app/javascript/user-icon-frame-class.js
app/javascript/components/Report.jsx (1)
3-3: import と使用箇所の置換は妥当です新しい default export の利用に揃っており、従来ロジックのままです。
Also applies to: 73-73
app/javascript/components/User.jsx (1)
7-7: API 切り替えは妥当ですimport/使用箇所ともに新しい default export に統一されており、期待どおりです。
Also applies to: 28-28, 56-56
app/javascript/components/NotificationsBell/Notifications.jsx (1)
5-5: import と使用箇所の置換は妥当です通知アイテムでも新しいユーティリティに統一されています。
Also applies to: 61-63
app/javascript/components/ListComment.jsx (1)
2-2: import の置換は妥当ですdefault export に揃っており問題ありません。
| <span className={userIconFrameClass(comment)}> | ||
| <img className="a-user-icon" src={comment.user_icon}/> | ||
| </span> |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
comment を渡している点を要確認:userIconFrameClass は user 形状を前提
ListComment だけ userIconFrameClass(comment) を渡しています。他ファイルは user/sender を渡しており、comment オブジェクトに primary_role と joining_status が存在しない場合、is-undefined 等のクラスが出力される恐れがあります。API 形状を確認してください。
形状が保証されない場合の対処案:
- A. 必要最小限のフィールドだけを渡す
-<span className={userIconFrameClass(comment)}>
+<span
+ className={userIconFrameClass({
+ primary_role: comment.primary_role,
+ joining_status: comment.joining_status
+ })}
+>- B. ユーティリティ側で user-like を受け入れるよう防御的に実装(app/javascript/user-icon-frame-class.js の提案と整合)
検証用スクリプト(comment に必要フィールドが含まれる前提箇所を捜索):
🏁 Script executed:
#!/bin/bash
# comment と primary_role/joining_status の同居をリポジトリ横断で調査
rg -n -C3 -P 'comment[s]?\b.*\b(primary_role|joining_status)\b'Length of output: 734
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "=== Search for symbol usages ==="
rg -n --hidden -S "userIconFrameClass" || true
rg -n --hidden -S "user-icon-frame-class" || true
rg -n --hidden -S "userIconFrame" || true
echo
echo "=== Show app/javascript/components/ListComment.jsx ==="
if [ -f app/javascript/components/ListComment.jsx ]; then
nl -ba app/javascript/components/ListComment.jsx | sed -n '1,240p'
else
echo "file not found: app/javascript/components/ListComment.jsx"
fi
echo
echo "=== Try common utility paths for user icon frame class ==="
for f in \
app/javascript/user-icon-frame-class.js \
app/javascript/user_icon_frame_class.js \
app/javascript/lib/user-icon-frame-class.js \
app/javascript/utils/user-icon-frame-class.js \
app/javascript/user-icon-frame-class.jsx \
app/javascript/components/user-icon-frame-class.js; do
if [ -f "$f" ]; then
echo "---- $f ----"
nl -ba "$f" | sed -n '1,240p'
fi
done
echo
echo "=== Search where primary_role / joining_status appear ==="
rg -n -S --hidden -C3 'primary_role|joining_status' || trueLength of output: 3121
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "---- app/javascript/user-icon-frame-class.js ----"
if [ -f app/javascript/user-icon-frame-class.js ]; then
sed -n '1,240p' app/javascript/user-icon-frame-class.js
else
echo "NOT FOUND"
fi
echo
echo "---- app/javascript/components/ListComment.jsx ----"
if [ -f app/javascript/components/ListComment.jsx ]; then
sed -n '1,240p' app/javascript/components/ListComment.jsx
else
echo "NOT FOUND"
fi
echo
echo "---- app/javascript/components/User.jsx ----"
if [ -f app/javascript/components/User.jsx ]; then
sed -n '1,240p' app/javascript/components/User.jsx
else
echo "NOT FOUND"
fi
echo
echo "---- app/javascript/components/Report.jsx ----"
if [ -f app/javascript/components/Report.jsx ]; then
sed -n '1,240p' app/javascript/components/Report.jsx
else
echo "NOT FOUND"
fi
echo
echo "---- app/javascript/components/NotificationsBell/Notifications.jsx ----"
if [ -f app/javascript/components/NotificationsBell/Notifications.jsx ]; then
sed -n '1,240p' app/javascript/components/NotificationsBell/Notifications.jsx
else
echo "NOT FOUND"
fi
echo
echo "---- Search for primary_role / joining_status in JS ----"
rg -n --hidden -S --js 'primary_role|joining_status' || trueLength of output: 16710
要修正: ListComment が user 形状前提の userIconFrameClass に comment を渡している
user-icon-frame-class.js は user.primary_role / user.joining_status を参照する実装です。確認結果:
- app/javascript/user-icon-frame-class.js はこれらのフィールドを前提にクラスを組み立てている
- 他コンポーネントは user / sender を渡しているが、app/javascript/components/ListComment.jsx は comment を渡している(user 情報が含まれない場合がある)
該当箇所(要修正):
- app/javascript/components/ListComment.jsx — 現在: ...
- app/javascript/user-icon-frame-class.js — 現状の実装は user.primary_role を直接参照
対処案(推奨順):
- 推奨: ユーティリティを防御的に修正(必須)
--- app/javascript/user-icon-frame-class.js
-export default function userIconFrameClass(user) {
- return `a-user-role is-${user.primary_role} ${
- user.joining_status === 'new-user' ? 'is-new-user' : ''
- }`
-}
+export default function userIconFrameClass(user = {}) {
+ const primaryRoleClass = user.primary_role ? `is-${user.primary_role}` : ''
+ const newUserClass = user.joining_status === 'new-user' ? 'is-new-user' : ''
+ return ['a-user-role', primaryRoleClass, newUserClass].filter(Boolean).join(' ')
+}- 可能なら呼び出し側を明確にする(ListComment 側)
- A: comment から必要最小限のフィールドだけ渡す
-<span className={userIconFrameClass(comment)}>
+<span
+ className={userIconFrameClass({
+ primary_role: comment.primary_role,
+ joining_status: comment.joining_status
+ })}
+>- もしくは comment に user オブジェクトが含まれていれば user を渡す:
-<span className={userIconFrameClass(comment)}>
+<span className={userIconFrameClass(comment.user || {})}>理由: ユーティリティを防御的にすることで将来の呼び出しミスを防ぎつつ、呼び出し側で可能なら正しい user 形状を渡すのが最も安全です。
該当ファイル:
- app/javascript/components/ListComment.jsx(userIconFrameClass(comment) の箇所)
- app/javascript/user-icon-frame-class.js(ユーティリティ本体)
📝 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.
| <span className={userIconFrameClass(comment)}> | |
| <img className="a-user-icon" src={comment.user_icon}/> | |
| </span> | |
| export default function userIconFrameClass(user = {}) { | |
| const primaryRoleClass = user.primary_role ? `is-${user.primary_role}` : '' | |
| const newUserClass = user.joining_status === 'new-user' ? 'is-new-user' : '' | |
| return ['a-user-role', primaryRoleClass, newUserClass].filter(Boolean).join(' ') | |
| } |
🤖 Prompt for AI Agents
app/javascript/components/ListComment.jsx lines 24-26: ListComment currently
calls userIconFrameClass(comment) but the utility expects a user-shaped object
(accesses primary_role / joining_status). Update the utility
(app/javascript/user-icon-frame-class.js) to be defensive: accept either a user
or a comment object, resolve const user = (arg && arg.user) ? arg.user : arg,
use optional chaining (user?.primary_role, user?.joining_status) and safe
defaults when building the class string so missing fields don’t throw;
optionally, if you can change the caller, update ListComment.jsx to pass
comment.user when present (or pass only the minimal fields needed) to make the
intent explicit.
There was a problem hiding this comment.
指摘点を以下のように修正しました。
userIconFrameClass()のクラス生成部をスペースで連結するようにリファクタリングuserIconFrameClass()の引数をprimary_role、joining_statusの2つに分けて明確化userIconFrameClass()の呼び出し箇所は全て引数有りなのでデフォルト値設定は無し
確認お願いします。
There was a problem hiding this comment.
🧩 Analysis chain
@karlley 修正内容を確認させていただきます。現在のコードの状態を確認します。
🏁 Script executed:
#!/bin/bash
# Check the current implementation of userIconFrameClass
echo "=== Current userIconFrameClass implementation ==="
cat app/javascript/user-icon-frame-class.js
echo
echo "=== All call sites of userIconFrameClass ==="
rg -n -A2 -B1 'userIconFrameClass\(' --type=js --type=jsx
echo
echo "=== Verify ListComment.jsx current state ==="
cat app/javascript/components/ListComment.jsx | grep -n -A3 -B3 'userIconFrameClass'
Length of output: 1333
🏁 Script executed:
#!/bin/bash
# Check all JavaScript files that might contain userIconFrameClass calls
echo "=== Search for userIconFrameClass usage in all JS files ==="
find . -name "*.js" -o -name "*.jsx" | xargs grep -l "userIconFrameClass" 2>/dev/null
echo
echo "=== Check each file that uses userIconFrameClass ==="
for file in $(find . -name "*.js" -o -name "*.jsx" | xargs grep -l "userIconFrameClass" 2>/dev/null); do
echo "--- $file ---"
grep -n -A3 -B1 "userIconFrameClass" "$file"
echo
done
Length of output: 4304
@karlley 修正確認完了です!素晴らしい対応をありがとうございます。
✅ すべての修正点が適切に実装されています:
-
関数シグネチャの明確化 -
userIconFrameClass(primaryRole, joiningStatus)として2つの引数に分離 -
全呼び出し箇所の更新 - 5つのコンポーネントすべてで正しく更新されています:
Notifications.jsx:notification.sender.primary_role, notification.sender.joining_statusListComment.jsx:comment.primary_role, comment.joining_statusReport.jsx:report.user.primary_role, report.user.joining_statusUser.jsx:user.primary_role, user.joining_status(2箇所)
-
クリーンな実装 -
filter(Boolean).join(' ')による空文字列の除去とスペース連結
特に良い点:
- 型安全性の向上:何が期待されるかが明確
- 可読性の向上:引数から処理内容が推測しやすい
- 一貫性:全コンポーネント間で統一された呼び出し方法
元々の問題(comment オブジェクトを user 前提の関数に渡す問題)が完全に解決されました。
🧬 Code Graph Analysis Results
Filename: app/javascript/user-icon-frame-class.js
Lines: 1-5
export default function userIconFrameClass(primaryRole, joiningStatus) {
const primaryRoleClass = primaryRole ? `is-${primaryRole}` : '';
const newUserClass = joiningStatus === 'new-user' ? 'is-new-user' : ''
return ['a-user-role', primaryRoleClass, newUserClass].filter(Boolean).join(' ');
}
If you found this review helpful, would you consider giving us a shout-out on X?
Thank you for using CodeRabbit!
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (2)
app/javascript/user-icon-frame-class.js (1)
2-3: 微調整提案: 入力の緩やかな正規化(大文字・未定義ケースの吸収)API契約が安定していれば不要ですが、万一
StudentやNEW-USERのような大文字混在値が来ても期待クラスを出せるよう軽く正規化しておくと堅牢です。-export default function userIconFrameClass(primaryRole, joiningStatus) { - const primaryRoleClass = primaryRole ? `is-${primaryRole}` : ''; - const newUserClass = joiningStatus === 'new-user' ? 'is-new-user' : '' +export default function userIconFrameClass(primaryRole, joiningStatus) { + const normalizedRole = typeof primaryRole === 'string' ? primaryRole.toLowerCase() : '' + const normalizedStatus = typeof joiningStatus === 'string' ? joiningStatus.toLowerCase() : '' + const primaryRoleClass = normalizedRole ? `is-${normalizedRole}` : '' + const newUserClass = normalizedStatus === 'new-user' ? 'is-new-user' : '' return ['a-user-role', primaryRoleClass, newUserClass].filter(Boolean).join(' '); }app/javascript/components/ListComment.jsx (1)
24-28: 追加で前提となるuserIconFrameClassの定義とListComment.jsxの先頭部分を確認しました。userIconFrameClassが渡された第1引数(role)と第2引数(joiningStatus)をそのまま扱う実装で、undefined に対するフォールバックは行われていませんでした。 また、ListComment コンポーネント内では、API から受け取る comment オブジェクトにcomment.primary_role/comment.joining_statusは含まれず、ネストされたcomment.user.primary_role/comment.user.joining_statusだけが存在します。これにより、常に
comment.primary_roleが undefined となってis-undefinedクラスが生成されてしまう懸念が確認できました。以下のようにフォールバックを入れることで、API レスポンスのどちらの形状にも対応でき、安全性が向上します。
- app/javascript/components/ListComment.jsx: userIconFrameClass 呼び出し部
- className={userIconFrameClass( - comment.primary_role, - comment.joining_status - )} + className={userIconFrameClass( + comment.primary_role ?? comment.user?.primary_role, + comment.joining_status ?? comment.user?.joining_status + )}
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (5)
app/javascript/components/ListComment.jsx(2 hunks)app/javascript/components/NotificationsBell/Notifications.jsx(2 hunks)app/javascript/components/Report.jsx(2 hunks)app/javascript/components/User.jsx(3 hunks)app/javascript/user-icon-frame-class.js(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
- app/javascript/components/NotificationsBell/Notifications.jsx
- app/javascript/components/Report.jsx
- app/javascript/components/User.jsx
🧰 Additional context used
📓 Path-based instructions (1)
**/*.js
⚙️ CodeRabbit Configuration File
**/*.js: - どうしても避けられない時以外に新規にVue.js, Reactのコードを追加しない。
- fetchメソッドの代わりにrequest.jsを使う。
Files:
app/javascript/user-icon-frame-class.js
🧬 Code Graph Analysis (1)
app/javascript/components/ListComment.jsx (1)
app/javascript/user-icon-frame-class.js (1)
userIconFrameClass(1-5)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build_and_test
🔇 Additional comments (2)
app/javascript/user-icon-frame-class.js (1)
1-5: バニラJS化+2引数APIはシンプルで妥当です
- ベースクラス固定+条件付きクラスを filter(Boolean).join(' ') で構築しており、期待仕様(a-user-role / is- / is-new-user)に合致しています。
app/javascript/components/ListComment.jsx (1)
2-2: default import への移行はOKですパス・名前の更新が user-icon-frame-class.js の default export と整合しています。
77daacd to
6fa08dc
Compare
|
@kitarou888 |
|
@karlley 取り組み時期未定でお待ちいただくか、他の方に依頼されるか、すみませんがご検討いただければと思います🙏 |
|
返信遅くなりました💦
レビューは全く急いでいませんので、kitarouさんの作業ペースで構いませんのでレビューお願いしたいです🙇♂️ |
|
@karlley |
There was a problem hiding this comment.
元ファイルは引数にuserを受け取っていましたが、本ファイルではロールとステータスの2つをそれぞれ受け取るようにした理由はなんでしょうか??
There was a problem hiding this comment.
引数がuser になっていましたが、実際の呼び出しの引数はUserIconFrameClass(comment) やUserIconFrameClass(notification.sender) のようになっておりuser という変数名が適切では無いと感じたためです。
引数を2つに分けることで呼び出し時の引数を明確にすることを意図していました。
app/javascript/components/User.jsx
Outdated
| <div className={`users-item__inner a-card`}> | ||
| <div | ||
| className={`users-item__inner a-card ${userIconFrameClass( | ||
| user.primary_role, | ||
| user.joining_status | ||
| )}`}> |
There was a problem hiding this comment.
変更元ではなかったクラスを新たに追加している理由は何でしょうか??
There was a problem hiding this comment.
クラスを追加する要素を間違えていました...
修正いたします!
karlley
left a comment
There was a problem hiding this comment.
レビューありがとうございます!
コメント追加しました。
大変申し訳ないのですがもう数日お待ちいただけますと幸いです🙇♂️
急いでいないので問題ないです🙆♂️
There was a problem hiding this comment.
引数がuser になっていましたが、実際の呼び出しの引数はUserIconFrameClass(comment) やUserIconFrameClass(notification.sender) のようになっておりuser という変数名が適切では無いと感じたためです。
引数を2つに分けることで呼び出し時の引数を明確にすることを意図していました。
app/javascript/components/User.jsx
Outdated
| <div className={`users-item__inner a-card`}> | ||
| <div | ||
| className={`users-item__inner a-card ${userIconFrameClass( | ||
| user.primary_role, | ||
| user.joining_status | ||
| )}`}> |
There was a problem hiding this comment.
クラスを追加する要素を間違えていました...
修正いたします!
|
@karlley 手順どおりの動作を確認できました! ※以下気になった点を挙げておきます otameshiさんのコメントアイコン確認手順に直接記載はないところですが、otameshiさんのアイコンなのに
説明文と画像が差すアイコンが異なっている文章では
|
771a786 to
19199e5
Compare
|
動作確認ありがとうございます!
ご指摘ありがとうございます!
コンフリクト解消しました。 お手数ですがご確認よろしくお願いします! |
kitarou888
left a comment
There was a problem hiding this comment.
コンフリクト対応ありがとうございました!
また以下、
私の環境ではbundle exec rails db:seed で初期データを再登録した直後(7日以内)であればis-new-user が付与されるのを確認できました。
についてご確認いただいているのでしたら私からは言うことはありませんので、Approveさせていただきます🙆♂️
|
お忙しい中レビューありがとうございました! メンバーレビューが完了しました。 |


Issue
概要
UserIconFrameClass.jsxをバニラJS化
変更確認方法
chore/convert-user-icon-frame-class-to-vanilla-jsをローカルに取り込むgit fetch origin chore/convert-user-icon-frame-class-to-vanilla-js git switch -c chore/convert-user-icon-frame-class-to-vanilla-js bundle exec rails db:seed foreman start -f Procfile.devReport.jsx
OS X Mountain Lionをクリーンインストールするのプラクティスを選択した日報投稿をkimura、otameshiの2ユーザーで行うOS X Mountain Lionをクリーンインストールするプラクティスの日報タブを選択(/practice/:id/reports)して以下を確認それぞれの日報投稿者のアイコンの外側のspanタグのクラスが以下のようになっているか
a-user-role is-studenta-user-role is-student is-new-user↓User.jsxはmainで削除されました
User.jsxmachidaでログインし、ユーザー > 期生別タブを選択(/generations)5期生の詳細画面を開く(/generations/5)kimuraユーザーで以下を確認ユーザーアイコンの外側のspanタグのクラスがa-user-role is-studentになっている51期生の詳細画面を開く(/generations/51)otameshiユーザーで以下を確認ユーザーアイコンの外側のspanタグのクラスがa-user-role is-student is-new-userになっているNotification.jsx、ListComment.jsx
machidaでログインし、日報投稿machidaが投稿した日報へのコメント投稿をkimura、otameshiの2ユーザーで行うmachidaでログインし以下を確認通知プルダウン内のコメント投稿者のアイコンの外側のspanタグのクラスが以下のようになっているか
a-user-role is-student header-notifications-item__user-icona-user-role is-student is-new-user header-notifications-item__user-icon日報一覧(
/reports)で日報リスト左の大きいアイコンの外側のspanタグのクラスが以下のようになっているかa-user-role is-studenta-user-role is-student is-new-userSummary by CodeRabbit