From dcbe0e7b17a36d633976e6a7cf0b36a3066a1566 Mon Sep 17 00:00:00 2001 From: Maximilian Kaske Date: Thu, 8 Oct 2026 15:32:07 +0200 Subject: [PATCH] refactor(maintenance): move the message into the update timeline A maintenance now works like a status report: creating one posts the message as the first `maintenance_update`, `message` on read is the newest update, `message` on update rewrites the newest one, and the last update cannot be deleted. The `maintenance.message` column stays as a create-only mirror until a follow-up drops it; migration 0092 backfills one update per existing row. - services: create returns `{ maintenance, initialUpdate }`, list/get derive `message`, `latestMaintenanceUpdate` helper - RPC v2 / v1 REST / agent tools keep their fields with the new meaning; v1 get/list/put derive and rewrite via `v1/maintenances/updates.ts` - ui blocks render updates oldest-first, `message` optional as fallback - status page feeds and markdown list updates, dispatcher uses the newest - importers date the first update at the window start - dashboard: single timeline composer, message only in the create sheet - docs, seed, OpenAPI v1 document, tests --- .../[id]/maintenances/(list)/page.tsx | 2 +- .../maintenances/[maintenanceId]/client.tsx | 32 +-- .../maintenances/data-table-row-actions.tsx | 2 - .../src/components/forms/maintenance/form.tsx | 76 +++--- .../forms/maintenance/sheet-create.tsx | 2 +- .../maintenances/maintenance-composer.tsx | 79 ------ .../maintenance-update-composer.tsx | 2 +- .../maintenances/use-update-maintenance.ts | 1 - .../dashboard/src/data/maintenances.client.ts | 1 - .../maintenance/__tests__/maintenance.test.ts | 33 ++- .../routes/rpc/handlers/maintenance/index.ts | 2 +- .../maintenance-updates.test.ts | 6 +- apps/server/src/routes/v1/maintenances/get.ts | 4 +- .../src/routes/v1/maintenances/get_all.ts | 4 +- .../server/src/routes/v1/maintenances/post.ts | 17 +- apps/server/src/routes/v1/maintenances/put.ts | 12 +- .../src/routes/v1/maintenances/schema.ts | 3 +- .../src/routes/v1/maintenances/updates.ts | 54 ++++ apps/server/static/openapi-v1.json | 14 +- .../[locale]/(public)/feed/[type]/route.ts | 11 +- .../[locale]/(public)/feed/json/route.ts | 2 +- .../src/content/markdown/generators.test.ts | 8 +- .../src/content/markdown/generators.ts | 18 +- .../pages/docs/reference/maintenance.mdx | 6 +- deno.lock | 258 ------------------ packages/api/src/router/maintenance.test.ts | 11 +- packages/api/src/router/maintenance.ts | 6 +- packages/db/drizzle/0092_yielding_banshee.sql | 7 +- .../db/src/schema/maintenances/maintenance.ts | 2 + packages/db/src/seed.mts | 29 ++ .../maintenance/v1/maintenance.proto | 4 +- .../openstatus/maintenance/v1/service.proto | 5 +- .../services/src/agent-tools/maintenance.ts | 6 +- packages/services/src/import/phase-writers.ts | 11 + .../maintenance/__tests__/attribution.test.ts | 3 +- .../maintenance/__tests__/maintenance.test.ts | 119 ++++++-- packages/services/src/maintenance/create.ts | 34 ++- .../services/src/maintenance/delete-update.ts | 15 +- packages/services/src/maintenance/index.ts | 3 +- packages/services/src/maintenance/list.ts | 7 +- packages/services/src/maintenance/schemas.ts | 4 + packages/services/src/maintenance/update.ts | 77 +++++- packages/services/src/maintenance/utils.ts | 18 ++ packages/subscriptions/src/dispatcher.ts | 7 +- .../src/components/blocks/status-events.tsx | 18 +- .../ui/src/components/blocks/status.types.ts | 3 +- 46 files changed, 538 insertions(+), 500 deletions(-) delete mode 100644 apps/dashboard/src/components/maintenances/maintenance-composer.tsx create mode 100644 apps/server/src/routes/v1/maintenances/updates.ts create mode 100644 packages/services/src/maintenance/utils.ts diff --git a/apps/dashboard/src/app/(dashboard)/status-pages/[id]/maintenances/(list)/page.tsx b/apps/dashboard/src/app/(dashboard)/status-pages/[id]/maintenances/(list)/page.tsx index 0658fb2f5..fa4a2068b 100644 --- a/apps/dashboard/src/app/(dashboard)/status-pages/[id]/maintenances/(list)/page.tsx +++ b/apps/dashboard/src/app/(dashboard)/status-pages/[id]/maintenances/(list)/page.tsx @@ -81,7 +81,7 @@ export default function Page() { await createMaintenanceMutation.mutateAsync({ pageId: Number.parseInt(id), title: values.title, - message: values.message, + message: values.message ?? "", startDate: values.startDate, endDate: values.endDate, pageComponents: values.pageComponents, diff --git a/apps/dashboard/src/app/(dashboard)/status-pages/[id]/maintenances/[maintenanceId]/client.tsx b/apps/dashboard/src/app/(dashboard)/status-pages/[id]/maintenances/[maintenanceId]/client.tsx index 280f84eb3..846481c4d 100644 --- a/apps/dashboard/src/app/(dashboard)/status-pages/[id]/maintenances/[maintenanceId]/client.tsx +++ b/apps/dashboard/src/app/(dashboard)/status-pages/[id]/maintenances/[maintenanceId]/client.tsx @@ -22,7 +22,6 @@ import { SectionGroup } from "@/components/content/section"; import { Timeline } from "@/components/content/timeline"; import { MaintenanceActions } from "@/components/maintenances/maintenance-actions"; import { MaintenanceComponents } from "@/components/maintenances/maintenance-components"; -import { MaintenanceComposer } from "@/components/maintenances/maintenance-composer"; import { MaintenanceProperties } from "@/components/maintenances/maintenance-properties"; import { MaintenanceUpdateComposer } from "@/components/maintenances/maintenance-update-composer"; import { MaintenanceUpdateTimelineItem } from "@/components/maintenances/maintenance-update-timeline"; @@ -99,27 +98,20 @@ export function Client({ id, pageId }: { id: number; pageId: number }) { - - Message - - - - Updates - - + + {updates.map((update, i) => ( + - {updates.map((update, i) => ( - - ))} - - + ))} + diff --git a/apps/dashboard/src/components/data-table/maintenances/data-table-row-actions.tsx b/apps/dashboard/src/components/data-table/maintenances/data-table-row-actions.tsx index c6ebdffaa..9bab71caa 100644 --- a/apps/dashboard/src/components/data-table/maintenances/data-table-row-actions.tsx +++ b/apps/dashboard/src/components/data-table/maintenances/data-table-row-actions.tsx @@ -69,7 +69,6 @@ export function MaintenanceRowActions({ )} defaultValues={{ title: maintenance.title, - message: maintenance.message, startDate: maintenance.from, endDate: maintenance.to, pageComponents: maintenance.pageComponents?.map((c) => c.id) ?? [], @@ -78,7 +77,6 @@ export function MaintenanceRowActions({ await updateMaintenanceMutation.mutateAsync({ id: maintenance.id, title: values.title, - message: values.message, startDate: values.startDate, endDate: values.endDate, pageComponents: values.pageComponents, diff --git a/apps/dashboard/src/components/forms/maintenance/form.tsx b/apps/dashboard/src/components/forms/maintenance/form.tsx index b61acbf7e..34bbefd49 100644 --- a/apps/dashboard/src/components/forms/maintenance/form.tsx +++ b/apps/dashboard/src/components/forms/maintenance/form.tsx @@ -46,7 +46,8 @@ import { useTRPC } from "@/lib/trpc/client"; const schema = z .object({ title: z.string().trim().min(1, "Title is required"), - message: z.string(), + /** Create only: becomes the first timeline update. */ + message: z.string().trim().min(1, "Message is required").optional(), startDate: z.date(), endDate: z.date(), pageComponents: z.array(z.number()), @@ -215,39 +216,46 @@ export function FormMaintenance({ )} /> - - - - - Writing - Preview - - - ( - - Message - -