refactor(frontend): migrate to useApi hook and add TSDoc documentation#140
refactor(frontend): migrate to useApi hook and add TSDoc documentation#140
Conversation
- Introduce useApi hook for centralized authentication state management - Replace legacy tokenAtom + apiClient.setToken() pattern across 40+ files - Add comprehensive TSDoc documentation to components, hooks, and types - Split large components into smaller, focused modules: - NoteCard -> NoteCard + NoteCardMenu - NoteComposer -> NoteComposer + Attachments/Drafts/SchedulePicker - EmojiPicker -> EmojiPicker + emojiData - Consolidate duplicate getApiBase() functions into client.ts - Fix unused catch parameter lint warnings (ES2019 empty catch)
|
@coderabbitai review |
📝 WalkthroughWalkthroughフロントエンドで認証/API 呼び出しを Changes
Sequence Diagram(s)sequenceDiagram
participant Component
participant useApi
participant apiClient
participant Server
rect rgba(230,120,120,0.5)
Note over Component,Server: 旧フロー(個別トークン設定)
Component->>apiClient: apiClient.setToken(token)
Component->>apiClient: apiClient.get("/api/...")
apiClient->>Server: GET /api/... (Authorization: Bearer token)
Server-->>apiClient: response
apiClient-->>Component: data
end
rect rgba(120,200,150,0.5)
Note over Component,Server: 新フロー(useApi 経由)
Component->>useApi: const api = useApi()
Component->>useApi: api.get("/api/...")
useApi->>apiClient: ensure token set on apiClient
useApi->>apiClient: apiClient.get("/api/...")
apiClient->>Server: GET /api/... (Authorization: Bearer token)
Server-->>apiClient: response
apiClient-->>useApi: data
useApi-->>Component: typed data
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested labels
詩
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
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: 12
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (8)
packages/frontend/src/pages/mod/audit-logs.tsx (1)
117-138: エラーハンドリングのロジックに問題があります:403 ステータスのチェックが到達不能です。
loadLogs()関数は内部で try-catch を持ち、エラーを自身で処理して state に設定しています(行 109-111)。エラーを再スローしないため、checkAccess内の外側の catch ブロック(行 126-134)は到達不能なデッドコードとなり、err.status === 403のチェックは実行されません。結果として、モデレーター権限がない場合に「Moderator access required」のエラーメッセージが表示されず、代わりに汎用的な「Failed to load audit logs」が表示されます。
🔧 提案する修正方法
方法 1:
loadLogsでエラーを再スローするconst loadLogs = useCallback(async () => { if (!api.isAuthenticated) return; setIsLoading(true); + setError(null); try { const params = new URLSearchParams(); params.set("limit", String(limit)); params.set("offset", String(offset)); if (actionFilter) params.set("action", actionFilter); if (targetTypeFilter) params.set("targetType", targetTypeFilter); const response = await api.get<AuditLogsResponse>(`/api/mod/audit-logs?${params}`); setLogs(response.logs); setTotal(response.total); } catch (err) { console.error("Failed to load audit logs:", err); - setError("Failed to load audit logs"); + throw err; // 呼び出し元でハンドリング } finally { setIsLoading(false); } }, [api, offset, actionFilter, targetTypeFilter]);方法 2: アクセスチェックを別の API 呼び出しで行う
初期化時のみ 403 チェックを行い、その後のリフレッシュでは
loadLogsの現行動作を維持する設計に変更。packages/frontend/src/pages/admin/gone-users.tsx (1)
57-71: リロード時にローディング状態が表示されない問題があります。
loadUsers関数の先頭でsetIsLoading(true)が呼ばれていないため、初回ロード後のリロード時(180-181行目のonReload経由)にローディングインジケーターが表示されません。
isLoadingの初期値はtrueなので初回ロードは正しく動作しますが、finallyでfalseに設定された後は、再度trueに戻されることがありません。🔧 修正案
const loadUsers = useCallback(async () => { if (!api.token) return; + setIsLoading(true); try { const response = await api.get<GoneUsersResponse>("/api/admin/gone-users?limit=100"); setUsers(response.users); setTotal(response.total); setError(null); } catch (err) { console.error("Failed to load gone users:", err); setError("Failed to load gone users"); } finally { setIsLoading(false); } }, [api]);packages/frontend/src/lib/types/uiSettings.ts (1)
58-77: フロントエンド UISettings 型定義の同期フィールドを明確にしてください。フロントエンド UISettings には
notificationSound、notificationVolume、notificationSoundsByTypeが含まれていますが、バックエンドの API 検証(routes/users.ts:542-600)でこれらのフィールドは処理されず、無視されます。つまり、これらのフィールドはバックエンドに永続化されません。一方、バックエンドは
languageとdisabledPushNotificationTypesを保持していますが、フロントエンドの UISettings 型には含まれていません。推奨事項:
- フロントエンド UISettings 型を、実際にバックエンドで永続化されるフィールドのみに制限するか、
- または、同期フィールドとクライアント専用フィールドを明確に区別するために、型定義をリファクタリングしてください。
現在のまま放置すると、開発者がこれらのフィールドが永続化されると誤解する可能性があります。
packages/frontend/src/lib/api/reactions.ts (1)
11-18:Reactionインターフェースの重複を解決してください。フロントエンドで定義されている
Reactionインターフェースは、packages/shared/src/types/reaction.tsの共有型定義と重複しています。バックエンドは既にこの共有型を使用していますが、フロントエンドのみが独立した定義を保持しており、型の不整合と将来的なメンテナンス問題を招きます。共有型を使用するか、または型の差異がある場合は明確に分離する必要があります。
packages/frontend/src/components/notification/NotificationList.tsx (1)
41-49:hasMore判定が古いクロージャ参照に依存し、無限スクロールが実質的に機能しません。
onLoadMore内でnotifications.lengthを比較すると、loadMore()完了後も state の更新がまだ反映されておらず、クロージャ内のnotificationsは古い参照のままです。結果として新規データ取得があってもnotifications.length === prevCountが true になり、一度の呼び出しでhasMoreが false に固定されます。更新後に判定するよう修正してください。
useRefで前回の件数を記録し、useEffectで loading が false になった時点で判定するパターンが確実です。修正案
-import { useState } from "react"; +import { useEffect, useRef, useState } from "react"; ... + const prevCountRef = useRef(0); const loadMoreRef = useInfiniteScroll({ onLoadMore: async () => { - const prevCount = notifications.length; + prevCountRef.current = notifications.length; await loadMore(); - // If no new notifications loaded, we've reached the end - if (notifications.length === prevCount) { - setHasMore(false); - } }, isLoading: loading, hasMore, }); + + useEffect(() => { + if (!loading && notifications.length === prevCountRef.current) { + setHasMore(false); + } + }, [loading, notifications.length]);packages/frontend/src/components/settings/AccountDeletionSection.tsx (1)
124-156: React Aria の TextField に置き換えてください。このファイルは
packages/frontend/src/components/配下のため、キーボード操作やスクリーンリーダ対応に対応した React Aria Components を使う必要があります。確認用テキストとパスワード入力はTextFieldコンポーネントで置き換えてください。また、現在のコードは separate な<label>要素を使用していますが、aria-label属性がないため、スクリーンリーダーで正しく読み上げられない可能性があります。TextFieldへの置き換え時に、aria-labelプロップでアクセシビリティを確保してください。packages/frontend/src/pages/settings.tsx (1)
95-111:usersApi.updateMe()がトークン設定をバイパスしている可能性があります。
api.tokenでガードしていても、usersApi.updateMe()がapiフックを経由せず直接apiClientを使用するため、グローバルなapiClient.tokenが設定されていない場合があります。useApi()フックのpatchメソッドを使用するか、usersApi.updateMe()の前に明示的にapiClient.setToken(api.token)を呼び出すことで対応できます。packages/frontend/src/components/pages/ListDetailPageClient.tsx (1)
85-103: 未認証時に古いトークンが残る可能性があります。
isAuthenticatedが false の場合にapiClient.setTokenを呼ばないため、ログアウト後でも旧トークンが残ってlistsApi.showに付与される恐れがあります。未認証表示のはずが認証付きでアクセスできる状態になる可能性があるので、tokenが null の場合も明示的にセットしてクリアしてください。🐛 修正案
- if (isAuthenticated) { - apiClient.setToken(token!); - } + apiClient.setToken(token);
🤖 Fix all issues with AI agents
In `@packages/frontend/src/components/note/NoteCardMenu.tsx`:
- Around line 132-229: The plain <button> elements inside NoteCardMenu.tsx (the
share submenu trigger and all submenu/action buttons that call handleCopyLink,
handleOpenInNewTab, handleNativeShare, onDeleteRequest, onReportRequest and
toggle showShareMenu/showMoreMenu) lack proper keyboard/focus and screen-reader
support; replace them with accessible menu components (react-aria-components'
MenuTrigger/Menu/MenuItem) following the pattern in ListCard.tsx, or at minimum
wrap each raw <button> with the existing Button component so they inherit
focus/aria behavior, ensure Menu/MenuItem or Button get proper
aria-labels/role/aria-expanded where applicable and preserve existing handlers
and className styling, and keep menuRef, setShowMoreMenu and setShowShareMenu
logic intact so open/close state remains unchanged.
In `@packages/frontend/src/components/note/NoteComposerAttachments.tsx`:
- Around line 3-6: In NoteComposerAttachments.tsx the code currently calls
URL.createObjectURL during render (creating a memory leak) and uses plain
elements for delete actions; fix by memoizing preview URLs (for DriveFile
previews) with useMemo so createObjectURL is only called when the file object
changes, and add a useEffect cleanup that calls URL.revokeObjectURL for those
memoized URLs; update the component (NoteComposerAttachments) to stop creating
object URLs in render and ensure getProxiedImageUrl usage remains for remote
images; also replace the existing delete buttons (the elements handling
removeAttachment / onRemove in the component) with React Aria Components' Button
so keyboard navigation and focus management follow the coding guidelines.
- Around line 48-55: Replace the plain <button> remove controls in
NoteComposerAttachments.tsx (the instances that call onRemoveFile) with the
shared Button component from packages/frontend/src/components/ui/Button.tsx; use
Button props variant="ghost", size="sm", onPress={() => onRemoveFile(index)},
aria-label={t`Remove image ${index + 1}`} and move the positioning/visibility
classes into className ("absolute top-1 right-1 w-6 h-6 opacity-0
group-hover:opacity-100 transition-opacity"), keep the <X /> icon as the child,
remove the native type/onClick usage, and add the Button import at the top of
the file so both removal buttons use the React Aria–based Button component for
accessibility.
In `@packages/frontend/src/components/note/NoteComposerDrafts.tsx`:
- Around line 45-116: The draft list uses a div with role="button" and manual
onKeyDown which only handles Enter and allows event propagation from the nested
delete button, so replace the clickable draft item with an accessible
interactive component (e.g., React Aria's Button or ListBox item using
useButton/useListBoxItem) so it handles both Enter and Space, proper focus
management, and ARIA semantics; wire the item's activation to the existing
onLoadDraft(draft.id) and ensure the nested delete button (onDeleteDraft) calls
e.stopPropagation() (or uses separate React Aria Button) to prevent the parent
activation from firing. Reference: NoteComposerDrafts.tsx draft mapping, the
onLoadDraft and onDeleteDraft handlers, currentDraftId and drafts array.
In `@packages/frontend/src/components/note/NoteComposerSchedulePicker.tsx`:
- Around line 45-59: The native <button> in NoteComposerSchedulePicker should be
replaced with the React Aria wrapper Button (already imported) to preserve
focus/keyboard/screen-reader behavior: replace the element that uses onClick,
disabled, title and type with the Button component, change onClick={onToggle} →
onPress={onToggle}, change disabled={isDisabled} → isDisabled={isDisabled}, keep
aria-label={t`Schedule post`} and aria-expanded={isOpen}, and preserve the
className and child <Clock />; ensure no duplicate title/type props remain.
- Around line 33-44: The datetime-local input in NoteComposerSchedulePicker is
using scheduledAt?.toISOString() (UTC) for the min/value which misaligns with
local time; update the component to convert the Date to a local datetime-local
string by applying the local timezone offset (e.g., creating a Date adjusted by
date.getTimezoneOffset() and formatting as "YYYY-MM-DDTHH:MM") and use that
local-formatted string for the input's value and min props (refer to
NoteComposerSchedulePicker and the scheduledAt prop handling to locate and
replace the toISOString usage).
In `@packages/frontend/src/components/note/ReactionPicker.tsx`:
- Around line 110-115: The component documentation comment for ReactionPicker is
out of sync: it mentions a "heart icon" but the implementation uses the
SmilePlus icon (SmilePlus) inside the ReactionPicker component; either update
the docstring to describe the SmilePlus icon and emoji picker behavior, or
replace the SmilePlus usage with the intended heart icon component so the code
matches the comment—locate the ReactionPicker component and the SmilePlus
reference and change the doc or the icon accordingly.
- Line 118: The selectedReactions parameter declared on ReactionButtonProps is
not used and not passed to EmojiPicker, so either implement highlighting or
remove it: if you want highlighting, add selectedReactions to EmojiPickerProps
and accept it in the EmojiPicker component, then update ReactionButton (in
ReactionPicker.tsx) to pass the selectedReactions prop through to EmojiPicker
and use it to apply a highlight state/class when rendering each reaction (e.g.,
check selectedReactions.includes(reactionId) or similar); if you don't need
highlighting, remove selectedReactions from ReactionButtonProps and the related
doc comment (lines referencing "現在のリアクション") and any unused variables in
ReactionButton to keep the API consistent.
In `@packages/frontend/src/components/pages/MessageThreadPageClient.tsx`:
- Around line 258-262: Replace the manual token handling and direct client call
in the session restore block: instead of calling apiClient.setToken and
apiClient.get, obtain the api object from useApi (e.g. const { api } =
useApi(...) or however useApi exposes it), call api.get("/api/auth/session"),
and pass the returned user into setCurrentUser; also update the hook/component
dependency array to include the api object so the effect re-runs correctly (and
remove any direct references to apiClient.setToken and apiClient.get).
- Around line 134-136: The current mix of useApi token handling and direct
apiClient.setToken calls is inconsistent with notesApi usage; either switch to
using the useApi hook's methods directly (e.g., call useApi() and use
api.post(...) instead of manipulating apiClient) or mirror NoteComposer.tsx by
reading the token from tokenAtom and setting it before calling notesApi. Locate
the token handling around useApi and apiClient.setToken in
MessageThreadPageClient.tsx and choose one pattern: replace manual
apiClient.setToken(token!) with direct useApi().post(...) calls for API
requests, or obtain the token from tokenAtom and apply it consistently before
invoking notesApi functions so all API calls follow the same auth pattern.
In `@packages/frontend/src/components/ui/ConfirmDialog.tsx`:
- Around line 64-84: Replace the current plain div-based ConfirmDialog with a
React Aria ModalOverlay/Dialog implementation: wrap the dialog content with
React Aria's ModalOverlay and Dialog (matching the pattern in
ListDeleteConfirmDialog.tsx), use the aria/dialog hooks to manage focus trap and
Esc handling, add aria-modal attribute and proper role via the Dialog component,
and wire the existing props and handlers (title, message, cancelText,
confirmText, loadingText, isLoading, onClose, onConfirm, confirmVariant) into
the Aria dialog buttons so behavior and disabled/loading states remain
identical; follow the ListDeleteConfirmDialog.tsx structure for lifecycle/focus
management and ensure onClose is called for cancel/Escape and onConfirm for
confirm action.
In `@packages/frontend/src/pages/admin/contacts.tsx`:
- Around line 25-26: The API helper functions (e.g., listContactThreadsAdmin)
are being called directly without setting the auth token from useApi(), causing
401s; either ensure the helper receives and applies the token (call
apiClient.setToken(token) inside those helpers or pass api.token into
listContactThreadsAdmin) or replace direct helper usage with the authenticated
wrapper methods returned by useApi (use api.get/post/patch/delete with api.token
already set). Update calls to use one consistent approach (reference: useApi,
api, api.token, listContactThreadsAdmin, apiClient.setToken,
api.get/post/delete).
🧹 Nitpick comments (35)
packages/frontend/src/components/pages/ContactThreadPageClient.tsx (1)
281-289: アクセシビリティ:aria-labelを追加してください。この
textareaはplaceholderのみに依存しており、aria-label属性がありません。スクリーンリーダーユーザーのために、明示的なラベルを追加することを推奨します。♿ 推奨される修正
<textarea value={replyText} onChange={(e) => setReplyText(e.target.value)} placeholder={t`Type your message...`} + aria-label={t`Reply message`} maxLength={10000} rows={3} className="flex-1 px-3 py-2 border border-(--border-color) rounded-lg bg-(--bg-primary) text-(--text-primary) focus:ring-2 focus:ring-primary-500 focus:border-primary-500 resize-none" disabled={isSending} />As per coding guidelines: "Always add aria-label to inputs; do not rely on placeholder alone for accessibility"
packages/frontend/src/components/settings/PushNotificationSection.tsx (1)
193-199: 任意:useApiフックへの移行を検討してください。このPRの目的として
useApiフックへの移行が挙げられていますが、このファイルではusersApiを直接使用しています。ドキュメント変更のみのスコープであれば、後続のPRで対応することも検討できます。packages/frontend/src/components/user/RoleBadge.tsx (1)
36-49: 3文字の短縮形hex colorに対応していない可能性があります。
#fffや#abcなどの3文字形式のhex colorが渡された場合、substringが期待通りに動作せず、parseIntがNaNを返します。これによりコントラスト計算が不正確になる可能性があります。♻️ 短縮形hex colorへの対応案
function isLightColor(hexColor: string): boolean { // Remove # if present - const hex = hexColor.replace("#", ""); + let hex = hexColor.replace("#", ""); + + // Expand 3-character shorthand to 6-character format + if (hex.length === 3) { + hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2]; + } // Parse RGB values const r = parseInt(hex.substring(0, 2), 16);packages/frontend/src/pages/mod/audit-logs.tsx (2)
95-115:api.tokenの代わりにapi.isAuthenticatedの使用を検討してください。
useApiフックは認証チェック用にisAuthenticatedプロパティを提供しています。boolean チェックの意図を明確にするため、こちらを使用する方がセマンティックに優れています。♻️ 提案する修正
const loadLogs = useCallback(async () => { - if (!api.token) return; + if (!api.isAuthenticated) return;
119-122:api.isAuthenticatedを使用してください。一貫性のため、ここでも
api.tokenではなくapi.isAuthenticatedを使用することを推奨します。♻️ 提案する修正
const checkAccess = async () => { - if (!api.token) { + if (!api.isAuthenticated) { window.location.href = "/login"; return; }packages/frontend/src/components/ui/EmojiPicker.tsx (2)
259-267:SearchFieldコンポーネントの使用を検討してくださいコーディングガイドラインでは、プレーンな HTML input よりも React Aria の
SearchFieldを推奨しています。SearchFieldを使用することで、キーボードナビゲーションやフォーカス管理が改善されます。♻️ 提案するリファクタリング
+import { SearchField, Input, Label } from "react-aria-components"; - <input - ref={searchInputRef} - type="text" - value={searchQuery} - onChange={(e) => setSearchQuery(e.target.value)} - placeholder={t`Search emoji...`} - aria-label={t`Search emoji`} - className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 placeholder:text-gray-400 dark:placeholder:text-gray-500 rounded-md focus:outline-none focus:ring-2 focus:ring-primary-500 text-sm" - /> + <SearchField + value={searchQuery} + onChange={setSearchQuery} + aria-label={t`Search emoji`} + className="w-full" + > + <Label className="sr-only">{t`Search emoji`}</Label> + <Input + ref={searchInputRef} + placeholder={t`Search emoji...`} + className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 placeholder:text-gray-400 dark:placeholder:text-gray-500 rounded-md focus:outline-none focus:ring-2 focus:ring-primary-500 text-sm" + /> + </SearchField>As per coding guidelines: "Prefer TextField, SearchField, RadioGroup, and Select components over custom state-based implementations and plain HTML inputs"
249-255: 閉じるボタンに React Aria のButtonを使用することを検討モーダル内の閉じるボタンに React Aria の
Buttonコンポーネントを使用すると、キーボードナビゲーションとスクリーンリーダーのサポートが向上します。♻️ 提案するリファクタリング
- <button + <Button + variant="ghost" onClick={close} className="p-1 rounded-md hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors" aria-label="Close" > <X className="w-5 h-5 text-gray-500 dark:text-gray-400" /> - </button> + </Button>packages/frontend/src/components/ui/emojiData.ts (1)
21-26:icon関数の型定義を改善可能
typeof Clockの代わりにlucide-reactが提供するLucideIcon型を使用すると、より明示的で再利用可能な型定義になります。♻️ 提案するリファクタリング
+import type { LucideIcon } from "lucide-react"; /** * Create an icon element with standard size + * `@param` Icon - A Lucide icon component + * `@returns` A React element with standardized sizing */ -function icon(Icon: typeof Clock) { +function icon(Icon: LucideIcon) { return createElement(Icon, { className: "w-5 h-5" }); }packages/frontend/src/pages/admin/gone-users.tsx (1)
71-71: 依存配列の指定について(オプションの改善)
[api]を依存配列に指定することは機能的に正しいですが、より正確には[api.get, api.token]と記述することもできます。ただし、useApiフックが返すオブジェクトはuseMemoでメモ化されており、tokenが変更された場合にのみオブジェクト参照が変わるため、現在の実装でも問題ありません。packages/frontend/src/lib/types/uiSettings.ts (2)
45-48: volumeフィールドの範囲制約について。
volume: numberには// 0-100というコメントがありますが、型レベルでの制約はありません。ランタイムでのバリデーション(例:設定保存時やUI入力時)で0-100の範囲を確認することを推奨します。
120-160: ラベル定義は適切ですが、国際化について考慮してください。ラベルは現在英語でハードコードされています。将来的にi18n対応が必要な場合は、翻訳キーを使用するか、ラベル生成を翻訳システムに委譲することを検討してください。現時点では問題ありません。
packages/frontend/src/components/notification/NotificationBell.tsx (1)
62-79: React Ariaコンポーネントの使用を検討してください。現在の実装ではネイティブの
<button>要素とカスタムのクリック外検出・Escapeキーハンドリングを使用していますが、コーディングガイドラインではインタラクティブなUI要素にReact Aria Componentsの使用を推奨しています。特にダイアログパネル(
role="dialog")については、React AriaのPopoverまたはDialogTriggerを使用することで、以下の利点があります:
- フォーカストラップの自動管理
- 画面リーダーへの適切なアナウンス
- クリック外・Escapeキーのハンドリングの標準化
現在の実装でも基本的なアクセシビリティは確保されているため、これはオプションの改善提案です。
Also applies to: 82-118
packages/frontend/src/components/notification/NotificationItem.tsx (1)
211-224:InteractiveCardコンポーネントの使用を検討してください。コーディングガイドラインによると、クリック/プレスハンドラーを持つインタラクティブなカードには
InteractiveCardコンポーネントを使用することが推奨されています。現在の実装ではdivにrole="button"とtabIndexを手動で設定していますが、これはReact AriaのButtonをラップしたInteractiveCardで置き換えることで、より堅牢なアクセシビリティサポートが得られます。現在の実装でも基本的なキーボードナビゲーション(Enter/Space)は実装されていますが、
InteractiveCardを使用することで:
- プレス状態の視覚的フィードバック
- フォーカス管理の標準化
- 画面リーダーとの互換性向上
が期待できます。
As per coding guidelines: "For interactive cards with click/press handlers, use InteractiveCard component which wraps React Aria's Button for proper accessibility"
packages/frontend/src/components/deck/AddColumnDialog.tsx (1)
136-140: 非null アサーション(token!)の使用について確認してください。
isAuthenticatedのチェック後にtoken!を使用していますが、useApiの実装ではisAuthenticated = Boolean(token)となっているため、このパターンは安全です。ただし、より型安全なアプローチとして、ガード条件で直接
tokenをチェックする方法も検討できます:if (!currentUser || !token) return;これにより、TypeScriptが自動的に
tokenをstringとして推論し、非nullアサーションが不要になります。現在の実装でも問題はありませんが、将来の保守性を考慮すると検討に値します。packages/frontend/src/components/admin/AdminLayout.tsx (1)
239-256: ナビゲーションボタンにReact Aria Buttonの使用を検討してください。
AdminSidebarのカテゴリ展開ボタンとMobileAdminNavの閉じるボタンはネイティブの<button>要素を使用しています。コーディングガイドラインではインタラクティブなUI要素にReact Aria Componentsの使用を推奨しています。現在の実装でも動作上の問題はありませんが、React Ariaの
Buttonを使用することで、プレス状態やフォーカス管理が統一されます。これはオプションの改善提案です。Also applies to: 316-321
packages/frontend/src/components/settings/UISettingsSection.tsx (1)
60-92: SelectGroup の選択UIは React Aria へ寄せるとアクセシビリティが上がります。
ボタン集合だと選択状態のARIA伝播やキーボード操作が不足しやすいため、RadioGroup/ToggleButton など React Aria Components の利用を検討してください。
As per coding guidelines: Use React Aria Components for interactive UI elements requiring keyboard navigation, focus management, screen reader announcements, and complex interaction patterns (buttons, inputs, dialogs, menus, selects, checkboxes, switches, tabs).packages/frontend/src/components/settings/AccountMigrationSection.tsx (2)
264-269: TextFieldにaria-labelを追加してください。コーディングガイドラインに従い、
placeholderのみに依存せず、aria-labelを追加する必要があります。♿ 修正案
<TextField value={newAliasUri} onChange={setNewAliasUri} placeholder={t`https://example.com/users/username`} className="flex-1" + aria-label={t`Account alias URI`} />As per coding guidelines: "Always add aria-label to inputs; do not rely on placeholder alone for accessibility"
332-341: TextFieldにaria-labelを追加してください。
placeholderだけではスクリーンリーダーでの識別が不十分です。♿ 修正案
<TextField value={targetAccountUri} onChange={(val) => { setTargetAccountUri(val); setValidation(null); }} placeholder={t`https://newserver.com/users/newusername`} className="flex-1" isDisabled={!status?.canMigrate} + aria-label={t`Target account URI`} />As per coding guidelines: "Always add aria-label to inputs; do not rely on placeholder alone for accessibility"
packages/frontend/src/pages/search.tsx (1)
340-346: 検索入力にaria-labelを追加し、TextFieldの使用を検討してください。現在のプレーンHTML
<input>はplaceholderのみに依存しています。アクセシビリティのためにaria-labelを追加するか、コーディングガイドラインに従ってTextFieldコンポーネントの使用を検討してください。♿ 修正案(aria-label追加)
<input type="text" value={query} onChange={(e) => setQuery(e.target.value)} placeholder={t`Search users... (e.g., alice or alice@mastodon.social)`} className="w-full pl-10 pr-4 py-3 border border-(--border-color) rounded-lg bg-(--bg-primary) text-(--text-primary) placeholder:text-(--text-muted) focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent" + aria-label={t`Search users`} />As per coding guidelines: "Always add aria-label to inputs; do not rely on placeholder alone for accessibility" and "Prefer TextField, SearchField, RadioGroup, and Select components over custom state-based implementations and plain HTML inputs"
packages/frontend/src/pages/mod/instances.tsx (3)
268-275: 入力フィールドにaria-labelを追加してください。プレーンHTML入力がplaceholderのみに依存しています。
♿ 修正案
<input type="text" value={checkHost} onChange={(e) => setCheckHost(e.target.value)} onKeyDown={(e) => e.key === "Enter" && handleCheckBlock()} placeholder={t`Enter instance hostname (e.g., spam.example.com)`} className="w-full px-4 py-2 border border-(--border-color) rounded-lg bg-(--bg-primary) text-(--text-primary) focus:outline-none focus:ring-2 focus:ring-primary-500" + aria-label={t`Instance hostname to check`} />As per coding guidelines: "Always add aria-label to inputs; do not rely on placeholder alone for accessibility"
362-368: インスタンスホスト名入力にaria-labelを追加してください。♿ 修正案
<input type="text" value={newHost} onChange={(e) => setNewHost(e.target.value)} placeholder={t`spam.example.com`} className="w-full px-3 py-2 border border-(--border-color) rounded-lg bg-(--bg-primary) text-(--text-primary) focus:outline-none focus:ring-2 focus:ring-primary-500" + aria-label={t`Instance hostname`} />As per coding guidelines: "Always add aria-label to inputs; do not rely on placeholder alone for accessibility"
374-380: 理由入力にaria-labelを追加してください。♿ 修正案
<textarea value={newReason} onChange={(e) => setNewReason(e.target.value)} placeholder={t`Reason for blocking this instance...`} className="w-full px-3 py-2 border border-(--border-color) rounded-lg bg-(--bg-primary) text-(--text-primary) focus:outline-none focus:ring-2 focus:ring-primary-500 resize-none" rows={2} + aria-label={t`Reason for blocking`} />As per coding guidelines: "Always add aria-label to inputs; do not rely on placeholder alone for accessibility"
packages/frontend/src/pages/admin/reports.tsx (2)
514-520: テキストエリアにaria-labelを追加してください。♿ 修正案
<textarea value={resolutionText} onChange={(e) => setResolutionText(e.target.value)} placeholder={t`Add notes about the action taken...`} className="w-full px-3 py-2 border border-(--border-color) rounded-lg bg-(--bg-primary) text-(--text-primary) focus:outline-none focus:ring-2 focus:ring-primary-500 resize-none" rows={3} + aria-label={t`Resolution notes`} />As per coding guidelines: "Always add aria-label to inputs; do not rely on placeholder alone for accessibility"
370-386: モーダルダイアログのアクセシビリティを検討してください。現在のモーダル実装にはフォーカス管理やキーボードナビゲーション(Escキーで閉じるなど)が含まれていません。コーディングガイドラインに従い、React Aria の Dialog コンポーネントの使用を検討してください。
As per coding guidelines: "Use React Aria Components for interactive UI elements requiring keyboard navigation, focus management, screen reader announcements, and complex interaction patterns (buttons, inputs, dialogs, menus, selects, checkboxes, switches, tabs)"
packages/frontend/src/pages/admin/invitations.tsx (3)
329-331:onClickではなくonPressを使用してください。このファイル内の他の
Buttonコンポーネント(例: 310行目、390行目など)はonPressを使用していますが、ここではonClickを使用しています。React Aria のButtonコンポーネントとの一貫性を保つため、onPressに統一してください。♻️ 修正案
- <Button variant="ghost" size="sm" onClick={loadInvitations}> + <Button variant="ghost" size="sm" onPress={loadInvitations}> <RefreshCw className="w-4 h-4" /> </Button>
256-263: 入力フィールドにaria-labelを追加してください。コーディングガイドラインに従い、入力フィールドには
aria-labelを追加する必要があります。placeholderのみに依存せず、アクセシビリティを確保してください。♻️ 修正案
<input type="text" value={customCode} onChange={(e) => setCustomCode(e.target.value.toUpperCase())} placeholder={t`Leave empty for auto-generated code`} + aria-label={t`Custom invitation code`} className="w-full px-3 py-2 border border-(--border-color) rounded-lg bg-(--bg-primary) text-(--text-primary) focus:outline-none focus:ring-2 focus:ring-primary-500" maxLength={20} />As per coding guidelines: "Always add aria-label to inputs; do not rely on placeholder alone for accessibility"
271-282: select 要素にaria-labelを追加してください。アクセシビリティのため、
select要素にもaria-labelを追加することを推奨します。♻️ 修正案
<select value={maxUses} onChange={(e) => setMaxUses(parseInt(e.target.value))} + aria-label={t`Maximum uses`} className="w-full px-3 py-2 border border-(--border-color) rounded-lg bg-(--bg-primary) text-(--text-primary) focus:outline-none focus:ring-2 focus:ring-primary-500" >packages/frontend/src/components/pages/MessageThreadPageClient.tsx (1)
190-200:textareaにaria-labelを追加してください。コーディングガイドラインに従い、
placeholderのみに依存せず、aria-labelを追加してアクセシビリティを確保してください。♻️ 修正案
<textarea ref={textareaRef} value={text} onChange={handleTextChange} onKeyDown={handleKeyDown} placeholder={t`Write a message...`} + aria-label={t`Message text`} disabled={isSubmitting} rows={1}As per coding guidelines: "Always add aria-label to inputs; do not rely on placeholder alone for accessibility"
packages/frontend/src/pages/mod/reports.tsx (3)
304-341: クリック可能なカードにはInteractiveCardの使用を検討してください。コーディングガイドラインによると、クリック/プレスハンドラーを持つインタラクティブなカードには、React Aria の
ButtonをラップしたInteractiveCardコンポーネントを使用する必要があります。現在のdiv+onClickパターンは、キーボードナビゲーションやスクリーンリーダーのアナウンスに対応していません。As per coding guidelines: "For interactive cards with click/press handlers, use InteractiveCard component which wraps React Aria's Button for proper accessibility; for static cards, use div"
278-287:select要素にaria-labelを追加してください。アクセシビリティのため、フィルター用の
select要素にaria-labelを追加することを推奨します。♻️ 修正案
<select value={statusFilter} onChange={(e) => setStatusFilter(e.target.value as any)} + aria-label={t`Filter by status`} className="px-3 py-1 text-sm border border-(--border-color) rounded-lg bg-(--bg-primary) text-(--text-primary) focus:outline-none focus:ring-2 focus:ring-primary-500" >
480-486:textareaにaria-labelを追加してください。
placeholderのみではアクセシビリティが不十分です。♻️ 修正案
<textarea value={resolutionText} onChange={(e) => setResolutionText(e.target.value)} placeholder={t`Add notes about the action taken...`} + aria-label={t`Resolution notes`} className="w-full px-3 py-2 border border-(--border-color) rounded-lg bg-(--bg-primary) text-(--text-primary) focus:outline-none focus:ring-2 focus:ring-primary-500 resize-none" rows={3} />packages/frontend/src/pages/mod/users.tsx (3)
277-284:inputにaria-labelを追加してください。ユーザー検索の入力フィールドに
aria-labelを追加して、アクセシビリティを確保してください。♻️ 修正案
<input type="text" value={searchQuery} onChange={(e) => setSearchQuery(e.target.value)} onKeyDown={(e) => e.key === "Enter" && searchUser()} placeholder={t`Enter username (e.g., alice or alice@remote.server)`} + aria-label={t`Search username`} className="w-full px-4 py-2 border border-(--border-color) rounded-lg bg-(--bg-primary) text-(--text-primary) focus:outline-none focus:ring-2 focus:ring-primary-500" />As per coding guidelines: "Always add aria-label to inputs; do not rely on placeholder alone for accessibility"
408-420: ネイティブbuttonの代わりにButtonコンポーネントを使用してください。このファイル内の他のボタンは
Buttonコンポーネントを使用していますが、警告削除ボタンはネイティブのbutton要素とonClickを使用しています。一貫性のため、ButtonコンポーネントとonPressを使用してください。♻️ 修正案
- <button - type="button" - onClick={() => handleDeleteWarning(warning.id)} - disabled={isDeletingWarning === warning.id} - className="p-1 text-(--text-muted) hover:text-red-500 disabled:opacity-50" - title={t`Delete warning`} - > + <Button + variant="ghost" + size="sm" + onPress={() => handleDeleteWarning(warning.id)} + isDisabled={isDeletingWarning === warning.id} + aria-label={t`Delete warning`} + className="p-1 text-(--text-muted) hover:text-red-500" + > {isDeletingWarning === warning.id ? ( <Spinner size="sm" /> ) : ( <Trash2 className="w-4 h-4" /> )} - </button> + </Button>
516-522:textareaにaria-labelを追加してください。警告理由の入力フィールドに
aria-labelを追加してください。♻️ 修正案
<textarea value={warningReason} onChange={(e) => setWarningReason(e.target.value)} placeholder={t`Describe the reason for this warning...`} + aria-label={t`Warning reason`} className="w-full px-3 py-2 border border-(--border-color) rounded-lg bg-(--bg-primary) text-(--text-primary) focus:outline-none focus:ring-2 focus:ring-primary-500 resize-none" rows={3} />packages/frontend/src/pages/admin/federation.tsx (1)
295-325: フィルタータブにrole="tablist"と適切な ARIA 属性を検討してください。これらのボタンはタブとして機能しているため、アクセシビリティ向上のために
role="tablist"とrole="tab"、aria-selected属性の追加を検討してください。現在の実装でも機能しますが、セマンティクスを明確にすることでスクリーンリーダーのユーザー体験が向上します。
packages/frontend/src/components/note/NoteComposerAttachments.tsx
Outdated
Show resolved
Hide resolved
packages/frontend/src/components/note/NoteComposerSchedulePicker.tsx
Outdated
Show resolved
Hide resolved
packages/frontend/src/components/pages/MessageThreadPageClient.tsx
Outdated
Show resolved
Hide resolved
- Simplify CLAUDE.md from 531 to 137 lines (essential info only) - Split detailed guidelines into .claude/rules/: - tsdoc.md: TSDoc documentation standards - react-aria.md: React Aria Components usage - architecture.md: Core architecture patterns - release.md: Release versioning procedure - devcontainer.md: DevContainer setup - mcp-servers.md: MCP server usage - discord-logger.md: Discord conversation logging - Update .gitignore to track .claude/rules and .claude/workflows - Update .claude/instruction.md with rule file references
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In @.claude/instruction.md:
- Around line 39-43: ドキュメント内の開発ブランチ設定が実際のリポジトリと不一致です:`開発ブランチ: dev` を確認し、リポジトリに
`dev` ブランチが存在しない場合はドキュメントの該当行("開発ブランチ: dev")を正しいブランチ名に更新するか、リポジトリ側で `dev`
ブランチを作成してください(他の関連設定例:"デフォルトブランチ: main" と "featureブランチプレフィックス:
`feature/issue-{番号}-`" を参照して整合性を保ってください)。
…atterns - Replace plain <button> elements with Button components for accessibility: - NoteCardMenu.tsx: menu items now use Button with aria labels - NoteComposerAttachments.tsx: remove buttons use Button - NoteComposerDrafts.tsx: drafts toggle and actions use Button - NoteComposerSchedulePicker.tsx: schedule toggle uses Button - Fix TSDoc documentation mismatch in ReactionPicker.tsx: - Changed "heart icon" to "smile-plus icon" to match actual code - Migrate MessageThreadPageClient.tsx to use api.get instead of manual apiClient.setToken + apiClient.get pattern - Update ConfirmDialog.tsx to use React Aria Dialog components: - Added ModalOverlay, Modal, AriaDialog, Heading from react-aria-components - Provides focus trapping, keyboard navigation, and ARIA compliance - Add apiClient.setToken calls before helper API calls in admin/contacts.tsx
…atibility Replace horizontal tab navigation with accordion-style category sections to prevent swipe conflicts with deck mode column switching on mobile devices. - Categories now expand/collapse vertically instead of horizontal scrolling - Only one category can be expanded at a time - Maintains all existing functionality: search, recent, custom emojis - Uses lazy loading for emoji rendering to maintain performance
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/frontend/src/components/pages/MessageThreadPageClient.tsx (1)
190-200: textarea にaria-labelを追加してください。
placeholderだけではスクリーンリーダーのアクセシビリティが不十分です。♿ 修正案
<textarea ref={textareaRef} value={text} onChange={handleTextChange} onKeyDown={handleKeyDown} placeholder={t`Write a message...`} + aria-label={t`Write a message`} disabled={isSubmitting} rows={1} className="w-full px-4 py-2 bg-(--bg-secondary) border border-(--border-color) rounded-2xl resize-none focus:outline-none focus:ring-2 focus:ring-primary-500 text-(--text-primary) placeholder:text-(--text-muted)" style={{ minHeight: "42px", maxHeight: "150px" }} />コーディングガイドラインに基づく指摘: "Always add aria-label to inputs; do not rely on placeholder alone for accessibility"
🤖 Fix all issues with AI agents
In `@packages/frontend/src/components/note/NoteComposerDrafts.tsx`:
- Around line 109-113: The onPress handler currently casts the React Aria
PressEvent to React.MouseEvent and passes it to onDeleteDraft; remove the
incorrect cast and event argument and call onDeleteDraft(draft.id) directly (or
alternatively change onDeleteDraft signature to accept a PressEvent instead of
React.MouseEvent). Specifically update the Button's onPress prop (the Button
component usage) to stop passing the event, and adjust the onDeleteDraft handler
implementation/typing to not expect React.MouseEvent (or to accept PressEvent)
so there is no unsafe cast hiding the type mismatch.
🧹 Nitpick comments (3)
packages/frontend/src/components/ui/ConfirmDialog.tsx (1)
74-74: 軽微: 早期リターンは冗長ですが、無害です。
ModalOverlayはisOpenプロパティに基づいて自身の表示/非表示を制御するため、この早期リターンは厳密には不要です。ただし、閉じた状態でのレンダリングツリー生成を完全に回避するマイクロ最適化として機能しており、維持しても問題ありません。packages/frontend/src/components/pages/MessageThreadPageClient.tsx (1)
211-221: React Aria のButtonコンポーネントを使用してください。コーディングガイドラインでは、インタラクティブな UI 要素には React Aria Components を使用することが求められています。コミットメッセージでも他のファイルで同様の修正が行われていると記載されています。
♿ 修正案
+import { Button } from "react-aria-components"; - <button + <Button type="submit" - disabled={!text.trim() || isSubmitting || isOverLimit} + isDisabled={!text.trim() || isSubmitting || isOverLimit} className="p-2.5 bg-primary-500 text-white rounded-full hover:bg-primary-600 disabled:opacity-50 disabled:cursor-not-allowed transition-colors shrink-0" + aria-label={t`Send message`} > {isSubmitting ? ( <Loader2 className="w-5 h-5 animate-spin" /> ) : ( <Send className="w-5 h-5" /> )} - </button> + </Button>コーディングガイドラインに基づく指摘: "Use React Aria Components for interactive UI elements requiring keyboard navigation, focus management... (buttons, inputs, dialogs, menus, selects, checkboxes, switches, tabs)"
packages/frontend/src/components/note/NoteComposerSchedulePicker.tsx (1)
61-105: Popover パターンを使用してアクセシビリティを改善してください現在のドロップダウンは条件付きレンダリングのみで、フォーカス管理やキーボード操作(Escape で閉じる、外側クリックで閉じる、フォーカストラップ)が実装されていません。プロジェクト内の
NoteComposer.tsxなど他のコンポーネントと同様に、React Aria のPopoverパターンを使用することで、これらのアクセシビリティ機能が自動的に提供されます。プレーンな HTMLinputの代わりに React Aria のTextFieldまたはDateFieldコンポーネントの使用も検討してください。
- NoteComposerDrafts: Remove event parameter from onDeleteDraft (React Aria Button stops propagation by default) - NoteComposerAttachments: Fix memory leak with URL.createObjectURL using useMemo and cleanup in useEffect - NoteComposerSchedulePicker: Fix timezone issue with datetime-local min by converting to local time - ReactionPicker: Remove unused selectedReactions prop from ReactionButton - MessageThreadPageClient: Replace notesApi with api.post for consistency - .claude/instruction.md: Fix non-existent dev branch reference
There was a problem hiding this comment.
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 (1)
packages/frontend/src/components/pages/MessageThreadPageClient.tsx (1)
120-132: アクセシビリティ: 入力と送信ボタンにアクセスラベルがなく、React Aria Components を使用していません。textarea はプレースホルダーのみで aria-label がなく、送信ボタンはアイコンのみでアクセスラベルがありません。コーディングガイドラインに従い、textarea を TextField に、button を Button(React Aria Components)に置き換え、aria-label を付与してください。
🤖 Fix all issues with AI agents
In `@packages/frontend/src/components/note/NoteComposerSchedulePicker.tsx`:
- Around line 44-49: The minDateTime value in NoteComposerSchedulePicker is
computed once on mount via useMemo, so it becomes stale; update it whenever the
schedule picker is opened by recalculating the min datetime on each open (e.g.,
remove the empty deps or add the picker-open state prop like isScheduleOpen /
isOpen to the useMemo dependency array) so minDateTime is recomputed at open and
always reflects "now + 1 minute" in local time.
- Around line 77-84: Replace the raw <input> in NoteComposerSchedulePicker with
the project's TextField wrapper to satisfy React Aria accessibility conventions:
use TextField (from packages/frontend/src/components/ui/TextField.tsx) and pass
the same props currently on the input (value={scheduledAt || ""}, onChange
handler to call onScheduledAtChange with the new value or null,
min={minDateTime}, aria-label={t`Schedule date and time`}) and preserve the
className and type="datetime-local" behavior via TextField props; ensure the
onChange signature matches TextField's API so scheduledAt updates correctly and
focus/readout behavior is handled by TextField.
In `@packages/frontend/src/components/ui/EmojiPicker.tsx`:
- Around line 96-123: Replace native <button> elements in the EmojiPicker
component with the existing React Aria Button wrapper (imported as Button) and
switch event handlers from onClick to onPress: for the category toggle button
(using onToggle, isExpanded, aria-expanded, aria-controls, icon, title,
categoryKey) replace the outer <button> with <Button onPress={onToggle} ...>
preserving className and aria props; for each emoji tile (using displayedEmojis,
onEmojiClick, emoji, index) replace the inner <button> with <Button onPress={()
=> onEmojiClick(emoji)} ...> preserving key, className, title and aria-label.
Apply the same replacements at the other affected blocks (the other ranges
noted) so all native buttons are unified to the Button component and use onPress
instead of onClick.
🧹 Nitpick comments (1)
packages/frontend/src/components/ui/EmojiPicker.tsx (1)
425-433: 検索入力は React Aria の SearchField/TextField を優先したいです。アクセシビリティと一貫したキーボード操作のため、可能なら React Aria の SearchField(または TextField)への置き換えを検討してください。
As per coding guidelines: Prefer TextField, SearchField, RadioGroup, and Select components over custom state-based implementations and plain HTML inputs
packages/frontend/src/components/note/NoteComposerSchedulePicker.tsx
Outdated
Show resolved
Hide resolved
packages/frontend/src/components/note/NoteComposerSchedulePicker.tsx
Outdated
Show resolved
Hide resolved
- NoteComposerSchedulePicker: Add isOpen to minDateTime useMemo deps for recalculation when picker opens - NoteComposerSchedulePicker: Replace raw input with TextField component for React Aria accessibility compliance - TextField: Add min/max props for date/number inputs - EmojiPicker: Replace all native button elements with Button component using onPress instead of onClick for React Aria compliance
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Fix all issues with AI agents
In `@packages/frontend/src/components/note/NoteComposerSchedulePicker.tsx`:
- Around line 14-25: The TSDoc for the scheduledAt prop on the
NoteComposerSchedulePicker component is misleading — update the comment for the
scheduledAt property (in NoteComposerSchedulePicker / prop named scheduledAt) to
state it is a local datetime-local string without timezone (e.g.
"YYYY-MM-DDTHH:mm") and not a full ISO/UTC timestamp; keep the type as string |
null but clarify in the docstring that it follows the HTML input
type="datetime-local" format and contains no timezone information.
- Around line 70-114: The schedule dropdown is a plain <div> and lacks ARIA
roles, focus management, keyboard handling and screen-reader support; replace
the conditional div block in NoteComposerSchedulePicker with React Aria's
DialogTrigger (or Popover trigger) + Popover/Dialog pattern used in ListCard.tsx
(MenuTrigger+Popover) or NoteComposer.tsx (Select+Popover): wrap the trigger
control with DialogTrigger, render the panel inside Popover/Dialog components,
move existing contents (TextField, preview, Cancel and Schedule buttons) into
the Popover panel, ensure the panel uses the Dialog role, handles Escape to
close, traps focus while open and restores focus on close, and wire existing
handlers/props (isOpen, scheduledAt, onScheduledAtChange, minDateTime, onCancel,
onScheduleSubmit, isSubmitDisabled) into the new components so behavior and
aria-labels remain intact.
In `@packages/frontend/src/components/ui/EmojiPicker.tsx`:
- Around line 294-313: handleScroll currently decides which loadMore to call
solely from expandedCategory, so when showing search results (categories like
"search-custom" or "search-unicode") it never increments those search result
visibleCounts; move the search-aware logic into handleScroll: detect search
categories (e.g., expandedCategory startsWith "search-"), consult the
searchResults structure to get the current count/length, and call loadMore with
the appropriate search category key so visibleCounts for search results
increase; update handleScroll's dependency list if you reference searchResults
and any search-specific counts.
- Around line 95-113: Sanitize categoryKey before using it in DOM ids and
aria-controls to strip or replace spaces and unsafe characters (used in the id
template `emoji-section-${categoryKey}` and the Button's aria-controls) to
ensure valid HTML attribute values; implement a helper or inline normalization
(e.g., lower-case, replace non-alphanumerics with hyphens, collapse multiple
hyphens) and use that sanitized value for id and aria-controls. Also normalize
the display title used in the Button (the `title` prop/variable) so that if it
equals the sentinel "__uncategorized__" you render "Uncategorized" instead.
🧹 Nitpick comments (2)
packages/frontend/src/components/ui/TextField.tsx (1)
40-47: TSDoc の@paramセクションにmin/maxの記載がありません。インターフェースの TSDoc では
min/maxが文書化されていますが、関数の@paramセクションには含まれていません。一貫性のために追加を検討してください。📝 TSDoc への追記案
* `@param` rows - Number of visible textarea rows (default: 4) + * `@param` min - Minimum value for date/number inputs + * `@param` max - Maximum value for date/number inputs * `@param` className - Additional CSS classes to applypackages/frontend/src/components/ui/EmojiPicker.tsx (1)
424-433: 絵文字検索は SearchField コンポーネントへの置き換えを推奨します。このファイルはコンポーネント配下にあるため、React Aria ベースの SearchField に統一することで、フォーカス管理、キーボードナビゲーション、スクリーンリーダー対応が改善されます。SearchField は検索入力に特化しており、検索アイコン、クリアボタン、Escape キーでのクリア機能が組み込まれています。現在の実装と同じ props(value、onChange、placeholder)をサポートしています。
packages/frontend/src/components/note/NoteComposerSchedulePicker.tsx
Outdated
Show resolved
Hide resolved
packages/frontend/src/components/note/NoteComposerSchedulePicker.tsx
Outdated
Show resolved
Hide resolved
NoteComposerSchedulePicker: - Update TSDoc: scheduledAt uses local datetime format, not ISO - Migrate from plain div to React Aria DialogTrigger + Popover for proper ARIA roles, focus management, and keyboard handling EmojiPicker: - Add sanitizeId helper for valid HTML id/aria-controls attributes - Add normalizeCategoryName helper to display __uncategorized__ as Uncategorized - Fix handleScroll to load more search results (search-custom/search-unicode)
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/frontend/src/components/ui/EmojiPicker.tsx (1)
451-459: 検索入力を SearchField コンポーネントに置き換えてください。ガイドラインに従い、キーボード操作やフォーカス管理が必要な入力フィールドは React Aria コンポーネントの使用が推奨されています。このプレーンな HTML
<input>要素は、既存のSearchFieldコンポーネント(packages/frontend/src/components/ui/SearchField.tsx)で置き換えるべきです。SearchField は検索パターン用に設計された専用コンポーネントで、検索アイコン、クリアボタン、キーボードナビゲーション(Escape キーでクリア)などのアクセシビリティ機能を備えています。現在のvalueとonChangeのパターンは SearchField でそのまま使用できます。
🤖 Fix all issues with AI agents
In `@packages/frontend/src/components/ui/EmojiPicker.tsx`:
- Around line 46-50: normalizeCategoryName currently returns the hard-coded
English "Uncategorized"; change it to return a localized string by wrapping with
the i18n translator (e.g. return t("Uncategorized") or return
translate("Uncategorized")). Update the function signature or call site to
either accept a t: (key: string) => string parameter
(normalizeCategoryName(name: string, t: TFunction)) or import/use the project's
i18n hook (e.g. useTranslation) inside EmojiPicker so normalizeCategoryName
calls t("Uncategorized") instead of the literal. Ensure callers are updated to
pass the translator if you add a parameter.
🧹 Nitpick comments (1)
packages/frontend/src/components/note/NoteComposerSchedulePicker.tsx (1)
97-103: 日時プレビュー表示について軽微な提案。
toLocaleString()はユーザーのロケールに基づいて適切にフォーマットされますが、より明示的なオプションを指定することで一貫性を高められます。現状でも動作に問題はありません。♻️ より明示的なフォーマット(任意)
<div className="text-xs text-gray-500 dark:text-gray-400"> <Trans> - Will be posted at {new Date(scheduledAt).toLocaleString()} + Will be posted at {new Date(scheduledAt).toLocaleString(undefined, { + year: "numeric", + month: "short", + day: "numeric", + hour: "2-digit", + minute: "2-digit", + })} </Trans> </div>
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@packages/frontend/src/components/ui/EmojiPicker.tsx`:
- Around line 36-41: sanitizeId currently collapses all non-ASCII to "-" causing
different non-ASCII category names to map to the same id; update sanitizeId to
preserve uniqueness by encoding the original key (e.g., use
encodeURIComponent(key)) and then normalize to a safe id token (for example
prefix with a constant like "cat-" and replace any remaining unsafe characters
from the encoded string with "-" if needed) so you still return a valid HTML id
but keep uniqueness for names containing Japanese/emoji; modify the sanitizeId
function to implement this encoding approach and ensure callers using
aria-controls/ids (the category key usage) continue to work.
Summary
useApihook for centralized authentication state managementtokenAtom+apiClient.setToken()pattern across 40+ filesgetApiBase()functions intoclient.tsChanges
New
useApiHookA centralized hook that provides:
token: Current authentication tokenisAuthenticated: Boolean authentication statusget/post/put/delete: Authenticated API methodsComponent Refactoring
NoteCard.tsx(929 lines)NoteCard.tsx+NoteCardMenu.tsxNoteComposer.tsx(1430 lines)NoteComposer.tsx+NoteComposerAttachments.tsx+NoteComposerDrafts.tsx+NoteComposerSchedulePicker.tsxEmojiPicker.tsx(1193 lines)EmojiPicker.tsx+emojiData.tsTSDoc Documentation
Added comprehensive English TSDoc comments to:
lib/types/Code Cleanup
getApiBase()functions fromdrive.ts,following.ts,reactions.tsTest plan
bun run typecheckpasses (0 errors)bun run lintpasses (0 warnings, 0 errors)Summary by CodeRabbit
新機能
リファクタリング
ドキュメント
互換性調整
✏️ Tip: You can customize this high-level summary in your review settings.