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,60 @@
1
+ import { useEffect } from 'react'
2
+ import { useSearchParams } from 'react-router-dom'
3
+
4
+ interface DeepLinkHighlightOptions {
5
+ param: string
6
+ ready: (target: string) => boolean
7
+ elementId: (target: string) => string
8
+ onResolve?: (target: string) => void
9
+ block?: ScrollLogicalPosition
10
+ }
11
+
12
+ // Deep-link from the command palette (?<param>=<id>): once the target row is
13
+ // renderable, scroll it into view and flash it, then drop the param so it
14
+ // doesn't re-fire. Returns the pending target (null once consumed) so callers
15
+ // can force the row open before it mounts.
16
+ export function useDeepLinkHighlight({
17
+ param,
18
+ ready,
19
+ elementId,
20
+ onResolve,
21
+ block = 'center'
22
+ }: DeepLinkHighlightOptions): null | string {
23
+ const [searchParams, setSearchParams] = useSearchParams()
24
+ const target = searchParams.get(param)
25
+
26
+ useEffect(() => {
27
+ if (!target || !ready(target)) {
28
+ return
29
+ }
30
+
31
+ onResolve?.(target)
32
+
33
+ // Defer a frame so async state (expansion, selection) mounts the row first.
34
+ const scrollTimeout = window.setTimeout(() => {
35
+ const element = document.getElementById(elementId(target))
36
+
37
+ if (!element) {
38
+ return
39
+ }
40
+
41
+ element.scrollIntoView({ behavior: 'smooth', block })
42
+ element.classList.add('setting-field-highlight')
43
+ window.setTimeout(() => element.classList.remove('setting-field-highlight'), 1600)
44
+ }, 80)
45
+
46
+ setSearchParams(
47
+ previous => {
48
+ const next = new URLSearchParams(previous)
49
+ next.delete(param)
50
+
51
+ return next
52
+ },
53
+ { replace: true }
54
+ )
55
+
56
+ return () => window.clearTimeout(scrollTimeout)
57
+ }, [block, elementId, onResolve, param, ready, setSearchParams, target])
58
+
59
+ return target
60
+ }
@@ -0,0 +1,167 @@
1
+ import { useStore } from '@nanostores/react'
2
+ import type { CSSProperties, ReactNode } from 'react'
3
+ import { useSyncExternalStore } from 'react'
4
+
5
+ import { NotificationStack } from '@/components/notifications'
6
+ import { PaneShell } from '@/components/pane-shell'
7
+ import { SidebarProvider } from '@/components/ui/sidebar'
8
+ import {
9
+ $fileBrowserOpen,
10
+ $panesFlipped,
11
+ $sidebarOpen,
12
+ FILE_BROWSER_DEFAULT_WIDTH,
13
+ FILE_BROWSER_PANE_ID,
14
+ setSidebarOpen
15
+ } from '@/store/layout'
16
+ import { $paneWidthOverride } from '@/store/panes'
17
+ import { $connection } from '@/store/session'
18
+
19
+ import { KeybindPanel } from './keybind-panel'
20
+ import { StatusbarControls, type StatusbarItem } from './statusbar-controls'
21
+ import { TITLEBAR_HEIGHT, titlebarControlsPosition } from './titlebar'
22
+ import { TitlebarControls, type TitlebarTool } from './titlebar-controls'
23
+
24
+ interface AppShellProps {
25
+ children: ReactNode
26
+ leftStatusbarItems?: readonly StatusbarItem[]
27
+ leftTitlebarTools?: readonly TitlebarTool[]
28
+ onOpenSettings: () => void
29
+ overlays?: ReactNode
30
+ statusbarItems?: readonly StatusbarItem[]
31
+ titlebarTools?: readonly TitlebarTool[]
32
+ }
33
+
34
+ // Renderer-side fallback so layout snaps even when the main-process fullscreen event
35
+ // hasn't landed yet (e.g. dev reloads, before the IPC bridge is wired).
36
+ function subscribeWindowSize(cb: () => void) {
37
+ window.addEventListener('resize', cb)
38
+ window.addEventListener('fullscreenchange', cb)
39
+
40
+ return () => {
41
+ window.removeEventListener('resize', cb)
42
+ window.removeEventListener('fullscreenchange', cb)
43
+ }
44
+ }
45
+
46
+ const viewportIsFullscreen = () =>
47
+ window.innerWidth >= window.screen.width && window.innerHeight >= window.screen.height
48
+
49
+ export function AppShell({
50
+ children,
51
+ leftStatusbarItems,
52
+ leftTitlebarTools,
53
+ onOpenSettings,
54
+ overlays,
55
+ statusbarItems,
56
+ titlebarTools
57
+ }: AppShellProps) {
58
+ const sidebarOpen = useStore($sidebarOpen)
59
+ const fileBrowserOpen = useStore($fileBrowserOpen)
60
+ const panesFlipped = useStore($panesFlipped)
61
+ const fileBrowserWidthOverride = useStore($paneWidthOverride(FILE_BROWSER_PANE_ID))
62
+ const connection = useStore($connection)
63
+ const viewportFullscreen = useSyncExternalStore(subscribeWindowSize, viewportIsFullscreen, () => false)
64
+ const isFullscreen = Boolean(connection?.isFullscreen) || viewportFullscreen
65
+ const titlebarControls = titlebarControlsPosition(connection?.windowButtonPosition, isFullscreen)
66
+ // Width Windows/Linux reserve for the OS-painted min/max/close overlay (zero
67
+ // on macOS, where window controls sit on the left and are reported via
68
+ // windowButtonPosition instead). The right tool cluster has to clear them.
69
+ const nativeOverlayWidth = connection?.nativeOverlayWidth ?? 0
70
+ const titlebarToolsRight = nativeOverlayWidth > 0 ? `${nativeOverlayWidth}px` : '0.75rem'
71
+
72
+ // The inset clears the top-left titlebar buttons when nothing covers the
73
+ // window's left edge. Default layout: the sessions sidebar sits there.
74
+ // Flipped layout: the file browser does instead.
75
+ const leftEdgePaneOpen = panesFlipped ? fileBrowserOpen : sidebarOpen
76
+
77
+ const titlebarContentInset = leftEdgePaneOpen
78
+ ? 0
79
+ : titlebarControls.left + TITLEBAR_HEIGHT + Math.round(TITLEBAR_HEIGHT / 2)
80
+
81
+ // The static system cluster (haptics, profiles, settings, right-sidebar) is
82
+ // hardcoded in TitlebarControls. Pane-supplied tools (preview's group) render
83
+ // in a separate cluster anchored further left.
84
+ //
85
+ // Width math has to include the `gap-x-1` (0.25rem) between buttons:
86
+ // N buttons + (N - 1) inner gaps, plus one extra 0.25rem of breathing room
87
+ // between the pane-tool cluster and the system cluster so they don't sit
88
+ // flush against each other. Modeled as N gaps (N - 1 inner + 1 trailing)
89
+ // to keep the formula generic for any pane-tool count.
90
+ const SYSTEM_TOOL_COUNT = 4
91
+ const paneToolCount = titlebarTools?.filter(tool => !tool.hidden).length ?? 0
92
+ const systemToolsWidth = `calc(${SYSTEM_TOOL_COUNT} * (var(--titlebar-control-size) + 0.25rem))`
93
+
94
+ const fileBrowserWidth =
95
+ fileBrowserWidthOverride !== undefined ? `${fileBrowserWidthOverride}px` : FILE_BROWSER_DEFAULT_WIDTH
96
+
97
+ // Where the pane-tool cluster's right edge sits, measured from the inner
98
+ // titlebar padding (--titlebar-tools-right). Two anchors:
99
+ // - file-browser closed → flush against static cluster's left edge
100
+ // - file-browser open → flush against the file-browser pane's left edge
101
+ // (= preview pane's right edge)
102
+ const previewToolbarGap = fileBrowserOpen ? fileBrowserWidth : systemToolsWidth
103
+
104
+ // Used by the drag region to know where the rightmost interactive element
105
+ // ends. When pane tools are present, that's `gap + paneCount * controlSize
106
+ // + paneCount * 0.25rem` (the leftmost button is at `tools-right + gap +
107
+ // paneCount * (size + gap-x-1)`). Otherwise the static cluster's footprint
108
+ // is enough.
109
+ const titlebarToolsWidth =
110
+ paneToolCount > 0
111
+ ? `calc(${previewToolbarGap} + ${paneToolCount} * (var(--titlebar-control-size) + 0.25rem))`
112
+ : systemToolsWidth
113
+
114
+ return (
115
+ <SidebarProvider
116
+ className="h-screen min-h-0 flex-col bg-background"
117
+ onOpenChange={setSidebarOpen}
118
+ open={sidebarOpen}
119
+ style={
120
+ {
121
+ // Alias for shadcn <Sidebar> descendants. Resolves to the chat-sidebar
122
+ // pane track via PaneShell's emitted --pane-chat-sidebar-width.
123
+ '--sidebar-width': 'var(--pane-chat-sidebar-width)',
124
+ '--titlebar-height': `${TITLEBAR_HEIGHT}px`,
125
+ '--titlebar-content-inset': `${titlebarContentInset}px`,
126
+ '--titlebar-controls-left': `${titlebarControls.left}px`,
127
+ '--titlebar-controls-top': `${titlebarControls.top}px`,
128
+ '--titlebar-tools-right': titlebarToolsRight,
129
+ '--titlebar-tools-width': titlebarToolsWidth,
130
+ // Anchor for the pane-tool cluster's right edge in TitlebarControls.
131
+ // Sourced from the layout store rather than the PaneShell-emitted
132
+ // --pane-*-width vars because the titlebar is a sibling of PaneShell
133
+ // and CSS variables resolve at the consumer's scope.
134
+ '--shell-preview-toolbar-gap': previewToolbarGap
135
+ } as CSSProperties
136
+ }
137
+ >
138
+ <TitlebarControls leftTools={leftTitlebarTools} onOpenSettings={onOpenSettings} tools={titlebarTools} />
139
+
140
+ <main className="relative z-3 flex min-h-0 w-full flex-1 flex-col overflow-hidden transition-none">
141
+ <PaneShell className="min-h-0 flex-1">
142
+ <div
143
+ aria-hidden="true"
144
+ className="pointer-events-none absolute left-0 top-0 z-1 h-(--titlebar-height) w-(--titlebar-controls-left) [-webkit-app-region:drag]"
145
+ />
146
+ <div
147
+ aria-hidden="true"
148
+ className="pointer-events-none absolute top-0 z-1 h-(--titlebar-height) left-[calc(var(--titlebar-controls-left)+(var(--titlebar-control-size)*2)+0.75rem)] right-[calc(var(--titlebar-tools-right)+var(--titlebar-tools-width)+0.75rem)] [-webkit-app-region:drag]"
149
+ />
150
+
151
+ {children}
152
+ </PaneShell>
153
+
154
+ <StatusbarControls items={statusbarItems} leftItems={leftStatusbarItems} />
155
+ </main>
156
+
157
+ {overlays}
158
+
159
+ {/* Keybind map dialog (titlebar ⌨ button / ⌘/). */}
160
+ <KeybindPanel />
161
+
162
+ {/* Mounted at the shell root (after overlays) so success/error toasts
163
+ surface above every route and overlay — not just the chat view. */}
164
+ <NotificationStack />
165
+ </SidebarProvider>
166
+ )
167
+ }
@@ -0,0 +1,150 @@
1
+ import { IconLayoutDashboard } from '@tabler/icons-react'
2
+
3
+ import { StatusDot, type StatusTone } from '@/components/status-dot'
4
+ import { Button } from '@/components/ui/button'
5
+ import { Tip } from '@/components/ui/tooltip'
6
+ import { useI18n } from '@/i18n'
7
+ import { Activity, AlertCircle } from '@/lib/icons'
8
+ import type { RuntimeReadinessResult } from '@/lib/runtime-readiness'
9
+ import { cn } from '@/lib/utils'
10
+ import type { StatusResponse } from '@/types/nastech'
11
+
12
+ interface GatewayMenuPanelProps {
13
+ gatewayState: string
14
+ inferenceStatus: RuntimeReadinessResult | null
15
+ logLines: readonly string[]
16
+ onOpenSystem: () => void
17
+ statusSnapshot: StatusResponse | null
18
+ }
19
+
20
+ const PLATFORM_TONE: Record<string, StatusTone> = {
21
+ connected: 'good',
22
+ connecting: 'warn',
23
+ retrying: 'warn',
24
+ pending_restart: 'warn',
25
+ startup_failed: 'bad',
26
+ fatal: 'bad'
27
+ }
28
+
29
+ const prettyState = (state: string) => state.replace(/_/g, ' ').replace(/^./, c => c.toUpperCase())
30
+
31
+ // Strip leading "YYYY-MM-DD HH:MM:SS,mmm " and "[runtime_id] " prefixes from
32
+ // log lines so they don't dominate the display. Full text preserved on hover.
33
+ const TIMESTAMP_RE = /^\d{4}-\d{2}-\d{2}[ T]\d{2}:\d{2}:\d{2}[,.\d]*\s+/
34
+ const RUNTIME_BRACKET_RE = /^\[[^\]]+]\s+/
35
+ const trimLogLine = (raw: string) => raw.trim().replace(TIMESTAMP_RE, '').replace(RUNTIME_BRACKET_RE, '')
36
+
37
+ export function GatewayMenuPanel({
38
+ gatewayState,
39
+ inferenceStatus,
40
+ logLines,
41
+ onOpenSystem,
42
+ statusSnapshot
43
+ }: GatewayMenuPanelProps) {
44
+ const { t } = useI18n()
45
+ const copy = t.shell.gatewayMenu
46
+ const gatewayOpen = gatewayState === 'open'
47
+ const gatewayConnecting = gatewayState === 'connecting'
48
+ const inferenceReady = gatewayOpen && inferenceStatus?.ready === true
49
+
50
+ const connectionLabel = gatewayOpen
51
+ ? copy.connected
52
+ : gatewayConnecting
53
+ ? copy.connecting
54
+ : prettyState(gatewayState || copy.offline)
55
+
56
+ const inferenceLabel = gatewayOpen
57
+ ? inferenceStatus?.ready
58
+ ? copy.inferenceReady
59
+ : inferenceStatus
60
+ ? copy.inferenceNotReady
61
+ : copy.checkingInference
62
+ : copy.disconnected
63
+
64
+ const platforms = Object.entries(statusSnapshot?.gateway_platforms || {}).sort(([l], [r]) => l.localeCompare(r))
65
+ const recentLogs = logLines.slice(-5)
66
+
67
+ return (
68
+ <div className="text-sm">
69
+ <div className="flex items-center justify-between gap-2 px-3 py-2.5">
70
+ <div className="flex min-w-0 items-center gap-2">
71
+ {inferenceReady ? (
72
+ <Activity className="size-3.5 text-primary" />
73
+ ) : (
74
+ <AlertCircle className={cn('size-3.5', gatewayOpen ? 'text-amber-600' : 'text-destructive')} />
75
+ )}
76
+ <span className="font-medium">{copy.gateway}</span>
77
+ <span className="flex items-center gap-1.5 text-xs text-muted-foreground">
78
+ <StatusDot tone={inferenceReady ? 'good' : gatewayOpen ? 'warn' : 'bad'} />
79
+ {inferenceLabel}
80
+ </span>
81
+ </div>
82
+ <div className="flex items-center">
83
+ <Tip label={copy.openSystem}>
84
+ <Button
85
+ aria-label={copy.openSystem}
86
+ className="text-muted-foreground hover:text-foreground"
87
+ onClick={onOpenSystem}
88
+ size="icon-sm"
89
+ variant="ghost"
90
+ >
91
+ <IconLayoutDashboard />
92
+ </Button>
93
+ </Tip>
94
+ </div>
95
+ </div>
96
+
97
+ <div className="border-t border-border/50 px-3 py-2 text-xs text-muted-foreground">
98
+ <div>{copy.connection(connectionLabel)}</div>
99
+ {inferenceStatus?.reason && <div className="mt-1 line-clamp-3">{inferenceStatus.reason}</div>}
100
+ </div>
101
+
102
+ {recentLogs.length > 0 && (
103
+ <div className="border-t border-border/50 px-3 py-2">
104
+ <SectionLabel>{copy.recentActivity}</SectionLabel>
105
+ <ul className="mt-1.5 space-y-0.5">
106
+ {recentLogs.map((line, index) => (
107
+ <Tip key={`${index}:${line}`} label={line.trim()}>
108
+ <li className="truncate font-mono text-[0.68rem] text-muted-foreground/85">
109
+ {trimLogLine(line) || '\u00A0'}
110
+ </li>
111
+ </Tip>
112
+ ))}
113
+ </ul>
114
+ <Button
115
+ className="-ml-2 mt-1.5 font-medium text-muted-foreground"
116
+ onClick={onOpenSystem}
117
+ size="xs"
118
+ type="button"
119
+ variant="text"
120
+ >
121
+ {copy.viewAllLogs}
122
+ </Button>
123
+ </div>
124
+ )}
125
+
126
+ {platforms.length > 0 && (
127
+ <div className="border-t border-border/50 px-3 py-2">
128
+ <SectionLabel>{copy.messagingPlatforms}</SectionLabel>
129
+ <ul className="mt-1.5 space-y-1">
130
+ {platforms.map(([name, platform]) => (
131
+ <li className="flex items-center justify-between gap-2 text-xs" key={name}>
132
+ <span className="truncate capitalize">{name}</span>
133
+ <span className="flex items-center gap-1.5 text-[0.66rem] text-muted-foreground">
134
+ <StatusDot tone={PLATFORM_TONE[platform.state] || 'muted'} />
135
+ {prettyState(platform.state)}
136
+ </span>
137
+ </li>
138
+ ))}
139
+ </ul>
140
+ </div>
141
+ )}
142
+ </div>
143
+ )
144
+ }
145
+
146
+ function SectionLabel({ children }: { children: string }) {
147
+ return (
148
+ <div className="text-[0.62rem] font-semibold uppercase tracking-[0.14em] text-muted-foreground/80">{children}</div>
149
+ )
150
+ }
@@ -0,0 +1,71 @@
1
+ import { useCallback, useEffect, useMemo, useRef } from 'react'
2
+ import { useLocation, useNavigate } from 'react-router-dom'
3
+
4
+ import { type CommandCenterSection } from '@/app/command-center'
5
+ import { AGENTS_ROUTE, appViewForPath, COMMAND_CENTER_ROUTE, isOverlayView, NEW_CHAT_ROUTE } from '@/app/routes'
6
+
7
+ const SECTIONS = ['sessions', 'system', 'usage'] as const
8
+
9
+ export function useOverlayRouting() {
10
+ const location = useLocation()
11
+ const navigate = useNavigate()
12
+
13
+ const currentView = appViewForPath(location.pathname)
14
+ const settingsOpen = currentView === 'settings'
15
+ const commandCenterOpen = currentView === 'command-center'
16
+ const agentsOpen = currentView === 'agents'
17
+ const cronOpen = currentView === 'cron'
18
+ const profilesOpen = currentView === 'profiles'
19
+ const chatOpen = currentView === 'chat'
20
+ const overlayOpen = isOverlayView(currentView)
21
+
22
+ // Overlay routes (settings/command-center/agents) stash the underlying path
23
+ // so closing them returns there instead of bouncing to /.
24
+ const returnPathRef = useRef(NEW_CHAT_ROUTE)
25
+
26
+ useEffect(() => {
27
+ if (!overlayOpen) {
28
+ returnPathRef.current = `${location.pathname}${location.search}${location.hash}`
29
+ }
30
+ }, [location.hash, location.pathname, location.search, overlayOpen])
31
+
32
+ const commandCenterInitialSection = useMemo<CommandCenterSection | undefined>(
33
+ () => SECTIONS.find(value => value === new URLSearchParams(location.search).get('section')),
34
+ [location.search]
35
+ )
36
+
37
+ const openCommandCenterSection = useCallback(
38
+ (section: CommandCenterSection) => navigate(`${COMMAND_CENTER_ROUTE}?section=${section}`),
39
+ [navigate]
40
+ )
41
+
42
+ const closeOverlayToPreviousRoute = useCallback(
43
+ () => navigate(returnPathRef.current || NEW_CHAT_ROUTE, { replace: true }),
44
+ [navigate]
45
+ )
46
+
47
+ const toggleCommandCenter = useCallback(() => {
48
+ if (commandCenterOpen) {
49
+ closeOverlayToPreviousRoute()
50
+ } else {
51
+ navigate(COMMAND_CENTER_ROUTE)
52
+ }
53
+ }, [closeOverlayToPreviousRoute, commandCenterOpen, navigate])
54
+
55
+ const openAgents = useCallback(() => navigate(AGENTS_ROUTE), [navigate])
56
+
57
+ return {
58
+ agentsOpen,
59
+ chatOpen,
60
+ closeOverlayToPreviousRoute,
61
+ commandCenterInitialSection,
62
+ commandCenterOpen,
63
+ cronOpen,
64
+ currentView,
65
+ openAgents,
66
+ openCommandCenterSection,
67
+ profilesOpen,
68
+ settingsOpen,
69
+ toggleCommandCenter
70
+ }
71
+ }
@@ -0,0 +1,57 @@
1
+ import { useEffect, useState } from 'react'
2
+
3
+ import { getLogs, getStatus } from '@/nastech'
4
+ import { evaluateRuntimeReadiness, type RuntimeReadinessResult } from '@/lib/runtime-readiness'
5
+ import type { StatusResponse } from '@/types/nastech'
6
+
7
+ const REFRESH_MS = 15_000
8
+ const LOG_TAIL = 12
9
+
10
+ type GatewayRequester = <T = unknown>(method: string, params?: Record<string, unknown>) => Promise<T>
11
+
12
+ export function useStatusSnapshot(gatewayState: string | undefined, requestGateway: GatewayRequester) {
13
+ const [statusSnapshot, setStatusSnapshot] = useState<StatusResponse | null>(null)
14
+ const [gatewayLogLines, setGatewayLogLines] = useState<string[]>([])
15
+ const [inferenceStatus, setInferenceStatus] = useState<RuntimeReadinessResult | null>(null)
16
+
17
+ useEffect(() => {
18
+ let cancelled = false
19
+
20
+ const refresh = async () => {
21
+ try {
22
+ const [next, logs, inference] = await Promise.all([
23
+ getStatus(),
24
+ getLogs({ file: 'gui', lines: LOG_TAIL }).catch(() => ({ lines: [] })),
25
+ gatewayState === 'open'
26
+ ? evaluateRuntimeReadiness(requestGateway).catch(error => ({
27
+ checksDisagree: false,
28
+ ready: false,
29
+ reason: error instanceof Error ? error.message : String(error),
30
+ source: 'fallback' as const
31
+ }))
32
+ : Promise.resolve(null)
33
+ ])
34
+
35
+ if (cancelled) {
36
+ return
37
+ }
38
+
39
+ setStatusSnapshot(next)
40
+ setGatewayLogLines(logs.lines.map(line => line.trim()).filter(Boolean))
41
+ setInferenceStatus(inference)
42
+ } catch {
43
+ // Keep last snapshot through transient gateway flaps.
44
+ }
45
+ }
46
+
47
+ void refresh()
48
+ const timer = window.setInterval(() => void refresh(), REFRESH_MS)
49
+
50
+ return () => {
51
+ cancelled = true
52
+ window.clearInterval(timer)
53
+ }
54
+ }, [gatewayState, requestGateway])
55
+
56
+ return { gatewayLogLines, inferenceStatus, statusSnapshot }
57
+ }