diff --git a/README.md b/README.md
index ea59078..465b1da 100644
--- a/README.md
+++ b/README.md
@@ -48,75 +48,58 @@ newer (installed automatically).
- **Live refresh** — open views re-read from disk as things change on disk;
press `p` to pause.
-### Keys
+Press `?` at any time for the key bindings available on the current screen.
-Press `?` at any time for the bindings available on the current screen.
+## Screenshots
-| Key | Does |
-| --- | --- |
-| `?` | Show key bindings for the current screen |
-| `Ctrl+P` | Command palette (actions, jump to change/spec, theme) |
-| `/` | Filter the current listing by name (`escape` clears) |
-| `enter` | Open the selected change or spec |
-| `k` / `d` | Kanban / dashboard |
-| `n` | New change |
-| `e` | Edit the current artifact in `$EDITOR` |
-| `r` | Refresh now |
-| `p` | Pause or resume live refresh |
-| `t` | Toggle the table of contents (change detail) |
-| `q` | Quit |
+Screenshots show an example project, not this repository.
-### Theming
+**Dashboard** — changes in an aligned table with stage, progress and task
+counts, over headline counts per stage.
-`openspec-ui` ships no palette of its own — all colour comes from the active
-Textual theme, so it follows your terminal. Switch themes in-session from the
-command palette (`Ctrl+P` → *Theme*). Nothing is written to disk: the app
-persists no state, theme included.
+
-## Design
+**Kanban** — `k`. Every column heading and card border carries its stage's
+colour, so the shape of the pipeline reads before any of the text does.
-`openspec-ui` is a **thin client**: it owns no state. Every read comes from
-`openspec ... --json` or direct file reads under `openspec/`; every write is
-delegated to the `openspec` CLI or to `$EDITOR`. This keeps the view layer
-replaceable (a future SvelteKit web UI could reuse the same data contract).
+
-## Status
+**Change detail** — `enter` on a change. Artifacts render as scrollable
+markdown with a table of contents; code blocks and ASCII diagrams keep their
+monospace alignment.
-Alpha. Built with [Textual](https://textual.textualize.io/). Supports OpenSpec
-CLI `1.7.x` (`@fission-ai/openspec`).
+
-## Releases
+**Tasks** — the `tasks.md` checklist, one section per heading with its
+done/total count. Finished sections start collapsed so the eye lands on what is
+left. Read-only: edits go through `$EDITOR`.
-Releases are **fully automated**. Maintainers never edit the version field by
-hand.
+
-1. Conventional commits land on `main` (via PRs).
-2. On each push to `main`, CI runs [`commitizen`](https://commitizen-tools.github.io/commitizen/)
- (`cz bump --yes --changelog`), which determines the next semver from the
- commit log, updates `version` in `pyproject.toml`, updates `CHANGELOG.md`,
- and creates/pushes a `v` tag.
-3. The tag push triggers a second CI job that builds the wheel+sdist and
- publishes them to [PyPI](https://pypi.org/project/openspec-ui/) using
- **trusted publishing (OIDC)** — no stored API tokens.
-4. `feat:`/`fix:`/breaking commits release; pure `chore:`/`docs:` commits do
- not (commitizen determines there is no increment).
+**Spec browser** — `enter` on a spec. Requirements expand to reveal their
+scenarios, which is the only way a spec with thirty of them stays readable.
-### Trusted publisher setup (one-time, manual)
+
-Before the publish job works, register a trusted publisher on
-[pypi.org](https://pypi.org) once:
+**Command palette** — `Ctrl+P`. Fuzzy-matched actions, plus every change and
+spec by name.
-- **PyPI project:** `openspec-ui` (create it if it does not yet exist).
-- **GitHub repo:** `mrn-dk/openspec-ui`.
-- **Workflow filename:** `.github/workflows/release.yml`.
-- **Environment name:** `pypi` (the publish job uses `environment: pypi`).
+
-This is a web step on pypi.org that cannot be automated from within the repo.
-Until it is done, the publish job will fail with an OIDC error; the build
-artifacts still exist in the workflow run and can be uploaded manually.
+**Filter** — `/`. Narrows the changes and specs by name; `escape` clears it.
-The first release also needs a seeded `v0.1.0` tag (commitizen bumps
-*relative* to an existing tag); after that, `cz bump` always has a reference.
+
+
+**Light theme** — all colour comes from the active Textual theme, so it follows
+your terminal.
+
+
## Contributing
@@ -133,21 +116,3 @@ Git history uses **Conventional Commits** and **conventional branch names**:
For changes managed through OpenSpec, see `openspec/config.yaml` for the
project context and per-operation guidance that the openspec skills follow.
-
-## Development
-
-```bash
-uv sync --group dev
-uv run pytest
-```
-
-The suite drives the real app through [Textual's headless test
-pilot](https://textual.textualize.io/guide/testing/), so it needs no terminal.
-It covers each screen's rendering and bindings, the thread-worker read path,
-theme readability under light, dark and ANSI themes, and that every screen
-still fits an 80x24 terminal. Tests that shell out to the `openspec` CLI skip
-themselves when it is not on PATH.
-
-## License
-
-MIT
diff --git a/docs/images/change-detail.png b/docs/images/change-detail.png
new file mode 100644
index 0000000..f73b1df
Binary files /dev/null and b/docs/images/change-detail.png differ
diff --git a/docs/images/command-palette.png b/docs/images/command-palette.png
new file mode 100644
index 0000000..6bc78f1
Binary files /dev/null and b/docs/images/command-palette.png differ
diff --git a/docs/images/dashboard-light.png b/docs/images/dashboard-light.png
new file mode 100644
index 0000000..703a311
Binary files /dev/null and b/docs/images/dashboard-light.png differ
diff --git a/docs/images/dashboard.png b/docs/images/dashboard.png
new file mode 100644
index 0000000..7ae8a1b
Binary files /dev/null and b/docs/images/dashboard.png differ
diff --git a/docs/images/filtering.png b/docs/images/filtering.png
new file mode 100644
index 0000000..167205d
Binary files /dev/null and b/docs/images/filtering.png differ
diff --git a/docs/images/kanban.png b/docs/images/kanban.png
new file mode 100644
index 0000000..d7ab2a2
Binary files /dev/null and b/docs/images/kanban.png differ
diff --git a/docs/images/spec-browser.png b/docs/images/spec-browser.png
new file mode 100644
index 0000000..fc0dbed
Binary files /dev/null and b/docs/images/spec-browser.png differ
diff --git a/docs/images/tasks.png b/docs/images/tasks.png
new file mode 100644
index 0000000..29bcf83
Binary files /dev/null and b/docs/images/tasks.png differ
diff --git a/openspec_ui/screens/dashboard.py b/openspec_ui/screens/dashboard.py
index 707d98b..41b9add 100644
--- a/openspec_ui/screens/dashboard.py
+++ b/openspec_ui/screens/dashboard.py
@@ -88,7 +88,7 @@ def __init__(self) -> None:
# -- compose --------------------------------------------------------
def compose(self) -> None:
- yield Header(show_clock=False)
+ yield Header(show_clock=False, icon="")
yield Static("", id="title")
yield FilterBar(id="filter")
yield Horizontal(
@@ -329,12 +329,16 @@ def _render_specs(self, specs) -> None:
self._show_specs_empty("No specs yet.")
return
self._show_specs_empty(None)
+ # Pad names to the longest so the counts line up in a column, matching
+ # the changes table above rather than sitting ragged beneath it.
+ name_width = max(len(spec.name) for spec in specs)
for spec in specs:
noun = "requirement" if spec.requirement_count == 1 else "requirements"
specs_list.append(
ListItem(
Label(
- f"{spec.name} [$text-muted]{spec.requirement_count} {noun}[/]"
+ f"{spec.name:<{name_width}} "
+ f"[$text-muted]{spec.requirement_count:>3} {noun}[/]"
),
name=spec.name,
)
diff --git a/openspec_ui/screens/detail.py b/openspec_ui/screens/detail.py
index 0b1792d..ec28396 100644
--- a/openspec_ui/screens/detail.py
+++ b/openspec_ui/screens/detail.py
@@ -195,7 +195,7 @@ def action_toggle_toc(self) -> None:
self._apply_toc()
def compose(self) -> None:
- yield Header(show_clock=False)
+ yield Header(show_clock=False, icon="")
yield Static("", id="title")
yield TabbedContent(id="tabs")
yield StatusBar()
diff --git a/openspec_ui/screens/kanban.py b/openspec_ui/screens/kanban.py
index 8c489e0..0af7006 100644
--- a/openspec_ui/screens/kanban.py
+++ b/openspec_ui/screens/kanban.py
@@ -118,7 +118,7 @@ def __init__(self) -> None:
self._filter = ""
def compose(self) -> None:
- yield Header(show_clock=False)
+ yield Header(show_clock=False, icon="")
yield Static("Kanban — change pipeline", id="title")
yield FilterBar(id="filter")
with Horizontal(id="board"):
diff --git a/openspec_ui/screens/spec_detail.py b/openspec_ui/screens/spec_detail.py
index c2a73aa..695e4db 100644
--- a/openspec_ui/screens/spec_detail.py
+++ b/openspec_ui/screens/spec_detail.py
@@ -29,7 +29,7 @@ def __init__(self, spec_name: str) -> None:
self.spec_name = spec_name
def compose(self):
- yield Header(show_clock=False)
+ yield Header(show_clock=False, icon="")
yield Static("", id="title")
yield VerticalScroll(id="spec-body")
yield StatusBar()