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,142 @@
|
|
|
1
|
+
# DHH Review: Future Refactors
|
|
2
|
+
|
|
3
|
+
Notes from DHH-style code review of service mode implementation (2026-01-02).
|
|
4
|
+
|
|
5
|
+
## Route Duplication Between Service and SinatraApp
|
|
6
|
+
|
|
7
|
+
The `/apps/:app_id/update`, `/apps/:app_id/action/:button_id`, `/apps/:app_id/event/:key`, and `/apps/:app_id/form/:form_name` routes in `service.rb` are nearly identical to their counterparts in `server.rb`. Extract shared logic to `RouteHelpers` module.
|
|
8
|
+
|
|
9
|
+
```ruby
|
|
10
|
+
# lib/stream_weaver/route_helpers.rb
|
|
11
|
+
module StreamWeaver
|
|
12
|
+
module RouteHelpers
|
|
13
|
+
def coerce_param_value(value, current_value)
|
|
14
|
+
case
|
|
15
|
+
when value.is_a?(Array) then value
|
|
16
|
+
when %w[on true].include?(value) then true
|
|
17
|
+
when value == "false" then false
|
|
18
|
+
when current_value.is_a?(Array) then Array(value)
|
|
19
|
+
else value
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def sync_params_to_state(state, excluded_keys: [])
|
|
24
|
+
excluded = %w[splat captures app_id button_id] + excluded_keys.map(&:to_s)
|
|
25
|
+
params.each do |key, value|
|
|
26
|
+
next if excluded.include?(key)
|
|
27
|
+
state[key.to_sym] = coerce_param_value(value, state[key.to_sym])
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## PID File Management Should Be Its Own Class
|
|
35
|
+
|
|
36
|
+
Extract PID file logic from Service (lines 96-129) to dedicated class:
|
|
37
|
+
|
|
38
|
+
```ruby
|
|
39
|
+
# lib/stream_weaver/pid_file.rb
|
|
40
|
+
module StreamWeaver
|
|
41
|
+
class PidFile
|
|
42
|
+
PATH = File.expand_path('~/.streamweaver/server.pid').freeze
|
|
43
|
+
|
|
44
|
+
class << self
|
|
45
|
+
def write(port:)
|
|
46
|
+
FileUtils.mkdir_p(File.dirname(PATH))
|
|
47
|
+
File.write(PATH, "port=#{port}\npid=#{Process.pid}\n")
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def delete
|
|
51
|
+
File.delete(PATH) if File.exist?(PATH)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def read
|
|
55
|
+
return unless File.exist?(PATH)
|
|
56
|
+
content = File.read(PATH)
|
|
57
|
+
{ port: content[/port=(\d+)/, 1]&.to_i,
|
|
58
|
+
pid: content[/pid=(\d+)/, 1]&.to_i }
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def process_alive?
|
|
62
|
+
info = read or return false
|
|
63
|
+
Process.kill(0, info[:pid])
|
|
64
|
+
true
|
|
65
|
+
rescue Errno::ESRCH
|
|
66
|
+
delete
|
|
67
|
+
false
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Admin Inline Styles Should Use Semantic CSS Classes
|
|
75
|
+
|
|
76
|
+
The Admin class has inline styles everywhere. Should use semantic classes that reference the theme system:
|
|
77
|
+
|
|
78
|
+
```ruby
|
|
79
|
+
# Before
|
|
80
|
+
div style: "display: inline-flex; align-items: center; gap: 0.5rem; ..." do
|
|
81
|
+
|
|
82
|
+
# After
|
|
83
|
+
div class: "admin-status-bar" do
|
|
84
|
+
status_indicator :running, "Port #{Service.settings.port}"
|
|
85
|
+
end
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## CLI Repetitive Error Handling
|
|
89
|
+
|
|
90
|
+
Every CLI method that contacts the service has identical error handling. Extract to helper:
|
|
91
|
+
|
|
92
|
+
```ruby
|
|
93
|
+
# Before: Repeated 5+ times
|
|
94
|
+
rescue Errno::ECONNREFUSED
|
|
95
|
+
puts "Error: Could not connect to StreamWeaver service"
|
|
96
|
+
exit 1
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# After
|
|
100
|
+
def self.with_service_connection
|
|
101
|
+
yield
|
|
102
|
+
rescue Errno::ECONNREFUSED
|
|
103
|
+
puts "Error: Could not connect to StreamWeaver service"
|
|
104
|
+
exit 1
|
|
105
|
+
end
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## CLI Case Statement Could Be More Declarative
|
|
109
|
+
|
|
110
|
+
```ruby
|
|
111
|
+
# After: Declarative command mapping
|
|
112
|
+
COMMANDS = {
|
|
113
|
+
'serve' => :serve,
|
|
114
|
+
'run' => :run_app,
|
|
115
|
+
'list' => :list_apps,
|
|
116
|
+
'remove' => ->(args) { remove_app(args.first) },
|
|
117
|
+
# ...
|
|
118
|
+
}.freeze
|
|
119
|
+
|
|
120
|
+
def self.run(args)
|
|
121
|
+
return help if args.empty?
|
|
122
|
+
command = args.shift
|
|
123
|
+
handler = COMMANDS[command]
|
|
124
|
+
|
|
125
|
+
case handler
|
|
126
|
+
when Symbol then send(handler, args)
|
|
127
|
+
when Proc then handler.call(args)
|
|
128
|
+
when nil then handle_unknown_command(command, args)
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Remove Comment Banners
|
|
134
|
+
|
|
135
|
+
The Service class has banner comments like:
|
|
136
|
+
```ruby
|
|
137
|
+
# =========================================
|
|
138
|
+
# Service Management Routes
|
|
139
|
+
# =========================================
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Well-organized code documents itself through structure. If you need a banner to find routes, the class is too large.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# StreamWeaver Goes Static
|
|
2
|
+
|
|
3
|
+
You've always been able to write reactive, server-rendered UIs without touching JavaScript. Now the same Ruby DSL runs entirely in the browser — no server, no round-trips, no ops.
|
|
4
|
+
|
|
5
|
+
Here's a complete interactive app:
|
|
6
|
+
|
|
7
|
+
```ruby
|
|
8
|
+
app "Hello World" do
|
|
9
|
+
header1 "Welcome to StreamWeaver!"
|
|
10
|
+
|
|
11
|
+
text_field :name, placeholder: "Enter your name"
|
|
12
|
+
|
|
13
|
+
if state[:name] && state[:name].strip != ""
|
|
14
|
+
text "Hello, #{state[:name]}!"
|
|
15
|
+
|
|
16
|
+
checkbox :subscribe, "Subscribe to newsletter"
|
|
17
|
+
|
|
18
|
+
if state[:subscribe]
|
|
19
|
+
text "You're subscribed!"
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
That's not a server app stripped of its `require` lines. That is the whole thing — identical DSL, running in-browser via Opal. State lives in the page. Interactions re-execute the block and patch the DOM with morphdom. No server involved.
|
|
26
|
+
|
|
27
|
+
## One command to deploy
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
streamweaver opal-build app.rb
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
That produces a `dist/` folder: `index.html`, `app.js`, `morphdom.min.js`. Drag it to GitHub Pages, Netlify, S3 — anywhere that serves files. Done.
|
|
34
|
+
|
|
35
|
+
## What this is not
|
|
36
|
+
|
|
37
|
+
It's not React. It's not Next.js. There's no webpack config, no `package.json`, no `node_modules`, no TypeScript compilation step, no context switch between your "frontend language" and your "backend language" because there is only one language. You write Ruby. You ship Ruby. The toolchain is one gem.
|
|
38
|
+
|
|
39
|
+
The mental model stays the same whether you're building a server-rendered dashboard or a static page: declare what the UI looks like given the current state, let StreamWeaver figure out the rest.
|
|
40
|
+
|
|
41
|
+
## Phase 1 — honest about scope
|
|
42
|
+
|
|
43
|
+
The core components are covered: `app`, `header`, `text_field`, `checkbox`, `button`, `md`, `div`. State is fully reactive in-browser.
|
|
44
|
+
|
|
45
|
+
What's not there yet: `feed`, `streamer`, and `service_client` (they require a server by definition), plus `mermaid`, `chartjs`, `tabs`, and `table` (coming in Phase 2). If your app uses those, keep running it server-side — that path isn't going anywhere.
|
|
46
|
+
|
|
47
|
+
Full component parity is the goal. Phase 1 gets you surprisingly far.
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
# StreamWeaver → Opal → NPM: Direction Document
|
|
2
|
+
|
|
3
|
+
**Author:** Forrest Chang
|
|
4
|
+
**Date:** May 2026
|
|
5
|
+
**Status:** Vision / Future Work — Do Not Pursue Until Prerequisites Met
|
|
6
|
+
**Purpose:** Preserve this architectural vision so it's not lost. This is a north star, not a sprint.
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## The Vision
|
|
11
|
+
|
|
12
|
+
StreamWeaver is currently a Ruby gem with a Sinatra server backend. The vision: **package StreamWeaver as an NPM module via Opal's "Sole" engine**, making it installable as:
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install stream-weaver
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
This would allow JavaScript developers to use a Ruby-authored reactive UI framework **without knowing Ruby**. They import it, write component trees in JS (which would be Opal-transpiled Ruby API), and get all the same reactive rendering, component library, and DSL expressiveness — in their existing JS stack.
|
|
19
|
+
|
|
20
|
+
This is a compelling story for reaching the much larger JavaScript ecosystem while keeping the canonical source of truth in Ruby.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## What Already Exists
|
|
25
|
+
|
|
26
|
+
### Opal Adapter
|
|
27
|
+
`lib/stream_weaver/adapter/opal.rb` — an Opal-compatible adapter layer already exists. This was written with browser execution in mind.
|
|
28
|
+
|
|
29
|
+
### Opal Runtime
|
|
30
|
+
`lib/stream_weaver/opal/runtime.rb` contains:
|
|
31
|
+
- `OpalRuntime` — Opal-clean entry point
|
|
32
|
+
- `OpalBridge` — browser ↔ Ruby communication bridge
|
|
33
|
+
- `ReactiveState` — client-side reactive state, already Opal-clean
|
|
34
|
+
|
|
35
|
+
These three modules are **already ready** for Opal compilation. They have no server-side dependencies.
|
|
36
|
+
|
|
37
|
+
### JS Stubs
|
|
38
|
+
`lib/stream_weaver/opal/stubs/` contains stubs for:
|
|
39
|
+
- `marked.umd.js` — markdown rendering (browser equivalent of kramdown)
|
|
40
|
+
- `morphdom.min.js` — DOM diffing
|
|
41
|
+
|
|
42
|
+
### Opal Compatibility Spike
|
|
43
|
+
`bin/opal_spike.rb` — an existing spike script that tests which modules compile cleanly through Opal. Run this to audit current compilation status.
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## What "Sole" Does
|
|
48
|
+
|
|
49
|
+
Opal's **Sole** engine is the build tool that makes Ruby→NPM publishing practical:
|
|
50
|
+
|
|
51
|
+
1. Reads your `.gemspec`
|
|
52
|
+
2. Generates a `package.json` from gem metadata
|
|
53
|
+
3. Compiles all Ruby source files to JavaScript via Opal
|
|
54
|
+
4. Creates an `index.js` entry point that exports your Ruby classes/modules as JS
|
|
55
|
+
5. Enables `npm publish` — the package is now installable like any JS library
|
|
56
|
+
|
|
57
|
+
The result: `npm install stream-weaver` gives a JS developer access to `StreamWeaver.canvas()`, reactive state, and all components, backed by Opal's Ruby runtime in the browser.
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## The Technical Gap
|
|
62
|
+
|
|
63
|
+
### What Can't Run in a Browser
|
|
64
|
+
|
|
65
|
+
The current server layer has components that fundamentally cannot run in a browser:
|
|
66
|
+
|
|
67
|
+
| Server Concern | Why It Can't Go to Browser |
|
|
68
|
+
|---------------|---------------------------|
|
|
69
|
+
| Sinatra HTTP server | No server socket in browser |
|
|
70
|
+
| Canvas Bridge WebSocket server | No TCP listener in browser |
|
|
71
|
+
| `kramdown` gem (Markdown) | Server-side Ruby gem, no browser equivalent |
|
|
72
|
+
| File I/O for table loading | No filesystem access in browser |
|
|
73
|
+
| Prism.js CDN loading via server | Needs to be bundled or loaded differently |
|
|
74
|
+
|
|
75
|
+
### The Replacement Model
|
|
76
|
+
|
|
77
|
+
Instead of a server → client push model, the Opal version needs a **pure client-side reactive model**:
|
|
78
|
+
|
|
79
|
+
```
|
|
80
|
+
Ruby DSL (compiled by Opal) → OpalRuntime → OpalBridge → DOM
|
|
81
|
+
↕
|
|
82
|
+
ReactiveState
|
|
83
|
+
(in-memory, browser)
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
The server is replaced by in-browser reactive state. Component updates are DOM mutations, not WebSocket pushes.
|
|
87
|
+
|
|
88
|
+
**The good news:** `OpalRuntime`, `OpalBridge`, and `ReactiveState` already implement this model. They were written with Opal in mind.
|
|
89
|
+
|
|
90
|
+
### Remaining Gaps to Close
|
|
91
|
+
|
|
92
|
+
Before a full Opal build would succeed:
|
|
93
|
+
|
|
94
|
+
#### 1. `components.rb` — Server-Side Concerns
|
|
95
|
+
- `kramdown` calls need to be replaced with `marked.umd.js` (stub already exists)
|
|
96
|
+
- File I/O for table data loading needs an Opal stub or in-memory equivalent
|
|
97
|
+
- Any `require 'json'` or `require 'tempfile'` needs Opal stubs
|
|
98
|
+
|
|
99
|
+
#### 2. `display_dsl.rb` — Server-Side Concerns
|
|
100
|
+
- Prism.js loading pattern needs to be browser-native (script tag or bundled)
|
|
101
|
+
- Any server-side rendering helpers need client-side equivalents
|
|
102
|
+
|
|
103
|
+
#### 3. WebSocket → In-Memory Events
|
|
104
|
+
`canvas_bridge.rb` uses WebSockets. The Opal version needs to replace this with an in-browser event bus (custom events or a simple pub/sub).
|
|
105
|
+
|
|
106
|
+
#### 4. Sole Configuration
|
|
107
|
+
A `stream-weaver.gemspec` needs Sole-compatible metadata and the Opal build needs to be configured to exclude the server layer files entirely.
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## Minimal Viable NPM Path
|
|
112
|
+
|
|
113
|
+
Rather than porting the entire feature set at once, there's a pragmatic MVP:
|
|
114
|
+
|
|
115
|
+
**MVP Scope:** Just the reactive core
|
|
116
|
+
|
|
117
|
+
```
|
|
118
|
+
OpalRuntime ← Opal-clean ✅
|
|
119
|
+
OpalBridge ← Opal-clean ✅
|
|
120
|
+
Opal adapter ← Opal-clean ✅
|
|
121
|
+
Basic components ← ~20 most-used components, stubs for server deps
|
|
122
|
+
ReactiveState ← Opal-clean ✅
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
**What's excluded from MVP:**
|
|
126
|
+
- Mermaid (external library dependency)
|
|
127
|
+
- Chart.js integration (manageable but deferred)
|
|
128
|
+
- Prism.js code blocks (deferred)
|
|
129
|
+
- Slide container (deferred)
|
|
130
|
+
- All navigation/server-rendered concerns
|
|
131
|
+
|
|
132
|
+
The MVP would demonstrate:
|
|
133
|
+
- `npm install stream-weaver` works
|
|
134
|
+
- `import StreamWeaver from 'stream-weaver'` gives you a working reactive component tree
|
|
135
|
+
- Basic layout, cards, text, stat displays, progress bars all render
|
|
136
|
+
- Reactive state updates propagate to DOM
|
|
137
|
+
|
|
138
|
+
This is enough to be interesting to JS developers and validate the concept.
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## Strategic Framing
|
|
143
|
+
|
|
144
|
+
### Do This AFTER Traction
|
|
145
|
+
|
|
146
|
+
The NPM story is most compelling when there's proven Ruby-side value to port. The sequence should be:
|
|
147
|
+
|
|
148
|
+
1. ✅ **Build the Ruby gem to be feature-rich** — fill the audit gaps, get to parity with artifact aesthetic
|
|
149
|
+
2. ✅ **Publish the gem publicly** — RubyGems, documentation, README with demos
|
|
150
|
+
3. ✅ **See community adoption** — GitHub stars, issues filed by others, blog posts, use in production
|
|
151
|
+
4. 🔜 **Then pursue NPM** — "The Ruby reactive UI framework that JS developers can also use"
|
|
152
|
+
|
|
153
|
+
Pursuing NPM too early means spending engineering time on a complex cross-compilation build before there's proven demand. The JS community will be more receptive to "port this popular Ruby thing" than "try this new thing that happens to be Ruby inside."
|
|
154
|
+
|
|
155
|
+
### The Narrative When the Time Is Right
|
|
156
|
+
|
|
157
|
+
> "StreamWeaver is Streamlit for Ruby — and now you can use it from JavaScript too, without ever touching Ruby. All the expressiveness of a Ruby DSL, compiled to JavaScript by Opal, available on NPM."
|
|
158
|
+
|
|
159
|
+
This is a genuinely novel story. Opal's NPM publishing via Sole is underutilized. StreamWeaver would be a showcase of what's possible.
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## Technical Checklist (When Ready to Execute)
|
|
164
|
+
|
|
165
|
+
- [ ] Run `bin/opal_spike.rb` — document which modules compile cleanly and which fail
|
|
166
|
+
- [ ] Create Opal stubs for `kramdown`, file I/O, and WebSocket dependencies
|
|
167
|
+
- [ ] Audit `components.rb` for every `require` and server assumption
|
|
168
|
+
- [ ] Audit `display_dsl.rb` same
|
|
169
|
+
- [ ] Implement in-browser event bus to replace WebSocket canvas bridge
|
|
170
|
+
- [ ] Configure Sole build with gemspec metadata
|
|
171
|
+
- [ ] Build and test MVP component set in a browser via Opal
|
|
172
|
+
- [ ] Publish `stream-weaver` to NPM (scoped or unscoped)
|
|
173
|
+
- [ ] Write JS-facing documentation (different audience from Ruby docs)
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## References
|
|
178
|
+
|
|
179
|
+
- Opal documentation: https://opalrb.com
|
|
180
|
+
- Sole (Opal NPM bridge): https://github.com/opal/opal (see `sole` tooling)
|
|
181
|
+
- Existing Opal adapter: `lib/stream_weaver/adapter/opal.rb`
|
|
182
|
+
- Existing Opal runtime: `lib/stream_weaver/opal/runtime.rb`
|
|
183
|
+
- Opal spike: `bin/opal_spike.rb`
|
|
184
|
+
- JS stubs: `lib/stream_weaver/opal/stubs/`
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
# Opal Compatibility Spike Findings
|
|
2
|
+
|
|
3
|
+
**Spike date:** 2026-04-29
|
|
4
|
+
**Opal version:** 1.8.2
|
|
5
|
+
**Ruby version:** 3.3.5
|
|
6
|
+
|
|
7
|
+
## Summary
|
|
8
|
+
|
|
9
|
+
All 7 candidate files compile cleanly under Opal when missing-require errors are suppressed (syntax-only pass). Two files have `require` statements for standard library modules unavailable in Opal. These are fixable by conditionally guarding the requires or replacing the calls. There are no fundamental syntax incompatibilities in the target files.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Files and Results
|
|
14
|
+
|
|
15
|
+
### Files that compiled cleanly (no issues at all)
|
|
16
|
+
|
|
17
|
+
| File | Result |
|
|
18
|
+
|---|---|
|
|
19
|
+
| `lib/stream_weaver/version.rb` | OK |
|
|
20
|
+
| `lib/stream_weaver/utils.rb` | OK |
|
|
21
|
+
| `lib/stream_weaver/theme.rb` | OK |
|
|
22
|
+
| `lib/stream_weaver/components.rb` | OK |
|
|
23
|
+
| `lib/stream_weaver/adapter/base.rb` | OK |
|
|
24
|
+
|
|
25
|
+
### Files with missing-require issues (fixable)
|
|
26
|
+
|
|
27
|
+
| File | Missing require | Impact |
|
|
28
|
+
|---|---|---|
|
|
29
|
+
| `lib/stream_weaver/display_dsl.rb` | `require 'digest/md5'` | Inside `DisplayDSL#button` at runtime — for generating stable button IDs |
|
|
30
|
+
| `lib/stream_weaver/app.rb` | `require 'digest'`, `require 'set'` | `digest` for `Digest::MD5` in `App#button`; `set` for `@transient_keys = Set.new` |
|
|
31
|
+
|
|
32
|
+
**Note:** `set`, `json`, `cgi`, `base64`, and `ostruct` ARE available in Opal's stdlib. Only `digest` and `digest/md5` are missing from Opal.
|
|
33
|
+
|
|
34
|
+
**All 7 files pass Opal syntax compilation when `missing_require_severity: :ignore` is used.**
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Missing Requires Analysis
|
|
39
|
+
|
|
40
|
+
### `digest` / `digest/md5`
|
|
41
|
+
|
|
42
|
+
Used for: generating stable 8-character hex IDs for buttons from source location or label+id pair.
|
|
43
|
+
|
|
44
|
+
**Where:** `display_dsl.rb:580` (in `DisplayDSL#button`) and `app.rb:3` (top-level require) + `app.rb:326` (in `App#button`).
|
|
45
|
+
|
|
46
|
+
**Workarounds:**
|
|
47
|
+
1. **Best for Opal:** Replace `Digest::MD5.hexdigest(id_input)[0..7]` with a pure-Ruby FNV or djb2 hash (no stdlib needed). This is the recommended approach for the Opal adapter.
|
|
48
|
+
2. **Conditional guard:** `require 'digest/md5' unless RUBY_PLATFORM == 'opal'` — but this leaks server/client coupling into shared files.
|
|
49
|
+
3. **OpalRenderer handles button IDs independently** — the Opal runtime can use JS `Date.now()` or a counter; the Ruby-side `stable_id` is server-only.
|
|
50
|
+
|
|
51
|
+
**Recommended resolution:** The Opal runtime/renderer does not need stable IDs from Ruby — buttons in the browser are handled via JS event listeners, not HTMX post-backs. The `DisplayDSL#button` and `App#button` methods can be overridden in `adapter/opal.rb` without the `digest` dependency.
|
|
52
|
+
|
|
53
|
+
### `require 'set'` in `app.rb`
|
|
54
|
+
|
|
55
|
+
`Set` IS available in Opal's stdlib (`stdlib/set.rb`). However, the `require 'set'` at the top of `app.rb` will trigger a missing-require warning in strict mode — but since Opal includes `set.rb` in its stdlib, this resolves correctly at runtime. **No action needed.**
|
|
56
|
+
|
|
57
|
+
Wait — re-checking: Opal's stdlib has `set.rb` but it needs to be added to the Opal path. In practice, when using `missing_require_severity: :ignore`, `Set` will be undefined at runtime unless explicitly included. The `opal_entry.rb` build step should add `require 'set'` before bundling, or stub it.
|
|
58
|
+
|
|
59
|
+
**Resolution:** Add `require 'set'` to the Opal bundle entrypoint build configuration.
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## Opal Standard Library Availability
|
|
64
|
+
|
|
65
|
+
| Library | In Opal | Notes |
|
|
66
|
+
|---|---|---|
|
|
67
|
+
| `set` | YES (`stdlib/set.rb`) | Must be explicitly included in bundle |
|
|
68
|
+
| `json` | YES (`stdlib/json.rb`) | Must be explicitly included in bundle |
|
|
69
|
+
| `cgi` | YES (`stdlib/cgi.rb`) | Must be explicitly included in bundle |
|
|
70
|
+
| `base64` | YES (`stdlib/base64.rb`) | Must be explicitly included in bundle |
|
|
71
|
+
| `ostruct` | YES (`stdlib/ostruct.rb`) | Must be explicitly included in bundle |
|
|
72
|
+
| `digest` | NO | Not in Opal core or stdlib — replace with pure-Ruby hash |
|
|
73
|
+
| `digest/md5` | NO | Same as above |
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## kramdown Opal Compatibility
|
|
78
|
+
|
|
79
|
+
**Result: NOT Opal-safe for production use.**
|
|
80
|
+
|
|
81
|
+
When kramdown's gem `lib/` is added to Opal's load path:
|
|
82
|
+
- Compiles with `missing_require_severity: :ignore` but produces warnings:
|
|
83
|
+
- "Cannot handle dynamic require" — `kramdown/document.rb:130`, `kramdown/converter.rb:39`, `kramdown/converter.rb:55`
|
|
84
|
+
- "Skipping the 'o' Regexp flag as it's not widely supported by JavaScript vendors" — multiple occurrences in `kramdown/parser/html.rb`
|
|
85
|
+
- Fails strict compilation with `MissingRequire` on `kramdown/converter/math_engine/`
|
|
86
|
+
- kramdown uses `require` with dynamic strings (string interpolation), which Opal cannot resolve
|
|
87
|
+
|
|
88
|
+
**Conclusion:** `render_markdown` in the Opal renderer MUST use `marked.js` via JS interop (or another browser-native Markdown library) rather than kramdown. This is a firm constraint.
|
|
89
|
+
|
|
90
|
+
**Implementation note:** `marked.js` is already a common CDN dependency for Markdown rendering in browser contexts. The Opal adapter can call `JS.global[:marked].call(content)` after loading marked.js.
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## Server-Only Constructs in Candidate Files
|
|
95
|
+
|
|
96
|
+
### `app.rb`
|
|
97
|
+
|
|
98
|
+
| Construct | Impact |
|
|
99
|
+
|---|---|
|
|
100
|
+
| `SinatraApp.create(self)` in `App#generate` | Only called from server entrypoint — not in browser path |
|
|
101
|
+
| `File.exist?` / `File.binread` in `build_favicon_href` | File I/O — not callable in browser. `build_favicon_href` only reached if `favicon` DSL is used. Override in Opal adapter. |
|
|
102
|
+
| `CGI.escape` in `define_path_helpers` | CGI is available in Opal stdlib — works in browser |
|
|
103
|
+
| `Time.now.to_f` in `show_toast` | `Time` is available in Opal core — works in browser |
|
|
104
|
+
| `require 'resource'` at top of `app.rb` | `resource.rb` requires `cgi` (available) but also integrates with Sinatra routing. Must stub or exclude. |
|
|
105
|
+
|
|
106
|
+
### `display_dsl.rb`
|
|
107
|
+
|
|
108
|
+
| Construct | Impact |
|
|
109
|
+
|---|---|
|
|
110
|
+
| `require 'digest/md5'` in `#button` | See above — override button in Opal adapter |
|
|
111
|
+
| `block.source_location` | Available in Opal — OK |
|
|
112
|
+
|
|
113
|
+
### `components.rb`
|
|
114
|
+
|
|
115
|
+
| Construct | Impact |
|
|
116
|
+
|---|---|
|
|
117
|
+
| `require "yaml"` / `require "json"` (inside Table/ScoreTable) | `json` available in Opal stdlib; `yaml` not needed for browser rendering |
|
|
118
|
+
| Direct `view.div`, `view.span`, `view.h4`, `view.p`, `view.hr` calls | These 5 HTML methods must exist on the OpalRenderer's view object |
|
|
119
|
+
| `view.adapter.*` delegation | All rendering goes through adapter — clean separation |
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## OpalRenderer Must-Implement Method List
|
|
124
|
+
|
|
125
|
+
The `OpalRenderer` (the `view` object passed to `component.render(view, state)`) must support:
|
|
126
|
+
|
|
127
|
+
### Direct HTML element methods (called directly on view in components.rb)
|
|
128
|
+
|
|
129
|
+
- `div(**attrs, &block)` — Card, CardHeader, CardBody, CardFooter, Div
|
|
130
|
+
- `span(&block)` — Phrase, Card label
|
|
131
|
+
- `h4(&block)` — CardHeader
|
|
132
|
+
- `p(&block)` — Text component
|
|
133
|
+
- `hr(**attrs)` — MenuDivider
|
|
134
|
+
|
|
135
|
+
### Adapter methods (via `view.adapter.*`)
|
|
136
|
+
|
|
137
|
+
From `components.rb` (64 adapter calls):
|
|
138
|
+
- `render_text_field`, `render_text_area`, `render_checkbox`, `render_select`, `render_radio_group`
|
|
139
|
+
- `render_button`, `render_app_header`, `render_div`, `render_term`, `render_lesson_text`
|
|
140
|
+
- `render_vstack`, `render_hstack`, `render_grid`, `render_scroll_box`
|
|
141
|
+
- `render_collapsible`, `render_alert`, `render_tabs`, `render_breadcrumbs`, `render_dropdown`
|
|
142
|
+
- `render_modal`, `render_toast_container`, `render_theme_toggle`, `render_theme_preset`, `render_theme_switcher`
|
|
143
|
+
- `render_header`, `render_markdown`, `render_stat_display`, `render_badge`, `render_status_dot`
|
|
144
|
+
- `render_type_tag`, `render_pulse_indicator`, `render_activity_item`, `render_timeline_event`
|
|
145
|
+
- `render_priority_item`, `render_progress_bar`, `render_spinner`, `render_score_table`, `render_table`
|
|
146
|
+
- `render_status_badge`, `render_external_link_button`, `render_link`, `render_navbar`, `render_nav_item`
|
|
147
|
+
- `render_checkbox_group`, `render_code_editor`, `render_tag_buttons`, `render_form`
|
|
148
|
+
- `render_expandable_card`, `render_canvas_continue`, `render_bar_chart`, `render_line_chart`
|
|
149
|
+
- `render_pie_chart`, `render_stacked_bar_chart`, `render_column`, `render_columns`
|
|
150
|
+
- `render_app_shell`, `render_main_content`, `render_sidebar`
|
|
151
|
+
|
|
152
|
+
From visual skills component sub-files:
|
|
153
|
+
- `render_code_block`, `render_image_block`, `render_mermaid`, `render_keyboard_shortcuts`
|
|
154
|
+
- `render_slide_container`, `render_slide`, `render_sidebar_toc`, `render_callout`, `render_comparison`
|
|
155
|
+
- `render_pipeline`, `render_kpi_dashboard`, `render_chartjs`, `render_timeline_event`
|
|
156
|
+
- `render_design_deck`, `render_deck_slide`, `render_deck_option`, `render_deck_summary`
|
|
157
|
+
- `render_generate_more_controls`, `render_skeleton_placeholder`, `render_model_selector`
|
|
158
|
+
- `render_close_overlay`, `render_confirmation_bar`
|
|
159
|
+
- `render_hero`, `render_prose`, `render_pullquote`, `render_dir_tree`, `render_legend`
|
|
160
|
+
- `render_flow_arrow`, `render_layout_toggle`
|
|
161
|
+
|
|
162
|
+
**Phase 1 OpalRenderer priority (minimal viable set for basic text/container rendering):**
|
|
163
|
+
- `div`, `span`, `h4`, `p`, `hr`
|
|
164
|
+
- `render_text`, `render_markdown` (via marked.js), `render_header`, `render_button`
|
|
165
|
+
- `render_div`, `render_vstack`, `render_hstack`, `render_card`
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
## Adjustments to Phase 1 Plan
|
|
170
|
+
|
|
171
|
+
1. **Opal version locked:** Use `opal ~> 1.8` (installed 1.8.2). API confirmed working.
|
|
172
|
+
|
|
173
|
+
2. **Builder pattern:** Use `Opal::Builder.new(missing_require_severity: :ignore)` for the entrypoint build. The 7 candidate files all compile under this mode.
|
|
174
|
+
|
|
175
|
+
3. **digest replacement:** The `App#button` and `DisplayDSL#button` use `Digest::MD5` for stable IDs. In the Opal adapter, override the button logic to use a pure counter or JS `Math.random()`. No digest dependency needed in the browser.
|
|
176
|
+
|
|
177
|
+
4. **stdlib requires in Opal bundle:** The opal build step must explicitly `require` stdlib modules: `set`, `json`, `cgi` before bundling `stream_weaver/app`.
|
|
178
|
+
|
|
179
|
+
5. **kramdown is out:** `render_markdown` in `OpalAdapter` must use `marked.js` via `JS.global[:marked].call(content)`. Add marked.js to the CDN list alongside the Opal runtime bundle.
|
|
180
|
+
|
|
181
|
+
6. **`require 'resource'` in app.rb:** `resource.rb` is a server-only routing concern. The `opal_entry.rb` entrypoint does NOT require `app.rb` directly — it requires only the safe subset. Phase 1 OpalRenderer can stub or skip resource/routing machinery.
|
|
182
|
+
|
|
183
|
+
7. **Direct view HTML calls are minimal:** Only `div`, `span`, `h4`, `p`, `hr` — the OpalRenderer's view interface is very simple.
|
|
184
|
+
|
|
185
|
+
8. **Adapter delegation is the primary pattern:** 64 of 73 `view.` calls go through `view.adapter.*` — the clean separation makes OpalAdapter straightforward to implement incrementally.
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
## Files Created/Modified This Spike
|
|
190
|
+
|
|
191
|
+
- `Gemfile` — added `group :development { gem "opal", "~> 1.8" }`
|
|
192
|
+
- `lib/stream_weaver/opal_entry.rb` — browser-only require tree (created, placeholder for non-existent files)
|
|
193
|
+
- `bin/opal_spike.rb` — Opal compatibility test script
|
|
194
|
+
- `docs/opal-spike-findings.md` — this document
|