Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ All notable changes to TextUI are documented here.

### Fixed

- Runtime table sorting preserves large-integer precision without float conversion, treats numeric NaN deterministically as text, and keeps table state unchanged when a header comparison fails. Refresh continues to retain the active sort and declared row identity.
- Project timers release finished async and threaded workers, including failed or cancelled work. Repeated close clears timer and worker ownership without suppressing native worker errors or changing overlap prevention.
- Actions and commands now share lifecycle ownership: synchronous completion releases loading, shared targets stay loading until all work finishes, and older failures cannot overwrite newer target state. Shutdown cancels untargeted work and queued command wrappers as well as targeted work, and closed documents reject new invocations.
- Focus cues now blend over native and author backgrounds instead of replacing them, so variant buttons retain their colors. Compact inputs, selects, and text areas also preserve their backgrounds; tint strengths remain 15% by default and 25% for compact editable controls.
- Clicking a data-table column heading now sorts once per click. The table's click and mouse-move handlers also called `super()`, which Textual had already run, so every heading click was delivered twice and the ascending/descending toggle always ended on descending. A column-resize press no longer sorts either, and no longer swallows the next heading click when the drag is released.
- Controller resize hooks now receive terminal dimensions even when the active screen has padding or a border.
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ TextUI 0.6 turns strict XML documents into native [Textual](https://textual.text

This is a breaking pre-1.0 reboot. See the [migration guide](docs/migration.md) for changes from 0.1, the [changelog](CHANGELOG.md) for release history, and the [implemented design](docs/superpowers/specs/2026-09-17-textui-core-design.md) for the complete contract.

The [roadmap](docs/roadmap.md) records current priorities, completion gates, and the reliability implementation plan. Historical library comparisons remain in the [extension triage](docs/2026-09-19-extension-triage.md).

## Install and run

Python `>=3.11,<4` is required; the release matrix covers 3.11, 3.12, and 3.14. Core dependencies are Textual `>=8.2.8,<9` and lxml `>=6.1.3,<7`. Core installation does not require Pillow or textual-imageview; image components are a future extension.
Expand Down Expand Up @@ -140,6 +142,8 @@ Host(DocumentLoader().from_string("<ui><label>Hello</label></ui>")).run()

`TextUI` supplies these built-in handlers for convenience. Add decorators for any other component messages used by your document. `get_by_id` returns only widgets with declared document IDs and requires them to be mounted. Use native `app.query()` / `app.query_one()` for general selectors. A `Document` can be reused in independent Apps; each binding constructs fresh widgets. There is one binding per App and a single composition attempt per binding. Recomposition, remounting, document replacement, and transparent attachment to a running App are unsupported.

Normal hosts must call `self.document.close()` when shutdown begins and on unmount to cancel owned actions and commands; the convenience Apps do this automatically. Closing is idempotent and ignores later queued document events. See the [runtime lifecycle guide](docs/project-runtime.md) for shared loading targets, supersession, and cooperative cancellation.

## Add components and events

`ComponentRegistry()` starts empty. To extend the built-ins, use `default_component_registry` from `textui.widgets.builtin_widgets`, then register additional immutable `ComponentSpec` definitions. Construct `DocumentLoader(registry)` after registration; the loader snapshots the registry.
Expand Down
8 changes: 6 additions & 2 deletions docs/2026-09-19-extension-triage.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
# TextUI Extension Triage and Delivery Order

Date: 2026-09-19
Updated: 2026-09-20
Updated: 2026-09-30

Current delivery priorities and completion gates are maintained in the [TextUI roadmap](roadmap.md). This document preserves the earlier ecosystem comparisons; external compatibility and license evidence below dates from 2026-09-20 and must be rechecked before adoption.

## Direction

Expand Down Expand Up @@ -34,9 +36,11 @@ The historic implementation plans remain as engineering records. Their unchecked

## Remaining roadmap

The current roadmap places runtime reliability and authoring tools before these extensions. Native `<range>` has shipped; the table below retains the extension grouping, with its completed control removed from the remaining work.

| Order | Deliverable | Scope and rationale |
| --- | --- | --- |
| 1 | Focused controls and dashboard widgets | Add native `<range>`, `<date-picker>`, and `<sparkline>` adapters. These have clear markup contracts and fill common form/dashboard gaps without a reactive template language. |
| 1 | Focused controls and dashboard widgets | Add a native `<sparkline>` adapter and evaluate `<date-picker>`. These have clear markup contracts and fill common form/dashboard gaps without a reactive template language. |
| 2 | Selection and state presentation | Add a `selection-list` adapter, autocomplete/combobox behavior, and reusable loading, empty, and error presentation patterns. |
| 3 | Command surfaces | Build menus or a command palette on the existing `@command` metadata when an application needs them. Dynamic enabled predicates and user-configurable shortcuts remain separate design work. |
| 4 | Reactive navigation | Define observation, refresh, routing, and history only after a project demonstrates the need beyond the existing content switcher, runtime lists, and tables. |
Expand Down
2 changes: 2 additions & 0 deletions docs/controls.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ Set `striped="true"` for alternating row backgrounds; style native `datatable--o

Runtime rows sort by their original values, so numeric fields remain numeric; seeded and manually added literal cells sort by their displayed values. Call `set_rows(records)` only after mounting. Every record must be a mapping with a non-empty string in the declared `row-key` field and every declared column key. TextUI validates the full batch before changing rows, renders `None` as an empty literal cell, and retains extra fields without adding columns. `get_record(row_key)` returns the read-only source record for the current batch and raises `KeyError` when absent. A refresh retains the active sort and cursor when its row key remains; otherwise the native cursor returns to the first cell. The [data example](../examples/data/app.ui) shows this runtime pattern beside seeded table and tree updates.

Ascending runtime sorts group booleans first, then `numbers.Real` values in numeric order, then other values as case-folded text. Real values are compared directly, preserving large-integer precision; infinities remain numeric. Numeric NaN sorts as text `"nan"`, and `None` sorts as `"None"` despite its empty displayed cell. `Decimal` and other non-Real objects retain textual ordering. Descending reverses category and value order; equal keys remain stable in both directions. Validation or comparison failure leaves existing rows, cursor, and active sort unchanged.

## Log selection

`<log on-selection-ended="copy_selection"/>` reports a nonempty mouse selection after release, even outside the log. Actions receive `context.event.log`, `.text`, and native `.selection` coordinates. Appending or redrawing content does not repeat the event. Copying is opt-in: a linked async action can call `await window.copy(context.event.text)`. See the [clipboard runtime guide](project-runtime.md#selection-and-clipboard) for native backend and terminal transport behavior.
Expand Down
6 changes: 5 additions & 1 deletion docs/project-runtime.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,9 @@ async def refresh():

An `@action` function is exposed under its Python name and may take zero arguments or one `ActionContext`; undecorated functions are private to the script. `on-pressed="save"` refers to that exact name. Duplicate action names, including collisions with host-supplied actions, are errors.

Async data actions can opt into target lifecycle state. Set `target` to a declared widget ID to add `-loading` while work runs and `-error` with a readable `textui_error` value when it fails. Set `supersede=True` to cancel an earlier invocation of the same action and target; shutdown also cancels active lifecycle work. `context.target` is the mounted target and `context.cancelled` reports cancellation.
Actions and commands can opt into target lifecycle state, for synchronous or asynchronous work. Set `target` to a declared widget ID to add `-loading` while work runs and `-error` with a readable `textui_error` value when it fails. A shared target stays loading until every active invocation finishes; synchronous completion also releases its ownership. Starting new work clears previous errors, and only the newest invocation may publish a target error. An older failure still reaches the normal source-located error handler.

Set `supersede=True` to cancel earlier invocations of the same operation and target, including when the replacement completes synchronously. Other operations sharing the target continue. `context.target` is the mounted target and `context.cancelled` reports cancellation, including for untargeted actions. Cancellation is cooperative: callbacks that suppress it and threads may still perform their own side effects.

```python
@action(target="results", supersede=True)
Expand Down Expand Up @@ -118,6 +120,8 @@ Render it with a self-labeling control:

Optional `on_setup`, `on_ready`, and `on_close` functions take no arguments and may be synchronous or asynchronous. Setup runs before component validation and binding; ready runs after mount; close runs once on shutdown and after a failed setup. `window.document` is available after binding, while ID lookup requires mounted widgets.

Both convenience Apps close their document when exit begins, cancelling all owned asynchronous actions and commands, including untargeted work and queued shortcut wrappers. Repeated `document.close()` calls are harmless. A closed document ignores queued `dispatch()` messages, rejects direct `invoke_command()` calls with `DocumentStateError`, and makes `start_command()` a no-op. Normal Textual hosts must call their bound document's `close()` when shutdown begins and on unmount; it requests cancellation without waiting for user callbacks.

Optional `on_resize(width, height)` runs after Textual delivers a terminal resize to the active screen and refreshes its layout. Width and height are terminal cells; mounted widget sizes can be read inside the hook. It accepts a synchronous or asynchronous function, runs only while the project is ready, and stops when exit begins. TextUI drops stale and duplicate sizes; Textual may also coalesce rapid terminal resizes, so paint from the dimensions supplied rather than counting raw resize events:

```python
Expand Down
77 changes: 77 additions & 0 deletions docs/roadmap.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# TextUI Roadmap

Updated: 2026-09-30. Status: planned. Review baseline: `f8e472c` (TextUI 0.6.0).

This is the current delivery order. The [extension triage](2026-09-19-extension-triage.md) retains the historical library comparisons and adoption rationale. Its external compatibility claims are dated evidence and must be checked again before adding a dependency.

## Direction

Keep the HTML-like authoring model: strict XML describes structure, native TCSS describes appearance, and linked Python supplies behavior. Preserve explicit registry extensions, normal Textual App integration, contextual errors, and the image-free core dependency boundary.

Prioritize reliable application lifecycles and approachable authoring before expanding the widget catalog. Each phase has a completion gate; the numbers express dependencies, not calendar promises.

## Current baseline

Main includes reusable local components and slots; linked scripts, includes and styles; actions, commands and timers; context and resize hooks; navigation, splits and modals; runtime tables/lists; compact/border presets; gradients; native range controls; autofocus on reveal; selectable logs and asynchronous clipboard copying.

The review ran 389 committed headless tests successfully, but separate behavioral probes exposed five defects. A green baseline does not waive the regression tests below.

## Phase 0 — Reliability

| Unit | Deliverable | Completion gate |
| --- | --- | --- |
| 0A | Shared action/command ownership and lifecycle cleanup | Untargeted tasks are cancelled on close; sync completion/failure cleans target state; loading remains while any action owns the target; supersession cannot publish stale target state. |
| 0B | Timer worker cleanup | Completed async/threaded workers are released; repeated close stops timers and clears ownership; overlap skipping and native error reporting remain intact. |
| 0C | Exact table sorting | Adjacent large integers and arbitrarily large integers sort correctly; mixed numeric/text values, missing values, NaN and infinity have documented deterministic behavior; refresh retains sort and row identity. |

Implement 0A first because it defines shared ownership semantics. 0B and 0C are independent afterward and may use separate worktrees. Integrate them into one reviewed reliability PR with separate logical commits, avoiding competing edits to shared documentation.

Detailed artifacts: [reliability design](superpowers/specs/2026-09-30-runtime-reliability-design.md) and [implementation plan](superpowers/plans/2026-09-30-runtime-reliability.md).

## Phase 1 — Developer experience

| Unit | Scope | Completion gate |
| --- | --- | --- |
| 1A | Dependable setup and showcase launch | Document one isolated environment path and a module-based launch; verify from a clean checkout and another working directory. A small launcher may wrap the existing Poetry workflow. |
| 1B | Project validation and CLI diagnostics | Add a headless `textui check` workflow, concise expected-error output and stable exit codes; missing files, XML, TCSS and action errors retain their source location. Keep tracebacks available for unexpected failures/debugging. |
| 1C | Registry-derived authoring reference | Generate tags, attributes, defaults, events and examples from registry metadata; provide machine-readable completion data. Compound structural rules remain explicit, and custom components remain supported. |
| 1D | Documentation consolidation | Separate current reference from historical design records; reconcile migration/examples/testing guidance and shipped roadmap items; make install, launch and extension paths easy to find. |

1A and 1D can proceed alongside Phase 0. Design 1B's execution boundary first: full validation may need trusted controller setup to register components. The command must explain which hooks execute and must not claim static or sandboxed validation. 1C depends on that boundary and on an explicit metadata format; a complete XML schema is a later step if it can express compound rules accurately.

## Phase 2 — Compatibility and maintenance

- Run focused behavioral probes against the lowest and latest allowed Textual versions, supplementing the locked full suite and existing clean-wheel smoke test. Cover styling, gradients, table rendering/resizing, selection, resize hooks, modals and shutdown.
- Add focused macOS/Windows CI for platform-specific code. Keep mocked clipboard tests; exercise real native tools only in disposable CI environments. Terminal OSC 52 acceptance remains a separate manual check.
- Expand clean-wheel checks to newer runtime facilities and verify that optional imaging packages remain absent.
- Share built-in message forwarding between convenience Apps while preserving exact-type dispatch and explicit custom-event forwarding in normal hosts.
- Improve public typing and editor support for the injected `window`; introduce type checks incrementally around supported public interfaces.

Gate: reviewed compatibility coverage, clean installed-wheel checks, documented Textual internal touchpoints, and no regression in host extension behavior. Preserve the Poetry lock policy; do not hand-edit dependency resolutions.

## Phase 3 — Focused application capabilities

| Order | Capability | Boundary and acceptance |
| --- | --- | --- |
| 3A | `<sparkline>` | Small native adapter with validated numeric data and a controller update API; include a live showcase trend and empty-data coverage. |
| 3B | `<selection-list>` | Explicit selected values, stable item identity, disabled choices, runtime replacement and one selection-changed event; keyboard and pointer tests. |
| 3C | Command palette | Reuse existing command metadata and invocation ownership; searchable labels/descriptions, keyboard access and correct disabled-command behavior. |
| 3D | Loading, empty and error presentation | Reusable component patterns built on proven target lifecycle semantics; include retry examples without automatic network/retry policy. |

Each capability gets a small design and implementation plan before code. Define its runtime update and event semantics, not just its markup spelling. Keep the showcase and reference synchronized with each addition.

## Phase 4 — Extensions and richer authoring

- Optional plotting: axes, legends and multiple series in a separate adapter/package after sparkline establishes the basic dashboard contract.
- Optional images: revisit the preferred renderer's compatibility and license before a separate image distribution; retain Python 3.11 and the image-free core.
- Date picking and autocomplete/combobox: decide ISO value, provider, identity and refresh behavior before adopting a library.
- Component-local styling and explicit state updates: design scope, cascade, ownership and instance isolation against a real application example.
- Routing/history and dynamic component properties: pursue when a concrete application exceeds the current content-switcher and controller APIs.

Embedded Python, expression evaluation, automatic data binding, hot reload/recomposition, terminal emulation and an untrusted-document mode remain separate architectural work, with no delivery commitment here.

## Delivery rules

Use an isolated worktree for implementation and preserve root-checkout edits. Write symptom-driven regressions before fixes. Update user-facing docs/examples and `CHANGELOG.md` when behavior changes. Run focused checks, the full committed suite, relevant visual checks, Pyflakes, lock validation and builds; use Python 3.11/3.12/3.14 for releases/dependency changes. Obtain independent review, inspect GitHub feedback and resolve verified findings before merging. Read the historical review threads again after merge if feedback arrives late.

Move a unit to completed only when its gate is met. Record the merged PR and validation evidence here; unchecked historical plan lists do not override this current status.
Loading
Loading