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,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StreamWeaver
4
+ module Components
5
+ module Deck
6
+ # Auto-generated summary slide appended to the end of a design deck.
7
+ # Reads DeckState to display:
8
+ # - Each slide's title, selected option label, aside text, and notes
9
+ # - A "Still need: Slide X, Slide Y" message when selections are incomplete
10
+ # - A final notes textarea for overall comments
11
+ # - A Submit button gated on all slides having a selection
12
+ #
13
+ # Submit sets state[:_result] with { deck_selections: {...}, deck_notes: {...} }
14
+ # which triggers run_once! to unblock.
15
+ #
16
+ # sw- CSS classes:
17
+ # sw-deck-summary - summary slide wrapper
18
+ # sw-deck-summary__title - summary heading
19
+ # sw-deck-summary__cards - grid of summary cards
20
+ # sw-deck-summary__card - individual summary card
21
+ # sw-deck-summary__card-title - slide title in card
22
+ # sw-deck-summary__card-label - selected option label
23
+ # sw-deck-summary__card-aside - aside text
24
+ # sw-deck-summary__card-notes - notes text
25
+ # sw-deck-summary__card--empty - card with no selection
26
+ # sw-deck-summary__missing - "still need" message
27
+ # sw-deck-summary__final-notes - final notes textarea container
28
+ # sw-deck-summary__submit - submit button
29
+ # sw-deck-summary__submit--disabled - disabled submit button
30
+ #
31
+ # @example Auto-appended by design_deck DSL method (no manual usage needed)
32
+ class DeckSummary < Base
33
+ attr_accessor :deck_slides
34
+
35
+ def initialize(**options)
36
+ @deck_slides = []
37
+ super(**options)
38
+ end
39
+
40
+ def render(view, state)
41
+ view.adapter.render_deck_summary(view, self, state)
42
+ end
43
+
44
+ # CSS class list
45
+ def css_classes
46
+ "sw-deck-summary"
47
+ end
48
+
49
+ # Check if all slides have selections
50
+ #
51
+ # @param deck_state [DeckState, nil] The deck state instance
52
+ # @return [Boolean] true if every slide has a selection
53
+ def all_selected?(deck_state)
54
+ return false unless deck_state
55
+ return true if @deck_slides.empty?
56
+
57
+ @deck_slides.all? { |slide| deck_state.selection(slide.id) }
58
+ end
59
+
60
+ # Get list of slide titles that still need selections
61
+ #
62
+ # @param deck_state [DeckState, nil] The deck state instance
63
+ # @return [Array<String>] titles of slides missing selections
64
+ def missing_slides(deck_state)
65
+ return @deck_slides.map { |s| s.title || s.id } unless deck_state
66
+
67
+ @deck_slides.reject { |slide| deck_state.selection(slide.id) }
68
+ .map { |s| s.title || s.id }
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StreamWeaver
4
+ module Components
5
+ module Deck
6
+ # Top-level orchestrator for a design deck.
7
+ # Contains DeckSlide children, wraps in SlideContainer with :swap mode,
8
+ # validates uniqueness of slide IDs, and auto-appends navigation.
9
+ #
10
+ # NOT an App subclass -- it is a component composed via DSL methods on App.
11
+ #
12
+ # sw- CSS classes:
13
+ # sw-deck - outermost deck wrapper
14
+ # sw-deck__title - deck title heading
15
+ #
16
+ # @example
17
+ # design_deck "Architecture Direction" do
18
+ # slide "arch", "System Architecture" do
19
+ # option "Monolith" do
20
+ # code_block "app.listen(3000)", lang: "ts"
21
+ # end
22
+ # end
23
+ # end
24
+ class DesignDeck < Base
25
+ attr_reader :title
26
+ attr_accessor :children
27
+
28
+ # @param title [String] Deck title
29
+ # @param options [Hash] Additional options
30
+ def initialize(title, **options)
31
+ @title = title
32
+ @children = []
33
+ super(**options)
34
+ end
35
+
36
+ def render(view, state)
37
+ view.adapter.render_design_deck(view, self, state)
38
+ end
39
+
40
+ # Validate the deck structure.
41
+ # Raises if duplicate slide IDs are found.
42
+ def validate!
43
+ ids = @children
44
+ .select { |c| c.is_a?(DeckSlide) }
45
+ .map(&:id)
46
+
47
+ dupes = ids.group_by(&:itself).select { |_k, v| v.size > 1 }.keys
48
+ unless dupes.empty?
49
+ raise ArgumentError, "Duplicate slide IDs in design_deck: #{dupes.join(', ')}"
50
+ end
51
+ end
52
+
53
+ # CSS class list
54
+ def css_classes
55
+ "sw-deck"
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StreamWeaver
4
+ module Components
5
+ module Deck
6
+ # Generate-more controls rendered within a deck slide footer.
7
+ # Provides prompt input, count dropdown, generate/cancel buttons,
8
+ # and status display during generation.
9
+ #
10
+ # sw- CSS classes:
11
+ # sw-generate-more - controls wrapper
12
+ # sw-generate-more__form - form with prompt + count + button
13
+ # sw-generate-more__prompt - prompt text input
14
+ # sw-generate-more__count - count dropdown
15
+ # sw-generate-more__btn - generate button
16
+ # sw-generate-more__btn--cancel - cancel button
17
+ # sw-generate-more__status - status banner during generation
18
+ # sw-generate-more__status-dot - animated pulse dot
19
+ # sw-generate-more__status-text - status text
20
+ #
21
+ # @example
22
+ # GenerateMoreControls.new("arch", generate_state: gen_state)
23
+ class GenerateMoreControls < Base
24
+ attr_reader :slide_id
25
+
26
+ # @param slide_id [String] Slide this controls belong to
27
+ # @param generate_state [Hash] Current generation state from DeckState
28
+ # @param timeout [Integer] Timeout in seconds (default: 15)
29
+ def initialize(slide_id, generate_state: {}, timeout: 15, **options)
30
+ @slide_id = slide_id.to_s
31
+ @generate_state = generate_state
32
+ @timeout = timeout
33
+ super(**options)
34
+ end
35
+
36
+ def render(view, state)
37
+ view.adapter.render_generate_more_controls(view, self, state)
38
+ end
39
+
40
+ # Current generation status
41
+ # @return [Symbol] :idle, :generating, :timed_out, or :cancelled
42
+ def status
43
+ (@generate_state["status"] || "idle").to_sym
44
+ end
45
+
46
+ def generating?
47
+ status == :generating
48
+ end
49
+
50
+ def timed_out?
51
+ status == :timed_out
52
+ end
53
+
54
+ def requested_count
55
+ @generate_state["requested_count"] || 0
56
+ end
57
+
58
+ def received_count
59
+ @generate_state["received_count"] || 0
60
+ end
61
+
62
+ def remaining_count
63
+ generating? ? [requested_count - received_count, 0].max : 0
64
+ end
65
+
66
+ def prompt
67
+ @generate_state["prompt"]
68
+ end
69
+
70
+ def request_id
71
+ @generate_state["request_id"]
72
+ end
73
+
74
+ def timeout
75
+ @timeout
76
+ end
77
+
78
+ def css_classes
79
+ "sw-generate-more"
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StreamWeaver
4
+ module Components
5
+ module Deck
6
+ # AI model picker for generate-more functionality.
7
+ # Renders provider filter pills and a model list.
8
+ # Selected model is stored in DeckState for use by generate-more.
9
+ #
10
+ # sw- CSS classes:
11
+ # sw-model-selector - outermost wrapper
12
+ # sw-model-selector__pills - provider filter pill row
13
+ # sw-model-selector__pill - individual provider pill
14
+ # sw-model-selector__pill--active - active provider pill
15
+ # sw-model-selector__list - model list container
16
+ # sw-model-selector__item - individual model item
17
+ # sw-model-selector__item--selected - selected model item
18
+ # sw-model-selector__name - model display name
19
+ # sw-model-selector__provider - model provider label
20
+ #
21
+ # @example
22
+ # model_selector(
23
+ # models: [
24
+ # { id: "claude-3", name: "Claude 3 Opus", provider: "Anthropic" },
25
+ # { id: "gpt-4", name: "GPT-4", provider: "OpenAI" }
26
+ # ],
27
+ # default_model: "claude-3"
28
+ # )
29
+ class ModelSelector < Base
30
+ attr_reader :models, :default_model
31
+
32
+ # @param models [Array<Hash>] Array of { id:, name:, provider: } hashes
33
+ # @param default_model [String, nil] ID of the default selected model
34
+ # @param options [Hash] Additional options
35
+ def initialize(models: [], default_model: nil, **options)
36
+ @models = normalize_models(models)
37
+ @default_model = default_model || @models.first&.dig(:id)
38
+ super(**options)
39
+ end
40
+
41
+ def render(view, state)
42
+ view.adapter.render_model_selector(view, self, state)
43
+ end
44
+
45
+ # All unique provider names from the model list, sorted.
46
+ # @return [Array<String>]
47
+ def providers
48
+ @models.map { |m| m[:provider] }.uniq.sort
49
+ end
50
+
51
+ # Models filtered by provider (nil returns all).
52
+ # @param provider [String, nil] Provider name or nil for all
53
+ # @return [Array<Hash>]
54
+ def models_for_provider(provider)
55
+ return @models if provider.nil? || provider == "All"
56
+ @models.select { |m| m[:provider] == provider }
57
+ end
58
+
59
+ # Whether to show the selector (hidden when fewer than 2 models).
60
+ # @return [Boolean]
61
+ def visible?
62
+ @models.length >= 2
63
+ end
64
+
65
+ # CSS class list
66
+ def css_classes
67
+ "sw-model-selector"
68
+ end
69
+
70
+ private
71
+
72
+ def normalize_models(models)
73
+ models.map do |m|
74
+ {
75
+ id: (m[:id] || m["id"]).to_s,
76
+ name: (m[:name] || m["name"]).to_s,
77
+ provider: (m[:provider] || m["provider"]).to_s
78
+ }
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StreamWeaver
4
+ module Components
5
+ module Deck
6
+ # Shimmer animation placeholder shown while options are being generated.
7
+ # Rendered for (requested_count - received_count) placeholders.
8
+ #
9
+ # sw- CSS classes:
10
+ # sw-skeleton - skeleton card wrapper
11
+ # sw-skeleton__line - shimmer line element
12
+ # sw-skeleton__line--title - wider title line
13
+ # sw-skeleton__line--body - narrower body line
14
+ #
15
+ # @example
16
+ # SkeletonPlaceholder.new(index: 0)
17
+ class SkeletonPlaceholder < Base
18
+ attr_reader :index
19
+
20
+ # @param index [Integer] Index of this skeleton (for staggered animation)
21
+ def initialize(index: 0, **options)
22
+ @index = index
23
+ super(**options)
24
+ end
25
+
26
+ def render(view, state)
27
+ view.adapter.render_skeleton_placeholder(view, self, state)
28
+ end
29
+
30
+ def css_classes
31
+ "sw-skeleton"
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,125 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Diffy shells out to the system diff(1) binary, so it exists only on the
4
+ # server. In the browser the equivalent work is done by jsdiff -- see
5
+ # #unified_diff below.
6
+ require "diffy" unless RUBY_ENGINE == "opal"
7
+
8
+ module StreamWeaver
9
+ module Components
10
+ # Unified diff viewer: removed lines red, added green, context neutral.
11
+ # Two-column line-number gutter (before | after). Prism.js highlights per-line
12
+ # fragments — multi-line constructs (heredocs, block comments) highlight each line
13
+ # independently, same limitation as AnnotatedCode.
14
+ class DiffBlock < Base
15
+ DiffLine = Struct.new(:type, :old_num, :new_num, :prefix, :content, keyword_init: true)
16
+
17
+ attr_accessor :before_code, :after_code
18
+ attr_reader :language
19
+
20
+ def initialize(language: nil, **options)
21
+ @language = language
22
+ @before_code = ""
23
+ @after_code = ""
24
+ @options = options
25
+ end
26
+
27
+ def language_class
28
+ language ? "language-#{language}" : "language-none"
29
+ end
30
+
31
+ def parsed_lines
32
+ before = before_code.to_s
33
+ after = after_code.to_s
34
+ return [] if before.empty? && after.empty?
35
+
36
+ before += "\n" unless before.end_with?("\n")
37
+ after += "\n" unless after.end_with?("\n")
38
+
39
+ parse_unified_diff(unified_diff(before, after))
40
+ end
41
+
42
+ def render(view, state)
43
+ view.adapter.render_diff_block(view, self, state)
44
+ end
45
+
46
+ private
47
+
48
+ CONTEXT_LINES = 3
49
+
50
+ if RUBY_ENGINE == "opal"
51
+ # jsdiff, so the browser never needs a diff(1) binary it cannot have.
52
+ #
53
+ # structuredPatch rather than createPatch: the latter prepends an
54
+ # Index:/=== header that the server-side output has no equivalent for,
55
+ # and stripping a fixed number of lines back off is the kind of thing
56
+ # that breaks quietly. Building the unified text from the hunks keeps
57
+ # both engines feeding #parse_unified_diff the same shape.
58
+ #
59
+ # "" markers are dropped -- they are
60
+ # annotations rather than content, and parse_unified_diff would read
61
+ # the leading backslash as a context line.
62
+ def unified_diff(before, after)
63
+ # :nocov:
64
+ %x{
65
+ var patch = Diff.structuredPatch("", "", #{before}, #{after}, "", "",
66
+ { context: #{CONTEXT_LINES} });
67
+ var out = [];
68
+ patch.hunks.forEach(function(h) {
69
+ out.push("@@ -" + h.oldStart + "," + h.oldLines +
70
+ " +" + h.newStart + "," + h.newLines + " @@");
71
+ h.lines.forEach(function(l) {
72
+ if (l.charAt(0) !== "\\\\") out.push(l);
73
+ });
74
+ });
75
+ return out.length ? out.join("\n") + "\n" : "";
76
+ }
77
+ # :nocov:
78
+ end
79
+ else
80
+ def unified_diff(before, after)
81
+ Diffy::Diff.new(before, after, context: CONTEXT_LINES).to_s(:text)
82
+ end
83
+ end
84
+
85
+ HUNK_HEADER_RE = /^@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@/.freeze
86
+
87
+ def parse_unified_diff(text)
88
+ lines = []
89
+ old_line = 1
90
+ new_line = 1
91
+
92
+ text.each_line do |raw_line|
93
+ line = raw_line.chomp
94
+
95
+ if (m = HUNK_HEADER_RE.match(line))
96
+ old_line = m[1].to_i
97
+ new_line = m[2].to_i
98
+ lines << DiffLine.new(type: :hunk_header, old_num: nil, new_num: nil, prefix: "@@", content: line)
99
+ next
100
+ end
101
+
102
+ next if line.start_with?("---", "+++")
103
+
104
+ prefix = line[0] || " "
105
+ content = line[1..] || ""
106
+
107
+ case prefix
108
+ when "-"
109
+ lines << DiffLine.new(type: :removed, old_num: old_line, new_num: nil, prefix: "-", content: content)
110
+ old_line += 1
111
+ when "+"
112
+ lines << DiffLine.new(type: :added, old_num: nil, new_num: new_line, prefix: "+", content: content)
113
+ new_line += 1
114
+ else
115
+ lines << DiffLine.new(type: :context, old_num: old_line, new_num: new_line, prefix: " ", content: content)
116
+ old_line += 1
117
+ new_line += 1
118
+ end
119
+ end
120
+
121
+ lines
122
+ end
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StreamWeaver
4
+ module Components
5
+ # Document-level header with eyebrow label, serif title, and meta pill row.
6
+ # Used to open long-form documents: PRDs, reports, explainers.
7
+ #
8
+ # Pills are an array of mixed items:
9
+ # - String → rendered as plain meta text
10
+ # - Hash { text:, variant: } → colored pill (:default, :warn, :good)
11
+ #
12
+ # @example
13
+ # doc_header(
14
+ # eyebrow: "cultiv-ai · Personal OS",
15
+ # title: "Calendar-Driven Travel State",
16
+ # pills: [
17
+ # { text: "Draft" },
18
+ # "June 25, 2026",
19
+ # "Author: Forrest Chang"
20
+ # ]
21
+ # )
22
+ class DocHeader < Base
23
+ attr_reader :eyebrow, :title, :pills
24
+
25
+ def initialize(eyebrow: nil, title:, pills: [], **options)
26
+ @eyebrow = eyebrow
27
+ @title = title
28
+ @pills = Array(pills)
29
+ @options = options
30
+ end
31
+
32
+ def render(view, state)
33
+ view.adapter.render_doc_header(view, self, state)
34
+ end
35
+ end
36
+
37
+ # Numbered section eyebrow + heading pair for document sections.
38
+ # Renders a monospace number badge and an h2 heading with a decorative line.
39
+ #
40
+ # @example
41
+ # doc_section_header "01", "Problem Statement", id: "problem"
42
+ class DocSectionHeader < Base
43
+ attr_reader :number, :title, :anchor_id
44
+
45
+ def initialize(number, title, id: nil, **options)
46
+ @number = number.to_s
47
+ @title = title
48
+ @anchor_id = id
49
+ @options = options
50
+ end
51
+
52
+ def render(view, state)
53
+ view.adapter.render_doc_section_header(view, self, state)
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'base64'
4
+
5
+ module StreamWeaver
6
+ module Components
7
+ # Image display component with optional caption and base64 export support.
8
+ #
9
+ # Renders an image with optional caption text below it.
10
+ # Supports local file paths, URLs, and base64 data URI conversion
11
+ # for self-contained HTML export.
12
+ #
13
+ # @example Basic usage
14
+ # image_block("photo.png", alt: "A photo")
15
+ #
16
+ # @example With caption
17
+ # image_block("diagram.svg", caption: "Figure 1: Architecture")
18
+ #
19
+ # @example Base64 export mode
20
+ # image_block("local/image.png", base64: true)
21
+ class ImageBlock < Base
22
+ attr_reader :src, :alt, :caption, :base64
23
+
24
+ # @param src [String] Image source (URL or file path)
25
+ # @param alt [String] Alt text for accessibility (default: "")
26
+ # @param caption [String, nil] Caption text to display below the image
27
+ # @param base64 [Boolean] Convert local file to base64 data URI (default: false)
28
+ # @param options [Hash] Additional options
29
+ def initialize(src, alt: "", caption: nil, base64: false, **options)
30
+ @src = src.to_s
31
+ @alt = alt.to_s
32
+ @caption = caption
33
+ @base64 = base64
34
+ @options = options
35
+ end
36
+
37
+ # Resolve the image source, converting to data URI if base64 mode is enabled.
38
+ # @return [String] The resolved src attribute value
39
+ def resolved_src
40
+ return @src unless @base64 && File.exist?(@src)
41
+
42
+ mime = mime_type(@src)
43
+ data = Base64.strict_encode64(File.binread(@src))
44
+ "data:#{mime};base64,#{data}"
45
+ end
46
+
47
+ def render(view, state)
48
+ view.adapter.render_image_block(view, self, state)
49
+ end
50
+
51
+ private
52
+
53
+ # Detect MIME type from file extension
54
+ # @param path [String] File path
55
+ # @return [String] MIME type
56
+ def mime_type(path)
57
+ case File.extname(path).downcase
58
+ when ".png" then "image/png"
59
+ when ".jpg", ".jpeg" then "image/jpeg"
60
+ when ".gif" then "image/gif"
61
+ when ".svg" then "image/svg+xml"
62
+ when ".webp" then "image/webp"
63
+ when ".bmp" then "image/bmp"
64
+ when ".ico" then "image/x-icon"
65
+ else "application/octet-stream"
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StreamWeaver
4
+ module Components
5
+ # File-path-to-rationale mapping component for pre-flight planning.
6
+ # Shows what files will be touched and why, before writing any code.
7
+ #
8
+ # @example
9
+ # implementation_map(files: [
10
+ # { path: "lib/foo.rb", note: "Add the new method" },
11
+ # { path: "spec/foo_spec.rb", note: "Cover edge cases" }
12
+ # ])
13
+ class ImplementationMap < Base
14
+ attr_reader :files
15
+
16
+ # @param files [Array<Hash>] Array of {path:, note:} or {"path"=>,"note"=>} hashes
17
+ def initialize(files: [], **options)
18
+ @files = files.map { |f| normalize(f) }
19
+ @options = options
20
+ end
21
+
22
+ def render(view, state)
23
+ view.adapter.render_implementation_map(view, self, state)
24
+ end
25
+
26
+ private
27
+
28
+ def normalize(entry)
29
+ { path: entry[:path] || entry["path"] || "", note: entry[:note] || entry["note"] || "" }
30
+ end
31
+ end
32
+ end
33
+ end