{ if (isDialogBackdrop(event, dialog) && !busy) open = false; }} oncancel={event => { if (busy) event.preventDefault(); }} onclose={() => open = false}>
Collection launch

{preview?.blueprintName ?? collectionName}

{#if open && games.length} { preview = null; confirmRemovals = false; }} />{/if}

{mode === 'exact' ? 'Keep matching lobbies, add missing ones, and remove excess lobbies and maps outside this collection across both games.' : 'Create missing lobbies and keep all existing servers.'}

{#if error}{/if} {#if !preview}

{busy ? 'Preparing the collection…' : 'Review the fleet changes, capacity estimate, and server keys. Queued launches wait automatically when hosts need more capacity or preparation.'}

{#if !busy}{/if} {:else} {#if keyNotice}

{keyNotice}

{/if} {#if expired}

This preview expired. Refresh before launch; keys remain entered for unchanged IDs.

{/if}
{preview.mode === 'exact' ? `${preview.retainedServers.length} keep · ${preview.servers.length} create · ${preview.removals.length} remove` : preview.servers.length ? `${preview.servers.length} missing server${preview.servers.length === 1 ? '' : 's'} to create` : 'Collection already satisfied'} {#each preview.entries as entry}{/each}
MapTargetExisting / queuedTo create
{entry.recipe.mapName} {entry.recipe.game.toUpperCase()}{entry.count}{entry.existing}{entry.missing}

Targets count inventory, including stopped servers; they do not mean every server is ready to play. {preview.mode === 'exact' ? 'Existing deployed lobbies are kept before queued creations, then the lowest lobby numbers. Retained servers are not changed or restarted.' : 'Only missing servers are created. Extra servers remain and existing servers are not updated.'}

{#if preview.mode === 'exact' && preview.retainedServers.length}
Servers to keep ({preview.retainedServers.length})
    {#each preview.retainedServers as name}
  • {name}
  • {/each}
{/if} {#if preview.mode === 'fill' && preview.entries.some(entry => entry.existing)}
Review existing servers{#each preview.entries.filter(entry => entry.existing) as entry}
{entry.recipe.mapName}: {entry.existing} existing

{[...new Set((entry.coverage ?? []).map(server => server.health))].map(health => `${entry.coverage.filter(server => server.health === health).length} ${health}`).join(', ')}

    {#each entry.coverage ?? [] as server}
  • {server.health === 'online' ? 'Healthy' : server.health}
  • {/each}
{/each}
{/if}
{#if preview.availability && preview.servers.length}

Latest reported capacity

View capacity and history ↗

{preview.availability.estimatedAvailable === null ? 'An estimate is not available yet.' : `${preview.availability.estimatedAvailable} additional servers estimated to fit.`} {preview.availability.requested} requested.

CPU available
{preview.availability.cpuCoresAvailable === null ? 'Unknown' : `${preview.availability.cpuCoresAvailable.toFixed(1)} cores`}
Memory available
{preview.availability.memoryBytesAvailable === null ? 'Unknown' : `${(preview.availability.memoryBytesAvailable / 1024 ** 3).toFixed(1)} GiB`}
Ports available
{preview.availability.portsAvailable ?? 'Unknown'}
Startup slots
{preview.availability.startupSlots ?? 'Unknown'}{#if preview.availability.startupLimit !== null} / {preview.availability.startupLimit}{/if}

{preview.availability.updatedAt ? `Updated ${$timeFormat.dateTime(preview.availability.updatedAt)}.` : 'Waiting for host reports.'}{#if preview.availability.queued !== null} {preview.availability.queued} servers already queued.{/if} Provisioning checks capacity separately for each server before it starts.

{/if}

{preview.mode === 'exact' ? 'Review fleet changes' : preview.servers.length ? 'Your launch order' : 'Nothing to create'}

{#each preview.blockers as blocker}{/each} {#each preview.warnings ?? [] as warning}

{warning}

{/each} {#if preview.servers.length || preview.removals.length} {#if preview.servers.length}

Saved keys are reused automatically. Add keys only where required below. Update saved keys in Keys (opens in a new tab). Apply saves the reviewed jobs and opens their progress page. Each launch waits for a startup slot and host capacity.

{/if}
{ event.preventDefault(); void launch(); }}> {#if preview.removals.length}

Remove {preview.removals.length} server{preview.removals.length === 1 ? '' : 's'}

Containers, files, and registrations will be removed after a fresh zero-player check. Busy or unreachable servers wait. Unstarted creations are cancelled. Saved keys are kept. New servers reusing these ports wait for removal to finish.

    {#each preview.removals as server}
  • {server.name}{server.reason}
  • {/each}
{/if} {#if preview.servers.length}
    {#each preview.servers as server, index (server.name ?? `unavailable-${index}`)}
  1. {#if index === 0 || preview.servers[index - 1].entryIndex !== server.entryIndex}{preview.entries[server.entryIndex].recipe.game.toUpperCase()} · {preview.entries[server.entryIndex].recipe.mapName}{/if}{#if server.name}{:else}{preview.entries[server.entryIndex].recipe.mapName}{/if}{server.name ?? 'No available lobby'} · {server.native ? 'Host and port assigned when provisioning starts' : server.port === null ? 'No available port' : `Port ${server.port}`}
    {#if server.name} {#if server.hasSavedKey}

    Saved key ready

    {:else}{/if} {/if}
  2. {/each}
{/if}
{:else}

{preview.mode === 'exact' ? 'Your fleet already matches this collection.' : 'Your fleet already meets or exceeds every target in this collection.'} No servers will be created or removed.

{/if}
Setup and launch requirements

Each new server is automatically placed on an eligible host with the latest game files and prepared runtime. CPU, memory, host readiness, synced files, runtime availability, and local port reservations are checked again before deployment. Existing servers keep their current files.

{#each preview.entries as entry}
{/each}
{/if}