feat(updates): "What's New" highlights after update + pre-update teaser

Surfaces release highlights to admins, sourced from the GitHub release notes
(no AI at runtime). Bullets are written once per release in CI via GitHub Models
(see docs/ci/whatsnew-highlights.yml) into a <!-- whatsnew --> block; the app
reads that block and falls back to the changelog's "### Features" for releases
without it — so it works against today's releases immediately.

- backend utils/whatsNew.parseWhatsNew(body): curated block else Features
  section, strips scope/PR-links, de-dups, caps at 8 (tested).
- GET  /admin/system/updates/whatsnew: highlights for every version moved
  through since the per-instance marker (whatsnew_last_seen_version); fresh
  installs self-anchor silently. Best-effort, never errors.
- POST /admin/system/updates/whatsnew/seen: advance the marker (per-instance).
- /admin/system/updates also returns latestHighlights for the teaser.
- Frontend: WhatsNewBanner (green bar -> modal with "Full changelog" link) on
  the dashboard via adminService; UpdateNotification shows a "New features
  include:" teaser. i18n de/en. No migration (uses app_settings).
This commit is contained in:
Luca
2026-06-30 02:31:13 +02:00
parent a24821de55
commit 500cf8522e
10 changed files with 463 additions and 2 deletions
+26
View File
@@ -341,6 +341,22 @@ export interface AnalyticsData {
devicesSource?: 'umami' | 'access_logs';
}
export interface WhatsNewVersion {
version: string;
name: string;
htmlUrl: string;
publishedAt: string;
bullets: string[];
}
export interface WhatsNewResponse {
enabled: boolean;
hasNews: boolean;
fromVersion?: string;
toVersion?: string;
versions?: WhatsNewVersion[];
}
export const adminService = {
// Dashboard statistics
async getDashboardStats(): Promise<DashboardStats> {
@@ -370,6 +386,16 @@ export const adminService = {
return response.data;
},
// After-update "What's New" highlights (per-instance acknowledgement).
async getWhatsNew(): Promise<WhatsNewResponse> {
const response = await api.get<WhatsNewResponse>('/admin/system/updates/whatsnew');
return response.data;
},
async markWhatsNewSeen(): Promise<void> {
await api.post('/admin/system/updates/whatsnew/seen');
},
// Backup-integrity verifier (read-only diagnostic). `scope` filters
// which document classes to walk; omit for a full scan.
async getBackupIntegrity(