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