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,699 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StreamWeaver
4
+ module University
5
+ # Curriculum data: what `streamweaver get-started` teaches, and what's
6
+ # coming next. Plain data only -- no rendering, no ledger knowledge. The
7
+ # `prompt:` on each Getting Started step is exactly what
8
+ # `driver-worker-runner`'s Runner sends to a worker session, so it stays
9
+ # a plain string here rather than markup or DSL. `why_it_matters:` and
10
+ # `what_you_should_see:` are the step screen's other two content slots
11
+ # (design-spec section 2, "Step screen") -- a short motivation
12
+ # (rendered as two paragraphs via `md`) and the payoff checklist
13
+ # (rendered as a bulleted list), respectively.
14
+ #
15
+ # Rules every step's content obeys (Forrest, after the live UAT rounds
16
+ # of 2026-09-03 -- see features/university-getting-started.context.md,
17
+ # "Course content law"):
18
+ #
19
+ # 1. Never show what a TUI already does, and does faster. Each step has
20
+ # to earn its minute with a capability delta -- live arbitrary UI,
21
+ # charts, diagrams, a blocking visual decision, a growing styled doc,
22
+ # portability. "The agent could have just printed that" means the
23
+ # step failed.
24
+ # 2. CANNED ARTIFACTS, NARRATING AGENT (round 5). The worker never
25
+ # concocts demo DSL live and never reads a source checkout: every
26
+ # demo ships finished inside the gem and is reached through
27
+ # `streamweaver university-demo <name>`. Round 5 measured ~5 minutes
28
+ # to first paint with the agent composing; the fix is to run first
29
+ # and narrate after, so the explanation lands on something the user
30
+ # can already see.
31
+ # 3. Verifying and presenting are different jobs (VERIFY_RULE vs
32
+ # PRESENT_RULE). Round 5 curl-verified step 2 and then never showed
33
+ # it to the user at all.
34
+ # 4. Every prompt opens with previous-step cleanup as ONE backgrounded
35
+ # command that nothing waits on (`cleanup_line`), so housekeeping
36
+ # never sits between the user and the demo. The demo sessions are
37
+ # `dashboard` (step 1), `decision` (step 3) and `doc-demo` (step 4);
38
+ # `university` is the controller canvas the user drives the course
39
+ # from and is never closed.
40
+ module Course
41
+ # Mined from the two real worker sessions
42
+ # (docs/university/worker-session-mining.md): both reached for `curl`
43
+ # and the app's own action log before any browser, and one burned a
44
+ # tool call plus a visible reasoning detour reflexively reaching for a
45
+ # browser MCP its own config forbids. Same line on every step so
46
+ # neither is rediscovered per step. Interpolated into each prompt
47
+ # rather than repeated, so the five can never drift.
48
+ VERIFY_RULE = "VERIFY (for you, silently): prove it with `curl` and the app's own " \
49
+ "logs -- a POST to `.../action/...` returning 200, plus the expected " \
50
+ "text in the HTML that comes back, is proof the DSL block re-ran " \
51
+ "server-side. Verification is yours alone: it opens nothing and shows " \
52
+ "me nothing. Use a headless/automation browser only if this session is " \
53
+ "already configured with one, and never fetch or install a new browser " \
54
+ "tool mid-course."
55
+
56
+ # Round-5 UAT's biggest content failure was not a bug: step 2 was
57
+ # verified with curl and then never shown to the user at all, and step
58
+ # 3 made the user find and open a page themselves. Verifying and
59
+ # presenting are two different jobs with two different tools, so they
60
+ # are now two different rules and every prompt carries both.
61
+ #
62
+ # The second mining pass (docs/university/worker-session-mining.md,
63
+ # "Round-5 latency + portability pass") added the portability half: a
64
+ # real session shelled a personal skill path to show the user a page,
65
+ # which would not exist on a coworker's machine, and baked a
66
+ # dynamically-discovered bridge port into commands it printed.
67
+ PRESENT_RULE = "PRESENT (for me): when something is meant for my eyes, open it in MY " \
68
+ "own default browser -- `open <url>` on macOS, `xdg-open <url>` on " \
69
+ "Linux -- or let `streamweaver panel` open the pane itself. Never use " \
70
+ "browser automation to show me something; that renders into your " \
71
+ "session, not mine. Never set SW_NO_OPEN on a run I am meant to " \
72
+ "interact with. And never hand me a command with a discovered port " \
73
+ "baked into it -- ports move between runs and machines; give me the " \
74
+ "URL that was printed, or a command that resolves the port itself."
75
+
76
+ # One backgrounded command, at the very start, that nothing waits on.
77
+ # Round-5 UAT measured ~5 minutes to first paint; serialized
78
+ # housekeeping in front of the demo was part of it, and none of it is
79
+ # anything the user came to watch.
80
+ def self.cleanup_line(*sessions)
81
+ closes = sessions.map { |s| "streamweaver canvas-close #{s}" }.join('; ')
82
+ "Housekeeping, backgrounded, first line, waited on by nothing: " \
83
+ "`( #{closes} ) >/dev/null 2>&1 &`. Kill any background app task from an earlier " \
84
+ "step the same way. Never close `university` -- that is the controller canvas I am " \
85
+ "driving this course from. Then go straight to the demo; do not report on the cleanup."
86
+ end
87
+
88
+ # Referenced by `closing_ritual` below while GETTING_STARTED_STEPS
89
+ # itself is still being built (its own step hashes call
90
+ # `closing_ritual`, so the array constant does not exist yet at that
91
+ # point) -- mirrors why VERIFY_RULE/PRESENT_RULE/cleanup_line are also
92
+ # plain methods/constants rather than reaching into the array.
93
+ TOTAL_STEPS = 5
94
+
95
+ # Round-8 UAT: even with the port-discovery advice llms.txt already
96
+ # carries, a worker still spent real time hunting for the port a
97
+ # plain launch picked, then booted a SECOND instance trying to "find"
98
+ # it. Step 2 now pins one port instead of discovering anything --
99
+ # env-overridable so a machine with 4570 already taken doesn't block
100
+ # the course. Falls back to the default rather than raising on a
101
+ # malformed override -- this loads on every `streamweaver` command
102
+ # (cli.rb requires the university listener, which requires this
103
+ # file), so a typo'd env var must not break the whole CLI.
104
+ STEP2_PORT = begin
105
+ Integer(ENV.fetch('SW_UNIVERSITY_APP_PORT', 4570))
106
+ rescue ArgumentError
107
+ 4570
108
+ end
109
+
110
+ # Round-6 UAT: with no fixed sign-off, a worker either kept talking
111
+ # past a finished demo or went quiet with the user unsure the step was
112
+ # actually done. Every prompt now ends on the exact same shape, so
113
+ # "done" always looks the same.
114
+ #
115
+ # Round-8 UAT: the sign-off used to hand a "click Mark done yourself"
116
+ # click back to the user -- real minutes of ambiguity about whether a
117
+ # step had actually finished. The worker closes the loop itself: run
118
+ # `streamweaver university-done N` (same ledger write as the button,
119
+ # plus it brings the University window forward on its own), THEN tell
120
+ # the user in one line what just happened. The manual Mark-done button
121
+ # still works exactly as before; this just means the worker never has
122
+ # to ask for a click. Step 5 has no next step to advance to, so its
123
+ # own variant below points at the course recap instead of "step N+1".
124
+ #
125
+ # Round-9 UAT: that hand-off used to fire the INSTANT the demo ended --
126
+ # too abrupt, with the user still reading the worker's closing words
127
+ # when the window jumped forward under them. The ritual now has an
128
+ # explicit middle step: print the closing words, then WAIT for the
129
+ # user's own typed "done" (or the same in other words), and only THEN
130
+ # run university-done.
131
+ #
132
+ # `done_signal:` is the one per-step exception to that generic wait --
133
+ # a step whose own canvas form already has a "done" option (step 4's
134
+ # picker "done, move on" choice) can already be holding that signal by
135
+ # the time it reaches this ritual, and must not ask the user to type
136
+ # "done" a second time. Kept as a parameter, not baked into the
137
+ # shared text, because only ONE of the five steps has such a form:
138
+ # code review caught the first pass naming step 4's picker inside
139
+ # every step's own wait paragraph, including steps that have no
140
+ # picker to be talking about.
141
+ def self.closing_ritual(number, done_signal: nil)
142
+ intro = "✅ Step #{number} demo complete -- play with it as long as you like."
143
+ generic_wait = "Do not run the next line yet -- WAIT here for my explicit signal that " \
144
+ "I am done: me typing \"done\" (or the same in other words) here in " \
145
+ "this Claude session. No signal yet means no next line: keep waiting, " \
146
+ "and keep answering whatever I ask about this step in the meantime."
147
+ wait_for_signal = done_signal ? "#{generic_wait} #{done_signal}" : generic_wait
148
+ run_it = "The MOMENT that signal lands, run `streamweaver university-done #{number}` " \
149
+ "yourself -- it marks step #{number} done (same as clicking Mark done) and " \
150
+ "brings the University window forward so I see it, with no click required " \
151
+ "from me."
152
+ report = if number >= TOTAL_STEPS
153
+ "Tell me: \"I've marked step #{number} done and brought University forward -- " \
154
+ "that's the whole course; take a look at the recap.\""
155
+ else
156
+ "Tell me: \"I've marked step #{number} done and brought University forward -- " \
157
+ "click Run on step #{number + 1} when ready.\""
158
+ end
159
+ "#{intro}\n\n#{wait_for_signal}\n\n#{run_it}\n\n#{report}"
160
+ end
161
+
162
+ # Step 4's own exception to the generic wait-for-"done" text above --
163
+ # its picker already has a "done, move on" choice, so submitting that
164
+ # already IS the signal; asking the worker to also wait for a typed
165
+ # "done" here would be a second ask for the one thing already in hand.
166
+ STEP4_DONE_SIGNAL = "But on THIS step, my picking \"done, move on\" on the doc's own " \
167
+ "picker form already IS that signal -- if that is how you got here, " \
168
+ "the wait is already over, so continue immediately without also " \
169
+ "waiting for me to type \"done\" here."
170
+
171
+ GETTING_STARTED_STEPS = [
172
+ {
173
+ number: 1,
174
+ title: "A dashboard in your pane",
175
+ payoff: "Stat tiles, a chart and a diagram appear beside your terminal -- then change while you watch.",
176
+ why_it_matters: <<~WHY.strip,
177
+ A terminal can print a table faster than any browser can draw one. What it
178
+ cannot do is show you a row of KPI tiles, a chart and a diagram at the same
179
+ time -- and then update all three in place while you keep typing.
180
+
181
+ That is the whole pitch in sixty seconds. Your agent pushes arbitrary UI into
182
+ a pane it controls, and re-pushes as the numbers move. No HTML, no server to
183
+ stand up by hand, no page reload.
184
+ WHY
185
+ prompt: <<~PROMPT.strip,
186
+ #{cleanup_line('dashboard', 'decision', 'doc-demo')}
187
+
188
+ Then run these two commands immediately, before you explain anything. Getting
189
+ something on my screen inside the first minute is the point of this step; the
190
+ explanation is worth more once I can see what you are explaining.
191
+
192
+ streamweaver panel dashboard
193
+ ruby "$(streamweaver university-demo dashboard)" dashboard
194
+
195
+ The demo is a finished file that ships inside the stream_weaver gem. Do not
196
+ write your own, and do not go looking for a source checkout -- there may not
197
+ be one on this machine.
198
+
199
+ NOW narrate what is on my screen: KPI tiles, a Chart.js bar chart and a
200
+ rendered mermaid sequence diagram, all in one push. Point out that the diagram
201
+ is a picture of the command you just ran -- you, the CLI, the bridge, this
202
+ pane. Then read the demo file (`streamweaver university-demo dashboard` prints
203
+ its path) and show me the eight or so lines of DSL that produced all three.
204
+
205
+ Then mutate it while I watch, a few seconds apart, saying what changes before
206
+ each one:
207
+
208
+ ruby "$(streamweaver university-demo dashboard)" dashboard 2
209
+ ruby "$(streamweaver university-demo dashboard)" dashboard 3
210
+
211
+ Same layout, new numbers, in place -- no reload, no flicker, no page I had to
212
+ open.
213
+
214
+ That third push was the last one for this step -- say so plainly: "That was
215
+ the final push -- the dashboard is done." Then stop moving the pane for a
216
+ moment and invite me to actually look at it: the mermaid diagram is not
217
+ decoration, it is the mechanism -- it names you, the CLI, the bridge and this
218
+ pane, the exact chain that just drew everything else on screen.
219
+
220
+ #{VERIFY_RULE}
221
+
222
+ #{PRESENT_RULE}
223
+
224
+ #{closing_ritual(1)}
225
+ PROMPT
226
+ what_you_should_see: [
227
+ "Something is on your screen within seconds -- your agent ran the demo first and explained it after, instead of composing one while you waited.",
228
+ "One push, and the pane holds KPI tiles, a chart and a diagram at once -- three things a terminal cannot draw.",
229
+ "The mermaid diagram describes the canvas-push that put it there: agent, CLI, bridge, pane.",
230
+ "Two more pushes a few seconds later change the numbers in place -- no reload, no flicker.",
231
+ "The demo came out of the installed gem, not out of this repo -- your agent ran `streamweaver university-demo dashboard` and never went looking for a checkout."
232
+ ]
233
+ },
234
+ {
235
+ number: 2,
236
+ title: "Six lines of Ruby",
237
+ payoff: "Run a real app and watch the block re-run on every click.",
238
+ why_it_matters: <<~WHY.strip,
239
+ StreamWeaver's whole trick is one idea: your DSL block re-executes on
240
+ every interaction. There is no event-handler code to write -- state
241
+ changes, the block runs again, the page reflects it.
242
+
243
+ Once that model clicks, everything else in StreamWeaver -- forms,
244
+ canvas, the doc theme -- is just this same loop wearing different clothes.
245
+ WHY
246
+ prompt: <<~PROMPT.strip,
247
+ #{cleanup_line('dashboard')}
248
+
249
+ Then start the app immediately, as a BACKGROUND task, before explaining
250
+ anything, on a PINNED port -- no discovery, no guessing:
251
+
252
+ PORT=#{STEP2_PORT} ruby "$(streamweaver university-demo counter)"
253
+
254
+ That is a finished eight-line file inside the stream_weaver gem. Do not write
255
+ your own version and do not go looking for a source checkout. NEVER boot a
256
+ second instance of it for any reason -- if port #{STEP2_PORT} is already bound,
257
+ it is always a stale course app left over from an earlier run, never something
258
+ else worth investigating: find and kill it first (`lsof -i :#{STEP2_PORT}
259
+ -sTCP:LISTEN`), then start this one. StreamWeaver's own port-scan means
260
+ launching a second instance on a busy port just starts an orphaned duplicate on
261
+ the NEXT free port -- a terrible first impression that tells you nothing about
262
+ the one already running.
263
+
264
+ Setting PORT suppresses the app's own auto-open, by design -- so confirm it is
265
+ up (curl it, or check your captured stdout for the app booting), then open it
266
+ for me yourself:
267
+
268
+ open http://127.0.0.1:#{STEP2_PORT}
269
+
270
+ Say out loud, as part of handing it over, exactly what the app's own callout
271
+ already tells me on screen: when I'm done playing, I come back to this Claude
272
+ session and say "done" -- that is the signal the closing ritual below is waiting
273
+ for, so say it now, not only leave it for me to notice in the callout.
274
+
275
+ NOW narrate. Print the file -- nine lines now, `streamweaver university-demo
276
+ counter` prints its path -- and walk me through it:
277
+
278
+ - The mechanism itself is still exactly six lines, in an eight-line file: four
279
+ statements wrapped by `app "Counter" do` and `end.run!`. The two hidden
280
+ bookends are `require 'stream_weaver'` on line 1, and `.run!` chained onto
281
+ the block's closing `end`. Without the first you get `NoMethodError:
282
+ undefined method 'app'`; without the second the process builds the app,
283
+ starts no server, and exits silently. Both are in the file, on purpose.
284
+ - The ninth line is new, and it is not part of the mechanism: a `callout` at
285
+ the top of the block that introduces the app to whoever it opens for. Point
286
+ at it on my screen and read it to me instead of paraphrasing it -- I may tab
287
+ over here before your narration reaches me.
288
+ - There is no event handler anywhere in it. Tell me to click `+1` a few times,
289
+ and say what actually happens: the whole block re-executes, `state[:count]`
290
+ is one higher when it does, so the `text` line renders a different number.
291
+ That is the entire mechanism, and everything else in StreamWeaver is this
292
+ loop wearing different clothes.
293
+
294
+ Say plainly that the app is running as a background task and that you will
295
+ kill it. When I say I have seen it, kill it and confirm it is gone -- never
296
+ leave a course demo server listening.
297
+
298
+ For any DSL syntax question, run `streamweaver llm` FIRST -- one command, the
299
+ canonical reference, and its counter example is exactly this app. Do not
300
+ dispatch a search agent to grep a repo for it.
301
+
302
+ #{VERIFY_RULE}
303
+
304
+ #{PRESENT_RULE}
305
+
306
+ #{closing_ritual(2)}
307
+ PROMPT
308
+ what_you_should_see: [
309
+ "The app comes up on a pinned port -- no port to guess, no second instance booted to \"find\" one -- and your agent opens it for you within seconds instead of quietly verifying it with curl and moving on without showing you.",
310
+ "Each click updates the count immediately, with no page reload or spinner.",
311
+ "The Ruby block ran again on every click; nothing else touched the page.",
312
+ "Your agent printed all nine lines of the file it actually ran, and named `require 'stream_weaver'` and `end.run!` as the two the \"six-line app\" framing hides -- the two that cost real sessions a debug cycle each.",
313
+ "A callout at the top of the app explains itself the moment it opens -- what it is and what to click -- in case you tab over here before the narration reaches you.",
314
+ "It said plainly that the app runs as a background task, offered to kill it, and did kill it at the end -- no orphaned server left behind."
315
+ ]
316
+ },
317
+ {
318
+ number: 3,
319
+ title: "Claude asks you a real question",
320
+ payoff: "Your agent stops, shows you two architectures, and blocks until you decide.",
321
+ why_it_matters: <<~WHY.strip,
322
+ An agent that needs a decision from you has exactly one move in a terminal:
323
+ print the question and wait for text. It cannot show you the two designs it
324
+ is choosing between, lay them side by side, and let you pick one.
325
+
326
+ Here it can. The same form has two modes -- blocking, where your agent's
327
+ terminal freezes until you submit, and non-blocking, where the form stays
328
+ live in the pane and the agent reads your answer whenever it needs it. This
329
+ is the step where the canvas stops being a display and becomes an input.
330
+ WHY
331
+ prompt: <<~PROMPT.strip,
332
+ #{cleanup_line('dashboard', 'decision')}
333
+
334
+ PART ONE -- the founding loop. Run this now, as a BACKGROUND task, before
335
+ explaining anything:
336
+
337
+ ruby "$(streamweaver university-demo decision-form)"
338
+
339
+ That is a finished file inside the stream_weaver gem: a mermaid diagram of two
340
+ candidate architectures, a comparison table, a `radio_group` and a rationale
341
+ `text_field`. Do not write your own and do not look for a source checkout.
342
+
343
+ It runs through `run_once!`, which means it finds a free port, OPENS my browser
344
+ itself, and BLOCKS until I submit. So: do not ask me to open anything, do not
345
+ print a URL for me to click, and do not ask me to tell you when I am done. The
346
+ page is already in front of me. Say one line -- "answer that, I am waiting" --
347
+ and then wait for the process to exit.
348
+
349
+ The INSTANT that process exits -- before you read its output, before you print
350
+ anything -- run `streamweaver focus-me`. It brings your own terminal tab back in
351
+ front of me (a fire-and-forget iTerm2 activate, silent everywhere else), and it
352
+ has to be the very first thing you do, because my attention is still on the
353
+ browser tab that form just closed; run it after your reaction and I would already
354
+ be looking at the reaction I never saw land.
355
+
356
+ Only THEN print my submitted state as JSON on stdout, verbatim, and REACT to it:
357
+ name the option I picked, quote the rationale I typed, and say what you would
358
+ actually do differently because of it. That round trip -- your process blocked
359
+ on a human, and resumed with their answer in hand -- is the whole step.
360
+
361
+ PART TWO -- the same file, a second surface. Say that plainly, because it is
362
+ the lesson: not a second form, the same one.
363
+
364
+ streamweaver panel decision
365
+ ruby "$(streamweaver university-demo decision-form)" canvas decision
366
+
367
+ Same context, same `radio_group`, same `text_field` -- now on the canvas,
368
+ with a callout saying you are frozen, and an explicit submit button.
369
+
370
+ Then immediately: `streamweaver canvas-raise decision`. PART ONE's blocking
371
+ form pulled my attention to its own browser tab, and a push you make mid-
372
+ response -- unlike a Run submit -- raises nothing on its own; without this
373
+ I would never see the canvas surface unprompted. This is PRESENT-mode
374
+ discipline, same as `open`/`streamweaver panel` elsewhere in this course:
375
+ for my eyes, never automation.
376
+
377
+ Then wait on it -- and how you wait is itself worth saying out loud. Run
378
+ `streamweaver canvas-wait decision` AS A BACKGROUND TASK from the start, not
379
+ in the foreground. A foreground wait on a human will blow past your harness's
380
+ foreground-block ceiling, get demoted to a background task anyway, and cost
381
+ minutes in polling lag on the way back. A blocking wait on a human is a
382
+ background job with a completion notification, and that is how an agent should
383
+ wait on a person. The moment the notification lands, read the result and react
384
+ to it immediately -- do not sit in a poll loop.
385
+
386
+ Close on one beat: the same form left live with no `canvas-wait` behind it
387
+ keeps its state in the pane, and you can read my answer back whenever you need
388
+ it. Blocking is a choice, not a limitation.
389
+
390
+ Once you have said that, the teaching for this step is DONE -- both surfaces
391
+ are demonstrated, full stop. Declare it plainly and move straight to the
392
+ closing ritual below; do not keep role-playing with the form as though there
393
+ is more to teach. If I want to keep clicking around in it afterward, that is
394
+ fine -- offer it as a pure aside, optional, in words like "the form still
395
+ works -- keep playing if you like; the course has moved on", never folded in
396
+ as more of the lesson.
397
+
398
+ #{VERIFY_RULE}
399
+
400
+ #{PRESENT_RULE}
401
+
402
+ #{closing_ritual(3)}
403
+ PROMPT
404
+ what_you_should_see: [
405
+ "The browser opened by itself with a real question in it -- you never opened a page, never copied a URL, and never had to tell your agent you were done.",
406
+ "The form carries a rendered diagram of both designs and a comparison table right next to the choice; a terminal prompt can carry neither.",
407
+ "The moment you submitted, your agent printed the JSON verbatim and reacted to it -- naming your choice and quoting the rationale you typed.",
408
+ "Then the same file, unchanged, appeared on the canvas -- your agent said so out loud: one artifact, two surfaces.",
409
+ "It waited on the canvas form as a background task and said why: a blocking wait on a human is a background job with a notification, not a frozen foreground prompt.",
410
+ "The closing beat: the same form left live without `canvas-wait` becomes state the agent can query later."
411
+ ]
412
+ },
413
+ {
414
+ number: 4,
415
+ title: "A doc that writes itself",
416
+ payoff: "Watch a script write a document, save it, then ask you what else it should say.",
417
+ why_it_matters: <<~WHY.strip,
418
+ A canvas isn't limited to one static push -- a script can keep adding to the
419
+ same session over time, and the pane updates each time without you touching
420
+ anything. What grows is a real document: an outline, a two-column section,
421
+ syntax-highlighted code, a callout, a rendered diagram, a table.
422
+
423
+ Then the interesting half. The script saves the doc itself and tells you where
424
+ it went -- and your agent asks what else it should say, blocks on your answer,
425
+ adds it, and saves again. That loop is the point: co-editing a document with an
426
+ agent, in a pane, with no file open on either side.
427
+ WHY
428
+ prompt: <<~PROMPT.strip,
429
+ #{cleanup_line('decision')}
430
+
431
+ Then start the doc growing immediately, before explaining anything:
432
+
433
+ streamweaver panel doc-demo --theme=doc
434
+ ruby "$(streamweaver university-demo doc)" doc-demo
435
+
436
+ Narrate it WHILE it runs -- it takes about twenty seconds and the pacing is
437
+ the point. Each push carries everything before it plus one more section, so
438
+ the page grows instead of blinking: a doc header, a two-column comparison, a
439
+ syntax-highlighted code block and a callout, a mermaid diagram, a table of
440
+ what survives an export, and a closing section. Six outline entries, which is
441
+ what makes the sidebar nav in step 5 worth looking at.
442
+
443
+ The script narrates its own progress to stdout so you don't have to guess when
444
+ a push landed: a `stage N/7 pushing: <name>` line right before each one, and a
445
+ `stage N/7 pushed: <name>` line right after. Watch that stream and say
446
+ something to me the moment each stage lands -- do not wait for the whole run
447
+ to finish and then summarize it in one breath at the end. That is what "while
448
+ it runs" means here.
449
+
450
+ The script saves the document itself, through the same `save-doc` endpoint the
451
+ floating button calls, under the deterministic name `university-doc`, and
452
+ prints the exact path it landed at. Read that path back to me verbatim -- step
453
+ 5 uses that file, and you are not to make me transcribe anything.
454
+
455
+ THEN, and only then, invite me to do it by hand: there is a floating "Save as
456
+ doc" button at the bottom right of the pane. Tell me to click it, and explain
457
+ the dialog before I do -- typing a name and pressing Save writes a permanent,
458
+ git-tracked `docs/streamweaver_canvas/<name>.rb`; "Save as Org" writes the
459
+ same content as a plain-text `.org` sibling. Say what the two formats are for,
460
+ not just what they're named: `.rb` is full fidelity -- StreamWeaver can
461
+ re-render and extend it again later, exactly as it looked here. `.org` is the
462
+ portable half -- plain text, human-readable anywhere with nothing to install,
463
+ and the StreamWeaver Doc Viewer extension (step 5) makes that same file
464
+ beautiful again without needing StreamWeaver at all. Mine is a bonus lap. Step
465
+ 5 uses yours.
466
+
467
+ THE TWEAK LOOP. Now tell me the doc is mine to change for as long as I like,
468
+ and offer to extend it -- and say what you are doing as you do it: "I am using
469
+ the same blocking form from step 3 to ask you a real question. This is how an
470
+ agent and a human co-edit a document."
471
+
472
+ ruby "$(streamweaver university-demo doc)" doc-demo --picker
473
+
474
+ That appends a picker to the bottom of the doc: a `radio_group` of sections
475
+ not yet in it (the visible choice IS the exact `--extend` key -- read a
476
+ legend line above the choices for what each one means), a free-text field,
477
+ and a "done, move on" option. Wait on it with `streamweaver canvas-wait
478
+ doc-demo` AS A BACKGROUND TASK -- same rule as step 3, never a foreground
479
+ block on a human -- and react the moment the notification lands.
480
+
481
+ If I pick a canned section, add it and re-save in one command, using the key
482
+ exactly as it appeared on the radio choice -- do not paraphrase or shorten it:
483
+
484
+ ruby "$(streamweaver university-demo doc)" doc-demo --extend=<key>
485
+
486
+ (Keys accumulate: `--extend=timeline,cheatsheet` for two -- and so do repeat
487
+ invocations, on their own: the command remembers every key you have already
488
+ applied to this session, so a later `--picker` round never loses one.) The
489
+ command tells you plainly -- one line per key, `OK <key> → section '<exact
490
+ rendered header>'` for a key it recognized, `FAILED` for one it did not -- and
491
+ it exits non-zero if anything failed. Every rendered header BEGINS with its
492
+ own key, capitalized, so that OK line is something you can act on directly.
493
+ Still VERIFY before telling me anything landed: curl the `doc-demo` session
494
+ (or read the command's own OK/FAILED output) and grep for that exact header
495
+ text. Only then say it is there -- a push that silently no-op'd once looked
496
+ identical to a successful one.
497
+
498
+ If I typed a description instead, IMMEDIATELY -- before you write a word of
499
+ the real section -- push a placeholder to the `doc-demo` session so the pane
500
+ never just sits there while you work: a bare `callout` reading "⏳ Building
501
+ your <my request, verbatim> -- hold on" is enough. THEN write the section
502
+ itself, and keep it to `doc_section_header`, `md`, `table headers:/rows:`,
503
+ `comparison`, `code_block`, `callout` and `mermaid`. Those are exactly the
504
+ components `streamweaver org-export` recognizes; anything else looks right in
505
+ the pane and then leaves as an unrecognized placeholder, silently, which is
506
+ the failure step 5 exists to disprove.
507
+
508
+ Before you push that section anywhere, VALIDATE it locally by actually
509
+ rendering it -- a syntax check is not enough. A live run once hit "wrong number
510
+ of arguments" from a component call that was perfectly valid Ruby and only
511
+ failed at render time, and I sat looking at that bare error for minutes with no
512
+ sign anything was happening:
513
+
514
+ ruby -e "require 'stream_weaver'; StreamWeaver::CLI.render_dsl_to_html(File.read(ARGV[0]))" /path/to/your/snippet.rb
515
+
516
+ If that errors, do not leave the placeholder sitting there unexplained --
517
+ immediately push an updated callout to `doc-demo`: "⚠️ Hit an error building
518
+ <my request, verbatim> -- fixing it now", then fix the section and re-validate.
519
+ Once your `--add-custom` push below lands, the real section replaces whatever
520
+ placeholder or error callout was showing -- that IS the success update, nothing
521
+ further to push.
522
+
523
+ A hand-written section is not a canned `--extend` key, so on its own it has
524
+ nowhere to persist -- the NEXT `--picker` round would clobber it right back
525
+ out. (A live run lost a user's own Star Wars chart section exactly this way.)
526
+ Save your finished, validated DSL to a scratch file and hand it to the script
527
+ instead of pushing it yourself:
528
+
529
+ ruby "$(streamweaver university-demo doc)" doc-demo --add-custom <a-short-key> <path-to-your-snippet-file>
530
+
531
+ That persists the section alongside everything else already in the doc, so
532
+ every later rebuild includes it automatically, and pushes + re-saves the
533
+ finished document in one command -- do not push a hand-written section any
534
+ other way, or the persistence never happens.
535
+
536
+ Then push the picker again and loop, until I choose "done".
537
+
538
+ The MOMENT I choose "done", before saying anything else, run:
539
+
540
+ ruby "$(streamweaver university-demo doc)" doc-demo --finish
541
+
542
+ That re-pushes the finished document -- base plus every section you added,
543
+ with NO picker form this time -- and saves it. Skipping this leaves the pane
544
+ showing whatever the canvas-wait adapter puts up the instant a form is
545
+ submitted (a plain "Submitted -- you can close this window" screen, not the
546
+ document) -- nothing else re-pushes on its own once I pick "done", so this
547
+ command is the only thing that puts the doc back on screen. Then say the saved
548
+ path once more at the end.
549
+
550
+ #{VERIFY_RULE}
551
+
552
+ #{PRESENT_RULE}
553
+
554
+ #{closing_ritual(4, done_signal: STEP4_DONE_SIGNAL)}
555
+ PROMPT
556
+ what_you_should_see: [
557
+ "The pane grows a new section every few seconds -- you watch it happen, you don't just see the end state.",
558
+ "What grows is a document, not three cards: a header and outline, a two-column section, a code block, a callout, a rendered diagram and a table.",
559
+ "The outline on the left fills in as each section arrives, and ends up long enough to be worth navigating.",
560
+ "Your agent saved the doc itself and read you the exact path -- no dialog to fill in, nothing to transcribe. The floating \"Save as doc\" button is then offered to you as a bonus lap, with the dialog explained before you click.",
561
+ "Then it asks what else the doc should say, using the same blocking form from step 3, and says so -- adds your pick, re-pushes, re-saves, and asks again until you say you're done. That is co-editing a document with an agent, with no file open on either side."
562
+ ]
563
+ },
564
+ {
565
+ number: 5,
566
+ title: "Take the doc with you",
567
+ payoff: "Export to org, drop it in a gist, read it anywhere.",
568
+ why_it_matters: <<~WHY.strip,
569
+ A canvas doc only exists while StreamWeaver is running -- until you
570
+ export it. `.org` is a plain-text format GitHub already renders
571
+ reasonably, and a gist is the fastest way to hand it to someone who
572
+ has never heard of StreamWeaver.
573
+
574
+ The last step is seeing the difference for yourself: plain GitHub
575
+ rendering versus the same file through the StreamWeaver Doc Viewer
576
+ extension.
577
+ WHY
578
+ prompt: <<~PROMPT.strip,
579
+ Before anything else, run `gh auth status`. If the `gh` CLI is missing, not
580
+ logged in, or logged in without the `gist` scope, say exactly which of those
581
+ it is, and hand it back friendly and concrete: `brew install gh`, then `gh auth
582
+ login`, or `gh auth refresh -s gist` if it is only the scope that is missing.
583
+ Then stop and wait for me. Do not fake the gist half of this step, and do not
584
+ press on hoping it works.
585
+
586
+ Then, with the doc still open in the `doc-demo` pane, state the plan before you
587
+ point at anything -- one breath, up front: "Look at two things -- the outline
588
+ nav and the mermaid popout. Say 'go' when you're done and I'll export it and
589
+ push it to a gist." That is the whole CTA, said FIRST, not stitched on after
590
+ you've already started pointing things out -- so I always know exactly what
591
+ you are waiting on me for and that nothing is running yet.
592
+
593
+ Before you point at anything, VERIFY the pane actually shows the document:
594
+ `curl` the `doc-demo` session and grep for a heading you know is in it (e.g.
595
+ "Terminal vs canvas", the first section's title). If it is missing -- the
596
+ picker's own submitted-form screen is showing instead, or anything else that
597
+ isn't the doc -- restore it first: `ruby "$(streamweaver university-demo
598
+ doc)" doc-demo --finish` re-pushes the finished document with no picker. Only
599
+ once the heading actually greps do you move on. Never narrate UI I cannot see
600
+ on my screen.
601
+
602
+ THEN point at the two things themselves, the ones I would otherwise walk past:
603
+
604
+ - The outline. Six-plus sections is what earns a sidebar; in a pane this
605
+ narrow the doc theme moves that nav to the top instead of the side, and it
606
+ is the same nav either way. Scroll it, or click an entry, so I see it work.
607
+ - The mermaid diagram's popout control -- a rendered diagram in a 750px pane
608
+ is a thumbnail until you open it full size.
609
+
610
+ Wait for me to say "go" (or the same in other words). Only then take it with
611
+ you. Step 4's script saved the doc under the deterministic
612
+ name `university-doc` and printed the path; use that path. If you no longer
613
+ have it, look in `docs/streamweaver_canvas/` AND `~/.streamweaver/canvas/`
614
+ (the bridge's working directory is often not this shell's) and tell me where
615
+ it actually is -- but do not make me name it.
616
+
617
+ Before you touch org-export or gh, push a small status so the doc-demo pane
618
+ says something is happening instead of just sitting there while gist creation
619
+ runs: `streamweaver canvas-toast doc-demo "⏳ pushing to gist..." --variant info`.
620
+
621
+ Run `streamweaver org-export <that file>` to write the sibling `.org`, and show
622
+ me the first few lines so I can see it is plain text with no server behind it.
623
+ Then `gh gist create --public <that .org file>` -- public on purpose, because
624
+ this step ends with the file being opened and compared in a browser -- and give
625
+ me the gist URL. Open it in MY browser, not yours. The moment you have that URL,
626
+ replace the status: `streamweaver canvas-toast doc-demo "✅ Pushed: <gist URL>"
627
+ --variant success` -- the pending toast never lingers once the real work is
628
+ actually done. If either command fails instead, replace it the same way with
629
+ what actually happened: `streamweaver canvas-toast doc-demo "⚠️ <what failed> --
630
+ fixing it now" --variant warning`, same honesty rule as step 4's error card --
631
+ never leave "pushing to gist..." sitting there past the point where it stopped
632
+ being true.
633
+
634
+ The last beat is NOT yours. Stop and hand it to me, and say why in plain words:
635
+ no automated or headless browser can install a Chrome Web Store extension or
636
+ see my logged-in Chrome, so this part is structurally out of your reach no
637
+ matter which browser tool you have. Do not attempt it and do not skip it
638
+ silently. Give me the StreamWeaver Doc Viewer link from this project's README,
639
+ tell me to install it, reload the gist and click "View rendered", and then wait
640
+ for me to tell you what I saw -- the same file, now carrying the outline, the
641
+ two-column section, the code block, the callout and the diagram it had in the
642
+ canvas.
643
+
644
+ Finish by cleaning up after the whole course, backgrounded and in one line:
645
+ `( streamweaver canvas-close dashboard; streamweaver canvas-close decision;
646
+ streamweaver canvas-close doc-demo ) >/dev/null 2>&1 &`. Kill any background
647
+ task still running. Then `streamweaver canvas-list` and confirm to me that
648
+ only `university` -- the controller -- is left.
649
+
650
+ #{VERIFY_RULE}
651
+
652
+ #{PRESENT_RULE}
653
+
654
+ #{closing_ritual(5)}
655
+ PROMPT
656
+ what_you_should_see: [
657
+ "Your agent checked `gh auth status` first and told you up front if gh was missing, logged out, or short the gist scope -- with the exact command to fix it -- instead of failing halfway through.",
658
+ "It pointed out the doc's own navigation before exporting it: the outline the six sections earned, moved to the top in a narrow pane, and the mermaid diagram's popout.",
659
+ "It used the doc step 4 saved for you, at the path step 4 printed -- you never had to name a file.",
660
+ "`org-export` writes a .org file next to the doc -- no network access, no server -- and `gh gist create --public` prints a gist URL that opens in your own browser.",
661
+ "Then it stops and hands the extension step back to you, saying why: no automated or headless browser can install a Web Store extension or reach your logged-in Chrome. It gives you the link and waits -- it does not try, and it does not quietly skip.",
662
+ "Plain GitHub already reads the .org file close to markdown; once you install it, the extension's \"View rendered\" button brings back the outline, two-column section, code block, callout and diagram exactly as they looked in the canvas.",
663
+ "Every demo session from steps 1-4 is closed at the end -- `streamweaver canvas-list` shows only `university`, the controller you have been driving from."
664
+ ]
665
+ }
666
+ ].freeze
667
+
668
+ def self.step(number)
669
+ GETTING_STARTED_STEPS.find { |s| s[:number] == number.to_i }
670
+ end
671
+
672
+ # What the runner sends to the worker session, and what the canvas
673
+ # offers for copying when it can't send. nil for a step number the
674
+ # course doesn't have (the runner reports that as :unknown_step);
675
+ # `fetch` rather than `dig` so a curriculum entry written without a
676
+ # `prompt:` raises for its author instead of masquerading as one.
677
+ def self.prompt_for(number)
678
+ step(number)&.fetch(:prompt)
679
+ end
680
+
681
+ # Rendered dormant on the course-list shelf, no controls -- names and
682
+ # blurbs only, per docs/university/design-spec.md section 2.
683
+ FUTURE_COURSES = [
684
+ {
685
+ name: "Docs deep dive",
686
+ blurb: "Org export, gists, and the reader extension, end to end."
687
+ },
688
+ {
689
+ name: "Canvas modes",
690
+ blurb: "Stateful, blocking, and streaming — when to reach for each."
691
+ },
692
+ {
693
+ name: "Skills and panels",
694
+ blurb: "Teach your agent to drive the canvas without you."
695
+ }
696
+ ].freeze
697
+ end
698
+ end
699
+ end