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,508 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../lib/stream_weaver'
4
+
5
+ # Pass theme as command line argument: ruby theme_demo.rb dashboard
6
+ THEME = (ARGV[0] || 'default').to_sym
7
+
8
+ # Sample data ------------------------------------------------------------
9
+
10
+ RECENT_ORDERS = [
11
+ { order: "#10482", customer: "Alice Johnson", total: 128.50, status: "Shipped" },
12
+ { order: "#10483", customer: "Bob Smith", total: 64.00, status: "Processing" },
13
+ { order: "#10484", customer: "Carol Davis", total: 342.10, status: "Delivered" },
14
+ { order: "#10485", customer: "Dan Wilson", total: 19.99, status: "Cancelled" },
15
+ { order: "#10486", customer: "Eve Turner", total: 88.25, status: "Shipped" }
16
+ ].freeze
17
+
18
+ app = StreamWeaver::App.new("Theme Demo", theme: THEME, layout: :fluid) do
19
+ # NOTE: section switching uses route_by + nav_item (plain <a href> links + GET routing)
20
+ # rather than button-with-block, because StreamWeaver's button-action dispatcher
21
+ # (find_button_recursive in server.rb) only walks component.children -- it does not
22
+ # know about AppShell#main_children / #sidebar_children, so any `button ... do |s| end`
23
+ # placed inside an app_shell's main/sidebar blocks is currently unreachable. Routing
24
+ # (GET requests) goes through a separate code path and is unaffected.
25
+ route_by :section, dashboard: "/", planning: "/planning", docs: "/docs", settings: "/settings"
26
+ state[:section] ||= :dashboard
27
+
28
+ app_shell sidebar_width: "220px", sidebar_position: :left do
29
+ sidebar header: "Theme Demo" do
30
+ vstack spacing: :xs do
31
+ nav_item "Dashboard", href: "/", active: state[:section] == :dashboard
32
+ nav_item "Planning", href: "/planning", active: state[:section] == :planning
33
+ nav_item "Docs", href: "/docs", active: state[:section] == :docs
34
+ nav_item "Settings", href: "/settings", active: state[:section] == :settings
35
+ end
36
+ end
37
+
38
+ main do
39
+ navbar do
40
+ hstack(justify: :between, align: :center) do
41
+ header2 "StreamWeaver Themes"
42
+ theme_switcher
43
+ end
44
+ end
45
+
46
+ text "Use the theme switcher above to change themes in real-time!"
47
+ text "Or run with: ruby theme_demo.rb [default|dashboard|document|doc]"
48
+
49
+ breadcrumbs do
50
+ crumb "Home", href: "/"
51
+ crumb state[:section].to_s.capitalize
52
+ end
53
+
54
+ case state[:section]
55
+
56
+ # =========================================================
57
+ # SECTION 1: Dashboard
58
+ # =========================================================
59
+ when :dashboard
60
+ header1 "Dashboard"
61
+
62
+ pulse_indicator color: :green, label: "All Systems Operational"
63
+
64
+ header2 "Key Metrics"
65
+ kpi_dashboard metrics: [
66
+ { value: "1,234", label: "USERS", color: :blue, trend: :up },
67
+ { value: "$45.6K", label: "REVENUE", color: :green, trend: :up },
68
+ { value: "+12.3%", label: "GROWTH", color: :purple, trend: :up },
69
+ { value: "3", label: "ALERTS", color: :red, trend: :down }
70
+ ]
71
+
72
+ header2 "Trends"
73
+ columns widths: ["1fr", "1fr"] do
74
+ column do
75
+ card do
76
+ header3 "Revenue by Channel"
77
+ bar_chart data: {
78
+ "Web" => 4200, "Mobile" => 3100, "Partner" => 1800, "Retail" => 950
79
+ }, colors: ["#4a90d9"], height: "220px"
80
+ end
81
+ end
82
+ column do
83
+ card do
84
+ header3 "Weekly Active Users"
85
+ line_chart data: [820, 932, 901, 934, 1290, 1330, 1420],
86
+ fill: true, colors: ["#10b981"], height: "220px"
87
+ end
88
+ end
89
+ end
90
+
91
+ header2 "Recent Orders"
92
+ card do
93
+ table RECENT_ORDERS do
94
+ column :order
95
+ column :customer
96
+ column :total, format: :currency, align: :right
97
+ column(:status) { |row| row[:status] }
98
+ end
99
+ end
100
+
101
+ header2 "Status"
102
+ hstack spacing: :lg, align: :center do
103
+ vstack spacing: :xs, align: :center do
104
+ status_dot status: :green, pulse: true
105
+ text "API"
106
+ end
107
+ vstack spacing: :xs, align: :center do
108
+ status_dot status: :green
109
+ text "Database"
110
+ end
111
+ vstack spacing: :xs, align: :center do
112
+ status_dot status: :yellow
113
+ text "Queue"
114
+ end
115
+ vstack spacing: :xs, align: :center do
116
+ status_dot status: :red
117
+ text "CDN"
118
+ end
119
+ end
120
+
121
+ hstack spacing: :md do
122
+ badge "5", variant: :default
123
+ badge "3", variant: :danger
124
+ badge "12", variant: :warning
125
+ badge "OK", variant: :success
126
+ end
127
+
128
+ status_badge :strong, "All core services healthy"
129
+
130
+ header2 "Rollout Progress"
131
+ progress_bar value: 72, show_label: true
132
+
133
+ header2 "Activity Feed"
134
+ vstack spacing: :none do
135
+ activity_item time: "15:00", title: "Deployment completed",
136
+ summary: "v2.4.1 shipped to production, zero downtime",
137
+ type: :task
138
+ activity_item time: "14:30", title: "Anomaly detected",
139
+ summary: "CDN latency spike in EU region, investigating",
140
+ type: :escalation
141
+ activity_item time: "14:00", title: "Sprint planning notes shared",
142
+ summary: "Team aligned on Q3 priorities",
143
+ type: :communication
144
+ end
145
+
146
+ # =========================================================
147
+ # SECTION 2: Planning
148
+ # =========================================================
149
+ when :planning
150
+ header1 "Planning"
151
+ text "A live look at the visual-plan skill: what's changing, open decisions, and mockups."
152
+
153
+ header2 "Implementation Map"
154
+ implementation_map(files: [
155
+ { path: "lib/stream_weaver/theme/auto_mode.rb", note: "Fix contrast bug in default theme's muted text" },
156
+ { path: "lib/stream_weaver/adapter/alpinejs.rb", note: "Add CSS variable fallback for table headers" },
157
+ { path: "spec/theme_enhanced_spec.rb", note: "Cover new theme-switch edge cases" }
158
+ ])
159
+
160
+ header2 "Open Decision"
161
+ decision(question: "Which theme should ship as the new default?") do
162
+ option(id: :dashboard, label: "Dashboard (dark)",
163
+ detail: "High contrast, data-dense — best for ops/monitoring use cases.",
164
+ recommended: true)
165
+ option(id: :default, label: "Default (light)",
166
+ detail: "Currently the least polished per design review — needs more work.")
167
+ option(id: :document, label: "Document",
168
+ detail: "Best for long-form/reading contexts, not general app chrome.")
169
+ end
170
+
171
+ header2 "Code Change"
172
+ diff(language: "ruby") do
173
+ before do
174
+ <<~RUBY
175
+ def muted_text_color
176
+ "#9ca3af"
177
+ end
178
+ RUBY
179
+ end
180
+ after do
181
+ <<~RUBY
182
+ def muted_text_color
183
+ "var(--sw-text-muted, #9ca3af)"
184
+ end
185
+ RUBY
186
+ end
187
+ end
188
+
189
+ header2 "Annotated Code"
190
+ annotated_code(language: "ruby", annotations: [
191
+ { line: 1, note: "Reads the CSS custom property set by the active theme" },
192
+ { line: 2, note: "Falls back to a fixed hex value if the var is undefined" }
193
+ ]) do
194
+ <<~RUBY
195
+ def muted_text_color
196
+ "var(--sw-text-muted, #9ca3af)"
197
+ end
198
+ RUBY
199
+ end
200
+
201
+ header2 "Wireframe: Theme Switcher Placement"
202
+ wireframe(surface: :browser) do
203
+ <<~HTML
204
+ <div class="wf-card">
205
+ <h2>Navbar</h2>
206
+ <p class="wf-muted">Theme switcher pinned top-right, always visible</p>
207
+ <button class="primary">Theme: Dashboard ▾</button>
208
+ </div>
209
+ HTML
210
+ end
211
+
212
+ header2 "API Reference"
213
+ api_endpoint method: "GET", path: "/api/themes",
214
+ description: "List available themes",
215
+ params: [
216
+ { name: "include_custom", type: "boolean", required: false }
217
+ ],
218
+ response: { themes: "array", default: "string" }
219
+
220
+ header2 "Design Deck: Theme Token Strategy"
221
+ design_deck "Theme Token Strategy" do
222
+ slide "tokens", "CSS Variable Approach",
223
+ context: "How should theme values propagate to components?" do
224
+ option "CSS Custom Properties", aside: "Live-swappable at runtime, no rebuild needed.",
225
+ recommended: true do
226
+ code_block <<~CSS, lang: "css"
227
+ :root.sw-theme-dashboard {
228
+ --sw-bg: #0f172a;
229
+ --sw-text: #e2e8f0;
230
+ }
231
+ CSS
232
+ end
233
+ option "Sass Variables", aside: "Compile-time only — requires a rebuild per theme switch." do
234
+ code_block <<~SCSS, lang: "scss"
235
+ $sw-bg: #0f172a;
236
+ $sw-text: #e2e8f0;
237
+ SCSS
238
+ end
239
+ end
240
+
241
+ slide "rollout", "Rollout Plan",
242
+ context: "How do we ship the fixes without breaking existing embeds?" do
243
+ option "Ship behind a flag", aside: "Safer, slower.", recommended: true do
244
+ mermaid <<~MERMAID, compact: true
245
+ graph LR
246
+ A[Fix CSS] --> B[Flag: theme_v2]
247
+ B --> C[Dogfood]
248
+ C --> D[Full rollout]
249
+ MERMAID
250
+ end
251
+ option "Ship directly to all themes", aside: "Faster, riskier for existing embeds." do
252
+ mermaid <<~MERMAID, compact: true
253
+ graph LR
254
+ A[Fix CSS] --> B[Ship to all themes]
255
+ MERMAID
256
+ end
257
+ end
258
+ end
259
+
260
+ # =========================================================
261
+ # SECTION 3: Docs
262
+ # =========================================================
263
+ when :docs
264
+ header1 "Docs"
265
+
266
+ columns widths: ["260px", "1fr"] do
267
+ column do
268
+ sidebar_toc sections: [
269
+ { id: "overview", label: "Overview" },
270
+ { id: "callouts", label: "Callouts" },
271
+ { id: "comparison", label: "Before / After" },
272
+ { id: "diagram", label: "Diagram" },
273
+ { id: "files", label: "File Layout" }
274
+ ]
275
+ end
276
+
277
+ column do
278
+ doc_header(
279
+ eyebrow: "StreamWeaver · Theming",
280
+ title: "Theme System Overview",
281
+ pills: [
282
+ { text: "Stable" },
283
+ "Updated July 2026",
284
+ "Owner: core team"
285
+ ]
286
+ )
287
+
288
+ doc_section_header "01", "Overview", id: "overview"
289
+ md <<~MD
290
+ StreamWeaver ships four built-in themes: **default**, **dashboard**, **document**,
291
+ and **doc**. Each theme is a set of CSS custom properties applied at the `body` level.
292
+ MD
293
+
294
+ prose do
295
+ text "Themes are swappable live via the theme_switcher component, with no page reload required."
296
+ end
297
+
298
+ pullquote "Design for the brain you have, not the brain you wish you had.",
299
+ attribution: "Gloria Foloran"
300
+
301
+ doc_section_header "02", "Callouts", id: "callouts"
302
+ callout(variant: :info, title: "Tip:") do
303
+ text "Use the theme_switcher component to preview all themes without restarting the server."
304
+ end
305
+ callout(variant: :warning, title: "Heads up:") do
306
+ text "Custom CSS overrides may not survive a theme switch unless scoped to a theme class."
307
+ end
308
+ callout(variant: :success, title: "Verified:") do
309
+ text "All four built-in themes now pass the contrast-ratio audit."
310
+ end
311
+
312
+ doc_section_header "03", "Before / After", id: "comparison"
313
+ comparison(before_label: "Before", after_label: "After") do
314
+ before do
315
+ md "- Flat, single-page demo\n- ~15 components shown\n- Hard to judge real-app fit"
316
+ end
317
+ after do
318
+ md "- Sidebar-navigable admin app\n- 40+ components shown\n- Realistic layout per section"
319
+ end
320
+ end
321
+
322
+ doc_section_header "04", "Diagram", id: "diagram"
323
+ mermaid <<~MERMAID
324
+ graph LR
325
+ A[Theme Switcher] --> B[body.sw-theme-*]
326
+ B --> C[CSS Custom Properties]
327
+ C --> D[Component Styles]
328
+ MERMAID
329
+
330
+ doc_section_header "05", "File Layout", id: "files"
331
+ dir_tree <<~TREE
332
+ examples/styling/
333
+ theme_demo.rb [modified]
334
+ style_showcase.rb
335
+ feedback_demo.rb
336
+ TREE
337
+
338
+ code_editor :sample_snippet, language: :ruby, readonly: true, height: "160px",
339
+ default: <<~RUBY
340
+ app_shell do
341
+ sidebar { text "Nav" }
342
+ main { text "Content" }
343
+ end
344
+ RUBY
345
+ end
346
+ end
347
+
348
+ # =========================================================
349
+ # SECTION 4: Settings
350
+ # =========================================================
351
+ when :settings
352
+ header1 "Settings"
353
+
354
+ header2 "Typography"
355
+ header3 "Headings"
356
+ text "This is a paragraph demonstrating body text. The quick brown fox jumps over the lazy dog."
357
+
358
+ header3 "Lists and Content"
359
+ text "Content flows naturally with appropriate spacing and line height for readability."
360
+
361
+ header2 "Cards"
362
+ card do
363
+ card_header "Card Title"
364
+ card_body do
365
+ text "Card content with some description text. Notice how the card styling changes between themes."
366
+ end
367
+ end
368
+
369
+ header2 "Buttons"
370
+ hstack(spacing: :sm) do
371
+ button "Primary Button"
372
+ button "Secondary", style: :secondary
373
+ end
374
+
375
+ header2 "Form Inputs"
376
+ columns widths: ["1fr", "1fr"] do
377
+ column do
378
+ text_field :name, placeholder: "Enter your name"
379
+ end
380
+ column do
381
+ select :color, ["Red", "Green", "Blue"]
382
+ end
383
+ end
384
+ checkbox :agree, "I agree to the terms"
385
+
386
+ header2 "Checkbox Group"
387
+ checkbox_group :permissions, select_all: "Select All", select_none: "Clear" do
388
+ item "read" do
389
+ text "Read - View content"
390
+ end
391
+ item "write" do
392
+ text "Write - Create and edit content"
393
+ end
394
+ item "admin" do
395
+ text "Admin - Full access"
396
+ end
397
+ end
398
+
399
+ header2 "Radio Group"
400
+ radio_group :plan, ["Free", "Pro", "Enterprise"]
401
+
402
+ header2 "Dropdown"
403
+ select :theme_pref, ["Light", "Dark", "System"], default: "System"
404
+ dropdown do
405
+ trigger do
406
+ button "Actions", style: :primary
407
+ end
408
+ menu do
409
+ menu_item "Edit" do |s|
410
+ s[:last_action] = "Edit clicked"
411
+ end
412
+ menu_item "Duplicate" do |s|
413
+ s[:last_action] = "Duplicate clicked"
414
+ end
415
+ menu_divider
416
+ menu_item "Delete", style: :destructive do |s|
417
+ s[:last_action] = "Delete clicked"
418
+ end
419
+ end
420
+ end
421
+ text -> (s) { s[:last_action] ? "Last action: #{s[:last_action]}" : "Click a menu item..." }
422
+
423
+ header2 "Tags"
424
+ tag_buttons :category, ["Fiction", "Non-fiction", "Mystery", "Sci-Fi"]
425
+
426
+ header2 "Tabs"
427
+ tabs :settings_tabs, variant: :line do
428
+ tab "General" do
429
+ text "General settings content."
430
+ end
431
+ tab "Notifications" do
432
+ checkbox :email_notifs, "Email notifications"
433
+ checkbox :sms_notifs, "SMS notifications"
434
+ end
435
+ tab "Advanced" do
436
+ spinner size: :md, label: "Loading advanced settings..."
437
+ end
438
+ end
439
+
440
+ header2 "Collapsible"
441
+ collapsible "Advanced Options", expanded: false do
442
+ text "Hidden content revealed when expanded."
443
+ checkbox :debug_mode, "Enable debug mode"
444
+ end
445
+
446
+ header2 "Expandable Card"
447
+ expandable_card key: :settings_expanded, title: "Account", subtitle: "Profile & billing",
448
+ badge_text: "2 items", status: :green do
449
+ text "Expandable card content — click the header to collapse."
450
+ end
451
+
452
+ header2 "Alerts"
453
+ alert(variant: :info) { text "This is an info alert" }
454
+ alert(variant: :success) { text "Success message" }
455
+
456
+ header2 "Progress & Spinners"
457
+ progress_bar value: 65, show_label: true
458
+ spinner size: :md, label: "Loading..."
459
+
460
+ header2 "Modal"
461
+ button "Open Settings Modal" do |s|
462
+ s[:settings_modal_open] = true
463
+ end
464
+ modal :settings_modal, title: "Settings", size: :md do
465
+ text "Adjust your preferences here."
466
+ checkbox :dark_mode, "Enable dark mode"
467
+ modal_footer do
468
+ button "Cancel", style: :secondary do |s|
469
+ s[:settings_modal_open] = false
470
+ end
471
+ button "Save", style: :primary do |s|
472
+ s[:settings_modal_open] = false
473
+ end
474
+ end
475
+ end
476
+
477
+ header2 "Toasts"
478
+ toast_container position: :top_right, duration: 4000
479
+ button "Show Toast" do |s|
480
+ show_toast("Settings saved successfully!", variant: :success)
481
+ end
482
+
483
+ header2 "Data Display"
484
+ text "Themes affect data density and scannability."
485
+ card do
486
+ card_body do
487
+ grid cols: 3 do
488
+ vstack do
489
+ text "Users"
490
+ header3 "1,234"
491
+ end
492
+ vstack do
493
+ text "Revenue"
494
+ header3 "$45.6K"
495
+ end
496
+ vstack do
497
+ text "Growth"
498
+ header3 "+12.3%"
499
+ end
500
+ end
501
+ end
502
+ end
503
+ end
504
+ end
505
+ end
506
+ end
507
+
508
+ app.generate.run!
@@ -0,0 +1,111 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Example: Endpoint Health Checker
5
+ # A genuinely useful URL health checker — configure endpoints, see live status and response times.
6
+ # Run with: bundle exec ruby examples/timer_health_checker.rb
7
+
8
+ require_relative "../lib/stream_weaver"
9
+ require "net/http"
10
+ require "uri"
11
+
12
+ ENDPOINTS = [
13
+ { name: "GitHub", key: :github, url: "https://github.com", sla_ms: 500 },
14
+ { name: "RubyGems", key: :rubygems, url: "https://rubygems.org", sla_ms: 300 },
15
+ { name: "Httpbin", key: :httpbin, url: "https://httpbin.org/delay/1", sla_ms: 800 },
16
+ ].freeze
17
+
18
+ STATUS_BADGE = { up: { text: "UP", variant: :success }, slow: { text: "SLOW", variant: :warning }, down: { text: "DOWN", variant: :danger } }.freeze
19
+ STATUS_COLOR = { up: :green, slow: :yellow, down: :red }.freeze
20
+ STATUS_EMOJI = { up: "\u2705", slow: "\u26A0\uFE0F", down: "\u274C" }.freeze
21
+ RING_CLASS = { down: "sw-alert-ring", slow: "sw-warn-ring" }.freeze
22
+
23
+ RING_CSS = "<style>" \
24
+ ".sw-alert-ring{box-shadow:0 0 0 3px #ef4444,0 0 12px rgba(239,68,68,0.4) !important;transition:box-shadow 0.3s ease}" \
25
+ ".sw-warn-ring{box-shadow:0 0 0 3px #eab308,0 0 12px rgba(234,179,8,0.3) !important;transition:box-shadow 0.3s ease}" \
26
+ "</style>"
27
+
28
+ SKIP_SSL_VERIFY = ENV.fetch("SSL_VERIFY", "0") == "0"
29
+
30
+ def check_endpoint(ep)
31
+ uri = URI(ep[:url])
32
+ start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
33
+
34
+ http = Net::HTTP.new(uri.host, uri.port)
35
+ http.use_ssl = uri.scheme == "https"
36
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE if SKIP_SSL_VERIFY && http.use_ssl?
37
+ http.open_timeout = 5
38
+ http.read_timeout = 10
39
+ http.get(uri.request_uri)
40
+
41
+ elapsed_ms = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - start) * 1000).round
42
+ { status: elapsed_ms > ep[:sla_ms] ? :slow : :up, ms: elapsed_ms }
43
+ rescue StandardError
44
+ { status: :down, ms: nil }
45
+ end
46
+
47
+ def format_timing(result)
48
+ result[:ms] ? "#{result[:ms]}ms" : "timeout"
49
+ end
50
+
51
+ App = app "Endpoint Health Checker", theme: :dark, layout: :wide do
52
+ app_header "Endpoint Health Checker", variant: :primary do
53
+ pulse_indicator color: :green, label: "Monitoring"
54
+ end
55
+
56
+ grid columns: ENDPOINTS.size, gap: :lg do
57
+ ENDPOINTS.each do |ep|
58
+ expandable_card key: ep[:key], title: ep[:name], subtitle: ep[:url],
59
+ status: :gray, badge_text: "\u2014", initially_expanded: true do
60
+ div id: "health-#{ep[:key]}" do
61
+ stat_display value: "\u2014", label: "RESPONSE TIME", color: :blue
62
+ text "SLA: #{ep[:sla_ms]}ms"
63
+ end
64
+ end
65
+ end
66
+ end
67
+
68
+ div id: "ring-css", style: "display:none"
69
+
70
+ header2 "Status Log"
71
+ div id: "status-log" do
72
+ text "Waiting for first health check..."
73
+ end
74
+
75
+ prev_status = {}
76
+
77
+ every(5) do |streamer|
78
+ streamer.replace("#ring-css", RING_CSS)
79
+
80
+ ENDPOINTS.each do |ep|
81
+ key = ep[:key]
82
+ result = check_endpoint(ep)
83
+ status = result[:status]
84
+
85
+ streamer.replace("#health-#{key}") do
86
+ div id: "health-#{key}" do
87
+ stat_display value: format_timing(result), label: "RESPONSE TIME", color: STATUS_COLOR[status]
88
+ status_dot status: STATUS_COLOR[status], pulse: status != :up
89
+ text "SLA: #{ep[:sla_ms]}ms"
90
+ badge STATUS_BADGE[status][:text], variant: STATUS_BADGE[status][:variant]
91
+ end
92
+ end
93
+
94
+ card_id = "#card-#{key}"
95
+ RING_CLASS.each_value { |cls| streamer.remove_class(card_id, cls) }
96
+ streamer.add_class(card_id, RING_CLASS[status]) if RING_CLASS[status]
97
+
98
+ old = prev_status[key]
99
+ if old && old != status
100
+ streamer.prepend("#status-log") do
101
+ div style: "padding:4px 8px;border-bottom:1px solid rgba(255,255,255,0.1)" do
102
+ text "#{STATUS_EMOJI[status]} #{ep[:name]} \u2192 #{status.to_s.upcase} (#{format_timing(result)}) at #{Time.now.strftime('%H:%M:%S')}"
103
+ end
104
+ end
105
+ end
106
+ prev_status[key] = status
107
+ end
108
+ end
109
+ end
110
+
111
+ App.run! if __FILE__ == $0