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,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* StreamWeaver SidebarToc Scroll Spy
|
|
3
|
+
*
|
|
4
|
+
* Uses IntersectionObserver to highlight the active section in the
|
|
5
|
+
* sidebar table-of-contents. Works with both desktop (sticky sidebar)
|
|
6
|
+
* and mobile (horizontal scrollable bar) layouts.
|
|
7
|
+
*/
|
|
8
|
+
(function() {
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
function initSidebarToc() {
|
|
12
|
+
var navEl = document.querySelector('.sw-sidebar-toc__nav');
|
|
13
|
+
// A real htmx swap replaces navEl outright, so this flag is naturally
|
|
14
|
+
// fresh there. A host that re-invokes init without a real swap (the
|
|
15
|
+
// browser extension's sandbox, which has no htmx at all) would
|
|
16
|
+
// otherwise attach a second click listener to every link and leak a
|
|
17
|
+
// second IntersectionObserver watching the first one's now-detached
|
|
18
|
+
// targets -- not hypothetical, this exact host now calls init directly.
|
|
19
|
+
if (!navEl || navEl.dataset.swTocReady) return;
|
|
20
|
+
navEl.dataset.swTocReady = 'true';
|
|
21
|
+
|
|
22
|
+
var links = navEl.querySelectorAll('.sw-sidebar-toc__link');
|
|
23
|
+
if (links.length === 0) return;
|
|
24
|
+
|
|
25
|
+
// Collect target section IDs
|
|
26
|
+
var sectionIds = [];
|
|
27
|
+
links.forEach(function(link) {
|
|
28
|
+
var id = link.getAttribute('data-sw-toc-target');
|
|
29
|
+
if (id) sectionIds.push(id);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// Track which sections are intersecting
|
|
33
|
+
var visibleSections = {};
|
|
34
|
+
|
|
35
|
+
// Create IntersectionObserver
|
|
36
|
+
var observer = new IntersectionObserver(function(entries) {
|
|
37
|
+
entries.forEach(function(entry) {
|
|
38
|
+
visibleSections[entry.target.id] = entry.isIntersecting;
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// Find the first visible section (in DOM order)
|
|
42
|
+
var activeId = null;
|
|
43
|
+
for (var i = 0; i < sectionIds.length; i++) {
|
|
44
|
+
if (visibleSections[sectionIds[i]]) {
|
|
45
|
+
activeId = sectionIds[i];
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (activeId) {
|
|
51
|
+
setActiveLink(activeId, links);
|
|
52
|
+
}
|
|
53
|
+
}, {
|
|
54
|
+
rootMargin: '-80px 0px -60% 0px',
|
|
55
|
+
threshold: 0
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// Observe each section target
|
|
59
|
+
sectionIds.forEach(function(id) {
|
|
60
|
+
var el = document.getElementById(id);
|
|
61
|
+
if (el) observer.observe(el);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// Smooth-scroll click handler
|
|
65
|
+
links.forEach(function(link) {
|
|
66
|
+
link.addEventListener('click', function(e) {
|
|
67
|
+
e.preventDefault();
|
|
68
|
+
var targetId = link.getAttribute('data-sw-toc-target');
|
|
69
|
+
var target = document.getElementById(targetId);
|
|
70
|
+
if (target) {
|
|
71
|
+
target.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
|
72
|
+
setActiveLink(targetId, links);
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// Activate the first link by default
|
|
78
|
+
if (links.length > 0 && sectionIds.length > 0) {
|
|
79
|
+
setActiveLink(sectionIds[0], links);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function setActiveLink(activeId, links) {
|
|
84
|
+
links.forEach(function(link) {
|
|
85
|
+
var isActive = link.getAttribute('data-sw-toc-target') === activeId;
|
|
86
|
+
if (isActive) {
|
|
87
|
+
link.classList.add('sw-is-active');
|
|
88
|
+
} else {
|
|
89
|
+
link.classList.remove('sw-is-active');
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// On mobile, scroll the active link into view in the horizontal bar
|
|
94
|
+
var activeLink = document.querySelector('.sw-sidebar-toc__link.sw-is-active');
|
|
95
|
+
if (activeLink) {
|
|
96
|
+
var nav = activeLink.closest('.sw-sidebar-toc__nav');
|
|
97
|
+
if (nav && nav.scrollWidth > nav.clientWidth) {
|
|
98
|
+
activeLink.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'center' });
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Hosts with no htmx at all (the browser extension's sandbox, a one-shot
|
|
104
|
+
// static render) call this directly instead of relying on the htmx
|
|
105
|
+
// listener below -- a named, greppable hook rather than that host faking
|
|
106
|
+
// an htmx event it never actually loads.
|
|
107
|
+
window.swInitSidebarToc = initSidebarToc;
|
|
108
|
+
|
|
109
|
+
// Initialize on DOMContentLoaded and after HTMX swaps
|
|
110
|
+
if (document.readyState === 'loading') {
|
|
111
|
+
document.addEventListener('DOMContentLoaded', initSidebarToc);
|
|
112
|
+
} else {
|
|
113
|
+
initSidebarToc();
|
|
114
|
+
}
|
|
115
|
+
document.addEventListener('htmx:afterSwap', initSidebarToc);
|
|
116
|
+
})();
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* StreamWeaver Slide Navigation (sw-slide-nav.js)
|
|
3
|
+
*
|
|
4
|
+
* Alpine.js data component for slide container navigation.
|
|
5
|
+
* Handles both :swap and :scroll_snap modes.
|
|
6
|
+
*
|
|
7
|
+
* Usage: x-data="swSlideNav(totalSlides, mode)"
|
|
8
|
+
*
|
|
9
|
+
* Registers keyboard shortcuts via swKeyboard (if available).
|
|
10
|
+
*/
|
|
11
|
+
(function() {
|
|
12
|
+
'use strict';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Alpine.js data factory for slide navigation.
|
|
16
|
+
* @param {number} total - Total number of slides
|
|
17
|
+
* @param {string} mode - 'swap' or 'scroll_snap'
|
|
18
|
+
* @param {boolean} keyboardNav - Whether to register keyboard shortcuts
|
|
19
|
+
* @returns {object} Alpine.js data object
|
|
20
|
+
*/
|
|
21
|
+
window.swSlideNav = function(total, mode, keyboardNav, initialSlide) {
|
|
22
|
+
return {
|
|
23
|
+
current: initialSlide || 0,
|
|
24
|
+
total: total,
|
|
25
|
+
mode: mode || 'swap',
|
|
26
|
+
|
|
27
|
+
init: function() {
|
|
28
|
+
if (keyboardNav !== false && typeof swKeyboard !== 'undefined') {
|
|
29
|
+
var self = this;
|
|
30
|
+
|
|
31
|
+
swKeyboard.register('arrowright', 'navigation', function() {
|
|
32
|
+
self.next();
|
|
33
|
+
});
|
|
34
|
+
swKeyboard.register('arrowleft', 'navigation', function() {
|
|
35
|
+
self.prev();
|
|
36
|
+
});
|
|
37
|
+
swKeyboard.register('arrowdown', 'navigation', function() {
|
|
38
|
+
self.next();
|
|
39
|
+
});
|
|
40
|
+
swKeyboard.register('arrowup', 'navigation', function() {
|
|
41
|
+
self.prev();
|
|
42
|
+
});
|
|
43
|
+
swKeyboard.register(' ', 'navigation', function() {
|
|
44
|
+
self.next();
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
next: function() {
|
|
50
|
+
if (this.current < this.total - 1) {
|
|
51
|
+
this.current++;
|
|
52
|
+
this._onNavigate();
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
|
|
56
|
+
prev: function() {
|
|
57
|
+
if (this.current > 0) {
|
|
58
|
+
this.current--;
|
|
59
|
+
this._onNavigate();
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
|
|
63
|
+
goTo: function(index) {
|
|
64
|
+
if (index >= 0 && index < this.total) {
|
|
65
|
+
this.current = index;
|
|
66
|
+
this._onNavigate();
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
|
|
70
|
+
/** Progress as a percentage (0-100) */
|
|
71
|
+
progress: function() {
|
|
72
|
+
if (this.total <= 1) return 100;
|
|
73
|
+
return Math.round((this.current / (this.total - 1)) * 100);
|
|
74
|
+
},
|
|
75
|
+
|
|
76
|
+
/** Whether the Back button should be enabled */
|
|
77
|
+
canPrev: function() {
|
|
78
|
+
return this.current > 0;
|
|
79
|
+
},
|
|
80
|
+
|
|
81
|
+
/** Whether the Next button should be enabled */
|
|
82
|
+
canNext: function() {
|
|
83
|
+
return this.current < this.total - 1;
|
|
84
|
+
},
|
|
85
|
+
|
|
86
|
+
/** Handle post-navigation actions (scroll-snap scroll, scroll-to-top for swap) */
|
|
87
|
+
_onNavigate: function() {
|
|
88
|
+
if (this.mode === 'scroll_snap') {
|
|
89
|
+
var slideEl = document.getElementById('sw-slide-' + this.current);
|
|
90
|
+
if (slideEl) {
|
|
91
|
+
slideEl.scrollIntoView({ behavior: 'smooth' });
|
|
92
|
+
}
|
|
93
|
+
} else if (this.mode === 'swap') {
|
|
94
|
+
// Scroll the slide container into view so the new slide is visible
|
|
95
|
+
var container = this.$el;
|
|
96
|
+
if (container) {
|
|
97
|
+
container.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
};
|
|
103
|
+
})();
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'protocol'
|
|
4
|
+
require_relative 'session'
|
|
5
|
+
|
|
6
|
+
module StreamWeaver
|
|
7
|
+
module Canvas
|
|
8
|
+
# Bridge between Claude Code (Unix socket) and browsers (WebSocket).
|
|
9
|
+
# Manages sessions and routes messages between the two.
|
|
10
|
+
class Bridge
|
|
11
|
+
DEFAULT_PORT = 4568
|
|
12
|
+
|
|
13
|
+
# Result of rendering DSL to HTML
|
|
14
|
+
RenderResult = Struct.new(:html, :error, :stylesheets, keyword_init: true)
|
|
15
|
+
|
|
16
|
+
attr_reader :sessions, :port
|
|
17
|
+
|
|
18
|
+
def initialize(port: DEFAULT_PORT)
|
|
19
|
+
@sessions = {}
|
|
20
|
+
@port = port
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Create or get an existing session
|
|
24
|
+
# @param name [String] Session name
|
|
25
|
+
# @return [Session]
|
|
26
|
+
def create_session(name, layout: :fluid, theme: :default)
|
|
27
|
+
@sessions[name] ||= Session.new(name, layout: layout, theme: theme)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Get a session by name
|
|
31
|
+
# @param name [String] Session name
|
|
32
|
+
# @return [Session, nil]
|
|
33
|
+
def get_session(name)
|
|
34
|
+
@sessions[name]
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Close and remove a session
|
|
38
|
+
# @param name [String] Session name
|
|
39
|
+
# @return [Boolean] true if session existed
|
|
40
|
+
def close_session(name)
|
|
41
|
+
!!@sessions.delete(name)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# List all sessions
|
|
45
|
+
# @return [Array<Hash>]
|
|
46
|
+
def list_sessions
|
|
47
|
+
@sessions.values.map(&:to_h)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Handle a message from Claude Code (via Unix socket)
|
|
51
|
+
# @param message [Hash] Message from Claude
|
|
52
|
+
# @return [Hash] Response message
|
|
53
|
+
def handle_claude_message(message)
|
|
54
|
+
case message[:type]
|
|
55
|
+
when 'create'
|
|
56
|
+
handle_create(message[:name], layout: (message[:layout] || :fluid).to_sym, theme: (message[:theme] || :default).to_sym)
|
|
57
|
+
when 'push'
|
|
58
|
+
handle_push(message[:name], message[:dsl], message[:source_dir])
|
|
59
|
+
when 'toast'
|
|
60
|
+
handle_toast(message[:name], message[:message], message[:variant], message[:duration])
|
|
61
|
+
when 'close'
|
|
62
|
+
handle_close(message[:name])
|
|
63
|
+
when 'get_dsl'
|
|
64
|
+
handle_get_dsl(message[:name])
|
|
65
|
+
when 'set_pane_id'
|
|
66
|
+
handle_set_pane_id(message[:name], message[:pane_id])
|
|
67
|
+
when 'reset'
|
|
68
|
+
handle_reset(message[:name], message[:all])
|
|
69
|
+
when 'list'
|
|
70
|
+
handle_list
|
|
71
|
+
when 'get_state'
|
|
72
|
+
handle_get_state(message[:name])
|
|
73
|
+
else
|
|
74
|
+
{ type: 'error', message: "Unknown message type: #{message[:type]}" }
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Handle a message from browser (via WebSocket)
|
|
79
|
+
# @param session_name [String] Session name
|
|
80
|
+
# @param message [Hash] Message from browser
|
|
81
|
+
# @return [Hash] Event message to forward to Claude
|
|
82
|
+
def handle_browser_message(session_name, message)
|
|
83
|
+
session = get_session(session_name)
|
|
84
|
+
return nil unless session
|
|
85
|
+
|
|
86
|
+
# Update session state from browser
|
|
87
|
+
if message[:state]
|
|
88
|
+
session.update_state(message[:state])
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Return event for Claude
|
|
92
|
+
{
|
|
93
|
+
type: 'event',
|
|
94
|
+
name: session_name,
|
|
95
|
+
event: message[:type],
|
|
96
|
+
data: {
|
|
97
|
+
button: message[:button],
|
|
98
|
+
state: message[:state]
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
private
|
|
104
|
+
|
|
105
|
+
def handle_create(name, layout: :fluid, theme: :default)
|
|
106
|
+
create_session(name, layout: layout, theme: theme)
|
|
107
|
+
{
|
|
108
|
+
type: 'ready',
|
|
109
|
+
name: name,
|
|
110
|
+
url: "http://localhost:#{@port}/canvas/#{name}"
|
|
111
|
+
}
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def handle_push(name, dsl, source_dir = nil)
|
|
115
|
+
session = get_session(name)
|
|
116
|
+
return { type: 'error', message: "Session not found: #{name}" } unless session
|
|
117
|
+
|
|
118
|
+
# Render DSL to HTML
|
|
119
|
+
result = render_dsl(dsl, session_name: name)
|
|
120
|
+
|
|
121
|
+
# Store in session for polling clients
|
|
122
|
+
session.set_html(result.html)
|
|
123
|
+
|
|
124
|
+
# Broadcast to all websockets (if any connected)
|
|
125
|
+
session.broadcast({ type: 'update', html: result.html })
|
|
126
|
+
|
|
127
|
+
# Return error info if rendering failed
|
|
128
|
+
if result.error
|
|
129
|
+
{ type: 'push_error', message: result.error }
|
|
130
|
+
else
|
|
131
|
+
# Persist raw DSL, its inline stylesheets, and the directory it was
|
|
132
|
+
# pushed from only on successful render so a later failed push
|
|
133
|
+
# doesn't clobber the user's last-good content, re-skin
|
|
134
|
+
# (stream_weaver-9uk), or desync source_dir from the DSL it's
|
|
135
|
+
# paired with (canvas-doc-location-and-discovery.md).
|
|
136
|
+
session.set_dsl(dsl)
|
|
137
|
+
session.set_stylesheets(result.stylesheets)
|
|
138
|
+
session.set_source_dir(source_dir)
|
|
139
|
+
{ type: 'push_ok' }
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def handle_toast(name, message, variant, duration)
|
|
144
|
+
session = get_session(name)
|
|
145
|
+
return { type: 'error', message: "Session not found: #{name}" } unless session
|
|
146
|
+
|
|
147
|
+
session.queue_toast(
|
|
148
|
+
message: message,
|
|
149
|
+
variant: variant || 'warning',
|
|
150
|
+
duration: duration || 0
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
nil # No response to Claude for toast
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def handle_close(name)
|
|
157
|
+
session = get_session(name)
|
|
158
|
+
pane_id = session&.pane_id
|
|
159
|
+
session&.broadcast(type: 'closed')
|
|
160
|
+
close_session(name)
|
|
161
|
+
{ type: 'closed', name: name, pane_id: pane_id }
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# canvas-snapshot's DSL/body source (stream_weaver-ps84): returns the
|
|
165
|
+
# session's current in-memory dsl plus the theme/layout it was created
|
|
166
|
+
# with, since the bridge only sets those at Session.new and neither
|
|
167
|
+
# can drift afterward (Session has no theme=/layout= setter).
|
|
168
|
+
def handle_get_dsl(name)
|
|
169
|
+
session = get_session(name)
|
|
170
|
+
return { type: 'error', message: "Session not found: #{name}" } unless session
|
|
171
|
+
|
|
172
|
+
{ type: 'dsl', name: name, dsl: session.dsl, theme: session.theme, layout: session.layout }
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def handle_set_pane_id(name, pane_id)
|
|
176
|
+
session = get_session(name)
|
|
177
|
+
return { type: 'error', message: "Session not found: #{name}" } unless session
|
|
178
|
+
|
|
179
|
+
session.pane_id = pane_id
|
|
180
|
+
{ type: 'pane_id_set', name: name }
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def handle_get_state(name)
|
|
184
|
+
session = get_session(name)
|
|
185
|
+
return { type: 'error', message: "Session not found: #{name}" } unless session
|
|
186
|
+
|
|
187
|
+
{
|
|
188
|
+
type: 'state',
|
|
189
|
+
name: name,
|
|
190
|
+
data: session.state
|
|
191
|
+
}
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def handle_reset(name, reset_all = false)
|
|
195
|
+
return reset_all_sessions if reset_all
|
|
196
|
+
|
|
197
|
+
session = get_session(name) or return { type: 'error', message: "Session not found: #{name}" }
|
|
198
|
+
session.reset!
|
|
199
|
+
{ type: 'reset_ok', name: name }
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def reset_all_sessions
|
|
203
|
+
@sessions.each_value(&:reset!)
|
|
204
|
+
{ type: 'reset_ok', count: @sessions.size }
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
def handle_list
|
|
208
|
+
{ type: 'list', sessions: list_sessions }
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
# Render StreamWeaver DSL to HTML
|
|
212
|
+
# @param dsl [String] DSL code
|
|
213
|
+
# @param session_name [String] Session name for routing
|
|
214
|
+
# @return [RenderResult] Result with html and optional error
|
|
215
|
+
def render_dsl(dsl, session_name:)
|
|
216
|
+
# Create a mini app to evaluate the DSL
|
|
217
|
+
mini_app = StreamWeaver::App.new("Canvas")
|
|
218
|
+
mini_app.instance_eval(dsl)
|
|
219
|
+
|
|
220
|
+
# Create adapter in websocket mode
|
|
221
|
+
adapter = StreamWeaver::Adapter::AlpineJS.new(
|
|
222
|
+
url_prefix: "/canvas/#{session_name}",
|
|
223
|
+
mode: :websocket
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
# Render to HTML
|
|
227
|
+
state = {}
|
|
228
|
+
html = StreamWeaver::Views::AppContentView.new(mini_app, state, adapter, false).call
|
|
229
|
+
RenderResult.new(html: html, error: nil, stylesheets: mini_app.inline_stylesheets)
|
|
230
|
+
rescue => e
|
|
231
|
+
RenderResult.new(
|
|
232
|
+
html: "<div class='error'>Error: #{e.message}</div>",
|
|
233
|
+
error: e.message,
|
|
234
|
+
stylesheets: []
|
|
235
|
+
)
|
|
236
|
+
end
|
|
237
|
+
end
|
|
238
|
+
end
|
|
239
|
+
end
|