From 4e5ed8b0a26c46b1c8c6da843e57a43985316435 Mon Sep 17 00:00:00 2001 From: "claude[bot]" <41898282+claude[bot]@users.noreply.github.com> Date: Wed, 20 May 2026 15:13:37 +0000 Subject: [PATCH 1/7] feat: add info disclaimer to moveToJointPositions widget (#75) Add a tooltip next to the "Joint Positions" header explaining that joint position limits are based solely on arm kinematics and do not account for motion service limit overrides. Co-Authored-By: Claude Opus 4.6 --- .changeset/joint-position-limits-disclaimer.md | 5 +++++ .../arm/__tests__/move-to-joint-positions.spec.ts | 11 +++++++++++ .../widgets/arm/move-to-joint-positions.svelte | 15 ++++++++++++++- 3 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 .changeset/joint-position-limits-disclaimer.md diff --git a/.changeset/joint-position-limits-disclaimer.md b/.changeset/joint-position-limits-disclaimer.md new file mode 100644 index 0000000..fbdc6d0 --- /dev/null +++ b/.changeset/joint-position-limits-disclaimer.md @@ -0,0 +1,5 @@ +--- +'@viamrobotics/test-widgets': patch +--- + +Add info disclaimer to `moveToJointPositions` widget about joint position limits diff --git a/src/lib/components/widgets/arm/__tests__/move-to-joint-positions.spec.ts b/src/lib/components/widgets/arm/__tests__/move-to-joint-positions.spec.ts index 7b7fa84..8fad5f6 100644 --- a/src/lib/components/widgets/arm/__tests__/move-to-joint-positions.spec.ts +++ b/src/lib/components/widgets/arm/__tests__/move-to-joint-positions.spec.ts @@ -31,6 +31,17 @@ describe('Arm move-to-joint-positions', () => { }) } + it('renders a disclaimer about joint position limits', () => { + renderSubject({ + positions: [1], + jointLimitsDegrees: jointLimitsForCount(1), + }) + + expect( + screen.getByText(/joint position limits are based solely on the arm kinematics/iu) + ).toBeInTheDocument() + }) + it('renders a row for each axis', () => { renderSubject({ positions: [1, 2, 3], diff --git a/src/lib/components/widgets/arm/move-to-joint-positions.svelte b/src/lib/components/widgets/arm/move-to-joint-positions.svelte index 4241d24..0c270e6 100644 --- a/src/lib/components/widgets/arm/move-to-joint-positions.svelte +++ b/src/lib/components/widgets/arm/move-to-joint-positions.svelte @@ -89,7 +89,20 @@
- Joint Positions + + Joint Positions + + + + + Joint position limits are based solely on the arm kinematics and do not take into + account motion service limit overrides. + + +
Date: Wed, 20 May 2026 12:16:51 -0400 Subject: [PATCH 2/7] format --- src/lib/components/widgets/arm/move-to-joint-positions.svelte | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/components/widgets/arm/move-to-joint-positions.svelte b/src/lib/components/widgets/arm/move-to-joint-positions.svelte index 0c270e6..43aa9e4 100644 --- a/src/lib/components/widgets/arm/move-to-joint-positions.svelte +++ b/src/lib/components/widgets/arm/move-to-joint-positions.svelte @@ -98,8 +98,8 @@ /> - Joint position limits are based solely on the arm kinematics and do not take into - account motion service limit overrides. + Joint position limits are based solely on the arm kinematics and do not take into account + motion service limit overrides. From ebb0a18a6759ea7180e19273339896daeb6451af Mon Sep 17 00:00:00 2001 From: Matthew MacFarquhar Date: Thu, 21 May 2026 13:06:05 -0400 Subject: [PATCH 3/7] enable 360 image viewing for cameras with viam xmp data --- .../__tests__/get-xmp-json-from-image.spec.ts | 52 +++++ .../components/widgets/camera/camera.svelte | 59 ++++-- .../widgets/camera/get-xmp-json-from-image.ts | 180 ++++++++++++++++++ .../camera/three-sixty-camera-view.svelte | 69 +++++++ 4 files changed, 349 insertions(+), 11 deletions(-) create mode 100644 src/lib/components/widgets/camera/__tests__/get-xmp-json-from-image.spec.ts create mode 100644 src/lib/components/widgets/camera/get-xmp-json-from-image.ts create mode 100644 src/lib/components/widgets/camera/three-sixty-camera-view.svelte diff --git a/src/lib/components/widgets/camera/__tests__/get-xmp-json-from-image.spec.ts b/src/lib/components/widgets/camera/__tests__/get-xmp-json-from-image.spec.ts new file mode 100644 index 0000000..3309ca7 --- /dev/null +++ b/src/lib/components/widgets/camera/__tests__/get-xmp-json-from-image.spec.ts @@ -0,0 +1,52 @@ +import { describe, expect, it } from 'vitest' + +import { getXmpJsonFromImageBytes } from '../get-xmp-json-from-image' + +const buildJpegWithXmp = (xmpXml: string): Uint8Array => { + const identifier = new TextEncoder().encode('http://ns.adobe.com/xap/1.0/\0') + const xmpBytes = new TextEncoder().encode(xmpXml) + const app1Length = 2 + identifier.length + xmpBytes.length + const app1Segment = new Uint8Array(2 + app1Length) + + app1Segment[0] = 0xff + app1Segment[1] = 0xe1 + app1Segment[2] = (app1Length >> 8) & 0xff + app1Segment[3] = app1Length & 0xff + app1Segment.set(identifier, 4) + app1Segment.set(xmpBytes, 4 + identifier.length) + + return new Uint8Array([0xff, 0xd8, ...app1Segment, 0xff, 0xd9]) +} + +describe('getXmpJsonFromImageBytes', () => { + it('returns null when the image has no XMP segment', () => { + const jpeg = new Uint8Array([0xff, 0xd8, 0xff, 0xd9]) + + expect(getXmpJsonFromImageBytes(jpeg)).toBeNull() + }) + + it('parses GPano fields from a JPEG XMP packet', () => { + const xmpXml = ` + + + + + +` + + const jpeg = buildJpegWithXmp(xmpXml) + const xmpJson = getXmpJsonFromImageBytes(jpeg, 'image/jpeg') + + expect(xmpJson).toEqual({ + 'GPano:ProjectionType': 'equirectangular', + 'GPano:UsePanoramaViewer': 'True', + 'GPano:PoseHeadingDegrees': '90', + }) + }) +}) diff --git a/src/lib/components/widgets/camera/camera.svelte b/src/lib/components/widgets/camera/camera.svelte index 32c7d79..d4203c4 100644 --- a/src/lib/components/widgets/camera/camera.svelte +++ b/src/lib/components/widgets/camera/camera.svelte @@ -1,4 +1,5 @@ + + + + + +{#if $texture} + {#await texture then map} + + + + + {/await} +{/if} + + + From 238a35c5b790289b242b7170d108bde06ff58f9c Mon Sep 17 00:00:00 2001 From: Matthew MacFarquhar Date: Thu, 21 May 2026 13:10:48 -0400 Subject: [PATCH 4/7] Revert "feat: add info disclaimer to moveToJointPositions widget (#75)" This reverts commit 4e5ed8b0a26c46b1c8c6da843e57a43985316435. --- .changeset/joint-position-limits-disclaimer.md | 5 ----- .../arm/__tests__/move-to-joint-positions.spec.ts | 11 ----------- .../widgets/arm/move-to-joint-positions.svelte | 15 +-------------- 3 files changed, 1 insertion(+), 30 deletions(-) delete mode 100644 .changeset/joint-position-limits-disclaimer.md diff --git a/.changeset/joint-position-limits-disclaimer.md b/.changeset/joint-position-limits-disclaimer.md deleted file mode 100644 index fbdc6d0..0000000 --- a/.changeset/joint-position-limits-disclaimer.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@viamrobotics/test-widgets': patch ---- - -Add info disclaimer to `moveToJointPositions` widget about joint position limits diff --git a/src/lib/components/widgets/arm/__tests__/move-to-joint-positions.spec.ts b/src/lib/components/widgets/arm/__tests__/move-to-joint-positions.spec.ts index 8fad5f6..7b7fa84 100644 --- a/src/lib/components/widgets/arm/__tests__/move-to-joint-positions.spec.ts +++ b/src/lib/components/widgets/arm/__tests__/move-to-joint-positions.spec.ts @@ -31,17 +31,6 @@ describe('Arm move-to-joint-positions', () => { }) } - it('renders a disclaimer about joint position limits', () => { - renderSubject({ - positions: [1], - jointLimitsDegrees: jointLimitsForCount(1), - }) - - expect( - screen.getByText(/joint position limits are based solely on the arm kinematics/iu) - ).toBeInTheDocument() - }) - it('renders a row for each axis', () => { renderSubject({ positions: [1, 2, 3], diff --git a/src/lib/components/widgets/arm/move-to-joint-positions.svelte b/src/lib/components/widgets/arm/move-to-joint-positions.svelte index 43aa9e4..4241d24 100644 --- a/src/lib/components/widgets/arm/move-to-joint-positions.svelte +++ b/src/lib/components/widgets/arm/move-to-joint-positions.svelte @@ -89,20 +89,7 @@
- - Joint Positions - - - - - Joint position limits are based solely on the arm kinematics and do not take into account - motion service limit overrides. - - - + Joint Positions
Date: Thu, 21 May 2026 13:14:15 -0400 Subject: [PATCH 5/7] update test file --- .../camera/__tests__/get-xmp-json-from-image.spec.ts | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/lib/components/widgets/camera/__tests__/get-xmp-json-from-image.spec.ts b/src/lib/components/widgets/camera/__tests__/get-xmp-json-from-image.spec.ts index 3309ca7..9e0ef51 100644 --- a/src/lib/components/widgets/camera/__tests__/get-xmp-json-from-image.spec.ts +++ b/src/lib/components/widgets/camera/__tests__/get-xmp-json-from-image.spec.ts @@ -25,16 +25,14 @@ describe('getXmpJsonFromImageBytes', () => { expect(getXmpJsonFromImageBytes(jpeg)).toBeNull() }) - it('parses GPano fields from a JPEG XMP packet', () => { + it('parses viam:is360 from a JPEG XMP packet', () => { const xmpXml = ` @@ -44,9 +42,7 @@ describe('getXmpJsonFromImageBytes', () => { const xmpJson = getXmpJsonFromImageBytes(jpeg, 'image/jpeg') expect(xmpJson).toEqual({ - 'GPano:ProjectionType': 'equirectangular', - 'GPano:UsePanoramaViewer': 'True', - 'GPano:PoseHeadingDegrees': '90', + 'viam:is360': 'true', }) }) }) From 34dedf62e20d0b7d7955b05c305fb74b4673b8d3 Mon Sep 17 00:00:00 2001 From: Matthew MacFarquhar Date: Thu, 21 May 2026 13:25:56 -0400 Subject: [PATCH 6/7] changeset --- .changeset/stale-games-float.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/stale-games-float.md diff --git a/.changeset/stale-games-float.md b/.changeset/stale-games-float.md new file mode 100644 index 0000000..db98601 --- /dev/null +++ b/.changeset/stale-games-float.md @@ -0,0 +1,5 @@ +--- +'@viamrobotics/test-widgets': minor +--- + +support 360 camera images with special XMP metadata From a4dd47f8476de7b3358512f324af1e810292badc Mon Sep 17 00:00:00 2001 From: Matthew MacFarquhar Date: Thu, 21 May 2026 13:29:57 -0400 Subject: [PATCH 7/7] format --- .../camera/__tests__/get-xmp-json-from-image.spec.ts | 3 +++ .../components/widgets/camera/get-xmp-json-from-image.ts | 9 ++------- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/lib/components/widgets/camera/__tests__/get-xmp-json-from-image.spec.ts b/src/lib/components/widgets/camera/__tests__/get-xmp-json-from-image.spec.ts index 9e0ef51..b56c373 100644 --- a/src/lib/components/widgets/camera/__tests__/get-xmp-json-from-image.spec.ts +++ b/src/lib/components/widgets/camera/__tests__/get-xmp-json-from-image.spec.ts @@ -43,6 +43,9 @@ describe('getXmpJsonFromImageBytes', () => { expect(xmpJson).toEqual({ 'viam:is360': 'true', + 'xmlns:rdf': 'http://www.w3.org/1999/02/22-rdf-syntax-ns#', + 'xmlns:viam': 'https://www.viam.com/', + 'xmlns:x': 'adobe:ns:meta/', }) }) }) diff --git a/src/lib/components/widgets/camera/get-xmp-json-from-image.ts b/src/lib/components/widgets/camera/get-xmp-json-from-image.ts index 17c37c3..d58b9ee 100644 --- a/src/lib/components/widgets/camera/get-xmp-json-from-image.ts +++ b/src/lib/components/widgets/camera/get-xmp-json-from-image.ts @@ -8,10 +8,7 @@ const XMP_IDENTIFIER = 'http://ns.adobe.com/xap/1.0/\0' export type XmpJson = Record /** Extract XMP metadata from image bytes and return it as a plain object. */ -export const getXmpJsonFromImageBytes = ( - image: Uint8Array, - mimeType?: string -): XmpJson | null => { +export const getXmpJsonFromImageBytes = (image: Uint8Array, mimeType?: string): XmpJson | null => { if (mimeType?.includes('png')) { return getXmpJsonFromPng(image) } @@ -146,9 +143,7 @@ const xmpXmlToJson = (xmpXml: string): XmpJson | null => { } if (element.childElementCount === 0 && element.textContent?.trim()) { - const key = element.prefix - ? `${element.prefix}:${element.localName}` - : element.localName + const key = element.prefix ? `${element.prefix}:${element.localName}` : element.localName json[key] = element.textContent.trim() } }