prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  Docs change - use `pane` instead of `tab` for an event listener on tab change
  We tried adding an event listener for a tab change using the docs here, but couldn't get it to work: https://textual.textualize.io/guide/events/#applying-css-selectors-to-arbitrary-attributes. Should the docs be updated to use `pane` instead of `tab`, or are we doing something wrong?  Specifically this snippet:
  
  ```python
  @on(TabbedContent.TabActivated, tab="#home")
  def home_tab(self) -> None:
      self.log("Switched back to home tab.")
  ```
  
  I got it working, I think it's related to the breaking change described in https://github.com/Textualize/textual/blob/main/CHANGELOG.md#0460---2023-12-17
  
  > Breaking change: tab is no longer a @on decorator selector for TabbedContent.TabActivated -- use pane instead https://github.com/Textualize/textual/pull/3815
  
  --- END ISSUE ---
  
  Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
  --- BEGIN FILES ---
  Path: `src/textual/_on.py`
  Content:
  ```
  1 from __future__ import annotations
  2 
  3 from typing import Callable, TypeVar
  4 
  5 from .css.model import SelectorSet
  6 from .css.parse import parse_selectors
  7 from .css.tokenizer import TokenError
  8 from .message import Message
  9 
  10 DecoratedType = TypeVar("DecoratedType")
  11 
  12 
  13 class OnDecoratorError(Exception):
  14     """Errors related to the `on` decorator.
  15 
  16     Typically raised at import time as an early warning system.
  17     """
  18 
  19 
  20 class OnNoWidget(Exception):
  21     """A selector was applied to an attribute that isn't a widget."""
  22 
  23 
  24 def on(
  25     message_type: type[Message], selector: str | None = None, **kwargs: str
  26 ) -> Callable[[DecoratedType], DecoratedType]:
  27     """Decorator to declare that the method is a message handler.
  28 
  29     The decorator accepts an optional CSS selector that will be matched against a widget exposed by
  30     a `control` property on the message.
  31 
  32     Example:
  33         ```python
  34         # Handle the press of buttons with ID "#quit".
  35         @on(Button.Pressed, "#quit")
  36         def quit_button(self) -> None:
  37             self.app.quit()
  38         ```
  39 
  40     Keyword arguments can be used to match additional selectors for attributes
  41     listed in [`ALLOW_SELECTOR_MATCH`][textual.message.Message.ALLOW_SELECTOR_MATCH].
  42 
  43     Example:
  44         ```python
  45         # Handle the activation of the tab "#home" within the `TabbedContent` "#tabs".
  46         @on(TabbedContent.TabActivated, "#tabs", tab="#home")
  47         def switch_to_home(self) -> None:
  48             self.log("Switching back to the home tab.")
  49             ...
  50         ```
  51 
  52     Args:
  53         message_type: The message type (i.e. the class).
  54         selector: An optional [selector](/guide/CSS#selectors). If supplied, the handler will only be called if `selector`
  55             matches the widget from the `control` attribute of the message.
  56         **kwargs: Additional selectors for other attributes of the message.
  57     """
  58 
  59     selectors: dict[str, str] = {}
  60     if selector is not None:
  61         selectors["control"] = selector
  62     if kwargs:
  63         selectors.update(kwargs)
  64 
  65     parsed_selectors: dict[str, tuple[SelectorSet, ...]] = {}
  66     for attribute, css_selector in selectors.items():
  67         if attribute == "control":
  68             if message_type.control == Message.control:
  69                 raise OnDecoratorError(
  70                     "The message class must have a 'control' to match with the on decorator"
  71                 )
  72         elif attribute not in message_type.ALLOW_SELECTOR_MATCH:
  73             raise OnDecoratorError(
  74                 f"The attribute {attribute!r} can't be matched; have you added it to "
  75                 + f"{message_type.__name__}.ALLOW_SELECTOR_MATCH?"
  76             )
  77         try:
  78             parsed_selectors[attribute] = parse_selectors(css_selector)
  79         except TokenError:
  80             raise OnDecoratorError(
  81                 f"Unable to parse selector {css_selector!r} for {attribute}; check for syntax errors"
  82             ) from None
  83 
  84     def decorator(method: DecoratedType) -> DecoratedType:
  85         """Store message and selector in function attribute, return callable unaltered."""
  86 
  87         if not hasattr(method, "_textual_on"):
  88             setattr(method, "_textual_on", [])
  89         getattr(method, "_textual_on").append((message_type, parsed_selectors))
  90 
  91         return method
  92 
  93     return decorator
  94
  ```
  --- END FILES ---
  
  Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.
  
  Here is an example:
  
  ```diff
  diff --git a/examples/server_async.py b/examples/server_async.py
  --- a/examples/server_async.py
  +++ b/examples/server_async.py
  @@ -313,4 +313,4 @@
   
   
   if __name__ == "__main__":
  -    asyncio.run(run_async_server("."), debug=True)
  +    asyncio.run(run_async_server(), debug=True)
  diff --git a/examples/server_sync.py b/examples/server_sync.py
  --- a/examples/server_sync.py
  +++ b/examples/server_sync.py
  @@ -313,5 +313,5 @@
   
   
   if __name__ == "__main__":
  -    server = run_sync_server(".")
  +    server = run_sync_server()
       server.shutdown()
  
  ```
  
golden_diff: diff --git a/src/textual/_on.py b/src/textual/_on.py
  --- a/src/textual/_on.py
  +++ b/src/textual/_on.py
  @@ -43,7 +43,7 @@
       Example:
           ```python
           # Handle the activation of the tab "#home" within the `TabbedContent` "#tabs".
  -        @on(TabbedContent.TabActivated, "#tabs", tab="#home")
  +        @on(TabbedContent.TabActivated, "#tabs", pane="#home")
           def switch_to_home(self) -> None:
               self.log("Switching back to the home tab.")
               ...
  
verification_info: {"golden_diff": "diff --git a/src/textual/_on.py b/src/textual/_on.py\n--- a/src/textual/_on.py\n+++ b/src/textual/_on.py\n@@ -43,7 +43,7 @@\n     Example:\n         ```python\n         # Handle the activation of the tab \"#home\" within the `TabbedContent` \"#tabs\".\n-        @on(TabbedContent.TabActivated, \"#tabs\", tab=\"#home\")\n+        @on(TabbedContent.TabActivated, \"#tabs\", pane=\"#home\")\n         def switch_to_home(self) -> None:\n             self.log(\"Switching back to the home tab.\")\n             ...\n", "issue": "Docs change - use `pane` instead of `tab` for an event listener on tab change\nWe tried adding an event listener for a tab change using the docs here, but couldn't get it to work: https://textual.textualize.io/guide/events/#applying-css-selectors-to-arbitrary-attributes. Should the docs be updated to use `pane` instead of `tab`, or are we doing something wrong?  Specifically this snippet:\r\n\r\n```python\r\n@on(TabbedContent.TabActivated, tab=\"#home\")\r\ndef home_tab(self) -> None:\r\n    self.log(\"Switched back to home tab.\")\r\n```\r\n\r\nI got it working, I think it's related to the breaking change described in https://github.com/Textualize/textual/blob/main/CHANGELOG.md#0460---2023-12-17\r\n\r\n> Breaking change: tab is no longer a @on decorator selector for TabbedContent.TabActivated -- use pane instead https://github.com/Textualize/textual/pull/3815\n", "before_files": [{"content": "from __future__ import annotations\n\nfrom typing import Callable, TypeVar\n\nfrom .css.model import SelectorSet\nfrom .css.parse import parse_selectors\nfrom .css.tokenizer import TokenError\nfrom .message import Message\n\nDecoratedType = TypeVar(\"DecoratedType\")\n\n\nclass OnDecoratorError(Exception):\n    \"\"\"Errors related to the `on` decorator.\n\n    Typically raised at import time as an early warning system.\n    \"\"\"\n\n\nclass OnNoWidget(Exception):\n    \"\"\"A selector was applied to an attribute that isn't a widget.\"\"\"\n\n\ndef on(\n    message_type: type[Message], selector: str | None = None, **kwargs: str\n) -> Callable[[DecoratedType], DecoratedType]:\n    \"\"\"Decorator to declare that the method is a message handler.\n\n    The decorator accepts an optional CSS selector that will be matched against a widget exposed by\n    a `control` property on the message.\n\n    Example:\n        ```python\n        # Handle the press of buttons with ID \"#quit\".\n        @on(Button.Pressed, \"#quit\")\n        def quit_button(self) -> None:\n            self.app.quit()\n        ```\n\n    Keyword arguments can be used to match additional selectors for attributes\n    listed in [`ALLOW_SELECTOR_MATCH`][textual.message.Message.ALLOW_SELECTOR_MATCH].\n\n    Example:\n        ```python\n        # Handle the activation of the tab \"#home\" within the `TabbedContent` \"#tabs\".\n        @on(TabbedContent.TabActivated, \"#tabs\", tab=\"#home\")\n        def switch_to_home(self) -> None:\n            self.log(\"Switching back to the home tab.\")\n            ...\n        ```\n\n    Args:\n        message_type: The message type (i.e. the class).\n        selector: An optional [selector](/guide/CSS#selectors). If supplied, the handler will only be called if `selector`\n            matches the widget from the `control` attribute of the message.\n        **kwargs: Additional selectors for other attributes of the message.\n    \"\"\"\n\n    selectors: dict[str, str] = {}\n    if selector is not None:\n        selectors[\"control\"] = selector\n    if kwargs:\n        selectors.update(kwargs)\n\n    parsed_selectors: dict[str, tuple[SelectorSet, ...]] = {}\n    for attribute, css_selector in selectors.items():\n        if attribute == \"control\":\n            if message_type.control == Message.control:\n                raise OnDecoratorError(\n                    \"The message class must have a 'control' to match with the on decorator\"\n                )\n        elif attribute not in message_type.ALLOW_SELECTOR_MATCH:\n            raise OnDecoratorError(\n                f\"The attribute {attribute!r} can't be matched; have you added it to \"\n                + f\"{message_type.__name__}.ALLOW_SELECTOR_MATCH?\"\n            )\n        try:\n            parsed_selectors[attribute] = parse_selectors(css_selector)\n        except TokenError:\n            raise OnDecoratorError(\n                f\"Unable to parse selector {css_selector!r} for {attribute}; check for syntax errors\"\n            ) from None\n\n    def decorator(method: DecoratedType) -> DecoratedType:\n        \"\"\"Store message and selector in function attribute, return callable unaltered.\"\"\"\n\n        if not hasattr(method, \"_textual_on\"):\n            setattr(method, \"_textual_on\", [])\n        getattr(method, \"_textual_on\").append((message_type, parsed_selectors))\n\n        return method\n\n    return decorator\n", "path": "src/textual/_on.py"}], "after_files": [{"content": "from __future__ import annotations\n\nfrom typing import Callable, TypeVar\n\nfrom .css.model import SelectorSet\nfrom .css.parse import parse_selectors\nfrom .css.tokenizer import TokenError\nfrom .message import Message\n\nDecoratedType = TypeVar(\"DecoratedType\")\n\n\nclass OnDecoratorError(Exception):\n    \"\"\"Errors related to the `on` decorator.\n\n    Typically raised at import time as an early warning system.\n    \"\"\"\n\n\nclass OnNoWidget(Exception):\n    \"\"\"A selector was applied to an attribute that isn't a widget.\"\"\"\n\n\ndef on(\n    message_type: type[Message], selector: str | None = None, **kwargs: str\n) -> Callable[[DecoratedType], DecoratedType]:\n    \"\"\"Decorator to declare that the method is a message handler.\n\n    The decorator accepts an optional CSS selector that will be matched against a widget exposed by\n    a `control` property on the message.\n\n    Example:\n        ```python\n        # Handle the press of buttons with ID \"#quit\".\n        @on(Button.Pressed, \"#quit\")\n        def quit_button(self) -> None:\n            self.app.quit()\n        ```\n\n    Keyword arguments can be used to match additional selectors for attributes\n    listed in [`ALLOW_SELECTOR_MATCH`][textual.message.Message.ALLOW_SELECTOR_MATCH].\n\n    Example:\n        ```python\n        # Handle the activation of the tab \"#home\" within the `TabbedContent` \"#tabs\".\n        @on(TabbedContent.TabActivated, \"#tabs\", pane=\"#home\")\n        def switch_to_home(self) -> None:\n            self.log(\"Switching back to the home tab.\")\n            ...\n        ```\n\n    Args:\n        message_type: The message type (i.e. the class).\n        selector: An optional [selector](/guide/CSS#selectors). If supplied, the handler will only be called if `selector`\n            matches the widget from the `control` attribute of the message.\n        **kwargs: Additional selectors for other attributes of the message.\n    \"\"\"\n\n    selectors: dict[str, str] = {}\n    if selector is not None:\n        selectors[\"control\"] = selector\n    if kwargs:\n        selectors.update(kwargs)\n\n    parsed_selectors: dict[str, tuple[SelectorSet, ...]] = {}\n    for attribute, css_selector in selectors.items():\n        if attribute == \"control\":\n            if message_type.control == Message.control:\n                raise OnDecoratorError(\n                    \"The message class must have a 'control' to match with the on decorator\"\n                )\n        elif attribute not in message_type.ALLOW_SELECTOR_MATCH:\n            raise OnDecoratorError(\n                f\"The attribute {attribute!r} can't be matched; have you added it to \"\n                + f\"{message_type.__name__}.ALLOW_SELECTOR_MATCH?\"\n            )\n        try:\n            parsed_selectors[attribute] = parse_selectors(css_selector)\n        except TokenError:\n            raise OnDecoratorError(\n                f\"Unable to parse selector {css_selector!r} for {attribute}; check for syntax errors\"\n            ) from None\n\n    def decorator(method: DecoratedType) -> DecoratedType:\n        \"\"\"Store message and selector in function attribute, return callable unaltered.\"\"\"\n\n        if not hasattr(method, \"_textual_on\"):\n            setattr(method, \"_textual_on\", [])\n        getattr(method, \"_textual_on\").append((message_type, parsed_selectors))\n\n        return method\n\n    return decorator\n", "path": "src/textual/_on.py"}]}