Repository navigation
fix(astro-renderer): status-page fixes after RFC-0043 PR 1 (#167) - #171
Merged
Merged
Conversation
- withErrorPage rewrites only Astro's own 500 HTML page; status pages (x-cloudhal-state), a themed 500 and any other 5xx pass and log nothing, so the starter's not-published 503 is no longer a 500 error page. - A store read failure in the state step answers 503 unavailable with Retry-After: 300, as /__cloudhal/health does. - The error page uses the locale and name of a site the request already read (loadedSite(), no store read), else English. - An invalid preview token on feeds, sitemaps, llms.txt and JSON paths answers statusPlain's 410; no response echoes the failure reason (x-cloudhal-preview-error: invalid). - The state step and error page live in status-entry.ts (unit-tested). - Docs: ADR-0012 preview fallback, the status catalog in the translate skill, DESIGN.md §1, the Weblate guide and ADR-0034, RFC-0043 §1.9 unknown hosts and §1.2/§1.8 wording. Closes #167 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Madraka <8898640+Madraka@users.noreply.github.com>
…failure (#167 review) - withErrorPage also rewrites Astro's empty 500 fallback (no body, no content type); a 500 with an explicit non-HTML type still passes. - The blog starter's catch-all rethrows when Astro renders it as the error route, so a page that throws gets the error page with a reference and a log line instead of the catch-all's 404 for /500. - The error page names the published snapshot's site node (SiteResolver.publicSite), the live row only before the first publish. - A failed multi-site hostname lookup is a store failure: health answers 503 { ok: false }, page and machine paths the English unavailable answer (503, Retry-After: 300). health moved to status-entry.ts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Madraka <8898640+Madraka@users.noreply.github.com>
…ts (#167 review) - resolveRequest no longer awaits publicSite: the error page's site is filled in the background (a failing render has already awaited the same snapshot load), and not at all for /__cloudhal/health, which reads only the pointer. - Tests: publicSite on an unreadable snapshot returns undefined; on workerd, a multi-site Worker whose site_hosts cannot be read answers health 503 { ok: false } and the English unavailable page and feed (fails with a 500 when the choose() catch is removed). - Wording: Astro renders its error route with the original path, so the starter's catch-all answered its 404 for that path. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Madraka <8898640+Madraka@users.noreply.github.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 #167: the follow-ups to RFC-0043 PR 1 (#162) from the adversarial review. Patch changeset for
@cloudhal/astro-renderer.The entry's status step and
errorpage moved fromcloudflare-entry.tsinto a new Worker-safe module,packages/astro-renderer/src/status-entry.ts(statusStep,withErrorPage,errorPage). It has nocloudflare:or Astro imports, so unit tests can run it.cloudflare-entry.tsshrinks by 20 lines.Issue items and how each was verified
withErrorPageturned the starter's not-published 503 into a 500errorpage with a false error logx-cloudhal-state(every renderer status page; the header is already public per RFC-0043 §1.2, so nothing new to strip), a theme'sx-cloudhal-error-page: theme, and any other 5xx pass unchanged and log nothing.test/status-entry.test.ts: the starter'sstatusPage("not-published")503 on aPOSTpasses untouched with no log; a theme's 502/503, a themed 500 and a plain-text 500 pass; Astro's 500 HTML becomes the loggederrorpage. On workerd,packages/cli/test/cloudflare-status.test.ts:POST /before the first publish now answers 503not-published. Before this fix it answered a 500.errorstatusStepcatches asiteStatefailure, logs it once, and answersunavailable: 503 withRetry-After: 300, the same as/__cloudhal/health. Page paths get the page, machine pathsstatusPlain.status-entry.test.ts"a store read failure is unavailable": the page path gets 503,Retry-After: 300andx-cloudhal-state: unavailable;/rss.xmlgets the same in plain text without the D1 error text.errorpage was always English and never named the siteruntime-workerd.tsstores the site row this request already read on thewithRequestSitecontext (loadedSite(), no store read). The entry's catch (now insidewithRequestSite) andwithErrorPagepass it on.statusInput("error")gives the locale from host or path prefix and the name only: no logo, no theme tokens (§1.6). Before the site is read, the page stays English.status-entry.test.ts"errorPage": a Turkish-default site giveslang="tr", the Turkish title and the escaped name, with no logo, no brand token and no error text./en/aboutgives English. Without a site the page is English and has no name.statusStepalso handles machine paths (statusPathKind:.xml/.txt/.json/.rss/.atom/.webmanifest, but notrobots.txt,/.well-known/, media or static assets).preview-expiredgetsstatusPlain: 410, text or JSON, in the site's language. Before the first publish, machine paths still get the theme's ownstatusPlain(§2.5).markInvalidPreviewreplaces the reason withx-cloudhal-preview-error: invalidplusno-storeon the media route and ingetSource(decision 12).status-entry.test.ts:/rss.xml,/sitemap.xml,/llms.txt,/feed.atomwith a forged token answer 410 plain text with no reason header;/search.jsonanswers 410 JSON;HEADhas no body; robots, media and/.well-known/are left alone; the marker test covers the header. On workerd,cloudflare-status.test.tsafter publish:/rss.xmland/sitemap.xmlwith a bad token answer 410text/plain, not<rss/<urlset.packages/astro-renderer/src/status-strings/,status.json) is added to thetranslateskill (files, import output,{name}-only rule, never hand-edit), DESIGN.md §1 "Translation workflow", the Weblate guide (component table and a note on thestatuscomponent) and ADR-0034 (Context and a dated amendment).pnpm design:lintReview fixes (second commit)
errorpagewithErrorPagenow also rewrites a 500 with no content type (Astro 7.4.7'srenderDefaultErrorfallback,new Response(null, { status })). A 500 with an explicit non-HTML type and a body still passes, and so do thex-cloudhal-stateand theme-marker cases. A theme's null-body 500 with a non-HTML type also becomes theerrorpage: Astro reroutes an empty 500 to its error route and the type is lost on the way. That is accepted. On workerd the starter's real path was worse than reported: when a page throws, Astro 7.4.7 renders its error route:/500matches the catch-all[...path].astro, which runs with the original path and answers its 404 page for that path. The starter now rethrowsAstro.props.errorwhen Astro renders it as the error route. Astro then falls back to the empty 500, which becomes theerrorpage.new Response(null, { status: 500 })becomes theerrorpage (state, HTML, CSP, reference, one log); a JSON 500 passes. Workerd: a fixturesrc/pages/broken.astrothat throws in its frontmatter gets 500,x-cloudhal-state: error, an HTML body withReference: <hex>and no error text, andharness.getLogs()hascloudhal: request failed (reference <same id>).errorpage used the live site rowSiteResolver.publicSite(resolution)returns the published snapshot's site node (source.site(), which the render loads anyway; a read failure givesundefined) forpublished, the live row only forunpublished, and nothing for a preview.loadedSite()is filled from it in the background, never awaited on the request path; health skips it and reads only the pointer. A render that throws has already awaited the same shared snapshot load, so the site is known by then.test/resolver.test.ts"publicSite": after publishing and then renaming the live site,publicSitestill gives the published name; before publishing it gives the live row; a preview givesundefined; a snapshot that cannot be read givesundefined, not a throw.routecatches aSiteDispatch.choose()failure and callshostLookupFailed. Health answers503 { ok: false }withno-store. GET/HEAD page paths get theunavailablepage and machine paths its plain answer (503,Retry-After: 300, English, no site name). Anything else gets a plain 503.healthmoved tostatus-entry.tswith the state injected, so it can be tested.status-entry.test.ts: aSiteDispatchoverCLOUDHAL_SITE_IDSwhose everysqlcall rejects, run throughhostLookupFailed. Health gets 503 JSON{ ok: false }with no-store;/aboutgets the page with 503,Retry-After: 300,unavailableand the English title;/rss.xmlgets the plain 503; aPOSTgets a plain 503. Plushealthitself (state shown, store failure gives 503). Wiring, on workerd (cloudflare-multisite.test.ts,CLOUDHAL_SITE_IDS): withsite_hostsrenamed away, after a purge reset the host map, health gets 503{ ok: false },/aboutgets the Englishunavailablepage (503,Retry-After: 300, no site name) and/rss.xmlgets the plain 503. With thechoose()catch removed, this case fails with a 500.Old theme copies: a site made from an earlier starter still shows its own 404 when a page throws. It needs the same two lines at the top of its
[...path].astro; the changeset says so. Thecloudhal-templaterepository needs the starter change at its next sync.Checks run (serially)
pnpm exec vitest run):status-entry.test.tsis new, with 16 cases);cloudflare*suite on workerd.pnpm typecheckpasses in both packages.astro buildruns inside the workerd test.pnpm lintexits 0. The two complexity warnings on the touched files (serveSite,SiteResolver.resolve) were there before this change.pnpm size:checkandpnpm design:lintpass.🤖 Generated with Claude Code