Files
wrkflw/Cargo.toml
Gokul f650f5533c feat(ui): overhaul TUI and CLI output (#85)
* feat(ui): overhaul TUI and CLI output for professional look

The terminal UI looked like it was thrown together during a
hackathon. Colors were hardcoded across 11 files with zero
consistency — Color::Green here, Color::Cyan there, emoji like 
and  mixed with Unicode symbols like ○ and ⟳, tab dividers
using a raw pipe character. This is not great.

The core problem was the complete absence of any centralized
theming. Every view file was its own little island of ad-hoc
styling decisions, and the non-TUI CLI output had *zero* color
support despite the colored crate sitting right there in the
workspace Cargo.toml, imported by absolutely nobody.

So let's fix all of that:

- Add theme.rs as single source of truth for colors, styles,
  symbols, and block/badge helpers. Every view now imports from
  here instead of hardcoding Color:: literals everywhere.
- Replace the inconsistent emoji zoo (⏭⟳) with proper
  single-cell-width Unicode symbols (✔✖⊘◉○) that don't render
  at double width and break column alignment.
- Upgrade ratatui 0.23 -> 0.28 and crossterm 0.26 -> 0.28,
  migrating all the breaking API changes (Frame generics gone,
  Table::new signature, f.size() -> f.area()).
- Add cli_style.rs and wire colored output through all CLI
  paths — validate, execute, and list commands now have proper
  colored output with tree-style rendering.
- Add braille spinner animation for running workflow states.
- Rip out the redundant instruction headers from workflows and
  logs tabs (the status bar already shows the same hints),
  reclaiming vertical space.
- Clean up the help tab from emoji section headers to properly
  styled underlined headers with fixed-width key columns.

Net result: -418 lines. The UI got *better* and *smaller*.

* fix(ui): replace fragile string-matching in status toast with typed severity

The status bar was detecting success/error toasts by doing
substring matching on the message text — checking for "success",
"Success", and the ✔ symbol. It turns out that any message
containing the word "success" anywhere (even in an error context)
would render with a green background. This is not great.

Add a StatusSeverity enum and a set_success_message() helper so
callers declare intent explicitly instead of hoping their message
text passes a regex-by-vibes check. Also replace the last 
emoji in set_status_message calls and swap the double-width 🔒
(U+1F512) LOCK symbol for single-width ⚿ (U+26BF), because the
whole point of the theme overhaul was to kill double-width emoji.

* fix(ui): align log detection with new Unicode symbols and clean up API

The previous commits replaced all user-facing emoji with single-cell
Unicode symbols, but *forgot* to update the two places that actually
match against those symbols: LogFilterLevel::matches() and
log_processor's type detection.

So we had the delightful situation where cli_style outputs ✖ on
errors, but the log filter is still looking for . The text-based
fallbacks ("Error", "WARN", etc.) masked the breakage, but the
symbol branches were effectively dead code.

While at it, rename set_status_message() to set_error_message()
because a generic "set status" method that silently defaults to
Error severity is a trap waiting to happen. Both existing call
sites are genuine error paths, so the rename is accurate.

Also run cargo fmt across the board -- the previous commits left a
fair amount of unformatted code behind.

* refactor: centralize Unicode symbols into wrkflw-logging

The previous commit introduced a proper theme module with Unicode
symbols for the TUI, but then three different places independently
hardcoded the *same* symbols: theme::symbols, cli_style.rs, and
models/mod.rs. Meanwhile, the executor, runtime, and logging crates
were still happily emitting double-width emoji into log output.

So we had the worst of both worlds: the UI layer had to pattern-
match against *both* old emoji and new Unicode to detect log levels,
and the "single source of truth" for symbols existed in triplicate.
This is not great.

Extract a shared `wrkflw_logging::symbols` module that every crate
already depends on. theme.rs now re-exports it instead of defining
its own copy. cli_style.rs and LogFilterLevel::matches() import
from it. All emoji in executor, runtime, and logging are replaced.
The old fallback in main.rs error filtering is gone because the
executor no longer emits the old symbols.

While at it, expand StatusSeverity with Info and Warning variants
so "no matches found" shows as a yellow warning toast instead of
an angry red error. Because not finding a search result is not an
error, it's just disappointing.

* fix(ui): stop hammering Docker on every render frame

The status bar was calling docker::is_available() on *every single
render tick* — that's 4 times per second, each spawning an OS
thread, a docker subprocess, and a throwaway tokio runtime. The
check has a 3-second timeout, we're calling it every 250ms. This
is not great.

It turns out that under this kind of self-inflicted load, the
check frequently times out and returns false. So Docker shows as
"Unavailable" even when it's sitting right there, perfectly happy.
The double-nested with_stderr_to_null wrapping wasn't helping
either.

While at it, the logging crate was also cheerfully eprintln!()-ing
warnings while the TUI had the terminal in raw mode, which is how
you get "Docker is not available" splattered across the middle of
your carefully rendered UI. Confusion ensues.

The fix: cache the availability result in App state, seed it from
the existing startup check, refresh it every 30 seconds in tick(),
and add a quiet_mode flag to the logging crate so it stops writing
to stderr while the TUI owns the terminal. Messages still go into
the log buffer for the Logs tab — they just don't corrupt the
display anymore.
2026-04-03 11:53:30 +05:30

76 lines
2.3 KiB
TOML

[workspace]
members = ["crates/*"]
resolver = "2"
[workspace.package]
version = "0.7.3"
edition = "2021"
description = "A GitHub Actions workflow validator and executor"
documentation = "https://github.com/bahdotsh/wrkflw"
homepage = "https://github.com/bahdotsh/wrkflw"
repository = "https://github.com/bahdotsh/wrkflw"
keywords = ["workflows", "github", "local"]
categories = ["command-line-utilities"]
license = "MIT"
[workspace.dependencies]
# Internal crate dependencies
wrkflw-models = { path = "crates/models", version = "0.7.3" }
wrkflw-evaluator = { path = "crates/evaluator", version = "0.7.3" }
wrkflw-executor = { path = "crates/executor", version = "0.7.3" }
wrkflw-github = { path = "crates/github", version = "0.7.3" }
wrkflw-gitlab = { path = "crates/gitlab", version = "0.7.3" }
wrkflw-logging = { path = "crates/logging", version = "0.7.3" }
wrkflw-matrix = { path = "crates/matrix", version = "0.7.3" }
wrkflw-parser = { path = "crates/parser", version = "0.7.3" }
wrkflw-runtime = { path = "crates/runtime", version = "0.7.3" }
wrkflw-secrets = { path = "crates/secrets", version = "0.7.3" }
wrkflw-ui = { path = "crates/ui", version = "0.7.3" }
wrkflw-utils = { path = "crates/utils", version = "0.7.3" }
wrkflw-validators = { path = "crates/validators", version = "0.7.3" }
# External dependencies
clap = { version = "4.3", features = ["derive"] }
colored = "2.0"
serde = { version = "1.0", features = ["derive"] }
serde_yaml = "0.9"
serde_json = "1.0"
jsonschema = "0.17"
tokio = { version = "1.28", features = ["full"] }
async-trait = "0.1"
bollard = "0.14"
futures-util = "0.3"
futures = "0.3"
chrono = "0.4"
uuid = { version = "1.3", features = ["v4"] }
tempfile = "3.6"
tar = "0.4"
dirs = "5.0"
thiserror = "1.0"
log = "0.4"
which = "4.4"
crossterm = "0.28"
ratatui = { version = "0.28", features = ["crossterm"] }
once_cell = "1.19.0"
itertools = "0.11.0"
indexmap = { version = "2.0.0", features = ["serde"] }
rayon = "1.7.0"
num_cpus = "1.16.0"
regex = "1.10"
lazy_static = "1.4"
reqwest = { version = "0.11", default-features = false, features = [
"rustls-tls",
"json",
] }
libc = "0.2"
nix = { version = "0.27.1", features = ["fs"] }
urlencoding = "2.1.3"
wiremock = "0.5"
glob = "0.3"
sha2 = "0.10"
shlex = "1.3"
[profile.release]
codegen-units = 1
lto = true