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:
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user