Publish System
BluePeriodのPublish(公開・出版)システムのアーキテクチャ、状態管理、データフロー、多言語対応を網羅的に解説
Publish System (22_publish_system.md)
1. 概要
Publishシステムは、ユーザーが執筆した作品をBluePeriodプラットフォーム上で公開するための一連のフローを提供します。ウィザード形式の4タブUI(Metadata → Localization → Quality → Final)により、メタデータ入力、多言語ローカライズ、AI品質レビュー、最終確認を段階的に進めます。
主な役割:
- 作品メタデータ(タイトル、見出し、あらすじ、タグ、表紙画像)の入力とAI生成
- 多言語ローカライゼーション(メタデータ翻訳 + プロット構造翻訳)
- AI品質レビューによる公開可否判定
- Supabase Storage / Databaseへのコンテンツ保存
- 公開後のメタデータ編集・非公開化
2. 画面構成とタブ構造
2.1. ルーティング
Publish画面は /project/[id]/publish に配置されています。
- Server Component: 認証チェック、既存公開データの取得
- Client Component: タブ切り替え、状態管理、ウィザードUI
2.2. タブ構成
| タブ | TabId | 役割 | 完了条件 |
|---|---|---|---|
| Metadata | metadata | 表紙画像、タイトル、見出し、あらすじ、タグの入力。AI生成機能あり | 原語のtitleとsynopsisが空でない |
| Localization | localization | 多言語メタデータとプロット構造の翻訳 | 原語が英語の場合は自動完了。それ以外は英語翻訳のtitle/synopsisが空でない |
| Quality | quality | AIによる作品品質レビュー | レビュー結果でis_publishableがtrue |
| Final | final | 公開先選択(新規/既存スロット)、最終プレビュー、公開実行 | 上記3タブがすべて完了 |
タブステータスは getTabStatus() で判定され、UI上に ✅(complete)/ ⚠(incomplete)として表示されます。
2.3. タブコンポーネント
MetadataTab
- Props:
titleOriginal,headlineOriginal,synopsisOriginal,tags,coverImagePreview,metadataProposals,isGenerating,isUpdateMode等 - 機能:
- ドラッグ&ドロップによる表紙画像アップロード
- AI メタデータ生成(OpenRouter, Anthropic, Gemini, Zai, Ollama, LM Studio対応)
- 生成結果の提案カードからワンクリック適用
LocalizationTab
- Props:
localizations,originalLanguage,plotTranslations,structuredPlot,manuscriptLanguages,isTranslating,translationInstructions等 - 機能:
- 言語ステータス可視化(Complete / Partial / Recommended / Available)
- AI翻訳(カスタム指示対応)
- プロット構造翻訳(Part/Arc/Chapter/Storyのタイトル・目的の翻訳)
- デスクトップ(マスター・ディテール)とモバイル(コンパクト)のレイアウト切替
- 言語検索機能
- 英語以外の原語の場合、英語翻訳が必須
QualityTab
- Props:
report,reportOriginal,isLoading,error,geminiApiKey等 - 機能:
- デュアルモードレビュー(Global English / Original Language)
- 公開可否判定と詳細レーティング
- Gemini APIキー設定
- 再生成機能
FinalTab
- Props:
isAuthenticated,title,headline,synopsis,tags,coverImagePreview,publishId,isNewPublishSlot,destinationSlots等 - 機能:
- 英語/原語のサイドバイサイドプレビュー
- 公開先選択(新規スロット / 既存スロット)
- 認証状態の確認
- 公開済みメタデータの取得・表示
3. 状態管理
3.1. Jotai Atoms
Publishウィザードの状態は publishStateAtoms.ts で一元管理されています。
export type TabId = "metadata" | "localization" | "quality" | "final";
export type TabStatus = "complete" | "incomplete";
export interface PublishState {
projectId: string | null;
publishId: string | null;
isNewPublishSlot: boolean;
activeTab: TabId;
reviewReport: ReviewReport | null;
reviewReportOriginal: ReviewReport | null;
originalLanguage: string;
localizations: Record<string, ProjectLocalization>;
coverImagePreview: string | null;
metadataProposals: GeneratedMetadata[];
translationInstructions: string;
}publishStateAtom:atomWithStorageで localStorage に永続化。キーはpublish:wizardStateresetPublishStateAtom: 状態をinitialPublishStateにリセットmyPublishedSlotsAtom: ユーザーの既存公開スロット一覧fetchMyPublishedSlotsAtom:/api/projects/my-publishedからスロットを取得
3.2. タブステータス判定
getTabStatus(tab, state) は各タブの完了状態を判定します:
- metadata: 原語の
localizations[originalLanguage]でtitleとsynopsisが非空 - localization: 原語が
enなら自動完了。それ以外はlocalizations["en"]のtitle/synopsisが非空 - quality:
reviewReport.overall_assessment.is_publishableが truthy - final: 上記3タスクがすべて complete
getAllTabStatuses(state) は全タブのステータスを一括取得します。
3.3. マイグレーション
migratePublishState() は旧フォーマット(titleOriginal, synopsisOriginal 等の個別フィールド)から新フォーマット(localizations マップ)への移行を custom storage の getItem 時に自動実行します。
旧フォーマットの検出条件: parsed.localizations がオブジェクトでない場合。
4. データモデル
4.1. ProjectLocalization
export interface ProjectLocalization {
title: string;
headline: string;
synopsis: string;
tags: string[];
}各言語のメタデータを保持する単位。localizations: Record<string, ProjectLocalization> の形で、BCP 47言語タグをキーとするマップとして管理されます。
4.2. PublishRequestData
export interface PublishRequestData {
id: string;
title: string;
headline: string;
synopsis: string;
original_language?: string;
localizations?: Record<string, ProjectLocalization>;
content: ProjectContent;
review_report?: Json;
cover_image_url?: string | null;
}Publish APIに送信されるデータ構造。localizations JSONBが唯一の多言語メタデータソース。title/headline/synopsisはトップレベルのフォールバック値(英語)として保持。
4.3. ProjectContent
export interface ProjectContent {
structuredPlot: StructuredPlot;
manuscripts: Manuscript;
writingPrompts?: PromptCard[];
plotTranslations?: {
[lang: string]: {
[id: string]: PlotTranslation;
};
};
}4.4. PlotTranslation
export interface PlotTranslation {
title?: string; // Part/Arc/Chapter/Story のタイトル
purpose?: string; // 目的・テーマ
content?: string; // セクションの要約
}プロット構造の多言語翻訳データ。要素IDをキーとし、各言語ごとに管理されます。
4.5. PublishedSlot
export interface PublishedSlot {
id: string;
title: string;
headline: string | null;
synopsis: string | null;
tags: string[] | null;
cover_image_url: string | null;
published_at: string | null;
updated_at: string | null;
}ユーザーの既存公開スロット情報。FinalTabの公開先選択で使用されます。
4.6. published_projects テーブル(Supabase)
主要カラム:
| カラム | 型 | 説明 |
|---|---|---|
id | UUID (PK) | プロジェクトID |
user_id | UUID (FK → auth.users) | 所有者 |
title | TEXT | グローバル(英語)タイトル |
headline | TEXT | グローバル見出し |
synopsis | TEXT | グローバルあらすじ |
tags | TEXT[] | タグ配列(DB未DROP: コードからは完全に除去済み。localizations[lang].tags を使用) |
title_original | TEXT | 原語タイトル(DB未DROP: コードからは完全に除去済み。localizations[original_language].title を使用) |
headline_original | TEXT | 原語見出し(DB未DROP: コードからは完全に除去済み。localizations[original_language].headline を使用) |
synopsis_original | TEXT | 原語あらすじ(DB未DROP: コードからは完全に除去済み。localizations[original_language].synopsis を使用) |
original_language | TEXT | 原語のBCP 47言語タグ |
localizations | JSONB | 言語別メタデータマップ(唯一のメタデータソース) |
default_language | TEXT | デフォルト表示言語 |
display_title | TEXT | 検索・表示用タイトル |
cover_image_url | TEXT | 表紙画像URL |
content_path | TEXT | Storage内のコンテンツパス |
review_report | JSONB | AI品質レビュー結果 |
published_at | TIMESTAMPTZ | 公開日時 |
updated_at | TIMESTAMPTZ | 更新日時 |
インデックス:
idx_published_projects_display_title: GIN trigram(テキスト検索用)idx_published_projects_localizations: GIN(JSONBクエリ用)
RLS ポリシー:
- SELECT: 全ユーザー公開(
true) - INSERT/UPDATE/DELETE: 所有者のみ(
auth.uid() = user_id)
5. Localization Studio
5.1. 言語ステータス判定
getLanguageStatus() により、各言語のローカライズ完了度が4段階で判定されます:
| ステータス | 条件 | アイコン |
|---|---|---|
| Complete | title と synopsis が非空 | CheckCircle2 (text-success) |
| Partial | title または synopsis のいずれかが非空 | AlertCircle (text-warning) |
| Recommended | マニュスクリプトにその言語の原稿が存在する | Lightbulb (text-primary) |
| Available | 上記いずれにも該当しない | なし(デフォルト表示) |
原語の場合は Recommended 判定がスキップされ、Complete / Partial / Available の3段階となります。
5.2. supported-languages.json
public/languages/supported-languages.json に対応言語一覧が定義されています:
[
{
"code": "en",
"name": "English",
"nativeName": "English",
"uiSupported": true
},
{
"code": "ja",
"name": "Japanese",
"nativeName": "日本語",
"uiSupported": true
},
{
"code": "zh-Hans",
"name": "Chinese (Simplified)",
"nativeName": "简体中文",
"uiSupported": true
},
{
"code": "ko",
"name": "Korean",
"nativeName": "한국어",
"uiSupported": false
}
]uiSupported: true の言語はUI表示に最適化されています。
5.3. AI翻訳の統合
- AI翻訳は各AIプロバイダー(Gemini, OpenRouter等)を経由して実行
translationInstructionsによりユーザーがカスタム指示を追加可能- メタデータ翻訳とプロット構造翻訳の2系統が存在
- プロット翻訳は
updatePlotTranslation(lang, id, field, value)で要素単位に更新
5.4. レイアウト
- デスクトップ: マスター・ディテール形式(言語一覧 + 詳細編集パネル)
- モバイル: コンパクト表示(アコーディオン展開)
6. Publish実行フロー
6.1. 処理シーケンス
publishProject() 関数(lib/features/publish/service.ts)が実行する一連の処理:
1. 表紙画像アップロード
├─ file が存在する場合
│ ├─ covers バケットに {userId}/{projectId}.{ext} でアップロード
│ └─ 公開URL取得
└─ 既存 cover_image_url を使用
2. マニュスクリプトの順序整理
├─ reorderManuscriptsByPlot() で構造順に並び替え
└─ Parts → Arcs → Chapters → Stories → Sections の順でトラバース
3. コンテンツJSONのStorage保存
├─ {userId}/{projectId}.json として contents バケットにアップロード
├─ upsert: true(既存なら上書き)
└─ contentType: application/json
4. DB保存(upsert)
├─ 既存スロットの所有権確認
├─ localizations マップをそのまま使用(クライアント側で構築済み)
├─ display_title の決定: defaultLang → GLOBAL_LANGUAGE → title の順
└─ published_projects テーブルに upsert6.2. コンテンツ検証
validateProjectContent()(lib/features/publish/validate.ts)による検証:
| 検査項目 | レベル | 内容 |
|---|---|---|
| Zodスキーマ検証 | Error | ProjectContentの構造的妥当性 |
| プロット構造空チェック | Error | parts/chaptersのいずれも存在しない |
| 孤立原稿 | Warning | マニュスクリプトに存在するがプロットにないセクションID |
| 未執筆セクション | Warning | プロットに存在するがマニュスクリプトがないセクション |
| 空原稿 | Warning | 全バージョンが空のマニュスクリプト |
7. コンポーネント一覧
Publish画面構成コンポーネント
| コンポーネント | 場所 | 責務 |
|---|---|---|
| PublishPage | app/project/[id]/publish/page.tsx | Server Component。認証チェック、データ取得 |
| PublishWizard | components/publish/ | クライアント側ウィザード管理(タブ切替、フッター) |
| MetadataTab | components/publish/tabs/MetadataTab.tsx | メタデータ入力、AI生成、表紙画像 |
| LocalizationTab | components/publish/tabs/LocalizationTab.tsx | 多言語管理、AI翻訳、言語ステータス表示 |
| QualityTab | components/publish/tabs/QualityTab.tsx | AI品質レビュー、結果表示 |
| FinalTab | components/publish/tabs/FinalTab.tsx | 最終プレビュー、公開先選択、公開実行 |
| AiSettingsSheet | components/publish/ | AIプロバイダー設定シート |
components/MetadataEditModal.tsx |
MetadataEditModal(廃止済み)
公開済み作品のメタデータを編集するモーダル。Publishウィザードが上位互換として機能するため、2026-05-18に廃止。 メタデータ編集はPublishウィザード(/project/[id]/publish)経由で行う。
Unpublish
非公開化フロー:
- API:
DELETE /api/projects/:id/unpublish - Service:
unpublishProject(supabase, userId, projectId) - 確認ダイアログ付き
8. データフロー図
┌─────────────────────────────────────────────────────────────┐
│ Publish Wizard UI │
│ ┌──────────┐ ┌──────────────┐ ┌─────────┐ ┌──────────┐ │
│ │ Metadata │ │ Localization │ │ Quality │ │ Final │ │
│ │ Tab │→│ Tab │→│ Tab │→│ Tab │ │
│ └────┬─────┘ └──────┬───────┘ └────┬────┘ └────┬─────┘ │
│ │ │ │ │ │
└───────┼───────────────┼──────────────┼────────────┼─────────┘
│ │ │ │
▼ ▼ ▼ ▼
┌─────────────────────────────────────────────────────────────┐
│ publishStateAtom (Jotai) │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ PublishState { │ │
│ │ localizations: Record<string, ProjectLocalization> │ │
│ │ reviewReport: ReviewReport | null │ │
│ │ coverImagePreview: string | null │ │
│ │ ... │ │
│ │ } │ │
│ └───────────────────────┬───────────────────────────────┘ │
└──────────────────────────┼──────────────────────────────────┘
│ handlePublish()
▼
┌─────────────────────────────────────────────────────────────┐
│ POST /api/publish │
│ 1. 表紙画像 → covers バケット (Storage) │
│ 2. コンテンツ → contents バケット (Storage) │
│ 3. メタデータ → published_projects (Database) │
└─────────────────────────────────────────────────────────────┘9. API エンドポイント
| メソッド | パス | 説明 |
|---|---|---|
| POST | /api/publish | 作品公開(メインエンドポイント) |
| GET | /api/projects/my-published | 自分の公開済み作品一覧 |
| POST | /api/projects/status | 公開ステータス確認 |
| GET | /api/projects/:id/metadata | 公開済みメタデータ取得 |
| PATCH | /api/projects/:id/metadata | メタデータ更新 |
| GET | /api/projects/:id/content | コンテンツ取得 |
| DELETE | /api/projects/:id/unpublish | 非公開化 |
| POST | /api/ai/review | AI品質レビュー |
| POST | /api/ai/metadata-generate | AIメタデータ生成 |
| POST | /api/ai/metadata-translate | AI翻訳 |
10. Hooks
usePublishActions
hooks/usePublishActions.ts にPublish操作を集約したカスタムフック:
handleStartReview()- 品質レビュー開始handleGenerateMetadata()- AI メタデータ生成executeTranslation(targetLang)- 指定言語への翻訳実行handlePublish()- 公開実行selectDestination(id, isNew)- 公開先スロット選択saveMetadata()- ローカル状態を IndexedDB に保存
11. 関連ドキュメント
- 03_state_management - Jotai状態管理の全体設計
- 04_database_schema_supabase - Supabaseデータベーススキーマ
- 05_api_specification - API仕様全般
- 07_locale_system - BCP 47言語タグの管理方式
- 07_i18n - 国際化システム
- 09_design_system - デザインシステム
- 13_publish_components - Publish関連コンポーネント詳細