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,169 @@
1
+ # Frontend-Only Compatibility
2
+
3
+ A canvas DSL file gets rendered by StreamWeaver in more places than the live canvas it was written for: `canvas-read` browses it with no app session behind it, and `streamweaver export` writes it out as a static HTML file with no server at all. Same `.rb`, three different runtimes. This doc tells you what survives each one, why, and what changed in the 2026-08-23 fix pass (canvas-safe epic).
4
+
5
+ Companion reading: `docs/research/frontend-only-matrix.md` is the full ~95-row spike (per-cell repro commands, source line numbers) that this doc summarizes and corrects. Read this doc first; drop into the matrix when you need the exact grep for a specific component.
6
+
7
+ ## The three contexts
8
+
9
+ | Ctx | Name | What's behind it | Adapter mode | htmx? | Alpine? |
10
+ |---|---|---|---|---|---|
11
+ | A | Live canvas | `Canvas::BridgeServer` + WebSocket; an agent may be listening on `canvas-wait` | `:websocket` | yes | yes |
12
+ | B | canvas-read | `Canvas::Reader`, a render-only viewer with no app session | `:websocket` | yes | yes |
13
+ | C | Exported HTML | nothing — a static file | `:http` | no | only if `x-data` is present |
14
+
15
+ Verdicts used below:
16
+
17
+ - **WORKS** — full intended function.
18
+ - **DEGRADES** — reduced but honest. The user can see that less is on offer (disabled control, visible title, read-only state).
19
+ - **SILENTLY-DEAD** — renders, looks interactive, does nothing. The dangerous class: no error, no visual sign, just a click that goes nowhere.
20
+ - **BREAKS** — errors, blanks, or visibly wrong output.
21
+
22
+ Contexts A and B both run the same `:websocket` adapter code, so they usually share a verdict — the difference is that B additionally renders with an `inert:` flag that turns "throws a ReferenceError" into "renders honestly disabled." Where A and B diverge, it's because of that flag, not because of different markup logic.
23
+
24
+ ## Nine mechanisms, in plain language
25
+
26
+ Everything in the compatibility table below falls out of these nine rules. Learn these and you can predict any row that isn't listed explicitly.
27
+
28
+ **State lives in Alpine, and only Alpine's own state is portable.** A component that owns its `x-data` (collapsible, tabs, dropdown, copy button, theme toggle, sortable table) behaves identically in all three contexts — it never asked anything outside itself for state. A component that emits a bare `x-model="key"` is betting on an enclosing scope that holds `key`. Context A's container carries only two WebSocket flags, never the pushed app state. Contexts B and C don't even give it an `x-data` wrapper. Bare `x-model` bindings are silently-dead everywhere backend-less — confirmed in-browser, console-clean, no error at all.
29
+
30
+ **`sendEvent` only exists where `cdn_scripts` defines it.** `window.sendEvent` / `window.getFormState` live inside the websocket init script, which only ships via `adapter.cdn_scripts`. The bridge (A) includes it. The reader (B) deliberately omits it — connecting a WebSocket that will never resolve is worse than not trying — so before the 2026-08-23 fixes, any control calling `sendEvent` directly on B threw `ReferenceError: sendEvent is not defined`. The export (C) never emits it at all; C's `:http` mode doesn't even try.
31
+
32
+ **`hx-post` renders everywhere; almost nothing serves it.** Most form-ish components (`text_field`, `checkbox`, `select`, `date_field`, and friends) still auto-submit via `hx-post` regardless of context. In A and B, htmx is loaded, so the request fires and 404s (the bridge only routes `/canvas/:name`, `/poll`, `/save-doc`, `/event`; the reader only routes its own browse/export endpoints — neither has `/update`, `/action/*`, or `/form/*`). In C, htmx is never loaded, so the request never fires — no network entry, no console line, nothing. **C is the quietest failure mode of the three**, which is exactly why it's easy to ship an export that looks fine and does nothing.
33
+
34
+ **Some JS is inlined; some is CDN-referenced — only the inlined kind survives export/CSP.** Anything the adapter writes directly into the page (`sw-mermaid-zoom.js`, `sw-route-tabs.js`, `sw-copy.js`, `sw-slide-nav.js`, `sw-keyboard.js`, `sw-sidebar-toc.js`, the deck JS, Chart.js's own lazy-load wrapper) travels into an export and survives a CSP that blocks external hosts. Anything referenced by `<script src="https://...">` — Alpine itself, Prism, mermaid's ESM module, Google Fonts — dies under a CSP-locked viewer. `--offline` on `export` inlines mermaid's library only, not Alpine or Prism. **This is the CSP finding that matters most:** any component whose interactivity depends on Alpine (tabs, collapsible, dropdown, theme toggle, `x-show`/`x-cloak` panels) goes from DEGRADES to full **BREAKS** in a CSP-locked viewer — `x-cloak` content never un-hides, so it stays invisible forever. Flat, non-Alpine content (headers, cards, tables, static markdown) is untouched; it was never waiting on a CDN script to begin with. This applies to route tabs too, despite their own JS being inlined: `sw-route-tabs.js` survives a CSP fine and keeps the URL and `pushState` working, but panel show/hide is still plain Alpine `x-show`/`x-cloak` underneath — browser-confirmed during the spike, every panel vanished while the tab triggers and flat content around them stayed visible.
35
+
36
+ **The Chart.js gate used to key on one class, not the family.** `html_exporter.rb`'s CDN-inclusion check originally tested `is_a?(Components::Chart)` — but every shorthand chart DSL method (`bar_chart`, `line_chart`, `pie_chart`, `sparkline`, `stacked_bar_chart`, `area_chart`, `hbar_chart`, `doughnut_chart`) builds a `ChartBase` subclass, not `Components::Chart` itself. Fixed 2026-08-23 (below).
37
+
38
+ **`tabs url: true` is more functional in an export than on canvas.** `route_tabs? = component.url && !websocket_mode?`. On canvas (A, B) route tabs degrade to plain client-side tabs and log a warning once per render — to the *agent's* stderr, never shown to the human viewing the page. In an export, `sw-route-tabs.js` is inlined and runs real `pushState` routing. This is intentional, informational (disc-099), and not a bug — but it means the one component that gets *more* capable off the live canvas is worth knowing about if you're diagnosing "why does this behave differently exported."
39
+
40
+ **Inlined JS still calls server-only routes.** The deck selection engine hardcodes `fetch('/deck/select')`, `/deck/note`, `/deck/submit`, `/deck/generate`, `/deck/refresh`; `local_asset` mints `/sw-asset/<key>/...`; a toast dismiss calls `/toast/dismiss/<id>`. None of these routes exist outside the one server that mounts them. All 404 (A, B) or fail silently (C).
41
+
42
+ **`AppView`-only scripts never reach canvas or export.** The SSE client and the routing/`popstate` scripts are emitted only by `AppView`. A and B render through `AppContentView`; C renders through `ComponentRenderer.render_html`. Neither ever gets those scripts, so `every`, `stream`, and `route_by` have no client at all in any backend-less context — the initial paint shows and nothing ever updates. Out of scope for this epic; know it going in.
43
+
44
+ **`getFormState()` reads the DOM, not Alpine.** It walks `document.querySelectorAll('[x-model]')` at button-click time and harvests raw element values. This is *why* plain inputs still deliver data on the live canvas despite the state-binding rule above — it's a DOM read, not a scope read. Before 2026-08-23 it also mishandled grouped checkboxes: assigning `state[key] = el.checked` for every item in a group meant the *last* item in the group silently overwrote the rest, so a `checkbox_group` sent a boolean instead of the selected array. Fixed 2026-08-23 (below).
45
+
46
+ ## What changed 2026-08-23 (canvas-safe epic)
47
+
48
+ Five defects the spike (disc-093) found are now fixed. Each entry is the honest before/after — what was silently wrong, and exactly what changed.
49
+
50
+ ### 1. Chart export family gate (disc-094)
51
+
52
+ **Before:** `streamweaver export` shipped Chart.js only when the app used `chart type: ...` directly. Every shorthand — `bar_chart`, `hbar_chart`, `line_chart`, `area_chart`, `sparkline`, `pie_chart`, `doughnut_chart`, `stacked_bar_chart` — built a `ChartBase` subclass that failed the `is_a?(Components::Chart)` check. The exported markup still carried the guarded `x-init="if (typeof Chart !== 'undefined') { new Chart(...) }"`, so nothing threw — you got an empty bordered box, console-silent, that looked like a rendering glitch rather than a missing library.
53
+
54
+ **After:** `html_exporter.rb:341-350` gates on `components_include?(Components::Chart) || components_include?(Components::ChartBase)` — the whole family, not a name allowlist. A spec builds an anonymous `Class.new(Components::BarChart)` the gate has never seen and proves it still gets Chart.js, so a future chart subclass can't silently join the dead list. Browser-verified: all 9 chart DSL methods render real ink in an exported file served over HTTP.
55
+
56
+ **Verdict change:** every shorthand chart, export context (C): SILENTLY-DEAD → **WORKS**.
57
+
58
+ ### 2. Reader inert controls (disc-095)
59
+
60
+ **Before:** `canvas-read` rendered `button` and `radio_group` in `:websocket` mode (for markup parity) but deliberately never shipped `cdn_scripts` — so `sendEvent` was undefined. `button`'s handler is `$el.disabled=true; sendEvent(...)` — the first statement runs, the second throws. The user watched the button visibly grey itself out, as if the click had been accepted, then nothing happened. That's the dangerous shape: a real disabled-state mutation on a call that did nothing.
61
+
62
+ **After:** the adapter gained an `inert:` construction flag. When set, `button` and `radio_group` render `disabled` (or `aria-disabled` for non-native elements) plus `title="Interactive on live canvas only"`, with no `@click`/`@change` handler emitting `sendEvent` at all — no ReferenceError is possible because nothing calls it. Confirmed live: zero console errors on click, no self-mutation (the control's disabled state is identical before and after the click, because it was set at render time, not click time). Live-canvas markup (real bridge, `inert:` unset) is pinned byte-for-byte unchanged by golden-string specs.
63
+
64
+ **Verdict change:** `button`, `radio_group`, canvas-read (B): SILENTLY-DEAD (self-disabling, `ReferenceError`) → **DEGRADES** (honestly disabled, zero errors).
65
+
66
+ ### 3. Checkbox array harvest (disc-098, disc-105)
67
+
68
+ **Before:** `getFormState()`'s checkbox branch did `state[key] = el.checked` for every matching element. A `checkbox_group` renders N checkboxes sharing one `x-model` key — so the harvest silently collapsed the whole group to the *last* item's boolean. Select "apple" and "cherry," harvest `{fruits: true}` (or `false`, depending on which checkbox happened to render last) — never `{fruits: ["apple", "cherry"]}`. No error, no warning; `canvas-wait` received confidently wrong data.
69
+
70
+ **After:** the harvest branches on `el.closest('.checkbox-group')`: items inside a group accumulate into an array of checked values (in DOM order, not insertion order); a lone checkbox outside a group keeps returning a boolean, untouched. Verified in a real browser: click apple + cherry + a standalone "subscribe" checkbox → `getFormState()` returns `{fruits: ["apple", "cherry"], subscribe: true}`. The same bug existed in `chip_group`'s multi-select mode (disc-105, same DOM-collapse mechanism) and was folded into the canvas-action-parity fix below with the matching selector: `el.closest('.checkbox-group, .sw-chip-group')`.
71
+
72
+ Checking a box does not, by itself, send anything to `canvas-wait` — `checkbox_group` was never ported to `sendEvent` (its own auto-submit `hx-post` still 404s, unfixed, see disc-106 below). The array only leaves the page when some *other* `sendEvent`-carrying control fires — a `button`, `clickable`, a form submit — and that control's handler calls `getFormState()` at the moment it fires, harvesting whatever boxes are checked right then. This fix corrects what's *in* that harvest; it doesn't give `checkbox_group` a dispatch of its own.
73
+
74
+ **Verdict change:** `checkbox_group`, live canvas (A): BREAKS (silently wrong data) → **DEGRADES** (auto-submit `hx-post` still 404s and is still ignored, same as before — but the value now harvests correctly as an array on button click). Contexts B and C are unaffected: `checkbox_group` was never ported to `sendEvent`, so it's still governed by the "hx-post nobody serves" mechanism, not this fix.
75
+
76
+ ### 4. Five sites ported to `sendEvent`, plus a sweep spec (disc-097)
77
+
78
+ **Before:** only `button` and `radio_group` had ever been made websocket-aware. Five other interactive components still emitted plain `hx-post` with no `websocket_mode?` branch at all: `clickable(action:)`, `menu_item` with an action block, `form` submit buttons, `tag_buttons`, and `chip_group`. Every one of them was silently-dead on the live canvas — a direct asymmetry with `button`, which worked.
79
+
80
+ **After:** all five now dispatch through `sendEvent`, each shaped to match what the control actually does:
81
+
82
+ | Component | Event | Payload |
83
+ |---|---|---|
84
+ | `clickable(action:)` | `action` | `{button: <token>, state: getFormState()}` — same signed token as `button` |
85
+ | `menu_item` (block) | `action` | `{button: 'menu_item_N', state: getFormState()}`; menu still closes on dispatch |
86
+ | `form` submit | `action` | `{button: 'form-<name>-submit', form: '<name>', values: _form, state: getFormState()}` |
87
+ | `tag_buttons` | `change` | `{field: <key>, value: <selected>, state: getFormState()}` — a state change, not a submission, so it mirrors `radio_group` rather than `button` |
88
+ | `chip_group` | `change` | `{field: <key>, value: <chip>, state: getFormState()}`, with the disc-105 array fix so `state` carries the full multi-select array |
89
+
90
+ Each of the five also gained the same inert treatment as #2 above: on canvas-read (B), they render honestly disabled/`aria-disabled` with the same "interactive on live canvas only" title, instead of throwing. `clickable` can't be natively `disabled` (it's a `<div>`), so it uses `aria-disabled` and drops its `tabindex` instead — a focusable element that does nothing would be its own lie.
91
+
92
+ A coverage spec (`htmx_call_site_sweep_spec.rb`) now inventories *every* `htmx_attrs` call site in the adapter and asserts each one's declared disposition (`:sendEvent` or `:htmx`) matches its actual behavior — so a future component can't silently join the dead list the way these five did. It already caught a real regression once, mid-implementation, when a refactor moved call sites into new helper methods without updating the inventory.
93
+
94
+ Two things fell out of this work and are tracked, not fixed here:
95
+
96
+ - **A quoting rule, and one exception.** Author-supplied strings (tag labels, menu item text) now always route through JSON quoting before reaching a JS handler — an apostrophe in a tag label used to produce a JS syntax error, silently killing that specific button. `external_link_button`'s `window.open(url)` call is the one place this rule *isn't* applied, because that line is emitted identically in HTTP mode and canvas mode, and quoting it would change HTTP output — out of scope for an epic that guarantees HTTP-mode bytes don't move (disc-107).
97
+ - **`canvas-wait` no longer returns on button clicks only.** Since `tag_buttons` and `chip_group` now dispatch `change` events, `canvas-wait` needs `--event change` or `--any` to catch them — `action`-only waiting (the default) still only catches `button`, `clickable`, `menu_item`, and `form` submit.
98
+
99
+ **Verdict change:** `clickable(action:)`, `menu_item` (block), `form` submit, `tag_buttons`, live canvas (A): SILENTLY-DEAD → **WORKS**. `chip_group`, live canvas (A): DEGRADES → **WORKS**. All five, canvas-read (B): SILENTLY-DEAD → **DEGRADES**. Export (C) is unaffected — HTTP-mode markup is byte-for-byte unchanged by construction.
100
+
101
+ ### 5. Deck: success-gated confirmation, read-only via `deck_server:` (disc-096)
102
+
103
+ **Before:** `swDeckSelect` was the most dangerous shape in the whole matrix. It applied the selected class and `aria-checked` **before** calling `fetch('/deck/select')`, with no `.catch`. Wherever `/deck/*` doesn't exist — canvas, canvas-read, export — the fetch 404s (or fails outright) and the failure is swallowed. The user sees a confirmed selection that was never recorded and never reached anything. It's not just dead, it actively lies.
104
+
105
+ **After:** confirmation is success-gated. Visual/aria state changes only after the POST resolves; a failed request surfaces a console error naming the route and reason, and applies no visual change at all — a card that was already selected stays selected, a click that failed shows nothing. An `aria-busy` state covers the in-flight moment (gating alone would have just replaced a lie with silence). Separately, the whole deck UI (option cards, notes textarea, submit, generate-more, model selector) now renders read-only wherever `/deck/*` genuinely can't exist, gated by a new `deck_server:` construction flag threaded through the adapter and set at 11 call sites: the bridge's live-push, the reader, the exporter, and — this was a second, previously undocumented bug the review caught — all nine `service.rb` mount points. Service mode (`streamweaver serve`) has real HTTP routes but never mounted `/deck/*` either, so it had the identical uncovered lie; it's now honestly read-only too.
106
+
107
+ One thing this fix does *not* address, filed as its own followup: the deck JS posts to `/deck/*` by absolute path, ignoring the adapter's `url_prefix`. So even a service-mode app that mounted deck routes under `/apps/<id>/deck/*` would still post to the wrong place. Today's fix makes that render read-only, which is honest for current behavior; if decks are ever wanted under `streamweaver serve`, url-prefixing the deck JS is a prerequisite, and only then would service adapters flip `deck_server: true`.
108
+
109
+ **Verdict change:** `design_deck` option selection, notes, and generate-more, all three backend-less contexts (A, B, C): SILENTLY-DEAD ("and it lies") → **DEGRADES** (honestly read-only). The one context where deck selection still fully works — the real standalone `streamweaver run` server — is unchanged behaviorally except that confirmation now lands slightly after the click (the fix, not a regression).
110
+
111
+ ## Compatibility table
112
+
113
+ Everything not listed here behaves as in the full matrix (`docs/research/frontend-only-matrix.md`) — unaffected by this fix pass. This table covers the rows that changed, plus the ones people ask about most.
114
+
115
+ ### Inputs
116
+
117
+ | Component | A live canvas | B canvas-read | C export |
118
+ |---|---|---|---|
119
+ | `text_field` / `text_area` / `date_field` / `checkbox` / `select` | DEGRADES — value harvested on button click (M9), but the auto-submit `hx-post` still 404s per keystroke/change, unfixed (disc-106) | SILENTLY-DEAD — same 404, no visible sign | SILENTLY-DEAD — `hx-post` present, htmx never loaded, no network activity at all |
120
+ | `radio_group` | WORKS — ported to `sendEvent('change')` before this epic | **DEGRADES** — honestly disabled, zero console errors (was SILENTLY-DEAD) | SILENTLY-DEAD — falls back to `hx-post /update` in `:http` mode |
121
+ | `checkbox_group` | **DEGRADES** — array harvested correctly on button click (was BREAKS: silently wrong boolean) | SILENTLY-DEAD — not ported to `sendEvent`; unaffected by the harvest fix | SILENTLY-DEAD |
122
+ | `chip_group` | **WORKS** — ported to `sendEvent('change')`, array harvest fixed (was DEGRADES) | **DEGRADES** — honestly disabled (was SILENTLY-DEAD) | SILENTLY-DEAD |
123
+ | `tag_buttons` | **WORKS** — ported to `sendEvent('change')` (was SILENTLY-DEAD) | **DEGRADES** (was SILENTLY-DEAD) | SILENTLY-DEAD |
124
+ | `form` block | **WORKS** — submit ported to `sendEvent('action')` (was SILENTLY-DEAD) | **DEGRADES** (was SILENTLY-DEAD) | SILENTLY-DEAD |
125
+
126
+ ### Buttons and actions
127
+
128
+ | Component | A live canvas | B canvas-read | C export |
129
+ |---|---|---|---|
130
+ | `button` (default) | WORKS when an agent is listening on `canvas-wait`; with no listener, click still fires the "✓ Submitted" replacement | **DEGRADES** — honestly disabled, zero errors (was SILENTLY-DEAD, self-disabling) | SILENTLY-DEAD |
131
+ | `clickable(action:)` | **WORKS** — ported (was SILENTLY-DEAD) | **DEGRADES**, `aria-disabled` + no `tabindex` (was SILENTLY-DEAD) | SILENTLY-DEAD |
132
+ | `menu_item` (action block) | **WORKS** — ported, menu still closes (was SILENTLY-DEAD) | **DEGRADES** (was SILENTLY-DEAD) | SILENTLY-DEAD |
133
+ | `external_link_button` | DEGRADES — `window.open` fires; paired submit 404s. Unquoted-URL edge case tracked, not fixed (disc-107) | DEGRADES | DEGRADES |
134
+
135
+ ### Diagrams and charts
136
+
137
+ | Component | A | B | C |
138
+ |---|---|---|---|
139
+ | `chart type: ...` (`Components::Chart` directly) | WORKS | WORKS | WORKS (was already immune to the drift) |
140
+ | `bar_chart` / `hbar_chart` / `line_chart` / `area_chart` / `sparkline` / `pie_chart` / `doughnut_chart` / `stacked_bar_chart` | WORKS | WORKS | **WORKS** — Chart.js now ships for the whole `ChartBase` family (was SILENTLY-DEAD: empty bordered box, no console error) |
141
+ | `mermaid` | WORKS | WORKS | WORKS; `--offline` inlines the library so it survives CSP (except `elk: true`, no global build) |
142
+
143
+ ### Deck and slides
144
+
145
+ | Component | A | B | C |
146
+ |---|---|---|---|
147
+ | `slide_container` / `slide` navigation | WORKS (unrelated to the deck fix) | WORKS | WORKS |
148
+ | `design_deck` option selection, notes, generate-more | **DEGRADES** — honestly read-only, `/deck/*` doesn't exist here (was SILENTLY-DEAD and lying: optimistic confirmation before an unhandled 404) | **DEGRADES** (same) | **DEGRADES** (same) |
149
+ | `design_deck` under a real `streamweaver run` deck server | WORKS unchanged, except confirmation now lands after the POST resolves instead of before it (the fix) | n/a | n/a |
150
+ | `design_deck` under `streamweaver serve` (service mode) | **DEGRADES** — read-only, previously-undocumented identical bug, now covered (was SILENTLY-DEAD and lying, same as above) | n/a | n/a |
151
+
152
+ ## The remaining edge: disc-106
153
+
154
+ Seven `htmx_attrs` call sites still post into routes nothing serves, on canvas: `text_field`, `text_area`, `date_field`, `checkbox`, `select`, `checkbox_group`'s auto-submit, and `external_link_button`. They're less visible than the five fixed in disc-097 because they're state *syncs*, not submissions — the value gets re-harvested correctly by `getFormState()` the next time an action fires, so the user doesn't see missing behavior. But every keystroke or change still fires a debounced `POST /canvas/<session>/update` that 404s, so a live canvas with a text field generates a steady trickle of 404s and htmx console noise that a careful user would notice. Fixing this is a canvas-protocol question (does per-keystroke state sync over the bridge even make sense?), not a rendering one, and is deliberately out of this epic's scope.
155
+
156
+ ## file:// open questions (untested)
157
+
158
+ These follow from the code but haven't been confirmed in a browser — they're the two remaining unknowns from the original spike, unaffected by the fixes above:
159
+
160
+ - **Route tabs under `file://`.** Exported `tabs url: true` calls `history.pushState` with a query string. Several browsers throw `SecurityError` for `pushState` on `file://` origins. If it throws, tab switching in a locally-opened export is BREAKS, not the WORKS this doc otherwise reports for exports — nobody has opened one from disk and clicked a tab to confirm either way.
161
+ - **`copy_button` clipboard access under `file://`.** `navigator.clipboard` requires a secure context, and whether a `file://` origin counts varies by browser. Untested.
162
+
163
+ Both are cheap to check by hand (open an export from disk, click a route tab, click a copy button) — nobody has done it yet.
164
+
165
+ ## See also
166
+
167
+ - `docs/research/frontend-only-matrix.md` — the full row-by-row spike this doc summarizes: per-cell repro commands, exact source lines, and everything not called out as changed above.
168
+ - `docs/components_reference.md` — component-level API reference; cross-references this doc where a component's backend-less behavior is non-obvious.
169
+ - `llms.txt` — the agent-facing quick reference; see its Detailed Documentation table.
@@ -0,0 +1,238 @@
1
+ # StreamWeaver vs. Claude HTML Artifact Aesthetic — Capability Audit
2
+
3
+ **Author:** Forrest Chang
4
+ **Date:** May 2026
5
+ **Purpose:** Identify gaps between StreamWeaver's current component set and the "Claude HTML artifact" aesthetic that dominates AI-generated UI sharing on social media. Use this audit to prioritize what to build next.
6
+
7
+ ---
8
+
9
+ ## What Is the "Claude HTML Artifact" Aesthetic?
10
+
11
+ When Claude generates a self-contained HTML file and people screenshot/share it, there's a recognizable aesthetic:
12
+
13
+ - **Dark cosmic backgrounds**: `#0a0a1a`, `#0f0f23`, `#1a1a2e` — near-black with a blue/indigo tint
14
+ - **Purple → blue → cyan gradients** everywhere: headings, borders, progress fills, highlights
15
+ - **Glassmorphism cards**: `backdrop-filter: blur(20px)`, `background: rgba(255,255,255,0.05)`, subtle border `rgba(255,255,255,0.1)`
16
+ - **Gradient text headings**: `background: linear-gradient(135deg, #a78bfa, #60a5fa, #34d399)` + `background-clip: text; -webkit-text-fill-color: transparent`
17
+ - **Animated entrance effects**: `@keyframes fadeInUp` — elements slide up from 20px below with opacity 0→1 on page load
18
+ - **Smooth hover transitions**: `transition: all 0.3s ease` — cards lift, borders glow, buttons shimmer
19
+ - **Typography**: Inter or system-ui, generous padding (24–40px), rounded corners (12–16px radius)
20
+ - **Interactive sliders** (`<input type="range">`) updating visuals in real-time without page reload
21
+ - **KPI dashboards** with counters that animate up from 0 to the final value
22
+ - **Step-by-step progressions**: Next/Prev buttons, animated reveals, progress bars that fill as you advance
23
+ - **Canvas/SVG animations**: physics simulations, neural network visualizers, algorithm step-throughs
24
+ - **Parameter-reactive charts**: change a slider → chart re-renders instantly
25
+
26
+ This aesthetic has become the benchmark for "impressive AI output." StreamWeaver needs to be able to match or exceed it.
27
+
28
+ ---
29
+
30
+ ## StreamWeaver Strengths — What Already Matches
31
+
32
+ ### ✅ Dark Theme Infrastructure
33
+ `theme_preset :technical` and `theme_toggle` provide dark mode. The `:terminal` preset goes full dark. Custom CSS via inline `style:` is supported on all components. **Foundation exists** — the colors just need to be dialed in.
34
+
35
+ ### ✅ Glassmorphism — Achievable Today
36
+ `card` accepts `style:` overrides. You can do glassmorphism manually:
37
+ ```ruby
38
+ card(style: "background: rgba(255,255,255,0.05); backdrop-filter: blur(20px); border: 1px solid rgba(255,255,255,0.1); border-radius: 16px;") do
39
+ # content
40
+ end
41
+ ```
42
+ Not ergonomic, but it works. A `:glass` card variant would be the right fix.
43
+
44
+ ### ✅ KPI Dashboard Component Exists
45
+ `kpi_dashboard` is present and working. However (see gaps below) it lacks the animated counter entrance effect.
46
+
47
+ ### ✅ Tabs
48
+ `tabs` / `tab` with `:line` and `:pill` variants — matches a core artifact interactive pattern.
49
+
50
+ ### ✅ Progress Bar (Animated)
51
+ `progress_bar` with animation exists. This covers the step-progression fill pattern.
52
+
53
+ ### ✅ Charts (Chart.js)
54
+ `chart` supports bar, line, pie, doughnut, radar. Chart.js is the same library many Claude artifacts use.
55
+
56
+ ### ✅ Slide Container
57
+ `slide_container` (swap/scroll_snap modes) + `keyboard_shortcuts` covers the step-through explainer pattern. This is actually **stronger** than typical Claude artifact step progressions.
58
+
59
+ ### ✅ Pipeline Component
60
+ `pipeline` for flow visualization — covers data/process flow diagrams.
61
+
62
+ ### ✅ Mermaid with Zoom
63
+ `mermaid` with zoom/elk support — architecture diagrams are fully covered, arguably better than a raw Claude SVG.
64
+
65
+ ### ✅ Code Blocks with Syntax Highlighting
66
+ `code_block` with Prism.js — matches the code-display pattern in explainer artifacts.
67
+
68
+ ### ✅ Callout / Alert Variants
69
+ `callout` (info/warning/success/error/tip) — the colored insight boxes that artifact layouts love.
70
+
71
+ ### ✅ Comparison Component
72
+ `comparison` (before/after panels) — a pattern that shows up constantly in refactor-explainer artifacts.
73
+
74
+ ### ✅ Hero Component
75
+ `hero` — the big splash header that anchors many artifact layouts.
76
+
77
+ ---
78
+
79
+ ## Gaps — What's Missing That Artifact World Loves
80
+
81
+ ### 🔴 CRITICAL GAPS
82
+
83
+ #### 1. No Range/Slider Input (`<input type="range">`)
84
+ **Impact: High.** The slider → real-time reactive visual update is THE signature Claude artifact interactive pattern. Without a `slider` component, StreamWeaver cannot replicate parameter-reactive charts, physics demos, or any "drag this to see the effect" UX.
85
+
86
+ **What's needed:**
87
+ ```ruby
88
+ slider(label: "Learning Rate", min: 0.001, max: 1.0, step: 0.001, default: 0.1, bind: :lr)
89
+ ```
90
+ …and the chart/display should reactively update when `:lr` changes.
91
+
92
+ #### 2. No Animated Counter on `stat_display` / `kpi_dashboard`
93
+ **Impact: High.** The "number counts up from 0 to 42,891 over 1.2 seconds on load" effect is a crowd-pleaser in artifact KPI dashboards. `stat_display` renders the number statically.
94
+
95
+ **What's needed:** A `count_up: true` option (or default behavior) on `stat_display` and `kpi_dashboard` entries, using a lightweight JS counter animation.
96
+
97
+ #### 3. No Animated Canvas Wrapper Component
98
+ **Impact: High.** Physics sims, neural net visualizers, sorting algorithm animations — these all require a `<canvas>` element with a JavaScript animation loop. SW has no `canvas` or `animation_canvas` component.
99
+
100
+ **What's needed:**
101
+ ```ruby
102
+ animation_canvas(height: 400, script: :bubble_sort) # built-in named animations
103
+ # OR
104
+ raw_canvas(height: 400, js: <<~JS)
105
+ // user-supplied animation loop
106
+ JS
107
+ ```
108
+
109
+ #### 4. No Gradient Text Heading — No `header` Style Variants
110
+ **Impact: Medium-High.** The purple→cyan gradient text on headings is the most visually distinctive artifact element. SW headers render as plain styled text.
111
+
112
+ **What's needed:** Either a `gradient: true` option on `header1`–`header6`, or a `gradient_header` component:
113
+ ```ruby
114
+ header1("Neural Architecture", gradient: "135deg, #a78bfa, #60a5fa, #34d399")
115
+ ```
116
+ **Workaround today:** Use `div` with inline `style:` containing the gradient CSS — verbose but functional.
117
+
118
+ #### 5. No Entrance Animations (fadeInUp on Load)
119
+ **Impact: Medium.** Artifact pages feel alive because elements cascade in on load. SW components render static.
120
+
121
+ **What's needed:** An `animate:` option on containers/cards: `card(animate: :fade_in_up, delay: 0.1)`. Could be implemented as CSS classes added by the DSL with a small animation stylesheet.
122
+
123
+ #### 6. No Glassmorphism Shorthand / `:glass` Card Variant
124
+ **Impact: Medium.** As noted above, it's doable manually but requires verbose inline CSS. A first-class `:glass` variant on `card` would make the aesthetic effortless.
125
+
126
+ ### 🟡 MODERATE GAPS
127
+
128
+ #### 7. No Step Wizard Component
129
+ `slide_container` covers sequential content well, but a purpose-built `step_wizard` with:
130
+ - Numbered step indicators at top
131
+ - Next/Prev/Submit buttons
132
+ - Per-step validation state
133
+ - Progress bar integrated
134
+
135
+ …would match the "onboarding wizard" artifact pattern more cleanly.
136
+
137
+ #### 8. Parameter-Reactive Charts
138
+ Current `chart` component renders once. There's no binding mechanism to re-render a chart when a reactive state value changes (e.g., slider changes → chart dataset updates).
139
+
140
+ **What's needed:** A `reactive:` option or `bind:` param on `chart`, wired to SW's reactive state system.
141
+
142
+ #### 9. No Number Format Options on `stat_display`
143
+ Artifact KPI cards show `$1.2M`, `99.7%`, `+12.4%` with appropriate formatting and color-coded deltas (green up / red down). `stat_display` needs `format:`, `prefix:`, `suffix:`, and `delta_color:` options.
144
+
145
+ #### 10. No Tooltip Component
146
+ Artifact dashboards extensively use hover tooltips on data points and labels. SW has no `tooltip` wrapper component.
147
+
148
+ #### 11. No Color Swatch / Palette Display Component
149
+ Minor but noticeable in design-system artifacts.
150
+
151
+ ### 🟢 MINOR / NICE-TO-HAVE
152
+
153
+ #### 12. No Sparkline
154
+ Inline mini-chart (a 5-value trend line inside a stat card). Would pair well with `kpi_dashboard`.
155
+
156
+ #### 13. No Tag Cloud / Word Cloud
157
+ Shows up in text-analysis artifacts.
158
+
159
+ #### 14. No Typing Animation Component
160
+ The "text types itself out" effect used in demo/explainer artifacts.
161
+
162
+ ---
163
+
164
+ ## The Design Aesthetic Gap
165
+
166
+ ### Current SW Default Look
167
+ StreamWeaver's out-of-box appearance (no theme specified) is a **light, clean, editorial/technical look** — good for documentation and internal tools, but nothing like the dark-cosmos-gradient aesthetic.
168
+
169
+ | Dimension | Claude Artifact Default | SW Default |
170
+ |-----------|------------------------|------------|
171
+ | Background | `#0a0a1a` near-black | White / light gray |
172
+ | Headings | Gradient text | Plain bold, themed color |
173
+ | Cards | Glass morphism, blur | Solid bordered card |
174
+ | Accent color | Purple/violet/cyan | Blue or neutral |
175
+ | Entrance | Animated fadeInUp | Static render |
176
+ | Font | Inter, generous spacing | System-ui, moderate spacing |
177
+
178
+ ### Bridging the Gap Today
179
+
180
+ **Option A: `theme_preset :technical` + custom overrides**
181
+ `:technical` gives a dark, dense, code-focused look. Then override with inline CSS:
182
+ ```ruby
183
+ theme_preset :technical
184
+ hero(style: "background: linear-gradient(135deg, #0a0a1a, #1a1a2e); padding: 60px 40px;") do
185
+ div(style: "font-size: 3rem; font-weight: 800; background: linear-gradient(135deg, #a78bfa, #60a5fa); -webkit-background-clip: text; -webkit-text-fill-color: transparent;") { "My Title" }
186
+ end
187
+ ```
188
+
189
+ **Option B: `:terminal` preset** for fully dark background, then add gradient accents.
190
+
191
+ **Option C: Custom CSS injection** — SW supports raw CSS blocks. Create a reusable "artifact aesthetic" CSS snippet that sets the dark background, gradient variables, and entrance animation keyframes. Include it at the top of any canvas session.
192
+
193
+ ### The Right Long-Term Fix
194
+ Add a `:artifact` or `:dark_gradient` theme preset that ships with:
195
+ - Dark cosmic background colors
196
+ - Purple→cyan gradient CSS custom properties
197
+ - Glassmorphism card defaults
198
+ - `fadeInUp` keyframe pre-defined
199
+ - Inter font import
200
+
201
+ This would make "looking like a Claude artifact" a one-liner: `theme_preset :dark_gradient`.
202
+
203
+ ---
204
+
205
+ ## Priority Matrix
206
+
207
+ | Gap | User Impact | Implementation Effort | Priority |
208
+ |-----|------------|----------------------|----------|
209
+ | Slider/range input | Very High | Medium | P0 |
210
+ | Animated counter on stat | High | Low | P0 |
211
+ | Parameter-reactive charts | High | Medium | P1 |
212
+ | `:dark_gradient` theme preset | High | Low-Medium | P1 |
213
+ | Gradient header option | Medium | Low | P1 |
214
+ | Entrance animations | Medium | Low | P1 |
215
+ | `:glass` card variant | Medium | Low | P2 |
216
+ | `animation_canvas` component | High | High | P2 |
217
+ | Step wizard | Medium | Medium | P2 |
218
+ | Tooltip | Medium | Medium | P2 |
219
+ | Animated sparkline | Low | Medium | P3 |
220
+ | Number formatting on stat | Low | Low | P2 |
221
+
222
+ ---
223
+
224
+ ## Summary
225
+
226
+ StreamWeaver has **strong structural foundations** for matching the Claude artifact aesthetic:
227
+ - The component variety is there (tabs, charts, mermaid, slides, comparison, kpi_dashboard)
228
+ - Dark theme exists
229
+ - Chart.js is already the same library
230
+
231
+ The **critical missing pieces** are:
232
+ 1. **Slider input** (the most-loved artifact pattern)
233
+ 2. **Animated counters** on stat displays
234
+ 3. **Gradient text / `:dark_gradient` theme** (purely aesthetic but high-signal)
235
+ 4. **Parameter-reactive chart binding**
236
+ 5. **Canvas animation wrapper** for physics/algorithm demos
237
+
238
+ Filling P0 and P1 items would allow StreamWeaver to produce outputs indistinguishable from — and in many ways superior to — the Claude HTML artifact aesthetic, with the added advantage of being a live server-rendered reactive app rather than a static HTML file.
@@ -0,0 +1,203 @@
1
+ Now I get it. This is seriously well thought out.
2
+
3
+ ---
4
+
5
+ ## What StreamWeaver Actually Enables
6
+
7
+ Your README nails the core insight: **"What do I need? Some text, a few inputs, a button. Why isn't *that* the interface?"**
8
+
9
+ The DSL-to-token ratio is striking:
10
+
11
+ ```ruby
12
+ # StreamWeaver: ~15 tokens
13
+ app "Survey" do
14
+ text_field :name
15
+ select :priority, ["Low", "Medium", "High"]
16
+ button "Submit"
17
+ end.run_once!
18
+ ```
19
+
20
+ vs.
21
+
22
+ ```jsx
23
+ // React equivalent: ~150+ tokens minimum
24
+ // Plus you need: npm, build step, server, state management...
25
+ ```
26
+
27
+ And the `run_once!` pattern is exactly what agentic workflows need - structured data in, structured data out, then *disappear*.
28
+
29
+ ---
30
+
31
+ ## The Bigger Picture: What Could This Become?
32
+
33
+ Here's where I think this gets interesting beyond just "a nice DSL":
34
+
35
+ ### 1. StreamWeaver as a Claude Code Communication Protocol
36
+
37
+ Right now Claude Code's interaction model is:
38
+ - **Output**: text stream, file diffs, bash commands
39
+ - **Input**: text prompt
40
+
41
+ What if there was a **recognized protocol** where Claude Code could emit:
42
+
43
+ ```ruby
44
+ stream_weaver do
45
+ header "I found 3 approaches to solve this"
46
+
47
+ radio_group :choice, [
48
+ "Approach A: Refactor the base class (clean but risky)",
49
+ "Approach B: Add adapter layer (safe but verbose)",
50
+ "Approach C: Monkey-patch (fast but dirty)"
51
+ ]
52
+
53
+ text_area :notes, placeholder: "Any constraints I should know?"
54
+
55
+ button "Proceed with selection"
56
+ end
57
+ ```
58
+
59
+ Instead of:
60
+ ```
61
+ I found 3 approaches:
62
+ 1. Approach A: Refactor...
63
+ 2. Approach B: Add adapter...
64
+ 3. Approach C: Monkey-patch...
65
+
66
+ Which would you prefer? Please type 1, 2, or 3.
67
+ ```
68
+
69
+ The first is **structured input** that Claude can parse cleanly. The second is ambiguous text parsing.
70
+
71
+ ### 2. The "Companion Display" Concept
72
+
73
+ What if `stream_weaver` ran as a persistent companion to Claude Code sessions?
74
+
75
+ ```
76
+ ┌─────────────────────────────────────┬──────────────────────────┐
77
+ │ Terminal (Claude Code) │ StreamWeaver Companion │
78
+ │ │ │
79
+ │ > claude │ ┌────────────────────┐ │
80
+ │ What would you like to do? │ │ Session Context │ │
81
+ │ > analyze this codebase │ │ │ │
82
+ │ │ │ Files touched: 12 │ │
83
+ │ [reading files...] │ │ Tokens used: 45k │ │
84
+ │ │ │ Cost: $0.23 │ │
85
+ │ I've identified several patterns... │ │ │ │
86
+ │ │ │ ┌────────────────┐ │ │
87
+ │ │ │ │ Quick Actions │ │ │
88
+ │ │ │ │ [Approve All] │ │ │
89
+ │ │ │ │ [Show Diff] │ │ │
90
+ │ │ │ │ [Rollback] │ │ │
91
+ │ │ │ └────────────────┘ │ │
92
+ │ │ └────────────────────┘ │
93
+ └─────────────────────────────────────┴──────────────────────────┘
94
+ ```
95
+
96
+ The companion could:
97
+ - Show accumulated context (what files were touched, what decisions were made)
98
+ - Provide quick action buttons that inject prompts
99
+ - Display visualizations Claude generates (charts, architecture diagrams)
100
+ - Persist between prompts (unlike current ephemeral output)
101
+
102
+ ### 3. Data-Only Generation Pattern
103
+
104
+ Your README hints at this but it's worth emphasizing - this is potentially the killer feature:
105
+
106
+ ```ruby
107
+ # Pre-built app (human creates once)
108
+ class CodeReviewApp < StreamWeaver::App
109
+ def initialize(findings:)
110
+ @findings = findings
111
+ end
112
+
113
+ def template
114
+ app "Code Review Results" do
115
+ @findings.each do |finding|
116
+ card do
117
+ header3 finding[:title]
118
+ text finding[:description]
119
+ status_badge finding[:severity], finding[:message]
120
+
121
+ checkbox :"approve_#{finding[:id]}", "Approve fix"
122
+ end
123
+ end
124
+
125
+ button "Apply Selected Fixes"
126
+ end
127
+ end
128
+ end
129
+
130
+ # Claude generates ONLY this data (~50 tokens):
131
+ findings = [
132
+ { id: 1, title: "N+1 Query", severity: :warning,
133
+ description: "UserController#index", message: "Add includes(:posts)" },
134
+ { id: 2, title: "Unused Variable", severity: :info,
135
+ description: "line 45", message: "Remove `temp`" }
136
+ ]
137
+
138
+ # Render with pre-built UI
139
+ CodeReviewApp.new(findings: findings).run_once!
140
+ ```
141
+
142
+ Claude generates **data**, not UI. The UI is pre-built and **token-free** at generation time.
143
+
144
+ ---
145
+
146
+ ## The Radical Direction: StreamWeaver as an MCP Server
147
+
148
+ Here's where it could get really different from what's being done:
149
+
150
+ What if StreamWeaver was an **MCP server** that Claude Code could invoke?
151
+
152
+ ```ruby
153
+ # MCP tool definition
154
+ {
155
+ name: "stream_weaver_ui",
156
+ description: "Present rich UI to user and collect structured response",
157
+ input_schema: {
158
+ type: "object",
159
+ properties: {
160
+ components: { type: "array", items: { ... } }
161
+ }
162
+ }
163
+ }
164
+ ```
165
+
166
+ Then Claude Code could literally call:
167
+
168
+ ```
169
+ Tool: stream_weaver_ui
170
+ Input: {
171
+ "title": "Choose Implementation",
172
+ "components": [
173
+ { "type": "radio_group", "name": "approach", "options": ["A", "B", "C"] },
174
+ { "type": "text_area", "name": "notes", "placeholder": "Constraints?" }
175
+ ]
176
+ }
177
+ ```
178
+
179
+ And get back:
180
+
181
+ ```
182
+ Result: { "approach": "B", "notes": "Need to maintain backwards compat" }
183
+ ```
184
+
185
+ This would work with **any** Claude Code-like system - OpenCode, Aider, whatever. The MCP protocol is the bridge.
186
+
187
+ ---
188
+
189
+ ## What's Missing That Would Make This Transform Claude Code?
190
+
191
+ 1. **Hooks Integration** - Claude Code has hooks (`PostToolUse`, `SessionStart`, etc.). StreamWeaver could hook into these to automatically present UI at decision points.
192
+
193
+ 2. **Session Memory UI** - A persistent view of what Claude "knows" about this session, editable by the user.
194
+
195
+ 3. **Approval Workflow Enhancement** - Replace yes/no/edit with richer approval UI (partial approvals, inline edits, confidence sliders).
196
+
197
+ 4. **Cost/Token Dashboard** - Real-time visibility into what this session is costing, with budget controls.
198
+
199
+ 5. **Multi-Agent Visualization** - If Claude is orchestrating sub-agents (like your Codex/Gemini routing system), show that visually.
200
+
201
+ ---
202
+
203
+ What aspects of this resonate? The MCP server angle seems like it could be the "different direction" that makes this composable with the existing ecosystem rather than competing with it.