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,1102 @@
|
|
|
1
|
+
# pi-design-deck Comprehensive Analysis
|
|
2
|
+
|
|
3
|
+
*Analysis date: 2026-03-12*
|
|
4
|
+
*Source: /Users/fkchang/work/rstreamlit/pi-design-deck/ (v0.3.2)*
|
|
5
|
+
*Purpose: Port feasibility analysis for StreamWeaver*
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 1. Core Intent
|
|
10
|
+
|
|
11
|
+
### Problem Solved
|
|
12
|
+
When an AI coding agent needs to present visual design/architecture decisions to a user, text descriptions are inadequate. The user needs to **see** the options side-by-side -- rendered UI mockups, architecture diagrams, syntax-highlighted code, images -- and make explicit selections that become an "implementation contract."
|
|
13
|
+
|
|
14
|
+
### Target User
|
|
15
|
+
A developer working with the Pi coding agent (analogous to Claude Code) who needs to make multi-dimensional design decisions before implementation begins.
|
|
16
|
+
|
|
17
|
+
### Workflow
|
|
18
|
+
1. User asks the agent about design/architecture options (or uses `/deck`, `/deck-plan`, `/deck-discover` slash commands)
|
|
19
|
+
2. Agent analyzes codebase, generates a JSON deck config with slides and options
|
|
20
|
+
3. Agent calls `design_deck` tool with the JSON -- a local HTTP server starts, browser opens
|
|
21
|
+
4. User navigates slides, selects one option per slide
|
|
22
|
+
5. Optionally requests more options via "Generate N options" button -- agent generates and pushes via SSE
|
|
23
|
+
6. User submits -- selections returned as `{ slideId: "selected label" }` map
|
|
24
|
+
7. Agent uses selections as implementation contract
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## 2. Feature Inventory
|
|
29
|
+
|
|
30
|
+
### 2.1 Slide Types and Options
|
|
31
|
+
|
|
32
|
+
**DeckConfig structure:**
|
|
33
|
+
```
|
|
34
|
+
{
|
|
35
|
+
title?: string,
|
|
36
|
+
slides: DeckSlide[]
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
**DeckSlide:**
|
|
41
|
+
- `id: string` (unique, "summary" is reserved)
|
|
42
|
+
- `title: string`
|
|
43
|
+
- `context?: string` (displayed below title as framing text)
|
|
44
|
+
- `columns?: 1 | 2 | 3 | 4` (grid override; auto-detected if omitted)
|
|
45
|
+
- `options: DeckOption[]` (at least 1)
|
|
46
|
+
|
|
47
|
+
**DeckOption:**
|
|
48
|
+
- `label: string` (required, non-empty)
|
|
49
|
+
- `description?: string` (shown on hover as title attribute)
|
|
50
|
+
- `aside?: string` (explanatory text below preview, supports `\n` line breaks)
|
|
51
|
+
- `recommended?: boolean` (shows "Recommended" badge)
|
|
52
|
+
- Exactly one of:
|
|
53
|
+
- `previewHtml: string` (raw HTML injected via innerHTML)
|
|
54
|
+
- `previewBlocks: PreviewBlock[]` (typed block array, at least 1)
|
|
55
|
+
|
|
56
|
+
### 2.2 Preview Block Types
|
|
57
|
+
|
|
58
|
+
1. **HTML block**: `{ type: "html", content: "<div>...</div>" }` -- raw HTML snippet
|
|
59
|
+
2. **Mermaid block**: `{ type: "mermaid", content: "graph LR\n A-->B", theme?: { primaryColor: "#ff0000" } }` -- renders via Mermaid.js CDN, optional per-block theme variable overrides
|
|
60
|
+
3. **Code block**: `{ type: "code", code: "const x = 1;", lang: "ts" }` -- Prism.js syntax highlighting with autoloader for language support
|
|
61
|
+
4. **Image block**: `{ type: "image", src: "/absolute/path.png", alt: "description", caption?: "optional" }` -- served from temp directory via `/assets/` endpoint
|
|
62
|
+
|
|
63
|
+
### 2.3 The Generate-More Loop
|
|
64
|
+
|
|
65
|
+
**Trigger:** User clicks "Generate N options" button on any slide.
|
|
66
|
+
|
|
67
|
+
**Client-side flow:**
|
|
68
|
+
1. User optionally types instructions in prompt input, selects count (1-3) from dropdown
|
|
69
|
+
2. Click sends POST `/generate-more` with `{ slideId, prompt?, model?, thinking?, count }`
|
|
70
|
+
3. Client shows skeleton placeholder(s) with shimmer animation in the options grid
|
|
71
|
+
4. Button enters loading state ("Generating...")
|
|
72
|
+
|
|
73
|
+
**Server-side flow:**
|
|
74
|
+
1. Server validates request, sets `pendingGenerate` with 90-second timeout
|
|
75
|
+
2. Calls `callbacks.onGenerateMore(slideId, prompt, model, thinking, count)`
|
|
76
|
+
3. This resolves the Promise blocking the tool, returning structured prompt text to the agent
|
|
77
|
+
|
|
78
|
+
**Agent-side flow:**
|
|
79
|
+
1. Tool returns with `status: "generate-more"` and a text prompt instructing the agent:
|
|
80
|
+
- Which slide needs options, how many
|
|
81
|
+
- What options already exist (labels + descriptions)
|
|
82
|
+
- Whether to use a specific model (via `deck_generate` tool)
|
|
83
|
+
- The format hint (previewBlocks or previewHtml)
|
|
84
|
+
- The exact tool call to make: `design_deck({ action: "add-options", slideId, options: "[...]" })`
|
|
85
|
+
2. Agent generates options and calls `design_deck({ action: "add-options", ... })`
|
|
86
|
+
3. Server pushes each option via SSE `new-option` event
|
|
87
|
+
4. Client receives, removes skeleton, renders new option card with entry animation
|
|
88
|
+
5. `add-options` call blocks until next user action
|
|
89
|
+
|
|
90
|
+
**Regenerate-all flow:**
|
|
91
|
+
- "Regenerate all" button replaces all options on a slide
|
|
92
|
+
- Uses `action: "replace-options"`
|
|
93
|
+
- Shows overlay with skeleton placeholders covering existing options
|
|
94
|
+
- SSE event: `replace-options` with full replacement array
|
|
95
|
+
|
|
96
|
+
**Single option (non-blocking):**
|
|
97
|
+
- `action: "add-option"` pushes one option and returns immediately (for parallel calls)
|
|
98
|
+
|
|
99
|
+
**Error handling:**
|
|
100
|
+
- 90-second generation timeout on server
|
|
101
|
+
- 30-second per-option timeout on client
|
|
102
|
+
- SSE events: `generate-failed`, `regenerate-failed`
|
|
103
|
+
- JSON parse errors return actionable messages
|
|
104
|
+
- Invalid option structure returns specific validation errors
|
|
105
|
+
- `cancelGenerate()` clears pending state and sends failure event
|
|
106
|
+
|
|
107
|
+
### 2.4 Selection/Submission Flow
|
|
108
|
+
|
|
109
|
+
**Selection:**
|
|
110
|
+
- Click option card or press number key (1-9)
|
|
111
|
+
- Stores in `selections[slideId] = label`
|
|
112
|
+
- Visual: radio button fills, checkmark appears (with pop animation), border highlights
|
|
113
|
+
- Persisted to localStorage keyed by sessionId
|
|
114
|
+
- Each option has a "Your notes (optional)" textarea
|
|
115
|
+
|
|
116
|
+
**Summary slide:**
|
|
117
|
+
- Auto-generated as last slide (id: "summary")
|
|
118
|
+
- Shows grid of summary cards with slide title, selected option label, preview thumbnail, aside text, and notes
|
|
119
|
+
- "Additional instructions" textarea for final notes
|
|
120
|
+
- Submit button enabled only when all slides have selections
|
|
121
|
+
- Shows "Still need: X, Y" message for incomplete selections
|
|
122
|
+
|
|
123
|
+
**Submit:**
|
|
124
|
+
- POST `/submit` with `{ selections, notes, finalNotes }`
|
|
125
|
+
- Auto-saves snapshot with `-submitted` suffix
|
|
126
|
+
- Clears localStorage
|
|
127
|
+
- Shows close overlay: "Selections sent to agent. You can close this tab."
|
|
128
|
+
- Auto-closes tab after 800ms
|
|
129
|
+
- Tool resolves with `{ status: "completed", selections, notes?, finalNotes? }`
|
|
130
|
+
|
|
131
|
+
### 2.5 Save/Load/Export Snapshots
|
|
132
|
+
|
|
133
|
+
**Manual save (Cmd+S):**
|
|
134
|
+
- POST `/save` with current selections and notes
|
|
135
|
+
- Saves to `~/.pi/deck-snapshots/{title}-{project}-{branch}-{date}-{time}/deck.json`
|
|
136
|
+
- Shows toast notification with path
|
|
137
|
+
- Tracks dirty state ("Unsaved changes" / "Saved at HH:MM")
|
|
138
|
+
|
|
139
|
+
**Auto-save on submit:** Enabled by default (`autoSaveOnSubmit: true`)
|
|
140
|
+
|
|
141
|
+
**Auto-save on cancel:** If selections exist when cancelling, saved with `-cancelled` suffix
|
|
142
|
+
|
|
143
|
+
**Snapshot structure:**
|
|
144
|
+
```
|
|
145
|
+
{title}-{project}-{branch}-{date}-{time}[-submitted|-cancelled]/
|
|
146
|
+
deck.json # { config, selections, savedAt, id, status, modifiedAt, notes, finalNotes, savedFrom }
|
|
147
|
+
images/ # Copied image assets with relative paths
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
**List saved decks:** `design_deck({ action: "list" })` -- returns array of `{ id, title, savedAt, modifiedAt, status, cwd, branch, slideCount }`
|
|
151
|
+
|
|
152
|
+
**Open saved deck:** `design_deck({ action: "open", deckId: "..." })` -- reopens with selections and notes restored
|
|
153
|
+
|
|
154
|
+
**Export:** `design_deck({ action: "export", deckId: "...", format: "html" })` -- generates standalone HTML with:
|
|
155
|
+
- Embedded CSS (all 4 CSS files)
|
|
156
|
+
- Inlined base64 images
|
|
157
|
+
- Mermaid CDN for diagrams
|
|
158
|
+
- Google Fonts link
|
|
159
|
+
- All slides visible simultaneously (no navigation)
|
|
160
|
+
- Meta chips showing deck ID, status, timestamps, cwd, branch
|
|
161
|
+
- Selected options highlighted with "Selected" badge
|
|
162
|
+
|
|
163
|
+
### 2.6 Keyboard Shortcuts
|
|
164
|
+
|
|
165
|
+
| Key | Action |
|
|
166
|
+
|-----|--------|
|
|
167
|
+
| Left/Right arrows | Navigate slides (or if focused on option, move between options) |
|
|
168
|
+
| Up/Down arrows | Move between options within a slide |
|
|
169
|
+
| 1-9 | Quick-select option by number |
|
|
170
|
+
| Space | Select focused option |
|
|
171
|
+
| Enter | Select focused option, or advance to next slide, or submit on summary |
|
|
172
|
+
| Escape | Cancel (shows confirmation bar if selections exist; second Escape confirms) |
|
|
173
|
+
| Cmd+S / Ctrl+S | Save snapshot |
|
|
174
|
+
| Cmd+Shift+L | Toggle theme (configurable hotkey) |
|
|
175
|
+
|
|
176
|
+
### 2.7 Theme Support
|
|
177
|
+
|
|
178
|
+
**Three modes:** `dark` (default), `light`, `auto` (follows OS `prefers-color-scheme`)
|
|
179
|
+
|
|
180
|
+
**Implementation:**
|
|
181
|
+
- CSS custom properties on `:root` and `[data-theme="light"]`
|
|
182
|
+
- `data-theme` attribute on `<html>`
|
|
183
|
+
- `colorScheme` CSS property set
|
|
184
|
+
- `<meta name="theme-color">` updated
|
|
185
|
+
- Theme override stored in localStorage (`pi-deck-theme-override`)
|
|
186
|
+
- Configurable hotkey parsed from string like `"mod+shift+l"` (mod = Cmd on Mac, Ctrl otherwise)
|
|
187
|
+
- Auto mode listens for `matchMedia` changes
|
|
188
|
+
|
|
189
|
+
**Preview palette themes (for previewHtml):**
|
|
190
|
+
- `midnight-rose`, `slate-jade`, `warm-copper`, `ocean-cyan`
|
|
191
|
+
- Font themes: `albert`, `jakarta`, `grotesk`
|
|
192
|
+
- Applied via `data-theme` and `data-fonts` attributes on `.preview` elements
|
|
193
|
+
|
|
194
|
+
### 2.8 Model Selector
|
|
195
|
+
|
|
196
|
+
**Visibility:** Shown when 2+ models available in the agent's model registry
|
|
197
|
+
|
|
198
|
+
**UI:** Model bar below header with:
|
|
199
|
+
- Provider filter pills (Current, anthropic, google, etc.)
|
|
200
|
+
- Model list with selectable items showing model name and "current" tag
|
|
201
|
+
- "Default" checkbox to save selection to settings
|
|
202
|
+
- Thinking level pills (off, low, medium, high, xhigh) -- only for reasoning models when using current model
|
|
203
|
+
|
|
204
|
+
**Model override flow:**
|
|
205
|
+
1. User selects a non-current model
|
|
206
|
+
2. Generate-more prompt includes instruction to use `deck_generate` tool
|
|
207
|
+
3. `deck_generate` spawns headless `pi` CLI with `--provider` and `--model` flags
|
|
208
|
+
4. Agent parses output as options JSON
|
|
209
|
+
|
|
210
|
+
**Persistence:** Default model saved to `~/.pi/agent/settings.json` under `designDeck.generateModel`
|
|
211
|
+
|
|
212
|
+
### 2.9 Accessibility Features
|
|
213
|
+
|
|
214
|
+
- `role="radiogroup"` on options container
|
|
215
|
+
- `role="radio"` + `aria-checked` on option cards
|
|
216
|
+
- `aria-label` on radiogroups, inputs, model list
|
|
217
|
+
- `aria-pressed` on layout toggle buttons
|
|
218
|
+
- `aria-live="polite"` on summary description and save status
|
|
219
|
+
- `focus-visible` outlines on interactive elements
|
|
220
|
+
- Heading focus on slide change (`h2.focus()`)
|
|
221
|
+
- `tabIndex` management (interactive elements inside previews set to -1)
|
|
222
|
+
- `prefers-reduced-motion` media query disables animations
|
|
223
|
+
|
|
224
|
+
### 2.10 Error Handling / Edge Cases
|
|
225
|
+
|
|
226
|
+
- **Single option per slide:** Valid (columns auto-set to 1)
|
|
227
|
+
- **Many options:** Grid rebalances; cols-4 supported
|
|
228
|
+
- **Cancel with selections:** Auto-saves with `-cancelled` suffix, confirmation bar shown
|
|
229
|
+
- **Browser disconnection:** Heartbeat watchdog (5s interval, 60s grace period) triggers stale cancel
|
|
230
|
+
- **Agent disconnection:** Idle timer (5 minutes) after generate-more closes deck
|
|
231
|
+
- **Abort signal:** Agent abort closes deck immediately
|
|
232
|
+
- **Duplicate slide IDs:** Rejected during validation
|
|
233
|
+
- **Reserved ID "summary":** Rejected during validation
|
|
234
|
+
- **Body size limit:** 15MB max (BodyTooLargeError)
|
|
235
|
+
- **Session token validation:** All endpoints require valid session token
|
|
236
|
+
- **JSON parse errors:** Descriptive error messages with snippet of bad input
|
|
237
|
+
- **Concurrent decks:** Only one active deck allowed
|
|
238
|
+
- **Path traversal:** `resolveDeckFilePath` validates deckId has no slashes or `..`
|
|
239
|
+
- **Tab close:** `beforeunload` sends cancel beacon via `navigator.sendBeacon`
|
|
240
|
+
- **Double submit:** `isSubmitting` flag prevents re-entry
|
|
241
|
+
|
|
242
|
+
---
|
|
243
|
+
|
|
244
|
+
## 3. Architecture & Implementation
|
|
245
|
+
|
|
246
|
+
### 3.1 Server Architecture
|
|
247
|
+
|
|
248
|
+
**HTTP server (Node.js `http`):**
|
|
249
|
+
- Binds to `127.0.0.1` on specified port (default: random/0)
|
|
250
|
+
- Token-based auth via `?session=UUID` query param (GET) or `token` body field (POST)
|
|
251
|
+
- No CORS (localhost only)
|
|
252
|
+
|
|
253
|
+
**Endpoints:**
|
|
254
|
+
| Method | Path | Purpose |
|
|
255
|
+
|--------|------|---------|
|
|
256
|
+
| GET | `/` | Serve deck HTML (template with inlined data) |
|
|
257
|
+
| GET | `/deck.css` | Concatenated CSS (4 files) |
|
|
258
|
+
| GET | `/deck.js` | Concatenated JS (4 files) |
|
|
259
|
+
| GET | `/assets/{file}` | Serve image assets from temp dir |
|
|
260
|
+
| GET | `/events` | SSE stream |
|
|
261
|
+
| GET | `/health` | Health check |
|
|
262
|
+
| GET | `/models` | Available models |
|
|
263
|
+
| POST | `/heartbeat` | Keep-alive ping |
|
|
264
|
+
| POST | `/submit` | Submit selections |
|
|
265
|
+
| POST | `/save` | Save snapshot |
|
|
266
|
+
| POST | `/cancel` | Cancel deck |
|
|
267
|
+
| POST | `/generate-more` | Request new options |
|
|
268
|
+
| POST | `/regenerate-slide` | Request replacement options |
|
|
269
|
+
| POST | `/save-model-default` | Save default model |
|
|
270
|
+
|
|
271
|
+
**SSE events:**
|
|
272
|
+
- `new-option` -- `{ slideId, option }` (new option generated)
|
|
273
|
+
- `replace-options` -- `{ slideId, options }` (regenerated options)
|
|
274
|
+
- `generate-failed` -- `{ slideId, reason? }`
|
|
275
|
+
- `regenerate-failed` -- `{ slideId, reason? }`
|
|
276
|
+
- `deck-close` -- `{ reason }` (submitted, user, stale, aborted, closed)
|
|
277
|
+
|
|
278
|
+
### 3.2 Client-Side State Management
|
|
279
|
+
|
|
280
|
+
**Global state in `deck-core.js`:**
|
|
281
|
+
- `slides[]` -- mutable copy of config slides (options can be appended)
|
|
282
|
+
- `selections{}` -- slideId -> label map
|
|
283
|
+
- `optionNotes{}` -- slideId -> { label, notes }
|
|
284
|
+
- `finalNotes` -- string
|
|
285
|
+
- `current` -- current slide index
|
|
286
|
+
- `isClosed`, `isSubmitting`, `isDirty`
|
|
287
|
+
- `pendingGenerate` -- Map of slideId -> pending generation state
|
|
288
|
+
- `selectedModel`, `selectedThinking`
|
|
289
|
+
|
|
290
|
+
**Persistence:**
|
|
291
|
+
- Selections + notes saved to localStorage keyed by `pi-deck-{sessionId}`
|
|
292
|
+
- Layout preference saved to localStorage (`pi-deck-layout`)
|
|
293
|
+
- Theme override saved to localStorage (`pi-deck-theme-override`)
|
|
294
|
+
|
|
295
|
+
**JS module structure (concatenated in order):**
|
|
296
|
+
1. `deck-core.js` -- state, config, DOM refs, utilities, theme, layout toggle, selection persistence
|
|
297
|
+
2. `deck-render.js` -- mermaid rendering, code blocks, preview blocks, option cards, slide rendering, summary
|
|
298
|
+
3. `deck-interact.js` -- selection logic, navigation, keyboard handling, model bar
|
|
299
|
+
4. `deck-session.js` -- network (postJson), save/snapshot, session lifecycle, generate-more, SSE, init
|
|
300
|
+
|
|
301
|
+
### 3.3 Agent-Tool Communication
|
|
302
|
+
|
|
303
|
+
The tool uses a Promise-based blocking pattern:
|
|
304
|
+
|
|
305
|
+
1. `design_deck()` starts server, opens browser, returns `blockOnDeck()` (Promise)
|
|
306
|
+
2. Promise resolves when user submits, cancels, or clicks generate-more
|
|
307
|
+
3. For generate-more: Promise resolves with structured prompt, agent processes, calls `add-options`
|
|
308
|
+
4. `add-options` pushes options via SSE and returns `blockOnDeck()` again
|
|
309
|
+
5. Cycle repeats until submit or cancel
|
|
310
|
+
|
|
311
|
+
**Module-level state:**
|
|
312
|
+
- `activeDeckServer` -- handle + current resolve callback
|
|
313
|
+
- `pendingDeckResult` -- for results that arrive when no resolve is waiting
|
|
314
|
+
- `activeDeckIdleTimer` -- 5-minute idle timer
|
|
315
|
+
- `restoreDeckThinking` -- function to restore thinking level on cleanup
|
|
316
|
+
|
|
317
|
+
### 3.4 Tool Parameter API
|
|
318
|
+
|
|
319
|
+
```typescript
|
|
320
|
+
DeckParams = {
|
|
321
|
+
slides?: string, // JSON string or file path
|
|
322
|
+
action?: "add-option" | "add-options" | "replace-options" | "list" | "open" | "export",
|
|
323
|
+
slideId?: string, // target slide for option actions
|
|
324
|
+
option?: string, // JSON string of single option (add-option)
|
|
325
|
+
options?: string, // JSON array string (add-options, replace-options)
|
|
326
|
+
deckId?: string, // saved deck folder name (open, export)
|
|
327
|
+
format?: string, // export format ("html")
|
|
328
|
+
}
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
### 3.5 Persistent Server Across Re-invocations
|
|
332
|
+
|
|
333
|
+
The server persists because:
|
|
334
|
+
1. `activeDeckServer` is module-level state (survives across tool calls)
|
|
335
|
+
2. First call creates server, subsequent `add-options`/`add-option` calls interact with same server
|
|
336
|
+
3. Server closes only on: submit, cancel, stale heartbeat, idle timeout, abort, or session shutdown
|
|
337
|
+
4. Browser stays open -- SSE reconnects automatically if needed
|
|
338
|
+
|
|
339
|
+
### 3.6 Asset Serving
|
|
340
|
+
|
|
341
|
+
- Image blocks reference absolute file paths on disk
|
|
342
|
+
- Server copies images to temp dir (`mkdtempSync`)
|
|
343
|
+
- Served via `/assets/{uuid}{ext}` with appropriate MIME types
|
|
344
|
+
- Supported: png, jpg/jpeg, gif, webp, svg, avif
|
|
345
|
+
- Cleanup: temp dir removed on server close (`rmSync`)
|
|
346
|
+
- Snapshots copy images to `images/` subdirectory with relative paths
|
|
347
|
+
|
|
348
|
+
### 3.7 Settings System
|
|
349
|
+
|
|
350
|
+
**File:** `~/.pi/agent/settings.json` under `designDeck` key
|
|
351
|
+
|
|
352
|
+
```typescript
|
|
353
|
+
interface DesignDeckSettings {
|
|
354
|
+
port?: number; // Server port (0 = random)
|
|
355
|
+
browser?: string; // Browser app name
|
|
356
|
+
theme?: {
|
|
357
|
+
mode?: "auto" | "light" | "dark";
|
|
358
|
+
toggleHotkey?: string; // e.g., "mod+shift+l"
|
|
359
|
+
};
|
|
360
|
+
snapshotDir?: string; // Snapshot directory (~ expansion)
|
|
361
|
+
autoSaveOnSubmit?: boolean; // Default: true
|
|
362
|
+
generateModel?: string; // Default model for generation
|
|
363
|
+
}
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
**Migration:** Auto-migrates `interview.deckGenerateModel` to `designDeck.generateModel`.
|
|
367
|
+
|
|
368
|
+
---
|
|
369
|
+
|
|
370
|
+
## 4. UI/UX Patterns
|
|
371
|
+
|
|
372
|
+
### 4.1 Layout System
|
|
373
|
+
|
|
374
|
+
**Grid columns auto-detection (`optionCountClass`):**
|
|
375
|
+
- 1 option: 1 column
|
|
376
|
+
- 2 or 4 options: 2 columns
|
|
377
|
+
- 3+ options: 3 columns
|
|
378
|
+
- Per-slide `columns` override
|
|
379
|
+
- Global layout toggle (1/2/3/4 buttons in footer) overrides everything via `data-layout` attribute
|
|
380
|
+
- Layout persisted in localStorage
|
|
381
|
+
|
|
382
|
+
**Responsive breakpoints:**
|
|
383
|
+
- <1200px: 3-col grid collapses to 2
|
|
384
|
+
- <900px: 2-col and 3-col collapse to 1; slide padding reduces; save status hidden
|
|
385
|
+
|
|
386
|
+
**CSS Grid subgrid:** Options use `grid-row: span 3` with subgrid for header/preview/footer alignment
|
|
387
|
+
|
|
388
|
+
### 4.2 Animations
|
|
389
|
+
|
|
390
|
+
- **Slide transition:** opacity + translateY (0.35s ease)
|
|
391
|
+
- **Check mark pop:** scale 0->1 (0.35s cubic-bezier overshoot)
|
|
392
|
+
- **Option entry (generated):** opacity + scale(0.92) + translateY(8px) + blur(6px) (0.5s)
|
|
393
|
+
- **Regenerated options:** translateY(12px) + scale(0.96) with staggered delays (0.05s per item)
|
|
394
|
+
- **Skeleton shimmer:** linear-gradient background-position animation (1.5s infinite)
|
|
395
|
+
- **Regen overlay fade-in:** 0.3s ease-out
|
|
396
|
+
- **Progress bar:** width transition (0.4s cubic-bezier)
|
|
397
|
+
- **Loading overlay:** fade-out (0.25s)
|
|
398
|
+
- **Toast notification:** translateY(8px) -> 0 (0.3s)
|
|
399
|
+
- **Confirm bar:** translateY(-100%) -> 0 (0.25s)
|
|
400
|
+
- **Reduced motion:** All animations disabled via `prefers-reduced-motion`
|
|
401
|
+
|
|
402
|
+
### 4.3 Navigation Patterns
|
|
403
|
+
|
|
404
|
+
- Linear slide progression (Back/Next buttons)
|
|
405
|
+
- Progress bar showing position
|
|
406
|
+
- Arrow keys for slide navigation (left/right) and option focus (up/down within radiogroup)
|
|
407
|
+
- Summary as final slide
|
|
408
|
+
- Back disabled on first slide; Next disabled on summary
|
|
409
|
+
|
|
410
|
+
### 4.4 Selection Patterns
|
|
411
|
+
|
|
412
|
+
- Radio-button metaphor (one selection per slide)
|
|
413
|
+
- Visual: radio indicator, accent border, checkmark badge, header background change
|
|
414
|
+
- Number keys for quick selection (1-9)
|
|
415
|
+
- Click anywhere on option card
|
|
416
|
+
- Selection state tracked independently from focus
|
|
417
|
+
|
|
418
|
+
### 4.5 Summary Slide Behavior
|
|
419
|
+
|
|
420
|
+
- Auto-populated from current selections
|
|
421
|
+
- Shows preview thumbnail (first block only: code snippet 3 lines, image 80px, mermaid miniature, or HTML preview)
|
|
422
|
+
- Shows aside text (truncated to 120 chars)
|
|
423
|
+
- Shows user notes per option
|
|
424
|
+
- Final notes textarea
|
|
425
|
+
- Submit button: disabled until all selections made, shows "Still need: X, Y"
|
|
426
|
+
- After submit: button changes to "Submitted" with green background
|
|
427
|
+
|
|
428
|
+
### 4.6 Confirmation Dialogs
|
|
429
|
+
|
|
430
|
+
**Cancel confirmation bar:**
|
|
431
|
+
- Fixed at top of viewport, slides down
|
|
432
|
+
- Shows: "Cancel deck? Selections will be lost."
|
|
433
|
+
- Two buttons: "Cancel" (red) and "Keep Going"
|
|
434
|
+
- Auto-hides after 5 seconds
|
|
435
|
+
- First Escape shows bar; second Escape (or click Cancel) confirms
|
|
436
|
+
|
|
437
|
+
**Close overlay:**
|
|
438
|
+
- Fixed full-screen overlay with blur backdrop
|
|
439
|
+
- Color-coded: green (submitted), amber (cancelled), red (stale/aborted/closed)
|
|
440
|
+
- Messages: "Selections sent to agent", "Deck cancelled", "Session ended -- lost connection", "Session was ended by the agent", "Session was closed"
|
|
441
|
+
- Auto-closes tab after 800ms for submit/cancel
|
|
442
|
+
|
|
443
|
+
---
|
|
444
|
+
|
|
445
|
+
## 5. Gherkin/Cucumber Scenarios
|
|
446
|
+
|
|
447
|
+
### Feature: Creating a New Deck
|
|
448
|
+
|
|
449
|
+
```gherkin
|
|
450
|
+
Feature: Deck Creation
|
|
451
|
+
As a developer using an AI agent
|
|
452
|
+
I want to view visual design options in a browser
|
|
453
|
+
So that I can make informed implementation decisions
|
|
454
|
+
|
|
455
|
+
Scenario: Start a new deck with previewHtml options
|
|
456
|
+
Given the agent has generated a deck config with 3 slides
|
|
457
|
+
And each slide has 2-4 options with previewHtml
|
|
458
|
+
When the agent calls design_deck with the slides JSON
|
|
459
|
+
Then a local HTTP server starts on a random port
|
|
460
|
+
And the browser opens to the deck URL with a session token
|
|
461
|
+
And the deck displays the first slide with options in a grid
|
|
462
|
+
And a progress bar shows 1/4 (including summary)
|
|
463
|
+
|
|
464
|
+
Scenario: Start a new deck with previewBlocks options
|
|
465
|
+
Given a deck config with code, mermaid, and image blocks
|
|
466
|
+
When the agent calls design_deck with the slides JSON
|
|
467
|
+
Then code blocks render with Prism.js syntax highlighting
|
|
468
|
+
And mermaid blocks render as SVG diagrams
|
|
469
|
+
And image blocks display from the temp assets directory
|
|
470
|
+
And blocks stack vertically within each option card
|
|
471
|
+
|
|
472
|
+
Scenario: Deck with per-slide column override
|
|
473
|
+
Given a slide with columns set to 1
|
|
474
|
+
When the deck renders that slide
|
|
475
|
+
Then options display in a single-column layout
|
|
476
|
+
And other slides use auto-detected column counts
|
|
477
|
+
|
|
478
|
+
Scenario: Deck with context text
|
|
479
|
+
Given a slide with a context property
|
|
480
|
+
When the deck renders that slide
|
|
481
|
+
Then the context text appears below the slide title
|
|
482
|
+
And it is styled as secondary text with max-width 640px
|
|
483
|
+
|
|
484
|
+
Scenario: Deck rejects invalid config
|
|
485
|
+
Given a deck config with a slide ID of "summary"
|
|
486
|
+
When the agent calls design_deck
|
|
487
|
+
Then it throws an error: '"summary" is reserved'
|
|
488
|
+
|
|
489
|
+
Scenario: Deck rejects duplicate slide IDs
|
|
490
|
+
Given a deck config with two slides both having id "arch"
|
|
491
|
+
When the agent calls design_deck
|
|
492
|
+
Then it throws an error about duplicate slide ids
|
|
493
|
+
|
|
494
|
+
Scenario: Deck rejects option with both previewHtml and previewBlocks
|
|
495
|
+
Given an option that has both previewHtml and previewBlocks
|
|
496
|
+
When the agent calls design_deck
|
|
497
|
+
Then it throws an error: "must have either previewHtml or previewBlocks, not both"
|
|
498
|
+
|
|
499
|
+
Scenario: Deck rejects option with neither preview type
|
|
500
|
+
Given an option that has neither previewHtml nor previewBlocks
|
|
501
|
+
When the agent calls design_deck
|
|
502
|
+
Then it throws an error about requiring non-empty preview
|
|
503
|
+
|
|
504
|
+
Scenario: Only one deck active at a time
|
|
505
|
+
Given a design deck is already active
|
|
506
|
+
When the agent calls design_deck with new slides
|
|
507
|
+
Then it returns an error: "A design deck is already active"
|
|
508
|
+
|
|
509
|
+
Scenario: Deck requires interactive mode
|
|
510
|
+
Given the agent is running in headless/RPC mode
|
|
511
|
+
When the agent calls design_deck (not list or export)
|
|
512
|
+
Then it throws an error about requiring interactive mode
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
### Feature: Navigating Slides
|
|
516
|
+
|
|
517
|
+
```gherkin
|
|
518
|
+
Feature: Slide Navigation
|
|
519
|
+
As a user viewing a design deck
|
|
520
|
+
I want to navigate between slides
|
|
521
|
+
So that I can review all decision points
|
|
522
|
+
|
|
523
|
+
Scenario: Navigate forward with button
|
|
524
|
+
Given the deck is showing slide 1 of 3
|
|
525
|
+
When I click the "Next" button
|
|
526
|
+
Then slide 2 becomes active with a fade transition
|
|
527
|
+
And the progress bar updates to 2/4
|
|
528
|
+
And the heading of slide 2 receives focus
|
|
529
|
+
|
|
530
|
+
Scenario: Navigate backward with button
|
|
531
|
+
Given the deck is showing slide 2 of 3
|
|
532
|
+
When I click the "Back" button
|
|
533
|
+
Then slide 1 becomes active
|
|
534
|
+
And the Back button becomes disabled
|
|
535
|
+
|
|
536
|
+
Scenario: Navigate with arrow keys
|
|
537
|
+
Given the deck is showing slide 1
|
|
538
|
+
And no option card is focused
|
|
539
|
+
When I press the Right arrow key
|
|
540
|
+
Then slide 2 becomes active
|
|
541
|
+
|
|
542
|
+
Scenario: Arrow keys within radiogroup
|
|
543
|
+
Given an option card has focus
|
|
544
|
+
When I press the Down arrow key
|
|
545
|
+
Then focus moves to the next option card
|
|
546
|
+
And the slide does not change
|
|
547
|
+
|
|
548
|
+
Scenario: Navigate to summary slide
|
|
549
|
+
Given the deck is showing the last regular slide
|
|
550
|
+
When I click "Next"
|
|
551
|
+
Then the summary slide appears
|
|
552
|
+
And the "Next" button shows "Done" and is disabled
|
|
553
|
+
|
|
554
|
+
Scenario: Back button disabled on first slide
|
|
555
|
+
Given the deck is showing slide 1
|
|
556
|
+
Then the "Back" button is disabled
|
|
557
|
+
```
|
|
558
|
+
|
|
559
|
+
### Feature: Selecting Options
|
|
560
|
+
|
|
561
|
+
```gherkin
|
|
562
|
+
Feature: Option Selection
|
|
563
|
+
As a user viewing a design deck
|
|
564
|
+
I want to select one option per slide
|
|
565
|
+
So that my choices are communicated to the agent
|
|
566
|
+
|
|
567
|
+
Scenario: Select by clicking
|
|
568
|
+
Given slide 1 has 3 options
|
|
569
|
+
When I click option "Microservices"
|
|
570
|
+
Then option "Microservices" shows as selected
|
|
571
|
+
And a checkmark badge appears with a pop animation
|
|
572
|
+
And the radio indicator fills with accent color
|
|
573
|
+
And selections are saved to localStorage
|
|
574
|
+
|
|
575
|
+
Scenario: Select by number key
|
|
576
|
+
Given slide 1 has 3 options
|
|
577
|
+
When I press the "2" key
|
|
578
|
+
Then the second option becomes selected
|
|
579
|
+
|
|
580
|
+
Scenario: Select by Space key
|
|
581
|
+
Given an option card has focus
|
|
582
|
+
When I press Space
|
|
583
|
+
Then that option becomes selected
|
|
584
|
+
|
|
585
|
+
Scenario: Change selection
|
|
586
|
+
Given option "Monolith" is selected on slide 1
|
|
587
|
+
When I click option "Microservices"
|
|
588
|
+
Then "Microservices" becomes selected
|
|
589
|
+
And "Monolith" is deselected
|
|
590
|
+
And dirty state is marked
|
|
591
|
+
|
|
592
|
+
Scenario: Selection persists across page reload
|
|
593
|
+
Given I have selected options on slides 1 and 2
|
|
594
|
+
When I reload the page
|
|
595
|
+
Then my previous selections are restored from localStorage
|
|
596
|
+
|
|
597
|
+
Scenario: Add notes to selected option
|
|
598
|
+
Given option "Microservices" is selected
|
|
599
|
+
When I type "Use event sourcing" in the notes textarea
|
|
600
|
+
Then the notes are saved to localStorage
|
|
601
|
+
And dirty state is marked
|
|
602
|
+
And notes appear in the summary slide
|
|
603
|
+
|
|
604
|
+
Scenario: Recommended badge display
|
|
605
|
+
Given an option has recommended: true
|
|
606
|
+
And it is not selected
|
|
607
|
+
Then a "Recommended" badge appears in the header
|
|
608
|
+
|
|
609
|
+
Scenario: Aside text display
|
|
610
|
+
Given an option has aside text with newlines
|
|
611
|
+
Then the aside text renders below the preview
|
|
612
|
+
And newline characters render as line breaks
|
|
613
|
+
```
|
|
614
|
+
|
|
615
|
+
### Feature: Generate-More Flow
|
|
616
|
+
|
|
617
|
+
```gherkin
|
|
618
|
+
Feature: Generate More Options
|
|
619
|
+
As a user who wants additional design options
|
|
620
|
+
I want to request AI-generated alternatives
|
|
621
|
+
So that I have more choices to consider
|
|
622
|
+
|
|
623
|
+
Scenario: Generate one additional option
|
|
624
|
+
Given the deck is showing a slide with 2 options
|
|
625
|
+
When I click "Generate" with count set to 1
|
|
626
|
+
Then a skeleton placeholder with shimmer animation appears
|
|
627
|
+
And the button shows "Generating..." with a spinner
|
|
628
|
+
And the prompt input is disabled
|
|
629
|
+
And the agent receives a generate-more instruction
|
|
630
|
+
When the agent pushes a new option via add-options
|
|
631
|
+
Then the skeleton is replaced with the new option card
|
|
632
|
+
And the card has an entry animation (scale + blur)
|
|
633
|
+
And a "Generated" badge appears on the option
|
|
634
|
+
And the grid rebalances for the new option count
|
|
635
|
+
|
|
636
|
+
Scenario: Generate multiple options at once
|
|
637
|
+
Given the deck is showing a slide with 2 options
|
|
638
|
+
When I select count "3" and click "Generate"
|
|
639
|
+
Then 3 skeleton placeholders appear
|
|
640
|
+
And the agent receives count=3 in the instruction
|
|
641
|
+
When the agent pushes 3 options via add-options
|
|
642
|
+
Then skeletons are removed one by one as options arrive
|
|
643
|
+
|
|
644
|
+
Scenario: Generate with custom prompt
|
|
645
|
+
Given I type "make it more minimal" in the prompt input
|
|
646
|
+
When I click "Generate"
|
|
647
|
+
Then the prompt text is included in the agent instruction
|
|
648
|
+
And the prompt input is cleared
|
|
649
|
+
|
|
650
|
+
Scenario: Generate with Enter key in prompt
|
|
651
|
+
Given focus is in the prompt input
|
|
652
|
+
When I press Enter
|
|
653
|
+
Then generation is triggered (same as clicking Generate)
|
|
654
|
+
|
|
655
|
+
Scenario: Generation timeout
|
|
656
|
+
Given I clicked "Generate"
|
|
657
|
+
And 30 seconds pass without receiving an option
|
|
658
|
+
Then a toast shows "Generation timed out -- try again"
|
|
659
|
+
And the generate button is restored
|
|
660
|
+
And skeletons are removed
|
|
661
|
+
|
|
662
|
+
Scenario: Generation failure
|
|
663
|
+
Given the agent encounters an error generating options
|
|
664
|
+
Then a "generate-failed" SSE event is sent
|
|
665
|
+
And a toast shows "Generation failed"
|
|
666
|
+
And the button is restored
|
|
667
|
+
|
|
668
|
+
Scenario: Regenerate all options
|
|
669
|
+
Given the slide has 3 options
|
|
670
|
+
When I click "Regenerate all"
|
|
671
|
+
Then a skeleton overlay covers the existing options grid
|
|
672
|
+
And a centered spinner with "Regenerating options..." text appears
|
|
673
|
+
And the agent receives a regenerate-slide instruction
|
|
674
|
+
When the agent pushes 3 replacement options via replace-options
|
|
675
|
+
Then the overlay is removed
|
|
676
|
+
And new options appear with staggered entry animations
|
|
677
|
+
And the previous selection for this slide is cleared
|
|
678
|
+
|
|
679
|
+
Scenario: Concurrent generation prevented
|
|
680
|
+
Given a generation is already in progress
|
|
681
|
+
When I try to click "Generate" or "Regenerate all"
|
|
682
|
+
Then the buttons are disabled and nothing happens
|
|
683
|
+
```
|
|
684
|
+
|
|
685
|
+
### Feature: Model Selection for Generation
|
|
686
|
+
|
|
687
|
+
```gherkin
|
|
688
|
+
Feature: Model Selector
|
|
689
|
+
As a user who wants control over option generation
|
|
690
|
+
I want to choose which AI model generates new options
|
|
691
|
+
So that I can get options from different model capabilities
|
|
692
|
+
|
|
693
|
+
Scenario: Model bar appears with multiple models
|
|
694
|
+
Given the agent has 3+ models available
|
|
695
|
+
When the deck loads
|
|
696
|
+
Then a model bar appears below the header
|
|
697
|
+
And "Current" pill is active by default
|
|
698
|
+
And provider pills are shown (anthropic, google, etc.)
|
|
699
|
+
|
|
700
|
+
Scenario: Select a model from a provider
|
|
701
|
+
Given the model bar is visible
|
|
702
|
+
When I click the "google" provider pill
|
|
703
|
+
Then a model list appears with Google models
|
|
704
|
+
When I click "gemini-3.1-pro"
|
|
705
|
+
Then the model is selected and shown in the label
|
|
706
|
+
And subsequent generate-more requests use this model
|
|
707
|
+
|
|
708
|
+
Scenario: Save model as default
|
|
709
|
+
Given a model is selected
|
|
710
|
+
When I check the "Default" checkbox
|
|
711
|
+
Then the model is saved to settings.json
|
|
712
|
+
And future decks pre-select this model
|
|
713
|
+
|
|
714
|
+
Scenario: Thinking level adjustment
|
|
715
|
+
Given the current model supports reasoning
|
|
716
|
+
And no override model is selected
|
|
717
|
+
Then thinking level pills appear (off, low, medium, high)
|
|
718
|
+
When I click "high"
|
|
719
|
+
Then the thinking level is included in generate requests
|
|
720
|
+
|
|
721
|
+
Scenario: Model bar hidden with fewer than 2 models
|
|
722
|
+
Given the agent has only 1 model available
|
|
723
|
+
When the deck loads
|
|
724
|
+
Then no model bar appears
|
|
725
|
+
```
|
|
726
|
+
|
|
727
|
+
### Feature: Save/Load/Export
|
|
728
|
+
|
|
729
|
+
```gherkin
|
|
730
|
+
Feature: Save and Load Decks
|
|
731
|
+
As a user who wants to preserve my design decisions
|
|
732
|
+
I want to save, load, and export deck snapshots
|
|
733
|
+
So that I can resume work or share decisions
|
|
734
|
+
|
|
735
|
+
Scenario: Manual save with Cmd+S
|
|
736
|
+
Given I have selections on 2 slides
|
|
737
|
+
When I press Cmd+S
|
|
738
|
+
Then a POST /save request is sent
|
|
739
|
+
And a snapshot is saved to the snapshots directory
|
|
740
|
+
And a toast shows "Saved to ~/.pi/deck-snapshots/..."
|
|
741
|
+
And the save status shows "Saved at HH:MM"
|
|
742
|
+
|
|
743
|
+
Scenario: Auto-save on submit
|
|
744
|
+
Given autoSaveOnSubmit is true (default)
|
|
745
|
+
When I submit the deck
|
|
746
|
+
Then a snapshot is saved with "-submitted" suffix
|
|
747
|
+
And image assets are copied to an images/ subdirectory
|
|
748
|
+
|
|
749
|
+
Scenario: Auto-save on cancel with selections
|
|
750
|
+
Given I have selected options
|
|
751
|
+
When I cancel the deck
|
|
752
|
+
Then a snapshot is saved with "-cancelled" suffix
|
|
753
|
+
|
|
754
|
+
Scenario: List saved decks
|
|
755
|
+
Given there are 3 saved decks
|
|
756
|
+
When the agent calls design_deck({ action: "list" })
|
|
757
|
+
Then it returns an array of deck metadata
|
|
758
|
+
And each entry has id, title, savedAt, status, slideCount
|
|
759
|
+
|
|
760
|
+
Scenario: Open a saved deck
|
|
761
|
+
Given a deck "api-design-myapp-main-submitted" exists
|
|
762
|
+
When the agent calls design_deck({ action: "open", deckId: "..." })
|
|
763
|
+
Then the deck opens with selections pre-populated
|
|
764
|
+
And notes are restored
|
|
765
|
+
And image paths are resolved relative to the snapshot
|
|
766
|
+
|
|
767
|
+
Scenario: Export as standalone HTML
|
|
768
|
+
Given a submitted deck exists
|
|
769
|
+
When the agent calls design_deck({ action: "export", deckId: "...", format: "html" })
|
|
770
|
+
Then an export.html file is written next to deck.json
|
|
771
|
+
And it contains embedded CSS, inlined images, and meta chips
|
|
772
|
+
And all slides are visible simultaneously (no navigation)
|
|
773
|
+
|
|
774
|
+
Scenario: Dirty state tracking
|
|
775
|
+
Given I make a selection
|
|
776
|
+
Then save status shows "Unsaved changes"
|
|
777
|
+
And the Save button gets a warning style
|
|
778
|
+
When I save
|
|
779
|
+
Then status shows "Saved at HH:MM"
|
|
780
|
+
And the Save button returns to normal
|
|
781
|
+
```
|
|
782
|
+
|
|
783
|
+
### Feature: Keyboard Navigation
|
|
784
|
+
|
|
785
|
+
```gherkin
|
|
786
|
+
Feature: Keyboard Navigation
|
|
787
|
+
As a user who prefers keyboard interaction
|
|
788
|
+
I want full keyboard support
|
|
789
|
+
So that I can navigate and select efficiently
|
|
790
|
+
|
|
791
|
+
Scenario: Quick select by number
|
|
792
|
+
Given slide 1 has 4 options
|
|
793
|
+
When I press "3"
|
|
794
|
+
Then the third option is selected
|
|
795
|
+
|
|
796
|
+
Scenario: Enter advances to next slide
|
|
797
|
+
Given I am on slide 1
|
|
798
|
+
And no option or button has focus
|
|
799
|
+
When I press Enter
|
|
800
|
+
Then slide 2 becomes active
|
|
801
|
+
|
|
802
|
+
Scenario: Enter on summary submits
|
|
803
|
+
Given I am on the summary slide
|
|
804
|
+
And all selections are made
|
|
805
|
+
When I press Enter
|
|
806
|
+
Then the deck is submitted
|
|
807
|
+
|
|
808
|
+
Scenario: Escape shows confirmation then cancels
|
|
809
|
+
Given I have selections
|
|
810
|
+
When I press Escape
|
|
811
|
+
Then the confirmation bar appears
|
|
812
|
+
When I press Escape again
|
|
813
|
+
Then the deck is cancelled
|
|
814
|
+
|
|
815
|
+
Scenario: Escape with no selections cancels immediately
|
|
816
|
+
Given I have no selections
|
|
817
|
+
When I press Escape
|
|
818
|
+
Then the deck is cancelled immediately
|
|
819
|
+
|
|
820
|
+
Scenario: Arrow navigation between options
|
|
821
|
+
Given an option card has focus
|
|
822
|
+
When I press ArrowDown
|
|
823
|
+
Then focus moves to the next option
|
|
824
|
+
When I press ArrowUp
|
|
825
|
+
Then focus moves to the previous option
|
|
826
|
+
And wrapping occurs at boundaries
|
|
827
|
+
```
|
|
828
|
+
|
|
829
|
+
### Feature: Theme Switching
|
|
830
|
+
|
|
831
|
+
```gherkin
|
|
832
|
+
Feature: Theme Switching
|
|
833
|
+
As a user with theme preferences
|
|
834
|
+
I want to toggle between light and dark themes
|
|
835
|
+
So that the deck matches my environment
|
|
836
|
+
|
|
837
|
+
Scenario: Default dark theme
|
|
838
|
+
Given no theme override exists
|
|
839
|
+
And theme mode is "dark" (default)
|
|
840
|
+
When the deck loads
|
|
841
|
+
Then the page uses dark theme variables
|
|
842
|
+
And meta theme-color is #18181e
|
|
843
|
+
|
|
844
|
+
Scenario: Toggle to light theme
|
|
845
|
+
Given the deck is in dark theme
|
|
846
|
+
When I press Cmd+Shift+L
|
|
847
|
+
Then the page switches to light theme
|
|
848
|
+
And meta theme-color is #f8f8f8
|
|
849
|
+
And the override is saved to localStorage
|
|
850
|
+
|
|
851
|
+
Scenario: Auto theme follows OS
|
|
852
|
+
Given theme mode is "auto"
|
|
853
|
+
And OS is in light mode
|
|
854
|
+
When the deck loads
|
|
855
|
+
Then light theme is applied
|
|
856
|
+
When OS switches to dark mode
|
|
857
|
+
Then dark theme is applied
|
|
858
|
+
|
|
859
|
+
Scenario: Theme shortcut label in footer
|
|
860
|
+
Given a theme toggle hotkey is configured
|
|
861
|
+
Then the footer shows the hotkey combination
|
|
862
|
+
```
|
|
863
|
+
|
|
864
|
+
### Feature: Error Cases
|
|
865
|
+
|
|
866
|
+
```gherkin
|
|
867
|
+
Feature: Error Handling
|
|
868
|
+
As a user of the design deck
|
|
869
|
+
I want graceful error handling
|
|
870
|
+
So that I don't lose my work
|
|
871
|
+
|
|
872
|
+
Scenario: Browser loses connection
|
|
873
|
+
Given the deck is open
|
|
874
|
+
When the browser stops sending heartbeats
|
|
875
|
+
And 60 seconds pass
|
|
876
|
+
Then the server detects stale connection
|
|
877
|
+
And the deck is cancelled with reason "stale"
|
|
878
|
+
And any pending generations are cleared
|
|
879
|
+
|
|
880
|
+
Scenario: Agent idle timeout
|
|
881
|
+
Given the agent received a generate-more request
|
|
882
|
+
And 5 minutes pass without the agent responding
|
|
883
|
+
Then the deck is closed with reason "idle-timeout"
|
|
884
|
+
|
|
885
|
+
Scenario: Agent aborts the deck
|
|
886
|
+
Given the deck is open
|
|
887
|
+
When the agent receives an abort signal
|
|
888
|
+
Then the deck is closed with reason "aborted"
|
|
889
|
+
And a close overlay shows "Session was ended by the agent"
|
|
890
|
+
|
|
891
|
+
Scenario: Invalid JSON in add-options
|
|
892
|
+
Given the agent calls add-options with malformed JSON
|
|
893
|
+
Then the tool returns an error with the parse message
|
|
894
|
+
And a snippet of the bad JSON is shown
|
|
895
|
+
And pending generation is cancelled
|
|
896
|
+
|
|
897
|
+
Scenario: Invalid option structure
|
|
898
|
+
Given the agent calls add-option with an option missing a label
|
|
899
|
+
Then the tool returns a validation error
|
|
900
|
+
And specifies what is required
|
|
901
|
+
|
|
902
|
+
Scenario: Tab closed by user
|
|
903
|
+
Given the deck is open with selections
|
|
904
|
+
When the user closes the browser tab
|
|
905
|
+
Then a cancel beacon is sent via navigator.sendBeacon
|
|
906
|
+
And selections are included for auto-save
|
|
907
|
+
```
|
|
908
|
+
|
|
909
|
+
---
|
|
910
|
+
|
|
911
|
+
## 6. Example Runs
|
|
912
|
+
|
|
913
|
+
### Minimal Deck Config
|
|
914
|
+
```json
|
|
915
|
+
{
|
|
916
|
+
"slides": [{
|
|
917
|
+
"id": "s1",
|
|
918
|
+
"title": "Pick one",
|
|
919
|
+
"options": [
|
|
920
|
+
{ "label": "A", "previewHtml": "<div>A</div>" },
|
|
921
|
+
{ "label": "B", "previewHtml": "<div>B</div>" }
|
|
922
|
+
]
|
|
923
|
+
}]
|
|
924
|
+
}
|
|
925
|
+
```
|
|
926
|
+
|
|
927
|
+
### Architecture Comparison Deck (from SKILL.md)
|
|
928
|
+
```json
|
|
929
|
+
{
|
|
930
|
+
"title": "Architecture Direction",
|
|
931
|
+
"slides": [{
|
|
932
|
+
"id": "arch",
|
|
933
|
+
"title": "System Architecture",
|
|
934
|
+
"context": "Choose the high-level architecture for the backend.",
|
|
935
|
+
"columns": 2,
|
|
936
|
+
"options": [
|
|
937
|
+
{
|
|
938
|
+
"label": "Monolith",
|
|
939
|
+
"description": "Single deployable, shared database",
|
|
940
|
+
"aside": "Simpler to deploy and debug. Good starting point.\nWatch for coupling as the codebase grows.",
|
|
941
|
+
"previewBlocks": [
|
|
942
|
+
{ "type": "mermaid", "content": "graph TD\n Client-->API\n API-->DB" },
|
|
943
|
+
{ "type": "code", "code": "app.listen(3000)", "lang": "ts" }
|
|
944
|
+
]
|
|
945
|
+
},
|
|
946
|
+
{
|
|
947
|
+
"label": "Microservices",
|
|
948
|
+
"description": "Event-driven, independently deployable",
|
|
949
|
+
"aside": "Independent scaling and deployment per service.\nRequires service mesh, distributed tracing, and eventual consistency patterns.",
|
|
950
|
+
"previewBlocks": [
|
|
951
|
+
{ "type": "mermaid", "content": "graph LR\n Gateway-->Auth\n Gateway-->Orders\n Gateway-->Inventory" },
|
|
952
|
+
{ "type": "code", "code": "bus.publish('order.created', payload)", "lang": "ts" }
|
|
953
|
+
],
|
|
954
|
+
"recommended": true
|
|
955
|
+
}
|
|
956
|
+
]
|
|
957
|
+
}]
|
|
958
|
+
}
|
|
959
|
+
```
|
|
960
|
+
|
|
961
|
+
### Saved Deck Structure (deck.json)
|
|
962
|
+
```json
|
|
963
|
+
{
|
|
964
|
+
"config": { "title": "API Design", "slides": [...] },
|
|
965
|
+
"selections": { "auth": "JWT + Refresh Tokens", "db": "PostgreSQL" },
|
|
966
|
+
"savedAt": "2026-03-01T10:30:45.000Z",
|
|
967
|
+
"id": "api-design-myapp-main-2026-03-01-103045-submitted",
|
|
968
|
+
"status": "submitted",
|
|
969
|
+
"modifiedAt": "2026-03-01T10:30:45.000Z",
|
|
970
|
+
"notes": { "auth": "Use short-lived tokens" },
|
|
971
|
+
"finalNotes": "Prioritize security over convenience",
|
|
972
|
+
"savedFrom": { "cwd": "~/work/myapp", "branch": "main", "sessionId": "uuid" }
|
|
973
|
+
}
|
|
974
|
+
```
|
|
975
|
+
|
|
976
|
+
### Generate-More Prompt (what agent receives)
|
|
977
|
+
```
|
|
978
|
+
The design deck is still open and waiting for your response.
|
|
979
|
+
|
|
980
|
+
User clicked "Generate 2 options" for slide "System Architecture".
|
|
981
|
+
Context: Choose the high-level architecture for the backend.
|
|
982
|
+
|
|
983
|
+
Existing options:
|
|
984
|
+
- Monolith: Single deployable, shared database -- Simpler to deploy and debug.
|
|
985
|
+
- Microservices: Event-driven, independently deployable -- Independent scaling and deployment per service.
|
|
986
|
+
|
|
987
|
+
YOU MUST generate 2 distinctive additional options and call design_deck with add-options (one call with all options in an array). Do not skip this step or decide the user has enough options -- they explicitly requested 2 more.
|
|
988
|
+
|
|
989
|
+
design_deck({"action":"add-options","slideId":"arch","options":"[{"label":"Option label","description":"Short rationale","aside":"Explanatory notes below the preview","previewBlocks":[{"type":"code","code":"...","lang":"ts"}],"recommended":false}, ...]"})
|
|
990
|
+
|
|
991
|
+
The options field must be a JSON string containing an array of 2 option objects.
|
|
992
|
+
Each option needs: label, optional description, optional aside, optional recommended, and either previewHtml or previewBlocks.
|
|
993
|
+
Use previewBlocks (array of typed blocks: html, mermaid, code, image) to match the existing options.
|
|
994
|
+
|
|
995
|
+
Make each option distinctive -- they should represent genuinely different approaches.
|
|
996
|
+
```
|
|
997
|
+
|
|
998
|
+
---
|
|
999
|
+
|
|
1000
|
+
## 7. Token Cost Analysis
|
|
1001
|
+
|
|
1002
|
+
### Current Approach: Token Usage per Interaction
|
|
1003
|
+
|
|
1004
|
+
**Initial deck creation (agent generates slides JSON):**
|
|
1005
|
+
- Tool description + parameters schema: ~500 tokens
|
|
1006
|
+
- Slides JSON for a 3-slide deck with 2-3 options each: ~2,000-8,000 tokens
|
|
1007
|
+
- previewHtml options: 500-2,000 tokens per option (raw HTML with inline styles)
|
|
1008
|
+
- previewBlocks with code: 200-500 tokens per option
|
|
1009
|
+
- previewBlocks with mermaid: 100-300 tokens per option
|
|
1010
|
+
- Total initial call: ~2,500-8,500 tokens output
|
|
1011
|
+
|
|
1012
|
+
**Generate-more loop (per round):**
|
|
1013
|
+
- Prompt returned to agent: ~300-500 tokens (structured instructions)
|
|
1014
|
+
- Agent generates 1-3 new options: ~500-3,000 tokens output per option
|
|
1015
|
+
- `add-options` call: ~500-3,000 tokens (JSON string of options)
|
|
1016
|
+
- Total per generate-more round: ~1,300-6,500 tokens
|
|
1017
|
+
|
|
1018
|
+
**Completion (submit):**
|
|
1019
|
+
- Result text: ~100-200 tokens
|
|
1020
|
+
- Selection map: ~50-100 tokens
|
|
1021
|
+
|
|
1022
|
+
**Typical 3-slide interaction with 1 generate-more:**
|
|
1023
|
+
- Initial: ~5,000 tokens
|
|
1024
|
+
- Generate-more: ~3,000 tokens
|
|
1025
|
+
- Completion: ~200 tokens
|
|
1026
|
+
- **Total: ~8,200 tokens** (output only; input context window cost depends on conversation)
|
|
1027
|
+
|
|
1028
|
+
### StreamWeaver DSL Approach: Estimated Token Savings
|
|
1029
|
+
|
|
1030
|
+
With a StreamWeaver DSL, the agent would not generate raw HTML or construct complex JSON. Instead:
|
|
1031
|
+
|
|
1032
|
+
**DSL-based approach:**
|
|
1033
|
+
```ruby
|
|
1034
|
+
design_deck "Architecture Direction" do
|
|
1035
|
+
slide "arch", "System Architecture", context: "Choose the backend architecture" do
|
|
1036
|
+
option "Monolith", recommended: false,
|
|
1037
|
+
aside: "Simpler to deploy and debug" do
|
|
1038
|
+
mermaid "graph TD\n Client-->API\n API-->DB"
|
|
1039
|
+
code "app.listen(3000)", lang: "ts"
|
|
1040
|
+
end
|
|
1041
|
+
option "Microservices", recommended: true,
|
|
1042
|
+
aside: "Independent scaling per service" do
|
|
1043
|
+
mermaid "graph LR\n Gateway-->Auth\n Gateway-->Orders"
|
|
1044
|
+
code "bus.publish('order.created', payload)", lang: "ts"
|
|
1045
|
+
end
|
|
1046
|
+
end
|
|
1047
|
+
end
|
|
1048
|
+
```
|
|
1049
|
+
|
|
1050
|
+
**Token comparison:**
|
|
1051
|
+
- DSL blocks: ~60-70% of equivalent JSON token count (no structural boilerplate, no escaping)
|
|
1052
|
+
- previewHtml savings: DSL components (if StreamWeaver provides them) could reduce UI mockup tokens by 50-80%
|
|
1053
|
+
- Generate-more: StreamWeaver's reactive model might eliminate the SSE push pattern entirely if the server can render new options directly
|
|
1054
|
+
|
|
1055
|
+
**Estimated savings:**
|
|
1056
|
+
- Initial deck creation: 30-50% token reduction
|
|
1057
|
+
- Generate-more: 20-40% reduction (still need option content)
|
|
1058
|
+
- **Overall: ~30-45% token reduction** for a typical interaction
|
|
1059
|
+
|
|
1060
|
+
**Key insight:** The biggest token cost is in previewHtml (raw HTML with inline styles for UI mockups). A DSL with pre-built UI component primitives would provide the largest savings here. previewBlocks (code, mermaid) are already relatively compact and would see smaller savings.
|
|
1061
|
+
|
|
1062
|
+
### Additional Considerations
|
|
1063
|
+
|
|
1064
|
+
- StreamWeaver's server-side rendering eliminates the need to send HTML/CSS to the client
|
|
1065
|
+
- Alpine.js reactivity in StreamWeaver could handle the generate-more loop without SSE
|
|
1066
|
+
- The persistent server pattern maps well to StreamWeaver's architecture
|
|
1067
|
+
- Selection state management could leverage StreamWeaver's existing state primitives
|
|
1068
|
+
- The skill/prompt system is agent-framework-specific and wouldn't port directly
|
|
1069
|
+
|
|
1070
|
+
---
|
|
1071
|
+
|
|
1072
|
+
## 8. Key Architectural Decisions for StreamWeaver Port
|
|
1073
|
+
|
|
1074
|
+
### What Maps Directly
|
|
1075
|
+
- Multi-slide navigation with options grid
|
|
1076
|
+
- Preview block types (code, mermaid, image, HTML)
|
|
1077
|
+
- Selection tracking and summary slide
|
|
1078
|
+
- Save/load snapshots
|
|
1079
|
+
- Theme support (StreamWeaver already has theme-aware CSS)
|
|
1080
|
+
- Keyboard shortcuts
|
|
1081
|
+
- Layout toggle
|
|
1082
|
+
|
|
1083
|
+
### What Needs Rearchitecting
|
|
1084
|
+
- **SSE push for generate-more:** StreamWeaver's Alpine.js reactivity with polling or WebSocket could replace this
|
|
1085
|
+
- **Agent communication protocol:** The Promise-based blocking pattern is Pi-specific; StreamWeaver would use its own tool/callback mechanism
|
|
1086
|
+
- **Module-level singleton state:** StreamWeaver's class-based approach is different
|
|
1087
|
+
- **Asset serving:** StreamWeaver already has Rack-based serving
|
|
1088
|
+
- **Client JS:** Would be replaced by StreamWeaver's component system + Alpine.js
|
|
1089
|
+
|
|
1090
|
+
### What Can Be Dropped
|
|
1091
|
+
- Pi-specific: `model-runner.ts` (headless pi spawner), Pi extension API integration
|
|
1092
|
+
- `generate-prompts.ts` (prompt building for Pi agent)
|
|
1093
|
+
- `server-utils.ts` session registry (StreamWeaver has its own session management)
|
|
1094
|
+
- Export HTML (could be added later as a separate feature)
|
|
1095
|
+
|
|
1096
|
+
### Unique Value to Preserve
|
|
1097
|
+
- The generate-more loop concept (user requests more AI options during an active session)
|
|
1098
|
+
- Skeleton shimmer placeholders during generation
|
|
1099
|
+
- The summary slide pattern with preview thumbnails
|
|
1100
|
+
- Notes per option + final notes
|
|
1101
|
+
- Auto-save behavior (submit saves, cancel with selections saves)
|
|
1102
|
+
- Confirmation dialog on cancel with existing selections
|