# 帳票設計書 7-Share Report (Web UI)

## 概要

本ドキュメントは、opencodeセッション内容をリアルタイムで共有するWebページの設計書である。

### 本帳票の処理概要

本帳票は、opencodeのセッション内容を外部共有用のWebページとしてリアルタイム表示する機能を提供する。WebSocket接続により、セッションの進行状況をリアルタイムで更新表示する。

**業務上の目的・背景**：AIアシスタントとの対話セッションをチームメンバーや関係者とリアルタイムで共有するために必要である。ペアプログラミングのような協調作業、レビュー、教育目的での画面共有、または非同期での確認作業を可能にする。共有URLを通じてセッション内容にアクセスできるため、opencode CLIを持たないメンバーとの情報共有にも有効である。

**帳票の利用シーン**：チームメンバーとのリアルタイムコーディングセッション共有、コードレビュー時の対話履歴確認、技術サポート時の問題再現確認、教育・トレーニング目的でのセッション公開に利用される。

**主要な出力内容**：
1. セッションヘッダー（タイトル、バージョン、使用モデル、作成日時）
2. メッセージ一覧（ユーザー/アシスタントの対話履歴）
3. パーツ詳細（テキスト、ツール呼び出し、コード等）
4. 統計サマリー（コスト、入力/出力/推論トークン数）
5. 接続状態表示（connected/connecting/disconnected/reconnecting/error）

**帳票の出力タイミング**：共有URL（`/share?id={sessionID}`）にアクセスした時点でWebSocket接続を開始し、リアルタイム更新を行う。

**帳票の利用者**：共有URLを受け取った開発者、チームメンバー、レビュアー、外部関係者

## 帳票種別

リアルタイム共有ページ / セッションビューア

## 利用画面

| 画面No | 画面名 | URL/ルーティング | 出力操作 |
|--------|--------|-----------------|---------|
| SH-001 | セッション共有 | `/share?id={sessionID}` | URLアクセス時自動表示 |

## 出力形式

### 基本仕様

| 項目 | 内容 |
|-----|------|
| ファイル形式 | HTML（Webページ） |
| 用紙サイズ | 該当なし |
| 向き | 該当なし |
| ファイル名 | 該当なし |
| 出力方法 | ブラウザ表示（WebSocket接続） |
| 文字コード | UTF-8 |

### リアルタイム更新仕様

| 項目 | 内容 |
|-----|------|
| プロトコル | WebSocket (wss://) |
| 再接続間隔 | 2秒 |
| イベントタイプ | session/info, session/message, session/part |

## 帳票レイアウト

### レイアウト概要

ヘッダー、メッセージ一覧、サマリーの順で表示する。

```
┌─────────────────────────────────────────────────────────────────┐
│                      Session Title                              │
│  [opencode v1.0.0] [claude-sonnet-4] ...  Jan 1, 2026 10:00 AM │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  ┌─ User ──────────────────────────────────────────────────┐  │
│  │  User message content...                                 │  │
│  └──────────────────────────────────────────────────────────┘  │
│                                                                 │
│  ┌─ Assistant ─────────────────────────────────────────────┐  │
│  │  Assistant response content...                           │  │
│  │  [Tool: Bash] ...                                       │  │
│  └──────────────────────────────────────────────────────────┘  │
│                                                                 │
│  ...                                                            │
│                                                                 │
├─────────────────────────────────────────────────────────────────┤
│  ● Connected - Waiting for updates                             │
│  Cost: $0.1234  Input: 12,345  Output: 6,789  Reasoning: 1,234 │
└─────────────────────────────────────────────────────────────────┘

                    [↓ Scroll to bottom]
```

### ヘッダー部

| No | 項目名 | 説明 | データ取得元 | 表示形式 |
|----|-------|------|-------------|---------|
| 1 | title | セッションタイトル | session.title | H1テキスト |
| 2 | version | opencodeバージョン | session.version | `v{version}` |
| 3 | models | 使用モデル一覧 | messages.modelID (unique) | アイコン + モデル名 |
| 4 | created | 作成日時 | session.time.created | DateTime.DATETIME_MED形式 |

### メッセージ部

| No | 項目名 | 説明 | データ取得元 | 表示形式 |
|----|-------|------|-------------|---------|
| 1 | role | メッセージの送信者 | message.role | User / Assistant |
| 2 | parts | メッセージパーツ | message.parts | タイプ別レンダリング |

### パーツタイプ別表示

| パートタイプ | 表示内容 | フィルタ条件 |
|-------------|---------|-------------|
| text | テキスト内容 | synthetic !== true かつ text が空でない |
| tool | ツール呼び出し | pending/running以外、todoread以外 |
| reasoning | 思考過程 | 表示 |
| step-start | ステップ開始 | index > 0 の場合は非表示 |
| snapshot | スナップショット | 非表示 |
| patch | パッチ | 非表示 |
| step-finish | ステップ終了 | 非表示 |

### サマリー部

| No | 項目名 | 説明 | データ取得元 | 表示形式 |
|----|-------|------|-------------|---------|
| 1 | status | 接続状態 | connectionStatus | テキスト + インジケーター |
| 2 | cost | 総コスト | data.cost | formatCurrency |
| 3 | input | 入力トークン | data.tokens.input | formatNumber |
| 4 | output | 出力トークン | data.tokens.output | formatNumber |
| 5 | reasoning | 推論トークン | data.tokens.reasoning | formatNumber |

## 出力条件

### 抽出条件

| 条件名 | 説明 | 必須 |
|-------|------|-----|
| id | セッションID（URLクエリパラメータ） | Yes |

### パーツフィルタリング条件

| 条件 | 動作 |
|-----|------|
| type === "step-start" && index > 0 | 非表示 |
| type === "snapshot" | 非表示 |
| type === "patch" | 非表示 |
| type === "step-finish" | 非表示 |
| type === "text" && synthetic === true | 非表示 |
| type === "tool" && tool === "todoread" | 非表示 |
| type === "text" && !text | 非表示 |
| type === "tool" && (status === "pending" \|\| status === "running") | 非表示 |

## データベース参照仕様

### WebSocket経由データ受信

本機能はAPIサーバーからWebSocket経由でデータを受信する。

#### session/info イベント

| プロパティ | 説明 |
|-----------|------|
| id | セッションID |
| slug | セッションスラッグ |
| projectID | プロジェクトID |
| directory | ディレクトリパス |
| title | セッションタイトル |
| version | バージョン |
| time.created | 作成日時 |
| time.updated | 更新日時 |

#### session/message イベント

| プロパティ | 説明 |
|-----------|------|
| id | メッセージID |
| sessionID | セッションID |
| role | user / assistant |
| (assistant) cost | コスト |
| (assistant) tokens | トークン情報 |
| (assistant) modelID | モデルID |
| (assistant) providerID | プロバイダーID |

#### session/part イベント

| プロパティ | 説明 |
|-----------|------|
| id | パートID |
| messageID | メッセージID |
| sessionID | セッションID |
| type | パートタイプ |
| (type別) 各種プロパティ | - |

## 計算仕様

### 計算項目一覧

| 項目名 | 計算式 | 端数処理 | 備考 |
|-------|-------|---------|------|
| totalCost | SUM(assistant.cost) | - | アシスタントメッセージのコスト合計 |
| totalInputTokens | SUM(assistant.tokens.input) | - | 入力トークン合計 |
| totalOutputTokens | SUM(assistant.tokens.output) | - | 出力トークン合計 |
| totalReasoningTokens | SUM(assistant.tokens.reasoning) | - | 推論トークン合計 |

### 数値フォーマット

| 関数 | 説明 | 例 |
|-----|------|-----|
| formatCurrency | ロケール対応通貨表示 | $0.1234 |
| formatNumber | ロケール対応数値表示 | 12,345 |

## 処理フロー

### 出力フロー

```mermaid
flowchart TD
    A[コンポーネントマウント] --> B{id存在?}
    B -->|No| C[エラー表示: ID not found]
    B -->|Yes| D{apiUrl存在?}
    D -->|No| E[エラー表示: API URL not found]
    D -->|Yes| F[setupWebSocket呼び出し]
    F --> G[WebSocket接続開始]
    G --> H[connecting状態]
    H --> I{接続成功?}
    I -->|Yes| J[connected状態]
    I -->|No| K[error状態]
    J --> L[メッセージ受信待機]
    L --> M{イベントタイプ}
    M -->|session/info| N[store.info更新]
    M -->|session/message| O[store.messages更新]
    M -->|session/part| P[store.messages.parts更新]
    N --> L
    O --> L
    P --> L
    L --> Q{接続切断?}
    Q -->|Yes| R[reconnecting状態]
    R --> S[2秒待機]
    S --> F
```

### V1→V2メッセージ変換フロー

```mermaid
flowchart TD
    A[メッセージ受信] --> B{metadata存在?}
    B -->|Yes| C[V1形式と判定]
    B -->|No| D[V2形式として処理]
    C --> E[fromV1関数呼び出し]
    E --> F[V2形式に変換]
    F --> D
    D --> G[store更新]
```

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 表示メッセージ | 対処方法 |
|----------|---------|--------------|---------|
| ID未指定 | URLにidパラメータなし | "ID not found" (i18n) | 正しいURLでアクセス |
| API URL未設定 | 環境変数未設定 | "API URL not found" (i18n) | サーバー設定確認 |
| 接続失敗 | WebSocket接続エラー | "Connection failed" (i18n) | 自動再接続（2秒後） |
| メッセージ解析エラー | JSONパースエラー | コンソールログ出力 | 処理継続 |

### 接続状態表示

| 状態 | 表示テキスト |
|-----|-------------|
| connected | "Connected - Waiting for updates" |
| connecting | "Connecting..." |
| disconnected | "Disconnected" |
| reconnecting | "Reconnecting..." |
| error | エラーメッセージ |

## パフォーマンス要件

| 項目 | 内容 |
|-----|------|
| 想定メッセージ数 | 数十〜数百 |
| WebSocket再接続 | 2秒間隔で自動再接続 |
| スクロール最適化 | IntersectionObserverで下部検出 |

## セキュリティ考慮事項

- 共有URLを知っている人のみアクセス可能
- セッションIDがURLに含まれるため、URLの取り扱いに注意
- WebSocket接続はwss://（TLS暗号化）を使用
- デバッグモードは`?debug=true`で有効化（開発用）

## 備考

- luxon（DateTime）を使用した日時フォーマット
- SolidJSのSuspenseListによる順次レンダリング
- 下部スクロールボタンは下方向スクロール時のみ表示（3秒後自動非表示）
- V1形式のメッセージはfromV1関数でV2形式に変換
- アンカーリンク（`#messageID`）による特定メッセージへのジャンプ対応

---

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

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

### 推奨読解順序

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

まず、WebSocketで受信するデータ構造とコンポーネントの状態を把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | Share.tsx | `packages/web/src/components/Share.tsx` | MessageWithParts型（13行）、Status型（15行）、store構造（60-77行） |

**読解のコツ**: SolidJSのcreateStoreとreconcileによるリアクティブ状態管理を理解する。

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | Share.tsx | `packages/web/src/components/Share.tsx` | Share関数コンポーネント（41-507行）、props定義 |

**主要処理フロー**:
1. **41-46行**: props定義（id, api, info, messages）
2. **60-77行**: store初期化
3. **81-175行**: WebSocket接続セットアップ
4. **253-296行**: data計算（createMemo）

#### Step 3: WebSocket処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | Share.tsx | `packages/web/src/components/Share.tsx` | setupWebSocket関数（99-163行）、onmessageハンドラー（119-147行） |

**主要処理フロー**:
- **108行**: WSS URL構築
- **119-147行**: メッセージ種別による分岐処理
- **155-162行**: 再接続ロジック（2秒後）

#### Step 4: パーツフィルタリングを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | Share.tsx | `packages/web/src/components/Share.tsx` | filteredParts createMemo（351-364行）でフィルタ条件を確認 |

**主要処理フロー**:
- **352-363行**: 各パートタイプの表示/非表示条件

#### Step 5: V1→V2変換を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | Share.tsx | `packages/web/src/components/Share.tsx` | fromV1関数（510-653行）でレガシー形式からの変換を確認 |

**主要処理フロー**:
- **510-606行**: assistant roleの変換
- **608-651行**: user roleの変換

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

```
Share (Share.tsx:41)
    │
    ├─ onMount() - 初期化
    │      │
    │      └─ setupWebSocket()
    │             │
    │             ├─ new WebSocket(wsUrl)
    │             │
    │             ├─ onopen → setConnectionStatus("connected")
    │             │
    │             ├─ onmessage → JSON.parse
    │             │      │
    │             │      ├─ session/info → setStore("info", reconcile)
    │             │      │
    │             │      ├─ session/message → setStore("messages", messageID, reconcile)
    │             │      │      └─ fromV1() (if V1 format)
    │             │      │
    │             │      └─ session/part → setStore("messages", messageID, "parts", ...)
    │             │
    │             └─ onclose → reconnect (2s delay)
    │
    ├─ data createMemo() - 統計計算
    │      │
    │      └─ messages loop → cost, tokens集計
    │
    └─ filteredParts createMemo() - パーツフィルタ
           │
           └─ filter by type, synthetic, status
```

### データフロー図

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

props.id ─────────────▶ setupWebSocket()
props.api ─────────────▶     │
props.info ────────────▶ store.info初期化
                              │
                              ▼
WebSocket接続 ────────────────▶ wss://{api}/share_poll?id={id}
                              │
                    ┌─────────┴──────────┐
                    ▼                    ▼
            session/info          session/message
                    │                    │
                    ▼                    ▼
            store.info更新      store.messages更新
                    │                    │
                    └─────────┬──────────┘
                              │
                              ▼
                    session/part ────────▶ store.messages.parts更新
                              │
                              ▼
                    data createMemo()
                              │
                    ┌─────────┴──────────┐
                    ▼                    ▼
                cost, tokens        models
                              │
                              ▼
                    JSXレンダリング
                              │
        ┌─────────┬───────────┼───────────┬─────────┐
        ▼         ▼           ▼           ▼         ▼
     Header    Messages   Summary    Scroll     Status
                              (stats)   Button   Indicator
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| Share.tsx | `packages/web/src/components/Share.tsx` | ソース | 共有ページメインコンポーネント |
| share.module.css | `packages/web/src/components/share.module.css` | スタイル | コンポーネントスタイル |
| common.tsx | `packages/web/src/components/share/common.tsx` | ソース | formatCurrency, formatNumber等 |
| part.tsx | `packages/web/src/components/share/part.tsx` | ソース | Part, ProviderIconコンポーネント |
| icons.tsx | `packages/web/src/components/icons.tsx` | ソース | IconArrowDown等 |
| icons/custom.tsx | `packages/web/src/components/icons/custom.tsx` | ソース | IconOpencode |
