Skip to content

Commit aaeb904

Browse files
serrrfiratclaude
andauthored
feat(tui): ship TUI in default binary (#2195)
* feat(tui): ship TUI in default binary Add `tui` to default Cargo features so the Ratatui terminal UI is included in standard builds. The TUI only activates when explicitly configured at runtime (`config.channels.tui`), so server deployments are unaffected — the deps compile in but nothing initializes. Also remove `dist = false` from ironclaw_tui so cargo-dist includes it in release artifacts. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(tui): make arboard/clipboard optional to support headless builds arboard requires X11/Wayland dev headers on Linux, breaking builds in minimal Docker images and headless CI runners. Move arboard and image behind an opt-in `clipboard` feature (defaulted on) so headless builds can exclude them while desktop builds keep full clipboard support. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent e0bdd74 commit aaeb904

3 files changed

Lines changed: 23 additions & 6 deletions

File tree

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,7 @@ tempfile = "3"
226226
insta = "1.46.3"
227227

228228
[features]
229-
default = ["postgres", "libsql", "html-to-markdown"]
229+
default = ["postgres", "libsql", "html-to-markdown", "tui"]
230230
postgres = [
231231
"dep:deadpool-postgres",
232232
"dep:tokio-postgres",

crates/ironclaw_tui/Cargo.toml

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,9 @@ license = "MIT OR Apache-2.0"
99
homepage = "https://github.com/nearai/ironclaw"
1010
repository = "https://github.com/nearai/ironclaw"
1111

12-
[package.metadata.dist]
13-
dist = false
12+
[features]
13+
default = ["clipboard"]
14+
clipboard = ["dep:arboard", "dep:image"]
1415

1516
[dependencies]
1617
ratatui = { version = "0.29", features = ["crossterm"] }
@@ -23,8 +24,8 @@ unicode-width = "0.2"
2324
pulldown-cmark = { version = "0.12", default-features = false }
2425
thiserror = "2"
2526
tracing = "0.1"
26-
arboard = "3"
27-
image = { version = "0.25", default-features = false, features = ["png"] }
27+
arboard = { version = "3", optional = true }
28+
image = { version = "0.25", default-features = false, features = ["png"], optional = true }
2829

2930
[dev-dependencies]
3031
tokio = { version = "1", features = ["full"] }

crates/ironclaw_tui/src/app.rs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1660,12 +1660,18 @@ fn copy_text_to_clipboard(text: &str) -> bool {
16601660
true
16611661
}
16621662

1663-
#[cfg(not(test))]
1663+
#[cfg(all(not(test), feature = "clipboard"))]
16641664
{
16651665
arboard::Clipboard::new()
16661666
.and_then(|mut clipboard| clipboard.set_text(text.to_string()))
16671667
.is_ok()
16681668
}
1669+
1670+
#[cfg(all(not(test), not(feature = "clipboard")))]
1671+
{
1672+
let _ = text;
1673+
false
1674+
}
16691675
}
16701676

16711677
async fn handle_mouse_click(
@@ -2442,6 +2448,7 @@ fn capture_screen_snapshot(frame: &mut ratatui::Frame<'_>, state: &mut AppState)
24422448
/// Try to read an image from the system clipboard and return it as a PNG-encoded
24432449
/// [`TuiAttachment`]. Returns `None` if the clipboard has no image data or if
24442450
/// encoding fails.
2451+
#[cfg(feature = "clipboard")]
24452452
fn try_paste_clipboard_image(state: &AppState) -> Option<TuiAttachment> {
24462453
let mut clipboard = arboard::Clipboard::new().ok()?;
24472454
let img_data = clipboard.get_image().ok()?;
@@ -2460,8 +2467,14 @@ fn try_paste_clipboard_image(state: &AppState) -> Option<TuiAttachment> {
24602467
})
24612468
}
24622469

2470+
#[cfg(not(feature = "clipboard"))]
2471+
fn try_paste_clipboard_image(_state: &AppState) -> Option<TuiAttachment> {
2472+
None
2473+
}
2474+
24632475
/// Encode raw RGBA pixel data to PNG. Returns `None` on invalid dimensions or
24642476
/// encoding failure.
2477+
#[cfg(feature = "clipboard")]
24652478
fn encode_rgba_to_png(rgba: &[u8], width: u32, height: u32) -> Option<Vec<u8>> {
24662479
let expected_len = (width as usize)
24672480
.checked_mul(height as usize)?
@@ -2526,6 +2539,7 @@ mod tests {
25262539
}
25272540
}
25282541

2542+
#[cfg(feature = "clipboard")]
25292543
#[test]
25302544
fn encode_rgba_to_png_valid() {
25312545
// 2x2 red image
@@ -2540,6 +2554,7 @@ mod tests {
25402554
assert_eq!(&bytes[..4], &[0x89, b'P', b'N', b'G']);
25412555
}
25422556

2557+
#[cfg(feature = "clipboard")]
25432558
#[test]
25442559
fn encode_rgba_to_png_bad_dimensions() {
25452560
let rgba = vec![0u8; 16]; // 4 pixels
@@ -2548,6 +2563,7 @@ mod tests {
25482563
assert!(png.is_none());
25492564
}
25502565

2566+
#[cfg(feature = "clipboard")]
25512567
#[test]
25522568
fn encode_rgba_to_png_zero_size() {
25532569
// 0x0 image: the image crate rejects zero-dimension buffers

0 commit comments

Comments
 (0)