From 09af60303dda6aafd405ff4dcaeb1a9463107169 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Tue, 2 Jun 2026 16:55:55 +0200 Subject: [PATCH] feat(medications): redraw the injection-site picker as an anatomical silhouette MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the stick-figure outline with a traced front-of-body silhouette, still an inline SVG so it inherits the theme tokens (currentColor + a soft currentColor wash) and tints correctly across light/dark and the zinc/Dracula palettes with no raster asset. Recalibrate SITE_COORDS onto the new 100x200 viewBox (aspect ≈0.5) using the iOS client's hands-on fractional coordinates, so both apps land their dots on the same anatomy. The coordinates double as the Euclidean basis for the rotation recommender, so picker and recommendation stay in sync. Dot states: selected (filled), recommended-next (dashed primary ring), and a new last-used solid ring; hit targets are r=14 on the viewBox (≈45 CSS px at the rendered scale), clearing the 44px floor. Props, callbacks, the screen-left=="links" convention and the v1.8.5 site→enum bridge are unchanged — graphic and coordinates only. Co-Authored-By: Claude Opus 4.8 (1M context) (cherry picked from commit 46125c3bbec5573910f41a319ae717d37f39c228) --- .../__tests__/injection-site-picker.test.tsx | 7 +- .../medications/injection-site-picker.tsx | 235 +++++++++++------- src/lib/medications/injection-sites.ts | 44 ++-- 3 files changed, 174 insertions(+), 112 deletions(-) diff --git a/src/components/medications/__tests__/injection-site-picker.test.tsx b/src/components/medications/__tests__/injection-site-picker.test.tsx index edc2ee342..72e384667 100644 --- a/src/components/medications/__tests__/injection-site-picker.test.tsx +++ b/src/components/medications/__tests__/injection-site-picker.test.tsx @@ -247,13 +247,14 @@ describe("", () => { }); it("uses the SVG body outline as the picker surface", () => { - // Sanity check the body outline renders — head circle + torso - // path are the load-bearing visual anchors for the click targets. + // Sanity check the body outline renders — the silhouette path is the + // load-bearing visual anchor for the click targets, drawn on the + // 100x200 viewBox the iOS reference coords are calibrated against. const html = render( {}} />, ); - expect(html).toMatch(/]*viewBox="0 0 120 300"/); + expect(html).toMatch(/]*viewBox="0 0 100 200"/); expect(html).toContain('aria-label="Body outline"'); // The outer group has the localised "Once weekly on …" picker // label so the wrapper announces its role properly. diff --git a/src/components/medications/injection-site-picker.tsx b/src/components/medications/injection-site-picker.tsx index ad1210511..514f5f3f8 100644 --- a/src/components/medications/injection-site-picker.tsx +++ b/src/components/medications/injection-site-picker.tsx @@ -1,6 +1,6 @@ "use client"; -import { useState } from "react"; +import { useId, useState } from "react"; import { useTranslations } from "@/lib/i18n/context"; import { @@ -12,27 +12,38 @@ import { } from "@/lib/medications/injection-sites"; /** - * v1.4.25 W4d — injection-site picker. + * Injection-site picker — anatomical front-of-body silhouette with 8 + * tappable sites. * - * Stylized front-of-body SVG with 8 click-targets. The active site - * highlights, the recommended next-site is ringed with a dashed outline - * so the user can rotate sensibly without consulting a separate guide. + * The silhouette is an INLINE SVG so it inherits the app's theme tokens + * (`currentColor` + CSS variables) and tints correctly in light/dark and + * across the zinc/Dracula palettes with no raster asset and no luminance + * key. The body is traced to the proportions the iOS client calibrated + * against (see `.planning/ios-coord/v0.12-…-injection-bodymap-coords.md`) + * so dots land on the same anatomy on both platforms. * - * Per Marc's "nichts brickt" directive the picker is opt-in — it's only + * `viewBox="0 0 100 200"` (aspect ≈0.5). `SITE_COORDS` is expressed on + * the same viewBox; the recommended-next ring, the last-used marker and + * the selected fill all read directly from those coordinates. + * + * Per the long-standing "nichts brickt" directive the picker is opt-in — * mounted from the GLP-1 dashboard tile and the optional log dialog. - * Existing medication intake flows continue to work without it (the - * intake-event row's `injectionSite` field stays NULL). + * Existing intake flows keep working without it (the row's + * `injectionSite` stays NULL). * - * Mobile-first: the picker is centred in a 320px column and remains - * touch-friendly at Pixel-5 width. SVG buttons have 44px hit areas - * (the visible circle is smaller but the