# 画面設計書 10-プロファイラー設定

## 概要

本ドキュメントは、Symfony WebProfilerBundleが提供する「プロファイラー設定」画面の設計書である。本画面はプロファイラーのテーマ設定（ライト/ダーク/システム）とページ幅設定（固定幅/ウィンドウフィット）を行うモーダルダイアログである。

### 本画面の処理概要

本画面は、プロファイラーの外観設定をユーザーが変更できるモーダルダイアログである。設定値はブラウザのlocalStorageに保存され、サーバーサイドとの通信なしにクライアントサイドで完結する。

**業務上の目的・背景**：開発者がプロファイラーの表示テーマ（ライトモード/ダークモード/OS設定追従）やページ幅（固定幅/ウィンドウ幅に合わせる）を好みに応じてカスタマイズできるようにすることが目的である。これにより、長時間のデバッグ作業において、開発者の視認性や快適性を向上させる。設定はブラウザのlocalStorageに永続化されるため、ブラウザを閉じても設定が維持される。

**画面へのアクセス方法**：プロファイラーパネル（No.2）のサイドバー下部に表示される「Profiler settings」リンクをクリックすることでモーダルが開く。プロファイラーのレイアウトテンプレート（layout.html.twig）にインクルードされているため、全てのプロファイラーパネル画面からアクセス可能である。

**主要な操作・処理内容**：
1. サイドバーの「Profiler settings」リンククリックでモーダルダイアログを開く
2. テーマ設定：System / OS（自動）、Light、Darkの3択から選択
3. ページ幅設定：Fixed width、Fit to windowの2択から選択
4. 選択変更時にlocalStorageに設定値を保存
5. 選択変更時にdocument.bodyのCSSクラスを即時更新（ページリロード不要）
6. テーマがautoの場合はmatchMedia('(prefers-color-scheme: dark)')でOS設定を検出
7. モーダルは背景クリック、閉じるボタン、Escキーで閉じることが可能

**画面遷移**：
- 遷移元：プロファイラーパネル（No.2）のサイドバー「Profiler settings」リンク
- 遷移先：なし（モーダルダイアログ、画面遷移は発生しない）

**権限による表示制御**：プロファイラーが表示される全ての画面で利用可能。特別な権限制御はない。

## 関連機能

| 機能No | 機能名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 63 | WebProfilerBundle | 主機能 | プロファイラーレイアウト内にインクルードされるテーマ設定モーダル画面 |
| 35 | TwigBundle | 補助機能 | Twig Environmentによるsettings.html.twigテンプレートのレンダリング |

## 画面種別

モーダルダイアログ（設定）

## URL/ルーティング

| 項目 | 値 |
|------|-----|
| ルート名 | なし（独立したルートを持たない） |
| URLパターン | なし（layout.html.twigにインクルードされる部品） |
| HTTPメソッド | なし |
| コントローラー | なし（テンプレートインクルード） |

## 入出力項目

### 入力（ユーザー操作）

| 項目名 | 入出力 | 型 | 説明 |
|--------|--------|-----|------|
| theme | 入力 | radio | テーマ設定（auto/light/dark） |
| width | 入力 | radio | ページ幅設定（normal/full） |

### localStorage保存キー

| キー名 | 保存値 | デフォルト値 |
|--------|--------|-------------|
| `symfony/profiler/theme` | `theme-auto` / `theme-light` / `theme-dark` | `theme-auto` |
| `symfony/profiler/width` | `width-normal` / `width-full` | `width-normal` |

## 表示項目

### モーダルダイアログ構成

| 領域 | 表示内容 |
|------|---------|
| モーダルヘッダー | "Configuration Settings"タイトル、閉じるボタン |
| テーマ設定 | "Theme"見出し、3つのラジオボタン（System / OS, Light, Dark）各々にSVGアイコン付き |
| ページ幅設定 | "Page Width"見出し、2つのラジオボタン（Fixed width, Fit to window）各々にSVGアイコン付き |

### テーマ設定オプション

| ID | ラベル | value | 説明 |
|----|--------|-------|------|
| settings-theme-auto | System / OS | auto | OS設定に追従（prefers-color-schemeメディアクエリ） |
| settings-theme-light | Light | light | ライトテーマ固定 |
| settings-theme-dark | Dark | dark | ダークテーマ固定 |

### ページ幅設定オプション

| ID | ラベル | value | 説明 |
|----|--------|-------|------|
| settings-width-normal | Fixed width | normal | 固定幅レイアウト |
| settings-width-full | Fit to window | full | ウィンドウ幅フィットレイアウト |

## イベント仕様

### 1-モーダル開閉

**開く**: サイドバーの「Profiler settings」リンク（id=`open-settings`）クリック時
1. localStorageから現在の設定値を読み込み
2. 対応するラジオボタンにcheckedを設定
3. モーダル要素（id=`profiler-settings`）に`visible`クラスを追加
4. 閉じるボタンにフォーカスを移動（30ms後）

**閉じる**: 以下のいずれかの操作時
1. 閉じるボタン（class=`close-modal`）クリック
2. モーダル背景（class=`modal-wrap`）クリック
3. Escキー押下
4. モーダル要素から`visible`クラスを削除
5. 「Profiler settings」リンクにフォーカスを戻す（30ms後）

### 2-テーマ変更

ラジオボタンの`change`イベント発火時：
1. 選択されたオプション名（theme）と値（auto/light/dark）を取得
2. `localStorage.setItem('symfony/profiler/theme', 'theme-{value}')`で保存
3. document.bodyから既存のtheme-*クラスを全て削除
4. `theme-auto`の場合は`matchMedia('(prefers-color-scheme: dark)')`で判定し、`theme-dark`または`theme-light`を適用
5. それ以外は`theme-{value}`クラスを追加
6. `document.body.style.colorScheme`を`light`または`dark`に設定

### 3-ページ幅変更

ラジオボタンの`change`イベント発火時：
1. 選択されたオプション名（width）と値（normal/full）を取得
2. `localStorage.setItem('symfony/profiler/width', 'width-{value}')`で保存
3. document.bodyから既存のwidth-*クラスを全て削除
4. `width-{value}`クラスを追加

## データベース更新仕様

### 操作別データベース影響一覧

| 操作（イベント） | 対象テーブル | 操作種別 | 概要 |
|----------------|-------------|---------|------|
| 設定変更 | なし | なし | localStorage（クライアントサイド）のみ。サーバーサイドのデータ操作なし |

## メッセージ仕様

本画面にはメッセージ表示はない。

## 例外処理

本画面はクライアントサイドJavaScriptで動作するため、サーバーサイドの例外処理は発生しない。

## 備考

- 本画面は独立したルートやコントローラーアクションを持たず、layout.html.twigの63行目で`{{ include('@WebProfiler/Profiler/settings.html.twig') }}`としてインクルードされる
- テーマ適用の初期化処理はbase.html.twigの27-44行目のscriptタグで行われ、ページ読み込み時にlocalStorageの値に基づいてbodyクラスが設定される
- `theme-auto`選択時にOS設定が変更された場合、base.html.twigのmatchMediaイベントリスナー（31-34行目）により動的にテーマが切り替わる
- CSSカスタムプロパティ（CSS変数）を使用してテーマのカラースキームを管理しており、`:root`と`.theme-dark`で色の定義が切り替わる
- モーダルの表示/非表示はCSSの`opacity`と`visibility`のトランジション（0.3秒、ease-in-out）でアニメーションされる
- モーダルの背景にはbackdrop-filter: blur(2px)が適用される
- レスポンシブ対応：768px以下のブレークポイントでは「Profiler settings」リンクのテキストが非表示（color: transparent）になり、サイドバーのhover/expanded時のみ表示される
- フォーカス管理：モーダル表示時は閉じるボタンにフォーカス、閉じた時は設定リンクにフォーカスが戻る（アクセシビリティ対応）
- ラジオボタンの`input`要素はCSSで視覚的に非表示（`clip: rect(0,0,0,0)`, `opacity: 0`）にされ、カスタムUIが表示される
- フォーカス表示はCSS `:has(input:focus-visible)`で対応

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | settings.html.twig | `src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/settings.html.twig` | localStorageのキー名（`symfony/profiler/theme`, `symfony/profiler/width`）と値のフォーマット（`{option}-{value}`）を確認 |

**読解のコツ**: 本画面は完全にクライアントサイドで動作する。CSS変数、localStorage、document.bodyのCSSクラス操作が3つの柱である。

#### Step 2: テンプレート構造を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | settings.html.twig | `src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/settings.html.twig` | テンプレートは3つのセクションで構成される。CSS（1-191行目）、HTML（193-255行目）、JavaScript（257-316行目） |

**主要処理フロー（CSS - 1-191行目）**:
- **1-27行目**: CSS変数定義（`:root`と`.theme-dark`でテーマ切り替え）
- **29-41行目**: 設定リンクのスタイル
- **43-68行目**: モーダルラッパーのスタイル（固定位置、背景オーバーレイ、トランジション）
- **69-76行目**: モーダルコンテナのスタイル（最大幅600px）
- **129-146行目**: ラジオボタンのカスタムスタイル（checked状態の視覚効果）
- **183-190行目**: レスポンシブ対応（768px以下）

**主要処理フロー（HTML - 193-255行目）**:
- **193-196行目**: 設定リンク（SVGアイコン付き）
- **198-255行目**: モーダルダイアログ
  - **200-203行目**: モーダルヘッダー（タイトルと閉じるボタン）
  - **208-232行目**: テーマ設定（3つのラジオボタン）
  - **236-252行目**: ページ幅設定（2つのラジオボタン）

**主要処理フロー（JavaScript - 257-316行目）**:
- **259-284行目**: ラジオボタンのchangeイベントハンドラー（localStorage保存、bodyクラス更新）
- **286-302行目**: モーダル開閉のイベントハンドラー（click）
- **304-308行目**: 背景クリックでモーダルを閉じる
- **310-313行目**: Escキーでモーダルを閉じる

#### Step 3: テーマ初期化処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | base.html.twig | `src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/base.html.twig` | ページ読み込み時のテーマ初期化スクリプト（27-44行目）。localStorageの値に基づいてbodyクラスを設定し、OS設定変更時のmatchMediaリスナーを登録する処理を確認 |

**主要処理フロー**:
- **28-29行目**: localStorageにテーマ設定がないかautoの場合、OS設定に基づいてtheme-light/theme-darkを適用
- **31-34行目**: OS設定変更のmatchMediaイベントリスナー（autoモード時に動的対応）
- **36-37行目**: localStorageのテーマ設定値でbodyクラスを設定
- **39行目**: localStorageのページ幅設定値（またはデフォルトのwidth-normal）でbodyクラスを設定

#### Step 4: インクルード元を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | layout.html.twig | `src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/layout.html.twig` | 63行目で`{{ include('@WebProfiler/Profiler/settings.html.twig') }}`としてサイドバーにインクルードされている |

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

```
layout.html.twig
    |
    +-- {{ include('settings.html.twig') }}
            |
            +-- [CSS] テーマ変数定義、モーダルスタイル
            |
            +-- [HTML] モーダルダイアログ構造
            |       +-- テーマ設定ラジオボタン (auto/light/dark)
            |       +-- ページ幅設定ラジオボタン (normal/full)
            |
            +-- [JavaScript]
                    +-- configOption.change → localStorage.setItem()
                    |                       → document.body.classList更新
                    +-- open-settings.click → modal.visible
                    +-- close-modal.click   → modal非表示
                    +-- Esc keydown         → modal非表示

base.html.twig [初期化]
    +-- localStorage.getItem('symfony/profiler/theme')
    +-- localStorage.getItem('symfony/profiler/width')
    +-- document.body.classList.add()
    +-- matchMedia('(prefers-color-scheme: dark)').addEventListener()
```

### データフロー図

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

ユーザー操作           --> JavaScript                  --> 画面表示
(ラジオボタン選択)         イベントハンドラー
    |                         |
    +-- theme:              +-- localStorage.setItem()  --> bodyクラス更新
        auto/light/dark     +-- body.classList更新       --> テーマ即時反映
    |                       +-- body.style.colorScheme
    +-- width:
        normal/full         +-- localStorage.setItem()  --> bodyクラス更新
                            +-- body.classList更新       --> レイアウト即時反映

ページ読み込み         --> base.html.twig              --> 初期テーマ適用
                           初期化スクリプト
                               |
                           +-- localStorage.getItem()
                           +-- matchMedia() [autoモード時]
                           +-- body.classList.add()
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| settings.html.twig | `src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/settings.html.twig` | テンプレート | 設定モーダルのCSS、HTML、JavaScript全体 |
| layout.html.twig | `src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/layout.html.twig` | テンプレート | 設定モーダルのインクルード元（63行目） |
| base.html.twig | `src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/base.html.twig` | テンプレート | テーマ初期化スクリプト（27-44行目） |
| profiler.css.twig | `src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/profiler.css.twig` | テンプレート | テーマのCSS変数定義元 |
