diff --git a/Cargo.lock b/Cargo.lock
index 511985cf2710449d9da23585f6ec1d8e192aab4a..0562121dbb7475db079cb6639d03a73328a04334 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -536,11 +536,12 @@ dependencies = [
 
 [[package]]
 name = "curve25519-dalek"
-version = "4.0.0-rc.2"
-source = "git+https://github.com/dalek-cryptography/curve25519-dalek#e111b5d913e8c857acf7589303f655f1fab2d64a"
+version = "4.0.0"
+source = "git+https://github.com/mkj/curve25519-dalek?branch=sunset#e4d2869ade3a2e511d54293a74e28be1d6125bdd"
 dependencies = [
  "cfg-if",
  "cpufeatures",
+ "curve25519-dalek-derive",
  "digest 0.10.7",
  "fiat-crypto",
  "platforms",
@@ -549,6 +550,16 @@ dependencies = [
  "zeroize",
 ]
 
+[[package]]
+name = "curve25519-dalek-derive"
+version = "0.1.0"
+source = "git+https://github.com/mkj/curve25519-dalek?branch=sunset#e4d2869ade3a2e511d54293a74e28be1d6125bdd"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.28",
+]
+
 [[package]]
 name = "cyw43"
 version = "0.1.0"
@@ -624,7 +635,7 @@ dependencies = [
  "proc-macro2",
  "quote",
  "strsim",
- "syn 2.0.18",
+ "syn 2.0.28",
 ]
 
 [[package]]
@@ -646,7 +657,7 @@ checksum = "29a358ff9f12ec09c3e61fef9b5a9902623a695a46a917b07f269bff1445611a"
 dependencies = [
  "darling_core 0.20.1",
  "quote",
- "syn 2.0.18",
+ "syn 2.0.28",
 ]
 
 [[package]]
@@ -808,10 +819,10 @@ dependencies = [
 
 [[package]]
 name = "ed25519-dalek"
-version = "2.0.0-rc.2"
-source = "git+https://github.com/mkj/ed25519-dalek?branch=sunset#29bf06dbb8867bf230ff18c6b5819d38c771dc84"
+version = "2.0.0-rc.3"
+source = "git+https://github.com/mkj/curve25519-dalek?branch=sunset#e4d2869ade3a2e511d54293a74e28be1d6125bdd"
 dependencies = [
- "curve25519-dalek 4.0.0-rc.2",
+ "curve25519-dalek 4.0.0",
  "ed25519 2.2.1",
  "rand_core 0.6.4",
  "sha2 0.10.7",
@@ -926,7 +937,7 @@ dependencies = [
  "darling 0.20.1",
  "proc-macro2",
  "quote",
- "syn 2.0.18",
+ "syn 2.0.28",
 ]
 
 [[package]]
@@ -1334,7 +1345,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.18",
+ "syn 2.0.28",
 ]
 
 [[package]]
@@ -2115,18 +2126,18 @@ dependencies = [
 
 [[package]]
 name = "proc-macro2"
-version = "1.0.60"
+version = "1.0.66"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dec2b086b7a862cf4de201096214fa870344cf922b2b30c167badb3af3195406"
+checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9"
 dependencies = [
  "unicode-ident",
 ]
 
 [[package]]
 name = "quote"
-version = "1.0.28"
+version = "1.0.32"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488"
+checksum = "50f3b39ccfb720540debaa0164757101c08ecb8d326b15358ce76a62c7e85965"
 dependencies = [
  "proc-macro2",
 ]
@@ -2691,10 +2702,10 @@ dependencies = [
  "chacha20",
  "cipher",
  "ctr",
- "curve25519-dalek 4.0.0-rc.2",
+ "curve25519-dalek 4.0.0",
  "defmt",
  "digest 0.10.7",
- "ed25519-dalek 2.0.0-rc.2",
+ "ed25519-dalek 2.0.0-rc.3",
  "embedded-io",
  "futures",
  "getrandom 0.2.10",
@@ -2749,7 +2760,7 @@ version = "0.1.0"
 dependencies = [
  "bcrypt",
  "defmt",
- "ed25519-dalek 2.0.0-rc.2",
+ "ed25519-dalek 2.0.0-rc.3",
  "embassy-futures",
  "embassy-net",
  "embassy-net-driver",
@@ -2870,9 +2881,9 @@ dependencies = [
 
 [[package]]
 name = "syn"
-version = "2.0.18"
+version = "2.0.28"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "32d41677bcbe24c20c52e7c70b0d8db04134c5d1066bf98662e2871ad200ea3e"
+checksum = "04361975b3f5e348b2189d8dc55bc942f278b2d482a6a0365de5bdd62d351567"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -2916,7 +2927,7 @@ checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.18",
+ "syn 2.0.28",
 ]
 
 [[package]]
@@ -2984,7 +2995,7 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.18",
+ "syn 2.0.28",
 ]
 
 [[package]]
@@ -3134,7 +3145,7 @@ dependencies = [
  "once_cell",
  "proc-macro2",
  "quote",
- "syn 2.0.18",
+ "syn 2.0.28",
  "wasm-bindgen-shared",
 ]
 
@@ -3156,7 +3167,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.18",
+ "syn 2.0.28",
  "wasm-bindgen-backend",
  "wasm-bindgen-shared",
 ]
@@ -3286,10 +3297,10 @@ checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
 
 [[package]]
 name = "x25519-dalek"
-version = "2.0.0-rc.2"
-source = "git+https://github.com/dalek-cryptography/x25519-dalek#f683cf4d501549bbfc7b24a2af7ebafb6dc0267b"
+version = "2.0.0-rc.3"
+source = "git+https://github.com/mkj/curve25519-dalek?branch=sunset#e4d2869ade3a2e511d54293a74e28be1d6125bdd"
 dependencies = [
- "curve25519-dalek 4.0.0-rc.2",
+ "curve25519-dalek 4.0.0",
  "rand_core 0.6.4",
  "zeroize",
 ]
@@ -3311,5 +3322,5 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.18",
+ "syn 2.0.28",
 ]
diff --git a/Cargo.toml b/Cargo.toml
index 3ca4054222408ef722143fc64facb69c37244fed..b40b404867142a9faebfbbd61bb37ffab5e184e8 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -45,9 +45,9 @@ cipher = { version = "0.4", features = ["zeroize"] }
 subtle = { version = "2.4", default-features = false }
 # ed25519/x25519
 # fork allows hashing by parts (sign/verify from sshwire), and zeroize
-ed25519-dalek = { version = "2.0.0-rc.2", default-features = false, features = ["zeroize", "rand_core", "hazmat"] }
-x25519-dalek = { version = "2.0.0-rc.2", default-features = false, features = ["zeroize"] }
-curve25519-dalek = { version = "4.0.0-rc.2", default-features = false, features = ["zeroize"] }
+ed25519-dalek = { version = "2.0.0-rc.3", default-features = false, features = ["zeroize", "rand_core", "hazmat"] }
+x25519-dalek = { version = "2.0.0-rc.3", default-features = false, features = ["zeroize"] }
+curve25519-dalek = { version = "4.0.0", default-features = false, features = ["zeroize"] }
 
 rsa = { version = "0.8", default-features = false, optional = true, features = ["sha2"] }
 # TODO: getrandom feature is a workaround for missing ssh-key dependency with rsa. fixed in pending 0.6
@@ -81,10 +81,12 @@ simplelog = { version = "0.12", features = ["test"] }
 
 
 [patch.crates-io]
-curve25519-dalek = { git = "https://github.com/dalek-cryptography/curve25519-dalek" }
-x25519-dalek = { git = "https://github.com/dalek-cryptography/x25519-dalek" }
-ed25519-dalek = { git = "https://github.com/mkj/ed25519-dalek", branch = "sunset" }
-# ed25519-dalek = { path = "/home/matt/3rd/rs/crypto/ed25519-dalek" }
+curve25519-dalek = { git = "https://github.com/mkj/curve25519-dalek", branch = "sunset" }
+ed25519-dalek = { git = "https://github.com/mkj/curve25519-dalek", branch = "sunset" }
+x25519-dalek = { git = "https://github.com/mkj/curve25519-dalek", branch = "sunset" }
+# curve25519-dalek = { path = "/home/matt/3rd/rs/crypto/curve25519-dalek/curve25519-dalek" }
+# ed25519-dalek = { path = "/home/matt/3rd/rs/crypto/curve25519-dalek/ed25519-dalek" }
+# x25519-dalek = { path = "/home/matt/3rd/rs/crypto/curve25519-dalek/x25519-dalek" }
 
 embassy-executor = { git = "https://github.com/embassy-rs/embassy", rev = "2eb7a67c7027c6768fa95031caf60bcd0eade1ad" }
 embassy-futures = { git = "https://github.com/embassy-rs/embassy", rev = "2eb7a67c7027c6768fa95031caf60bcd0eade1ad" }
diff --git a/async/examples/sunsetc.rs b/async/examples/sunsetc.rs
index b6b4493cfc4c600c5a1786aedd33b85fcda8dd3d..2c2e549ebd1b795e9e5928ea4cff29c4c258d3c0 100644
--- a/async/examples/sunsetc.rs
+++ b/async/examples/sunsetc.rs
@@ -77,7 +77,7 @@ async fn run(args: Args) -> Result<()> {
     let ssh_task = spawn_local(async move {
         let mut rxbuf = Zeroizing::new(vec![0; 3000]);
         let mut txbuf = Zeroizing::new(vec![0; 3000]);
-        let cli = SSHClient::new(&mut rxbuf, &mut txbuf)?;
+        let ssh = SSHClient::new(&mut rxbuf, &mut txbuf)?;
 
         let mut app = CmdlineClient::new(
             args.username.as_ref().unwrap(),
@@ -110,27 +110,27 @@ async fn run(args: Args) -> Result<()> {
         let mut rsock = FromTokio::new(rsock);
         let mut wsock = FromTokio::new(wsock);
 
-        let (hooks, mut cmd) = app.split();
+        let (hooks, mut cmdrun) = app.split();
 
         let hooks = Mutex::<SunsetRawMutex, _>::new(hooks);
 
-        let ssh = async {
-            let r = cli.run(&mut rsock, &mut wsock, &hooks).await;
+        // SSH connection future
+        let ssh_fut = async {
+            let r = ssh.run(&mut rsock, &mut wsock, &hooks).await;
             trace!("ssh run finished {r:?}");
             hooks.lock().await.exited().await;
             r
         };
 
-        // Circular reference here, cli -> cmd and cmd->cli
-        let session = cmd.run(&cli);
+        // Client session future
         let session = async {
-            let r = session.await;
+            let r = cmdrun.run(&ssh).await;
             trace!("client session run finished");
-            cli.exit().await;
+            ssh.exit().await;
             r
         };
 
-        let (res_ssh, res_session) = futures::future::join(ssh, session).await;
+        let (res_ssh, res_session) = futures::future::join(ssh_fut, session).await;
         debug!("res_ssh {res_ssh:?}");
         debug!("res_session {res_session:?}");
         res_ssh?;
diff --git a/embassy/demos/common/src/lib.rs b/embassy/demos/common/src/lib.rs
index afb4084f1ac8f2a2f452ee1de74c75711cb34d95..53c4861179e83a1771e9b738695b263fc700c79c 100644
--- a/embassy/demos/common/src/lib.rs
+++ b/embassy/demos/common/src/lib.rs
@@ -10,7 +10,7 @@ pub mod config;
 pub mod menu;
 pub mod demo_menu;
 
-pub use server::{Shell, listener};
+pub use server::{DemoServer, listener};
 pub use config::SSHConfig;
 pub use demo_menu::BufOutput;
 
diff --git a/embassy/demos/common/src/server.rs b/embassy/demos/common/src/server.rs
index d6c9fe043627405a98f5e32d0c527b7cfc2e2514..9fdcca981e1dd9beb3e5010a005eacc9eea823d3 100644
--- a/embassy/demos/common/src/server.rs
+++ b/embassy/demos/common/src/server.rs
@@ -46,7 +46,7 @@ macro_rules! singleton {
 
 
 // common entry point
-pub async fn listener<D: Driver, S: Shell>(stack: &'static Stack<D>,
+pub async fn listener<D: Driver, S: DemoServer>(stack: &'static Stack<D>,
     config: &SunsetMutex<SSHConfig>,
     init: S::Init) -> ! {
     // TODO: buffer size?
@@ -83,16 +83,16 @@ pub async fn listener<D: Driver, S: Shell>(stack: &'static Stack<D>,
 }
 
 /// Run a SSH session when a socket accepts a connection
-async fn session<S: Shell>(socket: &mut TcpSocket<'_>, config: &SunsetMutex<SSHConfig>,
+async fn session<S: DemoServer>(socket: &mut TcpSocket<'_>, config: &SunsetMutex<SSHConfig>,
     init: &S::Init) -> sunset::Result<()> {
     // OK unwrap: has been accepted
     let src = socket.remote_endpoint().unwrap();
     info!("Connection from {}:{}", src.addr, src.port);
 
-    let shell = S::new(init);
+    let s = S::new(init);
 
     let conf = config.lock().await.clone();
-    let app = DemoServer::new(&shell, conf)?;
+    let app = ServerApp::new(&s, conf)?;
     let app = Mutex::<NoopRawMutex, _>::new(app);
 
     let mut ssh_rxbuf = [0; 2000];
@@ -100,7 +100,7 @@ async fn session<S: Shell>(socket: &mut TcpSocket<'_>, config: &SunsetMutex<SSHC
     let serv = SSHServer::new(&mut ssh_rxbuf, &mut ssh_txbuf)?;
     let serv = &serv;
 
-    let session = shell.run(serv);
+    let session = s.run(serv);
 
     let (mut rsock, mut wsock) = socket.split();
 
@@ -115,7 +115,10 @@ async fn session<S: Shell>(socket: &mut TcpSocket<'_>, config: &SunsetMutex<SSHC
     Ok(())
 }
 
-struct DemoServer<'a, S: Shell> {
+/// Provides `ServBehaviour` for the server
+///
+/// Further customisations are provided by `DemoServer` generic
+struct ServerApp<'a, S: DemoServer> {
     config: SSHConfig,
 
     handle: Option<ChanHandle>,
@@ -124,7 +127,7 @@ struct DemoServer<'a, S: Shell> {
     shell: &'a S,
 }
 
-impl<'a, S: Shell> DemoServer<'a, S> {
+impl<'a, S: DemoServer> ServerApp<'a, S> {
     const ADMIN_USER: &'static str = "config";
 
     fn new(shell: &'a S, config: SSHConfig) -> Result<Self> {
@@ -142,7 +145,7 @@ impl<'a, S: Shell> DemoServer<'a, S> {
     }
 }
 
-impl<'a, S: Shell> ServBehaviour for DemoServer<'a, S> {
+impl<'a, S: DemoServer> ServBehaviour for ServerApp<'a, S> {
 
     fn hostkeys(&mut self) -> BhResult<heapless::Vec<&SignKey, 2>> {
         // OK unwrap: only one element
@@ -226,19 +229,23 @@ impl<'a, S: Shell> ServBehaviour for DemoServer<'a, S> {
     }
 }
 
-pub trait Shell {
-    type Init: Copy;
+pub trait DemoServer {
+    /// State to be passed to each new connection by the server
+    type Init;
 
     fn new(init: &Self::Init) -> Self;
 
+    /// Called when auth succeeds
     #[allow(unused_variables)]
-    // TODO: eventually the compiler should add must_use automatically?
     async fn authed(&self, username: &str) {
         info!("Authenticated")
     }
 
+    /// Called when a shell is opened after auth succeeds
     fn open_shell(&self, handle: ChanHandle);
 
+    /// A task to run for each incoming connection.
+    // TODO: eventually the compiler should add must_use automatically?
     #[must_use]
     async fn run<'f, S: ServBehaviour>(&self, serv: &'f SSHServer<'f, S>) -> Result<()>;
 }
@@ -250,7 +257,7 @@ pub trait Shell {
 pub struct BufOutput {
     /// Sufficient to hold output produced from a single keystroke input. Further output will be discarded
     // pub s: String<300>,
-    // todo
+    // todo size
     pub s: String<3000>,
 }
 
diff --git a/embassy/demos/picow/src/main.rs b/embassy/demos/picow/src/main.rs
index 9603fd6a43923d12c1a8815fce79db9e1d4de4eb..cb4089ac80fa79c88eba2fa29a2381bc8d894fde 100644
--- a/embassy/demos/picow/src/main.rs
+++ b/embassy/demos/picow/src/main.rs
@@ -52,7 +52,7 @@ compile_error!("No network device selected. Use cyw43 or w5500 feature");
 #[cfg(all(feature = "cyw43", feature = "w5500"))]
 compile_error!("Select only one of cyw43 or w5500");
 
-use demo_common::{SSHConfig, Shell};
+use demo_common::{SSHConfig, DemoServer};
 
 use takepipe::TakePipe;
 
@@ -66,9 +66,11 @@ async fn main(spawner: Spawner) {
 
     let mut p = embassy_rp::init(Default::default());
 
+    // RNG initialised early, all crypto relies on it
     caprand::setup(&mut p.PIN_10).unwrap();
     getrandom::register_custom_getrandom!(caprand::getrandom);
 
+    // Configuration loaded from flash
     let mut flash = embassy_rp::flash::Flash::new(p.FLASH);
 
     let config = if option_env!("RESET_CONFIG").is_some() {
@@ -76,16 +78,16 @@ async fn main(spawner: Spawner) {
     } else {
         flashconfig::load_or_create(&mut flash).unwrap()
     };
-
     let flash = &*singleton!(SunsetMutex::new(flash));
-
     let config = &*singleton!(SunsetMutex::new(config));
 
+    // A shared pipe to a local USB-serial (CDC)
     let usb_pipe = {
         let p = singleton!(takepipe::TakePipeStorage::new());
         singleton!(p.pipe())
     };
 
+    // A shared pipe to a local uart
     let serial1_pipe = {
         let s = singleton!(takepipe::TakePipeStorage::new());
         singleton!(s.pipe())
@@ -101,12 +103,15 @@ async fn main(spawner: Spawner) {
         ))
         .unwrap();
 
+    // Watchdog currently only used for triggering reset manually
     let watchdog = singleton!(SunsetMutex::new(
         embassy_rp::watchdog::Watchdog::new(p.WATCHDOG)
     ));
 
     let state;
 
+    // Spawn network tasks to handle incoming connections with demo_common::session()
+
     #[cfg(feature = "cyw43")]
     {
         let stack = wifi::wifi_stack(
@@ -140,6 +145,7 @@ async fn main(spawner: Spawner) {
         }
     }
 
+    // USB task requires `state`
     spawner.spawn(usbserial::task(p.USB, state)).unwrap();
 }
 
@@ -151,7 +157,7 @@ async fn cyw43_listener(
     config: &'static SunsetMutex<SSHConfig>,
     global: &'static GlobalState,
 ) -> ! {
-    demo_common::listener::<_, DemoShell>(stack, config, global).await
+    demo_common::listener::<_, PicoServer>(stack, config, global).await
 }
 
 #[cfg(feature = "w5500")]
@@ -161,11 +167,11 @@ async fn w5500_listener(
     config: &'static SunsetMutex<SSHConfig>,
     global: &'static GlobalState,
 ) -> ! {
-    demo_common::listener::<_, DemoShell>(stack, config, global).await
+    demo_common::listener::<_, PicoServer>(stack, config, global).await
 }
 
 pub(crate) struct GlobalState {
-    // If taking multiple mutexes, lock in the order below avoid inversion.
+    // If locking multiple mutexes, always lock in the order below avoid inversion.
     pub usb_pipe: &'static TakePipe<'static>,
     pub serial1_pipe: &'static TakePipe<'static>,
 
@@ -178,7 +184,7 @@ pub(crate) struct GlobalState {
     pub net_mac: [u8; 6],
 }
 
-struct DemoShell {
+struct PicoServer {
     notify: Signal<NoopRawMutex, ChanHandle>,
     global: &'static GlobalState,
 
@@ -186,6 +192,8 @@ struct DemoShell {
     username: SunsetMutex<String<20>>,
 }
 
+// Presents a menu, either on serial or incoming SSH
+//
 // `local` is set for usb serial menus which require different auth
 async fn menu<R, W>(
     chanr: &mut R,
@@ -235,6 +243,7 @@ where
     Ok(())
 }
 
+/// Forwards an incoming SSH connection to a local serial port, either uart or USB
 pub(crate) async fn serial<R, W>(
     chanr: &mut R,
     chanw: &mut W,
@@ -292,7 +301,7 @@ where
     Ok(())
 }
 
-impl Shell for DemoShell {
+impl DemoServer for PicoServer {
     type Init = &'static GlobalState;
 
     fn new(global: &Self::Init) -> Self {
diff --git a/embassy/demos/picow/src/picowmenu.rs b/embassy/demos/picow/src/picowmenu.rs
index 6088c77913ca5cc7b87f944ff7abd7001b0b9124..53b301f1467e29137f9c4345c3f51a94844e22b4 100644
--- a/embassy/demos/picow/src/picowmenu.rs
+++ b/embassy/demos/picow/src/picowmenu.rs
@@ -41,13 +41,14 @@ const MAX_PW_LEN: usize = 50;
 pub(crate) struct MenuCtx {
     pub out: BufOutput,
     state: &'static GlobalState,
+
+    // true for local serial console menu, false for SSH menu
     local: bool,
 
-    // flags to be handled by the calling async loop
+    // flags to be handled by progress() called from the async loop
     switch_usb1: bool,
     switch_serial1: bool,
     need_save: bool,
-
     logout: bool,
     reset: bool,
     bootsel: bool,
@@ -68,6 +69,9 @@ impl MenuCtx {
         }
     }
 
+    /// Calls a closure with the config.
+    ///
+    /// Any modifications will be saved to flash (on a future `progress()` call).
     fn with_config<F>(&mut self, f: F) -> bool
     where
         F: FnOnce(&mut SSHConfig, &mut BufOutput),
@@ -79,7 +83,12 @@ impl MenuCtx {
                 return false;
             }
         };
+        let prev_config = c.clone();
         f(c.deref_mut(), &mut self.out);
+        // test whether config was modified
+        if *c != prev_config {
+            self.need_save = true
+        }
         true
     }
 
@@ -557,14 +566,12 @@ fn do_key(_item: &Item<MenuCtx>, args: &[&str], context: &mut MenuCtx) {
         let _ = writeln!(context, "Bad slot");
         return;
     }
-    context.need_save = true;
 
     let _ = writeln!(context, "todo openssh key parsing");
 }
 
 fn do_clear_key(_item: &Item<MenuCtx>, args: &[&str], context: &mut MenuCtx) {
     let _ = writeln!(context, "todo");
-    context.need_save = true;
 }
 
 fn do_console_pw(_item: &Item<MenuCtx>, args: &[&str], context: &mut MenuCtx) {
@@ -579,7 +586,6 @@ fn do_console_pw(_item: &Item<MenuCtx>, args: &[&str], context: &mut MenuCtx) {
             Err(e) => writeln!(out, "Failed setting, {}", e),
         };
     });
-    context.need_save = true;
 }
 
 // TODO: this is a bit hazardous with the takepipe kickoff mechanism
@@ -592,17 +598,14 @@ fn do_console_noauth(_item: &Item<MenuCtx>, args: &[&str], context: &mut MenuCtx
             if c.console_noauth { "yes" } else { "no" }
         );
     });
-    context.need_save = true;
 }
 
 fn do_admin_key(_item: &Item<MenuCtx>, args: &[&str], context: &mut MenuCtx) {
     let _ = writeln!(context, "todo");
-    context.need_save = true;
 }
 
 fn do_admin_clear_key(_item: &Item<MenuCtx>, args: &[&str], context: &mut MenuCtx) {
     let _ = writeln!(context, "todo");
-    context.need_save = true;
 }
 
 fn do_console_clear_pw(_item: &Item<MenuCtx>, args: &[&str], context: &mut MenuCtx) {
@@ -610,7 +613,6 @@ fn do_console_clear_pw(_item: &Item<MenuCtx>, args: &[&str], context: &mut MenuC
         let _ = c.set_console_pw(None);
         let _ = writeln!(out, "Disabled console password");
     });
-    context.need_save = true;
 }
 
 fn do_admin_pw(_item: &Item<MenuCtx>, args: &[&str], context: &mut MenuCtx) {
@@ -625,7 +627,6 @@ fn do_admin_pw(_item: &Item<MenuCtx>, args: &[&str], context: &mut MenuCtx) {
             Err(e) => writeln!(out, "Failed setting, {}", e),
         };
     });
-    context.need_save = true;
 }
 
 fn do_admin_clear_pw(_item: &Item<MenuCtx>, args: &[&str], context: &mut MenuCtx) {
@@ -633,7 +634,6 @@ fn do_admin_clear_pw(_item: &Item<MenuCtx>, args: &[&str], context: &mut MenuCtx
         let _ = c.set_admin_pw(None);
         let _ = writeln!(out, "Disabled admin password");
     });
-    context.need_save = true;
 }
 
 // fn do_gpio_show(_item: &Item<MenuCtx>, _args: &[&str], context: &mut MenuCtx) {
@@ -649,7 +649,6 @@ fn do_erase_config(_item: &Item<MenuCtx>, _args: &[&str], context: &mut MenuCtx)
             let _ = writeln!(out, "failed: {e}");
         }
     });
-    context.need_save = true;
 }
 
 fn do_logout(_item: &Item<MenuCtx>, _args: &[&str], context: &mut MenuCtx) {
@@ -708,7 +707,6 @@ fn do_wifi_wpa2(_item: &Item<MenuCtx>, args: &[&str], context: &mut MenuCtx) {
         c.wifi_net = net.into();
         c.wifi_pw = Some(pw.into())
     });
-    context.need_save = true;
     wifi_entry(context);
 }
 
@@ -721,7 +719,6 @@ fn do_wifi_open(_item: &Item<MenuCtx>, args: &[&str], context: &mut MenuCtx) {
         }
         c.wifi_pw = None;
     });
-    context.need_save = true;
     wifi_entry(context);
 }
 
@@ -755,7 +752,6 @@ fn do_net_dhcp(_item: &Item<MenuCtx>, _args: &[&str], context: &mut MenuCtx) {
     context.with_config(|c, out| {
         c.ip4_static = None;
     });
-    context.need_save = true;
 }
 
 fn do_net_static(_item: &Item<MenuCtx>, args: &[&str], context: &mut MenuCtx) {
@@ -779,7 +775,6 @@ fn do_net_static(_item: &Item<MenuCtx>, args: &[&str], context: &mut MenuCtx) {
             }
         }
     });
-    context.need_save = true;
 }
 
 // Returns an error on EOF etc.
diff --git a/embassy/demos/std/src/main.rs b/embassy/demos/std/src/main.rs
index 0e24d3d50e809d934ac8f5dfac74ce9af4e4d2af..71f6420b4db82887b3f2ae3c2f855e5b73df7506 100644
--- a/embassy/demos/std/src/main.rs
+++ b/embassy/demos/std/src/main.rs
@@ -29,7 +29,7 @@ mod setupmenu;
 pub(crate) use sunset_demo_embassy_common as demo_common;
 use crate::demo_common::singleton;
 
-use demo_common::{SSHConfig, demo_menu, Shell};
+use demo_common::{SSHConfig, demo_menu, DemoServer};
 
 const NUM_LISTENERS: usize = 2;
 // +1 for dhcp
@@ -79,11 +79,11 @@ async fn main_task(spawner: Spawner) {
 }
 
 #[derive(Default)]
-struct DemoShell {
+struct StdDemo {
     notify: Signal<NoopRawMutex, ChanHandle>,
 }
 
-impl Shell for DemoShell {
+impl DemoServer for StdDemo {
     type Init = ();
 
     fn new(init: &Self::Init) -> Self {
@@ -139,7 +139,7 @@ impl Shell for DemoShell {
 async fn listener(stack: &'static Stack<TunTapDevice>,
     config: &'static SunsetMutex<SSHConfig>) -> ! {
 
-    demo_common::listener::<_, DemoShell>(stack, config, ()).await
+    demo_common::listener::<_, StdDemo>(stack, config, ()).await
 }
 
 
diff --git a/testing/ci.sh b/testing/ci.sh
index 28a8a018daba5544d89de6523092ce9dd70f45bb..160580d279555048cf71580e029e3fcbefaa4314 100755
--- a/testing/ci.sh
+++ b/testing/ci.sh
@@ -62,8 +62,8 @@ cd embassy/demos/picow
 cargo build --release
 cargo bloat --release -n 100 | tee "$OUT/picow-bloat.txt"
 cargo bloat --release --crates | tee "$OUT/picow-bloat-crates.txt"
-size target/thumbv6m-none-eabi/release/sunset-demo-embassy-picow | tee "$OUT/picow-size.txt"
 )
+size target/thumbv6m-none-eabi/release/sunset-demo-embassy-picow | tee "$OUT/picow-size.txt"
 
 
 # other checks