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,515 @@
1
+ # Weaving Web UIs with Ruby: A StreamWeaver Introduction
2
+
3
+ *Build reactive web interfaces with pure Ruby—no JavaScript required.*
4
+
5
+ ![StreamWeaver Hero](/images/heros/streamweaver-hero.png)
6
+ <!-- Hero image: Terminal showing StreamWeaver app code on left, browser with running app on right -->
7
+
8
+ ## A Personal Confession
9
+
10
+ I've spent more hours than I care to admit building internal tools. Dashboards for monitoring. Forms for data entry. Quick interfaces to wrap API calls. Every time, the same friction: set up a JavaScript build pipeline, configure a frontend framework, wire up API endpoints, manage state synchronization between client and server.
11
+
12
+ For a "simple" tool.
13
+
14
+ Python developers had Streamlit. They could write a script, sprinkle in some UI components, and have a working web app in minutes. Meanwhile, Ruby developers—blessed with one of the most expressive languages ever created—were still configuring Webpack.
15
+
16
+ StreamWeaver changes that. It's Streamlit's ease of use, brought to Ruby with a DSL that feels like it belongs in the language.
17
+
18
+ ## What is StreamWeaver?
19
+
20
+ StreamWeaver is a Ruby DSL for building reactive web UIs with minimal code. Write your interface in pure Ruby. Run the script. A browser opens with your working application.
21
+
22
+ ```ruby
23
+ require 'stream_weaver'
24
+
25
+ app "Hello World" do
26
+ text_field :name, placeholder: "What's your name?"
27
+
28
+ if state[:name].to_s.strip != ""
29
+ text "Hello, #{state[:name]}!"
30
+ end
31
+ end.run!
32
+ ```
33
+
34
+ ![Hello World Example](/images/streamweaver/hello-world.png)
35
+ <!-- Screenshot: Browser showing text input and greeting -->
36
+
37
+ That's it. No HTML templates. No JavaScript. No build step. No webpack. No npm. Just Ruby.
38
+
39
+ ## The Key Insight
40
+
41
+ Here's the magic that makes StreamWeaver tick:
42
+
43
+ **Your Ruby block re-executes on every user interaction.**
44
+
45
+ When a user types in a text field, clicks a button, or changes a selection, your entire block runs again with the updated state. This creates reactive UI without you writing any event handling code.
46
+
47
+ ```ruby
48
+ app "Counter" do
49
+ state[:count] ||= 0
50
+
51
+ text "Count: #{state[:count]}"
52
+
53
+ button "+" do |s|
54
+ s[:count] += 1
55
+ end
56
+
57
+ button "-" do |s|
58
+ s[:count] -= 1
59
+ end
60
+ end.run!
61
+ ```
62
+
63
+ ![Counter Example](/images/streamweaver/counter.png)
64
+ <!-- Screenshot: Counter UI with + and - buttons -->
65
+
66
+ Click the "+" button:
67
+ 1. The callback runs: `s[:count] += 1`
68
+ 2. Your entire block re-executes
69
+ 3. `text "Count: #{state[:count]}"` now shows the new value
70
+ 4. The browser updates automatically
71
+
72
+ No manual DOM manipulation. No state synchronization. No WebSocket configuration. StreamWeaver handles all of it.
73
+
74
+ ## Why Ruby Needs This
75
+
76
+ Ruby's philosophy has always been developer happiness. We optimize for expressiveness, readability, and joy. Rails brought this philosophy to web applications. Hotwire brought it to interactive features.
77
+
78
+ But there's a gap. When you need a quick internal tool, a prototype, or a simple UI for a script—Rails is overkill. You don't need models, migrations, and a full MVC stack for a form that calls an API.
79
+
80
+ StreamWeaver fills that gap:
81
+
82
+ | Need | Traditional Approach | StreamWeaver |
83
+ |------|---------------------|--------------|
84
+ | Quick form UI | Rails scaffold + views + JS | 10 lines of Ruby |
85
+ | Data dashboard | React app + API | Single Ruby file |
86
+ | Script with UI | CLI flags or Tk | Declarative DSL |
87
+ | Prototype | Full stack setup | Immediate iteration |
88
+
89
+ ### The GenAI Advantage
90
+
91
+ We're in an era where AI assistants write significant portions of our code. Token efficiency matters. The more concise your DSL, the more the AI can accomplish in a single context window.
92
+
93
+ StreamWeaver's declarative approach is token-efficient by design. Instead of describing separate model, view, controller, and JavaScript layers, you describe the UI once. The AI (and you) can iterate faster.
94
+
95
+ ## The Component Library
96
+
97
+ StreamWeaver provides all the building blocks you need for real applications.
98
+
99
+ ### Input Components
100
+
101
+ ```ruby
102
+ app "Form Demo" do
103
+ text_field :name, placeholder: "Your name"
104
+ text_area :bio, rows: 4, placeholder: "Tell us about yourself"
105
+ checkbox :newsletter, "Subscribe to newsletter"
106
+ select :role, ["Developer", "Designer", "Manager"]
107
+ radio_group :priority, ["Low", "Medium", "High"]
108
+ end.run!
109
+ ```
110
+
111
+ ![Form Components](/images/streamweaver/form-components.png)
112
+ <!-- Screenshot: Form with all input types displayed -->
113
+
114
+ Each component automatically binds to `state[:key]`. When the user types in `text_field :name`, the value is available as `state[:name]`.
115
+
116
+ ### Display Components
117
+
118
+ ```ruby
119
+ app "Display Demo" do
120
+ header1 "Page Title"
121
+ header "Section Header"
122
+ header3 "Subsection"
123
+
124
+ text "Plain text with interpolation: #{Time.now}"
125
+ md "**Markdown** support with *formatting* and `code`"
126
+ end.run!
127
+ ```
128
+
129
+ ![Display Components](/images/streamweaver/display-components.png)
130
+ <!-- Screenshot: Various header sizes and text styles -->
131
+
132
+ ### Buttons & Actions
133
+
134
+ ```ruby
135
+ app "Actions" do
136
+ state[:message] ||= "Click a button"
137
+
138
+ text state[:message]
139
+
140
+ button "Primary Action" do |s|
141
+ s[:message] = "Primary clicked!"
142
+ end
143
+
144
+ button "Secondary", style: :secondary do |s|
145
+ s[:message] = "Secondary clicked!"
146
+ end
147
+
148
+ button "Danger", style: :danger do |s|
149
+ s[:message] = "Danger clicked!"
150
+ end
151
+ end.run!
152
+ ```
153
+
154
+ ![Button Styles](/images/streamweaver/buttons.png)
155
+ <!-- Screenshot: Three button styles in a row -->
156
+
157
+ ### Layout Components
158
+
159
+ StreamWeaver provides flexible layout primitives:
160
+
161
+ ```ruby
162
+ app "Layout Demo" do
163
+ columns widths: ['30%', '70%'] do
164
+ column do
165
+ card do
166
+ header3 "Sidebar"
167
+ text "Navigation goes here"
168
+ end
169
+ end
170
+
171
+ column do
172
+ vstack spacing: :md do
173
+ card do
174
+ header3 "Main Content"
175
+ text "Your primary content area"
176
+ end
177
+
178
+ hstack spacing: :sm do
179
+ button "Save"
180
+ button "Cancel", style: :secondary
181
+ end
182
+ end
183
+ end
184
+ end
185
+ end.run!
186
+ ```
187
+
188
+ ![Layout Example](/images/streamweaver/layout.png)
189
+ <!-- Screenshot: Two-column layout with sidebar and main content -->
190
+
191
+ **Layout primitives:**
192
+ - `columns` - Multi-column layouts with custom widths
193
+ - `vstack` - Vertical stacking with spacing
194
+ - `hstack` - Horizontal stacking with spacing
195
+ - `card` - Bordered container with padding
196
+
197
+ ### Modals & Dialogs
198
+
199
+ ```ruby
200
+ app "Modal Demo" do
201
+ button "Open Settings" do |s|
202
+ s[:settings_open] = true
203
+ end
204
+
205
+ modal :settings, title: "Settings", size: :lg do
206
+ text_field :api_key, placeholder: "Enter API key"
207
+ checkbox :dark_mode, "Enable dark mode"
208
+
209
+ modal_footer do
210
+ button "Save" do |s|
211
+ s[:settings_open] = false
212
+ # Save logic here
213
+ end
214
+ button "Cancel", style: :secondary do |s|
215
+ s[:settings_open] = false
216
+ end
217
+ end
218
+ end
219
+ end.run!
220
+ ```
221
+
222
+ ![Modal Example](/images/streamweaver/modal.png)
223
+ <!-- Screenshot: Open modal dialog over main content -->
224
+
225
+ ### Feedback Components
226
+
227
+ ```ruby
228
+ app "Feedback Demo" do
229
+ state[:status] ||= nil
230
+
231
+ button "Submit" do |s|
232
+ # Simulate operation
233
+ s[:status] = rand > 0.5 ? :success : :error
234
+ end
235
+
236
+ case state[:status]
237
+ when :success
238
+ alert(variant: :success) { text "Operation completed successfully!" }
239
+ when :error
240
+ alert(variant: :error) { text "Something went wrong. Please try again." }
241
+ end
242
+ end.run!
243
+ ```
244
+
245
+ ![Alert Variants](/images/streamweaver/alerts.png)
246
+ <!-- Screenshot: Success and error alerts -->
247
+
248
+ **Alert variants:** `:info`, `:success`, `:warning`, `:error`
249
+
250
+ ## Common Patterns
251
+
252
+ ### Conditional Display
253
+
254
+ Show different UI based on state—a pattern that feels natural in Ruby:
255
+
256
+ ```ruby
257
+ app "Login Flow" do
258
+ if state[:authenticated]
259
+ text "Welcome back, #{state[:username]}!"
260
+
261
+ button "Logout" do |s|
262
+ s[:authenticated] = false
263
+ s[:username] = nil
264
+ end
265
+ else
266
+ text_field :username, placeholder: "Username"
267
+ text_field :password, placeholder: "Password"
268
+
269
+ button "Login" do |s|
270
+ # Validate credentials
271
+ s[:authenticated] = true
272
+ end
273
+ end
274
+ end.run!
275
+ ```
276
+
277
+ ![Login Flow](/images/streamweaver/login-flow.png)
278
+ <!-- Screenshot: Side-by-side of logged out and logged in states -->
279
+
280
+ ### Dynamic Lists
281
+
282
+ Build, modify, and display lists with standard Ruby iteration:
283
+
284
+ ```ruby
285
+ app "Todo List" do
286
+ state[:todos] ||= []
287
+
288
+ text_field :new_todo, placeholder: "What needs doing?"
289
+
290
+ button "Add" do |s|
291
+ if s[:new_todo].to_s.strip != ""
292
+ s[:todos] << { text: s[:new_todo], done: false }
293
+ s[:new_todo] = ""
294
+ end
295
+ end
296
+
297
+ state[:todos].each_with_index do |todo, i|
298
+ hstack do
299
+ checkbox :"done_#{i}", "" do |s, checked|
300
+ s[:todos][i][:done] = checked
301
+ end
302
+
303
+ text todo[:text],
304
+ style: todo[:done] ? "text-decoration: line-through; opacity: 0.6" : ""
305
+
306
+ button "X", style: :danger, size: :sm do |s|
307
+ s[:todos].delete_at(i)
308
+ end
309
+ end
310
+ end
311
+
312
+ if state[:todos].any?
313
+ text "#{state[:todos].count { |t| t[:done] }} of #{state[:todos].size} completed"
314
+ end
315
+ end.run!
316
+ ```
317
+
318
+ ![Todo List](/images/streamweaver/todo-list.png)
319
+ <!-- Screenshot: Todo list with items in various states -->
320
+
321
+ ### Multi-Step Wizards
322
+
323
+ Guide users through complex flows:
324
+
325
+ ```ruby
326
+ app "Setup Wizard" do
327
+ state[:step] ||= 1
328
+
329
+ case state[:step]
330
+ when 1
331
+ header "Step 1: Account"
332
+ text_field :email, placeholder: "Email"
333
+ text_field :name, placeholder: "Full name"
334
+
335
+ button "Next" do |s|
336
+ s[:step] = 2
337
+ end
338
+
339
+ when 2
340
+ header "Step 2: Preferences"
341
+ select :theme, ["Light", "Dark", "System"]
342
+ checkbox :notifications, "Enable notifications"
343
+
344
+ hstack do
345
+ button "Back", style: :secondary do |s|
346
+ s[:step] = 1
347
+ end
348
+ button "Next" do |s|
349
+ s[:step] = 3
350
+ end
351
+ end
352
+
353
+ when 3
354
+ header "Step 3: Confirm"
355
+ text "Email: #{state[:email]}"
356
+ text "Name: #{state[:name]}"
357
+ text "Theme: #{state[:theme]}"
358
+ text "Notifications: #{state[:notifications] ? 'Yes' : 'No'}"
359
+
360
+ hstack do
361
+ button "Back", style: :secondary do |s|
362
+ s[:step] = 2
363
+ end
364
+ button "Complete Setup" do |s|
365
+ s[:complete] = true
366
+ end
367
+ end
368
+ end
369
+
370
+ if state[:complete]
371
+ alert(variant: :success) { text "Setup complete! Welcome aboard." }
372
+ end
373
+ end.run!
374
+ ```
375
+
376
+ ![Wizard Flow](/images/streamweaver/wizard.png)
377
+ <!-- Screenshot: Multi-step wizard showing step 2 -->
378
+
379
+ ## Agentic Mode: One-Shot UIs
380
+
381
+ StreamWeaver isn't just for persistent applications. It excels at one-shot UIs where you need user input mid-script:
382
+
383
+ ```ruby
384
+ # Script that needs user input
385
+ result = app "Quick Survey" do
386
+ header "Before we continue..."
387
+
388
+ text_field :project_name, placeholder: "Project name"
389
+ select :priority, ["Low", "Medium", "High", "Critical"]
390
+ text_area :notes, placeholder: "Any additional notes?"
391
+ end.run_once!(auto_close_window: true)
392
+
393
+ # Script continues with the data
394
+ puts "Creating project: #{result['project_name']}"
395
+ puts "Priority: #{result['priority']}"
396
+ ```
397
+
398
+ ![Agentic Mode](/images/streamweaver/agentic.png)
399
+ <!-- Screenshot: One-shot form in browser -->
400
+
401
+ The browser opens, the user fills the form, submits, and the window closes. Your script receives a hash of the form values and continues execution.
402
+
403
+ This is particularly powerful for AI agents that need human input mid-task—configuration, confirmation, or parameter selection.
404
+
405
+ ## App Configuration
406
+
407
+ Customize your app's appearance:
408
+
409
+ ```ruby
410
+ app "Dashboard",
411
+ layout: :wide, # :default, :wide, :full, :fluid
412
+ theme: :dashboard # :default, :dashboard, :document
413
+ do
414
+ # Wide layout for data-heavy interfaces
415
+ # ...
416
+ end.run!
417
+ ```
418
+
419
+ ![Layout Options](/images/streamweaver/layouts.png)
420
+ <!-- Screenshot: Side-by-side of default and wide layouts -->
421
+
422
+ ## Under the Hood
423
+
424
+ StreamWeaver's architecture is intentionally simple:
425
+
426
+ - **Backend:** Sinatra server handles requests
427
+ - **Rendering:** Phlex generates HTML
428
+ - **Reactivity:** Alpine.js manages client-side state
429
+ - **Updates:** HTMX swaps content on state changes
430
+ - **State:** Server-side, persisted in session cookies
431
+
432
+ When a user interacts with the UI:
433
+ 1. HTMX sends a request with the new state
434
+ 2. Sinatra receives and stores the state
435
+ 3. Your Ruby block re-executes with updated state
436
+ 4. Phlex renders new HTML
437
+ 5. HTMX swaps the content in the browser
438
+
439
+ No WebSocket complexity. No client-side state management. Just HTTP and HTML—the technologies that have powered the web for 30 years.
440
+
441
+ ## Installation
442
+
443
+ Add to your Gemfile:
444
+
445
+ ```ruby
446
+ gem 'stream_weaver'
447
+ ```
448
+
449
+ Or install directly:
450
+
451
+ ```bash
452
+ gem install stream_weaver
453
+ ```
454
+
455
+ **Requirements:** Ruby 3.1+
456
+
457
+ ## Source Code
458
+
459
+ StreamWeaver is open source and available on GitHub:
460
+
461
+ | Resource | Link |
462
+ |----------|------|
463
+ | GitHub | [github.com/fkchang/stream_weaver](https://github.com/fkchang/stream_weaver) |
464
+ | Documentation | `docs/` directory |
465
+ | Examples | `examples/` directory |
466
+
467
+ ## What's Next
468
+
469
+ StreamWeaver is actively developed. On the roadmap:
470
+
471
+ - **Charts integration** - Data visualization components
472
+ - **Table component** - Sortable, filterable data tables
473
+ - **File upload** - Drag-and-drop file handling
474
+ - **Service mode** - Run multiple apps from a single server
475
+ - **Theming system** - Custom CSS and color schemes
476
+
477
+ ## Let's Build Together
478
+
479
+ I built StreamWeaver because I wanted the joy of Ruby for every interface I create—not just the ones worth setting up a full stack for.
480
+
481
+ If you share that vision, I'd love your input:
482
+
483
+ - **Try it** - Build something, however small
484
+ - **Break it** - File issues when things don't work
485
+ - **Extend it** - PRs welcome for new components
486
+ - **Share it** - Show what you've built
487
+
488
+ The best DSLs emerge from real use. Every dashboard you build, every tool you create, every experiment you run helps shape what StreamWeaver becomes.
489
+
490
+ ## A Renaissance in Ruby Tooling
491
+
492
+ StreamWeaver joins a wave of projects making Ruby development more delightful:
493
+
494
+ - **Hotwire** - Interactive features without JavaScript
495
+ - **Phlex** - Type-safe HTML generation
496
+ - **Charm Ruby** - Beautiful terminal UIs
497
+ - **Prism** - Modern Ruby parser
498
+
499
+ Ruby isn't just surviving—it's thriving. We're building the tools we've always wanted, with the expressiveness we've always loved.
500
+
501
+ StreamWeaver is my contribution to that renaissance. I hope it saves you the hours I've spent on internal tools, and lets you focus on what matters: the problems you're solving, not the frameworks you're configuring.
502
+
503
+ Now go build something beautiful.
504
+
505
+ ---
506
+
507
+ *StreamWeaver: Reactive Ruby UIs. No JavaScript required.*
508
+
509
+ ```ruby
510
+ require 'stream_weaver'
511
+
512
+ app "Your App" do
513
+ # Your beautiful UI here
514
+ end.run!
515
+ ```
@@ -0,0 +1,83 @@
1
+ # BUG: two canvas bridges racing for one hardcoded socket path - the later one silently wins, the earlier one goes unreachable
2
+
3
+ **Status:** open, worth a dedicated look
4
+ **Severity:** medium - not exploitable, but it's a silent-misroute waiting to happen: a push intended for one
5
+ bridge lands on a completely different one with no error
6
+ **Filed:** 2026-08-31, from an agent task in billing_engine that needed to push an edited doc to a specific
7
+ one of two live bridges (ports 4700 and 4701) and confirmed the wrong one received it
8
+ **Area:** canvas bridge (`bridge_server.rb#start_unix_socket_server`, `#write_pid_file`) /
9
+ `canvas/client.rb` (hardcoded `SOCKET_PATH` / `PID_FILE_PATH`) / `cli.rb#canvas_push`
10
+
11
+ ## Symptom
12
+
13
+ `StreamWeaver::Canvas::Client::SOCKET_PATH` and `PID_FILE_PATH` are process-wide constants
14
+ (`~/.streamweaver/canvas.sock`, `~/.streamweaver/canvas.pid`), overridable only via
15
+ `STREAMWEAVER_CANVAS_SOCKET` / `STREAMWEAVER_CANVAS_PID` env vars. When two `BridgeServer.run!` processes
16
+ start on the same machine without those env vars set (e.g. two independent `streamweaver panel` sessions on
17
+ different ports), both call `start_unix_socket_server`, which unconditionally does
18
+ `File.delete(socket_path) if File.exist?(socket_path)` then binds a fresh `UNIXServer` at that same path -
19
+ and both call `write_pid_file`, which unconditionally overwrites the pid file. Whichever process starts
20
+ **later** wins: it deletes the earlier process's live socket out from under it and overwrites the pid file
21
+ to point at itself. There is no check for "is this socket already owned by a live process" - it's a bare
22
+ delete-and-rebind race, not a refusal.
23
+
24
+ `canvas-push` (`cli.rb#canvas_push` -> `Canvas::Client.send_message`) always connects via the default
25
+ socket path, with no `--socket` / `--port` selector. So once the race resolves, **every** `canvas-push`
26
+ call - regardless of which bridge's URL the user is actually looking at - goes to whichever process won,
27
+ silently. The loser keeps serving HTTP on its own port, but its Unix-socket listener is orphaned: its
28
+ sessions become permanently unreachable for pushes until it's restarted, with no error surfaced anywhere.
29
+
30
+ ## Evidence
31
+
32
+ Two `puma` processes observed live: pid 58524 on port 4700 (started 00:31:09), pid 58674 on port 4701
33
+ (started 00:31:15, six seconds later). `~/.streamweaver/canvas.pid` read `pid=58674\nport=4701` - the
34
+ later process. `lsof -p <pid>` on both showed 58674 holding the listening end of
35
+ `~/.streamweaver/canvas.sock` (multiple accepted connections), while 58524 held a single stale fd to the
36
+ same path with nothing actually listening on its behalf.
37
+
38
+ Confirmed behaviorally, not just via lsof: `curl http://localhost:4700/health` and
39
+ `curl http://localhost:4701/health` returned two **different** session lists (4700: 3 sessions; 4701: 15
40
+ sessions - each bridge's own independent in-memory session store, as expected for two separate processes).
41
+ A `canvas-push pm-discount-eng-brief` (default socket, no override) landed its new content on port 4701's
42
+ session, confirmed via `/canvas/:name` page-content diff before/after the push and via `/canvas/:name/poll`
43
+ returning empty `html` for port 4700's same-named session throughout - it had been structurally empty since
44
+ 6 seconds after boot, because it never got a chance to receive a single push.
45
+
46
+ ## Why it matters
47
+
48
+ 1. **Silent misroute, not a failure.** `canvas-push` exits 0 and prints `Pushed to <name>` regardless of
49
+ which bridge actually received it. A user (or an agent) watching a specific port's browser tab has no
50
+ signal that their push went somewhere else.
51
+ 2. **Data can land on the wrong session store.** In the observed case, a bridge deliberately kept separate
52
+ for parked reference copies (many unrelated sessions) received a push meant for a different, narrower
53
+ working bridge - purely because of six seconds of startup ordering, nothing about intent.
54
+ 3. **Gets worse with GEA-style concurrency.** Multiple simultaneous `streamweaver panel` sessions (Forrest's
55
+ normal 20+-concurrent-session operating mode) make this race routine, not an edge case.
56
+
57
+ ## Suggested fix
58
+
59
+ - Derive the socket path (and pid file path) from the bridge's own port by default, e.g.
60
+ `~/.streamweaver/canvas-<port>.sock` / `canvas-<port>.pid`, instead of one shared hardcoded path across
61
+ every bridge process on the machine.
62
+ - Add a `--socket PATH` / `--port PORT` selector to `canvas-push` (and any other CLI command that talks to
63
+ a bridge) so a caller can address a specific bridge unambiguously instead of always hitting "whatever the
64
+ default socket currently resolves to."
65
+ - Make `start_unix_socket_server` refuse to start (or at least warn loudly) when the target socket path is
66
+ already owned by a live process, rather than deleting and rebinding over it. A stale socket (owning
67
+ process dead) should still be reclaimed - the current stale-cleanup behavior is fine - but a **live**
68
+ owner should not be silently evicted.
69
+
70
+ ## Acceptance criteria for a fix
71
+
72
+ - Two bridge processes started on different ports, with no env override, do not collide: both remain
73
+ independently reachable for `canvas-push` for the lifetime of both processes.
74
+ - `canvas-push` against a specific bridge (via its new selector, or by construction once sockets are
75
+ per-port) always lands on the session store of the bridge the caller intended, demonstrable with the same
76
+ two-bridge shape observed here.
77
+ - Starting a second bridge that WOULD collide under the old scheme either binds its own distinct socket
78
+ automatically, or fails fast with a clear message - never a silent takeover.
79
+
80
+ ## Provenance
81
+
82
+ Observed live during a billing_engine doc-editing task (`pm-discount-eng-brief` canvas session), 2026-08-31.
83
+ Diagnosed via `/health` and `/canvas/:name/poll` diffs across ports 4700 and 4701, pids 58524 and 58674.