fix(playback): keep the saved Embedded MPV player when the mpv check is inconclusive

On Linux native-view the support check runs `mpv --version` by bare name
and waits for the login shell PATH first. Since #1784 that lookup is
asynchronous with a 10 s budget; when it ran out, the check ran on the
inherited PATH and answered a plain `supported: false`. The settings store
took that as a verdict and persisted the default player over a saved
Embedded MPV selection. The main process probed again once the shell
answered, but nothing restored the setting.

`EmbeddedMpvSupport` now carries `inconclusive`. The native service sets it
on a missing mpv while its probe has only seen the inherited PATH; the IPC
handler declares that state before probing and registers the re-probe
before the check, so a throwing check cannot leave it stuck. Every other
answer stays final.

Consumers no longer settle on an inconclusive answer: the settings store
keeps the saved player, and the command palette and the settings search
probe again on their next use instead of caching it for the session.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
4grayandClaude Fable 5.1 committed 2026-10-03 19:39:41 +02:00
1 parent 8e1cb05913
commit 417eaf5005
14 files changed
+545 -16

No files matched your search

@@ -142,6 +142,7 @@ describe('SettingsSearchService', () => {
function probeWith(support: {
supported: boolean;
inconclusive?: boolean;
frameCopyAvailable?: boolean;
}) {
const getEmbeddedMpvSupport = jest
@@ -186,6 +187,32 @@ describe('SettingsSearchService', () => {
);
});
it('asks again after an inconclusive answer and keeps the final one', async () => {
// A slow login shell: mpv was looked up before its PATH arrived.
const getEmbeddedMpvSupport = probeWith({
supported: false,
inconclusive: true,
});
const { service } = setup({
...DESKTOP,
supportsEmbeddedMpv: true,
});
const entries = () => service.visibleEntries().map(({ id }) => id);
await service.ensureEmbeddedMpvSupportLoaded();
expect(entries()).not.toContain('embedded-mpv-extra-options');
// The shell answered meanwhile, and mpv is there.
getEmbeddedMpvSupport.mockResolvedValue({
platform: 'linux',
supported: true,
});
await service.ensureEmbeddedMpvSupportLoaded();
expect(entries()).toContain('embedded-mpv-extra-options');
expect(service.ensureEmbeddedMpvSupportLoaded()).toBeUndefined();
expect(getEmbeddedMpvSupport).toHaveBeenCalledTimes(2);
});
it('does not probe where the runtime has no embedded MPV bridge', () => {
const getEmbeddedMpvSupport = probeWith({ supported: true });
const { service } = setup(DESKTOP);
@@ -66,7 +66,8 @@ export class SettingsSearchService {
* searchable. Returns the pending probe, or `undefined` when there is
* nothing to wait for. Call it lazily (palette open, settings page),
* never from shell bootstrap: supported desktop builds may load the
* native addon while answering.
* native addon while answering. An inconclusive answer is used until
* the next call, which probes again.
*/
ensureEmbeddedMpvSupportLoaded(): Promise<void> | undefined {
if (this.embeddedMpvSupportChecked) {
@@ -88,7 +89,9 @@ export class SettingsSearchService {
.then((support) => this.embeddedMpvSupport.set(support))
.catch(() => this.embeddedMpvSupport.set(null))
.finally(() => {
this.embeddedMpvSupportChecked = true;
this.embeddedMpvSupportChecked =
!this.embeddedMpvSupport()?.inconclusive;
this.embeddedMpvSupportLoad = undefined;
});
return this.embeddedMpvSupportLoad;
}