mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 20:42:43 +00:00
Declare unavailableWidgetIds in the wire contract
The widgets GET grew a response field and the spec was never told. Nothing would have caught it: `openapi:check` compares the generated yaml against the Zod registry, and an undeclared field changes neither — so CI stays green while the published contract quietly stops describing what the route returns. iOS decodes this payload. `dashboardLayoutResult` is shared by the GET and the PUT echo, but the field is resolved per request from the caller's modules and their data, so it exists only on the read. A separate `dashboardLayoutReadResult` extends the shared schema for the GET rather than widening both verbs and describing a field the PUT never sends. docs/api/openapi.yaml is NOT regenerated in this commit: `pnpm openapi:generate` cannot run here (the Prisma engine download is refused, so `@/generated/prisma/client` is missing and the registry will not load). The tree is therefore in drift on purpose and `pnpm openapi:check` will fail until `pnpm openapi:generate` is run and the yaml committed.
This commit is contained in:
1 file changed
+26
-2
@@ -108,6 +108,27 @@ const dashboardLayoutResult = dashboardLayoutSchema
|
||||
"Resolved dashboard layout plus the optimistic-concurrency `updatedAt` token.",
|
||||
});
|
||||
|
||||
/**
|
||||
* The GET-only shape. `unavailableWidgetIds` is resolved per request from
|
||||
* the caller's module state and their data, so it belongs to the read and
|
||||
* never to the PUT echo — hence a separate schema rather than widening
|
||||
* `dashboardLayoutResult`, which both verbs share.
|
||||
*/
|
||||
const dashboardLayoutReadResult = dashboardLayoutResult
|
||||
.extend({
|
||||
unavailableWidgetIds: z
|
||||
.array(z.string())
|
||||
.optional()
|
||||
.describe(
|
||||
"Widget ids whose toggle cannot do anything for this account, and which a settings UI should therefore not offer — a switch over a tile that can never paint. Resolved per request from the caller's enabled modules and the data they actually have: `hrv` appears here when the Recovery module is off and the account has no SDNN series, because its only remaining HRV source (nightly RMSSD) is recovery-owned and withheld. Absent when nothing is unavailable. Additive: a client that ignores it behaves exactly as before.",
|
||||
),
|
||||
})
|
||||
.meta({
|
||||
id: "DashboardLayoutReadResult",
|
||||
description:
|
||||
"Resolved dashboard layout, its `updatedAt` token, and any widget toggles that are unavailable for this account.",
|
||||
});
|
||||
|
||||
// ── The native-client dashboard aggregator ───────────────────────────
|
||||
//
|
||||
// `GET /api/dashboard/summary` predates the registry and is the shape the iOS
|
||||
@@ -372,7 +393,7 @@ export const dashboardWidgetPaths: NonNullable<ZodOpenApiObject["paths"]> = {
|
||||
tags: ["Dashboard"],
|
||||
summary: "Read the calling user's dashboard widget layout",
|
||||
description:
|
||||
"Returns the resolved effective layout (defaults merged in if the user has not customised it) plus the optimistic-concurrency `updatedAt` token.",
|
||||
"Returns the resolved effective layout (defaults merged in if the user has not customised it), the optimistic-concurrency `updatedAt` token, and `unavailableWidgetIds` — any widget whose toggle cannot do anything for this account, which a settings UI should not offer.",
|
||||
responses: {
|
||||
...recordRefusal(),
|
||||
"200": {
|
||||
@@ -380,7 +401,10 @@ export const dashboardWidgetPaths: NonNullable<ZodOpenApiObject["paths"]> = {
|
||||
"The resolved layout (custom or default) plus its token.",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: dataEnvelope(dashboardLayoutResult, "DashboardLayout"),
|
||||
schema: dataEnvelope(
|
||||
dashboardLayoutReadResult,
|
||||
"DashboardLayout",
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
Reference in new issue
Block a user