Enforce the reminder nag cap, compare the disclaimer version, drop the unread analytics columns

- The daily Coach sweep now charges each ignored day against
  surfaceCount and auto-dismisses at three — the cap the schema comment
  promised since v1.22 but nothing enforced.
- The welcome gate compares the stored disclaimer acknowledgment
  version against the current one, so revised disclaimer text
  re-prompts; /api/auth/me carries the version for the client.
- slope90 and anomalyCount leave the analytics wire: slope90 had no
  reader anywhere (computed on every request, in SQL twice), and the
  slim path could never compute anomalyCount (it always reported 0).
  anomalyCount stays as an in-process field on summarize() for the
  insights feature builder, which is its only real consumer.
This commit is contained in:
Marc-André Bombeck committed 2026-08-14 05:19:05 +02:00
1 parent 6b3a5a4182
commit 3b1314d1b0
29 files changed
+214 -104

No files matched your search

+1 -2
View File
@@ -21,7 +21,7 @@ export interface RangeDisplayConfig {
* mean / median / avg7 / avg30 / avg30LastMonth / avg30LastYear) divides
* by 60; the slope tuples scale their `slope` (per-day rate) by the same
* factor so the trend arrow stays consistent; count / direction /
* confidence / anomalyCount are unit-free and pass through.
* confidence are unit-free and pass through.
*/
export function toHoursSummary(s: DataSummary): DataSummary {
const h = (v: number | null | undefined): number | null =>
@@ -43,7 +43,6 @@ export function toHoursSummary(s: DataSummary): DataSummary {
avg30LastYear: h(s.avg30LastYear),
slope7: scaleSlope(s.slope7),
slope30: scaleSlope(s.slope30),
slope90: scaleSlope(s.slope90),
};
}
@@ -64,7 +64,6 @@ function summary(over: Partial<DataSummary>): DataSummary {
avg30: null,
slope7: null,
slope30: null,
slope90: null,
anomalyCount: 0,
...over,
};
@@ -52,7 +52,7 @@ describe("<InsightsEditMode> — detail-page manager retired (v1.15.20)", () =>
expect(html).toContain(
'href="/settings/layout/insights#insights-pill-order"',
);
expect(html).toContain("Manage pills &amp; detail pages in Settings");
expect(html).toContain("Manage chips &amp; detail pages in Settings");
});
it("still renders the section rows with their eye toggles", () => {
@@ -25,7 +25,6 @@ function fakeSummary(count: number): DataSummary {
avg30: null,
slope7: null,
slope30: null,
slope90: null,
anomalyCount: 0,
};
}
@@ -36,7 +36,6 @@ function fakeSummary(count: number): DataSummary {
avg30: null,
slope7: null,
slope30: null,
slope90: null,
anomalyCount: 0,
};
}
@@ -36,7 +36,6 @@ const populated: DataSummary = {
avg30: 70,
slope7: null,
slope30: null,
slope90: null,
anomalyCount: 0,
avg30LastMonth: null,
avg30LastYear: null,
@@ -20,7 +20,6 @@ function summary(count: number): DataSummary {
avg30: null,
slope7: null,
slope30: null,
slope90: null,
anomalyCount: 0,
};
}
+16 -8
View File
@@ -24,7 +24,10 @@ import { cn } from "@/lib/utils";
import { apiPost } from "@/lib/api/api-fetch";
import { localizedApiError } from "@/lib/api/localized-error";
import { queryKeys } from "@/lib/query-keys";
import { DISCLAIMER_VERSION } from "@/lib/onboarding/disclaimer";
import {
DISCLAIMER_VERSION,
isDisclaimerAcknowledgmentCurrent,
} from "@/lib/onboarding/disclaimer";
/**
* v1.4.25 W14b-Content — onboarding welcome carousel.
@@ -87,11 +90,15 @@ export function WelcomeCarousel() {
const [active, setActive] = useState(0);
const [advancing, setAdvancing] = useState(false);
// v1.18.6 (DISC-02) — the one-time medical-disclaimer acknowledgment gates
// "Get started". Pre-checked for an account that already acknowledged (a
// re-walk of step 0) so a returning user is not re-asked.
const [acknowledged, setAcknowledged] = useState(
() => user?.disclaimerAcknowledgedAt != null,
// "Get started". Pre-checked only when the account acknowledged the
// CURRENT disclaimer version (a re-walk of step 0), so a returning user is
// not re-asked — but a revised disclaimer re-prompts instead of riding an
// acknowledgment given for older text.
const disclaimerCurrent = isDisclaimerAcknowledgmentCurrent(
user?.disclaimerAcknowledgedAt,
user?.disclaimerAcknowledgedVersion,
);
const [acknowledged, setAcknowledged] = useState(() => disclaimerCurrent);
// Track which slide is centred in the rail. We observe each slide
// with an IntersectionObserver — simpler and more reliable than
@@ -146,9 +153,10 @@ export function WelcomeCarousel() {
if (advancing || !acknowledged) return;
setAdvancing(true);
try {
// v1.18.6 (DISC-02) — record the one-time acknowledgment before
// advancing. Skipped when the account already acknowledged (re-walk).
if (user?.disclaimerAcknowledgedAt == null) {
// v1.18.6 (DISC-02) — record the acknowledgment before advancing.
// Skipped only when the account already acknowledged the CURRENT
// version (re-walk); an older-version acknowledgment re-records.
if (!disclaimerCurrent) {
await apiPost("/api/onboarding/disclaimer", {
version: DISCLAIMER_VERSION,
});