Repository: Textualize/textual

Issue:
Textual v3.1.0 introduces app crash on early keymap update
Up to Textual v3.0.1 this code worked fine:

```python
from textual.app import App

class RefreshBindingCrashApp(App[None]):

    def __init__(self) -> None:
        super().__init__()
        self.update_keymap({})

if __name__ == "__main__":
    RefreshBindingCrashApp().run()
```

but with v3.1.0 you get an app crash:

```
Traceback (most recent call last):
  File "/Users/davep/temp/x/keymap_crash.py", line 10, in <module>
    RefreshBindingCrashApp().run()
    ~~~~~~~~~~~~~~~~~~~~~~^^
  File "/Users/davep/temp/x/keymap_crash.py", line 7, in __init__
    self.update_keymap({})
    ~~~~~~~~~~~~~~~~~~^^^^
  File "/Users/davep/temp/x/.venv/lib/python3.13/site-packages/textual/app.py", line 3735, in update_keymap
    self.refresh_bindings()
    ~~~~~~~~~~~~~~~~~~~~~^^
  File "/Users/davep/temp/x/.venv/lib/python3.13/site-packages/textual/dom.py", line 1821, in refresh_bindings
    self.screen.refresh_bindings()
    ^^^^^^^^^^^
  File "/Users/davep/temp/x/.venv/lib/python3.13/site-packages/textual/app.py", line 1444, in screen
    raise ScreenStackError("No screens on stack") from None
textual.app.ScreenStackError: No screens on stack
```


Discussion:
A PR would be welcome.
Four hours or so old at this point and linked.

![Image](https://github.com/user-attachments/assets/9d2caf3a-176d-4d67-a2f2-50a5aa7de3d1)



Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 298e454812..76988284eb 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
 The format is based on [Keep a Changelog](http://keepachangelog.com/)
 and this project adheres to [Semantic Versioning](http://semver.org/).
 
+## Unreleased
+
+### Fixed
+
+- Fixed a crash when setting keymap before app mount https://github.com/Textualize/textual/issues/5742
+
 ## [3.1.0] - 2025-04-12
 
 ### Fixed
diff --git a/src/textual/dom.py b/src/textual/dom.py
index 00b5595c65..6898f7d3db 100644
--- a/src/textual/dom.py
+++ b/src/textual/dom.py
@@ -1818,7 +1818,8 @@ def refresh_bindings(self) -> None:
         See [actions](/guide/actions#dynamic-actions) for how to use this method.
 
         """
-        self.screen.refresh_bindings()
+        if self._is_mounted:
+            self.screen.refresh_bindings()
 
     async def action_toggle(self, attribute_name: str) -> None:
         """Toggle an attribute on the node.


Test patch:
diff --git a/tests/test_keymap.py b/tests/test_keymap.py
index fd92ff2990..d7615bd057 100644
--- a/tests/test_keymap.py
+++ b/tests/test_keymap.py
@@ -192,3 +192,28 @@ def on_mount(self) -> None:
         await pilot.press("i")
         assert parent_counter == 1
         assert child_counter == 1
+
+
+async def test_set_keymap_before_app_mount():
+    """Ensure we can set the keymap before mount without crash.
+
+    https://github.com/Textualize/textual/issues/5742
+    """
+
+    worked = False
+
+    class MyApp(App[None]):
+
+        BINDINGS = [Binding(key="x", action="test", id="test")]
+
+        def __init__(self) -> None:
+            super().__init__()
+            self.update_keymap({"test": "y"})
+
+        def action_test(self) -> None:
+            nonlocal worked
+            worked = True
+
+    async with MyApp().run_test() as pilot:
+        await pilot.press("y")
+        assert worked is True