diff --git a/src/app/api/withings/status/route.ts b/src/app/api/withings/status/route.ts
index 8b45d8e02..41f6ea1b6 100644
--- a/src/app/api/withings/status/route.ts
+++ b/src/app/api/withings/status/route.ts
@@ -5,6 +5,7 @@ import { apiSuccess } from "@/lib/api-response";
import { decrypt, encrypt } from "@/lib/crypto";
import { getUserWithingsCredentials } from "@/lib/withings/credentials";
import { refreshAccessToken } from "@/lib/withings/client";
+import { isWithingsRefreshReauthFailure } from "@/lib/withings/sync";
import { markReauthRequired } from "@/lib/integrations/status";
/**
@@ -80,17 +81,7 @@ export const GET = apiHandler(async () => {
// the user reconnects. We mirror the same status-code parsing
// the sync path does.
const message = error instanceof Error ? error.message : String(error);
- const statusMatch = /Withings\s+\w+\s+error:\s*(\d+)/.exec(message);
- const statusCode = statusMatch
- ? Number.parseInt(statusMatch[1], 10)
- : NaN;
- const isReauth =
- Number.isFinite(statusCode) &&
- (statusCode === 100 ||
- statusCode === 101 ||
- statusCode === 102 ||
- (statusCode >= 200 && statusCode <= 299));
- if (isReauth) {
+ if (isWithingsRefreshReauthFailure(message)) {
await markReauthRequired(user.id, "withings", message).catch(() => {});
}
}
diff --git a/src/components/settings/notification-status-card.tsx b/src/components/settings/notification-status-card.tsx
index 1fbceda47..e1f0d6314 100644
--- a/src/components/settings/notification-status-card.tsx
+++ b/src/components/settings/notification-status-card.tsx
@@ -327,7 +327,6 @@ function stateBadgeFor(
icon: ,
};
case "manually_disabled":
- default:
return {
label: t("settings.notificationStatus.stateManuallyDisabled"),
variant: "secondary",
diff --git a/src/lib/ai/__tests__/codex-slug-cache.test.ts b/src/lib/ai/__tests__/codex-slug-cache.test.ts
index 82f2b840d..d3070b937 100644
--- a/src/lib/ai/__tests__/codex-slug-cache.test.ts
+++ b/src/lib/ai/__tests__/codex-slug-cache.test.ts
@@ -4,7 +4,6 @@ import {
setCachedCodexSlug,
invalidateCachedCodexSlug,
clearCodexSlugCache,
- inspectCodexSlugCache,
CODEX_SLUG_CACHE_TTL_MS,
} from "../codex-slug-cache";
@@ -19,7 +18,6 @@ describe("Codex slug cache", () => {
it("starts empty", () => {
expect(getCachedCodexSlug()).toBeNull();
- expect(inspectCodexSlugCache()).toBeNull();
});
it("set + get returns the cached slug within TTL", () => {
@@ -43,8 +41,8 @@ describe("Codex slug cache", () => {
it("expired entry is evicted on read", () => {
setCachedCodexSlug("gpt-5.3-codex", 1_000);
getCachedCodexSlug(1_000 + CODEX_SLUG_CACHE_TTL_MS + 1);
- // Subsequent inspect should be null too.
- expect(inspectCodexSlugCache(1_000 + CODEX_SLUG_CACHE_TTL_MS + 1)).toBeNull();
+ // Subsequent get should be null too (no resurrection).
+ expect(getCachedCodexSlug(1_000 + CODEX_SLUG_CACHE_TTL_MS + 1)).toBeNull();
});
it("invalidate clears the slot", () => {
@@ -58,10 +56,4 @@ describe("Codex slug cache", () => {
setCachedCodexSlug("b", 2_000);
expect(getCachedCodexSlug(2_500)).toBe("b");
});
-
- it("inspect reports age in ms", () => {
- setCachedCodexSlug("gpt-5.3-codex", 1_000);
- const inspected = inspectCodexSlugCache(1_500);
- expect(inspected).toEqual({ slug: "gpt-5.3-codex", ageMs: 500 });
- });
});
diff --git a/src/lib/ai/__tests__/codex-slug-fallback.test.ts b/src/lib/ai/__tests__/codex-slug-fallback.test.ts
index f2d79892b..1005d8750 100644
--- a/src/lib/ai/__tests__/codex-slug-fallback.test.ts
+++ b/src/lib/ai/__tests__/codex-slug-fallback.test.ts
@@ -2,7 +2,7 @@ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import { CodexClient, __test } from "../codex-client";
import {
clearCodexSlugCache,
- inspectCodexSlugCache,
+ getCachedCodexSlug,
setCachedCodexSlug,
CODEX_SLUG_CACHE_TTL_MS,
} from "../codex-slug-cache";
@@ -105,7 +105,7 @@ describe("Codex slug fallback chain", () => {
expect(diagnostics?.cacheState).toBe("miss");
expect(diagnostics?.workingSlug).toBe("gpt-5.3-codex");
- expect(inspectCodexSlugCache()?.slug).toBe("gpt-5.3-codex");
+ expect(getCachedCodexSlug()).toBe("gpt-5.3-codex");
});
it("walks past first slug on 'not supported when using Codex with a ChatGPT account' 400", async () => {
@@ -134,7 +134,7 @@ describe("Codex slug fallback chain", () => {
expect(diagnostics?.workingSlug).toBe("gpt-5.3-codex");
// The working slug — not the rejected one — is now cached.
- expect(inspectCodexSlugCache()?.slug).toBe("gpt-5.3-codex");
+ expect(getCachedCodexSlug()).toBe("gpt-5.3-codex");
});
it("walks past first slug on 400 with model_not_found body", async () => {
@@ -302,7 +302,7 @@ describe("Codex slug fallback chain", () => {
it("cache invalidated when cached slug starts rejecting", async () => {
setCachedCodexSlug("gpt-5-codex");
- expect(inspectCodexSlugCache()?.slug).toBe("gpt-5-codex");
+ expect(getCachedCodexSlug()).toBe("gpt-5-codex");
const mockFetch = vi
.fn()
@@ -320,7 +320,7 @@ describe("Codex slug fallback chain", () => {
// After walking to "gpt-5.3-codex" (the next entry after the
// cached one), the cache is updated to the new working slug.
- expect(inspectCodexSlugCache()?.slug).toBe("gpt-5.3-codex");
+ expect(getCachedCodexSlug()).toBe("gpt-5.3-codex");
});
it("isSlugRejection helper recognises canonical rejection bodies", () => {
diff --git a/src/lib/ai/codex-slug-cache.ts b/src/lib/ai/codex-slug-cache.ts
index 0b62c9bd1..9b1ef3e16 100644
--- a/src/lib/ai/codex-slug-cache.ts
+++ b/src/lib/ai/codex-slug-cache.ts
@@ -59,16 +59,4 @@ export function clearCodexSlugCache(): void {
cache.clear();
}
-/**
- * Diagnostic helper — exposes (slug, ageMs) for Wide-Event annotation.
- * Returns null when nothing is cached.
- */
-export function inspectCodexSlugCache(
- now: number = Date.now(),
-): { slug: string; ageMs: number } | null {
- const entry = cache.get(CACHE_KEY);
- if (!entry) return null;
- return { slug: entry.slug, ageMs: now - entry.cachedAtMs };
-}
-
export const CODEX_SLUG_CACHE_TTL_MS = CACHE_TTL_MS;
diff --git a/src/lib/gamification/achievements.ts b/src/lib/gamification/achievements.ts
index f2924034c..fbf13ee39 100644
--- a/src/lib/gamification/achievements.ts
+++ b/src/lib/gamification/achievements.ts
@@ -41,11 +41,6 @@ export interface AchievementDefinition {
format: "count" | "days" | "percent";
}
-/**
- * Stable metric → category mapping. Add a metric here when it joins the
- * `AchievementMetricKey` union; the page falls back to "engagement" if
- * a future definition slips through, so there is no silent drop-off.
- */
export function getAchievementCategory(
metric: AchievementMetricKey,
): AchievementCategory {
diff --git a/src/lib/notifications/channel-state.ts b/src/lib/notifications/channel-state.ts
index 25515c036..62e7cc937 100644
--- a/src/lib/notifications/channel-state.ts
+++ b/src/lib/notifications/channel-state.ts
@@ -8,7 +8,6 @@
import { prisma } from "@/lib/db";
import { auditLog } from "@/lib/auth/audit";
import {
- MAX_CONSECUTIVE_FAILURES,
nextRetryAt,
shouldAutoDisableAfterTransient,
type SendOutcome,
@@ -167,5 +166,3 @@ export function isChannelInCooldown(
): boolean {
return channel.nextRetryAt !== null && channel.nextRetryAt > now;
}
-
-export const TEST_CONSTANTS = { MAX_CONSECUTIVE_FAILURES } as const;
diff --git a/src/lib/notifications/retry-policy.ts b/src/lib/notifications/retry-policy.ts
index 9a6d960a7..792afcd40 100644
--- a/src/lib/notifications/retry-policy.ts
+++ b/src/lib/notifications/retry-policy.ts
@@ -25,8 +25,6 @@
* response that doesn't match the hard-reject rules.
*/
-export type RejectKind = "hard" | "soft" | "ok";
-
export interface SendOutcome {
ok: boolean;
/** True iff the upstream signaled a permanent failure (410, blocked-by-user, …). */
diff --git a/src/lib/withings/sync.ts b/src/lib/withings/sync.ts
index 4de206589..5b1a7ce3a 100644
--- a/src/lib/withings/sync.ts
+++ b/src/lib/withings/sync.ts
@@ -237,7 +237,7 @@ export async function syncUserMeasurements(
* The error message format from `refreshAccessToken()` is
* "Withings refresh error: - ".
*/
-function isWithingsRefreshReauthFailure(message: string): boolean {
+export function isWithingsRefreshReauthFailure(message: string): boolean {
const status = extractWithingsStatus(message);
if (!status) return false;
const n = Number.parseInt(status, 10);
@@ -247,7 +247,7 @@ function isWithingsRefreshReauthFailure(message: string): boolean {
return false;
}
-function extractWithingsStatus(message: string): string | undefined {
+export function extractWithingsStatus(message: string): string | undefined {
// Captures the digit run after "Withings error:" — same shape
// exchangeCode and refreshAccessToken use.
const m = /Withings\s+\w+\s+error:\s*(\d+)/.exec(message);