We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
[CSS] Descendant type selectors can't have a numeric in their name
Consider the following code:

```python
from textual.app        import App, ComposeResult
from textual.containers import Vertical
from textual.widgets    import Header, Footer, Label

class LabelH1( Label ):
    ...

class CSSOddnessApp( App[ None ] ):

    CSS = """
    Vertical LabelH1 {
        background: red;
    }
    """

    def compose( self ) -> ComposeResult:
        yield Header()
        yield Vertical(
            Label( "Label" ),
            LabelH1( "LabelH1" ),
        )
        yield Footer()

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

When run we get the following error:

```
 Error in stylesheet:
 /Users/davep/develop/python/textual-sandbox/css_oddness.py:CSSOddnessApp:1:19
╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ ❱ 1 │                                                                                                                                                      │
│   2 │   Vertical LabelH1 {                                                                                                                                 │
│   3 │   │   background: red;                                                                                                                               │
╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
 • Expected one of 'combinator child', 'comment start', 'declaration set start', 'new selector', 'pseudo class', 'selector', 'selector class', 'selector id',
 'selector universal', or 'whitespace'.
 • Did you forget a semicolon at the end of a line?
```

The same thing happens with `Vertical LabelH1`. On the other hand, if I remove the number from the inherited label widget:

```python
from textual.app        import App, ComposeResult
from textual.containers import Vertical
from textual.widgets    import Header, Footer, Label

class LabelHOne( Label ):
    ...

class CSSOddnessApp( App[ None ] ):

    CSS = """
    Vertical LabelHOne {
        background: red;
    }
    """

    def compose( self ) -> ComposeResult:
        yield Header()
        yield Vertical(
            Label( "Label" ),
            LabelHOne( "LabelHOne" ),
        )
        yield Footer()

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

this works fine. Likewise, if I retain the name but *don't* use combination:

```python
from textual.app        import App, ComposeResult
from textual.containers import Vertical
from textual.widgets    import Header, Footer, Label

class LabelH1( Label ):
    ...

class CSSOddnessApp( App[ None ] ):

    CSS = """
    LabelH1 {
        background: red;
    }
    """

    def compose( self ) -> ComposeResult:
        yield Header()
        yield Vertical(
            Label( "Label" ),
            LabelH1( "LabelH1" ),
        )
        yield Footer()

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

that also works fine.

I would suspect a variation on #1253.
[CSS] Descendant type selectors can't have a numeric in their name
Consider the following code:

```python
from textual.app        import App, ComposeResult
from textual.containers import Vertical
from textual.widgets    import Header, Footer, Label

class LabelH1( Label ):
    ...

class CSSOddnessApp( App[ None ] ):

    CSS = """
    Vertical LabelH1 {
        background: red;
    }
    """

    def compose( self ) -> ComposeResult:
        yield Header()
        yield Vertical(
            Label( "Label" ),
            LabelH1( "LabelH1" ),
        )
        yield Footer()

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

When run we get the following error:

```
 Error in stylesheet:
 /Users/davep/develop/python/textual-sandbox/css_oddness.py:CSSOddnessApp:1:19
╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ ❱ 1 │                                                                                                                                                      │
│   2 │   Vertical LabelH1 {                                                                                                                                 │
│   3 │   │   background: red;                                                                                                                               │
╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
 • Expected one of 'combinator child', 'comment start', 'declaration set start', 'new selector', 'pseudo class', 'selector', 'selector class', 'selector id',
 'selector universal', or 'whitespace'.
 • Did you forget a semicolon at the end of a line?
```

The same thing happens with `Vertical LabelH1`. On the other hand, if I remove the number from the inherited label widget:

```python
from textual.app        import App, ComposeResult
from textual.containers import Vertical
from textual.widgets    import Header, Footer, Label

class LabelHOne( Label ):
    ...

class CSSOddnessApp( App[ None ] ):

    CSS = """
    Vertical LabelHOne {
        background: red;
    }
    """

    def compose( self ) -> ComposeResult:
        yield Header()
        yield Vertical(
            Label( "Label" ),
            LabelHOne( "LabelHOne" ),
        )
        yield Footer()

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

this works fine. Likewise, if I retain the name but *don't* use combination:

```python
from textual.app        import App, ComposeResult
from textual.containers import Vertical
from textual.widgets    import Header, Footer, Label

class LabelH1( Label ):
    ...

class CSSOddnessApp( App[ None ] ):

    CSS = """
    LabelH1 {
        background: red;
    }
    """

    def compose( self ) -> ComposeResult:
        yield Header()
        yield Vertical(
            Label( "Label" ),
            LabelH1( "LabelH1" ),
        )
        yield Footer()

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

that also works fine.

I would suspect a variation on #1253.

--- 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/css/tokenize.py`
Content:
```
1 from __future__ import annotations
2 
3 import re
4 from pathlib import PurePath
5 from typing import Iterable
6 
7 from textual.css.tokenizer import Expect, Token, Tokenizer
8 
9 PERCENT = r"-?\d+\.?\d*%"
10 DECIMAL = r"-?\d+\.?\d*"
11 COMMA = r"\s*,\s*"
12 OPEN_BRACE = r"\(\s*"
13 CLOSE_BRACE = r"\s*\)"
14 
15 HEX_COLOR = r"\#[0-9a-fA-F]{8}|\#[0-9a-fA-F]{6}|\#[0-9a-fA-F]{4}|\#[0-9a-fA-F]{3}"
16 RGB_COLOR = rf"rgb{OPEN_BRACE}{DECIMAL}{COMMA}{DECIMAL}{COMMA}{DECIMAL}{CLOSE_BRACE}|rgba{OPEN_BRACE}{DECIMAL}{COMMA}{DECIMAL}{COMMA}{DECIMAL}{COMMA}{DECIMAL}{CLOSE_BRACE}"
17 HSL_COLOR = rf"hsl{OPEN_BRACE}{DECIMAL}{COMMA}{PERCENT}{COMMA}{PERCENT}{CLOSE_BRACE}|hsla{OPEN_BRACE}{DECIMAL}{COMMA}{PERCENT}{COMMA}{PERCENT}{COMMA}{DECIMAL}{CLOSE_BRACE}"
18 
19 COMMENT_START = r"\/\*"
20 SCALAR = rf"{DECIMAL}(?:fr|%|w|h|vw|vh)"
21 DURATION = r"\d+\.?\d*(?:ms|s)"
22 NUMBER = r"\-?\d+\.?\d*"
23 COLOR = rf"{HEX_COLOR}|{RGB_COLOR}|{HSL_COLOR}"
24 KEY_VALUE = r"[a-zA-Z_-][a-zA-Z0-9_-]*=[0-9a-zA-Z_\-\/]+"
25 TOKEN = "[a-zA-Z][a-zA-Z0-9_-]*"
26 STRING = r"\".*?\""
27 VARIABLE_REF = r"\$[a-zA-Z0-9_\-]+"
28 
29 IDENTIFIER = r"[a-zA-Z_\-][a-zA-Z0-9_\-]*"
30 
31 # Values permitted in variable and rule declarations.
32 DECLARATION_VALUES = {
33     "scalar": SCALAR,
34     "duration": DURATION,
35     "number": NUMBER,
36     "color": COLOR,
37     "key_value": KEY_VALUE,
38     "token": TOKEN,
39     "string": STRING,
40     "variable_ref": VARIABLE_REF,
41 }
42 
43 # The tokenizers "expectation" while at the root/highest level of scope
44 # in the CSS file. At this level we might expect to see selectors, comments,
45 # variable definitions etc.
46 expect_root_scope = Expect(
47     whitespace=r"\s+",
48     comment_start=COMMENT_START,
49     selector_start_id=r"\#" + IDENTIFIER,
50     selector_start_class=r"\." + IDENTIFIER,
51     selector_start_universal=r"\*",
52     selector_start=IDENTIFIER,
53     variable_name=rf"{VARIABLE_REF}:",
54 ).expect_eof(True)
55 
56 # After a variable declaration e.g. "$warning-text: TOKENS;"
57 #              for tokenizing variable value ------^~~~~~~^
58 expect_variable_name_continue = Expect(
59     variable_value_end=r"\n|;",
60     whitespace=r"\s+",
61     comment_start=COMMENT_START,
62     **DECLARATION_VALUES,
63 ).expect_eof(True)
64 
65 expect_comment_end = Expect(
66     comment_end=re.escape("*/"),
67 )
68 
69 # After we come across a selector in CSS e.g. ".my-class", we may
70 # find other selectors, pseudo-classes... e.g. ".my-class :hover"
71 expect_selector_continue = Expect(
72     whitespace=r"\s+",
73     comment_start=COMMENT_START,
74     pseudo_class=r"\:[a-zA-Z_-]+",
75     selector_id=r"\#[a-zA-Z_\-][a-zA-Z0-9_\-]*",
76     selector_class=r"\.[a-zA-Z_\-][a-zA-Z0-9_\-]*",
77     selector_universal=r"\*",
78     selector=r"[a-zA-Z_\-]+",
79     combinator_child=">",
80     new_selector=r",",
81     declaration_set_start=r"\{",
82 )
83 
84 # A rule declaration e.g. "text: red;"
85 #                          ^---^
86 expect_declaration = Expect(
87     whitespace=r"\s+",
88     comment_start=COMMENT_START,
89     declaration_name=r"[a-zA-Z_\-]+\:",
90     declaration_set_end=r"\}",
91 )
92 
93 expect_declaration_solo = Expect(
94     whitespace=r"\s+",
95     comment_start=COMMENT_START,
96     declaration_name=r"[a-zA-Z_\-]+\:",
97     declaration_set_end=r"\}",
98 ).expect_eof(True)
99 
100 # The value(s)/content from a rule declaration e.g. "text: red;"
101 #                                                         ^---^
102 expect_declaration_content = Expect(
103     declaration_end=r";",
104     whitespace=r"\s+",
105     comment_start=COMMENT_START,
106     **DECLARATION_VALUES,
107     important=r"\!important",
108     comma=",",
109     declaration_set_end=r"\}",
110 )
111 
112 expect_declaration_content_solo = Expect(
113     declaration_end=r";",
114     whitespace=r"\s+",
115     comment_start=COMMENT_START,
116     **DECLARATION_VALUES,
117     important=r"\!important",
118     comma=",",
119     declaration_set_end=r"\}",
120 ).expect_eof(True)
121 
122 
123 class TokenizerState:
124     """State machine for the tokenizer.
125 
126     Attributes:
127         EXPECT: The initial expectation of the tokenizer. Since we start tokenizing
128             at the root scope, we might expect to see either a variable or selector, for example.
129         STATE_MAP: Maps token names to Expects, defines the sets of valid tokens
130             that we'd expect to see next, given the current token. For example, if
131             we've just processed a variable declaration name, we next expect to see
132             the value of that variable.
133     """
134 
135     EXPECT = expect_root_scope
136     STATE_MAP = {
137         "variable_name": expect_variable_name_continue,
138         "variable_value_end": expect_root_scope,
139         "selector_start": expect_selector_continue,
140         "selector_start_id": expect_selector_continue,
141         "selector_start_class": expect_selector_continue,
142         "selector_start_universal": expect_selector_continue,
143         "selector_id": expect_selector_continue,
144         "selector_class": expect_selector_continue,
145         "selector_universal": expect_selector_continue,
146         "declaration_set_start": expect_declaration,
147         "declaration_name": expect_declaration_content,
148         "declaration_end": expect_declaration,
149         "declaration_set_end": expect_root_scope,
150     }
151 
152     def __call__(self, code: str, path: str | PurePath) -> Iterable[Token]:
153         tokenizer = Tokenizer(code, path=path)
154         expect = self.EXPECT
155         get_token = tokenizer.get_token
156         get_state = self.STATE_MAP.get
157         while True:
158             token = get_token(expect)
159             name = token.name
160             if name == "comment_start":
161                 tokenizer.skip_to(expect_comment_end)
162                 continue
163             elif name == "eof":
164                 break
165             expect = get_state(name, expect)
166             yield token
167 
168 
169 class DeclarationTokenizerState(TokenizerState):
170     EXPECT = expect_declaration_solo
171     STATE_MAP = {
172         "declaration_name": expect_declaration_content,
173         "declaration_end": expect_declaration_solo,
174     }
175 
176 
177 class ValueTokenizerState(TokenizerState):
178     EXPECT = expect_declaration_content_solo
179 
180 
181 tokenize = TokenizerState()
182 tokenize_declarations = DeclarationTokenizerState()
183 tokenize_value = ValueTokenizerState()
184 
185 
186 def tokenize_values(values: dict[str, str]) -> dict[str, list[Token]]:
187     """Tokens the values in a dict of strings.
188 
189     Args:
190         values: A mapping of CSS variable name on to a value, to be
191             added to the CSS context.
192 
193     Returns:
194         A mapping of name on to a list of tokens,
195     """
196     value_tokens = {
197         name: list(tokenize_value(value, "__name__")) for name, value in values.items()
198     }
199     return value_tokens
200
```
--- 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()

```

diff --git a/src/textual/css/tokenize.py b/src/textual/css/tokenize.py
--- a/src/textual/css/tokenize.py
+++ b/src/textual/css/tokenize.py
@@ -75,7 +75,7 @@
     selector_id=r"\#[a-zA-Z_\-][a-zA-Z0-9_\-]*",
     selector_class=r"\.[a-zA-Z_\-][a-zA-Z0-9_\-]*",
     selector_universal=r"\*",
-    selector=r"[a-zA-Z_\-]+",
+    selector=IDENTIFIER,
     combinator_child=">",
     new_selector=r",",
     declaration_set_start=r"\{",

{"golden_diff": "diff --git a/src/textual/css/tokenize.py b/src/textual/css/tokenize.py\n--- a/src/textual/css/tokenize.py\n+++ b/src/textual/css/tokenize.py\n@@ -75,7 +75,7 @@\n     selector_id=r\"\\#[a-zA-Z_\\-][a-zA-Z0-9_\\-]*\",\n     selector_class=r\"\\.[a-zA-Z_\\-][a-zA-Z0-9_\\-]*\",\n     selector_universal=r\"\\*\",\n-    selector=r\"[a-zA-Z_\\-]+\",\n+    selector=IDENTIFIER,\n     combinator_child=\">\",\n     new_selector=r\",\",\n     declaration_set_start=r\"\\{\",\n", "issue": "[CSS] Descendant type selectors can't have a numeric in their name\nConsider the following code:\r\n\r\n```python\r\nfrom textual.app        import App, ComposeResult\r\nfrom textual.containers import Vertical\r\nfrom textual.widgets    import Header, Footer, Label\r\n\r\nclass LabelH1( Label ):\r\n    ...\r\n\r\nclass CSSOddnessApp( App[ None ] ):\r\n\r\n    CSS = \"\"\"\r\n    Vertical LabelH1 {\r\n        background: red;\r\n    }\r\n    \"\"\"\r\n\r\n    def compose( self ) -> ComposeResult:\r\n        yield Header()\r\n        yield Vertical(\r\n            Label( \"Label\" ),\r\n            LabelH1( \"LabelH1\" ),\r\n        )\r\n        yield Footer()\r\n\r\nif __name__ == \"__main__\":\r\n    CSSOddnessApp().run()\r\n```\r\n\r\nWhen run we get the following error:\r\n\r\n```\r\n Error in stylesheet:\r\n /Users/davep/develop/python/textual-sandbox/css_oddness.py:CSSOddnessApp:1:19\r\n\u256d\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256e\r\n\u2502 \u2771 1 \u2502                                                                                                                                                      \u2502\r\n\u2502   2 \u2502   Vertical LabelH1 {                                                                                                                                 \u2502\r\n\u2502   3 \u2502   \u2502   background: red;                                                                                                                               \u2502\r\n\u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256f\r\n \u2022 Expected one of 'combinator child', 'comment start', 'declaration set start', 'new selector', 'pseudo class', 'selector', 'selector class', 'selector id',\r\n 'selector universal', or 'whitespace'.\r\n \u2022 Did you forget a semicolon at the end of a line?\r\n```\r\n\r\nThe same thing happens with `Vertical LabelH1`. On the other hand, if I remove the number from the inherited label widget:\r\n\r\n```python\r\nfrom textual.app        import App, ComposeResult\r\nfrom textual.containers import Vertical\r\nfrom textual.widgets    import Header, Footer, Label\r\n\r\nclass LabelHOne( Label ):\r\n    ...\r\n\r\nclass CSSOddnessApp( App[ None ] ):\r\n\r\n    CSS = \"\"\"\r\n    Vertical LabelHOne {\r\n        background: red;\r\n    }\r\n    \"\"\"\r\n\r\n    def compose( self ) -> ComposeResult:\r\n        yield Header()\r\n        yield Vertical(\r\n            Label( \"Label\" ),\r\n            LabelHOne( \"LabelHOne\" ),\r\n        )\r\n        yield Footer()\r\n\r\nif __name__ == \"__main__\":\r\n    CSSOddnessApp().run()\r\n```\r\n\r\nthis works fine. Likewise, if I retain the name but *don't* use combination:\r\n\r\n```python\r\nfrom textual.app        import App, ComposeResult\r\nfrom textual.containers import Vertical\r\nfrom textual.widgets    import Header, Footer, Label\r\n\r\nclass LabelH1( Label ):\r\n    ...\r\n\r\nclass CSSOddnessApp( App[ None ] ):\r\n\r\n    CSS = \"\"\"\r\n    LabelH1 {\r\n        background: red;\r\n    }\r\n    \"\"\"\r\n\r\n    def compose( self ) -> ComposeResult:\r\n        yield Header()\r\n        yield Vertical(\r\n            Label( \"Label\" ),\r\n            LabelH1( \"LabelH1\" ),\r\n        )\r\n        yield Footer()\r\n\r\nif __name__ == \"__main__\":\r\n    CSSOddnessApp().run()\r\n```\r\n\r\nthat also works fine.\r\n\r\nI would suspect a variation on #1253.\n[CSS] Descendant type selectors can't have a numeric in their name\nConsider the following code:\r\n\r\n```python\r\nfrom textual.app        import App, ComposeResult\r\nfrom textual.containers import Vertical\r\nfrom textual.widgets    import Header, Footer, Label\r\n\r\nclass LabelH1( Label ):\r\n    ...\r\n\r\nclass CSSOddnessApp( App[ None ] ):\r\n\r\n    CSS = \"\"\"\r\n    Vertical LabelH1 {\r\n        background: red;\r\n    }\r\n    \"\"\"\r\n\r\n    def compose( self ) -> ComposeResult:\r\n        yield Header()\r\n        yield Vertical(\r\n            Label( \"Label\" ),\r\n            LabelH1( \"LabelH1\" ),\r\n        )\r\n        yield Footer()\r\n\r\nif __name__ == \"__main__\":\r\n    CSSOddnessApp().run()\r\n```\r\n\r\nWhen run we get the following error:\r\n\r\n```\r\n Error in stylesheet:\r\n /Users/davep/develop/python/textual-sandbox/css_oddness.py:CSSOddnessApp:1:19\r\n\u256d\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256e\r\n\u2502 \u2771 1 \u2502                                                                                                                                                      \u2502\r\n\u2502   2 \u2502   Vertical LabelH1 {                                                                                                                                 \u2502\r\n\u2502   3 \u2502   \u2502   background: red;                                                                                                                               \u2502\r\n\u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256f\r\n \u2022 Expected one of 'combinator child', 'comment start', 'declaration set start', 'new selector', 'pseudo class', 'selector', 'selector class', 'selector id',\r\n 'selector universal', or 'whitespace'.\r\n \u2022 Did you forget a semicolon at the end of a line?\r\n```\r\n\r\nThe same thing happens with `Vertical LabelH1`. On the other hand, if I remove the number from the inherited label widget:\r\n\r\n```python\r\nfrom textual.app        import App, ComposeResult\r\nfrom textual.containers import Vertical\r\nfrom textual.widgets    import Header, Footer, Label\r\n\r\nclass LabelHOne( Label ):\r\n    ...\r\n\r\nclass CSSOddnessApp( App[ None ] ):\r\n\r\n    CSS = \"\"\"\r\n    Vertical LabelHOne {\r\n        background: red;\r\n    }\r\n    \"\"\"\r\n\r\n    def compose( self ) -> ComposeResult:\r\n        yield Header()\r\n        yield Vertical(\r\n            Label( \"Label\" ),\r\n            LabelHOne( \"LabelHOne\" ),\r\n        )\r\n        yield Footer()\r\n\r\nif __name__ == \"__main__\":\r\n    CSSOddnessApp().run()\r\n```\r\n\r\nthis works fine. Likewise, if I retain the name but *don't* use combination:\r\n\r\n```python\r\nfrom textual.app        import App, ComposeResult\r\nfrom textual.containers import Vertical\r\nfrom textual.widgets    import Header, Footer, Label\r\n\r\nclass LabelH1( Label ):\r\n    ...\r\n\r\nclass CSSOddnessApp( App[ None ] ):\r\n\r\n    CSS = \"\"\"\r\n    LabelH1 {\r\n        background: red;\r\n    }\r\n    \"\"\"\r\n\r\n    def compose( self ) -> ComposeResult:\r\n        yield Header()\r\n        yield Vertical(\r\n            Label( \"Label\" ),\r\n            LabelH1( \"LabelH1\" ),\r\n        )\r\n        yield Footer()\r\n\r\nif __name__ == \"__main__\":\r\n    CSSOddnessApp().run()\r\n```\r\n\r\nthat also works fine.\r\n\r\nI would suspect a variation on #1253.\n", "before_files": [{"content": "from __future__ import annotations\n\nimport re\nfrom pathlib import PurePath\nfrom typing import Iterable\n\nfrom textual.css.tokenizer import Expect, Token, Tokenizer\n\nPERCENT = r\"-?\\d+\\.?\\d*%\"\nDECIMAL = r\"-?\\d+\\.?\\d*\"\nCOMMA = r\"\\s*,\\s*\"\nOPEN_BRACE = r\"\\(\\s*\"\nCLOSE_BRACE = r\"\\s*\\)\"\n\nHEX_COLOR = r\"\\#[0-9a-fA-F]{8}|\\#[0-9a-fA-F]{6}|\\#[0-9a-fA-F]{4}|\\#[0-9a-fA-F]{3}\"\nRGB_COLOR = rf\"rgb{OPEN_BRACE}{DECIMAL}{COMMA}{DECIMAL}{COMMA}{DECIMAL}{CLOSE_BRACE}|rgba{OPEN_BRACE}{DECIMAL}{COMMA}{DECIMAL}{COMMA}{DECIMAL}{COMMA}{DECIMAL}{CLOSE_BRACE}\"\nHSL_COLOR = rf\"hsl{OPEN_BRACE}{DECIMAL}{COMMA}{PERCENT}{COMMA}{PERCENT}{CLOSE_BRACE}|hsla{OPEN_BRACE}{DECIMAL}{COMMA}{PERCENT}{COMMA}{PERCENT}{COMMA}{DECIMAL}{CLOSE_BRACE}\"\n\nCOMMENT_START = r\"\\/\\*\"\nSCALAR = rf\"{DECIMAL}(?:fr|%|w|h|vw|vh)\"\nDURATION = r\"\\d+\\.?\\d*(?:ms|s)\"\nNUMBER = r\"\\-?\\d+\\.?\\d*\"\nCOLOR = rf\"{HEX_COLOR}|{RGB_COLOR}|{HSL_COLOR}\"\nKEY_VALUE = r\"[a-zA-Z_-][a-zA-Z0-9_-]*=[0-9a-zA-Z_\\-\\/]+\"\nTOKEN = \"[a-zA-Z][a-zA-Z0-9_-]*\"\nSTRING = r\"\\\".*?\\\"\"\nVARIABLE_REF = r\"\\$[a-zA-Z0-9_\\-]+\"\n\nIDENTIFIER = r\"[a-zA-Z_\\-][a-zA-Z0-9_\\-]*\"\n\n# Values permitted in variable and rule declarations.\nDECLARATION_VALUES = {\n    \"scalar\": SCALAR,\n    \"duration\": DURATION,\n    \"number\": NUMBER,\n    \"color\": COLOR,\n    \"key_value\": KEY_VALUE,\n    \"token\": TOKEN,\n    \"string\": STRING,\n    \"variable_ref\": VARIABLE_REF,\n}\n\n# The tokenizers \"expectation\" while at the root/highest level of scope\n# in the CSS file. At this level we might expect to see selectors, comments,\n# variable definitions etc.\nexpect_root_scope = Expect(\n    whitespace=r\"\\s+\",\n    comment_start=COMMENT_START,\n    selector_start_id=r\"\\#\" + IDENTIFIER,\n    selector_start_class=r\"\\.\" + IDENTIFIER,\n    selector_start_universal=r\"\\*\",\n    selector_start=IDENTIFIER,\n    variable_name=rf\"{VARIABLE_REF}:\",\n).expect_eof(True)\n\n# After a variable declaration e.g. \"$warning-text: TOKENS;\"\n#              for tokenizing variable value ------^~~~~~~^\nexpect_variable_name_continue = Expect(\n    variable_value_end=r\"\\n|;\",\n    whitespace=r\"\\s+\",\n    comment_start=COMMENT_START,\n    **DECLARATION_VALUES,\n).expect_eof(True)\n\nexpect_comment_end = Expect(\n    comment_end=re.escape(\"*/\"),\n)\n\n# After we come across a selector in CSS e.g. \".my-class\", we may\n# find other selectors, pseudo-classes... e.g. \".my-class :hover\"\nexpect_selector_continue = Expect(\n    whitespace=r\"\\s+\",\n    comment_start=COMMENT_START,\n    pseudo_class=r\"\\:[a-zA-Z_-]+\",\n    selector_id=r\"\\#[a-zA-Z_\\-][a-zA-Z0-9_\\-]*\",\n    selector_class=r\"\\.[a-zA-Z_\\-][a-zA-Z0-9_\\-]*\",\n    selector_universal=r\"\\*\",\n    selector=r\"[a-zA-Z_\\-]+\",\n    combinator_child=\">\",\n    new_selector=r\",\",\n    declaration_set_start=r\"\\{\",\n)\n\n# A rule declaration e.g. \"text: red;\"\n#                          ^---^\nexpect_declaration = Expect(\n    whitespace=r\"\\s+\",\n    comment_start=COMMENT_START,\n    declaration_name=r\"[a-zA-Z_\\-]+\\:\",\n    declaration_set_end=r\"\\}\",\n)\n\nexpect_declaration_solo = Expect(\n    whitespace=r\"\\s+\",\n    comment_start=COMMENT_START,\n    declaration_name=r\"[a-zA-Z_\\-]+\\:\",\n    declaration_set_end=r\"\\}\",\n).expect_eof(True)\n\n# The value(s)/content from a rule declaration e.g. \"text: red;\"\n#                                                         ^---^\nexpect_declaration_content = Expect(\n    declaration_end=r\";\",\n    whitespace=r\"\\s+\",\n    comment_start=COMMENT_START,\n    **DECLARATION_VALUES,\n    important=r\"\\!important\",\n    comma=\",\",\n    declaration_set_end=r\"\\}\",\n)\n\nexpect_declaration_content_solo = Expect(\n    declaration_end=r\";\",\n    whitespace=r\"\\s+\",\n    comment_start=COMMENT_START,\n    **DECLARATION_VALUES,\n    important=r\"\\!important\",\n    comma=\",\",\n    declaration_set_end=r\"\\}\",\n).expect_eof(True)\n\n\nclass TokenizerState:\n    \"\"\"State machine for the tokenizer.\n\n    Attributes:\n        EXPECT: The initial expectation of the tokenizer. Since we start tokenizing\n            at the root scope, we might expect to see either a variable or selector, for example.\n        STATE_MAP: Maps token names to Expects, defines the sets of valid tokens\n            that we'd expect to see next, given the current token. For example, if\n            we've just processed a variable declaration name, we next expect to see\n            the value of that variable.\n    \"\"\"\n\n    EXPECT = expect_root_scope\n    STATE_MAP = {\n        \"variable_name\": expect_variable_name_continue,\n        \"variable_value_end\": expect_root_scope,\n        \"selector_start\": expect_selector_continue,\n        \"selector_start_id\": expect_selector_continue,\n        \"selector_start_class\": expect_selector_continue,\n        \"selector_start_universal\": expect_selector_continue,\n        \"selector_id\": expect_selector_continue,\n        \"selector_class\": expect_selector_continue,\n        \"selector_universal\": expect_selector_continue,\n        \"declaration_set_start\": expect_declaration,\n        \"declaration_name\": expect_declaration_content,\n        \"declaration_end\": expect_declaration,\n        \"declaration_set_end\": expect_root_scope,\n    }\n\n    def __call__(self, code: str, path: str | PurePath) -> Iterable[Token]:\n        tokenizer = Tokenizer(code, path=path)\n        expect = self.EXPECT\n        get_token = tokenizer.get_token\n        get_state = self.STATE_MAP.get\n        while True:\n            token = get_token(expect)\n            name = token.name\n            if name == \"comment_start\":\n                tokenizer.skip_to(expect_comment_end)\n                continue\n            elif name == \"eof\":\n                break\n            expect = get_state(name, expect)\n            yield token\n\n\nclass DeclarationTokenizerState(TokenizerState):\n    EXPECT = expect_declaration_solo\n    STATE_MAP = {\n        \"declaration_name\": expect_declaration_content,\n        \"declaration_end\": expect_declaration_solo,\n    }\n\n\nclass ValueTokenizerState(TokenizerState):\n    EXPECT = expect_declaration_content_solo\n\n\ntokenize = TokenizerState()\ntokenize_declarations = DeclarationTokenizerState()\ntokenize_value = ValueTokenizerState()\n\n\ndef tokenize_values(values: dict[str, str]) -> dict[str, list[Token]]:\n    \"\"\"Tokens the values in a dict of strings.\n\n    Args:\n        values: A mapping of CSS variable name on to a value, to be\n            added to the CSS context.\n\n    Returns:\n        A mapping of name on to a list of tokens,\n    \"\"\"\n    value_tokens = {\n        name: list(tokenize_value(value, \"__name__\")) for name, value in values.items()\n    }\n    return value_tokens\n", "path": "src/textual/css/tokenize.py"}], "after_files": [{"content": "from __future__ import annotations\n\nimport re\nfrom pathlib import PurePath\nfrom typing import Iterable\n\nfrom textual.css.tokenizer import Expect, Token, Tokenizer\n\nPERCENT = r\"-?\\d+\\.?\\d*%\"\nDECIMAL = r\"-?\\d+\\.?\\d*\"\nCOMMA = r\"\\s*,\\s*\"\nOPEN_BRACE = r\"\\(\\s*\"\nCLOSE_BRACE = r\"\\s*\\)\"\n\nHEX_COLOR = r\"\\#[0-9a-fA-F]{8}|\\#[0-9a-fA-F]{6}|\\#[0-9a-fA-F]{4}|\\#[0-9a-fA-F]{3}\"\nRGB_COLOR = rf\"rgb{OPEN_BRACE}{DECIMAL}{COMMA}{DECIMAL}{COMMA}{DECIMAL}{CLOSE_BRACE}|rgba{OPEN_BRACE}{DECIMAL}{COMMA}{DECIMAL}{COMMA}{DECIMAL}{COMMA}{DECIMAL}{CLOSE_BRACE}\"\nHSL_COLOR = rf\"hsl{OPEN_BRACE}{DECIMAL}{COMMA}{PERCENT}{COMMA}{PERCENT}{CLOSE_BRACE}|hsla{OPEN_BRACE}{DECIMAL}{COMMA}{PERCENT}{COMMA}{PERCENT}{COMMA}{DECIMAL}{CLOSE_BRACE}\"\n\nCOMMENT_START = r\"\\/\\*\"\nSCALAR = rf\"{DECIMAL}(?:fr|%|w|h|vw|vh)\"\nDURATION = r\"\\d+\\.?\\d*(?:ms|s)\"\nNUMBER = r\"\\-?\\d+\\.?\\d*\"\nCOLOR = rf\"{HEX_COLOR}|{RGB_COLOR}|{HSL_COLOR}\"\nKEY_VALUE = r\"[a-zA-Z_-][a-zA-Z0-9_-]*=[0-9a-zA-Z_\\-\\/]+\"\nTOKEN = \"[a-zA-Z][a-zA-Z0-9_-]*\"\nSTRING = r\"\\\".*?\\\"\"\nVARIABLE_REF = r\"\\$[a-zA-Z0-9_\\-]+\"\n\nIDENTIFIER = r\"[a-zA-Z_\\-][a-zA-Z0-9_\\-]*\"\n\n# Values permitted in variable and rule declarations.\nDECLARATION_VALUES = {\n    \"scalar\": SCALAR,\n    \"duration\": DURATION,\n    \"number\": NUMBER,\n    \"color\": COLOR,\n    \"key_value\": KEY_VALUE,\n    \"token\": TOKEN,\n    \"string\": STRING,\n    \"variable_ref\": VARIABLE_REF,\n}\n\n# The tokenizers \"expectation\" while at the root/highest level of scope\n# in the CSS file. At this level we might expect to see selectors, comments,\n# variable definitions etc.\nexpect_root_scope = Expect(\n    whitespace=r\"\\s+\",\n    comment_start=COMMENT_START,\n    selector_start_id=r\"\\#\" + IDENTIFIER,\n    selector_start_class=r\"\\.\" + IDENTIFIER,\n    selector_start_universal=r\"\\*\",\n    selector_start=IDENTIFIER,\n    variable_name=rf\"{VARIABLE_REF}:\",\n).expect_eof(True)\n\n# After a variable declaration e.g. \"$warning-text: TOKENS;\"\n#              for tokenizing variable value ------^~~~~~~^\nexpect_variable_name_continue = Expect(\n    variable_value_end=r\"\\n|;\",\n    whitespace=r\"\\s+\",\n    comment_start=COMMENT_START,\n    **DECLARATION_VALUES,\n).expect_eof(True)\n\nexpect_comment_end = Expect(\n    comment_end=re.escape(\"*/\"),\n)\n\n# After we come across a selector in CSS e.g. \".my-class\", we may\n# find other selectors, pseudo-classes... e.g. \".my-class :hover\"\nexpect_selector_continue = Expect(\n    whitespace=r\"\\s+\",\n    comment_start=COMMENT_START,\n    pseudo_class=r\"\\:[a-zA-Z_-]+\",\n    selector_id=r\"\\#[a-zA-Z_\\-][a-zA-Z0-9_\\-]*\",\n    selector_class=r\"\\.[a-zA-Z_\\-][a-zA-Z0-9_\\-]*\",\n    selector_universal=r\"\\*\",\n    selector=IDENTIFIER,\n    combinator_child=\">\",\n    new_selector=r\",\",\n    declaration_set_start=r\"\\{\",\n)\n\n# A rule declaration e.g. \"text: red;\"\n#                          ^---^\nexpect_declaration = Expect(\n    whitespace=r\"\\s+\",\n    comment_start=COMMENT_START,\n    declaration_name=r\"[a-zA-Z_\\-]+\\:\",\n    declaration_set_end=r\"\\}\",\n)\n\nexpect_declaration_solo = Expect(\n    whitespace=r\"\\s+\",\n    comment_start=COMMENT_START,\n    declaration_name=r\"[a-zA-Z_\\-]+\\:\",\n    declaration_set_end=r\"\\}\",\n).expect_eof(True)\n\n# The value(s)/content from a rule declaration e.g. \"text: red;\"\n#                                                         ^---^\nexpect_declaration_content = Expect(\n    declaration_end=r\";\",\n    whitespace=r\"\\s+\",\n    comment_start=COMMENT_START,\n    **DECLARATION_VALUES,\n    important=r\"\\!important\",\n    comma=\",\",\n    declaration_set_end=r\"\\}\",\n)\n\nexpect_declaration_content_solo = Expect(\n    declaration_end=r\";\",\n    whitespace=r\"\\s+\",\n    comment_start=COMMENT_START,\n    **DECLARATION_VALUES,\n    important=r\"\\!important\",\n    comma=\",\",\n    declaration_set_end=r\"\\}\",\n).expect_eof(True)\n\n\nclass TokenizerState:\n    \"\"\"State machine for the tokenizer.\n\n    Attributes:\n        EXPECT: The initial expectation of the tokenizer. Since we start tokenizing\n            at the root scope, we might expect to see either a variable or selector, for example.\n        STATE_MAP: Maps token names to Expects, defines the sets of valid tokens\n            that we'd expect to see next, given the current token. For example, if\n            we've just processed a variable declaration name, we next expect to see\n            the value of that variable.\n    \"\"\"\n\n    EXPECT = expect_root_scope\n    STATE_MAP = {\n        \"variable_name\": expect_variable_name_continue,\n        \"variable_value_end\": expect_root_scope,\n        \"selector_start\": expect_selector_continue,\n        \"selector_start_id\": expect_selector_continue,\n        \"selector_start_class\": expect_selector_continue,\n        \"selector_start_universal\": expect_selector_continue,\n        \"selector_id\": expect_selector_continue,\n        \"selector_class\": expect_selector_continue,\n        \"selector_universal\": expect_selector_continue,\n        \"declaration_set_start\": expect_declaration,\n        \"declaration_name\": expect_declaration_content,\n        \"declaration_end\": expect_declaration,\n        \"declaration_set_end\": expect_root_scope,\n    }\n\n    def __call__(self, code: str, path: str | PurePath) -> Iterable[Token]:\n        tokenizer = Tokenizer(code, path=path)\n        expect = self.EXPECT\n        get_token = tokenizer.get_token\n        get_state = self.STATE_MAP.get\n        while True:\n            token = get_token(expect)\n            name = token.name\n            if name == \"comment_start\":\n                tokenizer.skip_to(expect_comment_end)\n                continue\n            elif name == \"eof\":\n                break\n            expect = get_state(name, expect)\n            yield token\n\n\nclass DeclarationTokenizerState(TokenizerState):\n    EXPECT = expect_declaration_solo\n    STATE_MAP = {\n        \"declaration_name\": expect_declaration_content,\n        \"declaration_end\": expect_declaration_solo,\n    }\n\n\nclass ValueTokenizerState(TokenizerState):\n    EXPECT = expect_declaration_content_solo\n\n\ntokenize = TokenizerState()\ntokenize_declarations = DeclarationTokenizerState()\ntokenize_value = ValueTokenizerState()\n\n\ndef tokenize_values(values: dict[str, str]) -> dict[str, list[Token]]:\n    \"\"\"Tokens the values in a dict of strings.\n\n    Args:\n        values: A mapping of CSS variable name on to a value, to be\n            added to the CSS context.\n\n    Returns:\n        A mapping of name on to a list of tokens,\n    \"\"\"\n    value_tokens = {\n        name: list(tokenize_value(value, \"__name__\")) for name, value in values.items()\n    }\n    return value_tokens\n", "path": "src/textual/css/tokenize.py"}]}