ratatui_ruby 0.7.1 → 0.7.2

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 (283) hide show
  1. checksums.yaml +4 -4
  2. data/.builds/ruby-3.2.yml +1 -1
  3. data/.builds/ruby-3.3.yml +1 -1
  4. data/.builds/ruby-3.4.yml +1 -1
  5. data/.builds/ruby-4.0.0.yml +1 -1
  6. data/AGENTS.md +8 -1
  7. data/CHANGELOG.md +21 -0
  8. data/README.md +5 -5
  9. data/Rakefile +1 -1
  10. data/doc/{application_architecture.md → concepts/application_architecture.md} +30 -0
  11. data/doc/{event_handling.md → concepts/event_handling.md} +1 -1
  12. data/doc/contributors/auditing/parity.md +233 -0
  13. data/doc/contributors/developing_examples.md +3 -3
  14. data/doc/contributors/v1.0.0_blockers.md +8 -8
  15. data/doc/{quickstart.md → getting_started/quickstart.md} +26 -26
  16. data/doc/{why.md → getting_started/why.md} +1 -1
  17. data/doc/index.md +23 -9
  18. data/doc/{terminal_limitations.md → troubleshooting/terminal_limitations.md} +33 -0
  19. data/examples/app_all_events/README.md +1 -0
  20. data/examples/app_all_events/app.rb +2 -0
  21. data/examples/app_all_events/model/app_model.rb +2 -0
  22. data/examples/app_all_events/model/event_color_cycle.rb +2 -0
  23. data/examples/app_all_events/model/event_entry.rb +2 -0
  24. data/examples/app_all_events/model/msg.rb +2 -0
  25. data/examples/app_all_events/model/timestamp.rb +2 -0
  26. data/examples/app_all_events/update.rb +2 -0
  27. data/examples/app_all_events/view/app_view.rb +2 -0
  28. data/examples/app_all_events/view/controls_view.rb +2 -0
  29. data/examples/app_all_events/view/counts_view.rb +2 -0
  30. data/examples/app_all_events/view/live_view.rb +2 -0
  31. data/examples/app_all_events/view/log_view.rb +2 -0
  32. data/examples/app_all_events/view.rb +2 -0
  33. data/examples/app_color_picker/README.md +2 -0
  34. data/examples/app_color_picker/app.rb +2 -0
  35. data/examples/app_color_picker/clipboard.rb +2 -0
  36. data/examples/app_color_picker/color.rb +2 -0
  37. data/examples/app_color_picker/controls.rb +2 -0
  38. data/examples/app_color_picker/copy_dialog.rb +2 -0
  39. data/examples/app_color_picker/export_pane.rb +2 -0
  40. data/examples/app_color_picker/harmony.rb +2 -0
  41. data/examples/app_color_picker/input.rb +2 -0
  42. data/examples/app_color_picker/main_container.rb +2 -0
  43. data/examples/app_color_picker/palette.rb +2 -0
  44. data/examples/app_login_form/README.md +3 -0
  45. data/examples/app_login_form/app.rb +2 -0
  46. data/examples/app_stateful_interaction/README.md +2 -0
  47. data/examples/app_stateful_interaction/app.rb +2 -0
  48. data/examples/timeout_demo.rb +2 -0
  49. data/examples/verify_quickstart_dsl/README.md +2 -2
  50. data/examples/verify_quickstart_dsl/app.rb +2 -0
  51. data/examples/verify_quickstart_layout/README.md +2 -2
  52. data/examples/verify_quickstart_layout/app.rb +2 -0
  53. data/examples/verify_quickstart_lifecycle/README.md +2 -2
  54. data/examples/verify_quickstart_lifecycle/app.rb +2 -0
  55. data/examples/verify_readme_usage/app.rb +2 -0
  56. data/examples/{widget_barchart_demo → widget_barchart}/README.md +5 -3
  57. data/examples/{widget_barchart_demo → widget_barchart}/app.rb +7 -5
  58. data/examples/{widget_block_demo → widget_block}/README.md +5 -3
  59. data/examples/{widget_block_demo → widget_block}/app.rb +6 -4
  60. data/examples/{widget_box_demo → widget_box}/README.md +7 -4
  61. data/examples/{widget_box_demo → widget_box}/app.rb +7 -5
  62. data/examples/{widget_calendar_demo → widget_calendar}/README.md +6 -3
  63. data/examples/{widget_calendar_demo → widget_calendar}/app.rb +6 -4
  64. data/examples/{widget_canvas_demo → widget_canvas}/README.md +2 -2
  65. data/examples/{widget_canvas_demo → widget_canvas}/app.rb +6 -4
  66. data/examples/{widget_cell_demo → widget_cell}/README.md +6 -3
  67. data/examples/{widget_cell_demo → widget_cell}/app.rb +7 -5
  68. data/examples/{widget_center_demo → widget_center}/README.md +2 -2
  69. data/examples/{widget_center_demo → widget_center}/app.rb +6 -4
  70. data/examples/{widget_chart_demo → widget_chart}/README.md +7 -4
  71. data/examples/{widget_chart_demo → widget_chart}/app.rb +7 -5
  72. data/examples/{widget_gauge_demo → widget_gauge}/README.md +6 -3
  73. data/examples/{widget_gauge_demo → widget_gauge}/app.rb +7 -5
  74. data/examples/widget_layout_split/README.md +5 -2
  75. data/examples/widget_layout_split/app.rb +3 -1
  76. data/examples/{widget_line_gauge_demo → widget_line_gauge}/README.md +6 -3
  77. data/examples/{widget_line_gauge_demo → widget_line_gauge}/app.rb +7 -5
  78. data/examples/{widget_list_demo → widget_list}/README.md +7 -4
  79. data/examples/{widget_list_demo → widget_list}/app.rb +7 -5
  80. data/examples/{widget_map_demo → widget_map}/README.md +7 -4
  81. data/examples/{widget_map_demo → widget_map}/app.rb +4 -2
  82. data/examples/{widget_overlay_demo → widget_overlay}/README.md +6 -3
  83. data/examples/{widget_overlay_demo → widget_overlay}/app.rb +5 -3
  84. data/examples/{widget_popup_demo → widget_popup}/README.md +7 -4
  85. data/examples/{widget_popup_demo → widget_popup}/app.rb +6 -4
  86. data/examples/{widget_ratatui_logo_demo → widget_ratatui_logo}/README.md +6 -3
  87. data/examples/{widget_ratatui_logo_demo → widget_ratatui_logo}/app.rb +8 -6
  88. data/examples/{widget_ratatui_mascot_demo → widget_ratatui_mascot}/README.md +6 -3
  89. data/examples/{widget_ratatui_mascot_demo → widget_ratatui_mascot}/app.rb +6 -4
  90. data/examples/widget_rect/README.md +5 -2
  91. data/examples/widget_rect/app.rb +2 -0
  92. data/examples/widget_render/README.md +4 -1
  93. data/examples/widget_render/app.rb +2 -0
  94. data/examples/widget_rich_text/README.md +4 -1
  95. data/examples/widget_rich_text/app.rb +2 -0
  96. data/examples/widget_scroll_text/README.md +4 -1
  97. data/examples/widget_scroll_text/app.rb +3 -1
  98. data/examples/{widget_scrollbar_demo → widget_scrollbar}/README.md +7 -4
  99. data/examples/{widget_scrollbar_demo → widget_scrollbar}/app.rb +6 -4
  100. data/examples/{widget_sparkline_demo → widget_sparkline}/README.md +6 -3
  101. data/examples/{widget_sparkline_demo → widget_sparkline}/app.rb +7 -5
  102. data/examples/widget_style_colors/README.md +4 -1
  103. data/examples/widget_style_colors/app.rb +2 -0
  104. data/examples/{widget_table_demo → widget_table}/README.md +7 -4
  105. data/examples/{widget_table_demo → widget_table}/app.rb +4 -2
  106. data/examples/{widget_tabs_demo → widget_tabs}/README.md +6 -3
  107. data/examples/{widget_tabs_demo → widget_tabs}/app.rb +7 -5
  108. data/examples/widget_text_width/README.md +5 -2
  109. data/examples/widget_text_width/app.rb +2 -0
  110. data/exe/.gitkeep +0 -0
  111. data/ext/ratatui_ruby/Cargo.lock +1 -1
  112. data/ext/ratatui_ruby/Cargo.toml +1 -1
  113. data/ext/ratatui_ruby/extconf.rb +2 -0
  114. data/ext/ratatui_ruby/src/widgets/barchart.rs +8 -6
  115. data/ext/ratatui_ruby/src/widgets/chart.rs +26 -4
  116. data/ext/ratatui_ruby/src/widgets/table.rs +13 -5
  117. data/ext/ratatui_ruby/src/widgets/tabs.rs +49 -9
  118. data/lib/ratatui_ruby/buffer/cell.rb +2 -0
  119. data/lib/ratatui_ruby/buffer.rb +2 -0
  120. data/lib/ratatui_ruby/cell.rb +2 -0
  121. data/lib/ratatui_ruby/event/focus_gained.rb +2 -0
  122. data/lib/ratatui_ruby/event/focus_lost.rb +2 -0
  123. data/lib/ratatui_ruby/event/key/character.rb +2 -0
  124. data/lib/ratatui_ruby/event/key/media.rb +2 -0
  125. data/lib/ratatui_ruby/event/key/modifier.rb +2 -0
  126. data/lib/ratatui_ruby/event/key/navigation.rb +2 -0
  127. data/lib/ratatui_ruby/event/key/system.rb +2 -0
  128. data/lib/ratatui_ruby/event/key.rb +2 -0
  129. data/lib/ratatui_ruby/event/mouse.rb +2 -0
  130. data/lib/ratatui_ruby/event/none.rb +2 -0
  131. data/lib/ratatui_ruby/event/paste.rb +2 -0
  132. data/lib/ratatui_ruby/event/resize.rb +2 -0
  133. data/lib/ratatui_ruby/event.rb +2 -0
  134. data/lib/ratatui_ruby/frame.rb +2 -0
  135. data/lib/ratatui_ruby/layout/constraint.rb +2 -0
  136. data/lib/ratatui_ruby/layout/layout.rb +2 -0
  137. data/lib/ratatui_ruby/layout/rect.rb +2 -0
  138. data/lib/ratatui_ruby/layout.rb +2 -0
  139. data/lib/ratatui_ruby/list_state.rb +2 -0
  140. data/lib/ratatui_ruby/schema/bar_chart/bar.rb +2 -0
  141. data/lib/ratatui_ruby/schema/bar_chart/bar_group.rb +2 -0
  142. data/lib/ratatui_ruby/schema/bar_chart.rb +4 -2
  143. data/lib/ratatui_ruby/schema/block.rb +4 -2
  144. data/lib/ratatui_ruby/schema/calendar.rb +4 -2
  145. data/lib/ratatui_ruby/schema/canvas.rb +2 -0
  146. data/lib/ratatui_ruby/schema/center.rb +2 -0
  147. data/lib/ratatui_ruby/schema/chart.rb +4 -2
  148. data/lib/ratatui_ruby/schema/clear.rb +2 -0
  149. data/lib/ratatui_ruby/schema/constraint.rb +2 -0
  150. data/lib/ratatui_ruby/schema/cursor.rb +2 -0
  151. data/lib/ratatui_ruby/schema/draw.rb +2 -0
  152. data/lib/ratatui_ruby/schema/gauge.rb +4 -2
  153. data/lib/ratatui_ruby/schema/layout.rb +2 -0
  154. data/lib/ratatui_ruby/schema/line_gauge.rb +4 -2
  155. data/lib/ratatui_ruby/schema/list.rb +3 -1
  156. data/lib/ratatui_ruby/schema/list_item.rb +2 -0
  157. data/lib/ratatui_ruby/schema/overlay.rb +2 -0
  158. data/lib/ratatui_ruby/schema/paragraph.rb +2 -0
  159. data/lib/ratatui_ruby/schema/ratatui_logo.rb +4 -2
  160. data/lib/ratatui_ruby/schema/ratatui_mascot.rb +4 -2
  161. data/lib/ratatui_ruby/schema/rect.rb +2 -0
  162. data/lib/ratatui_ruby/schema/row.rb +2 -0
  163. data/lib/ratatui_ruby/schema/scrollbar.rb +4 -2
  164. data/lib/ratatui_ruby/schema/shape/label.rb +2 -0
  165. data/lib/ratatui_ruby/schema/sparkline.rb +4 -2
  166. data/lib/ratatui_ruby/schema/style.rb +2 -0
  167. data/lib/ratatui_ruby/schema/table.rb +2 -0
  168. data/lib/ratatui_ruby/schema/tabs.rb +4 -2
  169. data/lib/ratatui_ruby/schema/text.rb +2 -0
  170. data/lib/ratatui_ruby/scrollbar_state.rb +2 -0
  171. data/lib/ratatui_ruby/style/style.rb +2 -0
  172. data/lib/ratatui_ruby/style.rb +2 -0
  173. data/lib/ratatui_ruby/table_state.rb +2 -0
  174. data/lib/ratatui_ruby/test_helper/event_injection.rb +2 -0
  175. data/lib/ratatui_ruby/test_helper/snapshot.rb +2 -0
  176. data/lib/ratatui_ruby/test_helper/style_assertions.rb +2 -0
  177. data/lib/ratatui_ruby/test_helper/terminal.rb +2 -0
  178. data/lib/ratatui_ruby/test_helper/test_doubles.rb +2 -0
  179. data/lib/ratatui_ruby/test_helper.rb +5 -3
  180. data/lib/ratatui_ruby/tui/buffer_factories.rb +2 -0
  181. data/lib/ratatui_ruby/tui/canvas_factories.rb +2 -0
  182. data/lib/ratatui_ruby/tui/core.rb +2 -0
  183. data/lib/ratatui_ruby/tui/layout_factories.rb +2 -0
  184. data/lib/ratatui_ruby/tui/state_factories.rb +2 -0
  185. data/lib/ratatui_ruby/tui/style_factories.rb +2 -0
  186. data/lib/ratatui_ruby/tui/text_factories.rb +2 -0
  187. data/lib/ratatui_ruby/tui/widget_factories.rb +2 -0
  188. data/lib/ratatui_ruby/tui.rb +2 -0
  189. data/lib/ratatui_ruby/version.rb +3 -1
  190. data/lib/ratatui_ruby/widgets/bar_chart/bar.rb +2 -0
  191. data/lib/ratatui_ruby/widgets/bar_chart/bar_group.rb +2 -0
  192. data/lib/ratatui_ruby/widgets/bar_chart.rb +4 -2
  193. data/lib/ratatui_ruby/widgets/block.rb +4 -2
  194. data/lib/ratatui_ruby/widgets/calendar.rb +4 -2
  195. data/lib/ratatui_ruby/widgets/canvas.rb +2 -0
  196. data/lib/ratatui_ruby/widgets/cell.rb +2 -0
  197. data/lib/ratatui_ruby/widgets/center.rb +2 -0
  198. data/lib/ratatui_ruby/widgets/chart.rb +4 -2
  199. data/lib/ratatui_ruby/widgets/clear.rb +2 -0
  200. data/lib/ratatui_ruby/widgets/cursor.rb +2 -0
  201. data/lib/ratatui_ruby/widgets/gauge.rb +4 -2
  202. data/lib/ratatui_ruby/widgets/line_gauge.rb +4 -2
  203. data/lib/ratatui_ruby/widgets/list.rb +3 -1
  204. data/lib/ratatui_ruby/widgets/list_item.rb +2 -0
  205. data/lib/ratatui_ruby/widgets/overlay.rb +2 -0
  206. data/lib/ratatui_ruby/widgets/paragraph.rb +2 -0
  207. data/lib/ratatui_ruby/widgets/ratatui_logo.rb +4 -2
  208. data/lib/ratatui_ruby/widgets/ratatui_mascot.rb +4 -2
  209. data/lib/ratatui_ruby/widgets/row.rb +2 -0
  210. data/lib/ratatui_ruby/widgets/scrollbar.rb +4 -2
  211. data/lib/ratatui_ruby/widgets/shape/label.rb +2 -0
  212. data/lib/ratatui_ruby/widgets/sparkline.rb +4 -2
  213. data/lib/ratatui_ruby/widgets/table.rb +2 -0
  214. data/lib/ratatui_ruby/widgets/tabs.rb +12 -8
  215. data/lib/ratatui_ruby/widgets.rb +2 -0
  216. data/lib/ratatui_ruby.rb +2 -0
  217. data/tasks/autodoc/examples.rb +2 -0
  218. data/tasks/autodoc/member.rb +2 -0
  219. data/tasks/autodoc/name.rb +2 -0
  220. data/tasks/autodoc.rake +2 -0
  221. data/tasks/bump/cargo_lockfile.rb +2 -0
  222. data/tasks/bump/changelog.rb +2 -0
  223. data/tasks/bump/header.rb +2 -0
  224. data/tasks/bump/history.rb +2 -0
  225. data/tasks/bump/links.rb +2 -0
  226. data/tasks/bump/manifest.rb +2 -0
  227. data/tasks/bump/ruby_gem.rb +2 -0
  228. data/tasks/bump/sem_ver.rb +2 -0
  229. data/tasks/bump/unreleased_section.rb +2 -0
  230. data/tasks/bump.rake +2 -0
  231. data/tasks/doc.rake +268 -0
  232. data/tasks/extension.rake +2 -0
  233. data/tasks/lint.rake +115 -0
  234. data/tasks/rdoc_config.rb +18 -4
  235. data/tasks/sourcehut.rake +2 -0
  236. data/tasks/terminal_preview/app_screenshot.rb +2 -0
  237. data/tasks/terminal_preview/crash_report.rb +2 -0
  238. data/tasks/terminal_preview/example_app.rb +2 -0
  239. data/tasks/terminal_preview/launcher_script.rb +2 -0
  240. data/tasks/terminal_preview/preview_collection.rb +2 -0
  241. data/tasks/terminal_preview/preview_timing.rb +2 -0
  242. data/tasks/terminal_preview/safety_confirmation.rb +2 -0
  243. data/tasks/terminal_preview/saved_screenshot.rb +2 -0
  244. data/tasks/terminal_preview/system_appearance.rb +2 -0
  245. data/tasks/terminal_preview/terminal_window.rb +2 -0
  246. data/tasks/terminal_preview/window_id.rb +2 -0
  247. data/tasks/terminal_preview.rake +2 -0
  248. data/tasks/test.rake +2 -0
  249. data/tasks/website/index_page.rb +2 -0
  250. data/tasks/website/version.rb +12 -2
  251. data/tasks/website/version_menu.rb +2 -0
  252. data/tasks/website/versioned_documentation.rb +2 -0
  253. data/tasks/website/website.rb +2 -0
  254. data/tasks/website.rake +2 -0
  255. metadata +72 -74
  256. data/doc/contributors/architectural_overhaul/chat_conversations.md +0 -4952
  257. data/doc/contributors/architectural_overhaul/implementation_plan.md +0 -60
  258. data/doc/contributors/architectural_overhaul/task.md +0 -37
  259. /data/doc/{application_testing.md → concepts/application_testing.md} +0 -0
  260. /data/doc/{async.md → concepts/async.md} +0 -0
  261. /data/doc/{interactive_design.md → concepts/interactive_design.md} +0 -0
  262. /data/doc/images/{widget_barchart_demo.png → widget_barchart.png} +0 -0
  263. /data/doc/images/{widget_block_demo.png → widget_block.png} +0 -0
  264. /data/doc/images/{widget_box_demo.png → widget_box.png} +0 -0
  265. /data/doc/images/{widget_calendar_demo.png → widget_calendar.png} +0 -0
  266. /data/doc/images/{widget_canvas_demo.png → widget_canvas.png} +0 -0
  267. /data/doc/images/{widget_cell_demo.png → widget_cell.png} +0 -0
  268. /data/doc/images/{widget_center_demo.png → widget_center.png} +0 -0
  269. /data/doc/images/{widget_chart_demo.png → widget_chart.png} +0 -0
  270. /data/doc/images/{widget_gauge_demo.png → widget_gauge.png} +0 -0
  271. /data/doc/images/{widget_line_gauge_demo.png → widget_line_gauge.png} +0 -0
  272. /data/doc/images/{widget_list_demo.png → widget_list.png} +0 -0
  273. /data/doc/images/{widget_map_demo.png → widget_map.png} +0 -0
  274. /data/doc/images/{widget_overlay_demo.png → widget_overlay.png} +0 -0
  275. /data/doc/images/{widget_popup_demo.png → widget_popup.png} +0 -0
  276. /data/doc/images/{widget_ratatui_logo_demo.png → widget_ratatui_logo.png} +0 -0
  277. /data/doc/images/{widget_ratatui_mascot_demo.png → widget_ratatui_mascot.png} +0 -0
  278. /data/doc/images/{widget_scrollbar_demo.png → widget_scrollbar.png} +0 -0
  279. /data/doc/images/{widget_sparkline_demo.png → widget_sparkline.png} +0 -0
  280. /data/doc/images/{widget_table_demo.png → widget_table.png} +0 -0
  281. /data/doc/images/{widget_tabs_demo.png → widget_tabs.png} +0 -0
  282. /data/doc/{v0.7.0_migration.md → migration/v0_7_0.md} +0 -0
  283. /data/doc/{debugging.md → troubleshooting/debugging.md} +0 -0
data/tasks/doc.rake CHANGED
@@ -1,7 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ #--
3
4
  # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
4
5
  # SPDX-License-Identifier: AGPL-3.0-or-later
6
+ #++
5
7
 
6
8
  require "rdoc/task"
7
9
 
@@ -611,9 +613,275 @@ end
611
613
  Rake::Task[:rdoc].enhance do
612
614
  Rake::Task[:copy_doc_images].invoke
613
615
  Rake::Task[:copy_examples].invoke
616
+ Rake::Task[:rewrite_examples_link].invoke
614
617
  end
615
618
 
616
619
  Rake::Task[:rerdoc].enhance do
617
620
  Rake::Task[:copy_doc_images].invoke
618
621
  Rake::Task[:copy_examples].invoke
622
+ Rake::Task[:rewrite_examples_link].invoke
623
+ end
624
+
625
+ task :rewrite_examples_link do
626
+ require "nokogiri"
627
+
628
+ rdoc_dir = ENV["RDOC_OUTPUT"] || "tmp/rdoc"
629
+
630
+ # Build a mapping of example READMEs to their H1 titles and categories
631
+ examples_by_category = { "Apps" => [], "Widgets" => [] }
632
+
633
+ Dir.glob("examples/*/README.md").each do |readme_path|
634
+ dir_name = File.dirname(readme_path).sub("examples/", "")
635
+
636
+ # Skip verify examples entirely
637
+ next if dir_name.start_with?("verify_")
638
+
639
+ content = File.read(readme_path)
640
+ if content =~ /^#\s+(.+)$/
641
+ title = $1.strip.sub(/ Example$/, "") # Remove trailing " Example"
642
+ rdoc_path = "examples/#{dir_name}/README_md.html"
643
+
644
+ # Categorize by prefix
645
+ category = if dir_name.start_with?("app_")
646
+ "Apps"
647
+ elsif dir_name.start_with?("widget_")
648
+ title = title.sub(/ Widget$/, "") # Also strip trailing " Widget" for widgets
649
+ "Widgets"
650
+ else
651
+ nil
652
+ end
653
+
654
+ if category
655
+ examples_by_category[category] << { title:, rdoc_path:, dir_name: }
656
+ end
657
+ end
658
+ end
659
+
660
+ # Sort each category alphabetically by title
661
+ examples_by_category.each_value { |list| list.sort_by! { |e| e[:title] } }
662
+
663
+ # Process all HTML files
664
+ Dir.glob("#{rdoc_dir}/**/*.html").each do |file|
665
+ content = File.read(file)
666
+ modified = false
667
+
668
+ doc = Nokogiri::HTML(content)
669
+
670
+ # Find the examples details section to remove from Pages
671
+ examples_detail = doc.css("details summary").find { |s| s.text.strip.downcase == "examples" }&.parent
672
+
673
+ # Find the classindex-section to insert Examples section before it
674
+ classindex_section = doc.at_css("#classindex-section")
675
+
676
+ if examples_detail && classindex_section
677
+ # Remove examples from Pages section
678
+ examples_detail.remove
679
+
680
+ # Build the new Examples section as a top-level nav-section
681
+ current_depth = file.sub("#{rdoc_dir}/", "").count("/")
682
+ prefix = "../" * current_depth
683
+
684
+ examples_section = Nokogiri::XML::Node.new("div", doc)
685
+ examples_section["id"] = "exampleindex-section"
686
+ examples_section["class"] = "nav-section"
687
+
688
+ examples_section.inner_html = <<~HTML
689
+ <details class="nav-section-collapsible" open>
690
+ <summary class="nav-section-header">
691
+ <span class="nav-section-icon">
692
+ <svg><use href="#icon-layers"></use></svg>
693
+ </span>
694
+ <span class="nav-section-title">Examples</span>
695
+ <span class="nav-section-chevron">
696
+ <svg><use href="#icon-chevron"></use></svg>
697
+ </span>
698
+ </summary>
699
+ <ul class="link-list nav-list">
700
+ </ul>
701
+ </details>
702
+ HTML
703
+
704
+ # Build the category structure
705
+ examples_ul = examples_section.at_css("ul.link-list")
706
+
707
+ examples_by_category.each do |category_name, examples|
708
+ next if examples.empty?
709
+
710
+ cat_li = Nokogiri::XML::Node.new("li", doc)
711
+ cat_details = Nokogiri::XML::Node.new("details", doc)
712
+ # Subcategories closed by default
713
+ cat_summary = Nokogiri::XML::Node.new("summary", doc)
714
+ cat_summary.content = category_name
715
+ cat_details.add_child(cat_summary)
716
+
717
+ cat_ul = Nokogiri::XML::Node.new("ul", doc)
718
+ cat_ul["class"] = "link-list nav-list"
719
+
720
+ examples.each do |example|
721
+ li = Nokogiri::XML::Node.new("li", doc)
722
+ a = Nokogiri::XML::Node.new("a", doc)
723
+ a["href"] = "#{prefix}#{example[:rdoc_path]}"
724
+ a.content = example[:title]
725
+ li.add_child(a)
726
+ cat_ul.add_child(li)
727
+ end
728
+
729
+ cat_details.add_child(cat_ul)
730
+ cat_li.add_child(cat_details)
731
+ examples_ul.add_child(cat_li)
732
+ end
733
+
734
+ # Insert Examples section before Classes and Modules
735
+ classindex_section.add_previous_sibling(examples_section)
736
+
737
+ # --- GUIDES SECTION ---
738
+ # Build dynamic hierarchical tree from doc/ folder structure
739
+ guides_tree = build_guides_tree
740
+
741
+ # Find and remove the doc details section from Pages
742
+ doc_detail = doc.css("details summary").find { |s| s.text.strip.downcase == "doc" }&.parent
743
+ doc_detail&.remove
744
+
745
+ # Create the Guides section
746
+ guides_section = Nokogiri::XML::Node.new("div", doc)
747
+ guides_section["id"] = "guidesindex-section"
748
+ guides_section["class"] = "nav-section"
749
+
750
+ guides_section.inner_html = <<~HTML
751
+ <details class="nav-section-collapsible" open>
752
+ <summary class="nav-section-header">
753
+ <span class="nav-section-icon">
754
+ <svg><use href="#icon-file"></use></svg>
755
+ </span>
756
+ <span class="nav-section-title">Guides</span>
757
+ <span class="nav-section-chevron">
758
+ <svg><use href="#icon-chevron"></use></svg>
759
+ </span>
760
+ </summary>
761
+ <ul class="link-list nav-list">
762
+ </ul>
763
+ </details>
764
+ HTML
765
+
766
+ # Get current file path relative to rdoc_dir (e.g. "doc/getting_started/quickstart_md.html")
767
+ current_file_rel = file.sub("#{rdoc_dir}/", "")
768
+
769
+ guides_ul = guides_section.at_css("ul.link-list")
770
+ build_guides_nav(guides_ul, guides_tree, doc, prefix, current_file_rel, "doc")
771
+
772
+ # Insert Guides section before Examples
773
+ examples_section.add_previous_sibling(guides_section)
774
+
775
+ content = doc.to_html
776
+ modified = true
777
+ end
778
+
779
+ # Also rewrite examples_md.html to examples/index.html
780
+ if content.include?("examples_md.html")
781
+ content = content.gsub(/href="([^"]*?)examples_md\.html"/, 'href="\1examples/index.html"')
782
+ modified = true
783
+ end
784
+
785
+ File.write(file, content) if modified
786
+ end
787
+
788
+ # Delete the now-unused examples_md.html
789
+ examples_page = "#{rdoc_dir}/examples_md.html"
790
+ FileUtils.rm_f(examples_page)
791
+
792
+ puts "Created Examples and Guides sections in sidebar"
793
+ end
794
+
795
+ # Build a hierarchical tree structure from doc/**/*.md files
796
+ def build_guides_tree
797
+ tree = { files: [], subdirs: {} }
798
+
799
+ Dir.glob("doc/**/*.md").each do |md_path|
800
+ # Skip images folder
801
+ next if md_path.include?("/images/")
802
+
803
+ relative = md_path.sub("doc/", "")
804
+ parts = relative.split("/")
805
+ filename = parts.pop
806
+
807
+ # Get title from H1
808
+ content = File.read(md_path)
809
+ title = if content =~ /^#\s+(.+)$/
810
+ $1.strip
811
+ else
812
+ filename.sub(/\.md$/, "").tr("_-", " ").split.map(&:capitalize).join(" ")
813
+ end
814
+
815
+ # Convert to RDoc path
816
+ rdoc_path = "doc/#{relative.gsub('.', '_')}.html"
817
+
818
+ # Navigate to correct position in tree
819
+ current = tree
820
+ parts.each do |dir|
821
+ current[:subdirs][dir] ||= { files: [], subdirs: {} }
822
+ current = current[:subdirs][dir]
823
+ end
824
+
825
+ current[:files] << { title:, rdoc_path:, filename: }
826
+ end
827
+
828
+ # Sort files in each level alphabetically by title
829
+ sort_guides_tree(tree)
830
+ tree
831
+ end
832
+
833
+ def sort_guides_tree(node)
834
+ node[:files].sort_by! { |f| f[:title] }
835
+ node[:subdirs].each_value { |subdir| sort_guides_tree(subdir) }
836
+ end
837
+
838
+ # Recursively build navigation elements from the tree
839
+ # current_file_rel: path of current HTML file relative to rdoc_dir (e.g. "doc/getting_started/quickstart_md.html")
840
+ # current_tree_path: path in the tree we're building (e.g. "doc", "doc/getting_started")
841
+ def build_guides_nav(parent_ul, tree, doc, prefix, current_file_rel, current_tree_path)
842
+ # Add files at this level first
843
+ tree[:files].each do |file|
844
+ # Check if this file is the current page
845
+ is_current = (file[:rdoc_path] == current_file_rel)
846
+
847
+ li = Nokogiri::XML::Node.new("li", doc)
848
+ a = Nokogiri::XML::Node.new("a", doc)
849
+ a["href"] = "#{prefix}#{file[:rdoc_path]}"
850
+ if is_current
851
+ a["class"] = "active"
852
+ strong = Nokogiri::XML::Node.new("strong", doc)
853
+ strong.content = file[:title]
854
+ a.add_child(strong)
855
+ else
856
+ a.content = file[:title]
857
+ end
858
+ li.add_child(a)
859
+ parent_ul.add_child(li)
860
+ end
861
+
862
+ # Add subdirectories as collapsible details
863
+ tree[:subdirs].each do |dir_name, subtree|
864
+ subdir_path = "#{current_tree_path}/#{dir_name}"
865
+
866
+ # Check if current file is inside this subdirectory
867
+ # current_file_rel might be "doc/getting_started/quickstart_md.html"
868
+ # subdir_path would be "doc/getting_started"
869
+ is_current_in_subdir = current_file_rel.start_with?("#{subdir_path}/")
870
+
871
+ li = Nokogiri::XML::Node.new("li", doc)
872
+ details = Nokogiri::XML::Node.new("details", doc)
873
+ # Open if current file is inside this subdir
874
+ details["open"] = "open" if is_current_in_subdir
875
+ summary = Nokogiri::XML::Node.new("summary", doc)
876
+ summary.content = dir_name.tr("_-", " ").split.map(&:capitalize).join(" ")
877
+ details.add_child(summary)
878
+
879
+ subdir_ul = Nokogiri::XML::Node.new("ul", doc)
880
+ subdir_ul["class"] = "link-list nav-list"
881
+ build_guides_nav(subdir_ul, subtree, doc, prefix, current_file_rel, subdir_path)
882
+
883
+ details.add_child(subdir_ul)
884
+ li.add_child(details)
885
+ parent_ul.add_child(li)
886
+ end
619
887
  end
data/tasks/extension.rake CHANGED
@@ -1,7 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ #--
3
4
  # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
4
5
  # SPDX-License-Identifier: AGPL-3.0-or-later
6
+ #++
5
7
 
6
8
  require "rake/extensiontask"
7
9
 
data/tasks/lint.rake CHANGED
@@ -1,7 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ #--
3
4
  # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
4
5
  # SPDX-License-Identifier: AGPL-3.0-or-later
6
+ #++
5
7
 
6
8
  require "rubocop/rake_task"
7
9
  require "rubycritic/rake_task"
@@ -35,6 +37,95 @@ namespace :reuse do
35
37
  task :lint do
36
38
  sh "reuse lint"
37
39
  end
40
+
41
+ desc "Add SPDX headers to files missing them (per AGENTS.md standards)"
42
+ task :fix do
43
+ copyright = "Kerrick Long <me@kerricklong.com>"
44
+
45
+ # Code files: AGPL-3.0-or-later
46
+ code_extensions = %w[rb rs rake gemspec rbs toml yml yaml json lock].freeze
47
+ code_license = "AGPL-3.0-or-later"
48
+
49
+ # Documentation files: CC-BY-SA-4.0
50
+ doc_extensions = %w[md txt].freeze
51
+ doc_license = "CC-BY-SA-4.0"
52
+
53
+ # Find files missing headers (listed after "no copyright and licensing" message)
54
+ puts "Checking for files missing REUSE headers..."
55
+ output = `reuse lint 2>&1`
56
+ in_missing_section = false
57
+ missing_files = output.lines.filter_map do |line|
58
+ in_missing_section = true if line.include?("no copyright and licensing")
59
+ in_missing_section = false if line.start_with?("# ") && !line.include?("copyright")
60
+ next unless in_missing_section
61
+
62
+ line.match(/^\* (.+)/)&.[](1)
63
+ end
64
+
65
+ if missing_files.empty?
66
+ puts "All files have REUSE headers!"
67
+ else
68
+ missing_files.each do |file|
69
+ ext = File.extname(file).delete(".")
70
+ license = if code_extensions.include?(ext)
71
+ code_license
72
+ elsif doc_extensions.include?(ext)
73
+ doc_license
74
+ else
75
+ puts " Skipping #{file} (unknown extension: .#{ext})"
76
+ next
77
+ end
78
+
79
+ puts " Annotating #{file} with #{license}"
80
+ sh "reuse annotate --license #{license} --copyright '#{copyright}' --skip-existing '#{file}'", verbose: false
81
+ end
82
+ end
83
+ end
84
+
85
+ desc "Normalize Ruby files: frozen_string_literal at top, SPDX in #--/#++ block"
86
+ task :normalize_ruby do
87
+ ruby_extensions = %w[rb rake gemspec].freeze
88
+ ruby_files = Dir.glob("**/*.{#{ruby_extensions.join(',')}}")
89
+ .reject { |f| f.start_with?("vendor/", "tmp/", ".") }
90
+
91
+ fixed_count = 0
92
+ ruby_files.each do |file|
93
+ content = File.read(file)
94
+ original = content.dup
95
+
96
+ # Skip if no SPDX header
97
+ next unless content.match?(/# SPDX-/)
98
+
99
+ # Extract components
100
+ frozen = content.match?(/^# frozen_string_literal: true/)
101
+ spdx_match = content.match(/(# SPDX-FileCopyrightText:[^\n]+\n(?:#[^\n]*\n)*# SPDX-License-Identifier:[^\n]+\n)/m)
102
+ next unless spdx_match
103
+
104
+ spdx_block = spdx_match[1]
105
+
106
+ # Remove existing frozen_string_literal and SPDX block (and any #--/#+++)
107
+ cleaned = content
108
+ .sub(/^# frozen_string_literal: true\n+/, "")
109
+ .sub(/^#--\s*\n/, "")
110
+ .sub(spdx_block, "")
111
+ .sub(/^#\+\+\s*\n/, "")
112
+ .sub(/\A\n+/, "") # Remove leading blank lines
113
+
114
+ # Rebuild file in correct order: frozen, blank, #--, SPDX, #++, rest
115
+ new_content = ""
116
+ new_content += "# frozen_string_literal: true\n\n" if frozen
117
+ new_content += "#--\n#{spdx_block}#++\n\n"
118
+ new_content += cleaned.sub(/\A\n+/, "") # Ensure no double blank lines
119
+
120
+ if new_content != original
121
+ File.write(file, new_content)
122
+ puts " Normalized #{file}"
123
+ fixed_count += 1
124
+ end
125
+ end
126
+
127
+ puts fixed_count.zero? ? "All Ruby files properly normalized!" : "Fixed #{fixed_count} files."
128
+ end
38
129
  end
39
130
  task(:reuse) { Rake::Task["reuse:lint"].invoke }
40
131
 
@@ -47,7 +138,31 @@ namespace :lint do
47
138
  task code: %w[rubocop rubycritic cargo:fmt cargo:clippy cargo:test]
48
139
  task licenses: %w[reuse:lint]
49
140
  task all: %w[docs code licenses]
141
+
142
+ namespace :fix do
143
+ desc "Auto-fix RuboCop offenses (most aggressive)"
144
+ task :rubocop do
145
+ sh "bundle exec rubocop --autocorrect-all"
146
+ end
147
+
148
+ desc "Auto-fix Clippy warnings (most aggressive: --fix --allow-dirty --allow-staged)"
149
+ task :clippy do
150
+ sh "cd ext/ratatui_ruby && cargo clippy --fix --allow-dirty --allow-staged"
151
+ end
152
+
153
+ desc "Add SPDX headers and normalize Ruby file structure"
154
+ task reuse: %w[reuse:fix reuse:normalize_ruby]
155
+
156
+ desc "Run all auto-fix tasks"
157
+ task all: %w[lint:fix:rubocop lint:fix:clippy lint:fix:reuse]
158
+ end
50
159
  end
51
160
 
161
+ desc "Run all lint auto-fix tasks"
162
+ task("lint:fix") { Rake::Task["lint:fix:all"].invoke }
163
+
164
+ # Aliases for convenience
165
+ task "rubocop:autocorrect_all" => "lint:fix:rubocop"
166
+
52
167
  desc "Run all lint tasks"
53
168
  task(:lint) { Rake::Task["lint:all"].invoke }
data/tasks/rdoc_config.rb CHANGED
@@ -1,15 +1,29 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ #--
3
4
  # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
4
5
  # SPDX-License-Identifier: AGPL-3.0-or-later
6
+ #++
5
7
 
6
8
  module RDocConfig
7
- RDOC_FILES = %w[
8
- **/*.md
9
- **/*.rdoc
9
+ MAX_FILE_SIZE = 100_000 # 100KB - skip large files like chat logs
10
+
11
+ RDOC_FILES = Dir.glob(%w[
12
+ doc/**/*.md
13
+ examples/**/*.md
14
+ *.md
15
+ *.rdoc
10
16
  lib/**/*.rb
11
17
  exe/**/*
12
- ].freeze
18
+ ]).reject { |f|
19
+ # Skip large files
20
+ if File.size(f) > MAX_FILE_SIZE
21
+ warn "RDoc: skipping #{f} (#{File.size(f) / 1024}KB > #{MAX_FILE_SIZE / 1024}KB limit)"
22
+ next true
23
+ end
24
+ # Skip verification examples (internal testing, not user-facing)
25
+ f.start_with?("examples/verify_")
26
+ }.freeze
13
27
 
14
28
  MAIN = "README.md"
15
29
  end
data/tasks/sourcehut.rake CHANGED
@@ -1,7 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ #--
3
4
  # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
4
5
  # SPDX-License-Identifier: AGPL-3.0-or-later
6
+ #++
5
7
 
6
8
  desc "Generate SourceHut build manifests from template"
7
9
  task sourcehut: "sourcehut:build"
@@ -1,7 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ #--
3
4
  # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
4
5
  # SPDX-License-Identifier: AGPL-3.0-or-later
6
+ #++
5
7
 
6
8
  require "tmpdir"
7
9
  require_relative "launcher_script"
@@ -1,7 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ #--
3
4
  # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
4
5
  # SPDX-License-Identifier: AGPL-3.0-or-later
6
+ #++
5
7
 
6
8
  class CrashReport < Data.define(:app, :error, :preamble)
7
9
  def self.new(app, error, preamble = nil)
@@ -1,7 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ #--
3
4
  # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
4
5
  # SPDX-License-Identifier: AGPL-3.0-or-later
6
+ #++
5
7
 
6
8
  class ExampleApp < Data.define(:directory)
7
9
  def self.all
@@ -1,7 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ #--
3
4
  # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
4
5
  # SPDX-License-Identifier: AGPL-3.0-or-later
6
+ #++
5
7
 
6
8
  require "fileutils"
7
9
  require "tmpdir"
@@ -1,7 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ #--
3
4
  # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
4
5
  # SPDX-License-Identifier: AGPL-3.0-or-later
6
+ #++
5
7
 
6
8
  require "fileutils"
7
9
  require_relative "example_app"
@@ -1,7 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ #--
3
4
  # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
4
5
  # SPDX-License-Identifier: AGPL-3.0-or-later
6
+ #++
5
7
 
6
8
  class PreviewTiming
7
9
  def self.window_startup
@@ -1,7 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ #--
3
4
  # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
4
5
  # SPDX-License-Identifier: AGPL-3.0-or-later
6
+ #++
5
7
 
6
8
  require_relative "preview_timing"
7
9
  require_relative "system_appearance"
@@ -1,7 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ #--
3
4
  # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
4
5
  # SPDX-License-Identifier: AGPL-3.0-or-later
6
+ #++
5
7
 
6
8
  require "time"
7
9
 
@@ -1,7 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ #--
3
4
  # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
4
5
  # SPDX-License-Identifier: AGPL-3.0-or-later
6
+ #++
5
7
 
6
8
  class SystemAppearance
7
9
  def self.dark?
@@ -1,7 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ #--
3
4
  # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
4
5
  # SPDX-License-Identifier: AGPL-3.0-or-later
6
+ #++
5
7
 
6
8
  require_relative "window_id"
7
9
  require_relative "preview_timing"
@@ -1,7 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ #--
3
4
  # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
4
5
  # SPDX-License-Identifier: AGPL-3.0-or-later
6
+ #++
5
7
 
6
8
  class WindowID < Data.define(:value)
7
9
  def valid?
@@ -1,7 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ #--
3
4
  # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
4
5
  # SPDX-License-Identifier: AGPL-3.0-or-later
6
+ #++
5
7
 
6
8
  require "fileutils"
7
9
  require_relative "terminal_preview/preview_collection"
data/tasks/test.rake CHANGED
@@ -1,7 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ #--
3
4
  # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
4
5
  # SPDX-License-Identifier: AGPL-3.0-or-later
6
+ #++
5
7
 
6
8
  require "minitest/test_task"
7
9
 
@@ -1,7 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ #--
3
4
  # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
4
5
  # SPDX-License-Identifier: AGPL-3.0-or-later
6
+ #++
5
7
 
6
8
  require "erb"
7
9
 
@@ -1,7 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ #--
3
4
  # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
4
5
  # SPDX-License-Identifier: AGPL-3.0-or-later
6
+ #++
5
7
 
6
8
  require "rubygems"
7
9
  require "fileutils"
@@ -13,7 +15,14 @@ class Version
13
15
  .sort_by(&:semver)
14
16
  .reverse
15
17
 
16
- [Edge.new] + sorted_versions
18
+ # Keep only the latest patch for each minor version
19
+ # e.g., if we have v0.6.0, v0.6.1, v0.6.2, only keep v0.6.2
20
+ latest_per_minor = sorted_versions
21
+ .group_by { |v| v.semver.segments[0..1] } # group by [major, minor]
22
+ .values
23
+ .map(&:first) # take the first (highest patch) from each group
24
+
25
+ [Edge.new] + latest_per_minor
17
26
  end
18
27
 
19
28
  def slug
@@ -85,7 +94,8 @@ class Tagged < Version
85
94
  end
86
95
 
87
96
  def slug
88
- @tag
97
+ segments = semver.segments
98
+ "v#{segments[0]}.#{segments[1]}"
89
99
  end
90
100
 
91
101
  def name
@@ -1,7 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ #--
3
4
  # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
4
5
  # SPDX-License-Identifier: AGPL-3.0-or-later
6
+ #++
5
7
 
6
8
  class VersionMenu
7
9
  def initialize(root:, versions:)
@@ -1,7 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ #--
3
4
  # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
4
5
  # SPDX-License-Identifier: AGPL-3.0-or-later
6
+ #++
5
7
 
6
8
  require_relative "../rdoc_config"
7
9