Files
HealthLog/src/hooks/use-module-enabled.ts
T
Marc-André BombeckandClaude Opus 4.8 3de53f549c fix(health-score): drop the mood pillar when the mood module is disabled
The Personal Health Score weighted the 0.20 mood-stability pillar
unconditionally — the fast-path read mood entries regardless of the
account's module state, so a user who turned the mood module off was
still scored (and silently penalised or rewarded) by a pillar hidden
everywhere else in the product. The dashboard already blanks the mood
tile and the coach snapshot unions mood out; the score ring and the
insights card pillar list did not.

Thread a default-on `moodEnabled` flag through the fast-path. When the
caller resolves the mood module as disabled the helper skips the mood
read entirely and leaves the series empty, so `moodStability` returns
null and the existing proportional null-redistribution re-scales the
surviving pillars to sum to 100. The analytics route resolves the
module server-side and threads the flag; the insights card hides the
Mood row via a new SSR-safe `useModuleEnabled` accessor so it never
advertises a pillar the account turned off.

Golden fixtures pin mood-on vs mood-off to different, correctly
re-weighted scores and assert the disabled read is never issued.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 00:39:55 +02:00

34 lines
1.3 KiB
TypeScript

"use client";
import { useAuth } from "@/hooks/use-auth";
import { useQueryClientMounted } from "@/hooks/_internal/use-query-client-safe";
import type { ModuleKey } from "@/lib/modules/registry";
/**
* v1.18.0 R4 — SSR-safe accessor for a per-account module flag.
*
* Mirrors `useDisableCoach()`: `useAuth()` calls `useQuery` directly and
* crashes when a legacy presentational component (`<HeroStrip>`, the
* isolated score-card snapshots, …) renders without a
* `<QueryClientProvider>`. We gate the inner hook on
* `useQueryClientMounted()` and short-circuit to the default-on result
* when no client is in the tree — the branch is stable across the
* component's lifetime, so the conditional call is safe per the Rules of
* Hooks.
*
* Default-on (disabled allowlist): an absent map, a missing key, or any
* non-`false` value reads as enabled — matching the server gate in
* `src/lib/modules/gate.ts`.
*/
export function useModuleEnabled(moduleKey: ModuleKey): boolean {
const hasClient = useQueryClientMounted();
if (!hasClient) return true;
// eslint-disable-next-line react-hooks/rules-of-hooks
return useModuleEnabledInner(moduleKey);
}
function useModuleEnabledInner(moduleKey: ModuleKey): boolean {
const { user } = useAuth();
return user?.modules?.[moduleKey] !== false;
}