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,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StreamWeaver
4
+ module Components
5
+ # Centralized keyboard shortcut registry.
6
+ # Non-visual component -- emits a <script> block that registers keyboard handlers
7
+ # with context awareness (suppress in text inputs, textareas, code editors).
8
+ #
9
+ # "mod" maps to Meta (Cmd) on Mac, Control elsewhere -- handled in JS.
10
+ #
11
+ # sw- CSS classes: none (non-visual)
12
+ #
13
+ # @example Basic usage
14
+ # keyboard_shortcuts do |kb|
15
+ # kb.on "mod+s", context: :global do |state|
16
+ # # save action
17
+ # end
18
+ # kb.on "ArrowRight", context: :navigation do |state|
19
+ # # next slide
20
+ # end
21
+ # end
22
+ class KeyboardShortcuts < Base
23
+ attr_reader :bindings
24
+
25
+ # Suppression selectors -- keyboard shortcuts are ignored when
26
+ # the active element matches any of these.
27
+ SUPPRESSION_SELECTORS = %w[
28
+ input[type=text]
29
+ input[type=search]
30
+ input[type=email]
31
+ input[type=url]
32
+ input[type=number]
33
+ input:not([type])
34
+ textarea
35
+ [contenteditable=true]
36
+ .sw-mermaid--zoom
37
+ .sw-code-scroll
38
+ ].freeze
39
+
40
+ # @param options [Hash] Additional options
41
+ def initialize(**options)
42
+ @bindings = []
43
+ super(**options)
44
+ end
45
+
46
+ # Register a keyboard shortcut binding.
47
+ #
48
+ # @param key_combo [String] Key combination (e.g. "mod+s", "ArrowRight", "1..9")
49
+ # @param context [Symbol] Context for suppression (:global, :navigation, :selection)
50
+ # @param js_action [String, nil] JavaScript code to execute (alternative to block)
51
+ # @param block [Proc, nil] Server-side callback (stored but not executed client-side)
52
+ def on(key_combo, context: :global, js_action: nil, &block)
53
+ @bindings << {
54
+ key: key_combo,
55
+ context: context,
56
+ js_action: js_action,
57
+ callback: block
58
+ }
59
+ end
60
+
61
+ def render(view, state)
62
+ view.adapter.render_keyboard_shortcuts(view, self, state)
63
+ end
64
+
65
+ # Generate the JS registration calls for all bindings.
66
+ # Each binding becomes a call to swKeyboard.register(...)
67
+ #
68
+ # @return [String] JavaScript code
69
+ def to_js
70
+ lines = @bindings.map do |binding|
71
+ key = binding[:key]
72
+ context = binding[:context]
73
+ action = binding[:js_action] || "console.log('shortcut: #{key}')"
74
+
75
+ if key.include?("..")
76
+ # Range shortcut (e.g. "1..9")
77
+ range_start, range_end = key.split("..").map(&:to_i)
78
+ (range_start..range_end).map do |n|
79
+ "swKeyboard.register('#{n}', '#{context}', function(e) { #{action.gsub('KEY', n.to_s)} });"
80
+ end.join("\n")
81
+ else
82
+ "swKeyboard.register('#{key}', '#{context}', function(e) { #{action} });"
83
+ end
84
+ end
85
+ lines.join("\n")
86
+ end
87
+
88
+ # CSS class list (empty -- non-visual component)
89
+ def css_classes
90
+ ""
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StreamWeaver
4
+ module Components
5
+ # KPI Dashboard component.
6
+ # Renders an auto-fit grid of metric cards, each showing a large value,
7
+ # label, optional trend arrow, and optional color accent.
8
+ # Entry animation via CSS fadeIn.
9
+ #
10
+ # sw- CSS classes:
11
+ # sw-kpi-dashboard - outer grid container (auto-fit)
12
+ # sw-kpi-card - individual metric card
13
+ # sw-kpi-card--{color} - color accent variant
14
+ # sw-kpi-card__value - large value display
15
+ # sw-kpi-card__label - label below value
16
+ # sw-kpi-card__trend - trend arrow indicator
17
+ # sw-kpi-card__trend--up - green up arrow
18
+ # sw-kpi-card__trend--down - red down arrow
19
+ # sw-kpi-card__trend--flat - gray flat arrow
20
+ #
21
+ # @example
22
+ # kpi_dashboard metrics: [
23
+ # { value: "99.9%", label: "Uptime", color: :green, trend: :up },
24
+ # { value: "42ms", label: "Latency", trend: :down },
25
+ # { value: "1.2M", label: "Requests", trend: :flat }
26
+ # ]
27
+ class KpiDashboard < Base
28
+ attr_reader :metrics
29
+
30
+ # @param metrics [Array<Hash>] Array of metric hashes with :value, :label, :color (optional), :trend (optional)
31
+ # trend values: :up, :down, :flat, nil
32
+ # color values: :green, :blue, :red, :orange, :purple, nil
33
+ # @param options [Hash] Additional options
34
+ def initialize(metrics:, **options)
35
+ @metrics = metrics.map { |m| normalize_metric(m) }
36
+ super(**options)
37
+ end
38
+
39
+ def render(view, state)
40
+ view.adapter.render_kpi_dashboard(view, self, state)
41
+ end
42
+
43
+ # Trend arrow character for a metric
44
+ def trend_arrow(metric)
45
+ case metric[:trend]
46
+ when :up then "\u2191" # up arrow
47
+ when :down then "\u2193" # down arrow
48
+ when :flat then "\u2192" # right arrow
49
+ end
50
+ end
51
+
52
+ # CSS class for trend indicator
53
+ def trend_css_class(metric)
54
+ base = "sw-kpi-card__trend"
55
+ return base unless metric[:trend]
56
+ "#{base} sw-kpi-card__trend--#{metric[:trend]}"
57
+ end
58
+
59
+ # CSS class for the card (with optional color)
60
+ def card_css_class(metric)
61
+ classes = ["sw-kpi-card"]
62
+ classes << "sw-kpi-card--#{metric[:color]}" if metric[:color]
63
+ classes.join(" ")
64
+ end
65
+
66
+ private
67
+
68
+ def normalize_metric(m)
69
+ {
70
+ value: m[:value].to_s,
71
+ label: m[:label] || "",
72
+ color: m[:color] ? m[:color].to_sym : nil,
73
+ trend: m[:trend] ? m[:trend].to_sym : nil
74
+ }
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StreamWeaver
4
+ module Components
5
+ # Mermaid diagram component.
6
+ # Renders Mermaid.js diagrams with optional zoom/pan support,
7
+ # compact mode for card embedding, and theme-aware styling.
8
+ #
9
+ # CDN loading is lazy: Mermaid.js 11 (ESM) is only loaded when
10
+ # this component is present on the page.
11
+ #
12
+ # sw- CSS classes:
13
+ # sw-mermaid - outer container
14
+ # sw-mermaid--compact - compact mode for card embedding
15
+ # sw-mermaid--zoom - zoom/pan enabled
16
+ # sw-mermaid__diagram - the diagram rendering area
17
+ # sw-mermaid__controls - zoom control buttons
18
+ # sw-mermaid__btn - individual zoom button
19
+ #
20
+ # @example Basic usage
21
+ # mermaid("graph LR; A-->B")
22
+ #
23
+ # @example With zoom
24
+ # mermaid("graph TD; A-->B-->C", zoom: true)
25
+ #
26
+ # @example Compact for cards
27
+ # mermaid("graph LR; A-->B", compact: true)
28
+ #
29
+ # @example ELK layout
30
+ # mermaid("graph TD; A-->B", layout: :elk)
31
+ class Mermaid < Base
32
+ attr_reader :code, :zoom, :compact, :layout, :theme_vars
33
+
34
+ # @param code [String] Mermaid diagram definition
35
+ # @param zoom [Boolean] Enable zoom/pan controls (default: false)
36
+ # @param compact [Boolean] Compact mode for card embedding (default: false)
37
+ # @param layout [Symbol] Layout engine (:default or :elk)
38
+ # @param theme_vars [Hash, nil] Per-block Mermaid themeVariables overrides
39
+ # @param options [Hash] Additional HTML options
40
+ def initialize(code, zoom: false, compact: false, layout: :default, theme_vars: nil, **options)
41
+ @code = code
42
+ @zoom = zoom
43
+ @compact = compact
44
+ @layout = layout.to_sym
45
+ @theme_vars = theme_vars
46
+ super(**options)
47
+ end
48
+
49
+ def render(view, state)
50
+ view.adapter.render_mermaid(view, self, state)
51
+ end
52
+
53
+ # Whether ELK layout engine is requested
54
+ def elk?
55
+ @layout == :elk
56
+ end
57
+
58
+ # Generate a unique ID for this diagram instance
59
+ def diagram_id
60
+ @diagram_id ||= "sw-mermaid-#{object_id}"
61
+ end
62
+
63
+ # Serialize theme_vars to JSON for the JS initializer
64
+ def theme_vars_json
65
+ return "null" unless @theme_vars
66
+ require "json"
67
+ JSON.generate(@theme_vars)
68
+ end
69
+
70
+ # CSS class list for the outer container
71
+ def css_classes
72
+ classes = ["sw-mermaid"]
73
+ classes << "sw-mermaid--compact" if @compact
74
+ classes << "sw-mermaid--zoom" if @zoom
75
+ classes.join(" ")
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StreamWeaver
4
+ module Components
5
+ # Pipeline step flow visualization.
6
+ # Renders a horizontal sequence of steps with arrow connectors,
7
+ # color-coded by status: complete (green), active (blue), pending (gray).
8
+ # Responsive: collapses to vertical layout on narrow screens.
9
+ #
10
+ # sw- CSS classes:
11
+ # sw-pipeline - outer flexbox container
12
+ # sw-pipeline__step - individual step box
13
+ # sw-pipeline__step--complete - green (done)
14
+ # sw-pipeline__step--active - blue (in progress)
15
+ # sw-pipeline__step--pending - gray (not started)
16
+ # sw-pipeline__label - step label text
17
+ # sw-pipeline__desc - step description text
18
+ # sw-pipeline__arrow - CSS triangle arrow connector
19
+ #
20
+ # @example Basic
21
+ # pipeline steps: [
22
+ # { label: "Build", status: :complete },
23
+ # { label: "Test", status: :active },
24
+ # { label: "Deploy", status: :pending }
25
+ # ]
26
+ class Pipeline < Base
27
+ attr_reader :steps
28
+
29
+ # @param steps [Array<Hash>] Array of step hashes with :label, :description (optional), :status
30
+ # status values: :complete, :active, :pending
31
+ # @param options [Hash] Additional options
32
+ def initialize(steps:, **options)
33
+ @steps = steps.map { |s| normalize_step(s) }
34
+ super(**options)
35
+ end
36
+
37
+ def render(view, state)
38
+ view.adapter.render_pipeline(view, self, state)
39
+ end
40
+
41
+ # CSS class for a step based on its status
42
+ def step_css_class(step)
43
+ base = "sw-pipeline__step"
44
+ modifier = case step[:status]
45
+ when :complete then "sw-pipeline__step--complete"
46
+ when :active then "sw-pipeline__step--active"
47
+ else "sw-pipeline__step--pending"
48
+ end
49
+ "#{base} #{modifier}"
50
+ end
51
+
52
+ private
53
+
54
+ def normalize_step(step)
55
+ {
56
+ label: step[:label] || "Step",
57
+ description: step[:description],
58
+ status: (step[:status] || :pending).to_sym
59
+ }
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StreamWeaver
4
+ module Components
5
+ # Sticky sidebar table-of-contents with IntersectionObserver scroll spy.
6
+ #
7
+ # Desktop (>=1000px): sticky 170px sidebar with active-section highlighting.
8
+ # Mobile (<1000px): horizontal scrollable sticky bar at the top.
9
+ #
10
+ # Sections are an array of hashes, each with :id and :label keys.
11
+ # The :id corresponds to the DOM id of the target section element.
12
+ #
13
+ # @example
14
+ # sidebar_toc sections: [
15
+ # { id: "summary", label: "Executive Summary" },
16
+ # { id: "architecture", label: "Architecture" },
17
+ # { id: "risks", label: "Risks" }
18
+ # ]
19
+ class SidebarToc < Base
20
+ attr_reader :sections
21
+
22
+ # @param sections [Array<Hash>] Array of { id:, label: } hashes
23
+ # @param options [Hash] Additional options
24
+ def initialize(sections:, **options)
25
+ @sections = normalize_sections(sections)
26
+ @options = options
27
+ end
28
+
29
+ def render(view, state)
30
+ view.adapter.render_sidebar_toc(view, self, state)
31
+ end
32
+
33
+ private
34
+
35
+ # Normalize sections to ensure string keys become symbols
36
+ def normalize_sections(sections)
37
+ sections.map do |s|
38
+ {
39
+ id: (s[:id] || s["id"]).to_s,
40
+ label: (s[:label] || s["label"]).to_s
41
+ }
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,141 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StreamWeaver
4
+ module Components
5
+ # Slide within a SlideContainer.
6
+ # Contains child components rendered as slide content.
7
+ #
8
+ # sw- CSS classes:
9
+ # sw-slide - individual slide wrapper
10
+ # sw-slide--active - currently visible slide (swap mode)
11
+ # sw-slide--title - title slide type
12
+ # sw-slide--content - content slide type
13
+ #
14
+ # @example
15
+ # slide "intro", "Introduction" do
16
+ # text "Welcome to the presentation"
17
+ # end
18
+ class Slide < Base
19
+ attr_reader :id, :title, :type
20
+ attr_accessor :children
21
+
22
+ # @param id [String] Unique identifier for this slide
23
+ # @param title [String, nil] Optional slide title
24
+ # @param type [Symbol] Slide type (:content, :title)
25
+ # @param options [Hash] Additional HTML options
26
+ def initialize(id, title = nil, type: :content, **options)
27
+ @id = id.to_s
28
+ @title = title
29
+ @type = type.to_sym
30
+ @children = []
31
+ super(**options)
32
+ end
33
+
34
+ def render(view, state)
35
+ view.adapter.render_slide(view, self, state)
36
+ end
37
+
38
+ # CSS class list
39
+ def css_classes
40
+ classes = ["sw-slide"]
41
+ classes << "sw-slide--#{@type}"
42
+ classes.join(" ")
43
+ end
44
+ end
45
+
46
+ # Container for navigable slides.
47
+ # Supports two modes:
48
+ # :swap -- one slide visible at a time, Back/Next buttons, fade transition (deck)
49
+ # :scroll_snap -- all slides rendered, CSS scroll-snap-type: y mandatory (explainer)
50
+ #
51
+ # Both modes share: keyboard navigation, progress tracking, Alpine.js x-data for current index.
52
+ #
53
+ # sw- CSS classes:
54
+ # sw-slide-container - outer container
55
+ # sw-slide-container--swap - swap mode
56
+ # sw-slide-container--scroll-snap - scroll-snap mode
57
+ # sw-slide-nav - navigation buttons wrapper
58
+ # sw-slide-nav__btn - Back/Next buttons
59
+ # sw-slide-nav__btn--prev - Back button modifier
60
+ # sw-slide-nav__btn--next - Next button modifier
61
+ # sw-slide-progress - progress bar
62
+ # sw-slide-progress--fixed - fixed at top of viewport
63
+ # sw-slide-progress__bar - progress bar fill
64
+ # sw-slide-dots - navigation dots container
65
+ # sw-slide-dots__dot - individual dot
66
+ # sw-slide-dots__dot--active - active dot
67
+ # sw-slide-counter - slide counter text (e.g. "2 / 5")
68
+ #
69
+ # @example Swap mode (deck)
70
+ # slide_container mode: :swap, progress_bar: true do
71
+ # slide "intro", "Introduction" do
72
+ # text "Welcome"
73
+ # end
74
+ # slide "arch", "Architecture" do
75
+ # text "System design"
76
+ # end
77
+ # end
78
+ #
79
+ # @example Scroll-snap mode (explainer)
80
+ # slide_container mode: :scroll_snap, nav_dots: true, counter: true do
81
+ # slide "title", type: :title do
82
+ # header1 "Title"
83
+ # end
84
+ # slide "content1" do
85
+ # text "Content..."
86
+ # end
87
+ # end
88
+ class SlideContainer < Base
89
+ attr_reader :mode, :progress_bar, :nav_dots, :counter, :keyboard_nav
90
+ attr_accessor :children
91
+
92
+ # @param mode [Symbol] Display mode (:swap or :scroll_snap)
93
+ # @param progress_bar [Boolean] Show progress bar (default: true)
94
+ # @param keyboard_nav [Boolean] Enable arrow key navigation (default: true)
95
+ # @param nav_dots [Boolean] Show navigation dots (default: false)
96
+ # @param counter [Boolean] Show slide counter (default: false)
97
+ # @param options [Hash] Additional options
98
+ def initialize(mode: :swap, progress_bar: true, keyboard_nav: true,
99
+ nav_dots: false, counter: false, **options)
100
+ @mode = mode.to_sym
101
+ @progress_bar = progress_bar
102
+ @keyboard_nav = keyboard_nav
103
+ @nav_dots = nav_dots
104
+ @counter = counter
105
+ @children = []
106
+ super(**options)
107
+ end
108
+
109
+ def render(view, state)
110
+ view.adapter.render_slide_container(view, self, state)
111
+ end
112
+
113
+ # Number of slides
114
+ def slide_count
115
+ @children.length
116
+ end
117
+
118
+ # Whether swap mode
119
+ def swap?
120
+ @mode == :swap
121
+ end
122
+
123
+ # Whether scroll-snap mode
124
+ def scroll_snap?
125
+ @mode == :scroll_snap
126
+ end
127
+
128
+ # Generate a unique container ID
129
+ def container_id
130
+ @container_id ||= "sw-slides-#{object_id}"
131
+ end
132
+
133
+ # CSS class list
134
+ def css_classes
135
+ classes = ["sw-slide-container"]
136
+ classes << "sw-slide-container--#{@mode.to_s.tr('_', '-')}"
137
+ classes.join(" ")
138
+ end
139
+ end
140
+ end
141
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StreamWeaver
4
+ module Components
5
+ # Timeline event row with colored type badge and expandable detail fields.
6
+ # Designed to replicate the DTO Run Viewer event log style:
7
+ # compact row with index, type badge, timestamp, label — click to expand key-value details.
8
+ #
9
+ # Event types map to distinct colors:
10
+ # phase → cyan (#00bcd4)
11
+ # snapshot → yellow (#ffc107)
12
+ # intervention → purple (#ce93d8)
13
+ # timeout → red (#ef5350)
14
+ # guard → red (#ef5350)
15
+ # final → green (#66bb6a)
16
+ #
17
+ # sw- CSS classes:
18
+ # sw-timeline-event - outer row container
19
+ # sw-timeline-event--{type} - type-specific border + badge colors
20
+ # sw-timeline-event__idx - index number (right-aligned)
21
+ # sw-timeline-event__badge - type badge pill
22
+ # sw-timeline-event__ts - timestamp
23
+ # sw-timeline-event__label - summary label
24
+ # sw-timeline-event__detail - expandable detail section
25
+ # sw-timeline-event__field - single key-value pair
26
+ # sw-timeline-event__field-key - field label (bold)
27
+ # sw-timeline-event__field-value - field value (may contain pre block)
28
+ #
29
+ # @example
30
+ # timeline_event index: 0, event_type: :phase, timestamp: "10:00:00",
31
+ # label: "launch", fields: { run_id: "abc-123", phase: "launch" }
32
+ class TimelineEvent < Base
33
+ attr_reader :index, :event_type, :timestamp, :label, :fields, :expanded
34
+
35
+ TYPES = %i[phase snapshot intervention timeout guard final].freeze
36
+
37
+ # @param index [Integer] Event sequence number
38
+ # @param event_type [Symbol] One of :phase, :snapshot, :intervention, :timeout, :guard, :final
39
+ # @param timestamp [String] Time display (e.g., "10:00:00")
40
+ # @param label [String] Summary label for the event row
41
+ # @param fields [Hash] Key-value pairs shown in the expanded detail section
42
+ # @param expanded [Boolean] Whether to start expanded (default: false)
43
+ def initialize(index:, event_type:, timestamp:, label:, fields: {}, expanded: false, **options)
44
+ @index = index
45
+ @event_type = TYPES.include?(event_type.to_sym) ? event_type.to_sym : :phase
46
+ @timestamp = timestamp
47
+ @label = label
48
+ @fields = fields
49
+ @expanded = expanded
50
+ super(**options)
51
+ end
52
+
53
+ def render(view, state)
54
+ view.adapter.render_timeline_event(view, self, state)
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StreamWeaver
4
+ module Components
5
+ # HTML mockup wrapped in device chrome matching the surface type.
6
+ # Renders the author's HTML fragment inside a visual device frame —
7
+ # browser window, phone bezel, desktop window, or compact floating frame.
8
+ #
9
+ # @example
10
+ # wireframe(surface: :browser) do
11
+ # "<h1>Login</h1><button class=\"primary\">Sign in</button>"
12
+ # end
13
+ class Wireframe < Base
14
+ SURFACES = %w[browser desktop mobile phone tablet popover card widget panel].freeze
15
+
16
+ attr_reader :html, :surface
17
+
18
+ def initialize(html: "", surface: :browser, **options)
19
+ @html = html
20
+ @surface = SURFACES.include?(surface.to_s) ? surface.to_s : "browser"
21
+ @options = options
22
+ end
23
+
24
+ def render(view, state)
25
+ view.adapter.render_wireframe(view, self, state)
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StreamWeaver
4
+ module Components
5
+ # A wireframe surface block that renders raw HTML as a hand-drawn mockup.
6
+ # Provides scoped --wf-* CSS tokens and helper classes within .sw-wireframe-surface.
7
+ #
8
+ # All styling is intentionally scoped to .sw-wireframe-surface so wireframe
9
+ # aesthetics cannot leak into the surrounding app UI.
10
+ #
11
+ # CSS tokens and helper classes are injected inline by the adapter (same pattern
12
+ # as other canvas components). See AlpineJS#wireframe_css for the full token set.
13
+ #
14
+ # @example
15
+ # wireframe_block(html: "<h1>Login</h1><button class=\"primary\">Sign in</button>")
16
+ class WireframeBlock < Base
17
+ SURFACES = %w[browser desktop mobile popover panel].freeze
18
+
19
+ attr_reader :html, :surface
20
+
21
+ # @param html [String] Raw HTML fragment to display inside the wireframe surface
22
+ # @param surface [String] Surface type: browser, desktop, mobile, popover, panel
23
+ def initialize(html: "", surface: "browser", **options)
24
+ @html = html
25
+ @surface = SURFACES.include?(surface) ? surface : "browser"
26
+ @options = options
27
+ end
28
+
29
+ def render(view, state)
30
+ view.adapter.render_wireframe_block(view, self, state)
31
+ end
32
+ end
33
+ end
34
+ end