# 通知設計書 17-適用失敗

## 概要

本ドキュメントは、Actualアプリケーションにおける「適用失敗」エラー通知の設計仕様を定義する。この通知は、サーバーからの変更をローカルデータベースに適用できなかった場合に表示され、バグ報告を促す。

### 本通知の処理概要

この通知は、ローカルで生成された同期メッセージをデータベースに適用しようとした際に失敗した場合（apply-failure）に発行される。これはローカル操作（ユーザーの変更）が正しく保存できなかったことを意味し、アプリケーションの内部バグを示唆する深刻なエラーである。

**業務上の目的・背景**：Actualの同期システムはCRDTベースで、すべての変更がメッセージとしてデータベースに適用される。ローカルで生成されたメッセージの適用に失敗することは、通常あり得ない状況であり、コードのバグを示している。この通知は、そのような深刻な問題を検出し、ユーザーにGitHubでのバグ報告を促すことで、開発チームが問題を認識し修正できるようにする目的がある。

**通知の送信タイミング**：_sendMessages関数内でapplyMessagesが失敗し、SyncError('invalid-schema')がスローされた場合に発行される。これはローカルで生成されたメッセージ（ユーザーの操作による変更）の適用時に発生する。

**通知の受信者**：現在アプリケーションを操作しているユーザー。通知はアプリ内のUIに直接表示されるため、ログイン中のユーザーが受信者となる。

**通知内容の概要**：データベースへの変更適用ができなかったことを伝え、これがバグである可能性が高いことを示唆する。GitHubでのバグ報告を促すリンクを提供する。

**期待されるアクション**：ユーザーはメッセージ内のGitHubリンクをクリックして、新しいissueを作成しバグを報告することが期待される。発生状況や再現手順を報告することで、開発チームが問題を特定し修正できる。

## 通知種別

アプリ内通知（In-App Notification）

## 送信仕様

### 基本情報

| 項目 | 内容 |
|-----|------|
| 送信方式 | 同期（イベントリスナー経由） |
| 優先度 | 高（error） |
| リトライ | 無し |

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

通知は現在アクティブなブラウザ/アプリウィンドウに表示される。特定のユーザー選定ロジックは存在せず、同期エラーが発生したセッションに対して直接通知が発行される。

## 通知テンプレート

### アプリ内通知の場合

| 項目 | 内容 |
|-----|------|
| 通知タイプ | error |
| タイトル | なし（messageのみ） |
| sticky | false（自動消去） |
| 識別ID | なし（自動生成） |

### 本文テンプレート

```
We couldn't apply that change to the database. Please report this as a bug by
[opening a GitHub issue](https://github.com/actualbudget/actual/issues/new?assignees=&labels=bug&template=bug-report.yml&title=%5BBug%5D%3A+).
```

### ボタン

ボタンなし（メッセージ内リンクのみ）

### メッセージ内リンク

| リンクテキスト | URL |
|--------------|-----|
| opening a GitHub issue | https://github.com/actualbudget/actual/issues/new?assignees=&labels=bug&template=bug-report.yml&title=%5BBug%5D%3A+ |

### 添付ファイル

該当なし

## テンプレート変数

| 変数名 | 説明 | データ取得元 | 必須 |
|--------|------|-------------|-----|
| githubIssueLink | GitHubのissue作成URL | ハードコード | Yes |

## 送信トリガー・条件

### トリガー一覧

| トリガー種別 | トリガーイベント | 送信条件 | 説明 |
|------------|----------------|---------|------|
| イベント | sync-event (type: error, subtype: apply-failure) | ローカル変更の適用失敗 | ユーザー操作による変更が保存できない |

### 送信抑止条件

| 条件 | 説明 |
|-----|------|
| 予算ファイル未読み込み | prefs.local.idが未設定の場合、通知は発行されない |

## 処理フロー

### 送信フロー

```mermaid
flowchart TD
    A[ユーザー操作] --> B[同期メッセージ生成]
    B --> C[_sendMessages 呼び出し]
    C --> D[applyMessages 実行]
    D --> E{適用成功?}
    E -->|成功| F[scheduleFullSync]
    E -->|失敗| G{エラー種別?}
    G -->|invalid-schema| H[apply-failure イベント発火]
    G -->|その他| I[通常エラー処理]
    H --> J[sync-event エミット]
    J --> K[listenForSyncEvent がキャッチ]
    K --> L[console.trace でログ出力]
    L --> M[通知オブジェクト作成]
    M --> N[addNotification ディスパッチ]
    N --> O[UI に通知表示]
    O --> P[ユーザーがGitHubリンクをクリック]
    P --> Q[GitHub issue作成ページへ遷移]
```

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

### 参照テーブル一覧

このエラーは適用が失敗するため、参照先テーブルは同期メッセージの内容による。

| テーブル名 | 用途 | 備考 |
|-----------|------|------|
| 動的（メッセージから） | 同期メッセージの適用先 | メッセージのdatasetフィールドで指定 |

### 更新テーブル一覧

このエラーでは更新は失敗するため、実際の更新は行われない。

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 対処方法 |
|----------|---------|---------|
| SQL実行エラー | クエリが失敗 | GitHubでバグ報告 |
| データ整合性エラー | 予期しないデータ状態 | GitHubでバグ報告 |

### リトライ仕様

| 項目 | 内容 |
|-----|------|
| リトライ回数 | 自動リトライなし |
| リトライ間隔 | - |
| リトライ対象エラー | - |

## 配信設定

### レート制限

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

### 配信時間帯

時間帯制限なし（イベント発生時に即座に表示）

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

- エラーの詳細情報はconsole.traceでログ出力される（開発者向け）
- GitHubリンクにはバグテンプレートが設定されている
- ユーザーの個人データはissueに含まれない（ユーザーが手動で追加しない限り）

## 備考

- この通知はstickyではないため、デフォルトの6.5秒後に自動消去される
- ローカル操作の適用失敗は深刻なバグを示唆するため、GitHubへの報告が強く推奨される
- console.traceでスタックトレースが出力され、開発者が問題を調査できる
- No.16（アップデート必要）との違い：No.16はサーバーからの変更、No.17はローカル変更の適用失敗

---

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

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

### 推奨読解順序

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

SyncErrorの型とapply-failureサブタイプを理解することが重要。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | server-events.ts | `packages/loot-core/src/types/server-events.ts` | apply-failure定義（6行目） |
| 1-2 | errors.ts | `packages/loot-core/src/server/errors.ts` | SyncError型（41-68行目） |

**読解のコツ**: apply-failureはSyncSubtypeの一つで、ローカル変更の適用失敗を示す。

#### Step 2: エラー発生箇所を理解する

_sendMessages関数内でapply-failureエラーが発生する箇所を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | index.ts | `packages/loot-core/src/server/sync/index.ts` | _sendMessages関数（431-455行目） |

**主要処理フロー**:
1. **431行目**: _sendMessages関数の定義
2. **433行目**: applyMessagesの呼び出し
3. **434-445行目**: エラーハンドリング
4. **436-444行目**: invalid-schemaの場合にapply-failureイベントを発火

#### Step 3: イベント発火の詳細を理解する

apply-failureイベントがどのように発火されるかを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | index.ts | `packages/loot-core/src/server/sync/index.ts` | _sendMessages内のエラー処理（436-444行目） |

**主要処理フロー**:
- **436行目**: if (e.reason === 'invalid-schema')
- **440-444行目**: app.events.emit('sync', {type: 'error', subtype: 'apply-failure', meta: e.meta})

#### Step 4: イベントリスナーを理解する

通知発行の処理を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | sync-events.ts | `packages/desktop-client/src/sync-events.ts` | apply-failureケース（335-343行目） |

**主要処理フロー**:
- **335行目**: case 'apply-failure'
- **336行目**: console.trace('apply-failure', event.meta)
- **337-342行目**: 通知オブジェクト作成（GitHubリンク付き）

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

```
ユーザー操作（UI）
    │
    ▼
同期メッセージ生成
    │
    ▼
sendMessages (sync/index.ts:482)
    │
    └─ _sendMessages (sync/index.ts:431)
           │
           └─ applyMessages (sync/index.ts:257)
                  │
                  └─ apply (sync/index.ts:76)
                         │
                         └─ db.runQuery
                                │
                                └─ [失敗] throw SyncError('invalid-schema')

    ▼ エラーキャッチ

_sendMessages: catch (e) {
    if (e.reason === 'invalid-schema') {
        app.events.emit('sync', {type: 'error', subtype: 'apply-failure', ...})
    }
}
    │
    ▼

listenForSyncEvent (sync-events.ts:23)
    │
    └─ case 'apply-failure' (sync-events.ts:335)
           │
           └─ addNotification (type: 'error', message: 'We couldn\'t apply...')
```

### データフロー図

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

ユーザー操作             同期メッセージ生成                SyncError
(UI変更) ───▶ sendMessages ───▶ applyMessages ───▶ (invalid-schema)
                           │
                           ▼
                   _sendMessages catch
                           │
                           ▼
                   app.events.emit('sync-event', {subtype: 'apply-failure'})
                           │
                           ▼
                   sync-events.ts
                           │
                           ├─ console.trace (デバッグ用)
                           │
                           └─ addNotification ───▶ UI表示
                                                      │
                                                      ▼
                                              ユーザーがGitHubリンククリック
                                                      │
                                                      ▼
                                              GitHub issue作成ページ
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| sync-events.ts | `packages/desktop-client/src/sync-events.ts` | ソース | 同期イベントリスナー、通知発行 |
| index.ts | `packages/loot-core/src/server/sync/index.ts` | ソース | _sendMessages関数、applyMessages |
| errors.ts | `packages/loot-core/src/server/errors.ts` | ソース | SyncError型定義 |
| notificationsSlice.ts | `packages/desktop-client/src/notifications/notificationsSlice.ts` | ソース | 通知状態管理 |
| server-events.ts | `packages/loot-core/src/types/server-events.ts` | 型定義 | SyncSubtype定義 |
