Skip to content

メモのコピー機能 #321

@Kewton

Description

@Kewton

Note: このIssueは 2026-02-20 にレビュー結果を反映して更新されました。
詳細: dev-reports/issue/321/issue-review/

概要

MemoCardコンポーネントにコピーボタンを追加し、メモのコンテンツ(content フィールド)をクリップボードにコピーできるようにする。

背景・課題

Worktreeのメモには重要なコードスニペット、コマンド、作業メモが記録されることが多い。
現在、メモ内容を他のツールやClaude CLIプロンプトに貼り付けるには、テキストを手動で選択してコピーする必要があり、操作が非効率である。

プロジェクト内の他コンポーネント(FileViewer、HistoryPane、MarkdownEditor、LogViewer)にはコピー機能が実装済みであり、MemoCardにも同等の機能を追加することで操作の一貫性を確保する。

ユースケース例:

  • メモの内容をClaude CLIへのプロンプトに貼り付けたい
  • メモの内容を別のworktreeのメモにコピーしたい
  • メモの内容を外部ツール(Slack、エディタ等)に共有したい

提案する解決策

コピー対象

  • コピー対象は content(メモ本文)のみ とする
  • title(メモタイトル)はコピー対象外(既存パターンに合わせ、本文コンテンツのみをコピーする方針)

UI実装パターン

FileViewer方式(パターンA)を採用する。

  • Copy/Checkアイコン切替(lucide-react の Copy / Check アイコン)
  • クリック後2秒間Checkアイコンに変化し、その後Copyアイコンに戻る
  • サイレントエラーハンドリング(Toast通知は使用しない)

採用理由: MemoCardは現時点で showToast propsを受け取っていないため、Toast通知方式(パターンB)を採用するとpropsの追加やMemoPane側の変更も必要になる。FileViewerパターンを踏襲することで、変更範囲を最小限に抑え、既存UIとの一貫性を保つ。

UI配置

  • MemoCardのヘッダー部分(タイトル行)の右側に配置
  • 既存の削除ボタンと並べて表示
  • ボタンのスタイルは既存の削除ボタンと統一

使用する既存基盤

  • src/lib/clipboard-utils.tscopyToClipboard() 関数を使用(ANSI除去・空文字バリデーション済み)
  • lucide-reactの Copy / Check アイコン(プロジェクト内で使用済み)
  • src/components/worktree/FileViewer.tsx のCopy/Checkアイコン切替パターンを参考実装として踏襲

受け入れ条件

  • MemoCardにコピーボタン(Copyアイコン)が表示される
  • コピーボタンをクリックするとメモの content がクリップボードにコピーされる
  • クリック後2秒間、アイコンがCopyからCheckに切り替わる
  • 2秒後にアイコンがCopyに戻る
  • content が空の場合、コピーボタンが無効化(disabled)またはコピー処理が実行されない(clipboard-utils.ts の空文字バリデーション準拠)
  • TypeScript型チェックが通る(npx tsc --noEmit
  • ESLintエラーが0件(npm run lint
  • 単体テストが追加されパスする(tests/unit/components/worktree/MemoCard.test.tsx
  • モバイル画面幅でヘッダー部の4要素(タイトル入力 + Saving + コピーボタン + 削除ボタン)のレイアウトが崩れないこと(タイトル入力の最小幅が十分であることを確認)

テスト要件

以下のテストケースを tests/unit/components/worktree/MemoCard.test.tsx に追加する:

  1. コピーボタンがレンダリングされること
  2. コピーボタンクリック時に copyToClipboard が正しい引数(content)で呼ばれること
  3. コピー成功後にCheckアイコンが表示されること
  4. 2秒後にCopyアイコンに戻ること
  5. content が空のメモでコピーボタンの適切な振る舞い(disabled or no-op)

モック指針: copyToClipboard 関数は vi.mock('@/lib/clipboard-utils') でモック化する。テスト環境(jsdom)では navigator.clipboard APIが利用できないため、関数レベルのモックが必須。MarkdownEditor等の既存テストパターン(tests/unit/components/MarkdownEditor.test.tsx)を参考にすること。

影響範囲

ファイル 変更内容
src/components/worktree/MemoCard.tsx コピーボタン追加(lucide-reactのCopy/Checkアイコンimport、useState(copied)、handleCopyコールバック、ヘッダー部JSX追加)
tests/unit/components/worktree/MemoCard.test.tsx コピー機能テスト追加(vi.mock('@/lib/clipboard-utils')によるモック設定含む)
CLAUDE.md MemoCard.tsxのモジュール説明追加(Issue #321: コピーボタン追加、Copy/Checkアイコン切替)

変更なしのファイル(確認済み)

ファイル 変更なしの根拠
src/components/worktree/MemoPane.tsx MemoCardPropsインタフェースの変更なし。コピー機能はMemoCard内部に閉じた状態管理(useState)とコールバックで実装されるため、propsの伝播変更は不要。
tests/unit/components/worktree/MemoPane.test.tsx aria-labelベースのボタン取得(getAllByRole('button', { name: /delete/i }))を使用しているため、MemoCardへのコピーボタン追加による既存テストの破壊は発生しない。ただし、実装時にはMemoPane.test.tsxの全テストが引き続きパスすることを確認すること。
src/lib/clipboard-utils.ts copyToClipboard()関数の提供元。MemoCardから新規importされるが、関数自体の変更は不要。
src/types/models.ts WorktreeMemo型定義への変更なし。コピー対象は既存のcontentフィールドであり、新規フィールドやDBスキーマの変更は発生しない。
src/components/worktree/WorktreeDetailRefactored.tsx MemoPaneの親コンポーネントだが、MemoCardの内部変更に影響されない。

i18n翻訳ファイルへの影響

翻訳ファイル(locales/en/, locales/ja/)への変更は不要。aria-labelはFileViewerの既存パターンに合わせてハードコードとする(i18n一括対応は別Issue)。

補足情報

  • タイトル(title フィールド)のコピーは本スコープ外とする
  • コピー成功/失敗のトースト通知はFileViewerと同様に省略(アイコン切替のみでフィードバック)
  • aria-labelはFileViewerの既存パターンに合わせてハードコードとする(i18n一括対応は別Issue)
    • 推奨値: 'Copy memo content'(既存パターン: FileViewer 'Copy file content'、ConversationPairCard 'Copy message''Copy [対象] content' の命名規則に従う)
  • CLAUDE.mdへのMemoCard.tsxモジュール説明追記が必要(実装PRに含めること)
  • 既存の削除ボタンはインラインSVGで実装されているが、コピーボタンはlucide-react(Copy/Check)を使用する。アイコン実装方式の統一(削除ボタンのlucide-react移行)は本Issueのスコープ外とし、別途リファクタリングIssueで対応する

関連Issue / 参考実装


レビュー履歴

Stage 2: 通常レビュー指摘事項反映 (2026-02-20)

  • Issue内容の初期レビュー結果を反映

Stage 4: 影響範囲レビュー指摘事項反映 (2026-02-20)

  • F101: 影響範囲テーブルにMemoPane.tsxを「変更なし」として明記
  • F102: MemoPane.test.tsxが影響を受けない根拠を明記(aria-labelベース取得のため破壊なし)
  • F103: WorktreeMemo型定義・DBスキーマ変更不要の根拠を明記
  • F104: i18n翻訳ファイルへの変更が不要であることを明記
  • F105: CLAUDE.mdへのモジュール説明追記を影響範囲テーブルに追加
  • F106: aria-labelの推奨値 'Copy memo content' を補足情報に明記
  • F107: モバイルUIでの4要素レイアウト確認を受け入れ条件に追加
  • F108: テスト要件にvi.mock('@/lib/clipboard-utils')によるモック指針を追記

Stage 6: 2回目通常レビュー指摘事項反映 (2026-02-20)

  • F201: 削除ボタンとコピーボタンのアイコン実装方式混在について補足情報に注記を追加(lucide-react統一は別Issueスコープ)
  • F202: copyToClipboard()の2段階ガード詳細はFileViewerパターン踏襲で自然に解決されるためスキップ

Stage 8: 2回目影響範囲レビュー指摘事項反映 (2026-02-20)

  • F301: テスト要件のモック参考先を「FileViewer等の既存テストパターン」から「MarkdownEditor等の既存テストパターン(tests/unit/components/MarkdownEditor.test.tsx)」に修正

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions