# 画面設計書 18-設定

## 概要

本ドキュメントは、OpenCodeコンソールの設定画面の設計書である。ワークスペースの基本設定（名前）を管理する画面である。管理者のみがアクセス可能。

### 本画面の処理概要

設定画面は、ワークスペースの基本設定を変更するためのシンプルな管理画面である。現在はワークスペース名の変更機能のみを提供している。将来的な拡張に備えたセクション構造を持つ。

**業務上の目的・背景**：ワークスペースは複数のチームや目的別に作成されることがあり、識別しやすい名前を設定することで管理効率を向上させる。本画面は管理者がワークスペース名を適切に管理できるようにする。

**画面へのアクセス方法**：ワークスペース詳細レイアウトのナビゲーションから「Settings」を選択（管理者のみ表示）、または`/workspace/:id/settings`に直接アクセス。

**主要な操作・処理内容**：
1. 現在のワークスペース名の確認
2. ワークスペース名の編集
3. 変更の保存

**画面遷移**：本画面内で完結する操作。

**権限による表示制御**：管理者のみがアクセス可能（親レイアウトのナビゲーション表示制御による）。

## 関連機能

| 機能No | 機能名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 48 | 設定ファイル管理 | 主機能 | ワークスペース設定の管理 |

## 画面種別

設定 / 編集

## URL/ルーティング

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

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

## 入出力項目

### 入力項目（ワークスペース名変更）

| 項目名 | 項目ID | データ型 | 必須 | 入力形式 | バリデーション | 備考 |
|--------|--------|----------|------|----------|----------------|------|
| ワークスペース名 | name | string | Yes | テキスト入力 | 必須チェック、255文字以内 | 空白トリム |
| ワークスペースID | workspaceID | string | Yes | hidden | - | URLパラメータから設定 |

### 出力項目（ワークスペース情報）

| 項目名 | データ型 | 取得元 | 備考 |
|--------|----------|--------|------|
| ワークスペースID | string | getWorkspaceInfo | WorkspaceTable.id |
| ワークスペース名 | string | getWorkspaceInfo | WorkspaceTable.name |
| スラッグ | string | getWorkspaceInfo | WorkspaceTable.slug |

## 表示項目

### セクションタイトル

| 要素 | 内容 |
|------|------|
| タイトル | workspace.settings.title |
| サブタイトル | workspace.settings.subtitle |

### 設定項目（ワークスペース名）

| 状態 | 表示内容 |
|------|---------|
| 通常表示 | 現在のワークスペース名 + 「Edit」ボタン |
| 編集モード | 入力フィールド + 「Save」「Cancel」ボタン |

### 編集フォーム（条件付き表示: store.show=true）

| 要素 | コンポーネント | 説明 |
|------|---------------|------|
| 名前入力 | input[type=text] | required属性、フォーカス自動設定 |
| エラーメッセージ | div | submission.result.error時に表示 |
| 保存ボタン | button | ワークスペース名更新を実行 |
| キャンセルボタン | button | 編集モードを終了 |

## イベント仕様

### 1-編集ボタンクリック

| 項目 | 内容 |
|------|------|
| トリガー | 「Edit」ボタンクリック |
| 処理 | show()関数が実行される |
| 詳細フロー | 1. submission.clear()で前回結果をクリア<br>2. setStore("show", true)でフォーム表示<br>3. input.focus()で入力フィールドにフォーカス |

### 2-ワークスペース名更新（フォーム送信）

| 項目 | 内容 |
|------|------|
| トリガー | 「Save」ボタンクリック |
| 処理 | updateWorkspace actionが実行される |
| 詳細フロー | 1. [SERVER] name取得、空白トリム、必須チェック<br>2. [SERVER] 255文字以内チェック<br>3. [SERVER] Workspace.update({ name })でワークスペース名更新<br>4. 成功時: 編集モード終了<br>5. 失敗時: エラーメッセージを表示 |

### 3-キャンセル

| 項目 | 内容 |
|------|------|
| トリガー | 「Cancel」ボタンクリック |
| 処理 | hide()関数が実行される |
| 結果 | setStore("show", false)で編集モード終了 |

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

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

| 操作（イベント） | 対象テーブル | 操作種別 | 概要 |
|----------------|-------------|---------|------|
| ページロード | WorkspaceTable | SELECT | ワークスペース情報の取得 |
| 名前更新 | WorkspaceTable | UPDATE | ワークスペース名の変更 |

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

#### getWorkspaceInfo クエリ

```typescript
const getWorkspaceInfo = query(async (workspaceID: string) => {
  return withActor(
    () => Database.use((tx) =>
      tx.select({
        id: WorkspaceTable.id,
        name: WorkspaceTable.name,
        slug: WorkspaceTable.slug,
      })
      .from(WorkspaceTable)
      .where(eq(WorkspaceTable.id, workspaceID))
      .then((rows) => rows[0] || null)
    ),
    workspaceID,
  )
}, "workspace.get")
```

#### updateWorkspace アクション

```typescript
const updateWorkspace = action(async (form: FormData) => {
  const name = form.get("name")?.toString().trim()
  if (!name) return { error: formError.workspaceNameRequired }
  if (name.length > 255) return { error: formError.nameTooLong }
  const workspaceID = form.get("workspaceID")?.toString()
  if (!workspaceID) return { error: formError.workspaceRequired }
  return withActor(
    () => Workspace.update({ name }),
    workspaceID,
  )
}, "workspace.update")
```

| 操作 | 項目（カラム名） | 更新値 | 備考 |
|-----|-----------------|--------|------|
| UPDATE | name | フォーム入力値 | 空白トリム済み、255文字以内 |

## メッセージ仕様

| メッセージID | メッセージ種別 | 表示条件 | メッセージ内容（キー） |
|-------------|---------------|----------|----------------------|
| title | タイトル | 常時 | workspace.settings.title |
| subtitle | サブタイトル | 常時 | workspace.settings.subtitle |
| workspaceName | ラベル | 常時 | workspace.settings.workspaceName |
| defaultName | プレースホルダー | ワークスペース名未設定時 | workspace.settings.defaultName |
| edit | ボタン | 通常表示時 | workspace.settings.edit |
| save | ボタン | 編集モード | workspace.settings.save |
| updating | ボタン | 更新中 | workspace.settings.updating |
| cancel | ボタン | 編集モード | common.cancel |

## 例外処理

| 例外パターン | 検出方法 | 対応処理 | ユーザーへの通知 |
|------------|----------|---------|----------------|
| 名前未入力 | サーバーサイドバリデーション | { error: formError.workspaceNameRequired } | フォーム内エラー表示 |
| 名前が長すぎる | サーバーサイドバリデーション | { error: formError.nameTooLong } | フォーム内エラー表示 |
| ワークスペースID未指定 | サーバーサイドバリデーション | { error: formError.workspaceRequired } | （通常発生しない） |
| 更新エラー | Workspace.update()のcatch | { error: e.message } | フォーム内エラー表示 |

## 備考

- 管理者のみアクセス可能（親レイアウトのナビゲーション制御）
- 現在はワークスペース名の変更機能のみ
- 将来的な設定項目の追加に備えたセクション構造
- CSSモジュール（settings-section.module.css）でスタイリング
- 入力フィールドにrequired属性でHTML5バリデーション併用

---

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

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

### 推奨読解順序

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

本コンポーネントでは1つのサーバーサイドクエリと1つのアクションを使用する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | settings-section.tsx | `packages/console/app/src/routes/workspace/[id]/settings/settings-section.tsx` | getWorkspaceInfo（12-29行目）、updateWorkspace（31-47行目） |

**読解のコツ**: Database.use()でDrizzle ORMのトランザクションを使用。WorkspaceTableからid、name、slugを取得。

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | index.tsx | `packages/console/app/src/routes/workspace/[id]/settings/index.tsx` | SettingsSectionコンポーネントの配置 |
| 2-2 | settings-section.tsx | `packages/console/app/src/routes/workspace/[id]/settings/settings-section.tsx` | SettingsSection関数（49-125行目） |

**主要処理フロー**:
1. **50行目**: useParams()でワークスペースID取得
2. **52行目**: createAsync()でワークスペース情報取得
3. **53行目**: useSubmission()で更新アクションの状態監視
4. **54行目**: createStore()でフォーム表示状態管理
5. **58-62行目**: createEffect()で更新成功時に編集モード終了
6. **64-71行目**: show()関数 - 編集モード開始
7. **73-75行目**: hide()関数 - 編集モード終了
8. **77-124行目**: JSXによるUI描画

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

```
SettingsPage (index.tsx)
    |
    +-- <SettingsSection />

SettingsSection (settings-section.tsx)
    |
    +-- useParams() -> params.id取得
    |
    +-- createAsync(getWorkspaceInfo)
    |       |
    |       +-- [SERVER] withActor()
    |               |
    |               +-- Database.use((tx) => ...)
    |                       |
    |                       +-- tx.select().from(WorkspaceTable)
    |
    +-- useSubmission(updateWorkspace) -> submission状態
    |
    +-- createStore() -> { show: false }
    |
    +-- createEffect() -> 更新成功時にhide()
    |
    +-- show() -> 編集モード開始
    |
    +-- hide() -> 編集モード終了
    |
    +-- <Show when={!store.show}>
    |       +-- 現在の名前表示
    |       +-- Editボタン -> show()
    |
    +-- <Show when={store.show}>
            +-- <form action={updateWorkspace}>
                    +-- <input name="name" />
                    +-- <input type="hidden" name="workspaceID" />
                    +-- Saveボタン
                    +-- Cancelボタン -> hide()
```

### データフロー図

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

ページロード ─────────> getWorkspaceInfo(workspaceID)
                              |
                              v
                        [SERVER] Database.use()
                              |
                              v
                        WorkspaceTable SELECT ────────> 現在の名前表示

Editボタン ───────────> show()
                              |
                              v
                        setStore("show", true) ───────> 編集フォーム表示

名前入力 → Save ──────> updateWorkspace action
                              |
                              v
                        [SERVER] バリデーション
                              |
                              v
                        Workspace.update({ name })
                              |
                              v
                        成功 ────────────────────────> hide() → 名前表示更新
                              |
                              v
                        失敗 ────────────────────────> エラーメッセージ表示

Cancelボタン ─────────> hide()
                              |
                              v
                        setStore("show", false) ──────> 名前表示に戻る
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| index.tsx | `packages/console/app/src/routes/workspace/[id]/settings/index.tsx` | ソース | ページエントリーポイント |
| settings-section.tsx | `packages/console/app/src/routes/workspace/[id]/settings/settings-section.tsx` | ソース | メインコンポーネント |
| settings-section.module.css | `packages/console/app/src/routes/workspace/[id]/settings/settings-section.module.css` | スタイル | セクション固有スタイル |
| form-error.ts | `packages/console/app/src/lib/form-error.ts` | ソース | エラーメッセージ定義、localizeError |
| workspace.js | `packages/console-core/src/workspace.js` | ソース | Workspaceドメインロジック |
| workspace.sql.js | `packages/console-core/src/schema/workspace.sql.js` | ソース | WorkspaceTableスキーマ |
