@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,268 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MessageBubble Component Tests
|
|
3
|
+
*
|
|
4
|
+
* Tests for the MessageBubble component which renders styled message bubbles
|
|
5
|
+
* with role-specific formatting and alignment.
|
|
6
|
+
*
|
|
7
|
+
* @module tui/components/Messages/__tests__/MessageBubble.test
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { getIcons } from "@vellum/shared";
|
|
11
|
+
import { render } from "ink-testing-library";
|
|
12
|
+
import type React from "react";
|
|
13
|
+
import { describe, expect, it } from "vitest";
|
|
14
|
+
import type { Message } from "../../../context/MessagesContext.js";
|
|
15
|
+
import { ThemeProvider } from "../../../theme/index.js";
|
|
16
|
+
import { MessageBubble } from "../MessageBubble.js";
|
|
17
|
+
|
|
18
|
+
// Get icons for test assertions
|
|
19
|
+
const icons = getIcons();
|
|
20
|
+
|
|
21
|
+
// =============================================================================
|
|
22
|
+
// Test Helpers
|
|
23
|
+
// =============================================================================
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Wrap component with ThemeProvider for testing.
|
|
27
|
+
*/
|
|
28
|
+
function renderWithTheme(ui: React.ReactElement) {
|
|
29
|
+
return render(<ThemeProvider>{ui}</ThemeProvider>);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Create a test message with default values.
|
|
34
|
+
*/
|
|
35
|
+
function createTestMessage(overrides: Partial<Message> = {}): Message {
|
|
36
|
+
return {
|
|
37
|
+
id: "test-1",
|
|
38
|
+
role: "user",
|
|
39
|
+
content: "Test message content",
|
|
40
|
+
timestamp: new Date("2025-01-01T12:00:00Z"),
|
|
41
|
+
...overrides,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// =============================================================================
|
|
46
|
+
// Tests
|
|
47
|
+
// =============================================================================
|
|
48
|
+
|
|
49
|
+
describe("MessageBubble", () => {
|
|
50
|
+
describe("rendering", () => {
|
|
51
|
+
it("renders user message content", () => {
|
|
52
|
+
const message = createTestMessage({
|
|
53
|
+
role: "user",
|
|
54
|
+
content: "Hello from user",
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
const { lastFrame } = renderWithTheme(<MessageBubble message={message} />);
|
|
58
|
+
|
|
59
|
+
expect(lastFrame()).toContain("Hello from user");
|
|
60
|
+
expect(lastFrame()).toContain("You");
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("renders assistant message content", () => {
|
|
64
|
+
const message = createTestMessage({
|
|
65
|
+
role: "assistant",
|
|
66
|
+
content: "Hello from assistant",
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const { lastFrame } = renderWithTheme(<MessageBubble message={message} />);
|
|
70
|
+
|
|
71
|
+
expect(lastFrame()).toContain("Hello from assistant");
|
|
72
|
+
expect(lastFrame()).toContain("Vellum");
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it("renders system message content", () => {
|
|
76
|
+
const message = createTestMessage({
|
|
77
|
+
role: "system",
|
|
78
|
+
content: "System notification",
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
const { lastFrame } = renderWithTheme(<MessageBubble message={message} />);
|
|
82
|
+
|
|
83
|
+
expect(lastFrame()).toContain("System notification");
|
|
84
|
+
expect(lastFrame()).toContain("System");
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it("renders tool message content", () => {
|
|
88
|
+
const message = createTestMessage({
|
|
89
|
+
role: "tool",
|
|
90
|
+
content: "Tool output",
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
const { lastFrame } = renderWithTheme(<MessageBubble message={message} />);
|
|
94
|
+
|
|
95
|
+
expect(lastFrame()).toContain("Tool output");
|
|
96
|
+
expect(lastFrame()).toContain("Tool");
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it("renders empty message placeholder", () => {
|
|
100
|
+
const message = createTestMessage({
|
|
101
|
+
content: "",
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
const { lastFrame } = renderWithTheme(<MessageBubble message={message} />);
|
|
105
|
+
|
|
106
|
+
expect(lastFrame()).toContain("(empty)");
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it("renders streaming indicator", () => {
|
|
110
|
+
const message = createTestMessage({
|
|
111
|
+
content: "",
|
|
112
|
+
isStreaming: true,
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
const { lastFrame } = renderWithTheme(<MessageBubble message={message} />);
|
|
116
|
+
|
|
117
|
+
expect(lastFrame()).toContain("streaming");
|
|
118
|
+
expect(lastFrame()).toContain("...");
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
describe("timestamp display", () => {
|
|
123
|
+
it("does not show timestamp by default", () => {
|
|
124
|
+
const message = createTestMessage();
|
|
125
|
+
|
|
126
|
+
const { lastFrame } = renderWithTheme(<MessageBubble message={message} />);
|
|
127
|
+
|
|
128
|
+
// Should not contain time format like "12:00"
|
|
129
|
+
expect(lastFrame()).not.toContain("•");
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it("shows timestamp when showTimestamp is true", () => {
|
|
133
|
+
const message = createTestMessage({
|
|
134
|
+
timestamp: new Date("2025-01-01T14:30:00Z"),
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
const { lastFrame } = renderWithTheme(<MessageBubble message={message} showTimestamp />);
|
|
138
|
+
|
|
139
|
+
expect(lastFrame()).toContain("•");
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
describe("avatar display", () => {
|
|
144
|
+
it("does not show avatar by default", () => {
|
|
145
|
+
const message = createTestMessage({ role: "user" });
|
|
146
|
+
|
|
147
|
+
const { lastFrame } = renderWithTheme(<MessageBubble message={message} />);
|
|
148
|
+
|
|
149
|
+
// User icon should not appear
|
|
150
|
+
expect(lastFrame()).not.toContain(icons.user);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it("shows user avatar when showAvatar is true", () => {
|
|
154
|
+
const message = createTestMessage({ role: "user" });
|
|
155
|
+
|
|
156
|
+
const { lastFrame } = renderWithTheme(<MessageBubble message={message} showAvatar />);
|
|
157
|
+
|
|
158
|
+
expect(lastFrame()).toContain(icons.user);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it("shows assistant avatar when showAvatar is true", () => {
|
|
162
|
+
const message = createTestMessage({ role: "assistant" });
|
|
163
|
+
|
|
164
|
+
const { lastFrame } = renderWithTheme(<MessageBubble message={message} showAvatar />);
|
|
165
|
+
|
|
166
|
+
expect(lastFrame()).toContain(icons.assistant);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it("shows system avatar when showAvatar is true", () => {
|
|
170
|
+
const message = createTestMessage({ role: "system" });
|
|
171
|
+
|
|
172
|
+
const { lastFrame } = renderWithTheme(<MessageBubble message={message} showAvatar />);
|
|
173
|
+
|
|
174
|
+
expect(lastFrame()).toContain(icons.system);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it("shows tool avatar when showAvatar is true", () => {
|
|
178
|
+
const message = createTestMessage({ role: "tool" });
|
|
179
|
+
|
|
180
|
+
const { lastFrame } = renderWithTheme(<MessageBubble message={message} showAvatar />);
|
|
181
|
+
|
|
182
|
+
expect(lastFrame()).toContain(icons.tool);
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
describe("compact mode", () => {
|
|
187
|
+
it("renders in compact mode", () => {
|
|
188
|
+
const message = createTestMessage({
|
|
189
|
+
content: "Compact message",
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
const { lastFrame } = renderWithTheme(<MessageBubble message={message} compact />);
|
|
193
|
+
|
|
194
|
+
expect(lastFrame()).toContain("Compact message");
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it("renders assistant message in compact mode without extra spacing", () => {
|
|
198
|
+
const message = createTestMessage({
|
|
199
|
+
role: "assistant",
|
|
200
|
+
content: "Compact assistant message",
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
const { lastFrame } = renderWithTheme(<MessageBubble message={message} compact />);
|
|
204
|
+
|
|
205
|
+
expect(lastFrame()).toContain("Compact assistant message");
|
|
206
|
+
expect(lastFrame()).toContain("Vellum");
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
describe("unknown/unhandled roles", () => {
|
|
211
|
+
it("renders unknown role with fallback label", () => {
|
|
212
|
+
// tool_group role is not handled by MessageBubble (use ToolGroupItem instead)
|
|
213
|
+
// This tests the fallback behavior for unrecognized roles
|
|
214
|
+
const message = createTestMessage({
|
|
215
|
+
role: "tool_group", // Not handled by MessageBubble
|
|
216
|
+
content: "Some content",
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
const { lastFrame } = renderWithTheme(<MessageBubble message={message} />);
|
|
220
|
+
|
|
221
|
+
// Shows "Unknown" label for unhandled roles
|
|
222
|
+
expect(lastFrame()).toContain("Unknown");
|
|
223
|
+
expect(lastFrame()).toContain("Some content");
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
it("renders unknown role with empty content placeholder", () => {
|
|
227
|
+
const message = createTestMessage({
|
|
228
|
+
role: "tool_group",
|
|
229
|
+
content: "",
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
const { lastFrame } = renderWithTheme(<MessageBubble message={message} />);
|
|
233
|
+
|
|
234
|
+
// Empty content shows placeholder
|
|
235
|
+
expect(lastFrame()).toContain("(empty)");
|
|
236
|
+
});
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
describe("combined props", () => {
|
|
240
|
+
it("renders with all props enabled", () => {
|
|
241
|
+
const message = createTestMessage({
|
|
242
|
+
role: "assistant",
|
|
243
|
+
content: "Full featured message",
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
const { lastFrame } = renderWithTheme(
|
|
247
|
+
<MessageBubble message={message} showTimestamp showAvatar />
|
|
248
|
+
);
|
|
249
|
+
|
|
250
|
+
expect(lastFrame()).toContain("Full featured message");
|
|
251
|
+
expect(lastFrame()).toContain(icons.assistant); // Avatar
|
|
252
|
+
expect(lastFrame()).toContain("Vellum");
|
|
253
|
+
expect(lastFrame()).toContain("•"); // Timestamp separator
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
it("renders message with thinking content", () => {
|
|
257
|
+
const message = createTestMessage({
|
|
258
|
+
role: "assistant",
|
|
259
|
+
content: "Final response",
|
|
260
|
+
thinking: "Let me think about this...",
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
const { lastFrame } = renderWithTheme(<MessageBubble message={message} />);
|
|
264
|
+
|
|
265
|
+
expect(lastFrame()).toContain("Final response");
|
|
266
|
+
});
|
|
267
|
+
});
|
|
268
|
+
});
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MessageList Component Tests (T017)
|
|
3
|
+
*
|
|
4
|
+
* Tests for the MessageList component with auto-scroll functionality.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { getIcons } from "@vellum/shared";
|
|
8
|
+
import { render } from "ink-testing-library";
|
|
9
|
+
import type React from "react";
|
|
10
|
+
import { describe, expect, it } from "vitest";
|
|
11
|
+
import type { Message } from "../../../context/MessagesContext.js";
|
|
12
|
+
import { ThemeProvider } from "../../../theme/index.js";
|
|
13
|
+
import { MessageList } from "../MessageList.js";
|
|
14
|
+
|
|
15
|
+
// Get icons for test assertions
|
|
16
|
+
const icons = getIcons();
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Wrapper to provide theme context for tests
|
|
20
|
+
*/
|
|
21
|
+
function renderWithTheme(element: React.ReactElement) {
|
|
22
|
+
return render(<ThemeProvider>{element}</ThemeProvider>);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Create a test message with defaults
|
|
27
|
+
*/
|
|
28
|
+
function createMessage(overrides: Partial<Message> = {}): Message {
|
|
29
|
+
return {
|
|
30
|
+
id: `msg-${Math.random().toString(36).slice(2)}`,
|
|
31
|
+
role: "user",
|
|
32
|
+
content: "Test message content",
|
|
33
|
+
timestamp: new Date(),
|
|
34
|
+
...overrides,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
describe("MessageList", () => {
|
|
39
|
+
describe("Rendering", () => {
|
|
40
|
+
it("should render without crashing with empty messages", () => {
|
|
41
|
+
const { lastFrame } = renderWithTheme(<MessageList messages={[]} />);
|
|
42
|
+
|
|
43
|
+
expect(lastFrame()).toBeDefined();
|
|
44
|
+
expect(lastFrame()).toContain("No messages yet");
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("should render a single message", () => {
|
|
48
|
+
const messages = [createMessage({ content: "Hello, world!" })];
|
|
49
|
+
|
|
50
|
+
const { lastFrame } = renderWithTheme(<MessageList messages={messages} />);
|
|
51
|
+
|
|
52
|
+
const frame = lastFrame() ?? "";
|
|
53
|
+
expect(frame).toContain("Hello, world!");
|
|
54
|
+
expect(frame).toContain("You"); // User role label
|
|
55
|
+
expect(frame).toContain(icons.user); // User role icon
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("should render multiple messages in order", () => {
|
|
59
|
+
const messages = [
|
|
60
|
+
createMessage({ id: "1", content: "First message", role: "user" }),
|
|
61
|
+
createMessage({ id: "2", content: "Second message", role: "assistant" }),
|
|
62
|
+
createMessage({ id: "3", content: "Third message", role: "user" }),
|
|
63
|
+
];
|
|
64
|
+
|
|
65
|
+
const { lastFrame } = renderWithTheme(<MessageList messages={messages} />);
|
|
66
|
+
|
|
67
|
+
const frame = lastFrame() ?? "";
|
|
68
|
+
expect(frame).toContain("First message");
|
|
69
|
+
expect(frame).toContain("Second message");
|
|
70
|
+
expect(frame).toContain("Third message");
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("should render different role icons correctly", () => {
|
|
74
|
+
const messages = [
|
|
75
|
+
createMessage({ role: "user", content: "User msg" }),
|
|
76
|
+
createMessage({ role: "assistant", content: "Assistant msg" }),
|
|
77
|
+
createMessage({ role: "system", content: "System msg" }),
|
|
78
|
+
createMessage({ role: "tool", content: "Tool msg" }),
|
|
79
|
+
];
|
|
80
|
+
|
|
81
|
+
const { lastFrame } = renderWithTheme(<MessageList messages={messages} />);
|
|
82
|
+
|
|
83
|
+
const frame = lastFrame() ?? "";
|
|
84
|
+
expect(frame).toContain(icons.user); // user
|
|
85
|
+
expect(frame).toContain(icons.assistant); // assistant
|
|
86
|
+
expect(frame).toContain(icons.system); // system
|
|
87
|
+
expect(frame).toContain(icons.tool); // tool
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it("should render role labels correctly", () => {
|
|
91
|
+
const messages = [
|
|
92
|
+
createMessage({ role: "user" }),
|
|
93
|
+
createMessage({ role: "assistant" }),
|
|
94
|
+
createMessage({ role: "system" }),
|
|
95
|
+
createMessage({ role: "tool" }),
|
|
96
|
+
];
|
|
97
|
+
|
|
98
|
+
const { lastFrame } = renderWithTheme(<MessageList messages={messages} />);
|
|
99
|
+
|
|
100
|
+
const frame = lastFrame() ?? "";
|
|
101
|
+
expect(frame).toContain("You");
|
|
102
|
+
expect(frame).toContain("Vellum");
|
|
103
|
+
expect(frame).toContain("System");
|
|
104
|
+
expect(frame).toContain("Tool");
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("should show streaming indicator when message is streaming", () => {
|
|
108
|
+
const messages = [createMessage({ content: "Typing...", isStreaming: true })];
|
|
109
|
+
|
|
110
|
+
const { lastFrame } = renderWithTheme(<MessageList messages={messages} />);
|
|
111
|
+
|
|
112
|
+
const frame = lastFrame() ?? "";
|
|
113
|
+
expect(frame).toContain("streaming");
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it("should show placeholder for empty content in non-streaming message", () => {
|
|
117
|
+
const messages = [createMessage({ content: "", isStreaming: false })];
|
|
118
|
+
|
|
119
|
+
const { lastFrame } = renderWithTheme(<MessageList messages={messages} />);
|
|
120
|
+
|
|
121
|
+
const frame = lastFrame() ?? "";
|
|
122
|
+
expect(frame).toContain("(empty)");
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it("should show ellipsis for empty content in streaming message", () => {
|
|
126
|
+
const messages = [createMessage({ content: "", isStreaming: true })];
|
|
127
|
+
|
|
128
|
+
const { lastFrame } = renderWithTheme(<MessageList messages={messages} />);
|
|
129
|
+
|
|
130
|
+
const frame = lastFrame() ?? "";
|
|
131
|
+
expect(frame).toContain("...");
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
describe("Tool Group Messages", () => {
|
|
136
|
+
it("should render tool_group message as inline tool rows", () => {
|
|
137
|
+
const messages = [
|
|
138
|
+
createMessage({
|
|
139
|
+
role: "assistant",
|
|
140
|
+
content: "Let me read that file",
|
|
141
|
+
}),
|
|
142
|
+
createMessage({
|
|
143
|
+
id: "tool-group-1",
|
|
144
|
+
role: "tool_group",
|
|
145
|
+
content: "",
|
|
146
|
+
toolCalls: [
|
|
147
|
+
{
|
|
148
|
+
id: "tc-1",
|
|
149
|
+
name: "read_file",
|
|
150
|
+
arguments: { path: "/test.txt" },
|
|
151
|
+
status: "completed",
|
|
152
|
+
},
|
|
153
|
+
],
|
|
154
|
+
}),
|
|
155
|
+
];
|
|
156
|
+
|
|
157
|
+
const { lastFrame } = renderWithTheme(<MessageList messages={messages} />);
|
|
158
|
+
|
|
159
|
+
const frame = lastFrame() ?? "";
|
|
160
|
+
expect(frame).toContain("Let me read that file");
|
|
161
|
+
expect(frame).toContain("read_file");
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it("should render tool_group when multiple tool calls present", () => {
|
|
165
|
+
const messages = [
|
|
166
|
+
createMessage({
|
|
167
|
+
role: "assistant",
|
|
168
|
+
content: "Processing multiple files",
|
|
169
|
+
}),
|
|
170
|
+
createMessage({
|
|
171
|
+
id: "tool-group-1",
|
|
172
|
+
role: "tool_group",
|
|
173
|
+
content: "",
|
|
174
|
+
toolCalls: [
|
|
175
|
+
{
|
|
176
|
+
id: "tc-1",
|
|
177
|
+
name: "read_file",
|
|
178
|
+
arguments: {},
|
|
179
|
+
status: "completed",
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
id: "tc-2",
|
|
183
|
+
name: "write_file",
|
|
184
|
+
arguments: {},
|
|
185
|
+
status: "running",
|
|
186
|
+
},
|
|
187
|
+
],
|
|
188
|
+
}),
|
|
189
|
+
];
|
|
190
|
+
|
|
191
|
+
const { lastFrame } = renderWithTheme(<MessageList messages={messages} />);
|
|
192
|
+
|
|
193
|
+
const frame = lastFrame() ?? "";
|
|
194
|
+
expect(frame).toContain("Processing multiple files");
|
|
195
|
+
expect(frame).toContain("read_file");
|
|
196
|
+
expect(frame).toContain("write_file");
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it("should not render a Tools header for tool_group rows", () => {
|
|
200
|
+
const messages = [
|
|
201
|
+
createMessage({
|
|
202
|
+
id: "tool-group-1",
|
|
203
|
+
role: "tool_group",
|
|
204
|
+
content: "",
|
|
205
|
+
toolCalls: [
|
|
206
|
+
{
|
|
207
|
+
id: "tc-1",
|
|
208
|
+
name: "bash",
|
|
209
|
+
arguments: { command: "ls" },
|
|
210
|
+
status: "pending",
|
|
211
|
+
},
|
|
212
|
+
],
|
|
213
|
+
}),
|
|
214
|
+
];
|
|
215
|
+
|
|
216
|
+
const { lastFrame } = renderWithTheme(<MessageList messages={messages} />);
|
|
217
|
+
|
|
218
|
+
const frame = lastFrame() ?? "";
|
|
219
|
+
expect(frame).toContain("bash");
|
|
220
|
+
expect(frame).not.toContain("Tools");
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
describe("Auto-Scroll Behavior", () => {
|
|
225
|
+
it("should have autoScroll enabled by default", () => {
|
|
226
|
+
const messages = [createMessage()];
|
|
227
|
+
|
|
228
|
+
// Just verify the component renders with autoScroll defaulting to true
|
|
229
|
+
const { lastFrame } = renderWithTheme(<MessageList messages={messages} />);
|
|
230
|
+
|
|
231
|
+
// If autoScroll was false and we scrolled up, we'd see the pause indicator
|
|
232
|
+
// Since autoScroll defaults to true and we haven't scrolled, no indicator
|
|
233
|
+
const frame = lastFrame() ?? "";
|
|
234
|
+
expect(frame).not.toContain("Auto-scroll paused");
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
it("should report being at bottom when all messages are visible", () => {
|
|
238
|
+
const messages = [createMessage(), createMessage(), createMessage()];
|
|
239
|
+
|
|
240
|
+
// Without maxHeight, all messages are visible, so we're always "at bottom"
|
|
241
|
+
const { lastFrame } = renderWithTheme(<MessageList messages={messages} />);
|
|
242
|
+
|
|
243
|
+
// Should render without scroll indicators since no maxHeight
|
|
244
|
+
const frame = lastFrame() ?? "";
|
|
245
|
+
expect(frame).not.toContain("more above");
|
|
246
|
+
expect(frame).not.toContain("more below");
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
it("should show scroll indicator when messages exceed maxHeight", () => {
|
|
250
|
+
// Create more messages than maxHeight
|
|
251
|
+
const messages = Array.from({ length: 10 }, (_, i) =>
|
|
252
|
+
createMessage({ id: `msg-${i}`, content: `Message ${i}` })
|
|
253
|
+
);
|
|
254
|
+
|
|
255
|
+
const { lastFrame } = renderWithTheme(
|
|
256
|
+
<MessageList messages={messages} maxHeight={3} autoScroll={false} />
|
|
257
|
+
);
|
|
258
|
+
|
|
259
|
+
const frame = lastFrame() ?? "";
|
|
260
|
+
// Should show scroll down indicator since we're at top (autoScroll=false)
|
|
261
|
+
// The component should show some messages but not all
|
|
262
|
+
expect(frame).toBeDefined();
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
it("should accept autoScroll prop set to false", () => {
|
|
266
|
+
const messages = [createMessage()];
|
|
267
|
+
|
|
268
|
+
const { lastFrame } = renderWithTheme(<MessageList messages={messages} autoScroll={false} />);
|
|
269
|
+
|
|
270
|
+
expect(lastFrame()).toBeDefined();
|
|
271
|
+
});
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
describe("Props Validation", () => {
|
|
275
|
+
it("should handle undefined optional props", () => {
|
|
276
|
+
const messages = [createMessage()];
|
|
277
|
+
|
|
278
|
+
const { lastFrame } = renderWithTheme(<MessageList messages={messages} />);
|
|
279
|
+
|
|
280
|
+
expect(lastFrame()).toBeDefined();
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
it("should handle maxHeight of 0", () => {
|
|
284
|
+
const messages = [createMessage()];
|
|
285
|
+
|
|
286
|
+
// maxHeight of 0 should be treated as "no max height"
|
|
287
|
+
const { lastFrame } = renderWithTheme(<MessageList messages={messages} maxHeight={0} />);
|
|
288
|
+
|
|
289
|
+
expect(lastFrame()).toBeDefined();
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
it("should handle empty message content", () => {
|
|
293
|
+
const messages = [createMessage({ content: "" })];
|
|
294
|
+
|
|
295
|
+
const { lastFrame } = renderWithTheme(<MessageList messages={messages} />);
|
|
296
|
+
|
|
297
|
+
expect(lastFrame()).toBeDefined();
|
|
298
|
+
});
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
describe("Performance", () => {
|
|
302
|
+
it("should render many messages without error", () => {
|
|
303
|
+
const messages = Array.from({ length: 100 }, (_, i) =>
|
|
304
|
+
createMessage({ id: `msg-${i}`, content: `Message number ${i}` })
|
|
305
|
+
);
|
|
306
|
+
|
|
307
|
+
const { lastFrame } = renderWithTheme(<MessageList messages={messages} />);
|
|
308
|
+
|
|
309
|
+
expect(lastFrame()).toBeDefined();
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
it("should handle messages with windowed rendering", () => {
|
|
313
|
+
const messages = Array.from({ length: 50 }, (_, i) =>
|
|
314
|
+
createMessage({ id: `msg-${i}`, content: `Message ${i}` })
|
|
315
|
+
);
|
|
316
|
+
|
|
317
|
+
const { lastFrame } = renderWithTheme(
|
|
318
|
+
<MessageList messages={messages} maxHeight={10} autoScroll={true} />
|
|
319
|
+
);
|
|
320
|
+
|
|
321
|
+
expect(lastFrame()).toBeDefined();
|
|
322
|
+
});
|
|
323
|
+
});
|
|
324
|
+
});
|