fix(insights): reconcile customizable-overview review findings

- Layout PUT accepts version 1 as well as 2 (live iOS client still PUTs
  version 1); the serializer normalises to the canonical v2 blob, so a v1
  body never 422s. Mirrored in the OpenAPI schema + description; yaml regen.
- Add the Weight tile to the inline tile editor so it can be hidden /
  reordered like every other Vitals grid tile.
- Replace the non-transitive Vitals tile-drag comparator with a pure,
  total-order rebuild helper (reorder the Vitals subset, substitute it back
  into the full list in original-order slots, densify).
- Gated-off section rows keep a disabled eye toggle in the same position
  with the gate hint as a caption below the title, so the row never jumps.
- Cap the disclosed Vitals tile list height (max-h-[60vh] scroll,
  overscroll-contain) so a tile drag does not fight the page scroll.
- Gate edit mode until the layout GET settles so a save can never flush
  defaults over a real saved layout.
- Drop the duplicate top-right "Done" affordance: hide the customize toggle
  while editing; the in-card Done / Reset own save and reset.
- Restore focus on enter (edit-card heading) and on close (customize toggle).

Tests: route regression for a version-1 PUT persisting a resolved v2 layout;
unit coverage for the new Vitals rebuild helper (order, non-Vitals stability,
per-tile visibility, no mutation).
This commit is contained in:
Marc-André Bombeck committed 2026-06-07 17:08:25 +02:00
1 parent 9b13b41f1a
commit 6ebac22c65
9 files changed
+331 -98

No files matched your search

+30 -20
View File
@@ -4555,8 +4555,11 @@ components:
type: object
properties:
version:
type: number
const: 2
anyOf:
- type: number
const: 1
- type: number
const: 2
sections:
maxItems: 50
type: array
@@ -4666,14 +4669,16 @@ components:
description: "Per-user Insights layout (v2). `tiles` is the per-metric pill list (ordered, with a visibility flag);
`sections` is the additive v2 list of the overview's big semantic blocks (wellness-scores, daily-briefing,
vitals, trends, period-review, cycle-summary, signals, rhythm-events), each with order + visibility. `version`
is the layout schema version (2). Both arrays are optional on input — a client sending only `tiles` (the pre-v2
contract) still validates, and the server fills missing defaults; a v1 blob with no `sections` resolves forward
with all sections default-visible. Tile ids are a closed enum: the canonical ids are English (matching the
routed `/insights/<slug>` sub-pages). The legacy German tile ids (blutdruck, puls, sauerstoff,
koerpertemperatur, gewicht, aktive-energie, schlaf, ruhepuls, stimmung, medikamente) remain accepted on input
for backward compatibility and are normalised to their English equivalents before persisting; GET responses
always carry the canonical English ids. Section ids are English-only. The legacy tile ids are deprecated and
will be removed in a future major version."
is the layout schema version: BOTH 1 and 2 are accepted on input (a pre-v2 iOS client still PUTs `version: 1`);
the server always normalises to the canonical v2 blob before persisting, and GET responses always carry
`version: 2`. Both arrays are optional on input — a client sending only `tiles` (the pre-v2 contract) still
validates, and the server fills missing defaults; a v1 blob with no `sections` resolves forward with all
sections default-visible. Tile ids are a closed enum: the canonical ids are English (matching the routed
`/insights/<slug>` sub-pages). The legacy German tile ids (blutdruck, puls, sauerstoff, koerpertemperatur,
gewicht, aktive-energie, schlaf, ruhepuls, stimmung, medikamente) remain accepted on input for backward
compatibility and are normalised to their English equivalents before persisting; GET responses always carry the
canonical English ids. Section ids are English-only. The legacy tile ids are deprecated and will be removed in a
future major version."
CoachMessageFeedbackRequest:
type: object
properties:
@@ -11474,8 +11479,11 @@ components:
type: object
properties:
version:
type: number
const: 2
anyOf:
- type: number
const: 1
- type: number
const: 2
sections:
maxItems: 50
type: array
@@ -11588,14 +11596,16 @@ components:
description: "Per-user Insights layout (v2). `tiles` is the per-metric pill list (ordered, with a visibility flag);
`sections` is the additive v2 list of the overview's big semantic blocks (wellness-scores, daily-briefing,
vitals, trends, period-review, cycle-summary, signals, rhythm-events), each with order + visibility. `version`
is the layout schema version (2). Both arrays are optional on input — a client sending only `tiles` (the pre-v2
contract) still validates, and the server fills missing defaults; a v1 blob with no `sections` resolves forward
with all sections default-visible. Tile ids are a closed enum: the canonical ids are English (matching the
routed `/insights/<slug>` sub-pages). The legacy German tile ids (blutdruck, puls, sauerstoff,
koerpertemperatur, gewicht, aktive-energie, schlaf, ruhepuls, stimmung, medikamente) remain accepted on input
for backward compatibility and are normalised to their English equivalents before persisting; GET responses
always carry the canonical English ids. Section ids are English-only. The legacy tile ids are deprecated and
will be removed in a future major version."
is the layout schema version: BOTH 1 and 2 are accepted on input (a pre-v2 iOS client still PUTs `version: 1`);
the server always normalises to the canonical v2 blob before persisting, and GET responses always carry
`version: 2`. Both arrays are optional on input — a client sending only `tiles` (the pre-v2 contract) still
validates, and the server fills missing defaults; a v1 blob with no `sections` resolves forward with all
sections default-visible. Tile ids are a closed enum: the canonical ids are English (matching the routed
`/insights/<slug>` sub-pages). The legacy German tile ids (blutdruck, puls, sauerstoff, koerpertemperatur,
gewicht, aktive-energie, schlaf, ruhepuls, stimmung, medikamente) remain accepted on input for backward
compatibility and are normalised to their English equivalents before persisting; GET responses always carry the
canonical English ids. Section ids are English-only. The legacy tile ids are deprecated and will be removed in a
future major version."
Medication:
type: object
properties: