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,247 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Demo of the Table component with all features
4
+ # Run: ruby examples/components/table_demo.rb
5
+ # Open: http://localhost:4567
6
+
7
+ require_relative '../../lib/stream_weaver'
8
+
9
+ # Sample data for demos
10
+ USERS = [
11
+ { name: "Alice Johnson", email: "alice@example.com", balance: 1234.56, joined: Date.new(2024, 3, 15), active: true },
12
+ { name: "Bob Smith", email: "bob@example.com", balance: 567.89, joined: Date.new(2024, 6, 22), active: true },
13
+ { name: "Carol Davis", email: "carol@example.com", balance: 89012.34, joined: Date.new(2023, 11, 1), active: false },
14
+ { name: "Dan Wilson", email: "dan@example.com", balance: 234.00, joined: Date.new(2025, 1, 5), active: true }
15
+ ].freeze
16
+
17
+ METRICS = {
18
+ month: %w[Jan Feb Mar Apr May Jun],
19
+ revenue: [45000, 52000, 48000, 61000, 55000, 67000],
20
+ users: [120, 145, 132, 178, 165, 203]
21
+ }.freeze
22
+
23
+ app "Table Component Demo", layout: :wide do
24
+ header "Table Component - All Features"
25
+ text "Demonstrating the enhanced table component with smart data inference, formatters, and interactive features."
26
+
27
+ tabs :demo_tabs do
28
+ # Tab 1: Basic Usage
29
+ tab "Basic" do
30
+ vstack spacing: :lg do
31
+ card do
32
+ header3 "Original API"
33
+ text "The original headers/rows API still works:"
34
+
35
+ table headers: ["Name", "Role", "Status"],
36
+ rows: [
37
+ ["Alice", "Engineer", "Active"],
38
+ ["Bob", "Designer", "Active"],
39
+ ["Carol", "Manager", "On Leave"]
40
+ ],
41
+ striped: true
42
+ end
43
+
44
+ card do
45
+ header3 "Array of Hashes (Auto-Infer Headers)"
46
+ text "Pass an array of hashes - headers are inferred from keys:"
47
+
48
+ table [
49
+ { name: "Alice", role: "Engineer", department: "Platform" },
50
+ { name: "Bob", role: "Designer", department: "Product" },
51
+ { name: "Carol", role: "Manager", department: "Engineering" }
52
+ ], striped: true
53
+ end
54
+
55
+ card do
56
+ header3 "Hash of Arrays"
57
+ text "Pass a hash where keys are columns and values are arrays:"
58
+
59
+ table METRICS, striped: true, compact: true
60
+ end
61
+ end
62
+ end
63
+
64
+ # Tab 2: Column DSL with Formatters
65
+ tab "Formatters" do
66
+ vstack spacing: :lg do
67
+ card do
68
+ header3 "Column DSL with Built-in Formatters"
69
+ text "Define columns with formatting and alignment:"
70
+
71
+ table USERS do
72
+ column :name
73
+ column :email, header: "E-mail"
74
+ column :balance, format: :currency, align: :right
75
+ column :joined, header: "Join Date", format: :date
76
+ column(:active) { |u| u[:active] ? "Yes" : "No" }
77
+ end
78
+ end
79
+
80
+ card do
81
+ header3 "Available Formatters"
82
+ table headers: ["Formatter", "Input", "Output"],
83
+ rows: [
84
+ [":date", "Date.today", Date.today.strftime("%b %d, %Y")],
85
+ [":datetime", "Time.now", Time.now.strftime("%b %d, %Y %l:%M %p")],
86
+ [":currency", "1234.56", "$1,234.56"],
87
+ [":number", "1234567", "1,234,567"],
88
+ [":percent", "0.42", "42%"]
89
+ ],
90
+ bordered: true, compact: true
91
+ end
92
+
93
+ card do
94
+ header3 "Custom Formatter (Proc)"
95
+ text "Use a Proc for custom formatting logic:"
96
+
97
+ table USERS do
98
+ column :name
99
+ column :balance, format: ->(v) { v > 1000 ? "#{(v/1000.0).round(1)}k" : v.to_s }, align: :right
100
+ column(:status) { |u| u[:active] ? "Active" : "Inactive" }
101
+ end
102
+ end
103
+ end
104
+ end
105
+
106
+ # Tab 3: Interactive Features
107
+ tab "Interactive" do
108
+ vstack spacing: :lg do
109
+ card do
110
+ header3 "Sortable Table"
111
+ text "Click column headers to sort. Handles both text and numeric sorting:"
112
+
113
+ table [
114
+ { product: "Widget A", price: 29.99, quantity: 150, revenue: 4498.50 },
115
+ { product: "Widget B", price: 49.99, quantity: 75, revenue: 3749.25 },
116
+ { product: "Widget C", price: 19.99, quantity: 300, revenue: 5997.00 },
117
+ { product: "Widget D", price: 99.99, quantity: 25, revenue: 2499.75 }
118
+ ], sortable: true, striped: true
119
+ end
120
+
121
+ card do
122
+ header3 "Sticky Header (Scroll to See)"
123
+ text "Header stays visible when scrolling long tables:"
124
+
125
+ # Generate more rows for scrolling demo
126
+ many_rows = (1..20).map do |i|
127
+ { id: i, name: "Item #{i}", category: %w[A B C].sample, value: rand(100..999) }
128
+ end
129
+
130
+ table many_rows, sticky_header: true, striped: true, compact: true
131
+ end
132
+
133
+ card do
134
+ header3 "Combined: Sortable + Sticky"
135
+
136
+ table USERS + USERS + USERS do
137
+ column :name
138
+ column :balance, format: :currency, align: :right
139
+ column :joined, format: :date
140
+ end
141
+ end
142
+ end
143
+ end
144
+
145
+ # Tab 4: Data Sources
146
+ tab "Data Sources" do
147
+ vstack spacing: :lg do
148
+ card do
149
+ header3 "State Binding"
150
+ text "Table reads from state - updates when state changes:"
151
+
152
+ state[:dynamic_items] ||= [
153
+ { item: "Apple", count: 5 },
154
+ { item: "Banana", count: 3 },
155
+ { item: "Cherry", count: 8 }
156
+ ]
157
+
158
+ table data: :dynamic_items, striped: true
159
+
160
+ hstack spacing: :sm do
161
+ button "Add Random Item" do |s|
162
+ fruits = %w[Mango Orange Grape Kiwi Peach]
163
+ s[:dynamic_items] << { item: fruits.sample, count: rand(1..10) }
164
+ end
165
+
166
+ button "Clear", style: :secondary do |s|
167
+ s[:dynamic_items] = []
168
+ end
169
+ end
170
+ end
171
+
172
+ card do
173
+ header3 "File Loading"
174
+ text "Load data from YAML or JSON files (like charts):"
175
+ md "`table file: \"data/users.yaml\", path: \"users\"`"
176
+ text "The file: and path: options work exactly like charts."
177
+ end
178
+
179
+ card do
180
+ header3 "Transform Block"
181
+ text "Process file data before display:"
182
+ md <<~MD
183
+ ```ruby
184
+ table file: "raw_data.yaml" do |data|
185
+ data.map { |r| { name: r[:n], value: r[:v] } }
186
+ end
187
+ ```
188
+ MD
189
+ end
190
+
191
+ card do
192
+ header3 "Markdown Links"
193
+ text "Enable clickable links in cells with markdown: true:"
194
+
195
+ table [
196
+ { issue: "[PROJ-101](https://example.com/issues/101)", title: "Fix login bug", status: "Open" },
197
+ { issue: "[PROJ-102](https://example.com/issues/102)", title: "Add dark mode", status: "In Progress" },
198
+ { issue: "[PROJ-103](https://example.com/issues/103)", title: "Update docs", status: "Closed" }
199
+ ], markdown: true, striped: true
200
+
201
+ text "Without markdown: true, links show as literal text."
202
+ end
203
+ end
204
+ end
205
+
206
+ # Tab 5: Styling
207
+ tab "Styling" do
208
+ columns do
209
+ column do
210
+ card do
211
+ header4 "Default"
212
+ table [{ a: 1, b: 2 }, { a: 3, b: 4 }]
213
+ end
214
+
215
+ card do
216
+ header4 "Striped"
217
+ table [{ a: 1, b: 2 }, { a: 3, b: 4 }, { a: 5, b: 6 }], striped: true
218
+ end
219
+
220
+ card do
221
+ header4 "Bordered"
222
+ table [{ a: 1, b: 2 }, { a: 3, b: 4 }], bordered: true
223
+ end
224
+ end
225
+
226
+ column do
227
+ card do
228
+ header4 "Compact"
229
+ table [{ a: 1, b: 2 }, { a: 3, b: 4 }], compact: true
230
+ end
231
+
232
+ card do
233
+ header4 "With Caption"
234
+ table [{ a: 1, b: 2 }, { a: 3, b: 4 }], caption: "Sample Data"
235
+ end
236
+
237
+ card do
238
+ header4 "All Options"
239
+ table [{ a: 1, b: 2 }, { a: 3, b: 4 }, { a: 5, b: 6 }],
240
+ striped: true, bordered: true, compact: true,
241
+ caption: "Full Style Demo"
242
+ end
243
+ end
244
+ end
245
+ end
246
+ end
247
+ end.run!
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Demonstrates: accessing `state` inside streamer.replace blocks
4
+ #
5
+ # KEY PATTERN: Pass `state: state` to streamer.replace/append/prepend whenever
6
+ # the block (or a helper it calls) needs to read session state.
7
+ #
8
+ # Without `state: state`, the replace block runs in FeedBuilder context where
9
+ # `state` is not defined — you'll see:
10
+ # [StreamWeaver] Timer error: NameError: undefined local variable or method `state'
11
+ #
12
+ # Run with: ruby examples/components/timer_with_state_demo.rb
13
+
14
+ require_relative "../../lib/stream_weaver"
15
+
16
+ # Top-level helper — works inside replace blocks (global methods are accessible).
17
+ # Receives state as an argument (not via DSL) since the helper is display-only.
18
+ def render_status_panel(label:, count:, state:)
19
+ card do
20
+ hstack justify: :between do
21
+ text label
22
+ badge state[:filter] || "all", variant: :info
23
+ end
24
+ stat_display value: count, label: "items", color: :blue
25
+ text "Last refresh: #{Time.now.strftime('%H:%M:%S')}"
26
+ end
27
+ end
28
+
29
+ app "Timer + State Demo" do
30
+ header1 "Timer + State Demo"
31
+ md "Change the filter, then watch the panel refresh every 3 seconds. " \
32
+ "The filter value persists across timer refreshes."
33
+
34
+ state[:filter] ||= "all"
35
+
36
+ select :filter, %w[all active paused archived], submit: true
37
+
38
+ md "---"
39
+
40
+ div id: "status-panel" do
41
+ render_status_panel(
42
+ label: "Showing: #{state[:filter]}",
43
+ count: rand(10..99),
44
+ state: state
45
+ )
46
+ end
47
+
48
+ # CORRECT: pass `state: state` so FeedBuilder exposes it inside the block
49
+ every(3) do |streamer|
50
+ streamer.replace("#status-panel", state: state) do
51
+ render_status_panel(
52
+ label: "Showing: #{state[:filter]}",
53
+ count: rand(10..99),
54
+ state: state
55
+ )
56
+ end
57
+ end
58
+ end.run!
@@ -0,0 +1,63 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Todo list with due dates -- exercises date_field inside a `form` block
5
+ # (stream_weaver-nvu): title + due date submit together as one payload, and
6
+ # the due date correctly lands in state[:new_task][:due_on] rather than
7
+ # leaking into a flat top-level key.
8
+ # Run: ruby examples/components/todo_due_dates.rb
9
+
10
+ require_relative '../../lib/stream_weaver'
11
+ require 'securerandom'
12
+
13
+ App = app "Todo List with Due Dates" do
14
+ header1 "📝 Todo List"
15
+ md "`date_field` inside a `form` block: title + due date submit together."
16
+
17
+ state[:tasks] ||= []
18
+
19
+ header2 "Add a task"
20
+ form :new_task do
21
+ text_field :title, placeholder: "What needs doing?"
22
+ date_field :due_on, label: "Due date", min: Date.today.iso8601
23
+
24
+ submit "Add Task" do |form_values|
25
+ title = form_values[:title].to_s.strip
26
+ next if title.empty?
27
+
28
+ state[:tasks] << { id: SecureRandom.uuid, title: title, due_on: form_values[:due_on] }
29
+ state[:new_task] = { title: "", due_on: "" }
30
+ end
31
+ end
32
+
33
+ header2 "Tasks (#{state[:tasks].length})"
34
+
35
+ if state[:tasks].empty?
36
+ text "No tasks yet -- add one above.", tone: :muted
37
+ else
38
+ sorted_tasks = state[:tasks].sort_by do |task|
39
+ due = StreamWeaver::Components::DateField.to_date(task[:due_on])
40
+ [due ? 0 : 1, due || Date.new(9999, 12, 31)]
41
+ end
42
+
43
+ sorted_tasks.each do |task|
44
+ due = StreamWeaver::Components::DateField.to_date(task[:due_on])
45
+ overdue = due && due < Date.today
46
+
47
+ div class: "todo-item" do
48
+ text task[:title]
49
+ if due
50
+ text "Due #{due.iso8601}#{overdue ? ' (overdue)' : ''}", tone: overdue ? :error : :muted
51
+ else
52
+ text "No due date", tone: :muted
53
+ end
54
+ # key: a stable per-task id, so reordering by due date can't misroute the click
55
+ button "✓", key: task[:id], style: :secondary do |s|
56
+ s[:tasks].reject! { |t| t[:id] == task[:id] }
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+
63
+ App.run! if __FILE__ == $0
@@ -0,0 +1,112 @@
1
+ # frozen_string_literal: true
2
+
3
+ # UAT demo for three framework gaps closed in one session (2026-08-06):
4
+ # 1. Table cell style escape hatches (col.style, id_style:, id_column:)
5
+ # 2. collapsible subtitle:/badge_text:/badge_variant: parity with expandable_card
6
+ # 3. copy_button primitive + code_block copy: affordance
7
+ #
8
+ # Run: SW_NO_OPEN=1 ruby examples/components/uat_gaps_demo.rb
9
+ # Open: http://localhost:4567 (or whatever port the banner prints)
10
+
11
+ require_relative '../../lib/stream_weaver'
12
+
13
+ TEAM = [
14
+ { assignee: "Priya Patel", tier: "P1", open_issues: 4 },
15
+ { assignee: "Sam Okafor", tier: "P2", open_issues: 1 },
16
+ { assignee: "Jin Watanabe", tier: "P0", open_issues: 7 }
17
+ ].freeze
18
+
19
+ app "UAT: Table / Collapsible / Copy gaps", layout: :wide do
20
+ header "Three Gaps, One Session"
21
+ text "Each card below is a UAT checkpoint for one specific fix. See docs/ideas/2026-08-06-table-collapsible-clipboard-gaps.md."
22
+
23
+ vstack spacing: :lg do
24
+ # ---------------------------------------------------------------
25
+ card do
26
+ header3 "1a. Table: legacy default UNCHANGED (regression guard)"
27
+ text "Raw headers:/rows: table, no options. First column (Key) should still render accent-blue monospace, like every table before this session."
28
+ table headers: ["Key", "Title", "Status"],
29
+ rows: [
30
+ ["PROJ-101", "Fix login bug", "Open"],
31
+ ["PROJ-102", "Add dark mode", "In Progress"]
32
+ ]
33
+ end
34
+
35
+ card do
36
+ header3 "1b. Table: id_column escape hatch for raw tables"
37
+ text "Same shape as Sprint Focus's Team table -- first column is an Assignee name, not an id. id_column: false turns OFF the accent-mono treatment table-wide."
38
+ table headers: ["Assignee", "Tier", "Open Issues"],
39
+ rows: TEAM.map { |r| [r[:assignee], r[:tier], r[:open_issues]] },
40
+ id_column: false
41
+ end
42
+
43
+ card do
44
+ header3 "1c. Table: id_column pointed at a different column"
45
+ text "id_column: 1 -- only the Tier column (index 1) gets the identifier treatment."
46
+ table headers: ["Assignee", "Tier", "Open Issues"],
47
+ rows: TEAM.map { |r| [r[:assignee], r[:tier], r[:open_issues]] },
48
+ id_column: 1
49
+ end
50
+
51
+ card do
52
+ header3 "1d. Table: column DSL with style: Proc (per-row) + id_style: false"
53
+ text "Open Issues column gets a Proc style (red when > 3); Assignee opts out of accent via id_style: false even though it's column 0."
54
+ table TEAM do
55
+ column :assignee, id_style: false
56
+ column :tier
57
+ column :open_issues, header: "Open Issues",
58
+ style: ->(item) { item[:open_issues] > 3 ? "color: #c0392b; font-weight: 700;" : nil }
59
+ end
60
+ end
61
+
62
+ # ---------------------------------------------------------------
63
+ card do
64
+ header3 "2a. Collapsible: plain (legacy look, unchanged)"
65
+ collapsible "Show details" do
66
+ text "No subtitle, no badge -- must look exactly like it did before this session."
67
+ end
68
+ end
69
+
70
+ card do
71
+ header3 "2b. Collapsible: subtitle + badge (the actual gap)"
72
+ collapsible "Priority Alignment: cultiv-dashboard", subtitle: "12 issues, 83% aligned", badge_text: "P1", badge_variant: :danger do
73
+ text "This is the pattern that used to force a switch to expandable_card just for a badge."
74
+ end
75
+ end
76
+
77
+ card do
78
+ header3 "2c. Collapsible: class:/style: forwarding (previously silently dropped)"
79
+ collapsible "Custom-styled section", subtitle: "class:/style: now apply", badge_text: "NEW", badge_variant: :success,
80
+ class: "uat-custom-collapsible", style: "border-color: #2563eb;" do
81
+ text "Inspect the outer div -- it should carry class=\"collapsible sw-collapsible uat-custom-collapsible\" and the inline border-color."
82
+ end
83
+ end
84
+
85
+ # ---------------------------------------------------------------
86
+ card do
87
+ header3 "3a. copy_button: plain text"
88
+ copy_button "Copy summary", text: "Sprint Focus: 3 gaps closed, 2613 tests green."
89
+ end
90
+
91
+ card do
92
+ header3 "3b. copy_button: hostile payload (quotes, newline, backslash, <script>)"
93
+ text "Click Copy, then paste somewhere to confirm the exact payload round-trips -- this is the safety-critical case."
94
+ copy_button "Copy hostile text", text: "Line one \"quoted\"\nLine two with a backslash \\ and <script>alert(1)</script>"
95
+ end
96
+
97
+ card do
98
+ header3 "3c. code_block: copy: true (opt-in, default off elsewhere)"
99
+ code_block <<~RUBY, lang: "ruby", copy: true
100
+ def hello(name)
101
+ puts "Hello, \#{name}!"
102
+ end
103
+ RUBY
104
+ text "Below: same code_block with copy: omitted -- must show NO copy affordance (regression guard)."
105
+ code_block <<~RUBY, lang: "ruby"
106
+ def goodbye(name)
107
+ puts "Goodbye, \#{name}!"
108
+ end
109
+ RUBY
110
+ end
111
+ end
112
+ end.run!
@@ -0,0 +1,60 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # External Feed Simulator - pushes targeted updates to a running StreamWeaver app.
5
+ #
6
+ # Demonstrates the Feed DSL: connect by app name (via portfile) and push
7
+ # updates using the same component DSL as the app itself. Zero raw HTML.
8
+ #
9
+ # Usage:
10
+ # 1. Start the dashboard: bundle exec ruby examples/dashboard/live_dashboard.rb
11
+ # 2. In another terminal: bundle exec ruby examples/dashboard/feed_simulator.rb
12
+
13
+ require_relative '../../lib/stream_weaver'
14
+
15
+ feed = StreamWeaver.connect("Live Monitor")
16
+
17
+ ACTIVITIES = [
18
+ ["Deploy v2.14.3 completed", "Zero-downtime rolling deploy to production"],
19
+ ["Anomaly detected in API gateway", "Latency spike on /api/v2/users (p99 > 200ms)"],
20
+ ["Auto-scaler triggered", "Added 2 instances to web-pool (CPU > 75%)"],
21
+ ["SSL certificate renewed", "Wildcard cert renewed for 90 days"],
22
+ ["Database failover test passed", "RDS Multi-AZ failover completed in 28s"],
23
+ ["Rate limiter adjusted", "API rate limit increased to 1000 req/min"],
24
+ ["Cache hit ratio improved", "Redis cache hit ratio now at 94.2%"],
25
+ ["Backup verification passed", "Nightly snapshot restored and validated"],
26
+ ].freeze
27
+
28
+ ALERTS = [
29
+ [:critical, "High error rate on API", "Error rate exceeded 5% threshold for 3 minutes"],
30
+ [:urgent, "Memory pressure detected", "Worker nodes approaching OOM limits"],
31
+ [:high, "Slow query detected", "Query on orders table taking >5s"],
32
+ [:normal, "Log volume increasing", "Log ingestion rate up 40% this week"],
33
+ [:critical, "Disk usage critical", "Production DB volume at 94% capacity"],
34
+ [:urgent, "SSL cert expiring soon", "Certificate expires in 7 days"],
35
+ ].freeze
36
+
37
+ puts "Feed simulator started - connected to #{feed.url}"
38
+ puts "Press Ctrl+C to stop\n\n"
39
+
40
+ loop do
41
+ # Push a new activity event (prepend to feed)
42
+ act = ACTIVITIES.sample
43
+ time_str = Time.now.strftime("%H:%M:%S")
44
+
45
+ feed.prepend("#activity-feed") do
46
+ activity_item time: time_str, title: act[0], summary: act[1], type: :task
47
+ end
48
+ puts " [activity] #{time_str} #{act[0]}"
49
+ sleep rand(3.0..6.0)
50
+
51
+ # Occasionally push an alert update
52
+ if rand < 0.4
53
+ alert = ALERTS.sample
54
+ feed.prepend("#alerts-panel") do
55
+ priority_item priority: alert[0], title: alert[1], description: alert[2]
56
+ end
57
+ puts " [alert] #{alert[0].upcase}: #{alert[1]}"
58
+ end
59
+ sleep rand(2.0..5.0)
60
+ end
@@ -0,0 +1,80 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Example: Live Dashboard with Server-Push via StreamWeaver Streams
5
+ #
6
+ # This dashboard sets up a shell with stable DOM target IDs.
7
+ # It receives real-time updates via SSE from:
8
+ # - An internal stream thread (built-in data source, using component DSL)
9
+ # - External scripts via POST /stream/push (pluggable data sources)
10
+ #
11
+ # Run with: bundle exec ruby examples/dashboard/live_dashboard.rb
12
+ #
13
+ # To push updates externally:
14
+ # bundle exec ruby examples/dashboard/feed_simulator.rb
15
+ # Or manually:
16
+ # curl -X POST localhost:4567/stream/push \
17
+ # -d 'target=#metric-rps&html=<div>MANUAL</div>'
18
+
19
+ require_relative '../../lib/stream_weaver'
20
+
21
+ App = app "Live Monitor", theme: :dark, layout: :fluid do
22
+ app_shell sidebar_width: "360px" do
23
+ main do
24
+ app_header "System Monitor", variant: :primary do
25
+ pulse_indicator color: :green, label: "Live"
26
+ end
27
+
28
+ header2 "KEY METRICS"
29
+ grid cols: 3, gap: :md do
30
+ div(id: "metric-rps") do
31
+ card { stat_display value: "---", label: "REQ/SEC", color: :blue, size: :lg }
32
+ end
33
+ div(id: "metric-latency") do
34
+ card { stat_display value: "---", label: "LATENCY", color: :purple, size: :lg }
35
+ end
36
+ div(id: "metric-errors") do
37
+ card { stat_display value: "---", label: "ERROR RATE", color: :green, size: :lg }
38
+ end
39
+ end
40
+
41
+ header2 "ACTIVITY"
42
+ div(id: "activity-feed") do
43
+ text "Waiting for events..."
44
+ end
45
+ end
46
+
47
+ sidebar header: "Alerts" do
48
+ div(id: "alerts-panel") do
49
+ text "Monitoring..."
50
+ end
51
+ end
52
+ end
53
+
54
+ # Internal stream thread - pushes simulated metrics using component DSL.
55
+ # No raw HTML needed - same components as the app definition.
56
+ stream do |streamer|
57
+ loop do
58
+ rps = rand(1200..3500)
59
+ streamer.replace("#metric-rps") do
60
+ card { stat_display value: rps, label: "REQ/SEC", color: :blue, size: :lg }
61
+ end
62
+ sleep rand(1.5..3.0)
63
+
64
+ latency = rand(12..85)
65
+ streamer.replace("#metric-latency") do
66
+ card { stat_display value: "#{latency}ms", label: "LATENCY", color: :purple, size: :lg }
67
+ end
68
+ sleep rand(1.5..3.0)
69
+
70
+ err = (rand * 2.5).round(2)
71
+ color = err > 1.5 ? :red : :green
72
+ streamer.replace("#metric-errors") do
73
+ card { stat_display value: "#{err}%", label: "ERROR RATE", color: color, size: :lg }
74
+ end
75
+ sleep rand(2.0..4.0)
76
+ end
77
+ end
78
+ end
79
+
80
+ App.run! if __FILE__ == $0