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,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../support"
4
+
5
+ module StreamWeaverBench
6
+ module Fixtures
7
+ module Ledger
8
+ VARIANTS = %i[legacy_full named_full named_fragments update_filter].freeze
9
+ INTERACTIONS = %i[create edit validation filter delete].freeze
10
+
11
+ module_function
12
+
13
+ def build(variant)
14
+ metrics = Metrics.new
15
+ people = MemoryStore.new(PEOPLE)
16
+ named = variant != :legacy_full
17
+ scoped = %i[named_fragments update_filter].include?(variant)
18
+ app = StreamWeaver::App.new("Ledger #{variant}") do
19
+ state[:query] ||= ""
20
+ state[:error] ||= ""
21
+ state[:selection] ||= nil
22
+
23
+ if named
24
+ action(:create_person, updates: (scoped ? :people : nil)) { |_s, _| metrics.callback!; people.create(id: 51, name: "New Person", email: "new@example.test", touched: "2026-07-01") }
25
+ action(:edit_person) { |_s, key| metrics.callback!; people.update(key, name: "Edited Person") }
26
+ action(:invalid_person) { |s, _| metrics.callback!; s[:error] = "Name is required" }
27
+ action(:delete_person) { |_s, key| metrics.callback!; people.destroy(key) }
28
+ end
29
+
30
+ toolbar = proc do
31
+ if named
32
+ button "Create", action: :create_person, key: "new"
33
+ button "Invalid", action: :invalid_person, key: "invalid"
34
+ else
35
+ button("Create") { |_s| metrics.callback!; people.create(id: 51, name: "New Person", email: "new@example.test", touched: "2026-07-01") }
36
+ button("Invalid") { |s| metrics.callback!; s[:error] = "Name is required" }
37
+ end
38
+ text state[:error]
39
+ form :quick_edit do
40
+ text_field :name, default: "Person 01"
41
+ text_field :email, default: "person01@example.test"
42
+ submit("Save quick edit") { |_values| metrics.callback! }
43
+ end
44
+ end
45
+ scoped ? fragment(:toolbar, &toolbar) : instance_exec(&toolbar)
46
+
47
+ people_view = proc do
48
+ text_field :query, placeholder: "Search", on_change: ->(_s, _value) { metrics.callback! } if variant == :update_filter
49
+ shown = people.all.select { |person| person[:name].downcase.include?(state[:query].to_s.downcase) }
50
+ table shown, row_key: ->(person) { person[:id] } do
51
+ column :name
52
+ column :email
53
+ column :touched
54
+ column :actions do |person|
55
+ if named
56
+ hstack do
57
+ button "Edit", action: :edit_person, key: person[:id]
58
+ button "Delete", action: :delete_person, key: person[:id]
59
+ end
60
+ else
61
+ hstack do
62
+ button("Edit", key: person[:id]) { |_s| metrics.callback!; people.update(person[:id], name: "Edited Person") }
63
+ button("Delete", key: person[:id]) { |_s| metrics.callback!; people.destroy(person[:id]) }
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
69
+ scoped ? fragment(:people, &people_view) : instance_exec(&people_view)
70
+ end
71
+ StreamWeaverBench.instrument(app, metrics)
72
+ [app.generate, metrics]
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../support"
4
+
5
+ module StreamWeaverBench
6
+ module Fixtures
7
+ module Warroom
8
+ VARIANTS = %i[legacy_full named_full named_fragments named_fragments_oob].freeze
9
+ INTERACTIONS = %i[note_append column_move].freeze
10
+
11
+ module_function
12
+
13
+ def build(variant)
14
+ metrics = Metrics.new
15
+ stories = MemoryStore.new(STORIES)
16
+ named = variant != :legacy_full
17
+ app = StreamWeaver::App.new("Warroom #{variant}") do
18
+ state[:query] ||= ""
19
+ state[:error] ||= ""
20
+ state[:selection] ||= stories.all.first[:id]
21
+ if named
22
+ action(:append_note) { |_s, key| metrics.callback!; stories.find(key)[:notes] << "Appended note" }
23
+ action(:move_story) { |_s, key| metrics.callback!; stories.update(key, column: "Active") }
24
+ end
25
+
26
+ board = proc do
27
+ grid(columns: 3) do
28
+ %w[Ready Active Done].each do |column|
29
+ card(title: column) do
30
+ stories.all.select { |story| story[:column] == column }.each { |story| text "#{story[:id]} — #{story[:title]}" }
31
+ end
32
+ end
33
+ end
34
+ end
35
+ scoped = %i[named_fragments named_fragments_oob].include?(variant)
36
+ scoped ? fragment(:board, &board) : instance_exec(&board)
37
+ detail = proc do
38
+ story = stories.find(state[:selection])
39
+ text story[:title]
40
+ story[:notes].each { |note| text note }
41
+ form :note do
42
+ text_area :body, default: "Appended note"
43
+ submit("Add note") { |_values| metrics.callback! }
44
+ end
45
+ if named
46
+ button "Append note", action: :append_note, key: story[:id]
47
+ button "Move story", action: :move_story, key: story[:id], updates: (variant == :named_fragments_oob ? :board : nil)
48
+ else
49
+ button("Append note") { |_s| metrics.callback!; story[:notes] << "Appended note" }
50
+ button("Move story") { |_s| metrics.callback!; stories.update(story[:id], column: "Active") }
51
+ end
52
+ end
53
+ scoped ? fragment(:detail, &detail) : instance_exec(&detail)
54
+ end
55
+ StreamWeaverBench.instrument(app, metrics)
56
+ [app.generate, metrics]
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,102 @@
1
+ # Phase-1 dispatch benchmark
2
+
3
+ Rack end-to-end time is the in-process request duration; server time excludes Rack::Test response handling.
4
+
5
+ ## ledger: create
6
+
7
+ | variant | median server ms | p95 server ms | median rack ms | p95 rack ms | response bytes | request bytes | rebuilds | callbacks | changed nodes / full | vs-baseline ratio |
8
+ |---|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|
9
+ | phlex_baseline | 0.072 | 0.160 | 0.072 | 0.160 | 140 | 0 | 0 | 1 | 7 / 356 | 1.00 |
10
+ | legacy_full | 3.463 | 8.082 | 3.464 | 8.083 | 75444 | 0 | 2 | 1 | 430 / 442 | 538.89 |
11
+ | named_full | 5.603 | 8.759 | 5.604 | 8.760 | 96075 | 0 | 1 | 1 | 430 / 442 | 686.25 |
12
+ | named_fragments | 7.309 | 16.644 | 7.310 | 16.645 | 99327 | 0 | 1 | 1 | 431 / 444 | 709.48 |
13
+ | update_filter | 7.304 | 15.314 | 7.306 | 15.316 | 99680 | 0 | 1 | 1 | 432 / 445 | 712.00 |
14
+
15
+ **FAIL** — `named_fragments` response 99327 B <= 1.20 × baseline 140 B (168.0 B).
16
+
17
+ **FAIL** — `update_filter` response 99680 B <= 1.20 × baseline 140 B (168.0 B).
18
+
19
+ ## ledger: edit
20
+
21
+ | variant | median server ms | p95 server ms | median rack ms | p95 rack ms | response bytes | request bytes | rebuilds | callbacks | changed nodes / full | vs-baseline ratio |
22
+ |---|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|
23
+ | phlex_baseline | 0.074 | 0.099 | 0.074 | 0.099 | 147 | 0 | 0 | 1 | 7 / 356 | 1.00 |
24
+ | legacy_full | 5.166 | 7.880 | 5.168 | 7.881 | 74037 | 0 | 2 | 1 | 422 / 442 | 503.65 |
25
+ | named_full | 7.925 | 10.217 | 7.926 | 10.218 | 94271 | 0 | 1 | 1 | 422 / 442 | 641.30 |
26
+ | named_fragments | 9.109 | 13.200 | 9.110 | 13.202 | 95467 | 0 | 1 | 1 | 414 / 444 | 649.44 |
27
+ | update_filter | 9.437 | 11.729 | 9.438 | 11.730 | 95820 | 0 | 1 | 1 | 415 / 445 | 651.84 |
28
+
29
+ **FAIL** — `named_fragments` response 95467 B <= 1.20 × baseline 147 B (176.4 B).
30
+
31
+ **FAIL** — `update_filter` response 95820 B <= 1.20 × baseline 147 B (176.4 B).
32
+
33
+ ## ledger: validation
34
+
35
+ | variant | median server ms | p95 server ms | median rack ms | p95 rack ms | response bytes | request bytes | rebuilds | callbacks | changed nodes / full | vs-baseline ratio |
36
+ |---|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|
37
+ | phlex_baseline | 0.068 | 0.075 | 0.068 | 0.075 | 100 | 0 | 0 | 1 | 4 / 356 | 1.00 |
38
+ | legacy_full | 5.987 | 10.311 | 5.987 | 10.313 | 74061 | 0 | 2 | 1 | 422 / 442 | 740.61 |
39
+ | named_full | 10.815 | 16.052 | 10.816 | 16.053 | 94295 | 0 | 1 | 1 | 422 / 442 | 942.95 |
40
+ | named_fragments | 9.625 | 13.420 | 9.625 | 13.420 | 2083 | 0 | 1 | 1 | 9 / 444 | 20.83 |
41
+ | update_filter | 10.424 | 15.887 | 10.425 | 15.888 | 2083 | 0 | 1 | 1 | 9 / 445 | 20.83 |
42
+
43
+ **FAIL** — `named_fragments` response 2083 B <= 1.20 × baseline 100 B (120.0 B).
44
+
45
+ **FAIL** — `update_filter` response 2083 B <= 1.20 × baseline 100 B (120.0 B).
46
+
47
+ ## ledger: filter
48
+
49
+ | variant | median server ms | p95 server ms | median rack ms | p95 rack ms | response bytes | request bytes | rebuilds | callbacks | changed nodes / full | vs-baseline ratio |
50
+ |---|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|
51
+ | phlex_baseline | 0.093 | 0.137 | 0.093 | 0.137 | 175 | 15 | 0 | 1 | 8 / 356 | 1.00 |
52
+ | legacy_full | 5.587 | 8.797 | 5.588 | 8.798 | 8427 | 15 | 1 | 0 | 30 / 442 | 48.15 |
53
+ | named_full | 6.568 | 10.267 | 6.569 | 10.268 | 9110 | 15 | 1 | 0 | 30 / 442 | 52.06 |
54
+ | named_fragments | 6.447 | 9.268 | 6.448 | 9.269 | 9412 | 15 | 1 | 0 | 32 / 444 | 53.78 |
55
+ | update_filter | 7.679 | 15.253 | 7.681 | 15.253 | 9774 | 15 | 1 | 1 | 33 / 445 | 55.85 |
56
+
57
+ **FAIL** — `named_fragments` response 9412 B <= 1.20 × baseline 175 B (210.0 B).
58
+
59
+ **FAIL** — `update_filter` response 9774 B <= 1.20 × baseline 175 B (210.0 B).
60
+
61
+ ## ledger: delete
62
+
63
+ | variant | median server ms | p95 server ms | median rack ms | p95 rack ms | response bytes | request bytes | rebuilds | callbacks | changed nodes / full | vs-baseline ratio |
64
+ |---|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|
65
+ | phlex_baseline | 0.064 | 0.068 | 0.064 | 0.069 | 0 | 0 | 0 | 1 | 0 / 356 | Inf |
66
+ | legacy_full | 9.806 | 13.792 | 9.808 | 13.793 | 72607 | 0 | 2 | 1 | 414 / 442 | Inf |
67
+ | named_full | 15.270 | 20.859 | 15.271 | 20.861 | 92447 | 0 | 1 | 1 | 414 / 442 | Inf |
68
+ | named_fragments | 15.908 | 23.024 | 15.910 | 23.026 | 93583 | 0 | 1 | 1 | 406 / 444 | Inf |
69
+ | update_filter | 17.374 | 26.321 | 17.375 | 26.322 | 93936 | 0 | 1 | 1 | 407 / 445 | Inf |
70
+
71
+ **FAIL** — `named_fragments` response 93583 B <= 1.20 × baseline 0 B (0.0 B).
72
+
73
+ **FAIL** — `update_filter` response 93936 B <= 1.20 × baseline 0 B (0.0 B).
74
+
75
+ ## warroom: note_append
76
+
77
+ | variant | median server ms | p95 server ms | median rack ms | p95 rack ms | response bytes | request bytes | rebuilds | callbacks | changed nodes / full | vs-baseline ratio |
78
+ |---|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|
79
+ | phlex_baseline | 0.067 | 0.074 | 0.067 | 0.074 | 147 | 0 | 0 | 1 | 6 / 23 | 1.00 |
80
+ | legacy_full | 10.135 | 17.264 | 10.136 | 17.266 | 2549 | 0 | 2 | 1 | 26 / 45 | 17.34 |
81
+ | named_full | 14.052 | 19.983 | 14.053 | 19.985 | 2928 | 0 | 1 | 1 | 26 / 45 | 19.92 |
82
+ | named_fragments | 16.454 | 47.339 | 16.455 | 47.342 | 2635 | 0 | 1 | 1 | 10 / 47 | 17.93 |
83
+ | named_fragments_oob | 15.482 | 22.797 | 15.483 | 22.799 | 2654 | 0 | 1 | 1 | 10 / 47 | 18.05 |
84
+
85
+ **FAIL** — `named_fragments` response 2635 B <= 1.20 × baseline 147 B (176.4 B).
86
+
87
+ **FAIL** — `named_fragments_oob` response 2654 B <= 1.20 × baseline 147 B (176.4 B).
88
+
89
+ ## warroom: column_move
90
+
91
+ | variant | median server ms | p95 server ms | median rack ms | p95 rack ms | response bytes | request bytes | rebuilds | callbacks | changed nodes / full | vs-baseline ratio |
92
+ |---|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|
93
+ | phlex_baseline | 0.075 | 0.082 | 0.075 | 0.082 | 370 | 0 | 0 | 1 | 12 / 23 | 1.00 |
94
+ | legacy_full | 11.041 | 16.529 | 11.041 | 16.530 | 2514 | 0 | 2 | 1 | 25 / 45 | 6.79 |
95
+ | named_full | 15.912 | 21.876 | 15.913 | 21.877 | 2893 | 0 | 1 | 1 | 25 / 45 | 7.82 |
96
+ | named_fragments | 17.256 | 23.950 | 17.258 | 23.952 | 2600 | 0 | 1 | 1 | 9 / 47 | 7.03 |
97
+ | named_fragments_oob | 18.317 | 28.865 | 18.318 | 28.866 | 3107 | 0 | 1 | 1 | 26 / 47 | 8.40 |
98
+
99
+ **FAIL** — `named_fragments` response 2600 B <= 1.20 × baseline 370 B (444.0 B).
100
+
101
+ **FAIL** — `named_fragments_oob` response 3107 B <= 1.20 × baseline 370 B (444.0 B).
102
+
@@ -0,0 +1,102 @@
1
+ # Phase-1 dispatch benchmark
2
+
3
+ Rack end-to-end time is the in-process request duration; server time excludes Rack::Test response handling.
4
+
5
+ ## ledger: create
6
+
7
+ | variant | median server ms | p95 server ms | median rack ms | p95 rack ms | response bytes | request bytes | rebuilds | callbacks | changed nodes / full | vs-baseline ratio |
8
+ |---|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|
9
+ | phlex_baseline | 0.069 | 0.185 | 0.069 | 0.186 | 140 | 0 | 0 | 1 | 7 / 356 | 1.00 |
10
+ | legacy_full | 3.343 | 10.680 | 3.344 | 10.681 | 71179 | 0 | 2 | 1 | 430 / 442 | 508.42 |
11
+ | named_full | 5.424 | 7.052 | 5.425 | 7.052 | 91810 | 0 | 1 | 1 | 430 / 442 | 655.79 |
12
+ | named_fragments | 7.190 | 11.612 | 7.191 | 11.614 | 95062 | 0 | 1 | 1 | 431 / 444 | 679.01 |
13
+ | update_filter | 7.456 | 14.048 | 7.457 | 14.049 | 95415 | 0 | 1 | 1 | 432 / 445 | 681.54 |
14
+
15
+ **FAIL** — `named_fragments` response 95062 B <= 1.20 × baseline 140 B (168.0 B).
16
+
17
+ **FAIL** — `update_filter` response 95415 B <= 1.20 × baseline 140 B (168.0 B).
18
+
19
+ ## ledger: edit
20
+
21
+ | variant | median server ms | p95 server ms | median rack ms | p95 rack ms | response bytes | request bytes | rebuilds | callbacks | changed nodes / full | vs-baseline ratio |
22
+ |---|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|
23
+ | phlex_baseline | 0.072 | 0.154 | 0.072 | 0.154 | 147 | 0 | 0 | 1 | 7 / 356 | 1.00 |
24
+ | legacy_full | 5.223 | 16.443 | 5.225 | 16.445 | 69848 | 0 | 2 | 1 | 422 / 442 | 475.16 |
25
+ | named_full | 7.716 | 13.097 | 7.717 | 13.099 | 90082 | 0 | 1 | 1 | 422 / 442 | 612.80 |
26
+ | named_fragments | 9.392 | 12.408 | 9.394 | 12.409 | 91278 | 0 | 1 | 1 | 414 / 444 | 620.94 |
27
+ | update_filter | 9.289 | 12.532 | 9.290 | 12.534 | 91631 | 0 | 1 | 1 | 415 / 445 | 623.34 |
28
+
29
+ **FAIL** — `named_fragments` response 91278 B <= 1.20 × baseline 147 B (176.4 B).
30
+
31
+ **FAIL** — `update_filter` response 91631 B <= 1.20 × baseline 147 B (176.4 B).
32
+
33
+ ## ledger: validation
34
+
35
+ | variant | median server ms | p95 server ms | median rack ms | p95 rack ms | response bytes | request bytes | rebuilds | callbacks | changed nodes / full | vs-baseline ratio |
36
+ |---|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|
37
+ | phlex_baseline | 0.072 | 0.179 | 0.072 | 0.180 | 100 | 0 | 0 | 1 | 4 / 356 | 1.00 |
38
+ | legacy_full | 6.742 | 10.122 | 6.743 | 10.124 | 69876 | 0 | 2 | 1 | 422 / 442 | 698.76 |
39
+ | named_full | 10.619 | 15.149 | 10.621 | 15.151 | 90110 | 0 | 1 | 1 | 422 / 442 | 901.10 |
40
+ | named_fragments | 9.460 | 15.436 | 9.462 | 15.438 | 2100 | 0 | 1 | 1 | 9 / 444 | 21.00 |
41
+ | update_filter | 9.434 | 12.735 | 9.434 | 12.736 | 2100 | 0 | 1 | 1 | 9 / 445 | 21.00 |
42
+
43
+ **FAIL** — `named_fragments` response 2100 B <= 1.20 × baseline 100 B (120.0 B).
44
+
45
+ **FAIL** — `update_filter` response 2100 B <= 1.20 × baseline 100 B (120.0 B).
46
+
47
+ ## ledger: filter
48
+
49
+ | variant | median server ms | p95 server ms | median rack ms | p95 rack ms | response bytes | request bytes | rebuilds | callbacks | changed nodes / full | vs-baseline ratio |
50
+ |---|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|
51
+ | phlex_baseline | 0.105 | 0.294 | 0.105 | 0.295 | 175 | 15 | 0 | 1 | 8 / 356 | 1.00 |
52
+ | legacy_full | 7.487 | 13.387 | 7.487 | 13.390 | 4242 | 15 | 1 | 0 | 30 / 442 | 24.24 |
53
+ | named_full | 7.067 | 12.082 | 7.069 | 12.082 | 4925 | 15 | 1 | 0 | 30 / 442 | 28.14 |
54
+ | named_fragments | 6.660 | 9.655 | 6.661 | 9.655 | 5227 | 15 | 1 | 0 | 32 / 444 | 29.87 |
55
+ | update_filter | 7.808 | 12.616 | 7.810 | 12.618 | 5589 | 15 | 1 | 1 | 33 / 445 | 31.94 |
56
+
57
+ **FAIL** — `named_fragments` response 5227 B <= 1.20 × baseline 175 B (210.0 B).
58
+
59
+ **FAIL** — `update_filter` response 5589 B <= 1.20 × baseline 175 B (210.0 B).
60
+
61
+ ## ledger: delete
62
+
63
+ | variant | median server ms | p95 server ms | median rack ms | p95 rack ms | response bytes | request bytes | rebuilds | callbacks | changed nodes / full | vs-baseline ratio |
64
+ |---|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|
65
+ | phlex_baseline | 0.066 | 0.081 | 0.066 | 0.082 | 0 | 0 | 0 | 1 | 0 / 356 | Inf |
66
+ | legacy_full | 10.433 | 16.797 | 10.434 | 16.798 | 68505 | 0 | 2 | 1 | 414 / 442 | Inf |
67
+ | named_full | 14.991 | 21.514 | 14.994 | 21.515 | 88345 | 0 | 1 | 1 | 414 / 442 | Inf |
68
+ | named_fragments | 16.332 | 23.606 | 16.334 | 23.607 | 89481 | 0 | 1 | 1 | 406 / 444 | Inf |
69
+ | update_filter | 16.146 | 23.649 | 16.147 | 23.650 | 89834 | 0 | 1 | 1 | 407 / 445 | Inf |
70
+
71
+ **FAIL** — `named_fragments` response 89481 B <= 1.20 × baseline 0 B (0.0 B).
72
+
73
+ **FAIL** — `update_filter` response 89834 B <= 1.20 × baseline 0 B (0.0 B).
74
+
75
+ ## warroom: note_append
76
+
77
+ | variant | median server ms | p95 server ms | median rack ms | p95 rack ms | response bytes | request bytes | rebuilds | callbacks | changed nodes / full | vs-baseline ratio |
78
+ |---|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|
79
+ | phlex_baseline | 0.069 | 0.425 | 0.069 | 0.425 | 147 | 0 | 0 | 1 | 6 / 23 | 1.00 |
80
+ | legacy_full | 10.179 | 17.379 | 10.181 | 17.380 | 1723 | 0 | 2 | 1 | 26 / 45 | 11.72 |
81
+ | named_full | 14.862 | 22.651 | 14.863 | 22.652 | 2102 | 0 | 1 | 1 | 26 / 45 | 14.30 |
82
+ | named_fragments | 15.308 | 26.121 | 15.310 | 26.123 | 1795 | 0 | 1 | 1 | 10 / 47 | 12.21 |
83
+ | named_fragments_oob | 15.934 | 23.927 | 15.936 | 23.927 | 1814 | 0 | 1 | 1 | 10 / 47 | 12.34 |
84
+
85
+ **FAIL** — `named_fragments` response 1795 B <= 1.20 × baseline 147 B (176.4 B).
86
+
87
+ **FAIL** — `named_fragments_oob` response 1814 B <= 1.20 × baseline 147 B (176.4 B).
88
+
89
+ ## warroom: column_move
90
+
91
+ | variant | median server ms | p95 server ms | median rack ms | p95 rack ms | response bytes | request bytes | rebuilds | callbacks | changed nodes / full | vs-baseline ratio |
92
+ |---|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|
93
+ | phlex_baseline | 0.074 | 0.400 | 0.075 | 0.400 | 370 | 0 | 0 | 1 | 12 / 23 | 1.00 |
94
+ | legacy_full | 11.895 | 17.866 | 11.897 | 17.867 | 1703 | 0 | 2 | 1 | 25 / 45 | 4.60 |
95
+ | named_full | 16.398 | 21.608 | 16.398 | 21.610 | 2082 | 0 | 1 | 1 | 25 / 45 | 5.63 |
96
+ | named_fragments | 17.878 | 23.662 | 17.879 | 23.662 | 1775 | 0 | 1 | 1 | 9 / 47 | 4.80 |
97
+ | named_fragments_oob | 17.959 | 25.112 | 17.961 | 25.113 | 2282 | 0 | 1 | 1 | 26 / 47 | 6.17 |
98
+
99
+ **FAIL** — `named_fragments` response 1775 B <= 1.20 × baseline 370 B (444.0 B).
100
+
101
+ **FAIL** — `named_fragments_oob` response 2282 B <= 1.20 × baseline 370 B (444.0 B).
102
+
@@ -0,0 +1,102 @@
1
+ # Phase-1 dispatch benchmark
2
+
3
+ Rack end-to-end time is the in-process request duration; server time excludes Rack::Test response handling.
4
+
5
+ ## ledger: create
6
+
7
+ | variant | median server ms | p95 server ms | median rack ms | p95 rack ms | response bytes | request bytes | rebuilds | callbacks | changed nodes / full | vs-baseline ratio |
8
+ |---|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|
9
+ | phlex_baseline | 0.077 | 0.141 | 0.077 | 0.142 | 140 | 0 | 0 | 1 | 7 / 356 | 1.00 |
10
+ | legacy_full | 3.287 | 9.863 | 3.288 | 9.864 | 72463 | 0 | 2 | 1 | 430 / 442 | 517.59 |
11
+ | named_full | 4.837 | 7.102 | 4.838 | 7.102 | 93094 | 0 | 1 | 1 | 430 / 442 | 664.96 |
12
+ | named_fragments | 3.795 | 5.566 | 3.795 | 5.568 | 3945 | 0 | 1 | 1 | 17 / 444 | 28.18 |
13
+ | update_filter | 4.274 | 6.624 | 4.275 | 6.624 | 3945 | 0 | 1 | 1 | 17 / 445 | 28.18 |
14
+
15
+ **PASS** — `named_fragments` response 3945 B <= 1.20 × baseline 140 B (168.0 B) or <= 8192 B floor.
16
+
17
+ **PASS** — `update_filter` response 3945 B <= 1.20 × baseline 140 B (168.0 B) or <= 8192 B floor.
18
+
19
+ ## ledger: edit
20
+
21
+ | variant | median server ms | p95 server ms | median rack ms | p95 rack ms | response bytes | request bytes | rebuilds | callbacks | changed nodes / full | vs-baseline ratio |
22
+ |---|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|
23
+ | phlex_baseline | 0.074 | 0.109 | 0.075 | 0.110 | 147 | 0 | 0 | 1 | 7 / 356 | 1.00 |
24
+ | legacy_full | 4.771 | 7.026 | 4.772 | 7.027 | 71107 | 0 | 2 | 1 | 422 / 442 | 483.72 |
25
+ | named_full | 7.445 | 10.962 | 7.446 | 10.963 | 91341 | 0 | 1 | 1 | 422 / 442 | 621.37 |
26
+ | named_fragments | 6.711 | 10.778 | 6.711 | 10.778 | 1999 | 0 | 1 | 1 | 9 / 444 | 13.60 |
27
+ | update_filter | 7.403 | 10.551 | 7.404 | 10.552 | 1999 | 0 | 1 | 1 | 9 / 445 | 13.60 |
28
+
29
+ **PASS** — `named_fragments` response 1999 B <= 1.20 × baseline 147 B (176.4 B) or <= 8192 B floor.
30
+
31
+ **PASS** — `update_filter` response 1999 B <= 1.20 × baseline 147 B (176.4 B) or <= 8192 B floor.
32
+
33
+ ## ledger: validation
34
+
35
+ | variant | median server ms | p95 server ms | median rack ms | p95 rack ms | response bytes | request bytes | rebuilds | callbacks | changed nodes / full | vs-baseline ratio |
36
+ |---|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|
37
+ | phlex_baseline | 0.070 | 0.096 | 0.070 | 0.096 | 100 | 0 | 0 | 1 | 4 / 356 | 1.00 |
38
+ | legacy_full | 5.332 | 8.063 | 5.332 | 8.065 | 71135 | 0 | 2 | 1 | 422 / 442 | 711.35 |
39
+ | named_full | 9.938 | 16.164 | 9.939 | 16.166 | 91369 | 0 | 1 | 1 | 422 / 442 | 913.69 |
40
+ | named_fragments | 10.268 | 16.219 | 10.269 | 16.220 | 2100 | 0 | 1 | 1 | 9 / 444 | 21.00 |
41
+ | update_filter | 12.519 | 20.038 | 12.520 | 20.040 | 2100 | 0 | 1 | 1 | 9 / 445 | 21.00 |
42
+
43
+ **PASS** — `named_fragments` response 2100 B <= 1.20 × baseline 100 B (120.0 B) or <= 8192 B floor.
44
+
45
+ **PASS** — `update_filter` response 2100 B <= 1.20 × baseline 100 B (120.0 B) or <= 8192 B floor.
46
+
47
+ ## ledger: filter
48
+
49
+ | variant | median server ms | p95 server ms | median rack ms | p95 rack ms | response bytes | request bytes | rebuilds | callbacks | changed nodes / full | vs-baseline ratio |
50
+ |---|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|
51
+ | phlex_baseline | 0.094 | 0.223 | 0.094 | 0.223 | 175 | 15 | 0 | 1 | 8 / 356 | 1.00 |
52
+ | legacy_full | 5.048 | 10.228 | 5.049 | 10.230 | 4284 | 15 | 1 | 0 | 30 / 442 | 24.48 |
53
+ | named_full | 5.947 | 9.940 | 5.947 | 9.941 | 4967 | 15 | 1 | 0 | 30 / 442 | 28.38 |
54
+ | named_fragments | 6.432 | 10.047 | 6.433 | 10.047 | 5289 | 15 | 1 | 0 | 32 / 444 | 30.22 |
55
+ | update_filter | 6.812 | 12.296 | 6.814 | 12.298 | 5651 | 15 | 1 | 1 | 33 / 445 | 32.29 |
56
+
57
+ **PASS** — `named_fragments` response 5289 B <= 1.20 × baseline 175 B (210.0 B) or <= 8192 B floor.
58
+
59
+ **PASS** — `update_filter` response 5651 B <= 1.20 × baseline 175 B (210.0 B) or <= 8192 B floor.
60
+
61
+ ## ledger: delete
62
+
63
+ | variant | median server ms | p95 server ms | median rack ms | p95 rack ms | response bytes | request bytes | rebuilds | callbacks | changed nodes / full | vs-baseline ratio |
64
+ |---|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|
65
+ | phlex_baseline | 0.072 | 0.114 | 0.072 | 0.115 | 0 | 0 | 0 | 1 | 0 / 356 | Inf |
66
+ | legacy_full | 9.727 | 16.812 | 9.729 | 16.814 | 69740 | 0 | 2 | 1 | 414 / 442 | Inf |
67
+ | named_full | 17.151 | 21.766 | 17.152 | 21.768 | 89580 | 0 | 1 | 1 | 414 / 442 | Inf |
68
+ | named_fragments | 16.349 | 21.140 | 16.351 | 21.141 | 172 | 0 | 1 | 1 | 1 / 444 | Inf |
69
+ | update_filter | 14.547 | 19.288 | 14.548 | 19.289 | 172 | 0 | 1 | 1 | 1 / 445 | Inf |
70
+
71
+ **PASS** — `named_fragments` response 172 B <= 1.20 × baseline 0 B (0.0 B) or <= 8192 B floor.
72
+
73
+ **PASS** — `update_filter` response 172 B <= 1.20 × baseline 0 B (0.0 B) or <= 8192 B floor.
74
+
75
+ ## warroom: note_append
76
+
77
+ | variant | median server ms | p95 server ms | median rack ms | p95 rack ms | response bytes | request bytes | rebuilds | callbacks | changed nodes / full | vs-baseline ratio |
78
+ |---|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|
79
+ | phlex_baseline | 0.068 | 0.138 | 0.068 | 0.138 | 147 | 0 | 0 | 1 | 6 / 23 | 1.00 |
80
+ | legacy_full | 9.103 | 14.929 | 9.103 | 14.931 | 1723 | 0 | 2 | 1 | 26 / 45 | 11.72 |
81
+ | named_full | 14.339 | 18.781 | 14.340 | 18.782 | 2102 | 0 | 1 | 1 | 26 / 45 | 14.30 |
82
+ | named_fragments | 16.124 | 22.015 | 16.126 | 22.016 | 1795 | 0 | 1 | 1 | 10 / 47 | 12.21 |
83
+ | named_fragments_oob | 16.704 | 25.253 | 16.705 | 25.255 | 1814 | 0 | 1 | 1 | 10 / 47 | 12.34 |
84
+
85
+ **PASS** — `named_fragments` response 1795 B <= 1.20 × baseline 147 B (176.4 B) or <= 8192 B floor.
86
+
87
+ **PASS** — `named_fragments_oob` response 1814 B <= 1.20 × baseline 147 B (176.4 B) or <= 8192 B floor.
88
+
89
+ ## warroom: column_move
90
+
91
+ | variant | median server ms | p95 server ms | median rack ms | p95 rack ms | response bytes | request bytes | rebuilds | callbacks | changed nodes / full | vs-baseline ratio |
92
+ |---|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|
93
+ | phlex_baseline | 0.077 | 0.163 | 0.077 | 0.164 | 370 | 0 | 0 | 1 | 12 / 23 | 1.00 |
94
+ | legacy_full | 10.802 | 17.991 | 10.804 | 17.993 | 1703 | 0 | 2 | 1 | 25 / 45 | 4.60 |
95
+ | named_full | 16.677 | 22.323 | 16.679 | 22.324 | 2082 | 0 | 1 | 1 | 25 / 45 | 5.63 |
96
+ | named_fragments | 17.006 | 24.690 | 17.006 | 24.692 | 1775 | 0 | 1 | 1 | 9 / 47 | 4.80 |
97
+ | named_fragments_oob | 18.229 | 26.563 | 18.231 | 26.564 | 2282 | 0 | 1 | 1 | 26 / 47 | 6.17 |
98
+
99
+ **PASS** — `named_fragments` response 1775 B <= 1.20 × baseline 370 B (444.0 B) or <= 8192 B floor.
100
+
101
+ **PASS** — `named_fragments_oob` response 2282 B <= 1.20 × baseline 370 B (444.0 B) or <= 8192 B floor.
102
+
data/bench/run.rb ADDED
@@ -0,0 +1,151 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ $LOAD_PATH.unshift File.expand_path("../lib", __dir__)
5
+
6
+ require "benchmark"
7
+ require "fileutils"
8
+ require "optparse"
9
+ require_relative "fixtures/ledger"
10
+ require_relative "fixtures/warroom"
11
+ require_relative "baselines/ledger"
12
+ require_relative "baselines/warroom"
13
+
14
+ module StreamWeaverBench
15
+ Sample = Data.define(:server_ms, :rack_ms, :response_bytes, :request_bytes, :rebuilds, :callbacks, :changed_nodes, :full_nodes)
16
+
17
+ class Runner
18
+ ABSOLUTE_FLOOR_BYTES = 8_192
19
+
20
+ BASELINE_PATHS = {
21
+ ledger: { create: "/create", edit: "/edit", validation: "/invalid", filter: "/filter", delete: "/delete" },
22
+ warroom: { note_append: "/note", column_move: "/move" }
23
+ }.freeze
24
+ LABELS = { create: "Create", edit: "Edit", validation: "Invalid", delete: "Delete", note_append: "Append note", column_move: "Move story" }.freeze
25
+
26
+ def initialize(iterations:, warmups:, write: true)
27
+ @iterations = iterations
28
+ @warmups = warmups
29
+ @write = write
30
+ end
31
+
32
+ def run
33
+ lines = ["# Phase-1 dispatch benchmark", "", "Rack end-to-end time is the in-process request duration; server time excludes Rack::Test response handling.", ""]
34
+ run_fixture(:ledger, Fixtures::Ledger, Baselines::Ledger, lines)
35
+ run_fixture(:warroom, Fixtures::Warroom, Baselines::Warroom, lines)
36
+ report = lines.join("\n") + "\n"
37
+ puts report
38
+ write_report(report) if @write
39
+ report
40
+ end
41
+
42
+ private
43
+
44
+ def run_fixture(name, fixture, baseline, lines)
45
+ fixture::INTERACTIONS.each do |interaction|
46
+ baseline_samples = samples { measure_baseline(name, baseline, interaction) }
47
+ baseline_bytes = median(baseline_samples.map(&:response_bytes))
48
+ lines << "## #{name}: #{interaction}"
49
+ lines << ""
50
+ lines << "| variant | median server ms | p95 server ms | median rack ms | p95 rack ms | response bytes | request bytes | rebuilds | callbacks | changed nodes / full | vs-baseline ratio |"
51
+ lines << "|---|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|"
52
+ append_row(lines, "phlex_baseline", baseline_samples, baseline_bytes)
53
+ fixture::VARIANTS.each do |variant|
54
+ measured = samples { measure_streamweaver(fixture, variant, interaction) }
55
+ append_row(lines, variant, measured, baseline_bytes)
56
+ end
57
+ scoped_variants = fixture::VARIANTS.select { |variant| variant.to_s.include?("fragment") || variant == :update_filter }
58
+ scoped_variants.each do |variant|
59
+ measured = samples { measure_streamweaver(fixture, variant, interaction) }
60
+ bytes = median(measured.map(&:response_bytes))
61
+ lines << ""
62
+ # Gate: ratio <= 1.20x OR absolute floor 8KB. Sub-floor responses are below
63
+ # perceptual/network significance; ratios vs sub-200B surgical baselines
64
+ # measure granularity, not slowness (plan intent: kill whole-tree payloads).
65
+ passed = bytes <= baseline_bytes * 1.20 || bytes <= ABSOLUTE_FLOOR_BYTES
66
+ lines << "**#{passed ? 'PASS' : 'FAIL'}** — `#{variant}` response #{bytes} B <= 1.20 × baseline #{baseline_bytes} B (#{(baseline_bytes * 1.20).round(1)} B) or <= #{ABSOLUTE_FLOOR_BYTES} B floor."
67
+ end
68
+ lines << ""
69
+ end
70
+ end
71
+
72
+ def samples
73
+ @warmups.times { yield }
74
+ Array.new(@iterations) { yield }
75
+ end
76
+
77
+ def measure_baseline(name, baseline, interaction)
78
+ app, metrics = baseline.build
79
+ session = Rack::Test::Session.new(Rack::MockSession.new(app))
80
+ full_nodes = Html.node_count(session.get("/").body)
81
+ params = interaction == :filter ? { query: "Person 01" } : {}
82
+ path = BASELINE_PATHS.fetch(name).fetch(interaction)
83
+ metrics.reset!
84
+ response, rack_ms, server_ms = timed_request(session, path, params)
85
+ Sample.new(server_ms:, rack_ms:, response_bytes: response.body.bytesize,
86
+ request_bytes: Rack::Utils.build_nested_query(params).bytesize, rebuilds: 0,
87
+ callbacks: metrics.callbacks, changed_nodes: Html.node_count(response.body), full_nodes: full_nodes)
88
+ end
89
+
90
+ def measure_streamweaver(fixture, variant, interaction)
91
+ app, metrics = fixture.build(variant)
92
+ session = Rack::Test::Session.new(Rack::MockSession.new(app))
93
+ setup = session.get("/")
94
+ full_nodes = Html.node_count(setup.body)
95
+ params = {}
96
+ path = if interaction == :filter
97
+ params = { query: "Person 01" }
98
+ if variant == :update_filter
99
+ params[:_sw_fragment] = Html.fragment_token(setup.body) if Html.fragment_token(setup.body)
100
+ "/event/query"
101
+ else
102
+ "/update"
103
+ end
104
+ else
105
+ Html.action_path(setup.body, LABELS.fetch(interaction))
106
+ end
107
+ metrics.reset!
108
+ response, rack_ms, server_ms = timed_request(session, path, params)
109
+ raise "#{variant} #{interaction}: HTTP #{response.status}" unless response.ok?
110
+ Sample.new(server_ms:, rack_ms:, response_bytes: response.body.bytesize,
111
+ request_bytes: Rack::Utils.build_nested_query(params).bytesize, rebuilds: metrics.rebuilds,
112
+ callbacks: metrics.callbacks, changed_nodes: Html.node_count(response.body), full_nodes: full_nodes)
113
+ end
114
+
115
+ def timed_request(session, path, params)
116
+ started = Process.clock_gettime(Process::CLOCK_MONOTONIC)
117
+ response = nil
118
+ server_started = Process.clock_gettime(Process::CLOCK_MONOTONIC)
119
+ response = session.post(path, params, "HTTP_HX_REQUEST" => "true")
120
+ server_ms = (Process.clock_gettime(Process::CLOCK_MONOTONIC) - server_started) * 1000
121
+ rack_ms = (Process.clock_gettime(Process::CLOCK_MONOTONIC) - started) * 1000
122
+ [response, rack_ms, server_ms]
123
+ end
124
+
125
+ def append_row(lines, variant, values, baseline_bytes)
126
+ response_bytes = median(values.map(&:response_bytes))
127
+ lines << format("| %s | %.3f | %.3f | %.3f | %.3f | %d | %d | %d | %d | %d / %d | %.2f |",
128
+ variant, median(values.map(&:server_ms)), percentile(values.map(&:server_ms), 0.95),
129
+ median(values.map(&:rack_ms)), percentile(values.map(&:rack_ms), 0.95), response_bytes,
130
+ median(values.map(&:request_bytes)), median(values.map(&:rebuilds)), median(values.map(&:callbacks)),
131
+ median(values.map(&:changed_nodes)), median(values.map(&:full_nodes)), baseline_bytes.zero? ? Float::INFINITY : response_bytes.fdiv(baseline_bytes))
132
+ end
133
+
134
+ def median(values) = percentile(values, 0.50)
135
+ def percentile(values, fraction) = values.sort.fetch([(values.length * fraction).ceil - 1, 0].max)
136
+ def write_report(report)
137
+ sha = `git rev-parse --short HEAD`.strip
138
+ FileUtils.mkdir_p(File.expand_path("results", __dir__))
139
+ File.write(File.expand_path("results/#{sha}.md", __dir__), report)
140
+ end
141
+ end
142
+ end
143
+
144
+ options = { iterations: 30, warmups: 5, write: true }
145
+ OptionParser.new do |parser|
146
+ parser.on("--iterations N", Integer) { |value| options[:iterations] = value }
147
+ parser.on("--warmups N", Integer) { |value| options[:warmups] = value }
148
+ parser.on("--no-write") { options[:write] = false }
149
+ end.parse!
150
+ abort "iterations must be positive" unless options[:iterations].positive?
151
+ StreamWeaverBench::Runner.new(**options).run