milktea 0.1.0 → 0.2.0
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 +4 -4
- data/.release-please-manifest.json +3 -0
- data/.rubocop.yml +2 -1
- data/CHANGELOG.md +19 -0
- data/CLAUDE.md +81 -35
- data/README.md +17 -7
- data/docs/devlog/20250705.md +56 -6
- data/docs/devlog/20250706.md +118 -0
- data/examples/container_layout.rb +8 -1
- data/examples/container_simple.rb +8 -1
- data/examples/hot_reload_demo.rb +8 -1
- data/examples/text_demo.rb +136 -0
- data/examples/tick_example.rb +63 -0
- data/lib/milktea/message.rb +6 -2
- data/lib/milktea/program.rb +1 -0
- data/lib/milktea/renderer.rb +2 -2
- data/lib/milktea/runtime.rb +0 -8
- data/lib/milktea/text.rb +51 -0
- data/lib/milktea/version.rb +1 -1
- data/release-please-config.json +10 -0
- metadata +25 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b8fc1c4405e6f8b4d6969285482fc16ec7d103ff9399f89a2606532e3545b9a6
|
4
|
+
data.tar.gz: 38638d551a1c7ffd34c0b2d32ff75824db9daff1371cb3ecf30ab7604f714f14
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0f2e50699ec28cddbeaabfa11ceeda12bf544d4572d38d78fd8edbcaa2efe0ad7d4b7e0f47af1b7945ec29fb74614ae2661da2ede93e40daa2952bab82d7426f
|
7
|
+
data.tar.gz: 5b4397dfb287ace7627b374a005f62b93fd76ac578aff0c9067df52175eb2a4345ff9548998dd63b8cf8ba27b6ac0387b1c0e57fdd76afb19034922ece5c2bf5
|
data/.rubocop.yml
CHANGED
data/CHANGELOG.md
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# Changelog
|
2
|
+
|
3
|
+
## [0.2.0](https://github.com/elct9620/milktea/compare/v0.1.0...v0.2.0) (2025-07-06)
|
4
|
+
|
5
|
+
|
6
|
+
### Features
|
7
|
+
|
8
|
+
* add truncation mode to Milktea::Text component ([031f87a](https://github.com/elct9620/milktea/commit/031f87a6f55476098ac7247b8041e32e38e67961))
|
9
|
+
* bump minimum Ruby version to 3.2+ for Zeitwerk compatibility ([18cc801](https://github.com/elct9620/milktea/commit/18cc8011c2ac4586ad849bf4670750ecbc011294))
|
10
|
+
* implement Message::Tick with timestamp for timing-based updates ([982baec](https://github.com/elct9620/milktea/commit/982baeca567eed949ec82187fefb1f45078e97f5))
|
11
|
+
* implement Milktea::Text component with overflow handling ([5a0ef38](https://github.com/elct9620/milktea/commit/5a0ef38708e74f4eef6905c2f72813c4259fdfa0))
|
12
|
+
* refactor examples to use bundler/inline for self-contained dependencies ([2192a6b](https://github.com/elct9620/milktea/commit/2192a6b185e04d26fa258231c88f412f39254b1a))
|
13
|
+
|
14
|
+
|
15
|
+
### Bug Fixes
|
16
|
+
|
17
|
+
* add rubocop-rubycw gem to resolve CI failure ([82b0dd7](https://github.com/elct9620/milktea/commit/82b0dd71bd535e8b2169401ca5e1f78e9f5dea79))
|
18
|
+
* properly configure RuboCop plugins to resolve CI failures ([d82e2c8](https://github.com/elct9620/milktea/commit/d82e2c83338eaca3841bfbb3cceec401bbdc6787))
|
19
|
+
* use clear_screen_down instead of clear_screen in renderer ([d41d431](https://github.com/elct9620/milktea/commit/d41d43115ca36b801fe5289d38996d4a38997e22))
|
data/CLAUDE.md
CHANGED
@@ -202,7 +202,63 @@ This project follows Clean Architecture and Domain-Driven Design (DDD) principle
|
|
202
202
|
|
203
203
|
Follow these conventions when writing RSpec tests:
|
204
204
|
|
205
|
-
|
205
|
+
### Critical Anti-Patterns to Avoid
|
206
|
+
|
207
|
+
**NEVER use introspection methods to test private implementation details:**
|
208
|
+
|
209
|
+
```ruby
|
210
|
+
# BAD - Never use these anti-patterns
|
211
|
+
it { expect(program.send(:check_resize)).to be_nil }
|
212
|
+
it { expect(subject.instance_variable_get(:@output)).to eq($stdout) }
|
213
|
+
it { expect(object.send(:private_method)).to eq(value) }
|
214
|
+
|
215
|
+
# GOOD - Test through public API and observable behavior
|
216
|
+
it { expect(runtime).to have_received(:enqueue) }
|
217
|
+
it { expect(output.string).to include("expected content") }
|
218
|
+
it { expect(program).to respond_to(:stop) }
|
219
|
+
```
|
220
|
+
|
221
|
+
**Why these are anti-patterns:**
|
222
|
+
- Tests implementation details instead of behavior
|
223
|
+
- Makes tests brittle and coupled to internal structure
|
224
|
+
- Violates encapsulation principles
|
225
|
+
- Makes refactoring difficult
|
226
|
+
|
227
|
+
**Always test through public interfaces and observable outcomes.**
|
228
|
+
|
229
|
+
### Core Testing Conventions
|
230
|
+
|
231
|
+
1. **Use `describe` blocks to reference actual methods**:
|
232
|
+
```ruby
|
233
|
+
# GOOD - Reference actual methods
|
234
|
+
describe "#initialize" do
|
235
|
+
describe "#update" do
|
236
|
+
describe "#with" do
|
237
|
+
describe ".configure" do # Class method
|
238
|
+
describe ".new" do # Class method
|
239
|
+
|
240
|
+
# BAD - Generic descriptions that don't map to methods
|
241
|
+
describe "initialization" do
|
242
|
+
describe "test creation" do
|
243
|
+
describe "updating behavior" do
|
244
|
+
describe "configuration setup" do
|
245
|
+
```
|
246
|
+
|
247
|
+
2. **Use `context` blocks with "when" for conditional scenarios**:
|
248
|
+
```ruby
|
249
|
+
# GOOD - Always start context with "when" for conditions
|
250
|
+
context "when merging provided state with default state" do
|
251
|
+
context "when no config is provided" do
|
252
|
+
context "when runtime is running" do
|
253
|
+
context "when renderer detects resize" do
|
254
|
+
|
255
|
+
# BAD - Don't use context for non-conditional descriptions
|
256
|
+
context "merging state" do
|
257
|
+
context "configuration testing" do
|
258
|
+
context "with custom values" do
|
259
|
+
```
|
260
|
+
|
261
|
+
3. **ALWAYS prefer one-line `it { ... }` syntax**:
|
206
262
|
```ruby
|
207
263
|
# Preferred - Always use this when possible
|
208
264
|
it { expect(model.state[:count]).to eq(0) }
|
@@ -215,7 +271,7 @@ Follow these conventions when writing RSpec tests:
|
|
215
271
|
end
|
216
272
|
```
|
217
273
|
|
218
|
-
|
274
|
+
4. **Use `subject` to define test targets**:
|
219
275
|
```ruby
|
220
276
|
subject(:program) { described_class.new }
|
221
277
|
subject(:new_model) { model.with(count: 5) }
|
@@ -226,7 +282,7 @@ Follow these conventions when writing RSpec tests:
|
|
226
282
|
subject { described_class.env }
|
227
283
|
```
|
228
284
|
|
229
|
-
|
285
|
+
5. **Use `let` for test dependencies and lazy evaluation**:
|
230
286
|
```ruby
|
231
287
|
let(:output) { StringIO.new }
|
232
288
|
let(:new_model) { result.first }
|
@@ -234,7 +290,7 @@ Follow these conventions when writing RSpec tests:
|
|
234
290
|
let(:original_children) { parent_model.children }
|
235
291
|
```
|
236
292
|
|
237
|
-
|
293
|
+
6. **Use `context` to group related scenarios and enable one-liners**:
|
238
294
|
```ruby
|
239
295
|
# Good - Use context to set up scenarios for one-line tests
|
240
296
|
context "when merging provided state with default state" do
|
@@ -251,7 +307,7 @@ Follow these conventions when writing RSpec tests:
|
|
251
307
|
end
|
252
308
|
```
|
253
309
|
|
254
|
-
|
310
|
+
7. **Use `is_expected` when testing the subject directly**:
|
255
311
|
```ruby
|
256
312
|
it { is_expected.to be_running } # Preferred
|
257
313
|
it { is_expected.not_to be(model) }
|
@@ -263,7 +319,7 @@ Follow these conventions when writing RSpec tests:
|
|
263
319
|
it { expect(described_class.root).to be_a(Pathname) }
|
264
320
|
```
|
265
321
|
|
266
|
-
|
322
|
+
8. **Use `before` blocks for setup actions, not variable assignments**:
|
267
323
|
```ruby
|
268
324
|
# Good - Setup actions in before blocks
|
269
325
|
context "when configuring with block" do
|
@@ -284,13 +340,13 @@ Follow these conventions when writing RSpec tests:
|
|
284
340
|
end
|
285
341
|
```
|
286
342
|
|
287
|
-
|
343
|
+
9. **Use `.to change()` for testing immutability**:
|
288
344
|
```ruby
|
289
345
|
it { expect { model.update(:increment) }.not_to change(model, :state) }
|
290
346
|
it { expect { model.with(count: 5) }.not_to change(model, :state) }
|
291
347
|
```
|
292
348
|
|
293
|
-
|
349
|
+
10. **Each `it` block should have only one expectation**:
|
294
350
|
```ruby
|
295
351
|
# Good - Separate one-line tests
|
296
352
|
it { expect(new_model).not_to be(model) }
|
@@ -304,7 +360,7 @@ Follow these conventions when writing RSpec tests:
|
|
304
360
|
end
|
305
361
|
```
|
306
362
|
|
307
|
-
|
363
|
+
11. **Transform multi-line tests into context + one-liners**:
|
308
364
|
```ruby
|
309
365
|
# Good - Use context to enable one-liner
|
310
366
|
context "when called on base class" do
|
@@ -328,7 +384,7 @@ Follow these conventions when writing RSpec tests:
|
|
328
384
|
end
|
329
385
|
```
|
330
386
|
|
331
|
-
|
387
|
+
12. **PRIORITY: Transform ANY multi-line test into context + one-liner**:
|
332
388
|
```ruby
|
333
389
|
# If you find yourself writing this:
|
334
390
|
it "merges provided state with default state" do
|
@@ -344,24 +400,6 @@ Follow these conventions when writing RSpec tests:
|
|
344
400
|
end
|
345
401
|
```
|
346
402
|
|
347
|
-
11. **Never test private instance variables directly**:
|
348
|
-
```ruby
|
349
|
-
# Bad - Testing implementation details
|
350
|
-
it { expect(subject.instance_variable_get(:@output)).to eq($stdout) }
|
351
|
-
|
352
|
-
# Good - Testing public behavior with one-liner
|
353
|
-
it { expect(output.string).to include("expected content") }
|
354
|
-
```
|
355
|
-
|
356
|
-
12. **Focus on observable behavior, not implementation**:
|
357
|
-
```ruby
|
358
|
-
# Bad - Checking internal state
|
359
|
-
it { expect(program.instance_variable_get(:@renderer)).to be_a(Milktea::Renderer) }
|
360
|
-
|
361
|
-
# Good - Testing actual public behavior with one-liner
|
362
|
-
it { expect(runtime).to have_received(:stop) }
|
363
|
-
```
|
364
|
-
|
365
403
|
13. **Prefer `instance_double` over extensive `allow` calls**:
|
366
404
|
```ruby
|
367
405
|
# Bad - Multiple allow calls
|
@@ -384,7 +422,7 @@ Follow these conventions when writing RSpec tests:
|
|
384
422
|
end
|
385
423
|
```
|
386
424
|
|
387
|
-
|
425
|
+
14. **Use spies for testing delegation instead of expect().to receive()**:
|
388
426
|
```ruby
|
389
427
|
# Bad - Pre-setting expectations
|
390
428
|
it "delegates to runtime stop" do
|
@@ -401,7 +439,7 @@ Follow these conventions when writing RSpec tests:
|
|
401
439
|
end
|
402
440
|
```
|
403
441
|
|
404
|
-
|
442
|
+
15. **Use RSpec's `output` matcher for testing stdout/stderr**:
|
405
443
|
```ruby
|
406
444
|
# Good - Using output matcher
|
407
445
|
it "prints to stdout" do
|
@@ -422,7 +460,7 @@ Follow these conventions when writing RSpec tests:
|
|
422
460
|
end
|
423
461
|
```
|
424
462
|
|
425
|
-
|
463
|
+
16. **Use `allow(ENV).to receive(:fetch)` for environment variable mocking**:
|
426
464
|
```ruby
|
427
465
|
# Good - Mock ENV.fetch calls
|
428
466
|
before { allow(ENV).to receive(:fetch).with("MILKTEA_ENV", nil).and_return("test") }
|
@@ -432,7 +470,7 @@ Follow these conventions when writing RSpec tests:
|
|
432
470
|
after { ENV.delete("MILKTEA_ENV") }
|
433
471
|
```
|
434
472
|
|
435
|
-
|
473
|
+
17. **Structure module/class method tests with clear subject definitions**:
|
436
474
|
```ruby
|
437
475
|
describe ".root" do
|
438
476
|
subject { described_class.root }
|
@@ -453,7 +491,7 @@ Follow these conventions when writing RSpec tests:
|
|
453
491
|
end
|
454
492
|
```
|
455
493
|
|
456
|
-
|
494
|
+
18. **Use named subjects for configuration testing**:
|
457
495
|
```ruby
|
458
496
|
describe ".configure" do
|
459
497
|
subject(:config) { described_class.config } # Named subject for clarity
|
@@ -474,7 +512,15 @@ Follow these conventions when writing RSpec tests:
|
|
474
512
|
|
475
513
|
### RSpec Style Summary
|
476
514
|
|
477
|
-
**CRITICAL
|
515
|
+
**CRITICAL ANTI-PATTERNS - NEVER DO THESE:**
|
516
|
+
- Never use `send(:private_method)` to test private methods
|
517
|
+
- Never use `instance_variable_get(:@var)` to test private variables
|
518
|
+
- Always test through public interfaces and observable behavior
|
519
|
+
|
520
|
+
**CRITICAL CONVENTIONS:**
|
521
|
+
- `describe` blocks must reference actual methods (`#initialize`, `.configure`)
|
522
|
+
- `context` blocks must start with "when" for conditional scenarios
|
523
|
+
- Always prefer `it { ... }` one-line syntax over multi-line blocks
|
478
524
|
|
479
525
|
**The Golden Pattern**:
|
480
526
|
```ruby
|
@@ -517,7 +563,7 @@ end
|
|
517
563
|
|
518
564
|
## Important Notes
|
519
565
|
|
520
|
-
- Ruby version requirement: >= 3.
|
566
|
+
- Ruby version requirement: >= 3.2.0
|
521
567
|
- Uses conventional commits format in English
|
522
568
|
- The gemspec uses git to determine which files to include in the gem
|
523
569
|
- Currently in early development (v0.1.0) with core architecture implemented
|
data/README.md
CHANGED
@@ -12,6 +12,8 @@ A Terminal User Interface (TUI) framework for Ruby, inspired by [Bubble Tea](htt
|
|
12
12
|
- 🔄 **Hot Reloading**: Instant feedback during development (similar to web frameworks)
|
13
13
|
- 📱 **Responsive Design**: Automatic adaptation to terminal resize events
|
14
14
|
- 🧩 **Composable Components**: Build complex UIs from simple, reusable models
|
15
|
+
- 📝 **Text Components**: Unicode-aware text rendering with wrapping and truncation
|
16
|
+
- ⏱️ **Timing System**: Built-in support for animations and time-based updates
|
15
17
|
- 🎨 **Rich Terminal Support**: Leverage TTY gems for advanced terminal features
|
16
18
|
|
17
19
|
## Installation
|
@@ -224,14 +226,20 @@ end
|
|
224
226
|
|
225
227
|
Explore the `examples/` directory for comprehensive demonstrations:
|
226
228
|
|
227
|
-
- **[
|
229
|
+
- **[Simple Counter](examples/simple.rb)**: Basic Elm Architecture patterns
|
230
|
+
- **[Container Layout](examples/container_layout.rb)**: Flexbox-style layouts with resize support
|
231
|
+
- **[Text Components](examples/text_demo.rb)**: Unicode text rendering with dual modes
|
232
|
+
- **[Animations](examples/tick_example.rb)**: Timing-based animations and dynamic content
|
228
233
|
- **[Hot Reload Demo](examples/hot_reload_demo.rb)**: Development workflow with instant updates
|
234
|
+
- **[Dashboard](examples/dashboard.rb)**: Complex multi-component layout
|
229
235
|
|
230
236
|
Run examples:
|
231
237
|
|
232
238
|
```bash
|
239
|
+
ruby examples/simple.rb
|
240
|
+
ruby examples/text_demo.rb
|
241
|
+
ruby examples/tick_example.rb
|
233
242
|
ruby examples/container_layout.rb
|
234
|
-
ruby examples/hot_reload_demo.rb
|
235
243
|
```
|
236
244
|
|
237
245
|
## Advanced Features
|
@@ -318,17 +326,19 @@ program.run
|
|
318
326
|
|
319
327
|
- **`Milktea::Model`**: Base class for all UI components
|
320
328
|
- **`Milktea::Container`**: Layout container with flexbox-style properties
|
329
|
+
- **`Milktea::Text`**: Unicode-aware text component with dual rendering modes
|
321
330
|
- **`Milktea::Application`**: High-level application wrapper
|
322
331
|
- **`Milktea::Program`**: Main application runtime
|
323
|
-
- **`Milktea::Message`**: Standard message types
|
332
|
+
- **`Milktea::Message`**: Standard message types for application events
|
324
333
|
|
325
334
|
### Message System
|
326
335
|
|
327
|
-
- **`Message::KeyPress`**: Keyboard input events
|
336
|
+
- **`Message::KeyPress`**: Keyboard input events with key details
|
337
|
+
- **`Message::Tick`**: Timing events with timestamps for animations
|
328
338
|
- **`Message::Exit`**: Application termination
|
329
|
-
- **`Message::Resize`**: Terminal size changes
|
330
|
-
- **`Message::Reload`**: Hot reload events
|
331
|
-
- **`Message::None`**: No-operation message
|
339
|
+
- **`Message::Resize`**: Terminal size changes
|
340
|
+
- **`Message::Reload`**: Hot reload events (development)
|
341
|
+
- **`Message::None`**: No-operation message (no render)
|
332
342
|
|
333
343
|
For detailed API documentation, see the [documentation website](https://rubydoc.info/gems/milktea).
|
334
344
|
|
data/docs/devlog/20250705.md
CHANGED
@@ -20,6 +20,12 @@ Two new comprehensive examples demonstrate the framework's layout capabilities:
|
|
20
20
|
|
21
21
|
These examples integrate with tty-box for professional terminal UI presentation and serve as practical references for developers building TUI applications.
|
22
22
|
|
23
|
+
#### Self-Contained Example Architecture
|
24
|
+
Examples have been completely refactored to use `bundler/inline` with inline gemfiles, making them truly self-contained. Each example now manages its own specific dependencies without affecting the main project's Gemfile. This architectural change eliminates the need for developers to modify project dependencies just to run examples and ensures examples remain functional regardless of the main project's development dependencies.
|
25
|
+
|
26
|
+
#### Enhanced Ruby Compatibility and CI Infrastructure
|
27
|
+
The project now enforces a minimum Ruby version of 3.2+ to ensure Zeitwerk compatibility and modern Ruby feature availability. The CI matrix has been expanded to test against Ruby 3.2, 3.3, and 3.4, providing comprehensive compatibility assurance across current Ruby versions while maintaining focus on supported versions.
|
28
|
+
|
23
29
|
## What's Fixed
|
24
30
|
|
25
31
|
#### Hot Reloading and Dynamic Layout Integration
|
@@ -31,6 +37,12 @@ Fixed children_views joining behavior to eliminate unnecessary newlines between
|
|
31
37
|
#### Test Organization and Coverage
|
32
38
|
Reorganized test suites to properly reflect the architectural changes. Dynamic child resolution tests were moved from container_spec to model_spec, aligning test coverage with the new framework-wide availability of the feature. This ensures maintainability and prevents confusion about where functionality is implemented.
|
33
39
|
|
40
|
+
#### Dependency Management and Development Environment
|
41
|
+
Resolved dependency bloat by removing unnecessary development dependencies (ruby-lsp, logger, rbs, sorbet-runtime) from the main Gemfile. The ruby-lsp dependency was causing maintenance overhead without providing essential functionality for the framework itself. This cleanup reduces installation size and eliminates potential dependency conflicts for users integrating Milktea into their applications.
|
42
|
+
|
43
|
+
#### CI Configuration and RuboCop Integration
|
44
|
+
Fixed CI failures caused by RuboCop plugin configuration issues. Removed unnecessary RuboCop plugins (rubocop-rubycw, rubocop-on-rbs) that were added to resolve CI failures but weren't actually required for this project. The GitHub Actions now correctly use the project's .rubocop.yml configuration without additional plugin dependencies.
|
45
|
+
|
34
46
|
## Design Decisions
|
35
47
|
|
36
48
|
#### Symbol-Based Dynamic Resolution Architecture
|
@@ -61,21 +73,59 @@ Reorganized test suites to properly reflect the architectural changes. Dynamic c
|
|
61
73
|
|
62
74
|
**Rationale**: Terminal UI layout requires precise coordinate and dimension management. Breaking bounds propagation creates visual artifacts and layout inconsistencies. The solution maintains flexbox semantics while enabling dynamic behavior.
|
63
75
|
|
76
|
+
#### Ruby Version Requirements and Zeitwerk Compatibility
|
77
|
+
**Context**: The framework uses Zeitwerk for autoloading, which has specific Ruby version requirements for optimal functionality and stability.
|
78
|
+
|
79
|
+
**Decision**: Bump minimum Ruby version requirement from 3.1.0 to 3.2.0 across all project configurations.
|
80
|
+
|
81
|
+
**Rationale**: Ruby 3.2+ provides better Zeitwerk integration, improved performance, and access to modern Ruby features that enhance framework capabilities. This decision ensures developers have access to the most stable and feature-complete Ruby environment while maintaining reasonable compatibility with current Ruby versions.
|
82
|
+
|
83
|
+
#### Self-Contained Example Dependencies
|
84
|
+
**Context**: Examples previously relied on development dependencies in the main Gemfile, creating coupling between framework dependencies and example requirements.
|
85
|
+
|
86
|
+
**Decision**: Implement `bundler/inline` with inline gemfiles for all examples, making each example completely self-contained.
|
87
|
+
|
88
|
+
**Rationale**: This architectural pattern eliminates dependency pollution in the main project while ensuring examples remain functional and runnable without project setup. It demonstrates best practices for Ruby gem development and provides better developer experience for users exploring framework capabilities.
|
89
|
+
|
90
|
+
#### Development Dependency Minimization
|
91
|
+
**Context**: The main Gemfile contained development tools (ruby-lsp, tty-box, listen) that aren't essential for framework functionality but were needed for specific development workflows.
|
92
|
+
|
93
|
+
**Decision**: Remove non-essential development dependencies from the main Gemfile and move them to example-specific inline gemfiles where needed.
|
94
|
+
|
95
|
+
**Rationale**: This reduces the installation footprint for users integrating Milktea into their applications and eliminates potential dependency conflicts. Essential testing and code quality tools remain in the main Gemfile, while convenience tools are available where specifically needed.
|
96
|
+
|
64
97
|
## Impact
|
65
98
|
|
66
|
-
These changes significantly enhance the framework's flexibility and developer experience. The dynamic child resolution system enables sophisticated component composition patterns previously impossible, while the enhanced Container system provides CSS-like layout capabilities for terminal interfaces.
|
99
|
+
These changes significantly enhance the framework's flexibility and developer experience while establishing a more robust foundation for long-term development. The dynamic child resolution system enables sophisticated component composition patterns previously impossible, while the enhanced Container system provides CSS-like layout capabilities for terminal interfaces.
|
100
|
+
|
101
|
+
The self-contained example architecture dramatically improves developer onboarding by eliminating setup friction and dependency conflicts. New users can now explore framework capabilities without modifying their development environment or dealing with complex dependency resolution.
|
67
102
|
|
68
|
-
|
103
|
+
The Ruby 3.2+ requirement ensures developers have access to modern language features and optimal Zeitwerk performance, while the streamlined CI pipeline provides faster feedback and more reliable testing across supported Ruby versions.
|
104
|
+
|
105
|
+
Developers can now build more maintainable TUI applications with less boilerplate code, greater architectural flexibility, and confidence in cross-version compatibility. The unified Symbol resolution pattern creates consistency across the framework, reducing learning curve and improving code predictability.
|
69
106
|
|
70
107
|
The improvements particularly benefit complex applications requiring dynamic layouts, state-driven component switching, and sophisticated terminal interface designs. Hot reloading reliability ensures smooth development experience even with complex dynamic component hierarchies.
|
71
108
|
|
72
109
|
## Files Modified
|
73
110
|
|
111
|
+
### Core Framework
|
74
112
|
- `lib/milktea/model.rb` - Added dynamic child resolution framework-wide
|
75
113
|
- `lib/milktea/container.rb` - Added default view implementation, removed Container-specific resolution
|
114
|
+
- `lib/milktea/renderer.rb` - Enhanced cursor management for cleaner terminal experience
|
115
|
+
|
116
|
+
### Testing and Quality
|
76
117
|
- `spec/milktea/model_spec.rb` - Added comprehensive dynamic resolution tests
|
77
118
|
- `spec/milktea/container_spec.rb` - Reorganized tests, removed duplicated dynamic tests
|
78
|
-
-
|
79
|
-
|
80
|
-
|
81
|
-
- `
|
119
|
+
- `.rubocop.yml` - Updated target Ruby version to 3.2, cleaned up plugin configuration
|
120
|
+
|
121
|
+
### Examples and Documentation
|
122
|
+
- `examples/container_layout.rb` - Interactive layout demo with inline dependencies
|
123
|
+
- `examples/container_simple.rb` - Basic layout demonstration with inline dependencies
|
124
|
+
- `examples/hot_reload_demo.rb` - Updated to use inline gemfile with listen dependency
|
125
|
+
- `CLAUDE.md` - Updated Ruby version requirement documentation
|
126
|
+
|
127
|
+
### Project Configuration
|
128
|
+
- `Gemfile` - Removed development dependencies (ruby-lsp, tty-box, listen)
|
129
|
+
- `Gemfile.lock` - Updated dependency tree reflecting removed packages
|
130
|
+
- `milktea.gemspec` - Updated minimum Ruby version to 3.2.0
|
131
|
+
- `.github/workflows/main.yml` - Updated CI matrix for Ruby 3.2, 3.3, 3.4 testing
|
@@ -0,0 +1,118 @@
|
|
1
|
+
# Development Log - 2025-07-06
|
2
|
+
|
3
|
+
## What's New
|
4
|
+
|
5
|
+
#### Message::Tick for Timing-Based UI Updates
|
6
|
+
Introduced a new message type `Message::Tick` that provides timestamp information for time-dependent UI components. This enables developers to build animations, clocks, loading indicators, and other dynamic interfaces that update based on elapsed time. The Tick message includes a timestamp parameter (defaulting to `Time.now`) and integrates seamlessly into the Elm Architecture message flow.
|
7
|
+
|
8
|
+
The implementation automatically generates tick messages in the main event loop at 60 FPS, providing consistent timing information to all components. Unlike `Message::None`, tick messages trigger rendering to ensure time-based components update their display properly. This design enables smooth animations and responsive time-dependent interfaces while maintaining the framework's predictable message handling.
|
9
|
+
|
10
|
+
#### Enhanced Testing Convention Framework
|
11
|
+
Established comprehensive testing guidelines that eliminate anti-patterns and enforce consistent test structure across the codebase. The new conventions prioritize testing public behavior over implementation details, making tests more maintainable and robust against refactoring.
|
12
|
+
|
13
|
+
Key improvements include mandatory use of one-liner `it { ... }` syntax, proper `describe` blocks that reference actual methods, and `context` blocks that always start with "when" for conditional scenarios. These patterns create more readable and maintainable test suites while reducing cognitive overhead for developers working across different components.
|
14
|
+
|
15
|
+
#### Milktea::Text Component with Dual Mode Architecture
|
16
|
+
A comprehensive text display component has been introduced to the Milktea framework, providing developers with sophisticated text rendering capabilities for terminal interfaces. The Text component inherits from Container, enabling it to participate in the flexbox layout system while offering specialized text handling functionality.
|
17
|
+
|
18
|
+
The component supports two distinct operational modes: wrap mode for traditional line-based text display and truncation mode for space-constrained scenarios. This dual approach addresses the fundamental challenge of text display in terminal interfaces where developers need both readable text flow and precise space management.
|
19
|
+
|
20
|
+
#### Intelligent Text Wrapping and Overflow Management
|
21
|
+
The wrap mode leverages the `strings` gem to provide Unicode-aware text wrapping that respects word boundaries and handles complex character sets including emojis and multi-byte characters. When text exceeds the available height, the component intelligently truncates at line boundaries while preserving content readability.
|
22
|
+
|
23
|
+
The implementation handles edge cases in text measurement and display, including proper positioning with terminal cursor controls and accurate bounds calculation for nested layout scenarios. This ensures text components integrate seamlessly with the broader Container layout system.
|
24
|
+
|
25
|
+
## What's Fixed
|
26
|
+
|
27
|
+
#### Testing Anti-Pattern Elimination
|
28
|
+
Systematically removed testing anti-patterns across all spec files, transforming 18+ multi-line tests into the preferred context + one-liner format. Fixed inappropriate use of `send(:private_method)` and `instance_variable_get(:@var)` that tested implementation details rather than public behavior.
|
29
|
+
|
30
|
+
The refactoring maintained 100% test coverage while improving readability and maintainability. Tests now focus on observable outcomes through public APIs, making them more resilient to internal refactoring and clearer about component expectations. This establishes a sustainable pattern for future test development.
|
31
|
+
|
32
|
+
#### Message::Tick Rendering Behavior
|
33
|
+
Corrected the initial design where Tick messages were excluded from triggering renders. The fix ensures that time-dependent components can update their display when tick events occur, enabling proper animation and dynamic UI behavior. This change resolved the fundamental issue that would have prevented time-based interfaces from working correctly.
|
34
|
+
|
35
|
+
The solution involved updating the runtime message handling to treat Tick messages like other render-triggering events while maintaining the optimization that `Message::None` doesn't cause unnecessary redraws. This balanced approach provides the timing functionality developers need while preserving performance for static content.
|
36
|
+
|
37
|
+
#### Text Positioning and Bounds Integration
|
38
|
+
Resolved initial implementation issues where text positioning used hardcoded coordinates instead of respecting the Container bounds system. The fix ensures text components correctly integrate with flexbox layouts and maintain proper positioning when used within complex Container hierarchies.
|
39
|
+
|
40
|
+
The solution involved implementing proper cursor positioning that respects both absolute coordinates (x, y) and relative positioning within allocated Container space. This enables text components to work correctly in nested layout scenarios and dynamic resizing situations.
|
41
|
+
|
42
|
+
#### Terminal Screen Clearing Behavior
|
43
|
+
Fixed the renderer's screen clearing behavior to use `clear_screen_down` instead of `clear_screen`, preventing the terminal from clearing the entire scrollback history. This change improves user experience by preserving terminal history while still properly clearing the application's display area.
|
44
|
+
|
45
|
+
The fix addresses the common terminal application issue where applications aggressively clear the entire screen, causing users to lose their command history and previous terminal output. The new approach only clears from the cursor position downward, maintaining the user's terminal session context while ensuring clean application rendering.
|
46
|
+
|
47
|
+
## Design Decisions
|
48
|
+
|
49
|
+
#### Testing Architecture: Public API Focus
|
50
|
+
**Context**: The codebase had accumulated testing patterns that relied on accessing private methods and instance variables, creating brittle tests coupled to implementation details.
|
51
|
+
|
52
|
+
**Decision**: Mandate testing only through public interfaces and observable behavior, with strict prohibition of `send(:private_method)` and `instance_variable_get(:@var)` patterns.
|
53
|
+
|
54
|
+
**Rationale**: Tests should verify what components do, not how they do it internally. This approach makes tests more maintainable during refactoring, clearer about component contracts, and focused on user-observable behavior. The stricter guidelines prevent future accumulation of implementation-dependent tests while establishing sustainable patterns for the growing codebase.
|
55
|
+
|
56
|
+
#### Message::Tick Rendering Strategy
|
57
|
+
**Context**: Time-based UI components need regular updates to display current state, but the framework optimizes performance by avoiding unnecessary renders.
|
58
|
+
|
59
|
+
**Decision**: Enable Tick messages to trigger rendering while maintaining the optimization that `Message::None` events don't cause redraws.
|
60
|
+
|
61
|
+
**Rationale**: Animations, clocks, and loading indicators require visual updates when time progresses, even if no user interaction occurs. Excluding Tick from rendering would break time-dependent interfaces. The selective approach preserves performance optimizations for static content while enabling dynamic time-based behavior where needed. This balances responsiveness with resource efficiency.
|
62
|
+
|
63
|
+
#### Testing Convention Enforcement
|
64
|
+
**Context**: As the codebase grows, inconsistent testing patterns create maintenance overhead and cognitive load for developers working across different components.
|
65
|
+
|
66
|
+
**Decision**: Enforce standardized test structure with one-liner syntax, method-referencing describe blocks, and "when"-prefixed context blocks.
|
67
|
+
|
68
|
+
**Rationale**: Consistent patterns reduce the mental overhead of understanding tests and make it easier to identify what functionality is being verified. The one-liner preference encourages clear, focused test cases that verify single behaviors. Standardization enables developers to quickly navigate and understand test intent across the entire framework, improving development velocity and reducing onboarding time.
|
69
|
+
|
70
|
+
#### Dual Mode Architecture for Text Display
|
71
|
+
**Context**: Terminal interfaces require different text handling strategies depending on use case - some scenarios need readable text flow while others prioritize space efficiency and content density.
|
72
|
+
|
73
|
+
**Decision**: Implement a mode-based architecture with `wrap: false` (truncation) as default and `wrap: true` for traditional wrapping behavior.
|
74
|
+
|
75
|
+
**Rationale**: The truncation mode addresses the common terminal interface challenge of displaying maximum information in limited space, making it the more universally applicable default. The wrap mode preserves traditional text display expectations for content-focused scenarios. This approach provides developers with explicit control over text behavior while establishing sensible defaults for most TUI applications.
|
76
|
+
|
77
|
+
#### Documentation Integration Strategy
|
78
|
+
**Context**: Testing conventions needed to be preserved and communicated to prevent regression to anti-pattern usage.
|
79
|
+
|
80
|
+
**Decision**: Update CLAUDE.md with comprehensive anti-pattern prevention guidelines and reorganize existing rules for clarity.
|
81
|
+
|
82
|
+
**Rationale**: Embedding conventions in the project documentation ensures they persist beyond the current development session and guide future contributors. The prominent placement of anti-pattern warnings helps developers avoid common mistakes while the reorganized structure makes the guidelines more discoverable and actionable.
|
83
|
+
|
84
|
+
## Impact
|
85
|
+
|
86
|
+
The Message::Tick implementation opens up entirely new categories of TUI applications that Milktea can support, including games, monitoring dashboards, and interactive animations. Time-based interfaces are now first-class citizens in the framework, with built-in support for smooth updates and consistent timing.
|
87
|
+
|
88
|
+
The testing convention improvements establish a sustainable foundation for the project's growth. With clear guidelines preventing anti-patterns and encouraging consistent structure, the codebase can scale without accumulating technical debt in the test suite. Future contributors have clear guidance for writing maintainable tests that focus on behavior rather than implementation.
|
89
|
+
|
90
|
+
The Text component introduction significantly expands Milktea's capabilities for building sophisticated terminal interfaces. Developers can now create information-dense applications with professional text rendering while maintaining the framework's layout system integration and hot reloading capabilities.
|
91
|
+
|
92
|
+
The combined effect of these improvements positions Milktea as a more mature and capable TUI framework. The timing system enables dynamic interfaces, the testing framework ensures long-term maintainability, and the text component provides essential UI building blocks. These foundational improvements support more ambitious application development while maintaining the framework's core strengths.
|
93
|
+
|
94
|
+
## Files Modified
|
95
|
+
|
96
|
+
### Core Framework
|
97
|
+
- `lib/milktea/message.rb` - Added timestamp parameter to Message::Tick for timing information
|
98
|
+
- `lib/milktea/program.rb` - Integrated tick message generation into main event loop
|
99
|
+
- `lib/milktea/runtime.rb` - Simplified side effect handling and enabled Tick rendering
|
100
|
+
- `lib/milktea/text.rb` - Complete Text component implementation with dual-mode architecture
|
101
|
+
- `lib/milktea/renderer.rb` - Fixed screen clearing behavior to preserve terminal history
|
102
|
+
- `milktea.gemspec` - Added strings gem dependency for text manipulation capabilities
|
103
|
+
|
104
|
+
### Testing Framework
|
105
|
+
- `spec/milktea_spec.rb` - Converted multi-line tests to one-liner format
|
106
|
+
- `spec/milktea/application_spec.rb` - Transformed 8 multi-line tests to context + one-liner pattern
|
107
|
+
- `spec/milktea/program_spec.rb` - Fixed 4 multi-line tests and removed anti-patterns
|
108
|
+
- `spec/milktea/renderer_spec.rb` - Converted multi-line test to one-liner
|
109
|
+
- `spec/milktea/runtime_spec.rb` - Fixed instance variable usage and multi-line tests
|
110
|
+
- `spec/milktea/message_spec.rb` - Added comprehensive Message::Tick test coverage
|
111
|
+
|
112
|
+
### Documentation and Examples
|
113
|
+
- `CLAUDE.md` - Enhanced RSpec testing guidelines with anti-pattern prevention
|
114
|
+
- `examples/tick_example.rb` - Demonstration of timing-based animation usage
|
115
|
+
- `examples/text_demo.rb` - Interactive demonstration of text component modes
|
116
|
+
|
117
|
+
### Dependencies
|
118
|
+
- `Gemfile.lock` - Updated with strings gem and its dependencies for Unicode text handling
|
@@ -1,7 +1,14 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
-
require "bundler/
|
4
|
+
require "bundler/inline"
|
5
|
+
|
6
|
+
gemfile do
|
7
|
+
source "https://rubygems.org"
|
8
|
+
gem "milktea", path: "../"
|
9
|
+
gem "tty-box", "~> 0.7.0"
|
10
|
+
end
|
11
|
+
|
5
12
|
require "milktea"
|
6
13
|
require "tty-box"
|
7
14
|
|
@@ -1,7 +1,14 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
-
require "bundler/
|
4
|
+
require "bundler/inline"
|
5
|
+
|
6
|
+
gemfile do
|
7
|
+
source "https://rubygems.org"
|
8
|
+
gem "milktea", path: "../"
|
9
|
+
gem "tty-box", "~> 0.7.0"
|
10
|
+
end
|
11
|
+
|
5
12
|
require "milktea"
|
6
13
|
require "tty-box"
|
7
14
|
|
data/examples/hot_reload_demo.rb
CHANGED
@@ -1,7 +1,14 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
-
require "bundler/
|
4
|
+
require "bundler/inline"
|
5
|
+
|
6
|
+
gemfile do
|
7
|
+
source "https://rubygems.org"
|
8
|
+
gem "milktea", path: "../"
|
9
|
+
gem "listen"
|
10
|
+
end
|
11
|
+
|
5
12
|
require "milktea"
|
6
13
|
|
7
14
|
# Hot Reload Demo using Milktea::Application - Simplified setup
|
@@ -0,0 +1,136 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "bundler/setup"
|
5
|
+
require "milktea"
|
6
|
+
|
7
|
+
# TextDemo model demonstrating Text component usage
|
8
|
+
class TextDemo < Milktea::Container
|
9
|
+
direction :column
|
10
|
+
child :header, flex: 1
|
11
|
+
child :short_text, flex: 2
|
12
|
+
child :long_text, flex: 3
|
13
|
+
child :truncated_text, flex: 2
|
14
|
+
child :unicode_text, flex: 2
|
15
|
+
child :footer, flex: 1
|
16
|
+
|
17
|
+
def header
|
18
|
+
HeaderText
|
19
|
+
end
|
20
|
+
|
21
|
+
def short_text
|
22
|
+
ShortText
|
23
|
+
end
|
24
|
+
|
25
|
+
def long_text
|
26
|
+
LongText
|
27
|
+
end
|
28
|
+
|
29
|
+
def truncated_text
|
30
|
+
TruncatedText
|
31
|
+
end
|
32
|
+
|
33
|
+
def unicode_text
|
34
|
+
UnicodeText
|
35
|
+
end
|
36
|
+
|
37
|
+
def footer
|
38
|
+
FooterText
|
39
|
+
end
|
40
|
+
|
41
|
+
def update(message)
|
42
|
+
case message
|
43
|
+
when Milktea::Message::KeyPress
|
44
|
+
return [self, Milktea::Message::Exit.new] if ["q", "ctrl+c"].include?(message.key)
|
45
|
+
When Milktea::Message::Resize
|
46
|
+
return [with, Milktea::Message::None.new]
|
47
|
+
end
|
48
|
+
|
49
|
+
[self, Milktea::Message::None.new]
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# Header text component
|
54
|
+
class HeaderText < Milktea::Text
|
55
|
+
private
|
56
|
+
|
57
|
+
def default_state
|
58
|
+
{
|
59
|
+
content: "Milktea Text Component Demo\nPress 'q' to quit",
|
60
|
+
wrap: true
|
61
|
+
}
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# Short text that fits within bounds
|
66
|
+
class ShortText < Milktea::Text
|
67
|
+
private
|
68
|
+
|
69
|
+
def default_state
|
70
|
+
{
|
71
|
+
content: "This is a short text that fits comfortably within the bounds. " \
|
72
|
+
"It demonstrates basic text rendering."
|
73
|
+
}
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# Long text that requires wrapping and truncation
|
78
|
+
class LongText < Milktea::Text
|
79
|
+
private
|
80
|
+
|
81
|
+
def default_state
|
82
|
+
{
|
83
|
+
content: "This is a much longer text that will demonstrate the wrapping " \
|
84
|
+
"and truncation capabilities of the Text component. When text " \
|
85
|
+
"is too long to fit within the specified width, it will " \
|
86
|
+
"automatically wrap to the next line. If there are too many " \
|
87
|
+
"lines to fit within the height bounds, the last visible line " \
|
88
|
+
"will be truncated with an ellipsis. This ensures that text " \
|
89
|
+
"always stays within its designated area and doesn't overflow " \
|
90
|
+
"into other components. The wrapping is word-aware, so it won't " \
|
91
|
+
"break words in the middle unless absolutely necessary.",
|
92
|
+
wrap: true
|
93
|
+
}
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
# Truncated text demonstrating the new truncation mode
|
98
|
+
class TruncatedText < Milktea::Text
|
99
|
+
private
|
100
|
+
|
101
|
+
def default_state
|
102
|
+
{
|
103
|
+
content: "This demonstrates the new truncation mode (wrap: false). " \
|
104
|
+
"Newlines are removed and text is truncated to fit within " \
|
105
|
+
"width * height characters. Custom trailing can be set.",
|
106
|
+
trailing: "..."
|
107
|
+
}
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
# Unicode and emoji text
|
112
|
+
class UnicodeText < Milktea::Text
|
113
|
+
private
|
114
|
+
|
115
|
+
def default_state
|
116
|
+
{
|
117
|
+
content: "Unicode support: 你好世界 🌍🌎🌏\n" \
|
118
|
+
"Japanese: ラドクリフ、マラソン五輪代表\n" \
|
119
|
+
"Emoji: 🚀 🎉 🔥 ✨ 💻 📱"
|
120
|
+
}
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
# Footer text
|
125
|
+
class FooterText < Milktea::Text
|
126
|
+
private
|
127
|
+
|
128
|
+
def default_state
|
129
|
+
{ content: "Text components handle wrapping, truncation, and Unicode!" }
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
# Create and run the program
|
134
|
+
model = TextDemo.new
|
135
|
+
program = Milktea::Program.new(model)
|
136
|
+
program.run
|
@@ -0,0 +1,63 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require_relative "../lib/milktea"
|
5
|
+
|
6
|
+
# Example model that uses Tick messages for animations
|
7
|
+
class TickModel < Milktea::Model
|
8
|
+
def view
|
9
|
+
elapsed = last_tick ? Time.now - last_tick : 0
|
10
|
+
dots = "." * ((elapsed * 2).to_i % 4)
|
11
|
+
|
12
|
+
<<~VIEW
|
13
|
+
╭─────────────────────────────────────────────────────────╮
|
14
|
+
│ Tick Example │
|
15
|
+
├─────────────────────────────────────────────────────────┤
|
16
|
+
│ │
|
17
|
+
│ Loading#{dots.ljust(3)} │
|
18
|
+
│ │
|
19
|
+
│ Last tick: #{last_tick&.strftime("%H:%M:%S.%L") || "none"} │
|
20
|
+
│ Elapsed: #{elapsed.round(3)}s │
|
21
|
+
│ │
|
22
|
+
│ Press 'q' to quit │
|
23
|
+
│ │
|
24
|
+
╰─────────────────────────────────────────────────────────╯
|
25
|
+
VIEW
|
26
|
+
end
|
27
|
+
|
28
|
+
def update(message)
|
29
|
+
case message
|
30
|
+
when Milktea::Message::Tick
|
31
|
+
# Track the tick timestamp - this demonstrates that tick messages
|
32
|
+
# provide timing information that models can use for animations
|
33
|
+
[with(last_tick: message.timestamp), Milktea::Message::None.new]
|
34
|
+
when Milktea::Message::KeyPress
|
35
|
+
if message.key == "q"
|
36
|
+
[self, Milktea::Message::Exit.new]
|
37
|
+
else
|
38
|
+
[self, Milktea::Message::None.new]
|
39
|
+
end
|
40
|
+
else
|
41
|
+
[self, Milktea::Message::None.new]
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def default_state
|
48
|
+
{ last_tick: nil }
|
49
|
+
end
|
50
|
+
|
51
|
+
def last_tick
|
52
|
+
state[:last_tick]
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# Configure Milktea
|
57
|
+
Milktea.configure do |config|
|
58
|
+
config.hot_reloading = false
|
59
|
+
end
|
60
|
+
|
61
|
+
# Create and run the tick demonstration
|
62
|
+
program = Milktea::Program.new(TickModel.new)
|
63
|
+
program.run
|
data/lib/milktea/message.rb
CHANGED
@@ -9,8 +9,12 @@ module Milktea
|
|
9
9
|
# Message to exit the program
|
10
10
|
Exit = Data.define
|
11
11
|
|
12
|
-
# Timer tick message
|
13
|
-
Tick = Data.define
|
12
|
+
# Timer tick message with timestamp
|
13
|
+
Tick = Data.define(:timestamp) do
|
14
|
+
def initialize(timestamp: Time.now)
|
15
|
+
super
|
16
|
+
end
|
17
|
+
end
|
14
18
|
|
15
19
|
# Keyboard event message
|
16
20
|
KeyPress = Data.define(:key, :value, :ctrl, :alt, :shift) do
|
data/lib/milktea/program.rb
CHANGED
data/lib/milktea/renderer.rb
CHANGED
@@ -22,13 +22,13 @@ module Milktea
|
|
22
22
|
|
23
23
|
def setup_screen
|
24
24
|
@output.print @cursor.hide
|
25
|
-
@output.print @cursor.
|
25
|
+
@output.print @cursor.clear_screen_down
|
26
26
|
@output.print @cursor.move_to(0, 0)
|
27
27
|
@output.flush
|
28
28
|
end
|
29
29
|
|
30
30
|
def restore_screen
|
31
|
-
@output.print @cursor.
|
31
|
+
@output.print @cursor.clear_screen_down
|
32
32
|
@output.print @cursor.show
|
33
33
|
@output.flush
|
34
34
|
end
|
data/lib/milktea/runtime.rb
CHANGED
@@ -53,18 +53,10 @@ module Milktea
|
|
53
53
|
|
54
54
|
def execute_side_effect(side_effect)
|
55
55
|
case side_effect
|
56
|
-
when Message::None
|
57
|
-
# Do nothing
|
58
56
|
when Message::Exit
|
59
57
|
stop
|
60
58
|
when Message::Batch
|
61
59
|
side_effect.messages.each { |msg| enqueue(msg) }
|
62
|
-
when Message::Reload
|
63
|
-
# Hot reload handled automatically by Zeitwerk
|
64
|
-
# No additional action needed
|
65
|
-
when Message::Resize
|
66
|
-
# Terminal resize detected
|
67
|
-
# No additional action needed at this level
|
68
60
|
end
|
69
61
|
end
|
70
62
|
end
|
data/lib/milktea/text.rb
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "strings"
|
4
|
+
require "tty-cursor"
|
5
|
+
|
6
|
+
module Milktea
|
7
|
+
# Text component for displaying text content with wrapping and truncation
|
8
|
+
class Text < Container
|
9
|
+
def view
|
10
|
+
return "" if content.empty?
|
11
|
+
return render(truncated_lines) if state[:wrap]
|
12
|
+
|
13
|
+
render_truncated
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def render(lines)
|
19
|
+
lines.map.with_index do |line, index|
|
20
|
+
TTY::Cursor.move_to(bounds.x, bounds.y + index) + line
|
21
|
+
end.join
|
22
|
+
end
|
23
|
+
|
24
|
+
def render_truncated
|
25
|
+
cleaned_content = content.gsub("\n", "")
|
26
|
+
max_length = bounds.width * bounds.height
|
27
|
+
truncated_content = Strings.truncate(cleaned_content, max_length, trailing: state[:trailing])
|
28
|
+
|
29
|
+
TTY::Cursor.move_to(bounds.x, bounds.y) + truncated_content
|
30
|
+
end
|
31
|
+
|
32
|
+
def truncated_lines
|
33
|
+
lines = wrap_content
|
34
|
+
return lines unless lines.length > bounds.height
|
35
|
+
|
36
|
+
lines.take(bounds.height)
|
37
|
+
end
|
38
|
+
|
39
|
+
def wrap_content
|
40
|
+
Strings.wrap(content, bounds.width).split("\n")
|
41
|
+
end
|
42
|
+
|
43
|
+
def content
|
44
|
+
state[:content] || ""
|
45
|
+
end
|
46
|
+
|
47
|
+
def default_state
|
48
|
+
{ content: "", wrap: false, trailing: "…" }.freeze
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
data/lib/milktea/version.rb
CHANGED
metadata
CHANGED
@@ -1,15 +1,28 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: milktea
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Aotokitsuruya
|
8
|
-
autorequire:
|
9
8
|
bindir: exe
|
10
9
|
cert_chain: []
|
11
|
-
date:
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
12
11
|
dependencies:
|
12
|
+
- !ruby/object:Gem::Dependency
|
13
|
+
name: strings
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
15
|
+
requirements:
|
16
|
+
- - "~>"
|
17
|
+
- !ruby/object:Gem::Version
|
18
|
+
version: '0.2'
|
19
|
+
type: :runtime
|
20
|
+
prerelease: false
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
22
|
+
requirements:
|
23
|
+
- - "~>"
|
24
|
+
- !ruby/object:Gem::Version
|
25
|
+
version: '0.2'
|
13
26
|
- !ruby/object:Gem::Dependency
|
14
27
|
name: timers
|
15
28
|
requirement: !ruby/object:Gem::Requirement
|
@@ -89,9 +102,11 @@ extra_rdoc_files: []
|
|
89
102
|
files:
|
90
103
|
- ".claude/commands/devlog.md"
|
91
104
|
- ".claude/settings.json"
|
105
|
+
- ".release-please-manifest.json"
|
92
106
|
- ".rspec"
|
93
107
|
- ".rubocop.yml"
|
94
108
|
- ARCHITECTURE.md
|
109
|
+
- CHANGELOG.md
|
95
110
|
- CLAUDE.md
|
96
111
|
- LICENSE.txt
|
97
112
|
- README.md
|
@@ -100,6 +115,7 @@ files:
|
|
100
115
|
- docs/devlog/20250704-2.md
|
101
116
|
- docs/devlog/20250704.md
|
102
117
|
- docs/devlog/20250705.md
|
118
|
+
- docs/devlog/20250706.md
|
103
119
|
- examples/container_layout.rb
|
104
120
|
- examples/container_simple.rb
|
105
121
|
- examples/counter.rb
|
@@ -108,6 +124,8 @@ files:
|
|
108
124
|
- examples/hot_reload_demo/models/demo_model.rb
|
109
125
|
- examples/hot_reload_demo/models/status_model.rb
|
110
126
|
- examples/simple.rb
|
127
|
+
- examples/text_demo.rb
|
128
|
+
- examples/tick_example.rb
|
111
129
|
- lib/milktea.rb
|
112
130
|
- lib/milktea/application.rb
|
113
131
|
- lib/milktea/bounds.rb
|
@@ -119,7 +137,9 @@ files:
|
|
119
137
|
- lib/milktea/program.rb
|
120
138
|
- lib/milktea/renderer.rb
|
121
139
|
- lib/milktea/runtime.rb
|
140
|
+
- lib/milktea/text.rb
|
122
141
|
- lib/milktea/version.rb
|
142
|
+
- release-please-config.json
|
123
143
|
- sig/milktea.rbs
|
124
144
|
homepage: https://github.com/elct9620/milktea
|
125
145
|
licenses:
|
@@ -129,7 +149,6 @@ metadata:
|
|
129
149
|
source_code_uri: https://github.com/elct9620/milktea
|
130
150
|
changelog_uri: https://github.com/elct9620/milktea/blob/main/CHANGELOG.md
|
131
151
|
rubygems_mfa_required: 'true'
|
132
|
-
post_install_message:
|
133
152
|
rdoc_options: []
|
134
153
|
require_paths:
|
135
154
|
- lib
|
@@ -137,15 +156,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
137
156
|
requirements:
|
138
157
|
- - ">="
|
139
158
|
- !ruby/object:Gem::Version
|
140
|
-
version: 3.
|
159
|
+
version: 3.2.0
|
141
160
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
142
161
|
requirements:
|
143
162
|
- - ">="
|
144
163
|
- !ruby/object:Gem::Version
|
145
164
|
version: '0'
|
146
165
|
requirements: []
|
147
|
-
rubygems_version: 3.
|
148
|
-
signing_key:
|
166
|
+
rubygems_version: 3.6.7
|
149
167
|
specification_version: 4
|
150
168
|
summary: The TUI framework for Ruby, inspired by the bubbletea framework for Go.
|
151
169
|
test_files: []
|