mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-12 06:06:10 +00:00
Maintainer-voice sweep across `src/`. ~190 references to the maintainer by first name in code comments and JSDoc neutralised to "the maintainer", "the live tenant", "users", or "the user" depending on context. Three sed passes covering ~50 distinct patterns (`Marc reported`, `Marc wants`, `Marc kept`, `Marc rolled back`, etc.). Test fixtures (`displayName: "Marc B."`, `username: "marc"`, `<HeroStrip userName="Marc">`) kept as opaque test data per the brief. CHANGELOG normalisation. The v1.4.14 + v1.4.15 entries paired a German bullet with its English translation by historical design — the in-app copy is English-default and the user docs are English, so the mixed-language style was a holdover from the early-marathon notes. A one-off node script extracted the `_..._` English paragraph from each bilingual bullet and rewrote the entry as that text. ~44 bullets reduced; one Docs bullet that lost a leading word in the regex fixed by hand. CLAUDE.md → CONTRIBUTING-AI.md rename so the filename is no longer AI-vendor-specific. `CONTRIBUTING.md` reference updated. `AGENTS.md` stays in place for multi-agent compatibility (the conventions doc is shared with Codex / Cursor / etc.). Wave-4 state wrap. STATE.md now records C1–C5 + D + D2 as complete; v1421-backlog.md grew a "Closed in v1.4.22" header listing the 20 items the marathon landed; `.planning/phase-W4-report.md` documents the per-commit decisions, test deltas, and the 23 items deferred to v1.4.23 with reasoning. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
74 lines
3.1 KiB
TypeScript
74 lines
3.1 KiB
TypeScript
/**
|
|
* v1.4.16 phase B5c — legacy-payload detection.
|
|
*
|
|
* Cached insight blobs persisted before B5c shipped don't carry the
|
|
* `rationale` field. The /api/insights/generate route surfaces a
|
|
* `legacyPayload: true` flag on cache-hit so the UI can render a
|
|
* "Insights updated — regenerate for new explainability features"
|
|
* CTA. User-initiated regeneration stays the trigger; we do NOT
|
|
* auto-regenerate on cache-hit (that would burn rate-limit tokens
|
|
* silently and surprise users who explicitly toggled cache-only).
|
|
*
|
|
* Detection is lenient on purpose: the cached payload may carry
|
|
* either the canonical `AIInsightResponse` shape (structured recs)
|
|
* or the legacy `InsightResult` shape (string-only recommendations).
|
|
* Both predate B5c rationale; both flag.
|
|
*/
|
|
|
|
/**
|
|
* Returns true when the payload predates the v1.4.16 strict insight
|
|
* shape and the UI must surface the regenerate CTA instead of trying
|
|
* to render the rich card.
|
|
*
|
|
* Detection branches:
|
|
* - Non-object / null / undefined input → false (nothing to migrate).
|
|
* - **v1.4.14 pre-strict shape** (`{changed, stable, drivers, ...}` —
|
|
* no `summary`, no `recommendations[]`, no `findings[]`): true. This
|
|
* is the production crash the maintainer hit on 2026-05-10 — the route's
|
|
* `safeParse` failed against `insightResultSchema`, fell through to
|
|
* the raw blob, and the rich card called `.replace()` on
|
|
* `undefined`. Detect by absence of *both* `summary` (string) and
|
|
* `recommendations` (array).
|
|
* - **v1.4.15 mid-shape** (recommendations[] present, all strings):
|
|
* true (plain-string recs predate B5c rationale).
|
|
* - **v1.4.16 pre-B5c shape** (recommendations[] present, structured,
|
|
* missing `rationale`): true.
|
|
* - Otherwise → false.
|
|
*/
|
|
export function isLegacyInsightPayload(payload: unknown): boolean {
|
|
if (payload === null || payload === undefined) return false;
|
|
if (typeof payload !== "object") return false;
|
|
const obj = payload as { summary?: unknown; recommendations?: unknown };
|
|
|
|
const recs = obj.recommendations;
|
|
const hasSummary = typeof obj.summary === "string" && obj.summary.length > 0;
|
|
const hasRecsArray = Array.isArray(recs);
|
|
|
|
// v1.4.14 / pre-strict shape: neither the modern `summary` string nor
|
|
// a `recommendations` array is present. The blob still parses as
|
|
// JSON but the rich card surface has nothing to render.
|
|
if (!hasSummary && !hasRecsArray) return true;
|
|
|
|
// Modern-shape but recs missing entirely — refusal payload, not
|
|
// legacy. Don't gate the regenerate CTA on this.
|
|
if (!hasRecsArray) return false;
|
|
if (!Array.isArray(recs)) return false; // narrowing for TS
|
|
if (recs.length === 0) return false;
|
|
|
|
for (const rec of recs) {
|
|
// Legacy InsightResult shape: string-only recommendations[]
|
|
if (typeof rec === "string") return true;
|
|
// Structured rec: rationale must be a populated object.
|
|
if (rec === null || typeof rec !== "object") return true;
|
|
const rationale = (rec as { rationale?: unknown }).rationale;
|
|
if (
|
|
rationale === undefined ||
|
|
rationale === null ||
|
|
typeof rationale !== "object"
|
|
) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|