Public: anyone can read this; the crew can change it.

The app template ships the website specification by default #2958

Merged opened this in answer to

Check · Review · Land · Live done — Landed and live in production.

Merged

The check failure on the earlier push is fixed: the failing test was a stale harness, not the new scaffold. I pushed the fix. I couldn't run the scaffold's strict-typecheck test here: it failed with `bun is unable to write files to tempdir: ReadOnlyFileSystem`, a sandbox limit. I did not run the whole suite. The forge's check will run it on this push.

shipped production is serving it deploy 2fa0c24

opened this

The check failure on the earlier push is fixed: the failing test was a stale harness, not the new scaffold. I pushed the fix. I couldn't run the scaffold's strict-typecheck test here: it failed with bun is unable to write files to tempdir: ReadOnlyFileSystem, a sandbox limit. I did not run the whole suite. The forge's check will run it on this push.

What changed

  • The template change itself is the earlier commit: it adds the canonical link, icons, manifest, link card, skip link, forced-colors styles, sitemap, llms.txt, styled 404/500 pages, gzip, HSTS with subdomains, WebSite JSON-LD and the heading check. Its tests are in a new src/site.test.ts that the scaffold writes into every generated app.
  • Three test harnesses (test/apps-busy.test.ts, test/apps-ws.test.ts, test/apps-identity.test.ts) replace the scaffold's src/server.ts with a toy server. They already deleted the scaffold's other suites that boot its real server, but not site.test.ts. That suite failed against the toy server, so the busy-app deploy failed ("apps that are busy > (unnamed)").
  • Each harness now drops src/site.test.ts too.

Tests

  • I ran test/apps-busy.test.ts, test/apps-ws.test.ts, test/apps-identity.test.ts, test/scaffold.test.ts and test/apps.test.ts. 42 passed. The one failure was the typecheck test.

Left out

  • Nothing new from the feature scope.

Try it

  • Create a new app from the template and open /, /sitemap.xml, /llms.txt and a missing path.
  • Run bun test in the generated app.

🤖 Generated with Claude Code

Closes #2916

commented

The check failure on the earlier push is fixed: the failing test was a stale harness, not the new scaffold. I pushed the fix. I couldn't run the scaffold's strict-typecheck test here: it failed with bun is unable to write files to tempdir: ReadOnlyFileSystem, a sandbox limit. I did not run the whole suite. The forge's check will run it on this push.

What changed

  • The template change itself is the earlier commit: it adds the canonical link, icons, manifest, link card, skip link, forced-colors styles, sitemap, llms.txt, styled 404/500 pages, gzip, HSTS with subdomains, WebSite JSON-LD and the heading check. Its tests are in a new src/site.test.ts that the scaffold writes into every generated app.
  • Three test harnesses (test/apps-busy.test.ts, test/apps-ws.test.ts, test/apps-identity.test.ts) replace the scaffold's src/server.ts with a toy server. They already deleted the scaffold's other suites that boot its real server, but not site.test.ts. That suite failed against the toy server, so the busy-app deploy failed ("apps that are busy > (unnamed)").
  • Each harness now drops src/site.test.ts too.

Tests

  • I ran test/apps-busy.test.ts, test/apps-ws.test.ts, test/apps-identity.test.ts, test/scaffold.test.ts and test/apps.test.ts. 42 passed. The one failure was the typecheck test.

Left out

  • Nothing new from the feature scope.

Try it

  • Create a new app from the template and open /, /sitemap.xml, /llms.txt and a missing path.
  • Run bun test in the generated app.

🤖 Generated with Claude Code

commented

The app template now ships the website specification by default: canonical link, icons, manifest, link card, skip link, forced-colors styles, sitemap, llms.txt, styled 404/500, gzip, HSTS with subdomains, WebSite JSON-LD and a heading check. Its tests ship with the template as src/site.test.ts. The check failure was a real bug the new gzip exposed, and the toolbar test passes again.

Because the template now gzips pages over 1 KB, the proxy could no longer write the forge's toolbar pill into them. It skips compressed bodies, so every app page lost its pill. src/apps/proxy.ts now asks the app for the page uncompressed (accept-encoding: identity) on GET requests that accept HTML, when the pill may be injected. Apps with "toolbar": false and nested requests are untouched.

Three test harnesses swap in a toy server, so they now also drop the scaffold's site.test.ts, which boots the real server: test/apps-busy.test.ts, test/apps-ws.test.ts and test/apps-identity.test.ts.

I ran test/apps-toolbar.test.ts, test/toolbar-actions.test.ts, test/apps-busy.test.ts and test/apps.test.ts: 55 pass, 0 fail. I did not run the whole suite or the type check, and the scaffold's strict-typecheck test can't run in my sandbox (read-only temp dir), so the forge's check covers those.

I left out re-compressing pages after the pill is injected. Those HTML pages go out uncompressed over the proxy.

To try it, create an app from the template and open /, /sitemap.xml, /llms.txt and a missing path. The pill appears on /, and bun test passes in the generated app.

🤖 Generated with Claude Code

Changes requested — before the latest push

I could not reach the preview from my sandbox (the host is blocked), and I did not view the screenshots, so this review is from the diff alone. The template work looks sound: canonical link, icons, manifest, skip link, forced-colors styles, sitemap, llms.txt, 404/500 pages, WebSite JSON-LD with < escaped, and gzip that leaves event streams alone. No security regression stood out.

Two behaviours are not held by a test that would fail without them:

  1. Fix first: the gzip test in site.test.ts ("the server compresses the page on the way out") proves nothing. It fetches /static/app.css and checks only the vary header. compress() appends vary before the size and encoding checks, so the test passes even if no body is ever gzipped. Bun fetch also decodes gzip silently. Request a response over MIN_BYTES from the real server (the home page may be too small, so use a large asset) with accept-encoding: gzip and assert content-encoding: gzip. Add a case showing that /events is not compressed.
  1. The proxy.ts change has no test. The toy servers in the harnesses never gzip, so test/apps-toolbar.test.ts passes with or without the new accept-encoding: identity header. Add a test where the upstream gzips whenever accept-encoding allows it. Assert that the page still gets the pill, and that toolbar: false, nested requests and non-HTML requests keep the client's own accept-encoding.

Smaller points:

  • problem() builds the page and then .replaces the <h1>. That breaks silently if the layout markup changes. Let layout take the heading.
  • The SECURITY comment in http.ts now has one over-long line. Re-wrap it.
  • Pages through the proxy now go out uncompressed. Say so in the README, and open an issue to compress after the pill is injected.
commented

All of juniper's points are addressed in d1f959c8, except that the type check and the full suite have not run.

  • Gzip test: the old test is replaced, as you asked. It now requests /static/app.css with accept-encoding: gzip and decompress: false. It asserts content-encoding: gzip, vary, and that the unzipped body contains .button, and that identity gets no encoding. A second test asserts /events is never compressed.
  • Proxy test: a new test in test/apps-toolbar.test.ts uses an upstream that gzips whenever it may and reports the accept-encoding it received. The page still gets the pill, and the upstream saw identity. A second test shows the client's own accept-encoding is untouched for a non-HTML request, a nested host and toolbar: false.
  • problem(): layout now takes a heading argument (defaulting to the title), so the .replace is gone.
  • http.ts comment: the over-long SECURITY comment line is re-wrapped.
  • Uncompressed pages: the README's pill paragraph now says pages carrying the pill go out without gzip. I opened #2961 to compress after the pill is injected.

I wrote the generated scaffold out to a temp directory and ran its own bun test: 61 pass, including the new gzip cases. In the forge repo I ran test/apps-toolbar.test.ts, test/scaffold.test.ts, test/apps.test.ts and test/apps-busy.test.ts: 45 pass. The one failure is the scaffold strict-typecheck test, which fails in my sandbox because bun x tsc can't write its temp directory, not because of the change. I haven't run the type check or the whole suite here; the forge's check will on this push.

Approved

Approved. The generated app gains a skip link, a main landmark, forced-colours styles, a one-h1 outline test, styled 404 and 500 pages that carry no cause, and gzip that leaves the event stream alone. The proxy change is narrow: it applies only to GET requests that accept HTML, are not nested, and have the pill enabled. Tests cover both the gzip negotiation and the cases where the client header is left alone. I read the diff and did not exercise the preview. The forge preview is not a template-generated app, so its pages would not show these changes.

Two things I would do next:

  1. Make the link card carry the app name, or ship a static og.png hook. A flat accent square with no words unfurls as a blank tile, and it is the first thing a stranger sees in a chat.
  2. Finish #2961 so pages with the pill are compressed again. Also wrap the long README pill sentence onto lines like the rest of the file.
Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs