chore(release): 0.7.0
Version bumped across package.json, tauri.conf.json and Cargo.toml (+ lock), CHANGELOG entry written from the five commits in the range rather than from the trace extractor's output — VideoPlayer.svelte alone carries dozens of TRACES, so the generated draft named most of the app's requirements for a five-commit release. DR-188 is retargeted: it recorded the native-video default as waiting on the background-audio handoff, which is now fixed (DR-196), so it records the completed flip and the evidence for it instead. Minor, not patch: the rendering path changes underneath every Android user.
This commit is contained in:
@@ -9,6 +9,50 @@ generated trace matrix lives in [docs/traceability.md](docs/traceability.md).
|
||||
For how long each fixed defect had been shipping before it was found, see
|
||||
[docs/defect-windows.md](docs/defect-windows.md).
|
||||
|
||||
## v0.7.0
|
||||
|
||||
### ✨ Changes
|
||||
|
||||
- **Native Android video is now the default.** Video decodes on the device's
|
||||
hardware decoder instead of the built-in web player, which is easier on the
|
||||
battery and lets picture-in-picture show the video rather than the app. The
|
||||
default had been held back deliberately since the picture defects were fixed,
|
||||
because returning from background audio left playback dead on that path; both
|
||||
blockers below are fixed and verified on a device, which is the standard this
|
||||
default has been held to since it last shipped early. The Settings toggle
|
||||
remains, now as the fallback to the web player, and an explicit choice still
|
||||
wins in both directions — anyone who turned it off keeps it off.
|
||||
(UR-003, UR-004 → DR-188)
|
||||
|
||||
### 🐛 Fixes
|
||||
|
||||
- **The letterbox bars stop showing things that are no longer there.** With
|
||||
native video on, the padding around the picture kept whatever had last been
|
||||
drawn in it: the previous frame flashing on rotation, a ghost copy of the
|
||||
control bar stranded at the top of the screen, each new clock digit drawn over
|
||||
the one before it, and the sleep-timer and quality menus leaving their imprint
|
||||
after closing. One cause under all of it — nothing painted those bars. The
|
||||
window surface is opaque, and for an opaque surface Android's renderer skips
|
||||
clearing the damaged region and assumes the view hierarchy covers every pixel;
|
||||
the video view covers only the letterboxed rect, so the bars were the window
|
||||
background's alone to paint, and enabling compositing had cleared that
|
||||
background to transparent. Three earlier attempts missed because they aimed at
|
||||
the window's rotation animation and at video-frame retention — which is also
|
||||
why the artefact reproduced standing still, with no rotation involved.
|
||||
(UR-003, UR-066 → DR-194)
|
||||
|
||||
- **Returning from background audio brings the picture back.** On the native
|
||||
path, coming back from the lockscreen left a black screen: a play overlay
|
||||
pinned at 0:00 and a play button that did nothing. Nothing had crashed — the
|
||||
transition was simply dropped. The two render paths resume by different means,
|
||||
and only one of them was performed: the web player reloads from its stream URL,
|
||||
while the native player owns no element and nothing watches that URL on its
|
||||
behalf, so it has to be handed the item again explicitly. It now is, at the
|
||||
position the audio reached. (UR-040, UR-003 → DR-196)
|
||||
|
||||
- **Next Up stops repeating what Continue Watching already shows.** The same
|
||||
episode could occupy both home rows at once. (UR-023 → DR-197)
|
||||
|
||||
## v0.6.0
|
||||
|
||||
### 🐛 Fixes
|
||||
|
||||
@@ -347,7 +347,7 @@ Internal architecture, components, and application logic.
|
||||
| DR-185 | The app shell stops painting over the video surface. `app.css` clears the page's opaque layers for native video through three selectors, and one of them — `html[data-native-video="active"] [data-app-shell]` — was written against an attribute **no component has ever set, in any commit**. The shell is `+layout.svelte`'s root `div`, which paints `--color-background` across the entire viewport; VideoPlayer is `fixed inset-0 z-50` and correctly makes *itself* transparent on the native path, but it stacks *above* the shell, so the WebView still composited the shell's opaque background over the whole screen and the SurfaceView behind it could never be seen. This is the missing half of the compositing DR-172 went looking for: the spec's own layer table lists this layer as "cleared by `data-native-video` → app.css", which was written but never wired, and `html`/`body` being genuinely transparent made the CSS look correct in isolation. The failure is invisible three ways over — the CSS is valid, the selector is plausible, and a rule matching nothing looks exactly like a rule matching something already transparent — while the symptom (black screen, audio fine) is identical to a real compositing failure, which is how it survived DR-150 through DR-172. Fixed by setting the attribute the rule was written for, and guarded by asserting the *relationship* rather than the rule: every attribute the compositing block targets must be set somewhere in the app, so a selector aimed at nothing fails the suite instead of failing silently on a device | UI | UR-003, UR-004, UR-041 | Done |
|
||||
| DR-186 | The play overlay comes down when the backend plays. `isPlaying` was assigned once from the `player_play_item` response and thereafter only by the `player://state-changed` listener — a channel the backend never emits, the same dead wire that DR-182's first fix was mistakenly hung on. On the native path the flag therefore froze at whatever the initial response said: with ExoPlayer playing, the UI still believed it was paused, so the `bg-black/30` play-button overlay stayed raised across the whole video area and the transport button kept showing ▶. The video was simultaneously dimmed and covered while it played, which reads as "the overlay never goes away" and is easily mistaken for a second compositing fault. The mirror reads the same `player` store `playerEvents.ts` feeds, which is what the architecture already says is authoritative — the player reports state, the UI consumes it — and is gated to the native path so HTML5 keeps its element-event wiring, which is authoritative there | UI | UR-003, UR-005 | Done |
|
||||
| DR-187 | The system bars go away with the player, not only with the fullscreen button. `enterImmersive()` had exactly one caller, `toggleFullscreen()`, so opening the player left the status and navigation bars painted over it until the user pressed a button most never press. On the native path this is worse than cosmetic: the SurfaceView fills the content view, so the bars sit directly on top of the video. The player is a full-screen surface by construction — `fixed inset-0 z-50` over a `MATCH_PARENT` surface — so entry is the right moment. Called synchronously in `onMount` before any `await`, per the native-mode pitfall, and paired with the `exitImmersive()` already unconditional in `onDestroy`, so a player torn down while immersive cannot leave the rest of the app without bars | UI | UR-066, UR-003 | Done |
|
||||
| DR-188 | Native Android video is **ready to be the default except for the background-audio handoff**, and the flip therefore waits. The picture defects behind DR-172 are all found, fixed and device-verified — DR-185 (the app shell painted over the surface through a CSS rule targeting an attribute nothing set), DR-182 (nothing could lift the poster card on a path with no `<video>` element), DR-183 (the JS bridges raced the page load, so `setTransparent(true)` could never arrive), DR-184 (the SurfaceView was never detached), plus DR-186 and DR-187, the two UI defects only this path could reveal. On a device logcat now carries `WebView transparent = true` and `Marking media ready` with video on screen, which is the pair DR-172 went looking for and could not find, and skip, seek and rotation were exercised by hand. Turning the default on then surfaced a *different* unverified sub-path: returning from background audio is HTML5-only (DR-190), so on the native path playback simply stays dead. Shipping it would have repeated DR-161 exactly — a verified sub-path made default over an unverified one — so the default stays off and the flip is gated on DR-190 rather than on more confidence | UI | UR-003, UR-004, UR-041 | Blocked by DR-190 |
|
||||
| DR-188 | Native Android video is **ready to be the default except for the background-audio handoff**, and the flip therefore waits. The picture defects behind DR-172 are all found, fixed and device-verified — DR-185 (the app shell painted over the surface through a CSS rule targeting an attribute nothing set), DR-182 (nothing could lift the poster card on a path with no `<video>` element), DR-183 (the JS bridges raced the page load, so `setTransparent(true)` could never arrive), DR-184 (the SurfaceView was never detached), plus DR-186 and DR-187, the two UI defects only this path could reveal. On a device logcat now carries `WebView transparent = true` and `Marking media ready` with video on screen, which is the pair DR-172 went looking for and could not find, and skip, seek and rotation were exercised by hand. Turning the default on then surfaced a *different* unverified sub-path: the background-audio handoff could only *return* through the HTML5 element, so coming back from the lockscreen left playback dead, and the flip waited for that rather than shipping a verified sub-path over an unverified one as DR-161 had. **The default is now on.** The two defects holding it back are fixed and device-verified — DR-196 (the handoff return restarts the renderer that is actually on screen) and DR-194 (the letterbox bars are painted rather than retaining stale framebuffer content) — with the evidence this default has been held to since DR-161: an audio handoff at 69:54 returning to video playing at 70:18, and clean bars across playback, the control bar and a rotation round-trip. An explicit stored choice still wins in both directions, so an opt-out survives the flip (the stored value is null-checked rather than compared to "true", which would have silently re-enabled it for everyone who turned it off) | Android | UR-003, UR-004 | Done |
|
||||
| DR-191 | Forcing the WebView overlay to redraw from the Activity, because with the ExoPlayer **SurfaceView** beneath it the overlay's ordinary damage stopped reaching the screen: the page kept mutating — the clock text every second, the control bar's opacity going to 0 — while the display held whatever frame it last presented, over video that animated perfectly. Not a state defect; the live DOM showed the slider advancing 476 → 479 across three seconds behind a screen showing neither. Only **structural** changes got through, which is why the play overlay always appeared to work (an `{#if}` block, added and removed) while the progress bar never did, and why rotation lost the transport UI. A CSS animation cannot help, since opacity animates on the compositor without repainting the layer. **Superseded by DR-192**: this drove `postInvalidateOnAnimation` in a loop, which treats the symptom — the cause is the SurfaceView's separate layer, and removing that removes the need. Kept as the record of how the mechanism was identified | Android | UR-003, UR-004 | Superseded by DR-192 |
|
||||
| DR-195 | Play/pause works on the native path, because the frontend stops claiming a webview element is playing when there is none. `html5_playing` is Rust's record of "a webview `<video>` is active and in this state", and `toggle_playback`, `play` and `pause` all route transport to that element whenever it is set. The player route mirrored element state into it **unconditionally** — from `handleReportStart` and, fatally, from `handleReportProgress`, which VideoPlayer calls on a 10-second interval — so on the native path the frontend re-declared every ten seconds that an element was playing when none existed, and every transport intent was emitted into the void. The pause button was dead from the on-screen tap, from the control bar, and from a direct `player_toggle` invocation, while seek and skip kept working because `player_seek_video` decides elsewhere; that asymmetry is the signature. It also explains the flashing, since the control bar and the JRay overlay both key off `isPlaying`, which was being contradicted on every interval tick. DR-193 clearing the flag at load was necessary but insufficient on its own — the interval put it straight back. The mirror now lives in `mirrorElementStateToRust` in VideoPlayer, gated on `useHtml5Element`, which is the only place that knows whether an element renders at all; the route cannot tell the two paths apart, which is precisely how it came to lie. Confirmed on device by ADB: surface tap and control bar each pause (position frozen across repeated samples, transport label flipped) and resume | Playback | UR-005, UR-003 | Done |
|
||||
| DR-196 | Returning from background audio brings the picture back on the **native** path, because the return now restarts the renderer that is actually on screen. The two paths resume by different means: the webview `<video>` reloads off its stream URL, watched by an `$effect` that reinitialises HLS and lets `canplay` drive the seek — while ExoPlayer owns no element and nothing watches the URL on its behalf, so its playback is only ever started by an explicit `player_play_item` + adapter load, issued once from `onMount`. `exitBackgroundAudioHandoff` did only the URL assignment, for both paths, so on the native path it restarted nothing: `player_exit_background_audio` had already stopped the handoff's audio player, leaving the backend holding no item at all. The symptom is a black screen with a play overlay pinned at 0:00, a seek bar at zero, and a play button that does nothing — the process alive and the frontend still logging, since nothing crashed; the transition was simply dropped. The branch is decided by `planHandoffReturn` (pure, in `backgroundAudioHandoff.ts`), which also folds in `shouldResumeOnForeground` so a lockscreen pause during the handoff still wins over the snapshot taken on the way out. Subtitle configurations are reused from the ones resolved at mount, since ExoPlayer sideloads them as `MediaItem.SubtitleConfiguration`s and cannot accept one after `prepare()`. Verified on device: handoff to audio at 69:54, return restored video playing at 70:18 | Playback | UR-040, UR-003 | Done |
|
||||
|
||||
+15
-6
@@ -1,11 +1,11 @@
|
||||
# Code Traceability Matrix
|
||||
|
||||
**Generated:** 8/16/2026, 10:15:02 PM
|
||||
**Generated:** 8/16/2026, 10:23:47 PM
|
||||
|
||||
## Summary
|
||||
|
||||
- **Total Files Scanned:** 366
|
||||
- **Total TRACES Found:** 823
|
||||
- **Total Files Scanned:** 367
|
||||
- **Total TRACES Found:** 824
|
||||
- **Requirements Covered:**
|
||||
- User Requirements (UR): 71
|
||||
- Integration Requirements (IR): 19
|
||||
@@ -4046,11 +4046,14 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-008, JA-010, JA-011, JA-012,
|
||||
|
||||
### DR-188
|
||||
|
||||
**Locations:** 1 file(s)
|
||||
**Locations:** 2 file(s)
|
||||
|
||||
- **File:** [`src/lib/stores/nativeVideo.ts`](src/lib/stores/nativeVideo.ts#L73)
|
||||
- **Line:** 73
|
||||
- **Context:** `Unknown`
|
||||
- **File:** [`src/lib/stores/nativeVideo.default.test.ts`](src/lib/stores/nativeVideo.default.test.ts#L15)
|
||||
- **Line:** 15
|
||||
- **Context:** `Unknown`
|
||||
|
||||
### DR-189
|
||||
|
||||
@@ -4518,7 +4521,7 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-008, JA-010, JA-011, JA-012,
|
||||
|
||||
### UR-003
|
||||
|
||||
**Locations:** 45 file(s)
|
||||
**Locations:** 46 file(s)
|
||||
|
||||
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L149)
|
||||
- **Line:** 149
|
||||
@@ -4580,6 +4583,9 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-008, JA-010, JA-011, JA-012,
|
||||
- **File:** [`src/lib/stores/nativeVideo.ts`](src/lib/stores/nativeVideo.ts#L73)
|
||||
- **Line:** 73
|
||||
- **Context:** `Unknown`
|
||||
- **File:** [`src/lib/stores/nativeVideo.default.test.ts`](src/lib/stores/nativeVideo.default.test.ts#L15)
|
||||
- **Line:** 15
|
||||
- **Context:** `Unknown`
|
||||
- **File:** [`src/lib/utils/nativeVideoLayers.test.ts`](src/lib/utils/nativeVideoLayers.test.ts#L4)
|
||||
- **Line:** 4
|
||||
- **Context:** `Unknown`
|
||||
@@ -4658,7 +4664,7 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-008, JA-010, JA-011, JA-012,
|
||||
|
||||
### UR-004
|
||||
|
||||
**Locations:** 62 file(s)
|
||||
**Locations:** 63 file(s)
|
||||
|
||||
- **File:** [`src/routes/player/[id]/+page.svelte`](src/routes/player/[id]/+page.svelte#L346)
|
||||
- **Line:** 346
|
||||
@@ -4711,6 +4717,9 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-008, JA-010, JA-011, JA-012,
|
||||
- **File:** [`src/lib/stores/nativeVideo.ts`](src/lib/stores/nativeVideo.ts#L73)
|
||||
- **Line:** 73
|
||||
- **Context:** `Unknown`
|
||||
- **File:** [`src/lib/stores/nativeVideo.default.test.ts`](src/lib/stores/nativeVideo.default.test.ts#L15)
|
||||
- **Line:** 15
|
||||
- **Context:** `Unknown`
|
||||
- **File:** [`src/lib/utils/nativeVideoLayers.test.ts`](src/lib/utils/nativeVideoLayers.test.ts#L4)
|
||||
- **Line:** 4
|
||||
- **Context:** `Unknown`
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "jellytau",
|
||||
"version": "0.6.0",
|
||||
"version": "0.7.0",
|
||||
"description": "",
|
||||
"type": "module",
|
||||
"packageManager": "bun@1.3.5",
|
||||
|
||||
Generated
+1
-1
@@ -2018,7 +2018,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "jellytau"
|
||||
version = "0.6.0"
|
||||
version = "0.7.0"
|
||||
dependencies = [
|
||||
"aes-gcm",
|
||||
"async-trait",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "jellytau"
|
||||
version = "0.6.0"
|
||||
version = "0.7.0"
|
||||
description = "A Tauri App"
|
||||
authors = ["you"]
|
||||
edition = "2021"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://schema.tauri.app/config/2",
|
||||
"productName": "jellytau",
|
||||
"version": "0.6.0",
|
||||
"version": "0.7.0",
|
||||
"identifier": "com.dtourolle.jellytau",
|
||||
"build": {
|
||||
"beforeDevCommand": "bun run dev",
|
||||
|
||||
Reference in New Issue
Block a user