feat(notices): bring the release notes back on every update

The release modal takes the 4.3.0 design: the release and its headline cards
on the left, the note and the support links on the right, at the design's own
size. The cards carry small drawings of the features instead of an icon.

The 4.0.0 entry is retired. In its place sits one entry, release-notes, that
every big release rewrites and keeps. It recurs per version with no upper
bound, so every user sees it once on each update, whatever they did with it
before, and a reload of the same version keeps it closed. An integration test
holds that promise through the real entry and the real dismiss route.

All 23 locales carry the new copy and drop the 4.0.0 strings.
This commit is contained in:
Maurice
2026-09-15 23:50:15 +02:00
parent 99783cc8da
commit 05ee3d50e4
34 changed files with 1562 additions and 1206 deletions
+51 -55
View File
@@ -31,92 +31,88 @@ export const RETIRED_NOTICE_IDS = [
'v3-mcp',
'v3-features',
'welcome-v1',
'release-4-0-0',
] as const;
export const SYSTEM_NOTICES: SystemNotice[] = [
// ── 4.0.0 release — what shipped, and a note from the maintainer ────────────
// Carries `release`, so it renders as the two-column release modal rather than
// the generic notice body. 4.0.0 was big enough that the whole 4.x line should
// carry it, so `recurring: 'per-version'` brings it back on each upgrade inside
// the window rather than the default one-time dismissal, which retired it for
// everyone who closed it once on 4.0.0. A 5.x release gets its own entry.
// ── Release notes: what the current release brought, and a note from the maintainer ──
// One entry for every release. Each big release swaps the copy (the release_notes
// keys) and the version below, and keeps this id. `recurring: 'per-version'` brings
// it back to every user on every upgrade, a patch release included, so nobody is
// ever left on a version with nothing to show. No upper bound for the same reason.
{
id: 'release-4-0-0',
id: 'release-notes',
recurring: 'per-version',
display: 'modal',
severity: 'info',
titleKey: 'system_notice.release_400.headline',
bodyKey: 'system_notice.release_400.intro',
titleKey: 'system_notice.release_notes.headline',
bodyKey: 'system_notice.release_notes.intro',
release: {
version: '4.0.0',
eyebrowKey: 'system_notice.release_400.eyebrow',
tagKey: 'system_notice.release_400.tag',
headlineKey: 'system_notice.release_400.headline',
introKey: 'system_notice.release_400.intro',
version: '4.3.0',
eyebrowKey: 'system_notice.release_notes.eyebrow',
headlineKey: 'system_notice.release_notes.headline',
introKey: 'system_notice.release_notes.intro',
featuresLabelKey: 'system_notice.release_notes.features_label',
featuresAsideKey: 'system_notice.release_notes.features_aside',
features: [
{
iconName: 'Smartphone',
titleKey: 'system_notice.release_400.feature_mobile_title',
bodyKey: 'system_notice.release_400.feature_mobile_body',
iconName: 'Database',
visual: 'places-api',
titleKey: 'system_notice.release_notes.feature_places_title',
bodyKey: 'system_notice.release_notes.feature_places_body',
},
{
iconName: 'BookOpen',
titleKey: 'system_notice.release_400.feature_studio_title',
bodyKey: 'system_notice.release_400.feature_studio_body',
badgeKey: 'system_notice.release_400.feature_studio_badge',
iconName: 'Route',
visual: 'roadtrip',
titleKey: 'system_notice.release_notes.feature_roadtrip_title',
bodyKey: 'system_notice.release_notes.feature_roadtrip_body',
},
{
iconName: 'CalendarDays',
titleKey: 'system_notice.release_400.feature_vacay_title',
bodyKey: 'system_notice.release_400.feature_vacay_body',
},
{
iconName: 'Image',
titleKey: 'system_notice.release_400.feature_places_title',
bodyKey: 'system_notice.release_400.feature_places_body',
iconName: 'MapPin',
visual: 'dawarich',
titleKey: 'system_notice.release_notes.feature_dawarich_title',
bodyKey: 'system_notice.release_notes.feature_dawarich_body',
},
],
// No stat row and no notes button here: the left column reads better short.
footnoteKey: 'system_notice.release_400.footnote',
note: {
eyebrowKey: 'system_notice.release_400.note_eyebrow',
titleKey: 'system_notice.release_400.note_title',
bodyKey: 'system_notice.release_400.note_body',
promiseLabelKey: 'system_notice.release_400.promise_label',
promiseTextKey: 'system_notice.release_400.promise_text',
bodyAfterKey: 'system_notice.release_400.note_body_after',
closingKey: 'system_notice.release_400.note_closing',
signatureKey: 'system_notice.release_400.note_signature',
footnoteKey: 'system_notice.release_notes.footnote',
notes: {
labelKey: 'system_notice.release_notes.notes_label',
href: 'https://github.com/liketrek/TREK/releases/tag/v4.3.0',
},
supportTextKey: 'system_notice.release_400.support_text',
note: {
eyebrowKey: 'system_notice.release_notes.note_eyebrow',
titleKey: 'system_notice.release_notes.note_title',
bodyKey: 'system_notice.release_notes.note_body',
promiseLabelKey: 'system_notice.release_notes.promise_label',
promiseLeadKey: 'system_notice.release_notes.promise_lead',
promiseTextKey: 'system_notice.release_notes.promise_text',
bodyAfterKey: 'system_notice.release_notes.note_body_after',
closingKey: 'system_notice.release_notes.note_closing',
},
supportLeadKey: 'system_notice.release_notes.support_lead',
supportTextKey: 'system_notice.release_notes.support_text',
},
cta: {
kind: 'link',
labelKey: 'system_notice.release_400.cta_bmc',
labelKey: 'system_notice.release_notes.cta_bmc',
href: 'https://buymeacoffee.com/mauriceboe',
},
secondaryCta: {
kind: 'link',
labelKey: 'system_notice.release_400.cta_kofi',
labelKey: 'system_notice.release_notes.cta_kofi',
href: 'https://ko-fi.com/mauriceboe',
},
dismissible: true,
// Desktop-only, like the thank-you modal it replaces: the two-column layout
// has no phone form, and the mobile release lands with its own onboarding.
// Desktop-only: the two-column layout has no phone form, and the phone has its
// own onboarding.
desktopOnly: true,
// Same reasoning as the thank-you notice below: it asks the reader to fund
// the project, and on a managed install they already pay whoever runs it.
// It asks the reader to fund the project, and on a managed install they already
// pay whoever runs it.
conditions: [{ kind: 'managed', is: false }],
publishedAt: '2026-08-22T00:00:00Z',
publishedAt: '2026-09-15T00:00:00Z',
priority: 110,
// The whole 4.x line. The copy is about what 4.0.0 brought, and that is still
// what somebody arriving anywhere on 4.x is being introduced to — the minor
// releases after it add to that picture rather than replace it. Held open to
// the 5.0.0 boundary (exclusive) so a 4.1/4.2 install is not left with no
// notice at all, which is what a per-release window did the moment 4.1.0
// shipped without an entry of its own.
// Where the thank-you notice below hands over; nothing on 3.x ships this copy.
minVersion: '4.0.0',
maxVersion: '5.0.0',
},
// ── Thank-you + support the project — shown once per install AND once per upgrade ──
+6 -10
View File
@@ -30,15 +30,10 @@ export type NoticeCta =
export interface NoticeReleaseFeature {
iconName: string;
/** A small illustration the client draws on the card; unknown names fall back to the icon. */
visual?: string;
titleKey: string;
bodyKey: string;
badgeKey?: string;
}
export interface NoticeReleaseStat {
/** The figure itself ("~150") — shown as-is, never translated. */
value: string;
labelKey: string;
}
/**
@@ -49,11 +44,11 @@ export interface NoticeReleaseStat {
export interface NoticeRelease {
version: string;
eyebrowKey: string;
tagKey: string;
headlineKey: string;
introKey: string;
featuresLabelKey: string;
featuresAsideKey?: string;
features: NoticeReleaseFeature[];
stats?: NoticeReleaseStat[];
notes?: { labelKey: string; href: string };
footnoteKey?: string;
note: {
@@ -61,11 +56,12 @@ export interface NoticeRelease {
titleKey: string;
bodyKey: string;
promiseLabelKey: string;
promiseLeadKey: string;
promiseTextKey: string;
bodyAfterKey: string;
closingKey: string;
signatureKey: string;
};
supportLeadKey: string;
supportTextKey: string;
}