Skip to content

feat: add source module for prompt edits - #50

Closed
wavekat-eason wants to merge 1 commit into
mainfrom
feat/source-prompt-edits
Closed

wavekat-eason wants to merge 1 commit into
mainfrom
feat/source-prompt-edits

Conversation

@wavekat-eason

Copy link
Copy Markdown
Contributor

Summary

Rust authoring tools (a CLI that voices and publishes flows, for one) need to edit a flow's YAML text: swap each text prompt for the clip generated from it, and swap a local recording's path for the clip uploaded from it. They have to keep the author's comments and layout, so the typed Flow round-trip won't do. That code encodes the format: which fields hold a prompt, what a generated clip ref is, and what a voiced prompt looks like. So it should live here, next to the model and checked against the corpus, not be copied into each consumer.

source module (new)

  • prompts(src) -> Prompts { text, audio } lists every prompt in document order, with node, field and kind. Audio prompts carry audio and transcript.
  • voice_text_prompts(src, &[(TextPrompt, clip_ref)]) writes each text prompt as { audio: <clip>, transcript: "<text>" }, the shape the web editor writes. It uses block style in block context and flow style inside {…}.
  • local_audio(src) and replace_audio_refs(src, &[(AudioPrompt, clip_ref)]) handle audio: ./intro.wav style prompts.
  • top_level_name(src) and with_placeholder_fields(src, …) let a draft with no id or name yet still be validated.
  • Every edit is a splice at the value's position, so the rest of the document stays byte-for-byte the same. The result is re-parsed and compared event by event with the original, with only the intended values changed. A mismatch returns SourceError::Mismatch instead of a broken document.
  • Replacement refs must be generated clip refs (SourceError::NotAClipRef).

Model helpers

  • prompt_fields(kind) and PROMPT_FIELDS say which fields a kind speaks from. source uses them, so a field like book's confirm_prompt is only voiced on a node of that kind. A node with no kind, or a kind this build doesn't know, keeps every prompt field, so nothing is skipped silently.
  • is_generated_clip_ref is the Rust twin of isGeneratedClipRef (^vprompt_[a-z0-9]+$, case-insensitive).
  • is_local_audio_ref: a path separator or an audio file extension makes a ref a local file. A generated clip ref never counts as one.

Dependency

  • saphyr-parser = "=0.2.2", event parser only, pure Rust. It's needed because serde_yaml_ng doesn't expose value positions. It's pinned exactly because 0.2.2's markers count chars, not bytes as its docs say, and the module relies on that. The non-ASCII tests would catch a change. Lockfile: adds saphyr-parser and arraydeque, and bumps thiserror 2.0.19 → 2.0.21, which saphyr-parser requires.

Not in this PR

  • A TS twin of voice_text_prompts. The platform voices prompts through its own editor code today. Moving that onto a shared op, with corpus cases both languages run, would be a natural follow-up.
  • The rule that book steps can't use uploaded clips. That depends on whether a clip was uploaded or generated, which only the platform knows, so it stays a platform check.

Test plan

  • cargo fmt --check, cargo clippy --all-targets -- -D warnings, cargo test -p wavekat-flow: all pass.
  • Unit tests ported from the consumer: quoted, literal, folded and multi-line plain values; flow-style nodes; trailing comments; non-ASCII text; partial voicing; idempotence; prompts outside nodes; per-kind fields; local audio detection and replacement; placeholder fields.
  • tests/source.rs runs over every valid corpus document (including outbound/valid):
    • source::prompts must agree with Node::prompts() node for node. Checked by hand: removing confirm_prompt from book's fields makes it fail on v2/valid/clinic.yaml.
    • Voicing every text prompt must still parse to the same flow, with each prompt now { audio, transcript }.
  • cargo package -p wavekat-flow builds.
  • After release, switch the consumer over and delete its copy.

🤖 Generated with Claude Code

https://claude.ai/code/session_01GNFHhzw4ZsqCFnv6ERigjd

Rust authoring tools need to voice a flow's text prompts and swap
local recordings for uploaded clips without losing the author's
comments and layout. That logic encodes the format (which fields hold
a prompt, what a clip ref is, the shape of a voiced prompt), so it
belongs here rather than in each consumer.

- `source`: find prompts in the YAML text, voice text prompts as
  `{ audio, transcript }`, replace `audio:` refs. Every edit is a
  splice, re-parsed and compared event by event before it's returned.
- `prompt_fields(kind)` and `PROMPT_FIELDS`, kept in step with
  `Node::prompts` by a corpus test.
- `is_generated_clip_ref` (twin of `isGeneratedClipRef`) and
  `is_local_audio_ref`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GNFHhzw4ZsqCFnv6ERigjd
@wavekat-eason

Copy link
Copy Markdown
Contributor Author

Closing: rather than splicing YAML text, we're making the document the complete source of truth (per-node note, ordered nodes, one canonical serialization tested in both languages), so edits are parse → change the model → serialize. Design doc to follow; is_generated_clip_ref / prompt_fields will be folded into that work.

@wavekat-eason
wavekat-eason deleted the feat/source-prompt-edits branch October 17, 2026 08:54
@wavekat-eason wavekat-eason mentioned this pull request Oct 17, 2026
1 of 2 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant