# SPEC-EPISODE-018: episode_title 仕様書

## SPEC-EPISODE-009: エピソードタイトル


## 1. 目的
エピソードのタイトルを表現する不変の値オブジェクト。各話に読者を惹きつけるタイトルを設定し、ファイルシステムでの保存にも対応する。

## 2. 前提条件
- DDD原則に基づく値オブジェクトとして実装
- 不変性（immutable）を保証
- ファイルシステムでの保存を考慮

## 3. 主要な振る舞い

### 3.1 タイトル文字列の制約
- **型**: 文字列（str）のみ
- **長さ**: 1文字以上100文字以下
- **空白**: 空文字列や空白のみは不可
- **禁止文字**: `\ / : * ? " < > |`（ファイルシステムで使用不可）
- **改行文字**: 改行文字（`\n`, `\r`）は不可

### 3.2 ファイル名安全変換
- 特殊文字を安全な文字に置換
- 置換ルール:
  - `：` → `_`
  - `、` → `_`
  - `。` → 削除
  - `！` → 削除
  - `？` → 削除
  - `　`（全角スペース） → `_`
  - ` `（半角スペース） → `_`

### 3.3 タイトル操作
- 前後の空白は保持（意図的な空白の可能性）
- 改行文字は許可しない

## 4. インターフェース仕様

```python
@dataclass(frozen=True)
class EpisodeTitle:
    """エピソードタイトルを表す値オブジェクト"""

    value: str  # 1-100文字の文字列

    def __post_init__(self) -> None:
        """バリデーション"""
        # 型チェック：文字列のみ許可
        # 空チェック：空文字列・空白のみ不可
        # 長さチェック：1-100文字
        # 禁止文字チェック

    def to_filename_safe(self) -> str:
        """ファイル名として安全な形式に変換"""

    def __str__(self) -> str:
        """文字列表現（元のタイトル）"""
```

## 5. エラーハンドリング

### 5.1 ValueError
- 文字列以外の値が渡された場合
- 空文字列または空白のみの文字列の場合
- 100文字を超える文字列の場合
- ファイルシステムで使用できない文字が含まれる場合

## 6. 使用例

```python
# 正常系
title = EpisodeTitle("始まりの朝")
assert str(title) == "始まりの朝"
assert title.to_filename_safe() == "始まりの朝"

title2 = EpisodeTitle("第1話　特別な一日！")
assert title2.to_filename_safe() == "第1話_特別な一日"

# エラー系
EpisodeTitle("")          # ValueError: タイトルは空にできません
EpisodeTitle("   ")       # ValueError: タイトルは空にできません
EpisodeTitle("a" * 101)   # ValueError: 100文字以内である必要があります
EpisodeTitle("危険な/タイトル")  # ValueError: 使用できない文字が含まれています
```

## 7. 実装メモ
- テストファイル: `tests/unit/domain/value_objects/test_episode_title.py`
- 実装ファイル: `scripts/domain/value_objects/episode_title.py`
- 作成日: 2025-01-21
- 更新日: 2025-07-23 (実装との整合性確認・更新)

## 8. 未決定事項
- [ ] 絵文字・特殊記号の扱い
- [ ] タイトルの正規化（全角・半角統一など）
- [ ] サブタイトル対応（「第1話　始まりの朝 〜新たな旅立ち〜」）
