ratatui_ruby 0.7.0 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e9da9703e1573d331115fd1635c1bf0564a6482c8d3d140c7b68230b56da8311
4
- data.tar.gz: 41efa864847825eed6d28ee3b9292e923b3f3c97ce28841892a65123df72c16e
3
+ metadata.gz: 4fc5e88c5e6146ec1aeae7816aeb8bea73926aaf90d5b2df75fa58d5c6490ab0
4
+ data.tar.gz: 90ec60b887d797c1cd8b8f06ca41506dbf8a463abc554c20b7caab8ec537b496
5
5
  SHA512:
6
- metadata.gz: efc0e59765a7cfea0108fa79335ae79d4b0bcdb2c9eff93f6e1f5d83dca04724484ce322b45a6d3e6d6cf45f80868a998c9684a8362c3e11b9e1ff02e30eade1
7
- data.tar.gz: be4afb8fe1b4bf7ed1ed4757521f8b90f68ca11c101aa69b184eae2de6807a2efa8a59fc2c19d0c47d3ed28f6afd89c906ba6480e824146b2b4ea8e7bc7211fa
6
+ metadata.gz: 3d6fdbb5c4c2970210f70cb0f16c0f40eca871afd6cc115cc5e744e9b85aed01b2c7209baabbb4b2e7129f6ddf460f1add31a6612070a8ef239fbb1e5e2893bd
7
+ data.tar.gz: ffef16474856ead4d0754dcd6f26104b795a6c2d8aac962df73d7f6c468d4fc6ad7c769a68bb97c1ad3fecc487973afbb50764c555f0a8385addf14864d5c22c
data/.builds/ruby-3.2.yml CHANGED
@@ -16,7 +16,7 @@ packages:
16
16
  - clang
17
17
  - git
18
18
  artifacts:
19
- - ratatui_ruby/pkg/ratatui_ruby-0.7.0.gem
19
+ - ratatui_ruby/pkg/ratatui_ruby-0.7.1.gem
20
20
  sources:
21
21
  - https://git.sr.ht/~kerrick/ratatui_ruby
22
22
  tasks:
data/.builds/ruby-3.3.yml CHANGED
@@ -16,7 +16,7 @@ packages:
16
16
  - clang
17
17
  - git
18
18
  artifacts:
19
- - ratatui_ruby/pkg/ratatui_ruby-0.7.0.gem
19
+ - ratatui_ruby/pkg/ratatui_ruby-0.7.1.gem
20
20
  sources:
21
21
  - https://git.sr.ht/~kerrick/ratatui_ruby
22
22
  tasks:
data/.builds/ruby-3.4.yml CHANGED
@@ -16,7 +16,7 @@ packages:
16
16
  - clang
17
17
  - git
18
18
  artifacts:
19
- - ratatui_ruby/pkg/ratatui_ruby-0.7.0.gem
19
+ - ratatui_ruby/pkg/ratatui_ruby-0.7.1.gem
20
20
  sources:
21
21
  - https://git.sr.ht/~kerrick/ratatui_ruby
22
22
  tasks:
@@ -16,7 +16,7 @@ packages:
16
16
  - clang
17
17
  - git
18
18
  artifacts:
19
- - ratatui_ruby/pkg/ratatui_ruby-0.7.0.gem
19
+ - ratatui_ruby/pkg/ratatui_ruby-0.7.1.gem
20
20
  sources:
21
21
  - https://git.sr.ht/~kerrick/ratatui_ruby
22
22
  tasks:
data/CHANGELOG.md CHANGED
@@ -18,6 +18,18 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
18
18
 
19
19
  ### Removed
20
20
 
21
+ ## [0.7.1] - 2026-01-03
22
+
23
+ ### Added
24
+
25
+ ### Changed
26
+
27
+ ### Fixed
28
+
29
+ - **Block Title Styling**: `Block` title `content` now correctly renders `Text::Line` objects with styled spans. Previously, passing a styled `Line` as title content displayed its Ruby inspect representation (e.g., `#<data RatatuiRuby::Text::Line...>`) instead of the styled text.
30
+
31
+ ### Removed
32
+
21
33
  ## [0.7.0] - 2026-01-03
22
34
 
23
35
  > [!WARNING]
@@ -356,6 +368,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
356
368
  - **Testing Support**: Included `RatatuiRuby::TestHelper` and RSpec integration to make testing your TUI applications possible.
357
369
 
358
370
  [Unreleased]: https://git.sr.ht/~kerrick/ratatui_ruby/refs/HEAD
371
+ [0.7.1]: https://git.sr.ht/~kerrick/ratatui_ruby/refs/v0.7.1
359
372
  [0.7.0]: https://git.sr.ht/~kerrick/ratatui_ruby/refs/v0.7.0
360
373
  [0.6.0]: https://git.sr.ht/~kerrick/ratatui_ruby/refs/v0.6.0
361
374
  [0.5.0]: https://git.sr.ht/~kerrick/ratatui_ruby/refs/v0.5.0
data/doc/async.md ADDED
@@ -0,0 +1,160 @@
1
+ <!--
2
+ SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
3
+ SPDX-License-Identifier: AGPL-3.0-or-later
4
+ -->
5
+
6
+ # Async Operations in TUI Applications
7
+
8
+ TUI applications fetch data from APIs, run shell commands, and query databases. These operations take time. Blocking the render loop freezes the interface.
9
+
10
+ You want responsive UIs. The checklist shows "Loading..." while data arrives. The interface never hangs.
11
+
12
+ This guide explains async patterns that work with raw terminal mode.
13
+
14
+ ## The Raw Terminal Problem
15
+
16
+ `RatatuiRuby.run` enters raw terminal mode:
17
+
18
+ - stdin reconfigures for character-by-character input
19
+ - stdout carries terminal escape sequences
20
+ - External commands expecting normal terminal I/O fail
21
+
22
+ ### What Breaks
23
+
24
+ ```ruby
25
+ # These fail inside a Thread during raw mode:
26
+ `git ls-remote --tags origin` # Returns empty or hangs
27
+ IO.popen(["git", "ls-remote", ...]) # Same
28
+ Open3.capture2("git", "ls-remote", ...) # Same
29
+ ```
30
+
31
+ The commands succeed synchronously. They fail asynchronously. The difference: thread context inherits the parent's raw terminal state.
32
+
33
+ ### Why Threads Fail
34
+
35
+ Ruby's GIL releases during I/O. But:
36
+
37
+ 1. Subprocesses inherit the parent's terminal state.
38
+ 2. Git/SSH try to read credentials from raw-mode stdin.
39
+ 3. The read blocks forever. Or returns empty.
40
+
41
+ `GIT_TERMINAL_PROMPT=0` prevents prompts. Auth fails silently instead of hanging.
42
+
43
+ ## Solutions
44
+
45
+ ### Pre-Check Before Raw Mode
46
+
47
+ Run slow operations before entering the TUI:
48
+
49
+ ```ruby
50
+ def initialize
51
+ @data = fetch_data # Runs before RatatuiRuby.run
52
+ end
53
+ ```
54
+
55
+ **Trade-off**: Delays startup.
56
+
57
+ ### Process.spawn with File Output
58
+
59
+ Spawn a separate process before entering raw mode. Write results to a temp file. Poll for completion:
60
+
61
+ ```ruby
62
+ class AsyncChecker
63
+ CACHE_FILE = File.join(Dir.tmpdir, "my_check_result.txt")
64
+
65
+ def initialize
66
+ @loading = true
67
+ @result = nil
68
+ @pid = Process.spawn("my-command > #{CACHE_FILE}")
69
+ end
70
+
71
+ def loading?
72
+ return false unless @loading
73
+
74
+ # Non-blocking poll
75
+ _pid, status = Process.waitpid2(@pid, Process::WNOHANG)
76
+ if status
77
+ @result = File.read(CACHE_FILE).strip
78
+ @loading = false
79
+ end
80
+ @loading
81
+ end
82
+ end
83
+ ```
84
+
85
+ **Key points**:
86
+
87
+ - `Process.spawn` returns immediately.
88
+ - The command runs in a separate process, not a thread.
89
+ - Results pass through a temp file. Avoids pipe/terminal issues.
90
+ - `Process::WNOHANG` polls without blocking.
91
+
92
+ ### Thread for CPU-Bound Work
93
+
94
+ Ruby threads work for pure computation:
95
+
96
+ ```ruby
97
+ Thread.new { @result = expensive_calculation }
98
+ ```
99
+
100
+ Avoid threads for shell commands.
101
+
102
+ ## Ractors
103
+
104
+ Ractors provide true parallelism. Trade-offs:
105
+
106
+ - No mutable shared state.
107
+ - Limited to Ractor-safe values.
108
+ - Terminal I/O issues remain.
109
+
110
+ For TUI async, `Process.spawn` solves the problem cleanly.
111
+
112
+ ## Pattern Summary
113
+
114
+ | Approach | Use Case | Raw Mode Safe? |
115
+ |----------|----------|----------------|
116
+ | Sync before TUI | Fast checks (<100ms) | Yes |
117
+ | Process.spawn + file | Shell commands, network | Yes |
118
+ | Thread | CPU-bound Ruby code | Yes |
119
+ | Thread + shell | Shell commands | **No** |
120
+
121
+ ## Real Example: Git Tag Check
122
+
123
+ Check if a tag exists on the remote:
124
+
125
+ ```ruby
126
+ class GitRepo
127
+ CACHE_FILE = File.join(Dir.tmpdir, "git_tag_pushed.txt")
128
+
129
+ def initialize
130
+ @version = `git describe --tags --abbrev=0`.strip
131
+ @tag_pushed = nil
132
+ @loading = true
133
+ @pid = Process.spawn(
134
+ "git ls-remote --tags origin | grep -q '#{@version}' " \
135
+ "&& echo true > #{CACHE_FILE} || echo false > #{CACHE_FILE}"
136
+ )
137
+ end
138
+
139
+ def loading?
140
+ return false unless @loading
141
+
142
+ _pid, status = Process.waitpid2(@pid, Process::WNOHANG)
143
+ if status
144
+ @tag_pushed = File.read(CACHE_FILE).strip == "true"
145
+ @loading = false
146
+ end
147
+ @loading
148
+ end
149
+
150
+ def refresh
151
+ # Sync version for manual refresh (user presses 'r')
152
+ @loading = true
153
+ remote_tags = `git ls-remote --tags origin`.strip
154
+ @tag_pushed = remote_tags.include?(@version)
155
+ @loading = false
156
+ end
157
+ end
158
+ ```
159
+
160
+ The TUI starts instantly. The tag check runs in the background. The checklist updates when the result arrives.
data/doc/debugging.md ADDED
@@ -0,0 +1,71 @@
1
+ <!--
2
+ SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
3
+ SPDX-License-Identifier: AGPL-3.0-or-later
4
+ -->
5
+
6
+ # Debugging TUI Applications
7
+
8
+ TUI applications run in raw terminal mode. stderr and stdout carry terminal escape sequences. Using `puts` or `warn` inside the render loop corrupts the display.
9
+
10
+ This creates a problem: how do you inspect variables and trace execution?
11
+
12
+ Write debug output to files. Tail them in a separate terminal.
13
+
14
+ ## File-Based Logging
15
+
16
+ Create timestamped log files to avoid overwrites:
17
+
18
+ ```ruby
19
+ FileUtils.mkdir_p(File.join(Dir.tmpdir, "my_debug"))
20
+ timestamp = Time.now.strftime('%Y%m%d_%H%M%S_%N')
21
+ File.write(
22
+ File.join(Dir.tmpdir, "my_debug", "#{timestamp}.log"),
23
+ "variable=#{value.inspect}\n"
24
+ )
25
+ ```
26
+
27
+ Or append to a single file:
28
+
29
+ ```ruby
30
+ File.write("/tmp/debug.log", "#{Time.now}: #{message}\n", mode: "a")
31
+ ```
32
+
33
+ Tail the logs in a separate terminal:
34
+
35
+ ```bash
36
+ # Single file
37
+ tail -f /tmp/debug.log
38
+
39
+ # Directory of timestamped files
40
+ watch -n 0.5 'ls -la /tmp/my_debug/ && cat /tmp/my_debug/*.log'
41
+ ```
42
+
43
+ ## REPL Debugging with `__FILE__` Guards
44
+
45
+ Unit tests verify correctness. But during exploratory debugging, you want to poke at objects interactively. Loading the full TUI just to inspect one object is slow.
46
+
47
+ Wrap your main execution in a guard:
48
+
49
+ ```ruby
50
+ if __FILE__ == $PROGRAM_NAME
51
+ MyApp.new.run
52
+ end
53
+ ```
54
+
55
+ Now load the file and interact with classes directly:
56
+
57
+ ```bash
58
+ ruby -e 'load "./bin/my_tui"; obj = MyClass.new; sleep 1; puts obj.result'
59
+ ```
60
+
61
+ This exercises domain logic without entering raw terminal mode. Use it for exploratory debugging. Write tests using the [TestHelper](application_testing.md) for regression coverage.
62
+
63
+ ## Isolating Terminal Issues
64
+
65
+ Something works in a `ruby -e` script but fails in the TUI. Common causes:
66
+
67
+ 1. **Thread context.** Ruby threads share the process's terminal state.
68
+ 2. **Raw mode.** External commands fail when stdin/stdout are reconfigured.
69
+ 3. **SSH/Git auth.** Commands that prompt for credentials hang or return empty.
70
+
71
+ See [Async Operations](async.md) for solutions.
@@ -1012,7 +1012,7 @@ dependencies = [
1012
1012
 
1013
1013
  [[package]]
1014
1014
  name = "ratatui_ruby"
1015
- version = "0.7.0"
1015
+ version = "0.7.1"
1016
1016
  dependencies = [
1017
1017
  "bumpalo",
1018
1018
  "lazy_static",
@@ -3,7 +3,7 @@
3
3
 
4
4
  [package]
5
5
  name = "ratatui_ruby"
6
- version = "0.7.0"
6
+ version = "0.7.1"
7
7
  edition = "2021"
8
8
 
9
9
  [lib]
@@ -12,6 +12,8 @@ use ratatui::{
12
12
  widgets::{Block, BorderType, Borders, Padding},
13
13
  };
14
14
 
15
+ use crate::text::parse_line;
16
+
15
17
  pub fn parse_color(color_str: &str) -> Option<Color> {
16
18
  // Try standard ratatui parsing first (named colors, indexed, etc.)
17
19
  if let Ok(color) = color_str.parse::<Color>() {
@@ -274,15 +276,22 @@ fn parse_titles(block_val: Value, mut block: Block<'_>) -> Result<Block<'_>, Err
274
276
  let index = isize::try_from(i)
275
277
  .map_err(|e| Error::new(ruby.exception_range_error(), e.to_string()))?;
276
278
  let title_item: Value = titles_array.entry(index)?;
277
- let mut content = String::new();
278
279
  let mut alignment = Alignment::Left;
279
280
  let mut is_bottom = false;
280
281
  let mut style = Style::default();
282
+ let mut line: Option<Line<'static>> = None;
281
283
 
282
284
  if let Some(hash) = magnus::RHash::from_value(title_item) {
283
285
  if let Ok(v) = hash.lookup::<_, Value>(ruby.to_symbol("content")) {
284
286
  if !v.is_nil() {
285
- content = v.funcall("to_s", ())?;
287
+ // First, try to parse as a Line object (preserves styling)
288
+ if let Ok(parsed_line) = parse_line(v) {
289
+ line = Some(parsed_line);
290
+ } else {
291
+ // Fallback to string
292
+ let content: String = v.funcall("to_s", ())?;
293
+ line = Some(Line::from(content));
294
+ }
286
295
  }
287
296
  }
288
297
  if let Ok(v) = hash.lookup::<_, Value>(ruby.to_symbol("alignment")) {
@@ -307,15 +316,22 @@ fn parse_titles(block_val: Value, mut block: Block<'_>) -> Result<Block<'_>, Err
307
316
  }
308
317
  }
309
318
  } else {
310
- content = title_item.funcall("to_s", ())?;
319
+ let content: String = title_item.funcall("to_s", ())?;
320
+ line = Some(Line::from(content));
311
321
  }
312
322
 
313
- let line = Line::from(content).alignment(alignment).style(style);
314
- block = if is_bottom {
315
- block.title_bottom(line)
316
- } else {
317
- block.title_top(line)
318
- };
323
+ if let Some(mut l) = line {
324
+ l = l.alignment(alignment);
325
+ // Only apply style if the line doesn't already have styled spans
326
+ if style != Style::default() {
327
+ l = l.style(style);
328
+ }
329
+ block = if is_bottom {
330
+ block.title_bottom(l)
331
+ } else {
332
+ block.title_top(l)
333
+ };
334
+ }
319
335
  }
320
336
  }
321
337
  }
@@ -6,5 +6,5 @@
6
6
  module RatatuiRuby
7
7
  # The version of the ratatui_ruby gem.
8
8
  # See https://semver.org/spec/v2.0.0.html
9
- VERSION = "0.7.0"
9
+ VERSION = "0.7.1"
10
10
  end
@@ -34,4 +34,12 @@ class Changelog
34
34
  File.write(@path, "#{header}#{UnreleasedSection.fresh}\n\n#{history}\n#{links}")
35
35
  nil
36
36
  end
37
+
38
+ def commit_message(version)
39
+ content = File.read(@path)
40
+ unreleased = UnreleasedSection.parse(content)
41
+ return nil unless unreleased
42
+
43
+ "chore: release v#{version}\n\n#{unreleased.commit_body}"
44
+ end
37
45
  end
@@ -17,19 +17,31 @@ class RubyGem
17
17
 
18
18
  def bump(segment)
19
19
  target = version.next(segment)
20
+ commit_message = @changelog.commit_message(target)
20
21
 
21
22
  puts "Bumping #{segment}: #{version} -> #{target}"
22
23
  @changelog.release(target)
23
24
  @manifests.each { |manifest| manifest.write(target) }
24
25
  @lockfile.refresh
26
+
27
+ puts_commit_message(commit_message)
25
28
  end
26
29
 
27
30
  def set(version_string)
28
31
  target = SemVer.parse(version_string)
32
+ commit_message = @changelog.commit_message(target)
29
33
 
30
34
  puts "Setting version: #{version} -> #{target}"
31
35
  @changelog.release(target)
32
36
  @manifests.each { |manifest| manifest.write(target) }
33
37
  @lockfile.refresh
38
+
39
+ puts_commit_message(commit_message)
40
+ end
41
+
42
+ private def puts_commit_message(message)
43
+ puts "=" * 80
44
+ puts message
45
+ puts "=" * 80
34
46
  end
35
47
  end
@@ -4,6 +4,7 @@
4
4
  # SPDX-License-Identifier: AGPL-3.0-or-later
5
5
 
6
6
  require "date"
7
+ require "rdoc"
7
8
 
8
9
  # UnreleasedSection manages the [Unreleased] section of the changelog.
9
10
  class UnreleasedSection
@@ -35,4 +36,19 @@ class UnreleasedSection
35
36
  def to_s
36
37
  @content
37
38
  end
39
+
40
+ def commit_body
41
+ formatter = Class.new { include RDoc::Text }.new
42
+ @content
43
+ .sub(/^## \[Unreleased\].*$/, "")
44
+ .gsub(/^### (Added|Changed|Fixed|Removed)\n*$/, "")
45
+ .gsub(/^- \*\*([^*]+)\*\*:/, '\1:')
46
+ .gsub(/`([^`]+)`/, '\1')
47
+ .strip
48
+ .lines
49
+ .map { |line| line.gsub(/^- /, "").strip }
50
+ .reject(&:empty?)
51
+ .map { |line| formatter.wrap(line, 72) }
52
+ .join("\n\n")
53
+ end
38
54
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ratatui_ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.0
4
+ version: 0.7.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kerrick Long
@@ -107,6 +107,7 @@ files:
107
107
  - Rakefile
108
108
  - doc/application_architecture.md
109
109
  - doc/application_testing.md
110
+ - doc/async.md
110
111
  - doc/contributors/architectural_overhaul/chat_conversations.md
111
112
  - doc/contributors/architectural_overhaul/implementation_plan.md
112
113
  - doc/contributors/architectural_overhaul/task.md
@@ -118,6 +119,7 @@ files:
118
119
  - doc/contributors/index.md
119
120
  - doc/contributors/v1.0.0_blockers.md
120
121
  - doc/custom.css
122
+ - doc/debugging.md
121
123
  - doc/event_handling.md
122
124
  - doc/images/app_all_events.png
123
125
  - doc/images/app_color_picker.png