Secrets must never be baked into the static build. Stop reading
VITE_AI_API_KEY from env — key comes only from localStorage via the
⚙️ config dialog (flow-state:llm). Gateway URL / model may still come
from env as non-secret defaults.
- src/llm/config.js: DEFAULTS model → meta/muse-spark-1.3-contributor
(cheap, per user request), readEnv no longer touches apiKey,
getLlmConfig resolves key only from stored.apiKey.
- .env.example: drop VITE_AI_API_KEY=, document browser-only key flow.
- src/main.js: dialog copy now says key is browser-only, never baked.
Build + lint:scenes + tests still green. No VITE_AI_API_KEY string
remains in source.
Co-Authored-By: Claude <noreply@anthropic.com>
Add a ✨ tune button that lets an LLM inspect the proposed
visualization and tweak the proc-song knobs so the next generation
looks better. Edits only the 7 songbank params (bpm, brightness,
noise, noiseColour, dynamics, transition, arrangement) — never the
Look — then re-synthesizes and re-generates locally for preview.
- ai + @ai-sdk/openai + zod (library per plan) against the Vercel AI
Gateway (OpenAI-compatible, configurable via
VITE_AI_GATEWAY_URL/MODEL/API_KEY, localStorage override,
.env.example).
- Structured output (TweakResultSchema) + clampEdits enforces ranges;
prompt ≤ ~2.5k tokens, max 2-3 knobs ±0.15 / bpm ±8.
- Budget caps: 3/load + 10/session (sessionStorage), checked before
network, NO_EDITS refunded, offline/auth/rate-limit mapped.
- Prompt split into src/llm/spec.js (pure, node-testable) + llmTweak.js
(gateway + digest) to keep `node --test` off the Vite JSON path.
- UI: ⚙️ gateway/model/key dialog (localStorage flow-state:llm),
confirm(prompt) with hint, diff modal with Apply/Dismiss; Apply
synthesizes → FeatureTrack → generateLook → Show.useTrack.
- Tests: 7 new LLM cases (clamp, schema, prompt, budget), 18/18 pass.
Build + lint:scenes green.
Co-Authored-By: Claude <noreply@anthropic.com>