# 通知設計書 15-CouldNotOpenUriDialog

## 概要

ハイパーリンクを開けなかった場合に表示されるエラーダイアログ（ContentDialog）の設計書である。URIが無効またはサポートされていないスキームであった場合にユーザーに通知する。

### 本通知の処理概要

CouldNotOpenUriDialogは、ターミナル出力内のハイパーリンクをユーザーがクリックした際に、URIの検証に失敗した場合やスキームがサポートされていない場合に表示されるエラーダイアログである。

**業務上の目的・背景**：ターミナルはOSC 8エスケープシーケンスなどによりハイパーリンクを表示でき、ユーザーがクリックすることでブラウザや関連アプリケーションを開くことができる。しかし、不正なURIや悪意のあるリンクを開くことはセキュリティリスクとなるため、サポートされていないスキームや無効なURIに対してはエラーダイアログを表示し、ユーザーに問題を通知する。

**通知の送信タイミング**：`_OpenHyperlinkHandler`メソッド内で、URIのパースに失敗した場合（InvalidUriText）、またはURIスキームがサポートされていない場合（UnsupportedSchemeText）に表示される。

**通知の受信者**：アプリケーションを操作中のユーザー本人。モーダルダイアログとして表示される。

**通知内容の概要**：エラーの理由（「This link is invalid:」または「This link type is currently not supported:」）と、問題のURIが表示される。URIは等幅フォント（Cascadia Mono）で表示され、テキスト選択が可能。

**期待されるアクション**：ユーザーは問題のURIを確認し、「Cancel」ボタンを押してダイアログを閉じる。必要に応じて手動でURIをコピーし、ブラウザのアドレスバーに貼り付けるなどの代替手段を取ることが想定される。

## 通知種別

アプリ内通知（モーダルダイアログ / ContentDialog）

## 送信仕様

### 基本情報

| 項目 | 内容 |
|-----|------|
| 送信方式 | 同期（モーダル） |
| 優先度 | 中 |
| リトライ | 無し |

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

ハイパーリンクをクリックしたユーザーに対して直接表示される。送信先の明示的な決定ロジックは存在しない。

## 通知テンプレート

### ダイアログ構成

| 項目 | 内容 |
|-----|------|
| タイトル | （x:Uidで定義されるが、Resources.reswにTitleの定義なし。CouldNotOpenUriDialogのタイトルはデフォルト） |
| 内容 | 理由テキスト + URI |
| プライマリボタン | Cancel |
| デフォルトボタン | Primary |

### エラー理由

| 理由キー | テキスト | 発生条件 |
|---------|---------|---------|
| InvalidUriText | This link is invalid: | URIのパースに失敗した場合 |
| UnsupportedSchemeText | This link type is currently not supported: | サポートされていないスキームの場合 |

### 本文テンプレート

```
Content:
  {理由テキスト}
  {URI（Cascadia Monoフォント）}
[Cancel]
```

### 添付ファイル

該当なし。

## テンプレート変数

| 変数名 | 説明 | データ取得元 | 必須 |
|--------|------|-------------|-----|
| CouldNotOpenUriReason | エラー理由テキスト | Resources.reswのInvalidUriTextまたはUnsupportedSchemeText | Yes |
| UnopenedUri | 開けなかったURI | OpenHyperlinkEventArgs.Uri() | Yes |

## 送信トリガー・条件

### トリガー一覧

| トリガー種別 | トリガーイベント | 送信条件 | 説明 |
|------------|----------------|---------|------|
| 画面操作 | ハイパーリンククリック | URIが無効（パース失敗） | 例外がキャッチされた場合 |
| 画面操作 | ハイパーリンククリック | スキームが非サポート | `_IsUriSupported`がfalseを返す場合 |
| 画面操作 | ハイパーリンククリック | ShellExecute失敗 | URIを開く際に例外が発生した場合 |

### 送信抑止条件

| 条件 | 説明 |
|-----|------|
| ダイアログプレゼンターが未設定 | `_dialogPresenter.get()`がnullptrの場合 |

## 処理フロー

### 送信フロー

```mermaid
flowchart TD
    A[ユーザーがハイパーリンクをクリック] --> B[OpenHyperlinkイベント発火]
    B --> C[_OpenHyperlinkHandler]
    C --> D{URIパース成功?}
    D -->|No| E[_ShowCouldNotOpenDialog - InvalidUriText]
    D -->|Yes| F{スキームサポート?}
    F -->|No| G[_ShowCouldNotOpenDialog - UnsupportedSchemeText]
    F -->|Yes| H[ShellExecuteExWで開く]
    H --> I{成功?}
    I -->|No| J[_ShowCouldNotOpenDialog - InvalidUriText]
    I -->|Yes| K[終了]
    E --> K
    G --> K
    J --> K
```

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

### 参照テーブル一覧

該当なし。

### 更新テーブル一覧

該当なし。

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 対処方法 |
|----------|---------|---------|
| URIパースエラー | 不正なURI文字列 | `InvalidUriText`で通知 |
| 非サポートスキーム | http/https/file以外で特殊ケースに該当しない場合 | `UnsupportedSchemeText`で通知（ただしGH#7562の議論により実際には多くのスキームがサポートされる） |
| ShellExecute失敗 | URIを開くアプリケーションが見つからない等 | 例外キャッチ後に`InvalidUriText`で通知 |

### リトライ仕様

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

## 配信設定

### レート制限

特に制限なし。

### 配信時間帯

制限なし。

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

- 本ダイアログはURIの検証に失敗した場合に表示されるため、悪意のあるURIのオープンを防ぐセキュリティ機能の一部として機能する。
- `_IsUriSupported`メソッドでhttp/https/fileスキームのみを明示的に許可している（ただしGH#7562の議論により、その他のスキームもデフォルトで許可される）。
- fileスキームでは、ホスト名がlocalhost、空、wsl$、wsl.localhostの場合のみ許可される。
- URIテキストは選択可能（IsTextSelectionEnabled="True"）であり、コンテキストメニューからコピーできる。

## 備考

- `x:Load="False"`により遅延読み込みが適用されている。
- URIのフォントは`Cascadia Mono`に設定されており、視認性を高めている。
- CouldNotOpenUriReasonとUnopenedUriはRun要素で表示され、LineBreakで区切られている。
- `_IsUriSupported`メソッドは実際にはほとんどのスキームをサポートするが、fileスキームのホスト名制限は厳密に適用される。

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | TerminalPage.xaml | `src/cascadia/TerminalApp/TerminalPage.xaml` | 行147-161: CouldNotOpenUriDialogのUI定義。Run要素（CouldNotOpenUriReason, UnopenedUri）の構成 |
| 1-2 | Resources.resw | `src/cascadia/TerminalApp/Resources/en-US/Resources.resw` | 行666-673: InvalidUriText、UnsupportedSchemeText、PrimaryButtonTextの文字列リソース |

#### Step 2: エントリーポイントを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | TerminalPage.cpp | `src/cascadia/TerminalApp/TerminalPage.cpp` | 行3055-3074: `_OpenHyperlinkHandler`メソッド。URIパース、スキーム検証、ShellExecute呼び出しのフロー |
| 2-2 | TerminalPage.cpp | `src/cascadia/TerminalApp/TerminalPage.cpp` | 行3103-3138: `_IsUriSupported`メソッド。スキーム別の検証ロジック |

**主要処理フロー**:
1. **行3059**: `winrt::Windows::Foundation::Uri(eventArgs.Uri())`でURIパース
2. **行3061-3067**: `_IsUriSupported`で検証、非サポート時に`UnsupportedSchemeText`で通知
3. **行3068-3073**: ShellExecuteExW実行、失敗時に`InvalidUriText`で通知

#### Step 3: ダイアログ表示処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | TerminalPage.cpp | `src/cascadia/TerminalApp/TerminalPage.cpp` | 行3081-3095: `_ShowCouldNotOpenDialog`メソッド |

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

```
TermControl::OpenHyperlink.raise()
    |
    +-- TerminalPage::_OpenHyperlinkHandler (TerminalPage.cpp:3055)
            |
            +-- Uri パース (行3059)
            |   |-- 失敗 -> _ShowCouldNotOpenDialog(InvalidUriText, uri)
            |
            +-- _IsUriSupported (行3103)
            |   |-- false -> _ShowCouldNotOpenDialog(UnsupportedSchemeText, uri)
            |
            +-- ShellExecuteExW
                |-- 失敗 -> _ShowCouldNotOpenDialog(InvalidUriText, uri)
                |
                +-- _ShowCouldNotOpenDialog (行3081)
                        |
                        +-- FindName(L"CouldNotOpenUriDialog")
                        +-- CouldNotOpenUriReason().Text(reason)
                        +-- UnopenedUri().Text(uri)
                        +-- presenter.ShowDialog()
```

### データフロー図

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

ハイパーリンク           TerminalPage.cpp
クリック        ------>  _OpenHyperlinkHandler
                             |
OpenHyperlinkEventArgs       |
  .Uri()                URIパース・検証               CouldNotOpenUriDialog
                             |               ------>  モーダル表示
                        _IsUriSupported                (理由 + URI)
                             |
                        ShellExecuteExW
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| TerminalPage.xaml | `src/cascadia/TerminalApp/TerminalPage.xaml` | XAML定義 | CouldNotOpenUriDialogのUI定義（行147-161） |
| TerminalPage.cpp | `src/cascadia/TerminalApp/TerminalPage.cpp` | ソース | ハイパーリンク処理・ダイアログ表示 |
| TerminalPage.h | `src/cascadia/TerminalApp/TerminalPage.h` | ヘッダー | メソッド宣言（行428） |
| Resources.resw | `src/cascadia/TerminalApp/Resources/en-US/Resources.resw` | リソース | 文字列リソース定義（行666-673） |
| ControlCore.idl | `src/cascadia/TerminalControl/ControlCore.idl` | IDL定義 | OpenHyperlinkイベントの定義 |
