diff --git a/docs/api/openapi.yaml b/docs/api/openapi.yaml index e360c1932..a6cc5abb1 100644 --- a/docs/api/openapi.yaml +++ b/docs/api/openapi.yaml @@ -68573,6 +68573,34 @@ components: - showEvidenceByDefault - defaultWindow additionalProperties: false + MoodTagLayout: + type: object + properties: + groupOrder: + maxItems: 50 + type: array + items: + type: string + minLength: 1 + maxLength: 80 + placements: + type: object + propertyNames: + type: string + minLength: 1 + maxLength: 80 + additionalProperties: + type: array + items: + type: string + minLength: 1 + maxLength: 80 + additionalProperties: false + description: "Per-user mood-tag presentation blob. `groupOrder`: category keys in display order (unknown dropped, + missing appended in seeded order). `placements`: categoryKey → ordered tag keys; a placed tag renders in that + group at that index, un-placed tags follow in their home category. Display-only — placements referencing + hidden/archived/unknown keys are silently dropped at read time. Both fields optional: PUT merges + preserve-when-absent." parameters: AccountSelector: name: X-HealthLog-Account diff --git a/src/lib/openapi/routes/index.ts b/src/lib/openapi/routes/index.ts index a77ccf325..33a921c43 100644 --- a/src/lib/openapi/routes/index.ts +++ b/src/lib/openapi/routes/index.ts @@ -72,7 +72,7 @@ import { } from "./medications"; import { mcpPaths } from "./mcp"; import { metaPaths } from "./meta"; -import { moodPaths } from "./mood"; +import { moodPaths, moodTagLayout } from "./mood"; import { notificationTransportPaths } from "./notifications-transport"; import { notificationChannelPaths } from "./notifications"; import { nutrientPaths } from "./nutrients"; @@ -183,6 +183,11 @@ export const openApiComponents: NonNullable = { // this forced registration the schema the description points to would be // silently absent from the published spec. // + // `MoodTagLayout` joins them for exactly the same reason: its annotation is + // in the assigning form and correct, but both consumers `.extend()` it, so + // the base name is never referenced and the description the mood surface + // relies on would go unpublished. + // // `CoachPrefs` is the same shape: the PUT `/api/auth/me/coach-prefs` // description tells the reader "Body is validated against `CoachPrefs`", // but every actual consumer is a `.extend()` of it (`PutCoachPrefsRequest`, @@ -193,6 +198,7 @@ export const openApiComponents: NonNullable = { MedicationDoseHistoryImportFatalReason: medicationDoseHistoryImportFatalReasonEnum, CoachPrefs: coachPrefsSchema, + MoodTagLayout: moodTagLayout, }, // v1.36.0 — the per-request account selector, defined in the sharing route // module so that `src/__tests__/acting-account-boundary-guard.test.ts` keeps diff --git a/src/lib/openapi/routes/mood.ts b/src/lib/openapi/routes/mood.ts index 6b4f45cd0..92cb9afe4 100644 --- a/src/lib/openapi/routes/mood.ts +++ b/src/lib/openapi/routes/mood.ts @@ -114,7 +114,15 @@ const listMoodEntriesQuery = listMoodEntriesSchema.meta({ "Filters + paging for the mood-entry list. `from` / `to` bound the local-day `date` label (YYYY-MM-DD, inclusive). `limit` capped at 500.", }); -const moodTagLayout = moodTagLayoutSchema.meta({ +/** + * Exported for the forced registration in `./index.ts`. + * + * Both consumers are an `.extend()` of this base, and `.extend()` builds a new + * object rather than a reference, so the name never carries forward on its own + * even though the annotation is in the assigning form. Same shape as + * `CoachPrefs` and `Medication`, and the same remedy. + */ +export const moodTagLayout = moodTagLayoutSchema.meta({ id: "MoodTagLayout", description: "Per-user mood-tag presentation blob. `groupOrder`: category keys in display order (unknown dropped, missing appended in seeded order). `placements`: categoryKey → ordered tag keys; a placed tag renders in that group at that index, un-placed tags follow in their home category. Display-only — placements referencing hidden/archived/unknown keys are silently dropped at read time. Both fields optional: PUT merges preserve-when-absent.",