# 通知設計書 35-新バージョン利用可能

## 概要

本ドキュメントは、Actual Budget アプリケーションにおける「新バージョン利用可能」通知の設計仕様を記述する。この通知は、新しいバージョンのアプリがリリースされたことをユーザーに知らせ、変更ログの確認を促すものである。

### 本通知の処理概要

**業務上の目的・背景**：新しいバージョンがリリースされた際に、ユーザーに最新機能やバグ修正の情報を提供する。これにより、ユーザーは新機能の存在を認識し、必要に応じて更新を検討できる。この通知は変更ログへのリンクを提供し、ユーザーが更新内容を確認した上で更新の判断ができるようにする。

**通知の送信タイミング**：アプリ起動時に、グローバル設定で `notifyWhenUpdateIsAvailable` が有効な場合、GitHub API を通じて最新バージョンを確認し、現在のバージョンより新しい場合に表示される。ただし、既に同じバージョンの通知を閉じた場合は再表示されない。

**通知の受信者**：更新通知を有効にしているユーザー。設定で通知を無効化している場合は表示されない。

**通知内容の概要**：新しいバージョンが利用可能であることを示すメッセージと「Open changelog」ボタンが表示される。この通知は sticky として設定されるが、閉じると同じバージョンでは再表示されない。

**期待されるアクション**：ユーザーは「Open changelog」ボタンを押して変更ログを確認し、更新の必要性を判断する。通知を閉じることで、そのバージョンに対する通知は以降表示されなくなる。

## 通知種別

アプリ内通知（トースト通知 - メッセージタイプ）

## 送信仕様

### 基本情報

| 項目 | 内容 |
|-----|------|
| 送信方式 | 非同期（バージョンチェック後） |
| 優先度 | 低 |
| リトライ | 無 |

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

- `globalPrefs.notifyWhenUpdateIsAvailable` が `true` の場合のみ表示
- `versionInfo.isOutdated` が `true` の場合に表示
- `lastUsedVersion !== versionInfo.latestVersion` の場合のみ表示（閉じた後は再表示しない）

## 通知テンプレート

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

| 項目 | 内容 |
|-----|------|
| 通知タイプ | `message` |
| 通知ID | `update-notification` |
| 表示位置 | 画面右下（固定位置） |
| sticky | `true`（自動消去されない） |

### 本文テンプレート

通常版:
```
Title: A new version of Actual is available!
Message: Version {latestVersion} of Actual was recently released.
[Open changelog ボタン]
```

Pikapods版:
```
Title: A new version of Actual is available!
Message: A new version of Actual is available! Your Pikapods instance will be automatically updated in the next few days - no action needed.
[Open changelog ボタン]
```

### 添付ファイル

該当なし

## テンプレート変数

| 変数名 | 説明 | データ取得元 | 必須 |
|--------|------|-------------|-----|
| type | 通知タイプ | 固定値 `message` | Yes |
| id | 通知ID | 固定値 `update-notification` | Yes |
| title | 通知タイトル | 固定値（i18n対応） | Yes |
| message | 通知メッセージ | バージョン情報を含む（i18n対応） | Yes |
| latestVersion | 最新バージョン | `versionInfo.latestVersion` | Yes（メッセージ内） |
| sticky | 自動消去無効フラグ | 固定値 `true` | Yes |
| button.title | ボタンラベル | 固定値 `Open changelog` | Yes |
| button.action | ボタンアクション | 変更ログURL を開く | Yes |
| onClose | 閉じた時のコールバック | `setLastUsedVersion` | Yes |

## 送信トリガー・条件

### トリガー一覧

| トリガー種別 | トリガーイベント | 送信条件 | 説明 |
|------------|----------------|---------|------|
| アプリ起動 | versionInfo 取得完了 | 更新通知有効 & バージョンが古い & 未表示 | FinancesApp.tsx で検知 |

### 送信抑止条件

| 条件 | 説明 |
|-----|------|
| `notifyWhenUpdateIsAvailable === false` | 設定で無効化されている |
| `versionInfo.isOutdated === false` | 最新バージョンを使用中 |
| `lastUsedVersion === versionInfo.latestVersion` | 既にこのバージョンの通知を閉じた |

## 処理フロー

### 送信フロー

```mermaid
flowchart TD
    A[アプリ起動] --> B[getLatestAppVersion dispatch]
    B --> C{notifyWhenUpdateIsAvailable?}
    C -->|No| D[終了]
    C -->|Yes| E[GitHub API でバージョン確認]
    E --> F{isOutdated?}
    F -->|No| D
    F -->|Yes| G{lastUsedVersion === latestVersion?}
    G -->|Yes| D
    G -->|No| H[addNotification dispatch]
    H --> I[Notifications コンポーネントで表示]
    I --> J{ユーザーアクション}
    J -->|ボタン押下| K[変更ログを開く]
    J -->|閉じる| L[setLastUsedVersion 実行]
    K --> D
    L --> D
```

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

### 参照テーブル一覧

| テーブル名 | 用途 | 備考 |
|-----------|------|------|
| - | - | 通知表示自体ではDB参照なし |

### ローカルストレージ更新

通知を閉じた際に `lastUsedVersion` がローカルストレージ（prefs）に保存され、次回起動時に同じバージョンの通知が表示されないようにする。

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 対処方法 |
|----------|---------|---------|
| バージョン取得失敗 | GitHub API エラー | 通知は表示されない（エラー表示なし） |
| 変更ログURL エラー | ネットワークエラー | ブラウザ側のエラーハンドリングに委ねる |

### リトライ仕様

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

## 配信設定

### レート制限

| 項目 | 内容 |
|-----|------|
| 1分あたり上限 | 制限なし（同一ID重複防止あり） |
| 1日あたり上限 | 制限なし |

### 配信時間帯

制限なし（アプリ起動時に確認）

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

- バージョン情報は GitHub API から取得
- 変更ログURL は actualbudget.org ドメインの固定値
- 外部リンクは新しいウィンドウで開かれる

## 備考

- この通知は閉じると同じバージョンでは再表示されない（`onClose` コールバック）
- Pikapods 環境では異なるメッセージが表示される（自動更新される旨）
- No.34「アプリ更新準備完了」とは異なり、こちらはバージョン確認と変更ログ案内が目的

---

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

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

### 推奨読解順序

#### Step 1: バージョンチェック処理を理解する

最新バージョンの取得処理を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | appSlice.ts | `packages/desktop-client/src/app/appSlice.ts` | getLatestAppVersion async thunk（113-131行目） |
| 1-2 | versions.ts | `packages/desktop-client/src/util/versions.ts` | getLatestVersion, getIsOutdated 関数 |

**読解のコツ**: `getLatestVersion` が GitHub API をどのように呼び出しているかを確認する。

#### Step 2: 通知発行処理を理解する

バージョン確認後の通知発行処理を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | FinancesApp.tsx | `packages/desktop-client/src/components/FinancesApp.tsx` | useEffect でのバージョン通知処理（144-188行目） |

**主要処理フロー**:
1. **140-142行目**: `getLatestAppVersion` を dispatch
2. **144行目**: `useEffect` で `versionInfo` を監視
3. **145-148行目**: 表示条件のチェック
4. **150-178行目**: `addNotification` で通知を追加
5. **156-162行目**: Pikapods かどうかで分岐
6. **168-170行目**: 変更ログを開くボタンアクション
7. **172-174行目**: `onClose` で `setLastUsedVersion` を呼び出し

#### Step 3: 設定の取得を理解する

通知表示の設定がどこから取得されるかを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | FinancesApp.tsx | `packages/desktop-client/src/components/FinancesApp.tsx` | useGlobalPref, useLocalPref フック（96-101行目） |

**主要処理フロー**:
- **96-98行目**: `notifyWhenUpdateIsAvailable` をグローバル設定から取得
- **99-101行目**: `lastUsedVersion` をローカル設定から取得・設定

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

```
FinancesApp.tsx
    │
    ├─ useEffect(() => dispatch(getLatestAppVersion()), [])
    │      │
    │      └─ appSlice.ts: getLatestAppVersion
    │             │
    │             ├─ getLatestVersion() → GitHub API
    │             │
    │             └─ setAppState({ versionInfo: ... })
    │
    ├─ useEffect(..., [versionInfo, ...])
    │      │
    │      ├─ 条件チェック
    │      │      ├─ notifyWhenUpdateIsAvailable
    │      │      ├─ isOutdated
    │      │      └─ lastUsedVersion !== latestVersion
    │      │
    │      └─ addNotification dispatch
    │             └─ Redux store 更新
    │                    └─ Notifications.tsx 表示
    │
Notifications.tsx
    │
    ├─ ButtonWithLoading ('Open changelog')
    │      └─ onClick: window.open('https://actualbudget.org/docs/releases')
    │
    └─ onRemove → onClose()
           └─ setLastUsedVersion(latestVersion)
```

### データフロー図

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

GitHub API ─────────────────▶ getLatestVersion() ──────────────▶ versionInfo
                                    │
globalPrefs ────────────────▶ notifyWhenUpdateIsAvailable ────▶ 表示判定
                                    │
localPrefs ─────────────────▶ lastUsedVersion ────────────────▶ 表示判定
                                    │
                                    └─▶ addNotification dispatch
                                           │
                                           └─▶ 通知表示

'Open changelog' ボタン押下 ─▶ window.open() ──────────────────▶ ブラウザで開く

通知を閉じる ───────────────▶ setLastUsedVersion() ────────────▶ localPrefs 更新
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| FinancesApp.tsx | `packages/desktop-client/src/components/FinancesApp.tsx` | ソース | 新バージョン通知の発行 |
| appSlice.ts | `packages/desktop-client/src/app/appSlice.ts` | ソース | getLatestAppVersion async thunk |
| versions.ts | `packages/desktop-client/src/util/versions.ts` | ソース | バージョン比較ユーティリティ |
| notificationsSlice.ts | `packages/desktop-client/src/notifications/notificationsSlice.ts` | ソース | 通知の Redux slice 定義 |
| Notifications.tsx | `packages/desktop-client/src/components/Notifications.tsx` | ソース | 通知表示コンポーネント |
| prefsSlice.ts | `packages/desktop-client/src/prefs/prefsSlice.ts` | ソース | 設定の管理 |
