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

The new forge page's Server-Timing names its phases: session, each read, render #3471

Merged opened this

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

Merged

scout/forge:scout/3460-the-new-forge-page-s-server-timing-names → main 1 commit fork 2 threads · 2 unresolved

  • Merged. scout/3460-the-new-forge-page-s-server-timing-names was merged into main as a773625.

Review

Sign in to review this change.
2 files changed +79 −9
src/web/pages/new-forge.ts +34 −9
@@ -13,6 +13,7 @@ import { listAgents } from "../../core/agents.ts";
1313 import { listReposVisibleTo } from "../../core/repos.ts";
1414 import type { User } from "../../core/users.ts";
1515 import type { Ctx } from "../../http/context.ts";
16 import { notePhase, takeSessionTime, timePhase } from "../../core/route_timing.ts";
1617 import { childHost, childUrlOf, noRoom, spawnDoor, type Door } from "../../spawn/door.ts";
1718 import {
1819 LEFT_BEHIND,
@@ -94,14 +95,37 @@ function checks(
9495 )}`;
9596 }
9697
98 /** What the form reads before it draws: room, then (when there is room) the repositories and the crew. */
99 interface Reads {
100 readonly full: string | undefined;
101 readonly repos: ReturnType<typeof listReposVisibleTo>;
102 readonly crew: ReturnType<typeof listAgents>;
103 }
104
105 function readsFor(ctx: Ctx, user: User | undefined): Reads {
106 const full = timePhase(ctx.request, "room", () => noRoom(ctx.app));
107 if (full) return { full, repos: [], crew: [] };
108 // A stranger sees only public repositories, and no crew: nothing private
109 // travels to a child they have not claimed.
110 const repos = timePhase(ctx.request, "repos", () =>
111 listReposVisibleTo(ctx.app.db, user, { limit: 200 }),
112 );
113 const crew =
114 user === undefined
115 ? []
116 : timePhase(ctx.request, "crew", () => listAgents(ctx.app.db));
117 return { full, repos, crew };
118 }
119
97120 function formView(
98121 ctx: Ctx,
99122 user: User | undefined,
100123 values: Values,
101124 errors: Errors,
102125 plan: Html | null,
126 reads: Reads,
103127 ): Html {
104 const full = noRoom(ctx.app);
128 const { full, repos, crew } = reads;
105129 if (full)
106130 return html`<div class="narrow-wide">
107131 ${pageHead("New forge")}
@@ -112,11 +136,7 @@ function formView(
112136 : null}`,
113137 )}
114138 </div>`;
115 // A stranger sees only public repositories, and no crew: nothing private
116 // travels to a child they have not claimed.
117139 const anon = user === undefined;
118 const repos = listReposVisibleTo(ctx.app.db, user, { limit: 200 });
119 const crew = anon ? [] : listAgents(ctx.app.db);
120140 return html`<div class="narrow-wide">
121141 ${pageHead("New forge", {
122142 sub: anon
@@ -369,11 +389,16 @@ const asBool = (form: Form, name: string): boolean =>
369389 export function newForgePage(ctx: Ctx): Response {
370390 const user = ctx.auth.user;
371391 if (user && user.kind !== "user") return notFoundPage(ctx);
372 return page({
392 notePhase(ctx.request, "session", takeSessionTime(ctx.request));
393 const reads = readsFor(ctx, user);
394 const rendering = performance.now();
395 const response = page({
373396 ctx,
374397 title: "New forge",
375 body: formView(ctx, user, EMPTY, {}, null),
398 body: formView(ctx, user, EMPTY, {}, null, reads),
376399 });
400 notePhase(ctx.request, "render", performance.now() - rendering);
401 return response;
377402 }
378403
379404 // POST /forges/new — `do=plan` shows the plan; anything else spawns.
@@ -417,7 +442,7 @@ export async function newForgeSubmit(ctx: Ctx): Promise<Response> {
417442 {
418443 ctx,
419444 title: "New forge",
420 body: formView(ctx, user, values, errorsOf(door), null),
445 body: formView(ctx, user, values, errorsOf(door), null, readsFor(ctx, user)),
421446 },
422447 door.status === 422 ? 422 : door.status,
423448 );
@@ -425,7 +450,7 @@ export async function newForgeSubmit(ctx: Ctx): Promise<Response> {
425450 return page({
426451 ctx,
427452 title: "New forge",
428 body: formView(ctx, user, values, {}, planView(ctx, user, values, door.body)),
453 body: formView(ctx, user, values, {}, planView(ctx, user, values, door.body), readsFor(ctx, user)),

This line is long and the plan re-render has no session/render phases, unlike GET. Wrap it, and either time it too or leave it deliberately.

429454 });
430455 const response = page(
431456 { ctx, title: "New forge", body: madeView(door.body) },
test/new-forge-timing.test.ts added +45
@@ -0,0 +1,45 @@
1 /** /forges/new's Server-Timing names its phases: session, room, repos, crew, render (#3460). */

Issue number reads #3460; this pull is #3471. Add an anonymous request asserting room and repos but no crew.

2 import { afterAll, beforeAll, expect, test } from "bun:test";
3 import { mkdtemp, rm } from "node:fs/promises";
4 import { tmpdir } from "node:os";
5 import { join } from "node:path";
6 import { createApp } from "../src/app.ts";
7 import { createToken } from "../src/core/tokens.ts";
8 import { createUser } from "../src/core/users.ts";
9 import { serve } from "../src/http/server.ts";
10
11 let dir = "";
12 let app: ReturnType<typeof createApp>;
13 let servers: Awaited<ReturnType<typeof serve>>;
14 let bearer = "";
15
16 beforeAll(async () => {
17 dir = await mkdtemp(join(tmpdir(), "forge-new-forge-timing-"));
18 app = createApp({ dataDir: dir, port: 0 });
19 servers = await serve(app);
20 const made = await createUser(app.db, {
21 origin: "seed", trusted: true, username: "alice", email: "[email protected]",
22 password: "a-good-passphrase", adminIfFirst: true,
23 });
24 if (!made.ok) throw new Error(made.error);
25 bearer = createToken(app.db, made.value.id, "t", "admin").token;
26 });
27
28 afterAll(async () => {
29 servers.stop();
30 app.close();
31 await rm(dir, { recursive: true, force: true });
32 });
33
34 test("the new forge page names session, room, repos, crew and render once each", async () => {
35 const res = await fetch(servers.url + "/forges/new", { headers: { authorization: `Bearer ${bearer}` } });
36 expect(res.status).toBe(200);
37 expect(await res.text()).toContain("New forge");
38 const parts = (res.headers.get("server-timing") ?? "").split(", ");
39 const names = parts.map((p) => p.split(";")[0]!);
40 for (const n of ["session", "room", "repos", "crew", "render"]) {
41 expect(names.filter((x) => x === n).length).toBe(1);
42 const part = parts.find((p) => p.startsWith(`${n};`))!;
43 expect(Number(/dur=([\d.]+)/.exec(part)?.[1])).not.toBeNaN();
44 }
45 });

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs