Skip to content

Modes selector improvements #4902

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 36 commits into from
Jun 24, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
f4fb0bf
Makes it possible to hide typeahead search in dropdowns
Jun 17, 2025
1837400
Redesigns the mode selector
Jun 17, 2025
5ac5a4e
Only shows the mode marketplace button if the setting is enabled
Jun 19, 2025
b4bdeb2
Opens the marketplace from the mode selector
Jun 19, 2025
aea3481
Only show mode description is available
Jun 19, 2025
489d4a2
Adds short descriptions to default modes
Jun 19, 2025
52c5e95
Conditionally highlights the mode selector button until the user clic…
Jun 19, 2025
5835621
Refactors the way to track whether the user has opened the mode selector
Jun 19, 2025
1da0fd8
Localizes ModeSelector strings
Jun 19, 2025
7ef7202
Makes Marketplace icon not conditional on dead experiment
Jun 19, 2025
e45d2da
Fixes type errors
Jun 19, 2025
fdb92d9
Internationalizes ModeSelector strings once again
Jun 20, 2025
c903eb8
Removes unnecessary Telemetry code
Jun 20, 2025
dcb2cab
Enables deep linking into subtabs of MarketplaceView and uses it to l…
Jun 20, 2025
df5574f
Uses short mode description by default in the slash command picker
Jun 20, 2025
b0d714f
Removes the Modes settings icon from the top bar, deferring to the on…
Jun 22, 2025
3dc4df6
Makes mode short descriptions editable
Jun 22, 2025
3afab13
Some visual cleanup of ModesView, as I add yet another section
Jun 22, 2025
df7a9c3
Properly loads short descriptions in settings
Jun 22, 2025
5dc5c31
Fixes saving of description when creating and editing modes
Jun 22, 2025
a5018d5
Only allow for customizing shoprt descriptions of custom modes, leavi…
Jun 22, 2025
9f126dc
Updates strings to reflect there's no Modes tab anymore, but settings
Jun 22, 2025
19ef6f2
Localized strings for mode configuration UI
Jun 22, 2025
f489b0b
Adds telemetry for modes: mode created, mode modified, mode settings …
Jun 22, 2025
61d1799
Better type safety
Jun 23, 2025
7302a3e
Removes unnecessary fallback case
Jun 23, 2025
0185d09
Removes PR cruft
Jun 23, 2025
75dc3db
Merge remote-tracking branch 'origin/main' into modes-selector-improv…
mrubens Jun 23, 2025
1b308ca
Reset READMEs
mrubens Jun 24, 2025
03e4fe9
Reset contributor script
mrubens Jun 24, 2025
07f5f3f
Fix tests
mrubens Jun 24, 2025
57977aa
Track all tab switches in posthog
mrubens Jun 24, 2025
35e2db2
Add a missing translation
mrubens Jun 24, 2025
4452bdc
fix: streamline description rendering for custom modes
daniel-lxs Jun 24, 2025
0a0c0da
feat: enhance ModeSelector with custom mode prompts and add tests
daniel-lxs Jun 24, 2025
0bbdd32
fix: update description section behavior for built-in modes in Prompt…
daniel-lxs Jun 24, 2025
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
36 changes: 18 additions & 18 deletions locales/ca/README.md

Large diffs are not rendered by default.

36 changes: 18 additions & 18 deletions locales/de/README.md

Large diffs are not rendered by default.

36 changes: 18 additions & 18 deletions locales/es/README.md

Large diffs are not rendered by default.

36 changes: 18 additions & 18 deletions locales/fr/README.md

Large diffs are not rendered by default.

36 changes: 18 additions & 18 deletions locales/hi/README.md

Large diffs are not rendered by default.

36 changes: 18 additions & 18 deletions locales/id/README.md

Large diffs are not rendered by default.

36 changes: 18 additions & 18 deletions locales/it/README.md

Large diffs are not rendered by default.

36 changes: 18 additions & 18 deletions locales/ja/README.md

Large diffs are not rendered by default.

36 changes: 18 additions & 18 deletions locales/ko/README.md

Large diffs are not rendered by default.

36 changes: 18 additions & 18 deletions locales/nl/README.md

Large diffs are not rendered by default.

36 changes: 18 additions & 18 deletions locales/pl/README.md

Large diffs are not rendered by default.

36 changes: 18 additions & 18 deletions locales/pt-BR/README.md

Large diffs are not rendered by default.

36 changes: 18 additions & 18 deletions locales/ru/README.md

Large diffs are not rendered by default.

36 changes: 18 additions & 18 deletions locales/tr/README.md

Large diffs are not rendered by default.

36 changes: 18 additions & 18 deletions locales/vi/README.md

Large diffs are not rendered by default.

36 changes: 18 additions & 18 deletions locales/zh-CN/README.md

Large diffs are not rendered by default.

36 changes: 18 additions & 18 deletions locales/zh-TW/README.md

Large diffs are not rendered by default.

25 changes: 25 additions & 0 deletions packages/telemetry/src/TelemetryService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,31 @@ export class TelemetryService {
this.captureEvent(TelemetryEventName.CONSECUTIVE_MISTAKE_ERROR, { taskId })
}

/**
* Captures when a tab is shown due to user action
* @param tab The tab that was shown
*/
public captureTabShown(tab: string): void {
this.captureEvent(TelemetryEventName.TAB_SHOWN, { tab })
}

/**
* Captures when a setting is changed in ModesView
* @param settingName The name of the setting that was changed
*/
public captureModeSettingChanged(settingName: string): void {
this.captureEvent(TelemetryEventName.MODE_SETTINGS_CHANGED, { settingName })
}

/**
* Captures when a user creates a new custom mode
* @param modeSlug The slug of the custom mode
* @param modeName The name of the custom mode
*/
public captureCustomModeCreated(modeSlug: string, modeName: string): void {
this.captureEvent(TelemetryEventName.CUSTOM_MODE_CREATED, { modeSlug, modeName })
}

/**
* Captures a marketplace item installation event
* @param itemId The unique identifier of the marketplace item
Expand Down
1 change: 1 addition & 0 deletions packages/types/src/global-settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ export const globalSettingsSchema = z.object({
enhancementApiConfigId: z.string().optional(),
historyPreviewCollapsed: z.boolean().optional(),
profileThresholds: z.record(z.string(), z.number()).optional(),
hasOpenedModeSelector: z.boolean().optional(),
})

export type GlobalSettings = z.infer<typeof globalSettingsSchema>
Expand Down
2 changes: 2 additions & 0 deletions packages/types/src/mode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ export const modeConfigSchema = z.object({
name: z.string().min(1, "Name is required"),
roleDefinition: z.string().min(1, "Role definition is required"),
whenToUse: z.string().optional(),
description: z.string().optional(),
customInstructions: z.string().optional(),
groups: groupEntryArraySchema,
source: z.enum(["global", "project"]).optional(),
Expand Down Expand Up @@ -106,6 +107,7 @@ export type CustomModesSettings = z.infer<typeof customModesSettingsSchema>
export const promptComponentSchema = z.object({
roleDefinition: z.string().optional(),
whenToUse: z.string().optional(),
description: z.string().optional(),
customInstructions: z.string().optional(),
})

Expand Down
7 changes: 7 additions & 0 deletions packages/types/src/telemetry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ export enum TelemetryEventName {
CHECKPOINT_RESTORED = "Checkpoint Restored",
CHECKPOINT_DIFFED = "Checkpoint Diffed",

TAB_SHOWN = "Tab Shown",
MODE_SETTINGS_CHANGED = "Mode Setting Changed",
CUSTOM_MODE_CREATED = "Custom Mode Created",

CONTEXT_CONDENSED = "Context Condensed",
SLIDING_WINDOW_TRUNCATION = "Sliding Window Truncation",

Expand Down Expand Up @@ -119,6 +123,9 @@ export const rooCodeTelemetryEventSchema = z.discriminatedUnion("type", [
TelemetryEventName.CONSECUTIVE_MISTAKE_ERROR,
TelemetryEventName.CONTEXT_CONDENSED,
TelemetryEventName.SLIDING_WINDOW_TRUNCATION,
TelemetryEventName.TAB_SHOWN,
TelemetryEventName.MODE_SETTINGS_CHANGED,
TelemetryEventName.CUSTOM_MODE_CREATED,
]),
properties: telemetryPropertiesSchema,
}),
Expand Down
1 change: 1 addition & 0 deletions src/core/webview/ClineProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1485,6 +1485,7 @@ export class ClineProvider
},
mdmCompliant: this.checkMdmCompliance(),
profileThresholds: profileThresholds ?? {},
hasOpenedModeSelector: this.getGlobalState("hasOpenedModeSelector") ?? false,
}
}

Expand Down
1 change: 1 addition & 0 deletions src/core/webview/__tests__/ClineProvider.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -523,6 +523,7 @@ describe("ClineProvider", () => {
cloudIsAuthenticated: false,
sharingEnabled: false,
profileThresholds: {},
hasOpenedModeSelector: false,
}

const message: ExtensionMessage = {
Expand Down
61 changes: 59 additions & 2 deletions src/core/webview/webviewMessageHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -942,8 +942,27 @@ export const webviewMessageHandler = async (
const updatedPrompts = { ...existingPrompts, [message.promptMode]: message.customPrompt }
await updateGlobalState("customModePrompts", updatedPrompts)
const currentState = await provider.getStateToPostToWebview()
const stateWithPrompts = { ...currentState, customModePrompts: updatedPrompts }
const stateWithPrompts = {
...currentState,
customModePrompts: updatedPrompts,
hasOpenedModeSelector: currentState.hasOpenedModeSelector ?? false,
}
provider.postMessageToWebview({ type: "state", state: stateWithPrompts })

if (TelemetryService.hasInstance()) {
// Determine which setting was changed by comparing objects
const oldPrompt = existingPrompts[message.promptMode] || {}
Copy link
Author

Choose a reason for hiding this comment

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

I don't love the complexity of this logic and the fact it's repeated elsewhere, but this was Roo's insistent suggestion on how to determine the changed setting and I couldn't figure out a different one which didn't lead to much more complex changes (eg one global event per setting change)

const newPrompt = message.customPrompt
const changedSettings = Object.keys(newPrompt).filter(
(key) =>
JSON.stringify((oldPrompt as Record<string, unknown>)[key]) !==
JSON.stringify((newPrompt as Record<string, unknown>)[key]),
)

if (changedSettings.length > 0) {
TelemetryService.instance.captureModeSettingChanged(changedSettings[0])
}
}
}
break
case "deleteMessage": {
Expand Down Expand Up @@ -1079,6 +1098,10 @@ export const webviewMessageHandler = async (
await updateGlobalState("showRooIgnoredFiles", message.bool ?? true)
await provider.postStateToWebview()
break
case "hasOpenedModeSelector":
await updateGlobalState("hasOpenedModeSelector", message.bool ?? true)
await provider.postStateToWebview()
break
case "maxReadFileLine":
await updateGlobalState("maxReadFileLine", message.value)
await provider.postStateToWebview()
Expand Down Expand Up @@ -1408,12 +1431,41 @@ export const webviewMessageHandler = async (
break
case "updateCustomMode":
if (message.modeConfig) {
// Check if this is a new mode or an update to an existing mode
const existingModes = await provider.customModesManager.getCustomModes()
const isNewMode = !existingModes.some((mode) => mode.slug === message.modeConfig?.slug)

await provider.customModesManager.updateCustomMode(message.modeConfig.slug, message.modeConfig)
// Update state after saving the mode
const customModes = await provider.customModesManager.getCustomModes()
await updateGlobalState("customModes", customModes)
await updateGlobalState("mode", message.modeConfig.slug)
await provider.postStateToWebview()

// Track telemetry for custom mode creation or update
if (TelemetryService.hasInstance()) {
if (isNewMode) {
// This is a new custom mode
TelemetryService.instance.captureCustomModeCreated(
message.modeConfig.slug,
message.modeConfig.name,
)
} else {
// Determine which setting was changed by comparing objects
const existingMode = existingModes.find((mode) => mode.slug === message.modeConfig?.slug)
const changedSettings = existingMode
? Object.keys(message.modeConfig).filter(
(key) =>
JSON.stringify((existingMode as Record<string, unknown>)[key]) !==
JSON.stringify((message.modeConfig as Record<string, unknown>)[key]),
)
: []

if (changedSettings.length > 0) {
TelemetryService.instance.captureModeSettingChanged(changedSettings[0])
}
}
}
}
break
case "deleteCustomMode":
Expand Down Expand Up @@ -1598,6 +1650,7 @@ export const webviewMessageHandler = async (
)
await provider.postStateToWebview()
console.log(`Marketplace item installed and config file opened: ${configFilePath}`)

// Send success message to webview
provider.postMessageToWebview({
type: "marketplaceInstallResult",
Expand Down Expand Up @@ -1650,7 +1703,11 @@ export const webviewMessageHandler = async (

case "switchTab": {
if (message.tab) {
// Send a message to the webview to switch to the specified tab
// Capture tab shown event for all switchTab messages (which are user-initiated)
if (TelemetryService.hasInstance()) {
TelemetryService.instance.captureTabShown(message.tab)
}

await provider.postMessageToWebview({ type: "action", action: "switchTab", tab: message.tab })
}
break
Expand Down
37 changes: 11 additions & 26 deletions src/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -80,11 +80,6 @@
"title": "%command.mcpServers.title%",
"icon": "$(server)"
},
{
"command": "roo-cline.promptsButtonClicked",
"title": "%command.prompts.title%",
"icon": "$(organization)"
},
{
"command": "roo-cline.historyButtonClicked",
"title": "%command.history.title%",
Expand Down Expand Up @@ -219,39 +214,34 @@
"group": "navigation@1",
"when": "view == roo-cline.SidebarProvider"
},
{
"command": "roo-cline.promptsButtonClicked",
"group": "navigation@2",
"when": "view == roo-cline.SidebarProvider"
},
{
"command": "roo-cline.mcpButtonClicked",
"group": "navigation@3",
"group": "navigation@2",
"when": "view == roo-cline.SidebarProvider"
},
{
"command": "roo-cline.marketplaceButtonClicked",
"group": "navigation@4",
"group": "navigation@3",
"when": "view == roo-cline.SidebarProvider"
},
{
"command": "roo-cline.historyButtonClicked",
"group": "navigation@5",
"group": "navigation@4",
"when": "view == roo-cline.SidebarProvider"
},
{
"command": "roo-cline.popoutButtonClicked",
"group": "navigation@6",
"group": "navigation@5",
"when": "view == roo-cline.SidebarProvider"
},
{
"command": "roo-cline.accountButtonClicked",
"group": "navigation@7",
"group": "navigation@6",
"when": "view == roo-cline.SidebarProvider && config.roo-cline.rooCodeCloudEnabled"
},
{
"command": "roo-cline.settingsButtonClicked",
"group": "navigation@8",
"group": "navigation@7",
"when": "view == roo-cline.SidebarProvider"
}
],
Expand All @@ -261,34 +251,29 @@
"group": "navigation@1",
"when": "activeWebviewPanelId == roo-cline.TabPanelProvider"
},
{
"command": "roo-cline.promptsButtonClicked",
"group": "navigation@2",
"when": "activeWebviewPanelId == roo-cline.TabPanelProvider"
},
{
"command": "roo-cline.mcpButtonClicked",
"group": "navigation@3",
"group": "navigation@2",
"when": "activeWebviewPanelId == roo-cline.TabPanelProvider"
},
{
"command": "roo-cline.marketplaceButtonClicked",
"group": "navigation@4",
"group": "navigation@3",
"when": "activeWebviewPanelId == roo-cline.TabPanelProvider"
},
{
"command": "roo-cline.historyButtonClicked",
"group": "navigation@5",
"group": "navigation@4",
"when": "activeWebviewPanelId == roo-cline.TabPanelProvider"
},
{
"command": "roo-cline.accountButtonClicked",
"group": "navigation@6",
"group": "navigation@5",
"when": "activeWebviewPanelId == roo-cline.TabPanelProvider && config.roo-cline.rooCodeCloudEnabled"
},
{
"command": "roo-cline.settingsButtonClicked",
"group": "navigation@7",
"group": "navigation@6",
"when": "activeWebviewPanelId == roo-cline.TabPanelProvider"
}
]
Expand Down
1 change: 1 addition & 0 deletions src/shared/ExtensionMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,7 @@ export type ExtensionState = Pick<
marketplaceItems?: MarketplaceItem[]
marketplaceInstalledMetadata?: { project: Record<string, any>; global: Record<string, any> }
profileThresholds: Record<string, number>
hasOpenedModeSelector: boolean
}

export interface ClineSayTool {
Expand Down
1 change: 1 addition & 0 deletions src/shared/WebviewMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ export interface WebviewMessage {
| "searchFiles"
| "toggleApiConfigPin"
| "setHistoryPreviewCollapsed"
| "hasOpenedModeSelector"
| "accountButtonClicked"
| "rooCloudSignIn"
| "rooCloudSignOut"
Expand Down
Loading
Loading