feat(desktop,web): window translucency + background modes (#58)#59
Merged
Conversation
Introduce per-scope background mode (image/solid/transparent) and window effect (none/auto/mica/blur/acrylic) to terminal profiles. Resolve through the 4-layer config cascade (defaults → TOML → host → channel) and persist via the settings API. Web client renders the modes with transparent backgrounds on Tauri; browsers fall back to the theme. Foundation for native desktop window effects and solid/transparent rendering modes.
Apply native window effects (mica/blur/acrylic on Windows, vibrancy on macOS) when the active pane's background mode is "transparent". Platform detection via @tauri-apps/plugin-os, resolved at startup and cached; unknown effects degrade to none. Per-OS + Windows-build effect picker in Wallpaper settings, with desktop-only indicator for browsers. Out-of-order async IPC handled latest-wins; no effect calls while config cascade unresolved; clearEffects only when an effect was applied. macOS effects untested on real hardware; acrylic flagged as laggy on Win10.
…window Without core:window:allow-set-effects and os:default in the default capability, the webview's setEffects and platform/version IPC calls are denied at runtime, leaving native window effects inert on desktop.
Collaborator
Author
|
Review coverage note: GitHub Copilot is out of credits (review request pending, never fulfilled; both orthogonal-gate attempts returned unable_to_assess). Coverage for this PR = codex xhigh orthogonal gate (1 refusal fixed in 78c0a9e, then clean on final HEAD) + senior architectural review (clean, all 16 spec scenarios mutation-verified). Remaining before merge: the manual smoke checklist in the PR body (Windows 10/11 + Linux real builds). |
Wallpaper background URLs now use the hubBaseUrl() prefix in the Tauri desktop context (same pattern as the existing @font-face injection). Relative /public/wallpapers/ URLs resolve against tauri://localhost and were 404ing; browsers unaffected (hubBaseUrl() returns ""). All /public/* asset responses (fonts, sounds, wallpapers) now carry Cross-Origin-Resource-Policy: cross-origin instead of helmet's same-origin default. Desktop webview (tauri://localhost) embeds these cross-origin from http://localhost:<port>; CORP same-origin was blocking valid CORS loads in WebKit. API routes and app shell keep same-origin; no other helmet setting changed.
…aders type Structural parameter type accepts both SetHeadersResponse and ServerResponse; tsc --noEmit is green again (vitest and the esbuild SEA bundle never typecheck, which is how the mismatch slipped through).
When the last pane closes, no channel is active, or the profile permanently fails to resolve, the window effect is now cleared instead of persisting indefinitely. Effect IPC calls are serialized to wait for in-flight operations before reconciling. Fallback wallpaper detection now exposes a flag to correctly determine active-scope state in App.vue. Window-effect picker is hidden on Linux where effects resolve to no-op. Tauri OS capability narrowed to only the two calls in use (allow-platform, allow-version).
The window effect (blur, mica, vibrancy) now derives from the displayed background state rather than scope/profile resolution gates. This ensures the native effect always matches what the UI paints: transparent backgrounds apply effects; solid, cached, fallback, or uninitialized backgrounds clear them. Eliminates the prior disagreement where effects could outlive displayed backgrounds in scope changes or fetch failures. Wallpaper picker previews in the desktop app now render via hubBaseUrl().
The settings cascade load, theme save, and host-override writes used relative /api URLs, which the Tauri webview resolves against tauri://localhost instead of the hub — the settings panel never loaded layer values on desktop (selection state, modes, sliders all read defaults) and theme/override writes failed. All four call sites now use hubBaseUrl() like their sibling requests; browsers are unaffected.
… None tile With explicit background modes, the None tile was a second path to the same visual result as Solid (a leftover from the wallpaper-or-nothing model). The grid now only selects which image to use; removing the wallpaper is expressed by switching the mode to Solid. Deleting the currently-selected wallpaper file still clears the setting internally.
Add per-boot random asset token (crypto.randomBytes) distributed via authenticated GET /api/assets/token. Desktop webview embeds /public/* resources (fonts, wallpapers, sounds) cross-origin only when request carries valid ?asset_token= URL parameter (constant-time verify). Rationale: prevent drive-by fingerprinting via localhost:4100 — an unconditional CORP: cross-origin header would let arbitrary websites embed user's local assets. Token proof is in the URL (webview sends no Origin header), authenticated GET ensures only paired clients get the token, and constant-time compare protects against timing attacks. All /public URL builders (useWallpaper, @font-face, useBellSound, WallpaperCategory) updated to thread the token. GET /api/fonts and GET /api/wallpapers now require auth + return signed URLs. Docs: PROTOCOL.md and decisions.md updated to signed-asset model.
Hub and desktop build jobs only ran on release tags, so cross-platform build breakage (Windows hub SEA, Tauri bundles) could only surface at release time. A workflow_dispatch run on any branch now exercises them for pre-merge validation; artifact retention stays at 1 day.
GET /public/{fonts,sounds,wallpapers} now returns 403
ASSET_TOKEN_REQUIRED unless the URL carries a valid ?asset_token=.
Previously the token only steered the Cross-Origin-Resource-Policy
header, so any local process or DNS-rebinding page could read user
assets by guessing filenames. Every legitimate consumer already signs
its URLs, so enforcement costs nothing for clients.
Wallpaper and thumbnail URLs read the asset token from a plain module variable, so a URL built before the token was set (pairing path: invalid token at boot, then pair, then token) never rebuilt — the wallpaper stayed blocked (403 / cross-origin) until a manual profile change. Auto-auth hid this because the token was set before the first render. Each reactive consumer now reads assetTokenReady so Vue re-runs the URL build once the token lands; fonts and bell sounds are imperative calls already ordered after initAssetToken.
This was referenced Jun 10, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #58
What
Three explicit background modes per scope — Image / Solid theme / Transparent — replacing the implicit "wallpaper set or not" model, plus native window effects on desktop (mica/blur/acrylic on Windows, vibrancy on macOS).
transparent: trueon all OS; CSS paints opacity in non-transparent modes, so the mode switches instantly per the active pane's scope (same cascade + cross-fade mechanics as the window-wide wallpaper).autoresolves to mica on Win11, blur on Win10, under-window vibrancy on macOS; hidden on Linux where every effect resolves to none). Unknown/invalid values degrade tonone.useWindowEffectsderives the desired effect from the displayed background state exposed byuseActiveWallpaper(resolved, cached, or fallback) — the effect can never outlive the painted background (pane closed, hung cascade fetch, cached scope, etc.). Native IPC is latest-wins, serialized, with reconciliation after stale completions.image+ empty wallpaper reproduces today's two states exactly — no profile migration.Desktop asset-loading fixes (surfaced by the real-build smoke test, pre-existing on main)
hubBaseUrl()-prefixed URLs — relative/public/...URLs resolved againsttauri://localhostand 404'd in the desktop app./public/*assets (fonts, sounds, wallpapers; disk and SEA in-memory serving) now carryCross-Origin-Resource-Policy: cross-origin— helmet's blanketsame-originmade WebKit block font/image embeds from thetauri://localhostorigin even with valid CORS. API routes keepsame-origin; no other helmet setting changed.Spec:
docs/plans/desktop-translucency.md(19 BDD scenarios, hardened). Brief:docs/briefs/desktop-translucency.md.Verification already run
pnpm install --frozen-lockfileclean (new deps:@tauri-apps/plugin-os+ cargotauri-plugin-os).cargo clippy --all-targets -- -D warningsclean; hub/web/shared typecheck clean.mainis already lint-red on 4 pre-existing files — tracked separately, untouched here).core:window:allow-set-effectscapability, a setHeaders typing break, the stale-effect lifecycle class (fixed structurally), and the OS capability narrowing (os:allow-platform/os:allow-versioninstead ofos:default). Copilot out of credits for the whole cycle — documented degraded coverage (codex + senior architectural review).Notes for review / before merge
transparent: truehas a known ~8× GPU power cost on macOS even for static content ([bug] macOS: transparent:true causes continuous full-window recomposite (~8x GPU power) even when the page is fully static tauri-apps/tauri#15471, open upstream) — accepted deliberately (macOS is not a tested distribution target yet); the mitigation path (macOS-only restart-gated opt-out) is designed in the brief.