@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,240 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SessionPreview Component (T056)
|
|
3
|
+
*
|
|
4
|
+
* Displays a preview of messages from a selected session.
|
|
5
|
+
*
|
|
6
|
+
* @module tui/components/session/SessionPreview
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { getIcons } from "@vellum/shared";
|
|
10
|
+
import { Box, Text } from "ink";
|
|
11
|
+
import type React from "react";
|
|
12
|
+
import { useMemo } from "react";
|
|
13
|
+
import { useTheme } from "../../theme/index.js";
|
|
14
|
+
import { truncateToDisplayWidth } from "../../utils/index.js";
|
|
15
|
+
import type { SessionPreviewMessage, SessionPreviewProps } from "./types.js";
|
|
16
|
+
|
|
17
|
+
// =============================================================================
|
|
18
|
+
// Constants
|
|
19
|
+
// =============================================================================
|
|
20
|
+
|
|
21
|
+
/** Default maximum height for the preview */
|
|
22
|
+
const DEFAULT_MAX_HEIGHT = 8;
|
|
23
|
+
|
|
24
|
+
/** Maximum characters for message content preview */
|
|
25
|
+
const MAX_CONTENT_LENGTH = 200;
|
|
26
|
+
|
|
27
|
+
// =============================================================================
|
|
28
|
+
// Helper Functions
|
|
29
|
+
// =============================================================================
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Format a timestamp for display.
|
|
33
|
+
*/
|
|
34
|
+
function formatTimestamp(date: Date): string {
|
|
35
|
+
return date.toLocaleTimeString(undefined, {
|
|
36
|
+
hour: "2-digit",
|
|
37
|
+
minute: "2-digit",
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Get the display icon for a message role.
|
|
43
|
+
*/
|
|
44
|
+
function getRoleIcon(role: SessionPreviewMessage["role"]): string {
|
|
45
|
+
const icons = getIcons();
|
|
46
|
+
switch (role) {
|
|
47
|
+
case "user":
|
|
48
|
+
return icons.user;
|
|
49
|
+
case "assistant":
|
|
50
|
+
return icons.assistant;
|
|
51
|
+
case "system":
|
|
52
|
+
return icons.system;
|
|
53
|
+
case "tool":
|
|
54
|
+
return icons.tool;
|
|
55
|
+
default:
|
|
56
|
+
return icons.info;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Get role label for display.
|
|
62
|
+
*/
|
|
63
|
+
function getRoleLabel(role: SessionPreviewMessage["role"]): string {
|
|
64
|
+
switch (role) {
|
|
65
|
+
case "user":
|
|
66
|
+
return "You";
|
|
67
|
+
case "assistant":
|
|
68
|
+
return "Vellum";
|
|
69
|
+
case "system":
|
|
70
|
+
return "System";
|
|
71
|
+
case "tool":
|
|
72
|
+
return "Tool";
|
|
73
|
+
default:
|
|
74
|
+
return "Unknown";
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Truncate text with ellipsis.
|
|
80
|
+
* Uses string-width for accurate CJK/Emoji handling.
|
|
81
|
+
*/
|
|
82
|
+
function truncateContent(text: string, maxLength: number): string {
|
|
83
|
+
return truncateToDisplayWidth(text, maxLength);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// =============================================================================
|
|
87
|
+
// Sub-Components
|
|
88
|
+
// =============================================================================
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Single message preview item.
|
|
92
|
+
*/
|
|
93
|
+
interface PreviewMessageItemProps {
|
|
94
|
+
readonly message: SessionPreviewMessage;
|
|
95
|
+
readonly roleColor: string;
|
|
96
|
+
readonly textColor: string;
|
|
97
|
+
readonly mutedColor: string;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function PreviewMessageItem({
|
|
101
|
+
message,
|
|
102
|
+
roleColor,
|
|
103
|
+
textColor,
|
|
104
|
+
mutedColor,
|
|
105
|
+
}: PreviewMessageItemProps): React.JSX.Element {
|
|
106
|
+
const icon = getRoleIcon(message.role);
|
|
107
|
+
const label = getRoleLabel(message.role);
|
|
108
|
+
const timestamp = formatTimestamp(message.timestamp);
|
|
109
|
+
const content = truncateContent(message.content, MAX_CONTENT_LENGTH);
|
|
110
|
+
|
|
111
|
+
return (
|
|
112
|
+
<Box flexDirection="column" marginBottom={0}>
|
|
113
|
+
{/* Header: icon, role, timestamp */}
|
|
114
|
+
<Box flexDirection="row" gap={1}>
|
|
115
|
+
<Text>
|
|
116
|
+
{icon}{" "}
|
|
117
|
+
<Text color={roleColor} bold>
|
|
118
|
+
{label}
|
|
119
|
+
</Text>
|
|
120
|
+
</Text>
|
|
121
|
+
<Text color={mutedColor}>• {timestamp}</Text>
|
|
122
|
+
</Box>
|
|
123
|
+
|
|
124
|
+
{/* Content preview */}
|
|
125
|
+
<Box marginLeft={2}>
|
|
126
|
+
<Text color={textColor} wrap="truncate-end">
|
|
127
|
+
{content || "(empty)"}
|
|
128
|
+
</Text>
|
|
129
|
+
</Box>
|
|
130
|
+
</Box>
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// =============================================================================
|
|
135
|
+
// Main Component
|
|
136
|
+
// =============================================================================
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* SessionPreview displays a preview of messages from a session.
|
|
140
|
+
*
|
|
141
|
+
* Features:
|
|
142
|
+
* - Shows recent messages with role icons
|
|
143
|
+
* - Truncates long messages
|
|
144
|
+
* - Displays session title
|
|
145
|
+
* - Scrolls to show latest messages
|
|
146
|
+
*
|
|
147
|
+
* @example
|
|
148
|
+
* ```tsx
|
|
149
|
+
* <SessionPreview
|
|
150
|
+
* messages={previewMessages}
|
|
151
|
+
* title="Debug React App"
|
|
152
|
+
* maxHeight={8}
|
|
153
|
+
* />
|
|
154
|
+
* ```
|
|
155
|
+
*/
|
|
156
|
+
export function SessionPreview({
|
|
157
|
+
messages,
|
|
158
|
+
maxHeight = DEFAULT_MAX_HEIGHT,
|
|
159
|
+
title,
|
|
160
|
+
}: SessionPreviewProps): React.JSX.Element {
|
|
161
|
+
const { theme } = useTheme();
|
|
162
|
+
|
|
163
|
+
const textColor = theme.semantic.text.primary;
|
|
164
|
+
const mutedColor = theme.semantic.text.muted;
|
|
165
|
+
const borderColor = theme.semantic.border.default;
|
|
166
|
+
const roleColor = theme.colors.primary;
|
|
167
|
+
|
|
168
|
+
// Get the last N messages that fit in the view (rough estimate: 2 lines per message)
|
|
169
|
+
const maxMessages = Math.floor((maxHeight - 2) / 2);
|
|
170
|
+
const visibleMessages = useMemo(() => messages.slice(-maxMessages), [messages, maxMessages]);
|
|
171
|
+
|
|
172
|
+
const hasMoreMessages = messages.length > maxMessages;
|
|
173
|
+
|
|
174
|
+
// Empty state
|
|
175
|
+
if (messages.length === 0) {
|
|
176
|
+
return (
|
|
177
|
+
<Box
|
|
178
|
+
flexDirection="column"
|
|
179
|
+
borderStyle="single"
|
|
180
|
+
borderColor={borderColor}
|
|
181
|
+
paddingX={1}
|
|
182
|
+
height={maxHeight}
|
|
183
|
+
>
|
|
184
|
+
<Text color={textColor} bold>
|
|
185
|
+
{getIcons().note} {title || "Session Preview"}
|
|
186
|
+
</Text>
|
|
187
|
+
<Box flexGrow={1} justifyContent="center" alignItems="center">
|
|
188
|
+
<Text color={mutedColor} italic>
|
|
189
|
+
No messages in this session
|
|
190
|
+
</Text>
|
|
191
|
+
</Box>
|
|
192
|
+
</Box>
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return (
|
|
197
|
+
<Box
|
|
198
|
+
flexDirection="column"
|
|
199
|
+
borderStyle="single"
|
|
200
|
+
borderColor={borderColor}
|
|
201
|
+
paddingX={1}
|
|
202
|
+
height={maxHeight}
|
|
203
|
+
>
|
|
204
|
+
{/* Header */}
|
|
205
|
+
<Box flexDirection="row" justifyContent="space-between">
|
|
206
|
+
<Text color={textColor} bold>
|
|
207
|
+
{getIcons().note} {title || "Session Preview"}
|
|
208
|
+
</Text>
|
|
209
|
+
<Text color={mutedColor}>
|
|
210
|
+
{messages.length} message{messages.length !== 1 ? "s" : ""}
|
|
211
|
+
</Text>
|
|
212
|
+
</Box>
|
|
213
|
+
|
|
214
|
+
{/* More messages indicator */}
|
|
215
|
+
{hasMoreMessages && (
|
|
216
|
+
<Box>
|
|
217
|
+
<Text color={mutedColor} dimColor>
|
|
218
|
+
... {messages.length - maxMessages} earlier message
|
|
219
|
+
{messages.length - maxMessages !== 1 ? "s" : ""}
|
|
220
|
+
</Text>
|
|
221
|
+
</Box>
|
|
222
|
+
)}
|
|
223
|
+
|
|
224
|
+
{/* Message list */}
|
|
225
|
+
<Box flexDirection="column" flexGrow={1} overflow="hidden">
|
|
226
|
+
{visibleMessages.map((message) => (
|
|
227
|
+
<PreviewMessageItem
|
|
228
|
+
key={message.id}
|
|
229
|
+
message={message}
|
|
230
|
+
roleColor={roleColor}
|
|
231
|
+
textColor={textColor}
|
|
232
|
+
mutedColor={mutedColor}
|
|
233
|
+
/>
|
|
234
|
+
))}
|
|
235
|
+
</Box>
|
|
236
|
+
</Box>
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
export default SessionPreview;
|
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session Components Tests (T056)
|
|
3
|
+
*
|
|
4
|
+
* Tests for session management components: SessionItem, SessionListPanel,
|
|
5
|
+
* SessionPicker, and SessionPreview.
|
|
6
|
+
*
|
|
7
|
+
* @module tui/components/session/__tests__/session.test
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { getIcons } from "@vellum/shared";
|
|
11
|
+
import { render } from "ink-testing-library";
|
|
12
|
+
import type React from "react";
|
|
13
|
+
import { act } from "react";
|
|
14
|
+
import { describe, expect, it, vi } from "vitest";
|
|
15
|
+
import { ThemeProvider } from "../../../theme/index.js";
|
|
16
|
+
import { SessionItem } from "../SessionItem.js";
|
|
17
|
+
import { SessionListPanel } from "../SessionListPanel.js";
|
|
18
|
+
import { SessionPicker } from "../SessionPicker.js";
|
|
19
|
+
import { SessionPreview } from "../SessionPreview.js";
|
|
20
|
+
import type { SessionMetadata, SessionPreviewMessage } from "../types.js";
|
|
21
|
+
|
|
22
|
+
// Get icons for test assertions
|
|
23
|
+
const icons = getIcons();
|
|
24
|
+
|
|
25
|
+
// =============================================================================
|
|
26
|
+
// Test Helpers
|
|
27
|
+
// =============================================================================
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Wrap component with ThemeProvider for testing.
|
|
31
|
+
*/
|
|
32
|
+
function renderWithTheme(ui: React.ReactElement) {
|
|
33
|
+
return render(<ThemeProvider>{ui}</ThemeProvider>);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Create a test session with default values.
|
|
38
|
+
*/
|
|
39
|
+
function createTestSession(overrides: Partial<SessionMetadata> = {}): SessionMetadata {
|
|
40
|
+
return {
|
|
41
|
+
id: "test-session-1",
|
|
42
|
+
title: "Test Session",
|
|
43
|
+
lastMessage: "This is the last message",
|
|
44
|
+
timestamp: new Date("2025-12-30T10:00:00"),
|
|
45
|
+
messageCount: 5,
|
|
46
|
+
...overrides,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Create test preview messages.
|
|
52
|
+
*/
|
|
53
|
+
function createTestMessages(): SessionPreviewMessage[] {
|
|
54
|
+
return [
|
|
55
|
+
{
|
|
56
|
+
id: "msg-1",
|
|
57
|
+
role: "user",
|
|
58
|
+
content: "Hello, how can you help?",
|
|
59
|
+
timestamp: new Date("2025-12-30T10:00:00"),
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
id: "msg-2",
|
|
63
|
+
role: "assistant",
|
|
64
|
+
content: "I can help you with many things!",
|
|
65
|
+
timestamp: new Date("2025-12-30T10:01:00"),
|
|
66
|
+
},
|
|
67
|
+
];
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// =============================================================================
|
|
71
|
+
// SessionItem Tests
|
|
72
|
+
// =============================================================================
|
|
73
|
+
|
|
74
|
+
describe("SessionItem", () => {
|
|
75
|
+
it("renders session title", () => {
|
|
76
|
+
const session = createTestSession({ title: "Debug React App" });
|
|
77
|
+
|
|
78
|
+
const { lastFrame } = renderWithTheme(<SessionItem session={session} />);
|
|
79
|
+
|
|
80
|
+
expect(lastFrame()).toContain("Debug React App");
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it("renders message count", () => {
|
|
84
|
+
const session = createTestSession({ messageCount: 12 });
|
|
85
|
+
|
|
86
|
+
const { lastFrame } = renderWithTheme(<SessionItem session={session} />);
|
|
87
|
+
|
|
88
|
+
expect(lastFrame()).toContain("(12)");
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("renders last message preview", () => {
|
|
92
|
+
const session = createTestSession({ lastMessage: "I will help you" });
|
|
93
|
+
|
|
94
|
+
const { lastFrame } = renderWithTheme(<SessionItem session={session} />);
|
|
95
|
+
|
|
96
|
+
expect(lastFrame()).toContain("I will help you");
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it("shows selection indicator when selected", () => {
|
|
100
|
+
const session = createTestSession();
|
|
101
|
+
|
|
102
|
+
const { lastFrame } = renderWithTheme(<SessionItem session={session} isSelected={true} />);
|
|
103
|
+
|
|
104
|
+
expect(lastFrame()).toContain("▶");
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("shows active indicator when active", () => {
|
|
108
|
+
const session = createTestSession();
|
|
109
|
+
|
|
110
|
+
const { lastFrame } = renderWithTheme(<SessionItem session={session} isActive={true} />);
|
|
111
|
+
|
|
112
|
+
expect(lastFrame()).toContain("●");
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it("truncates long titles", () => {
|
|
116
|
+
const session = createTestSession({
|
|
117
|
+
title: "This is a very long session title that should be truncated for display",
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
const { lastFrame } = renderWithTheme(<SessionItem session={session} />);
|
|
121
|
+
const frame = lastFrame() || "";
|
|
122
|
+
|
|
123
|
+
// Should contain truncated title (40 chars max) with ellipsis
|
|
124
|
+
expect(frame).toContain("…");
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
// =============================================================================
|
|
129
|
+
// SessionListPanel Tests
|
|
130
|
+
// =============================================================================
|
|
131
|
+
|
|
132
|
+
describe("SessionListPanel", () => {
|
|
133
|
+
it("renders empty state when no sessions", () => {
|
|
134
|
+
const { lastFrame } = renderWithTheme(<SessionListPanel sessions={[]} isFocused={false} />);
|
|
135
|
+
|
|
136
|
+
expect(lastFrame()).toContain("No sessions found");
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it("renders session list", () => {
|
|
140
|
+
const sessions = [
|
|
141
|
+
createTestSession({ id: "s1", title: "Session 1" }),
|
|
142
|
+
createTestSession({ id: "s2", title: "Session 2" }),
|
|
143
|
+
];
|
|
144
|
+
|
|
145
|
+
const { lastFrame } = renderWithTheme(
|
|
146
|
+
<SessionListPanel sessions={sessions} isFocused={false} />
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
expect(lastFrame()).toContain("Session 1");
|
|
150
|
+
expect(lastFrame()).toContain("Session 2");
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it("shows total session count", () => {
|
|
154
|
+
const sessions = [
|
|
155
|
+
createTestSession({ id: "s1" }),
|
|
156
|
+
createTestSession({ id: "s2" }),
|
|
157
|
+
createTestSession({ id: "s3" }),
|
|
158
|
+
];
|
|
159
|
+
|
|
160
|
+
const { lastFrame } = renderWithTheme(
|
|
161
|
+
<SessionListPanel sessions={sessions} isFocused={false} />
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
expect(lastFrame()).toContain("Sessions (3)");
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it("shows navigation hints", () => {
|
|
168
|
+
const sessions = [createTestSession()];
|
|
169
|
+
|
|
170
|
+
const { lastFrame } = renderWithTheme(
|
|
171
|
+
<SessionListPanel sessions={sessions} isFocused={false} />
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
expect(lastFrame()).toContain("j/k");
|
|
175
|
+
expect(lastFrame()).toContain("navigate");
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it("highlights active session", () => {
|
|
179
|
+
const sessions = [
|
|
180
|
+
createTestSession({ id: "s1", title: "Session 1" }),
|
|
181
|
+
createTestSession({ id: "s2", title: "Session 2" }),
|
|
182
|
+
];
|
|
183
|
+
|
|
184
|
+
const { lastFrame } = renderWithTheme(
|
|
185
|
+
<SessionListPanel sessions={sessions} activeSessionId="s2" isFocused={false} />
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
// Active session should have indicator
|
|
189
|
+
expect(lastFrame()).toContain("●");
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
// =============================================================================
|
|
194
|
+
// SessionPreview Tests
|
|
195
|
+
// =============================================================================
|
|
196
|
+
|
|
197
|
+
describe("SessionPreview", () => {
|
|
198
|
+
it("renders empty state when no messages", () => {
|
|
199
|
+
const { lastFrame } = renderWithTheme(<SessionPreview messages={[]} />);
|
|
200
|
+
|
|
201
|
+
expect(lastFrame()).toContain("No messages in this session");
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it("renders session title", () => {
|
|
205
|
+
const messages = createTestMessages();
|
|
206
|
+
|
|
207
|
+
const { lastFrame } = renderWithTheme(
|
|
208
|
+
<SessionPreview messages={messages} title="Debug Session" />
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
expect(lastFrame()).toContain("Debug Session");
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it("renders message count", () => {
|
|
215
|
+
const messages = createTestMessages();
|
|
216
|
+
|
|
217
|
+
const { lastFrame } = renderWithTheme(<SessionPreview messages={messages} />);
|
|
218
|
+
|
|
219
|
+
expect(lastFrame()).toContain("2 messages");
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
it("renders message content", () => {
|
|
223
|
+
const messages = createTestMessages();
|
|
224
|
+
|
|
225
|
+
const { lastFrame } = renderWithTheme(<SessionPreview messages={messages} />);
|
|
226
|
+
|
|
227
|
+
expect(lastFrame()).toContain("Hello, how can you help?");
|
|
228
|
+
expect(lastFrame()).toContain("I can help you");
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it("shows role icons", () => {
|
|
232
|
+
const messages = createTestMessages();
|
|
233
|
+
|
|
234
|
+
const { lastFrame } = renderWithTheme(<SessionPreview messages={messages} />);
|
|
235
|
+
|
|
236
|
+
expect(lastFrame()).toContain(icons.user); // User icon
|
|
237
|
+
expect(lastFrame()).toContain(icons.assistant); // Assistant icon
|
|
238
|
+
});
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
// =============================================================================
|
|
242
|
+
// SessionPicker Tests
|
|
243
|
+
// =============================================================================
|
|
244
|
+
|
|
245
|
+
describe("SessionPicker", () => {
|
|
246
|
+
it("renders nothing when closed", () => {
|
|
247
|
+
const sessions = [createTestSession()];
|
|
248
|
+
|
|
249
|
+
const { lastFrame } = renderWithTheme(
|
|
250
|
+
<SessionPicker sessions={sessions} onSelect={vi.fn()} onClose={vi.fn()} isOpen={false} />
|
|
251
|
+
);
|
|
252
|
+
|
|
253
|
+
expect(lastFrame()).toBe("");
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
it("renders when open", () => {
|
|
257
|
+
const sessions = [createTestSession({ title: "Test Session" })];
|
|
258
|
+
|
|
259
|
+
const { lastFrame } = renderWithTheme(
|
|
260
|
+
<SessionPicker sessions={sessions} onSelect={vi.fn()} onClose={vi.fn()} isOpen={true} />
|
|
261
|
+
);
|
|
262
|
+
|
|
263
|
+
expect(lastFrame()).toContain("Select Session");
|
|
264
|
+
expect(lastFrame()).toContain("Test Session");
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
it("shows empty state when no sessions", () => {
|
|
268
|
+
const { lastFrame } = renderWithTheme(
|
|
269
|
+
<SessionPicker sessions={[]} onSelect={vi.fn()} onClose={vi.fn()} isOpen={true} />
|
|
270
|
+
);
|
|
271
|
+
|
|
272
|
+
expect(lastFrame()).toContain("No sessions available");
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
it("shows keybinding hints", () => {
|
|
276
|
+
const sessions = [createTestSession()];
|
|
277
|
+
|
|
278
|
+
const { lastFrame } = renderWithTheme(
|
|
279
|
+
<SessionPicker sessions={sessions} onSelect={vi.fn()} onClose={vi.fn()} isOpen={true} />
|
|
280
|
+
);
|
|
281
|
+
|
|
282
|
+
expect(lastFrame()).toContain("j/k");
|
|
283
|
+
expect(lastFrame()).toContain("Enter");
|
|
284
|
+
expect(lastFrame()).toContain("Esc");
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
it("shows session count", () => {
|
|
288
|
+
const sessions = [
|
|
289
|
+
createTestSession({ id: "s1" }),
|
|
290
|
+
createTestSession({ id: "s2" }),
|
|
291
|
+
createTestSession({ id: "s3" }),
|
|
292
|
+
];
|
|
293
|
+
|
|
294
|
+
const { lastFrame } = renderWithTheme(
|
|
295
|
+
<SessionPicker sessions={sessions} onSelect={vi.fn()} onClose={vi.fn()} isOpen={true} />
|
|
296
|
+
);
|
|
297
|
+
|
|
298
|
+
expect(lastFrame()).toContain("(3)");
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
it("shows preview panel", () => {
|
|
302
|
+
const sessions = [
|
|
303
|
+
createTestSession({
|
|
304
|
+
title: "Debug Session",
|
|
305
|
+
lastMessage: "I found the bug",
|
|
306
|
+
}),
|
|
307
|
+
];
|
|
308
|
+
|
|
309
|
+
const { lastFrame } = renderWithTheme(
|
|
310
|
+
<SessionPicker sessions={sessions} onSelect={vi.fn()} onClose={vi.fn()} isOpen={true} />
|
|
311
|
+
);
|
|
312
|
+
|
|
313
|
+
// Preview should show the session title
|
|
314
|
+
expect(lastFrame()).toContain("Debug Session");
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
it("loads real preview messages when loadPreviewMessages is provided", async () => {
|
|
318
|
+
const sessions = [createTestSession({ id: "s1", title: "Session 1" })];
|
|
319
|
+
|
|
320
|
+
const loadPreviewMessages = vi.fn(async () => {
|
|
321
|
+
return [
|
|
322
|
+
{
|
|
323
|
+
id: "m1",
|
|
324
|
+
role: "user" as const,
|
|
325
|
+
content: "Loaded user message",
|
|
326
|
+
timestamp: new Date("2025-12-30T10:00:00"),
|
|
327
|
+
},
|
|
328
|
+
{
|
|
329
|
+
id: "m2",
|
|
330
|
+
role: "assistant" as const,
|
|
331
|
+
content: "Loaded assistant message",
|
|
332
|
+
timestamp: new Date("2025-12-30T10:01:00"),
|
|
333
|
+
},
|
|
334
|
+
];
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
const { lastFrame } = renderWithTheme(
|
|
338
|
+
<SessionPicker
|
|
339
|
+
sessions={sessions}
|
|
340
|
+
onSelect={vi.fn()}
|
|
341
|
+
onClose={vi.fn()}
|
|
342
|
+
isOpen={true}
|
|
343
|
+
loadPreviewMessages={loadPreviewMessages}
|
|
344
|
+
/>
|
|
345
|
+
);
|
|
346
|
+
|
|
347
|
+
await act(async () => {
|
|
348
|
+
await new Promise((r) => setTimeout(r, 0));
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
expect(loadPreviewMessages).toHaveBeenCalledWith("s1");
|
|
352
|
+
const frame = lastFrame() ?? "";
|
|
353
|
+
expect(frame).toContain("Loaded user message");
|
|
354
|
+
// The preview panel may truncate content depending on available space.
|
|
355
|
+
// Assert stable indicators that the assistant message exists.
|
|
356
|
+
expect(frame).toContain("2 messages");
|
|
357
|
+
expect(frame).toContain("Vellum");
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
it("caches preview messages per session id", async () => {
|
|
361
|
+
const sessions = [
|
|
362
|
+
createTestSession({ id: "s1", title: "Session 1" }),
|
|
363
|
+
createTestSession({ id: "s2", title: "Session 2" }),
|
|
364
|
+
];
|
|
365
|
+
|
|
366
|
+
const loadPreviewMessages = vi.fn(async (sessionId: string) => {
|
|
367
|
+
return [
|
|
368
|
+
{
|
|
369
|
+
id: `preview-${sessionId}`,
|
|
370
|
+
role: "assistant" as const,
|
|
371
|
+
content: `Preview for ${sessionId}`,
|
|
372
|
+
timestamp: new Date("2025-12-30T10:01:00"),
|
|
373
|
+
},
|
|
374
|
+
];
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
const { stdin } = renderWithTheme(
|
|
378
|
+
<SessionPicker
|
|
379
|
+
sessions={sessions}
|
|
380
|
+
onSelect={vi.fn()}
|
|
381
|
+
onClose={vi.fn()}
|
|
382
|
+
isOpen={true}
|
|
383
|
+
loadPreviewMessages={loadPreviewMessages}
|
|
384
|
+
/>
|
|
385
|
+
);
|
|
386
|
+
|
|
387
|
+
// Initial selection loads s1
|
|
388
|
+
await act(async () => {
|
|
389
|
+
await new Promise((r) => setTimeout(r, 0));
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
// Move down to s2
|
|
393
|
+
await act(async () => {
|
|
394
|
+
stdin.write("\u001b[B");
|
|
395
|
+
await new Promise((r) => setTimeout(r, 0));
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
// Move back up to s1 (should hit cache, no new load)
|
|
399
|
+
await act(async () => {
|
|
400
|
+
stdin.write("\u001b[A");
|
|
401
|
+
await new Promise((r) => setTimeout(r, 0));
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
expect(loadPreviewMessages).toHaveBeenCalledTimes(2);
|
|
405
|
+
expect(loadPreviewMessages).toHaveBeenNthCalledWith(1, "s1");
|
|
406
|
+
expect(loadPreviewMessages).toHaveBeenNthCalledWith(2, "s2");
|
|
407
|
+
});
|
|
408
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session Management Components (T056)
|
|
3
|
+
*
|
|
4
|
+
* React Ink components for session management UI.
|
|
5
|
+
*
|
|
6
|
+
* @module tui/components/session
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export type { CheckpointPanelProps } from "./CheckpointPanel.js";
|
|
10
|
+
// Checkpoint Components
|
|
11
|
+
export { CheckpointPanel, default as CheckpointPanelDefault } from "./CheckpointPanel.js";
|
|
12
|
+
export type { RollbackDialogProps } from "./RollbackDialog.js";
|
|
13
|
+
export { default as RollbackDialogDefault, RollbackDialog } from "./RollbackDialog.js";
|
|
14
|
+
// Components
|
|
15
|
+
export { default as SessionItemDefault, SessionItem } from "./SessionItem.js";
|
|
16
|
+
export { default as SessionListPanelDefault, SessionListPanel } from "./SessionListPanel.js";
|
|
17
|
+
export { default as SessionPickerDefault, SessionPicker } from "./SessionPicker.js";
|
|
18
|
+
export { default as SessionPreviewDefault, SessionPreview } from "./SessionPreview.js";
|
|
19
|
+
|
|
20
|
+
// Types
|
|
21
|
+
export type {
|
|
22
|
+
SessionItemProps,
|
|
23
|
+
SessionListPanelProps,
|
|
24
|
+
SessionMetadata,
|
|
25
|
+
SessionPickerProps,
|
|
26
|
+
SessionPreviewMessage,
|
|
27
|
+
SessionPreviewProps,
|
|
28
|
+
} from "./types.js";
|