feat(notices): add the 4.0.0 release modal

The thank-you modal was one column of markdown for every notice. A release
needs more room than that: what shipped, and why it took what it took.

Adds a `release` block to the notice contract. A notice carrying it renders
through ReleaseNoticeModal instead of the generic body — the release on the
left, the note from the maintainer on the right, both halves driven entirely
by translation keys so the next release only edits the registry entry.

Sizing lives on one CSS variable (--rn-s), so the panel scales as a piece and
the design's proportions hold at every breakpoint.

The thank-you notice gets maxVersion 4.0.0: from there on the release modal
carries the same thank-you and the same two support links, and installs still
on 3.x keep the old one.

All 23 locales.
This commit is contained in:
Maurice
2026-08-22 10:56:14 +02:00
committed by Maurice
parent 76c8c349b1
commit bb7c484ca4
31 changed files with 1914 additions and 0 deletions
+80
View File
@@ -34,6 +34,81 @@ export const RETIRED_NOTICE_IDS = [
] 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. Shown once, not per-version: the copy is about this
// release. The next one gets its own entry with its own id.
{
id: 'release-4-0-0',
display: 'modal',
severity: 'info',
titleKey: 'system_notice.release_400.headline',
bodyKey: 'system_notice.release_400.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',
features: [
{
iconName: 'Smartphone',
titleKey: 'system_notice.release_400.feature_mobile_title',
bodyKey: 'system_notice.release_400.feature_mobile_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: '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',
},
],
// 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',
},
supportTextKey: 'system_notice.release_400.support_text',
},
cta: {
kind: 'link',
labelKey: 'system_notice.release_400.cta_bmc',
href: 'https://buymeacoffee.com/mauriceboe',
},
secondaryCta: {
kind: 'link',
labelKey: 'system_notice.release_400.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.
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.
conditions: [{ kind: 'managed', is: false }],
publishedAt: '2026-08-22T00:00:00Z',
priority: 110,
minVersion: '4.0.0',
},
// ── Thank-you + support the project — shown once per install AND once per upgrade ──
// `recurring: 'per-version'` re-surfaces it whenever the app version moves up.
{
@@ -69,6 +144,11 @@ export const SYSTEM_NOTICES: SystemNotice[] = [
publishedAt: '2026-06-27T00:00:00Z',
priority: 100,
recurring: 'per-version',
// From 4.0.0 on, the release modal carries the same thank-you and the same
// two support links, so this one would be the second half of a message the
// reader just read. Retired by version rather than deleted: installs still
// on 3.x keep it.
maxVersion: '4.0.0',
},
// ── 3.0.14 admin notice — whitespace migration collision ───────────────────
+43
View File
@@ -28,6 +28,47 @@ export type NoticeCta =
| { kind: 'link'; labelKey: string; href: string } // external URL, opens in a new tab
| { kind: 'action'; labelKey: string; actionId: string; dismissOnAction?: boolean };
export interface NoticeReleaseFeature {
iconName: string;
titleKey: string;
bodyKey: string;
badgeKey?: string;
}
export interface NoticeReleaseStat {
/** The figure itself ("~150") — shown as-is, never translated. */
value: string;
labelKey: string;
}
/**
* The release layout: the release on the left, a note from the maintainer on
* the right. A notice carrying this renders through the dedicated two-column
* modal instead of the generic notice body.
*/
export interface NoticeRelease {
version: string;
eyebrowKey: string;
tagKey: string;
headlineKey: string;
introKey: string;
features: NoticeReleaseFeature[];
stats?: NoticeReleaseStat[];
notes?: { labelKey: string; href: string };
footnoteKey?: string;
note: {
eyebrowKey: string;
titleKey: string;
bodyKey: string;
promiseLabelKey: string;
promiseTextKey: string;
bodyAfterKey: string;
closingKey: string;
signatureKey: string;
};
supportTextKey: string;
}
export interface SystemNotice {
id: string;
display: Display;
@@ -40,6 +81,8 @@ export interface SystemNotice {
highlights?: Array<{ labelKey: string; iconName?: string }>;
cta?: NoticeCta;
secondaryCta?: NoticeCta;
/** Set to render the two-column release modal rather than the generic body. */
release?: NoticeRelease;
// Hide this notice on small/mobile viewports (evaluated client-side).
desktopOnly?: boolean;
dismissible: boolean;