anima-core 0.3.0 → 1.0.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.
Files changed (269) hide show
  1. checksums.yaml +4 -4
  2. data/.reek.yml +27 -1
  3. data/CHANGELOG.md +4 -0
  4. data/README.md +219 -25
  5. data/agents/codebase-analyzer.md +88 -0
  6. data/agents/codebase-pattern-finder.md +83 -0
  7. data/agents/documentation-researcher.md +59 -0
  8. data/agents/thoughts-analyzer.md +102 -0
  9. data/agents/web-search-researcher.md +71 -0
  10. data/anima-core.gemspec +3 -0
  11. data/app/channels/session_channel.rb +76 -28
  12. data/app/jobs/agent_request_job.rb +24 -0
  13. data/app/jobs/analytical_brain_job.rb +33 -0
  14. data/app/jobs/count_event_tokens_job.rb +1 -1
  15. data/app/models/concerns/event/broadcasting.rb +20 -2
  16. data/app/models/event.rb +1 -1
  17. data/app/models/goal.rb +91 -0
  18. data/app/models/session.rb +347 -22
  19. data/config/application.rb +2 -0
  20. data/db/migrate/20260314075248_add_subagent_support_to_sessions.rb +6 -0
  21. data/db/migrate/20260314112417_add_granted_tools_to_sessions.rb +5 -0
  22. data/db/migrate/20260314140000_add_name_to_sessions.rb +7 -0
  23. data/db/migrate/20260314150000_add_viewport_event_ids_to_sessions.rb +7 -0
  24. data/db/migrate/20260315100000_add_active_skills_to_sessions.rb +7 -0
  25. data/db/migrate/20260315140843_create_goals.rb +16 -0
  26. data/db/migrate/20260315144837_add_completed_at_to_goals.rb +5 -0
  27. data/db/migrate/20260315191105_add_active_workflow_to_sessions.rb +5 -0
  28. data/lib/agent_loop.rb +65 -9
  29. data/lib/agents/definition.rb +116 -0
  30. data/lib/agents/registry.rb +106 -0
  31. data/lib/analytical_brain/runner.rb +276 -0
  32. data/lib/analytical_brain/tools/activate_skill.rb +52 -0
  33. data/lib/analytical_brain/tools/deactivate_skill.rb +43 -0
  34. data/lib/analytical_brain/tools/deactivate_workflow.rb +34 -0
  35. data/lib/analytical_brain/tools/everything_is_ready.rb +28 -0
  36. data/lib/analytical_brain/tools/finish_goal.rb +62 -0
  37. data/lib/analytical_brain/tools/read_workflow.rb +58 -0
  38. data/lib/analytical_brain/tools/rename_session.rb +63 -0
  39. data/lib/analytical_brain/tools/set_goal.rb +60 -0
  40. data/lib/analytical_brain/tools/update_goal.rb +60 -0
  41. data/lib/analytical_brain.rb +23 -0
  42. data/lib/anima/cli/mcp/secrets.rb +76 -0
  43. data/lib/anima/cli/mcp.rb +197 -0
  44. data/lib/anima/cli.rb +4 -0
  45. data/lib/anima/installer.rb +168 -0
  46. data/lib/anima/settings.rb +226 -0
  47. data/lib/anima/version.rb +1 -1
  48. data/lib/anima.rb +9 -0
  49. data/lib/credential_store.rb +103 -0
  50. data/lib/environment_probe.rb +232 -0
  51. data/lib/llm/client.rb +29 -10
  52. data/lib/mcp/client_manager.rb +86 -0
  53. data/lib/mcp/config.rb +213 -0
  54. data/lib/mcp/health_check.rb +77 -0
  55. data/lib/mcp/secrets.rb +73 -0
  56. data/lib/mcp/stdio_transport.rb +206 -0
  57. data/lib/providers/anthropic.rb +8 -7
  58. data/lib/shell_session.rb +11 -10
  59. data/lib/skills/definition.rb +97 -0
  60. data/lib/skills/registry.rb +105 -0
  61. data/lib/tools/edit.rb +3 -4
  62. data/lib/tools/mcp_tool.rb +114 -0
  63. data/lib/tools/read.rb +15 -16
  64. data/lib/tools/registry.rb +14 -12
  65. data/lib/tools/request_feature.rb +121 -0
  66. data/lib/tools/return_result.rb +81 -0
  67. data/lib/tools/spawn_specialist.rb +109 -0
  68. data/lib/tools/spawn_subagent.rb +111 -0
  69. data/lib/tools/subagent_prompts.rb +12 -0
  70. data/lib/tools/web_get.rb +8 -9
  71. data/lib/tui/app.rb +332 -43
  72. data/lib/tui/message_store.rb +20 -0
  73. data/lib/tui/screens/chat.rb +207 -20
  74. data/lib/workflows/definition.rb +97 -0
  75. data/lib/workflows/registry.rb +89 -0
  76. data/skills/activerecord/SKILL.md +255 -0
  77. data/skills/activerecord/examples/associations/association_extensions.rb +298 -0
  78. data/skills/activerecord/examples/associations/basic_associations.rb +118 -0
  79. data/skills/activerecord/examples/associations/counter_caches.rb +215 -0
  80. data/skills/activerecord/examples/associations/polymorphic_associations.rb +217 -0
  81. data/skills/activerecord/examples/associations/self_referential.rb +302 -0
  82. data/skills/activerecord/examples/associations/through_associations.rb +203 -0
  83. data/skills/activerecord/examples/basics/crud_operations.rb +209 -0
  84. data/skills/activerecord/examples/basics/dirty_tracking.rb +218 -0
  85. data/skills/activerecord/examples/basics/inheritance.rb +377 -0
  86. data/skills/activerecord/examples/basics/type_casting.rb +317 -0
  87. data/skills/activerecord/examples/callbacks/alternatives_to_callbacks.rb +447 -0
  88. data/skills/activerecord/examples/callbacks/conditional_callbacks.rb +353 -0
  89. data/skills/activerecord/examples/callbacks/lifecycle_callbacks.rb +280 -0
  90. data/skills/activerecord/examples/callbacks/transaction_callbacks.rb +340 -0
  91. data/skills/activerecord/examples/migrations/indexes_and_constraints.rb +337 -0
  92. data/skills/activerecord/examples/migrations/reversible_patterns.rb +403 -0
  93. data/skills/activerecord/examples/migrations/safe_patterns.rb +420 -0
  94. data/skills/activerecord/examples/migrations/schema_changes.rb +277 -0
  95. data/skills/activerecord/examples/querying/batch_processing.rb +226 -0
  96. data/skills/activerecord/examples/querying/eager_loading.rb +259 -0
  97. data/skills/activerecord/examples/querying/finder_methods.rb +170 -0
  98. data/skills/activerecord/examples/querying/optimization.rb +275 -0
  99. data/skills/activerecord/examples/querying/scopes.rb +260 -0
  100. data/skills/activerecord/examples/validations/built_in_validators.rb +277 -0
  101. data/skills/activerecord/examples/validations/conditional_validations.rb +288 -0
  102. data/skills/activerecord/examples/validations/custom_validators.rb +381 -0
  103. data/skills/activerecord/examples/validations/database_constraints.rb +432 -0
  104. data/skills/activerecord/examples/validations/validation_contexts.rb +367 -0
  105. data/skills/activerecord/references/associations.md +709 -0
  106. data/skills/activerecord/references/basics.md +622 -0
  107. data/skills/activerecord/references/callbacks.md +738 -0
  108. data/skills/activerecord/references/migrations.md +657 -0
  109. data/skills/activerecord/references/querying.md +655 -0
  110. data/skills/activerecord/references/validations.md +596 -0
  111. data/skills/dragonruby/SKILL.md +250 -0
  112. data/skills/dragonruby/examples/audio/audio_events.rb +55 -0
  113. data/skills/dragonruby/examples/audio/background_music.rb +29 -0
  114. data/skills/dragonruby/examples/audio/crossfade.rb +51 -0
  115. data/skills/dragonruby/examples/audio/music_controls.rb +51 -0
  116. data/skills/dragonruby/examples/audio/sound_effects.rb +30 -0
  117. data/skills/dragonruby/examples/core/coordinate_system.rb +27 -0
  118. data/skills/dragonruby/examples/core/hello_world.rb +24 -0
  119. data/skills/dragonruby/examples/core/labels.rb +22 -0
  120. data/skills/dragonruby/examples/core/sprites.rb +35 -0
  121. data/skills/dragonruby/examples/core/state_management.rb +29 -0
  122. data/skills/dragonruby/examples/distribution/background_pause.rb +42 -0
  123. data/skills/dragonruby/examples/distribution/build_workflow.sh +26 -0
  124. data/skills/dragonruby/examples/distribution/cvars_production.txt +16 -0
  125. data/skills/dragonruby/examples/distribution/game_metadata_hd.txt +23 -0
  126. data/skills/dragonruby/examples/distribution/game_metadata_minimal.txt +9 -0
  127. data/skills/dragonruby/examples/distribution/game_metadata_mobile.txt +31 -0
  128. data/skills/dragonruby/examples/distribution/platform_detection.rb +36 -0
  129. data/skills/dragonruby/examples/distribution/steam_metadata.txt +19 -0
  130. data/skills/dragonruby/examples/entities/collision_detection.rb +43 -0
  131. data/skills/dragonruby/examples/entities/entity_lifecycle.rb +68 -0
  132. data/skills/dragonruby/examples/entities/entity_storage.rb +38 -0
  133. data/skills/dragonruby/examples/entities/factory_methods.rb +45 -0
  134. data/skills/dragonruby/examples/entities/random_spawning.rb +50 -0
  135. data/skills/dragonruby/examples/game-logic/reset_patterns.rb +98 -0
  136. data/skills/dragonruby/examples/game-logic/save_load.rb +101 -0
  137. data/skills/dragonruby/examples/game-logic/scoring.rb +104 -0
  138. data/skills/dragonruby/examples/game-logic/state_transitions.rb +103 -0
  139. data/skills/dragonruby/examples/game-logic/timers.rb +87 -0
  140. data/skills/dragonruby/examples/input/action_triggers.rb +36 -0
  141. data/skills/dragonruby/examples/input/analog_movement.rb +28 -0
  142. data/skills/dragonruby/examples/input/controller_input.rb +28 -0
  143. data/skills/dragonruby/examples/input/directional_input.rb +24 -0
  144. data/skills/dragonruby/examples/input/keyboard_input.rb +28 -0
  145. data/skills/dragonruby/examples/input/mouse_click.rb +26 -0
  146. data/skills/dragonruby/examples/input/movement_with_bounds.rb +22 -0
  147. data/skills/dragonruby/examples/input/normalized_movement.rb +32 -0
  148. data/skills/dragonruby/examples/rendering/frame_animation.rb +32 -0
  149. data/skills/dragonruby/examples/rendering/labels.rb +32 -0
  150. data/skills/dragonruby/examples/rendering/layering.rb +51 -0
  151. data/skills/dragonruby/examples/rendering/solids.rb +61 -0
  152. data/skills/dragonruby/examples/rendering/sprites.rb +33 -0
  153. data/skills/dragonruby/examples/rendering/spritesheet_animation.rb +39 -0
  154. data/skills/dragonruby/examples/scenes/case_dispatch.rb +60 -0
  155. data/skills/dragonruby/examples/scenes/class_based.rb +150 -0
  156. data/skills/dragonruby/examples/scenes/pause_overlay.rb +100 -0
  157. data/skills/dragonruby/examples/scenes/safe_transitions.rb +68 -0
  158. data/skills/dragonruby/examples/scenes/scene_transitions.rb +98 -0
  159. data/skills/dragonruby/examples/scenes/send_dispatch.rb +88 -0
  160. data/skills/dragonruby/references/audio.md +396 -0
  161. data/skills/dragonruby/references/core.md +385 -0
  162. data/skills/dragonruby/references/distribution.md +434 -0
  163. data/skills/dragonruby/references/entities.md +516 -0
  164. data/skills/dragonruby/references/game-logic/persistence.md +386 -0
  165. data/skills/dragonruby/references/game-logic/state.md +389 -0
  166. data/skills/dragonruby/references/input.md +414 -0
  167. data/skills/dragonruby/references/rendering/animation.md +467 -0
  168. data/skills/dragonruby/references/rendering/primitives.md +403 -0
  169. data/skills/dragonruby/references/scenes.md +443 -0
  170. data/skills/draper-decorators/SKILL.md +344 -0
  171. data/skills/draper-decorators/examples/application_decorator.rb +61 -0
  172. data/skills/draper-decorators/examples/decorator_spec.rb +253 -0
  173. data/skills/draper-decorators/examples/model_decorator.rb +152 -0
  174. data/skills/draper-decorators/references/anti-patterns.md +640 -0
  175. data/skills/draper-decorators/references/patterns.md +507 -0
  176. data/skills/draper-decorators/references/testing.md +559 -0
  177. data/skills/gh-issue.md +182 -0
  178. data/skills/mcp-server/SKILL.md +177 -0
  179. data/skills/mcp-server/examples/dynamic_tools.rb +36 -0
  180. data/skills/mcp-server/examples/file_manager_tool.rb +85 -0
  181. data/skills/mcp-server/examples/http_client.rb +48 -0
  182. data/skills/mcp-server/examples/http_server.rb +97 -0
  183. data/skills/mcp-server/examples/rails_integration.rb +88 -0
  184. data/skills/mcp-server/examples/stdio_server.rb +108 -0
  185. data/skills/mcp-server/examples/streaming_client.rb +95 -0
  186. data/skills/mcp-server/references/gotchas.md +183 -0
  187. data/skills/mcp-server/references/prompts.md +98 -0
  188. data/skills/mcp-server/references/resources.md +53 -0
  189. data/skills/mcp-server/references/server.md +140 -0
  190. data/skills/mcp-server/references/tools.md +146 -0
  191. data/skills/mcp-server/references/transport.md +104 -0
  192. data/skills/ratatui-ruby/SKILL.md +315 -0
  193. data/skills/ratatui-ruby/references/core-concepts.md +340 -0
  194. data/skills/ratatui-ruby/references/events.md +387 -0
  195. data/skills/ratatui-ruby/references/frameworks.md +522 -0
  196. data/skills/ratatui-ruby/references/layout.md +423 -0
  197. data/skills/ratatui-ruby/references/styling.md +268 -0
  198. data/skills/ratatui-ruby/references/testing.md +433 -0
  199. data/skills/ratatui-ruby/references/widgets.md +532 -0
  200. data/skills/rspec/SKILL.md +340 -0
  201. data/skills/rspec/examples/core/basic_structure.rb +69 -0
  202. data/skills/rspec/examples/core/configuration.rb +126 -0
  203. data/skills/rspec/examples/core/hooks.rb +126 -0
  204. data/skills/rspec/examples/core/memoized_helpers.rb +139 -0
  205. data/skills/rspec/examples/core/metadata_filtering.rb +144 -0
  206. data/skills/rspec/examples/core/shared_examples.rb +145 -0
  207. data/skills/rspec/examples/factory_bot/associations.rb +314 -0
  208. data/skills/rspec/examples/factory_bot/build_strategies.rb +272 -0
  209. data/skills/rspec/examples/factory_bot/callbacks.rb +320 -0
  210. data/skills/rspec/examples/factory_bot/custom_construction.rb +328 -0
  211. data/skills/rspec/examples/factory_bot/factory_definition.rb +191 -0
  212. data/skills/rspec/examples/factory_bot/inheritance.rb +314 -0
  213. data/skills/rspec/examples/factory_bot/traits.rb +293 -0
  214. data/skills/rspec/examples/factory_bot/transients.rb +229 -0
  215. data/skills/rspec/examples/matchers/change.rb +115 -0
  216. data/skills/rspec/examples/matchers/collections.rb +154 -0
  217. data/skills/rspec/examples/matchers/comparisons.rb +79 -0
  218. data/skills/rspec/examples/matchers/composing.rb +155 -0
  219. data/skills/rspec/examples/matchers/custom_matchers.rb +197 -0
  220. data/skills/rspec/examples/matchers/equality.rb +58 -0
  221. data/skills/rspec/examples/matchers/errors.rb +136 -0
  222. data/skills/rspec/examples/matchers/output.rb +103 -0
  223. data/skills/rspec/examples/matchers/predicates.rb +87 -0
  224. data/skills/rspec/examples/matchers/truthiness.rb +101 -0
  225. data/skills/rspec/examples/matchers/types.rb +82 -0
  226. data/skills/rspec/examples/matchers/yield.rb +147 -0
  227. data/skills/rspec/examples/mocks/any_instance.rb +172 -0
  228. data/skills/rspec/examples/mocks/argument_matchers.rb +206 -0
  229. data/skills/rspec/examples/mocks/constants.rb +177 -0
  230. data/skills/rspec/examples/mocks/doubles.rb +139 -0
  231. data/skills/rspec/examples/mocks/expectations.rb +137 -0
  232. data/skills/rspec/examples/mocks/message_chains.rb +173 -0
  233. data/skills/rspec/examples/mocks/ordering.rb +144 -0
  234. data/skills/rspec/examples/mocks/receive_counts.rb +181 -0
  235. data/skills/rspec/examples/mocks/responses.rb +223 -0
  236. data/skills/rspec/examples/mocks/spies.rb +149 -0
  237. data/skills/rspec/examples/mocks/stubbing.rb +133 -0
  238. data/skills/rspec/examples/rails/channels.rb +250 -0
  239. data/skills/rspec/examples/rails/controller_specs.rb +302 -0
  240. data/skills/rspec/examples/rails/helper_specs.rb +245 -0
  241. data/skills/rspec/examples/rails/job_specs.rb +256 -0
  242. data/skills/rspec/examples/rails/mailer_specs.rb +228 -0
  243. data/skills/rspec/examples/rails/matchers.rb +374 -0
  244. data/skills/rspec/examples/rails/model_specs.rb +193 -0
  245. data/skills/rspec/examples/rails/request_specs.rb +275 -0
  246. data/skills/rspec/examples/rails/routing_specs.rb +276 -0
  247. data/skills/rspec/examples/rails/system_specs.rb +294 -0
  248. data/skills/rspec/examples/rails/transactions.rb +254 -0
  249. data/skills/rspec/examples/rails/view_specs.rb +252 -0
  250. data/skills/rspec/references/core.md +816 -0
  251. data/skills/rspec/references/factory_bot.md +641 -0
  252. data/skills/rspec/references/matchers.md +516 -0
  253. data/skills/rspec/references/mocks.md +381 -0
  254. data/skills/rspec/references/rails.md +528 -0
  255. data/templates/soul.md +40 -0
  256. data/workflows/commit.md +45 -0
  257. data/workflows/create_handoff.md +98 -0
  258. data/workflows/create_note.md +82 -0
  259. data/workflows/create_plan.md +457 -0
  260. data/workflows/decompose_ticket.md +109 -0
  261. data/workflows/feature.md +91 -0
  262. data/workflows/implement_plan.md +87 -0
  263. data/workflows/iterate_plan.md +247 -0
  264. data/workflows/research_codebase.md +210 -0
  265. data/workflows/resume_handoff.md +217 -0
  266. data/workflows/review_pr.md +320 -0
  267. data/workflows/thoughts_init.md +71 -0
  268. data/workflows/validate_plan.md +166 -0
  269. metadata +284 -1
@@ -0,0 +1,423 @@
1
+ # Layout System
2
+
3
+ RatatuiRuby's constraint-based layout divides screen areas dynamically, adapting to terminal resizing for responsive TUI designs.
4
+
5
+ ## Core Components
6
+
7
+ - **Layout** - Divides areas using constraints
8
+ - **Constraint** - Sizing rules for sections
9
+ - **Rect** - Rectangular screen areas
10
+
11
+ ## Layout Split
12
+
13
+ Primary method for dividing areas:
14
+
15
+ ```ruby
16
+ # Using TUI factory (recommended)
17
+ top, bottom = tui.split(
18
+ frame.area,
19
+ direction: :vertical,
20
+ constraints: [
21
+ tui.constraint_percentage(75),
22
+ tui.constraint_percentage(25)
23
+ ]
24
+ )
25
+
26
+ # Full signature
27
+ tui.layout_split(
28
+ area, # Rect to split
29
+ direction: :vertical, # :vertical or :horizontal
30
+ constraints: [], # Array of constraints
31
+ flex: :legacy # Flex algorithm
32
+ )
33
+ ```
34
+
35
+ **Returns:** `Array<Rect>` of computed sections.
36
+
37
+ ## Direction
38
+
39
+ ### Vertical (Top to Bottom)
40
+
41
+ ```ruby
42
+ header, content, footer = tui.split(
43
+ frame.area,
44
+ direction: :vertical,
45
+ constraints: [
46
+ tui.constraint_length(3), # Top header
47
+ tui.constraint_fill(1), # Expanding content
48
+ tui.constraint_length(1) # Bottom status
49
+ ]
50
+ )
51
+ ```
52
+
53
+ ### Horizontal (Left to Right)
54
+
55
+ ```ruby
56
+ sidebar, main = tui.split(
57
+ frame.area,
58
+ direction: :horizontal,
59
+ constraints: [
60
+ tui.constraint_length(20), # Fixed sidebar
61
+ tui.constraint_min(0) # Expanding main
62
+ ]
63
+ )
64
+ ```
65
+
66
+ ## Constraints
67
+
68
+ ### Length (Fixed Size)
69
+
70
+ Exact, immutable size:
71
+
72
+ ```ruby
73
+ tui.constraint_length(10)
74
+ tui.fixed(10) # CSS-style alias
75
+
76
+ # Always 10 cells regardless of available space
77
+ ```
78
+
79
+ **Use:** Fixed headers, footers, sidebars.
80
+
81
+ ### Percentage
82
+
83
+ Proportional share of available space:
84
+
85
+ ```ruby
86
+ tui.constraint_percentage(50)
87
+ tui.percent(50) # CSS-style alias
88
+
89
+ # 50% of available space
90
+ ```
91
+
92
+ **Use:** Proportional layouts, split screens.
93
+
94
+ ### Min (Minimum)
95
+
96
+ At least N cells, grows if space permits:
97
+
98
+ ```ruby
99
+ tui.constraint_min(10)
100
+
101
+ # Enforces minimum, can expand beyond
102
+ ```
103
+
104
+ **Use:** Sections needing minimum space but can expand.
105
+
106
+ ### Max (Maximum)
107
+
108
+ Upper bound on section size:
109
+
110
+ ```ruby
111
+ tui.constraint_max(20)
112
+
113
+ # Never exceeds 20 cells
114
+ ```
115
+
116
+ **Use:** Limiting size while allowing responsiveness.
117
+
118
+ ### Ratio
119
+
120
+ Exact fractional allocation:
121
+
122
+ ```ruby
123
+ tui.constraint_ratio(1, 3) # One-third
124
+
125
+ # Golden ratio
126
+ constraints = [
127
+ tui.constraint_ratio(1, 3),
128
+ tui.constraint_ratio(2, 3)
129
+ ]
130
+ ```
131
+
132
+ **Use:** Mathematical proportions, aspect ratios.
133
+
134
+ ### Fill (Flexible)
135
+
136
+ Distributes remaining space after satisfying strict rules:
137
+
138
+ ```ruby
139
+ tui.constraint_fill(1)
140
+ tui.flex(2) # Flexbox alias
141
+ tui.fr(1) # CSS Grid fraction
142
+
143
+ # Weighted distribution
144
+ constraints = [
145
+ tui.constraint_length(20), # Fixed 20
146
+ tui.constraint_fill(1), # Gets 1x remaining
147
+ tui.constraint_fill(2) # Gets 2x remaining
148
+ ]
149
+ ```
150
+
151
+ **Use:** Flexible sections that grow.
152
+
153
+ ### Batch Creation
154
+
155
+ ```ruby
156
+ # Full path: RatatuiRuby::Layout::Constraint
157
+ Constraint = RatatuiRuby::Layout::Constraint
158
+
159
+ Constraint.from_lengths([10, 20, 10])
160
+ Constraint.from_percentages([25, 50, 25])
161
+ Constraint.from_mins([5, 10, 5])
162
+ Constraint.from_maxes([20, 30, 40])
163
+ Constraint.from_ratios([[1, 4], [2, 4], [1, 4]])
164
+ Constraint.from_fills([1, 2, 1])
165
+ ```
166
+
167
+ ### Polymorphic Factory
168
+
169
+ ```ruby
170
+ tui.constraint(:length, 10)
171
+ tui.constraint(:percentage, 50)
172
+ tui.constraint(:min, 10)
173
+ tui.constraint(:max, 20)
174
+ tui.constraint(:fill, 1)
175
+ tui.constraint(:ratio, 1, 3)
176
+ ```
177
+
178
+ ## Flex Options
179
+
180
+ Controls extra space distribution:
181
+
182
+ | Option | Behavior |
183
+ |--------|----------|
184
+ | `:legacy` | Default |
185
+ | `:start` | Pack at beginning |
186
+ | `:end` | Pack at end |
187
+ | `:center` | Center sections |
188
+ | `:space_between` | Space between sections |
189
+ | `:space_around` | Space around sections |
190
+ | `:space_evenly` | Evenly distribute all space |
191
+
192
+ ```ruby
193
+ tui.split(
194
+ frame.area,
195
+ direction: :vertical,
196
+ constraints: [tui.constraint_length(10)],
197
+ flex: :center
198
+ )
199
+ ```
200
+
201
+ ## Rect Class
202
+
203
+ Encapsulates terminal grid geometry.
204
+
205
+ ### Constructor
206
+
207
+ ```ruby
208
+ rect = tui.rect(x: 10, y: 5, width: 50, height: 15)
209
+ ```
210
+
211
+ ### Properties
212
+
213
+ ```ruby
214
+ rect.x # Column index of top-left
215
+ rect.y # Row index of top-left
216
+ rect.width # Width in characters
217
+ rect.height # Height in rows
218
+ ```
219
+
220
+ ### Geometry Queries
221
+
222
+ ```ruby
223
+ rect.area # width × height
224
+ rect.empty? # True if width or height is zero
225
+ rect.contains?(15, 8) # Point-in-rectangle hit testing
226
+ ```
227
+
228
+ ### Boundary Accessors
229
+
230
+ ```ruby
231
+ rect.left # Left edge x
232
+ rect.right # Right edge x
233
+ rect.top # Top edge y
234
+ rect.bottom # Bottom edge y
235
+ ```
236
+
237
+ ### Transformations
238
+
239
+ ```ruby
240
+ moved = rect.offset(10, 5) # Translate
241
+ resized = rect.resize(new_size) # Change dimensions
242
+ inner = rect.inner(margin) # Shrink by margin
243
+ outer = rect.outer(margin) # Expand by margin
244
+ clamped = rect.clamp(other_rect) # Constrain to bounds
245
+ ```
246
+
247
+ ### Centering
248
+
249
+ ```ruby
250
+ screen = frame.area
251
+
252
+ # Center both axes
253
+ dialog = screen.centered(
254
+ tui.constraint_length(40),
255
+ tui.constraint_length(20)
256
+ )
257
+
258
+ # Single axis
259
+ centered_h = screen.centered_horizontally(tui.constraint_length(40))
260
+ centered_v = screen.centered_vertically(tui.constraint_length(20))
261
+ ```
262
+
263
+ ### Set Operations
264
+
265
+ ```ruby
266
+ overlap = rect1.intersection(rect2) # Returns nil if disjoint
267
+ rect1.intersects?(rect2) # Check overlap
268
+ bounding = rect1.union(rect2) # Smallest enclosing box
269
+ ```
270
+
271
+ ### Iteration
272
+
273
+ ```ruby
274
+ rect.rows { |row_rect| } # Each row (height=1)
275
+ rect.columns { |col_rect| } # Each column (width=1)
276
+ rect.positions { |x, y| } # All cells, row-major
277
+
278
+ # Destructuring
279
+ x, y, width, height = rect
280
+ ```
281
+
282
+ ## Nested Layouts
283
+
284
+ ```ruby
285
+ # Level 1: sidebar and content
286
+ sidebar, content = tui.split(
287
+ frame.area,
288
+ direction: :horizontal,
289
+ constraints: [
290
+ tui.constraint_length(20),
291
+ tui.constraint_min(0)
292
+ ]
293
+ )
294
+
295
+ # Level 2: header and body within content
296
+ header, body = tui.split(
297
+ content,
298
+ direction: :vertical,
299
+ constraints: [
300
+ tui.constraint_length(3),
301
+ tui.constraint_fill(1)
302
+ ]
303
+ )
304
+
305
+ # Level 3: columns within body
306
+ left, right = tui.split(
307
+ body,
308
+ direction: :horizontal,
309
+ constraints: [
310
+ tui.constraint_percentage(50),
311
+ tui.constraint_percentage(50)
312
+ ]
313
+ )
314
+
315
+ # Render to computed areas
316
+ frame.render_widget(sidebar_widget, sidebar)
317
+ frame.render_widget(header_widget, header)
318
+ frame.render_widget(left_widget, left)
319
+ frame.render_widget(right_widget, right)
320
+ ```
321
+
322
+ ## Common Patterns
323
+
324
+ ### Responsive Dashboard
325
+
326
+ ```ruby
327
+ header, main, footer = tui.split(
328
+ frame.area,
329
+ direction: :vertical,
330
+ constraints: [
331
+ tui.constraint_length(1), # Fixed header
332
+ tui.constraint_fill(1), # Expanding main
333
+ tui.constraint_length(1) # Fixed status
334
+ ]
335
+ )
336
+ ```
337
+
338
+ ### Three-Column Layout
339
+
340
+ ```ruby
341
+ sidebar, content, tools = tui.split(
342
+ frame.area,
343
+ direction: :horizontal,
344
+ constraints: [
345
+ tui.constraint_percentage(20),
346
+ tui.constraint_percentage(60),
347
+ tui.constraint_percentage(20)
348
+ ]
349
+ )
350
+ ```
351
+
352
+ ### Centered Modal Dialog
353
+
354
+ ```ruby
355
+ dialog = frame.area.centered(
356
+ tui.constraint_length(60), # 60 chars wide
357
+ tui.constraint_length(20) # 20 rows tall
358
+ )
359
+
360
+ frame.render_widget(modal_widget, dialog)
361
+ ```
362
+
363
+ ### Even Distribution
364
+
365
+ ```ruby
366
+ buttons = tui.split(
367
+ control_area,
368
+ direction: :horizontal,
369
+ constraints: RatatuiRuby::Layout::Constraint.from_fills([1, 1, 1, 1]),
370
+ flex: :space_evenly
371
+ )
372
+ ```
373
+
374
+ ### Cached Layout for Mouse Handling
375
+
376
+ Compute during rendering, reuse for event handling:
377
+
378
+ ```ruby
379
+ class App
380
+ attr_accessor :button_area
381
+
382
+ def draw(frame, tui)
383
+ @button_area = frame.area.centered(
384
+ tui.constraint_length(20),
385
+ tui.constraint_length(3)
386
+ )
387
+ frame.render_widget(button, @button_area)
388
+ end
389
+
390
+ def handle_event(event)
391
+ if event.mouse? && @button_area.contains?(event.x, event.y)
392
+ handle_button_click
393
+ end
394
+ end
395
+ end
396
+ ```
397
+
398
+ ## Factory Methods Summary
399
+
400
+ | Factory | Purpose |
401
+ |---------|---------|
402
+ | `tui.split(area, direction:, constraints:, flex:)` | Split area |
403
+ | `tui.rect(x:, y:, width:, height:)` | Create Rect |
404
+ | `tui.constraint_length(n)` / `tui.fixed(n)` | Fixed size |
405
+ | `tui.constraint_percentage(n)` / `tui.percent(n)` | Proportional |
406
+ | `tui.constraint_min(n)` | Minimum |
407
+ | `tui.constraint_max(n)` | Maximum |
408
+ | `tui.constraint_ratio(n, d)` | Fractional |
409
+ | `tui.constraint_fill(n)` / `tui.flex(n)` / `tui.fr(n)` | Flexible |
410
+ | `tui.constraint(type, ...)` | Polymorphic |
411
+
412
+ ## Best Practices
413
+
414
+ 1. **Use TUI factories** for cleaner code
415
+ 2. **Compute layouts fresh each frame** for responsive resizing
416
+ 3. **Cache Rects** when needed for mouse hit testing
417
+ 4. **Start with vertical splits** for top-level structure
418
+ 5. **Use Fill** for flexible sections that should grow
419
+ 6. **Use Length** for fixed UI elements
420
+ 7. **Use Percentage/Ratio** for proportional layouts
421
+ 8. **Leverage Rect.centered()** for common positioning
422
+ 9. **Apply margins via Block borders** rather than layout level
423
+ 10. **Keep layouts immutable** - create new rather than modify
@@ -0,0 +1,268 @@
1
+ # Styling System
2
+
3
+ RatatuiRuby provides colors, modifiers, and hierarchical text composition for visually appealing terminal interfaces.
4
+
5
+ ## Style Class
6
+
7
+ ### Properties
8
+
9
+ ```ruby
10
+ Style::Style.new(
11
+ fg: nil, # Foreground color
12
+ bg: nil, # Background color
13
+ underline_color: nil, # Underline color
14
+ modifiers: [], # Text effects to apply
15
+ remove_modifiers: [] # Effects to remove from inherited styles
16
+ )
17
+ ```
18
+
19
+ ### Creating Styles
20
+
21
+ ```ruby
22
+ # Full constructor
23
+ Style::Style.new(fg: :red, bg: :white, modifiers: [:bold])
24
+
25
+ # Convenience method
26
+ Style::Style.with(fg: "#ff00ff")
27
+
28
+ # Empty style
29
+ Style::Style.default
30
+ ```
31
+
32
+ ### Factory Method
33
+
34
+ ```ruby
35
+ tui.style(fg: :red, modifiers: [:bold])
36
+ ```
37
+
38
+ ## Color Options
39
+
40
+ ### Named Colors (Symbols)
41
+
42
+ **Standard:** `:black`, `:red`, `:green`, `:yellow`, `:blue`, `:magenta`, `:cyan`, `:white`
43
+
44
+ **Light variants:** `:light_red`, `:light_green`, `:light_yellow`, `:light_blue`, `:light_magenta`, `:light_cyan`
45
+
46
+ **Gray variants:** `:gray`, `:dark_gray`
47
+
48
+ **Special:** `:reset` — Terminal default
49
+
50
+ ```ruby
51
+ tui.style(fg: :cyan, bg: :black)
52
+ ```
53
+
54
+ ### Indexed Colors (0-255)
55
+
56
+ Xterm 256-color palette:
57
+
58
+ ```ruby
59
+ tui.style(fg: 196) # Bright red
60
+ tui.style(bg: 240) # Dark gray
61
+ ```
62
+
63
+ ### True Color (Hex)
64
+
65
+ 24-bit color via `"#RRGGBB"`:
66
+
67
+ ```ruby
68
+ tui.style(fg: "#ff00ff") # Magenta
69
+ tui.style(bg: "#1a1a1a") # Near-black
70
+ ```
71
+
72
+ ## Color Factory Methods
73
+
74
+ Convert colors from design tools:
75
+
76
+ ### Hex Conversion
77
+
78
+ ```ruby
79
+ Color.from_u32(0xFF0000) # => "#ff0000"
80
+ Color.hex(0x00FF00) # => "#00ff00"
81
+ ```
82
+
83
+ ### HSL Conversion
84
+
85
+ ```ruby
86
+ Color.from_hsl(0, 100, 50) # Hue 0-360, Sat 0-100, Light 0-100
87
+ Color.hsl(120, 100, 50) # => "#00ff00" (green)
88
+ ```
89
+
90
+ ### HSLuv (Perceptually Uniform)
91
+
92
+ Equal lightness values appear equally bright:
93
+
94
+ ```ruby
95
+ Color.from_hsluv(12.18, 100, 53.2) # => "#ff0000"
96
+ Color.hsluv(-94.13, 100, 32.3) # => "#0000ff"
97
+ ```
98
+
99
+ ## Text Modifiers
100
+
101
+ ```ruby
102
+ tui.style(modifiers: [:bold, :underlined])
103
+ ```
104
+
105
+ | Modifier | Effect |
106
+ |----------|--------|
107
+ | `:bold` | Bold weight |
108
+ | `:dim` | Reduced brightness |
109
+ | `:italic` | Italicized |
110
+ | `:underlined` | Underline |
111
+ | `:slow_blink` | Slow blink |
112
+ | `:rapid_blink` | Fast blink |
113
+ | `:reversed` | Swap fg/bg |
114
+ | `:hidden` | Concealed text |
115
+ | `:crossed_out` | Line-through |
116
+
117
+ ## Text Composition
118
+
119
+ ### Span (Styled Fragment)
120
+
121
+ ```ruby
122
+ # Create span
123
+ tui.text_span(content: "Bold text", style: tui.style(modifiers: [:bold]))
124
+
125
+ # Raw/unstyled
126
+ Text::Span.raw("Plain text")
127
+
128
+ # Styled shorthand
129
+ Text::Span.styled("Quick", style)
130
+ ```
131
+
132
+ **Methods:**
133
+ - `width()` — Unicode-aware display width
134
+ - `patch_style(style)` — Merge styles
135
+ - `reset_style()` — Remove styling
136
+
137
+ ### Line (Span Collection)
138
+
139
+ ```ruby
140
+ tui.text_line(
141
+ spans: [span1, span2, span3],
142
+ alignment: :center,
143
+ style: base_style
144
+ )
145
+
146
+ # From string
147
+ Text::Line.from_string("Hello")
148
+ ```
149
+
150
+ **Alignment methods:**
151
+ ```ruby
152
+ line.left_aligned
153
+ line.centered
154
+ line.right_aligned
155
+ ```
156
+
157
+ **Methods:**
158
+ - `push_span(span)` — Add span (returns new Line)
159
+ - `patch_style(style)` — Merge across all spans
160
+ - `width()` — Total display width
161
+
162
+ ### Text Width Calculation
163
+
164
+ Unicode-aware measurement:
165
+
166
+ ```ruby
167
+ tui.text_width("Hello") # => 5
168
+ tui.text_width("你好") # => 4 (CJK = 2 cells each)
169
+ tui.text_width("Hello 👍") # => 8 (emoji = 2 cells)
170
+ ```
171
+
172
+ ## Hierarchical Styling
173
+
174
+ ### Widget-Level
175
+
176
+ ```ruby
177
+ tui.block(
178
+ title: "Title",
179
+ title_style: tui.style(fg: :cyan, modifiers: [:bold]),
180
+ border_style: tui.style(fg: :magenta),
181
+ style: tui.style(bg: :black)
182
+ )
183
+ ```
184
+
185
+ ### Line-Level
186
+
187
+ Affects all contained spans:
188
+
189
+ ```ruby
190
+ tui.text_line(
191
+ spans: [span1, span2],
192
+ style: tui.style(fg: :blue)
193
+ )
194
+ ```
195
+
196
+ ### Span-Level
197
+
198
+ Overrides line styles:
199
+
200
+ ```ruby
201
+ tui.text_span(
202
+ content: "Special",
203
+ style: tui.style(fg: :red, modifiers: [:underlined], underline_color: :red)
204
+ )
205
+ ```
206
+
207
+ ### Style Merging
208
+
209
+ `patch_style()` merges without replacing:
210
+
211
+ ```ruby
212
+ base = Style::Style.with(fg: :blue)
213
+ result = span.patch_style(Style::Style.with(modifiers: [:bold]))
214
+ # fg remains :blue, modifiers adds [:bold]
215
+ ```
216
+
217
+ ## Hash-Based Syntax
218
+
219
+ Widgets accept styles as hashes:
220
+
221
+ ```ruby
222
+ tui.paragraph(
223
+ text: "Content",
224
+ style: {fg: "green", modifiers: [:bold]},
225
+ block: tui.block(border_style: {fg: "cyan"})
226
+ )
227
+ ```
228
+
229
+ ## Complete Example
230
+
231
+ ```ruby
232
+ # Create styled spans
233
+ normal = tui.text_span(content: "Normal, ")
234
+ bold = tui.text_span(
235
+ content: "Bold",
236
+ style: tui.style(modifiers: [:bold])
237
+ )
238
+ colored = tui.text_span(
239
+ content: " and colored",
240
+ style: tui.style(fg: :green, modifiers: [:italic])
241
+ )
242
+
243
+ # Compose into centered line
244
+ line = tui.text_line(spans: [normal, bold, colored]).centered
245
+
246
+ # Use in widget
247
+ tui.paragraph(
248
+ text: [line],
249
+ block: tui.block(
250
+ title: "Styled Text",
251
+ title_style: tui.style(fg: :cyan, modifiers: [:bold]),
252
+ border_style: tui.style(fg: :magenta),
253
+ borders: [:all]
254
+ )
255
+ )
256
+ ```
257
+
258
+ ## Quick Reference
259
+
260
+ | Factory | Creates |
261
+ |---------|---------|
262
+ | `tui.style(fg:, bg:, modifiers:)` | Style object |
263
+ | `tui.text_span(content:, style:)` | Styled text fragment |
264
+ | `tui.text_line(spans:, alignment:)` | Line of spans |
265
+ | `tui.text_width(string)` | Display width |
266
+ | `Color.hex(0xRRGGBB)` | Hex color string |
267
+ | `Color.hsl(h, s, l)` | HSL to hex |
268
+ | `Color.hsluv(h, s, l)` | Perceptually uniform HSL |