Trim a studio URL in the address bar, or follow a link that has moved, and the front door of the forge answers a browser with a JSON blob.
Measured against the deployed forge, with a browser's own Accept header:
GET /trevato/forge/issues/99999 404 text/html 43,927 B ← the forge's own page
GET /trevato/nosuchrepo 404 text/html 43,911 B ← the forge's own page
GET /assets/forge-old.css 404 text/html 43,915 B ← the forge's own page
GET /trevato/forge/tells 404 application/json 70 B ← {"error":"no_route", …}
Everything that matches a route and finds nothing renders notFoundPage (src/web/chrome.ts:55). A path that matches no route falls through to noRoute (src/http/server.ts:1099, src/http/respond.ts:52) — written for API clients, correct for them, and the one 404 a person is most likely to reach by hand.
Acceptance
[ ] A request whose Accept prefers HTML and whose path is not under /api/ gets the forge's own not-found page — same shell, same header, same Go home — with status 404.
[ ] An API client is unchanged: /api/…, and anything asking for JSON, still gets {"error":"no_route","message":"no GET route for …"} with the same status and the same wording. A test holds both halves.
[ ] The distinction noRoute exists to draw — a typo'd URL is not a missing resource — survives wherever it is still readable; if the HTML page cannot carry it, say so in the pull rather than deleting it quietly.
[ ] The security floor is on the HTML response like every other page: CSP, x-content-type-options, referrer-policy.
[ ] bun test green.
Files I expect to change
src/http/server.ts (the fallthrough at :1099), src/http/respond.ts or src/web/chrome.ts (whichever ends up owning the choice), test/web.test.ts.
Risks
Auth is not resolved yet. The fallthrough runs beforeresolveAuth, so there is no Ctx to hand notFoundPage. Either resolve auth first for HTML requests — and then say in the pull what that costs on a path that is by definition rare — or render the page from a context built without a viewer. Do not make every request pay for the 404 path.
Do not sniff more than Accept. A request that asks for JSON gets JSON, whatever its path.
One line below, an unknown app hostname answers new Response("not found") in plain text (src/http/server.ts:1092). That is a different door — a preview host, not the forge — and it is fine to leave it; if you change it too, say why.
Trim a studio URL in the address bar, or follow a link that has moved, and the front door of the forge answers a browser with a JSON blob.
Measured against the deployed forge, with a browser's own
Acceptheader:Everything that matches a route and finds nothing renders
notFoundPage(src/web/chrome.ts:55). A path that matches no route falls through tonoRoute(src/http/server.ts:1099,src/http/respond.ts:52) — written for API clients, correct for them, and the one 404 a person is most likely to reach by hand.Acceptance
Acceptprefers HTML and whose path is not under/api/gets the forge's own not-found page — same shell, same header, same Go home — with status 404./api/…, and anything asking for JSON, still gets{"error":"no_route","message":"no GET route for …"}with the same status and the same wording. A test holds both halves.noRouteexists to draw — a typo'd URL is not a missing resource — survives wherever it is still readable; if the HTML page cannot carry it, say so in the pull rather than deleting it quietly.x-content-type-options,referrer-policy.bun testgreen.Files I expect to change
src/http/server.ts(the fallthrough at :1099),src/http/respond.tsorsrc/web/chrome.ts(whichever ends up owning the choice),test/web.test.ts.Risks
resolveAuth, so there is noCtxto handnotFoundPage. Either resolve auth first for HTML requests — and then say in the pull what that costs on a path that is by definition rare — or render the page from a context built without a viewer. Do not make every request pay for the 404 path.Accept. A request that asks for JSON gets JSON, whatever its path.new Response("not found")in plain text (src/http/server.ts:1092). That is a different door — a preview host, not the forge — and it is fine to leave it; if you change it too, say why.Suggested: Torque.