# 画面設計書 12-ワークスペースレイアウト

## 概要

本ドキュメントは、OpenCodeコンソールのワークスペースレイアウト画面の設計書である。認証済みユーザー向けの共通レイアウトコンポーネントとして、ヘッダー、ワークスペースピッカー、ユーザーメニューを提供する。

### 本画面の処理概要

ワークスペースレイアウトは、認証済みユーザーがワークスペース管理機能にアクセスする際の共通レイアウトを提供するシェルコンポーネントである。ヘッダー領域にブランドロゴ、ワークスペース切り替え機能、ユーザーアカウントメニューを配置し、子ルートのコンテンツを表示するためのコンテナを提供する。

**業務上の目的・背景**：OpenCodeでは複数のワークスペースを持つユーザーが存在し、それぞれのワークスペースでAPIキー、メンバー、請求情報などを独立して管理する必要がある。本レイアウトは、認証状態の維持とワークスペース間の切り替えを一貫したUIで提供することで、ユーザーの操作効率を向上させる。

**画面へのアクセス方法**：認証後に`/workspace`配下のルートにアクセスした際に自動的に適用される。直接アクセスする画面ではなく、子ルート（ワークスペースダッシュボード、APIキー管理等）をラップするレイアウトコンポーネントである。

**主要な操作・処理内容**：
1. ワークスペースピッカーによるワークスペース切り替え
2. ユーザーメニューからのログアウト、アカウント設定へのアクセス
3. ブランドロゴクリックによるトップページへの遷移
4. 子ルートコンテンツの表示

**画面遷移**：ブランドロゴクリックでトップページ（`/`）へ遷移。ワークスペースピッカーで選択したワークスペースの詳細画面へ遷移。ユーザーメニューからログアウトや設定画面へ遷移可能。

**権限による表示制御**：認証済みユーザーのみがアクセス可能。未認証の場合は認証画面へリダイレクトされる（withActor関数による制御）。

## 関連機能

| 機能No | 機能名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 28 | 認証管理 | 主機能 | 認証済みユーザー向けレイアウトの認証状態管理 |

## 画面種別

レイアウト（シェルコンポーネント）

## URL/ルーティング

| 環境 | URL |
|------|-----|
| 本番 | `https://opencode.ai/workspace/*` |
| 開発 | `http://localhost:3000/workspace/*` |

ルーティング定義: `packages/console/app/src/routes/workspace.tsx`

適用範囲: `/workspace`配下の全ルート

## 入出力項目

### 入力項目

| 項目名 | 項目ID | データ型 | 必須 | 取得方法 | 備考 |
|--------|--------|----------|------|----------|------|
| ワークスペースID | params.id | string | Yes | URLパラメータ | useParams()で取得 |

### 出力項目（サーバーサイドクエリ）

| 項目名 | データ型 | 取得元 | 備考 |
|--------|----------|--------|------|
| ユーザーメールアドレス | string | User.getAuthEmail() | getUserEmailクエリで取得 |

## 表示項目

### ヘッダーセクション

| 要素 | コンポーネント | 配置 | 説明 |
|------|---------------|------|------|
| ブランドロゴ | IconWorkspaceLogo | 左側（header-brand） | クリックでトップページへ遷移 |
| ワークスペースピッカー | WorkspacePicker | 左側（header-brand） | ワークスペース切り替えドロップダウン |
| ユーザーメニュー | UserMenu | 右側（header-actions） | ユーザーのメールアドレスとアカウントメニュー |

### コンテンツ領域

| 要素 | 説明 |
|------|------|
| props.children | 子ルートのコンテンツがレンダリングされる領域 |

## イベント仕様

### 1-ブランドロゴクリック

| 項目 | 内容 |
|------|------|
| トリガー | ブランドロゴ（IconWorkspaceLogo）クリック |
| 処理 | language.route("/")で言語対応のトップページURLを生成 |
| 結果 | トップページへ遷移 |

### 2-ワークスペース切り替え

| 項目 | 内容 |
|------|------|
| トリガー | WorkspacePickerでワークスペース選択 |
| 処理 | WorkspacePickerコンポーネント内部で処理 |
| 結果 | 選択したワークスペースの詳細画面へ遷移 |

### 3-ユーザーメニュー操作

| 項目 | 内容 |
|------|------|
| トリガー | UserMenuのクリック |
| 処理 | UserMenuコンポーネント内部で処理 |
| 結果 | ログアウト、設定画面遷移等のアクション |

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

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

| 操作（イベント） | 対象テーブル | 操作種別 | 概要 |
|----------------|-------------|---------|------|
| ページロード | UserTable | SELECT | ユーザーメールアドレスの取得 |

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

#### getUserEmailクエリ（サーバーサイド）

```typescript
const getUserEmail = query(async (workspaceID: string) => {
  return withActor(async () => {
    const actor = Actor.assert("user")
    const email = await User.getAuthEmail(actor.properties.userID)
    return email
  }, workspaceID)
}, "userEmail")
```

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | email | actor.properties.userIDに対応するメールアドレス | User.getAuthEmail()で取得 |

## メッセージ仕様

本レイアウトコンポーネント自体にはメッセージ表示機能はない。子コンポーネント（WorkspacePicker、UserMenu）が独自のメッセージを表示する可能性がある。

## 例外処理

| 例外パターン | 検出方法 | 対応処理 | ユーザーへの通知 |
|------------|----------|---------|----------------|
| 未認証アクセス | withActor関数 | 認証画面へリダイレクト | リダイレクト先でログイン促進 |
| ユーザー情報取得失敗 | createAsyncのエラー | undefined返却 | UserMenuでemailがundefinedの場合の処理 |

## 備考

- RouteSectionPropsを使用して子ルートのpropsを受け取る
- useLanguageフックで多言語対応のルーティングを実現
- サーバーサイドクエリ（"use server"）でユーザー情報を取得
- CSSは`workspace.css`で定義

---

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

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

### 推奨読解順序

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

本コンポーネントではユーザー情報の取得にサーバーサイドクエリを使用する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | workspace.tsx | `packages/console/app/src/routes/workspace.tsx` | getUserEmailクエリ（11-18行目）の定義 |
| 1-2 | actor.js | `packages/console-core/src/actor.js` | Actor.assert("user")の仕組み |
| 1-3 | user.js | `packages/console-core/src/user.js` | User.getAuthEmail()の実装 |

**読解のコツ**: `"use server"`ディレクティブはSolidStartのサーバーサイド実行を示す。queryはデータフェッチとキャッシングを提供する。

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | workspace.tsx | `packages/console/app/src/routes/workspace.tsx` | WorkspaceLayout関数コンポーネント（20-40行目） |

**主要処理フロー**:
1. **20行目**: WorkspaceLayout関数定義、RouteSectionPropsを受け取る
2. **21行目**: useParams()でURLパラメータ取得
3. **22行目**: useLanguage()で言語コンテキスト取得
4. **23行目**: createAsync()でuserEmailをリアクティブに取得
5. **24-39行目**: JSXでヘッダーとコンテンツ領域をレンダリング

#### Step 3: 子コンポーネントを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | workspace-picker.tsx | `packages/console/app/src/routes/workspace-picker.tsx` | ワークスペース切り替えUI |
| 3-2 | user-menu.tsx | `packages/console/app/src/routes/user-menu.tsx` | ユーザーアカウントメニュー |
| 3-3 | icon.tsx | `packages/console/app/src/component/icon.tsx` | IconWorkspaceLogoの定義 |

#### Step 4: 認証コンテキストを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | auth.withActor.ts | `packages/console/app/src/context/auth.withActor.ts` | withActor関数の実装 |

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

```
WorkspaceLayout (workspace.tsx)
    |
    +-- useParams() -> params.id取得
    |
    +-- useLanguage() -> languageコンテキスト
    |
    +-- createAsync(getUserEmail)
    |       |
    |       +-- [SERVER] withActor()
    |               |
    |               +-- Actor.assert("user")
    |               +-- User.getAuthEmail(userID)
    |
    +-- <header>
    |       |
    |       +-- <A href={language.route("/")}>
    |       |       +-- <IconWorkspaceLogo />
    |       |
    |       +-- <WorkspacePicker />
    |       +-- <UserMenu email={userEmail()} />
    |
    +-- <div>{props.children}</div>
```

### データフロー図

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

URLパラメータ ────────> useParams()
(params.id)                 |
                            v
                    getUserEmail(params.id)
                            |
                            v
                    [SERVER] withActor()
                            |
                            v
                    Actor.assert("user")
                            |
                            v
                    User.getAuthEmail(userID) ──────> ユーザーメールアドレス
                                                            |
                                                            v
                                                    <UserMenu email={...} />
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| workspace.tsx | `packages/console/app/src/routes/workspace.tsx` | ソース | メインレイアウトコンポーネント |
| workspace.css | `packages/console/app/src/routes/workspace.css` | スタイル | レイアウト固有のスタイル定義 |
| workspace-picker.tsx | `packages/console/app/src/routes/workspace-picker.tsx` | ソース | ワークスペース切り替えコンポーネント |
| user-menu.tsx | `packages/console/app/src/routes/user-menu.tsx` | ソース | ユーザーメニューコンポーネント |
| icon.tsx | `packages/console/app/src/component/icon.tsx` | ソース | アイコンコンポーネント |
| auth.withActor.ts | `packages/console/app/src/context/auth.withActor.ts` | ソース | 認証コンテキストユーティリティ |
| language.tsx | `packages/console/app/src/context/language.tsx` | ソース | 言語コンテキスト |
| user.js | `packages/console-core/src/user.js` | ソース | ユーザードメインロジック |
| actor.js | `packages/console-core/src/actor.js` | ソース | アクター（認証主体）管理 |
