Repository navigation
Conversation
Embedded UI responses carried only content-type (and CSP for HTML), so browsers were free to hold a stale index.html across server upgrades — leaving the app pinned to an old bundle until a hard refresh. This is one of the stale-UI mechanisms tracked in anomalyco#51858. - index.html and all unhashed files (manifest, icons, theme preload, verbatim public/assets files): cache-control: no-cache, so the document revalidates on every load. - Bundle assets under assets/ whose filename carries the Vite content hash: public, max-age=31536000, immutable. - ETag (sha256 of body) on every embedded response + If-None-Match -> 304, so revalidation is cheap. - x-opencode-version header so clients can detect a server upgrade. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
|
The following comment was made by an LLM, it may be inaccurate: |
|
cc @thdxr @adamdotdevin — requesting review when you get a chance. Fix details: Embedded UI responses previously sent only The policy split mirrors the build output shape:
The proxied ( New test covers all three cache classes + the conditional-request path: 13/13 pass in |
|
Embedded UI responses send no cache directives, so a stale Head Fork leaf kvnloo#197 recorded fail→pass on That locks hashed assets as immutable and HTML as no-cache. It does not change the UI itself. |
|
Reviewed — the cache policy split is correct, and crucially the failure direction is safe: the Nit — const matches = ifNoneMatch?.split(",").map((v) => v.trim()).includes(etag) ?? ifNoneMatch === "*"
if (matches) return HttpServerResponse.empty({ status: 304, headers })Nit — 304 path does wasted work. FYI — bodies are re-read + re-hashed per request. Verified the claim about scope: Verdict: approve — correct policy split, safe failure direction, good test coverage across all three file classes plus the conditional-request path. cc @Hona @Brendonovich — could you review and approve? Fixes the stale- |
Issue for this PR
Closes #51858 (embedded UI served without a usable cache policy). Related: #51857 (SSE liveness), #51860 (connection/version surface), #41280, #50398 (stale UI reports), #48622.
Type of change
What does this PR do?
serveEmbeddedUIEffectreturned embedded web UI files with onlycontent-type(plus CSP for HTML) — noCache-Control, noETag. With no cache directives, browsers apply heuristic caching and can hold a staleindex.htmlacross server upgrades, pinning users to an old app bundle untilCtrl+F5.Response policy now, by file class:
cache-controlindex.html+ unhashed files (site.webmanifest, icons,oc-theme-preload.js, verbatimpublic/assets/fonts)no-cacheassets/<name>-<hash>.<ext>(Vite content-hashed outputs)public, max-age=31536000, immutablePlus:
ETag(sha256 of the response body) on every embedded response, andIf-None-Match→304so theno-cacherevalidations are cheap.x-opencode-versionheader so clients can detect a server upgrade from any UI response (building block for the version-mismatch UX proposed in [FEATURE]: web: connection health surface — stream status, reconnect progress, client/server version mismatch #51860).Scope note: the
disableEmbeddedWebUipath proxiesapp.opencode.aiand forwards the upstream host's headers; this PR only fixes the embedded (self-hosted) path, which is the deployment that reports stale UI most often.How did you verify your code works?
New test in
packages/opencode/test/server/httpapi-ui.test.ts("marks content-hashed assets immutable and everything else revalidating") asserts:index.html→no-cache+ etagassets/index-Ab1cD2e3.js(hashed) →immutableassets/Inter.ttf+site.webmanifest(unhashed) →no-cacheIf-None-Match→304with etag preservedbun test --only-failures ./test/server/httpapi-ui.test.ts— 13 pass / 0 fail;bun typecheckclean; no new lint warnings.Screenshots / screen recording
N/A — response headers only.
Checklist