[Backport release/0.0.14] feat(cockpit): map click-to-goal, path overlay and cancel button - #4189
github-actions[bot] wants to merge 1 commit into
Conversation
(cherry picked from commit 5021ce2)
|
| const onCanvasClick = (e: MouseEvent): void => { | ||
| const rect = canvas.getBoundingClientRect(); | ||
| if (place === null || rect.width === 0 || rect.height === 0) return; | ||
| const t = fitTransform(place, canvas.width, canvas.height); | ||
| const px = (e.clientX - rect.left) * canvas.width / rect.width; | ||
| const py = (e.clientY - rect.top) * canvas.height / rect.height; | ||
| onClick?.(...canvasToWorld(t, px, py)); |
There was a problem hiding this comment.
When a fitted map is letterboxed, this handler accepts clicks in the blank canvas margins and publishes coordinates outside the displayed costmap. With a 2m×2m map centered in a 400×200 canvas, clicking (20,100) in the left margin published {x:-0.8,y:1} although the map bounds are x=[0,2]. A normal margin click can therefore replace the active goal with an unplannable out-of-bounds target. Reject clicks outside the fitted grid before converting and publishing them.
Knowledge Base Used:
Artifacts
- The executable browser script imports the candidate MapPanel source through Vite, renders the letterboxed map, and records both click conditions; it directly exercises the candidate handler.
- The executed command log records exit code 0, the valid control goal `{x:1,y:1}`, and the margin-click goal `{x:-0.8,y:1}`; the margin click publishes outside the costmap.
- Chromium recording of a click at the center of the visible map that publishes the valid in-bounds goal `{x:1,y:1}`; it establishes the same rendered canvas and normal click behavior.
Control click result inside the centered costmap
- Poster frame from the control recording showing the rendered letterboxed map and its in-bounds click result; the map-area control produces a valid goal.
- Captured data from the executed in-map control click records canvas `(200,100)` and emitted `{x:1,y:1}`; the control goal is inside bounds.
- Chromium recording of a click in the left black letterbox margin rather than the map that publishes `{x:-0.8,y:1}`; the candidate accepts and publishes an out-of-bounds goal.
Margin click result showing an out-of-bounds x coordinate
- Poster frame from the margin-click recording showing the letterboxed map and published negative x goal; the left margin produces an invalid costmap coordinate.
- Captured data from the executed left-margin click records canvas `(20,100)` and emitted `{x:-0.8,y:1}`; the emitted x coordinate is outside bounds.
| @web_decoder("point.json.v1") | ||
| def decode_point(msg: dict[str, Any]) -> PointStamped: | ||
| if not isinstance(msg, dict): | ||
| raise ValueError(f"point.json.v1 wants an object, got {type(msg).__name__}") | ||
| return PointStamped( | ||
| finite_number(msg.get("x"), "x"), finite_number(msg.get("y"), "y"), frame_id="world" | ||
| ) |
There was a problem hiding this comment.
A click on a costmap whose frame is not world is decoded as PointStamped(frame_id="world"). The map wire data drops the original OccupancyGrid.frame_id, so coordinates calculated in an odom, map, or other costmap frame are silently interpreted as world coordinates by frame-aware navigation consumers. Propagate the displayed costmap frame through the Map2D click contract, or reject non-world maps rather than publishing mislabeled goals.
Knowledge Base Used:
Artifacts
- Executable Python contract creates a costmap in an arbitrary frame and exercises the registered click decoder, showing whether the frame survives.
- Executable shell command checks out the parent revision temporarily and saves side-by-side runtime captures for the baseline and candidate.
- Captured command output from the parent revision shows `point.json.v1` was unregistered before the change, establishing the comparable baseline.
- Captured command output from the candidate encodes an `arbitrary_costmap_frame` map and decodes a click as a `world` point, confirming frame loss.
| function send( | ||
| session: Session, | ||
| ch: string, | ||
| value: JsonValue, | ||
| onError: (message: string | null) => void, | ||
| ): void { | ||
| session.publish(ch, value).then( | ||
| () => onError(null), | ||
| (err: unknown) => onError(`send failed: ${err instanceof Error ? err.message : String(err)}`), | ||
| ); |
There was a problem hiding this comment.
An older map-click success can clear the error from a later cancel request because both operations update the same error state without request correlation. The rendered cancel failure disappeared after the earlier click completed successfully. This is non-blocking, but it hides the latest failed action and makes operators believe the cancel was sent. Track the newest request or keep click and cancel error state separate.
Knowledge Base Used:
Artifacts
- Chromium executes the overlap harness, rejects the second cancel publish while the first click remains pending, and shows the cancel error alert; this establishes the state before the stale completion.
Poster frame showing send failed newer cancel failed on the map panel
- Poster frame from the before recording shows the rendered newer cancel failure alert; the newer failure is correctly visible before the stale click completion.
- Chromium first renders the newer cancel failure and then resolves the older click publish; the alert disappears, proving the stale completion overwrites newer error state.
Poster frame showing the map panel with no alert after the stale success
- Poster frame from the after recording shows no error alert after the earlier click succeeds; the newer cancel failure has been incorrectly cleared.
- The recorded command, working directory, Playwright output, and exit code show the targeted Chromium test ran successfully with both assertions passing; the behavior is confirmed.
- Playwright's machine-readable observed output lists both browser test cases, their video attachments, and passed status; the forced settle ordering was exercised.
- The executed Playwright source mounts the actual MapPanel, leaves click and cancel publishes pending, rejects cancel first, then resolves click; it directly tests the reported race.
- The executed harness renders the actual MapPanel with an active path and deferred Session.publish promises; it provides deterministic overlapping click and cancel operations.
- The executed configuration starts Vite and runs the targeted test in Chromium with video capture; it documents the browser test command environment.
- The executed Vite configuration resolves the cockpit workspace aliases while serving the test harness; it enables the actual component to run in Chromium.
Description
Backport of #4183 to
release/0.0.14.