# 機能設計書：80-ActivityPub Admin

## 1. 概要

### 1.1 機能の目的
ActivityPub Admin（@tryghost/activitypub）は、GhostのActivityPub連携機能を管理するReactベースのマイクロフロントエンドアプリケーションです。Fediverse（分散型ソーシャルネットワーク）への接続、フォロー/フォロワー管理、通知、投稿（Note）の作成・閲覧などの機能を提供します。

### 1.2 機能の範囲
- **Reader（Inbox）**: フォロー中アカウントの記事を閲覧
- **Notes（Feed）**: 自分のノート投稿一覧・新規投稿
- **Notifications**: いいね、リプライ、リポスト、フォロー通知
- **Explore**: トピック別探索、おすすめアカウント
- **Profile**: 自分/他者のプロフィール、フォロー/フォロワー一覧
- **Preferences**: 設定、モデレーション、Bluesky連携
- **Onboarding**: 初回セットアップウィザード

### 1.3 関連画面
| 画面No | 画面名 | 関連度 |
|--------|--------|--------|
| 121-140 | ActivityPub管理画面 | 高 |

## 2. 機能種別
- 種別：管理画面（マイクロフロントエンド）
- 分類：管理画面
- カテゴリ：ソーシャル連携・コミュニティ

## 3. 入力仕様

### 3.1 アプリプロパティ
| プロパティ | 型 | 必須 | 説明 |
|-----------|-----|------|------|
| framework | TopLevelFrameworkProps | Yes | Ghost Admin APIアクセス設定 |
| activityPubEnabled | boolean | No | ActivityPub機能有効フラグ |

### 3.2 ノート投稿入力
| フィールド | 型 | 必須 | 説明 |
|-----------|-----|------|------|
| content | string | Yes | 投稿内容 |
| image | object | No | 添付画像 {url, altText} |

### 3.3 アカウント更新入力
| フィールド | 型 | 必須 | 説明 |
|-----------|-----|------|------|
| name | string | Yes | 表示名 |
| username | string | Yes | ユーザー名 |
| bio | string | Yes | 自己紹介 |
| avatarUrl | string | Yes | アバター画像URL |
| bannerImageUrl | string | Yes | バナー画像URL |

## 4. 出力仕様

### 4.1 レンダリング出力
- コンテナクラス: `.shade-activitypub`
- ShadeApp（新デザインシステム）使用
- ダークモード: 常にfalse（固定）

### 4.2 主要データ型
| 型 | 説明 |
|----|------|
| Post | 投稿（Note/Article）|
| Account | アカウント情報 |
| Notification | 通知（like/reply/repost/follow/mention） |
| Topic | トピック分類 |

## 5. 処理フロー

### 5.1 アプリケーション初期化フロー
```
1. index.tsx
   └─ AdminXApp, routes, FeatureFlagsProvider エクスポート

2. App (app.tsx:11-27)
   ├─ activityPubEnabled === false: null返却
   ├─ FrameworkProvider
   ├─ RouterProvider (prefix='/')
   ├─ FeatureFlagsProvider
   └─ ShadeApp → Outlet

3. routes.tsx
   └─ basePath: 'activitypub'
       ├─ reader → Inbox
       ├─ notes → Feed
       ├─ notifications → Notifications
       ├─ explore → Explore
       ├─ profile → Profile
       ├─ preferences → Preferences
       └─ welcome → Onboarding
```

### 5.2 ActivityPub API フロー
```
1. ActivityPubAPI クラス (api/activitypub.ts)
   ├─ getToken(): 認証トークン取得
   ├─ fetchJSON(): API呼び出し
   │   ├─ Authorization: Bearer ${token}
   │   └─ Accept: application/activity+json
   └─ 各エンドポイントメソッド

2. 主要エンドポイント
   ├─ フィード: .ghost/activitypub/v1/feed/*
   ├─ アクション: .ghost/activitypub/v1/actions/*
   ├─ アカウント: .ghost/activitypub/v1/account/*
   ├─ 通知: .ghost/activitypub/v1/notifications/*
   └─ Bluesky: .ghost/activitypub/v2/actions/bluesky/*
```

### 5.3 Inbox（Reader）フロー
```
1. Inbox (inbox.tsx)
   ├─ useState(topic): 'following' / 他トピック
   ├─ useInboxForUser(): フォロー中フィード
   ├─ useDiscoveryFeedForUser(): 探索フィード
   └─ InboxList: アクティビティ一覧表示

2. データ取得
   ├─ following: .ghost/activitypub/v1/feed/reader
   └─ topic: .ghost/activitypub/v1/feed/discover/{topic}
```

### 5.4 Notes（Feed）フロー
```
1. Feed (feed.tsx)
   ├─ useFeedForUser(): 自分のノートフィード
   ├─ useUserDataForUser(): ユーザーデータ
   └─ FeedList: 投稿一覧表示

2. データ取得
   └─ .ghost/activitypub/v1/feed/notes
```

## 6. ビジネスルール

### 6.1 機能有効化ルール
| ルールID | ルール内容 | 実装箇所 |
|----------|-----------|----------|
| BR-01 | activityPubEnabled=falseでnull返却 | app.tsx:12-14 |
| BR-02 | 404エラーかつtopic!='following'でトピック未存在 | inbox.tsx:17 |

### 6.2 アクションルール
| アクション | APIエンドポイント | 説明 |
|-----------|------------------|------|
| follow | POST /actions/follow/{username} | フォロー |
| unfollow | POST /actions/unfollow/{username} | フォロー解除 |
| like | POST /actions/like/{id} | いいね |
| unlike | POST /actions/unlike/{id} | いいね取消 |
| repost | POST /actions/repost/{id} | リポスト |
| derepost | POST /actions/derepost/{id} | リポスト取消 |
| reply | POST /actions/reply/{id} | 返信 |
| note | POST /actions/note | ノート投稿 |
| block | POST /actions/block/{id} | ブロック |
| unblock | POST /actions/unblock/{id} | ブロック解除 |
| blockDomain | POST /actions/block/domain/{domain} | ドメインブロック |

### 6.3 通知タイプ
| タイプ | 説明 |
|--------|------|
| like | いいねされた |
| reply | 返信された |
| repost | リポストされた |
| follow | フォローされた |
| mention | メンションされた |

## 7. データベース操作仕様

本機能はクライアントサイドアプリのため、ActivityPub APIを使用します。

### 7.1 主要APIエンドポイント
| カテゴリ | エンドポイント | 用途 |
|---------|---------------|------|
| フィード | GET /feed/reader | フォロー中記事 |
| フィード | GET /feed/notes | 自分のノート |
| フィード | GET /feed/discover/{topic} | 探索フィード |
| アクション | POST /actions/follow/{username} | フォロー |
| アクション | POST /actions/like/{id} | いいね |
| アクション | POST /actions/note | ノート投稿 |
| アカウント | GET /account/{handle} | アカウント情報 |
| アカウント | PUT /account | アカウント更新 |
| 通知 | GET /notifications | 通知一覧 |
| 通知 | GET /notifications/unread/count | 未読数 |
| 検索 | GET /actions/search | アカウント検索 |
| ブロック | GET /blocks/accounts | ブロック一覧 |
| ブロック | GET /blocks/domains | ドメインブロック一覧 |
| Bluesky | POST /v2/actions/bluesky/enable | Bluesky有効化 |

## 8. エラー処理

### 8.1 APIエラー型
```typescript
type ApiError = {
    message: string;
    statusCode: number;
    code?: string;
};
```

### 8.2 エラーハンドリング
```typescript
// isApiError関数で型ガード
if (error && isApiError(error)) {
    return <AppError errorCode={error.code} statusCode={error.statusCode}/>;
}
```

### 8.3 エラーコンポーネント
- `AppError`: 統一エラー表示コンポーネント
- ルートレベルでerrorElementとして設定

## 9. トランザクション仕様

本機能はクライアントサイドアプリのため、トランザクション管理はActivityPubサーバーが担当します。

## 10. パフォーマンス要件

### 10.1 遅延ロード
- 各ビュー（Inbox, Feed, Notifications等）は`lazyComponent`で遅延ロード
- Onboardingステップも個別に遅延ロード

### 10.2 ページネーション
- 全フィードで`next`カーソルベースのページネーション
- `fetchNextPage`で追加データ取得

### 10.3 Debounce
- use-debounce: 検索入力などでデバウンス処理

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

### 11.1 認証
- Ghost Admin APIセッションからトークン取得
- `Authorization: Bearer ${token}`ヘッダー

### 11.2 ActivityPub認証
```typescript
async getToken(): Promise<string | null> {
    const response = await this.fetch(this.authApiUrl);
    const json = await response.json();
    return json?.identities?.[0]?.token || null;
}
```

### 11.3 モデレーション機能
- アカウントブロック
- ドメインブロック
- ブロック一覧管理

### 11.4 Bluesky連携
- Bluesky有効化/無効化
- ハンドル確認

## 12. 備考

### 12.1 技術スタック
- React 18.3.1
- TypeScript
- Vite（ビルドツール）
- @tryghost/admin-x-framework（APIフック）
- @tryghost/shade（デザインシステム）
- @radix-ui/react-form（フォーム）
- clsx（クラス名結合）
- use-debounce（デバウンス）
- html2canvas-objectfit-fix（画像処理）

### 12.2 ルート構成
| パス | コンポーネント | 説明 |
|------|---------------|------|
| /activitypub/reader | Inbox | Reader（フォロー中記事） |
| /activitypub/reader/:postId | Inbox | 記事詳細 |
| /activitypub/notes | Feed | ノート一覧 |
| /activitypub/notes/:postId | Note | ノート詳細 |
| /activitypub/notifications | Notifications | 通知 |
| /activitypub/explore | Explore | 探索 |
| /activitypub/explore/:topic | Explore | トピック別探索 |
| /activitypub/profile | Profile | 自分のプロフィール |
| /activitypub/profile/likes | Profile | いいね一覧 |
| /activitypub/profile/following | Profile | フォロー中 |
| /activitypub/profile/followers | Profile | フォロワー |
| /activitypub/profile/:handle/:tab? | Profile | 他者プロフィール |
| /activitypub/preferences | Preferences | 設定 |
| /activitypub/preferences/moderation | Moderation | モデレーション |
| /activitypub/preferences/bluesky-sharing | BlueskySharing | Bluesky連携 |
| /activitypub/welcome | Onboarding | 初回セットアップ |

### 12.3 投稿タイプ
```typescript
enum PostType {
    Note = 0,    // ノート（短文投稿）
    Article = 1, // 記事
    Tombstone = 2 // 削除済み
}
```

---

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

### 推奨読解順序

#### 1. エントリーポイント
**ファイル**: `apps/activitypub/src/index.tsx`

```
index.tsx (1-7行目)
├─ './styles/index.css': スタイル読み込み
├─ AdminXApp: メインアプリ
├─ routes: ルート定義
├─ FeatureFlagsProvider: フィーチャーフラグ
└─ useNotificationsCountForUser: 通知数フック
```

#### 2. アプリケーション構造
**ファイル**: `apps/activitypub/src/app.tsx`

```
App (11-27行目)
├─ activityPubEnabled === false → return null
├─ FrameworkProvider
├─ RouterProvider (prefix='/')
├─ FeatureFlagsProvider
└─ ShadeApp (className='shade-activitypub', darkMode=false)
    └─ Outlet
```

#### 3. ルーティング設定
**ファイル**: `apps/activitypub/src/routes.tsx`

```
routes (13-142行目)
└─ path: basePath ('activitypub')
    ├─ index → Navigate to 'reader'
    ├─ reader → Inbox
    ├─ reader/:postId → Inbox
    ├─ notes → Feed
    ├─ notes/:postId → Note
    ├─ notifications → Notifications
    ├─ explore → Explore
    ├─ explore/:topic → Explore
    ├─ profile → Profile (+ likes/following/followers)
    ├─ profile/:handle/:tab? → Profile
    ├─ preferences → Preferences
    ├─ preferences/moderation → Moderation
    ├─ preferences/bluesky-sharing → BlueskySharing
    └─ welcome → Onboarding (step 1/2/3)
```

#### 4. ActivityPub API
**ファイル**: `apps/activitypub/src/api/activitypub.ts`

```
ActivityPubAPI (244-767行目)
├─ constructor(apiUrl, authApiUrl, handle, fetch)
├─ getToken() (252-260行目): 認証トークン取得
├─ fetchJSON() (263-307行目): API呼び出し
├─ アクション系:
│   ├─ follow/unfollow (345-354行目)
│   ├─ like/unlike (357-364行目)
│   ├─ repost/derepost (367-374行目)
│   ├─ reply (377-385行目)
│   ├─ note (387-395行目)
│   ├─ block/unblock (327-342行目)
│   └─ blockDomain/unblockDomain (309-324行目)
├─ フィード系:
│   ├─ getFeed (475-476行目)
│   ├─ getInbox (479-480行目)
│   └─ getDiscoveryFeed (483-485行目)
├─ アカウント系:
│   ├─ getAccount (437-441行目)
│   ├─ getAccountFollows (444-473行目)
│   └─ updateAccount (695-716行目)
├─ 通知系:
│   ├─ getNotifications (545-573行目)
│   ├─ getNotificationsCount (576-592行目)
│   └─ resetNotificationsCount (594-600行目)
└─ Bluesky系:
    ├─ enableBluesky (744-747行目)
    ├─ disableBluesky (750-753行目)
    └─ confirmBlueskyHandle (756-766行目)
```

#### 5. Inboxビュー
**ファイル**: `apps/activitypub/src/views/inbox/inbox.tsx`

```
Inbox (8-36行目)
├─ useState(topic): 'following' / 他トピック
├─ useInboxForUser(): フォロー中
├─ useDiscoveryFeedForUser(): 探索
├─ topicNotFound判定
└─ InboxList: 一覧表示
```

#### 6. Feedビュー
**ファイル**: `apps/activitypub/src/views/feed/feed.tsx`

```
Feed (10-32行目)
├─ useFeedForUser(): ノートフィード
├─ useUserDataForUser(): ユーザーデータ
└─ FeedList: 一覧表示
```

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

```
[Ghost Admin (Ember)]
    │
    ▼
AdminXComponent.ReactComponent()
    │
    └─ <AdminXApp framework={...} activityPubEnabled={...} />
           │
           └─ index.tsx::AdminXApp
                  │
                  └─ app.tsx::App
                         │
                         ├─ activityPubEnabled check
                         │
                         ├─ FrameworkProvider
                         │
                         ├─ RouterProvider
                         │   └─ routes.tsx
                         │       │
                         │       └─ /activitypub
                         │           ├─ reader → Inbox
                         │           ├─ notes → Feed
                         │           ├─ notifications → Notifications
                         │           ├─ explore → Explore
                         │           ├─ profile → Profile
                         │           ├─ preferences → Preferences
                         │           └─ welcome → Onboarding
                         │
                         └─ FeatureFlagsProvider
                             └─ ShadeApp

[Views]
    │
    ├─ Inbox
    │   ├─ useInboxForUser() → ActivityPubAPI.getInbox()
    │   └─ useDiscoveryFeedForUser() → ActivityPubAPI.getDiscoveryFeed()
    │
    ├─ Feed
    │   ├─ useFeedForUser() → ActivityPubAPI.getFeed()
    │   └─ useUserDataForUser() → ActivityPubAPI.getUser()
    │
    └─ ActivityPubAPI
        ├─ getToken() → authApiUrl
        └─ fetchJSON() → apiUrl/.ghost/activitypub/v1/*
```

### データフロー図

```
[Ghost Admin (Ember)]
    │
    ├── framework props
    ├── activityPubEnabled
    │
    ▼
[FrameworkProvider]
    │
    ▼
[RouterProvider]
    │
    ├── route params
    │
    ▼
[Views (Inbox/Feed/Notifications/etc.)]
    │
    ├── React Query Hooks
    │   ├── useInboxForUser()
    │   ├── useFeedForUser()
    │   ├── useUserDataForUser()
    │   └── useNotificationsForUser()
    │
    ▼
[ActivityPubAPI]
    │
    ├── getToken()
    │   └─ fetch(authApiUrl) → token
    │
    ├── fetchJSON(url, method, body)
    │   └─ Authorization: Bearer ${token}
    │   └─ Accept: application/activity+json
    │
    └── API Endpoints
        ├── GET /feed/reader → 記事フィード
        ├── GET /feed/notes → ノートフィード
        ├── GET /notifications → 通知
        ├── POST /actions/follow → フォロー
        ├── POST /actions/like → いいね
        ├── POST /actions/note → ノート投稿
        └── GET /account/{handle} → アカウント情報
```

### 関連ファイル一覧

| ファイルパス | 役割 | 重要度 |
|-------------|------|--------|
| apps/activitypub/src/index.tsx | エントリーポイント | 高 |
| apps/activitypub/src/app.tsx | アプリルート | 高 |
| apps/activitypub/src/routes.tsx | ルーティング定義 | 高 |
| apps/activitypub/src/api/activitypub.ts | APIクライアント | 高 |
| apps/activitypub/src/views/inbox/inbox.tsx | Reader（Inbox）ビュー | 高 |
| apps/activitypub/src/views/feed/feed.tsx | Notes（Feed）ビュー | 高 |
| apps/activitypub/src/views/notifications/notifications.tsx | 通知ビュー | 中 |
| apps/activitypub/src/views/explore/explore.tsx | 探索ビュー | 中 |
| apps/activitypub/src/views/profile/profile.tsx | プロフィールビュー | 中 |
| apps/activitypub/src/views/preferences/preferences.tsx | 設定ビュー | 中 |
| apps/activitypub/src/components/layout/layout.tsx | レイアウト | 中 |
| apps/activitypub/src/components/layout/sidebar/sidebar.tsx | サイドバー | 中 |
| apps/activitypub/src/components/feed/feed-item.tsx | フィードアイテム | 中 |
| apps/activitypub/src/components/global/follow-button.tsx | フォローボタン | 中 |
| apps/activitypub/src/hooks/use-activity-pub-queries.ts | APIフック | 高 |
| apps/activitypub/src/lib/feature-flags.tsx | フィーチャーフラグ | 低 |
| apps/activitypub/package.json | 依存関係定義 | 中 |
