From f7fd8c153e5c4882bb7dd8a1e8376dcd55123f11 Mon Sep 17 00:00:00 2001 From: Chris Guidry Date: Tue, 4 Aug 2026 16:48:43 -0400 Subject: [PATCH] Add /settings and boot straight into the story storied play and storied sandbox no longer print the world paths and mount stack at startup, and the screen no longer opens its transcript with the model banner; the session begins with whatever gets said first. A new screen-local /settings command shows all of it on demand: the world root, the mount stack lowest first, the model, the api base, and the reasoning effort when set, composed once at startup and tab-completed with the other slash commands. The ask-before-create prompt and error output still print, because those are interaction, not chatter. Removing the banner uncovered a spacing bug it had masked: the transcript's first row could carry a spurious blank ahead of it when its kind differed from the default, and now the first row never takes a leading blank. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01HvctyUUkzw7PcNrjCGG6dF --- src/cli.rs | 39 +++----------- src/play/mod.rs | 84 ++++++++++++++++++++++++++---- src/play/screen.rs | 68 +++++++++++++++--------- src/play/screen_pinned_tests.rs | 46 ++-------------- src/play/screen_slash_tests.rs | 45 +++++++++++++++- src/play/screen_sync_tests.rs | 2 +- src/play/screen_tests.rs | 10 ++-- src/play/screen_turn_tests.rs | 21 ++------ src/play/terminal.rs | 7 ++- src/play/transcript.rs | 25 +++++---- src/play/transcript_tests.rs | 20 ++----- src/play/transcript_width_tests.rs | 7 +-- 12 files changed, 211 insertions(+), 163 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index c963d99..3cc1093 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -207,12 +207,12 @@ fn run_completions(shell: Shell) -> Result<(), String> { Ok(()) } -/// Boots a world for a sandbox session, prints where it lives, and plays -/// until the player quits. When `scenario` names a file, its contents open -/// the session as the player's first line. When `world` names a -/// directory, the session keeps its world and player knowledge there -/// instead of a temporary one deleted on exit, so a later run with the -/// same directory resumes it. +/// Boots a world for a sandbox session and plays until the player quits. +/// When `scenario` names a file, its contents open the session as the +/// player's first line. When `world` names a directory, the session keeps +/// its world and player knowledge there instead of a temporary one deleted +/// on exit, so a later run with the same directory resumes it. `/settings` +/// shows where the session's world and player directories live. #[cfg(not(coverage))] fn run_sandbox( overrides: &crate::config::Overrides, @@ -234,7 +234,6 @@ fn run_sandbox( None => None, }; - let kept = world.is_some(); let sandbox = match world { Some(root) => Sandbox::named(root)?, None => Sandbox::new()?, @@ -243,18 +242,6 @@ fn run_sandbox( layers.push(sandbox.world_dir()); layers.push(sandbox.player_dir()); - if kept { - println!("sandbox uses a kept world this session:"); - } else { - println!("sandbox uses a fresh world this session:"); - } - println!(" world: {}", sandbox.world_dir().display()); - println!(" player: {}", sandbox.player_dir().display()); - println!("mount stack, lowest first:"); - for layer in &layers { - println!(" {}", layer.display()); - } - crate::play::run(overrides, &layers, &sandbox.world_dir(), opening) } @@ -269,6 +256,8 @@ fn run_sandbox( /// no `world/` subdirectory inside it, and the player's own knowledge /// lives outside the world at a fixed directory shared across every /// world, with a `worlds/` overlay mounted above this one. +/// `/settings` shows the world directory, the player directory, and the +/// full mount stack once the session starts. #[cfg(not(coverage))] fn run_play( overrides: &crate::config::Overrides, @@ -318,18 +307,6 @@ fn run_play( let layers = crate::worlds::mount_stack(session_layers, &world_dir, &player_root, name); - if resumed { - println!("play resumes an existing world this session:"); - } else { - println!("play begins a new world this session:"); - } - println!(" world: {}", world_dir.display()); - println!(" player: {}", player_root.display()); - println!("mount stack, lowest first:"); - for layer in &layers { - println!(" {}", layer.display()); - } - crate::play::run(overrides, &layers, &world_dir, None) } diff --git a/src/play/mod.rs b/src/play/mod.rs index d22f4e0..246187f 100644 --- a/src/play/mod.rs +++ b/src/play/mod.rs @@ -31,28 +31,92 @@ pub mod worker; #[cfg(not(coverage))] pub use terminal::run; +use std::path::{Path, PathBuf}; + use crate::config::Config; -/// The opening line of the transcript: which model answers, and where it -/// lives. -pub fn banner(config: &Config) -> String { - format!("{} @ {}", config.model, config.api_base) +/// What `/settings` shows: the world root, the mount stack lowest first, +/// and the loaded config, one fact per line, grouped by a blank line +/// between each group. Omits the reasoning effort line when the config has +/// none. +pub fn settings(config: &Config, layers: &[PathBuf], world_root: &Path) -> String { + let mut mount_stack = vec!["mount stack, lowest first:".to_string()]; + mount_stack.extend(layers.iter().map(|layer| format!(" {}", layer.display()))); + + let mut model = vec![ + format!("model: {}", config.model), + format!("api base: {}", config.api_base), + ]; + if let Some(effort) = &config.reasoning_effort { + model.push(format!("reasoning effort: {effort}")); + } + + [ + format!("world: {}", world_root.display()), + mount_stack.join("\n"), + model.join("\n"), + ] + .join("\n\n") } #[cfg(test)] mod tests { use super::*; - #[test] - fn the_banner_names_the_model_and_the_api_base() { - let config = Config { + fn config(reasoning_effort: Option<&str>) -> Config { + Config { api_base: "https://api.example.test/v1".to_string(), api_key: "sk-test".to_string(), model: "a-model".to_string(), - reasoning_effort: None, + reasoning_effort: reasoning_effort.map(str::to_string), worlds_root: None, - }; + } + } + + #[test] + fn settings_shows_the_world_the_mount_stack_and_the_model() { + let layers = vec![PathBuf::from("rules/srd-5.2"), PathBuf::from("/world")]; + + let report = settings(&config(None), &layers, &PathBuf::from("/world")); + + assert_eq!( + report, + [ + "world: /world", + "", + "mount stack, lowest first:", + " rules/srd-5.2", + " /world", + "", + "model: a-model", + "api base: https://api.example.test/v1", + ] + .join("\n") + ); + } + + #[test] + fn settings_adds_the_reasoning_effort_line_when_the_config_has_one() { + let report = settings(&config(Some("high")), &[], &PathBuf::from("/world")); + + assert!(report.ends_with("reasoning effort: high")); + } + + #[test] + fn settings_omits_the_reasoning_effort_line_when_the_config_has_none() { + let report = settings(&config(None), &[], &PathBuf::from("/world")); + + assert!(!report.contains("reasoning effort")); + } + + #[test] + fn settings_keeps_the_layers_in_the_order_they_were_given() { + let layers = vec![PathBuf::from("a"), PathBuf::from("b"), PathBuf::from("c")]; + + let report = settings(&config(None), &layers, &PathBuf::from("/world")); - assert_eq!(banner(&config), "a-model @ https://api.example.test/v1"); + let position = |needle: &str| report.find(needle).unwrap(); + assert!(position(" a") < position(" b")); + assert!(position(" b") < position(" c")); } } diff --git a/src/play/screen.rs b/src/play/screen.rs index 0f4c0b5..842d5c5 100644 --- a/src/play/screen.rs +++ b/src/play/screen.rs @@ -67,7 +67,7 @@ const RULE: &str = "─"; const WORKER_GONE: &str = "the storyteller thread is gone; restart storied to continue"; /// Slash commands the player can type. Tab cycles through completions. -const SLASH_COMMANDS: &[&str] = &["/context", "/play"]; +const SLASH_COMMANDS: &[&str] = &["/context", "/play", "/settings"]; /// The input the player is typing, the reply that is still arriving, and /// how much of the screen the viewport holds while both go on. @@ -103,8 +103,8 @@ pub type Clock<'a> = &'a mut dyn FnMut() -> Option; /// Where the screen learns about the world's characters and puts one on /// stage. `/play` reads and writes through this, and the session's -/// opening reads it once to decide whether the unbound notice belongs -/// after the banner. +/// opening reads it once to decide whether the unbound notice opens the +/// session. pub trait Stage { /// Every character in the world, by slug. fn characters(&self) -> Vec; @@ -140,8 +140,9 @@ pub enum Opening { /// keys, and the worker: the prose it composes before the loop starts, /// the prompts it remembers, and where it reads the campaign clock. pub struct Session<'a> { - /// What the transcript opens with. - pub banner: &'a str, + /// What `/settings` shows: the world root, the mount stack, and the + /// loaded config, composed once at startup. + pub settings: &'a str, /// What `/context` prints: the DM's system prompt, frozen at startup. pub slash_context: &'a str, /// The prompts the player recalls with Up and Down. @@ -168,10 +169,10 @@ pub struct Session<'a> { /// /// `guard` brackets every render pass in a synchronized update, so the /// terminal paints each pass in one go instead of painting whatever has -/// landed on the wire so far. The opening banner queues before the loop -/// and paints with the first pass, inside that pass's update. An opening -/// line in `session` starts its turn right after the banner, before the -/// first key. +/// landed on the wire so far. The transcript opens empty; nothing queues +/// ahead of the loop. An opening line in `session` starts its turn before +/// the first key, so it is the first thing the transcript shows. A fresh +/// world with no opening line shows nothing until the player types one. /// /// `viewport` changes the viewport's height when the prompt needs more or /// fewer rows. Each resize sits inside the same guard as the repaint that @@ -189,7 +190,7 @@ pub struct Session<'a> { /// way through a keystroke. /// /// `session.stage` says who the world's characters are and who is on -/// stage. Right after the banner, this queries it once for the unbound +/// stage. Before the loop starts, this queries it once for the unbound /// notice: a world with several characters and nobody on stage yet gets /// a plain aside pointing at `/play`; any other world gets none. pub fn play>( @@ -209,9 +210,6 @@ pub fn play>( prefix: prompt::prefix((session.clock)()), divider: false, }; - screen - .transcript - .insert(terminal, session.banner, aside(), Kind::Aside)?; let characters = session.stage.characters(); let on_stage = session.stage.on_stage(); if let Some(notice) = unbound_notice(&characters, on_stage.as_deref()) { @@ -480,19 +478,13 @@ impl Screen { } self.input.take(); - // Slash commands stay local. /context shows the DM's system prompt. + // Slash commands stay local. /context shows the DM's system prompt, + // and /settings shows the world, the mount stack, and the model. if input == "/context" { - let line = format!("{}/context", self.prefix); - guard.begin(); - self.transcript - .insert(terminal, &line, player(), Kind::Player)?; - self.transcript - .insert(terminal, session.slash_context, aside(), Kind::Aside)?; - fit(self, terminal, viewport)?; - self.transcript.place(terminal)?; - terminal.draw(|frame| render(self, frame))?; - guard.end(); - return Ok(()); + return self.echo_local(terminal, guard, viewport, "/context", session.slash_context); + } + if input == "/settings" { + return self.echo_local(terminal, guard, viewport, "/settings", session.settings); } // /play stays local too. Alone, it lists the world's characters; @@ -538,6 +530,32 @@ impl Screen { let _ = worker.inputs.send(TurnInput { speaker, text }); } + /// Echoes `command` as a player line, then answers it with `text` as + /// an aside, the way `/context` and `/settings` both work: neither + /// reaches the worker, and neither touches `busy`, since + /// [`Self::submit`] has already refused to run this while a turn is in + /// flight. + fn echo_local>( + &mut self, + terminal: &mut Terminal, + guard: &mut G, + viewport: &mut V, + command: &str, + text: &str, + ) -> Result<(), B::Error> { + let line = format!("{}{command}", self.prefix); + guard.begin(); + self.transcript + .insert(terminal, &line, player(), Kind::Player)?; + self.transcript + .insert(terminal, text, aside(), Kind::Aside)?; + fit(self, terminal, viewport)?; + self.transcript.place(terminal)?; + terminal.draw(|frame| render(self, frame))?; + guard.end(); + Ok(()) + } + /// Handles `/play`, screen-local like `/context`: never reaches the /// worker, and never touches `busy`, since [`Self::submit`] has /// already refused to run this while a turn is in flight. diff --git a/src/play/screen_pinned_tests.rs b/src/play/screen_pinned_tests.rs index 9868436..d4eeb10 100644 --- a/src/play/screen_pinned_tests.rs +++ b/src/play/screen_pinned_tests.rs @@ -133,19 +133,7 @@ fn a_finished_table_leaves_no_blank_rows_behind_it() { let mut played = play_pinned(ROOMY, steps); - assert_eq!( - played.printed(), - [ - "a banner", - "", - TABLE_ROWS[0], - TABLE_ROWS[1], - TABLE_ROWS[2], - TABLE_ROWS[3], - TABLE_ROWS[4], - TABLE_ROWS[5] - ] - ); + assert_eq!(played.printed(), TABLE_ROWS); } #[test] @@ -170,8 +158,6 @@ fn a_table_that_finishes_while_the_player_types_leaves_no_blank_rows_behind_it() assert_eq!( played.printed(), [ - "a banner", - "", TABLE_ROWS[0], TABLE_ROWS[1], TABLE_ROWS[2], @@ -194,17 +180,7 @@ fn a_block_that_spilled_while_it_streamed_leaves_no_blank_rows_behind_it() { // already scrolling. let mut played = play_pinned(CRAMPED, steps); - assert_eq!( - played.printed(), - [ - "a banner", - "", - TALL_ROWS[0], - TALL_ROWS[1], - TALL_ROWS[2], - TALL_ROWS[3] - ] - ); + assert_eq!(played.printed(), TALL_ROWS); assert_eq!(played.viewport_bottom(), CRAMPED); } @@ -214,10 +190,7 @@ fn the_transcript_reaches_the_viewport_while_a_block_is_still_forming() { let mut played = play_pinned(CRAMPED, steps); - assert_eq!( - played.printed(), - ["a banner", "", TALL_ROWS[0], TALL_ROWS[1]] - ); + assert_eq!(played.printed(), [TALL_ROWS[0], TALL_ROWS[1]]); } #[test] @@ -232,7 +205,7 @@ fn the_row_the_input_gives_back_leaves_nothing_behind_it() { // and the row it gave back holds none of the prompt it used to. assert_eq!(played.viewport_bottom(), ROOMY - 1); assert_eq!(played.row(ROOMY - 1), (String::new(), false)); - assert_eq!(played.printed(), ["a banner"]); + assert!(played.printed().is_empty()); } #[test] @@ -244,15 +217,6 @@ fn the_line_the_player_submits_takes_back_the_row_the_prompt_gave_up() { let mut played = play_pinned(ROOMY, steps); - // The blank row is the one the transcript puts between two blocks of - // different kinds, the banner and what the player said. assert_eq!(played.viewport_bottom(), ROOMY); - assert_eq!( - played.printed(), - [ - "a banner".to_string(), - String::new(), - format!("> {}", "ab".repeat(19)), - ] - ); + assert_eq!(played.printed(), [format!("> {}", "ab".repeat(19))]); } diff --git a/src/play/screen_slash_tests.rs b/src/play/screen_slash_tests.rs index 6c81af2..e43d473 100644 --- a/src/play/screen_slash_tests.rs +++ b/src/play/screen_slash_tests.rs @@ -4,7 +4,9 @@ use std::cell::RefCell; -use super::tests::{SLASH_CONTEXT, play_script, play_script_on, play_staged, press, typing}; +use super::tests::{ + SLASH_CONTEXT, SLASH_SETTINGS, play_script, play_script_on, play_staged, press, typing, +}; use super::*; /// A stage with a fixed roster and a fixed answer for who is on it, that @@ -96,6 +98,37 @@ fn a_trailing_space_still_names_the_command() { assert!(played.nothing_submitted()); } +#[test] +fn slash_settings_puts_the_command_and_the_report_in_the_transcript() { + let mut steps = typing("/settings"); + steps.push(press(Key::Enter)); + + let played = play_script(steps); + + assert!(played.transcript().contains("> /settings")); + assert!(played.transcript().contains(SLASH_SETTINGS)); +} + +#[test] +fn slash_settings_sends_nothing_to_the_worker() { + let mut steps = typing("/settings"); + steps.push(press(Key::Enter)); + + let played = play_script(steps); + + assert!(played.nothing_submitted()); +} + +#[test] +fn slash_settings_clears_the_prompt_for_the_next_input() { + let mut steps = typing("/settings"); + steps.push(press(Key::Enter)); + + let played = play_script(steps); + + assert_eq!(played.prompt(), ">"); +} + #[test] fn tab_completes_a_slash_command_prefix() { let mut steps = typing("/c"); @@ -158,6 +191,16 @@ fn tab_completes_play() { assert_eq!(played.prompt(), "> /play"); } +#[test] +fn tab_completes_settings() { + let mut steps = typing("/se"); + steps.push(press(Key::Tab)); + + let played = play_script(steps); + + assert_eq!(played.prompt(), "> /settings"); +} + #[test] fn slash_play_lists_characters_and_marks_the_one_on_stage() { let stage = FakeStage::new(&["maren", "tomas"], Some("maren")); diff --git a/src/play/screen_sync_tests.rs b/src/play/screen_sync_tests.rs index 93446ed..2efd55b 100644 --- a/src/play/screen_sync_tests.rs +++ b/src/play/screen_sync_tests.rs @@ -227,7 +227,7 @@ fn submitting_repaints_the_shrunk_viewport_inside_its_own_guard() { let stage = EmptyStage; let mut session = Session { - banner: "", + settings: "", slash_context: "", history: &mut history, clock: &mut || None, diff --git a/src/play/screen_tests.rs b/src/play/screen_tests.rs index 6e3a3a8..27ea642 100644 --- a/src/play/screen_tests.rs +++ b/src/play/screen_tests.rs @@ -47,6 +47,10 @@ pub(in crate::play) fn press(key: Key) -> Step { /// can watch `/context` put it in the transcript. pub(in crate::play) const SLASH_CONTEXT: &str = "the DM's full system prompt"; +/// What the harness hands `play` as the settings report, so a test can +/// watch `/settings` put it in the transcript. +pub(in crate::play) const SLASH_SETTINGS: &str = "the settings report"; + /// A screen tall enough to hold a six-row block in the tail area: the /// transcript keeps five rows of it, which leaves the tail eleven. pub(in crate::play) const ROOMY: u16 = 18; @@ -451,7 +455,7 @@ fn run_script>( &mut keys, &worker, &mut Session { - banner: "a banner", + settings: SLASH_SETTINGS, slash_context: SLASH_CONTEXT, history: &mut history, clock: setup.clock, @@ -474,10 +478,10 @@ fn run_script>( } #[test] -fn the_banner_opens_the_transcript() { +fn the_transcript_opens_empty_until_something_is_said() { let played = play_script(vec![]); - assert!(played.transcript().contains("a banner")); + assert!(played.transcript().is_empty()); } #[test] diff --git a/src/play/screen_turn_tests.rs b/src/play/screen_turn_tests.rs index d989cfc..13b4589 100644 --- a/src/play/screen_turn_tests.rs +++ b/src/play/screen_turn_tests.rs @@ -177,12 +177,7 @@ fn whitespace_only_narration_flushes_without_a_blank_row() { let transcript = played.transcript(); let lines: Vec<&str> = transcript.lines().collect(); - let banner = lines.iter().position(|line| *line == "a banner").unwrap(); - let tool = lines - .iter() - .position(|line| *line == "a tool line") - .unwrap(); - assert_eq!(tool - banner, 2); + assert_eq!(lines, vec!["a tool line"]); } #[test] @@ -193,12 +188,7 @@ fn a_tool_event_with_no_narration_inserts_only_the_tool_line() { let transcript = played.transcript(); let lines: Vec<&str> = transcript.lines().collect(); - let banner = lines.iter().position(|line| *line == "a banner").unwrap(); - let tool = lines - .iter() - .position(|line| *line == "a tool line") - .unwrap(); - assert_eq!(tool - banner, 2); + assert_eq!(lines, vec!["a tool line"]); } #[test] @@ -421,12 +411,7 @@ fn a_cancelled_turn_with_no_partial_text_inserts_only_the_aside() { let transcript = played.transcript(); let lines: Vec<&str> = transcript.lines().collect(); - let banner = lines.iter().position(|line| *line == "a banner").unwrap(); - let interrupted = lines - .iter() - .position(|line| *line == "(interrupted)") - .unwrap(); - assert_eq!(interrupted - banner, 2); + assert_eq!(lines, vec!["(interrupted)"]); } #[test] diff --git a/src/play/terminal.rs b/src/play/terminal.rs index eacaa54..376854b 100644 --- a/src/play/terminal.rs +++ b/src/play/terminal.rs @@ -65,7 +65,10 @@ pub fn run( let config = config::load(overrides).map_err(|error| error.to_string())?; let mount = Arc::new(Mount::open(layers)?); let campaign = Campaign::open(world_root)?; - let banner = super::banner(&config); + // `/settings` shows this, composed once here where the config, the + // layers, and the world root are all in hand, before the config moves + // into the DM below. + let settings = super::settings(&config, layers, world_root); // The prompt reads the clock from its own handle on the campaign, // because the DM owns the one it advances and lives on the worker // thread. A read that fails leaves the prompt with the bare marker @@ -109,7 +112,7 @@ pub fn run( &mut CrosstermKeys, &worker, &mut screen::Session { - banner: &banner, + settings: &settings, slash_context: &slash_context, history: &mut history, clock: &mut clock, diff --git a/src/play/transcript.rs b/src/play/transcript.rs index 3c17a98..d6454b4 100644 --- a/src/play/transcript.rs +++ b/src/play/transcript.rs @@ -27,9 +27,10 @@ use ratatui::text::{Line, Span, Text}; use crate::markdown::MarkdownStream; use crate::wrap::{wrap, wrap_spans}; -/// Storied's own asides: the opening banner, the empty-reply line, and -/// the interrupted line. The rule above the prompt is drawn in this style -/// too, so everything that is not the game itself reads the same way. +/// Storied's own asides: the empty-reply line, the interrupted line, and +/// what `/context` and `/settings` show. The rule above the prompt is +/// drawn in this style too, so everything that is not the game itself +/// reads the same way. pub fn aside() -> Style { Style::new().add_modifier(Modifier::DIM) } @@ -65,10 +66,8 @@ pub enum Kind { Narration, /// One dispatched tool call's line. Tool, - /// Storied's own lines: the banner, the empty-reply line, the - /// interrupted line, and anything that failed. This is the kind a - /// transcript starts on, so the banner opens it with no blank row - /// ahead of it. + /// Storied's own lines: the empty-reply line, the interrupted line, + /// and anything that failed. #[default] Aside, } @@ -112,6 +111,9 @@ pub struct Transcript { pending_blank: bool, /// What the last row of the transcript holds. kind: Kind, + /// True once the transcript has put out its first row. The first row + /// never gets a blank row ahead of it, whatever kind it is. + started: bool, /// True once this turn has put a narration row or a tool line in the /// transcript. A turn that ends with this still false is the one that /// says the DM said nothing. @@ -346,7 +348,9 @@ impl Transcript { /// go out on its own: it only marks that one blank row belongs ahead /// of the next row that has something on it. A change of kind marks /// the same thing, which is what separates the blocks of the - /// transcript from each other. + /// transcript from each other. The transcript's first row gets no + /// blank row ahead of it, whatever kind it is: there is nothing above + /// it to separate it from. /// /// The test is for characters, not for columns. A row of characters /// that print in no columns at all, a combining mark that lost its @@ -356,7 +360,10 @@ impl Transcript { self.pending_blank = true; return; } - self.pending_blank |= self.kind != kind; + if self.started { + self.pending_blank |= self.kind != kind; + } + self.started = true; self.kind = kind; self.said |= matches!(kind, Kind::Narration | Kind::Tool); let blank = u16::from(self.pending_blank); diff --git a/src/play/transcript_tests.rs b/src/play/transcript_tests.rs index 60382ad..12194f7 100644 --- a/src/play/transcript_tests.rs +++ b/src/play/transcript_tests.rs @@ -139,10 +139,7 @@ fn a_finished_reply_ending_in_blank_lines_has_no_extra_blank_rows() { let transcript = played.transcript(); let lines: Vec<&str> = transcript.lines().collect(); - let banner = lines.iter().position(|line| *line == "a banner").unwrap(); - let narration = lines.iter().position(|line| *line == "You wake.").unwrap(); - assert_eq!(narration - banner, 2); - assert_eq!(lines.len(), narration + 1); + assert_eq!(lines, vec!["You wake."]); } #[test] @@ -259,8 +256,6 @@ fn a_table_reaches_the_transcript_bordered_and_aligned_when_the_turn_ends() { assert_eq!( lines, vec![ - "a banner", - "", "┌─────┬──────┐", "│ Die │ Face │", "├─────┼──────┤", @@ -312,7 +307,7 @@ fn a_block_taller_than_the_tail_area_spills_its_top_rows_while_the_turn_runs() { let transcript = played.transcript(); let lines: Vec<&str> = transcript.lines().collect(); - assert_eq!(lines, vec!["a banner", "", TALL_ROWS[0], TALL_ROWS[1]]); + assert_eq!(lines, vec![TALL_ROWS[0], TALL_ROWS[1]]); } #[test] @@ -325,14 +320,7 @@ fn every_row_of_a_spilled_block_lands_in_the_transcript_exactly_once() { let lines: Vec<&str> = transcript.lines().collect(); assert_eq!( lines, - vec![ - "a banner", - "", - TALL_ROWS[0], - TALL_ROWS[1], - TALL_ROWS[2], - TALL_ROWS[3], - ] + vec![TALL_ROWS[0], TALL_ROWS[1], TALL_ROWS[2], TALL_ROWS[3]] ); } @@ -354,8 +342,6 @@ fn two_narrated_rounds_around_a_tool_line_keep_one_blank_row_between_the_blocks( assert_eq!( lines, vec![ - "a banner", - "", "You reach for the lock.", "", "It resists.", diff --git a/src/play/transcript_width_tests.rs b/src/play/transcript_width_tests.rs index 3334ab3..dcd393f 100644 --- a/src/play/transcript_width_tests.rs +++ b/src/play/transcript_width_tests.rs @@ -81,7 +81,6 @@ fn a_round_that_widens_mid_stream_still_says_what_it_has_left() { assert_eq!( scrollback(&narrow), [ - "", "one two three four", "five six seven eight", "nine ten eleven", @@ -111,7 +110,6 @@ fn a_round_takes_the_width_it_first_has_something_to_say_at() { assert_eq!( scrollback(&narrow), [ - "", "one two three four", "five six seven eight", "nine ten eleven", @@ -131,7 +129,7 @@ fn a_round_that_narrows_mid_stream_says_nothing_a_second_time() { transcript.flush(&narrow).unwrap(); transcript.place(&mut narrow).unwrap(); - assert_eq!(scrollback(&wide), ["", PARAGRAPH]); + assert_eq!(scrollback(&wide), [PARAGRAPH]); assert_eq!(scrollback(&narrow), ["", AFTER]); } @@ -150,7 +148,7 @@ fn a_row_too_wide_for_the_terminal_it_lands_on_keeps_every_word() { // The paragraph rendered as one row at eighty columns. All of it is // here, over the four rows twenty columns takes, and the row that // separates it from the block above is one blank row of spacing. - assert_eq!(scrollback(&wide), ["", AFTER]); + assert_eq!(scrollback(&wide), [AFTER]); assert_eq!( scrollback(&narrow), [ @@ -245,7 +243,6 @@ fn a_table_too_wide_for_the_screen_clips_every_row_at_the_same_column() { assert_eq!( scrollback(&narrow), [ - "", "┌─────┬─────┬─────┬─", "│ abc │ def │ ghi │", "├─────┼─────┼─────┼─", -- 2.51.2