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

/favicon.ico and /apple-touch-icon.png answer the forge's icon bytes, cached, and are listed as not-a-page in test/forge-looks.test.ts #8221

Merged opened this

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

Merged

scout/forge:scout/7757-favicon-ico-and-apple-touch-icon-png-ans → main 3 commits fork

  • Merged. scout/7757-favicon-ico-and-apple-touch-icon-png-ans was merged into main as 5aa935d.

Review

Sign in to review this change.
6 files changed +65 −1
src/core/reserved.ts +3
@@ -93,6 +93,9 @@ export const SYSTEM_NAMES: readonly string[] = [
9393 "resilience",
9494 "origins",
9595 "robots.txt",
96 "favicon.ico",
97 "apple-touch-icon.png",
98 "apple-touch-icon-precomposed.png",
9699 "llms.txt",
97100 "runs",
98101 "search",
src/http/server.ts +3
@@ -190,6 +190,7 @@ import { ceilingAct, grantAct, modelAdd, modelPage } from "../web/pages/model.ts
190190 import {
191191 changePassword,
192192 iconPng,
193 appleTouchIcon,
193194 favicon,
194195 llmsTxt,
195196 releasesJson,
@@ -717,6 +718,8 @@ export function buildRouter(): Router<Handler> {
717718 router.add("GET", "/opensearch.xml", openSearch);
718719 router.add("GET", "/icons/:spec.png", iconPng);
719720 router.add("GET", "/favicon.ico", favicon);
721 router.add("GET", "/apple-touch-icon.png", appleTouchIcon);
722 router.add("GET", "/apple-touch-icon-precomposed.png", appleTouchIcon);
720723 // The highlighter bundle, content-addressed. A stale hash is a 404 rather
721724 // than a redirect: the page that asked for it names the hash it was built
722725 // with, so a miss means an old tab, and an old tab should reload.
src/web/standards.ts +8
@@ -303,6 +303,14 @@ function pngResponse(png: Uint8Array): Response {
303303 });
304304 }
305305
306 /** The icon a browser, a home screen or an unfurler asks for by habit. */
307 function fixedIcon(spec: string) {
308 return (ctx: Ctx): Response => iconPng({ ...ctx, params: { ...ctx.params, spec } });
309 }
310
311 /** GET /apple-touch-icon.png and the -precomposed twin — the 180 icon. */
312 export const appleTouchIcon = fixedIcon("forge-180");
313
306314 /** `Running 0.7.51 at a1b2c3d`: which build these instructions describe. */
307315 function runningSentence(): string {
308316 const r = running();
test/design-audit.test.ts +1 −1
@@ -11,7 +11,7 @@ const doc = readFileSync("docs/design/audit.md", "utf8");
1111
1212 /** Routes that answer a file, a fragment, a redirect hop or a callback. */
1313 const NOT_PAGES =
14 /^\/(assets\/|\.well-known\/|icons\/|og\/|live\/|__forge\/|go\/|events$|system\/stream$|settings\/teams\/stream$|sw\.js$|robots\.txt$|favicon\.ico$|sitemap\.xml$|manifest\.webmanifest$|opensearch\.xml$|llms\.txt$|releases\.json$|audit\.csv$|fleet\/graph\/live$|fleet\/graph\.csv$|settings\/origins\/callback$|home\/archived\/|settings\/backups\/live$)/;
14 /^\/(assets\/|\.well-known\/|icons\/|og\/|live\/|__forge\/|go\/|events$|system\/stream$|settings\/teams\/stream$|sw\.js$|robots\.txt$|favicon\.ico$|apple-touch-icon(-precomposed)?\.png$|sitemap\.xml$|manifest\.webmanifest$|opensearch\.xml$|llms\.txt$|releases\.json$|audit\.csv$|fleet\/graph\/live$|fleet\/graph\.csv$|settings\/origins\/callback$|home\/archived\/|settings\/backups\/live$)/;
1515
1616 const getRoutes = [
1717 ...server.matchAll(/router\.add\(\s*"GET",\s*"([^"]+)"/g),
test/favicon.test.ts added +48
@@ -0,0 +1,48 @@
1 /** The icons a browser, a home screen and an unfurler ask for by habit. */
2
3 import { afterAll, beforeAll, expect, test } from "bun:test";
4 import { mkdtemp, rm } from "node:fs/promises";
5 import { tmpdir } from "node:os";
6 import { join } from "node:path";
7 import { createApp, type App } from "../src/app.ts";
8 import { serve, type Servers } from "../src/http/server.ts";
9
10 let dir: string;
11 let app: App;
12 let servers: Servers;
13
14 beforeAll(async () => {
15 dir = await mkdtemp(join(tmpdir(), "forge-favicon-"));
16 app = createApp({ dataDir: dir, port: 0 });
17 servers = await serve(app);
18 });
19
20 afterAll(async () => {
21 servers.stop();
22 app.close();
23 await rm(dir, { recursive: true, force: true });
24 });
25
26 const CASES: [string, string][] = [
27 ["/favicon.ico", "/icons/forge-32.png"],
28 ["/apple-touch-icon.png", "/icons/forge-180.png"],
29 ["/apple-touch-icon-precomposed.png", "/icons/forge-180.png"],
30 ];
31
32 for (const [path, twin] of CASES) {
33 test(`${path} answers the bytes of ${twin}, cached`, async () => {
34 const r = await fetch(`${servers.url}${path}`);
35 const want = await fetch(`${servers.url}${twin}`);
36 expect(r.status).toBe(200);
37 expect(r.headers.get("content-type")).toBe("image/png");
38 expect(r.headers.get("cache-control")).toBe(want.headers.get("cache-control") ?? "x");
39 expect(new Uint8Array(await r.arrayBuffer())).toEqual(new Uint8Array(await want.arrayBuffer()));
40 });
41
42 test(`HEAD ${path} has no body`, async () => {
43 const r = await fetch(`${servers.url}${path}`, { method: "HEAD" });
44 expect(r.status).toBe(200);
45 expect(r.headers.get("content-type")).toBe("image/png");
46 expect((await r.arrayBuffer()).byteLength).toBe(0);
47 });
48 }
test/forge-looks.test.ts +2
@@ -45,6 +45,8 @@ const NOT_A_PAGE: Record<string, string> = {
4545 "/sign-in": "redirects to /signin",
4646 "/mcp": "a protocol endpoint, answers 405 to a GET",
4747 "/_measure": "the page-load beacon, a POST endpoint",
48 "/apple-touch-icon.png": "an icon, not a page",
49 "/apple-touch-icon-precomposed.png": "an icon, not a page",
4850 "/events": "a live event stream that never ends",
4951 };
5052

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs