Series voice exemplar/anti-exemplar passages injected into prose prompts#2232
Merged
Conversation
…prompts
The series style guide carried only tense/POV/audience/rating controls but no
concrete prose anchor. Adjective lists ("witty, atmospheric") anchor voice far
more weakly than an actual passage does, so add two fields — voiceExemplars (1-3
short passages that nail the target voice, each with an optional one-line note)
and voiceAntiExemplars (passages in the wrong register kept as negative
examples) — to sanitizeStyleGuide with Zod parity and an inline series-editor UI.
renderStyleGuide now emits a conditional "MATCH this voice" block and a "NEVER
drift toward this" block; both fold into the existing {{series.styleNotes}}
template variable via composeStyleNotes, so prose drafting, revision, arc
planning, and episode-seed passes honor them with no new stage-prompt variable
and no migration. The sanitizer drops empty passages, trims each to ~2000 chars,
and caps the list at 3 to keep the fixed per-call prompt overhead tight.
Refs #2179
The styleGuideSchema required voiceExemplars[].passage to be .min(1), which
contradicted the field's own comment (the sanitizer prunes empty passages).
The editor keeps a blank { passage: '', note: '' } row while the user fills it
in, so a save that flushed before the row was typed (or after a passage was
cleared) 400'd the entire series PATCH. Drop .min(1) on both exemplar lists so
cleanExemplars prunes the empty row server-side as documented.
… fields voiceExemplars/voiceAntiExemplars live inside the federated styleGuide sub-object, which round-trips through sanitizeStyleGuide on every peer receive. Without a schema-version bump, a same-version (v10) peer that predates this feature would strip the passages on sanitize and last-writer-wins the loss back onto the newer peer (preserveAbsentAdditiveFields only restores a wholly-absent styleGuide, not a present-but-sub-field-stripped one). Bumping to 11 makes the older peer reject the ahead-version series transfer instead — the same silent-strip-then-LWW guard every prior additive series/styleGuide field got (v4 styleGuide, v6 characterArcs, v7 factReference, v10 foreshadowing).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
CWQE Phase 14 (first slice of #2179): give a series style guide concrete voice anchors — exemplar and anti-exemplar prose passages — and inject them into every draft/revision prompt. Adjective lists ("witty, atmospheric") anchor prose voice far more weakly than an actual passage does ("the tuning fork").
server/lib/styleGuide.js): two new style-guide fields —voiceExemplars(1–3 short passages, each with an optional one-line note on what it demonstrates) andvoiceAntiExemplars(passages in the wrong register kept as negatives, noted with what's wrong).cleanExemplarsdrops empty passages, trims each to ~2000 chars, and caps the list at 3 so the fixed per-call prompt overhead stays tight. A guide with only exemplars is no longer collapsed tonull.renderStyleGuide): renders a conditionalMATCH this voiceblock and aNEVER drift toward thisblock. Both fold into the existing{{series.styleNotes}}template variable viacomposeStyleNotes, so prose drafting, revision, arc planning, and episode-seed passes all honor them with no new stage-prompt template variable and no migration (same pattern asappendTickingClock). Absent exemplars render nothing.server/routes/pipeline/series.js):styleGuideSchemagains the two array fields with the same per-field char caps and list cap; wired into both the create (POST) and patch (PATCH) paths that already runvalidateRequest.client/src/pages/PipelineSeries.jsx): aVoiceExemplarEditorcomponent adds/edits/removes passage rows inline on the series style-guide section. Client caps mirror the serverSTYLE_GUIDE_LIMITS; every input ishtmlFor/idpaired; mobile-responsive.This is the first slice of the multi-part #2179 — the AI-assisted voice-discovery flow, Writers Room parity, and the #2166 voice-fingerprint baseline hook ship separately.
Test plan
cd server && npm test -- styleGuide.test.js series.test.js— 87 passed (new sanitizer round-trip, cap/trim/drop, only-exemplars-not-null, and conditionalMATCH/NEVERrender tests).cd server && npm test -- routes/pipeline— 183 passed (route schema change is additive/optional; no service-signature change).Refs #2179