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

## 概要

本ドキュメントは、OpenCodeコンソールのワークスペース詳細レイアウト画面の設計書である。個別ワークスペースの管理画面における共通ナビゲーション（Zen、APIキー、メンバー、請求、設定）を提供するレイアウトコンポーネントである。

### 本画面の処理概要

ワークスペース詳細レイアウトは、特定のワークスペースID配下のすべての管理画面に対して共通のナビゲーションを提供するシェルコンポーネントである。デスクトップ用とモバイル用の2つのナビゲーションを持ち、管理者権限を持つユーザーには追加のメニュー項目（請求、設定）を表示する。

**業務上の目的・背景**：ワークスペース内の各機能（Zenダッシュボード、APIキー管理、メンバー管理、請求管理、設定）に一貫したナビゲーションを提供することで、ユーザーがワークスペース管理機能間をスムーズに移動できるようにする。また、管理者のみがアクセスすべき機能（請求、設定）を権限に基づいて表示制御することで、適切なアクセス管理を実現する。

**画面へのアクセス方法**：`/workspace/:id`配下のルートにアクセスした際に自動的に適用される。親レイアウト（ワークスペースレイアウト）の子として機能する。

**主要な操作・処理内容**：
1. ナビゲーションリンクによる各管理画面への遷移
2. 管理者権限に基づく追加メニュー項目の表示制御
3. 子ルートコンテンツの表示
4. レスポンシブ対応（デスクトップ/モバイル用ナビゲーション）

**画面遷移**：
- `/workspace/:id` - Zenダッシュボード
- `/workspace/:id/keys` - APIキー管理
- `/workspace/:id/members` - メンバー管理
- `/workspace/:id/billing` - 請求管理（管理者のみ）
- `/workspace/:id/settings` - 設定（管理者のみ）

**権限による表示制御**：管理者（isAdmin: true）の場合のみ、「Billing」と「Settings」のナビゲーションリンクが表示される。

## 関連機能

| 機能No | 機能名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 28 | 認証管理 | 主機能 | 管理者権限による追加メニュー表示制御 |

## 画面種別

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

## URL/ルーティング

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

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

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

## 入出力項目

### 入力項目

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

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

| 項目名 | データ型 | 取得元 | 備考 |
|--------|----------|--------|------|
| isAdmin | boolean | querySessionInfo | 管理者権限フラグ |
| isBeta | boolean | querySessionInfo | ベータ機能アクセスフラグ |

## 表示項目

### ナビゲーションセクション（デスクトップ/モバイル共通構造）

| メニュー項目 | リンク先 | 表示条件 | アクティブ判定 |
|-------------|---------|----------|--------------|
| Zen | `/workspace/:id` | 常時 | 完全一致（end属性） |
| API Keys | `/workspace/:id/keys` | 常時 | 部分一致 |
| Members | `/workspace/:id/members` | 常時 | 部分一致 |
| Billing | `/workspace/:id/billing` | isAdmin=true | 部分一致 |
| Settings | `/workspace/:id/settings` | isAdmin=true | 部分一致 |

### コンテンツ領域

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

## イベント仕様

### 1-ナビゲーションリンククリック

| 項目 | 内容 |
|------|------|
| トリガー | ナビゲーションボタン（data-nav-button）クリック |
| 処理 | SolidRouterの<A>コンポーネントによるクライアントサイドルーティング |
| 結果 | 対応する子ルートへ遷移、activeClassが適用される |

### 2-権限チェック

| 項目 | 内容 |
|------|------|
| トリガー | コンポーネントマウント時 |
| 処理 | createAsync()でquerySessionInfo実行、isAdminを取得 |
| 結果 | isAdmin=trueの場合、BillingとSettingsリンクが表示される |

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

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

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

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

#### querySessionInfoクエリ（common.tsxで定義）

```typescript
export const querySessionInfo = query(async (workspaceID: string) => {
  return withActor(() => {
    return {
      isAdmin: Actor.userRole() === "admin",
      isBeta: Resource.App.stage === "production" ? workspaceID === "wrk_..." : true,
    }
  }, workspaceID)
}, "session.get")
```

| 操作 | 項目 | 取得条件 | 備考 |
|-----|------|---------|------|
| SELECT | userRole | Actor.userRole()で取得 | "admin"または"member" |

## メッセージ仕様

| メッセージID | メッセージ種別 | 表示条件 | メッセージ内容（キー） |
|-------------|---------------|----------|----------------------|
| nav.zen | ラベル | 常時 | workspace.nav.zen |
| nav.apiKeys | ラベル | 常時 | workspace.nav.apiKeys |
| nav.members | ラベル | 常時 | workspace.nav.members |
| nav.billing | ラベル | isAdmin=true | workspace.nav.billing |
| nav.settings | ラベル | isAdmin=true | workspace.nav.settings |

## 例外処理

| 例外パターン | 検出方法 | 対応処理 | ユーザーへの通知 |
|------------|----------|---------|----------------|
| セッション情報取得失敗 | createAsyncエラー | userInfo()がundefined | Billing/Settingsリンク非表示 |
| 未認証アクセス | 親レイアウトで検出 | 認証画面へリダイレクト | 親レイアウトで処理 |

## 備考

- デスクトップ用（nav-desktop）とモバイル用（nav-mobile）の2つのナビゲーションを持つ
- CSSメディアクエリで表示を切り替え
- `activeClass="active"`により現在のルートがハイライトされる
- `end`属性はZenリンクのみに設定（完全一致判定）
- Legalコンポーネントはコンテンツ領域の下部に配置

---

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

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

### 推奨読解順序

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

本コンポーネントではセッション情報（管理者権限）の取得にサーバーサイドクエリを使用する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | common.tsx | `packages/console/app/src/routes/workspace/common.tsx` | querySessionInfo（64-72行目）の定義 |
| 1-2 | [id].tsx | `packages/console/app/src/routes/workspace/[id].tsx` | userInfoの取得（11行目） |

**読解のコツ**: `Actor.userRole()`は現在のユーザーのロール（"admin"または"member"）を返す。この値に基づいて管理者向けメニューの表示を制御する。

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

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

**主要処理フロー**:
1. **8行目**: WorkspaceLayout関数定義、RouteSectionPropsを受け取る
2. **9行目**: useParams()でワークスペースID取得
3. **10行目**: useI18n()で国際化コンテキスト取得
4. **11行目**: createAsync()でuserInfo（セッション情報）を取得
5. **16-37行目**: デスクトップ用ナビゲーション
6. **39-59行目**: モバイル用ナビゲーション
7. **61-64行目**: コンテンツ領域とLegalコンポーネント

#### Step 3: 条件付き表示を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | [id].tsx | `packages/console/app/src/routes/workspace/[id].tsx` | Show when={userInfo()?.isAdmin}（28-35行目、50-57行目） |

**読解のコツ**: SolidJSの`<Show when={...}>`は条件付きレンダリングを行う。`userInfo()?.isAdmin`が真の場合のみ、BillingとSettingsリンクが表示される。

#### Step 4: 共通コンポーネントを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | legal.tsx | `packages/console/app/src/component/legal.tsx` | 法的情報コンポーネント |

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

```
WorkspaceLayout ([id].tsx)
    |
    +-- useParams() -> params.id取得
    |
    +-- useI18n() -> i18nコンテキスト
    |
    +-- createAsync(querySessionInfo)
    |       |
    |       +-- [SERVER] withActor()
    |               |
    |               +-- Actor.userRole() -> "admin" | "member"
    |
    +-- <nav data-component="nav-desktop">
    |       |
    |       +-- <A href="/workspace/:id"> Zen
    |       +-- <A href="/workspace/:id/keys"> API Keys
    |       +-- <A href="/workspace/:id/members"> Members
    |       +-- <Show when={isAdmin}>
    |               +-- <A href="/workspace/:id/billing"> Billing
    |               +-- <A href="/workspace/:id/settings"> Settings
    |
    +-- <nav data-component="nav-mobile">
    |       +-- (同構造)
    |
    +-- <div data-component="workspace-content">
            +-- {props.children}
            +-- <Legal />
```

### データフロー図

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

URLパラメータ ────────> useParams()
(params.id)                 |
                            v
                    querySessionInfo(params.id)
                            |
                            v
                    [SERVER] withActor()
                            |
                            v
                    Actor.userRole()
                            |
                            v
                    isAdmin判定 ──────────────────> ナビゲーション表示制御
                    ("admin" === userRole)              |
                                                        v
                                            Billing/Settingsリンク表示
                                            (isAdmin=trueの場合のみ)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| [id].tsx | `packages/console/app/src/routes/workspace/[id].tsx` | ソース | メインレイアウトコンポーネント |
| [id].css | `packages/console/app/src/routes/workspace/[id].css` | スタイル | レイアウト固有のスタイル定義 |
| common.tsx | `packages/console/app/src/routes/workspace/common.tsx` | ソース | 共通クエリ・アクション定義 |
| legal.tsx | `packages/console/app/src/component/legal.tsx` | ソース | 法的情報コンポーネント |
| i18n.tsx | `packages/console/app/src/context/i18n.tsx` | ソース | 国際化コンテキスト |
| auth.withActor.ts | `packages/console/app/src/context/auth.withActor.ts` | ソース | 認証コンテキストユーティリティ |
| actor.js | `packages/console-core/src/actor.js` | ソース | アクター（認証主体）管理 |
