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,334 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# backtick_javascript: true
|
|
3
|
+
|
|
4
|
+
require "cgi"
|
|
5
|
+
require_relative "static"
|
|
6
|
+
require_relative "../opal/env"
|
|
7
|
+
|
|
8
|
+
module StreamWeaver
|
|
9
|
+
module Adapter
|
|
10
|
+
class Opal < Base
|
|
11
|
+
# Document renderers with no framework behavior, shared with the AlpineJS
|
|
12
|
+
# adapter. See adapter/static.rb.
|
|
13
|
+
include Static
|
|
14
|
+
# Not in Base — defined fresh here
|
|
15
|
+
def render_header(view, content, level, _state, options = {})
|
|
16
|
+
level = [[level.to_i, 1].max, 6].min
|
|
17
|
+
attrs = {}
|
|
18
|
+
attrs[:class] = options[:class] if options[:class]
|
|
19
|
+
attrs[:style] = options[:style] if options[:style]
|
|
20
|
+
view.send(:"h#{level}", **attrs) { view.plain(content.to_s) }
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Not in Base — defined fresh here
|
|
24
|
+
def render_div(view, component, state)
|
|
25
|
+
opts = component.respond_to?(:html_options) ? component.html_options : {}
|
|
26
|
+
view.div(**opts) do
|
|
27
|
+
Array(component.children).each { |c| c.render(view, state) }
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Not in Base — defined fresh here
|
|
32
|
+
def render_markdown(view, content, _state, options = {})
|
|
33
|
+
css_classes = ["sw-markdown"]
|
|
34
|
+
css_classes << options[:class] if options[:class]
|
|
35
|
+
attrs = { class: css_classes.join(" ") }
|
|
36
|
+
attrs[:style] = options[:style] if options[:style]
|
|
37
|
+
view.div(**attrs) { view.raw(md_to_html(content.to_s)) }
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Overrides Base
|
|
41
|
+
def render_text_field(view, key, options, state)
|
|
42
|
+
label_text = options[:label]
|
|
43
|
+
input_type = (options[:type] || :text).to_s
|
|
44
|
+
if label_text
|
|
45
|
+
view.div(style: "margin-bottom:8px") do
|
|
46
|
+
view.label(style: "display:block;font-weight:500;margin-bottom:4px") { view.plain(label_text) }
|
|
47
|
+
view.input(
|
|
48
|
+
type: input_type, name: key.to_s, value: state[key] || "",
|
|
49
|
+
placeholder: options[:placeholder] || "", data_sw_update: key.to_s
|
|
50
|
+
)
|
|
51
|
+
end
|
|
52
|
+
else
|
|
53
|
+
view.input(
|
|
54
|
+
type: input_type, name: key.to_s, value: state[key] || "",
|
|
55
|
+
placeholder: options[:placeholder] || "", data_sw_update: key.to_s
|
|
56
|
+
)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Overrides Base
|
|
61
|
+
def render_date_field(view, key, options, state)
|
|
62
|
+
bounds = { min: options[:min], max: options[:max] }.compact
|
|
63
|
+
attrs = { type: "date", name: key.to_s, value: state[key] || "", data_sw_update: key.to_s, **bounds }
|
|
64
|
+
|
|
65
|
+
if options[:label]
|
|
66
|
+
view.div(style: "margin-bottom:8px") do
|
|
67
|
+
view.label(style: "display:block;font-weight:500;margin-bottom:4px") { view.plain(options[:label]) }
|
|
68
|
+
view.input(**attrs)
|
|
69
|
+
end
|
|
70
|
+
else
|
|
71
|
+
view.input(**attrs)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Overrides Base
|
|
76
|
+
def render_checkbox(view, key, label, _options, state)
|
|
77
|
+
view.label do
|
|
78
|
+
view.input(type: "checkbox", name: key.to_s,
|
|
79
|
+
data_sw_toggle: key.to_s, checked: state[key] ? true : false)
|
|
80
|
+
view.plain(" #{label}")
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Overrides Base — 5 args: (view, button_id, label, options, modal_context)
|
|
85
|
+
def render_button(view, button_id, label, _options, _modal_context = nil)
|
|
86
|
+
view.button(data_sw_invoke: button_id) { view.plain(label.to_s) }
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Overrides Base — morphdom.js comes from OpalShell, nothing to emit here
|
|
90
|
+
def render_cdn_scripts(_view)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def render_badge(view, component, _state)
|
|
94
|
+
view.span(class: "sw-badge sw-badge-#{component.variant} sw-badge-#{component.size}") { view.plain(component.text) }
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Not in Base — defined fresh here
|
|
98
|
+
def render_tabs(view, component, state)
|
|
99
|
+
active_index = state[component.key] || 0
|
|
100
|
+
view.div(class: "sw-tabs sw-tabs-#{component.variant || 'line'}") do
|
|
101
|
+
view.div(class: "sw-tabs-list") do
|
|
102
|
+
component.children.each_with_index do |tab, index|
|
|
103
|
+
tab_classes = index == active_index ? "sw-tab-trigger sw-tab-active" : "sw-tab-trigger"
|
|
104
|
+
view.button(
|
|
105
|
+
class: tab_classes,
|
|
106
|
+
data_sw_invoke: "#{component.key}_tab_#{index}"
|
|
107
|
+
) { view.plain(tab.label) }
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
view.div(class: "sw-tab-panel") do
|
|
111
|
+
active_tab = component.children[active_index]
|
|
112
|
+
Array(active_tab&.children).each { |c| c.render(view, state) }
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Not in Base — defined fresh here
|
|
118
|
+
def render_table(view, headers, rows, options, state)
|
|
119
|
+
key = options[:key]
|
|
120
|
+
sort_col = state["#{key}_sort_col"]
|
|
121
|
+
sort_dir = (state["#{key}_sort_dir"] || :asc).to_sym
|
|
122
|
+
|
|
123
|
+
if options[:sortable] && sort_col
|
|
124
|
+
rows = rows.sort_by { |row| row[sort_col].to_s }
|
|
125
|
+
rows = rows.reverse if sort_dir == :desc
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
table_classes = ["sw-table"]
|
|
129
|
+
table_classes << "sw-table-bordered" if options[:bordered]
|
|
130
|
+
|
|
131
|
+
wrapper_attrs = {}
|
|
132
|
+
if options[:scrollable]
|
|
133
|
+
wrapper_attrs[:class] = "sw-table--scrollable"
|
|
134
|
+
wrapper_attrs[:style] = "max-height:400px;overflow:auto;"
|
|
135
|
+
elsif options[:sticky_header]
|
|
136
|
+
wrapper_attrs[:style] = "max-height:400px;overflow-y:auto;"
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
th_style = "padding:0.75rem 1rem;text-align:left;border-bottom:2px solid var(--sw-color-border,#e0e0e0);font-weight:600;"
|
|
140
|
+
td_style = "padding:0.75rem 1rem;border-bottom:1px solid var(--sw-color-border,#e0e0e0);"
|
|
141
|
+
|
|
142
|
+
view.div(**wrapper_attrs) do
|
|
143
|
+
view.table(class: table_classes.join(" ")) do
|
|
144
|
+
view.thead do
|
|
145
|
+
view.tr do
|
|
146
|
+
headers.each_with_index do |header, index|
|
|
147
|
+
if options[:sortable]
|
|
148
|
+
indicator = sort_col == index ? (sort_dir == :asc ? " ↑" : " ↓") : ""
|
|
149
|
+
view.th(style: th_style + "cursor:pointer;user-select:none;") do
|
|
150
|
+
view.button(data_sw_invoke: "#{key}_sort_#{index}") do
|
|
151
|
+
view.plain("#{header}#{indicator}")
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
else
|
|
155
|
+
view.th(style: th_style) { view.plain(header.to_s) }
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
view.tbody do
|
|
161
|
+
rows.each_with_index do |row, idx|
|
|
162
|
+
row_classes = []
|
|
163
|
+
row_classes << "sw-row-striped" if options[:striped] && idx.odd?
|
|
164
|
+
row_classes << "sw-row-hoverable" if options[:hoverable]
|
|
165
|
+
tr_attrs = row_classes.any? ? { class: row_classes.join(" ") } : {}
|
|
166
|
+
view.tr(**tr_attrs) do
|
|
167
|
+
Array(row).each_with_index do |cell, col_idx|
|
|
168
|
+
custom_style = options[:cell_styles]&.dig(idx, col_idx)
|
|
169
|
+
style = custom_style && !custom_style.to_s.empty? ? "#{td_style} #{custom_style}" : td_style
|
|
170
|
+
view.td(style: style) { view.plain(cell.to_s) }
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
# Not in Base — defined fresh here
|
|
180
|
+
def render_columns(view, widths, children, _options, state)
|
|
181
|
+
view.div(class: "sw-columns", style: "display:flex;gap:var(--sw-spacing-lg,1rem);") do
|
|
182
|
+
children.each_with_index do |column, index|
|
|
183
|
+
column.width = widths&.[](index)
|
|
184
|
+
column.render(view, state)
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
# Not in Base — defined fresh here
|
|
190
|
+
def render_column(view, width, children, _options, state)
|
|
191
|
+
# "fr" is a CSS Grid unit, not a valid flex-basis -- using it there makes the
|
|
192
|
+
# whole `flex` shorthand invalid and silently falls back to shrink-to-content.
|
|
193
|
+
# Translate the fraction to flex-grow instead. See alpinejs.rb#render_column.
|
|
194
|
+
fr_match = width && width.match(/\A(\d+(?:\.\d+)?)fr\z/)
|
|
195
|
+
style = if width.nil?
|
|
196
|
+
"flex:1 1 0;min-width:0;"
|
|
197
|
+
elsif fr_match
|
|
198
|
+
"flex:#{fr_match[1]} 1 0%;min-width:0;"
|
|
199
|
+
else
|
|
200
|
+
# Fixed length -- pin the width, don't grow. See alpinejs.rb#render_column.
|
|
201
|
+
"flex:0 1 #{width};min-width:0;"
|
|
202
|
+
end
|
|
203
|
+
view.div(class: "sw-column", style: style) do
|
|
204
|
+
children.each { |child| child.render(view, state) }
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
# Not in Base — defined fresh here
|
|
209
|
+
def render_theme_preset(view, component, state)
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
# Not in Base — defined fresh here
|
|
213
|
+
def render_theme_toggle(view, component, state)
|
|
214
|
+
view.button(data_sw_action: "toggle-theme") { view.plain("🌓") }
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
# Not in Base — defined fresh here
|
|
218
|
+
def render_theme_switcher(view, component, state)
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
# Every component stylesheet this adapter has been asked to emit, keyed by
|
|
222
|
+
# component so re-renders and repeated instances contribute exactly once.
|
|
223
|
+
#
|
|
224
|
+
# The DOM path writes these into <head> and forgets them; the DOM-free
|
|
225
|
+
# path has no <head> to write to, so the adapter is where the CSS lives
|
|
226
|
+
# and OpalRuntime#collected_css reads it back out after a render. Values
|
|
227
|
+
# are already @layer-wrapped, i.e. ready to drop into a <style> tag.
|
|
228
|
+
def collected_css
|
|
229
|
+
@collected_css ||= {}
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
# The collected stylesheets as one blob, in first-seen order.
|
|
233
|
+
def collected_css_text
|
|
234
|
+
collected_css.values.join("\n")
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
# Satisfies the Adapter::Static contract.
|
|
238
|
+
#
|
|
239
|
+
# Server-side this writes a <style> tag into the rendered fragment. In the
|
|
240
|
+
# browser that would re-emit on every re-render, since each render pass
|
|
241
|
+
# builds fresh renderers and replaces the region's innerHTML. So instead
|
|
242
|
+
# we append to <head> keyed by component, which is idempotent and survives
|
|
243
|
+
# re-renders untouched.
|
|
244
|
+
#
|
|
245
|
+
# Collection happens unconditionally, before the DOM write: it is the only
|
|
246
|
+
# channel available when there is no <head> (Node, render-to-string), and
|
|
247
|
+
# it is what makes this hook observable under MRI.
|
|
248
|
+
def inject_component_css(view, key, css)
|
|
249
|
+
return if css.nil? || css.strip.empty?
|
|
250
|
+
|
|
251
|
+
wrapped = StreamWeaver::CSS.layer_wrap(css)
|
|
252
|
+
collected_css[key.to_sym] ||= wrapped
|
|
253
|
+
|
|
254
|
+
return unless StreamWeaver::Opal::Env.dom?
|
|
255
|
+
|
|
256
|
+
# :nocov:
|
|
257
|
+
%x{
|
|
258
|
+
var id = "sw-css-" + #{key.to_s};
|
|
259
|
+
if (!document.getElementById(id)) {
|
|
260
|
+
var el = document.createElement("style");
|
|
261
|
+
el.id = id;
|
|
262
|
+
el.textContent = #{wrapped};
|
|
263
|
+
document.head.appendChild(el);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
# :nocov:
|
|
267
|
+
nil
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
# #render_mermaid now lives in Static, shared with AlpineJS
|
|
271
|
+
# (stream_weaver-mermaid-extension) -- it used to be adapter-specific
|
|
272
|
+
# here on the theory that mermaid was behavior (Alpine's x-data/
|
|
273
|
+
# x-init), but sw-mermaid-zoom.js self-inits on DOMContentLoaded with
|
|
274
|
+
# no Alpine dependency at all, so the markup itself was never
|
|
275
|
+
# adapter-specific. What stayed here is only the JS gap below.
|
|
276
|
+
|
|
277
|
+
# The remaining Adapter::Static seams. All four are asset/behavior
|
|
278
|
+
# concerns that a rendered document does not need:
|
|
279
|
+
#
|
|
280
|
+
# - scroll-spy JS is bundled by the host (bin/build_extension), not
|
|
281
|
+
# inlined per render -- a host with no <base target> override (e.g.
|
|
282
|
+
# opal-build's standalone HTML) still gets working, unhighlighted
|
|
283
|
+
# anchors without it; a host that does (the browser extension's
|
|
284
|
+
# sandbox, for outbound-doc-link safety) needs its own fragment-link
|
|
285
|
+
# click handler regardless, since that override affects every
|
|
286
|
+
# `#anchor` link on the page, not just sidebar_toc's
|
|
287
|
+
# - Prism cannot be pulled from a CDN here (a browser-extension host
|
|
288
|
+
# forbids remote script), so highlighting waits until it is bundled
|
|
289
|
+
# - copy-to-clipboard is Alpine-driven behavior, not document structure
|
|
290
|
+
# - sw-mermaid-zoom.js is bundled by the host the same way
|
|
291
|
+
# sw-sidebar-toc.js is, not inlined per render -- Opal-compiled code
|
|
292
|
+
# runs in the browser, where File.read (how AlpineJS inlines it) does
|
|
293
|
+
# not exist. Only the CSS is injectable here. Unlike the other three,
|
|
294
|
+
# this one is not optional: mermaid's source lives in a data
|
|
295
|
+
# attribute the shared markup writes, not visible text, so a host
|
|
296
|
+
# that skips bundling the engine gets an empty box per diagram, not
|
|
297
|
+
# a degraded-but-working render. Both real Opal hosts bundle it
|
|
298
|
+
# unconditionally (bin/build_extension, OpalBuilder#copy_browser_
|
|
299
|
+
# assets) -- this hook staying CSS-only is about *where* the JS gets
|
|
300
|
+
# loaded from, not whether it does.
|
|
301
|
+
#
|
|
302
|
+
# Each is a deliberate no-op (or partial no-op) rather than a missing
|
|
303
|
+
# method, so the document renders instead of raising NotImplementedError.
|
|
304
|
+
def inject_sidebar_toc_assets(view)
|
|
305
|
+
inject_component_css(view, :sidebar_toc, sidebar_toc_css)
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
def inject_code_highlighting(view)
|
|
309
|
+
nil
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
def render_code_block_copy_button(view, component)
|
|
313
|
+
nil
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
def inject_mermaid_assets(view)
|
|
317
|
+
inject_component_css(view, :mermaid, mermaid_css)
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
private
|
|
321
|
+
|
|
322
|
+
if RUBY_ENGINE == "opal"
|
|
323
|
+
def md_to_html(text)
|
|
324
|
+
%x{ return marked.parse(#{text}) }
|
|
325
|
+
end
|
|
326
|
+
else
|
|
327
|
+
def md_to_html(text)
|
|
328
|
+
require "kramdown"
|
|
329
|
+
Kramdown::Document.new(text, input: "GFM", hard_wrap: false).to_html
|
|
330
|
+
end
|
|
331
|
+
end
|
|
332
|
+
end
|
|
333
|
+
end
|
|
334
|
+
end
|