Skip to content

Feature: Reorganized settings menu#563

Open
grenkoca wants to merge 20 commits into
Nano-Collective:mainfrom
grenkoca:main
Open

Feature: Reorganized settings menu#563
grenkoca wants to merge 20 commits into
Nano-Collective:mainfrom
grenkoca:main

Conversation

@grenkoca

@grenkoca grenkoca commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Description

Draft implementation of #471, which reorganizes orphaned settings into a breadcrumb menu

Type of Change

  • Bug fix
  • New feature
  • Breaking change
  • Documentation update

Testing

Automated Tests

  • New features include passing tests in .spec.ts/tsx files
  • All existing tests pass (pnpm test:all completes successfully)
  • Tests cover both success and error scenarios

Manual Testing

  • Tested with Ollama
  • Tested with OpenRouter
  • Tested with OpenAI-compatible API
  • Tested MCP integration (if applicable)

Checklist

  • Code follows project style guidelines
  • Self-review completed
  • Documentation updated (if needed)
  • No breaking changes (or clearly documented)
  • Appropriate logging added using structured logging (see CONTRIBUTING.md)

grenkoca and others added 13 commits June 9, 2026 07:50
- Replace flat settings list with 8-category nested navigation
- Add breadcrumb titles (Settings · Appearance · Theme)
- Add Shift+Tab back-navigation at all levels
- Add Keep/Discard prompt for unsaved changes
- Migrate existing 5 settings into Appearance, Input, Behavior categories
- Add new Behavior panels: Auto-Compact, Sessions, Default Mode, Reasoning Traces
- Add Environment category with read-only NANOCODER_* env var display
- Add config-writer.ts utility for writing to agents.config.json
- Add settings-menu-types.ts with category definitions and path utilities
- Add placeholder panels for unimplemented settings (Providers, MCPs, Web Search, Advanced)
- Update settings command description
- Update tests for new category structure
- Add Tool Auto-Approval panel (Providers category) — read-only display
  of alwaysAllow tool lists from agents.config.json
- Add Web Search panel — API key input with masking for Brave Search
- Wire up new panels in settings panel router
- Placeholder panels remain for wizard entry points (configure providers,
  MCP servers, IDE, tune, config files) to be connected in future phases
- Update descriptions for /setup-config, /setup-providers, /setup-mcp,
  /ide, /tune, /copilot-login, /codex-login with deprecation banners
  pointing to /settings
- Add runtime logWarning for /setup-config
- Update lazy-registry inline descriptions to match
- Update /settings description to reflect expanded scope
Maps all 20+ documented configuration parameters to their TUI settings
category, with clear rationale for excluded parameters (env vars,
per-provider granularity, logging). Also documents future planned
parameters.
The ideCommand description was updated to include a deprecation notice.
The test was asserting an exact string match; updated to use substring
checks that verify both the original text and the deprecation tag.
- Fix stuck Keep/Discard prompt: replaced useInput handler with
  SelectInput onSelect callback (Enter was being swallowed by
  SelectInput's internal handler)
- Add changesSummary to dirty state: shows category and panel name
  in the prompt (e.g. "Appearance → Theme changed")
- Add summary field to DirtyState type
Environment category has no sub-items (depth-2 path), so currentPath[2]
is undefined. Added null guard for panelKey before calling .replace().
- Replace flat changesSummary with ChangeDiff[] showing setting name,
  old value, and new value (e.g. "Theme : Dracula → Nord")
- Add proper line breaks in prompt: unsaved changes header, diff list,
  and action prompt are now on separate lines
- Add onChanged callback to all settings panels (Theme, Title Shape,
  Nanocoder Shape, Paste Threshold, Notifications, Auto-Compact,
  Sessions, Default Mode, Reasoning Traces, Web Search)
- Use useRef accumulator to collect diffs across panel navigation
- Fix: handle categories with no sub-items (Environment) in dirty state
# Conflicts:
#	source/app/components/settings-selector.tsx
#	source/commands/setup-config.tsx
#	source/commands/setup-mcp.tsx
#	source/commands/setup-providers.tsx
#	source/config/preferences.ts
vestige from LLM planning document
Copilot AI review requested due to automatic review settings June 11, 2026 23:58

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Introduces a hierarchical /settings TUI with new panels (sessions, auto-compact, default mode, web search, tool approval, env view), adds global config read/write helpers, and marks several legacy commands as deprecated in favor of the new settings UI.

Changes:

  • Added a path-based settings navigation system with category menus, leaf panels, and a Keep/Discard prompt for tracked changes.
  • Added config-writer helpers to persist updates into global agents.config.json under the nanocoder key.
  • Deprecated older setup/tune/ide commands and updated command registry descriptions and tests.

Reviewed changes

Copilot reviewed 21 out of 21 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
source/hooks/useAppState.tsx Adds settings return path state for wizard → settings navigation.
source/config/config-writer.ts Adds sync helpers for reading/updating global agents.config.json.
source/commands/tune.ts Updates tune command description to deprecated guidance.
source/commands/setup-providers.tsx Marks providers wizard command deprecated; clarifies handler is intercepted.
source/commands/setup-mcp.tsx Marks MCP wizard command deprecated; clarifies handler is intercepted.
source/commands/setup-config.tsx Marks setup-config deprecated and logs a warning on use.
source/commands/settings.ts Updates settings command description for broader scope.
source/commands/lazy-registry.ts Updates descriptions with deprecation guidance and new settings framing.
source/commands/ide.tsx Marks IDE command deprecated in description.
source/commands/ide.spec.tsx Updates test expectations for deprecated description.
source/app/components/settings-web-search.tsx Adds Web Search API key panel that writes to global config.
source/app/components/settings-tool-approval.tsx Adds read-only tool auto-approval list panel.
source/app/components/settings-sessions.tsx Adds sessions configuration panel persisted to global config.
source/app/components/settings-selector.tsx Replaces flat menu with hierarchical categories + panel router + dirty tracking.
source/app/components/settings-selector.spec.tsx Updates tests for new top-level settings menu categories.
source/app/components/settings-reasoning-traces.tsx Adds reasoning traces default expand/collapse toggle panel.
source/app/components/settings-menu-types.ts Adds shared types/constants for menu categories, paths, and items.
source/app/components/settings-keep-discard-prompt.tsx Adds Keep/Discard prompt UI and ChangeDiff type.
source/app/components/settings-default-mode.tsx Adds default CLI mode selector persisted to global config.
source/app/components/settings-auto-compact.tsx Adds auto-compact settings panel persisted to global config.
docs/settings-menu-map.md Documents settings menu mapping across config sources.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +1513 to +1516
const [path, setPath] = useState<SettingsPath>(ROOT_PATH);
const [dirtyState, setDirtyState] = useState<DirtyState | null>(null);
// Accumulates change diffs as panels report them
const changesRef = useRef<ChangeDiff[]>([]);
Comment on lines +1529 to +1567
const goBack = useCallback(() => {
setPath(currentPath => {
const newParent = parentPath(currentPath);

// If going back to root, check for dirty state
if (isRootPath(newParent) && !isRootPath(currentPath)) {
const categorySegment = currentPath[1] as SettingsCategory;
const changes = changesRef.current;

if (changes.length > 0) {
setDirtyState({
isDirty: true,
category: categorySegment,
changes: [...changes],
});
}
} else {
setDirtyState(null);
}

return newParent;
});
}, []);

// Handle Keep/Discard
const handleKeep = useCallback(() => {
// Changes are already persisted by individual panels
changesRef.current = [];
setDirtyState(null);
setPath(ROOT_PATH);
}, []);

const handleDiscard = useCallback(() => {
// For Appearance settings that preview live, reload from preferences.
// For other categories, changes were already saved on apply.
changesRef.current = [];
setDirtyState(null);
setPath(ROOT_PATH);
}, []);
Comment thread docs/settings-menu-map.md
Comment on lines +40 to +50
| Providers (list) | wizard | Providers | Configure Providers | ✅ | `agents.config.json` | Launches existing wizard |
| Copilot credentials | wizard | Providers | Copilot Login | ✅ | `agents.config.json` | Launches login flow |
| Codex credentials | wizard | Providers | Codex Login | ✅ | `agents.config.json` | Launches login flow |
| `alwaysAllow` (top-level) | list | Providers | Tool Auto-Approval | ✅ | `agents.config.json` | Read-only display |
| `nanocoderTools.alwaysAllow` | list | Providers | Tool Auto-Approval | ✅ | `agents.config.json` | Read-only display |
| MCP servers | wizard | MCPs | Configure MCP Servers | ✅ | `agents.config.json` | Launches existing wizard |
| `nanocoderTools.webSearch.apiKey` | text | Web Search | API Key | ✅ | `agents.config.json` | Masked input |
| `NANOCODER_*` env vars | read-only | Environment | (all) | ❌ | `process.env` | Read-only display |
| `tune.*` | wizard | Advanced | Tune Model | ✅ | `nanocoder-preferences.json` | Launches existing wizard |
| Config file paths | wizard | Advanced | Edit Config Files | ✅ | filesystem | Launches file picker |
| IDE connection | wizard | Advanced | Connect IDE | ✅ | `agents.config.json` | Launches existing wizard |
Comment on lines +35 to +43
try {
const dir = dirname(configPath);
if (!existsSync(dir)) {
mkdirSync(dir, {recursive: true});
}
writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf-8');
} catch (error) {
logError(`Failed to write config update: ${String(error)}`);
}
Comment on lines +56 to +61
setSaved(true);
setTimeout(() => {
setEditMode(false);
setInputValue('');
setSaved(false);
}, 1500);
Comment on lines 130 to 135
name: 'setup-config',
description: 'Open a configuration file in your editor',
description:
'[deprecated — use /settings → Advanced] Open a configuration file in your editor',
load: () =>
import('@/commands/setup-config').then(m => m.setupConfigCommand),
},
Comment on lines 174 to 187
load: () => import('@/commands/tasks').then(m => m.tasksCommand),
},
{
name: 'settings',
description:
'Configure UI settings (theme, shapes, branding, paste threshold)',
'Configure nanocoder settings (appearance, behavior, providers, MCPs, and more)',
load: () => import('@/commands/settings').then(m => m.settingsCommand),
},
{
name: 'tune',
description:
'Tune model settings (parameters, tool profiles, prompt, compaction)',
'[deprecated — use /settings → Advanced] Tune model settings (parameters, tool profiles, prompt, compaction)',
load: () => import('@/commands/tune').then(m => m.tuneCommand),
},
Comment on lines +1569 to 1578
// If dirty state is active, show the Keep/Discard prompt
if (dirtyState?.isDirty) {
return (
<KeepDiscardPrompt
onKeep={handleKeep}
onDiscard={handleDiscard}
changes={dirtyState.changes}
/>
);
}
@akramcodez akramcodez self-assigned this Jun 12, 2026
@akramcodez

Copy link
Copy Markdown
Collaborator

@grenkoca Thanks for tackling this huge settings reorganization. The overall direction looks great, and I really like the new hierarchical structure, breadcrumbs, and navigation flow.

However, after reviewing the implementation and testing the workflows locally, I found a couple of regressions that I think need to be addressed before this can be merged.

1. Discard does not actually discard changes

I was able to reproduce a case where settings are persisted immediately when selected, but choosing Discard later does not restore the previous value.

Reproduction:

  1. Open /settings
  2. Change a setting (for example Theme)
  3. Navigate away and choose Discard
  4. Restart Nanocoder

The newly selected value remains active.

From what I can see, the panel persists changes immediately, while handleDiscard() only clears the local dirty state and does not restore the previous value.

Suggested fix:

  • Defer persistence until Keep is selected, or
  • Restore the previous values during the Discard flow.

2. Deprecated commands currently lead to placeholder screens

I tested the migration paths for several commands and found that users are directed into unimplemented screens.

Example:

  1. Run /setup-config
  2. Follow the warning to use /settings → Advanced → Edit Config Files
  3. Navigate there
  4. Encounter a "Coming soon" screen

I was able to reproduce similar behavior for:

  • /setup-config
  • /setup-providers
  • /ide
  • /tune

Suggested fix:

  • Keep the existing commands as the recommended path until the replacement panels are functional, or
  • Wire the new settings routes to invoke the existing command implementations.

3. Missing test coverage

I couldn't find coverage for:

  • Keep/Discard state transitions
  • Persistence/revert behavior
  • Config writer behavior

Given the issues above, I think additional tests around these flows would be valuable before merge.

4. Config writer safety (recommendation)

The new config writer currently writes directly to the target file. It may be worth considering an atomic write approach (.tmp → rename) to reduce the risk of corruption if the process is interrupted during a write.

The overall direction of this PR is excellent and I think it will be a major UX improvement once these issues are addressed, but for now I'm requesting changes due to the Discard behavior and the dead-end migration paths.

@will-lamerton

Copy link
Copy Markdown
Member

Hey @grenkoca - this looks awesome! I know you mentioned in the issue about getting support testing and finishing this issue - let me know if that's something you still want and @akramcodez and I can jump and wrapping too :)

@grenkoca

Copy link
Copy Markdown
Contributor Author

@akramcodez and @will-lamerton thanks for the thorough review! I know it's unfinished, but I figured it would be better to open a draft PR and get some help rather than let the changes sit uncommitted on my end. Perfect is the enemy of good, and y'all are the experts :)

@akramcodez: regarding your specific points

  1. Great catch! I'll see if I can fix this today
  2. Yep, I left this as a stub because I had a few questions regarding an implementation:
    • Currently, we are opening the user's $EDITOR (link to /setup-config that you mention) and allowing free-form editing of config files. I think it could benefit from a constrained UI with structured input validation.
    • This also would help resolve the tension between directly editing configs and the (intended) dirty state caching. I suppose we could use the atomic write strategy that you mention, but it would be a messy implementation.
    • We could also have $EDITOR as a secondary option by enabling Type 'e' to open in $EDITOR, but I think direct, unvalidated editing of config files shouldn't be the primary surface.
  3. I'll definitely need a hand with these.
  4. Great idea, I'll also see if I can implement today.

Regarding point 2, here are some good examples:

  • schemaui, but obviously including this as a dependency would add bloat and generally be overkill (and is also written in rust).
  • ink-form would work with ink integration, and doesn't add new dependencies, and is ~27Mb unpacked.
  • json-tui is ~0.7MB with zero dependencies, but is written in C and could complicate cross-platform installation

Ultimately a good solution would probably just be to extend source/wizards/base-config-wizard.tsx or to have it maintain the current direct-editing, but I don't want to re-invent the wheel.

Let me know what direction you want to go!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants