「公開を遅延」する機能はもともとあったけど、予約投稿もどうせなら欲しいなと思って実装してみた。しかし思ったよりも大変だった。

考えかたとして「予約投稿」で指定した日付に新規投稿されるだけだから、そんなに難しくないのだが、再編集を考慮しはじめると一気に複雑性が増す。

# エントリのステータスとパス(URL)決定ロジック

本プロジェクトにおけるエントリのステータス管理は、「URL(パス)がいつ確定するか」という一点において区別されます。

## 1. コンセプト:URLの確定タイミング

| ステータス | 役割 | URLの確定タイミング |
| :--- | :--- | :--- |
| **`public`** | 即時公開 | **保存時**に確定する。 |
| **`scheduled`** | 公開を遅延 | **保存時**に確定する(公開まで非表示なだけ)。 |
| **`reserved`** | 予約投稿 | **公開当日**の最新番号で確定する(未来の新着として扱うため)。 |

*   **`scheduled`** は「URLを今日の日付で固定したまま、公開だけを未来(30日後など)にしたい」場合に使用します。`public` と同様の扱いです。
*   **`reserved`** は「公開されるその日の新着記事として予約したい」場合に使用します。公開されるまで URL は未確定です。

## 2. URLの不変原則

一度確定した URL (`YYYY/MM/DD/N`) は、**原則として変更されません**。
本文の修正、`public``scheduled` の切り替え、あるいは一旦 `draft`(下書き)に戻して再公開した場合でも、最初期に確定した URL が維持されます。

### 唯一の例外:`reserved` による「未来への移動」
公開済みの記事を `reserved` に変更して未来の日付を指定した場合のみ、URL が変更(再採番)されます。これは、その記事を未来の「新着記事」として扱い直すための特別な操作です。

## 3. 状態遷移の基本ルール

| 行先ステータス | パス (`path`) の扱い |
| :--- | :--- |
| **`draft`** | 既存パスがあれば維持。なければ空。 |
| **`public`** | 既存パスがあれば維持。なければ**保存時**に採番。 |
| **`scheduled`** | 既存パスがあれば維持。なければ**保存時**に採番。 |
| **`reserved`** | 既存パスがあれば維持。ただし無視し、**公開時**にその日の最新番号で採番。 |

## 4. `date` との関係

`path``YYYY/MM/DD/N` の場合、 `date``YYYY-MM-DD` となる

テストも全遷移を一応テストさせている。これに加えて不安なシナリオのテストもある…… golang だと fmt が自動でかかるので空白いれてテーブルテストを成形するのが難しい。しょうがないので文字数をあわせてるけど、LLMはこの手の操作が死ぬほど下手なのがつらい。もっと良い方法あるのだろうか……

	tests := []struct {
		ST        Transition
		SavedPath string
		PubAt     time.Time
		WantStat  string
		WantPath  string
		Name      string
	}{
		//    Transition        SavedPath  PubAt    WantStat  WantPath
		// --- New Entry ---
		{FromTo(NONE_, DRAFT), _____, ZERO_, DRAFT, EMPT_, "New -> Draft"},
		{FromTo(NONE_, PUBLI), _____, ZERO_, PUBLI, GENT_, "New -> Public"},
		{FromTo(NONE_, PUBLI), _____, FUTUR, PUBLI, GENT_, "New -> Public (Future Ignored)"},
		{FromTo(NONE_, SCHED), _____, FUTUR, SCHED, GENT_, "New -> Scheduled"},
		{FromTo(NONE_, RESER), _____, FUTUR, RESER, EMPT_, "New -> Reserved"},

		// --- From Draft ---
		{FromTo(DRAFT, DRAFT), _____, ZERO_, DRAFT, EMPT_, "Draft -> Draft"},
		{FromTo(DRAFT, PUBLI), _____, ZERO_, PUBLI, GENT_, "Draft -> Public"},
		{FromTo(DRAFT, SCHED), _____, FUTUR, SCHED, GENT_, "Draft -> Scheduled"},
		{FromTo(DRAFT, SCHED), _____, FUTUR, SCHED, TODAY + "/1", "Draft -> Scheduled (Path is Today, not Future)"},
		{FromTo(DRAFT, RESER), _____, FUTUR, RESER, EMPT_, "Draft -> Reserved"},

		// --- From Public (URL Immutability) ---
		{FromTo(PUBLI, DRAFT), P2025, ZERO_, DRAFT, KEEP_, "Public -> Draft (Keep URL)"},
		{FromTo(PUBLI, PUBLI), P2025, ZERO_, PUBLI, KEEP_, "Public -> Public (Keep URL)"},
		{FromTo(PUBLI, SCHED), P2025, FUTUR, SCHED, KEEP_, "Public -> Scheduled (Keep URL)"},
		{FromTo(PUBLI, RESER), P2025, FUTUR, RESER, KEEP_, "Public -> Reserved (Keep URL until publish)"},

		// --- From Scheduled (URL Immutability) ---
		{FromTo(SCHED, DRAFT), P2025, ZERO_, DRAFT, KEEP_, "Scheduled -> Draft (Keep URL)"},
		{FromTo(SCHED, PUBLI), P2025, ZERO_, PUBLI, KEEP_, "Scheduled -> Public (Keep URL)"},
		{FromTo(SCHED, SCHED), P2025, FUTUR, SCHED, KEEP_, "Scheduled -> Scheduled (Keep URL)"},
		{FromTo(SCHED, RESER), P2025, FUTUR, RESER, KEEP_, "Scheduled -> Reserved (Keep URL until publish)"},

		// --- From Reserved ---
		{FromTo(RESER, DRAFT), _____, ZERO_, DRAFT, EMPT_, "Reserved -> Draft"},
		{FromTo(RESER, PUBLI), _____, ZERO_, PUBLI, GENT_, "Reserved -> Public"},
		{FromTo(RESER, SCHED), _____, FUTUR, SCHED, GENT_, "Reserved -> Scheduled"},
		{FromTo(RESER, RESER), _____, FUTUR, RESER, EMPT_, "Reserved -> Reserved"},

		// --- From Draft with Path (Previously Published) ---
		{FromTo(DRAFT, DRAFT), P2025, ZERO_, DRAFT, KEEP_, "Draft(P) -> Draft (Keep URL)"},
		{FromTo(DRAFT, PUBLI), P2025, ZERO_, PUBLI, KEEP_, "Draft(P) -> Public (Keep URL)"},
		{FromTo(DRAFT, SCHED), P2025, FUTUR, SCHED, KEEP_, "Draft(P) -> Scheduled (Keep URL)"},
		{FromTo(DRAFT, RESER), P2025, FUTUR, RESER, KEEP_, "Draft(P) -> Reserved (Keep URL until publish)"},
	}