Non-linear generation: backport across pipeline stages & Story Builder steps#566
Merged
Conversation
d9bd733 to
3157cde
Compare
…(backport)
The Create Series Pipeline was strictly forward-only: each text stage
(idea → prose → comicScript → teleplay) could only be generated from its
conventional upstream, so a series that started life as a drafted comic
script had no way to backfill the prose / beat sheet from it.
Each stage's Generate button now offers a "Generate from:" picker listing
every other stage that has content, pre-checked with the conventional
forward source. The server renders the chosen stages through a generic
{{#sourceMaterials}} block instead of a hardcoded single-source slot, so
prose can be written from a comic script, the beat sheet reverse-engineered
from finished prose, etc. Omitting sourceStageIds preserves the exact prior
behavior (auto-run + legacy callers fall back to the forward source).
The forward-source map and a shared stageContentOf accessor live in
apiPipeline.js / textStages.js so the client picker and server resolver read
one definition instead of duplicating it across the boundary.
Migration 054 rewrites the four shipped stage prompts in existing installs
to the source-agnostic templates, hash-gated so customized prompts are left
intact and warned about.
…fted issues
The Story Builder forced a strict forward order: setCurrentStep hard-threw a
409 unless every earlier step was locked-and-fresh, and the generators only
ever pulled from their conventional upstream. A user who began a series by
drafting the comic book had no way to walk the wizard backward and reconstruct
the idea / plot arc from the work that already existed.
Two changes:
- Navigation is now advisory, not gated. setCurrentStep accepts any valid step;
the step rail still flags an unlocked/stale upstream with a warning icon but
no longer disables the step. Lock enforcement remains at the generators
(a locked arc/readerMap record still refuses regeneration).
- The idea and plotArc generators take an optional `fromDownstream` flag. When
set, they synthesize from the series' existing issue content (richest stage
per issue: comicScript → teleplay → prose → idea) instead of the empty
upstream. plotArc routes through a new arcPlanner.generateArcFromSource that
reuses the importer's importer-arc-extract prompt and returns the same
{arc, seasons} shape, committed through the identical commitSeasonsWithRemap
path. idea passes the collected corpus as {{sourceMaterial}} in a new
optional prompt block. The forward (seed-only) paths are unchanged.
A "Backfill from existing issues" button appears on the idea + plot-arc steps
whenever any issue already has text content.
Migration 055 upgrades the shipped story-builder-idea-expand prompt in existing
installs (hash-gated; customized prompts are left intact and warned about).
c492b64 to
06435f9
Compare
…e-prompt migrations Making the pipeline + story-builder prompts source-agnostic (migrations 054/055) changed the live data.reference bodies. Per the repo convention, every prior migration that ships a NEW_SHIPPED_MD5 for one of those prompts must keep that hash pointing at the current reference and accumulate the prior hash in its accepted-old lineage — otherwise the drift-catch tests fail and an install at the pre-054 hash wouldn't recognize the upgrade path. Bumped NEW + appended the prior hash for the four pipeline stage prompts in migrations 003/004/013/025/027 and the story-builder idea prompt in 043, and mirrored both into the setup-data.js drift table.
9b37021 to
5d567da
Compare
…als prompts; fix dead goToStep args - Switch all four pipeline sourceMaterials blocks from triple-backtick fences to tilde fences + the importer's injection-guard sentence, matching the established pattern in importer-arc-extract and siblings. - Add the missing injection-guard sentence to story-builder-idea-expand.md (which already had the tilde fence). - Remove stale idx argument from StoryBuilder Back/Next buttons — goToStep was narrowed to (id) in this branch but the footer calls kept the old second arg. - Fix arcPlanner.js comment that overstated shapeSeasonOutlines: synopsis is present in importer-arc-extract output but is not forwarded to buildSeason. - Cascade new prompt hashes through migrations 003/004/013/025/027/054/055 and setup-data.js drift table so existing installs auto-upgrade cleanly. Note: z.enum(['idea','prose','comicScript','teleplay']) in pipeline.js:365, TEXT_STAGE_IDS in issues.js, and PIPELINE_TEXT_STAGES in apiPipeline.js are three separate literals that must stay in lockstep — they agree today but a future text-stage addition must update all three; the route enum is the one most likely to be forgotten and would silently 400 valid sourceStageIds.
…st, story-builder prompt drift entry - idea backfill now throws ERR_VALIDATION when fromDownstream:true but no issue has content, consistent with the plotArc backfill guard (was silently falling through to a seed-only expansion) - Add regression test covering the new guard - Add server/routes/pipeline.test.js case asserting sourceStageIds is accepted by the route schema and an unknown stage id 400s (plan doc listed this test as expected but it was missing) - Add story-builder-idea-expand.md to setup-data.js SHIPPED_PROMPT_OLD/NEW_MD5 drift tables so the boot-time drift warning covers it (CLAUDE.md: stage-prompt changes need migration + drift-table mirror)
…traction importer-arc-extract returns synopsis per season, but shapeSeasonOutlines wasn't forwarding it to buildSeason — so Arc Canvas synopsis fields stayed empty after a plotArc backfill even though the LLM populated them. One-line fix: add synopsis to the shapeSeasonOutlines buildSeason call. Extend the backfill test to assert the persisted season carries the extracted synopsis.
… literal - TextStagePanel: remove availableKey join-proxy; availableSources is already stable from useMemo so it can be used directly as a useEffect dep, removing the workaround variable and its eslint-disable comment - storyBuilder plotArc branch: replace 5 uninitialized let declarations with a single arcGenResult intermediate, then one const destructure — both branches write the same shape, so the let pattern was unnecessary - collectIssueSourceText: drop the spread-and-sort; listIssues already returns issues in number order when filtered to a single seriesId - resolveSourceStageIds: build the Set once from requested, then single-pass filter TEXT_STAGE_IDS — eliminates the intermediate filtered array - pipeline route sourceStageIds: drive z.enum from issuesSvc.TEXT_STAGE_IDS instead of a hand-duplicated literal, matching the STAGE_STATUSES pattern already used elsewhere in the file
Contributor
There was a problem hiding this comment.
Pull request overview
Makes the Create Series Pipeline and Story Builder non-linear so creators can backfill earlier steps from later ones. Pipeline text stages gain a multi-select source picker (any populated stage can feed any other) rendered via a generic {{#sourceMaterials}} block, and Story Builder relaxes step gating to advisory while letting the idea and plotArc generators synthesize from existing issue content via the importer's arc-extract prompt.
Changes:
- Pipeline: per-stage source picker (client + server
sourceStageIds) with hash-gated migration 054 rewriting four prompt templates to a source-agnostic block. - Story Builder:
setCurrentStepbecomes advisory;generateStep({fromDownstream})adds backfill paths foridea(new optional{{sourceMaterial}}block, migration 055) andplotArc(newarcPlanner.generateArcFromSourcereusingimporter-arc-extract). - Drift table in
setup-data.jsplus old migrations 003/004/013/025/027 updated to accept the new hashes and target the post-054-fence shipped versions.
Reviewed changes
Copilot reviewed 27 out of 27 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| server/services/pipeline/textStages.js | Adds sourceMaterials context + resolveSourceStageIds, exports stageContentOf. |
| server/services/pipeline/textStages.test.js | New backport / default-source / drop-invalid cases. |
| server/services/pipeline/arcPlanner.js | New generateArcFromSource using importer-arc-extract; adds synopsis to season outline shape. |
| server/routes/pipeline.js | Adds sourceStageIds enum array to generate schema. |
| server/routes/pipeline.test.js | Verifies accept/reject of sourceStageIds. |
| server/services/storyBuilder.js | Removes lock-gate from setCurrentStep; adds collectIssueSourceText + fromDownstream paths for idea/plotArc. |
| server/services/storyBuilder.test.js | New backfill tests + relaxed-nav test. |
| server/lib/validation.js | Adds fromDownstream to story step generate schema. |
| client/src/services/apiPipeline.js | Exports PIPELINE_DEFAULT_FORWARD_SOURCE. |
| client/src/components/pipeline/stages/TextStagePanel.jsx | Renders "Generate from:" chip picker, sends sourceStageIds. |
| client/src/pages/StoryBuilder.jsx | Free navigation with warning icons; adds Backfill button on idea + plotArc. |
| data.reference/prompts/stages/pipeline-{idea-expansion,prose,comic-script,teleplay}.md | Replace hardcoded slot with {{#sourceMaterials}} block + injection-guard fences. |
| data.reference/prompts/stages/story-builder-idea-expand.md | Adds optional {{sourceMaterial}} backfill block. |
| scripts/migrations/054-source-agnostic-stage-prompts.{js,test.js} | New hash-gated migration for the four pipeline prompts. |
| scripts/migrations/055-story-builder-idea-backfill-prompt.{js,test.js} | New hash-gated migration for the idea-expand prompt. |
| scripts/migrations/003,004,013,025,027-*.js | Extend ACCEPTED_OLD_MD5 chains + retarget NEW_SHIPPED_MD5 to post-054-fence hashes. |
| scripts/setup-data.js | Drift table updated with the new old/new prompt hashes. |
| docs/plans/2026-05-30-non-linear-stage-generation.md | Planning doc. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
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
Makes both the Create Series Pipeline and the Story Builder non-linear, so a creator who authored work out of order (e.g. started a series as a drafted comic book) can backfill the prior steps to evaluate whether the story is complete.
Pipeline — per-stage source picker. Each text stage (idea → prose → comicScript → teleplay) was forward-only: it could only be generated from its conventional upstream. Now each stage's Generate button shows a "Generate from:" picker listing every other stage that has content, pre-checked with the conventional forward source. The server renders the chosen stages through a generic
{{#sourceMaterials}}block instead of a hardcoded single-source slot, so prose can be written from a comic script, the beat sheet reverse-engineered from finished prose, etc. OmittingsourceStageIdspreserves exact prior behavior (auto-run and legacy callers fall back to the forward source).Story Builder — start anywhere + backfill. Navigation is now advisory rather than gated:
setCurrentStepaccepts any valid step, and the step rail flags an unlocked/stale upstream with a warning icon instead of disabling it. Lock enforcement stays at the generators (a locked arc/readerMap record still refuses regeneration). TheideaandplotArcgenerators take an optionalfromDownstreamflag that synthesizes the step from the series' existing issue content (richest stage per issue: comicScript → teleplay → prose → idea).plotArcroutes through a newarcPlanner.generateArcFromSourcethat reuses the importer'simporter-arc-extractprompt and returns the same{arc, seasons}shape, committed through the identicalcommitSeasonsWithRemappath;ideapasses the corpus as{{sourceMaterial}}in a new optional prompt block. A "Backfill from existing issues" button appears on the idea + plot-arc steps whenever any issue has content.Compatibility. Two hash-gated prompt migrations ship the updated templates to existing installs (054 for the four pipeline stage prompts, 055 for the story-builder idea prompt); customized prompts are left intact and warned about. No sync/schema-version payload changes (Story Builder sessions are local-only; pipeline issue shape is unchanged — only generation inputs change).
Test plan
cd server && npx vitest runfor the touched suites — 294 tests pass:textStages(incl. new backport cases),pipelineroute (newsourceStageIdsschema),storyBuilderservice (newfromDownstreambackfill cases + relaxed navigation),storyBuilderroute,arcPlanner, and both migration suites (054/055, each verifying the accepted-old hash matches git-HEAD and that customized prompts are skipped).cd client && npx eslintonTextStagePanel.jsx+StoryBuilder.jsx— clean.npm run dev): open an issue that has only a comic script → the Prose stage's "Generate from:" lists Comic Script (Idea absent); generate produces prose from the script. In Story Builder, a session whose series has drafted issues but no arc → enter Plot Arc out of order, click "Backfill from existing issues", get a populated arc; a locked step still refuses regeneration.