* fix(voice-design): build validator-safe instruct on the frontend (#132)
plan-05 (option A — frontend guard). The engine validator is whitelist-strict
by design; the #114/#115 failures came from useTTS.js merging the free-text
instruct field with the category dropdowns, producing unsupported items (#115)
or two items in one category (#114).
- voiceInstruct.js buildDesignInstruct(vdStates, freeText): dropdowns win their
category; free-text accepted only as a known tag in an open category;
unknown/duplicate items are dropped and returned so the UI can warn. Derives
TAG_TO_CATEGORY from CATEGORIES (single source of truth).
- useTTS.js design mode uses it instead of the raw merge; toasts dropped items.
Engine validator (_resolve_instruct) untouched — whitelist contract preserved,
no vendored-engine change.
Tests (TDD, vitest): voiceInstruct.test.js (6). Full frontend suite 72 passed;
typecheck + build green.
Closes#114, #115. Addresses #132.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(voice-design): split unsupported vs duplicate instruct; warn on dropdown drift (Greptile #141)
- buildDesignInstruct now returns { instruct, unsupported, duplicates }:
`unsupported` = free-text prose (not a known tag, #115); `duplicates` = a
valid tag whose category was already set (e.g. dropdown low pitch outranks a
typed high pitch, #114). useTTS shows an accurate toast per bucket instead of
calling a valid-but-outranked tag "unsupported".
- console.warn when a *dropdown* value isn't in CATEGORIES (option-list ↔
whitelist drift) instead of silently dropping it.
Tests updated + 1 added (7/7); typecheck + build green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>