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,1223 @@
1
+ # StreamWeaver Components Reference
2
+
3
+ Complete reference for all StreamWeaver components. For quick usage, see the main `llms.txt`.
4
+
5
+ > Building a doc that also needs to render in `canvas-read` or a `streamweaver export`? Not every component below is fully interactive outside the live canvas — see [`docs/frontend-only.md`](frontend-only.md) for what survives each context and why.
6
+
7
+ ## Text Display
8
+
9
+ ```ruby
10
+ text "Plain paragraph" # Literal text - what you type is what you get
11
+ text "Value: #{state[:value]}" # String interpolation works
12
+ ```
13
+
14
+ > **WARNING for LLMs:** `text` renders its argument as a literal string. Markdown syntax like `**bold**` or `## heading` will appear as raw characters on screen — not as formatted HTML. Use `md` for markdown, or `header*` for section labels.
15
+
16
+ ```ruby
17
+ # WRONG — asterisks show up literally on screen
18
+ text "**Select the article PDF:**"
19
+
20
+ # CORRECT — use md for inline markdown formatting
21
+ md "**Select the article PDF:**"
22
+
23
+ # BETTER — use semantic headers for labels and section titles
24
+ header3 "Select the article PDF:"
25
+ ```
26
+
27
+ ## Headers
28
+
29
+ ```ruby
30
+ header "Section Title" # <h2> - default header level
31
+ header1 "Page Title" # <h1>
32
+ header2 "Section" # <h2>
33
+ header3 "Subsection" # <h3>
34
+ header4 "Minor Section" # <h4>
35
+ header5 "Sub-subsection" # <h5>
36
+ header6 "Smallest" # <h6>
37
+ ```
38
+
39
+ ## Markdown Content
40
+
41
+ ```ruby
42
+ md "**Bold**, *italic*, and `code`" # Full markdown parsing
43
+ md "## Headers work too" # Headers within markdown blocks
44
+ markdown "Same as md" # Alias for md
45
+
46
+ # Supported markdown:
47
+ # - **bold** → <strong>
48
+ # - *italic* → <em>
49
+ # - `code` → <code>
50
+ # - [link](url) → <a href="url">link</a>
51
+ # - ## headers → <h2>
52
+ ```
53
+
54
+ ## Text Input
55
+
56
+ ```ruby
57
+ text_field :key, placeholder: "Hint text"
58
+ text_area :key, placeholder: "Multi-line", rows: 5
59
+
60
+ # Disable auto-submit (for display-only or manual submission)
61
+ text_field :key, placeholder: "Edit me", submit: false
62
+ ```
63
+
64
+ ## Dates
65
+
66
+ ```ruby
67
+ date_field :due_on, label: "Due date"
68
+
69
+ # Constrain the selectable range (both are ISO 8601 strings)
70
+ date_field :due_on, min: "2026-01-01", max: "2027-12-31"
71
+
72
+ # Disable auto-submit
73
+ date_field :due_on, submit: false
74
+ ```
75
+
76
+ Renders a native `<input type="date">` — no custom JS, no bundled calendar
77
+ widget. The browser supplies the calendar popup and, on mobile, its own
78
+ native date picker. Works the same as `text_field` inside `form`/`scope`
79
+ blocks.
80
+
81
+ State always holds an **ISO 8601 string** (`"YYYY-MM-DD"`), never a `Date`
82
+ object — coerce it yourself when you need one:
83
+
84
+ ```ruby
85
+ if (parsed = StreamWeaver::Components::DateField.to_date(state[:due_on]))
86
+ text "Parsed as a Date: #{parsed.iso8601}"
87
+ end
88
+ ```
89
+
90
+ `DateField.to_date` returns `nil` for blank or unparsable input instead of
91
+ raising.
92
+
93
+ ## Selection
94
+
95
+ ```ruby
96
+ # Dropdown
97
+ select :color, ["Red", "Green", "Blue"]
98
+
99
+ # Dropdown with default value
100
+ select :priority, ["Low", "Medium", "High"], default: "Medium"
101
+
102
+ # Radio buttons (all options visible)
103
+ radio_group :answer, ["Option A", "Option B", "Option C"]
104
+
105
+ # Disable auto-submit
106
+ select :role, ["Admin", "User"], submit: false
107
+ ```
108
+
109
+ ## Boolean
110
+
111
+ ```ruby
112
+ checkbox :agree, "I accept the terms"
113
+ # state[:agree] will be true/false
114
+
115
+ # Disable auto-submit
116
+ checkbox :preview_flag, "Show preview", submit: false
117
+ ```
118
+
119
+ ## Checkbox Group (Multi-Select with Select All/None)
120
+
121
+ ```ruby
122
+ # For batch selection (e.g., emails, files, items)
123
+ checkbox_group :selected_items, select_all: "Select All", select_none: "Clear" do
124
+ items.each do |item|
125
+ item item.id do
126
+ text item.name
127
+ # Any components can be nested here
128
+ end
129
+ end
130
+ end
131
+ # state[:selected_items] = ["id1", "id3", ...] (array of selected values)
132
+ ```
133
+
134
+ ## Buttons
135
+
136
+ ```ruby
137
+ button "Primary" do |state|
138
+ # Action when clicked
139
+ state[:clicked] = true
140
+ end
141
+
142
+ button "Secondary", style: :secondary do |state|
143
+ # Secondary styling
144
+ end
145
+
146
+ # Display-only button (no server request, for previews)
147
+ button "Preview Button", submit: false
148
+ button "Cancel", style: :secondary, submit: false
149
+
150
+ # Stable identity for buttons in loops
151
+ voices.each do |v|
152
+ button "Test", key: v[:name] do |state|
153
+ speak(v[:name]) # Each button triggers its own callback
154
+ end
155
+ end
156
+
157
+ # Verbatim id when other code targets the element by selector
158
+ button "Delete", id: "delete-voice-#{v[:name]}" do |state| ... end
159
+ ```
160
+
161
+ **Identity precedence: `id:` > `key:` > auto-derivation.** Auto-derived ids hash
162
+ the label plus the block's source location, so loop iterations derive the same
163
+ id — StreamWeaver auto-disambiguates each repeat occurrence (`-dup-N`) and warns
164
+ once, so every button dispatches its own callback with no author intervention.
165
+ Those suffixes are position-stable, not content-stable: inserting or deleting an
166
+ earlier item shifts the later ones. Pass `key: record_id` (String/Symbol/Integer
167
+ only — anything else raises) when identity must survive reordering or deletion,
168
+ and `id:` when you need the emitted DOM id verbatim. `strict_ids: true` (per app,
169
+ `StreamWeaver.strict_ids = true` globally, or `SW_STRICT_IDS=1`) turns the
170
+ warning into a raise in dev/test — production still only warns. Full write-up in
171
+ `llms.txt` under "Interactive IDs and keying".
172
+
173
+ ## Layout Containers
174
+
175
+ ```ruby
176
+ # Generic div
177
+ div class: "my-class" do
178
+ text "Nested content"
179
+ text_field :nested_field
180
+ end
181
+
182
+ # Card (styled container)
183
+ card do
184
+ header3 "Card Title"
185
+ text "Card content here"
186
+ end
187
+
188
+ # Structured card with header, body, footer
189
+ card do
190
+ card_header "Card Title"
191
+ card_body do
192
+ text "Main content goes here"
193
+ text_field :field
194
+ end
195
+ card_footer do
196
+ button "Cancel", style: :secondary
197
+ button "Save"
198
+ end
199
+ end
200
+
201
+ # Card header with badge + right-aligned meta (flex row: badge | title | meta)
202
+ card do
203
+ card_header "C1 — Title", badge: "C1", meta: "scheduler secretary"
204
+ card_body do
205
+ text "Main content goes here"
206
+ end
207
+ end
208
+ ```
209
+
210
+ ## Stacking Layouts (VStack / HStack)
211
+
212
+ ```ruby
213
+ # Vertical stack with spacing
214
+ vstack spacing: :lg do
215
+ text "Item 1"
216
+ text "Item 2"
217
+ text "Item 3"
218
+ end
219
+
220
+ # VStack with dividers between items
221
+ vstack spacing: :md, divider: true do
222
+ text "Item with divider below"
223
+ text "Another item"
224
+ end
225
+
226
+ # Horizontal stack
227
+ hstack spacing: :md, align: :center do
228
+ button "Action 1"
229
+ button "Action 2", style: :secondary
230
+ end
231
+
232
+ # HStack with justify (spread content)
233
+ hstack justify: :between do
234
+ text "Left"
235
+ text "Right"
236
+ end
237
+ ```
238
+
239
+ Spacing options: `:xs`, `:sm`, `:md`, `:lg`, `:xl`
240
+ Align options: `:start`, `:center`, `:end`
241
+ Justify options (hstack only): `:start`, `:center`, `:end`, `:between`, `:around`
242
+
243
+ ## Grid Layouts
244
+
245
+ ```ruby
246
+ # Fixed 3-column grid
247
+ grid columns: 3, gap: :md do
248
+ card { text "Item 1" }
249
+ card { text "Item 2" }
250
+ card { text "Item 3" }
251
+ end
252
+
253
+ # Responsive grid: 1 col mobile, 2 tablet, 3 desktop
254
+ grid columns: [1, 2, 3], gap: :lg do
255
+ items.each do |item|
256
+ card { text item.name }
257
+ end
258
+ end
259
+ ```
260
+
261
+ ## Columns (Multi-Column Layout)
262
+
263
+ ```ruby
264
+ # Equal-width columns (default)
265
+ columns do
266
+ column do
267
+ text "Left column"
268
+ end
269
+ column do
270
+ text "Right column"
271
+ end
272
+ end
273
+
274
+ # Custom widths (sidebar + main content)
275
+ columns widths: ['30%', '70%'] do
276
+ column do
277
+ header4 "Sidebar"
278
+ text "Navigation here"
279
+ end
280
+ column do
281
+ header4 "Main Content"
282
+ text "Primary content here"
283
+ end
284
+ end
285
+
286
+ # Custom gap between columns
287
+ columns gap: "2rem" do
288
+ column { text "More space" }
289
+ column { text "Between columns" }
290
+ end
291
+ ```
292
+
293
+ Columns stack vertically on mobile (< 768px) for responsive design.
294
+
295
+ ## Collapsible Sections
296
+
297
+ ```ruby
298
+ # Collapsed by default
299
+ collapsible "Show Details" do
300
+ text "Hidden content revealed on click"
301
+ text "Can contain any components"
302
+ end
303
+
304
+ # Start expanded
305
+ collapsible "View Context", expanded: true do
306
+ text "This content is visible initially"
307
+ end
308
+
309
+ # With a subtitle and badge in the header
310
+ collapsible "Activity Log", subtitle: "Last 30 days", badge_text: "12 new", badge_variant: :success do
311
+ text "Recent activity details"
312
+ end
313
+ ```
314
+
315
+ `collapsible` is client-side only — the server never learns the expanded state. If you need the server to know whether a section is expanded (e.g. to bind it to `state`), use `expandable_card` instead.
316
+
317
+ ## Form Blocks (Deferred Submission)
318
+
319
+ Group multiple form elements for batch editing. Fields use client-side only state until submit.
320
+
321
+ ```ruby
322
+ form :edit_person do
323
+ text_field :name, placeholder: 'Name'
324
+ select :status, %w[active paused archived]
325
+ text_area :notes, placeholder: 'Notes...', rows: 3
326
+
327
+ submit 'Save' do |form_values|
328
+ # form_values = { name: "...", status: "...", notes: "..." }
329
+ # state[:edit_person] already updated at this point
330
+ api.save_person(form_values) # optional side effects
331
+ end
332
+
333
+ cancel 'Cancel' # resets to original values, no server request
334
+ end
335
+ ```
336
+
337
+ **Key behaviors:**
338
+ - State stored as nested hash: `state[:edit_person][:name]` (Rails-style)
339
+ - Form reads initial values from `state[:form_name]`
340
+ - On submit: state auto-updates, THEN submit block runs
341
+ - On cancel: Alpine.js resets to original values (client-side)
342
+
343
+ ## Tabs
344
+
345
+ ```ruby
346
+ tabs :settings_tabs do
347
+ tab "General" do
348
+ text_field :app_name
349
+ checkbox :dark_mode, "Enable dark mode"
350
+ end
351
+
352
+ tab "Notifications" do
353
+ checkbox :email_notifications, "Email notifications"
354
+ end
355
+ end
356
+ # state[:settings_tabs] = 0 (active tab index)
357
+ ```
358
+
359
+ Tab variants:
360
+ ```ruby
361
+ tabs :demo, variant: :line do ... end # Underline (default)
362
+ tabs :demo, variant: :enclosed do ... end # Boxed tabs
363
+ tabs :demo, variant: :"soft-rounded" do ... end # Pill-style
364
+ ```
365
+
366
+ An out-of-range index (a stale session value, or a group that shrank) renders tab 0
367
+ rather than blanking every panel.
368
+
369
+ **Deprecated: `tabs :key, lazy: true`.** Lazy tabs defer evaluating inactive panels and
370
+ fetch the newly-active one with a server round-trip, which a canvas page has no route
371
+ for — inactive panels there stay empty forever (`stream_weaver-pkh`). The mode still
372
+ works exactly as before and warns once per process. Migrate to route tabs (`url: true`)
373
+ below; a lazy route-tab mode will replace this one.
374
+
375
+ ### Route tabs (`url: true`)
376
+
377
+ ```ruby
378
+ tabs :view, url: true do
379
+ tab("Overview") { text "..." }
380
+ tab("Findings") { text "..." }
381
+ end
382
+ # active tab lives in ?view=<index> — bookmarkable, back/forward-aware
383
+ ```
384
+
385
+ Switching tabs is client-side (History API `pushState`/`popstate`) — zero requests.
386
+ Multiple `url: true` groups compose, one param per key: `/?view=2&panel=1`.
387
+
388
+ **The URL is authoritative on a full GET.** Param present and valid → that index.
389
+ Absent or invalid → tab 0, *never* the session value. The same URL always renders the
390
+ same tabs.
391
+
392
+ Invalid values degrade instead of erroring — `?view=999`, `?view=abc`, and `?view[]=`
393
+ all return 200 with tab 0 active. Integer strings (`?view=2`) are valid deep links.
394
+ Build-time `ArgumentError`s: a reserved request param as the key (`app_id`, `splat`,
395
+ `captures`, `button_id`), two groups claiming the same key, or `url: true` with
396
+ `lazy: true`.
397
+
398
+ **Gotcha — read `state[:view]` *below* the declaration.** Above it, the key still holds
399
+ the raw pre-authority value (the param string, or a stale session index); the resolved
400
+ integer only exists once the group has applied URL authority:
401
+
402
+ ```ruby
403
+ tabs :view, url: true do ... end
404
+ md "Active view: #{state[:view]}" # resolved Integer
405
+ ```
406
+
407
+ **Gotcha — a route-tab group cannot be server-side preset.** `state[:view] ||= 1` above
408
+ the declaration is overwritten by URL authority (tab 0 on a bare GET). That is the
409
+ authority rule working as designed; use a deep link (`/?view=1`) instead.
410
+
411
+ On canvas pages `url:` is ignored — the group renders as plain client-side tabs and one
412
+ warning is logged per render pass, since a canvas page has no app URL to carry the tab.
413
+
414
+ See `examples/layout/route_tabs_demo.rb` for a two-group demo that exercises both
415
+ gotchas.
416
+
417
+ ## Breadcrumbs
418
+
419
+ ```ruby
420
+ breadcrumbs do
421
+ crumb "Home", href: "/"
422
+ crumb "Products", href: "/products"
423
+ crumb "Current" # No href = current page
424
+ end
425
+
426
+ # Custom separator
427
+ breadcrumbs separator: ">" do
428
+ crumb "Dashboard", href: "/dashboard"
429
+ crumb "Settings"
430
+ end
431
+ ```
432
+
433
+ ## Navbar
434
+
435
+ Cross-app horizontal navigation bar. Active item renders as bold text; others as links.
436
+
437
+ ```ruby
438
+ navbar do
439
+ nav_item "Home", href: "/", active: true # current page — bold, non-clickable
440
+ nav_item "Settings", href: "/settings" # link
441
+ nav_item "Help", href: "/help"
442
+ end
443
+ ```
444
+
445
+ ## Link
446
+
447
+ Inline anchor element.
448
+
449
+ ```ruby
450
+ link_to "Visit docs", href: "https://example.com"
451
+ link_to "Internal page", href: "/dashboard"
452
+ ```
453
+
454
+ ## Dropdown Menu
455
+
456
+ ```ruby
457
+ dropdown do
458
+ trigger do
459
+ button "Actions"
460
+ end
461
+
462
+ menu do
463
+ menu_item "Edit" do |s|
464
+ s[:editing] = true
465
+ end
466
+ menu_item "Duplicate" do |s|
467
+ s[:items] << s[:items].last.dup
468
+ end
469
+ menu_divider
470
+ menu_item "Delete", style: :destructive do |s|
471
+ s[:items].pop
472
+ end
473
+ end
474
+ end
475
+ ```
476
+
477
+ ## Modal Dialogs
478
+
479
+ ```ruby
480
+ # Open modal via button
481
+ button "Open Settings" do |s|
482
+ s[:settings_open] = true
483
+ end
484
+
485
+ # Define modal (state key is :{name}_open)
486
+ modal :settings, title: "Settings", size: :md do
487
+ text_field :setting_value
488
+ text "Configure your preferences here."
489
+
490
+ modal_footer do
491
+ button "Cancel", style: :secondary do |s|
492
+ s[:settings_open] = false
493
+ end
494
+ button "Save" do |s|
495
+ # save logic
496
+ s[:settings_open] = false
497
+ end
498
+ end
499
+ end
500
+ ```
501
+
502
+ Modal sizes: `:sm` (400px), `:md` (560px default), `:lg` (800px), `:xl` (1140px)
503
+
504
+ Close methods: click backdrop, press Escape, or button action setting `s[:modal_key_open] = false`
505
+
506
+ ## Status Badge
507
+
508
+ ```ruby
509
+ status_badge :strong, "Perfect match"
510
+ status_badge :maybe, "Good fit, but..."
511
+ status_badge :skip, "Not recommended"
512
+ ```
513
+
514
+ Renders visual indicators:
515
+ - Strong = green background
516
+ - Maybe = yellow background
517
+ - Skip = red background
518
+
519
+ ## Tag Buttons
520
+
521
+ ```ruby
522
+ # Default style
523
+ tag_buttons :category, ["Fiction", "Non-fiction", "Mystery"]
524
+
525
+ # Destructive style
526
+ tag_buttons :eliminate_reason, ["Too dark", "Wrong genre"], style: :destructive
527
+ # state[:eliminate_reason] = "too_dark" (normalized)
528
+ ```
529
+
530
+ Single-select: clicking a tag selects it and deselects others.
531
+
532
+ ## External Link Button
533
+
534
+ ```ruby
535
+ # Just open link
536
+ external_link_button "View on Amazon", url: "https://amazon.com/dp/B0XXX"
537
+
538
+ # Submit form AND open link (for agentic mode)
539
+ external_link_button "Get it!", url: book[:amazon_url], submit: true
540
+ ```
541
+
542
+ ## Copy Button
543
+
544
+ Client-side clipboard-copy trigger for a fixed text payload -- no server round-trip.
545
+
546
+ ```ruby
547
+ copy_button "Copy summary", text: report_text
548
+
549
+ # Custom label shown briefly after a successful copy
550
+ copy_button "Copy", text: api_key, copied_label: "Copied to clipboard!"
551
+ ```
552
+
553
+ Uses `navigator.clipboard` on secure origins (HTTPS or localhost) and automatically
554
+ falls back to a hidden-textarea + `execCommand('copy')` approach otherwise -- this
555
+ matters when StreamWeaver is served over plain HTTP to a LAN IP, where
556
+ `navigator.clipboard` is unavailable because the page isn't a secure context.
557
+
558
+ ## Code Block
559
+
560
+ Syntax-highlighted code display.
561
+
562
+ ```ruby
563
+ code_block "def hello\n puts 'hi'\nend", lang: "ruby"
564
+
565
+ # With a copy affordance in the header (off by default)
566
+ code_block source_code, lang: "ruby", copy: true
567
+
568
+ # With a filename shown in the header
569
+ code_block source_code, lang: "ruby", file: "app.rb"
570
+ ```
571
+
572
+ `copy:` reuses the same clipboard mechanism as `copy_button` -- the header gains
573
+ a small Copy button that always copies the full code text, even if the block is
574
+ truncated for display via `truncate:`.
575
+
576
+ ## Table
577
+
578
+ Display tabular data with smart data inference, formatters, and interactive features.
579
+
580
+ ### Basic Usage
581
+
582
+ ```ruby
583
+ # Original API - explicit headers and rows
584
+ table headers: ["Name", "Size"], rows: [["app.rb", "12kb"], ["cli.rb", "8kb"]]
585
+
586
+ # Array of hashes - headers auto-inferred from keys
587
+ table [
588
+ { name: "Alice", age: 30, role: "Engineer" },
589
+ { name: "Bob", age: 25, role: "Designer" }
590
+ ]
591
+ # Headers become: "Name", "Age", "Role" (titleized keys)
592
+
593
+ # Hash of arrays - keys become columns
594
+ table({ name: ["Alice", "Bob"], age: [30, 25] })
595
+ ```
596
+
597
+ ### Data Sources
598
+
599
+ ```ruby
600
+ # State binding - reads from state[:users]
601
+ table data: :users
602
+
603
+ # File loading (YAML or JSON)
604
+ table file: "data/users.yaml"
605
+ table file: "data.json", path: "results.users"
606
+
607
+ # Transform block for file data
608
+ table file: "raw.yaml" do |data|
609
+ data.map { |r| { name: r[:n], value: r[:v] } }
610
+ end
611
+ ```
612
+
613
+ ### Column DSL with Formatters
614
+
615
+ ```ruby
616
+ table users do
617
+ column :name
618
+ column :email, header: "E-mail"
619
+ column :balance, format: :currency, align: :right
620
+ column :joined, format: :date
621
+ column(:active) { |u| u.active ? "Yes" : "No" } # Computed column
622
+ end
623
+ ```
624
+
625
+ **Built-in formatters:**
626
+
627
+ | Format | Example Input | Output |
628
+ |--------|---------------|--------|
629
+ | `:date` | `Date.today` | "Jan 20, 2026" |
630
+ | `:datetime` | `Time.now` | "Jan 20, 2026 3:30 PM" |
631
+ | `:currency` | `1234.56` | "$1,234.56" |
632
+ | `:number` | `1234567` | "1,234,567" |
633
+ | `:percent` | `0.42` | "42%" |
634
+
635
+ Custom formatter with Proc:
636
+ ```ruby
637
+ column :balance, format: ->(v) { v > 1000 ? "#{(v/1000.0).round(1)}k" : v.to_s }
638
+ ```
639
+
640
+ ### Interactive Features
641
+
642
+ ```ruby
643
+ # Sortable - click headers to sort (handles text and numbers)
644
+ table data, sortable: true
645
+
646
+ # Sticky header - header stays visible when scrolling
647
+ table data, sticky_header: true
648
+
649
+ # Combined
650
+ table data, sortable: true, sticky_header: true, striped: true
651
+ ```
652
+
653
+ ### Markdown Links in Cells
654
+
655
+ ```ruby
656
+ # Enable markdown link parsing with markdown: true
657
+ table [
658
+ { issue: "[JIRA-123](https://jira.example.com/browse/JIRA-123)", status: "Open" },
659
+ { issue: "[JIRA-456](https://jira.example.com/browse/JIRA-456)", status: "Closed" }
660
+ ], markdown: true
661
+
662
+ # Default (markdown: false) shows literal text: "[text](url)"
663
+ ```
664
+
665
+ ### Styling Options
666
+
667
+ ```ruby
668
+ table data,
669
+ striped: true, # Alternate row colors
670
+ bordered: true, # Cell borders
671
+ hoverable: true, # Highlight rows on hover (default: true)
672
+ compact: true, # Reduced padding
673
+ sortable: true, # Client-side sorting
674
+ sticky_header: true, # Header stays visible on scroll
675
+ markdown: true, # Parse [text](url) as clickable links
676
+ caption: "Title" # Table caption above
677
+ ```
678
+
679
+ ### Cell Style Escape Hatches
680
+
681
+ By default, the first column renders in an accent monospace style (legacy behavior, unchanged). To control this per-cell:
682
+
683
+ ```ruby
684
+ # Column DSL: style: accepts a static String or a per-row Proc
685
+ table users do
686
+ column :name, style: "font-weight: 600;"
687
+ column :balance, style: ->(u) { u.balance.negative? ? "color: red;" : nil }
688
+
689
+ # id_style: overrides the default first-column accent styling, true/false, on any column
690
+ column :id, id_style: false # never accent this column
691
+ column :sku, id_style: true # always accent this column, regardless of position
692
+ end
693
+
694
+ # Raw headers:/rows: tables: id_column: picks which column (by index) gets the
695
+ # accent styling instead of the column-0 default, or disables it entirely
696
+ table headers: ["SKU", "Name"], rows: [...], id_column: 0 # explicit column 0
697
+ table headers: ["SKU", "Name"], rows: [...], id_column: false # no accent anywhere
698
+ ```
699
+
700
+ `style:` is appended after any built-in styling (including the accent styling above), so it always wins on conflicting CSS properties without needing `!important`.
701
+
702
+ ## Score Table
703
+
704
+ ```ruby
705
+ score_table scores: [
706
+ { label: "Novelty", value: 8, max: 10 },
707
+ { label: "Quality", value: 5, max: 10 },
708
+ { label: "Impact", value: 3, max: 10 }
709
+ ]
710
+ ```
711
+
712
+ Color coding: Green (>=70%), Yellow (40-69%), Red (<40%)
713
+
714
+ ## Charts
715
+
716
+ Data visualization using Chart.js (loaded via CDN only when charts are present):
717
+
718
+ ```ruby
719
+ # Bar charts
720
+ bar_chart data: { calendar: 45, news: 120, tasks: 30 }
721
+ hbar_chart data: { "Phase A" => 25, "Phase B" => 45 } # horizontal
722
+
723
+ # Line charts
724
+ line_chart data: [12, 19, 8, 15, 22] # array = sequential x-axis
725
+ line_chart data: { Mon: 5, Tue: 12, Wed: 8 }, fill: true
726
+
727
+ # Sparklines (minimal, inline trend indicators)
728
+ sparkline data: [45, 52, 48, 61, 55, 67, 72]
729
+
730
+ # File-based data
731
+ bar_chart file: "~/metrics/timing.yaml", path: "entries.-1.phases"
732
+
733
+ # State-bound
734
+ bar_chart data: :metrics
735
+ ```
736
+
737
+ **Bar chart options:** `horizontal:`, `show_values:`
738
+
739
+ **Line chart options:** `fill:`, `smooth:`, `points:`, `begin_at_zero:`
740
+
741
+ **Pie/Doughnut:**
742
+ ```ruby
743
+ pie_chart data: { sales: 100, costs: 60, profit: 40 }
744
+ doughnut_chart data: { frontend: 40, backend: 35, devops: 25 }
745
+ ```
746
+
747
+ **Stacked bar:**
748
+ ```ruby
749
+ stacked_bar_chart data: [
750
+ { label: "Mon", sales: 100, costs: 60 },
751
+ { label: "Tue", sales: 120, costs: 70 }
752
+ ]
753
+ ```
754
+
755
+ **Common options:** `data:`, `file:`, `path:`, `title:`, `height:`, `colors:`, `show_legend:`
756
+
757
+ ## Mermaid Diagrams
758
+
759
+ Renders Mermaid.js diagrams. Mermaid.js 11 (ESM) is loaded lazily from CDN — only injected when a `mermaid` component is present on the page.
760
+
761
+ ```ruby
762
+ # Basic — any valid Mermaid diagram type
763
+ mermaid <<~MERMAID
764
+ graph LR
765
+ A[Start] --> B{Decision}
766
+ B -- Yes --> C[Ship it]
767
+ B -- No --> D[Debug]
768
+ D --> A
769
+ MERMAID
770
+
771
+ # zoom: true — adds +/−/reset buttons and Ctrl+scroll zoom
772
+ mermaid diagram_code, zoom: true
773
+
774
+ # compact: true — reduced padding for embedding inside a card
775
+ card do
776
+ mermaid diagram_code, compact: true
777
+ end
778
+
779
+ # layout: :elk — ELK layout engine (better for dense graphs, loads separately from CDN)
780
+ mermaid diagram_code, layout: :elk
781
+
782
+ # theme_vars: — override Mermaid themeVariables per diagram
783
+ mermaid diagram_code, theme_vars: {
784
+ primaryColor: "#6366f1",
785
+ primaryTextColor: "#ffffff",
786
+ primaryBorderColor: "#4f46e5",
787
+ lineColor: "#6366f1"
788
+ }
789
+ ```
790
+
791
+ **Options:**
792
+
793
+ | Option | Type | Default | Description |
794
+ |---|---|---|---|
795
+ | `zoom:` | Boolean | `false` | Adds in-place zoom/pan controls (+/−/reset) and Ctrl+scroll |
796
+ | `compact:` | Boolean | `false` | Reduces padding — use when embedding inside a card |
797
+ | `layout:` | Symbol | `:default` | Layout engine: `:default` (Dagre) or `:elk` (ELK) |
798
+ | `theme_vars:` | Hash | `nil` | Per-diagram Mermaid `themeVariables` overrides |
799
+
800
+ **Supported diagram types:** flowchart, sequence, pie, gantt, gitgraph, classDiagram, stateDiagram, erDiagram, and any other type supported by Mermaid.js 11.
801
+
802
+ **Expand to full screen.** Every diagram, regardless of `zoom:`, gets an ⛶-style expand button that opens it full-viewport with no width constraint — a doc column's max-width shrinks a wide diagram's fixed-px labels proportionally no matter how the layout is tuned, so this is the actual fix for an illegible complex diagram, not just a bigger version of `zoom: true`. Scroll (or drag) to pan, Ctrl+scroll to zoom further, Escape/click the backdrop/the close button to exit. No opt-in needed and no extra dependency — it's client-side only, so it works the same in the live canvas, `canvas-read`, and any exported doc (including an `--offline` one).
803
+
804
+ **Theme awareness:** Mermaid diagrams automatically re-render when the page theme changes (dark/light). Use `theme_vars:` for brand-specific color overrides.
805
+
806
+ **Examples:** `examples/components/mermaid_demo.rb` (all options), `examples/canvas/mermaid_canvas_demo.sh` (canvas-push).
807
+
808
+ ## Educational Content (Glossary/Tooltips)
809
+
810
+ ```ruby
811
+ glossary = {
812
+ "term" => {
813
+ simple: "Short definition on hover",
814
+ detailed: "Longer explanation on click"
815
+ }
816
+ }
817
+
818
+ # String syntax - terms in {braces}
819
+ lesson_text "This has a {term}.", glossary: glossary
820
+ ```
821
+
822
+ ## Alerts
823
+
824
+ ```ruby
825
+ alert(variant: :info) do
826
+ text "Informational message."
827
+ end
828
+
829
+ alert(variant: :success, title: "Success!") do
830
+ text "Your changes have been saved."
831
+ end
832
+
833
+ alert(variant: :warning, title: "Warning") do
834
+ text "Your session will expire soon."
835
+ end
836
+
837
+ alert(variant: :error, title: "Error") do
838
+ text "Unable to connect to server."
839
+ end
840
+
841
+ # Dismissible
842
+ alert(variant: :info, dismissible: true) do
843
+ text "Click X to dismiss."
844
+ end
845
+ ```
846
+
847
+ Variants: `:info`, `:success`, `:warning`, `:error`
848
+
849
+ ## Toast Notifications
850
+
851
+ ```ruby
852
+ # Add toast container
853
+ toast_container position: :top_right, duration: 5000
854
+
855
+ # Trigger toasts from button actions
856
+ button "Save" do |s|
857
+ show_toast("Saved successfully!", variant: :success)
858
+ end
859
+
860
+ # Clear all toasts
861
+ button "Clear" do |s|
862
+ clear_toasts
863
+ end
864
+ ```
865
+
866
+ Positions: `:top_right`, `:top_left`, `:bottom_right`, `:bottom_left`
867
+ Variants: `:info`, `:success`, `:warning`, `:error`
868
+ Duration: milliseconds (0 = no auto-dismiss)
869
+
870
+ ## Progress Bar
871
+
872
+ ```ruby
873
+ progress_bar value: 75
874
+ progress_bar value: 65, show_label: true
875
+ progress_bar value: 100, variant: :success
876
+ progress_bar value: 80, animated: true
877
+ ```
878
+
879
+ Variants: `:default`, `:success`, `:warning`, `:error`
880
+
881
+ ## Spinner
882
+
883
+ ```ruby
884
+ spinner
885
+ spinner size: :sm
886
+ spinner size: :md # default
887
+ spinner size: :lg
888
+ spinner size: :md, label: "Loading data..."
889
+ ```
890
+
891
+ Sizes: `:sm`, `:md`, `:lg`
892
+
893
+ ## Dashboard Components
894
+
895
+ Dashboard-style components for operations dashboards, control panels, and status displays. Best used with `theme: :dark`.
896
+
897
+ ### Status Dot
898
+
899
+ Colored indicator dots with optional glow effect, pulse animation, and labels:
900
+
901
+ ```ruby
902
+ status_dot status: :red # Red with glow
903
+ status_dot status: :yellow # Yellow with glow
904
+ status_dot status: :green # Green with glow
905
+ status_dot status: :gray # Gray (inactive)
906
+
907
+ # Sizes
908
+ status_dot status: :green, size: :sm # 6px
909
+ status_dot status: :green, size: :md # 10px (default)
910
+ status_dot status: :green, size: :lg # 14px
911
+
912
+ # Pulse animation
913
+ status_dot status: :green, pulse: true
914
+
915
+ # With label (displayed below the dot)
916
+ status_dot status: :green, label: "user.rb"
917
+ status_dot status: :green, pulse: true, label: "processing..."
918
+ ```
919
+
920
+ ### Badge
921
+
922
+ Count/label badges in various color variants:
923
+
924
+ ```ruby
925
+ badge "5" # Default gray
926
+ badge "3", variant: :danger # Red
927
+ badge "12", variant: :warning # Yellow
928
+ badge "OK", variant: :success # Green
929
+ badge "new", variant: :info # Blue
930
+
931
+ # Sizes
932
+ badge "5", size: :sm # Smaller
933
+ badge "5", size: :md # Default
934
+ ```
935
+
936
+ ### Stat Display
937
+
938
+ Large metric numbers with labels:
939
+
940
+ ```ruby
941
+ stat_display value: 42, label: "TASKS"
942
+ stat_display value: 7, label: "PENDING", color: :blue
943
+ stat_display value: 5, label: "BLOCKED", color: :red
944
+ stat_display value: 12, label: "DONE", color: :purple
945
+
946
+ # Sizes
947
+ stat_display value: 99, label: "TOTAL", size: :sm
948
+ stat_display value: 99, label: "TOTAL", size: :md # Default
949
+ stat_display value: 99, label: "TOTAL", size: :lg
950
+ ```
951
+
952
+ Colors: `:default`, `:blue`, `:purple`, `:red`
953
+
954
+ ### Type Tag
955
+
956
+ Activity type badges:
957
+
958
+ ```ruby
959
+ type_tag :research # Blue
960
+ type_tag :task # Purple
961
+ type_tag :escalation # Red
962
+ type_tag :communication # Green
963
+ type_tag :warning # Yellow
964
+ type_tag :info # Gray
965
+ ```
966
+
967
+ ### Pulse Indicator
968
+
969
+ Animated status indicator with label (for headers):
970
+
971
+ ```ruby
972
+ pulse_indicator color: :green, label: "System Active"
973
+ pulse_indicator color: :red, label: "Alert"
974
+ pulse_indicator color: :yellow, label: "Degraded"
975
+ ```
976
+
977
+ ### Priority Item
978
+
979
+ Items with priority-colored left border and hover slide effect:
980
+
981
+ ```ruby
982
+ priority_item priority: :critical, title: "Database at capacity",
983
+ description: "Primary DB at 92% storage. Need immediate action.",
984
+ meta_left: "ops", meta_right: "Expand storage"
985
+
986
+ priority_item priority: :urgent, title: "API rate limited",
987
+ description: "Third-party integration hitting 429 errors."
988
+
989
+ priority_item priority: :high, title: "Security patch needed"
990
+ priority_item priority: :normal, title: "Documentation update"
991
+ ```
992
+
993
+ Priorities: `:critical` (red), `:urgent` (orange), `:high` (yellow), `:normal` (gray)
994
+
995
+ ### Activity Item
996
+
997
+ Activity feed items with time, title, summary, and type badge:
998
+
999
+ ```ruby
1000
+ activity_item time: "15:00", title: "Performance analysis",
1001
+ summary: "Identified 3 slow queries, recommended indexes",
1002
+ type: :research
1003
+
1004
+ activity_item time: "14:30", title: "Deployment complete",
1005
+ summary: "All services updated successfully",
1006
+ type: :task
1007
+ ```
1008
+
1009
+ Types: `:research`, `:task`, `:escalation`, `:communication`
1010
+
1011
+ ## Dashboard Layouts
1012
+
1013
+ ### App Shell
1014
+
1015
+ Two-column layout with main content and sidebar:
1016
+
1017
+ ```ruby
1018
+ app_shell sidebar_width: "320px" do
1019
+ main do
1020
+ header2 "Dashboard"
1021
+ # Main content here
1022
+ end
1023
+
1024
+ sidebar header: "Alerts" do
1025
+ # Sidebar content here
1026
+ end
1027
+ end
1028
+ ```
1029
+
1030
+ The sidebar is sticky and scrolls independently. Layout stacks on mobile.
1031
+
1032
+ ### Expandable Card
1033
+
1034
+ Cards that expand/collapse on click with smooth transitions:
1035
+
1036
+ ```ruby
1037
+ expandable_card key: :team_details,
1038
+ title: "Engineering",
1039
+ subtitle: "Product Development",
1040
+ badge_text: "5 activities",
1041
+ status: :green,
1042
+ initially_expanded: true do
1043
+ # Expanded content here
1044
+ stat_display value: 3, label: "TASKS"
1045
+ activity_item time: "15:00", title: "Code review"
1046
+ end
1047
+ ```
1048
+
1049
+ Options:
1050
+ - `key:` - State key for expansion state (required)
1051
+ - `title:` - Card header title
1052
+ - `subtitle:` - Secondary text
1053
+ - `badge_text:` - Text in top-right badge
1054
+ - `status:` - Status dot color (`:red`, `:yellow`, `:green`)
1055
+ - `initially_expanded:` - Start expanded (default: false)
1056
+
1057
+ ## Live Streaming (SSE Timers)
1058
+
1059
+ Push live updates to the browser without polling. The `every` DSL registers periodic timers that fire server-side and push DOM updates via Server-Sent Events.
1060
+
1061
+ ### `every(seconds)`
1062
+
1063
+ ```ruby
1064
+ app "Live", theme: :dark do
1065
+ div id: "clock" do
1066
+ text "..."
1067
+ end
1068
+
1069
+ every(1) do |streamer|
1070
+ streamer.replace("#clock") do
1071
+ div id: "clock" do
1072
+ text Time.now.strftime("%H:%M:%S")
1073
+ end
1074
+ end
1075
+ end
1076
+ end.run!
1077
+ ```
1078
+
1079
+ ### Streamer Actions
1080
+
1081
+ The `streamer` object passed to `every` blocks supports:
1082
+
1083
+ ```ruby
1084
+ # Replace element content (block or string)
1085
+ streamer.replace("#target") do
1086
+ div id: "target" do
1087
+ stat_display value: "42", label: "COUNT", color: :green
1088
+ end
1089
+ end
1090
+
1091
+ # Append/prepend content
1092
+ streamer.prepend("#feed") do
1093
+ div style: "padding:4px" do
1094
+ text "New entry at #{Time.now}"
1095
+ end
1096
+ end
1097
+
1098
+ streamer.append("#log", "<p>Raw HTML also works</p>")
1099
+
1100
+ # CSS class manipulation
1101
+ streamer.add_class("#card-cpu", "highlight")
1102
+ streamer.remove_class("#card-cpu", "highlight")
1103
+
1104
+ # Remove element entirely
1105
+ streamer.remove("#temporary-banner")
1106
+ ```
1107
+
1108
+ ### CSS Injection Pattern
1109
+
1110
+ To inject custom CSS for class effects, use a hidden placeholder with `replace` (not `append("head")` which creates duplicates):
1111
+
1112
+ ```ruby
1113
+ div id: "custom-css", style: "display:none"
1114
+
1115
+ every(5) do |streamer|
1116
+ streamer.replace("#custom-css", "<style>.glow{box-shadow:0 0 12px red}</style>")
1117
+ streamer.add_class("#card-alert", "glow")
1118
+ end
1119
+ ```
1120
+
1121
+ ### Multiple Timers
1122
+
1123
+ Multiple `every` blocks run independently. Use closure variables to share state:
1124
+
1125
+ ```ruby
1126
+ latest = { cpu: 0.0 }
1127
+
1128
+ every(3) do |streamer|
1129
+ latest[:cpu] = read_cpu
1130
+ streamer.replace("#cpu-metric") { ... }
1131
+ end
1132
+
1133
+ every(10) do |streamer|
1134
+ if latest[:cpu] > threshold
1135
+ streamer.prepend("#alerts") { ... }
1136
+ end
1137
+ end
1138
+ ```
1139
+
1140
+ ### Targeting `expandable_card`
1141
+
1142
+ `expandable_card key: :foo` generates `id="card-foo"` on the outer container. Target it with `#card-foo` for class manipulation:
1143
+
1144
+ ```ruby
1145
+ expandable_card key: :server, title: "Server", status: :green do
1146
+ div id: "server-stats" do
1147
+ stat_display value: "\u2014", label: "LOAD"
1148
+ end
1149
+ end
1150
+
1151
+ every(5) do |streamer|
1152
+ streamer.replace("#server-stats") { ... }
1153
+ streamer.add_class("#card-server", "alert-ring") if overloaded
1154
+ end
1155
+ ```
1156
+
1157
+ ### Accessing `state` Inside Replace Blocks
1158
+
1159
+ `streamer.replace` blocks run in a `FeedBuilder` context, not the App context. `state` is a method on App — it is **not available** inside replace blocks unless you pass it explicitly via the `state:` keyword.
1160
+
1161
+ ```ruby
1162
+ # WRONG — raises NameError in timer thread at runtime
1163
+ every(30) do |streamer|
1164
+ streamer.replace("#panel") do
1165
+ text "Mode: #{state[:mode]}" # NameError: undefined method `state'
1166
+ end
1167
+ end
1168
+
1169
+ # CORRECT
1170
+ every(30) do |streamer|
1171
+ streamer.replace("#panel", state: state) do
1172
+ text "Mode: #{state[:mode]}" # works
1173
+ end
1174
+ end
1175
+ ```
1176
+
1177
+ This applies to `replace`, `append`, and `prepend`. The error appears in the server log as `[StreamWeaver] Timer error: NameError` — it will not raise on startup, only when the timer fires.
1178
+
1179
+ ### Helper Methods and Replace Blocks
1180
+
1181
+ Top-level `def` helper methods are accessible inside replace blocks (they're global). If those helpers use `state`, pass `state:` to `replace` so it is available:
1182
+
1183
+ ```ruby
1184
+ def render_dashboard(metrics:, state:)
1185
+ columns do
1186
+ column do
1187
+ stat_display value: metrics[:count], label: "Total"
1188
+ end
1189
+ column do
1190
+ text "Filter: #{state[:filter]}"
1191
+ end
1192
+ end
1193
+ end
1194
+
1195
+ app "Dashboard" do
1196
+ div id: "dash" do
1197
+ render_dashboard(metrics: load_metrics, state: state)
1198
+ end
1199
+
1200
+ every(10) do |streamer|
1201
+ streamer.replace("#dash", state: state) do
1202
+ render_dashboard(metrics: load_metrics, state: state)
1203
+ end
1204
+ end
1205
+ end.run!
1206
+ ```
1207
+
1208
+ ## Dark Theme
1209
+
1210
+ Use `theme: :dark` for a dark color scheme optimized for dashboards:
1211
+
1212
+ ```ruby
1213
+ app "Operations Dashboard", theme: :dark do
1214
+ # components...
1215
+ end
1216
+ ```
1217
+
1218
+ Dark theme provides:
1219
+ - Deep dark backgrounds (`#0a0e14`, `#131820`)
1220
+ - Soft borders and elevated surfaces
1221
+ - Light text with proper contrast
1222
+ - Glow effects on status indicators
1223
+ - Hover lift and slide animations