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
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Demo of the Table component with all features
|
|
4
|
+
# Run: ruby examples/components/table_demo.rb
|
|
5
|
+
# Open: http://localhost:4567
|
|
6
|
+
|
|
7
|
+
require_relative '../../lib/stream_weaver'
|
|
8
|
+
|
|
9
|
+
# Sample data for demos
|
|
10
|
+
USERS = [
|
|
11
|
+
{ name: "Alice Johnson", email: "alice@example.com", balance: 1234.56, joined: Date.new(2024, 3, 15), active: true },
|
|
12
|
+
{ name: "Bob Smith", email: "bob@example.com", balance: 567.89, joined: Date.new(2024, 6, 22), active: true },
|
|
13
|
+
{ name: "Carol Davis", email: "carol@example.com", balance: 89012.34, joined: Date.new(2023, 11, 1), active: false },
|
|
14
|
+
{ name: "Dan Wilson", email: "dan@example.com", balance: 234.00, joined: Date.new(2025, 1, 5), active: true }
|
|
15
|
+
].freeze
|
|
16
|
+
|
|
17
|
+
METRICS = {
|
|
18
|
+
month: %w[Jan Feb Mar Apr May Jun],
|
|
19
|
+
revenue: [45000, 52000, 48000, 61000, 55000, 67000],
|
|
20
|
+
users: [120, 145, 132, 178, 165, 203]
|
|
21
|
+
}.freeze
|
|
22
|
+
|
|
23
|
+
app "Table Component Demo", layout: :wide do
|
|
24
|
+
header "Table Component - All Features"
|
|
25
|
+
text "Demonstrating the enhanced table component with smart data inference, formatters, and interactive features."
|
|
26
|
+
|
|
27
|
+
tabs :demo_tabs do
|
|
28
|
+
# Tab 1: Basic Usage
|
|
29
|
+
tab "Basic" do
|
|
30
|
+
vstack spacing: :lg do
|
|
31
|
+
card do
|
|
32
|
+
header3 "Original API"
|
|
33
|
+
text "The original headers/rows API still works:"
|
|
34
|
+
|
|
35
|
+
table headers: ["Name", "Role", "Status"],
|
|
36
|
+
rows: [
|
|
37
|
+
["Alice", "Engineer", "Active"],
|
|
38
|
+
["Bob", "Designer", "Active"],
|
|
39
|
+
["Carol", "Manager", "On Leave"]
|
|
40
|
+
],
|
|
41
|
+
striped: true
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
card do
|
|
45
|
+
header3 "Array of Hashes (Auto-Infer Headers)"
|
|
46
|
+
text "Pass an array of hashes - headers are inferred from keys:"
|
|
47
|
+
|
|
48
|
+
table [
|
|
49
|
+
{ name: "Alice", role: "Engineer", department: "Platform" },
|
|
50
|
+
{ name: "Bob", role: "Designer", department: "Product" },
|
|
51
|
+
{ name: "Carol", role: "Manager", department: "Engineering" }
|
|
52
|
+
], striped: true
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
card do
|
|
56
|
+
header3 "Hash of Arrays"
|
|
57
|
+
text "Pass a hash where keys are columns and values are arrays:"
|
|
58
|
+
|
|
59
|
+
table METRICS, striped: true, compact: true
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Tab 2: Column DSL with Formatters
|
|
65
|
+
tab "Formatters" do
|
|
66
|
+
vstack spacing: :lg do
|
|
67
|
+
card do
|
|
68
|
+
header3 "Column DSL with Built-in Formatters"
|
|
69
|
+
text "Define columns with formatting and alignment:"
|
|
70
|
+
|
|
71
|
+
table USERS do
|
|
72
|
+
column :name
|
|
73
|
+
column :email, header: "E-mail"
|
|
74
|
+
column :balance, format: :currency, align: :right
|
|
75
|
+
column :joined, header: "Join Date", format: :date
|
|
76
|
+
column(:active) { |u| u[:active] ? "Yes" : "No" }
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
card do
|
|
81
|
+
header3 "Available Formatters"
|
|
82
|
+
table headers: ["Formatter", "Input", "Output"],
|
|
83
|
+
rows: [
|
|
84
|
+
[":date", "Date.today", Date.today.strftime("%b %d, %Y")],
|
|
85
|
+
[":datetime", "Time.now", Time.now.strftime("%b %d, %Y %l:%M %p")],
|
|
86
|
+
[":currency", "1234.56", "$1,234.56"],
|
|
87
|
+
[":number", "1234567", "1,234,567"],
|
|
88
|
+
[":percent", "0.42", "42%"]
|
|
89
|
+
],
|
|
90
|
+
bordered: true, compact: true
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
card do
|
|
94
|
+
header3 "Custom Formatter (Proc)"
|
|
95
|
+
text "Use a Proc for custom formatting logic:"
|
|
96
|
+
|
|
97
|
+
table USERS do
|
|
98
|
+
column :name
|
|
99
|
+
column :balance, format: ->(v) { v > 1000 ? "#{(v/1000.0).round(1)}k" : v.to_s }, align: :right
|
|
100
|
+
column(:status) { |u| u[:active] ? "Active" : "Inactive" }
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Tab 3: Interactive Features
|
|
107
|
+
tab "Interactive" do
|
|
108
|
+
vstack spacing: :lg do
|
|
109
|
+
card do
|
|
110
|
+
header3 "Sortable Table"
|
|
111
|
+
text "Click column headers to sort. Handles both text and numeric sorting:"
|
|
112
|
+
|
|
113
|
+
table [
|
|
114
|
+
{ product: "Widget A", price: 29.99, quantity: 150, revenue: 4498.50 },
|
|
115
|
+
{ product: "Widget B", price: 49.99, quantity: 75, revenue: 3749.25 },
|
|
116
|
+
{ product: "Widget C", price: 19.99, quantity: 300, revenue: 5997.00 },
|
|
117
|
+
{ product: "Widget D", price: 99.99, quantity: 25, revenue: 2499.75 }
|
|
118
|
+
], sortable: true, striped: true
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
card do
|
|
122
|
+
header3 "Sticky Header (Scroll to See)"
|
|
123
|
+
text "Header stays visible when scrolling long tables:"
|
|
124
|
+
|
|
125
|
+
# Generate more rows for scrolling demo
|
|
126
|
+
many_rows = (1..20).map do |i|
|
|
127
|
+
{ id: i, name: "Item #{i}", category: %w[A B C].sample, value: rand(100..999) }
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
table many_rows, sticky_header: true, striped: true, compact: true
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
card do
|
|
134
|
+
header3 "Combined: Sortable + Sticky"
|
|
135
|
+
|
|
136
|
+
table USERS + USERS + USERS do
|
|
137
|
+
column :name
|
|
138
|
+
column :balance, format: :currency, align: :right
|
|
139
|
+
column :joined, format: :date
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Tab 4: Data Sources
|
|
146
|
+
tab "Data Sources" do
|
|
147
|
+
vstack spacing: :lg do
|
|
148
|
+
card do
|
|
149
|
+
header3 "State Binding"
|
|
150
|
+
text "Table reads from state - updates when state changes:"
|
|
151
|
+
|
|
152
|
+
state[:dynamic_items] ||= [
|
|
153
|
+
{ item: "Apple", count: 5 },
|
|
154
|
+
{ item: "Banana", count: 3 },
|
|
155
|
+
{ item: "Cherry", count: 8 }
|
|
156
|
+
]
|
|
157
|
+
|
|
158
|
+
table data: :dynamic_items, striped: true
|
|
159
|
+
|
|
160
|
+
hstack spacing: :sm do
|
|
161
|
+
button "Add Random Item" do |s|
|
|
162
|
+
fruits = %w[Mango Orange Grape Kiwi Peach]
|
|
163
|
+
s[:dynamic_items] << { item: fruits.sample, count: rand(1..10) }
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
button "Clear", style: :secondary do |s|
|
|
167
|
+
s[:dynamic_items] = []
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
card do
|
|
173
|
+
header3 "File Loading"
|
|
174
|
+
text "Load data from YAML or JSON files (like charts):"
|
|
175
|
+
md "`table file: \"data/users.yaml\", path: \"users\"`"
|
|
176
|
+
text "The file: and path: options work exactly like charts."
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
card do
|
|
180
|
+
header3 "Transform Block"
|
|
181
|
+
text "Process file data before display:"
|
|
182
|
+
md <<~MD
|
|
183
|
+
```ruby
|
|
184
|
+
table file: "raw_data.yaml" do |data|
|
|
185
|
+
data.map { |r| { name: r[:n], value: r[:v] } }
|
|
186
|
+
end
|
|
187
|
+
```
|
|
188
|
+
MD
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
card do
|
|
192
|
+
header3 "Markdown Links"
|
|
193
|
+
text "Enable clickable links in cells with markdown: true:"
|
|
194
|
+
|
|
195
|
+
table [
|
|
196
|
+
{ issue: "[PROJ-101](https://example.com/issues/101)", title: "Fix login bug", status: "Open" },
|
|
197
|
+
{ issue: "[PROJ-102](https://example.com/issues/102)", title: "Add dark mode", status: "In Progress" },
|
|
198
|
+
{ issue: "[PROJ-103](https://example.com/issues/103)", title: "Update docs", status: "Closed" }
|
|
199
|
+
], markdown: true, striped: true
|
|
200
|
+
|
|
201
|
+
text "Without markdown: true, links show as literal text."
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
# Tab 5: Styling
|
|
207
|
+
tab "Styling" do
|
|
208
|
+
columns do
|
|
209
|
+
column do
|
|
210
|
+
card do
|
|
211
|
+
header4 "Default"
|
|
212
|
+
table [{ a: 1, b: 2 }, { a: 3, b: 4 }]
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
card do
|
|
216
|
+
header4 "Striped"
|
|
217
|
+
table [{ a: 1, b: 2 }, { a: 3, b: 4 }, { a: 5, b: 6 }], striped: true
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
card do
|
|
221
|
+
header4 "Bordered"
|
|
222
|
+
table [{ a: 1, b: 2 }, { a: 3, b: 4 }], bordered: true
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
column do
|
|
227
|
+
card do
|
|
228
|
+
header4 "Compact"
|
|
229
|
+
table [{ a: 1, b: 2 }, { a: 3, b: 4 }], compact: true
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
card do
|
|
233
|
+
header4 "With Caption"
|
|
234
|
+
table [{ a: 1, b: 2 }, { a: 3, b: 4 }], caption: "Sample Data"
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
card do
|
|
238
|
+
header4 "All Options"
|
|
239
|
+
table [{ a: 1, b: 2 }, { a: 3, b: 4 }, { a: 5, b: 6 }],
|
|
240
|
+
striped: true, bordered: true, compact: true,
|
|
241
|
+
caption: "Full Style Demo"
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
end.run!
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Demonstrates: accessing `state` inside streamer.replace blocks
|
|
4
|
+
#
|
|
5
|
+
# KEY PATTERN: Pass `state: state` to streamer.replace/append/prepend whenever
|
|
6
|
+
# the block (or a helper it calls) needs to read session state.
|
|
7
|
+
#
|
|
8
|
+
# Without `state: state`, the replace block runs in FeedBuilder context where
|
|
9
|
+
# `state` is not defined — you'll see:
|
|
10
|
+
# [StreamWeaver] Timer error: NameError: undefined local variable or method `state'
|
|
11
|
+
#
|
|
12
|
+
# Run with: ruby examples/components/timer_with_state_demo.rb
|
|
13
|
+
|
|
14
|
+
require_relative "../../lib/stream_weaver"
|
|
15
|
+
|
|
16
|
+
# Top-level helper — works inside replace blocks (global methods are accessible).
|
|
17
|
+
# Receives state as an argument (not via DSL) since the helper is display-only.
|
|
18
|
+
def render_status_panel(label:, count:, state:)
|
|
19
|
+
card do
|
|
20
|
+
hstack justify: :between do
|
|
21
|
+
text label
|
|
22
|
+
badge state[:filter] || "all", variant: :info
|
|
23
|
+
end
|
|
24
|
+
stat_display value: count, label: "items", color: :blue
|
|
25
|
+
text "Last refresh: #{Time.now.strftime('%H:%M:%S')}"
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
app "Timer + State Demo" do
|
|
30
|
+
header1 "Timer + State Demo"
|
|
31
|
+
md "Change the filter, then watch the panel refresh every 3 seconds. " \
|
|
32
|
+
"The filter value persists across timer refreshes."
|
|
33
|
+
|
|
34
|
+
state[:filter] ||= "all"
|
|
35
|
+
|
|
36
|
+
select :filter, %w[all active paused archived], submit: true
|
|
37
|
+
|
|
38
|
+
md "---"
|
|
39
|
+
|
|
40
|
+
div id: "status-panel" do
|
|
41
|
+
render_status_panel(
|
|
42
|
+
label: "Showing: #{state[:filter]}",
|
|
43
|
+
count: rand(10..99),
|
|
44
|
+
state: state
|
|
45
|
+
)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# CORRECT: pass `state: state` so FeedBuilder exposes it inside the block
|
|
49
|
+
every(3) do |streamer|
|
|
50
|
+
streamer.replace("#status-panel", state: state) do
|
|
51
|
+
render_status_panel(
|
|
52
|
+
label: "Showing: #{state[:filter]}",
|
|
53
|
+
count: rand(10..99),
|
|
54
|
+
state: state
|
|
55
|
+
)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end.run!
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Todo list with due dates -- exercises date_field inside a `form` block
|
|
5
|
+
# (stream_weaver-nvu): title + due date submit together as one payload, and
|
|
6
|
+
# the due date correctly lands in state[:new_task][:due_on] rather than
|
|
7
|
+
# leaking into a flat top-level key.
|
|
8
|
+
# Run: ruby examples/components/todo_due_dates.rb
|
|
9
|
+
|
|
10
|
+
require_relative '../../lib/stream_weaver'
|
|
11
|
+
require 'securerandom'
|
|
12
|
+
|
|
13
|
+
App = app "Todo List with Due Dates" do
|
|
14
|
+
header1 "📝 Todo List"
|
|
15
|
+
md "`date_field` inside a `form` block: title + due date submit together."
|
|
16
|
+
|
|
17
|
+
state[:tasks] ||= []
|
|
18
|
+
|
|
19
|
+
header2 "Add a task"
|
|
20
|
+
form :new_task do
|
|
21
|
+
text_field :title, placeholder: "What needs doing?"
|
|
22
|
+
date_field :due_on, label: "Due date", min: Date.today.iso8601
|
|
23
|
+
|
|
24
|
+
submit "Add Task" do |form_values|
|
|
25
|
+
title = form_values[:title].to_s.strip
|
|
26
|
+
next if title.empty?
|
|
27
|
+
|
|
28
|
+
state[:tasks] << { id: SecureRandom.uuid, title: title, due_on: form_values[:due_on] }
|
|
29
|
+
state[:new_task] = { title: "", due_on: "" }
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
header2 "Tasks (#{state[:tasks].length})"
|
|
34
|
+
|
|
35
|
+
if state[:tasks].empty?
|
|
36
|
+
text "No tasks yet -- add one above.", tone: :muted
|
|
37
|
+
else
|
|
38
|
+
sorted_tasks = state[:tasks].sort_by do |task|
|
|
39
|
+
due = StreamWeaver::Components::DateField.to_date(task[:due_on])
|
|
40
|
+
[due ? 0 : 1, due || Date.new(9999, 12, 31)]
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
sorted_tasks.each do |task|
|
|
44
|
+
due = StreamWeaver::Components::DateField.to_date(task[:due_on])
|
|
45
|
+
overdue = due && due < Date.today
|
|
46
|
+
|
|
47
|
+
div class: "todo-item" do
|
|
48
|
+
text task[:title]
|
|
49
|
+
if due
|
|
50
|
+
text "Due #{due.iso8601}#{overdue ? ' (overdue)' : ''}", tone: overdue ? :error : :muted
|
|
51
|
+
else
|
|
52
|
+
text "No due date", tone: :muted
|
|
53
|
+
end
|
|
54
|
+
# key: a stable per-task id, so reordering by due date can't misroute the click
|
|
55
|
+
button "✓", key: task[:id], style: :secondary do |s|
|
|
56
|
+
s[:tasks].reject! { |t| t[:id] == task[:id] }
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
App.run! if __FILE__ == $0
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# UAT demo for three framework gaps closed in one session (2026-08-06):
|
|
4
|
+
# 1. Table cell style escape hatches (col.style, id_style:, id_column:)
|
|
5
|
+
# 2. collapsible subtitle:/badge_text:/badge_variant: parity with expandable_card
|
|
6
|
+
# 3. copy_button primitive + code_block copy: affordance
|
|
7
|
+
#
|
|
8
|
+
# Run: SW_NO_OPEN=1 ruby examples/components/uat_gaps_demo.rb
|
|
9
|
+
# Open: http://localhost:4567 (or whatever port the banner prints)
|
|
10
|
+
|
|
11
|
+
require_relative '../../lib/stream_weaver'
|
|
12
|
+
|
|
13
|
+
TEAM = [
|
|
14
|
+
{ assignee: "Priya Patel", tier: "P1", open_issues: 4 },
|
|
15
|
+
{ assignee: "Sam Okafor", tier: "P2", open_issues: 1 },
|
|
16
|
+
{ assignee: "Jin Watanabe", tier: "P0", open_issues: 7 }
|
|
17
|
+
].freeze
|
|
18
|
+
|
|
19
|
+
app "UAT: Table / Collapsible / Copy gaps", layout: :wide do
|
|
20
|
+
header "Three Gaps, One Session"
|
|
21
|
+
text "Each card below is a UAT checkpoint for one specific fix. See docs/ideas/2026-08-06-table-collapsible-clipboard-gaps.md."
|
|
22
|
+
|
|
23
|
+
vstack spacing: :lg do
|
|
24
|
+
# ---------------------------------------------------------------
|
|
25
|
+
card do
|
|
26
|
+
header3 "1a. Table: legacy default UNCHANGED (regression guard)"
|
|
27
|
+
text "Raw headers:/rows: table, no options. First column (Key) should still render accent-blue monospace, like every table before this session."
|
|
28
|
+
table headers: ["Key", "Title", "Status"],
|
|
29
|
+
rows: [
|
|
30
|
+
["PROJ-101", "Fix login bug", "Open"],
|
|
31
|
+
["PROJ-102", "Add dark mode", "In Progress"]
|
|
32
|
+
]
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
card do
|
|
36
|
+
header3 "1b. Table: id_column escape hatch for raw tables"
|
|
37
|
+
text "Same shape as Sprint Focus's Team table -- first column is an Assignee name, not an id. id_column: false turns OFF the accent-mono treatment table-wide."
|
|
38
|
+
table headers: ["Assignee", "Tier", "Open Issues"],
|
|
39
|
+
rows: TEAM.map { |r| [r[:assignee], r[:tier], r[:open_issues]] },
|
|
40
|
+
id_column: false
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
card do
|
|
44
|
+
header3 "1c. Table: id_column pointed at a different column"
|
|
45
|
+
text "id_column: 1 -- only the Tier column (index 1) gets the identifier treatment."
|
|
46
|
+
table headers: ["Assignee", "Tier", "Open Issues"],
|
|
47
|
+
rows: TEAM.map { |r| [r[:assignee], r[:tier], r[:open_issues]] },
|
|
48
|
+
id_column: 1
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
card do
|
|
52
|
+
header3 "1d. Table: column DSL with style: Proc (per-row) + id_style: false"
|
|
53
|
+
text "Open Issues column gets a Proc style (red when > 3); Assignee opts out of accent via id_style: false even though it's column 0."
|
|
54
|
+
table TEAM do
|
|
55
|
+
column :assignee, id_style: false
|
|
56
|
+
column :tier
|
|
57
|
+
column :open_issues, header: "Open Issues",
|
|
58
|
+
style: ->(item) { item[:open_issues] > 3 ? "color: #c0392b; font-weight: 700;" : nil }
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# ---------------------------------------------------------------
|
|
63
|
+
card do
|
|
64
|
+
header3 "2a. Collapsible: plain (legacy look, unchanged)"
|
|
65
|
+
collapsible "Show details" do
|
|
66
|
+
text "No subtitle, no badge -- must look exactly like it did before this session."
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
card do
|
|
71
|
+
header3 "2b. Collapsible: subtitle + badge (the actual gap)"
|
|
72
|
+
collapsible "Priority Alignment: cultiv-dashboard", subtitle: "12 issues, 83% aligned", badge_text: "P1", badge_variant: :danger do
|
|
73
|
+
text "This is the pattern that used to force a switch to expandable_card just for a badge."
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
card do
|
|
78
|
+
header3 "2c. Collapsible: class:/style: forwarding (previously silently dropped)"
|
|
79
|
+
collapsible "Custom-styled section", subtitle: "class:/style: now apply", badge_text: "NEW", badge_variant: :success,
|
|
80
|
+
class: "uat-custom-collapsible", style: "border-color: #2563eb;" do
|
|
81
|
+
text "Inspect the outer div -- it should carry class=\"collapsible sw-collapsible uat-custom-collapsible\" and the inline border-color."
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# ---------------------------------------------------------------
|
|
86
|
+
card do
|
|
87
|
+
header3 "3a. copy_button: plain text"
|
|
88
|
+
copy_button "Copy summary", text: "Sprint Focus: 3 gaps closed, 2613 tests green."
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
card do
|
|
92
|
+
header3 "3b. copy_button: hostile payload (quotes, newline, backslash, <script>)"
|
|
93
|
+
text "Click Copy, then paste somewhere to confirm the exact payload round-trips -- this is the safety-critical case."
|
|
94
|
+
copy_button "Copy hostile text", text: "Line one \"quoted\"\nLine two with a backslash \\ and <script>alert(1)</script>"
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
card do
|
|
98
|
+
header3 "3c. code_block: copy: true (opt-in, default off elsewhere)"
|
|
99
|
+
code_block <<~RUBY, lang: "ruby", copy: true
|
|
100
|
+
def hello(name)
|
|
101
|
+
puts "Hello, \#{name}!"
|
|
102
|
+
end
|
|
103
|
+
RUBY
|
|
104
|
+
text "Below: same code_block with copy: omitted -- must show NO copy affordance (regression guard)."
|
|
105
|
+
code_block <<~RUBY, lang: "ruby"
|
|
106
|
+
def goodbye(name)
|
|
107
|
+
puts "Goodbye, \#{name}!"
|
|
108
|
+
end
|
|
109
|
+
RUBY
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end.run!
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# External Feed Simulator - pushes targeted updates to a running StreamWeaver app.
|
|
5
|
+
#
|
|
6
|
+
# Demonstrates the Feed DSL: connect by app name (via portfile) and push
|
|
7
|
+
# updates using the same component DSL as the app itself. Zero raw HTML.
|
|
8
|
+
#
|
|
9
|
+
# Usage:
|
|
10
|
+
# 1. Start the dashboard: bundle exec ruby examples/dashboard/live_dashboard.rb
|
|
11
|
+
# 2. In another terminal: bundle exec ruby examples/dashboard/feed_simulator.rb
|
|
12
|
+
|
|
13
|
+
require_relative '../../lib/stream_weaver'
|
|
14
|
+
|
|
15
|
+
feed = StreamWeaver.connect("Live Monitor")
|
|
16
|
+
|
|
17
|
+
ACTIVITIES = [
|
|
18
|
+
["Deploy v2.14.3 completed", "Zero-downtime rolling deploy to production"],
|
|
19
|
+
["Anomaly detected in API gateway", "Latency spike on /api/v2/users (p99 > 200ms)"],
|
|
20
|
+
["Auto-scaler triggered", "Added 2 instances to web-pool (CPU > 75%)"],
|
|
21
|
+
["SSL certificate renewed", "Wildcard cert renewed for 90 days"],
|
|
22
|
+
["Database failover test passed", "RDS Multi-AZ failover completed in 28s"],
|
|
23
|
+
["Rate limiter adjusted", "API rate limit increased to 1000 req/min"],
|
|
24
|
+
["Cache hit ratio improved", "Redis cache hit ratio now at 94.2%"],
|
|
25
|
+
["Backup verification passed", "Nightly snapshot restored and validated"],
|
|
26
|
+
].freeze
|
|
27
|
+
|
|
28
|
+
ALERTS = [
|
|
29
|
+
[:critical, "High error rate on API", "Error rate exceeded 5% threshold for 3 minutes"],
|
|
30
|
+
[:urgent, "Memory pressure detected", "Worker nodes approaching OOM limits"],
|
|
31
|
+
[:high, "Slow query detected", "Query on orders table taking >5s"],
|
|
32
|
+
[:normal, "Log volume increasing", "Log ingestion rate up 40% this week"],
|
|
33
|
+
[:critical, "Disk usage critical", "Production DB volume at 94% capacity"],
|
|
34
|
+
[:urgent, "SSL cert expiring soon", "Certificate expires in 7 days"],
|
|
35
|
+
].freeze
|
|
36
|
+
|
|
37
|
+
puts "Feed simulator started - connected to #{feed.url}"
|
|
38
|
+
puts "Press Ctrl+C to stop\n\n"
|
|
39
|
+
|
|
40
|
+
loop do
|
|
41
|
+
# Push a new activity event (prepend to feed)
|
|
42
|
+
act = ACTIVITIES.sample
|
|
43
|
+
time_str = Time.now.strftime("%H:%M:%S")
|
|
44
|
+
|
|
45
|
+
feed.prepend("#activity-feed") do
|
|
46
|
+
activity_item time: time_str, title: act[0], summary: act[1], type: :task
|
|
47
|
+
end
|
|
48
|
+
puts " [activity] #{time_str} #{act[0]}"
|
|
49
|
+
sleep rand(3.0..6.0)
|
|
50
|
+
|
|
51
|
+
# Occasionally push an alert update
|
|
52
|
+
if rand < 0.4
|
|
53
|
+
alert = ALERTS.sample
|
|
54
|
+
feed.prepend("#alerts-panel") do
|
|
55
|
+
priority_item priority: alert[0], title: alert[1], description: alert[2]
|
|
56
|
+
end
|
|
57
|
+
puts " [alert] #{alert[0].upcase}: #{alert[1]}"
|
|
58
|
+
end
|
|
59
|
+
sleep rand(2.0..5.0)
|
|
60
|
+
end
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Example: Live Dashboard with Server-Push via StreamWeaver Streams
|
|
5
|
+
#
|
|
6
|
+
# This dashboard sets up a shell with stable DOM target IDs.
|
|
7
|
+
# It receives real-time updates via SSE from:
|
|
8
|
+
# - An internal stream thread (built-in data source, using component DSL)
|
|
9
|
+
# - External scripts via POST /stream/push (pluggable data sources)
|
|
10
|
+
#
|
|
11
|
+
# Run with: bundle exec ruby examples/dashboard/live_dashboard.rb
|
|
12
|
+
#
|
|
13
|
+
# To push updates externally:
|
|
14
|
+
# bundle exec ruby examples/dashboard/feed_simulator.rb
|
|
15
|
+
# Or manually:
|
|
16
|
+
# curl -X POST localhost:4567/stream/push \
|
|
17
|
+
# -d 'target=#metric-rps&html=<div>MANUAL</div>'
|
|
18
|
+
|
|
19
|
+
require_relative '../../lib/stream_weaver'
|
|
20
|
+
|
|
21
|
+
App = app "Live Monitor", theme: :dark, layout: :fluid do
|
|
22
|
+
app_shell sidebar_width: "360px" do
|
|
23
|
+
main do
|
|
24
|
+
app_header "System Monitor", variant: :primary do
|
|
25
|
+
pulse_indicator color: :green, label: "Live"
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
header2 "KEY METRICS"
|
|
29
|
+
grid cols: 3, gap: :md do
|
|
30
|
+
div(id: "metric-rps") do
|
|
31
|
+
card { stat_display value: "---", label: "REQ/SEC", color: :blue, size: :lg }
|
|
32
|
+
end
|
|
33
|
+
div(id: "metric-latency") do
|
|
34
|
+
card { stat_display value: "---", label: "LATENCY", color: :purple, size: :lg }
|
|
35
|
+
end
|
|
36
|
+
div(id: "metric-errors") do
|
|
37
|
+
card { stat_display value: "---", label: "ERROR RATE", color: :green, size: :lg }
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
header2 "ACTIVITY"
|
|
42
|
+
div(id: "activity-feed") do
|
|
43
|
+
text "Waiting for events..."
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
sidebar header: "Alerts" do
|
|
48
|
+
div(id: "alerts-panel") do
|
|
49
|
+
text "Monitoring..."
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Internal stream thread - pushes simulated metrics using component DSL.
|
|
55
|
+
# No raw HTML needed - same components as the app definition.
|
|
56
|
+
stream do |streamer|
|
|
57
|
+
loop do
|
|
58
|
+
rps = rand(1200..3500)
|
|
59
|
+
streamer.replace("#metric-rps") do
|
|
60
|
+
card { stat_display value: rps, label: "REQ/SEC", color: :blue, size: :lg }
|
|
61
|
+
end
|
|
62
|
+
sleep rand(1.5..3.0)
|
|
63
|
+
|
|
64
|
+
latency = rand(12..85)
|
|
65
|
+
streamer.replace("#metric-latency") do
|
|
66
|
+
card { stat_display value: "#{latency}ms", label: "LATENCY", color: :purple, size: :lg }
|
|
67
|
+
end
|
|
68
|
+
sleep rand(1.5..3.0)
|
|
69
|
+
|
|
70
|
+
err = (rand * 2.5).round(2)
|
|
71
|
+
color = err > 1.5 ? :red : :green
|
|
72
|
+
streamer.replace("#metric-errors") do
|
|
73
|
+
card { stat_display value: "#{err}%", label: "ERROR RATE", color: color, size: :lg }
|
|
74
|
+
end
|
|
75
|
+
sleep rand(2.0..4.0)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
App.run! if __FILE__ == $0
|