diff --git a/docs/badge/application-programming.rst b/docs/badge/application-programming.rst
index 7745c246feb59c2bcbc1a9401c490379c0f2d192..785d599a651b996eb89342c726f6f1f4a291557d 100644
--- a/docs/badge/application-programming.rst
+++ b/docs/badge/application-programming.rst
@@ -79,9 +79,9 @@ Example 1b: React to input
 --------------------------
 
 If we want to react to the user, we can use the :py:class:`InputState` which got
-handed to us. In this example we look at the state of the right shoulder button.
-The values contained in the input state are the same as used by the
-:py:mod:`hardware` module.
+handed to us. In this example we look at the state of the app (by default left)
+shoulder button.  The values contained in the input state are the same as used
+by the :py:mod:`hardware` module.
 
 .. code-block:: python
 
@@ -101,7 +101,7 @@ The values contained in the input state are the same as used by the
             ctx.rgb(255, 0, 0).rectangle(self._x, -20, 40, 40).fill()
 
         def think(self, ins: InputState, delta_ms: int) -> None:
-            direction = ins.left_button
+            direction = ins.buttons.app
 
             if direction == BUTTON_PRESSED_LEFT:
                 self._x -= 1
@@ -111,7 +111,8 @@ The values contained in the input state are the same as used by the
 
     st3m.run.run_responder(Example())
 
-Try it: when you run this code, you can move the red square using the left shoulder button.
+Try it: when you run this code, you can move the red square using the app (by
+default left) shoulder button.
 
 
 Example 1c: Taking time into consideration
@@ -142,7 +143,7 @@ represents the time which has passed since the last call to `think()`.
             ctx.rgb(255, 0, 0).rectangle(self._x, -20, 40, 40).fill()
 
         def think(self, ins: InputState, delta_ms: int) -> None:
-            direction = ins.left_button # -1 (left), 1 (right), or 2 (pressed)
+            direction = ins.buttons.app # -1 (left), 1 (right), or 2 (pressed)
 
             if direction == BUTTON_PRESSED_LEFT:
                 self._x -= 20 * delta_ms / 1000
@@ -165,6 +166,41 @@ Working on the bare state of the buttons and the captouch petals can be cumberso
 the flow3r application framework gives you a bit of help in the form of the :py:class:`InputController`
 which processes an input state and gives you higher level information about what is happening.
 
+The `InputController` contains multiple :py:class:`Pressable` sub-objects, for
+example the app/OS buttons are available as following attributes on the
+`InputController`:
+
++-----------------------------------+--------------------------+
+| Attribute on ``InputControlller`` | Meaning                  |
++===================================+==========================+
+| ``.buttons.app.left``             | App button, pushed left  |
++-----------------------------------+--------------------------+
+| ``.buttons.app.middle``           | App button, pushed down  |
++-----------------------------------+--------------------------+
+| ``.buttons.app.right``            | App button, pushed right |
++-----------------------------------+--------------------------+
+| ``.buttons.os.left``              | OS button, pushed left   |
++-----------------------------------+--------------------------+
+| ``.buttons.os.middle``            | OS button, pushed down   |
++-----------------------------------+--------------------------+
+| ``.buttons.os.right``             | OS button, pushed right  |
++-----------------------------------+--------------------------+
+
+And each `Pressable` in turn contains the following attributes, all of which are
+valid within the context of a single `think()` call:
+
++----------------------------+--------------------------------------------------------------------+
+| Attribute on ``Pressable`` | Meaning                                                            |
++============================+====================================================================+
+| ``.pressed``               | Button has just started being pressed, ie. it's a Half Press down. |
++----------------------------+--------------------------------------------------------------------+
+| ``.down``                  | Button is being held down.                                         |
++----------------------------+--------------------------------------------------------------------+
+| ``.released``              | Button has just stopped being pressed, ie. it's a Half Press up.   |
++----------------------------+--------------------------------------------------------------------+
+| ``.up``                    | Button is not being held down.                                     |
++----------------------------+--------------------------------------------------------------------+
+
 The following example shows how to properly react to single button presses without having to
 think about what happens if the user presses the button for a long time. It uses the `InputController`
 to detect single button presses and switches between showing a circle (by drawing a 360 deg arc) and
@@ -199,14 +235,12 @@ a square.
         def think(self, ins: InputState, delta_ms: int) -> None:
             self.input.think(ins, delta_ms) # let the input controller to its magic
 
-            direction = ins.left_button # -1 (left), 1 (right), or 2 (pressed)
-
-            if self.input.right_shoulder.middle.pressed:
+            if self.input.buttons.app.middle.pressed:
                 self._draw_rectangle = not self._draw_rectangle
 
-            if direction == BUTTON_PRESSED_LEFT:
+            if self.input.buttons.app.left.pressed:
                 self._x -= 20 * delta_ms / 1000
-            elif direction == BUTTON_PRESSED_RIGHT:
+            elif self.input.buttons.app.right.pressed:
                 self._x += 40 * delta_ms / 1000
 
 
@@ -222,8 +256,9 @@ With just the Responder class this can become a bit tricky as it never knows whe
 when it is not. It also doesn't directly allow you to launch a new screen.
 
 To help you with that you can use a :py:class:`View` instead. It can tell you when
-it becomes visible and you can also use it to bring a new screen or widget into the foreground or remove
-it again from the screen.
+it becomes visible, when it is about to become inactive (invisible) and you can
+also use it to bring a new screen or widget into the foreground or remove it
+again from the screen.
 
 Example 2a: Managing two views
 --------------------------------
@@ -259,8 +294,8 @@ into the two different views. We make use of an `InputController` again to handl
         def think(self, ins: InputState, delta_ms: int) -> None:
             self.input.think(ins, delta_ms) # let the input controller to its magic
 
-            if self.input.right_shoulder.middle.pressed:
-                self._vm.pop()
+            # No need to handle returning back to Example on button press - the
+            # flow3r's ViewManager takes care of that automatically.
 
 
     class Example(View):
@@ -277,11 +312,12 @@ into the two different views. We make use of an `InputController` again to handl
 
         def on_enter(self, vm: Optional[ViewManager]) -> None:
             self._vm = vm
+            self.input._ignore_pressed()
 
         def think(self, ins: InputState, delta_ms: int) -> None:
             self.input.think(ins, delta_ms) # let the input controller to its magic
 
-            if self.input.right_shoulder.middle.pressed:
+            if self.input.buttons.app.middle.pressed:
                 self._vm.push(SecondScreen())
 
     st3m.run.run_view(Example())
@@ -293,9 +329,12 @@ the still pressed button immediately closes `SecondScreen` we make us of a speci
 Example 2b: Easier view management
 ----------------------------------
 
-The idea that a button (physical or captouch) is used to enter / exit a view is so universal that
-there is a special view which helps you with that: :py:class:`BaseView`. It integrates an
-`InputController` and handles the ignoring of extra presses:
+The above code is so universal that we provide a special view which takes care
+of this boilerplate: :py:class:`BaseView`. It integrated a local
+`InputController` on ``self.input`` and a copy of the :py:class:`ViewManager`
+which caused the View to enter on ``self.vm``.
+
+Here is our previous example rewritten to make use of `BaseView`:
 
 .. code-block:: python
 
@@ -304,10 +343,14 @@ there is a special view which helps you with that: :py:class:`BaseView`. It inte
 
     class SecondScreen(BaseView):
         def __init__(self) -> None:
+            # Remember to call super().__init__() if you implement your own
+            # constructor!
             super().__init__()
 
         def on_enter(self, vm: Optional[ViewManager]) -> None:
-            super().on_enter(vm) # Let BaseView do its thing
+            # Remember to call super().on_enter() if you implement your own
+            # on_enter!
+            super().on_enter(vm)
 
         def draw(self, ctx: Context) -> None:
             # Paint the background black
@@ -315,27 +358,13 @@ there is a special view which helps you with that: :py:class:`BaseView`. It inte
             # Green square
             ctx.rgb(0, 255, 0).rectangle(-20, -20, 40, 40).fill()
 
-        def think(self, ins: InputState, delta_ms: int) -> None:
-            super().think(ins, delta_ms) # Let BaseView do its thing
-
-            if self.input.right_shoulder.middle.pressed:
-                self.vm.pop()
-
-
     class Example(BaseView):
-        def __init__(self) -> None:
-            super().__init__()
-
         def draw(self, ctx: Context) -> None:
             # Paint the background black
             ctx.rgb(0, 0, 0).rectangle(-120, -120, 240, 240).fill()
             # Red square
             ctx.rgb(255, 0, 0).rectangle(-20, -20, 40, 40).fill()
 
-
-        def on_enter(self, vm: Optional[ViewManager]) -> None:
-            super().on_enter(vm) # Let BaseView do its thing
-
         def think(self, ins: InputState, delta_ms: int) -> None:
             super().think(ins, delta_ms) # Let BaseView do its thing
 
@@ -353,7 +382,11 @@ All fine and good, you were able to write an application that you can run with `
 but certainly you also want to run it from flow3r's menu system.
 
 Let's introduce the final class you should actually be using for application development:
-:py:class:`Application` (yeah, right).
+:py:class:`Application`. It builds upon `BaseView` (so you still have access to
+`self.input` and `self.vm`) but additionally is made aware of an
+:py:class:`ApplicationContext` on startup and can be registered into a menu.
+
+Here is our previous code changed to use `Application` for the base of its main view:
 
 .. code-block:: python
 
@@ -366,28 +399,16 @@ Let's introduce the final class you should actually be using for application dev
     from typing import Optional
 
     class SecondScreen(BaseView):
-        def __init__(self) -> None:
-            super().__init__()
-
-        def on_enter(self, vm: Optional[ViewManager]) -> None:
-            super().on_enter(vm) # Let BaseView do its thing
-
         def draw(self, ctx: Context) -> None:
             # Paint the background black
             ctx.rgb(0, 0, 0).rectangle(-120, -120, 240, 240).fill()
             # Green square
             ctx.rgb(0, 255, 0).rectangle(-20, -20, 40, 40).fill()
 
-        def think(self, ins: InputState, delta_ms: int) -> None:
-            super().think(ins, delta_ms) # Let BaseView do its thing
-
-            if self.input.right_shoulder.middle.pressed:
-                self.vm.pop()
-
-
     class MyDemo(Application):
         def __init__(self, app_ctx: ApplicationContext) -> None:
             super().__init__(app_ctx)
+            # Ignore the app_ctx for now.
 
         def draw(self, ctx: Context) -> None:
             # Paint the background black
@@ -395,24 +416,15 @@ Let's introduce the final class you should actually be using for application dev
             # Red square
             ctx.rgb(255, 0, 0).rectangle(-20, -20, 40, 40).fill()
 
-        def on_enter(self, vm: Optional[ViewManager]) -> None:
-            super().on_enter(vm) # Let Application do its thing
-
         def think(self, ins: InputState, delta_ms: int) -> None:
             super().think(ins, delta_ms) # Let Application do its thing
 
             if self.input.right_shoulder.middle.pressed:
-                self._view_manager.push(SecondScreen())
-
-    ##### Uncomment when running the application via mpremote/Micro REPL ####
-    # st3m.run.run_view(MyDemo(ApplicationContext()))
-
-The `Application` class gives you the following extras:
-
- - Pressing the down the left shoulder button exits the app
- - You get a `_view_manager` member to manager your views
- - It can be picked up by the main menu system
+                self.vm.push(SecondScreen())
 
+    if __name__ == '__main__':
+        # Continue to make runnable via mpremote run.
+        st3m.run.run_view(MyDemo(ApplicationContext()))
 
 To add the application to the menu we are missing one more thing: a `flow3r.toml`
 file which describes the application so flow3r knows where to put it in the menu system.
diff --git a/python_payload/st3m/application.py b/python_payload/st3m/application.py
index 673890cc91a5388601c76ae776de7cee9c75652f..a50a0b6870612038f7702aa21d43f0a01f4edce8 100644
--- a/python_payload/st3m/application.py
+++ b/python_payload/st3m/application.py
@@ -34,20 +34,9 @@ class Application(BaseView):
         self._app_ctx = app_ctx
         super().__init__()
 
-    def on_exit(self) -> None:
-        pass
-
-    def on_enter(self, vm: Optional[ViewManager]) -> None:
-        super().on_enter(vm)
-
     def think(self, ins: InputState, delta_ms: int) -> None:
         super().think(ins, delta_ms)
 
-        if self.input.buttons.os.middle.pressed:
-            if self.vm is not None:
-                self.on_exit()
-                self.vm.pop(ViewTransitionSwipeRight())
-
 
 class BundleLoadException(BaseException):
     MSG = "failed to load"
diff --git a/python_payload/st3m/ui/view.py b/python_payload/st3m/ui/view.py
index c0b2ee5f11edcc46c0b6f7dc313b9b92260110b3..b3d8ba013a94d279779bb2580e9f34aa9f53981c 100644
--- a/python_payload/st3m/ui/view.py
+++ b/python_payload/st3m/ui/view.py
@@ -15,8 +15,13 @@ class View(Responder):
 
     def on_enter(self, vm: Optional["ViewManager"]) -> None:
         """
-        Called when the View has just become active. This is guaranteed to be
-        called before think().
+        Called when the View has just become active.
+        """
+        pass
+
+    def on_exit(self) -> None:
+        """
+        Called when the View is about to become inactive.
         """
         pass
 
@@ -147,8 +152,14 @@ class ViewManager(Responder):
         self._transitioning = False
         self._transition = 0.0
         self._history: List[View] = []
+        self._input = InputController()
 
     def think(self, ins: InputState, delta_ms: int) -> None:
+        self._input.think(ins, delta_ms)
+
+        if self._input.buttons.os.middle.pressed:
+            self.pop(ViewTransitionSwipeRight())
+
         if self._transitioning:
             self._transition += (delta_ms / 1000.0) * (1000 / self._time_ms)
             if self._transition >= 1.0:
@@ -186,6 +197,8 @@ class ViewManager(Responder):
         The new view will _not_ be added to history!
         """
         self._outgoing = self._incoming
+        if self._outgoing is not None:
+            self._outgoing.on_exit()
         self._incoming = r
         self._incoming.on_enter(self)
         self._overriden_vt = overide_vt
@@ -212,5 +225,7 @@ class ViewManager(Responder):
         override_vt will be used instead of the default ViewTransition
         animation.
         """
+        if len(self._history) < 1:
+            return
         r = self._history.pop()
         self.replace(r, override_vt)