Skip to content

Commit f689da0

Browse files
committed
Bugfixes: Theme at startup, Linux dead-code, Svelte render lint
- `theme.mode` now applies at every window's startup via `settings-applier.ts`, not just when the Themes section happens to mount. Restarting with light mode persisted on a dark-mode system used to start dark and flip when settings opened. The applier subscribes to `theme.mode` changes too, so the Themes section is now a pure UI shell. - Gate `restricted_paths::reprobe_all_async` to `#[cfg(target_os = "macos")]` — its only caller, the `NSApplicationDidBecomeActive` observer, is already macOS-only, so the function is dead code on Linux and `#![deny(unused)]` was failing the Linux build. - Add the canonical `eslint-disable-next-line @typescript-eslint/no-confusing-void-expression` comment on the two `{@render scanPhaseBody()}` lines in `TransferProgressDialog.svelte`. Same pattern as `DualPaneExplorer.svelte` — local snippets confuse the type-aware rule where prop `Snippet` ones don't. - Tiny cleanup: wrap the two `() => void handleCancel(false)` arrow shorthands in `TransferProgressDialog.svelte` with braces. Not what was failing lint, but the void-in-shorthand pattern is a known footgun and the braces are clearer.
1 parent be2333c commit f689da0

4 files changed

Lines changed: 51 additions & 40 deletions

File tree

apps/desktop/src-tauri/src/restricted_paths/mod.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,13 @@ pub fn snapshot() -> Vec<String> {
109109
/// Re-probe every path in the restricted set with a cheap `read_dir`. Any
110110
/// path that now opens successfully is cleared. Runs on a blocking task —
111111
/// safe to call from the main thread (the observer block does so).
112+
///
113+
/// macOS-only because the only caller — the `NSApplicationDidBecomeActive`
114+
/// observer in `install_did_become_active_observer` below — is itself macOS
115+
/// only. Other platforms have no equivalent re-probe trigger today, so the
116+
/// function would just be dead code there (and `#![deny(unused)]` would fail
117+
/// the Linux build).
118+
#[cfg(target_os = "macos")]
112119
pub fn reprobe_all_async() {
113120
let paths_to_probe = {
114121
let guard = state().read().expect("restricted_paths state poisoned");

apps/desktop/src/lib/file-operations/transfer/TransferProgressDialog.svelte

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -855,7 +855,9 @@
855855
titleId="progress-dialog-title"
856856
onkeydown={handleKeydown}
857857
dialogId="transfer-progress"
858-
onclose={() => void handleCancel(false)}
858+
onclose={() => {
859+
void handleCancel(false)
860+
}}
859861
containerStyle="width: 500px"
860862
>
861863
{#snippet title()}
@@ -877,11 +879,17 @@
877879
{/if}
878880

879881
<div class="scan-wait-section">
882+
<!-- eslint-disable-next-line @typescript-eslint/no-confusing-void-expression -- Svelte {@render} syntax -->
880883
{@render scanPhaseBody()}
881884
</div>
882885

883886
<div class="button-row">
884-
<Button variant="secondary" onclick={() => void handleCancel(false)}>Cancel</Button>
887+
<Button
888+
variant="secondary"
889+
onclick={() => {
890+
void handleCancel(false)
891+
}}>Cancel</Button
892+
>
885893
</div>
886894
{:else if !isDeleteOrTrash && conflictEvent}
887895
<!-- Conflict resolution (copy/move only) -->
@@ -1027,7 +1035,8 @@
10271035
{#if phase === 'scanning'}
10281036
<!-- Scanning phase: tallies, throughput, optional progress bar, current dir/file. -->
10291037
<div class="scan-wait-section">
1030-
{@render scanPhaseBody()}
1038+
<!-- eslint-disable-next-line @typescript-eslint/no-confusing-void-expression -- Svelte {@render} syntax -->
1039+
{@render scanPhaseBody()}
10311040
</div>
10321041
{:else}
10331042
<!-- Dual progress bars (size + count) for the active phase. -->

apps/desktop/src/lib/settings/sections/ThemesSection.svelte

Lines changed: 5 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,9 @@
11
<script lang="ts">
2-
import { onMount, onDestroy } from 'svelte'
32
import SettingsSection from '../components/SettingsSection.svelte'
43
import SettingRow from '../components/SettingRow.svelte'
54
import SettingToggleGroup from '../components/SettingToggleGroup.svelte'
6-
import { getSettingDefinition, onSpecificSettingChange, getSetting } from '$lib/settings'
5+
import { getSettingDefinition } from '$lib/settings'
76
import { createShouldShow } from '$lib/settings/settings-search'
8-
import { getAppLogger } from '$lib/logging/logger'
9-
10-
const log = getAppLogger('settings')
117
128
interface Props {
139
searchQuery: string
@@ -19,38 +15,10 @@
1915
2016
const themeModeDef = getSettingDefinition('theme.mode') ?? { label: '', description: '' }
2117
22-
let unsubscribe: (() => void) | undefined
23-
24-
async function applyTheme(mode: string) {
25-
log.debug('Applying theme: {mode}', { mode })
26-
try {
27-
const { setTheme } = await import('@tauri-apps/api/app')
28-
if (mode === 'system') {
29-
// Setting null lets Tauri follow system preference
30-
await setTheme(null)
31-
} else {
32-
await setTheme(mode as 'light' | 'dark')
33-
}
34-
log.info('Theme applied: {mode}', { mode })
35-
} catch (error) {
36-
log.error('Failed to apply theme: {error}', { error })
37-
}
38-
}
39-
40-
onMount(() => {
41-
// Apply current theme on mount (in case it changed while settings were closed)
42-
const currentTheme = getSetting('theme.mode')
43-
void applyTheme(currentTheme)
44-
45-
// Listen for theme changes
46-
unsubscribe = onSpecificSettingChange('theme.mode', (_id, value) => {
47-
void applyTheme(value)
48-
})
49-
})
50-
51-
onDestroy(() => {
52-
unsubscribe?.()
53-
})
18+
// Theme application lives in `settings-applier.ts` — it runs at every window's
19+
// startup, so the persisted choice takes effect on cold launches and not just
20+
// when this section happens to mount. Toggling the radio fires `setSetting`,
21+
// which the applier subscribes to.
5422
</script>
5523

5624
<SettingsSection title="Themes">

apps/desktop/src/lib/settings/settings-applier.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
type UiDensity,
1111
type SizeColorsPalette,
1212
type DateColorsPalette,
13+
type ThemeMode,
1314
} from '$lib/settings'
1415
import { getAppLogger, setVerboseLogging } from '$lib/logging/logger'
1516
import {
@@ -98,6 +99,24 @@ async function applyBackendSettings(): Promise<void> {
9899
})
99100
}
100101

102+
/**
103+
* Applies the persisted `theme.mode` setting via Tauri's per-app theme API.
104+
* Loaded dynamically so we don't pay the import cost on every startup of
105+
* non-Tauri contexts (tests, SSR) where the API isn't available.
106+
*
107+
* `'system'` is signaled by passing `null`, which tells Tauri to follow the
108+
* OS appearance.
109+
*/
110+
async function applyTheme(mode: ThemeMode): Promise<void> {
111+
try {
112+
const { setTheme } = await import('@tauri-apps/api/app')
113+
await setTheme(mode === 'system' ? null : mode)
114+
log.debug('Applied theme: {mode}', { mode })
115+
} catch (error) {
116+
log.error('Failed to apply theme: {error}', { error })
117+
}
118+
}
119+
101120
/**
102121
* Applies all settings that affect the UI.
103122
*/
@@ -112,6 +131,10 @@ function applyAllSettings(): void {
112131
// Date age color palette
113132
applyDateColors(getSetting('appearance.dateColors'))
114133

134+
// Theme (light / dark / system). Must run at startup or windows that open
135+
// before the user touches Settings will flash the wrong theme.
136+
void applyTheme(getSetting('theme.mode'))
137+
115138
// Backend settings (async, fire-and-forget for startup)
116139
void applyBackendSettings()
117140

@@ -162,6 +185,10 @@ function handleSettingChange(id: string, value: unknown): void {
162185
applyDateColors(value as DateColorsPalette)
163186
return
164187
}
188+
if (id === 'theme.mode') {
189+
void applyTheme(value as ThemeMode)
190+
return
191+
}
165192
if (id === 'advanced.maxLogStorageMb') {
166193
applyMaxLogStorageMb(value as number)
167194
return

0 commit comments

Comments
 (0)