# 通知設計書 16-アップデート必要

## 概要

本ドキュメントは、Actualアプリケーションにおける「アップデート必要」警告通知の設計仕様を定義する。この通知は、データベーススキーマのアップデートが必要な場合に表示され、アプリの更新を促す。

### 本通知の処理概要

この通知は、同期処理中にサーバーからの変更を適用しようとした際に、無効なスキーマエラー（invalid-schema）が発生した場合に発行される。これは通常、サーバーに新しいデータベース構造の変更が存在し、現在のアプリバージョンではそれをサポートしていないことを意味する。

**業務上の目的・背景**：Actualは継続的に開発されており、データベーススキーマが変更される場合がある。古いバージョンのアプリでは新しいスキーマの変更を適用できないため、同期が失敗する。この通知は、ユーザーにアプリの更新を促し、最新のデータベース構造をサポートするバージョンへのアップグレードを案内する目的がある。

**通知の送信タイミング**：同期処理中にサーバーから受信した変更をローカルデータベースに適用しようとした際に、スキーマが無効（テーブルやカラムが存在しないなど）でエラーが発生した場合に発行される。具体的には、sync/index.tsのapply関数内でSQLクエリが失敗し、SyncError('invalid-schema')がスローされた場合にトリガーされる。

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

**通知内容の概要**：サーバーからの変更を適用できなかったことを伝え、最新のデータベースをサポートするためにアプリの更新が必要であることを説明する。

**期待されるアクション**：ユーザーはアプリケーションを最新バージョンに更新することが期待される。デスクトップアプリの場合はアップデート機能を使用し、Webアプリの場合はページをリロードするか、管理者に更新を依頼する。

## 通知種別

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

## 送信仕様

### 基本情報

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

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

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

## 通知テンプレート

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

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

### 本文テンプレート

```
We couldn't apply changes from the server. This probably means you need to update the app to support the latest database.
```

### ボタン

ボタンなし

### 添付ファイル

該当なし

## テンプレート変数

| 変数名 | 説明 | データ取得元 | 必須 |
|--------|------|-------------|-----|
| 該当なし | 固定テキストのため変数なし | - | - |

## 送信トリガー・条件

### トリガー一覧

| トリガー種別 | トリガーイベント | 送信条件 | 説明 |
|------------|----------------|---------|------|
| イベント | sync-event (type: error, subtype: invalid-schema) | スキーマ不一致 | データベース変更を適用できない |

### 送信抑止条件

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

## 処理フロー

### 送信フロー

```mermaid
flowchart TD
    A[同期処理開始] --> B[サーバーからメッセージ受信]
    B --> C[メッセージを適用]
    C --> D{適用成功?}
    D -->|成功| E[次のメッセージへ]
    D -->|失敗| F{エラー種別?}
    F -->|invalid-schema| G[SyncError発生]
    F -->|その他| H[別のエラー処理]
    G --> I[sync-event エミット]
    I --> J[listenForSyncEvent がキャッチ]
    J --> K[console.trace でログ出力]
    K --> L[通知オブジェクト作成]
    L --> M[addNotification ディスパッチ]
    M --> N[UI に通知表示]
    N --> O[自動消去 6.5秒後]
```

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

### 参照テーブル一覧

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

### テーブル別参照項目詳細

このエラーは、存在しないテーブルやカラムに対してクエリを実行しようとした際に発生する。

| 参照項目 | 用途 | 取得条件 |
|---------|------|---------|
| msg.dataset | 適用先テーブル名 | 同期メッセージから |
| msg.column | 適用先カラム名 | 同期メッセージから |

### 更新テーブル一覧

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

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 対処方法 |
|----------|---------|---------|
| テーブル不在 | 新しいテーブルが追加された | アプリを更新 |
| カラム不在 | 新しいカラムが追加された | アプリを更新 |
| 型不一致 | カラムの型が変更された | アプリを更新 |

### リトライ仕様

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

## 配信設定

### レート制限

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

### 配信時間帯

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

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

- スキーマエラーの詳細情報はconsole.traceでログ出力される（開発者向け）
- ユーザーには一般的なメッセージのみ表示

## 備考

- この通知はstickyではないため、デフォルトの6.5秒後に自動消去される
- ボタンが提供されないため、ユーザーは手動でアプリを更新する必要がある
- エラー発生時にconsole.traceでデバッグ情報が出力される
- 同期処理はこのエラー後に中断される

---

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

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

### 推奨読解順序

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

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

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

**読解のコツ**: invalid-schemaはSyncSubtypeの一つとして定義されている。

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

同期処理でinvalid-schemaエラーが発生する箇所を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | index.ts | `packages/loot-core/src/server/sync/index.ts` | apply関数（76-104行目）、fetchAll関数（107-151行目） |

**主要処理フロー**:
1. **76行目**: apply関数の定義
2. **96-97行目**: db.runQueryでSQLを実行
3. **97-102行目**: catchブロックでSyncError('invalid-schema')をスロー
4. **136-147行目**: fetchAllでも同様のエラー処理

#### Step 3: イベント発火箇所を理解する

fullSyncでエラーがキャッチされてイベントが発火される箇所を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | index.ts | `packages/loot-core/src/server/sync/index.ts` | fullSync関数内のエラーハンドリング（574-579行目） |

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

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

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

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

**主要処理フロー**:
- **324行目**: case 'invalid-schema'
- **325行目**: console.trace('invalid-schema', event.meta)
- **326-333行目**: 通知オブジェクト作成（type: 'warning'）

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

```
fullSync (sync/index.ts:553)
    │
    └─ _fullSync (sync/index.ts:623)
           │
           └─ receiveMessages (sync/index.ts:423)
                  │
                  └─ applyMessages (sync/index.ts:257)
                         │
                         └─ apply (sync/index.ts:76)
                                │
                                └─ db.runQuery
                                       │
                                       └─ [失敗] throw SyncError('invalid-schema')

    ▼ エラーキャッチ

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

listenForSyncEvent (sync-events.ts:23)
    │
    └─ case 'invalid-schema' (sync-events.ts:324)
           │
           └─ addNotification (type: 'warning', title: 'Update required')
```

### データフロー図

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

サーバーからの             sync/index.ts                   SyncError
同期メッセージ ───▶ apply/fetchAll ───▶ [SQL失敗] ───▶ (invalid-schema)
                           │
                           ▼
                   fullSync catch
                           │
                           ▼
                   app.events.emit('sync-event')
                           │
                           ▼
                   sync-events.ts
                           │
                           ├─ console.trace (デバッグ用)
                           │
                           └─ addNotification ───▶ UI表示
                                                      │
                                                      ▼
                                              6.5秒後自動消去
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| sync-events.ts | `packages/desktop-client/src/sync-events.ts` | ソース | 同期イベントリスナー、通知発行 |
| index.ts | `packages/loot-core/src/server/sync/index.ts` | ソース | apply関数、fetchAll関数、fullSync |
| 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定義 |
| db/index.ts | `packages/loot-core/src/server/db/index.ts` | ソース | runQuery関数 |
