mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-15 06:52:04 +00:00
fix(onboarding): checklist queries follow its own setup rule
The card's visibility rule gained a "ran the setup flow" disjunct; the component's query gate kept restating the older half of it. A wizard-run account past five readings therefore saw a permanent checklist whose medication, data-source, notification and insight rows were frozen at "not done", because every supporting query was disabled. Both places now read one exported predicate, isStillInSetup, which also keeps the rows live for a restarted run nobody finished.
This commit is contained in:
1 parent
274923a060
commit
dec5732687
4 files changed
+265
-18
No files matched your search
@@ -9,6 +9,7 @@ import {
|
||||
CHECKLIST_BASE_ITEM_IDS,
|
||||
CHECKLIST_ITEM_IDS,
|
||||
isProfileComplete,
|
||||
isStillInSetup,
|
||||
shouldShowChecklist,
|
||||
trendHintFor,
|
||||
upcomingVisitCountFrom,
|
||||
@@ -608,3 +609,75 @@ describe("shouldShowChecklist and the setup flow", () => {
|
||||
).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("isStillInSetup", () => {
|
||||
// The one predicate both the visibility rule and the component's query gate
|
||||
// read, so the card can never render with its supporting queries switched
|
||||
// off (research I9).
|
||||
it("is true for a record that ran the flow, whatever its reading count", () => {
|
||||
expect(
|
||||
isStillInSetup({
|
||||
onboarding: onboardingState(),
|
||||
onboardingCompletedAt: "2026-01-01T00:00:00Z",
|
||||
measurementCount: 120,
|
||||
}),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("is true for a restarted run nobody finished (M-p)", () => {
|
||||
const restarted = onboardingState({ completedAt: null });
|
||||
expect(
|
||||
isStillInSetup({
|
||||
onboarding: restarted,
|
||||
// `restart` deliberately leaves the account stamp alone.
|
||||
onboardingCompletedAt: "2026-01-01T00:00:00Z",
|
||||
measurementCount: 120,
|
||||
}),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("keeps the pre-flow rule for a record that never entered the flow", () => {
|
||||
const untouched: OnboardingStateDto = {
|
||||
steps: defaultOnboardingSteps(),
|
||||
needs: emptyOnboardingNeeds(),
|
||||
completedAt: null,
|
||||
firstResult: null,
|
||||
};
|
||||
expect(
|
||||
isStillInSetup({
|
||||
onboarding: untouched,
|
||||
onboardingCompletedAt: "2026-01-01T00:00:00Z",
|
||||
measurementCount: 10,
|
||||
}),
|
||||
).toBe(false);
|
||||
expect(
|
||||
isStillInSetup({
|
||||
onboarding: untouched,
|
||||
onboardingCompletedAt: "2026-01-01T00:00:00Z",
|
||||
measurementCount: 4,
|
||||
}),
|
||||
).toBe(true);
|
||||
expect(
|
||||
isStillInSetup({
|
||||
onboarding: null,
|
||||
onboardingCompletedAt: null,
|
||||
measurementCount: 10,
|
||||
}),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("is the rule `shouldShowChecklist` itself applies", () => {
|
||||
const args = {
|
||||
onboarding: onboardingState(),
|
||||
onboardingCompletedAt: "2026-01-01T00:00:00Z",
|
||||
measurementCount: 120,
|
||||
};
|
||||
expect(
|
||||
shouldShowChecklist({
|
||||
...args,
|
||||
dismissedAll: false,
|
||||
items: buildChecklist(inputs({ measurementCount: 120 })),
|
||||
}),
|
||||
).toBe(isStillInSetup(args));
|
||||
});
|
||||
});
|
||||
@@ -282,11 +282,7 @@ export function checklistProgress(items: ChecklistItem[]): ChecklistProgress {
|
||||
* unfinished, because the one task the flow offered is not one of the six
|
||||
* rows: "every row done" and "the setup finished" are different statements.
|
||||
*/
|
||||
export function shouldShowChecklist(args: {
|
||||
onboardingCompletedAt: string | null;
|
||||
measurementCount: number;
|
||||
dismissedAll: boolean;
|
||||
items: ChecklistItem[];
|
||||
export interface StillInSetupInputs {
|
||||
/**
|
||||
* v1.39 (C1) — the needs-based setup state for this record, or null before
|
||||
* the account payload resolves. A record that never entered the flow reads
|
||||
@@ -295,17 +291,45 @@ export function shouldShowChecklist(args: {
|
||||
* on the rule it already had.
|
||||
*/
|
||||
onboarding?: OnboardingStateDto | null;
|
||||
}): boolean {
|
||||
onboardingCompletedAt: string | null;
|
||||
measurementCount: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* v1.39 (Wave C, C1) — "is this record still being set up?", as ONE predicate.
|
||||
*
|
||||
* Two places decide this: whether the card renders at all, and whether the
|
||||
* component fetches the data its rows are about. They were two restatements of
|
||||
* the same rule and they drifted — the visibility rule gained
|
||||
* `hasEnteredOnboardingFlow`, the query gate kept the older half, and a
|
||||
* wizard-run account past five readings got a permanently visible card whose
|
||||
* every network-backed row read "not done" (research I9). A record whose
|
||||
* restarted run was abandoned is in the same state (M-p): `restart` clears the
|
||||
* record's own `completedAt` and deliberately not the account stamp, so
|
||||
* `hasEnteredOnboardingFlow` is what keeps its rows live.
|
||||
*
|
||||
* Exported so the component calls it rather than restating it.
|
||||
*/
|
||||
export function isStillInSetup(args: StillInSetupInputs): boolean {
|
||||
return (
|
||||
hasEnteredOnboardingFlow(args.onboarding ?? null) ||
|
||||
args.onboardingCompletedAt == null ||
|
||||
args.measurementCount < 5
|
||||
);
|
||||
}
|
||||
|
||||
export function shouldShowChecklist(
|
||||
args: StillInSetupInputs & {
|
||||
dismissedAll: boolean;
|
||||
items: ChecklistItem[];
|
||||
},
|
||||
): boolean {
|
||||
if (args.dismissedAll) return false;
|
||||
const onboarding = args.onboarding ?? null;
|
||||
const visible = visibleChecklist(args.items);
|
||||
if (visible.length === 0) return false;
|
||||
|
||||
const stillInSetup =
|
||||
hasEnteredOnboardingFlow(onboarding) ||
|
||||
args.onboardingCompletedAt == null ||
|
||||
args.measurementCount < 5;
|
||||
if (!stillInSetup) return false;
|
||||
if (!isStillInSetup(args)) return false;
|
||||
|
||||
if (!isOnboardingSettled(onboarding)) return true;
|
||||
return visible.some((item) => !item.done);
|
||||
|
||||
Reference in new issue
Block a user