Skip to content

feat(ui): make configuration pickers compact and inline - #2182

Open
kevinjosethomas wants to merge 19 commits into
mainfrom
feat/ui-inline-pickers
Open

feat(ui): make configuration pickers compact and inline#2182
kevinjosethomas wants to merge 19 commits into
mainfrom
feat/ui-inline-pickers

Conversation

@kevinjosethomas

@kevinjosethomas kevinjosethomas commented Sep 10, 2026

Copy link
Copy Markdown
Member
  • move model, provider, and mcp pickers into the prompt area with compact searchable rows
  • show selected-model input, cached-input, and output rates per million tokens
  • split the configuration menu into separate single-purpose pickers with no tab bar; each command opens only its own picker
  • right-align the provider in each model row, move the sign-in hint to its left as "require sign in", and order signed-in providers first with Prime Inference pinned on top when signed in
  • add per-model effort squares to the models picker: left and right adjust the highlighted model's effort level, squares fill up to the selection, and enter applies model and effort together
  • keep signed-in providers first in picker search results, above better-scoring unsigned matches
  • soften the selected-row highlight: bold text in the normal color on a subtle blended band instead of the opaque block with accent-colored text
  • align the effort squares across rows, space them as distinct squares, add adjust arrows on the highlighted row, and show the level name on the row
  • fold the pricing unit onto the provider/model detail line and end the block with clear whitespace
  • round token prices to three decimals, rendering tiny nonzero prices as <0.001
  • render effort marks as literal square glyphs (■/□), centered near the row midpoint, colored by selection: dark-gray empties everywhere, light-gray fills on non-highlighted rows, saturated purple fills only on the highlighted row
  • drop the "Models", "Providers", and "MCP" picker titles and explanatory subtitles so the search row leads each picker
  • move the pricing unit onto the price row as "dollars per 1 million tokens" trailing the output price
  • keep the effort cluster width fixed across level changes by reserving the longest supported level-name width
  • fill effort squares with a pastel purple (#a78bfa dark / #8b5cf6 light) only on the highlighted row, and use the compact "$ / 1M tokens" unit on the price row
  • keep the effort marks as ■/□, the largest square glyphs available in common terminal fallback fonts
  • drop the model detail header line so the block is just the price rows and their whitespace
  • validated npm run check and 215 focused tests

no-ticket: ui proposals tracked in https://app.notion.com/p/3d672940136f817b99a3d97c7d5fa048


Note

Medium Risk
Changes model-selection UX and callback signatures (optional thinking level), plus auth/model apply flows guarded by a busy flag—behavioral regressions are possible though heavily tested.

Overview
Replaces the full-pane configuration overlay with compact inline pickers mounted in the prompt area; closing or canceling restores the editor and draft text. The tab bar is removed—each slash command opens one picker (models, providers, or MCP) led by a bordered search row, with softer row selection styling.

The models picker gains right-aligned provider metadata with a require sign in hint, signed-in-first ordering (Prime Inference pinned when authenticated), effort/reasoning controls (←/→ on the highlighted row, Enter applies model and level together), and inline input/cached/output rates per million tokens with sensible rounding.

Shared menu components gain an inline layout path (trailing segments, width budgeting, page up/down). Onboarding now dismisses the splash before opening model selection rather than holding it until the picker closes.

Reviewed by Cursor Bugbot for commit d07c77f. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Make configuration pickers render inline in the editor container

  • Converts the configuration menu from a full-pane overlay to an inline picker that mounts directly in the editor container, restoring the editor draft on close or cancel
  • Removes the tab bar and tab-switching logic from ConfigurationMenuComponent; each provider, model, and MCP picker is now a standalone single-purpose body with a responsive footer hint
  • Adds inline layout mode to MenuPanel, MenuSearchInput, MenuRow, and MenuList with compact bordered rows, soft selection highlighting, and right-aligned trailing status segments
  • Adds per-model thinking-level (effort) controls to ModelSelectorComponent with filled/unfilled square indicators, cyclic left/right adjustment, and a callback that reports the selected level on confirm
  • Adds responsive inline pricing display for selected models (input, cached-input, output rates) with wide aligned and narrow stacked formats, plus configured-provider and Prime Inference ordering priority in search results
  • Behavioral Change: InteractiveMode.showConfigurationMenu no longer opens a full-pane overlay; tests and out-of-tree callers that depend on overlay lifecycle handles must switch to container-membership checks. ConfigurationMenuComponent.handleInput no longer forwards left/right keys to the search input — those now adjust effort level. Unconfigured model wording changes from "sign in" to "require sign in".

Macroscope summarized d07c77f.

@github-actions

github-actions Bot commented Sep 10, 2026

Copy link
Copy Markdown

Prime Agent performance — completed

PR d07c77f8 compared with main d4bc773d.

Overall: 0 regressed · 0 improved · 17 no clear change.

Metric Main This PR Change
Cold startup 2,780.7 ms 2,863.6 ms ≈ +82.9 ms (+2.98%)
Warm startup 1,657.3 ms 1,642.5 ms ≈ -14.8 ms (-0.90%)
Installation 25.28 s 25.82 s ≈ +0.53 s (+2.11%)
Compressed release artifacts 11.10 MB 11.12 MB ≈ +0.02 MB (+0.22%)
Installed footprint 597.35 MB 597.45 MB ≈ +0.10 MB (+0.02%)
Idle memory, summed RSS 1,007.09 MB 1,011.43 MB ≈ +4.35 MB (+0.43%)

Python runtime

Metric Main This PR Change
Python kernel startup 120.9 ms 121.2 ms ≈ +0.4 ms (+0.33%)
Python cell round trip 0.471 ms 0.508 ms ≈ +0.037 ms (+7.89%)
Empty bash command 10.4 ms 11.2 ms ≈ +0.8 ms (+7.29%)
Bash git status 15.7 ms 16.3 ms ≈ +0.6 ms (+3.64%)
Bash 32 KiB output 10.9 ms 11.7 ms ≈ +0.9 ms (+7.90%)
35 cells / 9 shell calls 157.7 ms 164.1 ms ≈ +6.4 ms (+4.06%)
Python interrupt to done 1.434 ms 1.391 ms ≈ -0.042 ms (-2.95%)
Python state snapshot 25.2 ms 24.6 ms ≈ -0.6 ms (-2.23%)
Python state restore 347.2 ms 351.1 ms ≈ +3.9 ms (+1.12%)
Python idle RSS 33.20 MB 36.95 MB ≈ +3.75 MB (+11.28%)
Python RSS after pandas workload 95.08 MB 98.91 MB ≈ +3.83 MB (+4.03%)

Sandbox cost: ~$0.0861 — no inference calls.
Run, logs, and downloadable raw results

Methodology and samples

Main resolved at 2026-09-10T07:09:16.214281+00:00. Harness d4bc773d.
Linux x64, 4 vCPU, 8 GB RAM, 20 GB disk; region us.
Image: node:24-bookworm@sha256:be23f54a88d34e8824c741b19b91064094f92c1c97b194144bfc8b50d67258e2.
Stock tools, skills, daemon, and Python bootstrap enabled; fresh homes and a fixed Git fixture.
Onboarding is dismissed; the editor starts without a selected model or submitted prompt.
Medians shown. Arrows require a 20% timing/memory change plus absolute floors and IQR.
These practical noise floors are not a statistical significance test.
Cold means stopped Prime processes; OS filesystem caches are not flushed.
No model requests or credentials. Installation excludes build/setup time.
Installer tarballs use loopback; npm/Python downloads use the network with fresh caches.
Artifact size counts release tarballs; footprint after first use includes registry packages.
MB is decimal. Summed RSS can double-count shared pages; PSS is recorded when available.
Provisioning, setup, and build durations are recorded separately in the raw results.
Kernel probes use the installed JSONL runtime, outside the TUI/TypeScript host.
Per trial: 50 Python cells, 5 calls per shell case, and one 35-cell mix (9 git status calls).
Cell/shell values are batch means; other runtime timings are single operations.
State fixture: a 10,000-row × 8-column integer DataFrame and a 10,000-integer list.
Restore runs in a fresh kernel, including pandas imports; kernel startup is excluded.
Kernel RSS covers the isolated Python process; loaded RSS follows the pandas workload.
Costs estimate full sandbox lifetimes at configured rates, including setup and build.
Budget target: $1; not a billing cap. Checks are informational.

Metric Main successful/attempted PR successful/attempted Main spread PR spread
Cold startup 10/10 10/10 IQR 96.2 ms IQR 117.7 ms
Warm startup 10/10 10/10 IQR 46.7 ms IQR 38.6 ms
Installation 3/3 3/3 range 0.28 s range 0.52 s
Compressed release artifacts 1/1 1/1
Installed footprint 1/1 1/1
Idle memory, summed RSS 10/10 10/10 IQR 35.60 MB IQR 37.90 MB
Python kernel startup 10/10 10/10 IQR 4.3 ms IQR 4.9 ms
Python cell round trip 10/10 10/10 IQR 0.019 ms IQR 0.087 ms
Empty bash command 10/10 10/10 IQR 0.4 ms IQR 1.0 ms
Bash git status 10/10 10/10 IQR 0.6 ms IQR 2.0 ms
Bash 32 KiB output 10/10 10/10 IQR 0.9 ms IQR 0.4 ms
35 cells / 9 shell calls 10/10 10/10 IQR 6.8 ms IQR 16.5 ms
Python interrupt to done 10/10 10/10 IQR 0.052 ms IQR 0.170 ms
Python state snapshot 10/10 10/10 IQR 2.2 ms IQR 0.6 ms
Python state restore 10/10 10/10 IQR 17.3 ms IQR 18.1 ms
Python idle RSS 10/10 10/10 IQR 4.10 MB IQR 3.27 MB
Python RSS after pandas workload 10/10 10/10 IQR 4.66 MB IQR 2.92 MB

Comment on lines 8246 to 8247
conceal();
await this.completeModelSelection(model);

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.

🟠 High interactive/interactive-mode.ts:8246

The editor regains focus while completeModelSelection(model) is still pending, so users can start another model selection and run setModel concurrently; a slower first selection can then overwrite the newer choice. Keep the configuration menu focused until completeModelSelection completes, and only call conceal() afterward.

-							conceal();
 							await this.completeModelSelection(model);
+							conceal();
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @packages/coding-agent/src/modes/interactive/interactive-mode.ts around lines 8246-8247:

The editor regains focus while `completeModelSelection(model)` is still pending, so users can start another model selection and run `setModel` concurrently; a slower first selection can then overwrite the newer choice. Keep the configuration menu focused until `completeModelSelection` completes, and only call `conceal()` afterward.

Evidence trail:
c7559680: packages/coding-agent/src/modes/interactive/interactive-mode.ts:8132-8153 (conceal restores editor focus); 8230-8255 (local busy guard, conceal before awaiting completeModelSelection, completion/finalization); 4751-4755 (editor /model command); 8121-8122 (opening a new menu closes the prior one without cancelling its pending async work); 7777-7781 (asynchronous setModel call). Supporting implementations: packages/coding-agent/src/modes/agent-connection/in-process-agent-connection.ts:450-462 and packages/coding-agent/src/modes/agent-connection/daemon-agent-connection.ts:1196-1202.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale Bugbot comment from a previous run.

Comment thread packages/coding-agent/src/modes/interactive/components/menu-panel.ts Outdated

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale Bugbot comment from a previous run.

@@ -163,12 +93,12 @@ export class ConfigurationMenuComponent extends Container implements Focusable {
options.onCancel,
(providerId) => options.modelRegistry.getProviderAuthStatus(providerId),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Dead configuration tab keybinding

Low Severity

Removing tab navigation left app.configuration.previousTab with no non-test consumer. The exported keybinding still ships in the public map, so custom keybindings.json entries and Shift+Tab resolve to a no-op after the completed picker split.

Fix in Cursor Fix in Web

Triggered by project rule: Review rules

Reviewed by Cursor Bugbot for commit cf53afe. Configure here.

this.activeBody.getSearchInput().handleInput(keyData);
return;
}
this.activeBody.handleInput(keyData);

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.

🟡 Medium components/configuration-menu.ts:171

Left/right input is consumed by ModelSelectorComponent for non-reasoning models, so users cannot move the search cursor or close the picker with left-at-start. Restore the model-tab handling that forwards these keys directly to MenuSearchInput before delegating to activeBody.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @packages/coding-agent/src/modes/interactive/components/configuration-menu.ts around line 171:

Left/right input is consumed by `ModelSelectorComponent` for non-reasoning models, so users cannot move the search cursor or close the picker with left-at-start. Restore the model-tab handling that forwards these keys directly to `MenuSearchInput` before delegating to `activeBody`.

Evidence trail:
Reviewed commit 162659e6ffedae38d6ac309dd6bb825b7784b8cb
- packages/coding-agent/src/modes/interactive/components/configuration-menu.ts:170-172
- packages/coding-agent/src/modes/interactive/components/model-selector.ts:379-400, 583-627
- packages/coding-agent/src/modes/interactive/components/modal-back.ts:26-30
- packages/coding-agent/src/core/keybindings.ts:157

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 3 potential issues.

There are 4 total unresolved issues (including 1 from previous review).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 4c6c64e. Configure here.

await this.completeModelSelection(model);
if (thinkingLevel !== undefined) {
this.applyThinkingLevel(thinkingLevel);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Picker effort overrides model-switch default

High Severity

Confirming a model now always applies the picker's resolved effort, which starts from connectionState.thinkingLevel or off. That overwrites setModel's restore path: when the current model has no reasoning, _getThinkingLevelForModelSwitch reapplies the saved default (usually medium), and applyThinkingLevel then forces off. First-time and non-reasoning-to-reasoning switches disable thinking even if the user never touched effort.

Additional Locations (2)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 4c6c64e. Configure here.

await this.completeModelSelection(model);
if (thinkingLevel !== undefined) {
this.applyThinkingLevel(thinkingLevel);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Selected effort applied without waiting

Medium Severity

applyThinkingLevel is fire-and-forget after completeModelSelection, and finish() still resolves the picker promise. Callers that await showConfigurationMenu (including onboarding) and users who submit immediately can run against the level setModel just wrote, not the squares they confirmed.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 4c6c64e. Configure here.

this.updateList();
this.tui.requestRender();
}
return;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Arrows always steal search editing

Medium Severity

Left and right now always return from handleInput, even when adjustEffort does nothing. That removes cursor movement in the search field and drops the earlier forward-to-search path. Highlighting a non-reasoning row, or editing a filter, leaves the arrows dead, unlike OAuthSelectorComponent, which only steals them when the field is empty.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 4c6c64e. Configure here.

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.

1 participant