Skip to content

Series voice exemplar/anti-exemplar passages injected into prose prompts#2232

Merged
atomantic merged 3 commits into
mainfrom
claim/issue-2179
Jul 6, 2026
Merged

Series voice exemplar/anti-exemplar passages injected into prose prompts#2232
atomantic merged 3 commits into
mainfrom
claim/issue-2179

Conversation

@atomantic

Copy link
Copy Markdown
Owner

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").

  • Schema + sanitizer (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) and voiceAntiExemplars (passages in the wrong register kept as negatives, noted with what's wrong). cleanExemplars drops 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 to null.
  • Prompt injection (renderStyleGuide): renders 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 all honor them with no new stage-prompt template variable and no migration (same pattern as appendTickingClock). Absent exemplars render nothing.
  • Zod parity (server/routes/pipeline/series.js): styleGuideSchema gains 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 run validateRequest.
  • Editor UI (client/src/pages/PipelineSeries.jsx): a VoiceExemplarEditor component adds/edits/removes passage rows inline on the series style-guide section. Client caps mirror the server STYLE_GUIDE_LIMITS; every input is htmlFor/id paired; 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 conditional MATCH/NEVER render tests).
  • cd server && npm test -- routes/pipeline — 183 passed (route schema change is additive/optional; no service-signature change).

Refs #2179

atomantic added 3 commits July 6, 2026 08:26
…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).
@atomantic atomantic merged commit f5cbec8 into main Jul 6, 2026
1 of 2 checks passed
@atomantic atomantic deleted the claim/issue-2179 branch July 6, 2026 15:54
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