feat(whats-new): remote release notes with dev workspace mode (#1058)

* feat(whats-new): remote release notes with dev workspace mode

Add WHATS_NEW.md, CI whats-new.md asset upload, and client fetch/cache
with embedded fallbacks. Dev and -dev builds read the full file from the
repo for debugging; RC/stable download the release asset on first use.

* fix(whats-new): render ## headings and add changelog tab

Parse h2 sections in release-notes markdown; load changelog alongside
highlights and let users switch views on the What's New page.

* fix(whats-new): prefetch on startup and fix CI typecheck prebuild

Prefetch whats-new asset when the shell loads on RC/stable builds.
Run prebuild:release-notes before tsc and coverage jobs so the
gitignored generated bundle exists in CI.

* docs: CHANGELOG and credits for What's New remote notes (PR #1058)

* fix(whats-new): always slice embedded release notes to current line

Drop full CHANGELOG embed for -dev bundles; tauri:dev still reads live
markdown from the repo. Ignore all of src/generated/ in git.

* fix(whats-new): fetch release asset via Rust to bypass CORS

Route whats-new.md download through fetch_url_bytes; rename the
technical tab label; add fetch unit tests (PR #1058 review).
This commit is contained in:
cucadmuh
2026-06-10 23:35:23 +03:00
committed by GitHub
parent fb5a257735
commit c7d71ea57c
38 changed files with 956 additions and 186 deletions
+17
View File
@@ -0,0 +1,17 @@
import { useEffect } from 'react';
import { version as appVersion } from '../../package.json';
import { isWorkspaceReleaseNotesMode } from '../utils/releaseNotes/releaseNotesChannel';
import { resolveReleaseNotes } from '../utils/releaseNotes/releaseNotesResolve';
/**
* Warm the release-notes cache after an update (RC/stable only).
* Runs once per app session; resolveReleaseNotes skips network when cached.
*/
export function usePrefetchReleaseNotes(version: string = appVersion): void {
useEffect(() => {
if (isWorkspaceReleaseNotesMode(version)) return;
void resolveReleaseNotes(version).catch(() => {
/* offline or missing asset — embedded fallback loads on /whats-new */
});
}, [version]);
}
+50
View File
@@ -0,0 +1,50 @@
import { useEffect, useState } from 'react';
import { version as appVersion } from '../../package.json';
import {
resolveChangelogEntry,
resolveReleaseNotes,
type ReleaseNotesSource,
} from '../utils/releaseNotes/releaseNotesResolve';
import type { ReleaseNotesEntry } from '../utils/releaseNotes/releaseNotesMatch';
export interface UseReleaseNotesResult {
loading: boolean;
whatsNewEntry: ReleaseNotesEntry | null;
changelogEntry: ReleaseNotesEntry | null;
whatsNewSource: ReleaseNotesSource;
}
export function useReleaseNotes(version: string = appVersion): UseReleaseNotesResult {
const [loading, setLoading] = useState(true);
const [whatsNewEntry, setWhatsNewEntry] = useState<ReleaseNotesEntry | null>(null);
const [changelogEntry, setChangelogEntry] = useState<ReleaseNotesEntry | null>(null);
const [whatsNewSource, setWhatsNewSource] = useState<ReleaseNotesSource>('empty');
useEffect(() => {
let cancelled = false;
setLoading(true);
Promise.all([resolveReleaseNotes(version), resolveChangelogEntry(version)])
.then(([whatsNew, changelog]) => {
if (cancelled) return;
setWhatsNewEntry(whatsNew.entry);
setWhatsNewSource(whatsNew.source);
setChangelogEntry(changelog.entry);
})
.catch(() => {
if (cancelled) return;
setWhatsNewEntry(null);
setChangelogEntry(null);
setWhatsNewSource('empty');
})
.finally(() => {
if (!cancelled) setLoading(false);
});
return () => {
cancelled = true;
};
}, [version]);
return { loading, whatsNewEntry, changelogEntry, whatsNewSource };
}