diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9d5864c6..6942b786 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,26 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+## [1.0.9] - 2026-03-13
+
+### Added
+- **Gapless Playback**: The next track's audio pipeline is silently pre-warmed before the current track ends, eliminating the gap between songs — especially noticeable on live albums and concept records.
+- **Pre-caching**: Prefetched Howl instances are now actually reused for playback, giving near-instant track transitions instead of a new HTTP connection each time.
+- **Buffered Progress Indicator**: The seek bar now shows a secondary fill indicating how much of the current track has been buffered by the browser — visible in both the Player Bar and Fullscreen Player.
+- **Resume on Startup**: Pressing Play after launching the app now resumes the last track at the saved playback position instead of doing nothing.
+- **Album Track Hover Play Button**: Hovering over a track number in Album Detail reveals a play button for quick single-click playback.
+- **Ken Burns Background**: The Fullscreen Player background now slowly drifts and zooms (Ken Burns effect) for a more cinematic feel.
+- **F11 Fullscreen**: Toggle native borderless fullscreen with F11.
+- **Compact Queue Now-Playing**: The current track block in the Queue Panel is now a slim horizontal strip (72 px thumbnail) instead of a full-width cover, freeing up significantly more space for the queue list on smaller screens.
+
+### Fixed
+- **GStreamer Seek Stability**: Implemented a three-layer recovery system for Linux/GStreamer seek hangs: (1) seek queuing to prevent overlapping GStreamer seeks, (2) a 2-second watchdog that triggers automatic recovery if a seek never completes, (3) an 8-second hang detector that silently recreates the audio pipeline and resumes from the last known position if playback freezes entirely.
+- **Fullscreen Player**: Removed drop shadow from cover art — looks cleaner on lighter artist backgrounds.
+
+### Changed
+- **Hero Section**: Increased height (300 → 360 px) and cover art size (180 → 220 px) to prevent long album titles from clipping.
+- **Player Bar**: Controls and progress bar moved closer together for a more balanced layout.
+
## [1.0.8] - 2026-03-13
### Added
diff --git a/package.json b/package.json
index a411c6d4..19b55714 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "psysonic",
- "version": "1.0.8",
+ "version": "1.0.9",
"private": true,
"scripts": {
"dev": "vite",
diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock
index aaace869..a6d3f38a 100644
--- a/src-tauri/Cargo.lock
+++ b/src-tauri/Cargo.lock
@@ -2732,7 +2732,7 @@ dependencies = [
[[package]]
name = "psysonic"
-version = "1.0.8"
+version = "1.0.9"
dependencies = [
"serde",
"serde_json",
diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml
index ef1ba9f4..8f85e118 100644
--- a/src-tauri/Cargo.toml
+++ b/src-tauri/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "psysonic"
-version = "1.0.8"
+version = "1.0.9"
description = "Psysonic Desktop Music Player"
authors = []
license = ""
diff --git a/src-tauri/capabilities/default.json b/src-tauri/capabilities/default.json
index b5f014a9..bd7f7cba 100644
--- a/src-tauri/capabilities/default.json
+++ b/src-tauri/capabilities/default.json
@@ -25,6 +25,8 @@
"core:window:allow-set-title",
"core:window:allow-close",
"core:window:allow-hide",
- "core:window:allow-show"
+ "core:window:allow-show",
+ "core:window:allow-set-fullscreen",
+ "core:window:allow-is-fullscreen"
]
}
diff --git a/src-tauri/gen/schemas/capabilities.json b/src-tauri/gen/schemas/capabilities.json
index c0e94a76..522ed2db 100644
--- a/src-tauri/gen/schemas/capabilities.json
+++ b/src-tauri/gen/schemas/capabilities.json
@@ -1 +1 @@
-{"default":{"identifier":"default","description":"Default capabilities for Psysonic","local":true,"windows":["main"],"permissions":["core:default","shell:default","shell:allow-open","notification:default","global-shortcut:allow-register","global-shortcut:allow-unregister","store:default","store:allow-load","store:allow-set","store:allow-get","store:allow-save","dialog:default","dialog:allow-open","fs:default","fs:allow-write-file","fs:allow-mkdir","fs:scope-download-recursive","core:window:allow-set-title","core:window:allow-close","core:window:allow-hide","core:window:allow-show"],"platforms":["linux","macOS","windows"]}}
\ No newline at end of file
+{"default":{"identifier":"default","description":"Default capabilities for Psysonic","local":true,"windows":["main"],"permissions":["core:default","shell:default","shell:allow-open","notification:default","global-shortcut:allow-register","global-shortcut:allow-unregister","store:default","store:allow-load","store:allow-set","store:allow-get","store:allow-save","dialog:default","dialog:allow-open","fs:default","fs:allow-write-file","fs:allow-mkdir","fs:scope-download-recursive","core:window:allow-set-title","core:window:allow-close","core:window:allow-hide","core:window:allow-show","core:window:allow-set-fullscreen","core:window:allow-is-fullscreen"],"platforms":["linux","macOS","windows"]}}
\ No newline at end of file
diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json
index 5f31a01c..49560d02 100644
--- a/src-tauri/tauri.conf.json
+++ b/src-tauri/tauri.conf.json
@@ -1,7 +1,7 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "Psysonic",
- "version": "1.0.8",
+ "version": "1.0.9",
"identifier": "dev.psysonic.app",
"build": {
"beforeDevCommand": "npm run dev",
diff --git a/src/App.tsx b/src/App.tsx
index 7ed5fb43..0a54541a 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -184,9 +184,15 @@ function TauriEventBridge() {
const previous = usePlayerStore(s => s.previous);
const { minimizeToTray } = useAuthStore();
- // Spacebar → play/pause (ignore when focus is in an input)
+ // Spacebar → play/pause, F11 → window fullscreen
useEffect(() => {
const onKey = (e: KeyboardEvent) => {
+ if (e.code === 'F11') {
+ e.preventDefault();
+ const win = getCurrentWindow();
+ win.isFullscreen().then(fs => win.setFullscreen(!fs));
+ return;
+ }
if (e.code !== 'Space') return;
const tag = (e.target as HTMLElement)?.tagName;
if (tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT') return;
diff --git a/src/components/FullscreenPlayer.tsx b/src/components/FullscreenPlayer.tsx
index 9642b673..db35271c 100644
--- a/src/components/FullscreenPlayer.tsx
+++ b/src/components/FullscreenPlayer.tsx
@@ -52,6 +52,7 @@ const FsBg = memo(function FsBg({ url }: { url: string }) {
// ─── Progress bar (isolated — re-renders every tick) ──────────────────────────
const FsProgress = memo(function FsProgress({ duration }: { duration: number }) {
const progress = usePlayerStore(s => s.progress);
+ const buffered = usePlayerStore(s => s.buffered);
const currentTime = usePlayerStore(s => s.currentTime);
const seek = usePlayerStore(s => s.seek);
@@ -60,6 +61,9 @@ const FsProgress = memo(function FsProgress({ duration }: { duration: number })
[seek]
);
+ const pct = progress * 100;
+ const buf = Math.max(pct, buffered * 100);
+
return (