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,499 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Example: Visual Explainer — Diff Review
|
|
5
|
+
# A comprehensive code review document showcasing every explainer component.
|
|
6
|
+
# Scenario: AI agent reviews a PR that migrates from session-based auth to JWT.
|
|
7
|
+
#
|
|
8
|
+
# Run with: bundle exec ruby examples/visual_skills/explainer_demo.rb
|
|
9
|
+
|
|
10
|
+
require_relative '../../lib/stream_weaver'
|
|
11
|
+
|
|
12
|
+
ExplainerApp = app "Diff Review: feature/auth-migration", theme: :dark do
|
|
13
|
+
# -- Theme & chrome ----------------------------------------------------------
|
|
14
|
+
theme_preset :editorial
|
|
15
|
+
theme_toggle mode: :auto, hotkey: "mod+shift+l"
|
|
16
|
+
|
|
17
|
+
# -- Sidebar TOC with scroll spy ---------------------------------------------
|
|
18
|
+
sidebar_toc sections: [
|
|
19
|
+
{ id: "summary", label: "Summary" },
|
|
20
|
+
{ id: "kpis", label: "Key Metrics" },
|
|
21
|
+
{ id: "architecture", label: "Architecture" },
|
|
22
|
+
{ id: "file-changes", label: "File Changes" },
|
|
23
|
+
{ id: "auth-before-after", label: "Auth Diff" },
|
|
24
|
+
{ id: "middleware", label: "Middleware" },
|
|
25
|
+
{ id: "pipeline", label: "CI Pipeline" },
|
|
26
|
+
{ id: "test-coverage", label: "Test Coverage" },
|
|
27
|
+
{ id: "risks", label: "Risk Analysis" },
|
|
28
|
+
{ id: "verdict", label: "Verdict" }
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
# -- Hero section ------------------------------------------------------------
|
|
32
|
+
div id: "summary" do
|
|
33
|
+
hero do
|
|
34
|
+
header1 "Diff Review: feature/auth-migration"
|
|
35
|
+
md "**PR #347** — Migrate from session-based authentication to JWT tokens"
|
|
36
|
+
hstack spacing: :md, align: :center do
|
|
37
|
+
badge "87 files changed", variant: :info
|
|
38
|
+
badge "+2,041 / -1,388", variant: :warning
|
|
39
|
+
badge "3 open threads", variant: :danger
|
|
40
|
+
badge "CI passing", variant: :success
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# -- Executive summary (prose) -----------------------------------------------
|
|
46
|
+
prose dropcap: true do
|
|
47
|
+
md <<~MARKDOWN
|
|
48
|
+
This pull request replaces the legacy cookie-based session authentication
|
|
49
|
+
system with a stateless JWT token architecture. The migration touches the
|
|
50
|
+
authentication middleware, all protected API endpoints, the user model,
|
|
51
|
+
and the front-end token refresh logic. Session storage in Redis is replaced
|
|
52
|
+
by short-lived access tokens (15 min) paired with long-lived refresh tokens
|
|
53
|
+
(7 days) stored in an HttpOnly cookie.
|
|
54
|
+
|
|
55
|
+
The change is motivated by three factors: horizontal scaling without sticky
|
|
56
|
+
sessions, enabling a mobile API surface, and reducing Redis operational
|
|
57
|
+
burden. The overall approach is sound, but several security and performance
|
|
58
|
+
concerns require resolution before merge.
|
|
59
|
+
MARKDOWN
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
pullquote(
|
|
63
|
+
"Stateless auth is not free -- you trade server-side revocation for scalability. Make sure the trade is worth it.",
|
|
64
|
+
attribution: "Security Review Bot"
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
# -- KPI dashboard -----------------------------------------------------------
|
|
68
|
+
div id: "kpis" do
|
|
69
|
+
header2 "Key Metrics"
|
|
70
|
+
|
|
71
|
+
kpi_dashboard metrics: [
|
|
72
|
+
{ value: "87", label: "Files Changed", color: :blue, trend: :up },
|
|
73
|
+
{ value: "+2,041", label: "Lines Added", color: :green, trend: :up },
|
|
74
|
+
{ value: "-1,388", label: "Lines Removed", color: :red, trend: :down },
|
|
75
|
+
{ value: "94.2%", label: "Test Coverage", color: :green, trend: :up },
|
|
76
|
+
{ value: "15 min", label: "Access Token TTL", color: :orange },
|
|
77
|
+
{ value: "7 days", label: "Refresh Token TTL", color: :purple }
|
|
78
|
+
]
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# -- Test coverage chart -----------------------------------------------------
|
|
82
|
+
div id: "test-coverage" do
|
|
83
|
+
header2 "Test Coverage Trend"
|
|
84
|
+
|
|
85
|
+
chart type: :line, data: {
|
|
86
|
+
labels: ["Week 1", "Week 2", "Week 3", "Week 4", "Week 5", "This PR"],
|
|
87
|
+
datasets: [
|
|
88
|
+
{
|
|
89
|
+
label: "Line Coverage %",
|
|
90
|
+
data: [88.1, 89.3, 90.0, 91.5, 92.8, 94.2],
|
|
91
|
+
borderColor: "#22c55e",
|
|
92
|
+
backgroundColor: "rgba(34, 197, 94, 0.1)",
|
|
93
|
+
fill: true,
|
|
94
|
+
tension: 0.3
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
label: "Branch Coverage %",
|
|
98
|
+
data: [72.4, 74.0, 75.2, 78.1, 80.5, 83.7],
|
|
99
|
+
borderColor: "#3b82f6",
|
|
100
|
+
backgroundColor: "rgba(59, 130, 246, 0.1)",
|
|
101
|
+
fill: true,
|
|
102
|
+
tension: 0.3
|
|
103
|
+
}
|
|
104
|
+
]
|
|
105
|
+
}, options: {
|
|
106
|
+
plugins: { title: { display: true, text: "Coverage Over Sprint" } },
|
|
107
|
+
scales: {
|
|
108
|
+
y: { min: 60, max: 100, ticks: { callback: "function(v){return v+'%'}" } }
|
|
109
|
+
}
|
|
110
|
+
}, height: 280
|
|
111
|
+
|
|
112
|
+
md "*Coverage improved from 88.1% to 94.2% across the sprint. Branch coverage still trails at 83.7%.*"
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# -- Architecture diagram (Mermaid) ------------------------------------------
|
|
116
|
+
div id: "architecture" do
|
|
117
|
+
header2 "Authentication Architecture"
|
|
118
|
+
|
|
119
|
+
mermaid <<~MERMAID, zoom: true
|
|
120
|
+
graph TD
|
|
121
|
+
Client["Client (Browser / Mobile)"]
|
|
122
|
+
API["API Gateway"]
|
|
123
|
+
AuthSvc["Auth Service"]
|
|
124
|
+
UserDB[("Users DB")]
|
|
125
|
+
TokenStore["Token Blacklist<br/>(Redis)"]
|
|
126
|
+
Protected["Protected Resources"]
|
|
127
|
+
|
|
128
|
+
Client -->|"1. POST /auth/login<br/>email + password"| API
|
|
129
|
+
API -->|"2. Forward"| AuthSvc
|
|
130
|
+
AuthSvc -->|"3. Verify credentials"| UserDB
|
|
131
|
+
AuthSvc -->|"4. Issue JWT pair"| API
|
|
132
|
+
API -->|"5. Set HttpOnly cookie<br/>+ access_token body"| Client
|
|
133
|
+
|
|
134
|
+
Client -->|"6. GET /api/resource<br/>Authorization: Bearer token"| API
|
|
135
|
+
API -->|"7. Verify JWT signature<br/>+ check blacklist"| TokenStore
|
|
136
|
+
API -->|"8. Forward (valid)"| Protected
|
|
137
|
+
Protected -->|"9. Response"| Client
|
|
138
|
+
|
|
139
|
+
Client -->|"10. POST /auth/refresh"| AuthSvc
|
|
140
|
+
AuthSvc -->|"11. Rotate refresh token"| TokenStore
|
|
141
|
+
|
|
142
|
+
style Client fill:#dbeafe,stroke:#3b82f6,color:#1e3a5f
|
|
143
|
+
style AuthSvc fill:#dcfce7,stroke:#22c55e,color:#166534
|
|
144
|
+
style TokenStore fill:#fef3c7,stroke:#f59e0b,color:#92400e
|
|
145
|
+
style UserDB fill:#f3e8ff,stroke:#a855f7,color:#6b21a8
|
|
146
|
+
style Protected fill:#e0e7ff,stroke:#6366f1,color:#3730a3
|
|
147
|
+
style API fill:#f1f5f9,stroke:#94a3b8,color:#334155
|
|
148
|
+
MERMAID
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# -- File changes (dir_tree + legend) ----------------------------------------
|
|
152
|
+
div id: "file-changes" do
|
|
153
|
+
header2 "File Changes"
|
|
154
|
+
|
|
155
|
+
legend items: [
|
|
156
|
+
{ color: "#22c55e", label: "New" },
|
|
157
|
+
{ color: "#f59e0b", label: "Modified" },
|
|
158
|
+
{ color: "#ef4444", label: "Deleted" }
|
|
159
|
+
]
|
|
160
|
+
|
|
161
|
+
dir_tree <<~TREE
|
|
162
|
+
app/
|
|
163
|
+
controllers/
|
|
164
|
+
auth_controller.rb [modified]
|
|
165
|
+
sessions_controller.rb [deleted]
|
|
166
|
+
api/
|
|
167
|
+
base_controller.rb [modified]
|
|
168
|
+
middleware/
|
|
169
|
+
jwt_authenticator.rb [new]
|
|
170
|
+
session_authenticator.rb [deleted]
|
|
171
|
+
models/
|
|
172
|
+
user.rb [modified]
|
|
173
|
+
refresh_token.rb [new]
|
|
174
|
+
services/
|
|
175
|
+
token_service.rb [new]
|
|
176
|
+
token_blacklist_service.rb [new]
|
|
177
|
+
config/
|
|
178
|
+
initializers/
|
|
179
|
+
jwt.rb [new]
|
|
180
|
+
session_store.rb [deleted]
|
|
181
|
+
routes.rb [modified]
|
|
182
|
+
spec/
|
|
183
|
+
services/
|
|
184
|
+
token_service_spec.rb [new]
|
|
185
|
+
token_blacklist_service_spec.rb [new]
|
|
186
|
+
middleware/
|
|
187
|
+
jwt_authenticator_spec.rb [new]
|
|
188
|
+
requests/
|
|
189
|
+
auth_spec.rb [modified]
|
|
190
|
+
refresh_spec.rb [new]
|
|
191
|
+
lib/
|
|
192
|
+
jwt_encoder.rb [new]
|
|
193
|
+
TREE
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# -- Before / After comparison -----------------------------------------------
|
|
197
|
+
div id: "auth-before-after" do
|
|
198
|
+
header2 "Authentication Diff"
|
|
199
|
+
|
|
200
|
+
comparison(before_label: "Session Auth (before)", after_label: "JWT Auth (after)") do
|
|
201
|
+
before do
|
|
202
|
+
code_block <<~RUBY, lang: "ruby", file: "app/controllers/application_controller.rb"
|
|
203
|
+
class ApplicationController < ActionController::Base
|
|
204
|
+
before_action :require_login
|
|
205
|
+
|
|
206
|
+
private
|
|
207
|
+
|
|
208
|
+
def require_login
|
|
209
|
+
unless session[:user_id]
|
|
210
|
+
redirect_to login_path
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
def current_user
|
|
215
|
+
@current_user ||= User.find(session[:user_id])
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
RUBY
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
after do
|
|
222
|
+
code_block <<~RUBY, lang: "ruby", file: "app/controllers/api/base_controller.rb"
|
|
223
|
+
class Api::BaseController < ActionController::API
|
|
224
|
+
before_action :authenticate!
|
|
225
|
+
|
|
226
|
+
private
|
|
227
|
+
|
|
228
|
+
def authenticate!
|
|
229
|
+
token = extract_token(request)
|
|
230
|
+
payload = TokenService.decode(token)
|
|
231
|
+
raise JWT::DecodeError if TokenBlacklistService.revoked?(payload["jti"])
|
|
232
|
+
@current_user = User.find(payload["sub"])
|
|
233
|
+
rescue JWT::DecodeError, JWT::ExpiredSignature => e
|
|
234
|
+
render json: { error: "Unauthorized" }, status: :unauthorized
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
def extract_token(request)
|
|
238
|
+
request.headers["Authorization"]&.split(" ")&.last
|
|
239
|
+
end
|
|
240
|
+
end
|
|
241
|
+
RUBY
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
# -- Middleware code block ---------------------------------------------------
|
|
247
|
+
div id: "middleware" do
|
|
248
|
+
header2 "New Middleware: JWT Authenticator"
|
|
249
|
+
|
|
250
|
+
code_block <<~RUBY, lang: "ruby", file: "app/middleware/jwt_authenticator.rb"
|
|
251
|
+
# frozen_string_literal: true
|
|
252
|
+
|
|
253
|
+
class JwtAuthenticator
|
|
254
|
+
EXCLUDED_PATHS = %w[/auth/login /auth/refresh /health].freeze
|
|
255
|
+
|
|
256
|
+
def initialize(app)
|
|
257
|
+
@app = app
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
def call(env)
|
|
261
|
+
request = Rack::Request.new(env)
|
|
262
|
+
return @app.call(env) if skip_auth?(request.path)
|
|
263
|
+
|
|
264
|
+
token = extract_bearer_token(env)
|
|
265
|
+
return unauthorized_response unless token
|
|
266
|
+
|
|
267
|
+
payload = TokenService.decode(token)
|
|
268
|
+
return unauthorized_response unless payload
|
|
269
|
+
return unauthorized_response if TokenBlacklistService.revoked?(payload["jti"])
|
|
270
|
+
|
|
271
|
+
env["jwt.payload"] = payload
|
|
272
|
+
env["jwt.user_id"] = payload["sub"]
|
|
273
|
+
@app.call(env)
|
|
274
|
+
rescue JWT::ExpiredSignature
|
|
275
|
+
expired_response
|
|
276
|
+
rescue JWT::DecodeError
|
|
277
|
+
unauthorized_response
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
private
|
|
281
|
+
|
|
282
|
+
def skip_auth?(path)
|
|
283
|
+
EXCLUDED_PATHS.any? { |p| path.start_with?(p) }
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
def extract_bearer_token(env)
|
|
287
|
+
auth = env["HTTP_AUTHORIZATION"]
|
|
288
|
+
return nil unless auth&.start_with?("Bearer ")
|
|
289
|
+
auth.split(" ", 2).last
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
def unauthorized_response
|
|
293
|
+
[401, { "Content-Type" => "application/json" },
|
|
294
|
+
['{"error":"Unauthorized","code":"invalid_token"}']]
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
def expired_response
|
|
298
|
+
[401, { "Content-Type" => "application/json" },
|
|
299
|
+
['{"error":"Token expired","code":"token_expired"}']]
|
|
300
|
+
end
|
|
301
|
+
end
|
|
302
|
+
RUBY
|
|
303
|
+
|
|
304
|
+
flow_arrow label: "Token lifecycle"
|
|
305
|
+
|
|
306
|
+
code_block <<~RUBY, lang: "ruby", file: "app/services/token_service.rb", truncate: 20
|
|
307
|
+
# frozen_string_literal: true
|
|
308
|
+
|
|
309
|
+
class TokenService
|
|
310
|
+
ACCESS_TTL = 15.minutes
|
|
311
|
+
REFRESH_TTL = 7.days
|
|
312
|
+
|
|
313
|
+
class << self
|
|
314
|
+
def issue(user)
|
|
315
|
+
jti = SecureRandom.uuid
|
|
316
|
+
access_token = encode(
|
|
317
|
+
sub: user.id,
|
|
318
|
+
jti: jti,
|
|
319
|
+
exp: ACCESS_TTL.from_now.to_i,
|
|
320
|
+
roles: user.roles.pluck(:name)
|
|
321
|
+
)
|
|
322
|
+
refresh_token = RefreshToken.create!(
|
|
323
|
+
user: user,
|
|
324
|
+
token: SecureRandom.hex(32),
|
|
325
|
+
expires_at: REFRESH_TTL.from_now
|
|
326
|
+
)
|
|
327
|
+
{ access_token: access_token, refresh_token: refresh_token.token }
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
def decode(token)
|
|
331
|
+
JWT.decode(token, secret_key, true, algorithm: "HS256").first
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
def revoke(jti, exp)
|
|
335
|
+
TokenBlacklistService.revoke(jti, ttl: exp - Time.now.to_i)
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
private
|
|
339
|
+
|
|
340
|
+
def encode(payload)
|
|
341
|
+
JWT.encode(payload, secret_key, "HS256")
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
def secret_key
|
|
345
|
+
Rails.application.credentials.jwt_secret_key
|
|
346
|
+
end
|
|
347
|
+
end
|
|
348
|
+
end
|
|
349
|
+
RUBY
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
# -- CI Pipeline visualization -----------------------------------------------
|
|
353
|
+
div id: "pipeline" do
|
|
354
|
+
header2 "CI Pipeline Status"
|
|
355
|
+
|
|
356
|
+
pipeline steps: [
|
|
357
|
+
{ label: "Lint", description: "RuboCop + ESLint", status: :complete },
|
|
358
|
+
{ label: "Unit Tests", description: "RSpec (412 examples)", status: :complete },
|
|
359
|
+
{ label: "Integration", description: "Request specs + JWT flow", status: :complete },
|
|
360
|
+
{ label: "Security Scan", description: "Brakeman + bundler-audit", status: :active },
|
|
361
|
+
{ label: "Staging Deploy", description: "Auto-deploy to staging", status: :pending },
|
|
362
|
+
{ label: "QA Sign-off", description: "Manual verification", status: :pending }
|
|
363
|
+
]
|
|
364
|
+
end
|
|
365
|
+
|
|
366
|
+
# -- Callouts (all variants) -------------------------------------------------
|
|
367
|
+
div id: "risks" do
|
|
368
|
+
header2 "Risk Analysis"
|
|
369
|
+
|
|
370
|
+
callout(variant: :error, title: "Critical: Token Revocation Gap") do
|
|
371
|
+
md <<~MD
|
|
372
|
+
Access tokens remain valid for up to **15 minutes** after a user is
|
|
373
|
+
deactivated or their password is changed. The blacklist check mitigates
|
|
374
|
+
this for explicit logouts, but admin-initiated deactivation does not
|
|
375
|
+
currently trigger token revocation.
|
|
376
|
+
|
|
377
|
+
**Recommendation:** Add an `after_update` callback on `User` that
|
|
378
|
+
blacklists all active JTIs when `deactivated_at` changes.
|
|
379
|
+
MD
|
|
380
|
+
end
|
|
381
|
+
|
|
382
|
+
callout(variant: :warning, title: "Refresh Token Rotation") do
|
|
383
|
+
md <<~MD
|
|
384
|
+
The current implementation does **not** rotate refresh tokens on use.
|
|
385
|
+
A stolen refresh token grants persistent access for the full 7-day window.
|
|
386
|
+
|
|
387
|
+
**Recommendation:** Implement refresh token rotation -- issue a new
|
|
388
|
+
refresh token on each `/auth/refresh` call and invalidate the old one.
|
|
389
|
+
Detect token reuse as a sign of compromise and revoke the entire family.
|
|
390
|
+
MD
|
|
391
|
+
end
|
|
392
|
+
|
|
393
|
+
callout(variant: :info, title: "CORS Configuration") do
|
|
394
|
+
md <<~MD
|
|
395
|
+
The JWT-based API will be consumed by the mobile app and potentially
|
|
396
|
+
third-party integrations. Ensure `Access-Control-Allow-Headers` includes
|
|
397
|
+
`Authorization` and that the CORS origin whitelist is properly configured
|
|
398
|
+
in `config/initializers/cors.rb`.
|
|
399
|
+
MD
|
|
400
|
+
end
|
|
401
|
+
|
|
402
|
+
callout(variant: :success, title: "Improved Horizontal Scalability") do
|
|
403
|
+
md <<~MD
|
|
404
|
+
With session state removed from Redis, the application can now be
|
|
405
|
+
load-balanced across any number of instances without sticky sessions.
|
|
406
|
+
Token verification is purely CPU-bound (HMAC-SHA256), enabling true
|
|
407
|
+
stateless horizontal scaling.
|
|
408
|
+
MD
|
|
409
|
+
end
|
|
410
|
+
|
|
411
|
+
callout(variant: :tip, title: "Future Enhancement: Key Rotation") do
|
|
412
|
+
md <<~MD
|
|
413
|
+
Consider implementing JWT signing key rotation using JWK Sets (JWKS).
|
|
414
|
+
This enables zero-downtime key rotation and allows token verification
|
|
415
|
+
by external services without sharing the secret key.
|
|
416
|
+
MD
|
|
417
|
+
end
|
|
418
|
+
end
|
|
419
|
+
|
|
420
|
+
# -- Test breakdown chart ----------------------------------------------------
|
|
421
|
+
chart type: :bar, data: {
|
|
422
|
+
labels: [
|
|
423
|
+
"TokenService", "JwtAuthenticator", "AuthController",
|
|
424
|
+
"RefreshToken", "Blacklist", "Integration"
|
|
425
|
+
],
|
|
426
|
+
datasets: [
|
|
427
|
+
{
|
|
428
|
+
label: "Tests Added",
|
|
429
|
+
data: [18, 14, 12, 8, 6, 24],
|
|
430
|
+
backgroundColor: "#22c55e"
|
|
431
|
+
},
|
|
432
|
+
{
|
|
433
|
+
label: "Tests Modified",
|
|
434
|
+
data: [0, 0, 8, 3, 0, 12],
|
|
435
|
+
backgroundColor: "#f59e0b"
|
|
436
|
+
},
|
|
437
|
+
{
|
|
438
|
+
label: "Tests Removed",
|
|
439
|
+
data: [0, 0, 6, 0, 0, 4],
|
|
440
|
+
backgroundColor: "#ef4444"
|
|
441
|
+
}
|
|
442
|
+
]
|
|
443
|
+
}, options: {
|
|
444
|
+
plugins: {
|
|
445
|
+
title: { display: true, text: "Test Suite Changes by Component" }
|
|
446
|
+
},
|
|
447
|
+
scales: {
|
|
448
|
+
x: { stacked: true },
|
|
449
|
+
y: { stacked: true, title: { display: true, text: "Number of Tests" } }
|
|
450
|
+
}
|
|
451
|
+
}, height: 300
|
|
452
|
+
|
|
453
|
+
# -- Verdict -----------------------------------------------------------------
|
|
454
|
+
div id: "verdict" do
|
|
455
|
+
header2 "Review Verdict"
|
|
456
|
+
|
|
457
|
+
hstack spacing: :md, align: :center do
|
|
458
|
+
status_dot status: :yellow, pulse: true
|
|
459
|
+
header3 "Request Changes"
|
|
460
|
+
end
|
|
461
|
+
|
|
462
|
+
prose do
|
|
463
|
+
md <<~MARKDOWN
|
|
464
|
+
The architectural approach is solid and well-structured. The separation
|
|
465
|
+
of concerns between `TokenService`, `TokenBlacklistService`, and the
|
|
466
|
+
`JwtAuthenticator` middleware is clean and testable. Test coverage has
|
|
467
|
+
improved meaningfully.
|
|
468
|
+
|
|
469
|
+
However, **two blocking issues** must be resolved:
|
|
470
|
+
|
|
471
|
+
1. **Token revocation on user deactivation** -- admin-initiated account
|
|
472
|
+
disabling must immediately invalidate all outstanding tokens.
|
|
473
|
+
2. **Refresh token rotation** -- the current implementation is vulnerable
|
|
474
|
+
to refresh token theft without rotation and reuse detection.
|
|
475
|
+
|
|
476
|
+
Additionally, the CORS configuration should be verified in a staging
|
|
477
|
+
environment before merging.
|
|
478
|
+
MARKDOWN
|
|
479
|
+
end
|
|
480
|
+
|
|
481
|
+
pullquote(
|
|
482
|
+
"The bones are excellent. Fix the revocation gap and token rotation, and this ships.",
|
|
483
|
+
attribution: "AI Review Agent v2.4"
|
|
484
|
+
)
|
|
485
|
+
|
|
486
|
+
# -- Score summary table ---------------------------------------------------
|
|
487
|
+
header3 "Review Scores"
|
|
488
|
+
score_table scores: [
|
|
489
|
+
{ label: "Code Quality", score: 9, max: 10 },
|
|
490
|
+
{ label: "Test Coverage", score: 8, max: 10 },
|
|
491
|
+
{ label: "Security Posture", score: 6, max: 10 },
|
|
492
|
+
{ label: "Architecture", score: 9, max: 10 },
|
|
493
|
+
{ label: "Documentation", score: 7, max: 10 },
|
|
494
|
+
{ label: "Migration Safety", score: 7, max: 10 }
|
|
495
|
+
]
|
|
496
|
+
end
|
|
497
|
+
end
|
|
498
|
+
|
|
499
|
+
ExplainerApp.run! if __FILE__ == $0
|
data/exe/streamweaver
ADDED
data/gsd/ROADMAP-1.0.md
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
# StreamWeaver 1.0 Roadmap — The Pareto Production Plan
|
|
2
|
+
|
|
3
|
+
Synthesized 2026-07-02 from three research inputs (all in `gsd/research/`):
|
|
4
|
+
`repo-audit-1.0.md` (facts on the ground), `production-patterns-research.md`
|
|
5
|
+
(proven minimal mechanisms from Streamlit/Rails-Solid/LiveView/Roda), and
|
|
6
|
+
`market-positioning-research.md` (the market moment + site/video strategy).
|
|
7
|
+
Tracked as beads epic `stream_weaver-b9g`.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## The Thesis
|
|
12
|
+
|
|
13
|
+
StreamWeaver's moment: the industry is standardizing on **rich HTML as the
|
|
14
|
+
agent-to-human surface** (Anthropic's "Unreasonable Effectiveness of HTML"
|
|
15
|
+
post, May 2026; the ratified MCP Apps standard, Jan 2026 — shipped in Claude,
|
|
16
|
+
ChatGPT, Goose, VS Code) while openly conceding the token cost. Independent
|
|
17
|
+
benchmarks put HTML at **4-8x markdown tokens**. StreamWeaver's claim:
|
|
18
|
+
**markdown-level token cost, HTML-artifact richness, real-time push**.
|
|
19
|
+
|
|
20
|
+
Positioning rule: do NOT say "10x cheaper" until we publish our own benchmark
|
|
21
|
+
(bench script comparing StreamWeaver DSL vs equivalent raw HTML vs markdown
|
|
22
|
+
for the same rendered UI). The independent 4-8x figure is citable; our own
|
|
23
|
+
numbers become the headline.
|
|
24
|
+
|
|
25
|
+
Canvas mode is the flagship of this story — it replaces both the Chrome-based
|
|
26
|
+
visual companion and token-heavy HTML artifacts with a persistent, push-driven,
|
|
27
|
+
interactive surface. 1.0 messaging leads with canvas, not with "Streamlit for
|
|
28
|
+
Ruby."
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## The Four Modes (and the "where to use what" answer)
|
|
33
|
+
|
|
34
|
+
The audit confirms four legit modes plus a deployment wrapper. The 1.0 docs
|
|
35
|
+
must open with this table — it IS the "where to use what" doc Forrest wants:
|
|
36
|
+
|
|
37
|
+
| Mode | Reach for it when | Maturity gap for 1.0 |
|
|
38
|
+
|---|---|---|
|
|
39
|
+
| Standalone script (`ruby app.rb`) | Quick helper UI, one file, one user | None — this is done |
|
|
40
|
+
| Agentic (`run_once!`) | Agent needs one answer from a human | None |
|
|
41
|
+
| Service mode (`streamweaver run`) | Several apps, one server, lives all day | Hash URLs (fix exists, unwired) |
|
|
42
|
+
| Canvas push (`streamweaver panel` / `canvas-push`) | Agent pushes live UI to a human mid-session | Crash recovery, SSE replay, bridge_server specs |
|
|
43
|
+
| Puma-dev wrapper | Memorable `myapp.test` URLs on your Mac | Docs only |
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Tier 0 — Release Blockers (mechanical, do first)
|
|
48
|
+
|
|
49
|
+
These are why `gem install stream_weaver` doesn't work for anyone but Forrest.
|
|
50
|
+
|
|
51
|
+
1. **Unblock the `iterm2_ruby` dependency** — it's a runtime dep pointed at
|
|
52
|
+
`~/work/iterm2_ruby` (Gemfile:9). Either publish `iterm2_ruby` to
|
|
53
|
+
rubygems.org or demote it to an optional/soft dependency (rescue
|
|
54
|
+
LoadError; iTerm panel features degrade gracefully). Hard blocker.
|
|
55
|
+
2. **Version/changelog hygiene** — VERSION says 0.1.1, CHANGELOG has only ever
|
|
56
|
+
cut 0.1.0 with ~200 lines sitting in `[Unreleased]`. Cut 0.2.0 honestly,
|
|
57
|
+
then version toward 1.0.
|
|
58
|
+
3. **CI** — no `.github/workflows/` at all. Add rspec matrix (Ruby 3.3/3.4)
|
|
59
|
+
+ rubocop. The 2,090-example suite runs in ~5s; this is cheap insurance.
|
|
60
|
+
4. **Repo hygiene** — add `dist/` to `.gitignore` (2.7MB one `git add -A` from
|
|
61
|
+
disaster), remove stray root `.gem` files, reconcile the tracked-but-
|
|
62
|
+
supposedly-ignored `docs/superpowers/` files.
|
|
63
|
+
|
|
64
|
+
## Tier 1 — The Production 20% (the mechanisms)
|
|
65
|
+
|
|
66
|
+
Ordered by leverage-per-effort. Items 5 and 7 are the "never rewrite in
|
|
67
|
+
Sinatra again" fixes.
|
|
68
|
+
|
|
69
|
+
5. **Human-readable multi-app URLs** — smallest, highest-leverage fix in the
|
|
70
|
+
repo. `aliased_path_for` (service.rb:186) already builds `/:source/:name`
|
|
71
|
+
routes; the CLI just never passes `source:` (cli.rb:123-166). Wire a slug
|
|
72
|
+
derived from the app's declared name (fallback: filename) into the default
|
|
73
|
+
`streamweaver run` path: `/apps/sales-dashboard`, hash kept as canonical
|
|
74
|
+
fallback. This single change revives the whole multi-app feature.
|
|
75
|
+
6. **SessionStore story** — the cookie-overflow trauma is already 80% solved
|
|
76
|
+
(file store is default, 4KB ceiling gone). Finish it: (a) make the legacy
|
|
77
|
+
cookie store raise loudly on overflow instead of silently dropping keys,
|
|
78
|
+
(b) add a SQLite-backed store behind the same `SW_SESSION_STORE` switch as
|
|
79
|
+
the service-mode/multi-instance default (Rails 8 "Solid" philosophy — DB,
|
|
80
|
+
not Redis), (c) document the graduation path in one page.
|
|
81
|
+
7. **Escape hatches** — adopt Streamlit's graduated model, stop at two:
|
|
82
|
+
(a) a `raw_html` / iframe component for arbitrary markup, and
|
|
83
|
+
(b) an `endpoint` DSL (`app.get "/webhook" { |req| ... }` style) that
|
|
84
|
+
registers raw Rack endpoints inside the same app — webhooks, JSON APIs,
|
|
85
|
+
file downloads without leaving StreamWeaver. Add multipart file-upload
|
|
86
|
+
support to the form layer. Skip a bidirectional custom-component protocol
|
|
87
|
+
until demand exists.
|
|
88
|
+
8. **Canvas production hardening** — canvas is a shipped skill's dependency,
|
|
89
|
+
so the bar is higher: (a) persist bridge sessions to disk so a crash
|
|
90
|
+
doesn't lose open canvases (LiveView's "durable recipe" lesson), (b) SSE
|
|
91
|
+
monotonic event `id:` + `Last-Event-ID` replay + `retry:` backoff (the
|
|
92
|
+
classic silent-drop bug), (c) validate theme/layout wire values against
|
|
93
|
+
the registry, (d) a dedicated spec file for `bridge_server.rb` (1,111
|
|
94
|
+
lines, zero direct specs today).
|
|
95
|
+
9. **Security posture for 1.0 (docs + defaults, not systems)** — keep
|
|
96
|
+
localhost-bind default and document it as a feature; opt-in CSRF for
|
|
97
|
+
auto-submit forms (Roda `route_csrf` as the model); one documented
|
|
98
|
+
middleware slot with a Basic Auth example; one Kamal-ready Dockerfile
|
|
99
|
+
example. Do not build an auth system.
|
|
100
|
+
|
|
101
|
+
## Tier 2 — Adoption Surface (docs, site, tutorials, videos)
|
|
102
|
+
|
|
103
|
+
10. **docs/ cleanup** — 73 files, ~9 genuinely user-facing. Archive the
|
|
104
|
+
planning/session logs (`docs/plans`, `docs/visual-skills`, spikes) out of
|
|
105
|
+
the shipping tree; delete or fix `canvas-ipc-session-summary.md`, which
|
|
106
|
+
actively contradicts the current architecture. Merge `llms.txt` /
|
|
107
|
+
`docs/for_llms.md` overlap.
|
|
108
|
+
11. **Tutorial revamp** — the wired-in tutorial predates ~6 months of
|
|
109
|
+
features: zero coverage of canvas/panel, resource DSL, navbar, `:doc`
|
|
110
|
+
theme, theme_switcher, card_header. Restructure as one lesson per mode
|
|
111
|
+
(the four-modes table above becomes the tutorial's spine), and make it
|
|
112
|
+
the first-outside-tester onboarding path — their first session is the acceptance
|
|
113
|
+
test.
|
|
114
|
+
12. **Website (charm-ruby.dev pattern)** — shallow 4-item nav; sell with
|
|
115
|
+
runnable code not prose; component cards grouped by function; and the
|
|
116
|
+
charm trick applied literally: the site's visuals ARE StreamWeaver output
|
|
117
|
+
(dogfood the `:doc` theme — the site is a StreamWeaver app or its static
|
|
118
|
+
export). Hero = the token-cost benchmark, side-by-side with the same UI
|
|
119
|
+
as raw HTML.
|
|
120
|
+
13. **Demo videos (pareto stack)** — VHS `.tape` files for terminal demos +
|
|
121
|
+
Playwright 1.59 screencast for "agent pushes to canvas live" captures +
|
|
122
|
+
ElevenLabs voiceover + Descript assembly. Entire pipeline scriptable:
|
|
123
|
+
`bin/demos` regenerates every asset. Skip Remotion/asciinema for v1.
|
|
124
|
+
14. **README restructure** — install-first, pitch second (current order
|
|
125
|
+
inverted). Keep the philosophy essay, move it below Quick Start.
|
|
126
|
+
15. **Token benchmark** — small script + published table: same UI in
|
|
127
|
+
StreamWeaver DSL vs raw HTML vs markdown-equivalent fidelity. This is
|
|
128
|
+
the marketing headline AND a regression guard on DSL verbosity.
|
|
129
|
+
16. **Opal directive — copyable example + docs** — the Opal mode (Phases
|
|
130
|
+
2a+2b done; 2c ReactiveState and 2d Mermaid/ChartJS queued) becomes
|
|
131
|
+
useful at 1.0 if a newcomer can copy one polished example, modify it,
|
|
132
|
+
and ship — pick/finish one compelling scenario (the `dist/` demos are
|
|
133
|
+
the seed), document the `opal-build` workflow end to end, and cross-link
|
|
134
|
+
it from the four-modes table as the "client-side Ruby" option.
|
|
135
|
+
17. **Opal vs React benchmark** — extends item 15 with two more axes:
|
|
136
|
+
(a) authoring token cost — same app written via StreamWeaver+Opal vs
|
|
137
|
+
idiomatic React (an LLM writes both; count tokens), and (b) library
|
|
138
|
+
load — bytes shipped to the browser (Opal runtime + morphdom + marked
|
|
139
|
+
vs react + react-dom + build-toolchain output). Together with item 15
|
|
140
|
+
this becomes one benchmark suite: tokens-to-author, bytes-to-load,
|
|
141
|
+
fidelity delivered.
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
## Triumvirate Check (applies to items 10-14)
|
|
146
|
+
|
|
147
|
+
| Law | Application |
|
|
148
|
+
|---|---|
|
|
149
|
+
| Matt's Law (find it, digest it) | Four-modes table opens every doc surface; shallow nav; one decision guide instead of 73 files |
|
|
150
|
+
| Forrest's Law (zero friction + perks) | `gem install` just works (Tier 0); memorable URLs by default (item 5); value shown not discovered — site leads with live output and the benchmark |
|
|
151
|
+
| Gloria's Law (design for the brain you have) | The site/docs must be as attractive as the `:doc` theme output; videos over walls of text; the want-to threshold is functional, not decorative |
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## The 20% of the 20% — Buildable Immediately
|
|
156
|
+
|
|
157
|
+
In order, each independently shippable:
|
|
158
|
+
|
|
159
|
+
1. Tier 0 complete (deps/version/CI/hygiene) → `gem install` works, 0.2.0 cut
|
|
160
|
+
2. Item 5: slug URLs wired into `streamweaver run`
|
|
161
|
+
3. Item 7b: `endpoint` DSL escape hatch
|
|
162
|
+
4. Item 8a+8b: canvas crash recovery + SSE replay
|
|
163
|
+
5. Item 11: tutorial revamp → first-outside-tester onboarding
|
|
164
|
+
6. Item 15: token benchmark → unlocks honest positioning copy
|
|
165
|
+
|
|
166
|
+
Everything else (site, videos, SQLite store, CSRF) sequences behind these
|
|
167
|
+
without blocking them.
|
data/gsd/STATE.md
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Implementation State
|
|
2
|
+
|
|
3
|
+
## Current Wave: 1
|
|
4
|
+
## Status: DONE
|
|
5
|
+
|
|
6
|
+
| Task | Status | Commit | Notes |
|
|
7
|
+
|------|--------|--------|-------|
|
|
8
|
+
| T1 | DONE | 34ea6cc | Routing chain + server.rb guards |
|
|
9
|
+
| T2 | DONE | 65d477c | ResourceDefinition + App#resource/page/route + named-route helpers |
|
|
10
|
+
| T3 | DONE | 29b991e | DefaultViews + store validation + override blocks (T4 collapsed) |
|
|
11
|
+
| T4 | DONE | 29b991e | Collapsed into T3 — render_action save/restore implemented |
|
|
12
|
+
| T5 | DONE | 6a89742 | blog.rb + utf_lite.rb scaffolding examples |
|
|
13
|
+
| T6 | DONE | d64c861 | lib requires verified, resource-dsl.md, for_llms.md + crud-patterns.md updated |
|