Rework provider selector and settings UX

Surface all enabled providers in the model selector with inline loading
and error states instead of silently hiding non-ready ones. Tap any row
to drill in; loading and error states show in the drill-down body, with
a Retry button on error. Providers with no models render a synthetic
"Default" row so every provider's drill-down has symmetric structure.

Redesign the per-provider settings sheet: search lives in the header,
discovered and custom models share one scrolling list, and the footer
hosts action buttons. Diagnostic and "Add model" each open as their own
sub-sheet, so neither expands inline or shifts the surrounding layout.

Snapshot refresh now keeps cached data visible: setQueryData replaces
the cache atomically and sibling-scope caches are invalidated (not
removed), so refreshing a provider no longer blanks its row. Search
inputs across the app are uncontrolled — onChangeText drives parent
state, and resetKey remounts the field when an explicit reset is needed
— to avoid RN's controlled-input flicker.

Adds a dev-only mock-slow provider whose discovery never resolves, so
the 30s snapshot timeout produces a real error and lets loading and
timeout-error UI be exercised end to end.
This commit is contained in:
Mohamed Boudra
2026-05-27 22:00:34 +07:00
parent 5696cdb455
commit a4cb7431d8
18 changed files with 1018 additions and 622 deletions
@@ -151,6 +151,16 @@ const MOCK_LOAD_TEST_MODES: AgentProviderModeDefinition[] = [
},
];
const MOCK_SLOW_MODES: AgentProviderModeDefinition[] = [
{
id: "default",
label: "Default",
description: "Dev-only mode for the mock slow provider",
icon: "ShieldOff",
colorTier: "dangerous",
},
];
export const AGENT_PROVIDER_DEFINITIONS: AgentProviderDefinition[] = [
{
id: "claude",
@@ -212,6 +222,13 @@ export const DEV_AGENT_PROVIDER_DEFINITIONS: AgentProviderDefinition[] = [
defaultModeId: "load-test",
modes: MOCK_LOAD_TEST_MODES,
},
{
id: "mock-slow",
label: "Mock Slow Provider",
description: "Dev-only: hangs during model discovery to test loading and timeout UI",
defaultModeId: "default",
modes: MOCK_SLOW_MODES,
},
];
export function getAgentProviderDefinition(
@@ -31,6 +31,7 @@ import { OpenCodeAgentClient } from "./providers/opencode-agent.js";
import { OpenCodeServerManager } from "./providers/opencode/server-manager.js";
import { PiRpcAgentClient } from "./providers/pi/agent.js";
import { MockLoadTestAgentClient } from "./providers/mock-load-test-agent.js";
import { MockSlowProviderClient } from "./providers/mock-slow-provider.js";
import {
AGENT_PROVIDER_DEFINITIONS,
BUILTIN_PROVIDER_IDS,
@@ -126,6 +127,7 @@ const PROVIDER_CLIENT_FACTORIES: Record<string, ProviderClientFactory> = {
runtimeSettings,
}),
mock: (logger) => new MockLoadTestAgentClient(logger),
"mock-slow": () => new MockSlowProviderClient(),
};
function getCursorACPCommand(
@@ -0,0 +1,78 @@
import type {
AgentCapabilityFlags,
AgentClient,
AgentLaunchContext,
AgentMode,
AgentModelDefinition,
AgentPersistenceHandle,
AgentProvider,
AgentSession,
AgentSessionConfig,
ListModelsOptions,
ListModesOptions,
ListPersistedAgentsOptions,
PersistedAgentDescriptor,
} from "../agent-sdk-types.js";
export const MOCK_SLOW_PROVIDER_ID = "mock-slow";
const CAPABILITIES: AgentCapabilityFlags = {
supportsStreaming: false,
supportsSessionPersistence: false,
supportsDynamicModes: false,
supportsMcpServers: false,
supportsReasoningStream: false,
supportsToolInvocations: false,
supportsRewindConversation: false,
supportsRewindFiles: false,
supportsRewindBoth: false,
};
function neverResolves<T>(): Promise<T> {
return new Promise<T>(() => {});
}
export class MockSlowProviderClient implements AgentClient {
readonly provider: AgentProvider = MOCK_SLOW_PROVIDER_ID;
readonly capabilities = CAPABILITIES;
async isAvailable(): Promise<boolean> {
return true;
}
listModels(_options: ListModelsOptions): Promise<AgentModelDefinition[]> {
return neverResolves<AgentModelDefinition[]>();
}
listModes(_options: ListModesOptions): Promise<AgentMode[]> {
return neverResolves<AgentMode[]>();
}
async listPersistedAgents(
_options?: ListPersistedAgentsOptions,
): Promise<PersistedAgentDescriptor[]> {
return [];
}
async getDiagnostic(): Promise<{ diagnostic: string }> {
return {
diagnostic:
"Mock slow provider: dev-only. listModels() never resolves so the snapshot manager will time out.",
};
}
createSession(
_config: AgentSessionConfig,
_launchContext?: AgentLaunchContext,
): Promise<AgentSession> {
throw new Error("Mock slow provider is dev-only; sessions are not supported.");
}
resumeSession(
_handle: AgentPersistenceHandle,
_overrides?: Partial<AgentSessionConfig>,
_launchContext?: AgentLaunchContext,
): Promise<AgentSession> {
throw new Error("Mock slow provider is dev-only; sessions are not supported.");
}
}