# 機能設計書 25-差分比較

## 概要

本ドキュメントは、Wikipedia iOSアプリケーションにおける差分比較機能の設計を記載する。この機能により、ユーザーは2つのリビジョン間の変更内容（差分）を視覚的に確認できる。

### 本機能の処理概要

差分比較機能は、fromリビジョンとtoリビジョンの間で変更されたテキストを取得し、追加・削除・変更箇所をハイライト表示する。また、編集者への感謝送信、編集の取り消し（Undo）、ロールバック操作を提供する。

**業務上の目的・背景**：Wikipediaの版管理において、どのような編集が行われたかを確認することは記事品質の監視に不可欠である。差分比較により、荒らしの検出、編集の妥当性確認、議論の根拠となる変更の特定が可能になる。

**機能の利用シーン**：編集履歴画面からリビジョンを選択した場合、ウォッチリストから変更を確認する場合、ディープリンクから特定の差分を開く場合に使用される。

**主要な処理内容**：
1. 2つのリビジョン間の差分データ取得（DiffController）
2. 差分リスト表示（追加/削除/変更のハイライト）
3. ヘッダーに記事情報・編集者情報を表示
4. ツールバーによる操作（前/次リビジョン、感謝、Undo、ロールバック、共有、ウォッチ）
5. 初回リビジョン表示（単一リビジョン）
6. ディープリンク対応

**関連システム・外部連携**：MediaWiki API（compare, revisions）、WMFWatchlistDataController（Undo/Rollback）、ThanksGiving（感謝送信）

**権限による制御**：閲覧は全ユーザー可能。Undo/Rollbackは編集権限が必要。感謝送信はログインユーザーのみ。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| - | 編集履歴画面 | 呼び出し元 | リビジョン選択から遷移 |
| - | ウォッチリスト画面 | 呼び出し元 | 変更確認から遷移 |
| - | 差分比較画面 | 主画面 | 差分表示とアクション |
| - | Undoアラート | アラート | 取り消し確認と要約入力 |
| - | Rollbackアラート | アラート | ロールバック確認 |
| - | 感謝送信確認 | ヒント | 感謝送信の結果表示 |
| - | 初回説明パネル | モーダル | 差分機能の説明 |

## 機能種別

データ取得、差分表示、編集操作、共有

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| siteURL | URL | Yes | Wikipediaサイト URL | 有効なWikipediaサイトURL |
| fromRevisionID | Int? | No | 比較元リビジョンID | nilの場合は前リビジョンを取得 |
| toRevisionID | Int? | Yes | 比較先リビジョンID | 整数 |
| articleTitle | String? | No | 記事タイトル | nilの場合はAPIから取得 |
| fromModel | WMFPageHistoryRevision? | No | 比較元リビジョンモデル | nilの場合はAPIから取得 |
| toModel | WMFPageHistoryRevision? | No | 比較先リビジョンモデル | nilの場合はAPIから取得 |
| revisionRetrievingDelegate | DiffRevisionRetrieving? | No | リビジョン取得デリゲート | 前後リビジョン取得用 |
| firstRevision | WMFPageHistoryRevision? | No | 初回リビジョン | 履歴境界判定用 |

### 入力データソース

- PageHistoryViewController（リビジョン選択）
- ディープリンク（URL解析）
- DiffController（差分データ取得）
- WMFArticleDataController（ウォッチ/ロールバック権限）

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| listViewModel | [DiffListGroupViewModel] | 差分表示用ビューモデル |
| headerViewModel | DiffHeaderViewModel | ヘッダー表示用ビューモデル |

### 出力先

- DiffListViewController（差分リスト表示）
- DiffToolbarView（ツールバー状態更新）
- ThanksGiving（感謝送信結果）

## 処理フロー

### 処理シーケンス

```
1. 差分画面の初期化
   ├─ DiffToolbarViewのセットアップ
   └─ containerViewModel.state = .loading

2. モデル準備
   ├─ ディープリンク時：populateModelsFromDeepLink
   ├─ fromModel欠損時：fetchFromModelAndFinishSetup
   ├─ toModel欠損時：fetchToModelAndFinishSetup
   └─ 両方あり時：直接midSetup→completeSetup

3. midSetup
   ├─ ヘッダービューモデル生成
   ├─ DiffListViewController生成
   ├─ リード画像取得
   ├─ ウォッチ/ロールバック権限取得
   └─ 編集カウント取得（singleタイプ時）

4. completeSetup
   ├─ 初回リビジョン時：fetchFirstDiff
   ├─ 通常時：fetchDiff
   └─ 前後リビジョン準備（ツールバー用）

5. ユーザー操作
   ├─ 前/次リビジョン：新DiffContainerVCへ遷移
   ├─ 感謝送信：ThanksGivingプロトコル経由
   ├─ Undo：アラート表示→WMFWatchlistDataController.undo
   ├─ Rollback：アラート表示→WMFWatchlistDataController.rollback
   ├─ 共有：UIActivityViewController
   └─ ウォッチ/アンウォッチ：WatchlistController経由
```

### フローチャート

```mermaid
flowchart TD
    A[差分画面開始] --> B{モデル状況}
    B -->|ディープリンク| C[populateModelsFromDeepLink]
    B -->|fromなし| D[fetchFromModel]
    B -->|toなし| E[fetchToModel]
    B -->|両方あり| F[midSetup]
    C --> F
    D --> F
    E --> F
    F --> G[completeSetup]
    G --> H{初回リビジョン?}
    H -->|Yes| I[fetchFirstDiff]
    H -->|No| J[fetchDiff]
    I --> K{結果}
    J --> K
    K -->|成功+データあり| L[state = .data]
    K -->|成功+データなし| M[state = .empty]
    K -->|失敗| N[state = .error]
    L --> O[差分リスト表示]
    M --> P[空状態表示]
    N --> Q[エラー表示]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-001 | 初回リビジョン判定 | toModel.revisionID == firstRevision.revisionID かつ fromModel == nil | 履歴最初のリビジョン表示時 |
| BR-002 | 2番目リビジョン判定 | fromModel.revisionID == firstRevision.revisionID | 履歴2番目のリビジョン表示時 |
| BR-003 | 差分タイプ | 初回リビジョンは single、それ以外は compare | BR-001の判定結果に基づく |
| BR-004 | 前/次ボタン状態 | prevModel/nextModel の有無でボタンの有効/無効を制御 | 常時 |
| BR-005 | Undoボタン有効化 | wmfProjectがnilでない場合のみ有効 | completeSetup時 |
| BR-006 | 匿名ユーザー感謝不可 | 匿名ユーザーには感謝を送信できない | toModel.isAnon == true時 |
| BR-007 | 初回説明パネル | 初めての差分表示時に説明パネルを表示 | UserDefaults.didShowDiffPanel == false時 |

### 計算ロジック

| 計算項目 | 計算式・ロジック |
|---------|----------------|
| バイト差分 | toModel.articleSizeAtRevision - fromModel.articleSizeAtRevision |

## データベース操作仕様

### 操作別データベース影響一覧

| 操作 | 対象 | 操作種別 | 概要 |
|-----|------|---------|------|
| 初回パネル表示フラグ | UserDefaults | SELECT/UPDATE | didShowDiffPanelキー |

### API呼び出し一覧

| API | 操作 | 概要 |
|-----|------|------|
| fetchDiff | GET | 2リビジョン間の差分取得 |
| fetchFirstRevisionDiff | GET | 初回リビジョンの内容取得 |
| fetchAdjacentRevisionModel | GET | 前後リビジョンモデル取得 |
| populateModelsFromDeepLink | GET | ディープリンクからモデル取得 |
| fetchArticleInfo | GET | ウォッチ状態/ロールバック権限取得 |
| fetchEditCount | GET | 編集者の編集カウント取得 |
| fetchLeadImageURL | GET | 記事リード画像取得 |
| undo | POST | 編集の取り消し |
| rollback | POST | 編集のロールバック |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| networkError | 接続エラー | ネットワーク接続なし | アラート表示 |
| DiffError | 差分エラー | 差分取得失敗 | 警告アラート表示 |
| mediaWikiAPIResponseError | API エラー | ブロック等のAPI エラー | ブロックパネル表示 |

### リトライ仕様

自動リトライなし。ユーザーが画面を再表示する必要がある。

## トランザクション仕様

Undo/Rollback操作はMediaWiki APIで原子的に処理される。

## パフォーマンス要件

- 差分取得：ネットワーク依存、バックグラウンドスレッドで処理
- UI更新：メインスレッドで実行
- アニメーション：前後リビジョン遷移時に上下スライドアニメーション（0.3秒）

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

- ログイン状態に応じたボタン有効化制御
- ブロックエラー時のパネル表示
- WatchlistFunnel/EditAttemptFunnelによる操作ログ記録

## 備考

- DiffContainerViewModel.State: loading, empty, error, data の4状態で管理
- DiffRevisionTransitionでカスタム遷移アニメーションを提供
- ThanksGivingプロトコルで感謝送信機能を実装

---

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

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

### 推奨読解順序

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

差分画面で使用されるモデルとプロトコルを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | DiffContainerViewController.swift | `Wikipedia/Code/DiffContainerViewController.swift` | StubRevisionModel, DiffRevisionRetrieving（L7-18） |
| 1-2 | DiffContainerViewController.swift | `Wikipedia/Code/DiffContainerViewController.swift` | NextPrevModel（L22-25） |

**読解のコツ**: DiffRevisionRetrievingは編集履歴画面との連携プロトコル。NextPrevModelは前後リビジョンへの遷移用。

#### Step 2: 初期化を理解する

2つの初期化パターンを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | DiffContainerViewController.swift | `Wikipedia/Code/DiffContainerViewController.swift` | init (ディープリンク用)（L100-124） |
| 2-2 | DiffContainerViewController.swift | `Wikipedia/Code/DiffContainerViewController.swift` | init (モデル指定)（L126-151） |

**主要処理フロー**:
- **L100-124**: RevisionID指定での初期化（ディープリンク対応）
- **L126-151**: WMFPageHistoryRevisionモデル指定での初期化（履歴画面から）

#### Step 3: ライフサイクルとセットアップを理解する

viewDidLoadからのセットアップフローを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | DiffContainerViewController.swift | `Wikipedia/Code/DiffContainerViewController.swift` | viewDidLoad（L157-196） |

**主要処理フロー**:
- **L162-178**: onLoadクロージャでのモデル準備分岐
- **L180-195**: startSetup後にディープリンク/通常処理の分岐

#### Step 4: モデル取得と差分取得を理解する

API経由でのデータ取得処理を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | DiffContainerViewController.swift | `Wikipedia/Code/DiffContainerViewController.swift` | fetchFromModelAndFinishSetup, fetchToModelAndFinishSetup（L339-393） |
| 4-2 | DiffContainerViewController.swift | `Wikipedia/Code/DiffContainerViewController.swift` | fetchDiff, fetchFirstDiff（L743-814） |

**主要処理フロー**:
- **L362-393**: fetchFromModelAndFinishSetup - 前リビジョン取得
- **L339-360**: fetchToModelAndFinishSetup - 次リビジョン取得
- **L786-814**: fetchDiff - 2リビジョン間差分取得
- **L743-770**: fetchFirstDiff - 初回リビジョン差分取得

#### Step 5: 状態管理を理解する

ViewModel.Stateによる状態遷移を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | DiffContainerViewController.swift | `Wikipedia/Code/DiffContainerViewController.swift` | evaluateState（L527-595） |

**主要処理フロー**:
- **L551-554**: loading状態 - スピナー表示
- **L555-571**: empty状態 - 空状態ビュー表示
- **L572-583**: error状態 - エラービュー表示
- **L584-594**: data状態 - 差分リスト表示

#### Step 6: ツールバーアクションを理解する

DiffToolbarViewDelegateの各アクションを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 6-1 | DiffContainerViewController.swift | `Wikipedia/Code/DiffContainerViewController.swift` | tappedPrevious, tappedNext（L1101-1142） |
| 6-2 | DiffContainerViewController.swift | `Wikipedia/Code/DiffContainerViewController.swift` | tappedUndo, performUndo（L1206-1273） |
| 6-3 | DiffContainerViewController.swift | `Wikipedia/Code/DiffContainerViewController.swift` | tappedRollback, performRollback（L1275-1316） |

**主要処理フロー**:
- **L1101-1125**: tappedPrevious - 前リビジョンへの遷移
- **L1127-1142**: tappedNext - 次リビジョンへの遷移
- **L1206-1249**: tappedUndo - Undoアラート表示
- **L1255-1273**: performUndo - Undo実行
- **L1275-1298**: tappedRollback - Rollbackアラート表示
- **L1300-1316**: performRollback - Rollback実行

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

```
PageHistoryViewController / DeepLink
    │
    └─ DiffContainerViewController.init()
           │
           ├─ viewDidLoad()
           │      ├─ startSetup() → setupToolbarIfNeeded()
           │      └─ onLoad()
           │             ├─ populateModelsFromDeepLink()
           │             ├─ fetchFromModelAndFinishSetup()
           │             ├─ fetchToModelAndFinishSetup()
           │             └─ midSetup() + completeSetup()
           │
           ├─ midSetup()
           │      ├─ populateNewHeaderViewModel()
           │      ├─ setupDiffListViewControllerIfNeeded()
           │      ├─ fetchLeadImageIfNeeded()
           │      ├─ fetchWatchAndRollbackStatus()
           │      └─ fetchEditCountIfNeeded()
           │
           ├─ completeSetup()
           │      ├─ fetchFirstDiff() / fetchDiff()
           │      │      └─ DiffController
           │      │
           │      └─ populatePrevNextModelsForToolbar()
           │
           └─ DiffToolbarViewDelegate
                  ├─ tappedPrevious() → 新DiffContainerVC
                  ├─ tappedNext() → 新DiffContainerVC
                  ├─ tappedThankButton() → ThanksGiving
                  ├─ tappedUndo() → performUndo()
                  │                    └─ WMFWatchlistDataController.undo()
                  ├─ tappedRollback() → performRollback()
                  │                       └─ WMFWatchlistDataController.rollback()
                  ├─ tappedShare() → UIActivityViewController
                  └─ tappedWatch/Unwatch() → WatchlistController
```

### データフロー図

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

fromRevisionID       ───▶ DiffController                   ───▶ DiffListViewController
toRevisionID                 │
                             ├─ fetchDiff()
                             │        │
                             │        ▼
                             │  [DiffListGroupViewModel]
                             │
                             ├─ fetchFirstRevisionDiff()
                             │        │
                             │        ▼
                             │  [DiffListGroupViewModel]
                             │
                             └─ populateModelsFromDeepLink()
                                      │
                                      ▼
                              fromModel, toModel, articleTitle
                                      │
                              ┌───────┴───────┐
                              ▼               ▼
                       DiffHeaderViewModel  DiffToolbarView
                                            (前後/Undo/Rollback等)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| DiffContainerViewController.swift | `Wikipedia/Code/DiffContainerViewController.swift` | ソース | 差分画面メインコントローラー |
| DiffController.swift | `Wikipedia/Code/DiffController.swift` | ソース | 差分データ取得ロジック |
| DiffListViewController.swift | `Wikipedia/Code/DiffListViewController.swift` | ソース | 差分リスト表示 |
| DiffToolbarView.swift | `Wikipedia/Code/DiffToolbarView.swift` | ソース | ツールバービュー |
| DiffContainerViewModel.swift | `Wikipedia/Code/DiffContainerViewModel.swift` | ソース | 差分コンテナビューモデル |
| DiffHeaderViewModel.swift | `Wikipedia/Code/DiffHeaderViewModel.swift` | ソース | ヘッダービューモデル |
| DiffListGroupViewModel.swift | `Wikipedia/Code/DiffListGroupViewModel.swift` | ソース | 差分グループビューモデル |
| DiffRevisionTransition.swift | `Wikipedia/Code/DiffRevisionTransition.swift` | ソース | 前後遷移アニメーション |
| DiffEducationalPanelViewController.swift | `Wikipedia/Code/DiffEducationalPanelViewController.swift` | ソース | 初回説明パネル |
| WatchlistController.swift | `Wikipedia/Code/WatchlistController.swift` | ソース | ウォッチ操作 |
| WMFWatchlistDataController.swift | `WMFData/` | ソース | Undo/Rollback API |
