Improve portable launch, forge persistence, and operator auth UX.

Persist build extra_files for Build Manager history, print dashboard login on every start, add libp2p for Mesh P2P forge, defer WebSocket until login, and split devrun.bat from LAUNCH.bat with USB deck auto-detection.
This commit is contained in:
AetherForge
2026-05-31 18:56:43 -07:00
parent 2f528229f2
commit feba06e008
80 changed files with 2897 additions and 675 deletions

View File

@@ -19,7 +19,7 @@ import {
type ForgeDeliverable,
} from '../help/forgeFormNormalize';
import { ForgeFieldBadge, ForgeLockedHint, ForgeSectionHeader } from '../components/Forge/ForgeFieldHints';
import { buildRequestFromRecord } from '../help/buildManager';
import { blueprintDiff, buildRequestFromRecord } from '../help/buildManager';
import DownloadButton from '../components/DownloadButton';
import { useForge } from '../context/ForgeContext';
import {
@@ -272,6 +272,7 @@ export default function BuilderPage() {
try {
const data = await api.getBlueprint(name);
setCompareBlueprint(data as Record<string, unknown>);
setBlueprintName(name);
// Merge loaded data into form, preserving any fields not in the blueprint
setForm((prev) => (prev ? { ...prev, ...data } : prev));
setShowBlueprints(false);
@@ -341,7 +342,9 @@ export default function BuilderPage() {
const reader = new FileReader();
reader.onload = (evt) => {
try {
const data = JSON.parse(evt.target?.result as string);
const data = JSON.parse(evt.target?.result as string) as Record<string, unknown>;
setCompareBlueprint(data);
setBlueprintName(file.name);
setForm((prev) => (prev ? { ...prev, ...data } : prev));
setBlueprintMsg(`✅ Blueprint loaded from "${file.name}"`);
setTimeout(() => setBlueprintMsg(''), 3000);
@@ -594,6 +597,10 @@ export default function BuilderPage() {
);
const canForge = form ? !preflightHasErrors(preflightChecks) : false;
const errorCount = preflightChecks.filter((c) => c.level === 'error').length;
const blueprintChanges = useMemo(() => {
if (!compareBlueprint || !form) return [];
return blueprintDiff(compareBlueprint, form as unknown as Record<string, unknown>);
}, [compareBlueprint, form]);
const fusionIsExe = fusionPayloadKind(fusionPrepFile) === 'exe';
const fusionMediaMode = form?.fusion_media_mode || 'paired';
@@ -754,6 +761,44 @@ export default function BuilderPage() {
</div>
)}
{compareBlueprint && blueprintChanges.length > 0 && (
<div className="card" style={{ marginBottom: '16px' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '8px' }}>
<h3 style={{ margin: 0, fontSize: '1rem' }}>
Blueprint diff {blueprintName || 'loaded blueprint'}
</h3>
<button
type="button"
className="btn btn-outline btn-sm"
onClick={() => {
setCompareBlueprint(null);
setBlueprintName('');
}}
>
Dismiss
</button>
</div>
<p className="form-hint" style={{ marginTop: 0 }}>
Fields that differ from the loaded blueprint (your previous form values were kept where they conflict).
</p>
<ul className="blueprint-diff-list" style={{ margin: 0, paddingLeft: '1.25rem', fontSize: '0.85rem' }}>
{blueprintChanges.map((row) => (
<li key={row.key}>
<code>{row.key}</code>
{' — '}
{row.kind === 'added' && <span>kept from form</span>}
{row.kind === 'removed' && <span>not in current form</span>}
{row.kind === 'changed' && (
<span>
blueprint current
</span>
)}
</li>
))}
</ul>
</div>
)}
{/* Blueprint picker panel */}
{showBlueprints && (
<div className="card" style={{ marginBottom: '16px' }}>
@@ -2022,6 +2067,19 @@ export default function BuilderPage() {
Download
</DownloadButton>
)}
{lastBuild.build_id &&
lastBuild.extra_files?.map((f) =>
f.file_name ? (
<DownloadButton
key={f.file_name}
apiPath={api.buildArtifactUrl(lastBuild.build_id!, f.file_name)}
filename={f.file_name}
className="btn btn-outline btn-sm"
>
{f.file_name}
</DownloadButton>
) : null
)}
<button
type="button"
className="btn btn-outline btn-sm"