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,508 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../../lib/stream_weaver'
|
|
4
|
+
|
|
5
|
+
# Pass theme as command line argument: ruby theme_demo.rb dashboard
|
|
6
|
+
THEME = (ARGV[0] || 'default').to_sym
|
|
7
|
+
|
|
8
|
+
# Sample data ------------------------------------------------------------
|
|
9
|
+
|
|
10
|
+
RECENT_ORDERS = [
|
|
11
|
+
{ order: "#10482", customer: "Alice Johnson", total: 128.50, status: "Shipped" },
|
|
12
|
+
{ order: "#10483", customer: "Bob Smith", total: 64.00, status: "Processing" },
|
|
13
|
+
{ order: "#10484", customer: "Carol Davis", total: 342.10, status: "Delivered" },
|
|
14
|
+
{ order: "#10485", customer: "Dan Wilson", total: 19.99, status: "Cancelled" },
|
|
15
|
+
{ order: "#10486", customer: "Eve Turner", total: 88.25, status: "Shipped" }
|
|
16
|
+
].freeze
|
|
17
|
+
|
|
18
|
+
app = StreamWeaver::App.new("Theme Demo", theme: THEME, layout: :fluid) do
|
|
19
|
+
# NOTE: section switching uses route_by + nav_item (plain <a href> links + GET routing)
|
|
20
|
+
# rather than button-with-block, because StreamWeaver's button-action dispatcher
|
|
21
|
+
# (find_button_recursive in server.rb) only walks component.children -- it does not
|
|
22
|
+
# know about AppShell#main_children / #sidebar_children, so any `button ... do |s| end`
|
|
23
|
+
# placed inside an app_shell's main/sidebar blocks is currently unreachable. Routing
|
|
24
|
+
# (GET requests) goes through a separate code path and is unaffected.
|
|
25
|
+
route_by :section, dashboard: "/", planning: "/planning", docs: "/docs", settings: "/settings"
|
|
26
|
+
state[:section] ||= :dashboard
|
|
27
|
+
|
|
28
|
+
app_shell sidebar_width: "220px", sidebar_position: :left do
|
|
29
|
+
sidebar header: "Theme Demo" do
|
|
30
|
+
vstack spacing: :xs do
|
|
31
|
+
nav_item "Dashboard", href: "/", active: state[:section] == :dashboard
|
|
32
|
+
nav_item "Planning", href: "/planning", active: state[:section] == :planning
|
|
33
|
+
nav_item "Docs", href: "/docs", active: state[:section] == :docs
|
|
34
|
+
nav_item "Settings", href: "/settings", active: state[:section] == :settings
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
main do
|
|
39
|
+
navbar do
|
|
40
|
+
hstack(justify: :between, align: :center) do
|
|
41
|
+
header2 "StreamWeaver Themes"
|
|
42
|
+
theme_switcher
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
text "Use the theme switcher above to change themes in real-time!"
|
|
47
|
+
text "Or run with: ruby theme_demo.rb [default|dashboard|document|doc]"
|
|
48
|
+
|
|
49
|
+
breadcrumbs do
|
|
50
|
+
crumb "Home", href: "/"
|
|
51
|
+
crumb state[:section].to_s.capitalize
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
case state[:section]
|
|
55
|
+
|
|
56
|
+
# =========================================================
|
|
57
|
+
# SECTION 1: Dashboard
|
|
58
|
+
# =========================================================
|
|
59
|
+
when :dashboard
|
|
60
|
+
header1 "Dashboard"
|
|
61
|
+
|
|
62
|
+
pulse_indicator color: :green, label: "All Systems Operational"
|
|
63
|
+
|
|
64
|
+
header2 "Key Metrics"
|
|
65
|
+
kpi_dashboard metrics: [
|
|
66
|
+
{ value: "1,234", label: "USERS", color: :blue, trend: :up },
|
|
67
|
+
{ value: "$45.6K", label: "REVENUE", color: :green, trend: :up },
|
|
68
|
+
{ value: "+12.3%", label: "GROWTH", color: :purple, trend: :up },
|
|
69
|
+
{ value: "3", label: "ALERTS", color: :red, trend: :down }
|
|
70
|
+
]
|
|
71
|
+
|
|
72
|
+
header2 "Trends"
|
|
73
|
+
columns widths: ["1fr", "1fr"] do
|
|
74
|
+
column do
|
|
75
|
+
card do
|
|
76
|
+
header3 "Revenue by Channel"
|
|
77
|
+
bar_chart data: {
|
|
78
|
+
"Web" => 4200, "Mobile" => 3100, "Partner" => 1800, "Retail" => 950
|
|
79
|
+
}, colors: ["#4a90d9"], height: "220px"
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
column do
|
|
83
|
+
card do
|
|
84
|
+
header3 "Weekly Active Users"
|
|
85
|
+
line_chart data: [820, 932, 901, 934, 1290, 1330, 1420],
|
|
86
|
+
fill: true, colors: ["#10b981"], height: "220px"
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
header2 "Recent Orders"
|
|
92
|
+
card do
|
|
93
|
+
table RECENT_ORDERS do
|
|
94
|
+
column :order
|
|
95
|
+
column :customer
|
|
96
|
+
column :total, format: :currency, align: :right
|
|
97
|
+
column(:status) { |row| row[:status] }
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
header2 "Status"
|
|
102
|
+
hstack spacing: :lg, align: :center do
|
|
103
|
+
vstack spacing: :xs, align: :center do
|
|
104
|
+
status_dot status: :green, pulse: true
|
|
105
|
+
text "API"
|
|
106
|
+
end
|
|
107
|
+
vstack spacing: :xs, align: :center do
|
|
108
|
+
status_dot status: :green
|
|
109
|
+
text "Database"
|
|
110
|
+
end
|
|
111
|
+
vstack spacing: :xs, align: :center do
|
|
112
|
+
status_dot status: :yellow
|
|
113
|
+
text "Queue"
|
|
114
|
+
end
|
|
115
|
+
vstack spacing: :xs, align: :center do
|
|
116
|
+
status_dot status: :red
|
|
117
|
+
text "CDN"
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
hstack spacing: :md do
|
|
122
|
+
badge "5", variant: :default
|
|
123
|
+
badge "3", variant: :danger
|
|
124
|
+
badge "12", variant: :warning
|
|
125
|
+
badge "OK", variant: :success
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
status_badge :strong, "All core services healthy"
|
|
129
|
+
|
|
130
|
+
header2 "Rollout Progress"
|
|
131
|
+
progress_bar value: 72, show_label: true
|
|
132
|
+
|
|
133
|
+
header2 "Activity Feed"
|
|
134
|
+
vstack spacing: :none do
|
|
135
|
+
activity_item time: "15:00", title: "Deployment completed",
|
|
136
|
+
summary: "v2.4.1 shipped to production, zero downtime",
|
|
137
|
+
type: :task
|
|
138
|
+
activity_item time: "14:30", title: "Anomaly detected",
|
|
139
|
+
summary: "CDN latency spike in EU region, investigating",
|
|
140
|
+
type: :escalation
|
|
141
|
+
activity_item time: "14:00", title: "Sprint planning notes shared",
|
|
142
|
+
summary: "Team aligned on Q3 priorities",
|
|
143
|
+
type: :communication
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# =========================================================
|
|
147
|
+
# SECTION 2: Planning
|
|
148
|
+
# =========================================================
|
|
149
|
+
when :planning
|
|
150
|
+
header1 "Planning"
|
|
151
|
+
text "A live look at the visual-plan skill: what's changing, open decisions, and mockups."
|
|
152
|
+
|
|
153
|
+
header2 "Implementation Map"
|
|
154
|
+
implementation_map(files: [
|
|
155
|
+
{ path: "lib/stream_weaver/theme/auto_mode.rb", note: "Fix contrast bug in default theme's muted text" },
|
|
156
|
+
{ path: "lib/stream_weaver/adapter/alpinejs.rb", note: "Add CSS variable fallback for table headers" },
|
|
157
|
+
{ path: "spec/theme_enhanced_spec.rb", note: "Cover new theme-switch edge cases" }
|
|
158
|
+
])
|
|
159
|
+
|
|
160
|
+
header2 "Open Decision"
|
|
161
|
+
decision(question: "Which theme should ship as the new default?") do
|
|
162
|
+
option(id: :dashboard, label: "Dashboard (dark)",
|
|
163
|
+
detail: "High contrast, data-dense — best for ops/monitoring use cases.",
|
|
164
|
+
recommended: true)
|
|
165
|
+
option(id: :default, label: "Default (light)",
|
|
166
|
+
detail: "Currently the least polished per design review — needs more work.")
|
|
167
|
+
option(id: :document, label: "Document",
|
|
168
|
+
detail: "Best for long-form/reading contexts, not general app chrome.")
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
header2 "Code Change"
|
|
172
|
+
diff(language: "ruby") do
|
|
173
|
+
before do
|
|
174
|
+
<<~RUBY
|
|
175
|
+
def muted_text_color
|
|
176
|
+
"#9ca3af"
|
|
177
|
+
end
|
|
178
|
+
RUBY
|
|
179
|
+
end
|
|
180
|
+
after do
|
|
181
|
+
<<~RUBY
|
|
182
|
+
def muted_text_color
|
|
183
|
+
"var(--sw-text-muted, #9ca3af)"
|
|
184
|
+
end
|
|
185
|
+
RUBY
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
header2 "Annotated Code"
|
|
190
|
+
annotated_code(language: "ruby", annotations: [
|
|
191
|
+
{ line: 1, note: "Reads the CSS custom property set by the active theme" },
|
|
192
|
+
{ line: 2, note: "Falls back to a fixed hex value if the var is undefined" }
|
|
193
|
+
]) do
|
|
194
|
+
<<~RUBY
|
|
195
|
+
def muted_text_color
|
|
196
|
+
"var(--sw-text-muted, #9ca3af)"
|
|
197
|
+
end
|
|
198
|
+
RUBY
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
header2 "Wireframe: Theme Switcher Placement"
|
|
202
|
+
wireframe(surface: :browser) do
|
|
203
|
+
<<~HTML
|
|
204
|
+
<div class="wf-card">
|
|
205
|
+
<h2>Navbar</h2>
|
|
206
|
+
<p class="wf-muted">Theme switcher pinned top-right, always visible</p>
|
|
207
|
+
<button class="primary">Theme: Dashboard ▾</button>
|
|
208
|
+
</div>
|
|
209
|
+
HTML
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
header2 "API Reference"
|
|
213
|
+
api_endpoint method: "GET", path: "/api/themes",
|
|
214
|
+
description: "List available themes",
|
|
215
|
+
params: [
|
|
216
|
+
{ name: "include_custom", type: "boolean", required: false }
|
|
217
|
+
],
|
|
218
|
+
response: { themes: "array", default: "string" }
|
|
219
|
+
|
|
220
|
+
header2 "Design Deck: Theme Token Strategy"
|
|
221
|
+
design_deck "Theme Token Strategy" do
|
|
222
|
+
slide "tokens", "CSS Variable Approach",
|
|
223
|
+
context: "How should theme values propagate to components?" do
|
|
224
|
+
option "CSS Custom Properties", aside: "Live-swappable at runtime, no rebuild needed.",
|
|
225
|
+
recommended: true do
|
|
226
|
+
code_block <<~CSS, lang: "css"
|
|
227
|
+
:root.sw-theme-dashboard {
|
|
228
|
+
--sw-bg: #0f172a;
|
|
229
|
+
--sw-text: #e2e8f0;
|
|
230
|
+
}
|
|
231
|
+
CSS
|
|
232
|
+
end
|
|
233
|
+
option "Sass Variables", aside: "Compile-time only — requires a rebuild per theme switch." do
|
|
234
|
+
code_block <<~SCSS, lang: "scss"
|
|
235
|
+
$sw-bg: #0f172a;
|
|
236
|
+
$sw-text: #e2e8f0;
|
|
237
|
+
SCSS
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
slide "rollout", "Rollout Plan",
|
|
242
|
+
context: "How do we ship the fixes without breaking existing embeds?" do
|
|
243
|
+
option "Ship behind a flag", aside: "Safer, slower.", recommended: true do
|
|
244
|
+
mermaid <<~MERMAID, compact: true
|
|
245
|
+
graph LR
|
|
246
|
+
A[Fix CSS] --> B[Flag: theme_v2]
|
|
247
|
+
B --> C[Dogfood]
|
|
248
|
+
C --> D[Full rollout]
|
|
249
|
+
MERMAID
|
|
250
|
+
end
|
|
251
|
+
option "Ship directly to all themes", aside: "Faster, riskier for existing embeds." do
|
|
252
|
+
mermaid <<~MERMAID, compact: true
|
|
253
|
+
graph LR
|
|
254
|
+
A[Fix CSS] --> B[Ship to all themes]
|
|
255
|
+
MERMAID
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
# =========================================================
|
|
261
|
+
# SECTION 3: Docs
|
|
262
|
+
# =========================================================
|
|
263
|
+
when :docs
|
|
264
|
+
header1 "Docs"
|
|
265
|
+
|
|
266
|
+
columns widths: ["260px", "1fr"] do
|
|
267
|
+
column do
|
|
268
|
+
sidebar_toc sections: [
|
|
269
|
+
{ id: "overview", label: "Overview" },
|
|
270
|
+
{ id: "callouts", label: "Callouts" },
|
|
271
|
+
{ id: "comparison", label: "Before / After" },
|
|
272
|
+
{ id: "diagram", label: "Diagram" },
|
|
273
|
+
{ id: "files", label: "File Layout" }
|
|
274
|
+
]
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
column do
|
|
278
|
+
doc_header(
|
|
279
|
+
eyebrow: "StreamWeaver · Theming",
|
|
280
|
+
title: "Theme System Overview",
|
|
281
|
+
pills: [
|
|
282
|
+
{ text: "Stable" },
|
|
283
|
+
"Updated July 2026",
|
|
284
|
+
"Owner: core team"
|
|
285
|
+
]
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
doc_section_header "01", "Overview", id: "overview"
|
|
289
|
+
md <<~MD
|
|
290
|
+
StreamWeaver ships four built-in themes: **default**, **dashboard**, **document**,
|
|
291
|
+
and **doc**. Each theme is a set of CSS custom properties applied at the `body` level.
|
|
292
|
+
MD
|
|
293
|
+
|
|
294
|
+
prose do
|
|
295
|
+
text "Themes are swappable live via the theme_switcher component, with no page reload required."
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
pullquote "Design for the brain you have, not the brain you wish you had.",
|
|
299
|
+
attribution: "Gloria Foloran"
|
|
300
|
+
|
|
301
|
+
doc_section_header "02", "Callouts", id: "callouts"
|
|
302
|
+
callout(variant: :info, title: "Tip:") do
|
|
303
|
+
text "Use the theme_switcher component to preview all themes without restarting the server."
|
|
304
|
+
end
|
|
305
|
+
callout(variant: :warning, title: "Heads up:") do
|
|
306
|
+
text "Custom CSS overrides may not survive a theme switch unless scoped to a theme class."
|
|
307
|
+
end
|
|
308
|
+
callout(variant: :success, title: "Verified:") do
|
|
309
|
+
text "All four built-in themes now pass the contrast-ratio audit."
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
doc_section_header "03", "Before / After", id: "comparison"
|
|
313
|
+
comparison(before_label: "Before", after_label: "After") do
|
|
314
|
+
before do
|
|
315
|
+
md "- Flat, single-page demo\n- ~15 components shown\n- Hard to judge real-app fit"
|
|
316
|
+
end
|
|
317
|
+
after do
|
|
318
|
+
md "- Sidebar-navigable admin app\n- 40+ components shown\n- Realistic layout per section"
|
|
319
|
+
end
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
doc_section_header "04", "Diagram", id: "diagram"
|
|
323
|
+
mermaid <<~MERMAID
|
|
324
|
+
graph LR
|
|
325
|
+
A[Theme Switcher] --> B[body.sw-theme-*]
|
|
326
|
+
B --> C[CSS Custom Properties]
|
|
327
|
+
C --> D[Component Styles]
|
|
328
|
+
MERMAID
|
|
329
|
+
|
|
330
|
+
doc_section_header "05", "File Layout", id: "files"
|
|
331
|
+
dir_tree <<~TREE
|
|
332
|
+
examples/styling/
|
|
333
|
+
theme_demo.rb [modified]
|
|
334
|
+
style_showcase.rb
|
|
335
|
+
feedback_demo.rb
|
|
336
|
+
TREE
|
|
337
|
+
|
|
338
|
+
code_editor :sample_snippet, language: :ruby, readonly: true, height: "160px",
|
|
339
|
+
default: <<~RUBY
|
|
340
|
+
app_shell do
|
|
341
|
+
sidebar { text "Nav" }
|
|
342
|
+
main { text "Content" }
|
|
343
|
+
end
|
|
344
|
+
RUBY
|
|
345
|
+
end
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
# =========================================================
|
|
349
|
+
# SECTION 4: Settings
|
|
350
|
+
# =========================================================
|
|
351
|
+
when :settings
|
|
352
|
+
header1 "Settings"
|
|
353
|
+
|
|
354
|
+
header2 "Typography"
|
|
355
|
+
header3 "Headings"
|
|
356
|
+
text "This is a paragraph demonstrating body text. The quick brown fox jumps over the lazy dog."
|
|
357
|
+
|
|
358
|
+
header3 "Lists and Content"
|
|
359
|
+
text "Content flows naturally with appropriate spacing and line height for readability."
|
|
360
|
+
|
|
361
|
+
header2 "Cards"
|
|
362
|
+
card do
|
|
363
|
+
card_header "Card Title"
|
|
364
|
+
card_body do
|
|
365
|
+
text "Card content with some description text. Notice how the card styling changes between themes."
|
|
366
|
+
end
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
header2 "Buttons"
|
|
370
|
+
hstack(spacing: :sm) do
|
|
371
|
+
button "Primary Button"
|
|
372
|
+
button "Secondary", style: :secondary
|
|
373
|
+
end
|
|
374
|
+
|
|
375
|
+
header2 "Form Inputs"
|
|
376
|
+
columns widths: ["1fr", "1fr"] do
|
|
377
|
+
column do
|
|
378
|
+
text_field :name, placeholder: "Enter your name"
|
|
379
|
+
end
|
|
380
|
+
column do
|
|
381
|
+
select :color, ["Red", "Green", "Blue"]
|
|
382
|
+
end
|
|
383
|
+
end
|
|
384
|
+
checkbox :agree, "I agree to the terms"
|
|
385
|
+
|
|
386
|
+
header2 "Checkbox Group"
|
|
387
|
+
checkbox_group :permissions, select_all: "Select All", select_none: "Clear" do
|
|
388
|
+
item "read" do
|
|
389
|
+
text "Read - View content"
|
|
390
|
+
end
|
|
391
|
+
item "write" do
|
|
392
|
+
text "Write - Create and edit content"
|
|
393
|
+
end
|
|
394
|
+
item "admin" do
|
|
395
|
+
text "Admin - Full access"
|
|
396
|
+
end
|
|
397
|
+
end
|
|
398
|
+
|
|
399
|
+
header2 "Radio Group"
|
|
400
|
+
radio_group :plan, ["Free", "Pro", "Enterprise"]
|
|
401
|
+
|
|
402
|
+
header2 "Dropdown"
|
|
403
|
+
select :theme_pref, ["Light", "Dark", "System"], default: "System"
|
|
404
|
+
dropdown do
|
|
405
|
+
trigger do
|
|
406
|
+
button "Actions", style: :primary
|
|
407
|
+
end
|
|
408
|
+
menu do
|
|
409
|
+
menu_item "Edit" do |s|
|
|
410
|
+
s[:last_action] = "Edit clicked"
|
|
411
|
+
end
|
|
412
|
+
menu_item "Duplicate" do |s|
|
|
413
|
+
s[:last_action] = "Duplicate clicked"
|
|
414
|
+
end
|
|
415
|
+
menu_divider
|
|
416
|
+
menu_item "Delete", style: :destructive do |s|
|
|
417
|
+
s[:last_action] = "Delete clicked"
|
|
418
|
+
end
|
|
419
|
+
end
|
|
420
|
+
end
|
|
421
|
+
text -> (s) { s[:last_action] ? "Last action: #{s[:last_action]}" : "Click a menu item..." }
|
|
422
|
+
|
|
423
|
+
header2 "Tags"
|
|
424
|
+
tag_buttons :category, ["Fiction", "Non-fiction", "Mystery", "Sci-Fi"]
|
|
425
|
+
|
|
426
|
+
header2 "Tabs"
|
|
427
|
+
tabs :settings_tabs, variant: :line do
|
|
428
|
+
tab "General" do
|
|
429
|
+
text "General settings content."
|
|
430
|
+
end
|
|
431
|
+
tab "Notifications" do
|
|
432
|
+
checkbox :email_notifs, "Email notifications"
|
|
433
|
+
checkbox :sms_notifs, "SMS notifications"
|
|
434
|
+
end
|
|
435
|
+
tab "Advanced" do
|
|
436
|
+
spinner size: :md, label: "Loading advanced settings..."
|
|
437
|
+
end
|
|
438
|
+
end
|
|
439
|
+
|
|
440
|
+
header2 "Collapsible"
|
|
441
|
+
collapsible "Advanced Options", expanded: false do
|
|
442
|
+
text "Hidden content revealed when expanded."
|
|
443
|
+
checkbox :debug_mode, "Enable debug mode"
|
|
444
|
+
end
|
|
445
|
+
|
|
446
|
+
header2 "Expandable Card"
|
|
447
|
+
expandable_card key: :settings_expanded, title: "Account", subtitle: "Profile & billing",
|
|
448
|
+
badge_text: "2 items", status: :green do
|
|
449
|
+
text "Expandable card content — click the header to collapse."
|
|
450
|
+
end
|
|
451
|
+
|
|
452
|
+
header2 "Alerts"
|
|
453
|
+
alert(variant: :info) { text "This is an info alert" }
|
|
454
|
+
alert(variant: :success) { text "Success message" }
|
|
455
|
+
|
|
456
|
+
header2 "Progress & Spinners"
|
|
457
|
+
progress_bar value: 65, show_label: true
|
|
458
|
+
spinner size: :md, label: "Loading..."
|
|
459
|
+
|
|
460
|
+
header2 "Modal"
|
|
461
|
+
button "Open Settings Modal" do |s|
|
|
462
|
+
s[:settings_modal_open] = true
|
|
463
|
+
end
|
|
464
|
+
modal :settings_modal, title: "Settings", size: :md do
|
|
465
|
+
text "Adjust your preferences here."
|
|
466
|
+
checkbox :dark_mode, "Enable dark mode"
|
|
467
|
+
modal_footer do
|
|
468
|
+
button "Cancel", style: :secondary do |s|
|
|
469
|
+
s[:settings_modal_open] = false
|
|
470
|
+
end
|
|
471
|
+
button "Save", style: :primary do |s|
|
|
472
|
+
s[:settings_modal_open] = false
|
|
473
|
+
end
|
|
474
|
+
end
|
|
475
|
+
end
|
|
476
|
+
|
|
477
|
+
header2 "Toasts"
|
|
478
|
+
toast_container position: :top_right, duration: 4000
|
|
479
|
+
button "Show Toast" do |s|
|
|
480
|
+
show_toast("Settings saved successfully!", variant: :success)
|
|
481
|
+
end
|
|
482
|
+
|
|
483
|
+
header2 "Data Display"
|
|
484
|
+
text "Themes affect data density and scannability."
|
|
485
|
+
card do
|
|
486
|
+
card_body do
|
|
487
|
+
grid cols: 3 do
|
|
488
|
+
vstack do
|
|
489
|
+
text "Users"
|
|
490
|
+
header3 "1,234"
|
|
491
|
+
end
|
|
492
|
+
vstack do
|
|
493
|
+
text "Revenue"
|
|
494
|
+
header3 "$45.6K"
|
|
495
|
+
end
|
|
496
|
+
vstack do
|
|
497
|
+
text "Growth"
|
|
498
|
+
header3 "+12.3%"
|
|
499
|
+
end
|
|
500
|
+
end
|
|
501
|
+
end
|
|
502
|
+
end
|
|
503
|
+
end
|
|
504
|
+
end
|
|
505
|
+
end
|
|
506
|
+
end
|
|
507
|
+
|
|
508
|
+
app.generate.run!
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Example: Endpoint Health Checker
|
|
5
|
+
# A genuinely useful URL health checker — configure endpoints, see live status and response times.
|
|
6
|
+
# Run with: bundle exec ruby examples/timer_health_checker.rb
|
|
7
|
+
|
|
8
|
+
require_relative "../lib/stream_weaver"
|
|
9
|
+
require "net/http"
|
|
10
|
+
require "uri"
|
|
11
|
+
|
|
12
|
+
ENDPOINTS = [
|
|
13
|
+
{ name: "GitHub", key: :github, url: "https://github.com", sla_ms: 500 },
|
|
14
|
+
{ name: "RubyGems", key: :rubygems, url: "https://rubygems.org", sla_ms: 300 },
|
|
15
|
+
{ name: "Httpbin", key: :httpbin, url: "https://httpbin.org/delay/1", sla_ms: 800 },
|
|
16
|
+
].freeze
|
|
17
|
+
|
|
18
|
+
STATUS_BADGE = { up: { text: "UP", variant: :success }, slow: { text: "SLOW", variant: :warning }, down: { text: "DOWN", variant: :danger } }.freeze
|
|
19
|
+
STATUS_COLOR = { up: :green, slow: :yellow, down: :red }.freeze
|
|
20
|
+
STATUS_EMOJI = { up: "\u2705", slow: "\u26A0\uFE0F", down: "\u274C" }.freeze
|
|
21
|
+
RING_CLASS = { down: "sw-alert-ring", slow: "sw-warn-ring" }.freeze
|
|
22
|
+
|
|
23
|
+
RING_CSS = "<style>" \
|
|
24
|
+
".sw-alert-ring{box-shadow:0 0 0 3px #ef4444,0 0 12px rgba(239,68,68,0.4) !important;transition:box-shadow 0.3s ease}" \
|
|
25
|
+
".sw-warn-ring{box-shadow:0 0 0 3px #eab308,0 0 12px rgba(234,179,8,0.3) !important;transition:box-shadow 0.3s ease}" \
|
|
26
|
+
"</style>"
|
|
27
|
+
|
|
28
|
+
SKIP_SSL_VERIFY = ENV.fetch("SSL_VERIFY", "0") == "0"
|
|
29
|
+
|
|
30
|
+
def check_endpoint(ep)
|
|
31
|
+
uri = URI(ep[:url])
|
|
32
|
+
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
33
|
+
|
|
34
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
35
|
+
http.use_ssl = uri.scheme == "https"
|
|
36
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE if SKIP_SSL_VERIFY && http.use_ssl?
|
|
37
|
+
http.open_timeout = 5
|
|
38
|
+
http.read_timeout = 10
|
|
39
|
+
http.get(uri.request_uri)
|
|
40
|
+
|
|
41
|
+
elapsed_ms = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - start) * 1000).round
|
|
42
|
+
{ status: elapsed_ms > ep[:sla_ms] ? :slow : :up, ms: elapsed_ms }
|
|
43
|
+
rescue StandardError
|
|
44
|
+
{ status: :down, ms: nil }
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def format_timing(result)
|
|
48
|
+
result[:ms] ? "#{result[:ms]}ms" : "timeout"
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
App = app "Endpoint Health Checker", theme: :dark, layout: :wide do
|
|
52
|
+
app_header "Endpoint Health Checker", variant: :primary do
|
|
53
|
+
pulse_indicator color: :green, label: "Monitoring"
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
grid columns: ENDPOINTS.size, gap: :lg do
|
|
57
|
+
ENDPOINTS.each do |ep|
|
|
58
|
+
expandable_card key: ep[:key], title: ep[:name], subtitle: ep[:url],
|
|
59
|
+
status: :gray, badge_text: "\u2014", initially_expanded: true do
|
|
60
|
+
div id: "health-#{ep[:key]}" do
|
|
61
|
+
stat_display value: "\u2014", label: "RESPONSE TIME", color: :blue
|
|
62
|
+
text "SLA: #{ep[:sla_ms]}ms"
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
div id: "ring-css", style: "display:none"
|
|
69
|
+
|
|
70
|
+
header2 "Status Log"
|
|
71
|
+
div id: "status-log" do
|
|
72
|
+
text "Waiting for first health check..."
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
prev_status = {}
|
|
76
|
+
|
|
77
|
+
every(5) do |streamer|
|
|
78
|
+
streamer.replace("#ring-css", RING_CSS)
|
|
79
|
+
|
|
80
|
+
ENDPOINTS.each do |ep|
|
|
81
|
+
key = ep[:key]
|
|
82
|
+
result = check_endpoint(ep)
|
|
83
|
+
status = result[:status]
|
|
84
|
+
|
|
85
|
+
streamer.replace("#health-#{key}") do
|
|
86
|
+
div id: "health-#{key}" do
|
|
87
|
+
stat_display value: format_timing(result), label: "RESPONSE TIME", color: STATUS_COLOR[status]
|
|
88
|
+
status_dot status: STATUS_COLOR[status], pulse: status != :up
|
|
89
|
+
text "SLA: #{ep[:sla_ms]}ms"
|
|
90
|
+
badge STATUS_BADGE[status][:text], variant: STATUS_BADGE[status][:variant]
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
card_id = "#card-#{key}"
|
|
95
|
+
RING_CLASS.each_value { |cls| streamer.remove_class(card_id, cls) }
|
|
96
|
+
streamer.add_class(card_id, RING_CLASS[status]) if RING_CLASS[status]
|
|
97
|
+
|
|
98
|
+
old = prev_status[key]
|
|
99
|
+
if old && old != status
|
|
100
|
+
streamer.prepend("#status-log") do
|
|
101
|
+
div style: "padding:4px 8px;border-bottom:1px solid rgba(255,255,255,0.1)" do
|
|
102
|
+
text "#{STATUS_EMOJI[status]} #{ep[:name]} \u2192 #{status.to_s.upcase} (#{format_timing(result)}) at #{Time.now.strftime('%H:%M:%S')}"
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
prev_status[key] = status
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
App.run! if __FILE__ == $0
|