Files
HealthLog/src/lib/query-keys/mood.ts
T
Marc-André Bombeck f48afed82d feat(mood): show the day's own rating beside what the account's days imply
Two readings, side by side, never merged. The person's own rating leads
— larger, first, and returned exactly as it was stored — and the
comparison sits beside it with its range and the number of days it was
built from. The deviation is measured against the range, not against its
midpoint: inside it is a day that went as the account's own past days
suggest, and only outside it is worth a sentence.

The sentence is a counterfactual and says so. Never "your mood is 5.4",
always "a value around 5.4 would have been expected". The explanation
names what the comparison weighted for that day, out of what the job
stored for that day, in the words the capture sheet already uses — a
person reads "Workload" here because that is what the slider they moved
was called.

The route declares the whole record rather than the mind section, and
the reason is written at the declaration: the comparison reads the day's
context AND the figures the sleep, activity and recovery modules hold,
and it names them, so a delegate holding only the mind section would
receive a derived view of sections the owner declined to share. A scoped
grant is refused outright rather than served a contribution list with
the linked features quietly removed, which would read as "only the mood
fields mattered" and be false.

The causal-verb ban is a test now rather than a review habit: the copy
guard reads all six bundles and fails on a causal construction in any of
them, and on a statement that quotes a value without the days behind it.
Both halves were watched failing.
2026-08-08 11:43:58 +02:00

107 lines
4.3 KiB
TypeScript

/**
* Query keys — mood entries, mood analytics, and the tag catalog.
* Part of the centralized factory; aggregated in `./index.ts`.
*/
export const moodKeys = {
moodEntries: () => ["mood-entries"] as const,
/**
* v1.15.13 — the mood management-list read with its filter + sort +
* pagination state baked into the key, mirroring `measurementsList`.
* Rides under the `["mood-entries"]` prefix so `moodDependentKeys`
* (and a bulk-delete invalidation) reaches every slot.
*/
moodEntriesList: (params: {
mood: string | undefined;
source: string | undefined;
from: string | undefined;
to: string | undefined;
page: number;
sortBy: string;
sortDir: string;
}) =>
[
"mood-entries",
"list",
params.mood ?? null,
params.source ?? null,
params.from ?? null,
params.to ?? null,
params.page,
params.sortBy,
params.sortDir,
] as const,
/**
* v1.34.3 — every mood entry logged on ONE local day, read by the
* workout detail page's day context. Rides under the `["mood-entries"]`
* prefix so `moodDependentKeys` reaches it on a mood write, and carries
* its own `"day"` scope segment so it can never share a slot with
* `moodEntriesList` — same key with a different response shape silently
* poisons the cache.
*/
moodEntriesDay: (date: string) => ["mood-entries", "day", date] as const,
/**
* v1.38 — what the sleep, activity, vitals and illness modules already know
* about one local day, read by the mood capture sheet and the entry detail.
*
* Keyed by the day AND the zone: the same calendar date read under two
* timezones covers two different windows, and one key for both would serve
* whichever answer arrived first.
*
* Its own prefix rather than a segment under `["mood-entries"]`, and
* deliberately: nothing here comes from a mood row, so a mood write has no
* reason to invalidate it, and putting it under that prefix would refetch
* four modules' figures every time somebody tapped a face.
*/
moodLinkedContext: (date: string, tz: string) =>
["mood-linked-context", date, tz] as const,
/**
* v1.38 — the day's two readings: the person's own rating and what their
* past days imply, with the band and the case count.
*
* Under the `["mood-entries"]` prefix so `moodDependentKeys` reaches it: the
* self-assessment half of the payload comes straight off a mood row, and an
* edit to today's entry changes the deviation the card shows. The forecast
* half does not move until the nightly job runs again, which is why the read
* is cheap enough to refetch on a mood write.
*/
moodPrognosis: () => ["mood-entries", "prognosis"] as const,
moodAnalytics: () => ["mood-analytics"] as const,
/**
* v1.8.5 — pre-computed mood-insights aggregates (heatmap, distribution,
* weekday, tag breakdown, cross-metric correlations) for the Mood
* Insights page. Read-only; invalidated on a mood write through the
* `moodDependentKeys` fan-out in `./index.ts`.
*/
moodInsights: () => ["mood-insights"] as const,
/**
* v1.8.5 — structured mood-tag taxonomy catalog (global reference
* data, identical for every user). Read by the mood-logging form's
* tag-category capture surface. Not invalidated on a mood write — the
* catalog only changes on a migration / admin edit, so a long
* `staleTime` is fine.
*/
moodTagCatalog: () => ["mood-tag-catalog"] as const,
/**
* v1.17 — the tag-MANAGEMENT read (`/api/mood/tags?include=
* hidden,archived,usage`). Rides under the `["mood-tag-catalog"]`
* prefix so one `invalidateQueries({ queryKey: ["mood-tag-catalog"] })`
* after any management mutation refreshes both this read and the
* picker read above (same trick as `moodEntriesList` under
* `["mood-entries"]`). The scope segment keeps the two queryFn shapes
* on different keys — same-key/different-shape silently poisons the
* cache.
*/
moodTagManage: () => ["mood-tag-catalog", "manage"] as const,
/**
* v1.17 — the per-user picker layout blob (`/api/mood/tags/layout`):
* group order + per-group tag placement. Its own root key — layout
* writes invalidate it alongside the catalog prefix.
*/
moodTagLayout: () => ["mood-tag-layout"] as const,
};