# 通知設計書 15-チャットメンション通知

## 概要

本ドキュメントは、Etherpadにおけるチャットメンション通知の設計仕様を定義する。この通知は、チャットメッセージ内でユーザー名が言及（メンション）された際に、ブラウザタブにバッジを表示し、通知をスティッキー（手動で閉じるまで表示）にする機能である。

### 本通知の処理概要

この通知は自分のユーザー名がチャットメッセージで言及されたことをユーザーに強調して伝え、重要なメッセージの見落としを防ぐためのアプリ内通知である。

**業務上の目的・背景**：共同編集環境において、特定のユーザーに対するダイレクトなコミュニケーションは重要である。多くのメッセージが飛び交う中で、自分宛てのメッセージを見逃すと、チームワークに支障をきたす可能性がある。この機能により、自分の名前がチャットで言及された場合に、視覚的に目立つ通知（ブラウザタブのバッジとスティッキー通知）が表示され、確実に気づくことができる。

**通知の送信タイミング**：チャットメッセージの受信時、`chat.addMessage`関数内でメンション検出が行われる。メンション判定は、メッセージ本文にユーザーの表示名（正規化：ダイアクリティクス除去、小文字化）が含まれているかどうかで行われる。自分自身が送信したメッセージ、匿名ユーザーからのメッセージ、チャットボックスが既に開いている場合、入力欄にフォーカスがある場合は除外される。

**通知の受信者**：メンションされた特定のユーザー。チャットメッセージはすべてのユーザーに送信されるが、メンション検出と特別な通知処理はクライアント側でローカルに行われる。

**通知内容の概要**：Tinyconライブラリを使用してブラウザタブのファビコンにメンション数のバッジが表示される。また、Gritter通知がスティッキーモードで表示され、手動で閉じるまで消えない。

**期待されるアクション**：ユーザーはバッジまたはスティッキー通知に気づき、チャットボックスを開いてメッセージを確認する。チャットボックスを開くか、入力欄をクリックすると、メンションカウンターとTinyconバッジがリセットされる。

## 通知種別

アプリ内通知（Tinyconバッジ + Gritterスティッキーポップアップ）

## 送信仕様

### 基本情報

| 項目 | 内容 |
|-----|------|
| 送信方式 | 同期（クライアント側ローカル処理） |
| 優先度 | 高 |
| リトライ | 無 |

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

クライアント側でローカルに判定される。以下の条件をすべて満たす場合にメンション通知が発動する：
1. メッセージ送信者が自分自身ではない（`msg.authorId !== window.clientVars.userId`）
2. 送信者名が匿名でない（`ctx.authorName !== html10n.get('pad.userlist.unnamed')`）
3. メッセージ本文に自分の表示名が含まれている（正規化後の比較）
4. チャット入力欄にフォーカスがない（`!$('#chatinput').is(':focus')`）
5. 履歴読み込みではない（`!isHistoryAdd`）
6. チャットボックスが開いていない（`!chatOpen`）

## 通知テンプレート

### Tinyconバッジ

| 項目 | 内容 |
|-----|------|
| 表示形式 | ファビコン上の数字バッジ |
| 数値 | 累積メンション数（chatMentions変数） |
| リセット条件 | チャットボックス表示時、入力欄クリック時 |

### Gritterスティッキー通知

| 項目 | 内容 |
|-----|------|
| 送信者名 | メッセージ送信者の表示名 |
| メッセージ | チャットメッセージ本文 |
| sticky | true（手動で閉じるまで表示） |
| CSSクラス | chat-gritter-msg |

### 本文テンプレート

```javascript
// Tinyconバッジ設定
Tinycon.setBubble(chatMentions);

// Gritterスティッキー通知
$.gritter.add({
  text: $('<p>').append(...),
  sticky: true,  // メンション時はスティッキー
  time: ctx.duration,
  position: 'bottom',
  class_name: 'chat-gritter-msg',
});
```

### 添付ファイル

なし

## テンプレート変数

| 変数名 | 説明 | データ取得元 | 必須 |
|--------|------|-------------|-----|
| chatMentions | 累積メンション数 | chat.tsローカル変数 | Yes |
| ctx.authorName | 送信者の表示名 | ChatMessage.displayName | Yes |
| ctx.text | メッセージ本文 | ChatMessage.text | Yes |
| ctx.sticky | スティッキーフラグ（true） | メンション検出結果 | Yes |

## 送信トリガー・条件

### トリガー一覧

| トリガー種別 | トリガーイベント | 送信条件 | 説明 |
|------------|----------------|---------|------|
| メッセージ受信 | chat.addMessage実行 | メンション検出条件を満たすこと | 6つの条件すべてを満たす場合 |

### 送信抑止条件

| 条件 | 説明 |
|-----|------|
| 自分自身のメッセージ | 自分が送信したメッセージにはメンション通知しない |
| 匿名ユーザーからのメッセージ | 送信者が匿名の場合は除外 |
| チャット入力欄にフォーカス | 入力中のユーザーには通知しない |
| チャットボックス表示中 | 既にチャットを見ている場合は通知不要 |
| 履歴読み込み | 過去のメッセージ読み込み時は通知しない |

## 処理フロー

### 送信フロー

```mermaid
flowchart TD
    A[チャットメッセージ受信] --> B{自分のメッセージ?}
    B -->|Yes| C[メンション処理スキップ]
    B -->|No| D{送信者は匿名?}
    D -->|Yes| C
    D -->|No| E{メッセージに自分の名前含む?}
    E -->|No| C
    E -->|Yes| F{入力欄にフォーカス?}
    F -->|Yes| C
    F -->|No| G{履歴読み込み?}
    G -->|Yes| C
    G -->|No| H{チャットボックス表示中?}
    H -->|Yes| C
    H -->|No| I[メンション検出]
    I --> J[chatMentionsインクリメント]
    J --> K[Tinycon.setBubble]
    K --> L[ctx.sticky = true]
    L --> M[$.gritter.addスティッキー表示]
```

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

### 参照テーブル一覧

なし（すべてクライアント側ローカル処理）

### 更新テーブル一覧

なし

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 対処方法 |
|----------|---------|---------|
| Tinycon未対応 | 一部のブラウザでTinyconがサポートされない | エラーは発生せず、バッジのみ非表示 |

### リトライ仕様

| 項目 | 内容 |
|-----|------|
| リトライ回数 | N/A（ローカル処理） |
| リトライ間隔 | N/A |
| リトライ対象エラー | N/A |

## 配信設定

### レート制限

| 項目 | 内容 |
|-----|------|
| 1分あたり上限 | 制限なし（クライアント側ローカル処理） |
| 1日あたり上限 | 制限なし |

### 配信時間帯

制限なし（リアルタイムチャット）

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

- メンション検出はクライアント側でローカルに行われるため、サーバー負荷なし
- ユーザー名の比較は正規化（normalize関数）により、ダイアクリティクスや大文字小文字の違いを吸収
- 意図しないメンション（偶然の一致）の可能性はあるが、セキュリティリスクは低い

## 備考

- Tinyconは外部ライブラリ（tinycon/tinycon）として読み込まれる
- chatMentionsはchat.tsモジュールのローカル変数として管理される
- chat.show()が呼ばれると、chatMentionsは0にリセットされ、Tinycon.setBubble(0)も実行される
- 入力欄クリック時にもリセット処理が行われる（239-242行目）
- normalize関数はNFD正規化でダイアクリティクスを除去し、小文字化する

---

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

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

### 推奨読解順序

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

まず、メンション検出に使用される変数と関数を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | chat.ts | `src/static/js/chat.ts` | chatMentions変数（34行目）とnormalize関数（28行目） |
| 1-2 | chat.ts | `src/static/js/chat.ts` | Tinyconインポート（22行目） |

**読解のコツ**: normalize関数がどのようにテキストを正規化するかを確認する。NFD正規化とダイアクリティクス除去の仕組みを理解する。

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

メンション検出の実装を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | chat.ts | `src/static/js/chat.ts` | addMessage関数内のメンション検出（168-178行目） |

**主要処理フロー**:
1. **168-171行目**: wasMentioned判定（4つの条件のAND）
2. **174-178行目**: メンション検出時の処理（chatMentionsインクリメント、Tinycon設定、sticky設定）

#### Step 3: リセット処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | chat.ts | `src/static/js/chat.ts` | show関数内のリセット（40-41行目） |
| 3-2 | chat.ts | `src/static/js/chat.ts` | 入力欄クリック時のリセット（239-242行目） |

**主要処理フロー**:
- **40行目**: `chatMentions = 0;`
- **41行目**: `Tinycon.setBubble(0);`

#### Step 4: Gritter表示を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | chat.ts | `src/static/js/chat.ts` | $.gritter.add呼び出し（214-220行目）でstickyオプション確認 |

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

```
chat.addMessage(msg, increment, isHistoryAdd)
    │
    ├─ [メンション検出]
    │      │
    │      ├─ msg.authorId !== window.clientVars.userId?
    │      ├─ ctx.authorName !== 'unnamed'?
    │      ├─ normalize(ctx.text).includes(normalize(ctx.authorName))?
    │      └─ !alreadyFocused && !isHistoryAdd && !chatOpen?
    │             │
    │             └─ [すべてtrue]
    │                    │
    │                    ├─ chatMentions++
    │                    ├─ Tinycon.setBubble(chatMentions)
    │                    └─ ctx.sticky = true
    │
    └─ $.gritter.add({sticky: ctx.sticky, ...})
           │
           └─ [スティッキー通知表示]

chat.show() / $('#chatinput').click()
    │
    ├─ chatMentions = 0
    └─ Tinycon.setBubble(0)
```

### データフロー図

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

チャットメッセージ受信 ───▶ メンション検出 ───▶ [メンションあり?]
                                                        │
                                              ┌─────────┴─────────┐
                                              │                   │
                                           [なし]             [あり]
                                              │                   │
                                              ▼                   ▼
                                      通常通知表示        chatMentions++
                                                                  │
                                                   ┌──────────────┼──────────────┐
                                                   │              │              │
                                                   ▼              ▼              ▼
                                          Tinyconバッジ    ctx.sticky=true   Gritter表示
                                                                              (スティッキー)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| chat.ts | `src/static/js/chat.ts` | ソース | メンション検出、Tinycon/Gritter表示 |
| tinycon | `node_modules/tinycon/tinycon.js` | 外部ライブラリ | ファビコンバッジ表示 |
| gritter.css | `src/static/skins/colibris/src/components/gritter.css` | スタイル | Gritterポップアップのスタイル |
| html10n.js | `src/static/js/vendors/html10n.js` | ライブラリ | ローカライズ（unnamed判定用） |
