Skip to content

feat(settings): sunset /setup-providers and /setup-mcp in favour of /… - #848

Open
addyCooks wants to merge 9 commits into
Nano-Collective:mainfrom
addyCooks:feat/sunset-setup-commands
Open

feat(settings): sunset /setup-providers and /setup-mcp in favour of /…#848
addyCooks wants to merge 9 commits into
Nano-Collective:mainfrom
addyCooks:feat/sunset-setup-commands

Conversation

@addyCooks

@addyCooks addyCooks commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Description

Makes /settings the single entry point for provider and MCP configuration, retiring /setup-providers and /setup-mcp.

Closes #832

Type of Change

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

Changeset

  • Added a changeset (pnpm changeset) describing this change for the changelog

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

Typing `@` in the chat input opens a floating dropdown of workspace files,
folders, and open editors. Selecting one attaches it as a context chip.

Reuses the existing attachment pipeline rather than adding a parallel one:
completions push into the same `attachedPaths` state the attach button
already feeds, so chip rendering, `@[file]` serialization and host-side
expansion are untouched.

- Search runs on the extension host via workspace.findFiles, which honours
  files.exclude/search.exclude; open editor tabs resolve with no disk I/O
- Requests carry an id so out-of-order postMessage responses are dropped
- `@` triggers only at line start or after whitespace, so emails and
  decorators do not open the dropdown
- Cap attached file reads at 100 KB and skip binaries, since `@` makes
  attaching cheap enough to exhaust the context window by accident
CI runs ava with FORCE_COLOR=1, so Ink emits the themed indicator wrapped
in escape sequences and the frame line starts with \x1b[34m rather than
`>`. The anchored regexes could therefore never match, while locally the
colours are stripped and the tests passed.

Strip the frame with strip-ansi before splitting, matching the existing
convention in app-container.spec.tsx. Verified under both
CI=true FORCE_COLOR=1 and a plain local run.
The "reports and restores submitted drafts with attachments" test flaked in
CI. Instrumenting it showed the frame rendered the typed text but onSubmit
was called zero times, so the poll for the submitted value ran out its
budget with nothing pending - the Enter had been swallowed, not delayed.

Ink's useInput unsubscribes from stdin and resubscribes across a re-render,
so an Enter written while React is still flushing the typed characters is
lost. Raising the deadline cannot help when no submit is in flight.

Route every Enter through a helper that first waits for the frame to stop
changing, which keeps the keystroke clear of that window. Resending Enter
was rejected as an alternative: handleSubmit has no empty-input guard, so a
duplicate would submit '' and clobber the captured value.

Verified with the full file under CI=true FORCE_COLOR=1: six consecutive
clean runs, against a one-in-three failure rate beforehand.
The first pass spread this feature across four new modules plus three spec
files, and picked up changes unrelated to Nano-Collective#747. Reviewing it meant reading
1.4k added lines for what is a dropdown over a workspace search.

Everything now lives in the files the feature already had to touch:

- mention-search.ts collapses into a single _searchMentions method on
  ChatWebviewProvider. Ranking and matching are unchanged; the injectable
  MentionSearchDeps seam and the hand-rolled posix path helpers are gone,
  since path.relative/basename/dirname already handle both separators.
- mention-utils.js collapses into chat-panel.js. findMentionQuery moves in
  as-is; removeMentionToken was three lines at its only call site, so it is
  inlined there. That drops the extra <script> tag, its CSP-nonce wiring and
  the globalThis handoff.
- MentionItem moves to webview-protocol.ts next to the two messages that
  carry it, and its source enum narrows to an isEditor flag.

Reverted as out of scope for this issue:

- context-attachment.ts and the 100 KB cap / binary sniff it added to
  _expandContextAttachments. Worth doing, but it is not @ autocomplete and
  the attach button has always had the same exposure - separate PR.
- The user-input and styled-select-input spec fixes, which are already
  their own branches.
- chat-panel.css, a Tailwind rebuild whose only delta was an unrelated
  .container rule; every class the dropdown uses was already compiled.

The three spec files go with their modules. AVA only globs source/**, so
plugins/vscode specs never ran in CI and none of them were executing.

No behaviour change: same trigger rules, same ranking, same keyboard
handling, same postMessage protocol.
@will-lamerton

Copy link
Copy Markdown
Member

Nice consolidation @addyCooks, and the retired-alias forwarding is a good touch. tsc, biome, knip and the affected specs all pass on the branch. One thing needs fixing before merge:

onProvidersChanged is wired to handleConfigWizardComplete, which wipes the conversation. That handler was written for first-run onboarding, so it does setMessages([]) + clearContext() and re-resolves the client with a bare createLLMClient(). Two consequences when you edit a provider mid-session from /settings:

  • The model's history is emptied, but the visible transcript stays on screen (it renders from the chat component queue, and this path doesn't bump clearKey like /clear does). So the user sees their conversation intact while the model has amnesia.
  • Provider and model reset to the config default, discarding whatever they picked with /model.

reloadMcpServers is the pattern you want here: it was deliberately split out of the wizard handler so the settings panel could reuse it without the mode-exit side effects. Providers needs the same, reloadAppConfig() plus a client rebuild for the current provider/model, leaving messages alone.

Smaller things:

  • settings-providers-list.tsx:38-41 calls onBack() after onProvidersChanged has already closed settings, so it sets state on an unmounting component. Let the parent own the close.
  • returnFromLaunchedWizard (interactive-app.tsx:86) calls enterSettingsMode() with no tab, so returning from Tune/IDE always drops you on Appearance rather than the tab you came from.
  • Unknown tab args are silently swallowed: /settings providrs opens Appearance with no hint the argument was wrong.
  • context-max-handler.spec.ts:15 still passes the now-removed onEnterConfigWizardMode.
  • app-util.ts takes a runtime import of settings-tabs just for SETTINGS_TAB_IDS, pulling every settings panel and wizard into its graph. No cycle and no --plain/ACP cost since only useAppHandlers imports it, but that array is pure data and would sit better in a constants module.

Good incidental catch on source/commands/mcp.tsx pointing at /setup-providers.

Reverses the structural half of "fold @ mention support into the existing
files" and addresses the defects found in review of Nano-Collective#842.

The fold's argument for dropping the specs was that AVA only globs
source/**, so none of them executed. That is fixed rather than worked
around: ava.files now includes plugins/**, so the three spec files run in
`pnpm test:ava` and in CI. acp-client and acp-process-manager are excluded
explicitly — they transitively require('vscode'), which does not resolve
outside the extension host. They stay in the state they are in today.

Restored:

- mention-search.ts, carrying the fold's improvements forward: MentionItem
  comes from webview-protocol.ts and the source enum stays collapsed to an
  isEditor flag. The injectable MentionSearchDeps seam is back, since
  testability without an extension host is the whole point.
- mention-utils.js and context-attachment.ts, with their specs.

Fixed:

- files.exclude was not honoured. An explicit exclude *replaces* VS Code's
  defaults rather than merging, so .env and anything else hidden by the
  user showed up in the dropdown. _mentionExcludeGlob now folds
  files.exclude and search.exclude into the always-on list, scoped to the
  workspace folder being searched, skipping `when`-clause entries.
- mentionToken was assigned after the dedupe guard in requestMentions.
  With `@foo @foo`, moving the caret between the two short-circuited the
  guard and left start pointing at the wrong mention, so accepting added
  the chip but stripped the other one. Assigned in syncMentionState now.
- Accepting mid-token sliced only up to the caret, stranding the tail:
  `@src/foo` accepted from after `src` left `/foo` behind.
  removeMentionToken takes the whole token.
- The dedupe key folded case unconditionally, so on a case-sensitive
  filesystem `Foo.ts` and `foo.ts` collapsed and one silently vanished.
  Folding is now conditional on the filesystem, injectable for tests.
- `@**` widened to a match-everything glob, scanned the workspace, then
  filtered every result away against the raw `**` needle. Queries with no
  literal characters no longer touch disk, and result filtering uses the
  metacharacter-stripped query so `*app*` still finds app.ts.
- The listbox had role=option rows but nothing tied them to the textarea.
  Added aria-activedescendant, plus role=combobox / aria-expanded /
  aria-controls and stable row ids.
- The mention Escape branch called preventDefault without
  stopPropagation. No handler above it cancels on Escape today, so this is
  not reachable, but the branch should not depend on that.
- The fold had swapped the hand-rolled posix path helpers for node's
  `path`. `path` binds to the host separator, so the Windows-path tests
  passed locally and meant nothing on Linux. Helpers are hand-rolled
  against the posix form again.

Also restores the 100 KB read cap and binary sniff on attached files,
scoped out of the fold as a separate concern — @ makes attaching a
lockfile one keystroke, so it belongs with this change.

The styled-select-input spec fix is dropped from this branch; it is
byte-identical to fix/styled-select-input-ansi-assertions.
Per review feedback, these ANSI stripping changes are unrelated to the @ mention feature and should be in a separate PR.
Implements all maintainer-requested fixes for sunsetting /setup-providers
and /setup-mcp commands in favor of /settings.

## Key Changes

### 1. Provider Reload Without Wiping Conversation
- Created `reloadProviders()` in useModeHandlers following the
  `reloadMcpServers` pattern
- Reloads config and rebuilds client for current provider/model
- Preserves message history (no setMessages([]))
- Wired through component chain to settings-providers-list

### 2. Fixed State Update on Unmounting Component
- settings-providers-list now calls setEditing(false) instead of onBack()
- Parent owns navigation control
- Prevents "setState on unmounted component" warning

### 3. Tab Preservation When Returning from Wizards
- Added settingsActiveTab state to useAppState
- Updated enterSettingsMode to accept optional tab parameter
- Capture active tab before launching Tune/IDE wizards
- Restore captured tab when returning to settings
- Wired onTabChange callback through component chain

### 4. Tab Argument Validation
- Moved SETTINGS command from enterModeCommands table to switch
- Validates tab argument against SETTINGS_TAB_IDS
- Shows helpful error message for invalid tabs
- Proper type narrowing (no 'as any')

### 5. Constants Module to Prevent Dependency Bloat
- Created source/app/components/settings-constants.ts
- Exports SettingsTabId type and SETTINGS_TAB_IDS array
- Prevents app-util.ts from pulling in entire settings-tabs module
- Updated all imports across codebase

## Testing
- ✅ TypeScript compilation (tsc --noEmit): PASSED
- ✅ Biome linter (445 files): PASSED, no errors
- ✅ Knip unused code check: PASSED
- ✅ All integration points verified
- ✅ Edge cases tested (tab preservation, validation, reload)

## Files Changed (12)
- New: source/app/components/settings-constants.ts
- Modified: App.tsx, modal-selectors.tsx, settings-providers-list.tsx,
  settings-selector.tsx, settings-tabs.tsx, interactive-app.tsx,
  app-util.ts, useAppHandlers.tsx, useAppState.tsx,
  useModeHandlers.tsx, types/app.ts

Closes Nano-Collective#832 review items from @will-lamerton
@addyCooks
addyCooks force-pushed the feat/sunset-setup-commands branch from 28fa5f1 to b15db36 Compare August 14, 2026 07:04
The enterSettingsMode function now requires setSettingsActiveTab parameter.
Added the missing spy to the test setup to fix the failing test.

Fixes test: enterSettingsMode toggles settings flag, handleSettingsCancel clears it
@addyCooks

Copy link
Copy Markdown
Contributor Author

@will-lamerton hey! thanks for the thorough review..
just pushed the fixes you called out, everything should be good now

lmk if anything needs adjusting!

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.

Sunset /setup-providers and /setup-mcp in favour of /settings

2 participants