Final sweep: Crucible fixes, Path Tracer polish, forge progress, tests green.
Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled
Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled
Align dashboard subtitle default and UpsertAgent tests with fleet label behavior; WebSocket coalesce and PathForge hardening; Crucible expanded ops and visual DV fixes; Vitest 610/610 and full test-suite pass; trim PROBLEMS.md to open items only.
This commit is contained in:
@@ -98,18 +98,8 @@ function defaultsFromConfig(config: ServerConfig, serverInfo: ServerInfo, builds
|
||||
return forgeDefaultsFromServerSmart(config, serverInfo, builds);
|
||||
}
|
||||
|
||||
// Simulated stage timeline — real compiles (garble/universal/fusion) often take 10–30+ min.
|
||||
// Cap below 95% until the server responds; finishForgeSuccess sets 100%.
|
||||
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 },
|
||||
];
|
||||
// Poll interval (ms) for real server-side build progress.
|
||||
const FORGE_POLL_MS = 1000;
|
||||
|
||||
function ForgeProgressBar({ building, stage, progress }: { building: boolean; stage: string; progress: number }) {
|
||||
if (!building) return null;
|
||||
@@ -223,40 +213,48 @@ export default function BuilderPage() {
|
||||
|
||||
const forgeSkinClass = forgePageClass(operationMode, forgeTheme);
|
||||
|
||||
// Drive simulated stage progress while a single build is running
|
||||
// Poll real server-side build progress while a single build is running.
|
||||
// The server exposes GET /api/v1/builder/progress/{token} which returns
|
||||
// {stage, pct} updated at each key compile stage, so the bar reflects
|
||||
// actual server activity instead of a client-side time estimate.
|
||||
useEffect(() => {
|
||||
if (!building || batchJob) {
|
||||
endForge();
|
||||
if (forgeStageTimerRef.current) clearTimeout(forgeStageTimerRef.current);
|
||||
return;
|
||||
}
|
||||
const startMs = Date.now();
|
||||
startForge();
|
||||
|
||||
let stageIdx = 0;
|
||||
const advance = () => {
|
||||
const elapsed = Date.now() - startMs;
|
||||
// Find the furthest stage whose minMs has been reached
|
||||
let next = 0;
|
||||
for (let i = 0; i < FORGE_STAGES.length; i++) {
|
||||
if (elapsed >= FORGE_STAGES[i].minMs) next = i;
|
||||
else break;
|
||||
const token = cancelTokenRef.current;
|
||||
if (!token) return;
|
||||
|
||||
let active = true;
|
||||
|
||||
const poll = async () => {
|
||||
if (!active) return;
|
||||
try {
|
||||
const { authHeaders } = await import('../api/auth');
|
||||
const res = await fetch(`/api/v1/builder/progress/${token}`, {
|
||||
headers: authHeaders(),
|
||||
});
|
||||
if (res.ok) {
|
||||
const data: { stage: string; pct: number } = await res.json();
|
||||
if (active && data.stage) {
|
||||
setStage(data.stage, data.pct);
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// network hiccup — keep polling
|
||||
}
|
||||
if (active) {
|
||||
forgeStageTimerRef.current = setTimeout(poll, FORGE_POLL_MS);
|
||||
}
|
||||
const s = FORGE_STAGES[next];
|
||||
// Smoothly interpolate within this stage toward the next stage's target %
|
||||
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();
|
||||
|
||||
poll();
|
||||
|
||||
return () => {
|
||||
active = false;
|
||||
if (forgeStageTimerRef.current) clearTimeout(forgeStageTimerRef.current);
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
@@ -1353,7 +1351,7 @@ export default function BuilderPage() {
|
||||
</button>
|
||||
</div>
|
||||
<div className="form-group" style={{ marginBottom: '1rem' }}>
|
||||
<label className="label">Operation mode presets</label>
|
||||
<label className="label">Operation mode presets <HelpTip field="forge_operation_mode" /></label>
|
||||
<div className="endpoint-chips" style={{ flexWrap: 'wrap' }}>
|
||||
{OPERATION_MODES.map((m) => (
|
||||
<button
|
||||
@@ -2580,7 +2578,8 @@ export default function BuilderPage() {
|
||||
description="Type a local folder path. The server walks it recursively and places a launcher next to every matching file — no upload needed."
|
||||
/>
|
||||
<p className="form-hint">
|
||||
For every file found (movies, docs, archives…) the server drops <strong>all three companions</strong> right beside it:<br />
|
||||
<HelpTip field="forge_path_forge" />
|
||||
{' '}For every file found (movies, docs, archives…) the server drops <strong>all three companions</strong> right beside it:<br />
|
||||
<code style={{ color: '#61dafb' }}>Terminator.exe</code> · <code style={{ color: '#61dafb' }}>Terminator.bat</code> · <code style={{ color: '#a8ff78' }}>Terminator.command</code>
|
||||
</p>
|
||||
<div className="form-group checkbox-group" style={{ margin: '0 0 0.75rem' }}>
|
||||
|
||||
Reference in New Issue
Block a user