# 画面設計書 66-ノート詳細画面

## 概要

本ドキュメントは、Ghost管理画面における「ノート詳細画面（Note）」の設計仕様を記述したものである。この画面では、個別のノート（短文投稿）の詳細表示と、そのノートへの返信スレッド（リプライチェーン）を表示する。

### 本画面の処理概要

ノート詳細画面は、特定のノートとその返信スレッドを表示するための詳細ビューである。親投稿（スレッドの先頭）から対象ノートまでの会話の流れと、対象ノートへの返信を階層的に表示する。返信の追加や、いいね・リポスト操作も可能。

**業務上の目的・背景**：フェディバースでの会話は、返信の連鎖によってスレッド形式で進行する。この画面により、特定のノートを中心とした会話の文脈を把握でき、議論への参加が容易になる。親投稿から順に表示することで、会話の流れを追いやすくしている。

**画面へのアクセス方法**：ノート一覧画面から個別のノートをクリック、または通知画面から該当ノートをクリック。URL は `/ghost/#/activitypub/notes/:postId`。

**主要な操作・処理内容**：
1. 対象ノートの詳細表示
2. 親投稿（スレッドの祖先）の表示
3. 返信スレッドの階層的表示
4. 新規返信の投稿（APReplyBox）
5. いいね・リポスト・削除操作
6. 返信チェーンの展開/折りたたみ
7. 追加返信の読み込み

**画面遷移**：ノート一覧、通知画面からアクセス。プロフィールアイコンクリックでプロフィール画面へ遷移。返信ノートクリックでそのノートの詳細へ遷移。

**権限による表示制御**：ActivityPub機能が有効化されている場合にのみ表示される。自分が投稿したノートのみ削除可能（`allowDelete`）。

## 関連機能

| 機能No | 機能名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 57 | ActivityPub | 主機能 | 個別ノートの詳細表示・返信 |

## 画面種別

詳細画面 + スレッド表示

## URL/ルーティング

- メインパス: `/ghost/#/activitypub/notes/:postId`
- パラメータ: `postId` - URLエンコードされたノートID
- ページタイトル: 「Note」

## 入出力項目

### 返信入力

| 項目名 | データ型 | 入力/出力 | 説明 |
|--------|----------|----------|------|
| 返信本文 | string | 入力 | 返信として投稿するテキスト |

## 表示項目

### メインノート

| 項目名 | 表示形式 | 説明 |
|--------|----------|------|
| アバター | 画像 | 投稿者のアバター画像 |
| 投稿者名 | テキスト | アカウント表示名 |
| ハンドル | テキスト | @username@domain形式 |
| ノート本文 | リッチテキスト | ノートの内容 |
| 添付メディア | 画像/動画 | 添付されたメディア |
| 投稿日時 | テキスト | 絶対日時または相対時間 |
| いいね数 | 数値 | likeCount（クリッカブル） |
| リポスト数 | 数値 | repostCount |
| 返信数 | 数値 | replyCount |
| 統計情報 | コンポーネント | showStats=true で詳細表示 |

### 親投稿（threadParents）

| 項目名 | 表示形式 | 説明 |
|--------|----------|------|
| FeedItem | コンポーネント | 祖先ノートの簡易表示 |
| 接続線 | 視覚要素 | スレッドの継続を示す線 |
| 削除済み表示 | テキスト | Tombstoneタイプの場合 |

### 返信スレッド（processedReplies）

| 項目名 | 表示形式 | 説明 |
|--------|----------|------|
| メイン返信 | FeedItem | 各返信グループの主要返信 |
| チェーン返信 | FeedItem[] | 返信への返信（ネスト） |
| 展開ボタン | ボタン | チェーン展開/折りたたみ |
| 追加読み込みボタン | ボタン | 追加返信の読み込み |

## イベント仕様

### 1-ページロード

1. URLパラメータから `postId` を取得・デコード
2. `useReplyChainData` フックでデータ取得
   - `includeAncestors: true` で親投稿も取得
3. ローディング表示
4. データ取得完了後、スレッド表示

### 2-親投稿へのスクロール

1. `threadParents` が存在する場合
2. `postRef` 要素にスクロール（`scrollIntoView`）
3. `hasScrolledToPost` フラグで重複防止

### 3-返信投稿

1. `APReplyBox` に返信テキストを入力
2. 投稿ボタンをクリック
3. ActivityPub API経由で返信を投稿
4. 成功時：返信リストに追加
5. 失敗時：エラートースト表示

### 4-チェーン展開/折りたたみ

1. `ShowRepliesButton` をクリック
2. `toggleChain(chainId)` を実行
3. `expandedChains` 状態を更新
4. 展開時：`fullyExpandedChains` にも追加

### 5-追加返信読み込み

1. 「Load more」ボタンをクリック
2. `loadMoreForChain(chainId, childIndex)` を実行
3. `loadMoreChildReplies` API呼び出し
4. 追加返信をチェーンに追加

### 6-トップレベル返信の追加読み込み

1. IntersectionObserverが画面下部（200px手前）を検知
2. `hasMoreChildren` が true の場合
3. `loadMoreChildren` を実行
4. 追加のトップレベル返信を取得

### 7-ノート削除

1. 自分のノート（`authored=true`）の削除ボタンをクリック
2. 削除確認
3. ActivityPub API経由で削除
4. 成功時：キャッシュ無効化、リダイレクト

### 8-返信ノートクリック

1. 返信のFeedItemをクリック
2. `navigate('/notes/{encodedReplyId}')` を実行
3. そのノートの詳細画面へ遷移

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

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

この画面からの直接的なGhostデータベース更新はなし。ActivityPub APIを経由したリモートサーバーとの通信が行われる。

| 操作（イベント） | 対象 | 操作種別 | 概要 |
|----------------|------|---------|------|
| ノート詳細取得 | ActivityPub API | GET | リプライチェーンデータの取得 |
| 返信投稿 | ActivityPub API | POST | Reply作成 |
| いいね | ActivityPub API | POST/DELETE | Like/Unlike操作 |
| リポスト | ActivityPub API | POST/DELETE | Announce操作 |
| 削除 | ActivityPub API | DELETE | Note削除 |

## メッセージ仕様

| メッセージ種別 | メッセージ内容 | 表示条件 |
|---------------|---------------|----------|
| エラー | Error loading note. | ノートデータ取得失敗時 |

## 例外処理

| 例外条件 | 処理内容 |
|---------|---------|
| ノート取得失敗 | EmptyViewIndicatorでエラーアイコンと「Error loading note.」を表示 |
| ノートが存在しない | 404エラー表示 |
| 削除済みノート（Tombstone） | DeletedFeedItemコンポーネントで「削除済み」表示 |
| ネットワークエラー | ローディング表示継続 |

## 備考

- スレッドの祖先は `threadParents` として逆順（古い順）で表示
- 返信は `processedReplies` として、メイン返信とチェーン返信に整理される
- `isPendingActivity` でpending状態の投稿を視覚的に区別
- 親投稿がある場合、対象ノートの位置まで自動スクロール
- チェーンの展開状態は `expandedChains`、完全展開状態は `fullyExpandedChains` で管理
- IntersectionObserverで200px手前から追加読み込みをトリガー

---

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

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

### 推奨読解順序

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

ノート詳細では、対象ノート、親投稿群（threadParents）、返信群（processedReplies）の3つのデータを扱う。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | use-reply-chain-data.ts | `apps/activitypub/src/hooks/use-reply-chain-data.ts` | リプライチェーンデータの取得・処理 |

**読解のコツ**: `useReplyChainData` は `includeAncestors` オプションで親投稿も取得し、返信を `processedReplies` として整理された形式で返す。

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | note.tsx | `apps/activitypub/src/views/feed/note.tsx` | Noteコンポーネント（24-377行目） |

**主要処理フロー**:
1. **25-26行目**: URLパラメータとナビゲーション状態の取得
2. **28-36行目**: 状態管理（展開、読み込み状態）
3. **38-47行目**: `useReplyChainData` でデータ取得
4. **54-62行目**: 親投稿がある場合の自動スクロール
5. **64-100行目**: トップレベル返信の追加読み込み（IntersectionObserver）

#### Step 3: 表示ロジックを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | note.tsx | `apps/activitypub/src/views/feed/note.tsx` | 表示ロジック（189-373行目） |

**主要処理フロー**:
- **195-237行目**: 親投稿（threadParents）の表示
- **238-256行目**: メインノートの表示
- **253-256行目**: APReplyBoxの配置
- **259-357行目**: 返信スレッドの階層的表示

#### Step 4: チェーン展開ロジックを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | note.tsx | `apps/activitypub/src/views/feed/note.tsx` | toggleChain, loadMoreForChain（149-187行目） |

**主要処理フロー**:
- **149-164行目**: `toggleChain` - チェーン展開/折りたたみ
- **166-187行目**: `loadMoreForChain` - チェーン内の追加返信読み込み

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

```
Note (note.tsx)
    │
    ├─ useParams() → postId取得
    │
    ├─ useReplyChainData(postId, {includeAncestors: true})
    │      ├─ threadParents: Activity[] (祖先)
    │      ├─ post: Activity (対象ノート)
    │      ├─ processedReplies: ReplyGroup[] (返信)
    │      ├─ loadMoreChildren: () => void
    │      └─ loadMoreChildReplies: (index) => void
    │
    ├─ Layout
    │
    ├─ threadParents.map()
    │      └─ FeedItem (祖先ノート)
    │             └─ DeletedFeedItem (削除済みの場合)
    │
    ├─ FeedItem (メインノート)
    │      └─ layout='modal', showStats=true
    │
    ├─ APReplyBox
    │      └─ 返信投稿
    │
    ├─ processedReplies.map()
    │      ├─ FeedItem (メイン返信)
    │      ├─ FeedItem (チェーン返信[0])
    │      ├─ expandedChains.has(chainId)
    │      │      └─ FeedItem[] (残りのチェーン返信)
    │      ├─ ShowRepliesButton (expand)
    │      └─ ShowRepliesButton (loadMore)
    │
    └─ IntersectionObserver
           └─ loadMoreChildren (トップレベル追加読み込み)
```

### データフロー図

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

URL postId ─────────▶ decodeURIComponent
                            │
                            └─ useReplyChainData
                                   │
                                   ├─ GET /posts/{id}/replies
                                   │      └─ includeAncestors=true
                                   │
                                   └─ データ整理
                                          │
                                          ├─ threadParents ────▶ 祖先表示
                                          │
                                          ├─ currentPost ──────▶ メインノート表示
                                          │
                                          └─ processedReplies ─▶ 返信スレッド表示

チェーン展開 ───────▶ toggleChain(chainId)
                            │
                            └─ expandedChains更新 ────────▶ 展開表示

追加読み込み ───────▶ loadMoreForChain / loadMoreChildren
                            │
                            └─ API呼び出し ─────────────▶ 追加返信表示
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| note.tsx | `apps/activitypub/src/views/feed/note.tsx` | ソース | Noteメインコンポーネント |
| use-reply-chain-data.ts | `apps/activitypub/src/hooks/use-reply-chain-data.ts` | ソース | リプライチェーンデータフック |
| feed-item.tsx | `apps/activitypub/src/components/feed/feed-item.tsx` | ソース | フィードアイテム |
| deleted-feed-item.tsx | `apps/activitypub/src/components/feed/deleted-feed-item.tsx` | ソース | 削除済みアイテム |
| ap-reply-box.tsx | `apps/activitypub/src/components/global/ap-reply-box.tsx` | ソース | 返信入力ボックス |
| show-replies-button.tsx | `apps/activitypub/src/components/global/show-replies-button.tsx` | ソース | 返信展開ボタン |
| routes.tsx | `apps/activitypub/src/routes.tsx` | ソース | ルーティング定義 |
