fix(web): serve static web assets, and fix a DI error Production was masking (#63) - #65
Merged
Merged
Conversation
…masking (#63) Two bugs, and the second was hiding the first. ## 1. No framework JavaScript, so the UI was inert GET /_framework/blazor.web.js -> HTTP 500, 0 bytes GET /app.css (compressed) -> HTTP 200, 0 bytes System.IO.FileNotFoundException: Could not find file '.../src/Presentation/Web/wwwroot/_framework/blazor.web.js' at StaticAssetDevelopmentRuntimeHandler.AttachRuntimePatching No Blazor circuit could start, so every interactive page — Search, Activity, and the new Settings page — rendered its initial HTML and then did nothing. Compressed CSS came back empty too, so the UI was unstyled. Nothing logged an obvious error; it just looked broken. Root cause: the framework calls UseStaticWebAssets() only in Development, and none of our hosts run as Development — there are no launch profiles, and Aspire does not set one either. Without it the static-web-assets manifest is never loaded, so relative asset paths resolve against wwwroot, where framework assets and the content-hashed compressed files do not exist. The manifest itself was correct all along: blazor.web.js pointed at the microsoft.aspnetcore.app.internal.assets package and app.css.gz at obj/<config>/net10.0/compressed/<hash>.gz. Fixed by calling builder.WebHost.UseStaticWebAssets() explicitly. It is a no-op when the manifest is absent, i.e. in a published app where assets are copied into wwwroot, so production behaviour is unchanged. Rejected alternative: adding launchSettings.json with ASPNETCORE_ENVIRONMENT=Development to each host. It fixes the standalone case but is the wrong lever — it would also flip Development on for anything that runs the built binary, and an applicationUrl in those profiles interfered with Aspire's own endpoint allocation. Fixing the asset loading directly is narrower and works regardless of environment. ## 2. IArrClient was never registered anywhere Trying to run in Development to test the theory surfaced this: Unable to resolve service for type 'Krautwatch.Domain.Interfaces.IArrClient' while attempting to activate 'TestArrConnectionHandler' AddApplication() registered TestArrConnectionHandler unconditionally, but no host called AddArrClient(), so IArrClient had no registration at all. Every host would fail to start under DI validate-on-build, and in Production — where validation is off — the Test button would simply have thrown at the moment a user clicked it. Mine, from #61. Fixed by following the pattern this codebase already uses for RunDownloadHandler ("registered by the Downloader host only — it needs IDownloadProvider"): the handler is registered by the Web host, alongside AddArrClient(), because the two cannot be separated. Worth noting why this went unnoticed: running as Production disables DI validate-on-build, so the very bug that made the UI inert also suppressed the startup error that would have pointed at the missing registration. ## Verified on both run paths Standalone (dotnet run --project src/Presentation/Web): blazor.web.js plain 200/200645b gz 200/55644b app.css plain 200/4798b gz 200/4831b Aspire fleet (dotnet run --project src/Presentation/AppHost), on a clean environment: Web host on :51578 blazor.web.js plain 200/200645b gz 200/55644b app.css plain 200/4798b gz 200/4831b Both were 500/0b and 200/0b before. The Web host also now starts under DI validation. Tests: App 88 / Arch 11 / Infra 102 green, 0 warnings. Closes #63. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.
Fixes #63. Two bugs — and the second was hiding the first.
1. No framework JavaScript, so the whole UI was inert
No Blazor circuit could start, so every interactive page — Search, Activity, and the new Settings page — rendered its initial HTML then did nothing. Compressed CSS came back empty too, so the UI was unstyled. Nothing logged an obvious error; it just looked broken.
Root cause: the framework calls
UseStaticWebAssets()only in Development, and none of our hosts run as Development — there are no launch profiles, and Aspire doesn't set one either. Without it the static-web-assets manifest is never loaded, so relative asset paths resolve againstwwwroot, where framework assets and the content-hashed compressed files don't exist.The manifest was correct all along —
blazor.web.js→ themicrosoft.aspnetcore.app.internal.assetspackage,app.css.gz→obj/<config>/net10.0/compressed/<hash>.gz. Nothing was stale or missing; it simply was never consulted.Fix: call
builder.WebHost.UseStaticWebAssets()explicitly. No-op when the manifest is absent, i.e. in a published app where assets are copied intowwwroot— so production behaviour is unchanged.Rejected alternative:
launchSettings.jsonwithASPNETCORE_ENVIRONMENT=Developmenton each host. It fixes the standalone case but is the wrong lever — it flips Development on for anything running the built binary, and anapplicationUrlin those profiles interfered with Aspire's endpoint allocation. Fixing the asset loading directly is narrower and environment-independent. (I tried this first; it's in the branch history as a dead end.)2.
IArrClientwas never registered anywhereTrying to run in Development to test the theory immediately surfaced:
AddApplication()registeredTestArrConnectionHandlerunconditionally, but no host calledAddArrClient()—IArrClienthad no registration at all. Every host would fail to start under DI validate-on-build, and in Production (validation off) the Test button would simply have thrown the moment a user clicked it.This one is mine, from #61. Fixed by following the pattern this codebase already uses for
RunDownloadHandler("registered by the Downloader host only — it needs IDownloadProvider"): the handler is registered by the Web host alongsideAddArrClient(), because the two can't be separated.Worth stating plainly why it went unnoticed: running as Production disables DI validate-on-build, so the very bug that made the UI inert also suppressed the startup error that would have pointed straight at the missing registration.
Verified on both run paths
blazor.web.jsapp.cssgzblazor.web.jsapp.cssgzAspire verified on a clean environment (leftover containers and processes removed first — a stale environment sent me chasing a phantom "children aren't binding ports" problem that turned out to be my own
grep -i dotnetmissing processes namedKrautwatc).The Web host also now starts under DI validation, which it previously could not.
Tests: App 88 / Arch 11 / Infra 102 green, 0 warnings.
Next
This unblocks the browser test-ride of #64 (the settings page), which is why it went first.
🤖 Generated with Claude Code