From 1b0e280c54acec3c5454a98b7e9f1184fd7d3e70 Mon Sep 17 00:00:00 2001 From: caballeto Date: Wed, 22 Apr 2026 16:38:44 +0200 Subject: [PATCH] =?UTF-8?q?feat(status-pages):=20rename=20group=20`collaps?= =?UTF-8?q?ed`=20=E2=86=92=20`defaultOpen`=20and=20pull=20through=20Camp-A?= =?UTF-8?q?=20uptime=20spec?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mirrors the upstream API change in `mono` (PR linked from same feature branch). Two coordinated edits: 1. **YAML + CLI flags**: `componentGroups[].collapsed` → `defaultOpen`, default `true`. The flag pair flips from `--collapsed` / `--no-collapsed` to `--default-open` / `--no-default-open`. The YAML schema description now explains the field as a *seed* value (renderer is free to override on active incidents) so users reach for it correctly. 2. **Snapshot/diff plumbing**: handlers.ts, zod-schemas.ts, schema.ts, spec-field-parity.test.ts, and partial-failure-convergence.test.ts all use `defaultOpen`. Convergence fakes flipped accordingly. Generated artifacts (`api-zod.generated.ts`, `api.generated.ts`, `docs/openapi/monitoring-api.json`) refreshed via `scripts/regen-from.sh`. The Camp-A uptime contract (component-centric tracking + the new `trackingSince` map on `StatusPageBatchComponentUptimeDto`) is already type-correct from the regenerated zod schemas — no hand-coded callers in this repo. Made-with: Cursor --- docs/openapi/monitoring-api.json | 16 ++++++++-------- src/commands/status-pages/groups/create.ts | 7 +++++-- src/commands/status-pages/groups/update.ts | 7 +++++-- src/lib/api-zod.generated.ts | 6 +++--- src/lib/api.generated.ts | 14 +++++++------- src/lib/yaml/handlers.ts | 8 ++++---- src/lib/yaml/schema.ts | 7 ++++++- src/lib/yaml/zod-schemas.ts | 2 +- test/yaml/partial-failure-convergence.test.ts | 10 +++++----- test/yaml/spec-field-parity.test.ts | 2 +- 10 files changed, 45 insertions(+), 34 deletions(-) diff --git a/docs/openapi/monitoring-api.json b/docs/openapi/monitoring-api.json index 3cde442..9e73bdb 100644 --- a/docs/openapi/monitoring-api.json +++ b/docs/openapi/monitoring-api.json @@ -22744,9 +22744,9 @@ "format": "int32", "nullable": true }, - "collapsed": { + "defaultOpen": { "type": "boolean", - "description": "Whether the group is collapsed by default (default: true)", + "description": "Initial expand/collapse state when a visitor first loads the page; renderer may auto-expand on active incidents (default: true)", "nullable": true } } @@ -22816,7 +22816,7 @@ }, "startDate": { "type": "string", - "description": "Date from which to start showing uptime data", + "description": "Date from which to start showing uptime; defaults to component creation. Set earlier to backdate (e.g. launch day); clamped at the monitor's createdAt for MONITOR-type components", "format": "date", "nullable": true } @@ -29659,7 +29659,7 @@ "updatedAt", "displayOrder", "pageOrder", - "collapsed" + "defaultOpen" ], "type": "object", "properties": { @@ -29686,7 +29686,7 @@ "type": "integer", "format": "int32" }, - "collapsed": { + "defaultOpen": { "type": "boolean" }, "components": { @@ -32405,9 +32405,9 @@ "format": "int32", "nullable": true }, - "collapsed": { + "defaultOpen": { "type": "boolean", - "description": "Whether the group is collapsed by default; null preserves current", + "description": "Initial expand/collapse state on first page load; null preserves current. Renderer may auto-expand on active incidents", "nullable": true } } @@ -32458,7 +32458,7 @@ }, "startDate": { "type": "string", - "description": "Date from which to start showing uptime data; null preserves current", + "description": "Date from which to start showing uptime; null preserves current. Bars never extend earlier than the underlying monitor's createdAt regardless of value", "format": "date", "nullable": true } diff --git a/src/commands/status-pages/groups/create.ts b/src/commands/status-pages/groups/create.ts index 226d3f9..bea7e10 100644 --- a/src/commands/status-pages/groups/create.ts +++ b/src/commands/status-pages/groups/create.ts @@ -11,7 +11,10 @@ export default class StatusPagesGroupsCreate extends Command { ...globalFlags, name: Flags.string({description: 'Group name', required: true}), description: Flags.string({description: 'Optional group description'}), - collapsed: Flags.boolean({description: 'Whether the group is collapsed by default', allowNo: true}), + 'default-open': Flags.boolean({ + description: 'Initial expand/collapse state on first page load (default: true). Use --no-default-open to seed collapsed.', + allowNo: true, + }), 'display-order': Flags.integer({description: 'Position in the group list'}), } @@ -20,7 +23,7 @@ export default class StatusPagesGroupsCreate extends Command { const client = buildClient(flags) const body: Record = {name: flags.name} if (flags.description) body.description = flags.description - if (flags.collapsed !== undefined) body.collapsed = flags.collapsed + if (flags['default-open'] !== undefined) body.defaultOpen = flags['default-open'] if (flags['display-order'] !== undefined) body.displayOrder = flags['display-order'] const resp = await apiPost(client, `/api/v1/status-pages/${args.id}/groups`, body) display(this, unwrapData(resp), flags.output) diff --git a/src/commands/status-pages/groups/update.ts b/src/commands/status-pages/groups/update.ts index f0d0cb0..d57be29 100644 --- a/src/commands/status-pages/groups/update.ts +++ b/src/commands/status-pages/groups/update.ts @@ -14,7 +14,10 @@ export default class StatusPagesGroupsUpdate extends Command { ...globalFlags, name: Flags.string({description: 'Group name'}), description: Flags.string({description: 'Group description'}), - collapsed: Flags.boolean({description: 'Whether the group is collapsed', allowNo: true}), + 'default-open': Flags.boolean({ + description: 'Initial expand/collapse state on first page load. Use --no-default-open to seed collapsed.', + allowNo: true, + }), 'display-order': Flags.integer({description: 'Position in the group list'}), } @@ -24,7 +27,7 @@ export default class StatusPagesGroupsUpdate extends Command { const body: Record = {} if (flags.name) body.name = flags.name if (flags.description !== undefined) body.description = flags.description - if (flags.collapsed !== undefined) body.collapsed = flags.collapsed + if (flags['default-open'] !== undefined) body.defaultOpen = flags['default-open'] if (flags['display-order'] !== undefined) body.displayOrder = flags['display-order'] const resp = await apiPut(client, `/api/v1/status-pages/${args.id}/groups/${args['group-id']}`, body) display(this, unwrapData(resp), flags.output) diff --git a/src/lib/api-zod.generated.ts b/src/lib/api-zod.generated.ts index 29b7703..00cb0e3 100644 --- a/src/lib/api-zod.generated.ts +++ b/src/lib/api-zod.generated.ts @@ -1082,7 +1082,7 @@ const CreateStatusPageComponentGroupRequest = z name: z.string().min(0).max(255), description: z.string().min(0).max(500).nullish(), displayOrder: z.number().int().nullish(), - collapsed: z.boolean().nullish(), + defaultOpen: z.boolean().nullish(), }) .strict(); const UpdateStatusPageComponentGroupRequest = z @@ -1090,7 +1090,7 @@ const UpdateStatusPageComponentGroupRequest = z name: z.string().min(0).max(255).nullable(), description: z.string().min(0).max(500).nullable(), displayOrder: z.number().int().nullable(), - collapsed: z.boolean().nullable(), + defaultOpen: z.boolean().nullable(), }) .partial() .strict(); @@ -2666,7 +2666,7 @@ const StatusPageComponentGroupDto = z description: z.string().nullish(), displayOrder: z.number().int(), pageOrder: z.number().int(), - collapsed: z.boolean(), + defaultOpen: z.boolean(), components: z.array(StatusPageComponentDto).nullish(), createdAt: z.string().datetime({ offset: true }), updatedAt: z.string().datetime({ offset: true }), diff --git a/src/lib/api.generated.ts b/src/lib/api.generated.ts index 553233d..693d519 100644 --- a/src/lib/api.generated.ts +++ b/src/lib/api.generated.ts @@ -3163,8 +3163,8 @@ export interface components { * @description Position in the group list */ displayOrder?: number | null; - /** @description Whether the group is collapsed by default (default: true) */ - collapsed?: boolean | null; + /** @description Initial expand/collapse state when a visitor first loads the page; renderer may auto-expand on active incidents (default: true) */ + defaultOpen?: boolean | null; }; CreateStatusPageComponentRequest: { /** @description Component display name */ @@ -3202,7 +3202,7 @@ export interface components { excludeFromOverall?: boolean | null; /** * Format: date - * @description Date from which to start showing uptime data + * @description Date from which to start showing uptime; defaults to component creation. Set earlier to backdate (e.g. launch day); clamped at the monitor's createdAt for MONITOR-type components */ startDate?: string | null; }; @@ -6110,7 +6110,7 @@ export interface components { displayOrder: number; /** Format: int32 */ pageOrder: number; - collapsed: boolean; + defaultOpen: boolean; components?: components["schemas"]["StatusPageComponentDto"][] | null; /** Format: date-time */ createdAt: string; @@ -6974,8 +6974,8 @@ export interface components { * @description New position in the group list; null preserves current */ displayOrder?: number | null; - /** @description Whether the group is collapsed by default; null preserves current */ - collapsed?: boolean | null; + /** @description Initial expand/collapse state on first page load; null preserves current. Renderer may auto-expand on active incidents */ + defaultOpen?: boolean | null; }; UpdateStatusPageComponentRequest: { /** @description New component name; null preserves current */ @@ -7000,7 +7000,7 @@ export interface components { excludeFromOverall?: boolean | null; /** * Format: date - * @description Date from which to start showing uptime data; null preserves current + * @description Date from which to start showing uptime; null preserves current. Bars never extend earlier than the underlying monitor's createdAt regardless of value */ startDate?: string | null; }; diff --git a/src/lib/yaml/handlers.ts b/src/lib/yaml/handlers.ts index 36938a6..f10ae0d 100644 --- a/src/lib/yaml/handlers.ts +++ b/src/lib/yaml/handlers.ts @@ -906,7 +906,7 @@ export function statusPageGroupDesiredSnapshot( return { name: yaml.name, description: yaml.description ?? null, - collapsed: yaml.collapsed ?? true, + defaultOpen: yaml.defaultOpen ?? true, } } @@ -938,18 +938,18 @@ function makeGroupCollectionDef( toCurrentSnapshot: (api) => ({ name: api.name ?? '', description: api.description ?? null, - collapsed: api.collapsed ?? true, + defaultOpen: api.defaultOpen ?? true, }), async applyCreate(parentId, yaml, index) { const resp = (await apiPost( client, `/api/v1/status-pages/${parentId}/groups`, - {name: yaml.name, description: yaml.description ?? null, displayOrder: index, collapsed: yaml.collapsed ?? true}, + {name: yaml.name, description: yaml.description ?? null, displayOrder: index, defaultOpen: yaml.defaultOpen ?? true}, )) as {data?: Schemas['StatusPageComponentGroupDto']} return String(resp.data?.id ?? '') }, async applyUpdate(parentId, childId, yaml, index) { await apiPut(client, `/api/v1/status-pages/${parentId}/groups/${childId}`, - {name: yaml.name, description: yaml.description ?? null, displayOrder: index, collapsed: yaml.collapsed ?? true}, + {name: yaml.name, description: yaml.description ?? null, displayOrder: index, defaultOpen: yaml.defaultOpen ?? true}, ) }, async applyDelete(parentId, childId) { diff --git a/src/lib/yaml/schema.ts b/src/lib/yaml/schema.ts index 343ee03..7ac082a 100644 --- a/src/lib/yaml/schema.ts +++ b/src/lib/yaml/schema.ts @@ -395,7 +395,12 @@ export interface YamlStatusPageBranding { export interface YamlStatusPageComponentGroup { name: string description?: string - collapsed?: boolean + /** + * Initial expand/collapse state when a visitor first loads the page. + * Defaults to `true` (group renders expanded). Renderer may auto-expand + * a collapsed group on active incidents. + */ + defaultOpen?: boolean } export interface YamlStatusPageComponent { diff --git a/src/lib/yaml/zod-schemas.ts b/src/lib/yaml/zod-schemas.ts index 0055cf2..0143cb7 100644 --- a/src/lib/yaml/zod-schemas.ts +++ b/src/lib/yaml/zod-schemas.ts @@ -412,7 +412,7 @@ const StatusPageBrandingSchema = z.object({ const StatusPageComponentGroupSchema = z.object({ name: z.string(), description: z.string().optional(), - collapsed: z.boolean().optional(), + defaultOpen: z.boolean().optional(), }).strict() const StatusPageComponentSchema = z.object({ diff --git a/test/yaml/partial-failure-convergence.test.ts b/test/yaml/partial-failure-convergence.test.ts index 9b7adfe..bf53913 100644 --- a/test/yaml/partial-failure-convergence.test.ts +++ b/test/yaml/partial-failure-convergence.test.ts @@ -145,7 +145,7 @@ interface FakeStatusPageGroup { name: string description: string | null displayOrder: number - collapsed: boolean + defaultOpen: boolean } interface FakeStatusPageComponent { @@ -320,11 +320,11 @@ class FakeApi { const pageGroupsMatch = path.match(/^\/api\/v1\/status-pages\/([^/]+)\/groups$/) if (pageGroupsMatch) { const pageId = pageGroupsMatch[1] - const reqBody = body as {name: string; description?: string | null; displayOrder: number; collapsed?: boolean} + const reqBody = body as {name: string; description?: string | null; displayOrder: number; defaultOpen?: boolean} const id = this.nextId('spg') const dto: FakeStatusPageGroup = { id, name: reqBody.name, description: reqBody.description ?? null, - displayOrder: reqBody.displayOrder, collapsed: reqBody.collapsed ?? true, + displayOrder: reqBody.displayOrder, defaultOpen: reqBody.defaultOpen ?? true, } this.statusPageGroups.get(pageId)!.set(id, dto) return {data: dto} @@ -372,11 +372,11 @@ class FakeApi { if (groupPut) { const group = this.statusPageGroups.get(groupPut[1])?.get(groupPut[2]) if (!group) throw new Error(`fake api: group ${groupPut[2]} not found`) - const reqBody = body as {name?: string; description?: string | null; displayOrder?: number; collapsed?: boolean} + const reqBody = body as {name?: string; description?: string | null; displayOrder?: number; defaultOpen?: boolean} if (reqBody.name !== undefined) group.name = reqBody.name if (reqBody.description !== undefined) group.description = reqBody.description ?? null if (reqBody.displayOrder !== undefined) group.displayOrder = reqBody.displayOrder - if (reqBody.collapsed !== undefined) group.collapsed = reqBody.collapsed + if (reqBody.defaultOpen !== undefined) group.defaultOpen = reqBody.defaultOpen return {data: group} } const compPut = path.match(/^\/api\/v1\/status-pages\/([^/]+)\/components\/([^/]+)$/) diff --git a/test/yaml/spec-field-parity.test.ts b/test/yaml/spec-field-parity.test.ts index adc6a0e..81ddc9e 100644 --- a/test/yaml/spec-field-parity.test.ts +++ b/test/yaml/spec-field-parity.test.ts @@ -257,7 +257,7 @@ const SNAPSHOT_COMPONENT_DTO_ONLY = [ 'displayOrder', 'pageOrder', 'currentStatus', 'createdAt', 'updatedAt', ] -const SNAPSHOT_GROUP_FIELDS = ['name', 'description', 'collapsed'] +const SNAPSHOT_GROUP_FIELDS = ['name', 'description', 'defaultOpen'] const SNAPSHOT_GROUP_DTO_ONLY = [ 'id', 'statusPageId', 'displayOrder', 'pageOrder',