# 通知設計書 47-HoveredHyperlinkChanged

## 概要

本ドキュメントは、Windows Terminal における HoveredHyperlinkChanged イベントの設計仕様を記述する。このイベントは、マウスカーソルがホバーしているハイパーリンクが変更された際に発火し、ツールチップの更新やハイパーリンクのハイライト表示を制御する。

### 本通知の処理概要

HoveredHyperlinkChangedイベントは、ターミナルビューポート内でマウスカーソルが指しているハイパーリンクの状態変更をUI層に通知するためのイベントである。ControlCoreの_updateHoveredCellメソッド内で、ホバー中のセルのハイパーリンクIDまたはハイパーリンク範囲（interval）が変化した場合にイベントが発火される。レンダラーのハイパーリンクハイライトが更新され、TermControlはツールチップの表示・非表示やIDN homoglyph攻撃対策のPunycode表示を行う。

**業務上の目的・背景**：ターミナル内に表示されるハイパーリンク（URL等）は、マウスホバー時にユーザーにフィードバックを与える必要がある。リンクのハイライト表示やツールチップによるURL表示により、ユーザーがクリックする前にリンク先を確認できるようにする。また、IDN homoglyph攻撃への対策として、Unicode URIのPunycode表記を並記する機能も含む。

**通知の送信タイミング**：マウスカーソルの移動により、ホバーしているセルのハイパーリンクID（`_lastHoveredId`）またはハイパーリンクの範囲（`_lastHoveredInterval`）が変化した時に発火する。リンク上に移動した時とリンクから離れた時の両方で発火する。

**通知の受信者**：TermControlの`_hoveredHyperlinkChanged`ハンドラがControlCoreからのイベントを受信する。

**通知内容の概要**：イベント引数はnullptr（Object, Object型）。受信側はControlCore::HoveredUriText()とControlCore::HoveredCell()を呼び出して最新の状態を取得する。

**期待されるアクション**：TermControlはホバー中のURIテキストを取得し、ツールチップの表示・更新・非表示を制御する。IDN（国際化ドメイン名）の場合はPunycode表記を併記し、フィッシング対策とする。

## 通知種別

アプリ内通知（WinRT イベント）

## 送信仕様

### 基本情報

| 項目 | 内容 |
|-----|------|
| 送信方式 | 同期（UIスレッドから発火） |
| 優先度 | 低 |
| リトライ | 無し |

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

ControlCoreが_updateHoveredCell内でHoveredHyperlinkChangedイベントを発火し、TermControlがwinrt::auto_revokeで購読する。

## 通知テンプレート

### メール通知の場合

該当なし（アプリ内イベント通知のため）

### 本文テンプレート

```
該当なし（イベント引数なし。受信側がHoveredUriText()/HoveredCell()で情報を取得する）
```

### 添付ファイル

該当なし

## テンプレート変数

| 変数名 | 説明 | データ取得元 | 必須 |
|--------|------|-------------|-----|
| HoveredUriText | ホバー中のURI文字列 | ControlCore::HoveredUriText() / Terminal::GetHyperlinkAtViewportPosition() | No |
| HoveredCell | ホバー中のセル座標 | ControlCore::HoveredCell() / _lastHoveredCell | No |

## 送信トリガー・条件

### トリガー一覧

| トリガー種別 | トリガーイベント | 送信条件 | 説明 |
|------------|----------------|---------|------|
| マウス操作 | SetHoveredCell | ハイパーリンクIDまたはintervalが変化した場合 | マウスカーソル移動によるセル変更 |
| マウス操作 | ClearHoveredCell | ハイパーリンクIDまたはintervalが変化した場合 | マウスがターミナル領域から退出 |

### 送信抑止条件

| 条件 | 説明 |
|-----|------|
| セル位置が同一 | `terminalPosition == _lastHoveredCell` の場合はearly returnする |
| ハイパーリンクIDとintervalが同一 | `newId == _lastHoveredId && newInterval == _lastHoveredInterval` の場合はイベント発火しない |

## 処理フロー

### 送信フロー

```mermaid
flowchart TD
    A[マウスカーソル移動] --> B[ControlCore::SetHoveredCell / ClearHoveredCell]
    B --> C[_updateHoveredCell]
    C --> D{セル位置が同一?}
    D -->|Yes| E[早期リターン]
    D -->|No| F[_lastHoveredCell更新]
    F --> G[Terminal::GetHyperlinkIdAtViewportPosition]
    G --> H{ハイパーリンクID/intervalが変化?}
    H -->|No| I[イベント発火なし]
    H -->|Yes| J[ロック取得]
    J --> K[_lastHoveredId/_lastHoveredInterval更新]
    K --> L[Renderer::UpdateHyperlinkHoveredId]
    L --> M[Renderer::TriggerRedrawAll]
    M --> N[HoveredHyperlinkChanged.raise]
    N --> O[TermControl::_hoveredHyperlinkChanged]
    O --> P[ツールチップ表示/更新]
```

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

### 参照テーブル一覧

該当なし（データベースは使用しない）

### 更新テーブル一覧

該当なし（データベースは使用しない）

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 対処方法 |
|----------|---------|---------|
| URI変換例外 | HoveredUriText取得時やPunycode変換時の例外 | try-catchで例外を補足し処理続行 |

### リトライ仕様

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

## 配信設定

### レート制限

| 項目 | 内容 |
|-----|------|
| 1分あたり上限 | 制限なし（マウス移動ごとに発火可能だが、セル単位の変化検出により頻度を制限） |
| 1日あたり上限 | 制限なし |

### 配信時間帯

制限なし

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

- HoveredUriTextは最大1024文字にトランケートされる（`uri.resize(std::min<size_t>(1024u, uri.size()))` ）
- IDN homoglyph攻撃対策として、国際化ドメイン名を含むURIはPunycode表記が併記される
- TermControlの_hoveredHyperlinkChangedハンドラ内でWindows.Foundation.UriのAbsoluteCanonicalUri()を使用してPunycode変換を行う

## 備考

- ControlCore.idlではHoveredHyperlinkChangedは「UIスレッドから常に呼び出される」カテゴリに分類されている
- イベント引数はnullptrで、受信側がpullモデルで情報を取得する設計（デッドロック回避のため、ロック下でイベントを発火しない）
- GH#9618の対応として、readロックとwriteロックを分離して使用する

---

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

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

### 推奨読解順序

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

HoveredHyperlinkChangedは独自のEventArgsを持たない。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | ControlCore.h | `src/cascadia/TerminalControl/ControlCore.h` | 284行目: `til::typed_event<> HoveredHyperlinkChanged` - 引数なしイベント |
| 1-2 | ControlCore.h | `src/cascadia/TerminalControl/ControlCore.h` | 418-421行目: `_lastHoveredInterval`, `_lastHoveredCell`, `_lastHoveredId` フィールド |

**読解のコツ**: `_lastHoveredId` はuint16_tのハイパーリンクID、`_lastHoveredInterval` はinterval_tree::IntervalTreeのinterval型で、ハイパーリンクのテキスト範囲を表す。

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | ControlCore.cpp | `src/cascadia/TerminalControl/ControlCore.cpp` | 819-826行目: SetHoveredCell/ClearHoveredCell - _updateHoveredCellの呼び出し |

**主要処理フロー**:
1. **821行目**: `_updateHoveredCell(std::optional<til::point>{ pos })` でセル位置を設定
2. **825行目**: `_updateHoveredCell(std::nullopt)` でクリア

#### Step 3: 核心処理 _updateHoveredCell

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | ControlCore.cpp | `src/cascadia/TerminalControl/ControlCore.cpp` | 828-870行目: _updateHoveredCell - セル比較、ハイパーリンクID取得、interval取得、レンダラー更新、イベント発火 |

**主要処理フロー**:
- **830-833行目**: `terminalPosition == _lastHoveredCell` のearly return
- **838行目**: `_lastHoveredCell = terminalPosition` でセル位置更新
- **844-846行目**: readロック下でハイパーリンクIDとintervalを取得
- **851-852行目**: IDまたはintervalの変化を検出
- **859行目**: writeロック取得
- **861-865行目**: `_lastHoveredId`/`_lastHoveredInterval`更新、レンダラー更新、再描画トリガー
- **868行目**: ロック外で `HoveredHyperlinkChanged.raise(*this, nullptr)` - デッドロック回避のため

#### Step 4: TermControl側ハンドラ

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | TermControl.cpp | `src/cascadia/TerminalControl/TermControl.cpp` | 313行目: イベント購読 |
| 4-2 | TermControl.cpp | `src/cascadia/TerminalControl/TermControl.cpp` | 3443-3472行目: _hoveredHyperlinkChanged - URI取得、IDN homoglyph対策、ツールチップ表示 |

**主要処理フロー**:
- **3445-3448行目**: HoveredCellのnullチェック
- **3451-3455行目**: HoveredUriTextの取得と空チェック
- **3468-3472行目**: Punycode変換とIDN対策

#### Step 5: URI情報の取得

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | ControlCore.cpp | `src/cascadia/TerminalControl/ControlCore.cpp` | 878-888行目: HoveredUriText() - readロック下でGetHyperlinkAtViewportPositionを呼び出し、1024文字にトランケート |
| 5-2 | ControlCore.cpp | `src/cascadia/TerminalControl/ControlCore.cpp` | 890-893行目: HoveredCell() - _lastHoveredCellをIReferenceとして返す |

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

```
TermControl (マウスイベントハンドラ)
    |
    +-- ControlCore::SetHoveredCell(pos) / ClearHoveredCell()
            |
            +-- _updateHoveredCell(terminalPosition)
                    |
                    +-- [早期リターン] セル位置が同一
                    |
                    +-- Terminal::GetHyperlinkIdAtViewportPosition() [readロック]
                    +-- Terminal::GetHyperlinkIntervalFromViewportPosition() [readロック]
                    |
                    +-- [変化なし] ID/interval同一 -> リターン
                    |
                    +-- [writeロック]
                    |   +-- _lastHoveredId 更新
                    |   +-- _lastHoveredInterval 更新
                    |   +-- Renderer::UpdateHyperlinkHoveredId()
                    |   +-- Renderer::UpdateLastHoveredInterval()
                    |   +-- Renderer::TriggerRedrawAll()
                    |
                    +-- HoveredHyperlinkChanged.raise() [ロック外]
                            |
                            +-- TermControl::_hoveredHyperlinkChanged()
                                    |
                                    +-- HoveredCell() / HoveredUriText()
                                    +-- IDN homoglyph対策
                                    +-- ツールチップ表示
```

### データフロー図

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

マウス移動           ControlCore                     TermControl
(セル座標)      -->  _updateHoveredCell()             -->  ツールチップ表示/非表示
                     HyperlinkId取得                       (URI + Punycode)
                     Interval取得
                     レンダラーハイライト更新
                     HoveredHyperlinkChanged.raise()  -->  ハイパーリンクハイライト描画
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| ControlCore.idl | `src/cascadia/TerminalControl/ControlCore.idl` | IDL | HoveredHyperlinkChangedイベント宣言 |
| ControlCore.h | `src/cascadia/TerminalControl/ControlCore.h` | ソース | HoveredHyperlinkChangedイベント定義、_lastHoveredXxxフィールド |
| ControlCore.cpp | `src/cascadia/TerminalControl/ControlCore.cpp` | ソース | _updateHoveredCell/SetHoveredCell/ClearHoveredCell/HoveredUriText/HoveredCell |
| TermControl.cpp | `src/cascadia/TerminalControl/TermControl.cpp` | ソース | _hoveredHyperlinkChangedハンドラ |
| TermControl.h | `src/cascadia/TerminalControl/TermControl.h` | ソース | ハンドラ宣言 |
