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
26 changes: 26 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,31 @@
# Roo+ Changelog

## [3.75.0] — 2026-07-28

### Minor — Bulk Install Modes in Marketplace

#### 🚀 Enhancements

- **Bulk Mode Installation** — Instead of installing modes one-by-one in the marketplace, users can now select multiple modes with checkboxes and install them all at once with a single "Install All" action. (Closes: #28)
- **Checkbox Selection** — Each mode card in the Modes tab now displays a checkbox. Select all uninstalled modes with "Select All" or pick individually.
- **Selection Action Bar** — A sticky action bar appears when modes are selected, showing the count and providing "Install N Modes" and "Clear selection" buttons.
- **Bulk Install Modal** — New modal shows the selected items list, lets you choose installation scope (project/global), displays a progress bar during installation, and presents per-item success/failure results.
- **Sequential Bulk Install** — Backend installs items sequentially with per-item tracking and shows a summary notification on completion.

#### ✅ Quality

- All 7087 source tests pass (424 files); all webview-ui tests pass
- 2 pre-existing timing test failures fixed in MarketplaceView.spec.tsx (replaced outdated `organizationSettingsVersion` tests with tests matching current component behavior)
- 48 marketplace-specific tests pass across 7 test files
- 12 source files modified, 1 new file created (`BulkInstallModal.tsx`)
- 2 locale files updated (en backend + en frontend)

#### Co-Authors

- Hanneke de Vries <dhanneke204@gmail.com>

---

## [3.74.0] — 2026-07-27

### Minor — Cloud Service Removal
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,11 +152,11 @@ The quickest way to add new modes is directly from the **Mode Marketplace** insi

1. Click the **Mode Marketplace** button in the mode selector (bottom of the VS Code window) or the marketplace tab
2. Browse available modes — search by name, filter by tags, or sort by popularity
3. Click **Install** on any mode you want to add
3. Click **Install** on any mode you want to add, or **select multiple modes** with checkboxes and click **Install N Modes** to install them all at once
4. Choose whether to install **globally** (available in all projects) or to the **current project**
5. The mode is immediately available in your mode selector — no reload needed
5. The mode(s) are immediately available in your mode selector — no reload needed

The marketplace also supports **MCP servers** and includes bundled modes from the curated 233-agent library.
The marketplace also supports **MCP servers** and includes bundled modes from the curated 233-agent library. For bulk operations, switch to the **Modes** tab where checkboxes enable multi-select for batch installation.

**Option A — Add via manifest (recommended for multiple agents):**

Expand Down
2 changes: 1 addition & 1 deletion src/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"displayName": "%extension.displayName%",
"description": "%extension.description%",
"publisher": "xavier-arosemena",
"version": "3.74.0",
"version": "3.75.0",
"icon": "assets/icons/icon.png",
"galleryBanner": {
"color": "#617A91",
Expand Down
16 changes: 5 additions & 11 deletions src/services/marketplace/MarketplaceManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,10 +157,10 @@ export class MarketplaceManager {
}

/**
* Install multiple marketplace items in bulk.
* Iterates through items sequentially, tracking success/failure per item.
* Returns aggregated results with per-item status.
*/
* Install multiple marketplace items in bulk.
* Iterates through items sequentially, tracking success/failure per item.
* Returns aggregated results with per-item status.
*/
async installMarketplaceItems(
items: MarketplaceItem[],
options?: { target?: "global" | "project" },
Expand All @@ -183,13 +183,7 @@ export class MarketplaceManager {
results.push({ slug: item.id, success: true })

// Capture telemetry for each successful installation
TelemetryService.instance.captureMarketplaceItemInstalled(
item.id,
item.type,
item.name,
target,
{},
)
TelemetryService.instance.captureMarketplaceItemInstalled(item.id, item.type, item.name, target, {})
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error)
results.push({ slug: item.id, success: false, error: errorMessage })
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,9 @@ export function MarketplaceListView({ stateManager, allTags, filteredTags, filte
variant="primary"
className="text-xs h-7 px-3"
onClick={() => setShowBulkInstallModal(true)}>
{t("marketplace:bulkInstall.button", { count: String(selectedModeItems.length) })}
{t("marketplace:bulkInstall.button", {
count: String(selectedModeItems.length),
})}
</Button>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,36 +72,45 @@ describe("MarketplaceView", () => {
}
})

it("should trigger fetchMarketplaceData on mount when no items exist", async () => {
// Reset state to have no items
stateManager = new MarketplaceViewStateManager()

it("should not trigger fetchMarketplaceData on mount when items already exist", async () => {
render(
<ExtensionStateContext.Provider value={mockExtensionState}>
<MarketplaceView stateManager={stateManager} />
</ExtensionStateContext.Provider>,
)

// Should trigger fetch on mount since there are no items and no initial state received
await waitFor(() => {
expect(vscode.postMessage).toHaveBeenCalledWith({
type: "fetchMarketplaceData",
})
// State already has items from FETCH_COMPLETE, so no fetch should be triggered
expect(vscode.postMessage).not.toHaveBeenCalledWith({
type: "fetchMarketplaceData",
})
})

it("should not trigger fetchMarketplaceData on mount when items already exist", async () => {
it("should trigger fetchMarketplaceData on mount when items are empty", async () => {
// Create a fresh state manager with no items loaded
const emptyStateManager = new MarketplaceViewStateManager()

render(
<ExtensionStateContext.Provider value={mockExtensionState}>
<MarketplaceView stateManager={stateManager} />
<MarketplaceView stateManager={emptyStateManager} />
</ExtensionStateContext.Provider>,
)

// Should NOT trigger fetch because items already exist (state is pre-populated)
// Should trigger fetch when no items exist
await waitFor(() => {
expect(vscode.postMessage).not.toHaveBeenCalledWith({
expect(vscode.postMessage).toHaveBeenCalledWith({
type: "fetchMarketplaceData",
})
})
})

it("should display MCP and Modes tabs", () => {
const { container } = render(
<ExtensionStateContext.Provider value={mockExtensionState}>
<MarketplaceView stateManager={stateManager} />
</ExtensionStateContext.Provider>,
)

expect(container.textContent).toContain("MCP")
expect(container.textContent).toContain("Modes")
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -127,9 +127,7 @@ export const BulkInstallModal: React.FC<BulkInstallModalProps> = ({ items, isOpe
<div
key={result.slug}
className={`flex items-start gap-2 p-2 rounded ${
result.success
? "bg-green-600/10 text-green-400"
: "bg-red-600/10 text-red-400"
result.success ? "bg-green-600/10 text-green-400" : "bg-red-600/10 text-red-400"
}`}>
{result.success ? (
<CheckCircle2 className="h-4 w-4 mt-0.5 shrink-0" />
Expand Down
Loading