fix(ui): keep manifest options available in run trigger

This commit is contained in:
2026-02-23 19:07:35 -05:00
parent cf386e1aaa
commit e7dbc9870f

View File

@@ -117,6 +117,9 @@ const MANIFEST_EVENT_TRIGGERS = [
"branch_merged", "branch_merged",
]; ];
const RUN_MANIFEST_EDITOR_VALUE = "__editor__";
const RUN_MANIFEST_EDITOR_LABEL = "[Use Manifest Editor JSON]";
function fmtMoney(value) { function fmtMoney(value) {
return `$${Number(value || 0).toFixed(4)}`; return `$${Number(value || 0).toFixed(4)}`;
} }
@@ -838,6 +841,26 @@ function parseJsonSafe(text, fallback) {
} }
} }
function dedupeNonEmptyStrings(values) {
const output = [];
const seen = new Set();
for (const value of values) {
if (typeof value !== "string") {
continue;
}
const normalized = value.trim();
if (!normalized || seen.has(normalized)) {
continue;
}
seen.add(normalized);
output.push(normalized);
}
return output;
}
function populateSelect(select, values, selectedValue) { function populateSelect(select, values, selectedValue) {
const previous = selectedValue || select.value; const previous = selectedValue || select.value;
select.innerHTML = ""; select.innerHTML = "";
@@ -857,6 +880,35 @@ function populateSelect(select, values, selectedValue) {
} }
} }
function populateRunManifestSelect(values, selectedValue) {
const previous = selectedValue || dom.runManifestSelect.value;
dom.runManifestSelect.innerHTML = "";
const editorOption = document.createElement("option");
editorOption.value = RUN_MANIFEST_EDITOR_VALUE;
editorOption.textContent = RUN_MANIFEST_EDITOR_LABEL;
dom.runManifestSelect.append(editorOption);
for (const value of values) {
const option = document.createElement("option");
option.value = value;
option.textContent = value;
dom.runManifestSelect.append(option);
}
if (previous && [...dom.runManifestSelect.options].some((option) => option.value === previous)) {
dom.runManifestSelect.value = previous;
return;
}
if (state.selectedManifestPath && values.includes(state.selectedManifestPath)) {
dom.runManifestSelect.value = state.selectedManifestPath;
return;
}
dom.runManifestSelect.value = RUN_MANIFEST_EDITOR_VALUE;
}
async function loadConfig() { async function loadConfig() {
const payload = await apiRequest("/api/config"); const payload = await apiRequest("/api/config");
state.config = payload.config; state.config = payload.config;
@@ -888,14 +940,19 @@ async function loadConfig() {
async function loadManifests() { async function loadManifests() {
const payload = await apiRequest("/api/manifests"); const payload = await apiRequest("/api/manifests");
state.manifests = payload.manifests; const manifests = dedupeNonEmptyStrings([
...(Array.isArray(payload.manifests) ? payload.manifests : []),
state.selectedManifestPath,
dom.manifestPath.value,
]);
state.manifests = manifests;
if (!state.selectedManifestPath && state.manifests.length > 0) { if (!state.selectedManifestPath && state.manifests.length > 0) {
state.selectedManifestPath = state.manifests[0]; state.selectedManifestPath = state.manifests[0];
} }
populateSelect(dom.graphManifestSelect, state.manifests, state.selectedManifestPath); populateSelect(dom.graphManifestSelect, state.manifests, state.selectedManifestPath);
populateSelect(dom.runManifestSelect, state.manifests, state.selectedManifestPath); populateRunManifestSelect(state.manifests, state.selectedManifestPath);
if (state.selectedManifestPath) { if (state.selectedManifestPath) {
dom.manifestPath.value = state.selectedManifestPath; dom.manifestPath.value = state.selectedManifestPath;
@@ -1332,9 +1389,10 @@ async function startRun(event) {
return; return;
} }
const manifestSelection = dom.runManifestSelect.value.trim();
const payload = { const payload = {
prompt, prompt,
manifestPath: dom.runManifestSelect.value,
executionMode: dom.runExecutionMode.value, executionMode: dom.runExecutionMode.value,
provider: dom.runProvider.value, provider: dom.runProvider.value,
topologyHint: dom.runTopologyHint.value.trim() || undefined, topologyHint: dom.runTopologyHint.value.trim() || undefined,
@@ -1342,6 +1400,20 @@ async function startRun(event) {
simulateValidationNodeIds: fromCsv(dom.runValidationNodes.value), simulateValidationNodeIds: fromCsv(dom.runValidationNodes.value),
}; };
if (manifestSelection === RUN_MANIFEST_EDITOR_VALUE) {
const manifestFromEditor = parseJsonSafe(dom.manifestEditor.value, null);
if (!manifestFromEditor) {
showRunStatus("Manifest editor JSON is invalid. Save or fix it before running.", true);
return;
}
payload.manifest = manifestFromEditor;
} else if (manifestSelection) {
payload.manifestPath = manifestSelection;
} else {
showRunStatus("Select a manifest path or choose editor JSON.", true);
return;
}
try { try {
const response = await apiRequest("/api/runs", { const response = await apiRequest("/api/runs", {
method: "POST", method: "POST",
@@ -1458,7 +1530,7 @@ async function loadManifestEditor() {
dom.manifestPath.value = payload.manifest.path; dom.manifestPath.value = payload.manifest.path;
state.selectedManifestPath = payload.manifest.path; state.selectedManifestPath = payload.manifest.path;
populateSelect(dom.graphManifestSelect, state.manifests, state.selectedManifestPath); populateSelect(dom.graphManifestSelect, state.manifests, state.selectedManifestPath);
populateSelect(dom.runManifestSelect, state.manifests, state.selectedManifestPath); populateRunManifestSelect(state.manifests, state.selectedManifestPath);
setManifestStatus(`Loaded ${payload.manifest.path}.`); setManifestStatus(`Loaded ${payload.manifest.path}.`);
} catch (error) { } catch (error) {
setManifestStatus(error instanceof Error ? error.message : String(error), true); setManifestStatus(error instanceof Error ? error.message : String(error), true);
@@ -1511,7 +1583,9 @@ async function saveManifest(event) {
renderManifestHelper(); renderManifestHelper();
await loadManifests(); await loadManifests();
dom.graphManifestSelect.value = state.selectedManifestPath; dom.graphManifestSelect.value = state.selectedManifestPath;
if ([...dom.runManifestSelect.options].some((option) => option.value === state.selectedManifestPath)) {
dom.runManifestSelect.value = state.selectedManifestPath; dom.runManifestSelect.value = state.selectedManifestPath;
}
setManifestStatus(`Saved ${payload.manifest.path}.`); setManifestStatus(`Saved ${payload.manifest.path}.`);
await refreshGraph(); await refreshGraph();
} catch (error) { } catch (error) {
@@ -1528,7 +1602,9 @@ function bindUiEvents() {
dom.graphManifestSelect.addEventListener("change", async () => { dom.graphManifestSelect.addEventListener("change", async () => {
state.selectedManifestPath = dom.graphManifestSelect.value; state.selectedManifestPath = dom.graphManifestSelect.value;
if ([...dom.runManifestSelect.options].some((option) => option.value === state.selectedManifestPath)) {
dom.runManifestSelect.value = state.selectedManifestPath; dom.runManifestSelect.value = state.selectedManifestPath;
}
dom.manifestPath.value = state.selectedManifestPath; dom.manifestPath.value = state.selectedManifestPath;
await refreshGraph(); await refreshGraph();
}); });