diff --git a/README.md b/README.md index 0907702..6d7f35b 100644 --- a/README.md +++ b/README.md @@ -77,6 +77,11 @@ placement = "overlay" # overlay (full tab, default) | split | popup `plannotator-tui config` prints the file's path and the values in effect. The `herdr/` directory in this repo is the development manifest; users should install Herdr Annotate. +Actions forwarded by Herdr Mirror default to a split beside the invoking remote +pane. Mirror does not preserve overlay presentation, and Herdr 0.8.2 opens an +overlay in its server's active tab, which can differ from the tab you are viewing. +An explicit `--placement` or `PLANNOTATOR_TUI_PLACEMENT` still takes precedence. + ## Agent replies `plannotator-tui last` finds the transcript of the agent that launched your shell and shows a diff --git a/crates/plannotator-tui/src/herdr/context.rs b/crates/plannotator-tui/src/herdr/context.rs index aad4f8c..379882c 100644 --- a/crates/plannotator-tui/src/herdr/context.rs +++ b/crates/plannotator-tui/src/herdr/context.rs @@ -84,7 +84,7 @@ impl HerdrEnv { /// The focused pane from the context, only when Herdr saw an agent in it. pub(crate) fn focused_agent_pane(&self) -> Option { let context = self.context.as_ref()?; - let agent = context.focused_pane_agent.clone()?; + let agent = context.focused_pane_agent.clone().filter(|agent| !agent.trim().is_empty())?; Some(Target { pane: context.focused_pane_id.clone()?, agent: Some(agent) }) } @@ -171,6 +171,9 @@ mod tests { let shell = env(&[("HERDR_ENV", "1"), ("HERDR_PLUGIN_CONTEXT_JSON", r#"{"focused_pane_id":"w1:p2"}"#)]); assert_eq!(shell.delivery_target(), None); + let mirror_shell = + env(&[("HERDR_PLUGIN_CONTEXT_JSON", r#"{"focused_pane_id":"w1:p2","focused_pane_agent":""}"#)]); + assert_eq!(mirror_shell.delivery_target(), None); } #[test] diff --git a/crates/plannotator-tui/src/herdr/launch.rs b/crates/plannotator-tui/src/herdr/launch.rs index d77e67c..4c1669a 100644 --- a/crates/plannotator-tui/src/herdr/launch.rs +++ b/crates/plannotator-tui/src/herdr/launch.rs @@ -194,6 +194,15 @@ pub(crate) fn plan(env: &HerdrEnv, config: &Config, args: OpenArgs, cwd: &Path) let placement = match (args.placement, env.placement.as_deref()) { (Some(p), _) => p, (None, Some(text)) => text.parse().context("PLANNOTATOR_TUI_PLACEMENT")?, + // Mirror imports ordinary panes, not the remote client's overlays. Herdr + // 0.8.2 also places overlays in its globally active tab, which may differ + // from the invoking mirror. A targeted split reaches the intended tab. + (None, None) + if config.herdr.placement == Placement::Overlay + && context.and_then(|c| c.invocation_source.as_deref()) == Some("mirror") => + { + Placement::Split + } (None, None) => config.herdr.placement, }; diff --git a/crates/plannotator-tui/src/herdr/launch/tests.rs b/crates/plannotator-tui/src/herdr/launch/tests.rs index bf351fb..9cf2986 100644 --- a/crates/plannotator-tui/src/herdr/launch/tests.rs +++ b/crates/plannotator-tui/src/herdr/launch/tests.rs @@ -77,6 +77,40 @@ fn agent_skill_delivers_to_and_splits_beside_the_calling_pane() { std::fs::remove_dir_all(&root).expect("cleanup"); } +#[test] +fn mirror_defaults_to_a_split_next_to_the_remote_invoking_pane() { + let context = HerdrContext { + focused_pane_id: Some("w2:p4".into()), + focused_pane_agent: Some("codex".into()), + invocation_source: Some("mirror".into()), + ..HerdrContext::default() + }; + let env = env(None, Some(context)); + let launch = plan(&env, &Config::default(), OpenArgs::default(), Path::new("/")) + .expect("plans a mirrored document"); + assert_eq!(launch.placement, Placement::Split); + assert!(argv(&launch).windows(2).any(|pair| pair == ["--target-pane", "w2:p4"])); + let agent = r#"{"result":{"agent":{"agent":"codex","agent_session":{"kind":"path","value":"/sessions/exact.jsonl"}}}}"#; + let last = plan_last(&env, &Config::default(), OpenArgs::default(), Path::new("/"), None, Some(agent)) + .expect("plans the same pane's reply"); + assert_eq!(last.placement, Placement::Split); + assert_eq!(last.target_pane.as_deref(), Some("w2:p4")); + assert_eq!(last.session, Some(AgentSession::Path("/sessions/exact.jsonl".into()))); +} + +#[test] +fn explicit_placement_still_wins_over_the_mirror_default() { + let mut env = + env(None, Some(HerdrContext { invocation_source: Some("mirror".into()), ..HerdrContext::default() })); + env.placement = Some("popup".into()); + let launch = + plan(&env, &Config::default(), OpenArgs::default(), Path::new("/")).expect("environment placement"); + assert_eq!(launch.placement, Placement::Popup); + let args = OpenArgs { placement: Some(Placement::Overlay), ..OpenArgs::default() }; + let launch = plan(&env, &Config::default(), args, Path::new("/")).expect("explicit argument placement"); + assert_eq!(launch.placement, Placement::Overlay); +} + #[test] fn ctrl_click_opens_the_linked_file() { let root = temp_folder("click");