@butlerw/vellum 0.1.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.
- package/LICENSE +21 -0
- package/README.md +411 -0
- package/__fixtures__/responses/code-generation.json +42 -0
- package/__fixtures__/responses/error-response.json +20 -0
- package/__fixtures__/responses/hello-world.json +32 -0
- package/dist/auth-6MCXESOH.js +26 -0
- package/dist/chunk-SECXJGWA.js +597 -0
- package/dist/index.js +34023 -0
- package/package.json +67 -0
- package/src/__tests__/commands.e2e.test.ts +728 -0
- package/src/__tests__/credentials.test.ts +713 -0
- package/src/__tests__/mode-e2e.test.ts +391 -0
- package/src/__tests__/tui-integration.test.tsx +1271 -0
- package/src/agents/__tests__/task-persistence.test.ts +235 -0
- package/src/agents/commands/delegate.ts +240 -0
- package/src/agents/commands/index.ts +10 -0
- package/src/agents/commands/resume.ts +335 -0
- package/src/agents/index.ts +29 -0
- package/src/agents/task-persistence.ts +272 -0
- package/src/agents/task-resumption.ts +242 -0
- package/src/app.tsx +4737 -0
- package/src/commands/__tests__/.gitkeep +1 -0
- package/src/commands/__tests__/agents.test.ts +606 -0
- package/src/commands/__tests__/auth.test.ts +626 -0
- package/src/commands/__tests__/autocomplete.test.ts +683 -0
- package/src/commands/__tests__/batch.test.ts +287 -0
- package/src/commands/__tests__/chain-pipe-parser.test.ts +654 -0
- package/src/commands/__tests__/completion.test.ts +238 -0
- package/src/commands/__tests__/core.test.ts +363 -0
- package/src/commands/__tests__/executor.test.ts +496 -0
- package/src/commands/__tests__/exit-codes.test.ts +220 -0
- package/src/commands/__tests__/init.test.ts +243 -0
- package/src/commands/__tests__/language.test.ts +353 -0
- package/src/commands/__tests__/mode-cli.test.ts +667 -0
- package/src/commands/__tests__/model.test.ts +277 -0
- package/src/commands/__tests__/parser.test.ts +493 -0
- package/src/commands/__tests__/performance.bench.ts +380 -0
- package/src/commands/__tests__/registry.test.ts +534 -0
- package/src/commands/__tests__/resume.test.ts +449 -0
- package/src/commands/__tests__/security.test.ts +845 -0
- package/src/commands/__tests__/stream-json.test.ts +372 -0
- package/src/commands/__tests__/user-commands.test.ts +597 -0
- package/src/commands/adapters.ts +267 -0
- package/src/commands/agent.ts +395 -0
- package/src/commands/agents/generate.ts +506 -0
- package/src/commands/agents/index.ts +272 -0
- package/src/commands/agents/show.ts +271 -0
- package/src/commands/agents/validate.ts +387 -0
- package/src/commands/auth.ts +883 -0
- package/src/commands/autocomplete.ts +480 -0
- package/src/commands/batch/command.ts +388 -0
- package/src/commands/batch/executor.ts +361 -0
- package/src/commands/batch/index.ts +12 -0
- package/src/commands/commit.ts +235 -0
- package/src/commands/completion/index.ts +371 -0
- package/src/commands/condense.ts +191 -0
- package/src/commands/config.ts +344 -0
- package/src/commands/context-provider.ts +173 -0
- package/src/commands/copy.ts +329 -0
- package/src/commands/core/clear.ts +38 -0
- package/src/commands/core/exit.ts +43 -0
- package/src/commands/core/help.ts +354 -0
- package/src/commands/core/index.ts +15 -0
- package/src/commands/cost.ts +179 -0
- package/src/commands/credentials.tsx +618 -0
- package/src/commands/custom-agents/__tests__/custom-agents.test.ts +709 -0
- package/src/commands/custom-agents/create.ts +377 -0
- package/src/commands/custom-agents/export.ts +135 -0
- package/src/commands/custom-agents/import.ts +199 -0
- package/src/commands/custom-agents/index.ts +372 -0
- package/src/commands/custom-agents/info.ts +318 -0
- package/src/commands/custom-agents/list.ts +267 -0
- package/src/commands/custom-agents/validate.ts +388 -0
- package/src/commands/diff-mode.ts +241 -0
- package/src/commands/env.ts +53 -0
- package/src/commands/executor.ts +579 -0
- package/src/commands/exit-codes.ts +202 -0
- package/src/commands/index.ts +701 -0
- package/src/commands/init/index.ts +15 -0
- package/src/commands/init/prompts.ts +366 -0
- package/src/commands/init/templates/commands-readme.md +80 -0
- package/src/commands/init/templates/example-command.md +79 -0
- package/src/commands/init/templates/example-skill.md +168 -0
- package/src/commands/init/templates/example-workflow.md +101 -0
- package/src/commands/init/templates/prompts-readme.md +52 -0
- package/src/commands/init/templates/rules-readme.md +63 -0
- package/src/commands/init/templates/skills-readme.md +83 -0
- package/src/commands/init/templates/workflows-readme.md +94 -0
- package/src/commands/init.ts +391 -0
- package/src/commands/install.ts +90 -0
- package/src/commands/language.ts +191 -0
- package/src/commands/loaders/.gitkeep +1 -0
- package/src/commands/lsp.ts +199 -0
- package/src/commands/markdown-commands.ts +253 -0
- package/src/commands/mcp.ts +588 -0
- package/src/commands/memory/export.ts +341 -0
- package/src/commands/memory/index.ts +148 -0
- package/src/commands/memory/list.ts +261 -0
- package/src/commands/memory/search.ts +346 -0
- package/src/commands/memory/utils.ts +15 -0
- package/src/commands/metrics.ts +75 -0
- package/src/commands/migrate/index.ts +16 -0
- package/src/commands/migrate/prompts.ts +477 -0
- package/src/commands/mode.ts +331 -0
- package/src/commands/model.ts +298 -0
- package/src/commands/onboard.ts +205 -0
- package/src/commands/open.ts +169 -0
- package/src/commands/output/stream-json.ts +373 -0
- package/src/commands/parser/chain-parser.ts +370 -0
- package/src/commands/parser/index.ts +29 -0
- package/src/commands/parser/pipe-parser.ts +480 -0
- package/src/commands/parser.ts +588 -0
- package/src/commands/persistence.ts +355 -0
- package/src/commands/progress.ts +18 -0
- package/src/commands/prompt/index.ts +17 -0
- package/src/commands/prompt/validate.ts +621 -0
- package/src/commands/prompt-priority.ts +401 -0
- package/src/commands/registry.ts +374 -0
- package/src/commands/sandbox/index.ts +131 -0
- package/src/commands/security/index.ts +21 -0
- package/src/commands/security/input-sanitizer.ts +168 -0
- package/src/commands/security/permission-checker.ts +456 -0
- package/src/commands/security/sensitive-data.ts +350 -0
- package/src/commands/session/delete.ts +38 -0
- package/src/commands/session/export.ts +39 -0
- package/src/commands/session/index.ts +26 -0
- package/src/commands/session/list.ts +26 -0
- package/src/commands/session/resume.ts +562 -0
- package/src/commands/session/search.ts +434 -0
- package/src/commands/session/show.ts +26 -0
- package/src/commands/settings.ts +368 -0
- package/src/commands/setup.ts +23 -0
- package/src/commands/shell/index.ts +16 -0
- package/src/commands/shell/setup.ts +422 -0
- package/src/commands/shell-init.ts +50 -0
- package/src/commands/shell-integration/index.ts +194 -0
- package/src/commands/skill.ts +1220 -0
- package/src/commands/spec.ts +558 -0
- package/src/commands/status.ts +246 -0
- package/src/commands/theme.ts +211 -0
- package/src/commands/think.ts +551 -0
- package/src/commands/trust.ts +211 -0
- package/src/commands/tutorial.ts +522 -0
- package/src/commands/types.ts +512 -0
- package/src/commands/update.ts +274 -0
- package/src/commands/usage.ts +213 -0
- package/src/commands/user-commands.ts +630 -0
- package/src/commands/utils.ts +142 -0
- package/src/commands/vim.ts +152 -0
- package/src/commands/workflow.ts +257 -0
- package/src/components/header.tsx +25 -0
- package/src/components/input.tsx +25 -0
- package/src/components/message-list.tsx +32 -0
- package/src/components/status-bar.tsx +23 -0
- package/src/index.tsx +614 -0
- package/src/onboarding/__tests__/tutorial.test.ts +740 -0
- package/src/onboarding/index.ts +69 -0
- package/src/onboarding/tips/index.ts +9 -0
- package/src/onboarding/tips/tip-engine.ts +459 -0
- package/src/onboarding/tutorial/index.ts +88 -0
- package/src/onboarding/tutorial/lessons/basics.ts +151 -0
- package/src/onboarding/tutorial/lessons/index.ts +151 -0
- package/src/onboarding/tutorial/lessons/modes.ts +230 -0
- package/src/onboarding/tutorial/lessons/tools.ts +172 -0
- package/src/onboarding/tutorial/progress-tracker.ts +350 -0
- package/src/onboarding/tutorial/storage.ts +249 -0
- package/src/onboarding/tutorial/tutorial-system.ts +462 -0
- package/src/onboarding/tutorial/types.ts +310 -0
- package/src/orchestrator-singleton.ts +129 -0
- package/src/shutdown.ts +33 -0
- package/src/test/e2e/assertions.ts +267 -0
- package/src/test/e2e/fixtures.ts +204 -0
- package/src/test/e2e/harness.ts +575 -0
- package/src/test/e2e/index.ts +57 -0
- package/src/test/e2e/types.ts +228 -0
- package/src/test/fixtures/__tests__/fake-response-loader.test.ts +314 -0
- package/src/test/fixtures/fake-response-loader.ts +314 -0
- package/src/test/fixtures/index.ts +20 -0
- package/src/tui/__tests__/mcp-panel.test.tsx +82 -0
- package/src/tui/__tests__/mcp-wiring.test.tsx +78 -0
- package/src/tui/__tests__/mode-components.test.tsx +395 -0
- package/src/tui/__tests__/permission-ask-flow.test.tsx +138 -0
- package/src/tui/__tests__/sidebar-panel-data.test.tsx +148 -0
- package/src/tui/__tests__/tools-panel-hotkeys.test.tsx +41 -0
- package/src/tui/adapters/agent-adapter.ts +1008 -0
- package/src/tui/adapters/index.ts +48 -0
- package/src/tui/adapters/message-adapter.ts +315 -0
- package/src/tui/adapters/persistence-bridge.ts +331 -0
- package/src/tui/adapters/session-adapter.ts +419 -0
- package/src/tui/buffered-stdout.ts +223 -0
- package/src/tui/components/AgentProgress.tsx +424 -0
- package/src/tui/components/Banner/AsciiArt.ts +160 -0
- package/src/tui/components/Banner/Banner.tsx +355 -0
- package/src/tui/components/Banner/ShimmerContext.tsx +131 -0
- package/src/tui/components/Banner/ShimmerText.tsx +193 -0
- package/src/tui/components/Banner/TypeWriterGradient.tsx +321 -0
- package/src/tui/components/Banner/index.ts +61 -0
- package/src/tui/components/Banner/useShimmer.ts +241 -0
- package/src/tui/components/ChatView.tsx +11 -0
- package/src/tui/components/Checkpoint/CheckpointDiffView.tsx +371 -0
- package/src/tui/components/Checkpoint/SnapshotCheckpointPanel.tsx +440 -0
- package/src/tui/components/Checkpoint/index.ts +19 -0
- package/src/tui/components/CostDisplay.tsx +226 -0
- package/src/tui/components/InitErrorBanner.tsx +122 -0
- package/src/tui/components/Input/Autocomplete.tsx +603 -0
- package/src/tui/components/Input/EnhancedCommandInput.tsx +471 -0
- package/src/tui/components/Input/HighlightedText.tsx +236 -0
- package/src/tui/components/Input/MentionAutocomplete.tsx +375 -0
- package/src/tui/components/Input/TextInput.tsx +1002 -0
- package/src/tui/components/Input/__tests__/Autocomplete.test.tsx +374 -0
- package/src/tui/components/Input/__tests__/TextInput.test.tsx +241 -0
- package/src/tui/components/Input/__tests__/highlight.test.ts +219 -0
- package/src/tui/components/Input/__tests__/slash-command-utils.test.ts +104 -0
- package/src/tui/components/Input/highlight.ts +362 -0
- package/src/tui/components/Input/index.ts +36 -0
- package/src/tui/components/Input/slash-command-utils.ts +135 -0
- package/src/tui/components/Layout.tsx +432 -0
- package/src/tui/components/McpPanel.tsx +137 -0
- package/src/tui/components/MemoryPanel.tsx +448 -0
- package/src/tui/components/Messages/CodeBlock.tsx +527 -0
- package/src/tui/components/Messages/DiffView.tsx +679 -0
- package/src/tui/components/Messages/ImageReference.tsx +89 -0
- package/src/tui/components/Messages/MarkdownBlock.tsx +228 -0
- package/src/tui/components/Messages/MarkdownRenderer.tsx +498 -0
- package/src/tui/components/Messages/MessageBubble.tsx +270 -0
- package/src/tui/components/Messages/MessageList.tsx +1719 -0
- package/src/tui/components/Messages/StreamingText.tsx +216 -0
- package/src/tui/components/Messages/ThinkingBlock.tsx +408 -0
- package/src/tui/components/Messages/ToolResultPreview.tsx +243 -0
- package/src/tui/components/Messages/__tests__/CodeBlock.test.tsx +296 -0
- package/src/tui/components/Messages/__tests__/DiffView.test.tsx +239 -0
- package/src/tui/components/Messages/__tests__/MarkdownRenderer.test.tsx +303 -0
- package/src/tui/components/Messages/__tests__/MessageBubble.test.tsx +268 -0
- package/src/tui/components/Messages/__tests__/MessageList.test.tsx +324 -0
- package/src/tui/components/Messages/__tests__/StreamingText.test.tsx +215 -0
- package/src/tui/components/Messages/index.ts +25 -0
- package/src/tui/components/ModeIndicator.tsx +177 -0
- package/src/tui/components/ModeSelector.tsx +216 -0
- package/src/tui/components/ModelSelector.tsx +339 -0
- package/src/tui/components/OnboardingWizard.tsx +670 -0
- package/src/tui/components/PhaseProgressIndicator.tsx +270 -0
- package/src/tui/components/RateLimitIndicator.tsx +82 -0
- package/src/tui/components/ScreenReaderLayout.tsx +295 -0
- package/src/tui/components/SettingsPanel.tsx +643 -0
- package/src/tui/components/Sidebar/SystemStatusPanel.tsx +284 -0
- package/src/tui/components/Sidebar/index.ts +9 -0
- package/src/tui/components/Status/ModelStatusBar.tsx +270 -0
- package/src/tui/components/Status/index.ts +12 -0
- package/src/tui/components/StatusBar/AgentModeIndicator.tsx +257 -0
- package/src/tui/components/StatusBar/ContextProgress.tsx +167 -0
- package/src/tui/components/StatusBar/FileChangesIndicator.tsx +62 -0
- package/src/tui/components/StatusBar/GitIndicator.tsx +89 -0
- package/src/tui/components/StatusBar/HeaderBar.tsx +126 -0
- package/src/tui/components/StatusBar/ModelIndicator.tsx +157 -0
- package/src/tui/components/StatusBar/PersistenceStatusIndicator.tsx +210 -0
- package/src/tui/components/StatusBar/ResilienceIndicator.tsx +106 -0
- package/src/tui/components/StatusBar/SandboxIndicator.tsx +167 -0
- package/src/tui/components/StatusBar/StatusBar.tsx +368 -0
- package/src/tui/components/StatusBar/ThinkingModeIndicator.tsx +170 -0
- package/src/tui/components/StatusBar/TokenBreakdown.tsx +246 -0
- package/src/tui/components/StatusBar/TokenCounter.tsx +135 -0
- package/src/tui/components/StatusBar/TrustModeIndicator.tsx +130 -0
- package/src/tui/components/StatusBar/WorkspaceIndicator.tsx +86 -0
- package/src/tui/components/StatusBar/__tests__/AgentModeIndicator.test.tsx +193 -0
- package/src/tui/components/StatusBar/__tests__/StatusBar.test.tsx +729 -0
- package/src/tui/components/StatusBar/index.ts +60 -0
- package/src/tui/components/TipBanner.tsx +115 -0
- package/src/tui/components/TodoItem.tsx +208 -0
- package/src/tui/components/TodoPanel.tsx +455 -0
- package/src/tui/components/Tools/ApprovalQueue.tsx +407 -0
- package/src/tui/components/Tools/OptionSelector.tsx +160 -0
- package/src/tui/components/Tools/PermissionDialog.tsx +286 -0
- package/src/tui/components/Tools/ToolParams.tsx +483 -0
- package/src/tui/components/Tools/ToolsPanel.tsx +178 -0
- package/src/tui/components/Tools/__tests__/PermissionDialog.test.tsx +510 -0
- package/src/tui/components/Tools/__tests__/ToolParams.test.tsx +432 -0
- package/src/tui/components/Tools/index.ts +21 -0
- package/src/tui/components/TrustPrompt.tsx +279 -0
- package/src/tui/components/UpdateBanner.tsx +166 -0
- package/src/tui/components/VimModeIndicator.tsx +112 -0
- package/src/tui/components/backtrack/BacktrackControls.tsx +402 -0
- package/src/tui/components/backtrack/index.ts +13 -0
- package/src/tui/components/common/AutoApprovalStatus.tsx +251 -0
- package/src/tui/components/common/CostWarning.tsx +294 -0
- package/src/tui/components/common/DynamicShortcutHints.tsx +209 -0
- package/src/tui/components/common/EnhancedLoadingIndicator.tsx +305 -0
- package/src/tui/components/common/ErrorBoundary.tsx +140 -0
- package/src/tui/components/common/GradientText.tsx +224 -0
- package/src/tui/components/common/HotkeyHelpModal.tsx +193 -0
- package/src/tui/components/common/HotkeyHints.tsx +70 -0
- package/src/tui/components/common/MaxSizedBox.tsx +354 -0
- package/src/tui/components/common/NewMessagesBadge.tsx +65 -0
- package/src/tui/components/common/ProtectedFileLegend.tsx +89 -0
- package/src/tui/components/common/ScrollIndicator.tsx +160 -0
- package/src/tui/components/common/Spinner.tsx +342 -0
- package/src/tui/components/common/StreamingIndicator.tsx +316 -0
- package/src/tui/components/common/VirtualizedList/VirtualizedList.tsx +428 -0
- package/src/tui/components/common/VirtualizedList/hooks/index.ts +19 -0
- package/src/tui/components/common/VirtualizedList/hooks/useBatchedScroll.ts +64 -0
- package/src/tui/components/common/VirtualizedList/hooks/useScrollAnchor.ts +290 -0
- package/src/tui/components/common/VirtualizedList/hooks/useVirtualization.ts +340 -0
- package/src/tui/components/common/VirtualizedList/index.ts +30 -0
- package/src/tui/components/common/VirtualizedList/types.ts +107 -0
- package/src/tui/components/common/__tests__/NewMessagesBadge.test.tsx +74 -0
- package/src/tui/components/common/__tests__/ScrollIndicator.test.tsx +193 -0
- package/src/tui/components/common/index.ts +110 -0
- package/src/tui/components/index.ts +79 -0
- package/src/tui/components/session/CheckpointPanel.tsx +323 -0
- package/src/tui/components/session/RollbackDialog.tsx +169 -0
- package/src/tui/components/session/SessionItem.tsx +136 -0
- package/src/tui/components/session/SessionListPanel.tsx +252 -0
- package/src/tui/components/session/SessionPicker.tsx +449 -0
- package/src/tui/components/session/SessionPreview.tsx +240 -0
- package/src/tui/components/session/__tests__/session.test.tsx +408 -0
- package/src/tui/components/session/index.ts +28 -0
- package/src/tui/components/session/types.ts +116 -0
- package/src/tui/components/theme/__tests__/tokens.test.ts +471 -0
- package/src/tui/components/theme/index.ts +227 -0
- package/src/tui/components/theme/tokens.ts +484 -0
- package/src/tui/config/defaults.ts +134 -0
- package/src/tui/config/index.ts +17 -0
- package/src/tui/context/AnimationContext.tsx +284 -0
- package/src/tui/context/AppContext.tsx +349 -0
- package/src/tui/context/BracketedPasteContext.tsx +372 -0
- package/src/tui/context/LspContext.tsx +192 -0
- package/src/tui/context/McpContext.tsx +325 -0
- package/src/tui/context/MessagesContext.tsx +870 -0
- package/src/tui/context/OverflowContext.tsx +213 -0
- package/src/tui/context/RateLimitContext.tsx +108 -0
- package/src/tui/context/ResilienceContext.tsx +275 -0
- package/src/tui/context/RootProvider.tsx +136 -0
- package/src/tui/context/ScrollContext.tsx +331 -0
- package/src/tui/context/ToolsContext.tsx +702 -0
- package/src/tui/context/__tests__/BracketedPasteContext.test.tsx +416 -0
- package/src/tui/context/index.ts +140 -0
- package/src/tui/enterprise-integration.ts +282 -0
- package/src/tui/hooks/__tests__/useBacktrack.test.tsx +138 -0
- package/src/tui/hooks/__tests__/useBracketedPaste.test.tsx +222 -0
- package/src/tui/hooks/__tests__/useCopyMode.test.tsx +336 -0
- package/src/tui/hooks/__tests__/useHotkeys.ctrl-input.test.tsx +96 -0
- package/src/tui/hooks/__tests__/useHotkeys.test.tsx +454 -0
- package/src/tui/hooks/__tests__/useInputHistory.test.tsx +660 -0
- package/src/tui/hooks/__tests__/useLineBuffer.test.ts +295 -0
- package/src/tui/hooks/__tests__/useModeController.test.ts +137 -0
- package/src/tui/hooks/__tests__/useModeShortcuts.test.tsx +142 -0
- package/src/tui/hooks/__tests__/useScrollController.test.ts +464 -0
- package/src/tui/hooks/__tests__/useVim.test.tsx +531 -0
- package/src/tui/hooks/index.ts +252 -0
- package/src/tui/hooks/useAgentLoop.ts +712 -0
- package/src/tui/hooks/useAlternateBuffer.ts +398 -0
- package/src/tui/hooks/useAnimatedScrollbar.ts +241 -0
- package/src/tui/hooks/useBacktrack.ts +443 -0
- package/src/tui/hooks/useBracketedPaste.ts +104 -0
- package/src/tui/hooks/useCollapsible.ts +240 -0
- package/src/tui/hooks/useCopyMode.ts +382 -0
- package/src/tui/hooks/useCostSummary.ts +75 -0
- package/src/tui/hooks/useDesktopNotification.ts +414 -0
- package/src/tui/hooks/useDiffMode.ts +44 -0
- package/src/tui/hooks/useFileChangeStats.ts +110 -0
- package/src/tui/hooks/useFileSuggestions.ts +284 -0
- package/src/tui/hooks/useFlickerDetector.ts +250 -0
- package/src/tui/hooks/useGitStatus.ts +200 -0
- package/src/tui/hooks/useHotkeys.ts +579 -0
- package/src/tui/hooks/useImagePaste.ts +114 -0
- package/src/tui/hooks/useInputHighlight.ts +145 -0
- package/src/tui/hooks/useInputHistory.ts +246 -0
- package/src/tui/hooks/useKeyboardScroll.ts +209 -0
- package/src/tui/hooks/useLineBuffer.ts +356 -0
- package/src/tui/hooks/useMentionAutocomplete.ts +235 -0
- package/src/tui/hooks/useModeController.ts +167 -0
- package/src/tui/hooks/useModeShortcuts.ts +196 -0
- package/src/tui/hooks/usePermissionHandler.ts +146 -0
- package/src/tui/hooks/usePersistence.ts +480 -0
- package/src/tui/hooks/usePersistenceShortcuts.ts +225 -0
- package/src/tui/hooks/usePlaceholderRotation.ts +143 -0
- package/src/tui/hooks/useProviderStatus.ts +270 -0
- package/src/tui/hooks/useRateLimitStatus.ts +90 -0
- package/src/tui/hooks/useScreenReader.ts +315 -0
- package/src/tui/hooks/useScrollController.ts +450 -0
- package/src/tui/hooks/useScrollEventBatcher.ts +185 -0
- package/src/tui/hooks/useSidebarPanelData.ts +115 -0
- package/src/tui/hooks/useSmoothScroll.ts +202 -0
- package/src/tui/hooks/useSnapshots.ts +300 -0
- package/src/tui/hooks/useStateAndRef.ts +50 -0
- package/src/tui/hooks/useTerminalSize.ts +206 -0
- package/src/tui/hooks/useToolApprovalController.ts +91 -0
- package/src/tui/hooks/useVim.ts +334 -0
- package/src/tui/hooks/useWorkspace.ts +56 -0
- package/src/tui/i18n/__tests__/init.test.ts +278 -0
- package/src/tui/i18n/__tests__/language-config.test.ts +199 -0
- package/src/tui/i18n/__tests__/locale-detection.test.ts +250 -0
- package/src/tui/i18n/__tests__/settings-integration.test.ts +262 -0
- package/src/tui/i18n/index.ts +72 -0
- package/src/tui/i18n/init.ts +131 -0
- package/src/tui/i18n/language-config.ts +106 -0
- package/src/tui/i18n/locale-detection.ts +173 -0
- package/src/tui/i18n/settings-integration.ts +557 -0
- package/src/tui/i18n/tui-namespace.ts +538 -0
- package/src/tui/i18n/types.ts +312 -0
- package/src/tui/index.ts +43 -0
- package/src/tui/lsp-integration.ts +409 -0
- package/src/tui/metrics-integration.ts +366 -0
- package/src/tui/plugins.ts +383 -0
- package/src/tui/resilience.ts +342 -0
- package/src/tui/sandbox-integration.ts +317 -0
- package/src/tui/services/clipboard.ts +348 -0
- package/src/tui/services/fuzzy-search.ts +441 -0
- package/src/tui/services/index.ts +72 -0
- package/src/tui/services/markdown-renderer.ts +565 -0
- package/src/tui/services/open-external.ts +247 -0
- package/src/tui/services/syntax-highlighter.ts +483 -0
- package/src/tui/slash-commands.ts +12 -0
- package/src/tui/theme/index.ts +15 -0
- package/src/tui/theme/provider.tsx +206 -0
- package/src/tui/tip-integration.ts +300 -0
- package/src/tui/types/__tests__/ink-extended.test.ts +121 -0
- package/src/tui/types/ink-extended.ts +87 -0
- package/src/tui/utils/__tests__/bracketedPaste.test.ts +231 -0
- package/src/tui/utils/__tests__/heightEstimator.test.ts +157 -0
- package/src/tui/utils/__tests__/text-width.test.ts +158 -0
- package/src/tui/utils/__tests__/textSanitizer.test.ts +266 -0
- package/src/tui/utils/__tests__/ui-sizing.test.ts +169 -0
- package/src/tui/utils/bracketedPaste.ts +107 -0
- package/src/tui/utils/cursor-manager.ts +131 -0
- package/src/tui/utils/detectTerminal.ts +596 -0
- package/src/tui/utils/findLastSafeSplitPoint.ts +92 -0
- package/src/tui/utils/heightEstimator.ts +198 -0
- package/src/tui/utils/index.ts +91 -0
- package/src/tui/utils/isNarrowWidth.ts +52 -0
- package/src/tui/utils/stdoutGuard.ts +90 -0
- package/src/tui/utils/synchronized-update.ts +70 -0
- package/src/tui/utils/text-width.ts +225 -0
- package/src/tui/utils/textSanitizer.ts +225 -0
- package/src/tui/utils/textUtils.ts +114 -0
- package/src/tui/utils/ui-sizing.ts +192 -0
- package/src/tui-blessed/app.ts +160 -0
- package/src/tui-blessed/index.ts +2 -0
- package/src/tui-blessed/neo-blessed.d.ts +6 -0
- package/src/tui-blessed/test.ts +21 -0
- package/src/tui-blessed/types.ts +14 -0
- package/src/utils/icons.ts +130 -0
- package/src/utils/index.ts +33 -0
- package/src/utils/resume-hint.ts +86 -0
- package/src/version.ts +1 -0
- package/tsconfig.json +8 -0
- package/vitest.config.ts +35 -0
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PhaseProgressIndicator Component (T047)
|
|
3
|
+
*
|
|
4
|
+
* TUI component for displaying visual progress through spec mode phases.
|
|
5
|
+
* Shows a segmented progress bar with 6 phases and current phase highlight.
|
|
6
|
+
*
|
|
7
|
+
* @module tui/components/PhaseProgressIndicator
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { SpecPhase } from "@vellum/core";
|
|
11
|
+
import { SPEC_PHASE_CONFIG, SPEC_PHASES } from "@vellum/core";
|
|
12
|
+
import { getIcons } from "@vellum/shared";
|
|
13
|
+
import { Box, Text } from "ink";
|
|
14
|
+
import type React from "react";
|
|
15
|
+
import { useMemo } from "react";
|
|
16
|
+
import { useTheme } from "../theme/index.js";
|
|
17
|
+
|
|
18
|
+
// =============================================================================
|
|
19
|
+
// Types
|
|
20
|
+
// =============================================================================
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Props for the PhaseProgressIndicator component.
|
|
24
|
+
*/
|
|
25
|
+
export interface PhaseProgressIndicatorProps {
|
|
26
|
+
/** Current phase number (1-6) */
|
|
27
|
+
readonly currentPhase: number;
|
|
28
|
+
/** Total number of phases (default: 6) */
|
|
29
|
+
readonly totalPhases?: number;
|
|
30
|
+
/** Whether to show phase names */
|
|
31
|
+
readonly showLabels?: boolean;
|
|
32
|
+
/** Whether to show percentage */
|
|
33
|
+
readonly showPercentage?: boolean;
|
|
34
|
+
/** Width of the progress bar in characters (default: 24, 4 per phase) */
|
|
35
|
+
readonly width?: number;
|
|
36
|
+
/** Display orientation */
|
|
37
|
+
readonly orientation?: "horizontal" | "vertical";
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// =============================================================================
|
|
41
|
+
// Constants
|
|
42
|
+
// =============================================================================
|
|
43
|
+
|
|
44
|
+
/** Default progress bar width (4 chars per 6 phases) */
|
|
45
|
+
const DEFAULT_WIDTH = 24;
|
|
46
|
+
|
|
47
|
+
/** Default total phases */
|
|
48
|
+
const DEFAULT_TOTAL_PHASES = 6;
|
|
49
|
+
|
|
50
|
+
/** Progress bar characters */
|
|
51
|
+
const PROGRESS_CHARS = {
|
|
52
|
+
filled: "█",
|
|
53
|
+
current: "▓",
|
|
54
|
+
empty: "░",
|
|
55
|
+
separatorFilled: "│",
|
|
56
|
+
separatorEmpty: "┊",
|
|
57
|
+
} as const;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Get phase status icons using the icon system for proper Unicode/ASCII support.
|
|
61
|
+
*/
|
|
62
|
+
function getPhaseStatusIcons() {
|
|
63
|
+
const icons = getIcons();
|
|
64
|
+
return {
|
|
65
|
+
completed: icons.check,
|
|
66
|
+
current: icons.bullet,
|
|
67
|
+
pending: icons.pending,
|
|
68
|
+
} as const;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// =============================================================================
|
|
72
|
+
// Helper Functions
|
|
73
|
+
// =============================================================================
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Get the display name for a phase number.
|
|
77
|
+
*/
|
|
78
|
+
function getPhaseName(phaseNumber: number): string {
|
|
79
|
+
const phase = SPEC_PHASES[phaseNumber - 1] as SpecPhase | undefined;
|
|
80
|
+
if (!phase) {
|
|
81
|
+
return `Phase ${phaseNumber}`;
|
|
82
|
+
}
|
|
83
|
+
return SPEC_PHASE_CONFIG[phase].name;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Calculate completion percentage.
|
|
88
|
+
*/
|
|
89
|
+
function calculatePercentage(current: number, total: number): number {
|
|
90
|
+
if (total <= 0) return 0;
|
|
91
|
+
// Phase N is complete when we're at phase N+1
|
|
92
|
+
// So current phase means we've completed (current - 1) phases
|
|
93
|
+
const completed = Math.max(0, current - 1);
|
|
94
|
+
return Math.round((completed / total) * 100);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// =============================================================================
|
|
98
|
+
// PhaseProgressIndicator Component
|
|
99
|
+
// =============================================================================
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* PhaseProgressIndicator - Visual progress display for spec mode phases.
|
|
103
|
+
*
|
|
104
|
+
* Features:
|
|
105
|
+
* - 6-segment progress bar
|
|
106
|
+
* - Color-coded segments (completed, current, pending)
|
|
107
|
+
* - Optional phase labels
|
|
108
|
+
* - Optional percentage display
|
|
109
|
+
* - Horizontal or vertical orientation
|
|
110
|
+
*
|
|
111
|
+
* @example
|
|
112
|
+
* ```tsx
|
|
113
|
+
* // Basic usage
|
|
114
|
+
* <PhaseProgressIndicator currentPhase={3} />
|
|
115
|
+
*
|
|
116
|
+
* // With labels and percentage
|
|
117
|
+
* <PhaseProgressIndicator
|
|
118
|
+
* currentPhase={4}
|
|
119
|
+
* showLabels
|
|
120
|
+
* showPercentage
|
|
121
|
+
* />
|
|
122
|
+
*
|
|
123
|
+
* // Vertical orientation
|
|
124
|
+
* <PhaseProgressIndicator
|
|
125
|
+
* currentPhase={2}
|
|
126
|
+
* orientation="vertical"
|
|
127
|
+
* />
|
|
128
|
+
* ```
|
|
129
|
+
*/
|
|
130
|
+
export function PhaseProgressIndicator({
|
|
131
|
+
currentPhase,
|
|
132
|
+
totalPhases = DEFAULT_TOTAL_PHASES,
|
|
133
|
+
showLabels = false,
|
|
134
|
+
showPercentage = false,
|
|
135
|
+
width = DEFAULT_WIDTH,
|
|
136
|
+
orientation = "horizontal",
|
|
137
|
+
}: PhaseProgressIndicatorProps): React.ReactElement {
|
|
138
|
+
const { theme } = useTheme();
|
|
139
|
+
|
|
140
|
+
// Validate and clamp current phase
|
|
141
|
+
const validPhase = Math.max(1, Math.min(currentPhase, totalPhases));
|
|
142
|
+
|
|
143
|
+
// Calculate segment width
|
|
144
|
+
const segmentWidth = Math.max(1, Math.floor(width / totalPhases));
|
|
145
|
+
|
|
146
|
+
// Calculate completion percentage
|
|
147
|
+
const percentage = useMemo(
|
|
148
|
+
() => calculatePercentage(validPhase, totalPhases),
|
|
149
|
+
[validPhase, totalPhases]
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
// Build progress segments
|
|
153
|
+
const segments = useMemo(() => {
|
|
154
|
+
return Array.from({ length: totalPhases }, (_, index) => {
|
|
155
|
+
const phaseNumber = index + 1;
|
|
156
|
+
const isCompleted = phaseNumber < validPhase;
|
|
157
|
+
const isCurrent = phaseNumber === validPhase;
|
|
158
|
+
const phaseName = getPhaseName(phaseNumber);
|
|
159
|
+
|
|
160
|
+
return {
|
|
161
|
+
phaseNumber,
|
|
162
|
+
phaseName,
|
|
163
|
+
isCompleted,
|
|
164
|
+
isCurrent,
|
|
165
|
+
isPending: !isCompleted && !isCurrent,
|
|
166
|
+
};
|
|
167
|
+
});
|
|
168
|
+
}, [totalPhases, validPhase]);
|
|
169
|
+
|
|
170
|
+
// Render horizontal progress bar
|
|
171
|
+
if (orientation === "horizontal") {
|
|
172
|
+
return (
|
|
173
|
+
<Box flexDirection="column">
|
|
174
|
+
{/* Progress bar */}
|
|
175
|
+
<Box>
|
|
176
|
+
{segments.map((segment, index) => {
|
|
177
|
+
const char = segment.isCompleted
|
|
178
|
+
? PROGRESS_CHARS.filled
|
|
179
|
+
: segment.isCurrent
|
|
180
|
+
? PROGRESS_CHARS.current
|
|
181
|
+
: PROGRESS_CHARS.empty;
|
|
182
|
+
|
|
183
|
+
const color = segment.isCompleted
|
|
184
|
+
? theme.colors.success
|
|
185
|
+
: segment.isCurrent
|
|
186
|
+
? theme.colors.primary
|
|
187
|
+
: theme.semantic.text.muted;
|
|
188
|
+
|
|
189
|
+
const barSegment = char.repeat(segmentWidth);
|
|
190
|
+
|
|
191
|
+
return (
|
|
192
|
+
<Box key={segment.phaseNumber}>
|
|
193
|
+
<Text color={color}>{barSegment}</Text>
|
|
194
|
+
{/* Separator between segments (except last) */}
|
|
195
|
+
{index < segments.length - 1 && (
|
|
196
|
+
<Text color={theme.semantic.text.muted}>
|
|
197
|
+
{segment.isCompleted
|
|
198
|
+
? PROGRESS_CHARS.separatorFilled
|
|
199
|
+
: PROGRESS_CHARS.separatorEmpty}
|
|
200
|
+
</Text>
|
|
201
|
+
)}
|
|
202
|
+
</Box>
|
|
203
|
+
);
|
|
204
|
+
})}
|
|
205
|
+
|
|
206
|
+
{/* Percentage display */}
|
|
207
|
+
{showPercentage && <Text color={theme.semantic.text.secondary}> {percentage}%</Text>}
|
|
208
|
+
</Box>
|
|
209
|
+
|
|
210
|
+
{/* Phase labels */}
|
|
211
|
+
{showLabels && (
|
|
212
|
+
<Box marginTop={1}>
|
|
213
|
+
<Text color={theme.colors.primary} bold>
|
|
214
|
+
{getPhaseStatusIcons().current} {getPhaseName(validPhase)}
|
|
215
|
+
</Text>
|
|
216
|
+
<Text color={theme.semantic.text.muted}>
|
|
217
|
+
{" "}
|
|
218
|
+
({validPhase}/{totalPhases})
|
|
219
|
+
</Text>
|
|
220
|
+
</Box>
|
|
221
|
+
)}
|
|
222
|
+
</Box>
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Get icons for vertical list
|
|
227
|
+
const phaseIcons = getPhaseStatusIcons();
|
|
228
|
+
|
|
229
|
+
// Render vertical progress list
|
|
230
|
+
return (
|
|
231
|
+
<Box flexDirection="column">
|
|
232
|
+
{segments.map((segment) => {
|
|
233
|
+
const icon = segment.isCompleted
|
|
234
|
+
? phaseIcons.completed
|
|
235
|
+
: segment.isCurrent
|
|
236
|
+
? phaseIcons.current
|
|
237
|
+
: phaseIcons.pending;
|
|
238
|
+
|
|
239
|
+
const color = segment.isCompleted
|
|
240
|
+
? theme.colors.success
|
|
241
|
+
: segment.isCurrent
|
|
242
|
+
? theme.colors.primary
|
|
243
|
+
: theme.semantic.text.muted;
|
|
244
|
+
|
|
245
|
+
return (
|
|
246
|
+
<Box key={segment.phaseNumber}>
|
|
247
|
+
<Text color={color}>
|
|
248
|
+
{icon} {segment.phaseNumber}. {segment.phaseName}
|
|
249
|
+
</Text>
|
|
250
|
+
</Box>
|
|
251
|
+
);
|
|
252
|
+
})}
|
|
253
|
+
|
|
254
|
+
{/* Progress summary */}
|
|
255
|
+
{showPercentage && (
|
|
256
|
+
<Box marginTop={1}>
|
|
257
|
+
<Text color={theme.semantic.text.secondary}>
|
|
258
|
+
Progress: {percentage}% ({validPhase - 1}/{totalPhases} completed)
|
|
259
|
+
</Text>
|
|
260
|
+
</Box>
|
|
261
|
+
)}
|
|
262
|
+
</Box>
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// =============================================================================
|
|
267
|
+
// Exports
|
|
268
|
+
// =============================================================================
|
|
269
|
+
|
|
270
|
+
export type { SpecPhase };
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rate Limit Indicator Component
|
|
3
|
+
*
|
|
4
|
+
* Displays rate limit status and warnings in the TUI.
|
|
5
|
+
* Placeholder implementation - to be expanded.
|
|
6
|
+
*
|
|
7
|
+
* @module tui/components/RateLimitIndicator
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { Text } from "ink";
|
|
11
|
+
import type React from "react";
|
|
12
|
+
|
|
13
|
+
// =============================================================================
|
|
14
|
+
// Types
|
|
15
|
+
// =============================================================================
|
|
16
|
+
|
|
17
|
+
export interface RateLimitIndicatorProps {
|
|
18
|
+
/** Whether rate limiting is active */
|
|
19
|
+
isLimited?: boolean;
|
|
20
|
+
/** Remaining requests */
|
|
21
|
+
remaining?: number;
|
|
22
|
+
/** Total limit */
|
|
23
|
+
limit?: number;
|
|
24
|
+
/** Time until reset (seconds) */
|
|
25
|
+
resetIn?: number;
|
|
26
|
+
/** Whether to show compact view */
|
|
27
|
+
compact?: boolean;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// =============================================================================
|
|
31
|
+
// Component
|
|
32
|
+
// =============================================================================
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Displays rate limit status
|
|
36
|
+
*/
|
|
37
|
+
export function RateLimitIndicator({
|
|
38
|
+
isLimited = false,
|
|
39
|
+
remaining,
|
|
40
|
+
limit,
|
|
41
|
+
resetIn,
|
|
42
|
+
compact = false,
|
|
43
|
+
}: RateLimitIndicatorProps): React.ReactElement | null {
|
|
44
|
+
// Don't render if not limited and no info to show
|
|
45
|
+
if (!isLimited && remaining === undefined) {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (compact) {
|
|
50
|
+
if (isLimited) {
|
|
51
|
+
return <Text color="yellow">⚠ Rate limited</Text>;
|
|
52
|
+
}
|
|
53
|
+
if (remaining !== undefined && limit !== undefined) {
|
|
54
|
+
const percentage = (remaining / limit) * 100;
|
|
55
|
+
const color = percentage < 20 ? "yellow" : "green";
|
|
56
|
+
return (
|
|
57
|
+
<Text color={color}>
|
|
58
|
+
{remaining}/{limit}
|
|
59
|
+
</Text>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Full view
|
|
66
|
+
if (isLimited) {
|
|
67
|
+
const resetText = resetIn !== undefined ? ` (resets in ${resetIn}s)` : "";
|
|
68
|
+
return <Text color="yellow">⚠ Rate limited{resetText}</Text>;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (remaining !== undefined && limit !== undefined) {
|
|
72
|
+
return (
|
|
73
|
+
<Text dimColor>
|
|
74
|
+
Requests: {remaining}/{limit}
|
|
75
|
+
</Text>
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export default RateLimitIndicator;
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ScreenReaderLayout Component (T045)
|
|
3
|
+
*
|
|
4
|
+
* Simplified layout for screen reader accessibility.
|
|
5
|
+
* Provides a linear, sequential display optimized for screen readers
|
|
6
|
+
* with automatic status announcements.
|
|
7
|
+
*
|
|
8
|
+
* Key features:
|
|
9
|
+
* - Linear/sequential message display (no complex grid layouts)
|
|
10
|
+
* - Clear section headings for navigation
|
|
11
|
+
* - Automatic status change announcements
|
|
12
|
+
* - Simplified visual presentation
|
|
13
|
+
*
|
|
14
|
+
* @module tui/components/ScreenReaderLayout
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { Box, Text } from "ink";
|
|
18
|
+
import type React from "react";
|
|
19
|
+
import { useEffect, useMemo, useRef } from "react";
|
|
20
|
+
import {
|
|
21
|
+
formatForScreenReader,
|
|
22
|
+
type UseScreenReaderOptions,
|
|
23
|
+
useScreenReader,
|
|
24
|
+
} from "../hooks/useScreenReader.js";
|
|
25
|
+
import { useTheme } from "../theme/index.js";
|
|
26
|
+
|
|
27
|
+
// =============================================================================
|
|
28
|
+
// Types
|
|
29
|
+
// =============================================================================
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Props for the ScreenReaderLayout component.
|
|
33
|
+
*/
|
|
34
|
+
export interface ScreenReaderLayoutProps {
|
|
35
|
+
/** Header content (rendered as first section) */
|
|
36
|
+
readonly header?: React.ReactNode;
|
|
37
|
+
/** Main content area */
|
|
38
|
+
readonly children: React.ReactNode;
|
|
39
|
+
/** Footer content (rendered as last section) */
|
|
40
|
+
readonly footer?: React.ReactNode;
|
|
41
|
+
/** Current status message to announce on change */
|
|
42
|
+
readonly status?: string;
|
|
43
|
+
/** Screen reader options */
|
|
44
|
+
readonly screenReaderOptions?: UseScreenReaderOptions;
|
|
45
|
+
/** Callback when screen reader mode changes */
|
|
46
|
+
readonly onScreenReaderChange?: (isEnabled: boolean) => void;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Status information for tracking changes.
|
|
51
|
+
*/
|
|
52
|
+
interface StatusInfo {
|
|
53
|
+
readonly message: string;
|
|
54
|
+
readonly timestamp: number;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// =============================================================================
|
|
58
|
+
// Constants
|
|
59
|
+
// =============================================================================
|
|
60
|
+
|
|
61
|
+
/** Section separator for visual clarity */
|
|
62
|
+
const SECTION_SEPARATOR = "────────────────────────────────────────";
|
|
63
|
+
|
|
64
|
+
// =============================================================================
|
|
65
|
+
// Sub-Components
|
|
66
|
+
// =============================================================================
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Section header for screen reader navigation.
|
|
70
|
+
*/
|
|
71
|
+
interface SectionHeaderProps {
|
|
72
|
+
readonly title: string;
|
|
73
|
+
readonly level?: 1 | 2 | 3;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function SectionHeader({ title, level = 2 }: SectionHeaderProps): React.ReactElement {
|
|
77
|
+
const { theme } = useTheme();
|
|
78
|
+
|
|
79
|
+
// Use different prefixes for heading levels (helps screen readers)
|
|
80
|
+
const prefix = level === 1 ? "# " : level === 2 ? "## " : "### ";
|
|
81
|
+
|
|
82
|
+
return (
|
|
83
|
+
<Box flexDirection="column" marginBottom={1}>
|
|
84
|
+
<Text color={theme.semantic.text.muted}>{SECTION_SEPARATOR}</Text>
|
|
85
|
+
<Text bold color={theme.semantic.text.primary}>
|
|
86
|
+
{prefix}
|
|
87
|
+
{title}
|
|
88
|
+
</Text>
|
|
89
|
+
</Box>
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Simple message display for screen reader mode.
|
|
95
|
+
*/
|
|
96
|
+
interface SimpleMessageProps {
|
|
97
|
+
readonly children: React.ReactNode;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function SimpleMessage({ children }: SimpleMessageProps): React.ReactElement {
|
|
101
|
+
return (
|
|
102
|
+
<Box flexDirection="column" marginY={1}>
|
|
103
|
+
{children}
|
|
104
|
+
</Box>
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Status display with clear labeling.
|
|
110
|
+
*/
|
|
111
|
+
interface StatusDisplayProps {
|
|
112
|
+
readonly status: string;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function StatusDisplay({ status }: StatusDisplayProps): React.ReactElement {
|
|
116
|
+
const { theme } = useTheme();
|
|
117
|
+
|
|
118
|
+
return (
|
|
119
|
+
<Box marginY={1}>
|
|
120
|
+
<Text>
|
|
121
|
+
<Text color={theme.semantic.text.secondary} bold>
|
|
122
|
+
Status:{" "}
|
|
123
|
+
</Text>
|
|
124
|
+
<Text>{status}</Text>
|
|
125
|
+
</Text>
|
|
126
|
+
</Box>
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// =============================================================================
|
|
131
|
+
// Main Component
|
|
132
|
+
// =============================================================================
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* ScreenReaderLayout provides an accessible layout optimized for screen readers.
|
|
136
|
+
*
|
|
137
|
+
* Unlike the standard Layout component which uses complex grid arrangements,
|
|
138
|
+
* ScreenReaderLayout presents content in a simple, linear fashion that screen
|
|
139
|
+
* readers can navigate sequentially.
|
|
140
|
+
*
|
|
141
|
+
* @example
|
|
142
|
+
* ```tsx
|
|
143
|
+
* function AccessibleApp() {
|
|
144
|
+
* return (
|
|
145
|
+
* <ScreenReaderLayout
|
|
146
|
+
* header={<Text>Vellum AI Assistant</Text>}
|
|
147
|
+
* status="Ready for input"
|
|
148
|
+
* footer={<Text>Press Ctrl+C to exit</Text>}
|
|
149
|
+
* >
|
|
150
|
+
* <MessageList messages={messages} />
|
|
151
|
+
* </ScreenReaderLayout>
|
|
152
|
+
* );
|
|
153
|
+
* }
|
|
154
|
+
* ```
|
|
155
|
+
*/
|
|
156
|
+
export function ScreenReaderLayout({
|
|
157
|
+
header,
|
|
158
|
+
children,
|
|
159
|
+
footer,
|
|
160
|
+
status,
|
|
161
|
+
screenReaderOptions,
|
|
162
|
+
onScreenReaderChange,
|
|
163
|
+
}: ScreenReaderLayoutProps): React.ReactElement {
|
|
164
|
+
const { theme } = useTheme();
|
|
165
|
+
const { isEnabled, announce } = useScreenReader(screenReaderOptions);
|
|
166
|
+
|
|
167
|
+
// Track previous status to detect changes
|
|
168
|
+
const prevStatusRef = useRef<StatusInfo | null>(null);
|
|
169
|
+
|
|
170
|
+
// Notify parent of screen reader mode changes
|
|
171
|
+
useEffect(() => {
|
|
172
|
+
onScreenReaderChange?.(isEnabled);
|
|
173
|
+
}, [isEnabled, onScreenReaderChange]);
|
|
174
|
+
|
|
175
|
+
// Announce status changes
|
|
176
|
+
useEffect(() => {
|
|
177
|
+
if (!status || !isEnabled) return;
|
|
178
|
+
|
|
179
|
+
const prevStatus = prevStatusRef.current;
|
|
180
|
+
const statusChanged = !prevStatus || prevStatus.message !== status;
|
|
181
|
+
|
|
182
|
+
if (statusChanged) {
|
|
183
|
+
// Announce the new status
|
|
184
|
+
announce(formatForScreenReader(`Status: ${status}`), "polite");
|
|
185
|
+
|
|
186
|
+
// Update the ref
|
|
187
|
+
prevStatusRef.current = {
|
|
188
|
+
message: status,
|
|
189
|
+
timestamp: Date.now(),
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
}, [status, isEnabled, announce]);
|
|
193
|
+
|
|
194
|
+
// Announce initial state on mount
|
|
195
|
+
useEffect(() => {
|
|
196
|
+
if (isEnabled) {
|
|
197
|
+
announce("Vellum AI Assistant loaded. Screen reader mode active.", "polite");
|
|
198
|
+
}
|
|
199
|
+
}, [isEnabled, announce]);
|
|
200
|
+
|
|
201
|
+
// Memoize the layout structure
|
|
202
|
+
const layout = useMemo(() => {
|
|
203
|
+
return (
|
|
204
|
+
<Box flexDirection="column" padding={1}>
|
|
205
|
+
{/* Header Section */}
|
|
206
|
+
{header && (
|
|
207
|
+
<>
|
|
208
|
+
<SectionHeader title="Header" level={1} />
|
|
209
|
+
<SimpleMessage>{header}</SimpleMessage>
|
|
210
|
+
</>
|
|
211
|
+
)}
|
|
212
|
+
|
|
213
|
+
{/* Status Section */}
|
|
214
|
+
{status && (
|
|
215
|
+
<>
|
|
216
|
+
<SectionHeader title="Current Status" level={2} />
|
|
217
|
+
<StatusDisplay status={status} />
|
|
218
|
+
</>
|
|
219
|
+
)}
|
|
220
|
+
|
|
221
|
+
{/* Main Content Section */}
|
|
222
|
+
<SectionHeader title="Content" level={1} />
|
|
223
|
+
<SimpleMessage>{children}</SimpleMessage>
|
|
224
|
+
|
|
225
|
+
{/* Footer Section */}
|
|
226
|
+
{footer && (
|
|
227
|
+
<>
|
|
228
|
+
<SectionHeader title="Footer" level={2} />
|
|
229
|
+
<SimpleMessage>{footer}</SimpleMessage>
|
|
230
|
+
</>
|
|
231
|
+
)}
|
|
232
|
+
|
|
233
|
+
{/* Screen Reader Mode Indicator */}
|
|
234
|
+
<Box marginTop={1}>
|
|
235
|
+
<Text color={theme.semantic.text.muted} dimColor>
|
|
236
|
+
[Screen reader mode: {isEnabled ? "ON" : "OFF"}]
|
|
237
|
+
</Text>
|
|
238
|
+
</Box>
|
|
239
|
+
</Box>
|
|
240
|
+
);
|
|
241
|
+
}, [header, status, children, footer, isEnabled, theme.semantic.text.muted]);
|
|
242
|
+
|
|
243
|
+
return layout;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// =============================================================================
|
|
247
|
+
// Utility Components
|
|
248
|
+
// =============================================================================
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Wrapper to conditionally render ScreenReaderLayout or regular Layout.
|
|
252
|
+
*/
|
|
253
|
+
export interface AdaptiveLayoutProps extends Omit<ScreenReaderLayoutProps, "screenReaderOptions"> {
|
|
254
|
+
/** Regular layout to render when screen reader mode is off */
|
|
255
|
+
readonly regularLayout?: React.ReactElement;
|
|
256
|
+
/** Screen reader options */
|
|
257
|
+
readonly screenReaderOptions?: UseScreenReaderOptions;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* AdaptiveLayout automatically switches between ScreenReaderLayout and a
|
|
262
|
+
* regular layout based on screen reader detection.
|
|
263
|
+
*
|
|
264
|
+
* @example
|
|
265
|
+
* ```tsx
|
|
266
|
+
* function App() {
|
|
267
|
+
* return (
|
|
268
|
+
* <AdaptiveLayout
|
|
269
|
+
* regularLayout={<Layout>{content}</Layout>}
|
|
270
|
+
* status={status}
|
|
271
|
+
* >
|
|
272
|
+
* {content}
|
|
273
|
+
* </AdaptiveLayout>
|
|
274
|
+
* );
|
|
275
|
+
* }
|
|
276
|
+
* ```
|
|
277
|
+
*/
|
|
278
|
+
export function AdaptiveLayout({
|
|
279
|
+
regularLayout,
|
|
280
|
+
screenReaderOptions,
|
|
281
|
+
...screenReaderProps
|
|
282
|
+
}: AdaptiveLayoutProps): React.ReactElement {
|
|
283
|
+
const { isEnabled } = useScreenReader(screenReaderOptions);
|
|
284
|
+
|
|
285
|
+
if (isEnabled) {
|
|
286
|
+
return <ScreenReaderLayout screenReaderOptions={screenReaderOptions} {...screenReaderProps} />;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// If no regular layout provided, use screen reader layout as fallback
|
|
290
|
+
if (!regularLayout) {
|
|
291
|
+
return <ScreenReaderLayout screenReaderOptions={screenReaderOptions} {...screenReaderProps} />;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
return regularLayout;
|
|
295
|
+
}
|