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.
- package/.prettierrc +11 -0
- package/DESIGN.md +167 -0
- package/README.md +141 -0
- package/assets/icon.icns +0 -0
- package/assets/icon.ico +0 -0
- package/assets/icon.png +0 -0
- package/components.json +21 -0
- package/electron/backend-env.cjs +112 -0
- package/electron/backend-env.test.cjs +111 -0
- package/electron/backend-probes.cjs +106 -0
- package/electron/backend-probes.test.cjs +82 -0
- package/electron/backend-ready.cjs +66 -0
- package/electron/bootstrap-platform.cjs +91 -0
- package/electron/bootstrap-platform.test.cjs +111 -0
- package/electron/bootstrap-runner.cjs +720 -0
- package/electron/bootstrap-runner.test.cjs +138 -0
- package/electron/connection-config.cjs +254 -0
- package/electron/connection-config.test.cjs +329 -0
- package/electron/dashboard-token.cjs +99 -0
- package/electron/dashboard-token.test.cjs +142 -0
- package/electron/desktop-uninstall.cjs +232 -0
- package/electron/desktop-uninstall.test.cjs +246 -0
- package/electron/entitlements.mac.inherit.plist +14 -0
- package/electron/entitlements.mac.plist +14 -0
- package/electron/fs-read-dir.cjs +109 -0
- package/electron/fs-read-dir.test.cjs +364 -0
- package/electron/gateway-ws-probe.cjs +188 -0
- package/electron/gateway-ws-probe.test.cjs +122 -0
- package/electron/git-root.cjs +54 -0
- package/electron/git-root.test.cjs +40 -0
- package/electron/git-worktrees.cjs +174 -0
- package/electron/hardening.cjs +184 -0
- package/electron/hardening.test.cjs +116 -0
- package/electron/main.cjs +5762 -0
- package/electron/oauth-net-request.cjs +20 -0
- package/electron/oauth-net-request.test.cjs +34 -0
- package/electron/preload.cjs +135 -0
- package/electron/session-windows.cjs +99 -0
- package/electron/session-windows.test.cjs +177 -0
- package/electron/update-remote.cjs +56 -0
- package/electron/update-remote.test.cjs +78 -0
- package/electron/vscode-marketplace.cjs +331 -0
- package/electron/vscode-marketplace.test.cjs +113 -0
- package/electron/windows-child-process.test.cjs +57 -0
- package/electron/windows-user-env.cjs +76 -0
- package/electron/windows-user-env.test.cjs +90 -0
- package/electron/workspace-cwd.cjs +38 -0
- package/electron/workspace-cwd.test.cjs +45 -0
- package/eslint.config.mjs +122 -0
- package/index.html +17 -0
- package/package.json +254 -0
- package/pr-assets/session-source-folders.png +0 -0
- package/preview-demo.html +65 -0
- package/public/apple-touch-icon.png +0 -0
- package/public/ds-assets/filler-bg0.jpg +0 -0
- package/public/nastech-frames/nastech-frame-0.png +0 -0
- package/public/nastech-frames/nastech-frame-1.png +0 -0
- package/public/nastech-frames/nastech-frame-2.png +0 -0
- package/public/nastech-frames/nastech-frame-3.png +0 -0
- package/public/nastech-frames/nastech-frame-4.png +0 -0
- package/public/nastech-frames/nastech-frame-5.png +0 -0
- package/public/nastech-frames/nastech-frame-6.png +0 -0
- package/public/nastech-frames/nastech-frame-7.png +0 -0
- package/public/nastech-girl.jpg +0 -0
- package/public/nastech-sprite.png +0 -0
- package/public/nastech.png +0 -0
- package/scripts/after-pack.cjs +41 -0
- package/scripts/assert-dist-built.cjs +70 -0
- package/scripts/assert-dist-built.test.cjs +84 -0
- package/scripts/assert-root-install.cjs +13 -0
- package/scripts/before-build.cjs +11 -0
- package/scripts/before-pack.cjs +78 -0
- package/scripts/before-pack.test.cjs +53 -0
- package/scripts/click-session.mjs +51 -0
- package/scripts/dev-no-hmr.mjs +22 -0
- package/scripts/diag-jump.mjs +115 -0
- package/scripts/diag-scroll-reset.mjs +229 -0
- package/scripts/eval.mjs +21 -0
- package/scripts/leak-typing.mjs +222 -0
- package/scripts/measure-jump.mjs +108 -0
- package/scripts/measure-latency.mjs +184 -0
- package/scripts/measure-real-stream.mjs +252 -0
- package/scripts/measure-submit.mjs +179 -0
- package/scripts/measure-synthetic-stream.mjs +322 -0
- package/scripts/notarize-artifact.cjs +77 -0
- package/scripts/notarize.cjs +100 -0
- package/scripts/patch-electron-builder-mac-binary.cjs +59 -0
- package/scripts/probe-renderer.mjs +38 -0
- package/scripts/probe-thread.mjs +40 -0
- package/scripts/profile-long-stream.mjs +191 -0
- package/scripts/profile-real-stream.mjs +137 -0
- package/scripts/profile-synth-stream.mjs +103 -0
- package/scripts/profile-typing-lag.md +381 -0
- package/scripts/profile-typing.mjs +260 -0
- package/scripts/reload-renderer.mjs +25 -0
- package/scripts/reload.mjs +36 -0
- package/scripts/set-exe-identity.cjs +94 -0
- package/scripts/stage-native-deps.cjs +159 -0
- package/scripts/test-desktop.mjs +425 -0
- package/scripts/write-build-stamp.cjs +126 -0
- package/src/app/agents/index.tsx +398 -0
- package/src/app/artifacts/index.test.ts +62 -0
- package/src/app/artifacts/index.tsx +906 -0
- package/src/app/chat/chat-drop-overlay.tsx +48 -0
- package/src/app/chat/chat-swap-overlay.tsx +47 -0
- package/src/app/chat/composer/attachments.tsx +114 -0
- package/src/app/chat/composer/completion-drawer.tsx +63 -0
- package/src/app/chat/composer/context-menu.tsx +172 -0
- package/src/app/chat/composer/controls.tsx +289 -0
- package/src/app/chat/composer/drop-affordance.ts +2 -0
- package/src/app/chat/composer/enter-submit-dom-race.test.tsx +218 -0
- package/src/app/chat/composer/focus.ts +134 -0
- package/src/app/chat/composer/help-hint.tsx +59 -0
- package/src/app/chat/composer/hooks/use-at-completions.ts +141 -0
- package/src/app/chat/composer/hooks/use-live-completion-adapter.ts +119 -0
- package/src/app/chat/composer/hooks/use-mic-recorder.ts +291 -0
- package/src/app/chat/composer/hooks/use-slash-completions.ts +114 -0
- package/src/app/chat/composer/hooks/use-voice-conversation.ts +390 -0
- package/src/app/chat/composer/hooks/use-voice-recorder.ts +116 -0
- package/src/app/chat/composer/ime-composition-dom-repro.test.tsx +108 -0
- package/src/app/chat/composer/index.tsx +1611 -0
- package/src/app/chat/composer/inline-refs.ts +138 -0
- package/src/app/chat/composer/model-pill.tsx +86 -0
- package/src/app/chat/composer/queue-panel.tsx +130 -0
- package/src/app/chat/composer/rich-editor.test.ts +18 -0
- package/src/app/chat/composer/rich-editor.ts +165 -0
- package/src/app/chat/composer/skin-slash-popover.tsx +61 -0
- package/src/app/chat/composer/slash-nav-dom-repro.test.tsx +186 -0
- package/src/app/chat/composer/status-stack/index.tsx +202 -0
- package/src/app/chat/composer/status-stack/status-row.tsx +155 -0
- package/src/app/chat/composer/text-utils.test.ts +77 -0
- package/src/app/chat/composer/text-utils.ts +107 -0
- package/src/app/chat/composer/trigger-popover.test.tsx +42 -0
- package/src/app/chat/composer/trigger-popover.tsx +116 -0
- package/src/app/chat/composer/types.ts +64 -0
- package/src/app/chat/composer/url-dialog.tsx +82 -0
- package/src/app/chat/composer/voice-activity.tsx +252 -0
- package/src/app/chat/hooks/use-composer-actions.test.ts +57 -0
- package/src/app/chat/hooks/use-composer-actions.ts +525 -0
- package/src/app/chat/hooks/use-file-drop-zone.ts +118 -0
- package/src/app/chat/index.tsx +390 -0
- package/src/app/chat/perf-probe.tsx +269 -0
- package/src/app/chat/right-rail/index.ts +1 -0
- package/src/app/chat/right-rail/preview-console-state.ts +82 -0
- package/src/app/chat/right-rail/preview-console.tsx +290 -0
- package/src/app/chat/right-rail/preview-file.tsx +559 -0
- package/src/app/chat/right-rail/preview-pane.test.tsx +43 -0
- package/src/app/chat/right-rail/preview-pane.tsx +657 -0
- package/src/app/chat/right-rail/preview.tsx +171 -0
- package/src/app/chat/scroll-to-bottom-button.test.tsx +67 -0
- package/src/app/chat/scroll-to-bottom-button.tsx +74 -0
- package/src/app/chat/sidebar/cron-jobs-section.tsx +325 -0
- package/src/app/chat/sidebar/index.tsx +1219 -0
- package/src/app/chat/sidebar/load-more-row.tsx +30 -0
- package/src/app/chat/sidebar/order.test.ts +21 -0
- package/src/app/chat/sidebar/order.ts +17 -0
- package/src/app/chat/sidebar/profile-switcher.tsx +516 -0
- package/src/app/chat/sidebar/session-actions-menu.tsx +264 -0
- package/src/app/chat/sidebar/session-row.tsx +257 -0
- package/src/app/chat/sidebar/virtual-session-list.tsx +154 -0
- package/src/app/chat/sidebar/workspace-groups.test.ts +149 -0
- package/src/app/chat/sidebar/workspace-groups.ts +326 -0
- package/src/app/chat/thread-loading.test.ts +34 -0
- package/src/app/chat/thread-loading.ts +26 -0
- package/src/app/command-center/index.tsx +654 -0
- package/src/app/command-palette/index.tsx +513 -0
- package/src/app/command-palette/marketplace-theme-page.tsx +157 -0
- package/src/app/cron/index.tsx +942 -0
- package/src/app/cron/job-state.ts +29 -0
- package/src/app/desktop-controller.tsx +938 -0
- package/src/app/floating-hud.ts +22 -0
- package/src/app/gateway/hooks/use-gateway-boot.test.tsx +265 -0
- package/src/app/gateway/hooks/use-gateway-boot.ts +387 -0
- package/src/app/gateway/hooks/use-gateway-request.ts +138 -0
- package/src/app/hooks/use-keybinds.ts +186 -0
- package/src/app/hooks/use-refresh-hotkey.ts +45 -0
- package/src/app/hooks/use-route-enum-param.ts +38 -0
- package/src/app/index.tsx +1 -0
- package/src/app/layout-constants.ts +13 -0
- package/src/app/messaging/index.tsx +648 -0
- package/src/app/messaging/platform-icon.tsx +93 -0
- package/src/app/model-picker-overlay.tsx +42 -0
- package/src/app/model-visibility-overlay.tsx +31 -0
- package/src/app/overlays/overlay-chrome.tsx +66 -0
- package/src/app/overlays/overlay-search-input.tsx +33 -0
- package/src/app/overlays/overlay-split-layout.tsx +130 -0
- package/src/app/overlays/overlay-view.tsx +91 -0
- package/src/app/page-search-shell.tsx +75 -0
- package/src/app/profiles/create-profile-dialog.tsx +154 -0
- package/src/app/profiles/delete-profile-dialog.tsx +65 -0
- package/src/app/profiles/index.tsx +671 -0
- package/src/app/profiles/rename-profile-dialog.tsx +125 -0
- package/src/app/right-sidebar/files/dnd-manager.ts +27 -0
- package/src/app/right-sidebar/files/ipc.test.ts +100 -0
- package/src/app/right-sidebar/files/ipc.ts +161 -0
- package/src/app/right-sidebar/files/remote-picker.tsx +177 -0
- package/src/app/right-sidebar/files/tree.tsx +224 -0
- package/src/app/right-sidebar/files/use-project-tree.test.ts +190 -0
- package/src/app/right-sidebar/files/use-project-tree.ts +268 -0
- package/src/app/right-sidebar/index.test.tsx +75 -0
- package/src/app/right-sidebar/index.tsx +395 -0
- package/src/app/right-sidebar/store.ts +15 -0
- package/src/app/right-sidebar/terminal/buffer.ts +65 -0
- package/src/app/right-sidebar/terminal/index.tsx +98 -0
- package/src/app/right-sidebar/terminal/persistent.tsx +122 -0
- package/src/app/right-sidebar/terminal/selection.ts +75 -0
- package/src/app/right-sidebar/terminal/use-terminal-session.ts +504 -0
- package/src/app/routes.ts +88 -0
- package/src/app/session/hooks/use-context-suggestions.ts +58 -0
- package/src/app/session/hooks/use-cwd-actions.ts +109 -0
- package/src/app/session/hooks/use-message-stream.ts +957 -0
- package/src/app/session/hooks/use-model-controls.test.tsx +198 -0
- package/src/app/session/hooks/use-model-controls.ts +106 -0
- package/src/app/session/hooks/use-nastech-config.ts +74 -0
- package/src/app/session/hooks/use-preview-routing.test.tsx +168 -0
- package/src/app/session/hooks/use-preview-routing.ts +223 -0
- package/src/app/session/hooks/use-prompt-actions.test.tsx +316 -0
- package/src/app/session/hooks/use-prompt-actions.ts +1030 -0
- package/src/app/session/hooks/use-route-resume.test.tsx +136 -0
- package/src/app/session/hooks/use-route-resume.ts +115 -0
- package/src/app/session/hooks/use-session-actions.test.tsx +119 -0
- package/src/app/session/hooks/use-session-actions.ts +885 -0
- package/src/app/session/hooks/use-session-state-cache.test.tsx +118 -0
- package/src/app/session/hooks/use-session-state-cache.ts +191 -0
- package/src/app/session-picker-overlay.tsx +32 -0
- package/src/app/session-switcher.tsx +107 -0
- package/src/app/settings/about-settings.tsx +173 -0
- package/src/app/settings/appearance-settings.tsx +162 -0
- package/src/app/settings/config-settings.tsx +384 -0
- package/src/app/settings/constants.ts +545 -0
- package/src/app/settings/credential-key-ui.tsx +373 -0
- package/src/app/settings/env-credentials.tsx +198 -0
- package/src/app/settings/env-var-actions-menu.tsx +136 -0
- package/src/app/settings/field-copy.ts +56 -0
- package/src/app/settings/gateway-settings.tsx +620 -0
- package/src/app/settings/helpers.test.ts +138 -0
- package/src/app/settings/helpers.ts +151 -0
- package/src/app/settings/index.tsx +237 -0
- package/src/app/settings/keys-settings.tsx +96 -0
- package/src/app/settings/mcp-settings.tsx +271 -0
- package/src/app/settings/model-settings.test.tsx +157 -0
- package/src/app/settings/model-settings.tsx +559 -0
- package/src/app/settings/notifications-settings.tsx +150 -0
- package/src/app/settings/primitives.tsx +115 -0
- package/src/app/settings/providers-settings.test.tsx +100 -0
- package/src/app/settings/providers-settings.tsx +258 -0
- package/src/app/settings/sessions-settings.tsx +276 -0
- package/src/app/settings/toolset-config-panel.test.tsx +289 -0
- package/src/app/settings/toolset-config-panel.tsx +449 -0
- package/src/app/settings/types.ts +42 -0
- package/src/app/settings/uninstall-section.tsx +185 -0
- package/src/app/settings/use-deep-link-highlight.ts +60 -0
- package/src/app/shell/app-shell.tsx +167 -0
- package/src/app/shell/gateway-menu-panel.tsx +150 -0
- package/src/app/shell/hooks/use-overlay-routing.ts +71 -0
- package/src/app/shell/hooks/use-status-snapshot.ts +57 -0
- package/src/app/shell/hooks/use-statusbar-items.tsx +403 -0
- package/src/app/shell/keybind-panel.tsx +220 -0
- package/src/app/shell/model-edit-submenu.test.tsx +84 -0
- package/src/app/shell/model-edit-submenu.tsx +245 -0
- package/src/app/shell/model-menu-panel.tsx +295 -0
- package/src/app/shell/sidebar-label.tsx +22 -0
- package/src/app/shell/statusbar-controls.tsx +185 -0
- package/src/app/shell/titlebar-controls.tsx +244 -0
- package/src/app/shell/titlebar.test.ts +26 -0
- package/src/app/shell/titlebar.ts +45 -0
- package/src/app/shell/use-group-registry.ts +39 -0
- package/src/app/skills/index.test.tsx +103 -0
- package/src/app/skills/index.tsx +371 -0
- package/src/app/types.ts +99 -0
- package/src/app/updates-overlay.tsx +369 -0
- package/src/components/Backdrop.tsx +114 -0
- package/src/components/assistant-ui/ansi-text.tsx +34 -0
- package/src/components/assistant-ui/clarify-tool.tsx +281 -0
- package/src/components/assistant-ui/directive-text.test.ts +39 -0
- package/src/components/assistant-ui/directive-text.tsx +389 -0
- package/src/components/assistant-ui/markdown-text.test.ts +204 -0
- package/src/components/assistant-ui/markdown-text.tsx +497 -0
- package/src/components/assistant-ui/message-render-boundary.test.tsx +80 -0
- package/src/components/assistant-ui/message-render-boundary.tsx +48 -0
- package/src/components/assistant-ui/streaming.test.tsx +739 -0
- package/src/components/assistant-ui/thread-list.tsx +307 -0
- package/src/components/assistant-ui/thread-virtualizer.tsx +512 -0
- package/src/components/assistant-ui/thread.tsx +1474 -0
- package/src/components/assistant-ui/todo-tool.tsx +109 -0
- package/src/components/assistant-ui/tool-approval-group.test.tsx +158 -0
- package/src/components/assistant-ui/tool-approval.test.tsx +81 -0
- package/src/components/assistant-ui/tool-approval.tsx +209 -0
- package/src/components/assistant-ui/tool-fallback-model.test.ts +66 -0
- package/src/components/assistant-ui/tool-fallback-model.ts +1368 -0
- package/src/components/assistant-ui/tool-fallback.tsx +466 -0
- package/src/components/assistant-ui/tooltip-icon-button.tsx +33 -0
- package/src/components/assistant-ui/user-message-edit.test.tsx +141 -0
- package/src/components/assistant-ui/user-message-text.tsx +150 -0
- package/src/components/boot-failure-overlay.tsx +246 -0
- package/src/components/boot-failure-reauth.test.ts +100 -0
- package/src/components/boot-failure-reauth.ts +81 -0
- package/src/components/brand-mark.tsx +19 -0
- package/src/components/chat/activity-timer-text.tsx +24 -0
- package/src/components/chat/activity-timer.test.tsx +43 -0
- package/src/components/chat/activity-timer.ts +64 -0
- package/src/components/chat/code-card.tsx +78 -0
- package/src/components/chat/compact-markdown.tsx +113 -0
- package/src/components/chat/composer-dock.ts +31 -0
- package/src/components/chat/diff-lines.tsx +54 -0
- package/src/components/chat/disclosure-row.tsx +63 -0
- package/src/components/chat/generated-image-context.tsx +19 -0
- package/src/components/chat/generated-image-result.tsx +174 -0
- package/src/components/chat/image-generation-placeholder.tsx +279 -0
- package/src/components/chat/intro-copy.jsonl +75 -0
- package/src/components/chat/intro.tsx +182 -0
- package/src/components/chat/preview-attachment.tsx +125 -0
- package/src/components/chat/shiki-highlighter.tsx +107 -0
- package/src/components/chat/status-row.tsx +70 -0
- package/src/components/chat/status-section.tsx +42 -0
- package/src/components/chat/terminal-output.tsx +50 -0
- package/src/components/chat/zoomable-image.tsx +177 -0
- package/src/components/desktop-install-overlay.tsx +595 -0
- package/src/components/desktop-onboarding-overlay.test.tsx +100 -0
- package/src/components/desktop-onboarding-overlay.tsx +1286 -0
- package/src/components/error-boundary.tsx +77 -0
- package/src/components/gateway-connecting-overlay.test.tsx +143 -0
- package/src/components/gateway-connecting-overlay.tsx +183 -0
- package/src/components/haptics-provider.tsx +19 -0
- package/src/components/language-switcher.test.tsx +53 -0
- package/src/components/language-switcher.tsx +175 -0
- package/src/components/model-picker.tsx +340 -0
- package/src/components/model-visibility-dialog.tsx +155 -0
- package/src/components/notifications.tsx +196 -0
- package/src/components/page-loader.tsx +34 -0
- package/src/components/pane-shell/context.ts +14 -0
- package/src/components/pane-shell/index.ts +4 -0
- package/src/components/pane-shell/pane-shell.test.tsx +333 -0
- package/src/components/pane-shell/pane-shell.tsx +330 -0
- package/src/components/prompt-overlays.tsx +234 -0
- package/src/components/session-picker.tsx +108 -0
- package/src/components/status-dot.tsx +26 -0
- package/src/components/ui/action-status.tsx +25 -0
- package/src/components/ui/alert.tsx +53 -0
- package/src/components/ui/badge.tsx +35 -0
- package/src/components/ui/braille-spinner.tsx +61 -0
- package/src/components/ui/button.tsx +81 -0
- package/src/components/ui/checkbox.tsx +27 -0
- package/src/components/ui/codicon.tsx +20 -0
- package/src/components/ui/command.tsx +111 -0
- package/src/components/ui/confirm-dialog.tsx +109 -0
- package/src/components/ui/context-menu.tsx +141 -0
- package/src/components/ui/control.ts +25 -0
- package/src/components/ui/copy-button.test.tsx +36 -0
- package/src/components/ui/copy-button.tsx +229 -0
- package/src/components/ui/dialog.tsx +152 -0
- package/src/components/ui/disclosure-caret.tsx +20 -0
- package/src/components/ui/dropdown-menu.tsx +291 -0
- package/src/components/ui/error-state.tsx +50 -0
- package/src/components/ui/fade-text.tsx +110 -0
- package/src/components/ui/glyph-spinner.tsx +63 -0
- package/src/components/ui/input.tsx +22 -0
- package/src/components/ui/kbd.tsx +37 -0
- package/src/components/ui/loader.tsx +558 -0
- package/src/components/ui/log-view.tsx +17 -0
- package/src/components/ui/pagination.tsx +114 -0
- package/src/components/ui/popover.tsx +44 -0
- package/src/components/ui/scroll-area.tsx +43 -0
- package/src/components/ui/search-field.tsx +80 -0
- package/src/components/ui/segmented-control.tsx +51 -0
- package/src/components/ui/select.tsx +92 -0
- package/src/components/ui/separator.tsx +26 -0
- package/src/components/ui/sheet.tsx +116 -0
- package/src/components/ui/sidebar.tsx +674 -0
- package/src/components/ui/skeleton.tsx +7 -0
- package/src/components/ui/switch.tsx +49 -0
- package/src/components/ui/tabs.tsx +36 -0
- package/src/components/ui/text-tab.tsx +43 -0
- package/src/components/ui/textarea.tsx +11 -0
- package/src/components/ui/tool-icon.tsx +65 -0
- package/src/components/ui/tooltip.tsx +69 -0
- package/src/fonts/JetBrainsMono-Bold.woff2 +0 -0
- package/src/fonts/JetBrainsMono-Italic.woff2 +0 -0
- package/src/fonts/JetBrainsMono-Regular.woff2 +0 -0
- package/src/global.d.ts +457 -0
- package/src/hooks/use-image-download.ts +85 -0
- package/src/hooks/use-media-query.ts +24 -0
- package/src/hooks/use-mobile.ts +3 -0
- package/src/hooks/use-resize-observer.ts +38 -0
- package/src/hooks/use-worktree-info.ts +68 -0
- package/src/i18n/catalog.ts +12 -0
- package/src/i18n/context.test.tsx +232 -0
- package/src/i18n/context.tsx +183 -0
- package/src/i18n/define-locale.ts +41 -0
- package/src/i18n/en.ts +1779 -0
- package/src/i18n/index.ts +20 -0
- package/src/i18n/ja.ts +1890 -0
- package/src/i18n/languages.test.ts +43 -0
- package/src/i18n/languages.ts +86 -0
- package/src/i18n/runtime.test.ts +75 -0
- package/src/i18n/runtime.ts +53 -0
- package/src/i18n/types.ts +1452 -0
- package/src/i18n/zh-hant.ts +1849 -0
- package/src/i18n/zh.ts +1923 -0
- package/src/lib/ansi.test.ts +123 -0
- package/src/lib/ansi.ts +175 -0
- package/src/lib/chat-messages.test.ts +708 -0
- package/src/lib/chat-messages.ts +885 -0
- package/src/lib/chat-runtime.test.ts +18 -0
- package/src/lib/chat-runtime.ts +335 -0
- package/src/lib/clipboard.ts +28 -0
- package/src/lib/commit-changelog.test.ts +114 -0
- package/src/lib/commit-changelog.ts +177 -0
- package/src/lib/completion-sound.ts +519 -0
- package/src/lib/desktop-fs.test.ts +116 -0
- package/src/lib/desktop-fs.ts +113 -0
- package/src/lib/desktop-slash-commands.test.ts +126 -0
- package/src/lib/desktop-slash-commands.ts +286 -0
- package/src/lib/embedded-images.test.ts +35 -0
- package/src/lib/embedded-images.ts +60 -0
- package/src/lib/external-link.test.tsx +168 -0
- package/src/lib/external-link.tsx +303 -0
- package/src/lib/gateway-events.test.ts +27 -0
- package/src/lib/gateway-events.ts +49 -0
- package/src/lib/gateway-ws-url.test.ts +78 -0
- package/src/lib/gateway-ws-url.ts +91 -0
- package/src/lib/generated-images.test.ts +97 -0
- package/src/lib/generated-images.ts +116 -0
- package/src/lib/haptics.ts +129 -0
- package/src/lib/icons.ts +203 -0
- package/src/lib/incremental-external-store-runtime.ts +188 -0
- package/src/lib/katex-memo.ts +260 -0
- package/src/lib/keybinds/actions.ts +125 -0
- package/src/lib/keybinds/combo.test.ts +86 -0
- package/src/lib/keybinds/combo.ts +169 -0
- package/src/lib/local-preview.ts +126 -0
- package/src/lib/markdown-code.test.ts +23 -0
- package/src/lib/markdown-code.ts +195 -0
- package/src/lib/markdown-preprocess.ts +386 -0
- package/src/lib/media.remote.test.ts +58 -0
- package/src/lib/media.ts +111 -0
- package/src/lib/model-status-label.test.ts +31 -0
- package/src/lib/model-status-label.ts +103 -0
- package/src/lib/mutable-ref.ts +6 -0
- package/src/lib/preview-targets.test.ts +27 -0
- package/src/lib/preview-targets.ts +63 -0
- package/src/lib/profile-color.ts +58 -0
- package/src/lib/provider-setup-errors.test.ts +26 -0
- package/src/lib/provider-setup-errors.ts +12 -0
- package/src/lib/query-client.ts +13 -0
- package/src/lib/remend-tail.test.ts +105 -0
- package/src/lib/remend-tail.ts +108 -0
- package/src/lib/runtime-readiness.test.ts +65 -0
- package/src/lib/runtime-readiness.ts +147 -0
- package/src/lib/session-export.ts +57 -0
- package/src/lib/session-search.test.ts +58 -0
- package/src/lib/session-search.ts +19 -0
- package/src/lib/session-source.ts +62 -0
- package/src/lib/speech-text.ts +35 -0
- package/src/lib/statusbar.ts +91 -0
- package/src/lib/storage.test.ts +25 -0
- package/src/lib/storage.ts +107 -0
- package/src/lib/todos.test.ts +35 -0
- package/src/lib/todos.ts +51 -0
- package/src/lib/tool-result-summary.test.ts +106 -0
- package/src/lib/tool-result-summary.ts +467 -0
- package/src/lib/update-copy.test.ts +38 -0
- package/src/lib/update-copy.ts +44 -0
- package/src/lib/use-enter-animation.ts +100 -0
- package/src/lib/utils.ts +6 -0
- package/src/lib/voice-playback.ts +128 -0
- package/src/lib/yolo-session.ts +26 -0
- package/src/main.tsx +43 -0
- package/src/nastech.test.ts +49 -0
- package/src/nastech.ts +718 -0
- package/src/store/activity.ts +100 -0
- package/src/store/boot.ts +91 -0
- package/src/store/clarify.test.ts +81 -0
- package/src/store/clarify.ts +69 -0
- package/src/store/command-palette.ts +20 -0
- package/src/store/compaction.test.ts +53 -0
- package/src/store/compaction.ts +38 -0
- package/src/store/completion-sound.ts +32 -0
- package/src/store/composer-input-history.test.ts +147 -0
- package/src/store/composer-input-history.ts +158 -0
- package/src/store/composer-queue.test.ts +148 -0
- package/src/store/composer-queue.ts +239 -0
- package/src/store/composer-status.test.ts +99 -0
- package/src/store/composer-status.ts +277 -0
- package/src/store/composer.test.ts +106 -0
- package/src/store/composer.ts +184 -0
- package/src/store/cron.ts +19 -0
- package/src/store/gateway.ts +290 -0
- package/src/store/haptics.ts +17 -0
- package/src/store/keybinds.ts +139 -0
- package/src/store/layout.ts +176 -0
- package/src/store/model-presets.test.ts +51 -0
- package/src/store/model-presets.ts +86 -0
- package/src/store/model-visibility.test.ts +37 -0
- package/src/store/model-visibility.ts +108 -0
- package/src/store/native-notifications.test.ts +192 -0
- package/src/store/native-notifications.ts +203 -0
- package/src/store/notifications.ts +165 -0
- package/src/store/onboarding.test.ts +372 -0
- package/src/store/onboarding.ts +866 -0
- package/src/store/panes.test.ts +146 -0
- package/src/store/panes.ts +145 -0
- package/src/store/preview.test.ts +135 -0
- package/src/store/preview.ts +466 -0
- package/src/store/profile.test.ts +89 -0
- package/src/store/profile.ts +365 -0
- package/src/store/prompts.test.ts +121 -0
- package/src/store/prompts.ts +115 -0
- package/src/store/session-switcher.test.ts +115 -0
- package/src/store/session-switcher.ts +128 -0
- package/src/store/session-sync.ts +25 -0
- package/src/store/session.test.ts +131 -0
- package/src/store/session.ts +255 -0
- package/src/store/subagents.test.ts +111 -0
- package/src/store/subagents.ts +260 -0
- package/src/store/thread-scroll.ts +46 -0
- package/src/store/todos.test.ts +47 -0
- package/src/store/todos.ts +64 -0
- package/src/store/tool-diffs.ts +23 -0
- package/src/store/tool-dismiss.ts +45 -0
- package/src/store/tool-view.ts +91 -0
- package/src/store/translucency.ts +38 -0
- package/src/store/updates.test.ts +77 -0
- package/src/store/updates.ts +315 -0
- package/src/store/voice-playback.ts +24 -0
- package/src/store/windows.test.ts +143 -0
- package/src/store/windows.ts +77 -0
- package/src/styles.css +1235 -0
- package/src/themes/color.ts +142 -0
- package/src/themes/context.tsx +339 -0
- package/src/themes/index.ts +3 -0
- package/src/themes/install.test.ts +119 -0
- package/src/themes/install.ts +95 -0
- package/src/themes/presets.test.ts +33 -0
- package/src/themes/presets.ts +293 -0
- package/src/themes/profile-theme.test.ts +41 -0
- package/src/themes/types.ts +66 -0
- package/src/themes/use-skin-command.ts +60 -0
- package/src/themes/user-themes.test.ts +63 -0
- package/src/themes/user-themes.ts +122 -0
- package/src/themes/vscode.test.ts +171 -0
- package/src/themes/vscode.ts +343 -0
- package/src/types/nastech.ts +646 -0
- package/src/vite-env.d.ts +1 -0
- package/tsconfig.json +25 -0
- package/vite.config.ts +56 -0
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Install desktop themes from external sources.
|
|
3
|
+
*
|
|
4
|
+
* The heavy lifting (network + .vsix unzip) lives in the Electron main process
|
|
5
|
+
* (`electron/vscode-marketplace.cjs`), reached via `window.NASTECHDesktop.themes`.
|
|
6
|
+
* Main hands back the raw theme JSON; we parse + convert + persist here so the
|
|
7
|
+
* conversion stays in one unit-testable place.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { DesktopMarketplaceThemeResult } from '@/global'
|
|
11
|
+
|
|
12
|
+
import type { DesktopTheme } from './types'
|
|
13
|
+
import { installUserTheme } from './user-themes'
|
|
14
|
+
import { convertVscodeColorTheme, parseVscodeTheme, vscodeThemeSlug } from './vscode'
|
|
15
|
+
|
|
16
|
+
/** A `publisher.extension` id, e.g. `dracula-theme.theme-dracula`. */
|
|
17
|
+
export const MARKETPLACE_ID_RE = /^[\w-]+\.[\w-]+$/
|
|
18
|
+
|
|
19
|
+
/** Parse + convert + persist a pasted VS Code theme JSON. */
|
|
20
|
+
export function installVscodeThemeFromText(
|
|
21
|
+
text: string,
|
|
22
|
+
opts?: { label?: string; source?: string }
|
|
23
|
+
): DesktopTheme {
|
|
24
|
+
const raw = parseVscodeTheme(text)
|
|
25
|
+
const { theme } = convertVscodeColorTheme(raw, opts)
|
|
26
|
+
|
|
27
|
+
return installUserTheme(theme)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Fold every color theme an extension contributes into ONE desktop theme family.
|
|
32
|
+
*
|
|
33
|
+
* Many extensions ship a light *and* a dark variant (GitHub, Solarized, Winter
|
|
34
|
+
* is Coming…). Rather than install them as separate flat entries — which made
|
|
35
|
+
* the light/dark toggle a no-op and let "install in dark mode" land on the light
|
|
36
|
+
* variant — we map the first light variant onto `colors` and the first dark
|
|
37
|
+
* variant onto `darkColors`. The result is a single picker entry whose light/dark
|
|
38
|
+
* toggle switches between the real variants. A single-variant extension fills
|
|
39
|
+
* both slots with its one palette (the toggle is a no-op, as it must be).
|
|
40
|
+
*/
|
|
41
|
+
export function buildThemeFromMarketplace(result: DesktopMarketplaceThemeResult): DesktopTheme {
|
|
42
|
+
if (!result.themes.length) {
|
|
43
|
+
throw new Error(`"${result.extensionId}" does not contribute any color themes.`)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const variants = result.themes.map(file => {
|
|
47
|
+
const raw = parseVscodeTheme(file.contents)
|
|
48
|
+
const label = file.label || raw.name || result.displayName
|
|
49
|
+
const { mode, theme } = convertVscodeColorTheme(raw, { label, source: result.extensionId })
|
|
50
|
+
|
|
51
|
+
return { mode, palette: theme.colors, terminal: theme.terminal }
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
const fallback = variants[0]
|
|
55
|
+
const light = variants.find(variant => variant.mode === 'light') ?? fallback
|
|
56
|
+
const dark = variants.find(variant => variant.mode === 'dark') ?? fallback
|
|
57
|
+
|
|
58
|
+
// The terminal ANSI palette tracks the painted variant the same way colors do
|
|
59
|
+
// (light → terminal, dark → darkTerminal); each falls back to the other so a
|
|
60
|
+
// single-variant import still themes the terminal in both modes.
|
|
61
|
+
const terminal = light.terminal ?? dark.terminal
|
|
62
|
+
const darkTerminal = dark.terminal ?? light.terminal
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
name: vscodeThemeSlug(result.displayName),
|
|
66
|
+
label: result.displayName,
|
|
67
|
+
description: `VS Code · ${result.extensionId}`,
|
|
68
|
+
colors: light.palette,
|
|
69
|
+
darkColors: dark.palette,
|
|
70
|
+
...(terminal ? { terminal } : {}),
|
|
71
|
+
...(darkTerminal ? { darkTerminal } : {})
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Download a Marketplace extension and install the theme family it contributes
|
|
77
|
+
* (see `buildThemeFromMarketplace`). Returns the single installed theme.
|
|
78
|
+
*/
|
|
79
|
+
export async function installVscodeThemeFromMarketplace(id: string): Promise<DesktopTheme> {
|
|
80
|
+
const trimmed = id.trim()
|
|
81
|
+
|
|
82
|
+
if (!MARKETPLACE_ID_RE.test(trimmed)) {
|
|
83
|
+
throw new Error('Expected a Marketplace id like "publisher.extension".')
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const api = window.NASTECHDesktop?.themes
|
|
87
|
+
|
|
88
|
+
if (!api?.fetchMarketplace) {
|
|
89
|
+
throw new Error('Marketplace install is only available in the desktop app.')
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const result = await api.fetchMarketplace(trimmed)
|
|
93
|
+
|
|
94
|
+
return installUserTheme(buildThemeFromMarketplace(result))
|
|
95
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import { BUILTIN_THEME_LIST, DEFAULT_TYPOGRAPHY, EMOJI_FALLBACK } from './presets'
|
|
4
|
+
|
|
5
|
+
// #40364: none of the UI text/mono fonts carry emoji glyphs, so every font
|
|
6
|
+
// stack must end with a color-emoji fallback or emoji render as tofu on
|
|
7
|
+
// platforms whose default font lacks them (e.g. Linux).
|
|
8
|
+
describe('theme typography emoji fallback (#40364)', () => {
|
|
9
|
+
const stacks: Array<[string, string]> = [
|
|
10
|
+
['DEFAULT_TYPOGRAPHY.fontSans', DEFAULT_TYPOGRAPHY.fontSans],
|
|
11
|
+
['DEFAULT_TYPOGRAPHY.fontMono', DEFAULT_TYPOGRAPHY.fontMono],
|
|
12
|
+
// A theme may override only fontMono (fontSans then falls back to the
|
|
13
|
+
// default, which already carries the emoji stack), so skip undefined.
|
|
14
|
+
...BUILTIN_THEME_LIST.flatMap(theme =>
|
|
15
|
+
(
|
|
16
|
+
[
|
|
17
|
+
[`${theme.name}.fontSans`, theme.typography?.fontSans],
|
|
18
|
+
[`${theme.name}.fontMono`, theme.typography?.fontMono]
|
|
19
|
+
] as Array<[string, string | undefined]>
|
|
20
|
+
).filter((entry): entry is [string, string] => typeof entry[1] === 'string')
|
|
21
|
+
)
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
it.each(stacks)('%s includes a color-emoji font', (_label, stack) => {
|
|
25
|
+
expect(stack).toMatch(/Apple Color Emoji|Segoe UI Emoji|Noto Color Emoji|(^|,\s*)emoji\b/)
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
it('EMOJI_FALLBACK lists the major platform emoji fonts', () => {
|
|
29
|
+
expect(EMOJI_FALLBACK).toContain('Apple Color Emoji')
|
|
30
|
+
expect(EMOJI_FALLBACK).toContain('Segoe UI Emoji')
|
|
31
|
+
expect(EMOJI_FALLBACK).toContain('Noto Color Emoji')
|
|
32
|
+
})
|
|
33
|
+
})
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Built-in desktop themes. Names match the CLI skins / dashboard presets.
|
|
3
|
+
* Add new themes here — no code changes needed elsewhere.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { DesktopTheme, DesktopThemeTypography } from './types'
|
|
7
|
+
|
|
8
|
+
// Color-emoji fonts to append to every stack as a last resort. None of the UI
|
|
9
|
+
// text/mono fonts carry emoji glyphs, so without this emoji render as tofu
|
|
10
|
+
// boxes on platforms whose default text font lacks them (e.g. Linux/#40364).
|
|
11
|
+
// Covers macOS, Windows, Linux, plus the `emoji` generic for anything else.
|
|
12
|
+
export const EMOJI_FALLBACK =
|
|
13
|
+
'"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji", emoji'
|
|
14
|
+
|
|
15
|
+
const SYSTEM_SANS =
|
|
16
|
+
'"Segoe WPC", "Segoe UI", -apple-system, BlinkMacSystemFont, "SF Pro Text", "SF Pro Display", system-ui, sans-serif, ' +
|
|
17
|
+
EMOJI_FALLBACK
|
|
18
|
+
|
|
19
|
+
const SYSTEM_MONO =
|
|
20
|
+
'"Cascadia Code", "JetBrains Mono", "SF Mono", ui-monospace, Menlo, Monaco, Consolas, monospace, ' + EMOJI_FALLBACK
|
|
21
|
+
|
|
22
|
+
export const DEFAULT_TYPOGRAPHY: DesktopThemeTypography = { fontSans: SYSTEM_SANS, fontMono: SYSTEM_MONO }
|
|
23
|
+
|
|
24
|
+
const NASTECH_BLUE = '#0053FD'
|
|
25
|
+
const PSYCHE_BLUE = '#1540B1'
|
|
26
|
+
const PSYCHE_WARM = '#FFE6CB'
|
|
27
|
+
|
|
28
|
+
const nastechTint = (pct: number) => `color-mix(in srgb, ${NASTECH_BLUE} ${pct}%, #FFFFFF)`
|
|
29
|
+
const nastechTintTransparent = (pct: number) => `color-mix(in srgb, ${NASTECH_BLUE} ${pct}%, transparent)`
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* NasTech — canonical NasTech desktop identity. The palette keeps the current
|
|
33
|
+
* glass geometry neutral, then lets the old bb/gui blue and psyche cream
|
|
34
|
+
* return as accent seeds.
|
|
35
|
+
*/
|
|
36
|
+
export const nousTheme: DesktopTheme = {
|
|
37
|
+
name: 'nastech',
|
|
38
|
+
label: 'NasTech',
|
|
39
|
+
description: 'Glass neutrals with NasTech blue accents',
|
|
40
|
+
colors: {
|
|
41
|
+
background: '#F8FAFF',
|
|
42
|
+
foreground: '#17171A',
|
|
43
|
+
card: '#FFFFFF',
|
|
44
|
+
cardForeground: '#17171A',
|
|
45
|
+
muted: nastechTint(5),
|
|
46
|
+
mutedForeground: '#666678',
|
|
47
|
+
popover: '#FFFFFF',
|
|
48
|
+
popoverForeground: '#17171A',
|
|
49
|
+
primary: NASTECH_BLUE,
|
|
50
|
+
primaryForeground: '#FCFCFC',
|
|
51
|
+
secondary: nastechTint(7),
|
|
52
|
+
secondaryForeground: '#242432',
|
|
53
|
+
accent: nastechTint(10),
|
|
54
|
+
accentForeground: '#202030',
|
|
55
|
+
border: nastechTintTransparent(22),
|
|
56
|
+
input: nastechTintTransparent(30),
|
|
57
|
+
ring: NASTECH_BLUE,
|
|
58
|
+
midground: NASTECH_BLUE,
|
|
59
|
+
composerRing: NASTECH_BLUE,
|
|
60
|
+
destructive: '#C72E4D',
|
|
61
|
+
destructiveForeground: '#FFFFFF',
|
|
62
|
+
sidebarBackground: '#F3F7FF',
|
|
63
|
+
sidebarBorder: nastechTintTransparent(18),
|
|
64
|
+
userBubble: nastechTint(6),
|
|
65
|
+
userBubbleBorder: nastechTintTransparent(24)
|
|
66
|
+
},
|
|
67
|
+
darkColors: {
|
|
68
|
+
background: '#0D2F86',
|
|
69
|
+
foreground: PSYCHE_WARM,
|
|
70
|
+
card: '#12378F',
|
|
71
|
+
cardForeground: PSYCHE_WARM,
|
|
72
|
+
muted: '#183F9A',
|
|
73
|
+
mutedForeground: '#B5C7F3',
|
|
74
|
+
popover: '#123A96',
|
|
75
|
+
popoverForeground: PSYCHE_WARM,
|
|
76
|
+
primary: PSYCHE_WARM,
|
|
77
|
+
primaryForeground: '#0D2F86',
|
|
78
|
+
secondary: '#1B45A4',
|
|
79
|
+
secondaryForeground: '#E0E8FF',
|
|
80
|
+
accent: PSYCHE_BLUE,
|
|
81
|
+
accentForeground: '#F0F4FF',
|
|
82
|
+
border: '#3158AD',
|
|
83
|
+
input: '#0B2566',
|
|
84
|
+
ring: PSYCHE_WARM,
|
|
85
|
+
midground: NASTECH_BLUE,
|
|
86
|
+
composerRing: PSYCHE_WARM,
|
|
87
|
+
destructive: '#C0473A',
|
|
88
|
+
destructiveForeground: '#FEF2F2',
|
|
89
|
+
sidebarBackground: '#09286F',
|
|
90
|
+
sidebarBorder: '#234A9C',
|
|
91
|
+
userBubble: '#143B91',
|
|
92
|
+
userBubbleBorder: '#3A63BD'
|
|
93
|
+
},
|
|
94
|
+
typography: {
|
|
95
|
+
fontSans: SYSTEM_SANS,
|
|
96
|
+
fontMono: `"Courier Prime", ${SYSTEM_MONO}`,
|
|
97
|
+
fontUrl: 'https://fonts.googleapis.com/css2?family=Courier+Prime:wght@400;700&display=swap'
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Deep blue-violet with cool accents. Matches the dashboard midnight theme. */
|
|
102
|
+
export const midnightTheme: DesktopTheme = {
|
|
103
|
+
name: 'midnight',
|
|
104
|
+
label: 'Midnight',
|
|
105
|
+
description: 'Deep blue-violet with cool accents',
|
|
106
|
+
colors: {
|
|
107
|
+
background: '#08081c',
|
|
108
|
+
foreground: '#ddd6ff',
|
|
109
|
+
card: '#0d0d28',
|
|
110
|
+
cardForeground: '#ddd6ff',
|
|
111
|
+
muted: '#13133a',
|
|
112
|
+
mutedForeground: '#7c7ab0',
|
|
113
|
+
popover: '#0f0f2e',
|
|
114
|
+
popoverForeground: '#ddd6ff',
|
|
115
|
+
primary: '#ddd6ff',
|
|
116
|
+
primaryForeground: '#08081c',
|
|
117
|
+
secondary: '#1a1a4a',
|
|
118
|
+
secondaryForeground: '#c4bff0',
|
|
119
|
+
accent: '#1a1a44',
|
|
120
|
+
accentForeground: '#d0c8ff',
|
|
121
|
+
border: '#1e1e52',
|
|
122
|
+
input: '#1e1e52',
|
|
123
|
+
ring: '#8b80e8',
|
|
124
|
+
midground: '#8b80e8',
|
|
125
|
+
destructive: '#b03060',
|
|
126
|
+
destructiveForeground: '#fef2f2',
|
|
127
|
+
sidebarBackground: '#06061a',
|
|
128
|
+
sidebarBorder: '#12123a',
|
|
129
|
+
userBubble: '#14143a',
|
|
130
|
+
userBubbleBorder: '#242466'
|
|
131
|
+
},
|
|
132
|
+
typography: {
|
|
133
|
+
fontMono: `"JetBrains Mono", ${SYSTEM_MONO}`,
|
|
134
|
+
fontUrl: 'https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700&display=swap'
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** Warm crimson and bronze — forge vibes. Matches the CLI ares skin. */
|
|
139
|
+
export const emberTheme: DesktopTheme = {
|
|
140
|
+
name: 'ember',
|
|
141
|
+
label: 'Ember',
|
|
142
|
+
description: 'Warm crimson and bronze — forge vibes',
|
|
143
|
+
colors: {
|
|
144
|
+
background: '#160800',
|
|
145
|
+
foreground: '#ffd8b0',
|
|
146
|
+
card: '#1e0e04',
|
|
147
|
+
cardForeground: '#ffd8b0',
|
|
148
|
+
muted: '#2a1408',
|
|
149
|
+
mutedForeground: '#aa7a56',
|
|
150
|
+
popover: '#221008',
|
|
151
|
+
popoverForeground: '#ffd8b0',
|
|
152
|
+
primary: '#ffd8b0',
|
|
153
|
+
primaryForeground: '#160800',
|
|
154
|
+
secondary: '#341800',
|
|
155
|
+
secondaryForeground: '#f0c090',
|
|
156
|
+
accent: '#301600',
|
|
157
|
+
accentForeground: '#e8c080',
|
|
158
|
+
border: '#3a1c08',
|
|
159
|
+
input: '#3a1c08',
|
|
160
|
+
ring: '#d97316',
|
|
161
|
+
midground: '#d97316',
|
|
162
|
+
destructive: '#c43010',
|
|
163
|
+
destructiveForeground: '#fef2f2',
|
|
164
|
+
sidebarBackground: '#100600',
|
|
165
|
+
sidebarBorder: '#2a1004',
|
|
166
|
+
userBubble: '#2a1000',
|
|
167
|
+
userBubbleBorder: '#4a2010'
|
|
168
|
+
},
|
|
169
|
+
typography: {
|
|
170
|
+
fontMono: `"IBM Plex Mono", ${SYSTEM_MONO}`,
|
|
171
|
+
fontUrl: 'https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;700&display=swap'
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** Clean grayscale. Matches the CLI mono skin and dashboard mono theme. */
|
|
176
|
+
export const monoTheme: DesktopTheme = {
|
|
177
|
+
name: 'mono',
|
|
178
|
+
label: 'Mono',
|
|
179
|
+
description: 'Clean grayscale — minimal and focused',
|
|
180
|
+
colors: {
|
|
181
|
+
background: '#0e0e0e',
|
|
182
|
+
foreground: '#eaeaea',
|
|
183
|
+
card: '#141414',
|
|
184
|
+
cardForeground: '#eaeaea',
|
|
185
|
+
muted: '#1e1e1e',
|
|
186
|
+
mutedForeground: '#808080',
|
|
187
|
+
popover: '#181818',
|
|
188
|
+
popoverForeground: '#eaeaea',
|
|
189
|
+
primary: '#eaeaea',
|
|
190
|
+
primaryForeground: '#0e0e0e',
|
|
191
|
+
secondary: '#262626',
|
|
192
|
+
secondaryForeground: '#c8c8c8',
|
|
193
|
+
accent: '#222222',
|
|
194
|
+
accentForeground: '#d8d8d8',
|
|
195
|
+
border: '#2a2a2a',
|
|
196
|
+
input: '#2a2a2a',
|
|
197
|
+
ring: '#9a9a9a',
|
|
198
|
+
midground: '#9a9a9a',
|
|
199
|
+
destructive: '#a84040',
|
|
200
|
+
destructiveForeground: '#fef2f2',
|
|
201
|
+
sidebarBackground: '#0a0a0a',
|
|
202
|
+
sidebarBorder: '#202020',
|
|
203
|
+
userBubble: '#1a1a1a',
|
|
204
|
+
userBubbleBorder: '#363636'
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/** Neon green on black. Matches the CLI cyberpunk skin and dashboard theme. */
|
|
209
|
+
export const cyberpunkTheme: DesktopTheme = {
|
|
210
|
+
name: 'cyberpunk',
|
|
211
|
+
label: 'Cyberpunk',
|
|
212
|
+
description: 'Neon green on black — matrix terminal',
|
|
213
|
+
colors: {
|
|
214
|
+
background: '#000a00',
|
|
215
|
+
foreground: '#00ff41',
|
|
216
|
+
card: '#001200',
|
|
217
|
+
cardForeground: '#00ff41',
|
|
218
|
+
muted: '#001a00',
|
|
219
|
+
mutedForeground: '#1a8a30',
|
|
220
|
+
popover: '#001000',
|
|
221
|
+
popoverForeground: '#00ff41',
|
|
222
|
+
primary: '#00ff41',
|
|
223
|
+
primaryForeground: '#000a00',
|
|
224
|
+
secondary: '#002800',
|
|
225
|
+
secondaryForeground: '#00cc34',
|
|
226
|
+
accent: '#002000',
|
|
227
|
+
accentForeground: '#00e038',
|
|
228
|
+
border: '#003000',
|
|
229
|
+
input: '#003000',
|
|
230
|
+
ring: '#00ff41',
|
|
231
|
+
midground: '#00ff41',
|
|
232
|
+
destructive: '#ff003c',
|
|
233
|
+
destructiveForeground: '#000a00',
|
|
234
|
+
sidebarBackground: '#000600',
|
|
235
|
+
sidebarBorder: '#001800',
|
|
236
|
+
userBubble: '#001400',
|
|
237
|
+
userBubbleBorder: '#004800'
|
|
238
|
+
},
|
|
239
|
+
typography: {
|
|
240
|
+
fontMono: `"Courier New", Courier, monospace, ${EMOJI_FALLBACK}`,
|
|
241
|
+
fontSans: `"Courier New", Courier, monospace, ${EMOJI_FALLBACK}`
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/** Cool slate blue for developers. Matches the CLI slate skin. */
|
|
246
|
+
export const slateTheme: DesktopTheme = {
|
|
247
|
+
name: 'slate',
|
|
248
|
+
label: 'Slate',
|
|
249
|
+
description: 'Cool slate blue — focused developer theme',
|
|
250
|
+
colors: {
|
|
251
|
+
background: '#0d1117',
|
|
252
|
+
foreground: '#c9d1d9',
|
|
253
|
+
card: '#161b22',
|
|
254
|
+
cardForeground: '#c9d1d9',
|
|
255
|
+
muted: '#21262d',
|
|
256
|
+
mutedForeground: '#8b949e',
|
|
257
|
+
popover: '#1c2128',
|
|
258
|
+
popoverForeground: '#c9d1d9',
|
|
259
|
+
primary: '#c9d1d9',
|
|
260
|
+
primaryForeground: '#0d1117',
|
|
261
|
+
secondary: '#2a3038',
|
|
262
|
+
secondaryForeground: '#adb5bf',
|
|
263
|
+
accent: '#1e2530',
|
|
264
|
+
accentForeground: '#c0c8d0',
|
|
265
|
+
border: '#30363d',
|
|
266
|
+
input: '#30363d',
|
|
267
|
+
ring: '#58a6ff',
|
|
268
|
+
midground: '#58a6ff',
|
|
269
|
+
destructive: '#cf4848',
|
|
270
|
+
destructiveForeground: '#fef2f2',
|
|
271
|
+
sidebarBackground: '#090d13',
|
|
272
|
+
sidebarBorder: '#1c2228',
|
|
273
|
+
userBubble: '#1e2a38',
|
|
274
|
+
userBubbleBorder: '#2e4060'
|
|
275
|
+
},
|
|
276
|
+
typography: {
|
|
277
|
+
fontMono: `"JetBrains Mono", ${SYSTEM_MONO}`
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
export const BUILTIN_THEMES: Record<string, DesktopTheme> = {
|
|
282
|
+
nastech: nousTheme,
|
|
283
|
+
midnight: midnightTheme,
|
|
284
|
+
ember: emberTheme,
|
|
285
|
+
mono: monoTheme,
|
|
286
|
+
cyberpunk: cyberpunkTheme,
|
|
287
|
+
slate: slateTheme
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
export const BUILTIN_THEME_LIST = Object.values(BUILTIN_THEMES)
|
|
291
|
+
|
|
292
|
+
/** Skin used when nothing is persisted or the persisted name is retired. */
|
|
293
|
+
export const DEFAULT_SKIN_NAME = 'nastech'
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import { modePref, skinPref } from './context'
|
|
4
|
+
import { DEFAULT_SKIN_NAME } from './presets'
|
|
5
|
+
|
|
6
|
+
// Skin and mode share one per-profile contract, so assert it once over both.
|
|
7
|
+
interface Pref {
|
|
8
|
+
resolve: (profile: string) => string
|
|
9
|
+
assign: (profile: string, value: string) => void
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const cases = [
|
|
13
|
+
{ name: 'skin', pref: skinPref as unknown as Pref, fallback: DEFAULT_SKIN_NAME, a: 'ember', b: 'midnight', junk: 'nope' },
|
|
14
|
+
{ name: 'mode', pref: modePref as unknown as Pref, fallback: 'light', a: 'dark', b: 'system', junk: 'dusk' }
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
describe.each(cases)('per-profile $name', ({ pref, fallback, a, b, junk }) => {
|
|
18
|
+
beforeEach(() => window.localStorage.clear())
|
|
19
|
+
|
|
20
|
+
it('falls back to the default when unassigned', () => {
|
|
21
|
+
expect(pref.resolve('default')).toBe(fallback)
|
|
22
|
+
expect(pref.resolve('work')).toBe(fallback)
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
it('keeps each profile on its own value', () => {
|
|
26
|
+
pref.assign('work', a)
|
|
27
|
+
pref.assign('default', b)
|
|
28
|
+
expect(pref.resolve('work')).toBe(a)
|
|
29
|
+
expect(pref.resolve('default')).toBe(b)
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
it('lets unassigned profiles inherit the default profile as the global fallback', () => {
|
|
33
|
+
pref.assign('default', a)
|
|
34
|
+
expect(pref.resolve('never-themed')).toBe(a)
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
it('normalizes an unknown stored value back to the default', () => {
|
|
38
|
+
pref.assign('work', junk)
|
|
39
|
+
expect(pref.resolve('work')).toBe(fallback)
|
|
40
|
+
})
|
|
41
|
+
})
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Desktop app theme model.
|
|
3
|
+
*
|
|
4
|
+
* colors — Tailwind color tokens written directly to CSS vars.
|
|
5
|
+
* darkColors — optional hand-tuned dark variant (else `colors` is reused
|
|
6
|
+
* unchanged for dark, and a synth pass generates light).
|
|
7
|
+
* typography — font families + optional stylesheet URL.
|
|
8
|
+
*
|
|
9
|
+
* Everything else (layout, sizing, radius, line-height) lives in styles.css.
|
|
10
|
+
* Add new themes in `presets.ts` — no other code changes needed.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export interface DesktopThemeColors {
|
|
14
|
+
background: string
|
|
15
|
+
foreground: string
|
|
16
|
+
card: string
|
|
17
|
+
cardForeground: string
|
|
18
|
+
muted: string
|
|
19
|
+
mutedForeground: string
|
|
20
|
+
popover: string
|
|
21
|
+
popoverForeground: string
|
|
22
|
+
primary: string
|
|
23
|
+
primaryForeground: string
|
|
24
|
+
secondary: string
|
|
25
|
+
secondaryForeground: string
|
|
26
|
+
accent: string
|
|
27
|
+
accentForeground: string
|
|
28
|
+
border: string
|
|
29
|
+
input: string
|
|
30
|
+
/** Generic focus ring — buttons, inputs, etc. */
|
|
31
|
+
ring: string
|
|
32
|
+
/**
|
|
33
|
+
* Brand-accent stroke — focus rings, streaming cursors, active session
|
|
34
|
+
* pills, branded scrollbars, text selection. Falls back to `ring`.
|
|
35
|
+
* Aliased to the DS `--midground` token.
|
|
36
|
+
*/
|
|
37
|
+
midground?: string
|
|
38
|
+
/** Auto-derived from `midground` luminance when omitted. */
|
|
39
|
+
midgroundForeground?: string
|
|
40
|
+
/** Composer outline / focus color. Falls back to `midground`. */
|
|
41
|
+
composerRing?: string
|
|
42
|
+
destructive: string
|
|
43
|
+
destructiveForeground: string
|
|
44
|
+
sidebarBackground?: string
|
|
45
|
+
sidebarBorder?: string
|
|
46
|
+
userBubble?: string
|
|
47
|
+
userBubbleBorder?: string
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface DesktopThemeTypography {
|
|
51
|
+
fontSans: string
|
|
52
|
+
fontMono: string
|
|
53
|
+
/** Google/Bunny/self-hosted font stylesheet URL. */
|
|
54
|
+
fontUrl?: string
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface DesktopTheme {
|
|
58
|
+
name: string
|
|
59
|
+
label: string
|
|
60
|
+
description: string
|
|
61
|
+
/** Light palette (also reused for dark when `darkColors` is omitted). */
|
|
62
|
+
colors: DesktopThemeColors
|
|
63
|
+
/** Hand-tuned dark palette. Skins like `nastech` ship one. */
|
|
64
|
+
darkColors?: DesktopThemeColors
|
|
65
|
+
typography?: Partial<DesktopThemeTypography>
|
|
66
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { useCallback } from 'react'
|
|
2
|
+
|
|
3
|
+
import { useTheme } from './context'
|
|
4
|
+
|
|
5
|
+
// Retired skin names land on the canonical NasTech skin so old muscle memory works.
|
|
6
|
+
const ALIASES: Record<string, string> = {
|
|
7
|
+
ares: 'ember',
|
|
8
|
+
default: 'nastech',
|
|
9
|
+
gold: 'nastech',
|
|
10
|
+
NASTECH: 'nastech',
|
|
11
|
+
'nastech-light': 'nastech'
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function useSkinCommand() {
|
|
15
|
+
const { availableThemes, setTheme, themeName } = useTheme()
|
|
16
|
+
|
|
17
|
+
return useCallback(
|
|
18
|
+
(rawArg: string) => {
|
|
19
|
+
const arg = rawArg.trim()
|
|
20
|
+
|
|
21
|
+
if (!availableThemes.length) {
|
|
22
|
+
return 'No desktop themes are available.'
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const activeIndex = Math.max(
|
|
26
|
+
0,
|
|
27
|
+
availableThemes.findIndex(t => t.name === themeName)
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
if (!arg || arg === 'next') {
|
|
31
|
+
const next = availableThemes[(activeIndex + 1) % availableThemes.length]
|
|
32
|
+
setTheme(next.name)
|
|
33
|
+
|
|
34
|
+
return `Desktop theme switched to ${next.label}.`
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (arg === 'list' || arg === 'ls' || arg === 'status') {
|
|
38
|
+
const rows = availableThemes.map(t => `${t.name === themeName ? '*' : ' '} ${t.name.padEnd(10)} ${t.label}`)
|
|
39
|
+
|
|
40
|
+
return ['Desktop themes:', ...rows, '', 'Use /skin <name>, or /skin to cycle.'].join('\n')
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const normalized = arg.toLowerCase()
|
|
44
|
+
const targetName = ALIASES[normalized] || normalized
|
|
45
|
+
|
|
46
|
+
const target = availableThemes.find(
|
|
47
|
+
t => t.name.toLowerCase() === targetName || t.label.toLowerCase() === normalized
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
if (!target) {
|
|
51
|
+
return `Unknown desktop theme: ${arg}\nAvailable: ${availableThemes.map(t => t.name).join(', ')}`
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
setTheme(target.name)
|
|
55
|
+
|
|
56
|
+
return `Desktop theme switched to ${target.label}.`
|
|
57
|
+
},
|
|
58
|
+
[availableThemes, setTheme, themeName]
|
|
59
|
+
)
|
|
60
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import { BUILTIN_THEMES, DEFAULT_SKIN_NAME } from './presets'
|
|
4
|
+
import { $userThemes, installUserTheme, isUserTheme, listAllThemes, removeUserTheme, resolveTheme } from './user-themes'
|
|
5
|
+
import { convertVscodeColorTheme } from './vscode'
|
|
6
|
+
|
|
7
|
+
const makeTheme = (label: string) =>
|
|
8
|
+
convertVscodeColorTheme({
|
|
9
|
+
name: label,
|
|
10
|
+
type: 'dark',
|
|
11
|
+
colors: { 'editor.background': '#101014', 'editor.foreground': '#fafafa', focusBorder: '#7aa2f7' }
|
|
12
|
+
}).theme
|
|
13
|
+
|
|
14
|
+
describe('user theme registry', () => {
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
window.localStorage.clear()
|
|
17
|
+
$userThemes.set({})
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
it('installs a theme into the merged registry and persists it', () => {
|
|
21
|
+
const theme = installUserTheme(makeTheme('Tokyo Night'))
|
|
22
|
+
|
|
23
|
+
expect(isUserTheme(theme.name)).toBe(true)
|
|
24
|
+
expect(resolveTheme(theme.name)).toEqual(theme)
|
|
25
|
+
expect(listAllThemes().map(t => t.name)).toContain(theme.name)
|
|
26
|
+
expect(window.localStorage.getItem('nastech-desktop-user-themes-v1')).toContain(theme.name)
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
it('lists built-ins before user themes', () => {
|
|
30
|
+
installUserTheme(makeTheme('Custom'))
|
|
31
|
+
const names = listAllThemes().map(t => t.name)
|
|
32
|
+
|
|
33
|
+
expect(names.slice(0, Object.keys(BUILTIN_THEMES).length)).toEqual(Object.keys(BUILTIN_THEMES))
|
|
34
|
+
expect(names.at(-1)).toBe('vsc-custom')
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
it('removes a theme', () => {
|
|
38
|
+
const theme = installUserTheme(makeTheme('Throwaway'))
|
|
39
|
+
removeUserTheme(theme.name)
|
|
40
|
+
|
|
41
|
+
expect(isUserTheme(theme.name)).toBe(false)
|
|
42
|
+
expect(resolveTheme(theme.name)).toBeUndefined()
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it('resolves built-ins through the same lookup', () => {
|
|
46
|
+
expect(resolveTheme(DEFAULT_SKIN_NAME)).toBe(BUILTIN_THEMES[DEFAULT_SKIN_NAME])
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
it('refuses to shadow a built-in name', () => {
|
|
50
|
+
const builtinName = makeTheme('x')
|
|
51
|
+
builtinName.name = DEFAULT_SKIN_NAME
|
|
52
|
+
|
|
53
|
+
expect(() => installUserTheme(builtinName)).toThrow(/built-in/)
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
it('rejects a theme missing required colors', () => {
|
|
57
|
+
const broken = makeTheme('Broken')
|
|
58
|
+
// @ts-expect-error — intentionally corrupt the palette for the test.
|
|
59
|
+
broken.colors = { background: '#000000' }
|
|
60
|
+
|
|
61
|
+
expect(() => installUserTheme(broken)).toThrow(/colors/)
|
|
62
|
+
})
|
|
63
|
+
})
|