Conversation
Walkthrough大規模なMLS/E2EE関連コードをクライアントから削除。チャット設定/タイトルバーUI、MLS API/ラッパー/ストレージ/フック/テストを除去。Applicationのマルチアカウント自動TopUpを廃止。ProfileのopenChatを同期・クライアント内更新へ変更。その他は軽微な整形・キャッシュ仕様(メモリ限定)・文言/空白調整。 Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor User as User
participant Prof as Profile Component
participant State as AppState
participant Nav as Router
User->>Prof: Click "Open Chat"
Prof->>Prof: normalizeActor(name), validate
Prof->>State: setRoom(handle)
Prof->>Nav: navigate("/chat")
Note over Prof,State: 以前のサーバ側ルーム作成呼び出しは削除
sequenceDiagram
autonumber
participant App as Application
participant WS as WebSocket
participant Srv as Server
App->>App: detect mobile, read activeAccount
App->>Srv: registerUser(activeAccount)
App->>WS: connect()
WS-->>App: events
rect rgba(240,240,240,0.5)
Note over App: 旧フロー(全アカウントTopUpの即時実行と5分間隔タイマー)は削除
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Poem
✨ Finishing Touches
🧪 Generate unit tests
🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
Status, Documentation and Community
|
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
app/client/src/components/AddServerForm.tsx (1)
15-31: onAdd が例外を投げた場合にローディングが解除されず UI が固まる
await props.onAdd(...)が失敗すると後続のsetIsLoading(false)・入力クリアが実行されず、ボタンが無期限で無効化されます。ユーザー体験に直結するため早めに修正してください。const handleSubmit = async (e: Event) => { e.preventDefault(); setError(""); if (!url()) { setError("サーバーURLを入力してください"); return; } if (!password()) { setError("パスワードを入力してください"); return; } - setIsLoading(true); - await props.onAdd(url().trim(), password()); - setIsLoading(false); - setUrl(""); - setPassword(""); + setIsLoading(true); + try { + await props.onAdd(url().trim(), password()); + // 成功時のみフィールドをクリア + setUrl(""); + setPassword(""); + } catch (err) { + // 失敗理由を表示(string 以外にも対応) + const msg = err instanceof Error ? err.message : "追加に失敗しました"; + setError(msg); + } finally { + setIsLoading(false); + } };
🧹 Nitpick comments (5)
app/client/src/utils/firebase.ts (1)
10-10: Tauri 判定ロジックの一元化を提案本ファイルでは
__TAURI_IPC__存在チェックを用いていますが、app/client/src/utils/config.tsにはisTauri()ヘルパーがあり、そちらは__TAURI__/__TAURI_INTERNALS__も考慮しています。判定が分散すると条件不一致で挙動差が出る可能性があるため、共通ヘルパーに寄せることを推奨します。適用例:
-import { apiFetch } from "./config.ts"; +import { apiFetch, isTauri as isTauriEnv } from "./config.ts"; ... -const isTauri = typeof window !== "undefined" && "__TAURI_IPC__" in window; +const isTauri = isTauriEnv();Also applies to: 16-16
app/api/routes/posts.ts (1)
231-233: objId の再代入は冗長—シンプル化可
objectIdは直前でString((post as Record<string, unknown>)._id ?? "")によって定義済みです。ここでの|| String((post...))は実質的に同値なので簡略化できます。将来の読みやすさと意図の明確化のため、単純代入を提案します。-const objId = objectId || - String((post as Record<string, unknown>)._id ?? ""); +const objId = objectId;補足: DB 実装上
_idが常に付与される前提であれば問題ありませんが、万一空文字になる可能性がある場合は、WebSocket通知前にif (!objId) return;のガード追加をご検討ください。前段の FASP 送信では truthy チェックがありますが、WS 側では現状無条件送信です。app/client/src/components/Profile.tsx (1)
242-249: 微調整(任意):連続Signal更新のバッチングUI再計算を減らすため、
setRoomとsetAppをbatchでまとめると安定します(イベントハンドラ内なので差は小さいですが一応の最適化)。例(外部参照用):
import { batch } from "solid-js"; const openChat = (): void => { const raw = username()?.trim(); const user = account(); if (!raw || !user) return; const handle = normalizeActor(raw); const normalized = handle.includes("@") ? handle : `${handle}@${getDomain()}`; batch(() => { setRoom(normalized); setApp("chat"); }); };app/client/src/components/microblog/api.ts (2)
374-374: キャッシュ書き込みは await して将来の非同期化に備える現状は同期的な Map 書き込みのみですが、将来的に I/O 等へ拡張した際のフットガンを避けるため
awaitを付与しておくのが安全です。- setCachedUserInfo(identifier, userInfo, accountId); + await setCachedUserInfo(identifier, userInfo, accountId);併せて、型安全性を高めるなら JSON の型付けも推奨です(参考・任意):
- const userInfo = await response.json(); + const userInfo = (await response.json()) as UserInfo;
353-381: 同一 identifier の同時リクエスト重複を抑止する in-flight デデュープを検討短時間に同一ユーザーを複数箇所で解決する場合、現状は都度 API が走ります。軽量な in-flight マップでのデデュープで負荷とレイテンシを下げられます。
実装イメージ(参考、抜粋・追加コード):
// ファイル先頭付近に追加 const inFlightUserFetches = new Map<string, Promise<UserInfo | null>>(); // fetchUserInfo の先頭でキー生成(上記の accountId:identifier 方式と同一に) const key = (accountId ? `${accountId}:` : "") + identifier; const existing = inFlightUserFetches.get(key); if (existing) return await existing; // 実リクエスト処理全体を Promise に包んで登録 const p = (async () => { const cached = await getCachedUserInfo(identifier, accountId); if (cached) return cached; const response = await apiFetch(`/api/users/${encodeURIComponent(identifier)}`); if (!response.ok) throw new Error("Failed to fetch user info"); const userInfo = (await response.json()) as UserInfo; await setCachedUserInfo(identifier, userInfo, accountId); return userInfo; })(); inFlightUserFetches.set(key, p); try { return await p; } finally { inFlightUserFetches.delete(key); }
📜 Review details
Configuration used: Path: .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 (17)
app/api/routes/posts.ts(1 hunks)app/client/src/components/AddServerForm.tsx(2 hunks)app/client/src/components/Application.tsx(1 hunks)app/client/src/components/Profile.tsx(1 hunks)app/client/src/components/Setting/index.tsx(0 hunks)app/client/src/components/chat/ChatSettingsOverlay.tsx(0 hunks)app/client/src/components/chat/ChatTitleBar.tsx(0 hunks)app/client/src/components/e2ee/api.ts(0 hunks)app/client/src/components/e2ee/binding.ts(0 hunks)app/client/src/components/e2ee/mls_message.ts(0 hunks)app/client/src/components/e2ee/mls_test.ts(0 hunks)app/client/src/components/e2ee/mls_wrapper.ts(0 hunks)app/client/src/components/e2ee/storage.ts(0 hunks)app/client/src/components/e2ee/useMLS.ts(0 hunks)app/client/src/components/microblog/api.ts(2 hunks)app/client/src/utils/firebase.ts(1 hunks)app/client/vite.config.mts(1 hunks)
💤 Files with no reviewable changes (10)
- app/client/src/components/e2ee/useMLS.ts
- app/client/src/components/chat/ChatTitleBar.tsx
- app/client/src/components/e2ee/mls_test.ts
- app/client/src/components/chat/ChatSettingsOverlay.tsx
- app/client/src/components/e2ee/binding.ts
- app/client/src/components/e2ee/mls_message.ts
- app/client/src/components/e2ee/storage.ts
- app/client/src/components/Setting/index.tsx
- app/client/src/components/e2ee/mls_wrapper.ts
- app/client/src/components/e2ee/api.ts
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.{ts,tsx}: TypeScriptの型定義を活用する
エラーハンドリングを適切に実装する
Files:
app/client/src/utils/firebase.tsapp/api/routes/posts.tsapp/client/src/components/AddServerForm.tsxapp/client/src/components/Profile.tsxapp/client/src/components/Application.tsxapp/client/src/components/microblog/api.ts
app/api/**/*.ts
📄 CodeRabbit inference engine (CLAUDE.md)
環境変数は常に getEnv(c) を用いてコンテキストから取得する
Files:
app/api/routes/posts.ts
app/api/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Honoフレームワークのミドルウェアパターンに従う
Files:
app/api/routes/posts.ts
🧠 Learnings (1)
📚 Learning: 2025-08-13T10:14:15.274Z
Learnt from: CR
PR: takoserver/takos#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-13T10:14:15.274Z
Learning: Applies to app/api/{activity_handlers.ts,utils/activitypub*.ts} : ActivityPubの仕様に準拠したJSON-LD形式を使用する
Applied to files:
app/client/src/components/microblog/api.ts
🧬 Code graph analysis (3)
app/client/src/utils/firebase.ts (1)
app/client/src/utils/config.ts (1)
isTauri(20-26)
app/api/routes/posts.ts (1)
app/api/routes/ws.ts (1)
broadcast(32-39)
app/client/src/components/microblog/api.ts (1)
app/api/services/user-info.ts (1)
UserInfo(6-12)
🔇 Additional comments (9)
app/client/vite.config.mts (1)
19-23: コメントの整形のみ—動作影響なし (LGTM)説明・HMRの注意点も読みやすくなりました。機能差分は無く、そのままマージで問題ありません。
app/client/src/utils/firebase.ts (1)
57-62: クォートスタイル変更のみ—動作影響なし (LGTM)動作に影響する変更はありません。
app/client/src/components/AddServerForm.tsx (1)
43-45: onInput の改行整形のみ—可読性向上 (LGTM)イベント型注釈もそのままで問題ありません。
Also applies to: 54-56
app/client/src/components/Application.tsx (2)
1-1: 未使用だった import の整理—問題なし (LGTM)
onCleanupの削除に伴う差分で、実装上の影響はありません。
5-5: activeAccount ベースの登録のみへ簡素化—挙動確認のお願いE2EE/トップアップ系の撤去によりアクティブアカウントの
registerUserのみになりました。アカウント切り替え時にcreateEffectが確実に発火し、想定どおり再登録されるかを軽く手動確認しておいてください(HMR/再描画でも多重登録されない前提)。app/client/src/components/Profile.tsx (2)
242-249: E2EE依存の除去に伴う openChat の同期化は方針どおりで良いですサーバー呼び出しを外し、クライアント状態のみに集約した点は本PRの目的(E2EE撤去)と整合しています。
242-249: 要確認: ルーム遅延生成周りのUX/機能回帰クライアント側には明示的なルーム事前生成ロジックが残っておらず、
selectedRoomState/setRoomの呼び出し箇所のみが見つかりました。以下の点を手動で動作検証してください。• 呼び出し箇所
app/client/src/components/Profile.tsx(setRoom(handle))app/client/src/components/home/NotificationsContent.tsx(setRoom(rid))- その他、
Application.tsx/header.tsx/router.ts等でselectedRoomStateを利用• 古いルーム生成API呼び出しはなし
addRoom・createRoom・ensureRoom・openRoomなどの残存呼び出しは検出されず、クライアントからの明示的生成は行われていません。• チャット送信前の存在前提コードなし
ChatSendForm.tsxのprops.sendMessage()呼び出し等、初回送信でもルーム存在を前提とする記述は見当たりません。上記より、クライアント側のコードはサーバーの遅延ルーム生成前提に沿っているようです。
──
◎ 想定される挙動の手動確認
- 存在しないルームID(例:
user@domain/user)でチャット画面を開き、画面描画がエラーなく完了するか- 初回メッセージ送信時にサーバー側でルームが自動生成され、以降のメッセージ送受信が問題なく行えるか
user@domain・userいずれも同一ユーザーとしてルーティングされるか以上の動作確認をお願いします。
app/client/src/components/microblog/api.ts (2)
329-350: メモリ限定キャッシュへの移行方針は妥当(I/O 排除・単純化)永続層を外したことで、I/O レイテンシと失敗モードが減り、フロント側の責務も明確になりました。この方向性自体は賛成です。
329-338: getCachedUserInfo の呼び出しとキャッシュ分離の手動確認をお願いしますripgrep による自動検索では、
fetchUserInfo/getCachedUserInfoの 2 引数呼び出しやアカウント識別子(activeAccount等)の痕跡が検出できませんでした。自動検索結果は必ずしも呼び出し漏れの証明にはならないため、以下を手動でご確認ください。
- 全ての
getCachedUserInfo(identifier, accountId?)呼び出し箇所で、第二引数として適切にaccountIdを渡しているか- 同様に、
fetchUserInfo(identifier, accountId?)の呼び出しにも漏れがないか- 複数アカウント利用が想定されるコンポーネント(マルチテナント UI、アカウント切替機能など)で、キャッシュキーが
accountId:identifierの形式で一貫して分離されているか- キャッシュの登録側(
setCachedUserInfoや同等ロジック)にも同様のキー分離が反映されているか上記の確認後、クロスアカウント汚染のリスクを避けるため、必要に応じてキー生成と呼び出し側を修正してください。
| export const setCachedUserInfo = ( | ||
| identifier: string, | ||
| userInfo: UserInfo, | ||
| accountId?: string, | ||
| ) => { | ||
| _accountId?: string, | ||
| ): Promise<void> => { | ||
| userInfoCache.set(identifier, { | ||
| userInfo, | ||
| timestamp: Date.now(), | ||
| }); | ||
| if (accountId) { | ||
| await saveCacheEntry(accountId, `userInfo:${identifier}`, userInfo); | ||
| } | ||
| return Promise.resolve(); | ||
| }; |
There was a problem hiding this comment.
🛠️ Refactor suggestion
setCachedUserInfo も accountId をキーに含めて対称性を担保する
getCachedUserInfo と対で、こちらもキー分離しないと期待通りにヒットしません。あわせて async 化して明示的 Promise.resolve() を除去できます。
-export const setCachedUserInfo = (
+export const setCachedUserInfo = async (
identifier: string,
userInfo: UserInfo,
- _accountId?: string,
+ accountId?: string,
): Promise<void> => {
- userInfoCache.set(identifier, {
+ const key = accountId ? `${accountId}:${identifier}` : identifier;
+ userInfoCache.set(key, {
userInfo,
timestamp: Date.now(),
});
- return Promise.resolve();
+ return;
};📝 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.
| export const setCachedUserInfo = ( | |
| identifier: string, | |
| userInfo: UserInfo, | |
| accountId?: string, | |
| ) => { | |
| _accountId?: string, | |
| ): Promise<void> => { | |
| userInfoCache.set(identifier, { | |
| userInfo, | |
| timestamp: Date.now(), | |
| }); | |
| if (accountId) { | |
| await saveCacheEntry(accountId, `userInfo:${identifier}`, userInfo); | |
| } | |
| return Promise.resolve(); | |
| }; | |
| export const setCachedUserInfo = async ( | |
| identifier: string, | |
| userInfo: UserInfo, | |
| accountId?: string, | |
| ): Promise<void> => { | |
| const key = accountId ? `${accountId}:${identifier}` : identifier; | |
| userInfoCache.set(key, { | |
| userInfo, | |
| timestamp: Date.now(), | |
| }); | |
| return; | |
| }; |
🤖 Prompt for AI Agents
In app/client/src/components/microblog/api.ts around lines 340 to 350,
setCachedUserInfo currently only keys by identifier and returns
Promise.resolve(); update it to build the cache key the same way
getCachedUserInfo does (include accountId to separate caches, e.g. use
`${accountId ?? ''}:${identifier}` or the same helper used elsewhere), make the
function async and remove the explicit Promise.resolve(), and store the entry
under that composite key with userInfo and timestamp so lookups will hit
correctly.
| const openChat = () => { | ||
| const name = username(); | ||
| const user = account(); | ||
| if (!name || !user) return; | ||
| const handle = normalizeActor(name); | ||
| const me = `${user.userName}@${getDomain()}`; | ||
| await addRoom( | ||
| user.id, | ||
| { id: handle, name: handle, members: [handle, me] }, | ||
| { from: me, content: "hi", to: [handle, me] }, | ||
| ); | ||
| setRoom(handle); | ||
| setApp("chat"); | ||
| }; |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
ローカル名のみのケースで '@Domain' が付与されず、ルームキーの一意性が崩れる可能性
現在の normalizeActor は 'http' からの変換以外では入力をそのまま返すため、alice のようなローカル名がそのまま setRoom("alice") に渡る可能性があります。アプリ内の他箇所が user@domain を前提にしていると、同名ユーザーの衝突やルーム解決ミスが発生します。入力を trim し、@ を含まない場合は getDomain() を補完して完全ハンドルに正規化するのが安全です。ついでに明示的な戻り値型を付けて型の意図も固定しておきましょう。
以下の差分を提案します(この範囲内のみで完結します):
- const openChat = () => {
- const name = username();
- const user = account();
- if (!name || !user) return;
- const handle = normalizeActor(name);
- setRoom(handle);
- setApp("chat");
- };
+ const openChat = (): void => {
+ const raw = username()?.trim();
+ const user = account();
+ if (!raw || !user) return;
+ const handle = normalizeActor(raw);
+ const normalized = handle.includes("@") ? handle : `${handle}@${getDomain()}`;
+ setRoom(normalized);
+ setApp("chat");
+ };補足(代替案):
- 上記の代わりに、normalizeActor(行229-240)側で「
@が無ければ@${getDomain()}を付与」する仕様へ寄せると呼び出し側がシンプルになります(どちらでも可)。
normalizeActorの実装を確認するため、以下のスクリプトを実行してください。
🏁 Script executed:
#!/bin/bash
# normalizeActorの定義箇所と実装内容を検索
rg -n "normalizeActor" -C3 --glob 'app/client/src/**/*.ts*'Length of output: 1025
normalizeActor の完全な実装と、getDomain の定義箇所を確認するため、以下のスクリプトを実行してください。
#!/bin/bash
# Profile.tsx 内の normalizeActor 関数実装を表示
sed -n '229,247p' app/client/src/components/Profile.tsx
# getDomain 関数の定義箇所を検索
rg -n "getDomain" -C3 --glob 'app/client/src/**/*.ts*'normalizeActor の実装と getDomain の定義を正確に把握するため、以下のスクリプトを実行してください。
🏁 Script executed:
#!/bin/bash
# Profile.tsx 内で normalizeActor の実装部分を表示
sed -n '229,240p' app/client/src/components/Profile.tsx
# getDomain 関数の定義箇所を検索
rg -n "getDomain" -C3 --glob 'app/client/src/**/*.ts*'Length of output: 19007
openChat でローカル名のみの場合にドメインを補完し、一意のルームキーを確保しましょう
normalizeActor は HTTP URL 以外の入力をそのまま返すため、username() が "alice" のようにドメインを含まない場合、setRoom("alice") となりうることを確認しました。アプリ内の他箇所では @${getDomain()} を付与した完全ハンドルを前提としているため、ここでも同様にドメインを補完しておくのが安全です。
対象箇所:
app/client/src/components/Profile.tsxのopenChat関数定義付近(およそ 242–249 行目)
提案差分:
- const openChat = () => {
- const name = username();
- const user = account();
- if (!name || !user) return;
- const handle = normalizeActor(name);
- setRoom(handle);
- setApp("chat");
- };
+ const openChat = (): void => {
+ const raw = username()?.trim();
+ const user = account();
+ if (!raw || !user) return;
+ const handle = normalizeActor(raw);
+ // ローカル名のみならドメインを補完
+ const normalized = handle.includes("@")
+ ? handle
+ : `${handle}@${getDomain()}`;
+ setRoom(normalized);
+ setApp("chat");
+ };この修正で、ドメインを含まないローカル名でも常に user@domain 形式の一意ハンドルをルームキーに使えるようになります。
📝 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.
| const openChat = () => { | |
| const name = username(); | |
| const user = account(); | |
| if (!name || !user) return; | |
| const handle = normalizeActor(name); | |
| const me = `${user.userName}@${getDomain()}`; | |
| await addRoom( | |
| user.id, | |
| { id: handle, name: handle, members: [handle, me] }, | |
| { from: me, content: "hi", to: [handle, me] }, | |
| ); | |
| setRoom(handle); | |
| setApp("chat"); | |
| }; | |
| const openChat = (): void => { | |
| const raw = username()?.trim(); | |
| const user = account(); | |
| if (!raw || !user) return; | |
| const handle = normalizeActor(raw); | |
| // ローカル名のみならドメインを補完 | |
| const normalized = handle.includes("@") | |
| ? handle | |
| : `${handle}@${getDomain()}`; | |
| setRoom(normalized); | |
| setApp("chat"); | |
| }; |
🤖 Prompt for AI Agents
In app/client/src/components/Profile.tsx around lines 242 to 249, openChat calls
normalizeActor and may get a local name like "alice" which lacks a domain;
update openChat to detect when the normalized handle is not an HTTP URL and does
not already contain '@', and then append "@${getDomain()}" (using the existing
getDomain() helper) so the room key is always in the "user@domain" form before
calling setRoom; ensure you import/get access to getDomain() if needed and leave
handles that are URLs or already include a domain unchanged.
概要
テスト
deno fmtdeno lint(既存コードに起因するエラーあり)https://chatgpt.com/codex/tasks/task_e_68a91a6ca8288328a0e37e92331b7a37
Summary by CodeRabbit