New beta | Electric Cherry
{#if flash}{/if} {#if $fleetError}{/if} {#if notice}

{notice}

{/if} {#if loading} {:else if availableDraft}

Resume unfinished beta?

Saved {$timeFormat.dateTime(availableDraft.savedAt)} · {availableDraft.picks.filter(p=>p.scriptId!==null).length} files · {availableDraft.servers.length} selected servers. Versions and eligibility will be rechecked.

{:else if !eligibleFiles.length && !flash} {:else if scripts.length}
    {#each steps as label,index}
  1. {index+1}{label}
  2. {/each}
{#if storageUnavailable}Draft saving is unavailable in this browser. Keep this page open to preserve your work.{:else}Unfinished changes save in this browser.{/if}{#if dirty}{/if}

{steps[step]}

{#if step===0}
{#each picks as pick,index(pick.key)}
{#if pick.scriptId!==null}{@const file=detailById[pick.scriptId]?.script}{#if file}

{scopeText(file)} · {scopeCount(file)} eligible servers · Inspect file and versions{#if !loadingVersions.has(pick.key)&&!versionsFor(pick).length}
No unpromoted versions. Upload a new version from the file page.{/if}

{/if}{/if}
{/each}

Test servers must match every file's scope. Changing a file retains compatible server selections.

{:else if step===1}

{pickedServers.size} selected · {candidateServers.length} compatible servers. Restarts wait for confirmed empty lobbies.

Selected file scopes
    {#each picks as pick}{#if pick.scriptId!==null}{@const file=detailById[pick.scriptId]?.script}{#if file}
  • {file.name}: {scopeText(file)} · {scopeCount(file)} eligible servers
  • {/if}{/if}{/each}
{#if !candidateServers.length}
The selected file scopes have no servers in common. Review the scopes above, then go Back to replace or remove a conflicting file.
{:else}
{visibleServers.length} visible
{#each groups as group}
{fleetIdentity(group.servers[0]).map} · {group.servers[0].game==='t5'?'Black Ops I':'Black Ops II'}
{#each group.servers as server}{/each}
{/each} {#if !visibleServers.length}

No servers match these filters.

{/if} {/if} {:else if step===2} {:else}

Files and exact versions

    {#each picks as pick}
  • {scripts.find(file=>file.id===pick.scriptId)?.name} · {artifactVersionLabel(versionsFor(pick).find(v=>v.id===pick.versionId))} · {versionsFor(pick).find(v=>v.id===pick.versionId)?.sha256.slice(0,12)}
  • {/each}

Test servers ({pickedServers.size})

    {#each [...pickedServers] as name}
  • {/each}

Pass criteria

{coverageSummary(testPlan)} · {testPlan.timeoutHours}-hour window, starting when this beta opens.

    {#each testPlan.requirements as requirement}
  • {requirementSummary(requirement)}
  • {/each}

Waves and failure handling

{testPlan.waveSize?`${testPlan.waveSize} servers per wave.`:'Deploy to all selected servers.'} {testPlan.failureAction==='rollback'?'Failed checks trigger automatic rollback.':testPlan.failureAction==='pause'?'Failed checks pause testing.':'Failed checks hold the beta for review.'} {testPlan.rollbackRestart==='when-empty'?'Rollback restarts wait for empty servers.':'Rollback restores files; restart servers manually.'}

Starting installs the selected versions and restarts test servers when empty. Waiting, pausing, and retrying use the original testing window. Promotion is a separate reviewed action after testing.

{/if}

{forwardReason}

{#if step<3}{:else}{/if}
{/if}