Skip to content
Open
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 docs/TOOL_MATURITY_LOCAL_TESTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Tool Maturity UI Local Testing

## Setup

Use registry data containing `maturity: "Verified"`, no maturity value, and an unrecognized value such as `maturity: "Community Recommended"`.

The built-in Supabase source reads `tool_maturity(status)` through the `tools` relationship. A missing `tool_maturity` row is treated as Unverified. Because the desktop app uses an anonymous key, the backend must grant public read access only to `tool_maturity.tool_id` and `tool_maturity.status`; review requests, reviewer identities, CSP snapshots, and change reasons must remain restricted.

```bash
pnpm run typecheck
pnpm run lint
pnpm run test:unit -- tests/unit/renderer/toolMaturity.test.ts
pnpm exec playwright test tests/e2e/toolMaturity.spec.ts
pnpm run build
pnpm run dev
```

## Manual Checks

1. Open Marketplace and confirm Verified tools appear before other tools while each selected sort remains the order within those groups.
2. Confirm only `Verified` tools show the checkmark badge in standard and compact modes, in light and dark themes.
3. Hover and inspect the badge with a screen reader to verify its short explanation is available.
4. Enable **Verified only** in Marketplace and Installed; confirm only Verified tools remain and the existing empty state appears when none match.
5. In Installed, select **Maturity (Verified first)** and confirm Verified tools appear first, followed by name.
6. Change a tool's registry maturity, wait at least 30 seconds, then reload either list through navigation or a filter change. Confirm both views reflect the new status without restarting the app.
7. Confirm missing, `Unverified`, and unknown maturity values show no badge.
3 changes: 3 additions & 0 deletions src/common/types/tool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ export interface Tool {
marketplaceSourceId?: string;
marketplaceSourceLabel?: string;
marketplaceSourceType?: "builtin" | "private";
maturity?: string;
}

/**
Expand Down Expand Up @@ -112,6 +113,7 @@ export interface ToolRegistryEntry {
marketplaceSourceId?: string;
marketplaceSourceLabel?: string;
marketplaceSourceType?: "builtin" | "private";
maturity?: string;
}

/**
Expand Down Expand Up @@ -149,6 +151,7 @@ export interface ToolManifest {
marketplaceSourceId?: string;
marketplaceSourceLabel?: string;
marketplaceSourceType?: "builtin" | "private";
maturity?: string;
}

/**
Expand Down
3 changes: 2 additions & 1 deletion src/main/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,8 +137,9 @@ class ToolBoxApp {
this.connectionsManager = new ConnectionsManager();
this.api = new ToolBoxUtilityManager();
// Pass Supabase credentials and Azure Blob base URL from environment variables
const testToolsDirectory = process.env.PPTB_TEST_MODE === "1" ? process.env.PPTB_TEST_TOOLS_DIRECTORY : undefined;
this.toolManager = new ToolManager(
path.join(app.getPath("userData"), "tools"),
testToolsDirectory || path.join(app.getPath("userData"), "tools"),
process.env.SUPABASE_URL,
process.env.SUPABASE_ANON_KEY,
this.installIdManager,
Expand Down
22 changes: 21 additions & 1 deletion src/main/managers/toolRegistryManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,14 @@ interface SupabaseAnalyticsRow {
mau?: number; // Monthly Active Users
}

interface SupabaseMaturityRow {
status?: string;
}

export function getSupabaseMaturityStatus(relation: SupabaseMaturityRow | SupabaseMaturityRow[] | undefined): string | undefined {
return (Array.isArray(relation) ? relation[0] : relation)?.status;
}

function getOptionalAnalyticsNumber(value: number | null | undefined): number | undefined {
return typeof value === "number" ? value : undefined;
}
Expand Down Expand Up @@ -81,6 +89,7 @@ interface SupabaseTool {
website?: string;
min_api?: string; // Minimum ToolBox API version required
max_api?: string; // Maximum ToolBox API version tested
tool_maturity?: SupabaseMaturityRow | SupabaseMaturityRow[];
tool_categories?: SupabaseCategoryRow[];
tool_contributors?: SupabaseContributorRow[];
tool_analytics?: SupabaseAnalyticsRow | SupabaseAnalyticsRow[]; // sometimes array depending on RLS / joins
Expand Down Expand Up @@ -190,9 +199,10 @@ export class ToolRegistryManager extends EventEmitter {
// Initialize Supabase client
const url = supabaseUrl || SUPABASE_URL;
const key = supabaseKey || SUPABASE_ANON_KEY;
const useTestRegistry = process.env.PPTB_TEST_MODE === "1" && !!process.env.PPTB_TEST_REGISTRY_PATH;

// Validate Supabase credentials and create client
if (!url || !key || url === "" || key === "") {
if (useTestRegistry || !url || !key || url === "" || key === "") {
logWarn("[ToolRegistry] Supabase credentials not configured. Set SUPABASE_URL and SUPABASE_ANON_KEY environment variables.");
logWarn("[ToolRegistry] Falling back to local registry.json file.");
this.useLocalFallback = true;
Expand Down Expand Up @@ -347,6 +357,7 @@ export class ToolRegistryManager extends EventEmitter {
features: tool.features,
license: tool.license,
status: (tool.status as "active" | "deprecated" | "archived" | undefined) || "active",
maturity: tool.maturity,
marketplaceSourceId: source.id,
marketplaceSourceLabel: source.label,
marketplaceSourceType: source.type,
Expand Down Expand Up @@ -385,6 +396,7 @@ export class ToolRegistryManager extends EventEmitter {
"min_api",
"max_api",
// embedded relations
"tool_maturity(status)",
"tool_categories(categories(name))",
"tool_contributors(contributors(name,profile_url))",
"tool_analytics(downloads,rating,mau)",
Expand Down Expand Up @@ -444,6 +456,7 @@ export class ToolRegistryManager extends EventEmitter {
minAPI: tool.min_api, // Include min API version from database
maxAPI: tool.max_api, // Include max API version from database
npmPackageName: tool.packagename || undefined, // npm package name for pre-release detection
maturity: getSupabaseMaturityStatus(tool.tool_maturity),
} as ToolRegistryEntry;
});

Expand All @@ -460,6 +473,10 @@ export class ToolRegistryManager extends EventEmitter {
* Azure Blob is tried first (when configured), then the local registry.json.
*/
private async fetchFallbackRegistry(): Promise<ToolRegistryEntry[]> {
if (process.env.PPTB_TEST_MODE === "1" && process.env.PPTB_TEST_REGISTRY_PATH) {
return this.fetchLocalRegistry();
}

if (this.azureBlobBaseUrl) {
try {
const tools = await this.fetchAzureBlobRegistry();
Expand Down Expand Up @@ -529,6 +546,7 @@ export class ToolRegistryManager extends EventEmitter {
features: tool.features,
license: tool.license,
status: (tool.status as "active" | "deprecated" | "archived" | undefined) || "active",
maturity: tool.maturity,
}));

logInfo(`[ToolRegistry] Fetched ${tools.length} tools from Azure Blob registry`);
Expand Down Expand Up @@ -627,6 +645,7 @@ export class ToolRegistryManager extends EventEmitter {
status: (tool.status as "active" | "deprecated" | "archived" | undefined) || "active",
minAPI: tool.minAPI,
maxAPI: tool.maxAPI,
maturity: tool.maturity,
}));
}

Expand Down Expand Up @@ -874,6 +893,7 @@ export class ToolRegistryManager extends EventEmitter {
marketplaceSourceId: tool.marketplaceSourceId,
marketplaceSourceLabel: tool.marketplaceSourceLabel,
marketplaceSourceType: tool.marketplaceSourceType,
maturity: tool.maturity,
};

// Save to manifest file
Expand Down
1 change: 1 addition & 0 deletions src/main/managers/toolsManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ export class ToolManager extends EventEmitter {
marketplaceSourceId: manifest.marketplaceSourceId,
marketplaceSourceLabel: manifest.marketplaceSourceLabel,
marketplaceSourceType: manifest.marketplaceSourceType,
maturity: manifest.maturity,
};

const cached = this.analyticsCache.get(tool.id);
Expand Down
3 changes: 3 additions & 0 deletions src/main/utilities/mockRegistry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export interface OfflineMockRegistryTool {
status?: string;
minAPI?: string;
maxAPI?: string;
maturity?: string;
}

export interface OfflineMockRegistryLoadResult {
Expand All @@ -40,7 +41,9 @@ export interface OfflineMockRegistryLoadResult {
}

function resolveOfflineMockRegistryPath(): string | null {
const testRegistryPath = process.env.PPTB_TEST_MODE === "1" ? process.env.PPTB_TEST_REGISTRY_PATH : undefined;
const candidatePaths = [
...(testRegistryPath ? [testRegistryPath] : []),
// Bundled layout: dist/main/data/registry.json (most common runtime path)
path.join(__dirname, "..", "data", "registry.json"),
// Defensive fallback for alternate build layouts
Expand Down
5 changes: 5 additions & 0 deletions src/renderer/icons/dark/verified.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions src/renderer/icons/light/verified.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 9 additions & 0 deletions src/renderer/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ <h2 class="sidebar-title">INSTALLED</h2>
<option value="popularity">Popularity</option>
<option value="rating">Highly Rated</option>
<option value="downloads">Most Downloaded</option>
<option value="maturity">Maturity (Verified first)</option>
</select>
</div>
<div class="filter-divider"></div>
Expand Down Expand Up @@ -120,6 +121,10 @@ <h2 class="sidebar-title">INSTALLED</h2>
<input type="checkbox" id="tools-mcp-enabled-filter" class="filter-checkbox-input" />
<span>MCP Enabled</span>
</label>
<label class="filter-checkbox-label" for="tools-verified-only-filter">
<input type="checkbox" id="tools-verified-only-filter" class="filter-checkbox-input" />
<span>Verified only</span>
</label>
</div>
</div>
<div class="sidebar-body">
Expand Down Expand Up @@ -275,6 +280,10 @@ <h2 class="sidebar-title">MARKETPLACE</h2>
<input type="checkbox" id="marketplace-mcp-enabled-filter" class="filter-checkbox-input" />
<span>MCP Enabled</span>
</label>
<label class="filter-checkbox-label" for="marketplace-verified-only-filter">
<input type="checkbox" id="marketplace-verified-only-filter" class="filter-checkbox-input" />
<span>Verified only</span>
</label>
</div>
</div>
<div class="sidebar-body">
Expand Down
Loading
Loading