# Blur behind panels (#9809, #9810) Implementation note for PRs 5 and 6 of the #8876 series. Native rendering, theme-declared controls, runtime availability and a transient quality guard are present. Built-in themes/defaults are unchanged. No build, test, GPU run, screenshot, benchmark or pixel comparison has been run by this worker. The 22 ms/60-sample threshold remains a starting policy, not a measured tuning result. ## Theme controls and runtime intent Only a theme declaring `blur` offers its registered 0..60 physical-pixel range. The slider edits in steps of one; finite fractional persisted/API values remain valid and merely opening the control does not round them. Zero has a muted hollow thumb, Off readout and flat 2 px cost line. Positive values use accent fill/thumb, a px readout, and five bars of height 4/6/8/11/14 px: `ceil(radius / 8)` are filled, saturating at five from 33 through 60 px. The tooltip describes relative cost; it is not a timing or resource estimate. The readout, meter and any disabled reason reserve space beside the slider. An inactive parent prevents activation without changing the stored child value. The app resolves availability once per UI/API composition and before positive runtime activation. It uses the actual installed adapter's device type (cached at installation, `Cpu` means `gpu-software`) and PR5's lazy cached renderer capabilities, rather than renderer-name matching. Both current browser paths are unavailable because they lack the native compositor integration. Unsupported source/device paths and device/resource failures also fall back to ordinary painting. There are no effect resources or markers on unavailable paths or at zero. Opening Preferences alone does not activate a declared zero-radius effect. Saved/raw intent is separate from runtime availability. Clearing an OS/device restriction restores it automatically when the renderer can support it. Suspending for a restriction never toggles the radius through zero to clear a latched resource failure. Actual radius/host/size/background changes retain PR5's explicit recovery boundaries; no polling loop retries a failed effect allocation every frame. Authored declaration/ancestry and effective radius are resolved only on `prepare_tabs`' existing theme/raw-option cache miss. Its borrowed content comparison also catches followed reloads with unchanged id/base. Steady built-in, undeclared, and offered-zero frames read a small copyable intent/revision: they do not order option vectors, normalize raw maps, serialize/hash strings, or clone theme ids. Runtime OS/device availability stays outside this cache and palette tokens. The cache test counts normalization calls; it is not a global allocator measurement. ## Accessibility signals The desktop injects a cached `Services::reduce_transparency` reading. Observation starts lazily when a theme offers blur, reads on a background thread every five seconds while needed, and repaints only when the cached signal changes. No UI-thread registry/Objective-C IO, external processes, OS-setting writes, notification registration or new unsafe code is used. The first unavailable/failed reading is unknown, leaving other availability checks in charge; an idle thread stops reading when the theme no longer offers blur. - Windows: safe `winreg` reads HKCU `Software\Microsoft\Windows\CurrentVersion\Themes\Personalize\EnableTransparency`. `0` disables blur (system transparency off), `1` permits it; missing/unexpected values are unknown. - macOS: safe objc2 `NSWorkspace::sharedWorkspace()` / `accessibilityDisplayShouldReduceTransparency()`, with `NSWorkspace` and `NSAccessibility` features and an autorelease pool on the observer thread. - Other operating systems currently have no signal here. Unknown does not disable blur. Browser rendering is independently unavailable as described above. Platform readings and propagation latency still require manager validation on their respective systems. Evidence fixtures never modify the host OS settings. ## Automatic quality guard The guard uses elapsed **native queue-submission intervals**, not `app.frame_ms` (UI construction time) and not GPU timestamps. The pass owner records evidence only after an actual composition/presentation with a visible blur marker has been submitted. Sampling consumes each egui frame at most once and requires the immediately preceding painted frame, continued repaint activity on both sides, positive radius, runtime availability, and a viewport not reported minimized or occluded. Missing/logic-only paints, idle waits, unavailability, zero, or a changed theme revision/radius reset the streak. egui layout re-passes cannot add samples. Monitoring requests no continuous repaint. After **60 consecutive eligible intervals strictly over 22 ms**, lower the effective pass tier by one, down to two down/up steps. The natural tier remains determined by radius; the effective tier is `min(natural tier, session cap)`. Radius is never reduced or switched off. Recompute the normalized Kawase offset for that step count, include the cap in the source cache signature, and reset the streak. One inline notice is offered per session. Dismiss hides it; further eligible reductions can continue to the minimum without reannouncing it. **Undo restores the cap from before the first guard reduction and opts out of all further automatic reductions for this session**, including across theme changes. This is transient UI state, not a preference, design edit or document Undo entry. Submission intervals include CPU/event-loop scheduling, presentation pacing and other rendering work; they do not isolate blur or GPU execution. A low-refresh or loaded system can exceed the starting budget for unrelated reasons. A cached blur image still counts when visibly composited. Visibility depends on host reporting; unsupported occlusion reporting cannot be inferred. Deterministic tests exercise threshold/eligibility behavior; hardware measurements and threshold tuning are still manager work. No measured GPU timing claim is made. ## Theme notices Invalid saved entries, unsupported newer requirements and guard reduction share a compact inline notice inside the existing Preferences host, before the empty-row return. Dismissal is session-only, bounded to 64 problem identities and keyed by theme, problem kind and raw value/requirement identity. A changed problem reappears. Dismiss never changes invalid raw JSON or the API's notices; defaults remain in effect. Newer-requirements imports discard unsupported definitions, so the warning instructs users to upgrade and **reimport the original theme**, not to expect discarded tables to recover automatically. No controls/no notices produces no header or spacer. ## Accepted CPU budget The human decision is: **"Allow measured CPU checks; keep the change small."** This replaces the spike's proposed static policy propagation and its unresolved single-check requirement. The implementation has cheap per-host checks, a root UI scope, and outer GPU preparation/composition/submission checks. It does **not** claim one check per frame, two total dispatches, or measured zero CPU overhead. There are no blur branches in `egui_wgpu::Renderer::render`'s primitive loop. The initial source integration is for the root native viewport. The pass owner supplies its viewport id before preparation; other native viewports use plain painting and `capability_for` reports their missing source integration. They cannot sample the root window's source or retain its pending submission key. At radius zero, `Container` uses the original frame shape/painting path; no marker, Arc/Box for a blur callback, metadata vector, blur pipeline, texture, uniform, binding or extra pass is created. The plain Window/Panel hook delegates to the original `Frame::show` path, including its existing content boxing. A previously enabled effect is detached and its CPU/GPU storage dropped on zero; wgpu retains any in-flight references until their submitted work completes. Existing viewport/MSAA/AO/OIT resources are baseline resources, not blur resources. An explicit harness tint override intentionally changes ordinary panel fills even at zero; leave `surface_alpha` null for comparisons against main. ## Actual frame path and insertion points The pinned dependencies are egui/egui-wgpu/eframe 0.36.2 and wgpu 30.0.1. eframe and egui remain registry dependencies; only egui-wgpu has local compositor changes for blur. The egui dependency is the published 0.36.2 crate. | Stage | Actual path | | --- | --- | | Build UI | Native `eframe::App::ui` calls `SolveApp::ui` (`crates/ui-egui/src/lib.rs`). `blur::State::begin` installs a stack-scoped thread-local host policy. `cad_ui` draws the workspace under floating cards, or in the remaining central rectangle for docked layout. | | View background | `viewport.rs::show` emits an egui gradient mesh before the viewport callback. `render_workspace.rs::show` emits its effective preview background, shared with the blur source. Neither is contained in the GPU renderer's scene texture. | | Prepare scene | `gpu.rs::ViewportCallback::prepare` calls `solidcraft_gpu::view::Renderer::prepare`. Native `set_wgpu_split` already draws scene depth/MSAA/OIT offscreen. The UI target has one sample and no depth. The resolved scene is transparent outside geometry. | | Acquire/order | `vendor/egui-wgpu/src/winit.rs::Painter::paint_and_update_textures` acquires/probes the drawable before preparing callbacks. `Renderer::update_buffers` uploads meshes, runs all callback `prepare`s, then all `finish_prepare`s. The enabled frame hook supplies actual physical target extent and the effective scale, including surface clamping. | | Build source | The enabled `blur_native.rs::Source` replaces the viewport callback. Its `prepare` delegates unchanged to the original callback. Its `finish_prepare` retains the independent background/prepared viewport source and availability/cache evidence. A source cache hit avoids rebuilding that source. | | UI composition | The native pass owner initializes C from the cached source and segments the original ordered UI stream at semantic backing markers. Before each marker it runs the existing blur pyramid from current C, then the marker samples B. Ordinary fill, border and content follow. The replaced source callback is skipped during composition. | | Present/capture | A final triangle presents C into the normal drawable or existing capture texture. Existing screenshot copying, queue guard, texture frees and presentation remain after that. `submitted` commits a pending source key only after queue submission. | `CallbackTrait::paint` receives an already active pass, not its encoder or target; it cannot redirect the UI or start blur passes. Callback command buffers execute before the main encoder. The pass owner ends each UI segment before encoding its dependent blur pyramid and starts the next segment on the same encoder. No additional callback command buffers are returned. ## Ordered backdrop and color contract S, B and C retain separate resources and jobs. S is the independent cached viewport/background source used for initialization, availability and evidence. It includes prepared model/sketch/highlight/preview geometry, section caps, reference images and point clouds. Source construction uses the actual prepared renderer, background colors, placement and inherited clip. C is the current ordered UI composition. It starts from the source, then receives ordinary egui shapes in their original order. Before each actual translucent backing marker, the existing down/up pyramid filters current C into B. The marker samples that result before its ordinary surface fill and contents are painted. Thus menus, dialogs and nested cards include preceding UI in their backdrop; they do not replace underlying panels with the model scene. B is refreshed at each boundary rather than shared unchanged by all panels. The final presentation samples C. Read and write targets remain separate throughout the pyramid. This uses the existing effect resources and adds **2 × steps GPU passes per actual backing boundary**. Source-cache hits still save source construction; they do not eliminate the per-boundary backdrop filtering. No extra resources or disabled-path GPU passes are introduced. Outside the model rectangle the source remains the deterministic background; geometry is not invented under docks. Render preview uses its original product `Frame`, lighting, resolution reduction and effective preview background, including authored-scene precedence and the animated tab gradient. It does not call a model snapshot renderer or replace the Render scene settings. Home/result-image contexts are unavailable; a missing source callback in a painted frame drops target storage and reports unavailable instead of sampling a previous workspace's pixels. Source color is opaque viewport/background color. Translucent custom viewport or outside backgrounds report unavailable before effect GPU allocation; their composition with a transparent native window is not silently approximated. For an unorm UI target, the first down pass decodes each composition texel before bilinear interpolation; for an sRGB target, texture sampling performs the decode. All subsequent filtering is linear. Marker output follows egui's gamma-premultiplied convention: convert B to gamma, multiply RGB/alpha by rounded coverage and host opacity, and for an sRGB attachment decode that premultiplied RGB before framebuffer conversion. This matches egui's existing shader ordering rather than treating its output as straight-alpha linear color. Presentation samples C in its own format; it does not apply a second manual gamma transform. The harness exercises both unorm and sRGB formats. The worker has not yet verified those images on hardware. ## Semantic hosts, clipping and rounding Blur eligibility follows actual backing UI surfaces rather than a workspace-surface whitelist. Panels, ribbon and application bars, History, system windows, modal dialogs and their dimming backdrops, menus/submenus, popovers, canvas editors, notifications, file-loading shields, and tooltips use the same marker policy. Nested backing cards participate too. Zero-alpha layout frames stay transparent; text, strokes, selection/hover tints and model geometry do not create backing markers. The effective fill alpha includes inherited painter opacity, so a normally opaque surface fading in can participate without treating its text as another surface. The app uses stock egui 0.36.2 public host APIs. `show_frame`, `show_backing`, `show_edge`, `show_central` and bounded Window reserve a slot before the original frame paints. Native popup and tooltip wrappers keep their placement, hover gates, stacking, close behavior and response types. Response tooltip extension methods route native enabled/disabled and pointer-anchored variants through the same policy. Manual backing surfaces replace their original reserved slot, preserving ordering. The off path calls the original hosts without marker slots or effect allocations. `show_backing` adds no inset decoration to callers which previously had none. Modal rendering first reserves full-screen backdrop blur, then retains native input shielding/focus/dismissal and dimming. Its visible dialog frame is painted through `show_backing` after dimming, so the dialog samples the actual dimmed UI beneath it. The off path remains the original native Modal. Existing host geometry, interaction and legacy paint assertions require manager validation after this iteration; no automated checks were run by these implementation workers. Markers retain all four final corner radii, host clipping and painter opacity. Clipping does not invent rounded corners, and moved surfaces sample corresponding screen pixels. The compositor respects ordered preceding UI, so a translucent popup above a panel blurs the panel beneath it rather than cutting through to the model. No private storage access, global matching-color search or arbitrary rectangle heuristic selects backing surfaces. ## Radius, cache, allocation and recovery Radius is finite physical pixels in 0..60; invalid requests are refused before mutation. Zero selects plain rendering. Positive tiers have N down and N up passes: | Radius | N | First down target | | --- | --- | --- | | >0..8 | 2 | ceil-half resolution | | >8..18 | 3 | ceil-half resolution | | >18..30 | 4 | ceil-half resolution | | >30..60 | 5 | ceil-half resolution | Radii above 40 retain the existing five-step tier and increase only its normalized offset. Existing 0..40 values keep their original schedule and cost indication. Every subsequent dimension is ceil-halved with a minimum of one pixel. Upsampling ends at full resolution B. The original kernel has a center-plus-four-corners down filter (weights 4,1,1,1,1) and an eight-tap up filter (axis weight 1, diagonal weight 2). The offset, in input texels, is `radius / (2 * sqrt((35/18) * (4^N - 1)))`. This normalizes the filters' tap sigma across tier changes; resampling contributes an additional footprint. This is a documented radius mapping, not a measured Gaussian-radius equivalence. Physical spread and tier-boundary appearance still require the manager's impulse/golden review. `view::frame_key` is exposed without changing its existing behavior. The blur signature includes that exact scene signature (including camera, lighting, selection/hover, preview, images/clouds, analysis, section, AO and supersampling), source/background rectangles and clip after the source layer's final global transform, background colors, physical target extent, effective scale, target encoding and radius. It is per device/host; installation on a replacement device retires the old state. A pending key becomes valid only after submission. A resting repaint can reuse the source, while each painted backing boundary still filters the current UI composition. Panel-only tint/layout changes reuse the full-target source unless they change projection, source placement or extent. No repaint timer is added to keep blur alive. The source captures its Context/layer only in the enabled branch. `finish_prepare` queries `layer_transform_to_global` after UI emission, including a transform set after the callback shape was emitted. One validated snapshot transforms viewport, background and inherited clip for both the cache key and source painting. The callback shape stays local so egui applies its own transformation exactly once. All effect GPU allocation is lazy and bounded. The target budget is 256 MiB, computed with checked arithmetic before allocation, using four-byte S/C and eight-byte down/up targets. Uniform bytes are also reported. The budget does not claim to account for opaque driver pipeline/binding memory. Actual device limits and format capabilities are queried. Resource creation/marker growth use error scopes and return unavailable on refusal; failures latch until radius/host/size changes, avoiding an allocation retry storm. Positive radius changes currently retire/recreate effect storage, including pipelines; this is deliberately simple but cold activation cost must be measured before PR 6 adds an interactive slider. Native zero physical window size suspends the effect before callback preparation, including when the surface was clamped to 1x1. Geometry/scale is checked before casts or allocation. Device loss suspends storage before further effect work; existing device probing and acquired-frame lifetime handling are retained. Replacement size/device rebuilds from a fresh signature. No explicit destruction of in-flight textures, new unsafe block, unwrap/expect/panic path, synchronous readback, or GPU completion wait was added to production painting. ## Capability and other integrations `blur::Capability` is `Available` or `Unavailable(reason)`. `State::capability` distinguishes missing GPU/native split integration, software adapter, unsupported filterable/renderable formats, unsupported source context, missing source frame, resource/size refusal and lost device. The actual window adapter is classified from its actual cached `DeviceType` (`Cpu` is software); no renderer-name guessing. Software adapters and web paths allocate no effect resources or markers. Web still uses eframe's unvendored `src/web/web_painter_wgpu.rs`, not the native pass owner. Both WebGPU and WebGL2 report unavailable until that owner is wired with equivalent acquire/zero-size/error ordering. This is an integration limit, not a claim that WebGPU fragment blur is impossible. No eframe/epaint patch is used. `ui.render`, CLI snapshots and scenario `shot` remain model-only captures, through `engine::capture`/`gpu::capture::Capturer` or the existing CPU fallback. They do not paint egui panels and cannot prove blur correctness. Their code paths are unchanged. The full GPU fixture in existing `gpu.rs::tests` renders actual app egui output, font textures, viewport callbacks and compositor into a readback target. Tests remain in the existing area modules; no per-issue test module was added. The PR6 controls consume this capability/reason and finite 0..60 contract, leave saved intent intact on fallback, and request a frame on changes. They do not create resources merely by opening Preferences. Blur remains a theme option, with no independent global switch or quality preference. ## Internal activation and manager-only validation The control hook is `ui.blur {radius_px?, surface_alpha?, fixture?}`. `surface_alpha` is an internal capture-only override in 0..1; null restores ordinary fills. Transparent host frames remain transparent. The hook returns requested radius, maximum, capability, app-aware availability, transient guard/evidence state and cumulative counters. Explicit radius activation is an internal session-only override; ordinary product activation comes from a declared theme option. Default construction is zero and performs no environment lookup or GPU capability probe. The adapter capability probe is cached lazily on activation or an explicit query. Counters increment at actual `create_texture`, buffer/bind-group/pipeline creation, source/chain/UI/present encoding and marker emission/drawing. They include cache hits and live texture/buffer/group/pipeline counts and texture/uniform bytes. They are effect counters, not a fabricated whole-frame allocator or driver trace. At zero, test deltas must remain zero and all live effect counts must be zero. CPU allocations must additionally be measured with an external allocator/profiler; no unsafe counting allocator was added. Timing alone cannot prove zero allocations. No commands below were run by this worker. Manager's minimal focused checks: ```powershell cargo test --locked --profile ci -p solidcraft-ui-egui --lib blur_stock_ -- --nocapture cargo test --locked --profile ci -p solidcraft-ui-egui --lib blur_ -- --nocapture cargo build --locked --profile ci -p solidcraft ``` The five `blur_stock_` tests compare stock and app-owned hosts without a GPU: zero output includes identical Noop counts; positive output preserves chrome, semantic fill/frame information and persistent IDs; asymmetric margins, stroke width, host clipping, final joined corners and late layer transforms are covered. They replace the old test of the vendored Window hook. They are authored only, not executed by this worker. Open-flag forwarding also covers initially hidden, closed and reopened windows, including stock close-request propagation via `ui.close_kind(egui::UiKind::Window)`. Plain `ui.close()` targets Window's nearer internal Collapsible, even when native title/collapse controls are disabled. The other blur tests remain; PR5's broader `blur_` filter selected fourteen tests before the PR6 additions listed below. Cargo.lock was restored as text to the egui 0.36.2 registry source and checksum from the matching local crates.io index entry; dependency versions are unchanged. The full vendored egui copy and its path patch were removed; the egui-wgpu patch and licences remain. PR6 additionally lists the already-locked winreg dependency for the desktop app; dependency versions are unchanged. No cargo execution was used. Set `SOLIDCRAFT_REQUIRE_GPU=1` to require an adapter (hardware or software); a missing adapter/device fails every blur fixture consumer. The explicit `cfg(test)` rendering fixture runs all positive compositor, cache, pixel and suspension assertions on CI software adapters independently of production software policy, retaining real adapter identity and format/device requirements. A separate production-policy test asserts that actual software adapters report `Unavailable("software adapter (gpu-software)")`, allocate no blur resources and emit no markers. Production software blur remains unavailable. Its requested radius remains 18 throughout UI preparation: fallback must not silently replace the request with zero. Positive fixtures use isolated harness intent so theme preparation cannot erase their explicit radius. `FixturePolicy::Production` and `FixturePolicy::Rendering` exist only under `cfg(test)`. Rendering bypasses only CPU policy through State's capability result; the shared Preferences availability resolver consumes that result instead of independently vetoing the real software identity. No production bypass is added. To export fixed-scene 8/18/40 and zero light/dark images, counters, and warmed UI/encode/GPU-completion benchmark samples: ```powershell $env:SOLIDCRAFT_REQUIRE_GPU = '1' $env:SOLIDCRAFT_BLUR_EVIDENCE = Join-Path $env:TEMP 'solidcraft-9809-blur-review' cargo test --locked --profile ci -p solidcraft-ui-egui --lib blur_ -- --nocapture ``` `SOLIDCRAFT_BLUR_GOLDENS` can name a separate reviewed PNG directory for decoded RGBA equality. No expected pixels were invented or automatically blessed here. The fixed fixture tests both target encodings, source independence from panel tint, rest/cache hits, camera invalidation, exact tier pass counts, on/off pixel restoration, hostile sizes/radii, suspension/resource release, host-slot ordering, and late layer translation/scaling. The latter checks transformed viewport, background/clip placement, cache invalidation/reuse, and pixels outside the moved clip even on a forced miss. Further device-loss, resize/DPI, clipping/overlap and calibrated radius review remain manager validation work, not claimed test results. For actual window evidence after the manager builds, start the branch app with the same sample, dimensions, camera/theme and graphics settings as the baseline. Use the manager's isolated TEMP settings/control-blur launch environment and its written token file (`$blurTokenPath` below). Port 38876 is the user review app and must not be contacted or relaunched; blur validation uses explicit port 38877: ```powershell .\target\ci\solidcraft.exe --sample --control 38877 # In a second shell; always choose a NEW output directory: .\scripts\blur-evidence.ps1 -Port 38877 -TokenPath $blurTokenPath -OutputDirectory "$env:TEMP\solidcraft-9809-window-review" ``` The collector saves ordinary zero UI/model captures before applying a tint, light/dark 0/18 UI screenshots, feature counters, and warmed CPU `frame_ms` plus screenshot-roundtrip timing samples. Roundtrip timing includes PNG readback/I/O and is explicitly not GPU frame timing. The full fixture benchmark separately waits for GPU completion, only in test code. For before/after zero CPU timing, connect the collector to the manager's separately running main baseline with `-Baseline -Port -OutputDirectory `. It never launches/builds the baseline or overwrites existing evidence. Baseline artifacts under TEMP/solidcraft-8876-evidence and its checkout were not modified. Compare decoded images, not PNG file bytes. If ffmpeg is available, its RGBA framemd5 output provides pixel hashes and dimensions without Pillow: ```powershell $mainPng = "$env:TEMP\solidcraft-8876-evidence\blur-main-zero-ui.png" $branchPng = "$env:TEMP\solidcraft-9809-window-review\zero-ui.png" $mainDecoded = (& ffmpeg -v error -i $mainPng -frames:v 1 -pix_fmt rgba -f framemd5 -) -join "`n" if ($LASTEXITCODE -ne 0) { throw 'Baseline decode failed' } $branchDecoded = (& ffmpeg -v error -i $branchPng -frames:v 1 -pix_fmt rgba -f framemd5 -) -join "`n" if ($LASTEXITCODE -ne 0) { throw 'Branch decode failed' } if ($mainDecoded -cne $branchDecoded) { throw 'Decoded RGBA/dimensions differ' } ``` Report timing medians/ranges, machine, adapter/backend/driver, size/DPI and power settings from alternating warmed baseline/branch runs. Use PIX/RenderDoc or an allocator profiler for complete GPU/CPU allocation/pass accounting. The manager measured the exact prerequisite base `f1c2fc880`; comparisons require identical restored UI/model settings. Current validation records belong in the PR body; the new fixture is not a substitute for that comparison. Later manager gates are wasm, layers, licences, lists, containers/clippy, then full CI before commit/merge. Worker execution counts: tests/builds/checks/cargo/xtask/UI runs/screenshots = 0. ## PR6 manager reproduction and focused validation These instructions are prepared, **not executed or visually verified by the worker**. Use only the manager's isolated settings instance and control port (for example 38877); do not contact, restart or change the user's preview on 38876. No host OS setting changes are needed for the injected examples. A real OS-signal review must be reported separately from fixture screenshots. The original theme fixture is `crates/ui-egui/tests/fixtures/theme-blur.json` (id `blur-review`). It offers opacity, blur and tab colours, defaults blur to zero, and includes Sketch/Render surfaces. Send its JSON object as `ui.prefs`' `theme_import`, together with `open:"Interface"`. For light screenshots, copy the object in memory, change `name` to `Blur review light` and `base` to `light` (id `blur-review-light`), and import that. Model uses the base palette; switch to Sketch/Render separately to review the authored tab colours. Builds, launches, authenticated transport and screenshot saving belong to the manager. For each light/dark fixture, keep model, camera, layout, scale and window dimensions fixed. Use normal `ui.prefs`, with the active theme id, for each **0, 8, 18, 40**: ```json {"method":"ui.blur","params":{"fixture":"clear","surface_alpha":null}} {"method":"ui.prefs","params":{"theme_options":{"blur-review":{"opacity":75,"blur":18}}}} {"method":"ui.screenshot","params":{"path":"ABSOLUTE-NEW-OUTPUT-PATH.png"}} ``` At zero expect Off, a hollow muted thumb and one flat cost line; at 8/18/40 expect one/three/five filled bars. Verify the slider consumes remaining width, the readout and meter do not overlap, and the disabled reason remains beside its control. Also send `blur:18.25`, reopen Preferences and query `ui.prefs`: the value must remain 18.25 until an actual edit. At opacity 75 capture matched **blur 0 versus 18** views; repeat with opacity 100 as the opaque comparison. The fixture's opacity option enforces PR3's readability floors; do not assume opacity 0 means invisible text. If an explicit opacity-zero comparison is desired, send `opacity:0` at both blur 0 and 18 and report that readability floor. These are real UI captures, not `ui.render`. To display two unavailable reasons without changing this machine's OS/adapter: ```json {"method":"ui.blur","params":{"fixture":"gpu-software"}} {"method":"ui.prefs","params":{}} {"method":"ui.blur","params":{"fixture":"reduce-transparency"}} {"method":"ui.prefs","params":{}} {"method":"ui.blur","params":{"fixture":"clear"}} ``` Capture each disabled slider with its adjacent reason. Label these screenshots **injected availability evidence**, include the `evidence_fixture` reply, and verify saved blur remains 18. Clearing the fixture restores normal real capability checks; it cannot make a software/unsupported renderer capable. Compare effect counter deltas/live counts while unavailable; there must be no new effect work and no live effect resources. `web-unsupported` is an optional third reason fixture. For the guard notice, set blur 40 via `ui.prefs`, let the value apply, then send: ```json {"method":"ui.blur","params":{"fixture":"guard-60"}} {"method":"ui.blur","params":{}} ``` This explicitly injects sixty eligible 23 ms samples, without inventing GPU timing or starting a repaint loop. Capture the notice with **injected guard evidence** in its description. Repeat the fixture to exercise cap 4 → 3 → 2 (no radius change). Click Undo (`prefs:theme_notice_undo:guard`): cap returns to 5 and `opted_out` is true; another `guard-60`, even after switching themes, cannot reduce it. In a separate fresh session, Dismiss (`prefs:theme_notice_dismiss:guard`) hides the notice but subsequent injections may keep reducing to 2 without reannouncing. `fixture:"clear"` removes the injection/legacy radius override, not Undo's session opt-out. Restart the manager's isolated app when a fresh session policy is needed. For raw/newer warnings, the extended existing `prefs_theme_files` scenario saves blur through `ui.prefs`, reimports that theme without the declaration, dismisses the now-unknown-entry notice, checks that raw JSON and API notices persist, and reintroduces the definition to restore the saved value. It also imports `requires:2` and dismisses its warning without adding a controls header. The unit test additionally loads malformed raw JSON, changes it to ensure reappearance, and bounds dismissal storage. For a manager screenshot, import a copy of the fixture with name `Future blur review`, `requires:2`, then open Interface; retain the original file for reimport after upgrading, as its unsupported tables are discarded. All nine new unit tests match the **`blur_`** filter, in the existing area modules: - `blur_availability_preserves_saved_intent_and_parent_state` - `blur_evidence_fixtures_are_atomic_transient_and_never_fake_support` - `blur_guard_requires_consecutive_visible_actual_paints` - `blur_guard_threshold_idle_minimum_and_session_undo` - `blur_intent_cache_resolves_only_when_theme_or_raw_values_change` - `blur_meter_boundaries_and_off_thumb` - `blur_notices_dismiss_by_problem_identity_and_preserve_raw_json` - `blur_offered_zero_has_no_effect_work` - `blur_slider_reserves_readout_meter_and_disabled_reason` The existing `blur_radius_schedule_and_hostile_values` also checks capped offsets. The zero test uses actual GPU fixture counters with a declared zero option; the cache test asserts normalization counts on 100 repeated built-in/zero/undeclared cache hits. Neither pretends to count the application's global allocator. The headless UI tests inspect constructed shapes/geometry, not screenshots. Manager-only focused filters (none run by the worker): ```powershell cargo test --locked --profile ci -p solidcraft-ui-egui --lib blur_ -- --nocapture cargo test --locked --profile ci -p solidcraft-ui-egui --lib theme_options_ -- --nocapture cargo test --locked --profile ci -p solidcraft-ui-egui --test scenarios prefs_theme_files -- --nocapture ``` The zero GPU fixture uses `FixturePolicy::Production`, a declared zero option and no harness radius override. It follows the same adapter-availability gate: `SOLIDCRAFT_REQUIRE_GPU=1` requires any adapter, including software, and a missing adapter/device fails. `blur_production_policy_rejects_software_adapter` separately checks production CPU fallback; positive shader tests select `FixturePolicy::Rendering`. The PR5 fixture policy and PR6 integration have been reconciled in source only. GPU pixels, platform builds/APIs, wasm, actual OS propagation, real visible slow-frame behavior, allocator traces and threshold calibration remain unverified here. No test/build/check or app interaction has been performed by this worker.