From 7927865e17665e547614a13fc3af11aa7e14bab8 Mon Sep 17 00:00:00 2001 From: dawnho Date: Thu, 2 Jul 2026 13:04:34 -0700 Subject: [PATCH 1/2] feat: cross-link shared device errors from sub-category error pages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The lock/thermostat/phone Errors and Warnings pages are subsets of the aggregate /api/devices/errors page: they omit the device-level codes (device_offline, device_disconnected, missing_device_credentials, …) that apply to every device. A reader troubleshooting connectivity on /api/locks/errors previously had no pointer to those codes. Each device sub-category error page now opens with a linking to the shared Device Errors and Warnings page, derived from the page's noun and its actual sections (e.g. the phones page, which has only warnings, reads "Phone-specific warnings"). Also make the hand-maintained DEVICE_SUBCATEGORY_ERROR_ROUTES allowlist self-checking: a new device sub-category with its own object + events pages and an error/warning variant group would otherwise silently produce no errors page until someone edited the map. The generator now logs a warning for any device variant group that has codes and an object+events page under /api/ but is neither mapped nor a documented resource — without false-positiving on real resources (access_codes) or annotation-only groups (hardware, provider_metadata). Adds a defensive guard in writeErrorPage to skip a route already written, protecting against a variant-group key ever colliding with a real resource route (device_provider already shares /devices with device; the guard makes that ordering explicit rather than relying on an empty-codes early-return). Co-Authored-By: Claude Opus 4.8 --- mintlify-codegen/errors.ts | 123 ++++++++++++++++++++++- mintlify-docs/api/locks/errors.mdx | 4 + mintlify-docs/api/phones/errors.mdx | 4 + mintlify-docs/api/thermostats/errors.mdx | 4 + 4 files changed, 133 insertions(+), 2 deletions(-) diff --git a/mintlify-codegen/errors.ts b/mintlify-codegen/errors.ts index 80d1271c9..188bc1c78 100644 --- a/mintlify-codegen/errors.ts +++ b/mintlify-codegen/errors.ts @@ -282,11 +282,38 @@ function kindSuffix(hasErrors: boolean, hasWarnings: boolean): string { return hasErrors ? 'Errors' : 'Warnings' } -/** Render the full standalone errors/warnings page (frontmatter + sections). */ +/** + * Render a note pointing readers to the errors/warnings shared by all devices. + * Device sub-category pages (locks/thermostats/phones) are subsets that omit the + * device-level codes (`device_offline`, `device_disconnected`, …) applying to + * every device, so a reader troubleshooting connectivity there needs a pointer + * to the aggregate device page. `kinds` is the lowercased sections phrase + * (`errors and warnings` / `errors` / `warnings`) so the prose matches the + * page's actual content. + */ +function renderCommonDeviceErrorsNote( + noun: string, + kinds: string, + route: string, +): string { + const phrase = kinds.toLowerCase() + return [ + '', + ` These are ${noun}-specific ${phrase}. For ${phrase} common to all devices, see [Device Errors and Warnings](${route}).`, + '', + ].join('\n') +} + +/** + * Render the full standalone errors/warnings page (frontmatter + sections). When + * `commonDeviceErrorsRoute` is set, a note linking to the shared device + * errors/warnings page is prepended — used on device sub-category pages. + */ function renderPage( noun: string, errorSection: string, warningSection: string, + commonDeviceErrorsRoute?: string, ): string { const title = `${noun} ${kindSuffix(Boolean(errorSection), Boolean(warningSection))}` const kinds = @@ -302,7 +329,10 @@ function renderPage( `description: '${description.replace(/'/g, "\\'")}'`, '---', ].join('\n') - const body = [errorSection, warningSection].filter(Boolean).join('\n\n') + const note = commonDeviceErrorsRoute + ? renderCommonDeviceErrorsNote(noun, kinds, commonDeviceErrorsRoute) + : '' + const body = [note, errorSection, warningSection].filter(Boolean).join('\n\n') return `${frontmatter}\n\n${body}\n` } @@ -314,6 +344,13 @@ async function readFileOrNull(path: string): Promise { } } +interface ErrorPageOptions { + // When set, prepend a note linking to the errors/warnings shared by all + // devices. Used only for device sub-category pages (locks/thermostats/phones), + // which are subsets that omit the device-level codes. + commonDeviceErrorsRoute?: string +} + /** * Write one resource's `errors.mdx` (an `## Errors` and/or `## Warnings` * section) and record its route in `routes`. No-ops when the resource has no @@ -325,7 +362,16 @@ async function writeErrorPage( errorsProp: Property | undefined, warningsProp: Property | undefined, routes: string[], + options: ErrorPageOptions = {}, ): Promise { + // Defensive against a variant-group key ever coinciding with a real resource + // route: the resource loop writes first, so skip a route already emitted + // rather than clobbering it from the sub-category loop. + if (routes.includes(routePath)) { + console.log(` Errors page for ${routePath} already written, skipping`) + return + } + const errorGroups = groupCodes(errorsProp) const warningGroups = groupCodes(warningsProp) if (errorGroups.length === 0 && warningGroups.length === 0) return @@ -342,6 +388,7 @@ async function writeErrorPage( noun, renderSection('Errors', 'error', errorsProp, errorGroups), renderSection('Warnings', 'warning', warningsProp, warningGroups), + options.commonDeviceErrorsRoute, ) await writeFile(join(resourceDir, 'errors.mdx'), page) routes.push(routePath) @@ -365,6 +412,11 @@ const DEVICE_SUBCATEGORY_ERROR_ROUTES: Record = { thermostats: '/thermostats', } +// Route of the aggregate device errors/warnings page. Its codes apply to every +// device, so each sub-category page links back to it (see writeErrorPage's +// `commonDeviceErrorsRoute` option). +const COMMON_DEVICE_ERRORS_ROUTE = '/api/devices/errors' + /** * Narrow a device `errors`/`warnings` property to a single variant group, * flattening the kept variants to ungrouped (`variantGroupKey` cleared) so their @@ -384,6 +436,58 @@ function variantGroupProp( return { ...prop, variants, variantGroups: [] } } +/** + * Warn when a device errors/warnings variant group looks like it should have its + * own sub-category errors page but is missing from DEVICE_SUBCATEGORY_ERROR_ROUTES. + * + * That allowlist is maintained by hand, so a newly added device sub-category + * (its own object + events pages plus an error/warning variant group) would + * silently produce no errors page until someone edits the map. This surfaces + * that inconsistency at generate time. + * + * A group is flagged only when it (a) has documented codes, (b) has both an + * `object.mdx` and an `events.mdx` under `/api/` — the shape a real + * sub-category route has — and (c) is neither already mapped nor itself a + * documented blueprint resource. Condition (c) keeps real resources + * (`access_codes`, which owns its own distinct errors) and annotation-only + * groups (`hardware`, `provider_metadata` — no such route) from tripping it. + */ +async function warnUnmappedDeviceSubcategoryGroups( + docsDir: string, + errorsProp: Property | undefined, + warningsProp: Property | undefined, + documentedResourceRoutes: Set, +): Promise { + const groupKeys = new Set() + for (const prop of [errorsProp, warningsProp]) { + if (!isDiscriminatedListProperty(prop)) continue + for (const group of prop.variantGroups) groupKeys.add(group.variantGroupKey) + } + + for (const groupKey of groupKeys) { + if (groupKey in DEVICE_SUBCATEGORY_ERROR_ROUTES) continue + if (documentedResourceRoutes.has(`/${groupKey}`)) continue + + const hasCodes = + groupCodes(variantGroupProp(errorsProp, groupKey)).length > 0 || + groupCodes(variantGroupProp(warningsProp, groupKey)).length > 0 + if (!hasCodes) continue + + const resourceDir = join(docsDir, 'api', groupKey) + const hasObject = + (await readFileOrNull(join(resourceDir, 'object.mdx'))) != null + const hasEvents = + (await readFileOrNull(join(resourceDir, 'events.mdx'))) != null + if (!hasObject || !hasEvents) continue + + console.log( + ` WARNING: device errors/warnings variant group "${groupKey}" has codes ` + + `and an object+events page (/api/${groupKey}) but no dedicated errors ` + + `page. Add it to DEVICE_SUBCATEGORY_ERROR_ROUTES in mintlify-codegen/errors.ts.`, + ) + } +} + /** * Generate the per-resource `errors.mdx` pages. Returns the route paths that * received a page (e.g. `/devices`) so the caller can wire them into the @@ -422,8 +526,23 @@ export async function updateErrorPages( variantGroupProp(errorsProp, groupKey), variantGroupProp(warningsProp, groupKey), routes, + { commonDeviceErrorsRoute: COMMON_DEVICE_ERRORS_ROUTE }, ) } + + // Flag any device sub-category that has its own object+events pages and + // error codes but was never added to the allowlist above. + const documentedResourceRoutes = new Set( + blueprint.resources + .filter((r) => !r.isUndocumented) + .map((r) => r.routePath), + ) + await warnUnmappedDeviceSubcategoryGroups( + docsDir, + errorsProp, + warningsProp, + documentedResourceRoutes, + ) } return routes diff --git a/mintlify-docs/api/locks/errors.mdx b/mintlify-docs/api/locks/errors.mdx index 3ab463a6f..cad40bcd9 100644 --- a/mintlify-docs/api/locks/errors.mdx +++ b/mintlify-docs/api/locks/errors.mdx @@ -3,6 +3,10 @@ title: 'Lock Errors and Warnings' description: 'Errors and warnings that Seam reports on the Lock resource, each with its code and meaning.' --- + + These are Lock-specific errors and warnings. For errors and warnings common to all devices, see [Device Errors and Warnings](/api/devices/errors). + + ## Errors Each error is an object with the following shape: diff --git a/mintlify-docs/api/phones/errors.mdx b/mintlify-docs/api/phones/errors.mdx index a6733a214..b728cd04a 100644 --- a/mintlify-docs/api/phones/errors.mdx +++ b/mintlify-docs/api/phones/errors.mdx @@ -3,6 +3,10 @@ title: 'Phone Warnings' description: 'Warnings that Seam reports on the Phone resource, each with its code and meaning.' --- + + These are Phone-specific warnings. For warnings common to all devices, see [Device Errors and Warnings](/api/devices/errors). + + ## Warnings Each warning is an object with the following shape: diff --git a/mintlify-docs/api/thermostats/errors.mdx b/mintlify-docs/api/thermostats/errors.mdx index 6d6e9357f..bd0c0837c 100644 --- a/mintlify-docs/api/thermostats/errors.mdx +++ b/mintlify-docs/api/thermostats/errors.mdx @@ -3,6 +3,10 @@ title: 'Thermostat Errors and Warnings' description: 'Errors and warnings that Seam reports on the Thermostat resource, each with its code and meaning.' --- + + These are Thermostat-specific errors and warnings. For errors and warnings common to all devices, see [Device Errors and Warnings](/api/devices/errors). + + ## Errors Each error is an object with the following shape: From 19c0e343dcab1ad6eacaf994123566e25496c948 Mon Sep 17 00:00:00 2001 From: dawnho Date: Thu, 2 Jul 2026 13:56:10 -0700 Subject: [PATCH 2/2] feat: omit the common-device-errors cross-link on the phones page MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The shared device errors/warnings the note points to are physical-device connectivity and subscription codes (device_offline, hub_disconnected, salto_ks_subscription_limit_exceeded, …). They apply to locks and thermostats but not to phones (mobile devices used for credentials), so the note was misleading there. Make the cross-link per sub-category: DEVICE_SUBCATEGORY_ERROR_ROUTES now carries a `linkCommonDeviceErrors` flag alongside the route, set true for locks/thermostats and false for phones. The phones page no longer renders the note; locks and thermostats are unchanged. Co-Authored-By: Claude Opus 4.8 --- mintlify-codegen/errors.ts | 35 ++++++++++++++++++++--------- mintlify-docs/api/phones/errors.mdx | 4 ---- 2 files changed, 25 insertions(+), 14 deletions(-) diff --git a/mintlify-codegen/errors.ts b/mintlify-codegen/errors.ts index 188bc1c78..0d59f7d50 100644 --- a/mintlify-codegen/errors.ts +++ b/mintlify-codegen/errors.ts @@ -394,6 +394,18 @@ async function writeErrorPage( routes.push(routePath) } +interface DeviceSubcategory { + // Route that carries the sub-category's dedicated errors page. + routePath: string + // Whether the sub-category shares the device-level errors/warnings documented + // on /api/devices/errors, and so should cross-link to them. The shared codes + // are physical-device connectivity/subscription errors (`device_offline`, + // `hub_disconnected`, `salto_ks_subscription_limit_exceeded`, …), relevant to + // locks and thermostats but not to phones (mobile devices used for + // credentials), so the phones page omits the cross-link. + linkCommonDeviceErrors: boolean +} + // Some device sub-categories have their own docs route but no blueprint // resource of their own — the Seam API models a lock, thermostat, or phone as a // `device`. Their error/warning codes therefore live on the `device` resource @@ -406,14 +418,14 @@ async function writeErrorPage( // but have no standalone sub-category route. In particular /access_codes // already documents the `access_code` resource's own, distinct errors — routing // the device `access_codes` group there would clobber it. -const DEVICE_SUBCATEGORY_ERROR_ROUTES: Record = { - locks: '/locks', - phones: '/phones', - thermostats: '/thermostats', +const DEVICE_SUBCATEGORY_ERROR_ROUTES: Record = { + locks: { routePath: '/locks', linkCommonDeviceErrors: true }, + phones: { routePath: '/phones', linkCommonDeviceErrors: false }, + thermostats: { routePath: '/thermostats', linkCommonDeviceErrors: true }, } -// Route of the aggregate device errors/warnings page. Its codes apply to every -// device, so each sub-category page links back to it (see writeErrorPage's +// Route of the aggregate device errors/warnings page. Sub-categories that share +// the device-level codes link back to it (see writeErrorPage's // `commonDeviceErrorsRoute` option). const COMMON_DEVICE_ERRORS_ROUTE = '/api/devices/errors' @@ -517,16 +529,19 @@ export async function updateErrorPages( if (device != null) { const errorsProp = device.properties.find((p) => p.name === 'errors') const warningsProp = device.properties.find((p) => p.name === 'warnings') - for (const [groupKey, routePath] of Object.entries( - DEVICE_SUBCATEGORY_ERROR_ROUTES, - )) { + for (const [ + groupKey, + { routePath, linkCommonDeviceErrors }, + ] of Object.entries(DEVICE_SUBCATEGORY_ERROR_ROUTES)) { await writeErrorPage( docsDir, routePath, variantGroupProp(errorsProp, groupKey), variantGroupProp(warningsProp, groupKey), routes, - { commonDeviceErrorsRoute: COMMON_DEVICE_ERRORS_ROUTE }, + linkCommonDeviceErrors + ? { commonDeviceErrorsRoute: COMMON_DEVICE_ERRORS_ROUTE } + : {}, ) } diff --git a/mintlify-docs/api/phones/errors.mdx b/mintlify-docs/api/phones/errors.mdx index b728cd04a..a6733a214 100644 --- a/mintlify-docs/api/phones/errors.mdx +++ b/mintlify-docs/api/phones/errors.mdx @@ -3,10 +3,6 @@ title: 'Phone Warnings' description: 'Warnings that Seam reports on the Phone resource, each with its code and meaning.' --- - - These are Phone-specific warnings. For warnings common to all devices, see [Device Errors and Warnings](/api/devices/errors). - - ## Warnings Each warning is an object with the following shape: