nastechai-desktop 18.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 (546) hide show
  1. package/.prettierrc +11 -0
  2. package/DESIGN.md +167 -0
  3. package/README.md +141 -0
  4. package/assets/icon.icns +0 -0
  5. package/assets/icon.ico +0 -0
  6. package/assets/icon.png +0 -0
  7. package/components.json +21 -0
  8. package/electron/backend-env.cjs +112 -0
  9. package/electron/backend-env.test.cjs +111 -0
  10. package/electron/backend-probes.cjs +106 -0
  11. package/electron/backend-probes.test.cjs +82 -0
  12. package/electron/backend-ready.cjs +66 -0
  13. package/electron/bootstrap-platform.cjs +91 -0
  14. package/electron/bootstrap-platform.test.cjs +111 -0
  15. package/electron/bootstrap-runner.cjs +720 -0
  16. package/electron/bootstrap-runner.test.cjs +138 -0
  17. package/electron/connection-config.cjs +254 -0
  18. package/electron/connection-config.test.cjs +329 -0
  19. package/electron/dashboard-token.cjs +99 -0
  20. package/electron/dashboard-token.test.cjs +142 -0
  21. package/electron/desktop-uninstall.cjs +232 -0
  22. package/electron/desktop-uninstall.test.cjs +246 -0
  23. package/electron/entitlements.mac.inherit.plist +14 -0
  24. package/electron/entitlements.mac.plist +14 -0
  25. package/electron/fs-read-dir.cjs +109 -0
  26. package/electron/fs-read-dir.test.cjs +364 -0
  27. package/electron/gateway-ws-probe.cjs +188 -0
  28. package/electron/gateway-ws-probe.test.cjs +122 -0
  29. package/electron/git-root.cjs +54 -0
  30. package/electron/git-root.test.cjs +40 -0
  31. package/electron/git-worktrees.cjs +174 -0
  32. package/electron/hardening.cjs +184 -0
  33. package/electron/hardening.test.cjs +116 -0
  34. package/electron/main.cjs +5762 -0
  35. package/electron/oauth-net-request.cjs +20 -0
  36. package/electron/oauth-net-request.test.cjs +34 -0
  37. package/electron/preload.cjs +135 -0
  38. package/electron/session-windows.cjs +99 -0
  39. package/electron/session-windows.test.cjs +177 -0
  40. package/electron/update-remote.cjs +56 -0
  41. package/electron/update-remote.test.cjs +78 -0
  42. package/electron/vscode-marketplace.cjs +331 -0
  43. package/electron/vscode-marketplace.test.cjs +113 -0
  44. package/electron/windows-child-process.test.cjs +57 -0
  45. package/electron/windows-user-env.cjs +76 -0
  46. package/electron/windows-user-env.test.cjs +90 -0
  47. package/electron/workspace-cwd.cjs +38 -0
  48. package/electron/workspace-cwd.test.cjs +45 -0
  49. package/eslint.config.mjs +122 -0
  50. package/index.html +17 -0
  51. package/package.json +254 -0
  52. package/pr-assets/session-source-folders.png +0 -0
  53. package/preview-demo.html +65 -0
  54. package/public/apple-touch-icon.png +0 -0
  55. package/public/ds-assets/filler-bg0.jpg +0 -0
  56. package/public/nastech-frames/nastech-frame-0.png +0 -0
  57. package/public/nastech-frames/nastech-frame-1.png +0 -0
  58. package/public/nastech-frames/nastech-frame-2.png +0 -0
  59. package/public/nastech-frames/nastech-frame-3.png +0 -0
  60. package/public/nastech-frames/nastech-frame-4.png +0 -0
  61. package/public/nastech-frames/nastech-frame-5.png +0 -0
  62. package/public/nastech-frames/nastech-frame-6.png +0 -0
  63. package/public/nastech-frames/nastech-frame-7.png +0 -0
  64. package/public/nastech-girl.jpg +0 -0
  65. package/public/nastech-sprite.png +0 -0
  66. package/public/nastech.png +0 -0
  67. package/scripts/after-pack.cjs +41 -0
  68. package/scripts/assert-dist-built.cjs +70 -0
  69. package/scripts/assert-dist-built.test.cjs +84 -0
  70. package/scripts/assert-root-install.cjs +13 -0
  71. package/scripts/before-build.cjs +11 -0
  72. package/scripts/before-pack.cjs +78 -0
  73. package/scripts/before-pack.test.cjs +53 -0
  74. package/scripts/click-session.mjs +51 -0
  75. package/scripts/dev-no-hmr.mjs +22 -0
  76. package/scripts/diag-jump.mjs +115 -0
  77. package/scripts/diag-scroll-reset.mjs +229 -0
  78. package/scripts/eval.mjs +21 -0
  79. package/scripts/leak-typing.mjs +222 -0
  80. package/scripts/measure-jump.mjs +108 -0
  81. package/scripts/measure-latency.mjs +184 -0
  82. package/scripts/measure-real-stream.mjs +252 -0
  83. package/scripts/measure-submit.mjs +179 -0
  84. package/scripts/measure-synthetic-stream.mjs +322 -0
  85. package/scripts/notarize-artifact.cjs +77 -0
  86. package/scripts/notarize.cjs +100 -0
  87. package/scripts/patch-electron-builder-mac-binary.cjs +59 -0
  88. package/scripts/probe-renderer.mjs +38 -0
  89. package/scripts/probe-thread.mjs +40 -0
  90. package/scripts/profile-long-stream.mjs +191 -0
  91. package/scripts/profile-real-stream.mjs +137 -0
  92. package/scripts/profile-synth-stream.mjs +103 -0
  93. package/scripts/profile-typing-lag.md +381 -0
  94. package/scripts/profile-typing.mjs +260 -0
  95. package/scripts/reload-renderer.mjs +25 -0
  96. package/scripts/reload.mjs +36 -0
  97. package/scripts/set-exe-identity.cjs +94 -0
  98. package/scripts/stage-native-deps.cjs +159 -0
  99. package/scripts/test-desktop.mjs +425 -0
  100. package/scripts/write-build-stamp.cjs +126 -0
  101. package/src/app/agents/index.tsx +398 -0
  102. package/src/app/artifacts/index.test.ts +62 -0
  103. package/src/app/artifacts/index.tsx +906 -0
  104. package/src/app/chat/chat-drop-overlay.tsx +48 -0
  105. package/src/app/chat/chat-swap-overlay.tsx +47 -0
  106. package/src/app/chat/composer/attachments.tsx +114 -0
  107. package/src/app/chat/composer/completion-drawer.tsx +63 -0
  108. package/src/app/chat/composer/context-menu.tsx +172 -0
  109. package/src/app/chat/composer/controls.tsx +289 -0
  110. package/src/app/chat/composer/drop-affordance.ts +2 -0
  111. package/src/app/chat/composer/enter-submit-dom-race.test.tsx +218 -0
  112. package/src/app/chat/composer/focus.ts +134 -0
  113. package/src/app/chat/composer/help-hint.tsx +59 -0
  114. package/src/app/chat/composer/hooks/use-at-completions.ts +141 -0
  115. package/src/app/chat/composer/hooks/use-live-completion-adapter.ts +119 -0
  116. package/src/app/chat/composer/hooks/use-mic-recorder.ts +291 -0
  117. package/src/app/chat/composer/hooks/use-slash-completions.ts +114 -0
  118. package/src/app/chat/composer/hooks/use-voice-conversation.ts +390 -0
  119. package/src/app/chat/composer/hooks/use-voice-recorder.ts +116 -0
  120. package/src/app/chat/composer/ime-composition-dom-repro.test.tsx +108 -0
  121. package/src/app/chat/composer/index.tsx +1611 -0
  122. package/src/app/chat/composer/inline-refs.ts +138 -0
  123. package/src/app/chat/composer/model-pill.tsx +86 -0
  124. package/src/app/chat/composer/queue-panel.tsx +130 -0
  125. package/src/app/chat/composer/rich-editor.test.ts +18 -0
  126. package/src/app/chat/composer/rich-editor.ts +165 -0
  127. package/src/app/chat/composer/skin-slash-popover.tsx +61 -0
  128. package/src/app/chat/composer/slash-nav-dom-repro.test.tsx +186 -0
  129. package/src/app/chat/composer/status-stack/index.tsx +202 -0
  130. package/src/app/chat/composer/status-stack/status-row.tsx +155 -0
  131. package/src/app/chat/composer/text-utils.test.ts +77 -0
  132. package/src/app/chat/composer/text-utils.ts +107 -0
  133. package/src/app/chat/composer/trigger-popover.test.tsx +42 -0
  134. package/src/app/chat/composer/trigger-popover.tsx +116 -0
  135. package/src/app/chat/composer/types.ts +64 -0
  136. package/src/app/chat/composer/url-dialog.tsx +82 -0
  137. package/src/app/chat/composer/voice-activity.tsx +252 -0
  138. package/src/app/chat/hooks/use-composer-actions.test.ts +57 -0
  139. package/src/app/chat/hooks/use-composer-actions.ts +525 -0
  140. package/src/app/chat/hooks/use-file-drop-zone.ts +118 -0
  141. package/src/app/chat/index.tsx +390 -0
  142. package/src/app/chat/perf-probe.tsx +269 -0
  143. package/src/app/chat/right-rail/index.ts +1 -0
  144. package/src/app/chat/right-rail/preview-console-state.ts +82 -0
  145. package/src/app/chat/right-rail/preview-console.tsx +290 -0
  146. package/src/app/chat/right-rail/preview-file.tsx +559 -0
  147. package/src/app/chat/right-rail/preview-pane.test.tsx +43 -0
  148. package/src/app/chat/right-rail/preview-pane.tsx +657 -0
  149. package/src/app/chat/right-rail/preview.tsx +171 -0
  150. package/src/app/chat/scroll-to-bottom-button.test.tsx +67 -0
  151. package/src/app/chat/scroll-to-bottom-button.tsx +74 -0
  152. package/src/app/chat/sidebar/cron-jobs-section.tsx +325 -0
  153. package/src/app/chat/sidebar/index.tsx +1219 -0
  154. package/src/app/chat/sidebar/load-more-row.tsx +30 -0
  155. package/src/app/chat/sidebar/order.test.ts +21 -0
  156. package/src/app/chat/sidebar/order.ts +17 -0
  157. package/src/app/chat/sidebar/profile-switcher.tsx +516 -0
  158. package/src/app/chat/sidebar/session-actions-menu.tsx +264 -0
  159. package/src/app/chat/sidebar/session-row.tsx +257 -0
  160. package/src/app/chat/sidebar/virtual-session-list.tsx +154 -0
  161. package/src/app/chat/sidebar/workspace-groups.test.ts +149 -0
  162. package/src/app/chat/sidebar/workspace-groups.ts +326 -0
  163. package/src/app/chat/thread-loading.test.ts +34 -0
  164. package/src/app/chat/thread-loading.ts +26 -0
  165. package/src/app/command-center/index.tsx +654 -0
  166. package/src/app/command-palette/index.tsx +513 -0
  167. package/src/app/command-palette/marketplace-theme-page.tsx +157 -0
  168. package/src/app/cron/index.tsx +942 -0
  169. package/src/app/cron/job-state.ts +29 -0
  170. package/src/app/desktop-controller.tsx +938 -0
  171. package/src/app/floating-hud.ts +22 -0
  172. package/src/app/gateway/hooks/use-gateway-boot.test.tsx +265 -0
  173. package/src/app/gateway/hooks/use-gateway-boot.ts +387 -0
  174. package/src/app/gateway/hooks/use-gateway-request.ts +138 -0
  175. package/src/app/hooks/use-keybinds.ts +186 -0
  176. package/src/app/hooks/use-refresh-hotkey.ts +45 -0
  177. package/src/app/hooks/use-route-enum-param.ts +38 -0
  178. package/src/app/index.tsx +1 -0
  179. package/src/app/layout-constants.ts +13 -0
  180. package/src/app/messaging/index.tsx +648 -0
  181. package/src/app/messaging/platform-icon.tsx +93 -0
  182. package/src/app/model-picker-overlay.tsx +42 -0
  183. package/src/app/model-visibility-overlay.tsx +31 -0
  184. package/src/app/overlays/overlay-chrome.tsx +66 -0
  185. package/src/app/overlays/overlay-search-input.tsx +33 -0
  186. package/src/app/overlays/overlay-split-layout.tsx +130 -0
  187. package/src/app/overlays/overlay-view.tsx +91 -0
  188. package/src/app/page-search-shell.tsx +75 -0
  189. package/src/app/profiles/create-profile-dialog.tsx +154 -0
  190. package/src/app/profiles/delete-profile-dialog.tsx +65 -0
  191. package/src/app/profiles/index.tsx +671 -0
  192. package/src/app/profiles/rename-profile-dialog.tsx +125 -0
  193. package/src/app/right-sidebar/files/dnd-manager.ts +27 -0
  194. package/src/app/right-sidebar/files/ipc.test.ts +100 -0
  195. package/src/app/right-sidebar/files/ipc.ts +161 -0
  196. package/src/app/right-sidebar/files/remote-picker.tsx +177 -0
  197. package/src/app/right-sidebar/files/tree.tsx +224 -0
  198. package/src/app/right-sidebar/files/use-project-tree.test.ts +190 -0
  199. package/src/app/right-sidebar/files/use-project-tree.ts +268 -0
  200. package/src/app/right-sidebar/index.test.tsx +75 -0
  201. package/src/app/right-sidebar/index.tsx +395 -0
  202. package/src/app/right-sidebar/store.ts +15 -0
  203. package/src/app/right-sidebar/terminal/buffer.ts +65 -0
  204. package/src/app/right-sidebar/terminal/index.tsx +98 -0
  205. package/src/app/right-sidebar/terminal/persistent.tsx +122 -0
  206. package/src/app/right-sidebar/terminal/selection.ts +75 -0
  207. package/src/app/right-sidebar/terminal/use-terminal-session.ts +504 -0
  208. package/src/app/routes.ts +88 -0
  209. package/src/app/session/hooks/use-context-suggestions.ts +58 -0
  210. package/src/app/session/hooks/use-cwd-actions.ts +109 -0
  211. package/src/app/session/hooks/use-message-stream.ts +957 -0
  212. package/src/app/session/hooks/use-model-controls.test.tsx +198 -0
  213. package/src/app/session/hooks/use-model-controls.ts +106 -0
  214. package/src/app/session/hooks/use-nastech-config.ts +74 -0
  215. package/src/app/session/hooks/use-preview-routing.test.tsx +168 -0
  216. package/src/app/session/hooks/use-preview-routing.ts +223 -0
  217. package/src/app/session/hooks/use-prompt-actions.test.tsx +316 -0
  218. package/src/app/session/hooks/use-prompt-actions.ts +1030 -0
  219. package/src/app/session/hooks/use-route-resume.test.tsx +136 -0
  220. package/src/app/session/hooks/use-route-resume.ts +115 -0
  221. package/src/app/session/hooks/use-session-actions.test.tsx +119 -0
  222. package/src/app/session/hooks/use-session-actions.ts +885 -0
  223. package/src/app/session/hooks/use-session-state-cache.test.tsx +118 -0
  224. package/src/app/session/hooks/use-session-state-cache.ts +191 -0
  225. package/src/app/session-picker-overlay.tsx +32 -0
  226. package/src/app/session-switcher.tsx +107 -0
  227. package/src/app/settings/about-settings.tsx +173 -0
  228. package/src/app/settings/appearance-settings.tsx +162 -0
  229. package/src/app/settings/config-settings.tsx +384 -0
  230. package/src/app/settings/constants.ts +545 -0
  231. package/src/app/settings/credential-key-ui.tsx +373 -0
  232. package/src/app/settings/env-credentials.tsx +198 -0
  233. package/src/app/settings/env-var-actions-menu.tsx +136 -0
  234. package/src/app/settings/field-copy.ts +56 -0
  235. package/src/app/settings/gateway-settings.tsx +620 -0
  236. package/src/app/settings/helpers.test.ts +138 -0
  237. package/src/app/settings/helpers.ts +151 -0
  238. package/src/app/settings/index.tsx +237 -0
  239. package/src/app/settings/keys-settings.tsx +96 -0
  240. package/src/app/settings/mcp-settings.tsx +271 -0
  241. package/src/app/settings/model-settings.test.tsx +157 -0
  242. package/src/app/settings/model-settings.tsx +559 -0
  243. package/src/app/settings/notifications-settings.tsx +150 -0
  244. package/src/app/settings/primitives.tsx +115 -0
  245. package/src/app/settings/providers-settings.test.tsx +100 -0
  246. package/src/app/settings/providers-settings.tsx +258 -0
  247. package/src/app/settings/sessions-settings.tsx +276 -0
  248. package/src/app/settings/toolset-config-panel.test.tsx +289 -0
  249. package/src/app/settings/toolset-config-panel.tsx +449 -0
  250. package/src/app/settings/types.ts +42 -0
  251. package/src/app/settings/uninstall-section.tsx +185 -0
  252. package/src/app/settings/use-deep-link-highlight.ts +60 -0
  253. package/src/app/shell/app-shell.tsx +167 -0
  254. package/src/app/shell/gateway-menu-panel.tsx +150 -0
  255. package/src/app/shell/hooks/use-overlay-routing.ts +71 -0
  256. package/src/app/shell/hooks/use-status-snapshot.ts +57 -0
  257. package/src/app/shell/hooks/use-statusbar-items.tsx +403 -0
  258. package/src/app/shell/keybind-panel.tsx +220 -0
  259. package/src/app/shell/model-edit-submenu.test.tsx +84 -0
  260. package/src/app/shell/model-edit-submenu.tsx +245 -0
  261. package/src/app/shell/model-menu-panel.tsx +295 -0
  262. package/src/app/shell/sidebar-label.tsx +22 -0
  263. package/src/app/shell/statusbar-controls.tsx +185 -0
  264. package/src/app/shell/titlebar-controls.tsx +244 -0
  265. package/src/app/shell/titlebar.test.ts +26 -0
  266. package/src/app/shell/titlebar.ts +45 -0
  267. package/src/app/shell/use-group-registry.ts +39 -0
  268. package/src/app/skills/index.test.tsx +103 -0
  269. package/src/app/skills/index.tsx +371 -0
  270. package/src/app/types.ts +99 -0
  271. package/src/app/updates-overlay.tsx +369 -0
  272. package/src/components/Backdrop.tsx +114 -0
  273. package/src/components/assistant-ui/ansi-text.tsx +34 -0
  274. package/src/components/assistant-ui/clarify-tool.tsx +281 -0
  275. package/src/components/assistant-ui/directive-text.test.ts +39 -0
  276. package/src/components/assistant-ui/directive-text.tsx +389 -0
  277. package/src/components/assistant-ui/markdown-text.test.ts +204 -0
  278. package/src/components/assistant-ui/markdown-text.tsx +497 -0
  279. package/src/components/assistant-ui/message-render-boundary.test.tsx +80 -0
  280. package/src/components/assistant-ui/message-render-boundary.tsx +48 -0
  281. package/src/components/assistant-ui/streaming.test.tsx +739 -0
  282. package/src/components/assistant-ui/thread-list.tsx +307 -0
  283. package/src/components/assistant-ui/thread-virtualizer.tsx +512 -0
  284. package/src/components/assistant-ui/thread.tsx +1474 -0
  285. package/src/components/assistant-ui/todo-tool.tsx +109 -0
  286. package/src/components/assistant-ui/tool-approval-group.test.tsx +158 -0
  287. package/src/components/assistant-ui/tool-approval.test.tsx +81 -0
  288. package/src/components/assistant-ui/tool-approval.tsx +209 -0
  289. package/src/components/assistant-ui/tool-fallback-model.test.ts +66 -0
  290. package/src/components/assistant-ui/tool-fallback-model.ts +1368 -0
  291. package/src/components/assistant-ui/tool-fallback.tsx +466 -0
  292. package/src/components/assistant-ui/tooltip-icon-button.tsx +33 -0
  293. package/src/components/assistant-ui/user-message-edit.test.tsx +141 -0
  294. package/src/components/assistant-ui/user-message-text.tsx +150 -0
  295. package/src/components/boot-failure-overlay.tsx +246 -0
  296. package/src/components/boot-failure-reauth.test.ts +100 -0
  297. package/src/components/boot-failure-reauth.ts +81 -0
  298. package/src/components/brand-mark.tsx +19 -0
  299. package/src/components/chat/activity-timer-text.tsx +24 -0
  300. package/src/components/chat/activity-timer.test.tsx +43 -0
  301. package/src/components/chat/activity-timer.ts +64 -0
  302. package/src/components/chat/code-card.tsx +78 -0
  303. package/src/components/chat/compact-markdown.tsx +113 -0
  304. package/src/components/chat/composer-dock.ts +31 -0
  305. package/src/components/chat/diff-lines.tsx +54 -0
  306. package/src/components/chat/disclosure-row.tsx +63 -0
  307. package/src/components/chat/generated-image-context.tsx +19 -0
  308. package/src/components/chat/generated-image-result.tsx +174 -0
  309. package/src/components/chat/image-generation-placeholder.tsx +279 -0
  310. package/src/components/chat/intro-copy.jsonl +75 -0
  311. package/src/components/chat/intro.tsx +182 -0
  312. package/src/components/chat/preview-attachment.tsx +125 -0
  313. package/src/components/chat/shiki-highlighter.tsx +107 -0
  314. package/src/components/chat/status-row.tsx +70 -0
  315. package/src/components/chat/status-section.tsx +42 -0
  316. package/src/components/chat/terminal-output.tsx +50 -0
  317. package/src/components/chat/zoomable-image.tsx +177 -0
  318. package/src/components/desktop-install-overlay.tsx +595 -0
  319. package/src/components/desktop-onboarding-overlay.test.tsx +100 -0
  320. package/src/components/desktop-onboarding-overlay.tsx +1286 -0
  321. package/src/components/error-boundary.tsx +77 -0
  322. package/src/components/gateway-connecting-overlay.test.tsx +143 -0
  323. package/src/components/gateway-connecting-overlay.tsx +183 -0
  324. package/src/components/haptics-provider.tsx +19 -0
  325. package/src/components/language-switcher.test.tsx +53 -0
  326. package/src/components/language-switcher.tsx +175 -0
  327. package/src/components/model-picker.tsx +340 -0
  328. package/src/components/model-visibility-dialog.tsx +155 -0
  329. package/src/components/notifications.tsx +196 -0
  330. package/src/components/page-loader.tsx +34 -0
  331. package/src/components/pane-shell/context.ts +14 -0
  332. package/src/components/pane-shell/index.ts +4 -0
  333. package/src/components/pane-shell/pane-shell.test.tsx +333 -0
  334. package/src/components/pane-shell/pane-shell.tsx +330 -0
  335. package/src/components/prompt-overlays.tsx +234 -0
  336. package/src/components/session-picker.tsx +108 -0
  337. package/src/components/status-dot.tsx +26 -0
  338. package/src/components/ui/action-status.tsx +25 -0
  339. package/src/components/ui/alert.tsx +53 -0
  340. package/src/components/ui/badge.tsx +35 -0
  341. package/src/components/ui/braille-spinner.tsx +61 -0
  342. package/src/components/ui/button.tsx +81 -0
  343. package/src/components/ui/checkbox.tsx +27 -0
  344. package/src/components/ui/codicon.tsx +20 -0
  345. package/src/components/ui/command.tsx +111 -0
  346. package/src/components/ui/confirm-dialog.tsx +109 -0
  347. package/src/components/ui/context-menu.tsx +141 -0
  348. package/src/components/ui/control.ts +25 -0
  349. package/src/components/ui/copy-button.test.tsx +36 -0
  350. package/src/components/ui/copy-button.tsx +229 -0
  351. package/src/components/ui/dialog.tsx +152 -0
  352. package/src/components/ui/disclosure-caret.tsx +20 -0
  353. package/src/components/ui/dropdown-menu.tsx +291 -0
  354. package/src/components/ui/error-state.tsx +50 -0
  355. package/src/components/ui/fade-text.tsx +110 -0
  356. package/src/components/ui/glyph-spinner.tsx +63 -0
  357. package/src/components/ui/input.tsx +22 -0
  358. package/src/components/ui/kbd.tsx +37 -0
  359. package/src/components/ui/loader.tsx +558 -0
  360. package/src/components/ui/log-view.tsx +17 -0
  361. package/src/components/ui/pagination.tsx +114 -0
  362. package/src/components/ui/popover.tsx +44 -0
  363. package/src/components/ui/scroll-area.tsx +43 -0
  364. package/src/components/ui/search-field.tsx +80 -0
  365. package/src/components/ui/segmented-control.tsx +51 -0
  366. package/src/components/ui/select.tsx +92 -0
  367. package/src/components/ui/separator.tsx +26 -0
  368. package/src/components/ui/sheet.tsx +116 -0
  369. package/src/components/ui/sidebar.tsx +674 -0
  370. package/src/components/ui/skeleton.tsx +7 -0
  371. package/src/components/ui/switch.tsx +49 -0
  372. package/src/components/ui/tabs.tsx +36 -0
  373. package/src/components/ui/text-tab.tsx +43 -0
  374. package/src/components/ui/textarea.tsx +11 -0
  375. package/src/components/ui/tool-icon.tsx +65 -0
  376. package/src/components/ui/tooltip.tsx +69 -0
  377. package/src/fonts/JetBrainsMono-Bold.woff2 +0 -0
  378. package/src/fonts/JetBrainsMono-Italic.woff2 +0 -0
  379. package/src/fonts/JetBrainsMono-Regular.woff2 +0 -0
  380. package/src/global.d.ts +457 -0
  381. package/src/hooks/use-image-download.ts +85 -0
  382. package/src/hooks/use-media-query.ts +24 -0
  383. package/src/hooks/use-mobile.ts +3 -0
  384. package/src/hooks/use-resize-observer.ts +38 -0
  385. package/src/hooks/use-worktree-info.ts +68 -0
  386. package/src/i18n/catalog.ts +12 -0
  387. package/src/i18n/context.test.tsx +232 -0
  388. package/src/i18n/context.tsx +183 -0
  389. package/src/i18n/define-locale.ts +41 -0
  390. package/src/i18n/en.ts +1779 -0
  391. package/src/i18n/index.ts +20 -0
  392. package/src/i18n/ja.ts +1890 -0
  393. package/src/i18n/languages.test.ts +43 -0
  394. package/src/i18n/languages.ts +86 -0
  395. package/src/i18n/runtime.test.ts +75 -0
  396. package/src/i18n/runtime.ts +53 -0
  397. package/src/i18n/types.ts +1452 -0
  398. package/src/i18n/zh-hant.ts +1849 -0
  399. package/src/i18n/zh.ts +1923 -0
  400. package/src/lib/ansi.test.ts +123 -0
  401. package/src/lib/ansi.ts +175 -0
  402. package/src/lib/chat-messages.test.ts +708 -0
  403. package/src/lib/chat-messages.ts +885 -0
  404. package/src/lib/chat-runtime.test.ts +18 -0
  405. package/src/lib/chat-runtime.ts +335 -0
  406. package/src/lib/clipboard.ts +28 -0
  407. package/src/lib/commit-changelog.test.ts +114 -0
  408. package/src/lib/commit-changelog.ts +177 -0
  409. package/src/lib/completion-sound.ts +519 -0
  410. package/src/lib/desktop-fs.test.ts +116 -0
  411. package/src/lib/desktop-fs.ts +113 -0
  412. package/src/lib/desktop-slash-commands.test.ts +126 -0
  413. package/src/lib/desktop-slash-commands.ts +286 -0
  414. package/src/lib/embedded-images.test.ts +35 -0
  415. package/src/lib/embedded-images.ts +60 -0
  416. package/src/lib/external-link.test.tsx +168 -0
  417. package/src/lib/external-link.tsx +303 -0
  418. package/src/lib/gateway-events.test.ts +27 -0
  419. package/src/lib/gateway-events.ts +49 -0
  420. package/src/lib/gateway-ws-url.test.ts +78 -0
  421. package/src/lib/gateway-ws-url.ts +91 -0
  422. package/src/lib/generated-images.test.ts +97 -0
  423. package/src/lib/generated-images.ts +116 -0
  424. package/src/lib/haptics.ts +129 -0
  425. package/src/lib/icons.ts +203 -0
  426. package/src/lib/incremental-external-store-runtime.ts +188 -0
  427. package/src/lib/katex-memo.ts +260 -0
  428. package/src/lib/keybinds/actions.ts +125 -0
  429. package/src/lib/keybinds/combo.test.ts +86 -0
  430. package/src/lib/keybinds/combo.ts +169 -0
  431. package/src/lib/local-preview.ts +126 -0
  432. package/src/lib/markdown-code.test.ts +23 -0
  433. package/src/lib/markdown-code.ts +195 -0
  434. package/src/lib/markdown-preprocess.ts +386 -0
  435. package/src/lib/media.remote.test.ts +58 -0
  436. package/src/lib/media.ts +111 -0
  437. package/src/lib/model-status-label.test.ts +31 -0
  438. package/src/lib/model-status-label.ts +103 -0
  439. package/src/lib/mutable-ref.ts +6 -0
  440. package/src/lib/preview-targets.test.ts +27 -0
  441. package/src/lib/preview-targets.ts +63 -0
  442. package/src/lib/profile-color.ts +58 -0
  443. package/src/lib/provider-setup-errors.test.ts +26 -0
  444. package/src/lib/provider-setup-errors.ts +12 -0
  445. package/src/lib/query-client.ts +13 -0
  446. package/src/lib/remend-tail.test.ts +105 -0
  447. package/src/lib/remend-tail.ts +108 -0
  448. package/src/lib/runtime-readiness.test.ts +65 -0
  449. package/src/lib/runtime-readiness.ts +147 -0
  450. package/src/lib/session-export.ts +57 -0
  451. package/src/lib/session-search.test.ts +58 -0
  452. package/src/lib/session-search.ts +19 -0
  453. package/src/lib/session-source.ts +62 -0
  454. package/src/lib/speech-text.ts +35 -0
  455. package/src/lib/statusbar.ts +91 -0
  456. package/src/lib/storage.test.ts +25 -0
  457. package/src/lib/storage.ts +107 -0
  458. package/src/lib/todos.test.ts +35 -0
  459. package/src/lib/todos.ts +51 -0
  460. package/src/lib/tool-result-summary.test.ts +106 -0
  461. package/src/lib/tool-result-summary.ts +467 -0
  462. package/src/lib/update-copy.test.ts +38 -0
  463. package/src/lib/update-copy.ts +44 -0
  464. package/src/lib/use-enter-animation.ts +100 -0
  465. package/src/lib/utils.ts +6 -0
  466. package/src/lib/voice-playback.ts +128 -0
  467. package/src/lib/yolo-session.ts +26 -0
  468. package/src/main.tsx +43 -0
  469. package/src/nastech.test.ts +49 -0
  470. package/src/nastech.ts +718 -0
  471. package/src/store/activity.ts +100 -0
  472. package/src/store/boot.ts +91 -0
  473. package/src/store/clarify.test.ts +81 -0
  474. package/src/store/clarify.ts +69 -0
  475. package/src/store/command-palette.ts +20 -0
  476. package/src/store/compaction.test.ts +53 -0
  477. package/src/store/compaction.ts +38 -0
  478. package/src/store/completion-sound.ts +32 -0
  479. package/src/store/composer-input-history.test.ts +147 -0
  480. package/src/store/composer-input-history.ts +158 -0
  481. package/src/store/composer-queue.test.ts +148 -0
  482. package/src/store/composer-queue.ts +239 -0
  483. package/src/store/composer-status.test.ts +99 -0
  484. package/src/store/composer-status.ts +277 -0
  485. package/src/store/composer.test.ts +106 -0
  486. package/src/store/composer.ts +184 -0
  487. package/src/store/cron.ts +19 -0
  488. package/src/store/gateway.ts +290 -0
  489. package/src/store/haptics.ts +17 -0
  490. package/src/store/keybinds.ts +139 -0
  491. package/src/store/layout.ts +176 -0
  492. package/src/store/model-presets.test.ts +51 -0
  493. package/src/store/model-presets.ts +86 -0
  494. package/src/store/model-visibility.test.ts +37 -0
  495. package/src/store/model-visibility.ts +108 -0
  496. package/src/store/native-notifications.test.ts +192 -0
  497. package/src/store/native-notifications.ts +203 -0
  498. package/src/store/notifications.ts +165 -0
  499. package/src/store/onboarding.test.ts +372 -0
  500. package/src/store/onboarding.ts +866 -0
  501. package/src/store/panes.test.ts +146 -0
  502. package/src/store/panes.ts +145 -0
  503. package/src/store/preview.test.ts +135 -0
  504. package/src/store/preview.ts +466 -0
  505. package/src/store/profile.test.ts +89 -0
  506. package/src/store/profile.ts +365 -0
  507. package/src/store/prompts.test.ts +121 -0
  508. package/src/store/prompts.ts +115 -0
  509. package/src/store/session-switcher.test.ts +115 -0
  510. package/src/store/session-switcher.ts +128 -0
  511. package/src/store/session-sync.ts +25 -0
  512. package/src/store/session.test.ts +131 -0
  513. package/src/store/session.ts +255 -0
  514. package/src/store/subagents.test.ts +111 -0
  515. package/src/store/subagents.ts +260 -0
  516. package/src/store/thread-scroll.ts +46 -0
  517. package/src/store/todos.test.ts +47 -0
  518. package/src/store/todos.ts +64 -0
  519. package/src/store/tool-diffs.ts +23 -0
  520. package/src/store/tool-dismiss.ts +45 -0
  521. package/src/store/tool-view.ts +91 -0
  522. package/src/store/translucency.ts +38 -0
  523. package/src/store/updates.test.ts +77 -0
  524. package/src/store/updates.ts +315 -0
  525. package/src/store/voice-playback.ts +24 -0
  526. package/src/store/windows.test.ts +143 -0
  527. package/src/store/windows.ts +77 -0
  528. package/src/styles.css +1235 -0
  529. package/src/themes/color.ts +142 -0
  530. package/src/themes/context.tsx +339 -0
  531. package/src/themes/index.ts +3 -0
  532. package/src/themes/install.test.ts +119 -0
  533. package/src/themes/install.ts +95 -0
  534. package/src/themes/presets.test.ts +33 -0
  535. package/src/themes/presets.ts +293 -0
  536. package/src/themes/profile-theme.test.ts +41 -0
  537. package/src/themes/types.ts +66 -0
  538. package/src/themes/use-skin-command.ts +60 -0
  539. package/src/themes/user-themes.test.ts +63 -0
  540. package/src/themes/user-themes.ts +122 -0
  541. package/src/themes/vscode.test.ts +171 -0
  542. package/src/themes/vscode.ts +343 -0
  543. package/src/types/nastech.ts +646 -0
  544. package/src/vite-env.d.ts +1 -0
  545. package/tsconfig.json +25 -0
  546. package/vite.config.ts +56 -0
@@ -0,0 +1,75 @@
1
+ import { cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react'
2
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
3
+
4
+ import type { NasTechReadDirResult } from '@/global'
5
+ import { $connection, setCurrentCwd } from '@/store/session'
6
+
7
+ import { resetProjectTreeState } from './files/use-project-tree'
8
+
9
+ import { RightSidebarPane } from './index'
10
+
11
+ const readDir = vi.fn<(path: string) => Promise<NasTechReadDirResult>>()
12
+ const selectPaths = vi.fn()
13
+
14
+ function ok(entries: { name: string; path: string; isDirectory: boolean }[]): NasTechReadDirResult {
15
+ return { entries }
16
+ }
17
+
18
+ function installBridge() {
19
+ ;(
20
+ window as unknown as {
21
+ nastechDesktop: {
22
+ readDir: typeof readDir
23
+ selectPaths: typeof selectPaths
24
+ }
25
+ }
26
+ ).nastechDesktop = { readDir, selectPaths }
27
+ }
28
+
29
+ describe('RightSidebarPane', () => {
30
+ beforeEach(() => {
31
+ $connection.set(null)
32
+ resetProjectTreeState()
33
+ setCurrentCwd('/repo')
34
+ readDir.mockReset()
35
+ selectPaths.mockReset()
36
+ readDir.mockResolvedValue(ok([{ name: 'README.md', path: '/repo/README.md', isDirectory: false }]))
37
+ selectPaths.mockResolvedValue(['/repo-next'])
38
+ installBridge()
39
+ })
40
+
41
+ afterEach(() => {
42
+ cleanup()
43
+ $connection.set(null)
44
+ setCurrentCwd('')
45
+ resetProjectTreeState()
46
+ delete (window as unknown as { nastechDesktop?: unknown }).nastechDesktop
47
+ })
48
+
49
+ it('refreshes the current tree without opening the folder picker', async () => {
50
+ const onChangeCwd = vi.fn()
51
+
52
+ render(<RightSidebarPane onActivateFile={vi.fn()} onActivateFolder={vi.fn()} onChangeCwd={onChangeCwd} />)
53
+
54
+ await waitFor(() => expect(screen.getByRole('button', { name: 'Refresh tree' }).hasAttribute('disabled')).toBe(false))
55
+
56
+ readDir.mockClear()
57
+
58
+ fireEvent.click(screen.getByRole('button', { name: 'Refresh tree' }))
59
+
60
+ await waitFor(() => expect(readDir).toHaveBeenCalledWith('/repo'))
61
+ expect(selectPaths).not.toHaveBeenCalled()
62
+
63
+ fireEvent.click(screen.getByRole('button', { name: 'Open folder' }))
64
+
65
+ await waitFor(() =>
66
+ expect(selectPaths).toHaveBeenCalledWith({
67
+ defaultPath: '/repo',
68
+ directories: true,
69
+ multiple: false,
70
+ title: 'Change working directory'
71
+ })
72
+ )
73
+ await waitFor(() => expect(onChangeCwd).toHaveBeenCalledWith('/repo-next'))
74
+ })
75
+ })
@@ -0,0 +1,395 @@
1
+ import { useStore } from '@nanostores/react'
2
+ import type { ReactNode } from 'react'
3
+
4
+ import { ErrorBoundary } from '@/components/error-boundary'
5
+ import { Button } from '@/components/ui/button'
6
+ import { Codicon } from '@/components/ui/codicon'
7
+ import { useI18n } from '@/i18n'
8
+ import { Loader } from '@/components/ui/loader'
9
+ import { Tip } from '@/components/ui/tooltip'
10
+ import { normalizeOrLocalPreviewTarget } from '@/lib/local-preview'
11
+ import { cn } from '@/lib/utils'
12
+ import { $panesFlipped } from '@/store/layout'
13
+ import { notifyError } from '@/store/notifications'
14
+ import { setCurrentSessionPreviewTarget } from '@/store/preview'
15
+ import { $currentBranch, $currentCwd } from '@/store/session'
16
+
17
+ import { SidebarPanelLabel } from '../shell/sidebar-label'
18
+
19
+ import { ProjectTree } from './files/tree'
20
+ import { useProjectTree } from './files/use-project-tree'
21
+ import { $rightSidebarTab, $terminalTakeover, type RightSidebarTabId, setRightSidebarTab } from './store'
22
+ import { TerminalSlot } from './terminal/persistent'
23
+
24
+ interface RightSidebarPaneProps {
25
+ onActivateFile: (path: string) => void
26
+ onActivateFolder: (path: string) => void
27
+ onChangeCwd: (path: string) => Promise<void> | void
28
+ }
29
+
30
+ interface RightSidebarTab {
31
+ icon: string
32
+ id: RightSidebarTabId
33
+ labelKey: 'files' | 'terminal'
34
+ }
35
+
36
+ const RIGHT_SIDEBAR_TABS: readonly RightSidebarTab[] = [
37
+ { id: 'files', labelKey: 'files', icon: 'list-tree' },
38
+ { id: 'terminal', labelKey: 'terminal', icon: 'terminal' }
39
+ ]
40
+
41
+ export function RightSidebarPane({ onActivateFile, onActivateFolder, onChangeCwd }: RightSidebarPaneProps) {
42
+ const { t } = useI18n()
43
+ const r = t.rightSidebar
44
+ const activeTab = useStore($rightSidebarTab)
45
+ const terminalTakeover = useStore($terminalTakeover)
46
+ const panesFlipped = useStore($panesFlipped)
47
+ const currentBranch = useStore($currentBranch).trim()
48
+ const currentCwd = useStore($currentCwd).trim()
49
+ const hasCwd = currentCwd.length > 0
50
+
51
+ const cwdName = hasCwd
52
+ ? (currentCwd
53
+ .split(/[\\/]+/)
54
+ .filter(Boolean)
55
+ .pop() ?? currentCwd)
56
+ : r.noFolderSelected
57
+
58
+ const {
59
+ collapseAll,
60
+ collapseNonce,
61
+ data,
62
+ loadChildren,
63
+ openState,
64
+ refreshRoot,
65
+ rootError,
66
+ rootLoading,
67
+ setNodeOpen
68
+ } = useProjectTree(currentCwd)
69
+
70
+ const canCollapse = Object.values(openState).some(Boolean)
71
+ const effectiveTab: RightSidebarTabId = terminalTakeover ? 'files' : activeTab
72
+
73
+ const chooseFolder = async () => {
74
+ const selected = await window.NASTECHDesktop?.selectPaths({
75
+ defaultPath: hasCwd ? currentCwd : undefined,
76
+ directories: true,
77
+ multiple: false,
78
+ title: r.changeCwdTitle
79
+ })
80
+
81
+ if (selected?.[0]) {
82
+ await onChangeCwd(selected[0])
83
+ }
84
+ }
85
+
86
+ const previewFile = async (path: string) => {
87
+ try {
88
+ const preview = await normalizeOrLocalPreviewTarget(path, currentCwd || undefined)
89
+
90
+ if (!preview) {
91
+ throw new Error(r.couldNotPreview(path))
92
+ }
93
+
94
+ setCurrentSessionPreviewTarget(preview, 'file-browser', path)
95
+ } catch (error) {
96
+ notifyError(error, r.previewUnavailable)
97
+ }
98
+ }
99
+
100
+ const tabs = terminalTakeover ? RIGHT_SIDEBAR_TABS.filter(tab => tab.id !== 'terminal') : RIGHT_SIDEBAR_TABS
101
+
102
+ return (
103
+ <aside
104
+ aria-label={r.aria}
105
+ className={cn(
106
+ 'before:pointer-events-none relative flex h-full w-full min-w-0 flex-col overflow-hidden border-(--ui-stroke-secondary) bg-(--ui-sidebar-surface-background) pt-(--titlebar-height) text-(--ui-text-tertiary)',
107
+ panesFlipped
108
+ ? 'border-r shadow-[inset_-0.0625rem_0_0_color-mix(in_srgb,white_18%,transparent)]'
109
+ : 'border-l shadow-[inset_0.0625rem_0_0_color-mix(in_srgb,white_18%,transparent)]'
110
+ )}
111
+ >
112
+ <RightSidebarChrome activeTab={effectiveTab} branch={currentBranch} tabs={tabs} />
113
+
114
+ {effectiveTab === 'terminal' ? (
115
+ <TerminalSlot />
116
+ ) : (
117
+ <FilesystemTab
118
+ canCollapse={canCollapse}
119
+ collapseNonce={collapseNonce}
120
+ cwd={currentCwd}
121
+ cwdName={cwdName}
122
+ data={data}
123
+ error={rootError}
124
+ hasCwd={hasCwd}
125
+ loading={rootLoading}
126
+ onActivateFile={onActivateFile}
127
+ onActivateFolder={onActivateFolder}
128
+ onChangeFolder={chooseFolder}
129
+ onCollapseAll={collapseAll}
130
+ onLoadChildren={loadChildren}
131
+ onNodeOpenChange={setNodeOpen}
132
+ onPreviewFile={previewFile}
133
+ onRefresh={() => void refreshRoot()}
134
+ openState={openState}
135
+ />
136
+ )}
137
+ </aside>
138
+ )
139
+ }
140
+
141
+ function RightSidebarChrome({
142
+ activeTab,
143
+ branch,
144
+ tabs
145
+ }: {
146
+ activeTab: RightSidebarTabId
147
+ branch: string
148
+ tabs: readonly RightSidebarTab[]
149
+ }) {
150
+ const { t } = useI18n()
151
+ const r = t.rightSidebar
152
+
153
+ return (
154
+ <header className="shrink-0 bg-transparent text-[0.75rem]">
155
+ <div className="flex items-center gap-2 px-2.5 py-1">
156
+ <nav aria-label={r.panelsAria} className="flex min-w-0 items-center gap-1">
157
+ {tabs.map(tab => {
158
+ const label = r[tab.labelKey]
159
+
160
+ return (
161
+ <Tip key={tab.id} label={label}>
162
+ <Button
163
+ aria-label={label}
164
+ aria-pressed={tab.id === activeTab}
165
+ className={cn(
166
+ 'text-(--ui-text-tertiary) hover:bg-(--ui-control-hover-background) hover:text-foreground',
167
+ tab.id === activeTab && 'bg-(--ui-control-active-background) text-foreground'
168
+ )}
169
+ onClick={() => setRightSidebarTab(tab.id)}
170
+ size="icon-xs"
171
+ variant="ghost"
172
+ >
173
+ <Codicon name={tab.icon} size="0.875rem" />
174
+ </Button>
175
+ </Tip>
176
+ )
177
+ })}
178
+ </nav>
179
+
180
+ {branch && (
181
+ <span className="ml-auto flex min-w-0 items-center gap-1 text-[0.6875rem] text-(--ui-text-tertiary)">
182
+ <Codicon className="shrink-0" name="git-branch" size="0.75rem" />
183
+ <span className="truncate">{branch}</span>
184
+ </span>
185
+ )}
186
+ </div>
187
+ </header>
188
+ )
189
+ }
190
+
191
+ interface FilesystemTabProps extends FileTreeBodyProps {
192
+ canCollapse: boolean
193
+ cwdName: string
194
+ hasCwd: boolean
195
+ onChangeFolder: () => Promise<void> | void
196
+ onCollapseAll: () => void
197
+ onRefresh: () => void
198
+ }
199
+
200
+ // Sidebar-specific color/hover treatment only — size, radius, cursor and the
201
+ // base focus ring come from <Button size="icon-xs">. This constant exists
202
+ // purely to share the sidebar palette + the hover-reveal behavior below.
203
+ const HEADER_ACTION_CLASS =
204
+ 'text-sidebar-foreground/70 hover:bg-sidebar-accent! hover:text-sidebar-accent-foreground! focus-visible:ring-sidebar-ring'
205
+
206
+ const HEADER_ACTION_REVEAL_CLASS = `${HEADER_ACTION_CLASS} pointer-events-none opacity-0 transition-opacity focus-visible:opacity-100 group-focus-within/project-header:pointer-events-auto group-focus-within/project-header:opacity-100 group-hover/project-header:pointer-events-auto group-hover/project-header:opacity-100`
207
+
208
+ function FilesystemTab({
209
+ canCollapse,
210
+ collapseNonce,
211
+ cwd,
212
+ cwdName,
213
+ data,
214
+ error,
215
+ hasCwd,
216
+ loading,
217
+ onActivateFile,
218
+ onActivateFolder,
219
+ onChangeFolder,
220
+ onCollapseAll,
221
+ onLoadChildren,
222
+ onNodeOpenChange,
223
+ onPreviewFile,
224
+ onRefresh,
225
+ openState
226
+ }: FilesystemTabProps) {
227
+ const { t } = useI18n()
228
+ const r = t.rightSidebar
229
+
230
+ return (
231
+ <div className="group/project-header flex min-h-0 flex-1 flex-col">
232
+ <RightSidebarSectionHeader>
233
+ <Tip label={hasCwd ? r.folderTip(cwd) : r.openFolder}>
234
+ <button
235
+ className="flex min-w-0 flex-1 items-center rounded-md text-left hover:text-(--ui-text-secondary)"
236
+ onClick={() => void onChangeFolder()}
237
+ type="button"
238
+ >
239
+ <SidebarPanelLabel>{cwdName}</SidebarPanelLabel>
240
+ </button>
241
+ </Tip>
242
+ <Button
243
+ aria-label={r.refreshTree}
244
+ className={HEADER_ACTION_CLASS}
245
+ disabled={!hasCwd || loading}
246
+ onClick={onRefresh}
247
+ size="icon-xs"
248
+ variant="ghost"
249
+ >
250
+ <Codicon name="refresh" size="0.8125rem" spinning={loading} />
251
+ </Button>
252
+ <Button
253
+ aria-label={r.openFolder}
254
+ className={HEADER_ACTION_CLASS}
255
+ onClick={() => void onChangeFolder()}
256
+ size="icon-xs"
257
+ variant="ghost"
258
+ >
259
+ <Codicon name="folder-opened" size="0.8125rem" />
260
+ </Button>
261
+ <Button
262
+ aria-label={r.collapseAll}
263
+ className={HEADER_ACTION_REVEAL_CLASS}
264
+ disabled={!hasCwd || !canCollapse}
265
+ onClick={onCollapseAll}
266
+ size="icon-xs"
267
+ variant="ghost"
268
+ >
269
+ <Codicon name="collapse-all" size="0.8125rem" />
270
+ </Button>
271
+ </RightSidebarSectionHeader>
272
+ <FileTreeBody
273
+ collapseNonce={collapseNonce}
274
+ cwd={cwd}
275
+ data={data}
276
+ error={error}
277
+ loading={loading}
278
+ onActivateFile={onActivateFile}
279
+ onActivateFolder={onActivateFolder}
280
+ onLoadChildren={onLoadChildren}
281
+ onNodeOpenChange={onNodeOpenChange}
282
+ onPreviewFile={onPreviewFile}
283
+ openState={openState}
284
+ />
285
+ </div>
286
+ )
287
+ }
288
+
289
+ export function RightSidebarSectionHeader({ children }: { children: ReactNode }) {
290
+ return <div className="flex h-7 shrink-0 items-center px-2.5">{children}</div>
291
+ }
292
+
293
+ interface FileTreeBodyProps {
294
+ collapseNonce: number
295
+ cwd: string
296
+ data: ReturnType<typeof useProjectTree>['data']
297
+ error: string | null
298
+ loading: boolean
299
+ onActivateFile: (path: string) => void
300
+ onActivateFolder: (path: string) => void
301
+ onLoadChildren: (id: string) => void | Promise<void>
302
+ onNodeOpenChange: (id: string, open: boolean) => void
303
+ onPreviewFile?: (path: string) => void
304
+ openState: ReturnType<typeof useProjectTree>['openState']
305
+ }
306
+
307
+ function FileTreeBody({
308
+ collapseNonce,
309
+ cwd,
310
+ data,
311
+ error,
312
+ loading,
313
+ onActivateFile,
314
+ onActivateFolder,
315
+ onLoadChildren,
316
+ onNodeOpenChange,
317
+ onPreviewFile,
318
+ openState
319
+ }: FileTreeBodyProps) {
320
+ const { t } = useI18n()
321
+ const r = t.rightSidebar
322
+
323
+ if (!cwd) {
324
+ return <EmptyState body={r.noProjectBody} title={r.noProjectTitle} />
325
+ }
326
+
327
+ if (error) {
328
+ return <EmptyState body={r.unreadableBody(error)} title={r.unreadableTitle} />
329
+ }
330
+
331
+ if (loading && data.length === 0) {
332
+ return <FileTreeLoadingState />
333
+ }
334
+
335
+ if (data.length === 0) {
336
+ return <EmptyState body={r.emptyBody} title={r.emptyTitle} />
337
+ }
338
+
339
+ return (
340
+ <ErrorBoundary
341
+ fallback={({ reset }) => (
342
+ <div className="flex min-h-0 flex-1 flex-col items-center justify-center gap-2 px-4 text-center">
343
+ <EmptyState body={r.treeErrorBody} title={r.treeErrorTitle} />
344
+ <button
345
+ className="text-[0.68rem] font-medium text-muted-foreground transition hover:text-foreground"
346
+ onClick={reset}
347
+ type="button"
348
+ >
349
+ {r.tryAgain}
350
+ </button>
351
+ </div>
352
+ )}
353
+ key={cwd}
354
+ label="file-tree"
355
+ >
356
+ <ProjectTree
357
+ collapseNonce={collapseNonce}
358
+ cwd={cwd}
359
+ data={data}
360
+ onActivateFile={onActivateFile}
361
+ onActivateFolder={onActivateFolder}
362
+ onLoadChildren={onLoadChildren}
363
+ onNodeOpenChange={onNodeOpenChange}
364
+ onPreviewFile={onPreviewFile}
365
+ openState={openState}
366
+ />
367
+ </ErrorBoundary>
368
+ )
369
+ }
370
+
371
+ function FileTreeLoadingState() {
372
+ const { t } = useI18n()
373
+
374
+ return (
375
+ <div aria-label={t.rightSidebar.loadingTree} className="grid min-h-0 flex-1 place-items-center px-3" role="status">
376
+ <Loader
377
+ aria-hidden="true"
378
+ className="size-8 text-(--ui-text-tertiary)"
379
+ pathSteps={180}
380
+ role="presentation"
381
+ strokeScale={0.68}
382
+ type="spiral-search"
383
+ />
384
+ </div>
385
+ )
386
+ }
387
+
388
+ function EmptyState({ body, title }: { body: string; title: string }) {
389
+ return (
390
+ <div className="flex min-h-0 flex-1 flex-col items-center justify-center gap-1 px-4 text-center">
391
+ <div className="text-[0.7rem] font-semibold uppercase tracking-[0.07em] text-muted-foreground/75">{title}</div>
392
+ <div className="text-[0.68rem] leading-relaxed text-muted-foreground/65">{body}</div>
393
+ </div>
394
+ )
395
+ }
@@ -0,0 +1,15 @@
1
+ import { atom } from 'nanostores'
2
+
3
+ import { persistBoolean, storedBoolean } from '@/lib/storage'
4
+
5
+ export type RightSidebarTabId = 'files' | 'git' | 'terminal' | 'web'
6
+
7
+ const TAKEOVER_KEY = 'NASTECH.desktop.terminalTakeover'
8
+
9
+ export const $rightSidebarTab = atom<RightSidebarTabId>('files')
10
+ export const $terminalTakeover = atom(storedBoolean(TAKEOVER_KEY, false))
11
+
12
+ $terminalTakeover.subscribe(active => persistBoolean(TAKEOVER_KEY, active))
13
+
14
+ export const setRightSidebarTab = (tab: RightSidebarTabId) => $rightSidebarTab.set(tab)
15
+ export const setTerminalTakeover = (active: boolean) => $terminalTakeover.set(active)
@@ -0,0 +1,65 @@
1
+ import type { Terminal } from '@xterm/xterm'
2
+
3
+ // Serialized view of the in-app terminal, handed to the agent's `read_terminal`
4
+ // tool. Line indices are absolute into xterm's buffer (0 = oldest scrollback
5
+ // line), so the agent can page with start_line/count against `total_lines`.
6
+ export interface TerminalReadResult {
7
+ total_lines: number
8
+ start: number
9
+ end: number
10
+ viewport_rows: number
11
+ cursor_row: number
12
+ text: string
13
+ }
14
+
15
+ export interface TerminalReadOptions {
16
+ start?: number
17
+ count?: number
18
+ }
19
+
20
+ type Reader = (opts: TerminalReadOptions) => TerminalReadResult
21
+
22
+ // The persistent terminal is a singleton (one xterm mounted forever), so a
23
+ // module-level slot is enough — set while the session is live, cleared on
24
+ // dispose. The gateway `terminal.read.request` handler reads through this.
25
+ let activeReader: Reader | null = null
26
+
27
+ export function setActiveTerminalReader(reader: Reader | null): void {
28
+ activeReader = reader
29
+ }
30
+
31
+ export function readActiveTerminal(opts: TerminalReadOptions = {}): TerminalReadResult | null {
32
+ return activeReader ? activeReader(opts) : null
33
+ }
34
+
35
+ export function makeTerminalReader(term: Terminal): Reader {
36
+ return ({ start, count }) => {
37
+ const buf = term.buffer.active
38
+ const total = buf.length
39
+ const rows = term.rows
40
+ // Default window = the visible screen; baseY is the viewport's top row.
41
+ const from = Math.max(0, Math.min(start ?? buf.baseY, total))
42
+ const to = Math.max(from, Math.min(from + Math.max(1, count ?? rows), total))
43
+
44
+ const lines: string[] = []
45
+
46
+ // translateToString(true) right-trims and resolves wide chars, dropping SGR
47
+ // colors — exactly what the agent wants.
48
+ for (let i = from; i < to; i += 1) {
49
+ lines.push(buf.getLine(i)?.translateToString(true) ?? '')
50
+ }
51
+
52
+ while (lines.length && !lines[lines.length - 1].trim()) {
53
+ lines.pop()
54
+ }
55
+
56
+ return {
57
+ total_lines: total,
58
+ start: from,
59
+ end: to,
60
+ viewport_rows: rows,
61
+ cursor_row: buf.baseY + buf.cursorY,
62
+ text: lines.join('\n')
63
+ }
64
+ }
65
+ }
@@ -0,0 +1,98 @@
1
+ import '@xterm/xterm/css/xterm.css'
2
+
3
+ import { useStore } from '@nanostores/react'
4
+
5
+ import { Button } from '@/components/ui/button'
6
+ import { Codicon } from '@/components/ui/codicon'
7
+ import { Loader } from '@/components/ui/loader'
8
+ import { Tip } from '@/components/ui/tooltip'
9
+ import { useI18n } from '@/i18n'
10
+
11
+ import { SidebarPanelLabel } from '../../shell/sidebar-label'
12
+ import { $terminalTakeover, setRightSidebarTab, setTerminalTakeover } from '../store'
13
+
14
+ import { addSelectionShortcutLabel } from './selection'
15
+ import { useTerminalSession } from './use-terminal-session'
16
+
17
+ interface TerminalTabProps {
18
+ cwd: string
19
+ onAddSelectionToChat: (text: string, label?: string) => void
20
+ }
21
+
22
+ export function TerminalTab({ cwd, onAddSelectionToChat }: TerminalTabProps) {
23
+ const { t } = useI18n()
24
+ const { addSelectionToChat, hostRef, selection, selectionStyle, shellName, status } = useTerminalSession({
25
+ cwd,
26
+ onAddSelectionToChat
27
+ })
28
+
29
+ const takeover = useStore($terminalTakeover)
30
+ const label = takeover ? t.rightSidebar.terminalSplit : t.rightSidebar.terminalFocus
31
+
32
+ const toggleTakeover = () => {
33
+ // Pre-select the Terminal tab so the slot is ready to host us on return.
34
+ if (takeover) {
35
+ setRightSidebarTab('terminal')
36
+ }
37
+
38
+ setTerminalTakeover(!takeover)
39
+ }
40
+
41
+ return (
42
+ <div className="relative flex min-h-0 min-w-0 flex-1 flex-col">
43
+ <div className="flex h-8 shrink-0 items-center gap-2 px-2.5">
44
+ <SidebarPanelLabel className="text-white!">{shellName}</SidebarPanelLabel>
45
+ <Tip label={label}>
46
+ <Button
47
+ aria-label={label}
48
+ className="ml-auto size-6 rounded-md text-white!"
49
+ onClick={toggleTakeover}
50
+ size="icon"
51
+ type="button"
52
+ variant="ghost"
53
+ >
54
+ <Codicon name={takeover ? 'screen-normal' : 'screen-full'} size="0.875rem" />
55
+ </Button>
56
+ </Tip>
57
+ </div>
58
+ <div className="relative min-h-0 flex-1 bg-[#002b36] p-2">
59
+ {status === 'starting' && (
60
+ <div className="pointer-events-none absolute inset-0 z-10 grid place-items-center">
61
+ <Loader
62
+ className="size-8 text-(--ui-text-tertiary)"
63
+ pathSteps={180}
64
+ strokeScale={0.68}
65
+ type="spiral-search"
66
+ />
67
+ </div>
68
+ )}
69
+ {selection.trim() && (
70
+ <div className="absolute z-50 flex items-center gap-1" style={selectionStyle ?? { right: 12, top: 8 }}>
71
+ <Button
72
+ className="h-6 rounded-md px-2 text-[0.68rem] shadow-md backdrop-blur-md"
73
+ onClick={event => event.preventDefault()}
74
+ onMouseDown={event => {
75
+ event.preventDefault()
76
+ event.stopPropagation()
77
+ addSelectionToChat()
78
+ }}
79
+ type="button"
80
+ variant="secondary"
81
+ >
82
+ {t.rightSidebar.addToChat}
83
+ <span className="ml-1 text-[0.6rem] text-(--ui-text-tertiary)">{addSelectionShortcutLabel()}</span>
84
+ </Button>
85
+ </div>
86
+ )}
87
+ {/* Outer div paints the dark inset; inner div is the xterm host so the
88
+ canvas sizes to the *content* area and p-2 shows as terminal padding.
89
+ Forcing screen/viewport bg avoids xterm's default black peeking
90
+ through the unused pixels below the last full row. */}
91
+ <div
92
+ className="h-full min-h-0 overflow-hidden text-(--ui-text-secondary) [&_.xterm]:h-full [&_.xterm-screen]:bg-[#002b36]! [&_.xterm-viewport]:bg-[#002b36]!"
93
+ ref={hostRef}
94
+ />
95
+ </div>
96
+ </div>
97
+ )
98
+ }