Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -582,15 +582,13 @@ You can override this directory using `NANOCODER_DATA_DIR`.
- `/provider` - Switch between configured AI providers
- `/status` - Display current status (CWD, provider, model, theme, available updates, AGENTS setup)
- `/model-database` - Browse coding models from OpenRouter (searchable, filterable by open/proprietary)
- `/settings` - Interactive menu to access Nanocoder theme settings (theme, title-shape, nanocoder-shape) and commands
- `/mcp` - Show connected MCP servers and their tools
- `/custom-commands` - List all custom commands
- `/checkpoint` - Save and restore conversation snapshots (see [Checkpointing](#checkpointing) section)
- `/compact` - Compress message history to reduce context usage (see [Context Compression](docs/context-compression.md))
- `/exit` - Exit the application
- `/export` - Export current session to markdown file
- `/theme` - Select a theme for the Nanocoder CLI
- `/title-shape` - Select a title shape style for the Nanocoder CLI (real-time preview)
- `/nanocoder-shape` - Select a branding font style for the Nanocoder welcome banner (real-time preview)
- `/update` - Update Nanocoder to the latest version
- `/usage` – Get current model context usage visually
- `/lsp` – List connected LSP servers
Expand Down
29 changes: 19 additions & 10 deletions biome.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,17 @@
"files": {
"includes": [
"**",
"!**/dist",
"!**/pnpm-lock.yaml",
"!**/package-lock.json",
"!**/yarn.lock",
"!**/coverage",
"!**/agents",
"!**/node_modules",
"!**/plugins",
"!dist",
"!pnpm-lock.yaml",
"!package-lock.json",
"!yarn.lock",
"!coverage",
"!agents",
"!node_modules",
"!plugins",
"!**/*.spec.ts",
"!**/*.spec.tsx",
"!**/.husky"
"!.husky"
]
},
"formatter": {
Expand All @@ -28,7 +28,16 @@
"lineEnding": "lf",
"lineWidth": 80
},
"assist": { "actions": { "source": { "organizeImports": "on" } } },
"assist": {
"actions": {
"source": {
"organizeImports": {
"level": "on",
"options": {}
}
}
}
},
"linter": {
"enabled": true,
"rules": {
Expand Down
97 changes: 43 additions & 54 deletions source/app/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
VSCodeExtensionPrompt,
} from '@/components/vscode-extension-prompt';
import WelcomeMessage from '@/components/welcome-message';
import {updateSelectedTheme} from '@/config/preferences';
import {getThemeColors} from '@/config/themes';
import {setCurrentMode as setCurrentModeContext} from '@/context/mode-context';
import {useChatHandler} from '@/hooks/chat-handler';
Expand All @@ -28,6 +29,7 @@ import {TitleShapeContext, updateTitleShape} from '@/hooks/useTitleShape';
import {useToolHandler} from '@/hooks/useToolHandler';
import {UIStateProvider} from '@/hooks/useUIState';
import {useVSCodeServer} from '@/hooks/useVSCodeServer';
import type {ThemePreset} from '@/types/ui';
import {
generateCorrelationId,
withNewCorrelationContext,
Expand Down Expand Up @@ -166,7 +168,10 @@ export default function App({
const themeContextValue = {
currentTheme: appState.currentTheme,
colors: getThemeColors(appState.currentTheme),
setCurrentTheme: appState.setCurrentTheme,
setCurrentTheme: (theme: ThemePreset) => {
appState.setCurrentTheme(theme);
updateSelectedTheme(theme);
},
};

// Create title shape context value
Expand Down Expand Up @@ -356,11 +361,9 @@ export default function App({
setMessages: appState.updateMessages,
setIsModelSelectionMode: appState.setIsModelSelectionMode,
setIsProviderSelectionMode: appState.setIsProviderSelectionMode,
setIsThemeSelectionMode: appState.setIsThemeSelectionMode,
setIsTitleShapeSelectionMode: appState.setIsTitleShapeSelectionMode,
setIsNanocoderShapeSelectionMode: appState.setIsNanocoderShapeSelectionMode,
setIsModelDatabaseMode: appState.setIsModelDatabaseMode,
setIsConfigWizardMode: appState.setIsConfigWizardMode,
setIsSettingsMode: appState.setIsSettingsMode,
setIsMcpWizardMode: appState.setIsMcpWizardMode,
addToChatQueue: appState.addToChatQueue,
getNextComponentKey: appState.getNextComponentKey,
Expand Down Expand Up @@ -396,12 +399,9 @@ export default function App({
getMessageTokens: appState.getMessageTokens,
enterModelSelectionMode: modeHandlers.enterModelSelectionMode,
enterProviderSelectionMode: modeHandlers.enterProviderSelectionMode,
enterThemeSelectionMode: modeHandlers.enterThemeSelectionMode,
enterTitleShapeSelectionMode: modeHandlers.enterTitleShapeSelectionMode,
enterNanocoderShapeSelectionMode:
modeHandlers.enterNanocoderShapeSelectionMode,
enterModelDatabaseMode: modeHandlers.enterModelDatabaseMode,
enterConfigWizardMode: modeHandlers.enterConfigWizardMode,
enterSettingsMode: modeHandlers.enterSettingsMode,
enterMcpWizardMode: modeHandlers.enterMcpWizardMode,
handleChatMessage: chatHandler.handleChatMessage,
});
Expand Down Expand Up @@ -564,66 +564,55 @@ export default function App({
{/* Modal Selectors - rendered below chat history */}
{(appState.isModelSelectionMode ||
appState.isProviderSelectionMode ||
appState.isThemeSelectionMode ||
appState.isModelDatabaseMode ||
appState.isConfigWizardMode ||
appState.isMcpWizardMode ||
appState.isTitleShapeSelectionMode ||
appState.isNanocoderShapeSelectionMode ||
appState.isSettingsMode ||
appState.isCheckpointLoadMode) && (
<ModalSelectors
isModelSelectionMode={appState.isModelSelectionMode}
isProviderSelectionMode={appState.isProviderSelectionMode}
isThemeSelectionMode={appState.isThemeSelectionMode}
isModelDatabaseMode={appState.isModelDatabaseMode}
isConfigWizardMode={appState.isConfigWizardMode}
isMcpWizardMode={appState.isMcpWizardMode}
isCheckpointLoadMode={appState.isCheckpointLoadMode}
isTitleShapeSelectionMode={appState.isTitleShapeSelectionMode}
isNanocoderShapeSelectionMode={
appState.isNanocoderShapeSelectionMode
}
client={appState.client}
currentModel={appState.currentModel}
currentProvider={appState.currentProvider}
checkpointLoadData={appState.checkpointLoadData}
onModelSelect={modeHandlers.handleModelSelect}
onModelSelectionCancel={modeHandlers.handleModelSelectionCancel}
onProviderSelect={modeHandlers.handleProviderSelect}
onProviderSelectionCancel={
modeHandlers.handleProviderSelectionCancel
}
onThemeSelect={modeHandlers.handleThemeSelect}
onTitleShapeSelect={modeHandlers.handleTitleShapeSelect}
onTitleShapeSelectionCancel={
modeHandlers.handleTitleShapeSelectionCancel
}
onNanocoderShapeSelect={modeHandlers.handleNanocoderShapeSelect}
onNanocoderShapeSelectionCancel={
modeHandlers.handleNanocoderShapeSelectionCancel
}
onThemeSelectionCancel={modeHandlers.handleThemeSelectionCancel}
onModelDatabaseCancel={modeHandlers.handleModelDatabaseCancel}
onConfigWizardComplete={modeHandlers.handleConfigWizardComplete}
onConfigWizardCancel={modeHandlers.handleConfigWizardCancel}
onMcpWizardComplete={modeHandlers.handleMcpWizardComplete}
onMcpWizardCancel={modeHandlers.handleMcpWizardCancel}
onCheckpointSelect={appHandlers.handleCheckpointSelect}
onCheckpointCancel={appHandlers.handleCheckpointCancel}
/>
<Box marginLeft={-1} flexDirection="column">
<ModalSelectors
isModelSelectionMode={appState.isModelSelectionMode}
isProviderSelectionMode={appState.isProviderSelectionMode}
isModelDatabaseMode={appState.isModelDatabaseMode}
isConfigWizardMode={appState.isConfigWizardMode}
isMcpWizardMode={appState.isMcpWizardMode}
isSettingsMode={appState.isSettingsMode}
isCheckpointLoadMode={appState.isCheckpointLoadMode}
client={appState.client}
currentModel={appState.currentModel}
currentProvider={appState.currentProvider}
checkpointLoadData={appState.checkpointLoadData}
onModelSelect={modeHandlers.handleModelSelect}
onModelSelectionCancel={
modeHandlers.handleModelSelectionCancel
}
onProviderSelect={modeHandlers.handleProviderSelect}
onProviderSelectionCancel={
modeHandlers.handleProviderSelectionCancel
}
onModelDatabaseCancel={modeHandlers.handleModelDatabaseCancel}
onConfigWizardComplete={
modeHandlers.handleConfigWizardComplete
}
onConfigWizardCancel={modeHandlers.handleConfigWizardCancel}
onMcpWizardComplete={modeHandlers.handleMcpWizardComplete}
onMcpWizardCancel={modeHandlers.handleMcpWizardCancel}
onSettingsCancel={modeHandlers.handleSettingsCancel}
onCheckpointSelect={appHandlers.handleCheckpointSelect}
onCheckpointCancel={appHandlers.handleCheckpointCancel}
/>
</Box>
)}

{/* Chat Input - only rendered when not in modal mode */}
{appState.startChat &&
!(
appState.isModelSelectionMode ||
appState.isProviderSelectionMode ||
appState.isThemeSelectionMode ||
appState.isModelDatabaseMode ||
appState.isConfigWizardMode ||
appState.isSettingsMode ||
appState.isMcpWizardMode ||
appState.isTitleShapeSelectionMode ||
appState.isNanocoderShapeSelectionMode ||
appState.isCheckpointLoadMode
) && (
<ChatInput
Expand Down
33 changes: 23 additions & 10 deletions source/app/components/modal-selectors.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@ function createDefaultProps(
return {
isModelSelectionMode: false,
isProviderSelectionMode: false,
isThemeSelectionMode: false,
isModelDatabaseMode: false,
isConfigWizardMode: false,
isMcpWizardMode: false,
isCheckpointLoadMode: false,
isSettingsMode: false,
client: null,
currentModel: 'test-model',
currentProvider: 'test-provider',
Expand All @@ -23,13 +24,14 @@ function createDefaultProps(
onModelSelectionCancel: () => {},
onProviderSelect: async () => {},
onProviderSelectionCancel: () => {},
onThemeSelect: () => {},
onThemeSelectionCancel: () => {},
onModelDatabaseCancel: () => {},
onConfigWizardComplete: async () => {},
onConfigWizardCancel: () => {},
onMcpWizardComplete: async () => {},
onMcpWizardCancel: () => {},
onCheckpointSelect: async () => {},
onCheckpointCancel: () => {},
onSettingsCancel: () => {},
...overrides,
};
}
Expand Down Expand Up @@ -65,8 +67,8 @@ test('ModalSelectors renders ProviderSelector when isProviderSelectionMode is tr
unmount();
});

test('ModalSelectors renders ThemeSelector when isThemeSelectionMode is true', t => {
const props = createDefaultProps({isThemeSelectionMode: true});
test('ModalSelectors renders ModelDatabaseDisplay when isModelDatabaseMode is true', t => {
const props = createDefaultProps({isModelDatabaseMode: true});
const component = ModalSelectors(props);
t.truthy(component);

Expand All @@ -76,8 +78,8 @@ test('ModalSelectors renders ThemeSelector when isThemeSelectionMode is true', t
unmount();
});

test('ModalSelectors renders ModelDatabaseDisplay when isModelDatabaseMode is true', t => {
const props = createDefaultProps({isModelDatabaseMode: true});
test('ModalSelectors renders ConfigWizard when isConfigWizardMode is true', t => {
const props = createDefaultProps({isConfigWizardMode: true});
const component = ModalSelectors(props);
t.truthy(component);

Expand All @@ -87,8 +89,19 @@ test('ModalSelectors renders ModelDatabaseDisplay when isModelDatabaseMode is tr
unmount();
});

test('ModalSelectors renders ConfigWizard when isConfigWizardMode is true', t => {
const props = createDefaultProps({isConfigWizardMode: true});
test('ModalSelectors renders McpWizard when isMcpWizardMode is true', t => {
const props = createDefaultProps({isMcpWizardMode: true});
const component = ModalSelectors(props);
t.truthy(component);

const {lastFrame, unmount} = renderWithTheme(<>{component}</>);
const output = lastFrame();
t.truthy(output);
unmount();
});

test('ModalSelectors renders SettingsSelector when isSettingsMode is true', t => {
const props = createDefaultProps({isSettingsMode: true});
const component = ModalSelectors(props);
t.truthy(component);

Expand Down Expand Up @@ -128,7 +141,7 @@ test('ModalSelectors prioritizes first active mode when multiple are true', t =>
const props = createDefaultProps({
isModelSelectionMode: true,
isProviderSelectionMode: true,
isThemeSelectionMode: true,
isModelDatabaseMode: true,
client: {}, // Mock client for ModelSelector
});
const component = ModalSelectors(props);
Expand Down
Loading
Loading