From 20e262c85de3b25af3e8fc3c80f96504b1576e9a Mon Sep 17 00:00:00 2001 From: Kayos Date: Wed, 13 May 2026 13:44:41 -0700 Subject: [PATCH] =?UTF-8?q?web:=20new-story=20form=20gets=20'fire=20now'?= =?UTF-8?q?=20too=20=E2=80=94=20same=20pattern=20as=20continue=20form?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cobb tried creating a story via /stories/new tonight, ticked through the form, hit Create, then expected gen to fire. It didn't, because the fire-now checkbox only existed on the continue form. Story sat in 'seed' for 30 min before he asked. Fix: same path as continue. NewStoryForm picks up an optional 'fire' field; new_story_create spawns a tokio::spawn task that calls continue_story::run() with parent_story_id=None semantics: - Context is the story's own prompt (not parent's bible) - Audit pass is skipped (no parent to compare against) - Status flow: seed → generating → cleaning → complete The form copy explains the audit-skip for first chapters so the user isn't surprised to see no findings. Also fired Cobb's pending story manually via 'skald continue --story bd73dd19...' so it actually generates this round. --- skald/src/web.rs | 51 ++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 47 insertions(+), 4 deletions(-) diff --git a/skald/src/web.rs b/skald/src/web.rs index 1bb4bf2..79c0634 100644 --- a/skald/src/web.rs +++ b/skald/src/web.rs @@ -128,6 +128,8 @@ pub struct NewStoryForm { word_count_target: String, #[serde(default)] author_slug: String, + #[serde(default)] + fire: String, // "now" = spawn background gen; empty = just queue the seed row } async fn new_story_form(State(state): State>) -> Html { @@ -177,6 +179,39 @@ async fn new_story_create( } } + // Fire generation immediately if requested. No-parent gen path: + // continue_story::run handles parent_story_id=None by using the + // story's own prompt as the LLM context (instead of the parent + // chain's bible/chapters). Audit pass is skipped — there's no + // parent to compare against. So you get a single first-chapter + // gen + cleanup pass and status flows to 'complete'. + if form.fire == "now" { + let database_url = std::env::var("DATABASE_URL") + .unwrap_or_else(|_| "postgresql://skald:skald@localhost:5432/skald".into()); + let author_owned = if author_slug.is_empty() { + None + } else { + Some(author_slug.to_string()) + }; + tokio::spawn(async move { + if let Err(e) = crate::continue_story::run( + &database_url, + id, + author_owned.as_deref(), + None, // direction (no parent — uses story.prompt instead) + target, + 2, // recent_n + true, // skip_audit (no parent → can't audit) + ) + .await + { + tracing::error!(story_id = %id, error = %e, "background new-story task failed"); + } else { + tracing::info!(story_id = %id, "background new-story task succeeded"); + } + }); + } + Ok(Redirect::to(&format!("/stories/{}", id))) } @@ -457,7 +492,11 @@ fn new_story_panel(err: Option<&str>, authors: &[AuthorOpt]) -> Markup { article.form-panel { h1 { "Begin a new saga" } (ornament()) - p.muted { "Create a story row + pick its author. The gen pipeline fires on " code { "skald continue --story " } " or via the continue form once you've drafted the first chapter." } + p.muted { + "Create a story row, pick its author, and optionally fire the gen pipeline " + "immediately. Without " strong { "fire now" } ", the seed row waits for " + code { "skald continue --story " } "." + } @if let Some(e) = err { p.error { (e) } } form method="post" action="/stories/new" { label { "Title" @@ -474,12 +513,16 @@ fn new_story_panel(err: Option<&str>, authors: &[AuthorOpt]) -> Markup { } } } - label { "Seed prompt (optional)" - textarea name="prompt" rows="4" placeholder="What is this saga about? Setting, frame, conflict." {} + label { "Seed prompt" + textarea name="prompt" rows="5" placeholder="What is this saga about? Setting, frame, conflict, vibe. The richer this is, the better the first chapter." {} } - label { "Target word count per chapter (optional)" + label { "Target word count for the first chapter" input type="number" name="word_count_target" min="500" step="500" placeholder="3000"; } + label.checkbox-label { + input type="checkbox" name="fire" value="now"; + span { " fire generation now (background task, ~7 min — no audit pass on the first chapter since there's no parent yet)" } + } button type="submit" { "Create" } } }