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,639 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require_relative 'theme/presets'
5
+ require_relative 'theme/auto_mode'
6
+
7
+ module StreamWeaver
8
+ # Custom theme registration and management.
9
+ #
10
+ # == Visual Skills CSS Foundation (T2)
11
+ #
12
+ # This module provides the CSS custom property vocabulary used by ALL
13
+ # visual skills components (T3-T15). The convention is:
14
+ #
15
+ # === sw- CSS Prefix Convention
16
+ #
17
+ # All visual skills CSS classes MUST use the +sw-+ prefix to avoid
18
+ # collisions with user CSS and third-party libraries.
19
+ #
20
+ # BEM naming: sw-component--variant (e.g. sw-card--hero)
21
+ # State: sw-is-active, sw-is-selected
22
+ # Utility: sw-text-dim, sw-bg-surface
23
+ #
24
+ # === CSS Custom Properties
25
+ #
26
+ # Semantic tokens (set per light/dark mode):
27
+ # --sw-bg Page background
28
+ # --sw-surface Card / panel surface
29
+ # --sw-surface-elevated Elevated surface (dropdowns, popovers)
30
+ # --sw-border Default border color
31
+ # --sw-text Primary text color
32
+ # --sw-text-dim Secondary / muted text
33
+ # --sw-accent Primary accent color
34
+ # --sw-node-a/b/c Diagram node palette
35
+ # --sw-success/warning/error/info Status colors
36
+ #
37
+ # These are _additional_ to the existing --sw-color-* tokens.
38
+ # Components should prefer the short --sw-* names; the --sw-color-*
39
+ # variants remain for backward compatibility.
40
+ #
41
+ # @example Register a custom theme
42
+ # StreamWeaver.register_theme :corporate, {
43
+ # font_family: "'Inter', system-ui, sans-serif",
44
+ # color_primary: "#0066cc",
45
+ # spacing_md: "1rem"
46
+ # }, base: :dashboard
47
+ #
48
+ # @example Use in an app
49
+ # app "Corporate App", theme: :corporate do
50
+ # text "Hello, styled world!"
51
+ # end
52
+ class Theme
53
+ # Variable schema: maps Ruby keys to CSS variables with type info
54
+ # Types: :string, :size, :number, :color
55
+ VARIABLE_SCHEMA = {
56
+ # Typography
57
+ font_display: { css: "--sw-font-display", type: :string },
58
+ font_body: { css: "--sw-font-body", type: :string },
59
+ font_family: { css: "--sw-font-family", type: :string },
60
+ font_size_base: { css: "--sw-font-size-base", type: :size },
61
+ font_size_sm: { css: "--sw-font-size-sm", type: :size },
62
+ font_size_lg: { css: "--sw-font-size-lg", type: :size },
63
+ font_size_xl: { css: "--sw-font-size-xl", type: :size },
64
+ line_height: { css: "--sw-line-height", type: :number },
65
+
66
+ # Colors - Primary
67
+ color_primary: { css: "--sw-color-primary", type: :color },
68
+ color_primary_hover: { css: "--sw-color-primary-hover", type: :color },
69
+ color_primary_light: { css: "--sw-color-primary-light", type: :color },
70
+ color_primary_glow: { css: "--sw-color-primary-glow", type: :color },
71
+
72
+ # Colors - Neutrals
73
+ color_text: { css: "--sw-color-text", type: :color },
74
+ color_text_muted: { css: "--sw-color-text-muted", type: :color },
75
+ color_text_light: { css: "--sw-color-text-light", type: :color },
76
+ color_bg: { css: "--sw-color-bg", type: :color },
77
+ color_bg_card: { css: "--sw-color-bg-card", type: :color },
78
+ color_bg_elevated: { css: "--sw-color-bg-elevated", type: :color },
79
+ color_border: { css: "--sw-color-border", type: :color },
80
+ color_border_strong: { css: "--sw-color-border-strong", type: :color },
81
+ color_border_focus: { css: "--sw-color-border-focus", type: :color },
82
+
83
+ # Colors - Secondary
84
+ color_secondary: { css: "--sw-color-secondary", type: :color },
85
+ color_secondary_hover: { css: "--sw-color-secondary-hover", type: :color },
86
+ color_secondary_foreground: { css: "--sw-color-secondary-foreground", type: :color },
87
+
88
+ # Colors - Accent
89
+ color_accent: { css: "--sw-color-accent", type: :color },
90
+ color_accent_light: { css: "--sw-color-accent-light", type: :color },
91
+
92
+ # Spacing
93
+ spacing_xs: { css: "--sw-spacing-xs", type: :size },
94
+ spacing_sm: { css: "--sw-spacing-sm", type: :size },
95
+ spacing_md: { css: "--sw-spacing-md", type: :size },
96
+ spacing_lg: { css: "--sw-spacing-lg", type: :size },
97
+ spacing_xl: { css: "--sw-spacing-xl", type: :size },
98
+ spacing_2xl: { css: "--sw-spacing-2xl", type: :size },
99
+
100
+ # Border Radius
101
+ radius_sm: { css: "--sw-radius-sm", type: :size },
102
+ radius_md: { css: "--sw-radius-md", type: :size },
103
+ radius_lg: { css: "--sw-radius-lg", type: :size },
104
+ radius_xl: { css: "--sw-radius-xl", type: :size },
105
+
106
+ # Shadows
107
+ shadow_sm: { css: "--sw-shadow-sm", type: :string },
108
+ shadow_md: { css: "--sw-shadow-md", type: :string },
109
+ shadow_lg: { css: "--sw-shadow-lg", type: :string },
110
+ shadow_xl: { css: "--sw-shadow-xl", type: :string },
111
+ shadow_inner: { css: "--sw-shadow-inner", type: :string },
112
+
113
+ # Card styling
114
+ card_border_left: { css: "--sw-card-border-left", type: :string },
115
+
116
+ # Term highlighting
117
+ term_color: { css: "--sw-term-color", type: :color },
118
+ term_bg_hover: { css: "--sw-term-bg-hover", type: :color },
119
+
120
+ # ============================================
121
+ # Visual Skills Semantic Tokens (T2 foundation)
122
+ # These short-form --sw-* tokens are the primary
123
+ # vocabulary for visual skills components.
124
+ # ============================================
125
+
126
+ # Surfaces
127
+ vs_bg: { css: "--sw-bg", type: :color },
128
+ vs_surface: { css: "--sw-surface", type: :color },
129
+ vs_surface_elevated: { css: "--sw-surface-elevated", type: :color },
130
+ vs_border: { css: "--sw-border", type: :color },
131
+
132
+ # Text
133
+ vs_text: { css: "--sw-text", type: :color },
134
+ vs_text_dim: { css: "--sw-text-dim", type: :color },
135
+ vs_accent: { css: "--sw-accent", type: :color },
136
+
137
+ # Diagram node colors
138
+ vs_node_a: { css: "--sw-node-a", type: :color },
139
+ vs_node_b: { css: "--sw-node-b", type: :color },
140
+ vs_node_c: { css: "--sw-node-c", type: :color },
141
+
142
+ # Status colors
143
+ vs_success: { css: "--sw-success", type: :color },
144
+ vs_warning: { css: "--sw-warning", type: :color },
145
+ vs_error: { css: "--sw-error", type: :color },
146
+ vs_info: { css: "--sw-info", type: :color },
147
+
148
+ # Monospace font (for code blocks, dir trees)
149
+ font_mono: { css: "--sw-font-mono", type: :string }
150
+ }.freeze
151
+
152
+ # Built-in theme definitions (matches CSS in views.rb)
153
+ BUILT_IN_THEMES = {
154
+ default: {
155
+ name: "Default",
156
+ description: "Warm Industrial",
157
+ variables: {
158
+ font_display: "'Source Sans 3', system-ui, sans-serif",
159
+ font_body: "'Source Sans 3', system-ui, sans-serif",
160
+ font_size_base: "17px",
161
+ line_height: "1.7",
162
+ color_primary: "#a16207",
163
+ color_bg: "#f2ede4",
164
+ color_text: "#111111",
165
+ spacing_md: "1.25rem",
166
+ card_border_left: "3px solid var(--sw-color-primary)"
167
+ }
168
+ },
169
+ dashboard: {
170
+ name: "Dashboard",
171
+ description: "Data Dense",
172
+ variables: {
173
+ font_display: "'Source Sans 3', system-ui, sans-serif",
174
+ font_body: "'Source Sans 3', system-ui, sans-serif",
175
+ font_size_base: "15px",
176
+ line_height: "1.5",
177
+ color_primary: "#a16207",
178
+ color_bg: "#fafafa",
179
+ color_text: "#111111",
180
+ spacing_md: "0.75rem",
181
+ card_border_left: "1px solid var(--sw-color-border)"
182
+ }
183
+ },
184
+ document: {
185
+ name: "Document",
186
+ description: "Reading Mode",
187
+ variables: {
188
+ font_display: "'Source Sans 3', system-ui, sans-serif",
189
+ font_body: "'Crimson Pro', Georgia, 'Times New Roman', serif",
190
+ font_size_base: "19px",
191
+ line_height: "1.85",
192
+ color_primary: "#6b7280",
193
+ color_bg: "#faf8f5",
194
+ color_text: "#1a1a1a",
195
+ spacing_md: "1.5rem",
196
+ card_border_left: "none"
197
+ }
198
+ },
199
+ doc: {
200
+ name: "Doc",
201
+ description: "Compact Editorial",
202
+ variables: {
203
+ font_display: "Charter, 'Bitstream Charter', 'Sitka Text', Cambria, Georgia, serif",
204
+ font_body: "-apple-system, BlinkMacSystemFont, 'Helvetica Neue', Arial, sans-serif",
205
+ font_size_base: "15px",
206
+ line_height: "1.65",
207
+ color_primary: "#1E4ED8",
208
+ color_bg: "#F5F4EF",
209
+ color_text: "#181714",
210
+ spacing_md: "1rem",
211
+ card_border_left: "none"
212
+ }
213
+ }
214
+ }.freeze
215
+
216
+ attr_reader :name, :variables, :base_theme, :label, :description
217
+
218
+ # @param name [Symbol] Theme identifier
219
+ # @param variables [Hash] Theme variable overrides
220
+ # @param base [Symbol] Base theme to inherit from (:default, :dashboard, :document)
221
+ # @param label [String] Human-readable name
222
+ # @param description [String] Short description
223
+ def initialize(name, variables = {}, base: :default, label: nil, description: nil)
224
+ @name = name.to_sym
225
+ @base_theme = base.to_sym
226
+ @variables = normalize_variables(variables)
227
+ @label = label || name.to_s.split('_').map(&:capitalize).join(' ')
228
+ @description = description || "Custom theme"
229
+ end
230
+
231
+ # Generate CSS variable declarations for this theme
232
+ #
233
+ # @return [String] CSS variable block
234
+ def to_css
235
+ css_lines = @variables.map do |key, value|
236
+ schema = VARIABLE_SCHEMA[key]
237
+ css_var = schema ? schema[:css] : "--sw-#{key.to_s.tr('_', '-')}"
238
+ " #{css_var}: #{value};"
239
+ end
240
+
241
+ "body.sw-theme-#{@name} {\n#{css_lines.join("\n")}\n}"
242
+ end
243
+
244
+ # Export as Ruby registration code
245
+ #
246
+ # @return [String] Ruby code to register this theme
247
+ def to_ruby
248
+ vars = @variables.map { |k, v| " #{k}: #{v.inspect}" }.join(",\n")
249
+ <<~RUBY
250
+ StreamWeaver.register_theme :#{@name}, {
251
+ #{vars}
252
+ }, base: :#{@base_theme}, label: #{@label.inspect}, description: #{@description.inspect}
253
+ RUBY
254
+ end
255
+
256
+ # Export as JSON
257
+ #
258
+ # @return [String] JSON representation
259
+ def to_json(*_args)
260
+ JSON.pretty_generate(
261
+ name: @name,
262
+ label: @label,
263
+ description: @description,
264
+ base_theme: @base_theme,
265
+ variables: @variables
266
+ )
267
+ end
268
+
269
+ # Export as Hash
270
+ #
271
+ # @return [Hash] Hash representation
272
+ def to_h
273
+ {
274
+ name: @name,
275
+ label: @label,
276
+ description: @description,
277
+ base_theme: @base_theme,
278
+ variables: @variables
279
+ }
280
+ end
281
+
282
+ # Load theme from JSON
283
+ #
284
+ # @param json_string [String] JSON theme definition
285
+ # @return [Theme] New theme instance
286
+ def self.from_json(json_string)
287
+ data = JSON.parse(json_string, symbolize_names: true)
288
+ new(
289
+ data[:name],
290
+ data[:variables],
291
+ base: data[:base_theme] || :default,
292
+ label: data[:label],
293
+ description: data[:description]
294
+ )
295
+ end
296
+
297
+ # Load theme from file
298
+ #
299
+ # @param path [String] Path to JSON file
300
+ # @return [Theme] New theme instance
301
+ def self.from_file(path)
302
+ from_json(File.read(path))
303
+ end
304
+
305
+ # Get all available variable names
306
+ #
307
+ # @return [Array<Symbol>] Variable names
308
+ def self.variable_names
309
+ VARIABLE_SCHEMA.keys
310
+ end
311
+
312
+ # Get variables grouped by category
313
+ #
314
+ # @return [Hash] Variables grouped by category
315
+ def self.variables_by_category
316
+ {
317
+ typography: %i[font_display font_body font_family font_size_base font_size_sm font_size_lg font_size_xl line_height],
318
+ colors_primary: %i[color_primary color_primary_hover color_primary_light color_primary_glow],
319
+ colors_neutral: %i[color_text color_text_muted color_text_light color_bg color_bg_card color_bg_elevated color_border color_border_strong color_border_focus],
320
+ colors_secondary: %i[color_secondary color_secondary_hover color_secondary_foreground color_accent color_accent_light],
321
+ spacing: %i[spacing_xs spacing_sm spacing_md spacing_lg spacing_xl spacing_2xl],
322
+ border_radius: %i[radius_sm radius_md radius_lg radius_xl],
323
+ shadows: %i[shadow_sm shadow_md shadow_lg shadow_xl shadow_inner],
324
+ components: %i[card_border_left term_color term_bg_hover]
325
+ }
326
+ end
327
+
328
+ # Generate the visual skills CSS custom properties block.
329
+ # This produces the --sw-* semantic tokens for both light and dark modes,
330
+ # using sensible defaults that bridge to existing --sw-color-* tokens.
331
+ #
332
+ # @return [String] CSS with :root and html.dark blocks
333
+ def self.visual_skills_css
334
+ <<~CSS
335
+ /* ===========================================
336
+ Visual Skills CSS Foundation (T2)
337
+ Semantic tokens for visual skills components.
338
+ All classes use sw- prefix with BEM modifiers:
339
+ sw-component--variant (e.g. sw-card--hero)
340
+ sw-is-active (state classes)
341
+ =========================================== */
342
+
343
+ /* Light mode defaults -- bridge to existing --sw-color-* tokens */
344
+ :root {
345
+ --sw-bg: var(--sw-color-bg, #f8f8f8);
346
+ --sw-surface: var(--sw-color-bg-card, #ffffff);
347
+ --sw-surface-elevated: var(--sw-color-bg-elevated, #f3f3f3);
348
+ --sw-border: var(--sw-color-border, #e0e0e0);
349
+ --sw-text: var(--sw-color-text, #111111);
350
+ --sw-text-dim: var(--sw-color-text-muted, #444444);
351
+ --sw-accent: var(--sw-color-accent, #0d9488);
352
+ --sw-font-mono: 'JetBrains Mono', 'Fira Code', 'Cascadia Code', monospace;
353
+
354
+ /* Diagram node palette */
355
+ --sw-node-a: var(--sw-color-primary, #c2410c);
356
+ --sw-node-b: var(--sw-color-accent, #0d9488);
357
+ --sw-node-c: #7c3aed;
358
+
359
+ /* Status colors */
360
+ --sw-success: #16a34a;
361
+ --sw-warning: #d97706;
362
+ --sw-error: #dc2626;
363
+ --sw-info: #2563eb;
364
+ }
365
+
366
+ /* Re-bridge on body: --sw-color-* (what these fall back to) is itself
367
+ declared on body (per-theme, or an app's theme_overrides:), a
368
+ descendant of :root -- so the :root block above can never see it and
369
+ is stuck on its own light-mode fallback. Redeclaring here lets these
370
+ short tokens track whatever theme body actually ends up with
371
+ (stream_weaver-9u2). */
372
+ body {
373
+ --sw-bg: var(--sw-color-bg, #f8f8f8);
374
+ --sw-surface: var(--sw-color-bg-card, #ffffff);
375
+ --sw-surface-elevated: var(--sw-color-bg-elevated, #f3f3f3);
376
+ --sw-border: var(--sw-color-border, #e0e0e0);
377
+ --sw-text: var(--sw-color-text, #111111);
378
+ --sw-text-dim: var(--sw-color-text-muted, #444444);
379
+ --sw-accent: var(--sw-color-accent, #0d9488);
380
+ --sw-node-a: var(--sw-color-primary, #c2410c);
381
+ --sw-node-b: var(--sw-color-accent, #0d9488);
382
+ }
383
+
384
+ /* Theme toggle button (sw- prefixed, BEM) */
385
+ .sw-theme-toggle {
386
+ display: inline-flex;
387
+ align-items: center;
388
+ gap: 0.375rem;
389
+ }
390
+
391
+ .sw-theme-toggle__btn {
392
+ background: transparent;
393
+ border: 1px solid var(--sw-border);
394
+ border-radius: var(--sw-radius-md, 6px);
395
+ padding: 0.375rem 0.5rem;
396
+ cursor: pointer;
397
+ font-size: 1.1rem;
398
+ line-height: 1;
399
+ color: var(--sw-text);
400
+ transition: background 200ms ease-out, border-color 200ms ease-out;
401
+ }
402
+
403
+ .sw-theme-toggle__btn:hover {
404
+ background: var(--sw-surface-elevated);
405
+ border-color: var(--sw-accent);
406
+ }
407
+
408
+ .sw-theme-toggle__label {
409
+ font-size: 0.75rem;
410
+ color: var(--sw-text-dim);
411
+ text-transform: uppercase;
412
+ letter-spacing: 0.05em;
413
+ }
414
+
415
+ /* Dark mode overrides */
416
+ html.dark {
417
+ --sw-bg: var(--sw-color-bg, oklch(0.145 0 0));
418
+ --sw-surface: var(--sw-color-bg-card, oklch(0.205 0 0));
419
+ --sw-surface-elevated: var(--sw-color-bg-elevated, oklch(0.269 0 0));
420
+ --sw-border: var(--sw-color-border, oklch(1 0 0 / 10%));
421
+ --sw-text: var(--sw-color-text, oklch(0.985 0 0));
422
+ --sw-text-dim: var(--sw-color-text-muted, oklch(0.708 0 0));
423
+ --sw-accent: var(--sw-color-accent, #2dd4bf);
424
+
425
+ /* Brighter node colors for dark backgrounds */
426
+ --sw-node-a: #f97316;
427
+ --sw-node-b: #2dd4bf;
428
+ --sw-node-c: #a78bfa;
429
+
430
+ /* Brighter status colors for dark backgrounds */
431
+ --sw-success: #22c55e;
432
+ --sw-warning: #fbbf24;
433
+ --sw-error: #f87171;
434
+ --sw-info: #60a5fa;
435
+ }
436
+
437
+ /* ===========================================
438
+ Card Depth Tiers (T6)
439
+ sw-card--{depth} classes for visual hierarchy.
440
+ =========================================== */
441
+
442
+ .sw-card--hero {
443
+ background: color-mix(in oklch, var(--sw-accent) 8%, var(--sw-surface));
444
+ box-shadow: 0 4px 24px rgba(0, 0, 0, 0.12), 0 1px 4px rgba(0, 0, 0, 0.08);
445
+ border-left: 4px solid var(--sw-accent);
446
+ }
447
+
448
+ .sw-card--elevated {
449
+ background: var(--sw-surface);
450
+ box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08), 0 1px 3px rgba(0, 0, 0, 0.06);
451
+ }
452
+
453
+ .sw-card--default {
454
+ background: var(--sw-surface);
455
+ border: 1px solid var(--sw-border);
456
+ box-shadow: none;
457
+ }
458
+
459
+ .sw-card--recessed {
460
+ background: var(--sw-surface-elevated);
461
+ box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1);
462
+ border: 1px solid var(--sw-border);
463
+ }
464
+
465
+ .sw-card--glass {
466
+ background: rgba(255, 255, 255, 0.08);
467
+ backdrop-filter: blur(12px);
468
+ -webkit-backdrop-filter: blur(12px);
469
+ border: 1px solid rgba(255, 255, 255, 0.15);
470
+ box-shadow: 0 2px 16px rgba(0, 0, 0, 0.06);
471
+ }
472
+
473
+ html.dark .sw-card--glass {
474
+ background: rgba(255, 255, 255, 0.05);
475
+ border-color: rgba(255, 255, 255, 0.1);
476
+ }
477
+
478
+ /* Card accent borders (left border colored by node palette) */
479
+ .sw-card--accent-a { border-left: 4px solid var(--sw-node-a); }
480
+ .sw-card--accent-b { border-left: 4px solid var(--sw-node-b); }
481
+ .sw-card--accent-c { border-left: 4px solid var(--sw-node-c); }
482
+ .sw-card--accent { border-left: 4px solid; }
483
+
484
+ /* Card corner label */
485
+ .sw-card__label {
486
+ position: absolute;
487
+ top: 0.5rem;
488
+ right: 0.5rem;
489
+ font-size: 0.625rem;
490
+ font-weight: 700;
491
+ letter-spacing: 0.08em;
492
+ text-transform: uppercase;
493
+ padding: 0.125rem 0.5rem;
494
+ border-radius: var(--sw-radius-sm, 4px);
495
+ background: var(--sw-accent);
496
+ color: white;
497
+ line-height: 1.4;
498
+ }
499
+
500
+ /* Card with label: position: relative is applied inline by the component */
501
+
502
+ /* ===========================================
503
+ Table Enhancements (T6)
504
+ sw-table--{feature} classes for enhanced tables.
505
+ =========================================== */
506
+
507
+ /* Alternating row backgrounds */
508
+ .sw-table--alternating .sw-table__row--alt {
509
+ background: var(--sw-surface-elevated, rgba(0, 0, 0, 0.02));
510
+ }
511
+
512
+ html.dark .sw-table--alternating .sw-table__row--alt {
513
+ background: rgba(255, 255, 255, 0.03);
514
+ }
515
+
516
+ /* Row hover highlight */
517
+ .sw-table--hover .sw-table__row--hover:hover {
518
+ background: color-mix(in oklch, var(--sw-accent) 6%, transparent);
519
+ transition: background 150ms ease-out;
520
+ }
521
+
522
+ /* Scrollable wrapper */
523
+ .sw-table--scrollable {
524
+ border: 1px solid var(--sw-border, #e0e0e0);
525
+ border-radius: var(--sw-radius-md, 6px);
526
+ }
527
+
528
+ /* Sticky header styling */
529
+ .sw-table--sticky-header thead {
530
+ position: sticky;
531
+ top: 0;
532
+ z-index: 1;
533
+ }
534
+
535
+ #{Presets.animations_css}
536
+ CSS
537
+ end
538
+
539
+ # Generate a Google Fonts <link> tag URL for the given font families.
540
+ #
541
+ # @param families [Array<String>] Google Fonts family strings
542
+ # e.g. ["Instrument+Serif:ital@0;1", "JetBrains+Mono:wght@400;500"]
543
+ # @return [String] Full Google Fonts CSS URL
544
+ def self.google_fonts_url(*families)
545
+ families = families.flatten
546
+ query = families.map { |f| "family=#{f}" }.join("&")
547
+ "https://fonts.googleapis.com/css2?#{query}&display=swap"
548
+ end
549
+
550
+ private
551
+
552
+ # Normalize variable keys to symbols
553
+ def normalize_variables(vars)
554
+ vars.transform_keys(&:to_sym)
555
+ end
556
+ end
557
+
558
+ # Theme registry - stores custom themes
559
+ @themes = {}
560
+
561
+ class << self
562
+ # Access the theme registry
563
+ #
564
+ # @return [Hash<Symbol, Theme>] Registered themes
565
+ def themes
566
+ @themes
567
+ end
568
+
569
+ # Register a custom theme
570
+ #
571
+ # @param name [Symbol] Theme identifier (used in theme: :name)
572
+ # @param variables [Hash] CSS variable overrides
573
+ # @param base [Symbol] Base theme to inherit from
574
+ # @param label [String] Human-readable name
575
+ # @param description [String] Short description
576
+ # @return [Theme] The registered theme
577
+ #
578
+ # @example Basic registration
579
+ # StreamWeaver.register_theme :corporate, {
580
+ # color_primary: "#0066cc",
581
+ # font_family: "'Inter', system-ui, sans-serif"
582
+ # }
583
+ #
584
+ # @example With base theme
585
+ # StreamWeaver.register_theme :compact_corporate, {
586
+ # color_primary: "#0066cc"
587
+ # }, base: :dashboard
588
+ def register_theme(name, variables = {}, base: :default, label: nil, description: nil)
589
+ theme = Theme.new(name, variables, base: base, label: label, description: description)
590
+ @themes[name.to_sym] = theme
591
+ theme
592
+ end
593
+
594
+ # Get a theme by name (custom or built-in)
595
+ #
596
+ # @param name [Symbol] Theme name
597
+ # @return [Theme, nil] The theme or nil if not found
598
+ def get_theme(name)
599
+ name = name.to_sym
600
+ @themes[name] || (Theme::BUILT_IN_THEMES[name] && Theme.new(
601
+ name,
602
+ Theme::BUILT_IN_THEMES[name][:variables],
603
+ label: Theme::BUILT_IN_THEMES[name][:name],
604
+ description: Theme::BUILT_IN_THEMES[name][:description]
605
+ ))
606
+ end
607
+
608
+ # Check if a theme exists (custom or built-in)
609
+ #
610
+ # @param name [Symbol] Theme name
611
+ # @return [Boolean]
612
+ def theme_exists?(name)
613
+ name = name.to_sym
614
+ @themes.key?(name) || Theme::BUILT_IN_THEMES.key?(name)
615
+ end
616
+
617
+ # Get all available theme names
618
+ #
619
+ # @return [Array<Symbol>] Theme names
620
+ def available_themes
621
+ (Theme::BUILT_IN_THEMES.keys + @themes.keys).uniq
622
+ end
623
+
624
+ # Get all themes as an array suitable for theme_switcher
625
+ #
626
+ # @return [Array<Hash>] Array of theme info hashes
627
+ def all_themes_for_switcher
628
+ built_in = Theme::BUILT_IN_THEMES.map do |id, info|
629
+ { id: id, label: info[:name], description: info[:description] }
630
+ end
631
+
632
+ custom = @themes.map do |id, theme|
633
+ { id: id, label: theme.label, description: theme.description }
634
+ end
635
+
636
+ built_in + custom
637
+ end
638
+ end
639
+ end