# 通知設計書 27-Shortcut conflict

## 概要

本ドキュメントは、OpenCodeアプリケーションにおける「Shortcut conflict」トースト通知の設計を定義する。

### 本通知の処理概要

この通知は、ユーザーが既に他のコマンドに割り当てられているキーボードショートカットを設定しようとした場合に表示される警告通知である。

**業務上の目的・背景**：OpenCodeはカスタマイズ可能なキーボードショートカット機能を提供しており、ユーザーは自分の好みに合わせてショートカットを変更できる。しかし、同じキー組み合わせを複数のコマンドに割り当てると、意図しない動作を引き起こす可能性がある。この通知により、ユーザーはショートカットの競合を事前に認識し、適切な対処を行える。

**通知の送信タイミング**：設定画面のショートカット設定でキーを入力した際、そのキー組み合わせが既に他のコマンドに割り当てられている場合に即座に表示される。

**通知の受信者**：ショートカット設定操作を実行したユーザー本人。

**通知内容の概要**：競合するキー組み合わせと、そのキーが既に割り当てられているコマンド名を含む警告メッセージが表示される。

**期待されるアクション**：ユーザーは通知を確認し、別のキー組み合わせを選択するか、既存の割り当てを変更する。

## 通知種別

トースト通知（アプリ内通知）

## 送信仕様

### 基本情報

| 項目 | 内容 |
|-----|------|
| 送信方式 | 同期（UIスレッドで即時表示） |
| 優先度 | 中 |
| リトライ | なし |

### 送信先決定ロジック

現在アクティブなアプリケーションウィンドウ内でトースト通知として表示。ショートカット設定操作を実行したユーザーに対してのみ表示。

## 通知テンプレート

### トースト通知

| 項目 | 内容 |
|-----|------|
| タイトル | `settings.shortcuts.conflict.title` |
| 説明 | `settings.shortcuts.conflict.description` |
| アイコン | なし（デフォルト） |
| バリアント | default |

### 本文テンプレート

```
タイトル: Shortcut already in use
説明: {{keybind}} is already assigned to {{titles}}.
```

### 添付ファイル

なし

## テンプレート変数

| 変数名 | 説明 | データ取得元 | 必須 |
|--------|------|-------------|-----|
| keybind | フォーマットされたキー組み合わせ | `formatKeybind(next)` | Yes |
| titles | 競合するコマンドのタイトル（カンマ区切り） | `conflicts.values().join(", ")` | Yes |

## 送信トリガー・条件

### トリガー一覧

| トリガー種別 | トリガーイベント | 送信条件 | 説明 |
|------------|----------------|---------|------|
| 画面操作 | キーボード入力（ショートカット設定中） | 同一シグネチャが他コマンドに存在 | ショートカット競合検出 |

### 送信抑止条件

| 条件 | 説明 |
|-----|------|
| 競合なし | 入力されたキー組み合わせが未使用の場合 |
| 自分自身との競合 | 編集中のコマンド自身との一致は除外 |

## 処理フロー

### 送信フロー

```mermaid
flowchart TD
    A[キー入力イベント] --> B[signatures関数でシグネチャ生成]
    B --> C{他コマンドと競合?}
    C -->|競合あり| D[conflictsマップに追加]
    D --> E{conflicts.size > 0?}
    E -->|Yes| F[showToast警告通知]
    F --> G[return - 設定を適用しない]
    C -->|競合なし| H[input.set - 設定を適用]
    E -->|No| H
    H --> I[input.stop - 編集終了]
```

## データベース参照・更新仕様

### 参照テーブル一覧

| テーブル名 | 用途 | 備考 |
|-----------|------|------|
| なし | なし | クライアントサイド処理のみ |

### 更新テーブル一覧

| テーブル名 | 操作 | 概要 |
|-----------|------|------|
| なし | なし | 競合時は設定を更新しない |

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 対処方法 |
|----------|---------|---------|
| N/A | エラーではなく警告として処理 | N/A |

### リトライ仕様

| 項目 | 内容 |
|-----|------|
| リトライ回数 | N/A |
| リトライ間隔 | N/A |
| リトライ対象エラー | N/A |

## 配信設定

### レート制限

| 項目 | 内容 |
|-----|------|
| 1分あたり上限 | 制限なし |
| 1日あたり上限 | 制限なし |

### 配信時間帯

制限なし（24時間表示可能）

## セキュリティ考慮事項

- 特になし（ローカル設定操作のみ）

## 備考

- 国際化対応（i18n）済み
- 日本語: タイトル「ショートカットは既に使用されています」、説明「{{keybind}} は既に {{titles}} に割り当てられています。」
- 競合が検出された場合、新しいショートカットは設定されない（操作が拒否される）
- 複数のコマンドと競合する場合、全てのコマンド名がカンマ区切りで表示される

---

## コードリーディングガイド

本通知を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: データ構造を理解する

ショートカット競合検出のロジックを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | settings-keybinds.tsx | `packages/app/src/components/settings-keybinds.tsx` | signatures関数とformatKeybind関数の動作 |

**読解のコツ**: signatures関数は入力されたキー組み合わせのシグネチャ（一意識別子）を生成し、既存のショートカットと比較する。

#### Step 2: エントリーポイントを理解する

通知がトリガーされる場所。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | settings-keybinds.tsx | `packages/app/src/components/settings-keybinds.tsx` | useRecordKeybind内のhandle関数 |

**主要処理フロー（settings-keybinds.tsx）**:
1. **行198-255**: useRecordKeybind関数の定義
2. **行215-235**: handle関数内でのキー入力処理
3. **行230-235**: 競合検出ループ
4. **行237-245**: 競合検出時のshowToast呼び出し

#### Step 3: 国際化リソースを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | en.ts | `packages/app/src/i18n/en.ts` | `settings.shortcuts.conflict.*`キーの定義を確認 |
| 3-2 | ja.ts | `packages/app/src/i18n/ja.ts` | 日本語翻訳を確認 |

**主要処理フロー**:
- **en.ts 行719-720**: `settings.shortcuts.conflict.title/description`
- **ja.ts 行642-643**: 日本語翻訳

### プログラム呼び出し階層図

```
SettingsKeybinds (settings-keybinds.tsx)
    │
    └─ KeybindItem
           │
           └─ useRecordKeybind(input)
                  │
                  └─ document.addEventListener("keydown", handle)
                         │
                         ├─ next = Keybind.fromEvent(e)
                         │
                         ├─ signatures(next) → シグネチャ配列
                         │
                         ├─ for (sig of signatures)
                         │      │
                         │      └─ for (item of input.used().get(sig))
                         │             │
                         │             ├─ item.id === id → skip
                         │             │
                         │             └─ conflicts.set(item.id, item.title)
                         │
                         ├─ conflicts.size > 0?
                         │      │
                         │      └─ Yes: showToast({
                         │               title: i18n("settings.shortcuts.conflict.title"),
                         │               description: i18n("settings.shortcuts.conflict.description", {
                         │                 keybind: formatKeybind(next),
                         │                 titles: [...conflicts.values()].join(", ")
                         │               })
                         │             })
                         │             return
                         │
                         └─ input.set(id, next)
                            input.stop()
```

### データフロー図

```
[入力]               [処理]                    [出力]

KeyDown Event
    │
    ▼
Keybind.fromEvent(e)
    │
    ▼
signatures(keybind)
    │
    ▼
input.used().get(sig)
    │
    ▼
conflicts Map構築
    │
    ▼
conflicts.size > 0?
    │
    ├─ Yes ─────────────────────────────┐
    │                                    ▼
    │                    showToast({
    │                      title: "Shortcut already in use",
    │                      description: `${keybind} is already assigned to ${titles}`
    │                    })
    │                           │
    │                           ▼
    │                    [警告トースト通知表示]
    │                           │
    │                           ▼
    │                    return (設定は適用されない)
    │
    └─ No ──────────────────────────────┐
                                         ▼
                              input.set(id, next)
                                         │
                                         ▼
                              [ショートカット設定更新]
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| settings-keybinds.tsx | `packages/app/src/components/settings-keybinds.tsx` | ソース | ショートカット設定画面（通知トリガー元） |
| toast.tsx | `packages/ui/src/components/toast.tsx` | ソース | トースト通知コンポーネント |
| en.ts | `packages/app/src/i18n/en.ts` | 設定 | 英語メッセージ定義 |
| ja.ts | `packages/app/src/i18n/ja.ts` | 設定 | 日本語メッセージ定義 |
