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.
- checksums.yaml +7 -0
- data/.beads/.gitignore +77 -0
- data/.beads/README.md +81 -0
- data/.beads/config.yaml +54 -0
- data/.beads/metadata.json +7 -0
- data/.rspec +3 -0
- data/AGENTS.md +91 -0
- data/CHANGELOG.md +294 -0
- data/CLAUDE.md +67 -0
- data/LICENSE.txt +21 -0
- data/README.md +565 -0
- data/Rakefile +13 -0
- data/assets/streamweaver-hero.jpg +0 -0
- data/bench/baselines/ledger.rb +58 -0
- data/bench/baselines/warroom.rb +51 -0
- data/bench/fixtures/ledger.rb +76 -0
- data/bench/fixtures/warroom.rb +60 -0
- data/bench/results/6daf8b8.md +102 -0
- data/bench/results/c8d749b.md +102 -0
- data/bench/results/eb06ab9.md +102 -0
- data/bench/run.rb +151 -0
- data/bench/support.rb +84 -0
- data/copy_this/dto_run_sample_run_7631.html +220 -0
- data/docs/SERVICE_MODE.md +302 -0
- data/docs/architecture/how_streamweaver_works.md +772 -0
- data/docs/blog/2026-01-16-streamweaver-introduction.md +515 -0
- data/docs/bug-2026-08-31-canvas-bridge-socket-collision.md +83 -0
- data/docs/canvas-ipc-session-summary.md +127 -0
- data/docs/canvas-panel-workflow.md +172 -0
- data/docs/canvas-read.md +147 -0
- data/docs/canvas-roadmap.md +275 -0
- data/docs/case-studies/2026-09-03-didx-canvas-read-shelf.md +147 -0
- data/docs/claude-code-companion-skill-spec.md +567 -0
- data/docs/components_reference.md +1223 -0
- data/docs/crud-patterns.md +265 -0
- data/docs/endpoints.md +122 -0
- data/docs/for_llms.md +1 -0
- data/docs/form-patterns.md +165 -0
- data/docs/frontend-only.md +169 -0
- data/docs/html-artifact-audit.md +238 -0
- data/docs/ideas/2025-01-01-as-a-service.md +203 -0
- data/docs/ideas/2026-01-16-charm-tui-exploration.md +542 -0
- data/docs/ideas/2026-08-27-static-doc-shelf-export.md +153 -0
- data/docs/ideas/dhh-review-future-refactors.md +142 -0
- data/docs/opal-jamstack.md +47 -0
- data/docs/opal-npm-direction.md +184 -0
- data/docs/opal-spike-findings.md +194 -0
- data/docs/plans/2025-12-16-form-blocks-design.md +124 -0
- data/docs/plans/2025-12-30-examples-browser-design.md +152 -0
- data/docs/plans/2026-01-02-charts-design.md +288 -0
- data/docs/plans/2026-01-10-canvas-ipc-design.md +247 -0
- data/docs/plans/2026-01-19-cabinet-control-components.md +176 -0
- data/docs/plans/2026-01-26-gem-release-and-panel.md +140 -0
- data/docs/plans/canvas-claude-project-design.md +238 -0
- data/docs/plans/canvas-doc-location-and-discovery.md +32 -0
- data/docs/plans/org-doc-preview-surfaces.md +293 -0
- data/docs/plans/shadcn-polish-plan.md +417 -0
- data/docs/porting-artifacts.md +252 -0
- data/docs/reference/agent-skills-comparison.md +369 -0
- data/docs/reference/travel-state-prd.artifact.html +780 -0
- data/docs/research/2026-08-17-hotwire-alike-landscape.md +268 -0
- data/docs/research/2026-08-17-hotwire-concept-map.md +404 -0
- data/docs/research/2026-08-22-lazy-fragments-trigger-decision.md +246 -0
- data/docs/research/2026-08-22-learnhotwire-syllabus-coverage.md +239 -0
- data/docs/research/frontend-only-matrix.md +278 -0
- data/docs/research/streamweaver-way-spike-findings.md +529 -0
- data/docs/resource-dsl.md +342 -0
- data/docs/routing.md +302 -0
- data/docs/ruby-ui-comparison.md +319 -0
- data/docs/shared-dsl-fragments.md +356 -0
- data/docs/streamweaver-for-ai-agents.md +401 -0
- data/docs/streamweaver-frontend-vision.md +123 -0
- data/docs/streamweaver_canvas/glimmer_initial_final_layer.rb +77 -0
- data/docs/streamweaver_canvas/save_example.rb +9 -0
- data/docs/templates.md +213 -0
- data/docs/testing.md +155 -0
- data/docs/theming-hooks.md +281 -0
- data/docs/tutorials/the-streamweaver-way.md +882 -0
- data/docs/university/capability-inventory.md +137 -0
- data/docs/university/dependency-survey.md +114 -0
- data/docs/university/design-spec.md +217 -0
- data/docs/university/mockups/code-block-doc-theme-dark.png +0 -0
- data/docs/university/mockups/code-block-doc-theme-light.png +0 -0
- data/docs/university/mockups/course-list-dark.png +0 -0
- data/docs/university/mockups/course-list-light.png +0 -0
- data/docs/university/mockups/course_canvas_mockup.rb +741 -0
- data/docs/university/mockups/step-screen-dark.png +0 -0
- data/docs/university/mockups/step-screen-light.png +0 -0
- data/docs/university/roadmap.md +72 -0
- data/docs/university/send-to-coworker.md +77 -0
- data/docs/university/worker-session-mining.md +109 -0
- data/docs/visual/browser-rendering-spike.md +173 -0
- data/docs/visual/builder-visual-plan-analysis.md +378 -0
- data/docs/visual/sw-plan-format-exploration.md +511 -0
- data/docs/visual/sw-plan-rendering-deep-dive.md +453 -0
- data/docs/visual-skills/PROGRESS.md +60 -0
- data/docs/visual-skills/SESSION-CONTEXT.md +207 -0
- data/docs/visual-skills/analysis/components.md +317 -0
- data/docs/visual-skills/analysis/overlap.md +360 -0
- data/docs/visual-skills/analysis/pi-design-deck.md +1102 -0
- data/docs/visual-skills/analysis/streamweaver-inventory.md +93 -0
- data/docs/visual-skills/analysis/unified-specs.feature +1003 -0
- data/docs/visual-skills/analysis/visual-explainer.md +964 -0
- data/docs/visual-skills/blog/blog-series-outline.md +39 -0
- data/docs/visual-skills/blog/token-efficiency.md +73 -0
- data/docs/visual-skills/design/architecture.md +1725 -0
- data/docs/visual-skills/design/codex-review.md +340 -0
- data/docs/visual-skills/design/dhh-review.md +209 -0
- data/docs/visual-skills/design/evolution.md +100 -0
- data/docs/visual-skills/design/gemini-review.md +55 -0
- data/docs/visual-skills/design/review-synthesis.md +149 -0
- data/docs/visual-skills/implementation/STATE.md +22 -0
- data/docs/visual-skills/implementation/plan.md +347 -0
- data/docs/visual-skills/implementation/spike-findings.md +237 -0
- data/docs/visual-skills/implementation/tasks.md +702 -0
- data/docs/visual-skills/lessons-learned/process.md +145 -0
- data/examples/README.md +96 -0
- data/examples/advanced/all_components.rb +177 -0
- data/examples/advanced/examples_browser.rb +394 -0
- data/examples/advanced/teachables_browser.rb +261 -0
- data/examples/advanced/theme_tweaker.rb +366 -0
- data/examples/advanced/tutorial.rb +1528 -0
- data/examples/agentic/agentic_form.rb +30 -0
- data/examples/agentic/agentic_form_autoclose.rb +30 -0
- data/examples/agentic/cultivation_tracker.rb +69 -0
- data/examples/basic/hello_world.rb +22 -0
- data/examples/basic/opal_tabs_table.rb +44 -0
- data/examples/basic/todo_list.rb +39 -0
- data/examples/button_loading_test.rb +36 -0
- data/examples/canvas/mermaid_canvas_demo.sh +151 -0
- data/examples/charts/bar_chart_demo.rb +57 -0
- data/examples/charts/line_chart_demo.rb +73 -0
- data/examples/charts/pie_area_demo.rb +87 -0
- data/examples/charts/stacked_bar_chart_demo.rb +69 -0
- data/examples/claude_code/README.md +85 -0
- data/examples/claude_code/codebreaker/.claude/commands/infiltrate.md +632 -0
- data/examples/claude_code/codebreaker/.claude/settings.json +11 -0
- data/examples/claude_code/codebreaker/.claude/settings.local.json +17 -0
- data/examples/claude_code/codebreaker/.claude/skills/infiltrate.md +0 -0
- data/examples/claude_code/codebreaker/README.md +213 -0
- data/examples/claude_code/tutorial/.claude/commands/learn.md +453 -0
- data/examples/claude_code/tutorial/README.md +102 -0
- data/examples/claude_code/verification_flow/.claude/commands/verify.md +171 -0
- data/examples/claude_code/verification_flow/README.md +126 -0
- data/examples/components/annotated_code_demo.rb +54 -0
- data/examples/components/callout_demo.rb +33 -0
- data/examples/components/checkbox_group_demo.rb +46 -0
- data/examples/components/design_review.css +468 -0
- data/examples/components/design_review_demo.rb +44 -0
- data/examples/components/design_review_dsl.rb +300 -0
- data/examples/components/diff_block_demo.rb +112 -0
- data/examples/components/events_demo.rb +184 -0
- data/examples/components/form_demo.rb +62 -0
- data/examples/components/lesson_demo.rb +53 -0
- data/examples/components/markdown_demo.rb +110 -0
- data/examples/components/mermaid_demo.rb +166 -0
- data/examples/components/pareto_set.rb +64 -0
- data/examples/components/prd_demo.rb +21 -0
- data/examples/components/prd_dsl.rb +385 -0
- data/examples/components/quiz_demo.rb +47 -0
- data/examples/components/run_viewer_demo.rb +100 -0
- data/examples/components/score_and_collapsible_demo.rb +82 -0
- data/examples/components/select_stale_value_smoke_test.rb +39 -0
- data/examples/components/table_demo.rb +247 -0
- data/examples/components/timer_with_state_demo.rb +58 -0
- data/examples/components/todo_due_dates.rb +63 -0
- data/examples/components/uat_gaps_demo.rb +112 -0
- data/examples/dashboard/feed_simulator.rb +60 -0
- data/examples/dashboard/live_dashboard.rb +80 -0
- data/examples/dashboard_components.rb +108 -0
- data/examples/deferred_fragments_demo.rb +62 -0
- data/examples/generate_more_spike/README.md +63 -0
- data/examples/generate_more_spike/app.rb +441 -0
- data/examples/git_health.sh +401 -0
- data/examples/layout/layout_components_demo.rb +174 -0
- data/examples/layout/modal_demo.rb +215 -0
- data/examples/layout/navigation_demo.rb +227 -0
- data/examples/layout/route_tabs_demo.rb +52 -0
- data/examples/layout/routing_demo.rb +52 -0
- data/examples/layout/scroll_box_demo.rb +154 -0
- data/examples/lazy_fragments_demo.rb +151 -0
- data/examples/my_todos/README.md +29 -0
- data/examples/my_todos/my_todos.rb +312 -0
- data/examples/my_todos/store.rb +86 -0
- data/examples/opal/reactive_demo.rb +62 -0
- data/examples/opal/scenarios/s1_counter.rb +15 -0
- data/examples/opal/scenarios/s2_search_filter.rb +17 -0
- data/examples/opal/scenarios/s3_sibling_tabs.rb +25 -0
- data/examples/opal/scenarios/s4_shopping_cart.rb +34 -0
- data/examples/opal/scenarios/s5_watch.rb +39 -0
- data/examples/opal/scenarios/s6_on_start.rb +20 -0
- data/examples/opal/scenarios/s7_wizard.rb +33 -0
- data/examples/opal/scenarios/s8_loan_calculator.rb +21 -0
- data/examples/opal/scenarios/s9_dashboard.rb +27 -0
- data/examples/operations_dashboard_demo.rb +167 -0
- data/examples/panel_demo.sh +447 -0
- data/examples/parity/assets/tyrion_slice.css +86 -0
- data/examples/parity/rivet_people_slice.rb +182 -0
- data/examples/parity/tyrion_components.css +777 -0
- data/examples/parity/tyrion_warroom_components.rb +414 -0
- data/examples/parity/tyrion_warroom_slice.rb +549 -0
- data/examples/puma_dev/README.md +124 -0
- data/examples/puma_dev/config.ru +34 -0
- data/examples/puma_dev/standalone_app.rb +34 -0
- data/examples/scaffolding/blog.rb +49 -0
- data/examples/scaffolding/utf_lite.rb +105 -0
- data/examples/styling/feedback_demo.rb +229 -0
- data/examples/styling/style_showcase.rb +172 -0
- data/examples/styling/theme_demo.rb +508 -0
- data/examples/timer_health_checker.rb +111 -0
- data/examples/timer_showcase.rb +131 -0
- data/examples/tutorials/render_markdown.rb +112 -0
- data/examples/tutorials/streamweaver_way_tutorial.rb +44 -0
- data/examples/tutorials/tutorial_content.rb +1082 -0
- data/examples/visual_skills/design_deck_demo.rb +314 -0
- data/examples/visual_skills/explainer_demo.rb +499 -0
- data/exe/streamweaver +9 -0
- data/gsd/ROADMAP-1.0.md +167 -0
- data/gsd/STATE.md +13 -0
- data/gsd/research/market-positioning-research.md +139 -0
- data/gsd/research/production-patterns-research.md +270 -0
- data/gsd/research/repo-audit-1.0.md +274 -0
- data/lib/stream_weaver/action_token.rb +58 -0
- data/lib/stream_weaver/adapter/alpinejs.rb +8249 -0
- data/lib/stream_weaver/adapter/base.rb +591 -0
- data/lib/stream_weaver/adapter/opal.rb +334 -0
- data/lib/stream_weaver/adapter/static.rb +1118 -0
- data/lib/stream_weaver/admin.rb +176 -0
- data/lib/stream_weaver/app.rb +1821 -0
- data/lib/stream_weaver/assets/js/sw-copy.js +50 -0
- data/lib/stream_weaver/assets/js/sw-heredoc-rewrite.js +68 -0
- data/lib/stream_weaver/assets/js/sw-keyboard.js +165 -0
- data/lib/stream_weaver/assets/js/sw-mermaid-zoom.js +621 -0
- data/lib/stream_weaver/assets/js/sw-route-tabs.js +53 -0
- data/lib/stream_weaver/assets/js/sw-sidebar-toc.js +116 -0
- data/lib/stream_weaver/assets/js/sw-slide-nav.js +103 -0
- data/lib/stream_weaver/canvas/bridge.rb +239 -0
- data/lib/stream_weaver/canvas/bridge_server.rb +646 -0
- data/lib/stream_weaver/canvas/client.rb +298 -0
- data/lib/stream_weaver/canvas/doc_roots.rb +233 -0
- data/lib/stream_weaver/canvas/doc_store.rb +252 -0
- data/lib/stream_weaver/canvas/gist_publisher.rb +264 -0
- data/lib/stream_weaver/canvas/gist_save_handler.rb +89 -0
- data/lib/stream_weaver/canvas/gist_store.rb +135 -0
- data/lib/stream_weaver/canvas/helpers.rb +109 -0
- data/lib/stream_weaver/canvas/history.rb +90 -0
- data/lib/stream_weaver/canvas/protocol.rb +86 -0
- data/lib/stream_weaver/canvas/reader.rb +853 -0
- data/lib/stream_weaver/canvas/save_doc_widget.rb +457 -0
- data/lib/stream_weaver/canvas/scroll_top_hint.rb +21 -0
- data/lib/stream_weaver/canvas/session.rb +132 -0
- data/lib/stream_weaver/cli.rb +3235 -0
- data/lib/stream_weaver/component_assets.rb +70 -0
- data/lib/stream_weaver/component_registry.rb +67 -0
- data/lib/stream_weaver/component_renderer.rb +48 -0
- data/lib/stream_weaver/components/annotated_code.rb +53 -0
- data/lib/stream_weaver/components/api_endpoint.rb +42 -0
- data/lib/stream_weaver/components/callout.rb +59 -0
- data/lib/stream_weaver/components/chart.rb +84 -0
- data/lib/stream_weaver/components/code_block.rb +77 -0
- data/lib/stream_weaver/components/comparison.rb +39 -0
- data/lib/stream_weaver/components/decision.rb +38 -0
- data/lib/stream_weaver/components/deck/close_overlay.rb +84 -0
- data/lib/stream_weaver/components/deck/confirmation_bar.rb +60 -0
- data/lib/stream_weaver/components/deck/deck_option.rb +61 -0
- data/lib/stream_weaver/components/deck/deck_slide.rb +77 -0
- data/lib/stream_weaver/components/deck/deck_state.rb +469 -0
- data/lib/stream_weaver/components/deck/deck_summary.rb +73 -0
- data/lib/stream_weaver/components/deck/design_deck.rb +60 -0
- data/lib/stream_weaver/components/deck/generate_more_controls.rb +84 -0
- data/lib/stream_weaver/components/deck/model_selector.rb +84 -0
- data/lib/stream_weaver/components/deck/skeleton_placeholder.rb +36 -0
- data/lib/stream_weaver/components/diff_block.rb +125 -0
- data/lib/stream_weaver/components/doc_header.rb +57 -0
- data/lib/stream_weaver/components/image_block.rb +70 -0
- data/lib/stream_weaver/components/implementation_map.rb +33 -0
- data/lib/stream_weaver/components/keyboard_shortcuts.rb +94 -0
- data/lib/stream_weaver/components/kpi_dashboard.rb +78 -0
- data/lib/stream_weaver/components/mermaid.rb +79 -0
- data/lib/stream_weaver/components/pipeline.rb +63 -0
- data/lib/stream_weaver/components/sidebar_toc.rb +46 -0
- data/lib/stream_weaver/components/slide_container.rb +141 -0
- data/lib/stream_weaver/components/timeline_event.rb +58 -0
- data/lib/stream_weaver/components/wireframe.rb +29 -0
- data/lib/stream_weaver/components/wireframe_block.rb +34 -0
- data/lib/stream_weaver/components.rb +2711 -0
- data/lib/stream_weaver/css.rb +230 -0
- data/lib/stream_weaver/dev_fallback_overlay.rb +61 -0
- data/lib/stream_weaver/display_dsl.rb +1004 -0
- data/lib/stream_weaver/export/html_exporter.rb +478 -0
- data/lib/stream_weaver/feed.rb +34 -0
- data/lib/stream_weaver/feed_builder.rb +31 -0
- data/lib/stream_weaver/fonts.rb +33 -0
- data/lib/stream_weaver/interaction_runner.rb +487 -0
- data/lib/stream_weaver/iterm.rb +460 -0
- data/lib/stream_weaver/layout_registry.rb +92 -0
- data/lib/stream_weaver/opal/bridge.rb +52 -0
- data/lib/stream_weaver/opal/builder.rb +193 -0
- data/lib/stream_weaver/opal/env.rb +29 -0
- data/lib/stream_weaver/opal/reactive_state.rb +75 -0
- data/lib/stream_weaver/opal/regexp_anchor_patch.rb +62 -0
- data/lib/stream_weaver/opal/renderer.rb +77 -0
- data/lib/stream_weaver/opal/runtime.rb +250 -0
- data/lib/stream_weaver/opal/shell.rb +145 -0
- data/lib/stream_weaver/opal/string_bridge.rb +50 -0
- data/lib/stream_weaver/opal/stubs/diff.min.js +1 -0
- data/lib/stream_weaver/opal/stubs/digest.rb +15 -0
- data/lib/stream_weaver/opal/stubs/marked.umd.js +79 -0
- data/lib/stream_weaver/opal/stubs/md5.rb +3 -0
- data/lib/stream_weaver/opal/stubs/morphdom.min.js +775 -0
- data/lib/stream_weaver/opal/stubs/prism-tomorrow.min.css +1 -0
- data/lib/stream_weaver/opal/stubs/prism.min.js +1967 -0
- data/lib/stream_weaver/opal_entry.rb +136 -0
- data/lib/stream_weaver/org/inline.rb +95 -0
- data/lib/stream_weaver/org/reader.rb +561 -0
- data/lib/stream_weaver/org/recording_context.rb +86 -0
- data/lib/stream_weaver/org/source_splitter.rb +63 -0
- data/lib/stream_weaver/org/writer.rb +300 -0
- data/lib/stream_weaver/page_shell.rb +530 -0
- data/lib/stream_weaver/portfile.rb +79 -0
- data/lib/stream_weaver/pushable.rb +42 -0
- data/lib/stream_weaver/resource/default_views.rb +111 -0
- data/lib/stream_weaver/resource/field_input.rb +25 -0
- data/lib/stream_weaver/resource/state_keys.rb +16 -0
- data/lib/stream_weaver/resource/store.rb +18 -0
- data/lib/stream_weaver/resource.rb +104 -0
- data/lib/stream_weaver/server.rb +1471 -0
- data/lib/stream_weaver/service.rb +1240 -0
- data/lib/stream_weaver/service_client.rb +104 -0
- data/lib/stream_weaver/session_store.rb +186 -0
- data/lib/stream_weaver/skills/streamweaver-canvas-safe/SKILL.md +66 -0
- data/lib/stream_weaver/skills/streamweaver-canvas-safe/examples/canvas-safe-showcase.rb +98 -0
- data/lib/stream_weaver/skills/streamweaver-canvas-safe/references/actions-and-buttons.md +47 -0
- data/lib/stream_weaver/skills/streamweaver-canvas-safe/references/charts-and-diagrams.md +38 -0
- data/lib/stream_weaver/skills/streamweaver-canvas-safe/references/deck.md +32 -0
- data/lib/stream_weaver/skills/streamweaver-canvas-safe/references/inputs-and-forms.md +65 -0
- data/lib/stream_weaver/skills/streamweaver-canvas-safe/references/tabs-and-navigation.md +53 -0
- data/lib/stream_weaver/skills/streamweaver-doc-builder/SKILL.md +298 -0
- data/lib/stream_weaver/skills/streamweaver-visual-companion/SKILL.md +130 -0
- data/lib/stream_weaver/skills/streamweaver-visual-companion/examples/design-review-example.css +468 -0
- data/lib/stream_weaver/skills/streamweaver-visual-companion/examples/design-review-example.rb +39 -0
- data/lib/stream_weaver/skills/streamweaver-visual-companion/examples/design-review-example_dsl.rb +300 -0
- data/lib/stream_weaver/skills/streamweaver-visual-companion/examples/doc-parity-example.rb +24 -0
- data/lib/stream_weaver/skills/streamweaver-visual-companion/examples/doc-parity-example_dsl.rb +385 -0
- data/lib/stream_weaver/skills/streamweaver-visual-companion/references/checkpoints-and-forms.md +28 -0
- data/lib/stream_weaver/skills/streamweaver-visual-companion/references/cleanup-and-panel.md +44 -0
- data/lib/stream_weaver/skills/streamweaver-visual-companion/references/example-gallery.md +8 -0
- data/lib/stream_weaver/skills/streamweaver-visual-companion/references/persistence.md +58 -0
- data/lib/stream_weaver/skills/streamweaver-way/SKILL.md +396 -0
- data/lib/stream_weaver/skills/visual-plan/SKILL.md +201 -0
- data/lib/stream_weaver/skills/visual-recap/SKILL.md +244 -0
- data/lib/stream_weaver/streamer.rb +63 -0
- data/lib/stream_weaver/templates/choices.rb +149 -0
- data/lib/stream_weaver/templates/code.rb +157 -0
- data/lib/stream_weaver/templates/confirm.rb +110 -0
- data/lib/stream_weaver/templates/diff.rb +195 -0
- data/lib/stream_weaver/templates/info.rb +142 -0
- data/lib/stream_weaver/templates/table.rb +169 -0
- data/lib/stream_weaver/templates/wizard.rb +271 -0
- data/lib/stream_weaver/theme/auto_mode.rb +116 -0
- data/lib/stream_weaver/theme/presets.rb +555 -0
- data/lib/stream_weaver/theme.rb +639 -0
- data/lib/stream_weaver/university/canvas.rb +924 -0
- data/lib/stream_weaver/university/course.rb +699 -0
- data/lib/stream_weaver/university/demos/counter.rb +33 -0
- data/lib/stream_weaver/university/demos/dashboard.rb +147 -0
- data/lib/stream_weaver/university/demos/decision_form.rb +143 -0
- data/lib/stream_weaver/university/demos.rb +50 -0
- data/lib/stream_weaver/university/listener.rb +467 -0
- data/lib/stream_weaver/university/progress.rb +201 -0
- data/lib/stream_weaver/university/runner.rb +138 -0
- data/lib/stream_weaver/university/scripts/growing_doc.rb +609 -0
- data/lib/stream_weaver/university/scripts/growing_doc_state.rb +91 -0
- data/lib/stream_weaver/utils.rb +31 -0
- data/lib/stream_weaver/version.rb +5 -0
- data/lib/stream_weaver/views/canvas/reader_layout.erb +800 -0
- data/lib/stream_weaver/views.rb +3940 -0
- data/lib/stream_weaver.rb +122 -0
- data/llms.txt +1269 -0
- data/sig/stream_weaver.rbs +4 -0
- metadata +598 -0
data/README.md
ADDED
|
@@ -0,0 +1,565 @@
|
|
|
1
|
+
# StreamWeaver
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+
|
|
5
|
+
**Express intention, get interface. The joy of Ruby applied to UI.**
|
|
6
|
+
|
|
7
|
+
```ruby
|
|
8
|
+
app "Meeting Notes" do
|
|
9
|
+
header "1:1 with #{manager}"
|
|
10
|
+
md notes
|
|
11
|
+
end.run!
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
That's it. No HTML. No CSS. No JavaScript. No webpack.
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Platform support
|
|
19
|
+
|
|
20
|
+
| Tier | Platforms | What you get |
|
|
21
|
+
|---|---|---|
|
|
22
|
+
| Tested & supported | macOS + iTerm2 + Chrome + gh | The full `get-started` course: a controller canvas window, an agent worker tab, live demo panes, one-click sharing |
|
|
23
|
+
| Expected to work, untested | macOS plain terminal, Linux, WSL | The degraded path: `canvas-read` plus a browser-tab canvas, no panel/worker automation |
|
|
24
|
+
| Known degraded path | Any of the above | `streamweaver get-started --degraded` |
|
|
25
|
+
| Future | Windows native, other terminals/multiplexers | Not yet built |
|
|
26
|
+
|
|
27
|
+
A few things worth being explicit about:
|
|
28
|
+
|
|
29
|
+
- `streamweaver panel` and the worker-tab automation it drives are **macOS + iTerm2 only** — they script iTerm2's Python API.
|
|
30
|
+
- `canvas-read` is a plain local web server. It's the most portable piece of StreamWeaver and doesn't need iTerm2, Chrome, or `gh` — see [docs/canvas-read.md](docs/canvas-read.md).
|
|
31
|
+
- The Chrome extension and the `gh` CLI are enhancers for *sharing* docs (Gist links that render nicely), not requirements for reading them locally.
|
|
32
|
+
- Never assume a port. Every command that starts a server prints the URL it actually bound — StreamWeaver auto-increments past busy ports.
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Why StreamWeaver?
|
|
37
|
+
|
|
38
|
+
**TL;DR:** I want a quick UI. What do I need? Some text, a few inputs, a button. Why isn't *that* the interface? Instead: HTML, CSS, JavaScript, backend wiring... Streamlit showed me the interface *can* just be "text, inputs, button." StreamWeaver brings that to Ruby - and it turns out this minimal approach is perfect for AI agents too.
|
|
39
|
+
|
|
40
|
+
[Skip to Quick Start →](#quick-start)
|
|
41
|
+
|
|
42
|
+
<details>
|
|
43
|
+
<summary><b>The Longer Story</b></summary>
|
|
44
|
+
|
|
45
|
+
### The Interface Should Be What You Need
|
|
46
|
+
|
|
47
|
+
Think about what a simple UI actually requires: some text, a few inputs, maybe a dropdown, a button. That's it. That's what you're trying to build. But to get there you're dealing with HTML structure, CSS styling, JavaScript (or a backend framework), controllers, state management...
|
|
48
|
+
|
|
49
|
+
Streamlit's brilliance was recognizing that the DSL *can* just be the interface. You describe what you need - text, inputs, button - and you're done. StreamWeaver brings that philosophy to Ruby.
|
|
50
|
+
|
|
51
|
+
### Why This Matters for AI Agents
|
|
52
|
+
|
|
53
|
+
When you're building with Claude Code (or other AI coding assistants), this "what matters" approach pays off even more:
|
|
54
|
+
|
|
55
|
+
1. **Token efficiency** - The LLM generates a concise DSL instead of verbose HTML/React. **5-10x fewer tokens** means faster responses and lower costs.
|
|
56
|
+
|
|
57
|
+
2. **Rich interactions** - Instead of walls of terminal text for complex decisions, spin up an actual UI. What would be 5 pages of back-and-forth becomes one well-designed form.
|
|
58
|
+
|
|
59
|
+
3. **Persistent output** - Claude generates content (meeting notes, analysis, reports) but terminal output scrolls away. Canvas mode gives Claude a persistent display that stays visible.
|
|
60
|
+
|
|
61
|
+
4. **Data-only generation** - Pre-build your StreamWeaver app once, then have the LLM just generate the *data* to feed it. Minimal tokens, maximum speed.
|
|
62
|
+
|
|
63
|
+
```ruby
|
|
64
|
+
# Agent generates just this data:
|
|
65
|
+
meetings = [{ title: "1:1 with Sarah", notes: "..." }, ...]
|
|
66
|
+
|
|
67
|
+
# Pre-built app renders it:
|
|
68
|
+
MeetingNotesApp.new(meetings: meetings).run!
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
</details>
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## vs. "I can show you a mockup, but it's token-intensive"
|
|
76
|
+
|
|
77
|
+
Ask Claude for a visual and you'll often get something like this:
|
|
78
|
+
|
|
79
|
+
> Some of what we're working on might be easier to explain if I can show it to you in a web browser. I can put together mockups, layout comparisons, and other visuals as we go. This feature is still new and can be token-intensive. Want to try it? (Requires opening a local URL)
|
|
80
|
+
|
|
81
|
+
That's an honest warning, not a bug - hand-written HTML/React artifacts *are* token-intensive, so tools built that way have to gate visual explanation behind an expensive, novel, opt-in mode. TypeScript-flavored tooling doesn't have Ruby's sensibilities about generating a lot of interface from a little code.
|
|
82
|
+
|
|
83
|
+
StreamWeaver's answer: mockups, layout comparisons, and diagrams aren't a special mode you opt into - they're the default, at 5-10x fewer tokens than hand-written markup (see below), and they're **two-way interactive** while a static artifact isn't. Same local-browser mechanism, none of the token tax.
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## The Modes
|
|
88
|
+
|
|
89
|
+
StreamWeaver evolved through real needs, resulting in four modes:
|
|
90
|
+
|
|
91
|
+
| Mode | Command | What It Does | Use Case |
|
|
92
|
+
|------|---------|--------------|----------|
|
|
93
|
+
| **Standalone** | `ruby app.rb` | Auto-port, auto-browser, persistent server | Quick apps, utilities, prototypes |
|
|
94
|
+
| **Agentic** | `app.run_once!` | Popup UI → collect input → return JSON → quit | Claude Code needs structured input |
|
|
95
|
+
| **Canvas** | `streamweaver live SESSION` | Persistent display that Claude updates | Output that doesn't scroll away |
|
|
96
|
+
| **Service** | `streamweaver app.rb` | Single server, multiple apps | Development, showcase, tutorial |
|
|
97
|
+
|
|
98
|
+
### `ruby app.rb` vs `streamweaver app.rb`
|
|
99
|
+
|
|
100
|
+
| Command | Process Model | When to Use |
|
|
101
|
+
|---------|---------------|-------------|
|
|
102
|
+
| `ruby app.rb` | You own the process, runs until you kill it | Quick one-off scripts, standalone apps |
|
|
103
|
+
| `streamweaver app.rb` | Managed by background service, multi-app routing | Multiple apps side-by-side, development |
|
|
104
|
+
|
|
105
|
+
The service mode runs one Sinatra server for all apps instead of one process per app.
|
|
106
|
+
|
|
107
|
+
### From Local Script to Mobile Dashboard
|
|
108
|
+
|
|
109
|
+
StreamWeaver apps cover a wide range — the same DSL works whether you're hacking a quick one-off or running a persistent dashboard you check from your phone:
|
|
110
|
+
|
|
111
|
+
| Scenario | Host | Port | How |
|
|
112
|
+
|----------|------|------|-----|
|
|
113
|
+
| **Quick one-off** | localhost | auto-detect | `ruby app.rb` — browser opens, use it, Ctrl+C |
|
|
114
|
+
| **Agentic popup** | localhost | auto-detect | `app.run_once!` — collect input, return JSON, exit |
|
|
115
|
+
| **Puma-dev** | localhost | from Puma-dev | `config.ru` + `puma-dev link` — access at `http://myapp.test` |
|
|
116
|
+
| **Mobile/Tailscale** | `0.0.0.0` | fixed | `STREAMWEAVER_HOST=0.0.0.0 STREAMWEAVER_PORT=4580 ruby app.rb` |
|
|
117
|
+
| **LAN access** | `0.0.0.0` | fixed | Same — any device on your network can reach it |
|
|
118
|
+
| **Always-on dashboard** | `0.0.0.0` | fixed | Bookmark `http://your-machine:4580` on your phone |
|
|
119
|
+
|
|
120
|
+
**Environment variables** (overridden by code options if set):
|
|
121
|
+
- `STREAMWEAVER_HOST` — bind address (default: `127.0.0.1`)
|
|
122
|
+
- `STREAMWEAVER_PORT` — fixed port (default: auto-detect from 4567)
|
|
123
|
+
- `PORT` — standard port variable (used by Puma-dev, Heroku, etc.)
|
|
124
|
+
|
|
125
|
+
For quick local work, the defaults are perfect — auto-find a port, open the browser, done. For mobile or remote access (Tailscale, LAN), set a fixed host and port so your URL stays stable across restarts. For Puma-dev, see [examples/puma_dev](examples/puma_dev).
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
## Quick Start
|
|
130
|
+
|
|
131
|
+
Three steps to awesome:
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
gem install stream_weaver
|
|
135
|
+
streamweaver install
|
|
136
|
+
streamweaver get-started
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
`install` wires StreamWeaver into Claude Code (permissions + skills); `get-started` walks you
|
|
140
|
+
through a short interactive course, next to your own terminal. On macOS + iTerm2 you get the full
|
|
141
|
+
experience; anywhere else it falls back to a browser tab automatically (or jump straight there with
|
|
142
|
+
`streamweaver get-started --degraded`). See [Platform support](#platform-support) above.
|
|
143
|
+
|
|
144
|
+
Prefer to explore on your own first?
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
# Interactive tutorial
|
|
148
|
+
streamweaver tutorial
|
|
149
|
+
|
|
150
|
+
# Browse examples
|
|
151
|
+
streamweaver showcase
|
|
152
|
+
|
|
153
|
+
# Run any example
|
|
154
|
+
ruby examples/basic/hello_world.rb
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
## Standalone Mode
|
|
160
|
+
|
|
161
|
+
The simplest path - one Ruby file, one command:
|
|
162
|
+
|
|
163
|
+
```ruby
|
|
164
|
+
# todo.rb
|
|
165
|
+
require 'stream_weaver'
|
|
166
|
+
|
|
167
|
+
app "Todo List" do
|
|
168
|
+
text_field :new_todo, placeholder: "What needs doing?"
|
|
169
|
+
|
|
170
|
+
button "Add" do |state|
|
|
171
|
+
state[:todos] ||= []
|
|
172
|
+
state[:todos] << state[:new_todo]
|
|
173
|
+
state[:new_todo] = ""
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
state[:todos]&.each do |todo|
|
|
177
|
+
div { text "• #{todo}" }
|
|
178
|
+
end
|
|
179
|
+
end.run!
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
```bash
|
|
183
|
+
ruby todo.rb
|
|
184
|
+
# Browser opens at http://localhost:4567
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
## Puma-dev Mode
|
|
190
|
+
|
|
191
|
+
Run StreamWeaver apps with [Puma-dev](https://github.com/puma/puma-dev) for memorable local URLs like `http://myapp.test` that are always available without manually starting the server:
|
|
192
|
+
|
|
193
|
+
```ruby
|
|
194
|
+
# config.ru
|
|
195
|
+
require 'bundler/setup'
|
|
196
|
+
require 'stream_weaver'
|
|
197
|
+
|
|
198
|
+
App = app "My App" do
|
|
199
|
+
header1 "Hello from Puma-dev!"
|
|
200
|
+
text_field :name, placeholder: "Your name"
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
run App
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
```bash
|
|
207
|
+
# Link to Puma-dev
|
|
208
|
+
puma-dev link
|
|
209
|
+
|
|
210
|
+
# Access at http://[directory-name].test
|
|
211
|
+
# Browser won't auto-open - perfect for on-demand access
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
**Key differences from standalone mode:**
|
|
215
|
+
- Uses the `PORT` environment variable set by Puma-dev
|
|
216
|
+
- Browser doesn't auto-open (you access the URL when you need it)
|
|
217
|
+
- App starts automatically on first request
|
|
218
|
+
|
|
219
|
+
See [examples/puma_dev](examples/puma_dev) for a complete example.
|
|
220
|
+
|
|
221
|
+
---
|
|
222
|
+
|
|
223
|
+
## Agentic Mode
|
|
224
|
+
|
|
225
|
+
When Claude Code needs structured input, not terminal menus:
|
|
226
|
+
|
|
227
|
+
```ruby
|
|
228
|
+
result = app "Project Setup" do
|
|
229
|
+
header "Configure New Project"
|
|
230
|
+
text_field :name, placeholder: "Project name"
|
|
231
|
+
select :database, ["PostgreSQL", "SQLite", "MySQL"]
|
|
232
|
+
checkbox :docker, "Include Docker setup"
|
|
233
|
+
end.run_once!
|
|
234
|
+
|
|
235
|
+
# Browser opens, user fills form, returns:
|
|
236
|
+
# { "name" => "myapp", "database" => "PostgreSQL", "docker" => true }
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
The browser opens, user fills the form, JSON returns to the calling script. Perfect for AI workflows that need human input.
|
|
240
|
+
|
|
241
|
+
---
|
|
242
|
+
|
|
243
|
+
## Canvas Mode
|
|
244
|
+
|
|
245
|
+
A persistent browser display that Claude Code can update. Content stays visible instead of scrolling away in the terminal.
|
|
246
|
+
|
|
247
|
+
```bash
|
|
248
|
+
# Start the canvas (keeps running)
|
|
249
|
+
streamweaver live mynotes
|
|
250
|
+
|
|
251
|
+
# Claude Code pushes content as it works
|
|
252
|
+
streamweaver push mynotes --dsl 'md "# Analysis Results\n\n## Key Findings\n- ..."'
|
|
253
|
+
|
|
254
|
+
# Canvas updates in real-time
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
**Pro tip:** iTerm2 has a [built-in browser](https://iterm2.com/documentation-web.html) that fits into split panes - run Claude Code on the left, canvas on the right, same window.
|
|
258
|
+
|
|
259
|
+
### Saving & Sharing Docs
|
|
260
|
+
|
|
261
|
+
A canvas session's "Save as doc" button writes it to disk in two possible
|
|
262
|
+
formats:
|
|
263
|
+
|
|
264
|
+
- **`.rb`** — the DSL source, canonical. Always lossless.
|
|
265
|
+
- **`.org`** — a human-readable, roundtrippable export (`streamweaver
|
|
266
|
+
org-export <file.rb>` / `org-render <file.org>` from the CLI, or the
|
|
267
|
+
Save-as-Org button in the UI). `:doc`-vocabulary content (`doc_header`,
|
|
268
|
+
`callout`, `card`, `table`, etc.) round-trips cleanly and reads like a real
|
|
269
|
+
document in GitHub's own file view or any generic org-mode viewer, with no
|
|
270
|
+
StreamWeaver tooling required. Content outside that vocabulary still
|
|
271
|
+
round-trips (a verbatim-recovered raw block), just without the readability
|
|
272
|
+
payoff. Saving as org shows a coverage notice when a doc isn't a good fit
|
|
273
|
+
for the format — never blocks the save.
|
|
274
|
+
|
|
275
|
+
Where a doc saves is automatic today: repo-local
|
|
276
|
+
(`<repo>/docs/streamweaver_canvas/`) if you're inside a git repo, `~/.streamweaver/canvas`
|
|
277
|
+
otherwise — no way to choose yet. An explicit "global vs. this repo" toggle
|
|
278
|
+
is designed but not built (`docs/plans/canvas-doc-location-and-discovery.md`);
|
|
279
|
+
expect this UX to change.
|
|
280
|
+
|
|
281
|
+
Saved `.rb`/`.org` docs checked into a GitHub repo render with full
|
|
282
|
+
StreamWeaver styling via the [browser extension](extension/README.md) — see
|
|
283
|
+
below.
|
|
284
|
+
|
|
285
|
+
### Templates for Common Patterns
|
|
286
|
+
|
|
287
|
+
```bash
|
|
288
|
+
# Quick selection
|
|
289
|
+
streamweaver template choices mysession '{"title": "Pick DB", "options": ["PostgreSQL", "SQLite"]}'
|
|
290
|
+
# Returns: {"choice": "PostgreSQL"}
|
|
291
|
+
|
|
292
|
+
# Yes/No confirmation
|
|
293
|
+
streamweaver template confirm mysession '{"title": "Delete?", "message": "Cannot be undone"}'
|
|
294
|
+
# Returns: {"confirmed": true}
|
|
295
|
+
|
|
296
|
+
# Multi-step wizard
|
|
297
|
+
streamweaver template wizard mysession '{"steps": [...]}'
|
|
298
|
+
|
|
299
|
+
# Data table with selection
|
|
300
|
+
streamweaver template table mysession '{"headers": ["File", "Size"], "rows": [...]}'
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
---
|
|
304
|
+
|
|
305
|
+
## Browser Extension
|
|
306
|
+
|
|
307
|
+
Push a `.rb` or `.org` doc to GitHub or a Gist, and even without this
|
|
308
|
+
extension it's still legible — `.org` already reads close to markdown there.
|
|
309
|
+
With this extension, either format renders exactly as designed: sidebar nav,
|
|
310
|
+
callouts, cards, tables, live Mermaid diagrams, all compiled and displayed
|
|
311
|
+
entirely inside the browser. No StreamWeaver install, no Ruby, no server, no
|
|
312
|
+
vendor lock-in — just a "View rendered" button next to the file. Works on
|
|
313
|
+
public and private repo blob pages, GitHub Gists (including multi-file
|
|
314
|
+
gists), and local files dropped straight into the viewer with no GitHub at
|
|
315
|
+
all.
|
|
316
|
+
|
|
317
|
+
That's the actual point of shipping this publicly: docs like this only get to
|
|
318
|
+
move freely between a work team and outside collaborators once anyone with a
|
|
319
|
+
browser can open one.
|
|
320
|
+
|
|
321
|
+
### Install
|
|
322
|
+
|
|
323
|
+
**[Chrome Web Store](https://chromewebstore.google.com/detail/streamweaver-doc-viewer/odjjednfpfiagefgpcfdlelldphmpcgj)**
|
|
324
|
+
— the primary path. One click, no build step, works right away.
|
|
325
|
+
|
|
326
|
+
**Dev path** — building from source, for contributors working on the
|
|
327
|
+
extension itself:
|
|
328
|
+
|
|
329
|
+
```bash
|
|
330
|
+
bin/vendor_browser_assets # once
|
|
331
|
+
bin/build_extension
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
Then `chrome://extensions` → Developer mode → Load unpacked → select
|
|
335
|
+
`extension/`. See [`extension/README.md`](extension/README.md) for how it
|
|
336
|
+
works, the full architecture, and current known gaps.
|
|
337
|
+
|
|
338
|
+
### Share a doc
|
|
339
|
+
|
|
340
|
+
Two ways to hand someone a doc, depending on how much staying power it needs:
|
|
341
|
+
|
|
342
|
+
- **Quick collab** — push the doc to a Gist, then send the Gist link plus
|
|
343
|
+
the [extension link](https://chromewebstore.google.com/detail/streamweaver-doc-viewer/odjjednfpfiagefgpcfdlelldphmpcgj).
|
|
344
|
+
No install for you, one click for them: after installing, they open the
|
|
345
|
+
Gist link and a **View rendered** button appears in that file's header
|
|
346
|
+
bar, next to its Raw/Copy buttons — click it. (If the Gist tab was
|
|
347
|
+
already open before installing, refresh the page first; a newly
|
|
348
|
+
installed extension only runs in tabs loaded after install.) Good for a
|
|
349
|
+
one-off review or looping in someone outside the team.
|
|
350
|
+
- **Level up** — commit the same `.org` file to the team repo. It renders
|
|
351
|
+
identically in the repo's own file view, no format change required. The
|
|
352
|
+
doc graduates from shared-once to living with the code it documents.
|
|
353
|
+
|
|
354
|
+
---
|
|
355
|
+
|
|
356
|
+
## Components
|
|
357
|
+
|
|
358
|
+
### Text & Headers
|
|
359
|
+
|
|
360
|
+
```ruby
|
|
361
|
+
text "Plain text"
|
|
362
|
+
md "**Markdown** with *formatting* and [links](url)"
|
|
363
|
+
header "Section" # <h2> default
|
|
364
|
+
header1 "Page Title" # through header6
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
### Form Inputs
|
|
368
|
+
|
|
369
|
+
```ruby
|
|
370
|
+
text_field :name, placeholder: "Name", default: "Alice"
|
|
371
|
+
text_area :bio, rows: 5
|
|
372
|
+
select :color, ["Red", "Green", "Blue"], default: "Green"
|
|
373
|
+
checkbox :agree, "I accept"
|
|
374
|
+
radio_group :size, ["S", "M", "L"]
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
### Buttons
|
|
378
|
+
|
|
379
|
+
```ruby
|
|
380
|
+
button "Primary" do |state|
|
|
381
|
+
state[:clicked] = true
|
|
382
|
+
end
|
|
383
|
+
|
|
384
|
+
button "Secondary", style: :secondary do |state|
|
|
385
|
+
# ...
|
|
386
|
+
end
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
### Scoped Fragments
|
|
390
|
+
|
|
391
|
+
Fragments keep the normal single full DSL rerun while limiting the HTML swapped into
|
|
392
|
+
the page. Interactive controls target their enclosing fragment automatically.
|
|
393
|
+
|
|
394
|
+
```ruby
|
|
395
|
+
action :refresh, updates: :sidebar_count do |state, account_id|
|
|
396
|
+
state[:account_id] = account_id
|
|
397
|
+
end
|
|
398
|
+
|
|
399
|
+
fragment :results do
|
|
400
|
+
text "Account: #{state[:account_id]}"
|
|
401
|
+
button "Refresh", action: :refresh, key: 42
|
|
402
|
+
end
|
|
403
|
+
|
|
404
|
+
fragment :sidebar_count do
|
|
405
|
+
text "Selected: #{state[:account_id] ? 1 : 0}"
|
|
406
|
+
end
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
Use `updates:` on an action or button to refresh additional named fragments with
|
|
410
|
+
out-of-band swaps. Server-rendered content in other fragments is intentionally left
|
|
411
|
+
unchanged; omit fragments when an interaction must refresh the whole app. If a target
|
|
412
|
+
disappears or routing changes, StreamWeaver automatically falls back to a full swap.
|
|
413
|
+
|
|
414
|
+
### Layout
|
|
415
|
+
|
|
416
|
+
```ruby
|
|
417
|
+
columns widths: ['30%', '70%'] do
|
|
418
|
+
column { text "Sidebar" }
|
|
419
|
+
column { text "Main" }
|
|
420
|
+
end
|
|
421
|
+
|
|
422
|
+
vstack spacing: :md do
|
|
423
|
+
text "Item 1"
|
|
424
|
+
text "Item 2"
|
|
425
|
+
end
|
|
426
|
+
|
|
427
|
+
hstack justify: :between do
|
|
428
|
+
button "Cancel", style: :secondary
|
|
429
|
+
button "Save"
|
|
430
|
+
end
|
|
431
|
+
|
|
432
|
+
card do
|
|
433
|
+
header3 "Title"
|
|
434
|
+
text "Content"
|
|
435
|
+
end
|
|
436
|
+
|
|
437
|
+
collapsible "Show Details" do
|
|
438
|
+
text "Hidden until clicked"
|
|
439
|
+
end
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
### Tables
|
|
443
|
+
|
|
444
|
+
```ruby
|
|
445
|
+
# Simple
|
|
446
|
+
table headers: ["Name", "Age"], rows: [["Alice", 30], ["Bob", 25]]
|
|
447
|
+
|
|
448
|
+
# From hashes (headers inferred)
|
|
449
|
+
table [{ name: "Alice", age: 30 }, { name: "Bob", age: 25 }]
|
|
450
|
+
|
|
451
|
+
# With formatters
|
|
452
|
+
table users do
|
|
453
|
+
column :name
|
|
454
|
+
column :balance, format: :currency
|
|
455
|
+
column :joined, format: :date
|
|
456
|
+
end
|
|
457
|
+
|
|
458
|
+
# Interactive
|
|
459
|
+
table data, sortable: true, sticky_header: true, striped: true, markdown: true
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
### Charts
|
|
463
|
+
|
|
464
|
+
```ruby
|
|
465
|
+
bar_chart data: { sales: 100, costs: 60, profit: 40 }
|
|
466
|
+
line_chart data: [12, 19, 8, 15, 22], fill: true
|
|
467
|
+
pie_chart data: { frontend: 40, backend: 35, devops: 25 }
|
|
468
|
+
sparkline data: [45, 52, 48, 61, 55, 67, 72]
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
### Navigation & Modals
|
|
472
|
+
|
|
473
|
+
```ruby
|
|
474
|
+
tabs :settings do
|
|
475
|
+
tab("General") { text_field :name }
|
|
476
|
+
tab("Advanced") { checkbox :debug, "Debug" }
|
|
477
|
+
end
|
|
478
|
+
|
|
479
|
+
modal :confirm, title: "Are you sure?" do
|
|
480
|
+
text "This cannot be undone."
|
|
481
|
+
modal_footer do
|
|
482
|
+
button "Cancel", style: :secondary do |s| s[:confirm_open] = false end
|
|
483
|
+
button "Delete" do |s| do_delete; s[:confirm_open] = false end
|
|
484
|
+
end
|
|
485
|
+
end
|
|
486
|
+
```
|
|
487
|
+
|
|
488
|
+
### URL Routing
|
|
489
|
+
|
|
490
|
+
Deep links, bookmarkable URLs, and browser back/forward — no router library, just a
|
|
491
|
+
bidirectional map between a URL path and a slice of state:
|
|
492
|
+
|
|
493
|
+
```ruby
|
|
494
|
+
route_with(
|
|
495
|
+
parser: ->(path) { path == '/goals' ? { main_nav: 2 } : nil },
|
|
496
|
+
builder: ->(state) { state[:main_nav] == 2 ? '/goals' : nil }
|
|
497
|
+
)
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
`route_by` covers the simple single-key case. See [`docs/routing.md`](docs/routing.md) for the
|
|
501
|
+
full contract — including a **Common Pitfalls** section worth reading before an app grows past a
|
|
502
|
+
handful of routes (state merges rather than replaces on every GET, so an incomplete route table
|
|
503
|
+
fails silently rather than loudly).
|
|
504
|
+
|
|
505
|
+
### Feedback
|
|
506
|
+
|
|
507
|
+
```ruby
|
|
508
|
+
alert(variant: :success) { text "Saved!" }
|
|
509
|
+
progress_bar value: 75, variant: :success
|
|
510
|
+
spinner label: "Loading..."
|
|
511
|
+
toast_container position: :top_right
|
|
512
|
+
```
|
|
513
|
+
|
|
514
|
+
### Dashboard Components
|
|
515
|
+
|
|
516
|
+
For operations dashboards (best with `theme: :dark`):
|
|
517
|
+
|
|
518
|
+
```ruby
|
|
519
|
+
status_dot status: :green, pulse: true
|
|
520
|
+
badge "5", variant: :danger
|
|
521
|
+
stat_display value: 42, label: "TASKS"
|
|
522
|
+
priority_item priority: :critical, title: "Server down"
|
|
523
|
+
|
|
524
|
+
app_shell sidebar_width: "320px" do
|
|
525
|
+
main { header "Dashboard" }
|
|
526
|
+
sidebar(header: "Alerts") { priority_item priority: :high, title: "CPU 90%" }
|
|
527
|
+
end
|
|
528
|
+
```
|
|
529
|
+
|
|
530
|
+
---
|
|
531
|
+
|
|
532
|
+
## Theming
|
|
533
|
+
|
|
534
|
+
```ruby
|
|
535
|
+
app "My App", theme: :dark do
|
|
536
|
+
# Dark mode with glow effects
|
|
537
|
+
end
|
|
538
|
+
|
|
539
|
+
app "Report", theme: :document do
|
|
540
|
+
# Reading-optimized (serif, paper background)
|
|
541
|
+
end
|
|
542
|
+
```
|
|
543
|
+
|
|
544
|
+
**Built-in themes:** `:default`, `:dashboard`, `:document`, `:dark`
|
|
545
|
+
|
|
546
|
+
---
|
|
547
|
+
|
|
548
|
+
## More Resources
|
|
549
|
+
|
|
550
|
+
- [Canvas Mode Documentation](docs/canvas-roadmap.md)
|
|
551
|
+
- [canvas-read: the document shelf](docs/canvas-read.md) — the most portable way to read StreamWeaver docs, no iTerm2 required
|
|
552
|
+
- [Templates Reference](docs/templates.md)
|
|
553
|
+
- [Components Reference](docs/components_reference.md)
|
|
554
|
+
- [Service Mode](docs/SERVICE_MODE.md)
|
|
555
|
+
- [URL Routing](docs/routing.md) — `route_by`/`route_with`, plus Common Pitfalls for larger apps
|
|
556
|
+
|
|
557
|
+
---
|
|
558
|
+
|
|
559
|
+
## Contributing
|
|
560
|
+
|
|
561
|
+
Contributions welcome! [GitHub repository](https://github.com/fkchang/stream_weaver)
|
|
562
|
+
|
|
563
|
+
## License
|
|
564
|
+
|
|
565
|
+
MIT License - see [LICENSE.txt](LICENSE.txt)
|
data/Rakefile
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "bundler/gem_tasks"
|
|
4
|
+
require "rspec/core/rake_task"
|
|
5
|
+
|
|
6
|
+
RSpec::Core::RakeTask.new(:spec)
|
|
7
|
+
|
|
8
|
+
desc "Run the manual Phase-1 dispatch benchmark"
|
|
9
|
+
task :bench do
|
|
10
|
+
ruby File.expand_path("bench/run.rb", __dir__)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
task default: :spec
|
|
Binary file
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "sinatra/base"
|
|
4
|
+
require "phlex"
|
|
5
|
+
require_relative "../support"
|
|
6
|
+
|
|
7
|
+
module StreamWeaverBench
|
|
8
|
+
module Baselines
|
|
9
|
+
class LedgerRows < Phlex::HTML
|
|
10
|
+
def initialize(people) = @people = people
|
|
11
|
+
def view_template
|
|
12
|
+
tbody(id: "people-rows") do
|
|
13
|
+
@people.each do |person|
|
|
14
|
+
tr(id: "person-#{person[:id]}") do
|
|
15
|
+
td { plain person[:name] }; td { plain person[:email] }; td { plain person[:touched] }
|
|
16
|
+
td { button { plain "Edit" }; button { plain "Delete" } }
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
class LedgerRow < Phlex::HTML
|
|
24
|
+
def initialize(person) = @person = person
|
|
25
|
+
def view_template
|
|
26
|
+
tr(id: "person-#{@person[:id]}") do
|
|
27
|
+
td { plain @person[:name] }; td { plain @person[:email] }; td { plain @person[:touched] }
|
|
28
|
+
td { button { plain "Edit" }; button { plain "Delete" } }
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
class LedgerToolbar < Phlex::HTML
|
|
34
|
+
def initialize(error) = @error = error
|
|
35
|
+
def view_template = div(id: "toolbar") { button { plain "Create" }; button { plain "Invalid" }; span { plain @error } }
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
module Ledger
|
|
39
|
+
module_function
|
|
40
|
+
|
|
41
|
+
def build
|
|
42
|
+
metrics = Metrics.new
|
|
43
|
+
people = StreamWeaverBench.deep_copy(PEOPLE)
|
|
44
|
+
error = ""
|
|
45
|
+
app = Class.new(Sinatra::Base)
|
|
46
|
+
app.set :environment, :test
|
|
47
|
+
app.set :show_exceptions, false
|
|
48
|
+
app.get("/") { LedgerToolbar.new(error).call + "<table>#{LedgerRows.new(people).call}</table>" }
|
|
49
|
+
app.post("/create") { metrics.callback!; people << { id: 51, name: "New Person", email: "new@example.test", touched: "2026-07-01" }; LedgerRow.new(people.last).call }
|
|
50
|
+
app.post("/edit") { metrics.callback!; people.first[:name] = "Edited Person"; LedgerRow.new(people.first).call }
|
|
51
|
+
app.post("/invalid") { metrics.callback!; error = "Name is required"; LedgerToolbar.new(error).call }
|
|
52
|
+
app.post("/filter") { metrics.callback!; LedgerRows.new(people.select { |p| p[:name].downcase.include?(params[:query].to_s.downcase) }).call }
|
|
53
|
+
app.post("/delete") { metrics.callback!; people.shift; "" }
|
|
54
|
+
[app, metrics]
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "sinatra/base"
|
|
4
|
+
require "phlex"
|
|
5
|
+
require_relative "../support"
|
|
6
|
+
|
|
7
|
+
module StreamWeaverBench
|
|
8
|
+
module Baselines
|
|
9
|
+
class StoryDetail < Phlex::HTML
|
|
10
|
+
def initialize(story) = @story = story
|
|
11
|
+
def view_template
|
|
12
|
+
section(id: "story-detail") do
|
|
13
|
+
h2 { plain @story[:title] }
|
|
14
|
+
@story[:notes].each { |note| p { plain note } }
|
|
15
|
+
button { plain "Append note" }; button { plain "Move story" }
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
class BoardColumn < Phlex::HTML
|
|
21
|
+
def initialize(stories, column) = (@stories, @column = stories, column)
|
|
22
|
+
def view_template
|
|
23
|
+
section(id: "column-#{@column.downcase}") do
|
|
24
|
+
h2 { plain @column }
|
|
25
|
+
@stories.select { |story| story[:column] == @column }.each { |story| article { plain "#{story[:id]} — #{story[:title]}" } }
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
module Warroom
|
|
31
|
+
module_function
|
|
32
|
+
|
|
33
|
+
def build
|
|
34
|
+
metrics = Metrics.new
|
|
35
|
+
stories = StreamWeaverBench.deep_copy(STORIES)
|
|
36
|
+
app = Class.new(Sinatra::Base)
|
|
37
|
+
app.set :environment, :test
|
|
38
|
+
app.set :show_exceptions, false
|
|
39
|
+
app.get("/") do
|
|
40
|
+
%w[Ready Active Done].map { |column| BoardColumn.new(stories, column).call }.join + StoryDetail.new(stories.first).call
|
|
41
|
+
end
|
|
42
|
+
app.post("/note") { metrics.callback!; stories.first[:notes] << "Appended note"; StoryDetail.new(stories.first).call }
|
|
43
|
+
app.post("/move") do
|
|
44
|
+
metrics.callback!; stories.first[:column] = "Active"
|
|
45
|
+
BoardColumn.new(stories, "Ready").call + BoardColumn.new(stories, "Active").call
|
|
46
|
+
end
|
|
47
|
+
[app, metrics]
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|