diff --git a/CHANGELOG.md b/CHANGELOG.md
index 440cb162c8..58377cf257 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,7 +5,7 @@ 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/).
-## [0.4.0] - Unreleased
+## [0.4.0] - 2022-11-08
### Changed
diff --git a/docs/blog/images/faster-updates.excalidraw.svg b/docs/blog/images/faster-updates.excalidraw.svg
new file mode 100644
index 0000000000..18340ee26a
--- /dev/null
+++ b/docs/blog/images/faster-updates.excalidraw.svg
@@ -0,0 +1,16 @@
+
\ No newline at end of file
diff --git a/docs/blog/index.md b/docs/blog/index.md
index 5ae47bcdbe..210ffd3f9b 100644
--- a/docs/blog/index.md
+++ b/docs/blog/index.md
@@ -1,3 +1,2 @@
# Textual Blog
-Welcome to the Textual blog, where we post about the latest releases and developments in the Textual world.
diff --git a/docs/blog/posts/release0-4-0.md b/docs/blog/posts/release0-4-0.md
new file mode 100644
index 0000000000..fb55cc4936
--- /dev/null
+++ b/docs/blog/posts/release0-4-0.md
@@ -0,0 +1,58 @@
+---
+draft: true
+date: 2022-11-08
+categories:
+ - Release
+authors:
+ - willmcgugan
+---
+
+# Version 0.4.0
+
+We've released version 0.4.0 of [Textual](https://pypi.org/search/?q=textual).
+
+As this is the first post tagged with `release` let me first explain where the blog fits in with releases. We plan on doing a post for every note-worthy release. Which likely means all but the most trivial updates (typos just aren't that interesting). Blog posts will be supplementary to release notes which you will find on the [Textual repository](https://github.com/Textualize/textual).
+
+Blog posts will give a little more background for the highlights in a release, and a rationale for changes and new additions. We embrace *building in public*, which means that we would like you to be as up-to-date with new developments as if you were sitting in our office. It's a small office, and you might not be a fan of the Scottish weather (it's [dreich](https://www.bbc.co.uk/news/uk-scotland-50476008)), but you can be here virtually.
+
+
+
+Release 0.4.0 follows 0.3.0, released on October 31st. Here are the highlights of the update.
+
+## Updated Mount Method
+
+The [mount](/api/widget/#textual.widget.Widget.mount) method has seen some work. We've dropped the ability to assign an `id` via keyword attributes, which wasn't terribly useful. Now, an `id` must be assigned via the constructor.
+
+The mount method has also grown `before` and `after` parameters which tell Textual where to add a new Widget (the default was to add it to the end). Here are a few examples:
+
+```python
+
+# Mount at the start
+self.mount(Button(id="Buy Coffee"), before=0)
+
+# Mount after a selector
+self.mount(Static("Password is incorrect"), after="Dialog Input.-error")
+
+tweet = self.query_one("Tweet")
+self.mount(Static("Consider switching to Mastodon"), after=tweet)
+
+```
+
+Textual needs much of the same kind of operations as the [JS API](https://developer.mozilla.org/en-US/docs/Web/API/Node/appendChild) exposed by the browser. But we are determined to make this way more intuitive. The new mount method is a step towards that.
+
+## Faster Updates
+
+Textual now writes to stdout in a thread. The upshot of this is that Textual can work on the next update before the terminal has displayed the previous frame.
+
+This means smoother updates all round! You may notice this when scrolling and animating, but even if you don't you will have more CPU cycles to play with.
+
+
+
+
+## Multiple CSS Paths
+
+Up to version 0.3.0, Textual would only read a single CSS file set in the `CSS_PATH` class variable. You can now supply a list of paths if you have more than one CSS file.
+
+This change was prompted by [tuilwindcss](https://github.com/koaning/tuilwindcss/) which brings a TailwindCSS like approach to building Textual Widgets. Also checkout [calmcode.io](https://calmcode.io/) by the same author, which is an amazing resource.
diff --git a/docs/custom_theme/main.html b/docs/custom_theme/main.html
index eaf7085836..2f490a4837 100644
--- a/docs/custom_theme/main.html
+++ b/docs/custom_theme/main.html
@@ -15,7 +15,6 @@
-
{% endblock %}
diff --git a/docs/stylesheets/custom.css b/docs/stylesheets/custom.css
index d333036012..ea1639ef10 100644
--- a/docs/stylesheets/custom.css
+++ b/docs/stylesheets/custom.css
@@ -13,11 +13,11 @@ h3 .doc-heading code {
monospace;
}
-body[data-md-color-primary="indigo"] .excalidraw svg {
+body[data-md-color-primary="black"] .excalidraw svg {
filter: invert(100%) hue-rotate(180deg);
}
-body[data-md-color-primary="indigo"] .excalidraw svg rect {
+body[data-md-color-primary="black"] .excalidraw svg rect {
fill: transparent;
}
diff --git a/mkdocs.yml b/mkdocs.yml
index 46f51a2d1e..4677863ad3 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -175,7 +175,7 @@ theme:
name: Switch to dark mode
- media: "(prefers-color-scheme: dark)"
scheme: slate
- primary: indigo
+ primary: black
toggle:
icon: material/weather-night
name: Switch to light mode
@@ -189,6 +189,7 @@ plugins:
as_creation: date
categories:
- categories
+ - release
- tags
- search:
- autorefs:
diff --git a/pyproject.toml b/pyproject.toml
index 85a3502330..71b9335a25 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[tool.poetry]
name = "textual"
-version = "0.4.0a3"
+version = "0.4.0"
homepage = "https://github.com/Textualize/textual"
description = "Modern Text User Interface framework"
authors = ["Will McGugan "]
diff --git a/src/textual/pilot.py b/src/textual/pilot.py
index 6f3f046a40..fee51539d3 100644
--- a/src/textual/pilot.py
+++ b/src/textual/pilot.py
@@ -32,7 +32,7 @@ async def press(self, *keys: str) -> None:
"""Simulate key-presses.
Args:
- *key: Keys to press.
+ *keys: Keys to press.
"""
if keys:
diff --git a/src/textual/reactive.py b/src/textual/reactive.py
index 36fd5b736c..2ee3ba5dfd 100644
--- a/src/textual/reactive.py
+++ b/src/textual/reactive.py
@@ -68,6 +68,7 @@ def init(
layout (bool, optional): Perform a layout on change. Defaults to False.
repaint (bool, optional): Perform a repaint on change. Defaults to True.
always_update(bool, optional): Call watchers even when the new value equals the old value. Defaults to False.
+
Returns:
Reactive: A Reactive instance which calls watchers or initialize.
"""