{"id":35145385,"url":"https://github.com/dmk/tui-dispatch","last_synced_at":"2026-04-26T13:02:39.409Z","repository":{"id":330834576,"uuid":"1124053981","full_name":"dmk/tui-dispatch","owner":"dmk","description":null,"archived":false,"fork":false,"pushed_at":"2026-04-25T17:48:40.000Z","size":1411,"stargazers_count":10,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-25T19:23:36.403Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dmk.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-12-28T08:15:05.000Z","updated_at":"2026-04-25T17:48:45.000Z","dependencies_parsed_at":"2025-12-31T06:00:44.752Z","dependency_job_id":null,"html_url":"https://github.com/dmk/tui-dispatch","commit_stats":null,"previous_names":["dmk/tui-dispatch"],"tags_count":18,"template":false,"template_full_name":null,"purl":"pkg:github/dmk/tui-dispatch","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dmk%2Ftui-dispatch","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dmk%2Ftui-dispatch/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dmk%2Ftui-dispatch/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dmk%2Ftui-dispatch/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dmk","download_url":"https://codeload.github.com/dmk/tui-dispatch/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dmk%2Ftui-dispatch/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32297904,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-26T09:34:17.070Z","status":"ssl_error","status_checked_at":"2026-04-26T09:34:00.993Z","response_time":129,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-12-28T13:45:46.489Z","updated_at":"2026-04-26T13:02:39.404Z","avatar_url":"https://github.com/dmk.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# tui-dispatch\n\nCentralized state management for Rust TUI apps (ratatui + crossterm). Redux/Elm patterns: actions describe events, reducers mutate state, UI renders from state.\n\n## Quick Start\n\n```toml\n[dependencies]\ntui-dispatch = \"0.7.0\"\nratatui = \"0.29\"\ncrossterm = \"0.28\"\n```\n\nMinimal counter:\n\n```rust\nuse std::io;\nuse crossterm::event::{self, Event, KeyCode};\nuse crossterm::terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen};\nuse crossterm::execute;\nuse ratatui::{backend::CrosstermBackend, widgets::Paragraph, Terminal};\nuse tui_dispatch::prelude::*;\n\n#[derive(Default)]\nstruct State { count: i32 }\n\n#[derive(Action, Clone, Debug)]\nenum Action { Inc, Dec, Quit }\n\nfn reducer(state: \u0026mut State, action: Action) -\u003e ReducerResult {\n    match action {\n        Action::Inc =\u003e { state.count += 1; ReducerResult::changed() }\n        Action::Dec =\u003e { state.count -= 1; ReducerResult::changed() }\n        Action::Quit =\u003e ReducerResult::unchanged(),\n    }\n}\n\nfn main() -\u003e io::Result\u003c()\u003e {\n    enable_raw_mode()?;\n    execute!(io::stdout(), EnterAlternateScreen)?;\n    let mut terminal = Terminal::new(CrosstermBackend::new(io::stdout()))?;\n    let mut store = Store::new(State::default(), reducer);\n\n    loop {\n        terminal.draw(|f| {\n            f.render_widget(\n                Paragraph::new(format!(\"count = {}  (k/j, q)\", store.state().count)),\n                f.area(),\n            );\n        })?;\n\n        if let Event::Key(key) = event::read()? {\n            let action = match key.code {\n                KeyCode::Char('k') | KeyCode::Up =\u003e Action::Inc,\n                KeyCode::Char('j') | KeyCode::Down =\u003e Action::Dec,\n                KeyCode::Char('q') | KeyCode::Esc =\u003e Action::Quit,\n                _ =\u003e continue,\n            };\n            if matches!(\u0026action, Action::Quit) { break; }\n            store.dispatch(action);\n        }\n    }\n\n    disable_raw_mode()?;\n    execute!(terminal.backend_mut(), LeaveAlternateScreen)?;\n    Ok(())\n}\n```\n\nThat's the core: State, Action, reducer, Store.\n\n## Add What You Need\n\ntui-dispatch is layered. Start with the core, add extensions when needed.\nIf you want the bare minimum, use `tui-dispatch-core`; `tui-dispatch` is a batteries-included facade.\n\n| Extension | When to use |\n|-----------|-------------|\n| **Effects** | Async operations (HTTP, file I/O) |\n| **EventBus** | Multiple focusable components |\n| **TaskManager** | Task cancellation, debouncing |\n| **Subscriptions** | Timers, streams |\n| **Debug overlay** | State/action inspection (F12) |\n\n## Async and Side Effects\n\nWhen you need async work (HTTP, file IO, timers), use the same store/runtime\nmodel with an effect type:\n\n- Reducer returns `ReducerResult\u003cEffect\u003e`\n- Reducer emits `Effect` values (data), and an effect handler executes them\n- Async completion sends a normal action back into the runtime (often named `Did*`)\n\nEnable helpers:\n\n- `features = [\"tasks\"]` for cancellation + debouncing via `TaskManager`\n- `features = [\"subscriptions\"]` for continuous sources (interval ticks, streams)\n\nSee `docs/src/content/docs/patterns/async.md` and the `github-lookup-example` app.\n\n## Examples\n\n```bash\ncargo run -p counter-example\ncargo run -p github-lookup-example\ncargo run -p log-viewer-example --bin log-viewer -- --help\ncargo run -p md-preview-example --bin mdpreview -- README.md --debug\ncargo run -p minesweeper-example\n```\n\nFor more complete apps, see [dmk/tui-stuff](https://github.com/dmk/tui-stuff).\n\n## Documentation\n\n- Docs (Starlight): `docs/` (run `make docs-serve`)\n- EventBus guide: `docs/src/content/docs/patterns/event-bus.md`\n- API docs: https://docs.rs/tui-dispatch\n\n## Crates\n\n- `tui-dispatch`: re-exports + prelude\n- `tui-dispatch-core`: store/runtime/tasks/subscriptions/testing primitives\n- `tui-dispatch-macros`: derives (`Action`, `DebugState`, `FeatureFlags`, ...)\n- `tui-dispatch-components`: reusable components (SelectList, TextInput, TreeView, ...)\n- `tui-dispatch-debug`: debug overlay + headless debug sessions\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdmk%2Ftui-dispatch","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdmk%2Ftui-dispatch","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdmk%2Ftui-dispatch/lists"}