From 68fd08a335379db19b434ac687e0f23b53b73c9d Mon Sep 17 00:00:00 2001 From: Roomote Date: Fri, 17 Jul 2026 16:47:04 +0000 Subject: [PATCH 1/2] [Improve] Prefer configured cloud sandbox provider over Local Docker When Local Docker and a cloud provider are both available, default to the last catalog-ordered configured cloud instead of DEFAULT_COMPUTE_PROVIDER=docker. --- .../(authenticated)/home/Home.client.test.tsx | 11 +- .../web/src/app/(authenticated)/home/Home.tsx | 6 +- .../__tests__/compute-runtime-config.test.ts | 109 ++++++++++++++++-- packages/db/src/lib/compute-runtime-config.ts | 62 +++++++--- .../types/src/setup-compute-config.test.ts | 52 ++++++++- packages/types/src/setup-compute-config.ts | 93 ++++++++++++--- 6 files changed, 278 insertions(+), 55 deletions(-) diff --git a/apps/web/src/app/(authenticated)/home/Home.client.test.tsx b/apps/web/src/app/(authenticated)/home/Home.client.test.tsx index a7b47cb44..aed9d2ae9 100644 --- a/apps/web/src/app/(authenticated)/home/Home.client.test.tsx +++ b/apps/web/src/app/(authenticated)/home/Home.client.test.tsx @@ -874,20 +874,19 @@ describe('Home', () => { }); }); - it('falls back to the first catalog-ordered available provider', async () => { + it('falls back to the last catalog-ordered available cloud provider', async () => { render( , ); - expect(screen.getByLabelText('Sandbox provider')).toHaveTextContent( - 'Modal', - ); + expect(screen.getByLabelText('Sandbox provider')).toHaveTextContent('E2B'); fireEvent.click( screen.getByRole('button', { name: 'Use single-repo environment' }), @@ -897,7 +896,7 @@ describe('Home', () => { await waitFor(() => { expect(mockCreateStandardTaskRun).toHaveBeenCalledWith( expect.objectContaining({ - computeProvider: 'modal', + computeProvider: 'e2b', }), ); }); diff --git a/apps/web/src/app/(authenticated)/home/Home.tsx b/apps/web/src/app/(authenticated)/home/Home.tsx index 7f7a6fa05..f1c174408 100644 --- a/apps/web/src/app/(authenticated)/home/Home.tsx +++ b/apps/web/src/app/(authenticated)/home/Home.tsx @@ -11,6 +11,7 @@ import { type ComputeProvider, ALL_REPOSITORIES, DEFAULT_LAUNCH_CODING_HARNESS, + pickPreferredConfiguredComputeProvider, SETUP_COMPUTE_PROVIDER_CATALOG, } from '@roomote/types'; import type { RoutingDecision } from '@roomote/cloud-agents/server'; @@ -86,7 +87,10 @@ function resolveInitialComputeProvider( return defaultComputeProvider; } - return availableComputeProviders[0] ?? defaultComputeProvider; + return ( + pickPreferredConfiguredComputeProvider(availableComputeProviders) ?? + defaultComputeProvider + ); } type HomeProps = { diff --git a/packages/db/src/lib/__tests__/compute-runtime-config.test.ts b/packages/db/src/lib/__tests__/compute-runtime-config.test.ts index 9cb431148..55ea10546 100644 --- a/packages/db/src/lib/__tests__/compute-runtime-config.test.ts +++ b/packages/db/src/lib/__tests__/compute-runtime-config.test.ts @@ -9,6 +9,7 @@ vi.mock('../../encryption', () => ({ import { listConfiguredComputeProviders, resolveComputeProviderEnvValues, + resolveDefaultComputeProvider, } from '../compute-runtime-config'; type Executor = NonNullable< @@ -17,7 +18,15 @@ type Executor = NonNullable< type EnvVarRow = { name: string; value: string }; -function makeExecutor(rows: EnvVarRow[]): NonNullable { +function makeExecutor( + rows: EnvVarRow[], + options: { + runtimeComputeConfig?: { + defaultProvider?: string | null; + excludedProviders?: string[]; + } | null; + } = {}, +): NonNullable { // `where` must satisfy both call shapes used by the module: the direct // `inArray` read (awaited) and `resolveSavedWorkerImage`'s `.limit(1)` read. const whereResult = { @@ -31,6 +40,13 @@ function makeExecutor(rows: EnvVarRow[]): NonNullable { where: vi.fn(() => whereResult), })), })), + query: { + deploymentSettings: { + findFirst: vi.fn().mockResolvedValue({ + runtimeComputeConfig: options.runtimeComputeConfig ?? null, + }), + }, + }, } as unknown as NonNullable; } @@ -250,14 +266,9 @@ describe('listConfiguredComputeProviders', () => { }); it('excludes persisted providers from task launch options', async () => { - const executor = makeExecutor([]); - executor.query = { - deploymentSettings: { - findFirst: vi.fn().mockResolvedValue({ - runtimeComputeConfig: { excludedProviders: ['docker'] }, - }), - }, - } as never; + const executor = makeExecutor([], { + runtimeComputeConfig: { excludedProviders: ['docker'] }, + }); const providers = await listConfiguredComputeProviders({ runtimeEnv: {}, @@ -267,3 +278,83 @@ describe('listConfiguredComputeProviders', () => { expect(providers).toEqual([]); }); }); + +describe('resolveDefaultComputeProvider', () => { + beforeEach(() => { + vi.clearAllMocks(); + mockDecryptSecrets.mockImplementation(async (value) => value); + }); + + it('keeps an explicit persisted default', async () => { + const provider = await resolveDefaultComputeProvider({ + runtimeEnv: { + DEFAULT_COMPUTE_PROVIDER: 'docker', + MODAL_TOKEN_ID: 'id', + MODAL_TOKEN_SECRET: 'secret', + MODAL_BASE_IMAGE_REF: 'registry.example.com/image:tag', + }, + executor: makeExecutor([], { + runtimeComputeConfig: { defaultProvider: 'docker' }, + }), + }); + + expect(provider).toBe('docker'); + }); + + it('prefers configured cloud over DEFAULT_COMPUTE_PROVIDER=docker', async () => { + const provider = await resolveDefaultComputeProvider({ + runtimeEnv: { + DEFAULT_COMPUTE_PROVIDER: 'docker', + MODAL_TOKEN_ID: 'id', + MODAL_TOKEN_SECRET: 'secret', + MODAL_BASE_IMAGE_REF: 'registry.example.com/image:tag', + }, + executor: makeExecutor([]), + }); + + expect(provider).toBe('modal'); + }); + + it('uses the last catalog-ordered configured cloud when several are ready', async () => { + const provider = await resolveDefaultComputeProvider({ + runtimeEnv: { + DEFAULT_COMPUTE_PROVIDER: 'docker', + MODAL_TOKEN_ID: 'id', + MODAL_TOKEN_SECRET: 'secret', + MODAL_BASE_IMAGE_REF: 'registry.example.com/image:tag', + E2B_API_KEY: 'e2b-key', + E2B_TEMPLATE_ID: 'template', + }, + executor: makeExecutor([]), + }); + + expect(provider).toBe('e2b'); + }); + + it('honors a non-docker env default when it is set', async () => { + const provider = await resolveDefaultComputeProvider({ + runtimeEnv: { + DEFAULT_COMPUTE_PROVIDER: 'modal', + MODAL_TOKEN_ID: 'id', + MODAL_TOKEN_SECRET: 'secret', + MODAL_BASE_IMAGE_REF: 'registry.example.com/image:tag', + E2B_API_KEY: 'e2b-key', + E2B_TEMPLATE_ID: 'template', + }, + executor: makeExecutor([]), + }); + + expect(provider).toBe('modal'); + }); + + it('falls back to docker when only Local Docker is configured', async () => { + const provider = await resolveDefaultComputeProvider({ + runtimeEnv: { + DEFAULT_COMPUTE_PROVIDER: 'docker', + }, + executor: makeExecutor([]), + }); + + expect(provider).toBe('docker'); + }); +}); diff --git a/packages/db/src/lib/compute-runtime-config.ts b/packages/db/src/lib/compute-runtime-config.ts index a82c0990a..ae1dffeec 100644 --- a/packages/db/src/lib/compute-runtime-config.ts +++ b/packages/db/src/lib/compute-runtime-config.ts @@ -2,8 +2,8 @@ import { and, eq, inArray, isNull } from 'drizzle-orm'; import { getDefaultAvailableComputeProvider, getSetupComputeProvider, + pickPreferredConfiguredComputeProvider, SETUP_COMPUTE_PROVIDER_CATALOG, - SETUP_COMPUTE_PROVIDER_IDS, SHARED_WORKER_IMAGE_ENV_VAR, deriveModalBaseImageRefDefault, resolveEffectiveDockerWorkerImage, @@ -116,10 +116,15 @@ export async function listConfiguredComputeProviders( } /** - * Resolves the deployment default compute provider. The persisted setup - * choice wins over the DEFAULT_COMPUTE_PROVIDER env value because compose and - * PM2 stacks always inject an env default; the admin's explicit setup choice - * is the stronger signal. + * Resolves the deployment default compute provider. Order of preference: + * 1. Persisted setup/admin default (explicit deployment choice). + * 2. DEFAULT_COMPUTE_PROVIDER env when it is a non-docker provider, or when + * docker is the env default and no cloud provider is configured. Compose + * stacks often inject DEFAULT_COMPUTE_PROVIDER=docker; a configured cloud + * provider still wins over that barrier default. + * 3. Last catalog-ordered configured cloud provider when one or more clouds + * are ready (prefer cloud over Local Docker). + * 4. Local Docker / remaining availability fallback. */ export async function resolveDefaultComputeProvider( options: { @@ -128,9 +133,9 @@ export async function resolveDefaultComputeProvider( } = {}, ): Promise { const runtimeEnv = options.runtimeEnv ?? process.env; - const persistedComputeConfig = await loadPersistedRuntimeComputeConfig( - options.executor ?? db, - ); + const executor = options.executor ?? db; + const persistedComputeConfig = + await loadPersistedRuntimeComputeConfig(executor); const excludedProviders = parseExcludedComputeProviders( runtimeEnv.EXCLUDED_COMPUTE_PROVIDERS, ); @@ -145,27 +150,48 @@ export async function resolveDefaultComputeProvider( return persistedComputeConfig.defaultProvider; } + const configuredProviders = await listConfiguredComputeProviders({ + runtimeEnv, + executor, + }); + const configuredCloudProviders = configuredProviders.filter( + (provider) => provider !== 'docker', + ); + const preferredConfigured = + pickPreferredConfiguredComputeProvider(configuredProviders); + const runtimeDefault = runtimeEnv.DEFAULT_COMPUTE_PROVIDER?.trim(); if ( runtimeDefault && isComputeProvider(runtimeDefault) && !excludedProviders.has(runtimeDefault) ) { + // Env docker is a barrier default for stacks that also ship Local Docker. + // When a cloud provider is already configured, prefer that cloud instead. + if (runtimeDefault === 'docker' && configuredCloudProviders.length > 0) { + return ( + preferredConfigured ?? + configuredCloudProviders[configuredCloudProviders.length - 1]! + ); + } + return runtimeDefault; } + if (preferredConfigured) { + return preferredConfigured; + } + const availableProviders = await Promise.all( - SETUP_COMPUTE_PROVIDER_IDS.filter( - (provider) => !excludedProviders.has(provider), - ) - .filter((provider) => provider !== 'docker') - .map(async (provider) => ({ - provider, - configSatisfied: await isComputeProviderConfigured(provider, { + SETUP_COMPUTE_PROVIDER_CATALOG.map(async ({ provider }) => ({ + provider, + configSatisfied: + !excludedProviders.has(provider) && + (await isComputeProviderConfigured(provider, { runtimeEnv, - executor: options.executor ?? db, - }), - })), + executor, + })), + })), ); return getDefaultAvailableComputeProvider( diff --git a/packages/types/src/setup-compute-config.test.ts b/packages/types/src/setup-compute-config.test.ts index dd4d8a7fb..529c96e4f 100644 --- a/packages/types/src/setup-compute-config.test.ts +++ b/packages/types/src/setup-compute-config.test.ts @@ -13,6 +13,7 @@ import { isComputeOperatorEditableField, normalizeDeploymentComputeConfig, parseExcludedComputeProviders, + pickPreferredConfiguredComputeProvider, resolveDerivedModalBaseImageRef, resolveEffectiveDockerWorkerImage, resolveEffectiveModalBaseImageRef, @@ -503,6 +504,22 @@ describe('buildSetupComputeStatus', () => { expect(status.preselectedProvider).toBe('modal'); }); + it('prefers a configured cloud over DEFAULT_COMPUTE_PROVIDER=docker for preselection', () => { + const status = buildSetupComputeStatus({ + runtimeEnv: { + DEFAULT_COMPUTE_PROVIDER: 'docker', + MODAL_TOKEN_ID: 'id', + MODAL_TOKEN_SECRET: 'secret', + MODAL_BASE_IMAGE_REF: 'registry/image:tag', + E2B_API_KEY: 'key', + E2B_TEMPLATE_ID: 'template', + }, + }); + + expect(status.runtimeDefaultProvider).toBe('docker'); + expect(status.preselectedProvider).toBe('e2b'); + }); + it('satisfies the Modal base image via the derived worker-image default', () => { const status = buildSetupComputeStatus({ runtimeEnv: { @@ -909,8 +926,20 @@ describe('parseExcludedComputeProviders', () => { }); }); +describe('pickPreferredConfiguredComputeProvider', () => { + it('prefers the last catalog-ordered cloud over Local Docker', () => { + expect( + pickPreferredConfiguredComputeProvider(['docker', 'modal', 'e2b']), + ).toBe('e2b'); + }); + + it('returns docker when it is the only available provider', () => { + expect(pickPreferredConfiguredComputeProvider(['docker'])).toBe('docker'); + }); +}); + describe('getDefaultAvailableComputeProvider', () => { - it('returns the first non-excluded provider', () => { + it('returns the preferred configured non-excluded provider', () => { expect( getDefaultAvailableComputeProvider(new Set(['docker', 'modal']), [ { provider: 'daytona', configSatisfied: true }, @@ -918,12 +947,31 @@ describe('getDefaultAvailableComputeProvider', () => { ).toBe('daytona'); }); - it('keeps docker when it is not excluded', () => { + it('keeps docker when it is not excluded and no live status is provided', () => { expect(getDefaultAvailableComputeProvider(new Set(['modal']))).toBe( 'docker', ); }); + it('prefers a configured cloud provider over Local Docker', () => { + expect( + getDefaultAvailableComputeProvider(new Set(), [ + { provider: 'modal', configSatisfied: true }, + { provider: 'docker', configSatisfied: true }, + ]), + ).toBe('modal'); + }); + + it('prefers the last catalog-ordered configured cloud when several are ready', () => { + expect( + getDefaultAvailableComputeProvider(new Set(), [ + { provider: 'modal', configSatisfied: true }, + { provider: 'e2b', configSatisfied: true }, + { provider: 'docker', configSatisfied: true }, + ]), + ).toBe('e2b'); + }); + it('falls back to docker when excluded providers are not configured', () => { expect( getDefaultAvailableComputeProvider(new Set(['docker']), [ diff --git a/packages/types/src/setup-compute-config.ts b/packages/types/src/setup-compute-config.ts index 43fdac075..905a9e876 100644 --- a/packages/types/src/setup-compute-config.ts +++ b/packages/types/src/setup-compute-config.ts @@ -205,6 +205,33 @@ export function parseExcludedComputeProviders( ); } +/** + * Preferring a configured cloud provider over Local Docker when both are + * available. When multiple clouds are available, prefer the last one in + * setup-catalog order (latest cloud option among those that are ready). + */ +export function pickPreferredConfiguredComputeProvider( + providers: readonly ComputeProvider[], +): ComputeProvider | undefined { + if (providers.length === 0) { + return undefined; + } + + const available = new Set(providers); + const catalogOrdered = SETUP_COMPUTE_PROVIDER_CATALOG.map( + (descriptor) => descriptor.provider, + ).filter((provider) => available.has(provider)); + const cloudProviders = catalogOrdered.filter( + (provider) => provider !== DEFAULT_SETUP_COMPUTE_PROVIDER_ID, + ); + + if (cloudProviders.length > 0) { + return cloudProviders[cloudProviders.length - 1]; + } + + return catalogOrdered[0]; +} + export function getDefaultAvailableComputeProvider( excludedProviders: ReadonlySet = new Set(), availableProviders?: Iterable<{ @@ -212,24 +239,41 @@ export function getDefaultAvailableComputeProvider( configSatisfied: boolean; }>, ): ComputeProvider { + if (availableProviders !== undefined) { + const satisfiedProviders = Array.from(availableProviders) + .filter( + (provider) => + !excludedProviders.has(provider.provider) && + provider.configSatisfied !== false, + ) + .map((provider) => provider.provider); + + const preferred = + pickPreferredConfiguredComputeProvider(satisfiedProviders); + if (preferred) { + return preferred; + } + + // Nothing reported as ready (or everything excluded from the live set): + // keep Local Docker as the last-resort default id even when excluded from + // the operator-facing catalog. + return DEFAULT_SETUP_COMPUTE_PROVIDER_ID; + } + if (!excludedProviders.has(DEFAULT_SETUP_COMPUTE_PROVIDER_ID)) { + // No live readiness: Local Docker is the barrier default for setup when + // it is still allowed. Callers that know which providers are configured + // pass `availableProviders` so cloud can outrank docker. return DEFAULT_SETUP_COMPUTE_PROVIDER_ID; } + // Docker excluded and no live readiness: first catalog entry that could be + // operator-configured. return ( - Array.from( - availableProviders ?? - SETUP_COMPUTE_PROVIDER_CATALOG.map((provider) => ({ - provider: provider.provider, - // Without live status, deployment-managed providers (no - // operator-editable fields, e.g. Roomote Cloud) cannot be assumed - // satisfiable: only their runtime env can configure them. - configSatisfied: provider.fields.some(isComputeOperatorEditableField), - })), - ).find( + SETUP_COMPUTE_PROVIDER_CATALOG.find( (provider) => !excludedProviders.has(provider.provider) && - provider.configSatisfied !== false, + provider.fields.some(isComputeOperatorEditableField), )?.provider ?? DEFAULT_SETUP_COMPUTE_PROVIDER_ID ); } @@ -888,16 +932,27 @@ export function buildSetupComputeStatus(input: { !excludedProviders.has(persistedComputeConfig.defaultProvider) ? persistedComputeConfig.defaultProvider : null; + const providerReadiness = providers.map((provider) => ({ + provider: provider.provider, + configSatisfied: provider.configSatisfied, + })); + const configuredCloudReady = providers.some( + (provider) => + provider.provider !== DEFAULT_SETUP_COMPUTE_PROVIDER_ID && + provider.configSatisfied && + !excludedProviders.has(provider.provider), + ); + // Compose stacks often inject DEFAULT_COMPUTE_PROVIDER=docker. When a cloud + // provider is already configured, ignore that barrier default for preselect. + const effectiveRuntimeDefaultProvider = + runtimeDefaultProvider === DEFAULT_SETUP_COMPUTE_PROVIDER_ID && + configuredCloudReady + ? null + : runtimeDefaultProvider; const preselectedProvider = selectedProvider ?? - runtimeDefaultProvider ?? - getDefaultAvailableComputeProvider( - excludedProviders, - providers.map((provider) => ({ - provider: provider.provider, - configSatisfied: provider.configSatisfied, - })), - ); + effectiveRuntimeDefaultProvider ?? + getDefaultAvailableComputeProvider(excludedProviders, providerReadiness); const selectedProviderStatus = selectedProvider ? providers.find((candidate) => candidate.provider === selectedProvider) From 6bbb1e84128f0644b6317fe5ec845a3c7936123c Mon Sep 17 00:00:00 2001 From: Roomote Date: Fri, 17 Jul 2026 16:58:55 +0000 Subject: [PATCH 2/2] [Fix] Skip catalog readiness scan for non-docker compute defaults Preserve immediate return for authoritative non-Docker DEFAULT_COMPUTE_PROVIDER and only scan configured cloud providers when resolving docker-vs-cloud fallback. --- .../__tests__/compute-runtime-config.test.ts | 16 +++++ packages/db/src/lib/compute-runtime-config.ts | 60 ++++++++----------- 2 files changed, 40 insertions(+), 36 deletions(-) diff --git a/packages/db/src/lib/__tests__/compute-runtime-config.test.ts b/packages/db/src/lib/__tests__/compute-runtime-config.test.ts index 55ea10546..7a6144c64 100644 --- a/packages/db/src/lib/__tests__/compute-runtime-config.test.ts +++ b/packages/db/src/lib/__tests__/compute-runtime-config.test.ts @@ -347,6 +347,22 @@ describe('resolveDefaultComputeProvider', () => { expect(provider).toBe('modal'); }); + it('skips catalog readiness scans for non-docker env defaults', async () => { + const executor = makeExecutor([]); + + const provider = await resolveDefaultComputeProvider({ + runtimeEnv: { + DEFAULT_COMPUTE_PROVIDER: 'modal', + }, + executor, + }); + + expect(provider).toBe('modal'); + // listConfiguredComputeProviders is the only caller path that hits + // environment_variables selects for every catalog provider. + expect(executor.select).not.toHaveBeenCalled(); + }); + it('falls back to docker when only Local Docker is configured', async () => { const provider = await resolveDefaultComputeProvider({ runtimeEnv: { diff --git a/packages/db/src/lib/compute-runtime-config.ts b/packages/db/src/lib/compute-runtime-config.ts index ae1dffeec..ea35be9c1 100644 --- a/packages/db/src/lib/compute-runtime-config.ts +++ b/packages/db/src/lib/compute-runtime-config.ts @@ -118,12 +118,12 @@ export async function listConfiguredComputeProviders( /** * Resolves the deployment default compute provider. Order of preference: * 1. Persisted setup/admin default (explicit deployment choice). - * 2. DEFAULT_COMPUTE_PROVIDER env when it is a non-docker provider, or when - * docker is the env default and no cloud provider is configured. Compose - * stacks often inject DEFAULT_COMPUTE_PROVIDER=docker; a configured cloud - * provider still wins over that barrier default. - * 3. Last catalog-ordered configured cloud provider when one or more clouds - * are ready (prefer cloud over Local Docker). + * 2. DEFAULT_COMPUTE_PROVIDER env when it is a non-docker provider (direct + * return — no catalog readiness scan on hot dispatch paths). + * 3. When the env default is docker or unset: scan configured providers and + * prefer the last catalog-ordered configured cloud over Local Docker. + * Compose stacks often inject DEFAULT_COMPUTE_PROVIDER=docker; a ready + * cloud provider outranks that barrier default. * 4. Local Docker / remaining availability fallback. */ export async function resolveDefaultComputeProvider( @@ -150,54 +150,42 @@ export async function resolveDefaultComputeProvider( return persistedComputeConfig.defaultProvider; } + const runtimeDefault = runtimeEnv.DEFAULT_COMPUTE_PROVIDER?.trim(); + if ( + runtimeDefault && + isComputeProvider(runtimeDefault) && + !excludedProviders.has(runtimeDefault) && + runtimeDefault !== 'docker' + ) { + // Authoritative non-Docker env defaults skip the catalog readiness scan + // used by enqueue/retry/dequeue paths. + return runtimeDefault; + } + + // Docker env default or unset: resolve cloud-vs-Local Docker from readiness. const configuredProviders = await listConfiguredComputeProviders({ runtimeEnv, executor, }); - const configuredCloudProviders = configuredProviders.filter( - (provider) => provider !== 'docker', - ); const preferredConfigured = pickPreferredConfiguredComputeProvider(configuredProviders); - const runtimeDefault = runtimeEnv.DEFAULT_COMPUTE_PROVIDER?.trim(); if ( - runtimeDefault && + runtimeDefault === 'docker' && isComputeProvider(runtimeDefault) && !excludedProviders.has(runtimeDefault) ) { - // Env docker is a barrier default for stacks that also ship Local Docker. - // When a cloud provider is already configured, prefer that cloud instead. - if (runtimeDefault === 'docker' && configuredCloudProviders.length > 0) { - return ( - preferredConfigured ?? - configuredCloudProviders[configuredCloudProviders.length - 1]! - ); + if (preferredConfigured && preferredConfigured !== 'docker') { + return preferredConfigured; } - - return runtimeDefault; + return 'docker'; } if (preferredConfigured) { return preferredConfigured; } - const availableProviders = await Promise.all( - SETUP_COMPUTE_PROVIDER_CATALOG.map(async ({ provider }) => ({ - provider, - configSatisfied: - !excludedProviders.has(provider) && - (await isComputeProviderConfigured(provider, { - runtimeEnv, - executor, - })), - })), - ); - - return getDefaultAvailableComputeProvider( - excludedProviders, - availableProviders, - ); + return getDefaultAvailableComputeProvider(excludedProviders); } /**