# 1.4.0 — keyword highlight is first-class

Minor release. Per-word caption color (keyword highlight) is now produced natively by the engine, replacing the standalone Python `inject_word_captions.py` patcher. CapCut multi-span rich-text is emitted with colors as float32 (`Math.fround`, matching CapCut's internal encoding) and `range` offsets in UTF-16 code units. **Additive** — `add-text` without a keyword flag is byte-identical to 1.3.0.

## Highlights

- 🎯 **`add-text --keyword <word>` / `--keyword-range <s,e>` / `--keyword-color <hex>`.** Color one keyword inside a caption. `--keyword` matches the first substring occurrence (UTF-16 code-unit offsets via native `String.indexOf`/`.length` — correct even with emoji/surrogate pairs); `--keyword-range` takes explicit `start,end` offsets and wins over `--keyword`. Default color `#FFD600`.
- 📦 **`import-captions <project> <captions.json>`.** Batch word/keyword captions from `[{text,start,end,hl?:[s,e],color?}]` — the engine-native replacement for `inject_word_captions.py`. Replaces the named text track 1:1 (leaves prior materials orphaned ⇒ invisible ⇒ zero deletion risk), supports per-card `color` (else `--highlight-color`), and extends `draft.duration` to the last caption end.
- 🎨 **Native CapCut format, float32-accurate.** `buildRichTextContent` emits N contiguous, non-overlapping spans covering `[0, text.length]` in code units, with `is_rich_text: true`. Colors round-trip through `Math.fround` so they match a CapCut-UI capture bit-for-bit (e.g. `#8C6CFF → 0.5490196347236633`).
- 🛡️ **Fail-fast, never corrupt.** A keyword not found / out-of-bounds range is a hard error. `set-text` and `apply-template` now **refuse** a multi-span caption (they previously re-ranged only `styles[0]`, desyncing the keyword spans) and tell you to rebuild via `add-text --keyword` / `import-captions`.
- 🧪 **234 tracked tests** (208 prior + 26 keyword-highlight); highlight output validated against a live CapCut render. The no-keyword `add-text` path is locked byte-identical to 1.3.0.

## Bugs fixed

### Keyword-highlight oracle fixture was desynced

`test-fixtures/fixtures/animations-draft.json` (the CapCut-UI keyword-highlight oracle) had its caption text replaced by the generic LOREM anonymizer at *approximate* length while its style `range` offsets were left untouched — so the ranges summed to 25 over a 29-char text, violating the §4.3 "sum of ranges = text length" invariant.

**Fix:** restored the real captured text `"THE EYES ARE WATCHING ME."` (25 code units; public song lyric, not private data) so the ranges `[0,13]/[13,21]/[21,25]` map to the text again. Documented the exception to the LOREM policy in `test-fixtures/README.md`.

## CLI surface

```
capcut-david add-text <project> <start> <duration> <text>
    [--font-size <n>] [--color <hex>] [--align <0|1|2>] [--x <n>] [--y <n>] [--track-name <s>]
    [--keyword <word> | --keyword-range <s,e>] [--keyword-color <hex>]

capcut-david import-captions <project> <captions.json>
    [--highlight-color <hex>] [--track-name <name>]
    # captions.json = [{ "text", "start", "end", "hl"?: [s,e], "color"? }]   (start/end in microseconds)
```

## Migration

**No code changes required.** Existing `add-text` calls are unchanged and byte-identical. To add a keyword color, append `--keyword`/`--keyword-color`. Pipelines that shelled out to `inject_word_captions.py` can switch to `import-captions` (same `captions-styled.json` shape: `[{text,start,end,hl}]`).

## Compatibility

- CapCut ≥ 5.x desktop (Windows + macOS), JianYing 6+ unsupported — unchanged.
- Node `>= 18` — unchanged.
- Runtime dependencies: zero — unchanged.
- `add-text` without a keyword flag: **byte-identical** to 1.3.0 (single UTF-16-byte-range span, no `is_rich_text`). Multi-span output is new (only emitted when a keyword/`hl` is supplied).

## Roadmap (1.x — non-binding)

Unchanged from 1.0.0:

- `1.x.0` — `capcut-david query` (animation / sticker / effect / filter catalogue lookup)
- `1.x.0` — `capcut-david validate <project>` (schema-invariant linter)
- `1.x.0` — JianYing 6+ research; `psycho-build` dynamic audio ducking
