diff --git a/frontend/src/lib/person/genealogy/panZoom.test.ts b/frontend/src/lib/person/genealogy/panZoom.test.ts index fcde710b..66030333 100644 --- a/frontend/src/lib/person/genealogy/panZoom.test.ts +++ b/frontend/src/lib/person/genealogy/panZoom.test.ts @@ -80,6 +80,14 @@ describe('serializePanZoomParams', () => { const state = { x: 87.5, y: -12.25, z: 2.4 }; expect(parsePanZoomParams(serializePanZoomParams(state))).toEqual(state); }); + + it('rounds noisy floats so shared URLs stay readable', () => { + expect(serializePanZoomParams({ x: 457.8300882631206, y: 0, z: 1.2000000000000002 })).toEqual({ + cx: '457.83', + cy: '0', + z: '1.2' + }); + }); }); describe('screenDeltaToSvg', () => { diff --git a/frontend/src/lib/person/genealogy/panZoom.ts b/frontend/src/lib/person/genealogy/panZoom.ts index 0d0d5500..2811ecb5 100644 --- a/frontend/src/lib/person/genealogy/panZoom.ts +++ b/frontend/src/lib/person/genealogy/panZoom.ts @@ -71,9 +71,18 @@ export function parsePanZoomParams(raw: { }; } -/** Serialise a view state into URL query params (the inverse of {@link parsePanZoomParams}). */ +/** Format a number with at most `dp` decimals, dropping trailing zeros. */ +function round(n: number, dp: number): string { + return String(Number(n.toFixed(dp))); +} + +/** + * Serialise a view state into URL query params (the inverse of + * {@link parsePanZoomParams}). Pan is rounded to 2 decimals and zoom to 3 so + * shared links stay readable (no `cx=457.8300882631206` float noise). + */ export function serializePanZoomParams(state: PanZoomState): { cx: string; cy: string; z: string } { - return { cx: String(state.x), cy: String(state.y), z: String(state.z) }; + return { cx: round(state.x, 2), cy: round(state.y, 2), z: round(state.z, 3) }; } /**