-
Trend Weight
+
+ {latest?.smoothedWeightStatus.label} Trend Weight
+
{latest ? formatMeasurementText(units.formatWeight(latest.smoothedWeight)) : "—"}
- {latestScaleWeight != null && (
+ {latestScaleWeight != null && latestScaleWeightStatus != null && (
- Scale: {formatMeasurementText(units.formatWeight(latestScaleWeight))}
+ {latestScaleWeightStatus}:{" "}
+ {formatMeasurementText(units.formatWeight(latestScaleWeight))}
)}
diff --git a/packages/web/src/components/TodayPlanCard.stories.tsx b/packages/web/src/components/TodayPlanCard.stories.tsx
index 5d430d25f3..40d100527e 100644
--- a/packages/web/src/components/TodayPlanCard.stories.tsx
+++ b/packages/web/src/components/TodayPlanCard.stories.tsx
@@ -4,6 +4,7 @@ import { TodayPlanCard } from "./TodayPlanCard.tsx";
const readyPlan: TodayPlanResult = {
status: "ready",
+ epistemicStatus: { kind: "suggested", label: "Suggested" },
date: "2026-07-26",
action: {
id: "strain_target",
@@ -26,6 +27,7 @@ const readyPlan: TodayPlanResult = {
const insufficientPlan: TodayPlanResult = {
status: "insufficient_data",
+ epistemicStatus: { kind: "unavailable", label: "Unavailable" },
date: "2026-07-26",
action: null,
supportingFacts: [],
diff --git a/packages/web/src/components/TodayPlanCard.test.tsx b/packages/web/src/components/TodayPlanCard.test.tsx
index e3ceeb1075..d8268c31c8 100644
--- a/packages/web/src/components/TodayPlanCard.test.tsx
+++ b/packages/web/src/components/TodayPlanCard.test.tsx
@@ -7,6 +7,7 @@ import { TodayPlanCard } from "./TodayPlanCard.tsx";
const readyPlan: TodayPlanResult = {
status: "ready",
+ epistemicStatus: { kind: "suggested", label: "Suggested" },
date: "2026-07-26",
action: {
id: "strain_target",
@@ -29,6 +30,7 @@ const readyPlan: TodayPlanResult = {
const insufficientPlan: TodayPlanResult = {
status: "insufficient_data",
+ epistemicStatus: { kind: "unavailable", label: "Unavailable" },
date: "2026-07-26",
action: null,
supportingFacts: [],
@@ -62,6 +64,7 @@ describe("TodayPlanCard", () => {
expect(screen.getByText("Sleep performance")).toBeTruthy();
expect(screen.getByText("88 (Good)")).toBeTruthy();
expect(screen.getByText("High confidence")).toBeTruthy();
+ expect(screen.getByText("Suggested")).toBeTruthy();
expect(screen.getByText(/Recovery data from 2026-07-26/)).toBeTruthy();
});
@@ -121,6 +124,7 @@ describe("TodayPlanCard", () => {
),
).toBeTruthy();
expect(screen.queryByText("Train hard today — aim for 16.2 strain")).toBeNull();
+ expect(screen.getByText("Unavailable")).toBeTruthy();
});
it("renders a loading state", () => {
diff --git a/packages/web/src/components/TodayPlanCard.tsx b/packages/web/src/components/TodayPlanCard.tsx
index 1615a46238..08e7b3a37f 100644
--- a/packages/web/src/components/TodayPlanCard.tsx
+++ b/packages/web/src/components/TodayPlanCard.tsx
@@ -52,6 +52,7 @@ export function TodayPlanCard({ plan, loading = false, error }: TodayPlanCardPro
{refreshWarning}
{plan.message}
+
{plan.epistemicStatus.label}
{formatTodayPlanConfidence(plan.confidence)}
);
@@ -107,6 +108,7 @@ export function TodayPlanCard({ plan, loading = false, error }: TodayPlanCardPro
) : null}
+
{plan.epistemicStatus.label}
{formatTodayPlanConfidence(plan.confidence)}
{freshness != null ?
{freshness}
: null}
diff --git a/packages/web/src/pages/BodyPage.test.tsx b/packages/web/src/pages/BodyPage.test.tsx
index 30174f1d9c..d94d7f9342 100644
--- a/packages/web/src/pages/BodyPage.test.tsx
+++ b/packages/web/src/pages/BodyPage.test.tsx
@@ -112,7 +112,9 @@ const healthyWeightOverview = {
{
date: "2026-07-25",
rawWeight: 80,
+ rawWeightStatus: { kind: "observed", label: "Observed" },
smoothedWeight: 80,
+ smoothedWeightStatus: { kind: "estimated", label: "Estimated" },
weeklyChange: null,
interpolated: false,
},
diff --git a/packages/web/src/pages/CorrelationExplorerPage.stories.tsx b/packages/web/src/pages/CorrelationExplorerPage.stories.tsx
index 8221b520e8..3a644b65ed 100644
--- a/packages/web/src/pages/CorrelationExplorerPage.stories.tsx
+++ b/packages/web/src/pages/CorrelationExplorerPage.stories.tsx
@@ -39,6 +39,7 @@ const metrics = [
const availableResult = {
analysisVersion: 2,
availability: "available",
+ epistemicStatus: { kind: "associated", label: "Associated" },
spearmanRho: 0.72,
regression: { slope: 0.42, intercept: 8, rSquared: 0.46 },
dataPoints: [
@@ -77,6 +78,7 @@ const availableResult = {
const insufficientResult = {
analysisVersion: 2,
availability: "insufficient",
+ epistemicStatus: { kind: "unavailable", label: "Unavailable" },
dataPoints: [],
sampleCount: 0,
additionalSamplesRequired: 5,
diff --git a/packages/web/src/pages/CorrelationExplorerPage.test.tsx b/packages/web/src/pages/CorrelationExplorerPage.test.tsx
index bec531234d..f4bdbcbe9d 100644
--- a/packages/web/src/pages/CorrelationExplorerPage.test.tsx
+++ b/packages/web/src/pages/CorrelationExplorerPage.test.tsx
@@ -134,6 +134,7 @@ describe("CorrelationExplorerPage", () => {
state.correlationData = {
analysisVersion: 2,
availability: "insufficient",
+ epistemicStatus: { kind: "unavailable", label: "Unavailable" },
dataPoints: [],
sampleCount: 0,
additionalSamplesRequired: 5,
@@ -230,6 +231,7 @@ describe("CorrelationExplorerPage", () => {
render(
);
expect(screen.getByText("n = 0")).toBeTruthy();
+ expect(screen.getByText("Unavailable")).toBeTruthy();
expect(screen.getByText("5 more paired calendar days needed")).toBeTruthy();
expect(screen.getByText("90 selected")).toBeTruthy();
expect(screen.getByText("90 missing pairs")).toBeTruthy();
@@ -261,6 +263,7 @@ describe("CorrelationExplorerPage", () => {
state.correlationData = {
analysisVersion: 2,
availability: "available",
+ epistemicStatus: { kind: "associated", label: "Associated" },
spearmanRho: 0.75,
regression: { slope: 1, intercept: 0, rSquared: 0.49 },
dataPoints: [],
@@ -293,6 +296,7 @@ describe("CorrelationExplorerPage", () => {
render(
);
expect(screen.getByText("Spearman rho = 0.75")).toBeTruthy();
+ expect(screen.getByText("Associated")).toBeTruthy();
expect(screen.getByText("95% block-bootstrap interval: 0.42 to 0.86")).toBeTruthy();
expect(screen.getByText("Slope = 1.000 ms per g")).toBeTruthy();
expect(screen.getByText("R² = 0.490")).toBeTruthy();
@@ -307,6 +311,7 @@ describe("CorrelationExplorerPage", () => {
state.correlationData = {
analysisVersion: 2,
availability: "available",
+ epistemicStatus: { kind: "associated", label: "Associated" },
spearmanRho: -0.75,
regression: { slope: -1, intercept: 3, rSquared: 0.49 },
dataPoints: [
@@ -351,6 +356,7 @@ describe("CorrelationExplorerPage", () => {
state.correlationData = {
analysisVersion: 2,
availability: "available",
+ epistemicStatus: { kind: "associated", label: "Associated" },
spearmanRho: 0.75,
regression: { slope: 1, intercept: 0, rSquared: 0.49 },
dataPoints: [
diff --git a/packages/web/src/pages/CorrelationExplorerPage.tsx b/packages/web/src/pages/CorrelationExplorerPage.tsx
index 45585868d0..2a721207e8 100644
--- a/packages/web/src/pages/CorrelationExplorerPage.tsx
+++ b/packages/web/src/pages/CorrelationExplorerPage.tsx
@@ -492,6 +492,7 @@ export function CorrelationExplorerPage() {
Correlation Evidence
+
{data.epistemicStatus.label}
{data.availability === "available" ? (
<>
diff --git a/packages/web/src/pages/Dashboard.test.tsx b/packages/web/src/pages/Dashboard.test.tsx
index d636a3e8f3..3f524dd87d 100644
--- a/packages/web/src/pages/Dashboard.test.tsx
+++ b/packages/web/src/pages/Dashboard.test.tsx
@@ -222,6 +222,7 @@ describe("Dashboard", () => {
mockTodayPlanQuery.mockReturnValue({
data: {
status: "ready",
+ epistemicStatus: { kind: "suggested", label: "Suggested" },
date: "2026-05-27",
action: {
id: "strain_target",