# Journey version-history modal (list + restore)

## Goal

Add a "Version history" modal to the flow editor (`flow.jsx`) that lists a
journey's saved versions and lets the user restore one. Modal content lives in
a separate jsx file.

Builds on `2026-07-03-journey-editor-tracking-and-versions-design.md` (which
added `custom_journey_versions` + `last_edited_by`).

## Context

- Versions are snapshotted on every save via `CustomJourneyVersion::record()`.
  Current columns: `id, custom_journey_id, payload, edited_by, created_at`.
- The editor's save pipeline (`JourneyController::createJourney` / `createBlast`)
  regenerates workflows/templates from the **full request** (stored as
  `save_payload`), not just the flowchart `payload`. So a true restore must
  replay `save_payload`.
- Existing replay pattern: `saveOtherJourney()` decodes a stored `save_payload`,
  merges into a fresh `JourneySetupRequest`/`BlastSetupRequest` (with `id` set),
  and calls `createJourney`/`createBlast`. Restore reuses this.
- Modal styling pattern: `ConnectNodeModal` in `flow.jsx` (`config-modal-*`
  classes). Local component imports live under `resources/js/flow/components/`.
- Journey id is parsed from the URL in `flow.jsx` (`urlPath[1]`). Current editor
  user id is `window.__editorUserId` (set at wrapper read).
- Save routes are unauthenticated (`v1` group); editor identity is passed as
  `edited_by` in the body.

## Changes

### 1. Snapshot `save_payload` in versions
- Migration: add nullable `save_payload` (longText) to `custom_journey_versions`.
- `CustomJourneyVersion::record()`: also store `$journey->save_payload`.
- Existing rows (no `save_payload`) are **not restorable** — restore disabled.

`ponytail:` `payload` and `save_payload.raw` overlap (duplication accepted for a
simple audit trail). Add pruning/retention when growth matters.

### 2. List endpoint
`GET /api/v1/journeys/{id}/versions` (unauthenticated `v1` group):
- Returns the latest 50 versions, newest first, eager-loading
  `editor:id,name,masked_name`.
- Shape: `[{ id, created_at, editor: { name, masked_name }, restorable }]`
  where `restorable = !!save_payload`.

`ponytail:` capped at 50; add pagination only if journeys exceed it.

### 3. Restore endpoint
`POST /api/v1/journey-versions/{versionId}/restore` (unauthenticated `v1` group,
`edited_by` in body):
1. Load the version. If `save_payload` is empty → 422 "This version predates
   restore support."
2. Decode `save_payload`; set `id = custom_journey_id` and
   `edited_by = <request edited_by>`.
3. Pick `createBlast` vs `createJourney` from the journey's `is_blast`, build the
   matching request, replay it (regenerates journey + workflows + Meta sync).
4. The replay records a **new** version (the restore is itself a save).
5. Return `{ message, journey }`; frontend reloads the editor.

Restore reprocesses and re-syncs Meta — same side effects as a normal Save.

### 4. Modal component
New file `resources/js/flow/components/VersionHistoryModal.jsx`:
- Props: `{ journeyId, editedBy, onClose }`.
- On mount: `GET /api/v1/journeys/{journeyId}/versions`; loading + empty states.
- Row: `#<index> · <formatted created_at> · <editor.name || editor.masked_name || "—">`
  plus a **Restore** button (disabled when `!restorable`, with a hint).
- Restore: Swal confirm → `POST .../restore` with `{ edited_by }` → on success
  reload the editor; on error show a Swal.
- Styled with `config-modal-overlay` / `config-modal` (mirrors `ConnectNodeModal`),
  overlay click + close button dismiss.

### 5. `flow.jsx` wiring
- Import `VersionHistoryModal`.
- `showVersionsModal` state.
- "Version history" button in the top button-container next to "Save Journey";
  disabled when there is no journey id (unsaved new journey).
- Render `{showVersionsModal && <VersionHistoryModal journeyId={id}
  editedBy={window.__editorUserId} onClose={...} />}`.

## Edge cases
- Unsaved new journey (no id) → button disabled.
- Version without `save_payload` → listed, Restore disabled.
- Restore is destructive/reprocessing → Swal confirm before firing.

## Out of scope (`ponytail:`)
- Read-only "view JSON" of a version (payload is stored; add later).
- Version diffing.
- Retention/pruning.
