mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-12 06:06:10 +00:00
Surface shipped data sources in onboarding and tell the truth
Rewrite the source step to list the live integrations (Apple Health, WHOOP, Oura, Polar, Nightscout, Fitbit) alongside Withings and manual entry, each linking to where it is connected; drop the stale Apple Health coming-soon badge. Add an import affordance for existing history. Reword the goals and source copy so it no longer promises automatic dashboard or reminder configuration that does not happen yet. Add a calibration line to the done screen setting the expectation that some insights sharpen over the first weeks, and lead the success screen forward with connect-a-device and log-first-measurement actions. Generalize the dashboard checklist data-source item so any connected integration satisfies it, not Withings alone.
This commit is contained in:
1 parent
46f9dfa47f
commit
1ddc8a0c47
11 files changed
+295
-168
No files matched your search
@@ -21,7 +21,7 @@ function inputs(overrides: Partial<Parameters<typeof buildChecklist>[0]> = {}) {
|
||||
profile: completeProfile,
|
||||
measurementCount: 0,
|
||||
medicationCount: 0,
|
||||
withingsConnected: false,
|
||||
dataSourceConnected: false,
|
||||
notificationsConfigured: false,
|
||||
dismissedIds: new Set<ChecklistItemId>(),
|
||||
...overrides,
|
||||
@@ -49,7 +49,7 @@ describe("buildChecklist", () => {
|
||||
"profile",
|
||||
"measurement",
|
||||
"medication",
|
||||
"withings",
|
||||
"dataSource",
|
||||
"notifications",
|
||||
]);
|
||||
});
|
||||
@@ -64,12 +64,21 @@ describe("buildChecklist", () => {
|
||||
expect(items.find((i) => i.id === "measurement")?.done).toBe(true);
|
||||
});
|
||||
|
||||
it("flags medication and withings independently", () => {
|
||||
it("flags medication and dataSource independently", () => {
|
||||
const items = buildChecklist(
|
||||
inputs({ medicationCount: 2, withingsConnected: true }),
|
||||
inputs({ medicationCount: 2, dataSourceConnected: true }),
|
||||
);
|
||||
expect(items.find((i) => i.id === "medication")?.done).toBe(true);
|
||||
expect(items.find((i) => i.id === "withings")?.done).toBe(true);
|
||||
expect(items.find((i) => i.id === "dataSource")?.done).toBe(true);
|
||||
});
|
||||
|
||||
it("dataSource is satisfied by any connected source", () => {
|
||||
// The predicate is source-agnostic — a single boolean stands in for
|
||||
// Withings, WHOOP, Oura, Polar, Nightscout, Fitbit or Apple Health.
|
||||
const off = buildChecklist(inputs({ dataSourceConnected: false }));
|
||||
expect(off.find((i) => i.id === "dataSource")?.done).toBe(false);
|
||||
const on = buildChecklist(inputs({ dataSourceConnected: true }));
|
||||
expect(on.find((i) => i.id === "dataSource")?.done).toBe(true);
|
||||
});
|
||||
|
||||
it("propagates per-item dismissal", () => {
|
||||
@@ -86,7 +95,7 @@ describe("buildChecklist", () => {
|
||||
expect(hrefs.profile).toBe("/settings/account");
|
||||
expect(hrefs.measurement).toBe("/measurements");
|
||||
expect(hrefs.medication).toBe("/medications");
|
||||
expect(hrefs.withings).toBe("/settings/integrations");
|
||||
expect(hrefs.dataSource).toBe("/settings/integrations");
|
||||
expect(hrefs.notifications).toBe("/settings/notifications");
|
||||
});
|
||||
});
|
||||
@@ -94,10 +103,10 @@ describe("buildChecklist", () => {
|
||||
describe("visibleChecklist + checklistProgress", () => {
|
||||
it("hides per-item dismissed rows from the visible list", () => {
|
||||
const items = buildChecklist(
|
||||
inputs({ dismissedIds: new Set<ChecklistItemId>(["withings"]) }),
|
||||
inputs({ dismissedIds: new Set<ChecklistItemId>(["dataSource"]) }),
|
||||
);
|
||||
const visible = visibleChecklist(items);
|
||||
expect(visible.map((i) => i.id)).not.toContain("withings");
|
||||
expect(visible.map((i) => i.id)).not.toContain("dataSource");
|
||||
});
|
||||
|
||||
it("counts done items inside the visible subset", () => {
|
||||
@@ -106,7 +115,7 @@ describe("visibleChecklist + checklistProgress", () => {
|
||||
measurementCount: 3,
|
||||
medicationCount: 1,
|
||||
// dismiss the only one not done so percent jumps to 100
|
||||
dismissedIds: new Set<ChecklistItemId>(["withings", "notifications"]),
|
||||
dismissedIds: new Set<ChecklistItemId>(["dataSource", "notifications"]),
|
||||
}),
|
||||
);
|
||||
const progress = checklistProgress(items);
|
||||
@@ -121,7 +130,7 @@ describe("visibleChecklist + checklistProgress", () => {
|
||||
profile: { heightCm: null, dateOfBirth: null, gender: null },
|
||||
measurementCount: 0,
|
||||
medicationCount: 0,
|
||||
withingsConnected: false,
|
||||
dataSourceConnected: false,
|
||||
notificationsConfigured: false,
|
||||
dismissedIds: new Set(),
|
||||
});
|
||||
@@ -185,7 +194,7 @@ describe("shouldShowChecklist", () => {
|
||||
inputs({
|
||||
measurementCount: 3,
|
||||
medicationCount: 1,
|
||||
withingsConnected: true,
|
||||
dataSourceConnected: true,
|
||||
notificationsConfigured: true,
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -10,7 +10,7 @@ export const CHECKLIST_ITEM_IDS = [
|
||||
"profile",
|
||||
"measurement",
|
||||
"medication",
|
||||
"withings",
|
||||
"dataSource",
|
||||
"notifications",
|
||||
] as const;
|
||||
|
||||
@@ -41,8 +41,12 @@ export interface ChecklistInputs {
|
||||
measurementCount: number;
|
||||
/** Number of medications the user has created. */
|
||||
medicationCount: number;
|
||||
/** True iff Withings OAuth is connected (status.connected). */
|
||||
withingsConnected: boolean;
|
||||
/**
|
||||
* True iff at least one data source is connected — Withings, WHOOP,
|
||||
* Oura, Polar, Nightscout, Fitbit, or Apple Health. Any one satisfies
|
||||
* the "connect a data source" step.
|
||||
*/
|
||||
dataSourceConnected: boolean;
|
||||
/**
|
||||
* True iff the user has set up at least one notification channel
|
||||
* (Telegram, ntfy, or Web Push).
|
||||
@@ -54,7 +58,7 @@ export interface ChecklistInputs {
|
||||
|
||||
/**
|
||||
* Compute the ordered checklist for the dashboard hero. Stable order:
|
||||
* profile → measurement → medication → withings → notifications. Each
|
||||
* profile → measurement → medication → dataSource → notifications. Each
|
||||
* item carries the deep-link the row's CTA should navigate to.
|
||||
*/
|
||||
export function buildChecklist(inputs: ChecklistInputs): ChecklistItem[] {
|
||||
@@ -79,10 +83,10 @@ export function buildChecklist(inputs: ChecklistInputs): ChecklistItem[] {
|
||||
dismissed: inputs.dismissedIds.has("medication"),
|
||||
},
|
||||
{
|
||||
id: "withings",
|
||||
done: inputs.withingsConnected,
|
||||
id: "dataSource",
|
||||
done: inputs.dataSourceConnected,
|
||||
href: "/settings/integrations",
|
||||
dismissed: inputs.dismissedIds.has("withings"),
|
||||
dismissed: inputs.dismissedIds.has("dataSource"),
|
||||
},
|
||||
{
|
||||
id: "notifications",
|
||||
|
||||
Reference in new issue
Block a user