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,124 @@
|
|
|
1
|
+
# Form Blocks with Deferred Submission
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
Add a `form` block to StreamWeaver that groups multiple form elements together, uses client-side only state until submission, and sends all values in a single HTMX POST on submit.
|
|
6
|
+
|
|
7
|
+
## Problem
|
|
8
|
+
|
|
9
|
+
Currently, all form widgets trigger immediate HTMX requests on change, syncing state with the server on every keystroke/selection. This causes issues when:
|
|
10
|
+
1. Multiple fields need to be edited before saving
|
|
11
|
+
2. Navigation state gets reset during re-renders
|
|
12
|
+
3. Unnecessary server round-trips for uncommitted changes
|
|
13
|
+
|
|
14
|
+
## API
|
|
15
|
+
|
|
16
|
+
```ruby
|
|
17
|
+
form :edit_person do
|
|
18
|
+
text_field :name, placeholder: 'Name'
|
|
19
|
+
select :status, %w[active paused archived]
|
|
20
|
+
text_area :notes, placeholder: 'Notes...', rows: 3
|
|
21
|
+
|
|
22
|
+
submit 'Save' do |form_values|
|
|
23
|
+
# form_values = { name: "...", status: "...", notes: "..." }
|
|
24
|
+
# state[:edit_person] already updated at this point
|
|
25
|
+
api.save_person(form_values) # optional side effects
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
cancel 'Cancel' # resets to original values, no server request
|
|
29
|
+
end
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Design Decisions
|
|
33
|
+
|
|
34
|
+
### State Namespace: Nested Hash
|
|
35
|
+
Form values stored as `state[:edit_person][:name]` - nested hash structure matching Rails conventions (strong params, form_for).
|
|
36
|
+
|
|
37
|
+
### Initial Values: From State
|
|
38
|
+
Form reads initial values from `state[:form_name]`. App code is responsible for populating state before rendering (like Rails controller setting `@person = Person.find(id)`).
|
|
39
|
+
|
|
40
|
+
### Submit Behavior: Auto-Update State
|
|
41
|
+
On submit, state is automatically updated with form values, then the submit block runs. This keeps UI consistent - other components using `state[:edit_person][:name]` show current values.
|
|
42
|
+
|
|
43
|
+
### Context Flag for Widgets
|
|
44
|
+
Same DSL methods (`text_field`, `select`, etc.) work inside and outside forms. A context flag tells the adapter to render differently (no HTMX, form-scoped x-model).
|
|
45
|
+
|
|
46
|
+
## Client-Side Behavior
|
|
47
|
+
|
|
48
|
+
Form block wraps contents in Alpine.js scope with local state:
|
|
49
|
+
|
|
50
|
+
```html
|
|
51
|
+
<div x-data="{
|
|
52
|
+
_form: { name: 'Alice', status: 'active', notes: '' },
|
|
53
|
+
_original: { name: 'Alice', status: 'active', notes: '' }
|
|
54
|
+
}">
|
|
55
|
+
<input type="text" x-model="_form.name" name="edit_person[name]">
|
|
56
|
+
<select x-model="_form.status" name="edit_person[status]">...</select>
|
|
57
|
+
|
|
58
|
+
<button hx-post="/form/edit_person" hx-include="[name^='edit_person']">Save</button>
|
|
59
|
+
<button type="button" @click="_form = JSON.parse(JSON.stringify(_original))">Cancel</button>
|
|
60
|
+
</div>
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Widget Rendering Differences
|
|
64
|
+
|
|
65
|
+
| Aspect | Standalone widget | Widget inside form |
|
|
66
|
+
|--------|-------------------|-------------------|
|
|
67
|
+
| `x-model` | `x-model="name"` (root scope) | `x-model="_form.name"` (form-local) |
|
|
68
|
+
| `hx-post` | On every change (`/update`) | None |
|
|
69
|
+
| `hx-trigger` | `keyup changed delay:500ms` | None |
|
|
70
|
+
| `name` attr | `name="name"` | `name="form_name[field]"` (Rails nested) |
|
|
71
|
+
|
|
72
|
+
### Cancel Button
|
|
73
|
+
Pure Alpine - resets `_form` to `_original` snapshot, no server request.
|
|
74
|
+
|
|
75
|
+
### Submit Button
|
|
76
|
+
Single HTMX POST to `/form/:form_name` with all form values.
|
|
77
|
+
|
|
78
|
+
## Server-Side Handling
|
|
79
|
+
|
|
80
|
+
New endpoint `POST /form/:form_name`:
|
|
81
|
+
|
|
82
|
+
1. Parse Rails-style nested params: `edit_person[name]` -> `{ name: "..." }`
|
|
83
|
+
2. Auto-update state: `state[:edit_person] = form_values`
|
|
84
|
+
3. Execute submit block if defined
|
|
85
|
+
4. Re-render page with updated state
|
|
86
|
+
|
|
87
|
+
## Implementation Files
|
|
88
|
+
|
|
89
|
+
| File | Changes |
|
|
90
|
+
|------|---------|
|
|
91
|
+
| `lib/stream_weaver/components.rb` | Add `Form` component class |
|
|
92
|
+
| `lib/stream_weaver/app.rb` | Add `form`, `submit`, `cancel` DSL methods; add `@form_context` flag |
|
|
93
|
+
| `lib/stream_weaver/adapter/alpinejs.rb` | Add `render_form`, modify widget renderers to check form context |
|
|
94
|
+
| `lib/stream_weaver/server.rb` | Add `POST /form/:form_name` endpoint |
|
|
95
|
+
| `docs/for_llms.md` | Document form block usage and auto-update behavior |
|
|
96
|
+
|
|
97
|
+
## Form Component Structure
|
|
98
|
+
|
|
99
|
+
```ruby
|
|
100
|
+
class Form < Base
|
|
101
|
+
attr_reader :name, :submit_label, :cancel_label, :submit_action
|
|
102
|
+
attr_accessor :children
|
|
103
|
+
|
|
104
|
+
def initialize(name, **options)
|
|
105
|
+
@name = name
|
|
106
|
+
@children = []
|
|
107
|
+
@submit_label = nil
|
|
108
|
+
@cancel_label = nil
|
|
109
|
+
@submit_action = nil
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def execute_submit(state, form_values)
|
|
113
|
+
@submit_action&.call(form_values)
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Documentation Notes
|
|
119
|
+
|
|
120
|
+
For `docs/for_llms.md`, emphasize:
|
|
121
|
+
- Form values auto-update to state on submit (state[:form_name] = form_values)
|
|
122
|
+
- Submit block is for side effects (API calls), not state management
|
|
123
|
+
- Cancel resets to original values without server request
|
|
124
|
+
- Same DSL methods work inside/outside forms - context determines behavior
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
# Examples Browser Design
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
An interactive Examples Browser app that showcases StreamWeaver examples, built with StreamWeaver itself. Features directory navigation, syntax-highlighted code display, and ability to run examples.
|
|
6
|
+
|
|
7
|
+
## Goals
|
|
8
|
+
|
|
9
|
+
1. **Phase 1 (MVP):** Browse and run examples with read-only code display
|
|
10
|
+
2. **Phase 2:** Edit code and run modified versions
|
|
11
|
+
|
|
12
|
+
## Architecture
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
┌─────────────────────────────────────────────────────────┐
|
|
16
|
+
│ Examples Browser │
|
|
17
|
+
├──────────────┬──────────────────────────────────────────┤
|
|
18
|
+
│ Sidebar │ Main Area │
|
|
19
|
+
│ │ ┌────────────────────────────────────┐ │
|
|
20
|
+
│ 📁 basic/ │ │ Code Panel (CodeMirror 5) │ │
|
|
21
|
+
│ hello_wo │ │ ┌──────────────────────────────┐ │ │
|
|
22
|
+
│ todo_lis │ │ │ require 'stream_weaver' │ │ │
|
|
23
|
+
│ 📁 agentic/ │ │ │ │ │ │
|
|
24
|
+
│ ... │ │ │ app "Hello" do │ │ │
|
|
25
|
+
│ │ │ │ text_field :name │ │ │
|
|
26
|
+
│ │ │ └──────────────────────────────┘ │ │
|
|
27
|
+
│ │ │ [▶ Run] [✓ Check Syntax] │ │
|
|
28
|
+
│ │ └────────────────────────────────────┘ │
|
|
29
|
+
└──────────────┴──────────────────────────────────────────┘
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## State Structure
|
|
33
|
+
|
|
34
|
+
```ruby
|
|
35
|
+
state[:selected_dir] # Currently expanded directory
|
|
36
|
+
state[:selected_file] # Full path to selected file
|
|
37
|
+
state[:code_content] # File content (or edited content in phase 2)
|
|
38
|
+
state[:error_message] # Syntax error from ruby -c
|
|
39
|
+
state[:error_modal_open] # Show/hide error modal
|
|
40
|
+
state[:running_pid] # Track spawned process for cleanup
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## New Component: `code_editor`
|
|
44
|
+
|
|
45
|
+
Added to core StreamWeaver for reusability.
|
|
46
|
+
|
|
47
|
+
### Usage
|
|
48
|
+
|
|
49
|
+
```ruby
|
|
50
|
+
# Read-only (phase 1)
|
|
51
|
+
code_editor :code_content, language: :ruby, readonly: true, height: "500px"
|
|
52
|
+
|
|
53
|
+
# Editable (phase 2)
|
|
54
|
+
code_editor :code_content, language: :ruby, readonly: false
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Implementation Details
|
|
58
|
+
|
|
59
|
+
- Renders `<div>` with `hx-preserve="true"` to survive HTMX swaps
|
|
60
|
+
- Contains hidden `<textarea>` bound to state key
|
|
61
|
+
- Emits initialization JS for CodeMirror instance
|
|
62
|
+
- Single instance pattern: `window.cmEditor` persists across navigation
|
|
63
|
+
|
|
64
|
+
### CDN Dependencies (CodeMirror 5)
|
|
65
|
+
|
|
66
|
+
```ruby
|
|
67
|
+
stylesheets: [
|
|
68
|
+
"https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/codemirror.min.css"
|
|
69
|
+
]
|
|
70
|
+
scripts: [
|
|
71
|
+
"https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/codemirror.min.js",
|
|
72
|
+
"https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/mode/ruby/ruby.min.js"
|
|
73
|
+
]
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### HTMX Integration Strategy
|
|
77
|
+
|
|
78
|
+
**Challenge:** CodeMirror creates complex DOM that HTMX can orphan/duplicate.
|
|
79
|
+
|
|
80
|
+
**Solution:**
|
|
81
|
+
1. Use `hx-preserve="true"` on editor container
|
|
82
|
+
2. Update content via JS: `window.cmEditor.setValue(newContent)`
|
|
83
|
+
3. Initialize once on DOMContentLoaded, not on HTMX swaps
|
|
84
|
+
|
|
85
|
+
## Interactions
|
|
86
|
+
|
|
87
|
+
### Run Flow
|
|
88
|
+
|
|
89
|
+
1. User clicks "Run"
|
|
90
|
+
2. System runs `ruby -c` on content for syntax check
|
|
91
|
+
3. If error: Parse message, show error modal
|
|
92
|
+
4. If OK: Write temp file to `/tmp/sw_example_*.rb`
|
|
93
|
+
5. Spawn process: `ruby /tmp/sw_example_*.rb`
|
|
94
|
+
6. Track PID for cleanup
|
|
95
|
+
7. Show success toast
|
|
96
|
+
|
|
97
|
+
### Error Modal
|
|
98
|
+
|
|
99
|
+
```
|
|
100
|
+
┌─────────────────────────────────────┐
|
|
101
|
+
│ ❌ Syntax Error │
|
|
102
|
+
├─────────────────────────────────────┤
|
|
103
|
+
│ Line 5: syntax error, unexpected │
|
|
104
|
+
│ end-of-input, expecting `end' │
|
|
105
|
+
│ [Close] │
|
|
106
|
+
└─────────────────────────────────────┘
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Cleanup
|
|
110
|
+
|
|
111
|
+
`at_exit` hook kills spawned PIDs (matches tutorial.rb pattern).
|
|
112
|
+
|
|
113
|
+
## File Structure
|
|
114
|
+
|
|
115
|
+
```
|
|
116
|
+
lib/stream_weaver/
|
|
117
|
+
├── components.rb # Add CodeEditor class
|
|
118
|
+
├── adapter/
|
|
119
|
+
│ └── alpinejs.rb # Add render_code_editor method
|
|
120
|
+
└── views.rb # Add CodeMirror CSS
|
|
121
|
+
|
|
122
|
+
examples/
|
|
123
|
+
└── advanced/
|
|
124
|
+
└── examples_browser.rb
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Implementation Plan
|
|
128
|
+
|
|
129
|
+
### Phase 1: Core Component + MVP Browser
|
|
130
|
+
|
|
131
|
+
1. Add `CodeEditor` component to `components.rb`
|
|
132
|
+
2. Add `render_code_editor` to AlpineJS adapter
|
|
133
|
+
3. Add CodeMirror initialization JS
|
|
134
|
+
4. Build `examples_browser.rb`:
|
|
135
|
+
- Directory discovery
|
|
136
|
+
- Sidebar navigation
|
|
137
|
+
- Code display (read-only)
|
|
138
|
+
- Run button with syntax check
|
|
139
|
+
- Error modal
|
|
140
|
+
|
|
141
|
+
### Phase 2: Editing Support
|
|
142
|
+
|
|
143
|
+
1. Enable `readonly: false` mode
|
|
144
|
+
2. Sync edited content back to state
|
|
145
|
+
3. Track "modified" state
|
|
146
|
+
4. "Run Modified" writes temp file
|
|
147
|
+
5. "Reset" button restores original
|
|
148
|
+
|
|
149
|
+
## Inspiration
|
|
150
|
+
|
|
151
|
+
- [Glimmer meta_example.rb](https://github.com/AndyObtiva/glimmer-dsl-libui/blob/master/examples/meta_example.rb) - Example discovery, categorization, launch pattern
|
|
152
|
+
- StreamWeaver tutorial.rb - Process spawning, PID tracking, temp file approach
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
# StreamWeaver Charts Design
|
|
2
|
+
|
|
3
|
+
**Date**: 2026-01-02
|
|
4
|
+
**Status**: Approved
|
|
5
|
+
**Context**: Spin-off from cultiv-os/cultiv-ai daily-brief timing instrumentation work
|
|
6
|
+
|
|
7
|
+
## Overview
|
|
8
|
+
|
|
9
|
+
Add chart visualization components to StreamWeaver, following Streamlit's data-first philosophy while maintaining StreamWeaver's minimal-dependency ethos.
|
|
10
|
+
|
|
11
|
+
### Goals
|
|
12
|
+
|
|
13
|
+
1. Composable chart primitives (not a pre-built dashboard)
|
|
14
|
+
2. External data pattern (charts read files, don't bloat session)
|
|
15
|
+
3. Chart.js via CDN, lazy-loaded only when charts are used
|
|
16
|
+
4. Path toward Streamlit-like API as Ruby DataFrame ecosystem matures
|
|
17
|
+
|
|
18
|
+
## Components
|
|
19
|
+
|
|
20
|
+
| Component | Purpose | Primary Use |
|
|
21
|
+
|-----------|---------|-------------|
|
|
22
|
+
| `bar_chart` | Horizontal/vertical bars | Phase breakdown, comparisons |
|
|
23
|
+
| `line_chart` | Trend lines | Time series, progress |
|
|
24
|
+
| `stacked_bar_chart` | Segmented bars | Distribution across runs |
|
|
25
|
+
| `sparkline` | Minimal line (no axes) | Compact trends |
|
|
26
|
+
| `hbar_chart` | Horizontal bar shorthand | Quick horizontal bars |
|
|
27
|
+
|
|
28
|
+
## Architecture
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
┌─────────────────────────────────────────────────────────┐
|
|
32
|
+
│ StreamWeaver App │
|
|
33
|
+
│ ┌─────────────────────────────────────────────────┐ │
|
|
34
|
+
│ │ bar_chart file: "timing.yaml", path: "..." │ │
|
|
35
|
+
│ └─────────────────────────────────────────────────┘ │
|
|
36
|
+
│ │ │
|
|
37
|
+
│ ▼ │
|
|
38
|
+
│ ┌─────────────────────────────────────────────────┐ │
|
|
39
|
+
│ │ Components::BarChart │ │
|
|
40
|
+
│ │ - Loads data (file or inline) │ │
|
|
41
|
+
│ │ - Normalizes to labels/values │ │
|
|
42
|
+
│ └─────────────────────────────────────────────────┘ │
|
|
43
|
+
│ │ │
|
|
44
|
+
│ ▼ │
|
|
45
|
+
│ ┌─────────────────────────────────────────────────┐ │
|
|
46
|
+
│ │ AlpineJS Adapter │ │
|
|
47
|
+
│ │ - Renders <canvas> with Chart.js config │ │
|
|
48
|
+
│ │ - Lazy-loads Chart.js CDN on first chart │ │
|
|
49
|
+
│ └─────────────────────────────────────────────────┘ │
|
|
50
|
+
└─────────────────────────────────────────────────────────┘
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## API Design
|
|
54
|
+
|
|
55
|
+
### Data Input Options (Priority Order)
|
|
56
|
+
|
|
57
|
+
```ruby
|
|
58
|
+
# 1. File with dot-path (recommended for external data)
|
|
59
|
+
bar_chart file: "~/metrics/timing.yaml", path: "entries.-1.phases"
|
|
60
|
+
|
|
61
|
+
# 2. File with block transform (complex extraction)
|
|
62
|
+
bar_chart file: "timing.yaml" do |data|
|
|
63
|
+
data[:entries].last[:phases]
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# 3. Inline hash (quick/simple)
|
|
67
|
+
bar_chart data: { calendar: 45, news: 120, tasks: 30 }
|
|
68
|
+
|
|
69
|
+
# 4. Explicit labels/values (full control)
|
|
70
|
+
bar_chart labels: %w[Calendar News Tasks],
|
|
71
|
+
values: [45, 120, 30]
|
|
72
|
+
|
|
73
|
+
# 5. State-bound (when data is already in state)
|
|
74
|
+
bar_chart data: :timing_phases # reads state[:timing_phases]
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Common Options (All Chart Types)
|
|
78
|
+
|
|
79
|
+
```ruby
|
|
80
|
+
bar_chart data: timing,
|
|
81
|
+
title: "Phase Breakdown", # Optional header
|
|
82
|
+
height: "300px", # CSS height (default: 250px)
|
|
83
|
+
horizontal: true, # Bar direction (default: false)
|
|
84
|
+
colors: ["#c45b28", "#4a90d9"], # Custom palette
|
|
85
|
+
show_legend: false, # Hide legend (default: auto)
|
|
86
|
+
show_values: true # Display values on bars
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Chart-Specific Options
|
|
90
|
+
|
|
91
|
+
```ruby
|
|
92
|
+
# Line chart
|
|
93
|
+
line_chart data: trends,
|
|
94
|
+
fill: true, # Area fill under line
|
|
95
|
+
smooth: true, # Curved vs straight lines
|
|
96
|
+
points: false # Hide data points
|
|
97
|
+
|
|
98
|
+
# Stacked bar
|
|
99
|
+
stacked_bar_chart data: runs,
|
|
100
|
+
stack: true, # true (stacked) or false (grouped)
|
|
101
|
+
normalize: false # Percentage mode (100% bars)
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### DSL Methods
|
|
105
|
+
|
|
106
|
+
```ruby
|
|
107
|
+
app "Dashboard" do
|
|
108
|
+
bar_chart data: { a: 1, b: 2 } # Vertical bars
|
|
109
|
+
hbar_chart data: { a: 1, b: 2 } # Horizontal bars (shorthand)
|
|
110
|
+
line_chart data: [1, 2, 3, 4] # Simple array = sequential x
|
|
111
|
+
sparkline data: [1, 2, 3, 4] # Minimal line (no axes/labels)
|
|
112
|
+
stacked_bar_chart data: complex_data
|
|
113
|
+
end
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Implementation Details
|
|
117
|
+
|
|
118
|
+
### Chart.js Integration (Lazy Loading)
|
|
119
|
+
|
|
120
|
+
```ruby
|
|
121
|
+
# In views.rb - only include when charts are present
|
|
122
|
+
def chart_js_assets
|
|
123
|
+
return unless @app.has_charts?
|
|
124
|
+
|
|
125
|
+
script(src: "https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js",
|
|
126
|
+
defer: true)
|
|
127
|
+
end
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Component Class Pattern
|
|
131
|
+
|
|
132
|
+
```ruby
|
|
133
|
+
class BarChart < Base
|
|
134
|
+
attr_reader :labels, :values, :options
|
|
135
|
+
|
|
136
|
+
def initialize(data: nil, file: nil, path: nil, labels: nil, values: nil, **options, &block)
|
|
137
|
+
@data = data
|
|
138
|
+
@file = file
|
|
139
|
+
@path = path
|
|
140
|
+
@labels = labels
|
|
141
|
+
@values = values
|
|
142
|
+
@transform_block = block
|
|
143
|
+
@options = options
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def resolve_data(state)
|
|
147
|
+
if @file
|
|
148
|
+
raw = load_file(@file)
|
|
149
|
+
raw = @transform_block ? @transform_block.call(raw) : extract_path(raw, @path)
|
|
150
|
+
normalize_hash(raw)
|
|
151
|
+
elsif @data.is_a?(Symbol)
|
|
152
|
+
normalize_hash(state[@data])
|
|
153
|
+
elsif @data.is_a?(Hash)
|
|
154
|
+
normalize_hash(@data)
|
|
155
|
+
else
|
|
156
|
+
{ labels: @labels, values: @values }
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def render(view, state)
|
|
161
|
+
view.adapter.render_bar_chart(view, self, state)
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### File Loading & Path Extraction
|
|
167
|
+
|
|
168
|
+
```ruby
|
|
169
|
+
# Supports YAML, JSON, expanding ~
|
|
170
|
+
def load_file(path)
|
|
171
|
+
expanded = File.expand_path(path)
|
|
172
|
+
case File.extname(expanded)
|
|
173
|
+
when '.yaml', '.yml' then YAML.safe_load_file(expanded, symbolize_names: true)
|
|
174
|
+
when '.json' then JSON.parse(File.read(expanded), symbolize_keys: true)
|
|
175
|
+
else raise "Unsupported file type: #{path}"
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
# Dot-path extraction: "entries.-1.phases"
|
|
180
|
+
def extract_path(data, path)
|
|
181
|
+
return data unless path
|
|
182
|
+
path.split('.').reduce(data) do |obj, key|
|
|
183
|
+
case key
|
|
184
|
+
when /^-?\d+$/ then obj[key.to_i] # Array index
|
|
185
|
+
else obj[key.to_sym] rescue obj[key] # Hash key
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
## Example: Timing Dashboard
|
|
192
|
+
|
|
193
|
+
```ruby
|
|
194
|
+
# examples/charts/timing_dashboard.rb
|
|
195
|
+
require 'stream_weaver'
|
|
196
|
+
|
|
197
|
+
TIMING_FILE = File.expand_path("~/cultiv-os/metrics/brief-timing.yaml")
|
|
198
|
+
|
|
199
|
+
app "Daily Brief Timing", layout: :wide do
|
|
200
|
+
header2 "Performance Dashboard"
|
|
201
|
+
|
|
202
|
+
# Latest run breakdown
|
|
203
|
+
card do
|
|
204
|
+
header3 "Latest Run"
|
|
205
|
+
hbar_chart file: TIMING_FILE,
|
|
206
|
+
path: "entries.-1.phases",
|
|
207
|
+
title: "Phase Breakdown (seconds)",
|
|
208
|
+
show_values: true,
|
|
209
|
+
colors: ["#c45b28"]
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
columns widths: ['50%', '50%'] do
|
|
213
|
+
column do
|
|
214
|
+
card do
|
|
215
|
+
header3 "Total Duration Trend"
|
|
216
|
+
line_chart file: TIMING_FILE do |data|
|
|
217
|
+
data[:entries].last(14).map { |e| e[:wall_clock_seconds] }
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
column do
|
|
223
|
+
card do
|
|
224
|
+
header3 "News-Wait Over Time"
|
|
225
|
+
sparkline file: TIMING_FILE do |data|
|
|
226
|
+
data[:entries].last(14).map { |e| e[:phases][:"news-wait"] }
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
# Historical comparison
|
|
233
|
+
card do
|
|
234
|
+
header3 "Last 7 Runs by Phase"
|
|
235
|
+
stacked_bar_chart file: TIMING_FILE do |data|
|
|
236
|
+
data[:entries].last(7).map do |entry|
|
|
237
|
+
{ label: entry[:date], **entry[:phases] }
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
end
|
|
241
|
+
end.run!
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
## Implementation Plan
|
|
245
|
+
|
|
246
|
+
| Phase | What | Why First |
|
|
247
|
+
|-------|------|-----------|
|
|
248
|
+
| **1** | `BarChart` component + adapter | Most useful, validates pattern |
|
|
249
|
+
| **2** | Chart.js CDN lazy-loading in views.rb | Required for any chart |
|
|
250
|
+
| **3** | File loading + dot-path extraction | Core data pattern |
|
|
251
|
+
| **4** | `LineChart` + `sparkline` | Second most useful |
|
|
252
|
+
| **5** | `StackedBarChart` | More complex data shape |
|
|
253
|
+
| **6** | Example dashboard | Proves composition works |
|
|
254
|
+
| **7** | Tests | Ensure stability |
|
|
255
|
+
|
|
256
|
+
## Files to Create/Modify
|
|
257
|
+
|
|
258
|
+
| File | Action |
|
|
259
|
+
|------|--------|
|
|
260
|
+
| `lib/stream_weaver/components.rb` | Add BarChart, LineChart, StackedBarChart classes |
|
|
261
|
+
| `lib/stream_weaver/adapter/alpinejs.rb` | Add render methods for each chart type |
|
|
262
|
+
| `lib/stream_weaver/views.rb` | Add conditional Chart.js CDN include |
|
|
263
|
+
| `lib/stream_weaver/app.rb` | Add DSL methods |
|
|
264
|
+
| `examples/charts/timing_dashboard.rb` | New example |
|
|
265
|
+
| `spec/components/charts_spec.rb` | New test file |
|
|
266
|
+
|
|
267
|
+
## Design Decisions
|
|
268
|
+
|
|
269
|
+
### Why Chart.js via CDN?
|
|
270
|
+
- Zero build step (fits StreamWeaver's philosophy)
|
|
271
|
+
- Apps without charts pay no cost
|
|
272
|
+
- Well-documented, handles edge cases (scaling, tooltips, animation)
|
|
273
|
+
|
|
274
|
+
### Why file-based data loading?
|
|
275
|
+
- Session storage has limits (~4KB cookies, memory for server-side)
|
|
276
|
+
- Timing data already lives in external YAML
|
|
277
|
+
- Pattern: session for UI state, external for data
|
|
278
|
+
|
|
279
|
+
### Why explicit API first?
|
|
280
|
+
- Ruby hashes lack Pandas-style metadata
|
|
281
|
+
- Can evolve toward Streamlit-like inference later
|
|
282
|
+
- Optional DataFrame support (ruby-polars, Rover) when gems present
|
|
283
|
+
|
|
284
|
+
## Future Considerations
|
|
285
|
+
|
|
286
|
+
- **DataFrame support**: Detect ruby-polars/Rover, enable richer data inference
|
|
287
|
+
- **Auto-refresh**: `refresh: 30` option to re-read files periodically
|
|
288
|
+
- **Dashboard creator UI**: Visual tool for composing charts (ultrathink later)
|