feat(web): deploy-strategy selector UI for image/dockerfile/static sources

Phase-2 UI for the per-workload deploy_strategy shipped in e3d140c (which
was only reachable via the advanced-JSON editor). Adds DeployStrategyField,
a two-card radiogroup (recreate vs zero-downtime/blue-green) with CSS-only
motion glyphs that animate the deploy semantics — recreate shows the
downtime gap between versions, blue-green shows the overlapping cutover.
WAI-ARIA radiogroup with roving tabindex + arrow-key selection; respects
prefers-reduced-motion.

The field rides inside each source's *FormState via the shared sourceForms
module, so /apps/new and /apps/[id] need no changes:
- seed reads deploy_strategy; serialize is conditional-emit — the key is
  written ONLY when the operator deviates from the source default, so an
  untouched source_config stays byte-identical ('' is the canonical
  default, resolved by the backend's effectiveStrategy).
- dockerfile owns the key (form value wins, stale value scrubbed on clear).
- image defaults to blue-green; dockerfile/static default to recreate;
  static surfaces a caveat that storage-backed Deno sites fall back to
  recreate. Compose has no selector (recreate-only, blue-green rejected).

i18n apps.new.deployStrategy.* added to en+ru (parity 1750/1750). Extends
sourceForms.test.ts with seed/conditional-emit/owned-key/round-trip cases.
Verified: svelte-check 0 errors, 26/26 unit tests, build green.
This commit is contained in:
2026-06-19 17:09:17 +03:00
parent e3d140c57a
commit 5b51bbbd7f
8 changed files with 604 additions and 11 deletions
@@ -15,6 +15,7 @@
import type { DockerfileFormState } from '$lib/workload/sourceForms';
import StaticDiscoveryWizard from '$lib/components/workload/StaticDiscoveryWizard.svelte';
import ToggleSwitch from '$lib/components/ToggleSwitch.svelte';
import DeployStrategyField from '$lib/components/workload/DeployStrategyField.svelte';
import { IconX } from '$lib/components/icons';
import { t } from '$lib/i18n';
@@ -147,6 +148,7 @@
{@html $t('apps.new.sourceReportCommitStatusDesc')}
</span>
</label>
<DeployStrategyField bind:strategy={form.deployStrategy} defaultStrategy="recreate" idPrefix="app-df-strategy" />
<p class="hint image-form-foot">{$t('apps.new.dockerfileFoot')}</p>
</div>