# 画面設計書 1-App（メイン画面）

## 概要

本ドキュメントは、SFC Playground アプリケーションのメイン画面（App.vue）の画面設計書である。Vue SFC（Single File Component）をリアルタイムで編集・コンパイル・実行できるオンラインプレイグラウンドの中核画面について、構成・機能・データフロー・イベント仕様を定義する。

### 本画面の処理概要

**業務上の目的・背景**：Vue.js のSFC（Single File Component）をブラウザ上でリアルタイムに編集・コンパイル・プレビューできる環境を提供する。開発者がローカル環境を構築せずにVueコンポーネントの動作確認やプロトタイピングを行えるようにし、学習・共有・デバッグを容易にすることが目的である。URLハッシュによる状態永続化により、作成したコードを他者と共有することも可能である。

**画面へのアクセス方法**：ブラウザで SFC Playground の URL（play.vuejs.org）にアクセスする。index.html がエントリーポイントとなり、main.ts を経由して App.vue がマウントされる。URLハッシュにエンコードされた状態がある場合は、その状態が復元される。

**主要な操作・処理内容**：
1. Replコンポーネントを介したSFCコードの編集・リアルタイムコンパイル・プレビュー表示
2. Headerコンポーネントを介したDEV/PROD モード切替、SSRモード切替、AutoSave切替、テーマ切替
3. Vueバージョン・TypeScriptバージョンの選択と動的切替
4. URLハッシュへの状態永続化（watchEffectによる自動同期）
5. プロジェクトファイルのZIPダウンロード
6. 共有用URLのクリップボードコピー
7. Ctrl+S / Cmd+S ブラウザ保存ショートカットの無効化

**画面遷移**：本画面は SFC Playground のルート画面であり、他画面への遷移はない。子コンポーネントとして Header と Repl を含む。Header内のGitHubリンクは外部サイトへの遷移となる。

**権限による表示制御**：権限による表示制御は存在しない。全機能が全ユーザーに開放されている。

## 関連機能

| 機能No | 機能名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 1 | ref() | 主機能 | replRef, useSSRMode, autoSave, theme の状態管理に使用 |
| 4 | computed() | 主機能 | sfcOptions（SFCコンパイルオプション）, previewOptions（プレビューカスタマイズ）の算出 |
| 5 | watch() / watchEffect() | 主機能 | watchEffectでURLハッシュ永続化、watchでVueバージョン変更時のページリロード制御 |
| 18 | onBeforeMount() / onMounted() | 補助機能 | DOM初期化、テーマクラス設定、リサイズイベントリスナー登録、globalThis.store設定 |
| 32 | v-model | 補助機能 | autoSave設定の双方向バインディング（:model-value経由） |
| 34 | v-on修飾子 | 補助機能 | @keydown.ctrl.s.prevent / @keydown.meta.s.prevent によるブラウザ保存ショートカットの無効化 |
| 54 | parse() | API連携 | @vue/replコンポーネントを介したSFCファイルの解析処理 |
| 55 | compileScript() | API連携 | @vue/replコンポーネントを介した`<script setup>`ブロックのコンパイル |
| 56 | compileTemplate() | API連携 | @vue/replコンポーネントを介した`<template>`ブロックのコンパイル |
| 57 | compileStyle() / compileStyleAsync() | API連携 | @vue/replコンポーネントを介した`<style>`ブロックのスコープCSS生成 |

## 画面種別

SPA メイン画面（エディタ＆プレビュー統合画面）

## URL/ルーティング

- **URL**: `/` （ルート）
- **ハッシュ**: `#{エンコード済み状態}` 形式でプロジェクト状態を保持
  - `#__DEV__{state}`: DEVモード
  - `#__PROD__{state}`: PRODモード
  - `#__SSR__{state}`: SSRモード有効
  - 複合例: `#__PROD____SSR__{state}`

## 入出力項目

| 項目名 | 入力/出力 | データ型 | 説明 | 初期値 |
|--------|----------|---------|------|--------|
| useSSRMode | 入力 | boolean (ref) | SSRモードの有効/無効 | false（URLハッシュに`__SSR__`がある場合はtrue） |
| autoSave | 入力 | boolean (ref) | 自動保存の有効/無効 | true（localStorageから復元） |
| productionMode | 入力 | boolean (ref) | DEV/PRODモード切替 | useVueImportMap内部で管理 |
| theme | 入力 | 'dark' \| 'light' (ref) | テーマ設定 | 'dark' |
| vueVersion | 入力 | string \| null (ref) | 使用するVueバージョン | null（現在のコミットバージョン） |
| sfcOptions | 出力 | SFCOptions (computed) | SFCコンパイルオプション | productionModeに基づく算出値 |
| previewOptions | 出力 | object (computed) | プレビューカスタマイズオプション | vaporInteropPlugin対応含む |
| store | 出力 | ReplStore | Replの状態管理ストア | useStore()による生成 |

## 表示項目

| 項目名 | 表示領域 | データソース | 説明 |
|--------|---------|-------------|------|
| ナビゲーションバー | 画面上部 | Header コンポーネント | ロゴ、バージョン選択、各種トグルボタン、アクションボタン |
| エディタ＆プレビュー | 画面下部（nav-height以下） | Repl コンポーネント | Monaco Editor によるコード編集領域とプレビュー領域 |

## イベント仕様

### 1-ページ読み込み時の初期化

1. index.html でダークテーマ判定（localStorage `vue-sfc-playground-prefer-dark` または `prefers-color-scheme: dark`）
2. main.ts で `createApp(App).mount('#app')` 実行
3. App.vue の `<script setup>` がトップレベルで実行:
   - `setVH()` でビューポート高さCSS変数設定、resizeリスナー登録（9-13行目）
   - `useVueImportMap()` でVueランタイムのインポートマップ構築（23-33行目）
   - URLハッシュからモード状態（DEV/PROD/SSR）を復元（35-47行目）
   - `useStore()` でReplストアを初期化（70-77行目）
   - `watchEffect` でURLハッシュ永続化を開始（82-88行目）
4. `onMounted` でテーマクラスの初期判定と `window.process` シムの設定（111-117行目）
5. `watch` でVueバージョン変更の監視を開始（120-130行目）

### 2-DEV/PROD モード切替

Header から `toggle-prod` イベントを受信し `toggleProdMode()` を実行（90-92行目）。`productionMode.value` を反転する。変更は `sfcOptions` computed に反映され、Repl が再コンパイルを実行する。watchEffect により URLハッシュも更新される。

### 3-SSRモード切替

Header から `toggle-ssr` イベントを受信し `toggleSSR()` を実行（94-96行目）。`useSSRMode.value` を反転する。Repl の `:ssr` と `:showSsrOutput` プロパティに反映される。

### 4-AutoSave切替

Header から `toggle-autosave` イベントを受信し `toggleAutoSave()` を実行（98-101行目）。`autoSave.value` を反転し、localStorage に保存する。Repl の `:model-value` プロパティに反映される。

### 5-テーマ切替

Header から `toggle-theme` イベントを受信し `toggleTheme(isDark)` を実行（108-110行目）。`theme.value` を更新し、Repl の `:theme` プロパティに反映される。

### 6-ページリロード

Header から `reload-page` イベントを受信し `reloadPage()` を実行（103-105行目）。`replRef.value?.reload()` により Repl のプレビューをリロードする。

### 7-Vueバージョン変更によるリロード

`watch` により `store.vueVersion` の変更を監視（120-130行目）。バージョン変更時に Vapor モードサポート判定を更新し、初回以外は `reloadPage()` を呼び出す。

### 8-キーボードショートカット無効化

Repl コンポーネントに `@keydown.ctrl.s.prevent` および `@keydown.meta.s.prevent` を設定（165-166行目）。ブラウザのファイル保存ダイアログの表示を抑制する。

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

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

本画面はクライアントサイドのみで動作するため、データベースへのアクセスは発生しない。

| 操作（イベント） | 対象ストレージ | 操作種別 | 概要 |
|----------------|--------------|---------|------|
| AutoSave切替 | localStorage | SET | `vue-sfc-playground-auto-save` キーに true/false を保存 |
| テーマ切替 | localStorage | SET | `vue-sfc-playground-prefer-dark` キーに true/false を保存（Header.vue内で実行） |
| 状態永続化 | URLハッシュ | SET | `history.replaceState` でURLハッシュにシリアライズ済み状態を保存 |

### テーブル別更新項目詳細

#### localStorage

| 操作 | キー名 | 更新値 | 備考 |
|-----|--------|-------|------|
| SET | `vue-sfc-playground-auto-save` | `"true"` / `"false"` | toggleAutoSave()実行時 |
| GET | `vue-sfc-playground-auto-save` | - | 初期化時の復元（18-20行目） |
| SET | `vue-sfc-playground-prefer-dark` | `"true"` / `"false"` | Header.vue内のtoggleDark()実行時 |
| GET | `vue-sfc-playground-prefer-dark` | - | index.html内の初期テーマ判定時 |

## メッセージ仕様

| メッセージ種別 | メッセージ内容 | 発生条件 | 表示方法 |
|--------------|--------------|---------|---------|
| 情報 | "Sharable URL has been copied to clipboard." | Shareボタンクリック時（Header.vue内） | alert() |
| 確認 | "Download project files?" | Downloadボタンクリック時（download.ts内） | confirm() |

## 例外処理

| 例外条件 | 処理内容 | 発生箇所 |
|---------|---------|---------|
| URLハッシュの不正なデータ | useStore内部でフォールバック処理 | App.vue 70-77行目 |
| Vueバージョンロード失敗 | store.loading が true のまま残る | Header.vue の vueVersion computed |
| window.process未定義 | onMounted でシム設定 `{ env: {} }` | App.vue 116行目 |

## 備考

- 本画面は `@vue/repl` パッケージに強く依存しており、エディタ機能・コンパイル機能・プレビュー機能の大部分は外部パッケージに委譲している
- `globalThis.store = store`（79行目）によりデバッグ用にストアをグローバルに公開している
- Vapor モードは Vue 3.6 以上（major > 3 || (major === 3 && minor >= 6)）で利用可能
- `isCustomElement` 設定により `mjx-container`（MathJax）と `custom-` プレフィックスのタグがカスタム要素として扱われる

---

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

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

### 推奨読解順序

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

まず、App.vue で管理されるリアクティブ状態と、外部パッケージ `@vue/repl` が提供する型定義を理解することが重要である。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | App.vue | `packages-private/sfc-playground/src/App.vue` | 7-21行目: ref による状態定義（replRef, useSSRMode, autoSave）を確認 |
| 1-2 | App.vue | `packages-private/sfc-playground/src/App.vue` | 23-33行目: useVueImportMap の戻り値（productionMode, vueVersion, importMap）を確認 |
| 1-3 | App.vue | `packages-private/sfc-playground/src/App.vue` | 50-68行目: sfcOptions computed の構造を確認 |

**読解のコツ**: `useVueImportMap` は `@vue/repl` パッケージが提供するcomposable関数であり、Vue ランタイムの CDN URL を管理する。戻り値の `productionMode`, `vueVersion` は ref オブジェクトである。

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

処理の起点は index.html から main.ts、そして App.vue への流れである。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | index.html | `packages-private/sfc-playground/index.html` | 10-19行目: ダークテーマの初期判定スクリプト |
| 2-2 | main.ts | `packages-private/sfc-playground/src/main.ts` | 1-9行目: createApp(App).mount('#app') によるアプリ起動 |

**主要処理フロー**:
1. **index.html 10-19行目**: localStorage とメディアクエリによるダークテーマ事前判定
2. **index.html 21行目**: main.ts の読み込み開始
3. **main.ts 5-7行目**: VUE_DEVTOOLS_CONFIG の設定
4. **main.ts 9行目**: createApp(App).mount('#app') でApp.vueをDOMにマウント

#### Step 3: App.vue のリアクティブシステムを理解する

App.vue 内の状態管理とリアクティブな依存関係を把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | App.vue | `packages-private/sfc-playground/src/App.vue` | 35-47行目: URLハッシュからのモード状態復元ロジック |
| 3-2 | App.vue | `packages-private/sfc-playground/src/App.vue` | 70-79行目: useStore() によるストア初期化とグローバル公開 |
| 3-3 | App.vue | `packages-private/sfc-playground/src/App.vue` | 82-88行目: watchEffect による状態の URLハッシュ永続化 |
| 3-4 | App.vue | `packages-private/sfc-playground/src/App.vue` | 120-130行目: watch による Vueバージョン変更監視 |

**主要処理フロー**:
- **35-47行目**: URLハッシュを解析し `__DEV__`/`__PROD__`/`__SSR__` プレフィックスでモード復元
- **82-88行目**: `store.serialize()` で状態をシリアライズし `history.replaceState` でURLに反映
- **120-130行目**: バージョン文字列をパースして Vapor サポート判定、oldVersion存在時にリロード

#### Step 4: テンプレートとコンポーネント連携を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | App.vue | `packages-private/sfc-playground/src/App.vue` | 148-178行目: テンプレート部分 - Header と Repl のプロパティバインディングとイベントハンドリング |
| 4-2 | Header.vue | `packages-private/sfc-playground/src/Header.vue` | 13-26行目: defineProps / defineEmits の定義 |

**主要処理フロー**:
- **149-160行目**: Header に store, prod, ssr, autoSave, theme をプロパティとして渡し、5種のカスタムイベントをハンドリング
- **161-177行目**: Repl に Monaco エディタ、テーマ、SSR設定、autoSave、各種表示オプションを渡す

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

```
index.html
    |
    +-- main.ts
    |       |
    |       +-- createApp(App)
    |               |
    |               +-- App.vue <script setup> (トップレベル実行)
    |                       |
    |                       +-- useVueImportMap() --> { productionMode, vueVersion, importMap }
    |                       +-- useStore(config, hash) --> store (ReplStore)
    |                       +-- watchEffect() --> URLハッシュ永続化
    |                       +-- watch(store.vueVersion) --> バージョン変更検知
    |                       +-- onMounted() --> テーマ初期化, process シム
    |                       |
    |                       +-- <template>
    |                               |
    |                               +-- Header.vue
    |                               |       +-- VersionSelect.vue (x2)
    |                               |       +-- downloadProject(store)
    |                               |       +-- copyLink()
    |                               |
    |                               +-- Repl (@vue/repl)
    |                                       +-- Monaco Editor
    |                                       +-- Preview iframe
```

### データフロー図

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

URLハッシュ ──────────────> ハッシュ解析 ──────────────────> productionMode / useSSRMode 復元
                           (35-47行目)

localStorage ─────────────> autoSave 復元 ─────────────────> autoSave ref
(auto-save キー)            (18-20行目)

useVueImportMap() ────────> importMap 生成 ────────────────> store 初期化
                           (23-33行目)                       (70-77行目)

productionMode ref ───────> sfcOptions computed ──────────> Repl コンポーネント
                           (50-68行目)                       (コンパイルオプション)

store.serialize() ────────> watchEffect ──────────────────> URLハッシュ更新
                           (82-88行目)                       (history.replaceState)

Header イベント ──────────> toggle関数群 ─────────────────> ref 更新 --> Repl 再コンパイル
                           (90-110行目)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| index.html | `packages-private/sfc-playground/index.html` | テンプレート | エントリーポイント HTML。ダークテーマ事前判定スクリプトを含む |
| main.ts | `packages-private/sfc-playground/src/main.ts` | ソース | Vue アプリケーション起動。createApp(App).mount('#app') |
| App.vue | `packages-private/sfc-playground/src/App.vue` | ソース | メイン画面コンポーネント。状態管理・URLハッシュ永続化の中核 |
| Header.vue | `packages-private/sfc-playground/src/Header.vue` | ソース | ナビゲーションバー。各種トグル・アクションボタン |
| VersionSelect.vue | `packages-private/sfc-playground/src/VersionSelect.vue` | ソース | バージョン選択ドロップダウンコンポーネント |
| download.ts | `packages-private/sfc-playground/src/download/download.ts` | ソース | プロジェクトZIPダウンロード機能 |
| vue-dev-proxy.ts | `packages-private/sfc-playground/src/vue-dev-proxy.ts` | ソース | 開発環境用Vueランタイムプロキシ |
| vue-dev-proxy-prod.ts | `packages-private/sfc-playground/src/vue-dev-proxy-prod.ts` | ソース | 本番用Vueランタイムプロキシ |
| vue-server-renderer-dev-proxy.ts | `packages-private/sfc-playground/src/vue-server-renderer-dev-proxy.ts` | ソース | 開発環境用サーバーレンダラープロキシ |
| Sun.vue | `packages-private/sfc-playground/src/icons/Sun.vue` | ソース | ライトテーマ用アイコン |
| Moon.vue | `packages-private/sfc-playground/src/icons/Moon.vue` | ソース | ダークテーマ用アイコン |
| Share.vue | `packages-private/sfc-playground/src/icons/Share.vue` | ソース | 共有アイコン |
| Download.vue | `packages-private/sfc-playground/src/icons/Download.vue` | ソース | ダウンロードアイコン |
| GitHub.vue | `packages-private/sfc-playground/src/icons/GitHub.vue` | ソース | GitHubアイコン |
| Reload.vue | `packages-private/sfc-playground/src/icons/Reload.vue` | ソース | リロードアイコン |
| Copy.vue | `packages-private/sfc-playground/src/icons/Copy.vue` | ソース | コピーアイコン |
