fix: apply-template auto-solves + frontend reloads via activateProject

Two changes to close the UX hole m hit on slice 6 — Apply Template
appeared to do nothing because (a) the canvas wasn't refreshed cleanly
and (b) the cables hadn't been computed yet.

Backend (internal/server/solver.go applyTemplate handler):
- After ApplyTemplate succeeds, run Solve(false) inside the same
  request. Combined response shape:
    { template_apply: <ApplyTemplateResult>, solve: <SolveResult> }
- Opt out with ?solve=0 for power-users who want to inspect the
  seeded devices/requirements before the solver runs. Response in that
  case is { template_apply: ... } only.
- If Solve fails after a successful apply, return
  { template_apply, solve_error: "..." } so the frontend can recover
  (devices are still there; m can hit Solve manually).

Frontend (web/static/main.js apply-template modal submit):
- Replaced the bare re-snapshot with a call to activateProject(pid).
  That's the canonical project-load path — it re-hydrates ALL
  collections (frames, devices, ports, io_markers, cables, bundles,
  requirements, cable_types, device_types), clears state.selection
  so a stale pre-apply selection can't linger, and routes through the
  same render() the URL-state hydration uses on initial page load.
- The slice-6 inlined re-snapshot missed the device_types refresh +
  selection reset, which I suspect was what made the canvas look
  stuck — render()ing with state.selection.kind="cable_type" or
  "requirement" pointing at a not-yet-loaded row.

Hand-test (local): Living Room + auto-solve produces 4 devices + 3
requirements + 3 cables; ?solve=0 leaves cables empty. Snapshot
includes the cables on auto-solve path.
This commit is contained in:
mAi
2026-05-16 01:23:37 +02:00
parent 0c7d165ed6
commit 2cd981d3ae
2 changed files with 35 additions and 11 deletions

View File

@@ -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");
}