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

The home's live fragments /live/apps and /live/on-you name their Server-Timing phases: session, read, render #5403

Merged opened this

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

Merged

scout/forge:scout/5400-the-home-s-live-fragments-live-apps-and → main 3 commits fork

  • Merged. scout/5400-the-home-s-live-fragments-live-apps-and was merged into main as 7623e55.

Review

Sign in to review this change.
4 files changed +76 −9
scripts/shards.ts +1 −1
@@ -454,7 +454,7 @@ export function parseShardSpec(
454454 | { ok: true; none: true }
455455 | { ok: false; error: string } {
456456 if (spec === undefined || spec.trim() === "") return { ok: true, none: true };
457 const m = /^([1-9][0-9]{0,2})\/([1-9][0-9]{0,2})$/.exec(spec.trim());
457 const m = /^([1-9][0-9]{0,3})\/([1-9][0-9]{0,3})$/.exec(spec.trim());
458458 if (!m)
459459 return {
460460 ok: false,
src/web/studio/home.ts +15 −6
@@ -77,8 +77,8 @@ const RAIL_APPS = 8;
7777 * name and how production is — and the verbs that were the shelf's head.
7878 * The apps' full rows are `/apps`.
7979 */
80 function yourApps(ctx: Ctx, user: User): Html {
81 const { repos } = writableAppsOf(ctx, user);
80 function yourApps(ctx: Ctx, user: User, apps = writableAppsOf(ctx, user)): Html {
81 const { repos } = apps;
8282 const runtime = appsRuntime(ctx.app);
8383 const records = appsForRepos(
8484 ctx.app.db,
@@ -144,9 +144,9 @@ function yourApps(ctx: Ctx, user: User): Html {
144144 * so an event fetches that region — your apps, or what is on you — and not
145145 * the page; the fragment answers with the same element, id and all.
146146 */
147 const appsRegion = (ctx: Ctx, user: User): Html =>
147 const appsRegion = (ctx: Ctx, user: User, apps?: ReturnType<typeof writableAppsOf>): Html =>
148148 html`<div id="live-apps" data-live="${APPS_LIVE}" data-live-src="/live/apps">
149 ${yourApps(ctx, user)}
149 ${yourApps(ctx, user, apps)}
150150 </div>`;
151151
152152 export const onYouRegion = (
@@ -168,7 +168,12 @@ export const onYouRegion = (
168168 export function liveAppsFragment(ctx: Ctx): Response {
169169 const user = ctx.auth.user;
170170 if (!user) return fragment(html``, { status: 404 });
171 return fragment(appsRegion(ctx, user), { request: ctx.request });
171 const rendering = performance.now();
172 notePhase(ctx.request, "session", takeSessionTime(ctx.request));
173 const apps = timePhase(ctx.request, "apps", () => writableAppsOf(ctx, user));
174 const body = fragment(appsRegion(ctx, user, apps), { request: ctx.request });
175 notePhaseRest(ctx.request, "render", rendering, ["apps"]);
176 return body;
172177 }
173178
174179 /**
@@ -183,7 +188,11 @@ export function onYouAnswer(ctx: Ctx, user: User, note?: string): Response {
183188 export function liveOnYouFragment(ctx: Ctx): Response {
184189 const user = ctx.auth.user;
185190 if (!user) return fragment(html``, { status: 404 });
186 return fragment(onYouRegion(ctx, user), { request: ctx.request });
191 const rendering = performance.now();
192 notePhase(ctx.request, "session", takeSessionTime(ctx.request));
193 const body = fragment(onYouRegion(ctx, user), { request: ctx.request }); // needsYouSection times "asks"
194 notePhaseRest(ctx.request, "render", rendering, ["asks"]);
195 return body;
187196 }
188197
189198 /**
test/live-fragments-timing.test.ts added +60
@@ -0,0 +1,60 @@
1 /** The home's live fragments name their Server-Timing phases (#5400). */
2 import { afterAll, beforeAll, expect, test } from "bun:test";
3 import { mkdtempSync, rmSync } from "node:fs";
4 import { tmpdir } from "node:os";
5 import { join } from "node:path";
6 import { createApp, type App } from "../src/app.ts";
7 import { createUser } from "../src/core/users.ts";
8 import { serve, type Servers } from "../src/http/server.ts";
9 import { proveAll } from "./helpers/verified.ts";
10 import { quietLogs } from "./helpers/globals.ts";
11
12 quietLogs();
13
14 let root = "";
15 let app: App;
16 let servers: Servers;
17 let cookie = "";
18
19 beforeAll(async () => {
20 root = mkdtempSync(join(tmpdir(), "forge-live-timing-"));
21 app = createApp({ dataDir: root, port: 0 });
22 const u = await createUser(app.db, {
23 origin: "seed",
24 trusted: true,
25 username: "alice",
26 email: "[email protected]",
27 password: "a-good-passphrase",
28 adminIfFirst: true,
29 });
30 if (!u.ok) throw new Error(u.error);
31 proveAll(app);
32 servers = await serve(app);
33 const signin = await fetch(`${servers.url}/signin`, {
34 method: "POST",
35 headers: { origin: servers.url },
36 body: new URLSearchParams({ username: "alice", password: "a-good-passphrase" }),
37 redirect: "manual",
38 });
39 cookie = (signin.headers.get("set-cookie") ?? "").split(";")[0]!;
40 });
41 afterAll(() => {
42 servers.stop();
43 app.close();
44 rmSync(root, { recursive: true, force: true });
45 });
46
47 const phases = (res: Response) =>
48 (res.headers.get("server-timing") ?? "").split(",").map((p) => p.trim().split(";")[0]);
49
50 test.each([
51 ["/live/apps", "apps", 'id="live-apps"'],
52 ["/live/on-you", "asks", 'id="live-on-you"'],
53 ])("%s names session, %s and render", async (path, read, marker) => {
54 const res = await fetch(`${servers.url}${path}`, { headers: { cookie } });
55 const body = await res.text();
56 expect(res.status).toBe(200);
57 expect(body).toContain(marker);
58 const names = phases(res);
59 for (const name of ["app", "session", read, "render"]) expect(names).toContain(name);
60 });
test/server-timing-sweep.test.ts −2
@@ -25,8 +25,6 @@ const UNNAMED: Array<[path: string, issue: string]> = [
2525 ["/alice/forge/changes", "#3323"],
2626 ["/alice/forge/map", "#3320"],
2727 ["/alice/forge/tells", "#3322"],
28 ["/live/apps", "#3390"],
29 ["/live/on-you", "#3390"],
3028 ["/signin/parent", "#3390"],
3129 ["/signin/parent/callback", "#3390"],
3230 ];

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs