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,522 @@
1
+ # Frameworks: Tea and Kit
2
+
3
+ RatatuiRuby ecosystem includes two architectural frameworks: **Tea** (MVU/functional) and **Kit** (component-based/OOP). Both build on the core rendering engine.
4
+
5
+ > **Note:** These frameworks are **separate gems**, not part of the core `ratatui_ruby` gem.
6
+
7
+ ## Tea: Model-View-Update
8
+
9
+ Functional architecture emphasizing immutability and pure functions. Also called The Elm Architecture.
10
+
11
+ **Status:** v0.2.0 Pre-Release (ALPHA)
12
+
13
+ **Installation:** `gem install ratatui_ruby-tea` (provides `RatatuiRuby::TEA`)
14
+
15
+ **Repository:** https://git.sr.ht/~kerrick/ratatui_ruby-tea
16
+
17
+ ### Core Principle
18
+
19
+ "View as a Function of State" — UI is a pure function of an immutable model. Given the same state, rendering is identical.
20
+
21
+ ### Architecture Components
22
+
23
+ #### Model: Immutable State
24
+
25
+ ```ruby
26
+ Model = Data.define(:text, :count, :files, :error)
27
+ ```
28
+
29
+ All state lives in a single frozen object. Updates create new instances.
30
+
31
+ #### Init: Initialization
32
+
33
+ ```ruby
34
+ Init = -> do
35
+ Model.new(
36
+ text: "Hello! Press 'q' to quit.",
37
+ count: 0,
38
+ files: [],
39
+ error: nil
40
+ )
41
+ end
42
+ ```
43
+
44
+ With initial command:
45
+
46
+ ```ruby
47
+ Init = -> do
48
+ model = Model.new(text: "Loading...", files: [])
49
+ command = RatatuiRuby::Tea::Command.system('ls -la', :got_files)
50
+ [model, command]
51
+ end
52
+ ```
53
+
54
+ #### View: Pure Rendering
55
+
56
+ ```ruby
57
+ View = -> (model, tui) do
58
+ tui.paragraph(
59
+ text: model.text,
60
+ alignment: :center,
61
+ block: tui.block(
62
+ title: "My App",
63
+ borders: [:all],
64
+ border_style: {fg: "cyan"}
65
+ )
66
+ )
67
+ end
68
+ ```
69
+
70
+ Views are pure functions — no side effects or state mutation.
71
+
72
+ #### Update: Message Handler
73
+
74
+ ```ruby
75
+ Update = -> (msg, model) do
76
+ case msg
77
+ in [:got_files, {stdout:, status: 0}]
78
+ [model.with(files: stdout.lines), nil]
79
+
80
+ in [:got_files, {stderr:, status:}]
81
+ [model.with(error: "Exit #{status}: #{stderr}"), nil]
82
+
83
+ in {key: 'q'} | {key: 'ctrl_c'}
84
+ RatatuiRuby::Tea::Command.exit
85
+
86
+ in {key: 'r'}
87
+ [model, RatatuiRuby::Tea::Command.system('ls -la', :got_files)]
88
+
89
+ else
90
+ [model, nil]
91
+ end
92
+ end
93
+ ```
94
+
95
+ Returns:
96
+ - `[new_model, command]` — State change with side effect
97
+ - `model` — No state change
98
+ - `command` — Side effect without state change
99
+
100
+ ### Command System
101
+
102
+ Commands execute off the main thread, producing messages when complete.
103
+
104
+ #### Built-In Commands
105
+
106
+ ```ruby
107
+ # Shell execution
108
+ RatatuiRuby::Tea::Command.system('git status', :git_result)
109
+
110
+ # One-shot timer
111
+ RatatuiRuby::Tea::Command.wait(5, :timeout)
112
+
113
+ # Recurring timer
114
+ RatatuiRuby::Tea::Command.tick(1.0, :clock_tick)
115
+
116
+ # HTTP request
117
+ RatatuiRuby::Tea::Command.http(:get, 'https://api.example.com', :got_data)
118
+
119
+ # Parallel execution
120
+ RatatuiRuby::Tea::Command.batch([
121
+ RatatuiRuby::Tea::Command.system('git status', :status),
122
+ RatatuiRuby::Tea::Command.http(:get, 'https://api.example.com', :data)
123
+ ])
124
+
125
+ # Sequential execution
126
+ RatatuiRuby::Tea::Command.sequence([
127
+ RatatuiRuby::Tea::Command.system('npm install', :install_done),
128
+ RatatuiRuby::Tea::Command.system('npm test', :test_done)
129
+ ])
130
+
131
+ # Exit application
132
+ RatatuiRuby::Tea::Command.exit
133
+ ```
134
+
135
+ ### Message Handling Pattern
136
+
137
+ ```ruby
138
+ def update(msg, model)
139
+ case msg
140
+ in [:got_files, {stdout:, status: 0}]
141
+ files = stdout.lines.map(&:strip)
142
+ [model.with(files:, loading: false), nil]
143
+
144
+ in [:got_files, {stderr:, status:}]
145
+ [model.with(error: "Failed: #{stderr}", loading: false), nil]
146
+
147
+ in [:clock_tick]
148
+ new_model = model.with(time: Time.now.strftime("%H:%M:%S"))
149
+ # Re-dispatch to continue subscription
150
+ [new_model, RatatuiRuby::Tea::Command.tick(1.0, :clock_tick)]
151
+
152
+ in {key: 'r'}
153
+ [model.with(loading: true), RatatuiRuby::Tea::Command.system('ls', :got_files)]
154
+
155
+ else
156
+ [model, nil]
157
+ end
158
+ end
159
+ ```
160
+
161
+ ### Running Tea Application
162
+
163
+ ```ruby
164
+ RatatuiRuby::TEA.run(
165
+ model: Init.call,
166
+ update: Update,
167
+ view: View
168
+ )
169
+ ```
170
+
171
+ ### Fractal Architecture (Large Apps)
172
+
173
+ Decompose into **bags** — modules with Model, INITIAL, UPDATE, VIEW:
174
+
175
+ ```ruby
176
+ module Counter
177
+ Model = Data.define(:count)
178
+ INITIAL = Model.new(count: 0)
179
+
180
+ UPDATE = -> (msg, model) do
181
+ case msg
182
+ in :increment
183
+ [model.with(count: model.count + 1), nil]
184
+ in :decrement
185
+ [model.with(count: model.count - 1), nil]
186
+ else
187
+ [model, nil]
188
+ end
189
+ end
190
+
191
+ VIEW = -> (model, tui) do
192
+ tui.paragraph(text: "Count: #{model.count}")
193
+ end
194
+ end
195
+
196
+ # Parent integrates child bag
197
+ ParentUpdate = -> (msg, model) do
198
+ case msg
199
+ in [:counter, counter_msg]
200
+ new_counter, cmd = Counter::UPDATE.call(counter_msg, model.counter)
201
+ mapped_cmd = cmd&.map { |m| [:counter, m] }
202
+ [model.with(counter: new_counter), mapped_cmd]
203
+ end
204
+ end
205
+ ```
206
+
207
+ ---
208
+
209
+ ## Kit: Component-Based Architecture
210
+
211
+ Retained-mode components owning state and handling events. Similar to React class components, Vue Options API, Qt widgets.
212
+
213
+ **Status:** Coming Soon (design phase)
214
+
215
+ ### Core Principle
216
+
217
+ "Encapsulated State" — Components own UI state independently, persisting between frames.
218
+
219
+ ### Component Structure
220
+
221
+ ```ruby
222
+ class MyButton
223
+ include Kit::Component
224
+
225
+ def initialize(label:)
226
+ @label = label
227
+ @click_count = 0
228
+ end
229
+
230
+ def mount
231
+ # Called once when entering tree
232
+ @mounted_at = Time.now
233
+ end
234
+
235
+ def render(frame, area)
236
+ frame.render_widget(
237
+ tui.paragraph(text: @label, style: current_style),
238
+ area
239
+ )
240
+ end
241
+
242
+ def handle_event(event)
243
+ return unless event.key? && event.code == "enter"
244
+ @click_count += 1
245
+ :consumed # Stops propagation
246
+ end
247
+ end
248
+ ```
249
+
250
+ ### Component Mixins
251
+
252
+ | Mixin | Purpose |
253
+ |-------|---------|
254
+ | `Kit::KeyboardInteractive` | `focusable?`, `focus_boundary?`, `tab_index` |
255
+ | `Kit::MouseInteractive` | `area`, `contains_point?(x, y)` |
256
+ | `Kit::Lifecycle` | Mount/unmount hooks |
257
+ | `Kit::Visual` | `tui` accessor |
258
+ | `Kit::Stateful` | `state`, `is_focused?`, `hovered?`, `pressed?`, `disabled?` |
259
+ | `Kit::Component` | All mixins combined |
260
+
261
+ ### State Management
262
+
263
+ ```ruby
264
+ class TextInput
265
+ include Kit::Component
266
+
267
+ def initialize
268
+ @buffer = ""
269
+ @cursor_pos = 0
270
+ end
271
+
272
+ def handle_event(event)
273
+ case event
274
+ when {key: /^[a-zA-Z0-9]$/}
275
+ @buffer.insert(@cursor_pos, event.key)
276
+ @cursor_pos += 1
277
+ :consumed
278
+
279
+ when {key: "backspace"}
280
+ return if @cursor_pos.zero?
281
+ @buffer.slice!(@cursor_pos - 1)
282
+ @cursor_pos -= 1
283
+ :consumed
284
+
285
+ when {key: "left"}
286
+ @cursor_pos = [@cursor_pos - 1, 0].max
287
+ :consumed
288
+
289
+ else
290
+ nil # Propagate
291
+ end
292
+ end
293
+
294
+ def render(frame, area)
295
+ display = @buffer.dup.insert(@cursor_pos, "│")
296
+ frame.render_widget(tui.paragraph(text: display), area)
297
+ end
298
+ end
299
+ ```
300
+
301
+ ### Declarative Styling
302
+
303
+ ```ruby
304
+ class MyButton
305
+ include Kit::Component
306
+
307
+ styles do
308
+ state :focused, fg: :yellow, bold: true
309
+ state :hovered, fg: :blue
310
+ state :pressed, bg: :white, fg: :black
311
+ state :normal, fg: :white
312
+ end
313
+
314
+ def render(frame, area)
315
+ frame.render_widget(
316
+ tui.paragraph(text: @label, style: current_style),
317
+ area
318
+ )
319
+ end
320
+ end
321
+ ```
322
+
323
+ `current_style` automatically resolves based on interaction state.
324
+
325
+ ### Focus Management
326
+
327
+ ```ruby
328
+ # Programmatic focus
329
+ Kit.focus.set(button)
330
+ Kit.focus.next # Tab
331
+ Kit.focus.prev # Shift+Tab
332
+ Kit.focus.blur
333
+
334
+ # Focus boundaries (modals)
335
+ Kit.focus.enter_boundary(modal)
336
+ Kit.focus.exit_boundary
337
+ ```
338
+
339
+ Component focus control:
340
+
341
+ ```ruby
342
+ def focusable? = true
343
+ def focus_boundary? = false
344
+ def tab_index = 0 # Positive: explicit order, 0: tree order, -1: skip
345
+ ```
346
+
347
+ ### Event Propagation
348
+
349
+ Return values control propagation:
350
+ - `nil` / `false` — Unhandled, propagate to parent
351
+ - Truthy (`:consumed`, `:submitted`) — Handled, stop propagation
352
+
353
+ ```ruby
354
+ def handle_event(event)
355
+ # Delegate to focused child first
356
+ result = @child.handle_event(event) if @child.is_focused?
357
+ return result if result
358
+
359
+ # Handle component-level events
360
+ case event
361
+ when {key: "enter"}
362
+ submit_form
363
+ :submitted
364
+ else
365
+ nil # Propagate
366
+ end
367
+ end
368
+ ```
369
+
370
+ ---
371
+
372
+ ## Integration: Adapter Pattern
373
+
374
+ Reuse Tea views in Kit components:
375
+
376
+ ```ruby
377
+ # Pure Tea view
378
+ TeaView = -> (model, tui) do
379
+ tui.paragraph(text: "Count: #{model.count}")
380
+ end
381
+
382
+ # Kit component adapter
383
+ class DashboardWidget
384
+ include Kit::Component
385
+
386
+ def initialize(record)
387
+ @record = record # Mutable
388
+ end
389
+
390
+ def render(frame, area)
391
+ # Convert to immutable model
392
+ tea_model = Data.define(:count).new(count: @record.count)
393
+ widget = TeaView.call(tea_model, tui)
394
+ frame.render_widget(widget, area)
395
+ end
396
+
397
+ def handle_event(event)
398
+ case event
399
+ when {key: "+"}
400
+ @record.update!(count: @record.count + 1)
401
+ :consumed
402
+ end
403
+ end
404
+ end
405
+ ```
406
+
407
+ ---
408
+
409
+ ## When to Choose Each
410
+
411
+ ### Choose Tea When:
412
+
413
+ - State predictability matters (dashboards, installers)
414
+ - Extensive testing required (pure functions are trivially testable)
415
+ - State reproducibility critical (time-travel debugging)
416
+ - Functional programming preferred
417
+ - Single source of truth simplifies reasoning
418
+
419
+ ### Choose Kit When:
420
+
421
+ - Component reusability prioritized (shared libraries)
422
+ - Complex multi-panel interfaces (each panel owns state)
423
+ - Rich interactive components (inputs, forms)
424
+ - Object-oriented programming preferred
425
+ - Team distributes component ownership
426
+
427
+ ### Decision Matrix
428
+
429
+ | Criterion | Tea | Kit |
430
+ |-----------|---------|-----|
431
+ | State predictability | Excellent | Good |
432
+ | Testability | Exceptional | Good |
433
+ | Component reuse | Limited | Excellent |
434
+ | Complex interactions | Harder | Natural |
435
+ | Learning curve | Steeper | Shallower |
436
+ | Code volume | Larger | Smaller |
437
+ | Debugging | Time-travel | Standard |
438
+
439
+ ---
440
+
441
+ ## Comparison with Raw RatatuiRuby.run
442
+
443
+ ```ruby
444
+ # Raw approach
445
+ RatatuiRuby.run do |tui|
446
+ loop do
447
+ tui.draw { |frame| frame.render_widget(widget, frame.area) }
448
+ break if tui.poll_event.ctrl_c?
449
+ end
450
+ end
451
+ ```
452
+
453
+ **Raw approach:**
454
+ - Maximum control
455
+ - Minimal abstraction
456
+ - Best for simple scripts and prototypes
457
+
458
+ **Tea:**
459
+ - Declarative MVU
460
+ - Automatic re-rendering
461
+ - Command system for side effects
462
+
463
+ **Kit:**
464
+ - Stateful components
465
+ - Focus/hover tracking
466
+ - Event propagation system
467
+
468
+ Choose raw for prototypes. Use Tea or Kit for production applications.
469
+
470
+ ---
471
+
472
+ ## Complete Tea Example
473
+
474
+ > Requires the `ratatui_ruby-tea` gem.
475
+
476
+ ```ruby
477
+ require "ratatui_ruby"
478
+ require "ratatui_ruby/tea"
479
+
480
+ Model = Data.define(:items, :selected, :loading)
481
+
482
+ Init = -> do
483
+ model = Model.new(items: [], selected: 0, loading: true)
484
+ [model, RatatuiRuby::Tea::Command.system('ls', :got_files)]
485
+ end
486
+
487
+ View = -> (model, tui) do
488
+ if model.loading
489
+ tui.paragraph(text: "Loading...")
490
+ else
491
+ tui.list(
492
+ items: model.items,
493
+ block: tui.block(title: "Files", borders: [:all]),
494
+ highlight_style: {fg: "yellow", bold: true}
495
+ )
496
+ end
497
+ end
498
+
499
+ Update = -> (msg, model) do
500
+ case msg
501
+ in [:got_files, {stdout:, status: 0}]
502
+ items = stdout.lines.map(&:strip)
503
+ [model.with(items:, loading: false), nil]
504
+
505
+ in {key: 'j'} | {key: 'down'}
506
+ new_idx = [model.selected + 1, model.items.length - 1].min
507
+ [model.with(selected: new_idx), nil]
508
+
509
+ in {key: 'k'} | {key: 'up'}
510
+ new_idx = [model.selected - 1, 0].max
511
+ [model.with(selected: new_idx), nil]
512
+
513
+ in {key: 'q'} | {key: 'ctrl_c'}
514
+ RatatuiRuby::Tea::Command.exit
515
+
516
+ else
517
+ [model, nil]
518
+ end
519
+ end
520
+
521
+ RatatuiRuby::TEA.run(model: Init.call, update: Update, view: View)
522
+ ```