diff --git a/frontend/src/components/control/BrowserCameraPreview.test.tsx b/frontend/src/components/control/BrowserCameraPreview.test.tsx
index 818c762a7..c6bca6f70 100644
--- a/frontend/src/components/control/BrowserCameraPreview.test.tsx
+++ b/frontend/src/components/control/BrowserCameraPreview.test.tsx
@@ -39,3 +39,13 @@ test("a stream at another size keeps the recorded aspect ratio", () => {
expect(video.style.width).toBe("100%");
expect(video.style.objectFit).toBe("cover");
});
+test("the reference ghost sits over the video with its rotation and opacity", () => {
+ act(() => root.render(
+ )}
+ >
) : (
These are the cameras set up for this robot. To add or change a - camera, configure it on the Calibration page. + camera, configure it on the Calibration page. Save a reference + frame to see it as a ghost over the live preview and realign + the rig next session.
{rotationError} Select a valid rotation.
} {readOnly ? (
diff --git a/frontend/src/components/recording/CameraReferenceControls.tsx b/frontend/src/components/recording/CameraReferenceControls.tsx
new file mode 100644
index 000000000..c23e97acc
--- /dev/null
+++ b/frontend/src/components/recording/CameraReferenceControls.tsx
@@ -0,0 +1,67 @@
+import React from "react";
+import { Button } from "@/components/ui/button";
+
+interface Props {
+ hasReference: boolean;
+ opacity: number;
+ onOpacityChange: (opacity: number) => void;
+ onSave: () => void;
+ onClear: () => void;
+ disabled: boolean;
+ error: string | null;
+}
+
+/** Save the current frame as the reference, then slide the ghost overlay over the live preview. */
+const CameraReferenceControls: React.FC {error}`. Pass `robot = undefined` to disable it.
+ */
+export function useCameraReference(robot: string | undefined, camera: string) {
+ const { baseUrl, fetchWithHeaders } = useApi();
+ const [url, setUrl] = useState