Skip to content

dogfood: scaffold serves public at /public/* so the documented /sw.js service worker 404s #830

Description

@vivek7405

Problem

In a generated scaffold, the opt-in service worker (#271) cannot register at the path webjs documents. Static assets under public/ serve ONLY at /public/*, but every service-worker surface registers /sw.js at the site root:

  • agent-docs/service-worker.md (the registration snippet + "Registered from /sw.js, the worker's scope is the site root /").
  • The shipped packages/cli/templates/public/sw.js self-registration comment (/sw.js?v=<build>).
  • Root AGENTS.md app layout documents public/* as "static assets, served at /<name>" (i.e. root), which itself contradicts the actual /public/* serving.

Empirically, in a freshly generated full-stack app: GET /sw.js -> 404, GET /public/sw.js -> 200 (same for /offline.html vs /public/offline.html). So a user who follows the documented recipe registers a 404 and gets no worker. Worse, the only path that DOES serve (/public/sw.js) gives the worker a /public/* scope by default, so it would not control the site root even if registered, unless the response carries Service-Worker-Allowed: /.

Found while reviewing the scaffold feature gallery (#826): the new app/features/service-worker demo had to choose a path, and this inconsistency surfaced. The demo now uses /sw.js (matching the canonical docs + correct scope), which will 404 until this is fixed.

Design / approach

Serve the small set of "root assets" a webjs app expects at the site root, from public/. The framework already special-cases /favicon.ico -> public/favicon.ico in the same branch, so extend that to the service-worker + PWA root files: /sw.js, /offline.html, /robots.txt, /manifest.webmanifest (the exact set can match what the metadata routes + service-worker feature assume). For /sw.js, also send Service-Worker-Allowed: / so the worker controls the whole origin regardless of the served path. Decide and document ONE canonical model, then make AGENTS.md, agent-docs/service-worker.md, the shipped public/sw.js, and the scaffold layout all agree with it. (The scaffold layout currently uses /public/tailwind-browser.js, which matches the actual /public/* serving, so the two conventions coexist inconsistently today.)

Implementation notes (for the implementing agent)

  • Where to edit: packages/server/src/dev.js around L1828-1839 (the path.startsWith('/public/') || path === '/favicon.ico' static branch) and the equivalent path in the prod listener(s) (packages/server/src/listener-*.js / the serve path) so dev and prod agree. Mirror the existing /favicon.ico -> /public/favicon.ico remap for the root-asset set, and add the Service-Worker-Allowed: / header on the /sw.js response.
  • Landmines: the existing branch has a traversal guard (decoded .. containment under appDir/public/); keep any new remap inside that guard. Service-worker SCOPE is the real trap: serving the bytes at root is necessary but the browser also scopes by the request path, so the Service-Worker-Allowed header (or serving the file physically at root) is required for site-wide scope. Bun listener (listener-bun.js) has its own static path; fix both (runtime parity, Support both Bun and Node runtimes (first-class create + run) #508) and add a test/bun/* assertion.
  • Invariants: keep it no-build; do not move public/ files at scaffold time (they stay under public/, only the SERVED URL changes).
  • Tests + docs: add a handle: /sw.js serves public/sw.js at root with Service-Worker-Allowed unit test (packages/server/test/dev/dev-handler.test.js), a Bun parity assertion, and reconcile AGENTS.md + agent-docs/service-worker.md + packages/cli/templates/public/sw.js + the scaffold gallery app/features/service-worker demo to the chosen canonical path.

Acceptance criteria

  • GET /sw.js serves public/sw.js (200) in a generated scaffold, in dev AND prod, on Node AND Bun.
  • The /sw.js response carries Service-Worker-Allowed: / (or the file is physically root-scoped) so the worker controls the whole origin.
  • agent-docs/service-worker.md, root AGENTS.md, the shipped public/sw.js, and the app/features/service-worker demo all describe the SAME, working path.
  • A counterfactual proves the test fires (revert the remap -> /sw.js 404s -> test red).
  • Docs / AGENTS.md updated to state the canonical public-root-asset serving model.

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Fields

No fields configured for issues without a type.

Projects

Status
Done

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions