diff --git a/src/App.tsx b/src/App.tsx
index 5207df86..48e2b37f 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -38,6 +38,7 @@ import InternetRadio from './pages/InternetRadio';
import FolderBrowser from './pages/FolderBrowser';
import DeviceSync from './pages/DeviceSync';
import NowPlayingPage from './pages/NowPlaying';
+import WhatsNew from './pages/WhatsNew';
import MiniPlayer from './components/MiniPlayer';
import { initMiniPlayerBridgeOnMain } from './utils/miniPlayerBridge';
import FullscreenPlayer from './components/FullscreenPlayer';
@@ -53,7 +54,6 @@ import OfflineLibrary from './pages/OfflineLibrary';
import Genres from './pages/Genres';
import GenreDetail from './pages/GenreDetail';
import ExportPickerModal from './components/ExportPickerModal';
-import ChangelogModal from './components/ChangelogModal';
import AppUpdater from './components/AppUpdater';
import TitleBar from './components/TitleBar';
import { IS_LINUX } from './utils/platform';
@@ -229,14 +229,9 @@ function AppShell() {
fn();
}, [currentTrack, isPlaying]);
- const [changelogModalOpen, setChangelogModalOpen] = useState(false);
-
- useEffect(() => {
- const { showChangelogOnUpdate, lastSeenChangelogVersion } = useAuthStore.getState();
- if (showChangelogOnUpdate && lastSeenChangelogVersion !== version) {
- setChangelogModalOpen(true);
- }
- }, []);
+ // Post-update changelog is now surfaced via a dismissible banner in the
+ // sidebar (WhatsNewBanner) that links to the /whats-new page — no auto
+ // modal takeover on startup.
const [isSidebarCollapsed, setIsSidebarCollapsed] = useState(() => {
return localStorage.getItem('psysonic_sidebar_collapsed') === 'true';
@@ -402,6 +397,7 @@ function AppShell() {
} />
: } />
} />
+ } />
} />
} />
} />
@@ -436,7 +432,6 @@ function AppShell() {
- {changelogModalOpen && setChangelogModalOpen(false)} />}
);
}
diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx
index 44bd4286..6a11286f 100644
--- a/src/components/Sidebar.tsx
+++ b/src/components/Sidebar.tsx
@@ -15,6 +15,7 @@ import {
} from 'lucide-react';
import PsysonicLogo from './PsysonicLogo';
import PSmallLogo from './PSmallLogo';
+import WhatsNewBanner from './WhatsNewBanner';
import { getPlaylists } from '../api/subsonic';
import { usePlaylistStore } from '../store/playlistStore';
import { ALL_NAV_ITEMS } from '../config/navItems';
@@ -294,13 +295,18 @@ export default function Sidebar({
)
))}
+ {/* Spacer: everything from here onward sticks to the bottom of the sidebar. */}
+
+
+ {/* What's New banner — only visible while the current release hasn't been seen. */}
+
+
{/* Now Playing — fixed, always visible */}
`nav-link nav-link-nowplaying ${isActive ? 'active' : ''}`}
data-tooltip={isCollapsed ? t('sidebar.nowPlaying') : undefined}
data-tooltip-pos="bottom"
- style={{ marginTop: 'auto' }}
>
diff --git a/src/components/WhatsNewBanner.tsx b/src/components/WhatsNewBanner.tsx
new file mode 100644
index 00000000..88a09e14
--- /dev/null
+++ b/src/components/WhatsNewBanner.tsx
@@ -0,0 +1,65 @@
+import React from 'react';
+import { useNavigate } from 'react-router-dom';
+import { Sparkles, X } from 'lucide-react';
+import { useTranslation } from 'react-i18next';
+import { version } from '../../package.json';
+import { useAuthStore } from '../store/authStore';
+
+interface Props {
+ collapsed?: boolean;
+}
+
+/**
+ * Sidebar pill shown above Now Playing while the current app version hasn't
+ * been opened yet. Clicking opens the What's New page; X dismisses.
+ *
+ * Uses a fixed neutral palette so it looks identical across every theme.
+ */
+export default function WhatsNewBanner({ collapsed }: Props) {
+ const { t } = useTranslation();
+ const navigate = useNavigate();
+ const lastSeen = useAuthStore(s => s.lastSeenChangelogVersion);
+ const setLastSeen = useAuthStore(s => s.setLastSeenChangelogVersion);
+ const showOnUpdate = useAuthStore(s => s.showChangelogOnUpdate);
+
+ if (!showOnUpdate || lastSeen === version) return null;
+
+ const dismiss = (e: React.MouseEvent) => {
+ e.stopPropagation();
+ setLastSeen(version);
+ };
+
+ const open = () => navigate('/whats-new');
+
+ if (collapsed) {
+ return (
+
+ );
+ }
+
+ return (
+
+ );
+}
diff --git a/src/locales/de.ts b/src/locales/de.ts
index c15c4e42..cec68616 100644
--- a/src/locales/de.ts
+++ b/src/locales/de.ts
@@ -599,11 +599,13 @@ export const deTranslation = {
aboutVersion: 'Version',
aboutBuiltWith: 'Gebaut mit Tauri · React · TypeScript · Rust/rodio',
aboutAiCredit: 'Mit freundlicher Unterstützung von Claude Code by Anthropic',
+ aboutReleaseNotesLabel: 'Release Notes',
+ aboutReleaseNotesLink: 'Neuigkeiten dieser Version öffnen',
aboutContributorsLabel: 'Mitwirkende',
aboutSpecialThanksLabel: 'Besonderer Dank',
changelog: 'Changelog',
showChangelogOnUpdate: "'Was ist neu' bei Update anzeigen",
- showChangelogOnUpdateDesc: 'Zeigt automatisch die Neuerungen an, wenn eine neue Version zum ersten Mal gestartet wird.',
+ showChangelogOnUpdateDesc: 'Blendet nach einem Update einen dezenten Changelog-Banner über Now Playing ein. Klick öffnet die Release Notes, X blendet ihn aus.',
randomMixTitle: 'Zufallsmix',
randomMixBlacklistTitle: 'Eigene Filter-Keywords',
randomMixBlacklistDesc: 'Songs werden ausgeschlossen, wenn ein Keyword auf Genre, Titel, Album oder Künstler zutrifft (aktiv wenn die Checkbox oben an ist).',
@@ -739,6 +741,14 @@ export const deTranslation = {
dontShowAgain: 'Nicht mehr anzeigen',
close: 'Verstanden',
},
+ whatsNew: {
+ title: 'Was ist neu',
+ empty: 'Für diese Version liegt noch kein Changelog-Eintrag vor.',
+ bannerTitle: 'Changelog',
+ bannerCollapsed: 'Neu in v{{version}}',
+ dismiss: 'Ausblenden',
+ close: 'Schließen',
+ },
help: {
title: 'Hilfe',
s1: 'Erste Schritte',
diff --git a/src/locales/en.ts b/src/locales/en.ts
index ceee2906..c0847f43 100644
--- a/src/locales/en.ts
+++ b/src/locales/en.ts
@@ -601,11 +601,13 @@ export const enTranslation = {
aboutVersion: 'Version',
aboutBuiltWith: 'Built with Tauri · React · TypeScript · Rust/rodio',
aboutAiCredit: 'Developed with the support of Claude Code by Anthropic',
+ aboutReleaseNotesLabel: 'Release notes',
+ aboutReleaseNotesLink: "Open this version's what's-new",
aboutContributorsLabel: 'Contributors',
aboutSpecialThanksLabel: 'Special Thanks',
changelog: 'Changelog',
showChangelogOnUpdate: "Show 'What's New' on update",
- showChangelogOnUpdateDesc: "Automatically show what's new when a new version is launched for the first time.",
+ showChangelogOnUpdateDesc: "Show a discreet changelog banner above Now Playing after an update. Click opens the release notes; X dismisses it.",
randomMixTitle: 'Random Mix',
randomMixBlacklistTitle: 'Custom Filter Keywords',
randomMixBlacklistDesc: 'Songs are excluded when any keyword matches their genre, title, album, or artist (active when the checkbox above is on).',
@@ -741,6 +743,14 @@ export const enTranslation = {
dontShowAgain: "Don't show again",
close: 'Got it',
},
+ whatsNew: {
+ title: "What's New",
+ empty: 'No changelog entry for this version yet.',
+ bannerTitle: 'Changelog',
+ bannerCollapsed: "What's new in v{{version}}",
+ dismiss: 'Dismiss',
+ close: 'Close',
+ },
help: {
title: 'Help',
s1: 'Getting Started',
diff --git a/src/locales/es.ts b/src/locales/es.ts
index 779187d4..9fe6ab63 100644
--- a/src/locales/es.ts
+++ b/src/locales/es.ts
@@ -592,11 +592,13 @@ export const esTranslation = {
aboutVersion: 'Versión',
aboutBuiltWith: 'Construido con Tauri · React · TypeScript · Rust/rodio',
aboutAiCredit: 'Desarrollado con el apoyo de Claude Code by Anthropic',
+ aboutReleaseNotesLabel: 'Notas de la versión',
+ aboutReleaseNotesLink: 'Abrir las novedades de esta versión',
aboutContributorsLabel: 'Contribuidores',
aboutSpecialThanksLabel: 'Agradecimientos Especiales',
changelog: 'Registro de Cambios',
showChangelogOnUpdate: "Mostrar 'Novedades' al actualizar",
- showChangelogOnUpdateDesc: "Muestra automáticamente las novedades cuando se lanza una nueva versión por primera vez.",
+ showChangelogOnUpdateDesc: 'Muestra un discreto banner de changelog encima de Now Playing tras una actualización. Clic abre las notas de la versión; X lo oculta.',
randomMixTitle: 'Mezcla Aleatoria',
randomMixBlacklistTitle: 'Palabras Clave de Filtro Personalizadas',
randomMixBlacklistDesc: 'Las canciones se excluyen cuando cualquier palabra clave coincide con su género, título, álbum o artista (activo cuando el checkbox de arriba está activado).',
@@ -732,6 +734,14 @@ export const esTranslation = {
dontShowAgain: 'No mostrar de nuevo',
close: 'Entendido',
},
+ whatsNew: {
+ title: 'Novedades',
+ empty: 'Aún no hay entrada de changelog para esta versión.',
+ bannerTitle: 'Changelog',
+ bannerCollapsed: 'Novedades en v{{version}}',
+ dismiss: 'Ocultar',
+ close: 'Cerrar',
+ },
help: {
title: 'Ayuda',
s1: 'Primeros Pasos',
diff --git a/src/locales/fr.ts b/src/locales/fr.ts
index 5f271128..519c0139 100644
--- a/src/locales/fr.ts
+++ b/src/locales/fr.ts
@@ -587,11 +587,13 @@ export const frTranslation = {
aboutVersion: 'Version',
aboutBuiltWith: 'Construit avec Tauri · React · TypeScript · Rust/rodio',
aboutAiCredit: 'Développé avec le soutien de Claude Code par Anthropic',
+ aboutReleaseNotesLabel: 'Notes de version',
+ aboutReleaseNotesLink: 'Ouvrir les nouveautés de cette version',
aboutContributorsLabel: 'Contributeurs',
aboutSpecialThanksLabel: 'Remerciements spéciaux',
changelog: 'Journal des modifications',
showChangelogOnUpdate: "Afficher 'Quoi de neuf' lors des mises à jour",
- showChangelogOnUpdateDesc: 'Affiche automatiquement les nouveautés au premier lancement d\'une nouvelle version.',
+ showChangelogOnUpdateDesc: "Affiche une bannière discrète du changelog au-dessus de Now Playing après une mise à jour. Un clic ouvre les notes de version, le X la masque.",
randomMixTitle: 'Mix aléatoire',
randomMixBlacklistTitle: 'Mots-clés de filtre personnalisés',
randomMixBlacklistDesc: 'Les morceaux sont exclus si un mot-clé correspond à leur genre, titre, album ou artiste (actif quand la case ci-dessus est cochée).',
@@ -727,6 +729,14 @@ export const frTranslation = {
dontShowAgain: 'Ne plus afficher',
close: 'Compris',
},
+ whatsNew: {
+ title: 'Quoi de neuf',
+ empty: 'Aucune entrée de changelog pour cette version.',
+ bannerTitle: 'Changelog',
+ bannerCollapsed: 'Nouveau dans v{{version}}',
+ dismiss: 'Ignorer',
+ close: 'Fermer',
+ },
help: {
title: 'Aide',
s1: 'Démarrage',
diff --git a/src/locales/nb.ts b/src/locales/nb.ts
index 78f46604..a2218e33 100644
--- a/src/locales/nb.ts
+++ b/src/locales/nb.ts
@@ -586,11 +586,13 @@ export const nbTranslation = {
aboutVersion: 'Versjon',
aboutBuiltWith: 'Bygget med Tauri · React · TypeScript · Rust/rodio',
aboutAiCredit: 'Utviklet med støtte fra Claude Code laget av Anthropic',
+ aboutReleaseNotesLabel: 'Versjonsnotater',
+ aboutReleaseNotesLink: 'Åpne nyhetene for denne versjonen',
aboutContributorsLabel: 'Bidragsytere',
aboutSpecialThanksLabel: 'Spesiell takk',
changelog: 'Endringslogg',
showChangelogOnUpdate: "Vis 'Hva er nytt' ved oppdatering til ny versjon",
- showChangelogOnUpdateDesc: "Vis automatisk hva som er nytt når en ny versjon lanseres for første gang.",
+ showChangelogOnUpdateDesc: "Viser et diskret changelog-banner over Now Playing etter en oppdatering. Klikk åpner versjonsnotatene; X skjuler det.",
randomMixTitle: 'Tilfeldig miks',
randomMixBlacklistTitle: 'Egendefinerte filternøkkelord',
randomMixBlacklistDesc: 'Sanger ekskluderes når et nøkkelord samsvarer med sjanger, tittel, album eller artist (aktiv når avkrysningsboksen ovenfor er på).',
@@ -726,6 +728,14 @@ export const nbTranslation = {
dontShowAgain: "Ikke vis igjen",
close: 'Forstått',
},
+ whatsNew: {
+ title: 'Nyheter',
+ empty: 'Ingen endringslogg for denne versjonen ennå.',
+ bannerTitle: 'Changelog',
+ bannerCollapsed: 'Nyheter i v{{version}}',
+ dismiss: 'Skjul',
+ close: 'Lukk',
+ },
help: {
title: 'Hjelp',
s1: 'Komme i gang',
diff --git a/src/locales/nl.ts b/src/locales/nl.ts
index 3545c95c..0e204a4b 100644
--- a/src/locales/nl.ts
+++ b/src/locales/nl.ts
@@ -586,11 +586,13 @@ export const nlTranslation = {
aboutVersion: 'Versie',
aboutBuiltWith: 'Gebouwd met Tauri · React · TypeScript · Rust/rodio',
aboutAiCredit: 'Ontwikkeld met ondersteuning van Claude Code door Anthropic',
+ aboutReleaseNotesLabel: 'Release-notities',
+ aboutReleaseNotesLink: 'Nieuws van deze versie openen',
aboutContributorsLabel: 'Bijdragers',
aboutSpecialThanksLabel: 'Speciale dank',
changelog: 'Wijzigingslog',
showChangelogOnUpdate: "'Wat is nieuw' tonen bij update",
- showChangelogOnUpdateDesc: 'Toont automatisch de nieuwigheden bij de eerste start van een nieuwe versie.',
+ showChangelogOnUpdateDesc: 'Toont een discrete changelog-banner boven Now Playing na een update. Klik opent de release-notities; X verbergt hem.',
randomMixTitle: 'Willekeurige mix',
randomMixBlacklistTitle: 'Aangepaste filtertrefwoorden',
randomMixBlacklistDesc: 'Nummers worden uitgesloten als een trefwoord overeenkomt met hun genre, titel, album of artiest (actief wanneer het selectievakje hierboven is aangevinkt).',
@@ -726,6 +728,14 @@ export const nlTranslation = {
dontShowAgain: 'Niet meer weergeven',
close: 'Begrepen',
},
+ whatsNew: {
+ title: 'Wat is nieuw',
+ empty: 'Nog geen changelog-item voor deze versie.',
+ bannerTitle: 'Changelog',
+ bannerCollapsed: 'Nieuw in v{{version}}',
+ dismiss: 'Verbergen',
+ close: 'Sluiten',
+ },
help: {
title: 'Help',
s1: 'Aan de slag',
diff --git a/src/locales/ru.ts b/src/locales/ru.ts
index bddb7ed0..cceb4667 100644
--- a/src/locales/ru.ts
+++ b/src/locales/ru.ts
@@ -613,11 +613,13 @@ export const ruTranslation = {
aboutVersion: 'Версия',
aboutBuiltWith: 'Tauri · React · TypeScript · Rust/rodio',
aboutAiCredit: 'При поддержке Claude Code (Anthropic)',
+ aboutReleaseNotesLabel: 'Примечания к выпуску',
+ aboutReleaseNotesLink: 'Открыть «Что нового» этой версии',
aboutContributorsLabel: 'Участники',
aboutSpecialThanksLabel: 'Особая благодарность',
changelog: 'Список изменений',
showChangelogOnUpdate: 'Показывать «Что нового» после обновления',
- showChangelogOnUpdateDesc: 'Автоматически при первом запуске новой версии.',
+ showChangelogOnUpdateDesc: 'После обновления над «Сейчас играет» появится ненавязчивый баннер журнала изменений. Клик открывает заметки о выпуске, X скрывает.',
randomMixTitle: 'Случайный микс',
randomMixBlacklistTitle: 'Свои слова-фильтры',
randomMixBlacklistDesc:
@@ -756,6 +758,14 @@ export const ruTranslation = {
dontShowAgain: 'Больше не показывать',
close: 'Понятно',
},
+ whatsNew: {
+ title: 'Что нового',
+ empty: 'Для этой версии пока нет записи в журнале изменений.',
+ bannerTitle: 'Журнал изменений',
+ bannerCollapsed: 'Что нового в v{{version}}',
+ dismiss: 'Скрыть',
+ close: 'Закрыть',
+ },
help: {
title: 'Справка',
s1: 'С чего начать',
diff --git a/src/locales/zh.ts b/src/locales/zh.ts
index dfc4c417..d1cc4064 100644
--- a/src/locales/zh.ts
+++ b/src/locales/zh.ts
@@ -582,11 +582,13 @@ export const zhTranslation = {
aboutVersion: '版本',
aboutBuiltWith: '使用 Tauri · React · TypeScript · Rust/rodio 构建',
aboutAiCredit: '在 Anthropic 的 Claude Code 支持下开发',
+ aboutReleaseNotesLabel: '版本说明',
+ aboutReleaseNotesLink: '打开此版本的新功能',
aboutContributorsLabel: '贡献者',
aboutSpecialThanksLabel: '特别感谢',
changelog: '更新日志',
showChangelogOnUpdate: '更新时显示"新功能"',
- showChangelogOnUpdateDesc: '新版本首次启动时自动显示更新内容。',
+ showChangelogOnUpdateDesc: '更新后在「正在播放」上方显示一个低调的更新日志横幅。点击打开发行说明,X 按钮关闭。',
randomMixTitle: '随机混音',
randomMixBlacklistTitle: '自定义过滤关键词',
randomMixBlacklistDesc: '当任何关键词匹配流派、标题、专辑或艺术家时,歌曲将被排除(当上方复选框开启时生效)。',
@@ -722,6 +724,14 @@ export const zhTranslation = {
dontShowAgain: '不再显示',
close: '知道了',
},
+ whatsNew: {
+ title: '新功能',
+ empty: '此版本暂无更新日志。',
+ bannerTitle: '更新日志',
+ bannerCollapsed: 'v{{version}} 新功能',
+ dismiss: '关闭',
+ close: '关闭',
+ },
help: {
title: '帮助',
s1: '入门指南',
diff --git a/src/pages/Settings.tsx b/src/pages/Settings.tsx
index 3563ec5d..91f38586 100644
--- a/src/pages/Settings.tsx
+++ b/src/pages/Settings.tsx
@@ -2445,6 +2445,18 @@ export default function Settings() {
AI{t('settings.aboutAiCredit')}
+