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,91 @@
1
+ import type { NasTechConnection } from '@/global'
2
+
3
+ /**
4
+ * The desktop main process exposes `getGatewayWsUrl()` to re-mint a WebSocket
5
+ * URL immediately before every `gateway.connect()`. For OAuth-gated remote
6
+ * gateways the WS ticket is single-use with a ~30s TTL, so the ticket baked
7
+ * into the cached `conn.wsUrl` is stale (and, after the first connect, already
8
+ * consumed). For local/token gateways the URL carries a long-lived token and
9
+ * never needs re-minting.
10
+ *
11
+ * Resolution rules:
12
+ *
13
+ * - OAuth: the fresh mint is the *only* viable URL. If it fails, do NOT fall
14
+ * back to `conn.wsUrl` — that ticket is dead and the connect is guaranteed to
15
+ * fail with an opaque "connection closed" error. Instead, let the mint error
16
+ * propagate so the caller can surface the gateway's reauth message
17
+ * ("session has expired… Sign in again").
18
+ *
19
+ * - token / local, or when the preload method is genuinely absent (older
20
+ * preload shapes): fall back to `conn.wsUrl`. The token URL is long-lived, so
21
+ * the fallback is safe and preserves compatibility.
22
+ *
23
+ * The error thrown for OAuth mint failures is tagged with `needsOauthLogin` so
24
+ * callers can distinguish "the user must re-authenticate" from a generic
25
+ * transport failure.
26
+ */
27
+ export interface ResolveGatewayWsUrlDeps {
28
+ /** `window.NASTECHDesktop.getGatewayWsUrl`, if the preload exposes it. The
29
+ * optional profile selects which backend to mint for — critical when swapping
30
+ * to a pooled profile, since the default mint resolves the primary backend. */
31
+ getGatewayWsUrl?: (profile?: null | string) => Promise<string>
32
+ }
33
+
34
+ export class GatewayReauthRequiredError extends Error {
35
+ readonly needsOauthLogin = true
36
+
37
+ constructor(message: string, options?: { cause?: unknown }) {
38
+ super(message, options)
39
+ this.name = 'GatewayReauthRequiredError'
40
+ }
41
+ }
42
+
43
+ export function isGatewayReauthRequired(error: unknown): error is GatewayReauthRequiredError {
44
+ return (
45
+ error instanceof GatewayReauthRequiredError ||
46
+ (typeof error === 'object' && error !== null && (error as { needsOauthLogin?: unknown }).needsOauthLogin === true)
47
+ )
48
+ }
49
+
50
+ export async function resolveGatewayWsUrl(
51
+ desktop: ResolveGatewayWsUrlDeps,
52
+ conn: Pick<NasTechConnection, 'authMode' | 'profile' | 'wsUrl'>
53
+ ): Promise<string> {
54
+ const mint = desktop.getGatewayWsUrl
55
+ // Mint for THIS connection's profile, not the primary. Without it a pooled
56
+ // profile swap re-mints the default backend's URL and connects to the wrong
57
+ // backend.
58
+ const profile = conn.profile ?? null
59
+
60
+ if (conn.authMode === 'oauth') {
61
+ if (!mint) {
62
+ // OAuth gateway but no way to mint a fresh ticket: the cached ticket is
63
+ // dead, so connecting with it cannot succeed. Surface a reauth error
64
+ // rather than silently attempting a doomed connect.
65
+ throw new GatewayReauthRequiredError(
66
+ 'Your remote gateway session needs to be refreshed. Open Settings → Gateway and click "Sign in" again.'
67
+ )
68
+ }
69
+
70
+ try {
71
+ return await mint(profile)
72
+ } catch (error) {
73
+ throw new GatewayReauthRequiredError(
74
+ 'Your remote gateway session has expired. Open Settings → Gateway and click "Sign in" again.',
75
+ { cause: error }
76
+ )
77
+ }
78
+ }
79
+
80
+ // token / local: the URL carries a long-lived token. Re-mint when available
81
+ // (cheap, keeps parity), but the cached URL is a safe fallback.
82
+ if (mint) {
83
+ const fresh = await mint(profile).catch(() => null)
84
+
85
+ if (fresh) {
86
+ return fresh
87
+ }
88
+ }
89
+
90
+ return conn.wsUrl
91
+ }
@@ -0,0 +1,97 @@
1
+ import { describe, expect, it } from 'vitest'
2
+
3
+ import {
4
+ dedupeGeneratedImageEchoesInParts,
5
+ generatedImageEchoSources,
6
+ generatedImageFromResult,
7
+ stripGeneratedImageEchoes
8
+ } from './generated-images'
9
+
10
+ describe('generatedImageFromResult', () => {
11
+ it('prefers the host-visible image path', () => {
12
+ expect(
13
+ generatedImageFromResult({
14
+ agent_visible_image: '/container/cache/cat.png',
15
+ host_image: '/Users/me/.nastech/cache/images/cat.png',
16
+ image: '/Users/me/.nastech/cache/images/cat.png',
17
+ success: true
18
+ })
19
+ ).toBe('/Users/me/.nastech/cache/images/cat.png')
20
+ })
21
+
22
+ it('ignores failed image generation results', () => {
23
+ expect(generatedImageFromResult({ image: 'https://cdn.example/cat.png', success: false })).toBeNull()
24
+ })
25
+ })
26
+
27
+ describe('stripGeneratedImageEchoes', () => {
28
+ it('removes repeated generated image markdown without removing prose', () => {
29
+ expect(
30
+ stripGeneratedImageEchoes('Here you go.\n\n![Generated image](https://cdn.example/cat.png)', [
31
+ 'https://cdn.example/cat.png'
32
+ ])
33
+ ).toBe('Here you go.')
34
+ })
35
+
36
+ it('removes media links for generated local image paths', () => {
37
+ expect(
38
+ stripGeneratedImageEchoes('Saved image: [Image: cat.png](#media:%2Ftmp%2Fcat.png)', ['/tmp/cat.png'])
39
+ ).toBe('Saved image:')
40
+ })
41
+ })
42
+
43
+ describe('generatedImageEchoSources', () => {
44
+ it('collects every path variant the model might restate', () => {
45
+ expect(
46
+ generatedImageEchoSources([
47
+ {
48
+ result: { agent_visible_image: '/sandbox/cat.png', host_image: '/host/cat.png', image: '/host/cat.png', success: true },
49
+ toolName: 'image_generate',
50
+ type: 'tool-call'
51
+ }
52
+ ])
53
+ ).toEqual(['/host/cat.png', '/sandbox/cat.png'])
54
+ })
55
+ })
56
+
57
+ describe('dedupeGeneratedImageEchoesInParts', () => {
58
+ it('keeps the agent prose while removing the duplicated image', () => {
59
+ expect(
60
+ dedupeGeneratedImageEchoesInParts([
61
+ { text: 'Here is your peacock! ![peacock](/host/p.png) Enjoy.', type: 'text' },
62
+ { result: { host_image: '/host/p.png', image: '/host/p.png', success: true }, toolName: 'image_generate', type: 'tool-call' }
63
+ ])
64
+ ).toEqual([
65
+ { text: 'Here is your peacock! Enjoy.', type: 'text' },
66
+ { result: { host_image: '/host/p.png', image: '/host/p.png', success: true }, toolName: 'image_generate', type: 'tool-call' }
67
+ ])
68
+ })
69
+
70
+ it('strips a sandbox path the model restated instead of the host path', () => {
71
+ expect(
72
+ dedupeGeneratedImageEchoesInParts([
73
+ { text: '![cat](/sandbox/cat.png)', type: 'text' },
74
+ {
75
+ result: { agent_visible_image: '/sandbox/cat.png', host_image: '/host/cat.png', image: '/host/cat.png', success: true },
76
+ toolName: 'image_generate',
77
+ type: 'tool-call'
78
+ }
79
+ ])
80
+ ).toEqual([
81
+ {
82
+ result: { agent_visible_image: '/sandbox/cat.png', host_image: '/host/cat.png', image: '/host/cat.png', success: true },
83
+ toolName: 'image_generate',
84
+ type: 'tool-call'
85
+ }
86
+ ])
87
+ })
88
+
89
+ it('leaves pending generations untouched so the agent prose survives', () => {
90
+ const parts = [
91
+ { text: 'Another peacock, coming up!', type: 'text' },
92
+ { result: undefined, toolName: 'image_generate', type: 'tool-call' }
93
+ ]
94
+
95
+ expect(dedupeGeneratedImageEchoesInParts(parts)).toEqual(parts)
96
+ })
97
+ })
@@ -0,0 +1,116 @@
1
+ type ToolLike = {
2
+ result?: unknown
3
+ toolName?: unknown
4
+ type?: unknown
5
+ }
6
+
7
+ type TextLike = {
8
+ text?: unknown
9
+ type?: unknown
10
+ }
11
+
12
+ // Path-ish result fields the model may echo into its prose. Display prefers the
13
+ // host path (gateway-deliverable); stripping must catch every variant so a
14
+ // sandbox path the model restated doesn't slip through as a duplicate image.
15
+ const DISPLAY_KEYS = ['host_image', 'image'] as const
16
+ const ECHO_KEYS = ['host_image', 'image', 'agent_visible_image'] as const
17
+
18
+ function recordFromUnknown(value: unknown): Record<string, unknown> | null {
19
+ if (value && typeof value === 'object' && !Array.isArray(value)) {
20
+ return value as Record<string, unknown>
21
+ }
22
+
23
+ if (typeof value !== 'string' || !value.trim()) {
24
+ return null
25
+ }
26
+
27
+ try {
28
+ const parsed = JSON.parse(value)
29
+
30
+ return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? (parsed as Record<string, unknown>) : null
31
+ } catch {
32
+ return null
33
+ }
34
+ }
35
+
36
+ function stringFields(record: Record<string, unknown>, keys: readonly string[]): string[] {
37
+ return keys.map(key => record[key]).filter((v): v is string => typeof v === 'string' && v.trim().length > 0)
38
+ }
39
+
40
+ function regexEscape(value: string): string {
41
+ return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
42
+ }
43
+
44
+ function unique(values: string[]): string[] {
45
+ return [...new Set(values.filter(Boolean))]
46
+ }
47
+
48
+ function imageResult(part: ToolLike): Record<string, unknown> | null {
49
+ if (part.type !== 'tool-call' || part.toolName !== 'image_generate') {
50
+ return null
51
+ }
52
+
53
+ const record = recordFromUnknown(part.result)
54
+
55
+ return record && record.success !== false ? record : null
56
+ }
57
+
58
+ /** Display source for a completed `image_generate` result (host path wins). */
59
+ export function generatedImageFromResult(result: unknown): string | null {
60
+ const record = recordFromUnknown(result)
61
+
62
+ if (!record || record.success === false) {
63
+ return null
64
+ }
65
+
66
+ return stringFields(record, DISPLAY_KEYS)[0] ?? null
67
+ }
68
+
69
+ /** Every path/URL a generated image might appear as in prose, for de-duping. */
70
+ export function generatedImageEchoSources(parts: readonly ToolLike[]): string[] {
71
+ return unique(parts.flatMap(part => stringFields(imageResult(part) ?? {}, ECHO_KEYS)))
72
+ }
73
+
74
+ /** Strip a generated image out of prose so it only ever shows in the tool slot.
75
+ * Once a generation succeeded (`sources` is non-empty) we drop every embedded
76
+ * image and media link from that message — the model frequently restates the
77
+ * remote URL while the result holds the local path, so matching the exact
78
+ * source is not enough. Bare occurrences of the known paths/URLs are removed
79
+ * too. Surrounding prose is preserved. */
80
+ export function stripGeneratedImageEchoes(text: string, sources: readonly string[]): string {
81
+ if (!text || sources.length === 0) {
82
+ return text
83
+ }
84
+
85
+ let next = text
86
+ .replace(/!\[[^\]\n]*\]\([^)\n]*\)/g, '')
87
+ .replace(/\[[^\]\n]*\]\(\s*#media:[^)\n]*\)/g, '')
88
+
89
+ for (const source of unique([...sources])) {
90
+ next = next.replace(new RegExp(String.raw`(^|[\s([{])<?${regexEscape(source)}>?(?=$|[\s)\]},.!?])`, 'g'), '$1')
91
+ }
92
+
93
+ return next
94
+ .replace(/[ \t]+\n/g, '\n')
95
+ .replace(/\n{3,}/g, '\n\n')
96
+ .replace(/[ \t]{2,}/g, ' ')
97
+ .trim()
98
+ }
99
+
100
+ /** Strip generated-image echoes from text parts, dropping any part left empty.
101
+ * The image lives in the tool slot; prose keeps the agent's actual words. */
102
+ export function dedupeGeneratedImageEchoesInParts<T extends TextLike & ToolLike>(parts: readonly T[]): T[] {
103
+ const sources = generatedImageEchoSources(parts)
104
+
105
+ if (!sources.length) {
106
+ return [...parts]
107
+ }
108
+
109
+ return parts
110
+ .map(part =>
111
+ part.type === 'text' && typeof part.text === 'string'
112
+ ? { ...part, text: stripGeneratedImageEchoes(part.text, sources) }
113
+ : part
114
+ )
115
+ .filter(part => part.type !== 'text' || (typeof part.text === 'string' && part.text.trim().length > 0))
116
+ }
@@ -0,0 +1,129 @@
1
+ import type { HapticInput, TriggerOptions } from 'web-haptics'
2
+
3
+ import { $hapticsMuted } from '@/store/haptics'
4
+
5
+ export type HapticIntent =
6
+ | 'cancel'
7
+ | 'close'
8
+ | 'crisp'
9
+ | 'error'
10
+ | 'open'
11
+ | 'selection'
12
+ | 'streamDone'
13
+ | 'streamStart'
14
+ | 'submit'
15
+ | 'success'
16
+ | 'tap'
17
+ | 'warning'
18
+
19
+ interface HapticConfig {
20
+ options?: TriggerOptions
21
+ pattern: HapticInput
22
+ }
23
+
24
+ const airyTap = [{ duration: 16, intensity: 0.52 }]
25
+
26
+ const crispTap = [{ duration: 10, intensity: 0.92 }]
27
+
28
+ const friendlySuccess = [
29
+ { duration: 28, intensity: 0.5 },
30
+ { delay: 42, duration: 30, intensity: 0.68 },
31
+ { delay: 48, duration: 38, intensity: 0.86 }
32
+ ]
33
+
34
+ const softArrive = [
35
+ { duration: 18, intensity: 0.42 },
36
+ { delay: 36, duration: 22, intensity: 0.66 }
37
+ ]
38
+
39
+ const softLeave = [
40
+ { duration: 22, intensity: 0.58 },
41
+ { delay: 32, duration: 16, intensity: 0.34 }
42
+ ]
43
+
44
+ const HAPTIC_INTENTS: Record<HapticIntent, HapticConfig> = {
45
+ cancel: {
46
+ pattern: [
47
+ { duration: 34, intensity: 0.72 },
48
+ { delay: 54, duration: 26, intensity: 0.38 }
49
+ ]
50
+ },
51
+ close: { pattern: softLeave },
52
+ crisp: { pattern: crispTap },
53
+ error: {
54
+ pattern: [
55
+ { duration: 34, intensity: 0.82 },
56
+ { delay: 42, duration: 34, intensity: 0.72 },
57
+ { delay: 58, duration: 44, intensity: 0.86 }
58
+ ]
59
+ },
60
+ open: { pattern: softArrive },
61
+ selection: { pattern: airyTap },
62
+ streamDone: { pattern: friendlySuccess },
63
+ streamStart: { pattern: [{ duration: 10, intensity: 0.32 }] },
64
+ submit: {
65
+ pattern: [
66
+ { duration: 24, intensity: 0.58 },
67
+ { delay: 48, duration: 36, intensity: 0.82 }
68
+ ]
69
+ },
70
+ success: { pattern: friendlySuccess },
71
+ tap: {
72
+ pattern: [
73
+ { duration: 14, intensity: 0.58 },
74
+ { delay: 30, duration: 12, intensity: 0.42 }
75
+ ]
76
+ },
77
+ warning: {
78
+ pattern: [
79
+ { duration: 34, intensity: 0.64 },
80
+ { delay: 84, duration: 42, intensity: 0.5 }
81
+ ]
82
+ }
83
+ }
84
+
85
+ export type HapticTrigger = (input?: HapticInput, options?: TriggerOptions) => Promise<void> | undefined
86
+
87
+ let registeredTrigger: HapticTrigger | null = null
88
+ let lastSelectionAt = 0
89
+
90
+ // Global rolling rate-limit. A runaway upstream loop (auth-expiry error-toast
91
+ // storms, reconnect flaps) can request dozens of haptics a second, which the
92
+ // trackpad actuator renders as a frantic "clickity" buzz. Cap firings to
93
+ // RATE_LIMIT per RATE_WINDOW so no source can machine-gun the actuator;
94
+ // intentional UI haptics are human-paced and never approach the ceiling.
95
+ const RATE_WINDOW = 1000
96
+ const RATE_LIMIT = 5
97
+ let recentFires: number[] = []
98
+
99
+ export function registerHapticTrigger(trigger: HapticTrigger | null) {
100
+ registeredTrigger = trigger
101
+ }
102
+
103
+ export function triggerHaptic(intent: HapticIntent = 'selection') {
104
+ if ($hapticsMuted.get() || !registeredTrigger) {
105
+ return
106
+ }
107
+
108
+ const now = performance.now()
109
+
110
+ if (intent === 'selection') {
111
+ if (now - lastSelectionAt < 50) {
112
+ return
113
+ }
114
+
115
+ lastSelectionAt = now
116
+ }
117
+
118
+ recentFires = recentFires.filter(t => now - t < RATE_WINDOW)
119
+
120
+ if (recentFires.length >= RATE_LIMIT) {
121
+ return
122
+ }
123
+
124
+ recentFires.push(now)
125
+
126
+ const config = HAPTIC_INTENTS[intent]
127
+
128
+ void registeredTrigger(config.pattern, config.options)?.catch(() => undefined)
129
+ }
@@ -0,0 +1,203 @@
1
+ import {
2
+ IconActivity as Activity,
3
+ IconAlertCircle as AlertCircle,
4
+ IconAlertTriangle as AlertTriangle,
5
+ IconArchive as Archive,
6
+ IconArchiveOff as ArchiveOff,
7
+ IconArrowUp as ArrowUp,
8
+ IconArrowUpRight as ArrowUpRight,
9
+ IconAt as AtSign,
10
+ IconWaveSine as AudioLines,
11
+ IconChartBar as BarChart3,
12
+ IconBrain as Brain,
13
+ IconBug as Bug,
14
+ IconCheck as Check,
15
+ IconCircleCheck as CheckCircle2,
16
+ IconCheck as CheckIcon,
17
+ IconChevronDown as ChevronDown,
18
+ IconChevronDown as ChevronDownIcon,
19
+ IconChevronLeft as ChevronLeft,
20
+ IconChevronLeft as ChevronLeftIcon,
21
+ IconChevronRight as ChevronRight,
22
+ IconChevronRight as ChevronRightIcon,
23
+ IconCircle as CircleIcon,
24
+ IconClipboard as Clipboard,
25
+ IconClock as Clock,
26
+ IconCommand as Command,
27
+ IconCopy as Copy,
28
+ IconCopy as CopyIcon,
29
+ IconCpu as Cpu,
30
+ IconDownload as Download,
31
+ IconExternalLink as ExternalLink,
32
+ IconEye as Eye,
33
+ IconEyeOff as EyeOff,
34
+ IconPhoto as FileImage,
35
+ IconFileText as FileText,
36
+ IconFolderOpen as FolderOpen,
37
+ IconGitBranch as GitBranch,
38
+ IconGitBranch as GitBranchIcon,
39
+ IconGlobe as Globe,
40
+ IconHash as Hash,
41
+ IconHelpCircle as HelpCircle,
42
+ IconPhoto as ImageIcon,
43
+ IconInfoCircle as Info,
44
+ IconKey as KeyRound,
45
+ IconLayersIntersect2 as Layers3,
46
+ IconLink as Link,
47
+ IconLink as Link2,
48
+ IconLink as LinkIcon,
49
+ IconLoader2 as Loader2,
50
+ IconLoader2 as Loader2Icon,
51
+ IconLock as Lock,
52
+ IconLogin as LogIn,
53
+ IconMessageCircle as MessageCircle,
54
+ IconMessage2 as MessageSquareText,
55
+ IconMicrophone as Mic,
56
+ IconMicrophoneOff as MicOff,
57
+ IconDeviceDesktop as Monitor,
58
+ IconDeviceDesktopAnalytics as MonitorPlay,
59
+ IconMoon as Moon,
60
+ IconDots as MoreHorizontal,
61
+ IconDots as MoreHorizontalIcon,
62
+ IconDotsVertical as MoreVertical,
63
+ IconNotebook as NotebookTabs,
64
+ IconPackage as Package,
65
+ IconPalette as Palette,
66
+ IconLayoutBottombar as PanelBottom,
67
+ IconLayoutSidebar as PanelLeftIcon,
68
+ IconPlayerPause as Pause,
69
+ IconPencil as Pencil,
70
+ IconPencil as PencilIcon,
71
+ IconPencil as PencilLine,
72
+ IconPin as Pin,
73
+ IconPlayerPlay as Play,
74
+ IconPlus as Plus,
75
+ IconRefresh as RefreshCw,
76
+ IconRefresh as RefreshCwIcon,
77
+ IconDeviceFloppy as Save,
78
+ IconSearch as Search,
79
+ IconSearch as SearchIcon,
80
+ IconSend as Send,
81
+ IconSettings as Settings,
82
+ IconSettings2 as Settings2,
83
+ IconAdjustmentsHorizontal as SlidersHorizontal,
84
+ IconSparkles as Sparkles,
85
+ IconSquare as Square,
86
+ IconSteeringWheel as SteeringWheel,
87
+ IconSun as Sun,
88
+ IconTerminal2 as Terminal,
89
+ IconTrash as Trash2,
90
+ IconUsers as Users,
91
+ IconVolume2 as Volume2,
92
+ IconVolume2 as Volume2Icon,
93
+ IconVolumeOff as VolumeX,
94
+ IconVolumeOff as VolumeXIcon,
95
+ IconTool as Wrench,
96
+ IconX as X,
97
+ IconX as XIcon,
98
+ IconBolt as Zap,
99
+ IconBoltFilled as ZapFilled
100
+ } from '@tabler/icons-react'
101
+
102
+ export {
103
+ Activity,
104
+ AlertCircle,
105
+ AlertTriangle,
106
+ Archive,
107
+ ArchiveOff,
108
+ ArrowUp,
109
+ ArrowUpRight,
110
+ AtSign,
111
+ AudioLines,
112
+ BarChart3,
113
+ Brain,
114
+ Bug,
115
+ Check,
116
+ CheckCircle2,
117
+ CheckIcon,
118
+ ChevronDown,
119
+ ChevronDownIcon,
120
+ ChevronLeft,
121
+ ChevronLeftIcon,
122
+ ChevronRight,
123
+ ChevronRightIcon,
124
+ CircleIcon,
125
+ Clipboard,
126
+ Clock,
127
+ Command,
128
+ Copy,
129
+ CopyIcon,
130
+ Cpu,
131
+ Download,
132
+ ExternalLink,
133
+ Eye,
134
+ EyeOff,
135
+ FileImage,
136
+ FileText,
137
+ FolderOpen,
138
+ GitBranch,
139
+ GitBranchIcon,
140
+ Globe,
141
+ Hash,
142
+ HelpCircle,
143
+ ImageIcon,
144
+ Info,
145
+ KeyRound,
146
+ Layers3,
147
+ Link,
148
+ Link2,
149
+ LinkIcon,
150
+ Loader2,
151
+ Loader2Icon,
152
+ Lock,
153
+ LogIn,
154
+ MessageCircle,
155
+ MessageSquareText,
156
+ Mic,
157
+ MicOff,
158
+ Monitor,
159
+ MonitorPlay,
160
+ Moon,
161
+ MoreHorizontal,
162
+ MoreHorizontalIcon,
163
+ MoreVertical,
164
+ NotebookTabs,
165
+ Package,
166
+ Palette,
167
+ PanelBottom,
168
+ PanelLeftIcon,
169
+ Pause,
170
+ Pencil,
171
+ PencilIcon,
172
+ PencilLine,
173
+ Pin,
174
+ Play,
175
+ Plus,
176
+ RefreshCw,
177
+ RefreshCwIcon,
178
+ Save,
179
+ Search,
180
+ SearchIcon,
181
+ Send,
182
+ Settings,
183
+ Settings2,
184
+ SlidersHorizontal,
185
+ Sparkles,
186
+ Square,
187
+ SteeringWheel,
188
+ Sun,
189
+ Terminal,
190
+ Trash2,
191
+ Users,
192
+ Volume2,
193
+ Volume2Icon,
194
+ VolumeX,
195
+ VolumeXIcon,
196
+ Wrench,
197
+ X,
198
+ XIcon,
199
+ Zap,
200
+ ZapFilled
201
+ }
202
+
203
+ export type { Icon as IconComponent } from '@tabler/icons-react'