Repository: Textualize/textual

Issue:
Nested colors in markup are ignored
While investigating #5820, I found other cases where nested colors in markup seem to be ignored. 

Here's a simple example:

```
[blue][green][red]R[/red]G[/green]B[/blue]
```

![Image](https://github.com/user-attachments/assets/31313a9d-550a-4962-ac92-5eda91dac814)

Hints from issue comments:
@github-actions: We found the following entries in the [FAQ](https://textual.textualize.io/FAQ/) which you may find helpful:


- [Why doesn't Textual look good on macOS?](https://textual.textualize.io/FAQ/#why-doesnt-textual-look-good-on-macos)
- [Why doesn't Textual support ANSI themes?](https://textual.textualize.io/FAQ/#why-doesnt-textual-support-ansi-themes)
- [How do I center a widget in a screen?](https://textual.textualize.io/FAQ/#how-do-i-center-a-widget-in-a-screen)

Feel free to close this issue if you found an answer in the FAQ. Otherwise, please give us a little time to review.

This is an automated reply, generated by [FAQtory](https://github.com/willmcgugan/faqtory)@github-actions: Don't forget to [star](https://github.com/Textualize/textual) the repository!

Follow [@textualizeio](https://twitter.com/textualizeio) for Textual updates.

Solution patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 041c7ff43..e2c12be03 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -15,14 +15,17 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
 - Fixed running `App.run` after `asyncio.run` https://github.com/Textualize/textual/pull/5799
 - Fixed triggering a deprecation warning in py >= 3.10 https://github.com/Textualize/textual/pull/5799
 - Fixed `Input` invalid cursor position after updating the value https://github.com/Textualize/textual/issues/5811
+- Fixed `DEFAULT_CLASSES` when applied to App https://github.com/Textualize/textual/pull/5827
+- Fixed order of implicit content tag closing https://github.com/Textualize/textual/pull/5823
 
 ### Added
 
 - Exposed `CollapsibleTitle` https://github.com/Textualize/textual/pull/5810
+- Added `Color.hsv` property and `Color.from_hsv` class method https://github.com/Textualize/textual/pull/5803
 
-### Added
+### Changed
 
-- Added `Color.hsv` property and `Color.from_hsv` class method https://github.com/Textualize/textual/pull/5803
+- Added a few features to `python -m textual.markup` playgound https://github.com/Textualize/textual/pull/5823
 
 ## [3.2.0] - 2025-05-02
 
diff --git a/src/textual/app.py b/src/textual/app.py
index ba87005ab..478593124 100644
--- a/src/textual/app.py
+++ b/src/textual/app.py
@@ -565,7 +565,7 @@ class App(Generic[ReturnType], DOMNode):
             CssPathError: When the supplied CSS path(s) are an unexpected type.
         """
         self._start_time = perf_counter()
-        super().__init__()
+        super().__init__(classes=self.DEFAULT_CLASSES)
         self.features: frozenset[FeatureFlag] = parse_features(os.getenv("TEXTUAL", ""))
 
         self._registered_themes: dict[str, Theme] = {}
diff --git a/src/textual/markup.py b/src/textual/markup.py
index 06a0effd9..c2a4571bd 100644
--- a/src/textual/markup.py
+++ b/src/textual/markup.py
@@ -5,6 +5,8 @@ Utilities related to content markup.
 
 from __future__ import annotations
 
+from operator import itemgetter
+
 from textual.css.parse import substitute_references
 from textual.css.tokenizer import UnexpectedEnd
 
@@ -407,16 +409,25 @@ def _to_content(
                 if not style_stack:
                     raise MarkupError("auto closing tag ('[/]') has nothing to close")
                 open_position, tag_body, _ = style_stack.pop()
-                spans.append(Span(open_position, position, tag_body))
+                if open_position != position:
+                    spans.append(Span(open_position, position, tag_body))
 
     content_text = "".join(text)
     text_length = len(content_text)
-    for position, tag_body, _ in style_stack:
-        spans.append(Span(position, text_length, tag_body))
+    if style_stack and text_length:
+        spans.extend(
+            [
+                Span(position, text_length, tag_body)
+                for position, tag_body, _ in reversed(style_stack)
+                if position != text_length
+            ]
+        )
+    spans.reverse()
+    spans.sort(key=itemgetter(0))  # Zeroth item of Span is 'start' attribute
 
     content = Content(
         content_text,
-        [Span(0, len(content_text), style), *spans] if style else spans,
+        [Span(0, text_length, style), *spans] if (style and text_length) else spans,
     )
 
     return content


Test patch:
diff --git a/tests/snapshot_tests/test_snapshots.py b/tests/snapshot_tests/test_snapshots.py
index 7f95c17f8..5b9db6892 100644
--- a/tests/snapshot_tests/test_snapshots.py
+++ b/tests/snapshot_tests/test_snapshots.py
@@ -2554,6 +2554,7 @@ def test_pseudo_classes(snap_compare):
 
     assert snap_compare(PSApp())
 
+
 def test_child_pseudo_classes(snap_compare):
     """Test pseudo classes added in https://github.com/Textualize/textual/pull/XXXX
 
@@ -4105,6 +4106,7 @@ def test_breakpoints_horizontal(snap_compare, size):
 
     assert snap_compare(BreakpointApp(), terminal_size=size)
 
+
 @pytest.mark.parametrize(
     "size",
     [
@@ -4155,6 +4157,7 @@ def test_breakpoints_vertical(snap_compare, size):
 
     assert snap_compare(BreakpointApp(), terminal_size=size)
 
+
 def test_compact(snap_compare):
     """Test compact styles.
 
@@ -4186,3 +4189,29 @@ def test_compact(snap_compare):
                     yield TextArea("Edit me", compact=True)
 
     assert snap_compare(CompactApp())
+
+
+def test_app_default_classes(snap_compare):
+    """Test that default classes classvar is working.
+
+    You should see a blue screen with a white border, confirming that
+    the classes foo and bar have been added to the app.
+
+    """
+    from textual.app import App
+
+    class DC(App):
+        DEFAULT_CLASSES = "foo bar"
+
+        CSS = """
+        DC {
+            &.foo {
+                Screen { background: blue; }
+            }
+            &.bar {
+                Screen { border: white; }
+            }
+        }
+        """
+
+    assert snap_compare(DC())
diff --git a/tests/test_markup.py b/tests/test_markup.py
index 718c61752..93e2a37b3 100644
--- a/tests/test_markup.py
+++ b/tests/test_markup.py
@@ -35,9 +35,9 @@ from textual.markup import MarkupError, to_content
             Content(
                 "What is up with you?",
                 spans=[
+                    Span(0, 20, style="b"),
                     Span(0, 10, style="on red"),
                     Span(5, 20, style="i"),
-                    Span(0, 20, style="b"),
                 ],
             ),
         ),
@@ -84,6 +84,64 @@ from textual.markup import MarkupError, to_content
                 spans=[Span(0, 35, style="#ff0000"), Span(7, 35, style="#ffffff")],
             ),
         ),
+        (
+            "[blue][green][red]R[/red]G[/green]B[/blue]",
+            Content(
+                "RGB",
+                spans=[
+                    Span(0, 3, "blue"),
+                    Span(0, 2, "green"),
+                    Span(0, 1, "red"),
+                ],
+            ),
+        ),
+        (
+            "[red][blue]X[/blue][/red]",
+            Content(
+                "X",
+                spans=[
+                    Span(0, 1, "red"),
+                    Span(0, 1, "blue"),
+                ],
+            ),
+        ),
+        # Non-nested tags
+        (
+            "[red][blue]X[/red][/blue]",
+            Content(
+                "X",
+                spans=[
+                    Span(0, 1, "blue"),
+                    Span(0, 1, "red"),
+                ],
+            ),
+        ),
+        (
+            "[red][blue]X[/red]",
+            Content(
+                "X",
+                spans=[
+                    Span(0, 1, "blue"),
+                    Span(0, 1, "red"),
+                ],
+            ),
+        ),
+        (
+            "[red][blue]X",
+            Content(
+                "X",
+                spans=[
+                    Span(0, 1, "red"),
+                    Span(0, 1, "blue"),
+                ],
+            ),
+        ),
+        # Edge cases
+        ("[bold][/bold]", Content("")),
+        ("[bold][/]", Content("")),
+        ("[bold]", Content("")),
+        ("", Content("")),
+        ("[red][green][/red]", Content("")),
     ],
 )
 def test_to_content(markup: str, content: Content):