fix(calibration): add explicit "Save port" button so ports can be updated without recalibrating (#53) - #59
Conversation
SO-101 serial ports change on reconnect, and the only *visible* way to update a robot's port was to redo the entire calibration. The port already autosaves on blur (027575b), but with no button or confirmation users don't realize it — hence issue huggingface#53 ("Missing save button during calibration"). Add a visible "Save" button next to the port field that persists the port to the robot record and shows a confirmation toast, plus a hint line noting ports can change on reconnect. persistPort() now returns a result so the button can report success/failure. No backend change — POST /robots/{name} already supports partial (port-only) updates. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
nicolas-rabault
left a comment
There was a problem hiding this comment.
Thanks for digging into this. The root-cause analysis is spot on. But I think it points to a lighter fix than the button.
As you note, persistPort() already autosaves the port on blur and on re-detection (handlePortDetected), keyed by the robot config name. So the port already updates independently of calibration; #53's "only way is to recalibrate" was stale by the time it was written. The reporter just didn't get any feedback that the save happened.
Given that, the real gap is visibility, not a missing action — and a Save button duplicates the onBlur autosave that already fires. I'd rather not add a manual save step for something that already saves on its own.
Proposal, minus the button:
- Move the confirmation toast into
persistPort()itself, fired only when the value actually changed (it already early-returns onunchanged). Then editing or re-detecting the port shows "Saved<port>for the leader/follower, no recalibration needed" with zero extra clicks. - Keep the one-line hint under the field. That's the part that actually closes the discoverability gap.
- Drop the Save button and the
handleSavePorthandler.
Keeping persistPort's "saved" | "unchanged" | "skipped" | "error" return is still useful for the toast logic. Net result is the same UX #53 asked for (visible confirmation, no recalibration) with less surface area.
WDYT?
|
I can take this up if it helps move it along. nicolas's lighter version makes sense to me: move the confirmation toast into persistPort so it fires on the value-changed path the onBlur/detect autosave already hits, keep the hint under the field, and drop the button and handleSavePort. persistPort's saved/unchanged/skipped/error return still drives the toast. Happy to push that onto this branch or open a follow-up, whichever is easier. |
nicolas-rabault
left a comment
There was a problem hiding this comment.
Thanks for keeping this alive, and thanks for the offer @VihaanAgarwal, your read of the lighter version is right.
@pgsharma it is your branch, so first call is yours: push the toast-inside-persistPort version onto it and this lands. If you would rather hand it off, just say so here and Vihaan can push to the same branch. Either way please keep it on this one branch rather than a fresh PR.
One thing to fold in that neither of us caught earlier: a 200 from POST /robots/{name} does not mean the port was written. Without ?create=true the backend no-ops on a missing record and still returns success with "robot": null, so the toast has to be gated on the returned robot record, not on res.ok. Details in the review.
| } | ||
| const data = await res.json(); | ||
| if (data.robot) setRobot(data.robot); | ||
| return "saved"; |
There was a problem hiding this comment.
res.ok does not prove a write happened, so this can report success on a save that never landed. POST /robots/{name} without ?create=true calls save_robot_record(..., allow_create=False), which no-ops when the record does not exist and still returns 200 with {"status": "success", "robot": null}. The user gets a green toast and nothing is on disk. Reachable case: the robot was deleted or renamed in another tab while Calibration was open.
Gate on the payload:
const data = await res.json();
if (!data.robot) return "error";
setRobot(data.robot);
return "saved";This carries into the toast-inside-persistPort version, so it is worth fixing there rather than dropping with the button.
Problem
Fixes #53.
The SO-101 uses serial, so its port (
/dev/tty.usbmodem…,COM…) changes when the arm is unplugged/reconnected. As the issue reports, the only visible way to update a robot's saved port was to redo the entire calibration.Root cause
The port already does autosave:
persistPort()inCalibration.tsxwrites the port to the robot record on input blur and on port-detection (added in 027575b, ~2 weeks before this issue was filed). But it's silent — no button, no confirmation — so users don't realize the change was saved and reasonably assume recalibration is the only path. Hence "Missing save button during calibration."Fix (frontend only)
Saved <port> for the leader/follower — no recalibration needed).persistPort()now returns"saved" | "unchanged" | "skipped" | "error"(and checksres.ok) so the button can report success/failure. The existing silent onBlur / on-detect autosave is unchanged.No backend change —
POST /robots/{name}already does partial (port-only) merges viasave_robot_record.Testing
npm run build✓ andeslint src/pages/Calibration.tsx✓ (clean).frontend/dist/intentionally not committed — CI (build_frontend.yml) rebuilds it on merge.🤖 Generated with Claude Code