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,135 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'time'
5
+ require 'fileutils'
6
+ require 'securerandom'
7
+
8
+ module StreamWeaver
9
+ module Canvas
10
+ # Persistent record of gists StreamWeaver has published on the user's
11
+ # behalf, so a later session can find "the gist for this doc" without
12
+ # keeping any in-memory session state.
13
+ #
14
+ # Unlike DocStore (one file per doc, resolved via a 3-tier git-aware
15
+ # path), GistStore is a single flat JSON file holding every recorded
16
+ # gist, keyed by doc base name. There is no git-root behavior here --
17
+ # gist publication has no repo-relative meaning, so the path is just
18
+ # ENV override or DEFAULT_PATH.
19
+ #
20
+ # This is an index, not a source of truth -- every entry in it can be
21
+ # rebuilt by re-publishing. Two deliberate concessions follow from that:
22
+ # a corrupt or empty file degrades to empty rather than raising
23
+ # (raising would also break the calls you'd use to repair it), and two
24
+ # processes recording at once is last-writer-wins -- the atomic write
25
+ # below buys readers a whole file, not writers a serialized one.
26
+ #
27
+ # Resolved path priority:
28
+ # 1. ENV['STREAMWEAVER_GIST_STORE'] (test/override hook)
29
+ # 2. ~/.streamweaver/canvas/gists.json
30
+ module GistStore
31
+ DEFAULT_PATH = File.expand_path('~/.streamweaver/canvas/gists.json')
32
+
33
+ module_function
34
+
35
+ # Resolves the store file path. Read fresh on every call (not memoized)
36
+ # so an ENV override flips behavior mid-process -- specs rely on this,
37
+ # same rationale as DocStore#path.
38
+ def path
39
+ env = ENV['STREAMWEAVER_GIST_STORE']
40
+ return env if env && !env.empty?
41
+
42
+ DEFAULT_PATH
43
+ end
44
+
45
+ # Returns the full store: name => {"id" =>, "url" =>, "revisions" =>,
46
+ # "created_at" =>, "updated_at" =>}. Empty hash for a missing, empty,
47
+ # or corrupt file -- see the module comment for why this degrades
48
+ # instead of raising.
49
+ def all
50
+ file = path
51
+ return {} unless File.exist?(file)
52
+
53
+ content = File.read(file)
54
+ return {} if content.strip.empty?
55
+
56
+ JSON.parse(content)
57
+ rescue JSON::ParserError
58
+ {}
59
+ end
60
+
61
+ # Returns the entry for `name`, or nil if it has never been recorded.
62
+ def lookup(name)
63
+ all[name.to_s]
64
+ end
65
+
66
+ # Upserts the entry for `name`: id/url/revisions are overwritten,
67
+ # created_at is set once and preserved across later calls, updated_at
68
+ # always advances to now. Returns the resulting entry hash.
69
+ def record(name, id:, url:, revisions:)
70
+ name = name.to_s
71
+ store = all
72
+ now = timestamp
73
+
74
+ entry = {
75
+ 'id' => id,
76
+ 'url' => url,
77
+ 'revisions' => revisions,
78
+ 'created_at' => store.dig(name, 'created_at') || now,
79
+ 'updated_at' => now
80
+ }
81
+ store[name] = entry
82
+ persist(store)
83
+ entry
84
+ end
85
+
86
+ # Among recorded names starting with `prefix`, returns the entry with
87
+ # the most recent updated_at -- how a caller with only a doc-name
88
+ # prefix (e.g. a canvas session name) finds "the gist for this
89
+ # session" with no session state of its own. nil if nothing matches.
90
+ def latest_for_prefix(prefix)
91
+ prefix = prefix.to_s
92
+ match = all.select { |name, _| name.start_with?(prefix) }.max_by { |_, entry| entry['updated_at'] }
93
+ match&.last
94
+ end
95
+
96
+ # Removes the entry for `name` if present. No-op if it was never
97
+ # recorded.
98
+ def forget(name)
99
+ store = all
100
+ persist(store) if store.delete(name.to_s)
101
+ end
102
+
103
+ # Millisecond precision, not the 1-second default: two gists published
104
+ # within the same second are ordinary, and latest_for_prefix has to
105
+ # break that tie correctly rather than by hash insertion order.
106
+ def timestamp
107
+ Time.now.utc.iso8601(3)
108
+ end
109
+ private_class_method :timestamp
110
+
111
+ # Writes `store` to `path` atomically: JSON goes to a temp file in the
112
+ # SAME directory (so the rename is on one filesystem, where POSIX
113
+ # guarantees it is atomic) and is then renamed over the target. A plain
114
+ # File.write truncates first, which would let a concurrent reader
115
+ # observe an empty or half-written file; the temp file is removed if
116
+ # the write or rename fails so a failure never leaves stray files
117
+ # behind.
118
+ def persist(store)
119
+ file = path
120
+ dir = File.dirname(file)
121
+ FileUtils.mkdir_p(dir)
122
+
123
+ tmp = File.join(dir, ".#{File.basename(file)}.#{Process.pid}.#{SecureRandom.hex(4)}.tmp")
124
+ begin
125
+ File.write(tmp, JSON.generate(store))
126
+ File.rename(tmp, file)
127
+ rescue StandardError
128
+ FileUtils.rm_f(tmp)
129
+ raise
130
+ end
131
+ end
132
+ private_class_method :persist
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,109 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StreamWeaver
4
+ module Canvas
5
+ # High-level helper methods for common canvas patterns.
6
+ # Generates DSL code and parses results for pick, confirm, and form operations.
7
+ module Helpers
8
+ module_function
9
+
10
+ # Generate DSL for a single-choice picker
11
+ # @param title [String] The title/prompt
12
+ # @param choices [Array<String>] Available choices
13
+ # @return [String] DSL code
14
+ def pick_dsl(title, choices)
15
+ choices_str = choices.map { |c| "\"#{c}\"" }.join(", ")
16
+
17
+ <<~DSL
18
+ header1 "#{title}"
19
+ radio_group :choice, [#{choices_str}]
20
+ button "Select", id: "btn_submit"
21
+ DSL
22
+ end
23
+
24
+ # Generate DSL for a confirmation dialog
25
+ # @param message [String] The confirmation message
26
+ # @param yes_label [String] Label for confirm button (default: "Confirm")
27
+ # @param no_label [String] Label for cancel button (default: "Cancel")
28
+ # @return [String] DSL code
29
+ def confirm_dsl(message, yes_label: "Confirm", no_label: "Cancel")
30
+ <<~DSL
31
+ header1 "#{message}"
32
+ hstack do
33
+ button "#{no_label}", id: "btn_cancel", style: :secondary
34
+ button "#{yes_label}", id: "btn_confirm"
35
+ end
36
+ DSL
37
+ end
38
+
39
+ # Generate DSL for a form with specified fields
40
+ # @param title [String] The form title
41
+ # @param fields [Hash] Field definitions { name: { type: :text, ... }, ... }
42
+ # @return [String] DSL code
43
+ def form_dsl(title, fields)
44
+ field_dsl = fields.map do |name, config|
45
+ case config[:type]
46
+ when :text
47
+ placeholder = config[:placeholder] ? ", placeholder: \"#{config[:placeholder]}\"" : ""
48
+ "text_field :#{name}#{placeholder}"
49
+ when :textarea
50
+ placeholder = config[:placeholder] ? ", placeholder: \"#{config[:placeholder]}\"" : ""
51
+ rows = config[:rows] ? ", rows: #{config[:rows]}" : ""
52
+ "text_area :#{name}#{placeholder}#{rows}"
53
+ when :radio
54
+ choices_str = config[:choices].map { |c| "\"#{c}\"" }.join(", ")
55
+ "radio_group :#{name}, [#{choices_str}]"
56
+ when :select
57
+ choices_str = config[:choices].map { |c| "\"#{c}\"" }.join(", ")
58
+ "select :#{name}, [#{choices_str}]"
59
+ when :checkbox
60
+ label = config[:label] || name.to_s.capitalize
61
+ "checkbox :#{name}, \"#{label}\""
62
+ else
63
+ "text_field :#{name}"
64
+ end
65
+ end.join("\n")
66
+
67
+ <<~DSL
68
+ header1 "#{title}"
69
+ #{field_dsl}
70
+ button "Submit", id: "btn_submit"
71
+ DSL
72
+ end
73
+
74
+ # Parse pick result from event data
75
+ # @param event_data [Hash] Event data from browser
76
+ # @return [String, nil] Selected choice
77
+ def parse_pick_result(event_data)
78
+ # Choice is nested in state from the browser event
79
+ state = event_data[:state] || event_data["state"] || {}
80
+ state[:choice] || state["choice"] || event_data[:choice] || event_data["choice"]
81
+ end
82
+
83
+ # Parse confirm result from event data
84
+ # @param event_data [Hash] Event data from browser
85
+ # @return [Boolean] true if confirmed, false if cancelled
86
+ def parse_confirm_result(event_data)
87
+ # Button ID is in :button from the browser event
88
+ button = event_data[:button] || event_data["button"] ||
89
+ event_data[:_button] || event_data["_button"] || ""
90
+ button.to_s.include?("confirm")
91
+ end
92
+
93
+ # Parse form result from event data
94
+ # @param event_data [Hash] Event data from browser
95
+ # @param field_names [Array<Symbol>] Expected field names
96
+ # @return [Hash] Form field values
97
+ def parse_form_result(event_data, field_names)
98
+ # Form values are in state from the browser event
99
+ state = event_data[:state] || event_data["state"] || event_data
100
+ result = {}
101
+ field_names.each do |name|
102
+ key = name.to_sym
103
+ result[key] = state[key] || state[name.to_s]
104
+ end
105
+ result
106
+ end
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'fileutils'
4
+
5
+ module StreamWeaver
6
+ module Canvas
7
+ # Tier 1 (off-repo) history of canvas-push DSL snapshots.
8
+ #
9
+ # Snapshots live at <root>/<session>/<YYYYMMDD_HHMMSS>.rb where <root> is
10
+ # ENV['STREAMWEAVER_HISTORY_ROOT'] || ~/.streamweaver/history. The env-var
11
+ # override exists so specs (and ad-hoc tooling) can redirect writes away
12
+ # from the user's real home directory.
13
+ #
14
+ # Session names are validated against an allowlist (alnum + . _ -, must
15
+ # start with alnum) and an explicit '..'-substring check. Bad names raise
16
+ # ArgumentError -- raising rather than sanitizing keeps the contract
17
+ # explicit and avoids silently storing under an unexpected directory.
18
+ module History
19
+ MAX_AGE_DAYS = 7
20
+ MAX_AGE_SECONDS = MAX_AGE_DAYS * 86_400
21
+ DEFAULT_ROOT = File.expand_path('~/.streamweaver/history')
22
+ VALID_NAME = /\A[A-Za-z0-9][A-Za-z0-9._-]*\z/
23
+
24
+ module_function
25
+
26
+ def root
27
+ env = ENV['STREAMWEAVER_HISTORY_ROOT']
28
+ return env if env && !env.empty?
29
+
30
+ DEFAULT_ROOT
31
+ end
32
+
33
+ # Writes dsl to <root>/<session>/<timestamp>.rb and returns the path.
34
+ # Appends a numeric suffix on collision so back-to-back writes within
35
+ # the same second don't clobber each other. Paths are claimed atomically
36
+ # (File::EXCL), so concurrent callers get distinct files instead of
37
+ # racing between an exist? check and the write.
38
+ def record(session, dsl)
39
+ validate_session!(session)
40
+ dir = File.join(root, session)
41
+ FileUtils.mkdir_p(dir)
42
+
43
+ stamp = Time.now.strftime('%Y%m%d_%H%M%S')
44
+ write_unique(dir, stamp, dsl)
45
+ end
46
+
47
+ # Deletes snapshot files older than MAX_AGE_DAYS and prunes any
48
+ # session directory left empty afterwards. No-op if root is missing.
49
+ def cleanup
50
+ base = root
51
+ return unless Dir.exist?(base)
52
+
53
+ cutoff = Time.now - MAX_AGE_SECONDS
54
+
55
+ Dir.children(base).each do |session|
56
+ session_dir = File.join(base, session)
57
+ next unless File.directory?(session_dir)
58
+
59
+ Dir.glob(File.join(session_dir, '*.rb')).each do |file|
60
+ File.delete(file) if File.mtime(file) < cutoff
61
+ end
62
+
63
+ Dir.rmdir(session_dir) if Dir.children(session_dir).empty?
64
+ end
65
+ end
66
+
67
+ def validate_session!(session)
68
+ return if session.is_a?(String) && session.match?(VALID_NAME) && !session.include?('..')
69
+
70
+ raise ArgumentError, "invalid session name: #{session.inspect}"
71
+ end
72
+ private_class_method :validate_session!
73
+
74
+ def write_unique(dir, stamp, dsl)
75
+ suffix = nil
76
+ loop do
77
+ name = suffix ? "#{stamp}_#{suffix}.rb" : "#{stamp}.rb"
78
+ path = File.join(dir, name)
79
+ begin
80
+ File.open(path, File::WRONLY | File::CREAT | File::EXCL) { |f| f.write(dsl) }
81
+ return path
82
+ rescue Errno::EEXIST
83
+ suffix = (suffix || 0) + 1
84
+ end
85
+ end
86
+ end
87
+ private_class_method :write_unique
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+
5
+ module StreamWeaver
6
+ module Canvas
7
+ # Protocol for Unix socket IPC between Claude Code and Canvas Bridge.
8
+ # Uses newline-delimited JSON.
9
+ module Protocol
10
+ # Encode a message hash to newline-delimited JSON
11
+ # @param message [Hash] Message to encode
12
+ # @return [String] JSON string with trailing newline
13
+ def self.encode(message)
14
+ JSON.generate(message) + "\n"
15
+ end
16
+
17
+ # Decode a JSON string to a message hash
18
+ # @param json [String] JSON string
19
+ # @return [Hash, nil] Decoded message with symbol keys, or nil if invalid
20
+ def self.decode(json)
21
+ JSON.parse(json, symbolize_names: true)
22
+ rescue JSON::ParserError
23
+ nil
24
+ end
25
+
26
+ # Parse a buffer containing potentially multiple messages
27
+ # @param buffer [String] Buffer with newline-delimited JSON
28
+ # @return [Array<Array<Hash>, String>] [messages, remaining_buffer]
29
+ def self.parse_buffer(buffer)
30
+ lines = buffer.split("\n", -1)
31
+ remaining = lines.pop || ''
32
+
33
+ messages = lines.filter_map do |line|
34
+ decode(line) unless line.empty?
35
+ end
36
+
37
+ [messages, remaining]
38
+ end
39
+
40
+ # Message factory methods
41
+ module Messages
42
+ # Claude → Bridge: Create a new session
43
+ def self.create(name, layout: :fluid, theme: :default)
44
+ { type: 'create', name: name, layout: layout, theme: theme }
45
+ end
46
+
47
+ # Claude → Bridge: Push DSL content to session
48
+ # @param source_dir [String, nil] Git root of the pushing side's cwd
49
+ # (DocStore.git_root(Dir.pwd) computed by the caller), or nil
50
+ # outside a repo. Not the bridge's own cwd -- see Session#source_dir.
51
+ def self.push(name, dsl, source_dir: nil)
52
+ { type: 'push', name: name, dsl: dsl, source_dir: source_dir }
53
+ end
54
+
55
+ # Claude → Bridge: Close a session
56
+ def self.close(name)
57
+ { type: 'close', name: name }
58
+ end
59
+
60
+ # Claude → Bridge: Fetch a session's current raw DSL + theme/layout
61
+ # (canvas-snapshot, stream_weaver-ps84) -- the live in-memory source
62
+ # of truth, not the ephemeral ~/.streamweaver/history/ trail, which
63
+ # expires after 7 days and isn't written by every code path that can
64
+ # populate a session's DSL.
65
+ def self.get_dsl(name)
66
+ { type: 'get_dsl', name: name }
67
+ end
68
+
69
+ # Claude → Bridge: Set iTerm pane ID for a session
70
+ def self.set_pane_id(name, pane_id)
71
+ { type: 'set_pane_id', name: name, pane_id: pane_id }
72
+ end
73
+
74
+ # Bridge → Claude: Session is ready
75
+ def self.ready(name, url)
76
+ { type: 'ready', name: name, url: url }
77
+ end
78
+
79
+ # Bridge → Claude: User interaction event
80
+ def self.event(name, event_type, data)
81
+ { type: 'event', name: name, event: event_type, data: data }
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end