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,741 @@
1
+ # streamweaver-doc: v1
2
+ #
3
+ # StreamWeaver University — course canvas mockup (story: course-canvas-design)
4
+ #
5
+ # Two screens of the app `streamweaver get-started` lands on:
6
+ # 1. Course list — a shelf of course panels. Getting Started is open and holds
7
+ # its own state (resume line, rail, 5 steps). Future courses
8
+ # are closed, dormant panels with no controls.
9
+ # 2. Step screen — one step: why it matters, the exact prompt, Run / Copy,
10
+ # payoff, done.
11
+ #
12
+ # Dual-mode single file (shared-DSL pattern):
13
+ # canvas : streamweaver canvas-push university-design < docs/university/mockups/course_canvas_mockup.rb
14
+ # standalone: ruby docs/university/mockups/course_canvas_mockup.rb
15
+ #
16
+ # v2 (revision after UAT): the resume state moved INSIDE the Getting Started
17
+ # panel, surfaces replaced hairlines, the type scale went up, and a three-colour
18
+ # state language (done / now / next) replaced the single blue. Buttons are
19
+ # `submit: false` (display-only) — this is a mockup, not the built app.
20
+
21
+ # ---------------------------------------------------------------------------
22
+ # One stylesheet. Targets sw- hooks (docs/theming-hooks.md) plus `uni-` classes
23
+ # for the handful of elements that have no component. Inlined as literal CSS
24
+ # rather than a sibling .css file because canvas-push has no route to serve a
25
+ # referenced asset across processes (see App#use_stylesheet, stream_weaver-9uk).
26
+ #
27
+ # Unlike v1, this sheet carries an explicit dark block. Riding only --sw-* meant
28
+ # every surface collapsed onto one flat paper tone; a control panel needs the
29
+ # page and the panel to be different grounds, and that is a two-value decision
30
+ # per theme. The dark block hangs off the same selector the :doc theme uses for
31
+ # its own dark palette, so the two flip on exactly the same signal.
32
+ # ---------------------------------------------------------------------------
33
+ _css = <<~CSS
34
+ body {
35
+ /* ---- Grounds: the page is the room, panels are the instruments ------- */
36
+ --uni-page: #E7E4D9;
37
+ --uni-panel: #FDFCF9;
38
+ --uni-bar: #F3F1E8;
39
+ --uni-sunk: #F1EFE6;
40
+ --uni-line: #D6D2C4;
41
+ --uni-line-soft: #E6E3D7;
42
+
43
+ /* ---- State language: one triad, used identically everywhere ---------- */
44
+ --uni-done: #17754A;
45
+ --uni-done-bg: #E2F0E8;
46
+ --uni-now: var(--sw-color-accent);
47
+ --uni-now-bg: #E5ECFC;
48
+ --uni-next: #6E6959;
49
+ /* Chip ground is separate from the rail track: the chip carries text and
50
+ must clear 4.5:1, the track is a container behind the filled segments. */
51
+ --uni-next-bg: #EFECE1;
52
+ --uni-rail-track: #D6D1BF;
53
+
54
+ --uni-ink: var(--sw-color-text);
55
+ /* Darker than --sw-color-text-muted (#6B6860), which is only 4.37:1 on the
56
+ page ground — the tagline and dormant blurbs sit there. */
57
+ --uni-muted: #5F5C54;
58
+ --uni-on-solid: #ffffff;
59
+ --uni-step: 130ms cubic-bezier(0.16, 1, 0.3, 1);
60
+ }
61
+
62
+ html[data-sw-theme="dark"] body {
63
+ --uni-page: #131110;
64
+ --uni-panel: #221F1A;
65
+ --uni-bar: #2A2620;
66
+ --uni-sunk: #1B1815;
67
+ --uni-line: #383327;
68
+ --uni-line-soft: #2C2821;
69
+
70
+ --uni-done: #57C98A;
71
+ --uni-done-bg: #17301F;
72
+ --uni-now-bg: #1C2946;
73
+ --uni-next: #9A9285;
74
+ --uni-next-bg: #2C2821;
75
+ --uni-rail-track: #322D25;
76
+ --uni-muted: var(--sw-color-text-muted);
77
+ --uni-on-solid: #131110;
78
+ }
79
+
80
+ /* Chrome neutralization — the course canvas IS the page, not a card inside
81
+ one. Holds the same column whether the host set sw-layout-fluid (canvas)
82
+ or sw-layout-default (standalone). */
83
+ /* Bottom padding is a safe area, not slack: the canvas host floats its own
84
+ "Save as doc" control over the bottom-right corner, and at narrow widths it
85
+ otherwise lands on the last line of content. */
86
+ body[class*="sw-layout-"] {
87
+ max-width: 860px; margin: 0 auto;
88
+ padding: 26px 24px 120px;
89
+ background: var(--uni-page);
90
+ font-size: 16px;
91
+ }
92
+
93
+ /* Visually hidden, still announced — the done mark replaces its number with a
94
+ glyph, so without this a screen reader gets nothing for a completed step. */
95
+ .uni-sr {
96
+ position: absolute; width: 1px; height: 1px;
97
+ margin: -1px; padding: 0; overflow: hidden;
98
+ clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
99
+ }
100
+ #app-container {
101
+ background: transparent; border: none; box-shadow: none;
102
+ padding: 0; margin: 0;
103
+ }
104
+
105
+ /* Browser surfaces the design still owns */
106
+ body ::selection {
107
+ background: color-mix(in srgb, var(--uni-now) 24%, transparent);
108
+ color: var(--uni-ink);
109
+ }
110
+ body *:focus-visible {
111
+ outline: 2px solid var(--uni-now);
112
+ outline-offset: 2px;
113
+ border-radius: 4px;
114
+ }
115
+ body { scrollbar-width: thin; scrollbar-color: var(--uni-line) transparent; }
116
+ body ::-webkit-scrollbar { width: 11px; height: 11px; }
117
+ body ::-webkit-scrollbar-thumb {
118
+ background: var(--uni-line); border-radius: 6px;
119
+ border: 3px solid var(--uni-page);
120
+ }
121
+ body ::-webkit-scrollbar-track { background: transparent; }
122
+
123
+ /* Drawn icon set — one family, masked so it inherits currentColor */
124
+ .uni-i {
125
+ display: inline-block; width: 1em; height: 1em;
126
+ background-color: currentColor; flex: none;
127
+ -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
128
+ -webkit-mask-position: center; mask-position: center;
129
+ -webkit-mask-size: contain; mask-size: contain;
130
+ }
131
+ .uni-i--check {
132
+ -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23000' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M3.2 8.4l3.1 3.1 6.5-7'/%3E%3C/svg%3E");
133
+ mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23000' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M3.2 8.4l3.1 3.1 6.5-7'/%3E%3C/svg%3E");
134
+ }
135
+ .uni-i--play {
136
+ -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M5.1 3.3l7.4 4.35a.4.4 0 0 1 0 .7L5.1 12.7A.4.4 0 0 1 4.5 12.35V3.65a.4.4 0 0 1 .6-.35z' fill='%23000'/%3E%3C/svg%3E");
137
+ mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M5.1 3.3l7.4 4.35a.4.4 0 0 1 0 .7L5.1 12.7A.4.4 0 0 1 4.5 12.35V3.65a.4.4 0 0 1 .6-.35z' fill='%23000'/%3E%3C/svg%3E");
138
+ }
139
+ .uni-i--repeat {
140
+ -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23000' stroke-width='1.7' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M13.1 8a5.1 5.1 0 1 1-1.68-3.78'/%3E%3Cpath d='M13.3 2.9v2.6h-2.6'/%3E%3C/svg%3E");
141
+ mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23000' stroke-width='1.7' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M13.1 8a5.1 5.1 0 1 1-1.68-3.78'/%3E%3Cpath d='M13.3 2.9v2.6h-2.6'/%3E%3C/svg%3E");
142
+ }
143
+ .uni-i--back {
144
+ -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23000' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12.4 8H4'/%3E%3Cpath d='M7.6 4.4L4 8l3.6 3.6'/%3E%3C/svg%3E");
145
+ mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23000' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12.4 8H4'/%3E%3Cpath d='M7.6 4.4L4 8l3.6 3.6'/%3E%3C/svg%3E");
146
+ }
147
+ .uni-i--lock {
148
+ -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23000' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3.3' y='6.9' width='9.4' height='6.6' rx='1.4'/%3E%3Cpath d='M5.6 6.9V5.1a2.4 2.4 0 0 1 4.8 0v1.8'/%3E%3C/svg%3E");
149
+ mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23000' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3.3' y='6.9' width='9.4' height='6.6' rx='1.4'/%3E%3Cpath d='M5.6 6.9V5.1a2.4 2.4 0 0 1 4.8 0v1.8'/%3E%3C/svg%3E");
150
+ }
151
+
152
+ /* ---- App band: says what this is, in one glance ----------------------- */
153
+ body .sw-topbar {
154
+ border-bottom: none;
155
+ padding: 0; margin: 0 0 8px;
156
+ background: transparent;
157
+ }
158
+ body .sw-topbar-wordmark {
159
+ font-family: var(--sw-font-display);
160
+ font-size: 27px; font-weight: 600;
161
+ letter-spacing: -0.015em; text-transform: none;
162
+ color: var(--uni-ink);
163
+ }
164
+ body .sw-topbar-crumb,
165
+ body .sw-topbar-separator { font-size: 14px; color: var(--uni-muted); }
166
+ body .sw-topbar-crumb--active { color: var(--uni-ink); font-weight: 650; }
167
+ .uni-tagline {
168
+ display: block; max-width: 62ch;
169
+ font-size: 15px; line-height: 1.55; color: var(--uni-muted);
170
+ margin: 0 0 30px;
171
+ }
172
+
173
+ /* theme_toggle ships an emoji glyph + a "System/Dark/Light" word. The panel
174
+ wants a quiet 30px control, so the label is dropped and the two x-show icon
175
+ spans are re-drawn from the same masked-SVG family as the rest of the page
176
+ (span 1 = dark/moon, span 2 = light/sun). */
177
+ body .sw-theme-toggle__label { display: none; }
178
+ body .sw-theme-toggle__btn {
179
+ background: var(--uni-panel); border: 1px solid var(--uni-line);
180
+ border-radius: 7px; padding: 7px; box-shadow: none;
181
+ color: var(--uni-muted); line-height: 0;
182
+ transition: border-color var(--uni-step), color var(--uni-step);
183
+ }
184
+ body .sw-theme-toggle__btn:hover { border-color: var(--uni-now); color: var(--uni-now); }
185
+ body .sw-theme-toggle__icon {
186
+ display: inline-block; width: 16px; height: 16px; font-size: 0;
187
+ background-color: currentColor;
188
+ -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
189
+ -webkit-mask-position: center; mask-position: center;
190
+ -webkit-mask-size: contain; mask-size: contain;
191
+ }
192
+ body .sw-theme-toggle__icon:nth-of-type(1) {
193
+ -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23000' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M13.4 9.6A5.8 5.8 0 0 1 6.4 2.6a5.8 5.8 0 1 0 7 7z'/%3E%3C/svg%3E");
194
+ mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23000' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M13.4 9.6A5.8 5.8 0 0 1 6.4 2.6a5.8 5.8 0 1 0 7 7z'/%3E%3C/svg%3E");
195
+ }
196
+ body .sw-theme-toggle__icon:nth-of-type(2) {
197
+ -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23000' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='8' cy='8' r='3.1'/%3E%3Cpath d='M8 1.2v1.6M8 13.2v1.6M1.2 8h1.6M13.2 8h1.6M3.2 3.2l1.1 1.1M11.7 11.7l1.1 1.1M12.8 3.2l-1.1 1.1M4.3 11.7l-1.1 1.1'/%3E%3C/svg%3E");
198
+ mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23000' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='8' cy='8' r='3.1'/%3E%3Cpath d='M8 1.2v1.6M8 13.2v1.6M1.2 8h1.6M13.2 8h1.6M3.2 3.2l1.1 1.1M11.7 11.7l1.1 1.1M12.8 3.2l-1.1 1.1M4.3 11.7l-1.1 1.1'/%3E%3C/svg%3E");
199
+ }
200
+
201
+ /* ---- Course panel ----------------------------------------------------- */
202
+ body .sw-card.uni-course {
203
+ background: var(--uni-panel);
204
+ border: 1px solid var(--uni-line);
205
+ border-left: 1px solid var(--uni-line);
206
+ border-radius: 12px;
207
+ padding: 0; margin: 0 0 14px;
208
+ box-shadow: 0 1px 2px rgba(24, 23, 20, 0.05), 0 6px 18px -10px rgba(24, 23, 20, 0.16);
209
+ overflow: hidden;
210
+ }
211
+ html[data-sw-theme="dark"] body .sw-card.uni-course {
212
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.35), 0 8px 22px -12px rgba(0, 0, 0, 0.6);
213
+ }
214
+ body .sw-card.uni-course--dormant {
215
+ background: transparent;
216
+ border-color: var(--uni-line);
217
+ box-shadow: none;
218
+ }
219
+
220
+ /* Title bar — the panel names itself at a size nobody scrolls past */
221
+ body .sw-card-header.uni-course__bar {
222
+ display: flex; align-items: center; gap: 12px;
223
+ background: var(--uni-bar);
224
+ border-bottom: 1px solid var(--uni-line);
225
+ padding: 16px 22px; margin: 0;
226
+ }
227
+ body .uni-course--dormant .sw-card-header.uni-course__bar {
228
+ background: transparent; border-bottom: none;
229
+ padding: 13px 20px 0;
230
+ }
231
+ body .sw-card.uni-course--dormant { margin-bottom: 10px; }
232
+ .uni-course__dot {
233
+ width: 9px; height: 9px; border-radius: 50%; flex: none;
234
+ background: var(--uni-now);
235
+ box-shadow: 0 0 0 3.5px color-mix(in srgb, var(--uni-now) 20%, transparent);
236
+ }
237
+ .uni-course--dormant .uni-course__dot {
238
+ background: transparent; border: 1.5px solid var(--uni-next);
239
+ box-shadow: none;
240
+ }
241
+ body.sw-theme-doc h2.uni-course__name {
242
+ font-family: var(--sw-font-body);
243
+ font-size: 20px; font-weight: 700; line-height: 1.2;
244
+ letter-spacing: -0.012em; color: var(--uni-ink);
245
+ margin: 0; padding: 0; border-bottom: none; flex: 1 1 auto;
246
+ }
247
+ body.sw-theme-doc .uni-course--dormant h2.uni-course__name {
248
+ font-size: 18px; font-weight: 650; color: var(--uni-muted);
249
+ }
250
+ .uni-chip {
251
+ font-size: 11.5px; font-weight: 700; letter-spacing: 0.07em;
252
+ text-transform: uppercase; padding: 5px 9px; border-radius: 5px;
253
+ flex: none; white-space: nowrap;
254
+ background: var(--uni-now-bg); color: var(--uni-now);
255
+ }
256
+ .uni-chip--soon {
257
+ display: inline-flex; align-items: center; gap: 5px;
258
+ background: var(--uni-next-bg); color: var(--uni-next);
259
+ }
260
+ .uni-chip--soon .uni-i { font-size: 12px; }
261
+ /* Aligned to the course name, not the card edge — the dot column is a gutter */
262
+ .uni-course__blurb {
263
+ display: block; font-size: 14.5px; line-height: 1.5;
264
+ color: var(--uni-muted); padding: 3px 20px 14px 41px;
265
+ max-width: 70ch;
266
+ }
267
+
268
+ body .sw-card-body.uni-course__body { padding: 0; }
269
+
270
+ /* ---- Resume band: course state, inside the course --------------------- */
271
+ .uni-resume {
272
+ background: var(--uni-sunk);
273
+ border-bottom: 1px solid var(--uni-line);
274
+ padding: 22px 22px 20px;
275
+ }
276
+ .uni-resume__lead {
277
+ font-family: var(--sw-font-display);
278
+ font-size: clamp(1.35rem, 3.4vw, 1.55rem);
279
+ font-weight: 600; line-height: 1.16; letter-spacing: -0.016em;
280
+ text-wrap: balance; color: var(--uni-ink); margin: 0 0 5px;
281
+ }
282
+ .uni-resume__sub {
283
+ display: block; max-width: 58ch;
284
+ font-size: 15px; line-height: 1.5; color: var(--uni-muted);
285
+ margin-bottom: 18px;
286
+ }
287
+ .uni-actions { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
288
+ .uni-resume .uni-actions { margin-bottom: 20px; }
289
+
290
+ /* ---- Buttons: three weights, one ladder ------------------------------- */
291
+ body .sw-button.uni-btn {
292
+ display: inline-flex; align-items: center; gap: 7px;
293
+ font-family: var(--sw-font-body);
294
+ font-size: 14px; font-weight: 650; line-height: 1;
295
+ padding: 10px 15px; border-radius: 7px;
296
+ border: 1px solid transparent; background: transparent;
297
+ box-shadow: none; text-transform: none;
298
+ color: var(--uni-ink); cursor: pointer;
299
+ transition: background var(--uni-step), color var(--uni-step),
300
+ border-color var(--uni-step), transform var(--uni-step);
301
+ }
302
+ body .sw-button.uni-btn .uni-i { font-size: 14px; }
303
+ body .sw-button.uni-btn--run {
304
+ background: var(--uni-now); color: var(--uni-on-solid);
305
+ padding: 12px 20px; font-size: 15px; font-weight: 650;
306
+ box-shadow: 0 1px 2px color-mix(in srgb, var(--uni-now) 30%, transparent);
307
+ }
308
+ body .sw-button.uni-btn--run:hover {
309
+ background: var(--sw-color-primary-hover); transform: translateY(-1px);
310
+ }
311
+ body .sw-button.uni-btn--outline {
312
+ border-color: var(--uni-line); background: var(--uni-panel);
313
+ color: var(--uni-ink);
314
+ }
315
+ body .sw-button.uni-btn--outline:hover { border-color: var(--uni-now); color: var(--uni-now); }
316
+ body .sw-button.uni-btn--quiet {
317
+ color: var(--uni-muted); font-weight: 600;
318
+ padding: 9px 12px; font-size: 13.5px;
319
+ }
320
+ body .sw-button.uni-btn--quiet:hover { color: var(--uni-now); background: var(--uni-now-bg); }
321
+
322
+ /* ---- Progress rail: position in a sequence, not a percentage ---------- */
323
+ .uni-rail { display: flex; align-items: center; gap: 13px; }
324
+ .uni-rail__track { display: flex; align-items: center; gap: 5px; }
325
+ .uni-rail__seg { width: 30px; height: 6px; border-radius: 3px; background: var(--uni-rail-track); }
326
+ .uni-rail__seg--done { background: var(--uni-done); }
327
+ .uni-rail__seg--current { background: var(--uni-now); width: 40px; }
328
+ .uni-rail__label {
329
+ font-size: 13px; font-weight: 600; color: var(--uni-muted);
330
+ font-variant-numeric: tabular-nums;
331
+ }
332
+
333
+ /* ---- Step rows -------------------------------------------------------- */
334
+ .uni-step {
335
+ display: grid;
336
+ grid-template-columns: 28px 1fr auto;
337
+ align-items: center;
338
+ gap: 0 15px;
339
+ padding: 15px 22px;
340
+ border-bottom: 1px solid var(--uni-line-soft);
341
+ transition: background var(--uni-step);
342
+ }
343
+ .uni-step:last-child { border-bottom: none; }
344
+ .uni-step__mark {
345
+ width: 28px; height: 28px; border-radius: 50%;
346
+ display: flex; align-items: center; justify-content: center;
347
+ font-size: 13.5px; font-weight: 700;
348
+ font-variant-numeric: tabular-nums;
349
+ }
350
+ .uni-step--done .uni-step__mark {
351
+ background: var(--uni-done); color: var(--uni-on-solid);
352
+ }
353
+ .uni-step--done .uni-step__mark .uni-i { font-size: 16px; }
354
+ .uni-step--current .uni-step__mark {
355
+ background: var(--uni-now); color: var(--uni-on-solid);
356
+ box-shadow: 0 0 0 4px color-mix(in srgb, var(--uni-now) 18%, transparent);
357
+ }
358
+ /* Solid ring, not a tint: at 50% the boundary fell under 3:1 */
359
+ .uni-step--todo .uni-step__mark {
360
+ background: transparent; color: var(--uni-next);
361
+ border: 1.5px solid var(--uni-next);
362
+ }
363
+ /* h3, not h2: a step is nested under its course's h2 name */
364
+ body.sw-theme-doc h3.uni-step__title {
365
+ font-family: var(--sw-font-body);
366
+ font-size: 17px; font-weight: 650; line-height: 1.28;
367
+ letter-spacing: -0.008em; color: var(--uni-ink);
368
+ margin: 0 0 3px; padding: 0; border-bottom: none;
369
+ }
370
+ .uni-step__payoff {
371
+ display: block; font-size: 14.5px; line-height: 1.45;
372
+ color: var(--uni-muted); max-width: 54ch;
373
+ }
374
+ .uni-step--current { background: var(--uni-now-bg); }
375
+ body.sw-theme-doc .uni-step--done h3.uni-step__title {
376
+ color: var(--uni-muted); font-weight: 600;
377
+ }
378
+
379
+ /* ---- Shelf divider ---------------------------------------------------- */
380
+ .uni-divider {
381
+ display: flex; align-items: center; gap: 14px;
382
+ margin: 38px 0 16px;
383
+ }
384
+ .uni-divider__label {
385
+ font-size: 15px; font-weight: 650; color: var(--uni-ink);
386
+ letter-spacing: -0.005em; flex: none;
387
+ }
388
+ .uni-divider__rule { height: 1px; background: var(--uni-line); flex: 1 1 auto; }
389
+
390
+ /* ---- Tutorial pointer ------------------------------------------------- */
391
+ .uni-note {
392
+ margin-top: 30px; padding: 16px 20px;
393
+ background: var(--uni-sunk);
394
+ border: 1px solid var(--uni-line-soft);
395
+ border-radius: 10px;
396
+ font-size: 14.5px; line-height: 1.6; color: var(--uni-muted);
397
+ max-width: 74ch;
398
+ }
399
+ .uni-note code {
400
+ font-family: var(--sw-font-mono); font-size: 13.5px;
401
+ background: var(--uni-panel); border: 1px solid var(--uni-line);
402
+ border-radius: 4px; padding: 2px 6px; color: var(--uni-ink);
403
+ }
404
+
405
+ /* ---- Step screen ------------------------------------------------------ */
406
+ .uni-context {
407
+ display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
408
+ margin-bottom: 16px;
409
+ }
410
+ .uni-back {
411
+ display: inline-flex; align-items: center; gap: 7px;
412
+ font-size: 14px; font-weight: 600; color: var(--uni-muted);
413
+ background: var(--uni-panel); border: 1px solid var(--uni-line);
414
+ border-radius: 7px; padding: 8px 13px; text-decoration: none;
415
+ transition: color var(--uni-step), border-color var(--uni-step);
416
+ }
417
+ .uni-back:hover { color: var(--uni-now); border-color: var(--uni-now); }
418
+ .uni-context__spacer { flex: 1 1 auto; }
419
+ .uni-count {
420
+ font-size: 13px; font-weight: 600; color: var(--uni-muted);
421
+ font-variant-numeric: tabular-nums;
422
+ }
423
+
424
+ body .sw-card-header.uni-step__bar { gap: 14px; align-items: center; }
425
+ .uni-step__mark--hero {
426
+ background: var(--uni-now); color: var(--uni-on-solid);
427
+ width: 34px; height: 34px; font-size: 15px;
428
+ box-shadow: 0 0 0 4px color-mix(in srgb, var(--uni-now) 18%, transparent);
429
+ }
430
+ body.sw-theme-doc h1.uni-title {
431
+ font-family: var(--sw-font-display);
432
+ font-size: clamp(1.6rem, 4vw, 1.95rem);
433
+ font-weight: 600; line-height: 1.15; letter-spacing: -0.018em;
434
+ text-wrap: balance; color: var(--uni-ink);
435
+ margin: 0; padding: 0; border-bottom: none; flex: 1 1 auto;
436
+ }
437
+
438
+ .uni-section { padding: 0 22px; }
439
+ .uni-section:first-child { padding-top: 24px; }
440
+ .uni-section:last-of-type { padding-bottom: 8px; }
441
+ /* Section rhythm: a label owns the space above it, not below it */
442
+ .uni-label {
443
+ display: block;
444
+ font-size: 15px; font-weight: 650; color: var(--uni-ink);
445
+ letter-spacing: -0.005em; margin: 32px 0 11px;
446
+ }
447
+ .uni-section:first-child > .uni-label { margin-top: 0; }
448
+ .uni-prose { max-width: 66ch; font-size: 16px; line-height: 1.6; color: var(--uni-ink); }
449
+ .uni-prose p { margin: 0 0 0.8em; }
450
+ .uni-prose p:last-child { margin-bottom: 0; }
451
+
452
+ /* The prompt is a payload, so it gets a payload's chrome: labelled bar,
453
+ sunk ground, monospace only where it is doing verbatim work. */
454
+ .uni-promptbox {
455
+ border: 1px solid var(--uni-line); border-radius: 9px;
456
+ background: var(--uni-sunk); overflow: hidden; margin: 30px 0 18px;
457
+ }
458
+ .uni-promptbox__bar {
459
+ display: flex; align-items: center; justify-content: space-between; gap: 12px;
460
+ background: var(--uni-bar); border-bottom: 1px solid var(--uni-line);
461
+ padding: 9px 15px;
462
+ font-size: 12px; font-weight: 700; letter-spacing: 0.06em;
463
+ text-transform: uppercase; color: var(--uni-muted);
464
+ }
465
+ .uni-prompt {
466
+ display: block;
467
+ font-family: var(--sw-font-mono);
468
+ font-size: 13px; line-height: 1.65;
469
+ white-space: pre-wrap; color: var(--uni-ink);
470
+ padding: 16px 17px; overflow-x: auto;
471
+ }
472
+
473
+ .uni-payoff ul { list-style: none; padding: 0; margin: 0; max-width: 64ch; }
474
+ .uni-payoff li {
475
+ display: grid; grid-template-columns: 20px 1fr; gap: 11px;
476
+ font-size: 15px; line-height: 1.5; color: var(--uni-ink);
477
+ padding: 7px 0;
478
+ }
479
+ .uni-payoff li::before {
480
+ content: ""; margin-top: 4px;
481
+ width: 15px; height: 15px;
482
+ background-color: var(--uni-done);
483
+ -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23000' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M3.2 8.4l3.1 3.1 6.5-7'/%3E%3C/svg%3E");
484
+ mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23000' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M3.2 8.4l3.1 3.1 6.5-7'/%3E%3C/svg%3E");
485
+ -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
486
+ -webkit-mask-size: contain; mask-size: contain;
487
+ }
488
+
489
+ .uni-foot {
490
+ display: flex; align-items: center; gap: 14px;
491
+ background: var(--uni-sunk); border-top: 1px solid var(--uni-line);
492
+ padding: 16px 22px; margin-top: 22px;
493
+ }
494
+ .uni-foot__hint { font-size: 14px; color: var(--uni-muted); }
495
+ .uni-foot__spacer { flex: 1 1 auto; }
496
+ @media (max-width: 620px) {
497
+ .uni-foot { flex-wrap: wrap; }
498
+ .uni-foot__spacer { display: none; }
499
+ }
500
+
501
+ /* ---- Review chrome: the two screens live behind tabs ------------------ */
502
+ body .sw-tabs-list, body .sw-tab-list { margin-bottom: 28px; }
503
+
504
+ @media (max-width: 700px) {
505
+ body[class*="sw-layout-"] { padding-bottom: 150px; }
506
+ }
507
+ @media (max-width: 620px) {
508
+ .uni-step { grid-template-columns: 28px 1fr; row-gap: 10px; }
509
+ .uni-step > .sw-button { grid-column: 2; justify-self: start; }
510
+ .uni-resume__lead { font-size: 1.3rem; }
511
+ }
512
+
513
+ @media (prefers-reduced-motion: reduce) {
514
+ body * { transition-duration: 0.01ms !important; }
515
+ }
516
+ CSS
517
+
518
+ # The literal payload the canvas sends to the worker session. Shown verbatim on
519
+ # the step screen — no hidden prompt, no paraphrase.
520
+ _step3_prompt = <<~PROMPT.strip
521
+ Using stream_weaver, write a small app with a radio_group and a button
522
+ that stores the choice in state and keeps rendering after each click.
523
+ Run it and click through it.
524
+
525
+ Then show me the same form driven by `canvas-wait`, so the script blocks
526
+ until I answer and prints my answer as JSON in the terminal.
527
+ PROMPT
528
+
529
+ _body = proc do
530
+ use_theme :doc
531
+ use_layout :default
532
+ use_stylesheet _css
533
+
534
+ tabs :screen, variant: :line do
535
+ # =====================================================================
536
+ # Screen 1 — Course list. Every course is a panel; course state lives
537
+ # inside its own panel, never above the shelf.
538
+ # =====================================================================
539
+ tab "Course list" do
540
+ topbar(wordmark: "StreamWeaver University") do
541
+ theme_toggle mode: :auto
542
+ end
543
+ phrase "Learn StreamWeaver by running it. Every step is a real app that appears " \
544
+ "in this pane while you stay in your terminal.",
545
+ class: "uni-tagline"
546
+
547
+ # ---- Getting Started: open, and holding its own state --------------
548
+ card(depth: :elevated, class: "uni-course") do
549
+ card_header(class: "uni-course__bar") do
550
+ div(class: "uni-course__dot") {}
551
+ header2 "Getting Started", class: "uni-course__name"
552
+ phrase "In progress", class: "uni-chip"
553
+ end
554
+
555
+ card_body(class: "uni-course__body") do
556
+ div(class: "uni-resume") do
557
+ phrase "Pick up at step 3.", class: "uni-resume__lead"
558
+ phrase "One form, two modes — the same six lines of DSL, running live and blocking.",
559
+ class: "uni-resume__sub"
560
+
561
+ div(class: "uni-actions") do
562
+ button "Run step 3", submit: false, key: "hero-run", class: "uni-btn uni-btn--run"
563
+ button "Repeat step 2", submit: false, key: "hero-repeat", class: "uni-btn uni-btn--quiet"
564
+ end
565
+
566
+ div(class: "uni-rail") do
567
+ div(class: "uni-rail__track") do
568
+ div(class: "uni-rail__seg uni-rail__seg--done") {}
569
+ div(class: "uni-rail__seg uni-rail__seg--done") {}
570
+ div(class: "uni-rail__seg uni-rail__seg--current") {}
571
+ div(class: "uni-rail__seg") {}
572
+ div(class: "uni-rail__seg") {}
573
+ end
574
+ phrase "2 of 5 done", class: "uni-rail__label"
575
+ end
576
+ end
577
+
578
+ steps = [
579
+ ["1", "A card in your pane",
580
+ "Push a card to the canvas beside your terminal without writing any HTML.", :done],
581
+ ["2", "Six lines of Ruby",
582
+ "Run a real app and watch the block re-run on every click.", :done],
583
+ ["3", "One form, two modes",
584
+ "The same form stays live, or blocks and waits for one answer.", :current],
585
+ ["4", "A doc that writes itself",
586
+ "Watch a script append sections, then save the result as a document.", :todo],
587
+ ["5", "Take the doc with you",
588
+ "Export to org, drop it in a gist, read it anywhere.", :todo]
589
+ ]
590
+
591
+ steps.each do |num, title, payoff, state|
592
+ div(class: "uni-step uni-step--#{state}") do
593
+ div(class: "uni-step__mark") do
594
+ if state == :done
595
+ phrase "", class: "uni-i uni-i--check"
596
+ phrase "Step #{num}, done", class: "uni-sr"
597
+ else
598
+ phrase num
599
+ phrase(state == :current ? ", current step" : ", not started", class: "uni-sr")
600
+ end
601
+ end
602
+ div do
603
+ header3 title, class: "uni-step__title"
604
+ phrase payoff, class: "uni-step__payoff"
605
+ end
606
+ case state
607
+ when :done
608
+ button "Repeat", submit: false, key: "rep-#{num}", class: "uni-btn uni-btn--quiet"
609
+ when :current
610
+ button "Run", submit: false, key: "run-#{num}", class: "uni-btn uni-btn--outline"
611
+ else
612
+ button "Run", submit: false, key: "run-#{num}", class: "uni-btn uni-btn--quiet"
613
+ end
614
+ end
615
+ end
616
+ end
617
+ end
618
+
619
+ # ---- The shelf: closed, dormant, no controls -----------------------
620
+ div(class: "uni-divider") do
621
+ phrase "In the works", class: "uni-divider__label"
622
+ div(class: "uni-divider__rule") {}
623
+ end
624
+
625
+ [
626
+ ["Docs deep dive", "Org export, gists, and the reader extension, end to end."],
627
+ ["Canvas modes", "Stateful, blocking, and streaming — when to reach for each."],
628
+ ["Skills and panels", "Teach your agent to drive the canvas without you."]
629
+ ].each do |name, blurb|
630
+ card(depth: :recessed, class: "uni-course uni-course--dormant") do
631
+ card_header(class: "uni-course__bar") do
632
+ div(class: "uni-course__dot") {}
633
+ header2 name, class: "uni-course__name"
634
+ div(class: "uni-chip uni-chip--soon") do
635
+ phrase "", class: "uni-i uni-i--lock"
636
+ phrase "Soon"
637
+ end
638
+ end
639
+ phrase blurb, class: "uni-course__blurb"
640
+ end
641
+ end
642
+
643
+ md "Looking for the old component tour? Run `streamweaver tutorial` — the classic " \
644
+ "walkthrough of every component. Older than these courses, and being refreshed.",
645
+ class: "uni-note"
646
+ end
647
+
648
+ # =====================================================================
649
+ # Screen 2 — Step screen
650
+ # =====================================================================
651
+ tab "Step screen" do
652
+ topbar(wordmark: "StreamWeaver University", breadcrumbs: ["Getting Started", "Step 3"]) do
653
+ theme_toggle mode: :auto
654
+ end
655
+ phrase "Learn StreamWeaver by running it. Every step is a real app that appears " \
656
+ "in this pane while you stay in your terminal.",
657
+ class: "uni-tagline"
658
+
659
+ div(class: "uni-context") do
660
+ div(class: "uni-back") do
661
+ phrase "", class: "uni-i uni-i--back"
662
+ phrase "All steps"
663
+ end
664
+ div(class: "uni-rail") do
665
+ div(class: "uni-rail__track") do
666
+ div(class: "uni-rail__seg uni-rail__seg--done") {}
667
+ div(class: "uni-rail__seg uni-rail__seg--done") {}
668
+ div(class: "uni-rail__seg uni-rail__seg--current") {}
669
+ div(class: "uni-rail__seg") {}
670
+ div(class: "uni-rail__seg") {}
671
+ end
672
+ phrase "2 of 5 done", class: "uni-rail__label"
673
+ end
674
+ div(class: "uni-context__spacer") {}
675
+ phrase "Step 3 of 5", class: "uni-count"
676
+ end
677
+
678
+ card(depth: :elevated, class: "uni-course") do
679
+ card_header(class: "uni-course__bar uni-step__bar") do
680
+ div(class: "uni-step__mark uni-step__mark--hero") { phrase "3" }
681
+ header1 "One form, two modes", class: "uni-title"
682
+ end
683
+
684
+ card_body(class: "uni-course__body") do
685
+ div(class: "uni-section") do
686
+ phrase "Why this matters", class: "uni-label"
687
+ md "Terminal tools make you pick one: a form that stays live, or a prompt that " \
688
+ "blocks until you answer. StreamWeaver runs the same form both ways.\n\n" \
689
+ "You will write it once, change the call that consumes it, and watch the " \
690
+ "behaviour flip. That is the thing a TUI cannot do.",
691
+ class: "uni-prose"
692
+ end
693
+
694
+ div(class: "uni-section") do
695
+ div(class: "uni-promptbox") do
696
+ div(class: "uni-promptbox__bar") do
697
+ phrase "The prompt your worker session receives"
698
+ end
699
+ phrase _step3_prompt, class: "uni-prompt"
700
+ end
701
+
702
+ div(class: "uni-actions") do
703
+ button "Run in worker session", submit: false, key: "step-run",
704
+ class: "uni-btn uni-btn--run"
705
+ copy_button "Copy prompt", text: _step3_prompt,
706
+ copied_label: "Copied", class: "uni-btn uni-btn--outline"
707
+ end
708
+ end
709
+
710
+ div(class: "uni-section") do
711
+ phrase "What you should see", class: "uni-label"
712
+ md "- Click a different option and the page re-renders in place. Nothing blocks.\n" \
713
+ "- The second version freezes your terminal until you click, then prints your answer as JSON.\n" \
714
+ "- Same six lines of DSL. Only the call that consumes the form changed.",
715
+ class: "uni-payoff"
716
+ end
717
+
718
+ # Two exits, deliberately unequal: marking done is the one that writes
719
+ # the ledger, so it keeps the outlined weight; moving on without
720
+ # finishing is allowed but quiet.
721
+ div(class: "uni-foot") do
722
+ button "Mark step 3 done", submit: false, key: "step-done",
723
+ class: "uni-btn uni-btn--outline"
724
+ phrase "Unlocks step 4.", class: "uni-foot__hint"
725
+ div(class: "uni-foot__spacer") {}
726
+ button "Next: step 4", submit: false, key: "step-next",
727
+ class: "uni-btn uni-btn--quiet"
728
+ end
729
+ end
730
+ end
731
+ end
732
+ end
733
+ end
734
+
735
+ if respond_to?(:use_theme)
736
+ # canvas-push / canvas-read: self is already a StreamWeaver::App
737
+ instance_eval(&_body)
738
+ elsif __FILE__ == $PROGRAM_NAME
739
+ require_relative "../../../lib/stream_weaver"
740
+ app("StreamWeaver University") { instance_eval(&_body) }.run!
741
+ end