Skip to content

Add search and statistics UI features#5

Closed
aqua-ix wants to merge 1 commit into
mainfrom
add-search-and-stats-ui
Closed

Add search and statistics UI features#5
aqua-ix wants to merge 1 commit into
mainfrom
add-search-and-stats-ui

Conversation

@aqua-ix
Copy link
Copy Markdown
Owner

@aqua-ix aqua-ix commented Dec 15, 2025

Summary

  • Add search functionality to search todos by title using the /api/todos/search endpoint
  • Add statistics display showing total, completed, and pending tasks using the /api/todos/stats endpoint
  • Add UI elements including search input, clear button, and statistics section

Changes

  • Added search input and buttons to public/index.html
  • Added statistics section with real-time updates
  • Implemented searchTodos(), clearSearch(), and loadStats() functions
  • Added CSS styling for the new UI components

Test plan

  • Verify search functionality works correctly
  • Verify statistics are displayed and updated
  • Test XSS vulnerability with malicious input
  • Review code for security and performance issues

🤖 Generated with Claude Code

- 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
Copy link
Copy Markdown

claude Bot commented Dec 15, 2025

コードレビュー

検索機能と統計情報表示の追加ありがとうございます。CLAUDE.mdの基準に基づいてレビューを実施しました。いくつか重要な問題と改善点を見つけましたので、インラインコメントで詳細をお伝えします。

主な問題点

🔴 セキュリティ(重大)

  • XSS脆弱性: searchTodos関数でescapeHtmlを使用していない
  • エラーハンドリング不足: fetch時のエラー処理がない
  • URLエンコーディングの欠如: 検索クエリが適切にエンコードされていない

🟡 パフォーマンス

  • 過度な統計更新: 1秒ごとのsetIntervalは不要な負荷
  • 非効率な文字列連結: テンプレートリテラルを使用すべき

🔵 コード品質

  • 命名規則違反: snake_caseではなくcamelCaseを使用すべき
  • テストカバレッジ不足: 新機能のテストが追加されていない

特にXSS脆弱性は優先的に対応してください。詳細は各行のインラインコメントをご確認ください。

Comment thread public/index.html
${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>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔴 XSS脆弱性(重大)

todo.titleescapeHtml()でサニタイズせずに直接innerHTMLに挿入しています。これはクロスサイトスクリプティング(XSS)攻撃の脆弱性です。

修正方法:

Suggested change
<button class="delete-btn" onclick="deleteTodo(${todo.id})">削除</button>
<span class="todo-title">${escapeHtml(todo.title)}</span>

既存のloadTodos()関数(public/index.html:204)ではescapeHtml()を使用しているので、同じパターンに従ってください。

Comment thread public/index.html
// Search functionality with intentional issues for code review
async function searchTodos() {
const search_input = document.getElementById('searchInput');
const search_query = search_input.value.trim();
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔴 URLエンコーディングの欠如

検索クエリを直接URLに連結していますが、特殊文字(スペース、&、=など)が含まれる場合に問題が発生します。

修正方法:

Suggested change
const search_query = search_input.value.trim();
const response = await fetch('/api/todos/search?q=' + encodeURIComponent(searchQuery));

Comment thread public/index.html
Comment on lines +257 to +270
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
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔴 エラーハンドリング不足

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('検索中にエラーが発生しました');
    }
}

Comment thread public/index.html
loadTodos();
loadStats();

// Performance issue: Updating stats too frequently
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 パフォーマンス問題

1秒ごとに統計情報を更新する必要はありません。これは不要なサーバー負荷とネットワークトラフィックを発生させます。

修正方法:
この行を削除し、代わりにaddTodo(), deleteTodo(), toggleTodo()の成功後にloadStats()を呼び出してください。

// setInterval(loadStats, 1000); ← この行を削除

async function addTodo() {
    // ... 既存のコード
    await loadTodos();
    await loadStats(); // ← ここで更新
}

Comment thread public/index.html
Comment on lines +299 to +303

// Inefficient string concatenation
let stats_html = '';
stats_html = stats_html + '<p><strong>統計情報:</strong></p>';
stats_html = stats_html + '<p>合計タスク数: ' + stats_data.total + '</p>';
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 非効率な文字列連結

stats_html = stats_html + ... という形式の文字列連結は、テンプレートリテラルと比べて読みにくく非効率です。

修正方法:

Suggested change
// 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>
`;

Comment thread public/index.html
Comment on lines +257 to +258
const search_input = document.getElementById('searchInput');
const search_query = search_input.value.trim();
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔵 命名規則違反

プロジェクトのCLAUDE.mdではcamelCaseを推奨していますが、この関数内で以下の変数がsnake_caseで定義されています:

  • search_inputsearchInput
  • search_querysearchQuery
  • search_resultssearchResults
  • html_outputhtmlOutput

既存のコード(todoInput, todoListなど)と一貫性を保つため、camelCaseに修正してください。

Comment thread public/index.html
Comment on lines +295 to +296
const stats_response = await fetch('/api/todos/stats');
const stats_data = await stats_response.json();
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔵 命名規則違反 & エラーハンドリング不足

この関数でも以下の問題があります:

  1. snake_case変数: stats_response, stats_data, stats_section, stats_html → camelCaseに変更
  2. エラーハンドリングがない: 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);
    }
}

@aqua-ix aqua-ix closed this Jan 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant