@bastani/atomic 0.8.13 → 0.8.14
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.
- package/CHANGELOG.md +23 -0
- package/dist/builtin/intercom/package.json +1 -1
- package/dist/builtin/mcp/host-html-template.ts +1 -1
- package/dist/builtin/mcp/init.ts +15 -2
- package/dist/builtin/mcp/mcp-callback-server.ts +10 -9
- package/dist/builtin/mcp/package.json +1 -1
- package/dist/builtin/mcp/ui-session.ts +9 -6
- package/dist/builtin/subagents/CHANGELOG.md +8 -1
- package/dist/builtin/subagents/README.md +39 -32
- package/dist/builtin/subagents/package.json +1 -1
- package/dist/builtin/subagents/skills/subagent/SKILL.md +11 -11
- package/dist/builtin/subagents/src/agents/agent-management.ts +6 -1
- package/dist/builtin/subagents/src/agents/agent-serializer.ts +2 -0
- package/dist/builtin/subagents/src/agents/agents.ts +44 -19
- package/dist/builtin/subagents/src/extension/config.ts +16 -0
- package/dist/builtin/subagents/src/extension/fanout-child.ts +246 -0
- package/dist/builtin/subagents/src/extension/index.ts +466 -603
- package/dist/builtin/subagents/src/intercom/intercom-bridge.ts +6 -4
- package/dist/builtin/subagents/src/intercom/result-intercom.ts +109 -1
- package/dist/builtin/subagents/src/runs/background/async-execution.ts +124 -19
- package/dist/builtin/subagents/src/runs/background/async-job-tracker.ts +41 -6
- package/dist/builtin/subagents/src/runs/background/async-resume.ts +28 -15
- package/dist/builtin/subagents/src/runs/background/async-status.ts +60 -30
- package/dist/builtin/subagents/src/runs/background/result-watcher.ts +111 -54
- package/dist/builtin/subagents/src/runs/background/run-id-resolver.ts +83 -0
- package/dist/builtin/subagents/src/runs/background/run-status.ts +79 -3
- package/dist/builtin/subagents/src/runs/background/stale-run-reconciler.ts +46 -1
- package/dist/builtin/subagents/src/runs/background/subagent-runner.ts +66 -14
- package/dist/builtin/subagents/src/runs/foreground/chain-execution.ts +10 -3
- package/dist/builtin/subagents/src/runs/foreground/execution.ts +14 -2
- package/dist/builtin/subagents/src/runs/foreground/subagent-executor.ts +320 -23
- package/dist/builtin/subagents/src/runs/shared/completion-guard.ts +23 -1
- package/dist/builtin/subagents/src/runs/shared/mcp-direct-tool-allowlist.ts +369 -0
- package/dist/builtin/subagents/src/runs/shared/nested-events.ts +935 -0
- package/dist/builtin/subagents/src/runs/shared/nested-path.ts +52 -0
- package/dist/builtin/subagents/src/runs/shared/nested-render.ts +115 -0
- package/dist/builtin/subagents/src/runs/shared/parallel-utils.ts +1 -0
- package/dist/builtin/subagents/src/runs/shared/pi-args.ts +82 -9
- package/dist/builtin/subagents/src/runs/shared/pi-spawn.ts +1 -1
- package/dist/builtin/subagents/src/runs/shared/single-output.ts +12 -2
- package/dist/builtin/subagents/src/runs/shared/subagent-prompt-runtime.ts +32 -10
- package/dist/builtin/subagents/src/runs/shared/worktree.ts +3 -2
- package/dist/builtin/subagents/src/shared/artifacts.ts +0 -1
- package/dist/builtin/subagents/src/shared/types.ts +96 -1
- package/dist/builtin/subagents/src/shared/utils.ts +10 -2
- package/dist/builtin/subagents/src/slash/slash-commands.ts +468 -625
- package/dist/builtin/subagents/src/tui/render.ts +1227 -2093
- package/dist/builtin/web-access/package.json +1 -1
- package/dist/builtin/workflows/CHANGELOG.md +24 -0
- package/dist/builtin/workflows/README.md +28 -11
- package/dist/builtin/workflows/builtin/deep-research-codebase.ts +323 -40
- package/dist/builtin/workflows/builtin/ralph.ts +362 -176
- package/dist/builtin/workflows/package.json +2 -5
- package/dist/builtin/workflows/skills/research-codebase/SKILL.md +1 -1
- package/dist/builtin/workflows/skills/skill-creator/LICENSE.txt +202 -0
- package/dist/builtin/workflows/skills/skill-creator/SKILL.md +489 -0
- package/dist/builtin/workflows/skills/skill-creator/agents/analyzer.md +274 -0
- package/dist/builtin/workflows/skills/skill-creator/agents/comparator.md +202 -0
- package/dist/builtin/workflows/skills/skill-creator/agents/grader.md +223 -0
- package/dist/builtin/workflows/skills/skill-creator/assets/eval_review.html +146 -0
- package/dist/builtin/workflows/skills/skill-creator/eval-viewer/generate_review.py +471 -0
- package/dist/builtin/workflows/skills/skill-creator/eval-viewer/viewer.html +1325 -0
- package/dist/builtin/workflows/skills/skill-creator/references/schemas.md +430 -0
- package/dist/builtin/workflows/skills/skill-creator/scripts/__init__.py +0 -0
- package/dist/builtin/workflows/skills/skill-creator/scripts/aggregate_benchmark.py +401 -0
- package/dist/builtin/workflows/skills/skill-creator/scripts/generate_report.py +326 -0
- package/dist/builtin/workflows/skills/skill-creator/scripts/improve_description.py +247 -0
- package/dist/builtin/workflows/skills/skill-creator/scripts/package_skill.py +136 -0
- package/dist/builtin/workflows/skills/skill-creator/scripts/quick_validate.py +103 -0
- package/dist/builtin/workflows/skills/skill-creator/scripts/run_eval.py +310 -0
- package/dist/builtin/workflows/skills/skill-creator/scripts/run_loop.py +328 -0
- package/dist/builtin/workflows/skills/skill-creator/scripts/utils.py +47 -0
- package/dist/builtin/workflows/src/extension/index.ts +869 -93
- package/dist/builtin/workflows/src/extension/render-call.ts +34 -1
- package/dist/builtin/workflows/src/extension/render-result.ts +126 -21
- package/dist/builtin/workflows/src/extension/runtime.ts +91 -3
- package/dist/builtin/workflows/src/extension/wiring.ts +38 -12
- package/dist/builtin/workflows/src/extension/workflow-schema.ts +62 -5
- package/dist/builtin/workflows/src/runs/background/runner.ts +3 -3
- package/dist/builtin/workflows/src/runs/background/status.ts +42 -8
- package/dist/builtin/workflows/src/runs/foreground/executor.ts +410 -95
- package/dist/builtin/workflows/src/runs/foreground/stage-control-registry.ts +5 -2
- package/dist/builtin/workflows/src/runs/foreground/stage-runner.ts +8 -0
- package/dist/builtin/workflows/src/runs/shared/model-fallback.ts +6 -4
- package/dist/builtin/workflows/src/runs/shared/worktree.ts +3 -2
- package/dist/builtin/workflows/src/shared/persistence-restore.ts +138 -5
- package/dist/builtin/workflows/src/shared/persistence-session-entries.ts +30 -0
- package/dist/builtin/workflows/src/shared/render-inputs-schema.ts +78 -120
- package/dist/builtin/workflows/src/shared/stage-ui-broker.ts +193 -0
- package/dist/builtin/workflows/src/shared/store-types.ts +26 -1
- package/dist/builtin/workflows/src/shared/store.ts +145 -17
- package/dist/builtin/workflows/src/shared/timing.ts +6 -2
- package/dist/builtin/workflows/src/shared/workflow-failures.ts +375 -0
- package/dist/builtin/workflows/src/tui/chat-surface.ts +68 -17
- package/dist/builtin/workflows/src/tui/connectors.ts +2 -2
- package/dist/builtin/workflows/src/tui/dispatch-confirm.ts +24 -26
- package/dist/builtin/workflows/src/tui/graph-canvas.ts +4 -8
- package/dist/builtin/workflows/src/tui/graph-view.ts +17 -14
- package/dist/builtin/workflows/src/tui/header.ts +38 -0
- package/dist/builtin/workflows/src/tui/inline-form-card.ts +161 -238
- package/dist/builtin/workflows/src/tui/inline-form-editor.ts +68 -73
- package/dist/builtin/workflows/src/tui/inline-form-overlay.ts +2 -3
- package/dist/builtin/workflows/src/tui/inline-form-store.ts +2 -1
- package/dist/builtin/workflows/src/tui/inputs-overlay.ts +1 -3
- package/dist/builtin/workflows/src/tui/inputs-picker.ts +286 -399
- package/dist/builtin/workflows/src/tui/keybindings-adapter.ts +11 -0
- package/dist/builtin/workflows/src/tui/node-card.ts +2 -1
- package/dist/builtin/workflows/src/tui/overlay-adapter.ts +9 -1
- package/dist/builtin/workflows/src/tui/prompt-card.ts +46 -19
- package/dist/builtin/workflows/src/tui/run-detail.ts +63 -80
- package/dist/builtin/workflows/src/tui/session-confirm.ts +9 -3
- package/dist/builtin/workflows/src/tui/session-picker.ts +19 -16
- package/dist/builtin/workflows/src/tui/stage-chat-layout.ts +88 -0
- package/dist/builtin/workflows/src/tui/stage-chat-view.ts +368 -879
- package/dist/builtin/workflows/src/tui/status-helpers.ts +4 -0
- package/dist/builtin/workflows/src/tui/status-list.ts +67 -75
- package/dist/builtin/workflows/src/tui/store-widget-installer.ts +50 -12
- package/dist/builtin/workflows/src/tui/submit-pane.ts +164 -0
- package/dist/builtin/workflows/src/tui/switcher.ts +27 -4
- package/dist/builtin/workflows/src/tui/text-helpers.ts +98 -4
- package/dist/builtin/workflows/src/tui/widget.ts +90 -68
- package/dist/builtin/workflows/src/tui/workflow-attach-pane.ts +23 -2
- package/dist/builtin/workflows/src/tui/workflow-list.ts +44 -68
- package/dist/cli/file-processor.d.ts.map +1 -1
- package/dist/cli/file-processor.js +2 -3
- package/dist/cli/file-processor.js.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +3 -10
- package/dist/config.js.map +1 -1
- package/dist/core/agent-session-runtime.d.ts.map +1 -1
- package/dist/core/agent-session-runtime.js +2 -1
- package/dist/core/agent-session-runtime.js.map +1 -1
- package/dist/core/agent-session-services.d.ts.map +1 -1
- package/dist/core/agent-session-services.js +3 -2
- package/dist/core/agent-session-services.js.map +1 -1
- package/dist/core/agent-session.d.ts +6 -0
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +16 -2
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/atomic-guide-command.d.ts.map +1 -1
- package/dist/core/atomic-guide-command.js +8 -9
- package/dist/core/atomic-guide-command.js.map +1 -1
- package/dist/core/auth-storage.d.ts.map +1 -1
- package/dist/core/auth-storage.js +3 -2
- package/dist/core/auth-storage.js.map +1 -1
- package/dist/core/bash-executor.d.ts.map +1 -1
- package/dist/core/bash-executor.js +2 -1
- package/dist/core/bash-executor.js.map +1 -1
- package/dist/core/export-html/index.d.ts.map +1 -1
- package/dist/core/export-html/index.js +8 -6
- package/dist/core/export-html/index.js.map +1 -1
- package/dist/core/export-html/template.js +6 -3
- package/dist/core/extensions/loader.d.ts.map +1 -1
- package/dist/core/extensions/loader.js +12 -29
- package/dist/core/extensions/loader.js.map +1 -1
- package/dist/core/model-registry.d.ts.map +1 -1
- package/dist/core/model-registry.js +5 -1
- package/dist/core/model-registry.js.map +1 -1
- package/dist/core/package-manager.d.ts +8 -0
- package/dist/core/package-manager.d.ts.map +1 -1
- package/dist/core/package-manager.js +145 -58
- package/dist/core/package-manager.js.map +1 -1
- package/dist/core/prompt-templates.d.ts.map +1 -1
- package/dist/core/prompt-templates.js +6 -20
- package/dist/core/prompt-templates.js.map +1 -1
- package/dist/core/resource-loader.d.ts.map +1 -1
- package/dist/core/resource-loader.js +38 -31
- package/dist/core/resource-loader.js.map +1 -1
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +9 -4
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +32 -24
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +8 -15
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/skills.d.ts.map +1 -1
- package/dist/core/skills.js +8 -22
- package/dist/core/skills.js.map +1 -1
- package/dist/core/tools/ask-user-question/state/questionnaire-session.d.ts +5 -4
- package/dist/core/tools/ask-user-question/state/questionnaire-session.d.ts.map +1 -1
- package/dist/core/tools/ask-user-question/state/questionnaire-session.js +34 -11
- package/dist/core/tools/ask-user-question/state/questionnaire-session.js.map +1 -1
- package/dist/core/tools/ask-user-question/state/selectors/contract.d.ts +1 -0
- package/dist/core/tools/ask-user-question/state/selectors/contract.d.ts.map +1 -1
- package/dist/core/tools/ask-user-question/state/selectors/contract.js.map +1 -1
- package/dist/core/tools/ask-user-question/state/selectors/projections.d.ts.map +1 -1
- package/dist/core/tools/ask-user-question/state/selectors/projections.js +1 -0
- package/dist/core/tools/ask-user-question/state/selectors/projections.js.map +1 -1
- package/dist/core/tools/ask-user-question/state/state-reducer.d.ts +1 -2
- package/dist/core/tools/ask-user-question/state/state-reducer.d.ts.map +1 -1
- package/dist/core/tools/ask-user-question/state/state-reducer.js +26 -9
- package/dist/core/tools/ask-user-question/state/state-reducer.js.map +1 -1
- package/dist/core/tools/ask-user-question/state/state.d.ts +4 -0
- package/dist/core/tools/ask-user-question/state/state.d.ts.map +1 -1
- package/dist/core/tools/ask-user-question/state/state.js.map +1 -1
- package/dist/core/tools/ask-user-question/view/components/option-list-view.d.ts +1 -0
- package/dist/core/tools/ask-user-question/view/components/option-list-view.d.ts.map +1 -1
- package/dist/core/tools/ask-user-question/view/components/option-list-view.js +1 -0
- package/dist/core/tools/ask-user-question/view/components/option-list-view.js.map +1 -1
- package/dist/core/tools/ask-user-question/view/components/wrapping-select.d.ts +9 -6
- package/dist/core/tools/ask-user-question/view/components/wrapping-select.d.ts.map +1 -1
- package/dist/core/tools/ask-user-question/view/components/wrapping-select.js +28 -7
- package/dist/core/tools/ask-user-question/view/components/wrapping-select.js.map +1 -1
- package/dist/core/tools/ask-user-question/view/props-adapter.d.ts.map +1 -1
- package/dist/core/tools/ask-user-question/view/props-adapter.js +4 -1
- package/dist/core/tools/ask-user-question/view/props-adapter.js.map +1 -1
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js +56 -53
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/core/tools/edit-diff.d.ts +3 -1
- package/dist/core/tools/edit-diff.d.ts.map +1 -1
- package/dist/core/tools/edit-diff.js +8 -1
- package/dist/core/tools/edit-diff.js.map +1 -1
- package/dist/core/tools/edit.d.ts +3 -1
- package/dist/core/tools/edit.d.ts.map +1 -1
- package/dist/core/tools/edit.js +44 -81
- package/dist/core/tools/edit.js.map +1 -1
- package/dist/core/tools/file-mutation-queue.d.ts.map +1 -1
- package/dist/core/tools/file-mutation-queue.js +27 -12
- package/dist/core/tools/file-mutation-queue.js.map +1 -1
- package/dist/core/tools/find.d.ts.map +1 -1
- package/dist/core/tools/find.js +2 -3
- package/dist/core/tools/find.js.map +1 -1
- package/dist/core/tools/grep.d.ts.map +1 -1
- package/dist/core/tools/grep.js +3 -3
- package/dist/core/tools/grep.js.map +1 -1
- package/dist/core/tools/ls.d.ts.map +1 -1
- package/dist/core/tools/ls.js +5 -5
- package/dist/core/tools/ls.js.map +1 -1
- package/dist/core/tools/output-accumulator.d.ts +2 -0
- package/dist/core/tools/output-accumulator.d.ts.map +1 -1
- package/dist/core/tools/output-accumulator.js +11 -4
- package/dist/core/tools/output-accumulator.js.map +1 -1
- package/dist/core/tools/path-utils.d.ts +2 -0
- package/dist/core/tools/path-utils.d.ts.map +1 -1
- package/dist/core/tools/path-utils.js +39 -21
- package/dist/core/tools/path-utils.js.map +1 -1
- package/dist/core/tools/read.d.ts.map +1 -1
- package/dist/core/tools/read.js +9 -8
- package/dist/core/tools/read.js.map +1 -1
- package/dist/core/tools/truncate.d.ts.map +1 -1
- package/dist/core/tools/truncate.js +12 -2
- package/dist/core/tools/truncate.js.map +1 -1
- package/dist/core/tools/write.d.ts.map +1 -1
- package/dist/core/tools/write.js +20 -35
- package/dist/core/tools/write.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +5 -6
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/chat-input-actions.d.ts +24 -0
- package/dist/modes/interactive/chat-input-actions.d.ts.map +1 -0
- package/dist/modes/interactive/chat-input-actions.js +179 -0
- package/dist/modes/interactive/chat-input-actions.js.map +1 -0
- package/dist/modes/interactive/components/chat-message-renderer.d.ts +1 -0
- package/dist/modes/interactive/components/chat-message-renderer.d.ts.map +1 -1
- package/dist/modes/interactive/components/chat-message-renderer.js +14 -3
- package/dist/modes/interactive/components/chat-message-renderer.js.map +1 -1
- package/dist/modes/interactive/components/chat-session-host.d.ts +157 -0
- package/dist/modes/interactive/components/chat-session-host.d.ts.map +1 -0
- package/dist/modes/interactive/components/chat-session-host.js +1007 -0
- package/dist/modes/interactive/components/chat-session-host.js.map +1 -0
- package/dist/modes/interactive/components/config-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/config-selector.js +1 -1
- package/dist/modes/interactive/components/config-selector.js.map +1 -1
- package/dist/modes/interactive/components/footer.d.ts +1 -0
- package/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/dist/modes/interactive/components/footer.js +14 -5
- package/dist/modes/interactive/components/footer.js.map +1 -1
- package/dist/modes/interactive/components/index.d.ts +1 -0
- package/dist/modes/interactive/components/index.d.ts.map +1 -1
- package/dist/modes/interactive/components/index.js +1 -0
- package/dist/modes/interactive/components/index.js.map +1 -1
- package/dist/modes/interactive/components/login-dialog.d.ts +9 -1
- package/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
- package/dist/modes/interactive/components/login-dialog.js +29 -4
- package/dist/modes/interactive/components/login-dialog.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +18 -67
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/utils/child-process.d.ts +1 -0
- package/dist/utils/child-process.d.ts.map +1 -1
- package/dist/utils/child-process.js +8 -0
- package/dist/utils/child-process.js.map +1 -1
- package/dist/utils/clipboard-native.d.ts +3 -1
- package/dist/utils/clipboard-native.d.ts.map +1 -1
- package/dist/utils/clipboard-native.js +14 -8
- package/dist/utils/clipboard-native.js.map +1 -1
- package/dist/utils/image-resize-core.d.ts +30 -0
- package/dist/utils/image-resize-core.d.ts.map +1 -0
- package/dist/utils/image-resize-core.js +124 -0
- package/dist/utils/image-resize-core.js.map +1 -0
- package/dist/utils/image-resize-worker.d.ts +2 -0
- package/dist/utils/image-resize-worker.d.ts.map +1 -0
- package/dist/utils/image-resize-worker.js +31 -0
- package/dist/utils/image-resize-worker.js.map +1 -0
- package/dist/utils/image-resize.d.ts +7 -27
- package/dist/utils/image-resize.d.ts.map +1 -1
- package/dist/utils/image-resize.js +75 -115
- package/dist/utils/image-resize.js.map +1 -1
- package/dist/utils/paths.d.ts +16 -1
- package/dist/utils/paths.d.ts.map +1 -1
- package/dist/utils/paths.js +49 -7
- package/dist/utils/paths.js.map +1 -1
- package/docs/changelog.mdx +29 -0
- package/docs/compaction.md +1 -1
- package/docs/custom-provider.md +2 -2
- package/docs/development.md +1 -1
- package/docs/docs.json +98 -143
- package/docs/extensions.md +29 -16
- package/docs/favicon.svg +29 -0
- package/docs/images/interactive-mode.png +0 -0
- package/docs/images/tree-view.png +0 -0
- package/docs/images/workflow-command.png +0 -0
- package/docs/images/workflow-graph.png +0 -0
- package/docs/images/workflow-input-picker.png +0 -0
- package/docs/images/workflow-list.png +0 -0
- package/docs/index.md +10 -1
- package/docs/logo.svg +59 -0
- package/docs/packages.md +3 -3
- package/docs/providers.md +1 -1
- package/docs/quickstart.md +98 -2
- package/docs/rpc.md +8 -8
- package/docs/sdk.md +23 -12
- package/docs/sessions.md +1 -1
- package/docs/skills.md +15 -1
- package/docs/termux.md +11 -1
- package/docs/themes.md +6 -6
- package/docs/tui.md +18 -18
- package/docs/usage.md +1 -1
- package/docs/workflows.md +172 -2
- package/examples/extensions/subagent/index.ts +2 -1
- package/package.json +6 -6
- /package/dist/builtin/{workflows → subagents}/skills/playwright-cli/SKILL.md +0 -0
- /package/dist/builtin/{workflows → subagents}/skills/playwright-cli/references/element-attributes.md +0 -0
- /package/dist/builtin/{workflows → subagents}/skills/playwright-cli/references/playwright-tests.md +0 -0
- /package/dist/builtin/{workflows → subagents}/skills/playwright-cli/references/request-mocking.md +0 -0
- /package/dist/builtin/{workflows → subagents}/skills/playwright-cli/references/running-code.md +0 -0
- /package/dist/builtin/{workflows → subagents}/skills/playwright-cli/references/session-management.md +0 -0
- /package/dist/builtin/{workflows → subagents}/skills/playwright-cli/references/spec-driven-testing.md +0 -0
- /package/dist/builtin/{workflows → subagents}/skills/playwright-cli/references/storage-state.md +0 -0
- /package/dist/builtin/{workflows → subagents}/skills/playwright-cli/references/test-generation.md +0 -0
- /package/dist/builtin/{workflows → subagents}/skills/playwright-cli/references/tracing.md +0 -0
- /package/dist/builtin/{workflows → subagents}/skills/playwright-cli/references/video-recording.md +0 -0
- /package/dist/builtin/{workflows → subagents}/skills/tdd/SKILL.md +0 -0
- /package/dist/builtin/{workflows → subagents}/skills/tdd/deep-modules.md +0 -0
- /package/dist/builtin/{workflows → subagents}/skills/tdd/interface-design.md +0 -0
- /package/dist/builtin/{workflows → subagents}/skills/tdd/mocking.md +0 -0
- /package/dist/builtin/{workflows → subagents}/skills/tdd/refactoring.md +0 -0
- /package/dist/builtin/{workflows → subagents}/skills/tdd/tests.md +0 -0
|
@@ -2,47 +2,55 @@
|
|
|
2
2
|
* Interactive argument picker for `/workflow <name>` invocations.
|
|
3
3
|
*
|
|
4
4
|
* Opens when the user types `/workflow <name>` in the TUI without enough
|
|
5
|
-
* key=value tokens to satisfy the declared schema. Mirrors the
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
5
|
+
* key=value tokens to satisfy the declared schema. Mirrors the
|
|
6
|
+
* `ask_user_question` dialog shape: a top rule, a compact field tab bar,
|
|
7
|
+
* one page of ask-style question rows, and dim footer hints. The workflow is
|
|
8
|
+
* already chosen, so there is no fuzzy-list pane.
|
|
9
9
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
10
|
+
* ───────────────────────────────────────────
|
|
11
|
+
* ← ■ prompt ■ focus ✓ Submit →
|
|
12
12
|
*
|
|
13
|
-
*
|
|
13
|
+
* The high-level task to plan and execute.
|
|
14
14
|
*
|
|
15
|
-
*
|
|
16
|
-
* │ Build me a TUI for… │
|
|
17
|
-
* ╰──────────────────────────────────────────╯
|
|
18
|
-
* text · required · The high-level task to plan and execute.
|
|
15
|
+
* ❯ 1. Build me a TUI for…
|
|
19
16
|
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
* ╰──────────────────────────────────────────╯
|
|
23
|
-
* select · required · How aggressively to scope the work.
|
|
24
|
-
*
|
|
25
|
-
* tab next · shift+tab prev · ctrl+x run · esc cancel
|
|
17
|
+
* ───────────────────────────────────────────
|
|
18
|
+
* Enter to select · ↑/↓ to navigate · Tab to switch input fields · Esc to cancel
|
|
26
19
|
*
|
|
27
20
|
* Field-type renderers:
|
|
28
|
-
* - string / number : single-row
|
|
29
|
-
* - text : 3-row scrolling
|
|
30
|
-
* - boolean : on/off
|
|
31
|
-
* - select :
|
|
21
|
+
* - string / number : single-row ask-style input with blinking cursor
|
|
22
|
+
* - text : 3-row scrolling ask-style textarea
|
|
23
|
+
* - boolean : vertical on/off choice list (space flips)
|
|
24
|
+
* - select : vertical choice list, ←/→ cycles choices
|
|
32
25
|
*
|
|
33
26
|
* cross-ref:
|
|
34
27
|
* - flora131/atomic research/designs/workflow-picker-tui.tsx (PROMPT phase)
|
|
35
28
|
* - flora131/atomic packages/atomic-sdk/src/components/workflow-picker-panel.tsx
|
|
36
29
|
* - src/tui/session-picker.ts (sibling overlay; same chrome + key style)
|
|
37
|
-
* - DESIGN.md §
|
|
30
|
+
* - DESIGN.md §5 Section Labels
|
|
38
31
|
*/
|
|
39
32
|
|
|
40
33
|
import type { WorkflowInputEntry } from "../extension/render-result.js";
|
|
41
34
|
import type { GraphTheme } from "./graph-theme.js";
|
|
42
35
|
import { paint } from "./color-utils.js";
|
|
43
|
-
import {
|
|
36
|
+
import {
|
|
37
|
+
decodePrintableKey,
|
|
38
|
+
graphemes,
|
|
39
|
+
graphemeSegments,
|
|
40
|
+
Key,
|
|
41
|
+
matchesKey,
|
|
42
|
+
truncateToWidth,
|
|
43
|
+
visibleWidth,
|
|
44
|
+
wrapPlainText,
|
|
45
|
+
} from "./text-helpers.js";
|
|
46
|
+
import { renderCompactBandHeader } from "./header.js";
|
|
47
|
+
import {
|
|
48
|
+
renderAskChoiceRows,
|
|
49
|
+
renderSubmitControls,
|
|
50
|
+
} from "./submit-pane.js";
|
|
44
51
|
import {
|
|
45
52
|
type KeybindingsLike,
|
|
53
|
+
TUI_ACTION,
|
|
46
54
|
deleteRange,
|
|
47
55
|
lineEnd,
|
|
48
56
|
lineStart,
|
|
@@ -71,8 +79,8 @@ export interface InputsPickerState {
|
|
|
71
79
|
* `coerceValues()` converts these into typed objects at submit time.
|
|
72
80
|
*/
|
|
73
81
|
rawText: Record<string, string>;
|
|
74
|
-
/**
|
|
75
|
-
|
|
82
|
+
/** Reserved for older form snapshots; Submit is now a single final action. */
|
|
83
|
+
submitChoiceIdx: number;
|
|
76
84
|
/**
|
|
77
85
|
* Set of field indices that failed validation on the most recent submit
|
|
78
86
|
* attempt. Used to dim the run hint and to highlight a field if the user
|
|
@@ -93,8 +101,6 @@ export interface InputsPickerRenderOpts {
|
|
|
93
101
|
width: number;
|
|
94
102
|
theme: GraphTheme;
|
|
95
103
|
workflowName: string;
|
|
96
|
-
/** Optional one-line description shown directly under the workflow chip. */
|
|
97
|
-
description?: string;
|
|
98
104
|
fields: readonly WorkflowInputEntry[];
|
|
99
105
|
state: InputsPickerState;
|
|
100
106
|
/** True when the blinking cursor is in its visible half-period. */
|
|
@@ -144,7 +150,7 @@ export function createInputsPickerState(
|
|
|
144
150
|
return {
|
|
145
151
|
focusedIdx,
|
|
146
152
|
rawText,
|
|
147
|
-
|
|
153
|
+
submitChoiceIdx: 0,
|
|
148
154
|
invalidIndices: [],
|
|
149
155
|
caret: (rawText[fields[focusedIdx]?.name ?? ""] ?? "").length,
|
|
150
156
|
};
|
|
@@ -238,7 +244,7 @@ export function invalidForField(
|
|
|
238
244
|
return null;
|
|
239
245
|
}
|
|
240
246
|
|
|
241
|
-
function computeInvalid(
|
|
247
|
+
export function computeInvalid(
|
|
242
248
|
fields: readonly WorkflowInputEntry[],
|
|
243
249
|
raw: Record<string, string>,
|
|
244
250
|
): number[] {
|
|
@@ -254,18 +260,10 @@ function computeInvalid(
|
|
|
254
260
|
// Renderer
|
|
255
261
|
// ---------------------------------------------------------------------------
|
|
256
262
|
|
|
257
|
-
const dimSep = (theme: GraphTheme): string => paint(" · ", theme.dim);
|
|
258
|
-
|
|
259
|
-
const graphemeSegmenter = new Intl.Segmenter(undefined, { granularity: "grapheme" });
|
|
260
|
-
|
|
261
|
-
function graphemes(text: string): string[] {
|
|
262
|
-
return Array.from(graphemeSegmenter.segment(text), (s) => s.segment);
|
|
263
|
-
}
|
|
264
|
-
|
|
265
263
|
function previousGraphemeOffset(text: string, caret: number): number {
|
|
266
264
|
const c = Math.max(0, Math.min(caret, text.length));
|
|
267
265
|
let prev = 0;
|
|
268
|
-
for (const s of
|
|
266
|
+
for (const s of graphemeSegments(text)) {
|
|
269
267
|
if (s.index >= c) break;
|
|
270
268
|
prev = s.index;
|
|
271
269
|
}
|
|
@@ -274,7 +272,7 @@ function previousGraphemeOffset(text: string, caret: number): number {
|
|
|
274
272
|
|
|
275
273
|
function nextGraphemeOffset(text: string, caret: number): number {
|
|
276
274
|
const c = Math.max(0, Math.min(caret, text.length));
|
|
277
|
-
for (const s of
|
|
275
|
+
for (const s of graphemeSegments(text)) {
|
|
278
276
|
if (s.index >= c) return Math.min(text.length, s.index + s.segment.length);
|
|
279
277
|
if (s.index + s.segment.length > c) return s.index + s.segment.length;
|
|
280
278
|
}
|
|
@@ -284,7 +282,7 @@ function nextGraphemeOffset(text: string, caret: number): number {
|
|
|
284
282
|
function clampGraphemeOffset(text: string, caret: number): number {
|
|
285
283
|
const c = Math.max(0, Math.min(caret, text.length));
|
|
286
284
|
if (c === text.length) return c;
|
|
287
|
-
for (const s of
|
|
285
|
+
for (const s of graphemeSegments(text)) {
|
|
288
286
|
if (s.index === c) return c;
|
|
289
287
|
if (s.index > c) break;
|
|
290
288
|
}
|
|
@@ -340,7 +338,7 @@ function layoutEditableText(raw: string, usable: number): TextLayoutLine[] {
|
|
|
340
338
|
let line = "";
|
|
341
339
|
let lineStart = 0;
|
|
342
340
|
let lineWidth = 0;
|
|
343
|
-
for (const s of
|
|
341
|
+
for (const s of graphemeSegments(raw)) {
|
|
344
342
|
const offset = s.index;
|
|
345
343
|
const g = s.segment;
|
|
346
344
|
if (g === "\n") {
|
|
@@ -376,7 +374,7 @@ function visualColumnAt(text: string, caret: number): number {
|
|
|
376
374
|
|
|
377
375
|
function offsetAtVisualColumn(text: string, targetCol: number): number {
|
|
378
376
|
let col = 0;
|
|
379
|
-
for (const s of
|
|
377
|
+
for (const s of graphemeSegments(text)) {
|
|
380
378
|
const w = visibleWidth(s.segment);
|
|
381
379
|
if (col + w > targetCol) return s.index;
|
|
382
380
|
col += w;
|
|
@@ -416,178 +414,6 @@ function caretLineDown(raw: string, caret: number): number | null {
|
|
|
416
414
|
* Exported so the chat-history mirror (inline-form-card) renders fields
|
|
417
415
|
* identically to this overlay — single source of truth for the field shape.
|
|
418
416
|
*/
|
|
419
|
-
export function renderField(
|
|
420
|
-
field: WorkflowInputEntry,
|
|
421
|
-
raw: string,
|
|
422
|
-
focused: boolean,
|
|
423
|
-
caret: number,
|
|
424
|
-
cursorOn: boolean,
|
|
425
|
-
invalid: string | null,
|
|
426
|
-
theme: GraphTheme,
|
|
427
|
-
width: number,
|
|
428
|
-
): string[] {
|
|
429
|
-
// Border + label colour pick. Focused fields use `accent` (blue); a field
|
|
430
|
-
// that's currently flagged as invalid AFTER a submit attempt uses
|
|
431
|
-
// `error` to draw the eye.
|
|
432
|
-
const borderColor = invalid && !focused
|
|
433
|
-
? theme.error
|
|
434
|
-
: focused
|
|
435
|
-
? theme.accent
|
|
436
|
-
: theme.borderDim;
|
|
437
|
-
|
|
438
|
-
const innerWidth = Math.max(10, width - 4); // 2 chars of border + 1 pad each side
|
|
439
|
-
const top =
|
|
440
|
-
paint("╭ ", borderColor) +
|
|
441
|
-
paint(field.name, focused ? theme.text : theme.textMuted, { bold: focused }) +
|
|
442
|
-
" " +
|
|
443
|
-
paint("─".repeat(Math.max(0, innerWidth - field.name.length - 2)) + "╮", borderColor);
|
|
444
|
-
const bottom = paint("╰" + "─".repeat(innerWidth) + "╯", borderColor);
|
|
445
|
-
|
|
446
|
-
// Content row — branch per type.
|
|
447
|
-
const contentInner = renderFieldContent(
|
|
448
|
-
field,
|
|
449
|
-
raw,
|
|
450
|
-
focused,
|
|
451
|
-
caret,
|
|
452
|
-
cursorOn,
|
|
453
|
-
innerWidth,
|
|
454
|
-
theme,
|
|
455
|
-
);
|
|
456
|
-
const lines: string[] = [];
|
|
457
|
-
lines.push(top);
|
|
458
|
-
for (const inner of contentInner) {
|
|
459
|
-
lines.push(paint("│ ", borderColor) + inner + paint(" │", borderColor));
|
|
460
|
-
}
|
|
461
|
-
lines.push(bottom);
|
|
462
|
-
|
|
463
|
-
// Caption row — type · required|optional · description / invalid reason.
|
|
464
|
-
// Composed at full length, then ANSI-clipped to the terminal width so it
|
|
465
|
-
// never overflows into a second row regardless of how narrow the terminal
|
|
466
|
-
// gets. On overflow the rightmost cell becomes `…`.
|
|
467
|
-
const tagColour = invalid
|
|
468
|
-
? theme.error
|
|
469
|
-
: field.required
|
|
470
|
-
? theme.warning
|
|
471
|
-
: theme.dim;
|
|
472
|
-
const tagLabel = invalid ?? (field.required ? "required" : "optional");
|
|
473
|
-
const caption =
|
|
474
|
-
" " +
|
|
475
|
-
paint(field.type, theme.dim) +
|
|
476
|
-
dimSep(theme) +
|
|
477
|
-
paint(tagLabel, tagColour) +
|
|
478
|
-
(field.description ? dimSep(theme) + paint(field.description, theme.dim) : "");
|
|
479
|
-
lines.push(truncateToWidth(caption, width, "…", true));
|
|
480
|
-
return lines;
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
/**
|
|
484
|
-
* Return the inner content rows of a field, sized to fit `innerWidth - 2`
|
|
485
|
-
* (the border + padding consume 4 cells total). Text fields are 3 rows
|
|
486
|
-
* tall; all others are a single row.
|
|
487
|
-
*/
|
|
488
|
-
function renderFieldContent(
|
|
489
|
-
field: WorkflowInputEntry,
|
|
490
|
-
raw: string,
|
|
491
|
-
focused: boolean,
|
|
492
|
-
caret: number,
|
|
493
|
-
cursorOn: boolean,
|
|
494
|
-
innerWidth: number,
|
|
495
|
-
theme: GraphTheme,
|
|
496
|
-
): string[] {
|
|
497
|
-
const usable = innerWidth - 2; // padding on both sides
|
|
498
|
-
|
|
499
|
-
if (field.type === "select" && field.choices && field.choices.length > 0) {
|
|
500
|
-
const cells = field.choices.map((choice) => {
|
|
501
|
-
const sel = choice === raw;
|
|
502
|
-
const marker = sel ? "●" : "○";
|
|
503
|
-
const markerColor = sel
|
|
504
|
-
? focused
|
|
505
|
-
? theme.accent
|
|
506
|
-
: theme.success
|
|
507
|
-
: theme.dim;
|
|
508
|
-
const textColor = sel
|
|
509
|
-
? focused
|
|
510
|
-
? theme.text
|
|
511
|
-
: theme.textMuted
|
|
512
|
-
: theme.dim;
|
|
513
|
-
return paint(marker, markerColor) + " " + paint(choice, textColor);
|
|
514
|
-
});
|
|
515
|
-
return [padLine(cells.join(" "), usable)];
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
if (field.type === "boolean") {
|
|
519
|
-
const on = raw === "true";
|
|
520
|
-
const onCell =
|
|
521
|
-
paint(on ? "●" : "○", on ? theme.accent : theme.dim) +
|
|
522
|
-
" " +
|
|
523
|
-
paint("on", on ? theme.text : theme.dim);
|
|
524
|
-
const offCell =
|
|
525
|
-
paint(!on ? "●" : "○", !on ? theme.accent : theme.dim) +
|
|
526
|
-
" " +
|
|
527
|
-
paint("off", !on ? theme.text : theme.dim);
|
|
528
|
-
return [padLine(onCell + " " + offCell, usable)];
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
if (field.type === "text") {
|
|
532
|
-
// 3-row scrolling textarea — wrap by terminal cell width and keep the
|
|
533
|
-
// cursor's visual row in view. Newlines create hard row breaks.
|
|
534
|
-
const ROWS = 3;
|
|
535
|
-
if (raw === "") {
|
|
536
|
-
return Array.from({ length: ROWS }, (_, i) =>
|
|
537
|
-
i === 0
|
|
538
|
-
? renderInlineText("", focused, cursorOn, usable, theme, field.placeholder, true)
|
|
539
|
-
: padLine("", usable),
|
|
540
|
-
);
|
|
541
|
-
}
|
|
542
|
-
const layout = layoutEditableText(raw, usable);
|
|
543
|
-
const safeCaret = clampGraphemeOffset(raw, caret);
|
|
544
|
-
let cursorRow = layout.length - 1;
|
|
545
|
-
for (let i = 0; i < layout.length; i++) {
|
|
546
|
-
const line = layout[i]!;
|
|
547
|
-
const next = layout[i + 1];
|
|
548
|
-
if (safeCaret >= line.start && safeCaret < line.end) {
|
|
549
|
-
cursorRow = i;
|
|
550
|
-
break;
|
|
551
|
-
}
|
|
552
|
-
if (safeCaret === line.end) {
|
|
553
|
-
cursorRow = next?.start === safeCaret ? i + 1 : i;
|
|
554
|
-
}
|
|
555
|
-
}
|
|
556
|
-
cursorRow = Math.max(0, Math.min(cursorRow, layout.length - 1));
|
|
557
|
-
const start = focused
|
|
558
|
-
? Math.max(0, Math.min(cursorRow - ROWS + 1, layout.length - ROWS))
|
|
559
|
-
: Math.max(0, layout.length - ROWS);
|
|
560
|
-
const rows: string[] = [];
|
|
561
|
-
for (let i = 0; i < ROWS; i++) {
|
|
562
|
-
const rowIdx = start + i;
|
|
563
|
-
const line = layout[rowIdx];
|
|
564
|
-
if (!line) {
|
|
565
|
-
rows.push(padLine("", usable));
|
|
566
|
-
continue;
|
|
567
|
-
}
|
|
568
|
-
const lineCaret = safeCaret >= line.start && safeCaret <= line.end
|
|
569
|
-
? safeCaret - line.start
|
|
570
|
-
: line.text.length;
|
|
571
|
-
rows.push(
|
|
572
|
-
renderInlineText(
|
|
573
|
-
line.text,
|
|
574
|
-
focused && rowIdx === cursorRow,
|
|
575
|
-
cursorOn,
|
|
576
|
-
usable,
|
|
577
|
-
theme,
|
|
578
|
-
field.placeholder,
|
|
579
|
-
false,
|
|
580
|
-
lineCaret,
|
|
581
|
-
),
|
|
582
|
-
);
|
|
583
|
-
}
|
|
584
|
-
return rows;
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
// string / number / integer / default — single-line input.
|
|
588
|
-
return [renderInlineText(raw, focused, cursorOn, usable, theme, field.placeholder, raw === "", caret)];
|
|
589
|
-
}
|
|
590
|
-
|
|
591
417
|
/**
|
|
592
418
|
* Render a single editable line. When `value` is empty and the field is
|
|
593
419
|
* focused, paint a dim placeholder with the cursor sitting on its first
|
|
@@ -649,166 +475,226 @@ function padLine(s: string, usable: number): string {
|
|
|
649
475
|
return truncateToWidth(s, usable, "…", true);
|
|
650
476
|
}
|
|
651
477
|
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
478
|
+
function fitLine(line: string, width: number): string {
|
|
479
|
+
return truncateToWidth(line, Math.max(0, width), "…", true);
|
|
480
|
+
}
|
|
655
481
|
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
482
|
+
function renderWorkflowHeader(
|
|
483
|
+
workflowName: string,
|
|
484
|
+
fieldCount: number,
|
|
485
|
+
focusedIdx: number,
|
|
486
|
+
theme: GraphTheme,
|
|
487
|
+
width: number,
|
|
488
|
+
): string[] {
|
|
489
|
+
const current = Math.min(fieldCount, Math.max(1, focusedIdx + 1));
|
|
490
|
+
return renderCompactBandHeader({
|
|
491
|
+
label: "WORKFLOW",
|
|
492
|
+
subtitle: workflowName,
|
|
493
|
+
badges: fieldCount > 0 ? [{ text: `${current} / ${fieldCount}`, fg: theme.dim }] : [],
|
|
494
|
+
width,
|
|
495
|
+
theme,
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
function renderInputField(
|
|
500
|
+
field: WorkflowInputEntry,
|
|
501
|
+
raw: string,
|
|
502
|
+
caret: number,
|
|
503
|
+
cursorOn: boolean,
|
|
504
|
+
invalid: string | null,
|
|
505
|
+
focused: boolean,
|
|
506
|
+
theme: GraphTheme,
|
|
507
|
+
width: number,
|
|
508
|
+
): string[] {
|
|
509
|
+
const boxWidth = Math.max(4, width);
|
|
510
|
+
const contentWidth = Math.max(1, boxWidth - 2);
|
|
511
|
+
const borderColor = focused ? theme.accent : theme.borderDim;
|
|
512
|
+
const rows = renderAskStyleInputBody(field, raw, focused ? caret : raw.length, cursorOn, focused, theme, contentWidth);
|
|
513
|
+
const lines = [
|
|
514
|
+
renderFieldTop(field.name, boxWidth, borderColor, focused, theme),
|
|
515
|
+
...rows.map((row) => renderFieldRow(row, contentWidth, borderColor, theme)),
|
|
516
|
+
renderFieldBottom(boxWidth, borderColor),
|
|
517
|
+
...renderFieldMeta(field, invalid, theme, width),
|
|
518
|
+
];
|
|
519
|
+
return lines;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
function renderAskStyleInputBody(
|
|
523
|
+
field: WorkflowInputEntry,
|
|
524
|
+
raw: string,
|
|
525
|
+
caret: number,
|
|
526
|
+
cursorOn: boolean,
|
|
527
|
+
focused: boolean,
|
|
528
|
+
theme: GraphTheme,
|
|
529
|
+
width: number,
|
|
530
|
+
): string[] {
|
|
531
|
+
if (field.type === "select" && field.choices && field.choices.length > 0) {
|
|
532
|
+
const selected = Math.max(0, field.choices.indexOf(raw));
|
|
533
|
+
return field.choices.flatMap((choice, i) =>
|
|
534
|
+
renderAskChoiceRows(i + 1, focused || i !== selected ? choice : `✓ ${choice}`, focused && i === selected, theme, width),
|
|
535
|
+
);
|
|
668
536
|
}
|
|
669
|
-
lines.push("");
|
|
670
537
|
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
538
|
+
if (field.type === "boolean") {
|
|
539
|
+
const normalized = raw.trim().toLowerCase();
|
|
540
|
+
const hasValue = normalized.length > 0;
|
|
541
|
+
const on = normalized === "true" || normalized === "1";
|
|
542
|
+
return [
|
|
543
|
+
...renderAskChoiceRows(1, focused || !hasValue || !on ? "on" : "✓ on", focused && hasValue && on, theme, width),
|
|
544
|
+
...renderAskChoiceRows(2, focused || !hasValue || on ? "off" : "✓ off", focused && hasValue && !on, theme, width),
|
|
545
|
+
];
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
return renderAskInputRows(field, raw, caret, cursorOn, focused, theme, width);
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
function renderAskInputRows(
|
|
552
|
+
field: WorkflowInputEntry,
|
|
553
|
+
raw: string,
|
|
554
|
+
caret: number,
|
|
555
|
+
cursorOn: boolean,
|
|
556
|
+
focused: boolean,
|
|
557
|
+
theme: GraphTheme,
|
|
558
|
+
width: number,
|
|
559
|
+
): string[] {
|
|
560
|
+
const usable = Math.max(1, width);
|
|
561
|
+
|
|
562
|
+
if (field.type !== "text") {
|
|
563
|
+
return [renderInlineText(raw, focused, cursorOn, usable, theme, field.placeholder, raw === "", caret)];
|
|
689
564
|
}
|
|
565
|
+
|
|
566
|
+
const ROWS = 3;
|
|
567
|
+
if (raw === "") {
|
|
568
|
+
return [
|
|
569
|
+
renderInlineText("", focused, cursorOn, usable, theme, field.placeholder, true),
|
|
570
|
+
...Array.from({ length: ROWS - 1 }, () => padLine("", usable)),
|
|
571
|
+
];
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
const layout = layoutEditableText(raw, usable);
|
|
575
|
+
const safeCaret = clampGraphemeOffset(raw, caret);
|
|
576
|
+
let cursorRow = layout.length - 1;
|
|
577
|
+
for (let i = 0; i < layout.length; i++) {
|
|
578
|
+
const line = layout[i]!;
|
|
579
|
+
const next = layout[i + 1];
|
|
580
|
+
if (safeCaret >= line.start && safeCaret < line.end) {
|
|
581
|
+
cursorRow = i;
|
|
582
|
+
break;
|
|
583
|
+
}
|
|
584
|
+
if (safeCaret === line.end) {
|
|
585
|
+
cursorRow = next?.start === safeCaret ? i + 1 : i;
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
cursorRow = Math.max(0, Math.min(cursorRow, layout.length - 1));
|
|
589
|
+
const start = Math.max(0, Math.min(cursorRow - ROWS + 1, layout.length - ROWS));
|
|
590
|
+
const rows: string[] = [];
|
|
591
|
+
for (let i = 0; i < ROWS; i++) {
|
|
592
|
+
const rowIdx = start + i;
|
|
593
|
+
const line = layout[rowIdx];
|
|
594
|
+
if (!line) {
|
|
595
|
+
rows.push(padLine("", usable));
|
|
596
|
+
continue;
|
|
597
|
+
}
|
|
598
|
+
const lineCaret = safeCaret >= line.start && safeCaret <= line.end
|
|
599
|
+
? safeCaret - line.start
|
|
600
|
+
: line.text.length;
|
|
601
|
+
rows.push(
|
|
602
|
+
renderInlineText(
|
|
603
|
+
line.text,
|
|
604
|
+
focused && rowIdx === cursorRow,
|
|
605
|
+
cursorOn,
|
|
606
|
+
usable,
|
|
607
|
+
theme,
|
|
608
|
+
field.placeholder,
|
|
609
|
+
false,
|
|
610
|
+
lineCaret,
|
|
611
|
+
),
|
|
612
|
+
);
|
|
613
|
+
}
|
|
614
|
+
return rows;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
export function renderInputsPicker(opts: InputsPickerRenderOpts): string[] {
|
|
618
|
+
const { theme, workflowName, fields, state, width, cursorOn } = opts;
|
|
619
|
+
const lines: string[] = [];
|
|
620
|
+
|
|
621
|
+
lines.push(...renderWorkflowHeader(workflowName, fields.length, state.focusedIdx, theme, width));
|
|
690
622
|
lines.push("");
|
|
691
623
|
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
const
|
|
695
|
-
const raw = state.rawText[f.name] ?? "";
|
|
696
|
-
const focused = i === state.focusedIdx && !state.confirmOpen;
|
|
624
|
+
for (let i = 0; i < fields.length; i += 1) {
|
|
625
|
+
const field = fields[i]!;
|
|
626
|
+
const raw = state.rawText[field.name] ?? "";
|
|
697
627
|
const invalid = state.invalidIndices.includes(i)
|
|
698
|
-
? invalidForField(
|
|
628
|
+
? invalidForField(field, raw, i)
|
|
699
629
|
: null;
|
|
700
|
-
lines.push(...
|
|
701
|
-
lines.push("");
|
|
630
|
+
lines.push(...renderInputField(field, raw, state.caret, cursorOn, invalid, state.focusedIdx === i, theme, width));
|
|
631
|
+
lines.push("");
|
|
702
632
|
}
|
|
703
633
|
|
|
704
|
-
|
|
634
|
+
lines.push(...renderPickerSubmitControls(fields, state, theme, width));
|
|
705
635
|
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
// and finally essentials-only when the terminal cannot hold the row.
|
|
709
|
-
lines.push(renderFooterHints(width, theme, anyInvalid));
|
|
636
|
+
return lines.map((line) => fitLine(line, width));
|
|
637
|
+
}
|
|
710
638
|
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
639
|
+
function renderFieldTop(
|
|
640
|
+
title: string,
|
|
641
|
+
width: number,
|
|
642
|
+
borderColor: string,
|
|
643
|
+
focused: boolean,
|
|
644
|
+
theme: GraphTheme,
|
|
645
|
+
): string {
|
|
646
|
+
const label = ` ${title} `;
|
|
647
|
+
const labelText = paint(label, focused ? theme.accent : theme.textMuted, { bold: focused });
|
|
648
|
+
const fill = Math.max(0, width - visibleWidth(label) - 2);
|
|
649
|
+
return paint("╭", borderColor) + labelText + paint("─".repeat(fill) + "╮", borderColor);
|
|
716
650
|
}
|
|
717
651
|
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
* tight (≥ short): tab · ⇧tab · ctrl+x · esc
|
|
724
|
-
* narrow (else): ctrl+x · esc
|
|
725
|
-
*/
|
|
726
|
-
function renderFooterHints(width: number, theme: GraphTheme, submitDisabled: boolean): string {
|
|
727
|
-
const sep = dimSep(theme);
|
|
728
|
-
const sepWidth = 5; // " · "
|
|
729
|
-
const submitColor = submitDisabled ? theme.dim : theme.text;
|
|
730
|
-
const submitLabelColor = submitDisabled ? theme.dim : theme.textMuted;
|
|
731
|
-
const hint = (key: string, label: string, keyColor = theme.text, labelColor = theme.textMuted): string =>
|
|
732
|
-
paint(key, keyColor) + " " + paint(label, labelColor);
|
|
733
|
-
const keyOnly = (key: string, keyColor = theme.text): string => paint(key, keyColor);
|
|
652
|
+
function renderFieldRow(row: string, contentWidth: number, borderColor: string, _theme: GraphTheme): string {
|
|
653
|
+
const clipped = truncateToWidth(row, contentWidth, "", true);
|
|
654
|
+
const padded = clipped + " ".repeat(Math.max(0, contentWidth - visibleWidth(clipped)));
|
|
655
|
+
return paint("│", borderColor) + padded + paint("│", borderColor);
|
|
656
|
+
}
|
|
734
657
|
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
{ width: 10, render: () => hint("ctrl+x", "Run", submitColor, submitLabelColor) },
|
|
739
|
-
{ width: 10, render: () => hint("esc", "Cancel") },
|
|
740
|
-
];
|
|
741
|
-
const medium = [
|
|
742
|
-
{ width: 3, render: () => keyOnly("tab") },
|
|
743
|
-
{ width: 9, render: () => keyOnly("shift+tab") },
|
|
744
|
-
{ width: 6, render: () => keyOnly("ctrl+x", submitColor) },
|
|
745
|
-
{ width: 6, render: () => keyOnly("esc") },
|
|
746
|
-
];
|
|
747
|
-
const tight = [
|
|
748
|
-
{ width: 3, render: () => keyOnly("tab") },
|
|
749
|
-
{ width: 4, render: () => keyOnly("⇧tab") },
|
|
750
|
-
{ width: 6, render: () => keyOnly("ctrl+x", submitColor) },
|
|
751
|
-
{ width: 6, render: () => keyOnly("esc") },
|
|
752
|
-
];
|
|
753
|
-
const narrow = [
|
|
754
|
-
{ width: 6, render: () => keyOnly("ctrl+x", submitColor) },
|
|
755
|
-
{ width: 6, render: () => keyOnly("esc") },
|
|
756
|
-
];
|
|
658
|
+
function renderFieldBottom(width: number, borderColor: string): string {
|
|
659
|
+
return paint("╰" + "─".repeat(Math.max(0, width - 2)) + "╯", borderColor);
|
|
660
|
+
}
|
|
757
661
|
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
662
|
+
function renderFieldMeta(
|
|
663
|
+
field: WorkflowInputEntry,
|
|
664
|
+
invalid: string | null,
|
|
665
|
+
theme: GraphTheme,
|
|
666
|
+
width: number,
|
|
667
|
+
): string[] {
|
|
668
|
+
const required = field.required ? "required" : "optional";
|
|
669
|
+
const text = field.description && field.description.length > 0
|
|
670
|
+
? `${field.type} · ${required} · ${field.description}`
|
|
671
|
+
: `${field.type} · ${required}`;
|
|
672
|
+
const lines = wrapPlainText(text, width).map((line) => paintRequiredMetaLine(line, field.required === true, theme));
|
|
673
|
+
if (invalid) lines.push(...wrapPlainText(invalid, width).map((line) => paint(line, theme.error)));
|
|
674
|
+
return lines;
|
|
766
675
|
}
|
|
767
676
|
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
const { theme, workflowName, fields, state, width } = opts;
|
|
775
|
-
const values = coerceValues(fields, state.rawText);
|
|
776
|
-
const head =
|
|
777
|
-
paint("✓ ", theme.success) +
|
|
778
|
-
paint("ready to run", theme.text, { bold: true });
|
|
779
|
-
const cmdParts: string[] = [
|
|
780
|
-
paint("/workflow ", theme.dim) + paint(workflowName, theme.text),
|
|
781
|
-
];
|
|
782
|
-
for (const f of fields) {
|
|
783
|
-
if (values[f.name] === undefined) continue;
|
|
784
|
-
const shown = shortVal(String(state.rawText[f.name] ?? ""));
|
|
785
|
-
cmdParts.push(
|
|
786
|
-
paint(" ", theme.dim) +
|
|
787
|
-
paint(f.name, theme.text) +
|
|
788
|
-
paint("=", theme.dim) +
|
|
789
|
-
paint(shown, theme.text),
|
|
790
|
-
);
|
|
791
|
-
}
|
|
792
|
-
const prompt =
|
|
793
|
-
paint("submit this workflow? ", theme.dim) +
|
|
794
|
-
paint("y", theme.success, { bold: true }) +
|
|
795
|
-
paint(" submit", theme.dim) +
|
|
796
|
-
paint(" · ", theme.dim) +
|
|
797
|
-
paint("n", theme.error, { bold: true }) +
|
|
798
|
-
paint(" cancel", theme.dim);
|
|
799
|
-
return [
|
|
800
|
-
truncateToWidth(head, width, "…", true),
|
|
801
|
-
"",
|
|
802
|
-
...cmdParts.map((row) => truncateToWidth(row, width, "…", true)),
|
|
803
|
-
"",
|
|
804
|
-
truncateToWidth(prompt, width, "…", true),
|
|
805
|
-
];
|
|
677
|
+
function paintRequiredMetaLine(line: string, required: boolean, theme: GraphTheme): string {
|
|
678
|
+
if (!required) return paint(line, theme.textMuted);
|
|
679
|
+
return line
|
|
680
|
+
.split(/(\brequired\b)/g)
|
|
681
|
+
.map((part) => part === "required" ? paint(part, theme.warning) : paint(part, theme.textMuted))
|
|
682
|
+
.join("");
|
|
806
683
|
}
|
|
807
684
|
|
|
808
|
-
function
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
685
|
+
function renderPickerSubmitControls(
|
|
686
|
+
fields: readonly WorkflowInputEntry[],
|
|
687
|
+
state: InputsPickerState,
|
|
688
|
+
theme: GraphTheme,
|
|
689
|
+
width: number,
|
|
690
|
+
): string[] {
|
|
691
|
+
const invalid = computeInvalid(fields, state.rawText);
|
|
692
|
+
return renderSubmitControls({
|
|
693
|
+
invalidFieldNames: invalid.map((i) => fields[i]!.name),
|
|
694
|
+
submitFocused: state.focusedIdx === fields.length,
|
|
695
|
+
theme,
|
|
696
|
+
width,
|
|
697
|
+
});
|
|
812
698
|
}
|
|
813
699
|
|
|
814
700
|
// ---------------------------------------------------------------------------
|
|
@@ -822,18 +708,17 @@ function shortVal(s: string): string {
|
|
|
822
708
|
* with the coerced typed value map.
|
|
823
709
|
*
|
|
824
710
|
* Keys (form mode):
|
|
825
|
-
* tab
|
|
826
|
-
* shift+tab /
|
|
711
|
+
* tab — switch input fields, then the final Submit action
|
|
712
|
+
* shift+tab — previous input field / final Submit action
|
|
827
713
|
* left / right — select: cycle choices; boolean: flip; text: caret
|
|
828
714
|
* space — boolean: flip
|
|
829
715
|
* enter — text: newline; otherwise: next field
|
|
830
|
-
* ctrl+x — open confirm modal (if all required filled)
|
|
831
716
|
* backspace — delete char left of caret
|
|
832
717
|
* esc / ctrl+c — close picker without running
|
|
833
718
|
*
|
|
834
|
-
* Keys (
|
|
835
|
-
*
|
|
836
|
-
*
|
|
719
|
+
* Keys (Submit action):
|
|
720
|
+
* up / down — move back into the question list
|
|
721
|
+
* enter — submit immediately, or focus the first invalid field
|
|
837
722
|
*/
|
|
838
723
|
export function handleInputsPickerInput(
|
|
839
724
|
key: string,
|
|
@@ -848,7 +733,6 @@ export function handleInputsPickerInput(
|
|
|
848
733
|
if (isCancelKey(key)) return { kind: "cancel" };
|
|
849
734
|
return { kind: "noop" };
|
|
850
735
|
}
|
|
851
|
-
if (state.confirmOpen) return handleConfirmKey(key, state, fields);
|
|
852
736
|
return handleFormKey(key, state, fields, keybindings);
|
|
853
737
|
}
|
|
854
738
|
|
|
@@ -860,15 +744,15 @@ function handleFormKey(
|
|
|
860
744
|
): InputsPickerAction {
|
|
861
745
|
// ── Global navigation (workflow form contract, not Pi actions) ──
|
|
862
746
|
if (isCancelKey(key)) return { kind: "cancel" };
|
|
863
|
-
if (matchesKey(key,
|
|
864
|
-
if (matchesKey(key, "tab")) {
|
|
747
|
+
if (matchesKey(key, Key.tab)) {
|
|
865
748
|
moveFocus(state, fields, +1);
|
|
866
749
|
return { kind: "noop" };
|
|
867
750
|
}
|
|
868
|
-
if (matchesKey(key, "
|
|
751
|
+
if (matchesKey(key, Key.shift("tab"))) {
|
|
869
752
|
moveFocus(state, fields, -1);
|
|
870
753
|
return { kind: "noop" };
|
|
871
754
|
}
|
|
755
|
+
if (state.focusedIdx === fields.length) return handleSubmitKey(key, state, fields, kb);
|
|
872
756
|
|
|
873
757
|
const field = fields[state.focusedIdx]!;
|
|
874
758
|
const name = field.name;
|
|
@@ -887,7 +771,7 @@ function handleFormKey(
|
|
|
887
771
|
// KeybindingsManager so user-configured bindings work uniformly.
|
|
888
772
|
const caret = Math.max(0, Math.min(state.caret, cur.length));
|
|
889
773
|
|
|
890
|
-
if (matchesAction(kb, key,
|
|
774
|
+
if (matchesAction(kb, key, TUI_ACTION.editorCursorUp)) {
|
|
891
775
|
if (field.type === "text") {
|
|
892
776
|
const nextCaret = caretLineUp(cur, caret);
|
|
893
777
|
if (nextCaret !== null) {
|
|
@@ -898,7 +782,7 @@ function handleFormKey(
|
|
|
898
782
|
moveFocus(state, fields, -1);
|
|
899
783
|
return { kind: "noop" };
|
|
900
784
|
}
|
|
901
|
-
if (matchesAction(kb, key,
|
|
785
|
+
if (matchesAction(kb, key, TUI_ACTION.editorCursorDown)) {
|
|
902
786
|
if (field.type === "text") {
|
|
903
787
|
const nextCaret = caretLineDown(cur, caret);
|
|
904
788
|
if (nextCaret !== null) {
|
|
@@ -925,11 +809,11 @@ function handleFormKey(
|
|
|
925
809
|
state.caret = lineEnd(cur, caret);
|
|
926
810
|
return { kind: "noop" };
|
|
927
811
|
}
|
|
928
|
-
if (matchesAction(kb, key,
|
|
812
|
+
if (matchesAction(kb, key, TUI_ACTION.editorCursorLeft)) {
|
|
929
813
|
state.caret = previousGraphemeOffset(cur, caret);
|
|
930
814
|
return { kind: "noop" };
|
|
931
815
|
}
|
|
932
|
-
if (matchesAction(kb, key,
|
|
816
|
+
if (matchesAction(kb, key, TUI_ACTION.editorCursorRight)) {
|
|
933
817
|
state.caret = nextGraphemeOffset(cur, caret);
|
|
934
818
|
return { kind: "noop" };
|
|
935
819
|
}
|
|
@@ -978,7 +862,7 @@ function handleFormKey(
|
|
|
978
862
|
return { kind: "noop" };
|
|
979
863
|
}
|
|
980
864
|
if (
|
|
981
|
-
matchesAction(kb, key,
|
|
865
|
+
matchesAction(kb, key, TUI_ACTION.inputSubmit) ||
|
|
982
866
|
matchesAction(kb, key, "tui.input.newLine")
|
|
983
867
|
) {
|
|
984
868
|
if (field.type === "text") {
|
|
@@ -1012,19 +896,15 @@ function handleSelectKey(
|
|
|
1012
896
|
if (choices.length === 0) return { kind: "noop" };
|
|
1013
897
|
const current = state.rawText[field.name] ?? choices[0]!;
|
|
1014
898
|
const idx = Math.max(0, choices.indexOf(current));
|
|
1015
|
-
if (matchesAction(kb, key,
|
|
899
|
+
if (matchesAction(kb, key, TUI_ACTION.selectUp) || matchesAction(kb, key, TUI_ACTION.editorCursorLeft)) {
|
|
1016
900
|
state.rawText[field.name] = choices[(idx - 1 + choices.length) % choices.length]!;
|
|
1017
901
|
return { kind: "noop" };
|
|
1018
902
|
}
|
|
1019
|
-
if (matchesAction(kb, key,
|
|
903
|
+
if (matchesAction(kb, key, TUI_ACTION.selectDown) || matchesAction(kb, key, TUI_ACTION.editorCursorRight)) {
|
|
1020
904
|
state.rawText[field.name] = choices[(idx + 1) % choices.length]!;
|
|
1021
905
|
return { kind: "noop" };
|
|
1022
906
|
}
|
|
1023
|
-
if (matchesAction(kb, key,
|
|
1024
|
-
moveFocus(state, fields, -1);
|
|
1025
|
-
return { kind: "noop" };
|
|
1026
|
-
}
|
|
1027
|
-
if (matchesAction(kb, key, "tui.editor.cursorDown")) {
|
|
907
|
+
if (matchesAction(kb, key, TUI_ACTION.selectConfirm) || matchesAction(kb, key, TUI_ACTION.inputSubmit)) {
|
|
1028
908
|
moveFocus(state, fields, +1);
|
|
1029
909
|
return { kind: "noop" };
|
|
1030
910
|
}
|
|
@@ -1039,46 +919,48 @@ function handleBooleanKey(
|
|
|
1039
919
|
kb: KeybindingsLike | undefined,
|
|
1040
920
|
): InputsPickerAction {
|
|
1041
921
|
if (
|
|
1042
|
-
matchesKey(key,
|
|
1043
|
-
matchesAction(kb, key,
|
|
1044
|
-
matchesAction(kb, key,
|
|
1045
|
-
matchesAction(kb, key,
|
|
922
|
+
matchesKey(key, Key.space) ||
|
|
923
|
+
matchesAction(kb, key, TUI_ACTION.selectUp) ||
|
|
924
|
+
matchesAction(kb, key, TUI_ACTION.selectDown) ||
|
|
925
|
+
matchesAction(kb, key, TUI_ACTION.editorCursorLeft) ||
|
|
926
|
+
matchesAction(kb, key, TUI_ACTION.editorCursorRight)
|
|
1046
927
|
) {
|
|
1047
928
|
state.rawText[field.name] = state.rawText[field.name] === "true" ? "false" : "true";
|
|
1048
929
|
return { kind: "noop" };
|
|
1049
930
|
}
|
|
1050
|
-
if (matchesAction(kb, key,
|
|
1051
|
-
moveFocus(state, fields, -1);
|
|
1052
|
-
return { kind: "noop" };
|
|
1053
|
-
}
|
|
1054
|
-
if (matchesAction(kb, key, "tui.editor.cursorDown")) {
|
|
931
|
+
if (matchesAction(kb, key, TUI_ACTION.selectConfirm) || matchesAction(kb, key, TUI_ACTION.inputSubmit)) {
|
|
1055
932
|
moveFocus(state, fields, +1);
|
|
1056
933
|
return { kind: "noop" };
|
|
1057
934
|
}
|
|
1058
935
|
return { kind: "noop" };
|
|
1059
936
|
}
|
|
1060
937
|
|
|
1061
|
-
function
|
|
938
|
+
function handleSubmitKey(
|
|
1062
939
|
key: string,
|
|
1063
940
|
state: InputsPickerState,
|
|
1064
941
|
fields: readonly WorkflowInputEntry[],
|
|
942
|
+
kb: KeybindingsLike | undefined,
|
|
1065
943
|
): InputsPickerAction {
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
if (key === "y" || key === "Y" || matchesKey(key, "enter")) {
|
|
1070
|
-
return { kind: "run", values: coerceValues(fields, state.rawText) };
|
|
944
|
+
if (matchesAction(kb, key, TUI_ACTION.selectUp) || matchesAction(kb, key, TUI_ACTION.editorCursorUp)) {
|
|
945
|
+
moveFocus(state, fields, -1);
|
|
946
|
+
return { kind: "noop" };
|
|
1071
947
|
}
|
|
1072
|
-
if (
|
|
1073
|
-
|
|
1074
|
-
state.confirmOpen = false;
|
|
948
|
+
if (matchesAction(kb, key, TUI_ACTION.selectDown) || matchesAction(kb, key, TUI_ACTION.editorCursorDown)) {
|
|
949
|
+
moveFocus(state, fields, +1);
|
|
1075
950
|
return { kind: "noop" };
|
|
1076
951
|
}
|
|
952
|
+
if (
|
|
953
|
+
matchesKey(key, Key.enter) ||
|
|
954
|
+
matchesAction(kb, key, TUI_ACTION.selectConfirm) ||
|
|
955
|
+
matchesAction(kb, key, TUI_ACTION.inputSubmit)
|
|
956
|
+
) {
|
|
957
|
+
return attemptPickerSubmit(state, fields);
|
|
958
|
+
}
|
|
1077
959
|
return { kind: "noop" };
|
|
1078
960
|
}
|
|
1079
961
|
|
|
1080
962
|
function isCancelKey(key: string): boolean {
|
|
1081
|
-
return matchesKey(key, "
|
|
963
|
+
return matchesKey(key, Key.ctrl("c")) || matchesKey(key, Key.escape);
|
|
1082
964
|
}
|
|
1083
965
|
|
|
1084
966
|
function attemptPickerSubmit(
|
|
@@ -1088,13 +970,13 @@ function attemptPickerSubmit(
|
|
|
1088
970
|
const invalid = computeInvalid(fields, state.rawText);
|
|
1089
971
|
if (invalid.length > 0) {
|
|
1090
972
|
state.invalidIndices = invalid;
|
|
973
|
+
state.submitChoiceIdx = 0;
|
|
1091
974
|
state.focusedIdx = invalid[0]!;
|
|
1092
975
|
state.caret = (state.rawText[fields[state.focusedIdx]!.name] ?? "").length;
|
|
1093
976
|
return { kind: "noop" };
|
|
1094
977
|
}
|
|
1095
978
|
state.invalidIndices = [];
|
|
1096
|
-
state.
|
|
1097
|
-
return { kind: "noop" };
|
|
979
|
+
return { kind: "run", values: coerceValues(fields, state.rawText) };
|
|
1098
980
|
}
|
|
1099
981
|
|
|
1100
982
|
function moveFocus(
|
|
@@ -1102,9 +984,14 @@ function moveFocus(
|
|
|
1102
984
|
fields: readonly WorkflowInputEntry[],
|
|
1103
985
|
delta: number,
|
|
1104
986
|
): void {
|
|
1105
|
-
const n = fields.length;
|
|
1106
|
-
if (n
|
|
987
|
+
const n = fields.length + 1;
|
|
988
|
+
if (n <= 1) return;
|
|
1107
989
|
state.focusedIdx = (state.focusedIdx + delta + n) % n;
|
|
990
|
+
if (state.focusedIdx === fields.length) {
|
|
991
|
+
state.caret = 0;
|
|
992
|
+
state.submitChoiceIdx = 0;
|
|
993
|
+
return;
|
|
994
|
+
}
|
|
1108
995
|
const next = fields[state.focusedIdx]!;
|
|
1109
996
|
state.caret = (state.rawText[next.name] ?? "").length;
|
|
1110
997
|
}
|