@@ -608,11 +608,9 @@ fn scene_terminal(h: &mut EditorTestHarness, s: &mut BlogShowcase) {
608608 }
609609 h. render ( ) . unwrap ( ) ;
610610 h. send_key ( KeyCode :: Enter , KeyModifiers :: NONE ) . unwrap ( ) ;
611- h. render ( ) . unwrap ( ) ;
612-
613- // Give the PTY a beat to spawn its shell and paint a prompt, but return as
614- // soon as the terminal pane shows output (usually well under the cap).
615- poll_until ( h, 300 , |h| h. screen_to_string ( ) . contains ( '$' ) ) ;
611+ // Wait for the terminal pane to exist (its tab name appears) — that's when
612+ // terminal_bg/fg start rendering. Returns the instant it's up.
613+ let _ = h. wait_until ( |h| h. screen_to_string ( ) . contains ( "Terminal" ) ) ;
616614 h. render ( ) . unwrap ( ) ;
617615 snap ( h, s, Some ( "Terminal" ) , 300 ) ;
618616
@@ -883,30 +881,33 @@ fn rewrite_theme_name(json: &str, token: &str) -> String {
883881 }
884882}
885883
884+ /// Create a throwaway config dir holding the theme version under `theme_token`
885+ /// as a user theme, so a harness can select it via `config.theme`. Each harness
886+ /// gets its *own* context (sharing one across harnesses breaks plugin loading),
887+ /// so the returned `TempDir` must be kept alive for the harness's lifetime.
888+ fn make_theme_ctx (
889+ theme_token : & str ,
890+ theme_json : & str ,
891+ ) -> Option < ( tempfile:: TempDir , DirectoryContext ) > {
892+ let cfg_temp = tempfile:: TempDir :: new ( ) . ok ( ) ?;
893+ let ctx = DirectoryContext :: for_testing ( cfg_temp. path ( ) ) ;
894+ let themes_dir = ctx. themes_dir ( ) ;
895+ fs:: create_dir_all ( & themes_dir) . ok ( ) ?;
896+ let rewritten = rewrite_theme_name ( theme_json, theme_token) ;
897+ fs:: write ( themes_dir. join ( format ! ( "{theme_token}.json" ) ) , & rewritten) . ok ( ) ?;
898+ Some ( ( cfg_temp, ctx) )
899+ }
900+
886901/// Render the scene suite with `theme_json` active, writing frames under
887902/// `docs/blog/<gallery_name>/`. Returns `true` if the theme actually loaded
888903/// and frames were produced; `false` (without writing frames) if the version
889904/// could not be selected — which happens when a baseline JSON is incompatible
890905/// with the current theme schema. The caller decides whether that's fatal.
891906fn try_render_version ( theme_token : & str , theme_json : & str , gallery_name : & str ) -> bool {
892- let cfg_temp = match tempfile:: TempDir :: new ( ) {
893- Ok ( t) => t,
894- Err ( e) => {
895- eprintln ! ( "theme-diff: could not create temp dir: {e}" ) ;
896- return false ;
897- }
898- } ;
899- let ctx = DirectoryContext :: for_testing ( cfg_temp. path ( ) ) ;
900- let themes_dir = ctx. themes_dir ( ) ;
901- if let Err ( e) = fs:: create_dir_all ( & themes_dir) {
902- eprintln ! ( "theme-diff: could not create themes dir: {e}" ) ;
907+ let Some ( ( cfg_temp, ctx) ) = make_theme_ctx ( theme_token, theme_json) else {
908+ eprintln ! ( "theme-diff: could not stage theme ctx for '{theme_token}'" ) ;
903909 return false ;
904- }
905- let rewritten = rewrite_theme_name ( theme_json, theme_token) ;
906- if let Err ( e) = fs:: write ( themes_dir. join ( format ! ( "{theme_token}.json" ) ) , & rewritten) {
907- eprintln ! ( "theme-diff: could not write theme file: {e}" ) ;
908- return false ;
909- }
910+ } ;
910911
911912 let mut config = Config {
912913 theme : ThemeName ( theme_token. to_string ( ) ) ,
@@ -921,9 +922,7 @@ fn try_render_version(theme_token: &str, theme_json: &str, gallery_name: &str) -
921922 let opts = HarnessOptions :: new ( )
922923 . with_project_root ( )
923924 . with_config ( config)
924- // Clone the shared dir context (themes dir) so the git/LSP scenes below
925- // can spin up their own harnesses against the same theme.
926- . with_shared_dir_context ( ctx. clone ( ) )
925+ . with_shared_dir_context ( ctx)
927926 . with_full_grammar_registry ( ) ;
928927 let mut h = match EditorTestHarness :: create ( 120 , 35 , opts) {
929928 Ok ( h) => h,
@@ -956,11 +955,12 @@ fn try_render_version(theme_token: &str, theme_json: &str, gallery_name: &str) -
956955
957956 run_all_scenes ( & mut h, & mut s) ;
958957 drop ( h) ; // release the main harness before spinning up the extra ones
958+ drop ( cfg_temp) ; // main theme ctx no longer needed
959959
960960 // Scenes that need a different harness setup (own working dir / LSP config)
961- // but the same theme. They share the themes dir via the cloned dir context .
962- scene_git_file_status ( & mut s, ctx . clone ( ) , theme_token ) ;
963- scene_lsp_status ( & mut s, ctx , theme_token ) ;
961+ // but the same theme. Each makes its own theme ctx (see make_theme_ctx) .
962+ scene_git_file_status ( & mut s, theme_token , theme_json ) ;
963+ scene_lsp_status ( & mut s, theme_token , theme_json ) ;
964964
965965 s. finalize ( ) . expect ( "finalize diff gallery" ) ;
966966 true
@@ -971,15 +971,19 @@ fn try_render_version(theme_token: &str, theme_json: &str, gallery_name: &str) -
971971/// file_status_added_fg, file_status_untracked_fg. Best-effort — if the git
972972/// plugin/status doesn't surface in time it snaps whatever rendered (the same
973973/// on both before/after, so frames stay aligned).
974- fn scene_git_file_status ( s : & mut BlogShowcase , ctx : DirectoryContext , theme_token : & str ) {
974+ fn scene_git_file_status ( s : & mut BlogShowcase , theme_token : & str , theme_json : & str ) {
975+ let Some ( ( _cfg_temp, ctx) ) = make_theme_ctx ( theme_token, theme_json) else {
976+ return ;
977+ } ;
975978 let repo = GitTestRepo :: new ( ) ;
976979 repo. setup_git_explorer_plugin ( ) ;
977- repo. create_file ( "src/main.rs" , "fn main() {}\n " ) ;
980+ // Root-level files so the explorer shows them without expanding a subdir.
981+ repo. create_file ( "main.rs" , "fn main() {}\n " ) ;
978982 repo. create_file ( "README.md" , "# demo\n " ) ;
979983 repo. git_add_all ( ) ;
980984 repo. git_commit ( "initial" ) ;
981- // Now create the working -tree states the explorer decorates.
982- repo. modify_file ( "src/ main.rs" , "fn main() { println!(\" hi\" ); }\n " ) ;
985+ // Working -tree states the explorer decorates: a modified + an untracked file .
986+ repo. modify_file ( "main.rs" , "fn main() { println!(\" hi\" ); }\n " ) ;
983987 repo. create_file ( "notes.txt" , "untracked\n " ) ;
984988
985989 let config = Config {
@@ -1000,13 +1004,11 @@ fn scene_git_file_status(s: &mut BlogShowcase, ctx: DirectoryContext, theme_toke
10001004 } ;
10011005
10021006 h. editor_mut ( ) . toggle_file_explorer ( ) ;
1003- poll_until ( & mut h, 2000 , |h| {
1004- h. screen_to_string ( ) . contains ( "File Explorer" )
1005- } ) ;
1006- // Bounded wait for the git plugin to decorate a modified file. `wait_until`
1007- // has no timeout, so we poll ourselves and snap whatever rendered — the
1008- // same on both before/after, so frames stay aligned even if git is slow.
1009- poll_until ( & mut h, 3000 , |h| {
1007+ let _ = h. wait_until ( |h| h. screen_to_string ( ) . contains ( "File Explorer" ) ) ;
1008+ // Wait for the git plugin to decorate the modified file with its "M"
1009+ // status (file_status_modified_fg). `wait_until` returns the instant the
1010+ // condition holds, pumping async each tick (~tens of ms in practice).
1011+ let _ = h. wait_until ( |h| {
10101012 h. screen_to_string ( )
10111013 . lines ( )
10121014 . any ( |l| l. contains ( "main.rs" ) && l. contains ( 'M' ) )
@@ -1015,31 +1017,13 @@ fn scene_git_file_status(s: &mut BlogShowcase, ctx: DirectoryContext, theme_toke
10151017 snap ( & mut h, s, Some ( "Git Status" ) , 300 ) ;
10161018}
10171019
1018- /// Poll up to `max_ms`, ticking the harness, until `cond` holds. Returns
1019- /// whether the condition was met. Unlike `wait_until` this has a timeout, so a
1020- /// best-effort scene never hangs the suite when a feature doesn't surface.
1021- fn poll_until (
1022- h : & mut EditorTestHarness ,
1023- max_ms : u64 ,
1024- mut cond : impl FnMut ( & EditorTestHarness ) -> bool ,
1025- ) -> bool {
1026- let start = std:: time:: Instant :: now ( ) ;
1027- loop {
1028- let _ = h. tick_and_render ( ) ;
1029- if cond ( h) {
1030- return true ;
1031- }
1032- if start. elapsed ( ) . as_millis ( ) as u64 >= max_ms {
1033- return false ;
1034- }
1035- h. sleep ( std:: time:: Duration :: from_millis ( 50 ) ) ;
1036- }
1037- }
1038-
10391020/// Scene: LSP status indicator in the status bar (own harness with a fake LSP
10401021/// attached, sharing the theme). Covers: ui.status_lsp_on_fg/bg. Best-effort
10411022/// and PTY/bash-dependent; skips cleanly if the fake server can't spawn.
1042- fn scene_lsp_status ( s : & mut BlogShowcase , ctx : DirectoryContext , theme_token : & str ) {
1023+ fn scene_lsp_status ( s : & mut BlogShowcase , theme_token : & str , theme_json : & str ) {
1024+ let Some ( ( _cfg_temp, ctx) ) = make_theme_ctx ( theme_token, theme_json) else {
1025+ return ;
1026+ } ;
10431027 let lsp_dir = match tempfile:: TempDir :: new ( ) {
10441028 Ok ( d) => d,
10451029 Err ( _) => return ,
@@ -1087,7 +1071,8 @@ fn scene_lsp_status(s: &mut BlogShowcase, ctx: DirectoryContext, theme_token: &s
10871071 . with_working_dir ( work_dir)
10881072 . with_config ( config)
10891073 . with_shared_dir_context ( ctx)
1090- . without_empty_plugins_dir ( ) ;
1074+ // LSP status needs no plugins; skip embedded plugin loading for speed.
1075+ . with_empty_plugins_dir ( ) ;
10911076 let mut h = match EditorTestHarness :: create ( 120 , 35 , opts) {
10921077 Ok ( h) => h,
10931078 Err ( e) => {
@@ -1099,11 +1084,10 @@ fn scene_lsp_status(s: &mut BlogShowcase, ctx: DirectoryContext, theme_token: &s
10991084 if h. open_file ( & test_file) . is_err ( ) {
11001085 return ;
11011086 }
1102- // Let the fake server initialize so the status-bar LSP indicator settles.
1103- for _ in 0 ..12 {
1104- let _ = h. process_async_and_render ( ) ;
1105- h. sleep ( std:: time:: Duration :: from_millis ( 50 ) ) ;
1106- }
1087+ // Wait until the fake server is running and the status bar shows the
1088+ // "on" pill (status_lsp_on_* colors). `wait_until` returns the instant
1089+ // it appears, pumping async LSP messages each tick.
1090+ let _ = h. wait_until ( |h| h. screen_to_string ( ) . contains ( "LSP (on)" ) ) ;
11071091 let _ = h. render ( ) ;
11081092 snap ( & mut h, s, Some ( "LSP Status" ) , 300 ) ;
11091093
0 commit comments