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,417 @@
|
|
|
1
|
+
# PLAN: shadcn Polish for StreamWeaver
|
|
2
|
+
|
|
3
|
+
**Goal:** Get shadcn/ui-level visual polish in StreamWeaver components, with optional Tailwind mode for full fidelity.
|
|
4
|
+
|
|
5
|
+
**Reference:** `docs/ruby-ui-comparison.md` for full gap analysis.
|
|
6
|
+
**RubyUI source:** `~/work/ruby_ui/` (gem), `~/work/ruby_ui_web/` (docs/CSS)
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Phase 1: shadcn Token Layer + Dark Mode
|
|
11
|
+
|
|
12
|
+
**Outcome:** All existing components gain consistent colors, dark mode, and visual polish. Zero breaking changes.
|
|
13
|
+
|
|
14
|
+
### 1.1 Create `shadcn_tokens.css`
|
|
15
|
+
|
|
16
|
+
Add to StreamWeaver's base CSS output. This defines the semantic color system that both pure CSS and Tailwind modes will share.
|
|
17
|
+
|
|
18
|
+
**Location:** `lib/stream_weaver/assets/shadcn_tokens.css`
|
|
19
|
+
|
|
20
|
+
```css
|
|
21
|
+
:root {
|
|
22
|
+
--background: oklch(1 0 0);
|
|
23
|
+
--foreground: oklch(0.145 0 0);
|
|
24
|
+
--card: oklch(1 0 0);
|
|
25
|
+
--card-foreground: oklch(0.145 0 0);
|
|
26
|
+
--popover: oklch(1 0 0);
|
|
27
|
+
--popover-foreground: oklch(0.145 0 0);
|
|
28
|
+
--primary: oklch(0.205 0 0);
|
|
29
|
+
--primary-foreground: oklch(0.985 0 0);
|
|
30
|
+
--secondary: oklch(0.97 0 0);
|
|
31
|
+
--secondary-foreground: oklch(0.205 0 0);
|
|
32
|
+
--muted: oklch(0.97 0 0);
|
|
33
|
+
--muted-foreground: oklch(0.556 0 0);
|
|
34
|
+
--accent: oklch(0.97 0 0);
|
|
35
|
+
--accent-foreground: oklch(0.205 0 0);
|
|
36
|
+
--destructive: oklch(0.577 0.245 27.325);
|
|
37
|
+
--destructive-foreground: #fff;
|
|
38
|
+
--border: oklch(0.922 0 0);
|
|
39
|
+
--input: oklch(0.922 0 0);
|
|
40
|
+
--ring: oklch(0.708 0 0);
|
|
41
|
+
--radius: 0.5rem;
|
|
42
|
+
--warning: hsl(38 92% 50%);
|
|
43
|
+
--warning-foreground: #fff;
|
|
44
|
+
--success: hsl(87 100% 37%);
|
|
45
|
+
--success-foreground: #fff;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.dark {
|
|
49
|
+
--background: oklch(0.145 0 0);
|
|
50
|
+
--foreground: oklch(0.985 0 0);
|
|
51
|
+
--card: oklch(0.205 0 0);
|
|
52
|
+
--card-foreground: oklch(0.985 0 0);
|
|
53
|
+
--popover: oklch(0.205 0 0);
|
|
54
|
+
--popover-foreground: oklch(0.985 0 0);
|
|
55
|
+
--primary: oklch(0.922 0 0);
|
|
56
|
+
--primary-foreground: oklch(0.205 0 0);
|
|
57
|
+
--secondary: oklch(0.269 0 0);
|
|
58
|
+
--secondary-foreground: oklch(0.985 0 0);
|
|
59
|
+
--muted: oklch(0.269 0 0);
|
|
60
|
+
--muted-foreground: oklch(0.708 0 0);
|
|
61
|
+
--accent: oklch(0.269 0 0);
|
|
62
|
+
--accent-foreground: oklch(0.985 0 0);
|
|
63
|
+
--destructive: oklch(0.704 0.191 22.216);
|
|
64
|
+
--destructive-foreground: oklch(0.637 0.237 25.331);
|
|
65
|
+
--border: oklch(1 0 0 / 10%);
|
|
66
|
+
--input: oklch(1 0 0 / 15%);
|
|
67
|
+
--ring: oklch(0.556 0 0);
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
**Integration with existing Theme system:**
|
|
72
|
+
|
|
73
|
+
Update `Theme::VARIABLE_SCHEMA` to map existing `--sw-` vars to shadcn tokens. When a StreamWeaver theme sets `color_primary`, it should also update `--primary`. This can be done in the CSS output:
|
|
74
|
+
|
|
75
|
+
```css
|
|
76
|
+
:root {
|
|
77
|
+
--primary: var(--sw-color-primary, oklch(0.205 0 0));
|
|
78
|
+
--background: var(--sw-color-bg, oklch(1 0 0));
|
|
79
|
+
/* etc — shadcn tokens fall back to SW vars if set, else default */
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### 1.2 Create `shadcn_base.css`
|
|
84
|
+
|
|
85
|
+
Global base styles that give everything the shadcn feel.
|
|
86
|
+
|
|
87
|
+
**Location:** `lib/stream_weaver/assets/shadcn_base.css`
|
|
88
|
+
|
|
89
|
+
```css
|
|
90
|
+
/* Base reset for shadcn consistency */
|
|
91
|
+
*, *::before, *::after {
|
|
92
|
+
border-color: var(--border);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
body {
|
|
96
|
+
background-color: var(--background);
|
|
97
|
+
color: var(--foreground);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/* Shared interactive states */
|
|
101
|
+
.sw-interactive {
|
|
102
|
+
transition: color 0.15s ease, background-color 0.15s ease,
|
|
103
|
+
border-color 0.15s ease, box-shadow 0.15s ease;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.sw-interactive:focus-visible {
|
|
107
|
+
outline: none;
|
|
108
|
+
box-shadow: 0 0 0 2px var(--background), 0 0 0 4px var(--ring);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.sw-interactive:disabled,
|
|
112
|
+
.sw-interactive[aria-disabled="true"] {
|
|
113
|
+
opacity: 0.5;
|
|
114
|
+
pointer-events: none;
|
|
115
|
+
cursor: not-allowed;
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### 1.3 Update Component CSS
|
|
120
|
+
|
|
121
|
+
Migrate existing component styles from hardcoded colors to semantic tokens. Example diffs:
|
|
122
|
+
|
|
123
|
+
**Button (before):**
|
|
124
|
+
```css
|
|
125
|
+
.sw-button { background: var(--sw-color-primary); color: #fff; border-radius: var(--sw-radius-md); }
|
|
126
|
+
.sw-button:hover { background: var(--sw-color-primary-hover); }
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
**Button (after):**
|
|
130
|
+
```css
|
|
131
|
+
.sw-button {
|
|
132
|
+
display: inline-flex; align-items: center; justify-content: center;
|
|
133
|
+
border-radius: var(--radius);
|
|
134
|
+
font-weight: 500; font-size: 0.875rem;
|
|
135
|
+
height: 2.25rem; padding: 0.5rem 1rem;
|
|
136
|
+
transition: color 0.15s, background-color 0.15s;
|
|
137
|
+
}
|
|
138
|
+
.sw-button:focus-visible {
|
|
139
|
+
outline: none;
|
|
140
|
+
box-shadow: 0 0 0 2px var(--background), 0 0 0 4px var(--ring);
|
|
141
|
+
}
|
|
142
|
+
.sw-button:disabled { opacity: 0.5; pointer-events: none; }
|
|
143
|
+
|
|
144
|
+
.sw-button--primary { background: var(--primary); color: var(--primary-foreground); }
|
|
145
|
+
.sw-button--primary:hover { filter: brightness(1.1); }
|
|
146
|
+
|
|
147
|
+
.sw-button--secondary { background: var(--secondary); color: var(--secondary-foreground); }
|
|
148
|
+
.sw-button--secondary:hover { filter: brightness(0.95); }
|
|
149
|
+
|
|
150
|
+
.sw-button--destructive { background: var(--destructive); color: var(--destructive-foreground); }
|
|
151
|
+
.sw-button--destructive:hover { filter: brightness(1.1); }
|
|
152
|
+
|
|
153
|
+
.sw-button--outline {
|
|
154
|
+
background: var(--background); color: var(--foreground);
|
|
155
|
+
border: 1px solid var(--input); box-shadow: 0 1px 2px rgba(0,0,0,0.05);
|
|
156
|
+
}
|
|
157
|
+
.sw-button--outline:hover { background: var(--accent); color: var(--accent-foreground); }
|
|
158
|
+
|
|
159
|
+
.sw-button--ghost { background: transparent; color: var(--foreground); }
|
|
160
|
+
.sw-button--ghost:hover { background: var(--accent); color: var(--accent-foreground); }
|
|
161
|
+
|
|
162
|
+
.sw-button--link { background: transparent; color: var(--primary); text-decoration-line: underline; text-underline-offset: 4px; }
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
**Card (before):**
|
|
166
|
+
```css
|
|
167
|
+
.card { background: var(--sw-color-bg-card); border: 1px solid var(--sw-color-border); border-radius: var(--sw-radius-lg); }
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
**Card (after):**
|
|
171
|
+
```css
|
|
172
|
+
.sw-card {
|
|
173
|
+
background: var(--card); color: var(--card-foreground);
|
|
174
|
+
border: 1px solid var(--border);
|
|
175
|
+
border-radius: calc(var(--radius) + 4px);
|
|
176
|
+
box-shadow: 0 1px 3px rgba(0,0,0,0.08);
|
|
177
|
+
}
|
|
178
|
+
.sw-card-header { padding: 1.5rem 1.5rem 0; display: flex; flex-direction: column; gap: 0.375rem; }
|
|
179
|
+
.sw-card-content { padding: 1.5rem; padding-top: 0; }
|
|
180
|
+
.sw-card-footer { padding: 0 1.5rem 1.5rem; display: flex; align-items: center; }
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
**Apply this pattern to all components:**
|
|
184
|
+
- Alert → `var(--destructive)`, `var(--warning)`, `var(--success)`
|
|
185
|
+
- Badge → variant colors from tokens
|
|
186
|
+
- Input/TextArea → `var(--input)` border, `var(--ring)` focus
|
|
187
|
+
- Select/Dropdown → `var(--popover)` background
|
|
188
|
+
- Modal/Dialog → `var(--popover)` with backdrop
|
|
189
|
+
- Table → `var(--muted)` for header, `var(--border)` for rows
|
|
190
|
+
- Tabs → `var(--muted)` for inactive, `var(--background)` for active
|
|
191
|
+
- Sidebar → `var(--card)` or dedicated sidebar tokens
|
|
192
|
+
|
|
193
|
+
### 1.4 Dark Mode Toggle
|
|
194
|
+
|
|
195
|
+
Update `ThemeSwitcher` component to support dark mode:
|
|
196
|
+
|
|
197
|
+
```ruby
|
|
198
|
+
class ThemeSwitcher < Base
|
|
199
|
+
def render(view, state)
|
|
200
|
+
# Emit a button that toggles .dark class on <html> and persists to localStorage
|
|
201
|
+
view.adapter.render_theme_switcher(view, self, state)
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
Add JS to the adapter output:
|
|
207
|
+
```javascript
|
|
208
|
+
function toggleDarkMode() {
|
|
209
|
+
const html = document.documentElement;
|
|
210
|
+
html.classList.toggle('dark');
|
|
211
|
+
localStorage.setItem('theme', html.classList.contains('dark') ? 'dark' : 'light');
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// On load: respect saved preference or system preference
|
|
215
|
+
(function() {
|
|
216
|
+
if (localStorage.theme === 'dark' ||
|
|
217
|
+
(!('theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
|
|
218
|
+
document.documentElement.classList.add('dark');
|
|
219
|
+
}
|
|
220
|
+
})();
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### 1.5 Verify with Existing Apps
|
|
224
|
+
|
|
225
|
+
Run existing StreamWeaver examples/apps and confirm:
|
|
226
|
+
- Colors look correct with new tokens
|
|
227
|
+
- Dark mode works
|
|
228
|
+
- No visual regressions
|
|
229
|
+
- Existing `register_theme` still works (SW vars feed into shadcn tokens)
|
|
230
|
+
|
|
231
|
+
---
|
|
232
|
+
|
|
233
|
+
## Phase 2: Extract Adapter Pattern
|
|
234
|
+
|
|
235
|
+
**Outcome:** Clean separation between component logic (DSL) and rendering (adapter).
|
|
236
|
+
|
|
237
|
+
### 2.1 Formalize BaseAdapter
|
|
238
|
+
|
|
239
|
+
Ensure all component rendering goes through adapter methods:
|
|
240
|
+
|
|
241
|
+
```ruby
|
|
242
|
+
module StreamWeaver
|
|
243
|
+
class BaseAdapter
|
|
244
|
+
def render_button(view, component, state); end
|
|
245
|
+
def render_card(view, component, state); end
|
|
246
|
+
def render_input(view, component, state); end
|
|
247
|
+
# ... one method per component
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
### 2.2 Rename Current Adapter → PureCSSAdapter
|
|
253
|
+
|
|
254
|
+
The existing AlpineJS adapter becomes `PureCSSAdapter` (or `AlpineJSAdapter` stays but internally uses pure CSS classes).
|
|
255
|
+
|
|
256
|
+
### 2.3 Configuration
|
|
257
|
+
|
|
258
|
+
```ruby
|
|
259
|
+
StreamWeaver.configure do |config|
|
|
260
|
+
config.adapter = :alpine_js # default, uses pure CSS classes
|
|
261
|
+
# or
|
|
262
|
+
config.adapter = :tailwind # uses Tailwind utility classes
|
|
263
|
+
end
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
---
|
|
267
|
+
|
|
268
|
+
## Phase 3: Tailwind Adapter
|
|
269
|
+
|
|
270
|
+
**Outcome:** Full shadcn fidelity when Tailwind is available.
|
|
271
|
+
|
|
272
|
+
### 3.1 Add `tailwind_merge` Gem (Optional Dependency)
|
|
273
|
+
|
|
274
|
+
```ruby
|
|
275
|
+
# stream_weaver.gemspec
|
|
276
|
+
spec.add_development_dependency 'tailwind_merge', '~> 1.0'
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
### 3.2 Build TailwindAdapter
|
|
280
|
+
|
|
281
|
+
Port RubyUI's class strings directly. The classes are already defined in `~/work/ruby_ui/lib/ruby_ui/button/button.rb`, etc.
|
|
282
|
+
|
|
283
|
+
```ruby
|
|
284
|
+
module StreamWeaver
|
|
285
|
+
class TailwindAdapter < BaseAdapter
|
|
286
|
+
MERGER = TailwindMerge::Merger.new.freeze
|
|
287
|
+
|
|
288
|
+
BUTTON_BASE = "inline-flex items-center justify-center rounded-md font-medium transition-colors " \
|
|
289
|
+
"focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring " \
|
|
290
|
+
"disabled:pointer-events-none disabled:opacity-50"
|
|
291
|
+
|
|
292
|
+
BUTTON_VARIANTS = {
|
|
293
|
+
primary: "bg-primary text-primary-foreground shadow hover:bg-primary/90",
|
|
294
|
+
secondary: "bg-secondary text-secondary-foreground hover:bg-opacity-80",
|
|
295
|
+
destructive: "bg-destructive text-white shadow-sm hover:bg-destructive/90",
|
|
296
|
+
outline: "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
|
|
297
|
+
ghost: "hover:bg-accent hover:text-accent-foreground",
|
|
298
|
+
link: "text-primary underline-offset-4 hover:underline"
|
|
299
|
+
}.freeze
|
|
300
|
+
|
|
301
|
+
BUTTON_SIZES = {
|
|
302
|
+
sm: "px-3 py-1.5 h-8 text-xs",
|
|
303
|
+
md: "px-4 py-2 h-9 text-sm",
|
|
304
|
+
lg: "px-4 py-2 h-10 text-base",
|
|
305
|
+
xl: "px-6 py-3 h-12 text-base"
|
|
306
|
+
}.freeze
|
|
307
|
+
|
|
308
|
+
def render_button(view, component, state)
|
|
309
|
+
classes = MERGER.merge([
|
|
310
|
+
BUTTON_BASE,
|
|
311
|
+
BUTTON_VARIANTS[component.variant] || BUTTON_VARIANTS[:primary],
|
|
312
|
+
BUTTON_SIZES[component.size] || BUTTON_SIZES[:md],
|
|
313
|
+
component.options[:class]
|
|
314
|
+
].compact.join(" "))
|
|
315
|
+
|
|
316
|
+
view.button(class: classes, type: "button", **data_attrs(component)) do
|
|
317
|
+
yield if block_given?
|
|
318
|
+
end
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
# ... similar for Card, Input, Alert, etc.
|
|
322
|
+
# Port class strings from ~/work/ruby_ui/lib/ruby_ui/*/
|
|
323
|
+
end
|
|
324
|
+
end
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
### 3.3 Tailwind CSS Requirement
|
|
328
|
+
|
|
329
|
+
When using the Tailwind adapter, the output HTML needs Tailwind CSS loaded. Options:
|
|
330
|
+
- CDN link (easiest for development)
|
|
331
|
+
- Bundled Tailwind (for production)
|
|
332
|
+
- User provides their own Tailwind setup
|
|
333
|
+
|
|
334
|
+
The adapter should inject the appropriate `<link>` or `<script>` in the HTML head.
|
|
335
|
+
|
|
336
|
+
Tailwind v4 config needed for shadcn tokens:
|
|
337
|
+
```css
|
|
338
|
+
@theme inline {
|
|
339
|
+
--color-background: var(--background);
|
|
340
|
+
--color-foreground: var(--foreground);
|
|
341
|
+
--color-primary: var(--primary);
|
|
342
|
+
--color-primary-foreground: var(--primary-foreground);
|
|
343
|
+
/* ... */
|
|
344
|
+
}
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
---
|
|
348
|
+
|
|
349
|
+
## Phase 4: Port High-Value Components
|
|
350
|
+
|
|
351
|
+
Port these from RubyUI, adapting for StreamWeaver's DSL pattern:
|
|
352
|
+
|
|
353
|
+
### Priority 1 (most impact):
|
|
354
|
+
| Component | RubyUI Source | DSL Example |
|
|
355
|
+
|-----------|--------------|-------------|
|
|
356
|
+
| **Sheet** | `ruby_ui/sheet/` | `sheet(:right) { text "Details..." }` |
|
|
357
|
+
| **Skeleton** | `ruby_ui/skeleton/` | `skeleton width: "100%", height: "20px"` |
|
|
358
|
+
| **Command** | `ruby_ui/command/` | `command_palette { item "Search...", shortcut: "⌘K" }` |
|
|
359
|
+
| **Clipboard** | `ruby_ui/clipboard/` | `clipboard "code to copy"` |
|
|
360
|
+
|
|
361
|
+
### Priority 2 (nice to have):
|
|
362
|
+
| Component | RubyUI Source | DSL Example |
|
|
363
|
+
|-----------|--------------|-------------|
|
|
364
|
+
| **Combobox** | `ruby_ui/combobox/` | `combobox :user, choices, searchable: true` |
|
|
365
|
+
| **Calendar** | `ruby_ui/calendar/` | `calendar :date, range: true` |
|
|
366
|
+
| **AlertDialog** | `ruby_ui/alert_dialog/` | `alert_dialog "Are you sure?", confirm: "Delete"` |
|
|
367
|
+
| **HoverCard** | `ruby_ui/hover_card/` | `hover_card { text "Preview content" }` |
|
|
368
|
+
|
|
369
|
+
### Implementation Pattern
|
|
370
|
+
|
|
371
|
+
For each component:
|
|
372
|
+
1. Read RubyUI's Phlex component for structure
|
|
373
|
+
2. Define StreamWeaver DSL method (in `components.rb` or new file)
|
|
374
|
+
3. Add `render_*` method to PureCSSAdapter with CSS-variable styling
|
|
375
|
+
4. Add `render_*` method to TailwindAdapter with RubyUI's exact class strings
|
|
376
|
+
5. Add CSS for the pure CSS version to `shadcn_components.css`
|
|
377
|
+
|
|
378
|
+
---
|
|
379
|
+
|
|
380
|
+
## File Summary
|
|
381
|
+
|
|
382
|
+
```
|
|
383
|
+
lib/stream_weaver/
|
|
384
|
+
├── assets/
|
|
385
|
+
│ ├── shadcn_tokens.css # Phase 1: semantic color tokens + dark mode
|
|
386
|
+
│ ├── shadcn_base.css # Phase 1: global base styles
|
|
387
|
+
│ └── shadcn_components.css # Phase 1: component styles using tokens
|
|
388
|
+
├── adapters/
|
|
389
|
+
│ ├── base_adapter.rb # Phase 2: abstract adapter interface
|
|
390
|
+
│ ├── alpine_js_adapter.rb # Phase 2: existing adapter (pure CSS classes)
|
|
391
|
+
│ └── tailwind_adapter.rb # Phase 3: Tailwind utility classes
|
|
392
|
+
├── components.rb # Existing + Phase 4 new components
|
|
393
|
+
└── theme.rb # Updated to bridge --sw-* → shadcn tokens
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
---
|
|
397
|
+
|
|
398
|
+
## Quick Start (for Claude Code session)
|
|
399
|
+
|
|
400
|
+
```bash
|
|
401
|
+
cd ~/work/rstreamlit/stream_weaver
|
|
402
|
+
|
|
403
|
+
# Reference material:
|
|
404
|
+
# - RubyUI gem source: ~/work/ruby_ui/lib/ruby_ui/
|
|
405
|
+
# - RubyUI CSS tokens: ~/work/ruby_ui_web/app/assets/stylesheets/application.tailwind.css
|
|
406
|
+
# - Current SW theme: lib/stream_weaver/theme.rb
|
|
407
|
+
# - Current SW components: lib/stream_weaver/components.rb
|
|
408
|
+
# - Current SW adapter: lib/stream_weaver/adapter/
|
|
409
|
+
# - Full comparison: docs/ruby-ui-comparison.md
|
|
410
|
+
|
|
411
|
+
# Start with Phase 1:
|
|
412
|
+
# 1. Create lib/stream_weaver/assets/shadcn_tokens.css
|
|
413
|
+
# 2. Create lib/stream_weaver/assets/shadcn_base.css
|
|
414
|
+
# 3. Update component CSS to use var(--primary) etc.
|
|
415
|
+
# 4. Add dark mode toggle JS
|
|
416
|
+
# 5. Test with existing examples
|
|
417
|
+
```
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
# Porting a Claude Artifact into StreamWeaver
|
|
2
|
+
|
|
3
|
+
A repeatable process for reproducing a claude.ai-hosted Artifact (or any
|
|
4
|
+
rich static HTML/CSS document) as a StreamWeaver app at high visual
|
|
5
|
+
fidelity, and for feeding what you learn back into the framework. Proven
|
|
6
|
+
on `examples/components/design_review_dsl.rb` / `design_review.css` (a
|
|
7
|
+
six-option design-review document with option cards, chips, a comparison
|
|
8
|
+
matrix, and a checklist split) -- see that pair, plus the `doc-parity`
|
|
9
|
+
example bundled in the `streamweaver-visual-companion` skill, for two
|
|
10
|
+
different genres this process has produced.
|
|
11
|
+
|
|
12
|
+
This is a process doc, not a tutorial -- it assumes familiarity with
|
|
13
|
+
`docs/theming-hooks.md` (the sw- hook contract) and the component/DSL
|
|
14
|
+
vocabulary in `lib/stream_weaver/display_dsl.rb`.
|
|
15
|
+
|
|
16
|
+
## The steps
|
|
17
|
+
|
|
18
|
+
### a. Fetch the artifact HTML, strip the frame-runtime preamble
|
|
19
|
+
|
|
20
|
+
A claude.ai Artifact page is served inside a "frame-runtime" wrapper: a
|
|
21
|
+
large minified script handling theme sync with the parent claude.ai
|
|
22
|
+
shell, scroll-position restore, link-click interception, MCP capability
|
|
23
|
+
proxying, and an RTC lockdown. None of that is the authored content --
|
|
24
|
+
it's platform plumbing for embedding the page in an iframe. The authored
|
|
25
|
+
document always starts at the page's own `<title>` tag and the `<style>`
|
|
26
|
+
block that follows it. Locate that boundary before doing anything else;
|
|
27
|
+
everything above it is safe to ignore entirely.
|
|
28
|
+
|
|
29
|
+
### b. Dissect into a rendering-capability checklist
|
|
30
|
+
|
|
31
|
+
Read the authored `<style>` block and body once, closely, and produce two
|
|
32
|
+
things:
|
|
33
|
+
|
|
34
|
+
1. **Exact palettes.** Transcribe every custom property in both the light
|
|
35
|
+
and dark color sets verbatim (hex values, not approximations) -- these
|
|
36
|
+
become CSS custom properties in your own port, prefixed to avoid
|
|
37
|
+
collision with StreamWeaver's own `--sw-*` tokens (this port used
|
|
38
|
+
`--ad-*`; pick a prefix tied to the artifact's subject, not
|
|
39
|
+
StreamWeaver's).
|
|
40
|
+
2. **A numbered checklist of rendering techniques**, not a prose
|
|
41
|
+
description -- "bordered callout with colored left accent + mono
|
|
42
|
+
kicker label", "option card: badge + title/subtitle + optional
|
|
43
|
+
pinned tag + wrapping chip row", "checklist tile: bordered card with
|
|
44
|
+
fixed-width colored glyph column", etc. Each item should be scoped
|
|
45
|
+
tightly enough that step (c) can answer "component, CSS, or gap?" for
|
|
46
|
+
it individually. The design-review port used 15 such items.
|
|
47
|
+
|
|
48
|
+
### c. Gap-assess against the component vocabulary
|
|
49
|
+
|
|
50
|
+
For each checklist item, check in this order:
|
|
51
|
+
|
|
52
|
+
1. `docs/theming-hooks.md` -- does an existing component already emit a
|
|
53
|
+
stable `sw-` hook you can restyle? (Coverage note: that doc lists the
|
|
54
|
+
structural components; the doc-component family -- `doc_header`,
|
|
55
|
+
`doc_section_header`, `callout`, `columns`, `markdown` -- also emits
|
|
56
|
+
stable hooks not yet listed there (stream_weaver-d11). When a component
|
|
57
|
+
is missing from the doc, read the classes the adapter actually emits.)
|
|
58
|
+
2. The doc/component family (`doc_header`, `doc_section_header`,
|
|
59
|
+
`callout`, `card`/`card_header`/`card_body`, `table`, `comparison`,
|
|
60
|
+
`columns`/`column`, `status_dot`, `badge`) -- proven to reach 1:1
|
|
61
|
+
Artifact parity for editorial documents (`doc-parity-example`).
|
|
62
|
+
3. A prior parity slice (`examples/parity/tyrion_warroom_components.rb`)
|
|
63
|
+
for anything board/lane/topbar/navbar-shaped.
|
|
64
|
+
4. Only if none of the above expresses it: `wireframe`/`wireframe_block
|
|
65
|
+
(html:)` for raw HTML/SVG passthrough. This is a last resort, not a
|
|
66
|
+
starting point -- see the "sanctioned escape hatches inside existing
|
|
67
|
+
components" note below before reaching for it.
|
|
68
|
+
|
|
69
|
+
**Sanctioned escape hatches inside existing components, short of
|
|
70
|
+
`wireframe_block`:**
|
|
71
|
+
|
|
72
|
+
- `table(..., markdown: true)` renders each cell's markdown-processed
|
|
73
|
+
HTML *raw* -- already used by the framework to turn `[text](url)` into
|
|
74
|
+
`<a>` tags. A cell that needs a colored status dot or any other small
|
|
75
|
+
inline markup (not achievable through the table's own options) can emit
|
|
76
|
+
that markup directly as a string, e.g. a rating cell built from
|
|
77
|
+
`%(<span class="sw-status-dot sw-status-dot-green sw-status-dot-sm">
|
|
78
|
+
</span>#{value})`. This is a real, intentional feature of `Table`, not
|
|
79
|
+
a workaround -- prefer it over `wireframe_block` for anything
|
|
80
|
+
table-cell-shaped.
|
|
81
|
+
- A `card_header`/`callout`/any container's block accepts arbitrary DSL
|
|
82
|
+
calls as extra children, rendered after that component's own built-in
|
|
83
|
+
fields. Use this to add a pinned corner badge, a markdown-rendered
|
|
84
|
+
subtitle (see gap notes below on why `card_header`'s own `meta:` can't
|
|
85
|
+
hold markdown), or any other small addition -- then position it with
|
|
86
|
+
CSS rather than fighting the DSL for a parameter that doesn't exist.
|
|
87
|
+
|
|
88
|
+
### d. Port via the shared-DSL pattern + one unlayered CSS file
|
|
89
|
+
|
|
90
|
+
Follow `prd_dsl.rb`/`prd_demo.rb`'s split:
|
|
91
|
+
|
|
92
|
+
- `<name>_dsl.rb` -- a bare DSL body (no `app` wrapper), safe to load via
|
|
93
|
+
`instance_eval` from either a standalone demo or `canvas-push`. Caveat:
|
|
94
|
+
the canvas bridge currently injects only master-theme CSS, so a port
|
|
95
|
+
whose look lives in its own stylesheet renders on a canvas as correct
|
|
96
|
+
structure without the re-skin (stream_weaver-9uk) -- the standalone demo
|
|
97
|
+
is the faithful rendering until that lands or the genre's vocabulary is
|
|
98
|
+
promoted into a theme (step g).
|
|
99
|
+
- `<name>_demo.rb` -- the standalone wrapper.
|
|
100
|
+
- `<name>.css` -- **one** stylesheet, targeting only the `sw-` hooks
|
|
101
|
+
documented in `docs/theming-hooks.md` plus your own invented classes for
|
|
102
|
+
the handful of elements with no dedicated component.
|
|
103
|
+
|
|
104
|
+
Every framework-emitted style lives in `@layer stream-weaver` (since
|
|
105
|
+
stream_weaver-oeo), so a plain unlayered stylesheet always wins,
|
|
106
|
+
regardless of selector specificity or `<head>` document order. There is
|
|
107
|
+
no specificity fight to plan around -- write the CSS you want the
|
|
108
|
+
rendered page to have, full stop.
|
|
109
|
+
|
|
110
|
+
**The chrome-neutralization recipe.** StreamWeaver's default chrome wraps
|
|
111
|
+
app content in `body.sw-layout-default` (caps width, adds padding) plus a
|
|
112
|
+
child `#app-container` div that paints its *own* background/border/shadow
|
|
113
|
+
from theme tokens (`--sw-color-bg-card`, etc.). If your port's content
|
|
114
|
+
column is meant to be the *entire* page (an editorial document, not a
|
|
115
|
+
dashboard with chrome around it), both of those need neutralizing or
|
|
116
|
+
they'll show through as a visible "card within a card" boundary and can
|
|
117
|
+
silently reintroduce the base theme's own light/dark palette underneath
|
|
118
|
+
your own:
|
|
119
|
+
|
|
120
|
+
```css
|
|
121
|
+
body[class*="sw-layout-"] {
|
|
122
|
+
max-width: none;
|
|
123
|
+
padding: 0;
|
|
124
|
+
background: var(--your-bg-token);
|
|
125
|
+
}
|
|
126
|
+
#app-container {
|
|
127
|
+
background: transparent;
|
|
128
|
+
padding: 0;
|
|
129
|
+
margin: 0;
|
|
130
|
+
border: none;
|
|
131
|
+
box-shadow: none;
|
|
132
|
+
border-radius: 0;
|
|
133
|
+
max-width: none;
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Let your own top-level wrapper div (e.g. `.ad-doc`) become the sole
|
|
138
|
+
layout/background authority: its own `max-width`/`margin: 0 auto`/padding
|
|
139
|
+
replaces what `body`/`#app-container` used to provide.
|
|
140
|
+
|
|
141
|
+
**A related gotcha worth checking early:** if your port uses
|
|
142
|
+
`app(...) do ... end` (the top-level convenience wrapper) with a local
|
|
143
|
+
`stylesheets:` path, `App#script_dir` resolves to the wrapper's own call
|
|
144
|
+
site inside `lib/stream_weaver.rb`, not your script -- the local-file
|
|
145
|
+
auto-detection raises `ArgumentError: ... resolves outside the app's
|
|
146
|
+
script directory`. Use `StreamWeaver::App.new(...) do ... end` +
|
|
147
|
+
`App.generate.run!` directly (the pattern
|
|
148
|
+
`examples/parity/tyrion_warroom_components.rb` already uses), with an
|
|
149
|
+
explicit `assets_dirs: [__dir__]` -- the top-level helper's fixed kwarg
|
|
150
|
+
list has no `assets_dirs:` param at all, so this isn't optional once
|
|
151
|
+
`stylesheets:` points at a local file.
|
|
152
|
+
|
|
153
|
+
### e. Main-thread browser verification against the reference -- never builder self-assessment
|
|
154
|
+
|
|
155
|
+
The agent that builds the port should verify the server renders without
|
|
156
|
+
exceptions (boot it, curl it, grep the response for the structural
|
|
157
|
+
markers you expect, kill the boot). It should **not** be the one to judge
|
|
158
|
+
visual fidelity. A builder that wrote the CSS is the worst-positioned
|
|
159
|
+
reviewer of whether it actually looks right -- render a page confidently
|
|
160
|
+
wrong and re-reading your own rules won't catch it. Visual sign-off needs
|
|
161
|
+
a separate pass, on the main thread, with both the port and the original
|
|
162
|
+
artifact open side by side, in both light and dark mode.
|
|
163
|
+
|
|
164
|
+
Two sharp edges specific to this workflow:
|
|
165
|
+
|
|
166
|
+
- `playwright-cli` (and most headless browser tooling) blocks `file://`
|
|
167
|
+
navigation by default. Serve the reference artifact HTML over a real
|
|
168
|
+
origin instead: `ruby -run -e httpd <dir-containing-the-html> -p <port>`
|
|
169
|
+
from Ruby's stdlib, no extra gem needed.
|
|
170
|
+
- Verify `location.href` in the browser before trusting a screenshot --
|
|
171
|
+
a stale tab left open from a prior round, or a redirect, will silently
|
|
172
|
+
screenshot the wrong page. Similarly, dark-mode toggles that persist
|
|
173
|
+
their preference to `localStorage` will carry a prior round's choice
|
|
174
|
+
into the next one -- clear it (or use a fresh browser context) before
|
|
175
|
+
comparing light mode again, or you will spend time debugging a "bug"
|
|
176
|
+
that's actually stale toggle state.
|
|
177
|
+
|
|
178
|
+
### f. File framework gaps as bugs; fix at the app level first
|
|
179
|
+
|
|
180
|
+
A port will surface real gaps -- places where an existing component is
|
|
181
|
+
missing a `class:`/`style:` passthrough, has no per-row/per-item
|
|
182
|
+
modifier hook, or where a convenience wrapper (like the top-level `app`
|
|
183
|
+
helper above) breaks under a combination of options nobody had hit
|
|
184
|
+
before. File each one as its own issue (this port's run filed
|
|
185
|
+
stream_weaver-0n6, stream_weaver-mcn, stream_weaver-rhi, and
|
|
186
|
+
stream_weaver-t37) rather than silently working around it and moving on
|
|
187
|
+
-- the workaround belongs in the port either way (you can't block a port
|
|
188
|
+
on a framework fix), but the gap needs to be visible to whoever plans the
|
|
189
|
+
next promotion pass. Examples from this port:
|
|
190
|
+
|
|
191
|
+
| Gap | Component | Workaround used |
|
|
192
|
+
|---|---|---|
|
|
193
|
+
| No per-row `class:`/tone hook | `Table` | Rely on the "pick" row being deterministically last (`tr:last-child`) |
|
|
194
|
+
| No `class:`/`style:` passthrough | `Callout` | Wrap in a scoping `div` + descendant-selector CSS |
|
|
195
|
+
| `meta:` renders escaped plain text, no markdown | `CardHeader` | Render the subtitle as an extra block child through `md` instead |
|
|
196
|
+
| `stylesheets:` + top-level `app()` helper resolves `script_dir` wrong | `App` / top-level `app()` | Use `StreamWeaver::App.new` directly with `assets_dirs:` |
|
|
197
|
+
|
|
198
|
+
### g. The promotion pass -- this is where token savings actually come from
|
|
199
|
+
|
|
200
|
+
Be honest about the economics before claiming a port was cheap. This
|
|
201
|
+
port's `design_review.css` + `design_review_dsl.rb` totals roughly 36.5KB
|
|
202
|
+
of Ruby+CSS against the source artifact's 37.3KB of authored HTML+CSS --
|
|
203
|
+
essentially 1:1, not a savings. That's expected: the artifact's palette,
|
|
204
|
+
type pairing, chip/card idiom, and layout are all *bespoke to that one
|
|
205
|
+
document*, so nothing in this port's CSS was reusable from an existing
|
|
206
|
+
theme -- every rule had to be written fresh. Compare that to the
|
|
207
|
+
`doc-parity-example` port, which reached the same 1:1 visual fidelity at
|
|
208
|
+
roughly a 6x size reduction, because it could lean on the already-built
|
|
209
|
+
`:doc` theme (Charter serif, compact editorial spacing, `--sw-color-*`
|
|
210
|
+
tokens) almost as-is and only needed a handful of component calls on top.
|
|
211
|
+
|
|
212
|
+
The lesson: a single port's cost is dominated by however much of its
|
|
213
|
+
visual vocabulary is genuinely new. The savings show up the *second*
|
|
214
|
+
time a similar genre gets ported -- if, and only if, someone takes the
|
|
215
|
+
promotion step of pulling the recurring parts (the chip pattern, the
|
|
216
|
+
pick-state idiom, the checklist-tile pattern, the chrome-neutralization
|
|
217
|
+
recipe above) out of the one-off app CSS and into either a registered
|
|
218
|
+
theme (`StreamWeaver.register_theme`) or genuinely new components. Budget
|
|
219
|
+
for this as a distinct follow-up step, not something that happens for
|
|
220
|
+
free as a side effect of shipping the first port. Until it happens, treat
|
|
221
|
+
"tokens saved vs. hand-authoring the HTML" as *prose-only* savings (you
|
|
222
|
+
still write the artifact's text once, not twice) rather than a savings on
|
|
223
|
+
the surrounding visual machinery.
|
|
224
|
+
|
|
225
|
+
### h. Add the pair to the visual-companion skill gallery
|
|
226
|
+
|
|
227
|
+
Once a port is genericized (see below) and verified, copy the DSL/demo/CSS
|
|
228
|
+
trio into
|
|
229
|
+
`lib/stream_weaver/skills/streamweaver-visual-companion/examples/` so it
|
|
230
|
+
ships with the skill (duplicated, not symlinked, so the skill stays
|
|
231
|
+
self-contained if packaged separately -- see that directory's existing
|
|
232
|
+
files for why). Add one line to that skill's `SKILL.md` gallery index
|
|
233
|
+
naming the genre and the techniques it proves out, so a future session
|
|
234
|
+
can tell at a glance which example is the closer starting point for a new
|
|
235
|
+
request.
|
|
236
|
+
|
|
237
|
+
## A note on genericizing content before it ships
|
|
238
|
+
|
|
239
|
+
If the artifact you're porting contains anything personal or
|
|
240
|
+
organization-specific (real names, an employer, an internal tool's real
|
|
241
|
+
name, a personal design-philosophy framework), rewrite it as
|
|
242
|
+
fictional-but-plausible content *before* committing -- same standard
|
|
243
|
+
`prd_dsl.rb` already holds itself to (invented company, invented author).
|
|
244
|
+
Preserve every visual pattern exactly (same number of option cards, same
|
|
245
|
+
chip dimensions per card, same table shapes, same checklist tile count) --
|
|
246
|
+
only the words change. Rename the example's own files at the same time if
|
|
247
|
+
their name was content-derived (an artifact literally about "agent
|
|
248
|
+
discovery" produced `agent_discovery_*` files; once genericized to a
|
|
249
|
+
fictional product, the files were renamed to the genre-based
|
|
250
|
+
`design_review_*` to match how `doc-parity-example` is named after its
|
|
251
|
+
*genre* -- a document proving Artifact-grade doc parity -- not its
|
|
252
|
+
original PRD's subject matter).
|