fss-link 1.0.40 → 1.0.45

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 (428) hide show
  1. package/dist/commands/mcp/add.test.ts +122 -0
  2. package/dist/commands/mcp/add.ts +222 -0
  3. package/dist/commands/mcp/list.test.ts +154 -0
  4. package/dist/commands/mcp/list.ts +139 -0
  5. package/dist/commands/mcp/remove.test.ts +69 -0
  6. package/dist/commands/mcp/remove.ts +60 -0
  7. package/dist/commands/mcp.test.ts +55 -0
  8. package/dist/commands/mcp.ts +27 -0
  9. package/dist/config/apiValidation.test.ts +118 -0
  10. package/dist/config/auth.test.ts +79 -0
  11. package/dist/config/auth.ts +100 -0
  12. package/dist/config/config.integration.test.ts +407 -0
  13. package/dist/config/config.test.ts +1952 -0
  14. package/dist/config/config.ts +690 -0
  15. package/dist/config/database.test.ts +96 -0
  16. package/dist/config/database.ts +752 -0
  17. package/dist/config/extension.test.ts +236 -0
  18. package/dist/config/extension.ts +180 -0
  19. package/dist/config/keyBindings.test.ts +62 -0
  20. package/dist/config/keyBindings.ts +184 -0
  21. package/dist/config/modelManager.ts +275 -0
  22. package/dist/config/providerManager.ts +244 -0
  23. package/dist/config/providerPersistence.test.ts +377 -0
  24. package/dist/config/providerPersistence.ts +105 -0
  25. package/dist/config/sandboxConfig.ts +107 -0
  26. package/dist/config/settings.test.ts +1424 -0
  27. package/dist/config/settings.ts +517 -0
  28. package/dist/config/settingsSchema.test.ts +252 -0
  29. package/dist/config/settingsSchema.ts +728 -0
  30. package/dist/config/trustedFolders.test.ts +208 -0
  31. package/dist/config/trustedFolders.ts +167 -0
  32. package/dist/gemini.test.tsx +252 -0
  33. package/dist/gemini.tsx +357 -0
  34. package/dist/generated/git-commit.ts +10 -0
  35. package/dist/index.ts +21 -0
  36. package/dist/nonInteractiveCli.test.ts +276 -0
  37. package/dist/nonInteractiveCli.ts +143 -0
  38. package/dist/package.json +87 -87
  39. package/dist/patches/is-in-ci.ts +17 -0
  40. package/dist/services/BuiltinCommandLoader.test.ts +127 -0
  41. package/dist/services/BuiltinCommandLoader.ts +95 -0
  42. package/dist/services/CommandService.test.ts +352 -0
  43. package/dist/services/CommandService.ts +103 -0
  44. package/dist/services/FileCommandLoader.test.ts +1002 -0
  45. package/dist/services/FileCommandLoader.ts +289 -0
  46. package/dist/services/McpPromptLoader.ts +231 -0
  47. package/dist/services/SearchEngineConfigProvider.ts +100 -0
  48. package/dist/services/prompt-processors/argumentProcessor.test.ts +41 -0
  49. package/dist/services/prompt-processors/argumentProcessor.ts +23 -0
  50. package/dist/services/prompt-processors/shellProcessor.test.ts +709 -0
  51. package/dist/services/prompt-processors/shellProcessor.ts +248 -0
  52. package/dist/services/prompt-processors/types.ts +44 -0
  53. package/dist/services/types.ts +24 -0
  54. package/dist/src/config/apiValidation.test.d.ts +6 -0
  55. package/dist/src/config/apiValidation.test.js +99 -0
  56. package/dist/src/config/apiValidation.test.js.map +1 -0
  57. package/dist/src/config/database.d.ts +32 -0
  58. package/dist/src/config/database.js +281 -2
  59. package/dist/src/config/database.js.map +1 -1
  60. package/dist/src/config/database.test.d.ts +6 -0
  61. package/dist/src/config/database.test.js +80 -0
  62. package/dist/src/config/database.test.js.map +1 -0
  63. package/dist/src/config/providerManager.d.ts +74 -0
  64. package/dist/src/config/providerManager.js +203 -0
  65. package/dist/src/config/providerManager.js.map +1 -0
  66. package/dist/src/config/providerPersistence.d.ts +75 -0
  67. package/dist/src/config/providerPersistence.js +55 -0
  68. package/dist/src/config/providerPersistence.js.map +1 -0
  69. package/dist/src/config/providerPersistence.test.d.ts +6 -0
  70. package/dist/src/config/providerPersistence.test.js +283 -0
  71. package/dist/src/config/providerPersistence.test.js.map +1 -0
  72. package/dist/src/config/settingsSchema.d.ts +9 -0
  73. package/dist/src/config/settingsSchema.js +9 -0
  74. package/dist/src/config/settingsSchema.js.map +1 -1
  75. package/dist/src/generated/git-commit.d.ts +1 -1
  76. package/dist/src/generated/git-commit.js +1 -1
  77. package/dist/src/services/BuiltinCommandLoader.js +2 -0
  78. package/dist/src/services/BuiltinCommandLoader.js.map +1 -1
  79. package/dist/src/ui/App.js +14 -2
  80. package/dist/src/ui/App.js.map +1 -1
  81. package/dist/src/ui/commands/contextCommand.d.ts +7 -0
  82. package/dist/src/ui/commands/contextCommand.js +115 -0
  83. package/dist/src/ui/commands/contextCommand.js.map +1 -0
  84. package/dist/src/ui/components/ContextUsageDisplay.d.ts +3 -1
  85. package/dist/src/ui/components/ContextUsageDisplay.js +43 -3
  86. package/dist/src/ui/components/ContextUsageDisplay.js.map +1 -1
  87. package/dist/src/ui/components/Footer.d.ts +1 -0
  88. package/dist/src/ui/components/Footer.js +2 -2
  89. package/dist/src/ui/components/Footer.js.map +1 -1
  90. package/dist/src/ui/components/GeminiKeyDialog.d.ts +11 -0
  91. package/dist/src/ui/components/GeminiKeyDialog.js +156 -0
  92. package/dist/src/ui/components/GeminiKeyDialog.js.map +1 -0
  93. package/dist/src/ui/components/OpenAIEndpointDialog.d.ts +19 -0
  94. package/dist/src/ui/components/OpenAIEndpointDialog.js +163 -0
  95. package/dist/src/ui/components/OpenAIEndpointDialog.js.map +1 -0
  96. package/dist/src/ui/components/WelcomeBackDialog.d.ts +36 -0
  97. package/dist/src/ui/components/WelcomeBackDialog.js +109 -0
  98. package/dist/src/ui/components/WelcomeBackDialog.js.map +1 -0
  99. package/dist/src/ui/hooks/useWelcomeBack.d.ts +52 -0
  100. package/dist/src/ui/hooks/useWelcomeBack.js +214 -0
  101. package/dist/src/ui/hooks/useWelcomeBack.js.map +1 -0
  102. package/dist/src/zed-integration/schema.d.ts +1516 -1516
  103. package/dist/test-setup.ts +12 -0
  104. package/dist/test-utils/customMatchers.ts +65 -0
  105. package/dist/test-utils/mockCommandContext.test.ts +62 -0
  106. package/dist/test-utils/mockCommandContext.ts +105 -0
  107. package/dist/test-utils/render.tsx +18 -0
  108. package/dist/tsconfig.tsbuildinfo +1 -1
  109. package/dist/ui/App.test.tsx +2181 -0
  110. package/dist/ui/App.tsx +1344 -0
  111. package/dist/ui/IdeIntegrationNudge.tsx +98 -0
  112. package/dist/ui/__snapshots__/App.test.tsx.snap +124 -0
  113. package/dist/ui/colors.ts +56 -0
  114. package/dist/ui/commands/aboutCommand.test.ts +153 -0
  115. package/dist/ui/commands/aboutCommand.ts +49 -0
  116. package/dist/ui/commands/authCommand.test.ts +36 -0
  117. package/dist/ui/commands/authCommand.ts +17 -0
  118. package/dist/ui/commands/bugCommand.test.ts +114 -0
  119. package/dist/ui/commands/bugCommand.ts +92 -0
  120. package/dist/ui/commands/chatCommand.test.ts +414 -0
  121. package/dist/ui/commands/chatCommand.ts +280 -0
  122. package/dist/ui/commands/clearCommand.test.ts +100 -0
  123. package/dist/ui/commands/clearCommand.ts +29 -0
  124. package/dist/ui/commands/compressCommand.test.ts +129 -0
  125. package/dist/ui/commands/compressCommand.ts +78 -0
  126. package/dist/ui/commands/contextCommand.ts +132 -0
  127. package/dist/ui/commands/copyCommand.test.ts +296 -0
  128. package/dist/ui/commands/copyCommand.ts +67 -0
  129. package/dist/ui/commands/corgiCommand.test.ts +34 -0
  130. package/dist/ui/commands/corgiCommand.ts +16 -0
  131. package/dist/ui/commands/directoryCommand.test.tsx +185 -0
  132. package/dist/ui/commands/directoryCommand.tsx +179 -0
  133. package/dist/ui/commands/docsCommand.test.ts +99 -0
  134. package/dist/ui/commands/docsCommand.ts +42 -0
  135. package/dist/ui/commands/editorCommand.test.ts +30 -0
  136. package/dist/ui/commands/editorCommand.ts +21 -0
  137. package/dist/ui/commands/extensionsCommand.test.ts +67 -0
  138. package/dist/ui/commands/extensionsCommand.ts +46 -0
  139. package/dist/ui/commands/helpCommand.test.ts +52 -0
  140. package/dist/ui/commands/helpCommand.ts +23 -0
  141. package/dist/ui/commands/ideCommand.test.ts +255 -0
  142. package/dist/ui/commands/ideCommand.ts +283 -0
  143. package/dist/ui/commands/initCommand.test.ts +127 -0
  144. package/dist/ui/commands/initCommand.ts +117 -0
  145. package/dist/ui/commands/mcpCommand.test.ts +1057 -0
  146. package/dist/ui/commands/mcpCommand.ts +531 -0
  147. package/dist/ui/commands/memoryCommand.test.ts +344 -0
  148. package/dist/ui/commands/memoryCommand.ts +305 -0
  149. package/dist/ui/commands/privacyCommand.test.ts +38 -0
  150. package/dist/ui/commands/privacyCommand.ts +17 -0
  151. package/dist/ui/commands/quitCommand.test.ts +55 -0
  152. package/dist/ui/commands/quitCommand.ts +36 -0
  153. package/dist/ui/commands/restoreCommand.test.ts +250 -0
  154. package/dist/ui/commands/restoreCommand.ts +157 -0
  155. package/dist/ui/commands/searchEngineSetupCommand.ts +18 -0
  156. package/dist/ui/commands/settingsCommand.test.ts +36 -0
  157. package/dist/ui/commands/settingsCommand.ts +17 -0
  158. package/dist/ui/commands/setupGithubCommand.test.ts +238 -0
  159. package/dist/ui/commands/setupGithubCommand.ts +212 -0
  160. package/dist/ui/commands/speakCommand.ts +175 -0
  161. package/dist/ui/commands/statsCommand.test.ts +78 -0
  162. package/dist/ui/commands/statsCommand.ts +70 -0
  163. package/dist/ui/commands/terminalSetupCommand.test.ts +85 -0
  164. package/dist/ui/commands/terminalSetupCommand.ts +45 -0
  165. package/dist/ui/commands/themeCommand.test.ts +38 -0
  166. package/dist/ui/commands/themeCommand.ts +17 -0
  167. package/dist/ui/commands/toolsCommand.test.ts +105 -0
  168. package/dist/ui/commands/toolsCommand.ts +71 -0
  169. package/dist/ui/commands/ttsCommand.ts +143 -0
  170. package/dist/ui/commands/types.ts +204 -0
  171. package/dist/ui/commands/vimCommand.ts +25 -0
  172. package/dist/ui/commands/voiceCommand.ts +125 -0
  173. package/dist/ui/components/AboutBox.tsx +133 -0
  174. package/dist/ui/components/AsciiArt.ts +54 -0
  175. package/dist/ui/components/AuthDialog.test.tsx +334 -0
  176. package/dist/ui/components/AuthDialog.tsx +289 -0
  177. package/dist/ui/components/AuthInProgress.tsx +62 -0
  178. package/dist/ui/components/AutoAcceptIndicator.tsx +47 -0
  179. package/dist/ui/components/ConsoleSummaryDisplay.tsx +35 -0
  180. package/dist/ui/components/ContextSummaryDisplay.test.tsx +85 -0
  181. package/dist/ui/components/ContextSummaryDisplay.tsx +120 -0
  182. package/dist/ui/components/ContextUsageDisplay.tsx +77 -0
  183. package/dist/ui/components/DebugProfiler.tsx +36 -0
  184. package/dist/ui/components/DetailedMessagesDisplay.tsx +82 -0
  185. package/dist/ui/components/EditorSettingsDialog.tsx +172 -0
  186. package/dist/ui/components/FolderTrustDialog.test.tsx +36 -0
  187. package/dist/ui/components/FolderTrustDialog.tsx +74 -0
  188. package/dist/ui/components/Footer.test.tsx +159 -0
  189. package/dist/ui/components/Footer.tsx +158 -0
  190. package/dist/ui/components/GeminiKeyDialog.tsx +252 -0
  191. package/dist/ui/components/GeminiRespondingSpinner.tsx +34 -0
  192. package/dist/ui/components/Header.test.tsx +44 -0
  193. package/dist/ui/components/Header.tsx +70 -0
  194. package/dist/ui/components/Help.tsx +174 -0
  195. package/dist/ui/components/HistoryItemDisplay.test.tsx +125 -0
  196. package/dist/ui/components/HistoryItemDisplay.tsx +98 -0
  197. package/dist/ui/components/InputPrompt.test.tsx +1467 -0
  198. package/dist/ui/components/InputPrompt.tsx +641 -0
  199. package/dist/ui/components/LMStudioModelPrompt.tsx +215 -0
  200. package/dist/ui/components/LoadingIndicator.test.tsx +296 -0
  201. package/dist/ui/components/LoadingIndicator.tsx +82 -0
  202. package/dist/ui/components/MemoryUsageDisplay.tsx +36 -0
  203. package/dist/ui/components/ModelStatsDisplay.test.tsx +252 -0
  204. package/dist/ui/components/ModelStatsDisplay.tsx +197 -0
  205. package/dist/ui/components/OllamaModelPrompt.tsx +206 -0
  206. package/dist/ui/components/OpenAIEndpointDialog.tsx +261 -0
  207. package/dist/ui/components/OpenAIKeyPrompt.test.tsx +64 -0
  208. package/dist/ui/components/OpenAIKeyPrompt.tsx +197 -0
  209. package/dist/ui/components/PrepareLabel.tsx +48 -0
  210. package/dist/ui/components/SearchEngineConfigDialog.tsx +280 -0
  211. package/dist/ui/components/SessionSummaryDisplay.test.tsx +75 -0
  212. package/dist/ui/components/SessionSummaryDisplay.tsx +18 -0
  213. package/dist/ui/components/SettingsDialog.test.tsx +865 -0
  214. package/dist/ui/components/SettingsDialog.tsx +753 -0
  215. package/dist/ui/components/ShellConfirmationDialog.test.tsx +53 -0
  216. package/dist/ui/components/ShellConfirmationDialog.tsx +103 -0
  217. package/dist/ui/components/ShellModeIndicator.tsx +18 -0
  218. package/dist/ui/components/ShowMoreLines.tsx +40 -0
  219. package/dist/ui/components/StatsDisplay.test.tsx +401 -0
  220. package/dist/ui/components/StatsDisplay.tsx +273 -0
  221. package/dist/ui/components/SuggestionsDisplay.tsx +102 -0
  222. package/dist/ui/components/ThemeDialog.tsx +310 -0
  223. package/dist/ui/components/Tips.tsx +45 -0
  224. package/dist/ui/components/TodoDisplay.test.tsx +97 -0
  225. package/dist/ui/components/TodoDisplay.tsx +72 -0
  226. package/dist/ui/components/ToolStatsDisplay.test.tsx +180 -0
  227. package/dist/ui/components/ToolStatsDisplay.tsx +208 -0
  228. package/dist/ui/components/UpdateNotification.tsx +23 -0
  229. package/dist/ui/components/WelcomeBackDialog.tsx +290 -0
  230. package/dist/ui/components/__snapshots__/IDEContextDetailDisplay.test.tsx.snap +24 -0
  231. package/dist/ui/components/__snapshots__/ModelStatsDisplay.test.tsx.snap +121 -0
  232. package/dist/ui/components/__snapshots__/SessionSummaryDisplay.test.tsx.snap +30 -0
  233. package/dist/ui/components/__snapshots__/ShellConfirmationDialog.test.tsx.snap +21 -0
  234. package/dist/ui/components/__snapshots__/StatsDisplay.test.tsx.snap +264 -0
  235. package/dist/ui/components/__snapshots__/ToolStatsDisplay.test.tsx.snap +91 -0
  236. package/dist/ui/components/messages/CompressionMessage.tsx +49 -0
  237. package/dist/ui/components/messages/DiffRenderer.test.tsx +365 -0
  238. package/dist/ui/components/messages/DiffRenderer.tsx +358 -0
  239. package/dist/ui/components/messages/ErrorMessage.tsx +31 -0
  240. package/dist/ui/components/messages/GeminiMessage.tsx +43 -0
  241. package/dist/ui/components/messages/GeminiMessageContent.tsx +43 -0
  242. package/dist/ui/components/messages/InfoMessage.tsx +32 -0
  243. package/dist/ui/components/messages/ToolConfirmationMessage.test.tsx +58 -0
  244. package/dist/ui/components/messages/ToolConfirmationMessage.tsx +297 -0
  245. package/dist/ui/components/messages/ToolGroupMessage.tsx +126 -0
  246. package/dist/ui/components/messages/ToolMessage.test.tsx +183 -0
  247. package/dist/ui/components/messages/ToolMessage.tsx +296 -0
  248. package/dist/ui/components/messages/UserMessage.tsx +43 -0
  249. package/dist/ui/components/messages/UserShellMessage.tsx +25 -0
  250. package/dist/ui/components/shared/MaxSizedBox.test.tsx +425 -0
  251. package/dist/ui/components/shared/MaxSizedBox.tsx +624 -0
  252. package/dist/ui/components/shared/RadioButtonSelect.test.tsx +181 -0
  253. package/dist/ui/components/shared/RadioButtonSelect.tsx +234 -0
  254. package/dist/ui/components/shared/__snapshots__/RadioButtonSelect.test.tsx.snap +47 -0
  255. package/dist/ui/components/shared/text-buffer.test.ts +1728 -0
  256. package/dist/ui/components/shared/text-buffer.ts +2227 -0
  257. package/dist/ui/components/shared/vim-buffer-actions.test.ts +1119 -0
  258. package/dist/ui/components/shared/vim-buffer-actions.ts +814 -0
  259. package/dist/ui/constants.ts +17 -0
  260. package/dist/ui/contexts/KeypressContext.test.tsx +391 -0
  261. package/dist/ui/contexts/KeypressContext.tsx +440 -0
  262. package/dist/ui/contexts/OverflowContext.tsx +87 -0
  263. package/dist/ui/contexts/SessionContext.test.tsx +132 -0
  264. package/dist/ui/contexts/SessionContext.tsx +143 -0
  265. package/dist/ui/contexts/SettingsContext.tsx +20 -0
  266. package/dist/ui/contexts/StreamingContext.tsx +22 -0
  267. package/dist/ui/contexts/VimModeContext.tsx +79 -0
  268. package/dist/ui/editors/editorSettingsManager.ts +66 -0
  269. package/dist/ui/hooks/atCommandProcessor.test.ts +1102 -0
  270. package/dist/ui/hooks/atCommandProcessor.ts +485 -0
  271. package/dist/ui/hooks/shellCommandProcessor.test.ts +481 -0
  272. package/dist/ui/hooks/shellCommandProcessor.ts +314 -0
  273. package/dist/ui/hooks/slashCommandProcessor.test.ts +1044 -0
  274. package/dist/ui/hooks/slashCommandProcessor.ts +595 -0
  275. package/dist/ui/hooks/useAtCompletion.test.ts +497 -0
  276. package/dist/ui/hooks/useAtCompletion.ts +244 -0
  277. package/dist/ui/hooks/useAuthCommand.ts +129 -0
  278. package/dist/ui/hooks/useAutoAcceptIndicator.test.ts +300 -0
  279. package/dist/ui/hooks/useAutoAcceptIndicator.ts +52 -0
  280. package/dist/ui/hooks/useBracketedPaste.ts +37 -0
  281. package/dist/ui/hooks/useCommandCompletion.test.ts +518 -0
  282. package/dist/ui/hooks/useCommandCompletion.tsx +238 -0
  283. package/dist/ui/hooks/useCompletion.ts +128 -0
  284. package/dist/ui/hooks/useConsoleMessages.test.ts +147 -0
  285. package/dist/ui/hooks/useConsoleMessages.ts +110 -0
  286. package/dist/ui/hooks/useEditorSettings.test.ts +283 -0
  287. package/dist/ui/hooks/useEditorSettings.ts +75 -0
  288. package/dist/ui/hooks/useFocus.test.ts +119 -0
  289. package/dist/ui/hooks/useFocus.ts +48 -0
  290. package/dist/ui/hooks/useFolderTrust.test.ts +159 -0
  291. package/dist/ui/hooks/useFolderTrust.ts +72 -0
  292. package/dist/ui/hooks/useGeminiStream.test.tsx +1998 -0
  293. package/dist/ui/hooks/useGeminiStream.ts +1017 -0
  294. package/dist/ui/hooks/useGitBranchName.test.ts +280 -0
  295. package/dist/ui/hooks/useGitBranchName.ts +79 -0
  296. package/dist/ui/hooks/useHistoryManager.test.ts +202 -0
  297. package/dist/ui/hooks/useHistoryManager.ts +111 -0
  298. package/dist/ui/hooks/useInputHistory.test.ts +261 -0
  299. package/dist/ui/hooks/useInputHistory.ts +111 -0
  300. package/dist/ui/hooks/useKeypress.test.ts +280 -0
  301. package/dist/ui/hooks/useKeypress.ts +39 -0
  302. package/dist/ui/hooks/useKittyKeyboardProtocol.ts +31 -0
  303. package/dist/ui/hooks/useLoadingIndicator.test.ts +139 -0
  304. package/dist/ui/hooks/useLoadingIndicator.ts +57 -0
  305. package/dist/ui/hooks/useLogger.ts +32 -0
  306. package/dist/ui/hooks/useMessageQueue.test.ts +226 -0
  307. package/dist/ui/hooks/useMessageQueue.ts +69 -0
  308. package/dist/ui/hooks/usePhraseCycler.test.ts +145 -0
  309. package/dist/ui/hooks/usePhraseCycler.ts +198 -0
  310. package/dist/ui/hooks/usePrivacySettings.test.ts +242 -0
  311. package/dist/ui/hooks/usePrivacySettings.ts +150 -0
  312. package/dist/ui/hooks/useReactToolScheduler.ts +309 -0
  313. package/dist/ui/hooks/useRefreshMemoryCommand.ts +7 -0
  314. package/dist/ui/hooks/useReverseSearchCompletion.test.tsx +260 -0
  315. package/dist/ui/hooks/useReverseSearchCompletion.tsx +95 -0
  316. package/dist/ui/hooks/useSettingsCommand.ts +25 -0
  317. package/dist/ui/hooks/useShellHistory.test.ts +219 -0
  318. package/dist/ui/hooks/useShellHistory.ts +133 -0
  319. package/dist/ui/hooks/useShowMemoryCommand.ts +75 -0
  320. package/dist/ui/hooks/useSlashCompletion.test.ts +434 -0
  321. package/dist/ui/hooks/useSlashCompletion.ts +187 -0
  322. package/dist/ui/hooks/useStateAndRef.ts +36 -0
  323. package/dist/ui/hooks/useTerminalSize.ts +32 -0
  324. package/dist/ui/hooks/useThemeCommand.ts +110 -0
  325. package/dist/ui/hooks/useTimer.test.ts +120 -0
  326. package/dist/ui/hooks/useTimer.ts +65 -0
  327. package/dist/ui/hooks/useToolScheduler.test.ts +1123 -0
  328. package/dist/ui/hooks/useWelcomeBack.ts +253 -0
  329. package/dist/ui/hooks/vim.test.ts +1691 -0
  330. package/dist/ui/hooks/vim.ts +784 -0
  331. package/dist/ui/keyMatchers.test.ts +337 -0
  332. package/dist/ui/keyMatchers.ts +105 -0
  333. package/dist/ui/privacy/CloudFreePrivacyNotice.tsx +117 -0
  334. package/dist/ui/privacy/CloudPaidPrivacyNotice.tsx +59 -0
  335. package/dist/ui/privacy/GeminiPrivacyNotice.tsx +62 -0
  336. package/dist/ui/privacy/PrivacyNotice.tsx +42 -0
  337. package/dist/ui/semantic-colors.ts +26 -0
  338. package/dist/ui/themes/ansi-light.ts +150 -0
  339. package/dist/ui/themes/ansi.ts +159 -0
  340. package/dist/ui/themes/atom-one-dark.ts +147 -0
  341. package/dist/ui/themes/ayu-light.ts +139 -0
  342. package/dist/ui/themes/ayu.ts +113 -0
  343. package/dist/ui/themes/color-utils.test.ts +221 -0
  344. package/dist/ui/themes/color-utils.ts +231 -0
  345. package/dist/ui/themes/default-light.ts +108 -0
  346. package/dist/ui/themes/default.ts +151 -0
  347. package/dist/ui/themes/dracula.ts +124 -0
  348. package/dist/ui/themes/fss-code-dark.ts +156 -0
  349. package/dist/ui/themes/fss-dark.ts +113 -0
  350. package/dist/ui/themes/fss-light.ts +139 -0
  351. package/dist/ui/themes/github-dark.ts +147 -0
  352. package/dist/ui/themes/github-light.ts +149 -0
  353. package/dist/ui/themes/googlecode.ts +146 -0
  354. package/dist/ui/themes/no-color.ts +125 -0
  355. package/dist/ui/themes/qwen-dark.ts +118 -0
  356. package/dist/ui/themes/qwen-light.ts +144 -0
  357. package/dist/ui/themes/semantic-tokens.ts +127 -0
  358. package/dist/ui/themes/shades-of-purple.ts +352 -0
  359. package/dist/ui/themes/theme-manager.test.ts +99 -0
  360. package/dist/ui/themes/theme-manager.ts +257 -0
  361. package/dist/ui/themes/theme.test.ts +97 -0
  362. package/dist/ui/themes/theme.ts +451 -0
  363. package/dist/ui/themes/xcode.ts +154 -0
  364. package/dist/ui/types.ts +255 -0
  365. package/dist/ui/utils/CodeColorizer.tsx +217 -0
  366. package/dist/ui/utils/ConsolePatcher.ts +71 -0
  367. package/dist/ui/utils/InlineMarkdownRenderer.tsx +173 -0
  368. package/dist/ui/utils/MarkdownDisplay.test.tsx +244 -0
  369. package/dist/ui/utils/MarkdownDisplay.tsx +415 -0
  370. package/dist/ui/utils/TableRenderer.tsx +159 -0
  371. package/dist/ui/utils/__snapshots__/MarkdownDisplay.test.tsx.snap +93 -0
  372. package/dist/ui/utils/clipboardUtils.test.ts +76 -0
  373. package/dist/ui/utils/clipboardUtils.ts +149 -0
  374. package/dist/ui/utils/commandUtils.test.ts +384 -0
  375. package/dist/ui/utils/commandUtils.ts +106 -0
  376. package/dist/ui/utils/computeStats.test.ts +292 -0
  377. package/dist/ui/utils/computeStats.ts +86 -0
  378. package/dist/ui/utils/displayUtils.test.ts +58 -0
  379. package/dist/ui/utils/displayUtils.ts +32 -0
  380. package/dist/ui/utils/formatters.test.ts +72 -0
  381. package/dist/ui/utils/formatters.ts +63 -0
  382. package/dist/ui/utils/isNarrowWidth.ts +9 -0
  383. package/dist/ui/utils/kittyProtocolDetector.ts +105 -0
  384. package/dist/ui/utils/markdownUtilities.test.ts +50 -0
  385. package/dist/ui/utils/markdownUtilities.ts +125 -0
  386. package/dist/ui/utils/platformConstants.ts +52 -0
  387. package/dist/ui/utils/terminalSetup.ts +342 -0
  388. package/dist/ui/utils/textUtils.ts +40 -0
  389. package/dist/ui/utils/updateCheck.test.ts +163 -0
  390. package/dist/ui/utils/updateCheck.ts +100 -0
  391. package/dist/utils/checks.ts +28 -0
  392. package/dist/utils/cleanup.test.ts +68 -0
  393. package/dist/utils/cleanup.ts +36 -0
  394. package/dist/utils/dialogScopeUtils.ts +64 -0
  395. package/dist/utils/events.ts +14 -0
  396. package/dist/utils/gitUtils.test.ts +149 -0
  397. package/dist/utils/gitUtils.ts +116 -0
  398. package/dist/utils/handleAutoUpdate.test.ts +272 -0
  399. package/dist/utils/handleAutoUpdate.ts +145 -0
  400. package/dist/utils/installationInfo.test.ts +315 -0
  401. package/dist/utils/installationInfo.ts +176 -0
  402. package/dist/utils/package.ts +38 -0
  403. package/dist/utils/readStdin.ts +51 -0
  404. package/dist/utils/resolvePath.ts +21 -0
  405. package/dist/utils/sandbox-macos-permissive-closed.sb +32 -0
  406. package/dist/utils/sandbox-macos-permissive-open.sb +25 -0
  407. package/dist/utils/sandbox-macos-permissive-proxied.sb +37 -0
  408. package/dist/utils/sandbox-macos-restrictive-closed.sb +93 -0
  409. package/dist/utils/sandbox-macos-restrictive-open.sb +96 -0
  410. package/dist/utils/sandbox-macos-restrictive-proxied.sb +98 -0
  411. package/dist/utils/sandbox.ts +962 -0
  412. package/dist/utils/settingsUtils.test.ts +797 -0
  413. package/dist/utils/settingsUtils.ts +489 -0
  414. package/dist/utils/spawnWrapper.ts +9 -0
  415. package/dist/utils/startupWarnings.test.ts +83 -0
  416. package/dist/utils/startupWarnings.ts +40 -0
  417. package/dist/utils/updateEventEmitter.ts +13 -0
  418. package/dist/utils/userStartupWarnings.test.ts +87 -0
  419. package/dist/utils/userStartupWarnings.ts +69 -0
  420. package/dist/utils/version.ts +12 -0
  421. package/dist/validateNonInterActiveAuth.test.ts +260 -0
  422. package/dist/validateNonInterActiveAuth.ts +51 -0
  423. package/dist/vitest.config.ts +37 -0
  424. package/dist/zed-integration/acp.ts +366 -0
  425. package/dist/zed-integration/fileSystemService.ts +47 -0
  426. package/dist/zed-integration/schema.ts +466 -0
  427. package/dist/zed-integration/zedIntegration.ts +944 -0
  428. package/package.json +2 -2
@@ -0,0 +1,865 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2025 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ /**
8
+ *
9
+ *
10
+ * This test suite covers:
11
+ * - Initial rendering and display state
12
+ * - Keyboard navigation (arrows, vim keys, Tab)
13
+ * - Settings toggling (Enter, Space)
14
+ * - Focus section switching between settings and scope selector
15
+ * - Scope selection and settings persistence across scopes
16
+ * - Restart-required vs immediate settings behavior
17
+ * - VimModeContext integration
18
+ * - Complex user interaction workflows
19
+ * - Error handling and edge cases
20
+ * - Display values for inherited and overridden settings
21
+ *
22
+ */
23
+
24
+ import { render } from 'ink-testing-library';
25
+ import { waitFor } from '@testing-library/react';
26
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
27
+ import { SettingsDialog } from './SettingsDialog.js';
28
+ import { LoadedSettings } from '../../config/settings.js';
29
+ import { VimModeProvider } from '../contexts/VimModeContext.js';
30
+
31
+ // Mock the VimModeContext
32
+ const mockToggleVimEnabled = vi.fn();
33
+ const mockSetVimMode = vi.fn();
34
+
35
+ vi.mock('../contexts/VimModeContext.js', async () => {
36
+ const actual = await vi.importActual('../contexts/VimModeContext.js');
37
+ return {
38
+ ...actual,
39
+ useVimMode: () => ({
40
+ vimEnabled: false,
41
+ vimMode: 'INSERT' as const,
42
+ toggleVimEnabled: mockToggleVimEnabled,
43
+ setVimMode: mockSetVimMode,
44
+ }),
45
+ };
46
+ });
47
+
48
+ vi.mock('../../utils/settingsUtils.js', async () => {
49
+ const actual = await vi.importActual('../../utils/settingsUtils.js');
50
+ return {
51
+ ...actual,
52
+ saveModifiedSettings: vi.fn(),
53
+ };
54
+ });
55
+
56
+ // Mock the useKeypress hook to avoid context issues
57
+ interface Key {
58
+ name: string;
59
+ ctrl: boolean;
60
+ meta: boolean;
61
+ shift: boolean;
62
+ paste: boolean;
63
+ sequence: string;
64
+ }
65
+
66
+ // Variables for keypress simulation (not currently used)
67
+ // let currentKeypressHandler: ((key: Key) => void) | null = null;
68
+ // let isKeypressActive = false;
69
+
70
+ vi.mock('../hooks/useKeypress.js', () => ({
71
+ useKeypress: vi.fn(
72
+ (_handler: (key: Key) => void, _options: { isActive: boolean }) => {
73
+ // Mock implementation - simplified for test stability
74
+ },
75
+ ),
76
+ }));
77
+
78
+ // Helper function to simulate key presses (commented out for now)
79
+ // const simulateKeyPress = async (keyData: Partial<Key> & { name: string }) => {
80
+ // if (currentKeypressHandler) {
81
+ // const key: Key = {
82
+ // ctrl: false,
83
+ // meta: false,
84
+ // shift: false,
85
+ // paste: false,
86
+ // sequence: keyData.sequence || keyData.name,
87
+ // ...keyData,
88
+ // };
89
+ // currentKeypressHandler(key);
90
+ // // Allow React to process the state update
91
+ // await new Promise(resolve => setTimeout(resolve, 10));
92
+ // }
93
+ // };
94
+
95
+ // Mock console.log to avoid noise in tests
96
+ // const originalConsoleLog = console.log;
97
+ // const originalConsoleError = console.error;
98
+
99
+ describe('SettingsDialog', () => {
100
+ const wait = (ms = 50) => new Promise((resolve) => setTimeout(resolve, ms));
101
+
102
+ beforeEach(() => {
103
+ vi.clearAllMocks();
104
+ // Reset keypress mock state (variables are commented out)
105
+ // currentKeypressHandler = null;
106
+ // isKeypressActive = false;
107
+ // console.log = vi.fn();
108
+ // console.error = vi.fn();
109
+ mockToggleVimEnabled.mockResolvedValue(true);
110
+ });
111
+
112
+ afterEach(() => {
113
+ // Reset keypress mock state (variables are commented out)
114
+ // currentKeypressHandler = null;
115
+ // isKeypressActive = false;
116
+ // console.log = originalConsoleLog;
117
+ // console.error = originalConsoleError;
118
+ });
119
+
120
+ const createMockSettings = (
121
+ userSettings = {},
122
+ systemSettings = {},
123
+ workspaceSettings = {},
124
+ ) =>
125
+ new LoadedSettings(
126
+ {
127
+ settings: { customThemes: {}, mcpServers: {}, ...systemSettings },
128
+ path: '/system/settings.json',
129
+ },
130
+ {
131
+ settings: {
132
+ customThemes: {},
133
+ mcpServers: {},
134
+ ...userSettings,
135
+ },
136
+ path: '/user/settings.json',
137
+ },
138
+ {
139
+ settings: { customThemes: {}, mcpServers: {}, ...workspaceSettings },
140
+ path: '/workspace/settings.json',
141
+ },
142
+ [],
143
+ );
144
+
145
+ describe('Initial Rendering', () => {
146
+ it('should render the settings dialog with default state', () => {
147
+ const settings = createMockSettings();
148
+ const onSelect = vi.fn();
149
+
150
+ const { lastFrame } = render(
151
+ <SettingsDialog settings={settings} onSelect={onSelect} />,
152
+ );
153
+
154
+ const output = lastFrame();
155
+ expect(output).toContain('Settings');
156
+ expect(output).toContain('Apply To');
157
+ expect(output).toContain('Use Enter to select, Tab to change focus');
158
+ });
159
+
160
+ it('should show settings list with default values', () => {
161
+ const settings = createMockSettings();
162
+ const onSelect = vi.fn();
163
+
164
+ const { lastFrame } = render(
165
+ <SettingsDialog settings={settings} onSelect={onSelect} />,
166
+ );
167
+
168
+ const output = lastFrame();
169
+ // Should show some default settings
170
+ expect(output).toContain('●'); // Active indicator
171
+ });
172
+
173
+ it('should highlight first setting by default', () => {
174
+ const settings = createMockSettings();
175
+ const onSelect = vi.fn();
176
+
177
+ const { lastFrame } = render(
178
+ <SettingsDialog settings={settings} onSelect={onSelect} />,
179
+ );
180
+
181
+ const output = lastFrame();
182
+ // First item should be highlighted with green color and active indicator
183
+ expect(output).toContain('●');
184
+ });
185
+ });
186
+
187
+ describe('Settings Navigation', () => {
188
+ it('should navigate down with arrow key', async () => {
189
+ const settings = createMockSettings();
190
+ const onSelect = vi.fn();
191
+
192
+ const { stdin, unmount } = render(
193
+ <SettingsDialog settings={settings} onSelect={onSelect} />,
194
+ );
195
+
196
+ // Press down arrow
197
+ stdin.write('\u001B[B'); // Down arrow
198
+ await wait();
199
+
200
+ // The active index should have changed (tested indirectly through behavior)
201
+ unmount();
202
+ });
203
+
204
+ it('should navigate up with arrow key', async () => {
205
+ const settings = createMockSettings();
206
+ const onSelect = vi.fn();
207
+
208
+ const { stdin, unmount } = render(
209
+ <SettingsDialog settings={settings} onSelect={onSelect} />,
210
+ );
211
+
212
+ // First go down, then up
213
+ stdin.write('\u001B[B'); // Down arrow
214
+ await wait();
215
+ stdin.write('\u001B[A'); // Up arrow
216
+ await wait();
217
+
218
+ unmount();
219
+ });
220
+
221
+ it('should navigate with vim keys (j/k)', async () => {
222
+ const settings = createMockSettings();
223
+ const onSelect = vi.fn();
224
+
225
+ const { stdin, unmount } = render(
226
+ <SettingsDialog settings={settings} onSelect={onSelect} />,
227
+ );
228
+
229
+ // Navigate with vim keys
230
+ stdin.write('j'); // Down
231
+ await wait();
232
+ stdin.write('k'); // Up
233
+ await wait();
234
+
235
+ unmount();
236
+ });
237
+
238
+ it('should not navigate beyond bounds', async () => {
239
+ const settings = createMockSettings();
240
+ const onSelect = vi.fn();
241
+
242
+ const { stdin, unmount } = render(
243
+ <SettingsDialog settings={settings} onSelect={onSelect} />,
244
+ );
245
+
246
+ // Try to go up from first item
247
+ stdin.write('\u001B[A'); // Up arrow
248
+ await wait();
249
+
250
+ // Should still be on first item
251
+ unmount();
252
+ });
253
+ });
254
+
255
+ describe('Settings Toggling', () => {
256
+ it('should toggle setting with Enter key', async () => {
257
+ const settings = createMockSettings();
258
+ const onSelect = vi.fn();
259
+
260
+ const { stdin, unmount } = render(
261
+ <SettingsDialog settings={settings} onSelect={onSelect} />,
262
+ );
263
+
264
+ // Press Enter to toggle current setting
265
+ stdin.write('\u000D'); // Enter key
266
+ await wait();
267
+
268
+ unmount();
269
+ });
270
+
271
+ it('should toggle setting with Space key', async () => {
272
+ const settings = createMockSettings();
273
+ const onSelect = vi.fn();
274
+
275
+ const { stdin, unmount } = render(
276
+ <SettingsDialog settings={settings} onSelect={onSelect} />,
277
+ );
278
+
279
+ // Press Space to toggle current setting
280
+ stdin.write(' '); // Space key
281
+ await wait();
282
+
283
+ unmount();
284
+ });
285
+
286
+ it('should handle vim mode setting specially', async () => {
287
+ const settings = createMockSettings();
288
+ const onSelect = vi.fn();
289
+
290
+ const { stdin, unmount } = render(
291
+ <SettingsDialog settings={settings} onSelect={onSelect} />,
292
+ );
293
+
294
+ // Navigate to vim mode setting and toggle it
295
+ // This would require knowing the exact position, so we'll just test that the mock is called
296
+ stdin.write('\u000D'); // Enter key
297
+ await wait();
298
+
299
+ // The mock should potentially be called if vim mode was toggled
300
+ unmount();
301
+ });
302
+ });
303
+
304
+ describe('Scope Selection', () => {
305
+ it('should switch between scopes', async () => {
306
+ const settings = createMockSettings();
307
+ const onSelect = vi.fn();
308
+
309
+ const { stdin, unmount } = render(
310
+ <SettingsDialog settings={settings} onSelect={onSelect} />,
311
+ );
312
+
313
+ // Switch to scope focus
314
+ stdin.write('\t'); // Tab key
315
+ await wait();
316
+
317
+ // Select different scope (numbers 1-3 typically available)
318
+ stdin.write('2'); // Select second scope option
319
+ await wait();
320
+
321
+ unmount();
322
+ });
323
+
324
+ it('should reset to settings focus when scope is selected', async () => {
325
+ const settings = createMockSettings();
326
+ const onSelect = vi.fn();
327
+
328
+ const { lastFrame, unmount } = render(
329
+ <SettingsDialog settings={settings} onSelect={onSelect} />,
330
+ );
331
+
332
+ // Wait for initial render
333
+ await waitFor(() => {
334
+ expect(lastFrame()).toContain('Hide Window Title');
335
+ });
336
+
337
+ // The UI should show the settings section is active and scope section is inactive
338
+ expect(lastFrame()).toContain('● Hide Window Title'); // Settings section active
339
+ expect(lastFrame()).toContain(' Apply To'); // Scope section inactive
340
+
341
+ // This test validates the initial state - scope selection behavior
342
+ // is complex due to keypress handling, so we focus on state validation
343
+
344
+ unmount();
345
+ });
346
+ });
347
+
348
+ describe('Restart Prompt', () => {
349
+ it('should show restart prompt for restart-required settings', async () => {
350
+ const settings = createMockSettings();
351
+ const onRestartRequest = vi.fn();
352
+
353
+ const { unmount } = render(
354
+ <SettingsDialog
355
+ settings={settings}
356
+ onSelect={() => {}}
357
+ onRestartRequest={onRestartRequest}
358
+ />,
359
+ );
360
+
361
+ // This test would need to trigger a restart-required setting change
362
+ // The exact steps depend on which settings require restart
363
+ await wait();
364
+
365
+ unmount();
366
+ });
367
+
368
+ it('should handle restart request when r is pressed', async () => {
369
+ const settings = createMockSettings();
370
+ const onRestartRequest = vi.fn();
371
+
372
+ const { stdin, unmount } = render(
373
+ <SettingsDialog
374
+ settings={settings}
375
+ onSelect={() => {}}
376
+ onRestartRequest={onRestartRequest}
377
+ />,
378
+ );
379
+
380
+ // Press 'r' key (this would only work if restart prompt is showing)
381
+ stdin.write('r');
382
+ await wait();
383
+
384
+ // If restart prompt was showing, onRestartRequest should be called
385
+ unmount();
386
+ });
387
+ });
388
+
389
+ describe('Escape Key Behavior', () => {
390
+ it('should call onSelect with undefined when Escape is pressed', async () => {
391
+ const settings = createMockSettings();
392
+ const onSelect = vi.fn();
393
+
394
+ const { lastFrame, unmount } = render(
395
+ <SettingsDialog settings={settings} onSelect={onSelect} />,
396
+ );
397
+
398
+ // Wait for initial render
399
+ await waitFor(() => {
400
+ expect(lastFrame()).toContain('Hide Window Title');
401
+ });
402
+
403
+ // Verify the dialog is rendered properly
404
+ expect(lastFrame()).toContain('Settings');
405
+ expect(lastFrame()).toContain('Apply To');
406
+
407
+ // This test validates rendering - escape key behavior depends on complex
408
+ // keypress handling that's difficult to test reliably in this environment
409
+
410
+ unmount();
411
+ });
412
+ });
413
+
414
+ describe('Settings Persistence', () => {
415
+ it('should persist settings across scope changes', async () => {
416
+ const settings = createMockSettings({ vimMode: true });
417
+ const onSelect = vi.fn();
418
+
419
+ const { stdin, unmount } = render(
420
+ <SettingsDialog settings={settings} onSelect={onSelect} />,
421
+ );
422
+
423
+ // Switch to scope selector
424
+ stdin.write('\t'); // Tab
425
+ await wait();
426
+
427
+ // Change scope
428
+ stdin.write('2'); // Select workspace scope
429
+ await wait();
430
+
431
+ // Settings should be reloaded for new scope
432
+ unmount();
433
+ });
434
+
435
+ it('should show different values for different scopes', () => {
436
+ const settings = createMockSettings(
437
+ { vimMode: true }, // User settings
438
+ { vimMode: false }, // System settings
439
+ { autoUpdate: false }, // Workspace settings
440
+ );
441
+ const onSelect = vi.fn();
442
+
443
+ const { lastFrame } = render(
444
+ <SettingsDialog settings={settings} onSelect={onSelect} />,
445
+ );
446
+
447
+ // Should show user scope values initially
448
+ const output = lastFrame();
449
+ expect(output).toContain('Settings');
450
+ });
451
+ });
452
+
453
+ describe('Error Handling', () => {
454
+ it('should handle vim mode toggle errors gracefully', async () => {
455
+ mockToggleVimEnabled.mockRejectedValue(new Error('Toggle failed'));
456
+
457
+ const settings = createMockSettings();
458
+ const onSelect = vi.fn();
459
+
460
+ const { stdin, unmount } = render(
461
+ <SettingsDialog settings={settings} onSelect={onSelect} />,
462
+ );
463
+
464
+ // Try to toggle a setting (this might trigger vim mode toggle)
465
+ stdin.write('\u000D'); // Enter
466
+ await wait();
467
+
468
+ // Should not crash
469
+ unmount();
470
+ });
471
+ });
472
+
473
+ describe('Complex State Management', () => {
474
+ it('should track modified settings correctly', async () => {
475
+ const settings = createMockSettings();
476
+ const onSelect = vi.fn();
477
+
478
+ const { stdin, unmount } = render(
479
+ <SettingsDialog settings={settings} onSelect={onSelect} />,
480
+ );
481
+
482
+ // Toggle a setting
483
+ stdin.write('\u000D'); // Enter
484
+ await wait();
485
+
486
+ // Toggle another setting
487
+ stdin.write('\u001B[B'); // Down
488
+ await wait();
489
+ stdin.write('\u000D'); // Enter
490
+ await wait();
491
+
492
+ // Should track multiple modified settings
493
+ unmount();
494
+ });
495
+
496
+ it('should handle scrolling when there are many settings', async () => {
497
+ const settings = createMockSettings();
498
+ const onSelect = vi.fn();
499
+
500
+ const { stdin, unmount } = render(
501
+ <SettingsDialog settings={settings} onSelect={onSelect} />,
502
+ );
503
+
504
+ // Navigate down many times to test scrolling
505
+ for (let i = 0; i < 10; i++) {
506
+ stdin.write('\u001B[B'); // Down arrow
507
+ await wait(10);
508
+ }
509
+
510
+ unmount();
511
+ });
512
+ });
513
+
514
+ describe('VimMode Integration', () => {
515
+ it('should sync with VimModeContext when vim mode is toggled', async () => {
516
+ const settings = createMockSettings();
517
+ const onSelect = vi.fn();
518
+
519
+ const { stdin, unmount } = render(
520
+ <VimModeProvider settings={settings}>
521
+ <SettingsDialog settings={settings} onSelect={onSelect} />
522
+ </VimModeProvider>,
523
+ );
524
+
525
+ // Navigate to and toggle vim mode setting
526
+ // This would require knowing the exact position of vim mode setting
527
+ stdin.write('\u000D'); // Enter
528
+ await wait();
529
+
530
+ unmount();
531
+ });
532
+ });
533
+
534
+ describe('Specific Settings Behavior', () => {
535
+ it('should show correct display values for settings with different states', () => {
536
+ const settings = createMockSettings(
537
+ { vimMode: true, hideTips: false }, // User settings
538
+ { hideWindowTitle: true }, // System settings
539
+ { ideMode: false }, // Workspace settings
540
+ );
541
+ const onSelect = vi.fn();
542
+
543
+ const { lastFrame } = render(
544
+ <SettingsDialog settings={settings} onSelect={onSelect} />,
545
+ );
546
+
547
+ const output = lastFrame();
548
+ // Should contain settings labels
549
+ expect(output).toContain('Settings');
550
+ });
551
+
552
+ it('should handle immediate settings save for non-restart-required settings', async () => {
553
+ const settings = createMockSettings();
554
+ const onSelect = vi.fn();
555
+
556
+ const { stdin, unmount } = render(
557
+ <SettingsDialog settings={settings} onSelect={onSelect} />,
558
+ );
559
+
560
+ // Toggle a non-restart-required setting (like hideTips)
561
+ stdin.write('\u000D'); // Enter - toggle current setting
562
+ await wait();
563
+
564
+ // Should save immediately without showing restart prompt
565
+ unmount();
566
+ });
567
+
568
+ it('should show restart prompt for restart-required settings', async () => {
569
+ const settings = createMockSettings();
570
+ const onSelect = vi.fn();
571
+
572
+ const { lastFrame, unmount } = render(
573
+ <SettingsDialog settings={settings} onSelect={onSelect} />,
574
+ );
575
+
576
+ // This test would need to navigate to a specific restart-required setting
577
+ // Since we can't easily target specific settings, we test the general behavior
578
+ await wait();
579
+
580
+ // Should not show restart prompt initially
581
+ expect(lastFrame()).not.toContain(
582
+ 'To see changes, Gemini CLI must be restarted',
583
+ );
584
+
585
+ unmount();
586
+ });
587
+
588
+ it('should clear restart prompt when switching scopes', async () => {
589
+ const settings = createMockSettings();
590
+ const onSelect = vi.fn();
591
+
592
+ const { unmount } = render(
593
+ <SettingsDialog settings={settings} onSelect={onSelect} />,
594
+ );
595
+
596
+ // Restart prompt should be cleared when switching scopes
597
+ unmount();
598
+ });
599
+ });
600
+
601
+ describe('Settings Display Values', () => {
602
+ it('should show correct values for inherited settings', () => {
603
+ const settings = createMockSettings(
604
+ {},
605
+ { vimMode: true, hideWindowTitle: false }, // System settings
606
+ {},
607
+ );
608
+ const onSelect = vi.fn();
609
+
610
+ const { lastFrame } = render(
611
+ <SettingsDialog settings={settings} onSelect={onSelect} />,
612
+ );
613
+
614
+ const output = lastFrame();
615
+ // Settings should show inherited values
616
+ expect(output).toContain('Settings');
617
+ });
618
+
619
+ it('should show override indicator for overridden settings', () => {
620
+ const settings = createMockSettings(
621
+ { vimMode: false }, // User overrides
622
+ { vimMode: true }, // System default
623
+ {},
624
+ );
625
+ const onSelect = vi.fn();
626
+
627
+ const { lastFrame } = render(
628
+ <SettingsDialog settings={settings} onSelect={onSelect} />,
629
+ );
630
+
631
+ const output = lastFrame();
632
+ // Should show settings with override indicators
633
+ expect(output).toContain('Settings');
634
+ });
635
+ });
636
+
637
+ describe('Keyboard Shortcuts Edge Cases', () => {
638
+ it('should handle rapid key presses gracefully', async () => {
639
+ const settings = createMockSettings();
640
+ const onSelect = vi.fn();
641
+
642
+ const { stdin, unmount } = render(
643
+ <SettingsDialog settings={settings} onSelect={onSelect} />,
644
+ );
645
+
646
+ // Rapid navigation
647
+ for (let i = 0; i < 5; i++) {
648
+ stdin.write('\u001B[B'); // Down arrow
649
+ stdin.write('\u001B[A'); // Up arrow
650
+ }
651
+ await wait(100);
652
+
653
+ // Should not crash
654
+ unmount();
655
+ });
656
+
657
+ it('should handle Ctrl+C to reset current setting to default', async () => {
658
+ const settings = createMockSettings({ vimMode: true }); // Start with vimMode enabled
659
+ const onSelect = vi.fn();
660
+
661
+ const { stdin, unmount } = render(
662
+ <SettingsDialog settings={settings} onSelect={onSelect} />,
663
+ );
664
+
665
+ // Press Ctrl+C to reset current setting to default
666
+ stdin.write('\u0003'); // Ctrl+C
667
+ await wait();
668
+
669
+ // Should reset the current setting to its default value
670
+ unmount();
671
+ });
672
+
673
+ it('should handle Ctrl+L to reset current setting to default', async () => {
674
+ const settings = createMockSettings({ vimMode: true }); // Start with vimMode enabled
675
+ const onSelect = vi.fn();
676
+
677
+ const { stdin, unmount } = render(
678
+ <SettingsDialog settings={settings} onSelect={onSelect} />,
679
+ );
680
+
681
+ // Press Ctrl+L to reset current setting to default
682
+ stdin.write('\u000C'); // Ctrl+L
683
+ await wait();
684
+
685
+ // Should reset the current setting to its default value
686
+ unmount();
687
+ });
688
+
689
+ it('should handle navigation when only one setting exists', async () => {
690
+ const settings = createMockSettings();
691
+ const onSelect = vi.fn();
692
+
693
+ const { stdin, unmount } = render(
694
+ <SettingsDialog settings={settings} onSelect={onSelect} />,
695
+ );
696
+
697
+ // Try to navigate when potentially at bounds
698
+ stdin.write('\u001B[B'); // Down
699
+ await wait();
700
+ stdin.write('\u001B[A'); // Up
701
+ await wait();
702
+
703
+ unmount();
704
+ });
705
+
706
+ it('should properly handle Tab navigation between sections', async () => {
707
+ const settings = createMockSettings();
708
+ const onSelect = vi.fn();
709
+
710
+ const { lastFrame, unmount } = render(
711
+ <SettingsDialog settings={settings} onSelect={onSelect} />,
712
+ );
713
+
714
+ // Wait for initial render
715
+ await waitFor(() => {
716
+ expect(lastFrame()).toContain('Hide Window Title');
717
+ });
718
+
719
+ // Verify initial state: settings section active, scope section inactive
720
+ expect(lastFrame()).toContain('● Hide Window Title'); // Settings section active
721
+ expect(lastFrame()).toContain(' Apply To'); // Scope section inactive
722
+
723
+ // This test validates the rendered UI structure for tab navigation
724
+ // Actual tab behavior testing is complex due to keypress handling
725
+
726
+ unmount();
727
+ });
728
+ });
729
+
730
+ describe('Error Recovery', () => {
731
+ it('should handle malformed settings gracefully', () => {
732
+ // Create settings with potentially problematic values
733
+ const settings = createMockSettings(
734
+ { vimMode: null as unknown as boolean }, // Invalid value
735
+ {},
736
+ {},
737
+ );
738
+ const onSelect = vi.fn();
739
+
740
+ const { lastFrame } = render(
741
+ <SettingsDialog settings={settings} onSelect={onSelect} />,
742
+ );
743
+
744
+ // Should still render without crashing
745
+ expect(lastFrame()).toContain('Settings');
746
+ });
747
+
748
+ it('should handle missing setting definitions gracefully', () => {
749
+ const settings = createMockSettings();
750
+ const onSelect = vi.fn();
751
+
752
+ // Should not crash even if some settings are missing definitions
753
+ const { lastFrame } = render(
754
+ <SettingsDialog settings={settings} onSelect={onSelect} />,
755
+ );
756
+
757
+ expect(lastFrame()).toContain('Settings');
758
+ });
759
+ });
760
+
761
+ describe('Complex User Interactions', () => {
762
+ it('should handle complete user workflow: navigate, toggle, change scope, exit', async () => {
763
+ const settings = createMockSettings();
764
+ const onSelect = vi.fn();
765
+
766
+ const { lastFrame, unmount } = render(
767
+ <SettingsDialog settings={settings} onSelect={onSelect} />,
768
+ );
769
+
770
+ // Wait for initial render
771
+ await waitFor(() => {
772
+ expect(lastFrame()).toContain('Hide Window Title');
773
+ });
774
+
775
+ // Verify the complete UI is rendered with all necessary sections
776
+ expect(lastFrame()).toContain('Settings'); // Title
777
+ expect(lastFrame()).toContain('● Hide Window Title'); // Active setting
778
+ expect(lastFrame()).toContain('Apply To'); // Scope section
779
+ expect(lastFrame()).toContain('1. User Settings'); // Scope options
780
+ expect(lastFrame()).toContain(
781
+ '(Use Enter to select, Tab to change focus)',
782
+ ); // Help text
783
+
784
+ // This test validates the complete UI structure is available for user workflow
785
+ // Individual interactions are tested in focused unit tests
786
+
787
+ unmount();
788
+ });
789
+
790
+ it('should allow changing multiple settings without losing pending changes', async () => {
791
+ const settings = createMockSettings();
792
+ const onSelect = vi.fn();
793
+
794
+ const { stdin, unmount } = render(
795
+ <SettingsDialog settings={settings} onSelect={onSelect} />,
796
+ );
797
+
798
+ // Toggle first setting (should require restart)
799
+ stdin.write('\u000D'); // Enter
800
+ await wait();
801
+
802
+ // Navigate to next setting and toggle it (should not require restart - e.g., vimMode)
803
+ stdin.write('\u001B[B'); // Down
804
+ await wait();
805
+ stdin.write('\u000D'); // Enter
806
+ await wait();
807
+
808
+ // Navigate to another setting and toggle it (should also require restart)
809
+ stdin.write('\u001B[B'); // Down
810
+ await wait();
811
+ stdin.write('\u000D'); // Enter
812
+ await wait();
813
+
814
+ // The test verifies that all changes are preserved and the dialog still works
815
+ // This tests the fix for the bug where changing one setting would reset all pending changes
816
+ unmount();
817
+ });
818
+
819
+ it('should maintain state consistency during complex interactions', async () => {
820
+ const settings = createMockSettings({ vimMode: true });
821
+ const onSelect = vi.fn();
822
+
823
+ const { stdin, unmount } = render(
824
+ <SettingsDialog settings={settings} onSelect={onSelect} />,
825
+ );
826
+
827
+ // Multiple scope changes
828
+ stdin.write('\t'); // Tab to scope
829
+ await wait();
830
+ stdin.write('2'); // Workspace
831
+ await wait();
832
+ stdin.write('\t'); // Tab to settings
833
+ await wait();
834
+ stdin.write('\t'); // Tab to scope
835
+ await wait();
836
+ stdin.write('1'); // User
837
+ await wait();
838
+
839
+ // Should maintain consistent state
840
+ unmount();
841
+ });
842
+
843
+ it('should handle restart workflow correctly', async () => {
844
+ const settings = createMockSettings();
845
+ const onRestartRequest = vi.fn();
846
+
847
+ const { stdin, unmount } = render(
848
+ <SettingsDialog
849
+ settings={settings}
850
+ onSelect={() => {}}
851
+ onRestartRequest={onRestartRequest}
852
+ />,
853
+ );
854
+
855
+ // This would test the restart workflow if we could trigger it
856
+ stdin.write('r'); // Try restart key
857
+ await wait();
858
+
859
+ // Without restart prompt showing, this should have no effect
860
+ expect(onRestartRequest).not.toHaveBeenCalled();
861
+
862
+ unmount();
863
+ });
864
+ });
865
+ });