Files
HealthLog/src/lib/insights/trend-sentiment.ts
T
Marc-André Bombeck 4ace1b7706 refactor(insights): unify the metric subpage template — target tile, order, stat strip, drop the range row
Make every metric subpage identical by construction in the shared
scaffold rather than per-page bespoke.

- Target tile matches the other cards: dark `bg-card` background and the
  `rounded-xl border p-4` spacing the assessment and stat-strip cards
  use, instead of the old translucent `bg-card/20` panel.
- Drop the 7T/30T/90T/1J range row and its "+X% vs previous" delta from
  every subpage. The chart owns its own window via its in-card range
  tabs, so the chart query is unaffected. Retire the now-dead
  `MetricRangeControls`, `MetricRangeDelta`, `TimeRangePills`,
  `useAnalyticsRange`, the `useInsightsRangePref` persisted-range hook,
  and `sentimentFromMetricDirection` along with their tests.
- Fix the spine order to intro -> chart -> target card -> assessment ->
  Min/Max/Median/Mittelwert stat strip. The stat strip moves to the foot
  of the spine, below the assessment. Remove the "Letzte Messung" card
  and the headline primary tile from subpages; retire
  `MetricLastMeasurementCard` and `MetricPrimaryTile`.
- Blood pressure gains the stat strip it was missing: two stacked
  systolic / diastolic strips, since a single-series strip would drop the
  diastolic half. `MetricStatStrip` takes an optional series caption.

Update the spine guard to assert the target card precedes the
assessment and that the retired range row and last-measurement card
appear on no subpage.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
(cherry picked from commit 80b2a810314b4c0951c0d68c04bffc89003005d6)
2026-06-05 10:42:47 +02:00

56 lines
2.3 KiB
TypeScript

/**
* v1.9.0 — shared metric-aware trend sentiment helpers.
*
* Extracted from `trend-card.tsx` so the range-delta caption (the time-range
* pills feature) paints the exact same colour for the same signal as the
* dashboard tiles. The mapping is the v1.5 phase-5 audit's: an upward move is
* good for some metrics (mood, HRV, steps), bad for others (resting HR,
* weight, body fat), and value-judgement-free for the rest.
*/
/**
* Maps a metric's "up means" direction to colour sentiment.
*
* - `up-good` — higher value is better (mood, HRV, steps). ↑ green, ↓ orange.
* - `up-bad` — higher value is worse (resting HR, weight, body fat).
* ↑ orange, ↓ green.
* - `neutral` — direction carries no value judgement (the change renders
* muted).
*/
export type TrendDirectionSentiment = "up-good" | "up-bad" | "neutral";
/**
* The resolved sentiment of a signed change under a metric's direction.
*
* - `positive` — the change moves the metric toward its goal. Renders green.
* - `negative` — the change moves away from the goal. Renders orange.
* - `neutral` — the metric's direction is neutral, or the change is below
* the noise floor (|change| < 0.05). Renders muted.
*/
export type TrendSentimentDirection = "positive" | "negative" | "neutral";
export function getTrendSentiment(
change: number | null | undefined,
sentiment: TrendDirectionSentiment,
): TrendSentimentDirection {
if (change == null || Math.abs(change) < 0.05) return "neutral";
if (sentiment === "neutral") return "neutral";
const isUp = change > 0;
const isGood =
(sentiment === "up-good" && isUp) || (sentiment === "up-bad" && !isUp);
return isGood ? "positive" : "negative";
}
export function sentimentColorClass(
direction: TrendSentimentDirection,
): string {
// Semantic tokens, not raw --dracula-*: `--success` / `--warning` alias
// the same Dracula green/orange in dark mode (pixel-identical there) but
// carry the AA-safe `:root.light` overrides, so the trend arrow + delta
// clear AA on the white card instead of the ~1.2:1 the raw primitives hit.
if (direction === "positive") return "text-success";
if (direction === "negative") return "text-warning";
return "text-muted-foreground";
}