Repository: Textualize/textual

Issue:
UI doesn't react to visibility flag change
I have an app that has a dialogue box that is invisible unless someone clicks a button. When they do, I want the dialogue to appear instantly so they can confirm their action. The dialogue is in a "higher" layer than the rest of the app, and it starts invisible based on CSS. 

When they click a button, I change it to visible, but it doesn't appear until something else makes the window redraw. For eg, if i resize the window, it appears instantly. 

starting CSS: 
```
#confirm_panel {
    width: 30%;
    height: 16%;
    border: lime double;
    overflow: hidden;
    layer: above;
    offset: 50 20;
    visibility: hidden;
}
```
Change to visible. I have tried it via styles and via the new visible flag. Both have the same behavior: 
```
        self.query_one('#confirm_panel').styles.visibility = 'visible'
```

Using 0.6.0


Comments:

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8dc19b1ea..43062e425 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
 - Fixed issues with nested auto dimensions https://github.com/Textualize/textual/issues/1402
 - Fixed watch method incorrectly running on first set when value hasn't changed and init=False https://github.com/Textualize/textual/pull/1367
 - `App.dark` can now be set from `App.on_load` without an error being raised  https://github.com/Textualize/textual/issues/1369
+- Fixed setting `visibility` changes needing a `refresh` https://github.com/Textualize/textual/issues/1355
 
 ### Added
 
diff --git a/src/textual/css/styles.py b/src/textual/css/styles.py
index 96a28b8ba..0cff4a39e 100644
--- a/src/textual/css/styles.py
+++ b/src/textual/css/styles.py
@@ -209,7 +209,7 @@ class StylesBase(ABC):
     node: DOMNode | None = None
 
     display = StringEnumProperty(VALID_DISPLAY, "block", layout=True)
-    visibility = StringEnumProperty(VALID_VISIBILITY, "visible")
+    visibility = StringEnumProperty(VALID_VISIBILITY, "visible", layout=True)
     layout = LayoutProperty()
 
     auto_color = BooleanProperty(default=False)


Test patch:
diff --git a/tests/test_visibility_change.py b/tests/test_visibility_change.py
new file mode 100644
index 000000000..b06ea0e17
--- /dev/null
+++ b/tests/test_visibility_change.py
@@ -0,0 +1,46 @@
+"""See https://github.com/Textualize/textual/issues/1355 as the motivation for these tests."""
+
+from textual.app import App, ComposeResult
+from textual.containers import Vertical
+from textual.widget import Widget
+
+
+class VisibleTester(App[None]):
+    """An app for testing visibility changes."""
+
+    CSS = """
+    Widget {
+        height: 1fr;
+    }
+    .hidden {
+        visibility: hidden;
+    }
+    """
+
+    def compose(self) -> ComposeResult:
+        yield Vertical(
+            Widget(id="keep"), Widget(id="hide-via-code"), Widget(id="hide-via-css")
+        )
+
+
+async def test_visibility_changes() -> None:
+    """Test changing visibility via code and CSS."""
+    async with VisibleTester().run_test() as pilot:
+        assert len(pilot.app.screen.visible_widgets) == 5
+        assert pilot.app.query_one("#keep").visible is True
+        assert pilot.app.query_one("#hide-via-code").visible is True
+        assert pilot.app.query_one("#hide-via-css").visible is True
+
+        pilot.app.query_one("#hide-via-code").styles.visibility = "hidden"
+        await pilot.pause(0)
+        assert len(pilot.app.screen.visible_widgets) == 4
+        assert pilot.app.query_one("#keep").visible is True
+        assert pilot.app.query_one("#hide-via-code").visible is False
+        assert pilot.app.query_one("#hide-via-css").visible is True
+
+        pilot.app.query_one("#hide-via-css").set_class(True, "hidden")
+        await pilot.pause(0)
+        assert len(pilot.app.screen.visible_widgets) == 3
+        assert pilot.app.query_one("#keep").visible is True
+        assert pilot.app.query_one("#hide-via-code").visible is False
+        assert pilot.app.query_one("#hide-via-css").visible is False