mxdrw expects HTTP Basic Auth (BASIC_AUTH_USER + BASIC_AUTH_PASS on the
server side). Replace MEXDRAW_TOKEN with MEXDRAW_USER + MEXDRAW_PASS,
use req.SetBasicAuth on the export PUT.
Updated docker-compose.yml comment and README env table to match.
Roundtrip verified locally against mxdrw.msbls.de.
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.
New store methods on internal/db/ports.go:
- CreatePort / GetPort / UpdatePort / DeletePort (all project-scoped)
- ListPortsForDevice for the inspector's per-device list
New handlers (internal/server/ports.go):
- GET /api/projects/:pid/devices/:id/ports
- POST /api/projects/:pid/devices/:id/ports ← {type_id, label?, x_offset, y_offset}
- PATCH /api/projects/:pid/ports/:id ← partial
- DELETE /api/projects/:pid/ports/:id (cables ref → ON DELETE SET NULL)
Lets slice 7's +Port tool add/remove instance ports without going
through the type-seeded auto-creation path from slice 4.
- GET /api/device-types — built-ins only (read-only).
- GET /api/projects/:pid/device-types — built-ins + project-custom merged.
- POST/PATCH/DELETE /api/projects/:pid/device-types — project-custom only.
Mutating a built-in row returns 403 via the new ErrForbidden → 403 map
in writeError.
- devicePatch / deviceCreate JSON shapes accept type_id (tri-state for
PATCH via the existing parseFrameRef helper applied to type_id too).
- POST /api/projects/:pid/devices with type_id seeds ports in one tx
server-side; response carries the device row + the snapshot will then
carry the new ports.
Issue 1 — cursor lies about armed tool. .svg-draggable { cursor: grab }
on frame/device rects beat the .canvas-wrap.tool-device #canvas {
cursor: crosshair } rule because element-level wins over descendant.
m saw "grab" hovering a frame with +Dev armed and thought the tool was
broken even though clicks routed correctly after the previous fix. Add
a descendant rule with !important so tool-armed wraps any child cursor:
.canvas-wrap.tool-frame #canvas *,
.canvas-wrap.tool-device #canvas * { cursor: crosshair !important; }
Issue 2 — stale browser cache after each redeploy. http.FileServerFS
served embedded assets with no Cache-Control header, so browsers held
on to the previous main.js/style.css until hard-reload. New noCache
middleware on the static handler emits Cache-Control: no-cache. Note:
embedded FS files have zero ModTime, so http.FileServer suppresses
Last-Modified — every fetch is a fresh 200 rather than a 304. Fine at
~30KB of JS+CSS, and fixes the staleness problem completely.
Middleware is wrapped only around the static handler. /api/* responses
write their own headers and aren't touched.
Verified locally:
curl -I /main.js → Cache-Control: no-cache
curl -I /style.css → Cache-Control: no-cache + contains the new rule
curl -I /api/healthz → unaffected (no Cache-Control from us)
go test -race ./... still green.
All 8 endpoints (list, create, patch, delete) for both resources. Path
params parsed via Go 1.22 ServeMux PathValue.
devicePatch uses json.RawMessage for frame_id so the wire format
distinguishes:
- key absent → leave as-is
- "frame_id": null → clear (device leaves all frames)
- "frame_id": 42 → move to that frame
parseFrameRef translates that into the store's db.FrameRef tri-state.
Sentinel-error mapping unchanged (writeError covers ErrInvalidInput,
ErrConflict, ErrNotFound, etc.). Cross-project frame_id refs surface as
400.