diff --git a/.gitignore b/.gitignore
index 42e4ac07..6cf2fe6d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -16,5 +16,7 @@ Cargo.lock
# Ignore GIF files in the tapes directory
tapes/*.gif
-# Ignore test artifacts emitted by zsh-render-parity integration tests
-zsh-render-parity/.artifacts/
+# Keep tape fixtures under version control
+!tapes/*.csv
+!tapes/*.json
+!tapes/*.yaml
diff --git a/README.md b/README.md
index f35e1551..0d382705 100644
--- a/README.md
+++ b/README.md
@@ -16,18 +16,29 @@ promkit = { version = "0.14.0", features = ["runtime", "texteditor"] }
## Features
-- Cross-platform support for both UNIX and Windows utilizing [crossterm](https://github.com/crossterm-rs/crossterm)
-- Modularized architecture
- - [promkit-core](./promkit-core/)
- - Core functionality for terminal rendering and keyed grapheme chunk management
- - [promkit-widgets](./promkit-widgets/)
- - Various UI components (text, listbox, tree, etc.)
- - [promkit](./promkit/)
- - Optional prompt runtime, capabilities, and widget facade
- - [promkit-derive](./promkit-derive/)
- - A Derive macro that simplifies interactive form input
-- Composable examples for readline, selection, structured data, forms, and
- asynchronous workflows
+- **Application-owned composition** — Implement the
+ [`Prompt`](./promkit/src/runtime.rs) lifecycle and combine only the widget
+ states an application needs. Key bindings, focus, validation, background
+ work, and other event policy remain in the application instead of
+ framework-owned presets.
+- **Optional runtime and terminal lifecycle** — Use the asynchronous prompt
+ runtime when useful, and independently manage raw mode, the alternate screen,
+ cursor visibility, and mouse capture with
+ [`TerminalSession`](./promkit/src/terminal_session.rs).
+- **Viewport-aware rendering** — [`promkit-core`](./promkit-core/) handles
+ wrapping or truncation, vertical pane allocation, cursor scrolling, clipping,
+ resizing, and screen-to-widget hit testing.
+- **Reusable widget states** — [`promkit-widgets`](./promkit-widgets/) provides
+ text editing and display, list and checkbox selection, prefix search,
+ spinners and status output, trees, JSON and YAML documents, and CSV tables.
+- **Efficient large-content projection** — JSON, YAML, and table widgets can
+ project only the visible terminal viewport instead of rebuilding all content
+ on every redraw.
+- **Feature-gated modules** — No Cargo features are enabled by default. Choose
+ the runtime, terminal session, capabilities, and individual widgets needed by
+ the application.
+- **Cross-platform terminal support** — UNIX and Windows are supported through
+ [crossterm](https://github.com/crossterm-rs/crossterm).
## Concept
@@ -46,8 +57,26 @@ See [here](./Concept.md).
terminal applications. The examples own their event policies and demonstrate
how applications can compose the widgets they need.
-Show you commands, code, and actual demo screens for examples
-that can be executed immediately below.
+### Table of contents
+
+| Example | Description |
+| --- | --- |
+| [Readline](#readline) | Single-line editing with history and completion |
+| [Confirm](#confirm) | Validated yes-or-no input |
+| [Password](#password) | Masked input with validation |
+| [Form](#form) | Multiple editable fields in one prompt |
+| [Listbox](#listbox) | Keyboard-driven single selection |
+| [QuerySelector](#queryselector) | Prefix search over selectable candidates |
+| [Checkbox](#checkbox) | Keyboard-driven multiple selection |
+| [Tree](#tree) | Navigation and folding for hierarchical data |
+| [JSON](#json) | Line-numbered navigation and folding for JSON |
+| [YAML](#yaml) | Line-numbered navigation and folding for YAML |
+| [CSV](#csv) | Vertical and horizontal navigation for tabular data |
+| [Text](#text) | Scrollable styled text |
+| [Async Task with Spinner](#async-task-with-spinner) | Background work with live input and progress feedback |
+| [Multiline REPL](#multiline-repl) | Multiline editing in an interactive loop |
+
+Each section includes a command, source link, and recorded demo.
### Readline
@@ -130,7 +159,7 @@ cargo run --bin listbox
Command
```bash
-cargo run --bin query_selector
+cargo run --bin query-selector
```
@@ -194,6 +223,20 @@ cargo run --bin yaml ${PATH_TO_YAML_FILE}
+### CSV
+
+
+Command
+
+```bash
+cargo run --bin csv ${PATH_TO_CSV_FILE}
+```
+
+
+[Code](./examples/csv/src/csv.rs)
+
+
+
### Text
diff --git a/promkit-widgets/README.md b/promkit-widgets/README.md
index 6c5c526d..63f9377d 100644
--- a/promkit-widgets/README.md
+++ b/promkit-widgets/README.md
@@ -1,101 +1,56 @@
# promkit-widgets
-Reusable widget states for [promkit](https://github.com/ynqa/promkit).
+Reusable widget states for building interactive terminal applications with
+[promkit](https://github.com/ynqa/promkit).
-Each state implements `promkit_core::Widget` and projects its current state into
-styled graphemes and layout hints. Widgets do not own event loops or key
-bindings: event handling belongs to application `Prompt` implementations,
-while terminal layout and drawing belong to `promkit-core`.
-See [Concept.md](../Concept.md) for the full responsibility boundaries.
+`promkit-widgets` provides the state and view projection for common UI
+components. Each widget implements `promkit_core::Widget` and turns its current
+state into styled graphemes and layout hints for `promkit-core` to render.
-## Features
+## Getting started
-No widget is enabled by default.
-
-| Feature | Widget or capability |
-| --- | --- |
-| `checkbox` | Checkbox selection; enables `listbox` |
-| `json` | Navigable JSON document |
-| `yaml` | Navigable YAML document |
-| `listbox` | List selection |
-| `prefixsearch` | Prefix-matched candidate selection backed by a radix trie |
-| `spinner` | Asynchronous spinner; enables Tokio |
-| `status` | Status display; enables `text` |
-| `text` | Styled text |
-| `texteditor` | Editable text with history |
-| `tree` | Navigable structured tree |
-| `serde` | Serde support for widget configuration |
-| `all` | All features above |
-
-Enable only the widgets an application uses:
+Widgets are opt-in Cargo features; none are enabled by default. Applications
+using the `promkit` runtime can enable them through the main crate:
```toml
[dependencies]
-promkit-widgets = { version = "0.7", features = ["json", "yaml"] }
-```
-
-The crate re-exports `promkit-core` as `promkit_widgets::core`. JSON and YAML
-states provide viewport-bounded projection so callers do not need to materialize
-every visible row of a large document on each cursor movement.
-
-## Structured document loading
-
-JSON and YAML documents can be built directly from strings or readers without
-first materializing a `serde_json::Value` or `serde_yaml::Value` tree:
-
-```rust
-use std::{fs::File, io::BufReader};
-
-use promkit_widgets::{json, yaml};
-
-let json_document = json::Document::from_str(r#"{"name":"alice"}"#).unwrap();
-let yaml_file = File::open("input.yaml").unwrap();
-let yaml_document = yaml::Document::from_reader(BufReader::new(yaml_file)).unwrap();
+promkit = { version = "0.14.0", features = ["runtime", "texteditor"] }
```
-`Document::new` remains available when an application already has deserialized
-Serde values.
+The widget states can also be used directly:
-## Structured benchmark
-
-The Criterion benchmark covers file reading, deserialization, document
-construction, cursor movement, and viewport projection for the bundled JSON and
-YAML fixtures:
-
-```bash
-cargo bench -p promkit-widgets --bench structured --features json,yaml
+```toml
+[dependencies]
+promkit-widgets = { version = "0.7", features = ["json", "yaml"] }
```
-Criterion compares a run with its previous local result. Named baselines can be
-saved and compared across revisions with `--save-baseline NAME` and
-`--baseline NAME`. The benchmark currently reports regressions but does not
-enforce a CI failure threshold.
-
-Override either fixture when needed:
-
-```bash
-PROMKIT_STRUCTURED_JSON=/path/to/input.json \
-PROMKIT_STRUCTURED_YAML=/path/to/input.yaml \
-cargo bench -p promkit-widgets --bench structured --features json,yaml
-```
+`promkit` re-exports this crate as `promkit::widgets`, while
+`promkit-widgets` re-exports `promkit-core` as `promkit_widgets::core`.
-This benchmark exercises the `promkit-widgets` projection layer. It does not
-measure `promkit-core::Renderer`, terminal layout, or terminal I/O.
+## Widgets
-## Structured line numbers
+| Feature | Widget or capability |
+| --- | --- |
+| `checkbox` | Multiple-choice selection |
+| `listbox` | List selection |
+| `prefixsearch` | Prefix-matched candidate selection |
+| `json` | Navigable JSON documents |
+| `yaml` | Navigable YAML documents |
+| `tree` | Navigable tree structures |
+| `table` | Tabular CSV data |
+| `text` | Styled text |
+| `texteditor` | Editable text with history |
+| `spinner` | Asynchronous progress display |
+| `status` | Status display |
+| `serde` | Serde support for widget configuration |
+| `all` | All features above |
-The `json`, `yaml`, and `tree` widgets can display stable, one-based line
-numbers by enabling `show_line_numbers` in their `Config`. Numbers refer to the
-fully expanded structure, so collapsing a node leaves gaps for its hidden rows.
+## Responsibilities
-Applications configure line numbers directly on widget state:
+Widgets manage state and project it into renderable content. They intentionally
+do not own event loops or key bindings: application `Prompt` implementations
+define input and focus behavior, and `promkit-core` handles terminal layout and
+drawing.
-```rust
-let state = json::State {
- document,
- config: json::Config {
- show_line_numbers: true,
- ..Default::default()
- },
-};
-```
+See [Concept.md](../Concept.md) for the architecture and the repository
+[examples](../examples/) for complete compositions.
diff --git a/tapes/csv.csv b/tapes/csv.csv
new file mode 100644
index 00000000..4ba492c4
--- /dev/null
+++ b/tapes/csv.csv
@@ -0,0 +1,19 @@
+id,task,area,owner,status,progress,updated,notes
+001,Define widget API,Core,Alice,Done,100%,2026-07-03,Keep state separate from event policy
+002,Add table projection,Widgets,Bob,Done,100%,2026-07-05,Render only the visible viewport
+003,Parse CSV input,Widgets,Carol,Done,100%,2026-07-06,Support files and standard input
+004,Style table header,Examples,Diego,Done,100%,2026-07-08,Use a distinct header style
+005,Add row navigation,Examples,Erin,Done,100%,2026-07-09,Handle up and down arrow keys
+006,Add column scrolling,Examples,Farah,Done,100%,2026-07-10,Handle left and right arrow keys
+007,Support mouse wheel,Examples,Gabriel,Done,100%,2026-07-11,Map wheel events to table movement
+008,Measure parsing speed,Benchmarks,Hana,In progress,75%,2026-07-14,Compare representative CSV fixtures
+009,Measure projection speed,Benchmarks,Ivan,In progress,60%,2026-07-15,Track visible row rendering cost
+010,Test quoted fields,Tests,Julia,Done,100%,2026-07-16,Include commas and multiline values
+011,Test narrow terminals,Tests,Kai,In review,90%,2026-07-18,Verify horizontal clipping behavior
+012,Test empty input,Tests,Lina,Done,100%,2026-07-19,Keep empty documents safe to render
+013,Write CSV example,Examples,Min,Done,100%,2026-07-20,Show file and standard input usage
+014,Record demo tape,Docs,Nora,In progress,50%,2026-07-22,Demonstrate both scroll directions
+015,Review keyboard help,Docs,Omar,Planned,10%,2026-07-24,Document Enter and Ctrl-C behavior
+016,Build release artifacts,Release,Priya,Planned,0%,2026-07-25,Publish all workspace crates together
+017,Run compatibility checks,Release,Quinn,Planned,0%,2026-07-26,Test supported terminal environments
+018,Publish release notes,Release,Rui,Planned,0%,2026-07-28,Summarize the user-facing changes
diff --git a/tapes/csv.tape b/tapes/csv.tape
new file mode 100644
index 00000000..b051aa1a
--- /dev/null
+++ b/tapes/csv.tape
@@ -0,0 +1,16 @@
+Output tapes/csv.gif
+
+Require cargo
+
+Set Shell "bash"
+Set Theme "Dracula"
+Set FontSize 32
+Set Width 1200
+Set Height 600
+
+Type@50ms "cargo run -q --bin csv tapes/csv.csv" Enter Sleep 2s
+Down@80ms 14 Sleep 1s
+Right@50ms 36 Sleep 2s
+Up@200ms 4 Sleep 1s
+Left@50ms 12 Sleep 1s
+Enter Sleep 2s
diff --git a/tapes/json.tape b/tapes/json.tape
index 85d16636..5d03de1e 100644
--- a/tapes/json.tape
+++ b/tapes/json.tape
@@ -8,9 +8,10 @@ Set FontSize 32
Set Width 1200
Set Height 600
-Type@50ms "cargo run -q --bin json test.json" Enter Sleep 2s
-Down@300ms 2 Sleep 1s
+Type@50ms "cargo run -q --bin json tapes/kubernetes.json" Enter Sleep 2s
+Down@300ms 4 Sleep 1s
Space Sleep 1s
Down@300ms 1 Sleep 1s
Space Sleep 1s
-Up@300ms 2 Sleep 1s
+Down@200ms 2 Sleep 1s
+Enter Sleep 2s
diff --git a/tapes/kubernetes.json b/tapes/kubernetes.json
new file mode 100644
index 00000000..fb62de2a
--- /dev/null
+++ b/tapes/kubernetes.json
@@ -0,0 +1,61 @@
+[
+ {
+ "apiVersion": "v1",
+ "kind": "Service",
+ "metadata": {
+ "name": "my-nginx-svc",
+ "labels": {
+ "app": "nginx"
+ }
+ },
+ "spec": {
+ "type": "LoadBalancer",
+ "ports": [
+ {
+ "port": 80
+ }
+ ],
+ "selector": {
+ "app": "nginx"
+ }
+ }
+ },
+ {
+ "apiVersion": "apps/v1",
+ "kind": "Deployment",
+ "metadata": {
+ "name": "my-nginx",
+ "labels": {
+ "app": "nginx"
+ }
+ },
+ "spec": {
+ "replicas": 3,
+ "selector": {
+ "matchLabels": {
+ "app": "nginx"
+ }
+ },
+ "template": {
+ "metadata": {
+ "labels": {
+ "app": "nginx"
+ }
+ },
+ "spec": {
+ "containers": [
+ {
+ "name": "nginx",
+ "image": "nginx:1.14.2",
+ "ports": [
+ {
+ "containerPort": 80
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }
+ }
+]
diff --git a/tapes/kubernetes.yaml b/tapes/kubernetes.yaml
new file mode 100644
index 00000000..38f8a049
--- /dev/null
+++ b/tapes/kubernetes.yaml
@@ -0,0 +1,34 @@
+apiVersion: v1
+kind: Service
+metadata:
+ name: my-nginx-svc
+ labels:
+ app: nginx
+spec:
+ type: LoadBalancer
+ ports:
+ - port: 80
+ selector:
+ app: nginx
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: my-nginx
+ labels:
+ app: nginx
+spec:
+ replicas: 3
+ selector:
+ matchLabels:
+ app: nginx
+ template:
+ metadata:
+ labels:
+ app: nginx
+ spec:
+ containers:
+ - name: nginx
+ image: nginx:1.14.2
+ ports:
+ - containerPort: 80
diff --git a/tapes/yaml.tape b/tapes/yaml.tape
index b444afa5..19f85894 100644
--- a/tapes/yaml.tape
+++ b/tapes/yaml.tape
@@ -8,9 +8,10 @@ Set FontSize 32
Set Width 1200
Set Height 600
-Type@50ms "cargo run -q --bin yaml small.yaml" Enter Sleep 2s
+Type@50ms "cargo run -q --bin yaml tapes/kubernetes.yaml" Enter Sleep 2s
Down@300ms 2 Sleep 1s
Space Sleep 1s
Down@300ms 1 Sleep 1s
Space Sleep 1s
-Up@300ms 2 Sleep 1s
+Down@200ms 2 Sleep 1s
+Enter Sleep 2s