# 通知設計書 14-チャットメッセージ通知

## 概要

本ドキュメントは、Etherpadにおけるチャットメッセージ通知の設計仕様を定義する。この通知は、パッドのチャット機能で新しいメッセージが投稿された際に、チャットボックスが非表示のユーザーに対してポップアップ通知（Gritter）で表示される。

### 本通知の処理概要

この通知は新しいチャットメッセージの受信をリアルタイムにユーザーに伝え、コミュニケーションの見落としを防ぐためのポップアップ通知である。

**業務上の目的・背景**：共同編集環境において、チャット機能はユーザー間のコミュニケーションに不可欠である。しかし、ユーザーがドキュメント編集に集中している場合、チャットボックスを開いていないことがある。この通知により、他のユーザーからのメッセージを見逃すことなく、スムーズなコミュニケーションを維持できる。チャットボックスが開いている場合は通知は表示されず、直接チャットボックス内でメッセージが表示される。

**通知の送信タイミング**：サーバー側で`sendChatMessageToPadClients`関数が実行され、Socket.IO経由で`CHAT_MESSAGE`タイプのメッセージがパッドルームにブロードキャストされる。クライアント側では、`chat.addMessage`関数で受信処理が行われ、チャットボックスが非表示の場合にのみGritter通知が表示される。

**通知の受信者**：チャットメッセージが投稿されたパッドに接続しているすべてのユーザー。ただし、通知ポップアップが表示されるのはチャットボックスを開いていないユーザーのみである。

**通知内容の概要**：送信者の名前とメッセージ本文がポップアップ形式で表示される。通知は4秒後に自動的に消える。ただし、メンション（ユーザー名が含まれる）がある場合はスティッキー表示となり、手動で閉じるまで表示され続ける。

**期待されるアクション**：ユーザーは通知を確認し、必要に応じてチャットボックスを開いてメッセージに返信する。Alt+Cキーでチャットボックスを開くことができる。

## 通知種別

アプリ内通知（Gritterポップアップ）

## 送信仕様

### 基本情報

| 項目 | 内容 |
|-----|------|
| 送信方式 | 同期（WebSocket経由のリアルタイム通知） |
| 優先度 | 中 |
| リトライ | 無 |

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

Socket.IOの`room`機能を使用し、パッドIDに対応するルームに参加しているすべてのクライアントソケットに対してブロードキャストされる。具体的には`socketio.sockets.in(padId).emit('message', {...})`で実現される。クライアント側で、チャットボックスが開いているかどうかを判定し、ポップアップ表示の要否を決定する。

## 通知テンプレート

### ポップアップ通知の場合

| 項目 | 内容 |
|-----|------|
| 送信者名 | メッセージ送信者の表示名 |
| メッセージ | チャットメッセージ本文（HTMLエスケープ済み、リンクはクリック可能） |
| 表示時間 | 4秒（ctx.duration） |
| スティッキー | メンション時はtrue |
| CSSクラス | chat-gritter-msg |
| 表示位置 | bottom |

### 本文テンプレート

```javascript
$.gritter.add({
  text: $('<p>')
    .append($('<span>').addClass('author-name').text(ctx.authorName))
    .append($('<div>').html(ctx.text).contents()),
  sticky: ctx.sticky,
  time: ctx.duration,
  position: 'bottom',
  class_name: 'chat-gritter-msg',
});
```

### 添付ファイル

なし

## テンプレート変数

| 変数名 | 説明 | データ取得元 | 必須 |
|--------|------|-------------|-----|
| ctx.authorName | 送信者の表示名 | authorManager.getAuthorName | Yes |
| ctx.text | メッセージ本文（HTMLエスケープ済み） | ChatMessage.text | Yes |
| ctx.sticky | スティッキー表示フラグ | メンション検出結果 | Yes |
| ctx.duration | 表示時間（ミリ秒） | デフォルト4000 | Yes |

## 送信トリガー・条件

### トリガー一覧

| トリガー種別 | トリガーイベント | 送信条件 | 説明 |
|------------|----------------|---------|------|
| ユーザー操作 | チャットメッセージ送信 | メッセージが空でないこと | chat.send関数実行 |

### 送信抑止条件

| 条件 | 説明 |
|-----|------|
| チャットボックス表示中 | `$('#chatbox').hasClass('visible')`がtrueの場合 |
| duration <= 0 | フック関数でdurationが0以下に設定された場合 |
| 履歴読み込み | isHistoryAdd === trueの場合 |

## 処理フロー

### 送信フロー

```mermaid
flowchart TD
    A[チャットメッセージ送信] --> B[handleChatMessage]
    B --> C[ChatMessage作成]
    C --> D[sendChatMessageToPadClients]
    D --> E[pad.appendChatMessage]
    D --> F[socketio.sockets.in.emit]
    F --> G[クライアント受信]
    G --> H[chat.addMessage]
    H --> I{チャットボックス表示中?}
    I -->|Yes| J[チャット欄に追加のみ]
    I -->|No| K{duration > 0?}
    K -->|Yes| L[$.gritter.add表示]
    K -->|No| J
```

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

### 参照テーブル一覧

| テーブル名 | 用途 | 備考 |
|-----------|------|------|
| globalAuthor:{authorId} | 送信者名の取得 | authorManager.getAuthorName |

### 更新テーブル一覧

| テーブル名 | 操作 | 概要 |
|-----------|------|------|
| pad:{padId} | UPDATE | chatHead更新、メッセージ保存 |

#### チャットメッセージ保存

| 操作 | 項目（カラム名） | 更新値 | 備考 |
|-----|-----------------|-------|------|
| APPEND | chat:{padId}:{chatHead} | ChatMessageオブジェクト | チャット履歴に追加 |
| UPDATE | chatHead | インクリメント | 最新チャットインデックス |

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 対処方法 |
|----------|---------|---------|
| authorId不明 | DBに著者情報がない | 'unknown'に置換して処理続行 |

### リトライ仕様

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

## 配信設定

### レート制限

| 項目 | 内容 |
|-----|------|
| 1分あたり上限 | 制限なし（サーバー全体のレート制限に従う） |
| 1日あたり上限 | 制限なし |

### 配信時間帯

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

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

- メッセージ本文はpadutils.escapeHtmlWithClickableLinksでHTMLエスケープされる
- リンクは自動的に検出され、クリック可能に変換される（target="_blank"）
- ユーザー入力のサニタイズによりXSS攻撃を防止
- authorIdはサーバー側で上書きされ、なりすましを防止

## 備考

- chatNewMessageフックでカスタマイズ可能（rendered, sticky, durationなど）
- chatSendMessageフックで送信前にメッセージを修正可能
- チャットメッセージの最大長は999文字（chatinputのmaxlength属性）
- 未読メッセージカウンターが#chatcounterで表示される

---

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

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

### 推奨読解順序

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

まず、ChatMessageクラスの構造を理解することが重要である。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | ChatMessage.ts | `src/static/js/ChatMessage.ts` | ChatMessageクラスの構造（text, authorId, time, displayName） |

**読解のコツ**: fromObject静的メソッドでサーバーから受信したデータをインスタンス化する方法を確認する。

#### Step 2: サーバー側の送信処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | PadMessageHandler.ts | `src/node/handler/PadMessageHandler.ts` | handleChatMessage関数（492-499行目）でメッセージ受信処理 |
| 2-2 | PadMessageHandler.ts | `src/node/handler/PadMessageHandler.ts` | sendChatMessageToPadClients関数（512-526行目）でブロードキャスト |

**主要処理フロー**:
1. **493行目**: `ChatMessage.fromObject(message.data.message)`でメッセージ作成
2. **496-497行目**: 時刻とauthorIdをサーバー側で上書き
3. **520行目**: `authorManager.getAuthorName(message.authorId)`で表示名取得
4. **521-524行目**: `socketio.sockets.in(padId).emit`でブロードキャスト

#### Step 3: クライアント側の受信処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | chat.ts | `src/static/js/chat.ts` | addMessage関数（120-224行目）で受信処理とGritter表示 |

**主要処理フロー**:
- **143-159行目**: コンテキスト(ctx)オブジェクトの構築
- **165行目**: `$('#chatbox').hasClass('visible')`でチャットボックス表示確認
- **168-171行目**: メンション検出（normalize関数使用）
- **207-221行目**: $.gritter.addでポップアップ表示

#### Step 4: HTML構造を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | pad.html | `src/templates/pad.html` | chatbox要素（386-403行目）を確認 |

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

```
chat.send() [クライアント]
    │
    └─ collabClient.sendMessage({type: 'CHAT_MESSAGE', message})
           │
           ▼
handleChatMessage(socket, message) [サーバー]
    │
    └─ sendChatMessageToPadClients(chatMessage, padId)
           │
           ├─ pad.appendChatMessage(message)
           ├─ authorManager.getAuthorName(authorId)
           └─ socketio.sockets.in(padId).emit('message', {...})
                  │
                  ▼
           chat.addMessage(msg, true) [クライアント]
                  │
                  ├─ hooks.aCallAll('chatNewMessage', ctx)
                  ├─ チャット欄にメッセージ追加
                  └─ [チャットボックス非表示時]
                         │
                         └─ $.gritter.add({...})
```

### データフロー図

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

ユーザー入力 ───▶ chat.send() ───▶ WebSocket送信
                                        │
                                        ▼
                              handleChatMessage
                                        │
                                        ▼
                        sendChatMessageToPadClients
                                        │
                         ┌──────────────┴──────────────┐
                         │                             │
                         ▼                             ▼
                  DB保存                       WebSocketブロードキャスト
                         │                             │
                         ▼                             ▼
              pad.appendChatMessage             クライアント受信
                                                       │
                                                       ▼
                                               chat.addMessage
                                                       │
                                        ┌──────────────┴──────────────┐
                                        │                             │
                                   [表示中]                       [非表示]
                                        │                             │
                                        ▼                             ▼
                                チャット欄追加          $.gritter.add + チャット欄追加
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| chat.ts | `src/static/js/chat.ts` | ソース | クライアント側チャット処理、Gritter表示 |
| ChatMessage.ts | `src/static/js/ChatMessage.ts` | ソース | チャットメッセージデータ構造 |
| PadMessageHandler.ts | `src/node/handler/PadMessageHandler.ts` | ソース | サーバー側メッセージ処理 |
| pad_utils.ts | `src/static/js/pad_utils.ts` | ソース | HTMLエスケープ、リンク変換 |
| gritter.css | `src/static/skins/colibris/src/components/gritter.css` | スタイル | Gritterポップアップのスタイル |
| pad.html | `src/templates/pad.html` | テンプレート | チャットボックスHTML構造 |
