diff --git a/components/micropython/usermodule/micropython.cmake b/components/micropython/usermodule/micropython.cmake
index 14551f6e26ce70078c9cce90234b942a17c9f55a..811f9e71d4e573a742b15d64b3f14dae1932e34a 100644
--- a/components/micropython/usermodule/micropython.cmake
+++ b/components/micropython/usermodule/micropython.cmake
@@ -17,6 +17,7 @@ target_sources(usermod_badge23 INTERFACE
     ${CMAKE_CURRENT_LIST_DIR}/mp_sys_kernel.c
     ${CMAKE_CURRENT_LIST_DIR}/mp_uctx.c
     ${CMAKE_CURRENT_LIST_DIR}/mp_media.c
+    ${CMAKE_CURRENT_LIST_DIR}/mp_sys_mode.c
 )
 
 target_include_directories(usermod_badge23 INTERFACE
diff --git a/components/micropython/usermodule/mp_sys_mode.c b/components/micropython/usermodule/mp_sys_mode.c
new file mode 100644
index 0000000000000000000000000000000000000000..67df526d934eeafc35455c434f35199f27197e67
--- /dev/null
+++ b/components/micropython/usermodule/mp_sys_mode.c
@@ -0,0 +1,37 @@
+// probably doesn't need all of these idk
+#include <stdio.h>
+#include <string.h>
+
+#include "extmod/virtpin.h"
+#include "machine_rtc.h"
+#include "modmachine.h"
+#include "mphalport.h"
+#include "py/builtin.h"
+#include "py/mphal.h"
+#include "py/runtime.h"
+
+#include "flow3r_bsp.h"
+#include "st3m_mode.h"
+
+STATIC mp_obj_t mp_mode_set(mp_obj_t kind) {
+    // this does not support message at the time
+    st3m_mode_set(mp_obj_get_int(kind), NULL);
+    return mp_const_none;
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_mode_set_obj, mp_mode_set);
+
+STATIC const mp_rom_map_elem_t mp_module_sys_mode_globals_table[] = {
+    { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_sys_mode) },
+
+    { MP_ROM_QSTR(MP_QSTR_mode_set), MP_ROM_PTR(&mp_mode_set_obj) },
+};
+
+STATIC MP_DEFINE_CONST_DICT(mp_module_sys_mode_globals,
+                            mp_module_sys_mode_globals_table);
+
+const mp_obj_module_t mp_module_sys_mode = {
+    .base = { &mp_type_module },
+    .globals = (mp_obj_dict_t *)&mp_module_sys_mode_globals,
+};
+
+MP_REGISTER_MODULE(MP_QSTR_sys_mode, mp_module_sys_mode);
diff --git a/components/micropython/vendor/ports/esp32/main.c b/components/micropython/vendor/ports/esp32/main.c
index 77775df388fe83310025da9f8b3b285e3c26346b..68cda0912828153fb3482e9e0df3a5b2d357b0c6 100644
--- a/components/micropython/vendor/ports/esp32/main.c
+++ b/components/micropython/vendor/ports/esp32/main.c
@@ -158,6 +158,7 @@ soft_reset:
             }
             esp_log_set_vprintf(vprintf_log);
         } else {
+            st3m_mode_set(st3m_mode_kind_repl, NULL);
             if ((ret = pyexec_friendly_repl() != 0)) {
                 _diskmode_maybe(pyexec_system_exit);
                 break;
diff --git a/python_payload/mypystubs/sys_mode.pyi b/python_payload/mypystubs/sys_mode.pyi
new file mode 100644
index 0000000000000000000000000000000000000000..4b6bcbf3e2661fb87a21d5071ffdef828a30c25b
--- /dev/null
+++ b/python_payload/mypystubs/sys_mode.pyi
@@ -0,0 +1,11 @@
+def mode_set(kind: int) -> None:
+    """
+    Sets the mode of the badge, which handles what is displayed on screen and what behavior
+    is used for pressing the OS shoulder button.
+
+    Very low level, should not be used if you don't know what you're doing.
+    kind values can be viewed on components/st3m/st3m_mode.h
+
+    Need to switch to flash or SD mode? Use machine.disk_mode_flash/disk_mode_sd instead.
+    """
+    ...
diff --git a/python_payload/st3m/run.py b/python_payload/st3m/run.py
index 38e18aad4926e28f9d7a291f50dc1f0a96a45164..4e09ffbad0ad87adaa750f11d932939188564f40 100644
--- a/python_payload/st3m/run.py
+++ b/python_payload/st3m/run.py
@@ -20,7 +20,7 @@ from st3m.application import (
 from st3m.about import About
 from st3m import settings_menu as settings, logging, processors, wifi
 
-import captouch, audio, leds, gc, sys_buttons, sys_display
+import captouch, audio, leds, gc, sys_buttons, sys_display, sys_mode
 import os
 
 import machine
@@ -116,15 +116,16 @@ def _make_compositor(reactor: Reactor, vm: ViewManager) -> overlays.Compositor:
     return compositor
 
 
-def run_view(v: View) -> None:
+def run_view(v: View, debug_vm=True) -> None:
     """
     Run a given View in the foreground, with an empty ViewManager underneath.
 
     This is useful for debugging simple applications from the REPL.
     """
     reactor = _make_reactor()
-    vm = ViewManager(ViewTransitionBlend())
+    vm = ViewManager(ViewTransitionBlend(), debug=debug_vm)
     vm.push(v)
+    sys_mode.mode_set(2)  # st3m_mode_kind_app
     compositor = _make_compositor(reactor, vm)
     top = processors.ProcessorMidldeware(compositor)
     reactor.set_top(top)
@@ -132,7 +133,7 @@ def run_view(v: View) -> None:
 
 
 def run_app(klass):
-    run_view(klass(ApplicationContext()))
+    run_view(klass(ApplicationContext()), debug_vm=True)
 
 
 def _yeet_local_changes() -> None:
@@ -192,8 +193,8 @@ def run_main() -> None:
             raise Exception(f"More than one bundle named {override_main_app}")
         if len(requested) == 0:
             raise Exception(f"Requested bundle {override_main_app} not found")
-        run_view(requested[0].load())
-    run_view(menu_main)
+        run_view(requested[0].load(), debug_vm=True)
+    run_view(menu_main, debug_vm=False)
 
 
 __all__ = [
diff --git a/python_payload/st3m/ui/view.py b/python_payload/st3m/ui/view.py
index bd00920730312f89f666721fa216d17af7564c13..3ad556c973bf5a319fe3c9e8c22a986c0e8d445d 100644
--- a/python_payload/st3m/ui/view.py
+++ b/python_payload/st3m/ui/view.py
@@ -2,6 +2,8 @@ from st3m.reactor import Responder
 from st3m.goose import ABCBase, abstractmethod, Optional, List
 from st3m.input import InputState, InputController
 from ctx import Context
+import machine
+import utime
 
 
 class View(Responder):
@@ -145,13 +147,15 @@ class ViewManager(Responder):
     popped.
     """
 
-    def __init__(self, vt: ViewTransition) -> None:
+    def __init__(self, vt: ViewTransition, debug: bool) -> None:
         """
         Create a new ViewManager with a default ViewTransition.
         """
         self._incoming: Optional[View] = None
         self._outgoing: Optional[View] = None
 
+        self._debug = debug
+
         # Transition time.
         self._time_ms = 150
 
@@ -167,7 +171,11 @@ class ViewManager(Responder):
         self._input.think(ins, delta_ms)
 
         if self._input.buttons.os.middle.pressed:
-            self.pop(ViewTransitionSwipeRight())
+            if not self._history and self._debug:
+                utime.sleep(0.5)
+                machine.reset()
+            else:
+                self.pop(ViewTransitionSwipeRight())
 
         if self._transitioning:
             self._transition += (delta_ms / 1000.0) * (1000 / self._time_ms)
diff --git a/sim/fakes/sys_mode.py b/sim/fakes/sys_mode.py
new file mode 100644
index 0000000000000000000000000000000000000000..baa9ef9d354eecfff624210fbc7e7dd7eb9e362e
--- /dev/null
+++ b/sim/fakes/sys_mode.py
@@ -0,0 +1,2 @@
+def mode_set(kind: int) -> None:
+    return None
diff --git a/sim/fakes/utime.py b/sim/fakes/utime.py
new file mode 100644
index 0000000000000000000000000000000000000000..8c3bab81de6788d1c9843f7d48a378059cd72e5f
--- /dev/null
+++ b/sim/fakes/utime.py
@@ -0,0 +1,5 @@
+import time
+
+
+def sleep(i: int):
+    time.sleep(i)