# 1.9.0 — `sync-timelines`: repair a stale CapCut timeline mirror

Minor release. One new command, zero changes to existing behavior. Where v1.8.0's
`validate` *detects* a stale timeline mirror (`timelines.divergence`),
`sync-timelines` *fixes* it.

## The problem it solves

Once you open a draft in CapCut, CapCut writes `<draft>/Timelines/<guid>/draft_content.json`
as **its** timeline source of truth. If you then re-patch the root `draft_content.json`
from the CLI (restyle, import-captions, …), the root and that mirror **diverge** — and
CapCut renders the stale mirror, so your font/caption changes silently don't show. The
1.6.0 rule is "keep CapCut closed through the whole CLI chain"; when that rule was
violated, the only fix was to hand-copy the root back into the mirror. `sync-timelines`
automates that.

## Highlights

- 🔁 **`capcut-david sync-timelines <project>`** — copies the root `draft_content.json`
  (RAW bytes) into every `Timelines/<guid>/draft_content.json` + `template-2.tmp`. Root
  and mirrors agree again; reopen CapCut and your edits show.
- 🧭 **Deterministic direction: always root → mirror**, never inferred from mtime
  (cp/cloud/AV rewrite mtimes). The root is the source of truth; the mirror is the stale copy.
- 🛟 **Data-loss nets (apply-by-default):**
  - In `WRITE_COMMANDS` → the "CapCut is open" guard refuses while CapCut runs (`--force`).
  - **Timestamped, non-clobbering** backups `…synced-<epoch>.bak` before every overwrite —
    a 2nd run never destroys the 1st undo.
  - **Skip-when-byte-identical** — a converged draft writes zero bytes (mtime unchanged).
  - A **stderr WARNING** on each divergent overwrite: confirm CapCut has been closed since
    the draft was *modified*, not just since this command.
- 🔒 **Never touches:** the root `draft_content.json` (read-only source), the root
  `draft_content.json.bak` (`saveDraft`'s own rollback), the patch journals
  (`mini_draft.json` / `patch.json`), `draft_meta_info.json`, `key_value.json`, `Resources/`.
- 🧪 **350 tests** (+31). `sync-timelines.js` 98.6% lines / 100% functions. Typecheck + lint clean.

## Usage

```
$ capcut-david sync-timelines my-draft            # repair, JSON report, exit 0
$ capcut-david sync-timelines my-draft --dry-run  # report what WOULD change, write nothing
$ capcut-david sync-timelines my-draft -q         # exit code only
$ capcut-david sync-timelines my-draft -H         # human table
```

Typical pairing with the v1.8.0 detector:

```
$ capcut-david validate my-draft --check-timelines      # detect divergence
$ capcut-david sync-timelines my-draft                  # fix it (CapCut closed)
```

JSON envelope (`capcut-david/sync-timelines@1`):

```json
{
  "schema": "capcut-david/sync-timelines@1",
  "ok": true,
  "dry_run": false,
  "project": "C:\\...\\My Draft",
  "draft_file": "C:\\...\\My Draft\\draft_content.json",
  "synced": [
    { "guid": "<uuid>", "diverged": true,
      "files_written": ["Timelines/<uuid>/draft_content.json", "Timelines/<uuid>/template-2.tmp"],
      "backups": ["Timelines/<uuid>/draft_content.json.synced-1733500000000.bak"] }
  ],
  "already_in_sync": [],
  "root_siblings_written": ["template-2.tmp"],
  "summary": { "guids_total": 1, "guids_synced": 1, "files_written": 3, "files_skipped": 0 }
}
```

**Exit codes:** `0` = synced (or nothing to sync), `1` = tool failure. No exit 2.

## On data loss (read this once)

`sync-timelines` overwrites the mirror with the root, unconditionally for any divergent
guid. The one dangerous scenario: you opened CapCut, **edited in-app, closed**, then ran
`sync-timelines` — your newer in-app edits live in the mirror, and they get overwritten by
the older root. The CapCut-closed guard does **not** catch this (CapCut is closed). That's
why every overwrite makes a timestamped `…synced-<epoch>.bak` you can restore, and why the
command prints a `WARNING` line per divergent overwrite. Within the intended 1.6.0 workflow
(CapCut closed through the whole CLI chain), the mirror is never the newer copy, so this is
an out-of-workflow edge — but the backup is there if you hit it.

## Migration

**None.** `sync-timelines` is additive; no existing command changes output.

## Compatibility

- CapCut ≥ 5.x desktop (Windows + macOS), JianYing 6+ unsupported — unchanged.
- Node `>= 18` — unchanged.
- Runtime dependencies: zero — unchanged.

## Roadmap (1.x — non-binding)

- `1.x.0` — `gc` (sweep orphan materials), `init-meta` (generate `draft_meta_info.json`)
- `1.x.0` — `capcut-david query` (catalogue lookup)
- `1.x.0` — `validate --fix` umbrella over the per-finding fixers
