authorgravatar for git@paperclover.netclover caruso <git@paperclover.net> 2026-10-02 21:17:44-07:00
committergravatar for git@paperclover.netclover caruso <git@paperclover.net> 2026-10-02 21:38:18-07:00
loge1eb16f90d89d57b6c0a1003cb2a9468c121c530
tree804f377636765446eb0a78d4bcb57ccedf3793af
parentc574cadb7f1d18457fb810169f3ec9cd230b9be6
signature Signed by SSH key SHA256:52mNGHRsVFBDED9IAX5pe+LRWUefqTbxEReunq21QvU

fix: Open Shared Notebook sits in the sidebar's footer and beside Open Existing

With a notebook open, Open Shared Notebook was only in the File menu and the command palette (⌘⇧P), both of which work, but people looked where Open Existing is. The sidebar's footer now offers Open Shared… between Open Existing and Options, and the welcome screen lists it straight after Open Existing. Assisted-by: claude-opus-5.5

2 files changed, 50 insertions(+), 6 deletions(-)

crates/snowbound/src/sidebar.rs+17-6
...@@ -39,6 +39,8 @@ pub enum Action {...@@ -39,6 +39,8 @@ pub enum Action {
39 Fold(String),39 Fold(String),
40 NewNotebook,40 NewNotebook,
41 OpenNotebook,41 OpenNotebook,
42 #[cfg(feature = "live")]
43 OpenShared,
42 /// Signs in again to the notebook opened from its server at this location.44 /// Signs in again to the notebook opened from its server at this location.
43 SignIn(String),45 SignIn(String),
44 Options,46 Options,
...@@ -273,6 +275,13 @@ fn sidebar(...@@ -273,6 +275,13 @@ fn sidebar(
273 "Open Existing",275 "Open Existing",
274 Action::OpenNotebook,276 Action::OpenNotebook,
275 ),277 ),
278 #[cfg(feature = "live")]
279 (
280 "shared",
281 Leading::Icon(art::LINK),
282 "Open Shared…",
283 Action::OpenShared,
284 ),
276 (285 (
277 "options",286 "options",
278 Leading::Icon(art::OPTIONS),287 Leading::Icon(art::OPTIONS),
...@@ -843,6 +852,8 @@ impl crate::State {...@@ -843,6 +852,8 @@ impl crate::State {
843 Some(Action::Renamed(keep)) => self.finish_renaming(keep),852 Some(Action::Renamed(keep)) => self.finish_renaming(keep),
844 Some(Action::NewNotebook) => self.commands.push(crate::Command::NewNotebook),853 Some(Action::NewNotebook) => self.commands.push(crate::Command::NewNotebook),
845 Some(Action::OpenNotebook) => self.commands.push(crate::Command::OpenNotebook),854 Some(Action::OpenNotebook) => self.commands.push(crate::Command::OpenNotebook),
855 #[cfg(feature = "live")]
856 Some(Action::OpenShared) => self.open_shared(),
846 Some(Action::SignIn(location)) => self857 Some(Action::SignIn(location)) => self
847 .commands858 .commands
848 .push(crate::Command::OpenFromServer(Some(location))),859 .push(crate::Command::OpenFromServer(Some(location))),
...@@ -1252,12 +1263,6 @@ impl crate::State {...@@ -1252,12 +1263,6 @@ impl crate::State {
1252 "Open Existing",1263 "Open Existing",
1253 crate::Command::OpenNotebook,1264 crate::Command::OpenNotebook,
1254 ),1265 ),
1255 (
1256 "server",
1257 Leading::Icon(art::SERVER),
1258 "Open Notebook from Server…",
1259 crate::Command::OpenFromServer(None),
1260 ),
1261 #[cfg(feature = "live")]1266 #[cfg(feature = "live")]
1262 (1267 (
1263 "shared",1268 "shared",
...@@ -1265,6 +1270,12 @@ impl crate::State {...@@ -1265,6 +1270,12 @@ impl crate::State {
1265 "Open Shared Notebook…",1270 "Open Shared Notebook…",
1266 crate::Command::OpenShared,1271 crate::Command::OpenShared,
1267 ),1272 ),
1273 (
1274 "server",
1275 Leading::Icon(art::SERVER),
1276 "Open Notebook from Server…",
1277 crate::Command::OpenFromServer(None),
1278 ),
1268 ]1279 ]
1269 .into_iter()1280 .into_iter()
1270 .chain(crate::guide::OFFERED.then_some((1281 .chain(crate::guide::OFFERED.then_some((
crates/snowbound/tests/replay.rs+33
...@@ -549,3 +549,36 @@ fn stop_sharing_ends_the_share() {...@@ -549,3 +549,36 @@ fn stop_sharing_ends_the_share() {
549 let kept = std::fs::read_to_string(kept).unwrap_or_default();549 let kept = std::fs::read_to_string(kept).unwrap_or_default();
550 assert!(!kept.contains("code"), "still kept: {kept}");550 assert!(!kept.contains("code"), "still kept: {kept}");
551}551}
552
553/// Open Shared Notebook is where people look with notebooks open: in the sidebar's footer,
554/// and in the palette, which opens it.
555#[cfg(feature = "live")]
556#[test]
557fn open_shared_notebook_is_offered_with_notebooks_open() {
558 let scratch = Scratch::new("open-shared");
559 std::fs::write(
560 scratch.0.join("settings.json"),
561 r#"{"user_name": "Snowbound Test", "sidebar": true}"#,
562 )
563 .unwrap();
564 let notebook =
565 Path::new(env!("CARGO_MANIFEST_DIR")).join("../../corpus/cross-container/candidate");
566 let mut steps = vec!["accessibility sidebar", "modifiers command shift", "key p"];
567 steps.extend([
568 "modifiers",
569 "settle",
570 "type Open Shared",
571 "settle",
572 "key Enter",
573 ]);
574 steps.extend(["settle", "accessibility dialog"]);
575 let variables = [("SNOWBOUND_LIVE", "off"), ("SNOWBOUND_LIVE_RELAY", "off")];
576 let [sidebar, dialog] = replay_with(&scratch, Some(&notebook), &steps, &variables)
577 .try_into()
578 .unwrap();
579 assert!(sidebar.contains(r#""Open Shared…""#), "{sidebar}");
580 assert!(
581 dialog.contains(r#"Dialog "Open Shared Notebook""#),
582 "{dialog}"
583 );
584}