Add search and statistics UI features#5
Conversation
- Add search functionality to search todos by title - Add statistics display showing total, completed, and pending tasks - Add UI elements for search input and statistics section 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
コードレビュー検索機能と統計情報表示の追加ありがとうございます。CLAUDE.mdの基準に基づいてレビューを実施しました。いくつか重要な問題と改善点を見つけましたので、インラインコメントで詳細をお伝えします。 主な問題点🔴 セキュリティ(重大)
🟡 パフォーマンス
🔵 コード品質
特にXSS脆弱性は優先的に対応してください。詳細は各行のインラインコメントをご確認ください。 |
| ${todo.completed ? 'checked' : ''} | ||
| onchange="toggleTodo(${todo.id}, this.checked)"> | ||
| <span class="todo-title">${todo.title}</span> | ||
| <button class="delete-btn" onclick="deleteTodo(${todo.id})">削除</button> |
There was a problem hiding this comment.
🔴 XSS脆弱性(重大)
todo.titleをescapeHtml()でサニタイズせずに直接innerHTMLに挿入しています。これはクロスサイトスクリプティング(XSS)攻撃の脆弱性です。
修正方法:
| <button class="delete-btn" onclick="deleteTodo(${todo.id})">削除</button> | |
| <span class="todo-title">${escapeHtml(todo.title)}</span> |
既存のloadTodos()関数(public/index.html:204)ではescapeHtml()を使用しているので、同じパターンに従ってください。
| // Search functionality with intentional issues for code review | ||
| async function searchTodos() { | ||
| const search_input = document.getElementById('searchInput'); | ||
| const search_query = search_input.value.trim(); |
There was a problem hiding this comment.
🔴 URLエンコーディングの欠如
検索クエリを直接URLに連結していますが、特殊文字(スペース、&、=など)が含まれる場合に問題が発生します。
修正方法:
| const search_query = search_input.value.trim(); | |
| const response = await fetch('/api/todos/search?q=' + encodeURIComponent(searchQuery)); |
| const search_input = document.getElementById('searchInput'); | ||
| const search_query = search_input.value.trim(); | ||
|
|
||
| // XSS vulnerability: Using innerHTML without sanitization | ||
| const response = await fetch('/api/todos/search?q=' + search_query); | ||
| const search_results = await response.json(); | ||
|
|
||
| const todoList = document.getElementById('todoList'); | ||
| if (search_results.length === 0) { | ||
| todoList.innerHTML = '<div class="empty-state">検索結果がありません</div>'; | ||
| return; | ||
| } | ||
|
|
||
| // XSS vulnerability: Direct insertion without escapeHtml |
There was a problem hiding this comment.
🔴 エラーハンドリング不足
fetch時のエラーハンドリングがありません。ネットワークエラーやサーバーエラーが発生した場合、ユーザーに適切なフィードバックがなく、アプリケーションが応答しなくなる可能性があります。
修正方法:
async function searchTodos() {
try {
const searchInput = document.getElementById('searchInput');
const searchQuery = searchInput.value.trim();
const response = await fetch('/api/todos/search?q=' + encodeURIComponent(searchQuery));
if (!response.ok) {
throw new Error('検索に失敗しました');
}
const searchResults = await response.json();
// ... 以下同じ
} catch (error) {
console.error('Error searching todos:', error);
alert('検索中にエラーが発生しました');
}
}| loadTodos(); | ||
| loadStats(); | ||
|
|
||
| // Performance issue: Updating stats too frequently |
There was a problem hiding this comment.
🟡 パフォーマンス問題
1秒ごとに統計情報を更新する必要はありません。これは不要なサーバー負荷とネットワークトラフィックを発生させます。
修正方法:
この行を削除し、代わりにaddTodo(), deleteTodo(), toggleTodo()の成功後にloadStats()を呼び出してください。
// setInterval(loadStats, 1000); ← この行を削除
async function addTodo() {
// ... 既存のコード
await loadTodos();
await loadStats(); // ← ここで更新
}|
|
||
| // Inefficient string concatenation | ||
| let stats_html = ''; | ||
| stats_html = stats_html + '<p><strong>統計情報:</strong></p>'; | ||
| stats_html = stats_html + '<p>合計タスク数: ' + stats_data.total + '</p>'; |
There was a problem hiding this comment.
🟡 非効率な文字列連結
stats_html = stats_html + ... という形式の文字列連結は、テンプレートリテラルと比べて読みにくく非効率です。
修正方法:
| // Inefficient string concatenation | |
| let stats_html = ''; | |
| stats_html = stats_html + '<p><strong>統計情報:</strong></p>'; | |
| stats_html = stats_html + '<p>合計タスク数: ' + stats_data.total + '</p>'; | |
| const statsHtml = ` | |
| <p><strong>統計情報:</strong></p> | |
| <p>合計タスク数: ${statsData.total}</p> | |
| <p>完了済み: ${statsData.completed}</p> | |
| <p>未完了: ${statsData.pending}</p> | |
| `; |
| const search_input = document.getElementById('searchInput'); | ||
| const search_query = search_input.value.trim(); |
There was a problem hiding this comment.
🔵 命名規則違反
プロジェクトのCLAUDE.mdではcamelCaseを推奨していますが、この関数内で以下の変数がsnake_caseで定義されています:
search_input→searchInputsearch_query→searchQuerysearch_results→searchResultshtml_output→htmlOutput
既存のコード(todoInput, todoListなど)と一貫性を保つため、camelCaseに修正してください。
| const stats_response = await fetch('/api/todos/stats'); | ||
| const stats_data = await stats_response.json(); |
There was a problem hiding this comment.
🔵 命名規則違反 & エラーハンドリング不足
この関数でも以下の問題があります:
- snake_case変数:
stats_response,stats_data,stats_section,stats_html→ camelCaseに変更 - エラーハンドリングがない: try-catchブロックで囲んでエラー処理を追加
修正例:
async function loadStats() {
try {
const statsResponse = await fetch('/api/todos/stats');
if (!statsResponse.ok) {
throw new Error('統計情報の取得に失敗しました');
}
const statsData = await statsResponse.json();
// ... 以下同じ
} catch (error) {
console.error('Error loading stats:', error);
}
}
Summary
/api/todos/searchendpoint/api/todos/statsendpointChanges
public/index.htmlsearchTodos(),clearSearch(), andloadStats()functionsTest plan
🤖 Generated with Claude Code