diff --git a/internal/server/solver.go b/internal/server/solver.go index 99a4520..f5484cf 100644 --- a/internal/server/solver.go +++ b/internal/server/solver.go @@ -108,7 +108,27 @@ func (h *handlers) applyTemplate(w http.ResponseWriter, r *http.Request) { writeError(w, err, nil) return } - writeJSON(w, http.StatusOK, res) + + // Auto-solve by default. ?solve=0 opts out for power users who want + // to inspect the seeded devices/requirements before the solver runs. + // This is THE fix for the v6 UX hole: m hit Apply, saw an empty + // canvas because nothing reloaded *and* nothing solved. With the + // frontend re-snapshotting after the POST returns and the response + // already carrying solver output, m sees the wired diagram in one click. + skipSolve := r.URL.Query().Get("solve") == "0" + combined := map[string]any{"template_apply": res} + if !skipSolve { + solveRes, err := h.store.Solve(pid, false) + if err != nil { + // Apply succeeded but Solve failed — don't 500 the whole + // call. Return template_apply with the solve error inline so + // the UI can recover (devices are there; m can re-solve). + combined["solve_error"] = err.Error() + } else { + combined["solve"] = solveRes + } + } + writeJSON(w, http.StatusOK, combined) } // fmtSscan parses a base-10 int from a string, returning (n, nil) on success. diff --git a/web/static/main.js b/web/static/main.js index 57127fe..fbd4764 100644 --- a/web/static/main.js +++ b/web/static/main.js @@ -2055,21 +2055,25 @@ async function openApplyTemplateModal() { if (did) skip.push(did); }); try { - await applyTemplate(state.active.id, { + // The server auto-solves by default since v0c7d165 — the response + // is {template_apply, solve} (or {template_apply, solve_error}). + // We don't need to read the body here; activateProject() below + // pulls a fresh snapshot that includes both the seeded devices + // and any cables the solver placed. + const projID = state.active.id; + await applyTemplate(projID, { template_id: tid, name_overrides: overrides, skip_devices: skip, }); - const snap = await getSnapshot(state.active.id); - state.frames = snap.frames || []; - state.devices = snap.devices || []; - state.ports = snap.ports || []; - state.ioMarkers = snap.io_markers || []; - state.requirements = snap.connection_requirements || []; - state.cables = snap.cables || []; - state.bundles = snap.bundles || []; dlg.close(); - render(); + // Route through the canonical project-load path. That re-hydrates + // ALL collections (frames, devices, ports, io_markers, cables, + // bundles, requirements, cable_types, device_types) AND clears + // the selection — important because m may have had a stale + // selection from before the apply. Slice 6's bare re-snapshot + // missed the device_types refresh + selection reset. + await activateProject(projID); } catch (ex) { showError(err, ex.message || "Apply failed"); }