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,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ripper"
4
+
5
+ module StreamWeaver
6
+ module Org
7
+ # Splits DSL source text into its top-level statements, each as a
8
+ # literal source-text slice, using Ripper for grammar-accurate
9
+ # boundaries -- correctly distinguishes a modifier-if ("md \"x\" if
10
+ # flag", one statement) from a block-if, and isn't confused by heredoc
11
+ # bodies or nested do..end blocks the way a naive newline/bracket-depth
12
+ # split would be.
13
+ #
14
+ # Used by Writer to recover a genuinely verbatim source span for the
15
+ # raw-passthrough escape hatch (see Writer#raw_passthrough), when the
16
+ # top-level statement <-> component correspondence is unambiguous.
17
+ module SourceSplitter
18
+ # Returns an array of source-text strings, one per top-level
19
+ # statement, in document order. Returns nil if `source` doesn't parse
20
+ # (callers should already have proven it parses -- Writer only calls
21
+ # this after RecordingContext's own instance_eval succeeded -- so this
22
+ # is a defensive fallback, not an expected path).
23
+ def self.top_level_statements(source)
24
+ sexp = Ripper.sexp(source)
25
+ return nil unless sexp
26
+
27
+ # Empty (or whitespace/comment-only) source parses to a single
28
+ # synthetic :void_stmt node with no position of its own.
29
+ stmts = sexp[1]
30
+ return [] if stmts == [[:void_stmt]] || stmts.empty?
31
+
32
+ lines = source.lines
33
+ starts = stmts.map { |s| start_line(s) }
34
+ starts.each_with_index.map do |line, i|
35
+ end_line = i + 1 < starts.length ? starts[i + 1] - 1 : lines.length
36
+ span = lines[(line - 1)...end_line]
37
+ span.pop while span.last&.strip == ""
38
+ span.join.chomp
39
+ end
40
+ end
41
+
42
+ # Recursively finds the line number of the leftmost terminal token in
43
+ # a Ripper sexp subtree (as returned by Ripper.sexp, which -- unlike
44
+ # bare Ripper::SexpBuilder -- both reliably signals a syntax error
45
+ # (nil) and flattens :program's body into a plain array). Terminal
46
+ # (scanner-event) nodes are 3-element arrays whose last element is a
47
+ # [lineno, column] pair; composite (parser-event) nodes have no
48
+ # position of their own -- the position comes from the earliest
49
+ # terminal nested inside.
50
+ def self.start_line(node, best = nil)
51
+ return best unless node.is_a?(Array)
52
+
53
+ if node.length == 3 && node[2].is_a?(Array) && node[2].length == 2 && node[2].all? { |x| x.is_a?(Integer) }
54
+ pos = node[2][0]
55
+ best = pos if best.nil? || pos < best
56
+ end
57
+ node.each { |n| best = start_line(n, best) }
58
+ best
59
+ end
60
+ private_class_method :start_line
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,300 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "recording_context"
4
+ require_relative "inline"
5
+ require_relative "source_splitter"
6
+
7
+ module StreamWeaver
8
+ module Org
9
+ # Converts a StreamWeaver doc-builder DSL body (the text canvas-read
10
+ # instance_evals) into StreamWeaver-flavored org-mode text. See
11
+ # docs/superpowers/specs/2026-08-13-org-doc-format-design.md for the
12
+ # full format spec this implements.
13
+ class Writer
14
+ def self.from_dsl(dsl_text)
15
+ new(dsl_text).call
16
+ end
17
+
18
+ def initialize(dsl_text)
19
+ @dsl_text = dsl_text
20
+ @coverage_counts = { total: 0, passthrough_verbatim: 0, passthrough_lossy: 0, omitted: 0 }
21
+ @called = false
22
+ @nesting_depth = 0
23
+ end
24
+
25
+ # Top-level DSL statements RecordingContext deliberately no-ops (see
26
+ # its class comment) -- these produce zero components, so they'd
27
+ # otherwise break the 1:1 statement<->component correspondence
28
+ # #build_raw_sources relies on. Every real saved doc has at least one
29
+ # of these prepended by DocStore.dsl_with_metadata.
30
+ NO_OP_STATEMENT_RE = /\Ause_(?:theme|layout)\b/
31
+
32
+ # A saved .org is a static document and cannot hold a live control, so
33
+ # these are DROPPED rather than passed through as dead source (see
34
+ # #omitted_control). Two shapes reach that decision: a real component
35
+ # DisplayDSL knows how to build (button, select), and an
36
+ # UnsupportedCall placeholder for App's form vocabulary, which
37
+ # RecordingContext has no implementation for at all. Both are answered
38
+ # here, in the one place, so "is X a live control?" has a single
39
+ # source of truth.
40
+ OMITTED_COMPONENTS = [Components::Button, Components::Select].freeze
41
+ INTERACTIVE_CALLS = %i[
42
+ form form_for submit cancel submit_label cancel_label validate
43
+ text_field text_area date_field code_editor
44
+ checkbox checkbox_group radio_group tag_buttons chip_group item
45
+ clickable model_selector confirmation_bar
46
+ ].freeze
47
+
48
+ def call
49
+ ctx = RecordingContext.new
50
+ ctx.instance_eval(@dsl_text)
51
+ components = ctx.components
52
+ @raw_sources = build_raw_sources(components)
53
+
54
+ toc_sections = components.find { |c| c.is_a?(Components::SidebarToc) }&.sections || []
55
+ toc_by_id = toc_sections.each_with_object({}) { |s, h| h[s[:id]] = s[:label] }
56
+
57
+ header = components.find { |c| c.is_a?(Components::DocHeader) }
58
+ body_components = components.reject { |c| c.is_a?(Components::SidebarToc) || c.is_a?(Components::DocHeader) }
59
+
60
+ out = [preamble(header), sections_and_body(body_components, toc_by_id)].join("\n").rstrip + "\n"
61
+ @called = true
62
+ out
63
+ end
64
+
65
+ # Accounting for the single conversion pass #call just ran, one bucket
66
+ # per escape hatch: `passthrough_verbatim`/`passthrough_lossy` (raw
67
+ # source kept, styled or not) and `omitted` (interactive controls
68
+ # dropped on purpose -- the .org reader gets a document with the form
69
+ # missing, and the save UI says so). `recognized` is DERIVED from the
70
+ # rest rather than tracked, so the numbers cannot disagree by
71
+ # construction. `total` only reflects body_components (everything
72
+ # sections_and_body walks) -- DocHeader and SidebarToc are metadata,
73
+ # filtered out before that walk starts (see #call), and never
74
+ # contribute to any of these five numbers.
75
+ def coverage
76
+ raise "call #call before #coverage" unless @called
77
+
78
+ escaped = @coverage_counts.values_at(:passthrough_verbatim, :passthrough_lossy, :omitted).sum
79
+ @coverage_counts.merge(recognized: @coverage_counts[:total] - escaped)
80
+ end
81
+
82
+ private
83
+
84
+ def preamble(header)
85
+ return "#+STREAMWEAVER_DSL: 1\n" unless header
86
+
87
+ out = +"#+STREAMWEAVER_DSL: 1\n#+TITLE: #{header.title}\n"
88
+ if header.eyebrow || (header.pills && !header.pills.empty?)
89
+ out << "\n#+begin_quote\n"
90
+ out << "/#{header.eyebrow}/\n" if header.eyebrow
91
+ if header.pills && !header.pills.empty?
92
+ out << header.pills.map { |p| render_pill(p) }.join(" · ") + "\n"
93
+ end
94
+ out << "#+end_quote\n"
95
+ end
96
+ out
97
+ end
98
+
99
+ def render_pill(pill)
100
+ return pill unless pill.is_a?(Hash)
101
+ variant = pill[:variant] || pill["variant"]
102
+ text = pill[:text] || pill["text"]
103
+ variant ? "*[#{variant}] #{text}*" : text
104
+ end
105
+
106
+ def sections_and_body(components, toc_by_id)
107
+ out = +""
108
+ components.each do |c|
109
+ count(:total)
110
+ out << if c.is_a?(Components::DocSectionHeader)
111
+ section_headline(c, toc_by_id)
112
+ else
113
+ render_component(c)
114
+ end
115
+ end
116
+ out
117
+ end
118
+
119
+ def section_headline(section, toc_by_id)
120
+ in_toc = toc_by_id.key?(section.anchor_id)
121
+ depth = in_toc ? "*" : "**"
122
+ headline = "\n#{depth} #{section.number} #{section.title}\n:PROPERTIES:\n:CUSTOM_ID: #{section.anchor_id}\n"
123
+ toc_label = toc_by_id[section.anchor_id]
124
+ headline << ":TOC_LABEL: #{toc_label}\n" if in_toc && toc_label != section.title
125
+ headline << ":END:\n"
126
+ end
127
+
128
+ # WARNING for future editors: #count (below) only counts at
129
+ # @nesting_depth zero, and only render_quote's recursive call currently
130
+ # increments that depth. If you add ANOTHER path that calls
131
+ # render_component recursively (not via render_quote), wrap it in the
132
+ # same @nesting_depth += 1 / ensure @nesting_depth -= 1 pattern --
133
+ # otherwise a nested escape hatch there is silently counted as
134
+ # top-level, reintroducing the negative-`recognized` bug the guard
135
+ # exists to prevent (see #count for the story).
136
+ def render_component(component)
137
+ case component
138
+ when Components::Markdown
139
+ "\n#{Inline.md_to_org(component.content)}\n"
140
+ when Components::Table
141
+ render_table(component)
142
+ when Components::Callout
143
+ marker = component.title ? "*#{component.icon} #{component.title}*" : "*#{component.icon}*"
144
+ render_quote(marker, component.children)
145
+ when Components::Card
146
+ render_card(component)
147
+ when Components::Comparison
148
+ render_comparison(component)
149
+ when Components::Mermaid
150
+ header = component.zoom ? "mermaid :zoom t" : "mermaid"
151
+ "\n#+begin_src #{header}\n#{component.code.to_s.rstrip}\n#+end_src\n"
152
+ when Components::CodeBlock
153
+ "\n#+begin_src #{component.lang}\n#{component.code.to_s.rstrip}\n#+end_src\n"
154
+ else
155
+ live_control?(component) ? omitted_control(component) : raw_passthrough(component)
156
+ end
157
+ end
158
+
159
+ def live_control?(component)
160
+ return INTERACTIVE_CALLS.include?(component.name) if component.is_a?(RecordingContext::UnsupportedCall)
161
+
162
+ OMITTED_COMPONENTS.any? { |klass| component.is_a?(klass) }
163
+ end
164
+
165
+ # The third escape hatch, for live controls (see OMITTED_COMPONENTS /
166
+ # INTERACTIVE_CALLS). Raw-passthrough would "preserve" them as dead
167
+ # source in a document that has no server behind it; a static org doc
168
+ # simply cannot hold a form. So they are DROPPED from the body, marked
169
+ # by an org keyword line -- invisible in any org viewer and already
170
+ # ignored by Org::Reader's PREAMBLE_RE, so the breadcrumb never
171
+ # reappears as prose on the way back -- and counted separately in
172
+ # #coverage so the Save-as-doc UI can tell the user what the .org left
173
+ # behind. The .rb sibling is untouched by any of this and remains the
174
+ # full-fidelity format.
175
+ def omitted_control(component)
176
+ count(:omitted)
177
+ "\n#+STREAMWEAVER_OMITTED: #{control_name(component)}\n"
178
+ end
179
+
180
+ def control_name(component)
181
+ return component.name if component.is_a?(RecordingContext::UnsupportedCall)
182
+
183
+ component.class.name.split("::").last.gsub(/(?<!\A)([A-Z])/, '_\1').downcase
184
+ end
185
+
186
+ def render_table(table)
187
+ # table.headers/#rows are the raw constructor args, nil for any form
188
+ # other than table(headers:, rows:) -- e.g. table(data:) or the
189
+ # column DSL, which resolve their actual content lazily elsewhere
190
+ # and are out of scope for this format (see the design spec's
191
+ # Tables section). Falling through to the normal empty-array
192
+ # default here would silently emit a garbage 1-empty-column table
193
+ # with all row data gone; raw-passthrough matches the same
194
+ # unrecognized-shape convention used elsewhere in this file.
195
+ return raw_passthrough(table) if table.headers.nil?
196
+
197
+ headers = table.headers
198
+ rows = table.rows || []
199
+ convert = ->(s) { table.markdown ? Inline.md_to_org(s.to_s) : s.to_s }
200
+ widths = headers.each_index.map { |i| ([headers[i]] + rows.map { |r| r[i] }).map { |s| convert.call(s).length }.max }
201
+ out = +"\n"
202
+ out << "#+ATTR_STREAMWEAVER: :markdown nil\n" unless table.markdown
203
+ out << row_line(headers.map { |h| convert.call(h) }, widths)
204
+ out << "|" + widths.map { |w| "-" * (w + 2) }.join("|") + "|\n"
205
+ rows.each { |r| out << row_line(r.map { |c| convert.call(c) }, widths) }
206
+ out
207
+ end
208
+
209
+ def row_line(cells, widths)
210
+ "| " + cells.each_with_index.map { |c, i| c.ljust(widths[i]) }.join(" | ") + " |\n"
211
+ end
212
+
213
+ def render_card(card)
214
+ header = card.children.find { |c| c.is_a?(Components::CardHeader) }
215
+ # A card with no CardHeader (loose children directly under card
216
+ # do...end -- a real pattern elsewhere in this codebase, e.g. admin
217
+ # dashboard stat tiles) has no natural title for the marker-line
218
+ # convention and isn't part of the doc-builder vocabulary this
219
+ # format targets. Fall back to the same raw-passthrough convention
220
+ # render_component's unrecognized-component branch uses, rather
221
+ # than crash or silently drop the content.
222
+ return raw_passthrough(card) unless header
223
+
224
+ body = card.children.find { |c| c.is_a?(Components::CardBody) }
225
+ marker = header.badge ? "*[#{header.badge}] #{header.content}*" : "*#{header.content}*"
226
+ marker << " /(#{header.meta})/" if header.meta
227
+ render_quote(marker, body ? body.children : [])
228
+ end
229
+
230
+ # Best-effort verbatim-source recovery for the raw-passthrough escape
231
+ # hatch: maps each top-level component to the literal DSL source text
232
+ # of the top-level statement that produced it, ONLY when that
233
+ # correspondence is unambiguous (exactly one top-level statement per
234
+ # top-level component, after filtering out known no-ops -- the common
235
+ # case for the flat DSL body this format targets). Falls back to an
236
+ # empty map (raw_passthrough's comment-only placeholder) rather than
237
+ # guessing when a doc uses top-level control flow (loops,
238
+ # conditionals producing zero-or-many components per statement) that
239
+ # breaks the 1:1 assumption -- silent-but-safe beats attributing the
240
+ # wrong source to a component.
241
+ def build_raw_sources(components)
242
+ statements = SourceSplitter.top_level_statements(@dsl_text)
243
+ return {} unless statements
244
+
245
+ statements = statements.reject { |s| s.strip.match?(NO_OP_STATEMENT_RE) }
246
+ return {} unless statements.length == components.length
247
+
248
+ components.each_with_index.to_h { |c, i| [c.object_id, statements[i]] }
249
+ rescue StandardError
250
+ {}
251
+ end
252
+
253
+ # Coverage only counts TOP-LEVEL statements (see #coverage) -- but the
254
+ # escape hatches are also reachable for a component nested inside an
255
+ # already-recognized container (e.g. an unrecognized type inside a
256
+ # callout/card/comparison body, via render_quote's recursive
257
+ # render_component calls below). Counting one of those would increment
258
+ # passthrough_verbatim/lossy/omitted with no matching #total increment
259
+ # (only sections_and_body's top-level loop increments #total), making
260
+ # recognized go negative. @nesting_depth (incremented/decremented
261
+ # around render_quote's recursive walk) tracks whether we're inside
262
+ # such a nested render, and every hatch counts through here.
263
+ def count(bucket)
264
+ @coverage_counts[bucket] += 1 if @nesting_depth.zero?
265
+ end
266
+
267
+ def raw_passthrough(component)
268
+ source = @raw_sources[component.object_id]
269
+ count(source ? :passthrough_verbatim : :passthrough_lossy)
270
+ content = source || "# unrecognized component: #{unrecognized_label(component)}"
271
+ "\n#+begin_src ruby :streamweaver-raw t\n#{content.rstrip}\n#+end_src\n"
272
+ end
273
+
274
+ # The placeholder names what the DOC said, not how this class modelled
275
+ # it: for a call RecordingContext had no component for, the struct's
276
+ # own class name ("...::UnsupportedCall") tells the reader nothing,
277
+ # while the call it stands in for (`app_shell`, `bar_chart`) is exactly
278
+ # what was lost.
279
+ def unrecognized_label(component)
280
+ return "#{component.name} (unsupported call)" if component.is_a?(RecordingContext::UnsupportedCall)
281
+
282
+ component.class
283
+ end
284
+
285
+ def render_comparison(comparison)
286
+ before = render_quote("*◀ Before — #{comparison.before_label}*", comparison.before_children)
287
+ after = render_quote("*▶ After — #{comparison.after_label}*", comparison.after_children)
288
+ "\n" + before.strip + "\n" + after.strip + "\n"
289
+ end
290
+
291
+ def render_quote(marker, children)
292
+ @nesting_depth += 1
293
+ body = children.map { |c| render_component(c) }.join.strip
294
+ "\n#+begin_quote\n#{marker}\n#{body}\n#+end_quote\n"
295
+ ensure
296
+ @nesting_depth -= 1
297
+ end
298
+ end
299
+ end
300
+ end