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,417 @@
1
+ # PLAN: shadcn Polish for StreamWeaver
2
+
3
+ **Goal:** Get shadcn/ui-level visual polish in StreamWeaver components, with optional Tailwind mode for full fidelity.
4
+
5
+ **Reference:** `docs/ruby-ui-comparison.md` for full gap analysis.
6
+ **RubyUI source:** `~/work/ruby_ui/` (gem), `~/work/ruby_ui_web/` (docs/CSS)
7
+
8
+ ---
9
+
10
+ ## Phase 1: shadcn Token Layer + Dark Mode
11
+
12
+ **Outcome:** All existing components gain consistent colors, dark mode, and visual polish. Zero breaking changes.
13
+
14
+ ### 1.1 Create `shadcn_tokens.css`
15
+
16
+ Add to StreamWeaver's base CSS output. This defines the semantic color system that both pure CSS and Tailwind modes will share.
17
+
18
+ **Location:** `lib/stream_weaver/assets/shadcn_tokens.css`
19
+
20
+ ```css
21
+ :root {
22
+ --background: oklch(1 0 0);
23
+ --foreground: oklch(0.145 0 0);
24
+ --card: oklch(1 0 0);
25
+ --card-foreground: oklch(0.145 0 0);
26
+ --popover: oklch(1 0 0);
27
+ --popover-foreground: oklch(0.145 0 0);
28
+ --primary: oklch(0.205 0 0);
29
+ --primary-foreground: oklch(0.985 0 0);
30
+ --secondary: oklch(0.97 0 0);
31
+ --secondary-foreground: oklch(0.205 0 0);
32
+ --muted: oklch(0.97 0 0);
33
+ --muted-foreground: oklch(0.556 0 0);
34
+ --accent: oklch(0.97 0 0);
35
+ --accent-foreground: oklch(0.205 0 0);
36
+ --destructive: oklch(0.577 0.245 27.325);
37
+ --destructive-foreground: #fff;
38
+ --border: oklch(0.922 0 0);
39
+ --input: oklch(0.922 0 0);
40
+ --ring: oklch(0.708 0 0);
41
+ --radius: 0.5rem;
42
+ --warning: hsl(38 92% 50%);
43
+ --warning-foreground: #fff;
44
+ --success: hsl(87 100% 37%);
45
+ --success-foreground: #fff;
46
+ }
47
+
48
+ .dark {
49
+ --background: oklch(0.145 0 0);
50
+ --foreground: oklch(0.985 0 0);
51
+ --card: oklch(0.205 0 0);
52
+ --card-foreground: oklch(0.985 0 0);
53
+ --popover: oklch(0.205 0 0);
54
+ --popover-foreground: oklch(0.985 0 0);
55
+ --primary: oklch(0.922 0 0);
56
+ --primary-foreground: oklch(0.205 0 0);
57
+ --secondary: oklch(0.269 0 0);
58
+ --secondary-foreground: oklch(0.985 0 0);
59
+ --muted: oklch(0.269 0 0);
60
+ --muted-foreground: oklch(0.708 0 0);
61
+ --accent: oklch(0.269 0 0);
62
+ --accent-foreground: oklch(0.985 0 0);
63
+ --destructive: oklch(0.704 0.191 22.216);
64
+ --destructive-foreground: oklch(0.637 0.237 25.331);
65
+ --border: oklch(1 0 0 / 10%);
66
+ --input: oklch(1 0 0 / 15%);
67
+ --ring: oklch(0.556 0 0);
68
+ }
69
+ ```
70
+
71
+ **Integration with existing Theme system:**
72
+
73
+ Update `Theme::VARIABLE_SCHEMA` to map existing `--sw-` vars to shadcn tokens. When a StreamWeaver theme sets `color_primary`, it should also update `--primary`. This can be done in the CSS output:
74
+
75
+ ```css
76
+ :root {
77
+ --primary: var(--sw-color-primary, oklch(0.205 0 0));
78
+ --background: var(--sw-color-bg, oklch(1 0 0));
79
+ /* etc — shadcn tokens fall back to SW vars if set, else default */
80
+ }
81
+ ```
82
+
83
+ ### 1.2 Create `shadcn_base.css`
84
+
85
+ Global base styles that give everything the shadcn feel.
86
+
87
+ **Location:** `lib/stream_weaver/assets/shadcn_base.css`
88
+
89
+ ```css
90
+ /* Base reset for shadcn consistency */
91
+ *, *::before, *::after {
92
+ border-color: var(--border);
93
+ }
94
+
95
+ body {
96
+ background-color: var(--background);
97
+ color: var(--foreground);
98
+ }
99
+
100
+ /* Shared interactive states */
101
+ .sw-interactive {
102
+ transition: color 0.15s ease, background-color 0.15s ease,
103
+ border-color 0.15s ease, box-shadow 0.15s ease;
104
+ }
105
+
106
+ .sw-interactive:focus-visible {
107
+ outline: none;
108
+ box-shadow: 0 0 0 2px var(--background), 0 0 0 4px var(--ring);
109
+ }
110
+
111
+ .sw-interactive:disabled,
112
+ .sw-interactive[aria-disabled="true"] {
113
+ opacity: 0.5;
114
+ pointer-events: none;
115
+ cursor: not-allowed;
116
+ }
117
+ ```
118
+
119
+ ### 1.3 Update Component CSS
120
+
121
+ Migrate existing component styles from hardcoded colors to semantic tokens. Example diffs:
122
+
123
+ **Button (before):**
124
+ ```css
125
+ .sw-button { background: var(--sw-color-primary); color: #fff; border-radius: var(--sw-radius-md); }
126
+ .sw-button:hover { background: var(--sw-color-primary-hover); }
127
+ ```
128
+
129
+ **Button (after):**
130
+ ```css
131
+ .sw-button {
132
+ display: inline-flex; align-items: center; justify-content: center;
133
+ border-radius: var(--radius);
134
+ font-weight: 500; font-size: 0.875rem;
135
+ height: 2.25rem; padding: 0.5rem 1rem;
136
+ transition: color 0.15s, background-color 0.15s;
137
+ }
138
+ .sw-button:focus-visible {
139
+ outline: none;
140
+ box-shadow: 0 0 0 2px var(--background), 0 0 0 4px var(--ring);
141
+ }
142
+ .sw-button:disabled { opacity: 0.5; pointer-events: none; }
143
+
144
+ .sw-button--primary { background: var(--primary); color: var(--primary-foreground); }
145
+ .sw-button--primary:hover { filter: brightness(1.1); }
146
+
147
+ .sw-button--secondary { background: var(--secondary); color: var(--secondary-foreground); }
148
+ .sw-button--secondary:hover { filter: brightness(0.95); }
149
+
150
+ .sw-button--destructive { background: var(--destructive); color: var(--destructive-foreground); }
151
+ .sw-button--destructive:hover { filter: brightness(1.1); }
152
+
153
+ .sw-button--outline {
154
+ background: var(--background); color: var(--foreground);
155
+ border: 1px solid var(--input); box-shadow: 0 1px 2px rgba(0,0,0,0.05);
156
+ }
157
+ .sw-button--outline:hover { background: var(--accent); color: var(--accent-foreground); }
158
+
159
+ .sw-button--ghost { background: transparent; color: var(--foreground); }
160
+ .sw-button--ghost:hover { background: var(--accent); color: var(--accent-foreground); }
161
+
162
+ .sw-button--link { background: transparent; color: var(--primary); text-decoration-line: underline; text-underline-offset: 4px; }
163
+ ```
164
+
165
+ **Card (before):**
166
+ ```css
167
+ .card { background: var(--sw-color-bg-card); border: 1px solid var(--sw-color-border); border-radius: var(--sw-radius-lg); }
168
+ ```
169
+
170
+ **Card (after):**
171
+ ```css
172
+ .sw-card {
173
+ background: var(--card); color: var(--card-foreground);
174
+ border: 1px solid var(--border);
175
+ border-radius: calc(var(--radius) + 4px);
176
+ box-shadow: 0 1px 3px rgba(0,0,0,0.08);
177
+ }
178
+ .sw-card-header { padding: 1.5rem 1.5rem 0; display: flex; flex-direction: column; gap: 0.375rem; }
179
+ .sw-card-content { padding: 1.5rem; padding-top: 0; }
180
+ .sw-card-footer { padding: 0 1.5rem 1.5rem; display: flex; align-items: center; }
181
+ ```
182
+
183
+ **Apply this pattern to all components:**
184
+ - Alert → `var(--destructive)`, `var(--warning)`, `var(--success)`
185
+ - Badge → variant colors from tokens
186
+ - Input/TextArea → `var(--input)` border, `var(--ring)` focus
187
+ - Select/Dropdown → `var(--popover)` background
188
+ - Modal/Dialog → `var(--popover)` with backdrop
189
+ - Table → `var(--muted)` for header, `var(--border)` for rows
190
+ - Tabs → `var(--muted)` for inactive, `var(--background)` for active
191
+ - Sidebar → `var(--card)` or dedicated sidebar tokens
192
+
193
+ ### 1.4 Dark Mode Toggle
194
+
195
+ Update `ThemeSwitcher` component to support dark mode:
196
+
197
+ ```ruby
198
+ class ThemeSwitcher < Base
199
+ def render(view, state)
200
+ # Emit a button that toggles .dark class on <html> and persists to localStorage
201
+ view.adapter.render_theme_switcher(view, self, state)
202
+ end
203
+ end
204
+ ```
205
+
206
+ Add JS to the adapter output:
207
+ ```javascript
208
+ function toggleDarkMode() {
209
+ const html = document.documentElement;
210
+ html.classList.toggle('dark');
211
+ localStorage.setItem('theme', html.classList.contains('dark') ? 'dark' : 'light');
212
+ }
213
+
214
+ // On load: respect saved preference or system preference
215
+ (function() {
216
+ if (localStorage.theme === 'dark' ||
217
+ (!('theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
218
+ document.documentElement.classList.add('dark');
219
+ }
220
+ })();
221
+ ```
222
+
223
+ ### 1.5 Verify with Existing Apps
224
+
225
+ Run existing StreamWeaver examples/apps and confirm:
226
+ - Colors look correct with new tokens
227
+ - Dark mode works
228
+ - No visual regressions
229
+ - Existing `register_theme` still works (SW vars feed into shadcn tokens)
230
+
231
+ ---
232
+
233
+ ## Phase 2: Extract Adapter Pattern
234
+
235
+ **Outcome:** Clean separation between component logic (DSL) and rendering (adapter).
236
+
237
+ ### 2.1 Formalize BaseAdapter
238
+
239
+ Ensure all component rendering goes through adapter methods:
240
+
241
+ ```ruby
242
+ module StreamWeaver
243
+ class BaseAdapter
244
+ def render_button(view, component, state); end
245
+ def render_card(view, component, state); end
246
+ def render_input(view, component, state); end
247
+ # ... one method per component
248
+ end
249
+ end
250
+ ```
251
+
252
+ ### 2.2 Rename Current Adapter → PureCSSAdapter
253
+
254
+ The existing AlpineJS adapter becomes `PureCSSAdapter` (or `AlpineJSAdapter` stays but internally uses pure CSS classes).
255
+
256
+ ### 2.3 Configuration
257
+
258
+ ```ruby
259
+ StreamWeaver.configure do |config|
260
+ config.adapter = :alpine_js # default, uses pure CSS classes
261
+ # or
262
+ config.adapter = :tailwind # uses Tailwind utility classes
263
+ end
264
+ ```
265
+
266
+ ---
267
+
268
+ ## Phase 3: Tailwind Adapter
269
+
270
+ **Outcome:** Full shadcn fidelity when Tailwind is available.
271
+
272
+ ### 3.1 Add `tailwind_merge` Gem (Optional Dependency)
273
+
274
+ ```ruby
275
+ # stream_weaver.gemspec
276
+ spec.add_development_dependency 'tailwind_merge', '~> 1.0'
277
+ ```
278
+
279
+ ### 3.2 Build TailwindAdapter
280
+
281
+ Port RubyUI's class strings directly. The classes are already defined in `~/work/ruby_ui/lib/ruby_ui/button/button.rb`, etc.
282
+
283
+ ```ruby
284
+ module StreamWeaver
285
+ class TailwindAdapter < BaseAdapter
286
+ MERGER = TailwindMerge::Merger.new.freeze
287
+
288
+ BUTTON_BASE = "inline-flex items-center justify-center rounded-md font-medium transition-colors " \
289
+ "focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring " \
290
+ "disabled:pointer-events-none disabled:opacity-50"
291
+
292
+ BUTTON_VARIANTS = {
293
+ primary: "bg-primary text-primary-foreground shadow hover:bg-primary/90",
294
+ secondary: "bg-secondary text-secondary-foreground hover:bg-opacity-80",
295
+ destructive: "bg-destructive text-white shadow-sm hover:bg-destructive/90",
296
+ outline: "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
297
+ ghost: "hover:bg-accent hover:text-accent-foreground",
298
+ link: "text-primary underline-offset-4 hover:underline"
299
+ }.freeze
300
+
301
+ BUTTON_SIZES = {
302
+ sm: "px-3 py-1.5 h-8 text-xs",
303
+ md: "px-4 py-2 h-9 text-sm",
304
+ lg: "px-4 py-2 h-10 text-base",
305
+ xl: "px-6 py-3 h-12 text-base"
306
+ }.freeze
307
+
308
+ def render_button(view, component, state)
309
+ classes = MERGER.merge([
310
+ BUTTON_BASE,
311
+ BUTTON_VARIANTS[component.variant] || BUTTON_VARIANTS[:primary],
312
+ BUTTON_SIZES[component.size] || BUTTON_SIZES[:md],
313
+ component.options[:class]
314
+ ].compact.join(" "))
315
+
316
+ view.button(class: classes, type: "button", **data_attrs(component)) do
317
+ yield if block_given?
318
+ end
319
+ end
320
+
321
+ # ... similar for Card, Input, Alert, etc.
322
+ # Port class strings from ~/work/ruby_ui/lib/ruby_ui/*/
323
+ end
324
+ end
325
+ ```
326
+
327
+ ### 3.3 Tailwind CSS Requirement
328
+
329
+ When using the Tailwind adapter, the output HTML needs Tailwind CSS loaded. Options:
330
+ - CDN link (easiest for development)
331
+ - Bundled Tailwind (for production)
332
+ - User provides their own Tailwind setup
333
+
334
+ The adapter should inject the appropriate `<link>` or `<script>` in the HTML head.
335
+
336
+ Tailwind v4 config needed for shadcn tokens:
337
+ ```css
338
+ @theme inline {
339
+ --color-background: var(--background);
340
+ --color-foreground: var(--foreground);
341
+ --color-primary: var(--primary);
342
+ --color-primary-foreground: var(--primary-foreground);
343
+ /* ... */
344
+ }
345
+ ```
346
+
347
+ ---
348
+
349
+ ## Phase 4: Port High-Value Components
350
+
351
+ Port these from RubyUI, adapting for StreamWeaver's DSL pattern:
352
+
353
+ ### Priority 1 (most impact):
354
+ | Component | RubyUI Source | DSL Example |
355
+ |-----------|--------------|-------------|
356
+ | **Sheet** | `ruby_ui/sheet/` | `sheet(:right) { text "Details..." }` |
357
+ | **Skeleton** | `ruby_ui/skeleton/` | `skeleton width: "100%", height: "20px"` |
358
+ | **Command** | `ruby_ui/command/` | `command_palette { item "Search...", shortcut: "⌘K" }` |
359
+ | **Clipboard** | `ruby_ui/clipboard/` | `clipboard "code to copy"` |
360
+
361
+ ### Priority 2 (nice to have):
362
+ | Component | RubyUI Source | DSL Example |
363
+ |-----------|--------------|-------------|
364
+ | **Combobox** | `ruby_ui/combobox/` | `combobox :user, choices, searchable: true` |
365
+ | **Calendar** | `ruby_ui/calendar/` | `calendar :date, range: true` |
366
+ | **AlertDialog** | `ruby_ui/alert_dialog/` | `alert_dialog "Are you sure?", confirm: "Delete"` |
367
+ | **HoverCard** | `ruby_ui/hover_card/` | `hover_card { text "Preview content" }` |
368
+
369
+ ### Implementation Pattern
370
+
371
+ For each component:
372
+ 1. Read RubyUI's Phlex component for structure
373
+ 2. Define StreamWeaver DSL method (in `components.rb` or new file)
374
+ 3. Add `render_*` method to PureCSSAdapter with CSS-variable styling
375
+ 4. Add `render_*` method to TailwindAdapter with RubyUI's exact class strings
376
+ 5. Add CSS for the pure CSS version to `shadcn_components.css`
377
+
378
+ ---
379
+
380
+ ## File Summary
381
+
382
+ ```
383
+ lib/stream_weaver/
384
+ ├── assets/
385
+ │ ├── shadcn_tokens.css # Phase 1: semantic color tokens + dark mode
386
+ │ ├── shadcn_base.css # Phase 1: global base styles
387
+ │ └── shadcn_components.css # Phase 1: component styles using tokens
388
+ ├── adapters/
389
+ │ ├── base_adapter.rb # Phase 2: abstract adapter interface
390
+ │ ├── alpine_js_adapter.rb # Phase 2: existing adapter (pure CSS classes)
391
+ │ └── tailwind_adapter.rb # Phase 3: Tailwind utility classes
392
+ ├── components.rb # Existing + Phase 4 new components
393
+ └── theme.rb # Updated to bridge --sw-* → shadcn tokens
394
+ ```
395
+
396
+ ---
397
+
398
+ ## Quick Start (for Claude Code session)
399
+
400
+ ```bash
401
+ cd ~/work/rstreamlit/stream_weaver
402
+
403
+ # Reference material:
404
+ # - RubyUI gem source: ~/work/ruby_ui/lib/ruby_ui/
405
+ # - RubyUI CSS tokens: ~/work/ruby_ui_web/app/assets/stylesheets/application.tailwind.css
406
+ # - Current SW theme: lib/stream_weaver/theme.rb
407
+ # - Current SW components: lib/stream_weaver/components.rb
408
+ # - Current SW adapter: lib/stream_weaver/adapter/
409
+ # - Full comparison: docs/ruby-ui-comparison.md
410
+
411
+ # Start with Phase 1:
412
+ # 1. Create lib/stream_weaver/assets/shadcn_tokens.css
413
+ # 2. Create lib/stream_weaver/assets/shadcn_base.css
414
+ # 3. Update component CSS to use var(--primary) etc.
415
+ # 4. Add dark mode toggle JS
416
+ # 5. Test with existing examples
417
+ ```
@@ -0,0 +1,252 @@
1
+ # Porting a Claude Artifact into StreamWeaver
2
+
3
+ A repeatable process for reproducing a claude.ai-hosted Artifact (or any
4
+ rich static HTML/CSS document) as a StreamWeaver app at high visual
5
+ fidelity, and for feeding what you learn back into the framework. Proven
6
+ on `examples/components/design_review_dsl.rb` / `design_review.css` (a
7
+ six-option design-review document with option cards, chips, a comparison
8
+ matrix, and a checklist split) -- see that pair, plus the `doc-parity`
9
+ example bundled in the `streamweaver-visual-companion` skill, for two
10
+ different genres this process has produced.
11
+
12
+ This is a process doc, not a tutorial -- it assumes familiarity with
13
+ `docs/theming-hooks.md` (the sw- hook contract) and the component/DSL
14
+ vocabulary in `lib/stream_weaver/display_dsl.rb`.
15
+
16
+ ## The steps
17
+
18
+ ### a. Fetch the artifact HTML, strip the frame-runtime preamble
19
+
20
+ A claude.ai Artifact page is served inside a "frame-runtime" wrapper: a
21
+ large minified script handling theme sync with the parent claude.ai
22
+ shell, scroll-position restore, link-click interception, MCP capability
23
+ proxying, and an RTC lockdown. None of that is the authored content --
24
+ it's platform plumbing for embedding the page in an iframe. The authored
25
+ document always starts at the page's own `<title>` tag and the `<style>`
26
+ block that follows it. Locate that boundary before doing anything else;
27
+ everything above it is safe to ignore entirely.
28
+
29
+ ### b. Dissect into a rendering-capability checklist
30
+
31
+ Read the authored `<style>` block and body once, closely, and produce two
32
+ things:
33
+
34
+ 1. **Exact palettes.** Transcribe every custom property in both the light
35
+ and dark color sets verbatim (hex values, not approximations) -- these
36
+ become CSS custom properties in your own port, prefixed to avoid
37
+ collision with StreamWeaver's own `--sw-*` tokens (this port used
38
+ `--ad-*`; pick a prefix tied to the artifact's subject, not
39
+ StreamWeaver's).
40
+ 2. **A numbered checklist of rendering techniques**, not a prose
41
+ description -- "bordered callout with colored left accent + mono
42
+ kicker label", "option card: badge + title/subtitle + optional
43
+ pinned tag + wrapping chip row", "checklist tile: bordered card with
44
+ fixed-width colored glyph column", etc. Each item should be scoped
45
+ tightly enough that step (c) can answer "component, CSS, or gap?" for
46
+ it individually. The design-review port used 15 such items.
47
+
48
+ ### c. Gap-assess against the component vocabulary
49
+
50
+ For each checklist item, check in this order:
51
+
52
+ 1. `docs/theming-hooks.md` -- does an existing component already emit a
53
+ stable `sw-` hook you can restyle? (Coverage note: that doc lists the
54
+ structural components; the doc-component family -- `doc_header`,
55
+ `doc_section_header`, `callout`, `columns`, `markdown` -- also emits
56
+ stable hooks not yet listed there (stream_weaver-d11). When a component
57
+ is missing from the doc, read the classes the adapter actually emits.)
58
+ 2. The doc/component family (`doc_header`, `doc_section_header`,
59
+ `callout`, `card`/`card_header`/`card_body`, `table`, `comparison`,
60
+ `columns`/`column`, `status_dot`, `badge`) -- proven to reach 1:1
61
+ Artifact parity for editorial documents (`doc-parity-example`).
62
+ 3. A prior parity slice (`examples/parity/tyrion_warroom_components.rb`)
63
+ for anything board/lane/topbar/navbar-shaped.
64
+ 4. Only if none of the above expresses it: `wireframe`/`wireframe_block
65
+ (html:)` for raw HTML/SVG passthrough. This is a last resort, not a
66
+ starting point -- see the "sanctioned escape hatches inside existing
67
+ components" note below before reaching for it.
68
+
69
+ **Sanctioned escape hatches inside existing components, short of
70
+ `wireframe_block`:**
71
+
72
+ - `table(..., markdown: true)` renders each cell's markdown-processed
73
+ HTML *raw* -- already used by the framework to turn `[text](url)` into
74
+ `<a>` tags. A cell that needs a colored status dot or any other small
75
+ inline markup (not achievable through the table's own options) can emit
76
+ that markup directly as a string, e.g. a rating cell built from
77
+ `%(<span class="sw-status-dot sw-status-dot-green sw-status-dot-sm">
78
+ </span>#{value})`. This is a real, intentional feature of `Table`, not
79
+ a workaround -- prefer it over `wireframe_block` for anything
80
+ table-cell-shaped.
81
+ - A `card_header`/`callout`/any container's block accepts arbitrary DSL
82
+ calls as extra children, rendered after that component's own built-in
83
+ fields. Use this to add a pinned corner badge, a markdown-rendered
84
+ subtitle (see gap notes below on why `card_header`'s own `meta:` can't
85
+ hold markdown), or any other small addition -- then position it with
86
+ CSS rather than fighting the DSL for a parameter that doesn't exist.
87
+
88
+ ### d. Port via the shared-DSL pattern + one unlayered CSS file
89
+
90
+ Follow `prd_dsl.rb`/`prd_demo.rb`'s split:
91
+
92
+ - `<name>_dsl.rb` -- a bare DSL body (no `app` wrapper), safe to load via
93
+ `instance_eval` from either a standalone demo or `canvas-push`. Caveat:
94
+ the canvas bridge currently injects only master-theme CSS, so a port
95
+ whose look lives in its own stylesheet renders on a canvas as correct
96
+ structure without the re-skin (stream_weaver-9uk) -- the standalone demo
97
+ is the faithful rendering until that lands or the genre's vocabulary is
98
+ promoted into a theme (step g).
99
+ - `<name>_demo.rb` -- the standalone wrapper.
100
+ - `<name>.css` -- **one** stylesheet, targeting only the `sw-` hooks
101
+ documented in `docs/theming-hooks.md` plus your own invented classes for
102
+ the handful of elements with no dedicated component.
103
+
104
+ Every framework-emitted style lives in `@layer stream-weaver` (since
105
+ stream_weaver-oeo), so a plain unlayered stylesheet always wins,
106
+ regardless of selector specificity or `<head>` document order. There is
107
+ no specificity fight to plan around -- write the CSS you want the
108
+ rendered page to have, full stop.
109
+
110
+ **The chrome-neutralization recipe.** StreamWeaver's default chrome wraps
111
+ app content in `body.sw-layout-default` (caps width, adds padding) plus a
112
+ child `#app-container` div that paints its *own* background/border/shadow
113
+ from theme tokens (`--sw-color-bg-card`, etc.). If your port's content
114
+ column is meant to be the *entire* page (an editorial document, not a
115
+ dashboard with chrome around it), both of those need neutralizing or
116
+ they'll show through as a visible "card within a card" boundary and can
117
+ silently reintroduce the base theme's own light/dark palette underneath
118
+ your own:
119
+
120
+ ```css
121
+ body[class*="sw-layout-"] {
122
+ max-width: none;
123
+ padding: 0;
124
+ background: var(--your-bg-token);
125
+ }
126
+ #app-container {
127
+ background: transparent;
128
+ padding: 0;
129
+ margin: 0;
130
+ border: none;
131
+ box-shadow: none;
132
+ border-radius: 0;
133
+ max-width: none;
134
+ }
135
+ ```
136
+
137
+ Let your own top-level wrapper div (e.g. `.ad-doc`) become the sole
138
+ layout/background authority: its own `max-width`/`margin: 0 auto`/padding
139
+ replaces what `body`/`#app-container` used to provide.
140
+
141
+ **A related gotcha worth checking early:** if your port uses
142
+ `app(...) do ... end` (the top-level convenience wrapper) with a local
143
+ `stylesheets:` path, `App#script_dir` resolves to the wrapper's own call
144
+ site inside `lib/stream_weaver.rb`, not your script -- the local-file
145
+ auto-detection raises `ArgumentError: ... resolves outside the app's
146
+ script directory`. Use `StreamWeaver::App.new(...) do ... end` +
147
+ `App.generate.run!` directly (the pattern
148
+ `examples/parity/tyrion_warroom_components.rb` already uses), with an
149
+ explicit `assets_dirs: [__dir__]` -- the top-level helper's fixed kwarg
150
+ list has no `assets_dirs:` param at all, so this isn't optional once
151
+ `stylesheets:` points at a local file.
152
+
153
+ ### e. Main-thread browser verification against the reference -- never builder self-assessment
154
+
155
+ The agent that builds the port should verify the server renders without
156
+ exceptions (boot it, curl it, grep the response for the structural
157
+ markers you expect, kill the boot). It should **not** be the one to judge
158
+ visual fidelity. A builder that wrote the CSS is the worst-positioned
159
+ reviewer of whether it actually looks right -- render a page confidently
160
+ wrong and re-reading your own rules won't catch it. Visual sign-off needs
161
+ a separate pass, on the main thread, with both the port and the original
162
+ artifact open side by side, in both light and dark mode.
163
+
164
+ Two sharp edges specific to this workflow:
165
+
166
+ - `playwright-cli` (and most headless browser tooling) blocks `file://`
167
+ navigation by default. Serve the reference artifact HTML over a real
168
+ origin instead: `ruby -run -e httpd <dir-containing-the-html> -p <port>`
169
+ from Ruby's stdlib, no extra gem needed.
170
+ - Verify `location.href` in the browser before trusting a screenshot --
171
+ a stale tab left open from a prior round, or a redirect, will silently
172
+ screenshot the wrong page. Similarly, dark-mode toggles that persist
173
+ their preference to `localStorage` will carry a prior round's choice
174
+ into the next one -- clear it (or use a fresh browser context) before
175
+ comparing light mode again, or you will spend time debugging a "bug"
176
+ that's actually stale toggle state.
177
+
178
+ ### f. File framework gaps as bugs; fix at the app level first
179
+
180
+ A port will surface real gaps -- places where an existing component is
181
+ missing a `class:`/`style:` passthrough, has no per-row/per-item
182
+ modifier hook, or where a convenience wrapper (like the top-level `app`
183
+ helper above) breaks under a combination of options nobody had hit
184
+ before. File each one as its own issue (this port's run filed
185
+ stream_weaver-0n6, stream_weaver-mcn, stream_weaver-rhi, and
186
+ stream_weaver-t37) rather than silently working around it and moving on
187
+ -- the workaround belongs in the port either way (you can't block a port
188
+ on a framework fix), but the gap needs to be visible to whoever plans the
189
+ next promotion pass. Examples from this port:
190
+
191
+ | Gap | Component | Workaround used |
192
+ |---|---|---|
193
+ | No per-row `class:`/tone hook | `Table` | Rely on the "pick" row being deterministically last (`tr:last-child`) |
194
+ | No `class:`/`style:` passthrough | `Callout` | Wrap in a scoping `div` + descendant-selector CSS |
195
+ | `meta:` renders escaped plain text, no markdown | `CardHeader` | Render the subtitle as an extra block child through `md` instead |
196
+ | `stylesheets:` + top-level `app()` helper resolves `script_dir` wrong | `App` / top-level `app()` | Use `StreamWeaver::App.new` directly with `assets_dirs:` |
197
+
198
+ ### g. The promotion pass -- this is where token savings actually come from
199
+
200
+ Be honest about the economics before claiming a port was cheap. This
201
+ port's `design_review.css` + `design_review_dsl.rb` totals roughly 36.5KB
202
+ of Ruby+CSS against the source artifact's 37.3KB of authored HTML+CSS --
203
+ essentially 1:1, not a savings. That's expected: the artifact's palette,
204
+ type pairing, chip/card idiom, and layout are all *bespoke to that one
205
+ document*, so nothing in this port's CSS was reusable from an existing
206
+ theme -- every rule had to be written fresh. Compare that to the
207
+ `doc-parity-example` port, which reached the same 1:1 visual fidelity at
208
+ roughly a 6x size reduction, because it could lean on the already-built
209
+ `:doc` theme (Charter serif, compact editorial spacing, `--sw-color-*`
210
+ tokens) almost as-is and only needed a handful of component calls on top.
211
+
212
+ The lesson: a single port's cost is dominated by however much of its
213
+ visual vocabulary is genuinely new. The savings show up the *second*
214
+ time a similar genre gets ported -- if, and only if, someone takes the
215
+ promotion step of pulling the recurring parts (the chip pattern, the
216
+ pick-state idiom, the checklist-tile pattern, the chrome-neutralization
217
+ recipe above) out of the one-off app CSS and into either a registered
218
+ theme (`StreamWeaver.register_theme`) or genuinely new components. Budget
219
+ for this as a distinct follow-up step, not something that happens for
220
+ free as a side effect of shipping the first port. Until it happens, treat
221
+ "tokens saved vs. hand-authoring the HTML" as *prose-only* savings (you
222
+ still write the artifact's text once, not twice) rather than a savings on
223
+ the surrounding visual machinery.
224
+
225
+ ### h. Add the pair to the visual-companion skill gallery
226
+
227
+ Once a port is genericized (see below) and verified, copy the DSL/demo/CSS
228
+ trio into
229
+ `lib/stream_weaver/skills/streamweaver-visual-companion/examples/` so it
230
+ ships with the skill (duplicated, not symlinked, so the skill stays
231
+ self-contained if packaged separately -- see that directory's existing
232
+ files for why). Add one line to that skill's `SKILL.md` gallery index
233
+ naming the genre and the techniques it proves out, so a future session
234
+ can tell at a glance which example is the closer starting point for a new
235
+ request.
236
+
237
+ ## A note on genericizing content before it ships
238
+
239
+ If the artifact you're porting contains anything personal or
240
+ organization-specific (real names, an employer, an internal tool's real
241
+ name, a personal design-philosophy framework), rewrite it as
242
+ fictional-but-plausible content *before* committing -- same standard
243
+ `prd_dsl.rb` already holds itself to (invented company, invented author).
244
+ Preserve every visual pattern exactly (same number of option cards, same
245
+ chip dimensions per card, same table shapes, same checklist tile count) --
246
+ only the words change. Rename the example's own files at the same time if
247
+ their name was content-derived (an artifact literally about "agent
248
+ discovery" produced `agent_discovery_*` files; once genericized to a
249
+ fictional product, the files were renamed to the genre-based
250
+ `design_review_*` to match how `doc-parity-example` is named after its
251
+ *genre* -- a document proving Artifact-grade doc parity -- not its
252
+ original PRD's subject matter).