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,334 @@
1
+ # frozen_string_literal: true
2
+ # backtick_javascript: true
3
+
4
+ require "cgi"
5
+ require_relative "static"
6
+ require_relative "../opal/env"
7
+
8
+ module StreamWeaver
9
+ module Adapter
10
+ class Opal < Base
11
+ # Document renderers with no framework behavior, shared with the AlpineJS
12
+ # adapter. See adapter/static.rb.
13
+ include Static
14
+ # Not in Base — defined fresh here
15
+ def render_header(view, content, level, _state, options = {})
16
+ level = [[level.to_i, 1].max, 6].min
17
+ attrs = {}
18
+ attrs[:class] = options[:class] if options[:class]
19
+ attrs[:style] = options[:style] if options[:style]
20
+ view.send(:"h#{level}", **attrs) { view.plain(content.to_s) }
21
+ end
22
+
23
+ # Not in Base — defined fresh here
24
+ def render_div(view, component, state)
25
+ opts = component.respond_to?(:html_options) ? component.html_options : {}
26
+ view.div(**opts) do
27
+ Array(component.children).each { |c| c.render(view, state) }
28
+ end
29
+ end
30
+
31
+ # Not in Base — defined fresh here
32
+ def render_markdown(view, content, _state, options = {})
33
+ css_classes = ["sw-markdown"]
34
+ css_classes << options[:class] if options[:class]
35
+ attrs = { class: css_classes.join(" ") }
36
+ attrs[:style] = options[:style] if options[:style]
37
+ view.div(**attrs) { view.raw(md_to_html(content.to_s)) }
38
+ end
39
+
40
+ # Overrides Base
41
+ def render_text_field(view, key, options, state)
42
+ label_text = options[:label]
43
+ input_type = (options[:type] || :text).to_s
44
+ if label_text
45
+ view.div(style: "margin-bottom:8px") do
46
+ view.label(style: "display:block;font-weight:500;margin-bottom:4px") { view.plain(label_text) }
47
+ view.input(
48
+ type: input_type, name: key.to_s, value: state[key] || "",
49
+ placeholder: options[:placeholder] || "", data_sw_update: key.to_s
50
+ )
51
+ end
52
+ else
53
+ view.input(
54
+ type: input_type, name: key.to_s, value: state[key] || "",
55
+ placeholder: options[:placeholder] || "", data_sw_update: key.to_s
56
+ )
57
+ end
58
+ end
59
+
60
+ # Overrides Base
61
+ def render_date_field(view, key, options, state)
62
+ bounds = { min: options[:min], max: options[:max] }.compact
63
+ attrs = { type: "date", name: key.to_s, value: state[key] || "", data_sw_update: key.to_s, **bounds }
64
+
65
+ if options[:label]
66
+ view.div(style: "margin-bottom:8px") do
67
+ view.label(style: "display:block;font-weight:500;margin-bottom:4px") { view.plain(options[:label]) }
68
+ view.input(**attrs)
69
+ end
70
+ else
71
+ view.input(**attrs)
72
+ end
73
+ end
74
+
75
+ # Overrides Base
76
+ def render_checkbox(view, key, label, _options, state)
77
+ view.label do
78
+ view.input(type: "checkbox", name: key.to_s,
79
+ data_sw_toggle: key.to_s, checked: state[key] ? true : false)
80
+ view.plain(" #{label}")
81
+ end
82
+ end
83
+
84
+ # Overrides Base — 5 args: (view, button_id, label, options, modal_context)
85
+ def render_button(view, button_id, label, _options, _modal_context = nil)
86
+ view.button(data_sw_invoke: button_id) { view.plain(label.to_s) }
87
+ end
88
+
89
+ # Overrides Base — morphdom.js comes from OpalShell, nothing to emit here
90
+ def render_cdn_scripts(_view)
91
+ end
92
+
93
+ def render_badge(view, component, _state)
94
+ view.span(class: "sw-badge sw-badge-#{component.variant} sw-badge-#{component.size}") { view.plain(component.text) }
95
+ end
96
+
97
+ # Not in Base — defined fresh here
98
+ def render_tabs(view, component, state)
99
+ active_index = state[component.key] || 0
100
+ view.div(class: "sw-tabs sw-tabs-#{component.variant || 'line'}") do
101
+ view.div(class: "sw-tabs-list") do
102
+ component.children.each_with_index do |tab, index|
103
+ tab_classes = index == active_index ? "sw-tab-trigger sw-tab-active" : "sw-tab-trigger"
104
+ view.button(
105
+ class: tab_classes,
106
+ data_sw_invoke: "#{component.key}_tab_#{index}"
107
+ ) { view.plain(tab.label) }
108
+ end
109
+ end
110
+ view.div(class: "sw-tab-panel") do
111
+ active_tab = component.children[active_index]
112
+ Array(active_tab&.children).each { |c| c.render(view, state) }
113
+ end
114
+ end
115
+ end
116
+
117
+ # Not in Base — defined fresh here
118
+ def render_table(view, headers, rows, options, state)
119
+ key = options[:key]
120
+ sort_col = state["#{key}_sort_col"]
121
+ sort_dir = (state["#{key}_sort_dir"] || :asc).to_sym
122
+
123
+ if options[:sortable] && sort_col
124
+ rows = rows.sort_by { |row| row[sort_col].to_s }
125
+ rows = rows.reverse if sort_dir == :desc
126
+ end
127
+
128
+ table_classes = ["sw-table"]
129
+ table_classes << "sw-table-bordered" if options[:bordered]
130
+
131
+ wrapper_attrs = {}
132
+ if options[:scrollable]
133
+ wrapper_attrs[:class] = "sw-table--scrollable"
134
+ wrapper_attrs[:style] = "max-height:400px;overflow:auto;"
135
+ elsif options[:sticky_header]
136
+ wrapper_attrs[:style] = "max-height:400px;overflow-y:auto;"
137
+ end
138
+
139
+ th_style = "padding:0.75rem 1rem;text-align:left;border-bottom:2px solid var(--sw-color-border,#e0e0e0);font-weight:600;"
140
+ td_style = "padding:0.75rem 1rem;border-bottom:1px solid var(--sw-color-border,#e0e0e0);"
141
+
142
+ view.div(**wrapper_attrs) do
143
+ view.table(class: table_classes.join(" ")) do
144
+ view.thead do
145
+ view.tr do
146
+ headers.each_with_index do |header, index|
147
+ if options[:sortable]
148
+ indicator = sort_col == index ? (sort_dir == :asc ? " ↑" : " ↓") : ""
149
+ view.th(style: th_style + "cursor:pointer;user-select:none;") do
150
+ view.button(data_sw_invoke: "#{key}_sort_#{index}") do
151
+ view.plain("#{header}#{indicator}")
152
+ end
153
+ end
154
+ else
155
+ view.th(style: th_style) { view.plain(header.to_s) }
156
+ end
157
+ end
158
+ end
159
+ end
160
+ view.tbody do
161
+ rows.each_with_index do |row, idx|
162
+ row_classes = []
163
+ row_classes << "sw-row-striped" if options[:striped] && idx.odd?
164
+ row_classes << "sw-row-hoverable" if options[:hoverable]
165
+ tr_attrs = row_classes.any? ? { class: row_classes.join(" ") } : {}
166
+ view.tr(**tr_attrs) do
167
+ Array(row).each_with_index do |cell, col_idx|
168
+ custom_style = options[:cell_styles]&.dig(idx, col_idx)
169
+ style = custom_style && !custom_style.to_s.empty? ? "#{td_style} #{custom_style}" : td_style
170
+ view.td(style: style) { view.plain(cell.to_s) }
171
+ end
172
+ end
173
+ end
174
+ end
175
+ end
176
+ end
177
+ end
178
+
179
+ # Not in Base — defined fresh here
180
+ def render_columns(view, widths, children, _options, state)
181
+ view.div(class: "sw-columns", style: "display:flex;gap:var(--sw-spacing-lg,1rem);") do
182
+ children.each_with_index do |column, index|
183
+ column.width = widths&.[](index)
184
+ column.render(view, state)
185
+ end
186
+ end
187
+ end
188
+
189
+ # Not in Base — defined fresh here
190
+ def render_column(view, width, children, _options, state)
191
+ # "fr" is a CSS Grid unit, not a valid flex-basis -- using it there makes the
192
+ # whole `flex` shorthand invalid and silently falls back to shrink-to-content.
193
+ # Translate the fraction to flex-grow instead. See alpinejs.rb#render_column.
194
+ fr_match = width && width.match(/\A(\d+(?:\.\d+)?)fr\z/)
195
+ style = if width.nil?
196
+ "flex:1 1 0;min-width:0;"
197
+ elsif fr_match
198
+ "flex:#{fr_match[1]} 1 0%;min-width:0;"
199
+ else
200
+ # Fixed length -- pin the width, don't grow. See alpinejs.rb#render_column.
201
+ "flex:0 1 #{width};min-width:0;"
202
+ end
203
+ view.div(class: "sw-column", style: style) do
204
+ children.each { |child| child.render(view, state) }
205
+ end
206
+ end
207
+
208
+ # Not in Base — defined fresh here
209
+ def render_theme_preset(view, component, state)
210
+ end
211
+
212
+ # Not in Base — defined fresh here
213
+ def render_theme_toggle(view, component, state)
214
+ view.button(data_sw_action: "toggle-theme") { view.plain("🌓") }
215
+ end
216
+
217
+ # Not in Base — defined fresh here
218
+ def render_theme_switcher(view, component, state)
219
+ end
220
+
221
+ # Every component stylesheet this adapter has been asked to emit, keyed by
222
+ # component so re-renders and repeated instances contribute exactly once.
223
+ #
224
+ # The DOM path writes these into <head> and forgets them; the DOM-free
225
+ # path has no <head> to write to, so the adapter is where the CSS lives
226
+ # and OpalRuntime#collected_css reads it back out after a render. Values
227
+ # are already @layer-wrapped, i.e. ready to drop into a <style> tag.
228
+ def collected_css
229
+ @collected_css ||= {}
230
+ end
231
+
232
+ # The collected stylesheets as one blob, in first-seen order.
233
+ def collected_css_text
234
+ collected_css.values.join("\n")
235
+ end
236
+
237
+ # Satisfies the Adapter::Static contract.
238
+ #
239
+ # Server-side this writes a <style> tag into the rendered fragment. In the
240
+ # browser that would re-emit on every re-render, since each render pass
241
+ # builds fresh renderers and replaces the region's innerHTML. So instead
242
+ # we append to <head> keyed by component, which is idempotent and survives
243
+ # re-renders untouched.
244
+ #
245
+ # Collection happens unconditionally, before the DOM write: it is the only
246
+ # channel available when there is no <head> (Node, render-to-string), and
247
+ # it is what makes this hook observable under MRI.
248
+ def inject_component_css(view, key, css)
249
+ return if css.nil? || css.strip.empty?
250
+
251
+ wrapped = StreamWeaver::CSS.layer_wrap(css)
252
+ collected_css[key.to_sym] ||= wrapped
253
+
254
+ return unless StreamWeaver::Opal::Env.dom?
255
+
256
+ # :nocov:
257
+ %x{
258
+ var id = "sw-css-" + #{key.to_s};
259
+ if (!document.getElementById(id)) {
260
+ var el = document.createElement("style");
261
+ el.id = id;
262
+ el.textContent = #{wrapped};
263
+ document.head.appendChild(el);
264
+ }
265
+ }
266
+ # :nocov:
267
+ nil
268
+ end
269
+
270
+ # #render_mermaid now lives in Static, shared with AlpineJS
271
+ # (stream_weaver-mermaid-extension) -- it used to be adapter-specific
272
+ # here on the theory that mermaid was behavior (Alpine's x-data/
273
+ # x-init), but sw-mermaid-zoom.js self-inits on DOMContentLoaded with
274
+ # no Alpine dependency at all, so the markup itself was never
275
+ # adapter-specific. What stayed here is only the JS gap below.
276
+
277
+ # The remaining Adapter::Static seams. All four are asset/behavior
278
+ # concerns that a rendered document does not need:
279
+ #
280
+ # - scroll-spy JS is bundled by the host (bin/build_extension), not
281
+ # inlined per render -- a host with no <base target> override (e.g.
282
+ # opal-build's standalone HTML) still gets working, unhighlighted
283
+ # anchors without it; a host that does (the browser extension's
284
+ # sandbox, for outbound-doc-link safety) needs its own fragment-link
285
+ # click handler regardless, since that override affects every
286
+ # `#anchor` link on the page, not just sidebar_toc's
287
+ # - Prism cannot be pulled from a CDN here (a browser-extension host
288
+ # forbids remote script), so highlighting waits until it is bundled
289
+ # - copy-to-clipboard is Alpine-driven behavior, not document structure
290
+ # - sw-mermaid-zoom.js is bundled by the host the same way
291
+ # sw-sidebar-toc.js is, not inlined per render -- Opal-compiled code
292
+ # runs in the browser, where File.read (how AlpineJS inlines it) does
293
+ # not exist. Only the CSS is injectable here. Unlike the other three,
294
+ # this one is not optional: mermaid's source lives in a data
295
+ # attribute the shared markup writes, not visible text, so a host
296
+ # that skips bundling the engine gets an empty box per diagram, not
297
+ # a degraded-but-working render. Both real Opal hosts bundle it
298
+ # unconditionally (bin/build_extension, OpalBuilder#copy_browser_
299
+ # assets) -- this hook staying CSS-only is about *where* the JS gets
300
+ # loaded from, not whether it does.
301
+ #
302
+ # Each is a deliberate no-op (or partial no-op) rather than a missing
303
+ # method, so the document renders instead of raising NotImplementedError.
304
+ def inject_sidebar_toc_assets(view)
305
+ inject_component_css(view, :sidebar_toc, sidebar_toc_css)
306
+ end
307
+
308
+ def inject_code_highlighting(view)
309
+ nil
310
+ end
311
+
312
+ def render_code_block_copy_button(view, component)
313
+ nil
314
+ end
315
+
316
+ def inject_mermaid_assets(view)
317
+ inject_component_css(view, :mermaid, mermaid_css)
318
+ end
319
+
320
+ private
321
+
322
+ if RUBY_ENGINE == "opal"
323
+ def md_to_html(text)
324
+ %x{ return marked.parse(#{text}) }
325
+ end
326
+ else
327
+ def md_to_html(text)
328
+ require "kramdown"
329
+ Kramdown::Document.new(text, input: "GFM", hard_wrap: false).to_html
330
+ end
331
+ end
332
+ end
333
+ end
334
+ end