From 602fae8bb52c8e8afbbeb91a8462db4df9a7b050 Mon Sep 17 00:00:00 2001 From: Brendan Kellam Date: Mon, 25 May 2026 14:14:13 -0700 Subject: [PATCH 1/4] changelog impl --- .../migration.sql | 14 ++ packages/db/prisma/schema.prisma | 15 ++ packages/shared/src/env.server.ts | 4 + .../components/changelogEntryDialog.tsx | 168 +++++++++++++++ .../components/defaultSidebar/index.tsx | 1 - .../components/settingsSidebar/index.tsx | 6 - .../(app)/@sidebar/components/sidebarBase.tsx | 3 +- .../components/whatsNewSidebarButton.tsx | 194 +++++++++--------- packages/web/src/app/api/(client)/client.ts | 12 ++ .../api/(server)/changelog/entries/route.ts | 15 ++ .../src/features/changelog/listEntriesApi.ts | 41 ++++ .../src/features/changelog/pollChangelog.ts | 83 ++++++++ packages/web/src/features/changelog/types.ts | 17 ++ packages/web/src/initialize.ts | 2 + packages/web/src/lib/newsData.ts | 88 -------- 15 files changed, 468 insertions(+), 195 deletions(-) create mode 100644 packages/db/prisma/migrations/20260525181404_add_changelog_table/migration.sql create mode 100644 packages/web/src/app/(app)/@sidebar/components/changelogEntryDialog.tsx create mode 100644 packages/web/src/app/api/(server)/changelog/entries/route.ts create mode 100644 packages/web/src/features/changelog/listEntriesApi.ts create mode 100644 packages/web/src/features/changelog/pollChangelog.ts create mode 100644 packages/web/src/features/changelog/types.ts delete mode 100644 packages/web/src/lib/newsData.ts diff --git a/packages/db/prisma/migrations/20260525181404_add_changelog_table/migration.sql b/packages/db/prisma/migrations/20260525181404_add_changelog_table/migration.sql new file mode 100644 index 000000000..e3e7ee8e3 --- /dev/null +++ b/packages/db/prisma/migrations/20260525181404_add_changelog_table/migration.sql @@ -0,0 +1,14 @@ +-- CreateTable +CREATE TABLE "ChangelogEntry" ( + "slug" TEXT NOT NULL, + "title" TEXT NOT NULL, + "publishedAt" TIMESTAMP(3) NOT NULL, + "summary" TEXT, + "bodyMarkdown" TEXT NOT NULL, + "fetchedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "ChangelogEntry_pkey" PRIMARY KEY ("slug") +); + +-- CreateIndex +CREATE INDEX "ChangelogEntry_publishedAt_idx" ON "ChangelogEntry"("publishedAt"); diff --git a/packages/db/prisma/schema.prisma b/packages/db/prisma/schema.prisma index 140c15b44..8c60236a4 100644 --- a/packages/db/prisma/schema.prisma +++ b/packages/db/prisma/schema.prisma @@ -623,3 +623,18 @@ model OAuthToken { createdAt DateTime @default(now()) lastUsedAt DateTime? } + +/// Local cache of changelog entries fetched from the public feed at +/// `CHANGELOG_FEED_URL`. Shared across all users of an instance. +model ChangelogEntry { + slug String @id + title String + publishedAt DateTime + summary String? + bodyMarkdown String + + /// Updated each time the entry is upserted from the feed. + fetchedAt DateTime @default(now()) @updatedAt + + @@index([publishedAt]) +} diff --git a/packages/shared/src/env.server.ts b/packages/shared/src/env.server.ts index 036655018..d9ee5cae3 100644 --- a/packages/shared/src/env.server.ts +++ b/packages/shared/src/env.server.ts @@ -221,6 +221,10 @@ const options = { // Misc UI flags SECURITY_CARD_ENABLED: booleanSchema.default('false'), + // Changelog feed + CHANGELOG_ENABLED: booleanSchema.default('true'), + CHANGELOG_FEED_URL: z.string().url().default('https://static.sourcebot.dev/changelog/index.json'), + // EE License SOURCEBOT_EE_LICENSE_KEY: z.string().optional(), SOURCEBOT_EE_AUDIT_LOGGING_ENABLED: booleanSchema.default('true'), diff --git a/packages/web/src/app/(app)/@sidebar/components/changelogEntryDialog.tsx b/packages/web/src/app/(app)/@sidebar/components/changelogEntryDialog.tsx new file mode 100644 index 000000000..f985fe817 --- /dev/null +++ b/packages/web/src/app/(app)/@sidebar/components/changelogEntryDialog.tsx @@ -0,0 +1,168 @@ +"use client"; + +import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog"; +import type { ChangelogEntryDto } from "@/features/changelog/listEntriesApi"; +import { cn } from "@/lib/utils"; +import { format } from "date-fns"; +import { useEffect, useMemo, useState } from "react"; +import { createPortal } from "react-dom"; +import Markdown from "react-markdown"; +import rehypeRaw from "rehype-raw"; +import rehypeSanitize, { defaultSchema } from "rehype-sanitize"; +import remarkGfm from "remark-gfm"; + +const VIDEO_EXTENSIONS_RE = /\.(mp4|webm|ogg|mov)$/i; +const ABSOLUTE_URL_RE = /^(?:[a-z][a-z0-9+\-.]*:|\/\/|#)/i; + +// Allow