# 画面設計書 72-Bluesky共有設定画面

## 概要

本ドキュメントは、ActivityPub連携機能におけるBluesky共有設定画面の設計書です。

### 本画面の処理概要

この画面では、Blueskyとの連携を設定し、Ghost上のコンテンツをBlueskyへ自動共有する機能を有効化・無効化できます。Bridgy Fedサービスを経由してActivityPubプロフィールをBlueskyに接続します。

**業務上の目的・背景**：Ghostのソーシャルウェブ連携を拡張し、Blueskyプラットフォームのユーザーにもコンテンツを届けることで、より広いリーチを実現します。Blueskyは分散型SNSとして成長しており、Ghostサイトのコンテンツ配信チャネルとして重要な位置づけになっています。本画面により、サイト管理者はワンクリックでBluesky連携を有効化でき、専用のBlueskyプロフィールが自動生成されます。

**画面へのアクセス方法**：管理画面のActivityPubセクションから、設定画面（Preferences）を開き、「Bluesky sharing」リンクをクリックしてアクセスします。URLパスは `/activitypub/preferences/bluesky-sharing` です。

**主要な操作・処理内容**：
1. Bluesky共有機能の有効化
2. Bluesky共有機能の無効化
3. プロフィール画像が未設定の場合のプロフィール編集
4. Blueskyハンドルのコピー
5. 連携確認の自動ポーリング

**画面遷移**：
- 遷移元：ActivityPub設定画面（/activitypub/preferences）
- 遷移先：Blueskyプロフィールページ（外部リンク）
- 戻り先：ActivityPub設定画面

**権限による表示制御**：ActivityPub機能へのアクセス権限を持つ管理者ユーザーのみがこの画面にアクセス可能です。

## 関連機能

| 機能No | 機能名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 57 | ActivityPub | 主機能 | Bluesky連携の設定 |

## 画面種別

設定（Bluesky連携の有効化/無効化）

## URL/ルーティング

- パス: `/activitypub/preferences/bluesky-sharing`
- ルート定義: `apps/activitypub/src/routes.tsx` (L104-108)

## 入出力項目

| 項目名 | 入出力 | データ型 | 必須 | 説明 |
|--------|--------|----------|------|------|
| account | 出力 | Account | - | 現在のユーザーアカウント情報 |
| blueskyEnabled | 出力 | boolean | - | Bluesky共有の有効状態 |
| blueskyHandle | 出力 | string | - | 生成されたBlueskyハンドル |
| blueskyHandleConfirmed | 出力 | boolean | - | ハンドルの確認完了状態 |
| avatarUrl | 出力 | string | - | アバター画像URL |

## 表示項目

### 未有効化状態（プロフィール画像なし）

| 項目名 | データ型 | 説明 |
|--------|----------|------|
| 説明テキスト | string | プロフィール画像追加を促すメッセージ |
| Edit profileボタン | - | プロフィール編集ダイアログを開く |

### 未有効化状態（プロフィール画像あり）

| 項目名 | データ型 | 説明 |
|--------|----------|------|
| 説明テキスト | string | Bluesky連携の説明とBridgy Fedへのリンク |
| ハンドル変更不可の注意 | string | ハンドルが変更できない旨の警告 |
| Enable Bluesky sharingボタン | - | 有効化ボタン |

### 有効化済み状態

| 項目名 | データ型 | 説明 |
|--------|----------|------|
| Enabledステータス | - | 有効状態を示すバッジ（緑点） |
| Disableボタン | - | 無効化ボタン（ホバー時表示） |
| アバター | image | Blueskyアイコン付きアバター |
| 名前 | string | ユーザー名 |
| Blueskyハンドル | string | @付きのハンドル |
| コピーボタン | - | ハンドルをクリップボードにコピー |
| Open profileボタン | - | Blueskyプロフィールを開く（外部リンク） |

## イベント仕様

### 1-Bluesky共有有効化

- トリガー: 「Enable Bluesky sharing」ボタンクリック
- 前提条件: アバター画像が設定済み
- 処理:
  1. ローディング状態を開始
  2. `enableBlueskyMutation.mutateAsync()` を実行
  3. 5秒間隔でハンドル確認をポーリング（最大12回 = 60秒）
  4. 確認成功時にトースト「Bluesky sharing enabled」を表示
  5. タイムアウト時にエラートースト「Something went wrong, please try again.」を表示し自動無効化

### 2-Bluesky共有無効化

- トリガー: 確認ダイアログで「Disable」ボタンクリック
- 処理:
  1. ローディング状態を開始
  2. `disableBlueskyMutation.mutateAsync()` を実行
  3. 成功時にトースト「Bluesky sharing disabled」を表示
  4. 確認ダイアログを閉じる

### 3-プロフィール編集ダイアログ表示

- トリガー: アバター未設定時に「Enable Bluesky sharing」クリック、または「Edit profile」ボタンクリック
- 処理: プロフィール編集ダイアログを表示

### 4-ハンドルコピー

- トリガー: コピーボタンクリック
- 処理:
  1. `navigator.clipboard.writeText(account.blueskyHandle)` を実行
  2. アイコンをチェックマークに変更（2秒間）

### 5-Blueskyプロフィールを開く

- トリガー: 「Open profile」ボタンクリック
- 処理: `https://bsky.app/profile/{handle}` を新規タブで開く

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

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

| 操作（イベント） | 対象テーブル | 操作種別 | 概要 |
|----------------|-------------|---------|------|
| Bluesky有効化 | (ActivityPub API) | POST | Bridgy Fed経由でBlueskyアカウント作成 |
| Bluesky無効化 | (ActivityPub API) | DELETE | Bluesky連携の解除 |
| ハンドル確認 | (ActivityPub API) | GET | ハンドル確認状態の取得 |

※ 実際のデータはActivityPub APIサーバー側およびBridgy Fedで管理

## メッセージ仕様

| 種別 | メッセージ | 表示タイミング |
|------|-----------|---------------|
| 成功 | Bluesky sharing enabled | Bluesky共有有効化成功時 |
| 成功 | Bluesky sharing disabled | Bluesky共有無効化成功時 |
| エラー | Something went wrong, please try again. | 有効化時のタイムアウト、または有効化失敗時 |
| 情報 | Add a profile image to connect to Bluesky. Profile pictures help prevent spam. | アバター未設定時 |
| 情報 | You can't change your Bluesky username, so make sure you're happy with your current social web handle before connecting. | 有効化前の警告 |
| 情報 | You can leave this page and come back to check the status. | 有効化処理中のヒント |

### 確認ダイアログ

| 項目 | 内容 |
|------|------|
| タイトル | Disable Bluesky sharing? |
| 説明 | Your bridged Bluesky account will be deactivated and your content will no longer be shared on Bluesky. You can re-enable sharing at any time. |
| キャンセルボタン | Cancel |
| 確定ボタン | Disable |

## 例外処理

| 例外状況 | 対応内容 |
|---------|---------|
| アカウント情報読み込み中 | LoadingIndicator（中央表示） |
| アバター画像が未設定 | プロフィール編集を促すメッセージとダイアログを表示 |
| 有効化処理タイムアウト（60秒） | エラートースト表示、自動的に無効化 |
| 有効化失敗 | エラートースト表示 |

## 備考

- Bridgy Fedサービス（https://fed.brid.gy）を経由してBluesky連携を実現
- ハンドル確認は5秒間隔で最大12回（60秒）ポーリング
- 連携後はBlueskyハンドルの変更不可（ソーシャルウェブハンドルに基づく）
- 投稿はBridgy Fed経由で短い遅延後に自動同期

---

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

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

### 推奨読解順序

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

まず、プログラム間で受け渡されるデータ構造を理解することが重要です。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | activitypub.ts | `apps/activitypub/src/api/activitypub.ts` | Account型のbluesky関連プロパティを確認 |

**読解のコツ**: `blueskyEnabled`, `blueskyHandle`, `blueskyHandleConfirmed` の3つのプロパティが状態管理の鍵です。

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

処理の起点となるファイル・関数を特定します。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | bluesky-sharing.tsx | `apps/activitypub/src/views/preferences/components/bluesky-sharing.tsx` | メインコンポーネントの構造 |
| 2-2 | routes.tsx | `apps/activitypub/src/routes.tsx` | ルーティング定義（L104-108） |

**主要処理フロー**:
1. **L27-28**: 定数定義（CONFIRMATION_INTERVAL = 5000ms, MAX_RETRIES = 12）
2. **L31**: `useAccountForUser` でアカウント情報取得
3. **L38-40**: Bluesky関連のmutation hookを初期化
4. **L48-60**: `handleEnable` - 有効化処理（アバターチェック含む）
5. **L62-71**: `handleDisable` - 無効化処理
6. **L73-80**: `confirmHandle` - ハンドル確認処理
7. **L82-127**: useEffect - ポーリングによる確認ロジック

#### Step 3: 状態管理フローを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | bluesky-sharing.tsx | `apps/activitypub/src/views/preferences/components/bluesky-sharing.tsx` | useEffectのポーリングロジック（L82-127） |

**主要処理フロー**:
- **L82-89**: blueskyが無効の場合のリセット処理
- **L91-101**: 確認完了時の成功処理
- **L103-127**: 確認未完了時のポーリング開始

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

```
BlueskySharing Component
    |
    +-- useAccountForUser('index', 'me')
    |       +-- ActivityPubAPI.getAccount('me')
    |
    +-- handleEnable()
    |       +-- (avatarUrl check)
    |       |       +-- (if no avatar) setIsEditingProfile(true)
    |       |
    |       +-- enableBlueskyMutation.mutateAsync()
    |               +-- ActivityPubAPI.enableBluesky()
    |
    +-- useEffect (polling)
    |       +-- setInterval(5000ms)
    |               +-- confirmBlueskyHandleMutation.mutateAsync()
    |                       +-- ActivityPubAPI.confirmBlueskyHandle()
    |
    +-- handleDisable()
            +-- disableBlueskyMutation.mutateAsync()
                    +-- ActivityPubAPI.disableBluesky()
```

### データフロー図

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

Enable Bluesky クリック
  |
  +-- avatarUrlチェック --> 未設定 --> Edit Profile Dialog
  |
  +-- 設定済み ----------> enableBlueskyMutation ----------> ローディング表示
                                |
                                +-- ポーリング開始（5秒間隔）
                                        |
                                        +-- confirmHandle成功 --> 成功toast
                                        |                         プロフィール表示
                                        |
                                        +-- 12回失敗 ----------> エラーtoast
                                                                  自動無効化
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| bluesky-sharing.tsx | `apps/activitypub/src/views/preferences/components/bluesky-sharing.tsx` | ソース | メインコンポーネント |
| routes.tsx | `apps/activitypub/src/routes.tsx` | ソース | ルーティング定義 |
| use-activity-pub-queries.ts | `apps/activitypub/src/hooks/use-activity-pub-queries.ts` | ソース | API呼び出しフック |
| activitypub.ts | `apps/activitypub/src/api/activitypub.ts` | ソース | APIクライアント |
| edit-profile.tsx | `apps/activitypub/src/views/preferences/components/edit-profile.tsx` | ソース | プロフィール編集コンポーネント |
| ap-avatar.tsx | `apps/activitypub/src/components/global/ap-avatar.tsx` | ソース | アバターコンポーネント |
| Layout.tsx | `apps/activitypub/src/components/layout/index.tsx` | ソース | レイアウトコンポーネント |
