chore(v1.22): clear the QA punch-list across Coach, insights, settings

Code-review and design follow-ups before the v1.22 cut. No behaviour change
beyond the items below.

Coach / insights:
- Coach SSE route: a non-provider error raised inside the open producer (e.g. a
  DB failure persisting the reply) now records on the wide event and forwards to
  GlitchTip instead of masquerading as a provider outage; it still surfaces an
  error frame since the stream is open.
- Lab ↔ outcome correlations: hedge faint-tier pairs ("a faint hint, if
  anything … too small to lean on") so the narrated confidence never outruns the
  effect, matching the daily engine.
- Reminder sweep: mint the plan-review reminder and clear the plan's reviewDate
  in one transaction so a half-applied write can't duplicate the reminder.
- Fold the two raw provider-fallback runners onto one shared chain walker.

Design / accessibility:
- Lift sub-44px tap targets to the 44px mobile floor (suggested-action card,
  Coach bubble affordances), brighten the resting timestamp clock for non-text
  contrast, and route the lab chart height through the shared constant.
- Replace ad-hoc arbitrary type sizes with the scale token and static size-11
  icon buttons with the named icon-lg variant.
- Settings: add the missing About card-header descriptions, normalise the
  low-stock numeric widths, and relax the notification-status rows from nested
  cards to a divided list.

Housekeeping:
- Drop three unused exported types (and the now-orphaned source const), re-stamp
  v1.21.5 comments to v1.22, and register the live-judge eval harness as a knip
  entry so the script-only module resolves.
This commit is contained in:
Marc-André Bombeck committed 2026-06-27 16:29:58 +02:00
1 parent ee4fa55c1e
commit b217535b62
40 files changed
+330 -227

No files matched your search

+55 -1
View File
@@ -30,7 +30,8 @@ import { NextResponse, type NextRequest } from "next/server";
import { apiHandler, requireAuth, HttpError } from "@/lib/api-handler";
import { apiError, apiSuccess } from "@/lib/api-response";
import { annotate } from "@/lib/logging/context";
import { annotate, getEvent } from "@/lib/logging/context";
import { redactOptional, redactSecrets } from "@/lib/logging/redact";
import { auditLog } from "@/lib/auth/audit";
import { prisma } from "@/lib/db";
import { checkRateLimit } from "@/lib/rate-limit";
@@ -1169,10 +1170,23 @@ Reply now as the assistant, in ${locale === "de" ? "German" : "English"}. Fetch
// surfaces a graceful `coach.provider.*` frame; anything else degrades to
// a generic unavailable frame. A genuine defect is still annotated.
const providerError = classifyBubblingProviderError(err);
if (!providerError) {
// Not a tagged provider failure — a genuine server defect raised
// inside the open producer (e.g. a DB error in the post-stream
// `appendMessage` persistence). Record it on the wide event and
// forward it to GlitchTip so it stays visible instead of reading
// as a provider outage; the stream is open, so we still emit an
// error frame below rather than a 500.
getEvent()?.setError(
err instanceof Error ? err : new Error(String(err)),
);
void reportCoachStreamDefect(err);
}
annotate({
action: { name: "insights.coach.streamError" },
meta: {
unwrapped: Boolean(providerError),
reported: !providerError,
firstStatus: providerError?.httpStatus ?? null,
},
});
@@ -1327,6 +1341,46 @@ function classifyBubblingProviderError(err: unknown): {
return { code: "unavailable", httpStatus: status };
}
/**
* Fire-and-forget GlitchTip forward for a genuine server defect that
* surfaces INSIDE the open SSE producer — e.g. a Prisma failure in the
* post-stream `appendMessage` persistence, which runs after the provider
* call and therefore outside its try/catch. Once the stream is open the
* route can no longer return a 500, so without this such a defect would
* read as a generic provider outage in error tracking rather than the
* server bug it is. Mirrors the api-handler / worker forwarders: dynamic
* import (no cycle, no startup cost), redacted message + stack, and it
* NEVER throws so a sink failure cannot mask the original error.
*/
async function reportCoachStreamDefect(err: unknown): Promise<void> {
const e =
err instanceof Error ? err : new Error("Unknown coach stream error");
const message = redactSecrets(`[insights.coach.stream] ${e.message}`);
console.error("[coach-stream]", message, e);
try {
const [{ getGlitchtipSettings }, { sendGlitchtipEvent }] =
await Promise.all([
import("@/lib/monitoring-settings"),
import("@/lib/monitoring/glitchtip"),
]);
const settings = await getGlitchtipSettings();
if (!settings.glitchtipEnabled || !settings.glitchtipDsn) return;
await sendGlitchtipEvent({
dsn: settings.glitchtipDsn,
input: {
environment: settings.glitchtipEnvironment || "production",
message,
level: "error",
type: e.name || "Error",
stack: redactOptional(e.stack),
sourceTag: "healthlog-api-handler",
},
});
} catch {
/* the reporter must never throw */
}
}
function streamProviderError(args: { code: string }): Response {
const stream = createSseStream((controller) => {
controller.enqueue(