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

+25 -2
View File
@@ -26,7 +26,24 @@ import {
* layout lands.
*/
export function useInsightsLayout(enabled: boolean): InsightsLayout {
const { data } = useQuery({
return useInsightsLayoutQuery(enabled).layout;
}
/**
* v1.15.11 QA L1 — same query as {@link useInsightsLayout} but also surfaces
* the load status so a caller can gate a write surface (the inline "Anpassen"
* edit mode) until the GET has settled. Without this gate a user who enters
* edit mode while the layout is still in-flight would seed the editor from
* `DEFAULT_INSIGHTS_LAYOUT` and a "Fertig" save would PUT defaults over their
* real saved layout. Shares the query key, so mounting both hooks costs one
* request (TanStack dedupes).
*/
export function useInsightsLayoutQuery(enabled: boolean): {
layout: InsightsLayout;
isLoading: boolean;
isSuccess: boolean;
} {
const { data, isLoading, isSuccess } = useQuery({
queryKey: queryKeys.insightsLayout(),
queryFn: async () => {
const res = await fetch("/api/insights/layout");
@@ -35,5 +52,11 @@ export function useInsightsLayout(enabled: boolean): InsightsLayout {
},
enabled,
});
return data ?? DEFAULT_INSIGHTS_LAYOUT;
return {
layout: data ?? DEFAULT_INSIGHTS_LAYOUT,
// `enabled: false` keeps the query in a pending-but-idle state; treat a
// disabled (unauthenticated) query as not-loading so the gate never sticks.
isLoading: enabled && isLoading,
isSuccess,
};
}