mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-15 06:52:04 +00:00
feat(apns): time-sensitive interruption-level for medication reminders
Medication reminders are the one notification category the user has explicitly opted in to. iOS 15+ supports `aps.interruption-level: time-sensitive` to break through Focus modes per category, gated on a per-app toggle the user grants on install. Wire it for MEDICATION_REMINDER only — every other event (mood nudges, anomaly alerts, PRs, system messages) stays on the iOS default `active` level so Focus modes including Sleep keep silencing them as the user expects. Pairs with `apns-priority: 10` for time-sensitive deliveries per Apple's push best-practices doc; without the priority pairing APNs may downgrade delivery and silently lose the Focus-bypass intent. Pinned by 8 new unit tests across `apns.test.ts` — one positive case for MEDICATION_REMINDER and a parameterised negative covering all six other event-types. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
c47b7dade3
commit
8187d54969
10 files changed
+235
-13
No files matched your search
@@ -2,6 +2,7 @@
|
||||
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import { queryKeys } from "@/lib/query-keys";
|
||||
import {
|
||||
ResponsiveContainer,
|
||||
ComposedChart,
|
||||
@@ -541,8 +542,12 @@ export function HealthChart({
|
||||
}, [rangePoints, effectiveCompareBaseline]);
|
||||
|
||||
const { data, isLoading } = useQuery({
|
||||
queryKey: [
|
||||
"chart-data",
|
||||
// v1.4.40 W-RSC — route the chart-data key through `queryKeys.chartData`
|
||||
// so the `["chart-data"]` prefix lands in `measurementDependentKeys`
|
||||
// and a fresh measurement evicts every per-chart daily cache in one
|
||||
// pass (audit C2). The factory-packed tuple is byte-identical with
|
||||
// the pre-v1.4.40 inline literal so the cache layout stays stable.
|
||||
queryKey: queryKeys.chartData(
|
||||
types.join(","),
|
||||
valueMode,
|
||||
bmiDivisor ?? "no-bmi",
|
||||
@@ -555,7 +560,7 @@ export function HealthChart({
|
||||
// re-running the unbounded walk.
|
||||
fetchWindow.from,
|
||||
fetchWindow.to,
|
||||
],
|
||||
),
|
||||
// v1.4.28 FB-D2 — cache the bounded window for a minute so tab
|
||||
// navigation between insights sub-pages does not re-fire every
|
||||
// chart's fetch. `gcTime` keeps the inactive cache for five
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
*/
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { useMemo, useState } from "react";
|
||||
import { queryKeys } from "@/lib/query-keys";
|
||||
import {
|
||||
ResponsiveContainer,
|
||||
ComposedChart,
|
||||
@@ -226,8 +227,12 @@ export function MedicationComplianceChart({
|
||||
// room of the weight / BMI charts.
|
||||
const viewportWidth = useViewportWidth();
|
||||
|
||||
// v1.4.40 W-RSC — factory-routed key so the prefix
|
||||
// `["dashboard-medication-compliance"]` lands in
|
||||
// `medicationDependentKeys` and an intake POST refreshes the chart
|
||||
// immediately rather than waiting for `staleTime` (audit L4).
|
||||
const { data, isLoading } = useQuery({
|
||||
queryKey: ["medication-compliance-chart", days],
|
||||
queryKey: queryKeys.dashboardMedicationCompliance(days),
|
||||
queryFn: async (): Promise<DailyCompliancePoint[]> => {
|
||||
const res = await fetch(
|
||||
`/api/medications/intake?scope=compliance&days=${days}`,
|
||||
|
||||
@@ -9,6 +9,7 @@ import { useWorkouts } from "@/hooks/use-workouts";
|
||||
import { InsightsTabStrip } from "@/components/insights/insights-tab-strip";
|
||||
import { useInsightsAdvisorQuery } from "@/components/insights/use-insights-advisor";
|
||||
import { useAnalyticsQuery } from "@/lib/queries/use-analytics-query";
|
||||
import { queryKeys } from "@/lib/query-keys";
|
||||
import type { InsightInputs } from "@/lib/insights/metric-availability";
|
||||
|
||||
/**
|
||||
@@ -66,7 +67,7 @@ export function InsightsLayoutShell({ children }: { children: ReactNode }) {
|
||||
// event-driven gating branches. Sub-pages read the same key so the
|
||||
// payload lands once per route.
|
||||
const comprehensiveQuery = useQuery({
|
||||
queryKey: ["insights", "comprehensive"],
|
||||
queryKey: queryKeys.insightsComprehensive(),
|
||||
queryFn: async () => {
|
||||
const res = await fetch("/api/insights/comprehensive");
|
||||
if (!res.ok) throw new Error("Failed");
|
||||
|
||||
Reference in new issue
Block a user