stream_weaver 0.3.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 (381) hide show
  1. checksums.yaml +7 -0
  2. data/.beads/.gitignore +77 -0
  3. data/.beads/README.md +81 -0
  4. data/.beads/config.yaml +54 -0
  5. data/.beads/metadata.json +7 -0
  6. data/.rspec +3 -0
  7. data/AGENTS.md +91 -0
  8. data/CHANGELOG.md +294 -0
  9. data/CLAUDE.md +67 -0
  10. data/LICENSE.txt +21 -0
  11. data/README.md +565 -0
  12. data/Rakefile +13 -0
  13. data/assets/streamweaver-hero.jpg +0 -0
  14. data/bench/baselines/ledger.rb +58 -0
  15. data/bench/baselines/warroom.rb +51 -0
  16. data/bench/fixtures/ledger.rb +76 -0
  17. data/bench/fixtures/warroom.rb +60 -0
  18. data/bench/results/6daf8b8.md +102 -0
  19. data/bench/results/c8d749b.md +102 -0
  20. data/bench/results/eb06ab9.md +102 -0
  21. data/bench/run.rb +151 -0
  22. data/bench/support.rb +84 -0
  23. data/copy_this/dto_run_sample_run_7631.html +220 -0
  24. data/docs/SERVICE_MODE.md +302 -0
  25. data/docs/architecture/how_streamweaver_works.md +772 -0
  26. data/docs/blog/2026-01-16-streamweaver-introduction.md +515 -0
  27. data/docs/bug-2026-08-31-canvas-bridge-socket-collision.md +83 -0
  28. data/docs/canvas-ipc-session-summary.md +127 -0
  29. data/docs/canvas-panel-workflow.md +172 -0
  30. data/docs/canvas-read.md +147 -0
  31. data/docs/canvas-roadmap.md +275 -0
  32. data/docs/case-studies/2026-09-03-didx-canvas-read-shelf.md +147 -0
  33. data/docs/claude-code-companion-skill-spec.md +567 -0
  34. data/docs/components_reference.md +1223 -0
  35. data/docs/crud-patterns.md +265 -0
  36. data/docs/endpoints.md +122 -0
  37. data/docs/for_llms.md +1 -0
  38. data/docs/form-patterns.md +165 -0
  39. data/docs/frontend-only.md +169 -0
  40. data/docs/html-artifact-audit.md +238 -0
  41. data/docs/ideas/2025-01-01-as-a-service.md +203 -0
  42. data/docs/ideas/2026-01-16-charm-tui-exploration.md +542 -0
  43. data/docs/ideas/2026-08-27-static-doc-shelf-export.md +153 -0
  44. data/docs/ideas/dhh-review-future-refactors.md +142 -0
  45. data/docs/opal-jamstack.md +47 -0
  46. data/docs/opal-npm-direction.md +184 -0
  47. data/docs/opal-spike-findings.md +194 -0
  48. data/docs/plans/2025-12-16-form-blocks-design.md +124 -0
  49. data/docs/plans/2025-12-30-examples-browser-design.md +152 -0
  50. data/docs/plans/2026-01-02-charts-design.md +288 -0
  51. data/docs/plans/2026-01-10-canvas-ipc-design.md +247 -0
  52. data/docs/plans/2026-01-19-cabinet-control-components.md +176 -0
  53. data/docs/plans/2026-01-26-gem-release-and-panel.md +140 -0
  54. data/docs/plans/canvas-claude-project-design.md +238 -0
  55. data/docs/plans/canvas-doc-location-and-discovery.md +32 -0
  56. data/docs/plans/org-doc-preview-surfaces.md +293 -0
  57. data/docs/plans/shadcn-polish-plan.md +417 -0
  58. data/docs/porting-artifacts.md +252 -0
  59. data/docs/reference/agent-skills-comparison.md +369 -0
  60. data/docs/reference/travel-state-prd.artifact.html +780 -0
  61. data/docs/research/2026-08-17-hotwire-alike-landscape.md +268 -0
  62. data/docs/research/2026-08-17-hotwire-concept-map.md +404 -0
  63. data/docs/research/2026-08-22-lazy-fragments-trigger-decision.md +246 -0
  64. data/docs/research/2026-08-22-learnhotwire-syllabus-coverage.md +239 -0
  65. data/docs/research/frontend-only-matrix.md +278 -0
  66. data/docs/research/streamweaver-way-spike-findings.md +529 -0
  67. data/docs/resource-dsl.md +342 -0
  68. data/docs/routing.md +302 -0
  69. data/docs/ruby-ui-comparison.md +319 -0
  70. data/docs/shared-dsl-fragments.md +356 -0
  71. data/docs/streamweaver-for-ai-agents.md +401 -0
  72. data/docs/streamweaver-frontend-vision.md +123 -0
  73. data/docs/streamweaver_canvas/glimmer_initial_final_layer.rb +77 -0
  74. data/docs/streamweaver_canvas/save_example.rb +9 -0
  75. data/docs/templates.md +213 -0
  76. data/docs/testing.md +155 -0
  77. data/docs/theming-hooks.md +281 -0
  78. data/docs/tutorials/the-streamweaver-way.md +882 -0
  79. data/docs/university/capability-inventory.md +137 -0
  80. data/docs/university/dependency-survey.md +114 -0
  81. data/docs/university/design-spec.md +217 -0
  82. data/docs/university/mockups/code-block-doc-theme-dark.png +0 -0
  83. data/docs/university/mockups/code-block-doc-theme-light.png +0 -0
  84. data/docs/university/mockups/course-list-dark.png +0 -0
  85. data/docs/university/mockups/course-list-light.png +0 -0
  86. data/docs/university/mockups/course_canvas_mockup.rb +741 -0
  87. data/docs/university/mockups/step-screen-dark.png +0 -0
  88. data/docs/university/mockups/step-screen-light.png +0 -0
  89. data/docs/university/roadmap.md +72 -0
  90. data/docs/university/send-to-coworker.md +77 -0
  91. data/docs/university/worker-session-mining.md +109 -0
  92. data/docs/visual/browser-rendering-spike.md +173 -0
  93. data/docs/visual/builder-visual-plan-analysis.md +378 -0
  94. data/docs/visual/sw-plan-format-exploration.md +511 -0
  95. data/docs/visual/sw-plan-rendering-deep-dive.md +453 -0
  96. data/docs/visual-skills/PROGRESS.md +60 -0
  97. data/docs/visual-skills/SESSION-CONTEXT.md +207 -0
  98. data/docs/visual-skills/analysis/components.md +317 -0
  99. data/docs/visual-skills/analysis/overlap.md +360 -0
  100. data/docs/visual-skills/analysis/pi-design-deck.md +1102 -0
  101. data/docs/visual-skills/analysis/streamweaver-inventory.md +93 -0
  102. data/docs/visual-skills/analysis/unified-specs.feature +1003 -0
  103. data/docs/visual-skills/analysis/visual-explainer.md +964 -0
  104. data/docs/visual-skills/blog/blog-series-outline.md +39 -0
  105. data/docs/visual-skills/blog/token-efficiency.md +73 -0
  106. data/docs/visual-skills/design/architecture.md +1725 -0
  107. data/docs/visual-skills/design/codex-review.md +340 -0
  108. data/docs/visual-skills/design/dhh-review.md +209 -0
  109. data/docs/visual-skills/design/evolution.md +100 -0
  110. data/docs/visual-skills/design/gemini-review.md +55 -0
  111. data/docs/visual-skills/design/review-synthesis.md +149 -0
  112. data/docs/visual-skills/implementation/STATE.md +22 -0
  113. data/docs/visual-skills/implementation/plan.md +347 -0
  114. data/docs/visual-skills/implementation/spike-findings.md +237 -0
  115. data/docs/visual-skills/implementation/tasks.md +702 -0
  116. data/docs/visual-skills/lessons-learned/process.md +145 -0
  117. data/examples/README.md +96 -0
  118. data/examples/advanced/all_components.rb +177 -0
  119. data/examples/advanced/examples_browser.rb +394 -0
  120. data/examples/advanced/teachables_browser.rb +261 -0
  121. data/examples/advanced/theme_tweaker.rb +366 -0
  122. data/examples/advanced/tutorial.rb +1528 -0
  123. data/examples/agentic/agentic_form.rb +30 -0
  124. data/examples/agentic/agentic_form_autoclose.rb +30 -0
  125. data/examples/agentic/cultivation_tracker.rb +69 -0
  126. data/examples/basic/hello_world.rb +22 -0
  127. data/examples/basic/opal_tabs_table.rb +44 -0
  128. data/examples/basic/todo_list.rb +39 -0
  129. data/examples/button_loading_test.rb +36 -0
  130. data/examples/canvas/mermaid_canvas_demo.sh +151 -0
  131. data/examples/charts/bar_chart_demo.rb +57 -0
  132. data/examples/charts/line_chart_demo.rb +73 -0
  133. data/examples/charts/pie_area_demo.rb +87 -0
  134. data/examples/charts/stacked_bar_chart_demo.rb +69 -0
  135. data/examples/claude_code/README.md +85 -0
  136. data/examples/claude_code/codebreaker/.claude/commands/infiltrate.md +632 -0
  137. data/examples/claude_code/codebreaker/.claude/settings.json +11 -0
  138. data/examples/claude_code/codebreaker/.claude/settings.local.json +17 -0
  139. data/examples/claude_code/codebreaker/.claude/skills/infiltrate.md +0 -0
  140. data/examples/claude_code/codebreaker/README.md +213 -0
  141. data/examples/claude_code/tutorial/.claude/commands/learn.md +453 -0
  142. data/examples/claude_code/tutorial/README.md +102 -0
  143. data/examples/claude_code/verification_flow/.claude/commands/verify.md +171 -0
  144. data/examples/claude_code/verification_flow/README.md +126 -0
  145. data/examples/components/annotated_code_demo.rb +54 -0
  146. data/examples/components/callout_demo.rb +33 -0
  147. data/examples/components/checkbox_group_demo.rb +46 -0
  148. data/examples/components/design_review.css +468 -0
  149. data/examples/components/design_review_demo.rb +44 -0
  150. data/examples/components/design_review_dsl.rb +300 -0
  151. data/examples/components/diff_block_demo.rb +112 -0
  152. data/examples/components/events_demo.rb +184 -0
  153. data/examples/components/form_demo.rb +62 -0
  154. data/examples/components/lesson_demo.rb +53 -0
  155. data/examples/components/markdown_demo.rb +110 -0
  156. data/examples/components/mermaid_demo.rb +166 -0
  157. data/examples/components/pareto_set.rb +64 -0
  158. data/examples/components/prd_demo.rb +21 -0
  159. data/examples/components/prd_dsl.rb +385 -0
  160. data/examples/components/quiz_demo.rb +47 -0
  161. data/examples/components/run_viewer_demo.rb +100 -0
  162. data/examples/components/score_and_collapsible_demo.rb +82 -0
  163. data/examples/components/select_stale_value_smoke_test.rb +39 -0
  164. data/examples/components/table_demo.rb +247 -0
  165. data/examples/components/timer_with_state_demo.rb +58 -0
  166. data/examples/components/todo_due_dates.rb +63 -0
  167. data/examples/components/uat_gaps_demo.rb +112 -0
  168. data/examples/dashboard/feed_simulator.rb +60 -0
  169. data/examples/dashboard/live_dashboard.rb +80 -0
  170. data/examples/dashboard_components.rb +108 -0
  171. data/examples/deferred_fragments_demo.rb +62 -0
  172. data/examples/generate_more_spike/README.md +63 -0
  173. data/examples/generate_more_spike/app.rb +441 -0
  174. data/examples/git_health.sh +401 -0
  175. data/examples/layout/layout_components_demo.rb +174 -0
  176. data/examples/layout/modal_demo.rb +215 -0
  177. data/examples/layout/navigation_demo.rb +227 -0
  178. data/examples/layout/route_tabs_demo.rb +52 -0
  179. data/examples/layout/routing_demo.rb +52 -0
  180. data/examples/layout/scroll_box_demo.rb +154 -0
  181. data/examples/lazy_fragments_demo.rb +151 -0
  182. data/examples/my_todos/README.md +29 -0
  183. data/examples/my_todos/my_todos.rb +312 -0
  184. data/examples/my_todos/store.rb +86 -0
  185. data/examples/opal/reactive_demo.rb +62 -0
  186. data/examples/opal/scenarios/s1_counter.rb +15 -0
  187. data/examples/opal/scenarios/s2_search_filter.rb +17 -0
  188. data/examples/opal/scenarios/s3_sibling_tabs.rb +25 -0
  189. data/examples/opal/scenarios/s4_shopping_cart.rb +34 -0
  190. data/examples/opal/scenarios/s5_watch.rb +39 -0
  191. data/examples/opal/scenarios/s6_on_start.rb +20 -0
  192. data/examples/opal/scenarios/s7_wizard.rb +33 -0
  193. data/examples/opal/scenarios/s8_loan_calculator.rb +21 -0
  194. data/examples/opal/scenarios/s9_dashboard.rb +27 -0
  195. data/examples/operations_dashboard_demo.rb +167 -0
  196. data/examples/panel_demo.sh +447 -0
  197. data/examples/parity/assets/tyrion_slice.css +86 -0
  198. data/examples/parity/rivet_people_slice.rb +182 -0
  199. data/examples/parity/tyrion_components.css +777 -0
  200. data/examples/parity/tyrion_warroom_components.rb +414 -0
  201. data/examples/parity/tyrion_warroom_slice.rb +549 -0
  202. data/examples/puma_dev/README.md +124 -0
  203. data/examples/puma_dev/config.ru +34 -0
  204. data/examples/puma_dev/standalone_app.rb +34 -0
  205. data/examples/scaffolding/blog.rb +49 -0
  206. data/examples/scaffolding/utf_lite.rb +105 -0
  207. data/examples/styling/feedback_demo.rb +229 -0
  208. data/examples/styling/style_showcase.rb +172 -0
  209. data/examples/styling/theme_demo.rb +508 -0
  210. data/examples/timer_health_checker.rb +111 -0
  211. data/examples/timer_showcase.rb +131 -0
  212. data/examples/tutorials/render_markdown.rb +112 -0
  213. data/examples/tutorials/streamweaver_way_tutorial.rb +44 -0
  214. data/examples/tutorials/tutorial_content.rb +1082 -0
  215. data/examples/visual_skills/design_deck_demo.rb +314 -0
  216. data/examples/visual_skills/explainer_demo.rb +499 -0
  217. data/exe/streamweaver +9 -0
  218. data/gsd/ROADMAP-1.0.md +167 -0
  219. data/gsd/STATE.md +13 -0
  220. data/gsd/research/market-positioning-research.md +139 -0
  221. data/gsd/research/production-patterns-research.md +270 -0
  222. data/gsd/research/repo-audit-1.0.md +274 -0
  223. data/lib/stream_weaver/action_token.rb +58 -0
  224. data/lib/stream_weaver/adapter/alpinejs.rb +8249 -0
  225. data/lib/stream_weaver/adapter/base.rb +591 -0
  226. data/lib/stream_weaver/adapter/opal.rb +334 -0
  227. data/lib/stream_weaver/adapter/static.rb +1118 -0
  228. data/lib/stream_weaver/admin.rb +176 -0
  229. data/lib/stream_weaver/app.rb +1821 -0
  230. data/lib/stream_weaver/assets/js/sw-copy.js +50 -0
  231. data/lib/stream_weaver/assets/js/sw-heredoc-rewrite.js +68 -0
  232. data/lib/stream_weaver/assets/js/sw-keyboard.js +165 -0
  233. data/lib/stream_weaver/assets/js/sw-mermaid-zoom.js +621 -0
  234. data/lib/stream_weaver/assets/js/sw-route-tabs.js +53 -0
  235. data/lib/stream_weaver/assets/js/sw-sidebar-toc.js +116 -0
  236. data/lib/stream_weaver/assets/js/sw-slide-nav.js +103 -0
  237. data/lib/stream_weaver/canvas/bridge.rb +239 -0
  238. data/lib/stream_weaver/canvas/bridge_server.rb +646 -0
  239. data/lib/stream_weaver/canvas/client.rb +298 -0
  240. data/lib/stream_weaver/canvas/doc_roots.rb +233 -0
  241. data/lib/stream_weaver/canvas/doc_store.rb +252 -0
  242. data/lib/stream_weaver/canvas/gist_publisher.rb +264 -0
  243. data/lib/stream_weaver/canvas/gist_save_handler.rb +89 -0
  244. data/lib/stream_weaver/canvas/gist_store.rb +135 -0
  245. data/lib/stream_weaver/canvas/helpers.rb +109 -0
  246. data/lib/stream_weaver/canvas/history.rb +90 -0
  247. data/lib/stream_weaver/canvas/protocol.rb +86 -0
  248. data/lib/stream_weaver/canvas/reader.rb +853 -0
  249. data/lib/stream_weaver/canvas/save_doc_widget.rb +457 -0
  250. data/lib/stream_weaver/canvas/scroll_top_hint.rb +21 -0
  251. data/lib/stream_weaver/canvas/session.rb +132 -0
  252. data/lib/stream_weaver/cli.rb +3235 -0
  253. data/lib/stream_weaver/component_assets.rb +70 -0
  254. data/lib/stream_weaver/component_registry.rb +67 -0
  255. data/lib/stream_weaver/component_renderer.rb +48 -0
  256. data/lib/stream_weaver/components/annotated_code.rb +53 -0
  257. data/lib/stream_weaver/components/api_endpoint.rb +42 -0
  258. data/lib/stream_weaver/components/callout.rb +59 -0
  259. data/lib/stream_weaver/components/chart.rb +84 -0
  260. data/lib/stream_weaver/components/code_block.rb +77 -0
  261. data/lib/stream_weaver/components/comparison.rb +39 -0
  262. data/lib/stream_weaver/components/decision.rb +38 -0
  263. data/lib/stream_weaver/components/deck/close_overlay.rb +84 -0
  264. data/lib/stream_weaver/components/deck/confirmation_bar.rb +60 -0
  265. data/lib/stream_weaver/components/deck/deck_option.rb +61 -0
  266. data/lib/stream_weaver/components/deck/deck_slide.rb +77 -0
  267. data/lib/stream_weaver/components/deck/deck_state.rb +469 -0
  268. data/lib/stream_weaver/components/deck/deck_summary.rb +73 -0
  269. data/lib/stream_weaver/components/deck/design_deck.rb +60 -0
  270. data/lib/stream_weaver/components/deck/generate_more_controls.rb +84 -0
  271. data/lib/stream_weaver/components/deck/model_selector.rb +84 -0
  272. data/lib/stream_weaver/components/deck/skeleton_placeholder.rb +36 -0
  273. data/lib/stream_weaver/components/diff_block.rb +125 -0
  274. data/lib/stream_weaver/components/doc_header.rb +57 -0
  275. data/lib/stream_weaver/components/image_block.rb +70 -0
  276. data/lib/stream_weaver/components/implementation_map.rb +33 -0
  277. data/lib/stream_weaver/components/keyboard_shortcuts.rb +94 -0
  278. data/lib/stream_weaver/components/kpi_dashboard.rb +78 -0
  279. data/lib/stream_weaver/components/mermaid.rb +79 -0
  280. data/lib/stream_weaver/components/pipeline.rb +63 -0
  281. data/lib/stream_weaver/components/sidebar_toc.rb +46 -0
  282. data/lib/stream_weaver/components/slide_container.rb +141 -0
  283. data/lib/stream_weaver/components/timeline_event.rb +58 -0
  284. data/lib/stream_weaver/components/wireframe.rb +29 -0
  285. data/lib/stream_weaver/components/wireframe_block.rb +34 -0
  286. data/lib/stream_weaver/components.rb +2711 -0
  287. data/lib/stream_weaver/css.rb +230 -0
  288. data/lib/stream_weaver/dev_fallback_overlay.rb +61 -0
  289. data/lib/stream_weaver/display_dsl.rb +1004 -0
  290. data/lib/stream_weaver/export/html_exporter.rb +478 -0
  291. data/lib/stream_weaver/feed.rb +34 -0
  292. data/lib/stream_weaver/feed_builder.rb +31 -0
  293. data/lib/stream_weaver/fonts.rb +33 -0
  294. data/lib/stream_weaver/interaction_runner.rb +487 -0
  295. data/lib/stream_weaver/iterm.rb +460 -0
  296. data/lib/stream_weaver/layout_registry.rb +92 -0
  297. data/lib/stream_weaver/opal/bridge.rb +52 -0
  298. data/lib/stream_weaver/opal/builder.rb +193 -0
  299. data/lib/stream_weaver/opal/env.rb +29 -0
  300. data/lib/stream_weaver/opal/reactive_state.rb +75 -0
  301. data/lib/stream_weaver/opal/regexp_anchor_patch.rb +62 -0
  302. data/lib/stream_weaver/opal/renderer.rb +77 -0
  303. data/lib/stream_weaver/opal/runtime.rb +250 -0
  304. data/lib/stream_weaver/opal/shell.rb +145 -0
  305. data/lib/stream_weaver/opal/string_bridge.rb +50 -0
  306. data/lib/stream_weaver/opal/stubs/diff.min.js +1 -0
  307. data/lib/stream_weaver/opal/stubs/digest.rb +15 -0
  308. data/lib/stream_weaver/opal/stubs/marked.umd.js +79 -0
  309. data/lib/stream_weaver/opal/stubs/md5.rb +3 -0
  310. data/lib/stream_weaver/opal/stubs/morphdom.min.js +775 -0
  311. data/lib/stream_weaver/opal/stubs/prism-tomorrow.min.css +1 -0
  312. data/lib/stream_weaver/opal/stubs/prism.min.js +1967 -0
  313. data/lib/stream_weaver/opal_entry.rb +136 -0
  314. data/lib/stream_weaver/org/inline.rb +95 -0
  315. data/lib/stream_weaver/org/reader.rb +561 -0
  316. data/lib/stream_weaver/org/recording_context.rb +86 -0
  317. data/lib/stream_weaver/org/source_splitter.rb +63 -0
  318. data/lib/stream_weaver/org/writer.rb +300 -0
  319. data/lib/stream_weaver/page_shell.rb +530 -0
  320. data/lib/stream_weaver/portfile.rb +79 -0
  321. data/lib/stream_weaver/pushable.rb +42 -0
  322. data/lib/stream_weaver/resource/default_views.rb +111 -0
  323. data/lib/stream_weaver/resource/field_input.rb +25 -0
  324. data/lib/stream_weaver/resource/state_keys.rb +16 -0
  325. data/lib/stream_weaver/resource/store.rb +18 -0
  326. data/lib/stream_weaver/resource.rb +104 -0
  327. data/lib/stream_weaver/server.rb +1471 -0
  328. data/lib/stream_weaver/service.rb +1240 -0
  329. data/lib/stream_weaver/service_client.rb +104 -0
  330. data/lib/stream_weaver/session_store.rb +186 -0
  331. data/lib/stream_weaver/skills/streamweaver-canvas-safe/SKILL.md +66 -0
  332. data/lib/stream_weaver/skills/streamweaver-canvas-safe/examples/canvas-safe-showcase.rb +98 -0
  333. data/lib/stream_weaver/skills/streamweaver-canvas-safe/references/actions-and-buttons.md +47 -0
  334. data/lib/stream_weaver/skills/streamweaver-canvas-safe/references/charts-and-diagrams.md +38 -0
  335. data/lib/stream_weaver/skills/streamweaver-canvas-safe/references/deck.md +32 -0
  336. data/lib/stream_weaver/skills/streamweaver-canvas-safe/references/inputs-and-forms.md +65 -0
  337. data/lib/stream_weaver/skills/streamweaver-canvas-safe/references/tabs-and-navigation.md +53 -0
  338. data/lib/stream_weaver/skills/streamweaver-doc-builder/SKILL.md +298 -0
  339. data/lib/stream_weaver/skills/streamweaver-visual-companion/SKILL.md +130 -0
  340. data/lib/stream_weaver/skills/streamweaver-visual-companion/examples/design-review-example.css +468 -0
  341. data/lib/stream_weaver/skills/streamweaver-visual-companion/examples/design-review-example.rb +39 -0
  342. data/lib/stream_weaver/skills/streamweaver-visual-companion/examples/design-review-example_dsl.rb +300 -0
  343. data/lib/stream_weaver/skills/streamweaver-visual-companion/examples/doc-parity-example.rb +24 -0
  344. data/lib/stream_weaver/skills/streamweaver-visual-companion/examples/doc-parity-example_dsl.rb +385 -0
  345. data/lib/stream_weaver/skills/streamweaver-visual-companion/references/checkpoints-and-forms.md +28 -0
  346. data/lib/stream_weaver/skills/streamweaver-visual-companion/references/cleanup-and-panel.md +44 -0
  347. data/lib/stream_weaver/skills/streamweaver-visual-companion/references/example-gallery.md +8 -0
  348. data/lib/stream_weaver/skills/streamweaver-visual-companion/references/persistence.md +58 -0
  349. data/lib/stream_weaver/skills/streamweaver-way/SKILL.md +396 -0
  350. data/lib/stream_weaver/skills/visual-plan/SKILL.md +201 -0
  351. data/lib/stream_weaver/skills/visual-recap/SKILL.md +244 -0
  352. data/lib/stream_weaver/streamer.rb +63 -0
  353. data/lib/stream_weaver/templates/choices.rb +149 -0
  354. data/lib/stream_weaver/templates/code.rb +157 -0
  355. data/lib/stream_weaver/templates/confirm.rb +110 -0
  356. data/lib/stream_weaver/templates/diff.rb +195 -0
  357. data/lib/stream_weaver/templates/info.rb +142 -0
  358. data/lib/stream_weaver/templates/table.rb +169 -0
  359. data/lib/stream_weaver/templates/wizard.rb +271 -0
  360. data/lib/stream_weaver/theme/auto_mode.rb +116 -0
  361. data/lib/stream_weaver/theme/presets.rb +555 -0
  362. data/lib/stream_weaver/theme.rb +639 -0
  363. data/lib/stream_weaver/university/canvas.rb +924 -0
  364. data/lib/stream_weaver/university/course.rb +699 -0
  365. data/lib/stream_weaver/university/demos/counter.rb +33 -0
  366. data/lib/stream_weaver/university/demos/dashboard.rb +147 -0
  367. data/lib/stream_weaver/university/demos/decision_form.rb +143 -0
  368. data/lib/stream_weaver/university/demos.rb +50 -0
  369. data/lib/stream_weaver/university/listener.rb +467 -0
  370. data/lib/stream_weaver/university/progress.rb +201 -0
  371. data/lib/stream_weaver/university/runner.rb +138 -0
  372. data/lib/stream_weaver/university/scripts/growing_doc.rb +609 -0
  373. data/lib/stream_weaver/university/scripts/growing_doc_state.rb +91 -0
  374. data/lib/stream_weaver/utils.rb +31 -0
  375. data/lib/stream_weaver/version.rb +5 -0
  376. data/lib/stream_weaver/views/canvas/reader_layout.erb +800 -0
  377. data/lib/stream_weaver/views.rb +3940 -0
  378. data/lib/stream_weaver.rb +122 -0
  379. data/llms.txt +1269 -0
  380. data/sig/stream_weaver.rbs +4 -0
  381. metadata +598 -0
@@ -0,0 +1,772 @@
1
+ # How StreamWeaver Works: A Deep Dive
2
+
3
+ This document explains the internal architecture of StreamWeaver - how a Ruby DSL on the server handles frontend events and runs backend code.
4
+
5
+ ## The Core Innovation
6
+
7
+ StreamWeaver's key insight: **Your Ruby code re-executes on every user interaction**. When a user types in a text field or clicks a button, the entire DSL block is re-evaluated with the new state. This creates a reactive programming model where your code runs on the server but responds to frontend events.
8
+
9
+ ```ruby
10
+ app "Counter" do
11
+ # This block runs EVERY TIME any input changes or button is clicked
12
+ text "Count: #{state[:count] || 0}"
13
+
14
+ button "Increment" do |s|
15
+ s[:count] = (s[:count] || 0) + 1
16
+ end
17
+ end
18
+ ```
19
+
20
+ ## Architecture Overview
21
+
22
+ ```
23
+ ┌─────────────────────────────────────────────────────────────────────┐
24
+ │ BROWSER │
25
+ │ ┌──────────────────────────────────────────────────────────────┐ │
26
+ │ │ Alpine.js (x-model) HTMX (hx-post) │ │
27
+ │ │ - Captures input values - Sends POST requests │ │
28
+ │ │ - Client-side state - Swaps HTML responses │ │
29
+ │ └──────────────────────────────────────────────────────────────┘ │
30
+ │ │ │
31
+ │ ▼ HTTP POST │
32
+ └─────────────────────────────────────────────────────────────────────┘
33
+
34
+
35
+ ┌─────────────────────────────────────────────────────────────────────┐
36
+ │ SERVER │
37
+ │ ┌─────────────┐ ┌────────────────┐ ┌──────────────────────┐ │
38
+ │ │ SinatraApp │ │ App │ │ Adapter │ │
39
+ │ │ (Routes) │──▶│ (DSL + State) │──▶│ (HTML Generation) │ │
40
+ │ │ │ │ │ │ │ │
41
+ │ │ /update │ │ rebuild_with_ │ │ render_text_field() │ │
42
+ │ │ /action/:id │ │ state() │ │ render_button() │ │
43
+ │ │ /submit │ │ │ │ render_*() │ │
44
+ │ └─────────────┘ └────────────────┘ └──────────────────────┘ │
45
+ │ │ │
46
+ │ ▼ │
47
+ │ ┌────────────────┐ │
48
+ │ │ Views │ │
49
+ │ │ (Phlex HTML) │ │
50
+ │ └────────────────┘ │
51
+ └─────────────────────────────────────────────────────────────────────┘
52
+ ```
53
+
54
+ ## The Request-Response Cycle
55
+
56
+ ### 1. Initial Page Load (GET /)
57
+
58
+ ```
59
+ Browser ─────GET /─────▶ SinatraApp
60
+
61
+
62
+ App.rebuild_with_state({})
63
+
64
+
65
+ Execute DSL block (instance_eval)
66
+
67
+
68
+ Build component tree
69
+
70
+
71
+ Adapter renders HTML with HTMX/Alpine attributes
72
+
73
+
74
+ AppView generates full page
75
+
76
+ ◀────────HTML────────────────┘
77
+ ```
78
+
79
+ ### 2. Input Change (typing in text field)
80
+
81
+ ```
82
+ User types "hello"
83
+
84
+
85
+ Alpine.js x-model captures value
86
+
87
+
88
+ HTMX triggers POST /update (after debounce)
89
+
90
+
91
+ SinatraApp#post '/update'
92
+
93
+ ├─▶ state = session[:streamlit_state]
94
+ ├─▶ sync_params_to_state(state) # state[:name] = "hello"
95
+ ├─▶ app.rebuild_with_state(state) # Re-run DSL with new state
96
+ └─▶ AppContentView renders HTML
97
+
98
+ ◀────────HTML partial─────────────────────┘
99
+
100
+
101
+ HTMX swaps #app-container innerHTML
102
+ ```
103
+
104
+ ### 3. Button Click (executing a callback)
105
+
106
+ ```
107
+ User clicks "Submit"
108
+
109
+
110
+ HTMX POST /action/btn_submit_abc123
111
+
112
+
113
+ SinatraApp#post '/action/:button_id'
114
+
115
+ ├─▶ Find button by ID in component tree
116
+ ├─▶ button.execute(state) # Runs the Ruby block!
117
+ │ │
118
+ │ └─▶ Your code: puts state[:name]
119
+
120
+ ├─▶ app.rebuild_with_state(state)
121
+ └─▶ AppContentView renders HTML
122
+
123
+ ◀────────HTML partial─────────────────────┘
124
+ ```
125
+
126
+ ## Core Classes Explained
127
+
128
+ ### App (lib/stream_weaver/app.rb)
129
+
130
+ The `App` class is the heart of the DSL. It:
131
+
132
+ 1. **Stores the DSL block** - The block you pass to `app` is saved and re-executed
133
+ 2. **Manages state** - Tracks all form values in `@_state`
134
+ 3. **Builds component tree** - DSL methods like `text_field`, `button` create Component instances
135
+ 4. **Provides DSL methods** - All the UI building methods are defined here
136
+
137
+ ```ruby
138
+ class App
139
+ def initialize(title, &block)
140
+ @block = block # Your DSL code
141
+ @_state = {} # Current state
142
+ @components = [] # Component tree
143
+ end
144
+
145
+ # This is the magic - re-runs your code with fresh state
146
+ def rebuild_with_state(current_state)
147
+ @_state = current_state
148
+ @components = [] # Clear old components
149
+ instance_eval(&@block) # Execute YOUR code
150
+ end
151
+
152
+ # DSL methods create components
153
+ def text_field(key, **options)
154
+ @_state[key] ||= "" # Initialize state
155
+ @components << Components::TextField.new(key, **options)
156
+ end
157
+
158
+ def button(label, **options, &block)
159
+ @components << Components::Button.new(label, &block)
160
+ end
161
+ end
162
+ ```
163
+
164
+ ### SinatraApp (lib/stream_weaver/server.rb)
165
+
166
+ The generated Sinatra application that handles HTTP requests:
167
+
168
+ | Route | Purpose |
169
+ |-------|---------|
170
+ | `GET /` | Initial page load |
171
+ | `POST /update` | State sync from input changes |
172
+ | `POST /action/:button_id` | Button click handlers |
173
+ | `POST /submit` | Agentic mode final submission |
174
+ | `POST /event/:key` | on_change/on_blur callbacks |
175
+ | `POST /form/:form_name` | Deferred form submission |
176
+ | `POST /theme/:theme_name` | Runtime theme switching |
177
+
178
+ Key methods:
179
+
180
+ ```ruby
181
+ # Sync form params to state hash
182
+ def sync_params_to_state(state, excluded_keys: [])
183
+ params.each do |key, value|
184
+ state[key.to_sym] = coerce_param_value(value, state[key.to_sym])
185
+ end
186
+ end
187
+
188
+ # Find button in nested component tree
189
+ def self.find_button_recursive(components, button_id)
190
+ components.each do |component|
191
+ return component if component.is_a?(Components::Button) && component.id == button_id
192
+ if component.respond_to?(:children) && component.children
193
+ found = find_button_recursive(component.children, button_id)
194
+ return found if found
195
+ end
196
+ end
197
+ nil
198
+ end
199
+ ```
200
+
201
+ ### Adapter::AlpineJS (lib/stream_weaver/adapter/alpinejs.rb)
202
+
203
+ The adapter is responsible for rendering components with the right HTML attributes for the frontend framework (Alpine.js + HTMX).
204
+
205
+ ```ruby
206
+ def render_text_field(view, key, options, state)
207
+ view.input(
208
+ type: "text",
209
+ name: key.to_s,
210
+ value: state[key] || "",
211
+ "x-model" => key.to_s, # Alpine.js 2-way binding
212
+ "hx-post" => url("/update"), # HTMX POST on change
213
+ "hx-include" => "[x-model]", # Include all bound inputs
214
+ "hx-target" => "#app-container", # Replace this element
215
+ "hx-swap" => "innerHTML scroll:false", # Swap method
216
+ "hx-trigger" => "keyup changed delay:500ms" # Debounced trigger
217
+ )
218
+ end
219
+
220
+ def render_button(view, button_id, label, options, modal_context)
221
+ view.button(
222
+ "hx-post" => url("/action/#{button_id}"),
223
+ "hx-include" => "[x-model]",
224
+ "hx-target" => "#app-container",
225
+ "hx-swap" => "innerHTML scroll:false"
226
+ ) { label }
227
+ end
228
+ ```
229
+
230
+ ### Components (lib/stream_weaver/components.rb)
231
+
232
+ Each UI element is a component class. Components:
233
+ - Store their configuration
234
+ - Delegate rendering to the adapter
235
+ - Can have callbacks (on_change, on_blur)
236
+
237
+ ```ruby
238
+ class TextField < Base
239
+ include Callbacks
240
+ attr_reader :key
241
+
242
+ def initialize(key, on_change: nil, **options)
243
+ @key = key
244
+ @options = options
245
+ init_callbacks(on_change: on_change)
246
+ end
247
+
248
+ def render(view, state)
249
+ view.adapter.render_text_field(view, @key, @options, state)
250
+ end
251
+ end
252
+
253
+ class Button < Base
254
+ def initialize(label, stable_id, **options, &block)
255
+ @label = label
256
+ @action = block # Your Ruby callback
257
+ @button_id = "btn_#{label}_#{stable_id}"
258
+ end
259
+
260
+ def execute(state)
261
+ @action.call(state) if @action # Runs YOUR code!
262
+ end
263
+ end
264
+ ```
265
+
266
+ ### Views (lib/stream_weaver/views.rb)
267
+
268
+ Phlex-based views render the final HTML:
269
+
270
+ - **AppView** - Full page with `<html>`, `<head>`, `<body>`, CSS, JS
271
+ - **AppContentView** - Partial for HTMX swaps (just the component HTML)
272
+
273
+ ```ruby
274
+ class AppView < Phlex::HTML
275
+ def view_template
276
+ doctype
277
+ html do
278
+ head do
279
+ @adapter.render_cdn_scripts(self) # HTMX, Alpine.js
280
+ style { raw(safe(CSS_CONTENT)) } # Built-in CSS
281
+ end
282
+ body(class: body_classes) do
283
+ h1 { @app.title }
284
+ div(id: "app-container", **@adapter.container_attributes(@state)) do
285
+ @app.components.each { |c| c.render(self, @state) }
286
+ end
287
+ end
288
+ end
289
+ end
290
+ end
291
+ ```
292
+
293
+ ## Service Mode (lib/stream_weaver/service.rb)
294
+
295
+ Service mode runs a persistent server that can host multiple apps:
296
+
297
+ ```
298
+ ┌─────────────────────────────────────────────────────────────────────┐
299
+ │ StreamWeaver Service │
300
+ │ │
301
+ │ ┌───────────────────┐ ┌───────────────────┐ │
302
+ │ │ App: abc123 │ │ App: def456 │ │
303
+ │ │ hello_world.rb │ │ todo_list.rb │ │
304
+ │ │ /apps/abc123 │ │ /apps/def456 │ │
305
+ │ └───────────────────┘ └───────────────────┘ │
306
+ │ │
307
+ │ ┌───────────────────────────────────────────────────────────────┐ │
308
+ │ │ Live Sessions │ │
309
+ │ │ │ │
310
+ │ │ claude_chat_1: │ │
311
+ │ │ - Browser polls /live/claude_chat_1/poll │ │
312
+ │ │ - Claude pushes via /live/claude_chat_1/push │ │
313
+ │ │ - User actions stored for Claude to retrieve │ │
314
+ │ └───────────────────────────────────────────────────────────────┘ │
315
+ └─────────────────────────────────────────────────────────────────────┘
316
+ ```
317
+
318
+ ### Service Features:
319
+
320
+ 1. **Multi-app hosting** - Load apps dynamically via `/load-app`
321
+ 2. **Live Sessions** - Polling-based update-in-place for agentic workflows
322
+ 3. **Source-based routing** - Aliased URLs like `/examples/hello_world`
323
+ 4. **Background launch** - `Service.launch_background` spawns a detached process
324
+
325
+ ### Live Sessions Architecture
326
+
327
+ Live sessions enable Claude Code or other agents to push UI updates:
328
+
329
+ ```ruby
330
+ # Claude pushes content
331
+ POST /live/my_session/push
332
+ target: "#main"
333
+ content: "<h1>Hello!</h1>"
334
+ action: "replace"
335
+
336
+ # Browser polls for updates
337
+ GET /live/my_session/poll?since=1234567890
338
+ # Returns any updates since timestamp
339
+
340
+ # User interaction creates submission
341
+ POST /live/my_session/submit
342
+ # Form data stored for Claude to retrieve
343
+
344
+ # Claude retrieves user input
345
+ GET /live/my_session/submissions
346
+ # Returns pending submissions
347
+ ```
348
+
349
+ ## State Management
350
+
351
+ State flows through the system:
352
+
353
+ ```
354
+ ┌──────────────┐
355
+ │ Session │
356
+ │ Cookie Store │
357
+ └──────────────┘
358
+
359
+
360
+ ┌──────────────────────────────────────────────────────────────────┐
361
+ │ State Hash │
362
+ │ │
363
+ │ { │
364
+ │ name: "hello", # From text_field :name │
365
+ │ active: true, # From checkbox :active │
366
+ │ settings: { # From form :settings │
367
+ │ email: "x@y.com", │
368
+ │ notify: true │
369
+ │ }, │
370
+ │ my_tabs: 0, # From tabs :my_tabs (active index) │
371
+ │ confirm_open: false, # From modal :confirm (open state) │
372
+ │ _toasts: [...] # Internal toast queue │
373
+ │ } │
374
+ └──────────────────────────────────────────────────────────────────┘
375
+
376
+
377
+ App.rebuild_with_state(state)
378
+
379
+
380
+ DSL accesses via state[:key]
381
+ ```
382
+
383
+ ### State Initialization
384
+
385
+ Components initialize their state keys when the DSL executes:
386
+
387
+ ```ruby
388
+ def text_field(key, **options)
389
+ # Initialize if not present
390
+ @_state[key] ||= ""
391
+ @components << Components::TextField.new(key, **options)
392
+ end
393
+
394
+ def checkbox(key, label, **options)
395
+ @_state[key] ||= false
396
+ @components << Components::Checkbox.new(key, label, **options)
397
+ end
398
+
399
+ def tabs(key, **options, &block)
400
+ @_state[key] ||= 0 # Default to first tab
401
+ # ...
402
+ end
403
+ ```
404
+
405
+ ## Frontend Integration
406
+
407
+ ### Alpine.js Role
408
+
409
+ Alpine.js provides:
410
+ - **`x-data`** - Initializes reactive state on #app-container
411
+ - **`x-model`** - Two-way binding between inputs and state
412
+ - **`x-show`** - Conditional display (tabs, modals, tooltips)
413
+ - **`@click`** - Event handlers for client-only interactions
414
+
415
+ ```html
416
+ <div id="app-container" x-data="{name: '', active: false, my_tabs: 0}">
417
+ <input type="text" x-model="name" hx-post="/update" ...>
418
+ <div x-show="my_tabs === 0">Tab 1 content</div>
419
+ </div>
420
+ ```
421
+
422
+ ### HTMX Role
423
+
424
+ HTMX handles server communication:
425
+ - **`hx-post`** - Where to send the request
426
+ - **`hx-include`** - What data to include (`[x-model]` = all bound inputs)
427
+ - **`hx-target`** - What to replace (`#app-container`)
428
+ - **`hx-swap`** - How to replace (`innerHTML scroll:false`)
429
+ - **`hx-trigger`** - When to fire (`keyup changed delay:500ms`)
430
+
431
+ ### The Defer Mutations Pattern
432
+
433
+ A critical integration pattern prevents Alpine.js from overwriting HTMX updates:
434
+
435
+ ```javascript
436
+ // Before swap: pause Alpine's mutation observer
437
+ document.addEventListener('htmx:beforeSwap', function(e) {
438
+ Alpine.deferMutations();
439
+ });
440
+
441
+ // After settle: reinitialize Alpine with fresh state from server
442
+ document.addEventListener('htmx:afterSettle', function(e) {
443
+ const stateEl = document.getElementById('sw-state-data');
444
+ const container = document.getElementById('app-container');
445
+ const newState = JSON.parse(stateEl.textContent);
446
+ const alpineData = Alpine.$data(container);
447
+ Object.keys(newState).forEach(key => {
448
+ alpineData[key] = newState[key];
449
+ });
450
+ Alpine.flushAndStopDeferringMutations();
451
+ });
452
+ ```
453
+
454
+ ## Execution Modes
455
+
456
+ ### 1. Standalone Mode (`App.run!`)
457
+
458
+ Traditional persistent server:
459
+
460
+ ```ruby
461
+ app "My App" do
462
+ # ...
463
+ end.run!
464
+ ```
465
+
466
+ - Opens browser automatically
467
+ - Runs until Ctrl+C
468
+ - State persists in session
469
+
470
+ ### 2. Agentic Mode (`App.run_once!`)
471
+
472
+ One-shot form collection for AI agents:
473
+
474
+ ```ruby
475
+ result = app "Get User Info" do
476
+ text_field :name
477
+ text_field :email
478
+ end.run_once!(auto_close_window: true)
479
+
480
+ puts result # => {name: "John", email: "john@example.com"}
481
+ ```
482
+
483
+ - Shows a "Submit to Agent" button
484
+ - Returns data when submitted
485
+ - Auto-closes browser (optional)
486
+ - Timeout after 300s (configurable)
487
+
488
+ ### 3. Service Mode (multi-app)
489
+
490
+ Run multiple apps on one server:
491
+
492
+ ```bash
493
+ # Start service
494
+ streamweaver service start
495
+
496
+ # Load apps
497
+ streamweaver run hello.rb
498
+ streamweaver run todo.rb
499
+ ```
500
+
501
+ Each app gets its own route (`/apps/{id}`) and isolated state.
502
+
503
+ ## Form System
504
+
505
+ ### Immediate Mode (default)
506
+
507
+ Every input change triggers a server round-trip:
508
+
509
+ ```ruby
510
+ text_field :name # POST /update on every keystroke (debounced)
511
+ ```
512
+
513
+ ### Deferred Mode (form blocks)
514
+
515
+ Group inputs and submit together:
516
+
517
+ ```ruby
518
+ form :settings do
519
+ text_field :email
520
+ checkbox :notify, "Send notifications"
521
+
522
+ submit "Save" do |values|
523
+ # values = {email: "...", notify: true}
524
+ save_settings(values)
525
+ end
526
+ end
527
+ ```
528
+
529
+ Form inputs use Alpine-only binding until submit, then POST to `/form/:name`.
530
+
531
+ ## Callbacks and Events
532
+
533
+ ### Button Callbacks
534
+
535
+ ```ruby
536
+ button "Submit" do |state|
537
+ # This Ruby code runs on the server when clicked
538
+ puts state[:name]
539
+ state[:submitted] = true
540
+ end
541
+ ```
542
+
543
+ ### Input Callbacks
544
+
545
+ ```ruby
546
+ text_field :email, on_change: ->(state, value) {
547
+ state[:valid] = value.include?("@")
548
+ }
549
+
550
+ text_field :search, on_blur: ->(state, value) {
551
+ state[:results] = search_api(value)
552
+ }
553
+ ```
554
+
555
+ Routes to `/event/:key` instead of `/update` when callbacks are present.
556
+
557
+ ## Additional DSL Features
558
+
559
+ ### App Configuration Options
560
+
561
+ ```ruby
562
+ app "My App",
563
+ layout: :wide, # :default, :wide, :full, :fluid
564
+ theme: :dashboard, # :default, :dashboard, :document
565
+ theme_overrides: { color_primary: "#0066cc" },
566
+ scripts: ["custom.js"],
567
+ stylesheets: ["custom.css"],
568
+ components: [MyHelpers] # Mixin modules
569
+ do
570
+ # ...
571
+ end
572
+ ```
573
+
574
+ ### Dynamic Content with Procs
575
+
576
+ Text and markdown can accept procs for dynamic content:
577
+
578
+ ```ruby
579
+ # Static
580
+ text "Hello"
581
+
582
+ # Dynamic - re-evaluated each render
583
+ text -> (s) { "Count: #{s[:count]}" }
584
+
585
+ # Also works with markdown
586
+ md -> (s) { "**Status:** #{s[:status]}" }
587
+ ```
588
+
589
+ ### Header Aliases
590
+
591
+ ```ruby
592
+ header1 "Main Title" # <h1>
593
+ header2 "Section" # <h2>
594
+ header3 "Subsection" # <h3>
595
+ header "Also h2" # alias for header2
596
+ ```
597
+
598
+ ### Markdown Shorthand
599
+
600
+ ```ruby
601
+ md "**Bold** and *italic*"
602
+ markdown "Same as md" # alias
603
+ ```
604
+
605
+ ### Layout Components
606
+
607
+ ```ruby
608
+ # Vertical/horizontal stacking
609
+ vstack spacing: :md do
610
+ text "Item 1"
611
+ text "Item 2"
612
+ end
613
+
614
+ hstack spacing: :sm, justify: :between do
615
+ button "Left"
616
+ button "Right"
617
+ end
618
+
619
+ # Multi-column layouts
620
+ columns widths: ['30%', '70%'] do
621
+ column { text "Sidebar" }
622
+ column { text "Main content" }
623
+ end
624
+
625
+ # Grid layouts
626
+ grid columns: 3, gap: :md do
627
+ card { text "Card 1" }
628
+ card { text "Card 2" }
629
+ card { text "Card 3" }
630
+ end
631
+ ```
632
+
633
+ ### Container Components
634
+
635
+ ```ruby
636
+ # Cards with sections
637
+ card do
638
+ card_header "Title"
639
+ card_body do
640
+ text "Content"
641
+ end
642
+ card_footer do
643
+ button "Action"
644
+ end
645
+ end
646
+
647
+ # Collapsible sections
648
+ collapsible "More Details", expanded: false do
649
+ text "Hidden content"
650
+ end
651
+ ```
652
+
653
+ ### Navigation Components
654
+
655
+ ```ruby
656
+ # Tabs
657
+ tabs :my_tabs, variant: :line do # :line, :enclosed, :soft-rounded
658
+ tab "First" do
659
+ text "Tab 1 content"
660
+ end
661
+ tab "Second" do
662
+ text "Tab 2 content"
663
+ end
664
+ end
665
+
666
+ # Breadcrumbs
667
+ breadcrumbs separator: ">" do
668
+ crumb "Home", href: "/"
669
+ crumb "Products", href: "/products"
670
+ crumb "Current" # No href = current page
671
+ end
672
+
673
+ # Dropdowns
674
+ dropdown do
675
+ trigger { button "Menu" }
676
+ menu do
677
+ menu_item "Edit" do |s|
678
+ s[:action] = "edit"
679
+ end
680
+ menu_divider
681
+ menu_item "Delete", style: :destructive do |s|
682
+ s[:action] = "delete"
683
+ end
684
+ end
685
+ end
686
+ ```
687
+
688
+ ### Modal Dialogs
689
+
690
+ ```ruby
691
+ # Open modal via button
692
+ button "Show Dialog" do |s|
693
+ s[:confirm_open] = true
694
+ end
695
+
696
+ # Modal definition
697
+ modal :confirm, title: "Confirm", size: :sm do
698
+ text "Are you sure?"
699
+
700
+ modal_footer do
701
+ button "Yes" do |s|
702
+ s[:confirmed] = true
703
+ s[:confirm_open] = false
704
+ end
705
+ button "Cancel", style: :secondary do |s|
706
+ s[:confirm_open] = false
707
+ end
708
+ end
709
+ end
710
+ ```
711
+
712
+ ### Feedback Components
713
+
714
+ ```ruby
715
+ # Alerts
716
+ alert(variant: :success) { text "Saved!" }
717
+ alert(variant: :error, title: "Error") { text "Failed to save" }
718
+
719
+ # Progress
720
+ progress_bar value: 75, show_label: true
721
+
722
+ # Spinner
723
+ spinner size: :md, label: "Loading..."
724
+
725
+ # Toasts (notifications)
726
+ toast_container position: :top_right
727
+
728
+ button "Save" do |s|
729
+ show_toast("Saved successfully!", variant: :success)
730
+ end
731
+ ```
732
+
733
+ ### Charts
734
+
735
+ ```ruby
736
+ bar_chart data: { "A" => 10, "B" => 20 }, title: "Sales"
737
+ line_chart labels: ["Jan", "Feb"], values: [10, 20]
738
+ pie_chart data: :chart_data # From state
739
+ ```
740
+
741
+ ### Helper Module Pattern
742
+
743
+ Extend the DSL with custom methods:
744
+
745
+ ```ruby
746
+ module MyHelpers
747
+ def user_card(user)
748
+ card do
749
+ header3 user[:name]
750
+ text user[:email]
751
+ end
752
+ end
753
+ end
754
+
755
+ app "Users", components: [MyHelpers] do
756
+ state[:users].each do |user|
757
+ user_card(user)
758
+ end
759
+ end
760
+ ```
761
+
762
+ ## Summary: The Magic in 7 Steps
763
+
764
+ 1. **You write a DSL block** - Ruby code that builds UI
765
+ 2. **Block is stored in App** - Not executed immediately
766
+ 3. **User interacts in browser** - Alpine.js captures, HTMX sends
767
+ 4. **Server receives request** - Sinatra route handles it
768
+ 5. **State is updated** - From form params or button callback
769
+ 6. **DSL block re-executes** - `instance_eval(&@block)` with new state
770
+ 7. **Fresh HTML returned** - HTMX swaps it in, loop continues
771
+
772
+ Your Ruby code IS the UI definition AND the event handlers. The framework handles the plumbing to make this seamless.